diff --git a/Cargo.toml b/Cargo.toml index 9755ea0..9d2fd9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,7 @@ wabt = "0.9.2" wasmi = "0.5.0" log = "0.4.8" arrayref = "0.3.5" +typed-builder = "0.3.0" + +[dev-dependencies] +lazy_static = "1.4.0" diff --git a/src/buffer.rs b/src/buffer.rs index a60774a..3bb9438 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; type K = u8; type V = HashMap<[u8; 32], [u8; 32]>; -#[derive(Clone, Default)] +#[derive(Debug, Clone, Default)] pub struct Buffer { map: HashMap, } diff --git a/src/env/child/mod.rs b/src/env/child/mod.rs new file mode 100644 index 0000000..5b5bbdf --- /dev/null +++ b/src/env/child/mod.rs @@ -0,0 +1,171 @@ +mod resolver; + +use crate::env::root::{RootRuntime, RootRuntimeWeak}; + +use self::resolver::{externals, ChildModuleImportResolver}; + +use std::cell::RefCell; + +use super::{ExtResult, StackFrame}; + +use wasmi::{ + Externals, FuncInstance, ImportsBuilder, MemoryRef, Module, ModuleInstance, ModuleRef, + RuntimeArgs, RuntimeValue, Trap, +}; + +#[derive(Debug)] +pub struct ChildRuntime<'a> { + instance: ModuleRef, + root: RootRuntimeWeak<'a>, + + call_stack: RefCell>, +} + +impl<'a> ChildRuntime<'a> { + pub(crate) fn new(root: RootRuntimeWeak<'a>, code: &[u8]) -> Self { + let module = Module::from_buffer(code).expect("Module loading to succeed"); + + let mut imports = ImportsBuilder::new(); + imports.push_resolver("env", &ChildModuleImportResolver); + + let instance = ModuleInstance::new(&module, &imports) + .expect("Module instantation expected to succeed") + .assert_no_start(); + + Self { + instance, + root, + call_stack: Default::default(), + } + } + + pub(super) fn call(&self, name: &str, frame: StackFrame) -> i32 { + let export = self + .instance + .export_by_name(name) + .expect("name doesn't exist in child"); + + let func = export.as_func().expect("name isn't a function"); + + self.call_stack.borrow_mut().push(frame); + + let mut externals = ChildExternals(self); + let result = FuncInstance::invoke(&func, &[], &mut externals) + .expect("function provided by child runtime failed") + .expect("function provided by child runtime did not return a value") + .try_into() + .expect("funtion provided by child runtime return a non-i32 value"); + + self.call_stack.borrow_mut().pop().unwrap(); + + result + } + + fn memory(&self) -> MemoryRef { + self.instance + .export_by_name("memory") + .expect("Module expected to have 'memory' export") + .as_memory() + .cloned() + .expect("'memory' export should be a memory") + } + + fn root(&self) -> RootRuntime<'a> { + self.root + .upgrade() + .expect("root runtime dropped before child") + } + + fn ext_call(&self, args: RuntimeArgs) -> ExtResult { + let memory = self.memory(); + + let name_ptr: u32 = args.nth(0); + let name_len: u32 = args.nth(1); + let name_bytes = memory.get(name_ptr, name_len as usize).unwrap(); + let name = String::from_utf8(name_bytes).unwrap(); + + let arg_ptr: u32 = args.nth(2); + let arg_len: u32 = args.nth(3); + + let ret_ptr: u32 = args.nth(4); + let ret_len: u32 = args.nth(5); + + let frame = StackFrame::builder() + .argument_offset(arg_ptr) + .argument_length(arg_len) + .return_offset(ret_ptr) + .return_length(ret_len) + .memory(memory) + .build(); + + let retcode = self.root().call(&name, frame); + + Ok(Some(retcode.into())) + } + + /// Copies the argument data from the most recent call into memory at the + /// given offtet and length. Returns the actual length of the argument data. + /// + /// # Signature + /// + /// ```text + /// eth2_argument(dest_offset: u32, dest_length: u32) -> u32 + /// ``` + fn ext_argument(&self, args: RuntimeArgs) -> ExtResult { + let memory = self.memory(); + + let dest_ptr: u32 = args.nth(0); + let dest_len: u32 = args.nth(1); + + let call_stack = self.call_stack.borrow(); + let top = call_stack + .last() + .expect("eth2_argument requires a call stack"); + + let len = top.transfer_argument(&memory, dest_ptr, dest_len).unwrap(); + + Ok(Some(len.into())) + } + + /// Copies data from the given offset and length into the buffer allocated + /// by the caller. Returns the total size of the caller's buffer. + /// + /// # Signature + /// + /// ```text + /// eth2_return(offset: u32, length: u32) -> u32 + /// ``` + fn ext_return(&self, args: RuntimeArgs) -> ExtResult { + let memory = self.memory(); + + let src_ptr: u32 = args.nth(0); + let src_len: u32 = args.nth(1); + + let call_stack = self.call_stack.borrow(); + let top = call_stack + .last() + .expect("eth2_return requires a call stack"); + + let len = top.transfer_return(&memory, src_ptr, src_len).unwrap(); + + Ok(Some(len.into())) + } +} + +#[derive(Debug)] +struct ChildExternals<'a, 'b>(&'a ChildRuntime<'b>); + +impl<'a, 'b> Externals for ChildExternals<'a, 'b> { + fn invoke_index( + &mut self, + index: usize, + args: RuntimeArgs, + ) -> Result, Trap> { + match index { + externals::CALL => self.0.ext_call(args), + externals::ARGUMENT => self.0.ext_argument(args), + externals::RETURN => self.0.ext_return(args), + _ => panic!("unknown function index"), + } + } +} diff --git a/src/env/child/resolver.rs b/src/env/child/resolver.rs new file mode 100644 index 0000000..09a5809 --- /dev/null +++ b/src/env/child/resolver.rs @@ -0,0 +1,44 @@ +pub mod externals { + pub const CALL: usize = 1; + pub const ARGUMENT: usize = 2; + pub const RETURN: usize = 3; +} + +use wasmi::{ + Error as InterpreterError, FuncInstance, FuncRef, ModuleImportResolver, Signature, ValueType, +}; + +pub struct ChildModuleImportResolver; + +impl<'a> ModuleImportResolver for ChildModuleImportResolver { + fn resolve_func( + &self, + field_name: &str, + _signature: &Signature, + ) -> Result { + let func_ref = match field_name { + "eth2_return" => FuncInstance::alloc_host( + // eth2_return(offset: u32, length: u32) -> u32 + Signature::new(&[ValueType::I32; 2][..], Some(ValueType::I32)), + externals::RETURN, + ), + "eth2_argument" => FuncInstance::alloc_host( + // eth2_argument(offset: u32, length: u32) -> u32 + Signature::new(&[ValueType::I32; 2][..], Some(ValueType::I32)), + externals::ARGUMENT, + ), + "eth2_call" => FuncInstance::alloc_host( + // eth2_call(name, name_len, arg, arg_len, ret, ret_len) + Signature::new(&[ValueType::I32; 6][..], Some(ValueType::I32)), + externals::CALL, + ), + _ => { + return Err(InterpreterError::Function(format!( + "host module doesn't export function with name {}", + field_name + ))) + } + }; + Ok(func_ref) + } +} diff --git a/src/env/mod.rs b/src/env/mod.rs new file mode 100644 index 0000000..32230e7 --- /dev/null +++ b/src/env/mod.rs @@ -0,0 +1,57 @@ +pub mod child; +pub mod root; + +use typed_builder::TypedBuilder; + +use wasmi::{MemoryInstance, MemoryRef, RuntimeValue, Trap}; + +pub type ExtResult = Result, Trap>; + +#[derive(Debug, Clone, TypedBuilder)] +struct StackFrame { + memory: MemoryRef, + + argument_offset: u32, + argument_length: u32, + + return_offset: u32, + return_length: u32, +} + +impl StackFrame { + pub fn transfer_argument( + &self, + dest: &MemoryRef, + dest_ptr: u32, + dest_len: u32, + ) -> Result { + let len = std::cmp::min(dest_len, self.argument_length); + + MemoryInstance::transfer( + &self.memory, + self.argument_offset as usize, + dest, + dest_ptr as usize, + len as usize, + ) + .map(|_| self.argument_length) + } + + pub fn transfer_return( + &self, + src: &MemoryRef, + src_ptr: u32, + src_len: u32, + ) -> Result { + let len = std::cmp::min(src_len, self.return_length); + + MemoryInstance::transfer( + src, + src_ptr as usize, + &self.memory, + self.return_offset as usize, + len as usize, + ) + .map(|_| self.return_length) + } +} diff --git a/src/env/root/mod.rs b/src/env/root/mod.rs new file mode 100644 index 0000000..c8b9708 --- /dev/null +++ b/src/env/root/mod.rs @@ -0,0 +1,836 @@ +mod resolver; + +use arrayref::array_ref; + +use crate::buffer::Buffer; +use crate::env::child::ChildRuntime; +use crate::execute::Execute; + +use log::debug; + +use self::resolver::{ + RuntimeModuleImportResolver, ARGUMENT_FUNC_INDEX, BLOCKDATACOPY_FUNC_INDEX, + BLOCKDATASIZE_FUNC_INDEX, BUFFERCLEAR_FUNC_INDEX, BUFFERGET_FUNC_INDEX, BUFFERMERGE_FUNC_INDEX, + BUFFERSET_FUNC_INDEX, CALLMODULE_FUNC_INDEX, EXPOSE_FUNC_INDEX, LOADMODULE_FUNC_INDEX, + LOADPRESTATEROOT_FUNC_INDEX, RETURN_FUNC_INDEX, SAVEPOSTSTATEROOT_FUNC_INDEX, +}; + +use std::cell::RefCell; +use std::collections::hash_map::Entry; +use std::collections::{HashMap, HashSet}; +use std::rc::{Rc, Weak}; + +use super::{ExtResult, StackFrame}; + +use wasmi::{ + Externals, FuncInstance, ImportsBuilder, MemoryRef, Module, ModuleInstance, ModuleRef, + RuntimeArgs, RuntimeValue, Trap, +}; + +#[derive(Debug, Clone)] +pub(crate) struct RootRuntimeWeak<'a>(Weak>); + +impl<'a> RootRuntimeWeak<'a> { + pub fn upgrade(&self) -> Option> { + self.0.upgrade().map(RootRuntime) + } +} + +#[derive(Debug, Clone)] +pub struct RootRuntime<'a>(Rc>); + +impl<'a> RootRuntime<'a> { + pub fn new<'b>(code: &'b [u8], data: &'a [u8], pre_root: [u8; 32]) -> RootRuntime<'a> { + let module = Module::from_buffer(code).expect("Module loading to succeed"); + + let mut imports = ImportsBuilder::new(); + imports.push_resolver("env", &RuntimeModuleImportResolver); + + let instance = ModuleInstance::new(&module, &imports) + .expect("Module instantation expected to succeed") + .assert_no_start(); + + RootRuntime(Rc::new(Inner { + instance, + data, + pre_root, + children: Default::default(), + post_root: Default::default(), + call_targets: Default::default(), + call_stack: Default::default(), + buffer: Default::default(), + })) + } + + pub(super) fn call(&self, name: &str, frame: StackFrame) -> i32 { + if !self.0.call_targets.borrow().contains(name) { + panic!("function `{}` is not a safe call target", name); + } + + let export = self + .0 + .instance + .export_by_name(name) + .expect("Exposed name doesn't exist"); + + let func = export.as_func().expect("Exposed name isn't a function"); + + self.0.call_stack.borrow_mut().push(frame); + + let mut externals = RootExternals(self); + + let result = FuncInstance::invoke(&func, &[], &mut externals) + .expect("function provided by root runtime failed") + .expect("function provided by root runtime did not return a value") + .try_into() + .expect("funtion provided by rooot runtime return a non-i32 value"); + + self.0.call_stack.borrow_mut().pop().unwrap(); + + result + } + + fn memory(&self) -> MemoryRef { + self.0 + .instance + .export_by_name("memory") + .expect("Module expected to have 'memory' export") + .as_memory() + .cloned() + .expect("'memory' export should be a memory") + } + + pub(crate) fn downgrade(&self) -> RootRuntimeWeak<'a> { + RootRuntimeWeak(Rc::downgrade(&self.0)) + } + + /// Copies data from the given offset and length into the buffer allocated + /// by the caller. Returns the total size of the caller's buffer. + /// + /// # Signature + /// + /// ```text + /// eth2_return(offset: u32, length: u32) -> u32 + /// ``` + fn ext_return(&self, args: RuntimeArgs) -> ExtResult { + let memory = self.memory(); + + let src_ptr: u32 = args.nth(0); + let src_len: u32 = args.nth(1); + + let call_stack = self.0.call_stack.borrow(); + let top = call_stack + .last() + .expect("eth2_return requires a call stack"); + + let len = top.transfer_return(&memory, src_ptr, src_len).unwrap(); + + Ok(Some(len.into())) + } + + /// Copies the argument data from the most recent call into memory at the + /// given offtet and length. Returns the actual length of the argument data. + /// + /// # Signature + /// + /// ```text + /// eth2_argument(dest_offset: u32, dest_length: u32) -> u32 + /// ``` + fn ext_argument(&self, args: RuntimeArgs) -> ExtResult { + let memory = self.memory(); + + let dest_ptr: u32 = args.nth(0); + let dest_len: u32 = args.nth(1); + + let call_stack = self.0.call_stack.borrow(); + let top = call_stack + .last() + .expect("eth2_argument requires a call stack"); + + let len = top.transfer_argument(&memory, dest_ptr, dest_len).unwrap(); + + Ok(Some(len.into())) + } + + fn ext_expose(&self, args: RuntimeArgs) -> ExtResult { + let memory = self.memory(); + + let name_ptr: u32 = args.nth(0); + let name_len: u32 = args.nth(1); + let name_bytes = memory.get(name_ptr, name_len as usize).unwrap(); + let name = String::from_utf8(name_bytes).unwrap(); + + self.0.call_targets.borrow_mut().insert(name); + + Ok(None) + } + + fn ext_load_pre_state_root(&self, args: RuntimeArgs) -> ExtResult { + let ptr: u32 = args.nth(0); + + debug!("loadprestateroot to {}", ptr); + + // TODO: add checks for out of bounds access + let memory = self.memory(); + memory + .set(ptr, &self.0.pre_root[..]) + .expect("expects writing to memory to succeed"); + + Ok(None) + } + + fn ext_save_post_state_root(&self, args: RuntimeArgs) -> ExtResult { + let ptr: u32 = args.nth(0); + debug!("savepoststateroot from {}", ptr); + + // TODO: add checks for out of bounds access + let mut post_root = self.0.post_root.borrow_mut(); + let memory = self.memory(); + memory + .get_into(ptr, &mut post_root[..]) + .expect("expects reading from memory to succeed"); + + Ok(None) + } + + fn ext_block_data_size(&self, _: RuntimeArgs) -> ExtResult { + let ret: i32 = self.0.data.len() as i32; + debug!("blockdatasize {}", ret); + Ok(Some(ret.into())) + } + + fn ext_block_data_copy(&self, args: RuntimeArgs) -> ExtResult { + let ptr: u32 = args.nth(0); + let offset: u32 = args.nth(1); + let length: u32 = args.nth(2); + debug!( + "blockdatacopy to {} from {} for {} bytes", + ptr, offset, length + ); + + // TODO: add overflow check + let offset = offset as usize; + let length = length as usize; + + // TODO: add checks for out of bounds access + let memory = self.memory(); + memory + .set(ptr, &self.0.data[offset..length]) + .expect("expects writing to memory to succeed"); + + Ok(None) + } + + fn ext_buffer_get(&self, args: RuntimeArgs) -> ExtResult { + let frame: u32 = args.nth(0); + let key_ptr: u32 = args.nth(1); + let value_ptr: u32 = args.nth(2); + + debug!( + "bufferget for frame {} with key at {}, and returning the value to {}", + frame, key_ptr, value_ptr + ); + + // TODO: add overflow check + let frame = frame as u8; + + // TODO: add checks for out of bounds access + let memory = self.memory(); + + let key = memory.get(key_ptr, 32).expect("read to suceed"); + let key = *array_ref![key, 0, 32]; + + if let Some(value) = self.0.buffer.borrow().get(frame, key) { + memory + .set(value_ptr, value) + .expect("writing to memory to succeed"); + + Ok(Some(0.into())) + } else { + Ok(Some(1.into())) + } + } + + fn ext_buffer_set(&self, args: RuntimeArgs) -> ExtResult { + let frame: u32 = args.nth(0); + let key_ptr: u32 = args.nth(1); + let value_ptr: u32 = args.nth(2); + + debug!( + "bufferset for frame {} with key at {} and value at {}", + frame, key_ptr, value_ptr + ); + + // TODO: add overflow check + let frame = frame as u8; + + // TODO: add checks for out of bounds access + let memory = self.memory(); + + let key = memory.get(key_ptr, 32).expect("read to suceed"); + let key = *array_ref![key, 0, 32]; + + let value = memory.get(value_ptr, 32).expect("read to suceed"); + let value = *array_ref![value, 0, 32]; + + self.0.buffer.borrow_mut().insert(frame, key, value); + + Ok(None) + } + + fn ext_buffer_merge(&self, args: RuntimeArgs) -> ExtResult { + let frame_a: u32 = args.nth(0); + let frame_b: u32 = args.nth(1); + + debug!("buffermerge frame {} into frame {}", frame_b, frame_a); + + // TODO: add overflow check + let frame_a = frame_a as u8; + let frame_b = frame_b as u8; + + self.0.buffer.borrow_mut().merge(frame_a, frame_b); + + Ok(None) + } + + fn ext_buffer_clear(&self, args: RuntimeArgs) -> ExtResult { + let frame: u32 = args.nth(0); + + // TODO: add overflow check + let frame = frame as u8; + + debug!("bufferclear on frame {}", frame); + + self.0.buffer.borrow_mut().clear(frame); + + Ok(None) + } + + /// Loads a compiled Wasm module from memory into the slot specified. + /// + /// # Signature + /// + /// ```text + /// eth2_loadModule(slot: u32, code_offset: u32, code_length: u32) -> () + /// ``` + fn ext_load_module(&self, args: RuntimeArgs) -> ExtResult { + let slot: u32 = args.nth(0); + let code_ptr: u32 = args.nth(1); + let code_len: u32 = args.nth(2); + + debug!( + "load module 0x{:x} ({} bytes) into {}", + code_ptr, code_len, slot + ); + + let mut children = self.0.children.borrow_mut(); + + let entry = match children.entry(slot) { + Entry::Occupied(_) => panic!("reusing module slot identifiers not supported"), + Entry::Vacant(x) => x, + }; + + let memory = self.memory(); + let code = memory.get(code_ptr, code_len as usize).unwrap(); + + let child = ChildRuntime::new(self.downgrade(), &code); + entry.insert(child); + + Ok(None) + } + + /// Calls the function `name` from the module in `slot`. + /// + /// # Signature + /// + /// ```text + /// eth2_callModule( + /// slot: u32, + /// name_offset: u32, + /// name_length: u32 + /// argument_offset: u32, + /// argument_length: u32, + /// return_offset: u32, + /// return_length: u32, + /// ) -> u32 + /// ``` + fn ext_call_module(&self, args: RuntimeArgs) -> ExtResult { + let memory = self.memory(); + + let slot: u32 = args.nth(0); + + let name_ptr: u32 = args.nth(1); + let name_len: u32 = args.nth(2); + let name_bytes = memory.get(name_ptr, name_len as usize).unwrap(); + let name = String::from_utf8(name_bytes).unwrap(); + + let arg_ptr: u32 = args.nth(3); + let arg_len: u32 = args.nth(4); + + let ret_ptr: u32 = args.nth(5); + let ret_len: u32 = args.nth(6); + + let frame = StackFrame::builder() + .argument_offset(arg_ptr) + .argument_length(arg_len) + .return_offset(ret_ptr) + .return_length(ret_len) + .memory(memory) + .build(); + + // TODO: There's probably a bug here. It might be impossible to load a + // new module depending on the callstack. + + let children = self.0.children.borrow(); + let retcode = children[&slot].call(&name, frame); + + Ok(Some(retcode.into())) + } +} + +#[derive(Debug)] +struct Inner<'a> { + data: &'a [u8], + pre_root: [u8; 32], + post_root: RefCell<[u8; 32]>, + instance: ModuleRef, + buffer: RefCell, + + children: RefCell>>, + + call_targets: RefCell>, + call_stack: RefCell>, +} + +impl<'a> Execute for RootRuntime<'a> { + fn execute(&mut self) -> [u8; 32] { + let mut externals = RootExternals(self); + + self.0 + .instance + .invoke_export("main", &[], &mut externals) + .expect("Executed 'main'"); + + *self.0.post_root.borrow() + } +} + +#[derive(Debug)] +struct RootExternals<'a, 'b>(&'a RootRuntime<'b>); + +impl<'a, 'b> Externals for RootExternals<'a, 'b> { + fn invoke_index( + &mut self, + index: usize, + args: RuntimeArgs, + ) -> Result, Trap> { + match index { + LOADPRESTATEROOT_FUNC_INDEX => self.0.ext_load_pre_state_root(args), + SAVEPOSTSTATEROOT_FUNC_INDEX => self.0.ext_save_post_state_root(args), + BLOCKDATASIZE_FUNC_INDEX => self.0.ext_block_data_size(args), + BLOCKDATACOPY_FUNC_INDEX => self.0.ext_block_data_copy(args), + BUFFERGET_FUNC_INDEX => self.0.ext_buffer_get(args), + BUFFERSET_FUNC_INDEX => self.0.ext_buffer_set(args), + BUFFERMERGE_FUNC_INDEX => self.0.ext_buffer_merge(args), + BUFFERCLEAR_FUNC_INDEX => self.0.ext_buffer_clear(args), + LOADMODULE_FUNC_INDEX => self.0.ext_load_module(args), + CALLMODULE_FUNC_INDEX => self.0.ext_call_module(args), + EXPOSE_FUNC_INDEX => self.0.ext_expose(args), + ARGUMENT_FUNC_INDEX => self.0.ext_argument(args), + RETURN_FUNC_INDEX => self.0.ext_return(args), + _ => panic!("unknown function index"), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::buffer::Buffer; + use lazy_static::lazy_static; + use wabt::wat2wasm; + use wasmi::memory_units::Pages; + use wasmi::MemoryInstance; + + lazy_static! { + static ref NOP: Vec = wat2wasm( + r#" + (module + (memory (export "memory") 1) + (func $main (export "main") (nop))) + "# + ) + .unwrap(); + } + + fn build_root(n: u8) -> [u8; 32] { + let mut ret = [0u8; 32]; + ret[0] = n; + ret + } + + fn build_runtime<'a>(data: &'a [u8], pre_root: [u8; 32], buffer: Buffer) -> RootRuntime<'a> { + let mut rt = RootRuntime::new(&NOP, data, pre_root); + Rc::get_mut(&mut rt.0).unwrap().buffer = buffer.into(); + rt + } + + #[test] + fn return_long_value_does_not_overwrite() { + let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); + + let runtime = build_runtime(&[], build_root(42), Buffer::default()); + runtime.memory().set(0, &[45, 99, 7]).unwrap(); + + let frame = StackFrame::builder() + .argument_offset(0u32) + .argument_length(0u32) + .return_offset(0u32) + .return_length(2u32) + .memory(memory.clone()) + .build(); + + runtime.0.call_stack.borrow_mut().push(frame); + + let mut externals = RootExternals(&runtime); + let result: u32 = Externals::invoke_index( + &mut externals, + RETURN_FUNC_INDEX, + [0.into(), 3.into()][..].into(), + ) + .expect("trap while calling return") + .expect("return did not return a result") + .try_into() + .expect("return did not return an integer"); + + assert_eq!(result, 2); + assert_eq!(memory.get(0, 3).unwrap(), [45, 99, 0]); + } + + #[test] + fn return_copies_value_into_parent_frame() { + let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); + + let runtime = build_runtime(&[], build_root(42), Buffer::default()); + runtime.memory().set(0, &[45]).unwrap(); + + let frame = StackFrame::builder() + .argument_offset(0u32) + .argument_length(0u32) + .return_offset(0u32) + .return_length(2u32) + .memory(memory.clone()) + .build(); + + runtime.0.call_stack.borrow_mut().push(frame); + + let mut externals = RootExternals(&runtime); + let result: u32 = Externals::invoke_index( + &mut externals, + RETURN_FUNC_INDEX, + [0.into(), 1.into()][..].into(), + ) + .expect("trap while calling return") + .expect("return did not return a result") + .try_into() + .expect("return did not return an integer"); + + assert_eq!(result, 2); + assert_eq!(memory.get(0, 1).unwrap(), [45]); + } + + #[test] + fn return_provides_buffer_size() { + let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); + let runtime = build_runtime(&[], build_root(42), Buffer::default()); + + let frame = StackFrame::builder() + .argument_offset(0u32) + .argument_length(0u32) + .return_offset(0u32) + .return_length(2u32) + .memory(memory.clone()) + .build(); + + runtime.0.call_stack.borrow_mut().push(frame); + + let mut externals = RootExternals(&runtime); + let result: u32 = Externals::invoke_index( + &mut externals, + RETURN_FUNC_INDEX, + [0.into(), 0.into()][..].into(), + ) + .expect("trap while calling return") + .expect("return did not return a result") + .try_into() + .expect("return did not return an integer"); + + assert_eq!(result, 2); + } + + #[test] + fn argument_provides_buffer_size() { + let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); + let runtime = build_runtime(&[], build_root(42), Buffer::default()); + + let frame = StackFrame::builder() + .return_offset(0u32) + .return_length(0u32) + .argument_offset(0u32) + .argument_length(2u32) + .memory(memory.clone()) + .build(); + + runtime.0.call_stack.borrow_mut().push(frame); + + let mut externals = RootExternals(&runtime); + let result: u32 = Externals::invoke_index( + &mut externals, + ARGUMENT_FUNC_INDEX, + [0.into(), 0.into()][..].into(), + ) + .expect("trap while calling argument") + .expect("argument did not return a result") + .try_into() + .expect("argument did not return an integer"); + + assert_eq!(result, 2); + } + + #[test] + fn argument_copies_value_from_parent_frame() { + let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); + memory.set(0, &[32, 123]).unwrap(); + + let runtime = build_runtime(&[], build_root(42), Buffer::default()); + runtime.memory().set(0, &[32, 45]).unwrap(); + + let frame = StackFrame::builder() + .argument_offset(0u32) + .argument_length(2u32) + .return_offset(0u32) + .return_length(0u32) + .memory(memory.clone()) + .build(); + + runtime.0.call_stack.borrow_mut().push(frame); + + let mut externals = RootExternals(&runtime); + let result: u32 = Externals::invoke_index( + &mut externals, + ARGUMENT_FUNC_INDEX, + [0.into(), 1.into()][..].into(), + ) + .expect("trap while calling return") + .expect("return did not return a result") + .try_into() + .expect("return did not return an integer"); + + assert_eq!(result, 2); + assert_eq!(runtime.memory().get(0, 2).unwrap(), [32, 45]); + } + + #[test] + fn argument_long_value_does_not_leak() { + let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); + memory.set(0, &[32, 123, 234]).unwrap(); + + let runtime = build_runtime(&[], build_root(42), Buffer::default()); + runtime.memory().set(0, &[45, 45, 45]).unwrap(); + + let frame = StackFrame::builder() + .argument_offset(0u32) + .argument_length(2u32) + .return_offset(0u32) + .return_length(0u32) + .memory(memory.clone()) + .build(); + + runtime.0.call_stack.borrow_mut().push(frame); + + let mut externals = RootExternals(&runtime); + let result: u32 = Externals::invoke_index( + &mut externals, + ARGUMENT_FUNC_INDEX, + [0.into(), 3.into()][..].into(), + ) + .expect("trap while calling return") + .expect("return did not return a result") + .try_into() + .expect("return did not return an integer"); + + assert_eq!(result, 2); + assert_eq!(runtime.memory().get(0, 3).unwrap(), [32, 123, 45]); + } + + #[test] + fn load_pre_state_root() { + let runtime = build_runtime(&[], build_root(42), Buffer::default()); + + let mut externals = RootExternals(&runtime); + Externals::invoke_index( + &mut externals, + LOADPRESTATEROOT_FUNC_INDEX, + [0.into()][..].into(), + ) + .unwrap(); + + assert_eq!(runtime.memory().get(0, 32).unwrap(), build_root(42)); + } + + #[test] + fn save_post_state_root() { + let runtime = build_runtime(&[], build_root(0), Buffer::default()); + + let memory = runtime.memory(); + memory.set(100, &build_root(42)).expect("sets memory"); + + let mut externals = RootExternals(&runtime); + Externals::invoke_index( + &mut externals, + SAVEPOSTSTATEROOT_FUNC_INDEX, + [100.into()][..].into(), + ) + .unwrap(); + + assert_eq!(runtime.memory().get(100, 32).unwrap(), build_root(42)); + } + + #[test] + fn block_data_size() { + let runtime = build_runtime(&[1; 42], build_root(0), Buffer::default()); + + let mut externals = RootExternals(&runtime); + assert_eq!( + Externals::invoke_index(&mut externals, BLOCKDATASIZE_FUNC_INDEX, [][..].into()) + .unwrap() + .unwrap(), + 42.into() + ); + } + + #[test] + fn block_data_copy() { + let data: Vec = (1..21).collect(); + let runtime = build_runtime(&data, build_root(0), Buffer::default()); + + let mut externals = RootExternals(&runtime); + Externals::invoke_index( + &mut externals, + BLOCKDATACOPY_FUNC_INDEX, + [1.into(), 0.into(), 20.into()][..].into(), + ) + .unwrap(); + + let mut externals = RootExternals(&runtime); + Externals::invoke_index( + &mut externals, + BLOCKDATACOPY_FUNC_INDEX, + [23.into(), 10.into(), 20.into()][..].into(), + ) + .unwrap(); + + // This checks that the entire data blob was loaded into memory. + assert_eq!(runtime.clone().memory().get(1, 20).unwrap(), data); + + // This checks that the data after the offset was loaded into memory. + assert_eq!(runtime.memory().get(23, 10).unwrap()[..], data[10..]); + } + + #[test] + fn buffer_get() { + let mut buffer = Buffer::default(); + + // Insert a value into the buffer that corresponds to the above key. + buffer.insert(0, [1u8; 32], build_root(42)); + + let runtime = build_runtime(&[], build_root(0), buffer); + + let memory = runtime.memory(); + + // Save the 32 byte key at position 0 in memory + memory.set(0, &[1u8; 32]).unwrap(); + + let mut externals = RootExternals(&runtime); + Externals::invoke_index( + &mut externals, + BUFFERGET_FUNC_INDEX, + [0.into(), 0.into(), 32.into()][..].into(), + ) + .unwrap(); + + assert_eq!( + runtime.clone().memory().get(32, 32).unwrap(), + build_root(42) + ); + } + + #[test] + fn buffer_set() { + let runtime = build_runtime(&[], build_root(0), Buffer::default()); + + let memory = runtime.memory(); + memory.set(0, &[1u8; 32]).unwrap(); + memory.set(32, &[2u8; 32]).unwrap(); + + let mut externals = RootExternals(&runtime); + Externals::invoke_index( + &mut externals, + BUFFERSET_FUNC_INDEX, + [0.into(), 0.into(), 32.into()][..].into(), + ) + .unwrap(); + + let buffer = runtime.0.buffer.borrow(); + assert_eq!(buffer.get(0, [1u8; 32]), Some(&[2u8; 32])); + } + + #[test] + fn buffer_merge() { + let mut buffer = Buffer::default(); + + buffer.insert(1, [0u8; 32], [0u8; 32]); + buffer.insert(1, [1u8; 32], [1u8; 32]); + buffer.insert(2, [2u8; 32], [2u8; 32]); + buffer.insert(2, [0u8; 32], [3u8; 32]); + + let runtime = build_runtime(&[], build_root(0), buffer); + + let mut externals = RootExternals(&runtime); + Externals::invoke_index( + &mut externals, + BUFFERMERGE_FUNC_INDEX, + [1.into(), 2.into()][..].into(), + ) + .unwrap(); + + let buffer = runtime.0.buffer.borrow(); + assert_eq!(buffer.get(1, [0u8; 32]), Some(&[3u8; 32])); + assert_eq!(buffer.get(1, [1u8; 32]), Some(&[1u8; 32])); + assert_eq!(buffer.get(1, [2u8; 32]), Some(&[2u8; 32])); + assert_eq!(buffer.get(2, [0u8; 32]), Some(&[3u8; 32])); + assert_eq!(buffer.get(2, [2u8; 32]), Some(&[2u8; 32])); + } + + #[test] + fn buffer_clear() { + let mut buffer = Buffer::default(); + + buffer.insert(1, [0u8; 32], [0u8; 32]); + buffer.insert(2, [0u8; 32], [0u8; 32]); + + let runtime = build_runtime(&[], build_root(0), buffer); + + let mut externals = RootExternals(&runtime); + Externals::invoke_index( + &mut externals, + BUFFERCLEAR_FUNC_INDEX, + [2.into()][..].into(), + ) + .unwrap(); + + let buffer = runtime.0.buffer.borrow(); + assert_eq!(buffer.get(1, [0u8; 32]), Some(&[0u8; 32])); + assert_eq!(buffer.get(2, [0u8; 32]), None); + } +} diff --git a/src/resolver.rs b/src/env/root/resolver.rs similarity index 69% rename from src/resolver.rs rename to src/env/root/resolver.rs index d9146af..6e3ee76 100644 --- a/src/resolver.rs +++ b/src/env/root/resolver.rs @@ -10,6 +10,11 @@ pub const BUFFERGET_FUNC_INDEX: usize = 4; pub const BUFFERSET_FUNC_INDEX: usize = 5; pub const BUFFERMERGE_FUNC_INDEX: usize = 6; pub const BUFFERCLEAR_FUNC_INDEX: usize = 7; +pub const LOADMODULE_FUNC_INDEX: usize = 8; +pub const EXPOSE_FUNC_INDEX: usize = 9; +pub const ARGUMENT_FUNC_INDEX: usize = 10; +pub const RETURN_FUNC_INDEX: usize = 11; +pub const CALLMODULE_FUNC_INDEX: usize = 12; pub struct RuntimeModuleImportResolver; @@ -55,6 +60,26 @@ impl<'a> ModuleImportResolver for RuntimeModuleImportResolver { Signature::new(&[ValueType::I32][..], None), BUFFERCLEAR_FUNC_INDEX, ), + "eth2_loadModule" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 3][..], None), + LOADMODULE_FUNC_INDEX, + ), + "eth2_callModule" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32; 7][..], Some(ValueType::I32)), + CALLMODULE_FUNC_INDEX, + ), + "eth2_expose" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32, ValueType::I32][..], None), + EXPOSE_FUNC_INDEX, + ), + "eth2_argument" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32, ValueType::I32][..], Some(ValueType::I32)), + ARGUMENT_FUNC_INDEX, + ), + "eth2_return" => FuncInstance::alloc_host( + Signature::new(&[ValueType::I32, ValueType::I32][..], Some(ValueType::I32)), + RETURN_FUNC_INDEX, + ), _ => { return Err(InterpreterError::Function(format!( "host module doesn't export function with name {}", diff --git a/src/execute.rs b/src/execute.rs index ef027bd..aaa8f28 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -1,3 +1,3 @@ -pub trait Execute<'a> { - fn execute(&'a mut self) -> [u8; 32]; +pub trait Execute { + fn execute(&mut self) -> [u8; 32]; } diff --git a/src/lib.rs b/src/lib.rs index e74f088..76b925d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,6 @@ mod buffer; +mod env; mod execute; -mod resolver; -mod runtime; +pub use env::root::RootRuntime; pub use execute::Execute; -pub use runtime::Runtime; diff --git a/src/runtime.rs b/src/runtime.rs deleted file mode 100644 index e903321..0000000 --- a/src/runtime.rs +++ /dev/null @@ -1,423 +0,0 @@ -use arrayref::array_ref; - -use crate::buffer::Buffer; -use crate::execute::Execute; -use crate::resolver::{ - RuntimeModuleImportResolver, BLOCKDATACOPY_FUNC_INDEX, BLOCKDATASIZE_FUNC_INDEX, - BUFFERCLEAR_FUNC_INDEX, BUFFERGET_FUNC_INDEX, BUFFERMERGE_FUNC_INDEX, BUFFERSET_FUNC_INDEX, - LOADPRESTATEROOT_FUNC_INDEX, SAVEPOSTSTATEROOT_FUNC_INDEX, -}; - -use log::debug; - -use wasmi::{ - Externals, ImportsBuilder, MemoryRef, Module, ModuleInstance, RuntimeArgs, RuntimeValue, Trap, -}; - -pub type ExtResult = Result, Trap>; - -#[derive(Clone)] -pub struct Runtime<'a> { - pub(crate) code: &'a [u8], - pub(crate) data: &'a [u8], - pub(crate) pre_root: [u8; 32], - pub(crate) post_root: [u8; 32], - pub(crate) memory: Option, - pub(crate) buffer: Buffer, -} - -impl<'a> Runtime<'a> { - pub fn new(code: &'a [u8], data: &'a [u8], pre_root: [u8; 32]) -> Runtime<'a> { - Runtime { - code, - data, - pre_root, - post_root: [0u8; 32], - memory: None, - buffer: Buffer::default(), - } - } - - fn ext_load_pre_state_root(&mut self, args: RuntimeArgs) -> ExtResult { - let ptr: u32 = args.nth(0); - - debug!("loadprestateroot to {}", ptr); - - // TODO: add checks for out of bounds access - let memory = self.memory.as_ref().expect("expects memory object"); - memory - .set(ptr, &self.pre_root[..]) - .expect("expects writing to memory to succeed"); - - Ok(None) - } - - fn ext_save_post_state_root(&mut self, args: RuntimeArgs) -> ExtResult { - let ptr: u32 = args.nth(0); - debug!("savepoststateroot from {}", ptr); - - // TODO: add checks for out of bounds access - let memory = self.memory.as_ref().expect("expects memory object"); - memory - .get_into(ptr, &mut self.post_root[..]) - .expect("expects reading from memory to succeed"); - - Ok(None) - } - - fn ext_block_data_size(&mut self, _: RuntimeArgs) -> ExtResult { - let ret: i32 = self.data.len() as i32; - debug!("blockdatasize {}", ret); - Ok(Some(ret.into())) - } - - fn ext_block_data_copy(&mut self, args: RuntimeArgs) -> ExtResult { - let ptr: u32 = args.nth(0); - let offset: u32 = args.nth(1); - let length: u32 = args.nth(2); - debug!( - "blockdatacopy to {} from {} for {} bytes", - ptr, offset, length - ); - - // TODO: add overflow check - let offset = offset as usize; - let length = length as usize; - - // TODO: add checks for out of bounds access - let memory = self.memory.as_ref().expect("expects memory object"); - memory - .set(ptr, &self.data[offset..length]) - .expect("expects writing to memory to succeed"); - - Ok(None) - } - - fn ext_buffer_get(&mut self, args: RuntimeArgs) -> ExtResult { - let frame: u32 = args.nth(0); - let key_ptr: u32 = args.nth(1); - let value_ptr: u32 = args.nth(2); - - debug!( - "bufferget for frame {} with key at {}, and returning the value to {}", - frame, key_ptr, value_ptr - ); - - // TODO: add overflow check - let frame = frame as u8; - - // TODO: add checks for out of bounds access - let memory = self.memory.as_ref().expect("expects memory object"); - - let key = memory.get(key_ptr, 32).expect("read to suceed"); - let key = *array_ref![key, 0, 32]; - - if let Some(value) = self.buffer.get(frame, key) { - memory - .set(value_ptr, value) - .expect("writing to memory to succeed"); - - Ok(Some(0.into())) - } else { - Ok(Some(1.into())) - } - } - - fn ext_buffer_set(&mut self, args: RuntimeArgs) -> ExtResult { - let frame: u32 = args.nth(0); - let key_ptr: u32 = args.nth(1); - let value_ptr: u32 = args.nth(2); - - debug!( - "bufferset for frame {} with key at {} and value at {}", - frame, key_ptr, value_ptr - ); - - // TODO: add overflow check - let frame = frame as u8; - - // TODO: add checks for out of bounds access - let memory = self.memory.as_ref().expect("expects memory object"); - - let key = memory.get(key_ptr, 32).expect("read to suceed"); - let key = *array_ref![key, 0, 32]; - - let value = memory.get(value_ptr, 32).expect("read to suceed"); - let value = *array_ref![value, 0, 32]; - - self.buffer.insert(frame, key, value); - - Ok(None) - } - - fn ext_buffer_merge(&mut self, args: RuntimeArgs) -> ExtResult { - let frame_a: u32 = args.nth(0); - let frame_b: u32 = args.nth(1); - - debug!("buffermerge frame {} into frame {}", frame_b, frame_a); - - // TODO: add overflow check - let frame_a = frame_a as u8; - let frame_b = frame_b as u8; - - self.buffer.merge(frame_a, frame_b); - - Ok(None) - } - - fn ext_buffer_clear(&mut self, args: RuntimeArgs) -> ExtResult { - let frame: u32 = args.nth(0); - - // TODO: add overflow check - let frame = frame as u8; - - debug!("bufferclear on frame {}", frame); - - self.buffer.clear(frame); - - Ok(None) - } -} - -impl<'a> Execute<'a> for Runtime<'a> { - fn execute(&'a mut self) -> [u8; 32] { - let module = Module::from_buffer(self.code).expect("Module loading to succeed"); - let mut imports = ImportsBuilder::new(); - imports.push_resolver("env", &RuntimeModuleImportResolver); - - let instance = ModuleInstance::new(&module, &imports) - .expect("Module instantation expected to succeed") - .assert_no_start(); - - self.memory = Some( - instance - .export_by_name("memory") - .expect("Module expected to have 'memory' export") - .as_memory() - .cloned() - .expect("'memory' export should be a memory"), - ); - - instance - .invoke_export("main", &[], self) - .expect("Executed 'main'"); - - self.post_root - } -} - -impl<'a> Externals for Runtime<'a> { - fn invoke_index( - &mut self, - index: usize, - args: RuntimeArgs, - ) -> Result, Trap> { - match index { - LOADPRESTATEROOT_FUNC_INDEX => self.ext_load_pre_state_root(args), - SAVEPOSTSTATEROOT_FUNC_INDEX => self.ext_save_post_state_root(args), - BLOCKDATASIZE_FUNC_INDEX => self.ext_block_data_size(args), - BLOCKDATACOPY_FUNC_INDEX => self.ext_block_data_copy(args), - BUFFERGET_FUNC_INDEX => self.ext_buffer_get(args), - BUFFERSET_FUNC_INDEX => self.ext_buffer_set(args), - BUFFERMERGE_FUNC_INDEX => self.ext_buffer_merge(args), - BUFFERCLEAR_FUNC_INDEX => self.ext_buffer_clear(args), - _ => panic!("unknown function index"), - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::buffer::Buffer; - use wasmi::memory_units::Pages; - use wasmi::{MemoryInstance, MemoryRef}; - - fn build_root(n: u8) -> [u8; 32] { - let mut ret = [0u8; 32]; - ret[0] = n; - ret - } - - fn build_runtime<'a>( - data: &'a [u8], - pre_root: [u8; 32], - memory: MemoryRef, - buffer: Buffer, - ) -> Runtime<'a> { - Runtime { - code: &[], - data: data, - pre_root, - post_root: [0; 32], - memory: Some(memory), - buffer, - } - } - - #[test] - fn load_pre_state_root() { - let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); - let mut runtime = build_runtime(&[], build_root(42), memory, Buffer::default()); - - assert!(Externals::invoke_index( - &mut runtime, - LOADPRESTATEROOT_FUNC_INDEX, - [0.into()][..].into() - ) - .is_ok()); - - assert_eq!(runtime.memory.unwrap().get(0, 32).unwrap(), build_root(42)); - } - - #[test] - fn save_post_state_root() { - let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); - memory.set(100, &build_root(42)).expect("sets memory"); - - let mut runtime = build_runtime(&[], build_root(0), memory, Buffer::default()); - - assert!(Externals::invoke_index( - &mut runtime, - SAVEPOSTSTATEROOT_FUNC_INDEX, - [100.into()][..].into() - ) - .is_ok()); - - assert_eq!( - runtime.memory.unwrap().get(100, 32).unwrap(), - build_root(42) - ); - } - - #[test] - fn block_data_size() { - let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); - let mut runtime = build_runtime(&[1; 42], build_root(0), memory, Buffer::default()); - - assert_eq!( - Externals::invoke_index(&mut runtime, BLOCKDATASIZE_FUNC_INDEX, [][..].into()) - .unwrap() - .unwrap(), - 42.into() - ); - } - - #[test] - fn block_data_copy() { - let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); - let data: Vec = (1..21).collect(); - let mut runtime = build_runtime(&data, build_root(0), memory, Buffer::default()); - - assert!(Externals::invoke_index( - &mut runtime, - BLOCKDATACOPY_FUNC_INDEX, - [1.into(), 0.into(), 20.into()][..].into() - ) - .is_ok()); - - assert!(Externals::invoke_index( - &mut runtime, - BLOCKDATACOPY_FUNC_INDEX, - [23.into(), 10.into(), 20.into()][..].into() - ) - .is_ok()); - - // This checks that the entire data blob was loaded into memory. - assert_eq!(runtime.clone().memory.unwrap().get(1, 20).unwrap(), data); - - // This checks that the data after the offset was loaded into memory. - assert_eq!(runtime.memory.unwrap().get(23, 10).unwrap()[..], data[10..]); - } - - #[test] - fn buffer_get() { - let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); - let mut buffer = Buffer::default(); - - // Save the 32 byte key at position 0 in memory - memory.set(0, &[1u8; 32]).unwrap(); - - // Insert a value into the buffer that corresponds to the above key. - buffer.insert(0, [1u8; 32], build_root(42)); - - let mut runtime = build_runtime(&[], build_root(0), memory, buffer); - - assert!(Externals::invoke_index( - &mut runtime, - BUFFERGET_FUNC_INDEX, - [0.into(), 0.into(), 32.into()][..].into() - ) - .is_ok()); - - assert_eq!( - runtime.clone().memory.unwrap().get(32, 32).unwrap(), - build_root(42) - ); - } - - #[test] - fn buffer_set() { - let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); - memory.set(0, &[1u8; 32]).unwrap(); - memory.set(32, &[2u8; 32]).unwrap(); - - let mut runtime = build_runtime(&[], build_root(0), memory, Buffer::default()); - - assert!(Externals::invoke_index( - &mut runtime, - BUFFERSET_FUNC_INDEX, - [0.into(), 0.into(), 32.into()][..].into() - ) - .is_ok()); - - assert_eq!(runtime.buffer.get(0, [1u8; 32]), Some(&[2u8; 32])); - } - - #[test] - fn buffer_merge() { - let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); - let mut buffer = Buffer::default(); - - buffer.insert(1, [0u8; 32], [0u8; 32]); - buffer.insert(1, [1u8; 32], [1u8; 32]); - buffer.insert(2, [2u8; 32], [2u8; 32]); - buffer.insert(2, [0u8; 32], [3u8; 32]); - - let mut runtime = build_runtime(&[], build_root(0), memory, buffer); - - assert!(Externals::invoke_index( - &mut runtime, - BUFFERMERGE_FUNC_INDEX, - [1.into(), 2.into()][..].into() - ) - .is_ok()); - - assert_eq!(runtime.buffer.get(1, [0u8; 32]), Some(&[3u8; 32])); - assert_eq!(runtime.buffer.get(1, [1u8; 32]), Some(&[1u8; 32])); - assert_eq!(runtime.buffer.get(1, [2u8; 32]), Some(&[2u8; 32])); - assert_eq!(runtime.buffer.get(2, [0u8; 32]), Some(&[3u8; 32])); - assert_eq!(runtime.buffer.get(2, [2u8; 32]), Some(&[2u8; 32])); - } - - #[test] - fn buffer_clear() { - let memory = MemoryInstance::alloc(Pages(1), None).unwrap(); - let mut buffer = Buffer::default(); - - buffer.insert(1, [0u8; 32], [0u8; 32]); - buffer.insert(2, [0u8; 32], [0u8; 32]); - - let mut runtime = build_runtime(&[], build_root(0), memory, buffer); - - assert!(Externals::invoke_index( - &mut runtime, - BUFFERCLEAR_FUNC_INDEX, - [2.into()][..].into() - ) - .is_ok()); - - assert_eq!(runtime.buffer.get(1, [0u8; 32]), Some(&[0u8; 32])); - assert_eq!(runtime.buffer.get(2, [0u8; 32]), None); - } -} diff --git a/tests/child_functions.rs b/tests/child_functions.rs new file mode 100644 index 0000000..f04e8d3 --- /dev/null +++ b/tests/child_functions.rs @@ -0,0 +1,152 @@ +mod utils; + +use ewasm::{Execute, RootRuntime}; +use utils::escape; +use wabt::wat2wasm; + +fn compile_wat(child_code: &str) -> Vec { + let child_asm = wat2wasm(child_code).unwrap(); + + wat2wasm(format!( + r#" + (module + (import "env" "eth2_loadModule" (func $load (param i32) (param i32) (param i32))) + (import "env" "eth2_expose" (func $expose (param i32) (param i32))) + (import "env" "eth2_return" (func $return (param i32) (param i32) (result i32))) + (import "env" "eth2_argument" (func $argument (param i32) (param i32) (result i32))) + (import + "env" + "eth2_callModule" + (func + $call + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (result i32))) + + (memory (export "memory") 1) + (data (i32.const 0) "some_func") + (data (i32.const 10) "main") + (data (i32.const 22) "{}") + (func $some_func + (export "some_func") + (result i32) + + (; Read the argument, and check the result ;) + (drop (call $argument (i32.const 89) (i32.const 4))) + (if (i32.ne (i32.load (i32.const 89)) (i32.const 9999)) + (then (unreachable))) + + (; Return a value to the caller ;) + (i32.store (i32.const 99) (i32.const 8888)) + (drop (call $return (i32.const 99) (i32.const 4))) + + (i32.const 6654)) + + (func $main (export "main") + (call $expose (i32.const 0) (i32.const 9)) + (call $load (i32.const 0) (i32.const 22) (i32.const {})) + (i32.store (i32.const 14) (i32.const 1234)) + (drop + (call + $call + (i32.const 0) (; Slot ;) + (i32.const 10) (; Name Offset ;) + (i32.const 4) (; Name Length ;) + (i32.const 14) (; Argument Offset ;) + (i32.const 4) (; Argument Length ;) + (i32.const 18) (; Return Offset ;) + (i32.const 4) (; Return Length ;) + ) + ) + + (; Check the returned buffer from the child runtime ;) + (if + (i32.ne (i32.load (i32.const 18)) (i32.const 4321)) + (then (unreachable))) + ) + ) + "#, + escape(&child_asm), + child_asm.len(), + )) + .unwrap() +} + +#[test] +fn call() { + let child_code = r#" + (module + (import + "env" + "eth2_return" + (func + $eth2_return + (param i32) + (param i32) + (result i32))) + (import + "env" + "eth2_argument" + (func + $eth2_argument + (param i32) + (param i32) + (result i32))) + (import + "env" + "eth2_call" + (func + $eth2_call + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (result i32))) + (memory (export "memory") 1) + (data (i32.const 0) "some_func") + (func $main (export "main") (result i32) (local $x i32) + (; Check that the argument provided by the caller is 1234 ;) + (drop (call $eth2_argument (i32.const 10) (i32.const 4))) + (if + (i32.ne (i32.load (i32.const 10)) (i32.const 1234)) + (then (unreachable))) + + (; Return a value to the caller ;) + (i32.store (i32.const 10) (i32.const 4321)) + (drop (call $eth2_return (i32.const 10) (i32.const 4))) + + (i32.store (i32.const 10) (i32.const 9999)) + (set_local $x + (call + $eth2_call + (i32.const 0) + (i32.const 9) + (i32.const 10) + (i32.const 4) + (i32.const 15) + (i32.const 4))) + (if + (i32.ne (get_local $x) (i32.const 6654)) + (then (unreachable))) + (if + (i32.ne (i32.load (i32.const 15)) (i32.const 8888)) + (then (unreachable)) + ) + + (i32.const 6301) + ) + ) + "#; + + let code = compile_wat(child_code); + + let mut runtime = RootRuntime::new(&code, &[], [0u8; 32]); + runtime.execute(); +} diff --git a/tests/host_functions.rs b/tests/host_functions.rs index 969c994..4409de9 100644 --- a/tests/host_functions.rs +++ b/tests/host_functions.rs @@ -1,21 +1,28 @@ -use ewasm::{Execute, Runtime}; +mod utils; + +use ewasm::{Execute, RootRuntime}; +use utils::escape; use wabt::wat2wasm; +fn nop() -> Vec { + wat2wasm(r#"(module (func $main (export "main") (nop)))"#).unwrap() +} + fn compile_wat(code: &str) -> Vec { wat2wasm( [ r#" (module - (import "env" "eth2_savePostStateRoot" (func $save_post_root (param i32))) - (import "env" "eth2_loadPreStateRoot" (func $load_pre_root (param i32))) - (import "env" "eth2_blockDataSize" (func $block_data_size (result i32))) - (import "env" "eth2_blockDataCopy" (func $block_data_copy (param i32) (param i32) (param i32))) - (import "env" "eth2_bufferGet" (func $buffer_get (param i32) (param i32) (param i32) (result i32))) - (import "env" "eth2_bufferSet" (func $buffer_set (param i32) (param i32) (param i32))) - (import "env" "eth2_bufferMerge" (func $buffer_merge (param i32) (param i32))) - (import "env" "eth2_bufferClear" (func $buffer_clear (param i32))) - (memory (export "memory") 1) - (func $main (export "main") + (import "env" "eth2_savePostStateRoot" (func $save_post_root (param i32))) + (import "env" "eth2_loadPreStateRoot" (func $load_pre_root (param i32))) + (import "env" "eth2_blockDataSize" (func $block_data_size (result i32))) + (import "env" "eth2_blockDataCopy" (func $block_data_copy (param i32) (param i32) (param i32))) + (import "env" "eth2_bufferGet" (func $buffer_get (param i32) (param i32) (param i32) (result i32))) + (import "env" "eth2_bufferSet" (func $buffer_set (param i32) (param i32) (param i32))) + (import "env" "eth2_bufferMerge" (func $buffer_merge (param i32) (param i32))) + (import "env" "eth2_bufferClear" (func $buffer_clear (param i32))) + (memory (export "memory") 1) + (func $main (export "main") "#, code, r#"))"#, @@ -31,6 +38,42 @@ fn build_root(n: u8) -> [u8; 32] { ret } +#[test] +fn module_load_and_call() { + let child_code = nop(); + + let code = wat2wasm(format!( + r#" + (module + (import "env" "eth2_loadModule" (func $load (param i32) (param i32) (param i32))) + (import + "env" + "eth2_callModule" + (func + $call + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (param i32) + (result i32))) + (memory (export "memory") 1) + (data (i32.const 0) "{}") + (func $main (export "main") + (; Load a compiled module into slot 0 ;) + (call $load (i32.const 0) (i32.const 0) (i32.const {})))) + "#, + escape(&child_code), + child_code.len(), + )) + .unwrap(); + + let mut runtime = RootRuntime::new(&code, &[], [0u8; 32]); + runtime.execute(); +} + #[test] fn save_post_root() { let code = compile_wat( @@ -40,7 +83,7 @@ fn save_post_root() { "#, ); - let mut runtime = Runtime::new(&code, &[], [0u8; 32]); + let mut runtime = RootRuntime::new(&code, &[], [0u8; 32]); let post_root = runtime.execute(); assert_eq!(post_root, build_root(42)); } @@ -54,7 +97,7 @@ fn load_pre_root() { "#, ); - let mut runtime = Runtime::new(&code, &[], build_root(42)); + let mut runtime = RootRuntime::new(&code, &[], build_root(42)); let post_root = runtime.execute(); assert_eq!(post_root, build_root(42)); } @@ -69,7 +112,7 @@ fn block_data_size() { "#, ); - let mut runtime = Runtime::new(&code, &[0u8; 42], build_root(42)); + let mut runtime = RootRuntime::new(&code, &[0u8; 42], build_root(42)); let post_root = runtime.execute(); assert_eq!(post_root, build_root(42)); } @@ -84,7 +127,7 @@ fn block_data_copy() { ); let block_data = build_root(42); - let mut runtime = Runtime::new(&code, &block_data, [0u8; 32]); + let mut runtime = RootRuntime::new(&code, &block_data, [0u8; 32]); let post_root = runtime.execute(); assert_eq!(post_root, build_root(42)); } @@ -101,7 +144,7 @@ fn buffer_get_and_set() { "#, ); - let mut runtime = Runtime::new(&code, &[], [0u8; 32]); + let mut runtime = RootRuntime::new(&code, &[], [0u8; 32]); let post_root = runtime.execute(); assert_eq!(post_root, build_root(42)); } @@ -150,7 +193,7 @@ fn buffer_merge() { "#, ); - let mut runtime = Runtime::new(&code, &[], [0u8; 32]); + let mut runtime = RootRuntime::new(&code, &[], [0u8; 32]); let post_root = runtime.execute(); // The post root should be 1 + 3 + 4 = 8 @@ -188,7 +231,7 @@ fn buffer_clear() { "#, ); - let mut runtime = Runtime::new(&code, &[], [0u8; 32]); + let mut runtime = RootRuntime::new(&code, &[], [0u8; 32]); let post_root = runtime.execute(); // The post root should be 2 - 0 = 2 diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs new file mode 100644 index 0000000..d838a89 --- /dev/null +++ b/tests/utils/mod.rs @@ -0,0 +1,9 @@ +pub fn escape(bytes: &[u8]) -> String { + let mut output = String::with_capacity(bytes.len() * 4); + + for byte in bytes { + output.push_str(&format!(r#"\{:02x}"#, byte)); + } + + output +}