radix_transactions/model/
test_transaction.rs

1use crate::internal_prelude::*;
2
3#[derive(ManifestSbor)]
4pub enum TestTransaction {
5    V1(TestIntentV1),
6    V2 {
7        root_intent: TestIntentV2,
8        subintents: Vec<TestIntentV2>,
9    },
10}
11
12pub struct TestTransactionV2Builder {
13    nonce: u32,
14    subintents: IndexMap<SubintentHash, TestIntentV2>,
15}
16
17impl TestTransactionV2Builder {
18    pub fn new(nonce: u32) -> Self {
19        Self {
20            nonce,
21            subintents: Default::default(),
22        }
23    }
24
25    /// Yields to each child exactly once with empty arguments.
26    pub fn add_simple_subintent(
27        &mut self,
28        children: impl IntoIterator<Item = SubintentHash>,
29        proofs: impl IntoIterator<Item = NonFungibleGlobalId>,
30    ) -> SubintentHash {
31        let mut manifest_builder = ManifestBuilder::new_subintent_v2();
32        for (child_index, child_hash) in children.into_iter().enumerate() {
33            let child_name = format!("child_{child_index}");
34            manifest_builder = manifest_builder.use_child(&child_name, child_hash);
35            manifest_builder = manifest_builder.yield_to_child(child_name, ());
36        }
37        let manifest = manifest_builder.yield_to_parent(()).build();
38        self.add_subintent(manifest, proofs)
39    }
40
41    pub fn add_tweaked_simple_subintent(
42        &mut self,
43        children: impl IntoIterator<Item = SubintentHash>,
44        proofs: impl IntoIterator<Item = NonFungibleGlobalId>,
45        addition: impl FnOnce(SubintentManifestV2Builder) -> SubintentManifestV2Builder,
46    ) -> SubintentHash {
47        let mut manifest_builder = ManifestBuilder::new_subintent_v2();
48        for (child_index, child_hash) in children.into_iter().enumerate() {
49            let child_name = format!("child_{child_index}");
50            manifest_builder = manifest_builder.use_child(&child_name, child_hash);
51            manifest_builder = manifest_builder.yield_to_child(child_name, ());
52        }
53        manifest_builder = addition(manifest_builder);
54        let manifest = manifest_builder.yield_to_parent(()).build();
55        self.add_subintent(manifest, proofs)
56    }
57
58    pub fn add_subintent(
59        &mut self,
60        manifest: SubintentManifestV2,
61        proofs: impl IntoIterator<Item = NonFungibleGlobalId>,
62    ) -> SubintentHash {
63        let (instructions, blobs, child_intents) = manifest.for_intent();
64        let intent = self.create_intent(instructions, blobs, child_intents, proofs);
65        let hash = intent.hash;
66        self.subintents.insert(SubintentHash(hash), intent);
67        SubintentHash(hash)
68    }
69
70    /// Uses the faucet and yields to each child exactly once with empty arguments.
71    pub fn finish_with_simple_root_intent(
72        self,
73        children: impl IntoIterator<Item = SubintentHash>,
74        proofs: impl IntoIterator<Item = NonFungibleGlobalId>,
75    ) -> TestTransaction {
76        let mut manifest_builder = ManifestBuilder::new_v2();
77        manifest_builder = manifest_builder.lock_fee_from_faucet();
78        for (child_index, child_hash) in children.into_iter().enumerate() {
79            let child_name = format!("child_{child_index}");
80            // In the manifest builder, we allow USE_CHILD later than in a written manifest
81            manifest_builder = manifest_builder.use_child(&child_name, child_hash);
82            manifest_builder = manifest_builder.yield_to_child(child_name, ());
83        }
84        let manifest = manifest_builder.build();
85        self.finish_with_root_intent(manifest, proofs)
86    }
87
88    pub fn finish_with_root_intent(
89        self,
90        manifest: TransactionManifestV2,
91        proofs: impl IntoIterator<Item = NonFungibleGlobalId>,
92    ) -> TestTransaction {
93        let (instructions, blobs, child_intents) = manifest.for_intent();
94        let root_intent = self.create_intent(instructions, blobs, child_intents, proofs);
95        TestTransaction::V2 {
96            root_intent,
97            subintents: self.subintents.into_values().collect(),
98        }
99    }
100
101    fn create_intent(
102        &self,
103        instructions: InstructionsV2,
104        blobs: BlobsV1,
105        child_intents: ChildSubintentSpecifiersV2,
106        proofs: impl IntoIterator<Item = NonFungibleGlobalId>,
107    ) -> TestIntentV2 {
108        let children_subintent_indices = child_intents
109            .children
110            .into_iter()
111            .map(|child| {
112                let subintent_index = self
113                    .subintents
114                    .get_index_of(&child.hash)
115                    .expect("Child subintents should exist already in the Test Transaction");
116                SubintentIndex(subintent_index)
117            })
118            .collect();
119        let nonce = self.nonce;
120        let subintent_count = self.subintents.len();
121        let hash = hash(format!(
122            "Test transaction intent: {nonce} - {subintent_count}"
123        ));
124        TestIntentV2 {
125            instructions,
126            blobs,
127            hash,
128            initial_proofs: proofs.into_iter().collect(),
129            children_subintent_indices,
130        }
131    }
132}
133
134#[derive(ManifestSbor)]
135pub struct TestIntentV1 {
136    pub instructions: InstructionsV1,
137    pub blobs: BlobsV1,
138    pub hash: Hash,
139    pub initial_proofs: BTreeSet<NonFungibleGlobalId>,
140}
141
142#[derive(ManifestSbor)]
143pub struct TestIntentV2 {
144    pub instructions: InstructionsV2,
145    pub blobs: BlobsV1,
146    pub hash: Hash,
147    pub initial_proofs: BTreeSet<NonFungibleGlobalId>,
148    pub children_subintent_indices: Vec<SubintentIndex>,
149}
150
151pub enum PreparedTestTransaction {
152    V1(PreparedTestIntent),
153    V2 {
154        root_intent: PreparedTestIntent,
155        subintents: Vec<PreparedTestIntent>,
156    },
157}
158
159pub struct PreparedTestIntent {
160    pub encoded_instructions: Vec<u8>,
161    pub references: IndexSet<Reference>,
162    pub blobs: IndexMap<Hash, Vec<u8>>,
163    pub hash: Hash,
164    pub children_subintent_indices: Vec<SubintentIndex>,
165    pub initial_proofs: BTreeSet<NonFungibleGlobalId>,
166}
167
168impl PreparedTestIntent {
169    #[allow(deprecated)]
170    pub fn from_v1(
171        intent: TestIntentV1,
172        settings: &PreparationSettings,
173    ) -> Result<Self, PrepareError> {
174        let prepared_instructions = intent.instructions.prepare_partial(settings)?;
175        Ok(PreparedTestIntent {
176            encoded_instructions: manifest_encode(&prepared_instructions.inner.0)?,
177            references: prepared_instructions.references,
178            blobs: intent.blobs.prepare_partial(settings)?.blobs_by_hash,
179            hash: intent.hash,
180            children_subintent_indices: vec![],
181            initial_proofs: intent.initial_proofs,
182        })
183    }
184
185    pub fn from_v2(
186        intent: TestIntentV2,
187        settings: &PreparationSettings,
188    ) -> Result<Self, PrepareError> {
189        let prepared_instructions = intent.instructions.prepare_partial(settings)?;
190        Ok(PreparedTestIntent {
191            encoded_instructions: manifest_encode(&prepared_instructions.inner.0)?,
192            references: prepared_instructions.references,
193            blobs: intent.blobs.prepare_partial(settings)?.blobs_by_hash,
194            hash: intent.hash,
195            children_subintent_indices: intent.children_subintent_indices,
196            initial_proofs: intent.initial_proofs,
197        })
198    }
199
200    pub fn into_executable_intent(self) -> ExecutableIntent {
201        let auth_zone_init = AuthZoneInit::proofs(self.initial_proofs);
202
203        ExecutableIntent {
204            encoded_instructions: self.encoded_instructions,
205            auth_zone_init,
206            references: self.references,
207            blobs: self.blobs,
208            children_subintent_indices: self.children_subintent_indices,
209        }
210    }
211}
212
213impl TestTransaction {
214    /// The nonce needs to be globally unique amongst test transactions on your ledger
215    pub fn new_v1_from_nonce(
216        manifest: TransactionManifestV1,
217        nonce: u32,
218        initial_proofs: BTreeSet<NonFungibleGlobalId>,
219    ) -> Self {
220        Self::new_v1(
221            manifest,
222            hash(format!("Test transaction: {}", nonce)),
223            initial_proofs,
224        )
225    }
226
227    pub fn new_from_any_manifest(
228        any_manifest: AnyManifest,
229        nonce: u32,
230        initial_proofs: BTreeSet<NonFungibleGlobalId>,
231    ) -> Result<Self, String> {
232        match any_manifest {
233            AnyManifest::V1(manifest) => {
234                Ok(Self::new_v1_from_nonce(manifest, nonce, initial_proofs))
235            }
236            AnyManifest::SystemV1(_) => {
237                Err("Cannot convert a system manifest to a test transaction".to_string())
238            }
239            AnyManifest::V2(manifest) => {
240                Ok(Self::new_v2_builder(nonce).finish_with_root_intent(manifest, initial_proofs))
241            }
242            AnyManifest::SubintentV2(_) => {
243                Err("Cannot convert a subintent manifest to a test transaction".to_string())
244            }
245        }
246    }
247
248    pub fn new_v1(
249        manifest: TransactionManifestV1,
250        hash: Hash,
251        initial_proofs: BTreeSet<NonFungibleGlobalId>,
252    ) -> Self {
253        let (instructions, blobs) = manifest.for_intent();
254        Self::V1(TestIntentV1 {
255            instructions,
256            blobs,
257            hash,
258            initial_proofs,
259        })
260    }
261
262    /// ## Example usage
263    /// ```ignore
264    /// # // Ignored as it depends on scrypto_test which isn't a dev dependency
265    /// let mut ledger = LedgerSimulatorBuilder::new().build();
266    /// let mut builder = TestTransaction::new_v2_builder(ledger.next_transaction_nonce());
267    ///
268    /// let child = builder.add_subintent(
269    ///     ManifestBuilder::new_subintent_v2()
270    ///         .yield_to_parent(())
271    ///         .build(),
272    ///     [child_public_key.signature_proof()],
273    /// );
274    ///
275    /// let transaction = builder.finish_with_root_intent(
276    ///     ManifestBuilder::new_v2()
277    ///         .use_child("child", child)
278    ///         .lock_standard_test_fee(account)
279    ///         .yield_to_child("child", ())
280    ///         .build(),
281    ///     [public_key.signature_proof()],
282    /// );
283    ///
284    /// let receipt = ledger.execute_test_transaction(transaction);
285    /// ```
286    pub fn new_v2_builder(nonce: u32) -> TestTransactionV2Builder {
287        TestTransactionV2Builder::new(nonce)
288    }
289
290    #[allow(deprecated)]
291    pub fn prepare(
292        self,
293        settings: &PreparationSettings,
294    ) -> Result<PreparedTestTransaction, PrepareError> {
295        match self {
296            Self::V1(intent) => Ok(PreparedTestTransaction::V1(PreparedTestIntent::from_v1(
297                intent, settings,
298            )?)),
299            Self::V2 {
300                root_intent,
301                subintents,
302            } => Ok(PreparedTestTransaction::V2 {
303                root_intent: PreparedTestIntent::from_v2(root_intent, settings)?,
304                subintents: subintents
305                    .into_iter()
306                    .map(|intent| PreparedTestIntent::from_v2(intent, settings))
307                    .collect::<Result<_, _>>()?,
308            }),
309        }
310    }
311}
312
313impl IntoExecutable for TestTransaction {
314    type Error = PrepareError;
315
316    fn into_executable(
317        self,
318        validator: &TransactionValidator,
319    ) -> Result<ExecutableTransaction, Self::Error> {
320        Ok(self
321            .prepare(validator.preparation_settings())?
322            .into_unvalidated_executable())
323    }
324}
325
326impl PreparedTestTransaction {
327    pub fn into_unvalidated_executable(self) -> ExecutableTransaction {
328        match self {
329            PreparedTestTransaction::V1(intent) => {
330                let num_of_signature_validations = intent.initial_proofs.len() + 1;
331                ExecutableTransaction::new_v1(
332                    intent.encoded_instructions.clone(),
333                    AuthZoneInit::proofs(intent.initial_proofs.clone()),
334                    intent.references.clone(),
335                    intent.blobs.clone(),
336                    ExecutionContext {
337                        unique_hash: intent.hash,
338                        intent_hash_nullifications: vec![],
339                        epoch_range: None,
340                        payload_size: intent.encoded_instructions.len()
341                            + intent.blobs.values().map(|x| x.len()).sum::<usize>(),
342                        // For testing purpose, assume `num_of_signature_validations = num_of_initial_proofs + 1`
343                        num_of_signature_validations,
344                        costing_parameters: TransactionCostingParameters {
345                            tip: TipSpecifier::None,
346                            free_credit_in_xrd: Decimal::ZERO,
347                        },
348                        pre_allocated_addresses: vec![],
349                        disable_limits_and_costing_modules: false,
350                        proposer_timestamp_range: None,
351                    },
352                )
353            }
354            PreparedTestTransaction::V2 {
355                root_intent,
356                subintents,
357            } => {
358                let all_intents = core::iter::once(&root_intent)
359                    .chain(subintents.iter())
360                    .collect::<Vec<_>>();
361                let payload_size = all_intents
362                    .iter()
363                    .map(|intent| {
364                        intent.encoded_instructions.len()
365                            + intent.blobs.values().map(|x| x.len()).sum::<usize>()
366                    })
367                    .sum();
368                let num_of_signature_validations = all_intents
369                    .iter()
370                    .map(|intent| intent.initial_proofs.len())
371                    .sum();
372
373                let context = ExecutionContext {
374                    unique_hash: root_intent.hash,
375                    intent_hash_nullifications: vec![],
376                    epoch_range: None,
377                    payload_size,
378                    // For testing purpose, assume `num_of_signature_validations = num_of_initial_proofs + 1`
379                    num_of_signature_validations,
380                    costing_parameters: TransactionCostingParameters {
381                        tip: TipSpecifier::None,
382                        free_credit_in_xrd: Decimal::ZERO,
383                    },
384                    pre_allocated_addresses: vec![],
385                    disable_limits_and_costing_modules: false,
386                    proposer_timestamp_range: None,
387                };
388
389                ExecutableTransaction::new_v2(
390                    root_intent.into_executable_intent(),
391                    subintents
392                        .into_iter()
393                        .map(|intent| intent.into_executable_intent())
394                        .collect(),
395                    context,
396                )
397            }
398        }
399    }
400}
401
402impl IntoExecutable for PreparedTestTransaction {
403    type Error = core::convert::Infallible;
404
405    fn into_executable(
406        self,
407        _validator: &TransactionValidator,
408    ) -> Result<ExecutableTransaction, Self::Error> {
409        Ok(self.into_unvalidated_executable())
410    }
411}