马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
使用时间戳替代区块号的 Checkpoints 库实现(OpenZeppelin 风格)
在基于区块号的投票、锁仓等场景中,用区块高度作为时间点会遇到分叉回滚、出块不稳定等问题。一种更稳健的做法是使用「时间戳」作为时间点,并配合 uint256 类型的数值来记录数量(quantity)。下面给出一个可复用的 Checkpoints 库实现。
一、Checkpoints 库核心代码
- // SPDX-License-Identifier: MIT
- pragma solidity ^0.8.0;
- library Checkpoints {
- struct data{
- uint32 fromTime;
- uint256 quantity;
- }
- struct Checkpoint {
- mapping (address => mapping (uint32 => data)) checkpoints;
- mapping (address => uint32) numCheckpoints;
- }
- // --------------checkpoint-----------
- function _getCurrentQuantity(Checkpoint storage checkpoint,address account) internal view returns (uint256) {
- uint32 nCheckpoints = checkpoint.numCheckpoints[account];
- return nCheckpoints > 0 ? checkpoint.checkpoints[account][nCheckpoints - 1].quantity : 0;
- }
- function getCurrentQuantity(Checkpoint storage checkpoint,address account) external view returns (uint256) {
- return _getCurrentQuantity(checkpoint,account);
- }
- function getPriorQuantity(Checkpoint storage checkpoint,address account, uint blockTimestamp) external view returns (uint256) {
- return _getPriorQuantity(checkpoint,account,blockTimestamp);
- }
- function _getPriorQuantity(Checkpoint storage checkpoint,address account, uint blockTimestamp) internal view returns (uint256) {
- require(blockTimestamp < block.timestamp, "ShipSell::getPriorQuantity: not yet determined");
- uint32 nCheckpoints = checkpoint.numCheckpoints[account];
- if (nCheckpoints == 0) {
- return 0;
- }
- // First check most recent balance
- if (checkpoint.checkpoints[account][nCheckpoints - 1].fromTime <= blockTimestamp) {
- return checkpoint.checkpoints[account][nCheckpoints - 1].quantity;
- }
- // Next check implicit zero balance
- if (checkpoint.checkpoints[account][0].fromTime > blockTimestamp) {
- return 0;
- }
- uint32 lower = 0;
- uint32 upper = nCheckpoints - 1;
- while (upper > lower) {
- uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
- data memory cp = checkpoint.checkpoints[account][center];
- if (cp.fromTime == blockTimestamp) {
- return cp.quantity;
- } else if (cp.fromTime < blockTimestamp) {
- lower = center;
- } else {
- upper = center - 1;
- }
- }
- return checkpoint.checkpoints[account][lower].quantity;
- }
- function _writeCheckpoint(Checkpoint storage checkpoint,address account, uint256 newQuantity) internal {
- uint32 blockTimestamp = uint32(block.timestamp);
- uint32 nCheckpoints = checkpoint.numCheckpoints[msg.sender];
- if (nCheckpoints > 0 && checkpoint.checkpoints[account][nCheckpoints - 1].fromTime == blockTimestamp) {
- checkpoint.checkpoints[account][nCheckpoints - 1].quantity = newQuantity;
- } else {
- checkpoint.checkpoints[account][nCheckpoints] = data(blockTimestamp, newQuantity);
- checkpoint.numCheckpoints[account] = nCheckpoints + 1;
- }
- }
- function WriteCheckpoint(Checkpoint storage checkpoint,address account, uint256 newQuantity) external {
- _writeCheckpoint(checkpoint,account,newQuantity);
- }
- }
复制代码
二、配套 Mock 合约(用于测试)
- // SPDX-License-Identifier: MIT
- pragma solidity 0.8.7;
- import "./Checkpoints.sol";
- contract CheckpointsMock{
- using Checkpoints for Checkpoints.Checkpoint;
- Checkpoints.Checkpoint example;
- function write(uint256 amount)external{
- uint256 current = Checkpoints.getCurrentQuantity(example,msg.sender);
- Checkpoints.WriteCheckpoint(example,msg.sender,current+amount);
- }
- function readCurrent()public view returns (uint256){
- return Checkpoints.getCurrentQuantity(example,msg.sender);
- }
- function readPrior(uint256 fromTime)public view returns (uint256){
- return Checkpoints.getPriorQuantity(example,msg.sender,fromTime);
- }
- }
复制代码
三、设计要点
- 用 uint32 时间戳代替区块号,避免出块不稳定带来的时间点漂移;用 uint256 记录数量,容量更大。
- _getPriorQuantity 采用二分查找在历史时间点取值,require 约束传入时间必须小于当前时间,防止取到尚未确定的值。
- _writeCheckpoint 在「同一时间戳已存在记录」时直接覆盖数量,否则追加一条新记录,保证每个时间点只保留最新快照。
- Mock 合约通过 using for 语法为 Checkpoint 类型绑定库函数,方便单元测试读写当前值与历史值。
qidao123.com企服评测-应用市场 编辑提示
|