URI: 
       tAdd mem rw tests and some docs. - wasm-runtime - A wasm runtime
  HTML git clone https://git.parazyd.org/wasm-runtime
   DIR Log
   DIR Files
   DIR Refs
   DIR README
       ---
   DIR commit 7344cb541280a961be374457ace9894d756e98c9
   DIR parent 5f8ad66aa984f882f4c2d8926c9e15e26f6d5825
  HTML Author: parazyd <parazyd@dyne.org>
       Date:   Tue,  8 Mar 2022 23:48:55 +0100
       
       Add mem rw tests and some docs.
       
       Diffstat:
         M Cargo.toml                          |       3 +--
         M Makefile                            |       3 +++
         M drk-sdk/src/error.rs                |       7 +------
         A src/error.rs                        |       5 +++++
         M src/lib.rs                          |       1 +
         M src/memory.rs                       |      68 ++++++++++++++++++++++++++++++-
         M src/runtime.rs                      |      48 +++++++++----------------------
       
       7 files changed, 91 insertions(+), 44 deletions(-)
       ---
   DIR diff --git a/Cargo.toml b/Cargo.toml
       t@@ -14,13 +14,12 @@ exclude = ["smart-contract"]
        
        [dependencies]
        anyhow = "1.0.55"
       +thiserror = "1.0.30"
        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"
   DIR diff --git a/Makefile b/Makefile
       t@@ -18,3 +18,6 @@ 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 $@
       +
       +test:
       +        $(CARGO) test --release --lib
   DIR diff --git a/drk-sdk/src/error.rs b/drk-sdk/src/error.rs
       t@@ -13,8 +13,6 @@ pub enum ContractError {
        
            #[error("Internal error")]
            Internal,
       -    #[error("Out of memory")]
       -    OutOfMemory,
            #[error("IO error: {0}")]
            BorshIoError(String),
        }
       t@@ -29,14 +27,12 @@ macro_rules! to_builtin {
        
        pub const CUSTOM_ZERO: u64 = to_builtin!(1);
        pub const INTERNAL_ERROR: u64 = to_builtin!(2);
       -pub const OUT_OF_MEMORY: u64 = to_builtin!(3);
       -pub const BORSH_IO_ERROR: u64 = to_builtin!(4);
       +pub const BORSH_IO_ERROR: u64 = to_builtin!(3);
        
        impl From<ContractError> for u64 {
            fn from(err: ContractError) -> Self {
                match err {
                    ContractError::Internal => INTERNAL_ERROR,
       -            ContractError::OutOfMemory => OUT_OF_MEMORY,
                    ContractError::BorshIoError(_) => BORSH_IO_ERROR,
                    ContractError::Custom(error) => {
                        if error == 0 {
       t@@ -54,7 +50,6 @@ impl From<u64> for ContractError {
                match error {
                    CUSTOM_ZERO => Self::Custom(0),
                    INTERNAL_ERROR => Self::Internal,
       -            OUT_OF_MEMORY => Self::OutOfMemory,
                    BORSH_IO_ERROR => Self::BorshIoError("Unknown".to_string()),
                    _ => Self::Custom(error as u32),
                }
   DIR diff --git a/src/error.rs b/src/error.rs
       t@@ -0,0 +1,5 @@
       +#[derive(Debug, thiserror::Error)]
       +pub enum RuntimeError {
       +    #[error("Cannot write data on module: Out of memory")]
       +    OutOfMemory,
       +}
   DIR diff --git a/src/lib.rs b/src/lib.rs
       t@@ -1,3 +1,4 @@
       +pub mod error;
        pub mod memory;
        pub mod runtime;
        pub mod util;
   DIR diff --git a/src/memory.rs b/src/memory.rs
       t@@ -1,7 +1,7 @@
        use anyhow::Result;
        use wasmer::{Array, Memory, WasmPtr};
        
       -use drk_sdk::error::ContractError;
       +use crate::error::RuntimeError;
        
        pub trait MemoryManipulation {
            fn write(&self, mem_offset: u32, value_slice: &[u8]) -> Result<()>;
       t@@ -10,17 +10,20 @@ pub trait MemoryManipulation {
        
        impl MemoryManipulation for Memory {
            fn write(&self, mem_offset: u32, value_slice: &[u8]) -> Result<()> {
       +        // Prepare WasmPtr
                let target_ptr: WasmPtr<u8, Array> = WasmPtr::new(mem_offset);
        
       +        // Allocate necessary memory space on guest
                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())
       +            return Err(RuntimeError::OutOfMemory.into())
                }
        
       +        // Copy bytes to guest
                for i in 0..value_slice.len() {
                    guest_value_slice[i].set(value_slice[i]);
                }
       t@@ -39,3 +42,64 @@ impl MemoryManipulation for Memory {
                unsafe { Some(std::slice::from_raw_parts(ptr, value_len)) }
            }
        }
       +
       +#[cfg(test)]
       +mod tests {
       +    use super::*;
       +    use wasmer::{imports, wat2wasm, Instance, Module, Store};
       +
       +    fn wasmer_instance() -> Instance {
       +        let wasm_bytes = wat2wasm(
       +            br#"
       +            (module
       +              (type $add_one_t (func (param i32) (result i32)))
       +              (func $add_one_f (type $add_one_t) (param $value i32) (result i32)
       +                local.get $value
       +                i32.const 1
       +                i32.add)
       +              (export "add_one" (func $add_one_f))
       +              (memory $memory (export "memory") 17))
       +            "#,
       +        )
       +        .unwrap();
       +
       +        let store = Store::default();
       +        let module = Module::new(&store, wasm_bytes).unwrap();
       +
       +        let import_object = imports! {};
       +        Instance::new(&module, &import_object).unwrap()
       +    }
       +
       +    #[test]
       +    fn can_write_on_memory() {
       +        let wasmer_instance = wasmer_instance();
       +
       +        let memory = wasmer_instance.exports.get_memory("memory").unwrap();
       +        let data = String::from("data_test");
       +
       +        let mem_addr = 0x2220;
       +
       +        memory.write(mem_addr as u32, data.as_bytes()).unwrap();
       +
       +        let ptr = unsafe { memory.view::<u8>().as_ptr().add(mem_addr as usize) as *const u8 };
       +        let slice_raw = unsafe { std::slice::from_raw_parts(ptr, data.len()) };
       +
       +        assert_eq!(data.as_bytes(), slice_raw);
       +    }
       +
       +    #[test]
       +    fn can_read_from_memory() {
       +        let wasmer_instance = wasmer_instance();
       +
       +        let memory = wasmer_instance.exports.get_memory("memory").unwrap();
       +        let data = String::from("data_test");
       +
       +        let mem_addr = 0x2220;
       +
       +        memory.write(mem_addr as u32, data.as_bytes()).unwrap();
       +
       +        let slice_raw = memory.read(mem_addr as u32, data.as_bytes().len()).unwrap();
       +
       +        assert_eq!(data.as_bytes(), slice_raw);
       +    }
       +}
   DIR diff --git a/src/runtime.rs b/src/runtime.rs
       t@@ -1,11 +1,13 @@
        use anyhow::Result;
       -use drk_sdk::error::ContractError;
        use wasmer::{imports, Cranelift, Instance, Memory, Module, Store, Universal, Value};
        
        use crate::memory::MemoryManipulation;
        
       +/// Function name in our wasm module that allows us to allocate some memory
        const WASM_MEM_ALLOC: &str = "__drkruntime_mem_alloc";
       +/// Name of the wasm linear memory of our guest module
        const MEMORY: &str = "memory";
       +/// Hardcoded entrypoint function of a contract
        const ENTRYPOINT: &str = "entrypoint";
        
        pub struct Runtime {
       t@@ -13,7 +15,9 @@ pub struct Runtime {
        }
        
        impl Runtime {
       +    /// Create a new wasm runtime instance that contains the given wasm module.
            pub fn new(wasm_bytes: &[u8]) -> Result<Self> {
       +        // Define the compiler, engine, and store
                let compiler = Cranelift::default();
                let store = Store::new(&Universal::new(compiler).engine());
        
       t@@ -29,9 +33,12 @@ impl Runtime {
                Ok(Self { instance })
            }
        
       +    /// Run the hardcoded [ENTRYPOINT] function with the given payload as input.
            pub fn run(&mut self, payload: &[u8]) -> Result<()> {
       +        // Get module linear memory
                let memory = self.memory()?;
        
       +        // Retrieve ptr to pass data
                let mem_offset = self.guest_mem_alloc(payload.len())?;
                memory.write(mem_offset, payload)?;
        
       t@@ -40,48 +47,21 @@ impl Runtime {
                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!(),
       -                }
       -            }
       +        let ret = entrypoint.call(&[Value::I32(mem_offset as i32)])?;
        
       -            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()),
       -        }
       +        println!("Executed successfully");
       +        println!("Contract returned: {:?}", ret);
       +        Ok(())
            }
        
       +    /// Allocate some memory space on a wasm linear memory to allow direct rw
            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)
            }
        
       +    /// Retrieve linear memory from a wasm module and return its reference
            fn memory(&self) -> Result<&Memory> {
                Ok(self.instance.exports.get_memory(MEMORY)?)
            }