truntime.rs - wasm-runtime - A wasm runtime
git clone https://git.parazyd.org/wasm-runtime
Log
Files
Refs
README
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>>,
    28     pub memory: LazyInit,
    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 {
    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 {
   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 }