Skip to content

Audit remediations for Inflow Vault#388

Open
p-offtermatt wants to merge 7 commits intomainfrom
ph/audit-comments
Open

Audit remediations for Inflow Vault#388
p-offtermatt wants to merge 7 commits intomainfrom
ph/audit-comments

Conversation

@p-offtermatt
Copy link
Copy Markdown
Member

@p-offtermatt p-offtermatt commented Feb 4, 2026

Summary

Addresses findings from the Q1 2026 Control Center & Vault audit.

Audit Findings Addressed

Critical:

  • Share price manipulation: Pre-mint 1,000,000 shares during vault instantiation. Instantiator must send collateral that converts to at least 1,000,000 base tokens. This prevents attackers from inflating share prices via direct donation attacks.

Medium:

  • Deployment tracking accounting gaps: Sync DEPLOYED_AMOUNT when toggling between Tracked and NotTracked modes. Queries adapter position and adjusts accounting accordingly.
  • Adapter unregistration fund loss: Block unregistration of adapters with non-zero positions. Prevents funds from vanishing from pool value calculations.

Low:

  • Silent adapter query failures: Track failed adapter queries and include them as response attributes (failed_adapter_queries). Operators now have visibility into malfunctioning adapters.

Informational:

  • Zero shares minting: Add explicit check before minting to fail early with clear error message instead of letting tokenfactory reject it.
  • Config validation: Validate max_withdrawals_per_user >= 1 on both instantiation and config updates. Prevents withdrawal freeze.
  • Adapter trust assumptions: Created ADAPTER_SPEC.md documenting required adapter behaviors and trust assumptions.

Additional Fixes

  • Withdrawal cancellation hedging: Take minimum of shares burned and calculated shares when cancelling a withdrawal. Prevents users from "hedging" against ratio decreases by creating and cancelling withdrawals.

Files Changed

  • contracts/inflow/vault/src/contract.rs - Core remediation logic
  • contracts/inflow/vault/src/msg.rs - Added initial_shares_recipient field
  • contracts/inflow/vault/src/testing.rs - Tests for remediations
  • contracts/inflow/vault/src/testing_adapters.rs - Adapter-related tests
  • contracts/inflow/vault/ADAPTER_SPEC.md - New adapter interface documentation
  • docs/audit/ - Audit documentation
  • docs/plans/audit-remediation-plan.md - Implementation plan

Test plan

  • Unit tests added for all remediations
  • Run make test-unit to verify all tests pass
  • Run make clippy for linting
  • Run make compile to regenerate artifacts

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings February 4, 2026 15:26
@p-offtermatt p-offtermatt requested a review from a team as a code owner February 4, 2026 15:26
@claude
Copy link
Copy Markdown

claude Bot commented Feb 4, 2026

Code Review - PR #388: Audit Remediations for Inflow Vault

I've completed a comprehensive review of this PR addressing the Q1 2026 audit findings. Overall, the remediations are well-implemented and production-ready with strong test coverage.


✅ Successfully Addressed Findings

Critical: Share Price Manipulation

Status: Fixed

The pre-minting mechanism effectively prevents share price inflation attacks:

  • Enforces MINIMUM_INITIAL_SHARES = 1_000_000 at instantiation
  • Validates deposit amounts would mint non-zero shares
  • Reply handler creates initial shares atomically

contracts/inflow/vault/src/contract.rs:64-121

Medium: Deployment Tracking Synchronization

Status: Fixed

The toggle synchronization logic is robust:

  • Queries live adapter position before mode change
  • Directionally updates Control Center (Add/Subtract) based on transition
  • Atomic operation prevents inconsistent state

contracts/inflow/vault/src/contract.rs:1364-1440

Medium: Adapter Unregistration Protection

Status: Fixed

Position check prevents fund loss:

  • Queries adapter position before unregistration
  • Rejects if non-zero with clear error message
  • Only removes from ADAPTERS map if validation passes

contracts/inflow/vault/src/contract.rs:1293-1327

Low: Silent Adapter Query Failures

Status: Mitigated

Failed queries are now tracked and reported:

  • AllocationResult struct tracks failed_adapters list
  • Response attributes include failed_adapter_queries for monitoring
  • Graceful degradation continues with working adapters

contracts/inflow/vault/src/contract.rs:1750-1840

