Platform: Code4rena
Start Date: 11/11/2022
Pot Size: $36,500 USDC
Total HM: 5
Participants: 62
Period: 3 days
Judge: berndartmueller
Id: 181
League: ETH
Rank: 59/62
Findings: 1
Award: $22.22
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: ReyAdmirado
Also found by: 0x4non, 0xRoxas, 0xab00, Awesome, Aymen0909, Bnke0x0, Deivitto, Diana, IllIllI, Rahoz, RaymondFam, Rolezn, Sathish9098, ajtra, aphak5010, aviggiano, c3phas, carlitox477, ch0bu, cryptostellar5, erictee, lukris02, martin, rotcivegaf, saian, shark, trustindistrust, zaskoh
22.2155 USDC - $22.22
immutable
Avoids a Gusset (20000 gas)
2022-11-non-fungible/contracts/Exchange.sol::33 => uint256 public isOpen; 2022-11-non-fungible/contracts/Exchange.sol::78 => IExecutionDelegate public executionDelegate; 2022-11-non-fungible/contracts/Exchange.sol::79 => IPolicyManager public policyManager; 2022-11-non-fungible/contracts/Exchange.sol::80 => address public oracle; 2022-11-non-fungible/contracts/Exchange.sol::81 => uint256 public blockRange;
If variables occupying the same slot are both written the same function or by the constructor avoids a separate Gsset (20000 gas). Reads of the variables are also cheaper
2022-11-non-fungible/contracts/Exchange.sol::80 => address public oracle;
<array>.length
should not be looked up in every loop of a for
loopEven memory arrays incur the overhead of bit tests and bit shifts to calculate the array length. Storage array length checks incur an extra Gwarmaccess (100 gas) PER-LOOP.
2022-11-non-fungible/contracts/Exchange.sol::307 => for (uint8 i = 0; i < orders.length; i++) { 2022-11-non-fungible/contracts/Exchange.sol::598 => for (uint8 i = 0; i < fees.length; i++) {
++i/i++
should be unchecked{++i}
/unchecked{++i}
when it is not possible for them to overflow, as is the case when used in for
and while
loops2022-11-non-fungible/contracts/Exchange.sol::177 => for (uint8 i=0; i < executionsLength; i++) { 2022-11-non-fungible/contracts/Exchange.sol::184 => for (uint8 i = 0; i < executionsLength; i++) { 2022-11-non-fungible/contracts/Exchange.sol::307 => for (uint8 i = 0; i < orders.length; i++) { 2022-11-non-fungible/contracts/Exchange.sol::598 => for (uint8 i = 0; i < fees.length; i++) {
require()
/revert()
strings longer than 32 bytes cost extra gas2022-11-non-fungible/contracts/Exchange.sol::49 => require(isInternal, "This function should not be called directly"); 2022-11-non-fungible/contracts/Exchange.sol::295 => require(!cancelledOrFilled[hash], "Order already cancelled or filled"); 2022-11-non-fungible/contracts/Exchange.sol::604 => require(totalFee <= price, "Total amount of fees are more than the price");
2022-11-non-fungible/contracts/Exchange.sol::461 => return _verify(trader, hashToSign, v, r, s); 2022-11-non-fungible/contracts/Exchange.sol::505 => return _verify(oracle, oracleHash, v, r, s); 2022-11-non-fungible/contracts/Pool.sol::80 => return _balances[user];
> 0
costs more gas than != 0
when used on a uint
in a require()
statement2022-11-non-fungible/contracts/Exchange.sol::412 => if (order.order.extraParams.length > 0 && order.order.extraParams[0] == 0x01) {
2022-11-non-fungible/contracts/Exchange.sol::44 => remainingETH = 0; 2022-11-non-fungible/contracts/Exchange.sol::61 => isOpen = 0; 2022-11-non-fungible/contracts/Exchange.sol::147 => uint256 public remainingETH = 0; 2022-11-non-fungible/contracts/Exchange.sol::184 => for (uint8 i = 0; i < executionsLength; i++) { 2022-11-non-fungible/contracts/Exchange.sol::307 => for (uint8 i = 0; i < orders.length; i++) { 2022-11-non-fungible/contracts/Exchange.sol::597 => uint256 totalFee = 0; 2022-11-non-fungible/contracts/Exchange.sol::598 => for (uint8 i = 0; i < fees.length; i++) {
++i
costs less gas than i++
, especially when it’s used in forloops (--i
/i--
too)2022-11-non-fungible/contracts/Exchange.sol::177 => for (uint8 i=0; i < executionsLength; i++) { 2022-11-non-fungible/contracts/Exchange.sol::184 => for (uint8 i = 0; i < executionsLength; i++) { 2022-11-non-fungible/contracts/Exchange.sol::307 => for (uint8 i = 0; i < orders.length; i++) { 2022-11-non-fungible/contracts/Exchange.sol::598 => for (uint8 i = 0; i < fees.length; i++) {
uints
/ints
smaller than 32 bytes (256 bits) incurs overheadWhen using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Use a larger size then downcast where needed
2022-11-non-fungible/contracts/Exchange.sol::72 => uint256 public constant INVERSE_BASIS_POINT = 10_000;
require()
or revert()
statements that check input arguments should be at the top of the functionChecks that involve constants should come before checks that involve state variables
2022-11-non-fungible/contracts/Exchange.sol::327 => require(address(_executionDelegate) != address(0), "Address cannot be zero"); 2022-11-non-fungible/contracts/Exchange.sol::336 => require(address(_policyManager) != address(0), "Address cannot be zero"); 2022-11-non-fungible/contracts/Exchange.sol::345 => require(_oracle != address(0), "Address cannot be zero"); 2022-11-non-fungible/contracts/Exchange.sol::630 => require(to != address(0), "Transfer to zero address"); 2022-11-non-fungible/contracts/Pool.sol::72 => require(to != address(0));
revert()
/require()
strings to save deployment gas2022-11-non-fungible/contracts/Exchange.sol::36 => require(isOpen == 1, "Closed"); 2022-11-non-fungible/contracts/Exchange.sol::49 => require(isInternal, "This function should not be called directly"); 2022-11-non-fungible/contracts/Exchange.sol::245 => require(_validateSignatures(sell, sellHash), "Sell failed authorization"); 2022-11-non-fungible/contracts/Exchange.sol::246 => require(_validateSignatures(buy, buyHash), "Buy failed authorization"); 2022-11-non-fungible/contracts/Exchange.sol::248 => require(_validateOrderParameters(sell.order, sellHash), "Sell has invalid parameters"); 2022-11-non-fungible/contracts/Exchange.sol::249 => require(_validateOrderParameters(buy.order, buyHash), "Buy has invalid parameters"); 2022-11-non-fungible/contracts/Exchange.sol::295 => require(!cancelledOrFilled[hash], "Order already cancelled or filled"); 2022-11-non-fungible/contracts/Exchange.sol::327 => require(address(_executionDelegate) != address(0), "Address cannot be zero"); 2022-11-non-fungible/contracts/Exchange.sol::336 => require(address(_policyManager) != address(0), "Address cannot be zero"); 2022-11-non-fungible/contracts/Exchange.sol::345 => require(_oracle != address(0), "Address cannot be zero"); 2022-11-non-fungible/contracts/Exchange.sol::414 => require(block.number - order.blockNumber < blockRange, "Signed block number out of range"); 2022-11-non-fungible/contracts/Exchange.sol::523 => require(v == 27 || v == 28, "Invalid v parameter"); 2022-11-non-fungible/contracts/Exchange.sol::545 => require(policyManager.isPolicyWhitelisted(sell.matchingPolicy), "Policy is not whitelisted"); 2022-11-non-fungible/contracts/Exchange.sol::549 => require(policyManager.isPolicyWhitelisted(buy.matchingPolicy), "Policy is not whitelisted"); 2022-11-non-fungible/contracts/Exchange.sol::552 => require(canMatch, "Orders cannot be matched"); 2022-11-non-fungible/contracts/Exchange.sol::604 => require(totalFee <= price, "Total amount of fees are more than the price"); 2022-11-non-fungible/contracts/Exchange.sol::630 => require(to != address(0), "Transfer to zero address"); 2022-11-non-fungible/contracts/Exchange.sol::632 => require(success, "ETH transfer failed"); 2022-11-non-fungible/contracts/Exchange.sol::636 => require(success, "Pool transfer failed"); 2022-11-non-fungible/contracts/Exchange.sol::641 => revert("Invalid payment token");
payable
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.
2022-11-non-fungible/contracts/Exchange.sol::56 => function open() external onlyOwner { 2022-11-non-fungible/contracts/Exchange.sol::60 => function close() external onlyOwner { 2022-11-non-fungible/contracts/Exchange.sol::66 => function _authorizeUpgrade(address) internal override onlyOwner {} 2022-11-non-fungible/contracts/Pool.sol::15 => function _authorizeUpgrade(address) internal override onlyOwner {}
Checking non-zero transfer values can avoid an expensive external call and save gas.
While this is done in some places, it’s not consistently done in the solution.
I suggest adding a non-zero-value check here:
2022-11-non-fungible/contracts/Exchange.sol::635 => bool success = IPool(POOL).transferFrom(from, to, amount);
2022-11-non-fungible/contracts/Exchange.sol::631 => (bool success,) = payable(to).call{value: amount}(""); 2022-11-non-fungible/contracts/Pool.sol::47 => (bool success,) = payable(msg.sender).call{value: amount}("");
bools
for storage incurs overhead2022-11-non-fungible/contracts/Exchange.sol::85 => mapping(bytes32 => bool) public cancelledOrFilled; 2022-11-non-fungible/contracts/Exchange.sol::146 => bool public isInternal = false;
private
rather than public
for constants, saves gasIf needed, the value can be read from the verified contract source code. Savings are due to the compiler not having to create non-payable getter functions for deployment call data, and not adding another entry to the method ID table
2022-11-non-fungible/contracts/Exchange.sol::70 => string public constant NAME = "Exchange"; 2022-11-non-fungible/contracts/Exchange.sol::71 => string public constant VERSION = "1.0"; 2022-11-non-fungible/contracts/Exchange.sol::72 => uint256 public constant INVERSE_BASIS_POINT = 10_000; 2022-11-non-fungible/contracts/Exchange.sol::73 => address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 2022-11-non-fungible/contracts/Exchange.sol::74 => address public constant POOL = 0xF66CfDf074D2FFD6A4037be3A669Ed04380Aef2B;
The code should be refactored such that they no longer exist, or the block should do something useful, such as emitting an event or reverting. If the contract is meant to be extended, the contract should be abstract and the function signatures be added without any default implementation. If the block is an empty if-statement block to avoid doing subsequent checks in the else-if/else conditions, the else-if/else conditions should be nested under the negation of the if-statement, because they involve different classes of checks, which may lead to the introduction of errors when the code is later modified (if(x){}else if(y){...}else{...} => if(!x){if(y){...}else{...}})
2022-11-non-fungible/contracts/Exchange.sol::66 => function _authorizeUpgrade(address) internal override onlyOwner {} 2022-11-non-fungible/contracts/Pool.sol::15 => function _authorizeUpgrade(address) internal override onlyOwner {}
#0 - c4-judge
2022-11-17T14:34:10Z
berndartmueller marked the issue as grade-b