The Wildcat Protocol - cu5t0mpeo'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: 67/131

Findings: 3

Award: $29.84

🌟 Selected for report: 0

πŸš€ Solo Findings: 0

Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketConfig.sol#L128-L144

Vulnerability details

Impact

No one can modify TotalSupply, which will result in the borrower being unable to modify TotalSupply.

Proof of Concept

Since this function needs to be called by the controller, but the controller does not implement the function of calling this function, this function cannot be called.

    /**
     * @dev Sets the maximum total supply - this only limits deposits and
     *      does not affect interest accrual.
     *
     *      Can not be set lower than current total supply.
     */
    function setMaxTotalSupply(
        uint256 _maxTotalSupply
    ) external onlyController nonReentrant { // @audit high - this function donβ€˜t call
        MarketState memory state = _getUpdatedState();

        if (_maxTotalSupply < state.totalSupply()) {
            revert NewMaxSupplyTooLow();
        }

        state.maxTotalSupply = _maxTotalSupply.toUint128();
        _writeState(state);
        emit MaxTotalSupplyUpdated(_maxTotalSupply);
    }

Tools Used

Manual Review

Just add relevant functions in the controller

Assessed type

Error

#0 - c4-pre-sort

2023-10-27T06:21:56Z

minhquanym marked the issue as duplicate of #162

#1 - c4-pre-sort

2023-10-27T06:58:14Z

minhquanym marked the issue as duplicate of #147

#2 - c4-judge

2023-11-07T13:49:32Z

MarioPoneder marked the issue as partial-50

#3 - c4-judge

2023-11-07T14:16:53Z

MarioPoneder changed the severity to 3 (High Risk)

Awards

13.1205 USDC - $13.12

Labels

bug
3 (High Risk)
low quality report
partial-50
upgraded by judge
duplicate-266

External Links

Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketController.sol#L182-L190

Vulnerability details

Impact

In addition to block,strangers can set their approve to WithdrawOnly

Proof of Concept

Except for those whose approve is block, this function will set the permission of the parameter lender to WithdrawOnly.

  /**
   * @dev Update lender authorization for a set of markets to the current
   *      status.
   */
  function updateLenderAuthorization(address lender, address[] memory markets) external {
    for (uint256 i; i < markets.length; i++) {
      address market = markets[i];
      if (!_controlledMarkets.contains(market)) {
        revert NotControlledMarket();
      }
      WildcatMarket(market).updateAccountAuthorization(lender, _authorizedLenders.contains(lender));
    }
  }

updateAccountAuthorization

  /**
   * @dev Updates an account's authorization status based on whether the controller
   *      has it marked as approved.
   */
  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);
  }

poc:

// SPDX-License-Identifier: NONE
pragma solidity >=0.8.20;

import './BaseMarketTest.sol';
import 'src/interfaces/IMarketEventsAndErrors.sol';
import 'src/libraries/MathUtils.sol';
import 'src/libraries/SafeCastLib.sol';
import 'src/libraries/MarketState.sol';
import 'solady/utils/SafeTransferLib.sol';
import 'forge-std/console.sol';

contract MyTest is BaseMarketTest, IWildcatMarketControllerEventsAndErrors {

  function test_updateLenderAuthorization() public {
    address borrower = address(1);
    address account = address(2);
    address[] memory markets = new address[](1);
    markets[0] = address(market);
    vm.prank(account);
    controller.updateLenderAuthorization(account, markets);
    if (market.getAccountRole(account) == AuthRole.WithdrawOnly) {
        console.log("success");
    }
  }
}

result:

Running 1 test for test/MyTest.t.sol:MyTest [PASS] test_updateLenderAuthorization() (gas: 72967) Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.83ms

Tools Used

Manual Review

It is recommended to check whether the updateLenderAuthorization function is called by borrower

Assessed type

Access Control

#0 - c4-pre-sort

2023-10-27T08:38:11Z

minhquanym marked the issue as low quality report

#1 - c4-pre-sort

2023-10-27T08:40:53Z

minhquanym marked the issue as duplicate of #400

#2 - c4-pre-sort

2023-10-27T08:51:21Z

minhquanym marked the issue as duplicate of #155

#3 - c4-judge

2023-11-07T12:54:40Z

MarioPoneder marked the issue as satisfactory

#4 - c4-judge

2023-11-14T13:59:00Z

MarioPoneder changed the severity to 3 (High Risk)

#5 - c4-judge

2023-11-14T14:00:47Z

MarioPoneder marked the issue as partial-50

#6 - c4-judge

2023-11-14T14:02:26Z

MarioPoneder marked the issue as duplicate of #266

Awards

13.1205 USDC - $13.12

Labels

bug
3 (High Risk)
satisfactory
duplicate-266

External Links

Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketToken.sol#L64-L82 https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketToken.sol#L36-L57

Vulnerability details

