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 5 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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