The Wildcat Protocol - petrichor'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: 64/131

Findings: 1

Award: $43.99

Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: nisedo

Also found by: 0xAnah, 0xVolcano, 0xhex, 0xta, JCK, Raihan, SAQ, hunter_w3b, petrichor, shamsulhaq123

Labels

bug
G (Gas Optimization)
grade-b
high quality report
sponsor confirmed
G-03

Awards

43.9919 USDC - $43.99

External Links

GAS OPTIMIZATION

issueinstance
[G-01]Make 3 event parameters indexed when possible10
[G-02]Avoid contract existence checks by using low level calls9
[G-03]Use constants instead of type(uintx).max1
[G-04]Using Fixed Bytes Is Cheaper Than Using String1
[G-05]Use assembly to validate msg.sender1
[G-06]Can Make The Variable Outside The Loop To Save Gas3
[G-07]Expressions for constant values such as a call to keccak256(), should use immutable rather than constant1
[G-08]Amounts should be checked for 0 before calling a transfer2
[G-09]Use hardcode address instead address(this)3
[G-10]Structs can be packed to use fewer storage slots2

[G-01] Make 3 event parameters indexed when possible

If there are less than 3 parameters, you need to make all parameters indexed.

File:  src/WildcatArchController.sol
 29 event MarketAdded(address indexed controller, address market);
  event MarketRemoved(address market);

  event ControllerFactoryAdded(address controllerFactory);
  event ControllerFactoryRemoved(address controllerFactory);

  event BorrowerAdded(address borrower);
  event BorrowerRemoved(address borrower);

  event ControllerAdded(address indexed controllerFactory, address controller);
  event ControllerRemoved(address controller);

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatArchController.sol#L29-L39

File:  src/WildcatMarketControllerFactory.sol
 16  event NewController(address borrower, address controller, string namePrefix, string symbolPrefix);
  event UpdateProtocolFeeConfiguration(
    address feeRecipient,
    uint16 protocolFeeBips,
    address originationFeeAsset,
   22  uint256 originationFeeAmount
  );

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketControllerFactory.sol#L16-L22

[G‑02] Avoid contract existence checks by using low level calls

In more recent solidity versions, the compiler will not insert these checks if the external call has a return value. Similar behavior can be achieved in earlier versions by using low-level calls, since low level calls never check for contract existence.

