Governor Nexus - Implementation report

TL;DR: The Governor Nexus RFC we posted five months ago is now fully implemented, built on stock OpenZeppelin plus the mechanisms the RFC proposed, and the code is public at blockful/nexus. This post explains what we shipped and opens a two-week feedback window before we move to the external audit.


What we built

Governor Nexus is built on OpenZeppelin v5.6.1. The core is composed from the standard audited Governor modules. We wrote only what the RFC proposed and OZ does not provide:

  • the proposal-type registry, governed by vote
  • the three initially suggested rulesets: Standard, Optimistic, and Bond
  • the security mechanisms on the core: mutable votes, the late-vote extension, the per-proposer spam limit, the proposal cancellation policy, and batch voting

The existing timelock is untouched, as the RFC committed.

                    +------------------------------+        +-----------+
  Users ----------> |     Governor Nexus Core      | -----> |  Timelock |
                    | (proposal lifecycle, type    |        | (kept     |
                    |  registry, timelock admin)   |        |  as-is)   |
                    +------------------------------+        +-----------+
                               ^
                               | counting, quorum and success, per type
          +--------------------+--------------------+
          |                    |                    |
+------------------+  +--------------------+  +------------------+
| Standard Ruleset |  | Optimistic Ruleset |  |  Bond Ruleset    |
| same as live ENS |  | pass unless opposed|  | lock-to-propose  |
+------------------+  +--------------------+  +------------------+

Every proposal gets exactly one type at creation and keeps it forever. A type’s registry entry (ruleset, voting delay, voting period, proposal threshold) never changes after registration, and rulesets themselves are immutable contracts with no setters. The only things a vote can change are which types are active and which one is the default. The DAO evolves governance by registering new types, never by changing live ones, so what the DAO audited is what runs.


How we addressed each risk

The RFC was built on our Governance Security Assessment, which placed ENS at Stage 0 under the Anticapture framework. Each risk from that assessment now has a shipped mechanism:

Risk Severity Implemented fix Status
Insufficient voting delay Critical Voting delay is a per-type registry parameter. The migration proposal sets it to 2 days by vote, and tuning it later needs no code change :white_check_mark:
No active proposal limits Critical Each proposer can have at most 2 live proposals (Pending or Active), adjustable by vote between 1 and 10 :white_check_mark:
No continuous threshold enforcement High A proposal whose proposer drops below the threshold becomes cancellable by anyone, while voting continues :white_check_mark:
Interface security unverified Medium With mutable votes, a voter can recast and replace their vote, so a vote cast through a compromised interface can be corrected before the deadline :white_check_mark:
No late vote extension Medium A proposal that flips from failing to passing in the final 24h extends voting by 48h from the original deadline :white_check_mark:
Vote immutability Medium Addressed by the same mutable votes mechanism as interface security above :white_check_mark:
No optimistic path for routine ops Low With OptimisticRuleset, a proposal passes unless 500k ENS oppose it. Only allowlisted proposers and actions can use it, and the allowlists start empty :white_check_mark:
Uniform approval thresholds Low Every type has its own delay, period, threshold, and quorum in the registry :white_check_mark:
No batch voting Low A single call casts votes on several proposals (castVoteWithReasonAndParamsBatch, votes only, all-or-nothing) :white_check_mark:
Subsidy coverage for contract wallets Low Voting by signature now accepts ERC-1271 contract signatures (from OpenZeppelin v5), so Safes and multisigs can sign votes for a relayer to submit :white_check_mark:

With these mechanisms deployed and the migration parameters ratified, ENS moves from Stage 0 to Stage 1, as the RFC set out to do.


Design decisions

We made many design decisions during implementation. These are the ones the community should weigh in on:

