{"author":{"address":null,"user":"https://learnblockchain.cn/people/18158"},"content":{"body":"# 前言\r\n本文主要利用哈希时间锁合约（Hash Time Locked Contract，HTLC）是一种去中心化的合约机制，通过结合时间锁和哈希锁，实现了条件支付的功能，包含了开发、测试、部署全部流程；\r\n# 区块链上的时间锁\r\n**定义**：一种去中心化的合约机制，通过时间锁和哈希锁的结合从而实现。\u003cbr\u003e\r\n##### 工作流程\r\n-   创建交易，并加入到时间锁队列。\r\n-   在交易的锁定期满后，执行交易。\r\n-   后悔了，取消时间锁队列中的某些交易。\r\n\r\n**场景**：主要用在`DeFi`和`DAO`中，可以提高项目的安全性；\r\n\r\n**功能**：智能合约的某些功能锁定一段时间，大大减少项目方`rug pull`和黑客攻击的机会，增加去中心化应用的安全性；\r\n# 合约开发\r\n```\r\n// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.21;\r\nimport \"hardhat/console.sol\";\r\ncontract Timelock{\r\n    // 事件\r\n    // 交易取消事件\r\n    event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature,  bytes data, uint executeTime);\r\n    // 交易执行事件\r\n    event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature,  bytes data, uint executeTime);\r\n    // 交易创建并进入队列 事件\r\n    event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint executeTime);\r\n    // 修改管理员地址的事件\r\n    event NewAdmin(address indexed newAdmin);\r\n\r\n    // 状态变量\r\n    address public admin; // 管理员地址\r\n    uint public constant GRACE_PERIOD = 7 days; // 交易有效期，过期的交易作废\r\n    uint public delay; // 交易锁定时间 （秒）\r\n    mapping (bytes32 =\u003e bool) public queuedTransactions; // txHash到bool，记录所有在时间锁队列中的交易\r\n    \r\n    // onlyOwner modifier\r\n    modifier onlyOwner() {\r\n        require(msg.sender == admin, \"Timelock: Caller not admin\");\r\n        _;\r\n    }\r\n\r\n    // onlyTimelock modifier\r\n    modifier onlyTimelock() {\r\n        require(msg.sender == address(this), \"Timelock: Caller not Timelock\");\r\n        _;\r\n    }\r\n\r\n    /**\r\n     * @dev 构造函数，初始化交易锁定时间 （秒）和管理员地址\r\n     */\r\n    constructor(uint delay_) {\r\n        delay = delay_;\r\n        admin = msg.sender;\r\n    }\r\n\r\n    /**\r\n     * @dev 改变管理员地址，调用者必须是Timelock合约。\r\n     */\r\n    function changeAdmin(address newAdmin) public onlyTimelock {\r\n        admin = newAdmin;\r\n\r\n        emit NewAdmin(newAdmin);\r\n    }\r\n\r\n    /**\r\n     * @dev 创建交易并添加到时间锁队列中。\r\n     * @param target: 目标合约地址\r\n     * @param value: 发送eth数额\r\n     * @param signature: 要调用的函数签名（function signature）\r\n     * @param data: call data，里面是一些参数\r\n     * @param executeTime: 交易执行的区块链时间戳\r\n     *\r\n     * 要求：executeTime 大于 当前区块链时间戳+delay\r\n     */\r\n    function queueTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 executeTime) public onlyOwner returns (bytes32) {\r\n        // 检查：交易执行时间满足锁定时间\r\n        require(executeTime \u003e= getBlockTimestamp() + delay, \"Timelock::queueTransaction: Estimated execution block must satisfy delay.\");\r\n        // 计算交易的唯一识别符：一堆东西的hash\r\n        bytes32 txHash = getTxHash(target, value, signature, data, executeTime);\r\n        // 将交易添加到队列\r\n        queuedTransactions[txHash] = true;\r\n\r\n        emit QueueTransaction(txHash, target, value, signature, data, executeTime);\r\n        return txHash;\r\n    }\r\n\r\n    /**\r\n     * @dev 取消特定交易。\r\n     *\r\n     * 要求：交易在时间锁队列中\r\n     */\r\n    function cancelTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 executeTime) public onlyOwner{\r\n        // 计算交易的唯一识别符：一堆东西的hash\r\n        bytes32 txHash = getTxHash(target, value, signature, data, executeTime);\r\n        // 检查：交易在时间锁队列中\r\n        require(queuedTransactions[txHash], \"Timelock::cancelTransaction: Transaction hasn't been queued.\");\r\n        // 将交易移出队列\r\n        queuedTransactions[txHash] = false;\r\n\r\n        emit CancelTransaction(txHash, target, value, signature, data, executeTime);\r\n    }\r\n\r\n    /**\r\n     * @dev 执行特定交易。\r\n     *\r\n     * 要求：\r\n     * 1. 交易在时间锁队列中\r\n     * 2. 达到交易的执行时间\r\n     * 3. 交易没过期\r\n     */\r\n    function executeTransaction(address target, uint256 value, string memory signature, bytes memory data, uint256 executeTime) public payable onlyOwner returns (bytes memory) {\r\n        bytes32 txHash = getTxHash(target, value, signature, data, executeTime);\r\n        // 检查：交易是否在时间锁队列中\r\n        require(queuedTransactions[txHash], \"Timelock::executeTransaction: Transaction hasn't been queued.\");\r\n        // 检查：达到交易的执行时间\r\n        require(getBlockTimestamp() \u003e= executeTime, \"Timelock::executeTransaction: Transaction hasn't surpassed time lock.\");\r\n        // 检查：交易没过期\r\n       require(getBlockTimestamp() \u003c= executeTime + GRACE_PERIOD, \"Timelock::executeTransaction: Transaction is stale.\");\r\n        // 将交易移出队列\r\n        queuedTransactions[txHash] = false;\r\n\r\n        // 获取call data\r\n        bytes memory callData;\r\n        if (bytes(signature).length == 0) {\r\n            callData = data;\r\n        } else {\r\n// 这里如果采用encodeWithSignature的编码方式来实现调用管理员的函数，请将参数data的类型改为address。不然会导致管理员的值变为类似\"0x0000000000000000000000000000000000000020\"的值。其中的0x20是代表字节数组长度的意思.\r\n            callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);\r\n        }\r\n        // 利用call执行交易\r\n        (bool success, bytes memory returnData) = target.call{value: value}(callData);\r\n        \r\n        require(success, \"Timelock::executeTransaction: Transaction execution reverted.\");\r\n\r\n        emit ExecuteTransaction(txHash, target, value, signature, data, executeTime);\r\n\r\n        return returnData;\r\n    }\r\n\r\n    /**\r\n     * @dev 获取当前区块链时间戳\r\n     */\r\n    function getBlockTimestamp() public view returns (uint) {\r\n        return block.timestamp;\r\n    }\r\n\r\n    /**\r\n     * @dev 将一堆东西拼成交易的标识符\r\n     */\r\n    function getTxHash(\r\n        address target,\r\n        uint value,\r\n        string memory signature,\r\n        bytes memory data,\r\n        uint executeTime\r\n    ) public pure returns (bytes32) {\r\n        return keccak256(abi.encode(target, value, signature, data, executeTime));\r\n    }\r\n}\r\n# 编译指令\r\n# npx hardhat compile\r\n```\r\n# 合约测试\r\n**说明**：1.获取当前区块时间来模拟延迟执行，2.通过ethers.utils.defaultAbiCoder.encode([\"address\"], [newAdmin])方法获取hash或者使用[在线工具生成abiHash](https://abi.hashex.org/)\r\n```\r\nconst {ethers,getNamedAccounts,deployments} = require(\"hardhat\");\r\nconst { assert,expect } = require(\"chai\");\r\ndescribe(\"Timelock\",function(){\r\n    let Timelock;//合约地址\r\n    let firstAccount//第一个账户\r\n    let secondAccount//第二个账户\r\n    let addr1;\r\n    let addr2;\r\n    beforeEach(async function(){\r\n        await deployments.fixture([\"Timelock\"]);\r\n        [addr1,addr2]=await ethers.getSigners();\r\n        firstAccount=(await getNamedAccounts()).firstAccount;\r\n        secondAccount=(await getNamedAccounts()).secondAccount;\r\n        const TimelockDeployment = await deployments.get(\"Timelock\");\r\n        Timelock = await ethers.getContractAt(\"Timelock\",TimelockDeployment.address);//已经部署的合约交互\r\n       \r\n    });\r\n    describe(\"时间锁\",function(){\r\n        it(\"时间锁测试\",async function(){\r\n            // 获取当前区块时间\r\n    const currentTime = await ethers.provider.getBlock(\"latest\").then(block =\u003e block.timestamp);\r\n\r\n    // 预定执行时间：当前时间 + 100s\r\n    const executeTime = currentTime + 100;\r\n    console.log(\"执行时间\",executeTime)\r\n            //时间未到执行报错\r\n            // console.log('时间未到报错',await Timelock.changeAdmin())\r\n           console.log(\"admin的地址\",await Timelock.admin());\r\n        //放入时间对列里\r\n        let TimelockAddress=Timelock.address;//合约地址\r\n        let value=0;//代币数量\r\n        const newAdmin = \"0x70997970C51812dc3A010C7d01b50e0d17dc79C8\";\r\n        //生成hash值 方法1\r\n        let data=ethers.utils.defaultAbiCoder.encode([\"address\"], [newAdmin]);//生成hash\r\n        console.log(data)\r\n        //生成hash值 方法2 借助在线工具生成\r\n        let data1=\"0xec86135600000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8\";//可以通过在线工具生成，newAdmin\r\n        let changeAdmin=\"changeAdmin(address)\";//方法\r\n        //添加到时间队列里 参数说明：合约地址，代币数量，方法，hash，执行时间\r\n        await Timelock.queueTransaction(TimelockAddress,value,changeAdmin,data,executeTime)\r\n        console.log(\"添加到时间队列里\")\r\n        //模拟100s后修改管理员\r\n        await ethers.provider.send(\"evm_increaseTime\", [100]);\r\n        await ethers.provider.send(\"evm_mine\", []);\r\n        //执行交易参数说明：合约地址，代币数量，方法，hash，执行时间\r\n        await Timelock.executeTransaction(TimelockAddress,value,changeAdmin,data,executeTime)\r\n        console.log(\"修改后admin的地址\",await Timelock.admin());\r\n        })\r\n    })\r\n})\r\n# 测试指令\r\n# npx hardhat test ./test/xxx.js\r\n``` \r\n# 合约部署\r\n```\r\nmodule.exports = async function ({getNamedAccounts,deployments}) {\r\n  const  firstAccount= (await getNamedAccounts()).firstAccount;\r\n  const  secondAccount= (await getNamedAccounts()).secondAccount;\r\n  const {deploy,log}=deployments;\r\n  const Timelock=await deploy(\"Timelock\",{\r\n    from:firstAccount,\r\n    args: [100],//参数 锁定时间\r\n    log: true,\r\n  })\r\n  console.log('TokenLocker合约地址',Timelock.address)\r\n}\r\nmodule.exports.tags = [\"all\", \"Timelock\"];\r\n# 部署指令\r\n# npx hardhat deploy\r\n```\r\n# 总结\r\n以上就是时间锁合约从开发、测试、部署全过程以及对时间锁概念以及使用场景等相关介绍；","title":"利用时间锁和哈希锁实现一个哈希时间锁合约"},"history":null,"timestamp":1740899494,"version":1}