{"id":20002013,"url":"https://github.com/pooltogether/multichain-gov-proposal-spec","last_synced_at":"2025-03-01T23:54:33.749Z","repository":{"id":40277970,"uuid":"480565420","full_name":"pooltogether/multichain-gov-proposal-spec","owner":"pooltogether","description":"Governance Spec","archived":false,"fork":false,"pushed_at":"2022-05-17T16:32:50.000Z","size":582,"stargazers_count":6,"open_issues_count":0,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-01-12T13:14:30.129Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pooltogether.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-04-11T22:00:57.000Z","updated_at":"2024-09-11T06:04:23.000Z","dependencies_parsed_at":"2022-08-09T16:04:41.876Z","dependency_job_id":null,"html_url":"https://github.com/pooltogether/multichain-gov-proposal-spec","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pooltogether%2Fmultichain-gov-proposal-spec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pooltogether%2Fmultichain-gov-proposal-spec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pooltogether%2Fmultichain-gov-proposal-spec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pooltogether%2Fmultichain-gov-proposal-spec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pooltogether","download_url":"https://codeload.github.com/pooltogether/multichain-gov-proposal-spec/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241439791,"owners_count":19963098,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-13T05:19:30.016Z","updated_at":"2025-03-01T23:54:33.719Z","avatar_url":"https://github.com/pooltogether.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"---\neip: \u003cto be assigned\u003e\ntitle: Multi-chain Governance\ndescription: This specification allows token holders to vote and approve on state changes across multiple chains.\nauthor: Brendan Asselstine \u003cbrendan@pooltogether.com\u003e\ndiscussions-to: \u003cURL\u003e\nstatus: Draft\ntype: Standards Track\ncategory (*only required for Standards Track): ERC\ncreated: 2022-05-16\n---\n\n# EIP-?: Multi-Chain Governance\n\n# Abstract\n\nThis specification allows token holders to vote and approve on state changes across multiple chains (Ethereum, L2s, and sidechains). The model is similar to the existing Compound-style governance, but separates proposal execution from consensus:\n\n1. A proposal is created on a governance \"branch\".\n2. Votes are aggregated on each branch and sent to the governance \"root\" for consensus.\n3. If a proposal passes, the root queues the proposal hash on branches that require execution.\n4. Anyone can execute a queued proposal on a branch.\n\n# Motivation\n\nMany protocols need to govern smart contracts across Ethereum, L2s, and sidechains. This specification aims to standardize a multi-chain governance system so that DAOs can leverage common infrastructure.\n\nThe most popular governance system is Compound protocol's GovernorAlpha; this contract has seen numerous derivatives, including OpenZeppelin's excellent Governor contract. These contracts co-locate consensus with execution, however, making them impractical to use across multiple chains.\n\nWe need a governance system that is flexible enough to add new chains and efficient such that cross-chain messages are minimized.\n\n# Specification\n\nThe key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in [RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119).\n\n## Definitions\n\n```solidity\n\nstruct Call {\n    uint256 chainId;\n    address caller;\n    address target;\n    uint256 value;\n    bytes data;\n}\n\n```\n\n## GovernorBranch\n\nThe Governor Branch is a smart contract deployed to each chain that the protocol wishes to execute on. Users on that chain create, vote, and execute on proposals through the Governor Branch. The Governor Branch creates proposals and sends vote aggregates to its Governor Root. If a proposal passes, the Governor Root tells the branch to queue the proposal.  The Govornor Root may exist on another chain, so the branch could be communicating with a proxy.\n\n### Methods\n\n**createProposal**\n\nMUST emit the `BranchCreatedProposal` event when successfully called.\n\nMUST increment the branch nonce for each proposal request. Each GovernorBranch tracks its own branch nonce.\n\nMUST call `createProposal` in the associated `GovernorRoot` contract, whether directly or indirectly (as through a bridge).\n\nRECOMMENDED only be callable by appropriate users (those with sufficient tokens, for example).\n\n```yaml\n- name: createProposal\n  type: function\n  stateMutability: nonpayable\n  inputs: \n    - name: calls\n      type: Call[]\n    - name: message\n      type: bytes\n  outputs:\n    - name: executionHash\n      type: bytes32\n    - name: branchNonce\n      type: uint\n```\n\n**castVote**\n\nAllows a user to vote for a proposal. The proposal info is passed so that the proposal hash can be calculated for verification. The `support` options are: 0 = Against, 1 = For, 2 = Abstain. The users vote will be added to the appropriate support total for that proposal.\n\nMUST NOT allow a user to vote twice for a proposal.\n\nMUST emit the `VoteCast` event.\n\nMUST revert if the current time is greater than the proposal `endTime`.\n\nMUST revert if `support` is not one 0, 1 or 2.\n\nMUST revert if `startEpoch` is the current epoch or in the future.\n\nMUST record the users votes from the Epoch Voter at the `startEpoch` of the proposal.\n\n```yaml\n- name: castVote\n  type: function\n  stateMutability: nonpayable\n  inputs: \n    - name: executionHash\n      type: bytes32\n    - name: rootNonce\n      type: uint256\n    - name: startEpoch\n      type: uint32\n    - name: endTimestamp\n      type: uint64\n    - name: support\n      type: uint8\n```\n\n**queueProposal**\n\nMUST emit a `QueuedProposal` event.\n\nMUST only be callable by an authorized contract, whether the `GovernorRoot` itself, or from a bridge.\n\n```yaml\n- name: queueProposal\n  type: function\n  stateMutability: nonpayable\n  inputs: \n    - name: proposalHash\n      indexed: true\n      type: bytes32\n```\n\n**executeProposal**\n\nExecutes, in order, any applicable calls in the given proposal. Applicable calls may be where the `chainId` matches the `GovernorBranch` chainId and whose `caller` matches the `GovernorBranch` address, or it could be a proxy contract that the branch controls.\n\nMUST emit a `ExecutedProposal` event.\n\nMUST revert if already called\n\nMUST NOT execute a proposal that has not been queued.\n\n```yaml\n- name: executeProposal\n  type: function\n  stateMutability: payable\n  inputs:\n    - name: branchChainId\n      type: uint256\n    - name: branchAddress\n      type: address\n    - name: branchNonce\n      type: uint256\n    - name: calls\n      type: Call[]\n    - name: rootNonce\n      type: uint256\n    - name: startEpoch\n      type: uint32\n    - name: endTimestamp\n      type: uint64\n```\n\n### Events\n\n**BranchCreatedProposal**\n\nMUST be emitted when a GovernorBranch requests a new proposal.\n\n```yaml\n- name: BranchCreatedProposal\n  type: event\n\n  inputs:\n    - name: executionHash\n      indexed: true\n      type: bytes32\n    - name: branchNonce\n      indexed: true\n      type: uint\n    - name: calls\n      type: Call[]\n    - name: message\n      type: bytes\n```\n\n**VoteCast**\n\nMUST be emitted when a user casts their vote.\n\n```yaml\n- name: VoteCast\n  type: event\n  inputs:\n    - name: proposalHash\n      type: bytes32\n    - name: voter\n      type: address\n    - name: support\n      type: uint8\n    - name: votes\n      type: uint256\n```\n\n**QueuedProposal**\n\nMUST be emitted when a GovernorBranch queues a proposal.\n\n```yaml\n- name: QueuedProposal\n  type: event\n\n  inputs:\n    - name: proposalHash\n      type: bytes32\n```\n\n**ExecutedProposal**\n\nMUST be emitted when a GovernorBranch executes a proposal\n\n```yaml\n- name: ExecutedProposal\n  type: event\n  \n  inputs:\n    - name: executionHash\n      type: bytes32\n    - name: proposalHash\n      type: bytes32\n```\n\n## GovernorRoot\n\nThe GovernorRoot contract allows branches to creates proposals and aggregate votes.  When a proposal passes, the Governor Root can queue proposals on branches that require execution.\n\n### Methods\n\n**createProposal**\n\nMUST emit the `ProposalCreated` event.\n\nMUST only be callable by authorized contracts (GovernorBranches, or bridge contracts).\n\nThe `proposalHash` output value is a keccak hash of the ABI-encoded:\n- `executionHash`: Keccak hash of the abi-encoded (`branchChainId`, `branchAddress`, `branchNonce`, `callsHash`)\n- `rootNonce`: incremented every time `createProposal` is called. The nonce is specific to the GovernorRoot.\n- `epoch`: Epoch to measure votes at\n- `endTimestamp`: The time at which to stop counting votes\n\n```yaml\n- name: createProposal\n  type: function\n  stateMutability: nonpayable\n  inputs: \n    - name: executionHash\n      type: bytes32\n  outputs:\n    - name: rootNonce\n      type: uint\n    - name: proposalHash\n      type: bytes32\n    - name: epoch\n      type: uint32\n    - name: endTimestamp\n      type: uint64\n```\n\n**addVotes**\n\nMUST emit the `AddedVotes` event.\n\nMUST only be callable by authorized contracts (GovernorBranches, or bridge contracts).\n\nMUST REVERT if called more than once by msg.sender\n\n```yaml\n- name: addVotes\n  type: function\n  stateMutability: nonpayable\n  inputs:\n    - name: againstVotes\n      type: uint256\n    - name: forVotes\n      type: uint256\n    - name: abstainVotes\n      type: uint256\n    - name: proposalHash\n      type: bytes32\n  outputs:\n    - name: success\n      type: bool\n```\n\n### Events\n\n**ProposalCreated**\n\nMUST be emitted when a GovernorRoot creates a new proposal.\n\n```yaml\n- name: ProposalCreated\n  type: event\n\n  inputs:\n    - name: rootNonce\n      indexed: true\n      type: uint\n    - name: proposalHash\n      indexed: true\n      type: bytes32\n    - name: branchChainId\n      type: uint\n    - name: branchAddress\n      type: address\n    - name: branchNonce\n      type: uint\n    - name: callsHash\n      type: bytes32\n    - name: data\n      type: bytes\n```\n\n**AddedVotes**\n\nMUST be emitted when votes are added\n\n```yaml\n- name: AddedVotes\n  type: event\n\n  inputs:\n    - name: proposalHash\n      indexed: true\n      type: bytes32\n    - name: againstVotes\n      type: uint256\n    - name: forVotes\n      type: uint256\n    - name: abstainVotes\n      type: uint256\n    - name: from\n      type: address\n```\n\n## EpochVoter\n\nThe EpochVoter contract tracks a users voting power. Both Governor Branch and Governor Root use the Epoch Voter, so the Epoch Voter must be present everywhere those contracts are.\n\nVoting power is tracked as the *minimum balance held during an epoch*. An epoch is a large unit of time.  See below for details.\n\nEpoch Voter contracts are synchronized across chains by ensuring their start timestamp and epoch durations match.  The epoch duration must be more than long enough to cover clock differences between chains.\n\n### Methods\n\n**currentEpoch**\n\nMUST return the current epoch\n\n```yaml\n- name: currentEpoch\n  type: function\n  stateMutability: constant\n  outputs:\n    - name: epoch\n      type: uint\n```\n\n**currentVotes**\n\nMUST return the voting power of a user at the current epoch. This power can change until the epoch has passed.\n\n```yaml\n- name: currentVotes\n  type: function\n  stateMutability: constant\n  inputs:\n    - name: account\n      type: address\n  outputs:\n    - name: votes\n      type: uint\n```\n\n**votesAtEpoch**\n\nMUST return the voting power of a user at a particular epoch.\n\nMUST revert if the epoch has not passed.\n\n```yaml\n- name: votesAtEpoch\n  type: function\n  stateMutability: constant\n  inputs:\n    - name: account\n      type: address\n    - name: epoch\n      type: uint\n  outputs:\n    - name: votes\n      type: uint\n```\n\n# Rationale\n\n## Epochs Mitigate Double-Voting\n\nDouble-voting is the risk that a user can vote more than once. In a naive system which measures a users balance at a certain timestamp, it's possible for a user to bridge their tokens and have the same balance for the same timestamp across two chains:\n\n\u003cimg src=\"./assets/Timestamps.png\" alt=\"drawing\" width=\"35%\"/\u003e\n\nThis problem can be mitigated by using *epochs*. Time is evenly divided into epochs. An epoch is long enough to ensure that they overlap across chains, no matter what the minor relative clock differences are.\n\n\u003cimg src=\"./assets/Epochs.png\" alt=\"drawing\" width=\"60%\"/\u003e\n\nBy tracking voting power by the minimum balance for a given epoch, we can ensure that users cannot double-vote.\n\n## Data Lives Off-Chain\n\nOne of the major goals of this specification is to keep *as much data off-chain as possible*. This has two benefits:\n\n1. Less data is written to the chain, making the system cheaper to run.\n2. Minimizes the number of cross-chain messages. Proposal data can supplied by the caller and verified using the proposal hash.\n\n### Minimal Cross-Chain Messages\n\nLet's count the number of messages:\n\n1. Create proposal message from a branch -\u003e root. (1)\n2. Add votes message from each branch -\u003e root. (number of branches)\n3. Queue proposal message from root -\u003e branch only for *branches that require execution*. (1 - number of branches)\n\nWorst case scenario we'll have `1 + 2*(number of branches)` number of cross-chain messages.\n\n\u003cimg src=\"./assets/CrossChainMessages.png\" alt=\"drawing\" width=\"100%\"/\u003e\n\n## Governance is Modular\n\nIt's expected that there will be more chains or L2s in the future. Having a root and branches is a simple topology that makes adding new chains easy.\n\n\u003cimg src=\"./assets/GovTopology.png\" alt=\"drawing\" width=\"100%\"/\u003e\n\n# Backwards Compatibility\n\nThis system is can be easily adapted to support the original Compound Governor Timelock contract, which protocols to swap out the old Governor Alpha for this spec.\n\n# Reference Implementation\n\nThere is a reference implementation [on Github](https://github.com/pooltogether/multichain-gov-contracts). The project uses Foundry.\n\n# Security Considerations\n\nThe single biggest security risk is the cross-chain transport layer. Ethereum L2s such as zk-rollups and optimisic rollups inherit the base layer security, but sidechains such as Polygon do not.\n\n## Worst-Case Scenario: Bridge Compromised\n\nLet's assume one of the bridges has been compromised. What is our exposure?\n\n### GovernorBranch: queueProposal\n\nThe attacker could queue arbitrary code on the Governor Branch on that bridge. The scope of damage will be anything under the branch's control.\n\nMitigations:\n\n- Allow branches to set queuing delays. This will give people time to exit before the proposal takes effect.\n- Add a \"guardian\" to branches that can cancel proposals. However, this could introduce censorship issues unless it's carefully managed.\n\n### GovernorRoot: createProposal.\n\nThe attacker could create proposals without holding tokens.\n\nMitigations:\n\n- Only allow proposal creation from certain bridges.\n\n### GovernorRoot: addVotes\n\nThe attacker could control the branch's entire vote, including inflating the vote counts giving them majority power.\n\nMitigations:\n\n- Limit branch voting power. Voting could be limited by how many tokens are locked in the bridge.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpooltogether%2Fmultichain-gov-proposal-spec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpooltogether%2Fmultichain-gov-proposal-spec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpooltogether%2Fmultichain-gov-proposal-spec/lists"}