Contest ran 22 August 202227 August 2022

5 day contest

Nouns DAO contest

A DAO-driven NFT project on Ethereum.

$50,000 USDC

Total Awards

Nouns DAO contest details

About Nouns DAO

Nouns is a generative NFT project on Ethereum, where a new Noun is minted and auctioned off every day, all proceeds go to the DAO treasury, and each token represents one vote. To date more than 350 Nouns have been sold on auction, and the DAO has funded a variety of cool builders, including a recent 500 ETH grant to theprotocolguild.eth.

More intro information can be found on Nouns Center.

Developer guide

Setup

yarn

Running tests

Hardhat tests

yarn test

Forge tests

forge install
forge test -vvv --ffi

Possible Solc error

If you encounter the following error tring to build or run tests:

Solc Error: dyld[32338]: Library not loaded: '/opt/homebrew/opt/z3/lib/libz3.dylib'

Install the z3 library and try again. On MacOs you can run

brew install z3

The dependency on z3 is mentioned in forge build docs.

Audit scope

The focus of this audit is on:

  • The NounsDAOLogicV2 contract, which introduces two new features:
    • Dynamic quorum (spec)
    • Voting gas refund (spec)
    • A fix to how votingDelay is used (bug report)
  • Parent contracts which hold state variables: NounsDAOStorageV2, NounsDAOStorageV1Adjusted, NounsDAOProxyStorage (found in the file NounsDAOInterfaces.sol)
  • NounsToken's functions getPriorVotes and totalSupply, which are implemented in ERC721Checkpointable and ERC721Enumerable respectively
  • The upgrade process from v1 to v2
    • The NounsDAOLogicV1 contract for context
    • The upgrade will take place via a proposal; the proposal will include the following transactions:
      • call _setImplementation on the DAO proxy with the address of the V2 logic contract
      • call _setDynamicQuorumParams on the DAO proxy to set up dynamic quorum with parameters the DAO decided on
    • Proposals that were created on V1 and will remain active post-upgrade, should behave the same (e.g. maintain their quorum setting)
    • More generally, should ensure that there are no hidden storage issues when migrating from V1 → V2

Key risks we’d like you to explore:

  • Bricking the DAO: this upgrade should not result in a non-functional DAO that cannot execute any additional proposals.
  • Security: this upgrade should not introduce any new cost-effective attack vectors.

Clarification on getPriorVotes, votingDelay and proposalCreationBlock

In castVoteInternal there’s a change that fixes how we use votingDelay such that moving forward it can be edited without impacting vote snapshots for any active proposals.

Nouns DAO uses vote snapshots from the block of proposal creation. In V1 proposal creation block was calculated thus: proposal.startBlock - votingDelay in the castVoteInternal function, so if votingDelay was edited, vote counts would become inconsistent with the proposal’s intended snapshot. In V2 the fix is storing the proposals creationBlock on the proposal struct, which removes the dependency on votingDelay.

What might be confusing is the function proposalCreationBlock:

function proposalCreationBlock(Proposal storage proposal) internal view returns (uint256) {
    if (proposal.creationBlock == 0) {
        return proposal.startBlock - votingDelay;
    }
    return proposal.creationBlock;
}

This logic handles the case of proposals that were created using V1 logic, and remain active after the upgrade to V2; such proposals would not have creationBlock set, so we continue to calculate their creation block the same as V1. Based on the current DAO configuration about a week after V2 is deployed all V1 proposals should reach their final state, and from that moment on proposalCreationBlock would rely solely on proposal.creationBlock.

The risky point we’re aware of is that we should not attempt to edit votingDelay during this transition week when V1 proposals are still active.

Files in scope

Fileblankcommentcode
contracts/governance/NounsDAOLogicV2.sol125276630
contracts/governance/NounsDAOLogicV1.sol81199403
contracts/governance/NounsDAOInterfaces.sol66164210
contracts/governance/NounsDAOProxy.sol185667
contracts/base/ERC721Checkpointable.sol4282165
contracts/base/ERC721Enumerable.sol279169

All other source contracts (not in scope)

Fileblankcommentcode
contracts/libs/Inflate.sol81171618
contracts/NounsArt.sol61175214
contracts/NounsDescriptorV2.sol56213201
contracts/base/ERC721.sol55202198
contracts/NounsDescriptor.sol51140160
contracts/SVGRenderer.sol2947154
contracts/NounsAuctionHouse.sol4585131
contracts/governance/NounsDAOExecutor.sol3328128
contracts/NounsToken.sol4599119
contracts/libs/MultiPartRLEToSVG.sol1730111
contracts/interfaces/INounsArt.sol491489
contracts/interfaces/INounsDescriptorV2.sol431484
contracts/test/Multicall2.sol17777
contracts/governance/NounsDAOProxyV2.sol195867
contracts/test/WETH.sol16354
contracts/interfaces/INounsDescriptor.sol401445
contracts/libs/SSTORE2.sol193744
contracts/test/NounsTokenHarness.sol8140
contracts/libs/NFTDescriptor.sol82138
contracts/libs/NFTDescriptorV2.sol82138
contracts/test/NounsDAOImmutable.sol6137
contracts/NounsSeeder.sol71832
contracts/test/MaliciousVoter.sol6129
contracts/test/NounsDAOLogicV2Harness.sol4127
contracts/interfaces/INounsAuctionHouse.sol192026
contracts/test/NounsDAOExecutorHarness.sol9126
contracts/interfaces/INounsToken.sol231425
contracts/test/NounsDAOLogicV1Harness.sol4123
contracts/interfaces/ISVGRenderer.sol81414
contracts/interfaces/INounsSeeder.sol61412
contracts/test/MaliciousBidder.sol4112
contracts/interfaces/INounsDescriptorMinimal.sol132011
contracts/proxies/NounsAuctionHouseProxy.sol5149
contracts/Inflator.sol5228
contracts/interfaces/IWETH.sol416
contracts/interfaces/IInflator.sol5145
contracts/external/opensea/IProxyRegistry.sol214
contracts/proxies/NounsAuctionHouseProxyAdmin.sol5153
  • counted using cloc

Skipped tests

There are 3 skipped hardhat tests it's ok to ignore for this audit:

  • NounsDescriptor
    • should generate valid token uri metadata for all supported parts when data uris are enabled
  • NounsDescriptorV2
    • should generate valid token uri metadata when data uris are enabled [ @skip-on-coverage ]
    • should generate valid token uri metadata for all supported parts when data uris are enabled

How to get context

At this point you should have sufficient context to dive into V2:

Contact info ☎️

We'll be around the C4 Discord. Please let us know if we can help in any way!

NameDiscordTwitterTimezone
eladelad | ⌐◨-◨#6192eladmallelGMT-5
solimandersolimander#7468solimanderGMT-6