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
Rank: 63/131
Findings: 1
Award: $43.99
🌟 Selected for report: 0
🚀 Solo Findings: 0
43.9919 USDC - $43.99
Number | Gas | context |
---|---|---|
[G-01] | MULTIPLE ADDRESS/ID MAPPINGS CAN BE COMBINED INTO A SINGLE MAPPING OF AN ADDRESS/ID TO A STRUCT, WHERE APPROPRIATE | 3 |
[G-02] | Use do while loops instead of for loops | 12 |
[G-03] | Using > 0 costs more gas than != 0 when used on a uint in a require() and assert statement | 12 |
[G-04] | USE BITMAPS TO SAVE GAS | 1 |
[G-05] | Structs can be packed into fewer storage slots by editing time variables | 4 |
[G-06] | Avoid contract existence checks by using low level calls | 28 |
[G-07] | Using fixed bytes is cheaper than using string | 3 |
[G-08] | Caching global variables is more expensive than using the actual variable (use msg.sender instead of caching it) | 2 |
[G-09] | A modifier used only once and not being inherited should be inlined to save gas | 4 |
[G-10] | Refactor event to avoid emitting data that is already present in transaction data | 1 |
[G-11] | Do not calculate constants | 2 |
[G-12] | SHOULD USE ARGUMENTS INSTEAD OF STATE VARIABLE | 1 |
[G-13] | Use constants instead of type(uintx).max | 1 |
[G-14] | Use nested if and, avoid multiple check combinations && | 3 |
[G-15] | Missing zero address checks in the constructor | 4 |
[G-16] | INTERNAL FUNCTIONS NOT CALLED BY THE CONTRACT SHOULD BE REMOVED TO SAVE DEPLOYMENT GAS | 5 |
[G-17] | Pre-increment and pre-decrement are cheaper than +1 ,-1 | 2 |
[G-18] | Using delete statement can save gas | 5 |
[G-19] | abi.encode() is less efficient than abi.encodePacked() | 2 |
[G-20] | <X> += <Y> Costs more gas than <X> = <X> + <Y> for state variables | 4 |
[G-21] |
Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key’s keccak256 hash (Gkeccak256 - 30 gas) and that calculation’s associated stack operations.
File: src/market/WildcatMarketToken.sol 13 mapping(address => mapping(address => uint256)) public allowance;
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketToken.sol#L13
File: src/WildcatSanctionsSentinel.sol 20 mapping(address borrower => mapping(address account => bool sanctionOverride))
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatSanctionsSentinel.sol#L20
File: src/libraries/Withdrawal.sol 34 mapping(uint256 => mapping(address => AccountWithdrawalStatus)) accountStatuses;
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/Withdrawal.sol#L34
File: src/libraries/FIFOQueue.sol 48 for (uint256 i = 0; i < len; i++) { 75 for (uint256 i = 0; i < n; i++) {
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/FIFOQueue.sol#L48
File: src/WildcatArchController.sol 93 for (uint256 i = 0; i < count; i++) { 136 for (uint256 i = 0; i < count; i++) { 179 for (uint256 i = 0; i < count; i++) { 222 for (uint256 i = 0; i < count; i++) {
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatArchController.sol#L93
File: src/WildcatMarketController.sol 133 for (uint256 i = 0; i < count; i++) { 154 for (uint256 i = 0; i < lenders.length; i++) { 170 for (uint256 i = 0; i < lenders.length; i++) { 183 for (uint256 i; i < markets.length; i++) { 212 for (uint256 i = 0; i < count; i++) {
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketController.sol#L133
File: src/WildcatMarketControllerFactory.sol 146 for (uint256 i = 0; i < count; i++) {
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketControllerFactory.sol#L146
File: src/libraries/FeeMath.sol 67 if (timeWithPenalty > 0) { 155 if (protocolFeeBips > 0) { 159 if (delinquencyFeeBips > 0) {
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/FeeMath.sol#L67
File: src/market/WildcatMarket.sol 147 if (_withdrawalData.unpaidBatches.length() > 0) {
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarket.sol#L147
File: src/market/WildcatMarketBase.sol 79 if ((parameters.protocolFeeBips > 0).and(parameters.feeRecipient == address(0))) { 170 if (scaledBalance > 0) { 428 if (availableLiquidity > 0) { 472 if (availableLiquidity > 0) {
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L79
File: src/WildcatMarketControllerFactory.sol 201 bool hasOriginationFee = originationFeeAmount > 0; 205 (protocolFeeBips > 0 && nullFeeRecipient) ||
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketControllerFactory.sol#L201
File: src/market/WildcatMarketWithdrawals.sol 32 if ((expiry == expiredBatchExpiry).and(expiry > 0)) { 112 if (availableLiquidity > 0) {
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketWithdrawals.sol#L32
File: src/WildcatSanctionsSentinel.sol 20 mapping(address borrower => mapping(address account => bool sanctionOverride))
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatSanctionsSentinel.sol#L20
File: src/libraries/MarketState.sol 13 struct MarketState { 14 bool isClosed; 15 uint128 maxTotalSupply; 16 uint128 accruedProtocolFees; 17 // Underlying assets reserved for withdrawals which have been paid 18 // by the borrower but not yet executed. 19 uint128 normalizedUnclaimedWithdrawals; 20 // Scaled token supply (divided by scaleFactor) 21 uint104 scaledTotalSupply; 22 // Scaled token amount in withdrawal batches that have not been 23 // paid by borrower yet. 24 uint104 scaledPendingWithdrawals; 25 uint32 pendingWithdrawalExpiry; 26 // Whether market is currently delinquent (liquidity under requirement) 27 bool isDelinquent; 28 // Seconds borrower has been delinquent 29 uint32 timeDelinquent; 30 // Annual interest rate accrued to lenders, in basis points 31 uint16 annualInterestBips; 32 // Percentage of outstanding balance that must be held in liquid reserves 33 uint16 reserveRatioBips; 34 // Ratio between internal balances and underlying token amounts 35 uint112 scaleFactor; 36 uint32 lastInterestAccruedTimestamp; }
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/MarketState.sol#L13-L36
File: src/WildcatMarketController.sol 18 struct TmpMarketParameterStorage { 19 address asset; 20 string namePrefix; 21 string symbolPrefix; 22 address feeRecipient; 23 uint16 protocolFeeBips; 24 uint128 maxTotalSupply; 25 uint16 annualInterestBips; 26 uint16 delinquencyFeeBips; 27 uint32 withdrawalBatchDuration; 28 uint16 reserveRatioBips; 29 uint32 delinquencyGracePeriod; }
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketController.sol#L18-L29
File: src/libraries/Withdrawal.sol 17 struct WithdrawalBatch { 18 // Total scaled amount of tokens to be withdrawn 19 uint104 scaledTotalAmount; 20 // Amount of scaled tokens that have been paid by borrower 21 uint104 scaledAmountBurned; 22 // Amount of normalized tokens that have been paid by borrower 23 uint128 normalizedAmountPaid; 24 } 26 struct AccountWithdrawalStatus { 27 uint104 scaledAmount; 28 uint128 normalizedAmountWithdrawn; 29 }
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/Withdrawal.sol#L17
Prior to 0.8.10 the compiler inserted extra code, including EXTCODESIZE (100 gas), to check for contract existence for external function 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/WildcatArchController.sol 64 if (!_borrowers.add(borrower)) { 71 if (!_borrowers.remove(borrower)) { 78 return _borrowers.contains(borrower); 107 if (!_controllerFactories.add(factory)) { 114 if (!_controllerFactories.remove(factory)) { 121 return _controllerFactories.contains(factory); 150 if (!_controllers.add(controller)) { 157 if (!_controllers.remove(controller)) { 164 return _controllers.contains(controller); 193 if (!_markets.add(market)) { 200 if (!_markets.remove(market)) { 207 return _markets.contains(market);
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatArchController.sol#L64
File: src/market/WildcatMarketBase.sol 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#L172
File: src/WildcatMarketController.sol 185 if (!_controlledMarkets.contains(market)) { 188 WildcatMarket(market).updateAccountAuthorization(lender, _authorizedLenders.contains(lender)); 197 return _controlledMarkets.contains(market); 303 if (!archController.isRegisteredBorrower(msg.sender)) {
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketController.sol#L88
File: src/WildcatMarketControllerFactory.sol 127 return _deployedControllers.contains(controller); 329 market = IWildcatMarketController(controller).deployMarket(
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketControllerFactory.sol#L127
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
File: WildcatSanctionsEscrow.sol 22 return IERC20(asset).balanceOf(address(this)); 26 return !WildcatSanctionsSentinel(sentinel).isSanctioned(borrower, account); 38 IERC20(asset).transfer(account, amount);
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatSanctionsEscrow.sol#L22
File: src/WildcatSanctionsSentinel.sol 42 IChainalysisSanctionsList(chainalysisSanctionsList).isSanctioned(account); 100 if (!IWildcatArchController(archController).isRegisteredMarket(msg.sender)) {
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatSanctionsSentinel.sol#L42
If data can fit into 32 bytes, then you should use bytes32 datatype rather than bytes or strings as it is cheaper in solidity.
File: src/market/WildcatMarketBase.sol 24 string public constant version = '1.0'; 57 string public name; 60 string public symbol;
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L24
File: src/WildcatMarketControllerFactory.sol 286 _tmpMarketBorrowerParameter = msg.sender;
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketControllerFactory.sol#L286
File: src/WildcatSanctionsEscrow.sol 17 sentinel = msg.sender;
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatSanctionsEscrow.sol#L17
File: src/WildcatArchController.sol 41 modifier onlyControllerFactory() { 42 if (!_controllerFactories.contains(msg.sender)) { 43 revert NotControllerFactory(); 44 } 45 _; 46 } 48 modifier onlyController() { 49 if (!_controllers.contains(msg.sender)) { 50 revert NotController(); 51 } 52 _; 53 }
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatArchController.sol#L41
File: src/WildcatMarketController.sol 87 modifier onlyControlledMarket(address market) { 88 if (!_controlledMarkets.contains(market)) { 89 revert NotControlledMarket(); 90 } 91 _; 92 }
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketController.sol#L87-L92
File: src/WildcatMarketControllerFactory.sol 65 modifier onlyArchControllerOwner() { 66 if (msg.sender != archController.owner()) { 67 revert CallerNotArchControllerOwner(); 68 } 69 _; 70 }
File: src/market/WildcatMarket.sol 160 emit MarketClosed(block.timestamp);
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarket.sol#L160
File: src/libraries/Chainalysis.sol 6 IChainalysisSanctionsList constant SanctionsList = IChainalysisSanctionsList( 7 0x40C57923924B5c5c5455c48D93317139ADDaC8fb 8 );
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/Chainalysis.sol#L6-L8
File: src/WildcatSanctionsSentinel.sol 11 bytes32 public constant override WildcatSanctionsEscrowInitcodeHash = 12 keccak256(type(WildcatSanctionsEscrow).creationCode);
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatSanctionsSentinel.sol#L11
Emitting storage values instead of the memory one.
File: src/WildcatSanctionsEscrow.sol 40 emit EscrowReleased(account, asset, amount);
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatSanctionsEscrow.sol#L40
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
File: src/WildcatMarketControllerFactory.sol 204 if ( 205 (protocolFeeBips > 0 && nullFeeRecipient) || 206 (hasOriginationFee && nullFeeRecipient) || 207 (hasOriginationFee && nullOriginationFeeAsset) 208 ) {
File: src/WildcatMarketControllerFactory.sol 72 constructor( 73 address _archController, 74 address _sentinel, 75 MarketParameterConstraints memory constraints 76 ) { 77 archController = IWildcatArchController(_archController); 78 sentinel = _sentinel;
File: src/WildcatSanctionsSentinel.sol 24 constructor(address _archController, address _chainalysisSanctionsList) { 25 archController = _archController; 26 chainalysisSanctionsList = _chainalysisSanctionsList;
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatSanctionsSentinel.sol#L24-L26
File: src/market/WildcatMarketBase.sol 163 function _blockAccount(MarketState memory state, address accountAddress) internal { 197 function _getAccountWithRole( 358 function _getUpdatedState() internal returns (MarketState memory state) { 448 function _writeState(MarketState memory state) internal {
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L163
File: src/libraries/Withdrawal.sol 47 function availableLiquidityForPendingBatch(
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/Withdrawal.sol#L40
File: src/libraries/FIFOQueue.sol 58 arr.nextIndex = nextIndex + 1; 67 arr.startIndex = startIndex + 1;
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/FIFOQueue.sol#L58
File: src/market/WildcatMarket.sol 144 state.annualInterestBips = 0; 146 state.reserveRatioBips = 0;
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarket.sol#L144
File: src/market/WildcatMarketBase.sol 171 account.scaledBalance = 0; 431 state.pendingWithdrawalExpiry = 0; 489 state.pendingWithdrawalExpiry = 0;
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L171
File: src/WildcatSanctionsSentinel.sol 78 keccak256(abi.encode(borrower, account, asset)), 110 new WildcatSanctionsEscrow{ salt: keccak256(abi.encode(borrower, account, asset)) }();
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatSanctionsSentinel.sol#L78
Using compound assignment operators for state variables (like State += X or State -= X …) it’s more expensive than using operator assignment (like State = State + X or State = State - X …).
File: src/market/WildcatMarketBase.sol 178 _accounts[escrow].scaledBalance += scaledBalance; 323 apr += MathUtils.bipToRay(delinquencyFeeBips); 338 apr += delinquencyFeeBips;
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L178
File: src/market/WildcatMarketWithdrawals.sol 104 _withdrawalData.accountStatuses[expiry][msg.sender].scaledAmount += scaledAmount;
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketWithdrawals.sol#L104
#0 - c4-judge
2023-11-09T13:49:55Z
MarioPoneder marked the issue as grade-b