diff --git a/.envrc b/.envrc new file mode 100644 index 000000000..6424f5862 --- /dev/null +++ b/.envrc @@ -0,0 +1,10 @@ +# direnv configuration for automatic cargo wrapper activation +# Install direnv: https://direnv.net/ +# Then run: direnv allow + +# Add interactor scripts to PATH (so our cargo wrapper intercepts cargo test) +PATH_add interactor/scripts + +# Set workspace root +export WORKSPACE_ROOT="$(pwd)" + diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 9e064d76b..47624ca11 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -24,6 +24,84 @@ jobs: with: rust-toolchain: 1.87 coverage-args: --ignore-filename-regex='/.cargo/git' --output ./coverage.md - enable-interactor-tests: true + enable-interactor-tests: false secrets: token: ${{ secrets.GITHUB_TOKEN }} + + interactor-tests: + name: Interactor Tests + runs-on: ubuntu-latest + timeout-minutes: 120 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: 1.87 + target: wasm32-unknown-unknown + + - name: Install prerequisites + run: | + cargo install multiversx-sc-meta + + - name: Build contracts and generate proxies + run: | + sc-meta all build + sc-meta all proxy + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Setup Docker + run: | + sudo systemctl start docker + docker pull multiversx/chainsimulator + + - name: Run first interactor test suite + timeout-minutes: 60 + env: + PATH: ${{ github.workspace }}/interactor/scripts:${{ env.PATH }} + WORKSPACE_ROOT: ${{ github.workspace }} + MAX_TEST_CONCURRENCY: "2" + GITHUB_ACTIONS: "true" + run: | + cargo test --package rust-interact --test complete_flow_tests --all-features + + - name: Cleanup containers after first test suite + if: always() + run: | + # Clean up containers from first test suite before starting second + CONTAINERS=$(docker ps -a --filter "name=chain-sim-" --format "{{.ID}}") + if [ -n "$CONTAINERS" ]; then + echo "Cleaning up containers: $CONTAINERS" + docker stop $CONTAINERS + docker rm -f $CONTAINERS + fi + # Wait a moment for ports to be released + sleep 2 + + - name: Run second interactor test suite + timeout-minutes: 60 + env: + PATH: ${{ github.workspace }}/interactor/scripts:${{ env.PATH }} + WORKSPACE_ROOT: ${{ github.workspace }} + MAX_TEST_CONCURRENCY: "2" + GITHUB_ACTIONS: "true" + run: | + cargo test --package rust-interact --test mvx_esdt_safe_tests --all-features + + - name: Cleanup chain simulator containers + if: always() + run: | + # Remove only the per-test chain simulator containers created by the wrapper + CONTAINERS=$(docker ps -a --filter "name=chain-sim-" --format "{{.ID}}") + if [ -n "$CONTAINERS" ]; then + echo "Final cleanup of containers: $CONTAINERS" + docker stop $CONTAINERS + docker rm -f $CONTAINERS + fi + diff --git a/Cargo.lock b/Cargo.lock index 76e5b13fc..a387834f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2260,7 +2260,6 @@ dependencies = [ "proxies", "rstest", "serde", - "serial_test", "sov-esdt-safe", "structs", "toml 0.8.23", @@ -2333,15 +2332,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scc" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" -dependencies = [ - "sdd", -] - [[package]] name = "schannel" version = "0.1.28" @@ -2369,12 +2359,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "sdd" -version = "3.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" - [[package]] name = "security-framework" version = "2.11.1" @@ -2489,31 +2473,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serial_test" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" -dependencies = [ - "futures", - "log", - "once_cell", - "parking_lot", - "scc", - "serial_test_derive", -] - -[[package]] -name = "serial_test_derive" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "setup-phase" version = "0.1.0" diff --git a/common/common-interactor/src/common_sovereign_interactor.rs b/common/common-interactor/src/common_sovereign_interactor.rs index 20574526d..a7166381f 100644 --- a/common/common-interactor/src/common_sovereign_interactor.rs +++ b/common/common-interactor/src/common_sovereign_interactor.rs @@ -16,7 +16,6 @@ use common_test_setup::{ }; use multiversx_bls::{SecretKey, G1}; use multiversx_sc::{ - codec::num_bigint, imports::{ESDTSystemSCProxy, OptionalValue, UserBuiltinProxy}, types::{ Address, BigUint, CodeMetadata, ESDTSystemSCAddress, EgldOrEsdtTokenIdentifier, @@ -131,7 +130,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { match issue.token_ticker.as_str() { "MVX" => self.state().add_fungible_token(token.clone()), - "TRUSTED" => self.common_state().set_trusted_token(token_id.clone()), + "TRUSTED" => self.state().set_trusted_token(token.clone()), "FEE" => self.state().set_fee_token(token.clone()), "NFT" => self.state().add_nft_token(token.clone()), "SFT" => self.state().add_sft_token(token.clone()), @@ -207,18 +206,6 @@ pub trait CommonInteractorTrait: InteractorHelpers { ticker: &str, decimals: usize, ) { - if ticker == "FEE" && !self.common_state().fee_market_tokens.is_empty() { - let fee_token = self.retrieve_current_fee_token_for_wallet().await; - self.state().set_fee_token(fee_token); - return; - } - if ticker == "TRUSTED" && self.common_state().trusted_token.is_some() { - let trusted_token = self.retrieve_current_trusted_token_for_wallet().await; - self.state().set_trusted_token(trusted_token.clone()); - self.state() - .update_or_add_initial_wallet_token(trusted_token.clone()); - return; - } let amount = if matches!( token_type, EsdtTokenType::NonFungibleV2 | EsdtTokenType::DynamicNFT @@ -266,7 +253,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { .await; let new_address_bech32 = Bech32Address::from(&new_address); - self.common_state() + self.state() .set_sovereign_forge_sc_address(new_address_bech32.clone()); new_address @@ -298,7 +285,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { .await; let new_address_bech32 = Bech32Address::from(&new_address); - self.common_state() + self.state() .set_chain_factory_sc_address(new_address_bech32); } @@ -322,11 +309,10 @@ pub trait CommonInteractorTrait: InteractorHelpers { .await; let new_address_bech32 = Bech32Address::from(&new_address); - self.common_state() - .set_chain_config_sc_address(AddressInfo { - address: new_address_bech32, - chain_id, - }); + self.state().set_chain_config_sc_address(AddressInfo { + address: new_address_bech32, + chain_id, + }); } async fn deploy_template_contracts( @@ -426,11 +412,10 @@ pub trait CommonInteractorTrait: InteractorHelpers { .await; let new_address_bech32 = Bech32Address::from(&new_address); - self.common_state() - .set_header_verifier_address(AddressInfo { - address: new_address_bech32, - chain_id, - }); + self.state().set_header_verifier_address(AddressInfo { + address: new_address_bech32, + chain_id, + }); } async fn deploy_mvx_esdt_safe( @@ -460,7 +445,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { .await; let new_address_bech32 = Bech32Address::from(&new_address); - self.common_state() + self.state() .set_mvx_esdt_safe_contract_address(AddressInfo { address: new_address_bech32.clone(), chain_id, @@ -486,8 +471,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { .serialize() .expect("Failed to serialize BLS public key"); - self.common_state() - .add_bls_secret_key(shard, secret_key_bytes); + self.state().add_bls_secret_key(shard, secret_key_bytes); let bls_key_buffer = ManagedBuffer::::new_from_bytes(&public_key_bytes); @@ -527,7 +511,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { .await; let new_address_bech32 = Bech32Address::from(&new_address); - self.common_state().set_fee_market_address(AddressInfo { + self.state().set_fee_market_address(AddressInfo { address: new_address_bech32.clone(), chain_id, }); @@ -550,35 +534,12 @@ pub trait CommonInteractorTrait: InteractorHelpers { let new_address_bech32 = Bech32Address::from(&new_address); - self.common_state() + self.state() .set_testing_sc_address(new_address_bech32.clone()); println!("new testing sc address: {new_address_bech32}"); } - async fn deploy_and_complete_setup_phase( - &mut self, - deploy_cost: OptionalValue>, - optional_sov_config: OptionalValue>, - optional_esdt_safe_config: OptionalValue>, - ) { - let fee_struct = self.create_standard_fee(); - self.deploy_and_setup_common( - deploy_cost.clone(), - optional_sov_config, - optional_esdt_safe_config, - OptionalValue::Some(fee_struct), - ) - .await; - let fee_token_id = self.state().get_fee_token_id(); - let fee_token_fee_market = self.create_serializable_token(fee_token_id, 0u64); - self.common_state() - .set_fee_market_token_for_all_shards(fee_token_fee_market); - self.common_state().set_fee_status_for_all_shards(true); - self.common_state() - .set_mvx_egld_balance_for_all_shards(0u64); - } - async fn unpause_forge(&mut self, sovereign_forge_address: Address) { let bridge_owner = self.get_bridge_owner_for_shard(SHARD_0).clone(); @@ -593,7 +554,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { .await; } - async fn deploy_and_setup_common( + async fn deploy_and_complete_setup_phase( &mut self, deploy_cost: OptionalValue>, optional_sov_config: OptionalValue>, @@ -610,9 +571,9 @@ pub trait CommonInteractorTrait: InteractorHelpers { .await; self.unpause_forge(sovereign_forge_address.clone()).await; - let trusted_token = self.common_state().get_trusted_token(); + let trusted_token = self.state().get_trusted_token_string(); - self.register_trusted_token(initial_caller.clone(), trusted_token.as_str()) + self.register_trusted_token(initial_caller.clone(), &trusted_token) .await; for shard_id in 0..NUMBER_OF_SHARDS { @@ -683,11 +644,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { } async fn register_trusted_token(&mut self, caller: Address, trusted_token: &str) { - let forge_address = &self - .common_state() - .sovereign_forge_sc_address - .clone() - .unwrap(); + let forge_address = &self.state().sovereign_forge_sc_address.clone().unwrap(); self.interactor() .tx() @@ -710,7 +667,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { ) { let caller = self.get_sovereign_owner_for_shard(shard); let preferred_chain_id = Self::generate_random_chain_id(); - self.common_state().add_chain_id(preferred_chain_id.clone()); + self.state().add_chain_id(preferred_chain_id.clone()); self.deploy_phase_one( caller.clone(), deploy_cost.clone(), @@ -718,6 +675,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { optional_sov_config.clone(), ) .await; + let chain_config_address = self.get_chain_config_address(&preferred_chain_id).await; self.register_as_validator( shard, @@ -777,10 +735,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { chain_id: &str, sc_id: ScArray, ) -> Address { - let sovereign_forge_address = self - .common_state() - .current_sovereign_forge_sc_address() - .clone(); + let sovereign_forge_address = self.state().current_sovereign_forge_sc_address().clone(); self.interactor() .query() @@ -798,14 +753,8 @@ pub trait CommonInteractorTrait: InteractorHelpers { } async fn register_chain_factory(&mut self, caller: Address, shard_id: u32) { - let sovereign_forge_address = self - .common_state() - .current_sovereign_forge_sc_address() - .clone(); - let chain_factory_address = self - .common_state() - .get_chain_factory_sc_address(shard_id) - .clone(); + let sovereign_forge_address = self.state().current_sovereign_forge_sc_address().clone(); + let chain_factory_address = self.state().get_chain_factory_sc_address(shard_id).clone(); self.interactor() .tx() @@ -820,10 +769,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { } async fn update_smart_contracts_addresses_in_state(&mut self, chain_id: String) { - let sovereign_forge_address = self - .common_state() - .current_sovereign_forge_sc_address() - .clone(); + let sovereign_forge_address = self.state().current_sovereign_forge_sc_address().clone(); let result_value = self .interactor() @@ -839,31 +785,29 @@ pub trait CommonInteractorTrait: InteractorHelpers { let address = Bech32Address::from(contract.address.to_address()); match contract.id { ScArray::ChainConfig => { - self.common_state() - .set_chain_config_sc_address(AddressInfo { - address, - chain_id: chain_id.clone(), - }); + self.state().set_chain_config_sc_address(AddressInfo { + address, + chain_id: chain_id.clone(), + }); } ScArray::ESDTSafe => { - self.common_state() + self.state() .set_mvx_esdt_safe_contract_address(AddressInfo { address, chain_id: chain_id.clone(), }); } ScArray::FeeMarket => { - self.common_state().set_fee_market_address(AddressInfo { + self.state().set_fee_market_address(AddressInfo { address, chain_id: chain_id.clone(), }); } ScArray::HeaderVerifier => { - self.common_state() - .set_header_verifier_address(AddressInfo { - address, - chain_id: chain_id.clone(), - }); + self.state().set_header_verifier_address(AddressInfo { + address, + chain_id: chain_id.clone(), + }); } _ => {} } @@ -871,10 +815,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { } async fn get_chain_config_address(&mut self, chain_id: &str) -> Bech32Address { - let sovereign_forge_address = self - .common_state() - .current_sovereign_forge_sc_address() - .clone(); + let sovereign_forge_address = self.state().current_sovereign_forge_sc_address().clone(); let result_value = self .interactor() @@ -902,10 +843,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { opt_preferred_chain_id: Option>, opt_config: OptionalValue>, ) { - let sovereign_forge_address = self - .common_state() - .current_sovereign_forge_sc_address() - .clone(); + let sovereign_forge_address = self.state().current_sovereign_forge_sc_address().clone(); let mut egld_amount = BigUint::default(); @@ -916,8 +854,8 @@ pub trait CommonInteractorTrait: InteractorHelpers { self.interactor() .tx() .from(caller) - .to(sovereign_forge_address) - .gas(30_000_000u64) + .to(sovereign_forge_address.clone()) + .gas(40_000_000u64) .typed(SovereignForgeProxy) .deploy_phase_one(opt_preferred_chain_id, opt_config) .egld(egld_amount) @@ -931,15 +869,12 @@ pub trait CommonInteractorTrait: InteractorHelpers { opt_config: OptionalValue>, caller: Address, ) { - let sovereign_forge_address = self - .common_state() - .current_sovereign_forge_sc_address() - .clone(); + let sovereign_forge_address = self.state().current_sovereign_forge_sc_address().clone(); self.interactor() .tx() .from(caller) .to(sovereign_forge_address) - .gas(30_000_000u64) + .gas(40_000_000u64) .typed(SovereignForgeProxy) .deploy_phase_two(opt_config) .returns(ReturnsResultUnmanaged) @@ -952,10 +887,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { caller: Address, fee: OptionalValue>, ) { - let sovereign_forge_address = self - .common_state() - .current_sovereign_forge_sc_address() - .clone(); + let sovereign_forge_address = self.state().current_sovereign_forge_sc_address().clone(); self.interactor() .tx() @@ -970,10 +902,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { } async fn deploy_phase_four(&mut self, caller: Address) { - let sovereign_forge_address = self - .common_state() - .current_sovereign_forge_sc_address() - .clone(); + let sovereign_forge_address = self.state().current_sovereign_forge_sc_address().clone(); self.interactor() .tx() @@ -988,10 +917,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { } async fn complete_setup_phase(&mut self, caller: Address) { - let sovereign_forge_address = self - .common_state() - .current_sovereign_forge_sc_address() - .clone(); + let sovereign_forge_address = self.state().current_sovereign_forge_sc_address().clone(); self.interactor() .tx() @@ -1013,8 +939,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { shard: u32, ) { let bridge_service = self.get_bridge_service_for_shard(shard).clone(); - let current_mvx_esdt_safe_address = - self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let current_mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(shard).clone(); self.interactor() .tx() @@ -1041,7 +966,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { shard: u32, ) { let bridge_service = self.get_bridge_service_for_shard(shard).clone(); - let current_fee_market_address = self.common_state().get_fee_market_address(shard).clone(); + let current_fee_market_address = self.state().get_fee_market_address(shard).clone(); self.interactor() .tx() @@ -1062,7 +987,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { shard: u32, ) { let bridge_service = self.get_bridge_service_for_shard(shard).clone(); - let current_fee_market_address = self.common_state().get_fee_market_address(shard).clone(); + let current_fee_market_address = self.state().get_fee_market_address(shard).clone(); self.interactor() .tx() @@ -1082,17 +1007,12 @@ pub trait CommonInteractorTrait: InteractorHelpers { shard: u32, ) { let bridge_service = self.get_bridge_service_for_shard(shard).clone(); - let current_mvx_esdt_safe_address = - self.common_state().get_mvx_esdt_safe_address(shard).clone(); - - if self.common_state().get_is_burn_mechanism_set() { - return; - } + let current_mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(shard).clone(); let token_burn_mechanism_operation = SetBurnMechanismOperation { token_id, nonce: self - .common_state() + .state() .get_and_increment_operation_nonce(¤t_mvx_esdt_safe_address.to_string()), }; @@ -1120,8 +1040,6 @@ pub trait CommonInteractorTrait: InteractorHelpers { .returns(ReturnsResultUnmanaged) .run() .await; - - self.common_state().set_is_burn_mechanism_set(true); } async fn set_token_lock_mechanism( @@ -1130,13 +1048,12 @@ pub trait CommonInteractorTrait: InteractorHelpers { shard: u32, ) { let bridge_service = self.get_bridge_service_for_shard(shard).clone(); - let current_mvx_esdt_safe_address = - self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let current_mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(shard).clone(); let token_lock_mechanism_operation = SetLockMechanismOperation { token_id, nonce: self - .common_state() + .state() .get_and_increment_operation_nonce(¤t_mvx_esdt_safe_address.to_string()), }; @@ -1164,16 +1081,11 @@ pub trait CommonInteractorTrait: InteractorHelpers { .returns(ReturnsResultUnmanaged) .run() .await; - - self.common_state().set_is_burn_mechanism_set(false); } async fn set_token_burn_mechanism_before_setup_phase(&mut self, caller: Address) { - let sovereign_forge_address = self - .common_state() - .current_sovereign_forge_sc_address() - .clone(); - let trusted_token = self.common_state().get_trusted_token(); + let sovereign_forge_address = self.state().current_sovereign_forge_sc_address().clone(); + let trusted_token = self.state().get_trusted_token_string(); self.interactor() .tx() @@ -1189,7 +1101,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { async fn set_special_roles_for_trusted_token(&mut self, for_address: Address) { let user_address = self.user_address().clone(); - let trusted_token = self.common_state().get_trusted_token(); + let trusted_token = self.state().get_trusted_token_string(); let roles = vec![EsdtLocalRole::Mint, EsdtLocalRole::Burn]; @@ -1215,13 +1127,10 @@ pub trait CommonInteractorTrait: InteractorHelpers { operations_hashes: MultiValueEncoded>, ) { let bridge_service = self.get_bridge_service_for_shard(shard).clone(); - let header_verifier_address = self - .common_state() - .get_header_verifier_address(shard) - .clone(); + let header_verifier_address = self.state().get_header_verifier_address(shard).clone(); let secret_keys = self - .common_state() + .state() .get_bls_secret_keys(shard) .cloned() .unwrap_or_else(|| panic!("No BLS secret keys registered for shard {shard}")); @@ -1297,13 +1206,13 @@ pub trait CommonInteractorTrait: InteractorHelpers { } async fn switch_pause_status(&mut self, status: bool, shard: u32) { - let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(shard).clone(); let bridge_address = self.get_bridge_service_for_shard(shard).clone(); let operation = PauseStatusOperation { status, nonce: self - .common_state() + .state() .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), }; @@ -1339,10 +1248,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { } async fn complete_header_verifier_setup_phase(&mut self, caller: Address, shard: u32) { - let header_verifier_address = self - .common_state() - .get_header_verifier_address(shard) - .clone(); + let header_verifier_address = self.state().get_header_verifier_address(shard).clone(); self.interactor() .tx() @@ -1358,10 +1264,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { async fn complete_chain_config_setup_phase(&mut self, shard: u32) { let bridge_owner = self.get_bridge_owner_for_shard(shard).clone(); - let chain_config_address = self - .common_state() - .current_chain_config_sc_address() - .clone(); + let chain_config_address = self.state().current_chain_config_sc_address().clone(); self.interactor() .tx() @@ -1385,8 +1288,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { expected_log: Option>>, ) { let user_address = self.user_address().clone(); - let current_mvx_esdt_safe_address = - self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let current_mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(shard).clone(); let (response, logs) = self .interactor() .tx() @@ -1413,7 +1315,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { amount: BigUint, ) { let user_address = self.user_address().clone(); - let testing_sc_address = self.common_state().current_testing_sc_address().clone(); + let testing_sc_address = self.state().current_testing_sc_address().clone(); self.interactor() .tx() .from(user_address) @@ -1434,8 +1336,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { operation: Operation, expected_logs: Vec>, ) { - let current_mvx_esdt_safe_address = - self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let current_mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(shard).clone(); let (response, logs) = self .interactor() .tx() @@ -1460,7 +1361,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { token: RegisterTokenOperation, ) -> String { let user_address = self.user_address().clone(); - let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(shard).clone(); let token_hash = token.generate_hash(); let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&token_hash.to_vec())); @@ -1487,7 +1388,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { shard: u32, token_id: EgldOrEsdtTokenIdentifier, ) -> EgldOrEsdtTokenIdentifier { - let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(shard).clone(); let user_address = self.user_address().clone(); self.interactor() .tx() @@ -1506,7 +1407,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { token_id: EgldOrEsdtTokenIdentifier, nonce: u64, ) -> EsdtInfo { - let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(shard).clone(); let user_address = self.user_address().clone(); self.interactor() .tx() @@ -1520,10 +1421,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { } async fn check_setup_phase_status(&mut self, chain_id: &str, expected_value: bool) { - let sovereign_forge_address = self - .common_state() - .current_sovereign_forge_sc_address() - .clone(); + let sovereign_forge_address = self.state().current_sovereign_forge_sc_address().clone(); let result_value = self .interactor() .query() @@ -1547,10 +1445,7 @@ pub trait CommonInteractorTrait: InteractorHelpers { operation_hash: ManagedBuffer, expected_value: OperationHashStatus, ) { - let header_verifier_address = self - .common_state() - .get_header_verifier_address(shard_id) - .clone(); + let header_verifier_address = self.state().get_header_verifier_address(shard_id).clone(); let response = self .interactor() .query() @@ -1602,99 +1497,13 @@ pub trait CommonInteractorTrait: InteractorHelpers { } } - async fn retrieve_current_fee_token_for_wallet(&mut self) -> EsdtTokenInfo { - let fee_token_id = self - .common_state() - .fee_market_tokens - .get("0") - .map(|t| t.token_id.clone()) - .expect("Fee market token for shard 0 not found"); - - let user_address = &self.user_address().clone(); - let balances = self.interactor().get_account_esdt(user_address).await; - - let amount = if let Some(esdt_balance) = balances.get(&fee_token_id) { - BigUint::from( - num_bigint::BigUint::parse_bytes(esdt_balance.balance.as_bytes(), 10) - .expect("Failed to parse fee token balance as number"), - ) - } else { - BigUint::zero() - }; - - EsdtTokenInfo { - token_id: EgldOrEsdtTokenIdentifier::from(fee_token_id.as_str()), - nonce: 0, - token_type: EsdtTokenType::Fungible, - amount, - decimals: 18, - } - } - - async fn retrieve_current_trusted_token_for_wallet(&mut self) -> EsdtTokenInfo { - let user_address = &self.user_address().clone(); - let balances = self.interactor().get_account_esdt(user_address).await; - let trusted_token = self.common_state().get_trusted_token(); - - let amount = if let Some(esdt_balance) = balances.get(trusted_token.as_str()) { - BigUint::from( - num_bigint::BigUint::parse_bytes(esdt_balance.balance.as_bytes(), 10) - .expect("Failed to parse fee token balance as number"), - ) - } else { - BigUint::zero() - }; - - EsdtTokenInfo { - token_id: EgldOrEsdtTokenIdentifier::esdt(trusted_token.as_str()), - nonce: 0, - token_type: EsdtTokenType::Fungible, - amount, - decimals: 18, - } - } - - async fn remove_fee_wrapper(&mut self, shard: u32) { - let fee_activated = self.common_state().get_fee_status_for_shard(shard); - - if !fee_activated { - return; - } - - let fee_token = self.state().get_fee_token_identifier(); - let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); - - let operation: RemoveFeeOperation = RemoveFeeOperation { - token_id: fee_token.clone(), - nonce: self - .common_state() - .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), - }; - - let operation_hash = operation.generate_hash(); - let hash_of_hashes = ManagedBuffer::new_from_bytes(&sha256(&operation_hash.to_vec())); - - let operations_hashes = MultiValueEncoded::from_iter(vec![operation_hash.clone()]); - - self.register_operation(shard, &hash_of_hashes, operations_hashes) - .await; - - self.remove_fee(hash_of_hashes, operation, shard).await; - self.common_state().set_fee_status_for_shard(shard, false); - } - async fn set_fee_wrapper(&mut self, fee: FeeStruct, shard: u32) { - let fee_activated = self.common_state().get_fee_status_for_shard(shard); - - if fee_activated { - return; - } + let mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(shard).clone(); - let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); let operation: SetFeeOperation = SetFeeOperation { - fee_struct: fee.clone(), + fee_struct: fee, nonce: self - .common_state() + .state() .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), }; @@ -1707,11 +1516,10 @@ pub trait CommonInteractorTrait: InteractorHelpers { .await; self.set_fee(hash_of_hashes, operation, shard).await; - self.common_state().set_fee_status_for_shard(shard, true); } async fn get_native_token(&mut self, shard: u32) -> EgldOrEsdtTokenIdentifier { - let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(shard).clone(); self.interactor() .query() diff --git a/common/common-interactor/src/interactor_common_state.rs b/common/common-interactor/src/interactor_common_state.rs deleted file mode 100644 index 33b634dca..000000000 --- a/common/common-interactor/src/interactor_common_state.rs +++ /dev/null @@ -1,364 +0,0 @@ -use std::{ - collections::HashMap, - io::{Read, Write}, - path::Path, - str::FromStr, -}; - -use common_test_setup::constants::STATE_FILE; -use error_messages::{ - NO_KNOWN_CHAIN_CONFIG_SC, NO_KNOWN_CHAIN_FACTORY_SC, NO_KNOWN_FEE_MARKET, NO_KNOWN_FEE_TOKEN, - NO_KNOWN_HEADER_VERIFIER, NO_KNOWN_MVX_ESDT_SAFE, NO_KNOWN_SOVEREIGN_FORGE_SC, - NO_KNOWN_TESTING_SC, NO_KNOWN_TRUSTED_TOKEN, -}; -use multiversx_sc::{ - codec::num_bigint, - imports::Bech32Address, - types::{BigUint, EgldOrEsdtTokenIdentifier, EsdtTokenType}, -}; -use multiversx_sc_snippets::imports::StaticApi; -use serde::{Deserialize, Serialize}; - -use crate::{ - interactor_state::{AddressInfo, EsdtTokenInfo, ShardAddresses}, - interactor_structs::SerializableToken, -}; - -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct CommonState { - pub mvx_esdt_safe_addresses: Option, - pub header_verifier_addresses: Option, - pub fee_market_addresses: Option, - pub chain_config_sc_addresses: Option, - pub testing_sc_address: Option, - pub sovereign_forge_sc_address: Option, - pub chain_factory_sc_addresses: Option>, - pub fee_market_tokens: HashMap, - pub trusted_token: Option, - pub fee_status: HashMap, - pub operation_nonce: HashMap, - pub chain_ids: Vec, - pub mvx_egld_balances: Vec<(String, u64)>, - pub testing_egld_balance: u64, - pub bls_secret_keys: HashMap>>, - pub deposited_amount: String, - pub is_burn_mechanism_set: bool, -} - -impl CommonState { - pub fn load_state() -> Self { - if Path::new(STATE_FILE).exists() { - let mut file = std::fs::File::open(STATE_FILE).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - toml::from_str(&content).unwrap() - } else { - Self::default() - } - } - - pub fn set_mvx_esdt_safe_contract_address(&mut self, address: AddressInfo) { - let list = self.mvx_esdt_safe_addresses.get_or_insert_default(); - list.push(address); - } - - pub fn set_header_verifier_address(&mut self, address: AddressInfo) { - let list = self.header_verifier_addresses.get_or_insert_default(); - list.push(address); - } - - pub fn set_fee_market_address(&mut self, address: AddressInfo) { - let list = self.fee_market_addresses.get_or_insert_default(); - list.push(address); - } - - pub fn set_chain_config_sc_address(&mut self, address: AddressInfo) { - let list = self.chain_config_sc_addresses.get_or_insert_default(); - list.push(address); - } - - pub fn set_testing_sc_address(&mut self, address: Bech32Address) { - self.testing_sc_address = Some(address); - } - - pub fn set_sovereign_forge_sc_address(&mut self, address: Bech32Address) { - self.sovereign_forge_sc_address = Some(address); - } - - pub fn set_chain_factory_sc_address(&mut self, address: Bech32Address) { - let list = self.chain_factory_sc_addresses.get_or_insert_default(); - list.push(address); - } - - pub fn set_fee_status_for_shard(&mut self, shard: u32, status: bool) { - self.fee_status.insert(shard.to_string(), status); - } - - pub fn set_fee_status_for_all_shards(&mut self, status: bool) { - for shard in 0..3 { - self.fee_status.insert(shard.to_string(), status); - } - } - - pub fn set_fee_market_token_for_all_shards(&mut self, token: SerializableToken) { - for shard in 0..3 { - self.fee_market_tokens - .insert(shard.to_string(), token.clone()); - } - } - - pub fn set_fee_market_token_for_shard(&mut self, shard: u32, token: SerializableToken) { - self.fee_market_tokens.insert(shard.to_string(), token); - } - - pub fn add_chain_id(&mut self, chain_id: String) { - self.chain_ids.push(chain_id); - } - - pub fn set_mvx_egld_balance_for_all_shards(&mut self, balance: u64) { - for shard in 0..3 { - self.mvx_egld_balances.push((shard.to_string(), balance)); - } - } - - pub fn set_trusted_token(&mut self, token: String) { - self.trusted_token = Some(token); - } - - pub fn update_mvx_egld_balance_with_amount(&mut self, shard: u32, amount: u64) { - let shard_str = shard.to_string(); - if let Some((_, current_balance)) = self - .mvx_egld_balances - .iter_mut() - .find(|(s, _)| s == &shard_str) - { - *current_balance += amount; - } - } - - pub fn update_testing_egld_balance_with_amount(&mut self, amount: u64) { - self.testing_egld_balance += amount; - } - - fn parse_deposited_amount(&self) -> num_bigint::BigUint { - if self.deposited_amount.is_empty() { - return num_bigint::BigUint::from(0u64); - } - - let trimmed = self.deposited_amount.trim(); - num_bigint::BigUint::from_str(trimmed).unwrap_or_else(|_| { - eprintln!("Failed to parse deposited_amount '{}'", trimmed); - num_bigint::BigUint::from(0u64) - }) - } - - fn biguint_to_num_biguint(amount: &BigUint) -> num_bigint::BigUint { - let amount_bytes = amount.to_bytes_be(); - num_bigint::BigUint::from_bytes_be(amount_bytes.as_slice()) - } - - fn num_biguint_to_biguint(num: &num_bigint::BigUint) -> BigUint { - let bytes = num.to_bytes_be(); - BigUint::from_bytes_be(&bytes) - } - - pub fn add_to_deposited_amount(&mut self, amount: BigUint) { - let current = self.parse_deposited_amount(); - let amount_biguint = Self::biguint_to_num_biguint(&amount); - let sum = current + amount_biguint; - - self.deposited_amount = sum.to_string(); - } - - pub fn subtract_from_deposited_amount(&mut self, amount: BigUint) { - let current = self.parse_deposited_amount(); - let amount_biguint = Self::biguint_to_num_biguint(&amount); - let result = if current >= amount_biguint { - current - amount_biguint - } else { - num_bigint::BigUint::from(0u64) - }; - - self.deposited_amount = result.to_string(); - } - - pub fn set_is_burn_mechanism_set(&mut self, is_burn_mechanism_set: bool) { - self.is_burn_mechanism_set = is_burn_mechanism_set; - } - - pub fn get_is_burn_mechanism_set(&self) -> bool { - self.is_burn_mechanism_set - } - - pub fn get_deposited_amount(&self) -> BigUint { - let num_biguint = self.parse_deposited_amount(); - Self::num_biguint_to_biguint(&num_biguint) - } - - pub fn get_and_increment_operation_nonce(&mut self, contract_address: &str) -> u64 { - let nonce = self.get_operation_nonce(contract_address); - self.increment_operation_nonce(contract_address); - nonce - } - - /// Returns the contract addresses - pub fn current_chain_config_sc_address(&self) -> &Bech32Address { - self.chain_config_sc_addresses - .as_ref() - .expect(NO_KNOWN_CHAIN_CONFIG_SC) - .first() - } - - pub fn current_testing_sc_address(&self) -> &Bech32Address { - self.testing_sc_address.as_ref().expect(NO_KNOWN_TESTING_SC) - } - - pub fn current_sovereign_forge_sc_address(&self) -> &Bech32Address { - self.sovereign_forge_sc_address - .as_ref() - .expect(NO_KNOWN_SOVEREIGN_FORGE_SC) - } - - pub fn get_chain_factory_sc_address(&self, shard: u32) -> &Bech32Address { - self.chain_factory_sc_addresses - .as_ref() - .expect(NO_KNOWN_CHAIN_FACTORY_SC) - .get(shard as usize) - .unwrap_or_else(|| panic!("No Chain Factory SC address for shard {}", shard)) - } - - pub fn get_mvx_esdt_safe_address(&self, shard: u32) -> &Bech32Address { - self.mvx_esdt_safe_addresses - .as_ref() - .expect(NO_KNOWN_MVX_ESDT_SAFE) - .addresses - .get(shard as usize) - .map(|info| &info.address) - .unwrap_or_else(|| panic!("No MVX ESDT Safe address for shard {}", shard)) - } - - pub fn get_fee_market_address(&self, shard: u32) -> &Bech32Address { - self.fee_market_addresses - .as_ref() - .expect(NO_KNOWN_FEE_MARKET) - .addresses - .get(shard as usize) - .map(|info| &info.address) - .unwrap_or_else(|| panic!("No Fee Market address for shard {}", shard)) - } - - pub fn get_header_verifier_address(&self, shard: u32) -> &Bech32Address { - self.header_verifier_addresses - .as_ref() - .expect(NO_KNOWN_HEADER_VERIFIER) - .addresses - .get(shard as usize) - .map(|info| &info.address) - .unwrap_or_else(|| panic!("No Header Verifier address for shard {}", shard)) - } - - pub fn get_fee_status_for_shard(&self, shard: u32) -> bool { - self.fee_status - .get(&shard.to_string()) - .cloned() - .unwrap_or(false) - } - - pub fn get_fee_market_token_amount_for_shard(&self, shard: u32) -> u64 { - self.fee_market_tokens - .get(&shard.to_string()) - .cloned() - .expect(NO_KNOWN_FEE_TOKEN) - .amount - } - - pub fn get_fee_market_token_for_shard_converted(&self, shard: u32) -> EsdtTokenInfo { - let token = self - .fee_market_tokens - .get(&shard.to_string()) - .cloned() - .expect(NO_KNOWN_FEE_TOKEN); - EsdtTokenInfo { - token_id: EgldOrEsdtTokenIdentifier::from(token.token_id.as_str()), - nonce: token.nonce, - token_type: EsdtTokenType::from(token.token_type), - decimals: token.decimals, - amount: BigUint::from(token.amount), - } - } - - pub fn get_fee_market_token_for_shard(&self, shard: u32) -> SerializableToken { - self.fee_market_tokens - .get(&shard.to_string()) - .cloned() - .expect(NO_KNOWN_FEE_TOKEN) - } - - pub fn get_chain_id_for_shard(&self, shard: u32) -> &String { - self.chain_ids - .get(shard as usize) - .unwrap_or_else(|| panic!("No chain ID for shard {}", shard)) - } - - pub fn get_mvx_egld_balance_for_shard(&self, shard: u32) -> u64 { - self.mvx_egld_balances - .get(shard as usize) - .map(|(_, balance)| *balance) - .unwrap_or(0u64) - } - - pub fn get_testing_egld_balance(&self) -> u64 { - self.testing_egld_balance - } - - pub fn get_trusted_token(&self) -> String { - self.trusted_token - .as_ref() - .expect(NO_KNOWN_TRUSTED_TOKEN) - .clone() - } - - pub fn add_bls_secret_key(&mut self, shard: u32, secret_key_bytes: Vec) { - let shard_key = shard.to_string(); - self.bls_secret_keys - .entry(shard_key) - .or_default() - .push(secret_key_bytes); - } - - pub fn get_bls_secret_keys(&self, shard: u32) -> Option<&Vec>> { - let shard_key = shard.to_string(); - self.bls_secret_keys.get(&shard_key) - } - - pub fn get_operation_nonce(&self, contract_address: &str) -> u64 { - self.operation_nonce - .get(contract_address) - .copied() - .unwrap_or(0) - } - - pub fn increment_operation_nonce(&mut self, contract_address: &str) { - let current_nonce = self - .operation_nonce - .entry(contract_address.to_string()) - .or_insert(0); - *current_nonce += 1; - } - - pub fn set_operation_nonce(&mut self, contract_address: &str, nonce: u64) { - self.operation_nonce - .entry(contract_address.to_string()) - .and_modify(|existing| *existing = nonce) - .or_insert(nonce); - } -} - -impl Drop for CommonState { - // Serializes state to file - fn drop(&mut self) { - let mut file = std::fs::File::create(STATE_FILE).unwrap(); - file.write_all(toml::to_string(self).unwrap().as_bytes()) - .unwrap(); - } -} diff --git a/common/common-interactor/src/interactor_config.rs b/common/common-interactor/src/interactor_config.rs index 19cca077c..71cbfa3da 100644 --- a/common/common-interactor/src/interactor_config.rs +++ b/common/common-interactor/src/interactor_config.rs @@ -30,8 +30,9 @@ impl Config { } pub fn chain_simulator_config() -> Self { + let port = std::env::var("CHAIN_SIMULATOR_PORT").unwrap_or_else(|_| "8085".to_string()); Config { - gateway_uri: "http://localhost:8085".to_owned(), + gateway_uri: format!("http://localhost:{}", port), chain_type: ChainType::Simulator, } } diff --git a/common/common-interactor/src/interactor_helpers.rs b/common/common-interactor/src/interactor_helpers.rs index 612acf6aa..95fa77e53 100644 --- a/common/common-interactor/src/interactor_helpers.rs +++ b/common/common-interactor/src/interactor_helpers.rs @@ -34,7 +34,6 @@ use structs::{ }; use crate::{ - interactor_common_state::CommonState, interactor_state::{EsdtTokenInfo, State}, interactor_structs::{ActionConfig, BalanceCheckConfig, SerializableToken}, }; @@ -44,7 +43,6 @@ use crate::{ pub trait InteractorHelpers { fn interactor(&mut self) -> &mut Interactor; fn state(&mut self) -> &mut State; - fn common_state(&mut self) -> &mut CommonState; fn user_address(&self) -> &Address; fn prepare_transfer_data( @@ -136,7 +134,7 @@ pub trait InteractorHelpers { let user_address = self.user_address().clone(); let payment_vec = self.prepare_execute_payment(token); - let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(shard).clone(); match endpoint { Some(endpoint) => { @@ -148,7 +146,7 @@ pub trait InteractorHelpers { let transfer_data = TransferData::new(gas_limit, function, args); let operation_data = OperationData::new( - self.common_state() + self.state() .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), ManagedAddress::from_address(&user_address), Some(transfer_data), @@ -156,10 +154,7 @@ pub trait InteractorHelpers { Operation::new( ManagedAddress::from_address( - &self - .common_state() - .current_testing_sc_address() - .to_address(), + &self.state().current_testing_sc_address().to_address(), ), payment_vec, operation_data, @@ -167,7 +162,7 @@ pub trait InteractorHelpers { } None => { let operation_data = OperationData::new( - self.common_state() + self.state() .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), ManagedAddress::from_address(&user_address), None, @@ -183,7 +178,7 @@ pub trait InteractorHelpers { } fn get_address_name(&mut self, address: &Bech32Address) -> &'static str { - let testing_addr = self.common_state().current_testing_sc_address(); + let testing_addr = self.state().current_testing_sc_address(); if address == testing_addr { return TESTING_SC; } @@ -195,7 +190,7 @@ pub trait InteractorHelpers { // Check shard-specific contract addresses for shard_id in 0..3 { - let mvx_addr = self.common_state().get_mvx_esdt_safe_address(shard_id); + let mvx_addr = self.state().get_mvx_esdt_safe_address(shard_id); if address == mvx_addr { return match shard_id { 0 => MVX_ESDT_SAFE_SHARD_0, @@ -205,7 +200,7 @@ pub trait InteractorHelpers { }; } - let fee_addr = self.common_state().get_fee_market_address(shard_id); + let fee_addr = self.state().get_fee_market_address(shard_id); if address == fee_addr { return match shard_id { 0 => FEE_MARKET_SHARD_0, @@ -512,7 +507,7 @@ pub trait InteractorHelpers { // NOTE: This is a temporary workaround, the whole balance check method needs refactoring async fn check_mvx_esdt_balance(&mut self, shard: u32, expected_tokens: Vec) { - let mvx_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let mvx_address = self.state().get_mvx_esdt_safe_address(shard).clone(); let tokens = if expected_tokens.is_empty() { let mut tokens = self.create_empty_balance_state().await; tokens.retain(|token| { @@ -545,21 +540,14 @@ pub trait InteractorHelpers { } async fn check_fee_market_balance(&mut self, shard: u32, expected_tokens: Vec) { - let fee_market_address = self.common_state().get_fee_market_address(shard).clone(); - let tokens = if expected_tokens.is_empty() { - vec![self - .common_state() - .get_fee_market_token_for_shard_converted(shard)] - } else { - expected_tokens - }; - self.check_address_balance(&fee_market_address, tokens) + let fee_market_address = self.state().get_fee_market_address(shard).clone(); + self.check_address_balance(&fee_market_address, expected_tokens) .await; } // NOTE: This is a temporary workaround, the whole balance check method needs refactoring async fn check_testing_sc_balance(&mut self, expected_tokens: Vec) { - let testing_sc_address = self.common_state().current_testing_sc_address().clone(); + let testing_sc_address = self.state().current_testing_sc_address().clone(); let tokens = if expected_tokens.is_empty() { let mut tokens = self.create_empty_balance_state().await; tokens.retain(|token| { @@ -720,18 +708,9 @@ pub trait InteractorHelpers { //MVX Tokens if is_egld { - let current_balance = self.common_state().get_mvx_egld_balance_for_shard(shard); let amount_u64 = amount.clone().unwrap().to_u64().expect(AMOUNT_IS_TOO_LARGE); - let expected_amount = if is_execute { - current_balance - amount_u64 - } else { - current_balance + amount_u64 - }; - let address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); - self.check_address_egld_balance(&address, expected_amount) - .await; - self.common_state() - .update_mvx_egld_balance_with_amount(shard, expected_amount); + let address = self.state().get_mvx_esdt_safe_address(shard).clone(); + self.check_address_egld_balance(&address, amount_u64).await; } else { // ESDT tokens let mvx_tokens = self.get_expected_mvx_tokens( @@ -747,12 +726,7 @@ pub trait InteractorHelpers { // FEE market if fee_amount > 0u64 { let fee_token = self.state().get_fee_token_id(); - let previous_fee_amount = BigUint::from( - self.common_state() - .get_fee_market_token_amount_for_shard(shard), - ); - let expected_fee_tokens = - vec![self.clone_token_with_amount(fee_token, previous_fee_amount + fee_amount)]; + let expected_fee_tokens = vec![self.clone_token_with_amount(fee_token, fee_amount)]; self.check_fee_market_balance(shard, expected_fee_tokens) .await; } else { @@ -761,13 +735,10 @@ pub trait InteractorHelpers { // TESTING SC if is_egld && is_execute && with_transfer_data && expected_error.is_none() { - let expected_amount = self.common_state().get_testing_egld_balance() - + amount.clone().unwrap().to_u64().unwrap(); - let testing_address = self.common_state().current_testing_sc_address().clone(); - self.check_address_egld_balance(&testing_address, expected_amount) + let amount_u64 = amount.clone().unwrap().to_u64().expect(AMOUNT_IS_TOO_LARGE); + let testing_address = self.state().current_testing_sc_address().clone(); + self.check_address_egld_balance(&testing_address, amount_u64) .await; - self.common_state() - .update_testing_egld_balance_with_amount(expected_amount); } else { let testing_sc_tokens = match (&token, &amount) { (Some(token), Some(amount)) => { @@ -871,7 +842,7 @@ pub trait InteractorHelpers { } fn create_random_sovereign_token_id(&mut self, shard: u32) -> String { - let current_chain_id = self.common_state().get_chain_id_for_shard(shard).clone(); + let current_chain_id = self.state().get_chain_id_for_shard(shard).clone(); let rand_string: String = rand::rng() .sample_iter(&Alphanumeric) .filter(|c| c.is_ascii_alphanumeric() && c.is_ascii_lowercase()) @@ -880,23 +851,4 @@ pub trait InteractorHelpers { .collect(); format!("{}-SOV-{}", current_chain_id, rand_string) } - - async fn update_fee_market_balance_state( - &mut self, - fee: Option>, - payment_vec: PaymentsVec, - shard: u32, - ) { - if fee.is_none() || payment_vec.is_empty() { - return; - } - let mut fee_token_in_fee_market = self.common_state().get_fee_market_token_for_shard(shard); - - let payment = payment_vec.get(0); - if let Some(payment_amount) = payment.amount.to_u64() { - fee_token_in_fee_market.amount += payment_amount; - } - self.common_state() - .set_fee_market_token_for_shard(shard, fee_token_in_fee_market); - } } diff --git a/common/common-interactor/src/interactor_state.rs b/common/common-interactor/src/interactor_state.rs index 3ffacc106..767bee2fc 100644 --- a/common/common-interactor/src/interactor_state.rs +++ b/common/common-interactor/src/interactor_state.rs @@ -1,13 +1,17 @@ #![allow(non_snake_case)] +use std::collections::HashMap; + use error_messages::{ - NO_ADDRESSES_AVAILABLE, NO_KNOWN_DYNAMIC_META_ESDT_TOKEN_ID, NO_KNOWN_DYNAMIC_NFT_TOKEN_ID, - NO_KNOWN_DYNAMIC_SFT_TOKEN_ID, NO_KNOWN_FEE_TOKEN, NO_KNOWN_FIRST_TOKEN, - NO_KNOWN_FUNGIBLE_TOKEN, NO_KNOWN_META_ESDT_TOKEN, NO_KNOWN_NFT_TOKEN, NO_KNOWN_SFT_TOKEN, - NO_KNOWN_TRUSTED_TOKEN, + NO_ADDRESSES_AVAILABLE, NO_KNOWN_CHAIN_CONFIG_SC, NO_KNOWN_CHAIN_FACTORY_SC, + NO_KNOWN_DYNAMIC_META_ESDT_TOKEN_ID, NO_KNOWN_DYNAMIC_NFT_TOKEN_ID, + NO_KNOWN_DYNAMIC_SFT_TOKEN_ID, NO_KNOWN_FEE_MARKET, NO_KNOWN_FEE_TOKEN, NO_KNOWN_FIRST_TOKEN, + NO_KNOWN_FUNGIBLE_TOKEN, NO_KNOWN_HEADER_VERIFIER, NO_KNOWN_META_ESDT_TOKEN, + NO_KNOWN_MVX_ESDT_SAFE, NO_KNOWN_NFT_TOKEN, NO_KNOWN_SFT_TOKEN, NO_KNOWN_SOVEREIGN_FORGE_SC, + NO_KNOWN_TESTING_SC, NO_KNOWN_TRUSTED_TOKEN, }; +use multiversx_sc::imports::Bech32Address; use multiversx_sc_snippets::imports::*; -use serde::{Deserialize, Serialize}; #[derive(Debug, Clone)] pub struct EsdtTokenInfo { @@ -18,7 +22,7 @@ pub struct EsdtTokenInfo { pub amount: BigUint, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] pub struct AddressInfo { pub address: Bech32Address, pub chain_id: String, @@ -27,7 +31,7 @@ pub struct AddressInfo { // NOTE: This struct holds deployed contract addresses. // The index of each address corresponds to the shard number where the contract was deployed. // For example, index 0 = shard 0, index 1 = shard 1, etc. -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Default)] pub struct ShardAddresses { pub addresses: Vec, } @@ -59,6 +63,16 @@ pub struct State { pub sft_tokens: Vec, pub initial_wallet_tokens_state: Vec, pub trusted_token: Option, + pub mvx_esdt_safe_addresses: Option, + pub header_verifier_addresses: Option, + pub fee_market_addresses: Option, + pub chain_config_sc_addresses: Option, + pub testing_sc_address: Option, + pub sovereign_forge_sc_address: Option, + pub chain_factory_sc_addresses: Option>, + pub operation_nonce: HashMap, + pub chain_ids: Vec, + pub bls_secret_keys: HashMap>>, } impl State { @@ -117,6 +131,16 @@ impl State { .clone() } + pub fn get_trusted_token_string(&self) -> String { + self.trusted_token + .as_ref() + .expect(NO_KNOWN_TRUSTED_TOKEN) + .token_id + .clone() + .into_managed_buffer() + .to_string() + } + pub fn get_first_fungible_token_identifier(&self) -> EgldOrEsdtTokenIdentifier { self.fungible_tokens .first() @@ -206,4 +230,144 @@ impl State { .find(|token| token.token_id == token_id) .map_or_else(BigUint::zero, |token| token.amount.clone()) } + + pub fn set_mvx_esdt_safe_contract_address(&mut self, address: AddressInfo) { + let list = self.mvx_esdt_safe_addresses.get_or_insert_default(); + list.push(address); + } + + pub fn set_header_verifier_address(&mut self, address: AddressInfo) { + let list = self.header_verifier_addresses.get_or_insert_default(); + list.push(address); + } + + pub fn set_fee_market_address(&mut self, address: AddressInfo) { + let list = self.fee_market_addresses.get_or_insert_default(); + list.push(address); + } + + pub fn set_chain_config_sc_address(&mut self, address: AddressInfo) { + let list = self.chain_config_sc_addresses.get_or_insert_default(); + list.push(address); + } + + pub fn set_testing_sc_address(&mut self, address: Bech32Address) { + self.testing_sc_address = Some(address); + } + + pub fn set_sovereign_forge_sc_address(&mut self, address: Bech32Address) { + self.sovereign_forge_sc_address = Some(address); + } + + pub fn set_chain_factory_sc_address(&mut self, address: Bech32Address) { + let list = self.chain_factory_sc_addresses.get_or_insert_default(); + list.push(address); + } + + pub fn add_chain_id(&mut self, chain_id: String) { + self.chain_ids.push(chain_id); + } + + pub fn get_and_increment_operation_nonce(&mut self, contract_address: &str) -> u64 { + let nonce = self.get_operation_nonce(contract_address); + self.increment_operation_nonce(contract_address); + nonce + } + + /// Returns the contract addresses + pub fn current_chain_config_sc_address(&self) -> &Bech32Address { + self.chain_config_sc_addresses + .as_ref() + .expect(NO_KNOWN_CHAIN_CONFIG_SC) + .first() + } + + pub fn current_testing_sc_address(&self) -> &Bech32Address { + self.testing_sc_address.as_ref().expect(NO_KNOWN_TESTING_SC) + } + + pub fn current_sovereign_forge_sc_address(&self) -> &Bech32Address { + self.sovereign_forge_sc_address + .as_ref() + .expect(NO_KNOWN_SOVEREIGN_FORGE_SC) + } + + pub fn get_chain_factory_sc_address(&self, shard: u32) -> &Bech32Address { + self.chain_factory_sc_addresses + .as_ref() + .expect(NO_KNOWN_CHAIN_FACTORY_SC) + .get(shard as usize) + .unwrap_or_else(|| panic!("No Chain Factory SC address for shard {}", shard)) + } + + pub fn get_mvx_esdt_safe_address(&self, shard: u32) -> &Bech32Address { + self.mvx_esdt_safe_addresses + .as_ref() + .expect(NO_KNOWN_MVX_ESDT_SAFE) + .addresses + .get(shard as usize) + .map(|info| &info.address) + .unwrap_or_else(|| panic!("No MVX ESDT Safe address for shard {}", shard)) + } + + pub fn get_fee_market_address(&self, shard: u32) -> &Bech32Address { + self.fee_market_addresses + .as_ref() + .expect(NO_KNOWN_FEE_MARKET) + .addresses + .get(shard as usize) + .map(|info| &info.address) + .unwrap_or_else(|| panic!("No Fee Market address for shard {}", shard)) + } + + pub fn get_header_verifier_address(&self, shard: u32) -> &Bech32Address { + self.header_verifier_addresses + .as_ref() + .expect(NO_KNOWN_HEADER_VERIFIER) + .addresses + .get(shard as usize) + .map(|info| &info.address) + .unwrap_or_else(|| panic!("No Header Verifier address for shard {}", shard)) + } + + pub fn get_chain_id_for_shard(&self, shard: u32) -> &String { + self.chain_ids + .get(shard as usize) + .unwrap_or_else(|| panic!("No chain ID for shard {}", shard)) + } + + pub fn add_bls_secret_key(&mut self, shard: u32, secret_key_bytes: Vec) { + let shard_key = shard.to_string(); + self.bls_secret_keys + .entry(shard_key) + .or_default() + .push(secret_key_bytes); + } + + pub fn get_bls_secret_keys(&self, shard: u32) -> Option<&Vec>> { + let shard_key = shard.to_string(); + self.bls_secret_keys.get(&shard_key) + } + + pub fn get_operation_nonce(&self, contract_address: &str) -> u64 { + self.operation_nonce + .get(contract_address) + .copied() + .unwrap_or(0) + } + + pub fn increment_operation_nonce(&mut self, contract_address: &str) { + let current_nonce = self + .operation_nonce + .entry(contract_address.to_string()) + .or_insert(0); + *current_nonce += 1; + } + + pub fn set_operation_nonce(&mut self, contract_address: &str, nonce: u64) { + self.operation_nonce + .entry(contract_address.to_string()) + .and_modify(|existing| *existing = nonce) + .or_insert(nonce); + } } diff --git a/common/common-interactor/src/lib.rs b/common/common-interactor/src/lib.rs index 3b554e708..d913cee77 100644 --- a/common/common-interactor/src/lib.rs +++ b/common/common-interactor/src/lib.rs @@ -1,5 +1,4 @@ pub mod common_sovereign_interactor; -pub mod interactor_common_state; pub mod interactor_config; pub mod interactor_helpers; pub mod interactor_state; diff --git a/common/structs/src/lib.rs b/common/structs/src/lib.rs index 94711283c..80b07b557 100644 --- a/common/structs/src/lib.rs +++ b/common/structs/src/lib.rs @@ -17,10 +17,10 @@ pub mod operation; pub const MIN_BLOCKS_FOR_FINALITY: u64 = 10; pub const DEFAULT_MAX_TX_GAS_LIMIT: u64 = 500_000_000; -pub const PHASE_ONE_ASYNC_CALL_GAS: u64 = 9_000_000; +pub const PHASE_ONE_ASYNC_CALL_GAS: u64 = 12_000_000; pub const PHASE_ONE_CALLBACK_GAS: u64 = 3_000_000; -pub const PHASE_TWO_ASYNC_CALL_GAS: u64 = 18_000_000; +pub const PHASE_TWO_ASYNC_CALL_GAS: u64 = 25_000_000; pub const PHASE_TWO_CALLBACK_GAS: u64 = 2_000_000; pub const PHASE_THREE_ASYNC_CALL_GAS: u64 = 16_000_000; diff --git a/interactor/Cargo.toml b/interactor/Cargo.toml index 26ecde8c6..a56922323 100644 --- a/interactor/Cargo.toml +++ b/interactor/Cargo.toml @@ -40,9 +40,6 @@ features = ["derive"] version = "1.0" features = ["derive"] -[dependencies.serial_test] -version = "3.2.0" - [dependencies.error-messages] path = "../common/error-messages" diff --git a/interactor/HowToRun.md b/interactor/HowToRun.md deleted file mode 100644 index 6c364ffc3..000000000 --- a/interactor/HowToRun.md +++ /dev/null @@ -1,81 +0,0 @@ -# Chain Simulator Interactor Workflow - -This project uses the **Chain Simulator** to run tests against a **shared deployment** (“common state”) while ensuring each test also runs with its own **clean, per-test state**. - ---- - -## Quick Start - -1. **Start the Chain Simulator** - - ```bash - sc-meta cs start - ``` - - > Keep this running while you execute tests. - -2. **If you restart the simulator**, delete `state.toml` before running any test: - - ```bash - # Linux / macOS - find . -name state.toml -delete - - # Or manually: - rm -f path/to/interactor/state.toml - - ``` - - For a more convenient way to do the first 2 steps, a terminal alias can be set that **starts the chain simulator** and also **deletes the state.toml**. Example: - ``` - cs() { - rm -f interactor/state.toml - command sc-meta cs start "$@" - } - ``` - -3. **Run the deployment test** - - Run `deploy_setup` inside `always_deploy_setup_first.rs`. This creates the **common state**. - - Examples: - - ```bash - cargo test --package rust-interact --test always_deploy_setup_first --all-features -- deploy_setup --exact --show-output - ``` - -4. **Run any tests you want** - - With the common state in place, you can run specific tests or whole suites: - - ```bash - # Single test - cargo test --package rust-interact --test 'file_name_without_rs' --all-features -- 'test_name' --show-output - - # All tests in a file - cargo test --package rust-interact --test 'file_name_without_rs' --all-features -- --show-output - ``` - ---- - -## How It Works - -- The **deployment test** (`always_deploy_setup_first.rs::test`) seeds the simulator with a **common state** (contracts deployed once). -- **Subsequent tests reuse this state**, avoiding multiple redeploys. -- Each test still runs in **isolation**: - - A **per-test state** is created and deleted at the beginning of each new test. - - The **common state remains** intact across all tests. - -This makes tests **faster** and ensures **consistent deployments**. - ---- - -## Troubleshooting - -- **“Missing address / not deployed” error** - → Re-run the deployment test (`always_deploy_setup_first.rs::test`). - -- **State seems stale or inconsistent** - → Stop the simulator, delete `state.toml`, restart it, run the deployment test, then your tests. - -- **Unexpected slowness** - → Make sure you ran the deployment test, then only your target tests. diff --git a/interactor/README.md b/interactor/README.md new file mode 100644 index 000000000..125566aab --- /dev/null +++ b/interactor/README.md @@ -0,0 +1,111 @@ +# Automatic Test Wrapper + + +This directory contains scripts that automatically handle the full lifecycle for running interactor tests. + +## What It Does + +When you run `cargo test` for interactor tests, the wrapper automatically: +1. Starts a chain simulator Docker container on a unique port +2. Runs the deployment test to set up common state +3. Deletes `state.toml` for clean test isolation +4. Runs your actual test with the correct port configuration +5. Cleans up the Docker container on exit (success or failure) + +## Setup (One-Time) + +### Step 1: Install direnv + +**macOS:** +```bash +brew install direnv +``` + +**Linux:** +```bash +sudo apt install direnv # or use your package manager +``` + +### Step 2: Add direnv to your shell + +**For bash** - add to `~/.bashrc`: +```bash +eval "$(direnv hook bash)" +``` + +**For zsh** - add to `~/.zshrc`: +```zsh +eval "$(direnv hook zsh)" +``` + +Then reload your shell: +```bash +source ~/.bashrc # or source ~/.zshrc +``` + +### Step 3: Allow direnv in this repo + +```bash +cd /path/to/mx-sovereign-sc +direnv allow +``` + +**Any changes** made to the **`.envrc`** file require running `direnv allow` again for the changes to take effect. + +**Note for IDEs**: Some IDEs (like VS Code) may run commands without direnv active. If tests fail with "Connection refused" on port 8085, the wrapper isn't active. See "IDE Configuration" section below. + +## How It Works + +The `.envrc` file automatically adds `interactor/scripts/` to PATH when you enter the directory. The `cargo` wrapper script in that directory intercepts `cargo test` commands: + +1. **You run `cargo test`** → Shell finds our wrapper (it's first in PATH) +2. **Wrapper checks** if it's an interactor test (package name, test file path, or current directory) +3. **If it's an interactor test:** + - Routes to `cargo-test-wrapper.py` which handles the full lifecycle: + - Start chain simulator on a unique port + - Remove `state.toml` + - Run the deployment test (unless this IS the deployment test) + - Run the actual test + - Stop chain simulator +4. **Non-interactor tests** pass through to the real cargo command + +## IDE Configuration + +Some IDEs don't inherit the direnv environment. If you see "Connection refused" on port 8085, the wrapper isn't active. + +**Option 1: Run from terminal** (Recommended) +Just run tests from your terminal where direnv is active: +```bash +cargo test --package rust-interact --test complete_flow_tests --all-features -- test_name +``` + +**Option 2: VS Code Configuration** + +To use the "Run Test" buttons in VS Code, add the following to `.vscode/settings.json`: + +```json +{ + // Set PATH so rust-analyzer can find the wrapper + "rust-analyzer.server.extraEnv": { + "PATH": "${workspaceFolder}/interactor/scripts:${env:PATH}", + "WORKSPACE_ROOT": "${workspaceFolder}" + }, + + // Set PATH for integrated terminal (so manual cargo commands use wrapper) + "terminal.integrated.env.linux": { + "PATH": "${workspaceFolder}/interactor/scripts:${env:PATH}" + } +} +``` + +**After configuring**, you need to: + - **Reload VS Code window**: Press `Ctrl+Shift+P` → "Developer: Reload Window" (Or restart VS Code completely) + + +**If it still doesn't work**: The test runner might not be using the wrapper. In that case, use Option 1 (terminal) or Option 3 (direct wrapper call). + +**Option 3: Use the wrapper directly** +You can also call the wrapper script directly: +```bash +./interactor/scripts/cargo-test-wrapper.sh test --package rust-interact --test complete_flow_tests --all-features -- test_name +``` diff --git a/interactor/scripts/cargo b/interactor/scripts/cargo new file mode 100755 index 000000000..f1ee7baeb --- /dev/null +++ b/interactor/scripts/cargo @@ -0,0 +1,100 @@ +#!/usr/bin/bash +# Cargo wrapper that intercepts test commands for interactor tests +# This script is activated by direnv (.envrc) which adds it to PATH + +# Ensure basic PATH is set if not already available +# This is needed when the script is run in environments with minimal PATH (e.g., Flycheck) +# Always prepend standard system paths to ensure basic commands are available +STANDARD_PATHS="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" +if [ -z "$PATH" ]; then + export PATH="$STANDARD_PATHS" +else + # Prepend standard paths (duplicates are harmless, first match wins) + export PATH="$STANDARD_PATHS:$PATH" +fi +# Add cargo bin if it exists +if [ -d "$HOME/.cargo/bin" ]; then + export PATH="$HOME/.cargo/bin:$PATH" +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WRAPPER_SCRIPT="${SCRIPT_DIR}/cargo-test-wrapper.py" + +# Configuration +INTERACTOR_DIR="${INTERACTOR_DIR:-interactor}" +INTERACTOR_PACKAGE="${INTERACTOR_PACKAGE_NAME:-rust-interact}" + +# Determine workspace root - try environment variable first, then find it from script location +if [ -n "${WORKSPACE_ROOT:-}" ]; then + # Use provided WORKSPACE_ROOT + : +elif [ -f "${SCRIPT_DIR}/../../.envrc" ]; then + # Find workspace root by looking for .envrc (2 levels up from scripts/) + WORKSPACE_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +else + # Fallback to current directory + WORKSPACE_ROOT="$(pwd)" +fi +export WORKSPACE_ROOT + +# Function to detect if this is an interactor test +_is_interactor_test() { + local package="" + local test_file="" + + # Parse arguments to find --package and --test + while [ $# -gt 0 ]; do + case "$1" in + --package) + package="$2" + shift 2 + ;; + --test) + test_file="$2" + shift 2 + ;; + *) + shift + ;; + esac + done + + # Check if test file is in interactor/tests/ directory + if [ -n "$test_file" ]; then + local test_path="${WORKSPACE_ROOT}/${INTERACTOR_DIR}/tests/${test_file}.rs" + if [ -f "$test_path" ]; then + return 0 + fi + fi + + # Check if package matches interactor package name + if [ -n "$package" ] && [ "$package" = "$INTERACTOR_PACKAGE" ]; then + return 0 + fi + + # Check if current directory is within interactor package + if [[ "$(pwd)" == *"/${INTERACTOR_DIR}"* ]]; then + return 0 + fi + + return 1 +} + +# Only intercept cargo test commands +if [ "$1" = "test" ]; then + # Check if this is an interactor test + if _is_interactor_test "$@"; then + # Debug: log that we're intercepting (can be removed later) + if [ -n "${DEBUG_CARGO_WRAPPER:-}" ]; then + echo "DEBUG: Cargo wrapper intercepting test command: $@" >&2 + fi + # Route to wrapper script + exec "$WRAPPER_SCRIPT" "$@" + fi +fi + +# Not an interactor test or not a test command, use normal cargo +# Find the real cargo command by removing this script's directory from PATH +ORIGINAL_PATH=$(echo "$PATH" | tr ':' '\n' | grep -v "^${SCRIPT_DIR}$" | tr '\n' ':' | sed 's/:$//') +PATH="$ORIGINAL_PATH" command cargo "$@" + diff --git a/interactor/scripts/cargo-test-wrapper.py b/interactor/scripts/cargo-test-wrapper.py new file mode 100755 index 000000000..0be56acad --- /dev/null +++ b/interactor/scripts/cargo-test-wrapper.py @@ -0,0 +1,806 @@ +#!/usr/bin/env python3 +""" +Full lifecycle wrapper for interactor tests +Called by the cargo wrapper script when an interactor test is detected +""" + +import fcntl +import os +import random +import socket +import subprocess +import sys +import tempfile +import time +import urllib.error +import urllib.request +from pathlib import Path +from typing import List, Optional, Tuple + + +INTERACTOR_DIR = "interactor" +INTERACTOR_PACKAGE = "rust-interact" + + +# Maximum number of test cases to run in parallel +# Can be overridden via MAX_TEST_CONCURRENCY environment variable +def get_max_concurrency() -> int: + """Get maximum concurrency from environment or default to 4.""" + return int(os.environ.get("MAX_TEST_CONCURRENCY", "4")) + + +def remove_script_dir_from_path(script_dir: Path) -> str: + """Remove script directory from PATH to avoid wrapper recursion. + + Args: + script_dir: Path to the script directory to remove from PATH. + + Returns: + Modified PATH string with the script directory removed. + """ + original_path = os.environ.get("PATH", "") + path_parts = original_path.split(":") + path_parts = [p for p in path_parts if p != str(script_dir)] + return ":".join(path_parts) + + +def parse_cargo_args(args: List[str]) -> Tuple[Optional[str], Optional[str]]: + """Parse cargo test arguments to extract test file and test name. + + Args: + args: List of command-line arguments from cargo test. + + Returns: + Tuple of (test_file, test_name) where either can be None. + test_file is extracted from --test argument. + test_name is the first non-flag argument after -- separator. + """ + test_file = None + test_name = None + in_test_args = False + + i = 0 + while i < len(args): + arg = args[i] + + if arg == "--test": + if i + 1 < len(args): + test_file = args[i + 1] + i += 2 + else: + i += 1 + elif arg == "--": + in_test_args = True + i += 1 + else: + if in_test_args: + if test_name is None and not arg.startswith("--"): + test_name = arg + i += 1 + + return test_file, test_name + + +def discover_test_cases(test_file: str, package: str, workspace_root: str, filter_test_name: Optional[str] = None) -> List[str]: + """Discover all test cases in a test file using cargo test --list. + + Args: + test_file: Name of the test file to discover cases from. + package: Rust package name containing the tests. + workspace_root: Root directory of the workspace. + filter_test_name: Optional base test function name to filter by. + If provided, only returns test cases matching this name or its rstest cases. + + Returns: + List of discovered test case names. Empty list if discovery fails. + """ + script_dir = Path(__file__).parent.absolute() + env = os.environ.copy() + env["PATH"] = remove_script_dir_from_path(script_dir) + + result = subprocess.run( + [ + "cargo", + "test", + "--package", + package, + "--test", + test_file, + "--all-features", + "--", + "--list", + ], + env=env, + cwd=workspace_root, + capture_output=True, + text=True, + check=False, + ) + + if not result.stdout.strip(): + if result.stderr and "error:" in result.stderr.lower(): + print(f"Discovery error: {result.stderr}", file=sys.stderr) + return [] + + test_cases = [] + for line in result.stdout.split("\n"): + line = line.strip() + if line and line.endswith(": test"): + test_name = line[:-6].strip() + if test_name: + if not filter_test_name or test_name == filter_test_name or test_name.startswith(f"{filter_test_name}::"): + test_cases.append(test_name) + + return test_cases + + +def find_available_port() -> int: + """Find an available port for the chain simulator. + + Generates a port based on PID, timestamp, and random component, + then verifies it's not in use by checking socket binding and Docker. + Uses a lock file to prevent race conditions in parallel execution. + + Returns: + An available port number between 1024 and 65535. + + Raises: + SystemExit: If no available port is found after 200 attempts. + """ + base_port = 8085 + # Use a wider range to avoid collisions in parallel execution + port = base_port + (os.getpid() % 5000) + (int(time.time() * 1000) % 5000) + random.randint(0, 999) + + while port < 1024 or port > 65535: + port = base_port + (port % 5000) + random.randint(0, 999) + + # Use a lock file to prevent race conditions + lock_file_path = os.path.join(tempfile.gettempdir(), f"port_lock_{port}.lock") + lock_file = None + + attempts = 0 + while attempts < 200: # Increased attempts for better reliability + # Check if port is available via socket binding + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + sock.bind(("localhost", port)) + sock.close() + except OSError: + port = base_port + (port % 5000) + random.randint(0, 999) + if port < 1024 or port > 65535: + port = base_port + (port % 5000) + random.randint(0, 999) + attempts += 1 + continue + + # Check Docker containers using both publish filter and name pattern + result = subprocess.run( + ["docker", "ps", "--filter", f"publish={port}", "--format", "{{.ID}}"], + capture_output=True, + text=True, + check=False, + timeout=5, + ) + + if result.returncode == 0 and result.stdout.strip(): + port = base_port + (port % 5000) + random.randint(0, 999) + if port < 1024 or port > 65535: + port = base_port + (port % 5000) + random.randint(0, 999) + attempts += 1 + continue + + # Try to acquire lock on this port + try: + lock_file = open(lock_file_path, "w") + fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) + # Double-check Docker after acquiring lock + result = subprocess.run( + ["docker", "ps", "--filter", f"publish={port}", "--format", "{{.ID}}"], + capture_output=True, + text=True, + check=False, + timeout=5, + ) + if result.returncode == 0 and result.stdout.strip(): + lock_file.close() + try: + os.remove(lock_file_path) + except OSError: + pass + port = base_port + (port % 5000) + random.randint(0, 999) + if port < 1024 or port > 65535: + port = base_port + (port % 5000) + random.randint(0, 999) + attempts += 1 + continue + # Port is available, lock acquired + break + except (IOError, OSError): + # Lock acquisition failed, port might be in use + if lock_file: + lock_file.close() + port = base_port + (port % 5000) + random.randint(0, 999) + if port < 1024 or port > 65535: + port = base_port + (port % 5000) + random.randint(0, 999) + attempts += 1 + continue + + if port > 65535 or attempts >= 200: + if lock_file: + lock_file.close() + try: + os.remove(lock_file_path) + except OSError: + pass + print("Failed to find available port after 200 attempts", file=sys.stderr) + sys.exit(1) + + # Lock will be released when process exits, but we keep it for now + # The lock file will be cleaned up on process exit + return port + + +def wait_for_simulator(port: int, container_name: str, max_attempts: int = 30) -> bool: + """Wait for chain simulator to be ready. + + Polls the simulator's network config endpoint until it responds or max attempts reached. + + Args: + port: Port number where the simulator is running. + container_name: Name of the Docker container running the simulator. + max_attempts: Maximum number of polling attempts (default: 30). + + Returns: + True if simulator is ready, False otherwise. On failure, prints container logs. + """ + url = f"http://localhost:{port}/network/config" + + for i in range(max_attempts): + try: + urllib.request.urlopen(url, timeout=1) + return True + except (urllib.error.URLError, OSError): + if i == max_attempts - 1: + result = subprocess.run(["docker", "logs", container_name], capture_output=True, text=True, check=False) + if result.stdout: + print(result.stdout, file=sys.stderr) + if result.stderr: + print(result.stderr, file=sys.stderr) + return False + time.sleep(1) + + return False + + +def filter_output(output: str) -> str: + """Filter out duplicate and empty 'successes:' and 'failures:' sections. + + Removes redundant summary sections from cargo test output that appear + when multiple tests run in parallel. + + Args: + output: Raw output string from cargo test. + + Returns: + Filtered output string with duplicate/empty sections removed. + """ + if not output: + return output + + lines = output.split("\n") + filtered_lines = [] + i = 0 + while i < len(lines): + line = lines[i] + line_stripped = line.strip() + + if line_stripped in ("successes:", "failures:"): + next_non_empty_idx = i + 1 + while next_non_empty_idx < len(lines) and not lines[next_non_empty_idx].strip(): + next_non_empty_idx += 1 + + should_skip = False + if next_non_empty_idx < len(lines): + next_line_stripped = lines[next_non_empty_idx].strip() + if next_line_stripped == line_stripped: + should_skip = True + elif next_line_stripped in ("successes:", "failures:") and next_line_stripped != line_stripped: + should_skip = True + + if should_skip: + i = next_non_empty_idx + continue + + filtered_lines.append(line) + i += 1 + + return "\n".join(filtered_lines) + + +def print_test_output(case_name: str, output: str, exit_code: int): + """Print output for a completed test case with clear separators. + + Args: + case_name: Name of the test case that completed. + output: Output string from the test execution. + exit_code: Exit code from the test (0 = success, non-zero = failure). + """ + print(f"\n{'='*80}", file=sys.stderr) + print(f"TEST CASE: {case_name}", file=sys.stderr) + print(f"{'='*80}", file=sys.stderr) + + filtered_output = filter_output(output) + if filtered_output: + print(filtered_output, end="", file=sys.stdout) + + status = "PASSED" if exit_code == 0 else "FAILED" + print(f"\n{'='*80}", file=sys.stderr) + print(f"TEST CASE: {case_name} - {status} (exit code: {exit_code})", file=sys.stderr) + print(f"{'='*80}\n", file=sys.stderr) + + +def run_parallel_tests(test_cases: List[str], args: List[str], workspace_root: str, test_file: Optional[str] = None) -> None: + """Run multiple test cases in parallel with concurrency limit. + + Args: + test_cases: List of test case names to run. + args: Original cargo test arguments to reuse. + workspace_root: Root directory of the workspace. + test_file: Optional name of the test file (for summary). + + Exits with 0 if all tests pass, 1 if any fail, 130 on KeyboardInterrupt. + """ + max_concurrency = get_max_concurrency() + print(f"Running {len(test_cases)} test cases with max concurrency: {max_concurrency}", file=sys.stderr) + + processes = {} + completed_processes = set() + exit_codes = {} + container_names = {} # Track containers for cleanup + test_index = 0 + + try: + while test_index < len(test_cases) and len(processes) < max_concurrency: + case_name = test_cases[test_index] + case_args = list(args) + found_separator = False + for i, arg in enumerate(case_args): + if arg == "--": + if i + 1 < len(case_args) and not case_args[i + 1].startswith("--"): + case_args[i + 1] = case_name + else: + case_args.insert(i + 1, case_name) + found_separator = True + break + if not found_separator: + case_args.extend(["--", case_name]) + + wrapper_script = Path(__file__).absolute() + process = subprocess.Popen( + [sys.executable, str(wrapper_script)] + case_args, + env=os.environ.copy(), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + processes[case_name] = process + test_index += 1 + + while len(completed_processes) < len(test_cases): + for case_name, process in list(processes.items()): + if case_name in completed_processes: + continue + + if process.poll() is not None: + output, _ = process.communicate() + exit_code = process.returncode + exit_codes[case_name] = exit_code + completed_processes.add(case_name) + del processes[case_name] + + print_test_output(case_name, output, exit_code) + + if test_index < len(test_cases): + next_case_name = test_cases[test_index] + case_args = list(args) + found_separator = False + for i, arg in enumerate(case_args): + if arg == "--": + if i + 1 < len(case_args) and not case_args[i + 1].startswith("--"): + case_args[i + 1] = next_case_name + else: + case_args.insert(i + 1, next_case_name) + found_separator = True + break + if not found_separator: + case_args.extend(["--", next_case_name]) + + wrapper_script = Path(__file__).absolute() + next_process = subprocess.Popen( + [sys.executable, str(wrapper_script)] + case_args, + env=os.environ.copy(), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + processes[next_case_name] = next_process + test_index += 1 + + if len(completed_processes) < len(test_cases): + time.sleep(0.1) + + # Wait for all child processes to finish + time.sleep(1) + + passed_tests = [] + failed_tests = [] + + for case_name in test_cases: + exit_code = exit_codes.get(case_name, 0) + if exit_code == 0: + passed_tests.append(case_name) + else: + failed_tests.append(case_name) + + total_tests = len(test_cases) + passed_count = len(passed_tests) + failed_count = len(failed_tests) + + print(f"\n{'='*80}", file=sys.stderr) + print(f"TEST SUMMARY", file=sys.stderr) + print(f"{'='*80}", file=sys.stderr) + print(f"Total tests: {total_tests}", file=sys.stderr) + print(f"Passed: {passed_count}", file=sys.stderr) + print(f"Failed: {failed_count}", file=sys.stderr) + + if failed_tests: + print(f"\nFailed tests:", file=sys.stderr) + for test_name in failed_tests: + print(f" - {test_name}", file=sys.stderr) + + # Final cleanup at the end of the process + # In GitHub Actions: only clean up containers from this process + # In local: comprehensive cleanup (all containers, networks, volumes) + cleanup_all_docker_resources() + + overall_exit_code = 0 if failed_count == 0 else 1 + sys.exit(overall_exit_code) + + except KeyboardInterrupt: + for case_name, process in processes.items(): + process.terminate() + try: + process.wait(timeout=5) + except subprocess.TimeoutExpired: + process.kill() + process.wait() + # Final cleanup at the end of the process (even on interrupt) + # In GitHub Actions: only clean up containers from this process + # In local: comprehensive cleanup (all containers, networks, volumes) + cleanup_all_docker_resources() + sys.exit(130) + + +def cleanup_container(container_name: str) -> None: + """Stop and remove a Docker container. + + Args: + container_name: Name of the container to clean up. + """ + # Check if container exists before attempting cleanup + check_result = subprocess.run( + ["docker", "ps", "-a", "--filter", f"name=^{container_name}$", "--format", "{{.Names}}"], + capture_output=True, + text=True, + check=False, + timeout=5, + ) + + if check_result.returncode != 0 or not check_result.stdout.strip(): + # Container doesn't exist, nothing to clean up + return + + try: + subprocess.run( + ["docker", "stop", container_name], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + timeout=10, + ) + subprocess.run( + ["docker", "rm", container_name], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + timeout=10, + ) + except subprocess.TimeoutExpired: + # Force kill if stop times out + subprocess.run( + ["docker", "kill", container_name], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + ) + subprocess.run( + ["docker", "rm", "-f", container_name], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + ) + except Exception: + # Ignore errors during cleanup + pass + + +def is_github_actions() -> bool: + """Check if running in GitHub Actions environment. + + Returns: + True if running in GitHub Actions, False otherwise. + """ + return os.environ.get("GITHUB_ACTIONS") == "true" + + +def cleanup_all_docker_resources(container_name: Optional[str] = None) -> None: + """Clean up Docker resources created by tests. + + In GitHub Actions: Only cleans up the specified container (or containers matching current PID). + In local runs: Performs comprehensive cleanup of all chain-sim- containers, networks, and volumes. + + Args: + container_name: Optional specific container name to clean up. If None and in GitHub Actions, + only cleans up containers matching the current process PID. + """ + try: + if is_github_actions(): + # In GitHub Actions: only clean up containers from this process + if container_name: + # Clean up specific container + cleanup_container(container_name) + else: + # Clean up containers matching current PID pattern + current_pid = os.getpid() + result = subprocess.run( + ["docker", "ps", "-a", "--filter", f"name=chain-sim-", "--format", "{{.Names}}"], + capture_output=True, + text=True, + check=False, + timeout=10, + ) + + if result.returncode == 0 and result.stdout.strip(): + for name in result.stdout.strip().split("\n"): + if name.strip() and f"-{current_pid}-" in name: + cleanup_container(name.strip()) + else: + # Local runs: comprehensive cleanup + # Clean up all chain-sim- containers + result = subprocess.run( + ["docker", "ps", "-a", "--filter", "name=chain-sim-", "--format", "{{.ID}}"], + capture_output=True, + text=True, + check=False, + timeout=10, + ) + + if result.returncode == 0 and result.stdout.strip(): + container_ids = result.stdout.strip().split("\n") + for container_id in container_ids: + if container_id.strip(): + try: + subprocess.run( + ["docker", "stop", container_id.strip()], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + timeout=10, + ) + subprocess.run( + ["docker", "rm", "-f", container_id.strip()], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + timeout=10, + ) + except Exception: + pass + + # Clean up dangling networks + subprocess.run( + ["docker", "network", "prune", "-f"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + timeout=30, + ) + + # Clean up dangling volumes + subprocess.run( + ["docker", "volume", "prune", "-f"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + timeout=30, + ) + except Exception: + # Ignore errors during cleanup + pass + + +def start_simulator_container(port: int, container_name: str) -> bool: + """Start the chain simulator Docker container and wait for it to be ready. + + Args: + port: Port number to use for the simulator. + container_name: Name for the Docker container. + + Returns: + True if simulator started successfully, False otherwise. + """ + print(f"Starting chain simulator on port {port}...", file=sys.stderr) + result = subprocess.run( + ["docker", "run", "-d", "-p", f"{port}:8085", "--memory=2g", "--name", container_name, "multiversx/chainsimulator"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + ) + + if result.returncode != 0: + print("Failed to start chain simulator", file=sys.stderr) + return False + + if not wait_for_simulator(port, container_name): + print("Chain simulator failed to start after 30 seconds", file=sys.stderr) + cleanup_container(container_name) + return False + + return True + + +def run_test(args: List[str], script_dir: Path, port: int, test_file: Optional[str], test_name: Optional[str]) -> int: + """Run the actual test case. + + Args: + args: Original cargo test arguments. + script_dir: Path to the script directory. + port: Port number where the simulator is running. + test_file: Name of the test file, if specified. + test_name: Name of the test, if specified. + + Returns: + Exit code from the test (0 for success, non-zero for failure). + """ + env = os.environ.copy() + env["PATH"] = remove_script_dir_from_path(script_dir) + env["CHAIN_SIMULATOR_PORT"] = str(port) + + if test_file and test_name: + test_identifier = f"{test_file}:{test_name}" + elif test_file: + test_identifier = f"{test_file}:*" + else: + test_identifier = "unknown" + + env["TEST_IDENTIFIER"] = test_identifier + env["TEST_PORT"] = str(port) + + cargo_args = list(args) + if "--show-output" not in cargo_args: + found_separator = False + for i, arg in enumerate(cargo_args): + if arg == "--": + found_separator = True + cargo_args.insert(i + 1, "--show-output") + break + if not found_separator: + cargo_args.extend(["--", "--show-output"]) + + result = subprocess.run(["cargo"] + cargo_args, env=env, check=False) + return result.returncode + + +def main(): + workspace_root = os.environ.get("WORKSPACE_ROOT", os.getcwd()) + script_dir = Path(__file__).parent.absolute() + + args = sys.argv[1:] + test_file, test_name = parse_cargo_args(args) + + # Three cases: + # 1. All tests: test_file specified, no test_name -> discover all tests in file + # 2. One test with cases: test_name without "::" -> discover all cases for this test + # 3. Specific case: test_name with "::" -> run directly, no discovery needed + has_base_test_name = test_name and "::" not in test_name + should_discover = test_file and (not test_name or has_base_test_name) + filter_test_name = test_name if has_base_test_name else None + + if should_discover: + test_cases = discover_test_cases(test_file, INTERACTOR_PACKAGE, workspace_root, filter_test_name) + + if test_cases: + run_parallel_tests(test_cases, args, workspace_root, test_file) + else: + sys.exit(1) + + port_str = os.environ.get("CHAIN_SIMULATOR_PORT") + container_name = None + if port_str: + # This is a child process running a specific test (spawned by parallel runner) + # Each child creates its own container, so we need to track it for cleanup + port = int(port_str) + exit_code = 0 + + try: + # Check if container already exists (shouldn't happen in normal flow) + result = subprocess.run( + ["docker", "ps", "--filter", f"publish={port}", "--format", "{{.Names}}"], + capture_output=True, + text=True, + check=False, + ) + if result.returncode == 0 and result.stdout.strip(): + container_name = result.stdout.strip().split("\n")[0] + print(f"Using existing chain simulator container '{container_name}' on port {port}", file=sys.stderr) + if not wait_for_simulator(port, container_name, max_attempts=5): + print(f"Warning: Container {container_name} on port {port} may not be ready", file=sys.stderr) + else: + # Create a new container for this child process + random_suffix = random.randint(1000, 9999) + container_name = f"chain-sim-{port}-{os.getpid()}-{int(time.time())}-{random_suffix}" + if not start_simulator_container(port, container_name): + exit_code = 1 + container_name = None + + if exit_code == 0 and container_name: + exit_code = run_test(args, script_dir, port, test_file, test_name) + except KeyboardInterrupt: + exit_code = 130 + except Exception as e: + print(f"Unexpected error: {e}", file=sys.stderr) + exit_code = 1 + finally: + # Final cleanup at the end of the child process (always runs) + if container_name: + if is_github_actions(): + cleanup_all_docker_resources(container_name) + else: + cleanup_container(container_name) + # Additional comprehensive cleanup for local runs + cleanup_all_docker_resources() + + sys.exit(exit_code) + + # This is a single test execution (not spawned by parallel runner) + port = find_available_port() + os.environ["CHAIN_SIMULATOR_PORT"] = str(port) + + random_suffix = random.randint(1000, 9999) + container_name = f"chain-sim-{port}-{os.getpid()}-{int(time.time())}-{random_suffix}" + + exit_code = 0 + try: + if not start_simulator_container(port, container_name): + exit_code = 1 + else: + exit_code = run_test(args, script_dir, port, test_file, test_name) + except KeyboardInterrupt: + exit_code = 130 + except Exception as e: + print(f"Unexpected error: {e}", file=sys.stderr) + exit_code = 1 + finally: + # Final cleanup at the end of the process (always runs) + if container_name: + if is_github_actions(): + cleanup_all_docker_resources(container_name) + else: + cleanup_container(container_name) + # Additional comprehensive cleanup for local runs + cleanup_all_docker_resources() + + sys.exit(exit_code) + + +if __name__ == "__main__": + main() diff --git a/interactor/src/complete_flows/complete_flows_interactor_main.rs b/interactor/src/complete_flows/complete_flows_interactor_main.rs index 7722bd994..8dc9f8a54 100644 --- a/interactor/src/complete_flows/complete_flows_interactor_main.rs +++ b/interactor/src/complete_flows/complete_flows_interactor_main.rs @@ -1,6 +1,5 @@ #![allow(non_snake_case)] -use common_interactor::interactor_common_state::CommonState; use common_interactor::interactor_helpers::InteractorHelpers; use common_interactor::interactor_state::{EsdtTokenInfo, State}; use common_interactor::interactor_structs::{ActionConfig, BalanceCheckConfig}; @@ -9,7 +8,7 @@ use common_interactor::{ }; use common_test_setup::base_setup::init::ExpectedLogs; use common_test_setup::constants::{ - DEPOSIT_EVENT, INTERACTOR_WORKING_DIR, MULTI_ESDT_NFT_TRANSFER_EVENT, SHARD_1, + DEPLOY_COST, DEPOSIT_EVENT, INTERACTOR_WORKING_DIR, MULTI_ESDT_NFT_TRANSFER_EVENT, SHARD_1, SOVEREIGN_RECEIVER_ADDRESS, TOKEN_DISPLAY_NAME, TOKEN_TICKER, }; use common_test_setup::log; @@ -19,6 +18,7 @@ use multiversx_sc::api::{ESDT_LOCAL_MINT_FUNC_NAME, ESDT_NFT_CREATE_FUNC_NAME}; use multiversx_sc::chain_core::EGLD_000000_TOKEN_IDENTIFIER; use multiversx_sc_snippets::imports::*; use multiversx_sc_snippets::multiversx_sc_scenario::multiversx_chain_vm::crypto_functions::sha256; +use structs::configs::SovereignConfig; use structs::fee::FeeStruct; use structs::generate_hash::GenerateHash; use structs::operation::OperationData; @@ -28,7 +28,6 @@ pub struct CompleteFlowInteract { pub interactor: Interactor, pub user_address: Address, pub state: State, - pub common_state: CommonState, } impl InteractorHelpers for CompleteFlowInteract { @@ -40,10 +39,6 @@ impl InteractorHelpers for CompleteFlowInteract { &mut self.state } - fn common_state(&mut self) -> &mut CommonState { - &mut self.common_state - } - fn user_address(&self) -> &Address { &self.user_address } @@ -64,6 +59,18 @@ impl CompleteFlowInteract { println!("Skipping token initialization for real network"); } } + + println!("Deploying and completing setup phase"); + interactor + .deploy_and_complete_setup_phase( + OptionalValue::Some(DEPLOY_COST.into()), + OptionalValue::Some(SovereignConfig::default_config_for_test()), + OptionalValue::None, + OptionalValue::None, + ) + .await; + println!("Deployed and completed setup phase"); + interactor } @@ -83,7 +90,6 @@ impl CompleteFlowInteract { interactor, user_address, state: State::default(), - common_state: CommonState::load_state(), } } @@ -143,8 +149,6 @@ impl CompleteFlowInteract { .is_execute(false); self.check_balances_after_action(balance_config).await; - self.update_fee_market_balance_state(fee, payment_vec, config.shard) - .await; } async fn register_and_execute_operation( @@ -221,9 +225,9 @@ impl CompleteFlowInteract { } async fn register_sovereign_token(&mut self, shard: u32, token: EsdtTokenInfo) -> String { - let mvx_esdt_safe_address = self.common_state().get_mvx_esdt_safe_address(shard).clone(); + let mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(shard).clone(); let nonce = self - .common_state() + .state() .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()); let operation = RegisterTokenOperation { diff --git a/interactor/src/mvx_esdt_safe/mvx_esdt_safe_interactor_main.rs b/interactor/src/mvx_esdt_safe/mvx_esdt_safe_interactor_main.rs index 4e58a1e9b..54c91216d 100644 --- a/interactor/src/mvx_esdt_safe/mvx_esdt_safe_interactor_main.rs +++ b/interactor/src/mvx_esdt_safe/mvx_esdt_safe_interactor_main.rs @@ -1,15 +1,14 @@ use common_interactor::{ - common_sovereign_interactor::CommonInteractorTrait, interactor_common_state::CommonState, - interactor_helpers::InteractorHelpers, + common_sovereign_interactor::CommonInteractorTrait, interactor_helpers::InteractorHelpers, }; -use common_test_setup::base_setup::init::ExpectedLogs; +use common_test_setup::{base_setup::init::ExpectedLogs, constants::DEPLOY_COST}; use multiversx_sc_snippets::{ imports::*, multiversx_sc_scenario::multiversx_chain_vm::crypto_functions::sha256, }; use proxies::mvx_esdt_safe_proxy::MvxEsdtSafeProxy; use structs::{ - configs::{EsdtSafeConfig, UpdateEsdtSafeConfigOperation}, + configs::{EsdtSafeConfig, SovereignConfig, UpdateEsdtSafeConfigOperation}, generate_hash::GenerateHash, }; @@ -29,7 +28,6 @@ pub struct MvxEsdtSafeInteract { pub interactor: Interactor, pub user_address: Address, pub state: State, - pub common_state: CommonState, } impl InteractorHelpers for MvxEsdtSafeInteract { @@ -41,10 +39,6 @@ impl InteractorHelpers for MvxEsdtSafeInteract { &mut self.state } - fn common_state(&mut self) -> &mut CommonState { - &mut self.common_state - } - fn user_address(&self) -> &Address { &self.user_address } @@ -66,6 +60,17 @@ impl MvxEsdtSafeInteract { } } + println!("Deploying and completing setup phase"); + interactor + .deploy_and_complete_setup_phase( + OptionalValue::Some(DEPLOY_COST.into()), + OptionalValue::Some(SovereignConfig::default_config_for_test()), + OptionalValue::None, + OptionalValue::None, + ) + .await; + println!("Deployed and completed setup phase"); + interactor } @@ -85,7 +90,6 @@ impl MvxEsdtSafeInteract { interactor, user_address, state: State::default(), - common_state: CommonState::load_state(), } } @@ -104,10 +108,11 @@ impl MvxEsdtSafeInteract { pub async fn complete_setup_phase(&mut self, shard: u32) { let caller = self.get_bridge_owner_for_shard(shard).clone(); + let mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(shard).clone(); self.interactor .tx() .from(&caller) - .to(self.common_state.get_mvx_esdt_safe_address(shard).clone()) + .to(mvx_esdt_safe_address) .gas(90_000_000u64) .typed(MvxEsdtSafeProxy) .complete_setup_phase() @@ -118,9 +123,10 @@ impl MvxEsdtSafeInteract { pub async fn upgrade(&mut self) { let caller = self.get_bridge_owner_for_shard(SHARD_0).clone(); + let mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(SHARD_0).clone(); self.interactor .tx() - .to(self.common_state.get_mvx_esdt_safe_address(SHARD_0).clone()) + .to(mvx_esdt_safe_address) .from(caller) .gas(90_000_000u64) .typed(MvxEsdtSafeProxy) @@ -139,12 +145,12 @@ impl MvxEsdtSafeInteract { expected_log_error: Option<&str>, ) { let bridge_service = self.get_bridge_service_for_shard(shard); - let mvx_esdt_safe_address = self.common_state.get_mvx_esdt_safe_address(shard).clone(); + let mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(shard).clone(); let operation: UpdateEsdtSafeConfigOperation = UpdateEsdtSafeConfigOperation { esdt_safe_config, nonce: self - .common_state() + .state() .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), }; @@ -178,10 +184,11 @@ impl MvxEsdtSafeInteract { } pub async fn set_fee_market_address(&mut self, caller: Address, fee_market_address: Address) { + let mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(SHARD_0).clone(); self.interactor .tx() .from(caller) - .to(self.common_state.get_mvx_esdt_safe_address(SHARD_0).clone()) + .to(mvx_esdt_safe_address) .gas(90_000_000u64) .typed(MvxEsdtSafeProxy) .set_fee_market_address(fee_market_address) @@ -196,7 +203,7 @@ impl MvxEsdtSafeInteract { shard: u32, expected_amount: BigUint, ) { - let mvx_esdt_safe_address = self.common_state.get_mvx_esdt_safe_address(shard).clone(); + let mvx_esdt_safe_address = self.state().get_mvx_esdt_safe_address(shard).clone(); let result = self .interactor() .query() diff --git a/interactor/tests/always_deploy_setup_first.rs b/interactor/tests/always_deploy_setup_first.rs deleted file mode 100644 index 19e2c3e26..000000000 --- a/interactor/tests/always_deploy_setup_first.rs +++ /dev/null @@ -1,27 +0,0 @@ -use common_interactor::common_sovereign_interactor::CommonInteractorTrait; -use common_interactor::interactor_config::Config; -use common_test_setup::constants::DEPLOY_COST; -use multiversx_sc::imports::OptionalValue; -use multiversx_sc_snippets::imports::tokio; -use rust_interact::mvx_esdt_safe::mvx_esdt_safe_interactor_main::MvxEsdtSafeInteract; -use serial_test::serial; -use structs::configs::SovereignConfig; - -/// ### SETUP -/// DEPLOY_CONTRACTS -/// -/// ### ACTION -/// Deploys and completes setup phases for all smart contracts -#[tokio::test] -#[serial] -#[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] -async fn deploy_setup() { - let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor - .deploy_and_complete_setup_phase( - OptionalValue::Some(DEPLOY_COST.into()), - OptionalValue::Some(SovereignConfig::default_config_for_test()), - OptionalValue::None, - ) - .await; -} diff --git a/interactor/tests/complete_flow_tests.rs b/interactor/tests/complete_flow_tests.rs index b3c761a6e..6d43ededc 100644 --- a/interactor/tests/complete_flow_tests.rs +++ b/interactor/tests/complete_flow_tests.rs @@ -23,7 +23,6 @@ use multiversx_sc_snippets::imports::{tokio, StaticApi}; use multiversx_sc_snippets::multiversx_sc_scenario::multiversx_chain_vm::vm_err_msg::FUNCTION_NOT_FOUND; use rstest::rstest; use rust_interact::complete_flows::complete_flows_interactor_main::CompleteFlowInteract; -use serial_test::serial; use structs::operation::Operation; use structs::operation::OperationData; use structs::operation::TransferData; @@ -43,13 +42,10 @@ use structs::OperationHashStatus; #[case::sync_to_sync(SHARD_0)] #[case::sync_to_async(SHARD_1)] #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_complete_deposit_flow_no_fee_only_transfer_data(#[case] shard: u32) { let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(shard).await; - chain_interactor .deposit_wrapper( ActionConfig::new() @@ -73,7 +69,6 @@ async fn test_complete_deposit_flow_no_fee_only_transfer_data(#[case] shard: u32 #[case::sync_to_sync(SHARD_0)] #[case::sync_to_async(SHARD_1)] #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_complete_deposit_flow_with_fee_only_transfer_data(#[case] shard: u32) { let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; @@ -105,13 +100,10 @@ async fn test_complete_deposit_flow_with_fee_only_transfer_data(#[case] shard: u #[case::sync_to_sync(SHARD_0)] #[case::sync_to_async(SHARD_1)] #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_complete_execute_flow_with_transfer_data_only_success(#[case] shard: u32) { let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(shard).await; - let expected_logs = if shard == SHARD_0 { vec![log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT])] } else { @@ -141,13 +133,10 @@ async fn test_complete_execute_flow_with_transfer_data_only_success(#[case] shar #[case::sync_to_sync(SHARD_0)] #[case::sync_to_async(SHARD_1)] #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_complete_execute_flow_with_transfer_data_only_fail(#[case] shard: u32) { let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(shard).await; - let expected_logs = if shard == SHARD_0 { vec![ log!(EXECUTE_OPERATION_ENDPOINT, topics: [EXECUTED_BRIDGE_OP_EVENT], data: Some(FUNCTION_NOT_FOUND)), @@ -185,7 +174,6 @@ async fn test_complete_execute_flow_with_transfer_data_only_fail(#[case] shard: #[case::dynamic_sft(EsdtTokenType::DynamicSFT)] #[case::dynamic_meta(EsdtTokenType::DynamicMeta)] #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_deposit_with_fee( #[case] token_type: EsdtTokenType, @@ -222,7 +210,6 @@ async fn test_deposit_with_fee( #[case::dynamic_sft(EsdtTokenType::DynamicSFT)] #[case::dynamic_meta(EsdtTokenType::DynamicMeta)] #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_deposit_without_fee_and_execute( #[case] token_type: EsdtTokenType, @@ -233,8 +220,6 @@ async fn test_deposit_without_fee_and_execute( let token = chain_interactor.get_token_by_type(token_type, token_index); - chain_interactor.remove_fee_wrapper(shard).await; - chain_interactor .deposit_wrapper(ActionConfig::new().shard(shard), Some(token.clone()), None) .await; @@ -267,7 +252,6 @@ async fn test_deposit_without_fee_and_execute( #[case::dynamic_sft(EsdtTokenType::DynamicSFT, BigUint::from(ONE_HUNDRED_TOKENS))] #[case::dynamic_meta(EsdtTokenType::DynamicMeta, BigUint::from(ONE_HUNDRED_TOKENS))] #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_register_execute_and_deposit_sov_token( #[case] token_type: EsdtTokenType, @@ -276,8 +260,6 @@ async fn test_register_execute_and_deposit_sov_token( ) { let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(shard).await; - let (nonce, decimals) = chain_interactor.generate_nonce_and_decimals(token_type); let token_id = chain_interactor.create_random_sovereign_token_id(shard); @@ -333,7 +315,6 @@ async fn test_register_execute_and_deposit_sov_token( #[case::dynamic_sft(EsdtTokenType::DynamicSFT)] #[case::dynamic_meta(EsdtTokenType::DynamicMeta)] #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_deposit_mvx_token_with_transfer_data( #[case] token_type: EsdtTokenType, @@ -342,8 +323,6 @@ async fn test_deposit_mvx_token_with_transfer_data( ) { let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(shard).await; - let token = chain_interactor.get_token_by_type(token_type, token_index); chain_interactor @@ -374,7 +353,6 @@ async fn test_deposit_mvx_token_with_transfer_data( #[case::dynamic_sft(EsdtTokenType::DynamicSFT)] #[case::dynamic_meta(EsdtTokenType::DynamicMeta)] #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_deposit_mvx_token_with_transfer_data_and_fee( #[case] token_type: EsdtTokenType, @@ -417,7 +395,6 @@ async fn test_deposit_mvx_token_with_transfer_data_and_fee( #[case::dynamic_sft(EsdtTokenType::DynamicSFT)] #[case::dynamic_meta(EsdtTokenType::DynamicMeta)] #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_deposit_and_execute_with_transfer_data( #[case] token_type: EsdtTokenType, @@ -428,8 +405,6 @@ async fn test_deposit_and_execute_with_transfer_data( let token = chain_interactor.get_token_by_type(token_type, token_index); - chain_interactor.remove_fee_wrapper(shard).await; - chain_interactor .deposit_wrapper(ActionConfig::new().shard(shard), Some(token.clone()), None) .await; @@ -468,7 +443,6 @@ async fn test_deposit_and_execute_with_transfer_data( #[case::dynamic_sft(EsdtTokenType::DynamicSFT, BigUint::from(ONE_HUNDRED_TOKENS))] #[case::dynamic_meta(EsdtTokenType::DynamicMeta, BigUint::from(ONE_HUNDRED_TOKENS))] #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_register_execute_with_transfer_data_and_deposit_sov_token( #[case] token_type: EsdtTokenType, @@ -477,8 +451,6 @@ async fn test_register_execute_with_transfer_data_and_deposit_sov_token( ) { let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(shard).await; - let (nonce, decimals) = chain_interactor.generate_nonce_and_decimals(token_type); let token_id = chain_interactor.create_random_sovereign_token_id(shard); @@ -545,7 +517,6 @@ async fn test_register_execute_with_transfer_data_and_deposit_sov_token( #[case::dynamic_sft(EsdtTokenType::DynamicSFT, BigUint::from(ONE_HUNDRED_TOKENS))] #[case::dynamic_meta(EsdtTokenType::DynamicMeta, BigUint::from(ONE_HUNDRED_TOKENS))] #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_register_execute_call_failed( #[case] token_type: EsdtTokenType, @@ -554,8 +525,6 @@ async fn test_register_execute_call_failed( ) { let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(shard).await; - let (nonce, decimals) = chain_interactor.generate_nonce_and_decimals(token_type); let token_id = chain_interactor.create_random_sovereign_token_id(shard); @@ -591,20 +560,17 @@ async fn test_register_execute_call_failed( #[case::async_call(SHARD_1)] #[case::sync_call(SHARD_0)] #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_execute_operation_transfer_data_only_async_call_in_endpoint(#[case] shard: u32) { let mut chain_interactor = CompleteFlowInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(SHARD_1).await; - let mvx_esdt_safe_address = chain_interactor - .common_state + .state() .get_mvx_esdt_safe_address(SHARD_1) .clone(); let wanted_mvx_esdt_safe_address = chain_interactor - .common_state + .state() .get_mvx_esdt_safe_address(shard) .clone(); @@ -618,7 +584,7 @@ async fn test_execute_operation_transfer_data_only_async_call_in_endpoint(#[case let operation_data = OperationData::new( chain_interactor - .common_state() + .state() .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), ManagedAddress::from_address(&chain_interactor.user_address), Some(transfer_data), @@ -627,7 +593,7 @@ async fn test_execute_operation_transfer_data_only_async_call_in_endpoint(#[case let operation = Operation::new( ManagedAddress::from_address( &chain_interactor - .common_state() + .state() .current_testing_sc_address() .to_address(), ), diff --git a/interactor/tests/mvx_esdt_safe_tests.rs b/interactor/tests/mvx_esdt_safe_tests.rs index 16f2f672d..db4586c19 100644 --- a/interactor/tests/mvx_esdt_safe_tests.rs +++ b/interactor/tests/mvx_esdt_safe_tests.rs @@ -21,7 +21,6 @@ use multiversx_sc::chain_core::EGLD_000000_TOKEN_IDENTIFIER; use multiversx_sc_snippets::imports::*; use multiversx_sc_snippets::multiversx_sc_scenario::multiversx_chain_vm::crypto_functions::sha256; use rust_interact::mvx_esdt_safe::mvx_esdt_safe_interactor_main::MvxEsdtSafeInteract; -use serial_test::serial; use std::vec; use structs::aliases::PaymentsVec; use structs::configs::{EsdtSafeConfig, MaxBridgedAmount}; @@ -39,7 +38,6 @@ use structs::OperationHashStatus; /// ### EXPECTED /// Error 'MAX_GAS_LIMIT_PER_TX_EXCEEDED' log #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_update_invalid_config() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; @@ -68,13 +66,10 @@ async fn test_update_invalid_config() { /// ### EXPECTED /// Error 'DEPOSIT_OVER_MAX_AMOUNT' log #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_deposit_max_bridged_amount_exceeded() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(SHARD_1).await; - let config = EsdtSafeConfig { max_bridged_token_amounts: ManagedVec::from(vec![MaxBridgedAmount { token_id: chain_interactor.state.get_first_fungible_token_identifier(), @@ -123,13 +118,10 @@ async fn test_deposit_max_bridged_amount_exceeded() { /// ### EXPECTED /// Error NOTHING_TO_TRANSFER #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_deposit_nothing_to_transfer() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(SHARD_1).await; - chain_interactor .deposit_in_mvx_esdt_safe( SOVEREIGN_RECEIVER_ADDRESS.to_address(), @@ -154,13 +146,10 @@ async fn test_deposit_nothing_to_transfer() { /// ### EXPECTED /// The deposit is successful #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_deposit_no_transfer_data() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(SHARD_1).await; - let esdt_token_payment_one = EgldOrEsdtTokenPayment::::new( chain_interactor.state.get_first_fungible_token_identifier(), 0, @@ -205,13 +194,10 @@ async fn test_deposit_no_transfer_data() { /// ### EXPECTED /// Error GAS_LIMIT_TOO_HIGH #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_deposit_gas_limit_too_high_no_fee() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(SHARD_1).await; - let shard = SHARD_1; let config = EsdtSafeConfig { max_tx_gas_limit: 1, @@ -266,13 +252,10 @@ async fn test_deposit_gas_limit_too_high_no_fee() { /// ### EXPECTED /// Error BANNED_ENDPOINT_NAME #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_deposit_endpoint_banned_no_fee() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(SHARD_1).await; - let config = EsdtSafeConfig { banned_endpoints: ManagedVec::from(vec![ManagedBuffer::from(TESTING_SC_ENDPOINT)]), ..EsdtSafeConfig::default_config() @@ -326,7 +309,6 @@ async fn test_deposit_endpoint_banned_no_fee() { /// ### EXPECTED /// USER's balance is updated #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_deposit_fee_enabled() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; @@ -383,9 +365,6 @@ async fn test_deposit_fee_enabled() { chain_interactor .check_balances_after_action(balance_config) .await; - chain_interactor - .update_fee_market_balance_state(Some(fee), payments_vec, SHARD_1) - .await; } /// ### TEST @@ -397,7 +376,6 @@ async fn test_deposit_fee_enabled() { /// ### EXPECTED /// Error ERR_EMPTY_PAYMENTS #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_deposit_transfer_data_only_with_fee_nothing_to_transfer() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; @@ -438,13 +416,10 @@ async fn test_deposit_transfer_data_only_with_fee_nothing_to_transfer() { /// ### EXPECTED /// The endpoint is called in the testing smart contract #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_deposit_only_transfer_data_no_fee() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(SHARD_1).await; - let gas_limit = 1000u64; let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); let args = MultiValueEncoded::from(ManagedVec::>::from( @@ -479,13 +454,10 @@ async fn test_deposit_only_transfer_data_no_fee() { /// ### EXPECTED /// Error NO_ESDT_SAFE_ADDRESS #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_execute_operation_no_operation_registered() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(SHARD_1).await; - let payment = OperationEsdtPayment::new( chain_interactor.state.get_first_fungible_token_identifier(), 0, @@ -501,7 +473,7 @@ async fn test_execute_operation_no_operation_registered() { let operation = Operation::new( ManagedAddress::from_address( &chain_interactor - .common_state() + .state() .current_testing_sc_address() .to_address(), ), @@ -540,13 +512,10 @@ async fn test_execute_operation_no_operation_registered() { /// ### EXPECTED /// The operation is executed in the testing smart contract #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_execute_operation_with_egld_success_no_fee() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(SHARD_0).await; - let token = EsdtTokenInfo { token_id: EgldOrEsdtTokenIdentifier::egld(), amount: BigUint::from(EGLD_0_05), @@ -575,13 +544,13 @@ async fn test_execute_operation_with_egld_success_no_fee() { let transfer_data = TransferData::new(gas_limit, function, args); let mvx_esdt_safe_address = chain_interactor - .common_state + .state() .get_mvx_esdt_safe_address(SHARD_0) .clone(); let operation_data = OperationData::new( chain_interactor - .common_state() + .state() .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), ManagedAddress::from_address(&chain_interactor.user_address), Some(transfer_data), @@ -590,7 +559,7 @@ async fn test_execute_operation_with_egld_success_no_fee() { let operation = Operation::new( ManagedAddress::from_address( &chain_interactor - .common_state() + .state() .current_testing_sc_address() .to_address(), ), @@ -675,13 +644,10 @@ async fn test_execute_operation_with_egld_success_no_fee() { /// ### EXPECTED /// The operation is executed in the testing smart contract #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_execute_operation_only_transfer_data_no_fee() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(SHARD_0).await; - let gas_limit = 90_000_000u64; let function = ManagedBuffer::::from(TESTING_SC_ENDPOINT); let args = @@ -689,13 +655,13 @@ async fn test_execute_operation_only_transfer_data_no_fee() { let transfer_data = TransferData::new(gas_limit, function, args); let mvx_esdt_safe_address = chain_interactor - .common_state + .state() .get_mvx_esdt_safe_address(SHARD_0) .clone(); let operation_data = OperationData::new( chain_interactor - .common_state() + .state() .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), ManagedAddress::from_address(&chain_interactor.user_address), Some(transfer_data), @@ -704,7 +670,7 @@ async fn test_execute_operation_only_transfer_data_no_fee() { let operation = Operation::new( ManagedAddress::from_address( &chain_interactor - .common_state() + .state() .current_testing_sc_address() .to_address(), ), @@ -758,13 +724,10 @@ async fn test_execute_operation_only_transfer_data_no_fee() { /// ### EXPECTED /// The operation is executed in the testing smart contract #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_execute_operation_native_token_success_no_fee() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(SHARD_1).await; - let token_data = EsdtTokenData { amount: BigUint::from(TEN_TOKENS), ..Default::default() @@ -775,7 +738,7 @@ async fn test_execute_operation_native_token_success_no_fee() { let payment = OperationEsdtPayment::new(native_token.clone(), 0, token_data); let mvx_esdt_safe_address = chain_interactor - .common_state + .state() .get_mvx_esdt_safe_address(SHARD_1) .clone(); @@ -788,7 +751,7 @@ async fn test_execute_operation_native_token_success_no_fee() { let operation_data = OperationData::new( chain_interactor - .common_state() + .state() .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), ManagedAddress::from_address(&chain_interactor.user_address), Some(transfer_data), @@ -797,7 +760,7 @@ async fn test_execute_operation_native_token_success_no_fee() { let operation = Operation::new( ManagedAddress::from_address( &chain_interactor - .common_state() + .state() .current_testing_sc_address() .to_address(), ), @@ -869,13 +832,10 @@ async fn test_execute_operation_native_token_success_no_fee() { /// ### EXPECTED /// Transaction is successful and the logs contain the TOKEN_NOT_REGISTERED error #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_execute_operation_sovereign_token_not_registered() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(SHARD_1).await; - let sov_token_id = chain_interactor.create_random_sovereign_token_id(SHARD_1); let token_data = EsdtTokenData { @@ -890,13 +850,13 @@ async fn test_execute_operation_sovereign_token_not_registered() { ); let mvx_esdt_safe_address = chain_interactor - .common_state + .state() .get_mvx_esdt_safe_address(SHARD_1) .clone(); let operation_data = OperationData::new( chain_interactor - .common_state() + .state() .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), ManagedAddress::from_address(&chain_interactor.user_address), None, @@ -953,22 +913,15 @@ async fn test_execute_operation_sovereign_token_not_registered() { /// ### EXPECTED /// The token is burned and the deposited amount is tracked in storage, then the mechanism is switched #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_switch_mechanism_with_deposit() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(SHARD_0).await; + let trusted_token_id = chain_interactor.state().get_trusted_token().token_id; + let trusted_token = chain_interactor.state().get_trusted_token(); - let trusted_token = chain_interactor.common_state().get_trusted_token(); - let trusted_token_id = EgldOrEsdtTokenIdentifier::esdt(trusted_token.as_str()); - let trusted_token_info = EsdtTokenInfo { - token_id: trusted_token_id.clone(), - amount: BigUint::from(ONE_HUNDRED_TOKENS), - nonce: 0, - decimals: 18, - token_type: EsdtTokenType::Fungible, - }; + let trusted_token_info = + chain_interactor.clone_token_with_amount(trusted_token, BigUint::from(ONE_HUNDRED_TOKENS)); chain_interactor .set_token_burn_mechanism(trusted_token_id.clone(), SHARD_0) @@ -998,10 +951,6 @@ async fn test_switch_mechanism_with_deposit() { ) .await; - chain_interactor - .common_state() - .add_to_deposited_amount(deposit_amount.clone()); - let balance_config = BalanceCheckConfig::new() .shard(SHARD_0) .token(Some(trusted_token_info.clone())) @@ -1033,10 +982,6 @@ async fn test_switch_mechanism_with_deposit() { ) .await; - chain_interactor - .common_state() - .add_to_deposited_amount(deposit_amount.clone()); - // Since the mechanism was switched, the trusted token amount was minted in the sc, now we check for both the mint and the new deposit amount let balance_config = BalanceCheckConfig::new() .shard(SHARD_0) @@ -1061,22 +1006,15 @@ async fn test_switch_mechanism_with_deposit() { /// ### EXPECTED /// The operation is executed successfully and the deposited amount is updated in storage #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_execute_operation_with_burn_mechanism() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(SHARD_0).await; + let trusted_token_id = chain_interactor.state().get_trusted_token().token_id; + let trusted_token = chain_interactor.state().get_trusted_token(); - let trusted_token = chain_interactor.common_state().get_trusted_token(); - let trusted_token_id = EgldOrEsdtTokenIdentifier::esdt(trusted_token.as_str()); - let trusted_token_info = EsdtTokenInfo { - token_id: trusted_token_id.clone(), - amount: BigUint::from(ONE_HUNDRED_TOKENS), - nonce: 0, - decimals: 18, - token_type: EsdtTokenType::Fungible, - }; + let trusted_token_info = + chain_interactor.clone_token_with_amount(trusted_token, BigUint::from(ONE_HUNDRED_TOKENS)); chain_interactor .set_token_burn_mechanism(trusted_token_id.clone(), SHARD_0) @@ -1106,10 +1044,6 @@ async fn test_execute_operation_with_burn_mechanism() { ) .await; - chain_interactor - .common_state() - .add_to_deposited_amount(deposit_amount.clone()); - let balance_config = BalanceCheckConfig::new() .shard(SHARD_0) .token(Some(trusted_token_info.clone())) @@ -1120,10 +1054,8 @@ async fn test_execute_operation_with_burn_mechanism() { .check_balances_after_action(balance_config) .await; - let current_deposited_amount = chain_interactor.common_state().get_deposited_amount(); - chain_interactor - .check_deposited_tokens_amount(trusted_token_id.clone(), SHARD_0, current_deposited_amount) + .check_deposited_tokens_amount(trusted_token_id.clone(), SHARD_0, deposit_amount.clone()) .await; let operation = chain_interactor @@ -1157,10 +1089,6 @@ async fn test_execute_operation_with_burn_mechanism() { ) .await; - chain_interactor - .common_state() - .subtract_from_deposited_amount(deposit_amount.clone()); - let balance_config = BalanceCheckConfig::new() .shard(SHARD_0) .token(Some(trusted_token_info)) @@ -1172,10 +1100,8 @@ async fn test_execute_operation_with_burn_mechanism() { .check_balances_after_action(balance_config) .await; - let current_deposited_amount = chain_interactor.common_state().get_deposited_amount(); - chain_interactor - .check_deposited_tokens_amount(trusted_token_id.clone(), SHARD_0, current_deposited_amount) + .check_deposited_tokens_amount(trusted_token_id.clone(), SHARD_0, BigUint::zero()) .await; } @@ -1188,13 +1114,10 @@ async fn test_execute_operation_with_burn_mechanism() { /// ### EXPECTED /// The native token is minted and then burned via deposit #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_execute_and_deposit_with_native_token_no_fee() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(SHARD_1).await; - let token_data = EsdtTokenData { amount: BigUint::from(TEN_TOKENS), ..Default::default() @@ -1205,13 +1128,13 @@ async fn test_execute_and_deposit_with_native_token_no_fee() { let payment = OperationEsdtPayment::new(native_token.clone(), 0, token_data); let mvx_esdt_safe_address = chain_interactor - .common_state + .state() .get_mvx_esdt_safe_address(SHARD_1) .clone(); let operation_data = OperationData::new( chain_interactor - .common_state() + .state() .get_and_increment_operation_nonce(&mvx_esdt_safe_address.to_string()), ManagedAddress::from_address(&chain_interactor.user_address), None, @@ -1305,11 +1228,9 @@ async fn test_execute_and_deposit_with_native_token_no_fee() { .await; } #[tokio::test] -#[serial] #[cfg_attr(not(feature = "chain-simulator-tests"), ignore)] async fn test_pause_contract() { let mut chain_interactor = MvxEsdtSafeInteract::new(Config::chain_simulator_config()).await; - chain_interactor.remove_fee_wrapper(SHARD_1).await; chain_interactor.switch_pause_status(true, SHARD_1).await;