@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.
- package/Cargo.toml +28 -0
- package/LICENSE +23 -0
- package/clippy.toml +7 -0
- package/package.json +43 -0
- package/rust-toolchain.toml +4 -0
- package/rustfmt.toml +15 -0
- package/src/auth.rs +48 -0
- package/src/buffer_reader.rs +194 -0
- package/src/buffer_writer.rs +138 -0
- package/src/bytes_ext.rs +18 -0
- package/src/errors.rs +82 -0
- package/src/lib.rs +20 -0
- package/src/multisig.rs +247 -0
- package/src/option_ext.rs +38 -0
- package/src/ownable.rs +227 -0
- package/src/rbac.rs +438 -0
- package/src/testing_utils.rs +180 -0
- package/src/tests/auth.rs +179 -0
- package/src/tests/buffer_reader.rs +969 -0
- package/src/tests/buffer_writer.rs +689 -0
- package/src/tests/bytes_ext.rs +160 -0
- package/src/tests/mod.rs +13 -0
- package/src/tests/multisig.rs +760 -0
- package/src/tests/option_ext.rs +18 -0
- package/src/tests/ownable.rs +822 -0
- package/src/tests/rbac.rs +559 -0
- package/src/tests/test_helper.rs +67 -0
- package/src/tests/testing_utils.rs +522 -0
- package/src/tests/ttl_configurable.rs +586 -0
- package/src/tests/ttl_extendable.rs +64 -0
- package/src/tests/upgradeable.rs +529 -0
- package/src/ttl_configurable.rs +169 -0
- package/src/ttl_extendable.rs +25 -0
- package/src/upgradeable.rs +103 -0
|
@@ -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
|
+
}
|