时间锁是从时间的维度上,对智能合约中函数执行的时间做了一个限定;也就是将智能合约中的某个函数限制在将来某一段时间执行的代码。
时间锁使用场景
智能合约中的时间锁有很多潜在的应用场景,主要是在defi和dao中大量使用,前者是为了锁住合约中的资金,增加安全性;后者是为了方便全体成员共识。例如通证首次公开发行中,用于实现通证销售的一些功能。时间锁也可以被用来按照时间表授权投资资金使用,即用户只有在一段时间以后才可以取出资金。还有就是通过智能合约去实现遗嘱。在DAO中,TimeLock合约为不同意系统决定的成员提供在执行决定前退出系统的时间。
时间锁的应用场景还有很多,它们可以让合约内的交易变得更加安全和透明。但是时间锁无法自动触发,所以你需要在某个时间节点回来执行这个函数。想要让它们自己执行的话,就需要自动化你的合约。值得一提的是,时间锁作为安全解决方案的一种,并不能被当作是100%的保障手段。
时间锁一般使用方法
-
在创建
Timelock
合约,时间锁合约的管理员一般为项目的多签钱包,保证去中心化。 -
向Timelock合约内添加,需要锁定的交易。
-
满足时间要求之后,再次调用TimeLock合约执行合约。
时间锁合约内容
变量设置
uint public constant MIN_DELAY = 10; // 秒 最小延时时间
uint public constant MAX_DELAY = 1000; // 秒 最大延时时间
uint public constant GRACE_PERIOD = 1000; // 秒 宽限时间
address public owner; //管理员
mapping(bytes32 => bool) public queued; //时间锁交易队列
事件,包括加入队列事件,执行交易事件,取消交易事件。
event Queue(
bytes32 indexed txId,
address indexed target,
uint value,
string func,
bytes data,
uint timestamp
);
event Execute(
bytes32 indexed txId,
address indexed target,
uint value,
string func,
bytes data,
uint timestamp
);
event Cancel(bytes32 indexed txId);
函数
constructor()
构造方法设置管理员的地址和延期时间,延期时间需要大于等于MINIMUM_DELAY且小于等于MAXIMUM_DELAY。管理员地址不能是0地址。
receive() 是一个接收以太币函数,一个合约中最多可以有一个 receive 函数。在对合约转账时会执行 receive 函数,例如通过 transfer()、send() 或 call()。如果 receive 函数不存在,那么 fallback 回退函数会被调用。
getTxId() 通过hash函数给交易生成一个id。
constructor() {
owner = msg.sender;
}
modifier onlyOwner() { //被修饰的函数只能被管理员执行。
if (msg.sender != owner) {
revert NotOwnerError();
}
_;
}
receive() external payable {}
function getTxId(
address _target,
uint _value,
string calldata _func,
bytes calldata _data,
uint _timestamp
) public pure returns (bytes32) {
return keccak256(abi.encode(_target, _value, _func, _data, _timestamp));
}
queueTx
交易
加入队列由管理员调用。当满足条件 (当前区块时间+最小延期时间 <=_timestamp<=当前区块时间+最大延期时间)时,给交易生成id并添加到交易队列中,设置状态为true,代表交易等待执行。
function queueTx(
address _target,
uint _value,
string calldata _func,
bytes calldata _data,
uint _timestamp
) external onlyOwner returns (bytes32 txId) {
txId = getTxId(_target, _value, _func, _data, _timestamp);
if (queued[txId]) {
revert AlreadyQueuedError(txId);
}
// ---|------------|---------------|-------
// block block + min block + max
if (
_timestamp < block.timestamp + MIN_DELAY ||
_timestamp > block.timestamp + MAX_DELAY
) {
revert TimestampNotInRangeError(block.timestamp, _timestamp);
}
queued[txId] = true;
emit Queue(txId, _target, _value, _func, _data, _timestamp);
}
executeTx
执行交易由管理员执行。根据_target, _value, _func, _data, _timestamp从队列中取出交易,如果是待执行状态,接着判断时间知否满足条件,只有区块时间大于_timestamp,且小鱼_timestamp+宽限时间才能执行。使用call的进行执行交易。
function executeTx(
address _target,
uint _value,
string calldata _func,
bytes calldata _data,
uint _timestamp
) external payable onlyOwner returns (bytes memory) {
bytes32 txId = getTxId(_target, _value, _func, _data, _timestamp);
if (!queued[txId]) {
revert NotQueuedError(txId);
}
// ----|-------------------|-------
// timestamp timestamp + grace period
if (block.timestamp < _timestamp) {
revert TimestampNotPassedError(block.timestamp, _timestamp);
}
if (block.timestamp > _timestamp + GRACE_PERIOD) {
revert TimestampExpiredError(block.timestamp, _timestamp + GRACE_PERIOD);
}
queued[txId] = false;
// prepare data
bytes memory data;
if (bytes(_func).length > 0) {
// data = func selector + _data
data = abi.encodePacked(bytes4(keccak256(bytes(_func))), _data);
} else {
// call fallback with data
data = _data;
}
// call target
(bool ok, bytes memory res) = _target.call{value: _value}(data);
if (!ok) {
revert TxFailedError();
}
emit Execute(txId, _target, _value, _func, _data, _timestamp);
return res;
}
cancelTx
取消队列由管理员调用 将txId的交易状态设置为false;触发事件Cancel;
function cancelTx(bytes32 _txId) external onlyOwner {
if (!queued[_txId]) {
revert NotQueuedError(_txId);
}
queued[_txId] = false;
emit Cancel(_txId);
}