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

Owner Unable to Claim Yield Due to Zero Accrued Yield Calculation

Summary

A critical issue has been identified in the AaveDIVAWrapperCore contract, where the owner is unable to claim yield due to the AaveDIVAWrapperCore:: _getAccruedYieldPrivate function consistently returning zero. This results in a revert with the error INVALID_AMOUNT '26' when attempting to withdraw from Aave, as the function does not recognize any accrued yield.

Vulnerability Details

The issue originates from the AaveDIVAWrapperCore:: _getAccruedYieldPrivate function, which calculates the accrued yield as the difference between the Aave `aToken` balance and the `wToken` supply:

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();
//@audit aTokenBalance = wTokenSupply => 0
@> return aTokenBalance > wTokenSupply ? aTokenBalance - wTokenSupply : 0;
}
  • The function assumes yield accrues when aTokenBalance > wTokenSupply.

  • However, due to the logic in AaveDIVAWrapperCore:: _handleTokenOperations, every deposit supplies an equal amount of collateral to Aave and mints an equal number of wTokens, ensuring aTokenBalance == wTokenSupply at all times.

function _handleTokenOperations(address _collateralToken, uint256 _collateralAmount, address _wToken) private {
// Transfer collateral token from the caller to this contract. Requires prior approval by the caller
// to transfer the collateral token to the AaveDIVAWrapper contract.
IERC20Metadata(_collateralToken).safeTransferFrom(msg.sender, address(this), _collateralAmount);
// Supply the collateral token to Aave and receive aTokens. Approval to transfer the collateral token from this contract
// to Aave was given when the collateral token was registered via `registerCollateralToken` or when the
// allowance was set via `approveCollateralTokenForAave`.
//@audit We supply the same CollateralAmount we mint
IAave(_aaveV3Pool).supply(
_collateralToken, // Address of the asset to supply to the Aave reserve.
@> _collateralAmount, // Amount of asset to be supplied.
address(this), // Address that will receive the corresponding aTokens (`onBehalfOf`).
0 // Referral supply is currently inactive, you can pass 0 as referralCode. This program may be activated in the future through an Aave governance proposal.
);
// Mint wTokens associated with the supplied asset, used as a proxy collateral token in DIVA Protocol.
// Only this contract is authorized to mint wTokens.
//@audit We mint the same CollateralAmount we supply
@> IWToken(_wToken).mint(address(this), _collateralAmount);
}
  • This leads AaveDIVAWrapperCore:: _getAccruedYieldPrivate to return 0 always, preventing the owner from claiming yield.

function _claimYield(address _collateralToken, address _recipient) internal returns (uint256) {
// Confirm that the collateral token is registered
if (_collateralTokenToWToken[_collateralToken] == address(0)) {
revert CollateralTokenNotRegistered();
}
if (_recipient == address(0)) revert ZeroAddress();
// Redeem aToken for collateral token at Aave Protocol and send collateral token to recipient.
uint256 _amountReturned = IAave(_aaveV3Pool).withdraw(
_collateralToken, // Address of the underlying asset (e.g., USDT), not the aToken.
@> _getAccruedYieldPrivate(_collateralToken), // Amount to withdraw. //@audit return 0
_recipient // Address that will receive the underlying asset.
);
// IAave(_aaveV3Pool).withdraw(
// _collateralToken,
// 0, // This results in a revert with the error INVALID_AMOUNT '26' in Aave V3
// _recipient
// );
emit YieldClaimed(owner(), _recipient, _collateralToken, _amountReturned);
return _amountReturned;
}

https://github.com/aave-dao/aave-v3-origin/blob/ae2d19f998b421b381b85a62d79ecffbb0701501/src/contracts/protocol/libraries/helpers/Errors.sol#L35
https://github.com/aave-dao/aave-v3-origin/blob/ae2d19f998b421b381b85a62d79ecffbb0701501/src/contracts/protocol/libraries/logic/ValidationLogic.sol#L101

Impact

  • Yield Claim Failure: The owner cannot claim any yield since the function always calculates it as zero.

  • Protocol Inefficiency: The inability to access yield reduces the effectiveness of staking collateral in Aave.

