1use std::{
20 cell::{Cell, RefCell},
21 collections::BTreeMap,
22 sync::Arc,
23};
24
25use darkfi_sdk::{
26 crypto::contract_id::{
27 ContractId, SMART_CONTRACT_MONOTREE_DB_NAME, SMART_CONTRACT_ZKAS_DB_NAME,
28 },
29 tx::TransactionHash,
30 wasm, AsHex,
31};
32use darkfi_serial::serialize;
33use parking_lot::Mutex;
34use tracing::{debug, error, info};
35use wasmer::{
36 imports, sys::CompilerConfig, wasmparser::Operator, AsStoreMut, AsStoreRef, Function,
37 FunctionEnv, Instance, Memory, MemoryType, MemoryView, Module, Pages, Store, Value,
38 WASM_PAGE_SIZE,
39};
40use wasmer_compiler_singlepass::Singlepass;
41use wasmer_middlewares::{
42 metering::{get_remaining_points, set_remaining_points, MeteringPoints},
43 Metering,
44};
45
46use super::{import, import::db::DbHandle, memory::MemoryManipulation};
47use crate::{blockchain::BlockchainOverlayPtr, Error, Result};
48
49const MEMORY: &str = "memory";
51
52pub const GAS_LIMIT: u64 = 400_000_000;
54
55#[derive(Clone, Copy, PartialEq)]
57pub enum ContractSection {
58 Deploy,
60 Exec,
62 Update,
64 Metadata,
66 Null,
68}
69impl ContractSection {
72 pub const fn name(&self) -> &str {
73 match self {
74 Self::Deploy => "__initialize",
75 Self::Exec => "__entrypoint",
76 Self::Update => "__update",
77 Self::Metadata => "__metadata",
78 Self::Null => unreachable!(),
79 }
80 }
81}
82
83pub type TxLocalState = BTreeMap<ContractId, BTreeMap<[u8; 32], BTreeMap<Vec<u8>, Vec<u8>>>>;
89
90pub struct Env {
92 pub blockchain: BlockchainOverlayPtr,
94 pub db_handles: RefCell<Vec<DbHandle>>,
96 pub local_db_handles: RefCell<Vec<DbHandle>>,
98 pub tx_local: Arc<Mutex<TxLocalState>>,
100 pub contract_id: ContractId,
102 pub contract_bincode: Vec<u8>,
104 pub contract_section: ContractSection,
106 pub contract_return_data: Cell<Option<Vec<u8>>>,
108 pub logs: RefCell<Vec<String>>,
110 pub memory: Option<Memory>,
112 pub objects: RefCell<Vec<Vec<u8>>>,
114 pub verifying_block_height: u32,
117 pub block_target: u32,
119 pub tx_hash: TransactionHash,
121 pub call_idx: u8,
123 pub instance: Option<Arc<Instance>>,
125}
126
127impl Env {
128 pub fn memory_view<'a>(&'a self, store: &'a impl AsStoreRef) -> MemoryView<'a> {
136 self.memory().view(store)
137 }
138
139 pub fn memory(&self) -> &Memory {
141 self.memory.as_ref().unwrap()
142 }
143
144 pub fn subtract_gas(&mut self, ctx: &mut impl AsStoreMut, gas: u64) {
146 match get_remaining_points(ctx, self.instance.as_ref().unwrap()) {
147 MeteringPoints::Remaining(rem) => {
148 if gas > rem {
149 set_remaining_points(ctx, self.instance.as_ref().unwrap(), 0);
150 } else {
151 set_remaining_points(ctx, self.instance.as_ref().unwrap(), rem - gas);
152 }
153 }
154 MeteringPoints::Exhausted => {
155 set_remaining_points(ctx, self.instance.as_ref().unwrap(), 0);
156 }
157 }
158 }
159}
160
161pub struct Runtime {
163 pub instance: Arc<Instance>,
165 pub store: Store,
167 pub ctx: FunctionEnv<Env>,
169}
170
171impl Runtime {
172 #[allow(clippy::too_many_arguments)]
174 pub fn new(
175 wasm_bytes: &[u8],
176 blockchain: BlockchainOverlayPtr,
177 tx_local: Arc<Mutex<TxLocalState>>,
178 contract_id: ContractId,
179 verifying_block_height: u32,
180 block_target: u32,
181 tx_hash: TransactionHash,
182 call_idx: u8,
183 ) -> Result<Self> {
184 info!(target: "runtime::vm_runtime", "[WASM] Instantiating a new runtime");
185 let cost_function = |_operator: &Operator| -> u64 { 1 };
191
192 let metering = Arc::new(Metering::new(GAS_LIMIT, cost_function));
196
197 let mut compiler_config = Singlepass::new();
199 compiler_config.push_middleware(metering);
200 let mut store = Store::new(compiler_config);
201
202 let memory_type = MemoryType::new(
204 Pages(256), Some(Pages(4096)), false,
207 );
208 let memory = Memory::new(&mut store, memory_type)?;
209
210 debug!(target: "runtime::vm_runtime", "Compiling module");
211 let module = Module::new(&store, wasm_bytes)?;
212
213 let db_handles = RefCell::new(vec![]);
215 let local_db_handles = RefCell::new(vec![]);
216 let logs = RefCell::new(vec![]);
217
218 tx_local.lock().entry(contract_id).or_default();
220
221 debug!(target: "runtime::vm_runtime", "Importing functions");
222
223 let ctx = FunctionEnv::new(
224 &mut store,
225 Env {
226 blockchain,
227 db_handles,
228 local_db_handles,
229 tx_local,
230 contract_id,
231 contract_bincode: wasm_bytes.to_vec(),
232 contract_section: ContractSection::Null,
233 contract_return_data: Cell::new(None),
234 logs,
235 memory: Some(memory.clone()),
236 objects: RefCell::new(vec![]),
237 verifying_block_height,
238 block_target,
239 tx_hash,
240 call_idx,
241 instance: None,
242 },
243 );
244
245 let imports = imports! {
246 "env" => {
247 "memory" => memory,
248
249 "drk_log_" => Function::new_typed_with_env(
250 &mut store,
251 &ctx,
252 import::util::drk_log,
253 ),
254
255 "set_return_data_" => Function::new_typed_with_env(
256 &mut store,
257 &ctx,
258 import::util::set_return_data,
259 ),
260
261 "db_init_" => Function::new_typed_with_env(
262 &mut store,
263 &ctx,
264 import::db::db_init,
265 ),
266
267 "db_lookup_" => Function::new_typed_with_env(
268 &mut store,
269 &ctx,
270 import::db::db_lookup,
271 ),
272
273 "db_lookup_local_" => Function::new_typed_with_env(
274 &mut store,
275 &ctx,
276 import::db::db_lookup_local,
277 ),
278
279 "db_get_" => Function::new_typed_with_env(
280 &mut store,
281 &ctx,
282 import::db::db_get,
283 ),
284
285 "db_get_local_" => Function::new_typed_with_env(
286 &mut store,
287 &ctx,
288 import::db::db_get_local,
289 ),
290
291 "db_contains_key_" => Function::new_typed_with_env(
292 &mut store,
293 &ctx,
294 import::db::db_contains_key,
295 ),
296
297 "db_contains_key_local_" => Function::new_typed_with_env(
298 &mut store,
299 &ctx,
300 import::db::db_contains_key_local,
301 ),
302
303 "db_set_" => Function::new_typed_with_env(
304 &mut store,
305 &ctx,
306 import::db::db_set,
307 ),
308
309 "db_set_local_" => Function::new_typed_with_env(
310 &mut store,
311 &ctx,
312 import::db::db_set_local,
313 ),
314
315 "db_del_" => Function::new_typed_with_env(
316 &mut store,
317 &ctx,
318 import::db::db_del,
319 ),
320
321 "db_del_local_" => Function::new_typed_with_env(
322 &mut store,
323 &ctx,
324 import::db::db_del_local,
325 ),
326
327 "zkas_db_set_" => Function::new_typed_with_env(
328 &mut store,
329 &ctx,
330 import::db::zkas_db_set,
331 ),
332
333 "get_object_bytes_" => Function::new_typed_with_env(
334 &mut store,
335 &ctx,
336 import::util::get_object_bytes,
337 ),
338
339 "get_object_size_" => Function::new_typed_with_env(
340 &mut store,
341 &ctx,
342 import::util::get_object_size,
343 ),
344
345 "merkle_add_" => Function::new_typed_with_env(
346 &mut store,
347 &ctx,
348 import::merkle::merkle_add,
349 ),
350
351 "merkle_add_local_" => Function::new_typed_with_env(
352 &mut store,
353 &ctx,
354 import::merkle::merkle_add_local,
355 ),
356
357 "sparse_merkle_insert_batch_" => Function::new_typed_with_env(
358 &mut store,
359 &ctx,
360 import::smt::sparse_merkle_insert_batch,
361 ),
362
363 "get_verifying_block_height_" => Function::new_typed_with_env(
364 &mut store,
365 &ctx,
366 import::util::get_verifying_block_height,
367 ),
368
369 "get_block_target_" => Function::new_typed_with_env(
370 &mut store,
371 &ctx,
372 import::util::get_block_target,
373 ),
374
375 "get_tx_hash_" => Function::new_typed_with_env(
376 &mut store,
377 &ctx,
378 import::util::get_tx_hash,
379 ),
380
381 "get_call_index_" => Function::new_typed_with_env(
382 &mut store,
383 &ctx,
384 import::util::get_call_index,
385 ),
386
387 "get_blockchain_time_" => Function::new_typed_with_env(
388 &mut store,
389 &ctx,
390 import::util::get_blockchain_time,
391 ),
392
393 "get_last_block_height_" => Function::new_typed_with_env(
394 &mut store,
395 &ctx,
396 import::util::get_last_block_height,
397 ),
398
399 "get_tx_" => Function::new_typed_with_env(
400 &mut store,
401 &ctx,
402 import::util::get_tx,
403 ),
404
405 "get_tx_location_" => Function::new_typed_with_env(
406 &mut store,
407 &ctx,
408 import::util::get_tx_location,
409 ),
410 }
411 };
412
413 debug!(target: "runtime::vm_runtime", "Instantiating module");
414 let instance = Arc::new(Instance::new(&mut store, &module, &imports)?);
415
416 let env_mut = ctx.as_mut(&mut store);
417 env_mut.memory = Some(instance.exports.get_with_generics(MEMORY)?);
418 env_mut.instance = Some(Arc::clone(&instance));
419
420 Ok(Self { instance, store, ctx })
421 }
422
423 fn call(&mut self, section: ContractSection, payload: &[u8]) -> Result<Vec<u8>> {
427 debug!(target: "runtime::vm_runtime", "Calling {} method", section.name());
428
429 let env_mut = self.ctx.as_mut(&mut self.store);
430 env_mut.contract_section = section;
431 assert!(env_mut.contract_return_data.take().is_none());
433
434 let _ = env_mut.logs.take();
436
437 let payload = Self::serialize_payload(&env_mut.contract_id, payload);
439
440 let pages_required = payload.len() / WASM_PAGE_SIZE + 1;
442 self.set_memory_page_size(pages_required as u32)?;
443 self.copy_to_memory(&payload)?;
444
445 debug!(target: "runtime::vm_runtime", "Getting {} function", section.name());
446 let entrypoint = self.instance.exports.get_function(section.name())?;
447
448 debug!(target: "runtime::vm_runtime", "Executing wasm");
454 let ret = match entrypoint.call(&mut self.store, &[Value::I32(0_i32)]) {
455 Ok(retvals) => {
456 self.print_logs();
457 info!(target: "runtime::vm_runtime", "[WASM] {}", self.gas_info());
458 retvals
459 }
460 Err(e) => {
461 self.print_logs();
462 info!(target: "runtime::vm_runtime", "[WASM] {}", self.gas_info());
463 error!(target: "runtime::vm_runtime", "[WASM] Wasmer Runtime Error: {e:#?}");
465 return Err(e.into())
466 }
467 };
468
469 debug!(target: "runtime::vm_runtime", "wasm executed successfully");
470
471 let env_mut = self.ctx.as_mut(&mut self.store);
473 env_mut.contract_section = ContractSection::Null;
474 let retdata = env_mut.contract_return_data.take().unwrap_or_default();
475
476 let retval: i64 = match ret.len() {
479 0 => {
480 debug!(target: "runtime::vm_runtime", "Contract has no return value (expected)");
483 wasm::entrypoint::SUCCESS
484 }
485 _ => {
486 match ret[0] {
487 Value::I64(v) => {
488 debug!(target: "runtime::vm_runtime", "Contract returned: {:?}", ret[0]);
489 v
490 }
491 _ => unreachable!("Got unexpected result return value: {ret:?}"),
494 }
495 }
496 };
497
498 match retval {
502 wasm::entrypoint::SUCCESS => Ok(retdata),
503 _ => {
504 let err = darkfi_sdk::error::ContractError::from(retval);
505 error!(target: "runtime::vm_runtime", "[WASM] Contract returned: {err:?}");
506 Err(Error::ContractError(err))
507 }
508 }
509 }
510
511 pub fn deploy(&mut self, payload: &[u8]) -> Result<()> {
522 let cid = self.ctx.as_ref(&self.store).contract_id;
523 info!(target: "runtime::vm_runtime", "[WASM] Running deploy() for ContractID: {cid}");
524
525 {
527 let env_mut = self.ctx.as_mut(&mut self.store);
528 let contracts = &env_mut.blockchain.lock().unwrap().contracts;
531
532 let zkas_tree_handle =
534 match contracts.lookup(&env_mut.contract_id, SMART_CONTRACT_ZKAS_DB_NAME) {
535 Ok(v) => v,
536 Err(_) => contracts.init(&env_mut.contract_id, SMART_CONTRACT_ZKAS_DB_NAME)?,
537 };
538
539 if contracts.lookup(&env_mut.contract_id, SMART_CONTRACT_MONOTREE_DB_NAME).is_err() {
542 contracts.init(&env_mut.contract_id, SMART_CONTRACT_MONOTREE_DB_NAME)?;
543 }
544
545 let mut db_handles = env_mut.db_handles.borrow_mut();
546 db_handles.push(DbHandle::new(env_mut.contract_id, zkas_tree_handle));
547 }
548
549 let _ = self.call(ContractSection::Deploy, payload)?;
551
552 let env_mut = self.ctx.as_mut(&mut self.store);
554 env_mut
555 .blockchain
556 .lock()
557 .unwrap()
558 .contracts
559 .insert(env_mut.contract_id, &env_mut.contract_bincode)?;
560
561 info!(target: "runtime::vm_runtime", "[WASM] Successfully deployed ContractID: {cid}");
562 Ok(())
563 }
564
565 pub fn metadata(&mut self, payload: &[u8]) -> Result<Vec<u8>> {
572 let cid = self.ctx.as_ref(&self.store).contract_id;
573 info!(target: "runtime::vm_runtime", "[WASM] Running metadata() for ContractID: {cid}");
574
575 debug!(target: "runtime::vm_runtime", "metadata payload: {}", payload.hex());
576 let ret = self.call(ContractSection::Metadata, payload)?;
577 debug!(target: "runtime::vm_runtime", "metadata returned: {:?}", ret.hex());
578
579 info!(target: "runtime::vm_runtime", "[WASM] Successfully got metadata ContractID: {cid}");
580 Ok(ret)
581 }
582
583 pub fn exec(&mut self, payload: &[u8]) -> Result<Vec<u8>> {
589 let cid = self.ctx.as_ref(&self.store).contract_id;
590 info!(target: "runtime::vm_runtime", "[WASM] Running exec() for ContractID: {cid}");
591
592 debug!(target: "runtime::vm_runtime", "exec payload: {}", payload.hex());
593 let ret = self.call(ContractSection::Exec, payload)?;
594 debug!(target: "runtime::vm_runtime", "exec returned: {:?}", ret.hex());
595
596 info!(target: "runtime::vm_runtime", "[WASM] Successfully executed ContractID: {cid}");
597 Ok(ret)
598 }
599
600 pub fn apply(&mut self, update: &[u8]) -> Result<()> {
607 let cid = self.ctx.as_ref(&self.store).contract_id;
608 info!(target: "runtime::vm_runtime", "[WASM] Running apply() for ContractID: {cid}");
609
610 debug!(target: "runtime::vm_runtime", "apply payload: {:?}", update.hex());
611 let ret = self.call(ContractSection::Update, update)?;
612 debug!(target: "runtime::vm_runtime", "apply returned: {:?}", ret.hex());
613
614 info!(target: "runtime::vm_runtime", "[WASM] Successfully applied ContractID: {cid}");
615 Ok(())
616 }
617
618 fn print_logs(&self) {
620 let logs = self.ctx.as_ref(&self.store).logs.borrow();
621 for msg in logs.iter() {
622 info!(target: "runtime::vm_runtime", "[WASM] Contract log: {msg}");
623 }
624 }
625
626 pub fn gas_used(&mut self) -> u64 {
629 let remaining_points = get_remaining_points(&mut self.store, &self.instance);
630
631 match remaining_points {
632 MeteringPoints::Remaining(rem) => {
633 if rem > GAS_LIMIT {
634 unreachable!("Remaining wasm points exceed GAS_LIMIT");
637 }
638 GAS_LIMIT - rem
639 }
640 MeteringPoints::Exhausted => GAS_LIMIT + 1,
641 }
642 }
643
644 fn gas_info(&mut self) -> String {
649 let gas_used = self.gas_used();
650
651 if gas_used > GAS_LIMIT {
652 format!("Gas fully exhausted: {gas_used}/{GAS_LIMIT}")
653 } else {
654 format!("Gas used: {gas_used}/{GAS_LIMIT}")
655 }
656 }
657
658 fn set_memory_page_size(&mut self, pages: u32) -> Result<Pages> {
660 let memory = self.take_memory();
662 let ret = memory.grow(&mut self.store, Pages(pages))?;
664 self.ctx.as_mut(&mut self.store).memory = Some(memory);
666 Ok(ret)
667 }
668
669 fn take_memory(&mut self) -> Memory {
672 let env_memory = &mut self.ctx.as_mut(&mut self.store).memory;
673 let memory = env_memory.take();
674 memory.expect("memory should be set")
675 }
676
677 fn copy_to_memory(&self, payload: &[u8]) -> Result<()> {
679 let env = self.ctx.as_ref(&self.store);
682 let memory_view = env.memory_view(&self.store);
683 memory_view.write_slice(payload, 0)
684 }
685
686 fn serialize_payload(cid: &ContractId, payload: &[u8]) -> Vec<u8> {
690 let ser_cid = serialize(cid);
691 let payload_len = payload.len();
692 let mut out = Vec::with_capacity(ser_cid.len() + 8 + payload_len);
693 out.extend_from_slice(&ser_cid);
694 out.extend_from_slice(&(payload_len as u64).to_le_bytes());
695 out.extend_from_slice(payload);
696 out
697 }
698}