diff --git a/.env b/.env index 3d017649..5a4c0003 100644 --- a/.env +++ b/.env @@ -12,3 +12,7 @@ export SPONSOR_SECRET_KEY=413f42575f7f26fad3317a778771212fdb80245850981e48b58a4f # For the Github workflows, a repository secret (private deep history instance) is being used. export MAINNET_PROXY_URL="https://gateway.multiversx.com" + +# For internal testing: +export INTERNAL_TESTNET_PROXY_URL="" +export INTERNAL_TESTNET_EXPLORER_URL="" diff --git a/.github/workflows/check_with_mesh_cli.yml b/.github/workflows/check_with_mesh_cli.yml index e0ba4354..ebcd5d67 100644 --- a/.github/workflows/check_with_mesh_cli.yml +++ b/.github/workflows/check_with_mesh_cli.yml @@ -1,4 +1,4 @@ -name: Check with Mesh CLI +name: Check with Mesh CLI (testnet) on: pull_request: diff --git a/.github/workflows/check_with_mesh_cli_on_localnet.yml b/.github/workflows/check_with_mesh_cli_on_localnet.yml new file mode 100644 index 00000000..33263c39 --- /dev/null +++ b/.github/workflows/check_with_mesh_cli_on_localnet.yml @@ -0,0 +1,53 @@ +name: Check with Mesh CLI (localnet) + +on: + pull_request: + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/setup-python@v5 + with: + python-version: 3.11 + + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + pipx install multiversx-sdk-cli --force + pip install -r ./requirements-dev.txt + curl -sSfL https://raw.githubusercontent.com/coinbase/mesh-cli/master/scripts/install.sh | sh -s -- -b "$HOME/.local/bin" + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Build + run: | + cd $GITHUB_WORKSPACE/cmd/rosetta && go build . + cd $GITHUB_WORKSPACE/systemtests && go build ./proxyToObserverAdapter.go + + - name: Set up MultiversX localnet + run: | + mkdir -p ~/localnet && cd ~/localnet + mxpy localnet setup --configfile=${GITHUB_WORKSPACE}/systemtests/localnet.toml + + # Start the localnet and store the PID + nohup mxpy localnet start --configfile=${GITHUB_WORKSPACE}/systemtests/localnet.toml > localnet.log 2>&1 & echo $! > localnet.pid + sleep 60 # Allow time for the localnet to start + + - name: Generate testdata + run: | + source .env + PYTHONPATH=. python3 ./systemtests/generate_testdata_on_network.py setup --network localnet + PYTHONPATH=. python3 ./systemtests/generate_testdata_on_network.py run --network localnet + + - name: check:data + run: | + source .env + PYTHONPATH=. python3 ./systemtests/check_with_mesh_cli.py --mode=data --network=localnet + + - name: Stop MultiversX localnet + if: success() || failure() + run: | + kill $(cat localnet.pid) || echo "Testnet already stopped" diff --git a/.gitignore b/.gitignore index cb5d62f4..f4d5bfd4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ cmd/rosetta/logs/** systemtests/logs/** systemtests/memento/** systemtests/**/check-data/** +systemtests/localnet/** logs/** **/__pycache__/** diff --git a/README.md b/README.md index b3d5ac07..0bdff046 100644 --- a/README.md +++ b/README.md @@ -255,6 +255,9 @@ Run the checks: ``` PYTHONPATH=. python3 ./systemtests/check_with_mesh_cli.py --mode=data --network=testnet + +# Or, continuously: +while PYTHONPATH=. python3 ./systemtests/check_with_mesh_cli.py --mode=data --network=testnet; do sleep 5; done ``` ### Check:construction diff --git a/systemtests/config.py b/systemtests/config.py index 9cabb5e8..05513841 100644 --- a/systemtests/config.py +++ b/systemtests/config.py @@ -19,10 +19,12 @@ class Configuration: check_data_configuration_file: str check_data_directory: str check_data_num_blocks: int - explorer_url: str + view_url: str + custom_currency_issue_cost: int = 50000000000000000 memento_file: str = "" sponsor_secret_key: bytes = bytes.fromhex(os.environ.get("SPONSOR_SECRET_KEY", "")) users_mnemonic: str = os.environ.get("USERS_MNEMONIC", "") + num_users: int = 128 CONFIGURATIONS = { @@ -36,14 +38,14 @@ class Configuration: observer_url="", proxy_url=os.environ.get("MAINNET_PROXY_URL", "https://gateway.multiversx.com"), activation_epoch_sirius=1265, - activation_epoch_spica=4294967295, + activation_epoch_spica=1538, check_construction_native_configuration_file="", check_construction_custom_configuration_file="", check_data_configuration_file="systemtests/mesh_cli_config/check-data.json", check_data_directory="systemtests/mainnet-data", check_data_num_blocks=4096, memento_file="systemtests/memento/mainnet.json", - explorer_url="https://explorer.multiversx.com", + view_url="https://explorer.multiversx.com/transactions/{hash}", ), "devnet": Configuration( network_shard=0, @@ -55,14 +57,14 @@ class Configuration: observer_url="", proxy_url="https://devnet-gateway.multiversx.com", activation_epoch_sirius=629, - activation_epoch_spica=4294967295, + activation_epoch_spica=2327, check_construction_native_configuration_file="systemtests/mesh_cli_config/devnet-construction-native.json", check_construction_custom_configuration_file="systemtests/mesh_cli_config/devnet-construction-custom.json", check_data_configuration_file="systemtests/mesh_cli_config/check-data.json", check_data_directory="systemtests/devnet-data", check_data_num_blocks=0, memento_file="systemtests/memento/devnet.json", - explorer_url="https://devnet-explorer.multiversx.com", + view_url="https://devnet-explorer.multiversx.com/transactions/{hash}", ), "testnet": Configuration( network_shard=0, @@ -81,7 +83,7 @@ class Configuration: check_data_directory="systemtests/testnet-data", check_data_num_blocks=0, memento_file="systemtests/memento/testnet.json", - explorer_url="https://testnet-explorer.multiversx.com", + view_url="https://testnet-explorer.multiversx.com/transactions/{hash}", ), "localnet": Configuration( network_shard=0, @@ -89,17 +91,38 @@ class Configuration: network_name="untitled", native_currency="EGLD", config_file_custom_currencies="systemtests/rosetta_config/localnet-custom-currencies.json", - num_historical_epochs=2, + num_historical_epochs=3, observer_url="", proxy_url="http://localhost:7950", activation_epoch_sirius=1, - activation_epoch_spica=4294967295, + activation_epoch_spica=4, check_construction_native_configuration_file="systemtests/mesh_cli_config/localnet-construction-native.json", check_construction_custom_configuration_file="systemtests/mesh_cli_config/localnet-construction-custom.json", check_data_configuration_file="systemtests/mesh_cli_config/check-data.json", check_data_directory="systemtests/localnet-data", check_data_num_blocks=0, memento_file="systemtests/memento/localnet.json", - explorer_url="", + view_url="http://localhost:7950/transaction/{hash}?withResults=true&withLogs=true", + custom_currency_issue_cost=5000000000000000000 + ), + "internal": Configuration( + network_shard=0, + network_id="1", + network_name="untitled", + native_currency="EGLD", + config_file_custom_currencies="systemtests/rosetta_config/internal-custom-currencies.json", + num_historical_epochs=1, + observer_url="", + proxy_url=os.environ.get("INTERNAL_TESTNET_PROXY_URL", ""), + activation_epoch_sirius=1, + activation_epoch_spica=4, + check_construction_native_configuration_file="systemtests/mesh_cli_config/internal-construction-native.json", + check_construction_custom_configuration_file="systemtests/mesh_cli_config/internal-construction-custom.json", + check_data_configuration_file="systemtests/mesh_cli_config/check-data.json", + check_data_directory="systemtests/internal-data", + check_data_num_blocks=0, + memento_file="systemtests/memento/internal.json", + view_url=f"{os.environ.get('INTERNAL_TESTNET_EXPLORER_URL', '')}/transactions/{{hash}}", + custom_currency_issue_cost=5000000000000000000 ), } diff --git a/systemtests/contracts/dummy.wasm b/systemtests/contracts/dummy.wasm index 3c4efc12..db3b205a 100755 Binary files a/systemtests/contracts/dummy.wasm and b/systemtests/contracts/dummy.wasm differ diff --git a/systemtests/contracts/forwarder.wasm b/systemtests/contracts/forwarder.wasm new file mode 100755 index 00000000..fa83dd0e Binary files /dev/null and b/systemtests/contracts/forwarder.wasm differ diff --git a/systemtests/generate_testdata_on_network.py b/systemtests/generate_testdata_on_network.py index 5135ff7b..918efe3a 100644 --- a/systemtests/generate_testdata_on_network.py +++ b/systemtests/generate_testdata_on_network.py @@ -17,12 +17,14 @@ TransactionsFactoryConfig, TransferTransactionsFactory, UserSecretKey, UserSigner) +from multiversx_sdk.abi import AddressValue, Serializer, StringValue, U32Value from multiversx_sdk.network_providers.transactions import TransactionOnNetwork from systemtests.config import CONFIGURATIONS, Configuration CONTRACT_PATH_ADDER = Path(__file__).parent / "contracts" / "adder.wasm" CONTRACT_PATH_DUMMY = Path(__file__).parent / "contracts" / "dummy.wasm" +CONTRACT_PATH_FORWARDER = Path(__file__).parent / "contracts" / "forwarder.wasm" CONTRACT_PATH_DEVELOPER_REWARDS = Path(__file__).parent / "contracts" / "developer_rewards.wasm" @@ -53,6 +55,10 @@ def do_setup(args: Any): accounts = BunchOfAccounts(configuration, memento) controller = Controller(configuration, accounts, memento) + memento.clear() + + controller.wait_until_epoch(configuration.activation_epoch_sirius) + print("Do airdrops for native currency...") controller.do_airdrops_for_native_currency() @@ -65,6 +71,8 @@ def do_setup(args: Any): print("Do contract deployments...") controller.do_create_contract_deployments() + memento.replace_setup_transactions(controller.transactions_hashes_accumulator) + def do_run(args: Any): network = args.network @@ -73,41 +81,51 @@ def do_run(args: Any): accounts = BunchOfAccounts(configuration, memento) controller = Controller(configuration, accounts, memento) + controller.wait_until_epoch(configuration.activation_epoch_spica) + print("## Intra-shard, simple MoveBalance with refund") - controller.send(controller.create_simple_move_balance_with_refund( + controller.send(controller.create_simple_move_balance( sender=accounts.get_user(shard=0, index=0), receiver=accounts.get_user(shard=0, index=1).address, - )) + additional_gas_limit=42000, + ), await_completion=True) print("## Cross-shard, simple MoveBalance with refund") - controller.send(controller.create_simple_move_balance_with_refund( + controller.send(controller.create_simple_move_balance( sender=accounts.get_user(shard=0, index=1), receiver=accounts.get_user(shard=1, index=0).address, - )) + additional_gas_limit=42000, + ), await_completion=True) print("## Intra-shard, invalid MoveBalance with refund") - controller.send(controller.create_invalid_move_balance_with_refund( + controller.send(controller.create_simple_move_balance( sender=accounts.get_user(shard=0, index=2), receiver=accounts.get_user(shard=0, index=3).address, - )) + amount=1000000000000000000000000, + additional_gas_limit=42000, + ), await_completion=True) print("## Cross-shard, invalid MoveBalance with refund") - controller.send(controller.create_invalid_move_balance_with_refund( - sender=accounts.get_user(shard=0, index=4), + controller.send(controller.create_simple_move_balance( + sender=accounts.get_user(shard=0, index=3), receiver=accounts.get_user(shard=1, index=1).address, - )) + amount=1000000000000000000000000, + additional_gas_limit=42000, + ), await_completion=True) print("## Intra-shard, sending value to non-payable contract") - controller.send(controller.create_simple_move_balance_with_refund( + controller.send(controller.create_simple_move_balance( sender=accounts.get_user(shard=0, index=0), receiver=accounts.get_contract_address("adder", 0, 0), - )) + additional_gas_limit=42000, + ), await_completion=True) print("## Cross-shard, sending value to non-payable contract") - controller.send(controller.create_simple_move_balance_with_refund( + controller.send(controller.create_simple_move_balance( sender=accounts.get_user(shard=0, index=1), receiver=accounts.get_contract_address("adder", 1, 0), - )) + additional_gas_limit=42000, + ), await_completion=True) print("## Intra-shard, native transfer within MultiESDTTransfer") controller.send(controller.create_native_transfer_within_multiesdt( @@ -115,7 +133,7 @@ def do_run(args: Any): receiver=accounts.get_user(shard=0, index=1).address, native_amount=42, custom_amount=7 - )) + ), await_completion=True) print("## Cross-shard, native transfer within MultiESDTTransfer") controller.send(controller.create_native_transfer_within_multiesdt( @@ -123,7 +141,7 @@ def do_run(args: Any): receiver=accounts.get_user(shard=1, index=0).address, native_amount=42, custom_amount=7 - )) + ), await_completion=True) print("## Intra-shard, native transfer within MultiESDTTransfer, towards non-payable contract") controller.send(controller.create_native_transfer_within_multiesdt( @@ -131,7 +149,7 @@ def do_run(args: Any): receiver=accounts.get_contract_address("adder", 0, 0), native_amount=42, custom_amount=7 - )) + ), await_completion=True) print("## Cross-shard, native transfer within MultiESDTTransfer, towards non-payable contract") controller.send(controller.create_native_transfer_within_multiesdt( @@ -139,75 +157,25 @@ def do_run(args: Any): receiver=accounts.get_contract_address("adder", 1, 0), native_amount=42, custom_amount=7 - )) + ), await_completion=True) print("## Cross-shard, transfer & execute with native & custom transfer") - controller.send(controller.create_native_transfer_within_multiesdt_transfer_and_execute( + controller.send(controller.create_transfer_and_execute( sender=accounts.get_user(shard=0, index=1), - contract=accounts.get_contract_address("dummy", shard=2, index=0), + contract=accounts.get_contract_address("dummy", shard=0, index=0), function="doSomething", native_amount=42, custom_amount=7, - )) + ), await_completion=True) print("## Intra-shard, transfer & execute with native & custom transfer") - controller.send(controller.create_native_transfer_within_multiesdt_transfer_and_execute( - sender=accounts.get_user(shard=0, index=4), + controller.send(controller.create_transfer_and_execute( + sender=accounts.get_user(shard=0, index=3), contract=accounts.get_contract_address("dummy", shard=0, index=0), function="doSomething", native_amount=42, custom_amount=7, - )) - - print("## Cross-shard, transfer & execute with native & custom transfer, wrapped in Relayed V3") - controller.send( - controller.create_relayed_v3_with_inner_transactions( - relayer=accounts.get_user(shard=0, index=0), - inner_transactions=[ - controller.create_native_transfer_within_multiesdt_transfer_and_execute( - sender=accounts.get_user(shard=0, index=1), - contract=accounts.get_contract_address("dummy", shard=2, index=0), - function="doSomething", - native_amount=42, - custom_amount=7, - seal_for_broadcast=False, - ), - controller.create_native_transfer_within_multiesdt_transfer_and_execute( - sender=accounts.get_user(shard=0, index=2), - contract=accounts.get_contract_address("dummy", shard=2, index=0), - function="doNothing", - native_amount=42, - custom_amount=7, - seal_for_broadcast=False - ) - ] - ) - ) - - print("## Intra-shard, transfer & execute with native & custom transfer, wrapped in Relayed V3") - controller.send( - controller.create_relayed_v3_with_inner_transactions( - relayer=accounts.get_user(shard=0, index=0), - inner_transactions=[ - controller.create_native_transfer_within_multiesdt_transfer_and_execute( - sender=accounts.get_user(shard=0, index=3), - contract=accounts.get_contract_address("dummy", shard=0, index=0), - function="doSomething", - native_amount=42, - custom_amount=7, - seal_for_broadcast=False, - ), - controller.create_native_transfer_within_multiesdt_transfer_and_execute( - sender=accounts.get_user(shard=0, index=4), - contract=accounts.get_contract_address("dummy", shard=0, index=0), - function="doNothing", - native_amount=42, - custom_amount=7, - seal_for_broadcast=False - ) - ] - ) - ) + ), await_completion=True) print("## Intra-shard, relayed v1 transaction with MoveBalance") controller.send(controller.create_relayed_v1_with_move_balance( @@ -215,98 +183,41 @@ def do_run(args: Any): sender=accounts.get_user(shard=0, index=1), receiver=accounts.get_user(shard=0, index=2).address, amount=42 - )) - - print("## Relayed v3, senders and receivers in same shard") - controller.send(controller.create_relayed_v3_with_a_few_inner_move_balances( - relayer=accounts.get_user(shard=0, index=0), - senders=accounts.get_users_by_shard(0)[1:3], - receivers=[account.address for account in accounts.get_users_by_shard(0)[3:5]], - amount=42 - )) - - print("## Relayed v3, senders and receivers in different shards") - controller.send(controller.create_relayed_v3_with_a_few_inner_move_balances( - relayer=accounts.get_user(shard=0, index=0), - senders=accounts.get_users_by_shard(0)[1:3], - receivers=[account.address for account in accounts.get_users_by_shard(1)[3:5]], - amount=42 - )) - - print("## Relayed v3, senders and receivers in same shard (insufficient balance)") - controller.send(controller.create_relayed_v3_with_a_few_inner_move_balances( - relayer=accounts.get_user(shard=0, index=0), - senders=accounts.get_users_by_shard(0)[1:3], - receivers=[account.address for account in accounts.get_users_by_shard(0)[3:5]], - amount=1000000000000000000000 - )) - - print("## Relayed v3, senders and receivers in different shards (insufficient balance)") - controller.send(controller.create_relayed_v3_with_a_few_inner_move_balances( - relayer=accounts.get_user(shard=0, index=0), - senders=accounts.get_users_by_shard(0)[1:3], - receivers=[account.address for account in accounts.get_users_by_shard(1)[3:5]], - amount=1000000000000000000000 - )) - - print("## Relayed v3, senders and receivers in same shard, sending to non-payable contract") - controller.send(controller.create_relayed_v3_with_a_few_inner_move_balances( - relayer=accounts.get_user(shard=0, index=0), - senders=[accounts.get_user(shard=0, index=5)], - receivers=[accounts.get_contract_address("adder", 0, 0)], - amount=1000000000000000000 - )) - - print("## Relayed v3, completely intra-shard, with a few contract calls") - controller.send(controller.create_relayed_v3_with_a_few_contract_calls( - relayer=accounts.get_user(shard=0, index=0), - senders=[accounts.get_user(shard=0, index=0)] * 7, - contracts=[accounts.get_contract_address("dummy", shard=0, index=0)] * 7, - inner_transaction_amount=0 - )) - - print("## Relayed v3, cross-shard, with gas limit = 599999999") - controller.send(controller.create_relayed_v3_with_a_few_contract_calls( - relayer=accounts.get_user(shard=0, index=0), - senders=[accounts.get_user(shard=0, index=0)], - contracts=[accounts.get_contract_address("dummy", shard=1, index=0)], - inner_transaction_amount=0, - inner_transaction_gas_limit=599949999, - )) + ), await_completion=True) print("## Intra-shard, relayed v1 transaction with MoveBalance (with bad receiver, system smart contract)") controller.send(controller.create_relayed_v1_with_move_balance( relayer=accounts.get_user(shard=1, index=0), - sender=accounts.get_user(shard=1, index=9), + sender=accounts.get_user(shard=1, index=2), receiver=Address.from_bech32("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u"), amount=1000000000000000000 - )) + ), await_completion=True) print("## Direct contract deployment with MoveBalance") controller.send(controller.create_contract_deployment_with_move_balance( sender=accounts.get_user(shard=0, index=0), amount=10000000000000000 - )) + ), await_completion=True) print("## Intra-shard, contract call with MoveBalance, with signal error") controller.send(controller.create_contract_call_with_move_balance_with_signal_error( sender=accounts.get_user(shard=0, index=0), contract=accounts.get_contract_address("adder", 0, 0), amount=10000000000000000 - )) + ), await_completion=True) print("## Cross-shard, contract call with MoveBalance, with signal error") controller.send(controller.create_contract_call_with_move_balance_with_signal_error( sender=accounts.get_user(shard=0, index=0), contract=accounts.get_contract_address("adder", 1, 0), amount=10000000000000000 - )) + ), await_completion=True) print("## Direct contract deployment with MoveBalance, with signal error") controller.send(controller.create_contract_deployment_with_move_balance_with_signal_error( sender=accounts.get_user(shard=0, index=0), amount=77 - )) + ), await_completion=True) print("## Intra-shard, relayed v1 transaction with contract call with MoveBalance, with signal error") controller.send(controller.create_relayed_v1_with_contract_call_with_move_balance_with_signal_error( @@ -314,13 +225,21 @@ def do_run(args: Any): sender=accounts.get_user(shard=0, index=1), contract=accounts.get_contract_address("adder", 0, 0), amount=1 - )) + ), await_completion=True) + + print("## Cross-shard, relayed v1 transaction with contract call with MoveBalance, with signal error (1)") + controller.send(controller.create_relayed_v1_with_contract_call_with_move_balance_with_signal_error( + relayer=accounts.get_user(shard=0, index=0), + sender=accounts.get_user(shard=0, index=1), + contract=accounts.get_contract_address("adder", 1, 0), + amount=1 + ), await_completion=True) print("## Intra-shard ClaimDeveloperRewards on directly owned contract") controller.send(controller.create_claim_developer_rewards_on_directly_owned_contract( sender=accounts.get_user(shard=0, index=0), contract=accounts.get_contract_address("adder", 0, 0), - )) + ), await_completion=True) print("## Cross-shard ClaimDeveloperRewards on directly owned contract") controller.do_change_contract_owner( @@ -337,13 +256,39 @@ def do_run(args: Any): controller.send(controller.create_claim_developer_rewards_on_child_contract( sender=accounts.get_user(shard=0, index=0), parent_contract=accounts.get_contract_address("developerRewards", shard=0, index=0), - )) + ), await_completion=True) print("## ClaimDeveloperRewards on child contract (owned by a contract); user & parent contract in different shards") controller.send(controller.create_claim_developer_rewards_on_child_contract( sender=accounts.get_user(shard=0, index=0), parent_contract=accounts.get_contract_address("developerRewards", shard=1, index=0), - )) + ), await_completion=True) + + print("## Intra-shard, transfer native within MultiESDTTransfer (fuzzy, with tx.value != 0)") + controller.send(controller.create_native_transfer_within_multiesdt_fuzzy( + sender=accounts.get_user(shard=0, index=2), + receiver=accounts.get_user(shard=0, index=3).address, + native_amount_as_value=42, + native_amount_in_data=[43, 44] + ), await_completion=True) + + print("## Intra-shard, transfer native within MultiESDTTransfer (fuzzy, with tx.value == 0)") + controller.send(controller.create_native_transfer_within_multiesdt_fuzzy( + sender=accounts.get_user(shard=0, index=3), + receiver=accounts.get_user(shard=0, index=3).address, + native_amount_as_value=0, + native_amount_in_data=[43, 44] + ), await_completion=True) + + print("## Cross-shard, transfer native within MultiESDTTransfer (fuzzy, with tx.value == 0)") + controller.send(controller.create_native_transfer_within_multiesdt_fuzzy( + sender=accounts.get_user(shard=0, index=2), + receiver=accounts.get_user(shard=1, index=3).address, + native_amount_as_value=0, + native_amount_in_data=[43, 44] + ), await_completion=True) + + memento.replace_run_transactions(controller.transactions_hashes_accumulator) class BunchOfAccounts: @@ -355,7 +300,7 @@ def __init__(self, configuration: Configuration, memento: "Memento") -> None: self.users_by_bech32: Dict[str, Account] = {} self.contracts: List[SmartContract] = [] - for i in range(128): + for i in range(configuration.num_users): user = self._create_user(i) self.users.append(user) self.users_by_bech32[user.address.to_bech32()] = user @@ -442,6 +387,7 @@ def __init__(self, configuration: Configuration, accounts: BunchOfAccounts, meme self.transaction_computer = TransactionComputer() self.transactions_converter = TransactionsConverter() self.transactions_factory_config = TransactionsFactoryConfig(chain_id=configuration.network_id) + self.transactions_factory_config.issue_cost = configuration.custom_currency_issue_cost self.nonces_tracker = NoncesTracker(configuration.proxy_url) self.token_management_transactions_factory = TokenManagementTransactionsFactory(self.transactions_factory_config) self.token_management_outcome_parser = TokenManagementTransactionsOutcomeParser() @@ -450,6 +396,7 @@ def __init__(self, configuration: Configuration, accounts: BunchOfAccounts, meme self.contracts_transactions_factory = SmartContractTransactionsFactory(self.transactions_factory_config) self.contracts_query_controller = SmartContractQueriesController(QueryRunnerAdapter(self.network_provider)) self.transaction_awaiter = TransactionAwaiter(self) + self.transactions_hashes_accumulator: list[str] = [] # Temporary workaround, until the SDK is updated to simplify transaction awaiting. def get_transaction(self, tx_hash: str) -> TransactionOnNetwork: @@ -523,6 +470,7 @@ def do_airdrops_for_custom_currencies(self): def do_create_contract_deployments(self): transactions_adder: List[Transaction] = [] transactions_dummy: List[Transaction] = [] + transactions_forwarder: List[Transaction] = [] transactions_developer_rewards: List[Transaction] = [] address_computer = AddressComputer() @@ -570,6 +518,28 @@ def do_create_contract_deployments(self): arguments=[] )) + # Forwarder + transactions_forwarder.append(self.contracts_transactions_factory.create_transaction_for_deploy( + sender=self.accounts.get_user(shard=0, index=0).address, + bytecode=CONTRACT_PATH_FORWARDER, + gas_limit=25000000, + arguments=[] + )) + + transactions_forwarder.append(self.contracts_transactions_factory.create_transaction_for_deploy( + sender=self.accounts.get_user(shard=1, index=0).address, + bytecode=CONTRACT_PATH_FORWARDER, + gas_limit=25000000, + arguments=[] + )) + + transactions_forwarder.append(self.contracts_transactions_factory.create_transaction_for_deploy( + sender=self.accounts.get_user(shard=2, index=0).address, + bytecode=CONTRACT_PATH_FORWARDER, + gas_limit=25000000, + arguments=[] + )) + # Developer rewards transactions_developer_rewards.append(self.contracts_transactions_factory.create_transaction_for_deploy( sender=self.accounts.get_user(shard=0, index=0).address, @@ -592,7 +562,7 @@ def do_create_contract_deployments(self): arguments=[] )) - transactions_all = transactions_adder + transactions_dummy + transactions_developer_rewards + transactions_all = transactions_adder + transactions_dummy + transactions_forwarder + transactions_developer_rewards for transaction in transactions_all: self.apply_nonce(transaction) @@ -608,6 +578,11 @@ def do_create_contract_deployments(self): contract_address = address_computer.compute_contract_address(sender, transaction.nonce) self.memento.add_contract("dummy", contract_address.to_bech32()) + for transaction in transactions_forwarder: + sender = Address.from_bech32(transaction.sender) + contract_address = address_computer.compute_contract_address(sender, transaction.nonce) + self.memento.add_contract("forwarder", contract_address.to_bech32()) + for transaction in transactions_developer_rewards: sender = Address.from_bech32(transaction.sender) contract_address = address_computer.compute_contract_address(sender, transaction.nonce) @@ -664,42 +639,57 @@ def do_change_contract_owner(self, contract: Address, new_owner: "Account"): self.send(transaction) self.await_completed([transaction]) - def create_simple_move_balance_with_refund(self, sender: "Account", receiver: Address) -> Transaction: + def create_simple_move_balance(self, sender: "Account", receiver: Address, amount: int = 42, additional_gas_limit: int = 0) -> Transaction: transaction = self.transfer_transactions_factory.create_transaction_for_native_token_transfer( sender=sender.address, receiver=receiver, - native_amount=42 + native_amount=amount ) - transaction.gas_limit += 42000 + transaction.gas_limit += additional_gas_limit self.apply_nonce(transaction) self.sign(transaction) return transaction - def create_invalid_move_balance_with_refund(self, sender: "Account", receiver: Address) -> Transaction: - transaction = self.transfer_transactions_factory.create_transaction_for_native_token_transfer( + def create_native_transfer_within_multiesdt(self, sender: "Account", receiver: Address, native_amount: int, custom_amount: int) -> Transaction: + custom_currency = self.memento.get_custom_currencies()[0] + + transaction = self.transfer_transactions_factory.create_transaction_for_transfer( sender=sender.address, receiver=receiver, - native_amount=1000000000000000000000000 + native_amount=native_amount, + token_transfers=[TokenTransfer(Token(custom_currency), custom_amount)] ) - transaction.gas_limit += 42000 - self.apply_nonce(transaction) self.sign(transaction) return transaction - def create_native_transfer_within_multiesdt(self, sender: "Account", receiver: Address, native_amount: int, custom_amount: int) -> Transaction: - custom_currency = self.memento.get_custom_currencies()[0] + def create_native_transfer_within_multiesdt_fuzzy(self, sender: "Account", receiver: Address, native_amount_as_value: int, native_amount_in_data: list[int]) -> Transaction: + serializer = Serializer(parts_separator="@") - transaction = self.transfer_transactions_factory.create_transaction_for_transfer( - sender=sender.address, - receiver=receiver, - native_amount=native_amount, - token_transfers=[TokenTransfer(Token(custom_currency), custom_amount)] + data_args = [ + AddressValue(receiver.pubkey), + U32Value(len(native_amount_in_data)) + ] + + for amount in native_amount_in_data: + data_args.append(StringValue("EGLD-000000")) + data_args.append(U32Value(0)) + data_args.append(U32Value(amount)) + + data = "MultiESDTNFTTransfer@" + serializer.serialize(data_args) + + transaction = Transaction( + sender=sender.address.to_bech32(), + receiver=sender.address.to_bech32(), + gas_limit=5000000, + chain_id=self.configuration.network_id, + value=native_amount_as_value, + data=data.encode() ) self.apply_nonce(transaction) @@ -707,8 +697,12 @@ def create_native_transfer_within_multiesdt(self, sender: "Account", receiver: A return transaction - def create_native_transfer_within_multiesdt_transfer_and_execute(self, sender: "Account", contract: Address, function: str, native_amount: int, custom_amount: int, seal_for_broadcast: bool = True) -> Transaction: - custom_currency = self.memento.get_custom_currencies()[0] + def create_transfer_and_execute(self, sender: "Account", contract: Address, function: str, native_amount: int, custom_amount: int, seal_for_broadcast: bool = True) -> Transaction: + token_transfers: List[TokenTransfer] = [] + + if custom_amount: + custom_currency = self.memento.get_custom_currencies()[0] + token_transfers = [TokenTransfer(Token(custom_currency), custom_amount)] transaction = self.contracts_transactions_factory.create_transaction_for_execute( sender=sender.address, @@ -716,7 +710,7 @@ def create_native_transfer_within_multiesdt_transfer_and_execute(self, sender: " function=function, gas_limit=3000000, native_transfer_amount=native_amount, - token_transfers=[TokenTransfer(Token(custom_currency), custom_amount)] + token_transfers=token_transfers ) if seal_for_broadcast: @@ -799,97 +793,6 @@ def create_relayed_v2_with_move_balance(self, relayer: "Account", sender: "Accou return transaction - def create_relayed_v3_with_a_few_inner_move_balances(self, relayer: "Account", senders: List["Account"], receivers: List[Address], amount: int) -> Transaction: - # Relayer nonce is reserved before sender nonce, to ensure good ordering (if sender and relayer are the same account). - relayer_nonce = self._reserve_nonce(relayer) - - if len(senders) != len(receivers): - raise ValueError("senders and receivers must have the same length", len(senders), len(receivers)) - - inner_transactions: List[Transaction] = [] - - for sender, receiver in zip(senders, receivers): - inner_transaction = self.transfer_transactions_factory.create_transaction_for_native_token_transfer( - sender=sender.address, - receiver=receiver, - native_amount=amount, - ) - - inner_transaction.relayer = relayer.address.to_bech32() - self.apply_nonce(inner_transaction) - self.sign(inner_transaction) - inner_transactions.append(inner_transaction) - - transaction = self.relayed_transactions_factory.create_relayed_v3_transaction( - relayer_address=relayer.address, - inner_transactions=inner_transactions, - ) - - transaction.nonce = relayer_nonce - self.sign(transaction) - - return transaction - - def create_relayed_v3_with_a_few_contract_calls(self, - relayer: "Account", - senders: List["Account"], - contracts: List[Address], - inner_transaction_amount: int, - inner_transaction_gas_limit: int = 3000000) -> Transaction: - # Relayer nonce is reserved before sender nonce, to ensure good ordering (if sender and relayer are the same account). - relayer_nonce = self._reserve_nonce(relayer) - - if len(senders) != len(contracts): - raise ValueError("senders and receivers must have the same length", len(senders), len(contracts)) - - inner_transactions: List[Transaction] = [] - - for sender, contract in zip(senders, contracts): - inner_transaction = self.contracts_transactions_factory.create_transaction_for_execute( - sender=sender.address, - contract=contract, - function="doSomething", - gas_limit=inner_transaction_gas_limit, - arguments=[], - native_transfer_amount=inner_transaction_amount - ) - - inner_transaction.relayer = relayer.address.to_bech32() - self.apply_nonce(inner_transaction) - self.sign(inner_transaction) - inner_transactions.append(inner_transaction) - - transaction = self.relayed_transactions_factory.create_relayed_v3_transaction( - relayer_address=relayer.address, - inner_transactions=inner_transactions, - ) - - transaction.nonce = relayer_nonce - self.sign(transaction) - - return transaction - - def create_relayed_v3_with_inner_transactions(self, relayer: "Account", inner_transactions: List[Transaction]) -> Transaction: - # Relayer nonce is reserved before sender nonce, to ensure good ordering (if sender and relayer are the same account). - relayer_nonce = self._reserve_nonce(relayer) - - for inner_transaction in inner_transactions: - inner_transaction.relayer = relayer.address.to_bech32() - self.apply_nonce(inner_transaction) - - # Even if it was already signed, we need to sign it again (because it has been modified). - self.sign(inner_transaction) - - transaction = self.relayed_transactions_factory.create_relayed_v3_transaction( - relayer_address=relayer.address, - inner_transactions=inner_transactions, - ) - - transaction.nonce = relayer_nonce - self.sign(transaction) - - return transaction - def create_contract_deployment_with_move_balance(self, sender: "Account", amount: int) -> Transaction: transaction = self.contracts_transactions_factory.create_transaction_for_deploy( sender=sender.address, @@ -1012,23 +915,32 @@ def sign(self, transaction: Transaction): bytes_for_signing = self.transaction_computer.compute_bytes_for_signing(transaction) transaction.signature = sender.signer.sign(bytes_for_signing) - def send_multiple(self, transactions: List[Transaction], chunk_size: int = 1024, wait_between_chunks: float = 0): + def send_multiple(self, transactions: List[Transaction], chunk_size: int = 1024, wait_between_chunks: float = 0, await_completion: bool = False): print(f"Sending {len(transactions)} transactions...") chunks = list(split_to_chunks(transactions, chunk_size)) for chunk in chunks: - num_sent, _ = self.network_provider.send_transactions(chunk) - + num_sent, hashes_by_index = self.network_provider.send_transactions(chunk) print(f"Sent {num_sent} transactions. Waiting {wait_between_chunks} seconds...") + + self.transactions_hashes_accumulator.extend(hashes_by_index.values()) time.sleep(wait_between_chunks) - def send(self, transaction: Transaction): + if await_completion: + self.await_completed(transactions) + + def send(self, transaction: Transaction, await_completion: bool = False): transaction_hash = self.network_provider.send_transaction(transaction) - print(f"{self.configuration.explorer_url}/transactions/{transaction_hash}") + print(f" 🌍 {self.configuration.view_url.replace('{hash}', transaction_hash)}") + + if await_completion: + self.await_completed([transaction]) + + self.transactions_hashes_accumulator.append(transaction_hash) def await_completed(self, transactions: List[Transaction]) -> List[TransactionOnNetwork]: - print(f"Awaiting completion of {len(transactions)} transactions...") + print(f" ⏳ Awaiting completion of {len(transactions)} transactions...") # Short wait before starting requests, to avoid "transaction not found" errors. time.sleep(3) @@ -1037,12 +949,23 @@ def await_completed_one(transaction: Transaction) -> TransactionOnNetwork: transaction_hash = self.transaction_computer.compute_transaction_hash(transaction).hex() transaction_on_network = self.transaction_awaiter.await_completed(transaction_hash) - print(f"Completed: {self.configuration.explorer_url}/transactions/{transaction_hash}") + print(f" ✓ Completed: {self.configuration.view_url.replace('{hash}', transaction_hash)}") return transaction_on_network transactions_on_network = Pool(8).map(await_completed_one, transactions) return transactions_on_network + def wait_until_epoch(self, epoch: int): + print(f"⏳ Waiting until epoch {epoch}...") + + while True: + current_epoch = self.network_provider.get_network_status().epoch_number + if current_epoch >= epoch: + print(f"✓ Reached epoch {current_epoch} >= {epoch}") + break + + time.sleep(30) + class NoncesTracker: def __init__(self, proxy_url: str) -> None: @@ -1077,6 +1000,8 @@ def __init__(self, path: Path) -> None: self.path = path self._contracts: List[SmartContract] = [] self._custom_currencies: List[str] = [] + self._setup_transactions: List[str] = [] + self._run_transactions: List[str] = [] def clear(self): self._contracts = [] @@ -1107,6 +1032,16 @@ def get_contracts(self, tag: Optional[str] = None) -> List[SmartContract]: return contracts + def replace_setup_transactions(self, transactions_hashes: list[str]): + self.load() + self._setup_transactions = transactions_hashes + self.save() + + def replace_run_transactions(self, transactions_hashes: list[str]): + self.load() + self._run_transactions = transactions_hashes + self.save() + def load(self): if not self.path.exists(): return @@ -1116,6 +1051,8 @@ def load(self): contracts_raw = data.get("contracts", []) self._contracts = [SmartContract.from_dictionary(item) for item in contracts_raw] self._custom_currencies = data.get("customCurrencies", []) + self._setup_transactions = data.get("setupTransactions", []) + self._run_transactions = data.get("runTransactions", []) def save(self): contracts_raw = [contract.to_dictionary() for contract in self._contracts] @@ -1123,6 +1060,8 @@ def save(self): data = { "contracts": contracts_raw, "customCurrencies": self._custom_currencies, + "setupTransactions": self._setup_transactions, + "runTransactions": self._run_transactions, } self.path.parent.mkdir(parents=True, exist_ok=True) diff --git a/systemtests/localnet.toml b/systemtests/localnet.toml new file mode 100644 index 00000000..3a25fc37 --- /dev/null +++ b/systemtests/localnet.toml @@ -0,0 +1,41 @@ +[general] +log_level = "*:DEBUG" +genesis_delay_seconds = 10 +rounds_per_epoch = 50 +round_duration_milliseconds = 6000 + +[metashard] +consensus_size = 1 +num_observers = 0 +num_validators = 1 + +[shards] +num_shards = 3 +consensus_size = 1 +num_observers_per_shard = 0 +num_validators_per_shard = 1 + +[networking] +host = "127.0.0.1" +port_seednode = 9999 +port_seednode_rest_api = 10000 +p2p_id_seednode = "16Uiu2HAkx4QqgXXDdHdUWbLu5kxhd3Uo2hqB2FfCxmxH5Sd7bZFk" +port_proxy = 7950 +port_first_observer = 21100 +port_first_observer_rest_api = 10100 +port_first_validator = 21500 +port_first_validator_rest_api = 10200 + +[software.mx_chain_go] +resolution = "remote" +archive_url = "https://github.com/multiversx/mx-chain-go/archive/refs/tags/v1.8.4.zip" +archive_download_folder = "~/multiversx-sdk/localnet_software_remote/downloaded/mx-chain-go" +archive_extraction_folder = "~/multiversx-sdk/localnet_software_remote/extracted/mx-chain-go" +local_path = "~/multiversx-sdk/localnet_software_local/mx-chain-go" + +[software.mx_chain_proxy_go] +resolution = "remote" +archive_url = "https://github.com/multiversx/mx-chain-proxy-go/archive/refs/tags/v1.1.54.zip" +archive_download_folder = "~/multiversx-sdk/localnet_software_remote/downloaded/mx-chain-proxy-go" +archive_extraction_folder = "~/multiversx-sdk/localnet_software_remote/extracted/mx-chain-proxy-go" +local_path = "~/multiversx-sdk/localnet_software_local/mx-chain-proxy-go" diff --git a/systemtests/mesh_cli_config/testnet-construction-custom.json b/systemtests/mesh_cli_config/testnet-construction-custom.json index 852e3329..2536c75a 100644 --- a/systemtests/mesh_cli_config/testnet-construction-custom.json +++ b/systemtests/mesh_cli_config/testnet-construction-custom.json @@ -23,7 +23,7 @@ "privkey": "3e4e89e501eb542c12403fb15c52479e8721f2f4dedc3b3ef0f3b47b37de006c", "curve_type": "edwards25519", "currency": { - "symbol": "ROSETTA-4b5aa2", + "symbol": "ROSETTA-449e95", "decimals": 4 } }, diff --git a/systemtests/mesh_cli_config/testnet-construction-custom.ros b/systemtests/mesh_cli_config/testnet-construction-custom.ros index 921de26a..d89bade8 100644 --- a/systemtests/mesh_cli_config/testnet-construction-custom.ros +++ b/systemtests/mesh_cli_config/testnet-construction-custom.ros @@ -1,13 +1,13 @@ transfer(1){ transfer{ transfer.network = {"network":"untitled", "blockchain":"MultiversX"}; - custom_currency = {"symbol":"ROSETTA-4b5aa2", "decimals":4}; + custom_currency = {"symbol":"ROSETTA-449e95", "decimals":4}; sender = { "account_identifier": { "address": "erd1ldjsdetjvegjdnda0qw2h62kq6rpvrklkc5pw9zxm0nwulfhtyqqtyc4vq" }, "currency": { - "symbol": "ROSETTA-4b5aa2", + "symbol": "ROSETTA-449e95", "decimals": 4 } }; @@ -24,7 +24,7 @@ transfer(1){ "address": "erd1xtslmt67utuewwv8jsx729mxjxaa8dvyyzp7492hy99dl7hvcuqq30l98v" }, "currency": { - "symbol": "ROSETTA-4b5aa2", + "symbol": "ROSETTA-449e95", "decimals": 4 } }; diff --git a/systemtests/rosetta_config/testnet-custom-currencies.json b/systemtests/rosetta_config/testnet-custom-currencies.json index cd344c8f..254cb70b 100644 --- a/systemtests/rosetta_config/testnet-custom-currencies.json +++ b/systemtests/rosetta_config/testnet-custom-currencies.json @@ -1,6 +1,6 @@ [ { - "symbol": "ROSETTA-4b5aa2", + "symbol": "ROSETTA-449e95", "decimals": 4 } ]