Timelock and Multisig Patterns
Introduction
Timelock and multisig patterns are essential security mechanisms in smart contracts. This guide covers their implementation, use cases, and best practices.
Timelock Pattern
Basic Timelock
solidity
contract Timelock {
uint256 public constant DELAY = 2 days;
mapping(bytes32 => bool) public queuedTransactions;
event QueueTransaction(bytes32 indexed txHash, address target, uint256 value, bytes data, uint256 eta);
event ExecuteTransaction(bytes32 indexed txHash, address target, uint256 value, bytes data);
event CancelTransaction(bytes32 indexed txHash, address target, uint256 value, bytes data);
function queueTransaction(
address target,
uint256 value,
bytes memory data
) public returns (bytes32) {
bytes32 txHash = keccak256(abi.encode(target, value, data));
uint256 eta = block.timestamp + DELAY;
queuedTransactions[txHash] = true;
emit QueueTransaction(txHash, target, value, data, eta);
return txHash;
}
function executeTransaction(
address target,
uint256 value,
bytes memory data
) public payable returns (bytes memory) {
bytes32 txHash = keccak256(abi.encode(target, value, data));
require(queuedTransactions[txHash], "Transaction not queued");
require(block.timestamp >= eta, "Timelock not expired");
queuedTransactions[txHash] = false;
(bool success, bytes memory returnData) = target.call{value: value}(data);
require(success, "Transaction failed");
emit ExecuteTransaction(txHash, target, value, data);
return returnData;
}
function cancelTransaction(
address target,
uint256 value,
bytes memory data
) public {
bytes32 txHash = keccak256(abi.encode(target, value, data));
queuedTransactions[txHash] = false;
emit CancelTransaction(txHash, target, value, data);
}
}
Multisig Pattern
Basic Multisig
solidity
contract Multisig {
address[] public owners;
uint256 public required;
mapping(bytes32 => mapping(address => bool)) public confirmations;
mapping(bytes32 => bool) public executed;
event Submission(bytes32 indexed transactionId);
event Confirmation(address indexed sender, bytes32 indexed transactionId);
event Execution(bytes32 indexed transactionId);
constructor(address[] memory _owners, uint256 _required) {
require(_owners.length > 0, "Owners required");
require(_required > 0 && _required <= _owners.length, "Invalid required number");
owners = _owners;
required = _required;
}
function submitTransaction(
address target,
uint256 value,
bytes memory data
) public returns (bytes32) {
bytes32 txId = keccak256(abi.encode(target, value, data, block.number));
confirmations[txId][msg.sender] = true;
emit Submission(txId);
emit Confirmation(msg.sender, txId);
return txId;
}
function confirmTransaction(bytes32 txId) public {
require(!executed[txId], "Already executed");
confirmations[txId][msg.sender] = true;
emit Confirmation(msg.sender, txId);
}
function executeTransaction(
address target,
uint256 value,
bytes memory data,
bytes32 txId
) public {
require(!executed[txId], "Already executed");
require(getConfirmationCount(txId) >= required, "Not enough confirmations");
executed[txId] = true;
(bool success,) = target.call{value: value}(data);
require(success, "Transaction failed");
emit Execution(txId);
}
function getConfirmationCount(bytes32 txId) public view returns (uint256 count) {
for (uint256 i = 0; i < owners.length; i++) {
if (confirmations[txId][owners[i]]) {
count++;
}
}
}
}
Combined Pattern
Timelock Multisig
solidity
contract TimelockMultisig {
uint256 public constant DELAY = 2 days;
uint256 public required;
address[] public owners;
struct Transaction {
address target;
uint256 value;
bytes data;
uint256 eta;
bool executed;
mapping(address => bool) confirmations;
}
mapping(bytes32 => Transaction) public transactions;
event TransactionSubmitted(bytes32 indexed txId, address target, uint256 value, bytes data, uint256 eta);
event TransactionConfirmed(bytes32 indexed txId, address indexed owner);
event TransactionExecuted(bytes32 indexed txId);
function submitTransaction(
address target,
uint256 value,
bytes memory data
) public returns (bytes32) {
bytes32 txId = keccak256(abi.encode(target, value, data, block.number));
uint256 eta = block.timestamp + DELAY;
Transaction storage transaction = transactions[txId];
transaction.target = target;
transaction.value = value;
transaction.data = data;
transaction.eta = eta;
transaction.confirmations[msg.sender] = true;
emit TransactionSubmitted(txId, target, value, data, eta);
emit TransactionConfirmed(txId, msg.sender);
return txId;
}
function confirmTransaction(bytes32 txId) public {
Transaction storage transaction = transactions[txId];
require(!transaction.executed, "Already executed");
require(!transaction.confirmations[msg.sender], "Already confirmed");
transaction.confirmations[msg.sender] = true;
emit TransactionConfirmed(txId, msg.sender);
}
function executeTransaction(bytes32 txId) public {
Transaction storage transaction = transactions[txId];
require(!transaction.executed, "Already executed");
require(block.timestamp >= transaction.eta, "Timelock not expired");
require(getConfirmationCount(txId) >= required, "Not enough confirmations");
transaction.executed = true;
(bool success,) = transaction.target.call{value: transaction.value}(transaction.data);
require(success, "Transaction failed");
emit TransactionExecuted(txId);
}
function getConfirmationCount(bytes32 txId) public view returns (uint256 count) {
Transaction storage transaction = transactions[txId];
for (uint256 i = 0; i < owners.length; i++) {
if (transaction.confirmations[owners[i]]) {
count++;
}
}
}
}
Best Practices
Security
- Validate owners
- Check delays
- Verify signatures
- Handle errors
Implementation
- Use modifiers
- Emit events
- Document states
- Test thoroughly
Upgrades
- Plan migrations
- Version control
- Backup data
- Test upgrades
Common Patterns
Access Control
solidity
contract AccessControl {
mapping(address => bool) public owners;
uint256 public required;
modifier onlyOwner() {
require(owners[msg.sender], "Not owner");
_;
}
modifier onlyTimelocked() {
require(block.timestamp >= eta, "Timelock not expired");
_;
}
modifier onlyConfirmed(bytes32 txId) {
require(getConfirmationCount(txId) >= required, "Not enough confirmations");
_;
}
}
Practice Exercise
Create a system that:
- Implements timelock
- Handles multisig
- Combines patterns
- Manages access
- Monitors states
Key Takeaways
- Use timelock for delay
- Implement multisig for consensus
- Combine patterns for security
- Follow best practices
- Test thoroughly
Remember: Security patterns require careful implementation and testing.