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: 24/160
Findings: 2
Award: $86.69
🌟 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
70.0142 USDC - $70.01
L-01 Missing checks for address(0x0) when assigning values to address state variables (1 instance) L-02 Unused receive() function will lock Ether in contract (1 instance) L-03 Math library unnecessarily overflows during some operations (1 instance)
Total: 3 instances over 3 issues.
N-01 Numeric values having to do with time should use time units for readability (6 instances) N-02 Missing event for critical parameter change (14 instances) N-03 Use a more recent version of solidity (6 instances) N-04 Non-library/interface files should use fixed compiler versions, not floating ones (6 instances) N-05 NatSpec is incomplete (4 instances) N-06 Event is missing indexed fields (5 instances) N-07 Large multiples of ten should use scientific notation (3 instances) N-08 require()/revert() statements should have descriptive reason strings (6 instances)
Total: 50 instances over 8 issues.
There is 1 instance in 1 file: File: contracts/governance/NounsDAOProxy.sol
admin = admin_;
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOProxy.sol#L71
If the intention is for the Ether to be used, the function should call another function, otherwise it should revert
There is 1 instance in 1 file: File: contracts/governance/NounsDAOLogicV2.sol
receive() external payable {}
In the example below, a + b may overflow. This particular case can be prevented by doing (a & b) + (a ^ b) / b. See this library for ways of doing math without this sort of issue. https://github.com/barakman/solidity-math-utils/blob/master/project/contracts/IntegralMath.sol
There is 1 instance in 1 file: File: contracts/base/ERC721Checkpointable.sol
function add96( uint96 a, uint96 b, string memory errorMessage ) internal pure returns (uint96) { uint96 c = a + b; require(c >= a, errorMessage); return c; }
There are units for seconds, minutes, hours, days, and weeks. https://docs.soliditylang.org/en/latest/units-and-global-variables.html#time-units
There are 6 instances in 2 files:
File: contracts/governance/NounsDAOLogicV1.sol
uint256 public constant MIN_VOTING_PERIOD = 5_760; // About 24 hours
uint256 public constant MAX_VOTING_PERIOD = 80_640; // About 2 weeks
uint256 public constant MAX_VOTING_DELAY = 40_320; // About 1 week
File: contracts/governance/NounsDAOLogicV2.sol
uint256 public constant MIN_VOTING_PERIOD = 5_760; // About 24 hours
uint256 public constant MAX_VOTING_PERIOD = 80_640; // About 2 weeks
uint256 public constant MAX_VOTING_DELAY = 40_320; // About 1 week
There are 14 instances in 4 files:
File: contracts/governance/NounsDAOLogicV1.sol
function getActions(uint256 proposalId) external view returns ( address[] memory targets, uint256[] memory values, string[] memory signatures, bytes[] memory calldatas ) { Proposal storage p = proposals[proposalId]; return (p.targets, p.values, p.signatures, p.calldatas); }
function getReceipt(uint256 proposalId, address voter) external view returns (Receipt memory) { return proposals[proposalId].receipts[voter]; }
File: contracts/governance/NounsDAOLogicV2.sol
function getActions(uint256 proposalId) external view returns ( address[] memory targets, uint256[] memory values, string[] memory signatures, bytes[] memory calldatas ) { Proposal storage p = _proposals[proposalId]; return (p.targets, p.values, p.signatures, p.calldatas); }
function getReceipt(uint256 proposalId, address voter) external view returns (Receipt memory) { return _proposals[proposalId].receipts[voter]; }
File: contracts/governance/NounsDAOInterfaces.sol
interface INounsDAOExecutor { function delay() external view returns (uint256);
function GRACE_PERIOD() external view returns (uint256); function acceptAdmin() external; function queuedTransactions(bytes32 hash) external view returns (bool); function queueTransaction( address target, uint256 value, string calldata signature, bytes calldata data, uint256 eta ) external returns (bytes32); function cancelTransaction( address target, uint256 value, string calldata signature, bytes calldata data, uint256 eta ) external; function executeTransaction( address target, uint256 value, string calldata signature, bytes calldata data, uint256 eta ) external payable returns (bytes memory);
}
interface NounsTokenLike { function getPriorVotes(address account, uint256 blockNumber) external view returns (uint96);
function totalSupply() external view returns (uint96);
function delegateTo(address callee, bytes memory data) internal { (bool success, bytes memory returnData) = callee.delegatecall(data); assembly { if eq(success, 0) { revert(add(returnData, 0x20), returndatasize()) } } }
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.12 to get string.concat() instead of abi.encodePacked(<str>,<str>). Use a solidity version of at least 0.8.13 to get the ability to use using for with a list of free functions
There are 6 instances in 6 files: File: contracts/governance/NounsDAOLogicV1.sol
pragma solidity ^0.8.6;
File: contracts/governance/NounsDAOLogicV2.sol
pragma solidity ^0.8.6;
File: contracts/governance/NounsDAOInterfaces.sol
pragma solidity ^0.8.6;
File: contracts/governance/NounsDAOProxy.sol
pragma solidity ^0.8.6;
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOProxy.sol#L36
File: contracts/base/ERC721Checkpointable.sol
pragma solidity ^0.8.6;
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Checkpointable.sol#L35
File: contracts/base/ERC721Enumerable.sol pragma solidity ^0.8.0; https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Enumerable.sol#L28
There are 6 instances in 6 files: File: contracts/governance/NounsDAOLogicV1.sol
pragma solidity ^0.8.6;
File: contracts/governance/NounsDAOLogicV2.sol
pragma solidity ^0.8.6;
File: contracts/governance/NounsDAOInterfaces.sol
pragma solidity ^0.8.6;
File: contracts/governance/NounsDAOProxy.sol
pragma solidity ^0.8.6;
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOProxy.sol#L36
File: contracts/base/ERC721Checkpointable.sol
pragma solidity ^0.8.6;
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Checkpointable.sol#L35
File: contracts/base/ERC721Enumerable.sol
pragma solidity ^0.8.0;
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Enumerable.sol#L28
There are many instances in 4 files:
File: contracts/governance/NounsDAOLogicV1.sol
File: contracts/governance/NounsDAOLogicV2.sol
File: contracts/governance/NounsDAOInterfaces.sol
File: contracts/base/ERC721Checkpointable.sol
Each event should use three indexed fields if there are three or more fields
There are 5 instances in 2 files:
File: contracts/governance/NounsDAOInterfaces.sol
event ProposalCreated( uint256 id, address proposer, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, uint256 startBlock, uint256 endBlock, string description );
event ProposalCreatedWithRequirements( uint256 id, address proposer, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, uint256 startBlock, uint256 endBlock, uint256 proposalThreshold, uint256 quorumVotes, string description );
event VoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 votes, string reason);
event RefundableVote(address indexed voter, uint256 refundAmount, bool refundSent);
File: contracts/base/ERC721Checkpointable.sol
event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Checkpointable.sol#L73
(e.g. 1e6) rather than decimal literals (e.g. 1000000), for readability
There are 3 instances in 2 files: File: contracts/governance/NounsDAOLogicV1.sol
return (number * bps) / 10000;
File: contracts/governance/NounsDAOLogicV2.sol
uint256 againstVotesBPS = (10000 * againstVotes) / totalSupply;
return (number * bps) / 10000;
There are 4 instances in 2 files: File: contracts/governance/NounsDAOLogicV2.sol
revert AdminOnly();
revert UnsafeUint16Cast();
File: contracts/governance/NounsDAOProxy.sol
revert(add(returnData, 0x20), returndatasize())
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOProxy.sol#L98
revert(free_mem_ptr, returndatasize())
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOProxy.sol#L118
🌟 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.6802 USDC - $16.68
G-01 Multiple address mappings can be combined into a single mapping of an address to a struct (2 instances) G-02 ++i costs less gas than i++, especially when it's used in for loops (8 instances) G-03 ++i/i++ should be unchecked{++i}/unchecked{i++} (8 instances) G-04 <array>.length should not be looked up in every loop of a for-loop (8 instances) G-05 Comparison operators (46 instances) G-06 Using private rather than public for constants saves gas (31 instances) G-07 calldata instead of memory for read-only function parameter (31 instances) G-08 internal functions only called once can be inlined to save gas (12 instances) G-09 Use a more recent version of solidity (6 instances) G-10 Empty blocks should be removed or emit something (1 instances) G-11 abi.encode() is less efficient than abi.encodePacked() (8 instances) G-12 Using storage instead of memory for structs/arrays saves gas (8 instances) G-13 Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead (74 instances) G-14 Use custom errors rather than revert()/require() strings to save gas (84 instances) G-15 require()/revert() strings longer than 32 bytes cost extra gas (86 instances) G-16 Multiplication/division by two should use bit shifting (2 instances) G-17 It costs more gas to initialize variables to zero than to let the default of zero be applied (10 instances) G-18 Require instead of && (multiple require statements to save gas) (20 instances) G-19 Expressions for constant values such as a call to keccak256(), should use immutable rather than constant (6 instances) G-20 Use Shift Right/Left instead of Division/Multiplication if possible
Total: 455 instances over 19 issues
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.
There are 2 instances in 2 files: File: contracts/base/ERC721Checkpointable.sol
/// @notice A record of votes checkpoints for each account, by index mapping(address => mapping(uint32 => Checkpoint)) public checkpoints; /// @notice The number of checkpoints for each account mapping(address => uint32) public numCheckpoints;
// Mapping from owner to list of owned token IDs mapping(address => mapping(uint256 => uint256)) private _ownedTokens; // Mapping from token ID to index of the owner tokens list mapping(uint256 => uint256) private _ownedTokensIndex;
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Enumerable.sol#L39-L43
There are 8 instances in 2 files: File: contracts/governance/NounsDAOLogicV1.sol
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
File: contracts/governance/NounsDAOLogicV2.sol
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
when it is not possible for them to overflow, as is the case when used in for- and while-loops
The 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 https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc#the-increment-in-for-loop-post-condition-can-be-made-unchecked
There are 8 instances in 2 files: File: contracts/governance/NounsDAOLogicV1.sol
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
File: contracts/governance/NounsDAOLogicV2.sol
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
The overheads outlined below are PER LOOP, excluding the first loop: storage arrays incur a Gwarmaccess (100 gas), memory arrays use MLOAD (3 gas), calldata arrays use 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.
There are 8 instances in 2 files: File: contracts/governance/NounsDAOLogicV1.sol
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
File: contracts/governance/NounsDAOLogicV2.sol
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
In the EVM, there is no opcode for >= or <=. When using greater than or equal, two operations are performed: > and =. Using strict comparison operators hence saves gas.
Mitigation: Replace <= with <, and >= with >. Do not forget to increment/decrement the compared variable When the comparison is with a constant storage variable, you can also do the increment in the storage variable declaration
There are 46 instances in 3 files: File: contracts/governance/NounsDAOLogicV1.sol
votingPeriod_ >= MIN_VOTING_PERIOD && votingPeriod_ <= MAX_VOTING_PERIOD,
votingDelay_ >= MIN_VOTING_DELAY && votingDelay_ <= MAX_VOTING_DELAY,
proposalThresholdBPS_ >= MIN_PROPOSAL_THRESHOLD_BPS && proposalThresholdBPS_ <= MAX_PROPOSAL_THRESHOLD_BPS,
quorumVotesBPS_ >= MIN_QUORUM_VOTES_BPS && quorumVotesBPS_ <= MAX_QUORUM_VOTES_BPS,
require(targets.length <= proposalMaxOperations, 'NounsDAO::propose: too many actions');
} else if (block.number <= proposal.startBlock) {
} else if (block.number <= proposal.endBlock) {
} else if (block.timestamp >= proposal.eta + timelock.GRACE_PERIOD()) {
newVotingDelay >= MIN_VOTING_DELAY && newVotingDelay <= MAX_VOTING_DELAY,
newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD,
newProposalThresholdBPS >= MIN_PROPOSAL_THRESHOLD_BPS && newProposalThresholdBPS <= MAX_PROPOSAL_THRESHOLD_BPS,
newQuorumVotesBPS >= MIN_QUORUM_VOTES_BPS && newQuorumVotesBPS <= MAX_QUORUM_VOTES_BPS,
File: contracts/governance/NounsDAOLogicV2.sol
votingPeriod_ >= MIN_VOTING_PERIOD && votingPeriod_ <= MAX_VOTING_PERIOD,
votingDelay_ >= MIN_VOTING_DELAY && votingDelay_ <= MAX_VOTING_DELAY,
proposalThresholdBPS_ >= MIN_PROPOSAL_THRESHOLD_BPS && proposalThresholdBPS_ <= MAX_PROPOSAL_THRESHOLD_BPS,
require(targets.length <= proposalMaxOperations, 'NounsDAO::propose: too many actions');
require(proposalCount >= proposalId, 'NounsDAO::state: invalid proposal id');
} else if (block.number <= proposal.startBlock) {
} else if (block.number <= proposal.endBlock) {
} else if (proposal.forVotes <= proposal.againstVotes || proposal.forVotes < quorumVotes(proposal.id)) {
} else if (block.timestamp >= proposal.eta + timelock.GRACE_PERIOD()) {
require(support <= 2, 'NounsDAO::castVoteInternal: invalid vote type');
newVotingDelay >= MIN_VOTING_DELAY && newVotingDelay <= MAX_VOTING_DELAY,
newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD,
newProposalThresholdBPS >= MIN_PROPOSAL_THRESHOLD_BPS && newProposalThresholdBPS <= MAX_PROPOSAL_THRESHOLD_BPS,
newMinQuorumVotesBPS >= MIN_QUORUM_VOTES_BPS_LOWER_BOUND && newMinQuorumVotesBPS <= MIN_QUORUM_VOTES_BPS_UPPER_BOUND,
newMaxQuorumVotesBPS <= MAX_QUORUM_VOTES_BPS_UPPER_BOUND,
params.minQuorumVotesBPS <= newMaxQuorumVotesBPS,
require(n <= type(uint32).max, errorMessage);
File: contracts/base/ERC721Checkpointable.sol
require(c >= a, errorMessage);
require(b <= a, errorMessage);
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.
There are 31 instances in 3 files: File: contracts/governance/NounsDAOLogicV1.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 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 /// @notice The EIP-712 typehash for the contract's domain bytes32 public constant DOMAIN_TYPEHASH = keccak256('EIP712Domain(string name,uint256 chainId,address verifyingContract)'); /// @notice The EIP-712 typehash for the ballot struct used by the contract bytes32 public constant BALLOT_TYPEHASH = keccak256('Ballot(uint256 proposalId,uint8 support)');
File: contracts/governance/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; /// @notice The EIP-712 typehash for the contract's domain bytes32 public constant DOMAIN_TYPEHASH = keccak256('EIP712Domain(string name,uint256 chainId,address verifyingContract)'); /// @notice The EIP-712 typehash for the ballot struct used by the contract bytes32 public constant BALLOT_TYPEHASH = keccak256('Ballot(uint256 proposalId,uint8 support)');
File: contracts/base/ERC721Checkpointable.sol
uint8 public constant decimals = 0;
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Checkpointable.sol#L41
bytes32 public constant DOMAIN_TYPEHASH =
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Checkpointable.sol#L59
bytes32 public constant DELEGATION_TYPEHASH =
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Checkpointable.sol#L63
If a reference type function parameter is read-only, it is cheaper in gas to use calldata instead of memory. Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory. Try to use calldata as a data location because it will avoid copies and also makes sure that the data cannot be modified.
There are 31 instances in 3 files: File: contracts/governance/NounsDAOLogicV1.sol
address[] memory targets, uint256[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description
string memory signature, bytes memory data,
address[] memory targets, uint256[] memory values, string[] memory signatures, bytes[] memory calldatas
File: contracts/governance/NounsDAOLogicV2.sol
address[] memory targets, uint256[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description
string memory signature, bytes memory data,
address[] memory targets, uint256[] memory values, string[] memory signatures, bytes[] memory calldatas
string memory reason
DynamicQuorumParams memory params
function _writeQuorumParamsCheckpoint(DynamicQuorumParams memory params) internal {
function safe32(uint256 n, string memory errorMessage) internal pure returns (uint32) {
File: contracts/governance/NounsDAOProxy.sol
function delegateTo(address callee, bytes memory data) internal {
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOProxy.sol#L94
function safe32(uint256 n, string memory errorMessage) internal pure returns (uint32) {
function safe96(uint256 n, string memory errorMessage) internal pure returns (uint96) { https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Checkpointable.sol#L258
string memory errorMessage
string memory errorMessage
Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.
There are 12 instances in 4 files: File: contracts/governance/NounsDAOLogicV1.sol
function queueOrRevertInternal( address target, uint256 value, string memory signature, bytes memory data, uint256 eta ) internal {
function castVoteInternal( address voter, uint256 proposalId, uint8 support ) internal returns (uint96) {
File: contracts/governance/NounsDAOLogicV2.sol
function queueOrRevertInternal( address target, uint256 value, string memory signature, bytes memory data, uint256 eta ) internal {
function castRefundableVoteInternal( uint256 proposalId, uint8 support, string memory reason ) internal {
function castVoteInternal( address voter, uint256 proposalId, uint8 support ) internal returns (uint96) {
File: contracts/base/ERC721Checkpointable.sol
function _beforeTokenTransfer( address from, address to, uint256 tokenId ) internal override {
function _moveDelegates( address srcRep, address dstRep, uint96 amount ) internal {
function _writeCheckpoint( address delegatee, uint32 nCheckpoints, uint96 oldVotes, uint96 newVotes ) internal {
function add96( uint96 a, uint96 b, string memory errorMessage ) internal pure returns (uint96) {
function sub96( uint96 a, uint96 b, string memory errorMessage ) internal pure returns (uint96) {
File: contracts/base/ERC721Enumerable.sol
function _beforeTokenTransfer( address from, address to, uint256 tokenId ) internal virtual override {
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.12 to get string.concat() instead of abi.encodePacked(<str>,<str>). Use a solidity version of at least 0.8.13 to get the ability to use using for with a list of free functions
There are 6 instances in 6 files: File: contracts/governance/NounsDAOLogicV1.sol
pragma solidity ^0.8.6;
File: contracts/governance/NounsDAOLogicV2.sol
pragma solidity ^0.8.6;
File: contracts/governance/NounsDAOInterfaces.sol
pragma solidity ^0.8.6;
File: contracts/governance/NounsDAOProxy.sol
pragma solidity ^0.8.6;
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOProxy.sol#L36
File: contracts/base/ERC721Checkpointable.sol
pragma solidity ^0.8.6;
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Checkpointable.sol#L35
File: contracts/base/ERC721Enumerable.sol pragma solidity ^0.8.0; https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Enumerable.sol#L28
The code should be refactored such that they no longer exist, or the block should do something useful, such as emitting an event or reverting. If the contract is meant to be extended, the contract should be abstract and the function signatures be added without any default implementation. If the block is an empty if-statement block to avoid doing subsequent checks in the else-if/else conditions, the else-if/else conditions should be nested under the negation of the if-statement, because they involve different classes of checks, which may lead to the introduction of errors when the code is later modified (if(x){}else if(y){...}else{...} => if(!x){if(y){...}else{...}})
There are 4 instances in 4 files: File: contracts/VariableInterestRate.sol
receive() external payable {}
There are 8 instances in 3 files: File: contracts/governance/NounsDAOLogicV1.sol
!timelock.queuedTransactions(keccak256(abi.encode(target, value, signature, data, eta))),
abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainIdInternal(), address(this))
bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support));
File: contracts/governance/NounsDAOLogicV2.sol
!timelock.queuedTransactions(keccak256(abi.encode(target, value, signature, data, eta))),
abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainIdInternal(), address(this))
bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support));
File: contracts/base/ERC721Checkpointable.sol
abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name())), getChainId(), address(this))
bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry));
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
There are 8 instances in 5 files: File: contracts/governance/NounsDAOLogicV1.sol
address[] memory targets, uint256[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description
address[] memory targets, uint256[] memory values, string[] memory signatures, bytes[] memory calldatas
File: contracts/governance/NounsDAOLogicV2.sol
address[] memory targets, uint256[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description
address[] memory targets, uint256[] memory values, string[] memory signatures, bytes[] memory calldatas
When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Use a larger size then downcast where needed
There are 74 instances in 4 files: File: contracts/governance/NounsDAOLogicV1.sol
uint8 support,
uint8 support, uint8 v,
uint8 support
uint96 votes = nouns.getPriorVotes(voter, proposal.startBlock - votingDelay);
File: contracts/governance/NounsDAOLogicV2.sol
uint8 support,
uint8 support,
uint96 votes = castVoteInternal(msg.sender, proposalId, support);
uint8 support,
uint8 support, uint8 v,
uint8 support
function _setMinQuorumVotesBPS(uint16 newMinQuorumVotesBPS) external {
uint16 oldMinQuorumVotesBPS = params.minQuorumVotesBPS;
function _setMaxQuorumVotesBPS(uint16 newMaxQuorumVotesBPS) external {
uint16 oldMaxQuorumVotesBPS = params.maxQuorumVotesBPS;
function _setQuorumCoefficient(uint32 newQuorumCoefficient) external { https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/governance/NounsDAOLogicV2.sol#L726
uint32 oldQuorumCoefficient = params.quorumCoefficient;
uint16 newMinQuorumVotesBPS, uint16 newMaxQuorumVotesBPS, uint32 newQuorumCoefficient
uint32 blockNumber = safe32(blockNumber_, 'NounsDAO::getDynamicQuorumParamsAt: block number exceeds 32 bits');
uint32 blockNumber = safe32(block.number, 'block number exceeds 32 bits');
function safe32(uint256 n, string memory errorMessage) internal pure returns (uint32) { require(n <= type(uint32).max, errorMessage); return uint32(n);
function safe16(uint256 n) internal pure returns (uint16) { if (n > type(uint16).max) { revert UnsafeUint16Cast(); } return uint16(n);
File: contracts/governance/NounsDAOInterfaces.sol
event VoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 votes, string reason);
event MinQuorumVotesBPSSet(uint16 oldMinQuorumVotesBPS, uint16 newMinQuorumVotesBPS); /// @notice Emitted when maxQuorumVotesBPS is set event MaxQuorumVotesBPSSet(uint16 oldMaxQuorumVotesBPS, uint16 newMaxQuorumVotesBPS); /// @notice Emitted when quorumCoefficient is set event QuorumCoefficientSet(uint32 oldQuorumCoefficient, uint32 newQuorumCoefficient);
uint8 support; /// @notice The number of votes the voter had, which were cast uint96 votes;
uint8 support; /// @notice The number of votes the voter had, which were cast uint96 votes;
uint16 minQuorumVotesBPS; /// @notice The maximum basis point number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed. uint16 maxQuorumVotesBPS; /// @notice The dynamic quorum coefficient /// @dev Assumed to be fixed point integer with 6 decimals, i.e 0.2 is represented as 0.2 * 1e6 = 200000 uint32 quorumCoefficient;
uint32 fromBlock;
function getPriorVotes(address account, uint256 blockNumber) external view returns (uint96);
function totalSupply() external view returns (uint96);
File: contracts/base/ERC721Checkpointable.sol
uint8 public constant decimals = 0;
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Checkpointable.sol#L41
uint32 fromBlock; uint96 votes;
mapping(address => mapping(uint32 => Checkpoint)) public checkpoints;
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Checkpointable.sol#L53
mapping(address => uint32) public numCheckpoints; https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Checkpointable.sol#L56
uint8 v,
function getCurrentVotes(address account) external view returns (uint96) { uint32 nCheckpoints = numCheckpoints[account];
uint32 nCheckpoints = numCheckpoints[account];
uint32 lower = 0; uint32 upper = nCheckpoints - 1;
uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
uint96 amount = votesToDelegate(delegator);
uint96 amount
uint32 srcRepNum = numCheckpoints[srcRep]; uint96 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0; uint96 srcRepNew = sub96(srcRepOld, amount, 'ERC721Checkpointable::_moveDelegates: amount underflows');
uint32 dstRepNum = numCheckpoints[dstRep]; uint96 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0; uint96 dstRepNew = add96(dstRepOld, amount, 'ERC721Checkpointable::_moveDelegates: amount overflows');
uint32 nCheckpoints, uint96 oldVotes, uint96 newVotes
uint32 blockNumber = safe32(
function safe32(uint256 n, string memory errorMessage) internal pure returns (uint32) { require(n < 2**32, errorMessage); return uint32(n); } function safe96(uint256 n, string memory errorMessage) internal pure returns (uint96) { require(n < 2**96, errorMessage); return uint96(n); } function add96( uint96 a, uint96 b, string memory errorMessage ) internal pure returns (uint96) { uint96 c = a + b; require(c >= a, errorMessage); return c; } function sub96( uint96 a, uint96 b,
Custom errors are available from solidity version 0.8.4. The instances below match or exceed that version
There are 84 instances in 4 files: File: contracts/governance/NounsDAOLogicV1.sol
require(address(timelock) == address(0), 'NounsDAO::initialize: can only initialize once'); require(msg.sender == admin, 'NounsDAO::initialize: admin only'); require(timelock_ != address(0), 'NounsDAO::initialize: invalid timelock address'); require(nouns_ != address(0), 'NounsDAO::initialize: invalid nouns address'); require( votingPeriod_ >= MIN_VOTING_PERIOD && votingPeriod_ <= MAX_VOTING_PERIOD, 'NounsDAO::initialize: invalid voting period' ); require( votingDelay_ >= MIN_VOTING_DELAY && votingDelay_ <= MAX_VOTING_DELAY, 'NounsDAO::initialize: invalid voting delay' ); require( proposalThresholdBPS_ >= MIN_PROPOSAL_THRESHOLD_BPS && proposalThresholdBPS_ <= MAX_PROPOSAL_THRESHOLD_BPS, 'NounsDAO::initialize: invalid proposal threshold' ); require( quorumVotesBPS_ >= MIN_QUORUM_VOTES_BPS && quorumVotesBPS_ <= MAX_QUORUM_VOTES_BPS, 'NounsDAO::initialize: invalid proposal threshold'
require( nouns.getPriorVotes(msg.sender, block.number - 1) > temp.proposalThreshold, 'NounsDAO::propose: proposer votes below proposal threshold' ); require( targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, 'NounsDAO::propose: proposal function information arity mismatch' ); require(targets.length != 0, 'NounsDAO::propose: must provide actions'); require(targets.length <= proposalMaxOperations, 'NounsDAO::propose: too many actions'); temp.latestProposalId = latestProposalIds[msg.sender]; if (temp.latestProposalId != 0) { ProposalState proposersLatestProposalState = state(temp.latestProposalId); require( proposersLatestProposalState != ProposalState.Active, 'NounsDAO::propose: one live proposal per proposer, found an already active proposal' ); require( proposersLatestProposalState != ProposalState.Pending, 'NounsDAO::propose: one live proposal per proposer, found an already pending proposal'
require( state(proposalId) == ProposalState.Succeeded, 'NounsDAO::queue: proposal can only be queued if it is succeeded'
require( !timelock.queuedTransactions(keccak256(abi.encode(target, value, signature, data, eta))), 'NounsDAO::queueOrRevertInternal: identical proposal action already queued at eta'
require(state(proposalId) != ProposalState.Executed, 'NounsDAO::cancel: cannot cancel executed proposal');
require( msg.sender == proposal.proposer || nouns.getPriorVotes(proposal.proposer, block.number - 1) < proposal.proposalThreshold, 'NounsDAO::cancel: proposer above threshold'
require(vetoer != address(0), 'NounsDAO::veto: veto power burned'); require(msg.sender == vetoer, 'NounsDAO::veto: only vetoer'); require(state(proposalId) != ProposalState.Executed, 'NounsDAO::veto: cannot veto executed proposal');
require(proposalCount >= proposalId, 'NounsDAO::state: invalid proposal id');
require(signatory != address(0), 'NounsDAO::castVoteBySig: invalid signature');
require(state(proposalId) == ProposalState.Active, 'NounsDAO::castVoteInternal: voting is closed'); require(support <= 2, 'NounsDAO::castVoteInternal: invalid vote type');
require(receipt.hasVoted == false, 'NounsDAO::castVoteInternal: voter already voted');
require(msg.sender == admin, 'NounsDAO::_setVotingDelay: admin only'); require( newVotingDelay >= MIN_VOTING_DELAY && newVotingDelay <= MAX_VOTING_DELAY, 'NounsDAO::_setVotingDelay: invalid voting delay'
require(msg.sender == admin, 'NounsDAO::_setVotingPeriod: admin only'); require( newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD, 'NounsDAO::_setVotingPeriod: invalid voting period'
require(msg.sender == admin, 'NounsDAO::_setProposalThresholdBPS: admin only'); require( newProposalThresholdBPS >= MIN_PROPOSAL_THRESHOLD_BPS && newProposalThresholdBPS <= MAX_PROPOSAL_THRESHOLD_BPS, 'NounsDAO::_setProposalThreshold: invalid proposal threshold'
require(msg.sender == admin, 'NounsDAO::_setQuorumVotesBPS: admin only'); require( newQuorumVotesBPS >= MIN_QUORUM_VOTES_BPS && newQuorumVotesBPS <= MAX_QUORUM_VOTES_BPS, 'NounsDAO::_setProposalThreshold: invalid proposal threshold'
require(msg.sender == admin, 'NounsDAO::_setPendingAdmin: admin only');
require(msg.sender == pendingAdmin && msg.sender != address(0), 'NounsDAO::_acceptAdmin: pending admin only');
require(msg.sender == vetoer, 'NounsDAO::_setVetoer: vetoer only');
require(msg.sender == vetoer, 'NounsDAO::_burnVetoPower: vetoer only');
File: contracts/governance/NounsDAOLogicV2.sol
require(address(timelock) == address(0), 'NounsDAO::initialize: can only initialize once'); require(msg.sender == admin, 'NounsDAO::initialize: admin only'); require(timelock_ != address(0), 'NounsDAO::initialize: invalid timelock address'); require(nouns_ != address(0), 'NounsDAO::initialize: invalid nouns address'); require( votingPeriod_ >= MIN_VOTING_PERIOD && votingPeriod_ <= MAX_VOTING_PERIOD, 'NounsDAO::initialize: invalid voting period' ); require( votingDelay_ >= MIN_VOTING_DELAY && votingDelay_ <= MAX_VOTING_DELAY, 'NounsDAO::initialize: invalid voting delay' ); require( proposalThresholdBPS_ >= MIN_PROPOSAL_THRESHOLD_BPS && proposalThresholdBPS_ <= MAX_PROPOSAL_THRESHOLD_BPS, 'NounsDAO::initialize: invalid proposal threshold bps'
require( nouns.getPriorVotes(msg.sender, block.number - 1) > temp.proposalThreshold, 'NounsDAO::propose: proposer votes below proposal threshold' ); require( targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, 'NounsDAO::propose: proposal function information arity mismatch' ); require(targets.length != 0, 'NounsDAO::propose: must provide actions'); require(targets.length <= proposalMaxOperations, 'NounsDAO::propose: too many actions');
require( proposersLatestProposalState != ProposalState.Active, 'NounsDAO::propose: one live proposal per proposer, found an already active proposal' ); require( proposersLatestProposalState != ProposalState.Pending, 'NounsDAO::propose: one live proposal per proposer, found an already pending proposal'
require( state(proposalId) == ProposalState.Succeeded, 'NounsDAO::queue: proposal can only be queued if it is succeeded'
require( !timelock.queuedTransactions(keccak256(abi.encode(target, value, signature, data, eta))), 'NounsDAO::queueOrRevertInternal: identical proposal action already queued at eta'
require( state(proposalId) == ProposalState.Queued, 'NounsDAO::execute: proposal can only be executed if it is queued'
require(state(proposalId) != ProposalState.Executed, 'NounsDAO::cancel: cannot cancel executed proposal');
require( msg.sender == proposal.proposer || nouns.getPriorVotes(proposal.proposer, block.number - 1) < proposal.proposalThreshold, 'NounsDAO::cancel: proposer above threshold'
require(vetoer != address(0), 'NounsDAO::veto: veto power burned'); require(msg.sender == vetoer, 'NounsDAO::veto: only vetoer'); require(state(proposalId) != ProposalState.Executed, 'NounsDAO::veto: cannot veto executed proposal');
require(proposalCount >= proposalId, 'NounsDAO::state: invalid proposal id');
require(state(proposalId) == ProposalState.Active, 'NounsDAO::castVoteInternal: voting is closed'); require(support <= 2, 'NounsDAO::castVoteInternal: invalid vote type');
require(receipt.hasVoted == false, 'NounsDAO::castVoteInternal: voter already voted');
require(msg.sender == admin, 'NounsDAO::_setVotingDelay: admin only'); require( newVotingDelay >= MIN_VOTING_DELAY && newVotingDelay <= MAX_VOTING_DELAY, 'NounsDAO::_setVotingDelay: invalid voting delay'
require(msg.sender == admin, 'NounsDAO::_setVotingPeriod: admin only'); require( newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD, 'NounsDAO::_setVotingPeriod: invalid voting period'
require(msg.sender == admin, 'NounsDAO::_setProposalThresholdBPS: admin only'); require( newProposalThresholdBPS >= MIN_PROPOSAL_THRESHOLD_BPS && newProposalThresholdBPS <= MAX_PROPOSAL_THRESHOLD_BPS, 'NounsDAO::_setProposalThreshold: invalid proposal threshold bps'
require(msg.sender == admin, 'NounsDAO::_setMinQuorumVotesBPS: admin only'); DynamicQuorumParams memory params = getDynamicQuorumParamsAt(block.number); require( newMinQuorumVotesBPS >= MIN_QUORUM_VOTES_BPS_LOWER_BOUND && newMinQuorumVotesBPS <= MIN_QUORUM_VOTES_BPS_UPPER_BOUND, 'NounsDAO::_setMinQuorumVotesBPS: invalid min quorum votes bps' ); require( newMinQuorumVotesBPS <= params.maxQuorumVotesBPS, 'NounsDAO::_setMinQuorumVotesBPS: min quorum votes bps greater than max'
require(msg.sender == admin, 'NounsDAO::_setMaxQuorumVotesBPS: admin only'); DynamicQuorumParams memory params = getDynamicQuorumParamsAt(block.number); require( newMaxQuorumVotesBPS <= MAX_QUORUM_VOTES_BPS_UPPER_BOUND, 'NounsDAO::_setMaxQuorumVotesBPS: invalid max quorum votes bps' ); require( params.minQuorumVotesBPS <= newMaxQuorumVotesBPS, 'NounsDAO::_setMaxQuorumVotesBPS: min quorum votes bps greater than max'
require(msg.sender == admin, 'NounsDAO::_setQuorumCoefficient: admin only');
require(msg.sender == admin, 'NounsDAO::_setPendingAdmin: admin only');
require(msg.sender == pendingAdmin && msg.sender != address(0), 'NounsDAO::_acceptAdmin: pending admin only');
require(msg.sender == vetoer, 'NounsDAO::_setVetoer: vetoer only');
require(msg.sender == vetoer, 'NounsDAO::_burnVetoPower: vetoer only');
File: contracts/governance/NounsDAOProxy.sol
require(msg.sender == admin, 'NounsDAOProxy::_setImplementation: admin only'); require(implementation_ != address(0), 'NounsDAOProxy::_setImplementation: invalid implementation address');
File: contracts/base/ERC721Checkpointable.sol
require(signatory != address(0), 'ERC721Checkpointable::delegateBySig: invalid signature'); require(nonce == nonces[signatory]++, 'ERC721Checkpointable::delegateBySig: invalid nonce'); require(block.timestamp <= expiry, 'ERC721Checkpointable::delegateBySig: signature expired');
require(blockNumber < block.number, 'ERC721Checkpointable::getPriorVotes: not yet determined');
There are 86 instances in 5 files: File: contracts/governance/NounsDAOLogicV1.sol
require(address(timelock) == address(0), 'NounsDAO::initialize: can only initialize once'); require(msg.sender == admin, 'NounsDAO::initialize: admin only'); require(timelock_ != address(0), 'NounsDAO::initialize: invalid timelock address'); require(nouns_ != address(0), 'NounsDAO::initialize: invalid nouns address'); require( votingPeriod_ >= MIN_VOTING_PERIOD && votingPeriod_ <= MAX_VOTING_PERIOD, 'NounsDAO::initialize: invalid voting period' ); require( votingDelay_ >= MIN_VOTING_DELAY && votingDelay_ <= MAX_VOTING_DELAY, 'NounsDAO::initialize: invalid voting delay' ); require( proposalThresholdBPS_ >= MIN_PROPOSAL_THRESHOLD_BPS && proposalThresholdBPS_ <= MAX_PROPOSAL_THRESHOLD_BPS, 'NounsDAO::initialize: invalid proposal threshold' ); require( quorumVotesBPS_ >= MIN_QUORUM_VOTES_BPS && quorumVotesBPS_ <= MAX_QUORUM_VOTES_BPS, 'NounsDAO::initialize: invalid proposal threshold'
require( nouns.getPriorVotes(msg.sender, block.number - 1) > temp.proposalThreshold, 'NounsDAO::propose: proposer votes below proposal threshold' ); require( targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, 'NounsDAO::propose: proposal function information arity mismatch' ); require(targets.length != 0, 'NounsDAO::propose: must provide actions'); require(targets.length <= proposalMaxOperations, 'NounsDAO::propose: too many actions'); temp.latestProposalId = latestProposalIds[msg.sender]; if (temp.latestProposalId != 0) { ProposalState proposersLatestProposalState = state(temp.latestProposalId); require( proposersLatestProposalState != ProposalState.Active, 'NounsDAO::propose: one live proposal per proposer, found an already active proposal' ); require( proposersLatestProposalState != ProposalState.Pending, 'NounsDAO::propose: one live proposal per proposer, found an already pending proposal'
require( state(proposalId) == ProposalState.Succeeded, 'NounsDAO::queue: proposal can only be queued if it is succeeded'
require( !timelock.queuedTransactions(keccak256(abi.encode(target, value, signature, data, eta))), 'NounsDAO::queueOrRevertInternal: identical proposal action already queued at eta'
require(state(proposalId) != ProposalState.Executed, 'NounsDAO::cancel: cannot cancel executed proposal');
require( msg.sender == proposal.proposer || nouns.getPriorVotes(proposal.proposer, block.number - 1) < proposal.proposalThreshold, 'NounsDAO::cancel: proposer above threshold'
require(vetoer != address(0), 'NounsDAO::veto: veto power burned'); require(msg.sender == vetoer, 'NounsDAO::veto: only vetoer'); require(state(proposalId) != ProposalState.Executed, 'NounsDAO::veto: cannot veto executed proposal');
require(proposalCount >= proposalId, 'NounsDAO::state: invalid proposal id');
require(signatory != address(0), 'NounsDAO::castVoteBySig: invalid signature');
require(state(proposalId) == ProposalState.Active, 'NounsDAO::castVoteInternal: voting is closed'); require(support <= 2, 'NounsDAO::castVoteInternal: invalid vote type');
require(receipt.hasVoted == false, 'NounsDAO::castVoteInternal: voter already voted');
require(msg.sender == admin, 'NounsDAO::_setVotingDelay: admin only'); require( newVotingDelay >= MIN_VOTING_DELAY && newVotingDelay <= MAX_VOTING_DELAY, 'NounsDAO::_setVotingDelay: invalid voting delay'
require(msg.sender == admin, 'NounsDAO::_setVotingPeriod: admin only'); require( newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD, 'NounsDAO::_setVotingPeriod: invalid voting period'
require(msg.sender == admin, 'NounsDAO::_setProposalThresholdBPS: admin only'); require( newProposalThresholdBPS >= MIN_PROPOSAL_THRESHOLD_BPS && newProposalThresholdBPS <= MAX_PROPOSAL_THRESHOLD_BPS, 'NounsDAO::_setProposalThreshold: invalid proposal threshold'
require(msg.sender == admin, 'NounsDAO::_setQuorumVotesBPS: admin only'); require( newQuorumVotesBPS >= MIN_QUORUM_VOTES_BPS && newQuorumVotesBPS <= MAX_QUORUM_VOTES_BPS, 'NounsDAO::_setProposalThreshold: invalid proposal threshold'
require(msg.sender == admin, 'NounsDAO::_setPendingAdmin: admin only');
require(msg.sender == pendingAdmin && msg.sender != address(0), 'NounsDAO::_acceptAdmin: pending admin only');
require(msg.sender == vetoer, 'NounsDAO::_setVetoer: vetoer only');
require(msg.sender == vetoer, 'NounsDAO::_burnVetoPower: vetoer only');
File: contracts/governance/NounsDAOLogicV2.sol
require(address(timelock) == address(0), 'NounsDAO::initialize: can only initialize once'); require(msg.sender == admin, 'NounsDAO::initialize: admin only'); require(timelock_ != address(0), 'NounsDAO::initialize: invalid timelock address'); require(nouns_ != address(0), 'NounsDAO::initialize: invalid nouns address'); require( votingPeriod_ >= MIN_VOTING_PERIOD && votingPeriod_ <= MAX_VOTING_PERIOD, 'NounsDAO::initialize: invalid voting period' ); require( votingDelay_ >= MIN_VOTING_DELAY && votingDelay_ <= MAX_VOTING_DELAY, 'NounsDAO::initialize: invalid voting delay' ); require( proposalThresholdBPS_ >= MIN_PROPOSAL_THRESHOLD_BPS && proposalThresholdBPS_ <= MAX_PROPOSAL_THRESHOLD_BPS, 'NounsDAO::initialize: invalid proposal threshold bps'
require( nouns.getPriorVotes(msg.sender, block.number - 1) > temp.proposalThreshold, 'NounsDAO::propose: proposer votes below proposal threshold' ); require( targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, 'NounsDAO::propose: proposal function information arity mismatch' ); require(targets.length != 0, 'NounsDAO::propose: must provide actions'); require(targets.length <= proposalMaxOperations, 'NounsDAO::propose: too many actions');
require( proposersLatestProposalState != ProposalState.Active, 'NounsDAO::propose: one live proposal per proposer, found an already active proposal' ); require( proposersLatestProposalState != ProposalState.Pending, 'NounsDAO::propose: one live proposal per proposer, found an already pending proposal'
require( state(proposalId) == ProposalState.Succeeded, 'NounsDAO::queue: proposal can only be queued if it is succeeded'
require( !timelock.queuedTransactions(keccak256(abi.encode(target, value, signature, data, eta))), 'NounsDAO::queueOrRevertInternal: identical proposal action already queued at eta'
require( state(proposalId) == ProposalState.Queued, 'NounsDAO::execute: proposal can only be executed if it is queued'
require(state(proposalId) != ProposalState.Executed, 'NounsDAO::cancel: cannot cancel executed proposal');
require( msg.sender == proposal.proposer || nouns.getPriorVotes(proposal.proposer, block.number - 1) < proposal.proposalThreshold, 'NounsDAO::cancel: proposer above threshold'
require(vetoer != address(0), 'NounsDAO::veto: veto power burned'); require(msg.sender == vetoer, 'NounsDAO::veto: only vetoer'); require(state(proposalId) != ProposalState.Executed, 'NounsDAO::veto: cannot veto executed proposal');
require(proposalCount >= proposalId, 'NounsDAO::state: invalid proposal id');
require(state(proposalId) == ProposalState.Active, 'NounsDAO::castVoteInternal: voting is closed'); require(support <= 2, 'NounsDAO::castVoteInternal: invalid vote type');
require(receipt.hasVoted == false, 'NounsDAO::castVoteInternal: voter already voted');
require(msg.sender == admin, 'NounsDAO::_setVotingDelay: admin only'); require( newVotingDelay >= MIN_VOTING_DELAY && newVotingDelay <= MAX_VOTING_DELAY, 'NounsDAO::_setVotingDelay: invalid voting delay'
require(msg.sender == admin, 'NounsDAO::_setVotingPeriod: admin only'); require( newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD, 'NounsDAO::_setVotingPeriod: invalid voting period'
require(msg.sender == admin, 'NounsDAO::_setProposalThresholdBPS: admin only'); require( newProposalThresholdBPS >= MIN_PROPOSAL_THRESHOLD_BPS && newProposalThresholdBPS <= MAX_PROPOSAL_THRESHOLD_BPS, 'NounsDAO::_setProposalThreshold: invalid proposal threshold bps'
require(msg.sender == admin, 'NounsDAO::_setMinQuorumVotesBPS: admin only'); DynamicQuorumParams memory params = getDynamicQuorumParamsAt(block.number); require( newMinQuorumVotesBPS >= MIN_QUORUM_VOTES_BPS_LOWER_BOUND && newMinQuorumVotesBPS <= MIN_QUORUM_VOTES_BPS_UPPER_BOUND, 'NounsDAO::_setMinQuorumVotesBPS: invalid min quorum votes bps' ); require( newMinQuorumVotesBPS <= params.maxQuorumVotesBPS, 'NounsDAO::_setMinQuorumVotesBPS: min quorum votes bps greater than max'
require(msg.sender == admin, 'NounsDAO::_setMaxQuorumVotesBPS: admin only'); DynamicQuorumParams memory params = getDynamicQuorumParamsAt(block.number); require( newMaxQuorumVotesBPS <= MAX_QUORUM_VOTES_BPS_UPPER_BOUND, 'NounsDAO::_setMaxQuorumVotesBPS: invalid max quorum votes bps' ); require( params.minQuorumVotesBPS <= newMaxQuorumVotesBPS, 'NounsDAO::_setMaxQuorumVotesBPS: min quorum votes bps greater than max'
require(msg.sender == admin, 'NounsDAO::_setQuorumCoefficient: admin only');
require(msg.sender == admin, 'NounsDAO::_setPendingAdmin: admin only');
require(msg.sender == pendingAdmin && msg.sender != address(0), 'NounsDAO::_acceptAdmin: pending admin only');
require(msg.sender == vetoer, 'NounsDAO::_setVetoer: vetoer only');
require(msg.sender == vetoer, 'NounsDAO::_burnVetoPower: vetoer only');
File: contracts/governance/NounsDAOProxy.sol
require(msg.sender == admin, 'NounsDAOProxy::_setImplementation: admin only'); require(implementation_ != address(0), 'NounsDAOProxy::_setImplementation: invalid implementation address');
File: contracts/base/ERC721Checkpointable.sol
require(signatory != address(0), 'ERC721Checkpointable::delegateBySig: invalid signature'); require(nonce == nonces[signatory]++, 'ERC721Checkpointable::delegateBySig: invalid nonce'); require(block.timestamp <= expiry, 'ERC721Checkpointable::delegateBySig: signature expired');
require(blockNumber < block.number, 'ERC721Checkpointable::getPriorVotes: not yet determined');
File: contracts/base/ERC721Enumerable.sol
require(index < ERC721.balanceOf(owner), 'ERC721Enumerable: owner index out of bounds');
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Enumerable.sol#L62
require(index < ERC721Enumerable.totalSupply(), 'ERC721Enumerable: global index out of bounds');
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Enumerable.sol#L77
<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
There are 2 instances in 2 files: File: contracts/governance/NounsDAOLogicV2.sol
uint256 center = upper - (upper - lower) / 2;
File: contracts/base/ERC721Checkpointable.sol
uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
See default values: https://docs.soliditylang.org/en/v0.8.13/control-structures.html#default-value
There are 10 instances in 3 files: File: contracts/governance/NounsDAOLogicV1.sol
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
File: contracts/governance/NounsDAOLogicV2.sol
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
for (uint256 i = 0; i < proposal.targets.length; i++) {
File: contracts/base/ERC721Checkpointable.sol
uint8 public constant decimals = 0;
https://github.com/code-423n4/2022-08-nounsdao/blob/main/contracts/base/ERC721Checkpointable.sol#L41
uint32 lower = 0;
Require statements including conditions with the && operator can be broken down in multiple require statements to save gas.
There are 20 instances in 21 files: File: contracts/governance/NounsDAOLogicV1.sol
require( votingPeriod_ >= MIN_VOTING_PERIOD && votingPeriod_ <= MAX_VOTING_PERIOD, 'NounsDAO::initialize: invalid voting period' ); require( votingDelay_ >= MIN_VOTING_DELAY && votingDelay_ <= MAX_VOTING_DELAY, 'NounsDAO::initialize: invalid voting delay' ); require( proposalThresholdBPS_ >= MIN_PROPOSAL_THRESHOLD_BPS && proposalThresholdBPS_ <= MAX_PROPOSAL_THRESHOLD_BPS, 'NounsDAO::initialize: invalid proposal threshold' ); require( quorumVotesBPS_ >= MIN_QUORUM_VOTES_BPS && quorumVotesBPS_ <= MAX_QUORUM_VOTES_BPS, 'NounsDAO::initialize: invalid proposal threshold' );
require( targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, 'NounsDAO::propose: proposal function information arity mismatch' );
require( newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD, 'NounsDAO::_setVotingPeriod: invalid voting period' );
require( newProposalThresholdBPS >= MIN_PROPOSAL_THRESHOLD_BPS && newProposalThresholdBPS <= MAX_PROPOSAL_THRESHOLD_BPS, 'NounsDAO::_setProposalThreshold: invalid proposal threshold' );
require( newQuorumVotesBPS >= MIN_QUORUM_VOTES_BPS && newQuorumVotesBPS <= MAX_QUORUM_VOTES_BPS, 'NounsDAO::_setProposalThreshold: invalid proposal threshold' );
require(msg.sender == pendingAdmin && msg.sender != address(0), 'NounsDAO::_acceptAdmin: pending admin only');
File: contracts/governance/NounsDAOLogicV2.sol
require( votingPeriod_ >= MIN_VOTING_PERIOD && votingPeriod_ <= MAX_VOTING_PERIOD, 'NounsDAO::initialize: invalid voting period' ); require( votingDelay_ >= MIN_VOTING_DELAY && votingDelay_ <= MAX_VOTING_DELAY, 'NounsDAO::initialize: invalid voting delay' ); require( proposalThresholdBPS_ >= MIN_PROPOSAL_THRESHOLD_BPS && proposalThresholdBPS_ <= MAX_PROPOSAL_THRESHOLD_BPS, 'NounsDAO::initialize: invalid proposal threshold bps' );
require( targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length, 'NounsDAO::propose: proposal function information arity mismatch' );
require( newVotingDelay >= MIN_VOTING_DELAY && newVotingDelay <= MAX_VOTING_DELAY, 'NounsDAO::_setVotingDelay: invalid voting delay' );
require( newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD, 'NounsDAO::_setVotingPeriod: invalid voting period' );
require( newProposalThresholdBPS >= MIN_PROPOSAL_THRESHOLD_BPS && newProposalThresholdBPS <= MAX_PROPOSAL_THRESHOLD_BPS, 'NounsDAO::_setProposalThreshold: invalid proposal threshold bps' );
require( newMinQuorumVotesBPS >= MIN_QUORUM_VOTES_BPS_LOWER_BOUND && newMinQuorumVotesBPS <= MIN_QUORUM_VOTES_BPS_UPPER_BOUND, 'NounsDAO::_setMinQuorumVotesBPS: invalid min quorum votes bps' );
require(msg.sender == pendingAdmin && msg.sender != address(0), 'NounsDAO::_acceptAdmin: pending admin only');
See a description of the issue https://github.com/ethereum/solidity/issues/9232
There are 6 instances in 3 files: File: contracts/governance/NounsDAOLogicV1.sol
/// @notice The EIP-712 typehash for the contract's domain bytes32 public constant DOMAIN_TYPEHASH = keccak256('EIP712Domain(string name,uint256 chainId,address verifyingContract)'); /// @notice The EIP-712 typehash for the ballot struct used by the contract bytes32 public constant BALLOT_TYPEHASH = keccak256('Ballot(uint256 proposalId,uint8 support)');
File: contracts/governance/NounsDAOLogicV2.sol
/// @notice The EIP-712 typehash for the contract's domain bytes32 public constant DOMAIN_TYPEHASH = keccak256('EIP712Domain(string name,uint256 chainId,address verifyingContract)'); /// @notice The EIP-712 typehash for the ballot struct used by the contract bytes32 public constant BALLOT_TYPEHASH = keccak256('Ballot(uint256 proposalId,uint8 support)');
File: contracts/base/ERC721Checkpointable.sol
/// @notice The EIP-712 typehash for the contract's domain bytes32 public constant DOMAIN_TYPEHASH = keccak256('EIP712Domain(string name,uint256 chainId,address verifyingContract)'); /// @notice The EIP-712 typehash for the delegation struct used by the contract bytes32 public constant DELEGATION_TYPEHASH = keccak256('Delegation(address delegatee,uint256 nonce,uint256 expiry)');
A division/multiplication by any number x being a power of 2 can be calculated by shifting log2(x) to the right/left. While the DIV opcode uses 5 gas, the SHR opcode only uses 3 gas. Furthermore, Solidity's division operation also includes a division-by-0 prevention which is bypassed using shifting. (similar to G-16 but for x>1)
There are 1 instance in 1 file: File: contracts/base/ERC721Checkpointable.sol
require(n < 2**32, errorMessage);
Recommandation: +require(n < 2<<5, errorMessage);