Skip to content

Research notes

Governance Timelocks: Passing Is Not Execution

A proposal-and-operation receipt separates vote success, queueing, readiness, execution and cancellation, with a checked 48-hour delay example.

An ivory envelope rests on a ceramic rail before a closed ivory shutter gate with a lime locking peg.
Illustration for research notes; examples in this article are educational.

A crypto governance timelock separates approval from execution. After a vote succeeds, an authorized path must queue the exact action, wait until its scheduled timestamp and submit a successful execution transaction. Passing the vote or reaching the delay does not itself change the target contract. Cancellation, failed calls and separate admin powers can alter what happens next.

Read two lifecycles together

A governance page may display a proposal as successful while the proposed fee, treasury payment or contract setting remains unchanged. The missing evidence is often the execution path, rather than the vote count.

This guide uses OpenZeppelin Contracts 5.x documentation and the explicit v5.4.0 reference contracts for an Ethereum Virtual Machine example. It does not assume every DAO uses that composition. A Governor without a timelock can have a different path; OpenZeppelin's governance walkthrough distinguishes immediate execution after success from the additional queue step when a timelock is configured.

Keep the Governor proposal and the controller operation on separate lines in your record. In the v5.4.0 GovernorTimelockControl extension, the proposal can remain Queued while the controller operation is either Waiting or Ready. Direct execution or cancellation at the controller also affects the proposal state returned by that extension. Proposal and operation identifiers are computed differently: obtain the operation identifier from the schedule evidence and verify its exact inputs, including the integration's salt. See the pinned Governor integration source.

ObservationWhat it establishesWhat still needs evidence
Proposal succeededThe configured voting decision passedWhether its actions were queued
Operation WaitingScheduling occurred; its time has not arrivedReadiness and a valid execution
Operation ReadyIts scheduled timestamp has arrivedSuccessful calls and any dependency
Operation DoneController execution completedExpected target effect and settlement
Operation UnsetNo current schedule exists for that identifierWhether it was never scheduled or was cancelled

These operation labels follow the OpenZeppelin timelock API. Its broader term Pending includes both Waiting and Ready. A dashboard saying “pending” therefore needs a definition before it can answer whether execution is allowed.

Whether the vote passed is a separate calculation. Use the quorum versus majority comparison to check that decision; a convincing majority does not supply a missing queue or execution transaction.

Start the delay at the recorded scheduling event

The voting deadline, a forum announcement and a queue transaction's submission time are different observations. For the reference controller, the stored ready time is the scheduling block's timestamp plus the chosen delay. Scheduling enforces that the chosen delay is at least the controller's current minimum. The v5.4.0 controller source supplies those checks.

Use the operation's stored timestamp, not a countdown reconstructed from the announcement. Readiness uses the observed block's timestamp, not your computer's clock. Record the chain, timelock address, operation identifier and observation block alongside it. A timestamp from another chain or another controller does not describe this action.

The minimum is also not necessarily the delay selected for every operation. OpenZeppelin's minimum-delay explanation allows longer schedules. In the pinned Governor integration used here, queueing selects the current minimum and uses no predecessor. Custom integrations may choose differently.

Follow a hypothetical parameter change

Suppose an imaginary protocol approves changing a fee from 20 to 25 basis points. Assume the Governor uses the reference integration, its timelock minimum is 48 hours at queueing, and the operation has no predecessor. The target grants the timelock the required permission. The times below are invented UTC block timestamps in 2026, not a live protocol's schedule or a promise of block production at those moments.

Time, UTCEventGovernor / operationTarget fee
28 Oct, 09:00Voting has ended and success is observedSucceeded / Unset20 bps
28 Oct, 12:20Queue transaction is includedQueued / Waiting20 bps
30 Oct, 12:19Observe one minute before readinessQueued / Waiting20 bps
30 Oct, 12:20Scheduled timestamp is reachedQueued / Ready20 bps
30 Oct, 12:21Execution attempt revertsQueued / Ready20 bps
30 Oct, 13:05Retry succeeds with the approved payloadExecuted / Done25 bps

Assume a temporary target-side condition caused the failed attempt and was resolved before the retry. No approved parameter was substituted. The example deliberately separates elapsed time from successful work.

The delay is 48 × 3,600 = 172,800 seconds. Adding that to 28 October at 12:20 gives 30 October at 12:20. The gap between observed success and queue inclusion is 3 hours 20 minutes, or 12,000 seconds. The eventual execution occurs another 45 minutes, or 2,700 seconds, after readiness.

The total from observed success to successful execution is therefore 12,000 + 172,800 + 2,700 = 187,500 seconds: 52 hours 5 minutes. Calling this merely a “48-hour process” would hide both the queueing gap and the later execution gap.

