Criar um contrato com bloqueio de tempo

porMatheusem16/06/2022

Nesse artigo iremos aprender a como criar um contrato com bloqueio de tempo, para agendar a execução de uma transação no futuro.

Bloqueio de Tempo (TimeLock)

TimeLock é um contrato que publica uma transação a ser executada no futuro. Após um período de espera mínimo, a transação pode ser executada.

TimeLock's são comumente usados em DAOs.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract TimeLock {
error NotOwnerError();
error AlreadyQueuedError(bytes32 txId);
error TimestampNotInRangeError(uint blockTimestamp, uint timestamp);
error NotQueuedError(bytes32 txId);
error TimestampNotPassedError(uint blockTimestmap, uint timestamp);
error TimestampExpiredError(uint blockTimestamp, uint expiresAt);
error TxFailedError();
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);
uint public constant MIN_DELAY = 10; // segundos
uint public constant MAX_DELAY = 1000; // segundos
uint public constant GRACE_PERIOD = 1000; // segundos
address public owner;
// id transação => enfileiradas
mapping(bytes32 => bool) public queued;
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));
}
/**
* @param _target Endereço do contrato ou conta para chamar
* @param _value Quantia de ETH a enviar
* @param _func Assinatura de função, por exemplo "foo(address,uint256)"
* @param _data Envio de dados codificados ABI.
* @param _timestamp Timestamp após o qual a transação pode ser executada.
*/
function queue(
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);
}
function execute(
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 + período de carência
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;
// preparar os dados
bytes memory data;
if (bytes(_func).length > 0) {
// data = func selector + _data
data = abi.encodePacked(bytes4(keccak256(bytes(_func))), _data);
} else {
// chamada de fallback com dados
data = _data;
}
// alvo de chamada
(bool ok, bytes memory res) = _target.call{value: _value}(data);
if (!ok) {
revert TxFailedError();
}
emit Execute(txId, _target, _value, _func, _data, _timestamp);
return res;
}
function cancel(bytes32 _txId) external onlyOwner {
if (!queued[_txId]) {
revert NotQueuedError(_txId);
}
queued[_txId] = false;
emit Cancel(_txId);
}
}

Testar no Remix