Informational: Zero Shares & Config Validation

Status: Fixed

Edge cases properly handled with early validation and clear error messages.


🔍 Issues & Recommendations

1. Token Ratio Provider Trust (Medium Priority)

Location: contracts/inflow/vault/src/contract.rs:93-112

The pre-minting validation depends on token_info_provider.RatioToBaseToken. If this provider returns inflated ratios, the 1M minimum could be bypassed.

Example: If ratio = 1000:1, depositor only needs 1000 tokens instead of 1M base tokens.

Recommendation:

  • Validate the token provider contract address during deployment
  • Consider adding sanity checks on ratio ranges (e.g., ratio must be between 0.1 and 10)
  • Document expected ratio behavior in ADAPTER_SPEC.md
// Suggested validation
if ratio > Decimal::from_ratio(10u128, 1u128) || ratio < Decimal::from_ratio(1u128, 10u128) {
    return Err(new_generic_error("token ratio out of expected range"));
}

2. Adapter Query Failure During Admin Operations (Low Priority)

Locations:

  • unregister_adapter: contracts/inflow/vault/src/contract.rs:1309
  • set_adapter_deployment_tracking: contracts/inflow/vault/src/contract.rs:1391

If query_single_adapter_position fails, these operations are completely blocked. This prevents unregistering a malfunctioning adapter.

Recommendation:

  • Add an optional force: bool flag to admin operations
  • When force=true, skip position query (but require extra governance approval)
  • Log warning attributes when force is used

3. Race Condition with Tracked Adapters (Documented)

The code documents this limitation in the DeploymentTracking enum:

Using Tracked with AllocationMode::Automated creates a race condition where automated deposits/withdrawals update deployed amount concurrently with manual submissions.

Recommendation:

  • Default new adapters to NotTracked mode
  • Implement timestamp-based staleness detection in Control Center
  • Add warning in ADAPTER_SPEC.md about this footgun

4. No On-Chain Failed Adapter Registry (Low Priority)

Failed adapter queries are reported via events but not persisted on-chain. Consistently-failing adapters aren't automatically disabled.

Recommendation:

// Add state tracking
const ADAPTER_FAILURE_COUNT: Map<String, u32> = Map::new("adapter_failures");
const DISABLED_ADAPTERS: Map<String, Empty> = Map::new("disabled_adapters");

// In calculate_venues_allocation
Err(_) => {
    failed_adapters.push(adapter_name.clone());
    let count = ADAPTER_FAILURE_COUNT
        .may_load(deps.storage, adapter_name.clone())?
        .unwrap_or(0) + 1;
    
    if count >= 10 {  // Threshold
        DISABLED_ADAPTERS.save(deps.storage, adapter_name.clone(), &Empty {})?;
    }
    ADAPTER_FAILURE_COUNT.save(deps.storage, adapter_name, &count)?;
}

// Add admin function to re-enable adapters

5. Test Coverage Gaps

Missing negative-path tests:

  • Adapter query failure during unregister_adapter operation
  • Adapter query failure during set_adapter_deployment_tracking operation
  • Token ratio provider returning extreme values during instantiation
  • Concurrent operations with failing adapters

Recommendation:

#[test]
fn unregister_adapter_fails_when_query_fails() {
    // Setup adapter that returns errors on queries
    // Attempt unregister
    // Verify appropriate error handling
}

#[test]
fn toggle_tracking_fails_when_query_fails() {
    // Setup adapter with query failures
    // Attempt to toggle tracking mode
    // Verify transaction reverts safely
}

📋 Minor Code Quality Notes

1. Magic Number Documentation

Location: contracts/inflow/vault/src/contract.rs:64

const MINIMUM_INITIAL_SHARES: u128 = 1_000_000;

Consider adding a comment explaining why 1M was chosen:

// 1M shares prevent share price manipulation via donation attacks.
// This value makes the attack cost prohibitive (requires donating 
// millions of base tokens to meaningfully inflate share price).
const MINIMUM_INITIAL_SHARES: u128 = 1_000_000;

2. Error Message Consistency

Some errors use new_generic_error(format!(...)) while others use ContractError::* variants. Consider standardizing for consistency.


🧪 Test Coverage Summary

