diff --git a/soroban-sdk/src/storage.rs b/soroban-sdk/src/storage.rs index e52e9e82f..040740bdb 100644 --- a/soroban-sdk/src/storage.rs +++ b/soroban-sdk/src/storage.rs @@ -383,3 +383,106 @@ impl Instance { .unwrap_infallible(); } } + +#[cfg(any(test, feature = "testutils"))] +#[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))] +mod testutils { + use super::*; + use crate::{testutils, xdr, Map, TryIntoVal}; + + impl testutils::storage::Instance for Instance { + fn all(&self) -> Map { + let env = &self.storage.env; + let storage = env.host().with_mut_storage(|s| Ok(s.map.clone())).unwrap(); + let address: xdr::ScAddress = env.current_contract_address().try_into().unwrap(); + for entry in storage { + let (k, Some((v, _))) = entry else { + continue; + }; + let xdr::LedgerKey::ContractData(xdr::LedgerKeyContractData { + ref contract, .. + }) = *k + else { + continue; + }; + if contract != &address { + continue; + } + let xdr::LedgerEntry { + data: + xdr::LedgerEntryData::ContractData(xdr::ContractDataEntry { + key: xdr::ScVal::LedgerKeyContractInstance, + val: + xdr::ScVal::ContractInstance(xdr::ScContractInstance { + ref storage, + .. + }), + .. + }), + .. + } = *v + else { + continue; + }; + return match storage { + Some(map) => { + let map: Val = + Val::try_from_val(env, &xdr::ScVal::Map(Some(map.clone()))).unwrap(); + map.try_into_val(env).unwrap() + } + None => Map::new(env), + }; + } + panic!("contract instance for current contract address not found"); + } + } + + impl testutils::storage::Persistent for Persistent { + fn all(&self) -> Map { + all(&self.storage.env, xdr::ContractDataDurability::Persistent) + } + } + + impl testutils::storage::Temporary for Temporary { + fn all(&self) -> Map { + all(&self.storage.env, xdr::ContractDataDurability::Temporary) + } + } + + fn all(env: &Env, d: xdr::ContractDataDurability) -> Map { + let storage = env.host().with_mut_storage(|s| Ok(s.map.clone())).unwrap(); + let mut map = Map::::new(env); + for entry in storage { + let (_, Some((v, _))) = entry else { + continue; + }; + let xdr::LedgerEntry { + data: + xdr::LedgerEntryData::ContractData(xdr::ContractDataEntry { + ref key, + ref val, + durability, + .. + }), + .. + } = *v + else { + continue; + }; + if d != durability { + continue; + } + let Ok(key) = Val::try_from_val(env, key) else { + continue; + }; + let Ok(val) = Val::try_from_val(env, val) else { + continue; + }; + map.set(key, val); + } + map + } +} +#[cfg(any(test, feature = "testutils"))] +#[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))] +pub use testutils::*; diff --git a/soroban-sdk/src/tests.rs b/soroban-sdk/src/tests.rs index bc75168e1..91212487a 100644 --- a/soroban-sdk/src/tests.rs +++ b/soroban-sdk/src/tests.rs @@ -24,5 +24,6 @@ mod max_ttl; mod prng; mod proptest_scval_cmp; mod proptest_val_cmp; +mod storage_testutils; mod token_client; mod token_spec; diff --git a/soroban-sdk/src/tests/storage_testutils.rs b/soroban-sdk/src/tests/storage_testutils.rs new file mode 100644 index 000000000..ecbc6f1b1 --- /dev/null +++ b/soroban-sdk/src/tests/storage_testutils.rs @@ -0,0 +1,42 @@ +use crate::{ + self as soroban_sdk, + testutils::storage::{Instance as _, Persistent as _, Temporary as _}, + Map, Val, +}; +use soroban_sdk::{contract, Env}; + +#[contract] +pub struct Contract; + +#[test] +fn all() { + let e = Env::default(); + let id = e.register_contract(None, Contract); + + e.as_contract(&id, || { + e.storage().instance().set(&1, &2); + e.storage().instance().set(&1, &true); + e.storage().instance().set(&2, &3); + e.storage().persistent().set(&10, &20); + e.storage().persistent().set(&10, &false); + e.storage().persistent().set(&20, &30); + e.storage().temporary().set(&100, &200); + e.storage().temporary().set(&100, &()); + e.storage().temporary().set(&200, &300); + }); + + e.as_contract(&id, || { + assert_eq!( + e.storage().instance().all(), + Map::::from_array(&e, [(1.into(), true.into()), (2.into(), 3.into())]) + ); + assert_eq!( + e.storage().persistent().all(), + Map::::from_array(&e, [(10.into(), false.into()), (20.into(), 30.into())]) + ); + assert_eq!( + e.storage().temporary().all(), + Map::::from_array(&e, [(100.into(), ().into()), (200.into(), 300.into())]) + ); + }); +} diff --git a/soroban-sdk/src/testutils.rs b/soroban-sdk/src/testutils.rs index 6a0bbc517..bc9ef4852 100644 --- a/soroban-sdk/src/testutils.rs +++ b/soroban-sdk/src/testutils.rs @@ -11,6 +11,8 @@ pub use mock_auth::{ AuthorizedFunction, AuthorizedInvocation, MockAuth, MockAuthContract, MockAuthInvoke, }; +pub mod storage; + use crate::{Env, Val, Vec}; #[doc(hidden)] diff --git a/soroban-sdk/src/testutils/storage.rs b/soroban-sdk/src/testutils/storage.rs new file mode 100644 index 000000000..98d17d90b --- /dev/null +++ b/soroban-sdk/src/testutils/storage.rs @@ -0,0 +1,19 @@ +use crate::{Map, Val}; + +/// Test utilities for [`Persistent`][crate::storage::Persistent]. +pub trait Persistent { + /// Returns all data stored in persistent storage for the contract. + fn all(&self) -> Map; +} + +/// Test utilities for [`Temporary`][crate::storage::Temporary]. +pub trait Temporary { + /// Returns all data stored in temporary storage for the contract. + fn all(&self) -> Map; +} + +/// Test utilities for [`Instance`][crate::storage::Instance]. +pub trait Instance { + /// Returns all data stored in Instance storage for the contract. + fn all(&self) -> Map; +}