Impact

Users can bypass Escrow to claim target tokens, making WildcatSanctionsEscrow useless.

Proof of Concept

When the market token performs transfer or transferFrom, it will not determine whether the caller is in the block state.

  function transfer(address to, uint256 amount) external virtual nonReentrant returns (bool) {
    _transfer(msg.sender, to, amount);
    return true;
  }

  function transferFrom(
    address from,
    address to,
    uint256 amount
  ) external virtual nonReentrant returns (bool) {
    uint256 allowed = allowance[from][msg.sender];

    // Saves gas for unlimited approvals.
    if (allowed != type(uint256).max) {
      uint256 newAllowance = allowed - amount;
      _approve(from, msg.sender, newAllowance);
    }

    _transfer(from, to, amount);

    return true;
  }
    ...
    
      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);
  }
    

A sanctioned lender can transfer market tokens to other users, and then change its approve to WithdrawOnly to finally receive the target token through the lax permission check of updateLenderAuthorization

Tools Used

Manual Review

When market tokens are transferred, determine whether msg.sender has the authority to transfer tokens.

Assessed type

Token-Transfer

#0 - c4-pre-sort

2023-10-27T08:00:39Z

minhquanym marked the issue as duplicate of #54

#1 - c4-judge

2023-11-07T14:41:27Z

MarioPoneder marked the issue as satisfactory

Awards

16.6643 USDC - $16.66

Labels

bug
2 (Med Risk)
satisfactory
duplicate-196

External Links

Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketController.sol#L468-L488

Vulnerability details

Impact

Make annualInterestBips change to a value outside the range, causing some unnecessary errors

Proof of Concept

setAnnualInterestBips does not do a check from minimumAnnualInterestBips to maximumAnnualInterestBips

  /**
   * @dev Modify the interest rate for a market.
   * If the new interest rate is lower than the current interest rate,
   * the reserve ratio is set to 90% for the next two weeks.
   */
  function setAnnualInterestBips(
    address market,
    uint16 annualInterestBips
  ) external virtual onlyBorrower onlyControlledMarket(market) {
    // If borrower is reducing the interest rate, increase the reserve
    // ratio for the next two weeks.
    if (annualInterestBips < WildcatMarket(market).annualInterestBips()) {
      TemporaryReserveRatio storage tmp = temporaryExcessReserveRatio[market];

      if (tmp.expiry == 0) {
        tmp.reserveRatioBips = uint128(WildcatMarket(market).reserveRatioBips());

        // Require 90% liquidity coverage for the next 2 weeks
        WildcatMarket(market).setReserveRatioBips(9000);
      }

      tmp.expiry = uint128(block.timestamp + 2 weeks);
    }

    WildcatMarket(market).setAnnualInterestBips(annualInterestBips);
  }

poc(The MaximumAnnualInterestBips of the file TestConstants.sol needs to be set to 9000. The test sample is completed in WildcatMarketController.t.sol):

  function test_setAnnualInterestBips_range() public {
    MarketParameterConstraints memory constraints = controller.getParameterConstraints();
    vm.prank(borrower);

    console.log("before:%d\n",constraints.maximumAnnualInterestBips + 1);
    controller.setAnnualInterestBips(address(market), constraints.maximumAnnualInterestBips + 1);
    console.log("after:%d\n",market.annualInterestBips());

    assertEq(market.annualInterestBips(), constraints.maximumAnnualInterestBips + 1, 'APR');

  }

Tools Used

Manual Review

  /**
   * @dev Modify the interest rate for a market.
   * If the new interest rate is lower than the current interest rate,
   * the reserve ratio is set to 90% for the next two weeks.
   */
  function setAnnualInterestBips(
    address market,
    uint16 annualInterestBips
  ) external virtual onlyBorrower onlyControlledMarket(market) {
    if (annualInterestBips < MinimumAnnualInterestBips || annualInterestBips > MaximumAnnualInterestBips) {
    	revert OutFlowRange();
    }
    // If borrower is reducing the interest rate, increase the reserve
    // ratio for the next two weeks.
    if (annualInterestBips < WildcatMarket(market).annualInterestBips()) {
      TemporaryReserveRatio storage tmp = temporaryExcessReserveRatio[market];

      if (tmp.expiry == 0) {
        tmp.reserveRatioBips = uint128(WildcatMarket(market).reserveRatioBips());

        // Require 90% liquidity coverage for the next 2 weeks
        WildcatMarket(market).setReserveRatioBips(9000);
      }

      tmp.expiry = uint128(block.timestamp + 2 weeks);
    }

    WildcatMarket(market).setAnnualInterestBips(annualInterestBips);
  }

Assessed type

Other

#0 - c4-pre-sort

2023-10-27T14:14:19Z

minhquanym marked the issue as duplicate of #443

#1 - c4-judge

2023-11-07T12:25:52Z

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