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: 40/160
Findings: 2
Award: $52.65
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0bi, 0x040, 0x1337, 0x1f8b, 0xDjango, 0xNazgul, 0xNineDec, 0xRajeev, 0xSky, 0xSmartContract, 0xbepresent, 0xkatana, 0xmatt, 8olidity, Aymen0909, Bjorn_bug, Bnke0x0, CertoraInc, Ch_301, Chom, CodingNameKiki, Deivitto, DevABDee, DimitarDimitrov, Dravee, ElKu, Funen, GalloDaSballo, GimelSec, Guardian, Haruxe, JC, JansenC, Jeiwan, JohnSmith, KIntern_NA, Lambda, LeoS, Noah3o6, Olivierdem, R2, RaymondFam, Respx, ReyAdmirado, Rohan16, Rolezn, Ruhum, Saintcode_, Sm4rty, SooYa, Soosh, TomJ, Tomo, Trabajo_de_mates, Waze, _Adam, __141345__, ajtra, android69, asutorufos, auditor0517, berndartmueller, bobirichman, brgltd, c3phas, cRat1st0s, carlitox477, catchup, cccz, csanuragjain, d3e4, delfin454000, dipp, djxploit, durianSausage, erictee, exd0tpy, fatherOfBlocks, gogo, hyh, ladboy233, lukris02, mics, mrpathfindr, natzuu, oyc_109, p_crypt0, pashov, pauliax, pfapostol, prasantgupta52, rajatbeladiya, rbserver, ret2basic, rfa, robee, rokinot, rvierdiiev, sach1r0, saian, seyni, shenwilly, sikorico, simon135, sryysryy, sseefried, throttle, tnevler, tonisives, wagmi, xiaoming90, yixxas, z3s, zkhorse, zzzitron
35.6777 USDC - $35.68
 | Issue |
---|---|
1 | Custom errors could be used with parameters for better user experience |
2 | Checks for admin can be converted into a modifier |
3 | Avoid hardcoding gas values |
Custom errors can take in parameters which can help the user identify the exact reason for the revert. The following custom errors can take in a parameter to make the protocol more user-friendly.
File: NounsDAOLogicV2.sol
error InvalidMinQuorumVotesBPS(); error InvalidMaxQuorumVotesBPS(); error MinQuorumBPSGreaterThanMaxQuorumBPS(); error UnsafeUint16Cast();
The contracts, NounsDAOLogicV1
and NounsDAOLogicV2
have several functions where there is a need to check if the msg.sender
is admin
. The method used for this is very inconsistent within these contracts. For increasing readability and even for saving gas, its recommended to create a modifier for this with a custom error(instead of a revert string).
Instances below:
Line 123: require(msg.sender == admin, 'NounsDAO::initialize: admin only'); Line 530: require(msg.sender == admin, 'NounsDAO::_setVotingDelay: admin only'); Line 546: require(msg.sender == admin, 'NounsDAO::_setVotingPeriod: admin only'); Line 563: require(msg.sender == admin, 'NounsDAO::_setProposalThresholdBPS: admin only'); Line 581: require(msg.sender == admin, 'NounsDAO::_setQuorumVotesBPS: admin only'); Line 599: require(msg.sender == admin, 'NounsDAO::_setPendingAdmin: admin only');
Line 134: require(msg.sender == admin, 'NounsDAO::initialize: admin only'); Line 622: require(msg.sender == admin, 'NounsDAO::_setVotingDelay: admin only'); Line 638: require(msg.sender == admin, 'NounsDAO::_setVotingPeriod: admin only'); Line 655: require(msg.sender == admin, 'NounsDAO::_setProposalThresholdBPS: admin only'); Line 674: require(msg.sender == admin, 'NounsDAO::_setMinQuorumVotesBPS: admin only'); Line 702: require(msg.sender == admin, 'NounsDAO::_setMaxQuorumVotesBPS: admin only'); Line 727: require(msg.sender == admin, 'NounsDAO::_setQuorumCoefficient: admin only'); Line 753: if (msg.sender != admin) { revert AdminOnly(); } Line 784: if (msg.sender != admin) { revert AdminOnly(); } Line 801: require(msg.sender == admin, 'NounsDAO::_setPendingAdmin: admin only');
An example mitigation would look like this:
// declare a modifier with "require string" modifier onlyAdmin { require(msg.sender == admin, "only admin"); _; } // OR you can declare a modifier with a "custom error" error AdminOnly(); //declare a custom error modifier onlyAdmin { if(msg.sender != admin) revert AdminOnly(); _; } // use it in a function function _withdraw() external onlyAdmin { // statements }
In NounsDAOLogicV2
contract the _refundGas
method uses calculations based on hardcoded gas values. This is not recommended. The gas cost of EVM instructions may change significantly during hard forks which will result in, the users who need to get a refund, gets less than what they are supposed to get. See SWC-134 for more information.
Concerned declarations are of MAX_REFUND_PRIORITY_FEE and REFUND_BASE_GAS
/// @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;
Mitigation would be to declare them as state variables and make them settable.
 | Issue |
