commit e4fae9954d20225831248af32972253a3577d25f
Author: parazyd <parazyd@dyne.org>
Date: Tue, 8 Mar 2022 22:58:49 +0100
Code import
Diffstat:
17 files changed, 397 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,4 @@
+/target
+/wabt
+Cargo.lock
+smart_contract.wasm
diff --git a/Cargo.toml b/Cargo.toml
@@ -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"
diff --git a/Makefile b/Makefile
@@ -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 $@
diff --git a/README.md b/README.md
@@ -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`
diff --git a/drk-sdk/Cargo.toml b/drk-sdk/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "drk-sdk"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+thiserror = "1.0.30"
diff --git a/drk-sdk/src/entrypoint.rs b/drk-sdk/src/entrypoint.rs
@@ -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();
+}
diff --git a/drk-sdk/src/error.rs b/drk-sdk/src/error.rs
@@ -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
+ }
+}
diff --git a/drk-sdk/src/lib.rs b/drk-sdk/src/lib.rs
@@ -0,0 +1,2 @@
+pub mod entrypoint;
+pub mod error;
diff --git a/rustfmt.toml b/rustfmt.toml
@@ -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
diff --git a/smart-contract/.gitignore b/smart-contract/.gitignore
@@ -0,0 +1,3 @@
+/target
+/wabt
+Cargo.lock
diff --git a/smart-contract/Cargo.toml b/smart-contract/Cargo.toml
@@ -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"]
diff --git a/smart-contract/src/lib.rs b/smart-contract/src/lib.rs
@@ -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(())
+}
diff --git a/src/example.rs b/src/example.rs
@@ -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)
+}
diff --git a/src/lib.rs b/src/lib.rs
@@ -0,0 +1,3 @@
+pub mod memory;
+pub mod runtime;
+pub mod util;
diff --git a/src/memory.rs b/src/memory.rs
@@ -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)) }
+ }
+}
diff --git a/src/runtime.rs b/src/runtime.rs
@@ -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)?)
+ }
+}
diff --git a/src/util.rs b/src/util.rs
@@ -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
+}