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

Users can lose their supplied collateral via `AaveDIVAWrapper::addLiquidity()` if Aave deprecates the collateral token after it has been registered on DIVA via `AaveDIVAWrapperCore::_registerCollateralToken()`

Summary

Vulnerability Details

For users to be able to add liquidity to the pools for any certain collateral, it has to be registered via AaveDIVAWrapper::registerCollateralToken() / AaveDIVAWrapperCore::_registerCollateralToken(). AaveDIVAWrapperCore::_registerCollateralToken() performs checks to ensure that

  1. token isn't registered already

  2. Aave V3's aToken associated with the provided _collateralToken is not address(0).

#AaveDIVAWrapper::registerCollateralToken()
# https://github.com/Cyfrin/2025-01-diva/blob/1b6543768c341c2334cdff87b6dd627ee2f62c89/contracts/src/AaveDIVAWrapper.sol#L21-L25
function registerCollateralToken(
address _collateralToken
) external override onlyOwner nonReentrant returns (address) {
return _registerCollateralToken(_collateralToken);
}
# AaveDIVAWrapperCore::_registerCollateralToken()
#https://github.com/Cyfrin/2025-01-diva/blob/1b6543768c341c2334cdff87b6dd627ee2f62c89/contracts/src/AaveDIVAWrapperCore.sol#L71-L121
function _registerCollateralToken(address _collateralToken) internal returns (address) {
// Verify that the collateral token is not yet registered. @audit1
if (_collateralTokenToWToken[_collateralToken] != address(0)) {
revert CollateralTokenAlreadyRegistered();
}
// Retrieve the aToken address associated with the provided collateral token from Aave V3. Reverts if
// the collateral token is not supported by Aave V3.
// Note: aTokens have the same number of decimals as the collateral token: https://discord.com/channels/602826299974877205/636902500041228309/1249607036417867810
address _aToken = _getAToken(_collateralToken);
if (_aToken == address(0)) {
revert UnsupportedCollateralToken(); @audit2
}
IERC20Metadata _collateralTokenContract = IERC20Metadata(_collateralToken);
// Deploy a token that represents a wrapped version of the collateral token to be used as proxy collateral in DIVA Protocol.
// The symbol and name of the wToken are derived from the original collateral token, prefixed with 'w' (e.g., wUSDT or wUSDC).
// This naming convention helps in identifying the token as a wrapped version of the original collateral token.
// The wToken decimals are aligned with those of the collateral token and the aToken.
// This contract is set as the owner and has exclusive rights to mint and burn the wToken.
WToken _wTokenContract = new WToken(
string(abi.encodePacked("w", _collateralTokenContract.symbol())),
_collateralTokenContract.decimals(),
address(this) // wToken owner
);
address _wToken = address(_wTokenContract);
// Map collateral token to its corresponding wToken.
_collateralTokenToWToken[_collateralToken] = _wToken;
// Map wToken to its corresponding collateral token to facilitate reverse lookups.
_wTokenToCollateralToken[_wToken] = _collateralToken;
// Set unlimited approval for the wToken transfer to DIVA Protocol and the collateral token transfer to Aave V3. This setup reduces
// the need for repeated approval transactions, thereby saving on gas costs.
// The unlimited approvals are deemed safe as the `AaveDIVAWrapper` is a pass-through entity that does not hold excess wTokens or collateral tokens.
// Should a vulnerability be discovered in DIVA Protocol or Aave, users can simply stop interacting with the `AaveDIVAWrapper` contract.
//
// Note that granting an infinite allowance for wToken does not reduce the allowance on `transferFrom` as it uses a newer OpenZeppelin ERC20 implementation.
// However, this behavior may differ for collateral tokens like USDC, DAI, or WETH used in Aave. These tokens decrement the allowance with each use of
// `transferFrom`, even if an unlimited allowance is set. Consequently, though very unlikely, AaveDIVAWrapper could eventually exhaust its allowance.
// The `approveCollateralTokenForAave` function has been implemented to manually reset the allowance to unlimited.
_wTokenContract.approve(_diva, type(uint256).max);
_collateralTokenContract.approve(_aaveV3Pool, type(uint256).max);
emit CollateralTokenRegistered(_collateralToken, _wToken);
return _wToken;
}

