Skip to content

feat(registry): admin-gated emergency pause circuit breaker - #406

Merged
Cjay-Cyber-2 merged 5 commits into
ASTROIDX556:mainfrom
Oluwaferanmi18:add-emergency-pause
Oct 7, 2026
Merged

Cjay-Cyber-2 merged 5 commits into
ASTROIDX556:mainfrom
Oluwaferanmi18:add-emergency-pause

Conversation

@Oluwaferanmi18

Copy link
Copy Markdown
Contributor

Closes #335

Summary

Adds a protocol-wide emergency circuit breaker to astroid-registry. A protocol
administrator can now freeze all registry mutations during an incident with a single
call, without touching individual organizations, and release it just as cleanly.

This is deliberately distinct from the existing org-scoped freeze:

freeze(org) pause() (new)
Scope one organization whole registry / protocol
Caller org owner or admin admin only
Effect blocks writes for that org blocks every state-mutating entrypoint
Read paths still available still available

What changes

contracts/registry/src/lib.rs

  • New instance storage key DataKey::Paused (initialized to false in initialize).
  • New admin-gated entrypoints pause / unpause, idempotency-checked: pausing an
    already-paused registry (or unpausing a live one) fails with InvalidState rather than
    silently no-op'ing, so an operator always knows the breaker's state.
  • Internal paused() reader and require_not_paused() guard, which short-circuits
    with the dedicated paused code before any state is touched.
  • Guards added to every state-mutating entrypoint: register_org, set_org_owner,
    register_module, deprecate_module, reactivate_module, remove_module, grant_role,
    register_version, set_admin, add_approved_wasm, remove_approved_wasm,
    set_upgrade_authority, and upgrade.
  • is_wasm_approved reports false while paused, so every member contract's
    cross-contract upgrade gate fails closed and no code is swapped during an incident. The
    stored approvals are untouched, so unpausing restores exactly the prior approval set.
  • Exposes is_paused() through RegistryInterface so consumers observe the state
    through the generated RegistryClient.
  • Revocation stays open on purpose: revoke_role remains callable so an owner can always
    withdraw a compromised key's delegation during an incident.

interfaces/src/lib.rs

  • RegistryInterface::is_paused() — the cross-contract read surface.

shared/src/errors.rs

  • Error::RegistryPaused, an alias for the registry halt slot (code 30), plus a
    regression test pinning it. The protocol error table already sits at Stellar's hard limit
    of 50 ScSpecUdtUnionCaseV0 cases, so the registry's single halt code serves both the
    organization freeze and the admin pause rather than adding a 51st variant.

shared/src/events.rs

  • ContractEvent::RegistryPaused { paused } standard event.

Admin authorization

pause/unpause are gated by the existing require_admin helper, which performs a
verified caller-authorization check (caller.require_auth()) and compares the caller
against the stored admin from instance storage — the same verified-signature discipline used
by the other admin entrypoints, and consistent with env.current_contract_address()-style
trust of the stored protocol admin.

