wasm-runtime

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

runtime.rs (6118B)


      1 use anyhow::{anyhow, Result};
      2 use drk_sdk::entrypoint;
      3 use std::sync::{Arc, Mutex};
      4 use wasmer::{
      5     imports, wasmparser::Operator, CompilerConfig, Function, HostEnvInitError, Instance, LazyInit,
      6     Memory, Module, Store, Universal, Value, WasmerEnv,
      7 };
      8 use wasmer_compiler_singlepass::Singlepass;
      9 use wasmer_middlewares::{
     10     metering::{get_remaining_points, MeteringPoints},
     11     Metering,
     12 };
     13 
     14 use crate::{memory::MemoryManipulation, util::drk_log};
     15 
     16 /// Function name in our wasm module that allows us to allocate some memory
     17 const WASM_MEM_ALLOC: &str = "__drkruntime_mem_alloc";
     18 /// Name of the wasm linear memory of our guest module
     19 const MEMORY: &str = "memory";
     20 /// Hardcoded entrypoint function of a contract
     21 pub const ENTRYPOINT: &str = "entrypoint";
     22 /// Gas limit for a contract
     23 pub const GAS_LIMIT: u64 = 200000;
     24 
     25 #[derive(Clone)]
     26 pub struct Env {
     27     pub logs: Arc<Mutex<Vec<String>>>,
     28     pub memory: LazyInit<Memory>,
     29 }
     30 
     31 impl WasmerEnv for Env {
     32     fn init_with_instance(
     33         &mut self,
     34         instance: &Instance,
     35     ) -> std::result::Result<(), HostEnvInitError> {
     36         let memory: Memory = instance.exports.get_with_generics_weak("memory")?;
     37         self.memory.initialize(memory);
     38         Ok(())
     39     }
     40 }
     41 
     42 pub struct Runtime {
     43     pub(crate) instance: Instance,
     44     pub(crate) env: Env,
     45 }
     46 
     47 impl Runtime {
     48     /// Create a new wasm runtime instance that contains the given wasm module.
     49     pub fn new(wasm_bytes: &[u8]) -> Result<Self> {
     50         // This function will be called for each `Operator` encountered during
     51         // the wasm module execution. It should return the cost of the operator
     52         // that it received as its first argument.
     53         let cost_function = |operator: &Operator| -> u64 {
     54             match operator {
     55                 Operator::LocalGet { .. } | Operator::I32Const { .. } => 1,
     56                 Operator::I32Add { .. } => 2,
     57                 _ => 0,
     58             }
     59         };
     60 
     61         // `Metering` needs to be configured with a limit and a cost function.
     62         // For each `Operator`, the metering middleware will call the cost
     63         // function and subtract the cost from the remaining points.
     64         let metering = Arc::new(Metering::new(GAS_LIMIT, cost_function));
     65 
     66         // Define the compiler and middleware, engine, and store
     67         let mut compiler = Singlepass::new();
     68         compiler.push_middleware(metering);
     69         let store = Store::new(&Universal::new(compiler).engine());
     70 
     71         println!("Compiling module...");
     72         let module = Module::new(&store, wasm_bytes)?;
     73 
     74         println!("Importing functions...");
     75         let env = Env { logs: Arc::new(Mutex::new(vec![])), memory: LazyInit::new() };
     76         let import_object = imports! {
     77             "env" => {
     78                 "drk_log_" => Function::new_native_with_env(
     79                     &store,
     80                     env.clone(),
     81                     drk_log,
     82                 ),
     83             }
     84         };
     85 
     86         println!("Instantiating module...");
     87         let instance = Instance::new(&module, &import_object)?;
     88 
     89         Ok(Self { instance, env })
     90     }
     91 
     92     /// Run the hardcoded [ENTRYPOINT] function with the given payload as input.
     93     pub fn run(&mut self, payload: &[u8]) -> Result<()> {
     94         // Get module linear memory
     95         let memory = self.memory()?;
     96 
     97         // Retrieve ptr to pass data
     98         let mem_offset = self.guest_mem_alloc(payload.len())?;
     99         memory.write(mem_offset, payload)?;
    100 
    101         println!("Getting entrypoint function...");
    102         let entrypoint = self.instance.exports.get_function(ENTRYPOINT)?;
    103         println!("{:#?}", entrypoint);
    104 
    105         println!("Executing wasm...");
    106 
    107         let ret = match entrypoint.call(&[Value::I32(mem_offset as i32)]) {
    108             Ok(v) => {
    109                 self.print_logs();
    110                 println!("{}", self.gas_info());
    111                 v
    112             }
    113             Err(e) => {
    114                 self.print_logs();
    115                 println!("{}", self.gas_info());
    116                 return Err(e.into())
    117             }
    118         };
    119 
    120         println!("Executed successfully");
    121         println!("Contract returned: {:?}", ret[0]);
    122 
    123         let retval = match ret[0] {
    124             Value::I64(v) => v as u64,
    125             _ => unreachable!(),
    126         };
    127 
    128         match retval {
    129             entrypoint::SUCCESS => Ok(()),
    130             _ => Err(anyhow!("Contract exited with an error: {0:#x}", retval)),
    131         }
    132     }
    133 
    134     fn print_logs(&self) {
    135         let logs = self.env.logs.lock().unwrap();
    136         for msg in logs.iter() {
    137             println!("Contract log: {}", msg);
    138         }
    139     }
    140 
    141     fn gas_info(&self) -> String {
    142         let remaining_points = get_remaining_points(&self.instance);
    143         match remaining_points {
    144             MeteringPoints::Remaining(rem) => {
    145                 format!("Gas used: {}/{}", GAS_LIMIT - rem, GAS_LIMIT)
    146             }
    147             MeteringPoints::Exhausted => {
    148                 format!("Gas fully exhausted: {}/{}", GAS_LIMIT + 1, GAS_LIMIT)
    149             }
    150         }
    151     }
    152 
    153     /// Allocate some memory space on a wasm linear memory to allow direct rw
    154     fn guest_mem_alloc(&self, size: usize) -> Result<u32> {
    155         let mem_alloc = self.instance.exports.get_function(WASM_MEM_ALLOC)?;
    156         let res_target_ptr = mem_alloc.call(&[Value::I32(size as i32)])?.to_vec();
    157         Ok(res_target_ptr[0].unwrap_i32() as u32)
    158     }
    159 
    160     /// Retrieve linear memory from a wasm module and return its reference
    161     fn memory(&self) -> Result<&Memory> {
    162         Ok(self.instance.exports.get_memory(MEMORY)?)
    163     }
    164 }
    165 
    166 #[cfg(test)]
    167 mod tests {
    168     use super::*;
    169     use crate::util::serialize_payload;
    170 
    171     use borsh::BorshSerialize;
    172     use pasta_curves::pallas;
    173     use smart_contract::Args;
    174 
    175     #[test]
    176     fn run_contract() -> Result<()> {
    177         let wasm_bytes = std::fs::read("smart_contract.wasm")?;
    178         let mut runtime = Runtime::new(&wasm_bytes)?;
    179 
    180         let args = Args { a: pallas::Base::from(777), b: pallas::Base::from(666) };
    181         let payload = args.try_to_vec()?;
    182 
    183         let input = serialize_payload(&payload);
    184 
    185         runtime.run(&input)
    186     }
    187 }