TimelockUpgradeable
Inherits: Initializable, AccessControlUpgradeable, PausableUpgradeable, IVersioned
Title: TimelockUpgradeable
Author: Quantillon Labs - Nicolas Bellengé - @chewbaccoin
Secure upgrade mechanism with timelock and multi-sig requirements
Replaces unrestricted upgrade capability with governance-controlled upgrades
Note: security-contact: team@quantillon.money
Constants
UPGRADE_DELAY
Minimum delay for upgrades (48 hours)
uint256 public constant UPGRADE_DELAY = 48 hours
MAX_UPGRADE_DELAY
Maximum delay for upgrades (7 days)
uint256 public constant MAX_UPGRADE_DELAY = 7 days
MIN_MULTISIG_APPROVALS
Minimum number of multi-sig approvals required
uint256 public constant MIN_MULTISIG_APPROVALS = 2
MAX_MULTISIG_SIGNERS
Maximum number of multi-sig signers
uint256 public constant MAX_MULTISIG_SIGNERS = 5
MAX_PROPOSAL_AGE
Maximum age of a pending upgrade proposal (LOW-6: prevents stale proposal execution)
uint256 public constant MAX_PROPOSAL_AGE = 30 days
UPGRADE_PROPOSER_ROLE
Role for proposing upgrades
bytes32 public constant UPGRADE_PROPOSER_ROLE = keccak256("UPGRADE_PROPOSER_ROLE")
UPGRADE_EXECUTOR_ROLE
Role for executing upgrades after timelock
bytes32 public constant UPGRADE_EXECUTOR_ROLE = keccak256("UPGRADE_EXECUTOR_ROLE")
EMERGENCY_UPGRADER_ROLE
Role for emergency upgrades (bypasses timelock)
bytes32 public constant EMERGENCY_UPGRADER_ROLE = keccak256("EMERGENCY_UPGRADER_ROLE")
MULTISIG_MANAGER_ROLE
Role for managing multi-sig signers
bytes32 public constant MULTISIG_MANAGER_ROLE = keccak256("MULTISIG_MANAGER_ROLE")
TIME_PROVIDER
TimeProvider contract for centralized time management
Used to replace direct block.timestamp usage for testability and consistency
TimeProvider public immutable TIME_PROVIDER
State Variables
pendingUpgrades
Pending upgrades by implementation address
mapping(address => PendingUpgrade) public pendingUpgrades
multisigSigners
Multi-sig signers
mapping(address => bool) public multisigSigners
multisigSignerCount
Number of active multi-sig signers
uint256 public multisigSignerCount
upgradeApprovals
Upgrade approvals by signer
mapping(address => mapping(address => bool)) public upgradeApprovals
upgradeApprovalCount
Number of approvals for each pending upgrade
mapping(address => uint256) public upgradeApprovalCount
emergencyMode
Whether emergency mode is active
bool public emergencyMode
_multisigSignersList
Ordered list of multisig signers for approval clearing
address[] internal _multisigSignersList
_multisigSignerIndexPlusOne
1-based index mapping for O(1) signer removal from _multisigSignersList
mapping(address => uint256) internal _multisigSignerIndexPlusOne
_pendingUpgradesList
Ordered list of pending upgrade addresses for signer clearing
address[] internal _pendingUpgradesList
Functions
version
Returns the semantic version of this implementation.
Pure getter (no storage slot) read through the proxy, so it reflects the deployed
implementation. Bump per semver on any change; enforced by make check-version-bump.
See deployments/{chainId}/versions.json for the deployed impl/commit provenance.
Notes:
-
security: No security implications - returns a compile-time constant.
-
validation: No input validation required.
-
state-changes: None - pure function.
-
events: None.
-
errors: None.
-
reentrancy: Not applicable - pure function.
-
access: Public - anyone can read the version.
-
oracle: No oracle dependencies.
function version() external pure virtual override returns (string memory);
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | Semantic version string (e.g. "1.0.0"). |
onlyMultisigSigner
modifier onlyMultisigSigner() ;
onlyEmergencyUpgrader
modifier onlyEmergencyUpgrader() ;
_onlyEmergencyUpgrader
Reverts if caller is not emergency upgrader or emergency mode is not active
Used by onlyEmergencyUpgrader modifier; allows emergency upgrades only when enabled
Notes:
-
security: Restricts emergency upgrade path to EMERGENCY_UPGRADER_ROLE when emergencyMode
-
validation: Caller must have role and emergencyMode must be true
-
state-changes: None
-
events: None
-
errors: NotEmergencyRole if not authorized or not emergency mode
-
reentrancy: No external calls
-
access: Internal; used by modifier
-
oracle: None
function _onlyEmergencyUpgrader() internal view;
initialize
Initializes the timelock contract with admin privileges
Sets up access control roles and pausability. Can only be called once.
Notes:
-
security: Validates input parameters and enforces security checks
-
validation: Validates input parameters and business logic constraints
-
state-changes: Updates contract state variables
-
events: Emits relevant events for state changes
-
errors: Throws custom errors for invalid conditions
-
reentrancy: Not protected by a reentrancy guard
-
access: Restricted to initializer modifier
-
oracle: No oracle dependencies
function initialize(address admin) public initializer;
Parameters
| Name | Type | Description |
|---|---|---|
admin | address | The address that will receive admin and upgrade proposer roles |
proposeUpgrade
Propose an upgrade with timelock
Proposes an upgrade with timelock delay and multi-sig approval requirements
Notes:
-
security: Validates input parameters and enforces security checks
-
validation: Validates input parameters and business logic constraints
-
state-changes: Updates contract state variables
-
events: Emits relevant events for state changes
-
errors: Throws custom errors for invalid conditions
-
reentrancy: Not protected by a reentrancy guard
-
access: Restricted to UPGRADE_PROPOSER_ROLE
-
oracle: No oracle dependencies
function proposeUpgrade(address newImplementation, string calldata description, uint256 customDelay)
external
onlyRole(UPGRADE_PROPOSER_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
newImplementation | address | Address of the new implementation |
description | string | Description of the upgrade |
customDelay | uint256 | Optional custom delay (must be >= UPGRADE_DELAY) |
approveUpgrade
Approve a pending upgrade (multi-sig signer only)
Allows multi-sig signers to approve pending upgrades
Notes:
-
security: Validates input parameters and enforces security checks
-
validation: Validates input parameters and business logic constraints
-
state-changes: Updates contract state variables
-
events: Emits relevant events for state changes
-
errors: Throws custom errors for invalid conditions
-
reentrancy: Not protected by a reentrancy guard
-
access: Restricted to multi-sig signers
-
oracle: No oracle dependencies
function approveUpgrade(address implementation) external onlyMultisigSigner;
Parameters
| Name | Type | Description |
|---|---|---|
implementation | address | Address of the implementation to approve |
revokeUpgradeApproval
Revoke approval for a pending upgrade
Allows multi-sig signers to revoke their approval for pending upgrades
Notes:
-
security: Validates input parameters and enforces security checks
-
validation: Validates input parameters and business logic constraints
-
state-changes: Updates contract state variables
-
events: Emits relevant events for state changes
-
errors: Throws custom errors for invalid conditions
-
reentrancy: Not protected by a reentrancy guard
-
access: Restricted to multi-sig signers
-
oracle: No oracle dependencies
function revokeUpgradeApproval(address implementation) external onlyMultisigSigner;
Parameters
| Name | Type | Description |
|---|---|---|
implementation | address | Address of the implementation to revoke approval for |
executeUpgrade
Execute an upgrade after timelock and multi-sig approval
Executes an upgrade after timelock delay and sufficient multi-sig approvals
Notes:
-
security: Validates input parameters and enforces security checks
-
validation: Validates input parameters and business logic constraints
-
state-changes: Updates contract state variables
-
events: Emits relevant events for state changes
-
errors: Throws custom errors for invalid conditions
-
reentrancy: Not protected by a reentrancy guard
-
access: Restricted to UPGRADE_EXECUTOR_ROLE
-
oracle: No oracle dependencies
function executeUpgrade(address implementation) external onlyRole(UPGRADE_EXECUTOR_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
implementation | address | Address of the implementation to execute |
cancelUpgrade
Cancel a pending upgrade (only proposer or admin)
Allows proposer or admin to cancel pending upgrades
Notes:
-
security: Validates input parameters and enforces security checks
-
validation: Validates input parameters and business logic constraints
-
state-changes: Updates contract state variables
-
events: Emits relevant events for state changes
-
errors: Throws custom errors for invalid conditions
-
reentrancy: Not protected by a reentrancy guard
-
access: Restricted to proposer or admin
-
oracle: No oracle dependencies
function cancelUpgrade(address implementation) external;
Parameters
| Name | Type | Description |
|---|---|---|
implementation | address | Address of the implementation to cancel |
emergencyUpgrade
Emergency upgrade (bypasses timelock, requires emergency mode)
Performs emergency upgrade bypassing timelock and multi-sig requirements
Notes:
-
security: Validates input parameters and enforces security checks
-
validation: Validates input parameters and business logic constraints
-
state-changes: Updates contract state variables
-
events: Emits relevant events for state changes
-
errors: Throws custom errors for invalid conditions
-
reentrancy: Not protected by a reentrancy guard
-
access: Restricted to emergency upgrader role
-
oracle: No oracle dependencies
function emergencyUpgrade(address newImplementation, string calldata description) external onlyEmergencyUpgrader;
Parameters
| Name | Type | Description |
|---|---|---|
newImplementation | address | Address of the new implementation |
description | string | Description of the emergency upgrade |
addMultisigSigner
Add a multi-sig signer
Adds a new multi-sig signer to the timelock system
Notes:
-
security: Validates input parameters and enforces security checks
-
validation: Validates input parameters and business logic constraints
-
state-changes: Updates contract state variables
-
events: Emits relevant events for state changes
-
errors: Throws custom errors for invalid conditions
-
reentrancy: Not protected by a reentrancy guard
-
access: Restricted to MULTISIG_MANAGER_ROLE
-
oracle: No oracle dependencies
function addMultisigSigner(address signer) external onlyRole(MULTISIG_MANAGER_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
signer | address | Address of the signer to add |
removeMultisigSigner
Remove a multi-sig signer
Removes a multi-sig signer from the timelock system
Notes:
-
security: Validates input parameters and enforces security checks
-
validation: Validates input parameters and business logic constraints
-
state-changes: Updates contract state variables
-
events: Emits relevant events for state changes
-
errors: Throws custom errors for invalid conditions
-
reentrancy: Not protected by a reentrancy guard
-
access: Restricted to MULTISIG_MANAGER_ROLE
-
oracle: No oracle dependencies
function removeMultisigSigner(address signer) external onlyRole(MULTISIG_MANAGER_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
signer | address | Address of the signer to remove |
toggleEmergencyMode
Toggle emergency mode
Toggles emergency mode for emergency upgrades
Notes:
-
security: Validates input parameters and enforces security checks
-
validation: Validates input parameters and business logic constraints
-
state-changes: Updates contract state variables
-
events: Emits relevant events for state changes
-
errors: Throws custom errors for invalid conditions
-
reentrancy: Not protected by a reentrancy guard
-
access: Restricted to EMERGENCY_UPGRADER_ROLE
-
oracle: No oracle dependencies
function toggleEmergencyMode(bool enabled, string calldata reason) external onlyRole(EMERGENCY_UPGRADER_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
enabled | bool | Whether to enable emergency mode |
reason | string | Reason for the emergency mode change |
getPendingUpgrade
Get pending upgrade details
Returns pending upgrade details for a given implementation
Notes:
-
security: No security checks needed
-
validation: No validation needed
-
state-changes: No state changes
-
events: No events emitted
-
errors: No errors thrown
-
reentrancy: No reentrancy protection needed
-
access: No access restrictions
-
oracle: No oracle dependencies
function getPendingUpgrade(address implementation) external view returns (PendingUpgrade memory upgrade);
Parameters
| Name | Type | Description |
|---|---|---|
implementation | address | Address of the implementation |
Returns
| Name | Type | Description |
|---|---|---|
upgrade | PendingUpgrade | Pending upgrade details |
canExecuteUpgrade
Check if an upgrade can be executed
Checks if an upgrade can be executed based on timelock and approval requirements
Notes:
-
security: No security checks needed
-
validation: No validation needed
-
state-changes: No state changes
-
events: No events emitted
-
errors: No errors thrown
-
reentrancy: No reentrancy protection needed
-
access: No access restrictions
-
oracle: No oracle dependencies
function canExecuteUpgrade(address implementation) external view returns (bool canExecute);
Parameters
| Name | Type | Description |
|---|---|---|
implementation | address | Address of the implementation |
Returns
| Name | Type | Description |
|---|---|---|
canExecute | bool | Whether the upgrade can be executed |
hasUpgradeApproval
Get upgrade approval status for a signer
Returns whether a signer has approved a specific upgrade
Notes:
-
security: No security checks needed
-
validation: No validation needed
-
state-changes: No state changes
-
events: No events emitted
-
errors: No errors thrown
-
reentrancy: No reentrancy protection needed
-
access: No access restrictions
-
oracle: No oracle dependencies
function hasUpgradeApproval(address signer, address implementation) external view returns (bool approved);
Parameters
| Name | Type | Description |
|---|---|---|
signer | address | Address of the signer |
implementation | address | Address of the implementation |
Returns
| Name | Type | Description |
|---|---|---|
approved | bool | Whether the signer has approved the upgrade |
getMultisigSigners
Get all multi-sig signers
Returns array of all multi-sig signer addresses
Notes:
-
security: No security checks needed
-
validation: No validation needed
-
state-changes: No state changes
-
events: No events emitted
-
errors: No errors thrown
-
reentrancy: No reentrancy protection needed
-
access: No access restrictions
-
oracle: No oracle dependencies
function getMultisigSigners() external view returns (address[] memory signers);
Returns
| Name | Type | Description |
|---|---|---|
signers | address[] | Array of signer addresses |
currentTime
Returns protocol time from the shared TimeProvider
Exposes the canonical time source to dependent contracts (e.g. SecureUpgradeable)
Notes:
-
security: Returns canonical protocol time managed by immutable shared provider
-
validation: No input validation required
-
state-changes: None
-
events: None
-
errors: None
-
reentrancy: No external state mutation
-
access: Public view utility
-
oracle: No oracle dependencies
function currentTime() external view returns (uint256);
_clearUpgradeApprovals
Clear all approvals for an implementation
Clear all approvals for a specific implementation
Clears all approvals for a specific implementation
Resets approval counts for a pending upgrade and removes it from the ordered pending list
Notes:
-
security: No security checks needed
-
validation: No validation needed
-
state-changes: Updates contract state variables
-
events: No events emitted
-
errors: No errors thrown
-
reentrancy: No reentrancy protection needed
-
access: Internal function
-
oracle: No oracle dependencies
-
security: Internal helper used after execute/cancel paths; assumes caller already validated upgrade existence
-
validation: Assumes
implementationis currently tracked in_pendingUpgradesList -
state-changes: Sets
upgradeApprovalCount[implementation]to zero and clears all signer approvals -
events: No events emitted directly; caller is responsible for emitting high-level events
-
errors: None - function is best-effort cleanup
-
reentrancy: Not applicable - internal function with no external calls
-
access: Internal function only
-
oracle: No oracle dependencies
function _clearUpgradeApprovals(address implementation) internal;
Parameters
| Name | Type | Description |
|---|---|---|
implementation | address | Address of the implementation |
_removePendingUpgrade
Remove an implementation from the ordered list of pending upgrades
Uses swap-and-pop to maintain a compact array of pending upgrades for efficient iteration
Notes:
-
security: Internal helper; assumes caller has already validated that the upgrade is pending
-
validation: Performs a linear scan over
_pendingUpgradesListand stops at first match -
state-changes: Updates
_pendingUpgradesListby replacing the removed element with the last one and shrinking the array -
events: No events emitted directly; high-level events are emitted by caller functions
-
errors: None - function silently returns if no match is found
-
reentrancy: Not applicable - internal function with no external calls
-
access: Internal function only
-
oracle: No oracle dependencies
function _removePendingUpgrade(address implementation) internal;
Parameters
| Name | Type | Description |
|---|---|---|
implementation | address | Address of the implementation to remove from _pendingUpgradesList |
_clearSignerApprovals
Clear all approvals from a specific signer
Clears all approvals from a specific signer
Notes:
-
security: No security checks needed
-
validation: No validation needed
-
state-changes: Updates contract state variables
-
events: No events emitted
-
errors: No errors thrown
-
reentrancy: No reentrancy protection needed
-
access: Internal function
-
oracle: No oracle dependencies
function _clearSignerApprovals(address signer) internal;
Parameters
| Name | Type | Description |
|---|---|---|
signer | address | Address of the signer |
_addMultisigSigner
Add a multisig signer (internal)
Adds a multisig signer internally
Notes:
-
security: No security checks needed
-
validation: No validation needed
-
state-changes: Updates contract state variables
-
events: No events emitted
-
errors: No errors thrown
-
reentrancy: No reentrancy protection needed
-
access: Internal function
-
oracle: No oracle dependencies
function _addMultisigSigner(address signer) internal;
Parameters
| Name | Type | Description |
|---|---|---|
signer | address | Address of the signer |
pause
Pause the timelock contract
Pauses the timelock contract
Notes:
-
security: Validates input parameters and enforces security checks
-
validation: Validates input parameters and business logic constraints
-
state-changes: Updates contract state variables
-
events: Emits relevant events for state changes
-
errors: Throws custom errors for invalid conditions
-
reentrancy: Not protected by a reentrancy guard
-
access: Restricted to DEFAULT_ADMIN_ROLE
-
oracle: No oracle dependencies
function pause() external onlyRole(DEFAULT_ADMIN_ROLE);
unpause
Unpause the timelock contract
Unpauses the timelock contract
Notes:
-
security: Validates input parameters and enforces security checks
-
validation: Validates input parameters and business logic constraints
-
state-changes: Updates contract state variables
-
events: Emits relevant events for state changes
-
errors: Throws custom errors for invalid conditions
-
reentrancy: Not protected by a reentrancy guard
-
access: Restricted to DEFAULT_ADMIN_ROLE
-
oracle: No oracle dependencies
function unpause() external onlyRole(DEFAULT_ADMIN_ROLE);
constructor
Constructor for TimelockUpgradeable contract
Sets up the time provider and disables initializers for security
Notes:
-
security: Validates input parameters and enforces security checks
-
validation: Validates input parameters and business logic constraints
-
state-changes: Disables initializers
-
events: No events emitted
-
errors: Throws custom errors for invalid conditions
-
reentrancy: No reentrancy protection needed
-
access: No access restrictions
-
oracle: No oracle dependencies
constructor(TimeProvider _TIME_PROVIDER) ;
Parameters
| Name | Type | Description |
|---|---|---|
_TIME_PROVIDER | TimeProvider | TimeProvider contract for centralized time management |
Events
UpgradeProposed
event UpgradeProposed(
address indexed implementation,
uint256 proposedAt,
uint256 executableAt,
string description,
address indexed proposer
);
UpgradeApproved
event UpgradeApproved(address indexed implementation, address indexed signer, uint256 approvalCount);
UpgradeExecuted
event UpgradeExecuted(address indexed implementation, address indexed executor, uint256 executedAt);
UpgradeCancelled
event UpgradeCancelled(address indexed implementation, address indexed canceller);
MultisigSignerAdded
event MultisigSignerAdded(address indexed signer);
MultisigSignerRemoved
event MultisigSignerRemoved(address indexed signer);
EmergencyModeToggled
event EmergencyModeToggled(bool enabled, string reason);
Structs
PendingUpgrade
struct PendingUpgrade {
address implementation;
address proposingProxy; // HIGH-1: proxy contract that initiated this upgrade proposal
uint256 proposedAt;
uint256 executableAt;
uint256 expiryAt; // LOW-6: proposal expires after MAX_PROPOSAL_AGE to prevent stale execution
string description;
bool isEmergency;
address proposer;
}