truntime: Implement gas metering. - wasm-runtime - A wasm runtime HTML git clone https://git.parazyd.org/wasm-runtime DIR Log DIR Files DIR Refs DIR README --- DIR commit f87baa72e4447be535de9e5f64edd04095f5a500 DIR parent 48ba506e1bc994ce7c1f4bd216c876b3f82c4843 HTML Author: parazyd <parazyd@dyne.org> Date: Wed, 9 Mar 2022 09:13:32 +0100 runtime: Implement gas metering. Diffstat: M Cargo.toml | 1 + M src/runtime.rs | 58 ++++++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 5 deletions(-) --- DIR diff --git a/Cargo.toml b/Cargo.toml t@@ -18,6 +18,7 @@ drk-sdk = { path = "./drk-sdk" } thiserror = "1.0.30" wasmer = "2.2.0" wasmer-compiler-singlepass = "2.2.0" +wasmer-middlewares = "2.2.0" [dev-dependencies] borsh = "0.9.3" DIR diff --git a/src/runtime.rs b/src/runtime.rs t@@ -1,7 +1,15 @@ use anyhow::{anyhow, Result}; use drk_sdk::entrypoint; -use wasmer::{imports, Instance, Memory, Module, Store, Universal, Value}; +use std::sync::Arc; +use wasmer::{ + imports, wasmparser::Operator, CompilerConfig, Instance, Memory, Module, Store, Universal, + Value, +}; use wasmer_compiler_singlepass::Singlepass; +use wasmer_middlewares::{ + metering::{get_remaining_points, MeteringPoints}, + Metering, +}; use crate::memory::MemoryManipulation; t@@ -10,7 +18,9 @@ const WASM_MEM_ALLOC: &str = "__drkruntime_mem_alloc"; /// Name of the wasm linear memory of our guest module const MEMORY: &str = "memory"; /// Hardcoded entrypoint function of a contract -const ENTRYPOINT: &str = "entrypoint"; +pub const ENTRYPOINT: &str = "entrypoint"; +/// Gas limit for a contract +pub const GAS_LIMIT: u64 = 20000; pub struct Runtime { pub(crate) instance: Instance, t@@ -19,8 +29,25 @@ pub struct Runtime { impl Runtime { /// Create a new wasm runtime instance that contains the given wasm module. pub fn new(wasm_bytes: &[u8]) -> Result<Self> { - // Define the compiler, engine, and store - let compiler = Singlepass::new(); + // 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 { .. } | Operator::I32Const { .. } => 1, + Operator::I32Add { .. } => 2, + _ => 0, + } + }; + + // `Metering` needs to be configured with a limit and a const 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()); println!("Compiling module..."); t@@ -49,7 +76,16 @@ impl Runtime { println!("{:#?}", entrypoint); println!("Executing wasm..."); - let ret = entrypoint.call(&[Value::I32(mem_offset as i32)])?; + let ret = match entrypoint.call(&[Value::I32(mem_offset as i32)]) { + Ok(v) => { + println!("{}", self.gas_info()); + v + } + Err(e) => { + println!("{}", self.gas_info()); + return Err(e.into()) + } + }; println!("Executed successfully"); println!("Contract returned: {:?}", ret[0]); t@@ -65,6 +101,18 @@ impl Runtime { } } + 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)?;