# Decision What shipped Trade-off accepted
1 A re-vote replaces the old vote A re-vote removes the old weight and adds the new one in the same call, so a vote is never counted twice and never briefly missing Quorum and success can flip both ways while voting is open
2 Late-vote extension evaluates only at the deadline Fires only if the proposal was observed failing at some point in the final 24h and would pass at the deadline, with the 48h extension counted from that original deadline We gave up OZ’s audited PreventLateQuorum and wrote our own extension logic
3 Batch voting is an explicit function A dedicated function that batches votes only, applying all of them or none Only votes can be batched, and it is all-or-nothing, so one failing vote reverts the whole batch
4 Cancellation narrower than Bravo’s Proposers can cancel their own proposals, and anyone can cancel one whose proposer dropped below the threshold, only while it is Pending or Active Once a proposal passes, nobody can cancel it through the governor. A harmful proposal discovered after the vote can only be stopped by the timelock or the Security Council
5 Optimistic path starts empty Proposer and action allowlists start empty. Calls into the governor or the registry can never be allowlisted The optimistic path does nothing at launch, and adding each proposer or action requires a full governance vote
6 Bond slash rule is EP 5.15, verbatim The bond is forfeited only if the combined against votes strictly beat the for votes, and the votes asking to slash strictly beat those against without slashing. Any tie refunds the bond Bond parameters are fixed at deployment. Changing them means deploying a new ruleset through governance
7 Bonds refund once the proposal survives the vote Anyone can call resolveBond once the proposal reaches Succeeded A timelock veto forfeits the bond only if still unsettled when it lands (see note below)

Two of these deserve a longer note:

The crossing rule (decisions 1–2). The RFC discussion rightly flagged the game-theoretic complexity mutable votes add. We kept mutability because it is the only recovery path when a voting interface is compromised. This is not locked in either. Vote counting lives in the ruleset layer, so a future ruleset can implement immutable votes without changing the governor. We turned the concern into a hard design rule for the whole system. Nothing may be permanently triggered by a tally crossing a threshold. An attacker could cross a threshold early, re-vote back below it, and waste the trigger before the crossing that matters. So everything that needs finality evaluates at the deadline instead of reacting to crossings.

The bond softening (decision 7). We implemented the EP 5.15 slash rule exactly as ratified. But bonds refund from Succeeded onward, and anyone can settle a passed proposal’s bond. So if the Security Council vetoes the proposal while it sits in the timelock, the bond is only forfeited if nobody settled it first. We accept this because the bond is an anti-spam instrument, and surviving the vote fulfills its purpose. Still, this is the one place where we consciously softened what was originally discussed, and we flag it here for exactly that reason.


Risks we accepted

A registered ruleset is trusted. The registry chooses how votes are counted, not what a type is allowed to do. Every active type can execute any action through the timelock, and a buggy or malicious ruleset decides which of its own type’s proposals pass. We mitigate this with process rather than code, because registering a type is a full governance action, and the quorum and threshold chosen at registration are security parameters for the whole DAO.

Per-address limits don’t resist address-splitting. The spam cap and proposal threshold are per-address, like every production governor’s. The bond changes the economics instead, since each sybil identity locks a full bond and spam cost scales linearly with proposal count no matter how it is split.

A single-block dip below the threshold counts. A proposer whose power dips below threshold for one block leaves their proposal cancellable at the next. This is only a griefing vector, the proposer controls it, and it matches how Bravo already behaves. We added no grace period.

The extension fires only once. A flip at the end of the extended window gets no second extension, per the RFC. The defense here is delegates watching the vote, not a code change.

Each of these risks is documented in depth in the repo.


How we tested it

We tested at every level. Each mechanism has its own unit suite. Beyond units, adversarial suites prove a misbehaving ruleset cannot affect proposals of other types, and fuzz and invariant suites check system-wide properties, like bond custody staying solvent (locked = refunded + slashed) across randomized lifecycles.

We also ran fork and differential tests against the current mainnet governor. Each difference we introduced on purpose (mutable votes, the late-vote extension, the new functions and events) has its own dedicated test, so CI fails if the two ever diverge in a way we didn’t intend. The fork tests also gave us the gas numbers. Proposing costs about 24k more gas, voting 29k more, queueing 20k more, and executing gets 18k cheaper, with the full breakdown in the repo README.

Finally, every mechanism went through an internal security review before we moved to the next. All high-severity findings were addressed, and the risks we accepted are the ones listed above. These reviews complement but do not replace an external audit (see the open questions and timeline below).


Integration notes

