Skip to content

Proposals and voting

fact Sources contracts/src/governance/{NoteGovernor,UpgradeGovernor,TimelockDeployer}.sol. Both governors are OpenZeppelin v5.1 Governor with GovernorSettings, GovernorCountingFractional, GovernorVotes(veNOTE), GovernorVotesQuorumFraction and GovernorTimelockControl. UpgradeGovernor is a NoteGovernor subclass with constructor floors.

NoteGovernorUpgradeGovernor
NameNote GovernorNote Upgrade Governor
DecidesParameters, series policy, module swaps, recovery unwind, gauge registration, emission schedule, treasury actionsupgradeToAndCall on the four UUPS proxies (Treasury, BondDepository, RevenueRouter, Desk) and setUpgradeTimelock
Voting delay1 day1 day
Voting period5 days7 days (floor MIN_VOTING_PERIOD)
Proposal threshold0.5% of veNOTE supply (proposalThresholdBps = 50)0.5%
Quorummax(4% of veNOTE supply at snapshot, QUORUM_FLOOR) (quorumNumerator = 4)max(8%, QUORUM_FLOOR) (floor MIN_QUORUM_NUMERATOR)
Quorum floor (QUORUM_FLOOR, immutable, Params.quorumFloor)800,000 veNOTE in DeployGovernance.s.sol (QUORUM_FLOOR env override) — estimate, to be confirmed against the launch lock supplysame
Quorum countsfor + abstainfor + abstain
TimelockTimelock, 48 hUpgradeTimelock, 7 days (floor MIN_TIMELOCK_DELAY)
End to end (no cancellation)about 8 daysabout 15 days
Clocktimestamp, inherited from veNOTEsame

Both governors share CLOCK_MODE = mode=timestamp and COUNTING_MODE = support=bravo,fractional&quorum=for,abstain&params=fractional. The floor constants are MIN_VOTING_PERIOD = 7 days, MIN_QUORUM_NUMERATOR = 8 and MIN_TIMELOCK_DELAY = 7 days.

The UpgradeGovernor constructor reverts QuorumTooLow(numerator), VotingPeriodTooShort(period) or TimelockDelayTooShort(delay) if deployed below its floors. The floors are checked at construction; a later proposal to loosen them is itself subject to the 7-day process.

proposalThreshold() is max(proposalThresholdAbsolute, getPastTotalSupply(clock() − 1) × bps / 10,000). The supply is read one second in the past because IVotes.getPastTotalSupply requires a past timepoint. setProposalThresholdBps is onlyGovernance and emits ProposalThresholdBpsSet(previous, current); values above 10,000 revert InvalidBps.

Pending Active Succeeded Queued Executed Defeated Canceled votingDelay 1 d votingPeriod 5 d · 7 d queue() minDelay 48 h · 7 d guardian cancel majority against or quorum missed
States are the standard OpenZeppelin ProposalState values. The first duration is NoteGovernor's, the second UpgradeGovernor's. Execution is permissionless once the timelock delay has elapsed.
  1. Propose. propose(targets, values, calldatas, description). The proposer must hold at least the threshold in veNOTE at clock() − 1. Descriptions start with # <title> and include a ## Risk section so the app and indexers can render them consistently.
  2. Pending for votingDelay (1 day). The vote snapshot is taken at the end of this delay, so locks created after propose still count if they exist at the snapshot. The proposer may cancel in this state.
  3. Active for votingPeriod. Anyone with weight at the snapshot casts a vote.
  4. Succeeded if for-votes exceed against-votes and for + abstain reach quorum; otherwise Defeated.
  5. Queued by queue(). The governor schedules the operation on its timelock; the operation id is visible on-chain for the whole minDelay. The guardian may TimelockController.cancel(opId), which the governor reports as Canceled.
  6. Executed by anyone calling execute() after the delay. EXECUTOR_ROLE is address(0).

castVote(id, support) with support 0 = against, 1 = for, 2 = abstain, and castVoteWithReason, are the ordinary paths. Because both governors use GovernorCountingFractional, a voter can split weight across the three options in one transaction:

// support = 255 selects fractional mode; params packs three uint128 weights
governor.castVoteWithReasonAndParams(
proposalId,
255,
"60/25/15 split",
abi.encodePacked(uint128 againstVotes, uint128 forVotes, uint128 abstainVotes)
);

The three weights must not exceed the voter’s weight at the snapshot. Fractional mode also lets a voter cast in several transactions, as long as the running total stays within their weight. Votes cannot be changed once cast in nominal mode.

fact Both are OpenZeppelin TimelockController instances deployed by TimelockDeployer.

TimelockUpgradeTimelock
minDelay48 hours7 days
PROPOSER, CANCELLERNoteGovernor (and optionally a launch multisig as PROPOSER)UpgradeGovernor (and optionally a launch multisig as PROPOSER)
CANCELLERGuardianGuardian
EXECUTORaddress(0) (anyone)address(0)
DEFAULT_ADMIN_ROLErenounced by the deployerrenounced by the deployer
Is owner() ofevery Governed module, veNOTE, GaugeController, EmissionSchedule, Minter, gauges, SeriesRegistrynothing; it is the only address _authorizeUpgrade accepts

Because admin is renounced, granting or revoking a timelock role is itself a timelocked operation on that same timelock. Changing minDelay uses updateDelay, which only the timelock can call on itself.

The guardian address, expected to be a Security Council multisig, is guardian on every Governed module and holds CANCELLER_ROLE on both timelocks.

CanCannot
pause() any module (onlyOwnerOrGuardian)unpause() (owner only)
cancel any queued operation on either timelockPropose, queue or execute anything
NoteCore.cancelUnwind(seriesId)proposeUnwind, unwind, sweepExcess
OracleAdapter.revokeForceObservePropose a forced price or cancel an observation
Change any parameter, register or deprecate a core, add a gauge
Move funds from any contract, including recovered escrow
Upgrade code or shorten either delay

A pause is a liveness cost, never a solvency one: strikes and observations wait, but claim, redeem, refund, withdrawUnwound, Desk withdrawals, sNOTE matured withdrawals, veNOTE withdraw and gauge withdraw/getReward keep working. The guardian can pause indefinitely; only the Timelock can unpause, which means an unresponsive guardian plus a 48-hour proposal is the worst case for resuming activity.

The app’s Governance section lists proposals from both governors with their state, timeline and vote bars, and shows every pending operation on both timelocks with its execute time. Pending operations that renounce ownership of a module or hand a role to an unknown address should be treated as red flags; the contract documentation records renounceOwnership as forbidden by process because a scheduled renounce would make unpause impossible.