@layerzerolabs/common-utils-stellar-contracts 0.2.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,169 @@
1
+ use crate::{self as utils, auth::Auth, errors::TtlConfigurableError};
2
+ use common_macros::{contract_trait, only_auth, storage};
3
+ use soroban_sdk::{assert_with_error, contractevent, contracttype, Env, IntoVal, Val};
4
+
5
+ /// Ledgers per day (~5 second close time).
6
+ pub const LEDGERS_PER_DAY: u32 = (24 * 3600) / 5;
7
+
8
+ /// Maximum TTL (1 year) allowed by the protocol.
9
+ /// Note: Stellar's current maximum TTL is 6 months, but this constraint may change
10
+ /// in the future. In order to preserve LayerZero's censorship-resistance and protect
11
+ /// users from abusive parameter changes, a constant upper bound is enforced on the
12
+ /// extend_to value.
13
+ pub const MAX_TTL: u32 = 365 * LEDGERS_PER_DAY;
14
+
15
+ /// TTL configuration: threshold (when to extend) and extend_to (target TTL).
16
+ #[contracttype]
17
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
18
+ pub struct TtlConfig {
19
+ /// TTL threshold that triggers extension (in ledgers).
20
+ pub threshold: u32,
21
+ /// Target TTL after extension (in ledgers).
22
+ pub extend_to: u32,
23
+ }
24
+
25
+ impl TtlConfig {
26
+ /// Creates a new TTL config.
27
+ pub const fn new(threshold: u32, extend_to: u32) -> Self {
28
+ Self { threshold, extend_to }
29
+ }
30
+
31
+ /// Validates that threshold <= extend_to <= max_ttl.
32
+ pub fn is_valid(&self, max_ttl: u32) -> bool {
33
+ self.threshold <= self.extend_to && self.extend_to <= max_ttl
34
+ }
35
+ }
36
+
37
+ // =============================================================================
38
+ // Events
39
+ // =============================================================================
40
+
41
+ /// Event emitted when TTL configs are set.
42
+ #[contractevent]
43
+ #[derive(Clone, Debug, Eq, PartialEq)]
44
+ pub struct TtlConfigsSet {
45
+ pub instance: Option<TtlConfig>,
46
+ pub persistent: Option<TtlConfig>,
47
+ }
48
+
49
+ /// Event emitted when TTL configs are frozen.
50
+ #[contractevent]
51
+ #[derive(Clone, Debug, Eq, PartialEq)]
52
+ pub struct TtlConfigsFrozen {}
53
+
54
+ // =============================================================================
55
+ // Storage for default implementation
56
+ // =============================================================================
57
+
58
+ /// Storage keys for TTL configuration.
59
+ ///
60
+ /// Note: Auto-TTL extension is disabled for these instance storage entries to avoid infinite
61
+ /// recursion. Extending TTL config storage would require reading the TTL config, which would
62
+ /// trigger another extension, creating a deep loop.
63
+ #[storage]
64
+ pub enum TtlConfigStorage {
65
+ #[instance(bool)]
66
+ #[default(false)]
67
+ Frozen,
68
+
69
+ #[instance(TtlConfig)]
70
+ Instance,
71
+
72
+ #[instance(TtlConfig)]
73
+ Persistent,
74
+ }
75
+
76
+ /// Initializes TTL configs with the default values (threshold: 29 days, extend_to: 30 days).
77
+ ///
78
+ /// This sets both instance and persistent TTL configs to `DEFAULT_TTL_CONFIG`.
79
+ pub fn init_default_ttl_configs(env: &Env) {
80
+ let default_ttl_config = TtlConfig::new(29 * LEDGERS_PER_DAY, 30 * LEDGERS_PER_DAY);
81
+ TtlConfigStorage::set_instance(env, &default_ttl_config);
82
+ TtlConfigStorage::set_persistent(env, &default_ttl_config);
83
+ }
84
+
85
+ /// Extends instance storage TTL using the configured settings (if any).
86
+ pub fn extend_instance_ttl(env: &Env) {
87
+ if let Some(TtlConfig { threshold, extend_to }) = TtlConfigStorage::instance(env) {
88
+ env.storage().instance().extend_ttl(threshold, extend_to);
89
+ }
90
+ }
91
+
92
+ /// Extends persistent storage TTL for a key using the configured settings (if any).
93
+ pub fn extend_persistent_ttl<K: IntoVal<Env, Val>>(env: &Env, key: &K) {
94
+ if let Some(TtlConfig { threshold, extend_to }) = TtlConfigStorage::persistent(env) {
95
+ env.storage().persistent().extend_ttl(key, threshold, extend_to);
96
+ }
97
+ }
98
+
99
+ /// Trait for contracts that support configurable TTL (Time-To-Live) management.
100
+ ///
101
+ /// Allows the contract authorizer to configure how long instance and persistent storage entries
102
+ /// remain alive on Stellar.
103
+ ///
104
+ /// The authorizer can also permanently freeze the configuration to prevent future changes,
105
+ /// providing immutability guarantees to users.
106
+ ///
107
+ /// Requires the `Auth` trait to be implemented, which can be provided by either:
108
+ /// - `#[ownable]` macro for single-owner contracts
109
+ /// - `#[multisig]` macro for multisig-controlled contracts
110
+ #[contract_trait]
111
+ pub trait TtlConfigurable: Auth {
112
+ /// Sets TTL configs for instance and persistent storage.
113
+ ///
114
+ /// - `None` values remove the corresponding config (disables auto-extension for that type)
115
+ /// - Validates that `threshold <= extend_to <= MAX_TTL`
116
+ ///
117
+ /// # Arguments
118
+ /// - `instance` - TTL config for instance storage
119
+ /// - `persistent` - TTL config for persistent storage
120
+ ///
121
+ /// # Panics
122
+ /// - `TtlConfigFrozen` if configs are frozen
123
+ /// - `InvalidTtlConfig` if validation fails
124
+ #[only_auth]
125
+ fn set_ttl_configs(
126
+ env: &soroban_sdk::Env,
127
+ instance: &Option<utils::ttl_configurable::TtlConfig>,
128
+ persistent: &Option<utils::ttl_configurable::TtlConfig>,
129
+ ) {
130
+ assert_with_error!(env, !Self::is_ttl_configs_frozen(env), TtlConfigurableError::TtlConfigFrozen);
131
+
132
+ let max_ttl = MAX_TTL.min(env.storage().max_ttl());
133
+ let all_valid = [instance, persistent].iter().all(|c| c.is_none_or(|cfg| cfg.is_valid(max_ttl)));
134
+ assert_with_error!(env, all_valid, TtlConfigurableError::InvalidTtlConfig);
135
+
136
+ TtlConfigStorage::set_or_remove_instance(env, instance);
137
+ TtlConfigStorage::set_or_remove_persistent(env, persistent);
138
+
139
+ TtlConfigsSet { instance: *instance, persistent: *persistent }.publish(env);
140
+ }
141
+
142
+ /// Returns the current TTL configs as (instance_config, persistent_config).
143
+ fn ttl_configs(
144
+ env: &soroban_sdk::Env,
145
+ ) -> (Option<utils::ttl_configurable::TtlConfig>, Option<utils::ttl_configurable::TtlConfig>) {
146
+ (TtlConfigStorage::instance(env), TtlConfigStorage::persistent(env))
147
+ }
148
+
149
+ /// Permanently freezes TTL configs, preventing any future modifications.
150
+ ///
151
+ /// This is irreversible and provides immutability guarantees to users.
152
+ /// Emits `TtlConfigsFrozen` event.
153
+ ///
154
+ /// # Panics
155
+ /// - `TtlConfigAlreadyFrozen` if already frozen
156
+ #[only_auth]
157
+ fn freeze_ttl_configs(env: &soroban_sdk::Env) {
158
+ assert_with_error!(env, !Self::is_ttl_configs_frozen(env), TtlConfigurableError::TtlConfigAlreadyFrozen);
159
+
160
+ TtlConfigStorage::set_frozen(env, &true);
161
+
162
+ TtlConfigsFrozen {}.publish(env);
163
+ }
164
+
165
+ /// Returns whether TTL configs are frozen.
166
+ fn is_ttl_configs_frozen(env: &soroban_sdk::Env) -> bool {
167
+ TtlConfigStorage::frozen(env)
168
+ }
169
+ }
@@ -0,0 +1,25 @@
1
+ //! TtlExtendable trait for manual instance TTL extension.
2
+ //!
3
+ //! This module provides the `TtlExtendable` trait which allows external callers
4
+ //! to extend a contract's instance storage TTL, keeping the contract alive.
5
+
6
+ /// Trait for contracts that support manual instance TTL extension.
7
+ ///
8
+ /// This trait provides a public contract function to extend the instance storage TTL,
9
+ /// allowing external callers to keep the contract alive by paying for TTL extension.
10
+ ///
11
+ /// Uses `#[soroban_sdk::contracttrait]` directly (not `#[common_macros::contract_trait]`)
12
+ /// because auto TTL extension would be redundant for a trait whose purpose is manual
13
+ /// TTL control.
14
+ #[soroban_sdk::contracttrait]
15
+ pub trait TtlExtendable {
16
+ /// Extends the instance TTL.
17
+ ///
18
+ /// # Arguments
19
+ ///
20
+ /// * `threshold` - The threshold to extend the TTL (if current TTL is below this, extend).
21
+ /// * `extend_to` - The TTL to extend to.
22
+ fn extend_instance_ttl(env: &soroban_sdk::Env, threshold: u32, extend_to: u32) {
23
+ env.storage().instance().extend_ttl(threshold, extend_to);
24
+ }
25
+ }
@@ -0,0 +1,103 @@
1
+ use crate::{self as utils, auth::Auth, errors::UpgradeableError, option_ext::OptionExt, rbac::RoleBasedAccessControl};
2
+ use common_macros::{contract_trait, only_auth, only_role, storage};
3
+ use soroban_sdk::{assert_with_error, xdr::FromXdr, Bytes, BytesN, Env};
4
+
5
+ /// Role for upgrading the contract and running migrations.
6
+ pub const UPGRADER_ROLE: &str = "UPGRADER_ROLE";
7
+
8
+ /// Trait for contracts with upgrade and migration support (Auth-based).
9
+ ///
10
+ /// Implements a two-phase upgrade pattern:
11
+ /// 1. `upgrade` - Updates WASM bytecode and sets migration flag
12
+ /// 2. `migrate` - Runs state migration and clears the flag
13
+ ///
14
+ /// Requires implementing [`UpgradeableInternal`] and [`Auth`].
15
+ #[contract_trait]
16
+ pub trait Upgradeable: UpgradeableInternal + Auth {
17
+ /// Upgrades the contract to new WASM bytecode.
18
+ #[only_auth]
19
+ fn upgrade(env: &soroban_sdk::Env, new_wasm_hash: &soroban_sdk::BytesN<32>) {
20
+ upgrade(env, new_wasm_hash);
21
+ }
22
+
23
+ /// Runs migration logic after an upgrade.
24
+ #[only_auth]
25
+ fn migrate(env: &soroban_sdk::Env, migration_data: &soroban_sdk::Bytes) {
26
+ migrate::<Self>(env, migration_data);
27
+ }
28
+ }
29
+
30
+ /// Trait for contracts with upgrade and migration support (RBAC-based).
31
+ ///
32
+ /// Same two-phase upgrade pattern as [`Upgradeable`], but access control uses
33
+ /// `UPGRADER_ROLE` instead of Auth. Requires implementing [`UpgradeableInternal`]
34
+ /// and [`RoleBasedAccessControl`].
35
+ #[contract_trait]
36
+ pub trait UpgradeableRbac: UpgradeableInternal + RoleBasedAccessControl {
37
+ /// Upgrades the contract to new WASM bytecode.
38
+ #[only_role(operator, UPGRADER_ROLE)]
39
+ fn upgrade(env: &soroban_sdk::Env, new_wasm_hash: &soroban_sdk::BytesN<32>, operator: &soroban_sdk::Address) {
40
+ upgrade(env, new_wasm_hash);
41
+ }
42
+
43
+ /// Runs migration logic after an upgrade.
44
+ #[only_role(operator, UPGRADER_ROLE)]
45
+ fn migrate(env: &soroban_sdk::Env, migration_data: &soroban_sdk::Bytes, operator: &soroban_sdk::Address) {
46
+ migrate::<Self>(env, migration_data);
47
+ }
48
+ }
49
+
50
+ /// Trait for defining contract-specific migration logic.
51
+ /// Must be implemented by contracts using [`Upgradeable`] or [`UpgradeableRbac`].
52
+ pub trait UpgradeableInternal {
53
+ /// The XDR-decodable type for migration data. Use `()` if not needed.
54
+ type MigrationData: FromXdr;
55
+
56
+ /// Migration logic called by `migrate`. Implement state transformations here.
57
+ fn __migrate(env: &Env, migration_data: &Self::MigrationData);
58
+ }
59
+
60
+ // ============================================
61
+ // Helper Functions
62
+ // ============================================
63
+
64
+ /// Core upgrade logic: set migrating flag and update WASM.
65
+ ///
66
+ /// # Arguments
67
+ /// - `new_wasm_hash` - The hash of the new WASM bytecode
68
+ fn upgrade(env: &Env, new_wasm_hash: &BytesN<32>) {
69
+ UpgradeableStorage::set_migrating(env, &true);
70
+ env.deployer().update_current_contract_wasm(new_wasm_hash.clone());
71
+ }
72
+
73
+ /// Core migration logic: parse migration data, call `__migrate`, clear flag.
74
+ ///
75
+ /// # Arguments
76
+ /// - `migration_data` - The migration data
77
+ ///
78
+ /// # Panics
79
+ /// - `MigrationNotAllowed` if no migration is in progress
80
+ /// - `InvalidMigrationData` if the migration data cannot be parsed into the contract's `MigrationData` type
81
+ fn migrate<T: UpgradeableInternal>(env: &Env, migration_data: &Bytes) {
82
+ assert_with_error!(env, UpgradeableStorage::migrating(env), UpgradeableError::MigrationNotAllowed);
83
+
84
+ let parsed_data = T::MigrationData::from_xdr(env, migration_data)
85
+ .ok()
86
+ .unwrap_or_panic(env, UpgradeableError::InvalidMigrationData);
87
+ T::__migrate(env, &parsed_data);
88
+
89
+ UpgradeableStorage::set_migrating(env, &false);
90
+ }
91
+
92
+ // ============================================
93
+ // Storage
94
+ // ============================================
95
+
96
+ /// Storage for upgrade state.
97
+ #[storage]
98
+ pub enum UpgradeableStorage {
99
+ /// Whether a migration is pending.
100
+ #[instance(bool)]
101
+ #[default(false)]
102
+ Migrating,
103
+ }