numbat_wasm_debug/
blockchain_mock.rs

1use numbat_wasm::{Address, H256};
2
3use crate::big_int_mock::*;
4use crate::big_uint_mock::*;
5use crate::contract_map::*;
6use crate::display_util::*;
7use crate::ext_mock::*;
8
9use numbat_wasm::err_msg;
10use numbat_wasm::BigUintApi;
11use numbat_wasm::CallableContract;
12use numbat_wasm::ContractHookApi;
13
14use num_bigint::{BigInt, BigUint};
15use num_traits::{cast::ToPrimitive, Zero};
16
17use alloc::boxed::Box;
18use alloc::vec::Vec;
19
20use std::collections::HashMap;
21use std::fmt;
22use std::fmt::Write;
23
24use alloc::rc::Rc;
25use core::cell::RefCell;
26
27const NUMBAT_REWARD_KEY: &[u8] = b"NUMBATreward";
28
29pub struct AccountData {
30	pub address: Address,
31	pub nonce: u64,
32	pub balance: BigUint,
33	pub storage: HashMap<Vec<u8>, Vec<u8>>,
34	pub dcdt: Option<HashMap<Vec<u8>, BigUint>>,
35	pub contract_path: Option<Vec<u8>>,
36	pub contract_owner: Option<Address>,
37}
38
39impl fmt::Display for AccountData {
40	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41		let mut storage_buf = String::new();
42		let mut storage_keys: Vec<Vec<u8>> = self.storage.iter().map(|(k, _)| k.clone()).collect();
43		storage_keys.sort();
44
45		for key in &storage_keys {
46			let value = self.storage.get(key).unwrap();
47			write!(
48				&mut storage_buf,
49				"\n\t\t{} -> 0x{}",
50				key_hex(key.as_slice()),
51				hex::encode(value.as_slice())
52			)
53			.unwrap();
54		}
55
56		let mut dcdt_buf = String::new();
57		let dcdt_unwrapped = self.dcdt.clone().unwrap_or_default();
58		let mut dcdt_keys: Vec<Vec<u8>> = dcdt_unwrapped.iter().map(|(k, _)| k.clone()).collect();
59		dcdt_keys.sort();
60
61		for key in &dcdt_keys {
62			let value = dcdt_unwrapped.get(key).unwrap();
63			write!(
64				&mut dcdt_buf,
65				"\n\t\t{} -> 0x{}",
66				key_hex(key.as_slice()),
67				hex::encode(value.to_bytes_be())
68			)
69			.unwrap();
70		}
71
72		write!(
73			f,
74			"AccountData {{ nonce: {}, balance: {}, storage: [{} ], dcdt: [{} ] }}",
75			self.nonce, self.balance, storage_buf, dcdt_buf
76		)
77	}
78}
79
80#[derive(Clone, Debug)]
81pub struct BlockInfo {
82	pub block_timestamp: u64,
83	pub block_nonce: u64,
84	pub block_round: u64,
85	pub block_epoch: u64,
86	pub block_random_seed: Box<[u8; 48]>,
87}
88
89impl BlockInfo {
90	pub fn new() -> Self {
91		BlockInfo {
92			block_timestamp: 0,
93			block_nonce: 0,
94			block_round: 0,
95			block_epoch: 0,
96			block_random_seed: Box::from([0u8; 48]),
97		}
98	}
99}
100
101impl Default for BlockInfo {
102	fn default() -> Self {
103		Self::new()
104	}
105}
106
107pub struct BlockchainMock {
108	pub accounts: HashMap<Address, AccountData>,
109	pub new_addresses: HashMap<(Address, u64), Address>,
110	pub previous_block_info: BlockInfo,
111	pub current_block_info: BlockInfo,
112}
113
114impl BlockchainMock {
115	pub fn new() -> Self {
116		BlockchainMock {
117			accounts: HashMap::new(),
118			new_addresses: HashMap::new(),
119			previous_block_info: BlockInfo::new(),
120			current_block_info: BlockInfo::new(),
121		}
122	}
123}
124
125impl Default for BlockchainMock {
126	fn default() -> Self {
127		Self::new()
128	}
129}
130
131impl BlockchainMock {
132	pub fn add_account(&mut self, acct: AccountData) {
133		self.accounts.insert(acct.address.clone(), acct);
134	}
135
136	pub fn print_accounts(&self) {
137		let mut accounts_buf = String::new();
138		for (address, account) in &self.accounts {
139			write!(
140				&mut accounts_buf,
141				"\n\t{} -> {}",
142				address_hex(address),
143				account
144			)
145			.unwrap();
146		}
147		println!("Accounts: {}", &accounts_buf);
148	}
149
150	pub fn put_new_address(
151		&mut self,
152		creator_address: Address,
153		creator_nonce: u64,
154		new_address: Address,
155	) {
156		self.new_addresses
157			.insert((creator_address, creator_nonce), new_address);
158	}
159
160	fn get_new_address(&self, creator_address: Address, creator_nonce: u64) -> Option<Address> {
161		self.new_addresses
162			.get(&(creator_address, creator_nonce))
163			.cloned()
164	}
165
166	pub fn get_contract_path(&self, contract_address: &Address) -> Vec<u8> {
167		if let Some(account) = self.accounts.get(&contract_address) {
168			if let Some(contract_path) = &account.contract_path {
169				contract_path.clone()
170			} else {
171				panic!("Recipient account is not a smart contract");
172			}
173		} else {
174			panic!("Account not found");
175		}
176	}
177
178	pub fn subtract_tx_payment(&mut self, address: &Address, call_value: &BigUint) {
179		let sender_account = self
180			.accounts
181			.get_mut(address)
182			.unwrap_or_else(|| panic!("Sender account not found"));
183		assert!(
184			&sender_account.balance >= call_value,
185			"Not enough balance to send tx payment"
186		);
187		sender_account.balance -= call_value;
188	}
189
190	pub fn subtract_tx_gas(&mut self, address: &Address, gas_limit: u64, gas_price: u64) {
191		let sender_account = self
192			.accounts
193			.get_mut(address)
194			.unwrap_or_else(|| panic!("Sender account not found"));
195		let gas_cost = BigUint::from(gas_limit) * BigUint::from(gas_price);
196		assert!(
197			sender_account.balance >= gas_cost,
198			"Not enough balance to pay gas upfront"
199		);
200		sender_account.balance -= &gas_cost;
201	}
202
203	pub fn increase_balance(&mut self, address: &Address, amount: &BigUint) {
204		let account = self
205			.accounts
206			.get_mut(address)
207			.unwrap_or_else(|| panic!("Receiver account not found"));
208		account.balance += amount;
209	}
210
211	pub fn send_balance(&mut self, contract_address: &Address, send_balance_list: &[SendBalance]) {
212		for send_balance in send_balance_list {
213			self.subtract_tx_payment(contract_address, &send_balance.amount);
214			self.increase_balance(&send_balance.recipient, &send_balance.amount);
215		}
216	}
217
218	pub fn substract_dcdt_balance(
219		&mut self,
220		address: &Address,
221		dcdt_token_name: &[u8],
222		value: &BigUint,
223	) {
224		let sender_account = self
225			.accounts
226			.get_mut(address)
227			.unwrap_or_else(|| panic!("Sender account {} not found", address_hex(&address)));
228
229		let dcdt = sender_account
230			.dcdt
231			.as_mut()
232			.unwrap_or_else(|| panic!("Account {} has no dcdt tokens", address_hex(&address)));
233
234		let dcdt_balance = dcdt.get_mut(dcdt_token_name).unwrap_or_else(|| {
235			panic!(
236				"Account {} has no dcdt tokens with name {}",
237				address_hex(&address),
238				String::from_utf8(dcdt_token_name.to_vec()).unwrap()
239			)
240		});
241
242		assert!(
243			*dcdt_balance >= *value,
244			"Not enough dcdt balance, have {}, need at least {}",
245			dcdt_balance,
246			value
247		);
248
249		*dcdt_balance -= value;
250	}
251
252	pub fn increase_dcdt_balance(
253		&mut self,
254		address: &Address,
255		dcdt_token_name: &[u8],
256		value: &BigUint,
257	) {
258		let account = self
259			.accounts
260			.get_mut(address)
261			.unwrap_or_else(|| panic!("Receiver account not found"));
262
263		if account.dcdt.is_none() {
264			let mut new_dcdt = HashMap::<Vec<u8>, BigUint>::new();
265			new_dcdt.insert(dcdt_token_name.to_vec(), value.clone());
266
267			account.dcdt = Some(new_dcdt);
268		} else {
269			let dcdt = account.dcdt.as_mut().unwrap();
270
271			if dcdt.contains_key(dcdt_token_name) {
272				let dcdt_balance = dcdt.get_mut(dcdt_token_name).unwrap();
273				*dcdt_balance += value;
274			} else {
275				dcdt.insert(dcdt_token_name.to_vec(), value.clone());
276			}
277		}
278	}
279
280	pub fn increase_nonce(&mut self, address: &Address) {
281		let account = self
282			.accounts
283			.get_mut(address)
284			.unwrap_or_else(|| panic!("Account not found"));
285		account.nonce += 1;
286	}
287
288	pub fn create_account_after_deploy(
289		&mut self,
290		tx_input: &TxInput,
291		new_storage: HashMap<Vec<u8>, Vec<u8>>,
292		contract_path: Vec<u8>,
293	) -> Address {
294		let sender = self
295			.accounts
296			.get(&tx_input.from)
297			.unwrap_or_else(|| panic!("Unknown deployer"));
298		let sender_nonce_before_tx = sender.nonce - 1;
299		let new_address = self
300			.get_new_address(tx_input.from.clone(), sender_nonce_before_tx)
301			.unwrap_or_else(|| {
302				panic!("Missing new address. Only explicit new deploy addresses supported")
303			});
304		let mut dcdt = HashMap::<Vec<u8>, BigUint>::new();
305		let mut dcdt_opt: Option<HashMap<Vec<u8>, BigUint>> = None;
306
307		if !tx_input.dcdt_token_name.is_empty() {
308			dcdt.insert(
309				tx_input.dcdt_token_name.clone(),
310				tx_input.dcdt_value.clone(),
311			);
312			dcdt_opt = Some(dcdt);
313		}
314
315		let old_value = self.accounts.insert(
316			new_address.clone(),
317			AccountData {
318				address: new_address.clone(),
319				nonce: 0,
320				balance: tx_input.call_value.clone(),
321				storage: new_storage,
322				dcdt: dcdt_opt,
323				contract_path: Some(contract_path),
324				contract_owner: Some(tx_input.from.clone()),
325			},
326		);
327		if old_value.is_some() {
328			panic!("Account already exists at deploy address.");
329		}
330
331		new_address
332	}
333
334	pub fn increase_validator_reward(&mut self, address: &Address, amount: &BigUint) {
335		let account = self
336			.accounts
337			.get_mut(address)
338			.unwrap_or_else(|| panic!("Account not found"));
339		account.balance += amount;
340		let mut storage_v_rew =
341			if let Some(old_storage_value) = account.storage.get(NUMBAT_REWARD_KEY) {
342				BigUint::from_bytes_be(old_storage_value)
343			} else {
344				BigUint::zero()
345			};
346		storage_v_rew += amount;
347		account
348			.storage
349			.insert(NUMBAT_REWARD_KEY.to_vec(), storage_v_rew.to_bytes_be());
350	}
351}
352
353pub fn execute_tx(
354	tx_context: TxContext,
355	contract_identifier: &Vec<u8>,
356	contract_map: &ContractMap<TxContext>,
357) -> TxOutput {
358	let func_name = tx_context.tx_input_box.func_name.clone();
359	let contract_inst = contract_map.new_contract_instance(contract_identifier, tx_context);
360	let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
361		contract_inst.call(func_name.as_slice());
362		let context = contract_inst.into_api();
363		context.into_output()
364	}));
365	match result {
366		Ok(tx_result) => tx_result,
367		Err(panic_any) => panic_result(panic_any),
368	}
369}
370
371fn panic_result(panic_any: Box<dyn std::any::Any + std::marker::Send>) -> TxOutput {
372	if let Some(panic_obj) = panic_any.downcast_ref::<TxPanic>() {
373		return TxOutput::from_panic_obj(panic_obj);
374	}
375
376	if let Some(panic_string) = panic_any.downcast_ref::<String>() {
377		return TxOutput::from_panic_string(panic_string.as_str());
378	}
379
380	TxOutput::from_panic_string("unknown panic")
381}
382
383/// Some data to get copied for the tx.
384/// Would be nice maybe at some point to have a reference to the full blockchain mock in the tx context,
385/// but for now, copying some data is enough.
386#[derive(Clone, Debug)]
387pub struct BlockchainTxInfo {
388	pub previous_block_info: BlockInfo,
389	pub current_block_info: BlockInfo,
390	pub contract_balance: BigUint,
391	pub contract_owner: Option<Address>,
392}
393
394impl BlockchainMock {
395	pub fn create_tx_info(&self, contract_address: &Address) -> BlockchainTxInfo {
396		if let Some(contract) = self.accounts.get(contract_address) {
397			BlockchainTxInfo {
398				previous_block_info: self.previous_block_info.clone(),
399				current_block_info: self.current_block_info.clone(),
400				contract_balance: contract.balance.clone(),
401				contract_owner: contract.contract_owner.clone(),
402			}
403		} else {
404			BlockchainTxInfo {
405				previous_block_info: self.previous_block_info.clone(),
406				current_block_info: self.current_block_info.clone(),
407				contract_balance: 0u32.into(),
408				contract_owner: None,
409			}
410		}
411	}
412}