diff --git a/Cargo.lock b/Cargo.lock index 12c005d5c..5c9d0041e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1199,6 +1199,8 @@ dependencies = [ "proptest", "proptest-arbitrary-interop", "rand", + "serde", + "serde_json", "soroban-env-guest", "soroban-env-host", "soroban-ledger-snapshot", diff --git a/soroban-sdk/Cargo.toml b/soroban-sdk/Cargo.toml index dac353317..6f39e8776 100644 --- a/soroban-sdk/Cargo.toml +++ b/soroban-sdk/Cargo.toml @@ -25,6 +25,8 @@ soroban-env-host = { workspace = true, features = [] } soroban-ledger-snapshot = { workspace = true } stellar-strkey = { workspace = true } arbitrary = { version = "1.3.0", features = ["derive"], optional = true } +serde = { version = "1.0.0", features = ["derive"] } +serde_json = "1.0.0" ed25519-dalek = { version = "2.0.0", features = ["rand_core"], optional = true } # match the version of rand used in dalek rand = "0.8.5" diff --git a/soroban-sdk/src/address.rs b/soroban-sdk/src/address.rs index 52d018c94..37b1eb28d 100644 --- a/soroban-sdk/src/address.rs +++ b/soroban-sdk/src/address.rs @@ -26,7 +26,7 @@ use crate::{unwrap::UnwrapInfallible, Bytes, Vec}; /// that allow customizing authentication logic and adding custom authorization /// rules. /// -/// In tests Addresses should be generated via `Address::random()`. +/// In tests Addresses should be generated via `Address::generate()`. #[derive(Clone)] pub struct Address { env: Env, @@ -299,18 +299,19 @@ impl Address { } } -#[cfg(not(target_family = "wasm"))] +#[cfg(any(not(target_family = "wasm"), test, feature = "testutils"))] use crate::env::xdr::Hash; -#[cfg(any(test, feature = "testutils"))] -use crate::testutils::random; use crate::unwrap::UnwrapOptimized; #[cfg(any(test, feature = "testutils"))] #[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))] impl crate::testutils::Address for Address { - fn random(env: &Env) -> Self { - let sc_addr = ScVal::Address(ScAddress::Contract(Hash(random()))); - Self::try_from_val(env, &sc_addr).unwrap() + fn generate(env: &Env) -> Self { + Self::try_from_val( + env, + &ScAddress::Contract(Hash(env.with_generator(|mut g| g.address()))), + ) + .unwrap() } } diff --git a/soroban-sdk/src/env.rs b/soroban-sdk/src/env.rs index 4e2db06b7..b3963d849 100644 --- a/soroban-sdk/src/env.rs +++ b/soroban-sdk/src/env.rs @@ -131,6 +131,8 @@ use internal::{ pub struct MaybeEnv { maybe_env_impl: internal::MaybeEnvImpl, #[cfg(any(test, feature = "testutils"))] + generators: Option>>, + #[cfg(any(test, feature = "testutils"))] snapshot: Option>, } @@ -142,6 +144,8 @@ impl TryFrom for Env { Ok(Env { env_impl: internal::EnvImpl {}, #[cfg(any(test, feature = "testutils"))] + generators: value.generators, + #[cfg(any(test, feature = "testutils"))] snapshot: value.snapshot, }) } @@ -160,6 +164,8 @@ impl MaybeEnv { Self { maybe_env_impl: internal::EnvImpl {}, #[cfg(any(test, feature = "testutils"))] + generators: None, + #[cfg(any(test, feature = "testutils"))] snapshot: None, } } @@ -172,6 +178,8 @@ impl MaybeEnv { Self { maybe_env_impl: None, #[cfg(any(test, feature = "testutils"))] + generators: None, + #[cfg(any(test, feature = "testutils"))] snapshot: None, } } @@ -183,6 +191,8 @@ impl From for MaybeEnv { MaybeEnv { maybe_env_impl: value.env_impl, #[cfg(any(test, feature = "testutils"))] + generators: Some(value.generators), + #[cfg(any(test, feature = "testutils"))] snapshot: value.snapshot, } } @@ -197,6 +207,8 @@ impl TryFrom for Env { Ok(Env { env_impl, #[cfg(any(test, feature = "testutils"))] + generators: value.generators.unwrap_or_default(), + #[cfg(any(test, feature = "testutils"))] snapshot: value.snapshot, }) } else { @@ -211,6 +223,8 @@ impl From for MaybeEnv { MaybeEnv { maybe_env_impl: Some(value.env_impl), #[cfg(any(test, feature = "testutils"))] + generators: Some(value.generators), + #[cfg(any(test, feature = "testutils"))] snapshot: value.snapshot, } } @@ -228,6 +242,8 @@ impl From for MaybeEnv { pub struct Env { env_impl: internal::EnvImpl, #[cfg(any(test, feature = "testutils"))] + generators: Rc>, + #[cfg(any(test, feature = "testutils"))] snapshot: Option>, } @@ -417,12 +433,14 @@ impl Env { use crate::auth; #[cfg(any(test, feature = "testutils"))] use crate::testutils::{ - budget::Budget, random, Address as _, AuthorizedInvocation, ContractFunctionSet, Ledger as _, - MockAuth, MockAuthContract, + budget::Budget, Address as _, AuthorizedInvocation, ContractFunctionSet, Generators, + Ledger as _, MockAuth, MockAuthContract, Snapshot, }; #[cfg(any(test, feature = "testutils"))] use crate::{Bytes, BytesN}; #[cfg(any(test, feature = "testutils"))] +use core::{cell::RefCell, cell::RefMut}; +#[cfg(any(test, feature = "testutils"))] use soroban_ledger_snapshot::LedgerSnapshot; #[cfg(any(test, feature = "testutils"))] use std::{path::Path, rc::Rc}; @@ -440,6 +458,11 @@ impl Env { &self.env_impl } + #[doc(hidden)] + pub(crate) fn with_generator(&self, f: impl FnOnce(RefMut<'_, Generators>) -> T) -> T { + f((*self.generators).borrow_mut()) + } + fn default_with_testutils() -> Env { struct EmptySnapshotSource(); @@ -470,28 +493,33 @@ impl Env { max_entry_ttl: 6_312_000, }; - Env::new_for_testutils(rf, None, info) + Env::new_for_testutils(rf, None, info, None) } /// Used by multiple constructors to configure test environments consistently. fn new_for_testutils( recording_footprint: Rc, - snapshot: Option>, + generators: Option>>, ledger_info: internal::LedgerInfo, + snapshot: Option>, ) -> Env { let storage = internal::storage::Storage::with_recording_footprint(recording_footprint); let budget = internal::budget::Budget::default(); let env_impl = internal::EnvImpl::with_storage_and_budget(storage, budget.clone()); env_impl .set_source_account(xdr::AccountId(xdr::PublicKey::PublicKeyTypeEd25519( - xdr::Uint256(random()), + xdr::Uint256([0; 32]), ))) .unwrap(); env_impl .set_diagnostic_level(internal::DiagnosticLevel::Debug) .unwrap(); env_impl.set_base_prng_seed([0; 32]).unwrap(); - let env = Env { env_impl, snapshot }; + let env = Env { + env_impl, + generators: generators.unwrap_or_default(), + snapshot, + }; env.ledger().set(ledger_info); @@ -501,8 +529,8 @@ impl Env { /// Register a contract with the [Env] for testing. /// /// Passing a contract ID for the first arguments registers the contract - /// with that contract ID. Providing `None` causes a random ID to be - /// assigned to the contract. + /// with that contract ID. Providing `None` causes the Env to generate a new + /// contract ID that is assigned to the contract. /// /// Registering a contract that is already registered replaces it. /// @@ -558,7 +586,7 @@ impl Env { let contract_id = if let Some(contract_id) = contract_id.into() { contract_id.clone() } else { - Address::random(self) + Address::generate(self) }; self.env_impl .register_test_contract( @@ -572,8 +600,8 @@ impl Env { /// Register a contract in a WASM file with the [Env] for testing. /// /// Passing a contract ID for the first arguments registers the contract - /// with that contract ID. Providing `None` causes a random ID to be - /// assigned to the contract. + /// with that contract ID. Providing `None` causes the Env to generate a new + /// contract ID that is assigned to the contract. /// /// Registering a contract that is already registered replaces it. /// @@ -613,7 +641,7 @@ impl Env { /// is useful for using in the tests when an arbitrary token contract /// instance is needed. pub fn register_stellar_asset_contract(&self, admin: Address) -> Address { - let issuer_pk = random(); + let issuer_pk = self.with_generator(|mut g| g.address()); let issuer_id = xdr::AccountId(xdr::PublicKey::PublicKeyTypeEd25519(xdr::Uint256( issuer_pk.clone(), ))); @@ -656,7 +684,7 @@ impl Env { .unwrap(); let asset = xdr::Asset::CreditAlphanum4(xdr::AlphaNum4 { - asset_code: xdr::AssetCode4([b'a', b'a', b'a', b'a']), + asset_code: xdr::AssetCode4([b'a', b'a', b'a', 0]), issuer: issuer_id.clone(), }); let create = xdr::HostFunction::CreateContract(xdr::CreateContractArgs { @@ -704,8 +732,10 @@ impl Env { .invoke_function(xdr::HostFunction::CreateContract(xdr::CreateContractArgs { contract_id_preimage: xdr::ContractIdPreimage::Address( xdr::ContractIdPreimageFromAddress { - address: xdr::ScAddress::Contract(xdr::Hash(random())), - salt: xdr::Uint256(random()), + address: xdr::ScAddress::Contract(xdr::Hash( + self.with_generator(|mut g| g.address()), + )), + salt: xdr::Uint256([0; 32]), }, ), executable, @@ -770,7 +800,7 @@ impl Env { /// let contract_id = env.register_contract(None, HelloContract); /// /// let client = HelloContractClient::new(&env, &contract_id); - /// let addr = Address::random(&env); + /// let addr = Address::generate(&env); /// client.mock_auths(&[ /// MockAuth { /// address: &addr, @@ -841,7 +871,7 @@ impl Env { /// env.mock_all_auths(); /// /// let client = HelloContractClient::new(&env, &contract_id); - /// let addr = Address::random(&env); + /// let addr = Address::generate(&env); /// client.hello(&addr); /// } /// ``` @@ -896,7 +926,7 @@ impl Env { /// env.mock_all_auths_allowing_non_root_auth(); /// /// let client = ContractBClient::new(&env, &contract_b); - /// let addr = Address::random(&env); + /// let addr = Address::generate(&env); /// client.call_a(&contract_a, &addr); /// } /// ``` @@ -953,7 +983,7 @@ impl Env { /// let contract_id = env.register_contract(None, Contract); /// let client = ContractClient::new(&env, &contract_id); /// env.mock_all_auths(); - /// let address = Address::random(&env); + /// let address = Address::generate(&env); /// client.transfer(&address, &1000_i128); /// assert_eq!( /// env.auths(), @@ -1055,7 +1085,7 @@ impl Env { /// assert_eq!( /// e.try_invoke_contract_check_auth::( /// &account_contract.address, - /// &BytesN::random(&e), + /// &BytesN::from_array(&e, &[0; 32]), /// ().into(), /// &vec![&e], /// ), @@ -1068,7 +1098,7 @@ impl Env { /// assert_eq!( /// e.try_invoke_contract_check_auth::( /// &account_contract.address, - /// &BytesN::random(&e), + /// &BytesN::from_array(&e, &[0; 32]), /// 0_i32.into(), /// &vec![&e], /// ), @@ -1155,14 +1185,16 @@ impl Env { t.unwrap() } - /// Creates a new Env loaded with the [`LedgerSnapshot`]. + /// Creates a new Env loaded with the [`Snapshot`]. /// /// The ledger info and state in the snapshot are loaded into the Env. - pub fn from_snapshot(s: LedgerSnapshot) -> Env { - let rf = Rc::new(s.clone()); - let snapshot = rf.clone(); - let info = s.ledger_info(); - Env::new_for_testutils(rf, Some(snapshot), info) + pub fn from_snapshot(s: Snapshot) -> Env { + Env::new_for_testutils( + Rc::new(s.ledger.clone()), + Some(Rc::new(RefCell::new(s.generators))), + s.ledger.ledger_info(), + Some(Rc::new(s.ledger.clone())), + ) } /// Creates a new Env loaded with the ledger snapshot loaded from the file. @@ -1171,11 +1203,49 @@ impl Env { /// /// If there is any error reading the file. pub fn from_snapshot_file(p: impl AsRef) -> Env { - Self::from_snapshot(LedgerSnapshot::read_file(p).unwrap()) + Self::from_snapshot(Snapshot::read_file(p).unwrap()) } /// Create a snapshot from the Env's current state. - pub fn to_snapshot(&self) -> LedgerSnapshot { + pub fn to_snapshot(&self) -> Snapshot { + Snapshot { + generators: (*self.generators).borrow().clone(), + ledger: self.to_ledger_snapshot(), + } + } + + /// Create a snapshot file from the Env's current state. + /// + /// ### Panics + /// + /// If there is any error writing the file. + pub fn to_snapshot_file(&self, p: impl AsRef) { + self.to_snapshot().write_file(p).unwrap(); + } + + /// Creates a new Env loaded with the [`LedgerSnapshot`]. + /// + /// The ledger info and state in the snapshot are loaded into the Env. + pub fn from_ledger_snapshot(s: LedgerSnapshot) -> Env { + Env::new_for_testutils( + Rc::new(s.clone()), + None, + s.ledger_info(), + Some(Rc::new(s.clone())), + ) + } + + /// Creates a new Env loaded with the ledger snapshot loaded from the file. + /// + /// ### Panics + /// + /// If there is any error reading the file. + pub fn from_ledger_snapshot_file(p: impl AsRef) -> Env { + Self::from_ledger_snapshot(LedgerSnapshot::read_file(p).unwrap()) + } + + /// Create a snapshot from the Env's current state. + pub fn to_ledger_snapshot(&self) -> LedgerSnapshot { let snapshot = self.snapshot.clone().unwrap_or_default(); let mut snapshot = (*snapshot).clone(); snapshot.set_ledger_info(self.ledger().get()); @@ -1193,8 +1263,8 @@ impl Env { /// ### Panics /// /// If there is any error writing the file. - pub fn to_snapshot_file(&self, p: impl AsRef) { - self.to_snapshot().write_file(p).unwrap(); + pub fn to_ledger_snapshot_file(&self, p: impl AsRef) { + self.to_ledger_snapshot().write_file(p).unwrap(); } /// Get the budget that tracks the resources consumed for the environment. @@ -1209,6 +1279,8 @@ impl Env { Env { env_impl, #[cfg(any(test, feature = "testutils"))] + generators: Default::default(), + #[cfg(any(test, feature = "testutils"))] snapshot: None, } } diff --git a/soroban-sdk/src/tests/auth/auth_10_one.rs b/soroban-sdk/src/tests/auth/auth_10_one.rs index 8e172dd12..4a00e1add 100644 --- a/soroban-sdk/src/tests/auth/auth_10_one.rs +++ b/soroban-sdk/src/tests/auth/auth_10_one.rs @@ -25,7 +25,7 @@ fn test() { let contract_id = e.register_contract(None, Contract); let client = ContractClient::new(&e, &contract_id); - let a = Address::random(&e); + let a = Address::generate(&e); let c = client .mock_auths(&[MockAuth { diff --git a/soroban-sdk/src/tests/auth/auth_15_one_repeat.rs b/soroban-sdk/src/tests/auth/auth_15_one_repeat.rs index c66f3b4f4..d299b08b8 100644 --- a/soroban-sdk/src/tests/auth/auth_15_one_repeat.rs +++ b/soroban-sdk/src/tests/auth/auth_15_one_repeat.rs @@ -29,7 +29,7 @@ fn test() { let contract_id = e.register_contract(None, Contract); let client = ContractClient::new(&e, &contract_id); - let a = Address::random(&e); + let a = Address::generate(&e); let c = client .mock_auths(&[ diff --git a/soroban-sdk/src/tests/auth/auth_17_no_consume_requirement.rs b/soroban-sdk/src/tests/auth/auth_17_no_consume_requirement.rs index 7925158f8..663f61063 100644 --- a/soroban-sdk/src/tests/auth/auth_17_no_consume_requirement.rs +++ b/soroban-sdk/src/tests/auth/auth_17_no_consume_requirement.rs @@ -34,7 +34,7 @@ fn test() { let contract_id = e.register_contract(None, Contract); let client = ContractClient::new(&e, &contract_id); - let a = Address::random(&e); + let a = Address::generate(&e); let c = client .mock_auths(&[ diff --git a/soroban-sdk/src/tests/auth/auth_20_deep_one_address.rs b/soroban-sdk/src/tests/auth/auth_20_deep_one_address.rs index f31b90bdf..4adcdc7c8 100644 --- a/soroban-sdk/src/tests/auth/auth_20_deep_one_address.rs +++ b/soroban-sdk/src/tests/auth/auth_20_deep_one_address.rs @@ -35,7 +35,7 @@ fn test() { let contract_b_id = e.register_contract(None, ContractB); let client = ContractAClient::new(&e, &contract_a_id); - let a = Address::random(&e); + let a = Address::generate(&e); let c = client .mock_auths(&[MockAuth { @@ -65,7 +65,7 @@ fn test_auth_tree() { let contract_b_id = e.register_contract(None, ContractB); let client = ContractAClient::new(&e, &contract_a_id); - let a = Address::random(&e); + let a = Address::generate(&e); let c = client .mock_auths(&[MockAuth { diff --git a/soroban-sdk/src/tests/auth/auth_30_deep_one_address_repeat.rs b/soroban-sdk/src/tests/auth/auth_30_deep_one_address_repeat.rs index c08e1198c..10a02ed61 100644 --- a/soroban-sdk/src/tests/auth/auth_30_deep_one_address_repeat.rs +++ b/soroban-sdk/src/tests/auth/auth_30_deep_one_address_repeat.rs @@ -36,7 +36,7 @@ fn test() { let contract_b_id = e.register_contract(None, ContractB); let client = ContractAClient::new(&e, &contract_a_id); - let a = Address::random(&e); + let a = Address::generate(&e); let c = client .mock_auths(&[ @@ -80,7 +80,7 @@ fn test_auth_tree() { let contract_b_id = e.register_contract(None, ContractB); let client = ContractAClient::new(&e, &contract_a_id); - let a = Address::random(&e); + let a = Address::generate(&e); let c = client .mock_auths(&[MockAuth { diff --git a/soroban-sdk/src/tests/auth/auth_35_deep_one_address_repeat_grouped.rs b/soroban-sdk/src/tests/auth/auth_35_deep_one_address_repeat_grouped.rs index 43abc59dd..7cd8e5cb9 100644 --- a/soroban-sdk/src/tests/auth/auth_35_deep_one_address_repeat_grouped.rs +++ b/soroban-sdk/src/tests/auth/auth_35_deep_one_address_repeat_grouped.rs @@ -36,7 +36,7 @@ fn test() { let contract_b_id = e.register_contract(None, ContractB); let client = ContractAClient::new(&e, &contract_a_id); - let a = Address::random(&e); + let a = Address::generate(&e); let c = client .mock_auths(&[ @@ -80,7 +80,7 @@ fn test_auth_tree() { let contract_b_id = e.register_contract(None, ContractB); let client = ContractAClient::new(&e, &contract_a_id); - let a = Address::random(&e); + let a = Address::generate(&e); let c = client .mock_auths(&[MockAuth { diff --git a/soroban-sdk/src/tests/auth/auth_40_multi_one_address.rs b/soroban-sdk/src/tests/auth/auth_40_multi_one_address.rs index ea209ed3f..e9d151475 100644 --- a/soroban-sdk/src/tests/auth/auth_40_multi_one_address.rs +++ b/soroban-sdk/src/tests/auth/auth_40_multi_one_address.rs @@ -36,7 +36,7 @@ fn test_auth_not_allowed_with_separated_tree() { let contract_b_id = e.register_contract(None, ContractB); let client = ContractAClient::new(&e, &contract_a_id); - let a = Address::random(&e); + let a = Address::generate(&e); assert!(client .mock_auths(&[ @@ -70,7 +70,7 @@ fn test_auth_as_tree() { let contract_b_id = e.register_contract(None, ContractB); let client = ContractAClient::new(&e, &contract_a_id); - let a = Address::random(&e); + let a = Address::generate(&e); let c = client .mock_auths(&[MockAuth { diff --git a/soroban-sdk/src/tests/contract_snapshot.rs b/soroban-sdk/src/tests/contract_snapshot.rs index 0252a76c2..3a4086f71 100644 --- a/soroban-sdk/src/tests/contract_snapshot.rs +++ b/soroban-sdk/src/tests/contract_snapshot.rs @@ -24,9 +24,9 @@ fn test() { client.store(&2, &4); assert_eq!(client.get(&2), 4); - let snapshot = e.to_snapshot(); + let snapshot = e.to_ledger_snapshot(); - let e = Env::from_snapshot(snapshot); + let e = Env::from_ledger_snapshot(snapshot); let contract_id = Address::try_from_val(&e, &contract_id_xdr).unwrap(); e.register_contract(&contract_id, Contract); let client = ContractClient::new(&e, &contract_id); diff --git a/soroban-sdk/src/tests/env.rs b/soroban-sdk/src/tests/env.rs index 832de3624..5d51eb134 100644 --- a/soroban-sdk/src/tests/env.rs +++ b/soroban-sdk/src/tests/env.rs @@ -51,8 +51,8 @@ fn default_and_from_snapshot_same_settings() { c1client.test(); c2client.test(); - let c1addr2 = Address::random(&env1); - let c2addr2 = Address::random(&env2); + let c1addr2 = Address::generate(&env1); + let c2addr2 = Address::generate(&env2); let r1 = c1client.try_need_auth(&c1addr2); let r2 = c2client.try_need_auth(&c2addr2); assert_eq!( @@ -72,6 +72,35 @@ fn default_and_from_snapshot_same_settings() { let logs1 = env1.logs().all(); let logs2 = env2.logs().all(); - assert!(!logs1.is_empty()); - assert!(!logs2.is_empty()); + assert_eq!(logs1, &["[Diagnostic Event] contract:0000000000000000000000000000000000000000000000000000000000000001, topics:[log], data:\"test\""]); + assert_eq!(logs2, &["[Diagnostic Event] contract:0000000000000000000000000000000000000000000000000000000000000001, topics:[log], data:\"test\""]); +} + +#[test] +fn register_contract_deploys_predictable_contract_ids() { + let env1 = Env::default(); + let env2 = Env::from_snapshot(env1.to_snapshot()); + + let env1addr1 = env1.register_contract(None, Contract); + println!("env1 addr1 {:?}", env1addr1.contract_id()); + let env1addr2 = env1.register_contract(None, Contract); + println!("env1 addr2 {:?}", env1addr2.contract_id()); + let env2addr1 = env2.register_contract(None, Contract); + println!("env2 addr1 {:?}", env2addr1.contract_id()); + let env2addr2 = env2.register_contract(None, Contract); + println!("env2 addr2 {:?}", env2addr2.contract_id()); + + let env3 = Env::from_snapshot(env1.to_snapshot()); + let env1addr3 = env1.register_contract(None, Contract); + println!("env1 addr3 {:?}", env1addr3.contract_id()); + let env2addr3 = env2.register_contract(None, Contract); + println!("env2 addr3 {:?}", env2addr3.contract_id()); + let env3addr3 = env3.register_contract(None, Contract); + println!("env3 addr3 {:?}", env3addr3.contract_id()); + + // Check that contracts deployed in the envs are consistent and predictable. + assert_eq!(env2addr1.contract_id(), env1addr1.contract_id()); + assert_eq!(env2addr2.contract_id(), env1addr2.contract_id()); + assert_eq!(env2addr3.contract_id(), env1addr3.contract_id()); + assert_eq!(env3addr3.contract_id(), env1addr3.contract_id()); } diff --git a/soroban-sdk/src/tests/token_client.rs b/soroban-sdk/src/tests/token_client.rs index 0a5caba22..973cc59c3 100644 --- a/soroban-sdk/src/tests/token_client.rs +++ b/soroban-sdk/src/tests/token_client.rs @@ -47,7 +47,7 @@ fn test_mock_all_auth() { let env = Env::default(); - let admin = Address::random(&env); + let admin = Address::generate(&env); let token_contract_id = env.register_stellar_asset_contract(admin); let contract_id = env.register_contract(None, TestContract); @@ -56,8 +56,8 @@ fn test_mock_all_auth() { let token_client = TokenClient::new(&env, &client.get_token()); assert_eq!(token_client.decimals(), 7); - let from = Address::random(&env); - let spender = Address::random(&env); + let from = Address::generate(&env); + let spender = Address::generate(&env); // `TestContract` doesn't call `require_auth` before calling into token, // thus we need to allow non-root auth and regular `mock_all_auths` will @@ -94,7 +94,7 @@ fn test_mock_auth() { let env = Env::default(); - let admin = Address::random(&env); + let admin = Address::generate(&env); let token_contract_id = env.register_stellar_asset_contract(admin); let contract_id = env.register_contract(None, TestContract); @@ -103,8 +103,8 @@ fn test_mock_auth() { let token_client = TokenClient::new(&env, &client.get_token()); assert_eq!(token_client.decimals(), 7); - let from = Address::random(&env); - let spender = Address::random(&env); + let from = Address::generate(&env); + let spender = Address::generate(&env); client .mock_auths(&[MockAuth { diff --git a/soroban-sdk/src/testutils.rs b/soroban-sdk/src/testutils.rs index 15fdebce8..c19201ad3 100644 --- a/soroban-sdk/src/testutils.rs +++ b/soroban-sdk/src/testutils.rs @@ -16,6 +16,74 @@ pub use mock_auth::{ pub mod storage; use crate::{Env, Val, Vec}; +use soroban_ledger_snapshot::LedgerSnapshot; + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct Snapshot { + pub generators: Generators, + pub ledger: LedgerSnapshot, +} + +impl Snapshot { + // Read in a [`Snapshot`] from a reader. + pub fn read(r: impl std::io::Read) -> Result { + Ok(serde_json::from_reader::<_, Snapshot>(r)?) + } + + // Read in a [`Snapshot`] from a file. + pub fn read_file(p: impl AsRef) -> Result { + Self::read(std::fs::File::open(p)?) + } + + // Write a [`Snapshot`] to a writer. + pub fn write(&self, w: impl std::io::Write) -> Result<(), std::io::Error> { + Ok(serde_json::to_writer_pretty(w, self)?) + } + + // Write a [`Snapshot`] to file. + pub fn write_file(&self, p: impl AsRef) -> Result<(), std::io::Error> { + let p = p.as_ref(); + if let Some(dir) = p.parent() { + if !dir.exists() { + std::fs::create_dir_all(dir)?; + } + } + self.write(std::fs::File::create(p)?) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct Generators { + address: u64, + nonce: u64, +} + +impl Default for Generators { + fn default() -> Generators { + Generators { + address: 0, + nonce: 0, + } + } +} + +impl Generators { + pub fn address(&mut self) -> [u8; 32] { + self.address = self.address.checked_add(1).unwrap(); + let b: [u8; 8] = self.address.to_be_bytes(); + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, b[0], b[1], + b[2], b[3], b[4], b[5], b[6], b[7], + ] + } + + pub fn nonce(&mut self) -> i64 { + self.nonce = self.nonce.checked_add(1).unwrap(); + self.nonce as i64 + } +} #[doc(hidden)] pub trait ContractFunctionSet { @@ -179,10 +247,10 @@ pub(crate) fn random() -> [u8; N] { } pub trait Address { - /// Create a random Address. + /// Generate a new Address. /// /// Implementation note: this always builds the contract addresses now. This /// shouldn't normally matter though, as contracts should be agnostic to /// the underlying Address value. - fn random(env: &Env) -> crate::Address; + fn generate(env: &Env) -> crate::Address; } diff --git a/soroban-sdk/src/testutils/mock_auth.rs b/soroban-sdk/src/testutils/mock_auth.rs index 0bf666a21..3a617693f 100644 --- a/soroban-sdk/src/testutils/mock_auth.rs +++ b/soroban-sdk/src/testutils/mock_auth.rs @@ -1,7 +1,6 @@ #![cfg(any(test, feature = "testutils"))] use crate::{contract, contractimpl, xdr, Address, Env, Symbol, TryFromVal, Val, Vec}; -use rand::{thread_rng, Rng}; use super::Ledger; @@ -38,7 +37,7 @@ impl<'a> From<&MockAuth<'a>> for xdr::SorobanAuthorizationEntry { root_invocation: value.invoke.into(), credentials: xdr::SorobanCredentials::Address(xdr::SorobanAddressCredentials { address: value.address.try_into().unwrap(), - nonce: thread_rng().gen(), + nonce: env.with_generator(|mut g| g.nonce()), signature_expiration_ledger: curr_ledger + max_entry_ttl - 1, signature: xdr::ScVal::Void, }), diff --git a/tests/auth/src/lib.rs b/tests/auth/src/lib.rs index 08fe77995..ffd7f3ae9 100644 --- a/tests/auth/src/lib.rs +++ b/tests/auth/src/lib.rs @@ -36,7 +36,7 @@ mod test_a { let contract_id = e.register_contract(None, ContractA); let client = ContractAClient::new(&e, &contract_id); - let a = Address::random(&e); + let a = Address::generate(&e); let r = client.mock_all_auths().fn1(&a); assert_eq!(r, 2); @@ -63,7 +63,7 @@ mod test_a { let contract_id = e.register_contract(None, ContractA); let client = ContractAClient::new(&e, &contract_id); - let a = Address::random(&e); + let a = Address::generate(&e); let r = client .mock_auths(&[MockAuth { @@ -255,7 +255,7 @@ mod test_b { let contract_b_id = e.register_contract(None, ContractB); let client = ContractBClient::new(&e, &contract_b_id); - let a = Address::random(&e); + let a = Address::generate(&e); let r = client.mock_all_auths().fn2(&a, &contract_a_id); assert_eq!(r, 2); @@ -290,7 +290,7 @@ mod test_b { let contract_b_id = e.register_contract(None, ContractB); let client = ContractBClient::new(&e, &contract_b_id); - let a = Address::random(&e); + let a = Address::generate(&e); let r = client .mock_auths(&[MockAuth {