Nouns DAO V3 upgrade, including Nouns Fork
Proposed by 0x05...6Ff7
- on Loading at -
Quorum: 79 votes

Summary

This proposal upgrades the Nouns DAO logic contract to V3. This version introduces the following changes:

  1. Proposal editing: adding a new initial state to the proposal lifecycle: “Updatable”, during which proposers can update their proposal's transactions and text description.
    • This feature will be disabled at first and will require a separate proposal to configure the period of the Updatable state.
  2. Propose by signatures: allowing Nouners and delegates to pool their voting power towards submitting a proposal, by submitting their signature, instead of the current approach where sponsors must delegate their votes to help a proposer achieve threshold.
    • This feature will be activated once this proposal executes.
  3. Objection-only period: a conditional voting period that gets activated upon a “last-minute” proposal swing from defeated to successful, affording voters more reaction time. Only against votes are possible during the objection period.
    • This feature will be disabled at first and will require a separate proposal to enable it and configure its parameters.
  4. Votes snapshot after voting delay: moving votes snapshot up, to provide Nouners with reaction time per proposal, to get their votes ready (e.g. some might want to move their delegations around). Currently the vote snapshot block is the proposal creation block.
  5. Nouns Fork: allowing Nouners to exit the DAO by forking to a new DAO. Current threshold is set at 20%. For more details please read the tech spec and this post.

An updated version of nouns.wtf will be deployed to add support for these features.

Audit

The DAO funded two audits for this version, first with Spearbit and then with code4rena. Below is a summary of the results with links to the reports.

Please reach out to us (verbs team) if you have any questions regarding the findings.

Spearbit found:

  • 0 critical risk severity issues
  • 2 high risk severity issues
  • 18 medium risk severity issues
  • 22 low risk severity issues

For more info: the full Spearbit audit report.

code4rena found:

  • 1 high risk finding
  • 3 medium risk findings
  • 8 low risk findings

For more info: the full code4rena audit report.

Won’t Fix Issues

There were some issues we chose to not fix despite agreeing they were good findings, because we wanted to avoid adding further risk or time delays. We’re listing those here explicitly for the sake of max transparency. There were other issues we did not fix because we disagreed with the auditors’ analysis, and those are not listed here.

Changes in detail

Proposal editing

Proposals get voter feedback almost entirely only once they are onchain. At the same time, proposers are reluctant to cancel and resubmit their proposals for multiple reasons, e.g. prefering to avoid restarting the proposal lifecycle and thus delay funding.

There have been cases where proposers made promises offchain, e.g. to return part of the funds, but that is not captured onchain, and not visible to the entire voter base.

This feature introduces a new state into the proposal lifecycle:

Updatable (new) → Pending → Active …

During the Updatable state, a proposer can update their proposal’s description and/or transactions. Following the Updatable state, there’s a Pending state during which updates are no longer available before voting starts during the Active state.

The length of the Updatable state is configurable by the DAO, and starts at zero. This effectively keeps the feature disabled. A separate proposal will follow with proposed periods for the state, for example: Updatable: 5 days, Pending: 1 day

There’s also an onchain method of providing feedback to proposals while they are in the Updatable state.

Propose by signatures

Proposal threshold is the number of votes an account must control in order to submit a proposal. As of this writing an account must have at least 2 votes to pass the threshold.

Current methods of sponsoring a proposal and their problems:

Nouners today either delegate their Nouns to proposers or submit the proposal on their behalf. Delegating is limited to Nouners, since delegates can re-delegate their vote to another account. When a Nouner delegates, they can’t use their voting power for the length of the proposal lifecycle which is very inconvenient.

If a Nouner/delegate chooses to submit a proposal on behalf of the proposer, we lose the meaning of who is the actual proposer and also the process itself can be cumbersome where the delegate needs to make sure the proposal looks good and pay gas for it.

This upgrade introduces a new function to the DAO contract: proposeBySigs. This allows anyone to submit a proposal and a set of signatures. The signatures are provided by voters and need to match the proposal content. If the sum of votes from all the signers surpasses proposal threshold, the proposal can be submitted.

The goal is to open another channel through which proposers can submit proposals, without necessarily finding a sponsor through private channels.

Voters can only be signers if they don’t currently have an active proposal, either as a proposer or signer. Signers of a proposal can cancel it at any time, and the same is true for proposers; proposers should choose their signers carefully.

This allows both Nouners and delegates to “sign-off” on a proposal, while the proposer address remains the actual proposer and without the need to change delegations. It also allows pooling voting power, e.g. two delegates with 1 vote each can sign a proposal in order for it to pass the proposal threshold, currently at 2 votes. It also opens the door to raising the proposal threshold since it’s easier to collect voting power.

As part of this update we are adding support for “proposal candidates” to nouns.wtf, where proposers can submit their proposals and request signatures. In order to prevent spam, creating a candidate proposal requires either holding 1 vote or sending 0.1 ETH (the ETH recipient and the amount are configurable by the DAO). The candidate proposals data are submitted to a side contract to make this data publicly available for any frontend to interact with.

Objection-only period

The goal is to protect the DAO from executing proposals that the majority would not want to execute. The threat is one where a minority trying to pass a proposal can let the proposal look like it's failing, to cultivate voter apathy, then cast many of their For votes just before voting ends, swinging the proposal from defeated to successful, leaving the majority wishing they were more vigilant.

This upgrade introduces a new state in the proposal lifecycle: ObjectionPeriod.

… Active (regular voting period) → ObjectionPeriod (conditional) → Succeeded / Defeated \

ObjectionPeriod gets turned on only if a proposal turns from not passing to passing close enough to the end of the voting period.

