stellar_contract_utils/upgradeable/
mod.rs

1//! # Lightweight upgradeability framework
2//!
3//! This module defines a minimal system for managing contract upgrades, with
4//! optional support for handling migrations in a structured and safe manner.
5//! The framework enforces correct sequencing of operations, e.g. migration can
6//! only be invoked after an upgrade.
7//!
8//! It is recommended to use this module via the `#[derive(Upgradeable)]` macro,
9//! or via the `#[derive(UpgradeableMigratable)]` when custom migration logic is
10//! additionally needed.
11//!
12//! If a rollback is required, the contract can be upgraded to a newer version
13//! where the rollback-specific logic is defined and performed as a migration.
14//!
15//! **IMPORTANT**: While the framework structures the upgrade flow, it does NOT
16//! perform deeper checks and verifications such as:
17//!
18//! - Ensuring that the new contract does not include a constructor, as it will
19//!   not be invoked.
20//! - Verifying that the new contract includes an upgradability mechanism,
21//!   preventing an unintended loss of further upgradability capacity.
22//! - Checking for storage consistency, ensuring that the new contract does not
23//!   inadvertently introduce storage mismatches.
24//!
25//!
26//! Example for upgrade only:
27//! ```rust,ignore
28//! #[derive(Upgradeable)]
29//! #[contract]
30//! pub struct ExampleContract;
31//!
32//! impl UpgradeableInternal for ExampleContract {
33//!     fn _require_auth(e: &Env, operator: &Address) {
34//!         operator.require_auth();
35//!         let owner = e.storage().instance().get::<_, Address>(&OWNER).unwrap();
36//!         if *operator != owner {
37//!             panic_with_error!(e, ExampleContractError::Unauthorized)
38//!         }
39//!     }
40//! }
41//! ```
42//!
43//! # Example for upgrade and migration:
44//! ```ignore,rust
45//! #[contracttype]
46//! pub struct Data {
47//!     pub num1: u32,
48//!     pub num2: u32,
49//! }
50//!
51//! #[derive(UpgradeableMigratable)]
52//! #[contract]
53//! pub struct ExampleContract;
54//!
55//! impl UpgradeableMigratableInternal for ExampleContract {
56//!     type MigrationData = Data;
57//!
58//!     fn _require_auth(e: &Env, operator: &Address) {
59//!         operator.require_auth();
60//!         let owner = e.storage().instance().get::<_, Address>(&OWNER).unwrap();
61//!         if *operator != owner {
62//!             panic_with_error!(e, ExampleContractError::Unauthorized)
63//!         }
64//!     }
65//!
66//!     fn _migrate(e: &Env, data: &Self::MigrationData) {
67//!         e.storage().instance().set(&DATA_KEY, data);
68//!     }
69//! }
70//! ```
71//! Check in the "/examples/upgradeable/" directory for the full example, where
72//! you can also find a helper `Upgrader` contract that performs upgrade+migrate
73//! in a single transaction.
74
75mod storage;
76
77#[cfg(test)]
78mod test;
79
80use soroban_sdk::{contractclient, contracterror, Address, BytesN, Env, FromVal, Val};
81
82pub use crate::upgradeable::storage::{
83    can_complete_migration, complete_migration, enable_migration, ensure_can_complete_migration,
84};
85
86/// High-level trait for contract upgrades.
87///
88/// This trait defines the external entry point and can be used in two ways:
89///
90/// 1. Standalone – Implement this trait directly when full control over access
91///    control and upgrade logic is required. In this case, the implementor is
92///    responsible for ensuring:
93///    - Proper authorization of the `operator`
94///    - Versioning management
95///
96/// 2. Framework-assisted usage – When using the lightweight upgrade framework
97///    provided in this module, you should NOT manually implement this trait.
98///    Instead:
99///    - Derive it using `#[derive(Upgradeable)]`
100///    - Provide access control by implementing [`UpgradeableInternal`] with
101///      your custom logic
102#[contractclient(name = "UpgradeableClient")]
103pub trait Upgradeable {
104    /// Upgrades the contract by setting a new WASM bytecode. The
105    /// contract will only be upgraded after the invocation has
106    /// successfully completed.
107    ///
108    /// # Arguments
109    ///
110    /// * `e` - Access to Soroban environment.
111    /// * `new_wasm_hash` - A 32-byte hash identifying the new WASM blob,
112    ///   uploaded to the ledger.
113    /// * `operator` - The authorized address performing the upgrade.
114    fn upgrade(e: &Env, new_wasm_hash: BytesN<32>, operator: Address);
115}
116
117/// Trait to be implemented for a custom upgrade authorization mechanism.
118/// Requires defining access control logic for who can upgrade the contract.
119pub trait UpgradeableInternal {
120    /// Ensures the `operator` has signed and is authorized to perform the
121    /// upgrade.
122    ///
123    /// This must be implemented by the consuming contract.
124    ///
125    /// # Arguments
126    ///
127    /// * `e` - The Soroban environment.
128    /// * `operator` - The address attempting the upgrade. Can be G-account, or
129    ///   another contract (C-account) such as timelock or governor.
130    fn _require_auth(e: &Env, operator: &Address);
131}
132
133/// High-level trait for a combination of upgrade and migration logic in
134/// upgradeable contracts.
135///
136/// This trait defines the external entry points for applying both, an upgrade
137/// and a migration. It is recommended to be used only as part of the
138/// lightweight upgrade framework provided in this module.
139///
140/// When using the framework, this trait is automatically derived with
141/// `#[derive(UpgradeableMigratable)]`, and should NOT be manually implemented.
142/// Instead, the contract must define access control via `_require_auth` and
143/// provide its custom migration logic by implementing
144/// `UpgradeableMigratableInternal`.
145pub trait UpgradeableMigratable: UpgradeableMigratableInternal {
146    /// Upgrades the contract by setting a new WASM bytecode. The
147    /// contract will only be upgraded after the invocation has
148    /// successfully completed.
149    ///
150    /// # Arguments
151    ///
152    /// * `e` - Access to Soroban environment.
153    /// * `new_wasm_hash` - A 32-byte hash identifying the new WASM blob,
154    ///   uploaded to the ledger.
155    /// * `operator` - The authorized address performing the upgrade and the
156    ///   migration.
157    fn upgrade(e: &Env, new_wasm_hash: BytesN<32>, operator: Address);
158
159    /// Entry point to handle a contract migration.
160    ///
161    /// # Arguments
162    ///
163    /// * `e` - The Soroban environment.
164    /// * `migration_data` - Arbitrary data passed to the migration logic.
165    /// * `operator` - The authorized address performing the upgrade and the
166    ///   migration.
167    fn migrate(e: &Env, migration_data: Self::MigrationData, operator: Address);
168}
169
170/// Trait to be implemented for custom migration. Requires defining access
171/// control and custom business logic for a migration after an upgrade.
172pub trait UpgradeableMigratableInternal {
173    /// Type representing structured data needed during migration.
174    type MigrationData: FromVal<Env, Val>;
175
176    /// Applies migration logic using the given data.
177    ///
178    /// # Arguments
179    ///
180    /// * `e` - The Soroban environment.
181    /// * `migration_data` - Migration-specific input data.
182    fn _migrate(e: &Env, migration_data: &Self::MigrationData);
183
184    /// Ensures the `operator` has signed and is authorized to perform the
185    /// upgrade and the migration.
186    ///
187    /// This must be implemented by the consuming contract.
188    ///
189    /// # Arguments
190    ///
191    /// * `e` - The Soroban environment.
192    /// * `operator` - The address attempting the upgrade and the migration. Can
193    ///   be a G-account, or another contract (C-account) such as timelock or
194    ///   governor.
195    fn _require_auth(e: &Env, operator: &Address);
196}
197
198#[contracterror]
199#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
200#[repr(u32)]
201pub enum UpgradeableError {
202    /// When migration is attempted but not allowed due to upgrade state.
203    MigrationNotAllowed = 1100,
204}