This document describes how to upgrade StellopayCore contracts and migrate data safely, including procedures, rollback, and data compatibility.
- Overview
- Prerequisites
- Migration Procedure
- Data Compatibility
- Rollback Procedures
- Testing Migration Scenarios
- Multisig-Upgrade Flow
StellopayCore uses Soroban upgradeable contracts. Upgrading replaces the contract’s WASM code while keeping the same contract ID and existing persistent storage. Only the contract owner (or the multisig, when configured) can authorize an upgrade.
- Contracts that are upgradeable:
stello_pay_contract(main payroll), and optionally others that use the same pattern. - Scripts: All migration scripts live under
scripts/migrations/. See scripts/migrations/README.md for a short usage guide.
- Rust and
wasm32-unknown-unknowntarget. - Stellar CLI (
stellarorsoroban) for build, install, and invoke. - Environment variables (or equivalent):
CONTRACT_ID– existing payroll contract instance to upgrade.SOURCE_ACCOUNT– owner secret key (or identity) for authorization and upgrade.NETWORK– e.g.testnet,mainnet,futurenet.RPC_URL– Soroban RPC URL (e.g.https://soroban-testnet.stellar.org).
Follow these steps in order. Run all scripts from the repository root unless stated otherwise.
Export state and metadata so you can verify post-upgrade and roll back if needed.
export CONTRACT_ID="C..." # Your payroll contract ID
export NETWORK=testnet
export RPC_URL="https://soroban-testnet.stellar.org"
export BACKUP_DIR="./backups/$(date +%Y%m%d_%H%M%S)"
./scripts/migrations/01_backup_state.sh- Important: Record the current WASM hash for this contract (from deploy records or network) and store it with the backup. You need it for rollback.
Build the new contract and install it on the network to obtain the new WASM hash.
export SOURCE_ACCOUNT="S..." # Owner key (needed for install)
./scripts/migrations/02_build_and_install_wasm.sh- The script writes
NEW_WASM_HASHtoscripts/migrations/.env.migration. Do not commit this file.
Using the owner account, invoke the contract’s upgrade function with the new WASM hash. The contract enforces owner auth and then calls deployer().update_current_contract_wasm(new_wasm_hash).
source ./scripts/migrations/.env.migration
./scripts/migrations/03_authorize_and_upgrade.sh "$NEW_WASM_HASH"- If your CLI uses different invoke syntax (e.g.
--arg), adjust the script or run the equivalentupgrade(new_wasm_hash)invoke manually.
Run basic checks to ensure the contract is invokable and behavior is correct.
./scripts/migrations/04_verify_post_upgrade.sh- Manually confirm owner, active agreements, and at least one critical path (e.g. a read or a small disburse) if possible.
- Upgrades do not clear or migrate storage. Existing persistent and instance storage keys are left unchanged.
- Compatible upgrades are those that do not change the meaning or layout of existing storage keys. New code must continue to read/write the same keys and types as before (or only add new keys).
The main contract uses two key namespaces:
StorageKey(e.g. instorage.rs):Owner,Agreement(u128),AgreementEmployees(u128),NextAgreementId,EmployerAgreements(Address),DisputeStatus(u128),DisputeRaisedAt(u128),Arbiter.DataKey: agreement/employee counts, employee addresses, salaries, claimed periods, activation time, period duration, token, paid amount, escrow balance.
Compatibility rules:
- Do not remove or rename existing enum variants or change the type of values stored under existing keys.
- Adding new
StorageKeyorDataKeyvariants and writing new keys is safe. - Changing the Rust type (or Soroban contract type) of an existing key can break reading existing data; treat such changes as breaking and consider a one-off migration (e.g. a contract function that rewrites data once, guarded by a “migrated” flag).
- Contract addresses (e.g. payroll, escrow, payment history, multisig) do not change on upgrade of a single contract. Only the code (WASM) of the upgraded contract changes.
- Ensure new code remains compatible with any external contracts that call this one or rely on its event shapes.
- If you add a version or migration field in storage, document it in the release notes and in this doc.
- Event schemas: adding new event topics/fields is backward compatible; changing or removing existing ones can break indexers and integrations.
If an upgrade causes incorrect behavior or failures:
-
Stop using the upgraded contract for critical operations if possible.
-
Locate the last known-good WASM hash (from backup or deploy records).
-
Run the rollback script with that hash (owner must sign):
export CONTRACT_ID="C..." export SOURCE_ACCOUNT="S..." ./scripts/migrations/rollback.sh "<PREVIOUS_WASM_HASH>"
-
Verify with
04_verify_post_upgrade.shand manual checks. -
Investigate the failed upgrade (tests, staging, storage layout) before re-attempting.
Rollback is simply another upgrade to the previous WASM; no separate “downgrade” RPC exists. Storage is unchanged by both upgrade and rollback.
-
Upgrade and data persistence:
onchain/contracts/stello_pay_contract/src/tests/test_upgrade.rsincludes tests that upgrade the mock contract and assert that agreement, employee, balance, and settings data persist. -
Run before and after changing contract code or migration scripts:
cd onchain/contracts/stello_pay_contract cargo test
- Deploy the current production version (or current testnet version) and record its WASM hash.
- Create agreements, employees, and balances; optionally process a few claims.
- Run 01_backup_state.sh (and any CLI export you use) and store the backup.
- Build and install the new WASM with 02_build_and_install_wasm.sh.
- Run 03_authorize_and_upgrade.sh with the new hash.
- Run 04_verify_post_upgrade.sh and verify:
- Owner unchanged.
- Agreements and employees readable and consistent with backup.
- One or more critical flows (e.g. claim, disburse) work.
- Optionally run rollback.sh with the old hash, then verify again and re-upgrade to confirm the rollback path.
- Existing CI builds and runs tests for the payroll contract. Extend CI as needed to run migration-related tests (e.g. upgrade + persistence) on every change.
When upgrades are gated by the multisig contract:
- Propose: A signer proposes a
ContractUpgrade(payroll_contract_id, new_wasm_hash)operation viapropose_operation. - Approve: Other signers call
approve_operationuntil the threshold is met (or the emergency guardian executes). - Execute: Once the operation is executed on-chain, off-chain tooling (or the owner) must perform the actual upgrade by invoking the payroll contract’s
upgrade(new_wasm_hash)with the owner account (or as required by the contract). The multisig does not replace the need for the contract’s own upgrade authorization; it only records approval for the hash. - Verify: Run 04_verify_post_upgrade.sh and your usual checks after the upgrade.
See multisig.md for multisig API and workflow details.
| Step | Script | Purpose |
|---|---|---|
| 1 | 01_backup_state.sh |
Backup state and record current WASM hash |
| 2 | 02_build_and_install_wasm.sh |
Build new WASM, install on network, get new hash |
| 3 | 03_authorize_and_upgrade.sh |
Owner authorizes and runs upgrade (same contract ID) |
| 4 | 04_verify_post_upgrade.sh |
Basic post-upgrade verification |
| Rollback | rollback.sh |
Upgrade back to a previous WASM hash |
- Data compatibility: Preserve existing storage key layout and types across upgrades; document any one-off migrations.
- Rollback: Always keep the previous WASM hash and backup; rollback is an upgrade to that hash.
- Testing: Use in-repo upgrade tests and a testnet/staging run before production upgrades.