| ---
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 } |