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
amountinstead ofamountToDepositfrom_allocateafter 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
_allocatethrough the adapter interface:
Dust is stranded as native ETH on the strategy and not counted in
realAssets()(which uses Stargate poolredeemable()).
On accrual, the vault recomputes total assets using adapters’
realAssets(); dust is excluded and becomes a realized loss.
Deallocation reverts are amplified because
_deallocaterequires 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):
_deallocatemay 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
amountToDepositfrom_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.
Link to Proof of Concept
https://gist.github.com/IronsideSec/e0e6e14f6f6bd6e8700a654450a309c0
Proof of Concept
Proof of Concept
replace
src/test/strategies/StargateEthPoolStrategy.t.solwith whole file inhttps://gist.github.com/IronsideSec/e0e6e14f6f6bd6e8700a654450a309c0run
forge t --mt test_allocate -vvvv
Was this helpful?