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