Skip to content

security: harden upgrade path with two-step WASM hash verification - #79

Merged
arisu6804 merged 1 commit into
YieldVault-Org:mainfrom
Arome8240:security/harden-upgrade-wasm-hash-verification
Aug 27, 2026
Merged

security: harden upgrade path with two-step WASM hash verification#79
arisu6804 merged 1 commit into
YieldVault-Org:mainfrom
Arome8240:security/harden-upgrade-wasm-hash-verification

Conversation

@Arome8240

Copy link
Copy Markdown
Contributor

Summary

Closes #78

Hardens the contract upgrade path with a two-step, admin-authorized WASM hash verification gate. An upgrade now requires the admin to first stage an approved hash on-chain; only a call that presents the exact same hash can then apply the upgrade. Any mismatch is rejected atomically — no Wasm swap, no storage mutation, no event.


Trust model

The upgrade authority is the configured vault admin (the same account that controls pause, yield accrual, and admin rotation). Governance can transfer that role via set_admin before initiating an upgrade.

The two-step flow is:

  1. Admin calls set_expected_wasm_hash(hash) — records the approved artifact on-chain (instance storage).
  2. Admin calls upgrade(hash) — verifies hash == stored expected hash before invoking update_current_contract_wasm. On success the staged entry is cleared and an auditable event is emitted.

This means a typo, a supply-chain substitution, or a race-condition attempt with a different hash is always rejected before any state is touched.


Changes

src/error.rs

  • Added WasmHashMismatch = 10 — stable numeric code for artifact rejection.

src/types.rs

  • Added DataKey::ExpectedWasmHash — instance-storage key for the staged approved hash.

src/storage.rs

  • Added get/set/clear_expected_wasm_hash helpers. The clear fires only on a successful upgrade; a mismatch leaves the staged value intact so the admin can retry with the correct artifact.

src/lib.rs

  • Added set_expected_wasm_hash(expected_hash) entrypoint (admin-only, auth-guarded) to stage the approved hash before an upgrade.
  • Rewrote upgrade(): checks staged hash == supplied hash before calling the deployer; returns WasmHashMismatch otherwise. Clears the staged entry and emits the upgrade event only on success.

src/events.rs

  • upgrade event now includes admin as a second topic so indexers can attribute every upgrade to an authority without scanning the data payload.

Artifact verification

WASM hash verification works at the Soroban protocol layer: update_current_contract_wasm accepts only a hash that corresponds to a code entry already uploaded to the ledger. The ExpectedWasmHash staging adds a second, application-layer gate — both must pass for the upgrade to proceed.


Compatibility impact

The upgrade entrypoint signature is unchanged (BytesN<32>Result<(), Error>). Callers must now call set_expected_wasm_hash first; an upgrade attempt without a staged hash returns WasmHashMismatch (code 10). The new entrypoint and error code are additive.

The upgrade event gains a second topic (admin address). Off-chain indexers that subscribed to (Symbol("upgrade"),) should update their filter to (Symbol("upgrade"), admin).


Test evidence

Six new tests, all passing (cargo test — 52 passed, 0 failed, 3 pre-existing ignores untouched):

Test What it proves
test_upgrade_requires_expected_hash_to_be_staged No staged hash → WasmHashMismatch
test_upgrade_mismatch_is_rejected Wrong hash → WasmHashMismatch
test_upgrade_state_preserved_on_mismatch All vault state + staged hash intact after rejection; correct hash succeeds afterwards
test_upgrade_succeeds_with_matching_hash Happy path completes without error
test_set_expected_wasm_hash_without_auth_fails Unauthorized staging is denied
test_upgrade_event_payload Event has two topics (upgrade, admin) with hash as data (previously ignored, now active)

Existing test_upgrade_without_auth_fails continues to pass. No unrelated tests were modified or skipped.

- Add Error::WasmHashMismatch (code 10) for atomic rejection of
  unrecognized artifacts
- Add DataKey::ExpectedWasmHash instance-storage entry so the admin
  can stage an approved hash before applying an upgrade
- Add storage::set/get/clear_expected_wasm_hash helpers
- Add set_expected_wasm_hash entrypoint (admin-only) to record the
  approved artifact on-chain
- Rewrite upgrade() to compare new_wasm_hash against the staged value
  before calling update_current_contract_wasm; any mismatch returns
  WasmHashMismatch and the transaction rolls back atomically without
  touching Wasm, storage, or events
- Add admin address to upgrade event topics for indexer attribution
- Add six new tests covering: no-staged-hash rejection, hash mismatch
  rejection, state preservation after mismatch, happy-path upgrade,
  unauthorized set_expected_wasm_hash, and event payload shape
- Unblock and fix previously ignored test_upgrade_event_payload

Closes YieldVault-Org#78
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.

security(contract): harden upgrade authorization and wasm hash verification

2 participants