Skip to main content

Upgrade Governance & Timelock

Overview

The grainlify-core contract implements a timelocked multisig upgrade governance system. All WASM upgrades must pass through a proposal → approval → timelock → execution pipeline. The timelock delay prevents immediate execution after threshold approval, giving stakeholders time to review and react.

Timelock Constants

ConstantValueDescription
DEFAULT_TIMELOCK_DELAY86 400 s (24 h)Applied when no custom delay is set
MIN_TIMELOCK_DELAY3 600 s (1 h)Minimum allowed delay
MAX_TIMELOCK_DELAY2 592 000 s (30 d)Maximum allowed delay

Upgrade Flow

1. propose_upgrade(proposer, wasm_hash) → proposal_id
2. approve_upgrade(signer, proposal_id) → starts timelock when threshold met
3. [wait timelock_delay seconds]
4. execute_upgrade(proposal_id) → installs new WASM

Entrypoints

FunctionAuthDescription
propose_upgrade(proposer, wasm_hash)signerCreate upgrade proposal
approve_upgrade(signer, proposal_id)signerApprove; starts timelock at threshold
execute_upgrade(proposal_id)anyExecute after delay elapsed
cancel_upgrade(caller, proposal_id)admin/proposerCancel proposal
set_timelock_delay(delay_seconds)adminUpdate delay (1 h – 30 d)
get_timelock_delay()viewCurrent delay in seconds
get_timelock_status(proposal_id)viewRemaining seconds (0 = ready)

Boundary Enforcement

Minimum (1 hour)

set_timelock_delay panics with "Timelock delay must be at least 1 hour (3600 seconds)" for any value < 3 600.

This prevents an admin from setting a trivially short delay that would allow near-instant upgrades, defeating the purpose of the timelock.

Maximum (30 days)

set_timelock_delay panics with "Timelock delay cannot exceed 30 days (2592000 seconds)" for any value > 2 592 000.

This prevents an admin from accidentally (or maliciously) setting a delay so long that the upgrade path becomes permanently bricked.

Execute-upgrade timing

execute_upgrade panics with "Timelock delay not met: X seconds remaining" if called before timelock_start + timelock_delay seconds have elapsed.

Security Assumptions

  1. Admin key security — only the admin can change the timelock delay. A compromised admin key can reduce the delay to the minimum (1 h) but cannot bypass it entirely.
  2. Timelock start is immutable — once the approval threshold is met, the timelock start timestamp is written to storage and cannot be changed.
  3. No bypass — there is no emergency override that skips the timelock. Use cancel_upgrade + re-propose if an urgent fix is needed.
  4. Monotonic clock — the contract uses env.ledger().timestamp() which is set by validators and cannot be manipulated by the contract caller.

Boundary Test Coverage (issue #1293)

ScenarioExpected
set_timelock_delay(3600)✅ succeeds
set_timelock_delay(3599)❌ panics
set_timelock_delay(0)❌ panics
set_timelock_delay(2592000)✅ succeeds
set_timelock_delay(2592001)❌ panics
set_timelock_delay(u64::MAX)❌ panics
execute_upgrade at t < expiry❌ panics
execute_upgrade at t = expiry✅ succeeds
execute_upgrade at t > expiry✅ succeeds