🎓 A1Learner
🔍
0 of 45 lessons completed

Solidity Timelocks

A timelock is a delay between deciding an action and running it. The delay gives everyone time to see the action before it takes effect.

Queueing sets an execution time in the future

Queueing stores the earliest time the action may run. That time is the current time plus the delay.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract TimelockQueue {
    uint256 public constant DELAY = 100;
    mapping(bytes32 => uint256) public executeAfter;
    mapping(bytes32 => bool) public queued;

    function queue(bytes32 id) public {
        require(!queued[id], "already queued");
        queued[id] = true;
        executeAfter[id] = block.timestamp + DELAY;
    }

    function ready(bytes32 id) public view returns (bool) {
        return queued[id] && block.timestamp >= executeAfter[id];
    }
}

Execution before the time reverts and after it succeeds

The execute function checks the clock before it changes anything. Warping forward moves the test clock past the delay.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract TimelockExecute {
    uint256 public constant DELAY = 100;
    uint256 public value;
    mapping(bytes32 => uint256) public executeAfter;
    mapping(bytes32 => bool) public executed;

    function queue(bytes32 id) public {
        executeAfter[id] = block.timestamp + DELAY;
    }

    function execute(bytes32 id, uint256 newValue) public {
        require(executeAfter[id] != 0, "not queued");
        require(!executed[id], "already executed");
        require(block.timestamp >= executeAfter[id], "too early");
        executed[id] = true;
        value = newValue;
    }
}

Cancellation stops a queued action for good

Only the proposer may cancel in this toy contract. A cancelled action reverts even after its time arrives.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract TimelockCancel {
    uint256 public constant DELAY = 100;
    address public proposer;
    mapping(bytes32 => uint256) public executeAfter;
    mapping(bytes32 => bool) public cancelled;

    constructor() {
        proposer = msg.sender;
    }

    function queue(bytes32 id) public {
        executeAfter[id] = block.timestamp + DELAY;
    }

    function cancel(bytes32 id) public {
        require(msg.sender == proposer, "not proposer");
        cancelled[id] = true;
    }

    function execute(bytes32 id) public returns (bool) {
        require(executeAfter[id] != 0, "not queued");
        require(!cancelled[id], "cancelled");
        require(block.timestamp >= executeAfter[id], "too early");
        return true;
    }
}

Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract TimelockVault {
    uint256 public constant DELAY = 100;
    uint256 public value;
    mapping(bytes32 => uint256) public plannedValue;
    mapping(bytes32 => uint256) public executeAfter;
    mapping(bytes32 => bool) public cancelled;
    mapping(bytes32 => bool) public executed;

    function queue(bytes32 id, uint256 newValue) public {
        plannedValue[id] = newValue;
        executeAfter[id] = block.timestamp + DELAY;
    }

    function cancel(bytes32 id) public {
        cancelled[id] = true;
    }

    function execute(bytes32 id) public {
        require(executeAfter[id] != 0, "not queued");
        require(!cancelled[id], "cancelled");
        require(!executed[id], "already executed");
        require(block.timestamp >= executeAfter[id], "too early");
        executed[id] = true;
        value = plannedValue[id];
    }
}

Key points

  • Timelock: A timelock is a contract that holds a queued action until a set delay has passed.
  • Delay: The delay is a fixed number of seconds between queueing an action and its earliest execution time.
  • Queue: Queueing records an action and its earliest execution time so it cannot run at once.
  • Execute: Execution runs a queued action only after its earliest execution time has passed.
  • Cancel: Cancellation by the proposer stops a queued action from ever executing.
  • Restatement: A timelock slows an action down on purpose so a rushed or mistaken action can be stopped in time.

In this example, TimelockVault queues a planned value with its execution time, cancels a second plan, and executes the first plan only after a warp past the delay so the stored value changes to the planned value.

Note

A timelock does not judge whether an action is good and it only controls when the action may run.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Remix IDE

Exercise

A timelock action is queued at time 1 with a delay of 100 seconds. When may it first execute?