In test/AaveDIVAWrapperTest.t.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "forge-std/Test.sol";
import {IAaveDIVAWrapper} from "../src/interfaces/IAaveDIVAWrapper.sol";
import {IAave} from "../src/interfaces/IAave.sol";
import {IDIVA} from "../src/interfaces/IDIVA.sol";
import "../src/AaveDIVAWrapper.sol";
import "../src/interfaces/IDIVA.sol";
import "lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol";
interface IUSDC {
function approve(address spender, uint256 amount) external;
function transfer(address recipient, uint256 amount) external;
function mint(address account, uint256 amount) external;
function balanceOf(address) external view returns (uint256);
function transferFrom(address sender, address recipient, uint256 amount) external;
function decimals() external view returns (uint8);
}
contract AaveDIVAWrapperTest is Test {
AaveDIVAWrapperCore wrapper;
address trader = address(0x4D8336bDa6C11BD2a805C291Ec719BaeDD10AcB9); // in polygon
address blacklistedUser = address(0x0E6b8E34dC115a2848F585851AF23D99D09b8463); // in polygon USDC token
address shortRecipient;
address longRecipient;
bytes32 poolId;
IAave _aavePool = IAave(0x794a61358D6845594F94dc1DB02A252b5b4814aD); // in polygon
IDIVA _diva = IDIVA(0x2C9c47E7d254e493f02acfB410864b9a86c28e1D); // in polygon
IUSDC USDC = IUSDC(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359); // in polygon
function setUp() public {
// Deploy or connect to the necessary contracts
wrapper = new AaveDIVAWrapper(address(_diva),address(_aavePool),address(this));
shortRecipient = address(0x1); // short recipent address
longRecipient = address(0x2); // log recipent address
vm.startPrank(trader);
IUSDC(USDC).approve(address(this), type(uint256).max);
vm.stopPrank();
IUSDC(USDC).transferFrom(trader, address(this), 100* 10**USDC.decimals());
wrapper.registerCollateralToken(address(USDC));
}
modifier createPool {
// Create a pool
USDC.approve(address(wrapper), type(uint256).max);
poolId = wrapper.createContingentPool(IAaveDIVAWrapper.PoolParams({
referenceAsset: "BTC/USD",
expiryTime: uint96(block.timestamp + 86400),
floor: 100,
inflection: 150,
cap: 200,
gradient: uint256(5 * 10 ** (USDC.decimals()-1)),
collateralAmount: 10 * 10**USDC.decimals(),
collateralToken: address(USDC),
dataProvider: address(0x11),
capacity: type(uint256).max,
longRecipient: longRecipient,
shortRecipient: shortRecipient,
permissionedERC721Token: address(0)
}));
_;
}
modifier addliquidity {
vm.startPrank(trader);
USDC.approve(address(wrapper), type(uint256).max);
wrapper.addLiquidity(poolId,100 *10**USDC.decimals() , longRecipient, shortRecipient);
vm.stopPrank();
_;
}
function testClaimYield() external createPool addliquidity{
// Expected Revert with 26
// string public constant INVALID_AMOUNT = '26'; // 'Amount must be greater than 0'
// https://github.com/aave-dao/aave-v3-origin/blob/ae2d19f998b421b381b85a62d79ecffbb0701501/src/contracts/protocol/libraries/helpers/Errors.sol#L35C2-L35C83
wrapper.claimYield(address(USDC),address(this));
}
}

Run Thi Command :

forge test --mt testClaimYield --fork-url https://polygon-mainnet.g.alchemy.com/v2/Yn7tIZfhjHHmJQbJ4SeXkpuRju0udqkR -vvv

Output :