If you build on top of the governor (indexers, voting interfaces, gasless relayers), these are the changes that affect you:

  • Indexers: when someone re-votes, the governor emits another standard VoteCast event for the same proposal and voter, and the latest event in log order is the one that counts. If you’d rather not track events, voteReceipt(proposalId, voter) returns the standing vote directly.
  • Gasless relayers: build ballots from voteNonce(proposalId, account), not the account-global nonces(address), because ballot nonces are per-proposal. When a voter votes directly, that spends the proposal’s nonce and invalidates any outstanding signed ballot for it, while signatures held for other proposals stay valid.
  • Deadlines: always trust proposalDeadline(). The deadline only grows when an extension actually fires, so you will never see a tentative extension mid-window. The ProposalExtended event uses OZ’s ABI verbatim.
  • Counting modes: if your indexer already supports Optimism’s optimistic governor, it supports ours, since the optimistic type advertises the same COUNTING_MODE string.

Open questions for the DAO

  1. Audit firm. Which firms should we request quotes from? Our recommendation: firms with deep OpenZeppelin Governor experience. OpenZeppelin themselves (they wrote the code Nexus builds on) and Trail of Bits are the first firms we would ask, alongside other reputable firms suggested in this thread. We will collect suggestions during the feedback window and publish the quotes alongside the audit proposal.
  2. Optimistic allowlist curation. Which proposers and which actions should the first curation vote allow, or should the path stay empty at launch? Our recommendation: migrate with the allowlists empty and run the first curation vote only once the system is live, starting with recurring, bounded operations (service-provider stream management is the natural first candidate) and never treasury token approvals.
  3. Migration parameters. The migration proposal will set the concrete values: a 2-day voting delay, per-type voting periods and thresholds, a 1,000 ENS bond, and a 500k ENS opposition threshold for optimistic proposals. Do these look right to you? Our recommendation: keep them as proposed. The 2-day delay is what our assessment recommended, the 1,000 ENS bond is what the DAO already ratified in EP 5.15, and the 500k opposition threshold comes from the RFC.

Timeline and next steps

This post opens a two-week community feedback window on the questions and recommendations above. During the window we will walk through the implementation in a blockful office hours session, and we encourage technical readers to review the repo and point out risks we didn’t map.

Step Window Status
RFC posted for discussion Mar 2026 :white_check_mark: Done
Community feedback incorporated Mar – Jun 2026 :white_check_mark: Done
Implementation (mechanism by mechanism, each with spec, tests, and internal review) Jul 2026 :white_check_mark: Done
Implementation report (this post) and two-week feedback window Aug 2026 :left_arrow: We are here
External audit proposal: scope, firm, and price Once quotes are in Next
External audit and fixes Post-approval Pending
Migration proposal: deployment, parameter ratification, Timelock-admin transfer Post-audit Pending

After the window closes:

  1. We incorporate the thread’s feedback, finalize the audit scope, and request quotes from the shortlisted firms.
  2. Once the quotes are in, we post the external audit proposal (final scope, firm, and price) for the DAO’s approval.
  3. Post-audit, we publish the migration plan (deployment, parameter ratification, and the Timelock-admin transfer we deliberately left out of this phase) and take it to a vote.

blockful’s work on this project is covered under its Service Provider proposal.

Some thoughts/questions regarding the “How we addressed each risk” section:

  • My understanding is “No active proposal limits” and “No continuous threshold enforcement” need to happen together, otherwise someone can submit 2 proposals then delegate tokens to another account, repeating this infinite times. Is a simpler solution just a global rate limit of 3-5 active proposals at a time?
  • I agree that mutable votes would be good.
  • I don’t necessarily see the need for late vote extension. It’s very common for people to vote at the end, so I could see this just adding a day to ~every proposal’s lifecycle. I was going to raise the concern of DOS attacks, but see that it can only be extended once so that’s a non-issue.
  • Optimistic voting scares me, and I think delegate burden should reduce overtime as routine ops move to the Foundation. Are there any specific examples of things you’d want to support with Optimistic voting in the short-medium term?
  • I don’t think the current lack of batch voting is an issue, and think most voting UX leads people to do 1 at a time anyways. Plus I see this as more of a wallet issue over time with things like EIP-5792. But ultimately I don’t have a reason to be against this, so would consider it neutral.
1 Like