1use super::mock_error::BlockchainMockError;
2use crate::{
3 contract_map::*, display_util::*, dcdt_transfer_event_log, tx_context::*, SendBalance, TxInput,
4 TxLog, TxOutput, TxPanic,
5};
6use alloc::{boxed::Box, vec::Vec};
7use numbat_wasm::types::Address;
8use num_bigint::BigUint;
9use num_traits::Zero;
10use std::{collections::HashMap, fmt, fmt::Write};
11
12const NUMBAT_REWARD_KEY: &[u8] = b"NUMBATreward";
13const SC_ADDRESS_NUM_LEADING_ZEROS: u8 = 8;
14
15pub type AccountStorage = HashMap<Vec<u8>, Vec<u8>>;
16pub type AccountDcdt = HashMap<Vec<u8>, BigUint>;
17
18pub struct AccountData {
19 pub address: Address,
20 pub nonce: u64,
21 pub balance: BigUint,
22 pub storage: AccountStorage,
23 pub dcdt: AccountDcdt,
24 pub username: Vec<u8>,
25 pub contract_path: Option<Vec<u8>>,
26 pub contract_owner: Option<Address>,
27}
28
29impl fmt::Display for AccountData {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 let mut dcdt_buf = String::new();
32 let mut dcdt_keys: Vec<Vec<u8>> =
33 self.dcdt.clone().iter().map(|(k, _)| k.clone()).collect();
34 dcdt_keys.sort();
35
36 for key in &dcdt_keys {
37 let value = self.dcdt.get(key).unwrap();
38 write!(
39 &mut dcdt_buf,
40 "\n\t\t\t\t{} -> 0x{}",
41 key_hex(key.as_slice()),
42 hex::encode(value.to_bytes_be())
43 )
44 .unwrap();
45 }
46
47 let mut storage_buf = String::new();
48 let mut storage_keys: Vec<Vec<u8>> = self.storage.iter().map(|(k, _)| k.clone()).collect();
49 storage_keys.sort();
50
51 for key in &storage_keys {
52 let value = self.storage.get(key).unwrap();
53 write!(
54 &mut storage_buf,
55 "\n\t\t\t{} -> 0x{}",
56 key_hex(key.as_slice()),
57 hex::encode(value.as_slice())
58 )
59 .unwrap();
60 }
61
62 write!(
63 f,
64 "AccountData {{
65 nonce: {},
66 balance: {},
67 dcdt: [{} ],
68 username: {},
69 storage: [{} ]
70 }}",
71 self.nonce,
72 self.balance,
73 dcdt_buf,
74 String::from_utf8(self.username.clone()).unwrap(),
75 storage_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 validate_and_add_account(&mut self, acct: AccountData) {
137 self.validate_account(&acct);
138 self.add_account(acct);
139 }
140
141 pub fn print_accounts(&self) {
142 let mut accounts_buf = String::new();
143 for (address, account) in &self.accounts {
144 write!(
145 &mut accounts_buf,
146 "\n\t{} -> {}",
147 address_hex(address),
148 account
149 )
150 .unwrap();
151 }
152 println!("Accounts: {}", &accounts_buf);
153 }
154
155 pub fn put_new_address(
156 &mut self,
157 creator_address: Address,
158 creator_nonce: u64,
159 new_address: Address,
160 ) {
161 self.new_addresses
162 .insert((creator_address, creator_nonce), new_address);
163 }
164
165 fn get_new_address(&self, creator_address: Address, creator_nonce: u64) -> Option<Address> {
166 self.new_addresses
167 .get(&(creator_address, creator_nonce))
168 .cloned()
169 }
170
171 pub fn validate_account(&self, account: &AccountData) {
172 let is_sc = self.is_smart_contract_address(&account.address);
173 let has_code = self.check_account_has_code(account);
174
175 assert!(
176 !is_sc || has_code,
177 "Account has a smart contract address but no code"
178 );
179
180 assert!(
181 is_sc || !has_code,
182 "Account has no smart contract address but has code"
183 );
184 }
185
186 pub fn is_smart_contract_address(&self, address: &Address) -> bool {
187 address
188 .as_bytes()
189 .iter()
190 .take(SC_ADDRESS_NUM_LEADING_ZEROS.into())
191 .all(|item| item == &0u8)
192 }
193
194 pub fn check_account_has_code(&self, account: &AccountData) -> bool {
195 !account
196 .contract_path
197 .as_ref()
198 .unwrap_or(&Vec::<u8>::new())
199 .is_empty()
200 }
201
202 pub fn subtract_tx_payment(
203 &mut self,
204 address: &Address,
205 call_value: &BigUint,
206 ) -> Result<(), BlockchainMockError> {
207 let sender_account = self
208 .accounts
209 .get_mut(address)
210 .unwrap_or_else(|| panic!("Sender account not found"));
211 if &sender_account.balance < call_value {
212 return Err("failed transfer (insufficient funds)".into());
213 }
214 sender_account.balance -= call_value;
215 Ok(())
216 }
217
218 pub fn subtract_tx_gas(&mut self, address: &Address, gas_limit: u64, gas_price: u64) {
219 let sender_account = self
220 .accounts
221 .get_mut(address)
222 .unwrap_or_else(|| panic!("Sender account not found"));
223 let gas_cost = BigUint::from(gas_limit) * BigUint::from(gas_price);
224 assert!(
225 sender_account.balance >= gas_cost,
226 "Not enough balance to pay gas upfront"
227 );
228 sender_account.balance -= &gas_cost;
229 }
230
231 pub fn increase_balance(&mut self, address: &Address, amount: &BigUint) {
232 let account = self
233 .accounts
234 .get_mut(address)
235 .unwrap_or_else(|| panic!("Receiver account not found"));
236 account.balance += amount;
237 }
238
239 pub fn send_balance(
240 &mut self,
241 contract_address: &Address,
242 send_balance_list: &[SendBalance],
243 result_logs: &mut Vec<TxLog>,
244 ) -> Result<(), BlockchainMockError> {
245 for send_balance in send_balance_list {
246 if send_balance.token_identifier.is_empty() {
247 self.subtract_tx_payment(contract_address, &send_balance.amount)?;
248 self.increase_balance(&send_balance.recipient, &send_balance.amount);
249 } else {
250 let dcdt_token_identifier = send_balance.token_identifier.as_slice();
251 self.substract_dcdt_balance(
252 contract_address,
253 dcdt_token_identifier,
254 &send_balance.amount,
255 );
256 self.increase_dcdt_balance(
257 &send_balance.recipient,
258 dcdt_token_identifier,
259 &send_balance.amount,
260 );
261
262 let log = dcdt_transfer_event_log(
263 contract_address.clone(),
264 send_balance.recipient.clone(),
265 dcdt_token_identifier.to_vec(),
266 &send_balance.amount,
267 );
268 result_logs.insert(0, log); }
270 }
271 Ok(())
272 }
273
274 pub fn substract_dcdt_balance(
275 &mut self,
276 address: &Address,
277 dcdt_token_identifier: &[u8],
278 value: &BigUint,
279 ) {
280 let sender_account = self
281 .accounts
282 .get_mut(address)
283 .unwrap_or_else(|| panic!("Sender account {} not found", address_hex(address)));
284
285 let dcdt_balance = sender_account
286 .dcdt
287 .get_mut(dcdt_token_identifier)
288 .unwrap_or_else(|| {
289 panic!(
290 "Account {} has no dcdt tokens with name {}",
291 address_hex(address),
292 String::from_utf8(dcdt_token_identifier.to_vec()).unwrap()
293 )
294 });
295
296 assert!(
297 *dcdt_balance >= *value,
298 "Not enough dcdt balance, have {}, need at least {}",
299 dcdt_balance,
300 value
301 );
302
303 *dcdt_balance -= value;
304 }
305
306 pub fn increase_dcdt_balance(
307 &mut self,
308 address: &Address,
309 dcdt_token_identifier: &[u8],
310 value: &BigUint,
311 ) {
312 let account = self
313 .accounts
314 .get_mut(address)
315 .unwrap_or_else(|| panic!("Receiver account not found"));
316
317 if account.dcdt.contains_key(dcdt_token_identifier) {
318 let dcdt_balance = account.dcdt.get_mut(dcdt_token_identifier).unwrap();
319 *dcdt_balance += value;
320 } else {
321 account
322 .dcdt
323 .insert(dcdt_token_identifier.to_vec(), value.clone());
324 }
325 }
326
327 pub fn increase_nonce(&mut self, address: &Address) {
328 let account = self.accounts.get_mut(address).unwrap_or_else(|| {
329 panic!(
330 "Account not found: {}",
331 &std::str::from_utf8(address.as_ref()).unwrap()
332 )
333 });
334 account.nonce += 1;
335 }
336
337 pub fn create_account_after_deploy(
338 &mut self,
339 tx_input: &TxInput,
340 new_storage: HashMap<Vec<u8>, Vec<u8>>,
341 contract_path: Vec<u8>,
342 ) -> Address {
343 let sender = self
344 .accounts
345 .get(&tx_input.from)
346 .unwrap_or_else(|| panic!("Unknown deployer"));
347 let sender_nonce_before_tx = sender.nonce - 1;
348 let new_address = self
349 .get_new_address(tx_input.from.clone(), sender_nonce_before_tx)
350 .unwrap_or_else(|| {
351 panic!("Missing new address. Only explicit new deploy addresses supported")
352 });
353 let mut dcdt = HashMap::<Vec<u8>, BigUint>::new();
354 if !tx_input.dcdt_token_identifier.is_empty() {
355 dcdt.insert(
356 tx_input.dcdt_token_identifier.clone(),
357 tx_input.dcdt_value.clone(),
358 );
359 }
360
361 let old_value = self.accounts.insert(
362 new_address.clone(),
363 AccountData {
364 address: new_address.clone(),
365 nonce: 0,
366 balance: tx_input.call_value.clone(),
367 storage: new_storage,
368 dcdt,
369 username: Vec::new(),
370 contract_path: Some(contract_path),
371 contract_owner: Some(tx_input.from.clone()),
372 },
373 );
374 assert!(
375 old_value.is_none(),
376 "Account already exists at deploy address."
377 );
378
379 new_address
380 }
381
382 pub fn increase_validator_reward(&mut self, address: &Address, amount: &BigUint) {
383 let account = self.accounts.get_mut(address).unwrap_or_else(|| {
384 panic!(
385 "Account not found: {}",
386 &std::str::from_utf8(address.as_ref()).unwrap()
387 )
388 });
389 account.balance += amount;
390 let mut storage_v_rew =
391 if let Some(old_storage_value) = account.storage.get(NUMBAT_REWARD_KEY) {
392 BigUint::from_bytes_be(old_storage_value)
393 } else {
394 BigUint::zero()
395 };
396 storage_v_rew += amount;
397 account
398 .storage
399 .insert(NUMBAT_REWARD_KEY.to_vec(), storage_v_rew.to_bytes_be());
400 }
401
402 pub fn try_set_username(&mut self, address: &Address, username: &[u8]) -> bool {
403 let account = self.accounts.get_mut(address).unwrap_or_else(|| {
404 panic!(
405 "Account not found: {}",
406 &std::str::from_utf8(address.as_ref()).unwrap()
407 )
408 });
409 if account.username.is_empty() {
410 account.username = username.to_vec();
411 true
412 } else {
413 false
414 }
415 }
416}
417
418pub fn execute_tx(
419 tx_context: TxContext,
420 contract_identifier: &[u8],
421 contract_map: &ContractMap<TxContext>,
422) -> TxOutput {
423 let func_name = tx_context.tx_input_box.func_name.clone();
424 let contract_inst = contract_map.new_contract_instance(contract_identifier, tx_context);
425 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
426 let call_successful = contract_inst.call(func_name.as_slice());
427 if !call_successful {
428 std::panic::panic_any(TxPanic {
429 status: 1,
430 message: b"invalid function (not found)".to_vec(),
431 });
432 }
433 let context = contract_inst.into_api();
434 context.into_output()
435 }));
436 match result {
437 Ok(tx_output) => tx_output,
438 Err(panic_any) => panic_result(panic_any),
439 }
440}
441
442fn panic_result(panic_any: Box<dyn std::any::Any + std::marker::Send>) -> TxOutput {
443 if panic_any.downcast_ref::<TxOutput>().is_some() {
444 return *panic_any.downcast::<TxOutput>().unwrap();
447 }
448
449 if let Some(panic_obj) = panic_any.downcast_ref::<TxPanic>() {
450 return TxOutput::from_panic_obj(panic_obj);
451 }
452
453 if let Some(panic_string) = panic_any.downcast_ref::<String>() {
454 return TxOutput::from_panic_string(panic_string.as_str());
455 }
456
457 TxOutput::from_panic_string("unknown panic")
458}
459
460#[derive(Clone, Debug)]
464pub struct BlockchainTxInfo {
465 pub previous_block_info: BlockInfo,
466 pub current_block_info: BlockInfo,
467 pub contract_balance: BigUint,
468 pub contract_dcdt: HashMap<Vec<u8>, BigUint>,
469 pub contract_owner: Option<Address>,
470}
471
472impl BlockchainMock {
473 pub fn create_tx_info(&self, contract_address: &Address) -> BlockchainTxInfo {
474 if let Some(contract) = self.accounts.get(contract_address) {
475 BlockchainTxInfo {
476 previous_block_info: self.previous_block_info.clone(),
477 current_block_info: self.current_block_info.clone(),
478 contract_balance: contract.balance.clone(),
479 contract_dcdt: contract.dcdt.clone(),
480 contract_owner: contract.contract_owner.clone(),
481 }
482 } else {
483 BlockchainTxInfo {
484 previous_block_info: self.previous_block_info.clone(),
485 current_block_info: self.current_block_info.clone(),
486 contract_balance: 0u32.into(),
487 contract_dcdt: HashMap::new(),
488 contract_owner: None,
489 }
490 }
491 }
492}