Platform: Code4rena
Start Date: 03/05/2023
Pot Size: $60,500 USDC
Total HM: 25
Participants: 114
Period: 8 days
Judge: Picodes
Total Solo HM: 6
Id: 234
League: ETH
Rank: 49/114
Findings: 1
Award: $179.84
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: JCN
Also found by: 0x73696d616f, 0xSmartContract, 0xnev, Audit_Avengers, Aymen0909, Blckhv, Eurovickk, K42, Kenshin, Rageur, Raihan, ReyAdmirado, SAAJ, SAQ, Shubham, Tomio, Walter, ayden, codeslide, descharre, dicethedev, hunter_w3b, j4ld1na, kaveyjoe, okolicodes, patitonar, petrichor, pontifex, yongskiws
179.8397 USDC - $179.84
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.
_distributions
(#L69) and _quadraticVoters
(#L94) and _isSurplusFundsUpdated
(#L100) all get distributionId and they are internal so they can be combined to save gas.
hasClaimedReward(#L106)
and screeningVotesCast(#L112)
get distributionId and they are public so they can be combined to save gas.
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.
poolKey[tokenId_]
can be cached before #L522. this will result in 2 less complex storage reads in #L523 and #L529
we can make a stack variable and put the answer to treasury + fundingAmount_
inside it so we can use that in lines 62 and 64 instead of reading the treasury
with a SLOAD in #L64. this will also stop the usage of += which wastes gas in #L62.
_fundedExtraordinaryProposals.length
can be cached to possibly save gas. _fundedExtraordinaryProposals.length
is being checked in the if condition(#L208) and if it fails there is gonna be a extra read for it in #L213. so we can cache it before #L208 to save big amount of gas if the condition fails but lose only 3 gas if it passes
<x> += <y>
costs more gas than <x> = <x> + <y>
for state variablesUsing the addition operator instead of plus-equals saves gas (13 or 113 each dependant on the usage see here)
++i
costs less gas than i++
, especially when it’s used in for-loops (--i/i-- too)Saves 5 gas per loop
make the variable outside the loop and only give the value to variable inside
PositionManager.sol#L1882 instances
Use a solidity version of at least 0.8.15 because the conditions necessary for inlining are relaxed. Benchmarks show that the change significantly decreases the bytecode size (which impacts the deployment cost) while the effect on the runtime gas usage is smaller. Use a solidity version of at least 0.8.17 to get prevention of the incorrect removal of storage writes before calls to Yul functions that conditionally terminate the external EVM call; Simplify the starting offset of zero-length operations to zero. More efficient overflow checks for multiplication.
calldata
instead of memory
for read-only arguments in external functions saves gasWhen a function with a memory array is called externally, the abi.decode() step has to use a for-loop to copy each index of the calldata to the memory index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution.
bool
for storage incurs overheadBooleans 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(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past
Using ternary operator instead of the if else statement saves gas.
saves 6 gas per instance
a constant expression in a variable will compute the expression every time the variable is called. It's not the result of the expression that is stored, but the expression itself. this will result in keccak256() operation being done but if we used the result this wouldnt happen
these parts of the code can be pre calculated and given to the contract as constants this will stop the use of extra operations even if its for code readability consider putting comments instead.
0.9
can be used instead of 9 / 10
to stop a division operation
In Solidity, a constant expression in a variable will compute the expression every time the variable is called. It's not the result of the expression that is stored, but the expression itself.
As Solidity supports the scientific notation, constants of form 10**X can be rewritten as 1eX to save the gas cost from the calculation with the exponentiation operator **.
In the EVM, there is no opcode for non-strict inequalities (>=, <=) and two operations are performed (> + = or < + =).
consider replacing >= with the strict counterpart > and add - 1
to the second side
#0 - c4-judge
2023-05-17T11:06:46Z
Picodes marked the issue as grade-a