Skip to content

feat(admin): implement submit_upgrade_proposal for multi-sig WASM upgrades - #778

Open
odusanya03 wants to merge 6 commits into
BCPathway:mainfrom
odusanya03:Implement-submit_upgrade_proposal-function
Open

odusanya03 wants to merge 6 commits into
BCPathway:mainfrom
odusanya03:Implement-submit_upgrade_proposal-function

Conversation

@odusanya03

Copy link
Copy Markdown
Contributor

Closes #653

Summary

Implements submit_upgrade_proposal in the admin access-control module (contracts/admin), the core submission step of the Multi-Sig Gated WASM Upgrades epic. An authorized admin-pool member can now submit the 32-byte hash of a new WASM binary together with a description; the proposal is assigned a unique auto-incrementing ID and saved to storage, ready for the downstream approve_upgrade (#654) and execute_upgrade (#655) issues.

Changes

New entry points (contracts/admin/src/lib.rs)

  • submit_upgrade_proposal(env, creator, new_wasm_hash, description) -> Result<u64, AdminError>
    • Requires the creator's authorization (Address::require_auth) — holding pool membership alone is not enough.
    • Validates inputs with typed errors instead of panics:
      Condition Error
      Creator is the zero-address sentinel AdminError::InvalidAddress
      Creator not in the admin pool (explicit or fallback [admin] pool) AdminError::NotAdminPoolMember
      WASM hash is the all-zero 32-byte sentinel AdminError::InvalidWasmHash
      Description is empty AdminError::EmptyDescription
    • Assigns a unique proposal ID from a dedicated UpgradeProposalIdCounter (starts at 0, increments once per submission, never reused) so generic and upgrade proposal IDs never collide.
    • Saves the proposal to instance storage under AdminKey::UpgradeProposal(id) and extends the instance TTL.
    • Records the creator as the first approval, mirroring create_proposal semantics ([Core] Implement approve_upgrade function #654's approve_upgrade can build directly on this).
  • get_upgrade_proposal(env, proposal_id) -> Result<UpgradeProposal, AdminError> — typed getter that reports missing proposals via UpgradeProposalNotFound rather than panicking; gives [Core] Implement approve_upgrade function #654/[Core] Implement execute_upgrade function #655 a read API.

State

  • New #[contracttype] struct UpgradeProposal { creator, new_wasm_hash: BytesN<32>, description, approvals, executed }, stored under appended AdminKey::UpgradeProposal(u64) / AdminKey::UpgradeProposalIdCounter variants (append-only, preserving existing discriminants).
  • Deliberately a separate struct rather than extra fields on the existing Proposal: #[contracttype] structs encode field-by-name, so extending Proposal in place would break decoding of entries already persisted on-chain. This also matches how PR feat(admin): define MultiSig upgrade proposal state #777 scopes its state work.
  • Four ABI-stable AdminError variants appended (codes 7–10) per the module's append-only convention.

Eventing (contracts/admin/src/events.rs)

  • New emit_upgrade_proposal_submitted publishing topic upg_prop with data (proposal_id, creator, new_wasm_hash).

Docs

  • Module-level docs updated: storage-layout table, full AdminError code table (now includes previously undocumented codes 4–6), event table, and a new "WASM Upgrade Proposals" guarantees section.
  • docs/ACCESS_CONTROL.md: added the two operations to the protected-operations table and a "WASM upgrade proposals" section, per its "update the operation table in the same pull request" rule.

Tests

11 new unit tests in the admin crate (105 → 116), covering happy paths and every expected error state:

Test Asserts
assigns_unique_incrementing_ids IDs start at 0, increment monotonically, never collide
saves_proposal_to_storage Getter returns exact creator/hash/description, executed == false
auto_approves_creator Creator recorded as first approval (multi-member pool)
works_with_fallback_admin_pool Submission succeeds without explicit set_admin_pool
extends_ttl_across_ledger_advances Proposal still readable after +200 ledgers
emits_upg_prop_event Exact topic, emitter, and (id, creator, hash) payload
rejects_non_pool_member Err(NotAdminPoolMember), no state written
rejects_zero_address_creator Err(InvalidAddress)
rejects_zero_wasm_hash Err(InvalidWasmHash), rejected proposal consumes no ID
rejects_empty_description Err(EmptyDescription)
get_..._rejects_nonexistent Err(UpgradeProposalNotFound)

One test-suite quirk worth noting: env.events().all() reflects only the most recent contract invocation in soroban-sdk v22, so the event test uses init_storage (which emits nothing) before submitting — the same pattern the existing proposal tests use to "avoid extra event overhead".

Acceptance criteria checklist

  • Code compiles; Soroban/Rust best practices (typed errors over panics, append-only ABI-stable enums, TTL extension on read/write, doc comments matching repo conventions)
  • Unit tests cover happy paths and expected error states
  • CI-equivalent checks pass locally:
    • cargo fmt --all -- --check ✅
    • cargo clippy --all-targets --all-features -- -D warnings ✅
    • cargo test --all ✅ (116 admin tests green)
    • cargo build --workspace --exclude bc-forge-e2e-tests --target wasm32-unknown-unknown --release ✅

Out of scope (per the epic's issue breakdown)

@drips-wave

drips-wave Bot commented Aug 25, 2026

Copy link
Copy Markdown

@odusanya03 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! 🚀

Learn more about application limits

@p3ris0n

p3ris0n commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

fix conflicts @odusanya03

…rades

Resolves BCPathway#653 (epic: Multi-Sig Gated WASM Upgrades).

Adds submit_upgrade_proposal to the admin access-control module. An
authorized admin-pool member submits the 32-byte hash of a new WASM
binary together with a non-empty description; the proposal is assigned
a unique auto-incrementing ID from its own counter and saved to
instance storage under AdminKey::UpgradeProposal(id). The creator is
recorded as the first approval, mirroring create_proposal semantics.

- New UpgradeProposal contracttype stored separately from Proposal so
  the encoding of already-persisted generic proposals stays stable
  (contracttype structs encode fields by name).
- Appended AdminKey::UpgradeProposal(u64)/UpgradeProposalIdCounter and
  four ABI-stable AdminError variants: InvalidWasmHash (7),
  UpgradeProposalNotFound (8), NotAdminPoolMember (9),
  EmptyDescription (10). Input validation returns typed errors instead
  of panicking; the all-zero WASM-hash sentinel is rejected.
- Emits upg_prop (proposal_id, creator, new_wasm_hash) via
  emit_upgrade_proposal_submitted.
- Adds get_upgrade_proposal typed getter for downstream approve/
  execute issues (BCPathway#654/BCPathway#655).
- 11 new unit tests covering happy paths, unique IDs, creator auto-
  approval, fallback pool, TTL extension across ledger advances, event
  shape, and every error state.

cargo fmt/clippy -D warnings clean; cargo test --all green.
@p3ris0n
p3ris0n force-pushed the Implement-submit_upgrade_proposal-function branch from 6050aa9 to 1efbd1e Compare October 21, 2026 10:18

This branch has not been deployed

No deployments
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.

[Core] Implement submit_upgrade_proposal function

2 participants