CIP-94 On-chain DAO Vote for Chain Parameters
Conflux 计划在近期引入CIP-94(On-chain DAO Vote for Chain Parameters)提案。该提案将引入新的内置合约ParamsControl
, 允许DAO在链上投票来调整Conflux的链参数,如PoW的每个区块的出块奖励,或PoS链上的利率。
引入 CIP-94 前,链参数是硬编码在Conflux的代码中的,需要进行 hardfork 才能对链中的关键参数进行修改。引入该更新后,DAO在内置合约
ParamsControl
中投票后链参数就可被在线地更新(无需 hardfork)。
与之前的治理投票类似,用户需要质押并锁定CFX以获取投票权,之后利用投票权进行投票。完整的交互流程分为以下两大步。
- 在内置合约
Staking
中锁定CFX,以获取投票权(votePower
)- 首先质押 CFX(
deposit
) - 之后为投票锁定 CFX(
voteLock
)
- 首先质押 CFX(
-
与新引入的内置合约
ParamsControl
交互,为链参数进行投票(castVote
)。
本文会先介绍内置合约ParamsControl
所引入的投票机制——包括对投票选项、链参数的调整方式、投票的方式等内容,这些也是CIP-94的核心内容。之后会给出上述流程的完整交互代码供参考。
投票机制
持有投票权的用户将可以在ParamsControl
合约中投票调整链参数。下面我们对投票的相关机制进行介绍。
如何获取投票权可以参考锁定与投票权的说明。本文也在之后的代码部分提供了供参考的代码。
投票轮 投票对链参数的影响不是即时的。投票被分为若干轮(round/vote_round
),每轮持续 2 * 60 * 60 * 24 * 60
个 blockNumber(按照Conflux每秒出2个块计算即60天)。用户可以通过ParamsControl
合约的currentRound()
接口获知当前的投票轮。一轮内,每位用户都可以任意地重新分配自己的投票权。每轮的投票结果会在下一轮末生效,而非在当轮结束时立刻生效。
可投票的链参数与投票选项 CIP-94计划支持对链参数 “PoW 区块的挖矿奖励” 与 “PoS 的利率”投票。对于每个参数,有三个投票选项,投票者可以任意地将自己拥有的票数分配给这三个选项。三个投票选项分别意味着:
- 保持当前链参数“不变”
- “增加”链参数(所有投票均投该项将会使链参数变为当前的 2 倍)
- “减少”链参数(所有投票均投该项将会使链参数变为当前的 1/2)
链参数调整公式 在第x
轮结束时,链参数会根据x-1
轮(如前所述,参数调整存在一轮的延迟)的 所有投票 进行调整。我们以 old
表示调整前链参数,new
表示调整后的链参数,n_unchange, n_increase, n_decrease
表示“不变”“增加”“减少”选项的投票数,调整方式如下:
这一公式意味着,在每轮结束时:
- 当“增加”的投票数与“减少”的投票数相等时,链参数将不变。
- 每轮链参数的调整范围为
[1/2 * old, 2 * old]
,最小值与最大值分别对应着所有投票均投“减少”或“增加”。- 投“不变”选项时会导致最终的调整比例向
1
靠拢(即更加趋向于不变)。
投票方式 用户通过与ParamsControl
合约的castVote(uint64 vote_round, Vote[] vote_data)
接口交互来进行投票。vote_round
参数为指定的投票轮。vote_data
为 “投票”(Vote
结构体)数组,其中 Vote
的定义如下:
struct Vote {
// topic_index 代表投票的链参数。 0 代表 PoW 区块的挖矿奖励,1 代表 PoS 的利率
uint16 topic_index;
// votes[0], votes[1], votes[2] 代表分别为“不变”“增加”与“减少”选项分配的票数
// 每个Vote中votes各元素之和应小于等于用户持有的votePower
uint256[3] votes;
}
覆盖已有投票 在同一轮内,用户可以调用ParamsControl
合约的castVote(uint64 vote_round, Vote[] vote_data)
进行多次投票。对于同一参数而言,只有最新的投票会生效。换言之,用户可以通过这种方式覆盖已有的投票。
为了说明的方便,我们以形如
(0, [100, 0, 100])
的方式表示对PoW区块挖矿奖励(topic_index = 0
)的投票, 且分别为“不变”与“减少”分配了100票(votes[0] = 100, votes[2] = 100
)。如用户之前已有的两份投票为(0, [100, 0, 100]), (1, [0, 0, 200])
,用户的新投票为(0, [0, 0, 0])
, 那么用户现有的投票为(0, [0, 0, 0]), (1, [0, 0, 200])
,仅有topic_index
相同的投票被覆盖了。
CIP-105 的补充:当投票参与者较少时,操纵链参数投票的结果的代价会较小。因此CIP-105为链投票设置了额外的生效条件:投票数小于PoS质押数的5%时,投票结果将不会生效。
合约交互方式(附js代码)
如前所述,交互流程分为以下两步:
- 在内置合约
Staking
中锁定CFX,以获取投票权(votePower
)- 首先质押 CFX(
deposit
) - 之后为投票锁定 CFX(
voteLock
)
- 首先质押 CFX(
- 与新引入的内置合约
ParamsControl
交互,为链参数进行投票(castVote
)。
与 Staking
合约交互
与Staking
合约的交互方式没有发生变化。为了方便未曾了解过相关内容的读者,这里我们以代码为例对必要的交互步骤进行介绍,更多的交互细节可以参考之前文章中对锁定与投票权的说明。
// 这段代码用于对Staking合约功能进行说明
const { Conflux } = require('js-conflux-sdk');
async function main() {
// 初始化钱包
const PRIVATE_KEY = '0xxxxxxx'; // 填入私钥
const cfx = new Conflux({
url: 'https://portal-test.confluxrpc.com',
networkId: 1,
logger: console,
});
const account = cfx.wallet.addPrivateKey(PRIVATE_KEY);
const staking_contract = cfx.InternalContract('Staking');
// 质押 deposit(uint amount) 接收的单位为Drip, 这里我们质押 1CFX(即 1e18 Drip)
const stake_amount = BigInt(1e18);
await staking_contract.deposit(stake_amount).sendTransaction({
from: account
}).executed();
console.log("Stake finished")
// 锁定 voteLock(uint amount, uint unlock_block_number) 的单位与质押相同,也为Drip
// 在这里锁定 1CFX
const lock_amount = stake_amount;
// 投票权与锁定的CFX数量、时长有关,锁定时长在 1 年以上时 1 Drip 可获取 1 投票权。
// 低于 1 年时获取的投票权与锁定的季度数相关(注意并不连续)。
// Conflux 按照区块数量计算时间,Conflux网络每秒能生成两个区块。
// 这里我们选择锁定 1.01 年(略多于 1 年),解锁区块对应设置为 当前blockNumber + 1.01年产生的区块数。
const current_block_number = (await cfx.getStatus())['blockNumber']
const unlock_block_number = current_block_number + 2 * 60 * 60 * 24 * 365 * 1.01
await staking_contract.voteLock(lock_amount, unlock_block_number).sendTransaction({
from: account
}).executed();
console.log("Lock finished")
const votePower = await staking_contract.getVotePower(account.address, "")
console.log(`vote power: ${votePower}`) // vote power: 1000000000000000000
}
main()
与 ParamsControl
合约交互
需要说明的是,目前的测试网还未进行CIP-94的更新,以下给出的代码暂时无法正常运行
const { Conflux } = require('js-conflux-sdk');
async function main() {
// 初始化钱包
const PRIVATE_KEY = '0xxxxxxxxx';
const cfx = new Conflux({
url: 'https://portal-test.confluxrpc.com',
networkId: 1,
// logger: console,
});
const account = cfx.wallet.addPrivateKey(PRIVATE_KEY);
const control_contract = cfx.InternalContract('ParamsControl');
// 获取当前的投票轮
const current_round = await control_contract.currentRound()
const base_amount = BigInt(1e17);
// 初始化两份投票
let vote0 = [0, [base_amount, base_amount, base_amount]]
let vote1 = [1, [base_amount, base_amount, 0]]
await control_contract.castVote(current_round, [vote0, vote1]).sendTransaction({
from: account
}).executed();
console.log("Cast vote finished")
// 当前的个人投票
const vote_result = await control_contract.readVote(account.address)
console.log(vote_result)
// 当前的总体投票
const all_votes = await control_contract.totalVotes(current_round)
console.log(all_votes)
}
main()
No Comments