| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- use std::sync::{Arc, Mutex};
- use drk_sdk::entrypoint;
- use log::debug;
- use wasmer::{
- imports, wasmparser::Operator, CompilerConfig, Function, HostEnvInitError, Instance, LazyInit,
- Memory, Module, Store, Universal, Value, WasmerEnv,
- };
- use wasmer_compiler_singlepass::Singlepass;
- use wasmer_middlewares::{
- metering::{get_remaining_points, MeteringPoints},
- Metering,
- };
- use super::{memory::MemoryManipulation, util::drk_log};
- use crate::Result;
- /// Function name in our wasm module that allows us to allocate some memory.
- const WASM_MEM_ALLOC: &str = "__drkruntime_mem_alloc";
- /// Name of the wasm linear memory in our guest module
- const MEMORY: &str = "memory";
- /// Hardcoded entrypoint function of a contract
- const ENTRYPOINT: &str = "entrypoint";
- /// Gas limit for a contract
- const GAS_LIMIT: u64 = 200000;
- #[derive(Clone)]
- pub struct Env {
- pub logs: Arc<Mutex<Vec<String>>>,
- pub memory: LazyInit<Memory>,
- }
- impl WasmerEnv for Env {
- fn init_with_instance(
- &mut self,
- instance: &Instance,
- ) -> std::result::Result<(), HostEnvInitError> {
- let memory: Memory = instance.exports.get_with_generics_weak(MEMORY)?;
- self.memory.initialize(memory);
- Ok(())
- }
- }
- pub struct Runtime {
- pub(crate) instance: Instance,
- pub(crate) env: Env,
- }
- impl Runtime {
- /// Create a new wasm runtime instance that contains the given wasm module.
- pub fn new(wasm_bytes: &[u8]) -> Result<Self> {
- // This function will be called for each `Operator` encountered during
- // the wasm module execution. It should return the cost of the operator
- // that it received as its first argument.
- let cost_function = |operator: &Operator| -> u64 {
- match operator {
- Operator::LocalGet { .. } => 1,
- Operator::I32Const { .. } => 1,
- Operator::I32Add { .. } => 2,
- _ => 0,
- }
- };
- // `Metering` needs to be conigured with a limit and a cost function.
- // For each `Operator`, the metering middleware will call the cost
- // function and subtract the cost from the remaining points.
- let metering = Arc::new(Metering::new(GAS_LIMIT, cost_function));
- // Define the compiler and middleware, engine, and store
- let mut compiler = Singlepass::new();
- compiler.push_middleware(metering);
- let store = Store::new(&Universal::new(compiler).engine());
- debug!(target: "wasm-runtime", "Compiling module...");
- let module = Module::new(&store, wasm_bytes)?;
- debug!(target: "wasm-runtime", "Importing functions...");
- let env = Env { logs: Arc::new(Mutex::new(vec![])), memory: LazyInit::new() };
- let import_object = imports! {
- "env" => {
- "drk_log_" => Function::new_native_with_env(
- &store,
- env.clone(),
- drk_log,
- ),
- }
- };
- debug!(target: "wasm-runtime", "Instantiating module...");
- let instance = Instance::new(&module, &import_object)?;
- Ok(Self { instance, env })
- }
- /// Run the hardcoded [ENTRYPOINT] function with the given payload as input.
- pub fn run(&mut self, payload: &[u8]) -> Result<()> {
- // Get module linear memory
- let memory = self.memory()?;
- // Retrieve ptr to pass data
- let mem_offset = self.guest_mem_alloc(payload.len())?;
- memory.write(mem_offset, payload)?;
- debug!(target: "wasm-runtime", "Getting entrypoint function...");
- let entrypoint = self.instance.exports.get_function(ENTRYPOINT)?;
- debug!(target: "wasm-runtime", "Executing wasm...");
- let ret = match entrypoint.call(&[Value::I32(mem_offset as i32)]) {
- Ok(v) => {
- self.print_logs();
- debug!(target: "wasm-runtime", "{}", self.gas_info());
- v
- }
- Err(e) => {
- self.print_logs();
- debug!(target: "wasm-runtime", "{}", self.gas_info());
- return Err(e.into())
- }
- };
- debug!(target: "wasm-runtime", "wasm executed successfully");
- debug!(target: "wasm-runtime", "Contract returned: {:?}", ret[0]);
- let retval = match ret[0] {
- Value::I64(v) => v as u64,
- _ => unreachable!(),
- };
- match retval {
- entrypoint::SUCCESS => Ok(()),
- // _ => Err(ContractError(retval)),
- _ => todo!(),
- }
- }
- fn print_logs(&self) {
- let logs = self.env.logs.lock().unwrap();
- for msg in logs.iter() {
- debug!(target: "wasm-runtime", "Contract log: {}", msg);
- }
- }
- fn gas_info(&self) -> String {
- let remaining_points = get_remaining_points(&self.instance);
- match remaining_points {
- MeteringPoints::Remaining(rem) => {
- format!("Gas used: {}/{}", GAS_LIMIT - rem, GAS_LIMIT)
- }
- MeteringPoints::Exhausted => {
- format!("Gas fully exhausted: {}/{}", GAS_LIMIT + 1, GAS_LIMIT)
- }
- }
- }
- /// Allocate some memory space on a wasm linear memory to allow direct rw.
- 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)
- }
- /// Retrieve linear memory from a wasm module and return its reference.
- fn memory(&self) -> Result<&Memory> {
- Ok(self.instance.exports.get_memory(MEMORY)?)
- }
- }
|