feat(governance): add timelock controller

This commit is contained in:
dualfroz committed 2026-08-27 09:00:00 +02:00
1 parent 30dc10dc01
commit b2ec603567
1 file changed
+120
+120
View File
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.36;
/// @title Governance timelock queuing calls behind a mandatory delay before execution.
contract Timelock {
uint256 public constant gracePeriod = 14 days;
uint256 public constant minimumDelay = 2 days;
uint256 public constant maximumDelay = 30 days;
address public admin;
address public pendingAdmin;
uint256 public delay;
mapping(bytes32 => bool) public queuedTransactions;
event NewAdmin(address indexed newAdmin);
event NewPendingAdmin(address indexed newPendingAdmin);
event NewDelay(uint256 indexed newDelay);
event QueueTransaction(
bytes32 indexed txHash, address indexed target, uint256 value, string signature, bytes data, uint256 eta
);
event CancelTransaction(
bytes32 indexed txHash, address indexed target, uint256 value, string signature, bytes data, uint256 eta
);
event ExecuteTransaction(
bytes32 indexed txHash, address indexed target, uint256 value, string signature, bytes data, uint256 eta
);
constructor(address adminAddress, uint256 delayValue) {
require(delayValue >= minimumDelay, "Timelock: DELAY_SHORT");
require(delayValue <= maximumDelay, "Timelock: DELAY_LONG");
admin = adminAddress;
delay = delayValue;
}
receive() external payable {}
function setDelay(uint256 delayValue) external {
require(msg.sender == address(this), "Timelock: ONLY_SELF");
require(delayValue >= minimumDelay, "Timelock: DELAY_SHORT");
require(delayValue <= maximumDelay, "Timelock: DELAY_LONG");
delay = delayValue;
emit NewDelay(delayValue);
}
function acceptAdmin() external {
require(msg.sender == pendingAdmin, "Timelock: ONLY_PENDING");
admin = msg.sender;
pendingAdmin = address(0);
emit NewAdmin(admin);
}
function setPendingAdmin(address pendingAdminValue) external {
require(msg.sender == address(this), "Timelock: ONLY_SELF");
pendingAdmin = pendingAdminValue;
emit NewPendingAdmin(pendingAdminValue);
}
function queueTransaction(
address target,
uint256 value,
string calldata signature,
bytes calldata data,
uint256 eta
) external returns (bytes32) {
require(msg.sender == admin, "Timelock: ONLY_ADMIN");
require(eta >= block.timestamp + delay, "Timelock: ETA_TOO_SOON");
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = true;
emit QueueTransaction(txHash, target, value, signature, data, eta);
return txHash;
}
function cancelTransaction(
address target,
uint256 value,
string calldata signature,
bytes calldata data,
uint256 eta
) external {
require(msg.sender == admin, "Timelock: ONLY_ADMIN");
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
queuedTransactions[txHash] = false;
emit CancelTransaction(txHash, target, value, signature, data, eta);
}
function executeTransaction(
address target,
uint256 value,
string calldata signature,
bytes calldata data,
uint256 eta
) external payable returns (bytes memory) {
require(msg.sender == admin, "Timelock: ONLY_ADMIN");
bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));
require(queuedTransactions[txHash], "Timelock: NOT_QUEUED");
require(block.timestamp >= eta, "Timelock: NOT_READY");
require(block.timestamp <= eta + gracePeriod, "Timelock: STALE");
queuedTransactions[txHash] = false;
bytes memory callData;
if (bytes(signature).length == 0) {
callData = data;
} else {
callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
}
(bool success, bytes memory returnData) = target.call{value: value}(callData);
require(success, "Timelock: EXECUTION_REVERTED");
emit ExecuteTransaction(txHash, target, value, signature, data, eta);
return returnData;
}
}