HardhatDeFi
15,000 USDC
View results
Submission Details
Severity: medium
Invalid

Wrong Yield accrual calculation will lead to stuck funds and reverts in `claimYield`

Summary

This contract uses aToken.balanceOf(address(this)) to measure the amount of collateral held in Aave V3 and compares it to the total supply of the associated wToken to account for the accrued interest. Specifically, _getAccruedYieldPrivate calculates yield as aTokenBalance - wTokenSupply. However, aTokens are rebalancing tokens in Aave V3 and can deviate from a strict 1:1 relationship with the underlying asset. This means aToken.balanceOf may exceed the actual underlying tokens causing overstated yield calculations and potentially leading to stuck yields.

Vulnerability Details

The code assumes aTokenBalance - wTokenSupply accurately represents the interest part.

function _getAccruedYieldPrivate(address _collateralToken) private view returns (uint256) {
uint256 aTokenBalance = IERC20Metadata(IAave(_aaveV3Pool).getReserveData(_collateralToken).aTokenAddress)
.balanceOf(address(this));
uint256 wTokenSupply = IERC20Metadata(_collateralTokenToWToken[_collateralToken]).totalSupply();
return aTokenBalance > wTokenSupply ? aTokenBalance - wTokenSupply : 0;
}

In most cases aTokenBalance is same as asset balance. However, this ratio can be broken for a number of reasons.

For example, aUSDC is worth less than USDC.

https://etherscan.io/tx/0xfc0c25219c04f1398b68a9e6880d9282f63ff8508e96527a0cd8e708749e107a

In the above transaction, 20001.9 aUSDC were minted for 20000 USDC.

The function _claimYield calls:

IAave(_aaveV3Pool).withdraw(
_collateralToken,
_getAccruedYieldPrivate(_collateralToken),
_recipient
);

This withdraws the underlying asset in an amount equal to _getAccruedYieldPrivate. This amount still represents the quantity of aTokens left. Whereas aaveV3 withdraw requires the quantity of assets to be withdrawn, not the quantity of aTokens: https://aave.com/docs/developers/smart-contracts/pool#write-methods-withdraw

asset : The address of the underlying asset to withdraw, not the aToken

Impact

  1. If _getAccruedYieldPrivate is inflated and users have withdrawed all their tokens, a revert occurs in IAave(_aaveV3Pool).withdraw because the aToken is more than the actual asset supplied. Which will cause the funds to be stuck until more funds are supplied by users.

  2. If users have not withdrawed all the tokens, and claimYield is called before, it will leave less tokens for the users. This will thus block the users from withdrawing their full amounts.

Tools Used

Manual code review

Recommendation

Use Scaled Balances and Liquidity Index to account for the correct max withdrawable amount (must update the Liquidity index before the calculation):

function _getAccruedYieldPrivate(address _collateralToken) private view returns (uint256) {
ReserveData memory reserveData = IAave(_aaveV3Pool).getReserveData(_collateralToken);
uint256 scaledBalance = IERC20Metadata(IAave(_aaveV3Pool).getReserveData(_collateralToken).aTokenAddress).scaledBalanceOf(address(this));
// Update the Liquidity index before calculation
uint256 cumulatedLiquidityInterest = MathUtils.calculateLinearInterest(
reserveData.currentLiquidityRate, reserveData.lastUpdateTimestamp
uint256 nextLiquidityIndex =
rayMul(cumulatedLiquidityInterest, reserveData.liquidityIndex);
// Calculate the exact max left withdrawable amount
uint256 availableForWithdrawal =
(rayMul(scaledBalance, nextLiquidityIndex));
uint256 wTokenSupply = IERC20Metadata(_collateralTokenToWToken[_collateralToken]).totalSupply();
return availableForWithdrawal > wTokenSupply ? availableForWithdrawal - wTokenSupply : 0;
);
}
Updates

Lead Judging Commences

bube Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

maroutis Submitter
9 months ago
maroutis Submitter
9 months ago
maroutis Submitter
9 months ago
bube Lead Judge
8 months ago
bube Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.