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: 34/160
Findings: 2
Award: $55.50
🌟 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
38.8441 USDC - $38.84
The hardhat.config.ts
uses sensitive information imported from an un-committed environment file. The usage of either .env
imported variables or even plain pasted keys make it easier for an attacker to compromise the keys used for monitoring, deployment, testing and even if wallet private keys are used in such way funds can be compromised. The following data could be compromised if a leak happens or if the .gitignore
file is mistakenly deleted according to the imports performed on hardhat.config.ts
:
INFURA_PROJECT_ID ETHERSCAN_API_KEY MNEMONIC WALLET_PUBLIC_KEY WALLET_PRIVATE_KEY
Currently the mentioned event within NounsDAOLogicV2._withdraw()
emits the boolean sent
parameter.
function _withdraw() external { if (msg.sender != admin) { revert AdminOnly(); } uint256 amount = address(this).balance; (bool sent, ) = msg.sender.call{ value: amount }(''); emit Withdraw(amount, sent); }
The sent
parameter within the mentioned event is not checked after the low-level call, so there are scenarios where Withdraw
can be Withdraw(currentBalance, false)
which will be deceiving for filtering services and for event tracking. It is advised to check the truth of the sent parameter rather than emitting it on an event (and emit no events if the call fails, e.g. reverting before that line).
There are several parts of the codebase where require
statements with a revert string is used, and some other parts where if statements
with custom errors are used. It is advised to unify the criteria regarding error handling. It is remarked that the boolean check needs to be inverted while going from one method to another (the require revert on false statements whereas true statements trigger custom errors inside if statements). Also, as a reminder, using custom errors from 0.8.x
provide a clear feedback to users while saving gas against error strings.
It is advisable to have at least one indexed parameter per event in order to enable off-chain filtering for event tracking services. There are some events that have no indexed parameters:
This issue was found 14 times:
event ProposalCreated event ProposalCreatedWithRequirements event ProposalCanceled event ProposalQueued event ProposalExecuted event ProposalVetoed event VotingDelaySet event VotingPeriodSet event NewImplementation event ProposalThresholdBPSSet event QuorumVotesBPSSet event NewPendingAdmin event NewAdmin event NewVetoer
If a function is not meant to be called inside a contract, it should be marked as external instead. According to the Solidity Docs an inherited virtual external function can be overridden and its behavior can be changed as public.
This issue was found 8 times:
NounsDAOLogicV1.propose() NounsDAOLogicV1._burnVetoPower() NounsDAOLogicV1.proposalThreshold() NounsDAOLogicV1.quorumVotes() NounsDAOLogicV2.propose() NounsDAOLogicV2._burnVetoPower() NounsDAOLogicV2.proposalThreshold() NounsDAOLogicV2.maxQuorumVotes()
Using inline assembly for methods that are built-in within the current compiler versions avoid the need to use assembly which can make some compilers to interpret the code as complex. It is advised using inline assembly only when there is no other way to perform the intended behavior or when specific goals are meant to be achieved (such as optimizations).
NounsDAOLogicV1.getChainIdInternal() NounsDAOLogicV2.getChainIdInternal()
Which can be modified by uint256 chainId = block.chainid;
The main difference between both variable availability modifiers is that the constant
calculates the hash each time it is referenced whereas immutable
performs this calculation on deployment. More about this could be read here.
This issue was found 4 times:
NounsDAOLogicV1.DOMAIN_TYPEHASH NounsDAOLogicV1.BALLOT_TYPEHASH NounsDAOLogicV2.DOMAIN_TYPEHASH NounsDAOLogicV2.BALLOT_TYPEHASH
ecrecover()
can be malleableIt is advisable to consider using a third party library that avoids any signature malleability scenario that may occur while recovering signatures directly with ecrecover()
(such as OpenZeppelin's ECDSA
).
This issue was found 3 times:
NounsDAOLogicV1.castVoteBySig() NounsDAOLogicV2.castVoteBySig() ERC721Checkpointable.delegateBySig()
🌟 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.6568 USDC - $16.66
The usage of self made functions that check over-underflows as for solidity 0.8.x is pointless because the compiler comes with built in over-underflow checks. If those functions are meant to be implemented besides the fact of having built-in checks, unchecking their returns remove this built-in check and saves gas. Unchecked can be used because over/underflow is checked with require statements before performing the math op.
private
instead of public
save gasBecause constant variables should be declared an initialized as a one-liner, their value could be easily retrieved by reading directly the source code. Removing the public
modifier and using private
instead, does not create a function getter for the public constant saving around 3500 gas
on deployment.
Found 23 times:
NounsDAOLogicV1.sol /// @notice The minimum setable proposal threshold uint256 public constant MIN_PROPOSAL_THRESHOLD_BPS = 1; // 1 basis point or 0.01% /// @notice The maximum setable proposal threshold uint256 public constant MAX_PROPOSAL_THRESHOLD_BPS = 1_000; // 1,000 basis points or 10% /// @notice The minimum setable voting period uint256 public constant MIN_VOTING_PERIOD = 5_760; // About 24 hours /// @notice The max setable voting period uint256 public constant MAX_VOTING_PERIOD = 80_640; // About 2 weeks /// @notice The min setable voting delay uint256 public constant MIN_VOTING_DELAY = 1; /// @notice The max setable voting delay uint256 public constant MAX_VOTING_DELAY = 40_320; // About 1 week /// @notice The minimum setable quorum votes basis points uint256 public constant MIN_QUORUM_VOTES_BPS = 200; // 200 basis points or 2% /// @notice The maximum setable quorum votes basis points uint256 public constant MAX_QUORUM_VOTES_BPS = 2_000; // 2,000 basis points or 20% /// @notice The maximum number of actions that can be included in a proposal uint256 public constant proposalMaxOperations = 10; // 10 actions NounsDAOLogicV2.sol /// @notice The name of this contract string public constant name = 'Nouns DAO'; /// @notice The minimum setable proposal threshold uint256 public constant MIN_PROPOSAL_THRESHOLD_BPS = 1; // 1 basis point or 0.01% /// @notice The maximum setable proposal threshold uint256 public constant MAX_PROPOSAL_THRESHOLD_BPS = 1_000; // 1,000 basis points or 10% /// @notice The minimum setable voting period uint256 public constant MIN_VOTING_PERIOD = 5_760; // About 24 hours /// @notice The max setable voting period uint256 public constant MAX_VOTING_PERIOD = 80_640; // About 2 weeks /// @notice The min setable voting delay uint256 public constant MIN_VOTING_DELAY = 1; /// @notice The max setable voting delay uint256 public constant MAX_VOTING_DELAY = 40_320; // About 1 week /// @notice The lower bound of minimum quorum votes basis points uint256 public constant MIN_QUORUM_VOTES_BPS_LOWER_BOUND = 200; // 200 basis points or 2% /// @notice The upper bound of minimum quorum votes basis points uint256 public constant MIN_QUORUM_VOTES_BPS_UPPER_BOUND = 2_000; // 2,000 basis points or 20% /// @notice The upper bound of maximum quorum votes basis points uint256 public constant MAX_QUORUM_VOTES_BPS_UPPER_BOUND = 6_000; // 4,000 basis points or 60% /// @notice The maximum setable quorum votes basis points uint256 public constant MAX_QUORUM_VOTES_BPS = 2_000; // 2,000 basis points or 20% /// @notice The maximum number of actions that can be included in a proposal uint256 public constant proposalMaxOperations = 10; // 10 actions /// @notice The maximum priority fee used to cap gas refunds in `castRefundableVote` uint256 public constant MAX_REFUND_PRIORITY_FEE = 2 gwei; /// @notice The vote refund gas overhead, including 7K for ETH transfer and 29K for general transaction overhead uint256 public constant REFUND_BASE_GAS = 36000;
Custom errors available from 0.8.4
provide a clear and cheaper way of reverting saving around 50 gas
per reversal. Also they provide a cleaner feedback to the user if defined properly in a descriptive way. They can be triggered inside an if statement
.
Each memory slot has 32 bytes. If a revert string size exceeds this limit, more slots will be used incurring in an MSTORE
(adding up 3 gas
consumption). It is advised also using custom errors as for solidity 0.8.x which provide a clear and cheap way of reverting.
Found 47 times:
NounsDAOLogicV1.sol 'NounsDAO::propose: proposer votes below proposal threshold' 'NounsDAO::propose: proposal function information arity mismatch' 'NounsDAO::propose: one live proposal per proposer, found an already active proposal' 'NounsDAO::queue: proposal can only be queued if it is succeeded' 'NounsDAO::queueOrRevertInternal: identical proposal action already queued at eta' 'NounsDAO::execute: proposal can only be executed if it is queued' 'NounsDAO::cancel: cannot cancel executed proposal' 'NounsDAO::veto: cannot veto executed proposal' 'NounsDAO::state: invalid proposal id' 'NounsDAO::castVoteInternal: voting is closed' 'NounsDAO::castVoteInternal: invalid vote type' 'NounsDAO::castVoteInternal: voter already voted' 'NounsDAO::_setVotingDelay: admin only' 'NounsDAO::_setVotingPeriod: admin only' 'NounsDAO::_setVotingPeriod: invalid voting period' 'NounsDAO::castVoteBySig: invalid signature' 'NounsDAO::_setProposalThresholdBPS: admin only' 'NounsDAO::_setProposalThreshold: invalid proposal threshold' 'NounsDAO::_setPendingAdmin: admin only' 'NounsDAO::_acceptAdmin: pending admin only' 'NounsDAO::_setVetoer: vetoer only' 'NounsDAO::_burnVetoPower: vetoer only' NounsDAOLogicV2.sol 'NounsDAO::propose: proposer votes below proposal threshold' 'NounsDAO::propose: proposal function information arity mismatch' 'NounsDAO::propose: one live proposal per proposer, found an already active proposal' 'NounsDAO::queue: proposal can only be queued if it is succeeded' 'NounsDAO::queueOrRevertInternal: identical proposal action already queued at eta' 'NounsDAO::execute: proposal can only be executed if it is queued' 'NounsDAO::cancel: cannot cancel executed proposal' 'NounsDAO::veto: cannot veto executed proposal' 'NounsDAO::veto: veto power burned' 'NounsDAO::state: invalid proposal id' 'NounsDAO::castVoteInternal: voting is closed' 'NounsDAO::castVoteInternal: invalid vote type' 'NounsDAO::castVoteInternal: voter already voted' 'NounsDAO::_setVotingDelay: admin only' 'NounsDAO::_setVotingPeriod: admin only' 'NounsDAO::_setVotingPeriod: invalid voting period' 'NounsDAO::castVoteBySig: invalid signature' 'NounsDAO::_setProposalThresholdBPS: admin only' 'NounsDAO::_setProposalThreshold: invalid proposal threshold' 'NounsDAO::_setPendingAdmin: admin only' 'NounsDAO::_acceptAdmin: pending admin only' 'NounsDAO::_setVetoer: vetoer only' 'NounsDAO::_burnVetoPower: vetoer only' 'NounsDAO::_setQuorumCoefficient: admin only' 'NounsDAO::getDynamicQuorumParamsAt: block number exceeds 32 bits'