Platform: Code4rena
Start Date: 06/01/2023
Pot Size: $210,500 USDC
Total HM: 27
Participants: 73
Period: 14 days
Judge: 0xean
Total Solo HM: 18
Id: 203
League: ETH
Rank: 66/73
Findings: 1
Award: $72.44
π Selected for report: 0
π Solo Findings: 0
π Selected for report: IllIllI
Also found by: 0xA5DF, 0xSmartContract, 0xhacksmithh, AkshaySrivastav, Awesome, Aymen0909, Bauer, Bnke0x0, Breeje, Budaghyan, Cyfrin, Madalad, NoamYakov, RHaO-sec, Rageur, RaymondFam, ReyAdmirado, Rolezn, SAAJ, SaharDevep, Sathish9098, __141345__, amshirif, arialblack14, c3phas, carlitox477, chaduke, delfin454000, descharre, nadin, oyc_109, pavankv, saneryee, shark
72.4433 USDC - $72.44
private
rather than public
for constant state variablesSaves 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. If needed to be viewed externally, the values can be read from the verified contract source code.
Shortening revert strings to fit in 32 bytes will decrease gas costs for deployment and gas costs when the revert condition has been met. Consider shortening the strings, or using Custom Errors as they are more gas efficient while allowing developers to describe the error in detail using NatSpec.
unchecked
for operations that cannot overflow/underflowBy bypassing Solidity's built in overflow/underflow checks using unchecked
, we can save gas. This is especially beneficial for the index variable within for loops (saves 120 gas per iteration).
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. 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 costs an average of about 21 gas per call to the function, in addition to the extra deployment cost (2400 per instance).
Saves 5000 deployment gas per instance and 80 runtime gas per instance: instead of keccak256(abi.encodePacked(a, b))
, use
assembly { mstore(0x00, a) mstore(0x20, b) let hashedVal := keccak256(0x00, 0x40) }
Note that assembly cannot be used to hash non-numerical values such as strings.
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.
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 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.
#0 - c4-judge
2023-01-24T23:15:25Z
0xean marked the issue as grade-b