From ee8db58c9dabbb3cafec22d80af7d3707bf397a9 Mon Sep 17 00:00:00 2001 From: Owoh Chidubem Alexander Date: Fri, 26 Jun 2026 12:07:20 +0100 Subject: [PATCH] feat(contract): Extend instance TTL on admin actions - Add INSTANCE_TTL_EXTEND constant (10,000 ledgers) for TTL extension configuration - Implement _extend_instance_ttl() helper method to extend contract instance lifetime - Call _extend_instance_ttl() after all admin state modifications to maintain contract availability - Apply TTL extension to: initialize, remove_signer, propose_upgrade, finalize_upgrade, cancel_upgrade, set_value, set_heartbeat_interval, record_heartbeat, set_node_profile, set_tier_config, update_asset_metrics, bulk_add_signers, propose_ownership_transfer, and claim_ownership - Fix return type handling in propose_ownership_transfer and claim_ownership to properly propagate errors and extend TTL - Ensures contract instance remains available across the full lifecycle of admin operations --- src/lib.rs | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c927847..56ef056 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -118,6 +118,8 @@ const RELAYER_TTL_THRESHOLD: u32 = 5_000; // Telemetry TTL configuration: expire after validation window closes const HEARTBEAT_TTL_LEDGERS: u32 = 17_280; // ~24 hours at 5s/ledger const HEARTBEAT_TTL_THRESHOLD: u32 = 5_000; // Extend when < 5000 ledgers remain +/// Number of ledgers to extend the contract instance TTL by on admin actions. +const INSTANCE_TTL_EXTEND: u32 = 10_000; #[contracttype] #[derive(Clone)] @@ -197,6 +199,7 @@ impl TimeLockedUpgradeContract { admin.require_auth(); let data = ContractData { admin: admin.clone(), value: 0, max_fee_ceiling: MAX_FEE_CEILING }; env.storage().instance().set(&DATA_KEY, &data); + Self::_extend_instance_ttl(&env); Ok(()) } @@ -237,6 +240,7 @@ impl TimeLockedUpgradeContract { // Refactored for Issue #370: Zero-Allocation removal with Map signers.remove(signer); env.storage().instance().set(&SIGNERS_KEY, &signers); + Self::_extend_instance_ttl(&env); Ok(()) } @@ -285,6 +289,7 @@ impl TimeLockedUpgradeContract { consume_nonce(&env, &proposer, nonce, salt, salt_signature); let staged = StagedUpgrade { wasm_hash: new_wasm_hash.into(), staged_at: env.ledger().sequence() }; env.storage().instance().set(&PENDING_UPGRADE_KEY, &staged); + Self::_extend_instance_ttl(&env); Ok(()) } @@ -300,6 +305,7 @@ impl TimeLockedUpgradeContract { } env.deployer().update_current_contract_wasm(BytesN::from_array(&env, &pending.wasm_hash)); env.storage().instance().remove(&PENDING_UPGRADE_KEY); + Self::_extend_instance_ttl(&env); Ok(()) } @@ -318,6 +324,7 @@ impl TimeLockedUpgradeContract { if data.admin != canceller { return Err(ContractError::NotAdmin); } canceller.require_auth(); env.storage().instance().remove(&PENDING_UPGRADE_KEY); + Self::_extend_instance_ttl(&env); Ok(()) } @@ -333,6 +340,7 @@ impl TimeLockedUpgradeContract { data.value = new_value; env.storage().instance().set(&DATA_KEY, &data); Self::_record_heartbeat(&env, symbol_short!("VALUE")); + Self::_extend_instance_ttl(&env); Ok(()) } @@ -355,6 +363,7 @@ impl TimeLockedUpgradeContract { if data.admin != admin { return Err(ContractError::NotAdmin); } admin.require_auth(); env.storage().instance().set(&HB_INTERVAL_KEY, &interval); + Self::_extend_instance_ttl(&env); Ok(()) } @@ -384,6 +393,7 @@ impl TimeLockedUpgradeContract { if data.admin != updater { return Err(ContractError::NotAdmin); } updater.require_auth(); Self::_record_heartbeat(&env, asset); + Self::_extend_instance_ttl(&env); Ok(()) } @@ -401,6 +411,7 @@ impl TimeLockedUpgradeContract { let mut profiles = Self::_get_node_profiles(&env); profiles.set(node.clone(), NodeProfile { node, rate, confidence, updated_at: env.ledger().timestamp() }); env.storage().persistent().set(&NODE_PROFILES_KEY, &profiles); + Self::_extend_instance_ttl(&env); Ok(()) } @@ -439,6 +450,7 @@ impl TimeLockedUpgradeContract { env.storage() .instance() .set(&StakingStorageKey::TierConfig, &config); + Self::_extend_instance_ttl(&env); Ok(()) } @@ -478,6 +490,7 @@ impl TimeLockedUpgradeContract { .persistent() .set(&StakingStorageKey::AssetMetrics(asset.clone()), &metrics); + Self::_extend_instance_ttl(&env); Ok(metrics) } @@ -626,17 +639,22 @@ impl TimeLockedUpgradeContract { signers.set(signer, ()); env.storage().instance().set(&SIGNERS_KEY, &signers); } + Self::_extend_instance_ttl(&env); Ok(()) } // --- Admin Ownership Transfer (Issue #429) --- pub fn propose_ownership_transfer(env: Env, current_admin: Address, nominee: Address) -> Result<(), ContractError> { - admin::propose_ownership_transfer(&env, current_admin, nominee) + admin::propose_ownership_transfer(&env, current_admin, nominee)?; + Self::_extend_instance_ttl(&env); + Ok(()) } pub fn claim_ownership(env: Env, claimer: Address) -> Result<(), ContractError> { - admin::claim_ownership(&env, claimer) + admin::claim_ownership(&env, claimer)?; + Self::_extend_instance_ttl(&env); + Ok(()) } // --- Private Helpers --- @@ -685,6 +703,13 @@ impl TimeLockedUpgradeContract { ); } + fn _extend_instance_ttl(env: &Env) { + env.storage().instance().extend_ttl( + RELAYER_TTL_THRESHOLD, + RELAYER_TTL_THRESHOLD + INSTANCE_TTL_EXTEND, + ); + } + fn _is_signer(env: &Env, addr: &Address) -> bool { Self::_get_signers(env).contains_key(addr.clone()) }