There is no transaction at 12:20 merely because the clock reached that time. A new read can return Ready without a new scheduling event. State-changing work still needs a transaction included on-chain, as explained in Ethereum's transaction documentation.

A failed attempt is a diagnosis, not completion

Before retrying, establish which check failed. An unfinished predecessor, lost target permission, changed target conditions or insufficient execution resources calls for a different response. Repeatedly increasing fees cannot repair the wrong payload or missing permission.

The reference controller checks readiness and any predecessor before making the target call. A reverted call prevents the operation from reaching Done. Its execution path also makes a batch atomic: failure does not leave earlier calls from that reverted batch committed. These requirements appear in the controller execution functions and batch-operation documentation.

Review the transaction outcome, operation state and target state together. For a parameter change, read the actual parameter. For a payment, reconcile the intended asset, recipient and amount. An event name or successful low-level call is incomplete evidence if the target has application-specific return values or indirect effects.

Then apply the relevant chain settlement check. The confirmations versus finality guide explains why inclusion and settlement belong in separate fields. A final execution record should identify the inclusion block and the finality evidence used, rather than preserve only a transaction hash.

Cancellation creates a different branch

In the pinned controller, an account with the canceller role can cancel an operation while it is Waiting or Ready. Cancellation removes the stored schedule and returns the operation to Unset. This does not reverse an already completed target change. See the controller's cancellation check.

Consider an alternative to the execution branch above: the operation is cancelled directly at the controller on 30 October at 12:25, while the fee is still 20 bps. The controller then reports Unset and the reference integration reports the proposal as Canceled. Do not interpret the controller's zero timestamp as “ready since the beginning of time”; it represents the absence of a current schedule.

At the controller layer, an authorized proposer could schedule the same payload, predecessor and salt again at 15:00 with a fresh 48-hour delay. Its new ready time would be 1 November at 15:00. In this direct-controller cancellation branch, the reference integration then reads Queued again while the operation is Waiting. This follows from its operation-state lookup; rescheduling does not establish a new vote.

Cancellation through a permitted Governor path can additionally set the proposal's own cancellation flag. A controller reschedule does not clear that flag, as shown in the pinned Governor cancellation source. Save which cancellation path was used, and reread both states before treating a reschedule as a restored proposal.

Check who can act outside the normal vote

A timelock only constrains actions routed through it. OpenZeppelin's governance setup warns that an extra proposer can schedule operations outside the Governor's voting process, while an extra canceller can obstruct approved operations. An open executor role permits callers to execute eligible scheduled payloads; it does not let them rewrite those payloads. These are distinct powers in the role-configuration guide.

Also inspect retained external role administrators. They may be able to appoint proposers or executors without a community vote. That does not by itself let an administrator skip the controller's time check. In the documented controller, changing the minimum delay requires a call by the timelock itself through its scheduling flow. A lower minimum for future schedules does not rewrite a pending operation's stored timestamp. OpenZeppelin's access-control guide and delay-change API explain these restrictions.

The more direct escape path may be at the target: a separate upgrader, owner, emergency module or permission manager. Trace that path with the upgradeable-contract authority worksheet. A normal governance route does not establish that every authorized route shares its delay.

Check expiry too. The reference OpenZeppelin controller has no automatic time-based expiry after readiness. By comparison, Compound's published Timelock source, inspected on 28 October 2026, requires execution by eta plus a 14-day grace period and restricts execution to its admin. That is a source-code comparison, not a statement of every Compound deployment's current parameters. Read the deployed implementation before assuming an old ready action remains executable or has expired.

Keep one receipt for the whole action

Use this worksheet when following a passed proposal. Preserve full addresses and exact payload bytes in the working record; a readable summary helps review but cannot replace them.

Receipt fieldEvidence to retain
DeploymentChain, Governor, controller, target and relevant code versions
ApprovalProposal identifier, voting outcome and observed block
Payload identityEvery target, value and decoded call; predecessor and salt; proposal-to-operation mapping
ScheduleQueue receipt, scheduling block timestamp, selected delay and stored ready time
Present eligibilityWaiting/Ready/Done/Unset, dependency, expiry rule and executor permissions
InterruptionCancellation event or failed receipt, reason and current target state
CompletionSuccessful receipt, expected target changes and settlement evidence
Other authorityAdditional proposers, cancellers, role admins and routes around the controller

For a payload that changes token units, retain the execution boundary with the old and new unit definitions. The redenomination and adjusted-chart guide explains why historical chart comparisons need that distinction.

Finish the receipt with the next unmet condition: no schedule, time not reached, dependency unfinished, execution failed, cancellation observed, or target change verified and awaiting settlement. That gives “the proposal passed” an operational answer: exactly what can happen next, under which authority, and what evidence will prove it happened.

Keep the context with the example.

Each worked example is illustrative, with its sources and assumptions in the article. Read our editorial standards for authorship, verification and corrections.