For the complete documentation index, see llms.txt. This page is also available as Markdown.

58313 sc medium incorrect allocation accounting and dust handling in stargateethpoolstrategy causes systematic loss cap mis accounting and deallocation reverts

Submitted on Nov 1st 2025 at 07:25:28 UTC by @IronsideSec for Audit Comp | Alchemix V3

  • Report ID: #58313

  • Report Type: Smart Contract

  • Report severity: Medium

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

  • Impacts:

    • Contract fails to deliver promised returns, but doesn't lose value

Description

Brief/Intro

StargateEthPoolStrategy._allocate returns the requested amount instead of the actually deposited amount (floored to 1e12), creating an accounting mismatch with the vault. This over-reports allocation, can prematurely trip caps, and leaves ETH “dust” stranded on the strategy that is not reflected in realAssets(). Over time, this leads to systematic realized loss when interest/loss is accrued, and can also make deallocation revert due to insufficient wrapped WETH. Sub-1e12 allocations deposit 0 by design, causing a 100% loss of those amounts.

Vulnerability Details

  • Root cause: returning amount instead of amountToDeposit from _allocate after rounding down to the nearest 1e12.

47:58:src/strategies/optimism/StargateEthPoolStrategy.sol
function _allocate(uint256 amount) internal override returns (uint256) {
    require(TokenUtils.safeBalanceOf(address(weth), address(this)) >= amount, "not enough WETH");
    // unwrap to native ETH for Pool Native
    weth.withdraw(amount);
    uint256 amountToDeposit = (amount / 1e12) * 1e12;
    uint256 dust = amount - amountToDeposit;
    if (dust > 0) {
        emit StrategyAllocationLoss("Strategy allocation loss due to rounding.", amount, amountToDeposit);
    }
    pool.deposit{value: amountToDeposit}(address(this), amountToDeposit);
    return amount;
}
  • Why this is harmful:

    • The vault uses the strategy’s returned “change” to update the allocation and enforce caps; returning the wrong value overstates allocation.

  • The wrong value propagates from _allocate through the adapter interface:

  • Dust is stranded as native ETH on the strategy and not counted in realAssets() (which uses Stargate pool redeemable()).

  • On accrual, the vault recomputes total assets using adapters’ realAssets(); dust is excluded and becomes a realized loss.

  • Deallocation reverts are amplified because _deallocate requires WETH balance ≥ amount, while dust remains as ETH unless explicitly wrapped.

  • Proven by PoC tests:

    • Over-reported allocation and floored realAssets():

  • Sub-1e12 allocations deposit 0 (100% loss of the requested amount; all stranded as ETH dust):

  • Caps can be tripped due to over-reported “change”:

Impact Details

  • Systematic realized loss from dust:

    • Each allocation can leave up to < 1e12 wei (~1e-6 ETH) stranded. At ~$4,000/ETH, that is ~$0.004 per allocation.

    • 250 allocations ≈ $1 of loss; over many operations, losses accumulate.

  • Sub-1e12 allocations:

    • Entire requested amount becomes stranded as ETH dust (0 deposited to the pool), causing a 100% loss of that request in accounting terms.

  • Cap/Accounting integrity:

    • Allocation is overstated by the dust amount, potentially tripping absolute/relative caps spuriously and misrepresenting exposure.

  • Deallocation failures (DoS of allocator actions):

    • _deallocate may revert because the strategy checks WETH balance ≥ requested amount while dust is left as ETH; this can block deallocation in certain edge cases.

Vulnerability classification: Accounting error leading to loss of funds (rounding/dust), cap mis-accounting, and potential DoS of deallocation.

References

  • Affected code:

    • Wrong return in _allocate:

  • realAssets() excludes ETH dust:

  • Deallocation require on WETH:

  • Change propagation to caps:

  • Accrual based on adapters’ realAssets():

  • Adapter return used as “change”:

  • Proof-of-concept tests:

    • Over-reported allocation and floored realAssets():

  • Sub-1e12 allocation deposits 0:

  • Cap exceeded due to over-reported change:

  • Recommendation (concise):

    • Return amountToDeposit from _allocate.

    • Immediately re-wrap dust ETH back to WETH to keep balances consistent and avoid realized loss.

    • In _deallocate, wrap existing ETH dust before computing remaining redemption, then wrap all redeemed ETH to meet the requested WETH amount.

https://gist.github.com/IronsideSec/e0e6e14f6f6bd6e8700a654450a309c0

Proof of Concept

Proof of Concept

  1. replace src/test/strategies/StargateEthPoolStrategy.t.sol with whole file in https://gist.github.com/IronsideSec/e0e6e14f6f6bd6e8700a654450a309c0

  2. run forge t --mt test_allocate -vvvv

Was this helpful?