soroban_sdk/env.rs
1use core::convert::Infallible;
2
3#[cfg(target_family = "wasm")]
4pub mod internal {
5 use core::convert::Infallible;
6
7 pub use soroban_env_guest::*;
8 pub type EnvImpl = Guest;
9 pub type MaybeEnvImpl = Guest;
10
11 // In the Guest case, Env::Error is already Infallible so there is no work
12 // to do to "reject an error": if an error occurs in the environment, the
13 // host will trap our VM and we'll never get here at all.
14 pub(crate) fn reject_err<T>(_env: &Guest, r: Result<T, Infallible>) -> Result<T, Infallible> {
15 r
16 }
17}
18
19#[cfg(not(target_family = "wasm"))]
20pub mod internal {
21 use core::convert::Infallible;
22
23 pub use soroban_env_host::*;
24 pub type EnvImpl = Host;
25 pub type MaybeEnvImpl = Option<Host>;
26
27 // When we have `feature="testutils"` (or are in cfg(test)) we enable feature
28 // `soroban-env-{common,host}/testutils` which in turn adds the helper method
29 // `Env::escalate_error_to_panic` to the Env trait.
30 //
31 // When this is available we want to use it, because it works in concert
32 // with a _different_ part of the host that's also `testutils`-gated: the
33 // mechanism for emulating the WASM VM error-handling semantics with native
34 // contracts. In particular when a WASM contract calls a host function that
35 // fails with some error E, the host traps the VM (not returning to it at
36 // all) and propagates E to the caller of the contract. This is simulated in
37 // the native case by returning a (nontrivial) error E to us here, which we
38 // then "reject" back to the host, which stores E in a temporary cell inside
39 // any `TestContract` frame in progress and then _panics_, unwinding back to
40 // a panic-catcher it installed when invoking the `TestContract` frame, and
41 // then extracting E from the frame and returning it to its caller. This
42 // simulates the "crash, but catching the error" behavior of the WASM case.
43 // This only works if we panic via `escalate_error_to_panic`.
44 //
45 // (The reason we don't just panic_any() here and let the panic-catcher do a
46 // type-based catch is that there might _be_ no panic-catcher around us, and
47 // we want to print out a nice error message in that case too, which
48 // panic_any() does not do us the favor of producing. This is all very
49 // subtle. See also soroban_env_host::Host::escalate_error_to_panic.)
50 #[cfg(any(test, feature = "testutils"))]
51 pub(crate) fn reject_err<T>(env: &Host, r: Result<T, HostError>) -> Result<T, Infallible> {
52 r.map_err(|e| env.escalate_error_to_panic(e))
53 }
54
55 // When we're _not_ in a cfg enabling `soroban-env-{common,host}/testutils`,
56 // there is no `Env::escalate_error_to_panic` to call, so we just panic
57 // here. But this is ok because in that case there is also no multi-contract
58 // calling machinery set up, nor probably any panic-catcher installed that
59 // we need to hide error values for the benefit of. Any panic in this case
60 // is probably going to unwind completely anyways. No special case needed.
61 #[cfg(not(any(test, feature = "testutils")))]
62 pub(crate) fn reject_err<T>(_env: &Host, r: Result<T, HostError>) -> Result<T, Infallible> {
63 r.map_err(|e| panic!("{:?}", e))
64 }
65
66 #[doc(hidden)]
67 impl<F, T> Convert<F, T> for super::Env
68 where
69 EnvImpl: Convert<F, T>,
70 {
71 type Error = <EnvImpl as Convert<F, T>>::Error;
72 fn convert(&self, f: F) -> Result<T, Self::Error> {
73 self.env_impl.convert(f)
74 }
75 }
76}
77
78pub use internal::xdr;
79pub use internal::ContractTtlExtension;
80pub use internal::ConversionError;
81pub use internal::EnvBase;
82pub use internal::Error;
83pub use internal::MapObject;
84pub use internal::SymbolStr;
85pub use internal::TryFromVal;
86pub use internal::TryIntoVal;
87pub use internal::Val;
88pub use internal::VecObject;
89
90pub trait IntoVal<E: internal::Env, T> {
91 fn into_val(&self, e: &E) -> T;
92}
93
94pub trait FromVal<E: internal::Env, T> {
95 fn from_val(e: &E, v: &T) -> Self;
96}
97
98impl<E: internal::Env, T, U> FromVal<E, T> for U
99where
100 U: TryFromVal<E, T>,
101{
102 fn from_val(e: &E, v: &T) -> Self {
103 U::try_from_val(e, v).unwrap_optimized()
104 }
105}
106
107impl<E: internal::Env, T, U> IntoVal<E, T> for U
108where
109 T: FromVal<E, Self>,
110{
111 fn into_val(&self, e: &E) -> T {
112 T::from_val(e, self)
113 }
114}
115
116use crate::auth::InvokerContractAuthEntry;
117use crate::custom_account::CustomAccount;
118use crate::unwrap::UnwrapInfallible;
119use crate::unwrap::UnwrapOptimized;
120use crate::InvokeError;
121use crate::{
122 crypto::Crypto, deploy::Deployer, events::Events, ledger::Ledger, logs::Logs, prng::Prng,
123 storage::Storage, Address, Vec,
124};
125use internal::{
126 AddressObject, Bool, BytesObject, DurationObject, I128Object, I256Object, I256Val, I64Object,
127 MuxedAddressObject, StorageType, StringObject, Symbol, SymbolObject, TimepointObject,
128 U128Object, U256Object, U256Val, U32Val, U64Object, U64Val, Void,
129};
130
131#[doc(hidden)]
132#[derive(Clone)]
133pub struct MaybeEnv {
134 maybe_env_impl: internal::MaybeEnvImpl,
135 #[cfg(any(test, feature = "testutils"))]
136 test_state: Option<EnvTestState>,
137}
138
139#[cfg(target_family = "wasm")]
140impl TryFrom<MaybeEnv> for Env {
141 type Error = Infallible;
142
143 fn try_from(_value: MaybeEnv) -> Result<Self, Self::Error> {
144 Ok(Env {
145 env_impl: internal::EnvImpl {},
146 })
147 }
148}
149
150impl Default for MaybeEnv {
151 fn default() -> Self {
152 Self::none()
153 }
154}
155
156#[cfg(target_family = "wasm")]
157impl MaybeEnv {
158 // separate function to be const
159 pub const fn none() -> Self {
160 Self {
161 maybe_env_impl: internal::EnvImpl {},
162 }
163 }
164}
165
166#[cfg(not(target_family = "wasm"))]
167impl MaybeEnv {
168 // separate function to be const
169 pub const fn none() -> Self {
170 Self {
171 maybe_env_impl: None,
172 #[cfg(any(test, feature = "testutils"))]
173 test_state: None,
174 }
175 }
176}
177
178#[cfg(target_family = "wasm")]
179impl From<Env> for MaybeEnv {
180 fn from(value: Env) -> Self {
181 MaybeEnv {
182 maybe_env_impl: value.env_impl,
183 }
184 }
185}
186
187#[cfg(not(target_family = "wasm"))]
188impl TryFrom<MaybeEnv> for Env {
189 type Error = ConversionError;
190
191 fn try_from(value: MaybeEnv) -> Result<Self, Self::Error> {
192 if let Some(env_impl) = value.maybe_env_impl {
193 Ok(Env {
194 env_impl,
195 #[cfg(any(test, feature = "testutils"))]
196 test_state: value.test_state.unwrap_or_default(),
197 })
198 } else {
199 Err(ConversionError)
200 }
201 }
202}
203
204#[cfg(not(target_family = "wasm"))]
205impl From<Env> for MaybeEnv {
206 fn from(value: Env) -> Self {
207 MaybeEnv {
208 maybe_env_impl: Some(value.env_impl.clone()),
209 #[cfg(any(test, feature = "testutils"))]
210 test_state: Some(value.test_state.clone()),
211 }
212 }
213}
214
215/// The [Env] type provides access to the environment the contract is executing
216/// within.
217///
218/// The [Env] provides access to information about the currently executing
219/// contract, who invoked it, contract data, functions for signing, hashing,
220/// etc.
221///
222/// Most types require access to an [Env] to be constructed or converted.
223#[derive(Clone)]
224pub struct Env {
225 env_impl: internal::EnvImpl,
226 #[cfg(any(test, feature = "testutils"))]
227 test_state: EnvTestState,
228}
229
230impl Default for Env {
231 #[cfg(not(any(test, feature = "testutils")))]
232 fn default() -> Self {
233 Self {
234 env_impl: Default::default(),
235 }
236 }
237
238 #[cfg(any(test, feature = "testutils"))]
239 fn default() -> Self {
240 Self::new_with_config(EnvTestConfig::default())
241 }
242}
243
244#[cfg(any(test, feature = "testutils"))]
245#[derive(Default, Clone)]
246struct LastEnv {
247 test_name: String,
248 number: usize,
249}
250
251#[cfg(any(test, feature = "testutils"))]
252thread_local! {
253 static LAST_ENV: RefCell<Option<LastEnv>> = RefCell::new(None);
254}
255
256#[cfg(any(test, feature = "testutils"))]
257#[derive(Clone, Default)]
258struct EnvTestState {
259 test_name: Option<String>,
260 number: usize,
261 config: EnvTestConfig,
262 generators: Rc<RefCell<Generators>>,
263 auth_snapshot: Rc<RefCell<AuthSnapshot>>,
264 snapshot: Option<Rc<LedgerSnapshot>>,
265}
266
267/// Config for changing the default behavior of the Env when used in tests.
268#[cfg(any(test, feature = "testutils"))]
269#[derive(Clone)]
270pub struct EnvTestConfig {
271 /// Capture a test snapshot when the Env is dropped, causing a test snapshot
272 /// JSON file to be written to disk when the Env is no longer referenced.
273 /// Defaults to true.
274 pub capture_snapshot_at_drop: bool,
275 // NOTE: Next time a field needs to be added to EnvTestConfig it will be a breaking change,
276 // take the opportunity to make the current field private, new fields private, and settable via
277 // functions. Why: So that it is the last time a breaking change is needed to the type.
278}
279
280#[cfg(any(test, feature = "testutils"))]
281impl Default for EnvTestConfig {
282 fn default() -> Self {
283 Self {
284 capture_snapshot_at_drop: true,
285 }
286 }
287}
288
289impl Env {
290 /// Panic with the given error.
291 ///
292 /// Equivalent to `panic!`, but with an error value instead of a string.
293 #[doc(hidden)]
294 #[cfg(feature = "experimental_spec_shaking_v2")]
295 #[inline(always)]
296 pub fn panic_with_error<I>(&self, error: I) -> !
297 where
298 I: Into<internal::Error> + crate::SpecShakingMarker,
299 {
300 I::spec_shaking_marker();
301 self.panic_with_error_inner(error.into())
302 }
303
304 /// Panic with the given error.
305 ///
306 /// Equivalent to `panic!`, but with an error value instead of a string.
307 #[doc(hidden)]
308 #[cfg(not(feature = "experimental_spec_shaking_v2"))]
309 #[inline(always)]
310 pub fn panic_with_error(&self, error: impl Into<internal::Error>) -> ! {
311 self.panic_with_error_inner(error.into())
312 }
313
314 #[inline(always)]
315 fn panic_with_error_inner(&self, error: internal::Error) -> ! {
316 _ = internal::Env::fail_with_error(self, error);
317 #[cfg(target_family = "wasm")]
318 core::arch::wasm32::unreachable();
319 #[cfg(not(target_family = "wasm"))]
320 unreachable!();
321 }
322
323 /// Get a [Storage] for accessing and updating persistent data owned by the
324 /// currently executing contract.
325 #[inline(always)]
326 pub fn storage(&self) -> Storage {
327 Storage::new(self)
328 }
329
330 /// Get [Events] for publishing events associated with the
331 /// currently executing contract.
332 #[inline(always)]
333 pub fn events(&self) -> Events {
334 Events::new(self)
335 }
336
337 /// Get a [Ledger] for accessing the current ledger.
338 #[inline(always)]
339 pub fn ledger(&self) -> Ledger {
340 Ledger::new(self)
341 }
342
343 /// Get a deployer for deploying contracts.
344 #[inline(always)]
345 pub fn deployer(&self) -> Deployer {
346 Deployer::new(self)
347 }
348
349 /// Get an accessor for functions used for custom account implementation.
350 ///
351 /// The accessor's methods may only be called within `__check_auth` contract
352 /// function.
353 #[inline(always)]
354 pub fn custom_account(&self) -> CustomAccount {
355 CustomAccount::new(self)
356 }
357
358 /// Get a [Crypto] for accessing the current cryptographic functions.
359 #[inline(always)]
360 pub fn crypto(&self) -> Crypto {
361 Crypto::new(self)
362 }
363
364 /// # ⚠️ Hazardous Materials
365 ///
366 /// Get a [CryptoHazmat][crate::crypto::CryptoHazmat] for accessing the
367 /// cryptographic functions that are not generally recommended. Using them
368 /// incorrectly can introduce security vulnerabilities. Use [Crypto] if
369 /// possible.
370 #[cfg_attr(any(test, feature = "hazmat-crypto"), visibility::make(pub))]
371 #[cfg_attr(feature = "docs", doc(cfg(feature = "hazmat-crypto")))]
372 #[inline(always)]
373 pub(crate) fn crypto_hazmat(&self) -> crate::crypto::CryptoHazmat {
374 crate::crypto::CryptoHazmat::new(self)
375 }
376
377 /// Get a [Prng] for accessing the current functions which provide pseudo-randomness.
378 ///
379 /// # Warning
380 ///
381 /// **The pseudo-random generator returned is not suitable for
382 /// security-sensitive work.**
383 #[inline(always)]
384 pub fn prng(&self) -> Prng {
385 Prng::new(self)
386 }
387
388 /// Get the Address object corresponding to the current executing contract.
389 pub fn current_contract_address(&self) -> Address {
390 let address = internal::Env::get_current_contract_address(self).unwrap_infallible();
391 unsafe { Address::unchecked_new(self.clone(), address) }
392 }
393
394 #[doc(hidden)]
395 pub(crate) fn require_auth_for_args(&self, address: &Address, args: Vec<Val>) {
396 internal::Env::require_auth_for_args(self, address.to_object(), args.to_object())
397 .unwrap_infallible();
398 }
399
400 #[doc(hidden)]
401 pub(crate) fn require_auth(&self, address: &Address) {
402 internal::Env::require_auth(self, address.to_object()).unwrap_infallible();
403 }
404
405 /// Invokes a function of a contract that is registered in the [Env].
406 ///
407 /// # Panics
408 ///
409 /// Will panic if the `contract_id` does not match a registered contract,
410 /// `func` does not match a function of the referenced contract, or the
411 /// number of `args` do not match the argument count of the referenced
412 /// contract function.
413 ///
414 /// Will panic if the contract that is invoked fails or aborts in anyway.
415 ///
416 /// Will panic if the value returned from the contract cannot be converted
417 /// into the type `T`.
418 pub fn invoke_contract<T>(
419 &self,
420 contract_address: &Address,
421 func: &crate::Symbol,
422 args: Vec<Val>,
423 ) -> T
424 where
425 T: TryFromVal<Env, Val>,
426 {
427 let rv = internal::Env::call(
428 self,
429 contract_address.to_object(),
430 func.to_symbol_val(),
431 args.to_object(),
432 )
433 .unwrap_infallible();
434 T::try_from_val(self, &rv)
435 .map_err(|_| ConversionError)
436 .unwrap()
437 }
438
439 /// Invokes a function of a contract that is registered in the [Env],
440 /// returns an error if the invocation fails for any reason.
441 pub fn try_invoke_contract<T, E>(
442 &self,
443 contract_address: &Address,
444 func: &crate::Symbol,
445 args: Vec<Val>,
446 ) -> Result<Result<T, T::Error>, Result<E, InvokeError>>
447 where
448 T: TryFromVal<Env, Val>,
449 E: TryFrom<Error>,
450 E::Error: Into<InvokeError>,
451 {
452 let rv = internal::Env::try_call(
453 self,
454 contract_address.to_object(),
455 func.to_symbol_val(),
456 args.to_object(),
457 )
458 .unwrap_infallible();
459 match internal::Error::try_from_val(self, &rv) {
460 Ok(err) => Err(E::try_from(err).map_err(Into::into)),
461 Err(ConversionError) => Ok(T::try_from_val(self, &rv)),
462 }
463 }
464
465 /// Authorizes sub-contract calls on behalf of the current contract.
466 ///
467 /// All the direct calls that the current contract performs are always
468 /// considered to have been authorized. This is only needed to authorize
469 /// deeper calls that originate from the next contract call from the current
470 /// contract.
471 ///
472 /// For example, if the contract A calls contract B, contract
473 /// B calls contract C and contract C calls `A.require_auth()`, then an
474 /// entry corresponding to C call has to be passed in `auth_entries`. It
475 /// doesn't matter if contract B called `require_auth` or not. If contract A
476 /// calls contract B again, then `authorize_as_current_contract` has to be
477 /// called again with the respective entries.
478 ///
479 /// When testing a contract call that uses `authorize_as_current_contract`, avoid
480 /// using [`mock_all_auths_allowing_non_root_auth`][Self::mock_all_auths_allowing_non_root_auth].
481 /// A test that uses this mock will not fail if a missing or incorrect
482 /// `authorize_as_current_contract` call is present. It is recommended to use other
483 /// authorization mocking functions like [`mock_auths`][Self::mock_auths]
484 /// or [`set_auths`][Self::set_auths] if needed.
485 ///
486 /// ### Examples
487 /// ```
488 /// use soroban_sdk::{
489 /// auth::{ContractContext, InvokerContractAuthEntry, SubContractInvocation},
490 /// contract, contractimpl, vec, Address, Env, IntoVal, Symbol,
491 /// };
492 ///
493 /// // Contract C performs authorization for addr
494 /// #[contract]
495 /// pub struct ContractC;
496 ///
497 /// #[contractimpl]
498 /// impl ContractC {
499 /// pub fn do_auth(_env: Env, addr: Address, amount: i128) -> i128 {
500 /// addr.require_auth();
501 /// amount
502 /// }
503 /// }
504 ///
505 /// // Contract B performs authorization for `addr` and invokes Contract C with
506 /// // `addr` and `amount` as arguments.
507 /// #[contract]
508 /// pub struct ContractB;
509 ///
510 /// #[contractimpl]
511 /// impl ContractB {
512 /// pub fn call_c(env: Env, addr: Address, contract_c: Address, amount: i128) -> i128 {
513 /// addr.require_auth();
514 /// ContractCClient::new(&env, &contract_c).do_auth(&addr, &amount)
515 /// }
516 /// }
517 ///
518 /// // Contract A authorizes Contract B to call Contract C on its behalf with `addr` and
519 /// // `amount` as arguments.
520 /// #[contract]
521 /// pub struct ContractA;
522 ///
523 /// #[contractimpl]
524 /// impl ContractA {
525 /// pub fn call_b(env: Env, contract_b: Address, contract_c: Address, amount: i128) -> i128 {
526 /// let curr_contract = env.current_contract_address();
527 /// // Authorize the sub-call Contract B makes to Contract C
528 /// env.authorize_as_current_contract(vec![
529 /// &env,
530 /// InvokerContractAuthEntry::Contract(SubContractInvocation {
531 /// context: ContractContext {
532 /// contract: contract_c.clone(),
533 /// fn_name: Symbol::new(&env, "do_auth"),
534 /// args: vec![&env, curr_contract.into_val(&env), amount.into_val(&env)],
535 /// },
536 /// sub_invocations: vec![&env],
537 /// }),
538 /// ]);
539 /// ContractBClient::new(&env, &contract_b).call_c(&curr_contract, &contract_c, &amount)
540 /// }
541 /// }
542 ///
543 /// #[test]
544 /// fn test() {
545 /// # }
546 /// # fn main() {
547 /// let env = Env::default();
548 /// let contract_a = env.register(ContractA, ());
549 /// let contract_b = env.register(ContractB, ());
550 /// let contract_c = env.register(ContractC, ());
551 ///
552 /// // Auths are not mocked to ensure `authorize_as_current_contract`
553 /// // is working as intended. If Contract A includes additional auths, consider
554 /// // using `mock_auths` or `set_auths` for those authorizations.
555 ///
556 /// let client = ContractAClient::new(&env, &contract_a);
557 /// let result = client.call_b(&contract_b, &contract_c, &100);
558 /// assert_eq!(result, 100);
559 /// }
560 /// ```
561 pub fn authorize_as_current_contract(&self, auth_entries: Vec<InvokerContractAuthEntry>) {
562 internal::Env::authorize_as_curr_contract(self, auth_entries.to_object())
563 .unwrap_infallible();
564 }
565
566 /// Get the [Logs] for logging debug events.
567 #[inline(always)]
568 #[deprecated(note = "use [Env::logs]")]
569 #[doc(hidden)]
570 pub fn logger(&self) -> Logs {
571 self.logs()
572 }
573
574 /// Get the [Logs] for logging debug events.
575 #[inline(always)]
576 pub fn logs(&self) -> Logs {
577 Logs::new(self)
578 }
579}
580
581#[doc(hidden)]
582#[cfg(not(target_family = "wasm"))]
583impl Env {
584 pub(crate) fn is_same_env(&self, other: &Self) -> bool {
585 self.env_impl.is_same(&other.env_impl)
586 }
587}
588
589#[cfg(any(test, feature = "testutils"))]
590use crate::testutils::cost_estimate::CostEstimate;
591#[cfg(any(test, feature = "testutils"))]
592use crate::{
593 auth,
594 testutils::{
595 budget::Budget, cost_estimate::NetworkInvocationResourceLimits, default_ledger_info,
596 Address as _, AuthSnapshot, AuthorizedInvocation, ContractFunctionSet, EventsSnapshot,
597 Generators, Ledger as _, MockAuth, MockAuthContract, Register, Snapshot,
598 SnapshotSourceInput, StellarAssetContract, StellarAssetIssuer,
599 },
600 Bytes, BytesN, ConstructorArgs,
601};
602#[cfg(any(test, feature = "testutils"))]
603use core::{cell::RefCell, cell::RefMut};
604#[cfg(any(test, feature = "testutils"))]
605use internal::{InvocationEvent, InvocationResourceLimits};
606#[cfg(any(test, feature = "testutils"))]
607use soroban_ledger_snapshot::LedgerSnapshot;
608#[cfg(any(test, feature = "testutils"))]
609use std::{path::Path, rc::Rc};
610#[cfg(any(test, feature = "testutils"))]
611use xdr::{LedgerEntry, LedgerKey, LedgerKeyContractData, SorobanAuthorizationEntry};
612
613#[cfg(any(test, feature = "testutils"))]
614#[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))]
615impl Env {
616 #[doc(hidden)]
617 pub fn in_contract(&self) -> bool {
618 self.env_impl.has_frame().unwrap()
619 }
620
621 #[doc(hidden)]
622 pub fn host(&self) -> &internal::Host {
623 &self.env_impl
624 }
625
626 #[doc(hidden)]
627 pub(crate) fn with_generator<T>(&self, f: impl FnOnce(RefMut<'_, Generators>) -> T) -> T {
628 f((*self.test_state.generators).borrow_mut())
629 }
630
631 /// Create an Env with the test config.
632 pub fn new_with_config(config: EnvTestConfig) -> Env {
633 struct EmptySnapshotSource();
634
635 impl internal::storage::SnapshotSource for EmptySnapshotSource {
636 fn get(
637 &self,
638 _key: &Rc<xdr::LedgerKey>,
639 ) -> Result<Option<(Rc<xdr::LedgerEntry>, Option<u32>)>, soroban_env_host::HostError>
640 {
641 Ok(None)
642 }
643 }
644
645 let rf = Rc::new(EmptySnapshotSource());
646
647 Env::new_for_testutils(config, rf, None, None, None)
648 }
649
650 /// Change the test config of an Env.
651 pub fn set_config(&mut self, config: EnvTestConfig) {
652 self.test_state.config = config;
653 }
654
655 /// Used by multiple constructors to configure test environments consistently.
656 fn new_for_testutils(
657 config: EnvTestConfig,
658 recording_footprint: Rc<dyn internal::storage::SnapshotSource>,
659 generators: Option<Rc<RefCell<Generators>>>,
660 ledger_info: Option<internal::LedgerInfo>,
661 snapshot: Option<Rc<LedgerSnapshot>>,
662 ) -> Env {
663 // Store in the Env the name of the test it is for, and a number so that within a test
664 // where one or more Env's have been created they can be uniquely identified relative to
665 // each other.
666
667 let test_name = match std::thread::current().name() {
668 // When doc tests are running they're all run with the thread name main. There's no way
669 // to detect which doc test is being run.
670 Some(name) if name != "main" => Some(name.to_owned()),
671 _ => None,
672 };
673 let number = if let Some(ref test_name) = test_name {
674 LAST_ENV.with_borrow_mut(|l| {
675 if let Some(last_env) = l.as_mut() {
676 if test_name != &last_env.test_name {
677 last_env.test_name = test_name.clone();
678 last_env.number = 1;
679 1
680 } else {
681 let next_number = last_env.number + 1;
682 last_env.number = next_number;
683 next_number
684 }
685 } else {
686 *l = Some(LastEnv {
687 test_name: test_name.clone(),
688 number: 1,
689 });
690 1
691 }
692 })
693 } else {
694 1
695 };
696
697 let storage = internal::storage::Storage::with_recording_footprint(recording_footprint);
698 let budget = internal::budget::Budget::default();
699 let env_impl = internal::EnvImpl::with_storage_and_budget(storage, budget.clone());
700 env_impl
701 .set_source_account(xdr::AccountId(xdr::PublicKey::PublicKeyTypeEd25519(
702 xdr::Uint256([0; 32]),
703 )))
704 .unwrap();
705 env_impl
706 .set_diagnostic_level(internal::DiagnosticLevel::Debug)
707 .unwrap();
708 env_impl.set_base_prng_seed([0; 32]).unwrap();
709
710 let auth_snapshot = Rc::new(RefCell::new(AuthSnapshot::default()));
711 let auth_snapshot_in_hook = auth_snapshot.clone();
712 env_impl
713 .set_invocation_hook(Some(Rc::new(move |host, event| {
714 match event {
715 InvocationEvent::Start => {}
716 InvocationEvent::Finish => {
717 let new_auths = host
718 .get_authenticated_authorizations()
719 // If an error occurs getting the authenticated authorizations
720 // it means that no auth has occurred.
721 .unwrap();
722 (*auth_snapshot_in_hook).borrow_mut().0.push(new_auths);
723 }
724 }
725 })))
726 .unwrap();
727 env_impl.enable_invocation_metering();
728 env_impl
729 .set_invocation_resource_limits(Some(InvocationResourceLimits::mainnet()))
730 .unwrap();
731
732 let env = Env {
733 env_impl,
734 test_state: EnvTestState {
735 test_name,
736 number,
737 config,
738 generators: generators.unwrap_or_default(),
739 snapshot,
740 auth_snapshot,
741 },
742 };
743
744 let ledger_info = ledger_info.unwrap_or_else(default_ledger_info);
745 env.ledger().set(ledger_info);
746
747 env
748 }
749
750 /// Returns the resources metered during the last top level contract
751 /// invocation.
752 ///
753 /// In order to get non-`None` results, `enable_invocation_metering` has to
754 /// be called and at least one invocation has to happen after that.
755 ///
756 /// Take the return value with a grain of salt. The returned resources mostly
757 /// correspond only to the operations that have happened during the host
758 /// invocation, i.e. this won't try to simulate the work that happens in
759 /// production scenarios (e.g. certain XDR roundtrips). This also doesn't try
760 /// to model resources related to the transaction size.
761 ///
762 /// The returned value is as useful as the preceding setup, e.g. if a test
763 /// contract is used instead of a Wasm contract, all the costs related to
764 /// VM instantiation and execution, as well as Wasm reads/rent bumps will be
765 /// missed.
766 ///
767 /// While the resource metering may be useful for contract optimization,
768 /// keep in mind that resource and fee estimation may be imprecise. Use
769 /// simulation with RPC in order to get the exact resources for submitting
770 /// the transactions to the network.
771 pub fn cost_estimate(&self) -> CostEstimate {
772 CostEstimate::new(self.clone())
773 }
774
775 /// Register a contract with the [Env] for testing.
776 ///
777 /// Pass the contract type when the contract is defined in the current crate
778 /// and is being registered natively. Pass the contract wasm bytes when the
779 /// contract has been loaded as wasm.
780 ///
781 /// Pass the arguments for the contract's constructor, or `()` if none. For
782 /// contracts with a constructor, use the contract's generated `Args` type
783 /// to construct the arguments with the appropriate types for invoking
784 /// the constructor during registration.
785 ///
786 /// Returns the address of the registered contract that is the same as the
787 /// contract id passed in.
788 ///
789 /// If you need to specify the address the contract should be registered at,
790 /// use [`Env::register_at`].
791 ///
792 /// ### Examples
793 /// Register a contract defined in the current crate, by specifying the type
794 /// name:
795 /// ```
796 /// use soroban_sdk::{contract, contractimpl, testutils::Address as _, Address, BytesN, Env, Symbol};
797 ///
798 /// #[contract]
799 /// pub struct Contract;
800 ///
801 /// #[contractimpl]
802 /// impl Contract {
803 /// pub fn __constructor(_env: Env, _input: u32) {
804 /// }
805 /// }
806 ///
807 /// #[test]
808 /// fn test() {
809 /// # }
810 /// # fn main() {
811 /// let env = Env::default();
812 /// let contract_id = env.register(Contract, ContractArgs::__constructor(&123,));
813 /// }
814 /// ```
815 /// Register a contract wasm, by specifying the wasm bytes:
816 /// ```
817 /// use soroban_sdk::{testutils::Address as _, Address, BytesN, Env};
818 ///
819 /// const WASM: &[u8] = include_bytes!("../doctest_fixtures/contract.wasm");
820 ///
821 /// #[test]
822 /// fn test() {
823 /// # }
824 /// # fn main() {
825 /// let env = Env::default();
826 /// let contract_id = env.register(WASM, ());
827 /// }
828 /// ```
829 pub fn register<'a, C, A>(&self, contract: C, constructor_args: A) -> Address
830 where
831 C: Register,
832 A: ConstructorArgs,
833 {
834 contract.register(self, None, constructor_args)
835 }
836
837 /// Register a contract with the [Env] for testing.
838 ///
839 /// Passing a contract ID for the first arguments registers the contract
840 /// with that contract ID.
841 ///
842 /// Registering a contract that is already registered replaces it.
843 /// Use re-registration with caution as it does not exist in the real
844 /// (on-chain) environment. Specifically, the new contract's constructor
845 /// will be called again during re-registration. That behavior only exists
846 /// for this test utility and is not reproducible on-chain, where contract
847 /// Wasm updates don't cause constructor to be called.
848 ///
849 /// Pass the contract type when the contract is defined in the current crate
850 /// and is being registered natively. Pass the contract wasm bytes when the
851 /// contract has been loaded as wasm.
852 ///
853 /// Returns the address of the registered contract that is the same as the
854 /// contract id passed in.
855 ///
856 /// ### Examples
857 /// Register a contract defined in the current crate, by specifying the type
858 /// name:
859 /// ```
860 /// use soroban_sdk::{contract, contractimpl, testutils::Address as _, Address, BytesN, Env, Symbol};
861 ///
862 /// #[contract]
863 /// pub struct Contract;
864 ///
865 /// #[contractimpl]
866 /// impl Contract {
867 /// pub fn __constructor(_env: Env, _input: u32) {
868 /// }
869 /// }
870 ///
871 /// #[test]
872 /// fn test() {
873 /// # }
874 /// # fn main() {
875 /// let env = Env::default();
876 /// let contract_id = Address::generate(&env);
877 /// env.register_at(&contract_id, Contract, (123_u32,));
878 /// }
879 /// ```
880 /// Register a contract wasm, by specifying the wasm bytes:
881 /// ```
882 /// use soroban_sdk::{testutils::Address as _, Address, BytesN, Env};
883 ///
884 /// const WASM: &[u8] = include_bytes!("../doctest_fixtures/contract.wasm");
885 ///
886 /// #[test]
887 /// fn test() {
888 /// # }
889 /// # fn main() {
890 /// let env = Env::default();
891 /// let contract_id = Address::generate(&env);
892 /// env.register_at(&contract_id, WASM, ());
893 /// }
894 /// ```
895 pub fn register_at<C, A>(
896 &self,
897 contract_id: &Address,
898 contract: C,
899 constructor_args: A,
900 ) -> Address
901 where
902 C: Register,
903 A: ConstructorArgs,
904 {
905 contract.register(self, contract_id, constructor_args)
906 }
907
908 /// Register a contract with the [Env] for testing.
909 ///
910 /// Passing a contract ID for the first arguments registers the contract
911 /// with that contract ID. Providing `None` causes the Env to generate a new
912 /// contract ID that is assigned to the contract.
913 ///
914 /// If a contract has a constructor defined, then it will be called with
915 /// no arguments. If a constructor takes arguments, use `register`.
916 ///
917 /// Registering a contract that is already registered replaces it.
918 /// Use re-registration with caution as it does not exist in the real
919 /// (on-chain) environment. Specifically, the new contract's constructor
920 /// will be called again during re-registration. That behavior only exists
921 /// for this test utility and is not reproducible on-chain, where contract
922 /// Wasm updates don't cause constructor to be called.
923 ///
924 /// Returns the address of the registered contract.
925 ///
926 /// ### Examples
927 /// ```
928 /// use soroban_sdk::{contract, contractimpl, BytesN, Env, Symbol};
929 ///
930 /// #[contract]
931 /// pub struct HelloContract;
932 ///
933 /// #[contractimpl]
934 /// impl HelloContract {
935 /// pub fn hello(env: Env, recipient: Symbol) -> Symbol {
936 /// todo!()
937 /// }
938 /// }
939 ///
940 /// #[test]
941 /// fn test() {
942 /// # }
943 /// # fn main() {
944 /// let env = Env::default();
945 /// let contract_id = env.register_contract(None, HelloContract);
946 /// }
947 /// ```
948 #[deprecated(note = "use `register`")]
949 pub fn register_contract<'a, T: ContractFunctionSet + 'static>(
950 &self,
951 contract_id: impl Into<Option<&'a Address>>,
952 contract: T,
953 ) -> Address {
954 self.register_contract_with_constructor(contract_id, contract, ())
955 }
956
957 /// Register a contract with the [Env] for testing.
958 ///
959 /// This acts the in the same fashion as `register_contract`, but allows
960 /// passing arguments to the contract's constructor.
961 ///
962 /// Passing a contract ID for the first arguments registers the contract
963 /// with that contract ID. Providing `None` causes the Env to generate a new
964 /// contract ID that is assigned to the contract.
965 ///
966 /// Registering a contract that is already registered replaces it.
967 /// Use re-registration with caution as it does not exist in the real
968 /// (on-chain) environment. Specifically, the new contract's constructor
969 /// will be called again during re-registration. That behavior only exists
970 /// for this test utility and is not reproducible on-chain, where contract
971 /// Wasm updates don't cause constructor to be called.
972 ///
973 /// Returns the address of the registered contract.
974 pub(crate) fn register_contract_with_constructor<
975 'a,
976 T: ContractFunctionSet + 'static,
977 A: ConstructorArgs,
978 >(
979 &self,
980 contract_id: impl Into<Option<&'a Address>>,
981 contract: T,
982 constructor_args: A,
983 ) -> Address {
984 struct InternalContractFunctionSet<T: ContractFunctionSet>(pub(crate) T);
985 impl<T: ContractFunctionSet> internal::ContractFunctionSet for InternalContractFunctionSet<T> {
986 fn call(
987 &self,
988 func: &Symbol,
989 env_impl: &internal::EnvImpl,
990 args: &[Val],
991 ) -> Option<Val> {
992 let env = Env {
993 env_impl: env_impl.clone(),
994 test_state: Default::default(),
995 };
996 self.0.call(
997 crate::Symbol::try_from_val(&env, func)
998 .unwrap_infallible()
999 .to_string()
1000 .as_str(),
1001 env,
1002 args,
1003 )
1004 }
1005 }
1006
1007 let contract_id = if let Some(contract_id) = contract_id.into() {
1008 contract_id.clone()
1009 } else {
1010 Address::generate(self)
1011 };
1012 // Convert the constructor arguments before switching auth managers, so
1013 // that a panic during conversion cannot leave the environment stuck in
1014 // recording auth. This matches the wasm registration path.
1015 let constructor_args = constructor_args.into_val(self).to_object();
1016 let prev_auth_manager = self.env_impl.snapshot_auth_manager().unwrap();
1017 self.env_impl
1018 .switch_to_recording_auth_inherited_from_snapshot(&prev_auth_manager)
1019 .unwrap();
1020 let register_result = self.env_impl.register_test_contract_with_constructor(
1021 contract_id.to_object(),
1022 Rc::new(InternalContractFunctionSet(contract)),
1023 constructor_args,
1024 );
1025 self.env_impl.set_auth_manager(prev_auth_manager).unwrap();
1026 register_result.unwrap();
1027 contract_id
1028 }
1029
1030 /// Register a contract in a Wasm file with the [Env] for testing.
1031 ///
1032 /// Passing a contract ID for the first arguments registers the contract
1033 /// with that contract ID. Providing `None` causes the Env to generate a new
1034 /// contract ID that is assigned to the contract.
1035 ///
1036 /// Registering a contract that is already registered replaces it.
1037 /// Use re-registration with caution as it does not exist in the real
1038 /// (on-chain) environment. Specifically, the new contract's constructor
1039 /// will be called again during re-registration. That behavior only exists
1040 /// for this test utility and is not reproducible on-chain, where contract
1041 /// Wasm updates don't cause constructor to be called.
1042 ///
1043 /// Returns the address of the registered contract.
1044 ///
1045 /// ### Examples
1046 /// ```
1047 /// use soroban_sdk::{BytesN, Env};
1048 ///
1049 /// const WASM: &[u8] = include_bytes!("../doctest_fixtures/contract.wasm");
1050 ///
1051 /// #[test]
1052 /// fn test() {
1053 /// # }
1054 /// # fn main() {
1055 /// let env = Env::default();
1056 /// env.register_contract_wasm(None, WASM);
1057 /// }
1058 /// ```
1059 #[deprecated(note = "use `register`")]
1060 pub fn register_contract_wasm<'a>(
1061 &self,
1062 contract_id: impl Into<Option<&'a Address>>,
1063 contract_wasm: impl IntoVal<Env, Bytes>,
1064 ) -> Address {
1065 let wasm_hash: BytesN<32> = self.deployer().upload_contract_wasm(contract_wasm);
1066 self.register_contract_with_optional_contract_id_and_executable(
1067 contract_id,
1068 xdr::ContractExecutable::Wasm(xdr::Hash(wasm_hash.into())),
1069 crate::vec![&self],
1070 )
1071 }
1072
1073 /// Register a contract in a Wasm file with the [Env] for testing.
1074 ///
1075 /// This acts the in the same fashion as `register_contract`, but allows
1076 /// passing arguments to the contract's constructor.
1077 ///
1078 /// Passing a contract ID for the first arguments registers the contract
1079 /// with that contract ID. Providing `None` causes the Env to generate a new
1080 /// contract ID that is assigned to the contract.
1081 ///
1082 /// Registering a contract that is already registered replaces it.
1083 /// Use re-registration with caution as it does not exist in the real
1084 /// (on-chain) environment. Specifically, the new contract's constructor
1085 /// will be called again during re-registration. That behavior only exists
1086 /// for this test utility and is not reproducible on-chain, where contract
1087 /// Wasm updates don't cause constructor to be called.
1088 ///
1089 /// Returns the address of the registered contract.
1090 pub(crate) fn register_contract_wasm_with_constructor<'a>(
1091 &self,
1092 contract_id: impl Into<Option<&'a Address>>,
1093 contract_wasm: impl IntoVal<Env, Bytes>,
1094 constructor_args: impl ConstructorArgs,
1095 ) -> Address {
1096 let wasm_hash: BytesN<32> = self.deployer().upload_contract_wasm(contract_wasm);
1097 self.register_contract_with_optional_contract_id_and_executable(
1098 contract_id,
1099 xdr::ContractExecutable::Wasm(xdr::Hash(wasm_hash.into())),
1100 constructor_args.into_val(self),
1101 )
1102 }
1103
1104 /// Register the built-in Stellar Asset Contract with provided admin address.
1105 ///
1106 /// Returns a utility struct that contains the contract ID of the registered
1107 /// token contract, as well as methods to read and update issuer flags.
1108 ///
1109 /// The contract will wrap a randomly-generated Stellar asset. This function
1110 /// is useful for using in the tests when an arbitrary token contract
1111 /// instance is needed.
1112 pub fn register_stellar_asset_contract_v2(&self, admin: Address) -> StellarAssetContract {
1113 let issuer_pk = self.with_generator(|mut g| g.address());
1114 let issuer_id = xdr::AccountId(xdr::PublicKey::PublicKeyTypeEd25519(xdr::Uint256(
1115 issuer_pk.clone(),
1116 )));
1117
1118 let k = Rc::new(xdr::LedgerKey::Account(xdr::LedgerKeyAccount {
1119 account_id: issuer_id.clone(),
1120 }));
1121
1122 if self.host().get_ledger_entry(&k).unwrap().is_none() {
1123 let v = Rc::new(xdr::LedgerEntry {
1124 data: xdr::LedgerEntryData::Account(xdr::AccountEntry {
1125 account_id: issuer_id.clone(),
1126 balance: 0,
1127 flags: 0,
1128 home_domain: Default::default(),
1129 inflation_dest: None,
1130 num_sub_entries: 0,
1131 seq_num: xdr::SequenceNumber(0),
1132 thresholds: xdr::Thresholds([1; 4]),
1133 signers: xdr::VecM::default(),
1134 ext: xdr::AccountEntryExt::V0,
1135 }),
1136 last_modified_ledger_seq: 0,
1137 ext: xdr::LedgerEntryExt::V0,
1138 });
1139 self.host().add_ledger_entry(&k, &v, None).unwrap();
1140 }
1141
1142 let asset = xdr::Asset::CreditAlphanum4(xdr::AlphaNum4 {
1143 asset_code: xdr::AssetCode4([b'a', b'a', b'a', 0]),
1144 issuer: issuer_id.clone(),
1145 });
1146 let create = xdr::HostFunction::CreateContract(xdr::CreateContractArgs {
1147 contract_id_preimage: xdr::ContractIdPreimage::Asset(asset.clone()),
1148 executable: xdr::ContractExecutable::StellarAsset,
1149 });
1150
1151 let token_id: Address = self
1152 .env_impl
1153 .invoke_function(create)
1154 .unwrap()
1155 .try_into_val(self)
1156 .unwrap();
1157
1158 let prev_auth_manager = self.env_impl.snapshot_auth_manager().unwrap();
1159 self.env_impl
1160 .switch_to_recording_auth_inherited_from_snapshot(&prev_auth_manager)
1161 .unwrap();
1162 let admin_result = self.try_invoke_contract::<(), Error>(
1163 &token_id,
1164 &soroban_sdk_macros::internal_symbol_short!("set_admin"),
1165 (admin,).try_into_val(self).unwrap(),
1166 );
1167 self.env_impl.set_auth_manager(prev_auth_manager).unwrap();
1168 admin_result.unwrap().unwrap();
1169
1170 let issuer = StellarAssetIssuer::new(self.clone(), issuer_id);
1171 StellarAssetContract::new(token_id, issuer, asset)
1172 }
1173
1174 /// Register the built-in Stellar Asset Contract with provided admin address.
1175 ///
1176 /// Returns the contract ID of the registered token contract.
1177 ///
1178 /// The contract will wrap a randomly-generated Stellar asset. This function
1179 /// is useful for using in the tests when an arbitrary token contract
1180 /// instance is needed.
1181 #[deprecated(note = "use [Env::register_stellar_asset_contract_v2]")]
1182 pub fn register_stellar_asset_contract(&self, admin: Address) -> Address {
1183 self.register_stellar_asset_contract_v2(admin).address()
1184 }
1185
1186 fn register_contract_with_optional_contract_id_and_executable<'a>(
1187 &self,
1188 contract_id: impl Into<Option<&'a Address>>,
1189 executable: xdr::ContractExecutable,
1190 constructor_args: Vec<Val>,
1191 ) -> Address {
1192 if let Some(contract_id) = contract_id.into() {
1193 self.register_contract_with_contract_id_and_executable(
1194 contract_id,
1195 executable,
1196 constructor_args,
1197 );
1198 contract_id.clone()
1199 } else {
1200 self.register_contract_with_source(executable, constructor_args)
1201 }
1202 }
1203
1204 fn register_contract_with_source(
1205 &self,
1206 executable: xdr::ContractExecutable,
1207 constructor_args: Vec<Val>,
1208 ) -> Address {
1209 let args_vec: std::vec::Vec<xdr::ScVal> =
1210 constructor_args.iter().map(|v| v.into_val(self)).collect();
1211 let constructor_args = args_vec.try_into().unwrap();
1212 let prev_auth_manager = self.env_impl.snapshot_auth_manager().unwrap();
1213 self.env_impl
1214 .switch_to_recording_auth_inherited_from_snapshot(&prev_auth_manager)
1215 .unwrap();
1216 let create_result = self
1217 .env_impl
1218 .invoke_function(xdr::HostFunction::CreateContractV2(
1219 xdr::CreateContractArgsV2 {
1220 contract_id_preimage: xdr::ContractIdPreimage::Address(
1221 xdr::ContractIdPreimageFromAddress {
1222 address: xdr::ScAddress::Contract(xdr::ContractId(xdr::Hash(
1223 self.with_generator(|mut g| g.address()),
1224 ))),
1225 salt: xdr::Uint256([0; 32]),
1226 },
1227 ),
1228 executable,
1229 constructor_args,
1230 },
1231 ));
1232
1233 self.env_impl.set_auth_manager(prev_auth_manager).unwrap();
1234
1235 create_result.unwrap().try_into_val(self).unwrap()
1236 }
1237
1238 /// Set authorizations and signatures in the environment which will be
1239 /// consumed by contracts when they invoke [`Address::require_auth`] or
1240 /// [`Address::require_auth_for_args`] functions.
1241 ///
1242 /// Requires valid signatures for the authorization to be successful.
1243 ///
1244 /// This function can also be called on contract clients.
1245 ///
1246 /// To mock auth for testing, without requiring valid signatures, use
1247 /// [`mock_all_auths`][Self::mock_all_auths] or
1248 /// [`mock_auths`][Self::mock_auths]. If mocking of auths is enabled,
1249 /// calling [`set_auths`][Self::set_auths] disables any mocking.
1250 pub fn set_auths(&self, auths: &[SorobanAuthorizationEntry]) {
1251 self.env_impl
1252 .set_authorization_entries(auths.to_vec())
1253 .unwrap();
1254 }
1255
1256 /// Mock authorizations in the environment which will cause matching invokes
1257 /// of [`Address::require_auth`] and [`Address::require_auth_for_args`] to
1258 /// pass.
1259 ///
1260 /// This function can also be called on contract clients.
1261 ///
1262 /// Authorizations not matching a mocked auth will fail.
1263 ///
1264 /// To mock all auths, use [`mock_all_auths`][Self::mock_all_auths].
1265 ///
1266 /// ### Examples
1267 /// ```
1268 /// use soroban_sdk::{contract, contractimpl, Env, Address, testutils::{Address as _, MockAuth, MockAuthInvoke}, IntoVal};
1269 ///
1270 /// #[contract]
1271 /// pub struct HelloContract;
1272 ///
1273 /// #[contractimpl]
1274 /// impl HelloContract {
1275 /// pub fn hello(env: Env, from: Address) {
1276 /// from.require_auth();
1277 /// // TODO
1278 /// }
1279 /// }
1280 ///
1281 /// #[test]
1282 /// fn test() {
1283 /// # }
1284 /// # fn main() {
1285 /// let env = Env::default();
1286 /// let contract_id = env.register(HelloContract, ());
1287 ///
1288 /// let client = HelloContractClient::new(&env, &contract_id);
1289 /// let addr = Address::generate(&env);
1290 /// client.mock_auths(&[
1291 /// MockAuth {
1292 /// address: &addr,
1293 /// invoke: &MockAuthInvoke {
1294 /// contract: &contract_id,
1295 /// fn_name: "hello",
1296 /// args: (&addr,).into_val(&env),
1297 /// sub_invokes: &[],
1298 /// },
1299 /// },
1300 /// ]).hello(&addr);
1301 /// }
1302 /// ```
1303 pub fn mock_auths(&self, auths: &[MockAuth]) {
1304 for a in auths {
1305 self.register_at(a.address, MockAuthContract, ());
1306 }
1307 let auths = auths
1308 .iter()
1309 .cloned()
1310 .map(Into::into)
1311 .collect::<std::vec::Vec<_>>();
1312 self.env_impl.set_authorization_entries(auths).unwrap();
1313 }
1314
1315 /// Mock all calls to the [`Address::require_auth`] and
1316 /// [`Address::require_auth_for_args`] functions in invoked contracts,
1317 /// having them succeed as if authorization was provided.
1318 ///
1319 /// When mocking is enabled, if the [`Address`] being authorized is the
1320 /// address of a contract, that contract's `__check_auth` function will not
1321 /// be called, and the contract does not need to exist or be registered in
1322 /// the test.
1323 ///
1324 /// When mocking is enabled, if the [`Address`] being authorized is the
1325 /// address of an account, the account does not need to exist.
1326 ///
1327 /// This function can also be called on contract clients.
1328 ///
1329 /// To disable mocking, see [`set_auths`][Self::set_auths].
1330 ///
1331 /// To access a list of auths that have occurred, see [`auths`][Self::auths].
1332 ///
1333 /// It is not currently possible to mock a subset of auths.
1334 ///
1335 /// A test that uses `mock_all_auths` without verifying the resulting
1336 /// authorization tree via [`auths`][Self::auths] can pass even when a contract
1337 /// is missing a `require_auth` check. Use [`auths`][Self::auths] after
1338 /// the contract call to assert that the expected authorizations were required.
1339 ///
1340 /// ### Examples
1341 /// ```
1342 /// use soroban_sdk::{contract, contractimpl, Env, Address, IntoVal, symbol_short};
1343 /// use soroban_sdk::testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation};
1344 ///
1345 /// #[contract]
1346 /// pub struct HelloContract;
1347 ///
1348 /// #[contractimpl]
1349 /// impl HelloContract {
1350 /// pub fn hello(env: Env, from: Address) {
1351 /// from.require_auth();
1352 /// // TODO
1353 /// }
1354 /// }
1355 ///
1356 /// #[test]
1357 /// fn test() {
1358 /// # }
1359 /// # fn main() {
1360 /// let env = Env::default();
1361 /// let contract_id = env.register(HelloContract, ());
1362 ///
1363 /// env.mock_all_auths();
1364 ///
1365 /// let client = HelloContractClient::new(&env, &contract_id);
1366 /// let addr = Address::generate(&env);
1367 /// client.hello(&addr);
1368 ///
1369 /// // Verify that the expected authorization was required.
1370 /// assert_eq!(
1371 /// env.auths(),
1372 /// [(
1373 /// addr.clone(),
1374 /// AuthorizedInvocation {
1375 /// function: AuthorizedFunction::Contract((
1376 /// contract_id,
1377 /// symbol_short!("hello"),
1378 /// (&addr,).into_val(&env),
1379 /// )),
1380 /// sub_invocations: [].into(),
1381 /// }
1382 /// )]
1383 /// );
1384 /// }
1385 /// ```
1386 pub fn mock_all_auths(&self) {
1387 self.env_impl.switch_to_recording_auth(true).unwrap();
1388 }
1389
1390 /// A version of [`mock_all_auths`][Self::mock_all_auths] that allows authorizations that are not
1391 /// present in the root invocation.
1392 ///
1393 /// Refer to [`mock_all_auths`][Self::mock_all_auths] documentation for general information and
1394 /// prefer using [`mock_all_auths`][Self::mock_all_auths] unless non-root authorization is required.
1395 ///
1396 /// The only difference from [`mock_all_auths`][Self::mock_all_auths] is that this won't return an
1397 /// error when `require_auth` hasn't been called in the root invocation for
1398 /// any given address. This is useful to test contracts that bundle calls to
1399 /// another contract without atomicity requirements (i.e. any contract call
1400 /// can be frontrun).
1401 ///
1402 /// ### Examples
1403 /// ```
1404 /// use soroban_sdk::{contract, contractimpl, Env, Address, testutils::Address as _};
1405 ///
1406 /// #[contract]
1407 /// pub struct ContractA;
1408 ///
1409 /// #[contractimpl]
1410 /// impl ContractA {
1411 /// pub fn do_auth(env: Env, addr: Address) {
1412 /// addr.require_auth();
1413 /// }
1414 /// }
1415 /// #[contract]
1416 /// pub struct ContractB;
1417 ///
1418 /// #[contractimpl]
1419 /// impl ContractB {
1420 /// pub fn call_a(env: Env, contract_a: Address, addr: Address) {
1421 /// // Notice there is no `require_auth` call here.
1422 /// ContractAClient::new(&env, &contract_a).do_auth(&addr);
1423 /// }
1424 /// }
1425 /// #[test]
1426 /// fn test() {
1427 /// # }
1428 /// # fn main() {
1429 /// let env = Env::default();
1430 /// let contract_a = env.register(ContractA, ());
1431 /// let contract_b = env.register(ContractB, ());
1432 /// // The regular `env.mock_all_auths()` would result in the call
1433 /// // failure.
1434 /// env.mock_all_auths_allowing_non_root_auth();
1435 ///
1436 /// let client = ContractBClient::new(&env, &contract_b);
1437 /// let addr = Address::generate(&env);
1438 /// client.call_a(&contract_a, &addr);
1439 /// }
1440 /// ```
1441 pub fn mock_all_auths_allowing_non_root_auth(&self) {
1442 self.env_impl.switch_to_recording_auth(false).unwrap();
1443 }
1444
1445 /// Returns a list of authorization trees that were seen during the last
1446 /// contract or authorized host function invocation.
1447 ///
1448 /// Use this in tests to verify that the expected authorizations with the
1449 /// expected arguments are required.
1450 ///
1451 /// The return value is a vector of authorizations represented by tuples of
1452 /// `(address, AuthorizedInvocation)`. `AuthorizedInvocation` describes the
1453 /// tree of `require_auth_for_args(address, args)` from the contract
1454 /// functions (or `require_auth` with all the arguments of the function
1455 /// invocation). It also might contain the authorized host functions (
1456 /// currently CreateContract is the only such function) in case if
1457 /// corresponding host functions have been called.
1458 ///
1459 /// Refer to documentation for `AuthorizedInvocation` for detailed
1460 /// information on its contents.
1461 ///
1462 /// The order of the returned vector is defined by the order of
1463 /// [`Address::require_auth`] calls. Repeated calls to
1464 /// [`Address::require_auth`] with the same address and args in the same
1465 /// tree of contract invocations will appear only once in the vector.
1466 ///
1467 /// ### Examples
1468 /// ```
1469 /// use soroban_sdk::{contract, contractimpl, testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation}, symbol_short, Address, Symbol, Env, IntoVal};
1470 ///
1471 /// #[contract]
1472 /// pub struct Contract;
1473 ///
1474 /// #[contractimpl]
1475 /// impl Contract {
1476 /// pub fn transfer(env: Env, address: Address, amount: i128) {
1477 /// address.require_auth();
1478 /// }
1479 /// pub fn transfer2(env: Env, address: Address, amount: i128) {
1480 /// address.require_auth_for_args((amount / 2,).into_val(&env));
1481 /// }
1482 /// }
1483 ///
1484 /// #[test]
1485 /// fn test() {
1486 /// # }
1487 /// # #[cfg(feature = "testutils")]
1488 /// # fn main() {
1489 /// let env = Env::default();
1490 /// let contract_id = env.register(Contract, ());
1491 /// let client = ContractClient::new(&env, &contract_id);
1492 /// env.mock_all_auths();
1493 /// let address = Address::generate(&env);
1494 /// client.transfer(&address, &1000_i128);
1495 /// assert_eq!(
1496 /// env.auths(),
1497 /// [(
1498 /// address.clone(),
1499 /// AuthorizedInvocation {
1500 /// function: AuthorizedFunction::Contract((
1501 /// client.address.clone(),
1502 /// symbol_short!("transfer"),
1503 /// (&address, 1000_i128,).into_val(&env)
1504 /// )),
1505 /// sub_invocations: [].into()
1506 /// }
1507 /// )]
1508 /// );
1509 ///
1510 /// client.transfer2(&address, &1000_i128);
1511 /// assert_eq!(
1512 /// env.auths(),
1513 /// [(
1514 /// address.clone(),
1515 /// AuthorizedInvocation {
1516 /// function: AuthorizedFunction::Contract((
1517 /// client.address.clone(),
1518 /// symbol_short!("transfer2"),
1519 /// // `transfer2` requires auth for (amount / 2) == (1000 / 2) == 500.
1520 /// (500_i128,).into_val(&env)
1521 /// )),
1522 /// sub_invocations: [].into()
1523 /// }
1524 /// )]
1525 /// );
1526 /// }
1527 /// # #[cfg(not(feature = "testutils"))]
1528 /// # fn main() { }
1529 /// ```
1530 pub fn auths(&self) -> std::vec::Vec<(Address, AuthorizedInvocation)> {
1531 (*self.test_state.auth_snapshot)
1532 .borrow()
1533 .0
1534 .last()
1535 .cloned()
1536 .unwrap_or_default()
1537 .into_iter()
1538 .map(|(sc_addr, invocation)| {
1539 (
1540 xdr::ScVal::Address(sc_addr).try_into_val(self).unwrap(),
1541 AuthorizedInvocation::from_xdr(self, &invocation),
1542 )
1543 })
1544 .collect()
1545 }
1546
1547 /// Invokes the special `__check_auth` function of contracts that implement
1548 /// the custom account interface.
1549 ///
1550 /// `__check_auth` can't be called outside of the host-managed `require_auth`
1551 /// calls. This test utility allows testing custom account contracts without
1552 /// the need to setup complex contract call trees and enabling the enforcing
1553 /// auth on the host side.
1554 ///
1555 /// This function requires to provide the template argument for error. Use
1556 /// `soroban_sdk::Error` if `__check_auth` doesn't return a special
1557 /// contract error and use the error with `contracterror` attribute
1558 /// otherwise.
1559 ///
1560 /// ### Examples
1561 /// ```
1562 /// use soroban_sdk::{contract, contracterror, contractimpl, testutils::{Address as _, BytesN as _}, vec, auth::Context, BytesN, Env, Vec, Val};
1563 ///
1564 /// #[contracterror]
1565 /// #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
1566 /// #[repr(u32)]
1567 /// pub enum NoopAccountError {
1568 /// SomeError = 1,
1569 /// }
1570 /// #[contract]
1571 /// struct NoopAccountContract;
1572 /// #[contractimpl]
1573 /// impl NoopAccountContract {
1574 ///
1575 /// #[allow(non_snake_case)]
1576 /// pub fn __check_auth(
1577 /// _env: Env,
1578 /// _signature_payload: BytesN<32>,
1579 /// signature: Val,
1580 /// _auth_context: Vec<Context>,
1581 /// ) -> Result<(), NoopAccountError> {
1582 /// if signature.is_void() {
1583 /// Err(NoopAccountError::SomeError)
1584 /// } else {
1585 /// Ok(())
1586 /// }
1587 /// }
1588 /// }
1589 /// #[test]
1590 /// fn test() {
1591 /// # }
1592 /// # fn main() {
1593 /// let e: Env = Default::default();
1594 /// let account_contract = NoopAccountContractClient::new(&e, &e.register(NoopAccountContract, ()));
1595 /// // Non-successful call of `__check_auth` with a `contracterror` error.
1596 /// assert_eq!(
1597 /// e.try_invoke_contract_check_auth::<NoopAccountError>(
1598 /// &account_contract.address,
1599 /// &BytesN::from_array(&e, &[0; 32]),
1600 /// ().into(),
1601 /// &vec![&e],
1602 /// ),
1603 /// // The inner `Result` is for conversion error and will be Ok
1604 /// // as long as a valid error type used.
1605 /// Err(Ok(NoopAccountError::SomeError))
1606 /// );
1607 /// // Successful call of `__check_auth` with a `soroban_sdk::InvokeError`
1608 /// // error - this should be compatible with any error type.
1609 /// assert_eq!(
1610 /// e.try_invoke_contract_check_auth::<soroban_sdk::InvokeError>(
1611 /// &account_contract.address,
1612 /// &BytesN::from_array(&e, &[0; 32]),
1613 /// 0_i32.into(),
1614 /// &vec![&e],
1615 /// ),
1616 /// Ok(())
1617 /// );
1618 /// }
1619 /// ```
1620 pub fn try_invoke_contract_check_auth<E>(
1621 &self,
1622 contract: &Address,
1623 signature_payload: &BytesN<32>,
1624 signature: Val,
1625 auth_context: &Vec<auth::Context>,
1626 ) -> Result<(), Result<E, InvokeError>>
1627 where
1628 E: TryFrom<Error>,
1629 E::Error: Into<InvokeError>,
1630 {
1631 let args = Vec::from_array(
1632 self,
1633 [signature_payload.to_val(), signature, auth_context.to_val()],
1634 );
1635 let res = self
1636 .host()
1637 .call_account_contract_check_auth(contract.to_object(), args.to_object());
1638 match res {
1639 Ok(rv) => Ok(rv.into_val(self)),
1640 Err(e) => Err(e.error.try_into().map_err(Into::into)),
1641 }
1642 }
1643
1644 fn register_contract_with_contract_id_and_executable(
1645 &self,
1646 contract_address: &Address,
1647 executable: xdr::ContractExecutable,
1648 constructor_args: Vec<Val>,
1649 ) {
1650 let contract_id = contract_address.contract_id();
1651 let data_key = xdr::ScVal::LedgerKeyContractInstance;
1652 let key = Rc::new(LedgerKey::ContractData(LedgerKeyContractData {
1653 contract: xdr::ScAddress::Contract(contract_id.clone()),
1654 key: data_key.clone(),
1655 durability: xdr::ContractDataDurability::Persistent,
1656 }));
1657
1658 let instance = xdr::ScContractInstance {
1659 executable,
1660 storage: Default::default(),
1661 };
1662
1663 let entry = Rc::new(LedgerEntry {
1664 ext: xdr::LedgerEntryExt::V0,
1665 last_modified_ledger_seq: 0,
1666 data: xdr::LedgerEntryData::ContractData(xdr::ContractDataEntry {
1667 contract: xdr::ScAddress::Contract(contract_id.clone()),
1668 key: data_key,
1669 val: xdr::ScVal::ContractInstance(instance),
1670 durability: xdr::ContractDataDurability::Persistent,
1671 ext: xdr::ExtensionPoint::V0,
1672 }),
1673 });
1674 let live_until_ledger = self.ledger().sequence() + 1;
1675 self.host()
1676 .add_ledger_entry(&key, &entry, Some(live_until_ledger))
1677 .unwrap();
1678 let prev_auth_manager = self.env_impl.snapshot_auth_manager().unwrap();
1679 self.env_impl
1680 .switch_to_recording_auth_inherited_from_snapshot(&prev_auth_manager)
1681 .unwrap();
1682 let call_result = self.env_impl.call_constructor_for_stored_contract_unsafe(
1683 &contract_id,
1684 constructor_args.to_object(),
1685 );
1686 self.env_impl.set_auth_manager(prev_auth_manager).unwrap();
1687 call_result.unwrap();
1688 }
1689
1690 /// Run the function as if executed by the given contract ID.
1691 ///
1692 /// Used to write or read contract data, or take other actions in tests for
1693 /// setting up tests or asserting on internal state.
1694 ///
1695 /// ### Examples
1696 /// ```
1697 /// use soroban_sdk::{contract, contractimpl, Env, Symbol};
1698 ///
1699 /// #[contract]
1700 /// pub struct HelloContract;
1701 ///
1702 /// #[contractimpl]
1703 /// impl HelloContract {
1704 /// pub fn set_storage(env: Env, key: Symbol, val: Symbol) {
1705 /// env.storage().persistent().set(&key, &val);
1706 /// }
1707 /// }
1708 ///
1709 /// #[test]
1710 /// fn test() {
1711 /// # }
1712 /// # fn main() {
1713 /// let env = Env::default();
1714 /// let contract_id = env.register(HelloContract, ());
1715 /// let client = HelloContractClient::new(&env, &contract_id);
1716 ///
1717 /// let key = Symbol::new(&env, "foo");
1718 /// let val = Symbol::new(&env, "bar");
1719 ///
1720 /// // Set storage using the contract
1721 /// client.set_storage(&key, &val);
1722 ///
1723 /// // Successfully read the storage key
1724 /// let result = env.as_contract(&contract_id, || {
1725 /// env.storage()
1726 /// .persistent()
1727 /// .get::<Symbol, Symbol>(&key)
1728 /// .unwrap()
1729 /// });
1730 /// assert_eq!(result, val);
1731 /// }
1732 /// ```
1733 pub fn as_contract<T>(&self, id: &Address, f: impl FnOnce() -> T) -> T {
1734 let id = id.contract_id();
1735 let func = Symbol::from_small_str("");
1736 let mut t: Option<T> = None;
1737 self.env_impl
1738 .with_test_contract_frame(id, func, || {
1739 t = Some(f());
1740 Ok(().into())
1741 })
1742 .unwrap();
1743 t.unwrap()
1744 }
1745
1746 /// Run the function as if executed by the given contract ID. Returns an
1747 /// error if the function execution fails for any reason.
1748 ///
1749 /// Used to write or read contract data, or take other actions in tests for
1750 /// setting up tests or asserting on internal state.
1751 ///
1752 /// ### Examples
1753 /// ```
1754 /// use soroban_sdk::{contract, contractimpl, xdr::{ScErrorCode, ScErrorType}, Env, Error, Symbol};
1755 ///
1756 /// #[contract]
1757 /// pub struct HelloContract;
1758 ///
1759 /// #[contractimpl]
1760 /// impl HelloContract {
1761 /// pub fn set_storage(env: Env, key: Symbol, val: Symbol) {
1762 /// env.storage().persistent().set(&key, &val);
1763 /// }
1764 /// }
1765 ///
1766 /// #[test]
1767 /// fn test() {
1768 /// # }
1769 /// # fn main() {
1770 /// let env = Env::default();
1771 /// let contract_id = env.register(HelloContract, ());
1772 /// let client = HelloContractClient::new(&env, &contract_id);
1773 ///
1774 /// let key = Symbol::new(&env, "foo");
1775 /// let val = Symbol::new(&env, "bar");
1776 ///
1777 /// // Set storage using the contract
1778 /// client.set_storage(&key, &val);
1779 ///
1780 /// // Successfully read the storage key
1781 /// let result = env.try_as_contract::<Symbol, Error>(&contract_id, || {
1782 /// env.storage()
1783 /// .persistent()
1784 /// .get::<Symbol, Symbol>(&key)
1785 /// .unwrap()
1786 /// });
1787 /// assert_eq!(result, Ok(val));
1788 ///
1789 /// // Attempting to extend TTL of a non-existent key throws an error
1790 /// let new_key = Symbol::new(&env, "baz");
1791 /// let result = env.try_as_contract(&contract_id, || {
1792 /// env.storage().persistent().extend_ttl(&new_key, 1, 100);
1793 /// });
1794 /// assert_eq!(
1795 /// result,
1796 /// Err(Ok(Error::from_type_and_code(
1797 /// ScErrorType::Storage,
1798 /// ScErrorCode::MissingValue
1799 /// )))
1800 /// );
1801 /// }
1802 /// ```
1803 pub fn try_as_contract<T, E>(
1804 &self,
1805 id: &Address,
1806 f: impl FnOnce() -> T,
1807 ) -> Result<T, Result<E, InvokeError>>
1808 where
1809 E: TryFrom<Error>,
1810 E::Error: Into<InvokeError>,
1811 {
1812 let id = id.contract_id();
1813 let func = Symbol::from_small_str("");
1814 let mut t: Option<T> = None;
1815 let result = self.env_impl.try_with_test_contract_frame(id, func, || {
1816 t = Some(f());
1817 Ok(().into())
1818 });
1819
1820 match result {
1821 Ok(_) => Ok(t.unwrap()),
1822 Err(e) => Err(E::try_from(e.error).map_err(Into::into)),
1823 }
1824 }
1825
1826 /// Creates a new Env loaded with the [`Snapshot`].
1827 ///
1828 /// The ledger info and state in the snapshot are loaded into the Env.
1829 ///
1830 /// Events, as an output source only, are not loaded into the Env.
1831 pub fn from_snapshot(s: Snapshot) -> Env {
1832 Env::new_for_testutils(
1833 EnvTestConfig::default(),
1834 Rc::new(s.ledger.clone()),
1835 Some(Rc::new(RefCell::new(s.generators))),
1836 Some(s.ledger.ledger_info()),
1837 Some(Rc::new(s.ledger.clone())),
1838 )
1839 }
1840
1841 /// Creates a new Env loaded with the ledger snapshot loaded from the file.
1842 ///
1843 /// The ledger info and state in the snapshot are loaded into the Env.
1844 ///
1845 /// Events, as an output source only, are not loaded into the Env.
1846 ///
1847 /// ### Panics
1848 ///
1849 /// If there is any error reading the file.
1850 pub fn from_snapshot_file(p: impl AsRef<Path>) -> Env {
1851 Self::from_snapshot(Snapshot::read_file(p).unwrap())
1852 }
1853
1854 /// Create a snapshot from the Env's current state.
1855 pub fn to_snapshot(&self) -> Snapshot {
1856 Snapshot {
1857 generators: (*self.test_state.generators).borrow().clone(),
1858 auth: (*self.test_state.auth_snapshot).borrow().clone(),
1859 ledger: self.to_ledger_snapshot(),
1860 events: self.to_events_snapshot(),
1861 }
1862 }
1863
1864 /// Create a snapshot file from the Env's current state.
1865 ///
1866 /// ### Panics
1867 ///
1868 /// If there is any error writing the file.
1869 pub fn to_snapshot_file(&self, p: impl AsRef<Path>) {
1870 self.to_snapshot().write_file(p).unwrap();
1871 }
1872
1873 /// Creates a new Env loaded with the snapshot source.
1874 ///
1875 /// The ledger info and state from the snapshot source are loaded into the Env.
1876 pub fn from_ledger_snapshot(input: impl Into<SnapshotSourceInput>) -> Env {
1877 let SnapshotSourceInput {
1878 source,
1879 ledger_info,
1880 snapshot,
1881 } = input.into();
1882
1883 Env::new_for_testutils(
1884 EnvTestConfig::default(), // TODO: Allow setting the config.
1885 source,
1886 None,
1887 ledger_info,
1888 snapshot,
1889 )
1890 }
1891
1892 /// Creates a new Env loaded with the ledger snapshot loaded from the file.
1893 ///
1894 /// ### Panics
1895 ///
1896 /// If there is any error reading the file.
1897 pub fn from_ledger_snapshot_file(p: impl AsRef<Path>) -> Env {
1898 Self::from_ledger_snapshot(LedgerSnapshot::read_file(p).unwrap())
1899 }
1900
1901 /// Create a snapshot from the Env's current state.
1902 pub fn to_ledger_snapshot(&self) -> LedgerSnapshot {
1903 let snapshot = self.test_state.snapshot.clone().unwrap_or_default();
1904 let mut snapshot = (*snapshot).clone();
1905 snapshot.set_ledger_info(self.ledger().get());
1906 snapshot.update_entries(&self.host().get_stored_entries().unwrap());
1907 snapshot
1908 }
1909
1910 /// Create a snapshot file from the Env's current state.
1911 ///
1912 /// ### Panics
1913 ///
1914 /// If there is any error writing the file.
1915 pub fn to_ledger_snapshot_file(&self, p: impl AsRef<Path>) {
1916 self.to_ledger_snapshot().write_file(p).unwrap();
1917 }
1918
1919 /// Create an events snapshot from the Env's current state.
1920 pub(crate) fn to_events_snapshot(&self) -> EventsSnapshot {
1921 EventsSnapshot(
1922 self.host()
1923 .get_events()
1924 .unwrap()
1925 .0
1926 .into_iter()
1927 .filter(|e| match e.event.type_ {
1928 // Keep only system and contract events, because event
1929 // snapshots are used in test snapshots, and intended to be
1930 // stable over time because the goal is to record meaningful
1931 // observable behaviors. Diagnostic events are observable,
1932 // but events have no stability guarantees and are intended
1933 // to be used by developers when debugging, tracing, and
1934 // observing, not by systems that integrate.
1935 xdr::ContractEventType::System | xdr::ContractEventType::Contract => true,
1936 xdr::ContractEventType::Diagnostic => false,
1937 })
1938 .map(Into::into)
1939 .collect(),
1940 )
1941 }
1942
1943 /// Get the budget that tracks the resources consumed for the environment.
1944 #[deprecated(note = "use cost_estimate().budget()")]
1945 pub fn budget(&self) -> Budget {
1946 Budget::new(self.env_impl.budget_cloned())
1947 }
1948}
1949
1950#[cfg(any(test, feature = "testutils"))]
1951impl Drop for Env {
1952 fn drop(&mut self) {
1953 // If the env impl (Host) is finishable, that means this Env is the last
1954 // Env to hold a reference to the Host. The Env should only write a test
1955 // snapshot at that point when no other references to the host exist,
1956 // because it is only when there are no other references that the host
1957 // is being dropped.
1958 if self.env_impl.can_finish() && self.test_state.config.capture_snapshot_at_drop {
1959 self.to_test_snapshot_file();
1960 }
1961 }
1962}
1963
1964#[doc(hidden)]
1965#[cfg(any(test, feature = "testutils"))]
1966impl Env {
1967 /// Create a snapshot file for the currently executing test.
1968 ///
1969 /// Writes the file to the `test_snapshots/{test-name}.N.json` path where
1970 /// `N` is incremented for each unique `Env` in the test.
1971 ///
1972 /// Use to record the observable behavior of a test, and changes to that
1973 /// behavior over time. Commit the test snapshot file to version control and
1974 /// watch for changes in it on contract change, SDK upgrade, protocol
1975 /// upgrade, and other important events.
1976 ///
1977 /// No file will be created if the environment has no meaningful data such
1978 /// as stored entries or events.
1979 ///
1980 /// ### Panics
1981 ///
1982 /// If there is any error writing the file.
1983 pub(crate) fn to_test_snapshot_file(&self) {
1984 let snapshot = self.to_snapshot();
1985
1986 // Don't write a snapshot that has no data in it.
1987 if snapshot.ledger.entries().into_iter().count() == 0
1988 && snapshot.events.0.is_empty()
1989 && snapshot.auth.0.is_empty()
1990 {
1991 return;
1992 }
1993
1994 // Determine path to write test snapshots to.
1995 let Some(test_name) = &self.test_state.test_name else {
1996 // If there's no test name, we're not in a test context, so don't write snapshots.
1997 return;
1998 };
1999 let number = self.test_state.number;
2000 // Break up the test name into directories, using :: as the separator.
2001 // The :: module separator cannot be written into the filename because
2002 // some operating systems (e.g. Windows) do not allow the : character in
2003 // filenames.
2004 let test_name_path = test_name
2005 .split("::")
2006 .map(|p| std::path::Path::new(p).to_path_buf())
2007 .reduce(|p0, p1| p0.join(p1))
2008 .expect("test name to not be empty");
2009 let dir = std::path::Path::new("test_snapshots");
2010 let p = dir
2011 .join(&test_name_path)
2012 .with_extension(format!("{number}.json"));
2013
2014 // Write test snapshots to file.
2015 eprintln!("Writing test snapshot file for test {test_name:?} to {p:?}.");
2016 snapshot.write_file(p).unwrap();
2017 }
2018}
2019
2020#[doc(hidden)]
2021impl internal::EnvBase for Env {
2022 type Error = Infallible;
2023
2024 // This exists to allow code in conversion paths to upgrade an Error to an
2025 // Env::Error with some control granted to the underlying Env (and panic
2026 // paths kept out of the host). We delegate this to our env_impl and then,
2027 // since our own Error type is Infallible, immediately throw it into either
2028 // the env_impl's Error escalation path (if testing), or just plain panic.
2029 #[cfg(not(target_family = "wasm"))]
2030 fn error_from_error_val(&self, e: crate::Error) -> Self::Error {
2031 let host_err = self.env_impl.error_from_error_val(e);
2032 #[cfg(any(test, feature = "testutils"))]
2033 self.env_impl.escalate_error_to_panic(host_err);
2034 #[cfg(not(any(test, feature = "testutils")))]
2035 panic!("{:?}", host_err);
2036 }
2037
2038 // When targeting wasm we don't even need to do that, just delegate to
2039 // the Guest's impl, which calls core::arch::wasm32::unreachable.
2040 #[cfg(target_family = "wasm")]
2041 #[allow(unreachable_code)]
2042 fn error_from_error_val(&self, e: crate::Error) -> Self::Error {
2043 self.env_impl.error_from_error_val(e)
2044 }
2045
2046 fn check_protocol_version_lower_bound(&self, v: u32) -> Result<(), Self::Error> {
2047 Ok(self
2048 .env_impl
2049 .check_protocol_version_lower_bound(v)
2050 .unwrap_optimized())
2051 }
2052
2053 fn check_protocol_version_upper_bound(&self, v: u32) -> Result<(), Self::Error> {
2054 Ok(self
2055 .env_impl
2056 .check_protocol_version_upper_bound(v)
2057 .unwrap_optimized())
2058 }
2059
2060 // Note: the function `escalate_error_to_panic` only exists _on the `Env`
2061 // trait_ when the feature `soroban-env-common/testutils` is enabled. This
2062 // is because the host wants to never have this function even _compiled in_
2063 // when building for production, as it might be accidentally called (we have
2064 // mistakenly done so with conversion and comparison traits in the past).
2065 //
2066 // As a result, we only implement it here (fairly meaninglessly) when we're
2067 // in `cfg(test)` (which enables `soroban-env-host/testutils` thus
2068 // `soroban-env-common/testutils`) or when we've had our own `testutils`
2069 // feature enabled (which does the same).
2070 //
2071 // See the `internal::reject_err` functions above for more detail about what
2072 // it actually does (when implemented for real, on the host). In this
2073 // not-very-serious impl, since `Self::Error` is `Infallible`, this instance
2074 // can never actually be called and so its body is just a trivial
2075 // transformation from one empty type to another, for Type System Reasons.
2076 #[cfg(any(test, feature = "testutils"))]
2077 fn escalate_error_to_panic(&self, e: Self::Error) -> ! {
2078 match e {}
2079 }
2080
2081 fn bytes_copy_from_slice(
2082 &self,
2083 b: BytesObject,
2084 b_pos: U32Val,
2085 slice: &[u8],
2086 ) -> Result<BytesObject, Self::Error> {
2087 Ok(self
2088 .env_impl
2089 .bytes_copy_from_slice(b, b_pos, slice)
2090 .unwrap_optimized())
2091 }
2092
2093 fn bytes_copy_to_slice(
2094 &self,
2095 b: BytesObject,
2096 b_pos: U32Val,
2097 slice: &mut [u8],
2098 ) -> Result<(), Self::Error> {
2099 Ok(self
2100 .env_impl
2101 .bytes_copy_to_slice(b, b_pos, slice)
2102 .unwrap_optimized())
2103 }
2104
2105 fn bytes_new_from_slice(&self, slice: &[u8]) -> Result<BytesObject, Self::Error> {
2106 Ok(self.env_impl.bytes_new_from_slice(slice).unwrap_optimized())
2107 }
2108
2109 fn log_from_slice(&self, msg: &str, args: &[Val]) -> Result<Void, Self::Error> {
2110 Ok(self.env_impl.log_from_slice(msg, args).unwrap_optimized())
2111 }
2112
2113 fn string_copy_to_slice(
2114 &self,
2115 b: StringObject,
2116 b_pos: U32Val,
2117 slice: &mut [u8],
2118 ) -> Result<(), Self::Error> {
2119 Ok(self
2120 .env_impl
2121 .string_copy_to_slice(b, b_pos, slice)
2122 .unwrap_optimized())
2123 }
2124
2125 fn symbol_copy_to_slice(
2126 &self,
2127 b: SymbolObject,
2128 b_pos: U32Val,
2129 mem: &mut [u8],
2130 ) -> Result<(), Self::Error> {
2131 Ok(self
2132 .env_impl
2133 .symbol_copy_to_slice(b, b_pos, mem)
2134 .unwrap_optimized())
2135 }
2136
2137 fn string_new_from_slice(&self, slice: &[u8]) -> Result<StringObject, Self::Error> {
2138 Ok(self
2139 .env_impl
2140 .string_new_from_slice(slice)
2141 .unwrap_optimized())
2142 }
2143
2144 fn symbol_new_from_slice(&self, slice: &[u8]) -> Result<SymbolObject, Self::Error> {
2145 Ok(self
2146 .env_impl
2147 .symbol_new_from_slice(slice)
2148 .unwrap_optimized())
2149 }
2150
2151 fn map_new_from_slices(&self, keys: &[&str], vals: &[Val]) -> Result<MapObject, Self::Error> {
2152 Ok(self
2153 .env_impl
2154 .map_new_from_slices(keys, vals)
2155 .unwrap_optimized())
2156 }
2157
2158 fn map_unpack_to_slice(
2159 &self,
2160 map: MapObject,
2161 keys: &[&str],
2162 vals: &mut [Val],
2163 ) -> Result<Void, Self::Error> {
2164 Ok(self
2165 .env_impl
2166 .map_unpack_to_slice(map, keys, vals)
2167 .unwrap_optimized())
2168 }
2169
2170 fn vec_new_from_slice(&self, vals: &[Val]) -> Result<VecObject, Self::Error> {
2171 Ok(self.env_impl.vec_new_from_slice(vals).unwrap_optimized())
2172 }
2173
2174 fn vec_unpack_to_slice(&self, vec: VecObject, vals: &mut [Val]) -> Result<Void, Self::Error> {
2175 Ok(self
2176 .env_impl
2177 .vec_unpack_to_slice(vec, vals)
2178 .unwrap_optimized())
2179 }
2180
2181 fn symbol_index_in_strs(&self, key: Symbol, strs: &[&str]) -> Result<U32Val, Self::Error> {
2182 Ok(self
2183 .env_impl
2184 .symbol_index_in_strs(key, strs)
2185 .unwrap_optimized())
2186 }
2187}
2188
2189///////////////////////////////////////////////////////////////////////////////
2190/// X-macro use: impl Env for SDK's Env
2191///////////////////////////////////////////////////////////////////////////////
2192
2193// This is a helper macro used only by impl_env_for_sdk below. It consumes a
2194// token-tree of the form:
2195//
2196// {fn $fn_id:ident $args:tt -> $ret:ty}
2197//
2198// and produces the the corresponding method definition to be used in the
2199// SDK's Env implementation of the Env (calling through to the corresponding
2200// guest or host implementation).
2201macro_rules! sdk_function_helper {
2202 {$mod_id:ident, fn $fn_id:ident($($arg:ident:$type:ty),*) -> $ret:ty}
2203 =>
2204 {
2205 fn $fn_id(&self, $($arg:$type),*) -> Result<$ret, Self::Error> {
2206 internal::reject_err(&self.env_impl, self.env_impl.$fn_id($($arg),*))
2207 }
2208 };
2209}
2210
2211// This is a callback macro that pattern-matches the token-tree passed by the
2212// x-macro (call_macro_with_all_host_functions) and produces a suite of
2213// forwarding-method definitions, which it places in the body of the declaration
2214// of the implementation of Env for the SDK's Env.
2215macro_rules! impl_env_for_sdk {
2216 {
2217 $(
2218 // This outer pattern matches a single 'mod' block of the token-tree
2219 // passed from the x-macro to this macro. It is embedded in a `$()*`
2220 // pattern-repetition matcher so that it will match all provided
2221 // 'mod' blocks provided.
2222 $(#[$mod_attr:meta])*
2223 mod $mod_id:ident $mod_str:literal
2224 {
2225 $(
2226 // This inner pattern matches a single function description
2227 // inside a 'mod' block in the token-tree passed from the
2228 // x-macro to this macro. It is embedded in a `$()*`
2229 // pattern-repetition matcher so that it will match all such
2230 // descriptions.
2231 $(#[$fn_attr:meta])*
2232 { $fn_str:literal, $($min_proto:literal)?, $($max_proto:literal)?, fn $fn_id:ident $args:tt -> $ret:ty }
2233 )*
2234 }
2235 )*
2236 }
2237
2238 => // The part of the macro above this line is a matcher; below is its expansion.
2239
2240 {
2241 // This macro expands to a single item: the implementation of Env for
2242 // the SDK's Env struct used by client contract code running in a WASM VM.
2243 #[doc(hidden)]
2244 impl internal::Env for Env
2245 {
2246 $(
2247 $(
2248 // This invokes the guest_function_helper! macro above
2249 // passing only the relevant parts of the declaration
2250 // matched by the inner pattern above. It is embedded in two
2251 // nested `$()*` pattern-repetition expanders that
2252 // correspond to the pattern-repetition matchers in the
2253 // match section, but we ignore the structure of the 'mod'
2254 // block repetition-level from the outer pattern in the
2255 // expansion, flattening all functions from all 'mod' blocks
2256 // into the implementation of Env for Guest.
2257 sdk_function_helper!{$mod_id, fn $fn_id $args -> $ret}
2258 )*
2259 )*
2260 }
2261 };
2262}
2263
2264// Here we invoke the x-macro passing generate_env_trait as its callback macro.
2265internal::call_macro_with_all_host_functions! { impl_env_for_sdk }