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

Bad naming of wToken can lead to inconsistencies

Summary: The AaveDivaWrapperCore::_registerCollateralToken contract utilizes abi.encodePacked() to concatenate the string 'w' with _collateralTokenContract.symbol(). This method can lead to potential hash collisions when the contract is deployed on different chains with the same token, resulting in inconsistencies among WTokens.

Vulnerability Details: The improper concatenation of strings when creating WTokens can significantly confuse users of the platform. For instance, when the same collateral token, such as USDC, is registered on both Arbitrum Mainnet and Ethereum Mainnet, they share the same token symbol. When these tokens are registered as collateral in AaveDivaWrapper, it leads to the creation of WTokens that have identical names and symbols. While this may not pose a problem for DIVA, it can be highly confusing for users when they attempt to redeem their tokens using AaveDivaWrapperCore::_redeemWToken.

Proof of Concept:

I've created Foundry tests for that purpose

First of all, I installed forge-std and openzeppelin in foundry.
forge install foundry-rs/forge-std --no-commit and forge install openZeppelin/openzeppelin-contracts --no-commit
Used alchemy.
Then I added this to foundry.toml file

+remappings=['@openzeppelin/contracts=contracts/lib/openzeppelin-contracts/contracts']
+[rpc_endpoints]
+arbitrum = "https://arb-mainnet.g.alchemy.com/v2/<API_KEY>"
+ethereum = "https://eth-mainnet.g.alchemy.com/v2/<API_KEY>"

After the config preparation, I created a test folder and test file in this directory contracts/test/AaveDIVAWrapperFoundryTest.t.sol