├─ [46444] AaveDIVAWrapper::claimYield(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359, 0x0E6b8E34dC115a2848F585851AF23D99D09b8463)
│ ├─ [12291] 0x794a61358D6845594F94dc1DB02A252b5b4814aD::getReserveData(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359) [staticcall]
│ │ ├─ [11693] 0x5DFb8c777C19d3cEdcDc7398d2EeF1FB0b9b05c9::getReserveData(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359) [delegatecall]
│ │ │ ├─ [2488] 0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb::getAddress(0x4d4f434b5f535441424c455f4445425400000000000000000000000000000000) [staticcall]
│ │ │ │ └─ ← [Return] 0x000000000000000000000000d94112b5b62d53c9402e7a60289c6810def1dc9b
│ │ │ └─ ← [Return] ReserveDataLegacy({ configuration: ReserveConfigurationMap({ data: 7237005577332262213973186942896412476039334390506525738199614521317852192076 [7.237e75] }), liquidityIndex: 1093697577431803941150626743 [1.093e27], currentLiquidityRate: 71624501664828486057718072 [7.162e25], variableBorrowIndex: 1115720259726756204254285379 [1.115e27], currentVariableBorrowRate: 103009889251849476157308009 [1.03e26], currentStableBorrowRate: 0, lastUpdateTimestamp: 1738314186 [1.738e9], id: 20, aTokenAddress: 0xA4D94019934D8333Ef880ABFFbF2FDd611C762BD, stableDebtTokenAddress: 0xd94112B5B62d53C9402e7A60289c6810dEF1dC9B, variableDebtTokenAddress: 0xE701126012EC0290822eEA17B794454d1AF8b030, interestRateStrategyAddress: 0x56076f960980d453b5B749CB6A1c4D2E4e138B1A, accruedToTreasury: 135848380 [1.358e8], unbacked: 0, isolationModeTotalDebt: 0 })
│ │ └─ ← [Return] ReserveDataLegacy({ configuration: ReserveConfigurationMap({ data: 7237005577332262213973186942896412476039334390506525738199614521317852192076 [7.237e75] }), liquidityIndex: 1093697577431803941150626743 [1.093e27], currentLiquidityRate: 71624501664828486057718072 [7.162e25], variableBorrowIndex: 1115720259726756204254285379 [1.115e27], currentVariableBorrowRate: 103009889251849476157308009 [1.03e26], currentStableBorrowRate: 0, lastUpdateTimestamp: 1738314186 [1.738e9], id: 20, aTokenAddress: 0xA4D94019934D8333Ef880ABFFbF2FDd611C762BD, stableDebtTokenAddress: 0xd94112B5B62d53C9402e7A60289c6810dEF1dC9B, variableDebtTokenAddress: 0xE701126012EC0290822eEA17B794454d1AF8b030, interestRateStrategyAddress: 0x56076f960980d453b5B749CB6A1c4D2E4e138B1A, accruedToTreasury: 135848380 [1.358e8], unbacked: 0, isolationModeTotalDebt: 0 })
│ ├─ [5306] 0xA4D94019934D8333Ef880ABFFbF2FDd611C762BD::balanceOf(AaveDIVAWrapper: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f]) [staticcall]
│ │ ├─ [4698] 0xCf85FF1c37c594a10195F7A9Ab85CBb0a03f69dE::balanceOf(AaveDIVAWrapper: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f]) [delegatecall]
│ │ │ ├─ [1419] 0x794a61358D6845594F94dc1DB02A252b5b4814aD::getReserveNormalizedIncome(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359) [staticcall]
│ │ │ │ ├─ [867] 0x5DFb8c777C19d3cEdcDc7398d2EeF1FB0b9b05c9::getReserveNormalizedIncome(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359) [delegatecall]
│ │ │ │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000388af787984f09d5be993b7
│ │ │ │ └─ ← [Return] 0x00000000000000000000000000000000000000000388af787984f09d5be993b7
│ │ │ └─ ← [Return] 110000000 [1.1e8]
│ │ └─ ← [Return] 110000000 [1.1e8] // aToken = 110000000
│ ├─ [477] WToken::totalSupply() [staticcall]
│ │ └─ ← [Return] 110000000 [1.1e8] // wToken = 110000000
│ ├─ [15702] 0x794a61358D6845594F94dc1DB02A252b5b4814aD::withdraw(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359, 0 /** _getAccruedYieldPrivate(address _collateralToken) = 0 */, 0x0E6b8E34dC115a2848F585851AF23D99D09b8463)
│ │ ├─ [15131] 0x5DFb8c777C19d3cEdcDc7398d2EeF1FB0b9b05c9::withdraw(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359, 0, 0x0E6b8E34dC115a2848F585851AF23D99D09b8463) [delegatecall]
│ │ │ ├─ [2402] 0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb::getPriceOracle() [staticcall]
│ │ │ │ └─ ← [Return] 0x000000000000000000000000b023e699f5a33916ea823a16485e259257ca8bd1
│ │ │ ├─ [6619] 0x2b22E425C1322fbA0DbF17bb1dA25d71811EE7ba::186dea44(00000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000036000000000000000000000000000000000000000000000000000000000000003702d183c14bea6b53d1a60b293e9416ed749f2af0faddac5a251f02731f788fbe0000000000000000000000003c499c542cef5e3811e1192ce70d8cc03d5c335900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e6b8e34dc115a2848f585851af23d99d09b84630000000000000000000000000000000000000000000000000000000000000015000000000000000000000000b023e699f5a33916ea823a16485e259257ca8bd10000000000000000000000000000000000000000000000000000000000000000) [delegatecall]
│ │ │ │ ├─ [1024] 0xE701126012EC0290822eEA17B794454d1AF8b030::scaledTotalSupply() [staticcall]
│ │ │ │ │ ├─ [419] 0x79b5e91037AE441dE0d9e6fd3Fd85b96B83d4E93::scaledTotalSupply() [delegatecall]
│ │ │ │ │ │ └─ ← [Return] 0x000000000000000000000000000000000000000000000000000022352f711ff4
│ │ │ │ │ └─ ← [Return] 0x000000000000000000000000000000000000000000000000000022352f711ff4
│ │ │ │ ├─ [1299] 0xA4D94019934D8333Ef880ABFFbF2FDd611C762BD::scaledBalanceOf(AaveDIVAWrapper: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f]) [staticcall]
│ │ │ │ │ ├─ [691] 0xCf85FF1c37c594a10195F7A9Ab85CBb0a03f69dE::scaledBalanceOf(AaveDIVAWrapper: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f]) [delegatecall]
│ │ │ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000005feabf9
│ │ │ │ │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000005feabf9
│ │ │ │ └─ ← [Revert] revert: 26 // Revert INVALID_AMOUNT

Tools Used

  • Manuial Review

  • Foundry

Recommendations

  • Modify AaveDIVAWrapperCore:: _getAccruedYieldPrivate to track actual yield separately instead of relying solely on the difference between aTokenBalance and wTokenSupply.

Updates

Lead Judging Commences

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

Support

FAQs

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