numbat_wasm_debug/
ext_mock.rs

1use numbat_wasm::{Address, ArgBuffer, BoxedBytes, CodeMetadata, H256};
2
3use crate::async_data::*;
4use crate::big_int_mock::*;
5use crate::big_uint_mock::*;
6use crate::blockchain_mock::*;
7use crate::display_util::*;
8
9use numbat_wasm::err_msg;
10use numbat_wasm::ContractHookApi;
11use numbat_wasm::{BigIntApi, BigUintApi};
12
13use num_bigint::{BigInt, BigUint};
14use num_traits::cast::ToPrimitive;
15
16use alloc::vec::Vec;
17
18use std::collections::HashMap;
19use std::fmt;
20
21use alloc::rc::Rc;
22use core::cell::RefCell;
23
24use sha3::{Digest, Keccak256, Sha3_256};
25
26const ADDRESS_LENGTH: usize = 32;
27const TOPIC_LENGTH: usize = 32;
28
29pub struct TxPanic {
30	pub status: u64,
31	pub message: Vec<u8>,
32}
33
34#[derive(Clone, Debug)]
35pub struct TxInput {
36	pub from: Address,
37	pub to: Address,
38	pub call_value: BigUint,
39	pub dcdt_value: BigUint,
40	pub dcdt_token_name: Vec<u8>,
41	pub func_name: Vec<u8>,
42	pub args: Vec<Vec<u8>>,
43	pub gas_limit: u64,
44	pub gas_price: u64,
45	pub tx_hash: H256,
46}
47
48impl fmt::Display for TxInput {
49	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50		write!(f, "TxInput {{ func: {}, args: {:?}, call_value: {}, dcdt_token_name: {:?}, dcdt_value: {:?}, from: 0x{}, to: 0x{}\n}}", 
51            String::from_utf8(self.func_name.clone()).unwrap(),
52            self.args,
53            self.call_value,
54            self.dcdt_token_name,
55            self.dcdt_value,
56            address_hex(&self.from),
57            address_hex(&self.to))
58	}
59}
60
61impl TxInput {
62	pub fn add_arg(&mut self, arg: Vec<u8>) {
63		self.args.push(arg);
64	}
65}
66
67#[derive(Clone, Debug)]
68pub struct TxResult {
69	pub result_status: u64,
70	pub result_message: Vec<u8>,
71	pub result_values: Vec<Vec<u8>>,
72}
73
74impl fmt::Display for TxResult {
75	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76		let results_hex: Vec<String> = self
77			.result_values
78			.iter()
79			.map(|r| format!("0x{}", hex::encode(r)))
80			.collect();
81		write!(
82			f,
83			"TxResult {{\n\tresult_status: {},\n\tresult_values:{:?}\n}}",
84			self.result_status, results_hex
85		)
86	}
87}
88
89impl TxResult {
90	pub fn empty() -> TxResult {
91		TxResult {
92			result_status: 0,
93			result_message: Vec::new(),
94			result_values: Vec::new(),
95		}
96	}
97	pub fn print(&self) {
98		println!("{}", self);
99	}
100}
101
102#[derive(Debug)]
103pub struct SendBalance {
104	pub recipient: Address,
105	pub amount: BigUint,
106}
107
108#[derive(Debug)]
109pub struct TxOutput {
110	pub contract_storage: HashMap<Vec<u8>, Vec<u8>>,
111	pub result: TxResult,
112	pub send_balance_list: Vec<SendBalance>,
113	pub async_call: Option<AsyncCallTxData>,
114}
115
116impl Default for TxOutput {
117	fn default() -> Self {
118		TxOutput {
119			contract_storage: HashMap::new(),
120			result: TxResult::empty(),
121			send_balance_list: Vec::new(),
122			async_call: None,
123		}
124	}
125}
126
127impl TxOutput {
128	pub fn from_panic_obj(panic_obj: &TxPanic) -> Self {
129		TxOutput {
130			contract_storage: HashMap::new(),
131			result: TxResult {
132				result_status: panic_obj.status,
133				result_message: panic_obj.message.clone(),
134				result_values: Vec::new(),
135			},
136			send_balance_list: Vec::new(),
137			async_call: None,
138		}
139	}
140
141	pub fn from_panic_string(_: &str) -> Self {
142		TxOutput {
143			contract_storage: HashMap::new(),
144			result: TxResult {
145				result_status: 4,
146				result_message: b"panic occurred".to_vec(),
147				result_values: Vec::new(),
148			},
149			send_balance_list: Vec::new(),
150			async_call: None,
151		}
152	}
153}
154
155#[derive(Debug)]
156pub struct TxContext {
157	pub blockchain_info_box: Box<BlockchainTxInfo>,
158	pub tx_input_box: Box<TxInput>,
159	pub tx_output_cell: Rc<RefCell<TxOutput>>,
160}
161
162impl TxContext {
163	pub fn new(blockchain_info: BlockchainTxInfo, tx_input: TxInput, tx_output: TxOutput) -> Self {
164		TxContext {
165			blockchain_info_box: Box::new(blockchain_info),
166			tx_input_box: Box::new(tx_input),
167			tx_output_cell: Rc::new(RefCell::new(tx_output)),
168		}
169	}
170
171	pub fn into_output(self) -> TxOutput {
172		let ref_cell = Rc::try_unwrap(self.tx_output_cell).unwrap();
173		ref_cell.replace(TxOutput::default())
174	}
175
176	pub fn dummy() -> Self {
177		TxContext {
178			blockchain_info_box: Box::new(BlockchainTxInfo {
179				previous_block_info: BlockInfo::new(),
180				current_block_info: BlockInfo::new(),
181				contract_balance: 0u32.into(),
182				contract_owner: None,
183			}),
184			tx_input_box: Box::new(TxInput {
185				from: Address::zero(),
186				to: Address::zero(),
187				call_value: 0u32.into(),
188				dcdt_value: 0u32.into(),
189				dcdt_token_name: Vec::new(),
190				func_name: Vec::new(),
191				args: Vec::new(),
192				gas_limit: 0,
193				gas_price: 0,
194				tx_hash: b"dummy...........................".into(),
195			}),
196			tx_output_cell: Rc::new(RefCell::new(TxOutput::default())),
197		}
198	}
199}
200
201impl Clone for TxContext {
202	fn clone(&self) -> Self {
203		TxContext {
204			blockchain_info_box: self.blockchain_info_box.clone(),
205			tx_input_box: self.tx_input_box.clone(),
206			tx_output_cell: Rc::clone(&self.tx_output_cell),
207		}
208	}
209}
210
211impl numbat_wasm::ContractHookApi<RustBigInt, RustBigUint> for TxContext {
212	fn get_sc_address(&self) -> Address {
213		self.tx_input_box.to.clone()
214	}
215
216	fn get_owner_address(&self) -> Address {
217		self.blockchain_info_box
218			.contract_owner
219			.clone()
220			.unwrap_or_else(|| panic!("contract owner address not set"))
221	}
222
223	fn get_caller(&self) -> Address {
224		self.tx_input_box.from.clone()
225	}
226
227	fn get_balance(&self, address: &Address) -> RustBigUint {
228		if address != &self.get_sc_address() {
229			panic!("get balance not yet implemented for accounts other than the contract itself");
230		}
231		self.blockchain_info_box.contract_balance.clone().into()
232	}
233
234	fn storage_store_slice_u8(&self, key: &[u8], value: &[u8]) {
235		// TODO: extract magic strings somewhere
236		if key.starts_with(&b"NUMBAT"[..]) {
237			panic!(TxPanic {
238				status: 10,
239				message: b"cannot write to storage under Numbat reserved key".to_vec(),
240			});
241		}
242
243		let mut tx_output = self.tx_output_cell.borrow_mut();
244		tx_output
245			.contract_storage
246			.insert(key.to_vec(), value.to_vec());
247	}
248
249	fn storage_load_vec_u8(&self, key: &[u8]) -> Vec<u8> {
250		let tx_output = self.tx_output_cell.borrow();
251		match tx_output.contract_storage.get(&key.to_vec()) {
252			None => Vec::with_capacity(0),
253			Some(value) => value.clone(),
254		}
255	}
256
257	#[inline]
258	fn storage_load_len(&self, key: &[u8]) -> usize {
259		self.storage_load_vec_u8(key).len()
260	}
261
262	fn storage_store_bytes32(&self, key: &[u8], value: &[u8; 32]) {
263		self.storage_store_slice_u8(key, &value[..].to_vec());
264	}
265
266	fn storage_load_bytes32(&self, key: &[u8]) -> [u8; 32] {
267		let value = self.storage_load_vec_u8(key);
268		let mut res = [0u8; 32];
269		let offset = 32 - value.len();
270		if !value.is_empty() {
271			res[offset..].clone_from_slice(&value[..]);
272		}
273		res
274	}
275
276	fn storage_store_big_uint(&self, key: &[u8], value: &RustBigUint) {
277		self.storage_store_slice_u8(key, &value.to_bytes_be());
278	}
279
280	fn storage_load_big_uint(&self, key: &[u8]) -> RustBigUint {
281		let value = self.storage_load_vec_u8(key);
282		let bi = BigInt::from_bytes_be(num_bigint::Sign::Plus, value.as_slice());
283		bi.into()
284	}
285
286	fn storage_store_big_uint_raw(&self, _key: &[u8], _handle: i32) {
287		panic!("cannot call storage_store_big_uint_raw in debug mode");
288	}
289
290	fn storage_load_big_uint_raw(&self, _key: &[u8]) -> i32 {
291		panic!("cannot call storage_load_big_uint_raw in debug mode");
292	}
293
294	fn storage_store_big_int(&self, key: &[u8], value: &RustBigInt) {
295		self.storage_store_slice_u8(key, &value.to_signed_bytes_be());
296	}
297
298	fn storage_load_big_int(&self, key: &[u8]) -> RustBigInt {
299		let value = self.storage_load_vec_u8(key);
300		let bi = BigInt::from_signed_bytes_be(value.as_slice());
301		bi.into()
302	}
303
304	fn storage_store_i64(&self, key: &[u8], value: i64) {
305		self.storage_store_big_int(key, &RustBigInt::from(value));
306	}
307
308	fn storage_store_u64(&self, key: &[u8], value: u64) {
309		self.storage_store_big_uint(key, &RustBigUint::from(value));
310	}
311
312	fn storage_load_i64(&self, key: &[u8]) -> i64 {
313		let bi = self.storage_load_big_int(key);
314		if let Some(v) = bi.0.to_i64() {
315			v
316		} else {
317			panic!(TxPanic {
318				status: 10,
319				message: b"storage value out of range".to_vec(),
320			})
321		}
322	}
323
324	fn storage_load_u64(&self, key: &[u8]) -> u64 {
325		let bu = self.storage_load_big_uint(key);
326		if let Some(v) = bu.0.to_u64() {
327			v
328		} else {
329			panic!(TxPanic {
330				status: 10,
331				message: b"storage value out of range".to_vec(),
332			})
333		}
334	}
335
336	#[inline]
337	fn get_call_value_big_uint(&self) -> RustBigUint {
338		self.tx_input_box.call_value.clone().into()
339	}
340
341	#[inline]
342	fn get_dcdt_value_big_uint(&self) -> RustBigUint {
343		self.tx_input_box.dcdt_value.clone().into()
344	}
345
346	#[inline]
347	fn get_dcdt_token_name(&self) -> Vec<u8> {
348		self.tx_input_box.dcdt_token_name.clone()
349	}
350
351	fn send_tx(&self, to: &Address, amount: &RustBigUint, _data: &[u8]) {
352		let mut tx_output = self.tx_output_cell.borrow_mut();
353		tx_output.send_balance_list.push(SendBalance {
354			recipient: to.clone(),
355			amount: amount.value(),
356		})
357	}
358
359	fn async_call(&self, to: &Address, amount: &RustBigUint, data: &[u8]) {
360		let mut tx_output = self.tx_output_cell.borrow_mut();
361		tx_output.async_call = Some(AsyncCallTxData {
362			to: to.clone(),
363			call_value: amount.value(),
364			call_data: data.to_vec(),
365			tx_hash: self.get_tx_hash(),
366		});
367	}
368
369	fn deploy_contract(
370		&self,
371		_gas: u64,
372		_amount: &RustBigUint,
373		_code: &BoxedBytes,
374		_code_metadata: CodeMetadata,
375		_arg_buffer: &ArgBuffer,
376	) -> Address {
377		panic!("deploy_contract not yet implemented")
378	}
379
380	fn get_tx_hash(&self) -> H256 {
381		self.tx_input_box.tx_hash.clone()
382	}
383
384	fn get_gas_left(&self) -> u64 {
385		self.tx_input_box.gas_limit
386	}
387
388	fn get_block_timestamp(&self) -> u64 {
389		self.blockchain_info_box.current_block_info.block_timestamp
390	}
391
392	fn get_block_nonce(&self) -> u64 {
393		self.blockchain_info_box.current_block_info.block_nonce
394	}
395
396	fn get_block_round(&self) -> u64 {
397		self.blockchain_info_box.current_block_info.block_round
398	}
399
400	fn get_block_epoch(&self) -> u64 {
401		self.blockchain_info_box.current_block_info.block_epoch
402	}
403
404	fn get_block_random_seed(&self) -> Box<[u8; 48]> {
405		self.blockchain_info_box
406			.current_block_info
407			.block_random_seed
408			.clone()
409	}
410
411	fn get_prev_block_timestamp(&self) -> u64 {
412		self.blockchain_info_box.previous_block_info.block_timestamp
413	}
414
415	fn get_prev_block_nonce(&self) -> u64 {
416		self.blockchain_info_box.previous_block_info.block_nonce
417	}
418
419	fn get_prev_block_round(&self) -> u64 {
420		self.blockchain_info_box.previous_block_info.block_round
421	}
422
423	fn get_prev_block_epoch(&self) -> u64 {
424		self.blockchain_info_box.previous_block_info.block_epoch
425	}
426
427	fn get_prev_block_random_seed(&self) -> Box<[u8; 48]> {
428		self.blockchain_info_box
429			.previous_block_info
430			.block_random_seed
431			.clone()
432	}
433
434	fn sha256(&self, data: &[u8]) -> H256 {
435		let mut hasher = Sha3_256::new();
436		hasher.input(data);
437		let hash: [u8; 32] = hasher.result().into();
438		hash.into()
439	}
440
441	fn keccak256(&self, data: &[u8]) -> H256 {
442		let mut hasher = Keccak256::new();
443		hasher.input(data);
444		let hash: [u8; 32] = hasher.result().into();
445		hash.into()
446	}
447}
448
449impl numbat_wasm::ContractIOApi<RustBigInt, RustBigUint> for TxContext {
450	fn get_num_arguments(&self) -> i32 {
451		self.tx_input_box.args.len() as i32
452	}
453
454	fn check_not_payable(&self) {
455		if self.get_call_value_big_uint() > 0 {
456			self.signal_error(err_msg::NON_PAYABLE);
457		}
458	}
459
460	fn get_argument_len(&self, arg_index: i32) -> usize {
461		let arg = self.get_argument_vec_u8(arg_index);
462		arg.len()
463	}
464
465	fn copy_argument_to_slice(&self, _arg_index: i32, _slice: &mut [u8]) {
466		panic!("copy_argument_to_slice not yet implemented")
467	}
468
469	fn get_argument_vec_u8(&self, arg_index: i32) -> Vec<u8> {
470		let arg_idx_usize = arg_index as usize;
471		if arg_idx_usize >= self.tx_input_box.args.len() {
472			panic!("Tx arg index out of range");
473		}
474		self.tx_input_box.args[arg_idx_usize].clone()
475	}
476
477	fn get_argument_boxed_bytes(&self, arg_index: i32) -> BoxedBytes {
478		self.get_argument_vec_u8(arg_index).into()
479	}
480
481	fn get_argument_big_uint(&self, arg_index: i32) -> RustBigUint {
482		let bytes = self.get_argument_vec_u8(arg_index);
483		RustBigUint::from_bytes_be(&bytes[..])
484	}
485
486	fn get_argument_big_int(&self, arg_index: i32) -> RustBigInt {
487		let bytes = self.get_argument_vec_u8(arg_index);
488		RustBigInt::from_signed_bytes_be(&bytes)
489	}
490
491	fn get_argument_big_uint_raw(&self, _arg_index: i32) -> i32 {
492		panic!("cannot call get_argument_big_uint_raw in debug mode");
493	}
494
495	fn get_argument_big_int_raw(&self, _arg_index: i32) -> i32 {
496		panic!("cannot call get_argument_big_int_raw in debug mode");
497	}
498
499	fn get_argument_i64(&self, arg_index: i32) -> i64 {
500		let bytes = self.get_argument_vec_u8(arg_index);
501		let bi = BigInt::from_signed_bytes_be(&bytes);
502		if let Some(v) = bi.to_i64() {
503			v
504		} else {
505			panic!(TxPanic {
506				status: 10,
507				message: b"argument out of range".to_vec(),
508			})
509		}
510	}
511
512	fn get_argument_u64(&self, arg_index: i32) -> u64 {
513		let bytes = self.get_argument_vec_u8(arg_index);
514		let bu = BigUint::from_bytes_be(&bytes);
515		if let Some(v) = bu.to_u64() {
516			v
517		} else {
518			panic!(TxPanic {
519				status: 10,
520				message: b"argument out of range".to_vec(),
521			})
522		}
523	}
524
525	fn finish_slice_u8(&self, slice: &[u8]) {
526		let mut v = vec![0u8; slice.len()];
527		v.copy_from_slice(slice);
528		let mut tx_output = self.tx_output_cell.borrow_mut();
529		tx_output.result.result_values.push(v)
530	}
531
532	fn finish_big_int(&self, bi: &RustBigInt) {
533		self.finish_slice_u8(bi.to_signed_bytes_be().as_slice());
534	}
535
536	fn finish_big_uint(&self, bu: &RustBigUint) {
537		self.finish_slice_u8(bu.to_bytes_be().as_slice());
538	}
539
540	fn finish_big_int_raw(&self, _handle: i32) {
541		panic!("cannot call finish_big_int_raw in debug mode");
542	}
543
544	fn finish_big_uint_raw(&self, _handle: i32) {
545		panic!("cannot call finish_big_uint_raw in debug mode");
546	}
547
548	fn finish_i64(&self, value: i64) {
549		self.finish_big_int(&value.into());
550	}
551
552	fn finish_u64(&self, value: u64) {
553		self.finish_big_uint(&value.into());
554	}
555
556	fn signal_error(&self, message: &[u8]) -> ! {
557		panic!(TxPanic {
558			status: 4,
559			message: message.to_vec()
560		})
561	}
562
563	fn write_log(&self, _topics: &[[u8; 32]], _data: &[u8]) {
564		// does nothing yet
565		// TODO: implement at some point
566	}
567}