From 111c435006a9dedb1336b47549abbb109c4c7dd8 Mon Sep 17 00:00:00 2001 From: BigMick03 Date: Mon, 27 Apr 2026 15:23:41 +0100 Subject: [PATCH] Contracts | Gas-Efficient Storage: Bump Ledger Expiration --- README.md | 14 ++++++++++++++ src/lib.rs | 38 ++++++++++++++++++++++++++------------ 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index e74e5d7..6698f79 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,19 @@ This repository contains smart contracts for the StellarFlow Network with a time - **Admin-Only Operations**: Only contract administrators can propose and execute upgrades - **Upgrade Cancellation**: Ability to cancel pending upgrades before execution - **Timelock Monitoring**: Functions to check remaining timelock time +- **State Expiration Management**: Automatic TTL extension to prevent data deletion + +## State Expiration Management + +To ensure contract data is never deleted due to Soroban's state expiration mechanism, the contract uses persistent storage and automatically extends TTL (Time-To-Live) for all storage entries. TTL is extended to approximately 1 year (535,000 ledgers) whenever data is accessed or modified. + +### Implementation Details + +- **Persistent Storage**: All contract state is stored in persistent storage to survive contract upgrades +- **Automatic TTL Extension**: `env.storage().persistent().extend_ttl()` is called in all update functions +- **TTL Parameters**: + - Minimum ledgers: 535,000 (≈1 year) + - Maximum ledgers: 1,000,000 (≈2 years) ## Architecture @@ -90,6 +103,7 @@ cargo test ✅ **Pending State Storage**: New WASM hash stored in pending state before commitment ✅ **48-Hour Delay**: Enforced delay between proposal and execution ✅ **Flash Upgrade Prevention**: Complete protection against immediate upgrades +✅ **State Expiration Management**: Uses `env.storage().persistent().extend_ttl()` to keep data alive ## Build and Deploy diff --git a/src/lib.rs b/src/lib.rs index bd584ee..ecd8bdc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,10 @@ const DATA_KEY: Symbol = Symbol::short("DATA"); const PENDING_UPGRADE_KEY: Symbol = Symbol::short("PENDING"); const UPGRADE_DELAY_SECONDS: u64 = 48 * 60 * 60; // 48 hours in seconds +// TTL extension parameters (approximately 1 year in ledgers) +const MIN_LEDGERS_TTL: u32 = 535_000; +const MAX_LEDGERS_TTL: u32 = 1_000_000; + #[contracttype] pub struct PendingUpgrade { pub new_wasm_hash: BytesN<32>, @@ -26,7 +30,7 @@ pub struct TimeLockedUpgradeContract; impl TimeLockedUpgradeContract { /// Initialize the contract with an admin address pub fn initialize(env: Env, admin: Address) { - if env.storage().instance().has(&DATA_KEY) { + if env.storage().persistent().has(&DATA_KEY) { panic!("contract already initialized"); } @@ -37,15 +41,18 @@ impl TimeLockedUpgradeContract { value: 0, }; - env.storage().instance().set(&DATA_KEY, &data); + env.storage().persistent().set(&DATA_KEY, &data); + env.storage().persistent().extend_ttl(MIN_LEDGERS_TTL, MAX_LEDGERS_TTL, &DATA_KEY); } /// Get the current contract data pub fn get_data(env: Env) -> ContractData { - env.storage() - .instance() + let data = env.storage() + .persistent() .get(&DATA_KEY) - .unwrap_or_else(|| panic!("contract not initialized")) + .unwrap_or_else(|| panic!("contract not initialized")); + env.storage().persistent().extend_ttl(MIN_LEDGERS_TTL, MAX_LEDGERS_TTL, &DATA_KEY); + data } /// Propose an upgrade with a new WASM hash @@ -68,7 +75,8 @@ impl TimeLockedUpgradeContract { proposer: proposer.clone(), }; - env.storage().instance().set(&PENDING_UPGRADE_KEY, &pending_upgrade); + env.storage().persistent().set(&PENDING_UPGRADE_KEY, &pending_upgrade); + env.storage().persistent().extend_ttl(MIN_LEDGERS_TTL, MAX_LEDGERS_TTL, &PENDING_UPGRADE_KEY); } /// Execute a pending upgrade if the timelock period has passed @@ -84,7 +92,7 @@ impl TimeLockedUpgradeContract { let pending_upgrade: PendingUpgrade = env .storage() - .instance() + .persistent() .get(&PENDING_UPGRADE_KEY) .unwrap_or_else(|| panic!("no pending upgrade")); @@ -104,7 +112,7 @@ impl TimeLockedUpgradeContract { .update_current_contract(pending_upgrade.new_wasm_hash); // Clear the pending upgrade - env.storage().instance().remove(&PENDING_UPGRADE_KEY); + env.storage().persistent().remove(&PENDING_UPGRADE_KEY); } /// Cancel a pending upgrade @@ -118,16 +126,21 @@ impl TimeLockedUpgradeContract { canceller.require_auth(); - if !env.storage().instance().has(&PENDING_UPGRADE_KEY) { + if !env.storage().persistent().has(&PENDING_UPGRADE_KEY) { panic!("no pending upgrade to cancel"); } - env.storage().instance().remove(&PENDING_UPGRADE_KEY); + env.storage().persistent().remove(&PENDING_UPGRADE_KEY); } /// Get the current pending upgrade information pub fn get_pending_upgrade(env: Env) -> Option { - env.storage().instance().get(&PENDING_UPGRADE_KEY) + if let Some(pending) = env.storage().persistent().get(&PENDING_UPGRADE_KEY) { + env.storage().persistent().extend_ttl(MIN_LEDGERS_TTL, MAX_LEDGERS_TTL, &PENDING_UPGRADE_KEY); + Some(pending) + } else { + None + } } /// Get the remaining time before an upgrade can be executed @@ -158,7 +171,8 @@ impl TimeLockedUpgradeContract { setter.require_auth(); data.value = value; - env.storage().instance().set(&DATA_KEY, &data); + env.storage().persistent().set(&DATA_KEY, &data); + env.storage().persistent().extend_ttl(MIN_LEDGERS_TTL, MAX_LEDGERS_TTL, &DATA_KEY); } }