Platform: Code4rena
Start Date: 12/09/2022
Pot Size: $75,000 USDC
Total HM: 19
Participants: 110
Period: 7 days
Judge: HardlyDifficult
Total Solo HM: 9
Id: 160
League: ETH
Rank: 92/110
Findings: 1
Award: $35.36
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: CertoraInc
Also found by: 0x1f8b, 0x4non, 0x5rings, 0x85102, 0xNazgul, 0xSmartContract, 0xkatana, Amithuddar, Aymen0909, B2, Bnke0x0, CRYP70, Chom, ChristianKuri, CodingNameKiki, Deivitto, Diraco, Fitraldys, Funen, IgnacioB, JAGADESH, JC, Lambda, LeoS, Matin, Metatron, MiloTruck, Noah3o6, Ocean_Sky, Olivierdem, PaludoX0, RaymondFam, ReyAdmirado, Rohan16, Rolezn, Saintcode_, Sm4rty, SnowMan, StevenL, Tomio, Tomo, V_B, Waze, __141345__, ajtra, asutorufos, aysha, brgltd, bulej93, c3phas, ch0bu, d3e4, delfin454000, dharma09, djxploit, erictee, fatherOfBlocks, francoHacker, gianganhnguyen, gogo, got_targ, ignacio, jag, karanctf, ladboy233, leosathya, lukris02, m_Rassska, malinariy, martin, natzuu, pashov, peanuts, peiw, pfapostol, prasantgupta52, robee, simon135, slowmoses, sryysryy, tnevler
35.3567 USDC - $35.36
When 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.
When arguments are read-only on external functions, the data location should be calldata
58 Instances: -AuctionCrowdfund.sol line 196 -BuyCrowdfund.sol line 102 -BuyCrowdfundBase.sol line 96 -CollectionBuyCrowdfund.sol line 118 -Crowdfund.sol lines 191, 264, 265, 266, 308, 383 -CrowdfundFactory.sol line 110 -TokenDistributor.sol lines 331, 390 -PartyFactory.sol lines 28, 29, 30 -PartyGovernance.sol lines 394, 526, 655, 656, 657, 806, 807, 808, 810, 811, 926, 927, 973, 1001, 1012, 1083, 1084, 1095, 1096, 1106, 1107, 1127 -ArbitraryCallsProposal.sol lines 38, 95, 96, 97, 143, 145, 146, 203, 221 -FractionalizeProposal.sol line 40 -LibProposal.sol lines 9, 25, 26 -ListOnOpenseaProposal.sol lines 124, 243, 244 -ListOnZoraProposal.sol line 79 -ProposalExecutionEngine.sol lines 205, 228, 251,
Reading array length at each iteration of the loop consumes more gas than necessary. In the best case scenario (length read on a memory variable), caching the array length in the stack saves around 3 gas per iteration. In the worst case scenario (external calls at each iteration), the amount of gas wasted can be massive.
Consider storing the array’s length in a variable before the for-loop, and use this new variable instead 12 Instances: -CollectionBuyCrowfund.sol line 62 -Crowdfund.sol lines 180, 300 -TokenDistributor.sol lines 230, 239 -PartyGovernance.sol line 306 -ArbitraryCallsProposal.sol lines 52, 61, 78 -LibProposal.sol lines 14, 32 -ListOnOpenseaProposal.sol line 291
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 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
9 Instances: -Crowfund.sol 349, 424 -PartyGovernance.sol 569, 619, 670, 731, 980, 994, 1037
2 Instances: -Crowfund.sol line 441 -TokenDistributor.sol line 381
++I
INSTEAD OF I++
(OR USE ASSEMBLY WHEN APPLICABLE)Use ++i
instead of i++
. This is especially useful in for loops but this optimization can be used anywhere in your code.
1 Instance: -CollectionBuyCrowdfund.sol line 62
If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address…). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
As an example: for (uint256 i = 0; i < numIterations; ++i) { should be replaced with for (uint256 i; i < numIterations; ++i) {
12 Instances: -Crowdfund.sol lines 180, 242, 300, 348 -TokenDistributor.sol lines 230, 239 -PartyGovernance.sol line 432 -ArbitraryCallsProposal.sol lines 52, 61, 78 -LibProposal.sol lines 14, 32
>=
COSTS LESS GAS THAN >
The compiler uses opcodes GT
and ISZERO
for solidity code that uses >
, but only requires LT
for >=
, which saves 3 gas
6 Instances: -Crowfund.sol lines 144, 471 -ProposalExecutionEngine.sol line 236 -ArbitraryCallsProposal.sol lines 208, 226 -LibSafeERC721.sol 25
++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-LOOPSThe unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas per loop
for (uint256 i = 0; i < orders.length; /** NOTE: Removed i++ **/ ) { // Do the thing // Unchecked pre-increment is cheapest unchecked { ++i; } }
14 Instances: -CollectionBuyCrowdfund.sol line 62 -Crowdfund.sol lines 180, 242, 300, 348 -TokenDistributor.sol lines 230, 239 -PartyGovernance.sol line 306 -ArbitraryCallsProposal.sol lines 52, 61, 78 -LibProposal.sol 14, 32 -ListOnOpenseaProposal.sol 291