diff --git a/Cargo.lock b/Cargo.lock index 4f70cd7782..0172f15ea6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5979,9 +5979,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.137" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" dependencies = [ "serde_derive", ] @@ -6026,9 +6026,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" dependencies = [ "proc-macro2", "quote", @@ -7176,6 +7176,7 @@ dependencies = [ "tari_dan_common_types", "tari_mmr", "tari_template_abi", + "tari_template_lib", "tari_utilities", "thiserror", "wasmer", @@ -7490,6 +7491,8 @@ dependencies = [ name = "tari_template_lib" version = "0.1.0" dependencies = [ + "borsh", + "serde", "tari_template_abi", ] @@ -7501,8 +7504,6 @@ dependencies = [ "proc-macro2", "quote", "syn", - "tari_template_abi", - "tari_template_lib", ] [[package]] diff --git a/dan_layer/common_types/src/lib.rs b/dan_layer/common_types/src/lib.rs index abb6a597be..80f3671765 100644 --- a/dan_layer/common_types/src/lib.rs +++ b/dan_layer/common_types/src/lib.rs @@ -4,8 +4,6 @@ pub mod proto; pub mod storage; -mod hash; mod template_id; -pub use hash::Hash; pub use template_id::TemplateId; diff --git a/dan_layer/engine/Cargo.toml b/dan_layer/engine/Cargo.toml index 1320693979..e312b2bba9 100644 --- a/dan_layer/engine/Cargo.toml +++ b/dan_layer/engine/Cargo.toml @@ -6,13 +6,14 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tari_common_types = { path = "../../base_layer/common_types" } tari_common = { path = "../../common" } +tari_common_types = { path = "../../base_layer/common_types" } +tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", tag = "v0.15.4" } tari_dan_common_types = { path = "../common_types" } tari_mmr = { path = "../../base_layer/mmr" } -tari_utilities = { git = "https://github.com/tari-project/tari_utilities.git", tag = "v0.4.5" } -tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", tag = "v0.15.4" } tari_template_abi = { path = "../template_abi" } +tari_template_lib = { path = "../template_lib", features = ["serde"] } +tari_utilities = { git = "https://github.com/tari-project/tari_utilities.git", tag = "v0.4.5" } anyhow = "1.0.53" borsh = "0.9.3" diff --git a/dan_layer/engine/src/flow/workers/create_bucket_worker.rs b/dan_layer/engine/src/flow/workers/create_bucket_worker.rs index 0b543ecacd..f4368842e5 100644 --- a/dan_layer/engine/src/flow/workers/create_bucket_worker.rs +++ b/dan_layer/engine/src/flow/workers/create_bucket_worker.rs @@ -10,7 +10,10 @@ use d3ne::{InputData, Node, OutputData, OutputDataBuilder, Worker}; use tari_common_types::types::PublicKey; use tari_utilities::{hex::Hex, ByteArray}; -use crate::{models::Bucket, state::StateDbUnitOfWork}; +use crate::{ + models::{Bucket, ResourceAddress}, + state::StateDbUnitOfWork, +}; pub struct CreateBucketWorker { pub state_db: Arc>, @@ -24,11 +27,11 @@ impl Worker for CreateBucketWorker fn work(&self, node: &Node, input_data: InputData) -> anyhow::Result { // TODO: return proper errors.... let amount = u64::try_from(node.get_number_field("amount", &input_data)?)?; + let vault_id = ResourceAddress::from_hex(&node.get_string_field("vault_id", &input_data)?)?; let token_id = u64::try_from(node.get_number_field("token_id", &input_data)?)?; - let asset_id = u64::try_from(node.get_number_field("asset_id", &input_data)?)?; let from = PublicKey::from_hex(&node.get_string_field("from", &input_data)?).expect("Not a valid pub key"); let mut state = self.state_db.write().unwrap(); - let balance_key = format!("token_id-{}-{}", asset_id, token_id); + let balance_key = format!("token_id-{}-{}", vault_id, token_id); let balance = state.get_u64(&balance_key, from.as_bytes())?.unwrap_or(0); let new_balance = balance.checked_sub(amount).expect("Not enough funds to create bucket"); state @@ -36,7 +39,7 @@ impl Worker for CreateBucketWorker .expect("Could not save state"); let output = OutputDataBuilder::new() .data("default", Box::new(())) - .data("bucket", Box::new(Bucket::new(amount, token_id, asset_id))) + .data("bucket", Box::new(Bucket::for_token(vault_id, vec![token_id]))) .build(); Ok(output) } diff --git a/dan_layer/engine/src/flow/workers/mint_bucket_worker.rs b/dan_layer/engine/src/flow/workers/mint_bucket_worker.rs index 001f329f12..af5386330d 100644 --- a/dan_layer/engine/src/flow/workers/mint_bucket_worker.rs +++ b/dan_layer/engine/src/flow/workers/mint_bucket_worker.rs @@ -5,7 +5,7 @@ use std::convert::TryFrom; use d3ne::{InputData, Node, OutputData, OutputDataBuilder, Worker}; -use crate::models::Bucket; +use crate::models::{Bucket, ResourceAddress}; pub struct MintBucketWorker {} @@ -48,10 +48,10 @@ impl Worker for MintBucketWorker { } fn work(&self, node: &Node, inputs: InputData) -> anyhow::Result { - let amount = u64::try_from(node.get_number_field("amount", &inputs)?)?; + let _amount = u64::try_from(node.get_number_field("amount", &inputs)?)?; let token_id = u64::try_from(node.get_number_field("token_id", &inputs)?)?; - let asset_id = u64::try_from(node.get_number_field("asset_id", &inputs)?)?; - let bucket = Bucket::new(amount, token_id, asset_id); + let vault_id = ResourceAddress::from_hex(&node.get_string_field("vault_id", &inputs)?)?; + let bucket = Bucket::for_token(vault_id, vec![token_id]); let output = OutputDataBuilder::new() .data("default", Box::new(())) .data("bucket", Box::new(bucket)) diff --git a/dan_layer/engine/src/flow/workers/store_bucket_worker.rs b/dan_layer/engine/src/flow/workers/store_bucket_worker.rs index a625a6a0d9..3286f32132 100644 --- a/dan_layer/engine/src/flow/workers/store_bucket_worker.rs +++ b/dan_layer/engine/src/flow/workers/store_bucket_worker.rs @@ -70,7 +70,12 @@ impl Worker for StoreBucketWorker { let bucket: Bucket = serde_json::from_str(&node.get_string_field("bucket", &inputs)?)?; let to = PublicKey::from_hex(&node.get_string_field("to", &inputs)?)?; let mut state = self.state_db.write().unwrap(); - let balance_key = format!("token_id-{}-{}", bucket.asset_id(), bucket.token_id()); + // TODO: handle panics + let balance_key = format!( + "token_id-{}-{}", + bucket.resource_address(), + bucket.token_ids().unwrap()[0] + ); let balance = state.get_u64(&balance_key, to.as_bytes())?.unwrap_or(0); state.set_u64( &balance_key, diff --git a/dan_layer/engine/src/instruction/error.rs b/dan_layer/engine/src/instruction/error.rs index 35926900a7..9d4e9bb196 100644 --- a/dan_layer/engine/src/instruction/error.rs +++ b/dan_layer/engine/src/instruction/error.rs @@ -20,7 +20,9 @@ // 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::PackageId, wasm::WasmExecutionError}; +use tari_template_lib::models::PackageId; + +use crate::{runtime::RuntimeError, wasm::WasmExecutionError}; #[derive(Debug, thiserror::Error)] pub enum InstructionError { @@ -30,4 +32,6 @@ pub enum InstructionError { PackageNotFound { package_id: PackageId }, #[error("Invalid template")] TemplateNameNotFound { name: String }, + #[error(transparent)] + RuntimeError(#[from] RuntimeError), } diff --git a/dan_layer/engine/src/instruction/mod.rs b/dan_layer/engine/src/instruction/mod.rs index 286e4b5b40..f2ac91f665 100644 --- a/dan_layer/engine/src/instruction/mod.rs +++ b/dan_layer/engine/src/instruction/mod.rs @@ -29,8 +29,8 @@ mod processor; pub use processor::InstructionProcessor; mod signature; - -use crate::{instruction::signature::InstructionSignature, packager::PackageId}; +pub use signature::InstructionSignature; +use tari_template_lib::models::{ComponentId, PackageId}; #[derive(Debug, Clone)] pub enum Instruction { @@ -42,7 +42,7 @@ pub enum Instruction { }, CallMethod { package_id: PackageId, - component_id: String, + component_id: ComponentId, method: String, args: Vec>, }, diff --git a/dan_layer/engine/src/instruction/processor.rs b/dan_layer/engine/src/instruction/processor.rs index 0aa9f529b8..37b8408f6b 100644 --- a/dan_layer/engine/src/instruction/processor.rs +++ b/dan_layer/engine/src/instruction/processor.rs @@ -20,62 +20,59 @@ // 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, sync::Arc}; +use std::sync::Arc; + +use tari_template_abi::encode; use crate::{ instruction::{error::InstructionError, Instruction, InstructionSet}, - packager::{Package, PackageId}, + packager::Package, runtime::{Runtime, RuntimeInterface}, traits::Invokable, wasm::{ExecutionResult, Process}, }; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct InstructionProcessor { - packages: HashMap, + package: Package, runtime_interface: TRuntimeInterface, } impl InstructionProcessor where TRuntimeInterface: RuntimeInterface + Clone + 'static { - pub fn new(runtime_interface: TRuntimeInterface) -> Self { + pub fn new(runtime_interface: TRuntimeInterface, package: Package) -> Self { Self { - packages: HashMap::new(), + package, runtime_interface, } } - pub fn load(&mut self, package: Package) -> &mut Self { - self.packages.insert(package.id(), package); - self - } - pub fn execute(&self, instruction_set: InstructionSet) -> Result, InstructionError> { 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 { + let result = match instruction { Instruction::CallFunction { package_id, template, function, args, } => { - let package = self - .packages - .get(&package_id) - .ok_or(InstructionError::PackageNotFound { package_id })?; - let module = package + if package_id != self.package.id() { + return Err(InstructionError::PackageNotFound { package_id }); + } + + let module = self + .package .get_module_by_name(&template) .ok_or(InstructionError::TemplateNameNotFound { name: template })?; // TODO: implement intelligent instance caching - let process = Process::start(module.clone(), state.clone())?; - let result = process.invoke_by_name(&function, args)?; - results.push(result); + let process = Process::start(module.clone(), state.clone(), package_id)?; + process.invoke_by_name(&function, args)? }, Instruction::CallMethod { package_id, @@ -83,22 +80,27 @@ where TRuntimeInterface: RuntimeInterface + Clone + 'static 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 })?; + if package_id != self.package.id() { + return Err(InstructionError::PackageNotFound { package_id }); + } + let component = self.runtime_interface.get_component(&component_id)?; + let module = self.package.get_module_by_name(&component.module_name).ok_or_else(|| { + InstructionError::TemplateNameNotFound { + name: component.module_name.clone(), + } + })?; + + let mut final_args = Vec::with_capacity(args.len() + 1); + final_args.push(encode(&component).unwrap()); + final_args.extend(args); // TODO: implement intelligent instance caching - let process = Process::start(module.clone(), state.clone())?; - let result = process.invoke_by_name(&method, args)?; - results.push(result); + let process = Process::start(module.clone(), state.clone(), package_id)?; + process.invoke_by_name(&method, final_args)? }, - } + }; + + results.push(result); } Ok(results) diff --git a/dan_layer/engine/src/models/bucket.rs b/dan_layer/engine/src/models/bucket.rs index d9f43d7cfc..ee6a5900f5 100644 --- a/dan_layer/engine/src/models/bucket.rs +++ b/dan_layer/engine/src/models/bucket.rs @@ -2,32 +2,46 @@ // SPDX-License-Identifier: BSD-3-Clause use serde::Deserialize; +use tari_template_abi::{Decode, Encode}; -#[derive(Debug, Default, Clone, Deserialize)] +use crate::models::resource::{Resource, ResourceAddress}; + +#[derive(Debug, Clone, Encode, Decode, Deserialize)] pub struct Bucket { - amount: u64, - token_id: u64, - asset_id: u64, + resource: Resource, } impl Bucket { - pub fn new(amount: u64, token_id: u64, asset_id: u64) -> Self { + pub fn for_coin(address: ResourceAddress, amount: u64) -> Self { + Self { + resource: Resource::Coin { address, amount }, + } + } + + pub fn for_token(address: ResourceAddress, token_ids: Vec) -> Self { Self { - amount, - token_id, - asset_id, + resource: Resource::Token { address, token_ids }, } } pub fn amount(&self) -> u64 { - self.amount + match self.resource { + Resource::Coin { ref amount, .. } => *amount, + Resource::Token { ref token_ids, .. } => token_ids.len() as u64, + } } - pub fn token_id(&self) -> u64 { - self.token_id + pub fn resource_address(&self) -> ResourceAddress { + match self.resource { + Resource::Coin { address, .. } => address, + Resource::Token { address, .. } => address, + } } - pub fn asset_id(&self) -> u64 { - self.asset_id + pub fn token_ids(&self) -> Option<&[u64]> { + match self.resource { + Resource::Coin { .. } => None, + Resource::Token { ref token_ids, .. } => Some(token_ids), + } } } diff --git a/dan_layer/engine/src/models/component.rs b/dan_layer/engine/src/models/component.rs index f99e4fe432..97bc30dc6b 100644 --- a/dan_layer/engine/src/models/component.rs +++ b/dan_layer/engine/src/models/component.rs @@ -20,26 +20,28 @@ // 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_template_abi::{CreateComponentArg, Decode, Encode}; +use tari_template_lib::{ + models::{ContractAddress, PackageId}, + Hash, +}; -use tari_dan_common_types::Hash; -use tari_template_abi::CreateComponentArg; - -pub type ComponentId = (Hash, u32); +pub type ComponentId = Hash; +#[derive(Debug, Clone, Encode, Decode)] pub struct Component { - pub name: String, - pub quantity: u64, - pub metadata: HashMap, Vec>, + pub contract_address: ContractAddress, + pub package_id: PackageId, + pub module_name: String, pub state: Vec, } impl From for Component { fn from(arg: CreateComponentArg) -> Self { Self { - name: arg.name, - quantity: arg.quantity, - metadata: arg.metadata, + contract_address: arg.contract_address, + package_id: arg.package_id, + module_name: arg.component_name, state: arg.state, } } diff --git a/dan_layer/engine/src/models/mod.rs b/dan_layer/engine/src/models/mod.rs index bfc989b36d..5b7a99f148 100644 --- a/dan_layer/engine/src/models/mod.rs +++ b/dan_layer/engine/src/models/mod.rs @@ -4,8 +4,8 @@ mod bucket; pub use bucket::Bucket; -mod component; -pub use component::{Component, ComponentId}; +mod resource; +pub use resource::{Resource, ResourceAddress}; mod vault; -pub use vault::{Vault, VaultId}; +pub use vault::Vault; diff --git a/dan_layer/engine/src/models/resource.rs b/dan_layer/engine/src/models/resource.rs new file mode 100644 index 0000000000..ba2b078d68 --- /dev/null +++ b/dan_layer/engine/src/models/resource.rs @@ -0,0 +1,52 @@ +// 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 tari_template_abi::{Decode, Encode}; +use tari_template_lib::Hash; + +pub type ResourceAddress = Hash; + +#[derive(Debug, Clone, Encode, Decode, serde::Deserialize)] +pub enum Resource { + Coin { + address: ResourceAddress, + // type_descriptor: TypeDescriptor, + amount: u64, + }, + Token { + address: ResourceAddress, + // type_descriptor: TypeDescriptor, + token_ids: Vec, + }, +} + +pub trait ResourceTypeDescriptor { + fn type_descriptor(&self) -> TypeDescriptor; +} + +// The thinking here, that a resource address + a "local" type id together can used to validate type safety of the +// resources at runtime. The local type id can be defined as a unique id within the scope of the contract. We'll have to +// get further to see if this can work or is even needed. +#[derive(Debug, Clone, Encode, Decode, serde::Deserialize)] +pub struct TypeDescriptor { + type_id: u16, +} diff --git a/dan_layer/engine/src/models/vault.rs b/dan_layer/engine/src/models/vault.rs index dfa72ee415..a9ba3cd941 100755 --- a/dan_layer/engine/src/models/vault.rs +++ b/dan_layer/engine/src/models/vault.rs @@ -19,16 +19,18 @@ // 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; -pub type VaultId = FixedHash; +use tari_template_abi::{Decode, Encode}; -#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)] -pub struct Vault {} +use crate::models::Resource; + +#[derive(Debug, Clone, Encode, Decode)] +pub struct Vault { + resource: Resource, +} impl Vault { - pub fn empty() -> Self { - Self {} + pub fn new(resource: Resource) -> Self { + Self { resource } } } diff --git a/dan_layer/engine/src/packager/mod.rs b/dan_layer/engine/src/packager/mod.rs index edc894fbf3..f671e0e2db 100644 --- a/dan_layer/engine/src/packager/mod.rs +++ b/dan_layer/engine/src/packager/mod.rs @@ -24,7 +24,7 @@ mod error; pub use error::PackageError; mod package; -pub use package::{Package, PackageBuilder, PackageId}; +pub use package::{Package, PackageBuilder}; mod module_loader; pub use module_loader::PackageModuleLoader; diff --git a/dan_layer/engine/src/packager/package.rs b/dan_layer/engine/src/packager/package.rs index f78128e0c0..e0fc3e8c83 100644 --- a/dan_layer/engine/src/packager/package.rs +++ b/dan_layer/engine/src/packager/package.rs @@ -24,7 +24,7 @@ use std::collections::HashMap; use digest::Digest; use rand::{rngs::OsRng, RngCore}; -use tari_common_types::types::FixedHash; +use tari_template_lib::models::PackageId; use crate::{ crypto, @@ -32,8 +32,6 @@ use crate::{ wasm::{LoadedWasmModule, WasmModule}, }; -pub type PackageId = FixedHash; - #[derive(Debug, Clone)] pub struct Package { id: PackageId, @@ -85,8 +83,9 @@ impl PackageBuilder { fn new_package_id() -> PackageId { let v = OsRng.next_u32(); - crypto::hasher("package") + let hash: [u8; 32] = crypto::hasher("package") // TODO: Proper package id .chain(&v.to_le_bytes()) - .finalize().into() + .finalize().into(); + hash.into() } diff --git a/dan_layer/engine/src/runtime.rs b/dan_layer/engine/src/runtime.rs index 80fa392cf8..9681464a0b 100644 --- a/dan_layer/engine/src/runtime.rs +++ b/dan_layer/engine/src/runtime.rs @@ -27,9 +27,12 @@ use std::{ }; use tari_common_types::types::FixedHash; -use tari_template_abi::LogLevel; +use tari_template_lib::{ + args::LogLevel, + models::{Component, ComponentId, ComponentInstance}, +}; -use crate::models::{Bucket, Component, ComponentId}; +use crate::{models::Bucket, state_store::StateStoreError}; #[derive(Clone)] pub struct Runtime { @@ -66,11 +69,17 @@ pub struct ChangeTracker { #[derive(Debug, thiserror::Error)] pub enum RuntimeError { - #[error("todo")] - Todo, + #[error("State DB error: {0}")] + StateDbError(#[from] anyhow::Error), + #[error("State storage error: {0}")] + StateStoreError(#[from] StateStoreError), + #[error("Component not found with id '{id}'")] + ComponentNotFound { id: ComponentId }, } pub trait RuntimeInterface: Send + Sync { fn emit_log(&self, level: LogLevel, message: &str); fn create_component(&self, component: Component) -> Result; + fn get_component(&self, component_id: &ComponentId) -> Result; + fn set_component_state(&self, component_id: &ComponentId, state: Vec) -> Result<(), RuntimeError>; } diff --git a/dan_layer/engine/src/state_store/memory.rs b/dan_layer/engine/src/state_store/memory.rs index e7bc07f2d1..6e2ef15da3 100644 --- a/dan_layer/engine/src/state_store/memory.rs +++ b/dan_layer/engine/src/state_store/memory.rs @@ -73,12 +73,20 @@ impl<'a> StateReader for MemoryTransaction> { fn get_state_raw(&self, key: &[u8]) -> Result>, StateStoreError> { Ok(self.pending.get(key).cloned().or_else(|| self.guard.get(key).cloned())) } + + fn exists(&self, key: &[u8]) -> Result { + Ok(self.pending.contains_key(key) || self.guard.contains_key(key)) + } } impl<'a> StateReader for MemoryTransaction> { fn get_state_raw(&self, key: &[u8]) -> Result>, StateStoreError> { Ok(self.pending.get(key).cloned().or_else(|| self.guard.get(key).cloned())) } + + fn exists(&self, key: &[u8]) -> Result { + Ok(self.pending.contains_key(key) || self.guard.contains_key(key)) + } } impl<'a> StateWriter for MemoryTransaction> { diff --git a/dan_layer/engine/src/state_store/mod.rs b/dan_layer/engine/src/state_store/mod.rs index 4ac589dc2b..9493aad4f1 100644 --- a/dan_layer/engine/src/state_store/mod.rs +++ b/dan_layer/engine/src/state_store/mod.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. -mod memory; +pub mod memory; use std::{error::Error, io}; @@ -49,6 +49,8 @@ pub trait StateReader { let value = value.map(|v| V::deserialize(&mut v.as_slice())).transpose()?; Ok(value) } + + fn exists(&self, key: &[u8]) -> Result; } pub trait StateWriter: StateReader { diff --git a/dan_layer/engine/src/wasm/compile.rs b/dan_layer/engine/src/wasm/compile.rs index 61b50e3fcc..560d2c9256 100644 --- a/dan_layer/engine/src/wasm/compile.rs +++ b/dan_layer/engine/src/wasm/compile.rs @@ -26,7 +26,7 @@ use cargo_toml::{Manifest, Product}; use super::module::WasmModule; -pub fn build_wasm_module_from_source>(package_dir: P) -> io::Result { +pub fn compile_template>(package_dir: P) -> io::Result { let status = Command::new("cargo") .current_dir(package_dir.as_ref()) .args(["build", "--target", "wasm32-unknown-unknown", "--release"]) diff --git a/dan_layer/engine/src/wasm/module.rs b/dan_layer/engine/src/wasm/module.rs index 904e148797..53f98bcd3c 100644 --- a/dan_layer/engine/src/wasm/module.rs +++ b/dan_layer/engine/src/wasm/module.rs @@ -110,6 +110,10 @@ impl LoadedWasmModule { &self.template.template_name } + pub fn template_def(&self) -> &TemplateDef { + &self.template + } + pub fn find_func_by_name(&self, function_name: &str) -> Option<&FunctionDef> { self.template.functions.iter().find(|f| f.name == *function_name) } diff --git a/dan_layer/engine/src/wasm/process.rs b/dan_layer/engine/src/wasm/process.rs index f9f901ffd4..70d3e3fbd5 100644 --- a/dan_layer/engine/src/wasm/process.rs +++ b/dan_layer/engine/src/wasm/process.rs @@ -23,7 +23,13 @@ use std::io; use borsh::{BorshDeserialize, BorshSerialize}; -use tari_template_abi::{decode, encode_into, encode_with_len, ops, CallInfo, CreateComponentArg, EmitLogArg, Type}; +use tari_template_abi::{decode, encode, encode_into, encode_with_len, CallInfo, Type}; +use tari_template_lib::{ + abi_context::AbiContext, + args::{CreateComponentArg, EmitLogArg, GetComponentArg, SetComponentStateArg}, + models::{Component, Contract, ContractAddress, Package, PackageId}, + ops, +}; use wasmer::{Function, Instance, Module, Store, Val, WasmerEnv}; use crate::{ @@ -43,17 +49,26 @@ pub struct Process { module: LoadedWasmModule, env: WasmEnv, instance: Instance, + package_id: PackageId, + contract_address: ContractAddress, } impl Process { - pub fn start(module: LoadedWasmModule, state: Runtime) -> Result { + pub fn start(module: LoadedWasmModule, state: Runtime, package_id: PackageId) -> 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 }) + Ok(Self { + module, + env, + instance, + package_id, + // TODO: + contract_address: ContractAddress::default(), + }) } fn alloc_and_write(&self, val: &T) -> Result { @@ -77,16 +92,31 @@ impl Process { 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()) + env.state().interface().create_component(Component { + contract_address: arg.contract_address, + package_id: arg.package_id, + module_name: arg.module_name, + state: arg.state, + }) + }), + ops::OP_GET_COMPONENT => Self::handle(env, arg, |env, arg: GetComponentArg| { + env.state().interface().get_component(&arg.component_id) + }), + ops::OP_SET_COMPONENT_STATE => Self::handle(env, arg, |env, arg: SetComponentStateArg| { + env.state() + .interface() + .set_component_state(&arg.component_id, arg.state) }), _ => Err(WasmExecutionError::InvalidOperation { op }), }; + result.unwrap_or_else(|err| { log::error!(target: LOG_TARGET, "{}", err); 0 @@ -112,6 +142,16 @@ impl Process { // out-of-bounds access error. Ok(ptr.as_i32()) } + + fn encoded_abi_context(&self) -> Vec { + encode(&AbiContext { + package: Package { id: self.package_id }, + contract: Contract { + address: self.contract_address, + }, + }) + .unwrap() + } } impl Invokable for Process { @@ -124,6 +164,7 @@ impl Invokable for Process { .ok_or_else(|| WasmExecutionError::FunctionNotFound { name: name.into() })?; let call_info = CallInfo { + abi_context: self.encoded_abi_context(), func_name: func_def.name.clone(), args, }; diff --git a/dan_layer/engine/tests/hello_world/Cargo.lock b/dan_layer/engine/tests/hello_world/Cargo.lock index 7d65fd86d2..e3375cdea0 100644 --- a/dan_layer/engine/tests/hello_world/Cargo.lock +++ b/dan_layer/engine/tests/hello_world/Cargo.lock @@ -160,6 +160,7 @@ dependencies = [ name = "tari_template_lib" version = "0.1.0" dependencies = [ + "borsh", "tari_template_abi", ] @@ -170,8 +171,6 @@ dependencies = [ "proc-macro2", "quote", "syn", - "tari_template_abi", - "tari_template_lib", ] [[package]] diff --git a/dan_layer/engine/tests/hello_world/src/lib.rs b/dan_layer/engine/tests/hello_world/src/lib.rs index 4d96b755de..95d4bc8a11 100644 --- a/dan_layer/engine/tests/hello_world/src/lib.rs +++ b/dan_layer/engine/tests/hello_world/src/lib.rs @@ -24,11 +24,21 @@ use tari_template_macros::template; #[template] mod hello_world { - struct HelloWorld {} + struct HelloWorld { + greeting: String, + } impl HelloWorld { pub fn greet() -> String { "Hello World!".to_string() } + + pub fn new(greeting: String) -> Self { + Self { greeting } + } + + pub fn custom_greeting(&self, name: String) -> String { + format!("{} {}!", self.greeting, name) + } } } diff --git a/dan_layer/engine/tests/mock_runtime_interface.rs b/dan_layer/engine/tests/mock_runtime_interface.rs index c81591814b..04afb47e18 100644 --- a/dan_layer/engine/tests/mock_runtime_interface.rs +++ b/dan_layer/engine/tests/mock_runtime_interface.rs @@ -20,34 +20,59 @@ // 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 std::sync::{atomic::AtomicU32, Arc, RwLock}; -use tari_dan_common_types::Hash; +use digest::Digest; use tari_dan_engine::{ - models::{Component, ComponentId}, + crypto, runtime::{RuntimeError, RuntimeInterface}, + state_store::{memory::MemoryStateStore, AtomicDb, StateReader, StateWriter}, +}; +use tari_template_lib::{ + args::LogLevel, + models::{Component, ComponentId, ComponentInstance}, }; -use tari_template_abi::LogLevel; #[derive(Debug, Clone, Default)] pub struct MockRuntimeInterface { ids: Arc, + state: MemoryStateStore, + calls: Arc>>, } impl MockRuntimeInterface { pub fn new() -> Self { Self { ids: Arc::new(AtomicU32::new(0)), + state: MemoryStateStore::default(), + calls: Arc::new(RwLock::new(vec![])), } } + pub fn state_store(&self) -> MemoryStateStore { + self.state.clone() + } + pub fn next_id(&self) -> u32 { self.ids.fetch_add(1, std::sync::atomic::Ordering::Relaxed) } + + pub fn get_calls(&self) -> Vec<&'static str> { + self.calls.read().unwrap().clone() + } + + pub fn clear_calls(&self) { + self.calls.write().unwrap().clear(); + } + + fn add_call(&self, call: &'static str) { + self.calls.write().unwrap().push(call); + } } impl RuntimeInterface for MockRuntimeInterface { fn emit_log(&self, level: LogLevel, message: &str) { + self.add_call("emit_log"); let level = match level { LogLevel::Error => log::Level::Error, LogLevel::Warn => log::Level::Warn, @@ -58,7 +83,42 @@ impl RuntimeInterface for MockRuntimeInterface { log::log!(target: "tari::dan::engine::runtime", level, "{}", message); } - fn create_component(&self, _new_component: Component) -> Result { - Ok((Hash::default(), self.next_id())) + fn create_component(&self, new_component: Component) -> Result { + self.add_call("create_component"); + let component_id: [u8; 32] = crypto::hasher("component") + .chain(self.next_id().to_le_bytes()) + .finalize() + .into(); + + let component = ComponentInstance::new(component_id.into(), new_component); + let mut tx = self.state.write_access().map_err(RuntimeError::StateDbError)?; + tx.set_state(&component_id, component)?; + self.state.commit(tx).map_err(RuntimeError::StateDbError)?; + + Ok(component_id.into()) + } + + fn get_component(&self, component_id: &ComponentId) -> Result { + self.add_call("get_component"); + let component = self + .state + .read_access() + .map_err(RuntimeError::StateDbError)? + .get_state(component_id)? + .ok_or(RuntimeError::ComponentNotFound { id: *component_id })?; + Ok(component) + } + + fn set_component_state(&self, component_id: &ComponentId, state: Vec) -> Result<(), RuntimeError> { + self.add_call("set_component_state"); + let mut tx = self.state.write_access().map_err(RuntimeError::StateDbError)?; + let mut component: ComponentInstance = tx + .get_state(component_id)? + .ok_or(RuntimeError::ComponentNotFound { id: *component_id })?; + component.state = state; + tx.set_state(&component_id, component)?; + self.state.commit(tx).map_err(RuntimeError::StateDbError)?; + + Ok(()) } } diff --git a/dan_layer/engine/tests/state/Cargo.lock b/dan_layer/engine/tests/state/Cargo.lock index f89e31465c..659d755fe3 100644 --- a/dan_layer/engine/tests/state/Cargo.lock +++ b/dan_layer/engine/tests/state/Cargo.lock @@ -107,9 +107,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] @@ -160,6 +160,7 @@ dependencies = [ name = "tari_template_lib" version = "0.1.0" dependencies = [ + "borsh", "tari_template_abi", ] @@ -170,8 +171,6 @@ dependencies = [ "proc-macro2", "quote", "syn", - "tari_template_abi", - "tari_template_lib", ] [[package]] diff --git a/dan_layer/engine/tests/state/src/lib.rs b/dan_layer/engine/tests/state/src/lib.rs index a8ed6e3f17..de90eee6f4 100644 --- a/dan_layer/engine/tests/state/src/lib.rs +++ b/dan_layer/engine/tests/state/src/lib.rs @@ -34,6 +34,7 @@ mod state_template { } pub fn set(&mut self, value: u32) { + debug(format!("Changing value from {} to {}", self.value, value)); self.value = value; } diff --git a/dan_layer/engine/tests/test.rs b/dan_layer/engine/tests/test.rs index df7468e79a..d53deb69c1 100644 --- a/dan_layer/engine/tests/test.rs +++ b/dan_layer/engine/tests/test.rs @@ -22,83 +22,159 @@ mod mock_runtime_interface; +use std::path::Path; + use borsh::BorshDeserialize; use mock_runtime_interface::MockRuntimeInterface; -use tari_common_types::types::FixedHash; use tari_crypto::ristretto::RistrettoSecretKey; use tari_dan_engine::{ crypto::create_key_pair, instruction::{Instruction, InstructionBuilder, InstructionProcessor}, - models::ComponentId, packager::Package, - wasm::compile::build_wasm_module_from_source, + state_store::{memory::MemoryStateStore, AtomicDb, StateReader}, + wasm::{compile::compile_template, LoadedWasmModule}, +}; +use tari_template_lib::{ + args, + models::{ComponentId, ComponentInstance}, }; -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.call_function("greet".to_string(), vec![]); + let template_test = TemplateTest::new(vec!["tests/hello_world"]); + let result: String = template_test.call_function("HelloWorld", "greet", args![]); - // 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!")); + assert_eq!(result, "Hello World!"); } #[test] fn test_state() { - // TODO: use the Component and ComponentId types in the template - let template_test = TemplateTest::new("State".to_string(), "tests/state".to_string()); + let template_test = TemplateTest::new(vec!["tests/state"]); + let store = template_test.state_store(); // constructor - let component: ComponentId = template_test.call_function("new".to_string(), vec![]); + let component_id1: ComponentId = template_test.call_function("State", "new", args![]); + template_test.assert_calls(&["emit_log", "create_component"]); + template_test.clear_calls(); + + let component_id2: ComponentId = template_test.call_function("State", "new", args![]); + assert_ne!(component_id1, component_id2); + + let component: ComponentInstance = store + .read_access() + .unwrap() + .get_state(&component_id1) + .unwrap() + .expect("component1 not found"); + assert_eq!(component.module_name, "State"); + let component: ComponentInstance = store + .read_access() + .unwrap() + .get_state(&component_id2) + .unwrap() + .expect("component2 not found"); + assert_eq!(component.module_name, "State"); // call the "set" method to update the instance value let new_value = 20_u32; - template_test.call_method::<()>("State".to_string(), "set".to_string(), vec![ - encode_with_len(&component), - encode_with_len(&new_value), - ]); + template_test.call_method::<()>(component_id2, "set", args![new_value]); // call the "get" method to get the current value - let value: u32 = template_test.call_method("State".to_string(), "get".to_string(), vec![encode_with_len( - &component, - )]); - // TODO: when state storage is implemented in the engine, assert the previous setted value (20_u32) - assert_eq!(value, 0); + let value: u32 = template_test.call_method(component_id2, "get", args![]); + + assert_eq!(value, new_value); +} + +#[test] +fn test_composed() { + let template_test = TemplateTest::new(vec!["tests/state", "tests/hello_world"]); + + let functions = template_test + .get_module("HelloWorld") + .template_def() + .functions + .iter() + .map(|f| f.name.as_str()) + .collect::>(); + assert_eq!(functions, vec!["greet", "new", "custom_greeting"]); + + let functions = template_test + .get_module("State") + .template_def() + .functions + .iter() + .map(|f| f.name.as_str()) + .collect::>(); + assert_eq!(functions, vec!["new", "set", "get"]); + + let component_state: ComponentId = template_test.call_function("State", "new", args![]); + let component_hw: ComponentId = template_test.call_function("HelloWorld", "new", args!["أهلا"]); + + let result: String = template_test.call_method(component_hw, "custom_greeting", args!["Wasm"]); + assert_eq!(result, "أهلا Wasm!"); + + // call the "set" method to update the instance value + let new_value = 20_u32; + template_test.call_method::<()>(component_state, "set", args![new_value]); + + // call the "get" method to get the current value + let value: u32 = template_test.call_method(component_state, "get", args![]); + + assert_eq!(value, new_value); } struct TemplateTest { - template_name: String, - package_id: FixedHash, + package: Package, processor: InstructionProcessor, secret_key: RistrettoSecretKey, + runtime_interface: MockRuntimeInterface, } impl TemplateTest { - pub fn new(template_name: String, template_path: String) -> Self { - let mut processor = InstructionProcessor::new(MockRuntimeInterface::new()); + pub fn new>(template_paths: Vec

) -> Self { + let runtime_interface = MockRuntimeInterface::new(); let (secret_key, _pk) = create_key_pair(); - 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); + let wasms = template_paths.into_iter().map(|path| compile_template(path).unwrap()); + let mut builder = Package::builder(); + for wasm in wasms { + builder.add_wasm_module(wasm); + } + let package = builder.build().unwrap(); + let processor = InstructionProcessor::new(runtime_interface.clone(), package.clone()); Self { - template_name, - package_id, + package, processor, secret_key, + runtime_interface, } } - pub fn call_function(&self, func_name: String, args: Vec>) -> T + pub fn state_store(&self) -> MemoryStateStore { + self.runtime_interface.state_store() + } + + pub fn assert_calls(&self, expected: &[&'static str]) { + let calls = self.runtime_interface.get_calls(); + assert_eq!(calls, expected); + } + + pub fn clear_calls(&self) { + self.runtime_interface.clear_calls(); + } + + pub fn get_module(&self, module_name: &str) -> &LoadedWasmModule { + self.package.get_module_by_name(module_name).unwrap() + } + + pub fn call_function(&self, template_name: &str, func_name: &str, args: Vec>) -> T where T: BorshDeserialize { let instruction = InstructionBuilder::new() .add_instruction(Instruction::CallFunction { - package_id: self.package_id, - template: self.template_name.clone(), - function: func_name, + package_id: self.package.id(), + template: template_name.to_owned(), + function: func_name.to_owned(), args, }) .sign(&self.secret_key) @@ -108,13 +184,13 @@ impl TemplateTest { result[0].decode::().unwrap() } - pub fn call_method(&self, component_id: String, method_name: String, args: Vec>) -> T + pub fn call_method(&self, component_id: ComponentId, method_name: &str, args: Vec>) -> T where T: BorshDeserialize { let instruction = InstructionBuilder::new() .add_instruction(Instruction::CallMethod { - package_id: self.package_id, + package_id: self.package.id(), component_id, - method: method_name, + method: method_name.to_owned(), args, }) .sign(&self.secret_key) diff --git a/dan_layer/storage_lmdb/src/engine_state_store.rs b/dan_layer/storage_lmdb/src/engine_state_store.rs index 3799332383..365c17008e 100644 --- a/dan_layer/storage_lmdb/src/engine_state_store.rs +++ b/dan_layer/storage_lmdb/src/engine_state_store.rs @@ -91,6 +91,10 @@ impl<'a, T: Deref>> StateReader for LmdbTransactio .to_opt() .map_err(StateStoreError::custom) } + + fn exists(&self, key: &[u8]) -> Result { + Ok(self.get_state_raw(key)?.is_some()) + } } impl<'a> StateWriter for LmdbTransaction> { @@ -128,6 +132,7 @@ mod tests { { let mut access = store.write_access().unwrap(); access.set_state(b"abc", user_data.clone()).unwrap(); + assert!(access.exists(b"abc").unwrap()); let res = access.get_state(b"abc").unwrap(); assert_eq!(res, Some(user_data.clone())); let res = access.get_state::<_, UserData>(b"def").unwrap(); @@ -139,6 +144,7 @@ mod tests { let access = store.read_access().unwrap(); let res = access.get_state::<_, UserData>(b"abc").unwrap(); assert_eq!(res, None); + assert!(!access.exists(b"abc").unwrap()); } { diff --git a/dan_layer/storage_sqlite/src/engine_state_store.rs b/dan_layer/storage_sqlite/src/engine_state_store.rs index d2af765eb4..7477aa403e 100644 --- a/dan_layer/storage_sqlite/src/engine_state_store.rs +++ b/dan_layer/storage_sqlite/src/engine_state_store.rs @@ -110,6 +110,23 @@ impl<'a> StateReader for SqliteTransaction<'a> { Ok(val) } + + fn exists(&self, key: &[u8]) -> Result { + use crate::schema::metadata::dsl; + let val = dsl::metadata + .count() + .filter(metadata::key.eq(key)) + .limit(1) + .first::(self.conn) + .map_err(|source| { + StateStoreError::custom(SqliteStorageError::DieselError { + source, + operation: "get state".to_string(), + }) + })?; + + Ok(val > 0) + } } impl<'a> StateWriter for SqliteTransaction<'a> { @@ -177,6 +194,7 @@ mod tests { access.set_state(b"abc", user_data.clone()).unwrap(); let res = access.get_state(b"abc").unwrap(); assert_eq!(res, Some(user_data.clone())); + assert!(access.exists(b"abc").unwrap()); let res = access.get_state::<_, UserData>(b"def").unwrap(); assert_eq!(res, None); // Drop without commit rolls back @@ -186,6 +204,7 @@ mod tests { let access = store.read_access().unwrap(); let res = access.get_state::<_, UserData>(b"abc").unwrap(); assert_eq!(res, None); + assert!(!access.exists(b"abc").unwrap()); } { diff --git a/dan_layer/template_abi/src/abi.rs b/dan_layer/template_abi/src/abi.rs new file mode 100644 index 0000000000..a485c77b30 --- /dev/null +++ b/dan_layer/template_abi/src/abi.rs @@ -0,0 +1,112 @@ +// 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. + +// 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 crate::{decode, decode_len, encode_into, encode_with_len, Decode, Encode, FunctionDef, TemplateDef}; + +pub fn generate_abi(template_name: String, functions: Vec) -> *mut u8 { + let template = TemplateDef { + template_name, + functions, + }; + + let buf = encode_with_len(&template); + wrap_ptr(buf) +} + +extern "C" { + pub fn tari_engine(op: i32, input_ptr: *const u8, input_len: usize) -> *mut u8; + pub fn debug(input_ptr: *const u8, input_len: usize); +} + +type FunctionImpl = Box>) -> Vec>; + +#[derive(Default)] +pub struct TemplateImpl(HashMap); + +impl TemplateImpl { + pub fn new() -> Self { + Self(HashMap::new()) + } + + pub fn add_function(&mut self, name: String, implementation: FunctionImpl) { + self.0.insert(name, implementation); + } +} + +pub fn wrap_ptr(mut v: Vec) -> *mut u8 { + let ptr = v.as_mut_ptr(); + mem::forget(v); + ptr +} + +pub fn call_engine(op: i32, input: &T) -> Option { + 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 call_debug>(data: T) { + let ptr = data.as_ref().as_ptr(); + let len = data.as_ref().len(); + unsafe { debug(ptr, len) } +} + +/// Allocates a block of memory of length `len` bytes. +#[no_mangle] +pub extern "C" fn tari_alloc(len: u32) -> *mut u8 { + let cap = (len + 4) as usize; + let mut buf = Vec::::with_capacity(cap); + let ptr = buf.as_mut_ptr(); + mem::forget(buf); + unsafe { + copy(len.to_le_bytes().as_ptr(), ptr, 4); + } + ptr +} + +/// Frees a block of memory allocated by `tari_alloc`. +/// +/// # Safety +/// Caller must ensure that ptr must be a valid pointer to a block of memory allocated by `tari_alloc`. +#[no_mangle] +pub unsafe extern "C" fn tari_free(ptr: *mut u8) { + let mut len = [0u8; 4]; + copy(ptr, len.as_mut_ptr(), 4); + + let cap = (u32::from_le_bytes(len) + 4) as usize; + drop(Vec::::from_raw_parts(ptr, cap, cap)); +} diff --git a/dan_layer/template_abi/src/lib.rs b/dan_layer/template_abi/src/lib.rs index ddb74cdea0..12b9d56be5 100644 --- a/dan_layer/template_abi/src/lib.rs +++ b/dan_layer/template_abi/src/lib.rs @@ -25,76 +25,12 @@ //! 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; +mod abi; +pub use abi::*; +pub use borsh::{BorshDeserialize as Decode, BorshSerialize as Encode}; -pub use borsh::{self, BorshDeserialize as Decode, BorshSerialize as Encode}; +mod encoding; pub use encoding::{decode, decode_len, encode, encode_into, encode_with_len}; -#[derive(Debug, Clone, Encode, Decode)] -pub struct TemplateDef { - pub template_name: String, - pub functions: Vec, -} - -impl TemplateDef { - pub fn get_function(&self, name: &str) -> Option<&FunctionDef> { - self.functions.iter().find(|f| f.name.as_str() == name) - } -} - -#[derive(Debug, Clone, Encode, Decode)] -pub struct FunctionDef { - pub name: String, - pub arguments: Vec, - pub output: Type, -} - -#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] -pub enum Type { - Unit, - Bool, - I8, - I16, - I32, - I64, - I128, - U8, - U16, - U32, - U64, - U128, - String, -} - -#[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, -} +mod types; +pub use types::*; diff --git a/dan_layer/template_abi/src/types.rs b/dan_layer/template_abi/src/types.rs new file mode 100644 index 0000000000..dcd1726051 --- /dev/null +++ b/dan_layer/template_abi/src/types.rs @@ -0,0 +1,66 @@ +// 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::{Decode, Encode}; + +#[derive(Debug, Clone, Encode, Decode)] +pub struct TemplateDef { + pub template_name: String, + pub functions: Vec, +} + +impl TemplateDef { + pub fn get_function(&self, name: &str) -> Option<&FunctionDef> { + self.functions.iter().find(|f| f.name.as_str() == name) + } +} + +#[derive(Debug, Clone, Encode, Decode)] +pub struct FunctionDef { + pub name: String, + pub arguments: Vec, + pub output: Type, +} + +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] +pub enum Type { + Unit, + Bool, + I8, + I16, + I32, + I64, + I128, + U8, + U16, + U32, + U64, + U128, + String, +} + +#[derive(Debug, Clone, Encode, Decode)] +pub struct CallInfo { + pub func_name: String, + pub args: Vec>, + pub abi_context: Vec, +} diff --git a/dan_layer/template_lib/Cargo.toml b/dan_layer/template_lib/Cargo.toml index 41e823a992..950f49bc2d 100644 --- a/dan_layer/template_lib/Cargo.toml +++ b/dan_layer/template_lib/Cargo.toml @@ -3,17 +3,8 @@ 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 + +borsh = "0.9.3" +serde = { version = "1.0.143", optional = true } diff --git a/dan_layer/template_lib/src/abi_context.rs b/dan_layer/template_lib/src/abi_context.rs new file mode 100644 index 0000000000..c0ec3c82cd --- /dev/null +++ b/dan_layer/template_lib/src/abi_context.rs @@ -0,0 +1,31 @@ +// 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 tari_template_abi::{Decode, Encode}; + +use crate::models::{Contract, Package}; + +#[derive(Debug, Decode, Encode)] +pub struct AbiContext { + pub package: Package, + pub contract: Contract, +} diff --git a/dan_layer/template_lib/src/args.rs b/dan_layer/template_lib/src/args.rs new file mode 100644 index 0000000000..b95e5a5524 --- /dev/null +++ b/dan_layer/template_lib/src/args.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 tari_template_abi::{Decode, Encode}; + +use crate::models::{ComponentId, ContractAddress, PackageId}; + +#[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 { + pub contract_address: ContractAddress, + pub module_name: String, + pub package_id: PackageId, + pub state: Vec, +} + +#[derive(Debug, Clone, Encode, Decode)] +pub struct GetComponentArg { + pub component_id: ComponentId, +} + +#[derive(Debug, Clone, Encode, Decode)] +pub struct SetComponentStateArg { + pub component_id: ComponentId, + pub state: Vec, +} + +#[macro_export] +macro_rules! __template_lib_count { + () => (0usize); + ( $x:tt $($next:tt)* ) => (1usize + $crate::__template_lib_count!($($next)*)); +} + +#[macro_export] +macro_rules! args { + () => (Vec::new()); + + ($($args:expr),+) => {{ + let mut args = Vec::with_capacity($crate::__template_lib_count!($($args),+)); + $( + args.push(tari_template_abi::encode(&$args).unwrap()); + )+ + args + }} +} diff --git a/dan_layer/template_lib/src/context.rs b/dan_layer/template_lib/src/context.rs new file mode 100644 index 0000000000..5553cf9e74 --- /dev/null +++ b/dan_layer/template_lib/src/context.rs @@ -0,0 +1,61 @@ +// 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::{borrow::Borrow, cell::RefCell}; + +use tari_template_abi::{decode, CallInfo}; + +use crate::{ + abi_context::AbiContext, + models::{Contract, Package}, +}; + +thread_local! { + static CONTEXT: RefCell> = RefCell::new(None); +} + +pub fn set_context_from_call_info(call_info: &CallInfo) { + let abi_context = decode(&call_info.abi_context).expect("Failed to decode ABI context"); + with_context(|ctx| { + *ctx = Some(abi_context); + }); +} + +pub fn with_context) -> R>(f: F) -> R { + CONTEXT.borrow().with(|c| f(&mut c.borrow_mut())) +} + +pub fn get_context() -> Context { + Context +} + +#[derive(Debug, Default)] +pub struct Context; +impl Context { + pub fn package(&self) -> Package { + with_context(|ctx| ctx.as_ref().unwrap().package.clone()) + } + + pub fn contract(&self) -> Contract { + with_context(|ctx| ctx.as_ref().unwrap().contract.clone()) + } +} diff --git a/dan_layer/template_lib/src/engine.rs b/dan_layer/template_lib/src/engine.rs new file mode 100644 index 0000000000..111c49e946 --- /dev/null +++ b/dan_layer/template_lib/src/engine.rs @@ -0,0 +1,82 @@ +// 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 tari_template_abi::{call_engine, decode, encode, Decode, Encode}; + +use crate::{ + args::{CreateComponentArg, EmitLogArg, GetComponentArg, LogLevel, SetComponentStateArg}, + context::Context, + get_context, + models::{Component, ComponentId}, + ops::*, +}; + +pub fn engine() -> TariEngine { + // TODO: I expect some thread local state to be included here + TariEngine::new(get_context()) +} + +#[derive(Debug, Default)] +pub struct TariEngine { + context: Context, +} + +impl TariEngine { + fn new(context: Context) -> Self { + Self { context } + } + + pub fn instantiate(&self, template_name: String, initial_state: T) -> ComponentId { + let encoded_state = encode(&initial_state).unwrap(); + + // Call the engine to create a new component + // TODO: proper component id + // TODO: what happens if the user wants to return multiple components/types? + let component_id = call_engine::<_, ComponentId>(OP_CREATE_COMPONENT, &CreateComponentArg { + contract_address: *self.context.contract().address(), + module_name: template_name, + state: encoded_state, + package_id: *self.context.package().id(), + }); + component_id.expect("no asset id returned") + } + + pub fn emit_log>(&self, level: LogLevel, msg: T) { + call_engine::<_, ()>(OP_EMIT_LOG, &EmitLogArg { + level, + message: msg.into(), + }); + } + + /// Get the component state + pub fn get_component_state(&self, component_id: ComponentId) -> T { + let component = call_engine::<_, Component>(OP_GET_COMPONENT, &GetComponentArg { component_id }) + .expect("Component not found"); + + decode(&component.state).expect("Failed to decode component state") + } + + pub fn set_component_state(&self, component_id: ComponentId, state: T) { + let state = encode(&state).unwrap(); + call_engine::<_, ()>(OP_SET_COMPONENT_STATE, &SetComponentStateArg { component_id, state }); + } +} diff --git a/dan_layer/common_types/src/hash.rs b/dan_layer/template_lib/src/hash.rs similarity index 57% rename from dan_layer/common_types/src/hash.rs rename to dan_layer/template_lib/src/hash.rs index e6d7897832..ddc3bbb869 100644 --- a/dan_layer/common_types/src/hash.rs +++ b/dan_layer/template_lib/src/hash.rs @@ -20,38 +20,88 @@ // 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 std::{ + error::Error, + fmt::{Display, Formatter}, + io, + io::Write, + ops::Deref, +}; -use borsh::{BorshDeserialize, BorshSerialize}; -use tari_common_types::types::FixedHash; +use tari_template_abi::{Decode, Encode}; -// 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); +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize))] +pub struct Hash([u8; 32]); impl Hash { - pub fn into_inner(self) -> FixedHash { + pub fn into_inner(self) -> [u8; 32] { self.0 } + + pub fn from_hex(s: &str) -> Result { + if s.len() != 64 { + return Err(HashParseError); + } + + let mut hash = [0u8; 32]; + for (i, h) in hash.iter_mut().enumerate() { + *h = u8::from_str_radix(&s[2 * i..2 * (i + 1)], 16).map_err(|_| HashParseError)?; + } + Ok(Hash(hash)) + } } -impl From for Hash { - fn from(hash: FixedHash) -> Self { +impl AsRef<[u8]> for Hash { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl From<[u8; 32]> for Hash { + fn from(hash: [u8; 32]) -> Self { Self(hash) } } -impl BorshSerialize for Hash { +impl Deref for Hash { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Encode for Hash { fn serialize(&self, writer: &mut W) -> io::Result<()> { - (*self.0).serialize(writer) + self.0.serialize(writer) } } -impl BorshDeserialize for Hash { +impl Decode for Hash { fn deserialize(buf: &mut &[u8]) -> io::Result { - let hash = <[u8; 32] as BorshDeserialize>::deserialize(buf)?; - Ok(Hash(hash.into())) + let hash = <[u8; 32] as Decode>::deserialize(buf)?; + Ok(Hash(hash)) + } +} + +impl Display for Hash { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + for x in self.0 { + write!(f, "{:02x?}", x)?; + } + Ok(()) + } +} + +#[derive(Debug)] +pub struct HashParseError; + +impl Error for HashParseError {} + +impl Display for HashParseError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Failed to parse hash") } } diff --git a/dan_layer/template_lib/src/lib.rs b/dan_layer/template_lib/src/lib.rs index f178a71471..7da73f2e78 100644 --- a/dan_layer/template_lib/src/lib.rs +++ b/dan_layer/template_lib/src/lib.rs @@ -19,133 +19,28 @@ // 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 mod abi_context; -//! # 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 +mod hash; +pub use hash::Hash; +#[macro_use] +pub mod args; pub mod models; +pub mod ops; -// 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}; +// ---------------------------------------- WASM target exports ------------------------------------------------ -use tari_template_abi::{encode_with_len, Decode, Encode, FunctionDef, TemplateDef}; +#[cfg(target_arch = "wasm32")] +pub mod template_dependencies; -pub fn generate_abi(template_name: String, functions: Vec) -> *mut u8 { - let template = TemplateDef { - template_name, - functions, - }; +#[cfg(target_arch = "wasm32")] +mod context; +#[cfg(target_arch = "wasm32")] +pub use context::{get_context, set_context_from_call_info}; - let buf = encode_with_len(&template); - wrap_ptr(buf) -} +#[cfg(target_arch = "wasm32")] +mod engine; -type FunctionImpl = Box>) -> Vec>; - -#[derive(Default)] -pub struct TemplateImpl(HashMap); - -impl TemplateImpl { - pub fn new() -> Self { - Self(HashMap::new()) - } - - pub fn add_function(&mut self, name: String, implementation: FunctionImpl) { - self.0.insert(name, implementation); - } -} - -/// Generate main -/// -/// # Safety -/// The caller must provide a valid pointer and length. -pub unsafe 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 = 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, - None => panic!("invalid function name"), - }; - - // call the function - let result = function(call_info.args); - - // return the encoded results of the function call - 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) } -} - -/// Allocates a block of memory of length `len` bytes. -#[no_mangle] -pub extern "C" fn tari_alloc(len: u32) -> *mut u8 { - let cap = (len + 4) as usize; - let mut buf = Vec::::with_capacity(cap); - let ptr = buf.as_mut_ptr(); - mem::forget(buf); - unsafe { - copy(len.to_le_bytes().as_ptr(), ptr, 4); - } - ptr -} - -/// Frees a block of memory allocated by `tari_alloc`. -/// -/// # Safety -/// Caller must ensure that ptr must be a valid pointer to a block of memory allocated by `tari_alloc`. -#[no_mangle] -pub unsafe extern "C" fn tari_free(ptr: *mut u8) { - let mut len = [0u8; 4]; - copy(ptr, len.as_mut_ptr(), 4); - - let cap = (u32::from_le_bytes(len) + 4) as usize; - drop(Vec::::from_raw_parts(ptr, cap, cap)); -} +#[cfg(target_arch = "wasm32")] +pub use engine::engine; diff --git a/dan_layer/template_lib/src/models/bucket.rs b/dan_layer/template_lib/src/models/bucket.rs new file mode 100644 index 0000000000..ebd2b46180 --- /dev/null +++ b/dan_layer/template_lib/src/models/bucket.rs @@ -0,0 +1,36 @@ +// 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::marker::PhantomData; + +pub type BucketId = u32; + +pub struct Bucket { + id: BucketId, + _t: PhantomData, +} + +impl Bucket { + pub fn id(&self) -> BucketId { + self.id + } +} diff --git a/dan_layer/template_lib/src/models/component.rs b/dan_layer/template_lib/src/models/component.rs index 585f90fc8b..933afb5628 100644 --- a/dan_layer/template_lib/src/models/component.rs +++ b/dan_layer/template_lib/src/models/component.rs @@ -20,42 +20,41 @@ // 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: use the actual component id type -pub type ComponentId = ([u8; 32], u32); +use tari_template_abi::{Decode, Encode}; -use tari_template_abi::{encode_with_len, ops::OP_CREATE_COMPONENT, CreateComponentArg, Decode, Encode}; +use crate::models::{ContractAddress, PackageId}; -use crate::call_engine; +pub type ComponentId = crate::Hash; -pub fn initialise(template_name: String, initial_state: T) -> ComponentId { - let encoded_state = encode_with_len(&initial_state); - - // Call the engine to create a new component - // TODO: proper component id - // TODO: what happens if the user wants to return multiple components/types? - let component_id = call_engine::<_, ComponentId>(OP_CREATE_COMPONENT, &CreateComponentArg { - name: template_name, - quantity: 1, - metadata: Default::default(), - state: encoded_state, - }); - component_id.expect("no asset id returned") +#[derive(Debug, Clone, Encode, Decode)] +pub struct ComponentInstance { + pub component_id: ComponentId, + pub contract_address: ContractAddress, + pub package_id: PackageId, + pub module_name: String, + pub state: Vec, } -pub fn get_state(_id: u32) -> T { - // get the component state - // TODO: use a real op code (not "123") when they are implemented - let _state = call_engine::<_, ()>(123, &()); - - // create and return a mock state because state is not implemented yet in the engine - let len = std::mem::size_of::(); - let byte_vec = vec![0_u8; len]; - let mut mock_value = byte_vec.as_slice(); - T::deserialize(&mut mock_value).unwrap() +impl ComponentInstance { + pub fn new(component_id: ComponentId, component: Component) -> Self { + Self { + component_id, + contract_address: component.contract_address, + package_id: component.package_id, + module_name: component.module_name, + state: component.state, + } + } + + pub fn id(&self) -> ComponentId { + self.component_id + } } -pub fn set_state(_id: u32, _state: T) { - // update the component value - // TODO: use a real op code (not "123") when they are implemented - call_engine::<_, ()>(123, &()); +#[derive(Debug, Clone, Encode, Decode)] +pub struct Component { + pub contract_address: ContractAddress, + pub package_id: PackageId, + pub module_name: String, + pub state: Vec, } diff --git a/dan_layer/template_lib/src/models/contract.rs b/dan_layer/template_lib/src/models/contract.rs new file mode 100644 index 0000000000..a03e25444e --- /dev/null +++ b/dan_layer/template_lib/src/models/contract.rs @@ -0,0 +1,40 @@ +// 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 tari_template_abi::{Decode, Encode}; + +pub type ContractAddress = crate::Hash; + +#[derive(Debug, Clone, Encode, Decode)] +pub struct Contract { + pub address: ContractAddress, +} + +impl Contract { + pub fn new(address: ContractAddress) -> Self { + Self { address } + } + + pub fn address(&self) -> &ContractAddress { + &self.address + } +} diff --git a/dan_layer/template_lib/src/models/mod.rs b/dan_layer/template_lib/src/models/mod.rs index a2237b672d..b6aaf2bb08 100644 --- a/dan_layer/template_lib/src/models/mod.rs +++ b/dan_layer/template_lib/src/models/mod.rs @@ -20,5 +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. +mod bucket; +pub use bucket::{Bucket, BucketId}; + mod component; pub use component::*; + +mod contract; +pub use contract::{Contract, ContractAddress}; + +mod resource; +pub use resource::ResourceAddress; + +mod package; +pub use package::{Package, PackageId}; + +mod vault; +pub use vault::Vault; diff --git a/dan_layer/template_lib/src/models/package.rs b/dan_layer/template_lib/src/models/package.rs new file mode 100644 index 0000000000..c176111549 --- /dev/null +++ b/dan_layer/template_lib/src/models/package.rs @@ -0,0 +1,42 @@ +// 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 tari_template_abi::{Decode, Encode}; + +use crate::Hash; + +pub type PackageId = Hash; + +#[derive(Debug, Clone, Encode, Decode)] +pub struct Package { + pub id: PackageId, +} + +impl Package { + pub fn new(id: PackageId) -> Self { + Self { id } + } + + pub fn id(&self) -> &PackageId { + &self.id + } +} diff --git a/dan_layer/template_lib/src/models/resource.rs b/dan_layer/template_lib/src/models/resource.rs new file mode 100644 index 0000000000..41c3730e72 --- /dev/null +++ b/dan_layer/template_lib/src/models/resource.rs @@ -0,0 +1,55 @@ +// 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::marker::PhantomData; + +use crate::{hash::HashParseError, Hash}; + +#[derive(Debug)] +pub struct ResourceAddress { + address: Hash, + _t: PhantomData, +} + +impl ResourceAddress { + // pub fn descriptor(&self) -> (Hash, UniversalTypeId) { + // (self.address, T::universal_type_id()) + // } + + pub fn from_hex(s: &str) -> Result { + Ok(ResourceAddress { + address: Hash::from_hex(s)?, + _t: PhantomData, + }) + } +} + +impl Clone for ResourceAddress { + fn clone(&self) -> Self { + Self { + address: self.address, + _t: PhantomData, + } + } +} + +impl Copy for ResourceAddress {} diff --git a/dan_layer/template_lib/src/models/vault.rs b/dan_layer/template_lib/src/models/vault.rs new file mode 100644 index 0000000000..172c3de485 --- /dev/null +++ b/dan_layer/template_lib/src/models/vault.rs @@ -0,0 +1,56 @@ +// 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 tari_template_abi::{Decode, Encode}; + +use crate::models::{Bucket, ResourceAddress}; + +#[derive(Clone, Debug, Decode, Encode)] +pub struct Vault { + resource_address: ResourceAddress, +} + +impl Vault { + pub fn new(resource_address: ResourceAddress) -> Self { + // Call to call_engine will rather be in the ResourceBuilder/VaultBuilder, and the resulting address passed in + // here. let resource_address = call_engine(OP_RESOURCE_INVOKE, ResourceInvoke { + // resource_ref: ResourceRef::Vault, + // action: ResourceAction::Create, + // args: args![], + // }); + + Self { resource_address } + } + + pub fn put(&mut self, _bucket: Bucket) { + // let _ok: () = call_engine(OP_RESOURCE_INVOKE, ResourceInvoke { + // resource_ref: ResourceRef::VaultRef(self.resource_address()), + // action: ResourceAction::Put, + // args: args![bucket], + // }); + todo!() + } + + pub fn resource_address(&self) -> ResourceAddress { + self.resource_address + } +} diff --git a/dan_layer/template_abi/src/ops.rs b/dan_layer/template_lib/src/ops.rs similarity index 92% rename from dan_layer/template_abi/src/ops.rs rename to dan_layer/template_lib/src/ops.rs index 0ce2f3e287..67fcae63d8 100644 --- a/dan_layer/template_abi/src/ops.rs +++ b/dan_layer/template_lib/src/ops.rs @@ -22,3 +22,6 @@ pub const OP_EMIT_LOG: i32 = 0x00; pub const OP_CREATE_COMPONENT: i32 = 0x01; +pub const OP_GET_COMPONENT: i32 = 0x02; +pub const OP_SET_COMPONENT_STATE: i32 = 0x03; +pub const OP_RESOURCE_INVOKE: i32 = 0x04; diff --git a/dan_layer/template_lib/src/template_dependencies.rs b/dan_layer/template_lib/src/template_dependencies.rs new file mode 100644 index 0000000000..2057b2ec91 --- /dev/null +++ b/dan_layer/template_lib/src/template_dependencies.rs @@ -0,0 +1,33 @@ +// 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. + +//! Public types that are available to all template authors. + +pub use borsh; +pub use tari_template_abi::{call_debug as debug, Decode, Encode}; + +pub use crate::{ + args::LogLevel, + engine, + get_context as context, + models::{Bucket, BucketId, ResourceAddress, Vault}, +}; diff --git a/dan_layer/template_macros/Cargo.toml b/dan_layer/template_macros/Cargo.toml index a3335cabe6..b7bfa3da3d 100644 --- a/dan_layer/template_macros/Cargo.toml +++ b/dan_layer/template_macros/Cargo.toml @@ -9,11 +9,9 @@ edition = "2021" proc-macro = true [dependencies] -tari_template_abi = { path = "../template_abi" } -tari_template_lib = { path = "../template_lib" } -syn = { version = "1.0.98", features = ["full"] } proc-macro2 = "1.0.42" quote = "1.0.20" +syn = { version = "1.0.98", features = ["full"] } [dev-dependencies] indoc = "1.0.6" \ No newline at end of file diff --git a/dan_layer/template_macros/src/template/abi.rs b/dan_layer/template_macros/src/template/abi.rs index a2c964f019..a9dc4aeaa2 100644 --- a/dan_layer/template_macros/src/template/abi.rs +++ b/dan_layer/template_macros/src/template/abi.rs @@ -34,7 +34,7 @@ pub fn generate_abi(ast: &TemplateAst) -> Result { let output = quote! { #[no_mangle] pub extern "C" fn #abi_function_name() -> *mut u8 { - use ::tari_template_abi::{encode_with_len, FunctionDef, TemplateDef, Type}; + use ::tari_template_abi::{encode_with_len, FunctionDef, TemplateDef, Type, wrap_ptr}; let template = TemplateDef { template_name: #template_name_as_str.to_string(), @@ -137,7 +137,7 @@ mod tests { assert_code_eq(output, quote! { #[no_mangle] pub extern "C" fn Foo_abi() -> *mut u8 { - use ::tari_template_abi::{encode_with_len, FunctionDef, TemplateDef, Type}; + use ::tari_template_abi::{encode_with_len, FunctionDef, TemplateDef, Type, wrap_ptr}; let template = TemplateDef { template_name: "Foo".to_string(), diff --git a/dan_layer/template_macros/src/template/definition.rs b/dan_layer/template_macros/src/template/definition.rs index f3c98825ed..6ca969e74b 100644 --- a/dan_layer/template_macros/src/template/definition.rs +++ b/dan_layer/template_macros/src/template/definition.rs @@ -33,9 +33,9 @@ pub fn generate_definition(ast: &TemplateAst) -> TokenStream { quote! { pub mod template { - use tari_template_abi::borsh; + use super::*; - #[derive(tari_template_abi::borsh::BorshSerialize, tari_template_abi::borsh::BorshDeserialize)] + #[derive(Decode, Encode)] pub struct #template_name #template_fields #semi_token impl #template_name { diff --git a/dan_layer/template_macros/src/template/dependencies.rs b/dan_layer/template_macros/src/template/dependencies.rs index fb81436f95..706cc11431 100644 --- a/dan_layer/template_macros/src/template/dependencies.rs +++ b/dan_layer/template_macros/src/template/dependencies.rs @@ -23,43 +23,9 @@ use proc_macro2::TokenStream; use quote::quote; +/// Returns code that contains global functions and types implicitly available to contract authors pub fn generate_dependencies() -> TokenStream { - // TODO: these public abi functions are already declared in the common template lib/abi quote! { - use tari_template_lib::wrap_ptr; - // extern "C" { - // pub fn tari_engine(op: u32, input_ptr: *const u8, input_len: usize) -> *mut u8; - // } - // - // pub fn wrap_ptr(mut v: Vec) -> *mut u8 { - // use std::mem; - // - // let ptr = v.as_mut_ptr(); - // mem::forget(v); - // ptr - // } - // - // #[no_mangle] - // pub unsafe extern "C" fn tari_alloc(len: u32) -> *mut u8 { - // use std::{mem, intrinsics::copy}; - // - // let cap = (len + 4) as usize; - // let mut buf = Vec::::with_capacity(cap); - // let ptr = buf.as_mut_ptr(); - // mem::forget(buf); - // copy(len.to_le_bytes().as_ptr(), ptr, 4); - // ptr - // } - // - // #[no_mangle] - // pub unsafe extern "C" fn tari_free(ptr: *mut u8) { - // use std::intrinsics::copy; - // - // let mut len = [0u8; 4]; - // copy(ptr, len.as_mut_ptr(), 4); - // - // let cap = (u32::from_le_bytes(len) + 4) as usize; - // let _ = Vec::::from_raw_parts(ptr, cap, cap); - // } + use tari_template_lib::template_dependencies::*; } } diff --git a/dan_layer/template_macros/src/template/dispatcher.rs b/dan_layer/template_macros/src/template/dispatcher.rs index 90339769d2..44b57174f7 100644 --- a/dan_layer/template_macros/src/template/dispatcher.rs +++ b/dan_layer/template_macros/src/template/dispatcher.rs @@ -34,8 +34,8 @@ pub fn generate_dispatcher(ast: &TemplateAst) -> Result { let output = quote! { #[no_mangle] pub extern "C" fn #dispatcher_function_name(call_info: *mut u8, call_info_len: usize) -> *mut u8 { - use ::tari_template_abi::{decode, encode_with_len, CallInfo}; - use ::tari_template_lib::models::{get_state, set_state, initialise}; + use ::tari_template_abi::{decode, encode_with_len, CallInfo, wrap_ptr}; + use ::tari_template_lib::set_context_from_call_info; if call_info.is_null() { panic!("call_info is null"); @@ -44,6 +44,10 @@ pub fn generate_dispatcher(ast: &TemplateAst) -> Result { let call_data = unsafe { Vec::from_raw_parts(call_info, call_info_len, call_info_len) }; let call_info: CallInfo = decode(&call_data).unwrap(); + set_context_from_call_info(&call_info); + // TODO: wrap this in a nice macro + engine().emit_log(LogLevel::Debug, format!("Dispatcher called with function {}", call_info.func_name)); + let result; match call_info.func_name.as_str() { #( #function_names => #function_blocks ),*, @@ -75,7 +79,6 @@ fn get_function_blocks(ast: &TemplateAst) -> Vec { fn get_function_block(template_ident: &Ident, ast: FunctionAst) -> Expr { let mut args: Vec = vec![]; let mut stmts = vec![]; - let mut should_get_state = false; let mut should_set_state = false; // encode all arguments of the functions @@ -84,33 +87,30 @@ fn get_function_block(template_ident: &Ident, ast: FunctionAst) -> Expr { let stmt = match input_type { // "self" argument TypeAst::Receiver { mutability } => { - should_get_state = true; should_set_state = mutability; args.push(parse_quote! { &mut state }); - parse_quote! { - let #arg_ident = - decode::(&call_info.args[#i]) - .unwrap(); - } + vec![ + parse_quote! { + let component = + decode::<::tari_template_lib::models::ComponentInstance>(&call_info.args[#i]) + .unwrap(); + }, + parse_quote! { + let mut state = decode::(&component.state).unwrap(); + }, + ] }, // non-self argument TypeAst::Typed(type_ident) => { args.push(parse_quote! { #arg_ident }); - parse_quote! { + vec![parse_quote! { let #arg_ident = decode::<#type_ident>(&call_info.args[#i]) .unwrap(); - } + }] }, }; - stmts.push(stmt); - } - - // load the component state - if should_get_state { - stmts.push(parse_quote! { - let mut state: template::#template_ident = get_state(arg_0); - }); + stmts.extend(stmt); } // call the user defined function in the template @@ -122,7 +122,7 @@ fn get_function_block(template_ident: &Ident, ast: FunctionAst) -> Expr { let template_name_str = template_ident.to_string(); stmts.push(parse_quote! { - let rtn = initialise(#template_name_str.to_string(), state); + let rtn = engine().instantiate(#template_name_str.to_string(), state); }); } else { stmts.push(parse_quote! { @@ -138,7 +138,7 @@ fn get_function_block(template_ident: &Ident, ast: FunctionAst) -> Expr { // after user function invocation, update the component state if should_set_state { stmts.push(parse_quote! { - set_state(arg_0, state); + engine().set_component_state(component.id(), state); }); } diff --git a/dan_layer/template_macros/src/template/mod.rs b/dan_layer/template_macros/src/template/mod.rs index ceb8e149ca..7511d343ed 100644 --- a/dan_layer/template_macros/src/template/mod.rs +++ b/dan_layer/template_macros/src/template/mod.rs @@ -40,19 +40,19 @@ use crate::ast::TemplateAst; pub fn generate_template(input: TokenStream) -> Result { let ast = parse2::(input).unwrap(); + let dependencies = generate_dependencies(); let definition = generate_definition(&ast); let abi = generate_abi(&ast)?; let dispatcher = generate_dispatcher(&ast)?; - let dependencies = generate_dependencies(); let output = quote! { + #dependencies + #definition #abi #dispatcher - - #dependencies }; Ok(output) @@ -94,10 +94,12 @@ mod tests { let output = generate_template(input).unwrap(); assert_code_eq(output, quote! { + use tari_template_lib::template_dependencies::*; + pub mod template { - use tari_template_abi::borsh; + use super::*; - #[derive(tari_template_abi::borsh::BorshSerialize, tari_template_abi::borsh::BorshDeserialize)] + #[derive(Decode, Encode)] pub struct State { value: u32 } @@ -117,7 +119,7 @@ mod tests { #[no_mangle] pub extern "C" fn State_abi() -> *mut u8 { - use ::tari_template_abi::{encode_with_len, FunctionDef, TemplateDef, Type}; + use ::tari_template_abi::{encode_with_len, FunctionDef, TemplateDef, Type, wrap_ptr}; let template = TemplateDef { template_name: "State".to_string(), @@ -146,8 +148,8 @@ mod tests { #[no_mangle] pub extern "C" fn State_main(call_info: *mut u8, call_info_len: usize) -> *mut u8 { - use ::tari_template_abi::{decode, encode_with_len, CallInfo}; - use ::tari_template_lib::models::{get_state, set_state, initialise}; + use ::tari_template_abi::{decode, encode_with_len, CallInfo, wrap_ptr}; + use ::tari_template_lib::set_context_from_call_info; if call_info.is_null() { panic!("call_info is null"); @@ -156,34 +158,35 @@ mod tests { let call_data = unsafe { Vec::from_raw_parts(call_info, call_info_len, call_info_len) }; let call_info: CallInfo = decode(&call_data).unwrap(); + set_context_from_call_info(&call_info); + engine().emit_log(LogLevel::Debug, format!("Dispatcher called with function {}" , call_info.func_name)); + let result; match call_info.func_name.as_str() { "new" => { let state = template::State::new(); - let rtn = initialise("State".to_string(), state); + let rtn = engine().instantiate("State".to_string(), state); result = encode_with_len(&rtn); }, "get" => { - let arg_0 = decode::(&call_info.args[0usize]).unwrap(); - let mut state: template::State = get_state(arg_0); + let component = decode::<::tari_template_lib::models::ComponentInstance>(&call_info.args[0usize]).unwrap(); + let mut state = decode::(&component.state).unwrap(); let rtn = template::State::get(&mut state); result = encode_with_len(&rtn); }, "set" => { - let arg_0 = decode::(&call_info.args[0usize]).unwrap(); + let component = decode::<::tari_template_lib::models::ComponentInstance>(&call_info.args[0usize]).unwrap(); + let mut state = decode::(&component.state).unwrap(); let arg_1 = decode::(&call_info.args[1usize]).unwrap(); - let mut state: template::State = get_state(arg_0); let rtn = template::State::set(&mut state, arg_1); result = encode_with_len(&rtn); - set_state(arg_0, state); + engine().set_component_state(component.id(), state); }, _ => panic!("invalid function name") }; wrap_ptr(result) } - - use tari_template_lib::wrap_ptr; }); }