A Compound Timelock is a time-lock mechanism used in the Compound protocol to delay the execution of critical governance actions, such as protocol upgrades, parameter changes, and other sensitive decisions. The goal of the timelock is to ensure that all governance proposals, after being approved, cannot be immediately executed. This gives the community and participants time to review and react to decisions before they are implemented.
The Timelock
contract in Compound is typically used in conjunction with a governance contract (like a Governor
contract) to enforce delays on executing proposals that pass a vote.
Basic Structure of a Compound-style Timelock Contract
In Compound, the Timelock
contract holds a queue of transactions that are scheduled for execution. After a proposal is voted on and passed, it is queued in the Timelock
contract, and only after a specified delay can it be executed.
Here’s an example of a simplified Timelock contract implementation similar to what Compound uses:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Timelock {
address public admin; // The Timelock contract's admin
uint256 public delay; // Delay time (in seconds)
mapping (bytes32 => uint256) public queue; // Queue to store the transactions with their execution time
// Events for logging the actions
event QueueTransaction(bytes32 indexed txId, address target, uint256 value, uint256 timestamp);
event ExecuteTransaction(bytes32 indexed txId, address target, uint256 value);
event CancelTransaction(bytes32 indexed txId);
modifier onlyAdmin() {
require(msg.sender == admin, "Only admin can call this");
_;
}
constructor(uint256 _delay) {
admin = msg.sender; // Set the contract deployer as admin
delay = _delay; // Set the delay time
}
// Submit a proposal to be queued with a delay
function queueTransaction(address target, uint256 value, bytes memory data) external onlyAdmin returns (bytes32) {
bytes32 txId = keccak256(abi.encode(target, value, data, block.timestamp));
uint256 executeTime = block.timestamp + delay; // Set the time when the transaction can be executed
queue[txId] = executeTime;
emit QueueTransaction(txId, target, value, executeTime);
return txId;
}
// Execute a queued proposal, only after the delay time has passed
function executeTransaction(bytes32 txId, address target, uint256 value, bytes memory data) external onlyAdmin {
uint256 executeTime = queue[txId];
require(executeTime != 0, "Transaction not queued");
require(block.timestamp >= executeTime, "Delay period not over");
// Execute the transaction
(bool success, ) = target.call{value: value}(data);
require(success, "Transaction failed");
delete queue[txId];
emit ExecuteTransaction(txId, target, value);
}
// Cancel a queued transaction before execution
function cancelTransaction(bytes32 txId) external onlyAdmin {
require(queue[txId] != 0, "Transaction not queued");
delete queue[txId];
emit CancelTransaction(txId);
}
// Get the current delay time
function getDelay() external view returns (uint256) {
return delay;
}
// Set a new admin for the contract
function setAdmin(address newAdmin) external onlyAdmin {
admin = newAdmin;
}
}
Key Components:
- Admin (
admin
): The admin is the account that has permission to queue, execute, and cancel transactions.
- Delay (
delay
): This is the delay time (in seconds) that must pass before a queued transaction can be executed.
- Queue (
queue
): The queue stores the details of queued transactions, including their scheduled execution time.
- Events:
QueueTransaction
: Emitted when a transaction is added to the queue.
ExecuteTransaction
: Emitted when a queued transaction is executed.
CancelTransaction
: Emitted when a queued transaction is canceled.
How It Works:
Queueing a Transaction:
- The admin calls
queueTransaction
to submit a proposal, which could be a contract call, fund transfer, or any other action. This action is scheduled for execution after a specified delay period.
Executing a Transaction:
- After the delay period has passed, the admin can call
executeTransaction
to execute the queued transaction. The transaction is only executed if the delay has expired.
Canceling a Transaction:
- If a transaction has been queued but has not yet been executed, the admin can cancel it by calling
cancelTransaction
.
Example Scenario:
Let’s say you want to propose a change to the protocol parameters, or you need to transfer funds from the protocol. After the proposal passes in governance, it will be queued in the Timelock
contract. The proposal will be executed only after the delay period (for example, 48 hours), providing time for anyone to review or potentially halt the execution.
Compound Timelock in Real Use:
In the Compound protocol and similar DeFi governance systems, the Timelock contract is critical for ensuring that important decisions, like upgrades or parameter changes, are not executed instantly. This adds a layer of security and transparency by giving the community time to review the decision.
The timelock ensures that governance is not susceptible to rushed decisions or attacks where malicious actors could exploit an immediate execution window. By enforcing a delay, it increases confidence in the protocol's long-term security.
Use Cases:
- Protocol upgrades: Delaying the upgrade of smart contracts or adding new features.
- Governance decisions: Delaying the execution of protocol changes voted on by governance tokens.
- Security: Providing a window for the community or other stakeholders to react if they believe a proposal is malicious.
If you have further questions or need additional functionality, feel free to ask!