diff --git a/specs/SUMMARY.md b/specs/SUMMARY.md index 64b95e7ed..54f18914d 100644 --- a/specs/SUMMARY.md +++ b/specs/SUMMARY.md @@ -43,6 +43,8 @@ - [Superchain Upgrades](./protocol/superchain-upgrades.md) - [System Config](./protocol/system-config.md) - [Configurability](./protocol/configurability.md) +- [Governance]() + - [Governance Token](./governance/gov-token.md) - [Experimental]() - [Custom Gas Token](./experimental/custom-gas-token.md) - [Alt-DA](./experimental/alt-da.md) @@ -59,4 +61,5 @@ - [SuperchainWETH](./interop/superchain-weth.md) - [Security Council Safe](./experimental/security-council-safe.md) - [OP Stack Manager](./experimental/op-stack-manager.md) + - [Governance Token](./experimental/gov-token.md) - [Glossary](./glossary.md) diff --git a/specs/experimental/gov-token.md b/specs/experimental/gov-token.md new file mode 100644 index 000000000..b5dfbdaef --- /dev/null +++ b/specs/experimental/gov-token.md @@ -0,0 +1,98 @@ +# Governance Token + + + +**Table of Contents** + +- [Overview](#overview) + - [Hook-based Integration with `GovernanceDelegation`](#hook-based-integration-with-governancedelegation) + - [Token Minting](#token-minting) + - [Token Burning](#token-burning) + - [Voting Power](#voting-power) + - [Queries](#queries) + - [Delegation](#delegation) + + + +## Overview + +| Constants | Value | +|----------------|----------------------------------------------| +| Address | `0x4200000000000000000000000000000000000042` | +| Token name | `Optimism` | +| Token symbol | `OP` | +| Token decimals | `18` | + +`GovernanceToken` is an [ERC20](https://eips.ethereum.org/EIPS/eip-20) token contract that inherits from `ERC20Burnable`, +`ERC20Votes`, and `Ownable`. It allows token holders to delegate their voting power to other addresses, enabling a representative +voting system. The contract integrates with the `GovernanceDelegation` contract through a hook-based approach to support +advanced delegation. + +### Hook-based Integration with `GovernanceDelegation` + +Advanced delegation includes relative and absolute partial delegation, which allow delegators to distribute their voting +power among multiple delegates in a fractional manner. The `_afterTokenTransfer` function in the `GovernanceToken` is +modified to call the `afterTokenTransfer` function in the `GovernanceDelegation` contract, allowing the `GovernanceDelegation` +contract to consume the hooks and update its delegation and checkpoint mappings accordingly. + +If the call to the `GovernanceDelegation`'s `afterTokenTransfer` function fails, the token transfer MUST revert. This ensures +that the `GovernanceDelegation` contract remains in sync with the `GovernanceToken`. Otherwise, the `GovernanceToken` could +be left in an inconsistent state relative to the `GovernanceDelegation` contract, such as when a token transfer is successful +but the delegation state is not updated. + +All delegation-related state, including the `delegates`, `checkpoints`, and `numCheckpoints` mappings, is gradually +shifted from the `GovernanceToken` to the `GovernanceDelegation` contract through transactions that call the +`GovernanceDelegation`'s hook (e.g. transfers). In the hook, the `GovernanceDelegation` contract should check if the `to` +and `from` addresses have been migrated. If an address hasn't been migrated, the `GovernanceDelegation` contract should +write the data from the `GovernanceToken`'s mappings for that address to its own state. When reading delegation data in the +`GovernanceDelegation` contract for a delegator that hasn't been migrated, the `GovernanceDelegation` should pull the data +from the `GovernanceToken`'s state. + +For backwards compatibility, the getter methods in the `GovernanceToken` MUST check if the data of a given address has been +migrated or not. If the data has been migrated, the `GovernanceToken` MUST forward the call to the `GovernanceDelegation` +contract. Otherwise, the `GovernanceToken` MUST read from its state. + +The `delegate` and `delegateBySig` functions in the `GovernanceToken` MUST forward the calls to the `GovernanceDelegation` +contract, which implements the required delegation logic. + +### Token Minting + +`GovernanceToken` MUST have a `mint(address,uint256)` function with external visibility that allows the contract owner +to mint an arbitrary number of new tokens to a specific address. This function MUST only be called by the contract +owner, the `MintManager`, as enforced by the `onlyOwner` modifier inherited from the `Ownable` contract. When tokens +are minted, the voting power of the recipient address MUST be updated accordingly in the `GovernanceDelegation` contract +via the `afterTokenTransfer` hook. The total token supply is capped to `2^208 - 1` to prevent overflow risks in the voting +system. If the total supply exceeds this limit, `_mint(address,uint256)`, as inherited from `ERC20Votes`, MUST revert. + +### Token Burning + +The contract MUST allow token holders to burn their own tokens using the inherited `burn(uint256)` or +`burnFrom(address,uint256)` functions inherited from `ERC20Burnable`. When tokens are burned, the total supply and the +holder's voting power MUST be reduced accordingly in the `GovernanceDelegation` contract via the `afterTokenTransfer` hook. + +### Voting Power + +Each token corresponds to one unit of voting power. +By default, token balance does not account for voting power. To have their voting power counted, token holders MUST delegate +their voting power to an address (can be their own address). +The contract MUST offer public accessors for querying voting power, as outlined below. + +#### Queries + +- The `getVotes(address)(uint256)` function MUST retrieve the current voting power of an address from the + `GovernanceDelegation` contract. +- The `getPastVotes(address,uint256)(uint256)` function MUST allow querying the voting power of an address at a specific + block number in the past from the `GovernanceDelegation` contract. +- The `getPastTotalSupply(uint256)(uint256)` function MUST return the total voting power at a specific block number in + the past from the `GovernanceDelegation` contract. + +### Delegation + +Vote power can be delegated either by calling the `delegate(address)` function directly (to delegate as the `msg.sender`) +or by providing a signature to be used with function `delegateBySig(address,uint256,uint256,uint8,bytes32,bytes32)`, +as inherited from `ERC20Votes`. These functions are modified to forward the calls to the `GovernanceDelegation` contract +which implements the required logic. + +The `GovernanceDelegation` contract maintains the necessary invariants, such as preventing circular delegation chains, ensuring +vote weight consistency, and managing checkpoints. It is incorporated as a predeploy of the OP stack to avoid manual deployments +across the Superchain. diff --git a/specs/governance/gov-token.md b/specs/governance/gov-token.md index 709b27c14..70274f40f 100644 --- a/specs/governance/gov-token.md +++ b/specs/governance/gov-token.md @@ -5,93 +5,68 @@ **Table of Contents** - [Overview](#overview) - - [Hook-based Integration with `GovernanceDelegation`](#hook-based-integration-with-governancedelegation) - [Token Minting](#token-minting) - [Token Burning](#token-burning) - [Voting Power](#voting-power) - - [Queries](#queries) + - [Public Query Functions](#public-query-functions) - [Delegation](#delegation) + - [Basic Delegation](#basic-delegation) ## Overview -| Constants | Value | -|--------------|----------------------------------------------| -| Address | `0x4200000000000000000000000000000000000042` | -| Token name | `Optimism` | -| Token symbol | `OP` | +| Constants | Value | +|----------------|----------------------------------------------| +| Address | `0x4200000000000000000000000000000000000042` | +| Token name | `Optimism` | +| Token symbol | `OP` | +| Token decimals | `18` | `GovernanceToken` is an [ERC20](https://eips.ethereum.org/EIPS/eip-20) token contract that inherits from `ERC20Burnable`, `ERC20Votes`, and `Ownable`. It allows token holders to delegate their voting power to other addresses, enabling a representative -voting system. The contract integrates with the `GovernanceDelegation` contract through a hook-based approach to support -advanced delegation. - -### Hook-based Integration with `GovernanceDelegation` - -Advanced delegation includes relative and absolute partial delegation, which allow delegators to distribute their voting -power among multiple delegates in a fractional manner. The `_afterTokenTransfer` function in the `GovernanceToken` is -modified to call the `afterTokenTransfer` function in the `GovernanceDelegation` contract, allowing the `GovernanceDelegation` -contract to consume the hooks and update its delegation and checkpoint mappings accordingly. - -If the call to the `GovernanceDelegation`'s `afterTokenTransfer` function fails, the token transfer MUST revert. This ensures -that the `GovernanceDelegation` contract remains in sync with the `GovernanceToken`. Otherwise, the `GovernanceToken` could -be left in an inconsistent state relative to the `GovernanceDelegation` contract, such as when a token transfer is successful -but the delegation state is not updated. - -All delegation-related state, including the `delegates`, `checkpoints`, and `numCheckpoints` mappings, is gradually -shifted from the `GovernanceToken` to the `GovernanceDelegation` contract through transactions that call the -`GovernanceDelegation`'s hook (e.g. transfers). In the hook, the `GovernanceDelegation` contract should check if the `to` -and `from` addresses have been migrated. If an address hasn't been migrated, the `GovernanceDelegation` contract should -write the data from the `GovernanceToken`'s mappings for that address to its own state. When reading delegation data in the -`GovernanceDelegation` contract for a delegator that hasn't been migrated, the `GovernanceDelegation` should pull the data -from the `GovernanceToken`'s state. - -For backwards compatibility, the getter methods in the `GovernanceToken` MUST check if the data of a given address has been -migrated or not. If the data has been migrated, the `GovernanceToken` MUST forward the call to the `GovernanceDelegation` -contract. Otherwise, the `GovernanceToken` MUST read from its state. - -The `delegate` and `delegateBySig` functions in the `GovernanceToken` MUST forward the calls to the `GovernanceDelegation` -contract, which implements the required delegation logic. +voting system. The `GovernanceToken` currently only supports basic delegation whereby a user can delegate its entire token +balance to a specific address for voting and votes cannot be re-delegated. ### Token Minting -`GovernanceToken` MUST have a `mint(address,uint256)` function with external visibility that allows the contract owner +`GovernanceToken` MUST have a `mint(address,uint256)` function with external visibility that allows the `owner()` address to mint an arbitrary number of new tokens to a specific address. This function MUST only be called by the contract -owner, the `MintManager`, as enforced by the `onlyOwner` modifier inherited from the `Ownable` contract. When tokens -are minted, the voting power of the recipient address MUST be updated accordingly in the `GovernanceDelegation` contract -via the `afterTokenTransfer` hook. The total token supply is capped to `2^208 - 1` to prevent overflow risks in the voting -system. If the total supply exceeds this limit, `_mint(address,uint256)`, as inherited from `ERC20Votes`, MUST revert. +owner, the `MintManager`, as enforced by the `onlyOwner` modifier inherited from the `Ownable` contract. ### Token Burning The contract MUST allow token holders to burn their own tokens using the inherited `burn(uint256)` or -`burnFrom(address,uint256)` functions inherited from `ERC20Burnable`. When tokens are burned, the total supply and the -holder's voting power MUST be reduced accordingly in the `GovernanceDelegation` contract via the `afterTokenTransfer` hook. +`burnFrom(address,uint256)` functions inherited from `ERC20Burnable`. Burn functions MUST NOT allow a user's balance to +underflow and attempts to reduce balance below zero MUST revert. ### Voting Power -Each token corresponds to one unit of voting power. -By default, token balance does not account for voting power. To have their voting power counted, token holders MUST delegate -their voting power to an address (can be their own address). -The contract MUST offer public accessors for querying voting power, as outlined below. - -#### Queries - -- The `getVotes(address)(uint256)` function MUST retrieve the current voting power of an address from the - `GovernanceDelegation` contract. -- The `getPastVotes(address,uint256)(uint256)` function MUST allow querying the voting power of an address at a specific - block number in the past from the `GovernanceDelegation` contract. -- The `getPastTotalSupply(uint256)(uint256)` function MUST return the total voting power at a specific block number in - the past from the `GovernanceDelegation` contract. +Each token corresponds to one unit of voting power. Token holders who wish to utilize their tokens for voting MUST delegate +their voting power to an address (can be their own address). An active delegation MUST NOT prevent a user from transferring +tokens. The `GovernanceToken` contract MUST offer public accessor functions for querying voting power, as outlined below. + +#### Public Query Functions + +- `checkpoints(address,uint32) returns (Checkpoint)` + - MUST retrieve the n-th Checkpoint for a given address. +- `numCheckpoints(address) returns (uint32)` + - MUST retrieve the total number of Checkpoint objects for a given address. +- `delegates(address) returns (address)` + - MUST retrieve the address that an account is delegating to. +- `getVotes() returns (uint256)` + - MUST retrieve the current voting power of an address. +- `getPastVotes(address,uint256) returns (uint256)` + - MUST retrieve the voting power of an address at specific block number in the past. +- `getPastTotalSupply(uint256) returns (uint256)` + - MUST return the total token supply at a specific block number in the past. ### Delegation +#### Basic Delegation + Vote power can be delegated either by calling the `delegate(address)` function directly (to delegate as the `msg.sender`) or by providing a signature to be used with function `delegateBySig(address,uint256,uint256,uint8,bytes32,bytes32)`, -as inherited from `ERC20Votes`. These functions are modified to forward the calls to the `GovernanceDelegation` contract -which implements the required logic. - -The `GovernanceDelegation` contract maintains the necessary invariants, such as preventing circular delegation chains, ensuring -vote weight consistency, and managing checkpoints. It is incorporated as a predeploy of the OP stack to avoid manual deployments -across the Superchain. +as inherited from `ERC20Votes`. Delegation through these functions is considered "basic delegation" as these functions will +delegate the entirety of the user's available balance to the target address. Delegations cannot be re-delegated to another +address.