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#[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}