scrypto_test/
lib.rs

1//! This crate is an implementation of for Scrypto-Test, a unit testing framework for Scrypto that
2//! follows an invocation-based approach instead of a transaction-based approach to testing,
3//! allowing Scrypto developers to write tests that look and feel like Scrypto. Scrypto-Test is not
4//! a replacement for transaction-based tests offered by the LedgerSimulator, it would just be an
5//! addition and another way for Scrypto developers to test their code where Scrypto-Test could be
6//! classified as a unit-testing framework while the LedgerSimulator could be classified as an
7//! integration-testing framework.
8//!
9//! # Why
10//!
11//! We already have a way to test Scrypto blueprints in the from of the scrypto_test::prelude::LedgerSimulator
12//! which is essentially an in-memory ledger that we can run transactions against, get back
13//! transaction receipts, and determine-based on the TransactionReceipt-if the behavior of the
14//! blueprint or component is as we expect or not. This approach is tried and tested and has been
15//! proven to work as evident by the hundreds and thousands of tests in the radix_engine_tests crate
16//! that make it clear that the transaction-based approach works. However, it has a number of
17//! issues, especially when we think about the target audience of our testing framework: DeFi
18//! developers.
19//!
20//! In the current (transaction-based) model, there is a lot of boilerplate code involved to write
21//! what should be a straightforward test. As an example, to test that a contribution of X and Y
22//! resources to Radiswap results in Z pool units minted the test author needs to:
23//!
24//! * Create these two resources to use for testing.
25//! * Decide on whether these resources should just be mintable on demand or if the supply of these
26//!   resources should be stored in some account that will be used in the test to withdraw from.
27//! * If an account will hold those resources, then the author needs to create that account.
28//! * Split out the instructions into multiple manifests as needed such as in cases where one
29//!   instruction depends on the output of a previous instruction.
30//! * Ensure that the execution of the previous transactions did indeed succeed and extract out the
31//!   information required from receipts either through the worktop changes, balance changes, or by
32//!   other means.
33//! * Manage and ensure that the worktop by the end of the transaction is empty of all resources and
34//!   that the accounts that the resources will be deposited into sign the transaction.
35//!
36//! Most if not all of the items listed above are not core to what the developer wishes to test,
37//! recall that they wished to test whether a contribution of X and Y resources returns Z pool
38//! units. However, they spent a majority of their time thinking about completely different
39//! problems. Thus, there is not only a large amount of boilerplate code, but there is also large
40//! mental overhead for a test should be conceptually easy and simple to write. As you can see from
41//! the description above, most of the time that is spent writing tests is **not spent writing
42//! tests**, but spend initializing and creating the environment and managing side effects just to
43//! then write a simple test in the form of a method call to some node id.
44//!
45//! # Scrypto-Test Model
46//!
47//! This model differs from the transaction based model of writing tests in that we do not have
48//! transaction instructions, processor, and worktop at all. In fact, **nothing** related to
49//! transactions exists in this model. Instead, there exists a [`TestEnvironment`] struct that each
50//! test can instantiate instances of. Each [`TestEnvironment`] instance has a substate store,
51//! track, and kernel. On top of that, [`TestEnvironment`] implements the [`SystemApi`] trait. The
52//! [`TestEnvironment`] can be looked at as self-contained Radix Engine that’s exposed through the
53//! [`SystemApi`] and that has some other high-level helper methods as it contains all of the layers
54//! of the engine. This means that:
55//!
56//! * A [`TestEnvironment`] instance is a self-contained instance of the Radix Engine and Kernel
57//!   which are exposed through the [`SystemApi`].
58//! * Since [`TestEnvironment`] implements the [`SystemApi`] it can be used as a substitute to
59//!   ScryptoEnv from Scrypto and the SystemService from native. This means that the simple
60//!   interface seen in the radix_native_sdk crate can be used within tests.
61//! * If each test has it’s own [`TestEnvironment`] instance (they instantiate that themselves if
62//!   they need it), then tests have no shared dependencies and are isolated.
63//! * The biggest struggle with the transaction-based model was around dealing with transient nodes.
64//!   More specifically, if we wanted to make sure that bucket X returned from some invocation
65//!   contained Y resources, how would we do that? Unfortunately, there was no easy way to do it. In
66//!   this model, if we make an invocation and get a Bucket back, there is no worktop for the bucket
67//!   to go into, we have a proper Bucket object that we can call amount() on and assert against.
68//!   Thus, this approach makes it easier to have assertions around transient nodes.
69//!
70//! Once the [`TestEnvironment`] has been instantiated we would get a Kernel with two Call Frames:
71//! 1. **The Root Call Frame:** We have a root Call Frame to be consistent with how other parts of
72//!    the stack use the kernel where there is always a root Call Frame. After the instantiation is
73//!    complete, the root callframe is pushed onto the stack of previous Call Frames the kernel has.
74//! 2. **The Test Call Frame:** This is the Call Frame that is used for all of the invocation that
75//!    will be made throughout the test. This Call Frame functions exactly like any other Call
76//!    Frame, it can own nodes, get messages from other Call Frames, and so on. As an example, say
77//!    we invoke a method on some node that returns a Bucket, this Bucket is now owned and visible
78//!    to this Call Frame. We are now able to call methods such as resource_address() and amount()
79//!    on this Bucket since it’s a node we own and the [`TestEnvironment`] has a Heap.
80//!
81//! [`SystemApi`]: crate::prelude::SystemApi
82//! [`TestEnvironment`]: crate::prelude::TestEnvironment
83
84#![allow(
85    // Allowed since many of the result types we use are quite large and fixing them one by one is 
86    // not something we can easily do.
87    // TODO: Remove this in the future.
88    clippy::result_large_err
89)]
90
91pub mod environment;
92pub mod ledger_simulator;
93pub mod prelude;
94pub mod sdk;
95pub mod utils;
96
97#[macro_export]
98macro_rules! this_package {
99    () => {
100        env!("CARGO_MANIFEST_DIR")
101    };
102}
103
104/// Includes the WASM file of a Scrypto package.
105///
106/// Notes:
107/// * This macro will NOT compile the package;
108/// * The binary name is normally the package name with `-` replaced with `_`.
109///
110/// # Example
111/// ```ignore
112/// # // Ignoring because of include_code!
113/// use scrypto::prelude::*;
114///
115/// // This package
116/// let wasm1 = include_code!("bin_name");
117///
118/// // Another package
119/// let wasm2 = include_code!("/path/to/package", "bin_name");
120/// ```
121#[macro_export]
122macro_rules! include_code {
123    ($bin_name: expr) => {
124        include_bytes!(concat!(
125            env!("CARGO_MANIFEST_DIR"),
126            "/target/wasm32-unknown-unknown/release/",
127            $bin_name,
128            ".wasm"
129        ))
130    };
131    ($package_dir: expr, $bin_name: expr) => {
132        include_bytes!(concat!(
133            $package_dir,
134            "/target/wasm32-unknown-unknown/release/",
135            $bin_name,
136            ".wasm"
137        ))
138    };
139}
140
141/// Includes the schema file of a Scrypto package.
142///
143/// Notes:
144/// * This macro will NOT compile the package;
145/// * The binary name is normally the package name with `-` replaced with `_`.
146///
147/// # Example
148/// ```ignore
149/// # // Including because of include_schema!(..)
150/// use scrypto::prelude::*;
151///
152/// // This package
153/// let schema1 = include_schema!("bin_name");
154///
155/// // Another package
156/// let schema2 = include_schema!("/path/to/package", "bin_name");
157/// ```
158#[macro_export]
159macro_rules! include_schema {
160    ($bin_name: expr) => {
161        include_bytes!(concat!(
162            env!("CARGO_MANIFEST_DIR"),
163            "/target/wasm32-unknown-unknown/release/",
164            $bin_name,
165            ".rpd"
166        ))
167    };
168    ($package_dir: expr, $bin_name: expr) => {
169        include_bytes!(concat!(
170            $package_dir,
171            "/target/wasm32-unknown-unknown/release/",
172            $bin_name,
173            ".rpd"
174        ))
175    };
176}