tCode import - wasm-runtime - A wasm runtime
 (HTM) git clone https://git.parazyd.org/wasm-runtime
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
       ---
 (DIR) commit e4fae9954d20225831248af32972253a3577d25f
 (HTM) Author: parazyd <parazyd@dyne.org>
       Date:   Tue,  8 Mar 2022 22:58:49 +0100
       
       Code import
       
       Diffstat:
         A .gitignore                          |       4 ++++
         A Cargo.toml                          |      32 +++++++++++++++++++++++++++++++
         A Makefile                            |      20 ++++++++++++++++++++
         A README.md                           |      12 ++++++++++++
         A drk-sdk/Cargo.toml                  |       7 +++++++
         A drk-sdk/src/entrypoint.rs           |      59 +++++++++++++++++++++++++++++++
         A drk-sdk/src/error.rs                |      51 +++++++++++++++++++++++++++++++
         A drk-sdk/src/lib.rs                  |       2 ++
         A rustfmt.toml                        |       9 +++++++++
         A smart-contract/.gitignore           |       3 +++
         A smart-contract/Cargo.toml           |      16 ++++++++++++++++
         A smart-contract/src/lib.rs           |      23 +++++++++++++++++++++++
         A src/example.rs                      |      17 +++++++++++++++++
         A src/lib.rs                          |       3 +++
         A src/memory.rs                       |      41 +++++++++++++++++++++++++++++++
         A src/runtime.rs                      |      88 +++++++++++++++++++++++++++++++
         A src/util.rs                         |      10 ++++++++++
       
       17 files changed, 397 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/.gitignore b/.gitignore
       t@@ -0,0 +1,4 @@
       +/target
       +/wabt
       +Cargo.lock
       +smart_contract.wasm
 (DIR) diff --git a/Cargo.toml b/Cargo.toml
       t@@ -0,0 +1,32 @@
       +[package]
       +name = "wasm-runtime"
       +version = "0.1.0"
       +edition = "2021"
       +
       +[workspace]
       +members = ["drk-sdk"]
       +exclude = ["smart-contract"]
       +
       +#[profile.release]
       +#lto = true
       +#codegen-units = 1
       +#overflow-checks = true
       +
       +[dependencies]
       +anyhow = "1.0.55"
       +wasmer = "2.2.0"
       +drk-sdk = { path = "./drk-sdk" }
       +
       +[dev-dependencies]
       +borsh = "0.9.3"
       +smart-contract = { path = "./smart-contract" }
       +#wasmer = "^2.0.0"
       +
       +[dev-dependencies.pasta_curves]
       +git = "https://github.com/parazyd/pasta_curves"
       +branch = "optional-borsh-support"
       +features = ["borsh"]
       +
       +[[example]]
       +name = "runner"
       +path = "src/example.rs"
 (DIR) diff --git a/Makefile b/Makefile
       t@@ -0,0 +1,20 @@
       +SRC = \
       +        $(shell find src -type f) \
       +        $(shell find smart-contract -type f) \
       +        $(shell find drk-sdk -type f)
       +
       +CARGO = cargo
       +
       +DEPS = smart_contract.wasm
       +
       +all: $(DEPS)
       +        $(CARGO) run --release --example runner
       +
       +wabt:
       +        git clone --recursive https://github.com/WebAssembly/wabt $@
       +        $(MAKE) -C $@
       +
       +smart_contract.wasm: wabt $(SRC)
       +        cd smart-contract && $(CARGO) build --release --lib --target wasm32-unknown-unknown
       +        cp -f smart-contract/target/wasm32-unknown-unknown/release/$@ $@
       +        ./wabt/bin/wasm-strip $@
 (DIR) diff --git a/README.md b/README.md
       t@@ -0,0 +1,12 @@
       +wasm-runtime
       +============
       +
       +Experiments with WASM runtime.
       +
       +```
       +$ make
       +```
       +
       +* Smart contract is in `smart-contract/src/lib.rs`
       +* Contract helpers are in `drk-sdk/src`
       +* wasm runtime is in `src`
 (DIR) diff --git a/drk-sdk/Cargo.toml b/drk-sdk/Cargo.toml
       t@@ -0,0 +1,7 @@
       +[package]
       +name = "drk-sdk"
       +version = "0.1.0"
       +edition = "2021"
       +
       +[dependencies]
       +thiserror = "1.0.30"
 (DIR) diff --git a/drk-sdk/src/entrypoint.rs b/drk-sdk/src/entrypoint.rs
       t@@ -0,0 +1,59 @@
       +use std::{mem::size_of, slice::from_raw_parts};
       +
       +/// Success exit code for contract
       +pub const SUCCESS: u64 = 0;
       +
       +/// This macro is used to flag the contract entrypoint function.
       +/// All contracts must provide such a function and accept a payload.
       +///
       +/// The payload is a slice of u8 prepended with a little-endian u64
       +/// that tells the slice's length.
       +#[macro_export]
       +macro_rules! entrypoint {
       +    ($process_instruction:ident) => {
       +        /// # Safety
       +        #[no_mangle]
       +        pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
       +            let instruction_data = $crate::entrypoint::deserialize(input);
       +
       +            match $process_instruction(&instruction_data) {
       +                Ok(()) => $crate::entrypoint::SUCCESS,
       +                Err(e) => e.into(),
       +            }
       +        }
       +    };
       +}
       +
       +/// Deserialize a given payload in `entrypoint`
       +/// # Safety
       +pub unsafe fn deserialize<'a>(input: *mut u8) -> &'a [u8] {
       +    let mut offset: usize = 0;
       +
       +    let instruction_data_len = *(input.add(offset) as *const u64) as usize;
       +    offset += size_of::<u64>();
       +
       +    let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) };
       +
       +    instruction_data
       +}
       +
       +/// Allocate a piece of memory in the wasm VM
       +#[no_mangle]
       +pub extern "C" fn __drkruntime_mem_alloc(size: usize) -> *mut u8 {
       +    let align = std::mem::align_of::<usize>();
       +
       +    if let Ok(layout) = std::alloc::Layout::from_size_align(size, align) {
       +        unsafe {
       +            if layout.size() > 0 {
       +                let ptr = std::alloc::alloc(layout);
       +                if !ptr.is_null() {
       +                    return ptr
       +                }
       +            } else {
       +                return align as *mut u8
       +            }
       +        }
       +    }
       +
       +    std::process::abort();
       +}
 (DIR) diff --git a/drk-sdk/src/error.rs b/drk-sdk/src/error.rs
       t@@ -0,0 +1,51 @@
       +use std::result::Result as ResultGeneric;
       +
       +pub type ContractResult = ResultGeneric<(), ContractError>;
       +
       +#[derive(Debug, thiserror::Error)]
       +#[repr(C)]
       +pub enum ContractError {
       +    #[error("Internal error")]
       +    Internal,
       +
       +    #[error("Out of memory")]
       +    OutOfMemory,
       +
       +    #[error("IO error")]
       +    Io,
       +
       +    #[error("Custom contract error: {0:#x}")]
       +    Custom(u32),
       +}
       +
       +pub const INTERNAL_ERROR: u64 = 1;
       +pub const OUT_OF_MEMORY: u64 = 2;
       +pub const IO_ERROR: u64 = 3;
       +
       +impl From<ContractError> for u64 {
       +    fn from(err: ContractError) -> Self {
       +        match err {
       +            ContractError::Internal => INTERNAL_ERROR,
       +            ContractError::OutOfMemory => OUT_OF_MEMORY,
       +            ContractError::Io => IO_ERROR,
       +            ContractError::Custom(e) => e.into(),
       +        }
       +    }
       +}
       +
       +impl From<u64> for ContractError {
       +    fn from(err: u64) -> Self {
       +        match err {
       +            INTERNAL_ERROR => ContractError::Internal,
       +            OUT_OF_MEMORY => ContractError::OutOfMemory,
       +            IO_ERROR => ContractError::Io,
       +            e => ContractError::Custom(e.try_into().unwrap()),
       +        }
       +    }
       +}
       +
       +impl From<std::io::Error> for ContractError {
       +    fn from(_: std::io::Error) -> Self {
       +        Self::Io
       +    }
       +}
 (DIR) diff --git a/drk-sdk/src/lib.rs b/drk-sdk/src/lib.rs
       t@@ -0,0 +1,2 @@
       +pub mod entrypoint;
       +pub mod error;
 (DIR) diff --git a/rustfmt.toml b/rustfmt.toml
       t@@ -0,0 +1,9 @@
       +reorder_imports = true
       +imports_granularity = "Crate"
       +use_small_heuristics = "Max"
       +comment_width = 100
       +wrap_comments = false
       +binop_separator = "Back"
       +trailing_comma = "Vertical"
       +trailing_semicolon = false
       +use_field_init_shorthand = true
 (DIR) diff --git a/smart-contract/.gitignore b/smart-contract/.gitignore
       t@@ -0,0 +1,3 @@
       +/target
       +/wabt
       +Cargo.lock
 (DIR) diff --git a/smart-contract/Cargo.toml b/smart-contract/Cargo.toml
       t@@ -0,0 +1,16 @@
       +[package]
       +name = "smart-contract"
       +version = "0.1.0"
       +edition = "2021"
       +
       +[lib]
       +crate-type = ["cdylib", "rlib"]
       +
       +[dependencies]
       +borsh = "0.9.3"
       +drk-sdk = { path = "../drk-sdk" }
       +
       +[dependencies.pasta_curves]
       +git = "https://github.com/parazyd/pasta_curves"
       +branch = "optional-borsh-support"
       +features = ["borsh"]
 (DIR) diff --git a/smart-contract/src/lib.rs b/smart-contract/src/lib.rs
       t@@ -0,0 +1,23 @@
       +use borsh::{BorshDeserialize, BorshSerialize};
       +use drk_sdk::{
       +    entrypoint,
       +    error::{ContractError, ContractResult},
       +};
       +use pasta_curves::pallas;
       +
       +#[derive(BorshSerialize, BorshDeserialize)]
       +pub struct Args {
       +    pub a: pallas::Base,
       +    pub b: pallas::Base,
       +}
       +
       +entrypoint!(process_instruction);
       +fn process_instruction(ix: &[u8]) -> ContractResult {
       +    let args = Args::try_from_slice(ix)?;
       +
       +    if args.a < args.b {
       +        return Err(ContractError::Custom(69))
       +    }
       +
       +    Ok(())
       +}
 (DIR) diff --git a/src/example.rs b/src/example.rs
       t@@ -0,0 +1,17 @@
       +use anyhow::Result;
       +use borsh::BorshSerialize;
       +use pasta_curves::pallas;
       +use wasm_runtime::{runtime::Runtime, util::serialize_payload};
       +
       +use smart_contract::Args;
       +
       +fn main() -> Result<()> {
       +    let wasm_bytes = std::fs::read("smart_contract.wasm")?;
       +    let mut runtime = Runtime::new(&wasm_bytes)?;
       +
       +    let args = Args { a: pallas::Base::from(777), b: pallas::Base::from(666) };
       +    let payload = args.try_to_vec()?;
       +    let input = serialize_payload(&payload);
       +
       +    runtime.run(&input)
       +}
 (DIR) diff --git a/src/lib.rs b/src/lib.rs
       t@@ -0,0 +1,3 @@
       +pub mod memory;
       +pub mod runtime;
       +pub mod util;
 (DIR) diff --git a/src/memory.rs b/src/memory.rs
       t@@ -0,0 +1,41 @@
       +use anyhow::Result;
       +use wasmer::{Array, Memory, WasmPtr};
       +
       +use drk_sdk::error::ContractError;
       +
       +pub trait MemoryManipulation {
       +    fn write(&self, mem_offset: u32, value_slice: &[u8]) -> Result<()>;
       +    fn read(&self, mem_offset: u32, value_len: usize) -> Option<&[u8]>;
       +}
       +
       +impl MemoryManipulation for Memory {
       +    fn write(&self, mem_offset: u32, value_slice: &[u8]) -> Result<()> {
       +        let target_ptr: WasmPtr<u8, Array> = WasmPtr::new(mem_offset);
       +
       +        let guest_value_slice = match target_ptr.deref(self, 0, value_slice.len() as u32) {
       +            Some(slice) => slice,
       +            None => [].to_vec(),
       +        };
       +
       +        if guest_value_slice.is_empty() {
       +            return Err(ContractError::OutOfMemory.into())
       +        }
       +
       +        for i in 0..value_slice.len() {
       +            guest_value_slice[i].set(value_slice[i]);
       +        }
       +
       +        Ok(())
       +    }
       +
       +    fn read(&self, mem_offset: u32, value_len: usize) -> Option<&[u8]> {
       +        let memory_size = self.size().bytes().0;
       +
       +        if mem_offset as usize + value_len > memory_size || mem_offset as usize >= memory_size {
       +            return None
       +        }
       +
       +        let ptr = unsafe { self.view::<u8>().as_ptr().add(mem_offset as usize) as *const u8 };
       +        unsafe { Some(std::slice::from_raw_parts(ptr, value_len)) }
       +    }
       +}
 (DIR) diff --git a/src/runtime.rs b/src/runtime.rs
       t@@ -0,0 +1,88 @@
       +use anyhow::Result;
       +use drk_sdk::error::ContractError;
       +use wasmer::{imports, Cranelift, Instance, Memory, Module, Store, Universal, Value};
       +
       +use crate::memory::MemoryManipulation;
       +
       +const WASM_MEM_ALLOC: &str = "__drkruntime_mem_alloc";
       +const MEMORY: &str = "memory";
       +const ENTRYPOINT: &str = "entrypoint";
       +
       +pub struct Runtime {
       +    pub(crate) instance: Instance,
       +}
       +
       +impl Runtime {
       +    pub fn new(wasm_bytes: &[u8]) -> Result<Self> {
       +        let compiler = Cranelift::default();
       +        let store = Store::new(&Universal::new(compiler).engine());
       +
       +        println!("Compiling module...");
       +        let module = Module::new(&store, wasm_bytes)?;
       +
       +        println!("Importing functions...");
       +        let import_object = imports! {};
       +
       +        println!("Instantiating module...");
       +        let instance = Instance::new(&module, &import_object)?;
       +
       +        Ok(Self { instance })
       +    }
       +
       +    pub fn run(&mut self, payload: &[u8]) -> Result<()> {
       +        let memory = self.memory()?;
       +
       +        let mem_offset = self.guest_mem_alloc(payload.len())?;
       +        memory.write(mem_offset, payload)?;
       +
       +        println!("Getting entrypoint function...");
       +        let entrypoint = self.instance.exports.get_function(ENTRYPOINT)?;
       +        println!("{:#?}", entrypoint);
       +
       +        println!("Executing wasm...");
       +        let mut contract_ret: u64 = 1;
       +        match entrypoint.call(&[Value::I32(mem_offset as i32)]) {
       +            Ok(v) => {
       +                println!("wasm execution successful");
       +                match v[0] {
       +                    Value::I64(i) => {
       +                        println!("Contract returned: {}", i);
       +                        contract_ret = i as u64;
       +                    }
       +                    _ => unreachable!(),
       +                }
       +            }
       +
       +            Err(e) => {
       +                println!("wasm execution error: {:?}", e);
       +
       +                let frames = e.trace();
       +                let frames_len = frames.len();
       +
       +                for i in 0..frames_len {
       +                    println!(
       +                        "  Frame #{}: {:?}::{:?}",
       +                        frames_len - i,
       +                        frames[i].module_name(),
       +                        frames[i].function_name().or(Some("<func>")).unwrap()
       +                    );
       +                }
       +            }
       +        };
       +
       +        match ContractError::from(contract_ret) {
       +            ContractError::Custom(0) => Ok(()),
       +            e => Err(e.into()),
       +        }
       +    }
       +
       +    fn guest_mem_alloc(&self, size: usize) -> Result<u32> {
       +        let mem_alloc = self.instance.exports.get_function(WASM_MEM_ALLOC)?;
       +        let res_target_ptr = mem_alloc.call(&[Value::I32(size as i32)])?.to_vec();
       +        Ok(res_target_ptr[0].unwrap_i32() as u32)
       +    }
       +
       +    fn memory(&self) -> Result<&Memory> {
       +        Ok(self.instance.exports.get_memory(MEMORY)?)
       +    }
       +}
 (DIR) diff --git a/src/util.rs b/src/util.rs
       t@@ -0,0 +1,10 @@
       +/// Serialize payload to format accepted by the runtime entrypoint
       +pub fn serialize_payload(payload: &[u8]) -> Vec<u8> {
       +    let mut out = vec![];
       +
       +    let len = payload.len() as u64;
       +    out.extend_from_slice(&len.to_le_bytes());
       +    out.extend_from_slice(payload);
       +
       +    out
       +}