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: 87/160
Findings: 2
Award: $52.10
š Selected for report: 0
š Solo Findings: 0
š Selected for report: IllIllI
Also found by: 0bi, 0x040, 0x1337, 0x1f8b, 0xDjango, 0xNazgul, 0xNineDec, 0xRajeev, 0xSky, 0xSmartContract, 0xbepresent, 0xkatana, 0xmatt, 8olidity, Aymen0909, Bjorn_bug, Bnke0x0, CertoraInc, Ch_301, Chom, CodingNameKiki, Deivitto, DevABDee, DimitarDimitrov, Dravee, ElKu, Funen, GalloDaSballo, GimelSec, Guardian, Haruxe, JC, JansenC, Jeiwan, JohnSmith, KIntern_NA, Lambda, LeoS, Noah3o6, Olivierdem, R2, RaymondFam, Respx, ReyAdmirado, Rohan16, Rolezn, Ruhum, Saintcode_, Sm4rty, SooYa, Soosh, TomJ, Tomo, Trabajo_de_mates, Waze, _Adam, __141345__, ajtra, android69, asutorufos, auditor0517, berndartmueller, bobirichman, brgltd, c3phas, cRat1st0s, carlitox477, catchup, cccz, csanuragjain, d3e4, delfin454000, dipp, djxploit, durianSausage, erictee, exd0tpy, fatherOfBlocks, gogo, hyh, ladboy233, lukris02, mics, mrpathfindr, natzuu, oyc_109, p_crypt0, pashov, pauliax, pfapostol, prasantgupta52, rajatbeladiya, rbserver, ret2basic, rfa, robee, rokinot, rvierdiiev, sach1r0, saian, seyni, shenwilly, sikorico, simon135, sryysryy, sseefried, throttle, tnevler, tonisives, wagmi, xiaoming90, yixxas, z3s, zkhorse, zzzitron
35.4387 USDC - $35.44
The vetoer calls _setVetoer
in order to tranfers the ownership to the new address directly. There is a risk that the ownership is transferred to an invalid address, thus causing the contract to be without vetoer.
Consider a two step process where the vetoer nominates an prevetoer account and the nominated prevetoer account need to call an acceptVetoOwnership()
function for the transfer of vetoer to fully succeed. This ensures the nominated EOA account is a valid and active account
The require here should validates pendingAdmin != address(0)
instead msg.sender != address(0)
// Check caller is pendingAdmin and pendingAdmin ā address(0) require(msg.sender == pendingAdmin && msg.sender != address(0), 'NounsDAO::_acceptAdmin: pending admin only');
Consider use pendingAdmin != address(0)
in the require()
logic.
š 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
Not inlining costs more gas because of two extra JUMP
instructions and additional stack operations needed for function calls.
There are 3 instances of this issue:
contracts/governance/NounsDAOLogicV2.sol#L305 function queueOrRevertInternal( contracts/governance/NounsDAOLogicV2.sol#L866 function proposalCreationBlock(Proposal storage proposal) internal view returns (uint256) { contracts/governance/NounsDAOLogicV2.sol#L974 function _refundGas(uint256 startGas) internal {
In addition to checking that the function is not in the "Executed" state, you can also check that the function has not been processed.
Example:
# cancel function require(state(proposalId) != ProposalState.Canceled, 'NounsDAO::cancel: proposal already canceled'); # veto function require(state(proposalId) != ProposalState.Vetoed, 'NounsDAO::veto: proposal already vetoed');
There are 2 instances of this issue:
contracts/governance/NounsDAOLogicV2.sol#L347 require(state(proposalId) != ProposalState.Executed, 'NounsDAO::cancel: cannot cancel executed proposal'); contracts/governance/NounsDAOLogicV2.sol#L377 require(state(proposalId) != ProposalState.Executed, 'NounsDAO::veto: cannot veto executed proposal');
There is no risk that the loop counter can overflow, using solidity's unchecked block saves gas.
There are 4 instances of this issue:
governance/NounsDAOLogicV2.sol#292 => for (uint256 i = 0; i < proposal.targets.length; i++) { governance/NounsDAOLogicV2.sol#331 => for (uint256 i = 0; i < proposal.targets.length; i++) { governance/NounsDAOLogicV2.sol#359 => for (uint256 i = 0; i < proposal.targets.length; i++) { governance/NounsDAOLogicV2.sol#385 => for (uint256 i = 0; i < proposal.targets.length; i++) {
Unchecked implementation example:
for (uint256 i; i < 10;) { j++; unchecked { ++i; } }
Gas Report example using this gist:
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāā¬āāāāāāā¬āāāāāāāāā¬āāāāāāā¬āāāāāāāāāā® ā src/test/Unchecked.t.sol:Contract0 contract ā ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāŖāāāāāāāāāāāāāāāāāāŖāāāāāāāŖāāāāāāāāāŖāāāāāāāŖāāāāāāāāāā” ā Deployment Cost ā Deployment Size ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāā¼āāāāāāāāā¼āāāāāāā¼āāāāāāāāā⤠ā 55105 ā 307 ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāā¼āāāāāāāāā¼āāāāāāā¼āāāāāāāāā⤠ā Function Name ā min ā avg ā median ā max ā # calls ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāā¼āāāāāāāāā¼āāāāāāā¼āāāāāāāāā⤠ā withOutUnChecked ā 2068 ā 2068 ā 2068 ā 2068 ā 1 ā ā°āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāā“āāāāāāā“āāāāāāāāā“āāāāāāā“āāāāāāāāā⯠āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāā¬āāāāāāā¬āāāāāāāāā¬āāāāāāā¬āāāāāāāāāā® ā src/test/Unchecked.t.sol:Contract1 contract ā ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāŖāāāāāāāāāāāāāāāāāāŖāāāāāāāŖāāāāāāāāāŖāāāāāāāŖāāāāāāāāāā” ā Deployment Cost ā Deployment Size ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāā¼āāāāāāāāā¼āāāāāāā¼āāāāāāāāā⤠ā 53705 ā 300 ā ā ā ā ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāā¼āāāāāāāāā¼āāāāāāā¼āāāāāāāāā⤠ā Function Name ā min ā avg ā median ā max ā # calls ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāā¼āāāāāāā¼āāāāāāāāā¼āāāāāāā¼āāāāāāāāā⤠ā withUnchecked ā 1408 ā 1408 ā 1408 ā 1408 ā 1 ā ā°āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāā“āāāāāāā“āāāāāāāāā“āāāāāāā“āāāāāāāāāāÆ
Caching the array length outside a loop saves reading it on each iteration, as long as the array's length is not changed during the loop.
There are 4 instances of this issue:
governance/NounsDAOLogicV2.sol#292 for (uint256 i = 0; i < proposal.targets.length; i++) { governance/NounsDAOLogicV2.sol#331 for (uint256 i = 0; i < proposal.targets.length; i++) { governance/NounsDAOLogicV2.sol#359 for (uint256 i = 0; i < proposal.targets.length; i++) { governance/NounsDAOLogicV2.sol#385 for (uint256 i = 0; i < proposal.targets.length; i++) {
The contracts in scope use Solidity >=0.8.4, consider using Custom Errors as they are more gas efficient while allowing developers to describe the error in detail using Natspec