区块链中文技术社区

Ethereum POS + POA链系统合约测试

文章作用

本片文章主要是为了满足没有接触过Ethereum合约的同学,熟悉当前系统合约,以及了解Ethereum合约测试的交互流程。
基于文中所对应的系统合约,主要是测试阶段会尽量详细以及给出预计的测试结果做对比。
在相关系统合约未开源之前,不建议提前浏览此文章。

POS + POA 系统合约开源地址

待开源

合约组成

Validators 验证者合约

主要数据结构如下

// 验证者数据状态
enum Status {
    Inactivated,                       // 未激活
    Activated,                         // 已激活
    Disabled                           // 已禁用
}

// 验证者描述信息
struct Description {
    string nickname;                    // 昵称
    string location;                    // 位置
    string website;                     // 网址
    string contact;                     // 联系
    string remarks;                     // 备注
}

// 验证者数据结构
struct Validator {
    address payable rewardReceiveAddress;// 奖励接收地址
    address claimRewardAddress;          // 执行领取奖励地址
    Status status;                       // 节点状态
    uint256 totalStakeCoins;             // 总抵押代币数
    uint256 totalPendingStakeCoins;      // 总待合入的抵押代币数
    Description description;             // 节点描述
    uint256 shareRatioBase;              // 分成比例(1/shareRatioBase)
    uint256 bpPendingClaimCoins;         // 当前节点待领取的奖励
    uint256 issueTotalRewardsCoins;      // 支持者应该分的总奖励
    uint256 issueBalanceRewardsCoins;    // 支持者应该分的剩余奖励
    uint256 totalDisabledCoins;          // 总监禁的代币数(监禁的代币会被分发给其余验证者)
    uint256 bpLastClaimRewardTime;       // 最后领取奖励的时间
    uint256 lastEditValidatorInfoTime;   // 最后一次编辑验证者信息
    address[] stakers;                   // 当前验证者所支持的账户地址
}

// 抵押地址信息
struct StakingInfo {
    uint256 pendingStakeCoins;           // 对该节点待合入的抵押
    uint256 stakeBalanceCoins;           // 抵押的余额
    uint256 lastClaimRewardTime;         // 最后领取奖励的时间
}

// 投票者统一数据
struct UserCoreData {
    address payable rewardReceiveAddress;// 奖励接收地址
    address claimRewardAddress;          // 执行领取奖励地址
    uint256 totalUnstakeBalanceCoins;    // 取消抵押的余额
    uint256 lastUnstakeClaimTime;        // 最后取消抵押时的块高度
}

mapping(address => Validator) validatorInfo;                // 验证者信息
mapping(address => mapping(address => StakingInfo)) staked; // 用户对于各个验证者的抵押数据 (staker => validator => info)
mapping(address => UserCoreData) coreStaked;                // 用户抵押核心数据
address[] public workingValidators;                         // 当前正在工作验证者地址
address[] public topValidators;                             // 当前出块验证者地址(epoch 周期内更新)
address[] public activedValidators;                         // 处于激活状态的验证者地址
uint256 public totalStake;                                  // 全网当前总抵押
uint256 public totalPendingStake;                           // 全网当前待合入的总抵押
uint256 public totalDisabledCoins;                          // 全网历史总罚没

合约对外接口

合约初始化
initialize(address[] calldata vals, address proposalAddr, address punishContractAddr, address configContractAddr, uint256 epoch)

参数

设置用户奖励相关地址
setUserCoreRewardAddress(address payable rewardReceiveAddress, address claimRewardAddress)

参数

抵押投票
stake(address validator, uint256 stakeCoins)

参数

如果当前抵押总量大于转账金额,则消费已取消抵押待领取的代币数。
对于抵押成功后,抵押代币量,会进去待投票数据,等待下一领取周期后,才会进入真正的抵押投票数据

取消投票
unstake(address validator, uint256 unstakeCoins)

参数

赎回时,优先扣除待投票数据
赎回成功后,对应的代币数,将进入待领取(实时)

领取赎回
refund()

领取待赎回的代币,时间间隔默认3day,每次执行unstake都会刷新,已最后一次赎回时间为开始计算时间。

领取出块验证奖励
claimBpReward(address validator)

参数

执行操作的账户地址需要为创建节点信息时执行的claimRewardAddress账户地址
执行期间会根据创建节点时,设置的shareRatioBase分配比例,进行期间的奖励划分,该验证者会直接收到自己应得的奖励,接收奖励的账户地址为创建节点时配置的rewardReceiveAddress奖励接收账户(对于创世验证节点,初始化为该账户地址)。
同时记录支持该节点的抵押用户部分的奖励,等待用户方主动过来领取。(如果领取周期内未及时领取的用户奖励,将会合到下一周期统一继续分分配)