Strong coverage for:

  • ✅ Pre-minting initialization
  • ✅ Unregister with non-zero position
  • ✅ Deployment tracking synchronization
  • ✅ Failed adapter skip behavior
  • ✅ Zero shares prevention
  • ✅ Config validation

Gaps identified above.


📚 Documentation Quality

The new ADAPTER_SPEC.md is excellent:

  • Clear trust assumptions section
  • Concrete integration test scenarios
  • Well-documented error handling expectations
  • Good reference for adapter developers

Minor suggestion: Add a "Security Considerations" section highlighting:

  • The vault cannot verify adapter behavior on-chain
  • Malicious adapters can report incorrect positions
  • Recommend adapter contracts undergo independent audits

✅ Pre-Merge Checklist

Before merging, ensure:

  • Run make test-unit - all tests pass
  • Run make clippy - no warnings
  • Run make fmt-check - code formatted
  • Run make compile - artifacts regenerated (requires Docker)
  • Run make schema - schemas updated
  • Add changelog entry to .changelog/unreleased/
  • Consider adding recommended tests for negative paths
  • Validate token ratio provider contract address before mainnet deployment

🎯 Overall Assessment

This PR successfully remediates all critical and medium audit findings with well-designed, testable solutions. The code is production-ready with the following caveats:

Risk Level: LOW for critical findings, MEDIUM for operational monitoring

Blockers: None

Recommendations: Address token ratio provider validation before mainnet deployment. Other issues are operational concerns that can be addressed in follow-up PRs.

Approval Status: Approved with minor recommendations for future improvements.

Excellent work on the comprehensive remediation! The systematic approach to testing and documentation significantly improves the security posture of the Inflow Vault.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements security remediations for findings from the Q1 2026 Control Center & Vault audit. The primary focus is preventing share price manipulation attacks through pre-minting shares at instantiation, along with several medium and low severity fixes to improve vault safety and observability.

Changes:

  • Critical: Pre-mints minimum 1,000,000 shares during instantiation to prevent share price manipulation via donation attacks
  • Medium: Syncs DEPLOYED_AMOUNT when toggling adapter tracking modes and blocks adapter unregistration when positions are non-zero
  • Low/Informational: Adds failed adapter query visibility, zero shares checks, config validation, and comprehensive adapter interface documentation
  • Additional: Fixes withdrawal cancellation hedging by using min() logic for share reminting

Reviewed changes

Copilot reviewed 35 out of 35 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
contracts/inflow/vault/src/contract.rs Core remediation logic including pre-mint, tracking sync, adapter checks, failed query tracking, zero shares validation, and withdrawal cancellation fix
contracts/inflow/vault/src/msg.rs Added initial_shares_recipient field to InstantiateMsg and ReplyPayload
contracts/inflow/vault/src/testing.rs Updated tests for new instantiation requirements and added withdrawal cancellation tests
contracts/inflow/vault/src/testing_adapters.rs Updated adapter tests and added tests for tracking toggle and unregistration checks
contracts/inflow/vault/ADAPTER_SPEC.md New comprehensive adapter interface specification document
docs/plans/audit-remediation-plan.md Detailed implementation plan for all audit remediations
docs/audit/MyComments.md Audit reviewer's comments on findings and remediation approaches

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1377 to +1378
Uint128::zero(),
Uint128::zero(),
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After instantiation with MINIMUM_INITIAL_DEPOSIT (1,000,000 tokens), the contract will have 1,200,000 base tokens (1,000,000 * 1.2 ratio) and 1,200,000 shares minted to the initial_shares_recipient. The control center mock should be initialized with these values, not zero. This affects all share price calculations in subsequent deposits. Initialize the mock with: setup_control_center_mock(..., Uint128::new(1_200_000), Uint128::new(1_200_000)).

Suggested change
Uint128::zero(),
Uint128::zero(),
Uint128::new(1_200_000),
Uint128::new(1_200_000),

Copilot uses AI. Check for mistakes.
Comment on lines +1403 to +1421
let mut total_pool_value = 1200u128;
let total_shares_issued_before = 0u128;
let mut total_shares_issued_after = 1200u128;

execute_deposit(
&mut deps,
&env,
&wasm_querier,
&vault_contract_addr,
USER1,
&vault_shares_denom_str,
user1_deposit,
user1_shares,
user1_shares,
user1_deposit,
total_pool_value,
total_shares_issued_before,
total_shares_issued_after,
);
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The total_pool_value, total_shares_issued_before, and total_shares_issued_after values don't account for the initial deposit made during instantiation. After instantiation, the pool has 1,200,000 base tokens and 1,200,000 shares. User1's deposit should be: total_pool_value = 1,201,200 (not 1200), total_shares_issued_before = 1,200,000 (not 0), total_shares_issued_after = 1,201,200 (not 1200). This error propagates through all subsequent assertions in this test.

Copilot uses AI. Check for mistakes.
Comment on lines +1567 to +1568
Uint128::zero(),
Uint128::zero(),
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After instantiation with MINIMUM_INITIAL_DEPOSIT (1,000,000 tokens), the contract will have 1,200,000 base tokens (1,000,000 * 1.2 ratio) and 1,200,000 shares minted. The control center mock should be initialized with these values, not zero. Initialize with: setup_control_center_mock(..., Uint128::new(1_200_000), Uint128::new(1_200_000)).

Suggested change
Uint128::zero(),
Uint128::zero(),
Uint128::new(1_200_000),
Uint128::new(1_200_000),

Copilot uses AI. Check for mistakes.
Comment on lines +1593 to +1611
let mut total_pool_value = 1200u128;
let total_shares_issued_before = 0u128;
let mut total_shares_issued_after = 1200u128;

execute_deposit(
&mut deps,
&env,
&wasm_querier,
&vault_contract_addr,
USER1,
&vault_shares_denom_str,
user1_deposit,
user1_shares,
user1_shares,
user1_deposit,
total_pool_value,
total_shares_issued_before,
total_shares_issued_after,
);
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initial pool state values don't account for the 1,000,000 token initial deposit made during instantiation. The values should be: total_pool_value = 1,201,200, total_shares_issued_before = 1,200,000, total_shares_issued_after = 1,201,200. This affects all subsequent assertions.

Copilot uses AI. Check for mistakes.
.add_attribute("adapter_name", name)
.add_attribute("deployment_tracking", format!("{:?}", deployment_tracking)))
.add_attribute("deployment_tracking", format!("{:?}", deployment_tracking))
.add_attribute("synced_amount", adapter_position))
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The synced_amount attribute reports the position in deposit tokens, but the actual message sent to the control center uses the converted base token amount (adapter_position_base_tokens). For consistency and clarity, consider reporting the base token amount in the attribute: add_attribute("synced_amount", adapter_position_base_tokens).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one makes sense. I would also add a direction (add/subtract).

@p-offtermatt
Copy link
Copy Markdown
Member Author

Code review

Found 1 issue:

  1. Contract code was modified (contracts/inflow/vault/src/contract.rs, contracts/inflow/vault/src/msg.rs) but compiled WASM artifacts were not regenerated. (CLAUDE.md says "After modifying any contract code (including queries, but excluding tests), before committing: 1. Run make compile to regenerate artifacts" and "Add artifacts, schemas, and a changelog entry to your PR.")

hydro/CLAUDE.md

Lines 172 to 175 in 80b8be3

After modifying any contract code (including queries, but excluding tests), before committing:
1. Run `make compile` to regenerate artifacts
2. Artifacts are auto-generated in `artifacts`, including their checksums.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

p-offtermatt and others added 3 commits February 5, 2026 13:23
…tiation

The previous message conflated base tokens and shares as interchangeable
quantities. The new message clearly states how many shares the deposit
would mint vs. how many are required.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Freezing withdrawals is a legitimate operational need (e.g. during market
turbulence). Remove the >= 1 validation on max_withdrawals_per_user in both
instantiate and update_config, and update tests and remediation plan
accordingly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@dusan-maksimovic dusan-maksimovic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Are we sure that we want everything under docs to go into main?

.add_attribute("adapter_name", name)
.add_attribute("deployment_tracking", format!("{:?}", deployment_tracking)))
.add_attribute("deployment_tracking", format!("{:?}", deployment_tracking))
.add_attribute("synced_amount", adapter_position))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one makes sense. I would also add a direction (add/subtract).

@p-offtermatt
Copy link
Copy Markdown
Member Author

Yeah I think keeping the audit here makes sense for future reference

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants