CIP-94: On-chain DAO Vote for Chain Parameters

cip title author discussions-to status type created
94 On-chain DAO Vote for Chain Parameters Peilun Li (@peilun-conflux) Final Spec Breaking 2022-03-31

Simple Summary

This CIP proposes to use on-chain DAO voting to decide and update reward parameters without hardfork.


Currently, all reward parameters are hardcoded in the code. This CIP proposes to store these parameters as a part of the global state, so we can utilize the existing on-chain voting powers (by staking and locking CFXs through the internal Staking contract) to vote for parameter changes without a hardfork. The vote starts periodically (every 2 months), and only a fixed number of options (to increase the parameter, decrease the parameter, or keep it unchanged) are available. Accounts can cast their vote by sending transactions. The parameter state is updated when a vote period ends.


Traditionally, all chain parameter updates are incompatible changes and have to go through a hardfork. Since hardforks are expected to be infrequent and takes relatively a long time to prepare, we cannot update the parameters in time when the environment changes fast.

Besides, whether to change a parameter and how to change it have often been decided by casting a governance DAO voting. However, it’s still up to the developers to decide whether to follow these decisions and up to the miners to decide whether to apply this hardfork. This makes the voting less mandatory as we want it to be.

By integrating parameter update and the on-chain DAO voting, we can have a regular parameter update mechanism without hardfork.


When the block number BN is executed, a new ParameterControl internal contract will be initialized the and the default parameters for PoW base reward and PoS reward interest will be stored as special storage entries (the PoS reward interest has been kept in the storage before this CIP, and the PoW base reward will be stored under the key “pow_base_reward” in the contract ParameterControl). Besides the currently used parameter value, storage entries to store the summary of the on-going voting and the settled voting are also initialized as CURRENT_VOTES_ENTRIES and SETTLED_VOTES_ENTRIES.

The list of parameters to vote are:

  1. PoW base block reward.
  2. PoS base reward interest rate.

And the options of each parameters are:

  1. Remain unchanged.
  2. Increase by 100%.
  3. Decrease by 50%.

The internal contract ParameterControl has a struct Vote as

struct Vote {
     uint16 index;
     uint256[3] votes;

where index is the parameter index to vote for and the array votes is the number of votes cast to each option (i.e., unchange, increase, and decrease in order).

The contract has two interfaces to cast the vote and to read the vote:

  • function castVote(uint64 version, Vote[] vote_data) external;. The first parameter is a version number to indicate which vote period is the call for. The second parameter is an encoded list of Vote.
  • function readVote(address addr) external view returns (Vote[]). Read the votes data of an account.

An account can distribute his voting power to different options of the same parameter. For any parameter, the total votes for its options should not exceed the account’s current total voting power. And for each call of castVote, a parameter can only be voted at most once. If the function is called for several times within a voting period, for each parameter, only the last successful call takes effect and overrides the previous votes. For example, if an account voted for both parameters in the first transactions TX1 and voted for parameter 2 in the second transaction TX2, its vote for parameter 1 remain the same as the one in TX1 and its vote for parameter 2 is changed to the one in TX2.

At the end of a voting period (counted as block numbers), SETTLED_VOTES_ENTRIES is used to compute the new parameter values. Specifically, if the total votes for each option of a parameter is [n_unchange, n_increase, n_decrease], the new value for this parameter is computed as

new = old * 2 ** ((n_increase - n_decrease) / (n_increase + n_decease + n_unchange))

After the parameters are updated in the storage, we move the current votes CURRENT_VOTES_ENTRIES to SETTLED_VOTES_ENTRIES and reset the values of CURRENT_VOTES_ENTRIES to 0 for the votes in the next period.

An event is also emitted for each successful vote. For each account, the latest votes and the version for these votes will also be stored as storage entries in the contract ParamsControl.

The vote period is set to 2 months and is based on the block number, so every period is 2 * 60 * 60 * 24 * 30 * 2 = 10,368,000 blocks.


The rationale to store the voting results to SETTLED_VOTES_ENTRIES instead of applying it immediately is that, if the result is manipulated unexpectedly, there is still a chance to revert it with a hardfork. And adding a delay allows more parameters (header-validity related, like difficulty) to be voted in the future, because the latest state for a new block may not be available.

We want an account to vote for multiple options for two reasons. One is that the PoS pool contract now can collect its users’ choices and help to cast their votes. Another reason is that an account can combine these options to have a fine-grained option for a parameter. This allows us to make the options change range relatively large without a side effect.

We make later votes override early votes at the parameter level for efficiency reasons, because if an account only votes for one parameter, we only need to read the data for this parameter for validation. If we want every call to override all votes, we’ll need to read all entries to see if they are voted before.

Backwards Compatibility

This is a breaking change because it adds a new internal contract and add new entries in the state.

Test Cases




Security Considerations



Copyright and related rights waived via CC0.