// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IGovernanceCofig {
function isWhitelistedToken(address token) external returns (bool);
function tokenInfo(address token) external returns (uint256);
}
contract Timelock {
// Event emitted when a new admin is set
event NewAdmin(address indexed newAdmin);
// Event emitted when a new pending admin is set
event NewPendingAdmin(address indexed newPendingAdmin);
// Event emitted when the delay is updated
event NewDelay(uint indexed newDelay);
event NewConfig(address _config);
// Event emitted when a transaction is canceled
event CancelTransaction(
bytes32 indexed txHash,
address indexed target,
uint value,
string signature,
bytes data,
uint eta
);
// Event emitted when a transaction is executed
event ExecuteTransaction(
bytes32 indexed txHash,
address indexed target,
uint value,
string signature,
bytes data,
uint eta
);
// Event emitted when a transaction is queued
event QueueTransaction(
bytes32 indexed txHash,
address indexed target,
uint value,
string signature,
bytes data,
uint eta
);
// Event emitted when tokens are released
event TokensReleased(
address _token,
address indexed _receiver,
uint256 amount
);
// Constants
uint public constant GRACE_PERIOD = 14 days; // Grace period after the delay for executing transactions
uint public constant MINIMUM_DELAY = 1; // Minimum delay for executing transactions
uint public constant MAXIMUM_DELAY = 30 days; // Maximum delay for executing transactions
// Constant address representing ETH
address public constant ethAddress =
address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
// State variables
address public admin; // Address of the admin
IGovernanceCofig public governanceConfig; // Governance configuration contract
address public pendingAdmin; // Address of the pending admin
uint public delay; // Delay for executing transactions
bool public adminInitialized; // Admin initialization status
mapping(bytes32 => bool) public queuedTransactions; // Mapping for queued transactions
// Constructor to initialize the contract values
constructor(address admin_, address governanceConfig_, uint delay_) {
require(
admin_ != address(0),
"Timelock::constructor: Invalid address."
);
require(
delay_ >= MINIMUM_DELAY,
"Timelock::constructor: Delay must exceed minimum delay."
);
require(
delay_ <= MAXIMUM_DELAY,
"Timelock::setDelay: Delay must not exceed maximum delay."
);
admin = admin_;
delay = delay_;
governanceConfig = IGovernanceCofig(governanceConfig_);
}
// Fallback function to receive Ether
receive() external payable {}
// Function to set or update the delay
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);
}
// Function to set or update the config address
function setConfigAddress(address _config) public {
require(
msg.sender == address(governanceConfig),
"Timelock::setDelay: Call must come from config address."
);
governanceConfig = IGovernanceCofig(_config);
emit NewConfig(_config);
}
// Function to accept the pending admin address
function acceptAdmin() public {
require(
msg.sender == pendingAdmin,
"Timelock::acceptAdmin: Call must come from pendingAdmin."
);
admin = msg.sender;
pendingAdmin = address(0);
emit NewAdmin(admin);
}
// Function to set the pending admin address
function setPendingAdmin(address pendingAdmin_) public {
require(
pendingAdmin_ != address(0),
"Timelock::setPendingAdmin: Invalid address"
);
if (adminInitialized) {
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."
);
adminInitialized = true;
}
pendingAdmin = pendingAdmin_;
emit NewPendingAdmin(pendingAdmin);
}
// Function to queue a transaction
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(
target != address(0),
"Timelock::queueTransaction: Invalid address"
);
require(
eta >= getBlockTimestamp() + 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;
}
// Function to cancel a transaction
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."
);
require(
target != address(0),
"Timelock::cancelTransaction: Invalid address"
);
bytes32 txHash = keccak256(
abi.encode(target, value, signature, data, eta)
);
queuedTransactions[txHash] = false;
emit CancelTransaction(txHash, target, value, signature, data, eta);
}
// Function to execute a transaction
function executeTransaction(
address target,
uint value,
string memory signature,
bytes memory data,
uint eta
) public payable returns (bytes memory) {
require(
target != address(0),
"Timelock::executeTransaction: Invalid address"
);
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 + 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;
}
// Function to release tokens
function releaseTokens(
address _token,
address _receiver,
uint256 _amount
) public {
require(
msg.sender == address(this),
"Timelock::releaseTokens: Call must come from admin."
);
require(
governanceConfig.isWhitelistedToken(_token) == true,
"Token not whitelisted"
);
require(
governanceConfig.tokenInfo(_token) >= _amount,
"Can Not Withdraw More Than Max Limit"
);
if (_token == ethAddress) {
payable(_receiver).transfer(_amount);
} else {
IERC20(_token).transfer(_receiver, _amount);
}
emit TokensReleased(_token, _receiver, _amount);
}
// Internal function to get the current block timestamp
function getBlockTimestamp() internal view returns (uint) {
// solium-disable-next-line security/no-block-members
return block.timestamp;
}
}
{
"compilationTarget": {
"QuantumDAO/Timelock.sol": "Timelock"
},
"evmVersion": "shanghai",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"admin_","type":"address"},{"internalType":"address","name":"governanceConfig_","type":"address"},{"internalType":"uint256","name":"delay_","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"txHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"string","name":"signature","type":"string"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"},{"indexed":false,"internalType":"uint256","name":"eta","type":"uint256"}],"name":"CancelTransaction","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"txHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"string","name":"signature","type":"string"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"},{"indexed":false,"internalType":"uint256","name":"eta","type":"uint256"}],"name":"ExecuteTransaction","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newAdmin","type":"address"}],"name":"NewAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_config","type":"address"}],"name":"NewConfig","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"newDelay","type":"uint256"}],"name":"NewDelay","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newPendingAdmin","type":"address"}],"name":"NewPendingAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"txHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"string","name":"signature","type":"string"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"},{"indexed":false,"internalType":"uint256","name":"eta","type":"uint256"}],"name":"QueueTransaction","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_token","type":"address"},{"indexed":true,"internalType":"address","name":"_receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TokensReleased","type":"event"},{"inputs":[],"name":"GRACE_PERIOD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAXIMUM_DELAY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINIMUM_DELAY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"adminInitialized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"string","name":"signature","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint256","name":"eta","type":"uint256"}],"name":"cancelTransaction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"delay","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ethAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"string","name":"signature","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint256","name":"eta","type":"uint256"}],"name":"executeTransaction","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"governanceConfig","outputs":[{"internalType":"contract IGovernanceCofig","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingAdmin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"string","name":"signature","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint256","name":"eta","type":"uint256"}],"name":"queueTransaction","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"queuedTransactions","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"address","name":"_receiver","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"releaseTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_config","type":"address"}],"name":"setConfigAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"delay_","type":"uint256"}],"name":"setDelay","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pendingAdmin_","type":"address"}],"name":"setPendingAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]