Now, each time a user wants to deposit liquidity via AaveDIVAWrapper::addLiquidity() which calls AaveDIVAWrapperCore::_addLiquidity() which in turn checks that _collateralToken retrieved from poolId is not an address(0). It also calls AaveDIVAWrapperCore::_handleTokenOperations() which transfers the amount of collateralToken from msg.sender to AaveDIVAWrapperCore before providing to Aave via IAave(_aaveV3Pool).supply(). And finally IDIVA(_diva).addLiquidity() which adds collateral to an existing pool. Mints new long and short position tokens with supply equal to collateral amount added.

#AaveDIVAWrapper::addLiquidity()
#https://github.com/Cyfrin/2025-01-diva/blob/1b6543768c341c2334cdff87b6dd627ee2f62c89/contracts/src/AaveDIVAWrapper.sol#L21-L25
function addLiquidity(
bytes32 _poolId,
uint256 _collateralAmount,
address _longRecipient,
address _shortRecipient
) external override nonReentrant {
_addLiquidity(_poolId, _collateralAmount, _longRecipient, _shortRecipient);
}
#AaveDIVAWrapperCore::_addLiquidity()
#https://github.com/Cyfrin/2025-01-diva/blob/1b6543768c341c2334cdff87b6dd627ee2f62c89/contracts/src/AaveDIVAWrapperCore.sol#L167-L193
function _addLiquidity(
bytes32 _poolId,
uint256 _collateralAmount,
address _longRecipient,
address _shortRecipient
) internal {
// Verify that the collateral token used in the DIVA Protocol pool corresponds to a registered
// collateral token in the AaveDIVAWrapper contract. Returns zero address if the wToken is not registered.
IDIVA.Pool memory _pool = IDIVA(_diva).getPoolParameters(_poolId);
address _collateralToken = _wTokenToCollateralToken[_pool.collateralToken];
// Confirm that the collateral token is registered. This check is performed early
// to ensure an immediate and graceful revert rather than allowing execution to continue until the `mint`
// operation at the end of the `_handleTokenOperations` function, which would then fail when attempting to call
// the `mint` function on address(0).
if (_collateralToken == address(0)) {
revert CollateralTokenNotRegistered();
}
// Transfer collateral token from caller to this contract, supply to Aave, and mint wTokens
// to this contract.
_handleTokenOperations(_collateralToken, _collateralAmount, _pool.collateralToken);
// Add liquidity to the DIVA Protocol pool associated with the provided `_poolId`
// using the wToken and send the position tokens to the provided recipients.
IDIVA(_diva).addLiquidity(_poolId, _collateralAmount, _longRecipient, _shortRecipient);
}
#AaveDIVAWrapperCore::_handleTokenOperations()
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`.
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.
IWToken(_wToken).mint(address(this), _collateralAmount);
}

Impact

Users can lose their supplied collateral via AaveDIVAWrapper::addLiquidity() if Aave deprecates the collateral token after it has been registered on DIVA via AaveDIVAWrapperCore::_registerCollateralToken().

If between registration of collateral token and a call to add liquidity to pool Aave decides to deprecate a token the AaveDIVAWrapper::registerCollateralToken() / AaveDIVAWrapperCore::_registerCollateralToken() calls will pass, since the collateral's aToken can be retrieved from Aave, but the AaveDIVAWrapper::addLiquidity() doesn't check if the aToken for the collateralToken is still retrievable at time of adding liquidity, hence the supplier's collateral amount might get trapped in the AaveDIVAWrapperCore without them getting their positionTokens. Or maybe they will get their positionTokens,but at a loss to the protocol since they won't get their yield from Aave.

Tools Used

Manual Review.

Recommendations

Always check in AaveDIVAWrapper::addLiquidity() / AaveDIVAWrapperCore::_addLiquidity() that the aTokens for the collateralToken are retrievable from Aave.

Updates

Lead Judging Commences

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

Support

FAQs

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