scrypto_test/environment/
builder.rs

1use radix_common::prelude::*;
2use radix_engine::kernel::call_frame::*;
3use radix_engine::kernel::heap::*;
4use radix_engine::kernel::id_allocator::*;
5use radix_engine::kernel::kernel::*;
6use radix_engine::kernel::substate_io::*;
7use radix_engine::kernel::substate_locks::*;
8use radix_engine::system::actor::*;
9use radix_engine::system::system::*;
10use radix_engine::system::system_callback::*;
11use radix_engine::system::system_modules::auth::*;
12use radix_engine::system::system_modules::costing::*;
13use radix_engine::system::system_modules::execution_trace::ExecutionTraceModule;
14use radix_engine::system::system_modules::kernel_trace::KernelTraceModule;
15use radix_engine::system::system_modules::limits::LimitsModule;
16use radix_engine::system::system_modules::transaction_runtime::TransactionRuntimeModule;
17use radix_engine::system::system_modules::*;
18use radix_engine::track::*;
19use radix_engine::updates::*;
20use radix_engine::vm::wasm::*;
21use radix_engine::vm::*;
22use radix_engine_interface::blueprints::package::*;
23use radix_engine_interface::prelude::*;
24use radix_substate_store_impls::memory_db::*;
25use radix_substate_store_interface::interface::*;
26use radix_transactions::model::TransactionCostingParameters;
27
28use crate::sdk::PackageFactory;
29
30use super::*;
31
32pub type DbFlash = IndexMap<DbNodeKey, IndexMap<DbPartitionNum, IndexMap<DbSortKey, Vec<u8>>>>;
33
34pub struct TestEnvironmentBuilder<D>
35where
36    D: SubstateDatabase + CommittableSubstateDatabase + 'static,
37{
38    /// The database to use for the test environment.
39    database: D,
40
41    /// The database that substates are flashed to and then flashed to the actual database at build
42    /// time. This is to make sure that when we add methods for changing the database it doesn't
43    /// matter if flash is called before the set database method.
44    flash_database: FlashSubstateDatabase,
45
46    /// Additional references to add to the root [`CallFrame`] upon its creation.
47    added_global_references: IndexSet<GlobalAddress>,
48
49    /// The protocol updates the the user wishes to execute.
50    /// This defaults to all from genesis.
51    protocol_executor: ProtocolExecutor,
52}
53
54impl Default for TestEnvironmentBuilder<InMemorySubstateDatabase> {
55    fn default() -> Self {
56        Self::new()
57    }
58}
59
60impl TestEnvironmentBuilder<InMemorySubstateDatabase> {
61    pub fn new() -> Self {
62        TestEnvironmentBuilder {
63            database: InMemorySubstateDatabase::standard(),
64            flash_database: FlashSubstateDatabase::standard(),
65            added_global_references: Default::default(),
66            protocol_executor: ProtocolBuilder::for_simulator().from_bootstrap_to_latest(),
67        }
68    }
69}
70
71impl<D> TestEnvironmentBuilder<D>
72where
73    D: SubstateDatabase + CommittableSubstateDatabase + 'static,
74{
75    const DEFAULT_INTENT_HASH: Hash = Hash([0; 32]);
76
77    pub fn flash(mut self, data: DbFlash) -> Self {
78        // Flash the substates to the database.
79        let database_updates = DatabaseUpdates {
80            node_updates: data
81                .into_iter()
82                .map(|(db_node_key, partition_num_to_updates_mapping)| {
83                    (
84                        db_node_key,
85                        NodeDatabaseUpdates {
86                            partition_updates: partition_num_to_updates_mapping
87                                .into_iter()
88                                .map(|(partition_num, substates)| {
89                                    (
90                                        partition_num,
91                                        PartitionDatabaseUpdates::Delta {
92                                            substate_updates: substates
93                                                .into_iter()
94                                                .map(|(db_sort_key, value)| {
95                                                    (db_sort_key, DatabaseUpdate::Set(value))
96                                                })
97                                                .collect(),
98                                        },
99                                    )
100                                })
101                                .collect(),
102                        },
103                    )
104                })
105                .collect(),
106        };
107        self.flash_database.commit(&database_updates);
108
109        self
110            /* Global references found in the NodeKeys */
111            .add_global_references(
112                database_updates
113                    .node_ids()
114                    .filter_map(|item| GlobalAddress::try_from(item).ok()),
115            )
116            /* Global references found in the Substate Values */
117            .add_global_references(
118                database_updates
119                    .node_updates
120                    .values()
121                    .flat_map(|NodeDatabaseUpdates { partition_updates }| {
122                        partition_updates.values()
123                    })
124                    .flat_map(|item| -> Box<dyn Iterator<Item = &Vec<u8>>> {
125                        match item {
126                            PartitionDatabaseUpdates::Delta { substate_updates } => {
127                                Box::new(substate_updates.values().filter_map(|item| {
128                                    if let DatabaseUpdate::Set(value) = item {
129                                        Some(value)
130                                    } else {
131                                        None
132                                    }
133                                }))
134                            }
135                            PartitionDatabaseUpdates::Reset {
136                                new_substate_values,
137                            } => Box::new(new_substate_values.values()),
138                        }
139                    })
140                    .flat_map(|value| {
141                        IndexedScryptoValue::from_slice(value)
142                            .unwrap()
143                            .references()
144                            .clone()
145                    })
146                    .filter_map(|item| GlobalAddress::try_from(item).ok()),
147            )
148    }
149
150    pub fn add_global_reference(mut self, global_address: GlobalAddress) -> Self {
151        self.added_global_references.insert(global_address);
152        self
153    }
154
155    pub fn add_global_references(
156        mut self,
157        global_addresses: impl IntoIterator<Item = GlobalAddress>,
158    ) -> Self {
159        self.added_global_references.extend(global_addresses);
160        self
161    }
162
163    pub fn database<ND>(self, database: ND) -> TestEnvironmentBuilder<ND>
164    where
165        ND: SubstateDatabase + CommittableSubstateDatabase,
166    {
167        TestEnvironmentBuilder {
168            database,
169            added_global_references: self.added_global_references,
170            flash_database: self.flash_database,
171            protocol_executor: self.protocol_executor,
172        }
173    }
174
175    /// Can be used to configure whether to bootstrap and which protocol
176    /// updates to run.
177    pub fn with_protocol(
178        mut self,
179        executor: impl FnOnce(ProtocolBuilder) -> ProtocolExecutor,
180    ) -> Self {
181        self.protocol_executor = executor(ProtocolBuilder::for_simulator());
182        self
183    }
184
185    pub fn build(mut self) -> TestEnvironment<D> {
186        // Network definition used for the test environment is always simulator.
187        let network_definition = NetworkDefinition::simulator();
188
189        // Create the various VMs we will use
190        let native_vm = NativeVm::new();
191        let scrypto_vm = ScryptoVm::<DefaultWasmEngine>::default();
192
193        // Run bootstrap and any protocol updates against the database, if requested.
194        self.protocol_executor
195            .commit_each_protocol_update(&mut self.database);
196
197        // Reading the system boot substate from the substate store AFTER the protocol updates have
198        // been enacted. If none are set then we assume that we're on the Babylon settings.
199        let system_boot_substate = self
200            .database
201            .get_substate::<SystemBoot>(
202                TRANSACTION_TRACKER,
203                BOOT_LOADER_PARTITION,
204                BootLoaderField::SystemBoot,
205            )
206            .unwrap_or_else(|| {
207                SystemBoot::V1(SystemParameters::babylon_genesis(
208                    network_definition.clone(),
209                ))
210            });
211
212        // Create the Id allocator we will be using throughout this test
213        let id_allocator = IdAllocator::new(Self::DEFAULT_INTENT_HASH);
214
215        // If a flash is specified execute it.
216        let database_updates = self.flash_database.database_updates();
217        if !database_updates.node_updates.is_empty() {
218            self.database.commit(&database_updates);
219        }
220
221        // Getting the kernel boot to use for the kernel creation.
222        let kernel_boot = KernelBoot::load(&self.database);
223
224        let mut env = TestEnvironment(EncapsulatedRadixEngine::create(
225            self.database,
226            scrypto_vm,
227            native_vm.clone(),
228            id_allocator,
229            |substate_database| Track::new(substate_database),
230            |scrypto_vm, database| {
231                let system_version = system_boot_substate.system_version();
232                let system_parameters = system_boot_substate.into_parameters();
233
234                let vm_boot = VmBoot::load(database);
235
236                let transaction_runtime_module =
237                    TransactionRuntimeModule::new(network_definition, Self::DEFAULT_INTENT_HASH);
238
239                let auth_module = AuthModule::new();
240
241                let limits_module = LimitsModule::from_params(system_parameters.limit_parameters);
242
243                let costing_module = CostingModule {
244                    current_depth: 0,
245                    fee_reserve: SystemLoanFeeReserve::new(
246                        system_parameters.costing_parameters,
247                        TransactionCostingParameters::default(),
248                        false,
249                    ),
250                    fee_table: FeeTable::new(system_version),
251                    tx_payload_len: 0,
252                    tx_num_of_signature_validations: 0,
253                    config: system_parameters.costing_module_config,
254                    cost_breakdown: Some(Default::default()),
255                    detailed_cost_breakdown: Some(Default::default()),
256                    on_apply_cost: Default::default(),
257                };
258
259                System::new(
260                    system_version,
261                    Vm {
262                        scrypto_vm,
263                        native_vm: native_vm.clone(),
264                        vm_boot,
265                    },
266                    SystemModuleMixer::new(
267                        EnabledModules::LIMITS
268                            | EnabledModules::AUTH
269                            | EnabledModules::TRANSACTION_RUNTIME,
270                        KernelTraceModule,
271                        transaction_runtime_module,
272                        auth_module,
273                        limits_module,
274                        costing_module,
275                        ExecutionTraceModule::new(MAX_EXECUTION_TRACE_DEPTH),
276                    ),
277                    SystemFinalization::no_nullifications(),
278                )
279            },
280            |system_config, track, id_allocator| {
281                Kernel::kernel_create_kernel_for_testing(
282                    SubstateIO {
283                        heap: Heap::new(),
284                        store: track,
285                        non_global_node_refs: NonGlobalNodeRefs::new(),
286                        substate_locks: SubstateLocks::new(),
287                        heap_transient_substates: TransientSubstates {
288                            transient_substates: Default::default(),
289                        },
290                        pinned_to_heap: Default::default(),
291                    },
292                    id_allocator,
293                    system_config,
294                    kernel_boot.always_visible_global_nodes(),
295                )
296            },
297        ));
298
299        // Adding references to all of the well-known global nodes.
300        env.0.with_kernel_mut(|kernel| {
301            let (_, current_frame) = kernel.kernel_current_frame_mut();
302            for node_id in GLOBAL_VISIBLE_NODES {
303                let Ok(global_address) = GlobalAddress::try_from(node_id.0) else {
304                    continue;
305                };
306                current_frame.add_global_reference(global_address)
307            }
308            for global_address in self.added_global_references.iter() {
309                current_frame.add_global_reference(*global_address)
310            }
311        });
312
313        // Publishing the test-environment package.
314        let test_environment_package = {
315            let code = include_bytes!("../../assets/test_environment.wasm");
316            let package_definition = manifest_decode::<ManifestPackageDefinition>(include_bytes!(
317                "../../assets/test_environment.rpd"
318            ))
319            .expect("Must succeed")
320            .try_into_typed()
321            .unwrap();
322
323            env.with_auth_module_disabled(|env| {
324                PackageFactory::publish_advanced(
325                    OwnerRole::None,
326                    package_definition,
327                    code.to_vec(),
328                    Default::default(),
329                    None,
330                    env,
331                )
332                .expect("Must succeed")
333            })
334        };
335
336        // Creating the call-frame of the test environment & making it the current call frame
337        {
338            // Creating the auth zone of the next call-frame
339            let auth_zone = env.0.with_kernel_mut(|kernel| {
340                let mut system_service = SystemService::new(kernel);
341                AuthModule::on_call_fn_mock(
342                    &mut system_service,
343                    Some((TRANSACTION_PROCESSOR_PACKAGE.as_node_id(), false)),
344                    Default::default(),
345                    Default::default(),
346                )
347                .expect("Must succeed")
348            });
349
350            // Define the actor of the next call-frame. This would be a function actor of the test
351            // environment package.
352            let actor = Actor::Function(FunctionActor {
353                blueprint_id: BlueprintId {
354                    package_address: test_environment_package,
355                    blueprint_name: "TestEnvironment".to_owned(),
356                },
357                ident: "run".to_owned(),
358                auth_zone,
359            });
360
361            // Creating the message, call-frame, and doing the replacement.
362            let message = {
363                let mut message =
364                    CallFrameMessage::from_input(&IndexedScryptoValue::from_typed(&()), &actor);
365                for node_id in GLOBAL_VISIBLE_NODES {
366                    message.copy_global_references.push(node_id);
367                }
368                for global_address in self.added_global_references.iter() {
369                    message
370                        .copy_global_references
371                        .push(global_address.into_node_id())
372                }
373                message
374            };
375            env.0.with_kernel_mut(|kernel| {
376                let (substate_io, current_frame) = kernel.kernel_current_frame_mut();
377                let new_frame =
378                    CallFrame::new_child_from_parent(substate_io, current_frame, actor, message)
379                        .expect("Must succeed.");
380                let previous_frame = core::mem::replace(current_frame, new_frame);
381                kernel.kernel_prev_frame_stack_mut().push(previous_frame)
382            });
383        }
384
385        env
386    }
387}
388
389//=========================
390// Flash Substate Database
391//=========================
392
393#[derive(Debug, PartialEq, Eq, Clone)]
394pub struct FlashSubstateDatabase {
395    partitions: BTreeMap<DbPartitionKey, BTreeMap<DbSortKey, DbSubstateValue>>,
396}
397
398impl FlashSubstateDatabase {
399    pub fn standard() -> Self {
400        Self {
401            partitions: BTreeMap::new(),
402        }
403    }
404
405    pub fn database_updates(self) -> DatabaseUpdates {
406        let mut database_updates = DatabaseUpdates::default();
407
408        self.partitions.into_iter().for_each(
409            |(
410                DbPartitionKey {
411                    node_key,
412                    partition_num,
413                },
414                items,
415            )| {
416                database_updates
417                    .node_updates
418                    .entry(node_key)
419                    .or_default()
420                    .partition_updates
421                    .insert(
422                        partition_num,
423                        PartitionDatabaseUpdates::Delta {
424                            substate_updates: items
425                                .into_iter()
426                                .map(|(key, value)| (key, DatabaseUpdate::Set(value)))
427                                .collect(),
428                        },
429                    );
430            },
431        );
432
433        database_updates
434    }
435}
436
437impl SubstateDatabase for FlashSubstateDatabase {
438    fn get_raw_substate_by_db_key(
439        &self,
440        partition_key: &DbPartitionKey,
441        sort_key: &DbSortKey,
442    ) -> Option<DbSubstateValue> {
443        self.partitions
444            .get(partition_key)
445            .and_then(|partition| partition.get(sort_key))
446            .cloned()
447    }
448
449    fn list_raw_values_from_db_key(
450        &self,
451        partition_key: &DbPartitionKey,
452        from_sort_key: Option<&DbSortKey>,
453    ) -> Box<dyn Iterator<Item = PartitionEntry> + '_> {
454        let from_sort_key = from_sort_key.cloned();
455        let iter = self
456            .partitions
457            .get(partition_key)
458            .into_iter()
459            .flat_map(|partition| partition.iter())
460            .skip_while(move |(key, _substate)| Some(*key) < from_sort_key.as_ref())
461            .map(|(key, substate)| (key.clone(), substate.clone()));
462
463        Box::new(iter)
464    }
465}
466
467impl CommittableSubstateDatabase for FlashSubstateDatabase {
468    fn commit(&mut self, database_updates: &DatabaseUpdates) {
469        for (node_key, node_updates) in &database_updates.node_updates {
470            for (partition_num, partition_updates) in &node_updates.partition_updates {
471                let partition_key = DbPartitionKey {
472                    node_key: node_key.clone(),
473                    partition_num: *partition_num,
474                };
475                let partition = self.partitions.entry(partition_key.clone()).or_default();
476                match partition_updates {
477                    PartitionDatabaseUpdates::Delta { substate_updates } => {
478                        for (sort_key, update) in substate_updates {
479                            match update {
480                                DatabaseUpdate::Set(substate_value) => {
481                                    partition.insert(sort_key.clone(), substate_value.clone())
482                                }
483                                DatabaseUpdate::Delete => partition.remove(sort_key),
484                            };
485                        }
486                    }
487                    PartitionDatabaseUpdates::Reset {
488                        new_substate_values,
489                    } => {
490                        *partition = BTreeMap::from_iter(
491                            new_substate_values
492                                .iter()
493                                .map(|(sort_key, value)| (sort_key.clone(), value.clone())),
494                        )
495                    }
496                }
497                if partition.is_empty() {
498                    self.partitions.remove(&partition_key);
499                }
500            }
501        }
502    }
503}