1use std::{collections::HashMap, path::PathBuf, str::FromStr};
2
3use crate::{
4 api::DebugApi,
5 executor::debug::{
6 ContractContainer, ContractDebugInstance, ContractDebugStack, ContractDebugWhiteboxLambda,
7 },
8 multiversx_sc::{
9 codec::{TopDecode, TopEncode},
10 contract_base::{CallableContract, ContractBase},
11 types::{heap::Address, EsdtLocalRole},
12 },
13 scenario_model::{Account, BytesValue, ScCallStep, SetStateStep},
14 testing_framework::raw_converter::bytes_to_hex,
15 ScenarioWorld,
16};
17use multiversx_chain_scenario_format::interpret_trait::InterpretableFrom;
18use multiversx_chain_vm::host::context::{TxFunctionName, TxResult};
19use multiversx_sc::types::{BigUint, TimestampMillis, TimestampSeconds, H256};
20use num_traits::Zero;
21
22use super::{
23 tx_mandos::{ScCallMandos, TxExpectMandos},
24 AddressFactory, MandosGenerator, ScQueryMandos,
25};
26
27pub use multiversx_chain_vm::host::context::TxTokenTransfer;
28
29#[derive(Clone)]
30pub struct ContractObjWrapper<
31 CB: ContractBase<Api = DebugApi> + CallableContract + 'static,
32 ContractObjBuilder: 'static + Copy + Fn() -> CB,
33> {
34 pub(crate) address: Address,
35 pub(crate) obj_builder: ContractObjBuilder,
36}
37
38impl<CB, ContractObjBuilder> ContractObjWrapper<CB, ContractObjBuilder>
39where
40 CB: ContractBase<Api = DebugApi> + CallableContract + 'static,
41 ContractObjBuilder: 'static + Copy + Fn() -> CB,
42{
43 pub(crate) fn new(address: Address, obj_builder: ContractObjBuilder) -> Self {
44 ContractObjWrapper {
45 address,
46 obj_builder,
47 }
48 }
49
50 pub fn address_ref(&self) -> &Address {
51 &self.address
52 }
53}
54
55pub struct BlockchainStateWrapper {
56 world: ScenarioWorld,
57 address_factory: AddressFactory,
58 address_to_code_path: HashMap<Address, Vec<u8>>,
59 current_tx_id: u64,
60 workspace_path: PathBuf,
61}
62
63impl BlockchainStateWrapper {
64 #[allow(clippy::new_without_default)]
65 pub fn new() -> Self {
66 let mut current_dir = std::env::current_dir().unwrap();
67 current_dir.push(PathBuf::from_str("scenarios/").unwrap());
68
69 let mut world = ScenarioWorld::debugger();
70 world.start_trace();
71
72 BlockchainStateWrapper {
73 world,
74 address_factory: AddressFactory::new(),
75 address_to_code_path: HashMap::new(),
76 current_tx_id: 0,
77 workspace_path: current_dir,
78 }
79 }
80
81 pub fn write_mandos_output(mut self, file_name: &str) {
82 let mut full_path = self.workspace_path;
83 full_path.push(file_name);
84
85 if let Some(trace) = &mut self.world.get_mut_debugger_backend().trace {
86 trace.write_scenario_trace(&full_path);
87 }
88 }
89
90 pub fn check_egld_balance(&self, address: &Address, expected_balance: &num_bigint::BigUint) {
91 let actual_balance = match &self.world.get_state().accounts.get(address) {
92 Some(acc) => acc.egld_balance.clone(),
93 None => num_bigint::BigUint::zero(),
94 };
95
96 assert!(
97 expected_balance == &actual_balance,
98 "EGLD balance mismatch for address {}\n Expected: {}\n Have: {}\n",
99 address_to_hex(address),
100 expected_balance,
101 actual_balance
102 );
103 }
104
105 pub fn check_esdt_balance(
106 &self,
107 address: &Address,
108 token_id: &[u8],
109 expected_balance: &num_bigint::BigUint,
110 ) {
111 let actual_balance = match &self.world.get_state().accounts.get(address) {
112 Some(acc) => acc.esdt.get_esdt_balance(token_id, 0),
113 None => num_bigint::BigUint::zero(),
114 };
115
116 assert!(
117 expected_balance == &actual_balance,
118 "ESDT balance mismatch for address {}\n Token: {}\n Expected: {}\n Have: {}\n",
119 address_to_hex(address),
120 String::from_utf8(token_id.to_vec()).unwrap(),
121 expected_balance,
122 actual_balance
123 );
124 }
125
126 pub fn check_nft_balance<T>(
127 &self,
128 address: &Address,
129 token_id: &[u8],
130 nonce: u64,
131 expected_balance: &num_bigint::BigUint,
132 opt_expected_attributes: Option<&T>,
133 ) where
134 T: TopEncode + TopDecode + PartialEq + core::fmt::Debug,
135 {
136 let (actual_balance, actual_attributes_serialized) =
137 match &self.world.get_state().accounts.get(address) {
138 Some(acc) => {
139 let esdt_data = acc.esdt.get_by_identifier_or_default(token_id);
140 let opt_instance = esdt_data.instances.get_by_nonce(nonce);
141
142 match opt_instance {
143 Some(instance) => (
144 instance.balance.clone(),
145 instance.metadata.attributes.clone(),
146 ),
147 None => (num_bigint::BigUint::zero(), Vec::new()),
148 }
149 }
150 None => (num_bigint::BigUint::zero(), Vec::new()),
151 };
152
153 assert!(
154 expected_balance == &actual_balance,
155 "ESDT NFT balance mismatch for address {}\n Token: {}, nonce: {}\n Expected: {}\n Have: {}\n",
156 address_to_hex(address),
157 String::from_utf8(token_id.to_vec()).unwrap(),
158 nonce,
159 expected_balance,
160 actual_balance
161 );
162
163 if let Some(expected_attributes) = opt_expected_attributes {
164 let actual_attributes = T::top_decode(actual_attributes_serialized).unwrap();
165 assert!(
166 expected_attributes == &actual_attributes,
167 "ESDT NFT attributes mismatch for address {}\n Token: {}, nonce: {}\n Expected: {:?}\n Have: {:?}\n",
168 address_to_hex(address),
169 String::from_utf8(token_id.to_vec()).unwrap(),
170 nonce,
171 expected_attributes,
172 actual_attributes,
173 );
174 }
175 }
176}
177
178impl BlockchainStateWrapper {
179 pub fn create_user_account(&mut self, egld_balance: &num_bigint::BigUint) -> Address {
180 let address = self.address_factory.new_address();
181 self.world
182 .create_account_raw(&address, BigUint::from(egld_balance));
183
184 address
185 }
186
187 pub fn create_user_account_fixed_address(
188 &mut self,
189 address: &Address,
190 egld_balance: &num_bigint::BigUint,
191 ) {
192 self.world
193 .create_account_raw(address, BigUint::from(egld_balance));
194 }
195
196 pub fn create_sc_account<CB, ContractObjBuilder>(
197 &mut self,
198 egld_balance: &num_bigint::BigUint,
199 owner: Option<&Address>,
200 obj_builder: ContractObjBuilder,
201 contract_wasm_path: &str,
202 ) -> ContractObjWrapper<CB, ContractObjBuilder>
203 where
204 CB: ContractBase<Api = DebugApi> + CallableContract + 'static,
205 ContractObjBuilder: 'static + Copy + Fn() -> CB,
206 {
207 let address = self.address_factory.new_sc_address();
208 self.create_sc_account_fixed_address(
209 &address,
210 egld_balance,
211 owner,
212 obj_builder,
213 contract_wasm_path,
214 )
215 }
216
217 pub fn create_sc_account_fixed_address<CB, ContractObjBuilder>(
218 &mut self,
219 address: &Address,
220 egld_balance: &num_bigint::BigUint,
221 owner: Option<&Address>,
222 obj_builder: ContractObjBuilder,
223 contract_wasm_path: &str,
224 ) -> ContractObjWrapper<CB, ContractObjBuilder>
225 where
226 CB: ContractBase<Api = DebugApi> + CallableContract + 'static,
227 ContractObjBuilder: 'static + Copy + Fn() -> CB,
228 {
229 if !address.is_smart_contract_address() {
230 panic!("Invalid SC Address: {:?}", address_to_hex(address))
231 }
232
233 let mut wasm_full_path = std::env::current_dir().unwrap();
234 wasm_full_path.push(PathBuf::from_str(contract_wasm_path).unwrap());
235
236 let path_diff =
237 pathdiff::diff_paths(wasm_full_path.clone(), self.workspace_path.clone()).unwrap();
238 let path_str = path_diff.to_str().unwrap();
239
240 let contract_code_expr_str = format!("file:{path_str}");
241 let contract_code_expr = BytesValue::interpret_from(
242 contract_code_expr_str.clone(),
243 &self.world.interpreter_context(),
244 );
245
246 let mut account = Account::new()
247 .balance(egld_balance)
248 .code(contract_code_expr.clone());
249 if let Some(owner) = owner {
250 account = account.owner(owner);
251 }
252
253 self.world
254 .set_state_step(SetStateStep::new().put_account(address, account));
255
256 self.address_to_code_path
257 .insert(address.clone(), contract_code_expr_str.into_bytes());
258
259 let contains_contract = self
260 .world
261 .get_mut_debugger_backend()
262 .vm_runner
263 .contract_map_ref
264 .lock()
265 .contains_contract(contract_code_expr.value.as_slice());
266 if !contains_contract {
267 let contract_obj = create_contract_obj_box(obj_builder);
268
269 self.world
270 .get_mut_debugger_backend()
271 .vm_runner
272 .contract_map_ref
273 .lock()
274 .register_contract(
275 contract_code_expr.value,
276 ContractContainer::new(contract_obj, None, false),
277 );
278 }
279
280 ContractObjWrapper::new(address.clone(), obj_builder)
281 }
282
283 pub fn create_account_raw(
284 &mut self,
285 address: &Address,
286 egld_balance: &num_bigint::BigUint,
287 _owner: Option<&Address>,
288 _sc_identifier: Option<Vec<u8>>,
289 _sc_mandos_path_expr: Option<Vec<u8>>,
290 ) {
291 self.world
292 .create_account_raw(address, BigUint::from(egld_balance));
293 }
294
295 pub fn prepare_deploy_from_sc<CB, ContractObjBuilder>(
298 &mut self,
299 deployer: &Address,
300 obj_builder: ContractObjBuilder,
301 ) -> ContractObjWrapper<CB, ContractObjBuilder>
302 where
303 CB: ContractBase<Api = DebugApi> + CallableContract + 'static,
304 ContractObjBuilder: 'static + Copy + Fn() -> CB,
305 {
306 let deployer_acc = self
307 .world
308 .get_state()
309 .accounts
310 .get(deployer)
311 .unwrap()
312 .clone();
313
314 let new_sc_address = self.address_factory.new_sc_address();
315 self.world.get_mut_state().put_new_address(
316 deployer.clone(),
317 deployer_acc.nonce,
318 new_sc_address.clone(),
319 );
320
321 ContractObjWrapper::new(new_sc_address, obj_builder)
322 }
323
324 pub fn upgrade_wrapper<OldCB, OldContractObjBuilder, NewCB, NewContractObjBuilder>(
325 &self,
326 old_wrapper: ContractObjWrapper<OldCB, OldContractObjBuilder>,
327 new_builder: NewContractObjBuilder,
328 ) -> ContractObjWrapper<NewCB, NewContractObjBuilder>
329 where
330 OldCB: ContractBase<Api = DebugApi> + CallableContract + 'static,
331 OldContractObjBuilder: 'static + Copy + Fn() -> OldCB,
332 NewCB: ContractBase<Api = DebugApi> + CallableContract + 'static,
333 NewContractObjBuilder: 'static + Copy + Fn() -> NewCB,
334 {
335 ContractObjWrapper::new(old_wrapper.address, new_builder)
336 }
337
338 pub fn set_egld_balance(&mut self, address: &Address, balance: &num_bigint::BigUint) {
339 self.world.set_egld_balance(address, BigUint::from(balance));
340 }
341
342 pub fn set_esdt_balance(
343 &mut self,
344 address: &Address,
345 token_id: &[u8],
346 balance: &num_bigint::BigUint,
347 ) {
348 self.world
349 .set_esdt_balance(address, token_id, BigUint::from(balance));
350 }
351
352 pub fn set_nft_balance<T: TopEncode>(
353 &mut self,
354 address: &Address,
355 token_id: &[u8],
356 nonce: u64,
357 balance: &num_bigint::BigUint,
358 attributes: &T,
359 ) {
360 self.world.set_nft_balance_all_properties(
361 address,
362 token_id,
363 nonce,
364 BigUint::from(balance),
365 attributes,
366 0,
367 None::<Address>,
368 None,
369 None,
370 &[],
371 );
372 }
373
374 pub fn set_developer_rewards(
375 &mut self,
376 address: &Address,
377 developer_rewards: num_bigint::BigUint,
378 ) {
379 self.world
380 .set_developer_rewards(address, &developer_rewards);
381 }
382
383 #[allow(clippy::too_many_arguments)]
384 pub fn set_nft_balance_all_properties<T: TopEncode>(
385 &mut self,
386 address: &Address,
387 token_id: &[u8],
388 nonce: u64,
389 balance: &num_bigint::BigUint,
390 attributes: &T,
391 royalties: u64,
392 creator: Option<&Address>,
393 name: Option<&[u8]>,
394 hash: Option<&[u8]>,
395 uris: &[Vec<u8>],
396 ) {
397 self.world.set_nft_balance_all_properties(
398 address,
399 token_id,
400 nonce,
401 BigUint::from(balance),
402 attributes,
403 royalties,
404 creator,
405 name,
406 hash,
407 uris,
408 );
409 }
410
411 pub fn set_esdt_local_roles(
412 &mut self,
413 address: &Address,
414 token_id: &[u8],
415 roles: &[EsdtLocalRole],
416 ) {
417 self.world.set_esdt_local_roles(address, token_id, roles);
418 }
419
420 pub fn set_block_epoch(&mut self, block_epoch: u64) {
421 self.world
422 .set_state_step(SetStateStep::new().block_epoch(block_epoch));
423 }
424
425 pub fn set_block_nonce(&mut self, block_nonce: u64) {
426 self.world
427 .set_state_step(SetStateStep::new().block_nonce(block_nonce));
428 }
429
430 pub fn set_block_random_seed(&mut self, block_random_seed: &[u8; 48]) {
431 self.world
432 .set_state_step(SetStateStep::new().block_random_seed(block_random_seed.as_slice()));
433 }
434
435 pub fn set_block_round(&mut self, block_round: u64) {
436 self.world
437 .set_state_step(SetStateStep::new().block_round(block_round));
438 }
439
440 #[deprecated(since = "0.63.2", note = "Renamed to set_block_timestamp_seconds")]
441 pub fn set_block_timestamp(&mut self, block_timestamp: u64) {
442 self.set_block_timestamp_seconds(TimestampSeconds::new(block_timestamp));
443 }
444
445 pub fn set_block_timestamp_seconds(&mut self, block_timestamp: TimestampSeconds) {
446 self.world
447 .set_state_step(SetStateStep::new().block_timestamp_seconds(block_timestamp));
448 }
449
450 #[deprecated(since = "0.63.2", note = "Renamed to set_block_timestamp_millis")]
451 pub fn set_block_timestamp_ms(&mut self, block_timestamp_ms: u64) {
452 self.set_block_timestamp_millis(TimestampMillis::new(block_timestamp_ms));
453 }
454
455 pub fn set_block_timestamp_millis(&mut self, block_timestamp: TimestampMillis) {
456 self.world
457 .set_state_step(SetStateStep::new().block_timestamp_millis(block_timestamp));
458 }
459
460 pub fn set_prev_block_epoch(&mut self, block_epoch: u64) {
461 self.world
462 .set_state_step(SetStateStep::new().prev_block_epoch(block_epoch));
463 }
464
465 pub fn set_prev_block_nonce(&mut self, block_nonce: u64) {
466 self.world
467 .set_state_step(SetStateStep::new().prev_block_nonce(block_nonce));
468 }
469
470 pub fn set_prev_block_random_seed(&mut self, block_random_seed: &[u8; 48]) {
471 self.world.set_state_step(
472 SetStateStep::new().prev_block_random_seed(block_random_seed.as_slice()),
473 );
474 }
475
476 pub fn set_prev_block_round(&mut self, block_round: u64) {
477 self.world
478 .set_state_step(SetStateStep::new().prev_block_round(block_round));
479 }
480
481 pub fn set_prev_block_timestamp(&mut self, block_timestamp: u64) {
482 self.world
483 .set_state_step(SetStateStep::new().prev_block_timestamp(block_timestamp));
484 }
485
486 pub fn add_mandos_sc_call(
487 &mut self,
488 sc_call: ScCallMandos,
489 opt_expect: Option<TxExpectMandos>,
490 ) {
491 if let Some(trace) = &mut self.world.get_mut_debugger_backend().trace {
492 MandosGenerator::new(&mut trace.scenario_trace, &mut self.current_tx_id)
493 .create_tx(&sc_call, opt_expect.as_ref());
494 }
495 }
496
497 pub fn add_mandos_sc_query(
498 &mut self,
499 sc_query: ScQueryMandos,
500 opt_expect: Option<TxExpectMandos>,
501 ) {
502 if let Some(trace) = &mut self.world.get_mut_debugger_backend().trace {
503 MandosGenerator::new(&mut trace.scenario_trace, &mut self.current_tx_id)
504 .create_query(&sc_query, opt_expect.as_ref());
505 }
506 }
507
508 pub fn add_mandos_set_account(&mut self, address: &Address) {
509 if let Some(acc) = self.world.get_state().accounts.get(address).cloned() {
510 let opt_contract_path = self.address_to_code_path.get(address);
511 if let Some(trace) = &mut self.world.get_mut_debugger_backend().trace {
512 MandosGenerator::new(&mut trace.scenario_trace, &mut self.current_tx_id)
513 .set_account(&acc, opt_contract_path.cloned());
514 }
515 }
516 }
517
518 pub fn add_mandos_check_account(&mut self, address: &Address) {
519 if let Some(acc) = self.world.get_state().accounts.get(address).cloned() {
520 if let Some(trace) = &mut self.world.get_mut_debugger_backend().trace {
521 MandosGenerator::new(&mut trace.scenario_trace, &mut self.current_tx_id)
522 .check_account(&acc);
523 }
524 }
525 }
526}
527
528impl BlockchainStateWrapper {
529 pub fn execute_tx<CB, ContractObjBuilder, TxFn>(
530 &mut self,
531 caller: &Address,
532 sc_wrapper: &ContractObjWrapper<CB, ContractObjBuilder>,
533 egld_payment: &num_bigint::BigUint,
534 tx_fn: TxFn,
535 ) -> TxResult
536 where
537 CB: ContractBase<Api = DebugApi> + CallableContract + 'static,
538 ContractObjBuilder: 'static + Copy + Fn() -> CB,
539 TxFn: FnOnce(CB),
540 {
541 self.execute_tx_any(caller, sc_wrapper, egld_payment, Vec::new(), tx_fn)
542 }
543
544 pub fn execute_esdt_transfer<CB, ContractObjBuilder, TxFn>(
545 &mut self,
546 caller: &Address,
547 sc_wrapper: &ContractObjWrapper<CB, ContractObjBuilder>,
548 token_id: &[u8],
549 esdt_nonce: u64,
550 esdt_amount: &num_bigint::BigUint,
551 tx_fn: TxFn,
552 ) -> TxResult
553 where
554 CB: ContractBase<Api = DebugApi> + CallableContract + 'static,
555 ContractObjBuilder: 'static + Copy + Fn() -> CB,
556 TxFn: FnOnce(CB),
557 {
558 let esdt_transfer = vec![TxTokenTransfer {
559 token_identifier: token_id.to_vec(),
560 nonce: esdt_nonce,
561 value: esdt_amount.clone(),
562 }];
563 self.execute_tx_any(
564 caller,
565 sc_wrapper,
566 &num_bigint::BigUint::zero(),
567 esdt_transfer,
568 tx_fn,
569 )
570 }
571
572 pub fn execute_esdt_multi_transfer<CB, ContractObjBuilder, TxFn>(
573 &mut self,
574 caller: &Address,
575 sc_wrapper: &ContractObjWrapper<CB, ContractObjBuilder>,
576 esdt_transfers: &[TxTokenTransfer],
577 tx_fn: TxFn,
578 ) -> TxResult
579 where
580 CB: ContractBase<Api = DebugApi> + CallableContract + 'static,
581 ContractObjBuilder: 'static + Copy + Fn() -> CB,
582 TxFn: FnOnce(CB),
583 {
584 self.execute_tx_any(
585 caller,
586 sc_wrapper,
587 &num_bigint::BigUint::zero(),
588 esdt_transfers.to_vec(),
589 tx_fn,
590 )
591 }
592
593 pub fn execute_query<CB, ContractObjBuilder, TxFn>(
594 &mut self,
595 sc_wrapper: &ContractObjWrapper<CB, ContractObjBuilder>,
596 query_fn: TxFn,
597 ) -> TxResult
598 where
599 CB: ContractBase<Api = DebugApi> + CallableContract + 'static,
600 ContractObjBuilder: 'static + Copy + Fn() -> CB,
601 TxFn: FnOnce(CB),
602 {
603 self.execute_tx(
604 sc_wrapper.address_ref(),
605 sc_wrapper,
606 &num_bigint::BigUint::zero(),
607 query_fn,
608 )
609 }
610
611 fn execute_tx_any<CB, ContractObjBuilder, TxFn>(
613 &mut self,
614 caller: &Address,
615 sc_wrapper: &ContractObjWrapper<CB, ContractObjBuilder>,
616 egld_payment: &num_bigint::BigUint,
617 esdt_payments: Vec<TxTokenTransfer>,
618 tx_fn: TxFn,
619 ) -> TxResult
620 where
621 CB: ContractBase<Api = DebugApi> + CallableContract + 'static,
622 ContractObjBuilder: 'static + Copy + Fn() -> CB,
623 TxFn: FnOnce(CB),
624 {
625 let mut sc_call_step = ScCallStep::new()
626 .from(caller)
627 .to(sc_wrapper.address_ref())
628 .function(TxFunctionName::WHITEBOX_CALL.as_str())
629 .egld_value(egld_payment)
630 .gas_limit("100,000,000")
631 .no_expect();
632
633 sc_call_step.explicit_tx_hash = Some(H256::zero());
634
635 for esdt_payment in &esdt_payments {
636 sc_call_step = sc_call_step.esdt_transfer(
637 esdt_payment.token_identifier.as_slice(),
638 esdt_payment.nonce,
639 &esdt_payment.value,
640 );
641 }
642
643 let sc = (sc_wrapper.obj_builder)();
644 let tx_result = self
645 .world
646 .get_mut_debugger_backend()
647 .vm_runner
648 .perform_sc_call_lambda_and_check(
649 &sc_call_step,
650 ContractDebugWhiteboxLambda::new(TxFunctionName::WHITEBOX_LEGACY, || {
651 tx_fn(sc);
652 })
653 .panic_message(false),
654 );
655
656 tx_result
657 }
658
659 pub fn execute_in_managed_environment<T, F>(&self, f: F) -> T
663 where
664 F: FnOnce() -> T,
665 {
666 ContractDebugStack::static_push(ContractDebugInstance::dummy());
667 let result = f();
668 let _ = ContractDebugStack::static_pop();
669
670 result
671 }
672}
673
674impl BlockchainStateWrapper {
675 pub fn get_egld_balance(&self, address: &Address) -> num_bigint::BigUint {
676 match self.world.get_state().accounts.get(address) {
677 Some(acc) => acc.egld_balance.clone(),
678 None => panic!(
679 "get_egld_balance: Account {:?} does not exist",
680 address_to_hex(address)
681 ),
682 }
683 }
684
685 pub fn get_esdt_balance(
686 &self,
687 address: &Address,
688 token_id: &[u8],
689 token_nonce: u64,
690 ) -> num_bigint::BigUint {
691 match self.world.get_state().accounts.get(address) {
692 Some(acc) => acc.esdt.get_esdt_balance(token_id, token_nonce),
693 None => panic!(
694 "get_esdt_balance: Account {:?} does not exist",
695 address_to_hex(address)
696 ),
697 }
698 }
699
700 pub fn get_nft_attributes<T: TopDecode>(
701 &self,
702 address: &Address,
703 token_id: &[u8],
704 token_nonce: u64,
705 ) -> Option<T> {
706 match self.world.get_state().accounts.get(address) {
707 Some(acc) => match acc.esdt.get_by_identifier(token_id) {
708 Some(esdt_data) => esdt_data
709 .instances
710 .get_by_nonce(token_nonce)
711 .map(|inst| T::top_decode(inst.metadata.attributes.clone()).unwrap()),
712 None => None,
713 },
714 None => panic!(
715 "get_nft_attributes: Account {:?} does not exist",
716 address_to_hex(address)
717 ),
718 }
719 }
720
721 pub fn dump_state(&self) {
722 for address in self.world.get_state().accounts.keys() {
723 self.dump_state_for_account_hex_attributes(address);
724 println!();
725 }
726 }
727
728 #[inline]
729 pub fn dump_state_for_account_hex_attributes(&self, address: &Address) {
731 self.dump_state_for_account::<Vec<u8>>(address)
732 }
733
734 pub fn dump_state_for_account<AttributesType: 'static + TopDecode + core::fmt::Debug>(
736 &self,
737 address: &Address,
738 ) {
739 let account = match self.world.get_state().accounts.get(address) {
740 Some(acc) => acc,
741 None => panic!(
742 "dump_state_for_account: Account {:?} does not exist",
743 address_to_hex(address)
744 ),
745 };
746
747 println!("State for account: {:?}", address_to_hex(address));
748 println!("EGLD: {}", account.egld_balance);
749
750 if !account.esdt.is_empty() {
751 println!("ESDT Tokens:");
752 }
753 for (token_id, acc_esdt) in account.esdt.iter() {
754 let token_id_str = String::from_utf8(token_id.to_vec()).unwrap();
755 println!(" Token: {token_id_str}");
756
757 for (token_nonce, instance) in acc_esdt.instances.get_instances() {
758 if std::any::TypeId::of::<AttributesType>() == std::any::TypeId::of::<Vec<u8>>() {
759 print_token_balance_raw(
760 *token_nonce,
761 &instance.balance,
762 &instance.metadata.attributes,
763 );
764 } else {
765 match AttributesType::top_decode(&instance.metadata.attributes[..]) {
766 core::result::Result::Ok(attr) => {
767 print_token_balance_specialized(*token_nonce, &instance.balance, &attr)
768 }
769 core::result::Result::Err(_) => print_token_balance_raw(
770 *token_nonce,
771 &instance.balance,
772 &instance.metadata.attributes,
773 ),
774 }
775 }
776 }
777 }
778
779 if !account.storage.is_empty() {
780 println!();
781 println!("Storage: ");
782 }
783 for (key, value) in &account.storage {
784 let key_str = match String::from_utf8(key.to_vec()) {
785 core::result::Result::Ok(s) => s,
786 core::result::Result::Err(_) => bytes_to_hex(key),
787 };
788 let value_str = bytes_to_hex(value);
789
790 println!(" {key_str}: {value_str}");
791 }
792 }
793}
794
795fn address_to_hex(address: &Address) -> String {
796 hex::encode(address.as_bytes())
797}
798
799fn print_token_balance_raw(
800 token_nonce: u64,
801 token_balance: &num_bigint::BigUint,
802 attributes: &[u8],
803) {
804 println!(
805 " Nonce {}, balance: {}, attributes: {}",
806 token_nonce,
807 token_balance,
808 bytes_to_hex(attributes)
809 );
810}
811
812fn print_token_balance_specialized<T: core::fmt::Debug>(
813 token_nonce: u64,
814 token_balance: &num_bigint::BigUint,
815 attributes: &T,
816) {
817 println!(" Nonce {token_nonce}, balance: {token_balance}, attributes: {attributes:?}");
818}
819
820fn create_contract_obj_box<CB, ContractObjBuilder>(
821 func: ContractObjBuilder,
822) -> Box<dyn CallableContract>
823where
824 CB: ContractBase<Api = DebugApi> + CallableContract + 'static,
825 ContractObjBuilder: 'static + Fn() -> CB,
826{
827 let c_base = func();
828 Box::new(c_base)
829}