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:
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)?)
}