commit f87baa72e4447be535de9e5f64edd04095f5a500
parent 48ba506e1bc994ce7c1f4bd216c876b3f82c4843
Author: parazyd <parazyd@dyne.org>
Date: Wed, 9 Mar 2022 09:13:32 +0100
runtime: Implement gas metering.
Diffstat:
2 files changed, 54 insertions(+), 5 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -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"
diff --git a/src/runtime.rs b/src/runtime.rs
@@ -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;
@@ -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,
@@ -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...");
@@ -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]);
@@ -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)?;