feat(governance): add timelock controller
This commit is contained in:
1 parent
30dc10dc01
commit
b2ec603567
1 file changed
+120
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user