---|---|
1 | Unused variables should be indicated as such |
2 | Spelling mistakes in Natspec comments |
3 | Non-library/interface files should use fixed compiler versions, not floating ones |
4 | Use a newer version of Solidity |
In NounsDAOLogicV2
, the variable, MAX_QUORUM_VOTES_BPS is declared but not used. Either it should be removed or it should be mentioned in the comment to avoid confusion.
/// @notice The maximum setable quorum votes basis points uint256 public constant MAX_QUORUM_VOTES_BPS = 2_000; // 2,000 basis points or 20%
The following spelling mistakes were noticed:
Line 061: setable Line 064: setable Line 067: setable Line 070: setable Line 073: setable Line 076: setable Line 088: setable Line 115: contructor Line 582: caries Line 848: priviledges
Line 069: setable Line 072: setable Line 075: setable Line 078: setable Line 081: setable Line 084: setable Line 087: setable Line 090: setable Line 104: contructor Line 490: caries Line 646: priviledges
Line 072: canceled Line 197: favor Line 203: canceled Line 297: favor Line 303: canceled Line 383: favor Line 389: canceled
Contracts should be deployed with the same compiler version and flags that they have been tested with thoroughly. Locking the pragma helps to ensure that contracts do not accidentally get deployed using, for example, an outdated compiler version that might introduce bugs that affect the contract system negatively.
https://swcregistry.io/docs/SWC-103
All the files under scope uses floating pragma like the one below:
pragma solidity ^0.8.6;
Mitigation would be to change it to this:
pragma solidity 0.8.6;
The codebase uses Solidity version 0.8.6 which was released in June 2021. Though its not possible to keep up with the version changes of Solidity, its advisable to use a relatively newer version. The current Solidity version is 0.8.16 which was released in August 2022 (almost one year later than the current version used by the codebase).
By using an older version you might be missing the following features:
See this for a more detailed list of features and bug fixes in each solidity version.
🌟 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.9732 USDC - $16.97
 | Issue |
