Nouns DAO contest - 0xbepresent's results

A DAO-driven NFT project on Ethereum.

General Information

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

Nouns DAO

Findings Distribution

Researcher Performance

Rank: 87/160

Findings: 2

Award: $52.10

🌟 Selected for report: 0

šŸš€ Solo Findings: 0

1 - Consider two-phase vetoer transfer

The vetoer calls _setVetoer in order to tranfers the ownership to the new address directly. There is a risk that the ownership is transferred to an invalid address, thus causing the contract to be without vetoer.

Recommendation

Consider a two step process where the vetoer nominates an prevetoer account and the nominated prevetoer account need to call an acceptVetoOwnership() function for the transfer of vetoer to fully succeed. This ensures the nominated EOA account is a valid and active account

2 - The logic is not according to the comment

The require here should validates pendingAdmin != address(0) instead msg.sender != address(0)

// Check caller is pendingAdmin and pendingAdmin ≠ address(0) require(msg.sender == pendingAdmin && msg.sender != address(0), 'NounsDAO::_acceptAdmin: pending admin only');

Recommendation

Consider use pendingAdmin != address(0) in the require() logic.

1 - Internal functions only called once can be inlined to save gas

Not inlining costs more gas because of two extra JUMP instructions and additional stack operations needed for function calls.

There are 3 instances of this issue:

contracts/governance/NounsDAOLogicV2.sol#L305 function queueOrRevertInternal( contracts/governance/NounsDAOLogicV2.sol#L866 function proposalCreationBlock(Proposal storage proposal) internal view returns (uint256) { contracts/governance/NounsDAOLogicV2.sol#L974 function _refundGas(uint256 startGas) internal {

2 - Process functions only if they have not been processed

In addition to checking that the function is not in the "Executed" state, you can also check that the function has not been processed.

Example:

# cancel function require(state(proposalId) != ProposalState.Canceled, 'NounsDAO::cancel: proposal already canceled'); # veto function require(state(proposalId) != ProposalState.Vetoed, 'NounsDAO::veto: proposal already vetoed');

There are 2 instances of this issue:

contracts/governance/NounsDAOLogicV2.sol#L347 require(state(proposalId) != ProposalState.Executed, 'NounsDAO::cancel: cannot cancel executed proposal'); contracts/governance/NounsDAOLogicV2.sol#L377 require(state(proposalId) != ProposalState.Executed, 'NounsDAO::veto: cannot veto executed proposal');

3 - Unnecessary checked arithmetic in for loop

There is no risk that the loop counter can overflow, using solidity's unchecked block saves gas.

There are 4 instances of this issue:

governance/NounsDAOLogicV2.sol#292 => for (uint256 i = 0; i < proposal.targets.length; i++) { governance/NounsDAOLogicV2.sol#331 => for (uint256 i = 0; i < proposal.targets.length; i++) { governance/NounsDAOLogicV2.sol#359 => for (uint256 i = 0; i < proposal.targets.length; i++) { governance/NounsDAOLogicV2.sol#385 => for (uint256 i = 0; i < proposal.targets.length; i++) {

Unchecked implementation example:

for (uint256 i; i < 10;) { j++; unchecked { ++i; } }

Gas Report example using this gist:

╭─────────────────────────────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ │ src/test/Unchecked.t.sol:Contract0 contract ┆ ┆ ┆ ┆ ┆ │ ā•žā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•Ŗā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•Ŗā•ā•ā•ā•ā•ā•ā•Ŗā•ā•ā•ā•ā•ā•ā•ā•ā•Ŗā•ā•ā•ā•ā•ā•ā•Ŗā•ā•ā•ā•ā•ā•ā•ā•ā•ā•” │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ā”œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¤ │ 55105 ┆ 307 ┆ ┆ ┆ ┆ │ ā”œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ā”œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¤ │ withOutUnChecked ┆ 2068 ┆ 2068 ┆ 2068 ┆ 2068 ┆ 1 │ ╰─────────────────────────────────────────────┓─────────────────┓──────┓────────┓──────┓─────────╯ ╭─────────────────────────────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ │ src/test/Unchecked.t.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ ā•žā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•Ŗā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•Ŗā•ā•ā•ā•ā•ā•ā•Ŗā•ā•ā•ā•ā•ā•ā•ā•ā•Ŗā•ā•ā•ā•ā•ā•ā•Ŗā•ā•ā•ā•ā•ā•ā•ā•ā•ā•” │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ ā”œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¤ │ 53705 ┆ 300 ┆ ┆ ┆ ┆ │ ā”œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¤ │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ ā”œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā”¼ā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā•Œā”¤ │ withUnchecked ┆ 1408 ┆ 1408 ┆ 1408 ┆ 1408 ┆ 1 │ ╰─────────────────────────────────────────────┓─────────────────┓──────┓────────┓──────┓─────────╯

4 - Cache Array Length Outside of Loop

Caching the array length outside a loop saves reading it on each iteration, as long as the array's length is not changed during the loop.

There are 4 instances of this issue:

governance/NounsDAOLogicV2.sol#292 for (uint256 i = 0; i < proposal.targets.length; i++) { governance/NounsDAOLogicV2.sol#331 for (uint256 i = 0; i < proposal.targets.length; i++) { governance/NounsDAOLogicV2.sol#359 for (uint256 i = 0; i < proposal.targets.length; i++) { governance/NounsDAOLogicV2.sol#385 for (uint256 i = 0; i < proposal.targets.length; i++) {

5 - Consider using custom errors

The contracts in scope use Solidity >=0.8.4, consider using Custom Errors as they are more gas efficient while allowing developers to describe the error in detail using Natspec

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax Ā© 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter