First Flight #21: KittyFi

First Flight #21
Beginner FriendlyDeFiFoundry
100 EXP
View results
Submission Details
Severity: high
Invalid

`KittyVault::getTotalMeowllateralInAave` function is returning bad data, distribution of shares becomes totally unbalanced.

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

  1. 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; // 20 KittyCoin
deal(weth, user2, 10 ether);
deal(weth, user3, 10 ether);
deal(weth, user4, 10 ether);
// Deposits and mint collateral of the four users
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

  1. The Meowntainer deposits 3/4 of the collateral (15 ether) to the aavePool.

  2. user burns his kittyCoins and wants to withdraw the total collateral(5 cattyNips == 5 ether).

  3. 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());
// Put collateral to ave
vm.prank(meowntainer);
wethVault.purrrCollateralToAave(toSupply*3);
// Consoles
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);
// Burn the amount of kitty coin
kittyPool.burnKittyCoin(user, oneUserMint);
// Withdraw the collateral
kittyPool.whiskdrawMeowllateral(weth, oneUserDeposit);
// Consoles
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();
// assertions
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);
}
Updates

Lead Judging Commences

shikhar229169 Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Too generic

Support

FAQs

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