领取投票奖励
claimReward(address voter, address validator)

参数

操作的执行权限地址可以为当前投票账户地址,也可以为setUserCoreRewardAddress设置中的claimRewardAddress代为领取地址,同理奖励接收地址如果未设置,则为当前投票地址。

更新当前工作验证者地址 (链程序调用)
updateWorkingValidators(address[] memory newSet, uint256 epoch)

参数

上缴出块奖励
distributeBlockReward()

验证节点上缴出块奖励,奖励上缴后,根据各正在工作的验证节点所抵押的比例进行分配。

创建出块验证节点信息
function createOrEditValidator(
        address payable rewardReceiveAddress, // 奖励接收地址
        address claimRewardAddress,           // 奖励代为领取地址
        string calldata nickname,             // 昵称
        string calldata location,             // 位置
        string calldata website,              // 网址
        string calldata contact,              // 联系方式
        uint256 shareRatioBase,               // 奖励分配比例(1/N)
        string calldata remarks               // 备注
    )
重新激活节点
tryReactive(address validator)

当节点处于非Active时,置为激活状态

更新头部出块节点
updateTopValidators()

重新计算头部出块节点地址,为下一周期链程序获取做准备

其他数据获取类接口省略

Proposal 提案合约

struct ProposalInfo {
    address proposer;    // 提案发起者
    address dst;         // 提议谁加入
    string details;      // 描述信息
    uint256 createTime;  // 创建时间
    uint16 agree;        // 同意总数
    uint16 reject;       // 拒绝总数
    bool isEnd;          // 是否已结束 
}

struct VoteInfo {
    address voter;        // 投票者
    uint256 voteTime;     // 投票时间
    uint16 votedTimes;    // 已投票次数
    bool isAgree;         // 是否同意
}

合约接口

合约初始化
initialize(address validatorContractAddr, address configContractAddr)

参数

创建提案
createProposal(address val, string calldata details)

参数

通过监听event获取创建完的提案id

取消提案
cancelProposal(bytes32 id)

创建者可取消创建的提案

对提案投票
voteProposal(bytes32 id, bool isAgree)

对于提案,只有正在出块的节点才有资格投票,经过 1/2+1通过,通过后会把提案中的地址添加到验证者列表,状态为Active
默认全局配置,同一个提案,有最多三次的投票修改机会

Punish 惩罚治理合约

稍后补充

PublicBase 通用基类合约

稍后补充

Config 全局配置合约

稍后补充

Ownable 合约权限合约

稍后补充

为模拟测试修改合约

为了测试方便,我们直接使用普通账户模拟出块账户进行合约的调用
需要注释掉下PublicBase合约中的两个判断条件

modifier onlyMiner() {
    //require(msg.sender == block.coinbase, "Miner only");
    _;
}

modifier onlyBlockEpoch(uint256 epoch) {
    //require(block.number % epoch == 0, "Block epoch only");
    _;
}

合约部署步骤

先将各个合约编译好,通过 myetherwallet工具将以下合约部署到链上

各个合约部署完后的地址

合约名地址
Validators0x113D0CD580edB0a70e8De5F92C15145f83211318
Proposal0x00C8D6d9736347f66c069C9285a5b99B8ddEFDC7
Punish0x8049F22032C2F7b7fD0981014953bFe5CE891373
Config0xB3bB2C011277DB3CcB66250617437C42Df69455B

初始化各个系统合约

此处是为了模拟出块验证者测试,生产中当前初始化过程将在链程序打第一个块时做相应的初始化,各个初始化参数配置在genesis.json

"config": {
....
    "congress": {
      "period": 3,
      "epoch": 200,
      "validatorsContractName": "validators",
      "punishContractName": "punish",
      "proposalContractName": "proposal",
      "configContractName": "config",
      "validatorsContractAddr": "0x113D0CD580edB0a70e8De5F92C15145f83211318",
      "punishContractAddr": "0x8049F22032C2F7b7fD0981014953bFe5CE891373",
      "proposalContractAddr": "0x00C8D6d9736347f66c069C9285a5b99B8ddEFDC7",
      "configContractAddr": "0xB3bB2C011277DB3CcB66250617437C42Df69455B"
    }
....
  }

相应的合约bytecode 也会初始化加到genesis.json中的alloc里的各个对应地址。
此篇文章我们只讲模拟测试,更灵活控制测试的步骤。

初始化Validators

对于后面的合约交互统一使用myetherwallet工具

合约测试步骤

稍后补充

备注

文中所涉及的系统合约暂时并没有开源,等后面计划开源后,再做补充

参考

https://github.com/HuobiGroup/huobi-eco-chain
https://github.com/HuobiGroup/huobi-eco-contracts

当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »