-
Notifications
You must be signed in to change notification settings - Fork 521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RFC-191] On-chain governance #1519
Conversation
96b15d6
to
f7d8f1a
Compare
1e53432
to
712d622
Compare
26dc4d7
to
36e3b7c
Compare
b24e383
to
c3b2eca
Compare
c757a13
to
39e9266
Compare
We should strongly consider adding |
Added to the backlog and we are going to revisit it in the future. |
c6da696
to
219f557
Compare
7a8b5e0
to
4a92ab0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM aside from minor comments
* Add governorAdmin flag * Comments fix
* Add proposal-quorum flag * Comments fix
…1735) * Move distribute rewards to first block of epoch * Modification of reward distribution as part of governance fork * Small change * Comments fix
* Add contracts * Fix integration test
…proposals (#1749) * GovernanceManager * PostEpoch * PostBlock and update of client config * Lint fix * UTs * Comments fix * Retry in PostEpoch as well * Comments fix
* Governance e2e tests * Comments fix
* Add forks activation based on ForkParams contract * Comments fix
1a1b624
to
3c9262c
Compare
Description
This is the PR that introduces on-chain governance to Edge as a hard fork called
governance
.By default, when starting a new supernet,
governance
will be active from the first block.Tagging @DannyS03 so that he can update the docs based on described changes.
New contracts
There are 4 new contracts that need to be deployed on a supernet:
ChildGovernor
- main governance contract that implements all the main functions for proposals, likepropose
,execute
,cancel
, etc. It is an implementation ofopenzeppelin
Governor
contract, so it isBravo
compatible, and can be used withTally
(a UI for managing governance proposals on a network).ChildTimelock
- a contract that handles who can propose, execute and vote on a proposal.NetworkParams
- a contract that holds all the network configuration parameters of a supernet, that can be governed.ForkParams
- a contract that holds info about proposed hard forks and from which block are they proposed to become active.Changes in
genesis
commandThis PR introduces new flags in
genesis
command, and those are:checkpoint-interval
-uint64
flag that represents number of blocks after which a new checkpoint is submitted (default is 900 blocks).withdrawal-wait-period
-uint64
flag that represents number of epochs after which withdrawal can be done from child chain (default is 1 epoch).vote-delay
-string
flag that represents number of blocks after proposal is submitted before voting starts (can be represented by a hex or decimal value, or with big int, default is 10 blocks).vote-period
-string
flag that represents number of blocks that the voting period for a proposal lasts (can be represented by a hex or decimal value, or with big int, default is 10000 blocks).vote-proposal-threshold
-string
flag that represents number of vote tokens (in wei) required in order for a voter to submit a proposal (can be represented by a hex or decimal value, or with big int, default is 1000 wei).governor-admin
-string
flag that represents an address of a governance admin (governance admin can add new or remove old proposers of governance proposals, and add new and remove old executors of accepted proposals. Default is zero address, and that will mean no new addresses can be enabled to propose and execute governance proposals).proposal-quorum
-uint64
flag that represents percentage of total supply of vote token needed for a governance proposal to be accepted (from 0 to 100%, default is 67%).Changes in
genesis.json
Because of the new flags, mentioned in above text,
genesis.json
is now expanded to hold new data:params
forks
section, there will be a new fork added if the chain is starting a new calledgovernance
:polybft
section, two new json parts are added:polybft
section, a new json child is added calledgovernanceConfig
, that holds all the data in regards to on-chain governance setup:governanceConfig
ingenesis.json
governanceConfig
is a new part ofpolybft
configuration ingenesis.json
file, and holds these values:votingDelay
- is the voting delay setup ingenesis
command, as explained in previous sections.votingPeriod
- is the voting period setup ingenesis
command, as explained in previous sections.proposalThreshold
- is the proposal threshold setup ingenesis
command, as explained in previous sections.governorAdmin
- is the address of governance admin setup ingenesis
command, as explained in previous sections.proposalQuorumPercentage
- is the total voting power percentage needed to accept a proposal, setup ingenesis
command, as explained in previous sections.childGovernorAddr
- address ofChildGovernor
contract on supernet.childTimelockAddr
- address ofChildTimelock
contract on supernet.networkParamsAddr
- address ofNetworkParams
contract on supernet.forkParamsAddr
- address ofForkParams
contract on supernet.Governance on a new chain
By default, when starting a new supernet, the
governance
fork is active from genesis, and itsgovernanceConfig
will be populated ongenesis
command. Also, the new contracts will be automatically deployed.Governance on an already running chain
To enable governance on a supernet that was already running on an older version, these are the steps that you need to follow:
ValidatorSet
andRewardPool
contracts (because they are modified in regards to on-chain governance).genesis.json
to havecheckpointInterval
andwithdrawalWaitPeriod
with values that you provided when initializingNetworkParams
contract.genesis.json
to havegovernanceConfig
, and provide addresses of contracts from step 1, provide address of governance admin you specified when you deployedChildTimelock
contract, provide configuration values you specified when you deployedNetworkParams
contract.genesis.json
forks
section to havegovernance
and specify some future block from which governance becomes active.governance
fork.Network configuration parameters supported in governance
These are the client configuration parameters that can be governed in the on-chain governance:
uint256 checkpointBlockInterval
- in blocksuint256 epochSize
- in blocksuint256 epochReward
- in weiuint256 sprintSize
- in blocksuint256 minValidatorSetSize
- in number of validatorsuint256 maxValidatorSetSize
- in number of validatorsuint256 withdrawalWaitPeriod
- in blocksuint256 blockTime
- in secondsuint256 blockTimeDrift
- in secondsuint256 votingDelay
- in blocksuint256 votingPeriod
- in blocksuint256 proposalThreshold
- in percentRequirements when deploying governance contracts
When deploying governance contracts you are required to specify some things.
NetworkParams
contract - specify initial network configuration parameters when initializing the contract. When deploying a new supernet, this is done automatically ongenesis
, since those configuration parameters are read fromgenesis.json
and passed to contract initializer. When deploying the contract manually, you need to do this manually, but make sure those values match the ones ingenesis.json
. You also need to specify the contract owner. In our case, this is theChildTimelock
contract, because it is theChildTimelock
who executes an appropriate function on theNetworkParams
when proposal is executed.ForkParams
contract - specify contract owner. In our case, this is theChildTimelock
contract, because it is theChildTimelock
who executes an appropriate function on theForkParams
when proposal is executed.ChildTimelock
contract - specifyminDelay
value (which represents the delay after which accepted and queued proposal can be executed, and is specified in number of blocks). You also need to specify list of addresses that represent the initial proposers and executors of governance proposals. Please note that you need to add address ofChildGovernor
contract to these lists as well. You can do that after you initialize the contract by calling thegrantRole(bytes32 role, address account)
function onChildTimelock
contract (this can only be done by the governance admin). You also need to specify the address of governance admin (the one who can add and remove proposers and executioners). When deploying a new supernet, all this is done automatically by reading thegovernanceConfig
ingenesis.json
, and in this caseminDelay
is by default 1 block, and can not be changed.ChildGovernor
contract - specify token used for voting. This should be the address ofValidatorSet
contract on supernet, since he acts as a wrapper token around stake of validators. We do not recommend using any other token, because they need to implementERC20VotesUpgradable
interface fromopenzeppelin
to be compatible. You also need to specify the addresses ofNetworkParams
andChildTimelock
contracts, and initialproposalQuorumPercentage
fromgenesis.json
file. When deploying a new supernet, all this is done automatically by reading thegovernanceConfig
ingenesis.json
.Rules of governance on a supernet
By default, when deploying a new supernet with
governance
fork enabled, governance is configured like this:ValidatorSet
contract. It implementsERC20VotesUpgradable
to be compatible withBravo
governance. You can use some other token when you deploy the contracts manually, but we do not recommend this. Voting token provided byEdge
will always be the stake token onL2
(ValidatorSet
contract), and this can not be changed through genesis when deploying a new supernet. When deploying manually, we do not limit the users to use another token, but we do not recommend it.genesis
.governorAdmin
can later add new addresses and remove old ones. Please note that proposers need to have vote tokens in their balance in order to propose a proposal if the proposal threshold is greater than 0. When deploying contracts manually, we do not limit the users to specify other addresses. Once the supernet is started, thegovernorAdmin
can add new proposers and executioners.governorAdmin
is taken from thegenesis
specification and passed to theChildTimelock
contract. Only him can add new and remove old proposers and executioners, as well as admins.Proposal lifecycle for a change of some network configuration parameter
Proposing a new proposal
ChildTimelock
contract.proposalThreshold
is greater than zero. If it is zero, no vote tokens are required to propose a change on governance.function on
ChildGovernor
contract.targets
- are the addresses of contracts on which change will be executed. In this case address ofNetworkParams
contract.values
- values of transactions. In this case, it should be 0.calldatas
- abi encoded call to desired function onNetworkParams
contract. For example if we want to changeepochSize
, this should be abi encoded call tofunction setNewEpochSize(uint256 newEpochSize)
with specified new epoch size.description
- string description of given proposal that other users can see when voting. For example,Changing epoch size from 10 to 100 blocks
.Important note here is to remember, that
targets
,values
andcalldatas
are paired lists, and need to have the same amount of items.voteDelay
number of blocks passes, the voting can start.Voting on a proposal
To vote on a proposal, voters need to have some vote tokens in order to have influence on the proposal acceptance, and the
voteDelay
number of blocks needs to pass, otherwise, the vote will not be counted. There is no limit who can vote on a proposal, but they can only vote once for a given proposal. Voters can even cast a vote if they don't have any tokens, but they will not have any influence on the proposal.Voting is done by calling a function from the
castVote
family of functions onChildGovernor
:Once the vote is submitted, the voting power (weight, or amount of voting tokens that address had when proposal was submitted) of the vote will be added to required proposal and it's vote type. Users can vote:
For
,Against
orAbstained
.If the proposal has quorum of
For
votes (meaning, theproposalQuorum
percentage of total supply of vote token at the time of proposal submition is reached forFor
votes), then, that proposal is accepted after thevotePeriod
ends. Otherwise, it will not be accepted (marked asDefeated
on contract).Queue a proposal
Once the voting period ends, and proposal has quorum of
For
votes, user needs to queue the porposal for execution. This is done by calling thequeue(uint256 proposalId)
function onChildGovernor
contract. There is no limit who can queue a proposal.Execute a proposal
Once the proposal is queued and
minDelay
of blocks provided toChildTimelock
passes, proposal can be executed. This can be done by any address that is in the executors list onChildTimelock
contract, and by calling:function on
ChildGovernor
contract. The user needs to provide the same values as when proposal was submitted, only difference being thekeccak256
hash of proposal description string.Once the proposal is executed, it will be marked on contracts as
Executed
and no other action can be done on that proposal. It will be kept in contract storage.When the proposal is executed, the address in
targets
parameter will be called, by using itscalldata
. In our example,setNewEpochSize
function will be called, which will set the new epoch size onNetworkParams
contract, and the new epoch size will be used by the edge client. More about this in later sections.Canceling a proposal
Canceling a proposal can only be done by the proposer of the proposal, and if proposal is not already executed or canceled. This is done by calling the:
function on
ChildGovernor
contract. Notice that the parameters of function are the same as on execute.Proposal lifecycle for an introduction of some hard fork
The lifecycle of a governance proposal for some hard fork is the same as a proposal of change of some network configuration parameter. Only difference is in setting
targets
andcalldatas
fields when proposing and executing proposals.For example, we want to propose activation of fork
testFork
from block 100k. We would do that by specifying the address ofForkParams
contract intargets
parameter, and abi encoded call ofaddNewFeature(uint256 blockNumber, string calldata feature)
function on that contract (where you specified from which block the feature is active and it's name).When does a change of some network configuration parameter take effect?
Once the proposal for a change of some network configuration parameter is executed, and that parameter gets updated on
NetworkParams
contract, it's change does not happen immediately. To ensure correctness and liveness of our consensus, we can not just change some network configuration in the middle of an epoch. Changing some critical parameters like block time or epoch size in the middle of an epoch can have serious consequences, because consensus relies on these two fields. Because of that, once that proposal is executed and parameter updated onNetworkParams
contract, that event will be caught by the client and saved to it's database. Once the epoch, in which those proposals got executed, finishes, the client will read the new values, and update it's configuration for the next epoch. This ensures correctness of consensus and mitigates possible issues that could arise if these changes are turned on immediately.There are few exceptions to this. Change to next parameters takes effect immediately once their proposals get executed, since they are not used by the consensus engine and protocol, nor Edge client, but only on contracts:
epochReward
,withdrawalWaitPeriod
,votingDelay
,votingPeriod
,proposalThreshold
.To have a more clearer picture, check this diagram out:
When does a change to activate some hard fork take effect?
Proposals to activate/deactivate some hard fork take effect immediately on the block which was specified as the activation block for that feature. So, for example, if we want to activate
london
fork on block 100k, and that proposal is executed on block 90k, the edge client will immediately save the info that that fork will be active from block 100k.How to activate/deactivate a hord fork using on-chain governance?
To activate a fork using on-chain governance users need to:
Send a governance proposal to activate a fork, where proposal
calldata
must be a call toaddNewFeature
function onForkParams
contract. In givencalldata
user must specify fork name and block from which it should be active (fork name in the proposal must match the name of that fork in code:).Proposal must have enough votes, and executed.
After proposal gets executed, fork will be automatically added to edge client forks where it becomes active from specified block.
If a user didn't specify that fork in genesis, that fork will automatically be turned off when chain starts, and it can be activated by later specifying it in the genesis and setting some future block for it (restart of all nodes is required), or by using on-chain governance.
If a user specified a fork in genesis, and that fork gets activated when its block is reached, there is one way to deactivate it, and it's by using on-chain governance. Basically, user will need to create a proposal that changes it's block number to some block that will either never be reached, or it will be reached after a long period of time (they can always change this using ForkParams contract).
To deactivate a fork using on-chain governance users need to:
Send a governance proposal to deactivate a fork, where proposal
calldata
must be a call toaddNewFeature
function onForkParams
contract (if it is not already added to that contract), orupdateFeatureBlock
(if it was already added to that contract). In givencalldata
user must specify fork name and some big block number that will not be reached in a long time (fork name in the proposal must match the name of that fork in code:).Proposal must have enough votes, and executed.
After proposal gets executed, fork will be automatically deactivated on edge client.
Code changes
genesis
.genesis.json
file to hold new configuration.genesis
command with new flags.goernanceManager
onEdge
client which is a handler of governance proposal events. It does these things:consensus_runtime
is changed so that now each epoch has it's own client configuration, provided by the governance_manager. A new field is introduced toepochMetadata
struct (which holds config and data from current epoch), calledCurrentClientConfig
, and it will use that field whenever some parameter from client configuration is needed in some part of code.ValidatorSet
contract to implementERC20VoteUpgradable
. It'stotalSupplyAt
function got changed because of this, and now no longer supports gettingtotalSupply
in the current checkpoint, and this is actively used byRewardPool
to have more precise reward distribution. Because of this the distribution of rewards for previous epoch is moved to the first block of next epoch. This has three benefits:ValidatorSet
as voting token for governance.e2e
tests for successful and unsuccessful governance proposals.Changes include
Checklist
Testing