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 self.env_impl
1013 .register_test_contract_with_constructor(
1014 contract_id.to_object(),
1015 Rc::new(InternalContractFunctionSet(contract)),
1016 constructor_args.into_val(self).to_object(),
1017 )
1018 .unwrap();
1019 contract_id
1020 }
1021
1022 /// Register a contract in a Wasm file with the [Env] for testing.
1023 ///
1024 /// Passing a contract ID for the first arguments registers the contract
1025 /// with that contract ID. Providing `None` causes the Env to generate a new
1026 /// contract ID that is assigned to the contract.
1027 ///
1028 /// Registering a contract that is already registered replaces it.
1029 /// Use re-registration with caution as it does not exist in the real
1030 /// (on-chain) environment. Specifically, the new contract's constructor
1031 /// will be called again during re-registration. That behavior only exists
1032 /// for this test utility and is not reproducible on-chain, where contract
1033 /// Wasm updates don't cause constructor to be called.
1034 ///
1035 /// Returns the address of the registered contract.
1036 ///
1037 /// ### Examples
1038 /// ```
1039 /// use soroban_sdk::{BytesN, Env};
1040 ///
1041 /// const WASM: &[u8] = include_bytes!("../doctest_fixtures/contract.wasm");
1042 ///
1043 /// #[test]
1044 /// fn test() {
1045 /// # }
1046 /// # fn main() {
1047 /// let env = Env::default();
1048 /// env.register_contract_wasm(None, WASM);
1049 /// }
1050 /// ```
1051 #[deprecated(note = "use `register`")]
1052 pub fn register_contract_wasm<'a>(
1053 &self,
1054 contract_id: impl Into<Option<&'a Address>>,
1055 contract_wasm: impl IntoVal<Env, Bytes>,
1056 ) -> Address {
1057 let wasm_hash: BytesN<32> = self.deployer().upload_contract_wasm(contract_wasm);
1058 self.register_contract_with_optional_contract_id_and_executable(
1059 contract_id,
1060 xdr::ContractExecutable::Wasm(xdr::Hash(wasm_hash.into())),
1061 crate::vec![&self],
1062 )
1063 }
1064
1065 /// Register a contract in a Wasm file with the [Env] for testing.
1066 ///
1067 /// This acts the in the same fashion as `register_contract`, but allows
1068 /// passing arguments to the contract's constructor.
1069 ///
1070 /// Passing a contract ID for the first arguments registers the contract
1071 /// with that contract ID. Providing `None` causes the Env to generate a new
1072 /// contract ID that is assigned to the contract.
1073 ///
1074 /// Registering a contract that is already registered replaces it.
1075 /// Use re-registration with caution as it does not exist in the real
1076 /// (on-chain) environment. Specifically, the new contract's constructor
1077 /// will be called again during re-registration. That behavior only exists
1078 /// for this test utility and is not reproducible on-chain, where contract
1079 /// Wasm updates don't cause constructor to be called.
1080 ///
1081 /// Returns the address of the registered contract.
1082 pub(crate) fn register_contract_wasm_with_constructor<'a>(
1083 &self,
1084 contract_id: impl Into<Option<&'a Address>>,
1085 contract_wasm: impl IntoVal<Env, Bytes>,
1086 constructor_args: impl ConstructorArgs,
1087 ) -> Address {
1088 let wasm_hash: BytesN<32> = self.deployer().upload_contract_wasm(contract_wasm);
1089 self.register_contract_with_optional_contract_id_and_executable(
1090 contract_id,
1091 xdr::ContractExecutable::Wasm(xdr::Hash(wasm_hash.into())),
1092 constructor_args.into_val(self),
1093 )
1094 }
1095
1096 /// Register the built-in Stellar Asset Contract with provided admin address.
1097 ///
1098 /// Returns a utility struct that contains the contract ID of the registered
1099 /// token contract, as well as methods to read and update issuer flags.
1100 ///
1101 /// The contract will wrap a randomly-generated Stellar asset. This function
1102 /// is useful for using in the tests when an arbitrary token contract
1103 /// instance is needed.
1104 pub fn register_stellar_asset_contract_v2(&self, admin: Address) -> StellarAssetContract {
1105 let issuer_pk = self.with_generator(|mut g| g.address());
1106 let issuer_id = xdr::AccountId(xdr::PublicKey::PublicKeyTypeEd25519(xdr::Uint256(
1107 issuer_pk.clone(),
1108 )));
1109
1110 let k = Rc::new(xdr::LedgerKey::Account(xdr::LedgerKeyAccount {
1111 account_id: issuer_id.clone(),
1112 }));
1113
1114 if self.host().get_ledger_entry(&k).unwrap().is_none() {
1115 let v = Rc::new(xdr::LedgerEntry {
1116 data: xdr::LedgerEntryData::Account(xdr::AccountEntry {
1117 account_id: issuer_id.clone(),
1118 balance: 0,
1119 flags: 0,
1120 home_domain: Default::default(),
1121 inflation_dest: None,
1122 num_sub_entries: 0,
1123 seq_num: xdr::SequenceNumber(0),
1124 thresholds: xdr::Thresholds([1; 4]),
1125 signers: xdr::VecM::default(),
1126 ext: xdr::AccountEntryExt::V0,
1127 }),
1128 last_modified_ledger_seq: 0,
1129 ext: xdr::LedgerEntryExt::V0,
1130 });
1131 self.host().add_ledger_entry(&k, &v, None).unwrap();
1132 }
1133
1134 let asset = xdr::Asset::CreditAlphanum4(xdr::AlphaNum4 {
1135 asset_code: xdr::AssetCode4([b'a', b'a', b'a', 0]),
1136 issuer: issuer_id.clone(),
1137 });
1138 let create = xdr::HostFunction::CreateContract(xdr::CreateContractArgs {
1139 contract_id_preimage: xdr::ContractIdPreimage::Asset(asset.clone()),
1140 executable: xdr::ContractExecutable::StellarAsset,
1141 });
1142
1143 let token_id: Address = self
1144 .env_impl
1145 .invoke_function(create)
1146 .unwrap()
1147 .try_into_val(self)
1148 .unwrap();
1149
1150 let prev_auth_manager = self.env_impl.snapshot_auth_manager().unwrap();
1151 self.env_impl
1152 .switch_to_recording_auth_inherited_from_snapshot(&prev_auth_manager)
1153 .unwrap();
1154 let admin_result = self.try_invoke_contract::<(), Error>(
1155 &token_id,
1156 &soroban_sdk_macros::internal_symbol_short!("set_admin"),
1157 (admin,).try_into_val(self).unwrap(),
1158 );
1159 self.env_impl.set_auth_manager(prev_auth_manager).unwrap();
1160 admin_result.unwrap().unwrap();
1161
1162 let issuer = StellarAssetIssuer::new(self.clone(), issuer_id);
1163 StellarAssetContract::new(token_id, issuer, asset)
1164 }
1165
1166 /// Register the built-in Stellar Asset Contract with provided admin address.
1167 ///
1168 /// Returns the contract ID of the registered token contract.
1169 ///
1170 /// The contract will wrap a randomly-generated Stellar asset. This function
1171 /// is useful for using in the tests when an arbitrary token contract
1172 /// instance is needed.
1173 #[deprecated(note = "use [Env::register_stellar_asset_contract_v2]")]
1174 pub fn register_stellar_asset_contract(&self, admin: Address) -> Address {
1175 self.register_stellar_asset_contract_v2(admin).address()
1176 }
1177
1178 fn register_contract_with_optional_contract_id_and_executable<'a>(
1179 &self,
1180 contract_id: impl Into<Option<&'a Address>>,
1181 executable: xdr::ContractExecutable,
1182 constructor_args: Vec<Val>,
1183 ) -> Address {
1184 if let Some(contract_id) = contract_id.into() {
1185 self.register_contract_with_contract_id_and_executable(
1186 contract_id,
1187 executable,
1188 constructor_args,
1189 );
1190 contract_id.clone()
1191 } else {
1192 self.register_contract_with_source(executable, constructor_args)
1193 }
1194 }
1195
1196 fn register_contract_with_source(
1197 &self,
1198 executable: xdr::ContractExecutable,
1199 constructor_args: Vec<Val>,
1200 ) -> Address {
1201 let args_vec: std::vec::Vec<xdr::ScVal> =
1202 constructor_args.iter().map(|v| v.into_val(self)).collect();
1203 let constructor_args = args_vec.try_into().unwrap();
1204 let prev_auth_manager = self.env_impl.snapshot_auth_manager().unwrap();
1205 self.env_impl
1206 .switch_to_recording_auth_inherited_from_snapshot(&prev_auth_manager)
1207 .unwrap();
1208 let create_result = self
1209 .env_impl
1210 .invoke_function(xdr::HostFunction::CreateContractV2(
1211 xdr::CreateContractArgsV2 {
1212 contract_id_preimage: xdr::ContractIdPreimage::Address(
1213 xdr::ContractIdPreimageFromAddress {
1214 address: xdr::ScAddress::Contract(xdr::ContractId(xdr::Hash(
1215 self.with_generator(|mut g| g.address()),
1216 ))),
1217 salt: xdr::Uint256([0; 32]),
1218 },
1219 ),
1220 executable,
1221 constructor_args,
1222 },
1223 ));
1224
1225 self.env_impl.set_auth_manager(prev_auth_manager).unwrap();
1226
1227 create_result.unwrap().try_into_val(self).unwrap()
1228 }
1229
1230 /// Set authorizations and signatures in the environment which will be
1231 /// consumed by contracts when they invoke [`Address::require_auth`] or
1232 /// [`Address::require_auth_for_args`] functions.
1233 ///
1234 /// Requires valid signatures for the authorization to be successful.
1235 ///
1236 /// This function can also be called on contract clients.
1237 ///
1238 /// To mock auth for testing, without requiring valid signatures, use
1239 /// [`mock_all_auths`][Self::mock_all_auths] or
1240 /// [`mock_auths`][Self::mock_auths]. If mocking of auths is enabled,
1241 /// calling [`set_auths`][Self::set_auths] disables any mocking.
1242 pub fn set_auths(&self, auths: &[SorobanAuthorizationEntry]) {
1243 self.env_impl
1244 .set_authorization_entries(auths.to_vec())
1245 .unwrap();
1246 }
1247
1248 /// Mock authorizations in the environment which will cause matching invokes
1249 /// of [`Address::require_auth`] and [`Address::require_auth_for_args`] to
1250 /// pass.
1251 ///
1252 /// This function can also be called on contract clients.
1253 ///
1254 /// Authorizations not matching a mocked auth will fail.
1255 ///
1256 /// To mock all auths, use [`mock_all_auths`][Self::mock_all_auths].
1257 ///
1258 /// ### Examples
1259 /// ```
1260 /// use soroban_sdk::{contract, contractimpl, Env, Address, testutils::{Address as _, MockAuth, MockAuthInvoke}, IntoVal};
1261 ///
1262 /// #[contract]
1263 /// pub struct HelloContract;
1264 ///
1265 /// #[contractimpl]
1266 /// impl HelloContract {
1267 /// pub fn hello(env: Env, from: Address) {
1268 /// from.require_auth();
1269 /// // TODO
1270 /// }
1271 /// }
1272 ///
1273 /// #[test]
1274 /// fn test() {
1275 /// # }
1276 /// # fn main() {
1277 /// let env = Env::default();
1278 /// let contract_id = env.register(HelloContract, ());
1279 ///
1280 /// let client = HelloContractClient::new(&env, &contract_id);
1281 /// let addr = Address::generate(&env);
1282 /// client.mock_auths(&[
1283 /// MockAuth {
1284 /// address: &addr,
1285 /// invoke: &MockAuthInvoke {
1286 /// contract: &contract_id,
1287 /// fn_name: "hello",
1288 /// args: (&addr,).into_val(&env),
1289 /// sub_invokes: &[],
1290 /// },
1291 /// },
1292 /// ]).hello(&addr);
1293 /// }
1294 /// ```
1295 pub fn mock_auths(&self, auths: &[MockAuth]) {
1296 for a in auths {
1297 self.register_at(a.address, MockAuthContract, ());
1298 }
1299 let auths = auths
1300 .iter()
1301 .cloned()
1302 .map(Into::into)
1303 .collect::<std::vec::Vec<_>>();
1304 self.env_impl.set_authorization_entries(auths).unwrap();
1305 }
1306
1307 /// Mock all calls to the [`Address::require_auth`] and
1308 /// [`Address::require_auth_for_args`] functions in invoked contracts,
1309 /// having them succeed as if authorization was provided.
1310 ///
1311 /// When mocking is enabled, if the [`Address`] being authorized is the
1312 /// address of a contract, that contract's `__check_auth` function will not
1313 /// be called, and the contract does not need to exist or be registered in
1314 /// the test.
1315 ///
1316 /// When mocking is enabled, if the [`Address`] being authorized is the
1317 /// address of an account, the account does not need to exist.
1318 ///
1319 /// This function can also be called on contract clients.
1320 ///
1321 /// To disable mocking, see [`set_auths`][Self::set_auths].
1322 ///
1323 /// To access a list of auths that have occurred, see [`auths`][Self::auths].
1324 ///
1325 /// It is not currently possible to mock a subset of auths.
1326 ///
1327 /// A test that uses `mock_all_auths` without verifying the resulting
1328 /// authorization tree via [`auths`][Self::auths] can pass even when a contract
1329 /// is missing a `require_auth` check. Use [`auths`][Self::auths] after
1330 /// the contract call to assert that the expected authorizations were required.
1331 ///
1332 /// ### Examples
1333 /// ```
1334 /// use soroban_sdk::{contract, contractimpl, Env, Address, IntoVal, symbol_short};
1335 /// use soroban_sdk::testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation};
1336 ///
1337 /// #[contract]
1338 /// pub struct HelloContract;
1339 ///
1340 /// #[contractimpl]
1341 /// impl HelloContract {
1342 /// pub fn hello(env: Env, from: Address) {
1343 /// from.require_auth();
1344 /// // TODO
1345 /// }
1346 /// }
1347 ///
1348 /// #[test]
1349 /// fn test() {
1350 /// # }
1351 /// # fn main() {
1352 /// let env = Env::default();
1353 /// let contract_id = env.register(HelloContract, ());
1354 ///
1355 /// env.mock_all_auths();
1356 ///
1357 /// let client = HelloContractClient::new(&env, &contract_id);
1358 /// let addr = Address::generate(&env);
1359 /// client.hello(&addr);
1360 ///
1361 /// // Verify that the expected authorization was required.
1362 /// assert_eq!(
1363 /// env.auths(),
1364 /// [(
1365 /// addr.clone(),
1366 /// AuthorizedInvocation {
1367 /// function: AuthorizedFunction::Contract((
1368 /// contract_id,
1369 /// symbol_short!("hello"),
1370 /// (&addr,).into_val(&env),
1371 /// )),
1372 /// sub_invocations: [].into(),
1373 /// }
1374 /// )]
1375 /// );
1376 /// }
1377 /// ```
1378 pub fn mock_all_auths(&self) {
1379 self.env_impl.switch_to_recording_auth(true).unwrap();
1380 }
1381
1382 /// A version of [`mock_all_auths`][Self::mock_all_auths] that allows authorizations that are not
1383 /// present in the root invocation.
1384 ///
1385 /// Refer to [`mock_all_auths`][Self::mock_all_auths] documentation for general information and
1386 /// prefer using [`mock_all_auths`][Self::mock_all_auths] unless non-root authorization is required.
1387 ///
1388 /// The only difference from [`mock_all_auths`][Self::mock_all_auths] is that this won't return an
1389 /// error when `require_auth` hasn't been called in the root invocation for
1390 /// any given address. This is useful to test contracts that bundle calls to
1391 /// another contract without atomicity requirements (i.e. any contract call
1392 /// can be frontrun).
1393 ///
1394 /// ### Examples
1395 /// ```
1396 /// use soroban_sdk::{contract, contractimpl, Env, Address, testutils::Address as _};
1397 ///
1398 /// #[contract]
1399 /// pub struct ContractA;
1400 ///
1401 /// #[contractimpl]
1402 /// impl ContractA {
1403 /// pub fn do_auth(env: Env, addr: Address) {
1404 /// addr.require_auth();
1405 /// }
1406 /// }
1407 /// #[contract]
1408 /// pub struct ContractB;
1409 ///
1410 /// #[contractimpl]
1411 /// impl ContractB {
1412 /// pub fn call_a(env: Env, contract_a: Address, addr: Address) {
1413 /// // Notice there is no `require_auth` call here.
1414 /// ContractAClient::new(&env, &contract_a).do_auth(&addr);
1415 /// }
1416 /// }
1417 /// #[test]
1418 /// fn test() {
1419 /// # }
1420 /// # fn main() {
1421 /// let env = Env::default();
1422 /// let contract_a = env.register(ContractA, ());
1423 /// let contract_b = env.register(ContractB, ());
1424 /// // The regular `env.mock_all_auths()` would result in the call
1425 /// // failure.
1426 /// env.mock_all_auths_allowing_non_root_auth();
1427 ///
1428 /// let client = ContractBClient::new(&env, &contract_b);
1429 /// let addr = Address::generate(&env);
1430 /// client.call_a(&contract_a, &addr);
1431 /// }
1432 /// ```
1433 pub fn mock_all_auths_allowing_non_root_auth(&self) {
1434 self.env_impl.switch_to_recording_auth(false).unwrap();
1435 }
1436
1437 /// Returns a list of authorization trees that were seen during the last
1438 /// contract or authorized host function invocation.
1439 ///
1440 /// Use this in tests to verify that the expected authorizations with the
1441 /// expected arguments are required.
1442 ///
1443 /// The return value is a vector of authorizations represented by tuples of
1444 /// `(address, AuthorizedInvocation)`. `AuthorizedInvocation` describes the
1445 /// tree of `require_auth_for_args(address, args)` from the contract
1446 /// functions (or `require_auth` with all the arguments of the function
1447 /// invocation). It also might contain the authorized host functions (
1448 /// currently CreateContract is the only such function) in case if
1449 /// corresponding host functions have been called.
1450 ///
1451 /// Refer to documentation for `AuthorizedInvocation` for detailed
1452 /// information on its contents.
1453 ///
1454 /// The order of the returned vector is defined by the order of
1455 /// [`Address::require_auth`] calls. Repeated calls to
1456 /// [`Address::require_auth`] with the same address and args in the same
1457 /// tree of contract invocations will appear only once in the vector.
1458 ///
1459 /// ### Examples
1460 /// ```
1461 /// use soroban_sdk::{contract, contractimpl, testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation}, symbol_short, Address, Symbol, Env, IntoVal};
1462 ///
1463 /// #[contract]
1464 /// pub struct Contract;
1465 ///
1466 /// #[contractimpl]
1467 /// impl Contract {
1468 /// pub fn transfer(env: Env, address: Address, amount: i128) {
1469 /// address.require_auth();
1470 /// }
1471 /// pub fn transfer2(env: Env, address: Address, amount: i128) {
1472 /// address.require_auth_for_args((amount / 2,).into_val(&env));
1473 /// }
1474 /// }
1475 ///
1476 /// #[test]
1477 /// fn test() {
1478 /// # }
1479 /// # #[cfg(feature = "testutils")]
1480 /// # fn main() {
1481 /// let env = Env::default();
1482 /// let contract_id = env.register(Contract, ());
1483 /// let client = ContractClient::new(&env, &contract_id);
1484 /// env.mock_all_auths();
1485 /// let address = Address::generate(&env);
1486 /// client.transfer(&address, &1000_i128);
1487 /// assert_eq!(
1488 /// env.auths(),
1489 /// [(
1490 /// address.clone(),
1491 /// AuthorizedInvocation {
1492 /// function: AuthorizedFunction::Contract((
1493 /// client.address.clone(),
1494 /// symbol_short!("transfer"),
1495 /// (&address, 1000_i128,).into_val(&env)
1496 /// )),
1497 /// sub_invocations: [].into()
1498 /// }
1499 /// )]
1500 /// );
1501 ///
1502 /// client.transfer2(&address, &1000_i128);
1503 /// assert_eq!(
1504 /// env.auths(),
1505 /// [(
1506 /// address.clone(),
1507 /// AuthorizedInvocation {
1508 /// function: AuthorizedFunction::Contract((
1509 /// client.address.clone(),
1510 /// symbol_short!("transfer2"),
1511 /// // `transfer2` requires auth for (amount / 2) == (1000 / 2) == 500.
1512 /// (500_i128,).into_val(&env)
1513 /// )),
1514 /// sub_invocations: [].into()
1515 /// }
1516 /// )]
1517 /// );
1518 /// }
1519 /// # #[cfg(not(feature = "testutils"))]
1520 /// # fn main() { }
1521 /// ```
1522 pub fn auths(&self) -> std::vec::Vec<(Address, AuthorizedInvocation)> {
1523 (*self.test_state.auth_snapshot)
1524 .borrow()
1525 .0
1526 .last()
1527 .cloned()
1528 .unwrap_or_default()
1529 .into_iter()
1530 .map(|(sc_addr, invocation)| {
1531 (
1532 xdr::ScVal::Address(sc_addr).try_into_val(self).unwrap(),
1533 AuthorizedInvocation::from_xdr(self, &invocation),
1534 )
1535 })
1536 .collect()
1537 }
1538
1539 /// Invokes the special `__check_auth` function of contracts that implement
1540 /// the custom account interface.
1541 ///
1542 /// `__check_auth` can't be called outside of the host-managed `require_auth`
1543 /// calls. This test utility allows testing custom account contracts without
1544 /// the need to setup complex contract call trees and enabling the enforcing
1545 /// auth on the host side.
1546 ///
1547 /// This function requires to provide the template argument for error. Use
1548 /// `soroban_sdk::Error` if `__check_auth` doesn't return a special
1549 /// contract error and use the error with `contracterror` attribute
1550 /// otherwise.
1551 ///
1552 /// ### Examples
1553 /// ```
1554 /// use soroban_sdk::{contract, contracterror, contractimpl, testutils::{Address as _, BytesN as _}, vec, auth::Context, BytesN, Env, Vec, Val};
1555 ///
1556 /// #[contracterror]
1557 /// #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
1558 /// #[repr(u32)]
1559 /// pub enum NoopAccountError {
1560 /// SomeError = 1,
1561 /// }
1562 /// #[contract]
1563 /// struct NoopAccountContract;
1564 /// #[contractimpl]
1565 /// impl NoopAccountContract {
1566 ///
1567 /// #[allow(non_snake_case)]
1568 /// pub fn __check_auth(
1569 /// _env: Env,
1570 /// _signature_payload: BytesN<32>,
1571 /// signature: Val,
1572 /// _auth_context: Vec<Context>,
1573 /// ) -> Result<(), NoopAccountError> {
1574 /// if signature.is_void() {
1575 /// Err(NoopAccountError::SomeError)
1576 /// } else {
1577 /// Ok(())
1578 /// }
1579 /// }
1580 /// }
1581 /// #[test]
1582 /// fn test() {
1583 /// # }
1584 /// # fn main() {
1585 /// let e: Env = Default::default();
1586 /// let account_contract = NoopAccountContractClient::new(&e, &e.register(NoopAccountContract, ()));
1587 /// // Non-successful call of `__check_auth` with a `contracterror` error.
1588 /// assert_eq!(
1589 /// e.try_invoke_contract_check_auth::<NoopAccountError>(
1590 /// &account_contract.address,
1591 /// &BytesN::from_array(&e, &[0; 32]),
1592 /// ().into(),
1593 /// &vec![&e],
1594 /// ),
1595 /// // The inner `Result` is for conversion error and will be Ok
1596 /// // as long as a valid error type used.
1597 /// Err(Ok(NoopAccountError::SomeError))
1598 /// );
1599 /// // Successful call of `__check_auth` with a `soroban_sdk::InvokeError`
1600 /// // error - this should be compatible with any error type.
1601 /// assert_eq!(
1602 /// e.try_invoke_contract_check_auth::<soroban_sdk::InvokeError>(
1603 /// &account_contract.address,
1604 /// &BytesN::from_array(&e, &[0; 32]),
1605 /// 0_i32.into(),
1606 /// &vec![&e],
1607 /// ),
1608 /// Ok(())
1609 /// );
1610 /// }
1611 /// ```
1612 pub fn try_invoke_contract_check_auth<E>(
1613 &self,
1614 contract: &Address,
1615 signature_payload: &BytesN<32>,
1616 signature: Val,
1617 auth_context: &Vec<auth::Context>,
1618 ) -> Result<(), Result<E, InvokeError>>
1619 where
1620 E: TryFrom<Error>,
1621 E::Error: Into<InvokeError>,
1622 {
1623 let args = Vec::from_array(
1624 self,
1625 [signature_payload.to_val(), signature, auth_context.to_val()],
1626 );
1627 let res = self
1628 .host()
1629 .call_account_contract_check_auth(contract.to_object(), args.to_object());
1630 match res {
1631 Ok(rv) => Ok(rv.into_val(self)),
1632 Err(e) => Err(e.error.try_into().map_err(Into::into)),
1633 }
1634 }
1635
1636 fn register_contract_with_contract_id_and_executable(
1637 &self,
1638 contract_address: &Address,
1639 executable: xdr::ContractExecutable,
1640 constructor_args: Vec<Val>,
1641 ) {
1642 let contract_id = contract_address.contract_id();
1643 let data_key = xdr::ScVal::LedgerKeyContractInstance;
1644 let key = Rc::new(LedgerKey::ContractData(LedgerKeyContractData {
1645 contract: xdr::ScAddress::Contract(contract_id.clone()),
1646 key: data_key.clone(),
1647 durability: xdr::ContractDataDurability::Persistent,
1648 }));
1649
1650 let instance = xdr::ScContractInstance {
1651 executable,
1652 storage: Default::default(),
1653 };
1654
1655 let entry = Rc::new(LedgerEntry {
1656 ext: xdr::LedgerEntryExt::V0,
1657 last_modified_ledger_seq: 0,
1658 data: xdr::LedgerEntryData::ContractData(xdr::ContractDataEntry {
1659 contract: xdr::ScAddress::Contract(contract_id.clone()),
1660 key: data_key,
1661 val: xdr::ScVal::ContractInstance(instance),
1662 durability: xdr::ContractDataDurability::Persistent,
1663 ext: xdr::ExtensionPoint::V0,
1664 }),
1665 });
1666 let live_until_ledger = self.ledger().sequence() + 1;
1667 self.host()
1668 .add_ledger_entry(&key, &entry, Some(live_until_ledger))
1669 .unwrap();
1670 self.env_impl
1671 .call_constructor_for_stored_contract_unsafe(&contract_id, constructor_args.to_object())
1672 .unwrap();
1673 }
1674
1675 /// Run the function as if executed by the given contract ID.
1676 ///
1677 /// Used to write or read contract data, or take other actions in tests for
1678 /// setting up tests or asserting on internal state.
1679 ///
1680 /// ### Examples
1681 /// ```
1682 /// use soroban_sdk::{contract, contractimpl, Env, Symbol};
1683 ///
1684 /// #[contract]
1685 /// pub struct HelloContract;
1686 ///
1687 /// #[contractimpl]
1688 /// impl HelloContract {
1689 /// pub fn set_storage(env: Env, key: Symbol, val: Symbol) {
1690 /// env.storage().persistent().set(&key, &val);
1691 /// }
1692 /// }
1693 ///
1694 /// #[test]
1695 /// fn test() {
1696 /// # }
1697 /// # fn main() {
1698 /// let env = Env::default();
1699 /// let contract_id = env.register(HelloContract, ());
1700 /// let client = HelloContractClient::new(&env, &contract_id);
1701 ///
1702 /// let key = Symbol::new(&env, "foo");
1703 /// let val = Symbol::new(&env, "bar");
1704 ///
1705 /// // Set storage using the contract
1706 /// client.set_storage(&key, &val);
1707 ///
1708 /// // Successfully read the storage key
1709 /// let result = env.as_contract(&contract_id, || {
1710 /// env.storage()
1711 /// .persistent()
1712 /// .get::<Symbol, Symbol>(&key)
1713 /// .unwrap()
1714 /// });
1715 /// assert_eq!(result, val);
1716 /// }
1717 /// ```
1718 pub fn as_contract<T>(&self, id: &Address, f: impl FnOnce() -> T) -> T {
1719 let id = id.contract_id();
1720 let func = Symbol::from_small_str("");
1721 let mut t: Option<T> = None;
1722 self.env_impl
1723 .with_test_contract_frame(id, func, || {
1724 t = Some(f());
1725 Ok(().into())
1726 })
1727 .unwrap();
1728 t.unwrap()
1729 }
1730
1731 /// Run the function as if executed by the given contract ID. Returns an
1732 /// error if the function execution fails for any reason.
1733 ///
1734 /// Used to write or read contract data, or take other actions in tests for
1735 /// setting up tests or asserting on internal state.
1736 ///
1737 /// ### Examples
1738 /// ```
1739 /// use soroban_sdk::{contract, contractimpl, xdr::{ScErrorCode, ScErrorType}, Env, Error, Symbol};
1740 ///
1741 /// #[contract]
1742 /// pub struct HelloContract;
1743 ///
1744 /// #[contractimpl]
1745 /// impl HelloContract {
1746 /// pub fn set_storage(env: Env, key: Symbol, val: Symbol) {
1747 /// env.storage().persistent().set(&key, &val);
1748 /// }
1749 /// }
1750 ///
1751 /// #[test]
1752 /// fn test() {
1753 /// # }
1754 /// # fn main() {
1755 /// let env = Env::default();
1756 /// let contract_id = env.register(HelloContract, ());
1757 /// let client = HelloContractClient::new(&env, &contract_id);
1758 ///
1759 /// let key = Symbol::new(&env, "foo");
1760 /// let val = Symbol::new(&env, "bar");
1761 ///
1762 /// // Set storage using the contract
1763 /// client.set_storage(&key, &val);
1764 ///
1765 /// // Successfully read the storage key
1766 /// let result = env.try_as_contract::<Symbol, Error>(&contract_id, || {
1767 /// env.storage()
1768 /// .persistent()
1769 /// .get::<Symbol, Symbol>(&key)
1770 /// .unwrap()
1771 /// });
1772 /// assert_eq!(result, Ok(val));
1773 ///
1774 /// // Attempting to extend TTL of a non-existent key throws an error
1775 /// let new_key = Symbol::new(&env, "baz");
1776 /// let result = env.try_as_contract(&contract_id, || {
1777 /// env.storage().persistent().extend_ttl(&new_key, 1, 100);
1778 /// });
1779 /// assert_eq!(
1780 /// result,
1781 /// Err(Ok(Error::from_type_and_code(
1782 /// ScErrorType::Storage,
1783 /// ScErrorCode::MissingValue
1784 /// )))
1785 /// );
1786 /// }
1787 /// ```
1788 pub fn try_as_contract<T, E>(
1789 &self,
1790 id: &Address,
1791 f: impl FnOnce() -> T,
1792 ) -> Result<T, Result<E, InvokeError>>
1793 where
1794 E: TryFrom<Error>,
1795 E::Error: Into<InvokeError>,
1796 {
1797 let id = id.contract_id();
1798 let func = Symbol::from_small_str("");
1799 let mut t: Option<T> = None;
1800 let result = self.env_impl.try_with_test_contract_frame(id, func, || {
1801 t = Some(f());
1802 Ok(().into())
1803 });
1804
1805 match result {
1806 Ok(_) => Ok(t.unwrap()),
1807 Err(e) => Err(E::try_from(e.error).map_err(Into::into)),
1808 }
1809 }
1810
1811 /// Creates a new Env loaded with the [`Snapshot`].
1812 ///
1813 /// The ledger info and state in the snapshot are loaded into the Env.
1814 ///
1815 /// Events, as an output source only, are not loaded into the Env.
1816 pub fn from_snapshot(s: Snapshot) -> Env {
1817 Env::new_for_testutils(
1818 EnvTestConfig::default(),
1819 Rc::new(s.ledger.clone()),
1820 Some(Rc::new(RefCell::new(s.generators))),
1821 Some(s.ledger.ledger_info()),
1822 Some(Rc::new(s.ledger.clone())),
1823 )
1824 }
1825
1826 /// Creates a new Env loaded with the ledger snapshot loaded from the file.
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 ///
1832 /// ### Panics
1833 ///
1834 /// If there is any error reading the file.
1835 pub fn from_snapshot_file(p: impl AsRef<Path>) -> Env {
1836 Self::from_snapshot(Snapshot::read_file(p).unwrap())
1837 }
1838
1839 /// Create a snapshot from the Env's current state.
1840 pub fn to_snapshot(&self) -> Snapshot {
1841 Snapshot {
1842 generators: (*self.test_state.generators).borrow().clone(),
1843 auth: (*self.test_state.auth_snapshot).borrow().clone(),
1844 ledger: self.to_ledger_snapshot(),
1845 events: self.to_events_snapshot(),
1846 }
1847 }
1848
1849 /// Create a snapshot file from the Env's current state.
1850 ///
1851 /// ### Panics
1852 ///
1853 /// If there is any error writing the file.
1854 pub fn to_snapshot_file(&self, p: impl AsRef<Path>) {
1855 self.to_snapshot().write_file(p).unwrap();
1856 }
1857
1858 /// Creates a new Env loaded with the snapshot source.
1859 ///
1860 /// The ledger info and state from the snapshot source are loaded into the Env.
1861 pub fn from_ledger_snapshot(input: impl Into<SnapshotSourceInput>) -> Env {
1862 let SnapshotSourceInput {
1863 source,
1864 ledger_info,
1865 snapshot,
1866 } = input.into();
1867
1868 Env::new_for_testutils(
1869 EnvTestConfig::default(), // TODO: Allow setting the config.
1870 source,
1871 None,
1872 ledger_info,
1873 snapshot,
1874 )
1875 }
1876
1877 /// Creates a new Env loaded with the ledger snapshot loaded from the file.
1878 ///
1879 /// ### Panics
1880 ///
1881 /// If there is any error reading the file.
1882 pub fn from_ledger_snapshot_file(p: impl AsRef<Path>) -> Env {
1883 Self::from_ledger_snapshot(LedgerSnapshot::read_file(p).unwrap())
1884 }
1885
1886 /// Create a snapshot from the Env's current state.
1887 pub fn to_ledger_snapshot(&self) -> LedgerSnapshot {
1888 let snapshot = self.test_state.snapshot.clone().unwrap_or_default();
1889 let mut snapshot = (*snapshot).clone();
1890 snapshot.set_ledger_info(self.ledger().get());
1891 snapshot.update_entries(&self.host().get_stored_entries().unwrap());
1892 snapshot
1893 }
1894
1895 /// Create a snapshot file from the Env's current state.
1896 ///
1897 /// ### Panics
1898 ///
1899 /// If there is any error writing the file.
1900 pub fn to_ledger_snapshot_file(&self, p: impl AsRef<Path>) {
1901 self.to_ledger_snapshot().write_file(p).unwrap();
1902 }
1903
1904 /// Create an events snapshot from the Env's current state.
1905 pub(crate) fn to_events_snapshot(&self) -> EventsSnapshot {
1906 EventsSnapshot(
1907 self.host()
1908 .get_events()
1909 .unwrap()
1910 .0
1911 .into_iter()
1912 .filter(|e| match e.event.type_ {
1913 // Keep only system and contract events, because event
1914 // snapshots are used in test snapshots, and intended to be
1915 // stable over time because the goal is to record meaningful
1916 // observable behaviors. Diagnostic events are observable,
1917 // but events have no stability guarantees and are intended
1918 // to be used by developers when debugging, tracing, and
1919 // observing, not by systems that integrate.
1920 xdr::ContractEventType::System | xdr::ContractEventType::Contract => true,
1921 xdr::ContractEventType::Diagnostic => false,
1922 })
1923 .map(Into::into)
1924 .collect(),
1925 )
1926 }
1927
1928 /// Get the budget that tracks the resources consumed for the environment.
1929 #[deprecated(note = "use cost_estimate().budget()")]
1930 pub fn budget(&self) -> Budget {
1931 Budget::new(self.env_impl.budget_cloned())
1932 }
1933}
1934
1935#[cfg(any(test, feature = "testutils"))]
1936impl Drop for Env {
1937 fn drop(&mut self) {
1938 // If the env impl (Host) is finishable, that means this Env is the last
1939 // Env to hold a reference to the Host. The Env should only write a test
1940 // snapshot at that point when no other references to the host exist,
1941 // because it is only when there are no other references that the host
1942 // is being dropped.
1943 if self.env_impl.can_finish() && self.test_state.config.capture_snapshot_at_drop {
1944 self.to_test_snapshot_file();
1945 }
1946 }
1947}
1948
1949#[doc(hidden)]
1950#[cfg(any(test, feature = "testutils"))]
1951impl Env {
1952 /// Create a snapshot file for the currently executing test.
1953 ///
1954 /// Writes the file to the `test_snapshots/{test-name}.N.json` path where
1955 /// `N` is incremented for each unique `Env` in the test.
1956 ///
1957 /// Use to record the observable behavior of a test, and changes to that
1958 /// behavior over time. Commit the test snapshot file to version control and
1959 /// watch for changes in it on contract change, SDK upgrade, protocol
1960 /// upgrade, and other important events.
1961 ///
1962 /// No file will be created if the environment has no meaningful data such
1963 /// as stored entries or events.
1964 ///
1965 /// ### Panics
1966 ///
1967 /// If there is any error writing the file.
1968 pub(crate) fn to_test_snapshot_file(&self) {
1969 let snapshot = self.to_snapshot();
1970
1971 // Don't write a snapshot that has no data in it.
1972 if snapshot.ledger.entries().into_iter().count() == 0
1973 && snapshot.events.0.is_empty()
1974 && snapshot.auth.0.is_empty()
1975 {
1976 return;
1977 }
1978
1979 // Determine path to write test snapshots to.
1980 let Some(test_name) = &self.test_state.test_name else {
1981 // If there's no test name, we're not in a test context, so don't write snapshots.
1982 return;
1983 };
1984 let number = self.test_state.number;
1985 // Break up the test name into directories, using :: as the separator.
1986 // The :: module separator cannot be written into the filename because
1987 // some operating systems (e.g. Windows) do not allow the : character in
1988 // filenames.
1989 let test_name_path = test_name
1990 .split("::")
1991 .map(|p| std::path::Path::new(p).to_path_buf())
1992 .reduce(|p0, p1| p0.join(p1))
1993 .expect("test name to not be empty");
1994 let dir = std::path::Path::new("test_snapshots");
1995 let p = dir
1996 .join(&test_name_path)
1997 .with_extension(format!("{number}.json"));
1998
1999 // Write test snapshots to file.
2000 eprintln!("Writing test snapshot file for test {test_name:?} to {p:?}.");
2001 snapshot.write_file(p).unwrap();
2002 }
2003}
2004
2005#[doc(hidden)]
2006impl internal::EnvBase for Env {
2007 type Error = Infallible;
2008
2009 // This exists to allow code in conversion paths to upgrade an Error to an
2010 // Env::Error with some control granted to the underlying Env (and panic
2011 // paths kept out of the host). We delegate this to our env_impl and then,
2012 // since our own Error type is Infallible, immediately throw it into either
2013 // the env_impl's Error escalation path (if testing), or just plain panic.
2014 #[cfg(not(target_family = "wasm"))]
2015 fn error_from_error_val(&self, e: crate::Error) -> Self::Error {
2016 let host_err = self.env_impl.error_from_error_val(e);
2017 #[cfg(any(test, feature = "testutils"))]
2018 self.env_impl.escalate_error_to_panic(host_err);
2019 #[cfg(not(any(test, feature = "testutils")))]
2020 panic!("{:?}", host_err);
2021 }
2022
2023 // When targeting wasm we don't even need to do that, just delegate to
2024 // the Guest's impl, which calls core::arch::wasm32::unreachable.
2025 #[cfg(target_family = "wasm")]
2026 #[allow(unreachable_code)]
2027 fn error_from_error_val(&self, e: crate::Error) -> Self::Error {
2028 self.env_impl.error_from_error_val(e)
2029 }
2030
2031 fn check_protocol_version_lower_bound(&self, v: u32) -> Result<(), Self::Error> {
2032 Ok(self
2033 .env_impl
2034 .check_protocol_version_lower_bound(v)
2035 .unwrap_optimized())
2036 }
2037
2038 fn check_protocol_version_upper_bound(&self, v: u32) -> Result<(), Self::Error> {
2039 Ok(self
2040 .env_impl
2041 .check_protocol_version_upper_bound(v)
2042 .unwrap_optimized())
2043 }
2044
2045 // Note: the function `escalate_error_to_panic` only exists _on the `Env`
2046 // trait_ when the feature `soroban-env-common/testutils` is enabled. This
2047 // is because the host wants to never have this function even _compiled in_
2048 // when building for production, as it might be accidentally called (we have
2049 // mistakenly done so with conversion and comparison traits in the past).
2050 //
2051 // As a result, we only implement it here (fairly meaninglessly) when we're
2052 // in `cfg(test)` (which enables `soroban-env-host/testutils` thus
2053 // `soroban-env-common/testutils`) or when we've had our own `testutils`
2054 // feature enabled (which does the same).
2055 //
2056 // See the `internal::reject_err` functions above for more detail about what
2057 // it actually does (when implemented for real, on the host). In this
2058 // not-very-serious impl, since `Self::Error` is `Infallible`, this instance
2059 // can never actually be called and so its body is just a trivial
2060 // transformation from one empty type to another, for Type System Reasons.
2061 #[cfg(any(test, feature = "testutils"))]
2062 fn escalate_error_to_panic(&self, e: Self::Error) -> ! {
2063 match e {}
2064 }
2065
2066 fn bytes_copy_from_slice(
2067 &self,
2068 b: BytesObject,
2069 b_pos: U32Val,
2070 slice: &[u8],
2071 ) -> Result<BytesObject, Self::Error> {
2072 Ok(self
2073 .env_impl
2074 .bytes_copy_from_slice(b, b_pos, slice)
2075 .unwrap_optimized())
2076 }
2077
2078 fn bytes_copy_to_slice(
2079 &self,
2080 b: BytesObject,
2081 b_pos: U32Val,
2082 slice: &mut [u8],
2083 ) -> Result<(), Self::Error> {
2084 Ok(self
2085 .env_impl
2086 .bytes_copy_to_slice(b, b_pos, slice)
2087 .unwrap_optimized())
2088 }
2089
2090 fn bytes_new_from_slice(&self, slice: &[u8]) -> Result<BytesObject, Self::Error> {
2091 Ok(self.env_impl.bytes_new_from_slice(slice).unwrap_optimized())
2092 }
2093
2094 fn log_from_slice(&self, msg: &str, args: &[Val]) -> Result<Void, Self::Error> {
2095 Ok(self.env_impl.log_from_slice(msg, args).unwrap_optimized())
2096 }
2097
2098 fn string_copy_to_slice(
2099 &self,
2100 b: StringObject,
2101 b_pos: U32Val,
2102 slice: &mut [u8],
2103 ) -> Result<(), Self::Error> {
2104 Ok(self
2105 .env_impl
2106 .string_copy_to_slice(b, b_pos, slice)
2107 .unwrap_optimized())
2108 }
2109
2110 fn symbol_copy_to_slice(
2111 &self,
2112 b: SymbolObject,
2113 b_pos: U32Val,
2114 mem: &mut [u8],
2115 ) -> Result<(), Self::Error> {
2116 Ok(self
2117 .env_impl
2118 .symbol_copy_to_slice(b, b_pos, mem)
2119 .unwrap_optimized())
2120 }
2121
2122 fn string_new_from_slice(&self, slice: &[u8]) -> Result<StringObject, Self::Error> {
2123 Ok(self
2124 .env_impl
2125 .string_new_from_slice(slice)
2126 .unwrap_optimized())
2127 }
2128
2129 fn symbol_new_from_slice(&self, slice: &[u8]) -> Result<SymbolObject, Self::Error> {
2130 Ok(self
2131 .env_impl
2132 .symbol_new_from_slice(slice)
2133 .unwrap_optimized())
2134 }
2135
2136 fn map_new_from_slices(&self, keys: &[&str], vals: &[Val]) -> Result<MapObject, Self::Error> {
2137 Ok(self
2138 .env_impl
2139 .map_new_from_slices(keys, vals)
2140 .unwrap_optimized())
2141 }
2142
2143 fn map_unpack_to_slice(
2144 &self,
2145 map: MapObject,
2146 keys: &[&str],
2147 vals: &mut [Val],
2148 ) -> Result<Void, Self::Error> {
2149 Ok(self
2150 .env_impl
2151 .map_unpack_to_slice(map, keys, vals)
2152 .unwrap_optimized())
2153 }
2154
2155 fn vec_new_from_slice(&self, vals: &[Val]) -> Result<VecObject, Self::Error> {
2156 Ok(self.env_impl.vec_new_from_slice(vals).unwrap_optimized())
2157 }
2158
2159 fn vec_unpack_to_slice(&self, vec: VecObject, vals: &mut [Val]) -> Result<Void, Self::Error> {
2160 Ok(self
2161 .env_impl
2162 .vec_unpack_to_slice(vec, vals)
2163 .unwrap_optimized())
2164 }
2165
2166 fn symbol_index_in_strs(&self, key: Symbol, strs: &[&str]) -> Result<U32Val, Self::Error> {
2167 Ok(self
2168 .env_impl
2169 .symbol_index_in_strs(key, strs)
2170 .unwrap_optimized())
2171 }
2172}
2173
2174///////////////////////////////////////////////////////////////////////////////
2175/// X-macro use: impl Env for SDK's Env
2176///////////////////////////////////////////////////////////////////////////////
2177
2178// This is a helper macro used only by impl_env_for_sdk below. It consumes a
2179// token-tree of the form:
2180//
2181// {fn $fn_id:ident $args:tt -> $ret:ty}
2182//
2183// and produces the the corresponding method definition to be used in the
2184// SDK's Env implementation of the Env (calling through to the corresponding
2185// guest or host implementation).
2186macro_rules! sdk_function_helper {
2187 {$mod_id:ident, fn $fn_id:ident($($arg:ident:$type:ty),*) -> $ret:ty}
2188 =>
2189 {
2190 fn $fn_id(&self, $($arg:$type),*) -> Result<$ret, Self::Error> {
2191 internal::reject_err(&self.env_impl, self.env_impl.$fn_id($($arg),*))
2192 }
2193 };
2194}
2195
2196// This is a callback macro that pattern-matches the token-tree passed by the
2197// x-macro (call_macro_with_all_host_functions) and produces a suite of
2198// forwarding-method definitions, which it places in the body of the declaration
2199// of the implementation of Env for the SDK's Env.
2200macro_rules! impl_env_for_sdk {
2201 {
2202 $(
2203 // This outer pattern matches a single 'mod' block of the token-tree
2204 // passed from the x-macro to this macro. It is embedded in a `$()*`
2205 // pattern-repetition matcher so that it will match all provided
2206 // 'mod' blocks provided.
2207 $(#[$mod_attr:meta])*
2208 mod $mod_id:ident $mod_str:literal
2209 {
2210 $(
2211 // This inner pattern matches a single function description
2212 // inside a 'mod' block in the token-tree passed from the
2213 // x-macro to this macro. It is embedded in a `$()*`
2214 // pattern-repetition matcher so that it will match all such
2215 // descriptions.
2216 $(#[$fn_attr:meta])*
2217 { $fn_str:literal, $($min_proto:literal)?, $($max_proto:literal)?, fn $fn_id:ident $args:tt -> $ret:ty }
2218 )*
2219 }
2220 )*
2221 }
2222
2223 => // The part of the macro above this line is a matcher; below is its expansion.
2224
2225 {
2226 // This macro expands to a single item: the implementation of Env for
2227 // the SDK's Env struct used by client contract code running in a WASM VM.
2228 #[doc(hidden)]
2229 impl internal::Env for Env
2230 {
2231 $(
2232 $(
2233 // This invokes the guest_function_helper! macro above
2234 // passing only the relevant parts of the declaration
2235 // matched by the inner pattern above. It is embedded in two
2236 // nested `$()*` pattern-repetition expanders that
2237 // correspond to the pattern-repetition matchers in the
2238 // match section, but we ignore the structure of the 'mod'
2239 // block repetition-level from the outer pattern in the
2240 // expansion, flattening all functions from all 'mod' blocks
2241 // into the implementation of Env for Guest.
2242 sdk_function_helper!{$mod_id, fn $fn_id $args -> $ret}
2243 )*
2244 )*
2245 }
2246 };
2247}
2248
2249// Here we invoke the x-macro passing generate_env_trait as its callback macro.
2250internal::call_macro_with_all_host_functions! { impl_env_for_sdk }