Solidity 中如何处理时间锁(Timelock)机制?
时间锁(Timelock)是一种安全机制,要求交易在执行前必须经过一段延迟时间。这在 DeFi 协议、治理合约和升级机制中广泛应用,为用户提供反应时间,防止恶意操作。1. 时间锁的核心概念/*时间锁原理:- 交易提交后进入队列- 必须经过最小延迟时间才能执行- 在延迟期内可以取消交易- 超过最大延迟时间后交易过期应用场景:- 协议参数变更- 合约升级- 大额资金转移- 治理提案执行- 紧急功能暂停*/2. 基础时间锁实现contract BasicTimelock { // 事件 event TransactionScheduled( bytes32 indexed txHash, address indexed target, uint256 value, bytes data, uint256 eta ); event TransactionExecuted( bytes32 indexed txHash, address indexed target, uint256 value, bytes data ); event TransactionCancelled(bytes32 indexed txHash); event DelayChanged(uint256 oldDelay, uint256 newDelay); // 状态变量 address public admin; uint256 public delay; // 最小延迟时间 uint256 public constant GRACE_PERIOD = 14 days; // 宽限期 uint256 public constant MINIMUM_DELAY = 2 days; // 最小延迟 uint256 public constant MAXIMUM_DELAY = 30 days; // 最大延迟 // 交易哈希 => 计划执行时间 mapping(bytes32 => uint256) public queuedTransactions; modifier onlyAdmin() { require(msg.sender == admin, "Timelock: caller is not admin"); _; } constructor(address _admin, uint256 _delay) { require(_delay >= MINIMUM_DELAY, "Delay must exceed minimum"); require(_delay <= MAXIMUM_DELAY, "Delay must not exceed maximum"); admin = _admin; delay = _delay; } // 接收 ETH receive() external payable {} // 设置延迟时间 function setDelay(uint256 _delay) public onlyAdmin { require(_delay >= MINIMUM_DELAY, "Delay must exceed minimum"); require(_delay <= MAXIMUM_DELAY, "Delay must not exceed maximum"); emit DelayChanged(delay, _delay); delay = _delay; } // 计算交易哈希 function hashTransaction( address target, uint256 value, string memory signature, bytes memory data, uint256 eta ) public pure returns (bytes32) { return keccak256(abi.encode(target, value, signature, data, eta)); } // 计划交易 function queueTransaction( address target, uint256 value, string memory signature, bytes memory data, uint256 eta ) public onlyAdmin returns (bytes32 txHash) { require( eta >= block.timestamp + delay, "Timelock: estimated execution block must satisfy delay" ); txHash = hashTransaction(target, value, signature, data, eta); require( queuedTransactions[txHash] == 0, "Timelock: transaction already queued" ); queuedTransactions[txHash] = eta; emit TransactionScheduled(txHash, target, value, data, eta); } // 取消交易 function cancelTransaction( address target, uint256 value, string memory signature, bytes memory data, uint256 eta ) public onlyAdmin { bytes32 txHash = hashTransaction(target, value, signature, data, eta); require( queuedTransactions[txHash] != 0, "Timelock: transaction not queued" ); delete queuedTransactions[txHash]; emit TransactionCancelled(txHash); } // 执行交易 function executeTransaction( address target, uint256 value, string memory signature, bytes memory data, uint256 eta ) public payable onlyAdmin { bytes32 txHash = hashTransaction(target, value, signature, data, eta); require( queuedTransactions[txHash] != 0, "Timelock: transaction not queued" ); require( block.timestamp >= eta, "Timelock: transaction hasn't surpassed time lock" ); require( block.timestamp <= eta + GRACE_PERIOD, "Timelock: transaction is stale" ); delete queuedTransactions[txHash]; bytes memory callData; if (bytes(signature).length == 0) { callData = data; } else { callData = abi.encodePacked( bytes4(keccak256(bytes(signature))), data ); } (bool success, ) = target.call{value: value}(callData); require(success, "Timelock: transaction execution reverted"); emit TransactionExecuted(txHash, target, value, data); }}3. 高级时间锁(支持批量操作)contract AdvancedTimelock { // 事件 event OperationScheduled( bytes32 indexed id, uint256 indexed delay, address indexed target, uint256 value, bytes data ); event OperationExecuted(bytes32 indexed id); event OperationCancelled(bytes32 indexed id); event MinDelayChange(uint256 oldDuration, uint256 newDuration); event RoleGranted(bytes32 indexed role, address indexed account); event RoleRevoked(bytes32 indexed role, address indexed account); // 角色定义 bytes32 public constant TIMELOCK_ADMIN_ROLE = keccak256("TIMELOCK_ADMIN_ROLE"); bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE"); bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE"); bytes32 public constant CANCELLER_ROLE = keccak256("CANCELLER_ROLE"); // 操作状态 enum OperationState { Unset, // 未设置 Pending, // 等待中 Ready, // 可执行 Done, // 已完成 Expired // 已过期 } struct Operation { uint256 delay; // 延迟时间 uint256 scheduledAt; // 计划时间 bool executed; // 是否已执行 } // 状态变量 mapping(bytes32 => Operation) public operations; mapping(bytes32 => mapping(address => bool)) public hasRole; uint256 public minDelay; // 最小延迟 uint256 public maxDelay; // 最大延迟 uint256 public gracePeriod; // 宽限期 bytes32[] public operationIds; // 操作ID列表 modifier onlyRole(bytes32 role) { require(hasRole[role][msg.sender], "Missing role"); _; } constructor( uint256 _minDelay, address[] memory proposers, address[] memory executors ) { require(_minDelay > 0, "Min delay must be > 0"); minDelay = _minDelay; maxDelay = _minDelay * 10; gracePeriod = 14 days; // 设置管理员 _grantRole(TIMELOCK_ADMIN_ROLE, msg.sender); // 设置提议者 for (uint i = 0; i < proposers.length; i++) { _grantRole(PROPOSER_ROLE, proposers[i]); } // 设置执行者 for (uint i = 0; i < executors.length; i++) { _grantRole(EXECUTOR_ROLE, executors[i]); } // 取消者可以是任何人 _grantRole(CANCELLER_ROLE, msg.sender); } function _grantRole(bytes32 role, address account) internal { hasRole[role][account] = true; emit RoleGranted(role, account); } function grantRole(bytes32 role, address account) external onlyRole(TIMELOCK_ADMIN_ROLE) { _grantRole(role, account); } function revokeRole(bytes32 role, address account) external onlyRole(TIMELOCK_ADMIN_ROLE) { hasRole[role][account] = false; emit RoleRevoked(role, account); } // 接收 ETH receive() external payable {} // 计算操作ID function hashOperation( address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt ) public pure returns (bytes32) { return keccak256(abi.encode(target, value, data, predecessor, salt)); } // 批量操作的哈希 function hashOperationBatch( address[] calldata targets, uint256[] calldata values, bytes[] calldata datas, bytes32 predecessor, bytes32 salt ) public pure returns (bytes32) { return keccak256(abi.encode(targets, values, datas, predecessor, salt)); } // 获取操作状态 function getOperationState(bytes32 id) public view returns (OperationState) { Operation memory op = operations[id]; if (op.scheduledAt == 0) { return OperationState.Unset; } else if (op.executed) { return OperationState.Done; } else if (block.timestamp < op.scheduledAt + op.delay) { return OperationState.Pending; } else if (block.timestamp > op.scheduledAt + op.delay + gracePeriod) { return OperationState.Expired; } else { return OperationState.Ready; } } // 检查是否准备好执行 function isOperationReady(bytes32 id) public view returns (bool) { return getOperationState(id) == OperationState.Ready; } // 检查是否已完成 function isOperationDone(bytes32 id) public view returns (bool) { return getOperationState(id) == OperationState.Done; } // 计划操作 function schedule( address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt, uint256 delay ) public onlyRole(PROPOSER_ROLE) { bytes32 id = hashOperation(target, value, data, predecessor, salt); require( getOperationState(id) == OperationState.Unset, "Operation already scheduled" ); require(delay >= minDelay, "Delay too short"); require(delay <= maxDelay, "Delay too long"); operations[id] = Operation({ delay: delay, scheduledAt: block.timestamp, executed: false }); operationIds.push(id); emit OperationScheduled(id, delay, target, value, data); } // 批量计划 function scheduleBatch( address[] calldata targets, uint256[] calldata values, bytes[] calldata datas, bytes32 predecessor, bytes32 salt, uint256 delay ) public onlyRole(PROPOSER_ROLE) { require( targets.length == values.length && targets.length == datas.length, "Length mismatch" ); bytes32 id = hashOperationBatch(targets, values, datas, predecessor, salt); require( getOperationState(id) == OperationState.Unset, "Operation already scheduled" ); require(delay >= minDelay, "Delay too short"); operations[id] = Operation({ delay: delay, scheduledAt: block.timestamp, executed: false }); operationIds.push(id); for (uint i = 0; i < targets.length; i++) { emit OperationScheduled(id, delay, targets[i], values[i], datas[i]); } } // 取消操作 function cancel(bytes32 id) external onlyRole(CANCELLER_ROLE) { require( getOperationState(id) != OperationState.Unset, "Operation not scheduled" ); require( getOperationState(id) != OperationState.Done, "Operation already done" ); delete operations[id]; emit OperationCancelled(id); } // 执行操作 function execute( address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt ) public payable onlyRole(EXECUTOR_ROLE) { bytes32 id = hashOperation(target, value, data, predecessor, salt); require( getOperationState(id) == OperationState.Ready, "Operation not ready" ); operations[id].executed = true; (bool success, ) = target.call{value: value}(data); require(success, "Execution failed"); emit OperationExecuted(id); } // 批量执行 function executeBatch( address[] calldata targets, uint256[] calldata values, bytes[] calldata datas, bytes32 predecessor, bytes32 salt ) public payable onlyRole(EXECUTOR_ROLE) { require( targets.length == values.length && targets.length == datas.length, "Length mismatch" ); bytes32 id = hashOperationBatch(targets, values, datas, predecessor, salt); require( getOperationState(id) == OperationState.Ready, "Operation not ready" ); operations[id].executed = true; for (uint i = 0; i < targets.length; i++) { (bool success, ) = targets[i].call{value: values[i]}(datas[i]); require(success, "Batch execution failed"); } emit OperationExecuted(id); } // 更新延迟 function updateDelay(uint256 newDelay) external onlyRole(TIMELOCK_ADMIN_ROLE) { require(newDelay >= minDelay, "Delay too short"); require(newDelay <= maxDelay, "Delay too long"); emit MinDelayChange(minDelay, newDelay); minDelay = newDelay; } // 获取待处理操作 function getPendingOperations() external view returns (bytes32[] memory) { uint256 count = 0; for (uint i = 0; i < operationIds.length; i++) { if (getOperationState(operationIds[i]) == OperationState.Pending) { count++; } } bytes32[] memory pending = new bytes32[](count); uint256 index = 0; for (uint i = 0; i < operationIds.length; i++) { if (getOperationState(operationIds[i]) == OperationState.Pending) { pending[index] = operationIds[i]; index++; } } return pending; } // 获取可执行操作 function getReadyOperations() external view returns (bytes32[] memory) { uint256 count = 0; for (uint i = 0; i < operationIds.length; i++) { if (getOperationState(operationIds[i]) == OperationState.Ready) { count++; } } bytes32[] memory ready = new bytes32[](count); uint256 index = 0; for (uint i = 0; i < operationIds.length; i++) { if (getOperationState(operationIds[i]) == OperationState.Ready) { ready[index] = operationIds[i]; index++; } } return ready; }}4. 时间锁在治理中的应用contract GovernanceWithTimelock { // 事件 event ProposalCreated( uint256 indexed id, address proposer, address[] targets, uint256[] values, bytes[] calldatas, uint256 eta ); event ProposalExecuted(uint256 indexed id); event ProposalCancelled(uint256 indexed id); event VoteCast( address indexed voter, uint256 indexed proposalId, bool support, uint256 votes ); // 提案结构 struct Proposal { address proposer; address[] targets; uint256[] values; bytes[] calldatas; uint256 forVotes; uint256 againstVotes; bool executed; bool canceled; uint256 eta; uint256 startBlock; uint256 endBlock; } // 状态变量 address public timelock; address public governanceToken; uint256 public votingDelay; // 投票延迟(区块数) uint256 public votingPeriod; // 投票期(区块数) uint256 public proposalThreshold; // 提案门槛 uint256 public quorumVotes; // 法定票数 uint256 public gracePeriod; // 宽限期 mapping(uint256 => Proposal) public proposals; mapping(uint256 => mapping(address => bool)) public hasVoted; uint256 public proposalCount; constructor( address _timelock, address _governanceToken, uint256 _votingDelay, uint256 _votingPeriod, uint256 _proposalThreshold, uint256 _quorumVotes ) { timelock = _timelock; governanceToken = _governanceToken; votingDelay = _votingDelay; votingPeriod = _votingPeriod; proposalThreshold = _proposalThreshold; quorumVotes = _quorumVotes; gracePeriod = 14 days; } // 创建提案 function propose( address[] memory targets, uint256[] memory values, bytes[] memory calldatas, string memory description ) public returns (uint256) { require( getVotes(msg.sender, block.number - 1) >= proposalThreshold, "Below proposal threshold" ); require( targets.length == values.length && targets.length == calldatas.length, "Length mismatch" ); require(targets.length > 0, "Must provide actions"); proposalCount++; uint256 proposalId = proposalCount; Proposal storage newProposal = proposals[proposalId]; newProposal.proposer = msg.sender; newProposal.targets = targets; newProposal.values = values; newProposal.calldatas = calldatas; newProposal.startBlock = block.number + votingDelay; newProposal.endBlock = block.number + votingDelay + votingPeriod; emit ProposalCreated( proposalId, msg.sender, targets, values, calldatas, 0 ); return proposalId; } // 投票 function castVote(uint256 proposalId, bool support) external { require(state(proposalId) == ProposalState.Active, "Voting closed"); Proposal storage proposal = proposals[proposalId]; require(!hasVoted[proposalId][msg.sender], "Already voted"); uint256 votes = getVotes(msg.sender, proposal.startBlock); require(votes > 0, "No voting power"); hasVoted[proposalId][msg.sender] = true; if (support) { proposal.forVotes += votes; } else { proposal.againstVotes += votes; } emit VoteCast(msg.sender, proposalId, support, votes); } // 排队执行(通过时间锁) function queue(uint256 proposalId) external { require( state(proposalId) == ProposalState.Succeeded, "Proposal not succeeded" ); Proposal storage proposal = proposals[proposalId]; uint256 eta = block.timestamp + AdvancedTimelock(timelock).minDelay(); proposal.eta = eta; // 通过时间锁排队 for (uint i = 0; i < proposal.targets.length; i++) { AdvancedTimelock(timelock).schedule( proposal.targets[i], proposal.values[i], proposal.calldatas[i], bytes32(0), bytes32(proposalId), AdvancedTimelock(timelock).minDelay() ); } } // 执行提案 function execute(uint256 proposalId) external payable { require( state(proposalId) == ProposalState.Queued, "Proposal not queued" ); Proposal storage proposal = proposals[proposalId]; require( block.timestamp >= proposal.eta, "Timelock not passed" ); require( block.timestamp <= proposal.eta + gracePeriod, "Proposal expired" ); proposal.executed = true; // 通过时间锁执行 for (uint i = 0; i < proposal.targets.length; i++) { AdvancedTimelock(timelock).execute( proposal.targets[i], proposal.values[i], proposal.calldatas[i], bytes32(0), bytes32(proposalId) ); } emit ProposalExecuted(proposalId); } // 取消提案 function cancel(uint256 proposalId) external { require( state(proposalId) != ProposalState.Executed, "Already executed" ); Proposal storage proposal = proposals[proposalId]; require( msg.sender == proposal.proposer || getVotes(proposal.proposer, block.number - 1) < proposalThreshold, "Not authorized" ); proposal.canceled = true; // 取消时间锁中的操作 for (uint i = 0; i < proposal.targets.length; i++) { bytes32 id = keccak256(abi.encode( proposal.targets[i], proposal.values[i], proposal.calldatas[i], bytes32(0), bytes32(proposalId) )); if (AdvancedTimelock(timelock).getOperationState(id) != AdvancedTimelock.OperationState.Unset) { AdvancedTimelock(timelock).cancel(id); } } emit ProposalCancelled(proposalId); } // 提案状态枚举 enum ProposalState { Pending, Active, Canceled, Defeated, Succeeded, Queued, Expired, Executed } // 获取提案状态 function state(uint256 proposalId) public view returns (ProposalState) { require(proposalCount >= proposalId && proposalId > 0, "Invalid id"); Proposal storage proposal = proposals[proposalId]; if (proposal.canceled) { return ProposalState.Canceled; } else if (proposal.executed) { return ProposalState.Executed; } else if (block.number <= proposal.startBlock) { return ProposalState.Pending; } else if (block.number <= proposal.endBlock) { return ProposalState.Active; } else if (proposal.forVotes <= proposal.againstVotes || proposal.forVotes < quorumVotes) { return ProposalState.Defeated; } else if (proposal.eta == 0) { return ProposalState.Succeeded; } else if (block.timestamp >= proposal.eta + gracePeriod) { return ProposalState.Expired; } else { return ProposalState.Queued; } } // 获取投票权 function getVotes(address account, uint256 blockNumber) public view returns (uint256) { // 简化实现,实际应查询治理代币 return 1000; }}5. 时间锁安全最佳实践contract TimelockSecurity { /* 安全最佳实践: 1. 延迟时间设置 - 最小延迟:2-3 天(给用户反应时间) - 最大延迟:30 天(防止无限期延迟) - 宽限期:14 天(过期后无法执行) 2. 权限分离 - 提议者:可以提交操作 - 执行者:可以执行操作 - 取消者:可以取消操作 - 管理员:可以修改参数 3. 监控和报警 - 监听所有时间锁事件 - 设置异常操作报警 - 建立应急响应机制 4. 多重验证 - 操作哈希验证 - 参数边界检查 - 状态前置检查 */}// 带紧急暂停的时间锁contract PausableTimelock { bool public paused; address public guardian; modifier whenNotPaused() { require(!paused, "Timelock paused"); _; } modifier onlyGuardian() { require(msg.sender == guardian, "Not guardian"); _; } function pause() external onlyGuardian { paused = true; } function unpause() external onlyGuardian { paused = false; } function execute( address target, uint256 value, bytes calldata data, bytes32 predecessor, bytes32 salt ) external payable whenNotPaused { // 执行逻辑... }}6. 测试示例// Hardhat 测试时间锁const { expect } = require("chai");const { ethers } = require("hardhat");describe("Timelock", function () { let timelock; let admin, proposer, executor, canceller; const minDelay = 2 * 24 * 60 * 60; // 2 days beforeEach(async function () { [admin, proposer, executor, canceller] = await ethers.getSigners(); const Timelock = await ethers.getContractFactory("AdvancedTimelock"); timelock = await Timelock.deploy( minDelay, [proposer.address], [executor.address] ); await timelock.deployed(); // 给时间锁发送 ETH await admin.sendTransaction({ to: timelock.address, value: ethers.utils.parseEther("10") }); }); it("Should schedule and execute operation after delay", async function () { const target = admin.address; const value = ethers.utils.parseEther("1"); const data = "0x"; const predecessor = ethers.constants.HashZero; const salt = ethers.utils.randomBytes(32); // 计划操作 await timelock.connect(proposer).schedule( target, value, data, predecessor, salt, minDelay ); const id = await timelock.hashOperation(target, value, data, predecessor, salt); expect(await timelock.getOperationState(id)).to.equal(1); // Pending // 时间推进 await network.provider.send("evm_increaseTime", [minDelay]); await network.provider.send("evm_mine"); expect(await timelock.getOperationState(id)).to.equal(2); // Ready // 执行操作 await timelock.connect(executor).execute(target, value, data, predecessor, salt); expect(await timelock.getOperationState(id)).to.equal(3); // Done }); it("Should not execute before delay", async function () { const target = admin.address; const value = ethers.utils.parseEther("1"); const data = "0x"; const predecessor = ethers.constants.HashZero; const salt = ethers.utils.randomBytes(32); await timelock.connect(proposer).schedule( target, value, data, predecessor, salt, minDelay ); // 尝试提前执行 await expect( timelock.connect(executor).execute(target, value, data, predecessor, salt) ).to.be.revertedWith("Operation not ready"); });});7. 总结时间锁是重要的安全机制:核心功能:延迟执行可取消过期机制权限控制应用场景:治理合约协议升级参数调整资金转移安全要点:合理的延迟时间权限分离监控报警应急机制最佳实践:使用 OpenZeppelin 的 TimelockController充分测试时间边界建立监控体系制定应急预案