Summary
The _registerCollateralToken function is responsible for registering a collateral token and creating a wrapped version of it (wToken) to be used in the DIVA Protocol. However, it assumes that all ERC-20 tokens return string symbols, which is incorrect. Some tokens, such as MKR (MakerDAO’s token), WBTC, and others, return bytes32 instead of string, causing the function to revert unexpectedly when symbol() is called.
Vulnerability Details
function _registerCollateralToken(address _collateralToken) internal returns (address) {
if (_collateralTokenToWToken[_collateralToken] != address(0)) {
revert CollateralTokenAlreadyRegistered();
}
address _aToken = _getAToken(_collateralToken);
if (_aToken == address(0)) {
revert UnsupportedCollateralToken();
}
IERC20Metadata _collateralTokenContract = IERC20Metadata(_collateralToken);
WToken _wTokenContract = new WToken(
@> string(abi.encodePacked("w", _collateralTokenContract.symbol())),
_collateralTokenContract.decimals(),
address(this)
);
address _wToken = address(_wTokenContract);
_collateralTokenToWToken[_collateralToken] = _wToken;
_wTokenToCollateralToken[_wToken] = _collateralToken;
_wTokenContract.approve(_diva, type(uint256).max);
_collateralTokenContract.approve(_aaveV3Pool, type(uint256).max);
emit CollateralTokenRegistered(_collateralToken, _wToken);
return _wToken;
}
The function above retrieves the symbol from _collateralTokenContract.symbol(), If the token returns bytes32 instead of string, calling .symbol() reverts since Solidity cannot convert bytes32 directly into a string. This is an issue because the protocol is assumed to work with all collateral tokens on Aave, but the **MKR (MakerDAO’s token), which is one of the collateral tokens on Aave(Ethereum Mainnet), returns bytes32 instead of string, the above code guarantees that the owner will never be able to register MKR as a collateral token on AaveDIVAWrapper.
Impact
The function fails entirely for affected tokens, preventing their registration as collateral.
Tools Used
Manual Review
Recommendations
Use a helper function to safely convert bytes32 to string.
function _safeSymbol(IERC20Metadata token) internal view returns (string memory) {
(bool success, bytes memory data) = address(token).staticcall(
abi.encodeWithSignature("symbol()")
);
if (success && data.length > 0) {
if (data.length == 32) {
return _bytes32ToString(abi.decode(data, (bytes32)));
} else {
return abi.decode(data, (string));
}
}
return "UNKNOWN";
}
function _bytes32ToString(bytes32 _bytes32) internal pure returns (string memory) {
uint8 i = 0;
while (i < 32 && _bytes32[i] != 0) {
i++;
}
bytes memory bytesArray = new bytes(i);
for (uint8 j = 0; j < i; j++) {
bytesArray[j] = _bytes32[j];
}
return string(bytesArray);
}
function _registerCollateralToken(address _collateralToken) internal returns (address) {
// Verify that the collateral token is not yet registered.
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();
}
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
- );
+ string memory tokenSymbol = _safeSymbol(_collateralTokenContract);
+ WToken _wTokenContract = new WToken(
+ string(abi.encodePacked("w", tokenSymbol)),
+ _collateralTokenContract.decimals(),
+ address(this)
+ );
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;
}