Problem (spike / open-ended)
None of the four contracts implement any contract-upgrade mechanism. Soroban supports contract upgrades via env.deployer().update_current_contract_wasm(new_wasm_hash), but no lib.rs in this repository calls it, exposes an admin-gated upgrade() function, or documents an upgrade/migration strategy anywhere (README, module doc comments, or stubs.rs backlog files — none of the four stubs.rs/backlog-style comment lists mention upgrade at all, in contrast to how thoroughly they list pause/admin-rotation/supply-cap ideas).
Why this is open-ended rather than a fixed-scope fix
Adding a bare upgrade(new_wasm_hash: BytesN<32>) admin-gated function is mechanically simple. The hard, genuinely unresolved questions are about migration, not upgrade mechanics: (1) If stellar_send's ContractConfig struct gains new fields in a future version (e.g. the active-field-is-dead-code fix, or two-step admin rotation, both discussed elsewhere in this batch, would plausibly add fields), does a naive Wasm swap correctly deserialize the old stored ContractConfig value against the new struct definition, or does it need an explicit migration step that reads the old shape and rewrites it? Soroban's XDR-based contract type serialization is generally forward-compatible for appended fields in some cases but this needs to be verified empirically per Soroban SDK version, not assumed. (2) Should upgrades be gated by a timelock (e.g. announce an upgrade, wait N ledgers, then execute) to give integrators/users advance notice and a chance to exit (relevant especially for token_bridge and escrow, which hold user funds in escrow/vault form across ledger closes) — a timelock is a meaningfully bigger scope than a bare upgrade function and touches the same two-step-admin-action pattern already flagged as a shared concern in issue #9. (3) Given four separate contracts that call into each other (stellar_send → fee_collector, per the fee-forwarding issues in this batch), does upgrading one contract's interface risk breaking a live cross-contract call from another that hasn't been upgraded in lockstep — and if so, does that argue for a shared interface/versioning contract, or simply careful, coordinated deployment runbooks?
Investigation steps
Research current Soroban SDK (21.x) guidance and known community patterns for contract upgrades and storage migration (this evolves with SDK versions, so needs a fresh look rather than relying on older Stellar/Soroban documentation), specifically around contracttype schema evolution safety, before committing to a specific upgrade-function design.
Testing strategy
Once a design direction is chosen: a test that deploys an initial contract, writes state, "upgrades" (in the test harness, this typically means re-registering the contract with new WASM against the same contract id) to a modified version of the same contract with an added ContractConfig field, and asserts the old state is still readable in the expected shape (or, if a migration step is required, that the migration function correctly backfills the new field to a sane default) — this test would be the concrete artifact proving whichever migration strategy is chosen actually works, rather than being assumed safe.
Problem (spike / open-ended)
None of the four contracts implement any contract-upgrade mechanism. Soroban supports contract upgrades via
env.deployer().update_current_contract_wasm(new_wasm_hash), but nolib.rsin this repository calls it, exposes an admin-gatedupgrade()function, or documents an upgrade/migration strategy anywhere (README, module doc comments, orstubs.rsbacklog files — none of the fourstubs.rs/backlog-style comment lists mention upgrade at all, in contrast to how thoroughly they list pause/admin-rotation/supply-cap ideas).Why this is open-ended rather than a fixed-scope fix
Adding a bare
upgrade(new_wasm_hash: BytesN<32>)admin-gated function is mechanically simple. The hard, genuinely unresolved questions are about migration, not upgrade mechanics: (1) Ifstellar_send'sContractConfigstruct gains new fields in a future version (e.g. theactive-field-is-dead-code fix, or two-step admin rotation, both discussed elsewhere in this batch, would plausibly add fields), does a naive Wasm swap correctly deserialize the old storedContractConfigvalue against the new struct definition, or does it need an explicit migration step that reads the old shape and rewrites it? Soroban's XDR-based contract type serialization is generally forward-compatible for appended fields in some cases but this needs to be verified empirically per Soroban SDK version, not assumed. (2) Should upgrades be gated by a timelock (e.g. announce an upgrade, wait N ledgers, then execute) to give integrators/users advance notice and a chance to exit (relevant especially fortoken_bridgeandescrow, which hold user funds in escrow/vault form across ledger closes) — a timelock is a meaningfully bigger scope than a bare upgrade function and touches the same two-step-admin-action pattern already flagged as a shared concern in issue #9. (3) Given four separate contracts that call into each other (stellar_send→fee_collector, per the fee-forwarding issues in this batch), does upgrading one contract's interface risk breaking a live cross-contract call from another that hasn't been upgraded in lockstep — and if so, does that argue for a shared interface/versioning contract, or simply careful, coordinated deployment runbooks?Investigation steps
Research current Soroban SDK (21.x) guidance and known community patterns for contract upgrades and storage migration (this evolves with SDK versions, so needs a fresh look rather than relying on older Stellar/Soroban documentation), specifically around
contracttypeschema evolution safety, before committing to a specific upgrade-function design.Testing strategy
Once a design direction is chosen: a test that deploys an initial contract, writes state, "upgrades" (in the test harness, this typically means re-registering the contract with new WASM against the same contract id) to a modified version of the same contract with an added
ContractConfigfield, and asserts the old state is still readable in the expected shape (or, if a migration step is required, that the migration function correctly backfills the new field to a sane default) — this test would be the concrete artifact proving whichever migration strategy is chosen actually works, rather than being assumed safe.