radix_engine/vm/
vm.rs

1use crate::blueprints::package::*;
2use crate::errors::{ApplicationError, RuntimeError};
3use crate::internal_prelude::*;
4use crate::kernel::kernel_api::{KernelNodeApi, KernelSubstateApi};
5use crate::system::system_callback::*;
6use crate::system::system_callback_api::SystemCallbackObject;
7use crate::system::system_substates::KeyValueEntrySubstate;
8use crate::vm::wasm::{ScryptoV1WasmValidator, WasmEngine};
9use crate::vm::{NativeVm, NativeVmExtension, ScryptoVm};
10use radix_engine_interface::api::field_api::LockFlags;
11use radix_engine_interface::api::SystemApi;
12
13use crate::vm::ScryptoVmVersion;
14
15use super::wasm::DefaultWasmEngine;
16use super::NoExtension;
17
18pub type VmBootSubstate = VmBoot;
19
20#[derive(Debug, Clone, PartialEq, Eq, Sbor, ScryptoSborAssertion)]
21#[sbor_assert(backwards_compatible(cuttlefish = "FILE:vm_boot_substate_cuttlefish_schema.bin",))]
22pub enum VmBoot {
23    V1 { scrypto_version: u64 },
24}
25
26impl VmBoot {
27    /// Loads vm boot from the database, or resolves a fallback.
28    pub fn load(substate_db: &impl SubstateDatabase) -> Self {
29        substate_db
30            .get_substate(
31                TRANSACTION_TRACKER,
32                BOOT_LOADER_PARTITION,
33                BootLoaderField::VmBoot,
34            )
35            .unwrap_or_else(Self::babylon_genesis)
36    }
37
38    pub fn latest() -> Self {
39        Self::bottlenose()
40    }
41
42    pub fn bottlenose() -> Self {
43        Self::V1 {
44            scrypto_version: ScryptoVmVersion::V1_1.into(),
45        }
46    }
47
48    pub fn babylon_genesis() -> Self {
49        Self::V1 {
50            scrypto_version: ScryptoVmVersion::V1_0.into(),
51        }
52    }
53}
54
55pub trait VmApi {
56    fn get_scrypto_version(&self) -> ScryptoVmVersion;
57}
58
59impl VmApi for VmBoot {
60    fn get_scrypto_version(&self) -> ScryptoVmVersion {
61        match self {
62            VmBoot::V1 { scrypto_version } => ScryptoVmVersion::try_from(*scrypto_version)
63                .unwrap_or_else(|_| panic!("Unexpected scrypto version: {}", scrypto_version)),
64        }
65    }
66}
67
68/// This trait is intended to encapsulate the data and types required to
69/// initalize the VMs in the engine.
70///
71/// The canonical implementation is [`VmModules`] - but we have this trait
72/// as well so that functions can take an `&impl VmInitialize` parameter,
73/// and we can avoid the proliferation of [`WasmEngine`] and
74/// [`NativeVmExtension`] generics across the codebase.
75pub trait VmInitialize {
76    type WasmEngine: WasmEngine;
77    type NativeVmExtension: NativeVmExtension;
78
79    fn get_vm_extension(&self) -> Self::NativeVmExtension;
80
81    fn get_scrypto_vm(&self) -> &ScryptoVm<Self::WasmEngine>;
82}
83
84pub struct VmModules<W: WasmEngine, E: NativeVmExtension> {
85    pub scrypto_vm: ScryptoVm<W>,
86    pub vm_extension: E,
87}
88
89impl<W: WasmEngine, E: NativeVmExtension> VmModules<W, E> {
90    pub fn new(scrypto_vm: ScryptoVm<W>, vm_extension: E) -> Self {
91        Self {
92            scrypto_vm,
93            vm_extension,
94        }
95    }
96}
97
98impl<E: NativeVmExtension> VmModules<DefaultWasmEngine, E> {
99    pub fn default_with_extension(vm_extension: E) -> Self {
100        Self {
101            scrypto_vm: Default::default(),
102            vm_extension,
103        }
104    }
105}
106
107pub type DefaultVmModules = VmModules<DefaultWasmEngine, NoExtension>;
108
109impl Default for DefaultVmModules {
110    fn default() -> Self {
111        Self {
112            scrypto_vm: ScryptoVm::default(),
113            vm_extension: NoExtension,
114        }
115    }
116}
117
118impl<W: WasmEngine, E: NativeVmExtension> VmInitialize for VmModules<W, E> {
119    type WasmEngine = W;
120    type NativeVmExtension = E;
121
122    fn get_vm_extension(&self) -> Self::NativeVmExtension {
123        self.vm_extension.clone()
124    }
125
126    fn get_scrypto_vm(&self) -> &ScryptoVm<Self::WasmEngine> {
127        &self.scrypto_vm
128    }
129}
130
131pub struct VmInit<'g, W: WasmEngine, E: NativeVmExtension> {
132    pub scrypto_vm: &'g ScryptoVm<W>,
133    pub native_vm_extension: E,
134    pub vm_boot: VmBoot,
135}
136
137impl<'g, W: WasmEngine, E: NativeVmExtension> InitializationParameters for VmInit<'g, W, E> {
138    type For = Vm<'g, W, E>;
139}
140
141impl<'g, W: WasmEngine, E: NativeVmExtension> VmInit<'g, W, E> {
142    pub fn load(
143        substate_db: &impl SubstateDatabase,
144        vm_modules: &'g impl VmInitialize<WasmEngine = W, NativeVmExtension = E>,
145    ) -> Self {
146        Self {
147            scrypto_vm: vm_modules.get_scrypto_vm(),
148            native_vm_extension: vm_modules.get_vm_extension(),
149            vm_boot: VmBoot::load(substate_db),
150        }
151    }
152}
153
154pub struct Vm<'g, W: WasmEngine, E: NativeVmExtension> {
155    pub scrypto_vm: &'g ScryptoVm<W>,
156    pub native_vm: NativeVm<E>,
157    pub vm_boot: VmBoot,
158}
159
160impl<'g, W: WasmEngine + 'g, E: NativeVmExtension> SystemCallbackObject for Vm<'g, W, E> {
161    type Init = VmInit<'g, W, E>;
162
163    fn init(vm_init: VmInit<'g, W, E>) -> Result<Self, BootloadingError> {
164        Ok(Self {
165            scrypto_vm: vm_init.scrypto_vm,
166            native_vm: NativeVm::new_with_extension(vm_init.native_vm_extension),
167            vm_boot: vm_init.vm_boot,
168        })
169    }
170
171    fn invoke<
172        Y: SystemApi<RuntimeError>
173            + SystemBasedKernelInternalApi<SystemCallback = Self>
174            + KernelNodeApi
175            + KernelSubstateApi<SystemLockData>,
176    >(
177        address: &PackageAddress,
178        export: PackageExport,
179        input: &IndexedScryptoValue,
180        api: &mut Y,
181    ) -> Result<IndexedScryptoValue, RuntimeError> {
182        let vm_type = {
183            let handle = api.kernel_open_substate_with_default(
184                address.as_node_id(),
185                MAIN_BASE_PARTITION
186                    .at_offset(PACKAGE_VM_TYPE_PARTITION_OFFSET)
187                    .unwrap(),
188                &SubstateKey::Map(scrypto_encode(&export.code_hash).unwrap()),
189                LockFlags::read_only(),
190                Some(|| {
191                    let kv_entry = KeyValueEntrySubstate::<()>::default();
192                    IndexedScryptoValue::from_typed(&kv_entry)
193                }),
194                SystemLockData::default(),
195            )?;
196            let vm_type = api.kernel_read_substate(handle)?;
197            let vm_type: PackageCodeVmTypeEntrySubstate = vm_type.as_typed().unwrap();
198            api.kernel_close_substate(handle)?;
199            vm_type
200                .into_value()
201                .unwrap_or_else(|| panic!("Vm type not found: {:?}", export))
202        };
203
204        let vm_api = api.kernel_get_system().callback.vm_boot.clone();
205
206        let output = match vm_type.fully_update_and_into_latest_version().vm_type {
207            VmType::Native => {
208                let original_code = {
209                    let handle = api.kernel_open_substate_with_default(
210                        address.as_node_id(),
211                        MAIN_BASE_PARTITION
212                            .at_offset(PACKAGE_ORIGINAL_CODE_PARTITION_OFFSET)
213                            .unwrap(),
214                        &SubstateKey::Map(scrypto_encode(&export.code_hash).unwrap()),
215                        LockFlags::read_only(),
216                        Some(|| {
217                            let kv_entry = KeyValueEntrySubstate::<()>::default();
218                            IndexedScryptoValue::from_typed(&kv_entry)
219                        }),
220                        SystemLockData::default(),
221                    )?;
222                    let original_code = api.kernel_read_substate(handle)?;
223                    let original_code: PackageCodeOriginalCodeEntrySubstate =
224                        original_code.as_typed().unwrap();
225                    api.kernel_close_substate(handle)?;
226                    original_code
227                        .into_value()
228                        .unwrap_or_else(|| panic!("Original code not found: {:?}", export))
229                };
230
231                let mut vm_instance = api.kernel_get_system().callback.native_vm.create_instance(
232                    address,
233                    &original_code.fully_update_and_into_latest_version().code,
234                )?;
235                let output =
236                    { vm_instance.invoke(export.export_name.as_str(), input, api, &vm_api)? };
237
238                output
239            }
240            VmType::ScryptoV1 => {
241                let instrumented_code = {
242                    let handle = api.kernel_open_substate_with_default(
243                        address.as_node_id(),
244                        MAIN_BASE_PARTITION
245                            .at_offset(PACKAGE_INSTRUMENTED_CODE_PARTITION_OFFSET)
246                            .unwrap(),
247                        &SubstateKey::Map(scrypto_encode(&export.code_hash).unwrap()),
248                        LockFlags::read_only(),
249                        Some(|| {
250                            let kv_entry = KeyValueEntrySubstate::<()>::default();
251                            IndexedScryptoValue::from_typed(&kv_entry)
252                        }),
253                        SystemLockData::default(),
254                    )?;
255                    let instrumented_code = api.kernel_read_substate(handle)?;
256                    let instrumented_code: PackageCodeInstrumentedCodeEntrySubstate =
257                        instrumented_code.as_typed().unwrap();
258                    api.kernel_close_substate(handle)?;
259                    instrumented_code
260                        .into_value()
261                        .unwrap_or_else(|| panic!("Instrumented code not found: {:?}", export))
262                        .fully_update_and_into_latest_version()
263                };
264
265                let mut scrypto_vm_instance = {
266                    api.kernel_get_system().callback.scrypto_vm.create_instance(
267                        address,
268                        export.code_hash,
269                        &instrumented_code.instrumented_code,
270                    )
271                };
272
273                api.consume_cost_units(ClientCostingEntry::PrepareWasmCode {
274                    size: instrumented_code.instrumented_code.len(),
275                })?;
276
277                let output = {
278                    scrypto_vm_instance.invoke(export.export_name.as_str(), input, api, &vm_api)?
279                };
280
281                output
282            }
283        };
284
285        Ok(output)
286    }
287}
288
289pub trait VmInvoke {
290    // TODO: Remove KernelNodeAPI + KernelSubstateAPI from api, unify with VmApi
291    fn invoke<
292        Y: SystemApi<RuntimeError>
293            + KernelNodeApi
294            + KernelSubstateApi<SystemLockData>
295            + SystemBasedKernelInternalApi,
296        V: VmApi,
297    >(
298        &mut self,
299        export_name: &str,
300        input: &IndexedScryptoValue,
301        api: &mut Y,
302        vm_api: &V,
303    ) -> Result<IndexedScryptoValue, RuntimeError>;
304}
305
306pub struct VmPackageValidation;
307
308impl VmPackageValidation {
309    pub fn validate<V: VmApi>(
310        definition: &PackageDefinition,
311        vm_type: VmType,
312        code: &[u8],
313        vm_api: &V,
314    ) -> Result<Option<Vec<u8>>, RuntimeError> {
315        match vm_type {
316            VmType::Native => Ok(None),
317            VmType::ScryptoV1 => {
318                let version = vm_api.get_scrypto_version();
319
320                // Validate WASM
321                let instrumented_code = ScryptoV1WasmValidator::new(version)
322                    .validate(code, definition.blueprints.values())
323                    .map_err(|e| {
324                        RuntimeError::ApplicationError(ApplicationError::PackageError(
325                            PackageError::InvalidWasm(e),
326                        ))
327                    })?
328                    .0;
329
330                for BlueprintDefinitionInit {
331                    is_transient,
332                    blueprint_type,
333                    feature_set,
334                    schema:
335                        BlueprintSchemaInit {
336                            generics,
337                            state:
338                                BlueprintStateSchemaInit {
339                                    collections,
340                                    fields,
341                                },
342                            functions,
343                            hooks,
344                            ..
345                        },
346                    ..
347                } in definition.blueprints.values()
348                {
349                    match blueprint_type {
350                        BlueprintType::Outer => {}
351                        BlueprintType::Inner { .. } => {
352                            return Err(RuntimeError::ApplicationError(
353                                ApplicationError::PackageError(PackageError::WasmUnsupported(
354                                    "Inner blueprints not supported".to_string(),
355                                )),
356                            ));
357                        }
358                    }
359
360                    if !feature_set.is_empty() {
361                        return Err(RuntimeError::ApplicationError(
362                            ApplicationError::PackageError(PackageError::WasmUnsupported(
363                                "Feature set not supported".to_string(),
364                            )),
365                        ));
366                    }
367
368                    if !collections.is_empty() {
369                        return Err(RuntimeError::ApplicationError(
370                            ApplicationError::PackageError(PackageError::WasmUnsupported(
371                                "Static collections not supported".to_string(),
372                            )),
373                        ));
374                    }
375
376                    if fields.len() > 1 {
377                        return Err(RuntimeError::ApplicationError(
378                            ApplicationError::PackageError(PackageError::WasmUnsupported(
379                                "More than 1 substate field not supported".to_string(),
380                            )),
381                        ));
382                    }
383
384                    for field in fields {
385                        match &field.condition {
386                            Condition::Always => {}
387                            _ => {
388                                return Err(RuntimeError::ApplicationError(
389                                    ApplicationError::PackageError(PackageError::WasmUnsupported(
390                                        "Conditional fields are not supported".to_string(),
391                                    )),
392                                ));
393                            }
394                        }
395
396                        match field.transience {
397                            FieldTransience::NotTransient => {}
398                            _ => {
399                                return Err(RuntimeError::ApplicationError(
400                                    ApplicationError::PackageError(PackageError::WasmUnsupported(
401                                        "Transient fields are not supported".to_string(),
402                                    )),
403                                ));
404                            }
405                        }
406                    }
407
408                    if !hooks.hooks.is_empty() {
409                        return Err(RuntimeError::ApplicationError(
410                            ApplicationError::PackageError(PackageError::WasmUnsupported(
411                                "Hooks not supported".to_string(),
412                            )),
413                        ));
414                    }
415
416                    for (_name, schema) in &functions.functions {
417                        if let Some(info) = &schema.receiver {
418                            if info.ref_types != RefTypes::NORMAL {
419                                return Err(RuntimeError::ApplicationError(
420                                    ApplicationError::PackageError(PackageError::WasmUnsupported(
421                                        "Irregular ref types not supported".to_string(),
422                                    )),
423                                ));
424                            }
425                        }
426                    }
427
428                    if !generics.is_empty() {
429                        return Err(RuntimeError::ApplicationError(
430                            ApplicationError::PackageError(PackageError::WasmUnsupported(
431                                "Generics not supported".to_string(),
432                            )),
433                        ));
434                    }
435
436                    if *is_transient {
437                        return Err(RuntimeError::ApplicationError(
438                            ApplicationError::PackageError(PackageError::WasmUnsupported(
439                                "Transient blueprints not supported".to_string(),
440                            )),
441                        ));
442                    }
443                }
444                Ok(Some(instrumented_code))
445            }
446        }
447    }
448}