First Flight #21: KittyFi

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

The liquidate function: purrgeBadPawsition(KittyPool.sol) should take vaultCollateral amount instead of vaultCollateral Value During the calculation.

Summary

purrgeBadPawsition(KittyPool.sol) mistakenly use VaultMeowllateralInEuros instead of VaultMeowllateral amount, which leads to the wrong vaultCollateral for the following steps, then can't execuse purrgeBadPawsition successfully.

Vulnerability Details

The liquidation logic firstly is to calculate the redeemPercent of the user's vaultCollateral based on the current collateral percent(<169)

Then calcuating how much collateral token to the liquidator, should based on the liquidated user's collateral amount, not the collateral euro value.

If based on the collateral euro value, the vaultCollateral value will greater than the actual value, Then calling the executeWhiskdrawal function will beyond liquidator's cattyNip(share).

/**
* @notice Liquidates the bad debt position of the user
* @param _user address of the user
*/
function purrgeBadPawsition(address _user) external returns (uint256 _totalAmountReceived) {
require(!(_hasEnoughMeowllateral(_user)), KittyPool__UserIsPurrfect());
uint256 totalDebt = kittyCoinMeownted[_user];
kittyCoinMeownted[_user] = 0;
i_kittyCoin.burn(msg.sender, totalDebt);
uint256 userMeowllateralInEuros = getUserMeowllateralInEuros(_user);
uint256 redeemPercent;
if (totalDebt >= userMeowllateralInEuros) {
redeemPercent = PRECISION;
}
else {
redeemPercent = totalDebt.mulDiv(PRECISION, userMeowllateralInEuros);
}
uint256 vaults_length = vaults.length;
for (uint256 i; i < vaults_length; ) {
IKittyVault _vault = IKittyVault(vaults[i]);
// There should use _vault.getUserMeowllateral(), becasue for the following executeWhiskdrawal
// function, which take "The amount of shares corresponding to collateral to withdraw", not the actual Euro Value
uint256 vaultCollateral = _vault.getUserVaultMeowllateralInEuros(_user);
uint256 toDistribute = vaultCollateral.mulDiv(redeemPercent, PRECISION);
uint256 extraCollateral = vaultCollateral - toDistribute;
uint256 extraReward = toDistribute.mulDiv(REWARD_PERCENT, PRECISION);
extraReward = Math.min(extraReward, extraCollateral);
_totalAmountReceived += (toDistribute + extraReward);
_vault.executeWhiskdrawal(msg.sender, toDistribute + extraReward);
unchecked {
++i;
}
}
}

Impact

No one can execute purrgeBadPawsition successfully

The protocol can't guarantee the kittyCoin's price, such as when user's collateral value under the requirements. No ways to matain the totoal collateral value.

POC Test

