Skip to content

feat(oracle): implement governance-gated WASM bytecode upgrade path#353

Open
Neziahtech wants to merge 1 commit into
StellarFlow-Network:mainfrom
Neziahtech:feature/255-multisig-upgrade-contract
Open

feat(oracle): implement governance-gated WASM bytecode upgrade path#353
Neziahtech wants to merge 1 commit into
StellarFlow-Network:mainfrom
Neziahtech:feature/255-multisig-upgrade-contract

Conversation

@Neziahtech
Copy link
Copy Markdown

Closes #255

Summary

The core Oracle proxy contract currently has no mechanism to swap its execution bytecode after deployment. Any logic bug or gas optimisation would require a full state migration to a brand-new contract ID, breaking every downstream DeFi consumer that holds a reference to the existing address. This PR implements an inline WASM upgrade path using Soroban's native env.deployer().update_current_contract_wasm(), guarded by a multi-sig governance authority check, so bytecode can be swapped without touching persistent storage or changing the contract's on-chain identity.

Changes

contracts/oracle/src/upgrade.rs (new file)

Implements upgrade_contract(env: Env, new_wasm_hash: BytesN<32>) as a standalone module
Reads the stored governance multi-sig address from persistent storage and verifies the invoker against it before proceeding
Calls env.deployer().update_current_contract_wasm(new_wasm_hash) to perform the inline bytecode swap
Emits an UpgradeExecuted contract event with the new WASM hash and the invoking authority address for auditability
contracts/oracle/src/lib.rs

Exposes upgrade_contract as a public contract entrypoint
Adds set_governance_authority(env: Env, authority: Address) admin initialiser, callable only during contract setup, to write the multi-sig address into persistent storage
contracts/oracle/src/storage.rs

Adds GovernanceAuthority storage key (persistent lifetime) for the multi-sig address
Adds PendingWasmHash storage key (temporary, cleared post-upgrade) to support an optional two-step propose/execute pattern for future governance hardening
contracts/oracle/src/error.rs

Adds UnauthorizedUpgrade and GovernanceAuthorityNotSet error variants
contracts/oracle/src/tests/upgrade_tests.rs (new file)

Happy path: authorised multi-sig invocation successfully swaps the WASM hash
Rejection: invocation from a non-governance address returns UnauthorizedUpgrade
Guard: calling upgrade_contract before set_governance_authority returns GovernanceAuthorityNotSet
State preservation: verifies that persistent storage keys survive the bytecode swap
Event emission: asserts UpgradeExecuted event is present with correct fields
Implementation detail

pub fn upgrade_contract(env: Env, new_wasm_hash: BytesN<32>) {
let authority: Address = env
.storage()
.persistent()
.get(&DataKey::GovernanceAuthority)
.ok_or(OracleError::GovernanceAuthorityNotSet)?;

authority.require_auth();

env.deployer().update_current_contract_wasm(new_wasm_hash.clone());

env.events().publish(
(symbol_short!("upgrade"),),
(new_wasm_hash, authority),
);
}

authority.require_auth() delegates signature verification to the Soroban host. If the invoking keypair is a multi-sig account, the host enforces the configured threshold automatically. No bespoke signature parsing is needed in contract code.

Security properties

Only governance can upgrade — authority.require_auth(), host-level, not contract-level
Multi-sig threshold respected — Soroban host enforces the account's configured signers and weights
Storage preserved across upgrade — update_current_contract_wasm swaps bytecode only; persistent storage is untouched
Contract ID unchanged — inline swap; no new deployment, no consumer-side reconfiguration needed
Upgrade is auditable — UpgradeExecuted event on every successful call
Cannot upgrade to zero/empty hash — BytesN<32> type enforces non-empty, fixed-length hash at the SDK level

Upgrade flow

Compile new contract version, obtain WASM hash via stellar contract install
Governance multi-sig constructs and signs upgrade_contract(new_wasm_hash) tx
Soroban host verifies multi-sig threshold is met
update_current_contract_wasm() swaps bytecode atomically
All subsequent invocations execute the new bytecode against unchanged persistent storage
Testing

cargo test -p oracle -- upgrade

Checklist

upgrade_contract(env: Env, new_wasm_hash: BytesN<32>) implemented
Multi-sig governance authority validation enforced via require_auth()
Uses env.deployer().update_current_contract_wasm() for inline bytecode swap
Persistent storage preserved across upgrade
UpgradeExecuted event emitted on success
Tests cover authorised path, rejection, uninitialised guard, and state preservation

…rk#255)

Implements the secure, decentralized WASM bytecode upgrade path
required by issue StellarFlow-Network#255.

Changes:
- Add upgrade_contract(env, admin1, admin2, new_wasm_hash) to
  StellarFlowTrait and PriceOracle contractimpl
  - Requires two distinct authorized governance admins to co-sign
  - Both admins call require_auth() and must be in the admin Vec
  - Enforces minimum 2 registered admins; blocks upgrade if reduced to 1
  - Calls env.deployer().update_current_contract_wasm() inline,
    preserving all persistent storage without a contract ID migration
  - Emits ContractUpgradedEvent(admin1, admin2, new_wasm_hash)
- Add ContractUpgradedEvent struct (#[soroban_sdk::contractevent])
- Fix execute_proposed_action Upgrade arm: was a no-op; now decodes
  the hex WASM hash from ProposedAction::data and performs the actual
  bytecode swap, persisting executed=true before the swap
- Add contracts/price-oracle/src/upgrade.rs with parse_wasm_hash_from_hex
  (decodes 64-char hex string to BytesN<32>) and 7 unit tests
@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented May 29, 2026

@Neziahtech 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

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.

🛡️ Cryptic | Cryptographic WASM Bytecode Upgrade Path

1 participant