1use std::{
20 cell::{Cell, RefCell},
21 sync::Arc,
22};
23
24use darkfi_sdk::{
25 crypto::contract_id::{
26 ContractId, SMART_CONTRACT_MONOTREE_DB_NAME, SMART_CONTRACT_ZKAS_DB_NAME,
27 },
28 tx::TransactionHash,
29 wasm, AsHex,
30};
31use darkfi_serial::serialize;
32use tracing::{debug, error, info};
33use wasmer::{
34 imports, sys::CompilerConfig, wasmparser::Operator, AsStoreMut, AsStoreRef, Function,
35 FunctionEnv, Instance, Memory, MemoryType, MemoryView, Module, Pages, Store, Value,
36 WASM_PAGE_SIZE,
37};
38use wasmer_compiler_singlepass::Singlepass;
39use wasmer_middlewares::{
40 metering::{get_remaining_points, set_remaining_points, MeteringPoints},
41 Metering,
42};
43
44use super::{import, import::db::DbHandle, memory::MemoryManipulation};
45use crate::{blockchain::BlockchainOverlayPtr, Error, Result};
46
47const MEMORY: &str = "memory";
49
50pub const GAS_LIMIT: u64 = 400_000_000;
52
53#[derive(Clone, Copy, PartialEq)]
55pub enum ContractSection {
56 Deploy,
58 Exec,
60 Update,
62 Metadata,
64 Null,
66}
67impl ContractSection {
70 pub const fn name(&self) -> &str {
71 match self {
72 Self::Deploy => "__initialize",
73 Self::Exec => "__entrypoint",
74 Self::Update => "__update",
75 Self::Metadata => "__metadata",
76 Self::Null => unreachable!(),
77 }
78 }
79}
80
81pub struct Env {
83 pub blockchain: BlockchainOverlayPtr,
85 pub db_handles: RefCell<Vec<DbHandle>>,
87 pub contract_id: ContractId,
89 pub contract_bincode: Vec<u8>,
91 pub contract_section: ContractSection,
93 pub contract_return_data: Cell<Option<Vec<u8>>>,
95 pub logs: RefCell<Vec<String>>,
97 pub memory: Option<Memory>,
99 pub objects: RefCell<Vec<Vec<u8>>>,
101 pub verifying_block_height: u32,
104 pub block_target: u32,
106 pub tx_hash: TransactionHash,
108 pub call_idx: u8,
110 pub instance: Option<Arc<Instance>>,
112}
113
114impl Env {
115 pub fn memory_view<'a>(&'a self, store: &'a impl AsStoreRef) -> MemoryView<'a> {
123 self.memory().view(store)
124 }
125
126 pub fn memory(&self) -> &Memory {
128 self.memory.as_ref().unwrap()
129 }
130
131 pub fn subtract_gas(&mut self, ctx: &mut impl AsStoreMut, gas: u64) {
133 match get_remaining_points(ctx, self.instance.as_ref().unwrap()) {
134 MeteringPoints::Remaining(rem) => {
135 if gas > rem {
136 set_remaining_points(ctx, self.instance.as_ref().unwrap(), 0);
137 } else {
138 set_remaining_points(ctx, self.instance.as_ref().unwrap(), rem - gas);
139 }
140 }
141 MeteringPoints::Exhausted => {
142 set_remaining_points(ctx, self.instance.as_ref().unwrap(), 0);
143 }
144 }
145 }
146}
147
148pub struct Runtime {
150 pub instance: Arc<Instance>,
152 pub store: Store,
154 pub ctx: FunctionEnv<Env>,
156}
157
158impl Runtime {
159 pub fn new(
161 wasm_bytes: &[u8],
162 blockchain: BlockchainOverlayPtr,
163 contract_id: ContractId,
164 verifying_block_height: u32,
165 block_target: u32,
166 tx_hash: TransactionHash,
167 call_idx: u8,
168 ) -> Result<Self> {
169 info!(target: "runtime::vm_runtime", "[WASM] Instantiating a new runtime");
170 let cost_function = |_operator: &Operator| -> u64 { 1 };
176
177 let metering = Arc::new(Metering::new(GAS_LIMIT, cost_function));
181
182 let mut compiler_config = Singlepass::new();
184 compiler_config.push_middleware(metering);
185 let mut store = Store::new(compiler_config);
186
187 let memory_type = MemoryType::new(
189 Pages(256), Some(Pages(4096)), false,
192 );
193 let memory = Memory::new(&mut store, memory_type)?;
194
195 debug!(target: "runtime::vm_runtime", "Compiling module");
196 let module = Module::new(&store, wasm_bytes)?;
197
198 let db_handles = RefCell::new(vec![]);
200 let logs = RefCell::new(vec![]);
201
202 debug!(target: "runtime::vm_runtime", "Importing functions");
203
204 let ctx = FunctionEnv::new(
205 &mut store,
206 Env {
207 blockchain,
208 db_handles,
209 contract_id,
210 contract_bincode: wasm_bytes.to_vec(),
211 contract_section: ContractSection::Null,
212 contract_return_data: Cell::new(None),
213 logs,
214 memory: Some(memory.clone()),
215 objects: RefCell::new(vec![]),
216 verifying_block_height,
217 block_target,
218 tx_hash,
219 call_idx,
220 instance: None,
221 },
222 );
223
224 let imports = imports! {
225 "env" => {
226 "memory" => memory,
227
228 "drk_log_" => Function::new_typed_with_env(
229 &mut store,
230 &ctx,
231 import::util::drk_log,
232 ),
233
234 "set_return_data_" => Function::new_typed_with_env(
235 &mut store,
236 &ctx,
237 import::util::set_return_data,
238 ),
239
240 "db_init_" => Function::new_typed_with_env(
241 &mut store,
242 &ctx,
243 import::db::db_init,
244 ),
245
246 "db_lookup_" => Function::new_typed_with_env(
247 &mut store,
248 &ctx,
249 import::db::db_lookup,
250 ),
251
252 "db_get_" => Function::new_typed_with_env(
253 &mut store,
254 &ctx,
255 import::db::db_get,
256 ),
257
258 "db_contains_key_" => Function::new_typed_with_env(
259 &mut store,
260 &ctx,
261 import::db::db_contains_key,
262 ),
263
264 "db_set_" => Function::new_typed_with_env(
265 &mut store,
266 &ctx,
267 import::db::db_set,
268 ),
269
270 "db_del_" => Function::new_typed_with_env(
271 &mut store,
272 &ctx,
273 import::db::db_del,
274 ),
275
276 "zkas_db_set_" => Function::new_typed_with_env(
277 &mut store,
278 &ctx,
279 import::db::zkas_db_set,
280 ),
281
282 "get_object_bytes_" => Function::new_typed_with_env(
283 &mut store,
284 &ctx,
285 import::util::get_object_bytes,
286 ),
287
288 "get_object_size_" => Function::new_typed_with_env(
289 &mut store,
290 &ctx,
291 import::util::get_object_size,
292 ),
293
294 "merkle_add_" => Function::new_typed_with_env(
295 &mut store,
296 &ctx,
297 import::merkle::merkle_add,
298 ),
299
300 "sparse_merkle_insert_batch_" => Function::new_typed_with_env(
301 &mut store,
302 &ctx,
303 import::smt::sparse_merkle_insert_batch,
304 ),
305
306 "get_verifying_block_height_" => Function::new_typed_with_env(
307 &mut store,
308 &ctx,
309 import::util::get_verifying_block_height,
310 ),
311
312 "get_block_target_" => Function::new_typed_with_env(
313 &mut store,
314 &ctx,
315 import::util::get_block_target,
316 ),
317
318 "get_tx_hash_" => Function::new_typed_with_env(
319 &mut store,
320 &ctx,
321 import::util::get_tx_hash,
322 ),
323
324 "get_call_index_" => Function::new_typed_with_env(
325 &mut store,
326 &ctx,
327 import::util::get_call_index,
328 ),
329
330 "get_blockchain_time_" => Function::new_typed_with_env(
331 &mut store,
332 &ctx,
333 import::util::get_blockchain_time,
334 ),
335
336 "get_last_block_height_" => Function::new_typed_with_env(
337 &mut store,
338 &ctx,
339 import::util::get_last_block_height,
340 ),
341
342 "get_tx_" => Function::new_typed_with_env(
343 &mut store,
344 &ctx,
345 import::util::get_tx,
346 ),
347
348 "get_tx_location_" => Function::new_typed_with_env(
349 &mut store,
350 &ctx,
351 import::util::get_tx_location,
352 ),
353 }
354 };
355
356 debug!(target: "runtime::vm_runtime", "Instantiating module");
357 let instance = Arc::new(Instance::new(&mut store, &module, &imports)?);
358
359 let env_mut = ctx.as_mut(&mut store);
360 env_mut.memory = Some(instance.exports.get_with_generics(MEMORY)?);
361 env_mut.instance = Some(Arc::clone(&instance));
362
363 Ok(Self { instance, store, ctx })
364 }
365
366 fn call(&mut self, section: ContractSection, payload: &[u8]) -> Result<Vec<u8>> {
370 debug!(target: "runtime::vm_runtime", "Calling {} method", section.name());
371
372 let env_mut = self.ctx.as_mut(&mut self.store);
373 env_mut.contract_section = section;
374 assert!(env_mut.contract_return_data.take().is_none());
376
377 let _ = env_mut.logs.take();
379
380 let payload = Self::serialize_payload(&env_mut.contract_id, payload);
382
383 let pages_required = payload.len() / WASM_PAGE_SIZE + 1;
385 self.set_memory_page_size(pages_required as u32)?;
386 self.copy_to_memory(&payload)?;
387
388 debug!(target: "runtime::vm_runtime", "Getting {} function", section.name());
389 let entrypoint = self.instance.exports.get_function(section.name())?;
390
391 debug!(target: "runtime::vm_runtime", "Executing wasm");
397 let ret = match entrypoint.call(&mut self.store, &[Value::I32(0_i32)]) {
398 Ok(retvals) => {
399 self.print_logs();
400 info!(target: "runtime::vm_runtime", "[WASM] {}", self.gas_info());
401 retvals
402 }
403 Err(e) => {
404 self.print_logs();
405 info!(target: "runtime::vm_runtime", "[WASM] {}", self.gas_info());
406 error!(target: "runtime::vm_runtime", "[WASM] Wasmer Runtime Error: {e:#?}");
408 return Err(e.into())
409 }
410 };
411
412 debug!(target: "runtime::vm_runtime", "wasm executed successfully");
413
414 let env_mut = self.ctx.as_mut(&mut self.store);
416 env_mut.contract_section = ContractSection::Null;
417 let retdata = env_mut.contract_return_data.take().unwrap_or_default();
418
419 let retval: i64 = match ret.len() {
422 0 => {
423 debug!(target: "runtime::vm_runtime", "Contract has no return value (expected)");
426 wasm::entrypoint::SUCCESS
427 }
428 _ => {
429 match ret[0] {
430 Value::I64(v) => {
431 debug!(target: "runtime::vm_runtime", "Contract returned: {:?}", ret[0]);
432 v
433 }
434 _ => unreachable!("Got unexpected result return value: {ret:?}"),
437 }
438 }
439 };
440
441 match retval {
445 wasm::entrypoint::SUCCESS => Ok(retdata),
446 _ => {
447 let err = darkfi_sdk::error::ContractError::from(retval);
448 error!(target: "runtime::vm_runtime", "[WASM] Contract returned: {err:?}");
449 Err(Error::ContractError(err))
450 }
451 }
452 }
453
454 pub fn deploy(&mut self, payload: &[u8]) -> Result<()> {
465 let cid = self.ctx.as_ref(&self.store).contract_id;
466 info!(target: "runtime::vm_runtime", "[WASM] Running deploy() for ContractID: {cid}");
467
468 {
470 let env_mut = self.ctx.as_mut(&mut self.store);
471 let contracts = &env_mut.blockchain.lock().unwrap().contracts;
474
475 let zkas_tree_handle =
477 match contracts.lookup(&env_mut.contract_id, SMART_CONTRACT_ZKAS_DB_NAME) {
478 Ok(v) => v,
479 Err(_) => contracts.init(&env_mut.contract_id, SMART_CONTRACT_ZKAS_DB_NAME)?,
480 };
481
482 if contracts.lookup(&env_mut.contract_id, SMART_CONTRACT_MONOTREE_DB_NAME).is_err() {
485 contracts.init(&env_mut.contract_id, SMART_CONTRACT_MONOTREE_DB_NAME)?;
486 }
487
488 let mut db_handles = env_mut.db_handles.borrow_mut();
489 db_handles.push(DbHandle::new(env_mut.contract_id, zkas_tree_handle));
490 }
491
492 let _ = self.call(ContractSection::Deploy, payload)?;
494
495 let env_mut = self.ctx.as_mut(&mut self.store);
497 env_mut
498 .blockchain
499 .lock()
500 .unwrap()
501 .contracts
502 .insert(env_mut.contract_id, &env_mut.contract_bincode)?;
503
504 info!(target: "runtime::vm_runtime", "[WASM] Successfully deployed ContractID: {cid}");
505 Ok(())
506 }
507
508 pub fn metadata(&mut self, payload: &[u8]) -> Result<Vec<u8>> {
515 let cid = self.ctx.as_ref(&self.store).contract_id;
516 info!(target: "runtime::vm_runtime", "[WASM] Running metadata() for ContractID: {cid}");
517
518 debug!(target: "runtime::vm_runtime", "metadata payload: {}", payload.hex());
519 let ret = self.call(ContractSection::Metadata, payload)?;
520 debug!(target: "runtime::vm_runtime", "metadata returned: {:?}", ret.hex());
521
522 info!(target: "runtime::vm_runtime", "[WASM] Successfully got metadata ContractID: {cid}");
523 Ok(ret)
524 }
525
526 pub fn exec(&mut self, payload: &[u8]) -> Result<Vec<u8>> {
532 let cid = self.ctx.as_ref(&self.store).contract_id;
533 info!(target: "runtime::vm_runtime", "[WASM] Running exec() for ContractID: {cid}");
534
535 debug!(target: "runtime::vm_runtime", "exec payload: {}", payload.hex());
536 let ret = self.call(ContractSection::Exec, payload)?;
537 debug!(target: "runtime::vm_runtime", "exec returned: {:?}", ret.hex());
538
539 info!(target: "runtime::vm_runtime", "[WASM] Successfully executed ContractID: {cid}");
540 Ok(ret)
541 }
542
543 pub fn apply(&mut self, update: &[u8]) -> Result<()> {
550 let cid = self.ctx.as_ref(&self.store).contract_id;
551 info!(target: "runtime::vm_runtime", "[WASM] Running apply() for ContractID: {cid}");
552
553 debug!(target: "runtime::vm_runtime", "apply payload: {:?}", update.hex());
554 let ret = self.call(ContractSection::Update, update)?;
555 debug!(target: "runtime::vm_runtime", "apply returned: {:?}", ret.hex());
556
557 info!(target: "runtime::vm_runtime", "[WASM] Successfully applied ContractID: {cid}");
558 Ok(())
559 }
560
561 fn print_logs(&self) {
563 let logs = self.ctx.as_ref(&self.store).logs.borrow();
564 for msg in logs.iter() {
565 info!(target: "runtime::vm_runtime", "[WASM] Contract log: {msg}");
566 }
567 }
568
569 pub fn gas_used(&mut self) -> u64 {
572 let remaining_points = get_remaining_points(&mut self.store, &self.instance);
573
574 match remaining_points {
575 MeteringPoints::Remaining(rem) => {
576 if rem > GAS_LIMIT {
577 unreachable!("Remaining wasm points exceed GAS_LIMIT");
580 }
581 GAS_LIMIT - rem
582 }
583 MeteringPoints::Exhausted => GAS_LIMIT + 1,
584 }
585 }
586
587 fn gas_info(&mut self) -> String {
592 let gas_used = self.gas_used();
593
594 if gas_used > GAS_LIMIT {
595 format!("Gas fully exhausted: {gas_used}/{GAS_LIMIT}")
596 } else {
597 format!("Gas used: {gas_used}/{GAS_LIMIT}")
598 }
599 }
600
601 fn set_memory_page_size(&mut self, pages: u32) -> Result<Pages> {
603 let memory = self.take_memory();
605 let ret = memory.grow(&mut self.store, Pages(pages))?;
607 self.ctx.as_mut(&mut self.store).memory = Some(memory);
609 Ok(ret)
610 }
611
612 fn take_memory(&mut self) -> Memory {
615 let env_memory = &mut self.ctx.as_mut(&mut self.store).memory;
616 let memory = env_memory.take();
617 memory.expect("memory should be set")
618 }
619
620 fn copy_to_memory(&self, payload: &[u8]) -> Result<()> {
622 let env = self.ctx.as_ref(&self.store);
625 let memory_view = env.memory_view(&self.store);
626 memory_view.write_slice(payload, 0)
627 }
628
629 fn serialize_payload(cid: &ContractId, payload: &[u8]) -> Vec<u8> {
633 let ser_cid = serialize(cid);
634 let payload_len = payload.len();
635 let mut out = Vec::with_capacity(ser_cid.len() + 8 + payload_len);
636 out.extend_from_slice(&ser_cid);
637 out.extend_from_slice(&(payload_len as u64).to_le_bytes());
638 out.extend_from_slice(payload);
639 out
640 }
641}