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}