multiversx_sc_scenario/whitebox_legacy/
contract_obj_wrapper.rs

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    // Has to be used before performing a deploy from a SC
296    // The returned SC wrapper cannot be used before the deploy is actually executed
297    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    // deduplicates code for execution
612    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    /// Creates a temporary DebugApi context to run lambda function.
660    ///
661    /// Restores previous context (if any) after finishing execution.
662    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    /// Prints the state for the account, with any token attributes as hex
730    pub fn dump_state_for_account_hex_attributes(&self, address: &Address) {
731        self.dump_state_for_account::<Vec<u8>>(address)
732    }
733
734    /// Prints the state for the account, with token attributes decoded as the provided type, if possible
735    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}