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

`batchClaimYield` DoS when one entry has 0 accrued yield

Vulnerability Details

When owner wants to claim yield in batches, he will call batchClaimYield.

Problem is if one of the claimYield calls fail, the entire transaction will revert.

if any yields are zero, the function will revert as it tries to withdraw this from the aToken.(AAVE reverts when trying to withdraw 0).

https://github.com/aave-dao/aave-v3-origin/blob/7d706f00fa0c25f747c75284e8724dd927bbe42a/src/contracts/protocol/libraries/logic/ValidationLogic.sol#L101

function validateWithdraw(
DataTypes.ReserveCache memory reserveCache,
uint256 amount,
uint256 userBalance
) internal pure {
@> require(amount != 0, Errors.INVALID_AMOUNT);
...
}

Lets identify below how it happens:

function batchClaimYield(
ClaimYieldArgs[] calldata _claimYieldArgs
) external override onlyOwner nonReentrant returns (uint256[] memory) {
uint256 _length = _claimYieldArgs.length;
uint256[] memory _amountsClaimed = new uint256[]();
for (uint256 i = 0; i < _length; i++) {
@> _amountsClaimed[i] = _claimYield(_claimYieldArgs[i].collateralToken, _claimYieldArgs[i].recipient);
}
return _amountsClaimed;
}
// AaveDIVAWrapperCore
function _claimYield(address _collateralToken, address _recipient) internal returns (uint256) {
...
// 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.
// @audit-issue will return 0 when there is no accrued yield
@> _getAccruedYieldPrivate(_collateralToken), // Amount to withdraw.
_recipient // Address that will receive the underlying asset.
);
...
}
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;
}

When using batches operations, one transaction should never block the entire batch to execute unless an unexpected edge case happens. In this case, zero value is expected and handled in _getAccruedYieldPrivateso this value should not be passed to IAave(_aaveV3Pool).withdraw as this will prevent claiming yield in batch to execute.

Impact

DoS when claiming yield.

Tools Used

Manual Review

Recommendations

In the _claimYield function skip the cases where the accrued yield is zero so the transaction will not revert.

function _claimYield(address _collateralToken, address _recipient) internal returns (uint256) {
+ uint256 yield = _getAccruedYieldPrivate(_collateralToken);
+ if (yield == 0) { return 0; }
uint256 _amountReturned = IAave(_aaveV3Pool).withdraw(
_collateralToken, // Address of the underlying asset (e.g., USDT), not the aToken.
- _getAccruedYieldPrivate(_collateralToken), // Amount to withdraw.
+ yield, // Amount to withdraw.
_recipient // Address that will receive the underlying asset.
);
Updates

Lead Judging Commences

bube Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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