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