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
77mod test;
78
79use soroban_sdk::{contractclient, contracterror, Address, BytesN, Env, FromVal, Val};
80
81pub use crate::upgradeable::storage::{
82    can_complete_migration, complete_migration, enable_migration, ensure_can_complete_migration,
83};
84
85/// High-level trait for contract upgrades.
86///
87/// This trait defines the external entry point and can be used in two ways:
88///
89/// 1. Standalone – Implement this trait directly when full control over access
90///    control and upgrade logic is required. In this case, the implementor is
91///    responsible for ensuring:
92///    - Proper authorization of the `operator`
93///    - Versioning management
94///
95/// 2. Framework-assisted usage – When using the lightweight upgrade framework
96///    provided in this module, you should NOT manually implement this trait.
97///    Instead:
98///    - Derive it using `#[derive(Upgradeable)]`
99///    - Provide access control by implementing [`UpgradeableInternal`] with
100///      your custom logic
101#[contractclient(name = "UpgradeableClient")]
102pub trait Upgradeable {
103    /// Upgrades the contract by setting a new WASM bytecode. The
104    /// contract will only be upgraded after the invocation has
105    /// successfully completed.
106    ///
107    /// # Arguments
108    ///
109    /// * `e` - Access to Soroban environment.
110    /// * `new_wasm_hash` - A 32-byte hash identifying the new WASM blob,
111    ///   uploaded to the ledger.
112    /// * `operator` - The authorized address performing the upgrade.
113    fn upgrade(e: &Env, new_wasm_hash: BytesN<32>, operator: Address);
114}
115
116/// Trait to be implemented for a custom upgrade authorization mechanism.
117/// Requires defining access control logic for who can upgrade the contract.
118pub trait UpgradeableInternal {
119    /// Ensures the `operator` has signed and is authorized to perform the
120    /// upgrade.
121    ///
122    /// This must be implemented by the consuming contract.
123    ///
124    /// # Arguments
125    ///
126    /// * `e` - The Soroban environment.
127    /// * `operator` - The address attempting the upgrade. Can be G-account, or
128    ///   another contract (C-account) such as timelock or governor.
129    fn _require_auth(e: &Env, operator: &Address);
130}
131
132/// High-level trait for a combination of upgrade and migration logic in
133/// upgradeable contracts.
134///
135/// This trait defines the external entry points for applying both, an upgrade
136/// and a migration. It is recommended to be used only as part of the
137/// lightweight upgrade framework provided in this module.
138///
139/// When using the framework, this trait is automatically derived with
140/// `#[derive(UpgradeableMigratable)]`, and should NOT be manually implemented.
141/// Instead, the contract must define access control via `_require_auth` and
142/// provide its custom migration logic by implementing
143/// `UpgradeableMigratableInternal`.
144pub trait UpgradeableMigratable: UpgradeableMigratableInternal {
145    /// Upgrades the contract by setting a new WASM bytecode. The
146    /// contract will only be upgraded after the invocation has
147    /// successfully completed.
148    ///
149    /// # Arguments
150    ///
151    /// * `e` - Access to Soroban environment.
152    /// * `new_wasm_hash` - A 32-byte hash identifying the new WASM blob,
153    ///   uploaded to the ledger.
154    /// * `operator` - The authorized address performing the upgrade and the
155    ///   migration.
156    fn upgrade(e: &Env, new_wasm_hash: BytesN<32>, operator: Address);
157
158    /// Entry point to handle a contract migration.
159    ///
160    /// # Arguments
161    ///
162    /// * `e` - The Soroban environment.
163    /// * `migration_data` - Arbitrary data passed to the migration logic.
164    /// * `operator` - The authorized address performing the upgrade and the
165    ///   migration.
166    fn migrate(e: &Env, migration_data: Self::MigrationData, operator: Address);
167}
168
169/// Trait to be implemented for custom migration. Requires defining access
170/// control and custom business logic for a migration after an upgrade.
171pub trait UpgradeableMigratableInternal {
172    /// Type representing structured data needed during migration.
173    type MigrationData: FromVal<Env, Val>;
174
175    /// Applies migration logic using the given data.
176    ///
177    /// # Arguments
178    ///
179    /// * `e` - The Soroban environment.
180    /// * `migration_data` - Migration-specific input data.
181    fn _migrate(e: &Env, migration_data: &Self::MigrationData);
182
183    /// Ensures the `operator` has signed and is authorized to perform the
184    /// upgrade and the migration.
185    ///
186    /// This must be implemented by the consuming contract.
187    ///
188    /// # Arguments
189    ///
190    /// * `e` - The Soroban environment.
191    /// * `operator` - The address attempting the upgrade and the migration. Can
192    ///   be a G-account, or another contract (C-account) such as timelock or
193    ///   governor.
194    fn _require_auth(e: &Env, operator: &Address);
195}
196
197#[contracterror]
198#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
199#[repr(u32)]
200pub enum UpgradeableError {
201    /// When migration is attempted but not allowed due to upgrade state.
202    MigrationNotAllowed = 1100,
203}