Quantillon Protocol

SecureUpgradeable

Git Source

Inherits: UUPSUpgradeable, AccessControlUpgradeable

Title: SecureUpgradeable

Author: Quantillon Labs - Nicolas Bellengé - @chewbaccoin

Secure base contract for upgradeable contracts with timelock protection

Replaces UUPSUpgradeable with timelock and multi-sig requirements

Note: security-contact: team@quantillon.money

State Variables

UPGRADER_ROLE

Role for upgrade operations

bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE")

timelock

Timelock contract for secure upgrades

ITimelockUpgradeable public timelock

secureUpgradesEnabled

Whether the contract is using secure upgrades

bool public secureUpgradesEnabled

EMERGENCY_DISABLE_DELAY

INFO-4: Minimum delay before a proposed emergency-disable takes effect (24h)

uint256 public constant EMERGENCY_DISABLE_DELAY = 24 hours

EMERGENCY_DISABLE_DELAY_BLOCKS

INFO-4: Canonical block delay for emergency-disable proposals (12s block target)

uint256 public constant EMERGENCY_DISABLE_DELAY_BLOCKS = EMERGENCY_DISABLE_DELAY / 12

EMERGENCY_DISABLE_QUORUM

Emergency-disable approvals required before apply can succeed

uint256 public constant EMERGENCY_DISABLE_QUORUM = 2

emergencyDisablePendingAt

INFO-4: Block at which emergencyDisable can be applied (0 = no pending proposal)

uint256 public emergencyDisablePendingAt

EMERGENCY_DISABLE_STORAGE_SLOT

Unstructured storage slot to avoid shifting child storage layouts.

bytes32 private constant EMERGENCY_DISABLE_STORAGE_SLOT =
    keccak256("quantillon.secure-upgradeable.emergency-disable.storage.v1")

Functions

_emergencyDisableStorage

function _emergencyDisableStorage() private pure returns (EmergencyDisableStorage storage ds);

onlyTimelock

modifier onlyTimelock() ;

_onlyTimelock

Reverts if caller is not the timelock contract

Used by onlyTimelock modifier; ensures upgrade execution comes from timelock only

Notes:

  • security: Access control for upgrade execution

  • validation: Timelock must be set and msg.sender must equal timelock

  • state-changes: None

  • events: None

  • errors: NotAuthorized if timelock zero or caller not timelock

  • reentrancy: No external calls

  • access: Internal; used by modifier

  • oracle: None

function _onlyTimelock() internal view;

_protocolTime

Returns canonical protocol time from timelock's shared TimeProvider

Falls back to block timestamp only before timelock is configured.

Notes:

  • security: Uses timelock-backed canonical time when available; fallback preserves liveness during bootstrap

  • validation: Validates timelock address and code presence before external call

  • state-changes: None

  • events: None

  • errors: None - failures fall back to block.timestamp

  • reentrancy: Read-only helper; no state mutation

  • access: Internal helper

  • oracle: No oracle dependencies

function _protocolTime() internal view returns (uint256);

__SecureUpgradeable_init

Initializes the SecureUpgradeable contract

Sets up the secure upgrade system with timelock protection

Notes:

  • security: Validates timelock address and initializes secure upgrade system

  • validation: Validates _timelock is not address(0)

  • state-changes: Initializes timelock, enables secure upgrades, sets up access control

  • events: Emits TimelockSet and SecureUpgradesToggled events

  • errors: Throws "SecureUpgradeable: Invalid timelock" if _timelock is address(0)

  • reentrancy: Protected by onlyInitializing modifier

  • access: Internal function - only callable during initialization

  • oracle: No oracle dependencies

function __SecureUpgradeable_init(address _timelock) internal onlyInitializing;

Parameters

NameTypeDescription
_timelockaddressAddress of the timelock contract

setTimelock

Set the timelock contract

Configures the timelock contract for secure upgrade management

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: Protected by reentrancy guard

  • access: Restricted to authorized roles

  • oracle: Requires fresh oracle price data

function setTimelock(address _timelock) external onlyRole(DEFAULT_ADMIN_ROLE);

Parameters

NameTypeDescription
_timelockaddressAddress of the timelock contract

toggleSecureUpgrades

Toggle secure upgrades

Enables or disables the secure upgrade mechanism

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: Protected by reentrancy guard

  • access: Restricted to authorized roles

  • oracle: Requires fresh oracle price data

function toggleSecureUpgrades(bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE);

Parameters

NameTypeDescription
enabledboolWhether to enable secure upgrades

proposeUpgrade

Propose an upgrade through the timelock

Initiates a secure upgrade proposal with timelock delay 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: Protected by reentrancy guard

  • access: Restricted to authorized roles

  • oracle: Requires fresh oracle price data

function proposeUpgrade(address newImplementation, string calldata description, uint256 customDelay)
    external
    onlyRole(UPGRADER_ROLE);

Parameters

