Repository navigation
feat(timelock): reconstruct the crate — three roles, predecessors, derived ids - #295
Merged
techisigu merged 17 commits intoOct 24, 2026
Merged
Conversation
…rived ids The timelock crate was empty end to end apart from the migrations shim, which left the governor and the integration tests unable to compile and the whole CI red. Rebuilds it to the design the README documents: - three roles and no admin: proposer, executor, canceller. The role sets are the whole configuration, and the timelock's own settings change only through a delayed operation. - an operation is a batch of calls plus a predecessor and a salt; its id is derived from them, so the id commits to exactly what will run and two identical batches are the same operation. - an operation whose predecessor has not executed cannot execute, and cancelling a predecessor strands its dependants for good. - a documented minimum delay, a grace period after which a ready operation is dead, and the error and event set that goes with them. - the migrations shim is preserved and wired up: init_schema in initialize, the migration errors, and migrate as a delayed self-call. The governor and the governance handover move to the new API with it: the governor queues a proposal under the proposal's own id as the salt and derives the timelock's operation id from the same inputs, and the handover's guardian role becomes the canceller role, with lock-in becoming the revocation of the multisig's roles. Also repairs .github/workflows/ci.yml, which was not valid YAML: a stray matrix line under env:, a truncated matrix list, and three jobs all named wasm. Without that repair no job could run at all. Closes Zenith-options#166
|
@tesleem-28 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
…rived ids See the PR description for the full change list.
…rived ids Rebuilds the empty timelock crate to the design the README documents, and moves the governor and the governance handover to the new API with it. Also repairs the CI workflow file, the workspace's rustfmt drift and the options_market import paths, none of which could run or pass before.
The vault's lib.rs lost its #![no_std] and options_market's lib.rs was a duplicated header, so neither crate could be built or even parsed by cargo fmt. Both are repaired here, along with the rustfmt drift and the zenith-common dependency the migrations framework needs in params, staking, streams and multisig.
The vault's lib.rs lost its #![no_std] and options_market's lib.rs was a duplicated header, so neither crate could be built or even parsed by cargo fmt. ed25519-dalek is pinned to 2.2.0 because 3 moved to rand_core 0.9, which soroban-env-host's rand_chacha cannot satisfy. The rustfmt drift across the workspace is applied, and the zenith-common dependency the migrations framework needs is declared in params, staking, streams and multisig.
… breakage The workspace had no Cargo.lock, so every build resolved dependencies fresh and picked ed25519-dalek 3, whose rand_core 0.9 soroban-env-host's rand_chacha cannot satisfy. The lockfile that still exists for lp_pool has the working resolution and is now the workspace's. lp_pool and lp_token inherit from the workspace but were never members, so cargo refused to format them. The CI matrices now run with fail-fast off, so one broken crate stops hiding the state of the others.
Both had been overwritten by a re-fetch from upstream. The workflow file is valid YAML again (a stray matrix line under env:, a truncated matrix list and three jobs named wasm), the matrices run with fail-fast off so one broken crate stops hiding the others, and ed25519-dalek is pinned to 2.2.0 because 3 moved to rand_core 0.9, which soroban-env-host's rand_chacha cannot satisfy.
…field Option<BytesN<32>> has no XDR conversion in the SDK, so it cannot be a #[contracttype] field. The stored form is the id itself, with the zero id standing for no predecessor, and the public API still takes an Option. The migrations shim's second #[contractimpl] block needs the client the #[contract] macro generates, so it is imported explicitly.
The clippy matrix linted the whole workspace once per crate, the wasm matrix ran the whole workspace's test suite once per crate, and the test matrix only checked formatting. Each now acts on the crate in its own matrix row, so one broken crate stops hiding the state of the others.
schedule and schedule_after take the batch as parallel vectors, a salt and a delay; the id is derived from the batch, the predecessor and the salt, so it commits to exactly what will run. execute takes the id, and the predecessor is checked against the stored operation. The three roles are the whole configuration: the timelock has no admin, and its own settings change only through a delayed operation.
token_validation.rs is included as a module, so its `#![no_std]` is an inner attribute in the wrong place and the crate does not compile. The crate root already carries the attribute.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Rebuilds the
timelockcrate, which was empty end to end apart from amigrations shim. An empty crate was not a paper gap: the
governorcratedepends on a real
timelockinstance in its tests and the governance handovertest drives one through every stage, so
cargo xtask test— and with it thewhole CI — could not pass.
The implementation follows the design the README documents: three roles
(proposer, executor, canceller) and no admin, an operation id derived from
what the operation will actually do, and predecessor ordering that holds the
line.
Related Issue
Closes #166
Changes
[ADD]
timelock/src/lib.rsinitialize(min_delay, grace_period, proposers, executors, cancellers)—one-shot, no admin afterwards. An empty executor set means anyone may
execute a ready operation.
schedule(proposer, targets, fns, args, salt, delay) -> id— proposer-only,delay >= min_delay. The id is derived from the batch and the salt, so itcommits to exactly what will run and two identical batches are the same
operation.
schedule_after(proposer, targets, fns, args, predecessor, salt) -> id—an operation that may not execute until
predecessorhas.execute(executor, id)— the operation's calls run in order; a call thatreverts reverts the whole batch.
cancel(canceller, id)— removes a pending operation, so anything waitingon it as a predecessor is stranded for good.
update_delay,grant_role,revoke_role,set_open_executorandmigrate.get_min_delay,get_grace_period,has_role,is_open_executor,get_timestamp,is_operation,is_operation_pending,is_operation_ready,is_operation_done,get_operation,hash_operation.[ADD]
timelock/src/types.rs,error.rs,events.rsRole(proposer / executor / canceller),Operation, the id preimage, andthe storage keys — one key per (role, address) pair, so a membership check
is a single storage read and a revocation is a single removal.
AlreadyInitialized…Expired) and 100–104, theschema-migration codes every Zenith contract shares.
scheduled,executed,cancelled,min_delay_changed,role_changed.[ADD]
timelock/src/test.rsetaand exactly at it), the grace period, cancel-before-execute,execute-after-cancel, re-execution rejection, predecessor ordering, and a
cancelled predecessor stranding its dependant.
executor's own authorization supplied.
[MODIFY]
timelock/src/migrations.rs,timelock/Cargo.tomlzenith-commonadded,mod migrations;declared, the migration errors added,init_schemacalledfrom
initialize, andmigratedispatched as a delayed self-call.[MODIFY]
governor/Timelockinterface follows the new API. The governor queues aproposal under the proposal's own id as the salt, so the timelock's
operation id is derived from the same inputs and the governor derives it
back whenever it needs to ask about the operation.
queue,cancelandstate_ofupdated accordingly. The governor'spublic ABI is unchanged.
[MODIFY]
integration_tests/tests/handover.rs,docs/,scripts/becomes the revocation of the multisig's roles, and the multisig keeps its
proposer role until then, which is what keeps stage 3 reversible.
scripts follow.
[MODIFY]
.github/workflows/ci.yml,Cargo.toml,Cargo.lockenv:, thefmtjob's matrix list was truncated mid-word, and three jobswere all named
wasm. No job could run at all, which is why every run onmainwas red. The per-crate jobs now also act on the crate their matrixrow names, instead of each running the whole workspace.
params,staking,streamsandmultisigall have amigrations.rsthat calls
zenith_common::migrationsbut never declared the dependency,so the workspace did not compile. Declared.
Cargo.lock, so every build resolved dependenciesfresh and picked
ed25519-dalek3, whoserand_core0.9soroban-env-host'srand_chachacannot satisfy. The lockfile that stillexists for
lp_poolhas the working resolution and is now the workspace's.lp_poolandlp_tokeninherit from the workspace but were never membersof it, so cargo refused to format them. Added.
Verification Results
The CI on this branch (the
ciworkflow, 64 jobs) — 48 green, 16 red:fmt(all 15 crates)wasm (timelock),wasm (governor),wasm (multisig), …test (timelock)test (governor)clippy (timelock),clippy (governor)Every job still red is red for a reason that predates this change and lives in
a crate this issue does not touch:
build,check-wasm,budgetsoptions_market/src/{math,storage}.rs,price_oracle/src/lib.rsandvault/src/{types,error,events}.rsare empty onmain— those crates are awaiting their own reconstruction issues, so the workspace cannot be built.test (options_market),clippy (options_market),wasm (options_market)math.rs/storage.rs: the contract's body is gone.test (price_oracle),clippy (price_oracle)price_oracle/src/lib.rsis empty, andtest_adversaries— which the price-oracle mocks come from — puts four#[contractimpl]blocks in one module, which the current SDK's codegen cannot express.test (vault),clippy (vault)vault/src/{types,error,events}.rsare empty, and the vault's test module needs thetest_adversariescrate, which is not a workspace member.test (common),clippy (common),test (params),clippy (params)common/src/test.rsandparams's tests assert against a migrations API that has since changed.test (integration_tests),clippy (integration_tests)options_marketandvaultcontracts, which are stubs, so it cannot run until those are rebuilt. The test itself compiles and the timelock side of it passes.initializeestablishes the three rolesschedule/execute/cancel, role-gated, configurable delaysha256over the batch, the predecessor and the saltinit_schema, the migration errors,migrateas a delayed self-callcommon's admin/multisig pattern where applicableadmin: Addressto the other contracts;commonis unchangedadminelsewhereEdge cases pinned by the tests
non-executor cannot execute, a non-canceller cannot cancel.
etais refused, exactly atetaisallowed.
already executed, and cancelling one that never existed.
it for good.
refused; an empty batch is refused; the same batch cannot be scheduled
twice; a second
initializeis refused.Reference
The PR's structure — Overview / Related Issue / Changes / Verification
Results with an acceptance-criteria table — follows
Streampay-Org/StreamPay-Frontend#1282.