Skip to main content

Timelock

Timelock.sol

This contract is a representation of a locking mechanism. The time lock is a timer designed to prevent the execution of a transaction until it reaches the preset time, even if the correct lock combination(s) are known.

pragma solidity 0.6.12;
contract Timelock {
    using SafeMath for uint256;

The SafeMathHermes library is used to avoid overflows and underflows. This is important because otherwise we might end up with a situation where a value should be -1, but is instead 2^256-1.

Events

    event NewAdmin(address indexed newAdmin);

Emitted when a new admin is accepted

    event NewPendingAdmin(address indexed newPendingAdmin);

Emitted when a new admin is queued to be accepted

    event NewDelay(uint indexed newDelay);

Emitted when a new delay is setted

    event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature,  bytes data, uint eta);

Emitted when a pending transaction is cancelled

    event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature,  bytes data, uint eta);

Emitted when a pending transaction is executed

    event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta);

Emitted when a new transaction is queued

Variables

    uint public constant GRACE_PERIOD = 14 days;

The time after which an accepted proposal cannot be executed anymore, constantly set to 14 days.

    uint public constant MINIMUM_DELAY = 12 hours;

The minimum delay accepted

    uint public constant MAXIMUM_DELAY = 30 days;

The maximum delay accepted

    address public admin = address(0x7cef2432A2690168Fb8eb7118A74d5f8EfF9Ef55);

This address keeps the admin. Is used to allows one time setting of admin for deployment purposes

    address public pendingAdmin;

The new address of the admin

    uint public delay;

The delay waited to execute the transaction

    bool public admin_initialized;

Used to check if the admin has already this contract. Only allows transaction if the admin is the contract himself

    mapping (bytes32 => bool) public queuedTransactions;

Queue of transactions

Setup Functions

    constructor() public {
delay = MINIMUM_DELAY;
admin_initialized = false;
}

Externally Accessible Functions

receive
   receive() external payable { }
setDelay
   function setDelay(uint delay_) public {
require(msg.sender == address(this), "Timelock::setDelay: Call must come from Timelock.");
require(delay_ >= MINIMUM_DELAY, "Timelock::setDelay: Delay must exceed minimum delay.");
require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay.");
delay = delay_;

emit NewDelay(delay);
}

This function check if the caller is the timelock contract, check if the new delay is between MINIMUM_DELAY and MAXIMUM_DELAY, then set the new delay

acceptAdmin
   function acceptAdmin() public {
require(msg.sender == pendingAdmin, "Timelock::acceptAdmin: Call must come from pendingAdmin.");
admin = msg.sender;
pendingAdmin = address(0);

emit NewAdmin(admin);
}

Change the actual admin if the sender is seted before on setPendingAdmin

setPendingAdmin
   function setPendingAdmin(address pendingAdmin_) public {
// allows one time setting of admin for deployment purposes
if (admin_initialized) {
require(msg.sender == address(this), "Timelock::setPendingAdmin: Call must come from Timelock.");
} else {
require(msg.sender == admin, "Timelock::setPendingAdmin: First call must come from admin.");
admin_initialized = true;
}
pendingAdmin = pendingAdmin_;

emit NewPendingAdmin(pendingAdmin);
}

Set a new admin to be checked

queueTransaction
   function queueTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public returns (bytes32) {
require(msg.sender == admin, "Timelock::queueTransaction: Call must come from admin.");
require(eta >= getBlockTimestamp().add(delay), "Timelock::queueTransaction: Estimated execution block must satisfy delay.");

bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = true;

emit QueueTransaction(txHash, target, value, signature, data, eta);
return txHash;
}

Queue a new transaction

cancelTransaction
   function cancelTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public {
require(msg.sender == admin, "Timelock::cancelTransaction: Call must come from admin.");

bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = false;

emit CancelTransaction(txHash, target, value, signature, data, eta);
}

Cancel a pending transaction

executeTransaction

function executeTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public payable returns (bytes memory) {
require(msg.sender == admin, "Timelock::executeTransaction: Call must come from admin.");

bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued.");
require(getBlockTimestamp() >= eta, "Timelock::executeTransaction: Transaction hasn't surpassed time lock.");
require(getBlockTimestamp() <= eta.add(GRACE_PERIOD), "Timelock::executeTransaction: Transaction is stale.");

queuedTransactions[txHash] = false;

bytes memory callData;

if (bytes(signature).length == 0) {
callData = data;
} else {
callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
}

// solium-disable-next-line security/no-call-value
(bool success, bytes memory returnData) = target.call{value: value}(callData);
require(success, "Timelock::executeTransaction: Transaction execution reverted.");

emit ExecuteTransaction(txHash, target, value, signature, data, eta);

return returnData;
}

Execute a queued transaction

Internal functions
getBlockTimestamp
   function getBlockTimestamp() internal view returns (uint) {
// solium-disable-next-line security/no-block-members
return block.timestamp;
}


Get the actual time blockstamp