Skip to content

feat(timelock): permissionless execution - #302

Merged
thedavidmeister merged 5 commits into
mainfrom
feat/20260812-permissionless-timelock-execution
Aug 12, 2026
Merged

feat(timelock): permissionless execution#302
thedavidmeister merged 5 commits into
mainfrom
feat/20260812-permissionless-timelock-execution

Conversation

@hardyjosh

@hardyjosh hardyjosh commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Clearstar want a 48h lock with permissionless execution. EXECUTOR_ROLE is now granted to address(0) at construction, which OZ's onlyRoleOrOpenRole reads as "open to everyone": once the delay has run anyone may execute, so the operator cannot censor a matured operation. The Safe keeps PROPOSER and CANCELLER and executes as a member of the public rather than by privilege.

cancel() has no open-role path in OZ, so vetoing stays privileged — that asymmetry is the design. No separate canceller for now; TIMELOCK_CANCELLER stays a placeholder and the Safe cancels.

This moves every address

Executors are a constructor argument, so the CREATE2 address changes on all three chains. The permissionless timelocks are deployed and live on Base, Ethereum and HyperEVM, and this PR records their pins — testPinsMatchDerivedAddresses ties each pin to the derivation. The superseded timelocks hold no power — nothing was migrated to them, so they are abandoned in place.

Invariant inverted

assertTimelockState previously rejected an open executor. It now requires one, and the test that pinned the old policy is inverted to reject a closed executor — so silently re-closing execution fails rather than passing.

The property is also proven behaviourally, not just as a role bit: a roleless anon executes a matured operation end-to-end (and its effect lands), and the same anon can neither schedule nor cancel — the open/privileged asymmetry as tests.

Hazard recorded

OZ operations never expire. With open execution an abandoned operation stays executable by anyone indefinitely, so an operation you decide against must be cancelled, not merely left unexecuted. Written into the runbook.

Local: build, fmt, 18/18 timelock lib, 13/13 script suites, 6/6 forcing function — all three chains.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@thedavidmeister, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 6 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7e070da0-9700-4bf5-b72c-b8de0c6cdd96

📥 Commits

Reviewing files that changed from the base of the PR and between a881d0b and 001f815.

📒 Files selected for processing (3)
  • docs/TIMELOCK.md
  • src/lib/LibTimelockInvariants.sol
  • test/src/lib/LibTimelockInvariants.t.sol

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Josh Hardy added 3 commits August 12, 2026 14:34
Execution is permissionless, so the Safe never holds EXECUTOR_ROLE and
revoking it from the Safe is a no-op rather than a detectable break. The
executor requirement is pinned against address(0) by
testAssertRejectsClosedExecutorRole instead.
@hardyjosh
hardyjosh force-pushed the feat/20260812-permissionless-timelock-execution branch from 01762e7 to 4e84176 Compare August 12, 2026 14:37
@thedavidmeister
thedavidmeister changed the base branch from feat/20260729-timelock-beacon-ownership to main August 12, 2026 15:03
The open-executor property was pinned only as a role bit
(hasRole(EXECUTOR_ROLE, address(0))), relying on OZ's onlyRoleOrOpenRole
semantics transitively. Prove it end-to-end: a roleless caller executes a
matured operation and its effect lands, and the same caller can neither
schedule nor cancel — the privileged/open asymmetry the design claims.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thedavidmeister

Copy link
Copy Markdown
Contributor

Reviewed 001f815: KEEP — permissionless execution done the OZ-sanctioned way (executors = [address(0)] at construction, onlyRoleOrOpenRole open role), invariant correctly inverted (open executor REQUIRED, closed executor rejected by test; proposer/canceller/root-admin still closed), all three new CREATE2 pins verified live on-chain with the OZ runtime code, superseded timelocks abandoned clean (nothing was ever migrated to them), and the property is now proven behaviourally: a roleless anon executes a matured operation end-to-end and the same anon can neither schedule nor cancel. Full fork suite green at this head, including on the restack merge commit.

Rulings-conformance: checked against the repo CLAUDE.md rulings and the rulings stated for this work —

  • Governance section (timelock is the only admin path; timelockForChainId is the sanctioned resolver): obeyed; pins updated in LibTimelockInvariants and every consumer resolves through timelockForChainId — no hardcoded call sites.
  • via_ir stays OFF (Try --via-ir compilation to reduce StoxReceiptVault size #144) / single-profile codehash pinning: obeyed; foundry.toml untouched, frozen OZ creation code and runtime codehash pins unchanged (only constructor args differ).
  • Pragma convention: obeyed; no pragma changes.
  • Versioning: no deployed ST0x contract bytecode changes (the timelock is upstream OZ, not in LibProdDeploy pins); no snapshot or CHANGELOG entry owed; git-clean CI green.
  • No open roles ruling (docs): superseded by this PR's design and the doc now states the new invariant exactly — one open role (executor), everything else closed, with assertTimelockState enforcing both directions and tests pinning each.
  • Rulings stated for this work: the review finding that anons-can-execute lacked a behavioural test is fixed with direct tests (execute succeeds, schedule/cancel revert); the stale no-open-roles doc bullet and the false 'pins are unset' PR-body paragraph are corrected; restack was merge-based per the no-history-rewrite rule; merge is a true merge per the Graphite-stack ruling; branch retained.

@thedavidmeister

Copy link
Copy Markdown
Contributor

Reviewed 001f815: KEEP — permissionless execution done the OZ-sanctioned way (executors = [address(0)] at construction, onlyRoleOrOpenRole open role), invariant correctly inverted (open executor REQUIRED, closed executor rejected by test; proposer/canceller/root-admin still closed), all three new CREATE2 pins verified live on-chain with the OZ runtime code, superseded timelocks abandoned clean (nothing was ever migrated to them), and the property is now proven behaviourally: a roleless anon executes a matured operation end-to-end and the same anon can neither schedule nor cancel. Full fork suite green at this head, including on the restack merge commit.

Rulings-conformance: checked against the repo CLAUDE.md rulings and the rulings stated for this work —

  • Governance section (timelock is the only admin path; timelockForChainId is the sanctioned resolver): obeyed; pins updated in LibTimelockInvariants and every consumer resolves through timelockForChainId — no hardcoded call sites.
  • via_ir stays OFF (Try --via-ir compilation to reduce StoxReceiptVault size #144) / single-profile codehash pinning: obeyed; foundry.toml untouched, frozen OZ creation code and runtime codehash pins unchanged (only constructor args differ).
  • Pragma convention: obeyed; no pragma changes.
  • Versioning: no deployed ST0x contract bytecode changes (the timelock is upstream OZ, not in LibProdDeploy pins); no snapshot or CHANGELOG entry owed; git-clean CI green.
  • No open roles ruling (docs): superseded by this PR's design and the doc now states the new invariant exactly — one open role (executor), everything else closed, with assertTimelockState enforcing both directions and tests pinning each.
  • Rulings stated for this work: the review finding that anons-can-execute lacked a behavioural test is fixed with direct tests (execute succeeds, schedule/cancel revert); the stale no-open-roles doc bullet and the false 'pins are unset' PR-body paragraph are corrected; restack was merge-based per the no-history-rewrite rule; merge is a true merge per the Graphite-stack ruling; branch retained.

@thedavidmeister
thedavidmeister merged commit cd03f7b into main Aug 12, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants