TimelockController
Enforces a mandatory delay between proposing and executing on-chain actions.
TimelockController.sol
// SPDX-License-Identifier: MITpragma solidity ^0.8.20; contract TimelockController { uint256 public immutable minDelay; mapping(address => bool) public isProposer; mapping(address => bool) public isExecutor; mapping(bytes32 => uint256) public timestamps; event Scheduled(bytes32 indexed id, address target, uint256 value, uint256 readyAt); event Executed(bytes32 indexed id); constructor(uint256 minDelaySeconds, address[] memory proposers, address[] memory executors) { minDelay = minDelaySeconds; for (uint256 i = 0; i < proposers.length; i++) isProposer[proposers[i]] = true; for (uint256 i = 0; i < executors.length; i++) isExecutor[executors[i]] = true; } function hashOperation(address target, uint256 value, bytes calldata data, bytes32 salt) public pure returns (bytes32) { return keccak256(abi.encode(target, value, data, salt)); } function schedule(address target, uint256 value, bytes calldata data, bytes32 salt, uint256 delay) external { require(isProposer[msg.sender], "NOT_PROPOSER"); require(delay >= minDelay, "DELAY_TOO_SHORT"); bytes32 id = hashOperation(target, value, data, salt); require(timestamps[id] == 0, "ALREADY_SCHEDULED"); timestamps[id] = block.timestamp + delay; emit Scheduled(id, target, value, block.timestamp + delay); } function execute(address target, uint256 value, bytes calldata data, bytes32 salt) external payable { require(isExecutor[msg.sender], "NOT_EXECUTOR"); bytes32 id = hashOperation(target, value, data, salt); uint256 ready = timestamps[id]; require(ready != 0 && block.timestamp >= ready, "NOT_READY"); timestamps[id] = 0; (bool ok, ) = target.call{value: value}(data); require(ok, "CALL_FAILED"); emit Executed(id); } receive() external payable {}}