NameTypeDescription
newImplementationaddressAddress of the new implementation
descriptionstringDescription of the upgrade
customDelayuint256Optional custom delay

executeUpgrade

Execute an upgrade through the timelock

Executes a previously proposed upgrade after timelock delay

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: Protected by reentrancy guard

  • access: Restricted to authorized roles

  • oracle: Requires fresh oracle price data

function executeUpgrade(address newImplementation) external onlyTimelock;

Parameters

NameTypeDescription
newImplementationaddressAddress of the new implementation

emergencyUpgrade

Emergency upgrade (bypasses timelock, requires emergency mode)

Allows emergency upgrades when secure upgrades are disabled or timelock is unavailable

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: Protected by reentrancy guard

  • access: Restricted to authorized roles

  • oracle: Requires fresh oracle price data

function emergencyUpgrade(address newImplementation, string calldata description) external onlyRole(UPGRADER_ROLE);

Parameters

NameTypeDescription
newImplementationaddressAddress of the new implementation
descriptionstringDescription of the emergency upgrade

_authorizeUpgrade

Authorize upgrade (overrides UUPSUpgradeable)

Internal function that determines upgrade authorization based on secure upgrade settings

function _authorizeUpgrade(address newImplementation) internal view override;

Parameters

NameTypeDescription
newImplementationaddressAddress of the new implementation

isUpgradePending

Check if an upgrade is pending

Checks if there is a pending upgrade for the specified implementation

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: Protected by reentrancy guard

  • access: Restricted to authorized roles

  • oracle: Requires fresh oracle price data

function isUpgradePending(address implementation) external view returns (bool isPending);

Parameters

NameTypeDescription
implementationaddressAddress of the implementation

Returns

NameTypeDescription
isPendingboolWhether the upgrade is pending

getPendingUpgrade

Get pending upgrade details

Returns detailed information about a pending upgrade

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: Protected by reentrancy guard

  • access: Restricted to authorized roles

  • oracle: Requires fresh oracle price data

function getPendingUpgrade(address implementation)
    external
    view
    returns (ITimelockUpgradeable.PendingUpgrade memory upgrade);

Parameters

NameTypeDescription
implementationaddressAddress of the implementation

Returns

NameTypeDescription
upgradeITimelockUpgradeable.PendingUpgradePending upgrade details

canExecuteUpgrade

Check if an upgrade can be executed

Checks if a pending upgrade has passed the timelock delay and can be executed

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: Protected by reentrancy guard

  • access: Restricted to authorized roles

  • oracle: Requires fresh oracle price data

function canExecuteUpgrade(address implementation) external view returns (bool canExecute);

Parameters

NameTypeDescription
implementationaddressAddress of the implementation

Returns

NameTypeDescription
canExecuteboolWhether the upgrade can be executed

getUpgradeSecurityStatus

Get upgrade security status

Returns the current security configuration for 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: Protected by reentrancy guard

  • access: Restricted to authorized roles

  • oracle: Requires fresh oracle price data

function getUpgradeSecurityStatus()
    external
    view
    returns (address timelockAddress, bool secureUpgradesEnabled_, bool hasTimelock);

Returns

NameTypeDescription
timelockAddressaddressAddress of the timelock contract
secureUpgradesEnabled_boolWhether secure upgrades are enabled
hasTimelockboolWhether timelock is set

proposeEmergencyDisableSecureUpgrades

Disable secure upgrades in emergency

INFO-4: Propose disabling secure upgrades; enforces a 24-hour timelock

Disables secure upgrades for emergency situations

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: Protected by reentrancy guard

  • access: Restricted to authorized roles

  • oracle: Requires fresh oracle price data

function proposeEmergencyDisableSecureUpgrades() external onlyRole(DEFAULT_ADMIN_ROLE);

approveEmergencyDisableSecureUpgrades

INFO-4/NEW-3: Register an admin approval for the active emergency-disable proposal.

Records an approval from a DEFAULT_ADMIN_ROLE address for the current proposal. Uses per-proposal bitmap to prevent duplicate approvals from the same address.

Notes:

  • security: Only callable by DEFAULT_ADMIN_ROLE; prevents double-approval per admin.

  • validation: Reverts if no active proposal or caller already approved.

  • state-changes: Marks caller as approved and increments approvalCount in storage.

  • events: Emits EmergencyDisableApproved with updated approval count.

  • errors: NotActive if no pending proposal; NoChangeDetected if already approved.

  • reentrancy: Not applicable – function is external but has no external calls after state changes.

  • access: Restricted to DEFAULT_ADMIN_ROLE.

  • oracle: No oracle dependencies.

function approveEmergencyDisableSecureUpgrades() external onlyRole(DEFAULT_ADMIN_ROLE);

applyEmergencyDisableSecureUpgrades

INFO-4: Apply a previously proposed emergency-disable after the timelock has elapsed.

