DRAFT Governance ZIP for Comment

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 CreateSpork and ActivateSpork to the legacy spork-signer address, with a separate time-bounded CommunitySporkAddress path 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:

  1. Validates Name length is within [1, ProjectNameLengthMax], Description within [1, ProjectDescriptionLengthMax], Url matches the existing project-URL regex, Data is valid base64, Destination != TokenContract, and the decoded action targets an allowed governance method.
  2. Assigns Id = sendBlock.Hash, sets Owner = sendBlock.Address, records CreationTimestamp from the frontier momentum, sets Type per §4, initializes Round = 0, CurrentVoteId = Id, RoundStartTimestamp = CreationTimestamp, Status = Voting, and writes an ActionVariable to contract storage under key 0x00 || id.
  3. 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:

  1. Returns ErrDataNonExistent if the action does not exist. If the action is already executed or has a terminal status (Approved, Rejected, or NoDecision), it returns silently (nil, nil).
  2. Loads the current round schedule for the action type, the current VoteBreakdown for CurrentVoteId, and the current count of active Pillars from MomentumStore.GetActivePillars().
  3. Computes directionalVotes = breakdown.Yes + breakdown.No. Abstentions are recorded in breakdown.Total for transparency but are excluded from directionalVotes and from the active-Pillar threshold calculation.
  4. 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.
  5. If the action is finalizable, directionalVotes * 100 is greater than activePillars * activePillarThreshold, and breakdown.Yes * 100 is greater than directionalVotes * directionalThreshold, the action is approved: the contract sets Status = Approved, sets Executed = true, base64-decodes Data, and emits a contract-send block from GovernanceContract to Destination carrying the decoded payload, Amount = 0, TokenStandard = ZNN.
  6. If the action is finalizable, directionalVotes * 100 is greater than activePillars * activePillarThreshold, and breakdown.No * 100 is greater than directionalVotes * directionalThreshold, the action is rejected: the contract sets Status = Rejected, closes the current VotableHash, and emits no contract-send block.
  7. If the current round is still inside its voting period and no finalizable decision exists, the call returns without effect.
  8. If the current round has elapsed without approval or rejection and another ratchet round exists, the contract closes the current VotableHash, increments Round, derives a fresh CurrentVoteId from (action id, round), resets RoundStartTimestamp to the current frontier momentum timestamp, writes VotableHash{Id: CurrentVoteId}, and returns without executing the action.
  9. If the final ratchet round has elapsed without approval or rejection, the contract sets Status = NoDecision, closes the current VotableHash, 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

  • ProposeAction is priced at EmbeddedWDoubleWithdraw plasma.
  • ExecuteAction is priced at EmbeddedSimple plasma.
  • Vote calls inherit pricing from the existing Pillar-voting infrastructure.
  • The ProjectCreationAmount ZNN fee on ProposeAction is 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) -> Action
  • GetAllActions(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)

  1. Spork creation — COMPLETE. The authorized spork-signer created GovernanceSpork (3dce423d…9b1d) at momentum 10,231,257 on 2025-05-01.
  2. The ratchet implementation, or an equivalent upstream PR, is merged and released.
  3. Pillars upgrade.
  4. Once a supermajority is upgraded, the authorized spork-signer submits ActivateSpork(3dce423d…9b1d).
  5. From the enforcement height, GovernanceContract is 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.goProposeAction, 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}.goIsGovernanceSporkEnforced().
  • vm/embedded/embedded.go — governance-aware contract map.
  • vm/constants/{embedded,errors}.go — thresholds, periods, and error.

Required pre-merge changes

  1. The canonical GovernanceSpork ID is already applied on the ratchet branch: 3dce423d7bc10d2409e598e33811c9c9e79eff178f94adc571aeef30e8bc9b1d (created 2025-05-01, momentum 10,231,257). Reviewers SHOULD verify it remains unchanged before merge.
  2. Open the ratchet branch as a new PR against upstream and confirm it remains mergeable after upstream review.
  3. 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 Type is 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 NoDecision state instead of unbounded threshold decay.
  • Replay across forks. Id = sendBlock.Hash binds the action to a specific momentum chain; cross-fork replay is not possible without identical send-block content.
  • Idle execution. ExecuteAction is 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. ExecuteAction marks the governance action Approved and Executed before 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 TimeChallenge delays. 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 and GovernanceContract share 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.