---|---|
1 | propose function in NounsDAOLogicV1 and NounsDAOLogicV2 contract |
2 | Optimizing the for loops |
3 | Placing emit statements before state-variable update to save gas |
4 | Remove redundant safe32 function. |
5 | Use custom errors instead of revert strings |
propose
function in NounsDAOLogicV1
and NounsDAOLogicV2
contract</ins>Several gas optimizations can be applied to the propose function.
proposalCount
is accessed 3 times, which could be cached to save gas. Also incrementing of this variable could be converted to ++proposalCount
to save extra gas.uint256 cachedproposalCount = ++proposalCount; Proposal storage newProposal = proposals[cachedproposalCount]; newProposal.id = cachedproposalCount;
Proposal
struct are written with their default values. These statements are redundant and can be removed to save gas.newProposal.eta = 0; newProposal.forVotes = 0; newProposal.againstVotes = 0; newProposal.abstainVotes = 0; newProposal.canceled = false; newProposal.executed = false; newProposal.vetoed = false;
latestProposalIds[newProposal.proposer] = newProposal.id; //could be rewritten as: latestProposalIds[newProposal.proposer] = cachedproposalCount;
emit
statements could use local variables, instead of the state variables.emit ProposalCreated( cachedproposalCount, //gas saving msg.sender, targets, values, signatures, calldatas, temp.startBlock, //gas saving temp.endBlock, //gas saving description ); /// @notice Updated event with `proposalThreshold` and `quorumVotes` emit ProposalCreatedWithRequirements( cachedproposalCount, //gas saving msg.sender, targets, values, signatures, calldatas, temp.startBlock, //gas saving temp.endBlock, //gas saving temp.proposalThreshold, //gas saving newProposal.quorumVotes, description );
return newProposal.id; // can be written as: return cachedproposalCount;
Hardhat gas report for propose
function:
 | Original Report | Optimized Report | Gas savings |
---|---|---|---|
Min | 348312 | 336224 | 12088 |
Max | 599686 | 587598 | 12088 |
Avg | 433213 | 421119 | 12094 |
We can see that we saved an average of 12094 gas.
As for NounsDAOLogicV2
contract, we are using the same function there as well, so we can account for a saving of 12000 gas there too.
Total Amount of gas saved so far = 12000 + 12000 = 24000.
for
loops</ins>The for
loops in the code base generally follow the following pattern:
for (uint256 i = 0; i < proposal.targets.length; i++) { // do something }
This can be optimized in 3 ways:
len = proposal.targets.length; // 1. cache the array length for (uint256 i; i < len; ) { // 2. remove redundant initialization of i // do something unchecked { // 3. using unchecked block for incrementing the index ++i; //use pre-increment instead of post-increment } }
The following instances could be optimized in this way:
Line 292: for (uint256 i = 0; i < proposal.targets.length; i++) Line 330: for (uint256 i = 0; i < proposal.targets.length; i++) Line 357: for (uint256 i = 0; i < proposal.targets.length; i++) Line 382: for (uint256 i = 0; i < proposal.targets.length; i++)
Line 281: for (uint256 i = 0; i < proposal.targets.length; i++) Line 319: for (uint256 i = 0; i < proposal.targets.length; i++) Line 346: for (uint256 i = 0; i < proposal.targets.length; i++) Line 371: for (uint256 i = 0; i < proposal.targets.length; i++)
After the above for
loops were optimized, the gas savings as per Hardhat were as follows:
<ins>Hardhat Gas report:</ins>
 | Contract | Method | Original Avg Gas Cost | Optimized Avg Gas Cost | Avg Gas Saved |
---|---|---|---|---|---|
1 | NounsDAOLogicV1 | queue | 148150 | 147935 | 215 |
2 | NounsDAOLogicV1 | execute | 170128 | 169958 | 170 |
3 | NounsDAOLogicV1 | cancel | 99962 | 99921 | 41 |
4 | NounsDAOLogicV1 | veto | 96416 | 96330 | 86 |
Total gas saved in the V1 contract alone is 512.
NounsDAOLogicV2
implements the same set of functions, so the total gas saved in both of these contracts = 512 + 512 = 1024.
The emit statements are generally placed at the end of a function. But sometimes, placing them before we update an admin variable can save gas. This is already done in the _setVetoer
function in both NounsDAOLogicV1 and NounsDAOLogicV2 contracts:
function _setVetoer(address newVetoer) public { require(msg.sender == vetoer, 'NounsDAO::_setVetoer: vetoer only'); emit NewVetoer(vetoer, newVetoer); //emitting before updating state variable. vetoer = newVetoer; }
The same could be done in the following functions as well to save gas.
_acceptAdmin
function in NounsDAOLogicV1 and NounsDAOLogicV2 can do a few more optimizations as given below:function _acceptAdmin() external { // Check caller is pendingAdmin and pendingAdmin ≠address(0) require(msg.sender == pendingAdmin && msg.sender != address(0), 'NounsDAO::_acceptAdmin: pending admin only'); address cachedpendingAdmin = pendingAdmin; //cache state variable into local storage emit NewAdmin(admin, cachedpendingAdmin); //first emit emit NewPendingAdmin(cachedpendingAdmin, address(0)); //second emit // Store admin with value pendingAdmin admin = cachedpendingAdmin; //update state variable from local storage // Clear the pending value pendingAdmin = address(0); //update another state variable }
After the above rearrangements of emit
statements, Hardhat showed a gas saving of 25 gas per function. Since we have 5 functions each in 2 of these contracts, we are able to save 25 * 5 * 2 = 250 gas in total.
safe32
function</ins>safe32
function is used in NounsDAOLogicV2
contract to make sure that the current block.number
is less than 2 ^ 32. But this in an unnecessary concern as at the current rate at which blocks are processed, it will still take more than 1500 years to reach the limit. So we can just safely typecast it into an uint32.
uint32 blockNumber = safe32(block.number, 'block number exceeds 32 bits'); //becomes uint32 blockNumber = uint32(block.number);
The saved gas was observed through the gas savings of _setDynamicQuorumParams
method which uses safe32
in one of its internal functions. The average gas savings was 280.
Another major area in which we could save deployment cost would be in converting the revert
strings into custom errors. If the function does revert, you can also save on dynamic gas cost. See this example implementation to understand the dynamics of custom errors. It shows that each require
string converted into a custom error saves you around 11000 gas.
The codebase uses some custom errors already. The rest is listed below:
Line 079: require(msg.sender == admin, 'NounsDAOProxy::_setImplementation: admin only'); Line 080: require(implementation_ != address(0), 'NounsDAOProxy::_setImplementation: invalid implementation address');
Line 140: require(signatory != address(0), 'ERC721Checkpointable::delegateBySig: invalid signature'); Line 141: require(nonce == nonces[signatory]++, 'ERC721Checkpointable::delegateBySig: invalid nonce'); Line 142: require(block.timestamp <= expiry, 'ERC721Checkpointable::delegateBySig: signature expired'); Line 164: require(blockNumber < block.number, 'ERC721Checkpointable::getPriorVotes: not yet determined');
Line 062: require(index < ERC721.balanceOf(owner), 'ERC721Enumerable: owner index out of bounds'); Line 077: require(index < ERC721Enumerable.totalSupply(), 'ERC721Enumerable: global index out of bounds');
Line 122: 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' ); Line 187: 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' ); Line 275: require( state(proposalId) == ProposalState.Succeeded, 'NounsDAO::queue: proposal can only be queued if it is succeeded' ); Line 301: require( !timelock.queuedTransactions(keccak256(abi.encode(target, value, signature, data, eta))), 'NounsDAO::queueOrRevertInternal: identical proposal action already queued at eta' ); Line 313: require( state(proposalId) == ProposalState.Queued, 'NounsDAO::execute: proposal can only be executed if it is queued' ); Line 336: require(state(proposalId) != ProposalState.Executed, 'NounsDAO::cancel: cannot cancel executed proposal'); Line 339: require( msg.sender == proposal.proposer || nouns.getPriorVotes(proposal.proposer, block.number - 1) < proposal.proposalThreshold, 'NounsDAO::cancel: proposer above threshold' ); Line 364: 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'); Line 422: require(proposalCount >= proposalId, 'NounsDAO::state: invalid proposal id'); Line 485: require(signatory != address(0), 'NounsDAO::castVoteBySig: invalid signature'); Line 501: require(state(proposalId) == ProposalState.Active, 'NounsDAO::castVoteInternal: voting is closed'); Line 502: require(support <= 2, 'NounsDAO::castVoteInternal: invalid vote type'); Line 505: require(receipt.hasVoted == false, 'NounsDAO::castVoteInternal: voter already voted'); Line 530: require(msg.sender == admin, 'NounsDAO::_setVotingDelay: admin only'); Line 531: require( newVotingDelay >= MIN_VOTING_DELAY && newVotingDelay <= MAX_VOTING_DELAY, 'NounsDAO::_setVotingDelay: invalid voting delay' ); Line 546: require(msg.sender == admin, 'NounsDAO::_setVotingPeriod: admin only'); Line 547: require( newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD, 'NounsDAO::_setVotingPeriod: invalid voting period' ); Line 563: require(msg.sender == admin, 'NounsDAO::_setProposalThresholdBPS: admin only'); require( newProposalThresholdBPS >= MIN_PROPOSAL_THRESHOLD_BPS && newProposalThresholdBPS <= MAX_PROPOSAL_THRESHOLD_BPS, 'NounsDAO::_setProposalThreshold: invalid proposal threshold' ); Line 581: require(msg.sender == admin, 'NounsDAO::_setQuorumVotesBPS: admin only'); require( newQuorumVotesBPS >= MIN_QUORUM_VOTES_BPS && newQuorumVotesBPS <= MAX_QUORUM_VOTES_BPS, 'NounsDAO::_setProposalThreshold: invalid proposal threshold' ); Line 599: require(msg.sender == admin, 'NounsDAO::_setPendingAdmin: admin only'); Line 617: require(msg.sender == pendingAdmin && msg.sender != address(0), 'NounsDAO::_acceptAdmin: pending admin only'); Line 638: require(msg.sender == vetoer, 'NounsDAO::_setVetoer: vetoer only'); Line 651: require(msg.sender == vetoer, 'NounsDAO::_burnVetoPower: vetoer only');
Line 133: 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' ); Line 197: 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'); Line 213: 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' ); Line 286: require( state(proposalId) == ProposalState.Succeeded, 'NounsDAO::queue: proposal can only be queued if it is succeeded' ); Line 312: require( !timelock.queuedTransactions(keccak256(abi.encode(target, value, signature, data, eta))), 'NounsDAO::queueOrRevertInternal: identical proposal action already queued at eta' ); Line 324: require( state(proposalId) == ProposalState.Queued, 'NounsDAO::execute: proposal can only be executed if it is queued' ); Line 347: require(state(proposalId) != ProposalState.Executed, 'NounsDAO::cancel: cannot cancel executed proposal'); Line 350: require( msg.sender == proposal.proposer || nouns.getPriorVotes(proposal.proposer, block.number - 1) < proposal.proposalThreshold, 'NounsDAO::cancel: proposer above threshold' ); Line 375: 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'); Line 433: require(proposalCount >= proposalId, 'NounsDAO::state: invalid proposal id'); Line 577: require(signatory != address(0), 'NounsDAO::castVoteBySig: invalid signature'); Line 593: require(state(proposalId) == ProposalState.Active, 'NounsDAO::castVoteInternal: voting is closed'); require(support <= 2, 'NounsDAO::castVoteInternal: invalid vote type'); Line 597: require(receipt.hasVoted == false, 'NounsDAO::castVoteInternal: voter already voted'); Line 622: require(msg.sender == admin, 'NounsDAO::_setVotingDelay: admin only'); require( newVotingDelay >= MIN_VOTING_DELAY && newVotingDelay <= MAX_VOTING_DELAY, 'NounsDAO::_setVotingDelay: invalid voting delay' ); Line 638: require(msg.sender == admin, 'NounsDAO::_setVotingPeriod: admin only'); require( newVotingPeriod >= MIN_VOTING_PERIOD && newVotingPeriod <= MAX_VOTING_PERIOD, 'NounsDAO::_setVotingPeriod: invalid voting period' ); Line 655: 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' ); Line 674: require(msg.sender == admin, 'NounsDAO::_setMinQuorumVotesBPS: admin only'); Line 677: 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' ); Line 702: require(msg.sender == admin, 'NounsDAO::_setMaxQuorumVotesBPS: admin only'); Line 705: 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' ); Line 727: require(msg.sender == admin, 'NounsDAO::_setQuorumCoefficient: admin only'); Line 801: require(msg.sender == admin, 'NounsDAO::_setPendingAdmin: admin only'); Line 819: require(msg.sender == pendingAdmin && msg.sender != address(0), 'NounsDAO::_acceptAdmin: pending admin only'); Line 840: require(msg.sender == vetoer, 'NounsDAO::_setVetoer: vetoer only'); Line 853: require(msg.sender == vetoer, 'NounsDAO::_burnVetoPower: vetoer only');
Total number of instances = 2 + 4 + 2 + 39 + 43 = 90. Approximate deployment cost that can be saved = 90 * 11000 = 990,000.
The above 4 major changes saved a very significant amount of gas in the overall code base. The exact amounts are tabulated below:
 | Issue | Gas Saved |
---|---|---|
1 | propose function in NounsDAOLogicV1 and NounsDAOLogicV2 contract | 24000 |
2 | Optimizing the for loops | 1024 |
3 | Placing emit statements before state-variable update to save gas | 250 |
4 | Remove redundant safe32 function | 280 |
5 | Use custom errors instead of revert strings | * |
*saved deployment cost |