Disables secure upgrades permanently for this deployment once quorum and delay are satisfied. Resets pending state so a fresh proposal is required for any future changes.

Notes:

  • security: Requires DEFAULT_ADMIN_ROLE, quorum approvals and elapsed delay.

  • validation: Reverts on mismatched proposal id, missing quorum or no pending proposal.

  • state-changes: Clears emergencyDisablePendingAt and approvalCount, sets secureUpgradesEnabled=false.

  • events: Emits SecureUpgradesToggled(false) on successful application.

  • errors: NotActive if no pending or delay not elapsed; NotAuthorized on id mismatch or quorum not met.

  • reentrancy: Not applicable – no external calls after critical state changes.

  • access: Restricted to DEFAULT_ADMIN_ROLE.

  • oracle: No oracle dependencies.

function applyEmergencyDisableSecureUpgrades(uint256 expectedProposalId) external onlyRole(DEFAULT_ADMIN_ROLE);

Parameters

NameTypeDescription
expectedProposalIduint256Proposal id the caller expects to apply (replay/mismatch protection).

emergencyDisableProposalId

Returns the current emergency-disable proposal id.

Value is 0 when no proposal has ever been created.

Notes:

  • security: View-only helper; no access restriction.

  • validation: No input validation required.

  • state-changes: None – pure read from dedicated emergency-disable storage.

  • events: None.

  • errors: None.

  • reentrancy: Not applicable – view function.

  • access: Public – any caller may inspect current proposal id.

  • oracle: No oracle dependencies.

function emergencyDisableProposalId() public view returns (uint256);

Returns

NameTypeDescription
<none>uint256proposalId The active or last-used emergency-disable proposal id.

emergencyDisableApprovalCount

Returns the current approval count for the active emergency-disable proposal.

Reads the aggregate number of admin approvals recorded for the latest proposal.

Notes:

  • security: View-only helper; no access restriction.

  • validation: No input validation required.

  • state-changes: None – pure read from dedicated emergency-disable storage.

  • events: None.

  • errors: None.

  • reentrancy: Not applicable – view function.

  • access: Public – any caller may inspect approval count.

  • oracle: No oracle dependencies.

function emergencyDisableApprovalCount() public view returns (uint256);

Returns

NameTypeDescription
<none>uint256approvalCount Number of approvals for the current proposal.

emergencyDisableQuorum

Returns the quorum required to apply the emergency disable.

Exposes the EMERGENCY_DISABLE_QUORUM compile-time constant.

Notes:

  • security: View-only helper; no access restriction.

  • validation: No input validation required.

  • state-changes: None – pure return of constant.

  • events: None.

  • errors: None.

  • reentrancy: Not applicable – pure function.

  • access: Public – any caller may inspect required quorum.

  • oracle: No oracle dependencies.

function emergencyDisableQuorum() public pure returns (uint256);

Returns

NameTypeDescription
<none>uint256quorum Number of approvals required to apply emergency-disable.

hasEmergencyDisableApproval

Returns whether a given approver address approved a specific emergency-disable proposal.

Returns false when approver is zero or proposalId is zero for safety.

Notes:

  • security: View-only helper; no access restriction.

  • validation: Treats zero proposalId or zero approver as “not approved”.

  • state-changes: None – pure read from dedicated emergency-disable storage.

  • events: None.

  • errors: None.

  • reentrancy: Not applicable – view function.

  • access: Public – any caller may inspect approval status.

  • oracle: No oracle dependencies.

function hasEmergencyDisableApproval(uint256 proposalId, address approver) public view returns (bool hasApproved_);

Parameters

NameTypeDescription
proposalIduint256The proposal identifier to inspect.
approveraddressThe admin address whose approval status is queried.

Returns

NameTypeDescription
hasApproved_boolTrue if the approver has recorded an approval for proposalId.

enableSecureUpgrades

Enable secure upgrades after emergency

Re-enables secure upgrades after emergency situations

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: Protected by reentrancy guard

  • access: Restricted to authorized roles

  • oracle: Requires fresh oracle price data

function enableSecureUpgrades() external onlyRole(DEFAULT_ADMIN_ROLE);

Events

TimelockSet

event TimelockSet(address indexed timelock);

SecureUpgradesToggled

event SecureUpgradesToggled(bool enabled);

SecureUpgradeAuthorized

event SecureUpgradeAuthorized(address indexed newImplementation, address indexed authorizedBy, string description);

EmergencyDisableProposed

INFO-4: Emitted when an emergency-disable proposal is created

event EmergencyDisableProposed(uint256 indexed proposalId, uint256 activatesAt);

EmergencyDisableApproved

event EmergencyDisableApproved(uint256 indexed proposalId, address indexed approver, uint256 approvalCount);

Structs

EmergencyDisableStorage

struct EmergencyDisableStorage {
    uint256 proposalId;
    uint256 approvalCount;
    mapping(uint256 => mapping(address => bool)) hasApproved;
}