wasm-runtime

A wasm runtime
git clone https://git.parazyd.org/wasm-runtime
Log | Files | Refs | README

commit 7344cb541280a961be374457ace9894d756e98c9
parent 5f8ad66aa984f882f4c2d8926c9e15e26f6d5825
Author: parazyd <parazyd@dyne.org>
Date:   Tue,  8 Mar 2022 23:48:55 +0100

Add mem rw tests and some docs.

Diffstat:
MCargo.toml | 3+--
MMakefile | 3+++
Mdrk-sdk/src/error.rs | 7+------
Asrc/error.rs | 5+++++
Msrc/lib.rs | 1+
Msrc/memory.rs | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/runtime.rs | 48++++++++++++++----------------------------------
7 files changed, 91 insertions(+), 44 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -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" diff --git a/Makefile b/Makefile @@ -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 diff --git a/drk-sdk/src/error.rs b/drk-sdk/src/error.rs @@ -13,8 +13,6 @@ pub enum ContractError { #[error("Internal error")] Internal, - #[error("Out of memory")] - OutOfMemory, #[error("IO error: {0}")] BorshIoError(String), } @@ -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 { @@ -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), } diff --git a/src/error.rs b/src/error.rs @@ -0,0 +1,5 @@ +#[derive(Debug, thiserror::Error)] +pub enum RuntimeError { + #[error("Cannot write data on module: Out of memory")] + OutOfMemory, +} diff --git a/src/lib.rs b/src/lib.rs @@ -1,3 +1,4 @@ +pub mod error; pub mod memory; pub mod runtime; pub mod util; diff --git a/src/memory.rs b/src/memory.rs @@ -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<()>; @@ -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]); } @@ -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); + } +} diff --git a/src/runtime.rs b/src/runtime.rs @@ -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 { @@ -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()); @@ -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)?; @@ -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)?) }