Place the following code inside of AaveDIVAWrapperFoundryTest.t.sol file:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {Test, console} from "forge-std/Test.sol";
import {AaveDIVAWrapper} from "../src/AaveDIVAWrapper.sol";
import {IAaveDIVAWrapper} from "../src/interfaces/IAaveDIVAWrapper.sol";
import {IAave} from "../src/interfaces/IAave.sol";
import {IDIVA} from "../src/interfaces/IDIVA.sol";
import {ERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
contract AaveDIVAWrapperFoundryTest is Test {
AaveDIVAWrapper public aaveDIVAWrapper;
AaveDIVAWrapper public aaveDIVAWrapperETH;
IDIVA public diva;
IAave public aave;
ERC20 public collateralTokenContract;
address public owner;
address public dataProvider;
address public impersonatedSigner;
uint8 public collateralTokenDecimals;
uint8 public dummyTokenDecimals = 18;
/**
* Addresses on Arbutrum Mainnet
*
* DIVA arbitrumMain: "0x2C9c47E7d254e493f02acfB410864b9a86c28e1D"
* AaveV3 arbitrumMain: "0x794a61358D6845594F94dc1DB02A252b5b4814aD"
*
* collateralTokens
* USDC:
* address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
* holder: "0xE6fCC492FB5eA091bDBE2E1a9f163e5039F144BC" // Account to be impersonated during the tests
*
* unsupportedToken
* address: "0x7f9FBf9bDd3F4105C478b996B648FE6e828a1e98"
* description: "APE token (not supported on Aave)"
*
*/
address public constant DIVA_ADDRESS = 0x2C9c47E7d254e493f02acfB410864b9a86c28e1D; // Replace with actual DIVA contract address
address public constant AAVE_ADDRESS = 0x794a61358D6845594F94dc1DB02A252b5b4814aD; // Replace with actual AAVE contract address
address public constant COLLATERAL_TOKEN = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831; // 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9 Replace with USDT or another ERC20
address public constant COLLATERAL_TOKEN_HOLDER = 0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7; // 0xF977814e90dA44bFA03b6295A0616a897441aceC Address that holds collateral
address public constant collateralTokenEthereum = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
address public constant collateralTokenEthereumHolder = 0x37305B1cD40574E4C5Ce33f8e8306Be057fD7341;
function setUp() public {
vm.createSelectFork(vm.rpcUrl("arbitrum"));
owner = makeAddr("owner");
dataProvider = makeAddr("dataProvider");
// Impersonate an account that holds collateral tokens
vm.startPrank(COLLATERAL_TOKEN_HOLDER);
impersonatedSigner = COLLATERAL_TOKEN_HOLDER;
// Get collateral token contract instance
collateralTokenContract = ERC20(COLLATERAL_TOKEN);
collateralTokenDecimals = collateralTokenContract.decimals();
// Check balance
uint256 balance = collateralTokenContract.balanceOf(impersonatedSigner);
console.log("Balance: ", balance);
assertGt(balance, 1000 * (10 ** collateralTokenDecimals), "Insufficient balance for testing");
// Stop impersonation
vm.stopPrank();
// Deploy AaveDIVAWrapper
aaveDIVAWrapper = new AaveDIVAWrapper(DIVA_ADDRESS, AAVE_ADDRESS, owner);
// Get instances of DIVA and AAVE
diva = IDIVA(DIVA_ADDRESS);
aave = IAave(AAVE_ADDRESS);
// Approve AaveDIVAWrapper to spend collateralToken
vm.startPrank(impersonatedSigner);
collateralTokenContract.approve(address(aaveDIVAWrapper), type(uint256).max);
collateralTokenContract.approve(address(diva), type(uint256).max);
collateralTokenContract.approve(address(aave), type(uint256).max);
vm.stopPrank();
vm.createSelectFork(vm.rpcUrl("ethereum"));
address divaETH = 0x2C9c47E7d254e493f02acfB410864b9a86c28e1D;
address aaveETH = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;
collateralTokenContract = ERC20(collateralTokenEthereum);
aaveDIVAWrapperETH = new AaveDIVAWrapper(divaETH, aaveETH, owner);
vm.startPrank(collateralTokenEthereumHolder);
collateralTokenContract.approve(address(aaveDIVAWrapperETH), type(uint256).max);
collateralTokenContract.approve(divaETH, type(uint256).max);
collateralTokenContract.approve(aaveETH, type(uint256).max);
vm.stopPrank();
}
function testHashCollisionWhenRegisteringCollateralToken() public {
// First register the USDC token on Arbutrum
vm.selectFork(0);
vm.prank(owner);
aaveDIVAWrapper.registerCollateralToken(COLLATERAL_TOKEN);
ERC20 wTokenArb = ERC20(aaveDIVAWrapper.getWToken(COLLATERAL_TOKEN));
// Log the collisions
console.log("Collision 1: %s", wTokenArb.symbol());
console.log("Hash 1: %s", toHexString(keccak256(abi.encodePacked(wTokenArb.name()))));
// Then register the same token again, but on Ethereum
vm.selectFork(1);
vm.prank(owner);
aaveDIVAWrapperETH.registerCollateralToken(collateralTokenEthereum);
ERC20 wTokenEth = ERC20(aaveDIVAWrapperETH.getWToken(collateralTokenEthereum));
// Log the collisions
console.log("Collision 2: %s", wTokenEth.symbol());
console.log("Hash 2: %s", toHexString(keccak256(abi.encodePacked(wTokenEth.name()))));
}
function toHexString(bytes32 data) internal pure returns (string memory) {
bytes memory hexChars = "0123456789abcdef";
bytes memory str = new bytes(64);
for (uint256 i = 0; i < 32; i++) {
str[i * 2] = hexChars[uint8(data[i] >> 4)];
str[1 + i * 2] = hexChars[uint8(data[i] & 0x0f)];
}
return string(str);
}
}

Impact: This results in potential hash collisions when the contract is used on different chains with the same token, which could lead to inconsistencies with WToken. Additionally, this can result in a poor user experience and lead to incorrect decisions.

Tools Used: Primarily manual review, with some use of Aderyn.

Recommendations: Consider implementing a better naming convention. aTokens retrieved from Aave V3 can address this issue by prepending the name of the chain to the collateral token. For example, the symbol and name for USDC on Arbitrum is aArbUSDC, whereas on Ethereum it is aEthUSDC. This helps users avoid mistakes.

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())),
+ string(abi.encodePacked("w", IERC20Metadata(_aToken).symbol())),
_collateralTokenContract.decimals(),
address(this) // wToken owner
);
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;
}
Updates

Lead Judging Commences

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

Support

FAQs

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