diff --git a/Cargo.lock b/Cargo.lock index 868a8f8d..b51a8060 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -721,6 +721,9 @@ dependencies = [ "eth-address", "fee-estimator-module", "max-bridged-amount-module", + "mock-esdt-safe", + "mock-multi-transfer-esdt", + "mock-price-aggregator", "multi-transfer-esdt", "multiversx-price-aggregator-sc", "multiversx-sc", diff --git a/common/mock-contracts/mock-bridge-proxy/src/mock_bridge_proxy.rs b/common/mock-contracts/mock-bridge-proxy/src/mock_bridge_proxy.rs index ebd51f63..55a8bcaa 100644 --- a/common/mock-contracts/mock-bridge-proxy/src/mock_bridge_proxy.rs +++ b/common/mock-contracts/mock-bridge-proxy/src/mock_bridge_proxy.rs @@ -7,7 +7,7 @@ use multiversx_sc::imports::*; #[multiversx_sc::contract] pub trait MockBridgeProxy { #[init] - fn init(&self, _opt_multi_transfer_address: OptionalValue) {} + fn init(&self) {} #[upgrade] fn upgrade(&self) {} diff --git a/common/mock-contracts/mock-esdt-safe/src/mock_esdt_safe.rs b/common/mock-contracts/mock-esdt-safe/src/mock_esdt_safe.rs index 991fc205..1c5a8e3a 100644 --- a/common/mock-contracts/mock-esdt-safe/src/mock_esdt_safe.rs +++ b/common/mock-contracts/mock-esdt-safe/src/mock_esdt_safe.rs @@ -16,13 +16,7 @@ pub struct RefundInfo { #[multiversx_sc::contract] pub trait MockEsdtSafe { #[init] - fn init( - &self, - _fee_estimator_contract_address: ManagedAddress, - _multi_transfer_contract_address: ManagedAddress, - _eth_tx_gas_limit: BigUint, - ) { - } + fn init(&self, _eth_tx_gas_limit: BigUint) {} #[upgrade] fn upgrade(&self) {} @@ -35,4 +29,22 @@ pub trait MockEsdtSafe { _opt_refund_info: OptionalValue>, ) { } + + #[only_owner] + #[endpoint(withdrawTransactionFees)] + fn withdraw_transaction_fees( + &self, + _token_id: TokenIdentifier, + _multisig_owner: ManagedAddress, + ) { + } + + #[only_owner] + #[endpoint(withdrawRefundFeesForEthereum)] + fn withdraw_refund_fees_for_ethereum( + &self, + _token_id: TokenIdentifier, + _multisig_owner: ManagedAddress, + ) { + } } diff --git a/common/mock-contracts/mock-multi-transfer-esdt/src/mock_multi_transfer_esdt.rs b/common/mock-contracts/mock-multi-transfer-esdt/src/mock_multi_transfer_esdt.rs index 629d799b..16c4cbb2 100644 --- a/common/mock-contracts/mock-multi-transfer-esdt/src/mock_multi_transfer_esdt.rs +++ b/common/mock-contracts/mock-multi-transfer-esdt/src/mock_multi_transfer_esdt.rs @@ -11,4 +11,8 @@ pub trait MockMultiTransferEsdt { #[upgrade] fn upgrade(&self) {} + + #[only_owner] + #[endpoint(addUnprocessedRefundTxToBatch)] + fn add_unprocessed_refund_tx_to_batch(&self, _tx_id: u64) {} } diff --git a/common/mock-contracts/mock-multi-transfer-esdt/wasm/src/lib.rs b/common/mock-contracts/mock-multi-transfer-esdt/wasm/src/lib.rs index 36a8284b..8704aadc 100644 --- a/common/mock-contracts/mock-multi-transfer-esdt/wasm/src/lib.rs +++ b/common/mock-contracts/mock-multi-transfer-esdt/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 0 +// Endpoints: 1 // Async Callback (empty): 1 -// Total number of exported functions: 3 +// Total number of exported functions: 4 #![no_std] @@ -20,6 +20,7 @@ multiversx_sc_wasm_adapter::endpoints! { ( init => init upgrade => upgrade + addUnprocessedRefundTxToBatch => add_unprocessed_refund_tx_to_batch ) } diff --git a/multisig/Cargo.toml b/multisig/Cargo.toml index 18413c6c..72a7fee8 100644 --- a/multisig/Cargo.toml +++ b/multisig/Cargo.toml @@ -26,6 +26,15 @@ path = "../common/tx-batch-module" [dependencies.max-bridged-amount-module] path = "../common/max-bridged-amount-module" +[dependencies.mock-multi-transfer-esdt] +path = "../common/mock-contracts/mock-multi-transfer-esdt" + +[dependencies.mock-esdt-safe] +path = "../common/mock-contracts/mock-esdt-safe" + +[dependencies.mock-price-aggregator] +path = "../common/mock-contracts/mock-price-aggregator" + [dependencies.sc-proxies] path = "../common/sc-proxies" diff --git a/multisig/scenarios/ethereum_to_multiversx_relayer_call_data_several_tx_test.scen.json b/multisig/scenarios/ethereum_to_multiversx_relayer_call_data_several_tx_test.scen.json index 1808087b..be6db6b8 100644 --- a/multisig/scenarios/ethereum_to_multiversx_relayer_call_data_several_tx_test.scen.json +++ b/multisig/scenarios/ethereum_to_multiversx_relayer_call_data_several_tx_test.scen.json @@ -181,6 +181,30 @@ "status": "0" } }, + { + "step": "scCall", + "id": "", + "tx": { + "from": "sc:multisig", + "to": "sc:esdt-safe", + "function": "addTokenToWhitelist", + "arguments": [ + "0x45474c442d313233343536", + "0x45474c44", + "0x", + "0x01", + "0x", + "0x", + "0x", + "0x0249f0" + ], + "gasLimit": "5,000,000" + }, + "expect": { + "out": [], + "status": "0" + } + }, { "step": "scCall", "id": "", diff --git a/multisig/scenarios/ethereum_to_multiversx_relayer_query2_test.scen.json b/multisig/scenarios/ethereum_to_multiversx_relayer_query2_test.scen.json index b242dfb2..2ded2f90 100644 --- a/multisig/scenarios/ethereum_to_multiversx_relayer_query2_test.scen.json +++ b/multisig/scenarios/ethereum_to_multiversx_relayer_query2_test.scen.json @@ -181,6 +181,30 @@ "status": "0" } }, + { + "step": "scCall", + "id": "", + "tx": { + "from": "sc:multisig", + "to": "sc:esdt-safe", + "function": "addTokenToWhitelist", + "arguments": [ + "0x45474c442d313233343536", + "0x45474c44", + "0x", + "0x01", + "0x", + "0x", + "0x", + "0x0249f0" + ], + "gasLimit": "5,000,000" + }, + "expect": { + "out": [], + "status": "0" + } + }, { "step": "scCall", "id": "", diff --git a/multisig/scenarios/ethereum_to_multiversx_relayer_query_test.scen.json b/multisig/scenarios/ethereum_to_multiversx_relayer_query_test.scen.json index 5d68ad84..3bf2132c 100644 --- a/multisig/scenarios/ethereum_to_multiversx_relayer_query_test.scen.json +++ b/multisig/scenarios/ethereum_to_multiversx_relayer_query_test.scen.json @@ -181,6 +181,30 @@ "status": "0" } }, + { + "step": "scCall", + "id": "", + "tx": { + "from": "sc:multisig", + "to": "sc:esdt-safe", + "function": "addTokenToWhitelist", + "arguments": [ + "0x45474c442d313233343536", + "0x45474c44", + "0x", + "0x01", + "0x", + "0x", + "0x", + "0x0249f0" + ], + "gasLimit": "5,000,000" + }, + "expect": { + "out": [], + "status": "0" + } + }, { "step": "scCall", "id": "", @@ -399,7 +423,7 @@ "accounts": { "address:user1": { "esdt": { - "str:WEGLD-123456": "76000000000", + "str:WEGLD-123456": "76000100000", "+": "" }, "storage": "*", diff --git a/multisig/scenarios/ethereum_to_multiversx_tx_batch_ok_call_data_encoded.scen.json b/multisig/scenarios/ethereum_to_multiversx_tx_batch_ok_call_data_encoded.scen.json index 0d4c80ee..783cd978 100644 --- a/multisig/scenarios/ethereum_to_multiversx_tx_batch_ok_call_data_encoded.scen.json +++ b/multisig/scenarios/ethereum_to_multiversx_tx_batch_ok_call_data_encoded.scen.json @@ -181,6 +181,30 @@ "status": "0" } }, + { + "step": "scCall", + "id": "", + "tx": { + "from": "sc:multisig", + "to": "sc:esdt-safe", + "function": "addTokenToWhitelist", + "arguments": [ + "0x45474c442d313233343536", + "0x45474c44", + "0x", + "0x01", + "0x", + "0x", + "0x", + "0x0249f0" + ], + "gasLimit": "5,000,000" + }, + "expect": { + "out": [], + "status": "0" + } + }, { "step": "scCall", "id": "", @@ -359,7 +383,7 @@ "accounts": { "address:user1": { "esdt": { - "str:WEGLD-123456": "76000000000", + "str:WEGLD-123456": "76000100000", "+": "" }, "storage": "*", diff --git a/multisig/tests/multisig_blackbox_test.rs b/multisig/tests/multisig_blackbox_test.rs index b297eaf1..b1af3e82 100644 --- a/multisig/tests/multisig_blackbox_test.rs +++ b/multisig/tests/multisig_blackbox_test.rs @@ -1,34 +1,16 @@ -#![allow(unused)] - -use std::ops::Add; - -use bridge_proxy::{config::ProxyTrait as _, ProxyTrait as _}; -use esdt_safe::{EsdtSafe, ProxyTrait as _}; - -use multisig::__endpoints_5__::multi_transfer_esdt_address; use multiversx_sc::{ - api::{HandleConstraints, ManagedTypeApi}, - codec::{ - multi_types::{MultiValueVec, OptionalValue}, - Empty, - }, + codec::multi_types::OptionalValue, contract_base::ManagedSerializer, hex_literal::hex, - storage::mappers::SingleValue, + imports::MultiValue2, types::{ - Address, BigUint, CodeMetadata, EsdtLocalRole, ManagedAddress, ManagedBuffer, - ManagedByteArray, ManagedOption, ManagedType, ManagedVec, MultiValueEncoded, - ReturnsNewManagedAddress, ReturnsResult, TestAddress, TestSCAddress, TestTokenIdentifier, - TokenIdentifier, + Address, BigUint, EsdtLocalRole, ManagedAddress, ManagedBuffer, ManagedByteArray, + ManagedOption, ManagedVec, MultiValueEncoded, ReturnsResult, TestAddress, TestSCAddress, + TestTokenIdentifier, TokenIdentifier, }, }; -use multiversx_sc_modules::pause::ProxyTrait; use multiversx_sc_scenario::{ - api::{StaticApi, VMHooksApi, VMHooksApiBackend}, - imports::MxscPath, - scenario_format::interpret_trait::{InterpretableFrom, InterpreterContext}, - scenario_model::*, - ContractInfo, DebugApi, ExpectError, ExpectValue, ScenarioTxRun, ScenarioWorld, + api::StaticApi, imports::MxscPath, ExpectError, ExpectValue, ScenarioTxRun, ScenarioWorld, }; use eth_address::*; @@ -36,16 +18,11 @@ use sc_proxies::{ bridge_proxy_contract_proxy, bridged_tokens_wrapper_proxy, esdt_safe_proxy, multi_transfer_esdt_proxy, multisig_proxy, }; -use token_module::ProxyTrait as _; -use transaction::{ - transaction_status::TransactionStatus, CallData, EthTransaction, EthTxAsMultiValue, - TxBatchSplitInFields, -}; +use transaction::{transaction_status::TransactionStatus, CallData, EthTxAsMultiValue}; const WEGLD_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("WEGLD-123456"); const ETH_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("ETH-123456"); - -const USER_ETHEREUM_ADDRESS: &[u8] = b"0x0102030405060708091011121314151617181920"; +const NATIVE_TOKEN_ID: TestTokenIdentifier = TestTokenIdentifier::new("EGLD-123456"); const GAS_LIMIT: u64 = 100_000_000; const ETH_TX_GAS_LIMIT: u64 = 150_000; @@ -53,22 +30,28 @@ const ETH_TX_GAS_LIMIT: u64 = 150_000; const MULTISIG_CODE_PATH: MxscPath = MxscPath::new("output/multisig.mxsc.json"); const MULTI_TRANSFER_CODE_PATH: MxscPath = MxscPath::new("../multi-transfer-esdt/output/multi-transfer-esdt.mxsc.json"); +const MOCK_MULTI_TRANSFER_PATH_EXPR: MxscPath = MxscPath::new( + "../common/mock-contracts/mock-multi-transfer-esdt/output/mock-multi-transfer-esdt.mxsc.json", +); +const MOCK_ESDT_SAFE_PATH_EXPR: MxscPath = + MxscPath::new("../common/mock-contracts/mock-esdt-safe/output/mock-esdt-safe.mxsc.json"); const BRIDGE_PROXY_CODE_PATH: MxscPath = MxscPath::new("../bridge-proxy/output/bridge-proxy.mxsc.json"); const ESDT_SAFE_CODE_PATH: MxscPath = MxscPath::new("../esdt-safe/output/esdt-safe.mxsc.json"); const BRIDGED_TOKENS_WRAPPER_CODE_PATH: MxscPath = MxscPath::new("../bridged-tokens-wrapper/output/bridged-tokens-wrapper.mxsc.json"); const PRICE_AGGREGATOR_CODE_PATH: MxscPath = - MxscPath::new("../price-aggregator/price-aggregator.mxsc.json"); + MxscPath::new("../price-aggregator/multiversx-price-aggregator.mxsc.json"); const MULTISIG_ADDRESS: TestSCAddress = TestSCAddress::new("multisig"); const MULTI_TRANSFER_ADDRESS: TestSCAddress = TestSCAddress::new("multi-transfer"); +const MOCK_MULTI_TRANSFER_ADDRESS: TestSCAddress = TestSCAddress::new("mock-multi-transfer"); const BRIDGE_PROXY_ADDRESS: TestSCAddress = TestSCAddress::new("bridge-proxy"); const ESDT_SAFE_ADDRESS: TestSCAddress = TestSCAddress::new("esdt-safe"); +const MOCK_ESDT_SAFE_ADDRESS: TestSCAddress = TestSCAddress::new("mock-esdt-safe"); const BRIDGED_TOKENS_WRAPPER_ADDRESS: TestSCAddress = TestSCAddress::new("bridged-tokens-wrapper"); const PRICE_AGGREGATOR_ADDRESS: TestSCAddress = TestSCAddress::new("price-aggregator"); -const ORACLE_ADDRESS: TestAddress = TestAddress::new("oracle"); const OWNER_ADDRESS: TestAddress = TestAddress::new("owner"); const USER1_ADDRESS: TestAddress = TestAddress::new("user1"); const USER2_ADDRESS: TestAddress = TestAddress::new("user2"); @@ -76,11 +59,8 @@ const NON_BOARD_MEMEBER_ADDRESS: TestAddress = TestAddress::new("non-board-membe const RELAYER1_ADDRESS: TestAddress = TestAddress::new("relayer1"); const RELAYER2_ADDRESS: TestAddress = TestAddress::new("relayer2"); -const RANDOM_SC_ADDRESS: TestSCAddress = TestSCAddress::new("random-sc"); - const ESDT_SAFE_ETH_TX_GAS_LIMIT: u64 = 150_000; - -const BALANCE: &str = "2,000,000"; +const INITIAL_STAKE: u64 = 1_000u64; fn world() -> ScenarioWorld { let mut blockchain = ScenarioWorld::new(); @@ -98,17 +78,17 @@ fn world() -> ScenarioWorld { ); blockchain.register_contract( PRICE_AGGREGATOR_CODE_PATH, - fee_estimator_module::ContractBuilder, + mock_price_aggregator::ContractBuilder, ); + blockchain.register_contract( + MOCK_MULTI_TRANSFER_PATH_EXPR, + mock_multi_transfer_esdt::ContractBuilder, + ); + blockchain.register_contract(MOCK_ESDT_SAFE_PATH_EXPR, mock_esdt_safe::ContractBuilder); blockchain } -type MultiTransferContract = ContractInfo>; -type BridgeProxyContract = ContractInfo>; -type EsdtSafeContract = ContractInfo>; -type BridgedTokensWrapperContract = ContractInfo>; - struct MultiTransferTestState { world: ScenarioWorld, } @@ -122,21 +102,26 @@ impl MultiTransferTestState { .nonce(1) .esdt_balance(WEGLD_TOKEN_ID, 1001u64) .esdt_balance(ETH_TOKEN_ID, 1001u64) + .esdt_balance(NATIVE_TOKEN_ID, 100_000u64) .account(USER1_ADDRESS) + .esdt_balance(WEGLD_TOKEN_ID, 100000u64) + .nonce(1) + .account(USER2_ADDRESS) + .esdt_balance(WEGLD_TOKEN_ID, 1000u64) .nonce(1) .account(RELAYER1_ADDRESS) .nonce(1) - .balance(1_000u64) + .balance(2_000u64) .account(RELAYER2_ADDRESS) .nonce(1) - .balance(1_000u64) + .balance(2_000u64) .account(NON_BOARD_MEMEBER_ADDRESS) .nonce(1); - let roles = vec![ - "ESDTRoleLocalMint".to_string(), - "ESDTRoleLocalBurn".to_string(), - ]; + world + .account(PRICE_AGGREGATOR_ADDRESS) + .nonce(1) + .code(PRICE_AGGREGATOR_CODE_PATH); Self { world } } @@ -144,8 +129,8 @@ impl MultiTransferTestState { fn multisig_deploy(&mut self) -> &mut Self { let mut board: MultiValueEncoded> = MultiValueEncoded::new(); - board.push(ManagedAddress::from(RELAYER1_ADDRESS.eval_to_array())); - board.push(ManagedAddress::from(RELAYER2_ADDRESS.eval_to_array())); + board.push(RELAYER1_ADDRESS.to_managed_address()); + board.push(RELAYER2_ADDRESS.to_managed_address()); self.world .tx() .from(OWNER_ADDRESS) @@ -180,6 +165,19 @@ impl MultiTransferTestState { self } + fn mock_multi_transfer_deploy(&mut self) -> &mut Self { + self.world + .tx() + .from(MULTISIG_ADDRESS) + .typed(multi_transfer_esdt_proxy::MultiTransferEsdtProxy) + .init() + .code(MOCK_MULTI_TRANSFER_PATH_EXPR) + .new_address(MOCK_MULTI_TRANSFER_ADDRESS) + .run(); + + self + } + fn bridged_tokens_wrapper_deploy(&mut self) -> &mut Self { self.world .tx() @@ -217,6 +215,17 @@ impl MultiTransferTestState { .run(); } + fn mock_safe_deploy(&mut self) { + self.world + .tx() + .from(MULTISIG_ADDRESS) + .typed(esdt_safe_proxy::EsdtSafeProxy) + .init(ETH_TX_GAS_LIMIT) + .code(MOCK_ESDT_SAFE_PATH_EXPR) + .new_address(MOCK_ESDT_SAFE_ADDRESS) + .run(); + } + fn config_multisig(&mut self) { self.world .tx() @@ -252,6 +261,29 @@ impl MultiTransferTestState { ) .run(); + self.world.set_esdt_balance( + MULTISIG_ADDRESS, + b"EGLD-123456", + BigUint::from(100_000_000_000u64), + ); + + self.world + .tx() + .from(MULTISIG_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .typed(esdt_safe_proxy::EsdtSafeProxy) + .add_token_to_whitelist( + TokenIdentifier::from_esdt_bytes("EGLD-123456"), + "EGLD", + false, + true, + BigUint::zero(), + BigUint::zero(), + BigUint::zero(), + OptionalValue::Some(BigUint::from(ESDT_SAFE_ETH_TX_GAS_LIMIT)), + ) + .run(); + self.world .tx() .from(OWNER_ADDRESS) @@ -342,18 +374,174 @@ impl MultiTransferTestState { .to_vec() .contains(&RELAYER2_ADDRESS.to_managed_address())); } + + fn deploy_contracts_config(&mut self) { + self.multisig_deploy(); + self.safe_deploy(); + self.multi_transfer_deploy(); + self.bridge_proxy_deploy(); + self.bridged_tokens_wrapper_deploy(); + self.config_multisig(); + } + + fn setup_before_distribute_fees(&mut self) { + self.world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .init_supply_mint_burn_esdt_safe(WEGLD_TOKEN_ID, 10000u64, 1000u64) + .run(); + + self.world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .change_multiversx_to_eth_gas_limit(10u64) + .run(); + + self.world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .change_default_price_per_gas_unit(WEGLD_TOKEN_ID, 1u64) + .run(); + + self.world + .tx() + .from(USER1_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .typed(esdt_safe_proxy::EsdtSafeProxy) + .create_transaction( + EthAddress::zero(), + OptionalValue::None::>, + ) + .single_esdt( + &TokenIdentifier::from(WEGLD_TOKEN_ID), + 0, + &BigUint::from(100u64), + ) + .run(); + + self.world + .tx() + .from(USER2_ADDRESS) + .to(ESDT_SAFE_ADDRESS) + .typed(esdt_safe_proxy::EsdtSafeProxy) + .create_transaction( + EthAddress::zero(), + OptionalValue::None::>, + ) + .single_esdt( + &TokenIdentifier::from(WEGLD_TOKEN_ID), + 0, + &BigUint::from(100u64), + ) + .run(); + } + + fn check_distributed_fees( + &mut self, + accumulated_fees: u64, + user1_amount: u64, + user2_amount: u64, + ) { + self.world + .query() + .to(ESDT_SAFE_ADDRESS) + .typed(esdt_safe_proxy::EsdtSafeProxy) + .accumulated_transaction_fees(WEGLD_TOKEN_ID) + .returns(ExpectValue(accumulated_fees)) + .run(); + + self.world + .check_account(USER1_ADDRESS) + .esdt_balance(WEGLD_TOKEN_ID, user1_amount); + + self.world + .check_account(USER2_ADDRESS) + .esdt_balance(WEGLD_TOKEN_ID, user2_amount); + } + + fn stake(&mut self, address: TestAddress, amount: u64) -> &mut Self { + self.world + .tx() + .from(address) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .stake() + .egld(amount) + .run(); + self + } + + fn unstake(&mut self, address: TestAddress, amount: u64) -> &mut Self { + self.world + .tx() + .from(address) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .unstake(amount) + .run(); + self + } + + fn unstake_error(&mut self, address: TestAddress, amount: u64, error: &str) -> &mut Self { + self.world + .tx() + .from(address) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .unstake(amount) + .returns(ExpectError(4u64, error)) + .run(); + self + } + + fn check_stake(&mut self, address: TestAddress, expected_amount: u64) { + let amount = self + .world + .query() + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .amount_staked(address.to_managed_address()) + .returns(ReturnsResult) + .run(); + assert_eq!(amount, BigUint::from(expected_amount)); + } + + fn assert_board_member_count(&mut self, expected_count: usize) { + self.world + .query() + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .num_board_members() + .returns(ExpectValue(expected_count)) + .run(); + } + + fn slash_amount(&mut self) -> u64 { + let slash_amount = self + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .slash_amount() + .returns(ReturnsResult) + .run(); + + slash_amount.to_u64().unwrap() + } } #[test] fn config_test() { let mut state = MultiTransferTestState::new(); - state.multisig_deploy(); - state.safe_deploy(); - state.multi_transfer_deploy(); - state.bridge_proxy_deploy(); - state.bridged_tokens_wrapper_deploy(); - state.config_multisig(); + state.deploy_contracts_config(); } #[test] @@ -361,18 +549,13 @@ fn ethereum_to_multiversx_call_data_empty_test() { let mut state = MultiTransferTestState::new(); let token_amount = BigUint::from(76_000_000u64); - state.multisig_deploy(); - state.safe_deploy(); - state.multi_transfer_deploy(); - state.bridge_proxy_deploy(); - state.bridged_tokens_wrapper_deploy(); - state.config_multisig(); + state.deploy_contracts_config(); let eth_tx = EthTxAsMultiValue::::from(( EthAddress { raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), }, - ManagedAddress::from(USER1_ADDRESS.eval_to_array()), + USER1_ADDRESS.to_managed_address(), TokenIdentifier::from(WEGLD_TOKEN_ID), token_amount.clone(), 1u64, @@ -413,7 +596,7 @@ fn ethereum_to_multiversx_call_data_empty_test() { state .world .check_account(USER1_ADDRESS) - .esdt_balance(WEGLD_TOKEN_ID, token_amount.clone()); + .esdt_balance(WEGLD_TOKEN_ID, token_amount.clone() + 100000u64); } #[test] @@ -423,12 +606,7 @@ fn ethereum_to_multiversx_relayer_call_data_several_tx_test() { state.world.start_trace(); - state.multisig_deploy(); - state.safe_deploy(); - state.multi_transfer_deploy(); - state.bridge_proxy_deploy(); - state.bridged_tokens_wrapper_deploy(); - state.config_multisig(); + state.deploy_contracts_config(); let addr = Address::from_slice(b"erd1dyw7aysn0nwmuahvxnh2e0pm0kgjvs2gmfdxjgz3x0pet2nkvt8s7tkyrj"); @@ -536,18 +714,13 @@ fn ethereum_to_multiversx_relayer_query_test() { let token_amount = BigUint::from(76_000_000_000u64); state.world.start_trace(); - state.multisig_deploy(); - state.safe_deploy(); - state.multi_transfer_deploy(); - state.bridge_proxy_deploy(); - state.bridged_tokens_wrapper_deploy(); - state.config_multisig(); + state.deploy_contracts_config(); let eth_tx = EthTxAsMultiValue::::from(( EthAddress { raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), }, - ManagedAddress::from(USER1_ADDRESS.eval_to_array()), + USER1_ADDRESS.to_managed_address(), TokenIdentifier::from(WEGLD_TOKEN_ID), token_amount.clone(), 1u64, @@ -610,7 +783,7 @@ fn ethereum_to_multiversx_relayer_query_test() { state .world .check_account(USER1_ADDRESS) - .esdt_balance(WEGLD_TOKEN_ID, token_amount.clone()); + .esdt_balance(WEGLD_TOKEN_ID, token_amount.clone() + 100000u64); state .world @@ -623,15 +796,7 @@ fn ethereum_to_multiversx_relayer_query2_test() { let token_amount = BigUint::from(5_000u64); state.world.start_trace(); - state.multisig_deploy(); - state.safe_deploy(); - state.multi_transfer_deploy(); - state.bridge_proxy_deploy(); - state.bridged_tokens_wrapper_deploy(); - state.config_multisig(); - - let addr = - Address::from_slice(b"erd1dyw7aysn0nwmuahvxnh2e0pm0kgjvs2gmfdxjgz3x0pet2nkvt8s7tkyrj"); + state.deploy_contracts_config(); const ADDR: [u8; 32] = hex!("691dee92137cddbe76ec34eeacbc3b7d91264148da5a69205133c395aa7662cf"); @@ -711,12 +876,7 @@ fn ethereum_to_multiversx_tx_batch_ok_test() { let token_amount = BigUint::from(76_000_000_000u64); state.world.start_trace(); - state.multisig_deploy(); - state.safe_deploy(); - state.multi_transfer_deploy(); - state.bridge_proxy_deploy(); - state.bridged_tokens_wrapper_deploy(); - state.config_multisig(); + state.deploy_contracts_config(); let mut args = ManagedVec::new(); args.push(ManagedBuffer::from(&[5u8, 6u8])); @@ -736,7 +896,7 @@ fn ethereum_to_multiversx_tx_batch_ok_test() { EthAddress { raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), }, - ManagedAddress::from(USER1_ADDRESS.eval_to_array()), + USER1_ADDRESS.to_managed_address(), TokenIdentifier::from(WEGLD_TOKEN_ID), token_amount.clone(), 1u64, @@ -747,7 +907,7 @@ fn ethereum_to_multiversx_tx_batch_ok_test() { EthAddress { raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), }, - ManagedAddress::from(USER1_ADDRESS.eval_to_array()), + USER1_ADDRESS.to_managed_address(), TokenIdentifier::from(ETH_TOKEN_ID), token_amount.clone(), 2u64, @@ -789,7 +949,7 @@ fn ethereum_to_multiversx_tx_batch_ok_test() { state .world .check_account(USER1_ADDRESS) - .esdt_balance(WEGLD_TOKEN_ID, token_amount.clone()) + .esdt_balance(WEGLD_TOKEN_ID, token_amount.clone() + 100000u64) .esdt_balance(ETH_TOKEN_ID, token_amount.clone()); state.world.write_scenario_trace( @@ -802,12 +962,7 @@ fn ethereum_to_multiversx_tx_batch_rejected_test() { let mut state = MultiTransferTestState::new(); let over_the_limit_token_amount = BigUint::from(101_000_000_000u64); - state.multisig_deploy(); - state.safe_deploy(); - state.multi_transfer_deploy(); - state.bridge_proxy_deploy(); - state.bridged_tokens_wrapper_deploy(); - state.config_multisig(); + state.deploy_contracts_config(); let mut args = ManagedVec::new(); args.push(ManagedBuffer::from(&[5u8])); @@ -825,7 +980,7 @@ fn ethereum_to_multiversx_tx_batch_rejected_test() { EthAddress { raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), }, - ManagedAddress::from(BRIDGE_PROXY_ADDRESS.eval_to_array()), + BRIDGE_PROXY_ADDRESS.to_managed_address(), TokenIdentifier::from(WEGLD_TOKEN_ID), over_the_limit_token_amount.clone(), 1u64, @@ -836,7 +991,7 @@ fn ethereum_to_multiversx_tx_batch_rejected_test() { EthAddress { raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), }, - ManagedAddress::from(BRIDGE_PROXY_ADDRESS.eval_to_array()), + BRIDGE_PROXY_ADDRESS.to_managed_address(), TokenIdentifier::from(ETH_TOKEN_ID), over_the_limit_token_amount.clone(), 2u64, @@ -896,23 +1051,100 @@ fn ethereum_to_multiversx_tx_batch_rejected_test() { .run(); } +#[test] +fn init_test() { + let mut state = MultiTransferTestState::new(); + + let mut board: MultiValueEncoded> = + MultiValueEncoded::new(); + board.push(RELAYER1_ADDRESS.to_managed_address()); + board.push(RELAYER2_ADDRESS.to_managed_address()); + state + .world + .tx() + .from(OWNER_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .init( + ESDT_SAFE_ADDRESS, + MULTI_TRANSFER_ADDRESS, + BRIDGE_PROXY_ADDRESS, + BRIDGED_TOKENS_WRAPPER_ADDRESS, + PRICE_AGGREGATOR_ADDRESS, + 1_000u64, + 5000u64, + 2usize, + board.clone(), + ) + .code(MULTISIG_CODE_PATH) + .new_address(MULTISIG_ADDRESS) + .returns(ExpectError( + 4, + "slash amount must be less than or equal to required stake", + )) + .run(); + + let mut board2: MultiValueEncoded> = + MultiValueEncoded::new(); + board2.push(RELAYER1_ADDRESS.to_managed_address()); + board2.push(RELAYER1_ADDRESS.to_managed_address()); + let multisig2 = TestSCAddress::new("multisig2"); + state + .world + .tx() + .from(OWNER_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .init( + ESDT_SAFE_ADDRESS, + MULTI_TRANSFER_ADDRESS, + BRIDGE_PROXY_ADDRESS, + BRIDGED_TOKENS_WRAPPER_ADDRESS, + PRICE_AGGREGATOR_ADDRESS, + 1_000u64, + 500u64, + 2usize, + board2.clone(), + ) + .code(MULTISIG_CODE_PATH) + .new_address(multisig2) + .returns(ExpectError(4, "duplicate board member")) + .run(); +} + +#[test] +fn upgrade_test() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .upgrade( + ESDT_SAFE_ADDRESS, + MULTI_TRANSFER_ADDRESS, + BRIDGE_PROXY_ADDRESS, + BRIDGED_TOKENS_WRAPPER_ADDRESS, + PRICE_AGGREGATOR_ADDRESS, + ) + .code(MULTISIG_CODE_PATH) + .run(); +} + #[test] fn multisig_non_board_member_interaction_test() { let mut state = MultiTransferTestState::new(); let token_amount = BigUint::from(76_000_000_000u64); - state.multisig_deploy(); - state.safe_deploy(); - state.multi_transfer_deploy(); - state.bridge_proxy_deploy(); - state.bridged_tokens_wrapper_deploy(); - state.config_multisig(); + state.deploy_contracts_config(); let eth_tx = EthTxAsMultiValue::::from(( EthAddress { raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), }, - ManagedAddress::from(USER1_ADDRESS.eval_to_array()), + USER1_ADDRESS.to_managed_address(), TokenIdentifier::from(WEGLD_TOKEN_ID), token_amount.clone(), 1u64, @@ -952,18 +1184,13 @@ fn multisig_insuficient_signatures_test() { let mut state = MultiTransferTestState::new(); let token_amount = BigUint::from(76_000_000_000u64); - state.multisig_deploy(); - state.safe_deploy(); - state.multi_transfer_deploy(); - state.bridge_proxy_deploy(); - state.bridged_tokens_wrapper_deploy(); - state.config_multisig(); + state.deploy_contracts_config(); let eth_tx = EthTxAsMultiValue::::from(( EthAddress { raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), }, - ManagedAddress::from(USER1_ADDRESS.eval_to_array()), + USER1_ADDRESS.to_managed_address(), TokenIdentifier::from(WEGLD_TOKEN_ID), token_amount.clone(), 1u64, @@ -999,18 +1226,13 @@ fn multisig_non_board_member_sign_test() { let mut state = MultiTransferTestState::new(); let token_amount = BigUint::from(76_000_000_000u64); - state.multisig_deploy(); - state.safe_deploy(); - state.multi_transfer_deploy(); - state.bridge_proxy_deploy(); - state.bridged_tokens_wrapper_deploy(); - state.config_multisig(); + state.deploy_contracts_config(); let eth_tx = EthTxAsMultiValue::::from(( EthAddress { raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), }, - ManagedAddress::from(USER1_ADDRESS.eval_to_array()), + USER1_ADDRESS.to_managed_address(), TokenIdentifier::from(WEGLD_TOKEN_ID), token_amount.clone(), 1u64, @@ -1050,3 +1272,1205 @@ fn multisig_non_board_member_sign_test() { .returns(ExpectError(4, "quorum has not been reached")) .run(); } + +#[test] +fn test_distribute_fees_from_child_contracts_success() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let dest_address1 = USER1_ADDRESS.to_managed_address(); + let dest_address2 = USER2_ADDRESS.to_managed_address(); + + let percentage1 = 6000; + let percentage2 = 4000; + + let mut dest_address_percentage_pairs: MultiValueEncoded< + StaticApi, + MultiValue2, u32>, + > = MultiValueEncoded::new(); + + dest_address_percentage_pairs.push(MultiValue2::from((dest_address1.clone(), percentage1))); + dest_address_percentage_pairs.push(MultiValue2::from((dest_address2.clone(), percentage2))); + + state.setup_before_distribute_fees(); + + state.check_distributed_fees(20u64, 99900, 900); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .distribute_fees_from_child_contracts(dest_address_percentage_pairs) + .run(); + + state.check_distributed_fees(0u64, 99912, 908); +} + +#[test] +fn test_distribute_fees_from_child_contracts_invalid_percentage_sum() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + let dest_address1 = USER1_ADDRESS.to_managed_address(); + let dest_address2 = USER2_ADDRESS.to_managed_address(); + + let percentage1 = 5000; + let percentage2 = 4000; + + let mut dest_address_percentage_pairs: MultiValueEncoded< + StaticApi, + MultiValue2, u32>, + > = MultiValueEncoded::new(); + + dest_address_percentage_pairs.push(MultiValue2::from((dest_address1.clone(), percentage1))); + dest_address_percentage_pairs.push(MultiValue2::from((dest_address2.clone(), percentage2))); + + state.setup_before_distribute_fees(); + + state.check_distributed_fees(20u64, 99900, 900); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .distribute_fees_from_child_contracts(dest_address_percentage_pairs) + .returns(ExpectError(4, "Percentages do not add up to 100%")) + .run(); + + state.check_distributed_fees(20u64, 99900, 900); +} + +#[test] +fn test_distribute_fees_from_child_contracts_with_sc_address() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let dest_address1 = USER1_ADDRESS.to_managed_address(); + let dest_address2 = ESDT_SAFE_ADDRESS.to_managed_address(); + let percentage1 = 6000; + let percentage2 = 4000; + + let mut dest_address_percentage_pairs: MultiValueEncoded< + StaticApi, + MultiValue2, u32>, + > = MultiValueEncoded::new(); + + dest_address_percentage_pairs.push(MultiValue2::from((dest_address1.clone(), percentage1))); + dest_address_percentage_pairs.push(MultiValue2::from((dest_address2.clone(), percentage2))); + + state.setup_before_distribute_fees(); + + state.check_distributed_fees(20u64, 99900, 900); + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .distribute_fees_from_child_contracts(dest_address_percentage_pairs) + .returns(ExpectError( + 4, + "Cannot transfer to smart contract dest_address", + )) + .run(); + + state.check_distributed_fees(20u64, 99900, 900); +} + +#[test] +fn test_unstake_successful_board_member() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + let stake_amount = 1_000u64; + + state.stake(RELAYER1_ADDRESS, stake_amount); + + state.check_stake(RELAYER1_ADDRESS, stake_amount + INITIAL_STAKE); + + let unstake_amount = 500u64; + state.unstake(RELAYER1_ADDRESS, unstake_amount); + + state.check_stake(RELAYER1_ADDRESS, stake_amount + INITIAL_STAKE - 500u64); + + state + .world + .check_account(RELAYER1_ADDRESS) + .balance(unstake_amount); +} + +#[test] +fn test_unstake_more_than_staked_amount() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + state.unstake_error( + RELAYER1_ADDRESS, + 1500u64, + "can't unstake more than amount staked", + ); + + state.check_stake(RELAYER1_ADDRESS, INITIAL_STAKE); +} + +#[test] +fn test_unstake_below_required_stake_board_member() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + state.unstake_error( + RELAYER1_ADDRESS, + 600u64, + "can't unstake, must keep minimum amount as insurance", + ); + + state.check_stake(RELAYER1_ADDRESS, 1000u64); +} + +#[test] +fn test_unstake_updates_amount_staked_correctly() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + let stake_amount_relayer1 = 1_000u64; + state.stake(RELAYER1_ADDRESS, stake_amount_relayer1); + + let stake_amount_relayer2 = 1_000u64; + state.stake(RELAYER2_ADDRESS, stake_amount_relayer2); + + let unstake_amount_relayer1 = 200u64; + state.unstake(RELAYER1_ADDRESS, unstake_amount_relayer1); + + state.check_stake( + RELAYER1_ADDRESS, + INITIAL_STAKE + stake_amount_relayer1 - unstake_amount_relayer1, + ); + state.check_stake(RELAYER2_ADDRESS, INITIAL_STAKE + stake_amount_relayer2); +} + +#[test] +fn test_propose_esdt_safe_set_current_transaction_batch_status_batch_empty() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let statuses: MultiValueEncoded = + MultiValueEncoded::from(ManagedVec::from_single_item(TransactionStatus::Pending)); + + state + .world + .tx() + .from(RELAYER1_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .propose_esdt_safe_set_current_transaction_batch_status(1u64, statuses) + .returns(ExpectError(4, "Current batch is empty")) + .run(); +} + +#[test] +fn test_init_supply_from_child_contract_success() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let token_id = NATIVE_TOKEN_ID; + let amount = BigUint::from(1_000u64); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .init_supply_from_child_contract(TokenIdentifier::from(token_id), amount.clone()) + .single_esdt(&TokenIdentifier::from(token_id), 0, &amount.clone()) + .run(); + + state + .world + .check_account(ESDT_SAFE_ADDRESS) + .esdt_balance(token_id, amount.clone()); + + state + .world + .query() + .to(ESDT_SAFE_ADDRESS) + .typed(esdt_safe_proxy::EsdtSafeProxy) + .total_balances(TokenIdentifier::from(token_id)) + .returns(ExpectValue(amount.clone())) + .run(); +} + +#[test] +fn test_add_unprocessed_refund_tx_to_batch_success() { + let mut state = MultiTransferTestState::new(); + + let mut board: MultiValueEncoded> = + MultiValueEncoded::new(); + board.push(RELAYER1_ADDRESS.to_managed_address()); + board.push(RELAYER2_ADDRESS.to_managed_address()); + state + .world + .tx() + .from(OWNER_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .init( + ESDT_SAFE_ADDRESS, + MOCK_MULTI_TRANSFER_ADDRESS, + BRIDGE_PROXY_ADDRESS, + BRIDGED_TOKENS_WRAPPER_ADDRESS, + PRICE_AGGREGATOR_ADDRESS, + 1_000u64, + 500u64, + 2usize, + board, + ) + .code(MULTISIG_CODE_PATH) + .new_address(MULTISIG_ADDRESS) + .run(); + + state.mock_multi_transfer_deploy(); + + let tx_id = 1u64; + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .add_unprocessed_refund_tx_to_batch(tx_id) + .run(); +} + +#[test] +fn test_withdraw_slashed_amount_success() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let slashed_amount = state.slash_amount(); + + state + .world + .set_esdt_balance(MULTISIG_ADDRESS, WEGLD_TOKEN_ID.as_bytes(), slashed_amount); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .slash_board_member(RELAYER1_ADDRESS.to_managed_address()) + .run(); + + state.check_stake(RELAYER1_ADDRESS, INITIAL_STAKE - slashed_amount); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .withdraw_slashed_amount() + .run(); + + let remaining_slashed_amount = state.slash_amount(); + assert_eq!(remaining_slashed_amount, 500u64); +} + +#[test] +fn test_withdraw_refund_fees_for_ethereum_success() { + let mut state = MultiTransferTestState::new(); + + let mut board: MultiValueEncoded> = + MultiValueEncoded::new(); + board.push(RELAYER1_ADDRESS.to_managed_address()); + board.push(RELAYER2_ADDRESS.to_managed_address()); + state + .world + .tx() + .from(OWNER_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .init( + MOCK_ESDT_SAFE_ADDRESS, + MULTI_TRANSFER_ADDRESS, + BRIDGE_PROXY_ADDRESS, + BRIDGED_TOKENS_WRAPPER_ADDRESS, + PRICE_AGGREGATOR_ADDRESS, + 1_000u64, + 500u64, + 2usize, + board, + ) + .code(MULTISIG_CODE_PATH) + .new_address(MULTISIG_ADDRESS) + .run(); + state.mock_safe_deploy(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .withdraw_refund_fees_for_ethereum(TokenIdentifier::from(WEGLD_TOKEN_ID)) + .run(); +} + +#[test] +fn test_withdraw_transaction_fees_success() { + let mut state = MultiTransferTestState::new(); + + let mut board: MultiValueEncoded> = + MultiValueEncoded::new(); + board.push(RELAYER1_ADDRESS.to_managed_address()); + board.push(RELAYER2_ADDRESS.to_managed_address()); + state + .world + .tx() + .from(OWNER_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .init( + MOCK_ESDT_SAFE_ADDRESS, + MULTI_TRANSFER_ADDRESS, + BRIDGE_PROXY_ADDRESS, + BRIDGED_TOKENS_WRAPPER_ADDRESS, + PRICE_AGGREGATOR_ADDRESS, + 1_000u64, + 500u64, + 2usize, + board, + ) + .code(MULTISIG_CODE_PATH) + .new_address(MULTISIG_ADDRESS) + .run(); + state.mock_safe_deploy(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .withdraw_transaction_fees(TokenIdentifier::from(WEGLD_TOKEN_ID)) + .run(); +} + +#[test] +fn test_upgrade_child_contract_from_source_success() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let child_sc_address = MULTI_TRANSFER_ADDRESS.to_managed_address(); + + state + .world + .tx() + .from(MULTISIG_ADDRESS) + .typed(multi_transfer_esdt_proxy::MultiTransferEsdtProxy) + .init() + .code(MOCK_MULTI_TRANSFER_PATH_EXPR) + .new_address(MOCK_MULTI_TRANSFER_ADDRESS) + .run(); + + let init_args = MultiValueEncoded::new(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .upgrade_child_contract_from_source( + child_sc_address.clone(), + MOCK_MULTI_TRANSFER_ADDRESS, + false, + init_args.clone(), + ) + .run(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .upgrade_child_contract_from_source( + child_sc_address.clone(), + MOCK_MULTI_TRANSFER_ADDRESS, + true, + init_args.clone(), + ) + .run(); +} + +#[test] +fn test_add_board_member_success() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + state.assert_board_member_count(2usize); + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .add_board_member_endpoint(USER1_ADDRESS.to_managed_address()) + .run(); + + state.assert_board_member_count(3usize); +} + +#[test] +fn test_remove_user_success() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .add_board_member_endpoint(USER1_ADDRESS.to_managed_address()) + .run(); + + state.assert_board_member_count(3usize); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .remove_user(USER1_ADDRESS.to_managed_address()) + .run(); + + state.assert_board_member_count(2usize); +} + +#[test] +fn test_remove_user_cannot_remove_all() { + let mut state = MultiTransferTestState::new(); + + let mut board: MultiValueEncoded> = + MultiValueEncoded::new(); + board.push(RELAYER1_ADDRESS.to_managed_address()); + board.push(RELAYER2_ADDRESS.to_managed_address()); + state + .world + .tx() + .from(OWNER_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .init( + ESDT_SAFE_ADDRESS, + MULTI_TRANSFER_ADDRESS, + BRIDGE_PROXY_ADDRESS, + BRIDGED_TOKENS_WRAPPER_ADDRESS, + PRICE_AGGREGATOR_ADDRESS, + 1_000u64, + 500u64, + 1usize, + board, + ) + .code(MULTISIG_CODE_PATH) + .new_address(MULTISIG_ADDRESS) + .run(); + state.safe_deploy(); + state.multi_transfer_deploy(); + state.bridge_proxy_deploy(); + state.bridged_tokens_wrapper_deploy(); + state.config_multisig(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .remove_user(RELAYER1_ADDRESS.to_managed_address()) + .run(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .remove_user(RELAYER2_ADDRESS.to_managed_address()) + .returns(ExpectError(4u64, "cannot remove all board members")) + .run(); +} + +#[test] +fn test_remove_user_quorum_exceed_board_size() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + state.assert_board_member_count(2usize); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .remove_user(RELAYER1_ADDRESS.to_managed_address()) + .returns(ExpectError(4u64, "quorum cannot exceed board size")) + .run(); + + state.assert_board_member_count(2usize); +} + +#[test] +fn test_change_quorum_success() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + state.assert_board_member_count(2usize); + + let new_quorum = 1usize; + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .change_quorum(new_quorum) + .run(); + + let updated_quorum: usize = state + .world + .query() + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .quorum() + .returns(ReturnsResult) + .run(); + + assert_eq!(updated_quorum, new_quorum); +} + +#[test] +fn test_add_mapping_success() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let token_id = TokenIdentifier::from(WEGLD_TOKEN_ID); + + let erc20_address = EthAddress::zero(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .add_mapping(erc20_address.clone(), token_id.clone()) + .run(); + + state + .world + .query() + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .erc20_address_for_token_id(token_id.clone()) + .returns(ExpectValue(erc20_address.clone())) + .run(); + + state + .world + .query() + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .token_id_for_erc20_address(erc20_address.clone()) + .returns(ExpectValue(token_id.clone())) + .run(); +} + +#[test] +fn test_add_mapping_token_id_already_mapped() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let token_id = TokenIdentifier::from(WEGLD_TOKEN_ID); + + let erc20_address = EthAddress::zero(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .add_mapping(erc20_address.clone(), token_id.clone()) + .run(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .add_mapping(erc20_address.clone(), token_id.clone()) + .returns(ExpectError(4u64, "Mapping already exists for token ID")) + .run(); +} + +#[test] +fn test_add_mapping_erc20_address_already_mapped() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let token_id = TokenIdentifier::from(WEGLD_TOKEN_ID); + + let erc20_address = EthAddress::zero(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .add_mapping(erc20_address.clone(), token_id.clone()) + .run(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .add_mapping(erc20_address.clone(), TokenIdentifier::from(ETH_TOKEN_ID)) + .returns(ExpectError(4u64, "Mapping already exists for ERC20 token")) + .run(); +} + +#[test] +fn test_clear_mapping_success() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let token_id = TokenIdentifier::from(WEGLD_TOKEN_ID); + + let erc20_address = EthAddress::zero(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .add_mapping(erc20_address.clone(), token_id.clone()) + .run(); + state + .world + .query() + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .erc20_address_for_token_id(token_id.clone()) + .returns(ExpectValue(erc20_address.clone())) + .run(); + + state + .world + .query() + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .token_id_for_erc20_address(erc20_address.clone()) + .returns(ExpectValue(token_id.clone())) + .run(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .clear_mapping(erc20_address.clone(), token_id.clone()) + .run(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .clear_mapping(erc20_address.clone(), token_id.clone()) + .returns(ExpectError(4u64, "Mapping does not exist for ERC20 token")) + .run(); + + let erc20_address2 = EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080900"), + }; + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .add_mapping(erc20_address2, token_id.clone()) + .run(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .clear_mapping(erc20_address.clone(), token_id.clone()) + .returns(ExpectError(4u64, "Mapping does not exist for token id")) + .run(); +} + +#[test] +fn test_clear_mapping_invalid_mapping() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let token_id1 = TokenIdentifier::from(WEGLD_TOKEN_ID); + let token_id2 = TokenIdentifier::from("OTHER-123456"); + + let erc20_address1 = EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080910"), + }; + + let erc20_address2 = EthAddress { + raw_addr: ManagedByteArray::new_from_bytes(b"01020304050607080900"), + }; + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .add_mapping(erc20_address1.clone(), token_id1.clone()) + .run(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .add_mapping(erc20_address2.clone(), token_id2.clone()) + .run(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .clear_mapping(erc20_address1.clone(), token_id2.clone()) + .returns(ExpectError(4, "Invalid mapping")) + .run(); +} + +#[test] +fn test_pause_unpause_esdt_safe() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let is_paused_before: bool = state + .world + .query() + .to(ESDT_SAFE_ADDRESS) + .typed(esdt_safe_proxy::EsdtSafeProxy) + .paused_status() + .returns(ReturnsResult) + .run(); + + assert!(!is_paused_before); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .pause_esdt_safe() + .run(); + + let is_paused_after: bool = state + .world + .query() + .to(ESDT_SAFE_ADDRESS) + .typed(esdt_safe_proxy::EsdtSafeProxy) + .paused_status() + .returns(ReturnsResult) + .run(); + + assert!(is_paused_after); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .unpause_esdt_safe() + .run(); + + let is_paused_after_unpause: bool = state + .world + .query() + .to(ESDT_SAFE_ADDRESS) + .typed(esdt_safe_proxy::EsdtSafeProxy) + .paused_status() + .returns(ReturnsResult) + .run(); + + assert!(!is_paused_after_unpause); +} + +#[test] +fn test_init_supply_functions_success() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let amount = BigUint::from(100u64); + + state + .world + .set_esdt_balance(OWNER_ADDRESS, NATIVE_TOKEN_ID.as_bytes(), amount.clone()); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .init_supply_esdt_safe(NATIVE_TOKEN_ID, amount.clone()) + .single_esdt(&TokenIdentifier::from(NATIVE_TOKEN_ID), 0, &amount) + .run(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .init_supply_mint_burn_esdt_safe(ETH_TOKEN_ID, amount.clone(), amount.clone()) + .run(); +} + +#[test] +fn test_pause_unpause_proxy() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let is_paused_before: bool = state + .world + .query() + .to(BRIDGE_PROXY_ADDRESS) + .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) + .paused_status() + .returns(ReturnsResult) + .run(); + + assert!(!is_paused_before); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .pause_proxy() + .run(); + + let is_paused_after: bool = state + .world + .query() + .to(BRIDGE_PROXY_ADDRESS) + .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) + .paused_status() + .returns(ReturnsResult) + .run(); + + assert!(is_paused_after); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .unpause_proxy() + .run(); + + let is_paused_after_unpause: bool = state + .world + .query() + .to(BRIDGE_PROXY_ADDRESS) + .typed(bridge_proxy_contract_proxy::BridgeProxyContractProxy) + .paused_status() + .returns(ReturnsResult) + .run(); + + assert!(!is_paused_after_unpause); +} + +#[test] +fn test_change_esdt_safe_settings() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let esdt_safe_address = ESDT_SAFE_ADDRESS; + + let new_gas_limit = BigUint::from(5_000_000u64); + let token_id = TokenIdentifier::from(WEGLD_TOKEN_ID); + let new_price_per_gas_unit = BigUint::from(100u64); + let new_ticker = ManagedBuffer::from("WEGLD"); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .change_multiversx_to_eth_gas_limit(new_gas_limit.clone()) + .run(); + + let updated_gas_limit = state + .world + .query() + .to(esdt_safe_address) + .typed(esdt_safe_proxy::EsdtSafeProxy) + .eth_tx_gas_limit() + .returns(ReturnsResult) + .run(); + + assert_eq!(updated_gas_limit, new_gas_limit); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .change_default_price_per_gas_unit(token_id.clone(), new_price_per_gas_unit.clone()) + .run(); + + let updated_price_per_gas_unit = state + .world + .query() + .to(esdt_safe_address) + .typed(esdt_safe_proxy::EsdtSafeProxy) + .default_price_per_gas_unit(token_id.clone()) + .returns(ReturnsResult) + .run(); + + assert_eq!(updated_price_per_gas_unit, new_price_per_gas_unit); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .change_token_ticker(token_id.clone(), new_ticker.clone()) + .run(); +} + +#[test] +fn test_esdt_safe_whitelist_management() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let esdt_safe_address = ESDT_SAFE_ADDRESS; + + let token_id = TokenIdentifier::from("TEST-123456"); + let ticker = ManagedBuffer::from("TEST"); + let mint_burn_allowed = true; + let is_native_token = false; + let total_balance = BigUint::from(0u64); + let mint_balance = BigUint::from(500_000u64); + let burn_balance = BigUint::from(200_000u64); + let opt_default_price_per_gas_unit = OptionalValue::Some(BigUint::from(100u64)); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .esdt_safe_add_token_to_whitelist( + &token_id, + ticker.clone(), + mint_burn_allowed, + is_native_token, + &total_balance, + &mint_balance, + &burn_balance, + opt_default_price_per_gas_unit.clone(), + ) + .run(); + + let tokens_whitelisted = state + .world + .query() + .to(esdt_safe_address) + .typed(esdt_safe_proxy::EsdtSafeProxy) + .token_whitelist() + .returns(ReturnsResult) + .run(); + + assert!(tokens_whitelisted.to_vec().contains(&token_id)); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .esdt_safe_remove_token_from_whitelist(token_id.clone()) + .run(); + + let tokens_whitelisted_after = state + .world + .query() + .to(esdt_safe_address) + .typed(esdt_safe_proxy::EsdtSafeProxy) + .token_whitelist() + .returns(ReturnsResult) + .run(); + + assert!(!tokens_whitelisted_after.to_vec().contains(&token_id)); +} + +#[test] +fn test_esdt_safe_settings_management() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let esdt_safe_address = ESDT_SAFE_ADDRESS; + + let new_max_tx_batch_size = 100usize; + let new_max_tx_batch_block_duration = 600u64; + let token_id = TokenIdentifier::from("TEST-123456"); + let max_bridged_amount = BigUint::from(1_000_000u64); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .esdt_safe_set_max_tx_batch_size(new_max_tx_batch_size) + .run(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .esdt_safe_set_max_tx_batch_block_duration(new_max_tx_batch_block_duration) + .run(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .esdt_safe_set_max_bridged_amount_for_token(token_id.clone(), max_bridged_amount.clone()) + .run(); + + let updated_max_bridged_amount = state + .world + .query() + .to(esdt_safe_address) + .typed(esdt_safe_proxy::EsdtSafeProxy) + .max_bridged_amount(token_id.clone()) + .returns(ReturnsResult) + .run(); + + assert_eq!(updated_max_bridged_amount, max_bridged_amount); +} + +#[test] +fn test_multi_transfer_esdt_settings_management() { + let mut state = MultiTransferTestState::new(); + + state.deploy_contracts_config(); + + let token_id = TokenIdentifier::from("TEST-123456"); + let max_bridged_amount = BigUint::from(1_000_000u64); + let new_max_refund_tx_batch_size = 100usize; + let new_max_refund_tx_batch_block_duration = 600u64; + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .multi_transfer_esdt_set_max_bridged_amount_for_token( + token_id.clone(), + max_bridged_amount.clone(), + ) + .run(); + + let updated_max_bridged_amount = state + .world + .query() + .to(MULTI_TRANSFER_ADDRESS) + .typed(multi_transfer_esdt_proxy::MultiTransferEsdtProxy) + .max_bridged_amount(token_id.clone()) + .returns(ReturnsResult) + .run(); + + assert_eq!(updated_max_bridged_amount, max_bridged_amount); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .multi_transfer_esdt_set_max_refund_tx_batch_size(new_max_refund_tx_batch_size) + .run(); + + state + .world + .tx() + .from(OWNER_ADDRESS) + .to(MULTISIG_ADDRESS) + .typed(multisig_proxy::MultisigProxy) + .multi_transfer_esdt_set_max_refund_tx_batch_block_duration( + new_max_refund_tx_batch_block_duration, + ) + .run(); +}