Skip to content

Commit

Permalink
Merge pull request #291 from ethereum-optimism/sc/gov-token-cleanup
Browse files Browse the repository at this point in the history
feat: clean up experimental gov token, add basic gov token
  • Loading branch information
smartcontracts authored Aug 14, 2024
2 parents d7a5773 + cb15648 commit 2b02818
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 62 deletions.
3 changes: 3 additions & 0 deletions specs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
98 changes: 98 additions & 0 deletions specs/experimental/gov-token.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Governance Token

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**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)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## 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.
99 changes: 37 additions & 62 deletions specs/governance/gov-token.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## 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.

0 comments on commit 2b02818

Please sign in to comment.