First Flight #21: KittyFi

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

Inadequate Collateral Check Order whiskdrawMeowllateral()

Summary

The whiskdrawMeowllateral function performs the withdrawal action before checking if the user has enough collateral to maintain their position. This sequence can lead to a scenario where a user initiates a withdrawal, decreasing their collateral below the required level before the check is made. If the check fails, the transaction reverts, but the state changes related to the withdrawal may already have been executed.

Vulnerability Details

https://github.com/Cyfrin/2024-08-kitty-fi/blob/950ac553b935a3bf9277b71ffa5662a84f2633fe/src/KittyPool.sol#L92

function whiskdrawMeowllateral(address _token, uint256 _ameownt) external tokenExists(_token) {
IKittyVault(tokenToVault[_token]).executeWhiskdrawal(msg.sender, _ameownt);
require(_hasEnoughMeowllateral(msg.sender), KittyPool__NotEnoughMeowllateralPurrrr());
}

POC

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "forge-std/Test.sol";
import "./KittyPool.sol";
import "./KittyVault.sol";
import "./KittyCoin.sol";
contract KittyPoolTest is Test {
KittyPool public kittyPool;
KittyVault public kittyVault;
KittyCoin public kittyCoin;
ERC20 public collateralToken;
address public user = address(1);
address public maintainer = address(2);
address public priceFeed = address(3);
address public aavePool = address(4);
function setUp() public {
// Deploying the contracts
kittyCoin = new KittyCoin(address(this));
kittyPool = new KittyPool(maintainer, priceFeed, aavePool, address(kittyCoin));
kittyVault = new KittyVault(address(collateralToken), address(kittyPool), priceFeed, priceFeed, maintainer, aavePool);
// Assume the collateralToken is already deployed and has some initial supply
collateralToken = new ERC20("CollateralToken", "CTKN");
// Mint and distribute collateral tokens
collateralToken.mint(user, 1000 ether);
// User deposits collateral and mints KittyCoin
vm.startPrank(user);
collateralToken.approve(address(kittyPool), 500 ether);
kittyPool.depawsitMeowllateral(address(collateralToken), 500 ether);
vm.stopPrank();
}
function testExploitWhiskdrawMeowllateral() public {
// User withdraws a portion of the collateral
vm.startPrank(user);
// The user tries to withdraw more collateral than allowed
uint256 withdrawalAmount = 400 ether;
try kittyPool.whiskdrawMeowllateral(address(collateralToken), withdrawalAmount) {
assertTrue(false, "Withdrawal should have reverted");
} catch Error(string memory reason) {
assertEq(reason, "KittyPool__NotEnoughMeowllateralPurrrr()");
}
// Check the user's balance after the failed withdrawal attempt
uint256 userBalanceAfter = collateralToken.balanceOf(user);
assertEq(userBalanceAfter, 100 ether, "User should still have 100 ether of collateral");
vm.stopPrank();
}
}
Impact

Users might be able to withdraw more collateral than allowed, jeopardizing the over-collateralization requirement of the protocol.

Tools Used

Manual review

Recommendations

Perform the collateral adequacy check before executing the withdrawal to ensure that the user has enough collateral to support their position throughout the transaction.

function whiskdrawMeowllateral(address _token, uint256 _ameownt) external tokenExists(_token) {
require(_hasEnoughMeowllateral(msg.sender), KittyPool__NotEnoughMeowllateralPurrrr());
IKittyVault(tokenToVault[_token]).executeWhiskdrawal(msg.sender, _ameownt);
}
Updates

Lead Judging Commences

shikhar229169 Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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