soroban_sdk/_migrating/
v23_archived_testing.rs

1//! Accessing archived persistent entries in tests no longer results in an error.
2//!
3//! Prior to protocol 23 the SDK used to emulate the failure when an archived
4//! ledger entry was accessed in tests. This behavior has never represented the
5//! actual behavior of the network, just one possible scenario when an archived
6//! entry is present in the transaction footprint.
7//!
8//! In protocol 23 automatic entry restoration has been introduced, which makes
9//! it possible for a transaction to restore an archived entry before accessing
10//! it. As this behavior will become the most common case on the network, the
11//! SDK has been changed to emulate automatic restoration in tests as well.
12//!
13//! Note, that instance storage is a persistent entry as well, so it is subject
14//! to the same change.
15//!
16//! ## Example
17//!
18//! Consider the following simple contract that extends entry TTL
19//! along with the test that relies on the error on archived entry access in
20//! SDK 22:
21//!
22//! ```
23//! #![no_std]
24//! use soroban_sdk::{contract, contractimpl, contracttype, Env};
25//!
26//! #[contract]
27//! struct Contract;
28//!
29//! #[contracttype]
30//! enum DataKey {
31//!     Key,
32//! }
33//!
34//! #[contractimpl]
35//! impl Contract {
36//!     pub fn create_and_extend_entry(env: Env) {
37//!         env.storage().persistent().set(&DataKey::Key, &123_u32);
38//!         // Extend the entry to live for at least 1_000_000 ledgers.
39//!         env.storage()
40//!             .persistent()
41//!             .extend_ttl(&DataKey::Key, 1_000_000, 1_000_000);
42//!     }
43//!
44//!     pub fn read_entry(env: Env) -> u32 {
45//!         env.storage().persistent().get(&DataKey::Key).unwrap()
46//!     }
47//! }
48//!
49//! mod test {
50//!     extern crate std;
51//!     use soroban_sdk::testutils::{storage::Persistent, Ledger};
52//!
53//!     use super::*;
54//!
55//!     #[test]
56//!     fn test_entry_archived() {
57//!         let env = Env::default();
58//!         let contract = env.register(Contract, ());
59//!         let client = ContractClient::new(&env, &contract);
60//!         client.create_and_extend_entry();
61//!         let current_ledger = env.ledger().sequence();
62//!         assert_eq!(client.read_entry(), 123);
63//!
64//!         // Bump ledger sequence past entry TTL.
65//!         env.ledger()
66//!             .set_sequence_number(current_ledger + 1_000_000 + 1);
67//!         let res = client.try_read_entry();
68//!         // 👀 In SDK 22 `res` would be an error because the entry is archived.
69//!         // 👀 In SDK 23 `res` is Ok(123) because the entry is automatically restored.
70//!         assert!(res.is_err());
71//!     }
72//! }
73//!
74//! # fn main() { }
75//! ```
76//!
77//! The best way to address this change is to update the tests to explicitly
78//! verify the expected entry TTL after the extension. This way there is no need
79//! to rely on the storage behavior, and also the test becomes more robust as
80//! it enforces the exact expected TTL value, so there is no risk of bumping
81//! the ledger sequence further than the expected TTL and still having the test
82//! pass.
83//!
84//! The example test above can be re-written as follows:
85//!
86//! ```
87//! #![no_std]
88//! use soroban_sdk::{contract, contractimpl, contracttype, Env};
89//!
90//! #[contract]
91//! struct Contract;
92//!
93//! #[contracttype]
94//! enum DataKey {
95//!     Key,
96//! }
97//!
98//! #[contractimpl]
99//! impl Contract {
100//!     pub fn create_and_extend_entry(env: Env) {
101//!         env.storage().persistent().set(&DataKey::Key, &123_u32);
102//!         // Extend the entry to live for at least 1_000_000 ledgers.
103//!         env.storage()
104//!             .persistent()
105//!             .extend_ttl(&DataKey::Key, 1_000_000, 1_000_000);
106//!     }
107//!
108//!     pub fn read_entry(env: Env) -> u32 {
109//!         env.storage().persistent().get(&DataKey::Key).unwrap()
110//!     }
111//! }
112//!
113//! #[cfg(test)]
114//! mod test {
115//!     extern crate std;
116//!     use soroban_sdk::testutils::{storage::Persistent, Ledger};
117//!
118//!     use super::*;
119//!
120//!     #[test]
121//!     fn test_entry_ttl_extended() {
122//!         let env = Env::default();
123//!         let contract = env.register(Contract, ());
124//!         let client = ContractClient::new(&env, &contract);
125//!         client.create_and_extend_entry();
126//!         assert_eq!(client.read_entry(), 123);
127//!
128//!         // 👀 Verify that the entry TTL was extended correctly by 1000000 ledgers.
129//!         env.as_contract(&contract, || {
130//!             assert_eq!(env.storage().persistent().get_ttl(&DataKey::Key), 1_000_000);
131//!         });
132//!     }
133//!     
134//!     // 👀 This test is not really necessary, but it demonstrates the
135//!     // auto-restoration behavior in tests.
136//!     #[test]
137//!     fn test_auto_restore() {
138//!         let env = Env::default();
139//!         let contract = env.register(Contract, ());
140//!         let client = ContractClient::new(&env, &contract);
141//!         client.create_and_extend_entry();
142//!         let current_ledger = env.ledger().sequence();
143//!
144//!         // Bump ledger sequence past entry TTL.
145//!         env.ledger()
146//!             .set_sequence_number(current_ledger + 1_000_000 + 1);
147//!         // 👀 Entry can still be accessed because automatic restoration is emulated
148//!         // in tests.
149//!         assert_eq!(client.read_entry(), 123);
150//!
151//!         // 👀 Automatic restoration is also accounted for in cost_estimate():
152//!         let resources = env.cost_estimate().resources();
153//!         // Even though `read_entry` call is normally read-only, auto-restoration
154//!         // will cause 2 entry writes here: 1 for the contract instance, another
155//!         // one for the restored entry.
156//!         assert_eq!(resources.write_entries, 2);
157//!         // 2 rent bumps will happen as well for the respective entries.
158//!         assert_eq!(resources.persistent_entry_rent_bumps, 2);
159//!
160//!         // 👀 Entry TTL after auto-restoration can be observed via get_ttl().
161//!         env.as_contract(&contract, || {
162//!             // Auto-restored entries have their TTL extended by the minimum
163//!             // possible TTL worth of ledgers (`min_persistent_entry_ttl`),
164//!             // including the ledger in which they were restored (that's why
165//!             // we subtract 1 here).
166//!             assert_eq!(
167//!                 env.storage().persistent().get_ttl(&DataKey::Key),
168//!                 env.ledger().get().min_persistent_entry_ttl - 1
169//!             );
170//!         });
171//!     }
172//! }
173//!
174//! # fn main() { }
175//! ```
176//!