Platform: Code4rena
Start Date: 03/07/2023
End Date: 13/07/2023
Period: 10 days
Status: Completed
Pot Size: $100,000 USDC
Participants: 36
Reporter: liveactionllama
Judge: gzeon
Id: 257
League: ETH
cccz | 1/36 | $31,683.74 | 2 | 0 | 0 | 2 | 2 | 0 | 0 | 0 |
kutugu | 2/36 | $15,841.87 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 0 |
jasonxiale | 3/36 | $5,196.93 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
said | 4/36 | $4,056.62 | 2 | 1 | 0 | 0 | 0 | - | 0 | 0 |
0xA5DF | 5/36 | $3,997.64 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
iglyx | 6/36 | $3,997.64 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
0xG0P1 | 7/36 | $3,997.64 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
shark | 8/36 | $2,874.81 | 3 | 1 | 0 | 0 | 0 | Grade A | 0 | Grade A |
0xnev | 9/36 | $1,405.81 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | Grade A |
0xSmartContract | 10/36 | $1,081.39 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | Grade A |
Auditor per page
Automated findings output for the audit can be found here.
Note for C4 wardens: Anything included in the automated findings output is considered a publicly known issue and is ineligible for awards.
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 720 Nouns have been sold on auction, and the DAO has funded a variety of coders, artists, athletes, and IRL and Ethereum public goods.
More intro information can be found on Nouns Center.
TLDR: we're upgrading our governor contract with a bunch of new features, including Nouns Fork, a minority protection mechanism that allows a group of token holders to exit together into a new instance of Nouns DAO.
Nouns DAO relies on a handful of contracts on mainnet today:
For more detail on all features except Nouns Fork, see this spec.
The Nouns governor is currently at V2. This version under audit is V3, and the upgrade can only happen through a proposal, since only the treasury (executor) contract has the permission to set the governor proxy's implementation address.
The Nouns Fork feature introduced in this version requires the ability to send DAO funds to fork DAOs without going through a proposal process; this requirement means we need a new treasury, and since the current treasury is not upgradable, our plan is to migrate funds to the new treasury, and make the new treasury upgradeable to minimize the risk of another such migration in future.
Furthermore, for several reasons it's important to maintain the ability to execute proposals on the original treasury, and for that reason we've added proposal functions that would still use it on the new governor version, e.g. proposeOnTimelockV1
and executeOnTimelockV1
.
The deployment consists of the following scripts:
DeployDAOV3NewContractsMainnet
] Deploy all the new contracts.ProposeDAOV3UpgradeMainnet
] Propose the upgrade to V3 and all the config changes this upgrade entails:
ProposeTimelockMigrationCleanupMainnet
] Propose additional txs to treasury V1. This proposal will be proposed once ProposeDAOV3UpgradeMainnet
has been successfully executed. This gives time for proposals using TokenBuyer (asking for USDC)
to execute successfully.
This script includes the following:
ProposeENSReverseLookupConfigMainnet
] Propose post-upgrade on treasury V2 to set its ENS reverse lookup to nouns.eth
.
nouns.eth
updates the main record to point to treasury V2 instead of V1. The relevant person from the founders is ready to execute the ENS changes in time.The key risks we'd like for you to dig into are:
Main contracts to pay attention to:
NounsDAOLogicV3
, including the upgrade from NounsDAOLogicV2
to NounsDAOLogicV3
, keeping in mind there will be proposals in progress in various states when the upgrade is executed.NounsDAOForkEscrow
, making sure no token loss or theft is possible, including by the DAO itself, keeping in mind that forking might be used in attack scenarios.NounsDAOExecutorV2
, paying attention to the transition from the current version to this new version, and extra attention to security since this is the contract that holds all the DAO's funds.governance/fork/newdao/
, making sure the original DAO can't attack a fork DAO in any material way (there are known issues we're choosing to keep for other considerations, see more info below).Please refer to this commit on the nouns monorepo for the version to audit.
File | Lines | nLines | nSLOC | Comment Lines |
---|---|---|---|---|
script/ProposeTimelockMigrationCleanupMainnet.s.sol | 102 | 94 | 68 | 12 |
script/DeployDAOV3NewContractsTestnet.s.sol | 39 | 39 | 33 | 3 |
script/DeployDAOV3DataContractsSepolia.s.sol | 12 | 12 | 8 | 1 |
script/DeployDAOV3DataContractsGoerli.s.sol | 12 | 12 | 8 | 1 |
script/ProposeDAOV3UpgradeMainnet.s.sol | 147 | 137 | 106 | 9 |
script/DeployDAOV3NewContractsBase.s.sol | 107 | 89 | 75 | 4 |
script/ProposeENSReverseLookupConfigMainnet.s.sol | 48 | 44 | 29 | 6 |
script/DeployDAOV3NewContractsMainnet.s.sol | 22 | 22 | 18 | 3 |
script/ProposeDAOV3UpgradeTestnet.s.sol | 173 | 163 | 134 | 6 |
script/DeployDAOV3DataContractsBase.s.sol | 37 | 37 | 27 | 1 |
contracts/utils/ERC20Transferer.sol | 37 | 37 | 9 | 24 |
contracts/governance/NounsDAOLogicV2.sol | 1091 | 1032 | 616 | 292 |
contracts/governance/NounsDAOProxy.sol | 141 | 141 | 67 | 56 |
contracts/governance/NounsDAOExecutorProxy.sol | 28 | 28 | 5 | 18 |
contracts/governance/NounsDAOLogicV3.sol | 1036 | 932 | 336 | 484 |
contracts/governance/NounsDAOV3Votes.sol | 335 | 287 | 131 | 116 |
contracts/governance/NounsDAOV3Admin.sol | 608 | 565 | 291 | 164 |
contracts/governance/fork/ForkDAODeployer.sol | 173 | 165 | 98 | 41 |
contracts/governance/NounsDAOExecutorV2.sol | 249 | 227 | 136 | 48 |
contracts/governance/fork/NounsDAOV3Fork.sol | 260 | 232 | 114 | 76 |
contracts/governance/NounsDAOV3Proposals.sol | 1018 | 877 | 579 | 188 |
contracts/governance/NounsDAOV3DynamicQuorum.sol | 164 | 148 | 85 | 43 |
contracts/governance/NounsDAOInterfaces.sol | 873 | 770 | 395 | 284 |
contracts/governance/fork/NounsDAOForkEscrow.sol | 193 | 188 | 63 | 92 |
contracts/governance/NounsDAOExecutor.sol | 189 | 171 | 110 | 28 |
contracts/governance/fork/newdao/NounsAuctionHouseFork.sol | 282 | 274 | 133 | 94 |
contracts/governance/fork/newdao/governance/INounsTokenForkLike.sol | 23 | 6 | 3 | 1 |
contracts/governance/fork/newdao/governance/NounsDAOStorageV1Fork.sol | 147 | 147 | 68 | 60 |
contracts/governance/fork/newdao/governance/NounsDAOLogicV1Fork.sol | 802 | 758 | 413 | 257 |
contracts/governance/fork/newdao/governance/NounsDAOEventsFork.sol | 71 | 71 | 38 | 19 |
contracts/governance/fork/newdao/token/NounsTokenFork.sol | 328 | 320 | 140 | 124 |
contracts/governance/fork/newdao/token/INounsTokenFork.sol | 58 | 41 | 14 | 14 |
contracts/governance/fork/newdao/token/base/ERC721CheckpointableUpgradeable.sol | 293 | 265 | 131 | 94 |
Totals | 9098 | 8331 | 4481 | 2663 |
Lines counted using Solidity Metrics in VSCode.
Please see this Spearbit document.
We've decided in this version to deploy fork DAOs such that fork tokens reuse the same descriptor contract as the original DAO's token descriptor. Our motivations are minimizing lines of code and the gas cost of deploying a fork.
This design poses a risk on fork tokens: the original DAO can update the descriptor to use a new art contract that always reverts, which would then lead to fork token's mint function always reverting.
The solution would be for the fork DAO to execute a proposal that deploys and sets a new descriptor to its token, which would use a valid art contract, allowing minting to resume.
The fork DAO is guaranteed to be able to propose and execute such a proposal, because the function where Nouners claim their fork tokens does not use the descriptor, and so is not vulnerable to this attack.
In ForkDAODeployer.deployForDAO()
, the fork's AuctionHouse is initialized with values copied from the original Nouns AuctionHouse, by calling its getter functions. Therefore, if Nouns DAO executes a proposal that sets the token's minter to an account that does not implement the same getters, fork deployments will fail.
We view this as a minor issue, since the DAO can easily execute a proposal that switches ForkDAODeployer
out for a new version that would support the minter change.
For example, a malicious majority can vote For a proposal to drain the treasury, forcing others to fork; the majority can then join the fork with many of their tokens, benefiting from the passing proposal on the original DAO, while continuing to attack the minority in their new fork DAO, forcing them to quit the fork DAO.
This is a well known flaw of the current fork design, something we've chosen to go live with for the sake of shipping something the DAO has asked for urgently.
See the V2 audit report for all the details.
We've fixed this issue in the fork token contract, but cannot fix it in the original NounsToken because the contract isn't upgradeable.
We're aware of different ways of abusing this function:
We find these issues low risk and unlikely given the small size of the community, and the low ETH balance the governor contract has to spend on refunds. Should we see such abusive activity, we might reconsider this feature.
We're aware of this issue. It means the Nouns token will stop functioning a long long long time from now :)
Bidder Alice can bid from a smart contract that returns a large byte array when receiving ETH. Then if Bob outbids Alice, in his bid tx AuctionHouse refunds Alice, and the large return value causes a gas cost spike for Bob. See more details here.
We're planning to fix this in the next AuctionHouse version, its launch date is unknown at this point.
In all new code we're using custom errors. In code that's forked from previous versions we optimized for the smallest diff possible, and so leaving error strings untouched.
Clone with one of the following:
git clone --recurse-submodules git@github.com:code-423n4/2023-07-nounsdao.git git clone --recurse-submodules https://github.com/code-423n4/2023-07-nounsdao.git
If the repo was cloned without recursing, it's still possible to execute the following:
git submodule update --init --recursive
Install the dependencies with the following:
yarn
in the nouns-monorepo/
folder.forge install
in the nouns-monorepo/packages/nouns-contracts
folder.All new tests use forge, with a small remainder of legacy tests in hardhat.
In the nouns-monorepo/packages/nouns-contracts
folder:
forge test --ffi
.yarn test
.slither . --foundry-out-directory artifacts --filter-paths "/test/|/lib/|/script|/node_modules/"
The forge test suite contains a mainnet fork test:
RPC_MAINNET
to an RPC like Alchemy or Infura pointing to Ethereum mainnet. It's also possible to use a public RPC with export RPC_MAINNET="https://eth.llamarpc.com"
.forge test --ffi --nmc UpgradeToDAOV3ForkMainnetTest
.