File:  src/WildcatSanctionsSentinel.sol
100  if (!IWildcatArchController(archController).isRegisteredMarket(msg.sender)) {

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatSanctionsSentinel.sol#L100

File:  src/market/WildcatMarketBase.sol
99   decimals = IERC20Metadata(parameters.asset).decimals();

172  address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow(

204  if (IWildcatMarketController(controller).isAuthorizedLender(accountAddress)) {

239  return IERC20(asset).balanceOf(address(this));        

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L99 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L172 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L204 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L239

File:  src/market/WildcatMarketConfig.sol
75   if (!IWildcatSanctionsSentinel(sentinel).isSanctioned(borrower, accountAddress)) {

94   if (IWildcatSanctionsSentinel(sentinel).isSanctioned(borrower, accountAddress)) {    

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketConfig.sol#L75 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketConfig.sol#L94

File:  src/market/WildcatMarketWithdrawals.sol
164    if (IWildcatSanctionsSentinel(sentinel).isSanctioned(borrower, accountAddress)) {

166    address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow(    

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

File:  src/WildcatSanctionsSentinel.sol
42  IChainalysisSanctionsList(chainalysisSanctionsList).isSanctioned(account);

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatSanctionsSentinel.sol#L42

[G-03] Use constants instead of type(uintx).max

The reason for this is that the type(uintX).max expression involves a computation at runtime, whereas a constant is evaluated at compile-time. This means that using type(uintX).max can result in additional gas costs for each transaction that involves the expression.

By using a constant instead of type(uintX).max, you can avoid these additional gas costs and make your code more efficient.

File:  src/market/WildcatMarketToken.sol
49   if (allowed != type(uint256).max) {

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

[G-04] Using Fixed Bytes Is Cheaper Than Using String

If you can limit the length to a certain number of bytes, always use one of bytes1 to bytes32 because they are much cheaper.

File:  src/market/WildcatMarketBase.sol
57  string public name;

60  string public symbol;

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L57-L60

[G-05] Use assembly to validate msg.sender

We can use assembly to efficiently validate msg.sender for the deployMarket function with the least amount of opcodes necessary. to save gas.

File:  src/WildcatMarketController.sol
    if (msg.sender == borrower) {
      if (!archController.isRegisteredBorrower(msg.sender)) {
        revert NotRegisteredBorrower();
      }
    } else if (msg.sender != address(controllerFactory)) {
      revert CallerNotBorrowerOrControllerFactory();
    }

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketController.sol#L302-L308

[G-06] Can Make The Variable Outside The Loop To Save Gas

When you declare a variable inside a loop, Solidity creates a new instance of the variable for each iteration of the loop. This can lead to unnecessary gas costs, especially if the loop is executed frequently or iterates over a large number of elements.

File:  src/WildcatMarketController.sol
155   address lender = lenders[i];

171  address lender = lenders[i];

184  address market = markets[i];

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketController.sol#L155 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketController.sol#L171 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketController.sol#L184

[G-07] Expressions for constant values such as a call to keccak256(), should use immutable rather than constant

In Solidity, when you have expressions involving constant values, such as a call to keccak256(), it is recommended to use the immutable keyword instead of constant to save gas.

File:  src/WildcatSanctionsSentinel.sol
11    bytes32 public constant override WildcatSanctionsEscrowInitcodeHash =
    keccak256(type(WildcatSanctionsEscrow).creationCode);

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatSanctionsSentinel.sol#L11-L12

[G-08] Amounts should be checked for 0 before calling a transfer

In Solidity, when performing token transfers or transactions involving amounts, it is often recommended to check if the amount is zero before initiating the transfer. This check helps to save gas by avoiding unnecessary transaction executions when the amount is already zero.

File:  src/market/WildcatMarket.sol
129   asset.safeTransfer(msg.sender, amount);

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

File:  src/market/WildcatMarketToken.sol
54  _transfer(from, to, amount);

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

[G-09] Use hardcode address instead address(this)

The reason for this is that using address(this) requires an additional EXTCODESIZE operation to retrieve the contract's address from its bytecode, which can increase the gas cost of your contract. By pre-calculating and using a hardcoded address, you can avoid this additional operation and reduce the overall gas cost of your contract.

File:  src/market/WildcatMarketBase.sol
175   address(this)

239   return IERC20(asset).balanceOf(address(this));

524    emit Transfer(address(this), address(0), normalizedAmountPaid);

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L175 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L239 https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L524

[G-10] Structs can be packed to use fewer storage slots

In Solidity, structs can be packed to use fewer storage slots, which can lead to gas savings in certain cases. When a struct is declared, by default, each member of the struct is allocated its own storage slot, even if the members could potentially fit within a single slot.

File:  src/WildcatMarketController.sol
struct TmpMarketParameterStorage {
  address asset;
  string namePrefix;
  string symbolPrefix;
  address feeRecipient;
  uint16 protocolFeeBips;
  uint128 maxTotalSupply;
  uint16 annualInterestBips;
  uint16 delinquencyFeeBips;
  uint32 withdrawalBatchDuration;
  uint16 reserveRatioBips;
  uint32 delinquencyGracePeriod;
}

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketController.sol#L18-L30

File:   src/libraries/MarketState.sol
struct MarketState {
  bool isClosed;
  uint128 maxTotalSupply;
  uint128 accruedProtocolFees;
  // Underlying assets reserved for withdrawals which have been paid
  // by the borrower but not yet executed.
  uint128 normalizedUnclaimedWithdrawals;
  // Scaled token supply (divided by scaleFactor)
  uint104 scaledTotalSupply;
  // Scaled token amount in withdrawal batches that have not been
  // paid by borrower yet.
  uint104 scaledPendingWithdrawals;
  uint32 pendingWithdrawalExpiry;
  // Whether market is currently delinquent (liquidity under requirement)
  bool isDelinquent;
  // Seconds borrower has been delinquent
  uint32 timeDelinquent;
  // Annual interest rate accrued to lenders, in basis points
  uint16 annualInterestBips;
  // Percentage of outstanding balance that must be held in liquid reserves
  uint16 reserveRatioBips;
  // Ratio between internal balances and underlying token amounts
  uint112 scaleFactor;
  uint32 lastInterestAccruedTimestamp;
}

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/MarketState.sol#L13-L37

#0 - c4-pre-sort

2023-10-29T15:03:12Z

minhquanym marked the issue as high quality report

#1 - c4-sponsor

2023-11-06T09:57:56Z

laurenceday (sponsor) confirmed

#2 - c4-judge

2023-11-09T13:06:21Z

MarioPoneder marked the issue as grade-c

#3 - c4-judge

2023-11-09T13:06:42Z

MarioPoneder marked the issue as grade-b

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