热点、深度、趋势全掌握,尽在BTC区块圈

代币锁和时间锁

代币锁和时间锁

代币锁是一种限制代币提取的一种合约。它可以把合约中的代币先锁定一段时间,受益人在锁仓期满后才能发起提现取出代币(有点类似线性释放,但代币锁只能在锁定期满后提取,而线性释放一直可以提取)。代币锁常用于质押项目、流动性提供项目(如 Uniswap 中的流动性锁仓)。

image.png

时间锁是一种限制合约的行为的特殊合约。它通过给合约的重要函数(如转账、提现、交易等)加上一个锁定期,用于这个操作的延期执行。例如,对合约的转账调用加上一个 2 天的时间锁,假如资金被盗了,项目方起码还有两天的时间趁代币还未被转走进行补救操作。

image.png

代币锁和时间锁的相同和区别

代币锁和时间锁的最核心区别就是:代币锁控制的是代币的转移、时间锁控制的是合约本身行为(当然可以是代币,也可以不是代币)。

代币锁和时间锁都用到了同一个概念,那就是时间。两者的相同点是:都是通过对行为加上时间 锁 的概念来达到锁定代币、锁定行为函数的目的。

代币锁的实现逻辑核心

代币锁的实现非常简单,和线性释放类似 线性释放跳转链接。都是通过设定一个 beneficiary 受益人 一个 locktime 锁仓期(类似于线性释放的 duration 持续时间),一个 startTime 开始时间戳来进行管理代币的释放。相关核心状态变量如下:

    /*被锁仓的代币*/
    address public token;
     /*锁仓期*/
    uint256 public immutable lockTime;
    /*开始时间*/
    uint256 public immutable startTime;

    /*构造函数*/
    constructor(address _token,address beneficiary,uint256 _startTime, uint256 _lockTime) Ownable(beneficiary){
        require(_lockTime>0,"LockTime must be greater than zero");
        token = _token;
        startTime = _startTime;
        lockTime = _lockTime;
    }
    /*请求释放,检查锁仓期,然后释放*/
    function release() public {
        /*检查时间戳是否已过锁仓期*/
        require(block.timestamp >= startTime + lockTime,"you should wait for release");
        uint256 amount = IERC20(token).balanceOf(address(this));
        require(amount > 0, "not enough tokens for this release");
        IERC20(token).transfer(owner(),amount);
    }

时间锁的实现逻辑核心

时间锁的实现逻辑稍微复杂一丢丢。但时间锁的构思非常巧妙,相信你看了后也会赞叹。代码基于 OZ 代码库的 TimeLockController 简化而成。

时间锁的核心思想是,将要执行的操作用 hash 函数抽象成一个 id,存放在一个 mapping 的结构中,值为这个操作任务的锁定时间戳。在判断任务是否能执行,使用当前区块时间戳和任务时间戳判断大小即可(区块时间戳 > 任务时间戳说明可以执行,否则仍在锁定期)。

如果一个用户需要调用合约执行操作,那他需要调用两次合约,第一次调用将这个操作任务缓存在合约中,这时这个任务的时间戳就固定好了,第二次调用需要等到这个任务锁定期结束再发起调用,否则调用失败。

    /*将执行的某个操作存放到延迟队列中*/
    function scheduleOpt(
        address target,
        uint value,
        bytes calldata data,
        uint256 delay
    ) external {
        /*计算这次操作的 id */
        bytes32 optId = calculateOpt(target, value, data, delay);
        require(optToTimestamp[optId] == 0, "option already in optToTimestamp");
        require(delay >= minDelay,"not enough delay time");
        /*将本次操作 id 放入 map 中,值为锁定的时间戳*/
        optToTimestamp[optId] = block.timestamp + delay;
    }

cancelOpt:这是一个取消 mapping 结构中的任务的函数,如果我们在锁定过程中,不想执行该任务,可以使用该函数进行打断。这个函数非常简单,只需要在 mapping 结构中移除任务即可。

    /*将延迟队列中的某个操作移除*/
    function cancelOpt(
        address target,
        uint value,
        bytes calldata data,
        uint256 delay
    ) external {
        /*计算这次操作的 id */
        bytes32 optId = calculateOpt(target, value, data, delay);
        require(optToTimestamp[optId] != 0, "option not in optToTimestamp");
        delete optToTimestamp[optId];
    }

executeOpt这个函数是我们核心执行函数,其逻辑是:先将调用的参数用 hash 函数恢复出任务的 id。然后使用这个 id 在上面那个缓存任务的 mapping 中寻找出这个任务对应的锁定期。然后使用当前时间戳和任务的时间戳对比,如果当前时间戳大于任务的时间戳,则说明可以正常执行。然后删除缓存的任务,调用 call 即可。

    /*执行队列中的某个交易*/
    function executeOpt(
        address target,
        uint value,
        bytes calldata data,
        uint256 delay
    ) external {
        /*计算这次操作的 id */
        bytes32 optId = calculateOpt(target, value, data, delay);
        require(optToTimestamp[optId] != 0, "option not in optToTimestamp");
        /*检查操作是否过了锁定期*/
        uint256 executeTimestamp = optToTimestamp[optId];
        /*当前时间戳大于 map 缓存中的时间戳*/
        require(block.timestamp > executeTimestamp,"option not ready");
        delete optToTimestamp[optId];
        /*转发执行*/
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        Address.verifyCallResult(success, returndata);
    }

