scrypto_test/environment/
builder.rs1use 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 database: D,
40
41 flash_database: FlashSubstateDatabase,
45
46 added_global_references: IndexSet<GlobalAddress>,
48
49 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 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 .add_global_references(
112 database_updates
113 .node_ids()
114 .filter_map(|item| GlobalAddress::try_from(item).ok()),
115 )
116 .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 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 let network_definition = NetworkDefinition::simulator();
188
189 let native_vm = NativeVm::new();
191 let scrypto_vm = ScryptoVm::<DefaultWasmEngine>::default();
192
193 self.protocol_executor
195 .commit_each_protocol_update(&mut self.database);
196
197 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 let id_allocator = IdAllocator::new(Self::DEFAULT_INTENT_HASH);
214
215 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 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 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 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 {
338 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 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 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#[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}