| Field | Description |
|---|---|
| zip | ZIP:-XXXX (to be assigned by sponsoring Pillar) |
| title | Pillar-Governed Embedded Governance Contract |
| author | 0x3639 |
| status | Draft |
| type | Soft Fork (Spork-gated) |
| acceptance | Complete — Governance spork created on-chain on 2025-05-01 at momentum 10,231,257. |
| activation | Path A: legacy spork-signer activates the spork. Path B fallback: >2/3 of active Pillars upgrade to a release that enforces activation at a fixed height (TBD). |
| created | 2026-05-31 |
| license | GNU General Public License v3.0 |
| link | TBD — upstream PR to zenon-network/go-zenon |
| branch | GitHub - 0x3639/go-zenon at codex/governance-ratchet · GitHub (81c2474) |
| original | sumoshi21, governance v1 by sumoshi21 · Pull Request #47 · zenon-network/go-zenon · GitHub |
Abstract
This ZIP adds GovernanceContract, an embedded contract that lets active Pillars propose, vote on, and execute actions that create/activate Sporks or call administrative methods on Spork, Bridge, and Liquidity. Activation is gated by GovernanceSpork, created on-chain on 2025-05-01 at momentum 10,231,257. The contract becomes a parallel authorized caller beside the current legacy administrators, which remain active during transition. This ZIP is authored by 0x3639 and improves on sumoshi21’s go-zenon PR #47 with a low-turnout ratchet, Type-1 deliberation window, clarified execution semantics, and additional tests. Reference branch: 0x3639/go-zenon@codex/governance-ratchet, commit 81c2474.
Motivation
Several embedded contracts still rely on privileged hardcoded administrator paths:
- The Spork contract currently restricts
CreateSporkandActivateSporkto the legacy spork-signer address, with a separate time-boundedCommunitySporkAddresspath already present in code. - The Bridge contract restricts ~14 sensitive methods (
SetNetwork,Halt,ChangeTssECDSAPubKey,ChangeAdministrator,NominateGuardians, …) to a single administrator. - The Liquidity contract restricts
Fund,BurnZnn,SetTokenTuple,SetIsHalted, and similar methods to either the spork address or a single administrator.
This model conflicts with progressive decentralization and can block community-driven protocol work. This ZIP introduces a Pillar-governed contract authorized to call these privileged methods. Any address may propose an action, Pillars vote, and any address may execute if the vote passes. Existing administrators remain as parallel authorities during transition.
Specification
1. New Embedded Contract
A new embedded contract is added at address:
z1qxemdeddedxg0vernancexxxxxxxxxxxklyh23 // GovernanceContract
The address is added to common/types.EmbeddedContracts and is callable only after the activation of GovernanceSpork.
2. Activation Spork
A new spork has been created on the Network of Momentum:
Spork name: Governance Contract
Spork ID: 3dce423d7bc10d2409e598e33811c9c9e79eff178f94adc571aeef30e8bc9b1d
Create tx hash: 3dce423d7bc10d2409e598e33811c9c9e79eff178f94adc571aeef30e8bc9b1d
Created by: z1qqvwzz2xq7q5gwk6uhcddgrpxlfcyzc8rsu82s (authorized spork-signer)
Sent to: z1qxemdeddedxsp0rkxxxxxxxxxxxxxxxx956u48 (Spork contract)
Momentum height: 10,231,257
Timestamp: 2025-05-01 13:12:50 UTC
Status: Created — pending activation
This is the canonical GovernanceSpork referenced throughout this ZIP. PR #47 used a placeholder ID (ddd43466…, marked // todo change it in source); the ratchet branch already replaces it with 3dce423d7bc10d2409e598e33811c9c9e79eff178f94adc571aeef30e8bc9b1d in common/types/spork.go.
Prior to GovernanceSpork enforcement, GovernanceContract does not appear in the embedded-contracts map and any call to it is rejected. After enforcement, GetEmbeddedMethod resolves to the governance-aware map, which includes all post-HTLC embedded contracts plus GovernanceContract.
3. Contract ABI
function ProposeAction(
string name,
string description,
string url,
address destination,
string data
)
function ExecuteAction(hash id)
function VoteByName(hash id, string name, uint8 vote)
function VoteByProdAddress(hash id, uint8 vote)
variable action(
address owner,
string name,
string description,
string url,
address destination,
string data,
int64 creationTimestamp,
uint8 type,
uint8 round,
hash currentVoteId,
int64 roundStartTimestamp,
uint8 status,
bool executed
)
vote = 0 indicates Yes, vote = 1 indicates No (consistent with the existing definition.VoteYes / definition.VoteNo constants used by the Accelerator-Z and Pillar contracts).
4. Action Types, Rounds, and Thresholds
Two action types are defined, distinguished automatically by the proposal’s destination:
| Type | Destination | Initial active-Pillar decision threshold | Initial directional-majority threshold | Initial voting period |
|---|---|---|---|---|
| 1 | SporkContract |
>66% of active Pillars | >50% Yes or No among Yes+No votes | 45 * PhaseTimeUnit |
| 2 | Any other contract | >50% of active Pillars | >50% Yes or No among Yes+No votes | 30 * PhaseTimeUnit |
If the initial round ends without enough active-Pillar votes to approve or reject the action, the action enters a bounded low-turnout ratchet. Each ratchet round lowers the number of active Pillars required to make a decision, but raises the percentage of directional votes (Yes + No, excluding abstentions) required for either side to win. This preserves the high initial hurdle while allowing governance to progress when turnout is low but the participating Pillars are strongly aligned.
| Type | Round | Active-Pillar threshold | Directional threshold | Voting period |
|---|---|---|---|---|
| 1 | 0 | >66% | >50% | 45 days |
| 1 | 1 | >55% | >55% | 21 days |
| 1 | 2 | >45% | >60% | 21 days |
| 1 | 3 | >40% floor | >66% | 21 days |
| 2 | 0 | >50% | >50% | 30 days |
| 2 | 1 | >40% | >55% | 14 days |
| 2 | 2 | >33% | >60% | 14 days |
| 2 | 3 | >25% floor | >66% | 14 days |
A round approves when:
(yes + no) * 100 > activePillars * activePillarThreshold
yes * 100 > (yes + no) * directionalThreshold
A round rejects when:
(yes + no) * 100 > activePillars * activePillarThreshold
no * 100 > (yes + no) * directionalThreshold
Abstentions count toward VoteBreakdown.Total for transparency, but they do not count toward the active-Pillar threshold, do not count as directional votes, and do not help either side cross the directional threshold. For decision purposes, abstaining is therefore equivalent to not voting; its value is audit visibility. If a round ends without approval or rejection, votes for that round are closed and a new round starts with a fresh vote id. Old votes remain queryable for audit history, but they do not carry into the next round.
After the final round, if neither side satisfies both thresholds, the action expires as NoDecision and cannot be executed. Proposers cannot specify the type directly; it is derived from destination at propose time. destination == TokenContract is explicitly forbidden.
5. Proposal Lifecycle
Propose. Any address may call ProposeAction on GovernanceContract, sending ProjectCreationAmount of ZnnTokenStandard (the same anti-spam fee used by Accelerator-Z projects). The contract:
- Validates
Namelength is within[1, ProjectNameLengthMax],Descriptionwithin[1, ProjectDescriptionLengthMax],Urlmatches the existing project-URL regex,Datais valid base64,Destination != TokenContract, and the decoded action targets an allowed governance method. - Assigns
Id = sendBlock.Hash, setsOwner = sendBlock.Address, recordsCreationTimestampfrom the frontier momentum, setsTypeper §4, initializesRound = 0,CurrentVoteId = Id,RoundStartTimestamp = CreationTimestamp,Status = Voting, and writes anActionVariableto contract storage under key0x00 || id. - Writes a
VotableHash{Id: CurrentVoteId}so the existing pillar-voting machinery (VoteByName,VoteByProdAddress) can be reused for the current round.
Vote. Each active Pillar MAY cast one vote per action round via VoteByName (pillar name + producing-address signature) or VoteByProdAddress. The id argument to the vote method MUST be the action’s CurrentVoteId; after round 0, this is no longer the same value as the action’s Id. Votes accumulate against CurrentVoteId in the shared VoteBreakdown storage. Re-votes within the same round follow the existing pillar-voting semantics. When a new round starts, CurrentVoteId changes and prior-round votes stop counting toward the active decision.
Finalize / Execute. Any address MAY call ExecuteAction(id). The contract:
- Returns
ErrDataNonExistentif the action does not exist. If the action is already executed or has a terminal status (Approved,Rejected, orNoDecision), it returns silently (nil, nil). - Loads the current round schedule for the action type, the current
VoteBreakdownforCurrentVoteId, and the current count of active Pillars fromMomentumStore.GetActivePillars(). - Computes
directionalVotes = breakdown.Yes + breakdown.No. Abstentions are recorded inbreakdown.Totalfor transparency but are excluded fromdirectionalVotesand from the active-Pillar threshold calculation. - For Type-1 actions, approval or rejection is not finalized until the current round voting period has elapsed. This creates a minimum deliberation window for spork-targeted actions: a threshold-crossing vote can still be changed before the round closes. Type-2 actions may finalize as soon as the thresholds are crossed.
- If the action is finalizable,
directionalVotes * 100is greater thanactivePillars * activePillarThreshold, andbreakdown.Yes * 100is greater thandirectionalVotes * directionalThreshold, the action is approved: the contract setsStatus = Approved, setsExecuted = true, base64-decodesData, and emits a contract-send block fromGovernanceContracttoDestinationcarrying the decoded payload,Amount = 0,TokenStandard = ZNN. - If the action is finalizable,
directionalVotes * 100is greater thanactivePillars * activePillarThreshold, andbreakdown.No * 100is greater thandirectionalVotes * directionalThreshold, the action is rejected: the contract setsStatus = Rejected, closes the currentVotableHash, and emits no contract-send block. - If the current round is still inside its voting period and no finalizable decision exists, the call returns without effect.
- If the current round has elapsed without approval or rejection and another ratchet round exists, the contract closes the current
VotableHash, incrementsRound, derives a freshCurrentVoteIdfrom(action id, round), resetsRoundStartTimestampto the current frontier momentum timestamp, writesVotableHash{Id: CurrentVoteId}, and returns without executing the action. - If the final ratchet round has elapsed without approval or rejection, the contract sets
Status = NoDecision, closes the currentVotableHash, and emits no contract-send block.
6. Authorization Changes to Existing Contracts
The reference implementation extends, but does not replace, the existing administrator checks. In every case below, sendBlock.Address == X.Administrator is changed to (sendBlock.Address == X.Administrator) || (sendBlock.Address == GovernanceContract).
- Spork contract:
CreateSpork,ActivateSpork. - Bridge contract:
SetNetwork,RemoveNetwork,SetNetworkMetadata,SetTokenPair,RemoveTokenPair,Halt,Unhalt,Emergency,ChangeTssECDSAPubKey,ChangeAdministrator,SetAllowKeygen,SetOrchestratorInfo,SetBridgeMetadata,RevokeUnwrapRequest,NominateGuardians. - Liquidity contract:
Fund,BurnZnn,SetTokenTuple,SetIsHalted,UnlockLiquidityStakeEntries,SetAdditionalReward,ChangeAdministratorLiquidity,NominateGuardiansLiquidity,EmergencyLiquidity.
Destination == TokenContract is rejected at propose time, so the Token contract administrator remains unaffected by this ZIP.
7. Plasma and Fees
ProposeActionis priced atEmbeddedWDoubleWithdrawplasma.ExecuteActionis priced atEmbeddedSimpleplasma.- Vote calls inherit pricing from the existing Pillar-voting infrastructure.
- The
ProjectCreationAmountZNN fee onProposeActionis held by the contract (consistent with Accelerator-Z behaviour).
8. RPC
A new public namespace embedded.governance is registered with two methods:
GetActionById(id hash) -> ActionGetAllActions(pageIndex uint32, pageSize uint32) -> ActionList
Where ActionList is:
Count int
List []Action
And Action wraps ActionVariable with computed round, threshold, and vote fields:
Expired bool // Current round has elapsed
Round uint8 // Current ratchet round
Status uint8 // Voting, Approved, Rejected, NoDecision
CurrentVoteId hash // Vote id used by the active round
ActivePillarThreshold uint32 // Current active-Pillar threshold
DirectionalThreshold uint32 // Current Yes-or-No threshold among directional votes
VotingPeriod int64 // Current round voting period
Votes *definition.VoteBreakdown // {currentVoteId, total, yes, no}
A new method IsGovernanceSporkEnforced() bool is added to the AccountVmContext interface.
9. Constants Introduced
Type1Action = uint8(1)
Type2Action = uint8(2)
ActionStatusVoting = uint8(0)
ActionStatusApproved = uint8(1)
ActionStatusRejected = uint8(2)
ActionStatusNoDecision = uint8(3)
Type1ActionActivePillarThresholds = []uint32{66, 55, 45, 40}
Type1ActionDirectionalThresholds = []uint32{50, 55, 60, 66}
Type1ActionVotingPeriods = []int64{45 * PhaseTimeUnit, 21 * PhaseTimeUnit, 21 * PhaseTimeUnit, 21 * PhaseTimeUnit}
Type2ActionActivePillarThresholds = []uint32{50, 40, 33, 25}
Type2ActionDirectionalThresholds = []uint32{50, 55, 60, 66}
Type2ActionVotingPeriods = []int64{30 * PhaseTimeUnit, 14 * PhaseTimeUnit, 14 * PhaseTimeUnit, 14 * PhaseTimeUnit}
GovernanceActionDataMaxLength = MaxDataLength
ErrUnkownActionType = errors.New("unknown action type")
ErrInvalidActionRound = errors.New("invalid action round")
10. Fork Classification
This ZIP is a Spork-gated soft fork in the operational sense used for prior NoM embedded-contract additions. On the ratchet branch, the canonical GovernanceSpork ID is in ImplementedSporksMap. Before enforcement, GovernanceContract is absent from the embedded-contract map and calls to it are rejected. After enforcement, non-upgraded Pillars cannot validate governance transactions and will stop following the canonical chain, as with prior spork-gated upgrades. If the community classifies this differently, Sponsors may redraft as a Hard Fork with higher acceptance/activation thresholds.
11. Activation Paths
Path A — Standard Spork Activation (preferred)
- Spork creation — COMPLETE. The authorized spork-signer created
GovernanceSpork(3dce423d…9b1d) at momentum 10,231,257 on 2025-05-01. - The ratchet implementation, or an equivalent upstream PR, is merged and released.
- Pillars upgrade.
- Once a supermajority is upgraded, the authorized spork-signer submits
ActivateSpork(3dce423d…9b1d). - From the enforcement height,
GovernanceContractis callable and the §6 administrator checks take effect.
Path B — Pillar-Coordinated Hard Fork (fallback)
If the spork-signer does not activate within a community-agreed timeout, Sponsors may publish a follow-up ZIP that hard-codes enforcement of GovernanceSpork at a specified momentum height. Path B should proceed only if at least 2/3 of active Pillars are running the new release before that height; otherwise the height should be delayed by another release. Path B is explicitly a fallback after good-faith attempts at Path A.
Rationale
Why an embedded contract? It reuses NoM’s existing pillar-voting machinery and remains auditable on-chain. A multisig would reintroduce off-chain coordination and a small trusted set.
Why 66% / 50% initial thresholds? Spork actions alter protocol rules and start at 66%. Bridge/Liquidity operations start at 50% to remain operationally responsive.
Why a Type-1 deliberation window? Type-1 actions can create or activate sporks, so crossing the threshold early should not immediately execute. Type-2 actions may execute immediately because many are operational responses.
Why are Bridge/Liquidity admin rotations Type-2? Type is destination-based: Spork is Type-1; Bridge/Liquidity are Type-2. This keeps emergency and operations flows responsive. A follow-up ZIP may introduce method-level Type-1 classification for specific admin/guardian rotations.
Why the ratchet? Low turnout should not permanently block governance, but later rounds must require stronger agreement. Therefore active-Pillar thresholds fall while directional thresholds rise.
Other design choices. ExecuteAction is permissionless so execution cannot be censored; existing administrators remain during transition; TokenContract is forbidden because token issuance needs a separate higher-bar ZIP; the 1 ZNN proposal fee mirrors Accelerator-Z; abstentions are visible but do not count toward decision thresholds. Rejected alternatives include security councils, proposer-must-be-Pillar rules, proposer-specified types, unbounded threshold decay, vote carryover across rounds, and deleting executed/expired action history.
Backward Compatibility
Under Path A (Standard Spork Activation): identical to every prior spork-gated upgrade on NoM. Before GovernanceSpork enforcement, governance transactions are universally rejected. After enforcement, upgraded Pillars validate them and non-upgraded Pillars stop following the canonical chain — the same property that has held for the Accelerator-Z, Bridge, Liquidity, and HTLC sporks. The high Pillar upgrade rate targeted before signer activation is the mitigation.
Under Path B (Pillar-Coordinated Hard Fork fallback): a modified go-zenon release hard-codes enforcement of the existing GovernanceSpork (ID 3dce423d…9b1d) at a specified momentum height. The on-chain spork object is unchanged — what changes is that enforcement no longer requires the legacy signer’s ActivateSpork transaction. Pillars still running the legacy release at the activation height would be forked out, hence the >2/3 Pillar binary-upgrade prerequisite before the height is reached.
In both paths: address state, plasma rules, token issuance, momentum/account-block formats, and the Accelerator-Z, Pillar, Sentinel, Stake, HTLC, and Token contracts are unchanged. The Spork, Bridge, and Liquidity contracts gain a parallel authorized caller; their existing administrator continues to work.
Reference Implementation
Original implementation: zenon-network/go-zenon#47 — governance v1 by sumoshi21.
This ZIP’s proposed implementation: 0x3639/go-zenon@codex/governance-ratchet at commit 81c2474, improving on PR #47 with the governance ratchet, Type-1 deliberation window, clarified execution semantics, and additional tests.
Notable files:
vm/embedded/definition/governance.go— ABI,ActionVariable, storage helpers.vm/embedded/implementation/governance.go—ProposeAction,ExecuteAction,checkActionVotes.rpc/api/embedded/governance.go— RPC namespace.vm/embedded/tests/governance_test.go— end-to-end propose → vote → execute, ratchet rollover, and Bridge time-challenge lifecycle tests.common/types/{address,spork}.go— contract address and spork registration.vm/vm_context/{interfaces,spork}.go—IsGovernanceSporkEnforced().vm/embedded/embedded.go— governance-aware contract map.vm/constants/{embedded,errors}.go— thresholds, periods, and error.
Required pre-merge changes
- The canonical
GovernanceSporkID is already applied on the ratchet branch:3dce423d7bc10d2409e598e33811c9c9e79eff178f94adc571aeef30e8bc9b1d(created 2025-05-01, momentum 10,231,257). Reviewers SHOULD verify it remains unchanged before merge. - Open the ratchet branch as a new PR against upstream and confirm it remains mergeable after upstream review.
- Independent review of the augmented administrator checks on every Bridge and Liquidity method to confirm no method was missed.
Security Considerations
- Spork-ID grinding. Because
Typeis determined by destination at propose time, an adversary cannot bypass the Type-1 threshold schedule by routing through an indirect destination — there is no proxy contract able to forward to the Spork contract under the current authorization set. - Late-round minority capture. Lower active-Pillar thresholds improve liveness but can concentrate decision power. This ZIP mitigates that risk with explicit threshold floors, rising directional thresholds, fresh vote ids per round, and a terminal
NoDecisionstate instead of unbounded threshold decay. - Replay across forks.
Id = sendBlock.Hashbinds the action to a specific momentum chain; cross-fork replay is not possible without identical send-block content. - Idle execution.
ExecuteActionis permissionless, so a stalled execution cannot block subsequent governance; any pillar or community member may trigger it. - Storage growth. Action records are not garbage-collected. At 1 ZNN per proposal and current expected throughput this is not a concern, but a follow-up ZIP MAY introduce pruning of expired-and-unexecuted actions.
- Augmented administrator check. Until the legacy administrator addresses are revoked by a follow-up governance action, the security model is the weaker of {legacy admin key, governance contract}. This is intentional during transition and SHOULD be tightened in a follow-up ZIP once governance is operating reliably.
- Non-atomic target execution. Governance approval and target-contract execution are not atomic.
ExecuteActionmarks the governance actionApprovedandExecutedbefore the emitted contract-send is processed by the destination contract. If the destination later rejects because its state changed, a precondition is missing, or the method only starts a delayed workflow, the governance action remains terminal and cannot be retried. Operators SHOULD treat approval as authorization to send the target call, not proof that the target-side effect completed. - Time-challenged Bridge/Liquidity actions. Some Bridge and Liquidity administrator methods use existing
TimeChallengedelays. For those methods, governance requires a two-action lifecycle: the first approved action starts the time challenge, and a second approved action with identical calldata after the delay completes the target change. During the transition period, the legacy administrator andGovernanceContractshare the same method-name-keyed challenge slot, so either authority can reset or interfere with the other’s pending challenge by submitting different calldata for the same challenged method.
Copyright
This ZIP is licensed under the GNU General Public License v3.0.