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: 52/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
require()
/revert()
strings longer than 32 bytes cost extra gasEach extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas
49 require(isInternal, "This function should not be called directly"); 295 require(!cancelledOrFilled[hash], "Order already cancelled or filled"); 604 require(totalFee <= price, "Total amount of fees are more than the price");
https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Exchange.sol
uint/int
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 Each operation involving a uint8
costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8
) as compared to ones involving uint256
, due to the compiler having to clear the higher bits of the memory word before operating on the uint8
, as well as the associated stack operations of doing so. Use a larger size then downcast where needed.
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{...}}
). Empty receive()
/fallback()
payable functions that are not used, can be removed to save deployment gas.
66 function _authorizeUpgrade(address) internal override onlyOwner {}
https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Exchange.sol
15 function _authorizeUpgrade(address) internal override onlyOwner {}
https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Pool.sol
storage
instead of memory
for structs/arrays saves gasWhen 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 declearing 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 incuring 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
455 (bytes32[] memory merklePath) = abi.decode(extraSignature, (bytes32[]));
https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Exchange.sol
public
functions not called by the contract should be declared external
insteadContracts are allowed to override their parents’ functions and change the visibility from external
to public
and can save gas by doing so
44 function withdraw(uint256 amount) public { 58 function transferFrom(address from, address to, uint256 amount) 79 function balanceOf(address user) public view returns (uint256) { 83 function totalSupply() public view returns (uint256) {
https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Pool.sol
From the Solidity doc:
Why do Solidity examples use bytes32 type instead of string?
If data can fit into 32 bytes, then you should use bytes32 datatype rather than bytes or strings as it is cheaper in solidity. Basically, any fixed size variable in solidity is cheaper than variable size. That will save gas on the contract.
Instances of string constant that can be replaced by bytes(1..32) constant
70 string public constant NAME = "Exchange"; 71 string public constant VERSION = "1.0";
https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Exchange.sol
Here, the values emitted shouldn’t be read from storage. When they are the same, consider emitting the memory value instead of the storage value
329 emit NewExecutionDelegate(executionDelegate); 338 emit NewPolicyManager(policyManager); 347 emit NewOracle(oracle); 355 emit NewBlockRange(blockRange);
https://github.com/code-423n4/2022-11-non-fungible/blob/main/contracts/Exchange.sol
#0 - c4-judge
2022-11-17T14:05:45Z
berndartmueller marked the issue as grade-b