diff --git a/contracts/storage_heavy/README.md b/contracts/storage_heavy/README.md new file mode 100644 index 00000000..c3c41079 --- /dev/null +++ b/contracts/storage_heavy/README.md @@ -0,0 +1,17 @@ +# Storage-heavy contract + +This contract provides storage-cost benchmarks for persistent and temporary +Soroban entries, including batch access and compact boolean representations. + +## Keeping persistent entries alive + +Any account can extend a persistent entry's lifetime by invoking: + +```text +extend_ttl(key, threshold, extend_to) +``` + +The entry is extended to `extend_to` ledgers only when its remaining lifetime +is below `threshold`. The function requires no authorization so the invoker can +pay the network rent on behalf of the entry owner. The key must already exist +in persistent storage; temporary entries are intentionally not affected. diff --git a/contracts/storage_heavy/src/lib.rs b/contracts/storage_heavy/src/lib.rs index b7571f71..18a1525e 100644 --- a/contracts/storage_heavy/src/lib.rs +++ b/contracts/storage_heavy/src/lib.rs @@ -35,6 +35,17 @@ impl StorageHeavyContract { .unwrap_or(Bytes::new(&env)) } + /// Extends the lifetime of a persistent storage entry. + /// + /// The TTL is changed to `extend_to` ledgers only when its current value is + /// below `threshold`. This method intentionally requires no authorization, + /// allowing any caller to pay the network rent needed to keep an entry + /// available. + pub fn extend_ttl(env: Env, key: Symbol, threshold: u32, extend_to: u32) { + env.storage() + .persistent() + .extend_ttl(&key, threshold, extend_to); + } /// Batch-write to persistent storage. /// Demonstrates the cost of N separate ledger-entry writes. pub fn batch_write_persistent(env: Env, keys: Vec, data_points: Vec) { diff --git a/contracts/storage_heavy/src/test.rs b/contracts/storage_heavy/src/test.rs index d7ff5701..c265f08a 100644 --- a/contracts/storage_heavy/src/test.rs +++ b/contracts/storage_heavy/src/test.rs @@ -1,7 +1,11 @@ extern crate std; use super::*; -use soroban_sdk::{symbol_short, Bytes, Env, Vec, Symbol}; +use soroban_sdk::{ + symbol_short, + testutils::{storage::Persistent as _, Ledger as _, LedgerInfo}, + Bytes, Env, Symbol, Vec, +}; use std::println; @@ -27,6 +31,52 @@ fn test_storage() { assert_eq!(client.read_temporary(&key), data_bytes); } +#[test] +fn test_extend_ttl_for_persistent_entry() { + let env = Env::default(); + env.ledger().set(LedgerInfo { + timestamp: 0, + protocol_version: 22, + sequence_number: 1, + network_id: Default::default(), + base_reserve: 10, + min_temp_entry_ttl: 10, + min_persistent_entry_ttl: 10, + max_entry_ttl: 10_000, + }); + + let contract_id = env.register(StorageHeavyContract, ()); + let client = StorageHeavyContractClient::new(&env, &contract_id); + let key = symbol_short!("data"); + let data = Bytes::from_slice(&env, &[1, 2, 3]); + + client.write_persistent(&key, &data); + let initial_ttl = env.as_contract(&contract_id, || env.storage().persistent().get_ttl(&key)); + let extended_ttl = initial_ttl + 1_000; + + client.extend_ttl(&key, &(initial_ttl + 1), &extended_ttl); + + let ttl = env.as_contract(&contract_id, || env.storage().persistent().get_ttl(&key)); + assert_eq!(ttl, extended_ttl); + assert_eq!(client.read_persistent(&key), data); +} + +#[test] +fn test_extend_ttl_respects_threshold() { + let env = Env::default(); + let contract_id = env.register(StorageHeavyContract, ()); + let client = StorageHeavyContractClient::new(&env, &contract_id); + let key = symbol_short!("data"); + let data = Bytes::from_slice(&env, &[1, 2, 3]); + + client.write_persistent(&key, &data); + let initial_ttl = env.as_contract(&contract_id, || env.storage().persistent().get_ttl(&key)); + + client.extend_ttl(&key, &(initial_ttl - 1), &(initial_ttl + 1_000)); + + let ttl = env.as_contract(&contract_id, || env.storage().persistent().get_ttl(&key)); + assert_eq!(ttl, initial_ttl); +} #[test] fn test_batch_storage() { let env = Env::default();