vm_runtime.rs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. use std::sync::{Arc, Mutex};
  2. use drk_sdk::entrypoint;
  3. use log::debug;
  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. use super::{memory::MemoryManipulation, util::drk_log};
  14. use crate::Result;
  15. /// Function name in our wasm module that allows us to allocate some memory.
  16. const WASM_MEM_ALLOC: &str = "__drkruntime_mem_alloc";
  17. /// Name of the wasm linear memory in our guest module
  18. const MEMORY: &str = "memory";
  19. /// Hardcoded entrypoint function of a contract
  20. const ENTRYPOINT: &str = "entrypoint";
  21. /// Gas limit for a contract
  22. const GAS_LIMIT: u64 = 200000;
  23. #[derive(Clone)]
  24. pub struct Env {
  25. pub logs: Arc<Mutex<Vec<String>>>,
  26. pub memory: LazyInit<Memory>,
  27. }
  28. impl WasmerEnv for Env {
  29. fn init_with_instance(
  30. &mut self,
  31. instance: &Instance,
  32. ) -> std::result::Result<(), HostEnvInitError> {
  33. let memory: Memory = instance.exports.get_with_generics_weak(MEMORY)?;
  34. self.memory.initialize(memory);
  35. Ok(())
  36. }
  37. }
  38. pub struct Runtime {
  39. pub(crate) instance: Instance,
  40. pub(crate) env: Env,
  41. }
  42. impl Runtime {
  43. /// Create a new wasm runtime instance that contains the given wasm module.
  44. pub fn new(wasm_bytes: &[u8]) -> Result<Self> {
  45. // This function will be called for each `Operator` encountered during
  46. // the wasm module execution. It should return the cost of the operator
  47. // that it received as its first argument.
  48. let cost_function = |operator: &Operator| -> u64 {
  49. match operator {
  50. Operator::LocalGet { .. } => 1,
  51. Operator::I32Const { .. } => 1,
  52. Operator::I32Add { .. } => 2,
  53. _ => 0,
  54. }
  55. };
  56. // `Metering` needs to be conigured with a limit and a cost function.
  57. // For each `Operator`, the metering middleware will call the cost
  58. // function and subtract the cost from the remaining points.
  59. let metering = Arc::new(Metering::new(GAS_LIMIT, cost_function));
  60. // Define the compiler and middleware, engine, and store
  61. let mut compiler = Singlepass::new();
  62. compiler.push_middleware(metering);
  63. let store = Store::new(&Universal::new(compiler).engine());
  64. debug!(target: "wasm-runtime", "Compiling module...");
  65. let module = Module::new(&store, wasm_bytes)?;
  66. debug!(target: "wasm-runtime", "Importing functions...");
  67. let env = Env { logs: Arc::new(Mutex::new(vec![])), memory: LazyInit::new() };
  68. let import_object = imports! {
  69. "env" => {
  70. "drk_log_" => Function::new_native_with_env(
  71. &store,
  72. env.clone(),
  73. drk_log,
  74. ),
  75. }
  76. };
  77. debug!(target: "wasm-runtime", "Instantiating module...");
  78. let instance = Instance::new(&module, &import_object)?;
  79. Ok(Self { instance, env })
  80. }
  81. /// Run the hardcoded [ENTRYPOINT] function with the given payload as input.
  82. pub fn run(&mut self, payload: &[u8]) -> Result<()> {
  83. // Get module linear memory
  84. let memory = self.memory()?;
  85. // Retrieve ptr to pass data
  86. let mem_offset = self.guest_mem_alloc(payload.len())?;
  87. memory.write(mem_offset, payload)?;
  88. debug!(target: "wasm-runtime", "Getting entrypoint function...");
  89. let entrypoint = self.instance.exports.get_function(ENTRYPOINT)?;
  90. debug!(target: "wasm-runtime", "Executing wasm...");
  91. let ret = match entrypoint.call(&[Value::I32(mem_offset as i32)]) {
  92. Ok(v) => {
  93. self.print_logs();
  94. debug!(target: "wasm-runtime", "{}", self.gas_info());
  95. v
  96. }
  97. Err(e) => {
  98. self.print_logs();
  99. debug!(target: "wasm-runtime", "{}", self.gas_info());
  100. return Err(e.into())
  101. }
  102. };
  103. debug!(target: "wasm-runtime", "wasm executed successfully");
  104. debug!(target: "wasm-runtime", "Contract returned: {:?}", ret[0]);
  105. let retval = match ret[0] {
  106. Value::I64(v) => v as u64,
  107. _ => unreachable!(),
  108. };
  109. match retval {
  110. entrypoint::SUCCESS => Ok(()),
  111. // _ => Err(ContractError(retval)),
  112. _ => todo!(),
  113. }
  114. }
  115. fn print_logs(&self) {
  116. let logs = self.env.logs.lock().unwrap();
  117. for msg in logs.iter() {
  118. debug!(target: "wasm-runtime", "Contract log: {}", msg);
  119. }
  120. }
  121. fn gas_info(&self) -> String {
  122. let remaining_points = get_remaining_points(&self.instance);
  123. match remaining_points {
  124. MeteringPoints::Remaining(rem) => {
  125. format!("Gas used: {}/{}", GAS_LIMIT - rem, GAS_LIMIT)
  126. }
  127. MeteringPoints::Exhausted => {
  128. format!("Gas fully exhausted: {}/{}", GAS_LIMIT + 1, GAS_LIMIT)
  129. }
  130. }
  131. }
  132. /// Allocate some memory space on a wasm linear memory to allow direct rw.
  133. fn guest_mem_alloc(&self, size: usize) -> Result<u32> {
  134. let mem_alloc = self.instance.exports.get_function(WASM_MEM_ALLOC)?;
  135. let res_target_ptr = mem_alloc.call(&[Value::I32(size as i32)])?.to_vec();
  136. Ok(res_target_ptr[0].unwrap_i32() as u32)
  137. }
  138. /// Retrieve linear memory from a wasm module and return its reference.
  139. fn memory(&self) -> Result<&Memory> {
  140. Ok(self.instance.exports.get_memory(MEMORY)?)
  141. }
  142. }