Description: If the Meowntainer
deposits some collateral to the aavePool
, when the function KittyVault::getTotalMeowllateralInAave
is called returns a different value than the total collateral deposited in aavePool
so when this is used to calculate the amount the user wants to withdraw in KittyVault::executeWhiskdrawal
function, because of the bad return value, will calculate the value to withdraw based mostly on the collateral inside the vault, not the collateral in the aavePool
. This makes unabalance all the cattyNips
representing the collateral of each user, as the user that withdraw will get a less amount that he wanted to withdraw and the other collateral of the other users will increase due to this miscalculation.
FUNCTIONS
KittyVault::getTotalMeowllateralInAave
* @notice Gets the total sum of collateral deposited in Aave and the collateral earned by interest from Aave
*/
function getTotalMeowllateralInAave() public view returns (uint256) {
(uint256 totalCollateralBase, , , , , ) = i_aavePool.getUserAccountData(address(this));
(, int256 collateralToUsdPrice, , , ) = i_priceFeed.latestRoundData();
return totalCollateralBase.mulDiv(PRECISION, uint256(collateralToUsdPrice) * EXTRA_DECIMALS);
}
KittyVault::executeWhiskdrawal
* @param _user The user who wants to withdraw collateral
* @param _cattyNipToWithdraw The amount of shares corresponding to collateral to withdraw
*/
function executeWhiskdrawal(address _user, uint256 _cattyNipToWithdraw) external onlyPool {
uint256 _ameownt = _cattyNipToWithdraw.mulDiv(getTotalMeowllateral(), totalCattyNip);
userToCattyNip[_user] -= _cattyNipToWithdraw;
totalCattyNip -= _cattyNipToWithdraw;
totalMeowllateralInVault -= _ameownt;
IERC20(i_token).safeTransfer(_user, _ameownt);
}
Impact: cattyNips
not represent the collateral you deposit.
Proof of Concept: First you need to paste the next modifier to the KittyFiTest.t.sol
.
MODIFIER
First 4 users deposit 5 ether
each and mint 20 kittyCoins
.
address user2 = makeAddr("user2");
address user3 = makeAddr("user3");
address user4 = makeAddr("user4");
modifier usersDepositAndMint {
uint256 toDeposit = 5 ether;
uint256 amountToMint = 20e18;
deal(weth, user2, 10 ether);
deal(weth, user3, 10 ether);
deal(weth, user4, 10 ether);
vm.startPrank(user);
IERC20(weth).approve(address(wethVault), toDeposit);
kittyPool.depawsitMeowllateral(weth, toDeposit);
kittyPool.meowintKittyCoin(amountToMint);
vm.stopPrank();
vm.startPrank(user2);
IERC20(weth).approve(address(wethVault), toDeposit);
kittyPool.depawsitMeowllateral(weth, toDeposit);
kittyPool.meowintKittyCoin(amountToMint);
vm.stopPrank();
vm.startPrank(user3);
IERC20(weth).approve(address(wethVault), toDeposit);
kittyPool.depawsitMeowllateral(weth, toDeposit);
kittyPool.meowintKittyCoin(amountToMint);
vm.stopPrank();
vm.startPrank(user4);
IERC20(weth).approve(address(wethVault), toDeposit);
kittyPool.depawsitMeowllateral(weth, toDeposit);
kittyPool.meowintKittyCoin(amountToMint);
vm.stopPrank();
_;
}
TEST
The Meowntainer
deposits 3/4 of the collateral (15 ether
) to the aavePool
.
user
burns his kittyCoins
and wants to withdraw
the total collateral(5 cattyNips == 5 ether
).
It gets 1/4 of the 5 ether
remaining on the vault.
The collateral of the users reduced at the moment to 1/4 of the 5 ether
on vault, not counting the value deposited on aavePool
.
The return of getTotalMeowllateralInAave()
function is way less than 15 ether
.
function testCollateralAaveReturnsBadData() public usersDepositAndMint {
uint256 oneUserDeposit = 5 ether;
uint256 oneUserMint = 20e18;
uint256 toSupply = 5 ether;
console.log("Total collateral in vault: ", wethVault.totalMeowllateralInVault());
vm.prank(meowntainer);
wethVault.purrrCollateralToAave(toSupply*3);
console.log("Total collateral in vault: ", wethVault.totalMeowllateralInVault());
console.log("Total collateral in aave: ", wethVault.getTotalMeowllateralInAave());
console.log("Total collateral in vault + aave: ", wethVault.getTotalMeowllateral());
console.log("User collateral: ", wethVault.getUserMeowllateral(user));
console.log("User2 collateral: ", wethVault.getUserMeowllateral(user2));
console.log("User3 collateral: ", wethVault.getUserMeowllateral(user3));
console.log("User4 collateral: ", wethVault.getUserMeowllateral(user4));
vm.startPrank(user);
kittyPool.burnKittyCoin(user, oneUserMint);
kittyPool.whiskdrawMeowllateral(weth, oneUserDeposit);
console.log("Total cattyNip (after user withdraw): ", wethVault.totalCattyNip());
console.log("Total collateral in vault: (after user withdraw)", wethVault.totalMeowllateralInVault());
console.log("Total collateral in aave: ", wethVault.getTotalMeowllateralInAave());
console.log("Total collateral in vault + aave: ", wethVault.getTotalMeowllateral());
console.log("User collateral: ", wethVault.getUserMeowllateral(user));
console.log("User2 collateral: ", wethVault.getUserMeowllateral(user2));
console.log("User3 collateral: ", wethVault.getUserMeowllateral(user3));
console.log("User4 collateral: ", wethVault.getUserMeowllateral(user4));
vm.stopPrank();
assert(IERC20(weth).balanceOf(user) < AMOUNT);
assertEq(wethVault.getUserMeowllateral(user), 0);
assert(wethVault.getUserMeowllateral(user2) < oneUserDeposit);
assert(wethVault.getUserMeowllateral(user3) < oneUserDeposit);
assert(wethVault.getUserMeowllateral(user4) < oneUserDeposit);
assert(wethVault.getTotalMeowllateralInAave() < toSupply*2);
}
Recommended Mitigation: Inside the function KittyVault::getTotalMeowllateralInAave
instead of using AavePool::getUserAccountData
you can use the balance of the tokens you get minted when you deposit collateral to aave, representing the value with a relation 1:1
. You will get the right amount. And according to the docs of aave
AaveDocs you can use this to get the latest balance of the pool including the interest:
AToken
aTokens are tokens minted and burnt upon supply and withdraw of assets to an Aave market, which denote the amount of crypto assets supplied and the yield earned on those assets. The aTokens’ value is pegged to the value of the corresponding supplied asset at a 1:1 ratio and can be safely stored, transferred or traded. All yield collected by the aTokens' reserves are distributed to aToken holders directly by continuously increasing their wallet balance.
EIP20 Methods
All standard EIP20 methods are implemented for aTokens, such as balanceOf, transfer, transferFrom, approve, totalSupply etc.
💡 balanceOf
will always return the most up to date balance of the user, which includes their principal balance + the yield generated by the principal balance.
CODE MODIFICATIONS
function getTotalMeowllateralInAave() public view returns (uint256) {
- (uint256 totalCollateralBase, , , , , ) = i_aavePool.getUserAccountData(address(this));
+ ( , , address aTokenAddress, , , ) = i_aavePool.getReserveTokensAddresses(address(this));
+ uint256 totalCollateralBase = IERC20(aTokenAddress).balanceOf(address(this));
+ return totalCollateralBase;
- (, int256 collateralToUsdPrice, , , ) = i_priceFeed.latestRoundData();
- return totalCollateralBase.mulDiv(PRECISION, uint256(collateralToUsdPrice) * EXTRA_DECIMALS);
}