58033 sc medium unimplemented claimrewards function results in permanent freezing of aave incentive rewards
Submitted on Oct 30th 2025 at 06:28:43 UTC by @theboiledcorn for Audit Comp | Alchemix V3
Report ID: #58033
Report Type: Smart Contract
Report severity: Medium
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/strategies/arbitrum/AaveV3ARBUSDCStrategy.sol
Impacts:
Permanent freezing of unclaimed yield
Description
Brief/Intro
The AaveV3ARBUSDCStrategy contract fails to implement the required _claimRewards() function inherited from its MYTStrategy base contract. When the strategy deploys USDC to Aave V3, it accrues Aave incentive rewards (e.g., ARB tokens). Because the contract lacks the code to claim these rewards, and Aave's protocol only allows the aToken holder (the strategy itself) to claim, all earned yield becomes permanently frozen and unrecoverable, leading to a direct loss of funds.
Vulnerability Details
The core of the vulnerability lies in an incomplete implementation of the MYTStrategy interface.
Base Contract's Expectation: The
MYTStrategybase contract (whichAaveV3ARBUSDCStrategyinherits from) provides a publicclaimRewards()function. This function's sole purpose is to call an internal virtual function,_claimRewards(), which is empty by default and intended to be overridden by child strategies.// In MYTStrategy.sol (Base Contract) function claimRewards() public virtual returns (uint256) { require(!killSwitch, "emergency"); _claimRewards(); // Calls the internal function } /// @dev override this function to claim all available rewards... function _claimRewards() internal virtual returns (uint256) {} // Empty by defaultMissing Implementation: The
AaveV3ARBUSDCStrategycontract correctly overrides other necessary functions like_allocateand_deallocate, but it completely omits an override for_claimRewards(). Therefore, when the publicclaimRewards()function is called on this strategy, it executes the empty base implementation, doing nothing.Aave's Reward Mechanism: When the strategy allocates funds, it supplies USDC to the Aave pool and receives
aUSDCtokens. TheonBehalfOfparameter is set toaddress(this), making the strategy contract the direct owner of the aTokens.// In AaveV3ARBUSDCStrategy.sol function _allocate(uint256 amount) internal override returns (uint256) { // ... // Strategy (address(this)) becomes the owner of the aTokens pool.supply(address(usdc), amount, address(this), 0); return amount; }As the
aUSDCholder, the strategy contract begins to accrue Aave incentive rewards. According to Aave's protocol design, only the aToken holder (the strategy) can call theIncentivesControllercontract to claim its own rewards.Permanent Freeze: Since the strategy contract is the only entity that can claim the rewards, but it lacks the code to do so, there is no possible way to retrieve the accrued incentives. They will remain permanently locked in Aave's
IncentivesControllercontract.
Impact Details
This vulnerability leads to the Permanent Freezing of Unclaimed Yield.
Severity: High. This is a direct, irreversible loss of funds. The primary yield (USDC interest) is accessible, but the secondary yield (Aave incentive rewards) is lost forever.
Classification: This matches the severity classification: "Permanent freezing of unclaimed yield: A yield is any asset distributed as a reward for participation in a system. Whenever... the yield [cannot] be able to move from the contract... this would mean the yield is permanently frozen." In this case, the yield cannot be moved from Aave's
IncentivesController.Financial Loss: The total financial loss is 100% of all Aave incentive rewards ever earned by this strategy. The magnitude of this loss depends on three factors:
The total amount of USDC deposited into the strategy.
The incentive APR% offered by Aave for the USDC pool on Arbitrum.
The duration of time the strategy is deployed.
Example Scenario: If $1,000,000 USDC is deployed in this strategy for one year at an average incentive APR of 2%, approximately $20,000 worth of ARB (or other reward tokens) would be permanently and irretrievably lost.
Link to Proof of Concept
https://gist.github.com/theboiledcorn/ab8d6dd1fee8f6ebba4436e5a6ce19ef
Proof of Concept
3. Proof of Concept (PoC)
This Proof of Concept demonstrates that the reward-claiming mechanism is completely missing and that no other party can claim on the strategy's behalf. This proves that any future rewards emitted by Aave to aUSDC holders will be permanently frozen.
Test Setup:
A test is run on an Arbitrum mainnet fork.
The
AaveV3ARBUSDCStrategycontract is deployed, funded with USDC, and itsallocate()function is called.The test uses the function
test_aave_rewards_cannot_be_claimedfrom theAaveV3ARBUSDCStrategyTestcontract.
Step-by-Step Explanation:
Allocate Funds & Verify aToken Ownership:
Action: The test calls
allocate(1000e6)on the strategy contract.Result: The strategy successfully supplies
1,000USDC to the Aave V3 Pool. The test confirms that theAaveV3ARBUSDCStrategycontract itself is now the owner of theaUSDCtokens.Code:
assertGt(aTokenBalance, 0, "Strategy should hold aTokens");
Confirm Missing
_claimRewardsImplementation:Action: The public
claimRewards()function is called on theAaveV3ARBUSDCStrategycontract.Result: The function executes without error but returns
0. This demonstrates that the contract is using the empty base implementation of_claimRewards(), which takes no action.Code:
uint256 returned = MockAaveV3ARBUSDCStrategy(strategy).claimRewards();Code:
assertEq(returned, 0, "Empty implementation returns 0");
Confirm No External Party Can Claim:
Action: The test simulates a call from the
strategyOwner. The owner directly calls theclaimRewardsfunction on Aave'sIncentivesController(0x9...473e), attempting to claim any potential rewards (indicated byaddress(0)as the reward token) to their own address.Result: The call succeeds but returns
0rewards. This proves that, per Aave's protocol rules, only the aToken holder (the strategy contract) can claim its own rewards. No external party, not even the owner, can intervene.Code:
uint256 ownerClaimed = IAaveIncentivesController(AAVE_INCENTIVES_CONTROLLER).claimRewards(assets, type(uint256).max, strategyOwner, address(0));Code:
assertEq(ownerClaimed, 0, "Owner cannot claim strategy's rewards");
Conclusion:
The test (test_aave_rewards_cannot_be_claimed) definitively proves two things:
The strategy contract has no code to claim rewards (Step 2).
No other entity can claim rewards on its behalf (Step 3).
Therefore, when Aave enables an incentive program for the Arbitrum USDC pool, 100% of the rewards accrued by this strategy will be permanently and irretrievably frozen.
Was this helpful?