function test_poc_purrgeBadPawsition() public {
// For test, should create new kittyPool, and make the mockWethToken as the collotral
kittyPool = new KittyPool(
meowntainer,
config.euroPriceFeed,
config.aavePool
);
kittyCoin = KittyCoin(kittyPool.getKittyCoin());
// mock wethVault
ERC20Mock mockWethToken = new ERC20Mock();
MockV3Aggregator wethPriceFeed = new MockV3Aggregator(8, 20000e8);
// take mockWethToken as collotral
vm.prank(meowntainer);
kittyPool.meownufactureKittyVault(
address(mockWethToken),
address(wethPriceFeed)
);
KittyVault wethMockVault = KittyVault(
kittyPool.getTokenToVault(address(mockWethToken))
);
// user1 depawsitMeowllateral 0.1 ether, and mint almostMaxKittyCoins
address user1 = makeAddr("user1");
AMOUNT = 1 ether;
mockWethToken.mint(user1, AMOUNT);
uint256 toDeposit = 1 ether;
vm.startPrank(user1);
mockWethToken.approve(address(wethMockVault), toDeposit);
kittyPool.depawsitMeowllateral(address(mockWethToken), toDeposit);
uint256 amountToMint = getAlmostMaxKittyTokenForMockwethVault(
wethMockVault,
user1
);
kittyPool.meowintKittyCoin(amountToMint);
vm.stopPrank();
// console.log("user minted kittyCoins", amountToMint);
// liquidator get the same kittyCoins by depoisted the same colloateral as user1
address liquidator = makeAddr("liquidator");
mockWethToken.mint(liquidator, AMOUNT);
vm.startPrank(liquidator);
mockWethToken.approve(address(wethMockVault), toDeposit);
kittyPool.depawsitMeowllateral(address(mockWethToken), toDeposit);
kittyPool.meowintKittyCoin(amountToMint);
vm.stopPrank();
// when weth price = 2000 USD, show user1 collotrall and debt(kittyCoins)
console.log("When ETH price 2000");
console.log("user1 collateral info........................start");
uint256 user1VaultMeowllateralInEuros = wethMockVault
.getUserVaultMeowllateralInEuros(user1);
console.log("collllaterl Vaule in EUR", user1VaultMeowllateralInEuros);
uint256 user1lDebtForKittyCoins = kittyCoin.balanceOf(user1);
console.log("kittyCoins", user1lDebtForKittyCoins);
console.log(
"user1 collateral_percent",
(user1VaultMeowllateralInEuros * 100) / user1lDebtForKittyCoins
);
console.log("user1 collateral info........................end");
// make the price down, 2000=>1000
wethPriceFeed.updateAnswer(12000e8);
// wethPriceFeed.updateAnswer(10000e8);// this situation also can't run
console.log("Change ETH price 2000=>1200");
console.log(
"Show user1's current collateral percent...................start"
);
user1VaultMeowllateralInEuros = wethMockVault
.getUserVaultMeowllateralInEuros(user1);
console.log("collllaterl Vaule in EUR", user1VaultMeowllateralInEuros);
user1lDebtForKittyCoins = kittyCoin.balanceOf(user1);
console.log("kittyCoins", user1lDebtForKittyCoins);
console.log(
"user1 collateral_percent",
(user1VaultMeowllateralInEuros * 100) / user1lDebtForKittyCoins
);
console.log(
"Show user1's current collateral percent...................end"
);
// show liquidator MeowllateralInfo
console.log(
"liquidator weth balance",
mockWethToken.balanceOf(liquidator)
);
console.log(
"liquidator kittyCoins balance",
kittyCoin.balanceOf(liquidator)
);
// As user1's collateral percent < 169, liquidator can liquidate user1's collateral
vm.startPrank(liquidator);
kittyPool.purrgeBadPawsition(user1);
vm.stopPrank();
console.log("after liquuidation");
console.log(
"liquidator weth balance",
mockWethToken.balanceOf(liquidator)
);
console.log(
"liquidator kittyCoins balance",
kittyCoin.balanceOf(liquidator)
);
}
function getAlmostMaxKittyTokenForMockwethVault(
KittyVault wethMockVault,
address user
) internal view returns (uint256) {
return
(wethMockVault.getUserVaultMeowllateralInEuros(user) * 100) /
169 -
1e18;
}

Tools Used

Manual

Recommendations

  1. IKittyVault add getUserMeowllateral intereface.

  2. Apply _vault.getUserMeowllateral(_user); instead of _vault.getUserVaultMeowllateralInEuros(_user).

interface IKittyVault {
function getUserVaultMeowllateralInEuros(
address _user
) external view returns (uint256);
function executeDepawsit(address _user, uint256 _ameownt) external;
function executeWhiskdrawal(address _user, uint256 _ameownt) external;
// add this getUserMeowllateral.
function getUserMeowllateral(address _user) external view returns (uint256);
}
function purrgeBadPawsition(address _user) external returns (uint256 _totalAmountReceived) {
require(!(_hasEnoughMeowllateral(_user)), KittyPool__UserIsPurrfect());
uint256 totalDebt = kittyCoinMeownted[_user];
kittyCoinMeownted[_user] = 0;
i_kittyCoin.burn(msg.sender, totalDebt);
uint256 userMeowllateralInEuros = getUserMeowllateralInEuros(_user);
uint256 redeemPercent;
if (totalDebt >= userMeowllateralInEuros) {
redeemPercent = PRECISION;
}
else {
redeemPercent = totalDebt.mulDiv(PRECISION, userMeowllateralInEuros);
}
uint256 vaults_length = vaults.length;
for (uint256 i; i < vaults_length; ) {
IKittyVault _vault = IKittyVault(vaults[i]);
// Apply getUserMeowllateral instead of getUserVaultMeowllateralInEuros
//uint256 vaultCollateral = _vault.getUserVaultMeowllateralInEuros(_user);
uint256 vaultCollateral = _vault.getUserMeowllateral(_user);
uint256 toDistribute = vaultCollateral.mulDiv(redeemPercent, PRECISION);
uint256 extraCollateral = vaultCollateral - toDistribute;
uint256 extraReward = toDistribute.mulDiv(REWARD_PERCENT, PRECISION);
extraReward = Math.min(extraReward, extraCollateral);
_totalAmountReceived += (toDistribute + extraReward);
_vault.executeWhiskdrawal(msg.sender, toDistribute + extraReward);
unchecked {
++i;
}
}
}
Updates

Lead Judging Commences

shikhar229169 Lead Judge
10 months ago
shikhar229169 Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

`purrgeBadPawsition` calculates collateral to be distributed in terms of euros, which is incorrect.

Appeal created

bytesflow007 Submitter
10 months ago
shikhar229169 Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

`purrgeBadPawsition` calculates collateral to be distributed in terms of euros, which is incorrect.

Support

FAQs

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