The Wildcat Protocol - AS's results

Banking, but worse - a protocol for fixed-rate, undercollateralised credit facilities.

General Information

Platform: Code4rena

Start Date: 16/10/2023

Pot Size: $60,500 USDC

Total HM: 16

Participants: 131

Period: 10 days

Judge: 0xTheC0der

Total Solo HM: 3

Id: 296

League: ETH

Wildcat Protocol

Findings Distribution

Researcher Performance

Rank: 103/131

Findings: 1

Award: $6.67

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

6.6715 USDC - $6.67

Labels

bug
3 (High Risk)
satisfactory
edited-by-warden
duplicate-68

External Links

Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketWithdrawals.sol#L166-L170 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketBase.sol#L172-L176

Vulnerability details

Impact

When lender is removed from sanction list and try to release escrow, escrow's balance(asset, market token) will be released to borrower rather than lender, so this compromise market and user's balance.

Proof of Concept

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketWithdrawals.sol#L166-L170

In executeWithdrawal of WildcatMarketWithdrawals.sol

      address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow(
        accountAddress,
        borrower,
        address(asset)
      );

In _blockAcount of WildMarketBase.sol https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketBase.sol#L172-L176

        address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow(
          accountAddress,
          borrower,
          address(this)
        );

Above code blocks will record borrower as account on escrow.

When the lender is released from sanction list and try to release escrow, balance will be transferred to borrower. https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatSanctionsEscrow.sol#L33-L41

  function releaseEscrow() public override {
    if (!canReleaseEscrow()) revert CanNotReleaseEscrow();

    uint256 amount = balance();

    IERC20(asset).transfer(account, amount);

    emit EscrowReleased(account, asset, amount);
  }

The test blow illustrates the scenario above

    import "./BaseMarketTest.sol";
    import {MockChainalysis} from "./helpers/MockChainalysis.sol";
    import {WildcatSanctionsEscrow, IWildcatSanctionsEscrow} from "src/WildcatSanctionsEscrow.sol";

    contract EscrowSanctionTest is BaseMarketTest {
        using MathUtils for uint256;
        using FeeMath for uint256;

        function test_executeWithdrawal_Sanctioned() external {
            _deposit(alice, 1e18);
            _requestWithdrawal(alice, 1e18);
            fastForward(parameters.withdrawalBatchDuration);
            sanctionsSentinel.sanction(alice);
            IWildcatSanctionsEscrow escrow = IWildcatSanctionsEscrow(
                sanctionsSentinel.getEscrowAddress(alice, borrower, address(asset))
            );
            vm.expectEmit(address(asset));
            emit Transfer(address(market), address(escrow), 1e18);
            vm.expectEmit(address(market));
            emit SanctionedAccountWithdrawalSentToEscrow(
                alice,
                address(escrow),
                uint32(block.timestamp),
                1e18
            );
            market.executeWithdrawal(alice, uint32(block.timestamp));
            uint256 prevAliceAmount = asset.balanceOf(alice);
            uint256 prevBorrowerAmount = asset.balanceOf(borrower);
            uint256 escrowAmount = asset.balanceOf(address(escrow));
            sanctionsSentinel.unsanction(alice);
            assertEq(escrow.canReleaseEscrow(), true, "sanction isn't removed");
            escrow.releaseEscrow();
            assertEq(
                asset.balanceOf(alice),
                prevAliceAmount + escrowAmount,
                "assets wasn't be released to alice"
            );
            assertNotEq(
                asset.balanceOf(borrower),
                prevBorrowerAmount + escrowAmount,
                "assets was released to borrower"
            );
        }
    }

Added below code block into MockSanctionsSentinel.sol for testing

function unsanction(address account) external { MockChainalysis(chainalysisSanctionsList).unsanction(account); }

Above code can be fixed simply like below In executeWithdrawal in WildcatMarketWithdrawals.sol

      address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow(
        borrower,
        accountAddress,
        address(asset)
      );
        address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow(
          borrower,
          accountAddress,
          address(this)
        );

Tools Used

Visual Studio / Manual Review

Assessed type

Token-Transfer

#0 - c4-pre-sort

2023-10-27T02:44:19Z

minhquanym marked the issue as duplicate of #515

#1 - c4-judge

2023-11-07T12:06:45Z

MarioPoneder marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter