Skip to content

Commit

Permalink
add helper crate for writing wasm smartcontracts
Browse files Browse the repository at this point in the history
Signed-off-by: Marin Veršić <[email protected]>
  • Loading branch information
mversic committed Jan 14, 2022
1 parent b02dfcb commit 41eeb54
Show file tree
Hide file tree
Showing 9 changed files with 331 additions and 15 deletions.
2 changes: 1 addition & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ crossbeam-queue = "0.3"
warp = "0.3"
thiserror = "1.0.28"
pin-project = "1"
wasmtime = "0.29.0"
wasmtime = "0.33.0"

# transitive dependencies
anyhow = ">= 1.0"
Expand Down
32 changes: 24 additions & 8 deletions core/src/smartcontracts/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ use crate::{
wsv::{WorldStateView, WorldTrait},
};

const WASM_ALLOC_FN: &str = "alloc";
const WASM_ALLOC_FN: &str = "_iroha_wasm_alloc";
const WASM_MEMORY_NAME: &str = "memory";
const WASM_MAIN_FN_NAME: &str = "main";
const EXECUTE_ISI_FN_NAME: &str = "execute_isi";
const WASM_MAIN_FN_NAME: &str = "_iroha_wasm_main";
const EXECUTE_ISI_FN_NAME: &str = "execute_instruction";
const EXECUTE_QUERY_FN_NAME: &str = "execute_query";

/// `WebAssembly` execution error type
Expand All @@ -32,7 +32,7 @@ pub enum Error {
ExportNotFound(#[source] anyhow::Error),
/// Call to function exported from module failed
#[error("Exported function call failed")]
ExportFnCall(#[source] Trap),
ExportFnCall(#[from] Trap),
/// Some other error happened
#[error(transparent)]
Other(eyre::Error),
Expand Down Expand Up @@ -81,6 +81,11 @@ impl<'a, W: WorldTrait> Runtime<'a, W> {
/// Host defined function which executes query. When calling this function, module
/// serializes query to linear memory and provides offset and length as parameters
///
/// # Warning
///
/// This function doesn't take ownership of the provided allocation
/// but it does transfer ownership of the result to the caller
///
/// # Errors
///
/// If decoding or execution of the query fails
Expand Down Expand Up @@ -125,10 +130,19 @@ impl<'a, W: WorldTrait> Runtime<'a, W> {
/// Host defined function which executes ISI. When calling this function, module
/// serializes ISI to linear memory and provides offset and length as parameters
///
/// # Warning
///
/// This function doesn't take ownership of the provided allocation
/// but it does tranasfer ownership of the result to the caller
///
/// # Errors
///
/// If decoding or execution of the ISI fails
fn execute_isi(mut caller: Caller<State<W>>, offset: u32, len: u32) -> Result<(), Trap> {
fn execute_instruction(
mut caller: Caller<State<W>>,
offset: u32,
len: u32,
) -> Result<(), Trap> {
let memory = Self::get_memory(&mut caller)?;

// Accessing memory as a byte slice to avoid the use of unsafe
Expand All @@ -150,7 +164,7 @@ impl<'a, W: WorldTrait> Runtime<'a, W> {
let mut linker = Linker::new(engine);

linker
.func_wrap("iroha", EXECUTE_ISI_FN_NAME, Self::execute_isi)
.func_wrap("iroha", EXECUTE_ISI_FN_NAME, Self::execute_instruction)
.map_err(Error::Initialization)?;

linker
Expand Down Expand Up @@ -246,11 +260,13 @@ impl<'a, W: WorldTrait> Runtime<'a, W> {
acc_offset
};

let main = instance
let main_fn = instance
.get_typed_func::<(u32, u32), (), _>(&mut store, WASM_MAIN_FN_NAME)
.map_err(Error::ExportNotFound)?;

main.call(&mut store, (account_offset, account_bytes_len))
// NOTE: This function takes ownership of the pointer
main_fn
.call(&mut store, (account_offset, account_bytes_len))
.map_err(Error::ExportFnCall)?;

Ok(())
Expand Down
6 changes: 3 additions & 3 deletions data_model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1100,7 +1100,7 @@ pub mod asset {
//! instructions implementations.
#[cfg(not(feature = "std"))]
use alloc::{collections::btree_map, string::String, vec::Vec};
use alloc::{collections::btree_map, format, string::String, vec::Vec};
use core::{
cmp::Ordering,
fmt::{self, Display, Formatter},
Expand Down Expand Up @@ -1633,7 +1633,7 @@ pub mod domain {
//! This module contains [`Domain`](`crate::domain::Domain`) structure and related implementations and trait implementations.
#[cfg(not(feature = "std"))]
use alloc::{collections::btree_map, string::String, vec::Vec};
use alloc::{collections::btree_map, format, string::String, vec::Vec};
use core::{cmp::Ordering, fmt, str::FromStr};
#[cfg(feature = "std")]
use std::collections::btree_map;
Expand Down Expand Up @@ -1823,7 +1823,7 @@ pub mod peer {
//! This module contains [`Peer`] structure and related implementations and traits implementations.
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use alloc::{format, string::String, vec::Vec};
use core::{
cmp::Ordering,
fmt,
Expand Down
4 changes: 2 additions & 2 deletions data_model/src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ pub enum Error {
},
/// Empty path
#[display(fmt = "Path specification empty")]
EmptyPath(),
EmptyPath,
/// Middle path segment is missing. I.e. nothing was found at that key
#[display(fmt = "{}: path segment not found", _0)]
MissingSegment(Name),
Expand Down Expand Up @@ -165,7 +165,7 @@ impl Metadata {
actual: self.map.len(),
});
}
let key = path.last().ok_or_else(Error::EmptyPath)?;
let key = path.last().ok_or(Error::EmptyPath)?;
let mut layer = self;
for k in path.iter().take(path.len() - 1) {
layer = match layer
Expand Down
2 changes: 1 addition & 1 deletion data_model/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ declare_versioned_with_scale!(VersionedQueryResult 1..2, Debug, Clone, iroha_mac

/// Sized container for all possible Query results.
#[version_with_scale(n = 1, versioned = "VersionedQueryResult")]
#[derive(Debug, Clone, Decode, Encode, Deserialize, Serialize, IntoSchema)]
#[derive(Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema)]
pub struct QueryResult(pub Value);

#[cfg(all(feature = "std", feature = "warp"))]
Expand Down
23 changes: 23 additions & 0 deletions wasm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "iroha_wasm"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
default = ["panic_handler"]
panic_handler = []

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"

[dependencies]
iroha_data_model = { version = "=2.0.0-pre.1", path = "../data_model", default-features = false }
iroha_wasm_derive = { path = "derive" }

parity-scale-codec = { version = "2.3.1", default-features = false }
wee_alloc = "0.4.5"
19 changes: 19 additions & 0 deletions wasm/derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "iroha_wasm_derive"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
proc-macro = true

[dependencies]
syn = {version = "1", default-features = false }
quote = "1.0"
proc-macro-error = "1.0"

[dev-dependencies]
iroha_wasm = { path = "../" }

trybuild = "1.0.53"
70 changes: 70 additions & 0 deletions wasm/derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//! Exposes macros which facilitate writing smartcontracts
#![allow(clippy::str_to_string)]

use proc_macro::TokenStream;
use proc_macro_error::{abort, proc_macro_error};
use quote::quote;
use syn::{parse_macro_input, ItemFn, Path, ReturnType, Signature, Type};

/// Used to annotate user-defined function which starts the execution of smartcontract
#[proc_macro_error]
#[proc_macro_attribute]
pub fn iroha_wasm(_: TokenStream, item: TokenStream) -> TokenStream {
let ItemFn {
attrs,
vis,
sig,
block,
}: ItemFn = parse_macro_input!(item as ItemFn);

verify_function_signature(&sig);
let fn_name = &sig.ident;

quote! {
#[no_mangle]
unsafe extern "C" fn _iroha_wasm_main(ptr: u32, len: u32) {
#fn_name(iroha_wasm::_decode_from_raw::<AccountId>(ptr, len))
}

#(#attrs)*
#vis #sig
#block
}
.into()
}

fn verify_function_signature(sig: &Signature) -> bool {
if ReturnType::Default != sig.output {
abort!(sig.output, "Exported function must not have a return type");
}

if sig.inputs.len() != 1 {
abort!(
sig.inputs,
"Exported function must have exactly 1 input argument of type `AccountId`"
);
}

if let Some(syn::FnArg::Typed(pat)) = sig.inputs.iter().next() {
if let syn::Type::Reference(ty) = &*pat.ty {
return type_is_account_id(&ty.elem);
}
}

false
}

fn type_is_account_id(account_id_ty: &Type) -> bool {
const ACCOUNT_ID_IDENT: &str = "AccountId";

if let Type::Path(path) = account_id_ty {
let Path { segments, .. } = &path.path;

if let Some(type_name) = segments.iter().last().map(|ty| &ty.ident) {
return *type_name == ACCOUNT_ID_IDENT;
}
}

false
}
Loading

0 comments on commit 41eeb54

Please sign in to comment.