1use std::{
20 cell::{Cell, RefCell},
21 sync::Arc,
22};
23
24use darkfi_sdk::{
25 crypto::contract_id::{ContractId, SMART_CONTRACT_ZKAS_DB_NAME},
26 tx::TransactionHash,
27 wasm, AsHex,
28};
29use darkfi_serial::serialize;
30use log::{debug, error, info};
31use wasmer::{
32 imports, sys::CompilerConfig, wasmparser::Operator, AsStoreMut, AsStoreRef, Function,
33 FunctionEnv, Instance, Memory, MemoryView, Module, Pages, Store, Value, WASM_PAGE_SIZE,
34};
35use wasmer_compiler_singlepass::Singlepass;
36use wasmer_middlewares::{
37 metering::{get_remaining_points, set_remaining_points, MeteringPoints},
38 Metering,
39};
40
41use super::{import, import::db::DbHandle, memory::MemoryManipulation};
42use crate::{blockchain::BlockchainOverlayPtr, Error, Result};
43
44const MEMORY: &str = "memory";
46
47pub const GAS_LIMIT: u64 = 400_000_000;
49
50#[derive(Clone, Copy, PartialEq)]
52pub enum ContractSection {
53 Deploy,
55 Exec,
57 Update,
59 Metadata,
61 Null,
63}
64impl ContractSection {
67 pub const fn name(&self) -> &str {
68 match self {
69 Self::Deploy => "__initialize",
70 Self::Exec => "__entrypoint",
71 Self::Update => "__update",
72 Self::Metadata => "__metadata",
73 Self::Null => unreachable!(),
74 }
75 }
76}
77
78pub struct Env {
80 pub blockchain: BlockchainOverlayPtr,
82 pub db_handles: RefCell<Vec<DbHandle>>,
84 pub contract_id: ContractId,
86 pub contract_bincode: Vec<u8>,
88 pub contract_section: ContractSection,
90 pub contract_return_data: Cell<Option<Vec<u8>>>,
92 pub logs: RefCell<Vec<String>>,
94 pub memory: Option<Memory>,
96 pub objects: RefCell<Vec<Vec<u8>>>,
98 pub verifying_block_height: u32,
101 pub block_target: u32,
103 pub tx_hash: TransactionHash,
105 pub call_idx: u8,
107 pub instance: Option<Arc<Instance>>,
109}
110
111impl Env {
112 pub fn memory_view<'a>(&'a self, store: &'a impl AsStoreRef) -> MemoryView<'a> {
120 self.memory().view(store)
121 }
122
123 pub fn memory(&self) -> &Memory {
125 self.memory.as_ref().unwrap()
126 }
127
128 pub fn subtract_gas(&mut self, ctx: &mut impl AsStoreMut, gas: u64) {
130 match get_remaining_points(ctx, self.instance.as_ref().unwrap()) {
131 MeteringPoints::Remaining(rem) => {
132 if gas > rem {
133 set_remaining_points(ctx, self.instance.as_ref().unwrap(), 0);
134 } else {
135 set_remaining_points(ctx, self.instance.as_ref().unwrap(), rem - gas);
136 }
137 }
138 MeteringPoints::Exhausted => {
139 set_remaining_points(ctx, self.instance.as_ref().unwrap(), 0);
140 }
141 }
142 }
143}
144
145pub struct Runtime {
147 pub instance: Arc<Instance>,
149 pub store: Store,
151 pub ctx: FunctionEnv<Env>,
153}
154
155impl Runtime {
156 pub fn new(
158 wasm_bytes: &[u8],
159 blockchain: BlockchainOverlayPtr,
160 contract_id: ContractId,
161 verifying_block_height: u32,
162 block_target: u32,
163 tx_hash: TransactionHash,
164 call_idx: u8,
165 ) -> Result<Self> {
166 info!(target: "runtime::vm_runtime", "[WASM] Instantiating a new runtime");
167 let cost_function = |_operator: &Operator| -> u64 { 1 };
173
174 let metering = Arc::new(Metering::new(GAS_LIMIT, cost_function));
178
179 let mut compiler_config = Singlepass::new();
181 compiler_config.push_middleware(metering);
182 let mut store = Store::new(compiler_config);
183
184 debug!(target: "runtime::vm_runtime", "Compiling module");
185 let module = Module::new(&store, wasm_bytes)?;
186
187 let db_handles = RefCell::new(vec![]);
189 let logs = RefCell::new(vec![]);
190
191 debug!(target: "runtime::vm_runtime", "Importing functions");
192
193 let ctx = FunctionEnv::new(
194 &mut store,
195 Env {
196 blockchain,
197 db_handles,
198 contract_id,
199 contract_bincode: wasm_bytes.to_vec(),
200 contract_section: ContractSection::Null,
201 contract_return_data: Cell::new(None),
202 logs,
203 memory: None,
204 objects: RefCell::new(vec![]),
205 verifying_block_height,
206 block_target,
207 tx_hash,
208 call_idx,
209 instance: None,
210 },
211 );
212
213 let imports = imports! {
214 "env" => {
215 "drk_log_" => Function::new_typed_with_env(
216 &mut store,
217 &ctx,
218 import::util::drk_log,
219 ),
220
221 "set_return_data_" => Function::new_typed_with_env(
222 &mut store,
223 &ctx,
224 import::util::set_return_data,
225 ),
226
227 "db_init_" => Function::new_typed_with_env(
228 &mut store,
229 &ctx,
230 import::db::db_init,
231 ),
232
233 "db_lookup_" => Function::new_typed_with_env(
234 &mut store,
235 &ctx,
236 import::db::db_lookup,
237 ),
238
239 "db_get_" => Function::new_typed_with_env(
240 &mut store,
241 &ctx,
242 import::db::db_get,
243 ),
244
245 "db_contains_key_" => Function::new_typed_with_env(
246 &mut store,
247 &ctx,
248 import::db::db_contains_key,
249 ),
250
251 "db_set_" => Function::new_typed_with_env(
252 &mut store,
253 &ctx,
254 import::db::db_set,
255 ),
256
257 "db_del_" => Function::new_typed_with_env(
258 &mut store,
259 &ctx,
260 import::db::db_del,
261 ),
262
263 "zkas_db_set_" => Function::new_typed_with_env(
264 &mut store,
265 &ctx,
266 import::db::zkas_db_set,
267 ),
268
269 "get_object_bytes_" => Function::new_typed_with_env(
270 &mut store,
271 &ctx,
272 import::util::get_object_bytes,
273 ),
274
275 "get_object_size_" => Function::new_typed_with_env(
276 &mut store,
277 &ctx,
278 import::util::get_object_size,
279 ),
280
281 "merkle_add_" => Function::new_typed_with_env(
282 &mut store,
283 &ctx,
284 import::merkle::merkle_add,
285 ),
286
287 "sparse_merkle_insert_batch_" => Function::new_typed_with_env(
288 &mut store,
289 &ctx,
290 import::smt::sparse_merkle_insert_batch,
291 ),
292
293 "get_verifying_block_height_" => Function::new_typed_with_env(
294 &mut store,
295 &ctx,
296 import::util::get_verifying_block_height,
297 ),
298
299 "get_block_target_" => Function::new_typed_with_env(
300 &mut store,
301 &ctx,
302 import::util::get_block_target,
303 ),
304
305 "get_tx_hash_" => Function::new_typed_with_env(
306 &mut store,
307 &ctx,
308 import::util::get_tx_hash,
309 ),
310
311 "get_call_index_" => Function::new_typed_with_env(
312 &mut store,
313 &ctx,
314 import::util::get_call_index,
315 ),
316
317 "get_blockchain_time_" => Function::new_typed_with_env(
318 &mut store,
319 &ctx,
320 import::util::get_blockchain_time,
321 ),
322
323 "get_last_block_height_" => Function::new_typed_with_env(
324 &mut store,
325 &ctx,
326 import::util::get_last_block_height,
327 ),
328
329 "get_tx_" => Function::new_typed_with_env(
330 &mut store,
331 &ctx,
332 import::util::get_tx,
333 ),
334
335 "get_tx_location_" => Function::new_typed_with_env(
336 &mut store,
337 &ctx,
338 import::util::get_tx_location,
339 ),
340 }
341 };
342
343 debug!(target: "runtime::vm_runtime", "Instantiating module");
344 let instance = Arc::new(Instance::new(&mut store, &module, &imports)?);
345
346 let env_mut = ctx.as_mut(&mut store);
347 env_mut.memory = Some(instance.exports.get_with_generics(MEMORY)?);
348 env_mut.instance = Some(Arc::clone(&instance));
349
350 Ok(Self { instance, store, ctx })
351 }
352
353 fn call(&mut self, section: ContractSection, payload: &[u8]) -> Result<Vec<u8>> {
357 debug!(target: "runtime::vm_runtime", "Calling {} method", section.name());
358
359 let env_mut = self.ctx.as_mut(&mut self.store);
360 env_mut.contract_section = section;
361 assert!(env_mut.contract_return_data.take().is_none());
363
364 let _ = env_mut.logs.take();
366
367 let payload = Self::serialize_payload(&env_mut.contract_id, payload);
369
370 let pages_required = payload.len() / WASM_PAGE_SIZE + 1;
372 self.set_memory_page_size(pages_required as u32)?;
373 self.copy_to_memory(&payload)?;
374
375 debug!(target: "runtime::vm_runtime", "Getting {} function", section.name());
376 let entrypoint = self.instance.exports.get_function(section.name())?;
377
378 debug!(target: "runtime::vm_runtime", "Executing wasm");
384 let ret = match entrypoint.call(&mut self.store, &[Value::I32(0_i32)]) {
385 Ok(retvals) => {
386 self.print_logs();
387 info!(target: "runtime::vm_runtime", "[WASM] {}", self.gas_info());
388 retvals
389 }
390 Err(e) => {
391 self.print_logs();
392 info!(target: "runtime::vm_runtime", "[WASM] {}", self.gas_info());
393 error!(target: "runtime::vm_runtime", "[WASM] Wasmer Runtime Error: {:#?}", e);
395 return Err(e.into())
396 }
397 };
398
399 debug!(target: "runtime::vm_runtime", "wasm executed successfully");
400
401 let env_mut = self.ctx.as_mut(&mut self.store);
403 env_mut.contract_section = ContractSection::Null;
404 let retdata = env_mut.contract_return_data.take().unwrap_or_default();
405
406 let retval: i64 = match ret.len() {
409 0 => {
410 debug!(target: "runtime::vm_runtime", "Contract has no return value (expected)");
413 wasm::entrypoint::SUCCESS
414 }
415 _ => {
416 match ret[0] {
417 Value::I64(v) => {
418 debug!(target: "runtime::vm_runtime", "Contract returned: {:?}", ret[0]);
419 v
420 }
421 _ => unreachable!("Got unexpected result return value: {:?}", ret),
424 }
425 }
426 };
427
428 match retval {
432 wasm::entrypoint::SUCCESS => Ok(retdata),
433 _ => {
434 let err = darkfi_sdk::error::ContractError::from(retval);
435 error!(target: "runtime::vm_runtime", "[WASM] Contract returned: {:?}", err);
436 Err(Error::ContractError(err))
437 }
438 }
439 }
440
441 pub fn deploy(&mut self, payload: &[u8]) -> Result<()> {
452 let cid = self.ctx.as_ref(&self.store).contract_id;
453 info!(target: "runtime::vm_runtime", "[WASM] Running deploy() for ContractID: {}", cid);
454
455 {
457 let env_mut = self.ctx.as_mut(&mut self.store);
458 let contracts = &env_mut.blockchain.lock().unwrap().contracts;
461
462 let zkas_tree_handle =
464 match contracts.lookup(&env_mut.contract_id, SMART_CONTRACT_ZKAS_DB_NAME) {
465 Ok(v) => v,
466 Err(_) => contracts.init(&env_mut.contract_id, SMART_CONTRACT_ZKAS_DB_NAME)?,
467 };
468
469 let mut db_handles = env_mut.db_handles.borrow_mut();
470 db_handles.push(DbHandle::new(env_mut.contract_id, zkas_tree_handle));
471 }
472
473 let _ = self.call(ContractSection::Deploy, payload)?;
475
476 let env_mut = self.ctx.as_mut(&mut self.store);
478 env_mut
479 .blockchain
480 .lock()
481 .unwrap()
482 .contracts
483 .insert(env_mut.contract_id, &env_mut.contract_bincode)?;
484
485 info!(target: "runtime::vm_runtime", "[WASM] Successfully deployed ContractID: {}", cid);
486 Ok(())
487 }
488
489 pub fn metadata(&mut self, payload: &[u8]) -> Result<Vec<u8>> {
496 let cid = self.ctx.as_ref(&self.store).contract_id;
497 info!(target: "runtime::vm_runtime", "[WASM] Running metadata() for ContractID: {}", cid);
498
499 debug!(target: "runtime::vm_runtime", "metadata payload: {}", payload.hex());
500 let ret = self.call(ContractSection::Metadata, payload)?;
501 debug!(target: "runtime::vm_runtime", "metadata returned: {:?}", ret.hex());
502
503 info!(target: "runtime::vm_runtime", "[WASM] Successfully got metadata ContractID: {}", cid);
504 Ok(ret)
505 }
506
507 pub fn exec(&mut self, payload: &[u8]) -> Result<Vec<u8>> {
513 let cid = self.ctx.as_ref(&self.store).contract_id;
514 info!(target: "runtime::vm_runtime", "[WASM] Running exec() for ContractID: {}", cid);
515
516 debug!(target: "runtime::vm_runtime", "exec payload: {}", payload.hex());
517 let ret = self.call(ContractSection::Exec, payload)?;
518 debug!(target: "runtime::vm_runtime", "exec returned: {:?}", ret.hex());
519
520 info!(target: "runtime::vm_runtime", "[WASM] Successfully executed ContractID: {}", cid);
521 Ok(ret)
522 }
523
524 pub fn apply(&mut self, update: &[u8]) -> Result<()> {
531 let cid = self.ctx.as_ref(&self.store).contract_id;
532 info!(target: "runtime::vm_runtime", "[WASM] Running apply() for ContractID: {}", cid);
533
534 debug!(target: "runtime::vm_runtime", "apply payload: {:?}", update.hex());
535 let ret = self.call(ContractSection::Update, update)?;
536 debug!(target: "runtime::vm_runtime", "apply returned: {:?}", ret.hex());
537
538 info!(target: "runtime::vm_runtime", "[WASM] Successfully applied ContractID: {}", cid);
539 Ok(())
540 }
541
542 fn print_logs(&self) {
544 let logs = self.ctx.as_ref(&self.store).logs.borrow();
545 for msg in logs.iter() {
546 info!(target: "runtime::vm_runtime", "[WASM] Contract log: {}", msg);
547 }
548 }
549
550 pub fn gas_used(&mut self) -> u64 {
553 let remaining_points = get_remaining_points(&mut self.store, &self.instance);
554
555 match remaining_points {
556 MeteringPoints::Remaining(rem) => {
557 if rem > GAS_LIMIT {
558 unreachable!("Remaining wasm points exceed GAS_LIMIT");
561 }
562 GAS_LIMIT - rem
563 }
564 MeteringPoints::Exhausted => GAS_LIMIT + 1,
565 }
566 }
567
568 fn gas_info(&mut self) -> String {
573 let gas_used = self.gas_used();
574
575 if gas_used > GAS_LIMIT {
576 format!("Gas fully exhausted: {}/{}", gas_used, GAS_LIMIT)
577 } else {
578 format!("Gas used: {}/{}", gas_used, GAS_LIMIT)
579 }
580 }
581
582 fn set_memory_page_size(&mut self, pages: u32) -> Result<Pages> {
584 let memory = self.take_memory();
586 let ret = memory.grow(&mut self.store, Pages(pages))?;
588 self.ctx.as_mut(&mut self.store).memory = Some(memory);
590 Ok(ret)
591 }
592
593 fn take_memory(&mut self) -> Memory {
596 let env_memory = &mut self.ctx.as_mut(&mut self.store).memory;
597 let memory = env_memory.take();
598 memory.expect("memory should be set")
599 }
600
601 fn copy_to_memory(&self, payload: &[u8]) -> Result<()> {
603 let env = self.ctx.as_ref(&self.store);
606 let memory_view = env.memory_view(&self.store);
607 memory_view.write_slice(payload, 0)
608 }
609
610 fn serialize_payload(cid: &ContractId, payload: &[u8]) -> Vec<u8> {
614 let ser_cid = serialize(cid);
615 let payload_len = payload.len();
616 let mut out = Vec::with_capacity(ser_cid.len() + 8 + payload_len);
617 out.extend_from_slice(&ser_cid);
618 out.extend_from_slice(&(payload_len as u64).to_le_bytes());
619 out.extend_from_slice(payload);
620 out
621 }
622}