Platform: Code4rena
Start Date: 22/08/2022
Pot Size: $50,000 USDC
Total HM: 4
Participants: 160
Period: 5 days
Judge: gzeon
Total Solo HM: 2
Id: 155
League: ETH
Rank: 152/160
Findings: 1
Award: $16.66
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0x040, 0x1f8b, 0xDjango, 0xNazgul, 0xNineDec, 0xSmartContract, 0xbepresent, 0xc0ffEE, 0xkatana, 2997ms, ACai, Amithuddar, Aymen0909, Ben, BipinSah, Bjorn_bug, Bnke0x0, CertoraInc, Ch_301, Chom, CodingNameKiki, Deivitto, DevABDee, DimitarDimitrov, Diraco, Dravee, ElKu, EthLedger, Fitraldys, Funen, GalloDaSballo, GimelSec, Guardian, IgnacioB, JC, JohnSmith, Junnon, KIntern_NA, Lambda, LeoS, Noah3o6, Olivierdem, Polandia94, R2, Randyyy, RaymondFam, Respx, ReyAdmirado, Rohan16, RoiEvenHaim, Rolezn, Ruhum, SaharAP, Saintcode_, SerMyVillage, Shishigami, Sm4rty, SooYa, TomJ, Tomio, Tomo, Waze, Yiko, _Adam, __141345__, a12jmx, ajtra, ak1, bobirichman, brgltd, bulej93, c3phas, cRat1st0s, carlitox477, catchup, ch0bu, d3e4, delfin454000, djxploit, durianSausage, erictee, exolorkistis, fatherOfBlocks, francoHacker, gogo, hyh, ignacio, jag, joestakey, karanctf, ladboy233, lucacez, lukris02, m_Rassska, martin, medikko, mics, mrpathfindr, natzuu, newfork01, oyc_109, pauliax, peritoflores, pfapostol, prasantgupta52, rbserver, ret2basic, rfa, robee, rokinot, rotcivegaf, rvierdiiev, sach1r0, saian, samruna, seyni, shark, shr1ftyy, sikorico, simon135, sryysryy, tay054, tnevler, wagmi, zishansami
16.6573 USDC - $16.66
Saves 6 gas per loop.
Recommended mitigation steps:
Use ++i
instead
Not overwriting the default value for stack variable saves 8 gas per loop.
Recommended mitigation steps:
Do not initialize them to 0.
++i/i++
should be unchecked{++i}/unchecked{i++} when it is not possible to overflowIn solidity 0.8.0 and above, checked arithmetic is implemented but can be deactivated by using unchecked
keyword. This saves 30-40gas per loop. Since i
is bounded in the loop, there is no need for checked arithmetic if proposal.targets.length
< 2<sup>256</sup>-1.
Implementing [G-01], [G-02], [G-03] would result in for each instance:
for (uint256 i; i < proposal.targets.length) { // ... unchecked{++i;} }
The compiler uses opcodes LT
and ISZERO
for solidity code that uses <, but only requires GT
for <=, which saves 3 gas.
Recommended mitigation steps:
return a <= b ? a : b;
Each extra memory word of bytes past the original 32 incurs an MSTORE
which cost 3 gas.
Recommended mitigation steps:
Use shorter string or preferably use custom errors. Indeed, custom errors from Solidity 0.8.4 not only saves gas upon deployment - ~9500 gas saved per custom error instead of a require statement, but it is also cheaper in a function call, 22 gas saved per require statement replaced with a custom error.
Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value.
Use a solidity version of at least 0.8.2 to get compiler automatic inlining. Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require() strings Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value
Constant expressions are re-calculated each time they are in use, costing an extra 97 gas than a constant every time they are called. See discussion here
If needed, the value can be read from the verified contract source code. Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table.
<x> * 2
is equivalent to <x> << 1
and <x> / 2
is the same as <x> >> 1
. The MUL
and DIV
opcodes cost 5 gas, whereas SHL
and SHR
only cost 3 gas.
Here is a division by 2 done in a while
loop :
abi.encode()
is less efficient than abi.encodePacked()
proposal.targets.length
should not be looked up in every loop of a for-loopThe overheads outlined below are PER LOOP, excluding the first loop
MLOAD
(3 gas)CALLDATALOAD
(3 gas)Caching the length changes each of these to a DUP<N>
(3 gas), and gets rid of the extra DUP<N>
needed to store the stack offset
When the argument of the function and the memory value are the same, you should rather emit the argument variable (memory value).
NounsDAOLogicV2.sol#630 : should be emit VotingDelaySet(oldVotingDelay, newVotingDelay);
(i.e. replace votingDelay
by newVotingDelay
)
NounsDAOLogicV2.sol#646 : should be emit VotingPeriodSet(oldVotingPeriod, newVotingPeriod);
(i.e. replace votingPeriod
by newVotingPeriod
)
NounsDAOLogicV2.sol#664 : should be emit ProposalThresholdBPSSet(oldProposalThresholdBPS, newProposalThresholdBPS);
(i.e. replace proposalThresholdBPS
by newProposalThresholdBPS
)
NounsDAOLogicV2.sol#778 : should be emit MinQuorumVotesBPSSet(oldParams.minQuorumVotesBPS, newMinQuorumVotesBPS);
(i.e. replace params.minQuorumVotesBPS
by newMinQuorumVotesBPS
)
NounsDAOLogicV2.sol#779 : should be emit MaxQuorumVotesBPSSet(oldParams.maxQuorumVotesBPS, newMaxQuorumVotesBPS);
(i.e. replace params.maxQuorumVotesBPS
by newMaxQuorumVotesBPS
)
NounsDAOLogicV2.sol#780 : should be emit QuorumCoefficientSet(oldParams.quorumCoefficient, newQuorumCoefficient);
(i.e. replace params.quorumCoefficient
by newQuorumCoefficient
)
memory
keyword instead of storage
When copying a state struct in memory, there are as many SLOADs and MSTOREs as there are slots. When reading the whole struct multiple times is not needed, it's better to actually only read the relevant field(s). When only some of the fields are read several times, these particular values should be cached instead of the whole state struct.
Here, the storage
keyword should be used instead of memory
: (see @audit)
function dynamicQuorumVotes( uint256 againstVotes, uint256 totalSupply, DynamicQuorumParams memory params //@audit should be storage instead of memory ) public pure returns (uint256) { uint256 againstVotesBPS = (10000 * againstVotes) / totalSupply; uint256 quorumAdjustmentBPS = (params.quorumCoefficient * againstVotesBPS) / 1e6;//@audit params occurence 1 uint256 adjustedQuorumBPS = params.minQuorumVotesBPS + quorumAdjustmentBPS;//@audit params occurence 2 uint256 quorumBPS = min(params.maxQuorumVotesBPS, adjustedQuorumBPS);//@audit params occurence 3 return bps2Uint(quorumBPS, totalSupply); } }
!= 0
instead of > 0
on uint variablesuint variables will never be lower than 0. Therefore, > 0 and != 0 have same meanings. Using != 0 can reduce the gas deployment cost, so it is worth using != 0 wherever possible.