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