Platform: Code4rena
Start Date: 07/07/2023
Pot Size: $121,650 USDC
Total HM: 36
Participants: 111
Period: 7 days
Judge: Picodes
Total Solo HM: 13
Id: 258
League: ETH
Rank: 70/111
Findings: 1
Award: $24.30
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: c3phas
Also found by: 0x11singh99, 0xAnah, 0xn006e7, LeoS, Rageur, Raihan, ReyAdmirado, Rolezn, SAAJ, SAQ, SM3_SS, Udsen, alymurtazamemon, hunter_w3b, koxuan, naman1778, petrichor, ybansal2403
24.2984 USDC - $24.30
Avoids a Gsset (20000 gas) in the constructor, and replaces each Gwarmaccess (100 gas) with a PUSH32 (3 gas). While strings 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.
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.
drawManager
#L222 which is 20 bytes can be put together with claimCount
#L240 and canaryClaimCount
#L243 and largestTierClaimed
#L246 which will be less than 32 bytes so they will only use one slot instead of current 2. also its used in the same function with all 3 once which will reduce gas cost as well
Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key’s keccak256 hash (Gkeccak256 - 30 gas) and that calculation’s associated stack operations.
all 3 mappings can be a mapping to a struct and also in that struct mapping of #L36 and #L42 also can be a mapping to another struct
The instances below point to the second+ access of a state variable within a function. Caching of a state variable replace 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.
prizeTokenPerShare
should be cached before #L337 because its read from storage in #L364 as well and the second use is inside a for loop as well so caching this state var will save lots of gas
prizeTokenPerShare
can be cached before #L529 so we can use the cached version there and in #L540
if we change the #L538 from using -= to using just subtraction we can save gas if we had _reserve
cached. so we can cache _reserve
before #L535 and save gas
same logic as last one for _reserve
but here even if the first condition reverts we save 100 gas. cache before the if statement #L336
same as above for claimerRewards[msg.sender]
same logic as above for _yieldFeeTotalSupply
lastClosedDrawId
should be cached before #611 and use the cached version for that line and #L618 to save 100 gas
if the condition be true we will have a extra SLOAD for _liquidationPair
so we can cache it before and risk losing only 3 gas but save 100
if the condition be true we will have a extra SLOAD for _claimer
so we can cache it before and risk losing only 3 gas but save 100
cache answer to _currentExchangeRate()
and use it in the #L1111 emit
make the variable outside the loop and only give the value to variable inside
Checks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting a Gcoldsload (2100 gas*) in a function that may ultimately revert in the unhappy case.
the first if statement for revert is the most expensive to run so we can do it last
Using ternary operator instead of the if else statement saves gas.
payable
If a function modifier or require such as onlyOwner-admin 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.
Contracts most called functions could simply save gas by function ordering via Method ID. Calling a function at runtime will be cheaper if the function is positioned earlier in the order (has a relatively lower Method ID) because 22 gas are added to the cost of a function for every position that came before it. The caller can save on gas if you prioritize most called functions.
See more here. you can use this tool to get the optimized version for function and properties signitures
Instead of using address(this)
, it is more gas-efficient to pre-calculate and use the hardcoded address. Foundry's script.sol
and solmate's LibRlp.sol
contracts can help achieve this. - Reference
#0 - c4-judge
2023-07-18T19:00:19Z
Picodes marked the issue as grade-b
#1 - PierrickGT
2023-09-07T23:19:30Z
1 - Has been removed 2 - Has been fixed 3 - We would lose in code legibility 4, 5 - Has been fixed 6 - We would lose in code legibility 7, 8 - Has been fixed 9 - These functions are not payable so they shouldn't be marked as such 10 - We would lose in code legibility 11 - Does not save any gas