truntime.rs - wasm-runtime - A wasm runtime
 (HTM) git clone https://git.parazyd.org/wasm-runtime
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       truntime.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 }