library Checkpoints:Transform openzeppelin

[复制链接]
发表于 5 天前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
使用时间戳替代区块号的 Checkpoints 库实现(OpenZeppelin 风格)

在基于区块号的投票、锁仓等场景中,用区块高度作为时间点会遇到分叉回滚、出块不稳定等问题。一种更稳健的做法是使用「时间戳」作为时间点,并配合 uint256 类型的数值来记录数量(quantity)。下面给出一个可复用的 Checkpoints 库实现。

一、Checkpoints 库核心代码

  1. // SPDX-License-Identifier: MIT

  2. pragma solidity ^0.8.0;

  3. library Checkpoints {
  4.         struct data{
  5.         uint32 fromTime;
  6.         uint256 quantity;
  7.         }

  8.     struct Checkpoint {
  9.                 mapping (address => mapping (uint32 => data)) checkpoints;
  10.                 mapping (address => uint32) numCheckpoints;
  11.     }

  12.     // --------------checkpoint-----------
  13.         function _getCurrentQuantity(Checkpoint storage checkpoint,address account) internal view returns (uint256) {
  14.         uint32 nCheckpoints = checkpoint.numCheckpoints[account];
  15.         return nCheckpoints > 0 ? checkpoint.checkpoints[account][nCheckpoints - 1].quantity : 0;
  16.     }

  17.         function getCurrentQuantity(Checkpoint storage checkpoint,address account) external view returns (uint256) {
  18.         return _getCurrentQuantity(checkpoint,account);
  19.     }

  20.     function getPriorQuantity(Checkpoint storage checkpoint,address account, uint blockTimestamp) external view returns (uint256) {
  21.         return _getPriorQuantity(checkpoint,account,blockTimestamp);
  22.     }

  23.     function _getPriorQuantity(Checkpoint storage checkpoint,address account, uint blockTimestamp) internal view returns (uint256) {
  24.         require(blockTimestamp < block.timestamp, "ShipSell::getPriorQuantity: not yet determined");

  25.         uint32 nCheckpoints = checkpoint.numCheckpoints[account];
  26.         if (nCheckpoints == 0) {
  27.             return 0;
  28.         }

  29.         // First check most recent balance
  30.         if (checkpoint.checkpoints[account][nCheckpoints - 1].fromTime <= blockTimestamp) {
  31.             return checkpoint.checkpoints[account][nCheckpoints - 1].quantity;
  32.         }

  33.         // Next check implicit zero balance
  34.         if (checkpoint.checkpoints[account][0].fromTime > blockTimestamp) {
  35.             return 0;
  36.         }

  37.         uint32 lower = 0;
  38.         uint32 upper = nCheckpoints - 1;
  39.         while (upper > lower) {
  40.             uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
  41.             data memory cp = checkpoint.checkpoints[account][center];
  42.             if (cp.fromTime == blockTimestamp) {
  43.                 return cp.quantity;
  44.             } else if (cp.fromTime < blockTimestamp) {
  45.                 lower = center;
  46.             } else {
  47.                 upper = center - 1;
  48.             }
  49.         }
  50.         return checkpoint.checkpoints[account][lower].quantity;
  51.     }

  52.     function _writeCheckpoint(Checkpoint storage checkpoint,address account, uint256 newQuantity) internal {
  53.       uint32 blockTimestamp = uint32(block.timestamp);
  54.       uint32 nCheckpoints = checkpoint.numCheckpoints[msg.sender];

  55.       if (nCheckpoints > 0 && checkpoint.checkpoints[account][nCheckpoints - 1].fromTime == blockTimestamp) {
  56.           checkpoint.checkpoints[account][nCheckpoints - 1].quantity = newQuantity;
  57.       } else {
  58.           checkpoint.checkpoints[account][nCheckpoints] = data(blockTimestamp, newQuantity);
  59.           checkpoint.numCheckpoints[account] = nCheckpoints + 1;
  60.       }
  61.     }

  62.     function WriteCheckpoint(Checkpoint storage checkpoint,address account, uint256 newQuantity) external {
  63.         _writeCheckpoint(checkpoint,account,newQuantity);
  64.     }
  65. }
复制代码


二、配套 Mock 合约(用于测试)

  1. // SPDX-License-Identifier: MIT

  2. pragma solidity 0.8.7;

  3. import "./Checkpoints.sol";

  4. contract CheckpointsMock{
  5.     using Checkpoints for Checkpoints.Checkpoint;
  6.     Checkpoints.Checkpoint example;

  7.     function write(uint256 amount)external{
  8.         uint256 current = Checkpoints.getCurrentQuantity(example,msg.sender);
  9.         Checkpoints.WriteCheckpoint(example,msg.sender,current+amount);
  10.     }

  11.     function readCurrent()public view returns (uint256){
  12.         return Checkpoints.getCurrentQuantity(example,msg.sender);
  13.     }

  14.     function readPrior(uint256 fromTime)public view returns (uint256){
  15.         return Checkpoints.getPriorQuantity(example,msg.sender,fromTime);
  16.     }
  17. }
复制代码


三、设计要点

  • 用 uint32 时间戳代替区块号,避免出块不稳定带来的时间点漂移;用 uint256 记录数量,容量更大。
  • _getPriorQuantity 采用二分查找在历史时间点取值,require 约束传入时间必须小于当前时间,防止取到尚未确定的值。
  • _writeCheckpoint 在「同一时间戳已存在记录」时直接覆盖数量,否则追加一条新记录,保证每个时间点只保留最新快照。
  • Mock 合约通过 using for 语法为 Checkpoint 类型绑定库函数,方便单元测试读写当前值与历史值。


qidao123.com企服评测-应用市场 编辑提示
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表