feat(operator-rotation): add timelock and grace period to operator rotation - #27
Merged
Meshmulla merged 1 commit intoAug 18, 2026
Conversation
…tation
Adds a two-step propose/execute rotation flow with a configurable timelock
delay and grace period, plus a cancel path, so that operator changes (which
control sensitive operations) can no longer take effect instantly. This
gives observers a window to detect and react to a malicious or mistaken
rotation before it takes effect.
What changed (soroban/src/operator_rotation.rs):
- Add `RotationConfig { delay_secs, grace_period_secs }`, admin-configurable
via `set_rotation_config` / `get_rotation_config`, with sane defaults
(24h delay, 24h grace period) applied when unset.
- Add `RotationAction::Add(Address, String)` / `Remove(Address)` describing
the change a pending rotation will apply.
- Add `propose_rotation` (admin only) — stores a `PendingRotation` with
`executes_at = now + delay` and `expires_at = executes_at + grace_period`.
Only one rotation may be pending at a time. Validates the action up front
(non-empty name for Add, operator must be currently active for Remove).
- Add `execute_rotation` (admin only) — reverts with "rotation delay has
not elapsed" before `executes_at`, and "rotation grace period has
expired" after `expires_at`; otherwise applies the pending Add/Remove
and clears the pending state.
- Add `cancel_rotation` — callable by the admin or by any address holding
the `EmergencyPause` permission (via the existing `acl` module), so a
pending malicious rotation can be aborted even if the proposing admin
key is later found to be compromised.
- Add `get_pending_rotation` read view.
- Refactor `add_operator`/`remove_operator` into thin admin-check wrappers
around new `apply_add_operator`/`apply_remove_operator` internals (same
bodies, unchanged behavior) so `execute_rotation` can reuse the mutation
logic without re-invoking `require_auth()` on the same address within one
call frame.
- Events are published for propose (`rot_prop`), execute (`rot_exec`), and
cancel (`rot_cncl`), each carrying the full pending-rotation record.
Testing performed:
- 18 new unit tests in `operator_rotation.rs`'s own test module covering:
default/custom config, invalid config rejection, propose validation
(empty name, unknown/inactive operator for Remove, already-pending
rejection), execute before delay / after grace period reverting, execute
succeeding for both Add and Remove after the delay, cancel by admin and
by an EmergencyPause-permitted address, cancel by an unauthorized caller
reverting, cancel-then-repropose, and event emission across all three
actions.
- 9 new integration tests extending `soroban/tests/operator_rotation.test.rs`
exercising the same flows through the crate's public API.
- Full existing suite passes unchanged (525 -> 543 lib tests, 14 -> 23
operator_rotation integration tests), confirming `add_operator`/
`remove_operator`'s existing behavior and all other tests are unaffected.
- `cargo fmt --all -- --check`, `cargo clippy --all-targets --all-features
-- -D warnings`, `cargo build --release --target wasm32-unknown-unknown`,
and the full `cargo test` workspace run all pass.
Closes stellar-kracken#8
4 tasks
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.
Summary
Closes #8
Adds a two-step propose/execute rotation flow with a configurable timelock delay and grace period, plus a cancel path, to
soroban/src/operator_rotation.rs. Operator rotation controls who can perform sensitive operations, and until nowadd_operator/remove_operatortook effect instantly — this change gives observers a window to detect and react to a malicious or mistaken rotation before it takes effect, as requested in #8.RotationConfig { delay_secs, grace_period_secs }, admin-configurable viaset_rotation_config/get_rotation_config, with sane defaults (24h delay, 24h grace period) applied when the admin hasn't configured one.propose_rotation(admin only) stores aPendingRotation(RotationAction::Add(Address, String)orRemove(Address)) withexecutes_at = now + delay_secsandexpires_at = executes_at + grace_period_secs. Only one rotation may be pending at a time, and the action is validated up front (non-empty name for Add; operator must currently be active for Remove).execute_rotation(admin only) reverts with"rotation delay has not elapsed"beforeexecutes_atand"rotation grace period has expired"afterexpires_at(a stale rotation must be cancelled and re-proposed); otherwise it applies the pending Add/Remove and clears the pending state.cancel_rotationis callable by the admin or by any address holding theEmergencyPausepermission via the existingaclmodule — so a pending malicious rotation can be aborted even if the admin key that proposed it is later found to be compromised.get_pending_rotationread view.add_operator/remove_operatorare refactored into thin admin-check wrappers around newapply_add_operator/apply_remove_operatorinternals (identical bodies, unchanged behavior) soexecute_rotationcan reuse the mutation logic without re-invokingrequire_auth()on the same address within one call frame (Soroban rejects a repeatedrequire_auth()in the same frame).rot_prop), execute (rot_exec), and cancel (rot_cncl), each carrying the full pending-rotation record for observers to monitor.Acceptance criteria
Testing / validation performed
operator_rotation.rs's own test module: default/custom config, invalid config rejection, propose validation (empty name, unknown/inactive operator for Remove, already-pending rejection), execute before delay / after grace period reverting, execute succeeding for both Add and Remove after the delay, cancel by admin and by anEmergencyPause-permitted address, cancel by an unauthorized caller reverting, cancel-then-repropose, and event emission across all three actions.soroban/tests/operator_rotation.test.rs(per the issue's suggestion), exercising the same flows through the crate's public API.add_operator/remove_operator's existing behavior and every other test are unaffected by the refactor.cargo fmt --all -- --check— clean.cargo clippy --all-targets --all-features -- -D warnings— clean, zero warnings.cargo build --release --target wasm32-unknown-unknown— builds successfully.cargo test(full workspace) — all green.Issue: #8