Skip to main content

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以获取投票权,之后利用投票权进行投票。完整的交互流程分为以下两大步。

  1. 在内置合约Staking中锁定CFX,以获取投票权(votePower
    1. 首先质押 CFX(deposit
    2. 之后为投票锁定 CFX(voteLock
  2. 与新引入的内置合约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表示“不变”“增加”“减少”选项的投票数,调整方式如下:

mylatex20220715_182830.png

这一公式意味着,在每轮结束时:

  • 当“增加”的投票数与“减少”的投票数相等时,链参数将不变。
  • 每轮链参数的调整范围为 [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代码)

如前所述,交互流程分为以下两步:

  1. 在内置合约Staking中锁定CFX,以获取投票权(votePower
    1. 首先质押 CFX(deposit
    2. 之后为投票锁定 CFX(voteLock
  2. 与新引入的内置合约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()