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 }