Tests

  • contracts/registry/src/test.rs — unit coverage: admin-only gating, idempotency checks,
    every mutating entrypoint rejected while paused, reads still available, revocation still
    available, unpause restores operation, standard events emitted, upgrade freeze, and the
    error-code pin.
  • tests/src/registry_pause.rs — a cross-contract integration suite that deploys a
    consumer contract and observes the pause through the shared RegistryClient
    (astroid-interfaces), asserting modifications are rejected while paused and succeed again
    after unpausing.
  • Registry test_snapshots/* regenerated to reflect the new Paused instance key written
    by initialize.

Validation

cargo test -p astroid-registry     # 61 passed
cargo test --workspace             # all crates green (~620 tests)

Checklist

  • Storage keys + admin checks for pause state added
  • Key operational entrypoints guarded with pause verification
  • Integration tests confirm paused registries reject modifications
  • cargo test -p astroid-registry and workspace tests pass

Oluwaferanmi18 and others added 2 commits September 26, 2026 11:33
ASTROIDX556#303)

Add `ProposalState` and `ProposalInterface` to `astroid-interfaces` so the
proposal, multisig and wallet contracts share one canonical lifecycle
vocabulary and one status-query ABI instead of drifting copies.

- Hoist `ProposalState` (with its `has_executed` / `deposit_settled` /
  `is_live` / `is_terminal` helpers) into `interfaces/src/proposal.rs`.
  The proposal contract re-exports it, so there is a single source of
  truth for the on-chain `u32` discriminants and existing callers are
  unchanged.
- Add the `ProposalInterface` `#[contractclient]` for the fallible
  status queries (`state`, `is_expired`, `is_executed`, `dependencies`,
  `dependencies_met`, `can_execute`), returning the canonical `Error`.
- Move the proposal contract's status views into the trait impl so its
  entrypoints are compile-time-checked against the shared client.
- Add serialization/deserialization and discriminant-stability unit
  tests, plus a runtime interface-compliance test.

Closes ASTROIDX556#303

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
…OIDX556#335)

Introduce a protocol-wide circuit breaker for the registry so an incident
can be contained without freezing individual organizations. When engaged:

- Every state-mutating entrypoint (org/module registration, ownership and
  role administration, version records, WASM approvals and the
  registry-gated upgrade path) short-circuits with the designated paused
  error code before touching state.
- `is_wasm_approved` reports no hash as approved, so member contracts'
  cross-contract upgrade gates fail closed and no code is swapped.
- Read-only lookups and role revocation stay available: operators can
  still inspect protocol state, and an owner can always withdraw access.

`pause`/`unpause` are admin-gated (verified caller auth against the stored
admin) and idempotency-checked so the breaker's state is never ambiguous.
The pause is deliberately distinct from the org-scoped `freeze`, and its
code aliases the registry halt slot (30) because the protocol error table
is already at Stellar's 50-union-case limit.

Adds unit coverage in the registry crate plus a cross-contract integration
suite (`tests/src/registry_pause.rs`) that observes the pause through the
shared `RegistryClient` and asserts modifications are rejected while
paused, then succeed again after unpausing.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@Cjay-Cyber-2

Copy link
Copy Markdown
Contributor

CI checks needed

No checks are visible on this commit. Please make sure this PR triggers the repository's CI and resolve any workflow problems. DripTide will keep watching and review the change when checks pass.

@Cjay-Cyber-2

Copy link
Copy Markdown
Contributor

Merge Conflict — Action Needed

This pull request has merge conflicts with the base branch (main).

What to do: update your branch by merging or rebasing against main, resolve any conflicts locally, and push the result.

@Cjay-Cyber-2

Copy link
Copy Markdown
Contributor

CI checks needed

No checks are visible on this commit. Please make sure this PR triggers the repository's CI and resolve any workflow problems. DripTide will keep watching and review the change when checks pass.

@Cjay-Cyber-2

Copy link
Copy Markdown
Contributor

Merge Conflict — Action Needed

This pull request has merge conflicts with the base branch (main).

What to do: update your branch by merging or rebasing against main, resolve any conflicts locally, and push the result.

@Cjay-Cyber-2

Copy link
Copy Markdown
Contributor

CI checks needed

No checks are visible on this commit. Please make sure this PR triggers the repository's CI and resolve any workflow problems. DripTide will keep watching and review the change when checks pass.

@Cjay-Cyber-2

Copy link
Copy Markdown
Contributor

Merge Conflict — Action Needed

This pull request has merge conflicts with the base branch (main).

What to do: update your branch by merging or rebasing against main, resolve any conflicts locally, and push the result.

@Cjay-Cyber-2

Copy link
Copy Markdown
Contributor

CI checks needed

No checks are visible on this commit. Please make sure this PR triggers the repository's CI and resolve any workflow problems. DripTide will keep watching and review the change when checks pass.

@Cjay-Cyber-2

Copy link
Copy Markdown
Contributor

Merge Conflict — Action Needed

This pull request has merge conflicts with the base branch (main).

What to do: update your branch by merging or rebasing against main, resolve any conflicts locally, and push the result.

@Cjay-Cyber-2

Copy link
Copy Markdown
Contributor

CI Checks Failed — Action Needed

The conflict resolution is mergeable, but the latest CI run failed. Please fix the failing jobs and push an update before merge.

  • build: FAILURE
  • fmt: FAILURE
  • clippy: FAILURE

I will not merge this PR while CI is failing.

Rebases the ASTROIDX556#335 circuit breaker onto latest upstream/main:
- register_version now carries the validated wasm_hash/VersionRecord flow
  with both pause and freeze guards
- pause layer (Paused key, pause/unpause, require_not_paused, is_paused,
  fail-closed is_wasm_approved, RegistryPaused alias/event) re-applied
  onto the new registry base
- shared/errors, interfaces, and integration tests aligned with upstream

Closes ASTROIDX556#335
@Cjay-Cyber-2
Cjay-Cyber-2 merged commit 900375d into ASTROIDX556:main Oct 7, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add emergency pause and unpause controls to astroid-registry

2 participants