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