diff --git a/Cargo.lock b/Cargo.lock index 6330ec167c..3531e3ee17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7166,9 +7166,11 @@ dependencies = [ name = "tari_dan_common_types" version = "0.1.0" dependencies = [ + "borsh", "prost", "prost-types", "tari_common", + "tari_common_types", ] [[package]] diff --git a/dan_layer/common_types/Cargo.toml b/dan_layer/common_types/Cargo.toml index bf822c0abc..fc12b0ae16 100644 --- a/dan_layer/common_types/Cargo.toml +++ b/dan_layer/common_types/Cargo.toml @@ -7,9 +7,12 @@ license = "BSD-3-Clause" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +tari_common = { path = "../../common", features = ["build"] } +tari_common_types = { path = "../../base_layer/common_types" } + +borsh = "0.9.3" prost = "0.9" prost-types = "0.9" -tari_common = { path = "../../common", features = ["build"] } [build-dependencies] tari_common = { path = "../../common", features = ["build"] } diff --git a/dan_layer/common_types/src/hash.rs b/dan_layer/common_types/src/hash.rs new file mode 100644 index 0000000000..e6d7897832 --- /dev/null +++ b/dan_layer/common_types/src/hash.rs @@ -0,0 +1,70 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{io, io::Write}; + +use borsh::{BorshDeserialize, BorshSerialize}; +use tari_common_types::types::FixedHash; + +// This is to avoid adding borsh as a dependency in common types (and therefore every application). +// TODO: Either this becomes the standard Hash type for the dan layer, or add borsh support to FixedHash. +#[derive(Debug, Clone, PartialEq, Default)] +pub struct Hash(FixedHash); + +impl Hash { + pub fn into_inner(self) -> FixedHash { + self.0 + } +} + +impl From for Hash { + fn from(hash: FixedHash) -> Self { + Self(hash) + } +} + +impl BorshSerialize for Hash { + fn serialize(&self, writer: &mut W) -> io::Result<()> { + (*self.0).serialize(writer) + } +} + +impl BorshDeserialize for Hash { + fn deserialize(buf: &mut &[u8]) -> io::Result { + let hash = <[u8; 32] as BorshDeserialize>::deserialize(buf)?; + Ok(Hash(hash.into())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serialize_deserialize() { + let hash = Hash::default(); + let mut buf = Vec::new(); + hash.serialize(&mut buf).unwrap(); + let hash2 = Hash::deserialize(&mut &buf[..]).unwrap(); + assert_eq!(hash, hash2); + } +} diff --git a/dan_layer/common_types/src/lib.rs b/dan_layer/common_types/src/lib.rs index 6a2bf5022a..f738209b6a 100644 --- a/dan_layer/common_types/src/lib.rs +++ b/dan_layer/common_types/src/lib.rs @@ -4,8 +4,10 @@ pub mod proto; pub mod storage; +mod hash; mod template_id; +pub use hash::Hash; use tari_common::hashing_domain::HashingDomain; pub use template_id::TemplateId; diff --git a/dan_layer/engine/Cargo.toml b/dan_layer/engine/Cargo.toml index a9db59e36a..fe5d89b82d 100644 --- a/dan_layer/engine/Cargo.toml +++ b/dan_layer/engine/Cargo.toml @@ -24,4 +24,4 @@ rand = "0.8.1" serde = "1.0.126" serde_json = "1.0.81" thiserror = "^1.0.20" -wasmer = "2.2.1" +wasmer = "2.3.0" diff --git a/dan_layer/engine/src/crypto.rs b/dan_layer/engine/src/crypto.rs index 9413c91fbd..37f855003f 100644 --- a/dan_layer/engine/src/crypto.rs +++ b/dan_layer/engine/src/crypto.rs @@ -20,30 +20,19 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use digest::Digest; use rand::rngs::OsRng; use tari_common_types::types::{PrivateKey, PublicKey}; -use tari_crypto::{ - hash::blake2::Blake256, - hashing::{DomainSeparatedHasher, DomainSeparation}, - keys::PublicKey as PublicKeyT, -}; +use tari_crypto::{hash::blake2::Blake256, hash_domain, hashing::DomainSeparatedHasher, keys::PublicKey as PublicKeyT}; -pub fn create_key_pair() -> (PrivateKey, PublicKey) { - PublicKey::random_keypair(&mut OsRng) -} +hash_domain!(TariEngineHashDomain, "tari.dan.engine", 0); -pub struct TariEngineDomainSeparation; +pub type TariEngineHasher = DomainSeparatedHasher; -impl DomainSeparation for TariEngineDomainSeparation { - fn version() -> u8 { - 0 - } - - fn domain() -> &'static str { - "tari.dan.engine" - } +pub fn hasher(label: &'static str) -> impl Digest { + TariEngineHasher::new(label) } -pub fn domain_separated_hasher(label: &'static str) -> DomainSeparatedHasher { - DomainSeparatedHasher::new(label) +pub fn create_key_pair() -> (PrivateKey, PublicKey) { + PublicKey::random_keypair(&mut OsRng) } diff --git a/dan_layer/engine/src/instruction/error.rs b/dan_layer/engine/src/instruction/error.rs index 6f1cf4eed4..35926900a7 100644 --- a/dan_layer/engine/src/instruction/error.rs +++ b/dan_layer/engine/src/instruction/error.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::{package::PackageId, wasm::WasmExecutionError}; +use crate::{packager::PackageId, wasm::WasmExecutionError}; #[derive(Debug, thiserror::Error)] pub enum InstructionError { diff --git a/dan_layer/engine/src/instruction/mod.rs b/dan_layer/engine/src/instruction/mod.rs index 0d76d569f6..286e4b5b40 100644 --- a/dan_layer/engine/src/instruction/mod.rs +++ b/dan_layer/engine/src/instruction/mod.rs @@ -30,7 +30,7 @@ pub use processor::InstructionProcessor; mod signature; -use crate::{instruction::signature::InstructionSignature, package::PackageId}; +use crate::{instruction::signature::InstructionSignature, packager::PackageId}; #[derive(Debug, Clone)] pub enum Instruction { @@ -40,6 +40,12 @@ pub enum Instruction { function: String, args: Vec>, }, + CallMethod { + package_id: PackageId, + component_id: String, + method: String, + args: Vec>, + }, } #[derive(Debug, Clone)] diff --git a/dan_layer/engine/src/instruction/processor.rs b/dan_layer/engine/src/instruction/processor.rs index e2a71db139..0aa9f529b8 100644 --- a/dan_layer/engine/src/instruction/processor.rs +++ b/dan_layer/engine/src/instruction/processor.rs @@ -20,24 +20,29 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; use crate::{ instruction::{error::InstructionError, Instruction, InstructionSet}, - package::{Package, PackageId}, + packager::{Package, PackageId}, + runtime::{Runtime, RuntimeInterface}, traits::Invokable, - wasm::{ExecutionResult, Process, VmInstance}, + wasm::{ExecutionResult, Process}, }; #[derive(Debug, Clone, Default)] -pub struct InstructionProcessor { +pub struct InstructionProcessor { packages: HashMap, + runtime_interface: TRuntimeInterface, } -impl InstructionProcessor { - pub fn new() -> Self { +impl InstructionProcessor +where TRuntimeInterface: RuntimeInterface + Clone + 'static +{ + pub fn new(runtime_interface: TRuntimeInterface) -> Self { Self { packages: HashMap::new(), + runtime_interface, } } @@ -47,7 +52,10 @@ impl InstructionProcessor { } pub fn execute(&self, instruction_set: InstructionSet) -> Result, InstructionError> { - let mut results = vec![]; + let mut results = Vec::with_capacity(instruction_set.instructions.len()); + + // TODO: implement engine + let state = Runtime::new(Arc::new(self.runtime_interface.clone())); for instruction in instruction_set.instructions { match instruction { Instruction::CallFunction { @@ -65,11 +73,31 @@ impl InstructionProcessor { .ok_or(InstructionError::TemplateNameNotFound { name: template })?; // TODO: implement intelligent instance caching - let vm = VmInstance::instantiate(module.wasm_module())?; - let process = Process::new(module.clone(), vm); + let process = Process::start(module.clone(), state.clone())?; let result = process.invoke_by_name(&function, args)?; results.push(result); }, + Instruction::CallMethod { + package_id, + component_id, + method, + args, + } => { + let package = self + .packages + .get(&package_id) + .ok_or(InstructionError::PackageNotFound { package_id })?; + // TODO: load component, not module - component_id is currently hard-coded as the template name in + // tests + let module = package + .get_module_by_name(&component_id) + .ok_or(InstructionError::TemplateNameNotFound { name: component_id })?; + + // TODO: implement intelligent instance caching + let process = Process::start(module.clone(), state.clone())?; + let result = process.invoke_by_name(&method, args)?; + results.push(result); + }, } } diff --git a/dan_layer/engine/src/lib.rs b/dan_layer/engine/src/lib.rs index 1d4ec36f67..c2d7b34f9d 100644 --- a/dan_layer/engine/src/lib.rs +++ b/dan_layer/engine/src/lib.rs @@ -10,11 +10,10 @@ pub mod models; pub mod state; pub mod wasm; -pub mod compile; pub mod crypto; -pub mod env; pub mod instruction; -pub mod package; +pub mod packager; +pub mod runtime; pub mod traits; /// The DAN layer engine domain separated hashing domain diff --git a/dan_layer/engine/src/models/component.rs b/dan_layer/engine/src/models/component.rs new file mode 100644 index 0000000000..f99e4fe432 --- /dev/null +++ b/dan_layer/engine/src/models/component.rs @@ -0,0 +1,46 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::collections::HashMap; + +use tari_dan_common_types::Hash; +use tari_template_abi::CreateComponentArg; + +pub type ComponentId = (Hash, u32); + +pub struct Component { + pub name: String, + pub quantity: u64, + pub metadata: HashMap, Vec>, + pub state: Vec, +} + +impl From for Component { + fn from(arg: CreateComponentArg) -> Self { + Self { + name: arg.name, + quantity: arg.quantity, + metadata: arg.metadata, + state: arg.state, + } + } +} diff --git a/dan_layer/engine/src/models/mod.rs b/dan_layer/engine/src/models/mod.rs index 020c508d6d..bfc989b36d 100644 --- a/dan_layer/engine/src/models/mod.rs +++ b/dan_layer/engine/src/models/mod.rs @@ -2,5 +2,10 @@ // SPDX-License-Identifier: BSD-3-Clause mod bucket; - pub use bucket::Bucket; + +mod component; +pub use component::{Component, ComponentId}; + +mod vault; +pub use vault::{Vault, VaultId}; diff --git a/dan_layer/engine/src/env.rs b/dan_layer/engine/src/models/vault.rs old mode 100644 new mode 100755 similarity index 77% rename from dan_layer/engine/src/env.rs rename to dan_layer/engine/src/models/vault.rs index 09d6be4509..dfa72ee415 --- a/dan_layer/engine/src/env.rs +++ b/dan_layer/engine/src/models/vault.rs @@ -19,23 +19,16 @@ // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use borsh::{BorshDeserialize, BorshSerialize}; +use tari_common_types::types::FixedHash; -use std::sync::{Arc, Mutex}; +pub type VaultId = FixedHash; -pub fn tari_engine(env: &EngineEnvironment, op: i32, _args_ptr: i32, args_len: i32) -> i32 { - println!("tari_engine CALLED: op: {}, args: {}", op, args_len); - // TODO: - env.inc_counter(); - 0 -} - -#[derive(wasmer::WasmerEnv, Clone, Default)] -pub struct EngineEnvironment { - counter: Arc>, -} +#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +pub struct Vault {} -impl EngineEnvironment { - pub fn inc_counter(&self) { - *self.counter.lock().unwrap() += 1; +impl Vault { + pub fn empty() -> Self { + Self {} } } diff --git a/dan_layer/engine/src/package.rs b/dan_layer/engine/src/package.rs deleted file mode 100644 index f6227088fa..0000000000 --- a/dan_layer/engine/src/package.rs +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright 2022. The Tari Project -// -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -use std::{cell::Cell, collections::HashMap, convert::TryInto}; - -use rand::{rngs::OsRng, RngCore}; -use tari_common_types::types::FixedHash; -use tari_template_abi::TemplateDef; -use wasmer::{imports, Extern, Function, Instance, Memory, Module, Store, Val}; - -use crate::{crypto, wasm::LoadedWasmModule}; - -#[derive(Debug, Clone, Default)] -pub struct PackageBuilder { - wasm_code: Vec>, -} - -impl PackageBuilder { - pub fn new() -> Self { - Self { wasm_code: Vec::new() } - } - - pub fn add_wasm_template(&mut self, wasm_code: Vec) -> &mut Self { - self.wasm_code.push(wasm_code); - self - } - - pub fn build(&self) -> Result { - let mut wasm_modules = HashMap::with_capacity(self.wasm_code.len()); - let store = Store::default(); - let id = new_package_id(); - for code in &self.wasm_code { - let module = load_wasm_module(&store, code)?; - wasm_modules.insert(module.template_name().to_string(), module); - } - - Ok(Package { - id, - wasm_modules, - _store: store, - }) - } -} - -fn new_package_id() -> PackageId { - let v = OsRng.next_u32(); - crypto::domain_separated_hasher("package") - // TODO: Proper package id - .chain(&v.to_le_bytes()) - .finalize() - .as_ref() - .try_into() - .unwrap() -} - -fn load_wasm_module(store: &Store, code: &[u8]) -> Result { - let module = Module::new(store, code)?; - - fn stub(_op: i32, _args_ptr: i32, _args_len: i32) -> i32 { - 0 - } - - let imports = imports! { - "env" => { - "tari_engine" => Function::new_native(store, stub), - } - }; - let instance = Instance::new(&module, &imports)?; - validate_instance(&instance)?; - - let template = initialize_and_load_template_abi(&instance)?; - Ok(LoadedWasmModule::new(template, module)) -} - -fn initialize_and_load_template_abi(instance: &Instance) -> Result { - let abi_func = instance - .exports - .iter() - .find_map(|(name, export)| match export { - Extern::Function(f) if name.ends_with("_abi") => Some(f), - _ => None, - }) - .ok_or(PackageError::NoAbiDefinition)?; - - // Initialize ABI memory - let ret = abi_func.call(&[])?; - let ptr = match ret.get(0) { - Some(Val::I32(ptr)) => *ptr as u32, - Some(_) | None => return Err(PackageError::InvalidReturnTypeFromAbiFunc), - }; - - // Load ABI from memory - let memory = instance.exports.get_memory("memory")?; - let data = copy_abi_data_from_memory_checked(memory, ptr)?; - let decoded = tari_template_abi::decode(&data).map_err(|_| PackageError::AbiDecodeError)?; - Ok(decoded) -} - -fn copy_abi_data_from_memory_checked(memory: &Memory, ptr: u32) -> Result, PackageError> { - // Check memory bounds - if memory.data_size() < u64::from(ptr) { - return Err(PackageError::AbiPointerOutOfBounds); - } - - let view = memory.uint8view().subarray(ptr, memory.data_size() as u32 - 1); - let data = &*view; - if data.len() < 4 { - return Err(PackageError::MemoryUnderflow { - required: 4, - remaining: data.len(), - }); - } - - fn copy_from_cell_slice(src: &[Cell], dest: &mut [u8], len: usize) { - for i in 0..len { - dest[i] = src[i].get(); - } - } - - let mut buf = [0u8; 4]; - copy_from_cell_slice(data, &mut buf, 4); - let len = u32::from_le_bytes(buf) as usize; - const MAX_ABI_DATA_LEN: usize = 1024 * 1024; - if len > MAX_ABI_DATA_LEN { - return Err(PackageError::AbiDataTooLarge { - max: MAX_ABI_DATA_LEN, - size: len, - }); - } - if data.len() < 4 + len { - return Err(PackageError::MemoryUnderflow { - required: 4 + len, - remaining: data.len(), - }); - } - - let mut data = vec![0u8; len]; - let src = view.subarray(4, 4 + len as u32); - copy_from_cell_slice(&*src, &mut data, len); - Ok(data) -} - -pub fn validate_instance(instance: &Instance) -> Result<(), PackageError> { - if let Ok(mem) = instance.exports.get_memory("memory") { - if mem.size().bytes().0 > 2 * 1024 * 1024 { - return Err(PackageError::MaxMemorySizeExceeded); - } - } - // TODO other package validations - - Ok(()) -} - -pub type PackageId = FixedHash; - -#[derive(Debug, Clone)] -pub struct Package { - id: PackageId, - wasm_modules: HashMap, - _store: Store, -} - -impl Package { - pub fn get_module_by_name(&self, name: &str) -> Option<&LoadedWasmModule> { - self.wasm_modules.get(name) - } - - pub fn id(&self) -> PackageId { - self.id - } -} - -#[derive(Debug, thiserror::Error)] -pub enum PackageError { - #[error(transparent)] - CompileError(#[from] wasmer::CompileError), - #[error(transparent)] - InstantiationError(#[from] wasmer::InstantiationError), - #[error(transparent)] - RuntimeError(#[from] wasmer::RuntimeError), - #[error(transparent)] - ExportError(#[from] wasmer::ExportError), - #[error("Failed to decode ABI")] - AbiDecodeError, - #[error("maximum module memory size exceeded")] - MaxMemorySizeExceeded, - #[error("package did not contain an ABI definition")] - NoAbiDefinition, - #[error("package ABI function returned an invalid type")] - InvalidReturnTypeFromAbiFunc, - #[error("package ABI function returned an out of bounds pointer")] - AbiPointerOutOfBounds, - #[error("memory underflow: {required} bytes required but {remaining} remaining")] - MemoryUnderflow { required: usize, remaining: usize }, - #[error("ABI data is too large: a maximum of {max} bytes allowed but size is {size}")] - AbiDataTooLarge { max: usize, size: usize }, -} diff --git a/dan_layer/engine/src/packager/error.rs b/dan_layer/engine/src/packager/error.rs new file mode 100644 index 0000000000..bac307265c --- /dev/null +++ b/dan_layer/engine/src/packager/error.rs @@ -0,0 +1,29 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::wasm::WasmExecutionError; + +#[derive(Debug, thiserror::Error)] +pub enum PackageError { + #[error(transparent)] + WasmModuleError(#[from] WasmExecutionError), +} diff --git a/dan_layer/engine/src/packager/mod.rs b/dan_layer/engine/src/packager/mod.rs new file mode 100644 index 0000000000..edc894fbf3 --- /dev/null +++ b/dan_layer/engine/src/packager/mod.rs @@ -0,0 +1,30 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod error; +pub use error::PackageError; + +mod package; +pub use package::{Package, PackageBuilder, PackageId}; + +mod module_loader; +pub use module_loader::PackageModuleLoader; diff --git a/dan_layer/engine/src/packager/module_loader.rs b/dan_layer/engine/src/packager/module_loader.rs new file mode 100644 index 0000000000..871d9558b6 --- /dev/null +++ b/dan_layer/engine/src/packager/module_loader.rs @@ -0,0 +1,30 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::packager::PackageError; + +pub trait PackageModuleLoader { + type Loaded; + type Error: Into; + + fn load_module(&self) -> Result; +} diff --git a/dan_layer/engine/src/packager/package.rs b/dan_layer/engine/src/packager/package.rs new file mode 100644 index 0000000000..f78128e0c0 --- /dev/null +++ b/dan_layer/engine/src/packager/package.rs @@ -0,0 +1,92 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::collections::HashMap; + +use digest::Digest; +use rand::{rngs::OsRng, RngCore}; +use tari_common_types::types::FixedHash; + +use crate::{ + crypto, + packager::{error::PackageError, PackageModuleLoader}, + wasm::{LoadedWasmModule, WasmModule}, +}; + +pub type PackageId = FixedHash; + +#[derive(Debug, Clone)] +pub struct Package { + id: PackageId, + wasm_modules: HashMap, +} + +impl Package { + pub fn builder() -> PackageBuilder { + PackageBuilder::new() + } + + pub fn get_module_by_name(&self, name: &str) -> Option<&LoadedWasmModule> { + self.wasm_modules.get(name) + } + + pub fn id(&self) -> PackageId { + self.id + } +} + +#[derive(Debug, Clone, Default)] +pub struct PackageBuilder { + wasm_modules: Vec, +} + +impl PackageBuilder { + pub fn new() -> Self { + Self { + wasm_modules: Vec::new(), + } + } + + pub fn add_wasm_module(&mut self, wasm_module: WasmModule) -> &mut Self { + self.wasm_modules.push(wasm_module); + self + } + + pub fn build(&self) -> Result { + let mut wasm_modules = HashMap::with_capacity(self.wasm_modules.len()); + let id = new_package_id(); + for wasm in &self.wasm_modules { + let loaded = wasm.load_module()?; + wasm_modules.insert(loaded.template_name().to_string(), loaded); + } + + Ok(Package { id, wasm_modules }) + } +} + +fn new_package_id() -> PackageId { + let v = OsRng.next_u32(); + crypto::hasher("package") + // TODO: Proper package id + .chain(&v.to_le_bytes()) + .finalize().into() +} diff --git a/dan_layer/engine/src/runtime.rs b/dan_layer/engine/src/runtime.rs new file mode 100644 index 0000000000..80fa392cf8 --- /dev/null +++ b/dan_layer/engine/src/runtime.rs @@ -0,0 +1,76 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ + collections::HashMap, + fmt::Debug, + sync::{Arc, RwLock}, +}; + +use tari_common_types::types::FixedHash; +use tari_template_abi::LogLevel; + +use crate::models::{Bucket, Component, ComponentId}; + +#[derive(Clone)] +pub struct Runtime { + tracker: Arc>, + interface: Arc, +} + +impl Runtime { + pub fn new(engine: Arc) -> Self { + Self { + tracker: Arc::new(RwLock::new(ChangeTracker::default())), + interface: engine, + } + } + + pub fn interface(&self) -> &dyn RuntimeInterface { + &*self.interface + } +} + +impl Debug for Runtime { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Runtime") + .field("tracker", &self.tracker) + .field("engine", &"dyn RuntimeEngine") + .finish() + } +} + +#[derive(Debug, Clone, Default)] +pub struct ChangeTracker { + pub buckets: HashMap, +} + +#[derive(Debug, thiserror::Error)] +pub enum RuntimeError { + #[error("todo")] + Todo, +} + +pub trait RuntimeInterface: Send + Sync { + fn emit_log(&self, level: LogLevel, message: &str); + fn create_component(&self, component: Component) -> Result; +} diff --git a/dan_layer/engine/src/compile.rs b/dan_layer/engine/src/wasm/compile.rs similarity index 93% rename from dan_layer/engine/src/compile.rs rename to dan_layer/engine/src/wasm/compile.rs index a22dc6f3d6..61b50e3fcc 100644 --- a/dan_layer/engine/src/compile.rs +++ b/dan_layer/engine/src/wasm/compile.rs @@ -24,7 +24,9 @@ use std::{fs, io, io::ErrorKind, path::Path, process::Command}; use cargo_toml::{Manifest, Product}; -pub fn compile_template>(package_dir: P) -> io::Result> { +use super::module::WasmModule; + +pub fn build_wasm_module_from_source>(package_dir: P) -> io::Result { let status = Command::new("cargo") .current_dir(package_dir.as_ref()) .args(["build", "--target", "wasm32-unknown-unknown", "--release"]) @@ -65,5 +67,6 @@ pub fn compile_template>(package_dir: P) -> io::Result> { path.set_extension("wasm"); // return - fs::read(path) + let code = fs::read(path)?; + Ok(WasmModule::from_code(code)) } diff --git a/dan_layer/engine/src/wasm/environment.rs b/dan_layer/engine/src/wasm/environment.rs new file mode 100644 index 0000000000..91d5454030 --- /dev/null +++ b/dan_layer/engine/src/wasm/environment.rs @@ -0,0 +1,234 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ + cell::Cell, + fmt::{Debug, Formatter}, +}; + +use wasmer::{ + imports, + Function, + HostEnvInitError, + Instance, + LazyInit, + Memory, + NativeFunc, + Pages, + Resolver, + Store, + WasmerEnv, +}; + +use crate::wasm::WasmExecutionError; + +#[derive(Clone)] +pub struct WasmEnv { + memory: LazyInit, + mem_alloc: LazyInit>, + mem_free: LazyInit>, + state: T, +} + +impl WasmEnv { + pub fn new(state: T) -> Self { + Self { + state, + memory: LazyInit::new(), + mem_alloc: LazyInit::new(), + mem_free: LazyInit::new(), + } + } + + pub(super) fn alloc(&self, len: u32) -> Result { + let ptr = self.get_mem_alloc_func()?.call(len as i32)?; + if ptr == 0 { + return Err(WasmExecutionError::MemoryAllocationFailed); + } + + Ok(AllocPtr(ptr as u32, len)) + } + + pub(super) fn free(&self, ptr: AllocPtr) -> Result<(), WasmExecutionError> { + self.get_mem_free_func()?.call(ptr.as_i32())?; + Ok(()) + } + + pub(super) fn write_to_memory(&self, ptr: &AllocPtr, data: &[u8]) -> Result<(), WasmExecutionError> { + if data.len() != ptr.len() as usize { + return Err(WasmExecutionError::InvalidWriteLength { + allocated: ptr.len(), + requested: data.len() as u32, + }); + } + // SAFETY: The pointer has been allocated by alloc above and the runtime is single-threaded so data + // races are not possible. + unsafe { + self.get_memory()? + .uint8view() + .subarray(ptr.get(), ptr.end()) + .copy_from(data); + } + Ok(()) + } + + pub(super) fn read_memory_with_embedded_len(&self, ptr: u32) -> Result, WasmExecutionError> { + let memory = self.get_memory()?; + let view = memory.uint8view().subarray(ptr, memory.data_size() as u32 - 1); + let view_bytes = &*view; + if view_bytes.len() < 4 { + return Err(WasmExecutionError::MemoryUnderflow { + required: 4, + remaining: view_bytes.len(), + }); + } + + let mut buf = [0u8; 4]; + copy_from_cell_slice(view_bytes, &mut buf); + let len = u32::from_le_bytes(buf); + let data = self.read_from_memory(ptr + 4, len)?; + + Ok(data) + } + + pub(super) fn read_from_memory(&self, ptr: u32, len: u32) -> Result, WasmExecutionError> { + let memory = self.get_memory()?; + let size = memory.data_size(); + if u64::from(ptr) >= size || u64::from(ptr + len) >= memory.data_size() { + return Err(WasmExecutionError::MemoryPointerOutOfRange { + size: memory.data_size(), + pointer: u64::from(ptr), + len: u64::from(len), + }); + } + let view = memory.uint8view().subarray(ptr, ptr + len); + let mut data = vec![0u8; len as usize]; + copy_from_cell_slice(&*view, &mut data); + Ok(data) + } + + pub fn state(&self) -> &T { + &self.state + } + + fn get_mem_alloc_func(&self) -> Result<&NativeFunc, WasmExecutionError> { + self.mem_alloc + .get_ref() + .ok_or_else(|| WasmExecutionError::MissingAbiFunction { + function: "tari_alloc".into(), + }) + } + + fn get_mem_free_func(&self) -> Result<&NativeFunc, WasmExecutionError> { + self.mem_free + .get_ref() + .ok_or_else(|| WasmExecutionError::MissingAbiFunction { + function: "tari_free".into(), + }) + } + + fn get_memory(&self) -> Result<&Memory, WasmExecutionError> { + self.memory.get_ref().ok_or(WasmExecutionError::MemoryNotInitialized) + } + + pub fn mem_size(&self) -> Pages { + self.memory.get_ref().map(|mem| mem.size()).unwrap_or(Pages(0)) + } + + pub fn create_resolver(&self, store: &Store, tari_engine: Function) -> impl Resolver { + imports! { + "env" => { + "tari_engine" => tari_engine, + "debug" => Function::new_native_with_env(store, self.clone(), Self::debug_handler), + } + } + } + + fn debug_handler(env: &Self, arg_ptr: i32, arg_len: i32) { + const WASM_DEBUG_LOG_TARGET: &str = "tari::dan::wasm"; + match env.read_from_memory(arg_ptr as u32, arg_len as u32) { + Ok(arg) => { + eprintln!("DEBUG: {}", String::from_utf8_lossy(&arg)); + }, + Err(err) => { + log::error!(target: WASM_DEBUG_LOG_TARGET, "Failed to read from memory: {}", err); + }, + } + } +} + +impl WasmerEnv for WasmEnv { + fn init_with_instance(&mut self, instance: &Instance) -> Result<(), HostEnvInitError> { + self.memory + .initialize(instance.exports.get_with_generics_weak("memory")?); + self.mem_alloc + .initialize(instance.exports.get_with_generics_weak("tari_alloc")?); + self.mem_free + .initialize(instance.exports.get_with_generics_weak("tari_free")?); + Ok(()) + } +} + +impl Debug for WasmEnv { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WasmEnv") + .field("memory", &"LazyInit") + .field("tari_alloc", &" LazyInit") + .field("tari_free", &"LazyInit>") + .field("State", &self.state) + .finish() + } +} + +/// Efficiently copy read-only memory into a mutable buffer. +/// Panics if the length of `dest` is more than the length of `src`. +fn copy_from_cell_slice(src: &[Cell], dest: &mut [u8]) { + assert!(dest.len() <= src.len()); + let len = dest.len(); + // SAFETY: size_of::() is equal to size_of::(), we assert this below just in case. + let (head, body, tail) = unsafe { src[..len].align_to() }; + assert_eq!(head.len(), 0); + assert_eq!(tail.len(), 0); + dest.copy_from_slice(body); +} + +#[derive(Debug)] +pub struct AllocPtr(u32, u32); + +impl AllocPtr { + pub fn get(&self) -> u32 { + self.0 + } + + pub fn len(&self) -> u32 { + self.1 + } + + pub fn end(&self) -> u32 { + self.get() + self.len() + } + + pub fn as_i32(&self) -> i32 { + // We want the 'u32 as i32' conversion to wrap + self.get() as i32 + } +} diff --git a/dan_layer/engine/src/wasm/error.rs b/dan_layer/engine/src/wasm/error.rs index 9e2ba062bc..b632fd41bc 100644 --- a/dan_layer/engine/src/wasm/error.rs +++ b/dan_layer/engine/src/wasm/error.rs @@ -1,8 +1,12 @@ // Copyright 2022 The Tari Project // SPDX-License-Identifier: BSD-3-Clause +use std::io; + use thiserror::Error; -use wasmer::{ExportError, InstantiationError, RuntimeError}; +use wasmer::{ExportError, HostEnvInitError, InstantiationError}; + +use crate::runtime::RuntimeError; #[derive(Debug, Error)] pub enum WasmError { @@ -12,6 +16,16 @@ pub enum WasmError { #[derive(Debug, thiserror::Error)] pub enum WasmExecutionError { + #[error(transparent)] + InstantiationError(#[from] InstantiationError), + #[error(transparent)] + ExportError(#[from] ExportError), + #[error(transparent)] + WasmRuntimeError(#[from] wasmer::RuntimeError), + #[error(transparent)] + HostEnvInitError(#[from] HostEnvInitError), + #[error(transparent)] + CompileError(#[from] wasmer::CompileError), #[error("Function {name} not found")] FunctionNotFound { name: String }, #[error("Expected function {function} to return a pointer")] @@ -20,10 +34,28 @@ pub enum WasmExecutionError { InvalidWriteLength { allocated: u32, requested: u32 }, #[error("memory underflow: {required} bytes required but {remaining} remaining")] MemoryUnderflow { required: usize, remaining: usize }, - #[error(transparent)] - InstantiationError(#[from] InstantiationError), - #[error(transparent)] - ExportError(#[from] ExportError), - #[error(transparent)] + #[error("memory pointer out of range: memory size of {size} but pointer is {pointer}")] + MemoryPointerOutOfRange { size: u64, pointer: u64, len: u64 }, + #[error("Memory allocation failed")] + MemoryAllocationFailed, + #[error("Memory not initialized")] + MemoryNotInitialized, + #[error("Invalid operation {op}")] + InvalidOperation { op: i32 }, + #[error("Missing function {function}")] + MissingAbiFunction { function: String }, + #[error("Runtime error: {0}")] RuntimeError(#[from] RuntimeError), + #[error("Failed to decode argument for engine call: {0}")] + EngineArgDecodeFailed(io::Error), + #[error("maximum module memory size exceeded")] + MaxMemorySizeExceeded, + #[error("Failed to decode ABI")] + AbiDecodeError, + #[error("package ABI function returned an invalid type")] + InvalidReturnTypeFromAbiFunc, + #[error("package did not contain an ABI definition")] + NoAbiDefinition, + #[error("Unexpected ABI function {name}")] + UnexpectedAbiFunction { name: String }, } diff --git a/dan_layer/engine/src/wasm/mod.rs b/dan_layer/engine/src/wasm/mod.rs index 0612136de0..39f0d76272 100644 --- a/dan_layer/engine/src/wasm/mod.rs +++ b/dan_layer/engine/src/wasm/mod.rs @@ -1,19 +1,21 @@ // Copyright 2022 The Tari Project // SPDX-License-Identifier: BSD-3-Clause -mod error; mod wasm_module_definition; mod wasm_module_factory; -pub use error::{WasmError, WasmExecutionError}; pub use wasm_module_definition::WasmModuleDefinition; pub use wasm_module_factory::WasmModuleFactory; +pub mod compile; + +mod error; +pub use error::{WasmError, WasmExecutionError}; + +mod environment; + mod module; -pub use module::LoadedWasmModule; +pub use module::{LoadedWasmModule, WasmModule}; mod process; pub use process::{ExecutionResult, Process}; - -mod vm; -pub use vm::VmInstance; diff --git a/dan_layer/engine/src/wasm/module.rs b/dan_layer/engine/src/wasm/module.rs index 44ec5deee1..904e148797 100644 --- a/dan_layer/engine/src/wasm/module.rs +++ b/dan_layer/engine/src/wasm/module.rs @@ -21,6 +21,75 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use tari_template_abi::{FunctionDef, TemplateDef}; +use wasmer::{Extern, Function, Instance, Module, Store, Val, WasmerEnv}; + +use crate::{ + packager::PackageModuleLoader, + wasm::{environment::WasmEnv, WasmExecutionError}, +}; + +#[derive(Debug, Clone)] +pub struct WasmModule { + code: Vec, +} + +impl WasmModule { + pub fn from_code(code: Vec) -> Self { + Self { code } + } + + pub fn code(&self) -> &[u8] { + &self.code + } +} + +impl PackageModuleLoader for WasmModule { + type Error = WasmExecutionError; + type Loaded = LoadedWasmModule; + + fn load_module(&self) -> Result { + let store = Store::default(); + let module = Module::new(&store, &self.code)?; + let mut env = WasmEnv::new(()); + + fn stub(_env: &WasmEnv<()>, _op: i32, _arg_ptr: i32, _arg_len: i32) -> i32 { + panic!("WASM module called engine while loading ABI") + } + + let stub = Function::new_native_with_env(&store, env.clone(), stub); + let imports = env.create_resolver(&store, stub); + let instance = Instance::new(&module, &imports)?; + env.init_with_instance(&instance)?; + validate_instance(&instance)?; + validate_environment(&env)?; + + let template = initialize_and_load_template_abi(&instance, &env)?; + Ok(LoadedWasmModule::new(template, module)) + } +} + +fn initialize_and_load_template_abi(instance: &Instance, env: &WasmEnv<()>) -> Result { + let abi_func = instance + .exports + .iter() + .find_map(|(name, export)| match export { + Extern::Function(f) if name.ends_with("_abi") => Some(f), + _ => None, + }) + .ok_or(WasmExecutionError::NoAbiDefinition)?; + + // Initialize ABI memory + let ret = abi_func.call(&[])?; + let ptr = match ret.get(0) { + Some(Val::I32(ptr)) => *ptr as u32, + Some(_) | None => return Err(WasmExecutionError::InvalidReturnTypeFromAbiFunc), + }; + + // Load ABI from memory + let data = env.read_memory_with_embedded_len(ptr)?; + let decoded = tari_template_abi::decode(&data).map_err(|_| WasmExecutionError::AbiDecodeError)?; + Ok(decoded) +} #[derive(Debug, Clone)] pub struct LoadedWasmModule { @@ -45,3 +114,32 @@ impl LoadedWasmModule { self.template.functions.iter().find(|f| f.name == *function_name) } } + +fn validate_environment(env: &WasmEnv<()>) -> Result<(), WasmExecutionError> { + const MAX_MEM_SIZE: usize = 2 * 1024 * 1024; + let mem_size = env.mem_size(); + if mem_size.bytes().0 > MAX_MEM_SIZE { + return Err(WasmExecutionError::MaxMemorySizeExceeded); + } + // TODO other package validations + + Ok(()) +} + +fn validate_instance(instance: &Instance) -> Result<(), WasmExecutionError> { + // Enforce that only permitted functions are allowed + let unexpected_abi_func = instance + .exports + .iter() + .functions() + .find(|(name, _)| !is_func_permitted(name)); + if let Some((name, _)) = unexpected_abi_func { + return Err(WasmExecutionError::UnexpectedAbiFunction { name: name.to_string() }); + } + + Ok(()) +} + +fn is_func_permitted(name: &str) -> bool { + name.ends_with("_abi") || name.ends_with("_main") || name == "tari_alloc" || name == "tari_free" +} diff --git a/dan_layer/engine/src/wasm/process.rs b/dan_layer/engine/src/wasm/process.rs index 1fc2524053..f9f901ffd4 100644 --- a/dan_layer/engine/src/wasm/process.rs +++ b/dan_layer/engine/src/wasm/process.rs @@ -23,45 +23,44 @@ use std::io; use borsh::{BorshDeserialize, BorshSerialize}; -use tari_template_abi::{encode_into, CallInfo}; -use wasmer::{Module, Val}; +use tari_template_abi::{decode, encode_into, encode_with_len, ops, CallInfo, CreateComponentArg, EmitLogArg, Type}; +use wasmer::{Function, Instance, Module, Store, Val, WasmerEnv}; use crate::{ + runtime::Runtime, traits::Invokable, wasm::{ + environment::{AllocPtr, WasmEnv}, error::WasmExecutionError, - vm::{AllocPtr, VmInstance}, LoadedWasmModule, }, }; +const LOG_TARGET: &str = "tari::dan::wasm::process"; + #[derive(Debug)] pub struct Process { module: LoadedWasmModule, - vm: VmInstance, -} - -pub struct ExecutionResult { - pub value: wasmer::Value, - pub raw: Vec, -} - -impl ExecutionResult { - pub fn decode(&self) -> io::Result { - tari_template_abi::decode(&self.raw) - } + env: WasmEnv, + instance: Instance, } impl Process { - pub fn new(module: LoadedWasmModule, vm: VmInstance) -> Self { - Self { module, vm } + pub fn start(module: LoadedWasmModule, state: Runtime) -> Result { + let store = Store::default(); + let mut env = WasmEnv::new(state); + let tari_engine = Function::new_native_with_env(&store, env.clone(), Self::tari_engine_entrypoint); + let resolver = env.create_resolver(&store, tari_engine); + let instance = Instance::new(module.wasm_module(), &resolver)?; + env.init_with_instance(&instance)?; + Ok(Self { module, env, instance }) } fn alloc_and_write(&self, val: &T) -> Result { let mut buf = Vec::with_capacity(512); encode_into(val, &mut buf).unwrap(); - let ptr = self.vm.alloc(buf.len() as u32)?; - self.vm.write_to_memory(&ptr, &buf)?; + let ptr = self.env.alloc(buf.len() as u32)?; + self.env.write_to_memory(&ptr, &buf)?; Ok(ptr) } @@ -69,6 +68,50 @@ impl Process { pub fn wasm_module(&self) -> &Module { self.module.wasm_module() } + + fn tari_engine_entrypoint(env: &WasmEnv, op: i32, arg_ptr: i32, arg_len: i32) -> i32 { + let arg = match env.read_from_memory(arg_ptr as u32, arg_len as u32) { + Ok(arg) => arg, + Err(err) => { + log::error!(target: LOG_TARGET, "Failed to read from memory: {}", err); + return 0; + }, + }; + let result = match op { + ops::OP_EMIT_LOG => Self::handle(env, arg, |env, arg: EmitLogArg| { + env.state().interface().emit_log(arg.level, &arg.message); + Result::<_, WasmExecutionError>::Ok(()) + }), + ops::OP_CREATE_COMPONENT => Self::handle(env, arg, |env, arg: CreateComponentArg| { + env.state().interface().create_component(arg.into()) + }), + _ => Err(WasmExecutionError::InvalidOperation { op }), + }; + result.unwrap_or_else(|err| { + log::error!(target: LOG_TARGET, "{}", err); + 0 + }) + } + + pub fn handle( + env: &WasmEnv, + args: Vec, + f: fn(&WasmEnv, T) -> Result, + ) -> Result + where + T: BorshDeserialize, + U: BorshSerialize, + WasmExecutionError: From, + { + let decoded = decode(&args).map_err(WasmExecutionError::EngineArgDecodeFailed)?; + let resp = f(env, decoded)?; + let encoded = encode_with_len(&resp); + let ptr = env.alloc(encoded.len() as u32)?; + env.write_to_memory(&ptr, &encoded)?; + // TODO: It's not clear how/if this memory is freed. When I drop it on the WASM side I get an + // out-of-bounds access error. + Ok(ptr.as_i32()) + } } impl Invokable for Process { @@ -86,23 +129,36 @@ impl Invokable for Process { }; let main_name = format!("{}_main", self.module.template_name()); - let func = self.vm.get_function(&main_name)?; + let func = self.instance.exports.get_function(&main_name)?; let call_info_ptr = self.alloc_and_write(&call_info)?; - let res = func.call(&[call_info_ptr.as_val_i32(), Val::I32(call_info_ptr.len() as i32)])?; - self.vm.free(call_info_ptr)?; + let res = func.call(&[call_info_ptr.as_i32().into(), Val::I32(call_info_ptr.len() as i32)])?; + self.env.free(call_info_ptr)?; let ptr = res .get(0) .and_then(|v| v.i32()) .ok_or(WasmExecutionError::ExpectedPointerReturn { function: main_name })?; // Read response from memory - let raw = self.vm.read_from_memory(ptr as u32)?; + let raw = self.env.read_memory_with_embedded_len(ptr as u32)?; // TODO: decode raw as per function def Ok(ExecutionResult { value: wasmer::Value::I32(ptr), raw, + return_type: func_def.output.clone(), }) } } + +pub struct ExecutionResult { + pub value: wasmer::Value, + pub raw: Vec, + pub return_type: Type, +} + +impl ExecutionResult { + pub fn decode(&self) -> io::Result { + tari_template_abi::decode(&self.raw) + } +} diff --git a/dan_layer/engine/src/wasm/vm.rs b/dan_layer/engine/src/wasm/vm.rs deleted file mode 100644 index dfbc94d353..0000000000 --- a/dan_layer/engine/src/wasm/vm.rs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2022. The Tari Project -// -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -use std::cell::Cell; - -use wasmer::{imports, Function, Instance, Memory, Module, Store, Val}; - -use crate::{ - env::{tari_engine, EngineEnvironment}, - wasm::error::WasmExecutionError, -}; - -#[derive(Debug)] -pub struct VmInstance { - memory: Memory, - instance: Instance, - _store: Store, -} - -impl VmInstance { - pub fn instantiate(module: &Module) -> Result { - let store = Store::default(); - // TODO: proper environment - let env = EngineEnvironment::default(); - let imports = imports! { - "env" => { - "tari_engine" => Function::new_native_with_env(&store, env, tari_engine), - } - }; - let instance = Instance::new(module, &imports)?; - let memory = instance.exports.get_memory("memory")?; - Ok(Self { - memory: memory.clone(), - _store: store, - instance, - }) - } - - pub(super) fn alloc(&self, len: u32) -> Result { - let alloc = self.instance.exports.get_function("tari_alloc")?; - let ret = alloc.call(&[Val::I32(len as i32)])?; - match ret.get(0) { - Some(Val::I32(ptr)) => Ok(AllocPtr(*ptr as u32, len)), - _ => Err(WasmExecutionError::ExpectedPointerReturn { - function: "tari_alloc".into(), - }), - } - } - - pub(super) fn free(&self, ptr: AllocPtr) -> Result<(), WasmExecutionError> { - let alloc = self.instance.exports.get_function("tari_free")?; - alloc.call(&[ptr.as_val_i32()])?; - Ok(()) - } - - pub(super) fn write_to_memory(&self, ptr: &AllocPtr, data: &[u8]) -> Result<(), WasmExecutionError> { - if data.len() != ptr.len() as usize { - return Err(WasmExecutionError::InvalidWriteLength { - allocated: ptr.len(), - requested: data.len() as u32, - }); - } - // SAFETY: The VM owns the only memory instance, and the pointer has been allocated by alloc above so data races - // are not possible. - unsafe { - self.memory.uint8view().subarray(ptr.get(), ptr.end()).copy_from(data); - } - Ok(()) - } - - pub(super) fn read_from_memory(&self, ptr: u32) -> Result, WasmExecutionError> { - // TODO: DRY this up - let view = self - .memory - .uint8view() - .subarray(ptr, self.memory.data_size() as u32 - 1); - let view_bytes = &*view; - if view_bytes.len() < 4 { - return Err(WasmExecutionError::MemoryUnderflow { - required: 4, - remaining: view_bytes.len(), - }); - } - - fn copy_from_cell_slice(src: &[Cell], dest: &mut [u8], len: usize) { - // TODO: Is there a more efficient way to do this? - for i in 0..len { - dest[i] = src[i].get(); - } - } - - let mut buf = [0u8; 4]; - copy_from_cell_slice(view_bytes, &mut buf, 4); - let len = u32::from_le_bytes(buf) as usize; - if view_bytes.len() < 4 + len { - return Err(WasmExecutionError::MemoryUnderflow { - required: 4 + len, - remaining: view_bytes.len(), - }); - } - - let mut data = vec![0u8; len]; - let src = view.subarray(4, 4 + len as u32); - copy_from_cell_slice(&*src, &mut data, len); - Ok(data) - } - - pub fn get_function(&self, name: &str) -> Result<&Function, WasmExecutionError> { - let func = self.instance.exports.get_function(name)?; - Ok(func) - } -} - -#[derive(Debug)] -pub struct AllocPtr(u32, u32); - -impl AllocPtr { - pub fn get(&self) -> u32 { - self.0 - } - - pub fn len(&self) -> u32 { - self.1 - } - - pub fn end(&self) -> u32 { - self.get() + self.len() - } - - pub fn as_val_i32(&self) -> Val { - // We want the 'u32 as i32' conversion to wrap - Val::I32(self.get() as i32) - } -} diff --git a/dan_layer/engine/tests/common/Cargo.toml b/dan_layer/engine/tests/common/Cargo.toml deleted file mode 100644 index 29698acac4..0000000000 --- a/dan_layer/engine/tests/common/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[workspace] -[package] -name = "common" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -tari_template_abi = { path = "../../../template_abi" } - -[profile.release] -opt-level = 's' # Optimize for size. -lto = true # Enable Link Time Optimization. -codegen-units = 1 # Reduce number of codegen units to increase optimizations. -panic = 'abort' # Abort on panic. -strip = "debuginfo" # Strip debug info. - -[lib] -crate-type = ["cdylib", "lib"] \ No newline at end of file diff --git a/dan_layer/engine/tests/hello_world/Cargo.lock b/dan_layer/engine/tests/hello_world/Cargo.lock index 57f0926882..b09f1bec3b 100644 --- a/dan_layer/engine/tests/hello_world/Cargo.lock +++ b/dan_layer/engine/tests/hello_world/Cargo.lock @@ -64,13 +64,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "common" -version = "0.1.0" -dependencies = [ - "tari_template_abi", -] - [[package]] name = "getrandom" version = "0.2.7" @@ -95,8 +88,8 @@ dependencies = [ name = "hello_world" version = "0.1.0" dependencies = [ - "common", "tari_template_abi", + "tari_template_lib", "tari_template_macros", ] @@ -163,6 +156,13 @@ dependencies = [ "borsh", ] +[[package]] +name = "tari_template_lib" +version = "0.1.0" +dependencies = [ + "tari_template_abi", +] + [[package]] name = "tari_template_macros" version = "0.1.0" diff --git a/dan_layer/engine/tests/hello_world/Cargo.toml b/dan_layer/engine/tests/hello_world/Cargo.toml index b3ad4b9e15..e4fdadcce0 100644 --- a/dan_layer/engine/tests/hello_world/Cargo.toml +++ b/dan_layer/engine/tests/hello_world/Cargo.toml @@ -8,8 +8,8 @@ edition = "2021" [dependencies] tari_template_abi = { path = "../../../template_abi" } +tari_template_lib = { path = "../../../template_lib" } tari_template_macros = { path = "../../../template_macros" } -common = { path = "../common" } [profile.release] opt-level = 's' # Optimize for size. diff --git a/dan_layer/engine/tests/hello_world/src/lib.rs b/dan_layer/engine/tests/hello_world/src/lib.rs index 275d71e75f..4d96b755de 100644 --- a/dan_layer/engine/tests/hello_world/src/lib.rs +++ b/dan_layer/engine/tests/hello_world/src/lib.rs @@ -26,7 +26,7 @@ use tari_template_macros::template; mod hello_world { struct HelloWorld {} - impl HelloWorld { + impl HelloWorld { pub fn greet() -> String { "Hello World!".to_string() } diff --git a/dan_layer/engine/tests/mock_runtime_interface.rs b/dan_layer/engine/tests/mock_runtime_interface.rs new file mode 100644 index 0000000000..c81591814b --- /dev/null +++ b/dan_layer/engine/tests/mock_runtime_interface.rs @@ -0,0 +1,64 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::sync::{atomic::AtomicU32, Arc}; + +use tari_dan_common_types::Hash; +use tari_dan_engine::{ + models::{Component, ComponentId}, + runtime::{RuntimeError, RuntimeInterface}, +}; +use tari_template_abi::LogLevel; + +#[derive(Debug, Clone, Default)] +pub struct MockRuntimeInterface { + ids: Arc, +} + +impl MockRuntimeInterface { + pub fn new() -> Self { + Self { + ids: Arc::new(AtomicU32::new(0)), + } + } + + pub fn next_id(&self) -> u32 { + self.ids.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + } +} + +impl RuntimeInterface for MockRuntimeInterface { + fn emit_log(&self, level: LogLevel, message: &str) { + let level = match level { + LogLevel::Error => log::Level::Error, + LogLevel::Warn => log::Level::Warn, + LogLevel::Info => log::Level::Info, + LogLevel::Debug => log::Level::Debug, + }; + eprintln!("[{:?}] {}", level, message); + log::log!(target: "tari::dan::engine::runtime", level, "{}", message); + } + + fn create_component(&self, _new_component: Component) -> Result { + Ok((Hash::default(), self.next_id())) + } +} diff --git a/dan_layer/engine/tests/state/Cargo.lock b/dan_layer/engine/tests/state/Cargo.lock index 3f75811d93..9964c09d41 100644 --- a/dan_layer/engine/tests/state/Cargo.lock +++ b/dan_layer/engine/tests/state/Cargo.lock @@ -64,13 +64,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "common" -version = "0.1.0" -dependencies = [ - "tari_template_abi", -] - [[package]] name = "getrandom" version = "0.2.7" @@ -140,8 +133,8 @@ checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" name = "state" version = "0.1.0" dependencies = [ - "common", "tari_template_abi", + "tari_template_lib", ] [[package]] @@ -162,6 +155,13 @@ dependencies = [ "borsh", ] +[[package]] +name = "tari_template_lib" +version = "0.1.0" +dependencies = [ + "tari_template_abi", +] + [[package]] name = "toml" version = "0.5.9" diff --git a/dan_layer/engine/tests/state/Cargo.toml b/dan_layer/engine/tests/state/Cargo.toml index 008ec555f7..19b00846d8 100644 --- a/dan_layer/engine/tests/state/Cargo.toml +++ b/dan_layer/engine/tests/state/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] tari_template_abi = { path = "../../../template_abi" } -common = { path = "../common" } +tari_template_lib = { path = "../../../template_lib" } [profile.release] opt-level = 's' # Optimize for size. diff --git a/dan_layer/engine/tests/state/src/lib.rs b/dan_layer/engine/tests/state/src/lib.rs index cdc61c488e..0514d3bd6c 100644 --- a/dan_layer/engine/tests/state/src/lib.rs +++ b/dan_layer/engine/tests/state/src/lib.rs @@ -20,31 +20,31 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// TODO: we should only use stdlib if the template dev needs to include it e.g. use core::mem when stdlib is not -// available - -use common::{generate_abi, TemplateImpl, generate_main}; -use tari_template_abi::{FunctionDef, Type, encode_with_len, decode}; +use tari_template_abi::{decode, encode_with_len, FunctionDef, Type}; +use tari_template_lib::{call_engine, generate_abi, generate_main, TemplateImpl}; // that's what the example should look like from the user's perspective #[allow(dead_code)] -mod rust { +mod state_template { + use tari_template_abi::{borsh, Decode, Encode}; + // #[tari::template] + #[derive(Encode, Decode)] pub struct State { value: u32, } - + // #[tari::impl] impl State { // #[tari::constructor] pub fn new() -> Self { Self { value: 0 } } - + pub fn set(&mut self, value: u32) { self.value = value; } - + pub fn get(&self) -> u32 { self.value } @@ -56,19 +56,23 @@ mod rust { extern "C" fn State_abi() -> *mut u8 { let template_name = "State".to_string(); - let functions = vec![FunctionDef { - name: "new".to_string(), - arguments: vec![], - output: Type::U32, // the component_id - }, FunctionDef { - name: "set".to_string(), - arguments: vec![Type::U32, Type::U32], // the component_id and the new value - output: Type::Unit, // does not return anything - }, FunctionDef { - name: "get".to_string(), - arguments: vec![Type::U32], // the component_id - output: Type::U32, // the stored value - }]; + let functions = vec![ + FunctionDef { + name: "new".to_string(), + arguments: vec![], + output: Type::U32, // the component_id + }, + FunctionDef { + name: "set".to_string(), + arguments: vec![Type::U32, Type::U32], // the component_id and the new value + output: Type::Unit, // does not return anything + }, + FunctionDef { + name: "get".to_string(), + arguments: vec![Type::U32], // the component_id + output: Type::U32, // the stored value + }, + ]; generate_abi(template_name, functions) } @@ -76,48 +80,67 @@ extern "C" fn State_abi() -> *mut u8 { #[no_mangle] extern "C" fn State_main(call_info: *mut u8, call_info_len: usize) -> *mut u8 { let mut template_impl = TemplateImpl::new(); + use tari_template_abi::{ops::*, CreateComponentArg, EmitLogArg, LogLevel}; + use tari_template_lib::models::ComponentId; + + tari_template_lib::call_engine::<_, ()>(OP_EMIT_LOG, &EmitLogArg { + message: "This is a log message from State_main!".to_string(), + level: LogLevel::Info, + }); // constructor - template_impl.add_function("new".to_string(), Box::new(|_| { - // Call the engine to create a new component - // TODO: use a real op code (not "123") when they are implemented - let _component_id = unsafe { tari_engine(123, std::ptr::null(), 0) }; - - // TODO: decode the returning value into a real component id - let component_id = 1_u32; - encode_with_len(&component_id) - })); - - template_impl.add_function("set".to_string(), Box::new(|args| { - // read the function paramenters - let _component_id: u32 = decode(&args[0]).unwrap(); - let _new_value: u32 = decode(&args[1]).unwrap(); - - // update the component value - // TODO: use a real op code (not "123") when they are implemented - unsafe { tari_engine(123, std::ptr::null(), 0) }; - - // the function does not return any value - // TODO: implement "Unit" type empty responses. Right now this fails: wrap_ptr(vec![]) - encode_with_len(&0) - })); - - template_impl.add_function("get".to_string(), Box::new(|args| { - // read the function paramenters - let _component_id: u32 = decode(&args[0]).unwrap(); - - // get the component state - // TODO: use a real op code (not "123") when they are implemented - let _state = unsafe { tari_engine(123, std::ptr::null(), 0) }; - - // return the value - let value = 1_u32; // TODO: read from the component state - encode_with_len(&value) - })); + template_impl.add_function( + "new".to_string(), + Box::new(|_| { + let ret = state_template::State::new(); + let encoded = encode_with_len(&ret); + // Call the engine to create a new component + // TODO: proper component id + // The macro will know to generate this call because of the #[tari(constructor)] attribute + // TODO: what happens if the user wants to return multiple components/types? + let component_id = call_engine::<_, ComponentId>(OP_CREATE_COMPONENT, &CreateComponentArg { + name: "State".to_string(), + quantity: 1, + metadata: Default::default(), + state: encoded, + }); + let component_id = component_id.expect("no asset id returned"); + encode_with_len(&component_id) + }), + ); + + template_impl.add_function( + "set".to_string(), + Box::new(|args| { + // read the function paramenters + let _component_id: u32 = decode(&args[0]).unwrap(); + let _new_value: u32 = decode(&args[1]).unwrap(); + + // update the component value + // TODO: use a real op code (not "123") when they are implemented + call_engine::<_, ()>(123, &()); + + // the function does not return any value + // TODO: implement "Unit" type empty responses. Right now this fails: wrap_ptr(vec![]) + encode_with_len(&0) + }), + ); + + template_impl.add_function( + "get".to_string(), + Box::new(|args| { + // read the function paramenters + let _component_id: u32 = decode(&args[0]).unwrap(); + + // get the component state + // TODO: use a real op code (not "123") when they are implemented + let _state = call_engine::<_, ()>(123, &()); + + // return the value + let value = 1_u32; // TODO: read from the component state + encode_with_len(&value) + }), + ); generate_main(call_info, call_info_len, template_impl) } - -extern "C" { - pub fn tari_engine(op: u32, input_ptr: *const u8, input_len: usize) -> *mut u8; -} diff --git a/dan_layer/engine/tests/test.rs b/dan_layer/engine/tests/test.rs index cefcf564b6..65194806b9 100644 --- a/dan_layer/engine/tests/test.rs +++ b/dan_layer/engine/tests/test.rs @@ -20,21 +20,25 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +mod mock_runtime_interface; + use borsh::BorshDeserialize; +use mock_runtime_interface::MockRuntimeInterface; use tari_common_types::types::FixedHash; use tari_crypto::ristretto::RistrettoSecretKey; use tari_dan_engine::{ - compile::compile_template, crypto::create_key_pair, instruction::{Instruction, InstructionBuilder, InstructionProcessor}, - package::PackageBuilder, + models::ComponentId, + packager::Package, + wasm::compile::build_wasm_module_from_source, }; use tari_template_abi::encode_with_len; #[test] fn test_hello_world() { let template_test = TemplateTest::new("HelloWorld".to_string(), "tests/hello_world".to_string()); - let result: String = template_test.run_instruction("greet".to_string(), vec![]); + let result: String = template_test.call_function("greet".to_string(), vec![]); // FIXME: without the "encode_with_len" calls, the strings are different because of added padding characters assert_eq!(encode_with_len(&result), encode_with_len(&"Hello World!")); @@ -45,35 +49,38 @@ fn test_state() { let template_test = TemplateTest::new("State".to_string(), "tests/state".to_string()); // constructor - let component_id: u32 = template_test.run_instruction("new".to_string(), vec![]); + let component: ComponentId = template_test.call_function("new".to_string(), vec![]); + assert_eq!(component.1, 0); + let component: ComponentId = template_test.call_function("new".to_string(), vec![]); + assert_eq!(component.1, 1); // call the "set" method to update the instance value let new_value = 20_u32; - // TODO: implement "Unit" type empty responses - let _: u32 = template_test.run_instruction("set".to_string(), vec![ - encode_with_len(&component_id), + template_test.call_method::<()>("State".to_string(), "set".to_string(), vec![ + encode_with_len(&component), encode_with_len(&new_value), ]); - // call the "get" method to get the current value - let value: u32 = template_test.run_instruction("get".to_string(), vec![encode_with_len(&component_id)]); + let value: u32 = template_test.call_method("State".to_string(), "get".to_string(), vec![encode_with_len( + &component, + )]); assert_eq!(value, 1); } struct TemplateTest { template_name: String, package_id: FixedHash, - processor: InstructionProcessor, + processor: InstructionProcessor, secret_key: RistrettoSecretKey, } impl TemplateTest { pub fn new(template_name: String, template_path: String) -> Self { - let mut processor = InstructionProcessor::new(); + let mut processor = InstructionProcessor::new(MockRuntimeInterface::new()); let (secret_key, _pk) = create_key_pair(); - let wasm = compile_template(template_path).unwrap(); - let package = PackageBuilder::new().add_wasm_template(wasm).build().unwrap(); + let wasm = build_wasm_module_from_source(template_path).unwrap(); + let package = Package::builder().add_wasm_module(wasm).build().unwrap(); let package_id = package.id(); processor.load(package); @@ -85,7 +92,7 @@ impl TemplateTest { } } - pub fn run_instruction(&self, func_name: String, args: Vec>) -> T + pub fn call_function(&self, func_name: String, args: Vec>) -> T where T: BorshDeserialize { let instruction = InstructionBuilder::new() .add_instruction(Instruction::CallFunction { @@ -100,4 +107,20 @@ impl TemplateTest { result[0].decode::().unwrap() } + + pub fn call_method(&self, component_id: String, method_name: String, args: Vec>) -> T + where T: BorshDeserialize { + let instruction = InstructionBuilder::new() + .add_instruction(Instruction::CallMethod { + package_id: self.package_id, + component_id, + method: method_name, + args, + }) + .sign(&self.secret_key) + .build(); + let result = self.processor.execute(instruction).unwrap(); + + result[0].decode::().unwrap() + } } diff --git a/dan_layer/template_abi/src/encoding.rs b/dan_layer/template_abi/src/encoding.rs index b64ce07917..d5723270ee 100644 --- a/dan_layer/template_abi/src/encoding.rs +++ b/dan_layer/template_abi/src/encoding.rs @@ -24,9 +24,9 @@ use std::io; -use borsh::{BorshDeserialize, BorshSerialize}; +use crate::{Decode, Encode}; -pub fn encode_with_len(val: &T) -> Vec { +pub fn encode_with_len(val: &T) -> Vec { let mut buf = Vec::with_capacity(512); buf.extend([0u8; 4]); @@ -38,10 +38,24 @@ pub fn encode_with_len(val: &T) -> Vec { buf } -pub fn encode_into(val: &T, buf: &mut Vec) -> io::Result<()> { +pub fn encode_into(val: &T, buf: &mut Vec) -> io::Result<()> { val.serialize(buf) } -pub fn decode(mut input: &[u8]) -> io::Result { +pub fn decode(mut input: &[u8]) -> io::Result { T::deserialize(&mut input) } + +pub fn decode_len(input: &[u8]) -> io::Result { + if input.len() < 4 { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "Not enough bytes to decode length", + )); + } + + let mut buf = [0u8; 4]; + buf.copy_from_slice(&input[..4]); + let len = u32::from_le_bytes(buf); + Ok(len as usize) +} diff --git a/dan_layer/template_abi/src/lib.rs b/dan_layer/template_abi/src/lib.rs index db7b1d48f5..3037149407 100644 --- a/dan_layer/template_abi/src/lib.rs +++ b/dan_layer/template_abi/src/lib.rs @@ -20,12 +20,20 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//! # Tari WASM module ABI (application binary interface) +//! +//! This library provides types and encoding that allow low-level communication between the Tari WASM runtime and the +//! WASM modules. + mod encoding; +pub mod ops; + +use std::collections::HashMap; -use borsh::{BorshDeserialize, BorshSerialize}; -pub use encoding::{decode, encode_into, encode_with_len}; +pub use borsh::{self, BorshDeserialize as Decode, BorshSerialize as Encode}; +pub use encoding::{decode, decode_len, encode_into, encode_with_len}; -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, Encode, Decode)] pub struct TemplateDef { pub template_name: String, pub functions: Vec, @@ -37,14 +45,14 @@ impl TemplateDef { } } -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, Encode, Decode)] pub struct FunctionDef { pub name: String, pub arguments: Vec, pub output: Type, } -#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub enum Type { Unit, Bool, @@ -61,8 +69,32 @@ pub enum Type { String, } -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] +#[derive(Debug, Clone, Encode, Decode)] pub struct CallInfo { pub func_name: String, pub args: Vec>, } + +#[derive(Debug, Clone, Encode, Decode)] +pub struct EmitLogArg { + pub message: String, + pub level: LogLevel, +} + +#[derive(Debug, Clone, Encode, Decode)] +pub enum LogLevel { + Error, + Warn, + Info, + Debug, +} + +#[derive(Debug, Clone, Encode, Decode)] +pub struct CreateComponentArg { + // asset/component metadata + pub name: String, + pub quantity: u64, + pub metadata: HashMap, Vec>, + // encoded asset/component state + pub state: Vec, +} diff --git a/dan_layer/template_abi/src/ops.rs b/dan_layer/template_abi/src/ops.rs new file mode 100644 index 0000000000..0ce2f3e287 --- /dev/null +++ b/dan_layer/template_abi/src/ops.rs @@ -0,0 +1,24 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub const OP_EMIT_LOG: i32 = 0x00; +pub const OP_CREATE_COMPONENT: i32 = 0x01; diff --git a/dan_layer/engine/tests/common/Cargo.lock b/dan_layer/template_lib/Cargo.lock similarity index 100% rename from dan_layer/engine/tests/common/Cargo.lock rename to dan_layer/template_lib/Cargo.lock diff --git a/dan_layer/template_lib/Cargo.toml b/dan_layer/template_lib/Cargo.toml new file mode 100644 index 0000000000..b986932b1c --- /dev/null +++ b/dan_layer/template_lib/Cargo.toml @@ -0,0 +1,20 @@ +[workspace] +[package] +name = "tari_template_lib" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tari_template_abi = { path = "../template_abi" } +# +#[profile.release] +#opt-level = 's' # Optimize for size. +#lto = true # Enable Link Time Optimization. +#codegen-units = 1 # Reduce number of codegen units to increase optimizations. +#panic = 'abort' # Abort on panic. +#strip = "debuginfo" # Strip debug info. +# +#[lib] +#crate-type = ["cdylib", "lib"] \ No newline at end of file diff --git a/dan_layer/engine/tests/common/src/lib.rs b/dan_layer/template_lib/src/lib.rs similarity index 63% rename from dan_layer/engine/tests/common/src/lib.rs rename to dan_layer/template_lib/src/lib.rs index 6fad8278ff..cdb700bf4c 100644 --- a/dan_layer/engine/tests/common/src/lib.rs +++ b/dan_layer/template_lib/src/lib.rs @@ -20,9 +20,22 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{collections::HashMap, mem, intrinsics::copy}; +//! # Tari WASM module library +//! +//! This library provides primitives and functionality that allows Tari WASM modules to interact with the Tari engine. +//! It is intended to be used by WASM modules that are written in Rust and compiled into WASM. +//! +//! The tari engine itself should never depend on this crate. +//! +//! TODO: no_std support -use tari_template_abi::{encode_with_len, FunctionDef, TemplateDef, CallInfo, decode}; +pub mod models; + +// TODO: we should only use stdlib if the template dev needs to include it e.g. use core::mem when stdlib is not +// available +use std::{collections::HashMap, mem, ptr::copy, slice}; + +use tari_template_abi::{encode_with_len, Decode, Encode, FunctionDef, TemplateDef}; pub fn generate_abi(template_name: String, functions: Vec) -> *mut u8 { let template = TemplateDef { @@ -49,16 +62,17 @@ impl TemplateImpl { } pub fn generate_main(call_info: *mut u8, call_info_len: usize, template_impl: TemplateImpl) -> *mut u8 { + use tari_template_abi::{decode, CallInfo}; if call_info.is_null() { panic!("call_info is null"); } - let call_data = unsafe { Vec::from_raw_parts(call_info, call_info_len, call_info_len) }; - let call_info: CallInfo = decode(&call_data).unwrap(); + let call_data = unsafe { slice::from_raw_parts(call_info, call_info_len) }; + let call_info: CallInfo = decode(call_data).unwrap(); // get the function let function = match template_impl.0.get(&call_info.func_name) { - Some(f) => f.clone(), + Some(f) => f, None => panic!("invalid function name"), }; @@ -69,12 +83,42 @@ pub fn generate_main(call_info: *mut u8, call_info_len: usize, template_impl: Te wrap_ptr(result) } +pub fn call_engine(op: i32, input: &T) -> Option { + use tari_template_abi::{decode, decode_len, encode_into}; + + let mut encoded = Vec::with_capacity(512); + encode_into(input, &mut encoded).unwrap(); + let len = encoded.len(); + let input_ptr = wrap_ptr(encoded) as *const _; + let ptr = unsafe { tari_engine(op, input_ptr, len) }; + if ptr.is_null() { + return None; + } + + let slice = unsafe { slice::from_raw_parts(ptr as *const _, 4) }; + let len = decode_len(&slice).unwrap(); + let slice = unsafe { slice::from_raw_parts(ptr.offset(4), len) }; + let ret = decode(&slice).unwrap(); + Some(ret) +} + pub fn wrap_ptr(mut v: Vec) -> *mut u8 { let ptr = v.as_mut_ptr(); mem::forget(v); ptr } +extern "C" { + fn tari_engine(op: i32, input_ptr: *const u8, input_len: usize) -> *mut u8; + fn debug(input_ptr: *const u8, input_len: usize); +} + +pub fn call_debug>(data: T) { + let ptr = data.as_ref().as_ptr(); + let len = data.as_ref().len(); + unsafe { debug(ptr, len) } +} + #[no_mangle] pub unsafe extern "C" fn tari_alloc(len: u32) -> *mut u8 { let cap = (len + 4) as usize; @@ -92,4 +136,4 @@ pub unsafe extern "C" fn tari_free(ptr: *mut u8) { let cap = (u32::from_le_bytes(len) + 4) as usize; let _ = Vec::::from_raw_parts(ptr, cap, cap); -} \ No newline at end of file +} diff --git a/dan_layer/template_lib/src/models/component.rs b/dan_layer/template_lib/src/models/component.rs new file mode 100644 index 0000000000..3b27286bbc --- /dev/null +++ b/dan_layer/template_lib/src/models/component.rs @@ -0,0 +1,23 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub type ComponentId = ([u8; 32], u32); diff --git a/dan_layer/template_lib/src/models/mod.rs b/dan_layer/template_lib/src/models/mod.rs new file mode 100644 index 0000000000..ef04fea78d --- /dev/null +++ b/dan_layer/template_lib/src/models/mod.rs @@ -0,0 +1,24 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod component; +pub use component::ComponentId;