Skip to content

Instantly share code, notes, and snippets.

@ArpitxGit
Created August 3, 2022 22:45
Show Gist options
  • Save ArpitxGit/16893b2ae82953bbfe7e9b1d3f6745ec to your computer and use it in GitHub Desktop.
Save ArpitxGit/16893b2ae82953bbfe7e9b1d3f6745ec to your computer and use it in GitHub Desktop.
Delaying Transaction with Time Lock
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
/*
Contract to Delay Transaction
"for security puposes or taking fail safe measuers
as users trust contract more than anything."
1. You are going to broadcast a transaction
that you are going to execute,
with the help of function queue().
2. Once a transaction is queued,
you have to wait a certain amount of time,
depends on you, as you will be difining.
3. Once the defined time has passed,
the transaction will be executed,
with the help of function execute().
*/
contract TimeLock {
error NotOwnerError();
error AlreadyQueuedError(bytes32 txId);
error TimestampNotInRangeError(uint blockTimestamp, uint timestamp);
error NotQueuedError(bytes32 txId);
error TimestampNotPassedError(uint blockTimestamp, 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;//10seconds
uint public constant MAX_DELAY = 1000;//1000seconds
uint public constant GRACE_PERIOD = 1000;//1000seconds
//so we have now 1000 seconds before the tx is expired
address public owner;
mapping(bytes32 => bool) public queued;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
if(msg.sender != owner){
revert NotOwnerError();
}
_;
}
//defining a fallback receive function
receive() external payable {}
//function to compute tx id
function getTxId(
address _target,
uint _value,
string calldata _func,
bytes calldata _data,
uint _timestamp
) public pure returns (bytes32 txId) {
return keccak256(abi.encode(_target, _value, _func, _data, _timestamp));
}
/**
* @param _target Address of contract or account to call
* @param _value Amount of ETH to send
* @param _func Function signature, for example "foo(address,uint256)"
* @param _data ABI encoded data send.
* @param _timestamp Timestamp after which the transaction can be executed.
*/
//function for queuing the trasaction
function queue(
address _target,
uint _value,
string calldata _func,
bytes calldata _data,
uint _timestamp
) external onlyOwner returns (bytes32 txId) {
//create tx id
//after creating tx id, we will check that this tx id has not been queued yet
//so creating a mapping on top of tx id with boolean
//if tx id is queued then boolean will be true
txId = getTxId(_target, _value, _func, _data, _timestamp);
//checking tx id unique
if(queued[txId]) {
revert AlreadyQueuedError(txId);
}
//check timestamp
// ---|----------|------here-----|-------
// block block+min block+max
//we have to make sure that our timestamp fits between block+min and block+max
if(_timestamp < block.timestamp + MIN_DELAY || _timestamp > block.timestamp + MAX_DELAY){
revert TimestampNotInRangeError(block.timestamp, _timestamp);
}
//queue tx
queued[txId] = true;
//emit the event
emit Queue(txId, _target, _value, _func, _data, _timestamp);
}
//for executing the transaction only by the owner
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);
//chech if tx is queued
if(!queued[txId]) {
revert NotQueuedError(txId);
}
//check correct time i.e block.timestamp > _timestamp
if(block.timestamp < _timestamp) {
revert TimestampNotPassedError(block.timestamp, _timestamp);
}
//what if transaction got expired
//so introducing grace period
//----|-----valid tx----|------in this period tx is expired
//timestamp timestamp + grace period
if(block.timestamp > _timestamp + GRACE_PERIOD){
revert TimestampExpiredError(block.timestamp, _timestamp + GRACE_PERIOD);
}
//delete tx from queue
queued[txId] = false;
//data to be passed to execute tx, if function is empty then passing data
//if not then abi encode the function and appending the data
//taking the first 4 bytes
bytes memory data;
if(bytes(_func).length > 0) {
data = abi.encodePacked(bytes4(keccak256(bytes(_func))), _data);
} else {
data = _data;
}
//execute the tx
(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 to cancel a tx
function cancel(bytes32 _txId) external onlyOwner {
if(!queued[_txId]) {
revert NotQueuedError(_txId);
}
queued[_txId] = false;
emit Cancel(_txId);
}
}
contract TestTimeLock {
address public timeLock;
constructor(address _timeLock) {
_timeLock = timeLock;
}
function test() external {
//only be called by the TimeLock contract
require(msg.sender == timeLock);
//also can use to
//upgrade the contract
//funds transafer
//switch price oracle
}
//helper function
function getTimestamp() external view returns (uint) {
return block.timestamp + 100;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment