This is the top-ranked automated findings report, from henry bot. All findings in this report will be considered known issues for the purposes of your C4 audit.
Audit report for 2023-10-wildcat generated by Bot-Henry.
Note: There is a section for disputed findings below the usual findings sections.
Total 9 instances over 3 issues:
ID | Issue | Instances |
---|---|---|
[M‑01] | Centralization risk for privileged functions | 7 |
[M‑02] | Return values of transfer() /transferFrom() not checked |
1 |
[M‑03] | Unsafe use of ERC20 transfer() /transferFrom() |
1 |
Total 95 instances over 21 issues:
ID | Issue | Instances |
---|---|---|
[L‑01] | Use Ownable2Step instead of Ownable | 1 |
[L‑02] | Function decimals() is not a part of the ERC-20 standard |
1 |
[L‑03] | Missing zero address check in constructor | 4 |
[L‑04] | Revert on transfer to the zero address | 6 |
[L‑05] | SafeTransferLib does not ensure that the token contract exists |
1 |
[L‑06] | Using > />= without specifying an upper bound in version pragma is unsafe |
21 |
[L‑07] | Array is push() ed but not pop() ed |
1 |
[L‑08] | State variables not limited to reasonable values | 1 |
[L‑09] | Missing checks for address(0) when setting address state variables |
8 |
[L‑10] | Owner can renounce Ownership | 1 |
[L‑11] | Critical functions should use two-step procedure | 2 |
[L‑12] | Constructor / initialization function lacks parameter validation | 4 |
[L‑13] | Contracts are vulnerable to fee-on-transfer accounting-related issues | 8 |
[L‑14] | Functions calling contracts/addresses with transfer hooks should be protected by reentrancy guard | 2 |
[L‑15] | Governance functions should be controlled by time locks | 10 |
[L‑16] | External function calls within loops | 1 |
[L‑17] | Some tokens may revert when zero value transfers are made | 4 |
[L‑18] | Use SafeCast to downcast safely |
10 |
[L‑19] | Use descriptive constant instead of 0 as a parameter | 2 |
[L‑20] | Loss of precision in divisions | 2 |
[L‑21] | Code does not follow the best practice of check-effects-interaction | 5 |
Total 1919 instances over 87 issues:
ID | Issue | Instances |
---|---|---|
[N‑01] | Consider moving msg.sender checks to modifier s |
4 |
[N‑02] | Custom errors has no error details | 21 |
[N‑03] | Import declarations should import specific identifiers, rather than the whole file | 50 |
[N‑04] | Too long functions should be refactored | 4 |
[N‑05] | There is no need to initialize variables with 0 | 12 |
[N‑06] | NatSpec documentation for contract is missing | 18 |
[N‑07] | Names of constants should use the UPPER_CASE_WITH_UNDERSCORES style | 25 |
[N‑08] | Names of private /internal functions should be prefixed with an underscore |
80 |
[N‑09] | Names of private /internal state variables should be prefixed with an underscore |
23 |
[N‑10] | Order of functions does not follow the Solidity Style Guide | 43 |
[N‑11] | The nonReentrant modifier should occur before all other modifiers |
6 |
[N‑12] | Redundant inheritance specifier | 1 |
[N‑13] | Strings should use double quotes rather than single quotes | 3 |
[N‑14] | Use of override is unnecessary |
9 |
[N‑15] | Unused errors | 4 |
[N‑16] | Unused events | 2 |
[N‑17] | Large numeric literals should use underscores for readability | 4 |
[N‑18] | Assembly blocks should have extensive comments | 21 |
[N‑19] | Complex casting | 6 |
[N‑20] | Constants/Immutables redefined elsewhere | 38 |
[N‑21] | Convert simple if -statements to ternary expressions |
2 |
[N‑22] | Events should be emitted before external calls | 11 |
[N‑23] | Events are emitted without the sender information | 12 |
[N‑24] | Event is missing indexed fields |
10 |
[N‑25] | Inconsistent floating version pragma | 2 |
[N‑26] | Imports could be organized more systematically | 5 |
[N‑27] | Imports should use double quotes rather than single quotes | 67 |
[N‑28] | @openzeppelin/contracts should be upgraded to a newer version | 3 |
[N‑29] | Magic numbers should be replaced with constants | 5 |
[N‑30] | Expressions for constant values should use immutable rather than constant |
7 |
[N‑31] | Functions not used internally could be marked external | 9 |
[N‑32] | Use @inheritdoc for overridden functions |
9 |
[N‑33] | Contracts should have NatSpec @author tags |
18 |
[N‑34] | Contracts should have @notice tags |
18 |
[N‑35] | Contracts should have NatSpec @title tags |
18 |
[N‑36] | Event declarations should have NatSpec descriptions | 10 |
[N‑37] | NatSpec documentation for function is missing | 124 |
[N‑38] | Functions missing NatSpec @param tag |
257 |
[N‑39] | Modifiers missing NatSpec @param tag |
1 |
[N‑40] | Public variable declarations should have NatSpec descriptions | 23 |
[N‑41] | Functions missing NatSpec @return tag |
151 |
[N‑42] | Contract name does not follow the Solidity Style Guide | 3 |
[N‑43] | Functions and modifiers should be named in mixedCase style | 3 |
[N‑44] | Variable names for immutable s should use UPPER_CASE_WITH_UNDERSCORES |
50 |
[N‑45] | Non-assembly method available | 29 |
[N‑46] | Order of contract layout does not follow the Solidity Style Guide | 6 |
[N‑47] | Missing zero address check in functions with address parameters | 47 |
[N‑48] | Named imports of parent contracts are missing | 11 |
[N‑49] | Constants should be put on the left side of comparisons | 19 |
[N‑50] | Put all system-wide constants in one file | 2 |
[N‑51] | Redundant return statement in a function with named return variables | 1 |
[N‑52] | Duplicated require() /revert() checks should be refactored |
1 |
[N‑53] | Large multiples of ten should use scientific notation | 4 |
[N‑54] | Non-interface files should use fixed compiler versions | 19 |
[N‑55] | Consider bounding input array length | 3 |
[N‑56] | Unused import | 3 |
[N‑57] | Unused named return | 8 |
[N‑58] | Use delete instead of assigning values to false |
1 |
[N‑59] | Consider using delete rather than assigning zero to clear values |
5 |
[N‑60] | Use the latest Solidity version (0.8.19 for L2s) | 19 |
[N‑61] | Named mappings are recommended | 5 |
[N‑62] | Whitespace in Expressions | 6 |
[N‑63] | Use a struct to encapsulate multiple function parameters | 3 |
[N‑64] | Returning a struct instead of a bunch of variables is better | 1 |
[N‑65] | Addresses shouldn't be hard-coded | 1 |
[N‑66] | Events that mark critical parameter changes should contain both the old and the new value | 4 |
[N‑67] | Non-public state variables should include comments | 35 |
[N‑68] | File is missing NatSpec | 7 |
[N‑69] | Modifier declarations should have NatSpec descriptions | 7 |
[N‑70] | Empty bytes check is missing | 1 |
[N‑71] | Contract functions should use an interface |
93 |
[N‑72] | Don't define functions with the same name in a contract | 9 |
[N‑73] | Assembly block creates dirty bits | 7 |
[N‑74] | Control structures do not follow the Solidity Style Guide | 16 |
[N‑75] | Functions contain the same code | 4 |
[N‑76] | Missing event for critical changes | 2 |
[N‑77] | Consider adding emergency-stop functionality | 3 |
[N‑78] | Avoid the use of sensitive terms | 2 |
[N‑79] | Consider adding a block/deny-list | 11 |
[N‑80] | Enable IR-based code generation | 1 |
[N‑81] | Contracts should have NatSpec @dev tags |
19 |
[N‑82] | Error declarations should have NatSpec descriptions | 19 |
[N‑83] | Functions should have @notice tags |
124 |
[N‑84] | Contracts should have full test coverage | 1 |
[N‑85] | Large or complicated code bases should implement invariant tests | 1 |
[N‑86] | Top-level declarations should be separated by at least two lines | 145 |
[N‑87] | Consider adding formal verification proofs | 22 |
Total 422 instances over 36 issueswith 81743 gas saved:
ID | Issue | Instances | Gas |
---|---|---|---|
[G‑01] | The <array>.length should be cached outside of the for -loop |
3 | 9 |
[G‑02] | Use shift right instead of division if possible | 2 | 40 |
[G‑03] | Use storage instead of memory for structs/arrays |
8 | 33600 |
[G‑04] | Using private for constants saves gas |
2 | 6812 |
[G‑05] | Constructors can be marked as payable to save deployment gas | 7 | 147 |
[G‑06] | State variables only set in the constructor should be declared immutable |
2 | 4194 |
[G‑07] | Using ++X /--X instead of X++ /X-- can save gas |
12 | 60 |
[G‑08] | Unnecessary event parameters should be removed | 1 | 358 |
[G‑09] | Unused named return variables without optimizer waste gas | 8 | 72 |
[G‑10] | Use unchecked block for safe subtractions |
2 | 170 |
[G‑11] | internal functions only called once can be inlined to save gas |
5 | 150 |
[G‑12] | Functions that revert when called by normal users can be marked payable |
19 | 399 |
[G‑13] | Use s.x = s.x + y instead of s.x += y for memory structs |
19 | 1900 |
[G‑14] | Operator >= /<= costs less gas than operator > /< |
21 | 63 |
[G‑15] | Usage of int s/uint s smaller than 32 bytes incurs overhead |
143 | 7865 |
[G‑16] | Divisions can be unchecked to save gas | 4 | 80 |
[G‑17] | Increments can be unchecked to save gas |
12 | 720 |
[G‑18] | Unused non-constant state variables waste gas | 2 | - |
[G‑19] | Using assembly to check for zero can save gas | 15 | 90 |
[G‑20] | Use assembly to write address/contract type storage values |
2 | 100 |
[G‑21] | Use uint256(1) /uint256(2) instead of true /false to save gas for changes |
1 | 17100 |
[G‑22] | Avoid zero transfer to save gas | 4 | 400 |
[G‑23] | Don't emit events inside a loop | 2 | 750 |
[G‑24] | Optimize names to save gas | 9 | 198 |
[G‑25] | Duplicated require() /revert() checks should be refactored to a modifier Or function to save gas |
1 | - |
[G‑26] | Reduce gas usage by moving to Solidity 0.8.19 or later | 3 | - |
[G‑27] | Newer versions of solidity are more gas efficient | 19 | - |
[G‑28] | The result of a function call should be cached rather than re-calling the function | 3 | 300 |
[G‑29] | State variables that are used multiple times in a function should be cached in stack variables | 10 | 970 |
[G‑30] | Use assembly to emit events | 36 | 1368 |
[G‑31] | Use assembly to compute hashes to save gas | 4 | 320 |
[G‑32] | Use calldata instead of memory for immutable arguments |
9 | 2700 |
[G‑33] | Consider activating via-ir for deploying | 22 | - |
[G‑34] | Using bool s for storage incurs overhead |
1 | 100 |
[G‑35] | Multiple accesses of the same mapping/array key/index should be cached | 3 | 126 |
[G‑36] | State variable access within a loop | 6 | 582 |
The issues below may be reported by other bots/wardens, but can be penalized/ignored since either the rule or the specified instances are invalid.
Total 515 instances over 50 issues:
ID | Issue | Instances |
---|---|---|
[D‑01] | abi.encodePacked() should be replaced with bytes.concat() |
1 |
[D‑02] | internal |
2 |
[D‑03] | private rather than public for constants, saves gas |
27 |
[D‑04] | 10 | |
[D‑05] | internal functions not called by the contract should be removed |
71 |
[D‑06] | 21 | |
[D‑07] | 10 | |
[D‑08] | bytes or bytes32 for clearer semantic meaning |
1 |
[D‑09] | abi.encodePacked() with dynamic arguments to a hash can cause collisions |
1 |
[D‑10] | public function visibility to external to save gas |
9 |
[D‑11] | @notice tags |
1 |
[D‑12] | 11 | |
[D‑13] | x += y is more expensive than x = x + y for state variables |
2 |
[D‑14] | 3 | |
[D‑15] | SafeTransferLib does not ensure that the token contract exists |
5 |
[D‑16] | SafeTransferLib does not ensure that the token contract exists |
7 |
[D‑17] | 22 | |
[D‑18] | 3 | |
[D‑19] | 22 | |
[D‑20] | 11 | |
[D‑21] | internal functions should be removed to save deployment gas |
71 |
[D‑22] | 21 | |
[D‑23] | delete statement can save gas |
6 |
[D‑24] | != 0 or == 0 for unsigned integer comparison |
12 |
[D‑25] | 29 | |
[D‑26] | 1 | |
[D‑27] | 6 | |
[D‑28] | 1 | |
[D‑29] | safeTransfer function does not check for contract existence |
7 |
[D‑30] | 2 | |
[D‑31] | 38 | |
[D‑32] | 2 | |
[D‑33] | 21 | |
[D‑34] | 5 | |
[D‑35] | 1 | |
[D‑36] | keccak256() should only need to be called on a specific string literal once |
1 |
[D‑37] | SafeCast to downcast safely |
2 |
[D‑38] | 1 | |
[D‑39] | calldata instead of memory for immutable arguments |
9 |
[D‑40] | 8 | |
[D‑41] | 2 | |
[D‑42] | 1 | |
[D‑43] | 3 | |
[D‑44] | 2 | |
[D‑45] | bytes32 rather than a string |
3 |
[D‑46] | 8 | |
[D‑47] | unchecked blocks with additions/multiplications may overflow |
2 |
[D‑48] | unchecked blocks with subtractions may underflow |
2 |
[D‑49] | 1 | |
[D‑50] | 7 |
Contracts with privileged functions need owner/admin to be trusted not to perform malicious updates or drain funds. This may also cause a single point failure.
There are 7 instances:
63: function registerBorrower(address borrower) external onlyOwner {
70: function removeBorrower(address borrower) external onlyOwner {
106: function registerControllerFactory(address factory) external onlyOwner {
113: function removeControllerFactory(address factory) external onlyOwner {
156: function removeController(address controller) external onlyOwner {
199: function removeMarket(address market) external onlyOwner {
- WildcatMarketControllerFactory.sol ( #L195-L200 ):
195: function setProtocolFeeConfiguration(
196: address feeRecipient,
197: address originationFeeAsset,
198: uint80 originationFeeAmount,
199: uint16 protocolFeeBips
200: ) external onlyArchControllerOwner {
Not all ERC20 implementations revert()
when there's a failure in transfer()
or transferFrom()
. The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually transfer anything.
There is 1 instance:
- WildcatSanctionsEscrow.sol ( #L38 ):
38: IERC20(asset).transfer(account, amount);
Some tokens do not implement the ERC20 standard properly. For example Tether (USDT)'s transfer()
and transferFrom()
functions do not return booleans as the ERC20 specification requires, and instead have no return value. When these sorts of tokens are cast to IERC20/ERC20, their function signatures do not match and therefore the calls made will revert.
It is recommended to use the SafeERC20
's safeTransfer()
and safeTransferFrom()
from OpenZeppelin instead.
There is 1 instance:
- WildcatSanctionsEscrow.sol ( #L38 ):
38: IERC20(asset).transfer(account, amount);
Ownable2Step
and Ownable2StepUpgradeable
prevent the contract ownership from mistakenly being transferred to an address that cannot handle it (e.g. due to a typo in the address), by requiring that the recipient of the owner permissions actively accept via a contract call of its own.
There is 1 instance:
- WildcatArchController.sol ( #L8 ):
8: contract WildcatArchController is Ownable {
The symbol()
function is not a part of the ERC-20 standard, and was added later as an optional extension. As such, some valid ERC20 tokens do not support this interface, so it is unsafe to blindly cast all tokens to this interface, and then call this function.
There is 1 instance:
- WildcatMarketBase.sol ( #L99 ):
99: decimals = IERC20Metadata(parameters.asset).decimals();
Constructors often take address parameters to initialize important components of a contract, such as owner or linked contracts. However, without a checking, there's a risk that an address parameter could be mistakenly set to the zero address (0x0). This could be due to an error or oversight during contract deployment. A zero address in a crucial role can cause serious issues, as it cannot perform actions like a normal address, and any funds sent to it will be irretrievable. It's therefore crucial to include a zero address check in constructors to prevent such potential problems. If a zero address is detected, the constructor should revert the transaction.
There are 4 instances:
- WildcatMarketControllerFactory.sol ( #L72-L76 ):
/// @audit `_archController not checked`
/// @audit `_sentinel not checked`
72: constructor(
73: address _archController,
74: address _sentinel,
75: MarketParameterConstraints memory constraints
76: ) {
- WildcatSanctionsSentinel.sol ( #L24 ):
/// @audit `_archController not checked`
/// @audit `_chainalysisSanctionsList not checked`
24: constructor(address _archController, address _chainalysisSanctionsList) {
It's good practice to revert a token transfer transaction if the recipient's address is the zero address. This can prevent unintentional transfers to the zero address due to accidental operations or programming errors. Many token contracts implement such a safeguard, such as OpenZeppelin - ERC20, OpenZeppelin - ERC721.
There are 6 instances:
- WildcatMarketController.sol ( #L346 ):
346: originationFeeAsset.safeTransferFrom(borrower, parameters.feeRecipient, originationFeeAmount);
- WildcatSanctionsEscrow.sol ( #L38 ):
38: IERC20(asset).transfer(account, amount);
107: asset.safeTransfer(feeRecipient, withdrawableFees);
157: asset.safeTransfer(borrower, currentlyHeld - totalDebts);
171: asset.safeTransfer(escrow, normalizedAmountWithdrawn);
179: asset.safeTransfer(accountAddress, normalizedAmountWithdrawn);
There is a subtle difference between the implementation of solady/solmate's SafeTransferLib
and OZ's SafeERC20. OZ's SafeERC20 checks if the token is a contract or not, while SafeTransferLib
does not:
@dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
There is 1 instance:
- WildcatMarketController.sol ( #L346 ):
346: originationFeeAsset.safeTransferFrom(borrower, parameters.feeRecipient, originationFeeAmount);
There will be breaking changes in future versions of solidity, and at that point your code will no longer be compatible. While you may have the specific version to use in a configuration file, others that include your source files may not.
There are 21 instances (click to show):
- ReentrancyGuard.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatArchController.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketController.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketControllerFactory.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatSanctionsEscrow.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatSanctionsSentinel.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- BoolUtils.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- Errors.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- FIFOQueue.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- FeeMath.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- LibStoredInitCode.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- MarketState.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- MathUtils.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- SafeCastLib.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- StringQuery.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- Withdrawal.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarket.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketBase.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketConfig.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketToken.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketWithdrawals.sol ( #L2 ):
2: pragma solidity >=0.8.20;
Array entries are added but are never removed. Consider whether this should be the case, or whether there should be a maximum, or whether old entries should be removed. Cases where there are specific potential problems will be flagged separately under a different issue.
There is 1 instance:
- WildcatMarketBase.sol ( #L484 ):
484: _withdrawalData.unpaidBatches.push(expiry);
Consider adding appropriate minimum/maximum value checks to ensure that the following state variables can never be used to excessively harm users, including via griefing.
There is 1 instance:
- WildcatMarketControllerFactory.sol ( #L211-L216 ):
211: _protocolFeeConfiguration = ProtocolFeeConfiguration({
212: feeRecipient: feeRecipient,
213: originationFeeAsset: originationFeeAsset,
214: originationFeeAmount: originationFeeAmount,
215: protocolFeeBips: protocolFeeBips
216: });
There are 8 instances:
97: archController = IWildcatArchController(parameters.archController);
98: borrower = parameters.borrower;
99: sentinel = parameters.sentinel;
100: marketInitCodeStorage = parameters.marketInitCodeStorage;
77: archController = IWildcatArchController(_archController);
78: sentinel = _sentinel;
25: archController = _archController;
26: chainalysisSanctionsList = _chainalysisSanctionsList;
Each of the following contracts implements or inherits the renounceOwnership()
function. This can represent a certain risk if the ownership is renounced for any other reason than by design. Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.
There is 1 instance:
- WildcatArchController.sol ( #L8 ):
8: contract WildcatArchController is Ownable {
A copy-paste error or a typo may end up bricking protocol functionality, or sending tokens to an address with no known private key. Consider implementing a two-step procedure for critical functions, where the recipient is set as pending, and must "accept" the assignment by making an affirmative call. A straight forward way of doing this would be to have the target contracts implement EIP-165, and to have the "set" functions ensure that the recipient is of the right interface type.
There are 2 instances:
- WildcatMarketControllerFactory.sol ( #L195-L200 ):
195: function setProtocolFeeConfiguration(
196: address feeRecipient,
197: address originationFeeAsset,
198: uint80 originationFeeAmount,
199: uint16 protocolFeeBips
200: ) external onlyArchControllerOwner {
- WildcatMarketConfig.sol ( #L112-L115 ):
112: function updateAccountAuthorization(
113: address _account,
114: bool _isAuthorized
115: ) external onlyController nonReentrant {
Constructors and initialization functions play a critical role in contracts by setting important initial states when the contract is first deployed before the system starts. The parameters passed to the constructor and initialization functions directly affect the behavior of the contract / protocol. If incorrect parameters are provided, the system may fail to run, behave abnormally, be unstable, or lack security. Therefore, it's crucial to carefully check each parameter in the constructor and initialization functions. If an exception is found, the transaction should be rolled back.
There are 4 instances:
- WildcatMarketControllerFactory.sol ( #L72-L76 ):
/// @audit `_archController`
/// @audit `_sentinel`
72: constructor(
73: address _archController,
74: address _sentinel,
75: MarketParameterConstraints memory constraints
76: ) {
- WildcatSanctionsSentinel.sol ( #L24 ):
/// @audit `_archController`
/// @audit `_chainalysisSanctionsList`
24: constructor(address _archController, address _chainalysisSanctionsList) {
Some tokens take a transfer fee (e.g. STA, PAXG), some do not currently charge a fee but may do so in the future (e.g. USDT, USDC).
The functions below transfer funds from the caller to the receiver via transferFrom()
, but do not ensure that the actual number of tokens received is the same as the input amount to the transfer. If the token is a fee-on-transfer token, the balance after the transfer will be smaller than expected, leading to accounting issues. Even if there are checks later, related to a secondary transfer, an attacker may be able to use latent funds (e.g. mistakenly sent by another user) in order to get a free credit.
One way to solve this problem is to measure the balance before and after the transfer, and use the difference as the amount, rather than the stated amount.
There are 8 instances (click to show):
- WildcatMarketController.sol ( #L346 ):
346: originationFeeAsset.safeTransferFrom(borrower, parameters.feeRecipient, originationFeeAmount);
- WildcatSanctionsEscrow.sol ( #L38 ):
38: IERC20(asset).transfer(account, amount);
60: asset.safeTransferFrom(msg.sender, address(this), amount);
107: asset.safeTransfer(feeRecipient, withdrawableFees);
129: asset.safeTransfer(msg.sender, amount);
157: asset.safeTransfer(borrower, currentlyHeld - totalDebts);
171: asset.safeTransfer(escrow, normalizedAmountWithdrawn);
179: asset.safeTransfer(accountAddress, normalizedAmountWithdrawn);
[L‑14] Functions calling contracts/addresses with transfer hooks should be protected by reentrancy guard
Even if the function follows the best practice of check-effects-interaction, not using a reentrancy guard when there may be transfer hooks opens the users of this protocol up to read-only reentrancy vulnerability with no way to protect them except by block-listing the entire protocol.
There are 2 instances:
- WildcatMarketController.sol ( #L346 ):
346: originationFeeAsset.safeTransferFrom(borrower, parameters.feeRecipient, originationFeeAmount);
- WildcatSanctionsEscrow.sol ( #L38 ):
38: IERC20(asset).transfer(account, amount);
Governance functions (such as upgrading contracts, setting critical parameters) should be controlled using time locks to introduce a delay between a proposal and its execution. This gives users time to exit before a potentially dangerous or malicious operation is applied.
There are 10 instances (click to show):
70: function removeBorrower(address borrower) external onlyOwner {
113: function removeControllerFactory(address factory) external onlyOwner {
156: function removeController(address controller) external onlyOwner {
199: function removeMarket(address market) external onlyOwner {
- WildcatMarketController.sol ( #L182, #L468-L471 ):
182: function updateLenderAuthorization(address lender, address[] memory markets) external {
468: function setAnnualInterestBips(
469: address market,
470: uint16 annualInterestBips
471: ) external virtual onlyBorrower onlyControlledMarket(market) {
- WildcatMarketControllerFactory.sol ( #L195-L200 ):
195: function setProtocolFeeConfiguration(
196: address feeRecipient,
197: address originationFeeAsset,
198: uint80 originationFeeAmount,
199: uint16 protocolFeeBips
200: ) external onlyArchControllerOwner {
- WildcatSanctionsSentinel.sol ( #L56 ):
56: function removeSanctionOverride(address account) public override {
- WildcatMarket.sol ( #L142 ):
142: function closeMarket() external onlyController nonReentrant {
- WildcatMarketConfig.sol ( #L112-L115 ):
112: function updateAccountAuthorization(
113: address _account,
114: bool _isAuthorized
115: ) external onlyController nonReentrant {
Calling external functions within loops can easily result in insufficient gas. This greatly increases the likelihood of transaction failures, DOS attacks, and other unexpected actions. It is recommended to limit the number of loops within loops that call external functions, and to limit the gas line for each external call.
There is 1 instance:
- WildcatMarketController.sol ( #L188 ):
/// @audit Calling `updateAccountAuthorization()` within `for` loop
188: WildcatMarket(market).updateAccountAuthorization(lender, _authorizedLenders.contains(lender));
Despite the fact that EIP-20 states that zero-value transfers must be accepted, some tokens, such as LEND, will revert if this is attempted, which may cause transactions that involve other tokens (such as batch operations) to fully revert. Consider skipping the transfer if the amount is zero, which will also save gas.
There are 4 instances:
- WildcatMarketController.sol ( #L346 ):
346: originationFeeAsset.safeTransferFrom(borrower, parameters.feeRecipient, originationFeeAmount);
- WildcatSanctionsEscrow.sol ( #L38 ):
38: IERC20(asset).transfer(account, amount);
60: asset.safeTransferFrom(msg.sender, address(this), amount);
157: asset.safeTransfer(borrower, currentlyHeld - totalDebts);
When a type is downcast to a smaller type, the higher order bits are truncated, effectively applying a modulo to the original value. The loss of data may cause incorrect calculations, unexpected state changes, or other unexpected behavior. It is recommended to use the SafeCast library.
There are 10 instances (click to show):
- WildcatMarketController.sol ( #L484 ):
/// @audit uint256 -> uint128
484: tmp.expiry = uint128(block.timestamp + 2 weeks);
- WildcatSanctionsSentinel.sol ( #L72-L83 ):
/// @audit uint256 -> uint160
72: uint160(
73: uint256(
74: keccak256(
75: abi.encodePacked(
76: bytes1(0xff),
77: address(this),
78: keccak256(abi.encode(borrower, account, asset)),
79: WildcatSanctionsEscrowInitcodeHash
80: )
81: )
82: )
83: )
- FeeMath.sol ( #L172 ):
/// @audit uint256 -> uint32
172: state.lastInterestAccruedTimestamp = uint32(timestamp);
- MarketState.sol ( #L110 ):
/// @audit uint256 -> uint128
110: return uint128(MathUtils.min(totalAvailableAssets, state.accruedProtocolFees));
/// @audit uint256 -> uint112
113: scaleFactor: uint112(RAY),
/// @audit uint256 -> uint32
114: lastInterestAccruedTimestamp: uint32(block.timestamp)
/// @audit uint256 -> uint104
510: uint104 scaledAmountBurned = uint104(MathUtils.min(scaledAvailableLiquidity, scaledAmountOwed));
/// @audit uint256 -> uint104
539: uint104 scaledAmountBurned = uint104(MathUtils.min(scaledAvailableLiquidity, scaledAmountOwed));
- WildcatMarketWithdrawals.sol ( #L95, #L151-L153 ):
/// @audit uint256 -> uint32
95: state.pendingWithdrawalExpiry = uint32(block.timestamp + withdrawalBatchDuration);
/// @audit uint256 -> uint128
151: uint128 newTotalWithdrawn = uint128(
152: MathUtils.mulDiv(batch.normalizedAmountPaid, status.scaledAmount, batch.scaledTotalAmount)
153: );
Passing 0
or 0x0
as a function argument can sometimes result in a security issue(e.g. passing zero as the slippage parameter). A historical example is the infamous 0x0
address bug where numerous tokens were lost. This happens because 0
can be interpreted as an uninitialized address
, leading to transfers to the 0x0
address
, effectively burning tokens. Moreover, 0
as a denominator in division operations would cause a runtime exception. It's also often indicative of a logical error in the caller's code.
Consider using a constant variable with a descriptive name, so it's clear that the argument is intentionally being used, and for the right reasons.
There are 2 instances:
84: deployment = createWithStoredInitCode(initCodeStorage, 0);
103: deployment = create2WithStoredInitCode(initCodeStorage, salt, 0);
Division by large numbers may result in the result being zero, due to solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator.
There are 2 instances:
63: uint256 newTotalWithdrawn = uint256(batch.normalizedAmountPaid).mulDiv(
64: status.scaledAmount,
65: batch.scaledTotalAmount
66: );
152: MathUtils.mulDiv(batch.normalizedAmountPaid, status.scaledAmount, batch.scaledTotalAmount)
Code should follow the best-practice of check-effects-interaction, where state variables are updated before any external calls are made. Doing so prevents a large class of reentrancy bugs.
There are 5 instances:
- WildcatMarketController.sol ( #L500 ):
/// @audit `setReserveRatioBips()` is called on line 499
500: delete temporaryExcessReserveRatio[market];
- WildcatSanctionsSentinel.sol ( #L114 ):
/// @audit `()` is called on line 110
114: sanctionOverrides[borrower][escrowContract] = true;
- WildcatMarket.sol ( #L65 ):
/// @audit `safeTransferFrom()` is called on line 60
65: _accounts[msg.sender] = account;
/// @audit `createEscrow()` is called on line 172
178: _accounts[escrow].scaledBalance += scaledBalance;
/// @audit `createEscrow()` is called on line 172
185: _accounts[accountAddress] = account;
If some functions are only allowed to be called by some specific users, consider using a modifier instead of checking with a require statement, especially if this check is done in multiple functions.
There are 4 instances:
- WildcatMarketController.sol ( #L303-L305, #L306-L308 ):
303: if (!archController.isRegisteredBorrower(msg.sender)) {
304: revert NotRegisteredBorrower();
305: }
306: } else if (msg.sender != address(controllerFactory)) {
307: revert CallerNotBorrowerOrControllerFactory();
308: }
- WildcatMarketControllerFactory.sol ( #L283-L285 ):
283: if (!archController.isRegisteredBorrower(msg.sender)) {
284: revert NotRegisteredBorrower();
285: }
- WildcatSanctionsSentinel.sol ( #L100-L102 ):
100: if (!IWildcatArchController(archController).isRegisteredMarket(msg.sender)) {
101: revert NotRegisteredMarket();
102: }
Consider adding parameters to the error to indicate which user or values caused the failure.
There are 21 instances (click to show):
- ReentrancyGuard.sol ( #L17 ):
17: error NoReentrantCalls();
16: error NotControllerFactory();
17: error NotController();
19: error BorrowerAlreadyExists();
20: error ControllerFactoryAlreadyExists();
21: error ControllerAlreadyExists();
22: error MarketAlreadyExists();
24: error BorrowerDoesNotExist();
25: error ControllerFactoryDoesNotExist();
26: error ControllerDoesNotExist();
27: error MarketDoesNotExist();
24: error NotRegisteredBorrower();
25: error InvalidProtocolFeeConfiguration();
26: error CallerNotArchControllerOwner();
27: error InvalidConstraints();
28: error ControllerAlreadyDeployed();
- FIFOQueue.sol ( #L17 ):
17: error FIFOQueueOutOfBounds();
- LibStoredInitCode.sol ( #L5 ):
5: error InitCodeDeploymentFailed();
- MathUtils.sol ( #L19 ):
19: error MulDivFailed();
16: error InvalidReturnDataString();
17: error InvalidCompactString();
Using import declarations of the form import {<identifier_name>} from "some/file.sol"
avoids polluting the symbol namespace making flattened files smaller, and speeds up compilation (but does not save any gas).
There are 50 instances (click to show):
5: import 'solady/auth/Ownable.sol';
6: import './libraries/MathUtils.sol';
5: import 'solady/utils/SafeTransferLib.sol';
6: import './market/WildcatMarket.sol';
7: import './interfaces/IWildcatArchController.sol';
8: import './interfaces/IWildcatMarketControllerEventsAndErrors.sol';
9: import './interfaces/IWildcatMarketControllerFactory.sol';
10: import './libraries/LibStoredInitCode.sol';
11: import './libraries/MathUtils.sol';
5: import './interfaces/WildcatStructsAndEnums.sol';
6: import './interfaces/IWildcatMarketController.sol';
7: import './interfaces/IWildcatArchController.sol';
8: import './libraries/LibStoredInitCode.sol';
9: import './libraries/MathUtils.sol';
10: import './market/WildcatMarket.sol';
11: import './WildcatMarketController.sol';
- Chainalysis.sol ( #L4 ):
4: import '../interfaces/IChainalysisSanctionsList.sol';
4: import './MathUtils.sol';
5: import './SafeCastLib.sol';
6: import './MarketState.sol';
5: import './MathUtils.sol';
6: import './SafeCastLib.sol';
7: import './FeeMath.sol';
- MathUtils.sol ( #L4 ):
4: import './Errors.sol';
- SafeCastLib.sol ( #L4 ):
4: import './Errors.sol';
4: import './MarketState.sol';
5: import './FIFOQueue.sol';
4: import '../libraries/FeeMath.sol';
5: import './WildcatMarketBase.sol';
6: import './WildcatMarketConfig.sol';
7: import './WildcatMarketToken.sol';
8: import './WildcatMarketWithdrawals.sol';
4: import '../libraries/FeeMath.sol';
5: import '../libraries/Withdrawal.sol';
7: import '../interfaces/IMarketEventsAndErrors.sol';
8: import '../interfaces/IWildcatMarketController.sol';
9: import '../interfaces/IWildcatSanctionsSentinel.sol';
11: import '../ReentrancyGuard.sol';
12: import '../libraries/BoolUtils.sol';
4: import '../interfaces/IWildcatSanctionsSentinel.sol';
5: import '../libraries/FeeMath.sol';
6: import '../libraries/SafeCastLib.sol';
7: import './WildcatMarketBase.sol';
- WildcatMarketToken.sol ( #L4 ):
4: import './WildcatMarketBase.sol';
4: import './WildcatMarketBase.sol';
5: import '../libraries/MarketState.sol';
6: import '../libraries/FeeMath.sol';
7: import '../libraries/FIFOQueue.sol';
8: import '../interfaces/IWildcatSanctionsSentinel.sol';
9: import 'solady/utils/SafeTransferLib.sol';
Functions with too many lines are difficult to understand. It is recommended to refactor complex functions into multiple shorter and easier to understand functions.
There are 4 instances:
- WildcatMarketController.sol ( #L291 ):
/// @audit 70 lines
291: function deployMarket(
- StringQuery.sol ( #L33 ):
/// @audit 62 lines
33: function queryStringOrBytes32AsString(
- WildcatMarketBase.sol ( #L76 ):
/// @audit 50 lines
76: constructor() {
- WildcatMarketWithdrawals.sol ( #L137 ):
/// @audit 52 lines
137: function executeWithdrawal(
Since the variables are automatically set to 0 when created, it is redundant to initialize it with 0 again.
There are 12 instances (click to show):
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++) {
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++) {
212: for (uint256 i = 0; i < count; i++) {
- WildcatMarketControllerFactory.sol ( #L146 ):
146: for (uint256 i = 0; i < count; i++) {
- Errors.sol ( #L4 ):
4: uint256 constant Panic_CompilerPanic = 0x00;
48: for (uint256 i = 0; i < len; i++) {
75: for (uint256 i = 0; i < n; i++) {
e.g. @dev
or @notice
, and it must appear above the contract definition braces in order to be identified by the compiler as NatSpec.
There are 18 instances (click to show):
- WildcatArchController.sol ( #L8 ):
8: contract WildcatArchController is Ownable {
- WildcatMarketController.sol ( #L32 ):
32: contract WildcatMarketController is IWildcatMarketControllerEventsAndErrors {
- WildcatMarketControllerFactory.sol ( #L13 ):
13: contract WildcatMarketControllerFactory {
- WildcatSanctionsEscrow.sol ( #L10 ):
10: contract WildcatSanctionsEscrow is IWildcatSanctionsEscrow {
- WildcatSanctionsSentinel.sol ( #L10 ):
10: contract WildcatSanctionsSentinel is IWildcatSanctionsSentinel {
- BoolUtils.sol ( #L4 ):
4: library BoolUtils {
- FIFOQueue.sol ( #L16 ):
16: library FIFOQueueLib {
- FeeMath.sol ( #L11 ):
11: library FeeMath {
- LibStoredInitCode.sol ( #L4 ):
4: library LibStoredInitCode {
- MarketState.sol ( #L44 ):
44: library MarketStateLib {
- MathUtils.sol ( #L16 ):
16: library MathUtils {
- SafeCastLib.sol ( #L6 ):
6: library SafeCastLib {
- Withdrawal.sol ( #L37 ):
37: library WithdrawalLib {
- WildcatMarket.sol ( #L10 ):
10: contract WildcatMarket is
- WildcatMarketBase.sol ( #L14 ):
14: contract WildcatMarketBase is ReentrancyGuard, IMarketEventsAndErrors {
- WildcatMarketConfig.sol ( #L9 ):
9: contract WildcatMarketConfig is WildcatMarketBase {
- WildcatMarketToken.sol ( #L6 ):
6: contract WildcatMarketToken is WildcatMarketBase {
- WildcatMarketWithdrawals.sol ( #L11 ):
11: contract WildcatMarketWithdrawals is WildcatMarketBase {
It is recommended by the Solidity Style Guide
There are 25 instances (click to show):
- WildcatSanctionsSentinel.sol ( #L11-L12 ):
11: bytes32 public constant override WildcatSanctionsEscrowInitcodeHash =
12: keccak256(type(WildcatSanctionsEscrow).creationCode);
- Chainalysis.sol ( #L6-L8 ):
6: IChainalysisSanctionsList constant SanctionsList = IChainalysisSanctionsList(
7: 0x40C57923924B5c5c5455c48D93317139ADDaC8fb
8: );
4: uint256 constant Panic_CompilerPanic = 0x00;
5: uint256 constant Panic_AssertFalse = 0x01;
6: uint256 constant Panic_Arithmetic = 0x11;
7: uint256 constant Panic_DivideByZero = 0x12;
8: uint256 constant Panic_InvalidEnumValue = 0x21;
9: uint256 constant Panic_InvalidStorageByteArray = 0x22;
10: uint256 constant Panic_EmptyArrayPop = 0x31;
11: uint256 constant Panic_ArrayOutOfBounds = 0x32;
12: uint256 constant Panic_MemoryTooLarge = 0x41;
13: uint256 constant Panic_UninitializedFunctionPointer = 0x51;
15: uint256 constant Panic_ErrorSelector = 0x4e487b71;
16: uint256 constant Panic_ErrorCodePointer = 0x20;
17: uint256 constant Panic_ErrorLength = 0x24;
18: uint256 constant Error_SelectorPointer = 0x1c;
- StringQuery.sol ( #L8-L10, #L12, #L13, #L14, #L106-L108, #L109-L111, #L112-L114, #L115-L117 ):
8: uint256 constant InvalidReturnDataString_selector = (
9: 0x4cb9c00000000000000000000000000000000000000000000000000000000000
10: );
12: uint256 constant SixtyThreeBytes = 0x3f;
13: uint256 constant ThirtyOneBytes = 0x1f;
14: uint256 constant OnlyFullWordMask = 0xffffffe0;
106: uint256 constant UnknownNameQueryError_selector = (
107: 0xed3df7ad00000000000000000000000000000000000000000000000000000000
108: );
109: uint256 constant UnknownSymbolQueryError_selector = (
110: 0x89ff815700000000000000000000000000000000000000000000000000000000
111: );
112: uint256 constant NameFunction_selector = (
113: 0x06fdde0300000000000000000000000000000000000000000000000000000000
114: );
115: uint256 constant SymbolFunction_selector = (
116: 0x95d89b4100000000000000000000000000000000000000000000000000000000
117: );
- WildcatMarketBase.sol ( #L24 ):
24: string public constant version = '1.0';
It is recommended by the Solidity Style Guide.
There are 80 instances (click to show):
- WildcatMarketController.sol ( #L394-L402, #L503-L508 ):
394: function enforceParameterConstraints(
395: string memory namePrefix,
396: string memory symbolPrefix,
397: uint16 annualInterestBips,
398: uint16 delinquencyFeeBips,
399: uint32 withdrawalBatchDuration,
400: uint16 reserveRatioBips,
401: uint32 delinquencyGracePeriod
402: ) internal view virtual {
503: function assertValueInRange(
504: uint256 value,
505: uint256 min,
506: uint256 max,
507: bytes4 errorSelector
508: ) internal pure {
5: function and(bool a, bool b) internal pure returns (bool c) {
11: function or(bool a, bool b) internal pure returns (bool c) {
17: function xor(bool a, bool b) internal pure returns (bool c) {
19: function empty(FIFOQueue storage arr) internal view returns (bool) {
23: function first(FIFOQueue storage arr) internal view returns (uint32) {
30: function at(FIFOQueue storage arr, uint256 index) internal view returns (uint32) {
38: function length(FIFOQueue storage arr) internal view returns (uint128) {
42: function values(FIFOQueue storage arr) internal view returns (uint32[] memory _values) {
55: function push(FIFOQueue storage arr, uint32 value) internal {
61: function shift(FIFOQueue storage arr) internal {
70: function shiftN(FIFOQueue storage arr, uint128 n) internal {
19: function calculateLinearInterestFromBips(
20: uint256 rateBip,
21: uint256 timeDelta
22: ) internal pure returns (uint256 result) {
30: function calculateBaseInterest(
31: MarketState memory state,
32: uint256 timestamp
33: ) internal pure returns (uint256 baseInterestRay) {
40: function applyProtocolFee(
41: MarketState memory state,
42: uint256 baseInterestRay,
43: uint256 protocolFeeBips
44: ) internal pure returns (uint256 protocolFee) {
53: function updateDelinquency(
54: MarketState memory state,
55: uint256 timestamp,
56: uint256 delinquencyFeeBips,
57: uint256 delinquencyGracePeriod
58: ) internal pure returns (uint256 delinquencyFeeRay) {
89: function updateTimeDelinquentAndGetPenaltyTime(
90: MarketState memory state,
91: uint256 delinquencyGracePeriod,
92: uint256 timeDelta
93: ) internal pure returns (uint256 /* timeWithPenalty */) {
142: function updateScaleFactorAndFees(
143: MarketState memory state,
144: uint256 protocolFeeBips,
145: uint256 delinquencyFeeBips,
146: uint256 delinquencyGracePeriod,
147: uint256 timestamp
148: )
149: internal
150: pure
151: returns (uint256 baseInterestRay, uint256 delinquencyFeeRay, uint256 protocolFee)
7: function deployInitCode(bytes memory data) internal returns (address initCodeStorage) {
48: function getCreate2Prefix(address deployer) internal pure returns (uint256 create2Prefix) {
54: function calculateCreate2Address(
55: uint256 create2Prefix,
56: bytes32 salt,
57: uint256 initCodeHash
58: ) internal pure returns (address create2Address) {
83: function createWithStoredInitCode(address initCodeStorage) internal returns (address deployment) {
87: function createWithStoredInitCode(
88: address initCodeStorage,
89: uint256 value
90: ) internal returns (address deployment) {
99: function create2WithStoredInitCode(
100: address initCodeStorage,
101: bytes32 salt
102: ) internal returns (address deployment) {
106: function create2WithStoredInitCode(
107: address initCodeStorage,
108: bytes32 salt,
109: uint256 value
110: ) internal returns (address deployment) {
- MarketState.sol ( #L51, #L59, #L66-L69, #L76, #L87-L89, #L105-L108, #L123-L126, #L130, #L138 ):
51: function totalSupply(MarketState memory state) internal pure returns (uint256) {
59: function maximumDeposit(MarketState memory state) internal pure returns (uint256) {
66: function normalizeAmount(
67: MarketState memory state,
68: uint256 amount
69: ) internal pure returns (uint256) {
76: function scaleAmount(MarketState memory state, uint256 amount) internal pure returns (uint256) {
87: function liquidityRequired(
88: MarketState memory state
89: ) internal pure returns (uint256 _liquidityRequired) {
105: function withdrawableProtocolFees(
106: MarketState memory state,
107: uint256 totalAssets
108: ) internal pure returns (uint128) {
123: function borrowableAssets(
124: MarketState memory state,
125: uint256 totalAssets
126: ) internal pure returns (uint256) {
130: function hasPendingExpiredBatch(MarketState memory state) internal view returns (bool result) {
138: function totalDebts(MarketState memory state) internal pure returns (uint256) {
- MathUtils.sol ( #L30-L33, #L44, #L51, #L59, #L71-L75, #L85, #L105, #L121, #L138, #L155, #L173, #L191 ):
30: function calculateLinearInterestFromBips(
31: uint256 rateBip,
32: uint256 timeDelta
33: ) internal pure returns (uint256 result) {
44: function min(uint256 a, uint256 b) internal pure returns (uint256 c) {
51: function max(uint256 a, uint256 b) internal pure returns (uint256 c) {
59: function satSub(uint256 a, uint256 b) internal pure returns (uint256 c) {
71: function ternary(
72: bool condition,
73: uint256 valueIfTrue,
74: uint256 valueIfFalse
75: ) internal pure returns (uint256 c) {
85: function bipMul(uint256 a, uint256 b) internal pure returns (uint256 c) {
105: function bipDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {
121: function bipToRay(uint256 a) internal pure returns (uint256 b) {
138: function rayMul(uint256 a, uint256 b) internal pure returns (uint256 c) {
155: function rayDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {
173: function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
191: function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
- SafeCastLib.sol ( #L17, #L21, #L25, #L29, #L33, #L37, #L41, #L45, #L49, #L53, #L57, #L61, #L65, #L69, #L73, #L77, #L81, #L85, #L89, #L93, #L97, #L101, #L105, #L109, #L113, #L117, #L121, #L125, #L129, #L133, #L137 ):
17: function toUint8(uint256 x) internal pure returns (uint8 y) {
21: function toUint16(uint256 x) internal pure returns (uint16 y) {
25: function toUint24(uint256 x) internal pure returns (uint24 y) {
29: function toUint32(uint256 x) internal pure returns (uint32 y) {
33: function toUint40(uint256 x) internal pure returns (uint40 y) {
37: function toUint48(uint256 x) internal pure returns (uint48 y) {
41: function toUint56(uint256 x) internal pure returns (uint56 y) {
45: function toUint64(uint256 x) internal pure returns (uint64 y) {
49: function toUint72(uint256 x) internal pure returns (uint72 y) {
53: function toUint80(uint256 x) internal pure returns (uint80 y) {
57: function toUint88(uint256 x) internal pure returns (uint88 y) {
61: function toUint96(uint256 x) internal pure returns (uint96 y) {
65: function toUint104(uint256 x) internal pure returns (uint104 y) {
69: function toUint112(uint256 x) internal pure returns (uint112 y) {
73: function toUint120(uint256 x) internal pure returns (uint120 y) {
77: function toUint128(uint256 x) internal pure returns (uint128 y) {
81: function toUint136(uint256 x) internal pure returns (uint136 y) {
85: function toUint144(uint256 x) internal pure returns (uint144 y) {
89: function toUint152(uint256 x) internal pure returns (uint152 y) {
93: function toUint160(uint256 x) internal pure returns (uint160 y) {
97: function toUint168(uint256 x) internal pure returns (uint168 y) {
101: function toUint176(uint256 x) internal pure returns (uint176 y) {
105: function toUint184(uint256 x) internal pure returns (uint184 y) {
109: function toUint192(uint256 x) internal pure returns (uint192 y) {
113: function toUint200(uint256 x) internal pure returns (uint200 y) {
117: function toUint208(uint256 x) internal pure returns (uint208 y) {
121: function toUint216(uint256 x) internal pure returns (uint216 y) {
125: function toUint224(uint256 x) internal pure returns (uint224 y) {
129: function toUint232(uint256 x) internal pure returns (uint232 y) {
133: function toUint240(uint256 x) internal pure returns (uint240 y) {
137: function toUint248(uint256 x) internal pure returns (uint248 y) {
38: function scaledOwedAmount(WithdrawalBatch memory batch) internal pure returns (uint104) {
47: function availableLiquidityForPendingBatch(
48: WithdrawalBatch memory batch,
49: MarketState memory state,
50: uint256 totalAssets
51: ) internal pure returns (uint256) {
It is recommended by the Solidity Style Guide.
There are 23 instances (click to show):
53: uint256 internal immutable ownCreate2Prefix = LibStoredInitCode.getCreate2Prefix(address(this));
55: uint32 internal immutable MinimumDelinquencyGracePeriod;
56: uint32 internal immutable MaximumDelinquencyGracePeriod;
58: uint16 internal immutable MinimumReserveRatioBips;
59: uint16 internal immutable MaximumReserveRatioBips;
61: uint16 internal immutable MinimumDelinquencyFeeBips;
62: uint16 internal immutable MaximumDelinquencyFeeBips;
64: uint32 internal immutable MinimumWithdrawalBatchDuration;
65: uint32 internal immutable MaximumWithdrawalBatchDuration;
67: uint16 internal immutable MinimumAnnualInterestBips;
68: uint16 internal immutable MaximumAnnualInterestBips;
- WildcatMarketControllerFactory.sol ( #L44, #L46, #L47, #L49, #L50, #L52, #L53, #L55, #L56, #L58, #L59 ):
44: uint256 internal immutable ownCreate2Prefix = LibStoredInitCode.getCreate2Prefix(address(this));
46: uint32 internal immutable MinimumDelinquencyGracePeriod;
47: uint32 internal immutable MaximumDelinquencyGracePeriod;
49: uint16 internal immutable MinimumReserveRatioBips;
50: uint16 internal immutable MaximumReserveRatioBips;
52: uint16 internal immutable MinimumDelinquencyFeeBips;
53: uint16 internal immutable MaximumDelinquencyFeeBips;
55: uint32 internal immutable MinimumWithdrawalBatchDuration;
56: uint32 internal immutable MaximumWithdrawalBatchDuration;
58: uint16 internal immutable MinimumAnnualInterestBips;
59: uint16 internal immutable MaximumAnnualInterestBips;
- WildcatSanctionsEscrow.sol ( #L14 ):
14: address internal immutable asset;
According to the Solidity Style Guide, functions should be grouped according to their visibility and ordered: constructor
, receive
, fallback
, external
, public
, internal
, private
. Within a grouping, place the view and pure functions last.
There are 43 instances (click to show):
/// @audit ↓↓ Out of order with `registerControllerFactory()`
98: function getRegisteredBorrowersCount() external view returns (uint256) {
/// @audit ↑↑ Out of order with `getRegisteredBorrowersCount()`
106: function registerControllerFactory(address factory) external onlyOwner {
/// @audit ↑↑ Out of order with `registerControllerFactory()`
113: function removeControllerFactory(address factory) external onlyOwner {
/// @audit ↓↓ Out of order with `registerController()`
141: function getRegisteredControllerFactoriesCount() external view returns (uint256) {
/// @audit ↑↑ Out of order with `getRegisteredControllerFactoriesCount()`
149: function registerController(address controller) external onlyControllerFactory {
/// @audit ↑↑ Out of order with `registerController()`
156: function removeController(address controller) external onlyOwner {
/// @audit ↓↓ Out of order with `registerMarket()`
184: function getRegisteredControllersCount() external view returns (uint256) {
/// @audit ↑↑ Out of order with `getRegisteredControllersCount()`
192: function registerMarket(address market) external onlyController {
/// @audit ↑↑ Out of order with `registerMarket()`
199: function removeMarket(address market) external onlyOwner {
- WildcatMarketController.sol ( #L142, #L153, #L169, #L182, #L255, #L291-L301, #L394-L402, #L446-L449, #L468-L471, #L490 ):
/// @audit ↓↓ Out of order with `authorizeLenders()`
142: function isAuthorizedLender(address lender) external view virtual returns (bool) {
/// @audit ↑↑ Out of order with `isAuthorizedLender()`
153: function authorizeLenders(address[] memory lenders) external onlyBorrower {
/// @audit ↑↑ Out of order with `authorizeLenders()`
169: function deauthorizeLenders(address[] memory lenders) external onlyBorrower {
/// @audit ↑↑ Out of order with `deauthorizeLenders()`
182: function updateLenderAuthorization(address lender, address[] memory markets) external {
/// @audit ↓↓ Out of order with `deployMarket()`
255: function _resetTmpMarketParameters() internal {
/// @audit ↑↑ Out of order with `_resetTmpMarketParameters()`
291: function deployMarket(
292: address asset,
293: string memory namePrefix,
294: string memory symbolPrefix,
295: uint128 maxTotalSupply,
296: uint16 annualInterestBips,
297: uint16 delinquencyFeeBips,
298: uint32 withdrawalBatchDuration,
299: uint16 reserveRatioBips,
300: uint32 delinquencyGracePeriod
301: ) external returns (address market) {
/// @audit ↓↓ Out of order with `getParameterConstraints()`
394: function enforceParameterConstraints(
395: string memory namePrefix,
396: string memory symbolPrefix,
397: uint16 annualInterestBips,
398: uint16 delinquencyFeeBips,
399: uint32 withdrawalBatchDuration,
400: uint16 reserveRatioBips,
401: uint32 delinquencyGracePeriod
402: ) internal view virtual {
/// @audit ↑↑ Out of order with `enforceParameterConstraints()`
446: function getParameterConstraints()
447: external
448: view
449: returns (MarketParameterConstraints memory constraints)
/// @audit ↑↑ Out of order with `getParameterConstraints()`
468: function setAnnualInterestBips(
469: address market,
470: uint16 annualInterestBips
471: ) external virtual onlyBorrower onlyControlledMarket(market) {
/// @audit ↑↑ Out of order with `setAnnualInterestBips()`
490: function resetReserveRatio(address market) external virtual {
- WildcatMarketControllerFactory.sol ( #L116-L119, #L126, #L165-L168, #L195-L200, #L282, #L317-L327 ):
/// @audit ↓↓ Out of order with `isDeployedController()`
116: function _storeMarketInitCode()
117: internal
118: virtual
119: returns (address initCodeStorage, uint256 initCodeHash)
/// @audit ↑↑ Out of order with `_storeMarketInitCode()`
126: function isDeployedController(address controller) external view returns (bool) {
/// @audit ↓↓ Out of order with `setProtocolFeeConfiguration()`
165: function getProtocolFeeConfiguration()
166: external
167: view
168: returns (
/// @audit ↑↑ Out of order with `getProtocolFeeConfiguration()`
195: function setProtocolFeeConfiguration(
196: address feeRecipient,
197: address originationFeeAsset,
198: uint80 originationFeeAmount,
199: uint16 protocolFeeBips
200: ) external onlyArchControllerOwner {
/// @audit ↓↓ Out of order with `deployControllerAndMarket()`
282: function deployController() public returns (address controller) {
/// @audit ↑↑ Out of order with `deployController()`
317: function deployControllerAndMarket(
318: string memory namePrefix,
319: string memory symbolPrefix,
320: address asset,
321: uint128 maxTotalSupply,
322: uint16 annualInterestBips,
323: uint16 delinquencyFeeBips,
324: uint32 withdrawalBatchDuration,
325: uint16 reserveRatioBips,
326: uint32 delinquencyGracePeriod
327: ) external returns (address controller, address market) {
/// @audit ↓↓ Out of order with `releaseEscrow()`
29: function escrowedAsset() public view override returns (address, uint256) {
/// @audit ↑↑ Out of order with `escrowedAsset()`
33: function releaseEscrow() public override {
/// @audit ↓↓ Out of order with `isSanctioned()`
30: function _resetTmpEscrowParams() internal {
/// @audit ↑↑ Out of order with `_resetTmpEscrowParams()`
39: function isSanctioned(address borrower, address account) public view override returns (bool) {
/// @audit ↓↓ Out of order with `createEscrow()`
65: function getEscrowAddress(
66: address borrower,
67: address account,
68: address asset
69: ) public view override returns (address escrowAddress) {
/// @audit ↑↑ Out of order with `getEscrowAddress()`
95: function createEscrow(
96: address borrower,
97: address account,
98: address asset
99: ) public override returns (address escrowContract) {
/// @audit ↓↓ Out of order with `push()`
42: function values(FIFOQueue storage arr) internal view returns (uint32[] memory _values) {
/// @audit ↑↑ Out of order with `values()`
55: function push(FIFOQueue storage arr, uint32 value) internal {
/// @audit ↑↑ Out of order with `push()`
61: function shift(FIFOQueue storage arr) internal {
/// @audit ↑↑ Out of order with `shift()`
70: function shiftN(FIFOQueue storage arr, uint128 n) internal {
- LibStoredInitCode.sol ( #L54-L58, #L83, #L87-L90, #L99-L102, #L106-L110 ):
/// @audit ↓↓ Out of order with `createWithStoredInitCode()`
54: function calculateCreate2Address(
55: uint256 create2Prefix,
56: bytes32 salt,
57: uint256 initCodeHash
58: ) internal pure returns (address create2Address) {
/// @audit ↑↑ Out of order with `calculateCreate2Address()`
83: function createWithStoredInitCode(address initCodeStorage) internal returns (address deployment) {
/// @audit ↑↑ Out of order with `createWithStoredInitCode()`
87: function createWithStoredInitCode(
88: address initCodeStorage,
89: uint256 value
90: ) internal returns (address deployment) {
/// @audit ↑↑ Out of order with `createWithStoredInitCode()`
99: function create2WithStoredInitCode(
100: address initCodeStorage,
101: bytes32 salt
102: ) internal returns (address deployment) {
/// @audit ↑↑ Out of order with `create2WithStoredInitCode()`
106: function create2WithStoredInitCode(
107: address initCodeStorage,
108: bytes32 salt,
109: uint256 value
110: ) internal returns (address deployment) {
/// @audit ↓↓ Out of order with `toUint8()`
7: function _assertNonOverflow(bool didNotOverflow) private pure {
/// @audit ↑↑ Out of order with `_assertNonOverflow()`
17: function toUint8(uint256 x) internal pure returns (uint8 y) {
/// @audit ↓↓ Out of order with `deposit()`
42: function depositUpTo(
43: uint256 amount
44: ) public virtual nonReentrant returns (uint256 /* actualAmount */) {
/// @audit ↑↑ Out of order with `depositUpTo()`
86: function deposit(uint256 amount) external virtual {
- WildcatMarketBase.sol ( #L197-L200, #L223, #L238, #L252, #L276, #L285, #L399-L402, #L448, #L466, #L498-L503 ):
/// @audit ↓↓ Out of order with `coverageLiquidity()`
197: function _getAccountWithRole(
198: address accountAddress,
199: AuthRole requiredRole
200: ) internal returns (Account memory account) {
/// @audit ↑↑ Out of order with `_getAccountWithRole()`
223: function coverageLiquidity() external view nonReentrantView returns (uint256) {
/// @audit ↓↓ Out of order with `borrowableAssets()`
238: function totalAssets() public view returns (uint256) {
/// @audit ↑↑ Out of order with `totalAssets()`
252: function borrowableAssets() external view nonReentrantView returns (uint256) {
/// @audit ↓↓ Out of order with `scaledTotalSupply()`
276: function currentState() public view nonReentrantView returns (MarketState memory state) {
/// @audit ↑↑ Out of order with `currentState()`
285: function scaledTotalSupply() external view nonReentrantView returns (uint256) {
/// @audit ↓↓ Out of order with `_writeState()`
399: function _calculateCurrentState()
400: internal
401: view
402: returns (
/// @audit ↑↑ Out of order with `_calculateCurrentState()`
448: function _writeState(MarketState memory state) internal {
/// @audit ↑↑ Out of order with `_writeState()`
466: function _processExpiredWithdrawalBatch(MarketState memory state) internal {
/// @audit ↑↑ Out of order with `_processExpiredWithdrawalBatch()`
498: function _applyWithdrawalBatchPayment(
499: WithdrawalBatch memory batch,
500: MarketState memory state,
501: uint32 expiry,
502: uint256 availableLiquidity
503: ) internal {
/// @audit ↓↓ Out of order with `nukeFromOrbit()`
42: function reserveRatioBips() external view returns (uint256) {
/// @audit ↑↑ Out of order with `reserveRatioBips()`
74: function nukeFromOrbit(address accountAddress) external nonReentrant {
/// @audit ↑↑ Out of order with `nukeFromOrbit()`
88: function stunningReversal(address accountAddress) external nonReentrant {
/// @audit ↑↑ Out of order with `stunningReversal()`
112: function updateAccountAuthorization(
113: address _account,
114: bool _isAuthorized
115: ) external onlyController nonReentrant {
/// @audit ↑↑ Out of order with `updateAccountAuthorization()`
134: function setMaxTotalSupply(uint256 _maxTotalSupply) external onlyController nonReentrant {
/// @audit ↓↓ Out of order with `setReserveRatioBips()`
149: function setAnnualInterestBips(uint16 _annualInterestBips) public onlyController nonReentrant {
/// @audit ↑↑ Out of order with `setAnnualInterestBips()`
171: function setReserveRatioBips(uint16 _reserveRatioBips) public onlyController nonReentrant {
/// @audit ↓↓ Out of order with `totalSupply()`
16: function balanceOf(address account) public view virtual nonReentrantView returns (uint256) {
/// @audit ↑↑ Out of order with `balanceOf()`
22: function totalSupply() external view virtual nonReentrantView returns (uint256) {
- WildcatMarketWithdrawals.sol ( #L45-L48, #L77, #L137-L140, #L190 ):
/// @audit ↓↓ Out of order with `queueWithdrawal()`
45: function getAvailableWithdrawalAmount(
46: address accountAddress,
47: uint32 expiry
48: ) external view nonReentrantView returns (uint256) {
/// @audit ↑↑ Out of order with `getAvailableWithdrawalAmount()`
77: function queueWithdrawal(uint256 amount) external nonReentrant {
/// @audit ↑↑ Out of order with `queueWithdrawal()`
137: function executeWithdrawal(
138: address accountAddress,
139: uint32 expiry
140: ) external nonReentrant returns (uint256) {
/// @audit ↑↑ Out of order with `executeWithdrawal()`
190: function processUnpaidWithdrawalBatch() external nonReentrant {
This is a best-practice to protect against reentrancy in other modifiers
There are 6 instances:
119: function borrow(uint256 amount) external onlyBorrower nonReentrant {
142: function closeMarket() external onlyController nonReentrant {
- WildcatMarketConfig.sol ( #L112-L115, #L134, #L149, #L171 ):
112: function updateAccountAuthorization(
113: address _account,
114: bool _isAuthorized
115: ) external onlyController nonReentrant {
134: function setMaxTotalSupply(uint256 _maxTotalSupply) external onlyController nonReentrant {
149: function setAnnualInterestBips(uint16 _annualInterestBips) public onlyController nonReentrant {
171: function setReserveRatioBips(uint16 _reserveRatioBips) public onlyController nonReentrant {
The contracts below already extend the specified contract, so there is no need to list it in the inheritance list again.
There is 1 instance:
- WildcatMarket.sol ( #L10-L14 ):
/// @audit WildcatMarketConfig already extends WildcatMarketBase
10: contract WildcatMarket is
11: WildcatMarketBase,
12: WildcatMarketConfig,
13: WildcatMarketToken,
14: WildcatMarketWithdrawals
It is recommended by the Solidity Style Guide
There are 3 instances:
257: _tmpMarketParameters.namePrefix = '_';
258: _tmpMarketParameters.symbolPrefix = '_';
- WildcatMarketBase.sol ( #L24 ):
24: string public constant version = '1.0';
Starting from Solidity 0.8.8, the override keyword is not required when overriding an interface function, except for the case where the function is defined in multiple bases.
There are 9 instances (click to show):
21: function balance() public view override returns (uint256) {
25: function canReleaseEscrow() public view override returns (bool) {
29: function escrowedAsset() public view override returns (address, uint256) {
33: function releaseEscrow() public override {
39: function isSanctioned(address borrower, address account) public view override returns (bool) {
48: function overrideSanction(address account) public override {
56: function removeSanctionOverride(address account) public override {
65: function getEscrowAddress(
66: address borrower,
67: address account,
68: address asset
69: ) public view override returns (address escrowAddress) {
95: function createEscrow(
96: address borrower,
97: address account,
98: address asset
99: ) public override returns (address escrowContract) {
The following error
s are defined but not used. It is recommended to check the code for logical omissions that cause them not to be used. If it's determined that they are not needed anywhere, it's best to remove them from the codebase to improve code clarity and minimize confusion.
Note that there may be cases where an error appears to be used because it has multiple definitions in different files. In such cases, the definitions should be moved to a separate file.
There are 4 instances:
- LibStoredInitCode.sol ( #L5 ):
5: error InitCodeDeploymentFailed();
- MathUtils.sol ( #L19 ):
19: error MulDivFailed();
16: error InvalidReturnDataString();
17: error InvalidCompactString();
The following event
s are defined but not used. It is recommended to check the code for logical omissions that cause them not to be used. If it's determined that they are not needed anywhere, it's best to remove them from the codebase to improve code clarity and minimize confusion.
Note that there may be cases where an event appears to be used because it has multiple definitions in different files. In such cases, the definitions should be moved to a separate file.
There are 2 instances:
16: event NewController(address borrower, address controller, string namePrefix, string symbolPrefix);
17: event UpdateProtocolFeeConfiguration(
18: address feeRecipient,
19: uint16 protocolFeeBips,
20: address originationFeeAsset,
21: uint256 originationFeeAmount
22: );
Large hardcoded numbers in code can be difficult to read. Consider using underscores for number literals to improve readability (e.g 1234567
-> 1_234_567
). Consider using an underscore for every third digit from the right.
There are 4 instances:
- WildcatMarketController.sol ( #L481 ):
481: WildcatMarket(market).setReserveRatioBips(9000);
81: constraints.maximumAnnualInterestBips > 10000 ||
83: constraints.maximumDelinquencyFeeBips > 10000 ||
85: constraints.maximumReserveRatioBips > 10000 ||
Assembly blocks take a lot more time to audit than normal Solidity code, and often have gotchas and side-effects that the Solidity versions of the same code do not. Consider adding more comments explaining what is being done in every step of the assembly code, and describe why assembly is being used instead of Solidity.
There are 21 instances (click to show):
- WildcatMarketController.sol ( #L403-L409, #L509-L514 ):
403: assembly {
404: if or(iszero(mload(namePrefix)), iszero(mload(symbolPrefix))) {
405: // revert EmptyString();
406: mstore(0x00, 0xecd7b0d1)
407: revert(0x1c, 0x04)
408: }
409: }
509: assembly {
510: if or(lt(value, min), gt(value, max)) {
511: mstore(0, errorSelector)
512: revert(0, 4)
513: }
514: }
6: assembly {
7: c := and(a, b)
8: }
12: assembly {
13: c := or(a, b)
14: }
18: assembly {
19: c := xor(a, b)
20: }
25: assembly {
26: mstore(0, errorSelector)
27: revert(0, 4)
28: }
36: assembly {
37: mstore(0, errorSelector)
38: revert(Error_SelectorPointer, 4)
39: }
48: assembly {
49: mstore(0, errorSelector)
50: mstore(4, argument)
51: revert(0, 0x24)
52: }
61: assembly {
62: mstore(0, errorSelector)
63: mstore(0x20, argument)
64: revert(Error_SelectorPointer, 0x24)
65: }
- LibStoredInitCode.sol ( #L49-L51, #L91-L96, #L111-L116 ):
49: assembly {
50: create2Prefix := or(deployer, 0xff0000000000000000000000000000000000000000)
51: }
91: assembly {
92: let initCodePointer := mload(0x40)
93: let initCodeSize := sub(extcodesize(initCodeStorage), 1)
94: extcodecopy(initCodeStorage, initCodePointer, 1, initCodeSize)
95: deployment := create(value, initCodePointer, initCodeSize)
96: }
111: assembly {
112: let initCodePointer := mload(0x40)
113: let initCodeSize := sub(extcodesize(initCodeStorage), 1)
114: extcodecopy(initCodeStorage, initCodePointer, 1, initCodeSize)
115: deployment := create2(value, initCodePointer, initCodeSize, salt)
116: }
- MathUtils.sol ( #L76-L78, #L106-L115, #L123-L131, #L139-L148, #L156-L165 ):
76: assembly {
77: c := add(valueIfFalse, mul(condition, sub(valueIfTrue, valueIfFalse)))
78: }
106: assembly {
107: // equivalent to `require(b != 0 && a <= (type(uint256).max - b/2) / BIP)`
108: if or(iszero(b), gt(a, div(sub(not(0), div(b, 2)), BIP))) {
109: mstore(0, Panic_ErrorSelector)
110: mstore(Panic_ErrorCodePointer, Panic_Arithmetic)
111: revert(Error_SelectorPointer, Panic_ErrorLength)
112: }
113:
114: c := div(add(mul(a, BIP), div(b, 2)), b)
115: }
123: assembly {
124: b := mul(a, BIP_RAY_RATIO)
125: // equivalent to `require((b = a * BIP_RAY_RATIO) / BIP_RAY_RATIO == a )
126: if iszero(eq(div(b, BIP_RAY_RATIO), a)) {
127: mstore(0, Panic_ErrorSelector)
128: mstore(Panic_ErrorCodePointer, Panic_Arithmetic)
129: revert(Error_SelectorPointer, Panic_ErrorLength)
130: }
131: }
139: assembly {
140: // equivalent to `require(b == 0 || a <= (type(uint256).max - HALF_RAY) / b)`
141: if iszero(or(iszero(b), iszero(gt(a, div(sub(not(0), HALF_RAY), b))))) {
142: mstore(0, Panic_ErrorSelector)
143: mstore(Panic_ErrorCodePointer, Panic_Arithmetic)
144: revert(Error_SelectorPointer, Panic_ErrorLength)
145: }
146:
147: c := div(add(mul(a, b), HALF_RAY), RAY)
148: }
156: assembly {
157: // equivalent to `require(b != 0 && a <= (type(uint256).max - halfB) / RAY)`
158: if or(iszero(b), gt(a, div(sub(not(0), div(b, 2)), RAY))) {
159: mstore(0, Panic_ErrorSelector)
160: mstore(Panic_ErrorCodePointer, Panic_Arithmetic)
161: revert(Error_SelectorPointer, Panic_ErrorLength)
162: }
163:
164: c := div(add(mul(a, RAY), div(b, 2)), b)
165: }
- SafeCastLib.sol ( #L8-L14 ):
8: assembly {
9: if iszero(didNotOverflow) {
10: mstore(0, Panic_ErrorSelector)
11: mstore(Panic_ErrorCodePointer, Panic_Arithmetic)
12: revert(Error_SelectorPointer, Panic_ErrorLength)
13: }
14: }
25: assembly {
26: str := mload(0x40)
27: mstore(0x40, add(str, 0x40))
28: mstore(str, size)
29: mstore(add(str, 0x20), value)
30: }
66: assembly {
67: returndatacopy(0x00, 0x00, 0x20)
68: value := mload(0)
69: }
75: assembly {
76: str := mload(0x40)
77: mstore(0x40, add(str, 0x40))
78: mstore(str, size)
79: mstore(add(str, 0x20), value)
80: }
Consider whether the number of casts is really necessary, or whether using a different type would be more appropriate. Alternatively, add comments to explain in detail why the casts are necessary, and any implicit reasons why the cast does not introduce an overflow.
There are 6 instances (click to show):
288: bytes32 salt = bytes32(uint256(uint160(msg.sender)));
288: bytes32 salt = bytes32(uint256(uint160(msg.sender)));
344: bytes32 salt = bytes32(uint256(uint160(borrower)));
344: bytes32 salt = bytes32(uint256(uint160(borrower)));
71: address(
72: uint160(
73: uint256(
74: keccak256(
75: abi.encodePacked(
76: bytes1(0xff),
77: address(this),
78: keccak256(abi.encode(borrower, account, asset)),
79: WildcatSanctionsEscrowInitcodeHash
80: )
81: )
82: )
83: )
72: uint160(
73: uint256(
74: keccak256(
75: abi.encodePacked(
76: bytes1(0xff),
77: address(this),
78: keccak256(abi.encode(borrower, account, asset)),
79: WildcatSanctionsEscrowInitcodeHash
80: )
81: )
82: )
Consider defining in only one contract so that values cannot become out of sync when only one location is updated. A cheap way to store constants/immutables in a single location is to create an internal constant
in a library
. If the variable is a local cache of another contract's value, consider making the cache variable internal or private, which will require external users to query the contract with the source of truth, so that callers don't get out of sync.
There are 38 instances (click to show):
- WildcatMarketController.sol ( #L41, #L45, #L47, #L49, #L51, #L53, #L55, #L56, #L58, #L59, #L61, #L62, #L64, #L65, #L67, #L68 ):
/// @audit Seen in src/WildcatMarketControllerFactory.sol#31
41: IWildcatArchController public immutable archController;
/// @audit Seen in src/market/WildcatMarketBase.sol#30
45: address public immutable borrower;
/// @audit Seen in src/market/WildcatMarketBase.sol#27
47: address public immutable sentinel;
/// @audit Seen in src/WildcatMarketControllerFactory.sol#36
49: address public immutable marketInitCodeStorage;
/// @audit Seen in src/WildcatMarketControllerFactory.sol#38
51: uint256 public immutable marketInitCodeHash;
/// @audit Seen in src/WildcatMarketControllerFactory.sol#44
53: uint256 internal immutable ownCreate2Prefix = LibStoredInitCode.getCreate2Prefix(address(this));
/// @audit Seen in src/WildcatMarketControllerFactory.sol#46
55: uint32 internal immutable MinimumDelinquencyGracePeriod;
/// @audit Seen in src/WildcatMarketControllerFactory.sol#47
56: uint32 internal immutable MaximumDelinquencyGracePeriod;
/// @audit Seen in src/WildcatMarketControllerFactory.sol#49
58: uint16 internal immutable MinimumReserveRatioBips;
/// @audit Seen in src/WildcatMarketControllerFactory.sol#50
59: uint16 internal immutable MaximumReserveRatioBips;
/// @audit Seen in src/WildcatMarketControllerFactory.sol#52
61: uint16 internal immutable MinimumDelinquencyFeeBips;
/// @audit Seen in src/WildcatMarketControllerFactory.sol#53
62: uint16 internal immutable MaximumDelinquencyFeeBips;
/// @audit Seen in src/WildcatMarketControllerFactory.sol#55
64: uint32 internal immutable MinimumWithdrawalBatchDuration;
/// @audit Seen in src/WildcatMarketControllerFactory.sol#56
65: uint32 internal immutable MaximumWithdrawalBatchDuration;
/// @audit Seen in src/WildcatMarketControllerFactory.sol#58
67: uint16 internal immutable MinimumAnnualInterestBips;
/// @audit Seen in src/WildcatMarketControllerFactory.sol#59
68: uint16 internal immutable MaximumAnnualInterestBips;
- WildcatMarketControllerFactory.sol ( #L31, #L34, #L36, #L38, #L44, #L46, #L47, #L49, #L50, #L52, #L53, #L55, #L56, #L58, #L59 ):
/// @audit Seen in src/WildcatMarketController.sol#41
31: IWildcatArchController public immutable archController;
/// @audit Seen in src/market/WildcatMarketBase.sol#27
34: address public immutable sentinel;
/// @audit Seen in src/WildcatMarketController.sol#49
36: address public immutable marketInitCodeStorage;
/// @audit Seen in src/WildcatMarketController.sol#51
38: uint256 public immutable marketInitCodeHash;
/// @audit Seen in src/WildcatMarketController.sol#53
44: uint256 internal immutable ownCreate2Prefix = LibStoredInitCode.getCreate2Prefix(address(this));
/// @audit Seen in src/WildcatMarketController.sol#55
46: uint32 internal immutable MinimumDelinquencyGracePeriod;
/// @audit Seen in src/WildcatMarketController.sol#56
47: uint32 internal immutable MaximumDelinquencyGracePeriod;
/// @audit Seen in src/WildcatMarketController.sol#58
49: uint16 internal immutable MinimumReserveRatioBips;
/// @audit Seen in src/WildcatMarketController.sol#59
50: uint16 internal immutable MaximumReserveRatioBips;
/// @audit Seen in src/WildcatMarketController.sol#61
52: uint16 internal immutable MinimumDelinquencyFeeBips;
/// @audit Seen in src/WildcatMarketController.sol#62
53: uint16 internal immutable MaximumDelinquencyFeeBips;
/// @audit Seen in src/WildcatMarketController.sol#64
55: uint32 internal immutable MinimumWithdrawalBatchDuration;
/// @audit Seen in src/WildcatMarketController.sol#65
56: uint32 internal immutable MaximumWithdrawalBatchDuration;
/// @audit Seen in src/WildcatMarketController.sol#67
58: uint16 internal immutable MinimumAnnualInterestBips;
/// @audit Seen in src/WildcatMarketController.sol#68
59: uint16 internal immutable MaximumAnnualInterestBips;
/// @audit Seen in src/WildcatMarketControllerFactory.sol#34
11: address public immutable override sentinel;
/// @audit Seen in src/market/WildcatMarketBase.sol#30
12: address public immutable override borrower;
/// @audit Seen in src/market/WildcatMarketBase.sol#48
14: address internal immutable asset;
- WildcatSanctionsSentinel.sol ( #L16 ):
/// @audit Seen in src/WildcatMarketControllerFactory.sol#31
16: address public immutable override archController;
/// @audit Seen in src/WildcatMarketController.sol#47
27: address public immutable sentinel;
/// @audit Seen in src/WildcatMarketController.sol#45
30: address public immutable borrower;
/// @audit Seen in src/WildcatSanctionsEscrow.sol#14
48: address public immutable asset;
Converting some if statements to ternaries (such as z = (a < b) ? x : y
) can make the code more concise and easier to read.
There are 2 instances:
- WildcatMarketConfig.sol ( #L118-L122 ):
118: if (_isAuthorized) {
119: account.approval = AuthRole.DepositAndWithdraw;
120: } else {
121: account.approval = AuthRole.WithdrawOnly;
122: }
- WildcatMarketWithdrawals.sol ( #L54-L58 ):
54: if (expiry == expiredBatchExpiry) {
55: batch = expiredBatch;
56: } else {
57: batch = _withdrawalData.batches[expiry];
58: }
Ensure that events follow the best practice of check-effects-interaction, and are emitted before external calls.
There are 11 instances (click to show):
- WildcatSanctionsEscrow.sol ( #L40 ):
/// @audit `transfer()` is called on line 38
40: emit EscrowReleased(account, asset, amount);
/// @audit `()` is called on line 110
112: emit NewSanctionsEscrow(borrower, account, asset);
/// @audit `()` is called on line 110
116: emit SanctionOverride(borrower, escrowContract);
/// @audit `safeTransferFrom()` is called on line 60
67: emit Transfer(address(0), msg.sender, amount);
/// @audit `safeTransferFrom()` is called on line 60
68: emit Deposit(msg.sender, amount, scaledAmount);
/// @audit `safeTransfer()` is called on line 107
108: emit FeesCollected(withdrawableFees);
/// @audit `safeTransfer()` is called on line 129
130: emit Borrow(amount);
/// @audit `safeTransferFrom()` is called on line 154
160: emit MarketClosed(block.timestamp);
- WildcatMarketBase.sol ( #L177, #L179-L183 ):
/// @audit `createEscrow()` is called on line 172
177: emit Transfer(accountAddress, escrow, state.normalizeAmount(scaledBalance));
/// @audit `createEscrow()` is called on line 172
179: emit SanctionedAccountAssetsSentToEscrow(
180: accountAddress,
181: escrow,
182: state.normalizeAmount(scaledBalance)
183: );
- WildcatMarketWithdrawals.sol ( #L182 ):
/// @audit `safeTransfer()` is called on line 179
182: emit WithdrawalExecuted(expiry, accountAddress, normalizedAmountWithdrawn);
When an action is triggered based on a user's action, not being able to filter based on who triggered the action makes event processing a lot more cumbersome. Including the msg.sender
the events of these types of action will make events much more useful to end users, especially when msg.sender
is not tx.origin
.
There are 12 instances (click to show):
157: emit LenderAuthorized(lender);
173: emit LenderDeauthorized(lender);
- WildcatSanctionsEscrow.sol ( #L40 ):
40: emit EscrowReleased(account, asset, amount);
112: emit NewSanctionsEscrow(borrower, account, asset);
116: emit SanctionOverride(borrower, escrowContract);
108: emit FeesCollected(withdrawableFees);
130: emit Borrow(amount);
- WildcatMarketConfig.sol ( #L99 ):
99: emit AuthorizationStatusUpdated(accountAddress, account.approval);
- WildcatMarketWithdrawals.sol ( #L96, #L172-L177, #L182, #L208 ):
96: emit WithdrawalBatchCreated(state.pendingWithdrawalExpiry);
172: emit SanctionedAccountWithdrawalSentToEscrow(
173: accountAddress,
174: escrow,
175: expiry,
176: normalizedAmountWithdrawn
177: );
182: emit WithdrawalExecuted(expiry, accountAddress, normalizedAmountWithdrawn);
208: emit WithdrawalBatchClosed(expiry);
Index event fields makes the field more quickly accessible to off-chain tools that parse events. However, note that each indexed field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields). Each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three fields, all of the fields should be indexed.
There are 10 instances (click to show):
29: event MarketAdded(address indexed controller, address market);
30: event MarketRemoved(address market);
32: event ControllerFactoryAdded(address controllerFactory);
33: event ControllerFactoryRemoved(address controllerFactory);
35: event BorrowerAdded(address borrower);
36: event BorrowerRemoved(address borrower);
38: event ControllerAdded(address indexed controllerFactory, address controller);
39: event ControllerRemoved(address controller);
16: event NewController(address borrower, address controller, string namePrefix, string symbolPrefix);
17: event UpdateProtocolFeeConfiguration(
18: address feeRecipient,
19: uint16 protocolFeeBips,
20: address originationFeeAsset,
21: uint256 originationFeeAmount
22: );
Source files are using different floating version syntax, this is prone to compilation errors, and is not conducive to the code reliability and maintainability.
There are 2 instances:
- WildcatMarketController.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- Chainalysis.sol ( #L2 ):
2: pragma solidity ^0.8.20;
The contract's interface should be imported first, followed by each of the interfaces it uses, followed by all other files. The examples below do not follow this layout.
There are 5 instances (click to show):
- WildcatMarketController.sol ( #L7 ):
/// @audit Out of order with the prev import️ ⬆
7: import './interfaces/IWildcatArchController.sol';
- WildcatMarketControllerFactory.sol ( #L6 ):
/// @audit Out of order with the prev import️ ⬆
6: import './interfaces/IWildcatMarketController.sol';
- WildcatSanctionsEscrow.sol ( #L8 ):
/// @audit Out of order with the prev import️ ⬆
8: import { IWildcatSanctionsEscrow } from './interfaces/IWildcatSanctionsEscrow.sol';
- WildcatMarketBase.sol ( #L7 ):
/// @audit Out of order with the prev import️ ⬆
7: import '../interfaces/IMarketEventsAndErrors.sol';
- WildcatMarketWithdrawals.sol ( #L8 ):
/// @audit Out of order with the prev import️ ⬆
8: import '../interfaces/IWildcatSanctionsSentinel.sol';
There are 67 instances (click to show):
4: import { EnumerableSet } from 'openzeppelin/contracts/utils/structs/EnumerableSet.sol';
5: import 'solady/auth/Ownable.sol';
6: import './libraries/MathUtils.sol';
4: import { EnumerableSet } from 'openzeppelin/contracts/utils/structs/EnumerableSet.sol';
5: import 'solady/utils/SafeTransferLib.sol';
6: import './market/WildcatMarket.sol';
7: import './interfaces/IWildcatArchController.sol';
8: import './interfaces/IWildcatMarketControllerEventsAndErrors.sol';
9: import './interfaces/IWildcatMarketControllerFactory.sol';
10: import './libraries/LibStoredInitCode.sol';
11: import './libraries/MathUtils.sol';
4: import { EnumerableSet } from 'openzeppelin/contracts/utils/structs/EnumerableSet.sol';
5: import './interfaces/WildcatStructsAndEnums.sol';
6: import './interfaces/IWildcatMarketController.sol';
7: import './interfaces/IWildcatArchController.sol';
8: import './libraries/LibStoredInitCode.sol';
9: import './libraries/MathUtils.sol';
10: import './market/WildcatMarket.sol';
11: import './WildcatMarketController.sol';
4: import { IERC20 } from './interfaces/IERC20.sol';
5: import { IChainalysisSanctionsList } from './interfaces/IChainalysisSanctionsList.sol';
6: import { SanctionsList } from './libraries/Chainalysis.sol';
7: import { WildcatSanctionsSentinel } from './WildcatSanctionsSentinel.sol';
8: import { IWildcatSanctionsEscrow } from './interfaces/IWildcatSanctionsEscrow.sol';
4: import { IChainalysisSanctionsList } from './interfaces/IChainalysisSanctionsList.sol';
5: import { IWildcatArchController } from './interfaces/IWildcatArchController.sol';
6: import { IWildcatSanctionsSentinel } from './interfaces/IWildcatSanctionsSentinel.sol';
7: import { SanctionsList } from './libraries/Chainalysis.sol';
8: import { WildcatSanctionsEscrow } from './WildcatSanctionsEscrow.sol';
- Chainalysis.sol ( #L4 ):
4: import '../interfaces/IChainalysisSanctionsList.sol';
4: import './MathUtils.sol';
5: import './SafeCastLib.sol';
6: import './MarketState.sol';
4: import { AuthRole } from '../interfaces/WildcatStructsAndEnums.sol';
5: import './MathUtils.sol';
6: import './SafeCastLib.sol';
7: import './FeeMath.sol';
- MathUtils.sol ( #L4 ):
4: import './Errors.sol';
- SafeCastLib.sol ( #L4 ):
4: import './Errors.sol';
- StringQuery.sol ( #L4 ):
4: import { LibBit } from 'solady/utils/LibBit.sol';
4: import './MarketState.sol';
5: import './FIFOQueue.sol';
4: import '../libraries/FeeMath.sol';
5: import './WildcatMarketBase.sol';
6: import './WildcatMarketConfig.sol';
7: import './WildcatMarketToken.sol';
8: import './WildcatMarketWithdrawals.sol';
4: import '../libraries/FeeMath.sol';
5: import '../libraries/Withdrawal.sol';
6: import { queryName, querySymbol } from '../libraries/StringQuery.sol';
7: import '../interfaces/IMarketEventsAndErrors.sol';
8: import '../interfaces/IWildcatMarketController.sol';
9: import '../interfaces/IWildcatSanctionsSentinel.sol';
10: import { IERC20, IERC20Metadata } from '../interfaces/IERC20Metadata.sol';
11: import '../ReentrancyGuard.sol';
12: import '../libraries/BoolUtils.sol';
4: import '../interfaces/IWildcatSanctionsSentinel.sol';
5: import '../libraries/FeeMath.sol';
6: import '../libraries/SafeCastLib.sol';
7: import './WildcatMarketBase.sol';
- WildcatMarketToken.sol ( #L4 ):
4: import './WildcatMarketBase.sol';
4: import './WildcatMarketBase.sol';
5: import '../libraries/MarketState.sol';
6: import '../libraries/FeeMath.sol';
7: import '../libraries/FIFOQueue.sol';
8: import '../interfaces/IWildcatSanctionsSentinel.sol';
9: import 'solady/utils/SafeTransferLib.sol';
These contracts import contracts from @openzeppelin/contracts
, but they are not using the latest version.
There are 3 instances:
- WildcatArchController.sol ( #L4 ):
4: import { EnumerableSet } from 'openzeppelin/contracts/utils/structs/EnumerableSet.sol';
- WildcatMarketController.sol ( #L4 ):
4: import { EnumerableSet } from 'openzeppelin/contracts/utils/structs/EnumerableSet.sol';
- WildcatMarketControllerFactory.sol ( #L4 ):
4: import { EnumerableSet } from 'openzeppelin/contracts/utils/structs/EnumerableSet.sol';
Magic numbers are hard-coded values in code that can make it difficult for developers and maintainers to understand the code, and can also cause confusion or errors. To improve the readability and maintainability of code, it is recommended to replace magic numbers with constants that have good readability.
There are 5 instances:
- WildcatMarketController.sol ( #L481 ):
/// @audit 9000
481: WildcatMarket(market).setReserveRatioBips(9000);
/// @audit 10000
81: constraints.maximumAnnualInterestBips > 10000 ||
/// @audit 10000
83: constraints.maximumDelinquencyFeeBips > 10000 ||
/// @audit 10000
85: constraints.maximumReserveRatioBips > 10000 ||
- WildcatSanctionsSentinel.sol ( #L76 ):
/// @audit 0xff
76: bytes1(0xff),
While it doesn't save any gas because the compiler knows that developers often make this mistake, it's still best to use the right tool for the task at hand. There is a difference between constant
variables and immutable
variables, and they should each be used in their appropriate contexts. constants
should be used for literal values written into the code, and immutable
variables should be used for expressions, or values calculated in, or passed into the constructor.
There are 7 instances (click to show):
- WildcatSanctionsSentinel.sol ( #L11-L12 ):
11: bytes32 public constant override WildcatSanctionsEscrowInitcodeHash =
12: keccak256(type(WildcatSanctionsEscrow).creationCode);
- Chainalysis.sol ( #L6-L8 ):
6: IChainalysisSanctionsList constant SanctionsList = IChainalysisSanctionsList(
7: 0x40C57923924B5c5c5455c48D93317139ADDaC8fb
8: );
- StringQuery.sol ( #L8-L10, #L106-L108, #L109-L111, #L112-L114, #L115-L117 ):
8: uint256 constant InvalidReturnDataString_selector = (
9: 0x4cb9c00000000000000000000000000000000000000000000000000000000000
10: );
106: uint256 constant UnknownNameQueryError_selector = (
107: 0xed3df7ad00000000000000000000000000000000000000000000000000000000
108: );
109: uint256 constant UnknownSymbolQueryError_selector = (
110: 0x89ff815700000000000000000000000000000000000000000000000000000000
111: );
112: uint256 constant NameFunction_selector = (
113: 0x06fdde0300000000000000000000000000000000000000000000000000000000
114: );
115: uint256 constant SymbolFunction_selector = (
116: 0x95d89b4100000000000000000000000000000000000000000000000000000000
117: );
If desired, sub contracts can change the visibility from external
to public
by overriding the functions of their parents.
There are 9 instances (click to show):
29: function escrowedAsset() public view override returns (address, uint256) {
33: function releaseEscrow() public override {
39: function isSanctioned(address borrower, address account) public view override returns (bool) {
48: function overrideSanction(address account) public override {
56: function removeSanctionOverride(address account) public override {
95: function createEscrow(
96: address borrower,
97: address account,
98: address asset
99: ) public override returns (address escrowContract) {
149: function setAnnualInterestBips(uint16 _annualInterestBips) public onlyController nonReentrant {
171: function setReserveRatioBips(uint16 _reserveRatioBips) public onlyController nonReentrant {
- WildcatMarketToken.sol ( #L16 ):
16: function balanceOf(address account) public view virtual nonReentrantView returns (uint256) {
There are 9 instances (click to show):
21: function balance() public view override returns (uint256) {
25: function canReleaseEscrow() public view override returns (bool) {
29: function escrowedAsset() public view override returns (address, uint256) {
33: function releaseEscrow() public override {
34: /**
35: * @dev Returns boolean indicating whether `account` is sanctioned
36: * on Chainalysis and that status has not been overridden by
37: * `borrower`.
38: */
39: function isSanctioned(address borrower, address account) public view override returns (bool) {
45: /**
46: * @dev Overrides the sanction status of `account` for `borrower`.
47: */
48: function overrideSanction(address account) public override {
53: /**
54: * @dev Removes the sanction override of `account` for `borrower`.
55: */
56: function removeSanctionOverride(address account) public override {
61: /**
62: * @dev Calculate the create2 escrow address for the combination
63: * of `borrower`, `account`, and `asset`.
64: */
65: function getEscrowAddress(
66: address borrower,
67: address account,
68: address asset
69: ) public view override returns (address escrowAddress) {
87: /**
88: * @dev Creates a new WildcatSanctionsEscrow contract for `borrower`,
89: * `account`, and `asset` or returns the existing escrow contract
90: * if one already exists.
91: *
92: * The escrow contract is added to the set of sanction override
93: * addresses for `borrower` so that it can not be blocked.
94: */
95: function createEscrow(
96: address borrower,
97: address account,
98: address asset
99: ) public override returns (address escrowContract) {
There are 18 instances (click to show):
- WildcatArchController.sol ( #L8 ):
8: contract WildcatArchController is Ownable {
- WildcatMarketController.sol ( #L32 ):
32: contract WildcatMarketController is IWildcatMarketControllerEventsAndErrors {
- WildcatMarketControllerFactory.sol ( #L13 ):
13: contract WildcatMarketControllerFactory {
- WildcatSanctionsEscrow.sol ( #L10 ):
10: contract WildcatSanctionsEscrow is IWildcatSanctionsEscrow {
- WildcatSanctionsSentinel.sol ( #L10 ):
10: contract WildcatSanctionsSentinel is IWildcatSanctionsSentinel {
- BoolUtils.sol ( #L4 ):
4: library BoolUtils {
- FIFOQueue.sol ( #L16 ):
16: library FIFOQueueLib {
- FeeMath.sol ( #L11 ):
11: library FeeMath {
- LibStoredInitCode.sol ( #L4 ):
4: library LibStoredInitCode {
- MarketState.sol ( #L44 ):
44: library MarketStateLib {
- MathUtils.sol ( #L16 ):
16: library MathUtils {
- SafeCastLib.sol ( #L6 ):
6: library SafeCastLib {
- Withdrawal.sol ( #L37 ):
37: library WithdrawalLib {
- WildcatMarket.sol ( #L10 ):
10: contract WildcatMarket is
- WildcatMarketBase.sol ( #L14 ):
14: contract WildcatMarketBase is ReentrancyGuard, IMarketEventsAndErrors {
- WildcatMarketConfig.sol ( #L9 ):
9: contract WildcatMarketConfig is WildcatMarketBase {
- WildcatMarketToken.sol ( #L6 ):
6: contract WildcatMarketToken is WildcatMarketBase {
- WildcatMarketWithdrawals.sol ( #L11 ):
11: contract WildcatMarketWithdrawals is WildcatMarketBase {
The @notice
is used to explain to users what the contract does. The compiler interprets ///
or /**
comments as this tag if one wasn't explicitly provided.
There are 18 instances (click to show):
- WildcatArchController.sol ( #L8 ):
8: contract WildcatArchController is Ownable {
- WildcatMarketController.sol ( #L32 ):
32: contract WildcatMarketController is IWildcatMarketControllerEventsAndErrors {
- WildcatMarketControllerFactory.sol ( #L13 ):
13: contract WildcatMarketControllerFactory {
- WildcatSanctionsEscrow.sol ( #L10 ):
10: contract WildcatSanctionsEscrow is IWildcatSanctionsEscrow {
- WildcatSanctionsSentinel.sol ( #L10 ):
10: contract WildcatSanctionsSentinel is IWildcatSanctionsSentinel {
- BoolUtils.sol ( #L4 ):
4: library BoolUtils {
- FIFOQueue.sol ( #L16 ):
16: library FIFOQueueLib {
- FeeMath.sol ( #L11 ):
11: library FeeMath {
- LibStoredInitCode.sol ( #L4 ):
4: library LibStoredInitCode {
- MarketState.sol ( #L44 ):
44: library MarketStateLib {
- MathUtils.sol ( #L16 ):
16: library MathUtils {
- SafeCastLib.sol ( #L6 ):
6: library SafeCastLib {
- Withdrawal.sol ( #L37 ):
37: library WithdrawalLib {
- WildcatMarket.sol ( #L10 ):
10: contract WildcatMarket is
- WildcatMarketBase.sol ( #L14 ):
14: contract WildcatMarketBase is ReentrancyGuard, IMarketEventsAndErrors {
- WildcatMarketConfig.sol ( #L9 ):
9: contract WildcatMarketConfig is WildcatMarketBase {
- WildcatMarketToken.sol ( #L6 ):
6: contract WildcatMarketToken is WildcatMarketBase {
- WildcatMarketWithdrawals.sol ( #L11 ):
11: contract WildcatMarketWithdrawals is WildcatMarketBase {
Some contract definitions have an incomplete NatSpec: add a @title
notation to describe the contract to improve the code documentation.
There are 18 instances (click to show):
- WildcatArchController.sol ( #L8 ):
8: contract WildcatArchController is Ownable {
- WildcatMarketController.sol ( #L32 ):
32: contract WildcatMarketController is IWildcatMarketControllerEventsAndErrors {
- WildcatMarketControllerFactory.sol ( #L13 ):
13: contract WildcatMarketControllerFactory {
- WildcatSanctionsEscrow.sol ( #L10 ):
10: contract WildcatSanctionsEscrow is IWildcatSanctionsEscrow {
- WildcatSanctionsSentinel.sol ( #L10 ):
10: contract WildcatSanctionsSentinel is IWildcatSanctionsSentinel {
- BoolUtils.sol ( #L4 ):
4: library BoolUtils {
- FIFOQueue.sol ( #L16 ):
16: library FIFOQueueLib {
- FeeMath.sol ( #L11 ):
11: library FeeMath {
- LibStoredInitCode.sol ( #L4 ):
4: library LibStoredInitCode {
- MarketState.sol ( #L44 ):
44: library MarketStateLib {
- MathUtils.sol ( #L16 ):
16: library MathUtils {
- SafeCastLib.sol ( #L6 ):
6: library SafeCastLib {
- Withdrawal.sol ( #L37 ):
37: library WithdrawalLib {
- WildcatMarket.sol ( #L10 ):
10: contract WildcatMarket is
- WildcatMarketBase.sol ( #L14 ):
14: contract WildcatMarketBase is ReentrancyGuard, IMarketEventsAndErrors {
- WildcatMarketConfig.sol ( #L9 ):
9: contract WildcatMarketConfig is WildcatMarketBase {
- WildcatMarketToken.sol ( #L6 ):
6: contract WildcatMarketToken is WildcatMarketBase {
- WildcatMarketWithdrawals.sol ( #L11 ):
11: contract WildcatMarketWithdrawals is WildcatMarketBase {
There are 10 instances (click to show):
29: event MarketAdded(address indexed controller, address market);
30: event MarketRemoved(address market);
32: event ControllerFactoryAdded(address controllerFactory);
33: event ControllerFactoryRemoved(address controllerFactory);
35: event BorrowerAdded(address borrower);
36: event BorrowerRemoved(address borrower);
38: event ControllerAdded(address indexed controllerFactory, address controller);
39: event ControllerRemoved(address controller);
16: event NewController(address borrower, address controller, string namePrefix, string symbolPrefix);
17: event UpdateProtocolFeeConfiguration(
18: address feeRecipient,
19: uint16 protocolFeeBips,
20: address originationFeeAsset,
21: uint256 originationFeeAmount
22: );
It is recommended that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation. In complex projects such as DeFi, the interpretation of all functions and their arguments and returns is important for code readability and auditability.
There are 124 instances (click to show):
- WildcatArchController.sol ( #L55, #L63, #L70, #L77, #L81, #L85, #L98, #L106, #L113, #L120, #L124, #L128, #L141, #L149, #L156, #L163, #L167, #L171, #L184, #L192, #L199, #L206, #L210, #L214, #L227 ):
55: constructor() {
63: function registerBorrower(address borrower) external onlyOwner {
70: function removeBorrower(address borrower) external onlyOwner {
77: function isRegisteredBorrower(address borrower) external view returns (bool) {
81: function getRegisteredBorrowers() external view returns (address[] memory) {
85: function getRegisteredBorrowers(
98: function getRegisteredBorrowersCount() external view returns (uint256) {
106: function registerControllerFactory(address factory) external onlyOwner {
113: function removeControllerFactory(address factory) external onlyOwner {
120: function isRegisteredControllerFactory(address factory) external view returns (bool) {
124: function getRegisteredControllerFactories() external view returns (address[] memory) {
128: function getRegisteredControllerFactories(
141: function getRegisteredControllerFactoriesCount() external view returns (uint256) {
149: function registerController(address controller) external onlyControllerFactory {
156: function removeController(address controller) external onlyOwner {
163: function isRegisteredController(address controller) external view returns (bool) {
167: function getRegisteredControllers() external view returns (address[] memory) {
171: function getRegisteredControllers(
184: function getRegisteredControllersCount() external view returns (uint256) {
192: function registerMarket(address market) external onlyController {
199: function removeMarket(address market) external onlyOwner {
206: function isRegisteredMarket(address market) external view returns (bool) {
210: function getRegisteredMarkets() external view returns (address[] memory) {
214: function getRegisteredMarkets(
227: function getRegisteredMarketsCount() external view returns (uint256) {
- WildcatMarketController.sol ( #L94, #L125, #L138, #L142, #L196, #L200, #L204, #L217, #L221, #L255, #L490, #L503 ):
94: constructor() {
125: function getAuthorizedLenders(
138: function getAuthorizedLendersCount() external view returns (uint256) {
142: function isAuthorizedLender(address lender) external view virtual returns (bool) {
196: function isControlledMarket(address market) external view returns (bool) {
200: function getControlledMarkets() external view returns (address[] memory) {
204: function getControlledMarkets(
217: function getControlledMarketsCount() external view returns (uint256) {
221: function computeMarketAddress(
255: function _resetTmpMarketParameters() internal {
490: function resetReserveRatio(address market) external virtual {
503: function assertValueInRange(
- WildcatMarketControllerFactory.sol ( #L72, #L106, #L116, #L126, #L130, #L134, #L138, #L246, #L342 ):
72: constructor(
106: function _storeControllerInitCode()
116: function _storeMarketInitCode()
126: function isDeployedController(address controller) external view returns (bool) {
130: function getDeployedControllersCount() external view returns (uint256) {
134: function getDeployedControllers() external view returns (address[] memory) {
138: function getDeployedControllers(
246: function getMarketControllerParameters()
342: function computeControllerAddress(address borrower) external view returns (address) {
16: constructor() {
21: function balance() public view override returns (uint256) {
25: function canReleaseEscrow() public view override returns (bool) {
29: function escrowedAsset() public view override returns (address, uint256) {
33: function releaseEscrow() public override {
24: constructor(address _archController, address _chainalysisSanctionsList) {
30: function _resetTmpEscrowParams() internal {
5: function and(bool a, bool b) internal pure returns (bool c) {
11: function or(bool a, bool b) internal pure returns (bool c) {
17: function xor(bool a, bool b) internal pure returns (bool c) {
19: function empty(FIFOQueue storage arr) internal view returns (bool) {
23: function first(FIFOQueue storage arr) internal view returns (uint32) {
30: function at(FIFOQueue storage arr, uint256 index) internal view returns (uint32) {
38: function length(FIFOQueue storage arr) internal view returns (uint128) {
42: function values(FIFOQueue storage arr) internal view returns (uint32[] memory _values) {
55: function push(FIFOQueue storage arr, uint32 value) internal {
61: function shift(FIFOQueue storage arr) internal {
70: function shiftN(FIFOQueue storage arr, uint128 n) internal {
30: function calculateBaseInterest(
40: function applyProtocolFee(
53: function updateDelinquency(
7: function deployInitCode(bytes memory data) internal returns (address initCodeStorage) {
54: function calculateCreate2Address(
83: function createWithStoredInitCode(address initCodeStorage) internal returns (address deployment) {
87: function createWithStoredInitCode(
99: function create2WithStoredInitCode(
106: function create2WithStoredInitCode(
130: function hasPendingExpiredBatch(MarketState memory state) internal view returns (bool result) {
138: function totalDebts(MarketState memory state) internal pure returns (uint256) {
- SafeCastLib.sol ( #L7, #L17, #L21, #L25, #L29, #L33, #L37, #L41, #L45, #L49, #L53, #L57, #L61, #L65, #L69, #L73, #L77, #L81, #L85, #L89, #L93, #L97, #L101, #L105, #L109, #L113, #L117, #L121, #L125, #L129, #L133, #L137 ):
7: function _assertNonOverflow(bool didNotOverflow) private pure {
17: function toUint8(uint256 x) internal pure returns (uint8 y) {
21: function toUint16(uint256 x) internal pure returns (uint16 y) {
25: function toUint24(uint256 x) internal pure returns (uint24 y) {
29: function toUint32(uint256 x) internal pure returns (uint32 y) {
33: function toUint40(uint256 x) internal pure returns (uint40 y) {
37: function toUint48(uint256 x) internal pure returns (uint48 y) {
41: function toUint56(uint256 x) internal pure returns (uint56 y) {
45: function toUint64(uint256 x) internal pure returns (uint64 y) {
49: function toUint72(uint256 x) internal pure returns (uint72 y) {
53: function toUint80(uint256 x) internal pure returns (uint80 y) {
57: function toUint88(uint256 x) internal pure returns (uint88 y) {
61: function toUint96(uint256 x) internal pure returns (uint96 y) {
65: function toUint104(uint256 x) internal pure returns (uint104 y) {
69: function toUint112(uint256 x) internal pure returns (uint112 y) {
73: function toUint120(uint256 x) internal pure returns (uint120 y) {
77: function toUint128(uint256 x) internal pure returns (uint128 y) {
81: function toUint136(uint256 x) internal pure returns (uint136 y) {
85: function toUint144(uint256 x) internal pure returns (uint144 y) {
89: function toUint152(uint256 x) internal pure returns (uint152 y) {
93: function toUint160(uint256 x) internal pure returns (uint160 y) {
97: function toUint168(uint256 x) internal pure returns (uint168 y) {
101: function toUint176(uint256 x) internal pure returns (uint176 y) {
105: function toUint184(uint256 x) internal pure returns (uint184 y) {
109: function toUint192(uint256 x) internal pure returns (uint192 y) {
113: function toUint200(uint256 x) internal pure returns (uint200 y) {
117: function toUint208(uint256 x) internal pure returns (uint208 y) {
121: function toUint216(uint256 x) internal pure returns (uint216 y) {
125: function toUint224(uint256 x) internal pure returns (uint224 y) {
129: function toUint232(uint256 x) internal pure returns (uint232 y) {
133: function toUint240(uint256 x) internal pure returns (uint240 y) {
137: function toUint248(uint256 x) internal pure returns (uint248 y) {
19: function bytes32ToString(bytes32 value) pure returns (string memory str) {
33: function queryStringOrBytes32AsString(
96: function queryName(address target) view returns (string memory) {
101: function querySymbol(address target) view returns (string memory) {
- Withdrawal.sol ( #L38 ):
38: function scaledOwedAmount(WithdrawalBatch memory batch) internal pure returns (uint104) {
76: constructor() {
528: function _applyWithdrawalBatchPaymentView(
- WildcatMarketConfig.sol ( #L42 ):
42: function reserveRatioBips() external view returns (uint256) {
31: function approve(address spender, uint256 amount) external virtual nonReentrant returns (bool) {
36: function transfer(address to, uint256 amount) external virtual nonReentrant returns (bool) {
41: function transferFrom(
59: function _approve(address approver, address spender, uint256 amount) internal virtual {
64: function _transfer(address from, address to, uint256 amount) internal virtual {
28: function getWithdrawalBatch(
38: function getAccountWithdrawalStatus(
45: function getAvailableWithdrawalAmount(
190: function processUnpaidWithdrawalBatch() external nonReentrant {
There are 257 instances (click to show):
- WildcatArchController.sol ( #L63, #L70, #L77, #L85-L88, #L106, #L113, #L120, #L128-L131, #L149, #L156, #L163, #L171-L174, #L192, #L199, #L206, #L214-L217 ):
/// @audit Missing @param for `borrower`
63: function registerBorrower(address borrower) external onlyOwner {
/// @audit Missing @param for `borrower`
70: function removeBorrower(address borrower) external onlyOwner {
/// @audit Missing @param for `borrower`
77: function isRegisteredBorrower(address borrower) external view returns (bool) {
/// @audit Missing @param for `start`, `end`
85: function getRegisteredBorrowers(
86: uint256 start,
87: uint256 end
88: ) external view returns (address[] memory arr) {
/// @audit Missing @param for `factory`
106: function registerControllerFactory(address factory) external onlyOwner {
/// @audit Missing @param for `factory`
113: function removeControllerFactory(address factory) external onlyOwner {
/// @audit Missing @param for `factory`
120: function isRegisteredControllerFactory(address factory) external view returns (bool) {
/// @audit Missing @param for `start`, `end`
128: function getRegisteredControllerFactories(
129: uint256 start,
130: uint256 end
131: ) external view returns (address[] memory arr) {
/// @audit Missing @param for `controller`
149: function registerController(address controller) external onlyControllerFactory {
/// @audit Missing @param for `controller`
156: function removeController(address controller) external onlyOwner {
/// @audit Missing @param for `controller`
163: function isRegisteredController(address controller) external view returns (bool) {
/// @audit Missing @param for `start`, `end`
171: function getRegisteredControllers(
172: uint256 start,
173: uint256 end
174: ) external view returns (address[] memory arr) {
/// @audit Missing @param for `market`
192: function registerMarket(address market) external onlyController {
/// @audit Missing @param for `market`
199: function removeMarket(address market) external onlyOwner {
/// @audit Missing @param for `market`
206: function isRegisteredMarket(address market) external view returns (bool) {
/// @audit Missing @param for `start`, `end`
214: function getRegisteredMarkets(
215: uint256 start,
216: uint256 end
217: ) external view returns (address[] memory arr) {
- WildcatMarketController.sol ( #L125-L128, #L142, #L153, #L169, #L182, #L196, #L204-L207, #L221-L225, #L291-L301, #L370-L374, #L394-L402, #L468-L471, #L490, #L503-L508 ):
/// @audit Missing @param for `start`, `end`
125: function getAuthorizedLenders(
126: uint256 start,
127: uint256 end
128: ) external view returns (address[] memory arr) {
/// @audit Missing @param for `lender`
142: function isAuthorizedLender(address lender) external view virtual returns (bool) {
/// @audit Missing @param for `lenders`
153: function authorizeLenders(address[] memory lenders) external onlyBorrower {
/// @audit Missing @param for `lenders`
169: function deauthorizeLenders(address[] memory lenders) external onlyBorrower {
/// @audit Missing @param for `lender`, `markets`
182: function updateLenderAuthorization(address lender, address[] memory markets) external {
/// @audit Missing @param for `market`
196: function isControlledMarket(address market) external view returns (bool) {
/// @audit Missing @param for `start`, `end`
204: function getControlledMarkets(
205: uint256 start,
206: uint256 end
207: ) external view returns (address[] memory arr) {
/// @audit Missing @param for `asset`, `namePrefix`, `symbolPrefix`
221: function computeMarketAddress(
222: address asset,
223: string memory namePrefix,
224: string memory symbolPrefix
225: ) external view returns (address) {
/// @audit Missing @param for `asset`, `namePrefix`, `symbolPrefix`, `maxTotalSupply`, `annualInterestBips`, `delinquencyFeeBips`, `withdrawalBatchDuration`, `reserveRatioBips`, `delinquencyGracePeriod`
291: function deployMarket(
292: address asset,
293: string memory namePrefix,
294: string memory symbolPrefix,
295: uint128 maxTotalSupply,
296: uint16 annualInterestBips,
297: uint16 delinquencyFeeBips,
298: uint32 withdrawalBatchDuration,
299: uint16 reserveRatioBips,
300: uint32 delinquencyGracePeriod
301: ) external returns (address market) {
/// @audit Missing @param for `asset`, `namePrefix`, `symbolPrefix`
370: function _deriveSalt(
371: address asset,
372: string memory namePrefix,
373: string memory symbolPrefix
374: ) internal pure returns (bytes32 salt) {
/// @audit Missing @param for `namePrefix`, `symbolPrefix`, `annualInterestBips`, `delinquencyFeeBips`, `withdrawalBatchDuration`, `reserveRatioBips`, `delinquencyGracePeriod`
394: function enforceParameterConstraints(
395: string memory namePrefix,
396: string memory symbolPrefix,
397: uint16 annualInterestBips,
398: uint16 delinquencyFeeBips,
399: uint32 withdrawalBatchDuration,
400: uint16 reserveRatioBips,
401: uint32 delinquencyGracePeriod
402: ) internal view virtual {
/// @audit Missing @param for `market`, `annualInterestBips`
468: function setAnnualInterestBips(
469: address market,
470: uint16 annualInterestBips
471: ) external virtual onlyBorrower onlyControlledMarket(market) {
/// @audit Missing @param for `market`
490: function resetReserveRatio(address market) external virtual {
/// @audit Missing @param for `value`, `min`, `max`, `errorSelector`
503: function assertValueInRange(
504: uint256 value,
505: uint256 min,
506: uint256 max,
507: bytes4 errorSelector
508: ) internal pure {
- WildcatMarketControllerFactory.sol ( #L72-L76, #L126, #L138-L141, #L195-L200, #L317-L327, #L342 ):
/// @audit Missing @param for `_archController`, `_sentinel`, `constraints`
72: constructor(
73: address _archController,
74: address _sentinel,
75: MarketParameterConstraints memory constraints
76: ) {
/// @audit Missing @param for `controller`
126: function isDeployedController(address controller) external view returns (bool) {
/// @audit Missing @param for `start`, `end`
138: function getDeployedControllers(
139: uint256 start,
140: uint256 end
141: ) external view returns (address[] memory arr) {
/// @audit Missing @param for `feeRecipient`, `originationFeeAsset`, `originationFeeAmount`, `protocolFeeBips`
195: function setProtocolFeeConfiguration(
196: address feeRecipient,
197: address originationFeeAsset,
198: uint80 originationFeeAmount,
199: uint16 protocolFeeBips
200: ) external onlyArchControllerOwner {
/// @audit Missing @param for `namePrefix`, `symbolPrefix`, `asset`, `maxTotalSupply`, `annualInterestBips`, `delinquencyFeeBips`, `withdrawalBatchDuration`, `reserveRatioBips`, `delinquencyGracePeriod`
317: function deployControllerAndMarket(
318: string memory namePrefix,
319: string memory symbolPrefix,
320: address asset,
321: uint128 maxTotalSupply,
322: uint16 annualInterestBips,
323: uint16 delinquencyFeeBips,
324: uint32 withdrawalBatchDuration,
325: uint16 reserveRatioBips,
326: uint32 delinquencyGracePeriod
327: ) external returns (address controller, address market) {
/// @audit Missing @param for `borrower`
342: function computeControllerAddress(address borrower) external view returns (address) {
/// @audit Missing @param for `_archController`, `_chainalysisSanctionsList`
24: constructor(address _archController, address _chainalysisSanctionsList) {
/// @audit Missing @param for `borrower`, `account`
39: function isSanctioned(address borrower, address account) public view override returns (bool) {
/// @audit Missing @param for `account`
48: function overrideSanction(address account) public override {
/// @audit Missing @param for `account`
56: function removeSanctionOverride(address account) public override {
/// @audit Missing @param for `borrower`, `account`, `asset`
65: function getEscrowAddress(
66: address borrower,
67: address account,
68: address asset
69: ) public view override returns (address escrowAddress) {
/// @audit Missing @param for `borrower`, `account`, `asset`
95: function createEscrow(
96: address borrower,
97: address account,
98: address asset
99: ) public override returns (address escrowContract) {
/// @audit Missing @param for `a`, `b`
5: function and(bool a, bool b) internal pure returns (bool c) {
/// @audit Missing @param for `a`, `b`
11: function or(bool a, bool b) internal pure returns (bool c) {
/// @audit Missing @param for `a`, `b`
17: function xor(bool a, bool b) internal pure returns (bool c) {
/// @audit Missing @param for `arr`
19: function empty(FIFOQueue storage arr) internal view returns (bool) {
/// @audit Missing @param for `arr`
23: function first(FIFOQueue storage arr) internal view returns (uint32) {
/// @audit Missing @param for `arr`, `index`
30: function at(FIFOQueue storage arr, uint256 index) internal view returns (uint32) {
/// @audit Missing @param for `arr`
38: function length(FIFOQueue storage arr) internal view returns (uint128) {
/// @audit Missing @param for `arr`
42: function values(FIFOQueue storage arr) internal view returns (uint32[] memory _values) {
/// @audit Missing @param for `arr`, `value`
55: function push(FIFOQueue storage arr, uint32 value) internal {
/// @audit Missing @param for `arr`
61: function shift(FIFOQueue storage arr) internal {
/// @audit Missing @param for `arr`, `n`
70: function shiftN(FIFOQueue storage arr, uint128 n) internal {
/// @audit Missing @param for `state`, `timestamp`
30: function calculateBaseInterest(
31: MarketState memory state,
32: uint256 timestamp
33: ) internal pure returns (uint256 baseInterestRay) {
/// @audit Missing @param for `state`, `baseInterestRay`, `protocolFeeBips`
40: function applyProtocolFee(
41: MarketState memory state,
42: uint256 baseInterestRay,
43: uint256 protocolFeeBips
44: ) internal pure returns (uint256 protocolFee) {
/// @audit Missing @param for `state`, `timestamp`, `delinquencyFeeBips`, `delinquencyGracePeriod`
53: function updateDelinquency(
54: MarketState memory state,
55: uint256 timestamp,
56: uint256 delinquencyFeeBips,
57: uint256 delinquencyGracePeriod
58: ) internal pure returns (uint256 delinquencyFeeRay) {
/// @audit Missing @param for `data`
7: function deployInitCode(bytes memory data) internal returns (address initCodeStorage) {
/// @audit Missing @param for `deployer`
48: function getCreate2Prefix(address deployer) internal pure returns (uint256 create2Prefix) {
/// @audit Missing @param for `create2Prefix`, `salt`, `initCodeHash`
54: function calculateCreate2Address(
55: uint256 create2Prefix,
56: bytes32 salt,
57: uint256 initCodeHash
58: ) internal pure returns (address create2Address) {
/// @audit Missing @param for `initCodeStorage`
83: function createWithStoredInitCode(address initCodeStorage) internal returns (address deployment) {
/// @audit Missing @param for `initCodeStorage`, `value`
87: function createWithStoredInitCode(
88: address initCodeStorage,
89: uint256 value
90: ) internal returns (address deployment) {
/// @audit Missing @param for `initCodeStorage`, `salt`
99: function create2WithStoredInitCode(
100: address initCodeStorage,
101: bytes32 salt
102: ) internal returns (address deployment) {
/// @audit Missing @param for `initCodeStorage`, `salt`, `value`
106: function create2WithStoredInitCode(
107: address initCodeStorage,
108: bytes32 salt,
109: uint256 value
110: ) internal returns (address deployment) {
- MarketState.sol ( #L51, #L59, #L66-L69, #L76, #L87-L89, #L105-L108, #L123-L126, #L130, #L138 ):
/// @audit Missing @param for `state`
51: function totalSupply(MarketState memory state) internal pure returns (uint256) {
/// @audit Missing @param for `state`
59: function maximumDeposit(MarketState memory state) internal pure returns (uint256) {
/// @audit Missing @param for `state`, `amount`
66: function normalizeAmount(
67: MarketState memory state,
68: uint256 amount
69: ) internal pure returns (uint256) {
/// @audit Missing @param for `state`, `amount`
76: function scaleAmount(MarketState memory state, uint256 amount) internal pure returns (uint256) {
/// @audit Missing @param for `state`
87: function liquidityRequired(
88: MarketState memory state
89: ) internal pure returns (uint256 _liquidityRequired) {
/// @audit Missing @param for `state`, `totalAssets`
105: function withdrawableProtocolFees(
106: MarketState memory state,
107: uint256 totalAssets
108: ) internal pure returns (uint128) {
/// @audit Missing @param for `state`, `totalAssets`
123: function borrowableAssets(
124: MarketState memory state,
125: uint256 totalAssets
126: ) internal pure returns (uint256) {
/// @audit Missing @param for `state`
130: function hasPendingExpiredBatch(MarketState memory state) internal view returns (bool result) {
/// @audit Missing @param for `state`
138: function totalDebts(MarketState memory state) internal pure returns (uint256) {
/// @audit Missing @param for `a`, `b`
44: function min(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Missing @param for `a`, `b`
51: function max(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Missing @param for `a`, `b`
59: function satSub(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Missing @param for `condition`, `valueIfTrue`, `valueIfFalse`
71: function ternary(
72: bool condition,
73: uint256 valueIfTrue,
74: uint256 valueIfFalse
75: ) internal pure returns (uint256 c) {
/// @audit Missing @param for `a`, `b`
85: function bipMul(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Missing @param for `a`, `b`
105: function bipDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Missing @param for `a`
121: function bipToRay(uint256 a) internal pure returns (uint256 b) {
/// @audit Missing @param for `a`, `b`
138: function rayMul(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Missing @param for `a`, `b`
155: function rayDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Missing @param for `x`, `y`, `d`
173: function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @audit Missing @param for `x`, `y`, `d`
191: function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
- SafeCastLib.sol ( #L7, #L17, #L21, #L25, #L29, #L33, #L37, #L41, #L45, #L49, #L53, #L57, #L61, #L65, #L69, #L73, #L77, #L81, #L85, #L89, #L93, #L97, #L101, #L105, #L109, #L113, #L117, #L121, #L125, #L129, #L133, #L137 ):
/// @audit Missing @param for `didNotOverflow`
7: function _assertNonOverflow(bool didNotOverflow) private pure {
/// @audit Missing @param for `x`
17: function toUint8(uint256 x) internal pure returns (uint8 y) {
/// @audit Missing @param for `x`
21: function toUint16(uint256 x) internal pure returns (uint16 y) {
/// @audit Missing @param for `x`
25: function toUint24(uint256 x) internal pure returns (uint24 y) {
/// @audit Missing @param for `x`
29: function toUint32(uint256 x) internal pure returns (uint32 y) {
/// @audit Missing @param for `x`
33: function toUint40(uint256 x) internal pure returns (uint40 y) {
/// @audit Missing @param for `x`
37: function toUint48(uint256 x) internal pure returns (uint48 y) {
/// @audit Missing @param for `x`
41: function toUint56(uint256 x) internal pure returns (uint56 y) {
/// @audit Missing @param for `x`
45: function toUint64(uint256 x) internal pure returns (uint64 y) {
/// @audit Missing @param for `x`
49: function toUint72(uint256 x) internal pure returns (uint72 y) {
/// @audit Missing @param for `x`
53: function toUint80(uint256 x) internal pure returns (uint80 y) {
/// @audit Missing @param for `x`
57: function toUint88(uint256 x) internal pure returns (uint88 y) {
/// @audit Missing @param for `x`
61: function toUint96(uint256 x) internal pure returns (uint96 y) {
/// @audit Missing @param for `x`
65: function toUint104(uint256 x) internal pure returns (uint104 y) {
/// @audit Missing @param for `x`
69: function toUint112(uint256 x) internal pure returns (uint112 y) {
/// @audit Missing @param for `x`
73: function toUint120(uint256 x) internal pure returns (uint120 y) {
/// @audit Missing @param for `x`
77: function toUint128(uint256 x) internal pure returns (uint128 y) {
/// @audit Missing @param for `x`
81: function toUint136(uint256 x) internal pure returns (uint136 y) {
/// @audit Missing @param for `x`
85: function toUint144(uint256 x) internal pure returns (uint144 y) {
/// @audit Missing @param for `x`
89: function toUint152(uint256 x) internal pure returns (uint152 y) {
/// @audit Missing @param for `x`
93: function toUint160(uint256 x) internal pure returns (uint160 y) {
/// @audit Missing @param for `x`
97: function toUint168(uint256 x) internal pure returns (uint168 y) {
/// @audit Missing @param for `x`
101: function toUint176(uint256 x) internal pure returns (uint176 y) {
/// @audit Missing @param for `x`
105: function toUint184(uint256 x) internal pure returns (uint184 y) {
/// @audit Missing @param for `x`
109: function toUint192(uint256 x) internal pure returns (uint192 y) {
/// @audit Missing @param for `x`
113: function toUint200(uint256 x) internal pure returns (uint200 y) {
/// @audit Missing @param for `x`
117: function toUint208(uint256 x) internal pure returns (uint208 y) {
/// @audit Missing @param for `x`
121: function toUint216(uint256 x) internal pure returns (uint216 y) {
/// @audit Missing @param for `x`
125: function toUint224(uint256 x) internal pure returns (uint224 y) {
/// @audit Missing @param for `x`
129: function toUint232(uint256 x) internal pure returns (uint232 y) {
/// @audit Missing @param for `x`
133: function toUint240(uint256 x) internal pure returns (uint240 y) {
/// @audit Missing @param for `x`
137: function toUint248(uint256 x) internal pure returns (uint248 y) {
/// @audit Missing @param for `value`
19: function bytes32ToString(bytes32 value) pure returns (string memory str) {
/// @audit Missing @param for `target`, `rightPaddedFunctionSelector`, `rightPaddedGenericErrorSelector`
33: function queryStringOrBytes32AsString(
34: address target,
35: uint256 rightPaddedFunctionSelector,
36: uint256 rightPaddedGenericErrorSelector
37: ) view returns (string memory str) {
/// @audit Missing @param for `target`
96: function queryName(address target) view returns (string memory) {
/// @audit Missing @param for `target`
101: function querySymbol(address target) view returns (string memory) {
/// @audit Missing @param for `batch`
38: function scaledOwedAmount(WithdrawalBatch memory batch) internal pure returns (uint104) {
/// @audit Missing @param for `batch`, `state`, `totalAssets`
47: function availableLiquidityForPendingBatch(
48: WithdrawalBatch memory batch,
49: MarketState memory state,
50: uint256 totalAssets
51: ) internal pure returns (uint256) {
/// @audit Missing @param for `amount`
42: function depositUpTo(
43: uint256 amount
44: ) public virtual nonReentrant returns (uint256 /* actualAmount */) {
/// @audit Missing @param for `amount`
86: function deposit(uint256 amount) external virtual {
/// @audit Missing @param for `amount`
119: function borrow(uint256 amount) external onlyBorrower nonReentrant {
- WildcatMarketBase.sol ( #L150, #L163, #L197-L200, #L292, #L299, #L448, #L466, #L498-L503, #L528-L532 ):
/// @audit Missing @param for `accountAddress`
150: function _getAccount(address accountAddress) internal view returns (Account memory account) {
/// @audit Missing @param for `state`, `accountAddress`
163: function _blockAccount(MarketState memory state, address accountAddress) internal {
/// @audit Missing @param for `accountAddress`, `requiredRole`
197: function _getAccountWithRole(
198: address accountAddress,
199: AuthRole requiredRole
200: ) internal returns (Account memory account) {
/// @audit Missing @param for `account`
292: function scaledBalanceOf(address account) external view nonReentrantView returns (uint256) {
/// @audit Missing @param for `account`
299: function getAccountRole(address account) external view nonReentrantView returns (AuthRole) {
/// @audit Missing @param for `state`
448: function _writeState(MarketState memory state) internal {
/// @audit Missing @param for `state`
466: function _processExpiredWithdrawalBatch(MarketState memory state) internal {
/// @audit Missing @param for `batch`, `state`, `expiry`, `availableLiquidity`
498: function _applyWithdrawalBatchPayment(
499: WithdrawalBatch memory batch,
500: MarketState memory state,
501: uint32 expiry,
502: uint256 availableLiquidity
503: ) internal {
/// @audit Missing @param for `batch`, `state`, `availableLiquidity`
528: function _applyWithdrawalBatchPaymentView(
529: WithdrawalBatch memory batch,
530: MarketState memory state,
531: uint256 availableLiquidity
532: ) internal pure {
/// @audit Missing @param for `accountAddress`
74: function nukeFromOrbit(address accountAddress) external nonReentrant {
/// @audit Missing @param for `accountAddress`
88: function stunningReversal(address accountAddress) external nonReentrant {
/// @audit Missing @param for `_account`, `_isAuthorized`
112: function updateAccountAuthorization(
113: address _account,
114: bool _isAuthorized
115: ) external onlyController nonReentrant {
/// @audit Missing @param for `_maxTotalSupply`
134: function setMaxTotalSupply(uint256 _maxTotalSupply) external onlyController nonReentrant {
/// @audit Missing @param for `_annualInterestBips`
149: function setAnnualInterestBips(uint16 _annualInterestBips) public onlyController nonReentrant {
/// @audit Missing @param for `_reserveRatioBips`
171: function setReserveRatioBips(uint16 _reserveRatioBips) public onlyController nonReentrant {
/// @audit Missing @param for `account`
16: function balanceOf(address account) public view virtual nonReentrantView returns (uint256) {
/// @audit Missing @param for `spender`, `amount`
31: function approve(address spender, uint256 amount) external virtual nonReentrant returns (bool) {
/// @audit Missing @param for `to`, `amount`
36: function transfer(address to, uint256 amount) external virtual nonReentrant returns (bool) {
/// @audit Missing @param for `from`, `to`, `amount`
41: function transferFrom(
42: address from,
43: address to,
44: uint256 amount
45: ) external virtual nonReentrant returns (bool) {
/// @audit Missing @param for `approver`, `spender`, `amount`
59: function _approve(address approver, address spender, uint256 amount) internal virtual {
/// @audit Missing @param for `from`, `to`, `amount`
64: function _transfer(address from, address to, uint256 amount) internal virtual {
- WildcatMarketWithdrawals.sol ( #L28-L30, #L38-L41, #L45-L48, #L77, #L137-L140 ):
/// @audit Missing @param for `expiry`
28: function getWithdrawalBatch(
29: uint32 expiry
30: ) external view nonReentrantView returns (WithdrawalBatch memory) {
/// @audit Missing @param for `accountAddress`, `expiry`
38: function getAccountWithdrawalStatus(
39: address accountAddress,
40: uint32 expiry
41: ) external view nonReentrantView returns (AccountWithdrawalStatus memory) {
/// @audit Missing @param for `accountAddress`, `expiry`
45: function getAvailableWithdrawalAmount(
46: address accountAddress,
47: uint32 expiry
48: ) external view nonReentrantView returns (uint256) {
/// @audit Missing @param for `amount`
77: function queueWithdrawal(uint256 amount) external nonReentrant {
/// @audit Missing @param for `accountAddress`, `expiry`
137: function executeWithdrawal(
138: address accountAddress,
139: uint32 expiry
140: ) external nonReentrant returns (uint256) {
There is 1 instance:
- WildcatMarketController.sol ( #L87 ):
/// @audit Missing @param for `market`
87: modifier onlyControlledMarket(address market) {
It is recommended to use the NatSpec tags @notice
, @dev
, @return
, @inheritdoc
for public state variables.
There are 23 instances (click to show):
41: IWildcatArchController public immutable archController;
43: IWildcatMarketControllerFactory public immutable controllerFactory;
45: address public immutable borrower;
47: address public immutable sentinel;
49: address public immutable marketInitCodeStorage;
51: uint256 public immutable marketInitCodeHash;
76: mapping(address => TemporaryReserveRatio) public temporaryExcessReserveRatio;
31: IWildcatArchController public immutable archController;
34: address public immutable sentinel;
36: address public immutable marketInitCodeStorage;
38: uint256 public immutable marketInitCodeHash;
40: address public immutable controllerInitCodeStorage;
42: uint256 public immutable controllerInitCodeHash;
11: address public immutable override sentinel;
12: address public immutable override borrower;
13: address public immutable override account;
11: bytes32 public constant override WildcatSanctionsEscrowInitcodeHash =
14: address public immutable override chainalysisSanctionsList;
16: address public immutable override archController;
18: TmpEscrowParams public override tmpEscrowParams;
20: mapping(address borrower => mapping(address account => bool sanctionOverride))
- WildcatMarketBase.sol ( #L24 ):
24: string public constant version = '1.0';
- WildcatMarketToken.sol ( #L13 ):
13: mapping(address => mapping(address => uint256)) public allowance;
There are 151 instances (click to show):
- WildcatArchController.sol ( #L77, #L81, #L85-L88, #L98, #L120, #L124, #L128-L131, #L141, #L163, #L167, #L171-L174, #L184, #L206, #L210, #L214-L217, #L227 ):
77: function isRegisteredBorrower(address borrower) external view returns (bool) {
81: function getRegisteredBorrowers() external view returns (address[] memory) {
85: function getRegisteredBorrowers(
86: uint256 start,
87: uint256 end
88: ) external view returns (address[] memory arr) {
98: function getRegisteredBorrowersCount() external view returns (uint256) {
120: function isRegisteredControllerFactory(address factory) external view returns (bool) {
124: function getRegisteredControllerFactories() external view returns (address[] memory) {
128: function getRegisteredControllerFactories(
129: uint256 start,
130: uint256 end
131: ) external view returns (address[] memory arr) {
141: function getRegisteredControllerFactoriesCount() external view returns (uint256) {
163: function isRegisteredController(address controller) external view returns (bool) {
167: function getRegisteredControllers() external view returns (address[] memory) {
171: function getRegisteredControllers(
172: uint256 start,
173: uint256 end
174: ) external view returns (address[] memory arr) {
184: function getRegisteredControllersCount() external view returns (uint256) {
206: function isRegisteredMarket(address market) external view returns (bool) {
210: function getRegisteredMarkets() external view returns (address[] memory) {
214: function getRegisteredMarkets(
215: uint256 start,
216: uint256 end
217: ) external view returns (address[] memory arr) {
227: function getRegisteredMarketsCount() external view returns (uint256) {
- WildcatMarketController.sol ( #L121, #L125-L128, #L138, #L142, #L196, #L200, #L204-L207, #L217, #L221-L225, #L238, #L291-L301, #L370-L374, #L446-L449 ):
121: function getAuthorizedLenders() external view returns (address[] memory) {
125: function getAuthorizedLenders(
126: uint256 start,
127: uint256 end
128: ) external view returns (address[] memory arr) {
138: function getAuthorizedLendersCount() external view returns (uint256) {
142: function isAuthorizedLender(address lender) external view virtual returns (bool) {
196: function isControlledMarket(address market) external view returns (bool) {
200: function getControlledMarkets() external view returns (address[] memory) {
204: function getControlledMarkets(
205: uint256 start,
206: uint256 end
207: ) external view returns (address[] memory arr) {
217: function getControlledMarketsCount() external view returns (uint256) {
221: function computeMarketAddress(
222: address asset,
223: string memory namePrefix,
224: string memory symbolPrefix
225: ) external view returns (address) {
238: function getMarketParameters() external view returns (MarketParameters memory parameters) {
291: function deployMarket(
292: address asset,
293: string memory namePrefix,
294: string memory symbolPrefix,
295: uint128 maxTotalSupply,
296: uint16 annualInterestBips,
297: uint16 delinquencyFeeBips,
298: uint32 withdrawalBatchDuration,
299: uint16 reserveRatioBips,
300: uint32 delinquencyGracePeriod
301: ) external returns (address market) {
370: function _deriveSalt(
371: address asset,
372: string memory namePrefix,
373: string memory symbolPrefix
374: ) internal pure returns (bytes32 salt) {
446: function getParameterConstraints()
447: external
448: view
449: returns (MarketParameterConstraints memory constraints)
- WildcatMarketControllerFactory.sol ( #L106-L109, #L116-L119, #L126, #L130, #L134, #L138-L141, #L223-L226, #L246-L250, #L282, #L317-L327, #L342 ):
106: function _storeControllerInitCode()
107: internal
108: virtual
109: returns (address initCodeStorage, uint256 initCodeHash)
116: function _storeMarketInitCode()
117: internal
118: virtual
119: returns (address initCodeStorage, uint256 initCodeHash)
126: function isDeployedController(address controller) external view returns (bool) {
130: function getDeployedControllersCount() external view returns (uint256) {
134: function getDeployedControllers() external view returns (address[] memory) {
138: function getDeployedControllers(
139: uint256 start,
140: uint256 end
141: ) external view returns (address[] memory arr) {
223: function getParameterConstraints()
224: external
225: view
226: returns (MarketParameterConstraints memory constraints)
246: function getMarketControllerParameters()
247: external
248: view
249: virtual
250: returns (MarketControllerParameters memory parameters)
282: function deployController() public returns (address controller) {
317: function deployControllerAndMarket(
318: string memory namePrefix,
319: string memory symbolPrefix,
320: address asset,
321: uint128 maxTotalSupply,
322: uint16 annualInterestBips,
323: uint16 delinquencyFeeBips,
324: uint32 withdrawalBatchDuration,
325: uint16 reserveRatioBips,
326: uint32 delinquencyGracePeriod
327: ) external returns (address controller, address market) {
342: function computeControllerAddress(address borrower) external view returns (address) {
21: function balance() public view override returns (uint256) {
25: function canReleaseEscrow() public view override returns (bool) {
29: function escrowedAsset() public view override returns (address, uint256) {
39: function isSanctioned(address borrower, address account) public view override returns (bool) {
65: function getEscrowAddress(
66: address borrower,
67: address account,
68: address asset
69: ) public view override returns (address escrowAddress) {
95: function createEscrow(
96: address borrower,
97: address account,
98: address asset
99: ) public override returns (address escrowContract) {
5: function and(bool a, bool b) internal pure returns (bool c) {
11: function or(bool a, bool b) internal pure returns (bool c) {
17: function xor(bool a, bool b) internal pure returns (bool c) {
19: function empty(FIFOQueue storage arr) internal view returns (bool) {
23: function first(FIFOQueue storage arr) internal view returns (uint32) {
30: function at(FIFOQueue storage arr, uint256 index) internal view returns (uint32) {
38: function length(FIFOQueue storage arr) internal view returns (uint128) {
42: function values(FIFOQueue storage arr) internal view returns (uint32[] memory _values) {
30: function calculateBaseInterest(
31: MarketState memory state,
32: uint256 timestamp
33: ) internal pure returns (uint256 baseInterestRay) {
40: function applyProtocolFee(
41: MarketState memory state,
42: uint256 baseInterestRay,
43: uint256 protocolFeeBips
44: ) internal pure returns (uint256 protocolFee) {
53: function updateDelinquency(
54: MarketState memory state,
55: uint256 timestamp,
56: uint256 delinquencyFeeBips,
57: uint256 delinquencyGracePeriod
58: ) internal pure returns (uint256 delinquencyFeeRay) {
89: function updateTimeDelinquentAndGetPenaltyTime(
90: MarketState memory state,
91: uint256 delinquencyGracePeriod,
92: uint256 timeDelta
93: ) internal pure returns (uint256 /* timeWithPenalty */) {
7: function deployInitCode(bytes memory data) internal returns (address initCodeStorage) {
48: function getCreate2Prefix(address deployer) internal pure returns (uint256 create2Prefix) {
54: function calculateCreate2Address(
55: uint256 create2Prefix,
56: bytes32 salt,
57: uint256 initCodeHash
58: ) internal pure returns (address create2Address) {
83: function createWithStoredInitCode(address initCodeStorage) internal returns (address deployment) {
87: function createWithStoredInitCode(
88: address initCodeStorage,
89: uint256 value
90: ) internal returns (address deployment) {
99: function create2WithStoredInitCode(
100: address initCodeStorage,
101: bytes32 salt
102: ) internal returns (address deployment) {
106: function create2WithStoredInitCode(
107: address initCodeStorage,
108: bytes32 salt,
109: uint256 value
110: ) internal returns (address deployment) {
- MarketState.sol ( #L51, #L59, #L66-L69, #L76, #L87-L89, #L105-L108, #L123-L126, #L130, #L138 ):
51: function totalSupply(MarketState memory state) internal pure returns (uint256) {
59: function maximumDeposit(MarketState memory state) internal pure returns (uint256) {
66: function normalizeAmount(
67: MarketState memory state,
68: uint256 amount
69: ) internal pure returns (uint256) {
76: function scaleAmount(MarketState memory state, uint256 amount) internal pure returns (uint256) {
87: function liquidityRequired(
88: MarketState memory state
89: ) internal pure returns (uint256 _liquidityRequired) {
105: function withdrawableProtocolFees(
106: MarketState memory state,
107: uint256 totalAssets
108: ) internal pure returns (uint128) {
123: function borrowableAssets(
124: MarketState memory state,
125: uint256 totalAssets
126: ) internal pure returns (uint256) {
130: function hasPendingExpiredBatch(MarketState memory state) internal view returns (bool result) {
138: function totalDebts(MarketState memory state) internal pure returns (uint256) {
44: function min(uint256 a, uint256 b) internal pure returns (uint256 c) {
51: function max(uint256 a, uint256 b) internal pure returns (uint256 c) {
59: function satSub(uint256 a, uint256 b) internal pure returns (uint256 c) {
71: function ternary(
72: bool condition,
73: uint256 valueIfTrue,
74: uint256 valueIfFalse
75: ) internal pure returns (uint256 c) {
85: function bipMul(uint256 a, uint256 b) internal pure returns (uint256 c) {
105: function bipDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {
121: function bipToRay(uint256 a) internal pure returns (uint256 b) {
138: function rayMul(uint256 a, uint256 b) internal pure returns (uint256 c) {
155: function rayDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {
173: function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
191: function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
- SafeCastLib.sol ( #L17, #L21, #L25, #L29, #L33, #L37, #L41, #L45, #L49, #L53, #L57, #L61, #L65, #L69, #L73, #L77, #L81, #L85, #L89, #L93, #L97, #L101, #L105, #L109, #L113, #L117, #L121, #L125, #L129, #L133, #L137 ):
17: function toUint8(uint256 x) internal pure returns (uint8 y) {
21: function toUint16(uint256 x) internal pure returns (uint16 y) {
25: function toUint24(uint256 x) internal pure returns (uint24 y) {
29: function toUint32(uint256 x) internal pure returns (uint32 y) {
33: function toUint40(uint256 x) internal pure returns (uint40 y) {
37: function toUint48(uint256 x) internal pure returns (uint48 y) {
41: function toUint56(uint256 x) internal pure returns (uint56 y) {
45: function toUint64(uint256 x) internal pure returns (uint64 y) {
49: function toUint72(uint256 x) internal pure returns (uint72 y) {
53: function toUint80(uint256 x) internal pure returns (uint80 y) {
57: function toUint88(uint256 x) internal pure returns (uint88 y) {
61: function toUint96(uint256 x) internal pure returns (uint96 y) {
65: function toUint104(uint256 x) internal pure returns (uint104 y) {
69: function toUint112(uint256 x) internal pure returns (uint112 y) {
73: function toUint120(uint256 x) internal pure returns (uint120 y) {
77: function toUint128(uint256 x) internal pure returns (uint128 y) {
81: function toUint136(uint256 x) internal pure returns (uint136 y) {
85: function toUint144(uint256 x) internal pure returns (uint144 y) {
89: function toUint152(uint256 x) internal pure returns (uint152 y) {
93: function toUint160(uint256 x) internal pure returns (uint160 y) {
97: function toUint168(uint256 x) internal pure returns (uint168 y) {
101: function toUint176(uint256 x) internal pure returns (uint176 y) {
105: function toUint184(uint256 x) internal pure returns (uint184 y) {
109: function toUint192(uint256 x) internal pure returns (uint192 y) {
113: function toUint200(uint256 x) internal pure returns (uint200 y) {
117: function toUint208(uint256 x) internal pure returns (uint208 y) {
121: function toUint216(uint256 x) internal pure returns (uint216 y) {
125: function toUint224(uint256 x) internal pure returns (uint224 y) {
129: function toUint232(uint256 x) internal pure returns (uint232 y) {
133: function toUint240(uint256 x) internal pure returns (uint240 y) {
137: function toUint248(uint256 x) internal pure returns (uint248 y) {
19: function bytes32ToString(bytes32 value) pure returns (string memory str) {
33: function queryStringOrBytes32AsString(
34: address target,
35: uint256 rightPaddedFunctionSelector,
36: uint256 rightPaddedGenericErrorSelector
37: ) view returns (string memory str) {
96: function queryName(address target) view returns (string memory) {
101: function querySymbol(address target) view returns (string memory) {
38: function scaledOwedAmount(WithdrawalBatch memory batch) internal pure returns (uint104) {
47: function availableLiquidityForPendingBatch(
48: WithdrawalBatch memory batch,
49: MarketState memory state,
50: uint256 totalAssets
51: ) internal pure returns (uint256) {
- WildcatMarket.sol ( #L42-L44 ):
42: function depositUpTo(
43: uint256 amount
44: ) public virtual nonReentrant returns (uint256 /* actualAmount */) {
- WildcatMarketBase.sol ( #L150, #L197-L200, #L223, #L231, #L238, #L252, #L260, #L267, #L276, #L285, #L292, #L299, #L307, #L399-L406 ):
150: function _getAccount(address accountAddress) internal view returns (Account memory account) {
197: function _getAccountWithRole(
198: address accountAddress,
199: AuthRole requiredRole
200: ) internal returns (Account memory account) {
223: function coverageLiquidity() external view nonReentrantView returns (uint256) {
231: function scaleFactor() external view nonReentrantView returns (uint256) {
238: function totalAssets() public view returns (uint256) {
252: function borrowableAssets() external view nonReentrantView returns (uint256) {
260: function accruedProtocolFees() external view nonReentrantView returns (uint256) {
267: function previousState() external view returns (MarketState memory) {
276: function currentState() public view nonReentrantView returns (MarketState memory state) {
285: function scaledTotalSupply() external view nonReentrantView returns (uint256) {
292: function scaledBalanceOf(address account) external view nonReentrantView returns (uint256) {
299: function getAccountRole(address account) external view nonReentrantView returns (AuthRole) {
307: function withdrawableProtocolFees() external view returns (uint128) {
399: function _calculateCurrentState()
400: internal
401: view
402: returns (
403: MarketState memory state,
404: uint32 expiredBatchExpiry,
405: WithdrawalBatch memory expiredBatch
406: )
21: function maximumDeposit() external view returns (uint256) {
30: function maxTotalSupply() external view returns (uint256) {
38: function annualInterestBips() external view returns (uint256) {
42: function reserveRatioBips() external view returns (uint256) {
16: function balanceOf(address account) public view virtual nonReentrantView returns (uint256) {
22: function totalSupply() external view virtual nonReentrantView returns (uint256) {
31: function approve(address spender, uint256 amount) external virtual nonReentrant returns (bool) {
36: function transfer(address to, uint256 amount) external virtual nonReentrant returns (bool) {
41: function transferFrom(
42: address from,
43: address to,
44: uint256 amount
45: ) external virtual nonReentrant returns (bool) {
- WildcatMarketWithdrawals.sol ( #L24, #L28-L30, #L38-L41, #L45-L48, #L137-L140 ):
24: function getUnpaidBatchExpiries() external view nonReentrantView returns (uint32[] memory) {
28: function getWithdrawalBatch(
29: uint32 expiry
30: ) external view nonReentrantView returns (WithdrawalBatch memory) {
38: function getAccountWithdrawalStatus(
39: address accountAddress,
40: uint32 expiry
41: ) external view nonReentrantView returns (AccountWithdrawalStatus memory) {
45: function getAvailableWithdrawalAmount(
46: address accountAddress,
47: uint32 expiry
48: ) external view nonReentrantView returns (uint256) {
137: function executeWithdrawal(
138: address accountAddress,
139: uint32 expiry
140: ) external nonReentrant returns (uint256) {
According to the Solidity Style Guide, contracts and libraries should be named using the CapWords style and match their filenames.
There are 3 instances:
- FIFOQueue.sol ( #L16 ):
/// @audit File name does not match contract name
16: library FIFOQueueLib {
- MarketState.sol ( #L44 ):
/// @audit File name does not match contract name
44: library MarketStateLib {
- Withdrawal.sol ( #L37 ):
/// @audit File name does not match contract name
37: library WithdrawalLib {
As the Solidity Style Guide suggests: functions and modifiers should be named in mixedCase style.
There are 3 instances:
- FIFOQueue.sol ( #L70 ):
70: function shiftN(FIFOQueue storage arr, uint128 n) internal {
318: function effectiveBorrowerAPR() external view returns (uint256) {
334: function effectiveLenderAPR() external view returns (uint256) {
For immutable
variable names, each word should use all capital letters, with underscores separating each word (UPPER_CASE_WITH_UNDERSCORES)
There are 50 instances (click to show):
- WildcatMarketController.sol ( #L41, #L43, #L45, #L47, #L49, #L51, #L53, #L55, #L56, #L58, #L59, #L61, #L62, #L64, #L65, #L67, #L68 ):
41: IWildcatArchController public immutable archController;
43: IWildcatMarketControllerFactory public immutable controllerFactory;
45: address public immutable borrower;
47: address public immutable sentinel;
49: address public immutable marketInitCodeStorage;
51: uint256 public immutable marketInitCodeHash;
53: uint256 internal immutable ownCreate2Prefix = LibStoredInitCode.getCreate2Prefix(address(this));
55: uint32 internal immutable MinimumDelinquencyGracePeriod;
56: uint32 internal immutable MaximumDelinquencyGracePeriod;
58: uint16 internal immutable MinimumReserveRatioBips;
59: uint16 internal immutable MaximumReserveRatioBips;
61: uint16 internal immutable MinimumDelinquencyFeeBips;
62: uint16 internal immutable MaximumDelinquencyFeeBips;
64: uint32 internal immutable MinimumWithdrawalBatchDuration;
65: uint32 internal immutable MaximumWithdrawalBatchDuration;
67: uint16 internal immutable MinimumAnnualInterestBips;
68: uint16 internal immutable MaximumAnnualInterestBips;
- WildcatMarketControllerFactory.sol ( #L31, #L34, #L36, #L38, #L40, #L42, #L44, #L46, #L47, #L49, #L50, #L52, #L53, #L55, #L56, #L58, #L59 ):
31: IWildcatArchController public immutable archController;
34: address public immutable sentinel;
36: address public immutable marketInitCodeStorage;
38: uint256 public immutable marketInitCodeHash;
40: address public immutable controllerInitCodeStorage;
42: uint256 public immutable controllerInitCodeHash;
44: uint256 internal immutable ownCreate2Prefix = LibStoredInitCode.getCreate2Prefix(address(this));
46: uint32 internal immutable MinimumDelinquencyGracePeriod;
47: uint32 internal immutable MaximumDelinquencyGracePeriod;
49: uint16 internal immutable MinimumReserveRatioBips;
50: uint16 internal immutable MaximumReserveRatioBips;
52: uint16 internal immutable MinimumDelinquencyFeeBips;
53: uint16 internal immutable MaximumDelinquencyFeeBips;
55: uint32 internal immutable MinimumWithdrawalBatchDuration;
56: uint32 internal immutable MaximumWithdrawalBatchDuration;
58: uint16 internal immutable MinimumAnnualInterestBips;
59: uint16 internal immutable MaximumAnnualInterestBips;
11: address public immutable override sentinel;
12: address public immutable override borrower;
13: address public immutable override account;
14: address internal immutable asset;
14: address public immutable override chainalysisSanctionsList;
16: address public immutable override archController;
27: address public immutable sentinel;
30: address public immutable borrower;
33: address public immutable feeRecipient;
36: uint256 public immutable protocolFeeBips;
39: uint256 public immutable delinquencyFeeBips;
42: uint256 public immutable delinquencyGracePeriod;
45: address public immutable controller;
48: address public immutable asset;
51: uint256 public immutable withdrawalBatchDuration;
54: uint8 public immutable decimals;
There are some automated tools that will flag a project as having higher complexity if there is inline-assembly, so it's best to avoid using it where it's not necessary. In addition, most assembly methods can be replaced by non-assembly methods, for example:
assembly{ g := gas() }
=>uint256 g = gasleft()
assembly{ id := chainid() }
=>uint256 id = block.chainid
assembly { r := mulmod(a, b, d) }
=>uint256 m = mulmod(x, y, k)
assembly { size := extcodesize() }
=>uint256 size = address(a).code.length
- etc.
There are 29 instances (click to show):
380: mstore(0x20, keccak256(add(namePrefix, 32), mload(namePrefix)))
381: mstore(0x40, keccak256(add(symbolPrefix, 32), mload(symbolPrefix)))
382: salt := keccak256(0, 0x60)
407: revert(0x1c, 0x04)
512: revert(0, 4)
27: revert(0, 4)
38: revert(Error_SelectorPointer, 4)
51: revert(0, 0x24)
64: revert(Error_SelectorPointer, 0x24)
33: initCodeStorage := create(0, add(data, 21), createSize)
37: revert(0x1c, 0x04)
76: create2Address := keccak256(0x0b, 0x55)
93: let initCodeSize := sub(extcodesize(initCodeStorage), 1)
95: deployment := create(value, initCodePointer, initCodeSize)
113: let initCodeSize := sub(extcodesize(initCodeStorage), 1)
115: deployment := create2(value, initCodePointer, initCodeSize, salt)
- MarketState.sol ( #L134 ):
134: result := gt(timestamp(), sub(expiry, 1))
94: revert(Error_SelectorPointer, Panic_ErrorLength)
111: revert(Error_SelectorPointer, Panic_ErrorLength)
129: revert(Error_SelectorPointer, Panic_ErrorLength)
144: revert(Error_SelectorPointer, Panic_ErrorLength)
161: revert(Error_SelectorPointer, Panic_ErrorLength)
180: revert(0x1c, 0x04)
198: revert(0x1c, 0x04)
- SafeCastLib.sol ( #L12 ):
12: revert(Error_SelectorPointer, Panic_ErrorLength)
41: let status := staticcall(gas(), target, 0, 0x04, 0, 0)
53: revert(0, returndatasize())
57: revert(0, 0x04)
61: revert(0, 0x04)
As suggested by the Solidity Style Guide:
- Layout contract elements in the following order:
pragma statements
,import statements
,interfaces
,libraries
,contracts
. - Inside each contract, library or interface, use the following order:
type declarations
,state variables
,events
,errors
,modifiers
,functions
.
There are 6 instances (click to show):
- ReentrancyGuard.sol ( #L20 ):
/// @audit ↑ Out of order with error NoReentrantCalls
20: uint256 private _reentrancyGuard;
- WildcatArchController.sol ( #L29 ):
/// @audit ↑ Out of order with error MarketDoesNotExist
29: event MarketAdded(address indexed controller, address market);
/// @audit ↑ Out of order with error ControllerAlreadyDeployed
31: IWildcatArchController public immutable archController;
/// @audit ↑ Out of order with function getParameterConstraints()
244: address internal _tmpMarketBorrowerParameter = address(1);
- MathUtils.sol ( #L21 ):
/// @audit ↑ Out of order with error MulDivFailed
21: using MathUtils for uint256;
- WildcatMarketBase.sol ( #L131 ):
/// @audit ↑ Out of order with constructor ()
131: modifier onlyBorrower() {
Adding a zero address check for each address type parameter can prevent errors.
There are 47 instances (click to show):
- WildcatArchController.sol ( #L63, #L70, #L77, #L106, #L113, #L120, #L149, #L156, #L163, #L192, #L199, #L206 ):
/// @audit `borrower not checked`
63: function registerBorrower(address borrower) external onlyOwner {
/// @audit `borrower not checked`
70: function removeBorrower(address borrower) external onlyOwner {
/// @audit `borrower not checked`
77: function isRegisteredBorrower(address borrower) external view returns (bool) {
/// @audit `factory not checked`
106: function registerControllerFactory(address factory) external onlyOwner {
/// @audit `factory not checked`
113: function removeControllerFactory(address factory) external onlyOwner {
/// @audit `factory not checked`
120: function isRegisteredControllerFactory(address factory) external view returns (bool) {
/// @audit `controller not checked`
149: function registerController(address controller) external onlyControllerFactory {
/// @audit `controller not checked`
156: function removeController(address controller) external onlyOwner {
/// @audit `controller not checked`
163: function isRegisteredController(address controller) external view returns (bool) {
/// @audit `market not checked`
192: function registerMarket(address market) external onlyController {
/// @audit `market not checked`
199: function removeMarket(address market) external onlyOwner {
/// @audit `market not checked`
206: function isRegisteredMarket(address market) external view returns (bool) {
- WildcatMarketController.sol ( #L142, #L153, #L169, #L182, #L196, #L221-L225, #L291-L301, #L490 ):
/// @audit `lender not checked`
142: function isAuthorizedLender(address lender) external view virtual returns (bool) {
/// @audit `lenders not checked`
153: function authorizeLenders(address[] memory lenders) external onlyBorrower {
/// @audit `lenders not checked`
169: function deauthorizeLenders(address[] memory lenders) external onlyBorrower {
/// @audit `lender not checked`
/// @audit `markets not checked`
182: function updateLenderAuthorization(address lender, address[] memory markets) external {
/// @audit `market not checked`
196: function isControlledMarket(address market) external view returns (bool) {
/// @audit `asset not checked`
221: function computeMarketAddress(
222: address asset,
223: string memory namePrefix,
224: string memory symbolPrefix
225: ) external view returns (address) {
/// @audit `asset not checked`
291: function deployMarket(
292: address asset,
293: string memory namePrefix,
294: string memory symbolPrefix,
295: uint128 maxTotalSupply,
296: uint16 annualInterestBips,
297: uint16 delinquencyFeeBips,
298: uint32 withdrawalBatchDuration,
299: uint16 reserveRatioBips,
300: uint32 delinquencyGracePeriod
301: ) external returns (address market) {
/// @audit `market not checked`
490: function resetReserveRatio(address market) external virtual {
- WildcatMarketControllerFactory.sol ( #L126, #L317-L327, #L342 ):
/// @audit `controller not checked`
126: function isDeployedController(address controller) external view returns (bool) {
/// @audit `asset not checked`
317: function deployControllerAndMarket(
318: string memory namePrefix,
319: string memory symbolPrefix,
320: address asset,
321: uint128 maxTotalSupply,
322: uint16 annualInterestBips,
323: uint16 delinquencyFeeBips,
324: uint32 withdrawalBatchDuration,
325: uint16 reserveRatioBips,
326: uint32 delinquencyGracePeriod
327: ) external returns (address controller, address market) {
/// @audit `borrower not checked`
342: function computeControllerAddress(address borrower) external view returns (address) {
/// @audit `borrower not checked`
/// @audit `account not checked`
39: function isSanctioned(address borrower, address account) public view override returns (bool) {
/// @audit `account not checked`
48: function overrideSanction(address account) public override {
/// @audit `account not checked`
56: function removeSanctionOverride(address account) public override {
/// @audit `borrower not checked`
/// @audit `account not checked`
/// @audit `asset not checked`
65: function getEscrowAddress(
66: address borrower,
67: address account,
68: address asset
69: ) public view override returns (address escrowAddress) {
/// @audit `borrower not checked`
/// @audit `account not checked`
/// @audit `asset not checked`
95: function createEscrow(
96: address borrower,
97: address account,
98: address asset
99: ) public override returns (address escrowContract) {
/// @audit `account not checked`
292: function scaledBalanceOf(address account) external view nonReentrantView returns (uint256) {
/// @audit `account not checked`
299: function getAccountRole(address account) external view nonReentrantView returns (AuthRole) {
- WildcatMarketConfig.sol ( #L74, #L88, #L112-L115 ):
/// @audit `accountAddress not checked`
74: function nukeFromOrbit(address accountAddress) external nonReentrant {
/// @audit `accountAddress not checked`
88: function stunningReversal(address accountAddress) external nonReentrant {
/// @audit `_account not checked`
112: function updateAccountAuthorization(
113: address _account,
114: bool _isAuthorized
115: ) external onlyController nonReentrant {
/// @audit `account not checked`
16: function balanceOf(address account) public view virtual nonReentrantView returns (uint256) {
/// @audit `spender not checked`
31: function approve(address spender, uint256 amount) external virtual nonReentrant returns (bool) {
/// @audit `to not checked`
36: function transfer(address to, uint256 amount) external virtual nonReentrant returns (bool) {
/// @audit `from not checked`
/// @audit `to not checked`
41: function transferFrom(
42: address from,
43: address to,
44: uint256 amount
45: ) external virtual nonReentrant returns (bool) {
- WildcatMarketWithdrawals.sol ( #L38-L41, #L45-L48, #L137-L140 ):
/// @audit `accountAddress not checked`
38: function getAccountWithdrawalStatus(
39: address accountAddress,
40: uint32 expiry
41: ) external view nonReentrantView returns (AccountWithdrawalStatus memory) {
/// @audit `accountAddress not checked`
45: function getAvailableWithdrawalAmount(
46: address accountAddress,
47: uint32 expiry
48: ) external view nonReentrantView returns (uint256) {
/// @audit `accountAddress not checked`
137: function executeWithdrawal(
138: address accountAddress,
139: uint32 expiry
140: ) external nonReentrant returns (uint256) {
There are 11 instances (click to show):
- WildcatArchController.sol ( #L8 ):
/// @audit Ownable
8: contract WildcatArchController is Ownable {
- WildcatMarketController.sol ( #L32 ):
/// @audit IWildcatMarketControllerEventsAndErrors
32: contract WildcatMarketController is IWildcatMarketControllerEventsAndErrors {
- WildcatMarket.sol ( #L10-L14 ):
/// @audit WildcatMarketBase
/// @audit WildcatMarketConfig
/// @audit WildcatMarketToken
/// @audit WildcatMarketWithdrawals
10: contract WildcatMarket is
11: WildcatMarketBase,
12: WildcatMarketConfig,
13: WildcatMarketToken,
14: WildcatMarketWithdrawals
- WildcatMarketBase.sol ( #L14 ):
/// @audit ReentrancyGuard
/// @audit IMarketEventsAndErrors
14: contract WildcatMarketBase is ReentrancyGuard, IMarketEventsAndErrors {
- WildcatMarketConfig.sol ( #L9 ):
/// @audit WildcatMarketBase
9: contract WildcatMarketConfig is WildcatMarketBase {
- WildcatMarketToken.sol ( #L6 ):
/// @audit WildcatMarketBase
6: contract WildcatMarketToken is WildcatMarketBase {
- WildcatMarketWithdrawals.sol ( #L11 ):
/// @audit WildcatMarketBase
11: contract WildcatMarketWithdrawals is WildcatMarketBase {
Putting constants on the left side of comparison statements is a best practice known as Yoda conditions. Although solidity's static typing system prevents accidental assignments within conditionals, adopting this practice can improve code readability and consistency, especially when working across multiple languages.
There are 19 instances (click to show):
- ReentrancyGuard.sol ( #L83 ):
/// @audit put `_NOT_ENTERED` on the left
83: if (_reentrancyGuard != _NOT_ENTERED) {
/// @audit put `address(0)` on the left
345: if (originationFeeAsset != address(0)) {
/// @audit put `bytes32(0)` on the left
351: if (market.codehash != bytes32(0)) {
/// @audit put `0` on the left
477: if (tmp.expiry == 0) {
/// @audit put `0` on the left
492: if (tmp.expiry == 0) {
/// @audit put `address(0)` on the left
202: bool nullFeeRecipient = feeRecipient == address(0);
/// @audit put `address(0)` on the left
203: bool nullOriginationFeeAsset = originationFeeAsset == address(0);
/// @audit put `bytes32(0)` on the left
294: if (controller.codehash != bytes32(0)) {
- WildcatSanctionsSentinel.sol ( #L106 ):
/// @audit put `bytes32(0)` on the left
106: if (escrowContract.codehash != bytes32(0)) return escrowContract;
/// @audit put `0` on the left
57: if (scaledAmount == 0) revert NullMintAmount();
/// @audit put `0` on the left
98: if (state.accruedProtocolFees == 0) {
/// @audit put `0` on the left
102: if (withdrawableFees == 0) {
/// @audit put `address(0)` on the left
79: if ((parameters.protocolFeeBips > 0).and(parameters.feeRecipient == address(0))) {
/// @audit put `0` on the left
507: if (scaledAmountOwed == 0) {
/// @audit put `0` on the left
536: if (scaledAmountOwed == 0) {
- WildcatMarketToken.sol ( #L68 ):
/// @audit put `0` on the left
68: if (scaledAmount == 0) {
/// @audit put `0` on the left
84: if (scaledAmount == 0) {
/// @audit put `0` on the left
94: if (state.pendingWithdrawalExpiry == 0) {
/// @audit put `0` on the left
160: if (normalizedAmountWithdrawn == 0) {
Putting all the system-wide constants in a single file improves code readability, makes it easier to understand the basic configuration and limitations of the system, and makes maintenance easier.
There are 2 instances:
- WildcatSanctionsSentinel.sol ( #L11-L12 ):
11: bytes32 public constant override WildcatSanctionsEscrowInitcodeHash =
12: keccak256(type(WildcatSanctionsEscrow).creationCode);
- WildcatMarketBase.sol ( #L24 ):
24: string public constant version = '1.0';
Because the return variable (or its default value) has been assigned, explicit return at the end of the function is unnecessary, as it is returned automatically.
There is 1 instance:
- FIFOQueue.sol ( #L42-L53 ):
42: function values(FIFOQueue storage arr) internal view returns (uint32[] memory _values) {
43: uint256 startIndex = arr.startIndex;
44: uint256 nextIndex = arr.nextIndex;
45: uint256 len = nextIndex - startIndex;
46: _values = new uint32[](len);
47:
48: for (uint256 i = 0; i < len; i++) {
49: _values[i] = arr.data[startIndex + i];
50: }
51:
52: return _values;
53: }
Refactoring duplicate require()
/revert()
checks into a modifier or function can make the code more concise, readable and maintainable, and less likely to make errors or omissions when modifying the require()
or revert()
.
There is 1 instance:
- WildcatMarketWithdrawals.sol ( #L50 ):
/// @audit Duplicated on line 142
50: revert WithdrawalBatchNotExpired();
Use a scientific notation rather than decimal literals (e.g. 1e6
instead of 1000000
), for better code readability.
There are 4 instances:
- WildcatMarketController.sol ( #L481 ):
/// @audit 9000 -> 9e3
481: WildcatMarket(market).setReserveRatioBips(9000);
/// @audit 10000 -> 1e4
81: constraints.maximumAnnualInterestBips > 10000 ||
/// @audit 10000 -> 1e4
83: constraints.maximumDelinquencyFeeBips > 10000 ||
/// @audit 10000 -> 1e4
85: constraints.maximumReserveRatioBips > 10000 ||
To prevent the actual contracts being deployed from behaving differently depending on the compiler version, it is recommended to use fixed solidity versions for contracts and libraries.
Although we can configure a specific version through config (like hardhat, forge config files), it is recommended to set the fixed version in the solidity pragma directly before deploying to the mainnet.
There are 19 instances (click to show):
- ReentrancyGuard.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatArchController.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketController.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketControllerFactory.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatSanctionsEscrow.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatSanctionsSentinel.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- BoolUtils.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- FIFOQueue.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- FeeMath.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- LibStoredInitCode.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- MarketState.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- MathUtils.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- SafeCastLib.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- Withdrawal.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarket.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketBase.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketConfig.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketToken.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketWithdrawals.sol ( #L2 ):
2: pragma solidity >=0.8.20;
The functions below take in an unbounded array, and make function calls for entries in the array. While the function will revert if it eventually runs out of gas, it may be a nicer user experience to require() that the length of the array is below some reasonable maximum, so that the user doesn't have to use up a full transaction's gas only to see that the transaction reverts.
There are 3 instances:
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++) {
The identifier is imported but never used within the file.
There are 3 instances:
/// @audit IChainalysisSanctionsList
5: import { IChainalysisSanctionsList } from './interfaces/IChainalysisSanctionsList.sol';
/// @audit SanctionsList
6: import { SanctionsList } from './libraries/Chainalysis.sol';
- WildcatSanctionsSentinel.sol ( #L7 ):
/// @audit SanctionsList
7: import { SanctionsList } from './libraries/Chainalysis.sol';
Declaring named returns, but not using them, is confusing to the reader. Consider either completely removing them (by declaring just the type without a name), or remove the return statement and do a variable assignment. This would improve the readability of the code, and it may also help reduce regressions during future code refactors.
There are 8 instances (click to show):
- WildcatMarketControllerFactory.sol ( #L165-L173 ):
/// @audit feeRecipient
/// @audit originationFeeAsset
/// @audit originationFeeAmount
/// @audit protocolFeeBips
165: function getProtocolFeeConfiguration()
166: external
167: view
168: returns (
169: address feeRecipient,
170: address originationFeeAsset,
171: uint80 originationFeeAmount,
172: uint16 protocolFeeBips
173: )
- WildcatSanctionsSentinel.sol ( #L65-L69 ):
/// @audit escrowAddress
65: function getEscrowAddress(
66: address borrower,
67: address account,
68: address asset
69: ) public view override returns (address escrowAddress) {
- FeeMath.sol ( #L19-L22 ):
/// @audit result
19: function calculateLinearInterestFromBips(
20: uint256 rateBip,
21: uint256 timeDelta
22: ) internal pure returns (uint256 result) {
- MarketState.sol ( #L87-L89 ):
/// @audit _liquidityRequired
87: function liquidityRequired(
88: MarketState memory state
89: ) internal pure returns (uint256 _liquidityRequired) {
- MathUtils.sol ( #L30-L33 ):
/// @audit result
30: function calculateLinearInterestFromBips(
31: uint256 rateBip,
32: uint256 timeDelta
33: ) internal pure returns (uint256 result) {
The delete
keyword more closely matches the semantics of what is being done, and draws more attention to the changing of state, which may lead to a more thorough audit of its associated logic.
There is 1 instance:
- WildcatSanctionsSentinel.sol ( #L57 ):
57: sanctionOverrides[msg.sender][account] = false;
The delete
keyword more closely matches the semantics of what is being done, and draws more attention to the changing of state, which may lead to a more thorough audit of its associated logic.
There are 5 instances:
144: state.annualInterestBips = 0;
146: state.reserveRatioBips = 0;
171: account.scaledBalance = 0;
431: state.pendingWithdrawalExpiry = 0;
489: state.pendingWithdrawalExpiry = 0;
Upgrading to the latest solidity version can optimize gas usage, take advantage of new features and improve overall contract efficiency. Where possible, based on compatibility requirements, it is recommended to use newer/latest solidity version to take advantage of the latest optimizations and features.
There are 19 instances (click to show):
- ReentrancyGuard.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatArchController.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketController.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketControllerFactory.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatSanctionsEscrow.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatSanctionsSentinel.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- BoolUtils.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- FIFOQueue.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- FeeMath.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- LibStoredInitCode.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- MarketState.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- MathUtils.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- SafeCastLib.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- Withdrawal.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarket.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketBase.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketConfig.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketToken.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketWithdrawals.sol ( #L2 ):
2: pragma solidity >=0.8.20;
Named mappings (with syntax mapping(KeyType KeyName? => ValueType ValueName?)
) are recommended.It can make the mapping variables clearer, more readable and easier to maintain.
There are 5 instances:
- WildcatMarketController.sol ( #L76 ):
76: mapping(address => TemporaryReserveRatio) public temporaryExcessReserveRatio;
- WildcatSanctionsSentinel.sol ( #L20 ):
20: mapping(address borrower => mapping(address account => bool sanctionOverride))
- WildcatMarketBase.sol ( #L68 ):
68: mapping(address => Account) internal _accounts;
13: mapping(address => mapping(address => uint256)) public allowance;
13: mapping(address => mapping(address => uint256)) public allowance;
See the Whitespace in Expressions section of the Solidity Style Guide.
There are 6 instances:
/// @audit Whitespace inside parenthesis
277: (state, , ) = _calculateCurrentState();
/// @audit Whitespace before a comma
277: (state, , ) = _calculateCurrentState();
/// @audit Whitespace inside parenthesis
17: (MarketState memory state, , ) = _calculateCurrentState();
/// @audit Whitespace before a comma
17: (MarketState memory state, , ) = _calculateCurrentState();
/// @audit Whitespace inside parenthesis
23: (MarketState memory state, , ) = _calculateCurrentState();
/// @audit Whitespace before a comma
23: (MarketState memory state, , ) = _calculateCurrentState();
If a function has too many parameters, replacing them with a struct can improve code readability and maintainability, increase reusability, and reduce the likelihood of errors when passing the parameters.
There are 3 instances (click to show):
- WildcatMarketController.sol ( #L291-L301, #L394-L402 ):
291: function deployMarket(
292: address asset,
293: string memory namePrefix,
294: string memory symbolPrefix,
295: uint128 maxTotalSupply,
296: uint16 annualInterestBips,
297: uint16 delinquencyFeeBips,
298: uint32 withdrawalBatchDuration,
299: uint16 reserveRatioBips,
300: uint32 delinquencyGracePeriod
301: ) external returns (address market) {
394: function enforceParameterConstraints(
395: string memory namePrefix,
396: string memory symbolPrefix,
397: uint16 annualInterestBips,
398: uint16 delinquencyFeeBips,
399: uint32 withdrawalBatchDuration,
400: uint16 reserveRatioBips,
401: uint32 delinquencyGracePeriod
402: ) internal view virtual {
- WildcatMarketControllerFactory.sol ( #L317-L327 ):
317: function deployControllerAndMarket(
318: string memory namePrefix,
319: string memory symbolPrefix,
320: address asset,
321: uint128 maxTotalSupply,
322: uint16 annualInterestBips,
323: uint16 delinquencyFeeBips,
324: uint32 withdrawalBatchDuration,
325: uint16 reserveRatioBips,
326: uint32 delinquencyGracePeriod
327: ) external returns (address controller, address market) {
If a function returns too many variables, replacing them with a struct can improve code readability, maintainability and reusability.
There is 1 instance:
- WildcatMarketControllerFactory.sol ( #L165-L173 ):
165: function getProtocolFeeConfiguration()
166: external
167: view
168: returns (
169: address feeRecipient,
170: address originationFeeAsset,
171: uint80 originationFeeAmount,
172: uint16 protocolFeeBips
173: )
It is often better to declare address
es as constant
or immutable
which can be assigned in constructor. This allows the code to remain the same across deployments on different networks, and avoids recompilation when addresses need to change.
There is 1 instance:
- Chainalysis.sol ( #L7 ):
7: 0x40C57923924B5c5c5455c48D93317139ADDaC8fb
This should especially be done if the new value is not required to be different from the old value.
There are 4 instances:
125: emit AuthorizationStatusUpdated(_account, account.approval);
143: emit MaxTotalSupplyUpdated(_maxTotalSupply);
158: emit AnnualInterestBipsUpdated(_annualInterestBips);
192: emit ReserveRatioBipsUpdated(_reserveRatioBips);
Consider adding some comments on non-public state variables to explain what they are supposed to do. This will help for future code reviews.
There are 35 instances (click to show):
11: EnumerableSet.AddressSet internal _markets;
12: EnumerableSet.AddressSet internal _controllerFactories;
13: EnumerableSet.AddressSet internal _borrowers;
14: EnumerableSet.AddressSet internal _controllers;
- WildcatMarketController.sol ( #L53, #L55, #L56, #L58, #L59, #L61, #L62, #L64, #L65, #L67, #L68, #L70, #L71 ):
53: uint256 internal immutable ownCreate2Prefix = LibStoredInitCode.getCreate2Prefix(address(this));
55: uint32 internal immutable MinimumDelinquencyGracePeriod;
56: uint32 internal immutable MaximumDelinquencyGracePeriod;
58: uint16 internal immutable MinimumReserveRatioBips;
59: uint16 internal immutable MaximumReserveRatioBips;
61: uint16 internal immutable MinimumDelinquencyFeeBips;
62: uint16 internal immutable MaximumDelinquencyFeeBips;
64: uint32 internal immutable MinimumWithdrawalBatchDuration;
65: uint32 internal immutable MaximumWithdrawalBatchDuration;
67: uint16 internal immutable MinimumAnnualInterestBips;
68: uint16 internal immutable MaximumAnnualInterestBips;
70: EnumerableSet.AddressSet internal _authorizedLenders;
71: EnumerableSet.AddressSet internal _controlledMarkets;
- WildcatMarketControllerFactory.sol ( #L44, #L46, #L47, #L49, #L50, #L52, #L53, #L55, #L56, #L58, #L59, #L61, #L63, #L244 ):
44: uint256 internal immutable ownCreate2Prefix = LibStoredInitCode.getCreate2Prefix(address(this));
46: uint32 internal immutable MinimumDelinquencyGracePeriod;
47: uint32 internal immutable MaximumDelinquencyGracePeriod;
49: uint16 internal immutable MinimumReserveRatioBips;
50: uint16 internal immutable MaximumReserveRatioBips;
52: uint16 internal immutable MinimumDelinquencyFeeBips;
53: uint16 internal immutable MaximumDelinquencyFeeBips;
55: uint32 internal immutable MinimumWithdrawalBatchDuration;
56: uint32 internal immutable MaximumWithdrawalBatchDuration;
58: uint16 internal immutable MinimumAnnualInterestBips;
59: uint16 internal immutable MaximumAnnualInterestBips;
61: ProtocolFeeConfiguration internal _protocolFeeConfiguration;
63: EnumerableSet.AddressSet internal _deployedControllers;
244: address internal _tmpMarketBorrowerParameter = address(1);
- WildcatSanctionsEscrow.sol ( #L14 ):
14: address internal immutable asset;
66: MarketState internal _state;
68: mapping(address => Account) internal _accounts;
70: WithdrawalData internal _withdrawalData;
It is recommended that Solidity contracts are fully annotated using NatSpec
There are 7 instances:
There are 7 instances:
41: modifier onlyControllerFactory() {
48: modifier onlyController() {
80: modifier onlyBorrower() {
87: modifier onlyControlledMarket(address market) {
- WildcatMarketControllerFactory.sol ( #L65 ):
65: modifier onlyArchControllerOwner() {
131: modifier onlyBorrower() {
136: modifier onlyController() {
Passing empty bytes to a function can cause unexpected behavior, such as certain operations failing, producing incorrect results, or wasting gas. It is recommended to check that all byte parameters are not empty.
There is 1 instance:
- LibStoredInitCode.sol ( #L7 ):
/// @audit data
7: function deployInitCode(bytes memory data) internal returns (address initCodeStorage) {
All external
/public
functions should extend an interface
. This is useful to ensure that the whole API is extracted and can be more easily integrated by other projects.
There are 93 instances (click to show):
- WildcatArchController.sol ( #L63, #L70, #L77, #L81, #L85-L88, #L98, #L106, #L113, #L120, #L124, #L128-L131, #L141, #L149, #L156, #L163, #L167, #L171-L174, #L184, #L192, #L199, #L206, #L210, #L214-L217, #L227 ):
63: function registerBorrower(address borrower) external onlyOwner {
70: function removeBorrower(address borrower) external onlyOwner {
77: function isRegisteredBorrower(address borrower) external view returns (bool) {
81: function getRegisteredBorrowers() external view returns (address[] memory) {
85: function getRegisteredBorrowers(
86: uint256 start,
87: uint256 end
88: ) external view returns (address[] memory arr) {
98: function getRegisteredBorrowersCount() external view returns (uint256) {
106: function registerControllerFactory(address factory) external onlyOwner {
113: function removeControllerFactory(address factory) external onlyOwner {
120: function isRegisteredControllerFactory(address factory) external view returns (bool) {
124: function getRegisteredControllerFactories() external view returns (address[] memory) {
128: function getRegisteredControllerFactories(
129: uint256 start,
130: uint256 end
131: ) external view returns (address[] memory arr) {
141: function getRegisteredControllerFactoriesCount() external view returns (uint256) {
149: function registerController(address controller) external onlyControllerFactory {
156: function removeController(address controller) external onlyOwner {
163: function isRegisteredController(address controller) external view returns (bool) {
167: function getRegisteredControllers() external view returns (address[] memory) {
171: function getRegisteredControllers(
172: uint256 start,
173: uint256 end
174: ) external view returns (address[] memory arr) {
184: function getRegisteredControllersCount() external view returns (uint256) {
192: function registerMarket(address market) external onlyController {
199: function removeMarket(address market) external onlyOwner {
206: function isRegisteredMarket(address market) external view returns (bool) {
210: function getRegisteredMarkets() external view returns (address[] memory) {
214: function getRegisteredMarkets(
215: uint256 start,
216: uint256 end
217: ) external view returns (address[] memory arr) {
227: function getRegisteredMarketsCount() external view returns (uint256) {
- WildcatMarketController.sol ( #L121, #L125-L128, #L138, #L142, #L153, #L169, #L182, #L196, #L200, #L204-L207, #L217, #L221-L225, #L238, #L291-L301, #L446-L449, #L468-L471, #L490 ):
121: function getAuthorizedLenders() external view returns (address[] memory) {
125: function getAuthorizedLenders(
126: uint256 start,
127: uint256 end
128: ) external view returns (address[] memory arr) {
138: function getAuthorizedLendersCount() external view returns (uint256) {
142: function isAuthorizedLender(address lender) external view virtual returns (bool) {
153: function authorizeLenders(address[] memory lenders) external onlyBorrower {
169: function deauthorizeLenders(address[] memory lenders) external onlyBorrower {
182: function updateLenderAuthorization(address lender, address[] memory markets) external {
196: function isControlledMarket(address market) external view returns (bool) {
200: function getControlledMarkets() external view returns (address[] memory) {
204: function getControlledMarkets(
205: uint256 start,
206: uint256 end
207: ) external view returns (address[] memory arr) {
217: function getControlledMarketsCount() external view returns (uint256) {
221: function computeMarketAddress(
222: address asset,
223: string memory namePrefix,
224: string memory symbolPrefix
225: ) external view returns (address) {
238: function getMarketParameters() external view returns (MarketParameters memory parameters) {
291: function deployMarket(
292: address asset,
293: string memory namePrefix,
294: string memory symbolPrefix,
295: uint128 maxTotalSupply,
296: uint16 annualInterestBips,
297: uint16 delinquencyFeeBips,
298: uint32 withdrawalBatchDuration,
299: uint16 reserveRatioBips,
300: uint32 delinquencyGracePeriod
301: ) external returns (address market) {
446: function getParameterConstraints()
447: external
448: view
449: returns (MarketParameterConstraints memory constraints)
468: function setAnnualInterestBips(
469: address market,
470: uint16 annualInterestBips
471: ) external virtual onlyBorrower onlyControlledMarket(market) {
490: function resetReserveRatio(address market) external virtual {
- WildcatMarketControllerFactory.sol ( #L126, #L130, #L134, #L138-L141, #L165-L168, #L195-L200, #L223-L226, #L246-L250, #L282, #L317-L327, #L342 ):
126: function isDeployedController(address controller) external view returns (bool) {
130: function getDeployedControllersCount() external view returns (uint256) {
134: function getDeployedControllers() external view returns (address[] memory) {
138: function getDeployedControllers(
139: uint256 start,
140: uint256 end
141: ) external view returns (address[] memory arr) {
165: function getProtocolFeeConfiguration()
166: external
167: view
168: returns (
195: function setProtocolFeeConfiguration(
196: address feeRecipient,
197: address originationFeeAsset,
198: uint80 originationFeeAmount,
199: uint16 protocolFeeBips
200: ) external onlyArchControllerOwner {
223: function getParameterConstraints()
224: external
225: view
226: returns (MarketParameterConstraints memory constraints)
246: function getMarketControllerParameters()
247: external
248: view
249: virtual
250: returns (MarketControllerParameters memory parameters)
282: function deployController() public returns (address controller) {
317: function deployControllerAndMarket(
318: string memory namePrefix,
319: string memory symbolPrefix,
320: address asset,
321: uint128 maxTotalSupply,
322: uint16 annualInterestBips,
323: uint16 delinquencyFeeBips,
324: uint32 withdrawalBatchDuration,
325: uint16 reserveRatioBips,
326: uint32 delinquencyGracePeriod
327: ) external returns (address controller, address market) {
342: function computeControllerAddress(address borrower) external view returns (address) {
26: function updateState() external nonReentrant {
42: function depositUpTo(
43: uint256 amount
44: ) public virtual nonReentrant returns (uint256 /* actualAmount */) {
86: function deposit(uint256 amount) external virtual {
96: function collectFees() external nonReentrant {
119: function borrow(uint256 amount) external onlyBorrower nonReentrant {
142: function closeMarket() external onlyController nonReentrant {
- WildcatMarketBase.sol ( #L223, #L231, #L238, #L252, #L260, #L267, #L276, #L285, #L292, #L299, #L307, #L318, #L334 ):
223: function coverageLiquidity() external view nonReentrantView returns (uint256) {
231: function scaleFactor() external view nonReentrantView returns (uint256) {
238: function totalAssets() public view returns (uint256) {
252: function borrowableAssets() external view nonReentrantView returns (uint256) {
260: function accruedProtocolFees() external view nonReentrantView returns (uint256) {
267: function previousState() external view returns (MarketState memory) {
276: function currentState() public view nonReentrantView returns (MarketState memory state) {
285: function scaledTotalSupply() external view nonReentrantView returns (uint256) {
292: function scaledBalanceOf(address account) external view nonReentrantView returns (uint256) {
299: function getAccountRole(address account) external view nonReentrantView returns (AuthRole) {
307: function withdrawableProtocolFees() external view returns (uint128) {
318: function effectiveBorrowerAPR() external view returns (uint256) {
334: function effectiveLenderAPR() external view returns (uint256) {
21: function maximumDeposit() external view returns (uint256) {
30: function maxTotalSupply() external view returns (uint256) {
38: function annualInterestBips() external view returns (uint256) {
42: function reserveRatioBips() external view returns (uint256) {
74: function nukeFromOrbit(address accountAddress) external nonReentrant {
88: function stunningReversal(address accountAddress) external nonReentrant {
112: function updateAccountAuthorization(
113: address _account,
114: bool _isAuthorized
115: ) external onlyController nonReentrant {
134: function setMaxTotalSupply(uint256 _maxTotalSupply) external onlyController nonReentrant {
149: function setAnnualInterestBips(uint16 _annualInterestBips) public onlyController nonReentrant {
171: function setReserveRatioBips(uint16 _reserveRatioBips) public onlyController nonReentrant {
16: function balanceOf(address account) public view virtual nonReentrantView returns (uint256) {
22: function totalSupply() external view virtual nonReentrantView returns (uint256) {
31: function approve(address spender, uint256 amount) external virtual nonReentrant returns (bool) {
36: function transfer(address to, uint256 amount) external virtual nonReentrant returns (bool) {
41: function transferFrom(
42: address from,
43: address to,
44: uint256 amount
45: ) external virtual nonReentrant returns (bool) {
24: function getUnpaidBatchExpiries() external view nonReentrantView returns (uint32[] memory) {
28: function getWithdrawalBatch(
29: uint32 expiry
30: ) external view nonReentrantView returns (WithdrawalBatch memory) {
38: function getAccountWithdrawalStatus(
39: address accountAddress,
40: uint32 expiry
41: ) external view nonReentrantView returns (AccountWithdrawalStatus memory) {
45: function getAvailableWithdrawalAmount(
46: address accountAddress,
47: uint32 expiry
48: ) external view nonReentrantView returns (uint256) {
77: function queueWithdrawal(uint256 amount) external nonReentrant {
137: function executeWithdrawal(
138: address accountAddress,
139: uint32 expiry
140: ) external nonReentrant returns (uint256) {
190: function processUnpaidWithdrawalBatch() external nonReentrant {
In Solidity, while function overriding allows for functions with the same name to coexist, it is advisable to avoid this practice to enhance code readability and maintainability. Having multiple functions with the same name, even with different parameters or in inherited contracts, can cause confusion and increase the likelihood of errors during development, testing, and debugging. Using distinct and descriptive function names not only clarifies the purpose and behavior of each function, but also helps prevent unintended function calls or incorrect overriding. By adopting a clear and consistent naming convention, developers can create more comprehensible and maintainable smart contracts.
There are 9 instances (click to show):
/// @audit Different function with same name found on line 81
85: function getRegisteredBorrowers(
/// @audit Different function with same name found on line 124
128: function getRegisteredControllerFactories(
/// @audit Different function with same name found on line 167
171: function getRegisteredControllers(
/// @audit Different function with same name found on line 210
214: function getRegisteredMarkets(
/// @audit Different function with same name found on line 121
125: function getAuthorizedLenders(
/// @audit Different function with same name found on line 200
204: function getControlledMarkets(
- WildcatMarketControllerFactory.sol ( #L138 ):
/// @audit Different function with same name found on line 134
138: function getDeployedControllers(
/// @audit Different function with same name found on line 83
87: function createWithStoredInitCode(
/// @audit Different function with same name found on line 99
106: function create2WithStoredInitCode(
Writing data to the free memory pointer without later updating the free memory pointer will cause there to be dirty bits at that memory location. Not updating the free memory pointer will make it harder for the optimizer to reason about whether the memory needs to be cleaned before use, which will lead to worse optimizations. Update the free memory pointer and annotate the block (assembly ("memory-safe") { ... }
) to avoid this issue.
There are 7 instances (click to show):
- WildcatMarketController.sol ( #L375-L385 ):
375: assembly {
376: // Cache free memory pointer
377: let freeMemoryPointer := mload(0x40)
378: // `keccak256(abi.encode(asset, keccak256(namePrefix), keccak256(symbolPrefix)))`
379: mstore(0x00, asset)
380: mstore(0x20, keccak256(add(namePrefix, 32), mload(namePrefix)))
381: mstore(0x40, keccak256(add(symbolPrefix, 32), mload(symbolPrefix)))
382: salt := keccak256(0, 0x60)
383: // Restore free memory pointer
384: mstore(0x40, freeMemoryPointer)
385: }
- LibStoredInitCode.sol ( #L59-L80, #L91-L96, #L111-L116 ):
59: assembly {
60: // Cache the free memory pointer so it can be restored
61: // at the end
62: let ptr := mload(0x40)
63:
64: // Write 0xff + address to bytes 11:32
65: mstore(0x00, create2Prefix)
66:
67: // Write salt to bytes 32:64
68: mstore(0x20, salt)
69:
70: // Write initcode hash to bytes 64:96
71: mstore(0x40, initCodeHash)
72:
73: // Calculate create2 hash for token0, token1
74: // The EVM only looks at the last 20 bytes, so the dirty
75: // bits at the beginning do not need to be cleaned
76: create2Address := keccak256(0x0b, 0x55)
77:
78: // Restore the free memory pointer
79: mstore(0x40, ptr)
80: }
91: assembly {
92: let initCodePointer := mload(0x40)
93: let initCodeSize := sub(extcodesize(initCodeStorage), 1)
94: extcodecopy(initCodeStorage, initCodePointer, 1, initCodeSize)
95: deployment := create(value, initCodePointer, initCodeSize)
96: }
111: assembly {
112: let initCodePointer := mload(0x40)
113: let initCodeSize := sub(extcodesize(initCodeStorage), 1)
114: extcodecopy(initCodeStorage, initCodePointer, 1, initCodeSize)
115: deployment := create2(value, initCodePointer, initCodeSize, salt)
116: }
25: assembly {
26: str := mload(0x40)
27: mstore(0x40, add(str, 0x40))
28: mstore(str, size)
29: mstore(add(str, 0x20), value)
30: }
75: assembly {
76: str := mload(0x40)
77: mstore(0x40, add(str, 0x40))
78: mstore(str, size)
79: mstore(add(str, 0x20), value)
80: }
83: assembly {
84: str := mload(0x40)
85: // Get allocation size for the string including the length and data.
86: // Rounding down returndatasize to nearest word because the returndata
87: // has an extra offset word.
88: let allocSize := and(sub(returndatasize(), 1), OnlyFullWordMask)
89: mstore(0x40, add(str, allocSize))
90: // Copy returndata after the offset
91: returndatacopy(str, 0x20, sub(returndatasize(), 0x20))
92: }
Refer to the Solidity style guide - Control Structures.
There are 16 instances (click to show):
- WildcatMarketController.sol ( #L446-L450 ):
446: function getParameterConstraints()
447: external
448: view
449: returns (MarketParameterConstraints memory constraints)
450: {
- WildcatMarketControllerFactory.sol ( #L79, #L106-L110, #L116-L120, #L165-L174, #L204, #L223-L227, #L246-L251 ):
79: if (
106: function _storeControllerInitCode()
107: internal
108: virtual
109: returns (address initCodeStorage, uint256 initCodeHash)
110: {
116: function _storeMarketInitCode()
117: internal
118: virtual
119: returns (address initCodeStorage, uint256 initCodeHash)
120: {
165: function getProtocolFeeConfiguration()
166: external
167: view
168: returns (
169: address feeRecipient,
170: address originationFeeAsset,
171: uint80 originationFeeAmount,
172: uint16 protocolFeeBips
173: )
174: {
204: if (
223: function getParameterConstraints()
224: external
225: view
226: returns (MarketParameterConstraints memory constraints)
227: {
246: function getMarketControllerParameters()
247: external
248: view
249: virtual
250: returns (MarketControllerParameters memory parameters)
251: {
- WildcatSanctionsEscrow.sol ( #L34 ):
34: if (!canReleaseEscrow()) revert CanNotReleaseEscrow();
- WildcatSanctionsSentinel.sol ( #L106 ):
106: if (escrowContract.codehash != bytes32(0)) return escrowContract;
- FeeMath.sol ( #L142-L152 ):
142: function updateScaleFactorAndFees(
143: MarketState memory state,
144: uint256 protocolFeeBips,
145: uint256 delinquencyFeeBips,
146: uint256 delinquencyGracePeriod,
147: uint256 timestamp
148: )
149: internal
150: pure
151: returns (uint256 baseInterestRay, uint256 delinquencyFeeRay, uint256 protocolFee)
152: {
10: contract WildcatMarket is
11: WildcatMarketBase,
12: WildcatMarketConfig,
13: WildcatMarketToken,
14: WildcatMarketWithdrawals
15: {
57: if (scaledAmount == 0) revert NullMintAmount();
- WildcatMarketBase.sol ( #L132, #L137, #L399-L407 ):
132: if (msg.sender != borrower) revert NotApprovedBorrower();
137: if (msg.sender != controller) revert NotController();
399: function _calculateCurrentState()
400: internal
401: view
402: returns (
403: MarketState memory state,
404: uint32 expiredBatchExpiry,
405: WithdrawalBatch memory expiredBatch
406: )
407: {
The functions below have the same implementation as is seen in other files. The functions should be refactored into functions of a common base contract.
There are 4 instances (click to show):
- WildcatMarketController.sol ( #L446-L461 ):
/// @audit Seen on line 223 of src/WildcatMarketControllerFactory.sol
446: function getParameterConstraints()
447: external
448: view
449: returns (MarketParameterConstraints memory constraints)
450: {
451: constraints.minimumDelinquencyGracePeriod = MinimumDelinquencyGracePeriod;
452: constraints.maximumDelinquencyGracePeriod = MaximumDelinquencyGracePeriod;
453: constraints.minimumReserveRatioBips = MinimumReserveRatioBips;
454: constraints.maximumReserveRatioBips = MaximumReserveRatioBips;
455: constraints.minimumDelinquencyFeeBips = MinimumDelinquencyFeeBips;
456: constraints.maximumDelinquencyFeeBips = MaximumDelinquencyFeeBips;
457: constraints.minimumWithdrawalBatchDuration = MinimumWithdrawalBatchDuration;
458: constraints.maximumWithdrawalBatchDuration = MaximumWithdrawalBatchDuration;
459: constraints.minimumAnnualInterestBips = MinimumAnnualInterestBips;
460: constraints.maximumAnnualInterestBips = MaximumAnnualInterestBips;
461: }
- WildcatMarketControllerFactory.sol ( #L223-L238 ):
/// @audit Seen on line 446 of src/WildcatMarketController.sol
223: function getParameterConstraints()
224: external
225: view
226: returns (MarketParameterConstraints memory constraints)
227: {
228: constraints.minimumDelinquencyGracePeriod = MinimumDelinquencyGracePeriod;
229: constraints.maximumDelinquencyGracePeriod = MaximumDelinquencyGracePeriod;
230: constraints.minimumReserveRatioBips = MinimumReserveRatioBips;
231: constraints.maximumReserveRatioBips = MaximumReserveRatioBips;
232: constraints.minimumDelinquencyFeeBips = MinimumDelinquencyFeeBips;
233: constraints.maximumDelinquencyFeeBips = MaximumDelinquencyFeeBips;
234: constraints.minimumWithdrawalBatchDuration = MinimumWithdrawalBatchDuration;
235: constraints.maximumWithdrawalBatchDuration = MaximumWithdrawalBatchDuration;
236: constraints.minimumAnnualInterestBips = MinimumAnnualInterestBips;
237: constraints.maximumAnnualInterestBips = MaximumAnnualInterestBips;
238: }
- FeeMath.sol ( #L19-L28 ):
/// @audit Seen on line 30 of src/libraries/MathUtils.sol
19: function calculateLinearInterestFromBips(
20: uint256 rateBip,
21: uint256 timeDelta
22: ) internal pure returns (uint256 result) {
23: uint256 rate = rateBip.bipToRay();
24: uint256 accumulatedInterestRay = rate * timeDelta;
25: unchecked {
26: return accumulatedInterestRay / SECONDS_IN_365_DAYS;
27: }
28: }
- MathUtils.sol ( #L30-L39 ):
/// @audit Seen on line 19 of src/libraries/FeeMath.sol
30: function calculateLinearInterestFromBips(
31: uint256 rateBip,
32: uint256 timeDelta
33: ) internal pure returns (uint256 result) {
34: uint256 rate = rateBip.bipToRay();
35: uint256 accumulatedInterestRay = rate * timeDelta;
36: unchecked {
37: return accumulatedInterestRay / SECONDS_IN_365_DAYS;
38: }
39: }
Events should be emitted when critical changes are made to the contracts.
There are 2 instances:
- WildcatMarketController.sol ( #L468-L471 ):
468: function setAnnualInterestBips(
469: address market,
470: uint16 annualInterestBips
471: ) external virtual onlyBorrower onlyControlledMarket(market) {
- WildcatMarketControllerFactory.sol ( #L195-L200 ):
195: function setProtocolFeeConfiguration(
196: address feeRecipient,
197: address originationFeeAsset,
198: uint80 originationFeeAmount,
199: uint16 protocolFeeBips
200: ) external onlyArchControllerOwner {
Adding a way to quickly halt protocol functionality in an emergency, rather than having to pause individual contracts one-by-one, will make in-progress hack mitigation faster and much less stressful.
There are 3 instances:
- WildcatArchController.sol ( #L8 ):
8: contract WildcatArchController is Ownable {
- WildcatMarketController.sol ( #L32 ):
32: contract WildcatMarketController is IWildcatMarketControllerEventsAndErrors {
- WildcatMarketControllerFactory.sol ( #L13 ):
13: contract WildcatMarketControllerFactory {
Use alternative variants, e.g. allowlist/denylist instead of whitelist/blacklist.
There are 2 instances:
26: /// @dev Account with blacklist control, used for blocking sanctioned addresses.
153: revert AccountBlacklisted();
Doing so will significantly increase centralization, but will help to prevent hackers from using stolen tokens
There are 11 instances (click to show):
- ReentrancyGuard.sol ( #L12 ):
12: contract ReentrancyGuard {
- WildcatArchController.sol ( #L8 ):
8: contract WildcatArchController is Ownable {
- WildcatMarketController.sol ( #L32 ):
32: contract WildcatMarketController is IWildcatMarketControllerEventsAndErrors {
- WildcatMarketControllerFactory.sol ( #L13 ):
13: contract WildcatMarketControllerFactory {
- WildcatSanctionsEscrow.sol ( #L10 ):
10: contract WildcatSanctionsEscrow is IWildcatSanctionsEscrow {
- WildcatSanctionsSentinel.sol ( #L10 ):
10: contract WildcatSanctionsSentinel is IWildcatSanctionsSentinel {
- WildcatMarket.sol ( #L10-L15 ):
10: contract WildcatMarket is
11: WildcatMarketBase,
12: WildcatMarketConfig,
13: WildcatMarketToken,
14: WildcatMarketWithdrawals
15: {
- WildcatMarketBase.sol ( #L14 ):
14: contract WildcatMarketBase is ReentrancyGuard, IMarketEventsAndErrors {
- WildcatMarketConfig.sol ( #L9 ):
9: contract WildcatMarketConfig is WildcatMarketBase {
- WildcatMarketToken.sol ( #L6 ):
6: contract WildcatMarketToken is WildcatMarketBase {
- WildcatMarketWithdrawals.sol ( #L11 ):
11: contract WildcatMarketWithdrawals is WildcatMarketBase {
The IR-based code generator was introduced with an aim to not only allow code generation to be more transparent and auditable but also to enable more powerful optimization passes that span across functions.
You can enable it on the command line using --via-ir
or with the option {"viaIR": true}
.
This will take longer to compile, but you can just simple test it before deploying and if you got a better benchmark then you can add --via-ir to your deploy command
More on: https://docs.soliditylang.org/en/v0.8.17/ir-breaking-changes.html
There is 1 instance:
- Global finding
The @dev
tag is used to explain extra details to developers.
There are 19 instances (click to show):
- ReentrancyGuard.sol ( #L12 ):
12: contract ReentrancyGuard {
- WildcatArchController.sol ( #L8 ):
8: contract WildcatArchController is Ownable {
- WildcatMarketController.sol ( #L32 ):
32: contract WildcatMarketController is IWildcatMarketControllerEventsAndErrors {
- WildcatMarketControllerFactory.sol ( #L13 ):
13: contract WildcatMarketControllerFactory {
- WildcatSanctionsEscrow.sol ( #L10 ):
10: contract WildcatSanctionsEscrow is IWildcatSanctionsEscrow {
- WildcatSanctionsSentinel.sol ( #L10 ):
10: contract WildcatSanctionsSentinel is IWildcatSanctionsSentinel {
- BoolUtils.sol ( #L4 ):
4: library BoolUtils {
- FIFOQueue.sol ( #L16 ):
16: library FIFOQueueLib {
- FeeMath.sol ( #L11 ):
11: library FeeMath {
- LibStoredInitCode.sol ( #L4 ):
4: library LibStoredInitCode {
- MarketState.sol ( #L44 ):
44: library MarketStateLib {
- MathUtils.sol ( #L16 ):
16: library MathUtils {
- SafeCastLib.sol ( #L6 ):
6: library SafeCastLib {
- Withdrawal.sol ( #L37 ):
37: library WithdrawalLib {
- WildcatMarket.sol ( #L10 ):
10: contract WildcatMarket is
- WildcatMarketBase.sol ( #L14 ):
14: contract WildcatMarketBase is ReentrancyGuard, IMarketEventsAndErrors {
- WildcatMarketConfig.sol ( #L9 ):
9: contract WildcatMarketConfig is WildcatMarketBase {
- WildcatMarketToken.sol ( #L6 ):
6: contract WildcatMarketToken is WildcatMarketBase {
- WildcatMarketWithdrawals.sol ( #L11 ):
11: contract WildcatMarketWithdrawals is WildcatMarketBase {
There are 19 instances (click to show):
16: error NotControllerFactory();
17: error NotController();
19: error BorrowerAlreadyExists();
20: error ControllerFactoryAlreadyExists();
21: error ControllerAlreadyExists();
22: error MarketAlreadyExists();
24: error BorrowerDoesNotExist();
25: error ControllerFactoryDoesNotExist();
26: error ControllerDoesNotExist();
27: error MarketDoesNotExist();
24: error NotRegisteredBorrower();
25: error InvalidProtocolFeeConfiguration();
26: error CallerNotArchControllerOwner();
27: error InvalidConstraints();
28: error ControllerAlreadyDeployed();
- FIFOQueue.sol ( #L17 ):
17: error FIFOQueueOutOfBounds();
- LibStoredInitCode.sol ( #L5 ):
5: error InitCodeDeploymentFailed();
16: error InvalidReturnDataString();
17: error InvalidCompactString();
The @notice
is used to explain to users what the function does. The compiler interprets ///
or /**
comments as this tag if one wasn't explicitly provided.
There are 124 instances (click to show):
- WildcatArchController.sol ( #L55, #L63, #L70, #L77, #L81, #L85, #L98, #L106, #L113, #L120, #L124, #L128, #L141, #L149, #L156, #L163, #L167, #L171, #L184, #L192, #L199, #L206, #L210, #L214, #L227 ):
55: constructor() {
63: function registerBorrower(address borrower) external onlyOwner {
70: function removeBorrower(address borrower) external onlyOwner {
77: function isRegisteredBorrower(address borrower) external view returns (bool) {
81: function getRegisteredBorrowers() external view returns (address[] memory) {
85: function getRegisteredBorrowers(
98: function getRegisteredBorrowersCount() external view returns (uint256) {
106: function registerControllerFactory(address factory) external onlyOwner {
113: function removeControllerFactory(address factory) external onlyOwner {
120: function isRegisteredControllerFactory(address factory) external view returns (bool) {
124: function getRegisteredControllerFactories() external view returns (address[] memory) {
128: function getRegisteredControllerFactories(
141: function getRegisteredControllerFactoriesCount() external view returns (uint256) {
149: function registerController(address controller) external onlyControllerFactory {
156: function removeController(address controller) external onlyOwner {
163: function isRegisteredController(address controller) external view returns (bool) {
167: function getRegisteredControllers() external view returns (address[] memory) {
171: function getRegisteredControllers(
184: function getRegisteredControllersCount() external view returns (uint256) {
192: function registerMarket(address market) external onlyController {
199: function removeMarket(address market) external onlyOwner {
206: function isRegisteredMarket(address market) external view returns (bool) {
210: function getRegisteredMarkets() external view returns (address[] memory) {
214: function getRegisteredMarkets(
227: function getRegisteredMarketsCount() external view returns (uint256) {
- WildcatMarketController.sol ( #L94, #L125, #L138, #L142, #L196, #L200, #L204, #L217, #L221, #L255, #L490, #L503 ):
94: constructor() {
125: function getAuthorizedLenders(
138: function getAuthorizedLendersCount() external view returns (uint256) {
142: function isAuthorizedLender(address lender) external view virtual returns (bool) {
196: function isControlledMarket(address market) external view returns (bool) {
200: function getControlledMarkets() external view returns (address[] memory) {
204: function getControlledMarkets(
217: function getControlledMarketsCount() external view returns (uint256) {
221: function computeMarketAddress(
255: function _resetTmpMarketParameters() internal {
490: function resetReserveRatio(address market) external virtual {
503: function assertValueInRange(
- WildcatMarketControllerFactory.sol ( #L72, #L106, #L116, #L126, #L130, #L134, #L138, #L246, #L342 ):
72: constructor(
106: function _storeControllerInitCode()
116: function _storeMarketInitCode()
126: function isDeployedController(address controller) external view returns (bool) {
130: function getDeployedControllersCount() external view returns (uint256) {
134: function getDeployedControllers() external view returns (address[] memory) {
138: function getDeployedControllers(
246: function getMarketControllerParameters()
342: function computeControllerAddress(address borrower) external view returns (address) {
16: constructor() {
21: function balance() public view override returns (uint256) {
25: function canReleaseEscrow() public view override returns (bool) {
29: function escrowedAsset() public view override returns (address, uint256) {
33: function releaseEscrow() public override {
24: constructor(address _archController, address _chainalysisSanctionsList) {
30: function _resetTmpEscrowParams() internal {
5: function and(bool a, bool b) internal pure returns (bool c) {
11: function or(bool a, bool b) internal pure returns (bool c) {
17: function xor(bool a, bool b) internal pure returns (bool c) {
19: function empty(FIFOQueue storage arr) internal view returns (bool) {
23: function first(FIFOQueue storage arr) internal view returns (uint32) {
30: function at(FIFOQueue storage arr, uint256 index) internal view returns (uint32) {
38: function length(FIFOQueue storage arr) internal view returns (uint128) {
42: function values(FIFOQueue storage arr) internal view returns (uint32[] memory _values) {
55: function push(FIFOQueue storage arr, uint32 value) internal {
61: function shift(FIFOQueue storage arr) internal {
70: function shiftN(FIFOQueue storage arr, uint128 n) internal {
30: function calculateBaseInterest(
40: function applyProtocolFee(
53: function updateDelinquency(
7: function deployInitCode(bytes memory data) internal returns (address initCodeStorage) {
54: function calculateCreate2Address(
83: function createWithStoredInitCode(address initCodeStorage) internal returns (address deployment) {
87: function createWithStoredInitCode(
99: function create2WithStoredInitCode(
106: function create2WithStoredInitCode(
130: function hasPendingExpiredBatch(MarketState memory state) internal view returns (bool result) {
138: function totalDebts(MarketState memory state) internal pure returns (uint256) {
- SafeCastLib.sol ( #L7, #L17, #L21, #L25, #L29, #L33, #L37, #L41, #L45, #L49, #L53, #L57, #L61, #L65, #L69, #L73, #L77, #L81, #L85, #L89, #L93, #L97, #L101, #L105, #L109, #L113, #L117, #L121, #L125, #L129, #L133, #L137 ):
7: function _assertNonOverflow(bool didNotOverflow) private pure {
17: function toUint8(uint256 x) internal pure returns (uint8 y) {
21: function toUint16(uint256 x) internal pure returns (uint16 y) {
25: function toUint24(uint256 x) internal pure returns (uint24 y) {
29: function toUint32(uint256 x) internal pure returns (uint32 y) {
33: function toUint40(uint256 x) internal pure returns (uint40 y) {
37: function toUint48(uint256 x) internal pure returns (uint48 y) {
41: function toUint56(uint256 x) internal pure returns (uint56 y) {
45: function toUint64(uint256 x) internal pure returns (uint64 y) {
49: function toUint72(uint256 x) internal pure returns (uint72 y) {
53: function toUint80(uint256 x) internal pure returns (uint80 y) {
57: function toUint88(uint256 x) internal pure returns (uint88 y) {
61: function toUint96(uint256 x) internal pure returns (uint96 y) {
65: function toUint104(uint256 x) internal pure returns (uint104 y) {
69: function toUint112(uint256 x) internal pure returns (uint112 y) {
73: function toUint120(uint256 x) internal pure returns (uint120 y) {
77: function toUint128(uint256 x) internal pure returns (uint128 y) {
81: function toUint136(uint256 x) internal pure returns (uint136 y) {
85: function toUint144(uint256 x) internal pure returns (uint144 y) {
89: function toUint152(uint256 x) internal pure returns (uint152 y) {
93: function toUint160(uint256 x) internal pure returns (uint160 y) {
97: function toUint168(uint256 x) internal pure returns (uint168 y) {
101: function toUint176(uint256 x) internal pure returns (uint176 y) {
105: function toUint184(uint256 x) internal pure returns (uint184 y) {
109: function toUint192(uint256 x) internal pure returns (uint192 y) {
113: function toUint200(uint256 x) internal pure returns (uint200 y) {
117: function toUint208(uint256 x) internal pure returns (uint208 y) {
121: function toUint216(uint256 x) internal pure returns (uint216 y) {
125: function toUint224(uint256 x) internal pure returns (uint224 y) {
129: function toUint232(uint256 x) internal pure returns (uint232 y) {
133: function toUint240(uint256 x) internal pure returns (uint240 y) {
137: function toUint248(uint256 x) internal pure returns (uint248 y) {
19: function bytes32ToString(bytes32 value) pure returns (string memory str) {
33: function queryStringOrBytes32AsString(
96: function queryName(address target) view returns (string memory) {
101: function querySymbol(address target) view returns (string memory) {
- Withdrawal.sol ( #L38 ):
38: function scaledOwedAmount(WithdrawalBatch memory batch) internal pure returns (uint104) {
76: constructor() {
528: function _applyWithdrawalBatchPaymentView(
- WildcatMarketConfig.sol ( #L42 ):
42: function reserveRatioBips() external view returns (uint256) {
31: function approve(address spender, uint256 amount) external virtual nonReentrant returns (bool) {
36: function transfer(address to, uint256 amount) external virtual nonReentrant returns (bool) {
41: function transferFrom(
59: function _approve(address approver, address spender, uint256 amount) internal virtual {
64: function _transfer(address from, address to, uint256 amount) internal virtual {
28: function getWithdrawalBatch(
38: function getAccountWithdrawalStatus(
45: function getAvailableWithdrawalAmount(
190: function processUnpaidWithdrawalBatch() external nonReentrant {
While 100% code coverage does not guarantee that there are no bugs, it often will catch easy-to-find bugs, and will ensure that there are fewer regressions when the code invariably has to be modified. Furthermore, in order to get full coverage, code authors will often have to re-organize their code so that it is more modular, so that each component can be tested separately, which reduces interdependencies between modules and layers, and makes for code that is easier to reason about and audit.
There is 1 instance:
- Global finding
This includes: large code bases, or code with lots of inline-assembly, complicated math, or complicated interactions between multiple contracts. Invariant fuzzers such as Echidna require the test writer to come up with invariants which should not be violated under any circumstances, and the fuzzer tests various inputs and function calls to ensure that the invariants always hold. Even code with 100% code coverage can still have bugs due to the order of the operations a user performs, and invariant fuzzers may help significantly.
There is 1 instance:
- Global finding
There are 145 instances (click to show):
- WildcatArchController.sol ( #L2-L4, #L6-L8, #L46-L48, #L68-L70, #L75-L77, #L79-L81, #L83-L85, #L96-L98, #L111-L113, #L118-L120, #L122-L124, #L126-L128, #L139-L141, #L154-L156, #L161-L163, #L165-L167, #L169-L171, #L182-L184, #L197-L199, #L204-L206, #L208-L210, #L212-L214, #L225-L227 ):
2: pragma solidity >=0.8.20;
3:
4: import { EnumerableSet } from 'openzeppelin/contracts/utils/structs/EnumerableSet.sol';
6: import './libraries/MathUtils.sol';
7:
8: contract WildcatArchController is Ownable {
46: }
47:
48: modifier onlyController() {
68: }
69:
70: function removeBorrower(address borrower) external onlyOwner {
75: }
76:
77: function isRegisteredBorrower(address borrower) external view returns (bool) {
79: }
80:
81: function getRegisteredBorrowers() external view returns (address[] memory) {
83: }
84:
85: function getRegisteredBorrowers(
96: }
97:
98: function getRegisteredBorrowersCount() external view returns (uint256) {
111: }
112:
113: function removeControllerFactory(address factory) external onlyOwner {
118: }
119:
120: function isRegisteredControllerFactory(address factory) external view returns (bool) {
122: }
123:
124: function getRegisteredControllerFactories() external view returns (address[] memory) {
126: }
127:
128: function getRegisteredControllerFactories(
139: }
140:
141: function getRegisteredControllerFactoriesCount() external view returns (uint256) {
154: }
155:
156: function removeController(address controller) external onlyOwner {
161: }
162:
163: function isRegisteredController(address controller) external view returns (bool) {
165: }
166:
167: function getRegisteredControllers() external view returns (address[] memory) {
169: }
170:
171: function getRegisteredControllers(
182: }
183:
184: function getRegisteredControllersCount() external view returns (uint256) {
197: }
198:
199: function removeMarket(address market) external onlyOwner {
204: }
205:
206: function isRegisteredMarket(address market) external view returns (bool) {
208: }
209:
210: function getRegisteredMarkets() external view returns (address[] memory) {
212: }
213:
214: function getRegisteredMarkets(
225: }
226:
227: function getRegisteredMarketsCount() external view returns (uint256) {
- WildcatMarketController.sol ( #L2-L4, #L30-L32, #L85-L87, #L123-L125, #L136-L138, #L140-L142, #L198-L200, #L202-L204, #L215-L217, #L219-L221, #L253-L255, #L488-L490, #L501-L503 ):
2: pragma solidity >=0.8.20;
3:
4: import { EnumerableSet } from 'openzeppelin/contracts/utils/structs/EnumerableSet.sol';
30: }
31:
32: contract WildcatMarketController is IWildcatMarketControllerEventsAndErrors {
85: }
86:
87: modifier onlyControlledMarket(address market) {
123: }
124:
125: function getAuthorizedLenders(
136: }
137:
138: function getAuthorizedLendersCount() external view returns (uint256) {
140: }
141:
142: function isAuthorizedLender(address lender) external view virtual returns (bool) {
198: }
199:
200: function getControlledMarkets() external view returns (address[] memory) {
202: }
203:
204: function getControlledMarkets(
215: }
216:
217: function getControlledMarketsCount() external view returns (uint256) {
219: }
220:
221: function computeMarketAddress(
253: }
254:
255: function _resetTmpMarketParameters() internal {
488: }
489:
490: function resetReserveRatio(address market) external virtual {
501: }
502:
503: function assertValueInRange(
- WildcatMarketControllerFactory.sol ( #L2-L4, #L11-L13, #L104-L106, #L114-L116, #L124-L126, #L128-L130, #L132-L134, #L136-L138, #L340-L342 ):
2: pragma solidity >=0.8.20;
3:
4: import { EnumerableSet } from 'openzeppelin/contracts/utils/structs/EnumerableSet.sol';
11: import './WildcatMarketController.sol';
12:
13: contract WildcatMarketControllerFactory {
104: }
105:
106: function _storeControllerInitCode()
114: }
115:
116: function _storeMarketInitCode()
124: }
125:
126: function isDeployedController(address controller) external view returns (bool) {
128: }
129:
130: function getDeployedControllersCount() external view returns (uint256) {
132: }
133:
134: function getDeployedControllers() external view returns (address[] memory) {
136: }
137:
138: function getDeployedControllers(
340: }
341:
342: function computeControllerAddress(address borrower) external view returns (address) {
2: pragma solidity >=0.8.20;
3:
4: import { IERC20 } from './interfaces/IERC20.sol';
8: import { IWildcatSanctionsEscrow } from './interfaces/IWildcatSanctionsEscrow.sol';
9:
10: contract WildcatSanctionsEscrow is IWildcatSanctionsEscrow {
19: }
20:
21: function balance() public view override returns (uint256) {
23: }
24:
25: function canReleaseEscrow() public view override returns (bool) {
27: }
28:
29: function escrowedAsset() public view override returns (address, uint256) {
31: }
32:
33: function releaseEscrow() public override {
2: pragma solidity >=0.8.20;
3:
4: import { IChainalysisSanctionsList } from './interfaces/IChainalysisSanctionsList.sol';
8: import { WildcatSanctionsEscrow } from './WildcatSanctionsEscrow.sol';
9:
10: contract WildcatSanctionsSentinel is IWildcatSanctionsSentinel {
28: }
29:
30: function _resetTmpEscrowParams() internal {
2: pragma solidity >=0.8.20;
3:
4: library BoolUtils {
2: pragma solidity >=0.8.20;
3:
4: library BoolUtils {
9: }
10:
11: function or(bool a, bool b) internal pure returns (bool c) {
15: }
16:
17: function xor(bool a, bool b) internal pure returns (bool c) {
- Chainalysis.sol ( #L2-L4 ):
2: pragma solidity ^0.8.20;
3:
4: import '../interfaces/IChainalysisSanctionsList.sol';
14: using FIFOQueueLib for FIFOQueue global;
15:
16: library FIFOQueueLib {
21: }
22:
23: function first(FIFOQueue storage arr) internal view returns (uint32) {
28: }
29:
30: function at(FIFOQueue storage arr, uint256 index) internal view returns (uint32) {
36: }
37:
38: function length(FIFOQueue storage arr) internal view returns (uint128) {
40: }
41:
42: function values(FIFOQueue storage arr) internal view returns (uint32[] memory _values) {
53: }
54:
55: function push(FIFOQueue storage arr, uint32 value) internal {
59: }
60:
61: function shift(FIFOQueue storage arr) internal {
68: }
69:
70: function shiftN(FIFOQueue storage arr, uint128 n) internal {
2: pragma solidity >=0.8.20;
3:
4: import './MathUtils.sol';
9: using MathUtils for uint256;
10:
11: library FeeMath {
28: }
29:
30: function calculateBaseInterest(
38: }
39:
40: function applyProtocolFee(
51: }
52:
53: function updateDelinquency(
2: pragma solidity >=0.8.20;
3:
4: library LibStoredInitCode {
2: pragma solidity >=0.8.20;
3:
4: library LibStoredInitCode {
52: }
53:
54: function calculateCreate2Address(
81: }
82:
83: function createWithStoredInitCode(address initCodeStorage) internal returns (address deployment) {
85: }
86:
87: function createWithStoredInitCode(
97: }
98:
99: function create2WithStoredInitCode(
104: }
105:
106: function create2WithStoredInitCode(
- MarketState.sol ( #L2-L4, #L42-L44, #L128-L130, #L136-L138 ):
2: pragma solidity >=0.8.20;
3:
4: import { AuthRole } from '../interfaces/WildcatStructsAndEnums.sol';
42: }
43:
44: library MarketStateLib {
128: }
129:
130: function hasPendingExpiredBatch(MarketState memory state) internal view returns (bool result) {
136: }
137:
138: function totalDebts(MarketState memory state) internal pure returns (uint256) {
2: pragma solidity >=0.8.20;
3:
4: import './Errors.sol';
14: uint256 constant SECONDS_IN_365_DAYS = 365 days;
15:
16: library MathUtils {
- SafeCastLib.sol ( #L2-L4, #L4-L6, #L15-L17, #L19-L21, #L23-L25, #L27-L29, #L31-L33, #L35-L37, #L39-L41, #L43-L45, #L47-L49, #L51-L53, #L55-L57, #L59-L61, #L63-L65, #L67-L69, #L71-L73, #L75-L77, #L79-L81, #L83-L85, #L87-L89, #L91-L93, #L95-L97, #L99-L101, #L103-L105, #L107-L109, #L111-L113, #L115-L117, #L119-L121, #L123-L125, #L127-L129, #L131-L133, #L135-L137 ):
2: pragma solidity >=0.8.20;
3:
4: import './Errors.sol';
4: import './Errors.sol';
5:
6: library SafeCastLib {
15: }
16:
17: function toUint8(uint256 x) internal pure returns (uint8 y) {
19: }
20:
21: function toUint16(uint256 x) internal pure returns (uint16 y) {
23: }
24:
25: function toUint24(uint256 x) internal pure returns (uint24 y) {
27: }
28:
29: function toUint32(uint256 x) internal pure returns (uint32 y) {
31: }
32:
33: function toUint40(uint256 x) internal pure returns (uint40 y) {
35: }
36:
37: function toUint48(uint256 x) internal pure returns (uint48 y) {
39: }
40:
41: function toUint56(uint256 x) internal pure returns (uint56 y) {
43: }
44:
45: function toUint64(uint256 x) internal pure returns (uint64 y) {
47: }
48:
49: function toUint72(uint256 x) internal pure returns (uint72 y) {
51: }
52:
53: function toUint80(uint256 x) internal pure returns (uint80 y) {
55: }
56:
57: function toUint88(uint256 x) internal pure returns (uint88 y) {
59: }
60:
61: function toUint96(uint256 x) internal pure returns (uint96 y) {
63: }
64:
65: function toUint104(uint256 x) internal pure returns (uint104 y) {
67: }
68:
69: function toUint112(uint256 x) internal pure returns (uint112 y) {
71: }
72:
73: function toUint120(uint256 x) internal pure returns (uint120 y) {
75: }
76:
77: function toUint128(uint256 x) internal pure returns (uint128 y) {
79: }
80:
81: function toUint136(uint256 x) internal pure returns (uint136 y) {
83: }
84:
85: function toUint144(uint256 x) internal pure returns (uint144 y) {
87: }
88:
89: function toUint152(uint256 x) internal pure returns (uint152 y) {
91: }
92:
93: function toUint160(uint256 x) internal pure returns (uint160 y) {
95: }
96:
97: function toUint168(uint256 x) internal pure returns (uint168 y) {
99: }
100:
101: function toUint176(uint256 x) internal pure returns (uint176 y) {
103: }
104:
105: function toUint184(uint256 x) internal pure returns (uint184 y) {
107: }
108:
109: function toUint192(uint256 x) internal pure returns (uint192 y) {
111: }
112:
113: function toUint200(uint256 x) internal pure returns (uint200 y) {
115: }
116:
117: function toUint208(uint256 x) internal pure returns (uint208 y) {
119: }
120:
121: function toUint216(uint256 x) internal pure returns (uint216 y) {
123: }
124:
125: function toUint224(uint256 x) internal pure returns (uint224 y) {
127: }
128:
129: function toUint232(uint256 x) internal pure returns (uint232 y) {
131: }
132:
133: function toUint240(uint256 x) internal pure returns (uint240 y) {
135: }
136:
137: function toUint248(uint256 x) internal pure returns (uint248 y) {
2: pragma solidity >=0.8.20;
3:
4: import { LibBit } from 'solady/utils/LibBit.sol';
31: }
32:
33: function queryStringOrBytes32AsString(
94: }
95:
96: function queryName(address target) view returns (string memory) {
99: }
100:
101: function querySymbol(address target) view returns (string memory) {
2: pragma solidity >=0.8.20;
3:
4: import './MarketState.sol';
35: }
36:
37: library WithdrawalLib {
2: pragma solidity >=0.8.20;
3:
4: import '../libraries/FeeMath.sol';
8: import './WildcatMarketWithdrawals.sol';
9:
10: contract WildcatMarket is
- WildcatMarketBase.sol ( #L2-L4, #L12-L14, #L134-L136, #L526-L528 ):
2: pragma solidity >=0.8.20;
3:
4: import '../libraries/FeeMath.sol';
12: import '../libraries/BoolUtils.sol';
13:
14: contract WildcatMarketBase is ReentrancyGuard, IMarketEventsAndErrors {
134: }
135:
136: modifier onlyController() {
526: }
527:
528: function _applyWithdrawalBatchPaymentView(
2: pragma solidity >=0.8.20;
3:
4: import '../interfaces/IWildcatSanctionsSentinel.sol';
7: import './WildcatMarketBase.sol';
8:
9: contract WildcatMarketConfig is WildcatMarketBase {
40: }
41:
42: function reserveRatioBips() external view returns (uint256) {
2: pragma solidity >=0.8.20;
3:
4: import './WildcatMarketBase.sol';
4: import './WildcatMarketBase.sol';
5:
6: contract WildcatMarketToken is WildcatMarketBase {
34: }
35:
36: function transfer(address to, uint256 amount) external virtual nonReentrant returns (bool) {
39: }
40:
41: function transferFrom(
57: }
58:
59: function _approve(address approver, address spender, uint256 amount) internal virtual {
62: }
63:
64: function _transfer(address from, address to, uint256 amount) internal virtual {
2: pragma solidity >=0.8.20;
3:
4: import './WildcatMarketBase.sol';
9: import 'solady/utils/SafeTransferLib.sol';
10:
11: contract WildcatMarketWithdrawals is WildcatMarketBase {
26: }
27:
28: function getWithdrawalBatch(
36: }
37:
38: function getAccountWithdrawalStatus(
43: }
44:
45: function getAvailableWithdrawalAmount(
188: }
189:
190: function processUnpaidWithdrawalBatch() external nonReentrant {
Formal verification is the act of proving or disproving the correctness of intended algorithms underlying a system with respect to a certain formal specification/property/invariant, using formal methods of mathematics.
Some tools that are currently available to perform these tests on smart contracts are SMTChecker and Certora Prover.
There are 22 instances (click to show):
The overheads outlined below are PER LOOP, excluding the first loop:
- storage arrays incur a Gwarmaccess (100 gas)
- memory arrays use
MLOAD
(3 gas) - calldata arrays use
CALLDATALOAD
(3 gas) Caching the length changes each of these to aDUP<N>
(3 gas), and gets rid of the extraDUP<N>
needed to store the stack offset.
There are 3 instances:
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++) {
Shifting right by n
is like dividing by 2^n
, while the shifting cost less gas because it does not require checks and jumps.
There are 2 instances:
/// @audit div 8
23: size = (sizeInBits + 7) / 8;
/// @audit div 8
73: size = (sizeInBits + 7) / 8;
When fetching data from a storage location, assigning the data to a memory
variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD
rather than a cheap stack read. Instead of declaring the variable with the memory
keyword, declaring the variable with the storage
keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incurring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory
variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory
, or if the array/struct is being read from another memory
array/struct.
There are 8 instances (click to show):
- WildcatMarketController.sol ( #L491 ):
491: TemporaryReserveRatio memory tmp = temporaryExcessReserveRatio[market];
164: Account memory account = _accounts[accountAddress];
468: WithdrawalBatch memory batch = _withdrawalData.batches[expiry];
- WildcatMarketConfig.sol ( #L89 ):
89: Account memory account = _accounts[accountAddress];
59: AccountWithdrawalStatus memory status = _withdrawalData.accountStatuses[expiry][accountAddress];
101: WithdrawalBatch memory batch = _withdrawalData.batches[expiry];
146: WithdrawalBatch memory batch = _withdrawalData.batches[expiry];
197: WithdrawalBatch memory batch = _withdrawalData.batches[expiry];
If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table
There are 2 instances:
- WildcatSanctionsSentinel.sol ( #L11-L12 ):
11: bytes32 public constant override WildcatSanctionsEscrowInitcodeHash =
12: keccak256(type(WildcatSanctionsEscrow).creationCode);
- WildcatMarketBase.sol ( #L24 ):
24: string public constant version = '1.0';
Payable functions cost less gas to execute, because the compiler does not have to add extra checks to ensure that no payment is provided. A constructor can be safely marked as payable, because only the deployer would be able to pass funds, and the project itself would not pass any funds.
There are 7 instances (click to show):
- ReentrancyGuard.sol ( #L49 ):
49: constructor() {
- WildcatArchController.sol ( #L55 ):
55: constructor() {
- WildcatMarketController.sol ( #L94 ):
94: constructor() {
- WildcatMarketControllerFactory.sol ( #L72-L76 ):
72: constructor(
73: address _archController,
74: address _sentinel,
75: MarketParameterConstraints memory constraints
76: ) {
- WildcatSanctionsEscrow.sol ( #L16 ):
16: constructor() {
- WildcatSanctionsSentinel.sol ( #L24 ):
24: constructor(address _archController, address _chainalysisSanctionsList) {
- WildcatMarketBase.sol ( #L76 ):
76: constructor() {
This can avoid a Gsset (20000 gas) on deployment (in constructor), and replaces the first access in each transaction (Gcoldsload - 2100 gas) and each access thereafter (Gwarmacces - 100 gas) with a PUSH32
(3 gas).
While string
s are not value types, and therefore cannot be immutable
/constant
if not hard-coded outside of the constructor, the same behavior can be achieved by making the current contract abstract
with virtual
functions for the string
accessors, and having a child contract override the functions with the hard-coded implementation-specific values.
There are 2 instances:
97: name = string.concat(parameters.namePrefix, queryName(parameters.asset));
98: symbol = string.concat(parameters.symbolPrefix, querySymbol(parameters.asset));
It can save 5 gas for each execution / per iteration.
There are 12 instances (click to show):
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++) {
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++) {
- WildcatMarketControllerFactory.sol ( #L146 ):
146: for (uint256 i = 0; i < count; i++) {
48: for (uint256 i = 0; i < len; i++) {
75: for (uint256 i = 0; i < n; i++) {
Data that can be obtained from off-chain data and event log details should not be added as event parameters to save gas.
There is 1 instance:
- WildcatMarket.sol ( #L160 ):
/// @audit `block.timestamp`
160: emit MarketClosed(block.timestamp);
Consider changing the variable to be an unnamed one, since the variable is never assigned, nor is it returned by name. If the optimizer is not turned on, leaving the code as it is will also waste gas for the stack variable.
There are 8 instances (click to show):
- WildcatMarketControllerFactory.sol ( #L165-L173 ):
/// @audit feeRecipient
/// @audit originationFeeAsset
/// @audit originationFeeAmount
/// @audit protocolFeeBips
165: function getProtocolFeeConfiguration()
166: external
167: view
168: returns (
169: address feeRecipient,
170: address originationFeeAsset,
171: uint80 originationFeeAmount,
172: uint16 protocolFeeBips
173: )
- WildcatSanctionsSentinel.sol ( #L65-L69 ):
/// @audit escrowAddress
65: function getEscrowAddress(
66: address borrower,
67: address account,
68: address asset
69: ) public view override returns (address escrowAddress) {
- FeeMath.sol ( #L19-L22 ):
/// @audit result
19: function calculateLinearInterestFromBips(
20: uint256 rateBip,
21: uint256 timeDelta
22: ) internal pure returns (uint256 result) {
- MarketState.sol ( #L87-L89 ):
/// @audit _liquidityRequired
87: function liquidityRequired(
88: MarketState memory state
89: ) internal pure returns (uint256 _liquidityRequired) {
- MathUtils.sol ( #L30-L33 ):
/// @audit result
30: function calculateLinearInterestFromBips(
31: uint256 rateBip,
32: uint256 timeDelta
33: ) internal pure returns (uint256 result) {
If it can be confirmed that the subtraction operation will not overflow, using an unchecked block can save gas.
For example, require(x <= y); z = y - x;
can be optimized to require(x <= y); unchecked { z = y - x; }
.
There are 2 instances:
/// @audit checked on line 152
154: asset.safeTransferFrom(borrower, address(this), totalDebts - currentlyHeld);
/// @audit checked on line 152
157: asset.safeTransfer(borrower, currentlyHeld - totalDebts);
If an internal
function is only used once, there is no need to modularize it, unless the function calling it would otherwise be too long and complex. Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.
There are 5 instances:
59: function _setReentrancyGuard() internal {
72: function _clearReentrancyGuard() internal {
- WildcatMarketController.sol ( #L255 ):
255: function _resetTmpMarketParameters() internal {
- WildcatMarketBase.sol ( #L466, #L528-L532 ):
466: function _processExpiredWithdrawalBatch(MarketState memory state) internal {
528: function _applyWithdrawalBatchPaymentView(
529: WithdrawalBatch memory batch,
530: MarketState memory state,
531: uint256 availableLiquidity
532: ) internal pure {
If a function modifier such as onlyOwner
is used, the function will revert if a normal user tries to pay the function. Marking the function as payable
will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided.
The extra opcodes avoided are:
CALLVALUE(2), DUP1(3), ISZERO(3), PUSH2(3), JUMPI(10), PUSH1(3), DUP1(3), REVERT(0), JUMPDEST(1), POP(2)
which cost an average of about 21 gas per call to the function, in addition to the extra deployment cost.
There are 19 instances (click to show):
63: function registerBorrower(address borrower) external onlyOwner {
70: function removeBorrower(address borrower) external onlyOwner {
106: function registerControllerFactory(address factory) external onlyOwner {
113: function removeControllerFactory(address factory) external onlyOwner {
149: function registerController(address controller) external onlyControllerFactory {
156: function removeController(address controller) external onlyOwner {
192: function registerMarket(address market) external onlyController {
199: function removeMarket(address market) external onlyOwner {
- WildcatMarketController.sol ( #L153, #L169, #L468-L471 ):
153: function authorizeLenders(address[] memory lenders) external onlyBorrower {
169: function deauthorizeLenders(address[] memory lenders) external onlyBorrower {
468: function setAnnualInterestBips(
469: address market,
470: uint16 annualInterestBips
471: ) external virtual onlyBorrower onlyControlledMarket(market) {
- WildcatMarketControllerFactory.sol ( #L195-L200, #L282 ):
195: function setProtocolFeeConfiguration(
196: address feeRecipient,
197: address originationFeeAsset,
198: uint80 originationFeeAmount,
199: uint16 protocolFeeBips
200: ) external onlyArchControllerOwner {
282: function deployController() public returns (address controller) {
119: function borrow(uint256 amount) external onlyBorrower nonReentrant {
142: function closeMarket() external onlyController nonReentrant {
- WildcatMarketConfig.sol ( #L112-L115, #L134, #L149, #L171 ):
112: function updateAccountAuthorization(
113: address _account,
114: bool _isAuthorized
115: ) external onlyController nonReentrant {
134: function setMaxTotalSupply(uint256 _maxTotalSupply) external onlyController nonReentrant {
149: function setAnnualInterestBips(uint16 _annualInterestBips) public onlyController nonReentrant {
171: function setReserveRatioBips(uint16 _reserveRatioBips) public onlyController nonReentrant {
Using the s.x = s.x + y
instead of s.x += y
for memory structs can save 100 gas. The same applies to -=
, *=
, etc.
There are 19 instances (click to show):
64: account.scaledBalance += scaledAmount;
71: state.scaledTotalSupply += scaledAmount;
105: state.accruedProtocolFees -= withdrawableFees;
513: batch.scaledAmountBurned += scaledAmountBurned;
514: batch.normalizedAmountPaid += normalizedAmountPaid;
515: state.scaledPendingWithdrawals -= scaledAmountBurned;
518: state.normalizedUnclaimedWithdrawals += normalizedAmountPaid;
521: state.scaledTotalSupply -= scaledAmountBurned;
542: batch.scaledAmountBurned += scaledAmountBurned;
543: batch.normalizedAmountPaid += normalizedAmountPaid;
544: state.scaledPendingWithdrawals -= scaledAmountBurned;
547: state.normalizedUnclaimedWithdrawals += normalizedAmountPaid;
550: state.scaledTotalSupply -= scaledAmountBurned;
73: fromAccount.scaledBalance -= scaledAmount;
77: toAccount.scaledBalance += scaledAmount;
89: account.scaledBalance -= scaledAmount;
105: batch.scaledTotalAmount += scaledAmount;
106: state.scaledPendingWithdrawals += scaledAmount;
158: state.normalizedUnclaimedWithdrawals -= normalizedAmountWithdrawn;
The compiler uses opcodes GT
and ISZERO
for code that uses >
, but only requires LT
for >=
. A similar behavior applies for >
, which uses opcodes LT
and ISZERO
, but only requires GT
for <=
. It can save 3 gas for each.
It should be converted to the <=
/>=
equivalent when comparing against integer literals.
There are 21 instances (click to show):
81: constraints.maximumAnnualInterestBips > 10000 ||
83: constraints.maximumDelinquencyFeeBips > 10000 ||
85: constraints.maximumReserveRatioBips > 10000 ||
201: bool hasOriginationFee = originationFeeAmount > 0;
205: (protocolFeeBips > 0 && nullFeeRecipient) ||
67: if (timeWithPenalty > 0) {
155: if (protocolFeeBips > 0) {
159: if (delinquencyFeeBips > 0) {
- WildcatMarket.sol ( #L147 ):
147: if (_withdrawalData.unpaidBatches.length() > 0) {
79: if ((parameters.protocolFeeBips > 0).and(parameters.feeRecipient == address(0))) {
82: if (parameters.annualInterestBips > BIP) {
85: if (parameters.reserveRatioBips > BIP) {
88: if (parameters.protocolFeeBips > BIP) {
91: if (parameters.delinquencyFeeBips > BIP) {
170: if (scaledBalance > 0) {
428: if (availableLiquidity > 0) {
472: if (availableLiquidity > 0) {
152: if (_annualInterestBips > BIP) {
172: if (_reserveRatioBips > BIP) {
32: if ((expiry == expiredBatchExpiry).and(expiry > 0)) {
112: if (availableLiquidity > 0) {
Using ints
/uints
smaller than 32 bytes may cost more gas. This is because the EVM operates on 32 bytes at a time, so if an element is smaller than 32 bytes, the EVM must perform more operations to reduce the size of the element from 32 bytes to the desired size.
There are 143 instances (click to show):
- WildcatMarketController.sol ( #L14, #L15, #L23, #L24, #L25, #L26, #L27, #L28, #L29, #L55, #L56, #L58, #L59, #L61, #L62, #L64, #L65, #L67, #L68, #L295, #L296, #L297, #L298, #L299, #L300, #L335, #L397, #L398, #L399, #L400, #L401, #L470 ):
/// @audit uint128
14: uint128 reserveRatioBips;
/// @audit uint128
15: uint128 expiry;
/// @audit uint16
23: uint16 protocolFeeBips;
/// @audit uint128
24: uint128 maxTotalSupply;
/// @audit uint16
25: uint16 annualInterestBips;
/// @audit uint16
26: uint16 delinquencyFeeBips;
/// @audit uint32
27: uint32 withdrawalBatchDuration;
/// @audit uint16
28: uint16 reserveRatioBips;
/// @audit uint32
29: uint32 delinquencyGracePeriod;
/// @audit uint32
55: uint32 internal immutable MinimumDelinquencyGracePeriod;
/// @audit uint32
56: uint32 internal immutable MaximumDelinquencyGracePeriod;
/// @audit uint16
58: uint16 internal immutable MinimumReserveRatioBips;
/// @audit uint16
59: uint16 internal immutable MaximumReserveRatioBips;
/// @audit uint16
61: uint16 internal immutable MinimumDelinquencyFeeBips;
/// @audit uint16
62: uint16 internal immutable MaximumDelinquencyFeeBips;
/// @audit uint32
64: uint32 internal immutable MinimumWithdrawalBatchDuration;
/// @audit uint32
65: uint32 internal immutable MaximumWithdrawalBatchDuration;
/// @audit uint16
67: uint16 internal immutable MinimumAnnualInterestBips;
/// @audit uint16
68: uint16 internal immutable MaximumAnnualInterestBips;
/// @audit uint128
295: uint128 maxTotalSupply,
/// @audit uint16
296: uint16 annualInterestBips,
/// @audit uint16
297: uint16 delinquencyFeeBips,
/// @audit uint32
298: uint32 withdrawalBatchDuration,
/// @audit uint16
299: uint16 reserveRatioBips,
/// @audit uint32
300: uint32 delinquencyGracePeriod
/// @audit uint80
335: uint80 originationFeeAmount;
/// @audit uint16
397: uint16 annualInterestBips,
/// @audit uint16
398: uint16 delinquencyFeeBips,
/// @audit uint32
399: uint32 withdrawalBatchDuration,
/// @audit uint16
400: uint16 reserveRatioBips,
/// @audit uint32
401: uint32 delinquencyGracePeriod
/// @audit uint16
470: uint16 annualInterestBips
- WildcatMarketControllerFactory.sol ( #L19, #L46, #L47, #L49, #L50, #L52, #L53, #L55, #L56, #L58, #L59, #L171, #L172, #L198, #L199, #L321, #L322, #L323, #L324, #L325, #L326 ):
/// @audit uint16
19: uint16 protocolFeeBips,
/// @audit uint32
46: uint32 internal immutable MinimumDelinquencyGracePeriod;
/// @audit uint32
47: uint32 internal immutable MaximumDelinquencyGracePeriod;
/// @audit uint16
49: uint16 internal immutable MinimumReserveRatioBips;
/// @audit uint16
50: uint16 internal immutable MaximumReserveRatioBips;
/// @audit uint16
52: uint16 internal immutable MinimumDelinquencyFeeBips;
/// @audit uint16
53: uint16 internal immutable MaximumDelinquencyFeeBips;
/// @audit uint32
55: uint32 internal immutable MinimumWithdrawalBatchDuration;
/// @audit uint32
56: uint32 internal immutable MaximumWithdrawalBatchDuration;
/// @audit uint16
58: uint16 internal immutable MinimumAnnualInterestBips;
/// @audit uint16
59: uint16 internal immutable MaximumAnnualInterestBips;
/// @audit uint80
171: uint80 originationFeeAmount,
/// @audit uint16
172: uint16 protocolFeeBips
/// @audit uint80
198: uint80 originationFeeAmount,
/// @audit uint16
199: uint16 protocolFeeBips
/// @audit uint128
321: uint128 maxTotalSupply,
/// @audit uint16
322: uint16 annualInterestBips,
/// @audit uint16
323: uint16 delinquencyFeeBips,
/// @audit uint32
324: uint32 withdrawalBatchDuration,
/// @audit uint16
325: uint16 reserveRatioBips,
/// @audit uint32
326: uint32 delinquencyGracePeriod
/// @audit uint128
5: uint128 startIndex;
/// @audit uint128
6: uint128 nextIndex;
/// @audit uint32
23: function first(FIFOQueue storage arr) internal view returns (uint32) {
/// @audit uint32
30: function at(FIFOQueue storage arr, uint256 index) internal view returns (uint32) {
/// @audit uint128
38: function length(FIFOQueue storage arr) internal view returns (uint128) {
/// @audit uint32
55: function push(FIFOQueue storage arr, uint32 value) internal {
/// @audit uint128
56: uint128 nextIndex = arr.nextIndex;
/// @audit uint128
62: uint128 startIndex = arr.startIndex;
/// @audit uint128
70: function shiftN(FIFOQueue storage arr, uint128 n) internal {
/// @audit uint128
71: uint128 startIndex = arr.startIndex;
/// @audit uint128
15: uint128 maxTotalSupply;
/// @audit uint128
16: uint128 accruedProtocolFees;
/// @audit uint128
19: uint128 normalizedUnclaimedWithdrawals;
/// @audit uint104
21: uint104 scaledTotalSupply;
/// @audit uint104
24: uint104 scaledPendingWithdrawals;
/// @audit uint32
25: uint32 pendingWithdrawalExpiry;
/// @audit uint32
29: uint32 timeDelinquent;
/// @audit uint16
31: uint16 annualInterestBips;
/// @audit uint16
33: uint16 reserveRatioBips;
/// @audit uint112
35: uint112 scaleFactor;
/// @audit uint32
36: uint32 lastInterestAccruedTimestamp;
/// @audit uint104
41: uint104 scaledBalance;
/// @audit uint128
108: ) internal pure returns (uint128) {
- SafeCastLib.sol ( #L17, #L21, #L25, #L29, #L33, #L37, #L41, #L45, #L49, #L53, #L57, #L61, #L65, #L69, #L73, #L77, #L81, #L85, #L89, #L93, #L97, #L101, #L105, #L109, #L113, #L117, #L121, #L125, #L129, #L133, #L137 ):
/// @audit uint8
17: function toUint8(uint256 x) internal pure returns (uint8 y) {
/// @audit uint16
21: function toUint16(uint256 x) internal pure returns (uint16 y) {
/// @audit uint24
25: function toUint24(uint256 x) internal pure returns (uint24 y) {
/// @audit uint32
29: function toUint32(uint256 x) internal pure returns (uint32 y) {
/// @audit uint40
33: function toUint40(uint256 x) internal pure returns (uint40 y) {
/// @audit uint48
37: function toUint48(uint256 x) internal pure returns (uint48 y) {
/// @audit uint56
41: function toUint56(uint256 x) internal pure returns (uint56 y) {
/// @audit uint64
45: function toUint64(uint256 x) internal pure returns (uint64 y) {
/// @audit uint72
49: function toUint72(uint256 x) internal pure returns (uint72 y) {
/// @audit uint80
53: function toUint80(uint256 x) internal pure returns (uint80 y) {
/// @audit uint88
57: function toUint88(uint256 x) internal pure returns (uint88 y) {
/// @audit uint96
61: function toUint96(uint256 x) internal pure returns (uint96 y) {
/// @audit uint104
65: function toUint104(uint256 x) internal pure returns (uint104 y) {
/// @audit uint112
69: function toUint112(uint256 x) internal pure returns (uint112 y) {
/// @audit uint120
73: function toUint120(uint256 x) internal pure returns (uint120 y) {
/// @audit uint128
77: function toUint128(uint256 x) internal pure returns (uint128 y) {
/// @audit uint136
81: function toUint136(uint256 x) internal pure returns (uint136 y) {
/// @audit uint144
85: function toUint144(uint256 x) internal pure returns (uint144 y) {
/// @audit uint152
89: function toUint152(uint256 x) internal pure returns (uint152 y) {
/// @audit uint160
93: function toUint160(uint256 x) internal pure returns (uint160 y) {
/// @audit uint168
97: function toUint168(uint256 x) internal pure returns (uint168 y) {
/// @audit uint176
101: function toUint176(uint256 x) internal pure returns (uint176 y) {
/// @audit uint184
105: function toUint184(uint256 x) internal pure returns (uint184 y) {
/// @audit uint192
109: function toUint192(uint256 x) internal pure returns (uint192 y) {
/// @audit uint200
113: function toUint200(uint256 x) internal pure returns (uint200 y) {
/// @audit uint208
117: function toUint208(uint256 x) internal pure returns (uint208 y) {
/// @audit uint216
121: function toUint216(uint256 x) internal pure returns (uint216 y) {
/// @audit uint224
125: function toUint224(uint256 x) internal pure returns (uint224 y) {
/// @audit uint232
129: function toUint232(uint256 x) internal pure returns (uint232 y) {
/// @audit uint240
133: function toUint240(uint256 x) internal pure returns (uint240 y) {
/// @audit uint248
137: function toUint248(uint256 x) internal pure returns (uint248 y) {
/// @audit uint104
19: uint104 scaledTotalAmount;
/// @audit uint104
21: uint104 scaledAmountBurned;
/// @audit uint128
23: uint128 normalizedAmountPaid;
/// @audit uint104
27: uint104 scaledAmount;
/// @audit uint128
28: uint128 normalizedAmountWithdrawn;
/// @audit uint104
38: function scaledOwedAmount(WithdrawalBatch memory batch) internal pure returns (uint104) {
/// @audit uint104
56: uint104 scaledAmount = state.scaleAmount(amount).toUint104();
/// @audit uint128
101: uint128 withdrawableFees = state.withdrawableProtocolFees(totalAssets());
- WildcatMarketBase.sol ( #L54, #L166, #L307, #L404, #L467, #L501, #L504, #L505, #L510, #L511, #L533, #L534, #L539, #L540 ):
/// @audit uint8
54: uint8 public immutable decimals;
/// @audit uint104
166: uint104 scaledBalance = account.scaledBalance;
/// @audit uint128
307: function withdrawableProtocolFees() external view returns (uint128) {
/// @audit uint32
404: uint32 expiredBatchExpiry,
/// @audit uint32
467: uint32 expiry = state.pendingWithdrawalExpiry;
/// @audit uint32
501: uint32 expiry,
/// @audit uint104
504: uint104 scaledAvailableLiquidity = state.scaleAmount(availableLiquidity).toUint104();
/// @audit uint104
505: uint104 scaledAmountOwed = batch.scaledTotalAmount - batch.scaledAmountBurned;
/// @audit uint104
510: uint104 scaledAmountBurned = uint104(MathUtils.min(scaledAvailableLiquidity, scaledAmountOwed));
/// @audit uint128
511: uint128 normalizedAmountPaid = state.normalizeAmount(scaledAmountBurned).toUint128();
/// @audit uint104
533: uint104 scaledAvailableLiquidity = state.scaleAmount(availableLiquidity).toUint104();
/// @audit uint104
534: uint104 scaledAmountOwed = batch.scaledTotalAmount - batch.scaledAmountBurned;
/// @audit uint104
539: uint104 scaledAmountBurned = uint104(MathUtils.min(scaledAvailableLiquidity, scaledAmountOwed));
/// @audit uint128
540: uint128 normalizedAmountPaid = state.normalizeAmount(scaledAmountBurned).toUint128();
/// @audit uint16
149: function setAnnualInterestBips(uint16 _annualInterestBips) public onlyController nonReentrant {
/// @audit uint16
171: function setReserveRatioBips(uint16 _reserveRatioBips) public onlyController nonReentrant {
- WildcatMarketToken.sol ( #L66 ):
/// @audit uint104
66: uint104 scaledAmount = state.scaleAmount(amount).toUint104();
- WildcatMarketWithdrawals.sol ( #L29, #L31, #L40, #L47, #L52, #L83, #L99, #L139, #L151, #L155, #L194 ):
/// @audit uint32
29: uint32 expiry
/// @audit uint32
31: (, uint32 expiredBatchExpiry, WithdrawalBatch memory expiredBatch) = _calculateCurrentState();
/// @audit uint32
40: uint32 expiry
/// @audit uint32
47: uint32 expiry
/// @audit uint32
52: (, uint32 expiredBatchExpiry, WithdrawalBatch memory expiredBatch) = _calculateCurrentState();
/// @audit uint104
83: uint104 scaledAmount = state.scaleAmount(amount).toUint104();
/// @audit uint32
99: uint32 expiry = state.pendingWithdrawalExpiry;
/// @audit uint32
139: uint32 expiry
/// @audit uint128
151: uint128 newTotalWithdrawn = uint128(
/// @audit uint128
155: uint128 normalizedAmountWithdrawn = newTotalWithdrawn - status.normalizedAmountWithdrawn;
/// @audit uint32
194: uint32 expiry = _withdrawalData.unpaidBatches.first();
The expression type(int).min/(-1)
is the only case where division causes an overflow. Therefore, uncheck can be used to save gas in scenarios where it is certain that such an overflow will not occur.
There are 4 instances:
- FeeMath.sol ( #L26 ):
26: return accumulatedInterestRay / SECONDS_IN_365_DAYS;
- MathUtils.sol ( #L37 ):
37: return accumulatedInterestRay / SECONDS_IN_365_DAYS;
23: size = (sizeInBits + 7) / 8;
73: size = (sizeInBits + 7) / 8;
Using unchecked
increments can save gas by bypassing the built-in overflow checks. This can save 30-40 gas per iteration. So it is recommended to use unchecked
increments when overflow is not possible.
There are 12 instances (click to show):
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++) {
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++) {
- WildcatMarketControllerFactory.sol ( #L146 ):
146: for (uint256 i = 0; i < count; i++) {
48: for (uint256 i = 0; i < len; i++) {
75: for (uint256 i = 0; i < n; i++) {
Saves a storage slot. If the variable is assigned a non-zero value, saves Gsset (20000 gas). If it's assigned a zero value, saves Gsreset (2900 gas). If the variable remains unassigned, there is no gas savings unless the variable is public
, in which case the compiler-generated non-payable getter deployment cost is saved. If the state variable is overriding an interface's public function, mark the variable as constant
or immutable
so that it does not use a storage slot.
There are 2 instances:
57: string public name;
60: string public symbol;
Using assembly to check for zero can save gas by allowing more direct access to the evm and reducing some of the overhead associated with high-level operations in solidity.
There are 15 instances (click to show):
345: if (originationFeeAsset != address(0)) {
351: if (market.codehash != bytes32(0)) {
477: if (tmp.expiry == 0) {
492: if (tmp.expiry == 0) {
- WildcatMarketControllerFactory.sol ( #L294 ):
294: if (controller.codehash != bytes32(0)) {
- WildcatSanctionsSentinel.sol ( #L106 ):
106: if (escrowContract.codehash != bytes32(0)) return escrowContract;
57: if (scaledAmount == 0) revert NullMintAmount();
98: if (state.accruedProtocolFees == 0) {
102: if (withdrawableFees == 0) {
507: if (scaledAmountOwed == 0) {
536: if (scaledAmountOwed == 0) {
- WildcatMarketToken.sol ( #L68 ):
68: if (scaledAmount == 0) {
84: if (scaledAmount == 0) {
94: if (state.pendingWithdrawalExpiry == 0) {
160: if (normalizedAmountWithdrawn == 0) {
Using assembly { sstore(state.slot, addr)
instead of state = addr
can save gas.
There are 2 instances:
286: _tmpMarketBorrowerParameter = msg.sender;
298: _tmpMarketBorrowerParameter = address(1);
Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past. Refer to the source.
There is 1 instance:
- WildcatSanctionsSentinel.sol ( #L20-L22 ):
20: mapping(address borrower => mapping(address account => bool sanctionOverride))
21: public
22: override sanctionOverrides;
In Solidity, unnecessary operations can waste gas. For example, a transfer function without a zero amount check uses gas even if called with a zero amount, since the contract state remains unchanged. Implementing a zero amount check avoids these unnecessary function calls, saving gas and improving efficiency.
There are 4 instances:
- WildcatMarketController.sol ( #L346 ):
346: originationFeeAsset.safeTransferFrom(borrower, parameters.feeRecipient, originationFeeAmount);
- WildcatSanctionsEscrow.sol ( #L38 ):
38: IERC20(asset).transfer(account, amount);
60: asset.safeTransferFrom(msg.sender, address(this), amount);
157: asset.safeTransfer(borrower, currentlyHeld - totalDebts);
Emitting an event has an overhead of 375 gas, which will be incurred on every iteration of the loop. It is cheaper to emit only once after the loop has finished.
There are 2 instances:
157: emit LenderAuthorized(lender);
173: emit LenderDeauthorized(lender);
public
/external
function names and public
member variable names can be optimized to save gas. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted.
There are 9 instances (click to show):
- WildcatArchController.sol ( #L8 ):
/// @audit registerBorrower(), removeBorrower(), isRegisteredBorrower(), getRegisteredBorrowers(), getRegisteredBorrowers(), getRegisteredBorrowersCount(), registerControllerFactory(), removeControllerFactory(), isRegisteredControllerFactory(), getRegisteredControllerFactories(), getRegisteredControllerFactories(), getRegisteredControllerFactoriesCount(), registerController(), removeController(), isRegisteredController(), getRegisteredControllers(), getRegisteredControllers(), getRegisteredControllersCount(), registerMarket(), removeMarket(), isRegisteredMarket(), getRegisteredMarkets(), getRegisteredMarkets(), getRegisteredMarketsCount()
8: contract WildcatArchController is Ownable {
- WildcatMarketController.sol ( #L32 ):
/// @audit getAuthorizedLenders(), getAuthorizedLenders(), getAuthorizedLendersCount(), isAuthorizedLender(), authorizeLenders(), deauthorizeLenders(), updateLenderAuthorization(), isControlledMarket(), getControlledMarkets(), getControlledMarkets(), getControlledMarketsCount(), computeMarketAddress(), getMarketParameters(), deployMarket(), getParameterConstraints(), setAnnualInterestBips(), resetReserveRatio(), archController, controllerFactory, borrower, sentinel, marketInitCodeStorage, marketInitCodeHash, temporaryExcessReserveRatio
32: contract WildcatMarketController is IWildcatMarketControllerEventsAndErrors {
- WildcatMarketControllerFactory.sol ( #L13 ):
/// @audit isDeployedController(), getDeployedControllersCount(), getDeployedControllers(), getDeployedControllers(), getProtocolFeeConfiguration(), setProtocolFeeConfiguration(), getParameterConstraints(), getMarketControllerParameters(), deployController(), deployControllerAndMarket(), computeControllerAddress(), archController, sentinel, marketInitCodeStorage, marketInitCodeHash, controllerInitCodeStorage, controllerInitCodeHash
13: contract WildcatMarketControllerFactory {
- WildcatSanctionsEscrow.sol ( #L10 ):
/// @audit balance(), canReleaseEscrow(), escrowedAsset(), releaseEscrow(), sentinel, borrower, account
10: contract WildcatSanctionsEscrow is IWildcatSanctionsEscrow {
- WildcatSanctionsSentinel.sol ( #L10 ):
/// @audit isSanctioned(), overrideSanction(), removeSanctionOverride(), getEscrowAddress(), createEscrow(), WildcatSanctionsEscrowInitcodeHash, chainalysisSanctionsList, archController, tmpEscrowParams, sanctionOverrides
10: contract WildcatSanctionsSentinel is IWildcatSanctionsSentinel {
- WildcatMarket.sol ( #L10 ):
/// @audit updateState(), depositUpTo(), deposit(), collectFees(), borrow(), closeMarket()
10: contract WildcatMarket is
- WildcatMarketBase.sol ( #L14 ):
/// @audit coverageLiquidity(), scaleFactor(), totalAssets(), borrowableAssets(), accruedProtocolFees(), previousState(), currentState(), scaledTotalSupply(), scaledBalanceOf(), getAccountRole(), withdrawableProtocolFees(), effectiveBorrowerAPR(), effectiveLenderAPR(), version, sentinel, borrower, feeRecipient, protocolFeeBips, delinquencyFeeBips, delinquencyGracePeriod, controller, asset, withdrawalBatchDuration
14: contract WildcatMarketBase is ReentrancyGuard, IMarketEventsAndErrors {
- WildcatMarketConfig.sol ( #L9 ):
/// @audit maximumDeposit(), maxTotalSupply(), annualInterestBips(), reserveRatioBips(), nukeFromOrbit(), stunningReversal(), updateAccountAuthorization(), setMaxTotalSupply(), setAnnualInterestBips(), setReserveRatioBips()
9: contract WildcatMarketConfig is WildcatMarketBase {
- WildcatMarketWithdrawals.sol ( #L11 ):
/// @audit getUnpaidBatchExpiries(), getWithdrawalBatch(), getAccountWithdrawalStatus(), getAvailableWithdrawalAmount(), queueWithdrawal(), executeWithdrawal(), processUnpaidWithdrawalBatch()
11: contract WildcatMarketWithdrawals is WildcatMarketBase {
[G‑25] Duplicated require()
/revert()
checks should be refactored to a modifier Or function to save gas
Saves deployment costs.
There is 1 instance:
- WildcatMarketWithdrawals.sol ( #L50 ):
/// @audit Duplicated on line 142
50: revert WithdrawalBatchNotExpired();
Solidity version 0.8.19 introduced a number of gas optimizations, refer to the Solidity 0.8.19 Release Announcement for details.
There are 3 instances:
- Chainalysis.sol ( #L2 ):
2: pragma solidity ^0.8.20;
- Errors.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- StringQuery.sol ( #L2 ):
2: pragma solidity >=0.8.20;
The solidity language continues to pursue more efficient gas optimization schemes. Adopting a newer version of solidity can be more gas efficient.
There are 19 instances (click to show):
- ReentrancyGuard.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatArchController.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketController.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketControllerFactory.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatSanctionsEscrow.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatSanctionsSentinel.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- BoolUtils.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- FIFOQueue.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- FeeMath.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- LibStoredInitCode.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- MarketState.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- MathUtils.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- SafeCastLib.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- Withdrawal.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarket.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketBase.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketConfig.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketToken.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketWithdrawals.sol ( #L2 ):
2: pragma solidity >=0.8.20;
The function calls in solidity are expensive. If the same result of the same function calls are to be used several times, the result should be cached to reduce the gas consumption of repeated calls.
There are 3 instances:
- WildcatMarketBase.sol ( #L163 ):
/// @audit `state.normalizeAmount(scaledBalance)` called on lines: 177, 182
163: function _blockAccount(MarketState memory state, address accountAddress) internal {
- WildcatMarketConfig.sol ( #L171 ):
/// @audit `state.liquidityRequired()` called on lines: 181, 187
/// @audit `totalAssets()` called on lines: 181, 187
171: function setReserveRatioBips(uint16 _reserveRatioBips) public onlyController nonReentrant {
[G‑29] State variables that are used multiple times in a function should be cached in stack variables
When performing multiple operations on a state variable in a function, it is recommended to cache it first. Either multiple reads or multiple writes to a state variable can save gas by caching it on the stack. Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses. Saves 100 gas per instance.
There are 10 instances:
- WildcatMarketControllerFactory.sol ( #L282 ):
/// @audit _tmpMarketBorrowerParameter: 2 writes
282: function deployController() public returns (address controller) {
- WildcatMarketBase.sol ( #L466 ):
/// @audit _withdrawalData.batches: 2 reads
466: function _processExpiredWithdrawalBatch(MarketState memory state) internal {
/// @audit _withdrawalData.batches: 2 reads
77: function queueWithdrawal(uint256 amount) external nonReentrant {
/// @audit _withdrawalData.batches: 2 reads
/// @audit _withdrawalData.unpaidBatches: 2 reads
190: function processUnpaidWithdrawalBatch() external nonReentrant {
To efficiently emit events, it's possible to utilize assembly by making use of scratch space and the free memory pointer. This approach has the advantage of potentially avoiding the costs associated with memory expansion.
However, it's important to note that in order to safely optimize this process, it is preferable to cache and restore the free memory pointer.
A good example of such practice can be seen in Solady's codebase.
There are 36 instances (click to show):
67: emit BorrowerAdded(borrower);
74: emit BorrowerRemoved(borrower);
110: emit ControllerFactoryAdded(factory);
117: emit ControllerFactoryRemoved(factory);
153: emit ControllerAdded(msg.sender, controller);
160: emit ControllerRemoved(controller);
196: emit MarketAdded(msg.sender, market);
203: emit MarketRemoved(market);
157: emit LenderAuthorized(lender);
173: emit LenderDeauthorized(lender);
- WildcatSanctionsEscrow.sol ( #L40 ):
40: emit EscrowReleased(account, asset, amount);
50: emit SanctionOverride(msg.sender, account);
58: emit SanctionOverrideRemoved(msg.sender, account);
112: emit NewSanctionsEscrow(borrower, account, asset);
116: emit SanctionOverride(borrower, escrowContract);
67: emit Transfer(address(0), msg.sender, amount);
68: emit Deposit(msg.sender, amount, scaledAmount);
108: emit FeesCollected(withdrawableFees);
130: emit Borrow(amount);
160: emit MarketClosed(block.timestamp);
168: emit AuthorizationStatusUpdated(accountAddress, AuthRole.Blocked);
177: emit Transfer(accountAddress, escrow, state.normalizeAmount(scaledBalance));
206: emit AuthorizationStatusUpdated(accountAddress, AuthRole.DepositAndWithdraw);
452: emit StateUpdated(state.scaleFactor, isDelinquent);
486: emit WithdrawalBatchClosed(expiry);
524: emit Transfer(address(this), address(0), normalizedAmountPaid);
99: emit AuthorizationStatusUpdated(accountAddress, account.approval);
125: emit AuthorizationStatusUpdated(_account, account.approval);
143: emit MaxTotalSupplyUpdated(_maxTotalSupply);
158: emit AnnualInterestBipsUpdated(_annualInterestBips);
192: emit ReserveRatioBipsUpdated(_reserveRatioBips);
61: emit Approval(approver, spender, amount);
81: emit Transfer(from, to, amount);
91: emit Transfer(msg.sender, address(this), amount);
96: emit WithdrawalBatchCreated(state.pendingWithdrawalExpiry);
208: emit WithdrawalBatchClosed(expiry);
If the arguments to the encode call can fit into the scratch space (two words or fewer), then it's more efficient to use assembly to generate the hash (80 gas):
keccak256(abi.encodePacked(x, y))
-> assembly {mstore(0x00, a); mstore(0x20, b); let hash := keccak256(0x00, 0x40); }
There are 4 instances:
112: initCodeHash = uint256(keccak256(controllerInitCode));
122: initCodeHash = uint256(keccak256(marketInitCode));
78: keccak256(abi.encode(borrower, account, asset)),
110: new WildcatSanctionsEscrow{ salt: keccak256(abi.encode(borrower, account, asset)) }();
Mark data types as calldata
instead of memory
where possible. This makes it so that the data is not automatically loaded into memory. If the data passed into the function does not need to be changed (like updating values in an array), it can be passed in as calldata
. The one exception to this is if the argument must later be passed into another function that takes an argument that specifies memory
storage.
There are 9 instances (click to show):
- WildcatMarketController.sol ( #L153, #L169, #L182, #L221-L225, #L291-L301 ):
/// @audit lenders
153: function authorizeLenders(address[] memory lenders) external onlyBorrower {
/// @audit lenders
169: function deauthorizeLenders(address[] memory lenders) external onlyBorrower {
/// @audit markets
182: function updateLenderAuthorization(address lender, address[] memory markets) external {
/// @audit namePrefix
/// @audit symbolPrefix
221: function computeMarketAddress(
222: address asset,
223: string memory namePrefix,
224: string memory symbolPrefix
225: ) external view returns (address) {
/// @audit namePrefix
/// @audit symbolPrefix
291: function deployMarket(
292: address asset,
293: string memory namePrefix,
294: string memory symbolPrefix,
295: uint128 maxTotalSupply,
296: uint16 annualInterestBips,
297: uint16 delinquencyFeeBips,
298: uint32 withdrawalBatchDuration,
299: uint16 reserveRatioBips,
300: uint32 delinquencyGracePeriod
301: ) external returns (address market) {
- WildcatMarketControllerFactory.sol ( #L317-L327 ):
/// @audit namePrefix
/// @audit symbolPrefix
317: function deployControllerAndMarket(
318: string memory namePrefix,
319: string memory symbolPrefix,
320: address asset,
321: uint128 maxTotalSupply,
322: uint16 annualInterestBips,
323: uint16 delinquencyFeeBips,
324: uint32 withdrawalBatchDuration,
325: uint16 reserveRatioBips,
326: uint32 delinquencyGracePeriod
327: ) external returns (address controller, address market) {
By using --via-ir
or {"viaIR": true}
, the compiler is able to use more advanced multi-function optimizations, for extra gas savings.
There are 22 instances (click to show):
Booleans are more expensive than uint256 or any type that takes up a full word because each write operation emits an extra SLOAD to first read the slot's contents, replace the bits taken up by the boolean, and then write back. This is the compiler's defense against contract upgrades and pointer aliasing, and it cannot be disabled.
Use uint256(0)
and uint256(1)
for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD.
There is 1 instance:
- WildcatSanctionsSentinel.sol ( #L20-L22 ):
20: mapping(address borrower => mapping(address account => bool sanctionOverride))
21: public
22: override sanctionOverrides;
The instances below point to the second+ access of a value inside a mapping/array, within a function. Caching a mapping's value in a local storage
or calldata
variable when the value is accessed multiple times, saves ~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. Caching an array's struct avoids recalculating the array offsets into memory/calldata
There are 3 instances:
- WildcatMarketController.sol ( #L500 ):
/// @audit `temporaryExcessReserveRatio[market]` is also accessed on line 491
500: delete temporaryExcessReserveRatio[market];
- WildcatMarketBase.sol ( #L185 ):
/// @audit `_accounts[accountAddress]` is also accessed on line 164
185: _accounts[accountAddress] = account;
- WildcatMarketConfig.sol ( #L101 ):
/// @audit `_accounts[accountAddress]` is also accessed on line 89
101: _accounts[accountAddress] = account;
State variable reads and writes are more expensive than local variable reads and writes. Therefore, it is recommended to replace state variable reads and writes within loops with a local variable. Gas savings should be multiplied by the average loop length.
There are 6 instances:
/// @audit Access `_authorizedLenders` within for loop.
134: arr[i] = _authorizedLenders.at(start + i);
/// @audit Access `_authorizedLenders` within for loop.
156: if (_authorizedLenders.add(lender)) {
/// @audit Access `_authorizedLenders` within for loop.
172: if (_authorizedLenders.remove(lender)) {
/// @audit Access `_controlledMarkets` within for loop.
185: if (!_controlledMarkets.contains(market)) {
/// @audit Access `_authorizedLenders` within for loop.
188: WildcatMarket(market).updateAccountAuthorization(lender, _authorizedLenders.contains(lender));
/// @audit Access `_controlledMarkets` within for loop.
213: arr[i] = _controlledMarkets.at(start + i);
Solidity version 0.8.4 introduces bytes.concat()
, which can be used to replace abi.encodePacked()
on bytes/strings. It can make the intended operation clearer, leading to less reviewer confusion.
There is 1 instance:
- WildcatSanctionsSentinel.sol ( #L75-L80 ):
/// @audit abi.encodePacked() is clearer for non-bytes parameters.
75: abi.encodePacked(
76: bytes1(0xff),
77: address(this),
78: keccak256(abi.encode(borrower, account, asset)),
79: WildcatSanctionsEscrowInitcodeHash
80: )
The rule is valid, but the following findings are invalid.
There are 2 instances:
11: bytes32 public constant override WildcatSanctionsEscrowInitcodeHash =
12: keccak256(type(WildcatSanctionsEscrow).creationCode);
20: mapping(address borrower => mapping(address account => bool sanctionOverride))
21: public
22: override sanctionOverrides;
If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table
There are 27 instances (click to show):
41: IWildcatArchController public immutable archController;
43: IWildcatMarketControllerFactory public immutable controllerFactory;
45: address public immutable borrower;
47: address public immutable sentinel;
49: address public immutable marketInitCodeStorage;
51: uint256 public immutable marketInitCodeHash;
31: IWildcatArchController public immutable archController;
34: address public immutable sentinel;
36: address public immutable marketInitCodeStorage;
38: uint256 public immutable marketInitCodeHash;
40: address public immutable controllerInitCodeStorage;
42: uint256 public immutable controllerInitCodeHash;
11: address public immutable override sentinel;
12: address public immutable override borrower;
13: address public immutable override account;
14: address public immutable override chainalysisSanctionsList;
16: address public immutable override archController;
27: address public immutable sentinel;
30: address public immutable borrower;
33: address public immutable feeRecipient;
36: uint256 public immutable protocolFeeBips;
39: uint256 public immutable delinquencyFeeBips;
42: uint256 public immutable delinquencyGracePeriod;
45: address public immutable controller;
48: address public immutable asset;
51: uint256 public immutable withdrawalBatchDuration;
54: uint8 public immutable decimals;
The instances below are already CamelCase (events are supposed to use CamelCase, not lowerCamelCase).
There are 10 instances:
29: event MarketAdded(address indexed controller, address market);
30: event MarketRemoved(address market);
32: event ControllerFactoryAdded(address controllerFactory);
33: event ControllerFactoryRemoved(address controllerFactory);
35: event BorrowerAdded(address borrower);
36: event BorrowerRemoved(address borrower);
38: event ControllerAdded(address indexed controllerFactory, address controller);
39: event ControllerRemoved(address controller);
16: event NewController(address borrower, address controller, string namePrefix, string symbolPrefix);
17: event UpdateProtocolFeeConfiguration(
All unused code should be removed or commented out. If the functions are required by an interface, the contract should inherit from that interface and use the override keyword.
There are 71 instances (click to show):
/// @audit Invalid for library function
5: function and(bool a, bool b) internal pure returns (bool c) {
/// @audit Invalid for library function
11: function or(bool a, bool b) internal pure returns (bool c) {
/// @audit Invalid for library function
17: function xor(bool a, bool b) internal pure returns (bool c) {
/// @audit Invalid for library function
19: function empty(FIFOQueue storage arr) internal view returns (bool) {
/// @audit Invalid for library function
23: function first(FIFOQueue storage arr) internal view returns (uint32) {
/// @audit Invalid for library function
30: function at(FIFOQueue storage arr, uint256 index) internal view returns (uint32) {
/// @audit Invalid for library function
38: function length(FIFOQueue storage arr) internal view returns (uint128) {
/// @audit Invalid for library function
42: function values(FIFOQueue storage arr) internal view returns (uint32[] memory _values) {
/// @audit Invalid for library function
55: function push(FIFOQueue storage arr, uint32 value) internal {
/// @audit Invalid for library function
61: function shift(FIFOQueue storage arr) internal {
/// @audit Invalid for library function
70: function shiftN(FIFOQueue storage arr, uint128 n) internal {
- FeeMath.sol ( #L30-L33, #L40-L44, #L53-L58, #L142-L151 ):
/// @audit Invalid for library function
30: function calculateBaseInterest(
31: MarketState memory state,
32: uint256 timestamp
33: ) internal pure returns (uint256 baseInterestRay) {
/// @audit Invalid for library function
40: function applyProtocolFee(
41: MarketState memory state,
42: uint256 baseInterestRay,
43: uint256 protocolFeeBips
44: ) internal pure returns (uint256 protocolFee) {
/// @audit Invalid for library function
53: function updateDelinquency(
54: MarketState memory state,
55: uint256 timestamp,
56: uint256 delinquencyFeeBips,
57: uint256 delinquencyGracePeriod
58: ) internal pure returns (uint256 delinquencyFeeRay) {
/// @audit Invalid for library function
142: function updateScaleFactorAndFees(
143: MarketState memory state,
144: uint256 protocolFeeBips,
145: uint256 delinquencyFeeBips,
146: uint256 delinquencyGracePeriod,
147: uint256 timestamp
148: )
149: internal
150: pure
151: returns (uint256 baseInterestRay, uint256 delinquencyFeeRay, uint256 protocolFee)
/// @audit Invalid for library function
7: function deployInitCode(bytes memory data) internal returns (address initCodeStorage) {
/// @audit Invalid for library function
48: function getCreate2Prefix(address deployer) internal pure returns (uint256 create2Prefix) {
/// @audit Invalid for library function
54: function calculateCreate2Address(
55: uint256 create2Prefix,
56: bytes32 salt,
57: uint256 initCodeHash
58: ) internal pure returns (address create2Address) {
- MarketState.sol ( #L51, #L59, #L66-L69, #L76, #L87-L89, #L105-L108, #L123-L126, #L130, #L138 ):
/// @audit Invalid for library function
51: function totalSupply(MarketState memory state) internal pure returns (uint256) {
/// @audit Invalid for library function
59: function maximumDeposit(MarketState memory state) internal pure returns (uint256) {
/// @audit Invalid for library function
66: function normalizeAmount(
67: MarketState memory state,
68: uint256 amount
69: ) internal pure returns (uint256) {
/// @audit Invalid for library function
76: function scaleAmount(MarketState memory state, uint256 amount) internal pure returns (uint256) {
/// @audit Invalid for library function
87: function liquidityRequired(
88: MarketState memory state
89: ) internal pure returns (uint256 _liquidityRequired) {
/// @audit Invalid for library function
105: function withdrawableProtocolFees(
106: MarketState memory state,
107: uint256 totalAssets
108: ) internal pure returns (uint128) {
/// @audit Invalid for library function
123: function borrowableAssets(
124: MarketState memory state,
125: uint256 totalAssets
126: ) internal pure returns (uint256) {
/// @audit Invalid for library function
130: function hasPendingExpiredBatch(MarketState memory state) internal view returns (bool result) {
/// @audit Invalid for library function
138: function totalDebts(MarketState memory state) internal pure returns (uint256) {
/// @audit Invalid for library function
30: function calculateLinearInterestFromBips(
31: uint256 rateBip,
32: uint256 timeDelta
33: ) internal pure returns (uint256 result) {
/// @audit Invalid for library function
44: function min(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Invalid for library function
51: function max(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Invalid for library function
59: function satSub(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Invalid for library function
85: function bipMul(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Invalid for library function
105: function bipDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Invalid for library function
121: function bipToRay(uint256 a) internal pure returns (uint256 b) {
/// @audit Invalid for library function
138: function rayMul(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Invalid for library function
155: function rayDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Invalid for library function
173: function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @audit Invalid for library function
191: function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
- SafeCastLib.sol ( #L17, #L21, #L25, #L29, #L33, #L37, #L41, #L45, #L49, #L53, #L57, #L61, #L65, #L69, #L73, #L77, #L81, #L85, #L89, #L93, #L97, #L101, #L105, #L109, #L113, #L117, #L121, #L125, #L129, #L133, #L137 ):
/// @audit Invalid for library function
17: function toUint8(uint256 x) internal pure returns (uint8 y) {
/// @audit Invalid for library function
21: function toUint16(uint256 x) internal pure returns (uint16 y) {
/// @audit Invalid for library function
25: function toUint24(uint256 x) internal pure returns (uint24 y) {
/// @audit Invalid for library function
29: function toUint32(uint256 x) internal pure returns (uint32 y) {
/// @audit Invalid for library function
33: function toUint40(uint256 x) internal pure returns (uint40 y) {
/// @audit Invalid for library function
37: function toUint48(uint256 x) internal pure returns (uint48 y) {
/// @audit Invalid for library function
41: function toUint56(uint256 x) internal pure returns (uint56 y) {
/// @audit Invalid for library function
45: function toUint64(uint256 x) internal pure returns (uint64 y) {
/// @audit Invalid for library function
49: function toUint72(uint256 x) internal pure returns (uint72 y) {
/// @audit Invalid for library function
53: function toUint80(uint256 x) internal pure returns (uint80 y) {
/// @audit Invalid for library function
57: function toUint88(uint256 x) internal pure returns (uint88 y) {
/// @audit Invalid for library function
61: function toUint96(uint256 x) internal pure returns (uint96 y) {
/// @audit Invalid for library function
65: function toUint104(uint256 x) internal pure returns (uint104 y) {
/// @audit Invalid for library function
69: function toUint112(uint256 x) internal pure returns (uint112 y) {
/// @audit Invalid for library function
73: function toUint120(uint256 x) internal pure returns (uint120 y) {
/// @audit Invalid for library function
77: function toUint128(uint256 x) internal pure returns (uint128 y) {
/// @audit Invalid for library function
81: function toUint136(uint256 x) internal pure returns (uint136 y) {
/// @audit Invalid for library function
85: function toUint144(uint256 x) internal pure returns (uint144 y) {
/// @audit Invalid for library function
89: function toUint152(uint256 x) internal pure returns (uint152 y) {
/// @audit Invalid for library function
93: function toUint160(uint256 x) internal pure returns (uint160 y) {
/// @audit Invalid for library function
97: function toUint168(uint256 x) internal pure returns (uint168 y) {
/// @audit Invalid for library function
101: function toUint176(uint256 x) internal pure returns (uint176 y) {
/// @audit Invalid for library function
105: function toUint184(uint256 x) internal pure returns (uint184 y) {
/// @audit Invalid for library function
109: function toUint192(uint256 x) internal pure returns (uint192 y) {
/// @audit Invalid for library function
113: function toUint200(uint256 x) internal pure returns (uint200 y) {
/// @audit Invalid for library function
117: function toUint208(uint256 x) internal pure returns (uint208 y) {
/// @audit Invalid for library function
121: function toUint216(uint256 x) internal pure returns (uint216 y) {
/// @audit Invalid for library function
125: function toUint224(uint256 x) internal pure returns (uint224 y) {
/// @audit Invalid for library function
129: function toUint232(uint256 x) internal pure returns (uint232 y) {
/// @audit Invalid for library function
133: function toUint240(uint256 x) internal pure returns (uint240 y) {
/// @audit Invalid for library function
137: function toUint248(uint256 x) internal pure returns (uint248 y) {
/// @audit Invalid for library function
38: function scaledOwedAmount(WithdrawalBatch memory batch) internal pure returns (uint104) {
/// @audit Invalid for library function
47: function availableLiquidityForPendingBatch(
48: WithdrawalBatch memory batch,
49: MarketState memory state,
50: uint256 totalAssets
51: ) internal pure returns (uint256) {
Consider changing the variable to be an unnamed one, since the variable is never assigned, nor is it returned by name. If the optimizer is not turned on, leaving the code as it is will also waste gas for the stack variable.
There are 21 instances (click to show):
- WildcatMarketController.sol ( #L370-L374 ):
/// @audit salt is used in assembly
370: function _deriveSalt(
371: address asset,
372: string memory namePrefix,
373: string memory symbolPrefix
374: ) internal pure returns (bytes32 salt) {
/// @audit c is used in assembly
5: function and(bool a, bool b) internal pure returns (bool c) {
/// @audit c is used in assembly
11: function or(bool a, bool b) internal pure returns (bool c) {
/// @audit c is used in assembly
17: function xor(bool a, bool b) internal pure returns (bool c) {
- LibStoredInitCode.sol ( #L7, #L48, #L54-L58, #L87-L90, #L106-L110 ):
/// @audit initCodeStorage is used in assembly
7: function deployInitCode(bytes memory data) internal returns (address initCodeStorage) {
/// @audit create2Prefix is used in assembly
48: function getCreate2Prefix(address deployer) internal pure returns (uint256 create2Prefix) {
/// @audit create2Address is used in assembly
54: function calculateCreate2Address(
55: uint256 create2Prefix,
56: bytes32 salt,
57: uint256 initCodeHash
58: ) internal pure returns (address create2Address) {
/// @audit deployment is used in assembly
87: function createWithStoredInitCode(
88: address initCodeStorage,
89: uint256 value
90: ) internal returns (address deployment) {
/// @audit deployment is used in assembly
106: function create2WithStoredInitCode(
107: address initCodeStorage,
108: bytes32 salt,
109: uint256 value
110: ) internal returns (address deployment) {
- MarketState.sol ( #L130 ):
/// @audit result is used in assembly
130: function hasPendingExpiredBatch(MarketState memory state) internal view returns (bool result) {
/// @audit c is used in assembly
59: function satSub(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit c is used in assembly
71: function ternary(
72: bool condition,
73: uint256 valueIfTrue,
74: uint256 valueIfFalse
75: ) internal pure returns (uint256 c) {
/// @audit c is used in assembly
85: function bipMul(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit c is used in assembly
105: function bipDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit b is used in assembly
121: function bipToRay(uint256 a) internal pure returns (uint256 b) {
/// @audit c is used in assembly
138: function rayMul(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit c is used in assembly
155: function rayDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit z is used in assembly
173: function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @audit z is used in assembly
191: function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @audit str is used in assembly
19: function bytes32ToString(bytes32 value) pure returns (string memory str) {
/// @audit str is used in assembly
33: function queryStringOrBytes32AsString(
34: address target,
35: uint256 rightPaddedFunctionSelector,
36: uint256 rightPaddedGenericErrorSelector
37: ) view returns (string memory str) {
Assembly blocks take a lot more time to audit than normal Solidity code, and often have gotchas and side-effects that the Solidity versions of the same code do not. Consider adding more comments explaining what is being done in every step of the assembly code, and describe why assembly is being used instead of Solidity.
There are 10 instances (click to show):
- WildcatMarketController.sol ( #L375 ):
375: assembly {
8: assembly {
59: assembly {
- MarketState.sol ( #L132-L135 ):
132: assembly {
133: // Equivalent to expiry > 0 && expiry <= block.timestamp
134: result := gt(timestamp(), sub(expiry, 1))
135: }
- MathUtils.sol ( #L60-L64, #L86, #L174-L183, #L192-L201 ):
60: assembly {
61: // (a > b) * (a - b)
62: // If a-b underflows, the product will be zero
63: c := mul(gt(a, b), sub(a, b))
64: }
86: assembly {
174: assembly {
175: // Equivalent to require(d != 0 && (y == 0 || x <= type(uint256).max / y))
176: if iszero(mul(d, iszero(mul(y, gt(x, div(not(0), y)))))) {
177: // Store the function selector of `MulDivFailed()`.
178: mstore(0x00, 0xad251c27)
179: // Revert with (offset, size).
180: revert(0x1c, 0x04)
181: }
182: z := div(mul(x, y), d)
183: }
192: assembly {
193: // Equivalent to require(d != 0 && (y == 0 || x <= type(uint256).max / y))
194: if iszero(mul(d, iszero(mul(y, gt(x, div(not(0), y)))))) {
195: // Store the function selector of `MulDivFailed()`.
196: mstore(0x00, 0xad251c27)
197: // Revert with (offset, size).
198: revert(0x1c, 0x04)
199: }
200: z := add(iszero(iszero(mod(mul(x, y), d))), div(mul(x, y), d))
201: }
39: assembly {
83: assembly {
84: str := mload(0x40)
85: // Get allocation size for the string including the length and data.
86: // Rounding down returndatasize to nearest word because the returndata
87: // has an extra offset word.
88: let allocSize := and(sub(returndatasize(), 1), OnlyFullWordMask)
89: mstore(0x40, add(str, allocSize))
90: // Copy returndata after the offset
91: returndatacopy(str, 0x20, sub(returndatasize(), 0x20))
92: }
The rule is valid, but the following findings are invalid.
There is 1 instance:
- WildcatSanctionsSentinel.sol ( #L75-L80 ):
75: abi.encodePacked(
76: bytes1(0xff),
77: address(this),
78: keccak256(abi.encode(borrower, account, asset)),
79: WildcatSanctionsEscrowInitcodeHash
80: )
The cases below do not have multiple bytes
/string
arguments
There is 1 instance:
- WildcatSanctionsSentinel.sol ( #L74-L81 ):
74: keccak256(
75: abi.encodePacked(
76: bytes1(0xff),
77: address(this),
78: keccak256(abi.encode(borrower, account, asset)),
79: WildcatSanctionsEscrowInitcodeHash
80: )
81: )
After Solidity version 0.6.9 both public
and external
functions save the same amount of gas, and since these files are >0.6.9, these findings are invalid.
There are 9 instances (click to show):
29: function escrowedAsset() public view override returns (address, uint256) {
33: function releaseEscrow() public override {
39: function isSanctioned(address borrower, address account) public view override returns (bool) {
48: function overrideSanction(address account) public override {
56: function removeSanctionOverride(address account) public override {
95: function createEscrow(
96: address borrower,
97: address account,
98: address asset
99: ) public override returns (address escrowContract) {
149: function setAnnualInterestBips(uint16 _annualInterestBips) public onlyController nonReentrant {
171: function setReserveRatioBips(uint16 _reserveRatioBips) public onlyController nonReentrant {
- WildcatMarketToken.sol ( #L16 ):
16: function balanceOf(address account) public view virtual nonReentrantView returns (uint256) {
The @notice
is used to explain to users what the contract does. The compiler interprets ///
or /**
comments as this tag if one wasn't explicitly provided.
There is 1 instance:
- ReentrancyGuard.sol ( #L12 ):
12: contract ReentrancyGuard {
This is only true for state variables, and does not save gas for local variables. The examples below are for local variables and therefore do not save gas, and are invalid.
There are 11 instances (click to show):
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++) {
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++) {
212: for (uint256 i = 0; i < count; i++) {
- WildcatMarketControllerFactory.sol ( #L146 ):
146: for (uint256 i = 0; i < count; i++) {
48: for (uint256 i = 0; i < len; i++) {
75: for (uint256 i = 0; i < n; i++) {
It is not applicable to complex state variables.
There are 2 instances:
- WildcatMarketBase.sol ( #L178 ):
178: _accounts[escrow].scaledBalance += scaledBalance;
- WildcatMarketWithdrawals.sol ( #L104 ):
104: _withdrawalData.accountStatuses[expiry][msg.sender].scaledAmount += scaledAmount;
The rule is valid, but the following findings are invalid.
There are 3 instances:
60: asset.safeTransferFrom(msg.sender, address(this), amount);
129: asset.safeTransfer(msg.sender, amount);
154: asset.safeTransferFrom(borrower, address(this), totalDebts - currentlyHeld);
safeTransfer()
/safeTransferFrom()
aren't called from this file, so the vulnerability doesn't exist here
There are 5 instances:
5: import 'solady/utils/SafeTransferLib.sol';
35: using SafeTransferLib for address;
- WildcatMarket.sol ( #L18 ):
18: using SafeTransferLib for address;
9: import 'solady/utils/SafeTransferLib.sol';
12: using SafeTransferLib for address;
There is a subtle difference between the implementation of solady/solmate's SafeTransferLib
and OZ's SafeERC20. OZ's SafeERC20 checks if the token is a contract or not, while SafeTransferLib
does not:
@dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
There are 7 instances:
60: asset.safeTransferFrom(msg.sender, address(this), amount);
107: asset.safeTransfer(feeRecipient, withdrawableFees);
129: asset.safeTransfer(msg.sender, amount);
154: asset.safeTransferFrom(borrower, address(this), totalDebts - currentlyHeld);
157: asset.safeTransfer(borrower, currentlyHeld - totalDebts);
171: asset.safeTransfer(escrow, normalizedAmountWithdrawn);
179: asset.safeTransfer(accountAddress, normalizedAmountWithdrawn);
The rule is invalid for this project
There are 22 instances (click to show):
- ReentrancyGuard.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatArchController.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketController.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketControllerFactory.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatSanctionsEscrow.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatSanctionsSentinel.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- BoolUtils.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- Chainalysis.sol ( #L2 ):
2: pragma solidity ^0.8.20;
- Errors.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- FIFOQueue.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- FeeMath.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- LibStoredInitCode.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- MarketState.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- MathUtils.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- SafeCastLib.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- StringQuery.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- Withdrawal.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarket.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketBase.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketConfig.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketToken.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- WildcatMarketWithdrawals.sol ( #L2 ):
2: pragma solidity >=0.8.20;
Interfaces should not use fixed compiler versions, since they may be used by projects using a different version.
There are 3 instances:
- Chainalysis.sol ( #L2 ):
2: pragma solidity ^0.8.20;
- Errors.sol ( #L2 ):
2: pragma solidity >=0.8.20;
- StringQuery.sol ( #L2 ):
2: pragma solidity >=0.8.20;
There are 22 instances (click to show):
- ReentrancyGuard.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- WildcatArchController.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- WildcatMarketController.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- WildcatMarketControllerFactory.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- WildcatSanctionsEscrow.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- WildcatSanctionsSentinel.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- BoolUtils.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- Chainalysis.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- Errors.sol ( #L1 ):
1: // SPDX-License-Identifier: BUSL-1.1
- FIFOQueue.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- FeeMath.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- LibStoredInitCode.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- MarketState.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- MathUtils.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- SafeCastLib.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- StringQuery.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- Withdrawal.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- WildcatMarket.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- WildcatMarketBase.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- WildcatMarketConfig.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- WildcatMarketToken.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
- WildcatMarketWithdrawals.sol ( #L1 ):
1: // SPDX-License-Identifier: MIT
Use of block.timestamp
, in and of itself, is not evidence of an issue; there must be an incorrect usage in the code in order for there to be a vulnerability. There should also be a corresponding suggested fix.
There are 11 instances (click to show):
484: tmp.expiry = uint128(block.timestamp + 2 weeks);
495: if (block.timestamp < tmp.expiry) {
- WildcatMarket.sol ( #L160 ):
160: emit MarketClosed(block.timestamp);
114: lastInterestAccruedTimestamp: uint32(block.timestamp)
378: if (block.timestamp != state.lastInterestAccruedTimestamp) {
384: block.timestamp
434: if (state.lastInterestAccruedTimestamp != block.timestamp) {
439: block.timestamp
49: if (expiry > block.timestamp) {
95: state.pendingWithdrawalExpiry = uint32(block.timestamp + withdrawalBatchDuration);
141: if (expiry > block.timestamp) {
All internal
functions that are never used can be safely removed or commented out to save gas. If the functions are required by an interface, the contract should inherit from that interface and use the override keyword.
There are 71 instances (click to show):
/// @audit Invalid for library function
5: function and(bool a, bool b) internal pure returns (bool c) {
/// @audit Invalid for library function
11: function or(bool a, bool b) internal pure returns (bool c) {
/// @audit Invalid for library function
17: function xor(bool a, bool b) internal pure returns (bool c) {
/// @audit Invalid for library function
19: function empty(FIFOQueue storage arr) internal view returns (bool) {
/// @audit Invalid for library function
23: function first(FIFOQueue storage arr) internal view returns (uint32) {
/// @audit Invalid for library function
30: function at(FIFOQueue storage arr, uint256 index) internal view returns (uint32) {
/// @audit Invalid for library function
38: function length(FIFOQueue storage arr) internal view returns (uint128) {
/// @audit Invalid for library function
42: function values(FIFOQueue storage arr) internal view returns (uint32[] memory _values) {
/// @audit Invalid for library function
55: function push(FIFOQueue storage arr, uint32 value) internal {
/// @audit Invalid for library function
61: function shift(FIFOQueue storage arr) internal {
/// @audit Invalid for library function
70: function shiftN(FIFOQueue storage arr, uint128 n) internal {
- FeeMath.sol ( #L30-L33, #L40-L44, #L53-L58, #L142-L151 ):
/// @audit Invalid for library function
30: function calculateBaseInterest(
31: MarketState memory state,
32: uint256 timestamp
33: ) internal pure returns (uint256 baseInterestRay) {
/// @audit Invalid for library function
40: function applyProtocolFee(
41: MarketState memory state,
42: uint256 baseInterestRay,
43: uint256 protocolFeeBips
44: ) internal pure returns (uint256 protocolFee) {
/// @audit Invalid for library function
53: function updateDelinquency(
54: MarketState memory state,
55: uint256 timestamp,
56: uint256 delinquencyFeeBips,
57: uint256 delinquencyGracePeriod
58: ) internal pure returns (uint256 delinquencyFeeRay) {
/// @audit Invalid for library function
142: function updateScaleFactorAndFees(
143: MarketState memory state,
144: uint256 protocolFeeBips,
145: uint256 delinquencyFeeBips,
146: uint256 delinquencyGracePeriod,
147: uint256 timestamp
148: )
149: internal
150: pure
151: returns (uint256 baseInterestRay, uint256 delinquencyFeeRay, uint256 protocolFee)
/// @audit Invalid for library function
7: function deployInitCode(bytes memory data) internal returns (address initCodeStorage) {
/// @audit Invalid for library function
48: function getCreate2Prefix(address deployer) internal pure returns (uint256 create2Prefix) {
/// @audit Invalid for library function
54: function calculateCreate2Address(
55: uint256 create2Prefix,
56: bytes32 salt,
57: uint256 initCodeHash
58: ) internal pure returns (address create2Address) {
- MarketState.sol ( #L51, #L59, #L66-L69, #L76, #L87-L89, #L105-L108, #L123-L126, #L130, #L138 ):
/// @audit Invalid for library function
51: function totalSupply(MarketState memory state) internal pure returns (uint256) {
/// @audit Invalid for library function
59: function maximumDeposit(MarketState memory state) internal pure returns (uint256) {
/// @audit Invalid for library function
66: function normalizeAmount(
67: MarketState memory state,
68: uint256 amount
69: ) internal pure returns (uint256) {
/// @audit Invalid for library function
76: function scaleAmount(MarketState memory state, uint256 amount) internal pure returns (uint256) {
/// @audit Invalid for library function
87: function liquidityRequired(
88: MarketState memory state
89: ) internal pure returns (uint256 _liquidityRequired) {
/// @audit Invalid for library function
105: function withdrawableProtocolFees(
106: MarketState memory state,
107: uint256 totalAssets
108: ) internal pure returns (uint128) {
/// @audit Invalid for library function
123: function borrowableAssets(
124: MarketState memory state,
125: uint256 totalAssets
126: ) internal pure returns (uint256) {
/// @audit Invalid for library function
130: function hasPendingExpiredBatch(MarketState memory state) internal view returns (bool result) {
/// @audit Invalid for library function
138: function totalDebts(MarketState memory state) internal pure returns (uint256) {
/// @audit Invalid for library function
30: function calculateLinearInterestFromBips(
31: uint256 rateBip,
32: uint256 timeDelta
33: ) internal pure returns (uint256 result) {
/// @audit Invalid for library function
44: function min(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Invalid for library function
51: function max(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Invalid for library function
59: function satSub(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Invalid for library function
85: function bipMul(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Invalid for library function
105: function bipDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Invalid for library function
121: function bipToRay(uint256 a) internal pure returns (uint256 b) {
/// @audit Invalid for library function
138: function rayMul(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Invalid for library function
155: function rayDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit Invalid for library function
173: function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @audit Invalid for library function
191: function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
- SafeCastLib.sol ( #L17, #L21, #L25, #L29, #L33, #L37, #L41, #L45, #L49, #L53, #L57, #L61, #L65, #L69, #L73, #L77, #L81, #L85, #L89, #L93, #L97, #L101, #L105, #L109, #L113, #L117, #L121, #L125, #L129, #L133, #L137 ):
/// @audit Invalid for library function
17: function toUint8(uint256 x) internal pure returns (uint8 y) {
/// @audit Invalid for library function
21: function toUint16(uint256 x) internal pure returns (uint16 y) {
/// @audit Invalid for library function
25: function toUint24(uint256 x) internal pure returns (uint24 y) {
/// @audit Invalid for library function
29: function toUint32(uint256 x) internal pure returns (uint32 y) {
/// @audit Invalid for library function
33: function toUint40(uint256 x) internal pure returns (uint40 y) {
/// @audit Invalid for library function
37: function toUint48(uint256 x) internal pure returns (uint48 y) {
/// @audit Invalid for library function
41: function toUint56(uint256 x) internal pure returns (uint56 y) {
/// @audit Invalid for library function
45: function toUint64(uint256 x) internal pure returns (uint64 y) {
/// @audit Invalid for library function
49: function toUint72(uint256 x) internal pure returns (uint72 y) {
/// @audit Invalid for library function
53: function toUint80(uint256 x) internal pure returns (uint80 y) {
/// @audit Invalid for library function
57: function toUint88(uint256 x) internal pure returns (uint88 y) {
/// @audit Invalid for library function
61: function toUint96(uint256 x) internal pure returns (uint96 y) {
/// @audit Invalid for library function
65: function toUint104(uint256 x) internal pure returns (uint104 y) {
/// @audit Invalid for library function
69: function toUint112(uint256 x) internal pure returns (uint112 y) {
/// @audit Invalid for library function
73: function toUint120(uint256 x) internal pure returns (uint120 y) {
/// @audit Invalid for library function
77: function toUint128(uint256 x) internal pure returns (uint128 y) {
/// @audit Invalid for library function
81: function toUint136(uint256 x) internal pure returns (uint136 y) {
/// @audit Invalid for library function
85: function toUint144(uint256 x) internal pure returns (uint144 y) {
/// @audit Invalid for library function
89: function toUint152(uint256 x) internal pure returns (uint152 y) {
/// @audit Invalid for library function
93: function toUint160(uint256 x) internal pure returns (uint160 y) {
/// @audit Invalid for library function
97: function toUint168(uint256 x) internal pure returns (uint168 y) {
/// @audit Invalid for library function
101: function toUint176(uint256 x) internal pure returns (uint176 y) {
/// @audit Invalid for library function
105: function toUint184(uint256 x) internal pure returns (uint184 y) {
/// @audit Invalid for library function
109: function toUint192(uint256 x) internal pure returns (uint192 y) {
/// @audit Invalid for library function
113: function toUint200(uint256 x) internal pure returns (uint200 y) {
/// @audit Invalid for library function
117: function toUint208(uint256 x) internal pure returns (uint208 y) {
/// @audit Invalid for library function
121: function toUint216(uint256 x) internal pure returns (uint216 y) {
/// @audit Invalid for library function
125: function toUint224(uint256 x) internal pure returns (uint224 y) {
/// @audit Invalid for library function
129: function toUint232(uint256 x) internal pure returns (uint232 y) {
/// @audit Invalid for library function
133: function toUint240(uint256 x) internal pure returns (uint240 y) {
/// @audit Invalid for library function
137: function toUint248(uint256 x) internal pure returns (uint248 y) {
/// @audit Invalid for library function
38: function scaledOwedAmount(WithdrawalBatch memory batch) internal pure returns (uint104) {
/// @audit Invalid for library function
47: function availableLiquidityForPendingBatch(
48: WithdrawalBatch memory batch,
49: MarketState memory state,
50: uint256 totalAssets
51: ) internal pure returns (uint256) {
The rule is valid, but the following findings are invalid.
There are 21 instances (click to show):
- WildcatMarketController.sol ( #L370-L374 ):
/// @audit salt is used in assembly
370: function _deriveSalt(
371: address asset,
372: string memory namePrefix,
373: string memory symbolPrefix
374: ) internal pure returns (bytes32 salt) {
/// @audit c is used in assembly
5: function and(bool a, bool b) internal pure returns (bool c) {
/// @audit c is used in assembly
11: function or(bool a, bool b) internal pure returns (bool c) {
/// @audit c is used in assembly
17: function xor(bool a, bool b) internal pure returns (bool c) {
- LibStoredInitCode.sol ( #L7, #L48, #L54-L58, #L87-L90, #L106-L110 ):
/// @audit initCodeStorage is used in assembly
7: function deployInitCode(bytes memory data) internal returns (address initCodeStorage) {
/// @audit create2Prefix is used in assembly
48: function getCreate2Prefix(address deployer) internal pure returns (uint256 create2Prefix) {
/// @audit create2Address is used in assembly
54: function calculateCreate2Address(
55: uint256 create2Prefix,
56: bytes32 salt,
57: uint256 initCodeHash
58: ) internal pure returns (address create2Address) {
/// @audit deployment is used in assembly
87: function createWithStoredInitCode(
88: address initCodeStorage,
89: uint256 value
90: ) internal returns (address deployment) {
/// @audit deployment is used in assembly
106: function create2WithStoredInitCode(
107: address initCodeStorage,
108: bytes32 salt,
109: uint256 value
110: ) internal returns (address deployment) {
- MarketState.sol ( #L130 ):
/// @audit result is used in assembly
130: function hasPendingExpiredBatch(MarketState memory state) internal view returns (bool result) {
/// @audit c is used in assembly
59: function satSub(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit c is used in assembly
71: function ternary(
72: bool condition,
73: uint256 valueIfTrue,
74: uint256 valueIfFalse
75: ) internal pure returns (uint256 c) {
/// @audit c is used in assembly
85: function bipMul(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit c is used in assembly
105: function bipDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit b is used in assembly
121: function bipToRay(uint256 a) internal pure returns (uint256 b) {
/// @audit c is used in assembly
138: function rayMul(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit c is used in assembly
155: function rayDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {
/// @audit z is used in assembly
173: function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @audit z is used in assembly
191: function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @audit str is used in assembly
19: function bytes32ToString(bytes32 value) pure returns (string memory str) {
/// @audit str is used in assembly
33: function queryStringOrBytes32AsString(
34: address target,
35: uint256 rightPaddedFunctionSelector,
36: uint256 rightPaddedGenericErrorSelector
37: ) view returns (string memory str) {
Using delete instead of assigning zero to state variables does not save any extra gas with the optimizer on (saves 5-8 gas with optimizer completely off), so this finding is invalid, especially since if they were interested in gas savings, they'd have the optimizer enabled.
There are 6 instances:
- WildcatSanctionsSentinel.sol ( #L57 ):
57: sanctionOverrides[msg.sender][account] = false;
144: state.annualInterestBips = 0;
146: state.reserveRatioBips = 0;
171: account.scaledBalance = 0;
431: state.pendingWithdrawalExpiry = 0;
489: state.pendingWithdrawalExpiry = 0;
Only valid prior to solidity version 0.8.13, and only for require() statements, and at least one of those is not true for the examples below.
There are 12 instances (click to show):
201: bool hasOriginationFee = originationFeeAmount > 0;
205: (protocolFeeBips > 0 && nullFeeRecipient) ||
67: if (timeWithPenalty > 0) {
155: if (protocolFeeBips > 0) {
159: if (delinquencyFeeBips > 0) {
- WildcatMarket.sol ( #L147 ):
147: if (_withdrawalData.unpaidBatches.length() > 0) {
79: if ((parameters.protocolFeeBips > 0).and(parameters.feeRecipient == address(0))) {
170: if (scaledBalance > 0) {
428: if (availableLiquidity > 0) {
472: if (availableLiquidity > 0) {
32: if ((expiry == expiredBatchExpiry).and(expiry > 0)) {
112: if (availableLiquidity > 0) {
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.
There are 29 instances (click to show):
- WildcatMarketController.sol ( #L96, #L188, #L303, #L341, #L356, #L474, #L478, #L481, #L487, #L499 ):
/// @audit Invalid for solidity version >= 0.8.10
96: MarketControllerParameters memory parameters = controllerFactory.getMarketControllerParameters();
/// @audit Return value not used.
188: WildcatMarket(market).updateAccountAuthorization(lender, _authorizedLenders.contains(lender));
/// @audit Invalid for solidity version >= 0.8.10
303: if (!archController.isRegisteredBorrower(msg.sender)) {
/// @audit Invalid for solidity version >= 0.8.10
341: ) = controllerFactory.getProtocolFeeConfiguration();
/// @audit Return value not used.
356: archController.registerMarket(market);
/// @audit Invalid for solidity version >= 0.8.10
474: if (annualInterestBips < WildcatMarket(market).annualInterestBips()) {
/// @audit Invalid for solidity version >= 0.8.10
478: tmp.reserveRatioBips = uint128(WildcatMarket(market).reserveRatioBips());
/// @audit Return value not used.
481: WildcatMarket(market).setReserveRatioBips(9000);
/// @audit Return value not used.
487: WildcatMarket(market).setAnnualInterestBips(annualInterestBips);
/// @audit Return value not used.
499: WildcatMarket(market).setReserveRatioBips(uint256(tmp.reserveRatioBips).toUint16());
- WildcatMarketControllerFactory.sol ( #L283, #L299, #L329-L339 ):
/// @audit Invalid for solidity version >= 0.8.10
283: if (!archController.isRegisteredBorrower(msg.sender)) {
/// @audit Return value not used.
299: archController.registerController(controller);
/// @audit Invalid for solidity version >= 0.8.10
329: market = IWildcatMarketController(controller).deployMarket(
330: asset,
331: namePrefix,
332: symbolPrefix,
333: maxTotalSupply,
334: annualInterestBips,
335: delinquencyFeeBips,
336: withdrawalBatchDuration,
337: reserveRatioBips,
338: delinquencyGracePeriod
339: );
/// @audit Invalid for solidity version >= 0.8.10
18: (borrower, account, asset) = WildcatSanctionsSentinel(sentinel).tmpEscrowParams();
/// @audit Invalid for solidity version >= 0.8.10
22: return IERC20(asset).balanceOf(address(this));
/// @audit Invalid for solidity version >= 0.8.10
26: return !WildcatSanctionsSentinel(sentinel).isSanctioned(borrower, account);
/// @audit Return value not used.
38: IERC20(asset).transfer(account, amount);
/// @audit Invalid for solidity version >= 0.8.10
42: IChainalysisSanctionsList(chainalysisSanctionsList).isSanctioned(account);
/// @audit Invalid for solidity version >= 0.8.10
100: if (!IWildcatArchController(archController).isRegisteredMarket(msg.sender)) {
/// @audit Return value not used.
110: new WildcatSanctionsEscrow{ salt: keccak256(abi.encode(borrower, account, asset)) }();
- WildcatMarketBase.sol ( #L77, #L99, #L172-L176, #L204, #L239 ):
/// @audit Invalid for solidity version >= 0.8.10
77: MarketParameters memory parameters = IWildcatMarketController(msg.sender).getMarketParameters();
/// @audit Invalid for solidity version >= 0.8.10
99: decimals = IERC20Metadata(parameters.asset).decimals();
/// @audit Invalid for solidity version >= 0.8.10
172: address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow(
173: accountAddress,
174: borrower,
175: address(this)
176: );
/// @audit Invalid for solidity version >= 0.8.10
204: if (IWildcatMarketController(controller).isAuthorizedLender(accountAddress)) {
/// @audit Invalid for solidity version >= 0.8.10
239: return IERC20(asset).balanceOf(address(this));
/// @audit Invalid for solidity version >= 0.8.10
75: if (!IWildcatSanctionsSentinel(sentinel).isSanctioned(borrower, accountAddress)) {
/// @audit Invalid for solidity version >= 0.8.10
94: if (IWildcatSanctionsSentinel(sentinel).isSanctioned(borrower, accountAddress)) {
- WildcatMarketWithdrawals.sol ( #L164, #L166-L170 ):
/// @audit Invalid for solidity version >= 0.8.10
164: if (IWildcatSanctionsSentinel(sentinel).isSanctioned(borrower, accountAddress)) {
/// @audit Invalid for solidity version >= 0.8.10
166: address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow(
167: accountAddress,
168: borrower,
169: address(asset)
170: );
The rule is valid, but the following findings are invalid.
There is 1 instance:
- WildcatSanctionsSentinel.sol ( #L20 ):
20: mapping(address borrower => mapping(address account => bool sanctionOverride))
This should especially be done if the new value is not required to be different from the old value.
There are 6 instances:
- WildcatMarket.sol ( #L67 ):
67: emit Transfer(address(0), msg.sender, amount);
177: emit Transfer(accountAddress, escrow, state.normalizeAmount(scaledBalance));
524: emit Transfer(address(this), address(0), normalizedAmountPaid);
61: emit Approval(approver, spender, amount);
81: emit Transfer(from, to, amount);
- WildcatMarketWithdrawals.sol ( #L91 ):
91: emit Transfer(msg.sender, address(this), amount);
There are comments for the following state variables.
There is 1 instance:
- ReentrancyGuard.sol ( #L20 ):
20: uint256 private _reentrancyGuard;
The examples below are either not token transfers, or are making high-level transfer()
/transferFrom()
calls (which check for contract existence), or are from a library that checks for contract existence.
There are 7 instances (click to show):
- ReentrancyGuard.sol ( #L49 ):
49: constructor() {
- WildcatArchController.sol ( #L55 ):
55: constructor() {
- WildcatMarketController.sol ( #L94 ):
94: constructor() {
- WildcatMarketControllerFactory.sol ( #L72 ):
72: constructor(
- WildcatSanctionsEscrow.sol ( #L16 ):
16: constructor() {
- WildcatSanctionsSentinel.sol ( #L24 ):
24: constructor(address _archController, address _chainalysisSanctionsList) {
- WildcatMarketBase.sol ( #L76 ):
76: constructor() {
The rule is invalid for this project
There are 2 instances:
- WildcatMarketController.sol ( #L182, #L468-L471 ):
182: function updateLenderAuthorization(address lender, address[] memory markets) external {
468: function setAnnualInterestBips(
469: address market,
470: uint16 annualInterestBips
471: ) external virtual onlyBorrower onlyControlledMarket(market) {
Writing data to the free memory pointer without later updating the free memory pointer will cause there to be dirty bits at that memory location. Not updating the free memory pointer will make it harder for the optimizer to reason about whether the memory needs to be cleaned before use, which will lead to worse optimizations. Update the free memory pointer and annotate the block (assembly ("memory-safe") { ... }
) to avoid this issue.
There are 38 instances (click to show):
403: assembly {
404: if or(iszero(mload(namePrefix)), iszero(mload(symbolPrefix))) {
509: assembly {
510: if or(lt(value, min), gt(value, max)) {
6: assembly {
12: assembly {
18: assembly {
25: assembly {
36: assembly {
48: assembly {
61: assembly {
8: assembly {
35: if iszero(initCodeStorage) {
49: assembly {
- MarketState.sol ( #L132 ):
132: assembly {
- MathUtils.sol ( #L60, #L76, #L86, #L88, #L106, #L108, #L123, #L126, #L139, #L141, #L156, #L158, #L174, #L176, #L192, #L194 ):
60: assembly {
76: assembly {
86: assembly {
88: if iszero(or(iszero(b), iszero(gt(a, div(sub(not(0), HALF_BIP), b))))) {
106: assembly {
108: if or(iszero(b), gt(a, div(sub(not(0), div(b, 2)), BIP))) {
123: assembly {
126: if iszero(eq(div(b, BIP_RAY_RATIO), a)) {
139: assembly {
141: if iszero(or(iszero(b), iszero(gt(a, div(sub(not(0), HALF_RAY), b))))) {
156: assembly {
158: if or(iszero(b), gt(a, div(sub(not(0), div(b, 2)), RAY))) {
174: assembly {
176: if iszero(mul(d, iszero(mul(y, gt(x, div(not(0), y)))))) {
192: assembly {
194: if iszero(mul(d, iszero(mul(y, gt(x, div(not(0), y)))))) {
8: assembly {
9: if iszero(didNotOverflow) {
39: assembly {
47: if or(iszero(status), iszero(or(isBytes32, eq(returndatasize(), 0x60)))) {
49: if iszero(status) {
51: if returndatasize() {
66: assembly {
Manipulating storage in solidity is gas-intensive. It can be optimized by avoiding unnecessary storage updates when the new value equals the existing value. If the old value is equal to the new value, not re-storing the value will avoid a Gsreset (2900 gas), potentially at the expense of a Gcoldsload (2100 gas) or a Gwarmaccess (100 gas).
There are 2 instances:
- WildcatMarketControllerFactory.sol ( #L286 ):
286: _tmpMarketBorrowerParameter = msg.sender;
- WildcatMarketToken.sol ( #L60 ):
60: allowance[approver][spender] = amount;
The function calls in solidity are expensive. If the same result of the same function calls are to be used several times, the result should be cached to reduce the gas consumption of repeated calls.
There are 21 instances (click to show):
/// @audit assertValueInRange(annualInterestBips,MinimumAnnualInterestBips,MaximumAnnualInterestBips,AnnualInterestBipsOutOfBounds.selector)
410: assertValueInRange(
/// @audit assertValueInRange(delinquencyFeeBips,MinimumDelinquencyFeeBips,MaximumDelinquencyFeeBips,DelinquencyFeeBipsOutOfBounds.selector)
416: assertValueInRange(
/// @audit assertValueInRange(withdrawalBatchDuration,MinimumWithdrawalBatchDuration,MaximumWithdrawalBatchDuration,WithdrawalBatchDurationOutOfBounds.selector)
422: assertValueInRange(
/// @audit assertValueInRange(reserveRatioBips,MinimumReserveRatioBips,MaximumReserveRatioBips,ReserveRatioBipsOutOfBounds.selector)
428: assertValueInRange(
/// @audit assertValueInRange(delinquencyGracePeriod,MinimumDelinquencyGracePeriod,MaximumDelinquencyGracePeriod,DelinquencyGracePeriodOutOfBounds.selector)
434: assertValueInRange(
/// @audit revertWithSelector(AprChangeNotPending.selector)
493: revertWithSelector(AprChangeNotPending.selector);
/// @audit revertWithSelector(ExcessReserveRatioStillActive.selector)
496: revertWithSelector(ExcessReserveRatioStillActive.selector);
/// @audit previousTimeDelinquent.satSub(timeDelta)
115: state.timeDelinquent = previousTimeDelinquent.satSub(timeDelta).toUint32();
/// @audit previousTimeDelinquent.satSub(delinquencyGracePeriod)
119: uint256 secondsRemainingWithPenalty = previousTimeDelinquent.satSub(delinquencyGracePeriod);
/// @audit string.concat(parameters.namePrefix,queryName(parameters.asset))
97: name = string.concat(parameters.namePrefix, queryName(parameters.asset));
/// @audit string.concat(parameters.symbolPrefix,querySymbol(parameters.asset))
98: symbol = string.concat(parameters.symbolPrefix, querySymbol(parameters.asset));
/// @audit MathUtils.bipToRay(state.annualInterestBips)
321: uint256 apr = MathUtils.bipToRay(state.annualInterestBips).bipMul(BIP + protocolFeeBips);
/// @audit MathUtils.bipToRay(delinquencyFeeBips)
323: apr += MathUtils.bipToRay(delinquencyFeeBips);
/// @audit state.updateScaleFactorAndFees(protocolFeeBips,delinquencyFeeBips,delinquencyGracePeriod,expiry)
366: (uint256 baseInterestRay, uint256 delinquencyFeeRay, uint256 protocolFee) = state
/// @audit state.updateScaleFactorAndFees(protocolFeeBips,delinquencyFeeBips,delinquencyGracePeriod,block.timestamp)
379: (uint256 baseInterestRay, uint256 delinquencyFeeRay, uint256 protocolFee) = state
/// @audit state.updateScaleFactorAndFees(protocolFeeBips,delinquencyFeeBips,delinquencyGracePeriod,expiredBatchExpiry)
415: state.updateScaleFactorAndFees(
/// @audit state.updateScaleFactorAndFees(protocolFeeBips,delinquencyFeeBips,delinquencyGracePeriod,block.timestamp)
435: state.updateScaleFactorAndFees(
/// @audit _getAccount(from)
72: Account memory fromAccount = _getAccount(from);
/// @audit _getAccount(to)
76: Account memory toAccount = _getAccount(to);
/// @audit asset.safeTransfer(escrow,normalizedAmountWithdrawn)
171: asset.safeTransfer(escrow, normalizedAmountWithdrawn);
/// @audit asset.safeTransfer(accountAddress,normalizedAmountWithdrawn)
179: asset.safeTransfer(accountAddress, normalizedAmountWithdrawn);
The comments below are in the //x
format, which differs from the commonly used idiomatic comment syntax of //<space>x
. It is recommended to use a consistent comment syntax throughout.
There are 5 instances:
- ReentrancyGuard.sol ( #L7 ):
/// @audit It is an URL
7: * https://github.com/ProjectOpenSea/seaport/blob/main/contracts/lib/ReentrancyGuard.sol
/// @audit It is an URL
83: * see https://twitter.com/transmissions11/status/1451131036377571328
/// @audit It is an URL
103: * see https://twitter.com/transmissions11/status/1451131036377571328
/// @audit It is an URL
136: * see https://twitter.com/transmissions11/status/1451131036377571328
/// @audit It is an URL
153: * see https://twitter.com/transmissions11/status/1451131036377571328
The rule is valid, but the following findings are invalid.
There is 1 instance:
- WildcatMarketBase.sol ( #L267-L269 ):
/// @audit `_state` is not public
267: function previousState() external view returns (MarketState memory) {
268: return _state;
269: }
The hashes of literals should be stored in immutable variables, and the immutable variables should be used instead. If the hash is being used as a part of a function selector, the cast to bytes4
should also only be done once.
There is 1 instance:
- WildcatSanctionsSentinel.sol ( #L12 ):
/// @audit Assigned to constant
12: keccak256(type(WildcatSanctionsEscrow).creationCode);
The rule is valid, but the following findings are invalid.
There are 2 instances:
/// @audit uint256(account.approval)
210: if (uint256(account.approval) < uint256(requiredRole)) {
/// @audit uint256(requiredRole)
210: if (uint256(account.approval) < uint256(requiredRole)) {
If the arguments to the encode call can fit into the scratch space (two words or fewer), then it's more efficient to use assembly to generate the hash (80 gas):
keccak256(abi.encodePacked(x, y))
-> assembly {mstore(0x00, a); mstore(0x20, b); let hash := keccak256(0x00, 0x40); }
There is 1 instance:
- WildcatSanctionsSentinel.sol ( #L74-L81 ):
74: keccak256(
75: abi.encodePacked(
76: bytes1(0xff),
77: address(this),
78: keccak256(abi.encode(borrower, account, asset)),
79: WildcatSanctionsEscrowInitcodeHash
80: )
81: )
Mark data types as calldata
instead of memory
where possible. This makes it so that the data is not automatically loaded into memory. If the data passed into the function does not need to be changed (like updating values in an array), it can be passed in as calldata
. The one exception to this is if the argument must later be passed into another function that takes an argument that specifies memory
storage.
There are 9 instances (click to show):
- FeeMath.sol ( #L40-L44, #L89-L93, #L142-L148 ):
/// @audit state
40: function applyProtocolFee(
41: MarketState memory state,
42: uint256 baseInterestRay,
43: uint256 protocolFeeBips
44: ) internal pure returns (uint256 protocolFee) {
/// @audit state
89: function updateTimeDelinquentAndGetPenaltyTime(
90: MarketState memory state,
91: uint256 delinquencyGracePeriod,
92: uint256 timeDelta
93: ) internal pure returns (uint256 /* timeWithPenalty */) {
/// @audit state
142: function updateScaleFactorAndFees(
143: MarketState memory state,
144: uint256 protocolFeeBips,
145: uint256 delinquencyFeeBips,
146: uint256 delinquencyGracePeriod,
147: uint256 timestamp
148: )
- WildcatMarketBase.sol ( #L448, #L466, #L498-L503, #L528-L532 ):
/// @audit state
448: function _writeState(MarketState memory state) internal {
/// @audit state
466: function _processExpiredWithdrawalBatch(MarketState memory state) internal {
/// @audit batch
/// @audit state
498: function _applyWithdrawalBatchPayment(
499: WithdrawalBatch memory batch,
500: MarketState memory state,
501: uint32 expiry,
502: uint256 availableLiquidity
503: ) internal {
/// @audit batch
/// @audit state
528: function _applyWithdrawalBatchPaymentView(
529: WithdrawalBatch memory batch,
530: MarketState memory state,
531: uint256 availableLiquidity
532: ) internal pure {
The rule is valid, but the following findings are invalid.
There are 8 instances (click to show):
- BoolUtils.sol ( #L4 ):
4: library BoolUtils {
- FIFOQueue.sol ( #L16 ):
16: library FIFOQueueLib {
- FeeMath.sol ( #L11 ):
11: library FeeMath {
- LibStoredInitCode.sol ( #L4 ):
4: library LibStoredInitCode {
- MarketState.sol ( #L44 ):
44: library MarketStateLib {
- MathUtils.sol ( #L16 ):
16: library MathUtils {
- SafeCastLib.sol ( #L6 ):
6: library SafeCastLib {
- Withdrawal.sol ( #L37 ):
37: library WithdrawalLib {
Passing 0
or 0x0
as a function argument can sometimes result in a security issue(e.g. passing zero as the slippage parameter). A historical example is the infamous 0x0
address bug where numerous tokens were lost. This happens because 0
can be interpreted as an uninitialized address
, leading to transfers to the 0x0
address
, effectively burning tokens. Moreover, 0
as a denominator in division operations would cause a runtime exception. It's also often indicative of a logical error in the caller's code.
Consider using a constant variable with a descriptive name, so it's clear that the argument is intentionally being used, and for the right reasons.
There are 2 instances:
- WildcatMarket.sol ( #L67 ):
67: emit Transfer(address(0), msg.sender, amount);
- WildcatMarketBase.sol ( #L524 ):
524: emit Transfer(address(this), address(0), normalizedAmountPaid);
This especially problematic when the setter also emits the same value, which may be confusing to offline parsers.
There is 1 instance:
- WildcatMarketControllerFactory.sol ( #L286 ):
286: _tmpMarketBorrowerParameter = msg.sender;
According to EIP-2612, signatures used on exactly the deadline timestamp are supposed to be allowed. While the signature may or may not be used for the exact EIP-2612 use case (transfer approvals). For consistency's sake, all deadlines should follow this semantic. If the timestamp is an expiration rather than a deadline, consider whether it makes more sense to include the expiration timestamp as a valid timestamp, as is done for deadlines.
There are 3 instances:
- WildcatMarketController.sol ( #L495 ):
495: if (block.timestamp < tmp.expiry) {
49: if (expiry > block.timestamp) {
141: if (expiry > block.timestamp) {
Division by large numbers may result in the result being zero, due to solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator.
There are 2 instances:
23: size = (sizeInBits + 7) / 8;
73: size = (sizeInBits + 7) / 8;
Using the bytes
types for fixed-length strings is more efficient than having the EVM have to incur the overhead of string processing. Consider whether the value needs to be a string
. A good reason to keep it as a string
would be if the variable is defined in an interface that this project does not own.
There are 3 instances:
24: string public constant version = '1.0';
57: string public name;
60: string public symbol;
Tokens such as COMP or UNI will revert when an address' balance reaches type(uint96).max
. Ensure that the calls below can be broken up into smaller batches if necessary.
There are 8 instances (click to show):
- WildcatMarketController.sol ( #L346 ):
346: originationFeeAsset.safeTransferFrom(borrower, parameters.feeRecipient, originationFeeAmount);
- WildcatSanctionsEscrow.sol ( #L38 ):
38: IERC20(asset).transfer(account, amount);
60: asset.safeTransferFrom(msg.sender, address(this), amount);
107: asset.safeTransfer(feeRecipient, withdrawableFees);
129: asset.safeTransfer(msg.sender, amount);
157: asset.safeTransfer(borrower, currentlyHeld - totalDebts);
171: asset.safeTransfer(escrow, normalizedAmountWithdrawn);
179: asset.safeTransfer(accountAddress, normalizedAmountWithdrawn);
There aren't any checks to avoid an overflow which can happen inside an unchecked
block, so the following subtractions may overflow silently.
There are 2 instances:
23: size = (sizeInBits + 7) / 8;
73: size = (sizeInBits + 7) / 8;
There aren't any checks to avoid an underflow which can happen inside an unchecked
block, so the following subtractions may underflow silently.
There are 2 instances:
22: uint256 sizeInBits = 255 - uint256(value).ffs();
72: uint256 sizeInBits = 255 - value.ffs();
The rule is valid, but the following findings are invalid.
There is 1 instance:
- WildcatMarketController.sol ( #L484 ):
/// @audit 2 weeks
484: tmp.expiry = uint128(block.timestamp + 2 weeks);
Rebasing tokens are tokens that have each holder's balanceof()
increase over time. Aave aTokens are an example of such tokens. If rebasing tokens are used, rewards accrue to the contract holding the tokens, and cannot be withdrawn by the original depositor. To address the issue, track 'shares' deposited on a pro-rata basis, and let shares be redeemed for their proportion of the current balance at the time of the withdrawal.
There are 7 instances (click to show):
- WildcatMarketController.sol ( #L291-L301 ):
291: function deployMarket(
292: address asset,
293: string memory namePrefix,
294: string memory symbolPrefix,
295: uint128 maxTotalSupply,
296: uint16 annualInterestBips,
297: uint16 delinquencyFeeBips,
298: uint32 withdrawalBatchDuration,
299: uint16 reserveRatioBips,
300: uint32 delinquencyGracePeriod
301: ) external returns (address market) {
- WildcatSanctionsEscrow.sol ( #L33 ):
33: function releaseEscrow() public override {
42: function depositUpTo(
43: uint256 amount
44: ) public virtual nonReentrant returns (uint256 /* actualAmount */) {
96: function collectFees() external nonReentrant {
119: function borrow(uint256 amount) external onlyBorrower nonReentrant {
142: function closeMarket() external onlyController nonReentrant {
- WildcatMarketWithdrawals.sol ( #L137-L140 ):
137: function executeWithdrawal(
138: address accountAddress,
139: uint32 expiry
140: ) external nonReentrant returns (uint256) {
[M-01] Sponsor acknowledged
[M-02] Sponsor confirmed
[M-03] Sponsor confirmed
Both M-02/03 fixed by utilising SafeERC20