diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..0ecb9535 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/mx-bridge-eth-sc-rs.iml b/.idea/mx-bridge-eth-sc-rs.iml new file mode 100644 index 00000000..e5185826 --- /dev/null +++ b/.idea/mx-bridge-eth-sc-rs.iml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..c8397c94 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 67ceff37..ce4d64da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,6 +370,24 @@ dependencies = [ "multiversx-sc-meta-lib", ] +[[package]] +name = "esdt_safe" +version = "0.1.0" +dependencies = [ + "esdt-safe", + "eth-address", + "fee-estimator-module", + "max-bridged-amount-module", + "multi-transfer-esdt", + "multiversx-price-aggregator-sc", + "multiversx-sc", + "multiversx-sc-modules", + "multiversx-sc-scenario", + "token-module", + "transaction", + "tx-batch-module", +] + [[package]] name = "eth-address" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 2c45d19e..5f99cef4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,4 @@ members = [ "bridged-tokens-wrapper/meta", "test-caller", "test-caller/meta" -] +, "tests/esdt_safe"] diff --git a/esdt-safe/tests/whitebox_test.rs b/esdt-safe/tests/whitebox_test.rs new file mode 100644 index 00000000..a99ea5e1 --- /dev/null +++ b/esdt-safe/tests/whitebox_test.rs @@ -0,0 +1,1340 @@ +use eth_address::EthAddress; +use fee_estimator_module::FeeEstimatorModule; +use max_bridged_amount_module::MaxBridgedAmountModule; +use multiversx_sc_modules::pause::PauseModule; +use multiversx_sc_scenario::imports::*; +use esdt_safe::EsdtSafe; +use token_module::TokenModule; +use transaction::Transaction; +use transaction::transaction_status::TransactionStatus; +use tx_batch_module::TxBatchModule; + +const OWNER_ADDRESS_EXPR: &str = "address:owner"; +const SIGNER_0_ADDRESS_EXPR: &str = "address:signer0"; +const ETH_ADDRESS: &str = "0x0x2E110BBe2eEcd819c721D1a4fb91F3c33BDF0798"; +const ESTD_SAFE_ADDRESS_EXPR: &str = "sc:esdt-safe"; +// const MULTITRANSFER_ADDRESS_EXPR: &str = "sc:multi_transfer"; +const ESTD_SAFE_PATH_EXPR: &str = "mxsc:output/esdt-safe.mxsc.json"; +const TOKEN_ID: &[u8] = b"TOKEN-abc123"; +const TOKEN_ID_2: &[u8] = b"TOKEN-xyz789"; + + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + + blockchain.register_contract( + ESTD_SAFE_PATH_EXPR, + multiversx_price_aggregator_sc::ContractBuilder, + ); + + blockchain +} + + +/*********************************************************************************/ +/*************************[ENDPOINT]: create_transaction *************************/ +#[test] +fn test_create_transaction_should_fail_case_1() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.paused_status().set(true); + } + ); + + const ESDT_TRANSFER_AMOUNT: u32 = 100u32; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, ESDT_TRANSFER_AMOUNT) + .expect(TxExpect::user_error("str:Cannot create transaction while paused")), + |sc| { + sc.create_transaction(convert_to_eth_address(ETH_ADDRESS)) + }, + |r| r.assert_user_error("Cannot create transaction while paused"), + ); +} + + +#[test] +fn test_create_transaction_should_fail_case_2() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.paused_status().set(false); + } + ); + + const ESDT_TRANSFER_AMOUNT: u32 = 100u32; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, ESDT_TRANSFER_AMOUNT) + .expect(TxExpect::user_error("str:Token not in whitelist")), + |sc| { + sc.create_transaction(convert_to_eth_address(ETH_ADDRESS)) + }, + |r| r.assert_user_error("Token not in whitelist"), + ); +} + + +#[test] +fn test_create_transaction_should_fail_case_3() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.paused_status().set(false); + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + } + ); + + const BIG_VALUE: u32 = 100000000u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.eth_tx_gas_limit().set(BigUint::from(BIG_VALUE)); + sc.default_price_per_gas_unit(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(BIG_VALUE)); + } + ); + + const ESDT_TRANSFER_AMOUNT: u32 = 1u32; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, ESDT_TRANSFER_AMOUNT) + .expect(TxExpect::user_error("str:Transaction fees cost more than the entire bridged amount")), + |sc| { + sc.create_transaction(convert_to_eth_address(ETH_ADDRESS)) + }, + |r| r.assert_user_error("Transaction fees cost more than the entire bridged amount"), + ); +} + +#[test] +fn test_create_transaction_should_fail_case_4() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + const MAX_AMOUNT: u32 = 100u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.paused_status().set(false); + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.max_bridged_amount(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(MAX_AMOUNT)); + } + ); + + const ESDT_TRANSFER_AMOUNT: u32 = 101u32; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, ESDT_TRANSFER_AMOUNT) + .expect(TxExpect::user_error("str:Deposit over max amount")), + |sc| { + sc.create_transaction(convert_to_eth_address(ETH_ADDRESS)) + }, + |r| r.assert_user_error("Deposit over max amount"), + ); +} + +#[test] +fn test_create_transaction_should_fail_case_5() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + const MAX_AMOUNT: u32 = 100_000_000u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.paused_status().set(false); + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.max_bridged_amount(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(MAX_AMOUNT)); + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + sc.native_token(&TokenIdentifier::from(TOKEN_ID)).set(false); + } + ); + + const MINTED_AMOUNT: u32 = 199u32; + const BURNED_AMOUNT: u32 = 100u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.mint_balances(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(MINTED_AMOUNT)); + sc.burn_balances(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(BURNED_AMOUNT)); + } + ); + + const ESDT_TRANSFER_AMOUNT: u32 = 100u32; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, ESDT_TRANSFER_AMOUNT) + .expect(TxExpect::user_error("str:Not enough minted tokens!")), + |sc| { + sc.create_transaction(convert_to_eth_address(ETH_ADDRESS)) + }, + |r| r.assert_user_error("Not enough minted tokens!"), + ); +} + +#[test] +fn test_create_transaction_should_fail_case_6() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + const MAX_AMOUNT: u32 = 100_000_000u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.paused_status().set(false); + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.max_bridged_amount(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(MAX_AMOUNT)); + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + sc.native_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + } + ); + + const ESDT_TRANSFER_AMOUNT: u32 = 100u32; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, ESDT_TRANSFER_AMOUNT) + .expect(TxExpect::user_error("str:Cannot do the burn action!")), + |sc| { + sc.create_transaction(convert_to_eth_address(ETH_ADDRESS)) + }, + |r| r.assert_user_error("Cannot do the burn action!"), + ); +} + +#[test] +fn test_create_transaction_should_work_case_1() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + const MAX_AMOUNT: u32 = 100_000_000u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.paused_status().set(false); + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.max_bridged_amount(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(MAX_AMOUNT)); + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + sc.native_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + } + ); + + world.set_esdt_local_roles( + managed_address!(&AddressValue::from(ESTD_SAFE_ADDRESS_EXPR).to_address()), + TOKEN_ID, + &[EsdtLocalRole::Mint, EsdtLocalRole::Burn] + ); + + const ESDT_TRANSFER_AMOUNT: u32 = 100u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, ESDT_TRANSFER_AMOUNT), + |sc| { + let burned_amount_before = sc.burn_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + sc.create_transaction(convert_to_eth_address(ETH_ADDRESS)); + + let burned_amount_after = sc.burn_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + assert_eq!(burned_amount_after, burned_amount_before + ESDT_TRANSFER_AMOUNT); + }, + ); +} + +#[test] +fn test_create_transaction_should_work_case_2() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + const MAX_AMOUNT: u32 = 100_000_000u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.paused_status().set(false); + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.max_bridged_amount(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(MAX_AMOUNT)); + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + sc.native_token(&TokenIdentifier::from(TOKEN_ID)).set(false); + } + ); + + const MINTED_AMOUNT: u32 = 201u32; + const BURNED_AMOUNT: u32 = 100u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.mint_balances(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(MINTED_AMOUNT)); + sc.burn_balances(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(BURNED_AMOUNT)); + } + ); + + + world.set_esdt_local_roles( + managed_address!(&AddressValue::from(ESTD_SAFE_ADDRESS_EXPR).to_address()), + TOKEN_ID, + &[EsdtLocalRole::Mint, EsdtLocalRole::Burn] + ); + + const ESDT_TRANSFER_AMOUNT: u32 = 100u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, ESDT_TRANSFER_AMOUNT), + |sc| { + let burned_amount_before = sc.burn_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + sc.create_transaction(convert_to_eth_address(ETH_ADDRESS)); + + let burned_amount_after = sc.burn_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + assert_eq!(burned_amount_after, burned_amount_before + ESDT_TRANSFER_AMOUNT); + }, + ); +} + +#[test] +fn test_create_transaction_should_work_case_3() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + const MAX_AMOUNT: u32 = 100_000_000u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.paused_status().set(false); + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.max_bridged_amount(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(MAX_AMOUNT)); + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(false); + sc.native_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + } + ); + + world.set_esdt_local_roles( + managed_address!(&AddressValue::from(ESTD_SAFE_ADDRESS_EXPR).to_address()), + TOKEN_ID, + &[EsdtLocalRole::Mint, EsdtLocalRole::Burn] + ); + + const ESDT_TRANSFER_AMOUNT: u32 = 100u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, ESDT_TRANSFER_AMOUNT), + |sc| { + let total_balances_before = sc.total_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + sc.create_transaction(convert_to_eth_address(ETH_ADDRESS)); + + let total_balances_after = sc.total_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + assert_eq!(total_balances_after, total_balances_before + ESDT_TRANSFER_AMOUNT); + }, + ); +} + + + +/***************************************************************************/ +/*************************[ENDPOINT]: claim_refund *************************/ +#[test] +fn test_claim_refund_should_fail_case_1() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .expect(TxExpect::user_error("str:Nothing to refund")), + |sc| { + sc.claim_refund(TokenIdentifier::from(TOKEN_ID)); + }, + |r| r.assert_user_error("Nothing to refund"), + ); +} + +#[test] +fn test_claim_refund_should_work() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + const REFUND_AMOUNT: u32 = 100u32; + const TOTAL_REFUND_AMOUNT: u32 = 150u32; + const TOTAL_BALANCE_MAPPER: u32 = 300u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.refund_amount( + &managed_address!(&AddressValue::from(OWNER_ADDRESS_EXPR).to_address()), + &TokenIdentifier::from(TOKEN_ID) + ).set(BigUint::from(REFUND_AMOUNT)); + + sc.total_refund_amount(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(TOTAL_REFUND_AMOUNT)); + + sc.total_balances(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(TOTAL_BALANCE_MAPPER)); + } + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + let esdt_token_payment = sc.claim_refund(TokenIdentifier::from(TOKEN_ID)); + let (token_id, nonce, amount) = esdt_token_payment.into_tuple(); + assert_eq!(token_id, TokenIdentifier::from(TOKEN_ID)); + assert_eq!(nonce, 0u64); + assert_eq!(amount, REFUND_AMOUNT); + } + ); +} + + +/**************************************************************************/ +/*************************[ENDPOINT]: init_supply *************************/ +#[test] +fn test_init_supply_should_fail_case_1() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + const SEND_AMOUNT: u32 = 10_000u32; + const INIT_AMOUNT: u32 = SEND_AMOUNT; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID_2, 0, SEND_AMOUNT) + .expect(TxExpect::user_error("str:Invalid token ID")), + |sc| { + sc.init_supply(TokenIdentifier::from(TOKEN_ID), BigUint::from(INIT_AMOUNT)); + }, + |r| r.assert_user_error("Invalid token ID"), + ); +} + +#[test] +fn test_init_supply_should_fail_case_2() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + const SEND_AMOUNT: u32 = 10_000u32; + const INIT_AMOUNT: u32 = 100_000u32; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, SEND_AMOUNT) + .expect(TxExpect::user_error("str:Invalid amount")), + |sc| { + sc.init_supply(TokenIdentifier::from(TOKEN_ID), BigUint::from(INIT_AMOUNT)); + }, + |r| r.assert_user_error("Invalid amount"), + ); +} + +#[test] +fn test_init_supply_should_fail_case_3() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + const SEND_AMOUNT: u32 = 10_000u32; + const INIT_AMOUNT: u32 = SEND_AMOUNT; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, SEND_AMOUNT) + .expect(TxExpect::user_error("str:Token not in whitelist")), + |sc| { + sc.init_supply(TokenIdentifier::from(TOKEN_ID), BigUint::from(INIT_AMOUNT)); + }, + |r| r.assert_user_error("Token not in whitelist"), + ); +} + +#[test] +fn test_init_supply_should_fail_case_4() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + sc.native_token(&TokenIdentifier::from(TOKEN_ID)).set(false); + } + ); + + const SEND_AMOUNT: u32 = 10_000u32; + const INIT_AMOUNT: u32 = SEND_AMOUNT; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, SEND_AMOUNT) + .expect(TxExpect::user_error("str:Cannot init for non native tokens")), + |sc| { + sc.init_supply(TokenIdentifier::from(TOKEN_ID), BigUint::from(INIT_AMOUNT)); + }, + |r| r.assert_user_error("Cannot init for non native tokens"), + ); +} + +#[test] +fn test_init_supply_should_fail_case_5() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + sc.native_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + } + ); + + const SEND_AMOUNT: u32 = 10_000u32; + const INIT_AMOUNT: u32 = SEND_AMOUNT; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, SEND_AMOUNT) + .expect(TxExpect::user_error("str:Cannot do the burn action!")), + |sc| { + sc.init_supply(TokenIdentifier::from(TOKEN_ID), BigUint::from(INIT_AMOUNT)); + }, + |r| r.assert_user_error("Cannot do the burn action!"), + ); +} + +#[test] +fn test_init_supply_should_work_case_1() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(false); + } + ); + + const SEND_AMOUNT: u32 = 10_000u32; + const INIT_AMOUNT: u32 = SEND_AMOUNT; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, SEND_AMOUNT), + |sc| { + let total_balances_before = sc.total_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + sc.init_supply(TokenIdentifier::from(TOKEN_ID), BigUint::from(INIT_AMOUNT)); + + let total_balances_after = sc.total_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + assert_eq!(total_balances_after, total_balances_before + SEND_AMOUNT); + } + ); +} + +#[test] +fn test_init_supply_should_work_case_2() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + sc.native_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + } + ); + + const SEND_AMOUNT: u32 = 10_000u32; + const INIT_AMOUNT: u32 = SEND_AMOUNT; + + world.set_esdt_local_roles( + managed_address!(&AddressValue::from(ESTD_SAFE_ADDRESS_EXPR).to_address()), + TOKEN_ID, + &[EsdtLocalRole::Mint, EsdtLocalRole::Burn] + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, SEND_AMOUNT), + |sc| { + let burn_balance_before = sc.burn_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + sc.init_supply(TokenIdentifier::from(TOKEN_ID), BigUint::from(INIT_AMOUNT)); + + let burn_balance_after = sc.burn_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + assert_eq!(burn_balance_after, burn_balance_before + SEND_AMOUNT); + } + ); +} + + +/*******************************************************************************************/ +/*************************[ENDPOINT]: set_transaction_batch_status *************************/ +#[test] +fn test_set_transaction_batch_status_should_fail_case_1() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + let batch_id = 2u64; + let array_of_statuses = vec![TransactionStatus::Executed]; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .expect(TxExpect::user_error("str:Batches must be processed in order")), + |sc| { + + let managed_vec_of_statuses = ManagedVec::from(array_of_statuses); + sc.set_transaction_batch_status(batch_id, MultiValueEncoded::from(managed_vec_of_statuses)); + }, + |r| r.assert_user_error("Batches must be processed in order"), + ); +} + +#[test] +fn test_set_transaction_batch_status_should_fail_case_2() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR), + |sc| { + let txn_vec = vec![ + Transaction { + block_nonce: 1u64, + nonce: 1u64, + from: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID), + amount: BigUint::from(100u32), + is_refund_tx: false, + }, + Transaction { + block_nonce: 1u64, + nonce: 2u64, + from: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID_2), + amount: BigUint::from(20u32), + is_refund_tx: false, + } + ]; + let txn_managed_vec = ManagedVec::from(txn_vec); + sc.add_multiple_tx_to_batch(&txn_managed_vec); + } + ); + + + let batch_id = 1u64; + let array_of_statuses = vec![TransactionStatus::Executed, TransactionStatus::Rejected, TransactionStatus::Executed]; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .expect(TxExpect::user_error("str:Invalid number of statuses provided")), + |sc| { + + let managed_vec_of_statuses = ManagedVec::from(array_of_statuses); + sc.set_transaction_batch_status(batch_id, MultiValueEncoded::from(managed_vec_of_statuses)); + }, + |r| r.assert_user_error("Invalid number of statuses provided"), + ); +} + +#[test] +fn test_set_transaction_batch_status_should_fail_case_3() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR), + |sc| { + let txn_vec = vec![ + Transaction { + block_nonce: 1u64, + nonce: 1u64, + from: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID), + amount: BigUint::from(100u32), + is_refund_tx: false, + }, + ]; + let txn_managed_vec = ManagedVec::from(txn_vec); + sc.add_multiple_tx_to_batch(&txn_managed_vec); + } + ); + + + let batch_id = 1u64; + let array_of_statuses = vec![TransactionStatus::Pending]; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .expect(TxExpect::user_error("str:Transaction status may only be set to Executed or Rejected")), + |sc| { + + let managed_vec_of_statuses = ManagedVec::from(array_of_statuses); + sc.set_transaction_batch_status(batch_id, MultiValueEncoded::from(managed_vec_of_statuses)); + }, + |r| r.assert_user_error("Transaction status may only be set to Executed or Rejected"), + ); +} + +#[test] +fn test_set_transaction_batch_status_should_work() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR), + |sc| { + let txn_vec = vec![ + Transaction { + block_nonce: 1u64, + nonce: 1u64, + from: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID), + amount: BigUint::from(100u32), + is_refund_tx: false, + }, + Transaction { + block_nonce: 1u64, + nonce: 2u64, + from: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID_2), + amount: BigUint::from(200u32), + is_refund_tx: false, + } + ]; + let txn_managed_vec = ManagedVec::from(txn_vec); + sc.add_multiple_tx_to_batch(&txn_managed_vec); + } + ); + + + let batch_id = 1u64; + let array_of_statuses = vec![TransactionStatus::Executed, TransactionStatus::Rejected]; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR), + |sc| { + let managed_vec_of_statuses = ManagedVec::from(array_of_statuses); + sc.set_transaction_batch_status(batch_id, MultiValueEncoded::from(managed_vec_of_statuses)); + + let refound_amount = sc.refund_amount( + &managed_address!(&AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address()), + &TokenIdentifier::from(TOKEN_ID_2) + ).get(); + + let total_refund_amount = sc.total_refund_amount(&TokenIdentifier::from(TOKEN_ID_2)).get(); + + assert_eq!(total_refund_amount, BigUint::from(200u32)); + assert_eq!(refound_amount, BigUint::from(200u32)); + + assert_eq!(sc.first_batch_id().get(), batch_id + 1); + }, + ); +} + + +#[test] +fn test_add_refund_batch_should_fail_case_1() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .expect(TxExpect::user_error("str:Invalid caller")), + |sc| { + sc.add_refund_batch(ManagedVec::new()); + }, + |r| r.assert_user_error("Invalid caller"), + ); +} + +// #[test] +// fn test_add_refund_batch_should_fail_case_2() { +// let mut world = setup(); +// +// let esdt_safe_whitebox = WhiteboxContract::new( +// ESTD_SAFE_ADDRESS_EXPR, +// esdt_safe::contract_obj, +// ); +// +// world.whitebox_call_check( +// &esdt_safe_whitebox, +// ScCallStep::new() +// .from(MULTITRANSFER_ADDRESS_EXPR) +// .expect(TxExpect::user_error("str:Cannot refund with no payments")), +// |sc| { +// sc.multi_transfer_contract_address().set(managed_address!(&AddressValue::from(MULTITRANSFER_ADDRESS_EXPR).to_address())); +// // sc.add_refund_batch(ManagedVec::new()); +// }, +// |r| r.assert_user_error("Cannot refund with no payments"), +// ); +// } + + + +#[test] +fn test_compute_total_amounts_from_index() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR), + |sc| { + let txn1 = Transaction { + block_nonce: 1u64, + nonce: 1u64, + from: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID), + amount: BigUint::from(100u32), + is_refund_tx: false, + }; + + let txn2 = Transaction { + block_nonce: 1u64, + nonce: 2u64, + from: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID_2), + amount: BigUint::from(200u32), + is_refund_tx: false, + }; + + let txn3 = Transaction { + block_nonce: 1u64, + nonce: 2u64, + from: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID), + amount: BigUint::from(400u32), + is_refund_tx: false, + }; + + sc.add_to_batch(txn1); + sc.add_to_batch(txn2); + + sc.create_new_batch(txn3); + + let total_amounts = sc.compute_total_amounts_from_index(1u64, 3u64); + assert_eq!(total_amounts.get(0), EsdtTokenPayment::from((TokenIdentifier::from(TOKEN_ID), 0u64, BigUint::from(500u32)))); + assert_eq!(total_amounts.get(1), EsdtTokenPayment::from((TokenIdentifier::from(TOKEN_ID_2), 0u64, BigUint::from(200u32)))); + } + ); +} + + +#[test] +fn test_get_total_refund_amounts_for_address() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + let owner_address = AddressValue::from(OWNER_ADDRESS_EXPR); + let signer_0_address = AddressValue::from(SIGNER_0_ADDRESS_EXPR); + + // 1st step: Prepare the refund amounts for the owner and signer_0 + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID_2)); + + sc.refund_amount( + &managed_address!(&owner_address.to_address()), + &TokenIdentifier::from(TOKEN_ID) + ).set(BigUint::from(100u32)); + + sc.refund_amount( + &managed_address!(&signer_0_address.to_address()), + &TokenIdentifier::from(TOKEN_ID_2) + ).set(BigUint::from(200u32)); + } + ); + + // 2nd step: Query the total refund amounts for the owner and signer_0 + world.whitebox_query(&esdt_safe_whitebox, |sc| { + let total_refund_amounts = sc.get_refund_amounts(managed_address!(&owner_address.to_address())); + + let expected = MultiValue2::from((TokenIdentifier::from(TOKEN_ID), BigUint::from(100u32))); + let result_vec: Vec<_> = total_refund_amounts.into_iter().collect(); + + assert!(result_vec.contains(&expected), "Expected refund amount for TOKEN_ID not found."); + }); + + world.whitebox_query(&esdt_safe_whitebox, |sc| { + let total_refund_amounts = sc.get_refund_amounts(managed_address!(&signer_0_address.to_address())); + + let expected = MultiValue2::from((TokenIdentifier::from(TOKEN_ID_2), BigUint::from(200u32))); + let result_vec: Vec<_> = total_refund_amounts.into_iter().collect(); + + assert!(result_vec.contains(&expected), "Expected refund amount for TOKEN_ID_MINT_BURN not found."); + }); +} + + +#[test] +fn test_get_total_refund_amounts() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.total_refund_amount(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(100u32)); + } + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(SIGNER_0_ADDRESS_EXPR), + |sc| { + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID_2)); + sc.total_refund_amount(&TokenIdentifier::from(TOKEN_ID_2)).set(BigUint::from(200u32)); + } + ); + + // Query the total refund amounts + world.whitebox_query(&esdt_safe_whitebox, |sc| { + let total_refund_amounts = sc.getTotalRefundAmounts(); + + // Expected tuples + let expected_1 = MultiValue2::from((TokenIdentifier::from(TOKEN_ID), BigUint::from(100u32))); + let expected_2 = MultiValue2::from((TokenIdentifier::from(TOKEN_ID_2), BigUint::from(200u32))); + + // Convert to vector for easy comparison + let result_vec: Vec<_> = total_refund_amounts.into_iter().collect(); + + // Check that both expected tuples are in the result set + assert!(result_vec.contains(&expected_1), "Expected refund amount for TOKEN_ID not found."); + assert!(result_vec.contains(&expected_2), "Expected refund amount for TOKEN_ID_MINT_BURN not found."); + assert_eq!(result_vec.len(), 2); + }); +} + + + +#[test] +fn test_rebalance_for_refund_case_1() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + // Scenario 1: mintBurnToken = false + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(false); + sc.total_balances(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(100u32)); + }, + ); + + let refund_amount = 10u64; + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + let balance_before = sc.total_balances(&managed_token_id!(TOKEN_ID)).get().to_u64().unwrap_or(0); + + sc.rebalance_for_refund(&TokenIdentifier::from(TOKEN_ID), &BigUint::from(refund_amount)); + + let balance_after = sc.total_balances(&TokenIdentifier::from(TOKEN_ID)).get().to_u64().unwrap_or(0); + + assert_eq!(balance_after, balance_before - refund_amount); + }, + ); +} + +#[test] +fn test_rebalance_for_refund_case_2() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + // Scenario 2: mintBurnToken = true + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID_2)).set(true); + assert_eq!(sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID_2)).get(), true); + }, + ); + + world.set_esdt_local_roles( + managed_address!(&AddressValue::from(ESTD_SAFE_ADDRESS_EXPR).to_address()), + TOKEN_ID_2, + &[EsdtLocalRole::Mint, EsdtLocalRole::Burn] + ); + + let refund_amount = 10u64; + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + let balance_before = sc.mint_balances(&TokenIdentifier::from(TOKEN_ID_2)).get().to_u64().unwrap_or(0); + + sc.rebalance_for_refund(&TokenIdentifier::from(TOKEN_ID_2), &BigUint::from(refund_amount)); + + let balance_after = sc.mint_balances(&TokenIdentifier::from(TOKEN_ID_2)).get().to_u64().unwrap_or(0); + + assert_eq!(balance_after, balance_before + refund_amount); + }, + ); +} + + +#[test] +fn test_mark_refund() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.mark_refund( + &managed_address!(&AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address()), + &TokenIdentifier::from(TOKEN_ID), + &BigUint::from(10u32) + ); + + let refund_amount = sc.refund_amount( + &managed_address!(&AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address()), + &managed_token_id!(TOKEN_ID) + ).get(); + assert_eq!(refund_amount, 10u64); + + let total_refund_amount = sc.total_refund_amount(&managed_token_id!(TOKEN_ID)).get(); + assert_eq!(total_refund_amount, 10u64); + } + + ); +} + + +fn setup() -> ScenarioWorld { + // setup + let mut world = world(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + let estd_safe_code = world.code_expression(ESTD_SAFE_PATH_EXPR); + + let set_state_step = SetStateStep::new() + .put_account( + OWNER_ADDRESS_EXPR, + Account::new().nonce(1) + .balance(100_000_000u64) + .esdt_balance(TOKEN_ID.to_vec(), 100_000_000u64) + .esdt_balance(TOKEN_ID_2.to_vec(), 100_000_000u64) + ) + .put_account( + SIGNER_0_ADDRESS_EXPR, + Account::new().nonce(1) + .balance(100_000_000u64) + .esdt_balance(TOKEN_ID.to_vec(), 100_000_000u64) + .esdt_balance(TOKEN_ID_2.to_vec(), 100_000_000u64) + ) + .new_address(OWNER_ADDRESS_EXPR, 1, ESTD_SAFE_ADDRESS_EXPR) + .block_timestamp(100); + + let fee_estimator_expr = format!("sc:fee_estimator"); + + let multi_transfer_expr = format!("sc:multi_transfer"); + + world.set_state_step(set_state_step).whitebox_deploy( + &esdt_safe_whitebox, + ScDeployStep::new() + .from(OWNER_ADDRESS_EXPR) + .code(estd_safe_code), + |sc| { + sc.init( + managed_address!(&AddressValue::from(fee_estimator_expr.as_str()).to_address()), + managed_address!(&AddressValue::from(multi_transfer_expr.as_str()).to_address()), + BigUint::from(1000u32), + ); + }, + ).set_esdt_balance(esdt_safe_whitebox.address_expr.value.clone(), TOKEN_ID, 100_000_000u64); + + world +} + + +fn convert_to_eth_address(address: &str) -> EthAddress { + let address_str = address.trim_start_matches("0x"); // Remove the "0x" prefix if it's present + + // Convert the hexadecimal string to a byte array + let mut address_bytes = [0u8; 20]; // Initialize a 20-byte array + for (i, byte) in address_bytes.iter_mut().enumerate() { + let offset = i * 2; + *byte = u8::from_str_radix(&address_str[offset..offset + 2], 16).expect("Parsing error"); + } + + EthAddress{raw_addr: ManagedByteArray::new_from_bytes(&address_bytes)} +} + +#[test] +fn test_all() { + test_create_transaction_should_fail_case_1(); + test_create_transaction_should_fail_case_2(); + // test_create_transaction_should_fail_case_3(); + // test_create_transaction_should_fail_case_4(); + // test_create_transaction_should_fail_case_5(); + // test_create_transaction_should_fail_case_6(); + // test_create_transaction_should_work_case_1(); + // test_create_transaction_should_work_case_2(); + // test_create_transaction_should_work_case_3(); + + test_claim_refund_should_fail_case_1(); + test_claim_refund_should_work(); + + test_init_supply_should_fail_case_1(); + test_init_supply_should_fail_case_2(); + test_init_supply_should_fail_case_3(); + test_init_supply_should_fail_case_4(); + test_init_supply_should_fail_case_5(); + test_init_supply_should_work_case_1(); + test_init_supply_should_work_case_2(); + + test_set_transaction_batch_status_should_fail_case_1(); + test_set_transaction_batch_status_should_fail_case_2(); + test_set_transaction_batch_status_should_fail_case_3(); + test_set_transaction_batch_status_should_work(); + + test_add_refund_batch_should_fail_case_1(); + + test_compute_total_amounts_from_index(); + + test_get_total_refund_amounts_for_address(); + + test_get_total_refund_amounts(); + + test_rebalance_for_refund_case_1(); + test_rebalance_for_refund_case_2(); + + test_mark_refund(); +} \ No newline at end of file diff --git a/tests/esdt_safe/Cargo.toml b/tests/esdt_safe/Cargo.toml new file mode 100644 index 00000000..53a3d610 --- /dev/null +++ b/tests/esdt_safe/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "esdt_safe" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src/main.rs" + +[dependencies.esdt-safe] +path = "../../esdt-safe" + +[dependencies.multi-transfer-esdt] +path = "../../multi-transfer-esdt" + +[dependencies.transaction] +path = "../../common/transaction" + +[dependencies.eth-address] +path = "../../common/eth-address" + +[dependencies.fee-estimator-module] +path = "../../common/fee-estimator-module" + +[dependencies.token-module] +path = "../../common/token-module" + +[dependencies.tx-batch-module] +path = "../../common/tx-batch-module" + +[dependencies.max-bridged-amount-module] +path = "../../common/max-bridged-amount-module" + +[dependencies.multiversx-price-aggregator-sc] +version = "=0.52.0" + +[dependencies.multiversx-sc] +version = "=0.52.3" + +[dependencies.multiversx-sc-modules] +version = "=0.52.3" + +[dev-dependencies.multiversx-sc-scenario] +version = "=0.52.3" \ No newline at end of file diff --git a/tests/esdt_safe/src/main.rs b/tests/esdt_safe/src/main.rs new file mode 100644 index 00000000..db8368fd --- /dev/null +++ b/tests/esdt_safe/src/main.rs @@ -0,0 +1,1280 @@ +use eth_address::EthAddress; +use fee_estimator_module::FeeEstimatorModule; +use max_bridged_amount_module::MaxBridgedAmountModule; +use multiversx_sc_modules::pause::PauseModule; +use multiversx_sc_scenario::imports::*; +use esdt_safe::EsdtSafe; +use token_module::TokenModule; +use transaction::Transaction; +use transaction::transaction_status::TransactionStatus; +use tx_batch_module::TxBatchModule; +use multi_transfer_esdt::MultiTransferEsdt; + +const OWNER_ADDRESS_EXPR: &str = "address:owner"; +const SIGNER_0_ADDRESS_EXPR: &str = "address:signer0"; +const ETH_ADDRESS: &str = "0x0x2E110BBe2eEcd819c721D1a4fb91F3c33BDF0798"; +const ESTD_SAFE_ADDRESS_EXPR: &str = "sc:esdt-safe"; +const MULTI_TRANSFER_ADDRESS_EXPR: &str = "sc:multi-transfer"; +const ESTD_SAFE_PATH_EXPR: &str = "mxsc:output/esdt-safe.mxsc.json"; +const MULTI_TRANSFER_PATH_EXPR: &str = "mxsc:output/multi-transfer-esdt.mxsc.json"; +const TOKEN_ID: &[u8] = b"TOKEN-abc123"; +const TOKEN_ID_2: &[u8] = b"TOKEN-xyz789"; + + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + + blockchain.register_contract( + ESTD_SAFE_PATH_EXPR, + multiversx_price_aggregator_sc::ContractBuilder, + ); + + blockchain +} + + +#[test] +fn test_create_transaction_should_fail_case_1() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.paused_status().set(true); + } + ); + + const ESDT_TRANSFER_AMOUNT: u32 = 100u32; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, ESDT_TRANSFER_AMOUNT) + .expect(TxExpect::user_error("str:Cannot create transaction while paused")), + |sc| { + sc.create_transaction(convert_to_eth_address(ETH_ADDRESS)) + }, + |r| r.assert_user_error("Cannot create transaction while paused"), + ); +} + +#[test] +fn test_create_transaction_should_fail_case_2() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.paused_status().set(false); + } + ); + + const ESDT_TRANSFER_AMOUNT: u32 = 100u32; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, ESDT_TRANSFER_AMOUNT) + .expect(TxExpect::user_error("str:Token not in whitelist")), + |sc| { + sc.create_transaction(convert_to_eth_address(ETH_ADDRESS)) + }, + |r| r.assert_user_error("Token not in whitelist"), + ); +} + +//TODO: Add tests with fee-estimator contract integrated + + +#[test] +fn test_create_transaction_should_work_case_1() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + const MAX_AMOUNT: u32 = 100_000_000u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.paused_status().set(false); + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.max_bridged_amount(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(MAX_AMOUNT)); + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + sc.native_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + } + ); + + world.set_esdt_local_roles( + managed_address!(&AddressValue::from(ESTD_SAFE_ADDRESS_EXPR).to_address()), + TOKEN_ID, + &[EsdtLocalRole::Mint, EsdtLocalRole::Burn] + ); + + const ESDT_TRANSFER_AMOUNT: u32 = 100u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, ESDT_TRANSFER_AMOUNT), + |sc| { + let burned_amount_before = sc.burn_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + sc.create_transaction(convert_to_eth_address(ETH_ADDRESS)); + + let burned_amount_after = sc.burn_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + assert_eq!(burned_amount_after, burned_amount_before + ESDT_TRANSFER_AMOUNT); + }, + ); +} + +#[test] +fn test_create_transaction_should_work_case_2() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + const MAX_AMOUNT: u32 = 100_000_000u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.paused_status().set(false); + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.max_bridged_amount(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(MAX_AMOUNT)); + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + sc.native_token(&TokenIdentifier::from(TOKEN_ID)).set(false); + } + ); + + const MINTED_AMOUNT: u32 = 201u32; + const BURNED_AMOUNT: u32 = 100u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.mint_balances(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(MINTED_AMOUNT)); + sc.burn_balances(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(BURNED_AMOUNT)); + } + ); + + + world.set_esdt_local_roles( + managed_address!(&AddressValue::from(ESTD_SAFE_ADDRESS_EXPR).to_address()), + TOKEN_ID, + &[EsdtLocalRole::Mint, EsdtLocalRole::Burn] + ); + + const ESDT_TRANSFER_AMOUNT: u32 = 100u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, ESDT_TRANSFER_AMOUNT), + |sc| { + let burned_amount_before = sc.burn_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + sc.create_transaction(convert_to_eth_address(ETH_ADDRESS)); + + let burned_amount_after = sc.burn_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + assert_eq!(burned_amount_after, burned_amount_before + ESDT_TRANSFER_AMOUNT); + }, + ); +} + +#[test] +fn test_create_transaction_should_work_case_3() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + const MAX_AMOUNT: u32 = 100_000_000u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.paused_status().set(false); + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.max_bridged_amount(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(MAX_AMOUNT)); + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(false); + sc.native_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + } + ); + + world.set_esdt_local_roles( + managed_address!(&AddressValue::from(ESTD_SAFE_ADDRESS_EXPR).to_address()), + TOKEN_ID, + &[EsdtLocalRole::Mint, EsdtLocalRole::Burn] + ); + + const ESDT_TRANSFER_AMOUNT: u32 = 100u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, ESDT_TRANSFER_AMOUNT), + |sc| { + let total_balances_before = sc.total_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + sc.create_transaction(convert_to_eth_address(ETH_ADDRESS)); + + let total_balances_after = sc.total_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + assert_eq!(total_balances_after, total_balances_before + ESDT_TRANSFER_AMOUNT); + }, + ); +} + + +#[test] +fn test_claim_refund_should_fail_case_1() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .expect(TxExpect::user_error("str:Nothing to refund")), + |sc| { + sc.claim_refund(TokenIdentifier::from(TOKEN_ID)); + }, + |r| r.assert_user_error("Nothing to refund"), + ); +} + +#[test] +fn test_claim_refund_should_work() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + const REFUND_AMOUNT: u32 = 100u32; + const TOTAL_REFUND_AMOUNT: u32 = 150u32; + const TOTAL_BALANCE_MAPPER: u32 = 300u32; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.refund_amount( + &managed_address!(&AddressValue::from(OWNER_ADDRESS_EXPR).to_address()), + &TokenIdentifier::from(TOKEN_ID) + ).set(BigUint::from(REFUND_AMOUNT)); + + sc.total_refund_amount(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(TOTAL_REFUND_AMOUNT)); + + sc.total_balances(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(TOTAL_BALANCE_MAPPER)); + } + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + let esdt_token_payment = sc.claim_refund(TokenIdentifier::from(TOKEN_ID)); + let (token_id, nonce, amount) = esdt_token_payment.into_tuple(); + assert_eq!(token_id, TokenIdentifier::from(TOKEN_ID)); + assert_eq!(nonce, 0u64); + assert_eq!(amount, REFUND_AMOUNT); + } + ); +} + + +#[test] +fn test_init_supply_should_fail_case_1() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + const SEND_AMOUNT: u32 = 10_000u32; + const INIT_AMOUNT: u32 = SEND_AMOUNT; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID_2, 0, SEND_AMOUNT) + .expect(TxExpect::user_error("str:Invalid token ID")), + |sc| { + sc.init_supply(TokenIdentifier::from(TOKEN_ID), BigUint::from(INIT_AMOUNT)); + }, + |r| r.assert_user_error("Invalid token ID"), + ); +} + +#[test] +fn test_init_supply_should_fail_case_2() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + const SEND_AMOUNT: u32 = 10_000u32; + const INIT_AMOUNT: u32 = 100_000u32; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, SEND_AMOUNT) + .expect(TxExpect::user_error("str:Invalid amount")), + |sc| { + sc.init_supply(TokenIdentifier::from(TOKEN_ID), BigUint::from(INIT_AMOUNT)); + }, + |r| r.assert_user_error("Invalid amount"), + ); +} + +#[test] +fn test_init_supply_should_fail_case_3() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + const SEND_AMOUNT: u32 = 10_000u32; + const INIT_AMOUNT: u32 = SEND_AMOUNT; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, SEND_AMOUNT) + .expect(TxExpect::user_error("str:Token not in whitelist")), + |sc| { + sc.init_supply(TokenIdentifier::from(TOKEN_ID), BigUint::from(INIT_AMOUNT)); + }, + |r| r.assert_user_error("Token not in whitelist"), + ); +} + +#[test] +fn test_init_supply_should_fail_case_4() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + sc.native_token(&TokenIdentifier::from(TOKEN_ID)).set(false); + } + ); + + const SEND_AMOUNT: u32 = 10_000u32; + const INIT_AMOUNT: u32 = SEND_AMOUNT; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, SEND_AMOUNT) + .expect(TxExpect::user_error("str:Cannot init for non native tokens")), + |sc| { + sc.init_supply(TokenIdentifier::from(TOKEN_ID), BigUint::from(INIT_AMOUNT)); + }, + |r| r.assert_user_error("Cannot init for non native tokens"), + ); +} + +#[test] +fn test_init_supply_should_fail_case_5() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + sc.native_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + } + ); + + const SEND_AMOUNT: u32 = 10_000u32; + const INIT_AMOUNT: u32 = SEND_AMOUNT; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, SEND_AMOUNT) + .expect(TxExpect::user_error("str:Cannot do the burn action!")), + |sc| { + sc.init_supply(TokenIdentifier::from(TOKEN_ID), BigUint::from(INIT_AMOUNT)); + }, + |r| r.assert_user_error("Cannot do the burn action!"), + ); +} + +#[test] +fn test_init_supply_should_work_case_1() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(false); + } + ); + + const SEND_AMOUNT: u32 = 10_000u32; + const INIT_AMOUNT: u32 = SEND_AMOUNT; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, SEND_AMOUNT), + |sc| { + let total_balances_before = sc.total_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + sc.init_supply(TokenIdentifier::from(TOKEN_ID), BigUint::from(INIT_AMOUNT)); + + let total_balances_after = sc.total_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + assert_eq!(total_balances_after, total_balances_before + SEND_AMOUNT); + } + ); +} + +#[test] +fn test_init_supply_should_work_case_2() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + sc.native_token(&TokenIdentifier::from(TOKEN_ID)).set(true); + } + ); + + const SEND_AMOUNT: u32 = 10_000u32; + const INIT_AMOUNT: u32 = SEND_AMOUNT; + + world.set_esdt_local_roles( + managed_address!(&AddressValue::from(ESTD_SAFE_ADDRESS_EXPR).to_address()), + TOKEN_ID, + &[EsdtLocalRole::Mint, EsdtLocalRole::Burn] + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, SEND_AMOUNT), + |sc| { + let burn_balance_before = sc.burn_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + sc.init_supply(TokenIdentifier::from(TOKEN_ID), BigUint::from(INIT_AMOUNT)); + + let burn_balance_after = sc.burn_balances(&TokenIdentifier::from(TOKEN_ID)).get(); + + assert_eq!(burn_balance_after, burn_balance_before + SEND_AMOUNT); + } + ); +} + + +#[test] +fn test_set_transaction_batch_status_should_fail_case_1() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + let batch_id = 2u64; + let array_of_statuses = vec![TransactionStatus::Executed]; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .expect(TxExpect::user_error("str:Batches must be processed in order")), + |sc| { + + let managed_vec_of_statuses = ManagedVec::from(array_of_statuses); + sc.set_transaction_batch_status(batch_id, MultiValueEncoded::from(managed_vec_of_statuses)); + }, + |r| r.assert_user_error("Batches must be processed in order"), + ); +} + +#[test] +fn test_set_transaction_batch_status_should_fail_case_2() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR), + |sc| { + let txn_vec = vec![ + Transaction { + block_nonce: 1u64, + nonce: 1u64, + from: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID), + amount: BigUint::from(100u32), + is_refund_tx: false, + }, + Transaction { + block_nonce: 1u64, + nonce: 2u64, + from: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID_2), + amount: BigUint::from(20u32), + is_refund_tx: false, + } + ]; + let txn_managed_vec = ManagedVec::from(txn_vec); + sc.add_multiple_tx_to_batch(&txn_managed_vec); + } + ); + + + let batch_id = 1u64; + let array_of_statuses = vec![TransactionStatus::Executed, TransactionStatus::Rejected, TransactionStatus::Executed]; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .expect(TxExpect::user_error("str:Invalid number of statuses provided")), + |sc| { + + let managed_vec_of_statuses = ManagedVec::from(array_of_statuses); + sc.set_transaction_batch_status(batch_id, MultiValueEncoded::from(managed_vec_of_statuses)); + }, + |r| r.assert_user_error("Invalid number of statuses provided"), + ); +} + +#[test] +fn test_set_transaction_batch_status_should_fail_case_3() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR), + |sc| { + let txn_vec = vec![ + Transaction { + block_nonce: 1u64, + nonce: 1u64, + from: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID), + amount: BigUint::from(100u32), + is_refund_tx: false, + }, + ]; + let txn_managed_vec = ManagedVec::from(txn_vec); + sc.add_multiple_tx_to_batch(&txn_managed_vec); + } + ); + + + let batch_id = 1u64; + let array_of_statuses = vec![TransactionStatus::Pending]; + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .expect(TxExpect::user_error("str:Transaction status may only be set to Executed or Rejected")), + |sc| { + + let managed_vec_of_statuses = ManagedVec::from(array_of_statuses); + sc.set_transaction_batch_status(batch_id, MultiValueEncoded::from(managed_vec_of_statuses)); + }, + |r| r.assert_user_error("Transaction status may only be set to Executed or Rejected"), + ); +} + +#[test] +fn test_set_transaction_batch_status_should_work() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR), + |sc| { + let txn_vec = vec![ + Transaction { + block_nonce: 1u64, + nonce: 1u64, + from: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID), + amount: BigUint::from(100u32), + is_refund_tx: false, + }, + Transaction { + block_nonce: 1u64, + nonce: 2u64, + from: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID_2), + amount: BigUint::from(200u32), + is_refund_tx: false, + } + ]; + let txn_managed_vec = ManagedVec::from(txn_vec); + sc.add_multiple_tx_to_batch(&txn_managed_vec); + } + ); + + + let batch_id = 1u64; + let array_of_statuses = vec![TransactionStatus::Executed, TransactionStatus::Rejected]; + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR), + |sc| { + let managed_vec_of_statuses = ManagedVec::from(array_of_statuses); + sc.set_transaction_batch_status(batch_id, MultiValueEncoded::from(managed_vec_of_statuses)); + + let refound_amount = sc.refund_amount( + &managed_address!(&AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address()), + &TokenIdentifier::from(TOKEN_ID_2) + ).get(); + + let total_refund_amount = sc.total_refund_amount(&TokenIdentifier::from(TOKEN_ID_2)).get(); + + assert_eq!(total_refund_amount, BigUint::from(200u32)); + assert_eq!(refound_amount, BigUint::from(200u32)); + + assert_eq!(sc.first_batch_id().get(), batch_id + 1); + }, + ); +} + + +#[test] +fn test_add_refund_batch_should_fail_case_1() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .expect(TxExpect::user_error("str:Invalid caller")), + |sc| { + sc.add_refund_batch(ManagedVec::new()); + }, + |r| r.assert_user_error("Invalid caller"), + ); +} + +#[test] +fn test_add_refund_batch_should_fail_case_2() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(MULTI_TRANSFER_ADDRESS_EXPR) + .expect(TxExpect::user_error("str:Cannot refund with no payments")), + |sc| { + sc.multi_transfer_contract_address().set(managed_address!(&AddressValue::from(MULTI_TRANSFER_ADDRESS_EXPR).to_address())); + sc.add_refund_batch(ManagedVec::new()); + }, + |r| r.assert_user_error("Cannot refund with no payments"), + ); +} + +#[test] +fn test_add_refund_batch_should_fail_case_3() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(MULTI_TRANSFER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID_2, 0, 100u32) + .esdt_transfer(TOKEN_ID_2, 0, 20u32) + .expect(TxExpect::user_error("str:Token identifiers do not match")), + |sc| { + sc.multi_transfer_contract_address().set(managed_address!(&AddressValue::from(MULTI_TRANSFER_ADDRESS_EXPR).to_address())); + + let txn_vec = vec![ + Transaction { + block_nonce: 1u64, + nonce: 1u64, + from: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID), + amount: BigUint::from(100u32), + is_refund_tx: false, + }, + Transaction { + block_nonce: 1u64, + nonce: 2u64, + from: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID_2), + amount: BigUint::from(20u32), + is_refund_tx: false, + } + ]; + let txn_managed_vec = ManagedVec::from(txn_vec); + + sc.add_refund_batch(txn_managed_vec); + }, + |r| r.assert_user_error("Token identifiers do not match"), + ); +} + +#[test] +fn test_add_refund_batch_should_fail_case_4() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call_check( + &esdt_safe_whitebox, + ScCallStep::new() + .from(MULTI_TRANSFER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, 90u32) + .esdt_transfer(TOKEN_ID_2, 0, 30u32) + .expect(TxExpect::user_error("str:Amounts do not match")), + |sc| { + sc.multi_transfer_contract_address().set(managed_address!(&AddressValue::from(MULTI_TRANSFER_ADDRESS_EXPR).to_address())); + + let txn_vec = vec![ + Transaction { + block_nonce: 1u64, + nonce: 1u64, + from: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID), + amount: BigUint::from(100u32), + is_refund_tx: false, + }, + Transaction { + block_nonce: 1u64, + nonce: 2u64, + from: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID_2), + amount: BigUint::from(20u32), + is_refund_tx: false, + } + ]; + let txn_managed_vec = ManagedVec::from(txn_vec); + + sc.add_refund_batch(txn_managed_vec); + }, + |r| r.assert_user_error("Amounts do not match"), + ); +} + +#[test] +fn test_add_refund_batch_should_work() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(MULTI_TRANSFER_ADDRESS_EXPR) + .esdt_transfer(TOKEN_ID, 0, 100u32) + .esdt_transfer(TOKEN_ID_2, 0, 20u32), + |sc| { + sc.multi_transfer_contract_address().set(managed_address!(&AddressValue::from(MULTI_TRANSFER_ADDRESS_EXPR).to_address())); + + let txn_vec = vec![ + Transaction { + block_nonce: 1u64, + nonce: 1u64, + from: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID), + amount: BigUint::from(100u32), + is_refund_tx: false, + }, + Transaction { + block_nonce: 1u64, + nonce: 2u64, + from: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID_2), + amount: BigUint::from(20u32), + is_refund_tx: false, + } + ]; + let txn_managed_vec = ManagedVec::from(txn_vec); + sc.add_refund_batch(txn_managed_vec); + }, + ); +} + + +#[test] +fn test_compute_total_amounts_from_index() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR), + |sc| { + let txn1 = Transaction { + block_nonce: 1u64, + nonce: 1u64, + from: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID), + amount: BigUint::from(100u32), + is_refund_tx: false, + }; + + let txn2 = Transaction { + block_nonce: 1u64, + nonce: 2u64, + from: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID_2), + amount: BigUint::from(200u32), + is_refund_tx: false, + }; + + let txn3 = Transaction { + block_nonce: 1u64, + nonce: 2u64, + from: managed_buffer!(AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address().as_bytes()), + to: managed_buffer!(AddressValue::from(OWNER_ADDRESS_EXPR).to_address().as_bytes()), + token_identifier: TokenIdentifier::from(TOKEN_ID), + amount: BigUint::from(400u32), + is_refund_tx: false, + }; + + sc.add_to_batch(txn1); + sc.add_to_batch(txn2); + + sc.create_new_batch(txn3); + + let total_amounts = sc.compute_total_amounts_from_index(1u64, 3u64); + assert_eq!(total_amounts.get(0), EsdtTokenPayment::from((TokenIdentifier::from(TOKEN_ID), 0u64, BigUint::from(500u32)))); + assert_eq!(total_amounts.get(1), EsdtTokenPayment::from((TokenIdentifier::from(TOKEN_ID_2), 0u64, BigUint::from(200u32)))); + } + ); +} + + +#[test] +fn test_get_total_refund_amounts_for_address() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + let owner_address = AddressValue::from(OWNER_ADDRESS_EXPR); + let signer_0_address = AddressValue::from(SIGNER_0_ADDRESS_EXPR); + + // 1st step: Prepare the refund amounts for the owner and signer_0 + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID_2)); + + sc.refund_amount( + &managed_address!(&owner_address.to_address()), + &TokenIdentifier::from(TOKEN_ID) + ).set(BigUint::from(100u32)); + + sc.refund_amount( + &managed_address!(&signer_0_address.to_address()), + &TokenIdentifier::from(TOKEN_ID_2) + ).set(BigUint::from(200u32)); + } + ); + + // 2nd step: Query the total refund amounts for the owner and signer_0 + world.whitebox_query(&esdt_safe_whitebox, |sc| { + let total_refund_amounts = sc.get_refund_amounts(managed_address!(&owner_address.to_address())); + + let expected = MultiValue2::from((TokenIdentifier::from(TOKEN_ID), BigUint::from(100u32))); + let result_vec: Vec<_> = total_refund_amounts.into_iter().collect(); + + assert!(result_vec.contains(&expected), "Expected refund amount for TOKEN_ID not found."); + }); + + world.whitebox_query(&esdt_safe_whitebox, |sc| { + let total_refund_amounts = sc.get_refund_amounts(managed_address!(&signer_0_address.to_address())); + + let expected = MultiValue2::from((TokenIdentifier::from(TOKEN_ID_2), BigUint::from(200u32))); + let result_vec: Vec<_> = total_refund_amounts.into_iter().collect(); + + assert!(result_vec.contains(&expected), "Expected refund amount for TOKEN_ID_MINT_BURN not found."); + }); +} + + +#[test] +fn test_get_total_refund_amounts() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID)); + sc.total_refund_amount(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(100u32)); + } + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(SIGNER_0_ADDRESS_EXPR), + |sc| { + sc.token_whitelist().insert(TokenIdentifier::from(TOKEN_ID_2)); + sc.total_refund_amount(&TokenIdentifier::from(TOKEN_ID_2)).set(BigUint::from(200u32)); + } + ); + + // Query the total refund amounts + world.whitebox_query(&esdt_safe_whitebox, |sc| { + let total_refund_amounts = sc.getTotalRefundAmounts(); + + // Expected tuples + let expected_1 = MultiValue2::from((TokenIdentifier::from(TOKEN_ID), BigUint::from(100u32))); + let expected_2 = MultiValue2::from((TokenIdentifier::from(TOKEN_ID_2), BigUint::from(200u32))); + + // Convert to vector for easy comparison + let result_vec: Vec<_> = total_refund_amounts.into_iter().collect(); + + // Check that both expected tuples are in the result set + assert!(result_vec.contains(&expected_1), "Expected refund amount for TOKEN_ID not found."); + assert!(result_vec.contains(&expected_2), "Expected refund amount for TOKEN_ID_MINT_BURN not found."); + assert_eq!(result_vec.len(), 2); + }); +} + + +#[test] +fn test_rebalance_for_refund_case_1() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + // Scenario 1: mintBurnToken = false + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID)).set(false); + sc.total_balances(&TokenIdentifier::from(TOKEN_ID)).set(BigUint::from(100u32)); + }, + ); + + let refund_amount = 10u64; + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + let balance_before = sc.total_balances(&managed_token_id!(TOKEN_ID)).get().to_u64().unwrap_or(0); + + sc.rebalance_for_refund(&TokenIdentifier::from(TOKEN_ID), &BigUint::from(refund_amount)); + + let balance_after = sc.total_balances(&TokenIdentifier::from(TOKEN_ID)).get().to_u64().unwrap_or(0); + + assert_eq!(balance_after, balance_before - refund_amount); + }, + ); +} + +#[test] +fn test_rebalance_for_refund_case_2() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + // Scenario 2: mintBurnToken = true + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID_2)).set(true); + assert_eq!(sc.mint_burn_token(&TokenIdentifier::from(TOKEN_ID_2)).get(), true); + }, + ); + + world.set_esdt_local_roles( + managed_address!(&AddressValue::from(ESTD_SAFE_ADDRESS_EXPR).to_address()), + TOKEN_ID_2, + &[EsdtLocalRole::Mint, EsdtLocalRole::Burn] + ); + + let refund_amount = 10u64; + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + let balance_before = sc.mint_balances(&TokenIdentifier::from(TOKEN_ID_2)).get().to_u64().unwrap_or(0); + + sc.rebalance_for_refund(&TokenIdentifier::from(TOKEN_ID_2), &BigUint::from(refund_amount)); + + let balance_after = sc.mint_balances(&TokenIdentifier::from(TOKEN_ID_2)).get().to_u64().unwrap_or(0); + + assert_eq!(balance_after, balance_before + refund_amount); + }, + ); +} + + +#[test] +fn test_mark_refund() { + let mut world = setup(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + + world.whitebox_call( + &esdt_safe_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + sc.mark_refund( + &managed_address!(&AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address()), + &TokenIdentifier::from(TOKEN_ID), + &BigUint::from(10u32) + ); + + let refund_amount = sc.refund_amount( + &managed_address!(&AddressValue::from(SIGNER_0_ADDRESS_EXPR).to_address()), + &managed_token_id!(TOKEN_ID) + ).get(); + assert_eq!(refund_amount, 10u64); + + let total_refund_amount = sc.total_refund_amount(&managed_token_id!(TOKEN_ID)).get(); + assert_eq!(total_refund_amount, 10u64); + } + + ); +} + + +fn setup() -> ScenarioWorld { + let mut world = world(); + + let esdt_safe_whitebox = WhiteboxContract::new( + ESTD_SAFE_ADDRESS_EXPR, + esdt_safe::contract_obj, + ); + let estd_safe_code = world.code_expression(ESTD_SAFE_PATH_EXPR); + + let multi_transfer_whitebox = WhiteboxContract::new( + MULTI_TRANSFER_ADDRESS_EXPR, + multi_transfer_esdt::contract_obj, + ); + let multi_transfer_code = world.code_expression(MULTI_TRANSFER_PATH_EXPR); + + let set_state_step = SetStateStep::new() + .put_account( + OWNER_ADDRESS_EXPR, + Account::new().nonce(1) + .balance(100_000_000u64) + .esdt_balance(TOKEN_ID.to_vec(), 100_000_000u64) + .esdt_balance(TOKEN_ID_2.to_vec(), 100_000_000u64) + ) + .put_account( + SIGNER_0_ADDRESS_EXPR, + Account::new().nonce(1) + .balance(100_000_000u64) + .esdt_balance(TOKEN_ID.to_vec(), 100_000_000u64) + .esdt_balance(TOKEN_ID_2.to_vec(), 100_000_000u64) + ) + .new_address(OWNER_ADDRESS_EXPR, 1, ESTD_SAFE_ADDRESS_EXPR) + .new_address(OWNER_ADDRESS_EXPR, 2, MULTI_TRANSFER_ADDRESS_EXPR) + .block_timestamp(100); + + let fee_estimator_expr = format!("sc:fee_estimator"); + let multi_transfer_expr = format!("sc:multi_transfer"); + + world.set_state_step(set_state_step) + .whitebox_deploy( + &esdt_safe_whitebox, + ScDeployStep::new() + .from(OWNER_ADDRESS_EXPR) + .code(estd_safe_code), + |sc| { + sc.init( + managed_address!(&AddressValue::from(fee_estimator_expr.as_str()).to_address()), + managed_address!(&AddressValue::from(multi_transfer_expr.as_str()).to_address()), + BigUint::from(1000u32), + ); + }, + ) + .whitebox_deploy( + &multi_transfer_whitebox, + ScDeployStep::new() + .from(OWNER_ADDRESS_EXPR) + .code(multi_transfer_code), + |sc| + sc.init(), + + ); + + world.set_esdt_balance(esdt_safe_whitebox.address_expr.value.clone(), TOKEN_ID, 100_000_000u64); + world.set_esdt_balance(esdt_safe_whitebox.address_expr.value.clone(), TOKEN_ID_2, 100_000_000u64); + world.set_esdt_balance(multi_transfer_whitebox.address_expr.value.clone(), TOKEN_ID, 100_000_000u64); + world.set_esdt_balance(multi_transfer_whitebox.address_expr.value.clone(), TOKEN_ID_2, 100_000_000u64); + + world +} + + +fn convert_to_eth_address(address: &str) -> EthAddress { + let address_str = address.trim_start_matches("0x"); // Remove the "0x" prefix if it's present + + // Convert the hexadecimal string to a byte array + let mut address_bytes = [0u8; 20]; // Initialize a 20-byte array + for (i, byte) in address_bytes.iter_mut().enumerate() { + let offset = i * 2; + *byte = u8::from_str_radix(&address_str[offset..offset + 2], 16).expect("Parsing error"); + } + + EthAddress{raw_addr: ManagedByteArray::new_from_bytes(&address_bytes)} +} \ No newline at end of file