The Wildcat Protocol - HChang26'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: 48/131

Findings: 3

Award: $104.42

🌟 Selected for report: 0

🚀 Solo Findings: 0

Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarket.sol#L142

Vulnerability details

Impact

The platform experiences a critical issue where markets are unable to be closed as intended. This leads to undesirable consequences, including the continuous ability for lenders to make deposits and collect interest, even when it should not be allowed.

Proof of Concept

The issue at hand is in direct contradiction to the documentation, which indicates that borrowers should have the ability to close a market when necessary. Per the documentation:

In the event that a borrower has finished utilising the funds for the purpose that the market was set up to facilitate (or if lenders are choosing not to withdraw their assets and the borrower is paying too much interest on assets that have been re-deposited to the market), the borrower can close a market at will.

closeMarket() can only be called by WildcatMarketController due to onlyController modifier.

  function closeMarket() external onlyController nonReentrant {

However, closeMarket() is not implemented anywhere on WildcatMarketController.sol. This function is not accessible to anyone and lenders can continue to deposit() funds and borrower will have to pay interest on funds they are not utilizing.

Tools Used

Manual Review

Implement _closeMarket() on WilcatMarketController.sol

  function _closeMarket() external onlyBorrower {
    closeMarket();
  }

Assessed type

Context

#0 - c4-pre-sort

2023-10-27T07:18:09Z

minhquanym marked the issue as duplicate of #147

#1 - c4-judge

2023-11-07T14:04:54Z

MarioPoneder marked the issue as partial-50

#2 - c4-judge

2023-11-07T14:16:54Z

MarioPoneder changed the severity to 3 (High Risk)

Awards

13.1205 USDC - $13.12

Labels

bug
3 (High Risk)
satisfactory
upgraded by judge
duplicate-266

External Links

Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketToken.sol#L64 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketConfig.sol#L74 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketController.sol#L182 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketConfig.sol#L112 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketWithdrawals.sol#L77

Vulnerability details

Impact

Users designated as "sanctioned" by the Chainalysis oracle can effectively circumvent their sanctioned status, enabling them to accrue interest and withdraw funds without restriction.

Proof of Concept

The Chainalysis oracle identifies users as "sanctioned" based on specific criteria. However, the market does not immediately register this status until a user initiates the nukeFromOrbit() function. This function transfers a sanctioned user's market tokens into an escrow. These tokens are effectively locked until the user's sanctioned status is overridden.

The existing security mechanism fails to entirely prevent sanctioned users from accessing their tokens. The core issue lies within the _transfer() function in WildcatMarketToken.sol. A sanctioned user can easily front-run the nukeFromOrbit() function and then use transfer() to send their tokens to another one of their accounts. This manipulation bypasses the security checks since the account.approval status is not yet set to AuthRole.Blocked.

  function _transfer(address from, address to, uint256 amount) internal virtual {
    MarketState memory state = _getUpdatedState();
    uint104 scaledAmount = state.scaleAmount(amount).toUint104();

    if (scaledAmount == 0) {
      revert NullTransferAmount();
    }

    Account memory fromAccount = _getAccount(from);
    fromAccount.scaledBalance -= scaledAmount;
    _accounts[from] = fromAccount;

    Account memory toAccount = _getAccount(to);
    toAccount.scaledBalance += scaledAmount;
    _accounts[to] = toAccount;

    _writeState(state);
    emit Transfer(from, to, amount);
  }

Once this transfer is executed, the user can call updateLenderAuthorization(), which triggers updateAccountAuthorization(). This, in turn, permits the user to access queueWithdrawal(). The outcome is that the "sanctioned" user can effectively avoid being banned from the market, continuing to accumulate interest and withdraw funds as if they were a regular, unsanctioned user.

  function updateAccountAuthorization(
    address _account,
    bool _isAuthorized
  ) external onlyController nonReentrant {
    MarketState memory state = _getUpdatedState();
    Account memory account = _getAccount(_account);
    if (_isAuthorized) {
      account.approval = AuthRole.DepositAndWithdraw;
    } else {
      account.approval = AuthRole.WithdrawOnly;
    }
    _accounts[_account] = account;
    _writeState(state);
    emit AuthorizationStatusUpdated(_account, account.approval);
  }

Tools Used

Manual Review

import { WildcatSanctionsSentinel } from './WildcatSanctionsSentinel.sol' into WildcatMarketToken.sol initialize it with correct address and add isSanctioned() checks for both from and to.

Assessed type

Context

#0 - c4-pre-sort

2023-10-27T08:07:19Z

minhquanym marked the issue as duplicate of #54

#1 - c4-judge

2023-11-07T14:36:22Z

MarioPoneder changed the severity to 3 (High Risk)

#2 - c4-judge

2023-11-07T14:41:36Z

MarioPoneder marked the issue as satisfactory

Findings Information

🌟 Selected for report: osmanozdemir1

Also found by: 0xCiphky, 0xStalin, HChang26, Infect3d, Jiamin, Juntao, QiuhaoLi, circlelooper, crunch, rvierdiiev

Labels

bug
2 (Med Risk)
satisfactory
edited-by-warden
duplicate-550

Awards

91.2409 USDC - $91.24

External Links

Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L163 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketConfig.sol#L74 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/MarketState.sol#L87 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/MarketState.sol#L123 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/MarketState.sol#L138

Vulnerability details

Impact

The _blockAccount() function fails to accurately adjust state.scaledTotalSupply, resulting in a cascade of accounting inaccuracies. This miscalculation leads to inflated figures for borrowable assets, liquidity requirements, and total debt

Proof of Concept

The Chainalysis oracle designates users as "sanctioned" if they meet certain criteria. The key issue revolves around the _blockAccount() function, which can be invoked by anyone through the nukeFromOrbit() method:

In this process, account.scaledBalance is reset to 0, and a corresponding escrow is created. The scaledBalance value is then transferred into the escrow's _accounts[escrow].scaledBalance. It's important to note that state.scaledTotalSupply for the entire market is not reduced accordingly.

  function _blockAccount(MarketState memory state, address accountAddress) internal {
    Account memory account = _accounts[accountAddress];
    if (account.approval != AuthRole.Blocked) {
      uint104 scaledBalance = account.scaledBalance;
      account.approval = AuthRole.Blocked;
      emit AuthorizationStatusUpdated(accountAddress, AuthRole.Blocked);

      if (scaledBalance > 0) {
        account.scaledBalance = 0;
        address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow(
          accountAddress,
          borrower,
          address(this)
        );
        emit Transfer(accountAddress, escrow, state.normalizeAmount(scaledBalance));
        _accounts[escrow].scaledBalance += scaledBalance;
        emit SanctionedAccountAssetsSentToEscrow(
          accountAddress,
          escrow,
          state.normalizeAmount(scaledBalance)
        );
      }
      _accounts[accountAddress] = account;
    }
  }

The absence of an adjustment to state.scaledTotalSupply triggers a series of internal accounting inaccuracies. It inflates critical metrics such as liquidityRequired(), totalDebts(), and borrowableAssets().

Tools Used

Manual Review

  function _blockAccount(MarketState memory state, address accountAddress) internal {
    Account memory account = _accounts[accountAddress];
    if (account.approval != AuthRole.Blocked) {
      uint104 scaledBalance = account.scaledBalance;
      account.approval = AuthRole.Blocked;
      emit AuthorizationStatusUpdated(accountAddress, AuthRole.Blocked);

      if (scaledBalance > 0) {
        account.scaledBalance = 0;
        address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow(
          accountAddress,
          borrower,
          address(this)
        );
        emit Transfer(accountAddress, escrow, state.normalizeAmount(scaledBalance));
        _accounts[escrow].scaledBalance += scaledBalance;
+       state.scaledTotalSupply -= scaledBalance;
        emit SanctionedAccountAssetsSentToEscrow(
          accountAddress,
          escrow,
          state.normalizeAmount(scaledBalance)
        );
      }
      _accounts[accountAddress] = account;
    }
  }

Assessed type

Math

#0 - c4-pre-sort

2023-10-27T17:10:52Z

minhquanym marked the issue as duplicate of #123

#1 - c4-judge

2023-11-07T18:17:11Z

MarioPoneder marked the issue as not a duplicate

#2 - c4-judge

2023-11-07T18:17:16Z

MarioPoneder marked the issue as unsatisfactory: Insufficient proof

#3 - Henrychang26

2023-11-13T16:51:00Z

Hi, this should be a valid dupe of #550. It mentions the core problem of the issue: scaledBalance not being removed from scaledTotalSupply. Similar valid dupes are #286, #416

#4 - MarioPoneder

2023-11-14T22:15:54Z

Thank you for your comment! I apologize for being too quick and harsh with this one.
After re-consideration, it seems justified to restore the duplication.

#5 - c4-judge

2023-11-14T22:16:08Z

MarioPoneder marked the issue as duplicate of #550

#6 - c4-judge

2023-11-14T22:16:25Z

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