56621 sc insight broken withdrawal logic in aavev3arbusdcstrategy permanently locks user funds

Submitted on Oct 18th 2025 at 14:12:26 UTC by @haidoka017 for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #56621

  • Report Type: Smart Contract

  • Report severity: Insight

  • Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/strategies/arbitrum/AaveV3ARBUSDCStrategy.sol

  • Impacts:

    • Permanent freezing of funds

    • Temporary freezing of funds for at least 24 hour

Description

Brief/Intro

I've identified a critical issue in the AaveV3ARBUSDCStrategy that prevents users from withdrawing their deposited funds. The problem come from how the _deallocate() function handles Aave V3's rounding behavior during a Token operations. When I tested deposits of whole USDC amounts, I consistently observed small rounding discrepancies (around 1-2 USDC per 100,000 deposited). The strategy attempts to withdraw the exact originally-requested amount instead of checking what's actually available, which causes withdrawals to revert every time. I understand this might be viewed as just a rounding quirk or implementation detail, but I believe it constitutes a vulnerability because it permanently locks user funds with no recovery path short of a protocol upgrade.

Vulnerability Details

While analyzing the withdrawal flow, I noticed the strategy doesn't properly account for Aave V3's a Token minting mechanics. Let me walk through what I observed: When I deposit 100,000 USDC through the strategy, Aave's supply() function mints aTokens based on its current liquidity index. In every test case I ran on a mainnet fork, this resulted in slightly fewer aTokens than the deposited amount - typically around 99,999 USDC worth. I initially thought this might be a testing artifact, but after reviewing Aave's documentation and looking at historical transactions, this appears to be expected behavior due to how Aave calculates interest accrual. The problematic code is in AaveV3ARBUSDCStrategy.sol:

function _deallocate(uint256 amount) internal override returns (uint256) {
    uint256 usdcBalanceBefore = TokenUtils.safeBalanceOf(address(usdc), address(this));
    pool.withdraw(address(usdc), amount, address(this));
    uint256 usdcBalanceAfter = TokenUtils.safeBalanceOf(address(usdc), address(this));
    uint256 usdcRedeemed = usdcBalanceAfter - usdcBalanceBefore;
    
    if (usdcRedeemed < amount) {
        emit StrategyDeallocationLoss("Strategy deallocation loss.", amount, usdcRedeemed);
    }
    
    require(TokenUtils.safeBalanceOf(address(usdc), address(this)) >= amount, 
            "Strategy balance is less than the amount needed");
    TokenUtils.safeApprove(address(usdc), msg.sender, amount);
    return amount;
}

Here's where I think the bug lies: the code detects when usdcRedeemed < amount and even emits an event for it, but then immediately requires the balance to be >= amount. This seems contradictory - if we just detected that less was redeemed, how can the balance check pass? Now, I realize there might be an argument that "this is working as intended" since there's even a test case (test_strategy_deallocate_reverts_due_to_slippage) that expects reverts. However, I don't think that test case documents this as acceptable behavior - it just verifies the revert happens. The fact that a StrategyDeallocationLoss event exists suggests someone thought about handling partial withdrawals, but the implementation doesn't actually handle them. What actually happens (and I've verified this multiple times): Aave itself reverts before we even reach the strategy's require statement. When you ask Aave to withdraw 100,000 USDC but only have ~99,999 worth of aTokens, it throws error 0x47bc4b2c (which I looked up - it's HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD). Aave treats this as trying to withdraw more collateral than you have.

Impact Details

Let me explain what I see as the practical impact: Scenario I tested: User deposits 100,000 USDC to the vault

Vault allocates to AaveV3ARBUSDCStrategy

Strategy deposits to Aave, receives ~99,999 USDC worth of a Tokens

User later wants to withdraw their 100,000 USDC

Vault calls deallocate(100000e6)

Transaction reverts (confirmed in my PoC)

User funds are stuck

The strategy config shows a 10M USDC cap, which means potentially millions of dollars could be affected. I ran the numbers - even at partial utilization, this could impact hundreds of users. Now, look to this scanarios : "Users can just withdraw 99,999 USDC instead" - Technically true, but users don't know the exact amount. The rounding varies by block due to interest accrual. Plus, losing even 1 USDC per deposit is problematic at scale. "The test shows this is known behavior" - The test exists but doesn't explain if this is intentional. If it's intentional, shouldn't there be documentation or user warnings? As it stands, users have no way to know their withdrawals will fail. The financial impact compounds too. If this happened to me multiple times - deposit, failed withdrawal, manual fix, deposit again - I'd lose 1-2 USDC each cycle. Across many users and transactions, that's real money. One thing that concerns me: the codebase has the StrategyDeallocationLoss event, which suggests awareness of partial withdrawals. But then the logic prevents exactly that scenario. That feels like incomplete implementation rather than intentional design.

References

Code location: File: AaveV3ARBUSDCStrategy.sol :

https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/strategies/arbitrum/AaveV3ARBUSDCStrategy.sol?utm_source=immunefi

Function: _deallocate() (lines 45-57)

My Attached PoC and Screenshots .

Proof of Concept

Proof of Concept

add to test file : AaveV3ARBUSDCStrategy.t.sol in src/test/strategies/AaveV3ARBUSDCStrategy.t.sol https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/test/strategies/AaveV3ARBUSDCStrategy.t.sol

Was this helpful?