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.
Two governors, two timelocks
Section titled “Two governors, two timelocks”| NoteGovernor | UpgradeGovernor | |
|---|---|---|
| Name | Note Governor | Note Upgrade Governor |
| Decides | Parameters, series policy, module swaps, recovery unwind, gauge registration, emission schedule, treasury actions | upgradeToAndCall on the four UUPS proxies (Treasury, BondDepository, RevenueRouter, Desk) and setUpgradeTimelock |
| Voting delay | 1 day | 1 day |
| Voting period | 5 days | 7 days (floor MIN_VOTING_PERIOD) |
| Proposal threshold | 0.5% of veNOTE supply (proposalThresholdBps = 50) | 0.5% |
| Quorum | max(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 supply | same |
| Quorum counts | for + abstain | for + abstain |
| Timelock | Timelock, 48 h | UpgradeTimelock, 7 days (floor MIN_TIMELOCK_DELAY) |
| End to end (no cancellation) | about 8 days | about 15 days |
| Clock | timestamp, inherited from veNOTE | same |
Both governors share CLOCK_MODE = mode=timestamp and COUNTING_MODE = support=bravo,fractional&quorum=for,abstain¶ms=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.
Lifecycle
Section titled “Lifecycle”ProposalState values. The first duration is NoteGovernor's, the second UpgradeGovernor's. Execution is permissionless once the timelock delay has elapsed.- Propose.
propose(targets, values, calldatas, description). The proposer must hold at least the threshold in veNOTE atclock() − 1. Descriptions start with# <title>and include a## Risksection so the app and indexers can render them consistently. - Pending for
votingDelay(1 day). The vote snapshot is taken at the end of this delay, so locks created afterproposestill count if they exist at the snapshot. The proposer maycancelin this state. - Active for
votingPeriod. Anyone with weight at the snapshot casts a vote. - Succeeded if for-votes exceed against-votes and for + abstain reach quorum; otherwise Defeated.
- Queued by
queue(). The governor schedules the operation on its timelock; the operation id is visible on-chain for the wholeminDelay. The guardian mayTimelockController.cancel(opId), which the governor reports as Canceled. - Executed by anyone calling
execute()after the delay.EXECUTOR_ROLEisaddress(0).
Voting
Section titled “Voting”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 weightsgovernor.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.
Timelocks
Section titled “Timelocks”fact Both are OpenZeppelin TimelockController instances deployed by TimelockDeployer.
| Timelock | UpgradeTimelock | |
|---|---|---|
minDelay | 48 hours | 7 days |
| PROPOSER, CANCELLER | NoteGovernor (and optionally a launch multisig as PROPOSER) | UpgradeGovernor (and optionally a launch multisig as PROPOSER) |
| CANCELLER | Guardian | Guardian |
| EXECUTOR | address(0) (anyone) | address(0) |
DEFAULT_ADMIN_ROLE | renounced by the deployer | renounced by the deployer |
Is owner() of | every Governed module, veNOTE, GaugeController, EmissionSchedule, Minter, gauges, SeriesRegistry | nothing; 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.
Security Council
Section titled “Security Council”The guardian address, expected to be a Security Council multisig, is guardian on every Governed module and holds CANCELLER_ROLE on both timelocks.
| Can | Cannot |
|---|---|
pause() any module (onlyOwnerOrGuardian) | unpause() (owner only) |
cancel any queued operation on either timelock | Propose, queue or execute anything |
NoteCore.cancelUnwind(seriesId) | proposeUnwind, unwind, sweepExcess |
OracleAdapter.revokeForceObserve | Propose 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.
Reading a proposal in the app
Section titled “Reading a proposal in the app”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.