- $47,500 USDC main award pot
- $2,500 USDC gas optimization award pot
- Join C4 Discord to register
- Submit findings using the C4 form
- Read our guidelines for more details
- Starts August 22, 2022 20:00 UTC
- Ends August 27, 2022 20:00 UTC
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.
yarn
yarn test
forge install
forge test -vvv --ffi
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.
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 fileNounsDAOInterfaces.sol
) - NounsToken's functions
getPriorVotes
andtotalSupply
, which are implemented inERC721Checkpointable
andERC721Enumerable
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
- call
- 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.
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.
File | blank | comment | code |
---|---|---|---|
contracts/governance/NounsDAOLogicV2.sol | 125 | 276 | 630 |
contracts/governance/NounsDAOLogicV1.sol | 81 | 199 | 403 |
contracts/governance/NounsDAOInterfaces.sol | 66 | 164 | 210 |
contracts/governance/NounsDAOProxy.sol | 18 | 56 | 67 |
contracts/base/ERC721Checkpointable.sol | 42 | 82 | 165 |
contracts/base/ERC721Enumerable.sol | 27 | 91 | 69 |
File | blank | comment | code |
---|---|---|---|
contracts/libs/Inflate.sol | 81 | 171 | 618 |
contracts/NounsArt.sol | 61 | 175 | 214 |
contracts/NounsDescriptorV2.sol | 56 | 213 | 201 |
contracts/base/ERC721.sol | 55 | 202 | 198 |
contracts/NounsDescriptor.sol | 51 | 140 | 160 |
contracts/SVGRenderer.sol | 29 | 47 | 154 |
contracts/NounsAuctionHouse.sol | 45 | 85 | 131 |
contracts/governance/NounsDAOExecutor.sol | 33 | 28 | 128 |
contracts/NounsToken.sol | 45 | 99 | 119 |
contracts/libs/MultiPartRLEToSVG.sol | 17 | 30 | 111 |
contracts/interfaces/INounsArt.sol | 49 | 14 | 89 |
contracts/interfaces/INounsDescriptorV2.sol | 43 | 14 | 84 |
contracts/test/Multicall2.sol | 17 | 7 | 77 |
contracts/governance/NounsDAOProxyV2.sol | 19 | 58 | 67 |
contracts/test/WETH.sol | 16 | 3 | 54 |
contracts/interfaces/INounsDescriptor.sol | 40 | 14 | 45 |
contracts/libs/SSTORE2.sol | 19 | 37 | 44 |
contracts/test/NounsTokenHarness.sol | 8 | 1 | 40 |
contracts/libs/NFTDescriptor.sol | 8 | 21 | 38 |
contracts/libs/NFTDescriptorV2.sol | 8 | 21 | 38 |
contracts/test/NounsDAOImmutable.sol | 6 | 1 | 37 |
contracts/NounsSeeder.sol | 7 | 18 | 32 |
contracts/test/MaliciousVoter.sol | 6 | 1 | 29 |
contracts/test/NounsDAOLogicV2Harness.sol | 4 | 1 | 27 |
contracts/interfaces/INounsAuctionHouse.sol | 19 | 20 | 26 |
contracts/test/NounsDAOExecutorHarness.sol | 9 | 1 | 26 |
contracts/interfaces/INounsToken.sol | 23 | 14 | 25 |
contracts/test/NounsDAOLogicV1Harness.sol | 4 | 1 | 23 |
contracts/interfaces/ISVGRenderer.sol | 8 | 14 | 14 |
contracts/interfaces/INounsSeeder.sol | 6 | 14 | 12 |
contracts/test/MaliciousBidder.sol | 4 | 1 | 12 |
contracts/interfaces/INounsDescriptorMinimal.sol | 13 | 20 | 11 |
contracts/proxies/NounsAuctionHouseProxy.sol | 5 | 14 | 9 |
contracts/Inflator.sol | 5 | 22 | 8 |
contracts/interfaces/IWETH.sol | 4 | 1 | 6 |
contracts/interfaces/IInflator.sol | 5 | 14 | 5 |
contracts/external/opensea/IProxyRegistry.sol | 2 | 1 | 4 |
contracts/proxies/NounsAuctionHouseProxyAdmin.sol | 5 | 15 | 3 |
- counted using cloc
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
- A basic understanding of Compound’s Governor Bravo is very useful, since Nouns DAO V1 is an NFT-adapted fork of it
- Read NounsDAOLogicV1, since V2 is a diff from that; most importantly read the docs articulating the modifications from Compound’s contract
- Get acquainted with NounsToken, and specifically the ERC721Checkpointable features
- For full context on how Nouns works (beyond the in-scope contracts), read the Nouns Protocol wiki that covers the auction house and on-chain art contracts
- Nice-to-have: read Dialectic’s two great posts on a Nouns governance attack on the treasury: 1, 2
At this point you should have sufficient context to dive into V2:
- Read the docs on V2’s modifications
- Read the diff between
NounsDAOLogicV2
andNounsDAOLogicV1
- Read the e2e test that demos the upgrade to V2
We'll be around the C4 Discord. Please let us know if we can help in any way!
Name | Discord | Timezone | |
---|---|---|---|
elad | elad | ⌐◨-◨#6192 | eladmallel | GMT-5 |
solimander | solimander#7468 | solimander | GMT-6 |