During the ObjectionPeriod state, only voters who haven’t voted yet are allowed to vote, but only against the proposal.

Q: Why not allow voting For a proposal during this extra voting period? A: Similar to the attack scenario described above, it would still allow voters to keep their votes to themselves and vote only at the last minute. In this case it would be right before the ObjectionPeriod ends.

More broadly speaking, the risk that a proposal passes when it shouldn’t, is much higher than the risk of a proposal not passing when it should. A proposal can be re-proposed, but a proposal that passes can’t be reversed.

DAO configurable parameters:

  1. objectionPeriodDurationInBlocks: how long the objection period lasts, e.g. 2 days
  2. lastMinuteWindowInBlocks: what is considered “last minute”? e.g. 2 hours

Both parameters start with values of zero, effectively making this feature disabled. A separate proposal will follow turning on this feature with suggested parameters.

Votes Snapshot After Voting Delay

The current DAO implementation takes a snapshot of votes & delegation at the block a proposal is created, meaning Nouners have no lead time to put their votes in order, e.g. moving their delegations back to themselves to vote on something they really care about.

This upgrade changes the snapshot to be after the voting delay, i.e. once voting starts. Nouners will now have the duration of voting delay to transfer, buy, sell, delegate and undelegate their votes.

Because the nouns supply grows during the voting delay, nouns that are minted during the voting delay also participate in voting. Originally the snapshot was taken during proposal creation in order to align the total supply and the quorum and proposal calculations, which are performed at proposal creation time. Now that the supply has grown, this inconsistency is insignificant.

Nouns Fork

Further details can be found here in the tech spec and this post.

New treasury

The DAO fork feature requires switching to a new treasury contract which allows the DAO to transfer ETH and ERC20 tokens when a fork executes. The current treasury (treasury v1) contract is not upgradable, so it requires a migration to a new one. The new treasury (treasury v2), deployed at 0xb1a32FC9F9D8b2cf86C068Cae13108809547ef71, adds the new functions to the existing contract, and is upgradable to avoid the need for migrations in the future.

The migration includes:

  1. Transferring ETH, stETH, wstETH, rETH & USDC
  2. Changing ownership of the auction house, auction house proxy admin, nouns token & nouns descriptor contracts
  3. Change ownership of TokenBuyer and Payer to allow treasury v2 to perform ETH/USDC swaps
  4. Approve the treasury v2 to transfer lil nouns from treasury v1

Other assets in treasury v1 can be moved or approved to be moved in follow up proposals. Only assets in treasury v2 are included in the treasury forking in case of a fork.

DAO proposals are limited to 10 transactions, therefore we are doing the migration in two steps. There will be a follow up proposal with the second part of the migration. The follow up proposal will go onchain once this one executes.

The DAO will still have access to the original treasury (treasury v1), by proposing proposals via the proposeOnTimelockV1 function.

Existing streams created by the DAO will remain controlled by treasury v1. If the DAO chooses to cancel them, the proposeOnTimelockV1 function will need to be used.

Important heads up

Proposals that are queued but not yet executed before this proposal executes will need to be executed using the executeOnTimelockV1 function. The regular execute function will attempt to execute via treasury v2 and will fail.

Proposal #1 Transactions (this proposal):

  1. Send 2,500 ETH to new treasury contract
    The rest of the ETH will be transferred in a followup proposal. We can’t tell in advance how much ETH the treasury will have at the moment this proposal executes so we are sending a first chunk now.
  2. Upgrade the DAO contract logic to V3
  3. Set the fork params: a. forkEscrow: the escrow contract b. forkDeployer: the contract which deploys new forked DAOs c. erc20TokensToIncludeInFork: addresses of erc20 tokens which will be included in the treasury fork, specifically: stETH, wstETH, rETH and USDC d. forkPeriod: the period during which token holders can join a fork after it has been executed, 7 days e. forkThresholdBPS: the % of nouns required to surpass in order to activate a fork, 2,000 bps = 20%
  4. Set the proposal id at which the vote snapshot will be changed to be after voting delay a. Sets it to the next proposal after the current one
  5. Transfer ownership of the auction house proxy contract
  6. Send stETH to the new treasury, leaving 1,000 stETH for proposals that may be queued
  7. Change ownership of the Nouns token contract
  8. Change ownership of the Nouns descriptor contract
  9. Transfer ownership of the auction house proxy admin
  10. Set treasury V2 as the new treasury of the DAO contract, and set treasury v1 to be available to reference for proposeOnTimelockV1

Proposal #2 Transactions:

This proposal will be submitted only after the first one executes, since only then we will know the final amount of ETH in treasury V1 to transfer as part of this proposal.

  1. Move leftover ETH from treasury v1 to treasury v2
  2. Approve treasury v2 to transfer lil nouns from treasury v1
  3. Transfer ownership of TokenBuyer to treasury v2
  4. Transfer ownership of Payer to treasury v2
  5. Approve unlimited stETH to be transferred by the ERC20Transferer contract
  6. Invoke ERC20Transferer to send remaining stETH to the new treasury
  7. Transfer all wstETH to treasury v2
  8. Transfer all rETH to treasury v2
  9. Transfer all USDC to treasury v2

Can be viewed in more detail here.

Proposal #3 Transactions:

  1. Set ENS reverse lookup on treasury V2 to resolve to nouns.eth

Proposal execution notes

  • Proposals #1 and #2 will be executed on treasury v1, while proposal #3 is executed on treasury v2.
  • Prior to executing proposal #3, the accounts that control nouns.eth will set nouns.eth to point to treasury v2.