手搓一个最小的代币锁

代币锁合约:TokenLock.sol

contract TokenLock is Ownable {
/*被锁仓的代币*/
address public token;
 /*锁仓期*/
uint256 public immutable lockTime;
/*开始时间*/
uint256 public immutable startTime;
/*构造函数*/
constructor(address _token,address beneficiary,uint256 _startTime, uint256 _lockTime) Ownable(beneficiary){
    require(_lockTime>0,"LockTime must be greater than zero");
    token = _token;
    startTime = _startTime;
    lockTime = _lockTime;
}
/*请求释放,检查锁仓期,然后释放*/
function release() public {
    /*检查时间戳是否已过锁仓期*/
    require(block.timestamp >= startTime + lockTime,"you should wait for release");
    uint256 amount = IERC20(token).balanceOf(address(this));
    require(amount > 0, "not enough tokens for this release");
    IERC20(token).transfer(owner(),amount);
}
}

测试合约 TokenLockTest.t.sol

contract TokenLockTest is Test {
    MockERC20 public token;
    TokenLock public lock;
    address public beneficiary = address(0xBEEF);
    uint256 public start;
    uint256 public lockDuration = 7 days;
    uint256 public amount = 1_000 ether;
    /*初始化*/
    function setUp() public {
        token = new MockERC20();
        start = block.timestamp + 1 days;
        // 创建锁仓合约
        lock = new TokenLock(address(token), beneficiary, start, lockDuration);
        // 铸币并转入锁仓合约
        token.mint(address(this), amount);
        token.transfer(address(lock), amount);
    }
    /*锁仓期内,资金为 0*/
    function test_RevertIfNoTokens() public {
        // 清空代币
        vm.warp(start + lockDuration);
        console.logUint(token.balanceOf(address(lock)));
        /*先烧掉 lock 里面的所有资金*/
        token.burn(address(lock), amount);
        vm.prank(beneficiary);
        vm.expectRevert("not enough tokens for this release");
        lock.release();
    }
    /*锁仓期内,不能释放*/
    function test_CannotReleaseBeforeUnlockTime() public {
        vm.warp(start + lockDuration / 2);
        vm.prank(beneficiary);
        vm.expectRevert("you should wait for release");
        lock.release();
    }
    /*锁仓期后,可以释放*/
    function test_CanReleaseAfterUnlockTime() public {
        vm.warp(start + lockDuration);
        vm.prank(beneficiary);
        lock.release();
        assertEq(token.balanceOf(beneficiary), amount);
    }
}

测试命令

forge test --match-path "./test/tokenLock/TokenLockTest.t.sol"  -vvv

image.png

手搓一个最小的时间锁

contract TimeLockerTest is Test {TimeLocker locker;Target target;

address user = address(0xBEEF);
uint256 minDelay = 1 days;
function setUp() public {
    locker = new TimeLocker(minDelay);
    target = new Target();
    vm.deal(user, 1 ether);
}
/*测试操作锁定、执行*/
function testScheduleAndExecute() public {
    // 构造 calldata
    bytes memory data = abi.encodeWithSignature("setValue(uint256)", 42);
    uint256 valueToSend = 0;
    uint256 delay = 2 days;
    // 调用 scheduleOpt
    locker.scheduleOpt(address(target), valueToSend, data, delay);
    // 确保还未到执行时间不能执行
    vm.expectRevert("option not ready");
    locker.executeOpt(address(target), valueToSend, data, delay);
    // 跳过时间
    vm.warp(block.timestamp + delay + 1);
    // 执行操作
    locker.executeOpt(address(target), valueToSend, data, delay);
    // 验证目标合约状态已更新
    assertEq(target.value(), 42);
}
/*测试打断锁定,取消执行*/
function testCancel() public {
    bytes memory data = abi.encodeWithSignature("setValue(uint256)", 100);
    uint256 valueToSend = 0;
    uint256 delay = 1 days;
    // 调用 scheduleOpt
    locker.scheduleOpt(address(target), valueToSend, data, delay);
    // 取消
    locker.cancelOpt(address(target), valueToSend, data, delay);
    // 再次执行应 revert
    vm.expectRevert("option not in optToTimestamp");
    locker.executeOpt(address(target), valueToSend, data, delay);
}

- **测试命令**
```js
forge test --match-path "./test/timeLocker/TimeLockerTest.t.sol"  -vvv

image.png

使用本文
0
共享
上一篇

BlackRock支持的Libre推出5亿美元电报债券基金,推动TON生态扩展

下一篇

特朗普在密歇根州集会上夸耀经济成就:关税成“经济生命线”

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

阅读下一页

来自 Solana 智能合约开发的真实案例

本文通过一个Solana智能合约中的奖励分配逻辑的实际案例,强调了使用集合论的思维方式来确保代码的健壮性。核心思想是将问题划分为完整的子集,从而全面考虑所有可能的情况

作为区块链的 Rollups

本文介绍了以太坊的Rollups技术,Rollups是一种Layer2解决方案,通过将交易处理从主链转移到链下,从而提高交易速度和降低交易成本。