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: 26/160
Findings: 2
Award: $69.53
๐ 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
50.2965 USDC - $50.30
Severity: Low
Low-level calls return success if there is no code present at the specified address. In addition to the zero-address checks, add a check to verify that <address>.code.length > 0
(bool success, bytes memory returnData) = callee.delegatecall(data);
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/governance/NounsDAOProxy.sol#L95
(bool success, ) = implementation.delegatecall(msg.data);
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/governance/NounsDAOProxy.sol#L110
Add zero-address checks and add a check to verify that <address>.code.length > 0
Severity: Low
function _delegate(address delegator, address delegatee) internal {
function _setPendingAdmin(address newPendingAdmin) external {
function _setVetoer(address newVetoer) public {
function _setPendingAdmin(address newPendingAdmin) external {
function _setVetoer(address newVetoer) public {
Consider adding zero-address checks in the mentioned codebase.
Severity: Low
If the intention is for the Ether to be used, the function should call another function, otherwise it should revert
receive() external payable {}
The function should call another function, otherwise it should revert
Severity: Non-Critical
Found usage of floating pragmas ^0.8.6 of Solidity
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Checkpointable.sol#L35
Found usage of floating pragmas ^0.8.0 of Solidity
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L28
Found usage of floating pragmas ^0.8.6 of Solidity
Found usage of floating pragmas ^0.8.6 of Solidity
Found usage of floating pragmas ^0.8.6 of Solidity
Found usage of floating pragmas ^0.8.6 of Solidity
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/governance/NounsDAOProxy.sol#L36
Avoid Floating Pragmas: The Version Should Be Locked
Severity: Non-Critical
require(n < 2**32, errorMessage);
require(n < 2**96, errorMessage);
Severity: Non-Critical
Each event should use three indexed fields if there are three or more fields. In addition, each event should have at least one indexed fields to allow easier filtering of logs.
event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Checkpointable.sol#L73
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 ProposalQueued(uint256 id, uint256 eta);
event VotingDelaySet(uint256 oldVotingDelay, uint256 newVotingDelay);
event VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod);
event NewImplementation(address oldImplementation, address newImplementation);
event ProposalThresholdBPSSet(uint256 oldProposalThresholdBPS, uint256 newProposalThresholdBPS);
event QuorumVotesBPSSet(uint256 oldQuorumVotesBPS, uint256 newQuorumVotesBPS);
event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);
event NewAdmin(address oldAdmin, address newAdmin);
event NewVetoer(address oldVetoer, address newVetoer);
event MinQuorumVotesBPSSet(uint16 oldMinQuorumVotesBPS, uint16 newMinQuorumVotesBPS);
event MaxQuorumVotesBPSSet(uint16 oldMaxQuorumVotesBPS, uint16 newMaxQuorumVotesBPS);
event QuorumCoefficientSet(uint32 oldQuorumCoefficient, uint32 newQuorumCoefficient);
event RefundableVote(address indexed voter, uint256 refundAmount, bool refundSent);
event Withdraw(uint256 amount, bool sent);
OpenZeppelin recommends that the initializer modifier be applied to constructors. Per OZs Post implementation contract should be initialized to avoid potential griefs or exploits. https://forum.openzeppelin.com/t/uupsupgradeable-vulnerability-post-mortem/15680/5
Severity: Non-Critical
contract NounsDAOEvents
contract NounsDAOLogicV1 is NounsDAOStorageV1, NounsDAOEvents
contract NounsDAOLogicV2 is NounsDAOStorageV2, NounsDAOEventsV2
contract NounsDAOProxy is NounsDAOProxyStorage, NounsDAOEvents
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/governance/NounsDAOProxy.sol#L40
Severity: Non-Critical
chainId := chainid()
chainId := chainid()
chainId := chainid()
Severity: Non-Critical
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
Found old version 0.8.6 of Solidity
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Checkpointable.sol#L35
Found old version 0.8.0 of Solidity
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L28
Found old version 0.8.6 of Solidity
Found old version 0.8.6 of Solidity
Found old version 0.8.6 of Solidity
Found old version 0.8.6 of Solidity
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/governance/NounsDAOProxy.sol#L36
Consider updating to a more recent solidity version.
Severity: Non-Critical
Contracts are allowed to override their parentsโ functions and change the visibility from external to public.
function delegate(
function delegateBySig(
Severity: Non-Critical
Use (e.g. 1e6) rather than decimal literals (e.g. 100000), for better code readability.
return (number * bps) / 10000;
uint256 againstVotesBPS = (10000 * againstVotes) / totalSupply;
return (number * bps) / 10000;
uint256 againstVotesBPS = (10000 * againstVotes) / totalSupply;
return (number * bps) / 10000;
Severity: Non-Critical
Block timestamps have historically been used for a variety of applications, such as entropy for random numbers (see the Entropy Illusion for further details), locking funds for periods of time, and various state-changing conditional statements that are time-dependent. Miners have the ability to adjust timestamps slightly, which can prove to be dangerous if block timestamps are used incorrectly in smart contracts.
require(block.timestamp <= expiry, 'ERC721Checkpointable::delegateBySig: signature expired');
uint256 eta = block.timestamp + timelock.delay();
} else if (block.timestamp >= proposal.eta + timelock.GRACE_PERIOD()) {
uint256 eta = block.timestamp + timelock.delay();
} else if (block.timestamp >= proposal.eta + timelock.GRACE_PERIOD()) {
Block timestamps should not be used for entropy or generating random numbersโi.e., they should not be the deciding factor (either directly or through some derivation) for winning a game or changing an important state.
Time-sensitive logic is sometimes required; e.g., for unlocking contracts (time-locking), completing an ICO after a few weeks, or enforcing expiry dates. It is sometimes recommended to use block.number and an average block time to estimate times; with a 10 second block time, 1 week equates to approximately, 60480 blocks. Thus, specifying a block number at which to change a contract state can be more secure, as miners are unable to easily manipulate the block number.
๐ 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
19.2277 USDC - $19.23
Severity: Gas Optimizations
abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name())), getChainId(), address(this))
bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry));
!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));
!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));
Severity: Gas Optimizations
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
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++) {
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++) {
Severity: Gas Optimizations
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. Structs have the same overhead as an array of length one
function propose( address[] memory targets, uint256[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description )
function getActions(uint256 proposalId)
function propose( address[] memory targets, uint256[] memory values, string[] memory signatures, bytes[] memory calldatas, string memory description )
function getActions(uint256 proposalId)
Severity: Gas Optimizations
For cases of: if (<x> == true), use if (<x>) instead For cases of: if (<x> == false), use if (!<x>) instead
require(receipt.hasVoted == false, 'NounsDAO::castVoteInternal: voter already voted');
require(receipt.hasVoted == false, 'NounsDAO::castVoteInternal: voter already voted');
Severity: Gas Optimizations
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{...}})
receive() external payable {}
Severity: Gas Optimizations
Saves 6 gas per loop
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++) {
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++) {
For example, use ++i instead of i++
Severity: Gas Optimizations
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++) {
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++) {
Severity: Gas Optimizations
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.
mapping(address => address) private _delegates; mapping(address => mapping(uint32 => Checkpoint)) public checkpoints; mapping(address => uint32) public numCheckpoints; mapping(address => uint256) public nonces;
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Checkpointable.sol#L44
mapping(address => uint256) public latestProposalIds; mapping(address => Receipt) receipts;
Severity: Gas Optimizations
<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
uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
uint256 center = upper - (upper - lower) / 2;
Severity: Gas Optimizations
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
Found 3 usage of public constants
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Checkpointable.sol#L41 https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Checkpointable.sol#L59-L64
Found 12 usage of public constants
Found 16 usage of public constants
Set variable to private.
Severity: Gas Optimizations
The following functions could be set external to save gas and improve code quality. External call cost is less expensive than of public functions.
function votesToDelegate(address delegator) public view returns (uint96) {
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Checkpointable.sol#L79
function delegates(address delegator) public view returns (address) {
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Checkpointable.sol#L88
function delegate(address delegatee) public {
function delegateBySig(
function getPriorVotes(address account, uint256 blockNumber) public view returns (uint96) {
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L54
function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) {
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L61
function totalSupply() public view virtual override returns (uint256) {
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L69
function tokenByIndex(uint256 index) public view virtual override returns (uint256) {
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L76
function initialize(
function propose(
function state(uint256 proposalId) public view returns (ProposalState) {
function _setVetoer(address newVetoer) public {
function _burnVetoPower() public {
function proposalThreshold() public view returns (uint256) {
function quorumVotes() public view returns (uint256) {
function initialize(
function propose(
function state(uint256 proposalId) public view returns (ProposalState) {
function _setDynamicQuorumParams(
function _setVetoer(address newVetoer) public {
function _burnVetoPower() public {
function proposalThreshold() public view returns (uint256) {
function quorumVotes(uint256 proposalId) public view returns (uint256) {
function dynamicQuorumVotes(
function getDynamicQuorumParamsAt(uint256 blockNumber_) public view returns (DynamicQuorumParams memory) {
function minQuorumVotes() public view returns (uint256) {
function maxQuorumVotes() public view returns (uint256) {
function _setImplementation(address implementation_) public {
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/governance/NounsDAOProxy.sol#L78
Severity: Gas Optimizations
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');
require(index < ERC721.balanceOf(owner), 'ERC721Enumerable: owner index out of bounds');
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L62
require(index < ERC721Enumerable.totalSupply(), 'ERC721Enumerable: global index out of bounds');
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L77
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(targets.length != 0, 'NounsDAO::propose: must provide actions');
require(targets.length <= proposalMaxOperations, 'NounsDAO::propose: too many actions');
require(state(proposalId) != ProposalState.Executed, 'NounsDAO::cancel: cannot cancel executed proposal');
require(vetoer != address(0), 'NounsDAO::veto: veto power burned');
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(msg.sender == admin, 'NounsDAO::_setVotingPeriod: admin only');
require(msg.sender == admin, 'NounsDAO::_setProposalThresholdBPS: admin only');
require(msg.sender == admin, 'NounsDAO::_setQuorumVotesBPS: 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');
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(targets.length != 0, 'NounsDAO::propose: must provide actions');
require(targets.length <= proposalMaxOperations, 'NounsDAO::propose: too many actions');
require(state(proposalId) != ProposalState.Executed, 'NounsDAO::cancel: cannot cancel executed proposal');
require(vetoer != address(0), 'NounsDAO::veto: veto power burned');
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(msg.sender == admin, 'NounsDAO::_setVotingPeriod: admin only');
require(msg.sender == admin, 'NounsDAO::_setProposalThresholdBPS: admin only');
require(msg.sender == admin, 'NounsDAO::_setMinQuorumVotesBPS: admin only');
require(msg.sender == admin, 'NounsDAO::_setMaxQuorumVotesBPS: admin only');
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');
require(msg.sender == admin, 'NounsDAOProxy::_setImplementation: admin only');
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/governance/NounsDAOProxy.sol#L79
require(implementation_ != address(0), 'NounsDAOProxy::_setImplementation: invalid implementation address');
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/governance/NounsDAOProxy.sol#L80
Severity: Gas Optimizations
See https://github.com/code-423n4/2022-01-xdefi-findings/issues/128 which describes the fact that there is a larger deployment gas cost, but with enough runtime calls, the change ends up being cheaper
require(msg.sender == pendingAdmin && msg.sender != address(0), 'NounsDAO::_acceptAdmin: pending admin only');
require(msg.sender == pendingAdmin && msg.sender != address(0), 'NounsDAO::_acceptAdmin: pending admin only');
Severity: Gas Optimizations
To help the optimizer, declare a storage type variable and use it instead of repeatedly fetching the reference in a map or an array. The effect can be quite significant. As an example, instead of repeatedly calling someMap[someIndex], save its reference like this: SomeStruct storage someStruct = someMap[someIndex] and use it.
address current = _delegates[delegator];
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Checkpointable.sol#L89
require(nonce == nonces[signatory]++, 'ERC721Checkpointable::delegateBySig: invalid nonce');
uint32 nCheckpoints = numCheckpoints[account];
return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0;
uint32 nCheckpoints = numCheckpoints[account];
return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0;
uint32 nCheckpoints = numCheckpoints[account];
if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {
return checkpoints[account][nCheckpoints - 1].votes;
if (checkpoints[account][0].fromBlock > blockNumber) {
Checkpoint memory cp = checkpoints[account][center];
return checkpoints[account][lower].votes;
_delegates[delegator] = delegatee;
uint32 srcRepNum = numCheckpoints[srcRep];
uint96 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;
uint32 dstRepNum = numCheckpoints[dstRep];
uint96 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;
if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) {
checkpoints[delegatee][nCheckpoints - 1].votes = newVotes;
checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes);
numCheckpoints[delegatee] = nCheckpoints + 1;
return _ownedTokens[owner][index];
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L63
return _allTokens[index];
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L78
_ownedTokens[to][length] = tokenId;
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L122
_ownedTokensIndex[tokenId] = length;
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L123
_allTokensIndex[tokenId] = _allTokens.length;
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L131
uint256 tokenIndex = _ownedTokensIndex[tokenId];
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L148
uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L152
_ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L154
_ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L155
delete _ownedTokensIndex[tokenId];
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L159
delete _ownedTokens[from][lastTokenIndex];
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L160
uint256 tokenIndex = _allTokensIndex[tokenId];
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L173
uint256 lastTokenId = _allTokens[lastTokenIndex];
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L178
_allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L180
_allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L181
delete _allTokensIndex[tokenId];
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Enumerable.sol#L184
Proposal storage newProposal = proposals[proposalCount];
Proposal storage proposal = proposals[proposalId];
Proposal storage proposal = proposals[proposalId];
Proposal storage proposal = proposals[proposalId];
Proposal storage proposal = proposals[proposalId];
Proposal storage p = proposals[proposalId];
return proposals[proposalId].receipts[voter];
Proposal storage proposal = proposals[proposalId];
Proposal storage proposal = proposals[proposalId];
Receipt storage receipt = proposal.receipts[voter];
Proposal storage newProposal = _proposals[proposalCount];
Proposal storage proposal = _proposals[proposalId];
Proposal storage proposal = _proposals[proposalId];
Proposal storage proposal = _proposals[proposalId];
Proposal storage proposal = _proposals[proposalId];
Proposal storage p = _proposals[proposalId];
return _proposals[proposalId].receipts[voter];
Proposal storage proposal = _proposals[proposalId];
Proposal storage proposal = _proposals[proposalId];
Proposal storage proposal = _proposals[proposalId];
Receipt storage receipt = proposal.receipts[voter];
Proposal storage proposal = _proposals[proposalId];
DynamicQuorumParamsCheckpoint memory cp = quorumParamsCheckpoints[center];
return quorumParamsCheckpoints[lower].params;
Severity: Gas Optimizations
Lower than uint256 size storage variables are less gas efficient. Using uint64 does not give any efficiency, actually, it is the opposite as EVM operates on default of 256-bit values so uint64 is more expensive in this case as it needs a conversion. It only gives improvements in cases where you can pack variables together, e.g. structs.
uint8 public constant decimals = 0;
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Checkpointable.sol#L41
uint32 fromBlock;
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Checkpointable.sol#L48
uint96 votes;
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/base/ERC721Checkpointable.sol#L49
uint32 nCheckpoints = numCheckpoints[account];
uint32 lower = 0;
uint32 upper = nCheckpoints - 1;
uint32 center = upper - (upper - lower) / 2;
uint96 amount = votesToDelegate(delegator);
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');
uint96 c = a + b;
uint8 support;
uint96 votes;
uint16 minQuorumVotesBPS;
uint16 maxQuorumVotesBPS;
uint32 quorumCoefficient;
uint32 fromBlock;
uint96 votes = nouns.getPriorVotes(voter, proposal.startBlock - votingDelay);
uint96 votes = castVoteInternal(msg.sender, proposalId, support);
uint96 votes = nouns.getPriorVotes(voter, proposalCreationBlock(proposal));
uint16 oldMinQuorumVotesBPS = params.minQuorumVotesBPS;
uint16 oldMaxQuorumVotesBPS = params.maxQuorumVotesBPS;
uint32 oldQuorumCoefficient = params.quorumCoefficient;
uint32 blockNumber = safe32(blockNumber_, 'NounsDAO::getDynamicQuorumParamsAt: block number exceeds 32 bits');
uint32 blockNumber = safe32(block.number, 'block number exceeds 32 bits');
Severity: Gas Optimizations
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
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++) {
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++) {
Severity: Gas Optimizations
Save 6 gas per instance if using assembly to check for address(0)
e.g. assembly { if iszero(_addr) { mstore(0x00, "AddressZero") revert(0x00, 0x20) } }
function delegate(address delegatee) public {
function _setImplementation(address implementation_) public {
https://github.com/code-423n4/2022-08-nounsdao/tree/main/contracts/governance/NounsDAOProxy.sol#L78