From a5d3cbefa91895a42c5023672ceb3df51db16d8a Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Mon, 23 Oct 2023 18:07:40 +0200 Subject: [PATCH 01/56] gas estimation fixes --- integration/tests/basic/rpc/test_rpc_base_calls.py | 1 - .../tests/basic/rpc/test_rpc_estimate_gas.py | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/integration/tests/basic/rpc/test_rpc_base_calls.py b/integration/tests/basic/rpc/test_rpc_base_calls.py index 6e9d50b40a..e2840aa53c 100644 --- a/integration/tests/basic/rpc/test_rpc_base_calls.py +++ b/integration/tests/basic/rpc/test_rpc_base_calls.py @@ -333,7 +333,6 @@ def test_get_evm_params(self): expected_fields = [ "NEON_GAS_LIMIT_MULTIPLIER_NO_CHAINID", - "NEON_POOL_SEED", "NEON_COMPUTE_BUDGET_UNITS", "NEON_SEED_VERSION", "NEON_EVM_STEPS_LAST_ITERATION_MAX", diff --git a/integration/tests/basic/rpc/test_rpc_estimate_gas.py b/integration/tests/basic/rpc/test_rpc_estimate_gas.py index d66934c2b8..16c80c32e5 100644 --- a/integration/tests/basic/rpc/test_rpc_estimate_gas.py +++ b/integration/tests/basic/rpc/test_rpc_estimate_gas.py @@ -52,7 +52,7 @@ def test_eth_estimate_gas_different_block_param( assert rpc_checks.is_hex( response["result"] ), f"the result for estimated gas should be in hex, but got'{response['result']}'" - assert int(response["result"], 16) == 30_000 + assert int(response["result"], 16) == 25_000 def test_eth_estimate_gas_negative(self): response = self.proxy_api.send_rpc(method="eth_estimateGas", params=[]) @@ -128,7 +128,7 @@ def test_rpc_estimate_gas_send_neon(self): assert "gas" in transaction estimated_gas = transaction["gas"] - assert estimated_gas == 30_000 + assert estimated_gas == 25_000 def test_rpc_estimate_gas_erc20(self, erc20_simple): tx_receipt = erc20_simple.transfer( @@ -140,7 +140,7 @@ def test_rpc_estimate_gas_erc20(self, erc20_simple): assert "gas" in transaction estimated_gas = transaction["gas"] - assert estimated_gas == 1_552_280 + assert estimated_gas == 1_394_160 def test_rpc_estimate_gas_spl(self, erc20_spl): tx_receipt = erc20_spl.transfer(erc20_spl.account, self.recipient_account, 1) @@ -150,7 +150,7 @@ def test_rpc_estimate_gas_spl(self, erc20_spl): assert "gas" in transaction estimated_gas = transaction["gas"] - assert estimated_gas == 2_084_280 + assert estimated_gas == 2_079_280 def test_rpc_estimate_gas_contract_get_value(self, common_contract): tx = self.make_contract_tx_object() @@ -164,7 +164,7 @@ def test_rpc_estimate_gas_contract_get_value(self, common_contract): assert "gas" in transaction estimated_gas = transaction["gas"] - assert estimated_gas == 30_000 + assert estimated_gas == 25_000 def test_rpc_estimate_gas_contract_set_value(self, common_contract): tx = self.make_contract_tx_object() @@ -180,7 +180,7 @@ def test_rpc_estimate_gas_contract_set_value(self, common_contract): assert "gas" in transaction estimated_gas = transaction["gas"] - assert estimated_gas == 30_000 + assert estimated_gas == 25_000 def test_rpc_estimate_gas_contract_calls_another_contract(self, common_contract): caller_contract, _ = self.web3_client.deploy_and_get_contract( @@ -202,4 +202,4 @@ def test_rpc_estimate_gas_contract_calls_another_contract(self, common_contract) assert "gas" in transaction estimated_gas = transaction["gas"] - assert estimated_gas == 30_000 + assert estimated_gas == 25_000 From 58ca13ff4b28b654f16cab375f06555741ab08ce Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Tue, 24 Oct 2023 11:37:34 +0200 Subject: [PATCH 02/56] increased neon amount for 721 sender --- utils/erc721ForMetaplex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/erc721ForMetaplex.py b/utils/erc721ForMetaplex.py index 5b10ddad78..313a84eab0 100644 --- a/utils/erc721ForMetaplex.py +++ b/utils/erc721ForMetaplex.py @@ -10,7 +10,7 @@ def __init__(self, web3_client: web3client.NeonWeb3Client, faucet, account=None, contract_name="ERC721ForMetaplex"): self.web3_client = web3_client self.account = account or web3_client.create_account() - faucet.request_neon(self.account.address, 300) + faucet.request_neon(self.account.address, 600) self.contract = self.deploy(contract, contract_name) def make_tx_object(self, from_address, gasPrice=None, gas=None): From 9fbf12a72c2bfe30489a4e24cad31f0e1c37a487 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Tue, 24 Oct 2023 11:59:51 +0200 Subject: [PATCH 03/56] fixes --- integration/tests/basic/helpers/assert_message.py | 2 +- integration/tests/basic/helpers/rpc_checks.py | 2 +- integration/tests/basic/rpc/test_rpc_base_calls.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/integration/tests/basic/helpers/assert_message.py b/integration/tests/basic/helpers/assert_message.py index 0568a85ca5..74864ca276 100644 --- a/integration/tests/basic/helpers/assert_message.py +++ b/integration/tests/basic/helpers/assert_message.py @@ -3,7 +3,7 @@ class ErrorMessage(Enum): NEGATIVE_VALUE = "Resulting wei value must be between 1 and " - INSUFFICIENT_FUNDS = "insufficient funds for transfer" + INSUFFICIENT_FUNDS = "insufficient balance for transfer" GAS_OVERFLOW = "gas uint64 overflow" GAS_LIMIT_REACHED = "gas limit reached" INVALID_FIELDS_GAS = "Transaction had invalid fields: {'gas'" diff --git a/integration/tests/basic/helpers/rpc_checks.py b/integration/tests/basic/helpers/rpc_checks.py index 4ea0b55e76..b2fa099ef4 100644 --- a/integration/tests/basic/helpers/rpc_checks.py +++ b/integration/tests/basic/helpers/rpc_checks.py @@ -143,4 +143,4 @@ def assert_equal_fields(result, comparable_object, comparable_fields, keys_mappi r = hex(r) if isinstance(r, HexBytes): r = r.hex() - assert l == r, f"{field} from response {l} is not equal to {field} from receipt {r}" + assert l == r, f"The field '{field}' from response {l} is not equal to {field} from receipt {r}" diff --git a/integration/tests/basic/rpc/test_rpc_base_calls.py b/integration/tests/basic/rpc/test_rpc_base_calls.py index e2840aa53c..9ee4cfcd87 100644 --- a/integration/tests/basic/rpc/test_rpc_base_calls.py +++ b/integration/tests/basic/rpc/test_rpc_base_calls.py @@ -333,7 +333,6 @@ def test_get_evm_params(self): expected_fields = [ "NEON_GAS_LIMIT_MULTIPLIER_NO_CHAINID", - "NEON_COMPUTE_BUDGET_UNITS", "NEON_SEED_VERSION", "NEON_EVM_STEPS_LAST_ITERATION_MAX", "NEON_PAYMENT_TO_DEPOSIT", From 12ff3e06bd9f6cc2931a62e70feec1e61d4adfe2 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Tue, 24 Oct 2023 12:27:53 +0200 Subject: [PATCH 04/56] fixes --- integration/tests/basic/helpers/assert_message.py | 3 ++- integration/tests/basic/rpc/test_rpc_base_calls.py | 1 - integration/tests/basic/test_wneon.py | 2 +- integration/tests/basic/transfer/test_neon.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/integration/tests/basic/helpers/assert_message.py b/integration/tests/basic/helpers/assert_message.py index 74864ca276..19d0e18250 100644 --- a/integration/tests/basic/helpers/assert_message.py +++ b/integration/tests/basic/helpers/assert_message.py @@ -3,7 +3,8 @@ class ErrorMessage(Enum): NEGATIVE_VALUE = "Resulting wei value must be between 1 and " - INSUFFICIENT_FUNDS = "insufficient balance for transfer" + INSUFFICIENT_FUNDS = "insufficient funds for transfer" + INSUFFICIENT_BALANCE = "insufficient balance for transfer" GAS_OVERFLOW = "gas uint64 overflow" GAS_LIMIT_REACHED = "gas limit reached" INVALID_FIELDS_GAS = "Transaction had invalid fields: {'gas'" diff --git a/integration/tests/basic/rpc/test_rpc_base_calls.py b/integration/tests/basic/rpc/test_rpc_base_calls.py index 9ee4cfcd87..104eacfb6d 100644 --- a/integration/tests/basic/rpc/test_rpc_base_calls.py +++ b/integration/tests/basic/rpc/test_rpc_base_calls.py @@ -333,7 +333,6 @@ def test_get_evm_params(self): expected_fields = [ "NEON_GAS_LIMIT_MULTIPLIER_NO_CHAINID", - "NEON_SEED_VERSION", "NEON_EVM_STEPS_LAST_ITERATION_MAX", "NEON_PAYMENT_TO_DEPOSIT", "NEON_COMPUTE_UNITS", diff --git a/integration/tests/basic/test_wneon.py b/integration/tests/basic/test_wneon.py index fe8a977e98..3a4efb7058 100644 --- a/integration/tests/basic/test_wneon.py +++ b/integration/tests/basic/test_wneon.py @@ -121,7 +121,7 @@ def test_transfer_and_check_token_does_not_use_spl(self, wneon, new_account): lambda: self.sol_client.get_transaction( Signature.from_string(solana_trx["result"][0]), ) - != GetTransactionResp(None) + != GetTransactionResp(None), timeout_sec=30 ) solana_resp = self.sol_client.get_transaction( Signature.from_string(solana_trx["result"][0]) diff --git a/integration/tests/basic/transfer/test_neon.py b/integration/tests/basic/transfer/test_neon.py index 0b476dbf6c..230ffe883e 100644 --- a/integration/tests/basic/transfer/test_neon.py +++ b/integration/tests/basic/transfer/test_neon.py @@ -42,7 +42,7 @@ def test_send_more_than_exist_on_account_neon(self): self.sender_account, self.recipient_account, amount, - error_message=ErrorMessage.INSUFFICIENT_FUNDS.value, + error_message=ErrorMessage.INSUFFICIENT_BALANCE.value, ) self.assert_balance(self.sender_account.address, sender_balance, rnd_dig=1) From 4ddacc35a1def2a0c20a2783e3176a0b24d9cf2a Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Wed, 25 Oct 2023 12:59:11 +0200 Subject: [PATCH 05/56] fixed changed instructions --- .../tests/basic/helpers/assert_message.py | 2 +- integration/tests/basic/helpers/rpc_checks.py | 4 +- .../tests/basic/rpc/test_rpc_base_calls.py | 29 +-- integration/tests/basic/test_deposit.py | 55 +++-- utils/instructions.py | 72 +++---- utils/solana_client.py | 21 +- utils/transfers_inter_networks.py | 201 +++++++++--------- 7 files changed, 174 insertions(+), 210 deletions(-) diff --git a/integration/tests/basic/helpers/assert_message.py b/integration/tests/basic/helpers/assert_message.py index 19d0e18250..58777adf97 100644 --- a/integration/tests/basic/helpers/assert_message.py +++ b/integration/tests/basic/helpers/assert_message.py @@ -4,7 +4,7 @@ class ErrorMessage(Enum): NEGATIVE_VALUE = "Resulting wei value must be between 1 and " INSUFFICIENT_FUNDS = "insufficient funds for transfer" - INSUFFICIENT_BALANCE = "insufficient balance for transfer" + INSUFFICIENT_BALANCE = "Insufficient balance for transfer" GAS_OVERFLOW = "gas uint64 overflow" GAS_LIMIT_REACHED = "gas limit reached" INVALID_FIELDS_GAS = "Transaction had invalid fields: {'gas'" diff --git a/integration/tests/basic/helpers/rpc_checks.py b/integration/tests/basic/helpers/rpc_checks.py index b2fa099ef4..d7a0c5ae45 100644 --- a/integration/tests/basic/helpers/rpc_checks.py +++ b/integration/tests/basic/helpers/rpc_checks.py @@ -137,10 +137,8 @@ def assert_equal_fields(result, comparable_object, comparable_fields, keys_mappi r = comparable_object[keys_mappings.get(field)] else: r = comparable_object[field] - if isinstance(r, str): - r = r.lower() if isinstance(r, int): r = hex(r) if isinstance(r, HexBytes): r = r.hex() - assert l == r, f"The field '{field}' from response {l} is not equal to {field} from receipt {r}" + assert l == r, f"The field '{field}' {l} from response is not equal to {field} from receipt {r}" diff --git a/integration/tests/basic/rpc/test_rpc_base_calls.py b/integration/tests/basic/rpc/test_rpc_base_calls.py index 104eacfb6d..8ff3f4db02 100644 --- a/integration/tests/basic/rpc/test_rpc_base_calls.py +++ b/integration/tests/basic/rpc/test_rpc_base_calls.py @@ -330,32 +330,19 @@ def test_check_unsupported_methods(self, method: str): def test_get_evm_params(self): response = self.proxy_api.send_rpc(method="neon_getEvmParams", params=[]) - expected_fields = [ - "NEON_GAS_LIMIT_MULTIPLIER_NO_CHAINID", - "NEON_EVM_STEPS_LAST_ITERATION_MAX", - "NEON_PAYMENT_TO_DEPOSIT", - "NEON_COMPUTE_UNITS", - "NEON_REQUEST_UNITS_ADDITIONAL_FEE", - "NEON_PKG_VERSION", - "NEON_HEAP_FRAME", "NEON_ACCOUNT_SEED_VERSION", - "NEON_TOKEN_MINT", - "NEON_TREASURY_POOL_SEED", - "NEON_STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT", + "NEON_EVM_STEPS_LAST_ITERATION_MAX", "NEON_EVM_STEPS_MIN", - "NEON_PAYMENT_TO_TREASURE", - "NEON_OPERATOR_PRIORITY_SLOTS", - "NEON_STATUS_NAME", - "NEON_REVISION", - "NEON_ADDITIONAL_FEE", - "NEON_CHAIN_ID", - "NEON_COMPUTE_BUDGET_HEAP_FRAME", - "NEON_POOL_COUNT", + "NEON_GAS_LIMIT_MULTIPLIER_NO_CHAINID", "NEON_HOLDER_MSG_SIZE", + "NEON_OPERATOR_PRIORITY_SLOTS", + "NEON_PAYMENT_TO_DEPOSIT", + "NEON_PAYMENT_TO_TREASURE", + "NEON_STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT", "NEON_TREASURY_POOL_COUNT", - "NEON_TOKEN_MINT_DECIMALS", - "NEON_EVM_ID", + "NEON_TREASURY_POOL_SEED", + "NEON_EVM_ID" ] for field in expected_fields: assert ( diff --git a/integration/tests/basic/test_deposit.py b/integration/tests/basic/test_deposit.py index 3585a0523e..5e4107ceff 100644 --- a/integration/tests/basic/test_deposit.py +++ b/integration/tests/basic/test_deposit.py @@ -18,10 +18,9 @@ from integration.tests.basic.helpers.basic import BaseMixin, BaseTests from utils.consts import LAMPORT_PER_SOL -from utils.transfers_inter_networks import Transfer +from utils.transfers_inter_networks import neon_transfer_tx, wSOL_tx, neon_from_solana_to_neon_tx from utils.helpers import wait_condition - wSOL = { "chain_id": 111, "address_spl": "So11111111111111111111111111111111111111112", @@ -70,32 +69,32 @@ def send_tx_and_check_status_ok(self, tx, solana_account): opts = TxOpts(skip_preflight=True, skip_confirmation=False) sig = self.sol_client.send_transaction(tx, solana_account, opts=opts).value sig_status = json.loads((self.sol_client.confirm_transaction(sig)).to_json()) - assert sig_status["result"]["value"][0]["status"] == {"Ok": None} + assert sig_status["result"]["value"][0]["status"] == {"Ok": None}, f"error:{sig_status}" def test_transfer_neon_from_solana_to_neon( - self, new_account, solana_account, pytestconfig: Config, neon_mint, erc20_spl + self, new_account, solana_account, pytestconfig: Config, neon_mint ): """Transfer Neon from Solana -> Neon""" amount = 0.1 full_amount = int(amount * LAMPORT_PER_SOL) evm_loader_id = pytestconfig.environment.evm_loader - neon_wallet = self.sol_client.get_neon_account_address( - new_account.address, evm_loader_id - ) + balance_pubkey = self.sol_client.ether2balance(new_account.address, + self.web3_client._chain_id, + evm_loader_id) neon_balance_before = self.get_balance_from_wei(new_account.address) self.create_ata(solana_account, neon_mint) self.withdraw_neon(solana_account, amount) - - tx = Transfer.neon_from_solana_to_neon_tx( + tx = neon_from_solana_to_neon_tx( solana_account, - neon_wallet, + balance_pubkey, neon_mint, new_account, full_amount, evm_loader_id, + self.web3_client._chain_id ) self.send_tx_and_check_status_ok(tx, solana_account) @@ -103,12 +102,9 @@ def test_transfer_neon_from_solana_to_neon( assert neon_balance_after == neon_balance_before + amount def test_transfer_spl_token_from_solana_to_neon( - self, solana_account, new_account, pytestconfig: Config, erc20_spl + self, solana_account, new_account, pytestconfig: Config, erc20_spl ): evm_loader_id = pytestconfig.environment.evm_loader - response = self.proxy_api.send_rpc(method="neon_getEvmParams", params=[]) - neon_pool_count = response["result"]["NEON_POOL_COUNT"] - amount = 0.1 full_amount = int(amount * LAMPORT_PER_SOL) @@ -127,13 +123,13 @@ def test_transfer_spl_token_from_solana_to_neon( ) # wrap SOL - wrap_sol_tx = Transfer.wSOL_tx( - self.sol_client, wSOL, full_amount, solana_account.public_key, ata_address + wrap_sol_tx = wSOL_tx(self.sol_client, + wSOL, full_amount, solana_account.public_key, ata_address ) self.send_tx_and_check_status_ok(wrap_sol_tx, solana_account) # transfer wSOL - transfer_tx = Transfer.neon_transfer_tx( + transfer_tx = neon_transfer_tx( self.web3_client, self.sol_client, full_amount, @@ -141,8 +137,7 @@ def test_transfer_spl_token_from_solana_to_neon( solana_account, new_account, erc20_spl, - evm_loader_id, - neon_pool_count, + evm_loader_id ) self.send_tx_and_check_status_ok(transfer_tx, solana_account) @@ -151,8 +146,8 @@ def test_transfer_spl_token_from_solana_to_neon( ) assert ( - int(ata_balance_after.value.amount) - == int(ata_balance_before.value.amount) + full_amount + int(ata_balance_after.value.amount) + == int(ata_balance_before.value.amount) + full_amount ) @@ -175,7 +170,7 @@ def withdraw(self, dest_acc, move_amount, withdraw_contract): @pytest.mark.only_stands def test_success_withdraw_to_non_existing_account( - self, pytestconfig: Config, withdraw_contract, neon_mint, solana_account + self, pytestconfig: Config, withdraw_contract, neon_mint, solana_account ): """Should successfully withdraw NEON tokens to previously non-existing Associated Token Account""" dest_acc = Keypair.generate() @@ -206,7 +201,7 @@ def test_success_withdraw_to_non_existing_account( ) def test_success_withdraw_to_existing_account( - self, pytestconfig: Config, withdraw_contract, neon_mint, solana_account + self, pytestconfig: Config, withdraw_contract, neon_mint, solana_account ): """Should successfully withdraw NEON tokens to existing Associated Token Account""" dest_acc = solana_account @@ -244,7 +239,7 @@ def test_success_withdraw_to_existing_account( assert int(destination_balance_after.value.amount) == move_amount_galan def test_failed_withdraw_non_divisible_amount( - self, pytestconfig: Config, withdraw_contract, neon_mint, solana_account + self, pytestconfig: Config, withdraw_contract, neon_mint, solana_account ): dest_acc = solana_account @@ -271,12 +266,12 @@ def test_failed_withdraw_non_divisible_amount( @pytest.mark.parametrize("move_amount", [11000, 10000]) def test_failed_withdraw_insufficient_balance( - self, - pytestconfig: Config, - move_amount, - withdraw_contract, - neon_mint, - solana_account, + self, + pytestconfig: Config, + move_amount, + withdraw_contract, + neon_mint, + solana_account, ): dest_acc = solana_account diff --git a/utils/instructions.py b/utils/instructions.py index 190ace43b8..904273dfd2 100644 --- a/utils/instructions.py +++ b/utils/instructions.py @@ -1,14 +1,11 @@ import hashlib import json -import math -import random import base58 from solana.publickey import PublicKey from solana.system_program import SYS_PROGRAM_ID from solana.transaction import AccountMeta, TransactionInstruction from spl.token.constants import ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID -from spl.token.instructions import get_associated_token_address COMPUTE_BUDGET_ID: PublicKey = PublicKey( "ComputeBudget111111111111111111111111111111") @@ -18,18 +15,19 @@ class Instruction: @staticmethod - def account_v3(solana_wallet, neon_wallet_pda, - neon_wallet, evm_loader_id) -> TransactionInstruction: + def balance_account(solana_wallet, account_pubkey, + neon_wallet, evm_loader_id, chain_id) -> TransactionInstruction: + keys = [ AccountMeta(pubkey=solana_wallet, is_signer=True, is_writable=True), AccountMeta(pubkey=SYS_PROGRAM_ID, is_signer=False, is_writable=False), - AccountMeta(pubkey=neon_wallet_pda, + AccountMeta(pubkey=account_pubkey, is_signer=False, is_writable=True), ] - data = bytes.fromhex('28') + bytes.fromhex(str(neon_wallet)[2:]) + data = bytes.fromhex('2D') + bytes.fromhex(str(neon_wallet)[2:]) + chain_id.to_bytes(8, 'little') return TransactionInstruction( program_id=PublicKey(evm_loader_id), keys=keys, @@ -41,31 +39,28 @@ def sync_native(account: PublicKey): data = bytes.fromhex('11') return TransactionInstruction(keys=keys, program_id=TOKEN_PROGRAM_ID, data=data) - @staticmethod - def deposit(solana_pubkey, neon_pubkey, deposit_pubkey, - neon_wallet_address, neon_mint, evm_loader_id) -> TransactionInstruction: - associated_token_address = get_associated_token_address( - solana_pubkey, neon_mint) - pool_key = get_associated_token_address(deposit_pubkey, neon_mint) - keys = [ - AccountMeta(pubkey=associated_token_address, - is_signer=False, is_writable=True), - AccountMeta(pubkey=pool_key, is_signer=False, is_writable=True), - AccountMeta(pubkey=neon_pubkey, is_signer=False, is_writable=True), - AccountMeta(pubkey=TOKEN_PROGRAM_ID, - is_signer=False, is_writable=False), - AccountMeta(pubkey=solana_pubkey, - is_signer=True, is_writable=True), - AccountMeta(pubkey=SYS_PROGRAM_ID, - is_signer=False, is_writable=False), + def deposit( + ether_address: bytes, + chain_id: int, + balance_account: PublicKey, + mint: PublicKey, + source: PublicKey, + pool: PublicKey, + operator_pubkey: PublicKey, + evm_loader_id + ) -> TransactionInstruction: + data = bytes.fromhex('27') + ether_address + chain_id.to_bytes(8, 'little') + accounts = [ + AccountMeta(pubkey=mint, is_signer=False, is_writable=True), + AccountMeta(pubkey=source, is_signer=False, is_writable=True), + AccountMeta(pubkey=pool, is_signer=False, is_writable=True), + AccountMeta(pubkey=balance_account, is_signer=False, is_writable=True), + AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), + AccountMeta(pubkey=operator_pubkey, is_signer=True, is_writable=True), + AccountMeta(pubkey=SYS_PROGRAM_ID, is_signer=False, is_writable=False), ] - - data = bytes.fromhex('27') + bytes.fromhex(neon_wallet_address[2:]) - return TransactionInstruction( - program_id=PublicKey(evm_loader_id), - keys=keys, - data=data) + return TransactionInstruction(program_id=PublicKey(evm_loader_id), data=data, keys=accounts) @staticmethod def compute_budget_utils(operator, units=DEFAULT_UNITS) -> TransactionInstruction: @@ -133,14 +128,12 @@ def claim(_from, to, amount, web3_client, ata_address, signed_tx = web3_client._web3.eth.account.sign_transaction( tx, _from.key) - if signed_tx.rawTransaction is not None: emulated_tx = web3_client.get_neon_emulate( str(signed_tx.rawTransaction.hex())[2:]) - if emulated_tx is not None: - for account in emulated_tx['result']['accounts']: - key = account['account'] + for account in emulated_tx['result']['solana_accounts']: + key = account['pubkey'] result[key] = AccountMeta(pubkey=PublicKey( key), is_signer=False, is_writable=True) if 'contract' in account: @@ -156,16 +149,15 @@ def claim(_from, to, amount, web3_client, ata_address, return signed_tx, result @staticmethod - def buld_tx_instruction(solana_wallet, neon_wallet, neon_raw_transaction, - neon_keys, evm_loader_id, neon_pool_count): + def build_tx_instruction(solana_wallet, neon_wallet, neon_raw_transaction, + neon_keys, evm_loader_id): program_id = PublicKey(evm_loader_id) - treasure_pool_index = math.floor(random.randint( - 0, 1) * int(neon_pool_count)) % int(neon_pool_count) + treasure_pool_index = 2 treasure_pool_address = get_collateral_pool_address( treasure_pool_index, evm_loader_id) data = bytes.fromhex('1f') + treasure_pool_index.to_bytes(4, 'little') + \ - bytes.fromhex(str(neon_raw_transaction.hex())[2:]) + bytes.fromhex(str(neon_raw_transaction.hex())[2:]) keys = [AccountMeta(pubkey=solana_wallet, is_signer=True, is_writable=True), AccountMeta(pubkey=treasure_pool_address, is_signer=False, is_writable=True), @@ -199,4 +191,4 @@ def get_solana_wallet_signer(solana_account, neon_account, web3_client): neon_wallet = bytes(neon_account.address, 'utf-8') new_wallet = hashlib.sha256(solana_wallet + neon_wallet).hexdigest() emulate_signer_private_key = f'0x{new_wallet}' - return web3_client._web3.eth.account.from_key(emulate_signer_private_key) + return web3_client.eth.account.from_key(emulate_signer_private_key) diff --git a/utils/solana_client.py b/utils/solana_client.py index b91d7bf531..5ad5ccee02 100644 --- a/utils/solana_client.py +++ b/utils/solana_client.py @@ -26,10 +26,10 @@ def __init__(self, endpoint, account_seed_version="\3"): ) def request_airdrop( - self, - pubkey: PublicKey, - lamports: int, - commitment: tp.Optional[Commitment] = None, + self, + pubkey: PublicKey, + lamports: int, + commitment: tp.Optional[Commitment] = None, ) -> RequestAirdropResp: airdrop_resp = None for _ in range(5): @@ -65,17 +65,16 @@ def send_sol(self, from_: Keypair, to: PublicKey, amount_lamports: int): else: raise AssertionError(f"Balance not changed in account {to}") - def get_neon_account_address( - self, neon_account_address: str, evm_loader_id: str - ) -> PublicKey: - neon_account_addressbytes = bytes.fromhex(neon_account_address[2:]) + def ether2balance(self, address: tp.Union[str, bytes], chain_id: int, evm_loader_id: str) -> PublicKey: + address_bytes = bytes.fromhex(address[2:]) + chain_id_bytes = chain_id.to_bytes(32, 'big') return PublicKey.find_program_address( - [self.account_seed_version, neon_account_addressbytes], - PublicKey(evm_loader_id), + [self.account_seed_version, address_bytes, chain_id_bytes], + PublicKey(evm_loader_id) )[0] def get_erc_auth_address( - self, neon_account_address: str, token_address: str, evm_loader_id: str + self, neon_account_address: str, token_address: str, evm_loader_id: str ): neon_account_addressbytes = bytes(12) + bytes.fromhex(neon_account_address[2:]) if token_address.startswith("0x"): diff --git a/utils/transfers_inter_networks.py b/utils/transfers_inter_networks.py index 6424663278..ffee36426f 100644 --- a/utils/transfers_inter_networks.py +++ b/utils/transfers_inter_networks.py @@ -1,4 +1,3 @@ - from solana.publickey import PublicKey from solana.system_program import TransferParams, transfer from solana.transaction import Transaction @@ -9,109 +8,103 @@ from utils.instructions import Instruction, get_solana_wallet_signer -class Transfer: - @staticmethod - def neon_from_solana_to_neon_tx(solana_account, neon_wallet, neon_mint, neon_account, - amount, evm_loader_id): - '''Transfer NEON from solana to neon transaction''' - tx = Transaction(fee_payer=solana_account.public_key) - associated_token_address = get_associated_token_address( - solana_account.public_key, neon_mint) - - tx.add(approve( - ApproveParams( - program_id=TOKEN_PROGRAM_ID, - source=associated_token_address, - delegate=neon_wallet, - owner=solana_account.public_key, - amount=amount))) - - authority_pool = get_authority_pool_address( - evm_loader_id) - - tx.add(Instruction.deposit( - solana_account.public_key, - neon_wallet, - authority_pool, - neon_account.address, - neon_mint, - evm_loader_id)) - - return tx - - def wSOL_tx(sol_client, spl_token, amount, solana_wallet, ata_address): - mint_pubkey = PublicKey(spl_token['address_spl']) - wSOL_account = sol_client.get_account_info(ata_address).value - - tx = Transaction(fee_payer=solana_wallet) - if (wSOL_account is None): - tx.add(Instruction.associated_token_account( - solana_wallet, ata_address, solana_wallet, mint_pubkey, instruction_data=bytes(0))) - tx.add(transfer(TransferParams(solana_wallet, ata_address, amount))) - tx.add(Instruction.sync_native(ata_address)) - - return tx - - def neon_transfer_tx(web3_client, sol_client, amount, spl_token, solana_account, - neon_account, erc20_spl, evm_loader_id, neon_pool_count): - - neon_wallet_pda = sol_client.get_neon_account_address( - neon_account.address, evm_loader_id) - neon_wallet_account = sol_client.get_account_info( - neon_wallet_pda).value - delegate_pda = sol_client.get_erc_auth_address( - neon_account.address, spl_token['address'], evm_loader_id) - - emulate_signer = get_solana_wallet_signer( - solana_account, neon_account, web3_client) - emulated_signer_pda = sol_client.get_neon_account_address( - emulate_signer.address, evm_loader_id) - emulate_signer_pda_account = sol_client.get_account_info( - emulated_signer_pda).value - - solana_wallet = solana_account.public_key - - ata_address = get_associated_token_address( - solana_wallet, PublicKey(spl_token['address_spl'])) - - neon_transaction, neon_keys = Instruction.claim(neon_account, - spl_token['address'], - amount, - web3_client, - ata_address, - emulate_signer, - erc20_spl) - tx = Transaction(fee_payer=solana_wallet) - - compute_budget_instruction = Instruction.compute_budget_utils( - solana_account) - tx.add(compute_budget_instruction) - - heap_frame_instruction = Instruction.request_heap_frame(solana_account) - tx.add(heap_frame_instruction) - - tx.add(approve( - ApproveParams( - program_id=TOKEN_PROGRAM_ID, - source=ata_address, - delegate=delegate_pda, - owner=solana_account.public_key, - amount=amount))) - - if neon_wallet_account is None: - tx.add(Instruction.account_v3(solana_wallet, neon_wallet_pda, - neon_account.address, evm_loader_id)) - - if emulate_signer_pda_account is None: - tx.add(Instruction.account_v3(solana_wallet, emulated_signer_pda, - emulate_signer.address, evm_loader_id)) - - if neon_transaction.rawTransaction is not None: - tx.add(Instruction.buld_tx_instruction(solana_wallet, neon_wallet_pda, - neon_transaction.rawTransaction, neon_keys, - evm_loader_id, neon_pool_count)) - - return tx +def neon_from_solana_to_neon_tx(solana_account, neon_wallet, neon_mint, neon_account, + amount, evm_loader_id, chain_id): + '''Transfer NEON from solana to neon transaction''' + tx = Transaction(fee_payer=solana_account.public_key) + associated_token_address = get_associated_token_address( + solana_account.public_key, neon_mint) + + tx.add(approve( + ApproveParams( + program_id=TOKEN_PROGRAM_ID, + source=associated_token_address, + delegate=neon_wallet, + owner=solana_account.public_key, + amount=amount))) + + authority_pool = get_authority_pool_address( + evm_loader_id) + + associated_token_address = get_associated_token_address( + solana_account.public_key, neon_mint) + pool = get_associated_token_address(authority_pool, neon_mint) + + tx.add(Instruction.deposit( + bytes.fromhex(neon_account.address[2:]), + chain_id, + neon_wallet, + neon_mint, + associated_token_address, + pool, + solana_account.public_key, + evm_loader_id)) + return tx + + +def wSOL_tx(sol_client, spl_token, amount, solana_wallet, ata_address): + mint_pubkey = PublicKey(spl_token['address_spl']) + wSOL_account = sol_client.get_account_info(ata_address).value + + tx = Transaction(fee_payer=solana_wallet) + if (wSOL_account is None): + tx.add(Instruction.associated_token_account( + solana_wallet, ata_address, solana_wallet, mint_pubkey, instruction_data=bytes(0))) + tx.add(transfer(TransferParams(solana_wallet, ata_address, amount))) + tx.add(Instruction.sync_native(ata_address)) + + return tx + + +def neon_transfer_tx(web3_client, sol_client, amount, spl_token, solana_account, + neon_account, erc20_spl, evm_loader_id): + chain_id = web3_client._chain_id + neon_wallet_pda = sol_client.ether2balance(neon_account.address, chain_id, evm_loader_id) + + delegate_pda = neon_wallet_pda + emulate_signer = get_solana_wallet_signer( + solana_account, neon_account, web3_client) + emulated_signer_pda = sol_client.ether2balance(emulate_signer.address, chain_id, evm_loader_id) + + solana_wallet = solana_account.public_key + + ata_address = get_associated_token_address( + solana_wallet, PublicKey(spl_token['address_spl'])) + + neon_transaction, neon_keys = Instruction.claim(neon_account, + spl_token['address'], + amount, + web3_client, + ata_address, + emulate_signer, + erc20_spl) + tx = Transaction(fee_payer=solana_wallet) + + compute_budget_instruction = Instruction.compute_budget_utils( + solana_account) + tx.add(compute_budget_instruction) + + heap_frame_instruction = Instruction.request_heap_frame(solana_account) + tx.add(heap_frame_instruction) + + tx.add(approve( + ApproveParams( + program_id=TOKEN_PROGRAM_ID, + source=ata_address, + delegate=delegate_pda, + owner=solana_account.public_key, + amount=amount))) + + tx.add(Instruction.balance_account(solana_wallet, neon_wallet_pda, + neon_account.address, evm_loader_id, chain_id)) + + tx.add(Instruction.balance_account(solana_wallet, emulated_signer_pda, + emulate_signer.address, evm_loader_id, chain_id)) + + tx.add(Instruction.build_tx_instruction(solana_wallet, neon_wallet_pda, + neon_transaction.rawTransaction, neon_keys, + evm_loader_id)) + return tx def get_authority_pool_address(evm_loader_id: str): From e95692d13de499717dff8752f0c38a21804c3026 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Wed, 25 Oct 2023 13:22:55 +0200 Subject: [PATCH 06/56] fixed lowercase --- integration/tests/basic/rpc/test_rpc_get_logs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/tests/basic/rpc/test_rpc_get_logs.py b/integration/tests/basic/rpc/test_rpc_get_logs.py index f89dd97044..733d725261 100644 --- a/integration/tests/basic/rpc/test_rpc_get_logs.py +++ b/integration/tests/basic/rpc/test_rpc_get_logs.py @@ -212,9 +212,9 @@ def test_eth_get_logs(self, event_caller_contract, param_fields, tag1, tag2): ) assert_fields_are_boolean(response["result"][0], ["removed"]) if "address" in param_fields: - assert response["result"][0]["address"] == receipt["to"].lower(), ( + assert response["result"][0]["address"] == receipt["to"], ( f"address from response {response['result'][0]['address']} " - f"is not equal to address from receipt {receipt['to'].lower()}" + f"is not equal to address from receipt {receipt['to']}" ) def test_eth_get_logs_eq_val(self, event_caller_contract): From 77702abd5611c4178e122854f4b14633c2ce0a23 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Fri, 27 Oct 2023 11:49:24 +0200 Subject: [PATCH 07/56] deposit-wsol --- contracts/opcodes/ChainId.sol | 15 ++++++ .../{EIPs => opcodes}/UnsupportedOpcodes.sol | 0 .../tests/basic/evm/opcodes/test_chainid.py | 15 ++++++ .../evm/opcodes/test_unsupported_opcodes.py | 2 +- integration/tests/basic/test_deposit.py | 53 +++++++++++++++++-- utils/transfers_inter_networks.py | 43 ++++++++++++--- utils/web3client.py | 9 ++-- 7 files changed, 123 insertions(+), 14 deletions(-) create mode 100644 contracts/opcodes/ChainId.sol rename contracts/{EIPs => opcodes}/UnsupportedOpcodes.sol (100%) create mode 100644 integration/tests/basic/evm/opcodes/test_chainid.py diff --git a/contracts/opcodes/ChainId.sol b/contracts/opcodes/ChainId.sol new file mode 100644 index 0000000000..18a383326f --- /dev/null +++ b/contracts/opcodes/ChainId.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +contract ChainId { + event Added(bytes32 hash); + + function getCurrentValues() public view returns (uint256) { + uint256 id; + assembly { + id := chainid() + } + return id; + } + +} \ No newline at end of file diff --git a/contracts/EIPs/UnsupportedOpcodes.sol b/contracts/opcodes/UnsupportedOpcodes.sol similarity index 100% rename from contracts/EIPs/UnsupportedOpcodes.sol rename to contracts/opcodes/UnsupportedOpcodes.sol diff --git a/integration/tests/basic/evm/opcodes/test_chainid.py b/integration/tests/basic/evm/opcodes/test_chainid.py new file mode 100644 index 0000000000..b80467e9b4 --- /dev/null +++ b/integration/tests/basic/evm/opcodes/test_chainid.py @@ -0,0 +1,15 @@ +import pytest + +from integration.tests.basic.helpers.basic import BaseMixin + + +class TestChainId(BaseMixin): + @pytest.fixture(scope="class") + def contract(self, web3_client, class_account): + contract, _ = web3_client.deploy_and_get_contract( + "opcodes/ChainId", "0.8.10", class_account) + return contract + + def test_chainId(self, contract): + assert contract.functions.getCurrentValues().call() == 0 + diff --git a/integration/tests/basic/evm/opcodes/test_unsupported_opcodes.py b/integration/tests/basic/evm/opcodes/test_unsupported_opcodes.py index e8608d8963..5d4985d970 100644 --- a/integration/tests/basic/evm/opcodes/test_unsupported_opcodes.py +++ b/integration/tests/basic/evm/opcodes/test_unsupported_opcodes.py @@ -12,7 +12,7 @@ class TestUnsupportedOpcodes(BaseMixin): @pytest.fixture(scope="class") def contract(self, web3_client, class_account): contract, _ = web3_client.deploy_and_get_contract( - "EIPs/UnsupportedOpcodes", "0.8.10", class_account) + "opcodes/UnsupportedOpcodes", "0.8.10", class_account) return contract def test_basefee(self, contract): diff --git a/integration/tests/basic/test_deposit.py b/integration/tests/basic/test_deposit.py index 5e4107ceff..3284bd5194 100644 --- a/integration/tests/basic/test_deposit.py +++ b/integration/tests/basic/test_deposit.py @@ -18,7 +18,8 @@ from integration.tests.basic.helpers.basic import BaseMixin, BaseTests from utils.consts import LAMPORT_PER_SOL -from utils.transfers_inter_networks import neon_transfer_tx, wSOL_tx, neon_from_solana_to_neon_tx +from utils.transfers_inter_networks import neon_transfer_tx, wSOL_tx, neon_from_solana_to_neon_tx, \ + wSOL_from_solana_to_neon_tx from utils.helpers import wait_condition wSOL = { @@ -69,6 +70,7 @@ def send_tx_and_check_status_ok(self, tx, solana_account): opts = TxOpts(skip_preflight=True, skip_confirmation=False) sig = self.sol_client.send_transaction(tx, solana_account, opts=opts).value sig_status = json.loads((self.sol_client.confirm_transaction(sig)).to_json()) + print(sig_status) assert sig_status["result"]["value"][0]["status"] == {"Ok": None}, f"error:{sig_status}" def test_transfer_neon_from_solana_to_neon( @@ -124,8 +126,9 @@ def test_transfer_spl_token_from_solana_to_neon( # wrap SOL wrap_sol_tx = wSOL_tx(self.sol_client, - wSOL, full_amount, solana_account.public_key, ata_address - ) + wSOL, full_amount, + solana_account.public_key, + ata_address) self.send_tx_and_check_status_ok(wrap_sol_tx, solana_account) # transfer wSOL @@ -150,6 +153,50 @@ def test_transfer_spl_token_from_solana_to_neon( == int(ata_balance_before.value.amount) + full_amount ) + def test_transfer_wrapped_sol_token_from_solana_to_neon( + self, solana_account, pytestconfig: Config, web3_client_sol, new_account + ): + evm_loader_id = pytestconfig.environment.evm_loader + amount = 0.1 + full_amount = int(amount * LAMPORT_PER_SOL) + + mint_pubkey = PublicKey(wSOL["address_spl"]) + ata_address = get_associated_token_address( + solana_account.public_key, mint_pubkey + ) + + self.create_ata(solana_account, mint_pubkey) + + # wrap SOL + wrap_sol_tx = wSOL_tx(self.sol_client, + wSOL, full_amount, solana_account.public_key, ata_address + ) + self.send_tx_and_check_status_ok(wrap_sol_tx, solana_account) + + chain_id_sol = 112 + balance_pubkey = self.sol_client.ether2balance(new_account.address, + chain_id_sol, + evm_loader_id) + + tx = wSOL_from_solana_to_neon_tx( + solana_account, + balance_pubkey, + PublicKey("So11111111111111111111111111111111111111112"), + new_account, + full_amount, + evm_loader_id, + chain_id_sol + ) + + self.send_tx_and_check_status_ok(tx, solana_account) + + balance_before = web3_client_sol.get_balance(new_account.address) + + contract, _ = web3_client_sol.deploy_and_get_contract( + "opcodes/ChainId", "0.8.10", new_account) + + balance_after = web3_client_sol.get_balance(new_account.address) + @allure.feature("Transfer NEON <-> Solana") @allure.story("Withdraw from NEON to Solana") diff --git a/utils/transfers_inter_networks.py b/utils/transfers_inter_networks.py index ffee36426f..c34eb3baf2 100644 --- a/utils/transfers_inter_networks.py +++ b/utils/transfers_inter_networks.py @@ -26,8 +26,6 @@ def neon_from_solana_to_neon_tx(solana_account, neon_wallet, neon_mint, neon_acc authority_pool = get_authority_pool_address( evm_loader_id) - associated_token_address = get_associated_token_address( - solana_account.public_key, neon_mint) pool = get_associated_token_address(authority_pool, neon_mint) tx.add(Instruction.deposit( @@ -42,6 +40,40 @@ def neon_from_solana_to_neon_tx(solana_account, neon_wallet, neon_mint, neon_acc return tx +def wSOL_from_solana_to_neon_tx(solana_account, neon_wallet, wSOL_mint, neon_account, + amount, evm_loader_id, chain_id): + '''Transfer wSOL from solana to neon transaction''' + tx = Transaction(fee_payer=solana_account.public_key) + associated_token_address = get_associated_token_address( + solana_account.public_key, wSOL_mint) + print(associated_token_address) + tx.add(approve( + ApproveParams( + program_id=TOKEN_PROGRAM_ID, + source=associated_token_address, + delegate=neon_wallet, + owner=solana_account.public_key, + amount=amount))) + + authority_pool = get_authority_pool_address( + evm_loader_id) + + associated_token_address = get_associated_token_address( + solana_account.public_key, wSOL_mint) + pool = get_associated_token_address(authority_pool, wSOL_mint) + + tx.add(Instruction.deposit( + bytes.fromhex(neon_account.address[2:]), + chain_id, + neon_wallet, + wSOL_mint, + associated_token_address, + pool, + solana_account.public_key, + evm_loader_id)) + return tx + + def wSOL_tx(sol_client, spl_token, amount, solana_wallet, ata_address): mint_pubkey = PublicKey(spl_token['address_spl']) wSOL_account = sol_client.get_account_info(ata_address).value @@ -59,9 +91,8 @@ def wSOL_tx(sol_client, spl_token, amount, solana_wallet, ata_address): def neon_transfer_tx(web3_client, sol_client, amount, spl_token, solana_account, neon_account, erc20_spl, evm_loader_id): chain_id = web3_client._chain_id - neon_wallet_pda = sol_client.ether2balance(neon_account.address, chain_id, evm_loader_id) + delegate_pda = sol_client.ether2balance(neon_account.address, chain_id, evm_loader_id) - delegate_pda = neon_wallet_pda emulate_signer = get_solana_wallet_signer( solana_account, neon_account, web3_client) emulated_signer_pda = sol_client.ether2balance(emulate_signer.address, chain_id, evm_loader_id) @@ -95,13 +126,13 @@ def neon_transfer_tx(web3_client, sol_client, amount, spl_token, solana_account, owner=solana_account.public_key, amount=amount))) - tx.add(Instruction.balance_account(solana_wallet, neon_wallet_pda, + tx.add(Instruction.balance_account(solana_wallet, delegate_pda, neon_account.address, evm_loader_id, chain_id)) tx.add(Instruction.balance_account(solana_wallet, emulated_signer_pda, emulate_signer.address, evm_loader_id, chain_id)) - tx.add(Instruction.build_tx_instruction(solana_wallet, neon_wallet_pda, + tx.add(Instruction.build_tx_instruction(solana_wallet, delegate_pda, neon_transaction.rawTransaction, neon_keys, evm_loader_id)) return tx diff --git a/utils/web3client.py b/utils/web3client.py index 8f4604455b..3df46fdcd1 100644 --- a/utils/web3client.py +++ b/utils/web3client.py @@ -172,7 +172,7 @@ def deploy_contract( bytecode: str, gas: tp.Optional[int] = 0, gas_price: tp.Optional[int] = None, - constructor_args: tp.Optional[tp.List] = None, + constructor_args: tp.Optional[tp.List] = None ) -> web3.types.TxReceipt: """Proxy doesn't support send_transaction""" gas_price = gas_price or self.gas_price() @@ -192,9 +192,10 @@ def deploy_contract( if transaction["gas"] == 0: transaction["gas"] = self._web3.eth.estimate_gas(transaction) + signed_tx = self._web3.eth.account.sign_transaction(transaction, from_.key) - tx = self._web3.eth.send_raw_transaction(signed_tx.rawTransaction) - return self._web3.eth.wait_for_transaction_receipt(tx) + tx = self.eth.send_raw_transaction(signed_tx.rawTransaction) + return self.eth.wait_for_transaction_receipt(tx) def send_transaction( self, @@ -238,7 +239,7 @@ def deploy_and_get_contract( abi=contract_interface["abi"], bytecode=contract_interface["bin"], constructor_args=constructor_args, - gas=gas, + gas=gas ) contract = self.eth.contract( From 7c4002ed9a813311adec8ebe7ff3c7f0791c8bd2 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Tue, 31 Oct 2023 09:53:31 +0100 Subject: [PATCH 08/56] fixed deposit tests --- integration/tests/basic/test_deposit.py | 55 +++++++------------------ utils/consts.py | 13 ++++++ utils/solana_client.py | 7 ++++ utils/transfers_inter_networks.py | 46 +++------------------ utils/web3client.py | 22 ++++++---- 5 files changed, 55 insertions(+), 88 deletions(-) diff --git a/integration/tests/basic/test_deposit.py b/integration/tests/basic/test_deposit.py index 3284bd5194..b62ba9dcf5 100644 --- a/integration/tests/basic/test_deposit.py +++ b/integration/tests/basic/test_deposit.py @@ -1,5 +1,3 @@ -import json - import pytest import allure from _pytest.config import Config @@ -17,20 +15,11 @@ from web3 import exceptions as web3_exceptions from integration.tests.basic.helpers.basic import BaseMixin, BaseTests -from utils.consts import LAMPORT_PER_SOL -from utils.transfers_inter_networks import neon_transfer_tx, wSOL_tx, neon_from_solana_to_neon_tx, \ - wSOL_from_solana_to_neon_tx +from utils.consts import LAMPORT_PER_SOL, wSOL +from utils.transfers_inter_networks import neon_transfer_tx, wSOL_tx, token_from_solana_to_neon_tx from utils.helpers import wait_condition -wSOL = { - "chain_id": 111, - "address_spl": "So11111111111111111111111111111111111111112", - "address": "0x16869acc45BA20abEFB2DdE2096F66373fDe364F", - "decimals": 9, - "name": "Wrapped SOL", - "symbol": "wSOL", - "logo_uri": "https://raw.githubusercontent.com/neonlabsorg/token-list/master/assets/solana-wsol-logo.svg", -} + @allure.feature("Transfer NEON <-> Solana") @@ -66,12 +55,6 @@ def create_ata(self, solana_account, neon_mint): opts = TxOpts(skip_preflight=True, skip_confirmation=False) self.sol_client.send_transaction(trx, solana_account, opts=opts) - def send_tx_and_check_status_ok(self, tx, solana_account): - opts = TxOpts(skip_preflight=True, skip_confirmation=False) - sig = self.sol_client.send_transaction(tx, solana_account, opts=opts).value - sig_status = json.loads((self.sol_client.confirm_transaction(sig)).to_json()) - print(sig_status) - assert sig_status["result"]["value"][0]["status"] == {"Ok": None}, f"error:{sig_status}" def test_transfer_neon_from_solana_to_neon( self, new_account, solana_account, pytestconfig: Config, neon_mint @@ -89,7 +72,7 @@ def test_transfer_neon_from_solana_to_neon( self.create_ata(solana_account, neon_mint) self.withdraw_neon(solana_account, amount) - tx = neon_from_solana_to_neon_tx( + tx = token_from_solana_to_neon_tx( solana_account, balance_pubkey, neon_mint, @@ -98,7 +81,7 @@ def test_transfer_neon_from_solana_to_neon( evm_loader_id, self.web3_client._chain_id ) - self.send_tx_and_check_status_ok(tx, solana_account) + self.sol_client.send_tx_and_check_status_ok(tx, solana_account) neon_balance_after = self.get_balance_from_wei(new_account.address) assert neon_balance_after == neon_balance_before + amount @@ -110,7 +93,7 @@ def test_transfer_spl_token_from_solana_to_neon( amount = 0.1 full_amount = int(amount * LAMPORT_PER_SOL) - mint_pubkey = PublicKey(wSOL["address_spl"]) + mint_pubkey = wSOL["address_spl"] ata_address = get_associated_token_address( solana_account.public_key, mint_pubkey ) @@ -142,7 +125,7 @@ def test_transfer_spl_token_from_solana_to_neon( erc20_spl, evm_loader_id ) - self.send_tx_and_check_status_ok(transfer_tx, solana_account) + self.sol_client.send_tx_and_check_status_ok(transfer_tx, solana_account) ata_balance_after = spl_neon_token.get_balance( ata_address, commitment=Commitment("confirmed") @@ -154,13 +137,13 @@ def test_transfer_spl_token_from_solana_to_neon( ) def test_transfer_wrapped_sol_token_from_solana_to_neon( - self, solana_account, pytestconfig: Config, web3_client_sol, new_account + self, solana_account, pytestconfig: Config, web3_client_sol, new_account, ): evm_loader_id = pytestconfig.environment.evm_loader amount = 0.1 full_amount = int(amount * LAMPORT_PER_SOL) - mint_pubkey = PublicKey(wSOL["address_spl"]) + mint_pubkey = wSOL["address_spl"] ata_address = get_associated_token_address( solana_account.public_key, mint_pubkey ) @@ -171,31 +154,25 @@ def test_transfer_wrapped_sol_token_from_solana_to_neon( wrap_sol_tx = wSOL_tx(self.sol_client, wSOL, full_amount, solana_account.public_key, ata_address ) - self.send_tx_and_check_status_ok(wrap_sol_tx, solana_account) + self.sol_client.send_tx_and_check_status_ok(wrap_sol_tx, solana_account) - chain_id_sol = 112 balance_pubkey = self.sol_client.ether2balance(new_account.address, - chain_id_sol, + web3_client_sol.eth.chain_id, evm_loader_id) - tx = wSOL_from_solana_to_neon_tx( + tx = token_from_solana_to_neon_tx( solana_account, balance_pubkey, - PublicKey("So11111111111111111111111111111111111111112"), + wSOL["address_spl"], new_account, full_amount, evm_loader_id, - chain_id_sol + web3_client_sol.eth.chain_id ) - self.send_tx_and_check_status_ok(tx, solana_account) - - balance_before = web3_client_sol.get_balance(new_account.address) - - contract, _ = web3_client_sol.deploy_and_get_contract( - "opcodes/ChainId", "0.8.10", new_account) + self.sol_client.send_tx_and_check_status_ok(tx, solana_account) - balance_after = web3_client_sol.get_balance(new_account.address) + assert web3_client_sol.get_balance(new_account) / LAMPORT_PER_SOL == full_amount @allure.feature("Transfer NEON <-> Solana") diff --git a/utils/consts.py b/utils/consts.py index 6a823ba170..7aca95cb02 100644 --- a/utils/consts.py +++ b/utils/consts.py @@ -1,5 +1,6 @@ from enum import Enum +from solana.publickey import PublicKey LAMPORT_PER_SOL = 1_000_000_000 ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" @@ -7,6 +8,7 @@ INITIAL_ACCOUNT_AMOUNT = 100 MAX_UINT_256 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + class Unit(Enum): WEI = "wei" KWEI = "kwei" @@ -32,3 +34,14 @@ def get_transfer_amount(self) -> float: def get_default_initial_amount(self) -> int: return self.FAUCET_1ST_REQUEST_AMOUNT.value + + +wSOL = { + "chain_id": 111, + "address_spl": PublicKey("So11111111111111111111111111111111111111112"), + "address": "0x16869acc45BA20abEFB2DdE2096F66373fDe364F", + "decimals": 9, + "name": "Wrapped SOL", + "symbol": "wSOL", + "logo_uri": "https://raw.githubusercontent.com/neonlabsorg/token-list/master/assets/solana-wsol-logo.svg", +} diff --git a/utils/solana_client.py b/utils/solana_client.py index 5ad5ccee02..3a782b681e 100644 --- a/utils/solana_client.py +++ b/utils/solana_client.py @@ -1,3 +1,4 @@ +import json import time import typing as tp @@ -107,3 +108,9 @@ def create_spl(self, owner: Keypair, decimals: int = 9): ) return token_mint, assoc_addr + + def send_tx_and_check_status_ok(self, tx, solana_account): + opts = TxOpts(skip_preflight=True, skip_confirmation=False) + sig = self.send_transaction(tx, solana_account, opts=opts).value + sig_status = json.loads((self.confirm_transaction(sig)).to_json()) + assert sig_status["result"]["value"][0]["status"] == {"Ok": None}, f"error:{sig_status}" diff --git a/utils/transfers_inter_networks.py b/utils/transfers_inter_networks.py index c34eb3baf2..0aebdd8e80 100644 --- a/utils/transfers_inter_networks.py +++ b/utils/transfers_inter_networks.py @@ -8,12 +8,12 @@ from utils.instructions import Instruction, get_solana_wallet_signer -def neon_from_solana_to_neon_tx(solana_account, neon_wallet, neon_mint, neon_account, +def token_from_solana_to_neon_tx(solana_account, neon_wallet, mint, neon_account, amount, evm_loader_id, chain_id): - '''Transfer NEON from solana to neon transaction''' + '''Transfer any token from solana to neon transaction''' tx = Transaction(fee_payer=solana_account.public_key) associated_token_address = get_associated_token_address( - solana_account.public_key, neon_mint) + solana_account.public_key, mint) tx.add(approve( ApproveParams( @@ -26,47 +26,13 @@ def neon_from_solana_to_neon_tx(solana_account, neon_wallet, neon_mint, neon_acc authority_pool = get_authority_pool_address( evm_loader_id) - pool = get_associated_token_address(authority_pool, neon_mint) + pool = get_associated_token_address(authority_pool, mint) tx.add(Instruction.deposit( bytes.fromhex(neon_account.address[2:]), chain_id, neon_wallet, - neon_mint, - associated_token_address, - pool, - solana_account.public_key, - evm_loader_id)) - return tx - - -def wSOL_from_solana_to_neon_tx(solana_account, neon_wallet, wSOL_mint, neon_account, - amount, evm_loader_id, chain_id): - '''Transfer wSOL from solana to neon transaction''' - tx = Transaction(fee_payer=solana_account.public_key) - associated_token_address = get_associated_token_address( - solana_account.public_key, wSOL_mint) - print(associated_token_address) - tx.add(approve( - ApproveParams( - program_id=TOKEN_PROGRAM_ID, - source=associated_token_address, - delegate=neon_wallet, - owner=solana_account.public_key, - amount=amount))) - - authority_pool = get_authority_pool_address( - evm_loader_id) - - associated_token_address = get_associated_token_address( - solana_account.public_key, wSOL_mint) - pool = get_associated_token_address(authority_pool, wSOL_mint) - - tx.add(Instruction.deposit( - bytes.fromhex(neon_account.address[2:]), - chain_id, - neon_wallet, - wSOL_mint, + mint, associated_token_address, pool, solana_account.public_key, @@ -90,7 +56,7 @@ def wSOL_tx(sol_client, spl_token, amount, solana_wallet, ata_address): def neon_transfer_tx(web3_client, sol_client, amount, spl_token, solana_account, neon_account, erc20_spl, evm_loader_id): - chain_id = web3_client._chain_id + chain_id = web3_client.eth.chain_id delegate_pda = sol_client.ether2balance(neon_account.address, chain_id, evm_loader_id) emulate_signer = get_solana_wallet_signer( diff --git a/utils/web3client.py b/utils/web3client.py index f1ce35d8be..40c8f38372 100644 --- a/utils/web3client.py +++ b/utils/web3client.py @@ -18,16 +18,15 @@ class Web3Client: def __init__( - self, - proxy_url: str, - tracer_url: tp.Optional[tp.Any] = None, + self, + proxy_url: str, + tracer_url: tp.Optional[tp.Any] = None, session: tp.Optional[tp.Any] = None ): self._proxy_url = proxy_url self._tracer_url = tracer_url self._web3 = web3.Web3(web3.HTTPProvider(proxy_url, session=session, request_kwargs={"timeout": 30})) - def __getattr__(self, item): return getattr(self._web3, item) @@ -217,6 +216,12 @@ def call_function_at_address(self, contract_address, signature, args, result_typ result = self._web3.eth.call(tx) return abi.decode(result_types, result)[0] + def get_balance( + self, address: tp.Union[str, eth_account.signers.local.LocalAccount] + ): + if not isinstance(address, str): + address = address.address + return self._web3.eth.get_balance(address, "pending") class NeonChainWeb3Client(Web3Client): def __init__(self, proxy_url: str, tracer_url: tp.Optional[tp.Any] = None, session: tp.Optional[tp.Any] = None): @@ -250,6 +255,8 @@ def create_account_with_balance( ) return account + + def send_neon( self, from_: eth_account.signers.local.LocalAccount, @@ -280,11 +287,7 @@ def send_neon( def get_balance( self, address: tp.Union[str, eth_account.signers.local.LocalAccount] ): - if not isinstance(address, str): - address = address.address - return web3.Web3.from_wei( - self._web3.eth.get_balance(address, "pending"), "ether" - ) + return web3.Web3.from_wei(super().get_balance(address), "ether") class SolChainWeb3Client(Web3Client): @@ -293,3 +296,4 @@ def __init__(self, proxy_url: str): def create_account_with_balance(self): pass + From 648cadc172c67d4f5a9135f9e685811abbd68d3b Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Wed, 1 Nov 2023 17:41:12 +0100 Subject: [PATCH 09/56] added tests for payment in sol --- contracts/common/WNativeChainToken.sol | 82 ++++++++++ integration/tests/base.py | 23 +-- integration/tests/basic/helpers/basic.py | 10 +- integration/tests/basic/test_deposit.py | 53 +++---- .../basic/test_payment_in_different_tokens.py | 141 ++++++++++++++++++ integration/tests/conftest.py | 34 ++++- utils/solana_client.py | 47 ++++++ utils/transfers_inter_networks.py | 3 +- utils/web3client.py | 18 ++- 9 files changed, 350 insertions(+), 61 deletions(-) create mode 100644 contracts/common/WNativeChainToken.sol create mode 100644 integration/tests/basic/test_payment_in_different_tokens.py diff --git a/contracts/common/WNativeChainToken.sol b/contracts/common/WNativeChainToken.sol new file mode 100644 index 0000000000..4e2bd714ea --- /dev/null +++ b/contracts/common/WNativeChainToken.sol @@ -0,0 +1,82 @@ +pragma solidity ^0.8.12; + +contract WNativeChainToken { + + event Approval(address indexed src, address indexed guy, uint wad); + event Transfer(address indexed src, address indexed dst, uint wad); + event Deposit(address indexed dst, uint wad); + event Withdrawal(address indexed src, uint wad); + + mapping(address => uint) public balanceOf; + mapping(address => mapping(address => uint)) public allowance; + + constructor() payable { + deposit(); + } + + function deposit() public payable { + balanceOf[msg.sender] += msg.value; + emit Deposit(msg.sender, msg.value); + } + + function withdraw(uint wad) public { + require(balanceOf[msg.sender] >= wad); + balanceOf[msg.sender] -= wad; + payable(msg.sender).transfer(wad); + emit Withdrawal(msg.sender, wad); + } + + function withdrawTo(address to, uint wad) public { + require(balanceOf[msg.sender] >= wad); + balanceOf[msg.sender] -= wad; + payable(to).transfer(wad); + emit Withdrawal(to, wad); + } + + function totalSupply() public view returns (uint) { + return address(this).balance; + } + + function approve(address guy, uint wad) public returns (bool) { + allowance[msg.sender][guy] = wad; + emit Approval(msg.sender, guy, wad); + return true; + } + + function transfer(address dst, uint wad) public returns (bool) { + return transferFrom(msg.sender, dst, wad); + } + + function transferFrom(address src, address dst, uint wad) + public + returns (bool) + { + require(balanceOf[src] >= wad); + + if (src != msg.sender) { + require(allowance[src][msg.sender] >= wad); + allowance[src][msg.sender] -= wad; + } + + balanceOf[src] -= wad; + balanceOf[dst] += wad; + + emit Transfer(src, dst, wad); + + return true; + } +} + +contract WNativeChainTokenCaller { + WNativeChainToken nativeChainToken; + event Log(address indexed addr); + + constructor() payable { + nativeChainToken = new WNativeChainToken{value: msg.value}(); + emit Log(address(nativeChainToken)); + } + + function deposit() public payable { + nativeChainToken.deposit{value: msg.value}(); + } +} \ No newline at end of file diff --git a/integration/tests/base.py b/integration/tests/base.py index 2d7fab3327..1aacc48a19 100644 --- a/integration/tests/base.py +++ b/integration/tests/base.py @@ -6,7 +6,7 @@ from utils.faucet import Faucet from utils.operator import Operator from utils.solana_client import SolanaClient -from utils.web3client import NeonChainWeb3Client +from utils.web3client import NeonChainWeb3Client, SolChainWeb3Client class BaseTests: @@ -14,14 +14,16 @@ class BaseTests: operator: Operator faucet: Faucet web3_client: NeonChainWeb3Client + web3_client_sol: SolChainWeb3Client sol_client: SolanaClient sol_price: float @pytest.fixture(autouse=True) - def prepare(self, operator: Operator, faucet: Faucet, web3_client, sol_client): + def prepare(self, operator: Operator, faucet: Faucet, web3_client, web3_client_sol, sol_client): self.operator = operator self.faucet = faucet self.web3_client = web3_client + self.web3_client_sol = web3_client_sol self.sol_client = sol_client @pytest.fixture(autouse=True) @@ -29,23 +31,25 @@ def prepare_account(self, prepare_account): self.acc = prepare_account def create_tx_object(self, sender, recipient=None, amount=0, nonce=None, gas=None, gas_price=None, data=None, - estimate_gas=True): + estimate_gas=True, web3_client=None): + if web3_client is None: + web3_client = self.web3_client if gas_price is None: - gas_price = self.web3_client.gas_price() + gas_price = web3_client.gas_price() if nonce is None: - nonce = self.web3_client.eth.get_transaction_count(sender) + nonce = web3_client.eth.get_transaction_count(sender) transaction = { "from": sender, - "chainId": self.web3_client.eth.chain_id, "gasPrice": gas_price, - "nonce": nonce, + "chainId": web3_client.eth.chain_id, + "nonce": nonce } if gas is not None: transaction["gas"] = gas if amount is not None: - transaction["value"] = self.web3_client.to_wei(amount, Unit.ETHER) + transaction["value"] = web3_client.to_main_currency(amount) if recipient is not None: transaction["to"] = recipient @@ -54,5 +58,6 @@ def create_tx_object(self, sender, recipient=None, amount=0, nonce=None, gas=Non transaction["data"] = data if estimate_gas: - transaction["gas"] = self.web3_client.eth.estimate_gas(transaction) + transaction["gas"] = web3_client.eth.estimate_gas(transaction) + return transaction diff --git a/integration/tests/basic/helpers/basic.py b/integration/tests/basic/helpers/basic.py index 2375306d40..110f88a183 100644 --- a/integration/tests/basic/helpers/basic.py +++ b/integration/tests/basic/helpers/basic.py @@ -184,19 +184,21 @@ def wait_finalized_block(self, block_num: int): response = self.proxy_api.send_rpc("neon_finalizedBlockNumber", []) fin_block_num = int(response["result"], 16) - def make_contract_tx_object(self, sender=None, amount=0, estimate_gas=False) -> tp.Dict: + def make_contract_tx_object(self, sender=None, amount=0, estimate_gas=False, web3_client=None) -> tp.Dict: """Can be used with build_transaction method""" if sender is None: sender = self.sender_account.address - return super().create_tx_object(sender, recipient=None, amount=amount, estimate_gas=estimate_gas) + return super().create_tx_object(sender, recipient=None, amount=amount, estimate_gas=estimate_gas, + web3_client=web3_client) def create_tx_object(self, sender=None, recipient=None, amount=2, nonce=None, gas=None, gas_price=None, data=None, - estimate_gas=True): + estimate_gas=True, web3_client=None): if sender is None: sender = self.sender_account.address if recipient is None: recipient = self.recipient_account.address - return super().create_tx_object(sender, recipient, amount, nonce, gas, gas_price, data, estimate_gas) + return super().create_tx_object(sender, recipient, amount, nonce, gas, gas_price, + data, estimate_gas, web3_client) def create_contract_call_tx_object(self, sender=None, amount=None): if sender is None: diff --git a/integration/tests/basic/test_deposit.py b/integration/tests/basic/test_deposit.py index b62ba9dcf5..49284e7801 100644 --- a/integration/tests/basic/test_deposit.py +++ b/integration/tests/basic/test_deposit.py @@ -1,7 +1,6 @@ import pytest import allure from _pytest.config import Config -from solana.publickey import PublicKey from solana.keypair import Keypair from solana.rpc.commitment import Commitment from solana.rpc.types import TxOpts @@ -20,8 +19,6 @@ from utils.helpers import wait_condition - - @allure.feature("Transfer NEON <-> Solana") @allure.story("Deposit from Solana to NEON") class TestDeposit(BaseMixin): @@ -29,33 +26,13 @@ def withdraw_neon(self, dest_acc, move_amount): contract, _ = self.web3_client.deploy_and_get_contract( "precompiled/NeonToken", "0.8.10", account=self.sender_account ) + tx = self.create_contract_call_tx_object(amount=move_amount) instruction_tx = contract.functions.withdraw( - bytes(dest_acc.public_key) - ).build_transaction( - { - "from": self.sender_account.address, - "nonce": self.web3_client.eth.get_transaction_count( - self.sender_account.address - ), - "gasPrice": self.web3_client.gas_price(), - "value": self.web3_client._web3.to_wei(move_amount, "ether"), - } - ) + bytes(dest_acc.public_key)).build_transaction(tx) receipt = self.web3_client.send_transaction(self.sender_account, instruction_tx) assert receipt["status"] == 1 - def create_ata(self, solana_account, neon_mint): - trx = Transaction() - trx.add( - create_associated_token_account( - solana_account.public_key, solana_account.public_key, neon_mint - ) - ) - opts = TxOpts(skip_preflight=True, skip_confirmation=False) - self.sol_client.send_transaction(trx, solana_account, opts=opts) - - def test_transfer_neon_from_solana_to_neon( self, new_account, solana_account, pytestconfig: Config, neon_mint ): @@ -65,12 +42,12 @@ def test_transfer_neon_from_solana_to_neon( evm_loader_id = pytestconfig.environment.evm_loader balance_pubkey = self.sol_client.ether2balance(new_account.address, - self.web3_client._chain_id, + self.web3_client.eth.chain_id, evm_loader_id) neon_balance_before = self.get_balance_from_wei(new_account.address) - self.create_ata(solana_account, neon_mint) + self.sol_client.create_ata(solana_account, neon_mint) self.withdraw_neon(solana_account, amount) tx = token_from_solana_to_neon_tx( solana_account, @@ -79,7 +56,7 @@ def test_transfer_neon_from_solana_to_neon( new_account, full_amount, evm_loader_id, - self.web3_client._chain_id + self.web3_client.eth.chain_id ) self.sol_client.send_tx_and_check_status_ok(tx, solana_account) @@ -98,7 +75,7 @@ def test_transfer_spl_token_from_solana_to_neon( solana_account.public_key, mint_pubkey ) - self.create_ata(solana_account, mint_pubkey) + self.sol_client.create_ata(solana_account, mint_pubkey) spl_neon_token = SplToken( self.sol_client, mint_pubkey, TOKEN_PROGRAM_ID, solana_account @@ -108,11 +85,12 @@ def test_transfer_spl_token_from_solana_to_neon( ) # wrap SOL - wrap_sol_tx = wSOL_tx(self.sol_client, + wSOL_account = self.sol_client.get_account_info(ata_address).value + wrap_sol_tx = wSOL_tx(wSOL_account, wSOL, full_amount, solana_account.public_key, ata_address) - self.send_tx_and_check_status_ok(wrap_sol_tx, solana_account) + self.sol_client.send_tx_and_check_status_ok(wrap_sol_tx, solana_account) # transfer wSOL transfer_tx = neon_transfer_tx( @@ -137,8 +115,10 @@ def test_transfer_spl_token_from_solana_to_neon( ) def test_transfer_wrapped_sol_token_from_solana_to_neon( - self, solana_account, pytestconfig: Config, web3_client_sol, new_account, + self, solana_account, pytestconfig: Config, web3_client_sol ): + new_account = self.web3_client.create_account() + evm_loader_id = pytestconfig.environment.evm_loader amount = 0.1 full_amount = int(amount * LAMPORT_PER_SOL) @@ -148,12 +128,13 @@ def test_transfer_wrapped_sol_token_from_solana_to_neon( solana_account.public_key, mint_pubkey ) - self.create_ata(solana_account, mint_pubkey) + self.sol_client.create_ata(solana_account, mint_pubkey) # wrap SOL - wrap_sol_tx = wSOL_tx(self.sol_client, - wSOL, full_amount, solana_account.public_key, ata_address - ) + wSOL_account = self.sol_client.get_account_info(ata_address).value + wrap_sol_tx = wSOL_tx( + wSOL_account, wSOL, full_amount, solana_account.public_key, ata_address + ) self.sol_client.send_tx_and_check_status_ok(wrap_sol_tx, solana_account) balance_pubkey = self.sol_client.ether2balance(new_account.address, diff --git a/integration/tests/basic/test_payment_in_different_tokens.py b/integration/tests/basic/test_payment_in_different_tokens.py new file mode 100644 index 0000000000..7ea3856618 --- /dev/null +++ b/integration/tests/basic/test_payment_in_different_tokens.py @@ -0,0 +1,141 @@ +import allure +import pytest + +from integration.tests.basic.helpers.basic import BaseMixin + + +@allure.feature("Multiply token") +@allure.story("Payments in sol tokens") +class TestSolChain(BaseMixin): + + @pytest.fixture(scope="class") + def bob(self, class_account): + return class_account + + @pytest.fixture(scope="class") + def alice(self, web3_client, web3_client_sol, faucet, eth_bank_account): + return web3_client_sol.create_account() + + @pytest.fixture(scope="function") + def check_neon_balance_does_not_changed(self, alice, bob, web3_client): + alica_balance_alica_before = web3_client.get_balance(alice) + bob_balance_alica_before = web3_client.get_balance(bob) + yield + alica_balance_alica_after = web3_client.get_balance(alice) + bob_balance_alica_after = web3_client.get_balance(bob) + assert alica_balance_alica_after == alica_balance_alica_before + assert bob_balance_alica_after == bob_balance_alica_before + + def test_user_to_user_trx(self, web3_client_sol, alice, bob, + check_neon_balance_does_not_changed): + bob_sol_balance_before = web3_client_sol.get_balance(bob) + alice_sol_balance_before = web3_client_sol.get_balance(alice) + value = 10 + transaction = self.create_tx_object(bob.address, alice.address, value, web3_client=web3_client_sol) + receipt = web3_client_sol.send_transaction(bob, transaction) + assert receipt["status"] == 1 + bob_sol_balance_after = web3_client_sol.get_balance(bob) + alice_sol_balance_after = web3_client_sol.get_balance(alice) + assert alice_sol_balance_after == alice_sol_balance_before + web3_client_sol.to_main_currency(value) + assert bob_sol_balance_after < bob_sol_balance_before - web3_client_sol.to_main_currency(value) + + def test_user_to_contract_and_contract_to_user_trx(self, web3_client_sol, bob, + check_neon_balance_does_not_changed, + wsol_contract): + bob_sol_balance_before = web3_client_sol.get_balance(bob) + contract_sol_balance_initial = web3_client_sol.get_balance(wsol_contract.address) + amount = 10 + tx = self.make_contract_tx_object(bob.address, amount=amount, web3_client=web3_client_sol) + instruction_tx = wsol_contract.functions.deposit().build_transaction(tx) + receipt = self.web3_client_sol.send_transaction(bob, instruction_tx) + assert receipt["status"] == 1 + bob_sol_balance_after_deposit = web3_client_sol.get_balance(bob) + contract_sol_balance_after_deposit = web3_client_sol.get_balance(wsol_contract.address) + assert contract_sol_balance_after_deposit == contract_sol_balance_initial + web3_client_sol.to_main_currency( + amount) + assert bob_sol_balance_after_deposit < bob_sol_balance_before - web3_client_sol.to_main_currency(amount) + + tx = self.make_contract_tx_object(bob.address, web3_client=web3_client_sol) + instruction_tx = wsol_contract.functions.withdraw( + web3_client_sol.to_main_currency(10)).build_transaction(tx) + receipt = self.web3_client_sol.send_transaction(bob, instruction_tx) + assert receipt["status"] == 1 + bob_sol_balance_after_withdraw = web3_client_sol.get_balance(bob) + contract_sol_balance_after_withdraw = web3_client_sol.get_balance(wsol_contract.address) + assert contract_sol_balance_after_withdraw == contract_sol_balance_initial + assert bob_sol_balance_after_withdraw < bob_sol_balance_after_deposit + \ + web3_client_sol.to_main_currency(amount) + + def test_contract_to_contract_trx(self, web3_client_sol, bob): + # contract to new contract + amount = 1 + value = web3_client_sol.to_main_currency(amount) + bob_sol_balance_before = web3_client_sol.get_balance(bob) + wsol_contract_caller, resp = web3_client_sol.deploy_and_get_contract( + contract="common/WNativeChainToken", version="0.8.12", + contract_name="WNativeChainTokenCaller", account=bob, value=value + ) + wrapper_address = wsol_contract_caller.events.Log().process_receipt(resp)[0].args["addr"] + assert web3_client_sol.get_balance(wrapper_address) == value + + # contract to existing contract + tx = self.make_contract_tx_object(bob.address, amount=amount, web3_client=web3_client_sol) + instruction_tx = wsol_contract_caller.functions.deposit().build_transaction(tx) + receipt = self.web3_client_sol.send_transaction(bob, instruction_tx) + assert receipt["status"] == 1 + bob_sol_balance_after = web3_client_sol.get_balance(bob) + + assert web3_client_sol.get_balance(wrapper_address) == value * 2 + assert bob_sol_balance_after < bob_sol_balance_before - value * 2 + + def test_user_to_contract_wrong_chain_id_trx(self, web3_client_sol, bob, + check_neon_balance_does_not_changed, + event_caller_contract): + tx = self.make_contract_tx_object(bob.address, amount=1) + instruction_tx = event_caller_contract.functions.unnamedArg("hello").build_transaction(tx) + with pytest.raises(ValueError, match="wrong chain id"): + self.web3_client_sol.send_transaction(bob, instruction_tx) + + def test_deploy_contract(self, web3_client_sol, bob, + check_neon_balance_does_not_changed): + sol_balance_before = web3_client_sol.get_balance(bob) + contract, _ = web3_client_sol.deploy_and_get_contract( + contract="common/Common", version="0.8.12", + contract_name="Common", account=bob, + ) + sol_balance_after = web3_client_sol.get_balance(bob) + assert sol_balance_after < sol_balance_before + + def test_deploy_contract_with_sending_tokens(self, web3_client_sol, bob, + check_neon_balance_does_not_changed): + sol_bob_balance_before = web3_client_sol.get_balance(bob) + value = 1000 + contract, receipt = web3_client_sol.deploy_and_get_contract( + contract="common/WNativeChainToken", version="0.8.12", + contract_name="WNativeChainToken", account=bob, value=value + ) + assert receipt["status"] == 1 + sol_bob_balance_after = web3_client_sol.get_balance(bob) + contract_balance = web3_client_sol.get_balance(contract.address) + assert contract_balance == value + assert sol_bob_balance_after < sol_bob_balance_before - value + + def test_deploy_contract_by_one_user_to_different_chain(self, web3_client_sol, new_account, alice, + web3_client): + def deploy_contract(w3_client): + _, rcpt = w3_client.deploy_and_get_contract( + contract="common/Common", version="0.8.12", + contract_name="Common", account=new_account + ) + return rcpt + + deploy_contract(web3_client_sol) + + with pytest.raises(ValueError, match="EVM Error. Attempt to deploy to existing account"): + deploy_contract(web3_client) + + # any transaction to raise the nonce + web3_client.send_neon(new_account, alice, amount=1) + + receipt = deploy_contract(web3_client) + assert receipt["status"] == 1 diff --git a/integration/tests/conftest.py b/integration/tests/conftest.py index 1f75639787..704121804b 100644 --- a/integration/tests/conftest.py +++ b/integration/tests/conftest.py @@ -184,7 +184,7 @@ def erc20_spl( @pytest.fixture(scope="session") def erc20_simple(web3_client, faucet): - erc20 = ERC20(web3_client,faucet) + erc20 = ERC20(web3_client, faucet) return erc20 @@ -205,13 +205,28 @@ def erc20_spl_mintable(web3_client: NeonChainWeb3Client, faucet, sol_client, sol @pytest.fixture(scope="function") -def new_account(web3_client, faucet, eth_bank_account): - yield web3_client.create_account_with_balance(faucet, bank_account=eth_bank_account) +def new_account(web3_client, sol_client, faucet, eth_bank_account, + web3_client_sol, pytestconfig, solana_account): + account = web3_client.create_account_with_balance(faucet, bank_account=eth_bank_account) + sol_client.deposit_wrapped_sol_from_solana_to_neon(solana_account, + account, + web3_client_sol.eth.chain_id, + pytestconfig.environment.evm_loader) + yield account @pytest.fixture(scope="class") -def class_account(web3_client, faucet, eth_bank_account): - yield web3_client.create_account_with_balance(faucet, bank_account=eth_bank_account) +def class_account(web3_client, faucet, eth_bank_account, solana_account, sol_client, web3_client_sol, pytestconfig): + account = web3_client.create_account_with_balance(faucet, bank_account=eth_bank_account) + sol_client.request_airdrop(solana_account.public_key, 5 * LAMPORT_PER_SOL) + sol_client.deposit_wrapped_sol_from_solana_to_neon(solana_account, + account, + web3_client_sol.eth.chain_id, + pytestconfig.environment.evm_loader, + 5 * LAMPORT_PER_SOL) + + + yield account @pytest.fixture(scope="function") @@ -248,3 +263,12 @@ def event_caller_contract(web3_client, class_account) -> typing.Any: "common/EventCaller", "0.8.12", class_account ) yield event_caller + + +@pytest.fixture(scope="class") +def wsol_contract(web3_client_sol, class_account): + wsol, _ = web3_client_sol.deploy_and_get_contract( + contract="common/WNativeChainToken", version="0.8.12", + contract_name="WNativeChainToken", account=class_account, + ) + return wsol \ No newline at end of file diff --git a/utils/solana_client.py b/utils/solana_client.py index 3a782b681e..8d029401a4 100644 --- a/utils/solana_client.py +++ b/utils/solana_client.py @@ -12,10 +12,14 @@ from solana.transaction import Transaction from solders.rpc.errors import InternalErrorMessage from solders.rpc.responses import RequestAirdropResp +from spl.token.instructions import get_associated_token_address, create_associated_token_account +from utils.consts import LAMPORT_PER_SOL, wSOL from utils.helpers import wait_condition from spl.token.constants import TOKEN_PROGRAM_ID +from utils.transfers_inter_networks import wSOL_tx, token_from_solana_to_neon_tx + class SolanaClient(solana.rpc.api.Client): def __init__(self, endpoint, account_seed_version="\3"): @@ -114,3 +118,46 @@ def send_tx_and_check_status_ok(self, tx, solana_account): sig = self.send_transaction(tx, solana_account, opts=opts).value sig_status = json.loads((self.confirm_transaction(sig)).to_json()) assert sig_status["result"]["value"][0]["status"] == {"Ok": None}, f"error:{sig_status}" + + def create_ata(self, solana_account, neon_mint): + trx = Transaction() + trx.add( + create_associated_token_account( + solana_account.public_key, solana_account.public_key, neon_mint + ) + ) + opts = TxOpts(skip_preflight=True, skip_confirmation=False) + self.send_transaction(trx, solana_account, opts=opts) + + def deposit_wrapped_sol_from_solana_to_neon(self, solana_account, neon_account, chain_id, evm_loader_id, full_amount=None): + if not full_amount: + full_amount = int(0.1 * LAMPORT_PER_SOL) + mint_pubkey = wSOL["address_spl"] + ata_address = get_associated_token_address( + solana_account.public_key, mint_pubkey + ) + + self.create_ata(solana_account, mint_pubkey) + + # wrap SOL + wSOL_account = self.get_account_info(ata_address).value + wrap_sol_tx = wSOL_tx(wSOL_account, + wSOL, full_amount, solana_account.public_key, ata_address + ) + self.send_tx_and_check_status_ok(wrap_sol_tx, solana_account) + + balance_pubkey = self.ether2balance(neon_account.address, + chain_id, + evm_loader_id) + tx = token_from_solana_to_neon_tx( + solana_account, + balance_pubkey, + wSOL["address_spl"], + neon_account, + full_amount, + evm_loader_id, + chain_id + ) + + self.send_tx_and_check_status_ok(tx, solana_account) + diff --git a/utils/transfers_inter_networks.py b/utils/transfers_inter_networks.py index 0aebdd8e80..05166df8bd 100644 --- a/utils/transfers_inter_networks.py +++ b/utils/transfers_inter_networks.py @@ -40,9 +40,8 @@ def token_from_solana_to_neon_tx(solana_account, neon_wallet, mint, neon_account return tx -def wSOL_tx(sol_client, spl_token, amount, solana_wallet, ata_address): +def wSOL_tx(wSOL_account, spl_token, amount, solana_wallet, ata_address): mint_pubkey = PublicKey(spl_token['address_spl']) - wSOL_account = sol_client.get_account_info(ata_address).value tx = Transaction(fee_payer=solana_wallet) if (wSOL_account is None): diff --git a/utils/web3client.py b/utils/web3client.py index 40c8f38372..b5632bc35b 100644 --- a/utils/web3client.py +++ b/utils/web3client.py @@ -108,6 +108,7 @@ def deploy_contract( gas: tp.Optional[int] = 0, gas_price: tp.Optional[int] = None, constructor_args: tp.Optional[tp.List] = None, + value = 0 ) -> web3.types.TxReceipt: """Proxy doesn't support send_transaction""" gas_price = gas_price or self.gas_price() @@ -120,6 +121,7 @@ def deploy_contract( "gas": gas, "gasPrice": gas_price, "nonce": self.get_nonce(from_), + "value": value } ) @@ -142,6 +144,8 @@ def send_transaction( transaction["gasPrice"] = self.gas_price() if "gas" not in transaction: transaction["gas"] = self._web3.eth.estimate_gas(transaction) + if "nonce" not in transaction: + transaction["nonce"] = self.get_nonce(account) if gas_multiplier is not None: transaction["gas"] = int(transaction["gas"] * gas_multiplier) instruction_tx = self._web3.eth.account.sign_transaction( @@ -159,6 +163,7 @@ def deploy_and_get_contract( constructor_args: tp.Optional[tp.Any] = None, import_remapping: tp.Optional[dict] = None, gas: tp.Optional[int] = 0, + value = 0 ) -> tp.Tuple[tp.Any, web3.types.TxReceipt]: contract_interface = helpers.get_contract_interface( contract, @@ -173,6 +178,7 @@ def deploy_and_get_contract( bytecode=contract_interface["bin"], constructor_args=constructor_args, gas=gas, + value=value ) contract = self.eth.contract( @@ -223,6 +229,7 @@ def get_balance( address = address.address return self._web3.eth.get_balance(address, "pending") + class NeonChainWeb3Client(Web3Client): def __init__(self, proxy_url: str, tracer_url: tp.Optional[tp.Any] = None, session: tp.Optional[tp.Any] = None): super().__init__(proxy_url, tracer_url, session) @@ -255,8 +262,6 @@ def create_account_with_balance( ) return account - - def send_neon( self, from_: eth_account.signers.local.LocalAccount, @@ -288,12 +293,15 @@ def get_balance( self, address: tp.Union[str, eth_account.signers.local.LocalAccount] ): return web3.Web3.from_wei(super().get_balance(address), "ether") + @staticmethod + def to_main_currency(amount): + return web3.Web3.to_wei(amount, "ether") class SolChainWeb3Client(Web3Client): def __init__(self, proxy_url: str): super().__init__(f"{proxy_url}/sol") - def create_account_with_balance(self): - pass - + @staticmethod + def to_main_currency(amount): + return amount * 1_000_000_000 From 8e313cd69d8db9a19ed2691c25cf4b6a59c45471 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Thu, 2 Nov 2023 18:37:39 +0100 Subject: [PATCH 10/56] added tests for different chains --- .../tests/basic/evm/opcodes/test_chainid.py | 16 +- .../tests/basic/rpc/test_rpc_estimate_gas.py | 17 +- .../basic/test_payment_in_different_tokens.py | 148 ++++++++++++++---- integration/tests/basic/test_wneon.py | 8 - integration/tests/conftest.py | 64 +++++--- utils/erc20wrapper.py | 16 +- utils/web3client.py | 52 ++++-- 7 files changed, 211 insertions(+), 110 deletions(-) diff --git a/integration/tests/basic/evm/opcodes/test_chainid.py b/integration/tests/basic/evm/opcodes/test_chainid.py index b80467e9b4..51f474022a 100644 --- a/integration/tests/basic/evm/opcodes/test_chainid.py +++ b/integration/tests/basic/evm/opcodes/test_chainid.py @@ -5,11 +5,21 @@ class TestChainId(BaseMixin): @pytest.fixture(scope="class") - def contract(self, web3_client, class_account): + def contract_neon(self, web3_client, class_account): contract, _ = web3_client.deploy_and_get_contract( "opcodes/ChainId", "0.8.10", class_account) return contract - def test_chainId(self, contract): - assert contract.functions.getCurrentValues().call() == 0 + @pytest.fixture(scope="class") + def contract_sol(self, web3_client_sol, class_account_sol_chain): + contract, _ = web3_client_sol.deploy_and_get_contract( + "opcodes/ChainId", "0.8.10", class_account_sol_chain) + return contract + + def test_chain_id_sol(self, contract_sol, pytestconfig): + assert contract_sol.functions.getCurrentValues().call() \ + == pytestconfig.environment.network_ids["sol"] + def test_chain_id_neon(self, contract_neon, pytestconfig): + assert contract_neon.functions.getCurrentValues().call() \ + == pytestconfig.environment.network_ids["neon"] diff --git a/integration/tests/basic/rpc/test_rpc_estimate_gas.py b/integration/tests/basic/rpc/test_rpc_estimate_gas.py index 1e241cfd4b..e425230852 100644 --- a/integration/tests/basic/rpc/test_rpc_estimate_gas.py +++ b/integration/tests/basic/rpc/test_rpc_estimate_gas.py @@ -9,17 +9,6 @@ from integration.tests.basic.helpers.errors import Error32000 -@pytest.fixture(scope="class") -def common_contract(web3_client, class_account) -> tp.Any: - contract, receipt = web3_client.deploy_and_get_contract( - contract="common/Common", - version="0.8.12", - contract_name="Common", - account=class_account, - ) - yield contract, receipt - - @allure.feature("JSON-RPC validation") @allure.story("Verify eth_estimateGas RPC call") class TestRpcEstimateGas(BaseMixin): @@ -154,7 +143,7 @@ def test_rpc_estimate_gas_spl(self, erc20_spl): def test_rpc_estimate_gas_contract_get_value(self, common_contract): tx = self.make_contract_tx_object() - instruction_tx = common_contract[0].functions.getText().build_transaction(tx) + instruction_tx = common_contract.functions.getText().build_transaction(tx) tx_receipt = self.web3_client.send_transaction( self.sender_account, instruction_tx ) @@ -169,7 +158,7 @@ def test_rpc_estimate_gas_contract_get_value(self, common_contract): def test_rpc_estimate_gas_contract_set_value(self, common_contract): tx = self.make_contract_tx_object() instruction_tx = ( - common_contract[0].functions.setNumber(100).build_transaction(tx) + common_contract.functions.setNumber(100).build_transaction(tx) ) tx_receipt = self.web3_client.send_transaction( self.sender_account, instruction_tx @@ -188,7 +177,7 @@ def test_rpc_estimate_gas_contract_calls_another_contract(self, common_contract) "0.8.12", contract_name="CommonCaller", account=self.sender_account, - constructor_args=[common_contract[0].address], + constructor_args=[common_contract.address], ) tx = self.make_contract_tx_object() instruction_tx = caller_contract.functions.getNumber().build_transaction(tx) diff --git a/integration/tests/basic/test_payment_in_different_tokens.py b/integration/tests/basic/test_payment_in_different_tokens.py index 7ea3856618..4df79ee392 100644 --- a/integration/tests/basic/test_payment_in_different_tokens.py +++ b/integration/tests/basic/test_payment_in_different_tokens.py @@ -1,67 +1,76 @@ +import random + import allure import pytest from integration.tests.basic.helpers.basic import BaseMixin +from utils.consts import LAMPORT_PER_SOL @allure.feature("Multiply token") @allure.story("Payments in sol tokens") class TestSolChain(BaseMixin): - @pytest.fixture(scope="class") - def bob(self, class_account): - return class_account + def bob(self, class_account_sol_chain): + return class_account_sol_chain @pytest.fixture(scope="class") - def alice(self, web3_client, web3_client_sol, faucet, eth_bank_account): - return web3_client_sol.create_account() + def alice(self, sol_client, web3_client, web3_client_sol, + faucet, eth_bank_account, solana_account, pytestconfig): + account = web3_client.create_account_with_balance(faucet, bank_account=eth_bank_account) + sol_client.request_airdrop(solana_account.public_key, 1 * LAMPORT_PER_SOL) + sol_client.deposit_wrapped_sol_from_solana_to_neon(solana_account, + account, + web3_client_sol.eth.chain_id, + pytestconfig.environment.evm_loader, + 1 * LAMPORT_PER_SOL) + return account @pytest.fixture(scope="function") def check_neon_balance_does_not_changed(self, alice, bob, web3_client): - alica_balance_alica_before = web3_client.get_balance(alice) - bob_balance_alica_before = web3_client.get_balance(bob) + alice_balance_before = web3_client.get_balance(alice) + bob_balance_before = web3_client.get_balance(bob) yield - alica_balance_alica_after = web3_client.get_balance(alice) - bob_balance_alica_after = web3_client.get_balance(bob) - assert alica_balance_alica_after == alica_balance_alica_before - assert bob_balance_alica_after == bob_balance_alica_before + alice_balance_after = web3_client.get_balance(alice) + bob_balance_after = web3_client.get_balance(bob) + assert alice_balance_after == alice_balance_before + assert bob_balance_after == bob_balance_before def test_user_to_user_trx(self, web3_client_sol, alice, bob, check_neon_balance_does_not_changed): bob_sol_balance_before = web3_client_sol.get_balance(bob) alice_sol_balance_before = web3_client_sol.get_balance(alice) - value = 10 - transaction = self.create_tx_object(bob.address, alice.address, value, web3_client=web3_client_sol) - receipt = web3_client_sol.send_transaction(bob, transaction) + value = 1000 + receipt = web3_client_sol.send_tokens(bob, alice, value) assert receipt["status"] == 1 bob_sol_balance_after = web3_client_sol.get_balance(bob) alice_sol_balance_after = web3_client_sol.get_balance(alice) - assert alice_sol_balance_after == alice_sol_balance_before + web3_client_sol.to_main_currency(value) - assert bob_sol_balance_after < bob_sol_balance_before - web3_client_sol.to_main_currency(value) + assert alice_sol_balance_after == alice_sol_balance_before + value + assert bob_sol_balance_after < bob_sol_balance_before - value def test_user_to_contract_and_contract_to_user_trx(self, web3_client_sol, bob, check_neon_balance_does_not_changed, - wsol_contract): + wsol): bob_sol_balance_before = web3_client_sol.get_balance(bob) - contract_sol_balance_initial = web3_client_sol.get_balance(wsol_contract.address) + contract_sol_balance_initial = web3_client_sol.get_balance(wsol.address) amount = 10 tx = self.make_contract_tx_object(bob.address, amount=amount, web3_client=web3_client_sol) - instruction_tx = wsol_contract.functions.deposit().build_transaction(tx) + instruction_tx = wsol.functions.deposit().build_transaction(tx) receipt = self.web3_client_sol.send_transaction(bob, instruction_tx) assert receipt["status"] == 1 bob_sol_balance_after_deposit = web3_client_sol.get_balance(bob) - contract_sol_balance_after_deposit = web3_client_sol.get_balance(wsol_contract.address) + contract_sol_balance_after_deposit = web3_client_sol.get_balance(wsol.address) assert contract_sol_balance_after_deposit == contract_sol_balance_initial + web3_client_sol.to_main_currency( amount) assert bob_sol_balance_after_deposit < bob_sol_balance_before - web3_client_sol.to_main_currency(amount) tx = self.make_contract_tx_object(bob.address, web3_client=web3_client_sol) - instruction_tx = wsol_contract.functions.withdraw( + instruction_tx = wsol.functions.withdraw( web3_client_sol.to_main_currency(10)).build_transaction(tx) receipt = self.web3_client_sol.send_transaction(bob, instruction_tx) assert receipt["status"] == 1 bob_sol_balance_after_withdraw = web3_client_sol.get_balance(bob) - contract_sol_balance_after_withdraw = web3_client_sol.get_balance(wsol_contract.address) + contract_sol_balance_after_withdraw = web3_client_sol.get_balance(wsol.address) assert contract_sol_balance_after_withdraw == contract_sol_balance_initial assert bob_sol_balance_after_withdraw < bob_sol_balance_after_deposit + \ web3_client_sol.to_main_currency(amount) @@ -96,32 +105,33 @@ def test_user_to_contract_wrong_chain_id_trx(self, web3_client_sol, bob, with pytest.raises(ValueError, match="wrong chain id"): self.web3_client_sol.send_transaction(bob, instruction_tx) - def test_deploy_contract(self, web3_client_sol, bob, + def test_deploy_contract(self, web3_client_sol, alice, check_neon_balance_does_not_changed): - sol_balance_before = web3_client_sol.get_balance(bob) + sol_balance_before = web3_client_sol.get_balance(alice) contract, _ = web3_client_sol.deploy_and_get_contract( contract="common/Common", version="0.8.12", - contract_name="Common", account=bob, + contract_name="Common", account=alice, ) - sol_balance_after = web3_client_sol.get_balance(bob) + sol_balance_after = web3_client_sol.get_balance(alice) assert sol_balance_after < sol_balance_before - def test_deploy_contract_with_sending_tokens(self, web3_client_sol, bob, + def test_deploy_contract_with_sending_tokens(self, web3_client_sol, alice, check_neon_balance_does_not_changed): - sol_bob_balance_before = web3_client_sol.get_balance(bob) + sol_alice_balance_before = web3_client_sol.get_balance(alice) value = 1000 contract, receipt = web3_client_sol.deploy_and_get_contract( contract="common/WNativeChainToken", version="0.8.12", - contract_name="WNativeChainToken", account=bob, value=value + contract_name="WNativeChainToken", account=alice, value=value ) assert receipt["status"] == 1 - sol_bob_balance_after = web3_client_sol.get_balance(bob) + sol_alice_balance_after = web3_client_sol.get_balance(alice) contract_balance = web3_client_sol.get_balance(contract.address) assert contract_balance == value - assert sol_bob_balance_after < sol_bob_balance_before - value + assert sol_alice_balance_after < sol_alice_balance_before - value - def test_deploy_contract_by_one_user_to_different_chain(self, web3_client_sol, new_account, alice, - web3_client): + def test_deploy_contract_by_one_user_to_different_chain(self, web3_client_sol, new_account, + solana_account, alice, + web3_client, pytestconfig): def deploy_contract(w3_client): _, rcpt = w3_client.deploy_and_get_contract( contract="common/Common", version="0.8.12", @@ -129,6 +139,11 @@ def deploy_contract(w3_client): ) return rcpt + self.sol_client.deposit_wrapped_sol_from_solana_to_neon(solana_account, + new_account, + web3_client_sol.eth.chain_id, + pytestconfig.environment.evm_loader) + deploy_contract(web3_client_sol) with pytest.raises(ValueError, match="EVM Error. Attempt to deploy to existing account"): @@ -139,3 +154,70 @@ def deploy_contract(w3_client): receipt = deploy_contract(web3_client) assert receipt["status"] == 1 + print(web3_client_sol.get_nonce(new_account)) + print(web3_client.get_nonce(new_account)) + + # web3_client_sol.send_tokens(new_account, alice, value=10) + + def test_interact_with_contract_from_another_chain(self, web3_client_sol, bob, + check_neon_balance_does_not_changed, + common_contract): + tx = self.make_contract_tx_object(bob.address, web3_client=web3_client_sol) + common_contract_sol_chain = web3_client_sol.get_deployed_contract( + common_contract.address, "common/Common") + number = random.randint(0, 1000000) + instruction_tx = common_contract_sol_chain.functions.setNumber(number).build_transaction(tx) + + self.web3_client_sol.send_transaction(bob, instruction_tx) + assert common_contract_sol_chain.functions.getNumber().call() == number + assert common_contract.functions.getNumber().call() == number + + def test_transfer_neons_in_sol_chain(self, web3_client_sol, web3_client, bob, alice, wneon): + amount = 12 + value = self.web3_client._web3.to_wei(amount, "ether") + + tx = self.make_contract_tx_object(bob.address, amount=amount) + + instruction_tx = wneon.functions.deposit().build_transaction(tx) + self.web3_client.send_transaction(bob, instruction_tx) + + wneon_sol_chain = web3_client_sol.get_deployed_contract( + wneon.address, "common/WNeon", "WNEON", "0.4.26") + + tx = self.make_contract_tx_object(bob.address, web3_client=web3_client_sol) + neon_balance_before = web3_client.get_balance(alice.address) + instruction_tx = wneon_sol_chain.functions.transfer(alice.address, value).build_transaction(tx) + receipt = self.web3_client_sol.send_transaction(bob, instruction_tx) + assert receipt["status"] == 1 + + tx = self.make_contract_tx_object(alice.address, web3_client=web3_client_sol) + instruction_tx = wneon_sol_chain.functions.withdraw(value).build_transaction(tx) + receipt = self.web3_client_sol.send_transaction(alice, instruction_tx) + assert receipt["status"] == 1 + + assert web3_client.get_balance(alice.address) == neon_balance_before + amount + + def test_transfer_sol_in_neon_chain(self, web3_client_sol, web3_client, bob, alice, wsol): + amount = 12 + value = web3_client_sol.to_main_currency(amount) + + tx = self.make_contract_tx_object(bob.address, amount=amount, web3_client=web3_client_sol) + + instruction_tx = wsol.functions.deposit().build_transaction(tx) + self.web3_client_sol.send_transaction(bob, instruction_tx) + + wsol_neon_chain = web3_client.get_deployed_contract( + wsol.address, "common/WNativeChainToken") + + tx = self.make_contract_tx_object(bob.address, web3_client=web3_client) + sol_balance_before = web3_client_sol.get_balance(alice.address) + instruction_tx = wsol_neon_chain.functions.transfer(alice.address, value).build_transaction(tx) + receipt = self.web3_client.send_transaction(bob, instruction_tx) + assert receipt["status"] == 1 + + tx = self.make_contract_tx_object(alice.address, web3_client=web3_client) + instruction_tx = wsol_neon_chain.functions.withdraw(value).build_transaction(tx) + receipt = self.web3_client.send_transaction(alice, instruction_tx) + assert receipt["status"] == 1 + + assert web3_client_sol.get_balance(alice.address) == sol_balance_before + value diff --git a/integration/tests/basic/test_wneon.py b/integration/tests/basic/test_wneon.py index 3a4efb7058..452f411ff0 100644 --- a/integration/tests/basic/test_wneon.py +++ b/integration/tests/basic/test_wneon.py @@ -18,14 +18,6 @@ from utils.helpers import wait_condition -@pytest.fixture(scope="class") -def wneon(web3_client, faucet, class_account): - contract, _ = web3_client.deploy_and_get_contract( - "common/WNeon", "0.4.26", account=class_account, contract_name="WNEON" - ) - return contract - - @allure.feature("Ethereum compatibility") @allure.story("Wrapped NEON tests") class TestWNeon(BaseMixin): diff --git a/integration/tests/conftest.py b/integration/tests/conftest.py index 5f3841e66b..b71e391300 100644 --- a/integration/tests/conftest.py +++ b/integration/tests/conftest.py @@ -89,21 +89,21 @@ def prepare_account(operator, faucet, web3_client: NeonChainWeb3Client): with allure.step("Create account for tests"): acc = web3_client.eth.account.create() with allure.step( - f"Request {NEON_AIRDROP_AMOUNT} NEON from faucet for {acc.address}" + f"Request {NEON_AIRDROP_AMOUNT} NEON from faucet for {acc.address}" ): faucet.request_neon(acc.address, NEON_AIRDROP_AMOUNT) assert web3_client.get_balance(acc) == NEON_AIRDROP_AMOUNT start_neon_balance = operator.get_neon_balance() start_sol_balance = operator.get_solana_balance() with allure.step( - f"Operator initial balance: {start_neon_balance / LAMPORT_PER_SOL} NEON {start_sol_balance / LAMPORT_PER_SOL} SOL" + f"Operator initial balance: {start_neon_balance / LAMPORT_PER_SOL} NEON {start_sol_balance / LAMPORT_PER_SOL} SOL" ): pass yield acc end_neon_balance = operator.get_neon_balance() end_sol_balance = operator.get_solana_balance() with allure.step( - f"Operator end balance: {end_neon_balance / LAMPORT_PER_SOL} NEON {end_sol_balance / LAMPORT_PER_SOL} SOL" + f"Operator end balance: {end_neon_balance / LAMPORT_PER_SOL} NEON {end_sol_balance / LAMPORT_PER_SOL} SOL" ): pass with allure.step(f"Account end balance: {web3_client.get_balance(acc)} NEON"): @@ -152,11 +152,11 @@ def solana_account(bank_account, pytestconfig: Config, sol_client): @pytest.fixture(scope="session") def erc20_spl( - web3_client: NeonChainWeb3Client, - faucet, - pytestconfig: Config, - sol_client, - solana_account, + web3_client: NeonChainWeb3Client, + faucet, + pytestconfig: Config, + sol_client, + solana_account, ): symbol = "".join([random.choice(string.ascii_uppercase) for _ in range(3)]) erc20 = ERC20Wrapper( @@ -195,7 +195,7 @@ def erc20_simple(web3_client, faucet): @pytest.fixture(scope="session") def erc20_spl_mintable( - web3_client: NeonChainWeb3Client, faucet, sol_client, solana_account + web3_client: NeonChainWeb3Client, faucet, sol_client, solana_account ): symbol = "".join([random.choice(string.ascii_uppercase) for _ in range(3)]) erc20 = ERC20Wrapper( @@ -215,25 +215,26 @@ def erc20_spl_mintable( def new_account(web3_client, sol_client, faucet, eth_bank_account, web3_client_sol, pytestconfig, solana_account): account = web3_client.create_account_with_balance(faucet, bank_account=eth_bank_account) - sol_client.deposit_wrapped_sol_from_solana_to_neon(solana_account, - account, - web3_client_sol.eth.chain_id, - pytestconfig.environment.evm_loader) yield account @pytest.fixture(scope="class") def class_account(web3_client, faucet, eth_bank_account, solana_account, sol_client, web3_client_sol, pytestconfig): account = web3_client.create_account_with_balance(faucet, bank_account=eth_bank_account) - sol_client.request_airdrop(solana_account.public_key, 5 * LAMPORT_PER_SOL) + yield account + + +@pytest.fixture(scope="class") +def class_account_sol_chain(sol_client, solana_account, web3_client, web3_client_sol, pytestconfig, faucet, + eth_bank_account): + account = web3_client.create_account_with_balance(faucet, bank_account=eth_bank_account) + sol_client.request_airdrop(solana_account.public_key, 1 * LAMPORT_PER_SOL) sol_client.deposit_wrapped_sol_from_solana_to_neon(solana_account, account, web3_client_sol.eth.chain_id, pytestconfig.environment.evm_loader, - 5 * LAMPORT_PER_SOL) - - - yield account + 1 * LAMPORT_PER_SOL) + return account @pytest.fixture(scope="function") @@ -256,6 +257,17 @@ def withdraw_contract(web3_client, faucet, class_account): return contract +@pytest.fixture(scope="class") +def common_contract(web3_client, class_account): + contract, _ = web3_client.deploy_and_get_contract( + contract="common/Common", + version="0.8.12", + contract_name="Common", + account=class_account, + ) + yield contract + + @pytest.fixture(scope="class") def meta_proxy_contract(web3_client, faucet, class_account): contract, _ = web3_client.deploy_and_get_contract( @@ -273,9 +285,17 @@ def event_caller_contract(web3_client, class_account) -> typing.Any: @pytest.fixture(scope="class") -def wsol_contract(web3_client_sol, class_account): - wsol, _ = web3_client_sol.deploy_and_get_contract( +def wsol(web3_client_sol, class_account_sol_chain): + contract, _ = web3_client_sol.deploy_and_get_contract( contract="common/WNativeChainToken", version="0.8.12", - contract_name="WNativeChainToken", account=class_account, + contract_name="WNativeChainToken", account=class_account_sol_chain, + ) + return contract + + +@pytest.fixture(scope="class") +def wneon(web3_client, faucet, class_account): + contract, _ = web3_client.deploy_and_get_contract( + "common/WNeon", "0.4.26", account=class_account, contract_name="WNEON" ) - return wsol \ No newline at end of file + return contract diff --git a/utils/erc20wrapper.py b/utils/erc20wrapper.py index 7a21cf91f0..8fe07c5d2c 100644 --- a/utils/erc20wrapper.py +++ b/utils/erc20wrapper.py @@ -40,7 +40,9 @@ def __init__( self.decimals = decimals self.sol_client = sol_client self.contract_address = self.deploy_wrapper(mintable) - self.contract = self.get_wrapper_contract() + self.contract = self.web3_client.get_deployed_contract(self.contract_address, + "EIPs/ERC20/IERC20ForSpl") + def make_tx_object(self, from_address, gas_price=None, gas=None): tx = { @@ -90,18 +92,6 @@ def deploy_wrapper(self, mintable: bool): return logs[0]["args"]["pair"] return instruction_receipt - def get_wrapper_contract(self): - contract_path = (pathlib.Path.cwd() / "contracts" / "EIPs" / "ERC20" / "IERC20ForSpl.sol").absolute() - - with open(contract_path, "r") as s: - source = s.read() - - compiled = solcx.compile_source(source, output_values=["abi", "bin"], solc_version="0.8.10") - contract_interface = compiled[list(compiled.keys())[0]] - - contract = self.web3_client.eth.contract(address=self.contract_address, abi=contract_interface["abi"]) - return contract - # TODO: In all this methods verify if exist self.account def mint_tokens(self, signer, to_address, amount: int = INIT_TOKEN_AMOUNT, gas_price=None, gas=None): tx = self.make_tx_object(signer.address, gas_price, gas) diff --git a/utils/web3client.py b/utils/web3client.py index b5632bc35b..ce87152fa5 100644 --- a/utils/web3client.py +++ b/utils/web3client.py @@ -4,6 +4,7 @@ import typing as tp from decimal import Decimal +import solcx import web3 import web3.types import requests @@ -229,6 +230,38 @@ def get_balance( address = address.address return self._web3.eth.get_balance(address, "pending") + def get_deployed_contract(self, address, contract_file, contract_name=None, solc_version = "0.8.12"): + contract_interface = helpers.get_contract_interface(contract_file, solc_version, contract_name) + contract = self.eth.contract(address=address, abi=contract_interface["abi"]) + return contract + + def send_tokens( + self, + from_: eth_account.signers.local.LocalAccount, + to: tp.Union[str, eth_account.signers.local.LocalAccount], + value: int, + gas: tp.Optional[int] = 0, + gas_price: tp.Optional[int] = None, + nonce: int = None, + ) -> web3.types.TxReceipt: + to_addr = to if isinstance(to, str) else to.address + if nonce is None: + nonce = self.get_nonce(from_) + transaction = { + "from": from_.address, + "to": to_addr, + "value": value, + "gasPrice": gas_price or self.gas_price(), + "gas": gas, + "nonce": nonce, + "chainId": self.eth.chain_id + } + if transaction["gas"] == 0: + transaction["gas"] = self.eth.estimate_gas(transaction) + signed_tx = self.eth.account.sign_transaction(transaction, from_.key) + tx = self.eth.send_raw_transaction(signed_tx.rawTransaction) + return self.eth.wait_for_transaction_receipt(tx) + class NeonChainWeb3Client(Web3Client): def __init__(self, proxy_url: str, tracer_url: tp.Optional[tp.Any] = None, session: tp.Optional[tp.Any] = None): @@ -271,23 +304,8 @@ def send_neon( gas_price: tp.Optional[int] = None, nonce: int = None, ) -> web3.types.TxReceipt: - to_addr = to if isinstance(to, str) else to.address - if nonce is None: - nonce = self.get_nonce(from_) - transaction = { - "from": from_.address, - "to": to_addr, - "value": web3.Web3.to_wei(amount, "ether"), - "gasPrice": gas_price or self.gas_price(), - "gas": gas, - "nonce": nonce, - } - if transaction["gas"] == 0: - transaction["gas"] = self._web3.eth.estimate_gas(transaction) - - signed_tx = self._web3.eth.account.sign_transaction(transaction, from_.key) - tx = self._web3.eth.send_raw_transaction(signed_tx.rawTransaction) - return self._web3.eth.wait_for_transaction_receipt(tx) + value = web3.Web3.to_wei(amount, "ether") + return self.send_tokens(from_, to, value,gas, gas_price, nonce) def get_balance( self, address: tp.Union[str, eth_account.signers.local.LocalAccount] From b0734f4bd4da8f86383d1cc67a40dc0eb8fa556a Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Fri, 3 Nov 2023 19:38:57 +0100 Subject: [PATCH 11/56] tests to check balance on different chains --- contracts/opcodes/ChainId.sol | 15 --- contracts/opcodes/ChainIdDependentOpCodes.sol | 36 ++++++ .../test_chain_id_dependent_opcodes.py | 110 ++++++++++++++++++ .../tests/basic/evm/opcodes/test_chainid.py | 25 ---- 4 files changed, 146 insertions(+), 40 deletions(-) delete mode 100644 contracts/opcodes/ChainId.sol create mode 100644 contracts/opcodes/ChainIdDependentOpCodes.sol create mode 100644 integration/tests/basic/evm/opcodes/test_chain_id_dependent_opcodes.py delete mode 100644 integration/tests/basic/evm/opcodes/test_chainid.py diff --git a/contracts/opcodes/ChainId.sol b/contracts/opcodes/ChainId.sol deleted file mode 100644 index 18a383326f..0000000000 --- a/contracts/opcodes/ChainId.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.10; - -contract ChainId { - event Added(bytes32 hash); - - function getCurrentValues() public view returns (uint256) { - uint256 id; - assembly { - id := chainid() - } - return id; - } - -} \ No newline at end of file diff --git a/contracts/opcodes/ChainIdDependentOpCodes.sol b/contracts/opcodes/ChainIdDependentOpCodes.sol new file mode 100644 index 0000000000..aca2bfe3c3 --- /dev/null +++ b/contracts/opcodes/ChainIdDependentOpCodes.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.10; + +contract ChainIdDependentOpCodes { + + function getChainId() public view returns (uint256) { + uint256 id; + assembly { + id := chainid() + } + return id; + } + + function getBalance(address _addr) public view returns (uint) { + return _addr.balance; + } +} + +contract ChainIdDependentOpCodesCaller { + + ChainIdDependentOpCodes public chainIdDependentOpCodes; + + constructor() { + } + + function getChainId(address _addr) public view returns (uint) { + ChainIdDependentOpCodes my = ChainIdDependentOpCodes(_addr); + return my.getChainId(); + } + + function getBalance(address _addr, address account) public view returns (uint) { + ChainIdDependentOpCodes my = ChainIdDependentOpCodes(_addr); + + return my.getBalance(account); + } +} \ No newline at end of file diff --git a/integration/tests/basic/evm/opcodes/test_chain_id_dependent_opcodes.py b/integration/tests/basic/evm/opcodes/test_chain_id_dependent_opcodes.py new file mode 100644 index 0000000000..e197b8ba91 --- /dev/null +++ b/integration/tests/basic/evm/opcodes/test_chain_id_dependent_opcodes.py @@ -0,0 +1,110 @@ +import pytest + +from integration.tests.basic.helpers.basic import BaseMixin + + +class TestChainIdDependentOpcodes(BaseMixin): + @pytest.fixture(scope="class") + def contract_neon(self, web3_client, class_account): + contract, _ = web3_client.deploy_and_get_contract( + "opcodes/ChainIdDependentOpCodes", "0.8.10", class_account) + return contract + + @pytest.fixture(scope="class") + def contract_neon_caller(self, web3_client_sol, class_account): + contract, _ = web3_client_sol.deploy_and_get_contract( + "opcodes/ChainIdDependentOpCodes", "0.8.10", class_account, + contract_name='ChainIdDependentOpCodesCaller', + constructor_args=[class_account.address], + ) + return contract + + @pytest.fixture(scope="class") + def contract_sol(self, web3_client_sol, class_account_sol_chain): + contract, _ = web3_client_sol.deploy_and_get_contract( + "opcodes/ChainIdDependentOpCodes", "0.8.10", class_account_sol_chain) + return contract + + @pytest.fixture(scope="class") + def contract_caller_sol(self, web3_client_sol, class_account_sol_chain): + contract, _ = web3_client_sol.deploy_and_get_contract( + "opcodes/ChainIdDependentOpCodes", "0.8.10", class_account_sol_chain, + contract_name='ChainIdDependentOpCodesCaller', + ) + return contract + + @pytest.fixture(scope="class") + def contract_caller_neon(self, web3_client, class_account): + contract, _ = web3_client.deploy_and_get_contract( + "opcodes/ChainIdDependentOpCodes", "0.8.10", class_account, + contract_name='ChainIdDependentOpCodesCaller', + ) + return contract + + def test_chain_id_sol(self, contract_sol, pytestconfig): + assert contract_sol.functions.getChainId().call() \ + == pytestconfig.environment.network_ids["sol"] + + def test_chain_id_neon(self, contract_neon, pytestconfig): + assert contract_neon.functions.getChainId().call() \ + == pytestconfig.environment.network_ids["neon"] + + def test_balance_by_sol_contract(self, contract_sol, class_account_sol_chain, web3_client, + web3_client_sol, contract_caller_sol): + # user calls sol contract in sol chain == sol balance + # user calls sol contract in neon chain == neon balance + # sol contract calls sol contract in sol chain == sol balance + # sol contract calls sol contract in neon chain == sol balance + + expected_balance_sol = web3_client_sol.get_balance(class_account_sol_chain.address) + expected_balance_neon = self.web3_client.to_main_currency( + web3_client.get_balance(class_account_sol_chain.address)) + balance = contract_sol.functions.getBalance(class_account_sol_chain.address).call() + + assert balance == expected_balance_sol + + contract_sol_in_neon_network = web3_client.get_deployed_contract(contract_sol.address, + "opcodes/ChainIdDependentOpCodes") + balance = contract_sol_in_neon_network.functions.getBalance(class_account_sol_chain.address).call() + assert balance == expected_balance_neon + + balance = contract_caller_sol.functions.getBalance(contract_sol.address, class_account_sol_chain.address).call() + assert balance == expected_balance_sol + + caller_in_neon_network = web3_client.get_deployed_contract(contract_caller_sol.address, + 'opcodes/ChainIdDependentOpCodes', + 'ChainIdDependentOpCodesCaller') + + balance = caller_in_neon_network.functions.getBalance(contract_sol.address, + class_account_sol_chain.address).call() + assert balance == expected_balance_sol + + def test_balance_by_neon_contract(self, contract_neon, sol_client, class_account, web3_client, + web3_client_sol, contract_caller_neon): + # user call neon contract in neon chain == neon balance + # user calls neon contract in sol chain == sol balance + # neon contract calls neon contract in neon chain == neon balance + # neon contract calls neon contract in sol chain == neon balance + + expected_balance_sol = web3_client_sol.get_balance(class_account.address) + expected_balance_neon = self.web3_client.to_main_currency( + web3_client.get_balance(class_account.address)) + + balance = contract_neon.functions.getBalance(class_account.address).call() + assert balance == expected_balance_neon + + contract_neon_in_sol_network = web3_client_sol.get_deployed_contract(contract_neon.address, + "opcodes/ChainIdDependentOpCodes") + balance = contract_neon_in_sol_network.functions.getBalance(class_account.address).call() + assert balance == expected_balance_sol + + caller_in_sol_network = web3_client_sol.get_deployed_contract(contract_caller_neon.address, + 'opcodes/ChainIdDependentOpCodes', + 'ChainIdDependentOpCodesCaller') + + balance = caller_in_sol_network.functions.getBalance(contract_neon.address, class_account.address).call() + assert balance == expected_balance_neon + + balance = caller_in_sol_network.functions.getBalance(contract_neon.address, + class_account.address).call() + assert balance == expected_balance_neon diff --git a/integration/tests/basic/evm/opcodes/test_chainid.py b/integration/tests/basic/evm/opcodes/test_chainid.py deleted file mode 100644 index 51f474022a..0000000000 --- a/integration/tests/basic/evm/opcodes/test_chainid.py +++ /dev/null @@ -1,25 +0,0 @@ -import pytest - -from integration.tests.basic.helpers.basic import BaseMixin - - -class TestChainId(BaseMixin): - @pytest.fixture(scope="class") - def contract_neon(self, web3_client, class_account): - contract, _ = web3_client.deploy_and_get_contract( - "opcodes/ChainId", "0.8.10", class_account) - return contract - - @pytest.fixture(scope="class") - def contract_sol(self, web3_client_sol, class_account_sol_chain): - contract, _ = web3_client_sol.deploy_and_get_contract( - "opcodes/ChainId", "0.8.10", class_account_sol_chain) - return contract - - def test_chain_id_sol(self, contract_sol, pytestconfig): - assert contract_sol.functions.getCurrentValues().call() \ - == pytestconfig.environment.network_ids["sol"] - - def test_chain_id_neon(self, contract_neon, pytestconfig): - assert contract_neon.functions.getCurrentValues().call() \ - == pytestconfig.environment.network_ids["neon"] From 649c10d8c51163923f06d6b8de0a12aa5c3f090d Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Mon, 6 Nov 2023 18:17:22 +0100 Subject: [PATCH 12/56] added tests and pytest tag for multiple chain tests --- contracts/common/Common.sol | 9 ++++ .../test_chain_id_dependent_opcodes.py | 3 ++ integration/tests/basic/test_deposit.py | 1 + integration/tests/basic/test_nonce.py | 25 ++++++++-- .../basic/test_payment_in_different_tokens.py | 49 +++++++++++++++++-- integration/tests/conftest.py | 19 +++++-- utils/solana_client.py | 1 + 7 files changed, 95 insertions(+), 12 deletions(-) diff --git a/contracts/common/Common.sol b/contracts/common/Common.sol index 987d93ead9..cc940dc860 100644 --- a/contracts/common/Common.sol +++ b/contracts/common/Common.sol @@ -34,4 +34,13 @@ contract CommonCaller { function getNumber() public view returns (uint256) { return myCommon.getNumber(); } +} + +contract BunchActions { + + function setNumber(address[] memory addresses, uint256[] memory _numbers) public { + for (uint256 i = 0; i < addresses.length; ++i) { + Common(addresses[i]).setNumber(_numbers[i]); + } + } } \ No newline at end of file diff --git a/integration/tests/basic/evm/opcodes/test_chain_id_dependent_opcodes.py b/integration/tests/basic/evm/opcodes/test_chain_id_dependent_opcodes.py index e197b8ba91..bd474811bb 100644 --- a/integration/tests/basic/evm/opcodes/test_chain_id_dependent_opcodes.py +++ b/integration/tests/basic/evm/opcodes/test_chain_id_dependent_opcodes.py @@ -41,6 +41,7 @@ def contract_caller_neon(self, web3_client, class_account): ) return contract + @pytest.mark.multipletokens def test_chain_id_sol(self, contract_sol, pytestconfig): assert contract_sol.functions.getChainId().call() \ == pytestconfig.environment.network_ids["sol"] @@ -49,6 +50,7 @@ def test_chain_id_neon(self, contract_neon, pytestconfig): assert contract_neon.functions.getChainId().call() \ == pytestconfig.environment.network_ids["neon"] + @pytest.mark.multipletokens def test_balance_by_sol_contract(self, contract_sol, class_account_sol_chain, web3_client, web3_client_sol, contract_caller_sol): # user calls sol contract in sol chain == sol balance @@ -79,6 +81,7 @@ def test_balance_by_sol_contract(self, contract_sol, class_account_sol_chain, we class_account_sol_chain.address).call() assert balance == expected_balance_sol + @pytest.mark.multipletokens def test_balance_by_neon_contract(self, contract_neon, sol_client, class_account, web3_client, web3_client_sol, contract_caller_neon): # user call neon contract in neon chain == neon balance diff --git a/integration/tests/basic/test_deposit.py b/integration/tests/basic/test_deposit.py index 49284e7801..604414d9f3 100644 --- a/integration/tests/basic/test_deposit.py +++ b/integration/tests/basic/test_deposit.py @@ -114,6 +114,7 @@ def test_transfer_spl_token_from_solana_to_neon( == int(ata_balance_before.value.amount) + full_amount ) + @pytest.mark.multipletokens def test_transfer_wrapped_sol_token_from_solana_to_neon( self, solana_account, pytestconfig: Config, web3_client_sol ): diff --git a/integration/tests/basic/test_nonce.py b/integration/tests/basic/test_nonce.py index 498f19339c..38737f1222 100644 --- a/integration/tests/basic/test_nonce.py +++ b/integration/tests/basic/test_nonce.py @@ -2,10 +2,12 @@ import time import allure +import pytest from integration.tests.basic.helpers import rpc_checks from integration.tests.basic.helpers.assert_message import ErrorMessage from integration.tests.basic.helpers.basic import BaseMixin +from utils.consts import LAMPORT_PER_SOL @allure.feature("Ethereum compatibility") @@ -89,7 +91,7 @@ def test_send_transaction_with_low_nonce_after_several_high(self): def test_send_transaction_with_low_nonce_after_high(self): """Check that transaction with a higher nonce is waiting for its turn in the mempool""" nonce = ( - self.web3_client.eth.get_transaction_count(self.sender_account.address) + 1 + self.web3_client.eth.get_transaction_count(self.sender_account.address) + 1 ) transaction = self.create_tx_object(nonce=nonce) signed_tx = self.web3_client.eth.account.sign_transaction( @@ -121,7 +123,7 @@ def test_send_transaction_with_low_nonce_after_high(self): def test_send_transaction_with_the_same_nonce_and_lower_gas(self): """Check that transaction with a low gas and the same nonce can't be sent""" nonce = ( - self.web3_client.eth.get_transaction_count(self.sender_account.address) + 1 + self.web3_client.eth.get_transaction_count(self.sender_account.address) + 1 ) gas = self.web3_client.gas_price() transaction = self.create_tx_object(nonce=nonce, gas_price=gas) @@ -140,14 +142,14 @@ def test_send_transaction_with_the_same_nonce_and_lower_gas(self): ) assert "error" in response, f"Response doesn't has an error: {response}" assert ( - ErrorMessage.REPLACEMENT_UNDERPRICED.value in response["error"]["message"] + ErrorMessage.REPLACEMENT_UNDERPRICED.value in response["error"]["message"] ) assert response["error"]["code"] == -32000 def test_send_transaction_with_the_same_nonce_and_higher_gas(self): """Check that transaction with higher gas and the same nonce can be sent""" nonce = ( - self.web3_client.eth.get_transaction_count(self.sender_account.address) + 1 + self.web3_client.eth.get_transaction_count(self.sender_account.address) + 1 ) gas = self.web3_client.gas_price() transaction = self.create_tx_object(nonce=nonce, gas_price=gas) @@ -218,3 +220,18 @@ def test_send_transaction_with_old_nonce(self): ) assert ErrorMessage.NONCE_TOO_LOW.value in response["error"]["message"] assert response["error"]["code"] == -32002 + + @pytest.mark.multipletokens + def test_nonce_with_several_chains(self, class_account_sol_chain, + web3_client_sol, web3_client, faucet): + sender = class_account_sol_chain + self.faucet.request_neon(sender.address, 100) + neon_chain_nonce = self.web3_client.get_nonce(sender.address) + sol_chain_nonce = self.web3_client_sol.get_nonce(sender.address) + transaction_order_list = ['sol', 'neon', 'sol', 'sol', 'sol', 'neon'] + + for item in transaction_order_list: + client = web3_client_sol if item == 'sol' else web3_client + client.send_tokens(sender, self.recipient_account, 1000) + assert self.web3_client.get_nonce(sender.address) == neon_chain_nonce + 2 + assert self.web3_client_sol.get_nonce(sender.address) == sol_chain_nonce + 4 diff --git a/integration/tests/basic/test_payment_in_different_tokens.py b/integration/tests/basic/test_payment_in_different_tokens.py index 4df79ee392..4dc7ccfd34 100644 --- a/integration/tests/basic/test_payment_in_different_tokens.py +++ b/integration/tests/basic/test_payment_in_different_tokens.py @@ -36,6 +36,7 @@ def check_neon_balance_does_not_changed(self, alice, bob, web3_client): assert alice_balance_after == alice_balance_before assert bob_balance_after == bob_balance_before + @pytest.mark.multipletokens def test_user_to_user_trx(self, web3_client_sol, alice, bob, check_neon_balance_does_not_changed): bob_sol_balance_before = web3_client_sol.get_balance(bob) @@ -48,6 +49,7 @@ def test_user_to_user_trx(self, web3_client_sol, alice, bob, assert alice_sol_balance_after == alice_sol_balance_before + value assert bob_sol_balance_after < bob_sol_balance_before - value + @pytest.mark.multipletokens def test_user_to_contract_and_contract_to_user_trx(self, web3_client_sol, bob, check_neon_balance_does_not_changed, wsol): @@ -75,6 +77,7 @@ def test_user_to_contract_and_contract_to_user_trx(self, web3_client_sol, bob, assert bob_sol_balance_after_withdraw < bob_sol_balance_after_deposit + \ web3_client_sol.to_main_currency(amount) + @pytest.mark.multipletokens def test_contract_to_contract_trx(self, web3_client_sol, bob): # contract to new contract amount = 1 @@ -97,6 +100,7 @@ def test_contract_to_contract_trx(self, web3_client_sol, bob): assert web3_client_sol.get_balance(wrapper_address) == value * 2 assert bob_sol_balance_after < bob_sol_balance_before - value * 2 + @pytest.mark.multipletokens def test_user_to_contract_wrong_chain_id_trx(self, web3_client_sol, bob, check_neon_balance_does_not_changed, event_caller_contract): @@ -105,6 +109,7 @@ def test_user_to_contract_wrong_chain_id_trx(self, web3_client_sol, bob, with pytest.raises(ValueError, match="wrong chain id"): self.web3_client_sol.send_transaction(bob, instruction_tx) + @pytest.mark.multipletokens def test_deploy_contract(self, web3_client_sol, alice, check_neon_balance_does_not_changed): sol_balance_before = web3_client_sol.get_balance(alice) @@ -115,6 +120,7 @@ def test_deploy_contract(self, web3_client_sol, alice, sol_balance_after = web3_client_sol.get_balance(alice) assert sol_balance_after < sol_balance_before + @pytest.mark.multipletokens def test_deploy_contract_with_sending_tokens(self, web3_client_sol, alice, check_neon_balance_does_not_changed): sol_alice_balance_before = web3_client_sol.get_balance(alice) @@ -129,6 +135,7 @@ def test_deploy_contract_with_sending_tokens(self, web3_client_sol, alice, assert contract_balance == value assert sol_alice_balance_after < sol_alice_balance_before - value + @pytest.mark.multipletokens def test_deploy_contract_by_one_user_to_different_chain(self, web3_client_sol, new_account, solana_account, alice, web3_client, pytestconfig): @@ -154,11 +161,8 @@ def deploy_contract(w3_client): receipt = deploy_contract(web3_client) assert receipt["status"] == 1 - print(web3_client_sol.get_nonce(new_account)) - print(web3_client.get_nonce(new_account)) - - # web3_client_sol.send_tokens(new_account, alice, value=10) + @pytest.mark.multipletokens def test_interact_with_contract_from_another_chain(self, web3_client_sol, bob, check_neon_balance_does_not_changed, common_contract): @@ -172,6 +176,7 @@ def test_interact_with_contract_from_another_chain(self, web3_client_sol, bob, assert common_contract_sol_chain.functions.getNumber().call() == number assert common_contract.functions.getNumber().call() == number + @pytest.mark.multipletokens def test_transfer_neons_in_sol_chain(self, web3_client_sol, web3_client, bob, alice, wneon): amount = 12 value = self.web3_client._web3.to_wei(amount, "ether") @@ -197,6 +202,7 @@ def test_transfer_neons_in_sol_chain(self, web3_client_sol, web3_client, bob, al assert web3_client.get_balance(alice.address) == neon_balance_before + amount + @pytest.mark.multipletokens def test_transfer_sol_in_neon_chain(self, web3_client_sol, web3_client, bob, alice, wsol): amount = 12 value = web3_client_sol.to_main_currency(amount) @@ -221,3 +227,38 @@ def test_transfer_sol_in_neon_chain(self, web3_client_sol, web3_client, bob, ali assert receipt["status"] == 1 assert web3_client_sol.get_balance(alice.address) == sol_balance_before + value + + @pytest.mark.multipletokens + def test_call_different_chains_contracts_in_one_transaction(self, alice, common_contract, + web3_client_sol, class_account_sol_chain): + bunch_contract_neon, _ = self.web3_client.deploy_and_get_contract( + contract="common/Common", version="0.8.12", + contract_name="BunchActions", account=alice + ) + bunch_contract_sol = web3_client_sol.get_deployed_contract( + bunch_contract_neon.address, "common/Common", contract_name="BunchActions") + common_contract_sol, _ = web3_client_sol.deploy_and_get_contract( + contract="common/Common", + version="0.8.12", + account=class_account_sol_chain, + ) + + common_contract_neon = common_contract + + tx = self.make_contract_tx_object(alice.address) + instruction_tx = bunch_contract_neon.functions.setNumber([common_contract_sol.address, + common_contract_neon.address], + [1, 2]).build_transaction(tx) + receipt = self.web3_client.send_transaction(alice, instruction_tx) + assert receipt["status"] == 1 + assert common_contract_sol.functions.getNumber().call() == 1 + assert common_contract_neon.functions.getNumber().call() == 2 + + tx = self.make_contract_tx_object(alice.address, web3_client=web3_client_sol) + instruction_tx = bunch_contract_sol.functions.setNumber([common_contract_sol.address, + common_contract_neon.address], + [3, 4]).build_transaction(tx) + receipt = self.web3_client_sol.send_transaction(alice, instruction_tx) + assert receipt["status"] == 1 + assert common_contract_sol.functions.getNumber().call() == 3 + assert common_contract_neon.functions.getNumber().call() == 4 diff --git a/integration/tests/conftest.py b/integration/tests/conftest.py index b71e391300..a0a7844fe6 100644 --- a/integration/tests/conftest.py +++ b/integration/tests/conftest.py @@ -2,6 +2,8 @@ import random import typing import string +import pathlib +import json import allure import base58 @@ -27,13 +29,22 @@ def pytest_collection_modifyitems(config, items): deselected_items = [] selected_items = [] - if config.getoption("--network") == "devnet": - deselected_mark = "only_stands" + deselected_marks = [] + network_name = config.getoption("--network") + + if network_name == "devnet": + deselected_marks.append("only_stands") else: - deselected_mark = "only_devnet" + deselected_marks.append("only_devnet") + + envs_file = config.getoption("--envs") + with open(pathlib.Path().parent.parent / envs_file, "r+") as f: + environments = json.load(f) + if len(environments[network_name]["network_ids"]) == 1: + deselected_marks.append("multipletokens") for item in items: - if item.get_closest_marker(deselected_mark): + if any([item.get_closest_marker(mark) for mark in deselected_marks]): deselected_items.append(item) else: selected_items.append(item) diff --git a/utils/solana_client.py b/utils/solana_client.py index 9404920a1b..a4253716ec 100644 --- a/utils/solana_client.py +++ b/utils/solana_client.py @@ -71,6 +71,7 @@ def send_sol(self, from_: Keypair, to: PublicKey, amount_lamports: int): raise AssertionError(f"Balance not changed in account {to}") def ether2balance(self, address: tp.Union[str, bytes], chain_id: int, evm_loader_id: str) -> PublicKey: + # get public key associated with chain_id for an address address_bytes = bytes.fromhex(address[2:]) chain_id_bytes = chain_id.to_bytes(32, 'big') return PublicKey.find_program_address( From 7ffbe32df8d06cb994d6cda5b404881417070949 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Thu, 9 Nov 2023 15:21:42 +0100 Subject: [PATCH 13/56] added creating new token --- evm_loader-keypair.json | 1 + integration/tests/basic/test_deposit.py | 181 +++++++++++------------- integration/tests/conftest.py | 72 ++++------ operator-keypair.json | 1 + utils/solana_client.py | 73 +++------- utils/transfers_inter_networks.py | 15 +- 6 files changed, 144 insertions(+), 199 deletions(-) create mode 100644 evm_loader-keypair.json create mode 100644 operator-keypair.json diff --git a/evm_loader-keypair.json b/evm_loader-keypair.json new file mode 100644 index 0000000000..da4a1d778d --- /dev/null +++ b/evm_loader-keypair.json @@ -0,0 +1 @@ +[127,240,45,117,60,55,248,79,137,128,176,124,177,53,125,213,194,211,20,110,232,87,87,27,194,68,199,200,135,231,13,127,60,0,57,43,120,125,56,168,83,209,36,5,118,52,196,60,113,51,198,18,70,29,116,254,177,127,66,72,21,82,134,192] \ No newline at end of file diff --git a/integration/tests/basic/test_deposit.py b/integration/tests/basic/test_deposit.py index 604414d9f3..74b3cc358c 100644 --- a/integration/tests/basic/test_deposit.py +++ b/integration/tests/basic/test_deposit.py @@ -1,5 +1,9 @@ +import json +import time + import pytest import allure +import solana from _pytest.config import Config from solana.keypair import Keypair from solana.rpc.commitment import Commitment @@ -15,7 +19,7 @@ from integration.tests.basic.helpers.basic import BaseMixin, BaseTests from utils.consts import LAMPORT_PER_SOL, wSOL -from utils.transfers_inter_networks import neon_transfer_tx, wSOL_tx, token_from_solana_to_neon_tx +from utils.transfers_inter_networks import neon_transfer_tx, wSOL_tx, token_from_solana_to_neon_tx, mint_tx from utils.helpers import wait_condition @@ -28,27 +32,25 @@ def withdraw_neon(self, dest_acc, move_amount): ) tx = self.create_contract_call_tx_object(amount=move_amount) - instruction_tx = contract.functions.withdraw( - bytes(dest_acc.public_key)).build_transaction(tx) + instruction_tx = contract.functions.withdraw(bytes(dest_acc.public_key)).build_transaction(tx) receipt = self.web3_client.send_transaction(self.sender_account, instruction_tx) assert receipt["status"] == 1 - def test_transfer_neon_from_solana_to_neon( - self, new_account, solana_account, pytestconfig: Config, neon_mint - ): + def test_transfer_neon_from_solana_to_neon(self, new_account, solana_account, pytestconfig: Config, neon_mint): """Transfer Neon from Solana -> Neon""" - amount = 0.1 - full_amount = int(amount * LAMPORT_PER_SOL) + amount = 1 + full_amount = self.web3_client.to_main_currency(amount) evm_loader_id = pytestconfig.environment.evm_loader - balance_pubkey = self.sol_client.ether2balance(new_account.address, - self.web3_client.eth.chain_id, - evm_loader_id) + balance_pubkey = self.sol_client.ether2balance( + new_account.address, self.web3_client.eth.chain_id, evm_loader_id + ) neon_balance_before = self.get_balance_from_wei(new_account.address) self.sol_client.create_ata(solana_account, neon_mint) - self.withdraw_neon(solana_account, amount) + self.withdraw_neon(solana_account, amount) # insufficient funds + tx = token_from_solana_to_neon_tx( solana_account, balance_pubkey, @@ -56,63 +58,80 @@ def test_transfer_neon_from_solana_to_neon( new_account, full_amount, evm_loader_id, - self.web3_client.eth.chain_id + self.web3_client.eth.chain_id, ) self.sol_client.send_tx_and_check_status_ok(tx, solana_account) neon_balance_after = self.get_balance_from_wei(new_account.address) assert neon_balance_after == neon_balance_before + amount - def test_transfer_spl_token_from_solana_to_neon( - self, solana_account, new_account, pytestconfig: Config, erc20_spl + def test_create_and_transfer_new_token_from_solana_to_neon( + self, new_account, solana_account, pytestconfig: Config, neon_mint, web3_client_abc, operator ): + amount = 100000000 + evm_loader_id = pytestconfig.environment.evm_loader + + balance_pubkey = self.sol_client.ether2balance(new_account.address, web3_client_abc.eth.chain_id, evm_loader_id) + + + with open("operator-keypair.json", "r") as key: + secret_key = json.load(key)[:32] + operator = Keypair.from_secret_key(secret_key) + + with open("evm_loader-keypair.json", "r") as key: + secret_key = json.load(key)[:32] + evm_loader_keypair = Keypair.from_secret_key(secret_key) + + spl_neon_token = SplToken(self.sol_client, neon_mint, TOKEN_PROGRAM_ID, payer=operator) + associated_token_address = spl_neon_token.create_associated_token_account(solana_account.public_key) + + tx = mint_tx(amount=amount, dest=associated_token_address, neon_mint=neon_mint, + mint_authority=evm_loader_keypair.public_key) + tx.fee_payer = operator.public_key + + self.sol_client.send_tx_and_check_status_ok(tx, operator, evm_loader_keypair) + + tx = token_from_solana_to_neon_tx( + solana_account, + balance_pubkey, + neon_mint, + new_account, + amount, + evm_loader_id, + web3_client_abc.eth.chain_id, + ) + self.sol_client.send_tx_and_check_status_ok(tx, solana_account) + + abc_balance_after = web3_client_abc.get_balance(new_account) + assert abc_balance_after == amount * 100000000 + + def test_transfer_spl_token_from_solana_to_neon(self, solana_account, new_account, pytestconfig: Config, erc20_spl): evm_loader_id = pytestconfig.environment.evm_loader amount = 0.1 full_amount = int(amount * LAMPORT_PER_SOL) mint_pubkey = wSOL["address_spl"] - ata_address = get_associated_token_address( - solana_account.public_key, mint_pubkey - ) + ata_address = get_associated_token_address(solana_account.public_key, mint_pubkey) self.sol_client.create_ata(solana_account, mint_pubkey) - spl_neon_token = SplToken( - self.sol_client, mint_pubkey, TOKEN_PROGRAM_ID, solana_account - ) - ata_balance_before = spl_neon_token.get_balance( - ata_address, commitment=Commitment("confirmed") - ) + spl_neon_token = SplToken(self.sol_client, mint_pubkey, TOKEN_PROGRAM_ID, solana_account) + ata_balance_before = spl_neon_token.get_balance(ata_address, commitment=Commitment("confirmed")) # wrap SOL wSOL_account = self.sol_client.get_account_info(ata_address).value - wrap_sol_tx = wSOL_tx(wSOL_account, - wSOL, full_amount, - solana_account.public_key, - ata_address) + wrap_sol_tx = wSOL_tx(wSOL_account, wSOL, full_amount, solana_account.public_key, ata_address) self.sol_client.send_tx_and_check_status_ok(wrap_sol_tx, solana_account) # transfer wSOL transfer_tx = neon_transfer_tx( - self.web3_client, - self.sol_client, - full_amount, - wSOL, - solana_account, - new_account, - erc20_spl, - evm_loader_id + self.web3_client, self.sol_client, full_amount, wSOL, solana_account, new_account, erc20_spl, evm_loader_id ) self.sol_client.send_tx_and_check_status_ok(transfer_tx, solana_account) - ata_balance_after = spl_neon_token.get_balance( - ata_address, commitment=Commitment("confirmed") - ) + ata_balance_after = spl_neon_token.get_balance(ata_address, commitment=Commitment("confirmed")) - assert ( - int(ata_balance_after.value.amount) - == int(ata_balance_before.value.amount) + full_amount - ) + assert int(ata_balance_after.value.amount) == int(ata_balance_before.value.amount) + full_amount @pytest.mark.multipletokens def test_transfer_wrapped_sol_token_from_solana_to_neon( @@ -125,22 +144,16 @@ def test_transfer_wrapped_sol_token_from_solana_to_neon( full_amount = int(amount * LAMPORT_PER_SOL) mint_pubkey = wSOL["address_spl"] - ata_address = get_associated_token_address( - solana_account.public_key, mint_pubkey - ) + ata_address = get_associated_token_address(solana_account.public_key, mint_pubkey) self.sol_client.create_ata(solana_account, mint_pubkey) # wrap SOL wSOL_account = self.sol_client.get_account_info(ata_address).value - wrap_sol_tx = wSOL_tx( - wSOL_account, wSOL, full_amount, solana_account.public_key, ata_address - ) + wrap_sol_tx = wSOL_tx(wSOL_account, wSOL, full_amount, solana_account.public_key, ata_address) self.sol_client.send_tx_and_check_status_ok(wrap_sol_tx, solana_account) - balance_pubkey = self.sol_client.ether2balance(new_account.address, - web3_client_sol.eth.chain_id, - evm_loader_id) + balance_pubkey = self.sol_client.ether2balance(new_account.address, web3_client_sol.eth.chain_id, evm_loader_id) tx = token_from_solana_to_neon_tx( solana_account, @@ -149,7 +162,7 @@ def test_transfer_wrapped_sol_token_from_solana_to_neon( new_account, full_amount, evm_loader_id, - web3_client_sol.eth.chain_id + web3_client_sol.eth.chain_id, ) self.sol_client.send_tx_and_check_status_ok(tx, solana_account) @@ -161,9 +174,7 @@ def test_transfer_wrapped_sol_token_from_solana_to_neon( @allure.story("Withdraw from NEON to Solana") class TestWithdraw(BaseTests): def withdraw(self, dest_acc, move_amount, withdraw_contract): - instruction_tx = withdraw_contract.functions.withdraw( - bytes(dest_acc.public_key) - ).build_transaction( + instruction_tx = withdraw_contract.functions.withdraw(bytes(dest_acc.public_key)).build_transaction( { "from": self.acc.address, "nonce": self.web3_client.eth.get_transaction_count(self.acc.address), @@ -182,29 +193,21 @@ def test_success_withdraw_to_non_existing_account( dest_acc = Keypair.generate() self.sol_client.request_airdrop(dest_acc.public_key, 1_000_000_000) - spl_neon_token = SplToken( - self.sol_client, neon_mint, TOKEN_PROGRAM_ID, dest_acc - ) + spl_neon_token = SplToken(self.sol_client, neon_mint, TOKEN_PROGRAM_ID, dest_acc) dest_token_acc = get_associated_token_address(dest_acc.public_key, neon_mint) move_amount = self.web3_client._web3.to_wei(5, "ether") - destination_balance_before = spl_neon_token.get_balance( - dest_acc.public_key, commitment=Commitment("confirmed") - ) + destination_balance_before = spl_neon_token.get_balance(dest_acc.public_key, commitment=Commitment("confirmed")) with pytest.raises(AttributeError): _ = destination_balance_before.value self.withdraw(dest_acc, move_amount, withdraw_contract) - destination_balance_after = spl_neon_token.get_balance( - dest_token_acc, commitment=Commitment("confirmed") - ) + destination_balance_after = spl_neon_token.get_balance(dest_token_acc, commitment=Commitment("confirmed")) - assert int(destination_balance_after.value.amount) == int( - move_amount / 1_000_000_000 - ) + assert int(destination_balance_after.value.amount) == int(move_amount / 1_000_000_000) def test_success_withdraw_to_existing_account( self, pytestconfig: Config, withdraw_contract, neon_mint, solana_account @@ -215,11 +218,7 @@ def test_success_withdraw_to_existing_account( wait_condition(lambda: self.sol_client.get_balance(dest_acc.public_key) != 0) trx = Transaction() - trx.add( - create_associated_token_account( - dest_acc.public_key, dest_acc.public_key, neon_mint - ) - ) + trx.add(create_associated_token_account(dest_acc.public_key, dest_acc.public_key, neon_mint)) opts = TxOpts(skip_preflight=True, skip_confirmation=False) self.sol_client.send_transaction(trx, dest_acc, opts=opts) @@ -228,20 +227,14 @@ def test_success_withdraw_to_existing_account( move_amount_alan = 2_123_000_321_000_000_000 move_amount_galan = int(move_amount_alan / 1_000_000_000) - spl_neon_token = SplToken( - self.sol_client, neon_mint, TOKEN_PROGRAM_ID, dest_acc - ) + spl_neon_token = SplToken(self.sol_client, neon_mint, TOKEN_PROGRAM_ID, dest_acc) - destination_balance_before = spl_neon_token.get_balance( - dest_token_acc, commitment=Commitment("confirmed") - ) + destination_balance_before = spl_neon_token.get_balance(dest_token_acc, commitment=Commitment("confirmed")) assert int(destination_balance_before.value.amount) == 0 self.withdraw(dest_acc, move_amount_alan, withdraw_contract) - destination_balance_after = spl_neon_token.get_balance( - dest_token_acc, commitment=Commitment("confirmed") - ) + destination_balance_after = spl_neon_token.get_balance(dest_token_acc, commitment=Commitment("confirmed")) assert int(destination_balance_after.value.amount) == move_amount_galan def test_failed_withdraw_non_divisible_amount( @@ -249,24 +242,18 @@ def test_failed_withdraw_non_divisible_amount( ): dest_acc = solana_account - spl_neon_token = SplToken( - self.sol_client, neon_mint, TOKEN_PROGRAM_ID, dest_acc.public_key - ) + spl_neon_token = SplToken(self.sol_client, neon_mint, TOKEN_PROGRAM_ID, dest_acc.public_key) move_amount = pow(10, 18) + 123 - destination_balance_before = spl_neon_token.get_balance( - dest_acc.public_key, commitment=Commitment("confirmed") - ) + destination_balance_before = spl_neon_token.get_balance(dest_acc.public_key, commitment=Commitment("confirmed")) with pytest.raises(AttributeError): _ = destination_balance_before.value with pytest.raises(web3_exceptions.ContractLogicError): self.withdraw(dest_acc, move_amount, withdraw_contract) - destination_balance_after = spl_neon_token.get_balance( - dest_acc.public_key, commitment=Commitment("confirmed") - ) + destination_balance_after = spl_neon_token.get_balance(dest_acc.public_key, commitment=Commitment("confirmed")) with pytest.raises(AttributeError): _ = destination_balance_after.value @@ -281,23 +268,17 @@ def test_failed_withdraw_insufficient_balance( ): dest_acc = solana_account - spl_neon_token = SplToken( - self.sol_client, neon_mint, TOKEN_PROGRAM_ID, dest_acc.public_key - ) + spl_neon_token = SplToken(self.sol_client, neon_mint, TOKEN_PROGRAM_ID, dest_acc.public_key) amount = move_amount * pow(10, 18) - destination_balance_before = spl_neon_token.get_balance( - dest_acc.public_key, commitment=Commitment("confirmed") - ) + destination_balance_before = spl_neon_token.get_balance(dest_acc.public_key, commitment=Commitment("confirmed")) with pytest.raises(AttributeError): _ = destination_balance_before.value with pytest.raises(ValueError): self.withdraw(dest_acc, amount, withdraw_contract) - destination_balance_after = spl_neon_token.get_balance( - dest_acc.public_key, commitment=Commitment("confirmed") - ) + destination_balance_after = spl_neon_token.get_balance(dest_acc.public_key, commitment=Commitment("confirmed")) with pytest.raises(AttributeError): _ = destination_balance_after.value diff --git a/integration/tests/conftest.py b/integration/tests/conftest.py index a0a7844fe6..6f9b3116ec 100644 --- a/integration/tests/conftest.py +++ b/integration/tests/conftest.py @@ -99,22 +99,20 @@ def prepare_account(operator, faucet, web3_client: NeonChainWeb3Client): """Create new account for tests and save operator pre and post balances""" with allure.step("Create account for tests"): acc = web3_client.eth.account.create() - with allure.step( - f"Request {NEON_AIRDROP_AMOUNT} NEON from faucet for {acc.address}" - ): + with allure.step(f"Request {NEON_AIRDROP_AMOUNT} NEON from faucet for {acc.address}"): faucet.request_neon(acc.address, NEON_AIRDROP_AMOUNT) assert web3_client.get_balance(acc) == NEON_AIRDROP_AMOUNT start_neon_balance = operator.get_neon_balance() start_sol_balance = operator.get_solana_balance() with allure.step( - f"Operator initial balance: {start_neon_balance / LAMPORT_PER_SOL} NEON {start_sol_balance / LAMPORT_PER_SOL} SOL" + f"Operator initial balance: {start_neon_balance / LAMPORT_PER_SOL} NEON {start_sol_balance / LAMPORT_PER_SOL} SOL" ): pass yield acc end_neon_balance = operator.get_neon_balance() end_sol_balance = operator.get_solana_balance() with allure.step( - f"Operator end balance: {end_neon_balance / LAMPORT_PER_SOL} NEON {end_sol_balance / LAMPORT_PER_SOL} SOL" + f"Operator end balance: {end_neon_balance / LAMPORT_PER_SOL} NEON {end_sol_balance / LAMPORT_PER_SOL} SOL" ): pass with allure.step(f"Account end balance: {web3_client.get_balance(acc)} NEON"): @@ -135,9 +133,7 @@ def bank_account(pytestconfig: Config) -> typing.Optional[Keypair]: def eth_bank_account(pytestconfig: Config, web3_client) -> typing.Optional[Keypair]: account = None if pytestconfig.environment.eth_bank_account != "": - account = web3_client.eth.account.from_key( - pytestconfig.environment.eth_bank_account - ) + account = web3_client.eth.account.from_key(pytestconfig.environment.eth_bank_account) yield account @@ -145,16 +141,12 @@ def eth_bank_account(pytestconfig: Config, web3_client) -> typing.Optional[Keypa def solana_account(bank_account, pytestconfig: Config, sol_client): account = Keypair.generate() if pytestconfig.environment.use_bank: - sol_client.send_sol( - bank_account, account.public_key, int(0.5 * LAMPORT_PER_SOL) - ) + sol_client.send_sol(bank_account, account.public_key, int(0.5 * LAMPORT_PER_SOL)) else: sol_client.request_airdrop(account.public_key, 1 * LAMPORT_PER_SOL) yield account if pytestconfig.environment.use_bank: - balance = sol_client.get_balance( - account.public_key, commitment=commitment.Confirmed - ).value + balance = sol_client.get_balance(account.public_key, commitment=commitment.Confirmed).value try: sol_client.send_sol(account, bank_account.public_key, balance - 5000) except: @@ -163,11 +155,11 @@ def solana_account(bank_account, pytestconfig: Config, sol_client): @pytest.fixture(scope="session") def erc20_spl( - web3_client: NeonChainWeb3Client, - faucet, - pytestconfig: Config, - sol_client, - solana_account, + web3_client: NeonChainWeb3Client, + faucet, + pytestconfig: Config, + sol_client, + solana_account, ): symbol = "".join([random.choice(string.ascii_uppercase) for _ in range(3)]) erc20 = ERC20Wrapper( @@ -192,9 +184,7 @@ def erc20_spl( opts=TxOpts(preflight_commitment=commitment.Confirmed, skip_confirmation=False), ) - erc20.claim( - erc20.account, bytes(erc20.solana_associated_token_acc), 100000000000000 - ) + erc20.claim(erc20.account, bytes(erc20.solana_associated_token_acc), 100000000000000) yield erc20 @@ -205,9 +195,7 @@ def erc20_simple(web3_client, faucet): @pytest.fixture(scope="session") -def erc20_spl_mintable( - web3_client: NeonChainWeb3Client, faucet, sol_client, solana_account -): +def erc20_spl_mintable(web3_client: NeonChainWeb3Client, faucet, sol_client, solana_account): symbol = "".join([random.choice(string.ascii_uppercase) for _ in range(3)]) erc20 = ERC20Wrapper( web3_client, @@ -223,8 +211,7 @@ def erc20_spl_mintable( @pytest.fixture(scope="function") -def new_account(web3_client, sol_client, faucet, eth_bank_account, - web3_client_sol, pytestconfig, solana_account): +def new_account(web3_client, sol_client, faucet, eth_bank_account, web3_client_sol, pytestconfig, solana_account): account = web3_client.create_account_with_balance(faucet, bank_account=eth_bank_account) yield account @@ -236,15 +223,14 @@ def class_account(web3_client, faucet, eth_bank_account, solana_account, sol_cli @pytest.fixture(scope="class") -def class_account_sol_chain(sol_client, solana_account, web3_client, web3_client_sol, pytestconfig, faucet, - eth_bank_account): +def class_account_sol_chain( + sol_client, solana_account, web3_client, web3_client_sol, pytestconfig, faucet, eth_bank_account +): account = web3_client.create_account_with_balance(faucet, bank_account=eth_bank_account) sol_client.request_airdrop(solana_account.public_key, 1 * LAMPORT_PER_SOL) - sol_client.deposit_wrapped_sol_from_solana_to_neon(solana_account, - account, - web3_client_sol.eth.chain_id, - pytestconfig.environment.evm_loader, - 1 * LAMPORT_PER_SOL) + sol_client.deposit_wrapped_sol_from_solana_to_neon( + solana_account, account, web3_client_sol.eth.chain_id, pytestconfig.environment.evm_loader, 1 * LAMPORT_PER_SOL + ) return account @@ -262,9 +248,7 @@ def neon_mint(pytestconfig: Config): @pytest.fixture(scope="class") def withdraw_contract(web3_client, faucet, class_account): - contract, _ = web3_client.deploy_and_get_contract( - "precompiled/NeonToken", "0.8.10", account=class_account - ) + contract, _ = web3_client.deploy_and_get_contract("precompiled/NeonToken", "0.8.10", account=class_account) return contract @@ -281,25 +265,23 @@ def common_contract(web3_client, class_account): @pytest.fixture(scope="class") def meta_proxy_contract(web3_client, faucet, class_account): - contract, _ = web3_client.deploy_and_get_contract( - "./EIPs/MetaProxy", "0.8.10", account=class_account - ) + contract, _ = web3_client.deploy_and_get_contract("./EIPs/MetaProxy", "0.8.10", account=class_account) return contract @pytest.fixture(scope="class") def event_caller_contract(web3_client, class_account) -> typing.Any: - event_caller, _ = web3_client.deploy_and_get_contract( - "common/EventCaller", "0.8.12", class_account - ) + event_caller, _ = web3_client.deploy_and_get_contract("common/EventCaller", "0.8.12", class_account) yield event_caller @pytest.fixture(scope="class") def wsol(web3_client_sol, class_account_sol_chain): contract, _ = web3_client_sol.deploy_and_get_contract( - contract="common/WNativeChainToken", version="0.8.12", - contract_name="WNativeChainToken", account=class_account_sol_chain, + contract="common/WNativeChainToken", + version="0.8.12", + contract_name="WNativeChainToken", + account=class_account_sol_chain, ) return contract diff --git a/operator-keypair.json b/operator-keypair.json new file mode 100644 index 0000000000..54e7bbcc6b --- /dev/null +++ b/operator-keypair.json @@ -0,0 +1 @@ +[161,247,66,57,203,188,141,236,124,123,200,192,255,23,161,34,116,202,70,182,176,94,195,68,185,32,61,42,203,57,245,190,153,233,189,58,187,209,15,232,83,112,145,116,89,187,201,44,36,255,81,102,84,101,62,1,180,217,194,37,147,74,7,170] \ No newline at end of file diff --git a/utils/solana_client.py b/utils/solana_client.py index a4253716ec..abafa7a7cb 100644 --- a/utils/solana_client.py +++ b/utils/solana_client.py @@ -12,7 +12,7 @@ from solana.transaction import Transaction from solders.rpc.errors import InternalErrorMessage from solders.rpc.responses import RequestAirdropResp -from spl.token.instructions import get_associated_token_address, create_associated_token_account +from spl.token.instructions import get_associated_token_address, create_associated_token_account, mint_to, MintToParams from utils.consts import LAMPORT_PER_SOL, wSOL from utils.helpers import wait_condition @@ -25,22 +25,18 @@ class SolanaClient(solana.rpc.api.Client): def __init__(self, endpoint, account_seed_version="\3"): super().__init__(endpoint=endpoint, timeout=60) self.account_seed_version = ( - bytes(account_seed_version, encoding="utf-8") - .decode("unicode-escape") - .encode("utf-8") + bytes(account_seed_version, encoding="utf-8").decode("unicode-escape").encode("utf-8") ) def request_airdrop( - self, - pubkey: PublicKey, - lamports: int, - commitment: tp.Optional[Commitment] = None, + self, + pubkey: PublicKey, + lamports: int, + commitment: tp.Optional[Commitment] = None, ) -> RequestAirdropResp: airdrop_resp = None for _ in range(5): - airdrop_resp = super().request_airdrop( - pubkey, lamports, commitment=Finalized - ) + airdrop_resp = super().request_airdrop(pubkey, lamports, commitment=Finalized) if isinstance(airdrop_resp, InternalErrorMessage): time.sleep(10) print(f"Get error from solana airdrop: {airdrop_resp}") @@ -48,18 +44,12 @@ def request_airdrop( break else: raise AssertionError(f"Can't get airdrop from solana: {airdrop_resp}") - wait_condition( - lambda: self.get_balance(pubkey).value >= lamports, timeout_sec=30 - ) + wait_condition(lambda: self.get_balance(pubkey).value >= lamports, timeout_sec=30) return airdrop_resp def send_sol(self, from_: Keypair, to: PublicKey, amount_lamports: int): tx = Transaction().add( - transfer( - TransferParams( - from_pubkey=from_.public_key, to_pubkey=to, lamports=amount_lamports - ) - ) + transfer(TransferParams(from_pubkey=from_.public_key, to_pubkey=to, lamports=amount_lamports)) ) balance_before = self.get_balance(to).value self.send_transaction(tx, from_) @@ -73,15 +63,12 @@ def send_sol(self, from_: Keypair, to: PublicKey, amount_lamports: int): def ether2balance(self, address: tp.Union[str, bytes], chain_id: int, evm_loader_id: str) -> PublicKey: # get public key associated with chain_id for an address address_bytes = bytes.fromhex(address[2:]) - chain_id_bytes = chain_id.to_bytes(32, 'big') + chain_id_bytes = chain_id.to_bytes(32, "big") return PublicKey.find_program_address( - [self.account_seed_version, address_bytes, chain_id_bytes], - PublicKey(evm_loader_id) + [self.account_seed_version, address_bytes, chain_id_bytes], PublicKey(evm_loader_id) )[0] - def get_erc_auth_address( - self, neon_account_address: str, token_address: str, evm_loader_id: str - ): + def get_erc_auth_address(self, neon_account_address: str, token_address: str, evm_loader_id: str): neon_account_addressbytes = bytes(12) + bytes.fromhex(neon_account_address[2:]) if token_address.startswith("0x"): token_address = token_address[2:] @@ -114,51 +101,37 @@ def create_spl(self, owner: Keypair, decimals: int = 9): return token_mint, assoc_addr - def send_tx_and_check_status_ok(self, tx, solana_account): + def send_tx_and_check_status_ok(self, tx, *signers): opts = TxOpts(skip_preflight=True, skip_confirmation=False) - sig = self.send_transaction(tx, solana_account, opts=opts).value + sig = self.send_transaction(tx, *signers, opts=opts).value sig_status = json.loads((self.confirm_transaction(sig)).to_json()) assert sig_status["result"]["value"][0]["status"] == {"Ok": None}, f"error:{sig_status}" def create_ata(self, solana_account, neon_mint): trx = Transaction() - trx.add( - create_associated_token_account( - solana_account.public_key, solana_account.public_key, neon_mint - ) - ) + trx.add(create_associated_token_account(solana_account.public_key, solana_account.public_key, neon_mint)) opts = TxOpts(skip_preflight=True, skip_confirmation=False) self.send_transaction(trx, solana_account, opts=opts) - def deposit_wrapped_sol_from_solana_to_neon(self, solana_account, neon_account, chain_id, evm_loader_id, full_amount=None): + + def deposit_wrapped_sol_from_solana_to_neon( + self, solana_account, neon_account, chain_id, evm_loader_id, full_amount=None + ): if not full_amount: full_amount = int(0.1 * LAMPORT_PER_SOL) mint_pubkey = wSOL["address_spl"] - ata_address = get_associated_token_address( - solana_account.public_key, mint_pubkey - ) + ata_address = get_associated_token_address(solana_account.public_key, mint_pubkey) self.create_ata(solana_account, mint_pubkey) # wrap SOL wSOL_account = self.get_account_info(ata_address).value - wrap_sol_tx = wSOL_tx(wSOL_account, - wSOL, full_amount, solana_account.public_key, ata_address - ) + wrap_sol_tx = wSOL_tx(wSOL_account, wSOL, full_amount, solana_account.public_key, ata_address) self.send_tx_and_check_status_ok(wrap_sol_tx, solana_account) - balance_pubkey = self.ether2balance(neon_account.address, - chain_id, - evm_loader_id) + balance_pubkey = self.ether2balance(neon_account.address, chain_id, evm_loader_id) tx = token_from_solana_to_neon_tx( - solana_account, - balance_pubkey, - wSOL["address_spl"], - neon_account, - full_amount, - evm_loader_id, - chain_id + solana_account, balance_pubkey, wSOL["address_spl"], neon_account, full_amount, evm_loader_id, chain_id ) self.send_tx_and_check_status_ok(tx, solana_account) - diff --git a/utils/transfers_inter_networks.py b/utils/transfers_inter_networks.py index 05166df8bd..a0fb6525bf 100644 --- a/utils/transfers_inter_networks.py +++ b/utils/transfers_inter_networks.py @@ -3,18 +3,17 @@ from solana.transaction import Transaction from spl.token.constants import TOKEN_PROGRAM_ID from spl.token.instructions import (ApproveParams, approve, - get_associated_token_address) + get_associated_token_address, MintToParams, mint_to) from utils.instructions import Instruction, get_solana_wallet_signer def token_from_solana_to_neon_tx(solana_account, neon_wallet, mint, neon_account, - amount, evm_loader_id, chain_id): + amount, evm_loader_id, chain_id): '''Transfer any token from solana to neon transaction''' tx = Transaction(fee_payer=solana_account.public_key) associated_token_address = get_associated_token_address( solana_account.public_key, mint) - tx.add(approve( ApproveParams( program_id=TOKEN_PROGRAM_ID, @@ -27,7 +26,6 @@ def token_from_solana_to_neon_tx(solana_account, neon_wallet, mint, neon_account evm_loader_id) pool = get_associated_token_address(authority_pool, mint) - tx.add(Instruction.deposit( bytes.fromhex(neon_account.address[2:]), chain_id, @@ -53,6 +51,15 @@ def wSOL_tx(wSOL_account, spl_token, amount, solana_wallet, ata_address): return tx +def mint_tx(amount, dest, neon_mint, mint_authority): + trx = Transaction() + params = MintToParams( + amount=amount, dest=dest, mint=neon_mint, mint_authority=mint_authority, program_id=TOKEN_PROGRAM_ID + ) + trx.add(mint_to(params)) + return trx + + def neon_transfer_tx(web3_client, sol_client, amount, spl_token, solana_account, neon_account, erc20_spl, evm_loader_id): chain_id = web3_client.eth.chain_id From fec0f1b2e60fd9ef4c719b88b729f092b94a330c Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Thu, 9 Nov 2023 18:13:51 +0100 Subject: [PATCH 14/56] added tests for account migrations --- .../tests/basic/test_account_migration.py | 204 ++++++++++++++++++ utils/erc20wrapper.py | 11 +- utils/erc721ForMetaplex.py | 8 +- 3 files changed, 218 insertions(+), 5 deletions(-) create mode 100644 integration/tests/basic/test_account_migration.py diff --git a/integration/tests/basic/test_account_migration.py b/integration/tests/basic/test_account_migration.py new file mode 100644 index 0000000000..5fb1b4a062 --- /dev/null +++ b/integration/tests/basic/test_account_migration.py @@ -0,0 +1,204 @@ +import os + +import pytest +from web3.logs import DISCARD + +from utils.erc20wrapper import ERC20Wrapper +from utils.erc721ForMetaplex import ERC721ForMetaplex +from utils.helpers import gen_hash_of_block + + +@pytest.fixture(scope="session") +def accounts(web3_client): + account_keys = os.environ.get("ACCOUNTS").split(",") + accounts = [] + for key in account_keys: + accounts.append(web3_client.eth.account.from_key(key)) + print("Balances before testing:") + for acc in accounts: + print(f"Balance for {acc.address}: {web3_client.to_main_currency(web3_client.get_balance(acc.address))}") + + yield accounts + print("Balances after testing:") + for acc in accounts: + print(f"Balance for {acc.address}: {web3_client.to_main_currency(web3_client.get_balance(acc.address))}") + + +@pytest.fixture(scope="session") +def bob(accounts): + return accounts[0] + + +@pytest.fixture(scope="session") +def alice(accounts): + return accounts[1] + + +@pytest.fixture(scope="session") +def erc20(erc20_spl_mintable, web3_client, faucet, sol_client, solana_account, bob): + contract_address = os.environ.get("ERC20_ADDRESS") + + if contract_address: + erc20 = ERC20Wrapper( + web3_client, + faucet, + "Test AAA", + "AAA", + sol_client, + account=bob, + solana_account=solana_account, + mintable=True, + contract_address=contract_address, + ) + print(f"Using ERC20 deployed earlier at {contract_address}") + + else: + erc20 = ERC20Wrapper( + web3_client, + faucet, + "Test AAA", + "AAA", + sol_client, + account=bob, + solana_account=solana_account, + mintable=True, + ) + print(f"ERC20 deployed at address: {erc20.contract.address}") + erc20.mint_tokens(erc20.account, erc20.account.address) + return erc20 + + +@pytest.fixture(scope="session") +def erc721(web3_client, erc20_spl_mintable, faucet, bob): + contract_address = os.environ.get("ERC721_ADDRESS") + if contract_address: + erc721 = ERC721ForMetaplex(web3_client, faucet, account=bob, contract_address=contract_address) + print(f"Using ERC721 deployed earlier at {contract_address}") + else: + erc721 = ERC721ForMetaplex(web3_client, faucet, account=bob) + print(f"ERC721 deployed at address: {erc721.contract.address}") + + return erc721 + + +def test_transfers(alice, bob, accounts, web3_client): + web3_client.send_neon(alice, bob, 5) + for i in range(5): + receipt = web3_client.send_neon(alice, accounts[i + 1], 5) + assert receipt["status"] == 1 + receipt = web3_client.send_neon(bob, accounts[i + 2], 1) + assert receipt["status"] == 1 + + +def test_contract_deploy_and_interact(web3_client, alice, bob): + contract_a, _ = web3_client.deploy_and_get_contract("common/NestedCallsChecker", "0.8.12", bob, contract_name="A") + contract_b, _ = web3_client.deploy_and_get_contract("common/NestedCallsChecker", "0.8.12", alice, contract_name="B") + contract_c, _ = web3_client.deploy_and_get_contract("common/NestedCallsChecker", "0.8.12", alice, contract_name="C") + tx = { + "from": alice.address, + "nonce": web3_client.eth.get_transaction_count(alice.address), + "gasPrice": web3_client.gas_price(), + } + + instruction_tx = contract_a.functions.method1(contract_b.address, contract_c.address).build_transaction(tx) + resp = web3_client.send_transaction(alice, instruction_tx) + event_a1_logs = contract_a.events.EventA1().process_receipt(resp, errors=DISCARD) + assert len(event_a1_logs) == 1 + event_b1_logs = contract_b.events.EventB1().process_receipt(resp, errors=DISCARD) + assert len(event_b1_logs) == 1 + event_b2_logs = contract_b.events.EventB2().process_receipt(resp, errors=DISCARD) + event_c1_logs = contract_c.events.EventC1().process_receipt(resp, errors=DISCARD) + event_c2_logs = contract_c.events.EventC2().process_receipt(resp, errors=DISCARD) + for log in (event_b2_logs, event_c1_logs, event_c2_logs): + assert log == (), f"Trx shouldn't contain logs for the events: eventB2, eventC1, eventC2_log0. Log: {log}" + + +def test_erc721_interaction(erc721, web3_client, bob, alice, accounts): + seed = web3_client.text_to_bytes32(gen_hash_of_block(8)) + token_id = erc721.mint(seed, erc721.account.address, "uri") + + balance_usr1_before = erc721.contract.functions.balanceOf(erc721.account.address).call() + balance_usr2_before = erc721.contract.functions.balanceOf(alice.address).call() + + erc721.approve(alice.address, token_id, erc721.account) + erc721.transfer_from(erc721.account.address, alice.address, token_id, alice) + + balance_usr1_after = erc721.contract.functions.balanceOf(erc721.account.address).call() + balance_usr2_after = erc721.contract.functions.balanceOf(alice.address).call() + + assert balance_usr1_after - balance_usr1_before == -1 + assert balance_usr2_after - balance_usr2_before == 1 + + token_ids = [] + for _ in range(5): + seed = web3_client.text_to_bytes32(gen_hash_of_block(8)) + token_ids.append(erc721.mint(seed, erc721.account.address, "uri")) + + for i in range(5): + recipient = accounts[i + 3] + + balance_usr1_before = erc721.contract.functions.balanceOf(erc721.account.address).call() + balance_usr2_before = erc721.contract.functions.balanceOf(recipient.address).call() + + erc721.transfer_from(erc721.account.address, recipient.address, token_ids[i], erc721.account) + + balance_usr1_after = erc721.contract.functions.balanceOf(erc721.account.address).call() + balance_usr2_after = erc721.contract.functions.balanceOf(recipient.address).call() + + assert balance_usr1_after - balance_usr1_before == -1 + assert balance_usr2_after - balance_usr2_before == 1 + + +def test_erc20_interaction(erc20, web3_client, bob, alice, accounts): + balance_before = erc20.contract.functions.balanceOf(erc20.account.address).call() + amount = 500 + erc20.mint_tokens(erc20.account, erc20.account.address, amount) + balance_after = erc20.contract.functions.balanceOf(erc20.account.address).call() + assert balance_after == balance_before + amount + + tom = accounts[9] + balance_before = erc20.contract.functions.balanceOf(tom.address).call() + erc20.mint_tokens(erc20.account, tom.address, amount) + balance_after = erc20.contract.functions.balanceOf(tom.address).call() + assert balance_after == amount + balance_before + + balance_before = erc20.contract.functions.balanceOf(tom.address).call() + total_before = erc20.contract.functions.totalSupply().call() + erc20.burn(tom, tom.address, amount) + balance_after = erc20.contract.functions.balanceOf(tom.address).call() + total_after = erc20.contract.functions.totalSupply().call() + + assert balance_after == balance_before - amount + assert total_after == total_before - amount + + amount = 1000000000000000 + erc20.mint_tokens(erc20.account, accounts[9].address, amount) + + amount = 500 + for i in range(8): + recipient = accounts[i] + sender = accounts[9] + balance_acc1_before = erc20.contract.functions.balanceOf(sender.address).call() + balance_acc2_before = erc20.contract.functions.balanceOf(recipient.address).call() + total_before = erc20.contract.functions.totalSupply().call() + erc20.transfer(sender, recipient.address, amount) + balance_acc1_after = erc20.contract.functions.balanceOf(sender.address).call() + balance_acc2_after = erc20.contract.functions.balanceOf(recipient.address).call() + total_after = erc20.contract.functions.totalSupply().call() + assert balance_acc1_after == balance_acc1_before - amount + assert balance_acc2_after == balance_acc2_before + amount + assert total_before == total_after + + for i in range(7): + recipient = accounts[8] + sender = accounts[i] + balance_acc1_before = erc20.contract.functions.balanceOf(sender.address).call() + balance_acc2_before = erc20.contract.functions.balanceOf(recipient.address).call() + total_before = erc20.contract.functions.totalSupply().call() + erc20.transfer(sender, recipient.address, amount) + balance_acc1_after = erc20.contract.functions.balanceOf(sender.address).call() + balance_acc2_after = erc20.contract.functions.balanceOf(recipient.address).call() + total_after = erc20.contract.functions.totalSupply().call() + assert balance_acc1_after == balance_acc1_before - amount + assert balance_acc2_after == balance_acc2_before + amount + assert total_before == total_after diff --git a/utils/erc20wrapper.py b/utils/erc20wrapper.py index 8fe07c5d2c..e26851c9ed 100644 --- a/utils/erc20wrapper.py +++ b/utils/erc20wrapper.py @@ -25,6 +25,7 @@ def __init__( evm_loader_id=None, account=None, mintable=True, + contract_address=None ): self.solana_associated_token_acc = None self.token_mint = None @@ -39,10 +40,14 @@ def __init__( self.symbol = symbol self.decimals = decimals self.sol_client = sol_client - self.contract_address = self.deploy_wrapper(mintable) - self.contract = self.web3_client.get_deployed_contract(self.contract_address, - "EIPs/ERC20/IERC20ForSpl") + if contract_address: + self.contract = web3_client.get_deployed_contract(contract_address, + contract_file = "EIPs/ERC20/IERC20ForSpl") + else: + self.contract_address = self.deploy_wrapper(mintable) + self.contract = self.web3_client.get_deployed_contract(self.contract_address, + "EIPs/ERC20/IERC20ForSpl") def make_tx_object(self, from_address, gas_price=None, gas=None): tx = { diff --git a/utils/erc721ForMetaplex.py b/utils/erc721ForMetaplex.py index f40b6fd129..4f88e5390f 100644 --- a/utils/erc721ForMetaplex.py +++ b/utils/erc721ForMetaplex.py @@ -7,11 +7,15 @@ class ERC721ForMetaplex: def __init__(self, web3_client: web3client.NeonChainWeb3Client, faucet, account=None, contract="erc721_for_metaplex.sol", - contract_name="ERC721ForMetaplex"): + contract_name="ERC721ForMetaplex", contract_address=None): self.web3_client = web3_client self.account = account or web3_client.create_account() faucet.request_neon(self.account.address, 600) - self.contract = self.deploy(contract, contract_name) + if contract_address: + self.contract = web3_client.get_deployed_contract(contract_address, + contract_file = contract, contract_name=contract_name) + else: + self.contract = self.deploy(contract, contract_name) def make_tx_object(self, from_address, gasPrice=None, gas=None): tx = {"from": from_address, "nonce": self.web3_client.eth.get_transaction_count(from_address), From 6163a350d54698b18d273f1913fb33d07827be02 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Fri, 10 Nov 2023 11:45:31 +0100 Subject: [PATCH 15/56] Update test_account_migration.py --- .../tests/basic/test_account_migration.py | 81 ++++++++++++++----- 1 file changed, 61 insertions(+), 20 deletions(-) diff --git a/integration/tests/basic/test_account_migration.py b/integration/tests/basic/test_account_migration.py index 5fb1b4a062..5f13baf0be 100644 --- a/integration/tests/basic/test_account_migration.py +++ b/integration/tests/basic/test_account_migration.py @@ -14,14 +14,18 @@ def accounts(web3_client): accounts = [] for key in account_keys: accounts.append(web3_client.eth.account.from_key(key)) - print("Balances before testing:") + print("Before testing:") for acc in accounts: - print(f"Balance for {acc.address}: {web3_client.to_main_currency(web3_client.get_balance(acc.address))}") + print( + f"Balance for {acc.address}: {web3_client.to_main_currency(web3_client.get_balance(acc.address))}, nonce: {web3_client.eth.get_transaction_count(acc.address)}" + ) yield accounts - print("Balances after testing:") + print("After testing:") for acc in accounts: - print(f"Balance for {acc.address}: {web3_client.to_main_currency(web3_client.get_balance(acc.address))}") + print( + f"Balance for {acc.address}: {web3_client.to_main_currency(web3_client.get_balance(acc.address))}, nonce: {web3_client.eth.get_transaction_count(acc.address)}" + ) @pytest.fixture(scope="session") @@ -34,6 +38,15 @@ def alice(accounts): return accounts[1] +@pytest.fixture(scope="session") +def trx_list(): + list = [] + yield list + print("Trx list:") + for trx in list: + print(trx.hex()) + + @pytest.fixture(scope="session") def erc20(erc20_spl_mintable, web3_client, faucet, sol_client, solana_account, bob): contract_address = os.environ.get("ERC20_ADDRESS") @@ -81,19 +94,31 @@ def erc721(web3_client, erc20_spl_mintable, faucet, bob): return erc721 -def test_transfers(alice, bob, accounts, web3_client): +def test_transfers(alice, bob, accounts, web3_client, trx_list): web3_client.send_neon(alice, bob, 5) for i in range(5): receipt = web3_client.send_neon(alice, accounts[i + 1], 5) + trx_list.append(receipt["transactionHash"]) assert receipt["status"] == 1 receipt = web3_client.send_neon(bob, accounts[i + 2], 1) + trx_list.append(receipt["transactionHash"]) assert receipt["status"] == 1 -def test_contract_deploy_and_interact(web3_client, alice, bob): - contract_a, _ = web3_client.deploy_and_get_contract("common/NestedCallsChecker", "0.8.12", bob, contract_name="A") - contract_b, _ = web3_client.deploy_and_get_contract("common/NestedCallsChecker", "0.8.12", alice, contract_name="B") - contract_c, _ = web3_client.deploy_and_get_contract("common/NestedCallsChecker", "0.8.12", alice, contract_name="C") +def test_contract_deploy_and_interact(web3_client, alice, bob, trx_list): + contract_a, receipt = web3_client.deploy_and_get_contract( + "common/NestedCallsChecker", "0.8.12", bob, contract_name="A" + ) + trx_list.append(receipt["transactionHash"]) + contract_b, receipt = web3_client.deploy_and_get_contract( + "common/NestedCallsChecker", "0.8.12", alice, contract_name="B" + ) + trx_list.append(receipt["transactionHash"]) + contract_c, receipt = web3_client.deploy_and_get_contract( + "common/NestedCallsChecker", "0.8.12", alice, contract_name="C" + ) + trx_list.append(receipt["transactionHash"]) + tx = { "from": alice.address, "nonce": web3_client.eth.get_transaction_count(alice.address), @@ -102,6 +127,8 @@ def test_contract_deploy_and_interact(web3_client, alice, bob): instruction_tx = contract_a.functions.method1(contract_b.address, contract_c.address).build_transaction(tx) resp = web3_client.send_transaction(alice, instruction_tx) + trx_list.append(resp["transactionHash"]) + event_a1_logs = contract_a.events.EventA1().process_receipt(resp, errors=DISCARD) assert len(event_a1_logs) == 1 event_b1_logs = contract_b.events.EventB1().process_receipt(resp, errors=DISCARD) @@ -113,15 +140,18 @@ def test_contract_deploy_and_interact(web3_client, alice, bob): assert log == (), f"Trx shouldn't contain logs for the events: eventB2, eventC1, eventC2_log0. Log: {log}" -def test_erc721_interaction(erc721, web3_client, bob, alice, accounts): +def test_erc721_interaction(erc721, web3_client, bob, alice, accounts, trx_list): seed = web3_client.text_to_bytes32(gen_hash_of_block(8)) token_id = erc721.mint(seed, erc721.account.address, "uri") balance_usr1_before = erc721.contract.functions.balanceOf(erc721.account.address).call() balance_usr2_before = erc721.contract.functions.balanceOf(alice.address).call() - erc721.approve(alice.address, token_id, erc721.account) - erc721.transfer_from(erc721.account.address, alice.address, token_id, alice) + resp = erc721.approve(alice.address, token_id, erc721.account) + trx_list.append(resp["transactionHash"]) + + resp = erc721.transfer_from(erc721.account.address, alice.address, token_id, alice) + trx_list.append(resp["transactionHash"]) balance_usr1_after = erc721.contract.functions.balanceOf(erc721.account.address).call() balance_usr2_after = erc721.contract.functions.balanceOf(alice.address).call() @@ -140,7 +170,8 @@ def test_erc721_interaction(erc721, web3_client, bob, alice, accounts): balance_usr1_before = erc721.contract.functions.balanceOf(erc721.account.address).call() balance_usr2_before = erc721.contract.functions.balanceOf(recipient.address).call() - erc721.transfer_from(erc721.account.address, recipient.address, token_ids[i], erc721.account) + resp = erc721.transfer_from(erc721.account.address, recipient.address, token_ids[i], erc721.account) + trx_list.append(resp["transactionHash"]) balance_usr1_after = erc721.contract.functions.balanceOf(erc721.account.address).call() balance_usr2_after = erc721.contract.functions.balanceOf(recipient.address).call() @@ -149,22 +180,28 @@ def test_erc721_interaction(erc721, web3_client, bob, alice, accounts): assert balance_usr2_after - balance_usr2_before == 1 -def test_erc20_interaction(erc20, web3_client, bob, alice, accounts): +def test_erc20_interaction(erc20, web3_client, bob, alice, accounts, trx_list): balance_before = erc20.contract.functions.balanceOf(erc20.account.address).call() amount = 500 - erc20.mint_tokens(erc20.account, erc20.account.address, amount) + resp = erc20.mint_tokens(erc20.account, erc20.account.address, amount) + trx_list.append(resp["transactionHash"]) + balance_after = erc20.contract.functions.balanceOf(erc20.account.address).call() assert balance_after == balance_before + amount tom = accounts[9] balance_before = erc20.contract.functions.balanceOf(tom.address).call() - erc20.mint_tokens(erc20.account, tom.address, amount) + resp = erc20.mint_tokens(erc20.account, tom.address, amount) + trx_list.append(resp["transactionHash"]) + balance_after = erc20.contract.functions.balanceOf(tom.address).call() assert balance_after == amount + balance_before balance_before = erc20.contract.functions.balanceOf(tom.address).call() total_before = erc20.contract.functions.totalSupply().call() - erc20.burn(tom, tom.address, amount) + resp = erc20.burn(tom, tom.address, amount) + trx_list.append(resp["transactionHash"]) + balance_after = erc20.contract.functions.balanceOf(tom.address).call() total_after = erc20.contract.functions.totalSupply().call() @@ -172,7 +209,8 @@ def test_erc20_interaction(erc20, web3_client, bob, alice, accounts): assert total_after == total_before - amount amount = 1000000000000000 - erc20.mint_tokens(erc20.account, accounts[9].address, amount) + resp = erc20.mint_tokens(erc20.account, accounts[9].address, amount) + trx_list.append(resp["transactionHash"]) amount = 500 for i in range(8): @@ -181,7 +219,9 @@ def test_erc20_interaction(erc20, web3_client, bob, alice, accounts): balance_acc1_before = erc20.contract.functions.balanceOf(sender.address).call() balance_acc2_before = erc20.contract.functions.balanceOf(recipient.address).call() total_before = erc20.contract.functions.totalSupply().call() - erc20.transfer(sender, recipient.address, amount) + resp = erc20.transfer(sender, recipient.address, amount) + trx_list.append(resp["transactionHash"]) + balance_acc1_after = erc20.contract.functions.balanceOf(sender.address).call() balance_acc2_after = erc20.contract.functions.balanceOf(recipient.address).call() total_after = erc20.contract.functions.totalSupply().call() @@ -195,7 +235,8 @@ def test_erc20_interaction(erc20, web3_client, bob, alice, accounts): balance_acc1_before = erc20.contract.functions.balanceOf(sender.address).call() balance_acc2_before = erc20.contract.functions.balanceOf(recipient.address).call() total_before = erc20.contract.functions.totalSupply().call() - erc20.transfer(sender, recipient.address, amount) + resp = erc20.transfer(sender, recipient.address, amount) + trx_list.append(resp["transactionHash"]) balance_acc1_after = erc20.contract.functions.balanceOf(sender.address).call() balance_acc2_after = erc20.contract.functions.balanceOf(recipient.address).call() total_after = erc20.contract.functions.totalSupply().call() From c447ff81298ae7711f755999b5638120ffb450e0 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Fri, 10 Nov 2023 15:04:57 +0100 Subject: [PATCH 16/56] test_account_migration.py is moved --- integration/tests/migrations/__init__.py | 1 + .../test_account_migration.py | 22 +++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 integration/tests/migrations/__init__.py rename integration/tests/{basic => migrations}/test_account_migration.py (92%) diff --git a/integration/tests/migrations/__init__.py b/integration/tests/migrations/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/integration/tests/migrations/__init__.py @@ -0,0 +1 @@ + diff --git a/integration/tests/basic/test_account_migration.py b/integration/tests/migrations/test_account_migration.py similarity index 92% rename from integration/tests/basic/test_account_migration.py rename to integration/tests/migrations/test_account_migration.py index 5f13baf0be..91fa510328 100644 --- a/integration/tests/basic/test_account_migration.py +++ b/integration/tests/migrations/test_account_migration.py @@ -1,3 +1,5 @@ +"""Tests to check old accounts continue work after structure changes +Environment variables ACCOUNTS, ERC20_ADDRESS, ERC721_ADDRESS should be set""" import os import pytest @@ -48,7 +50,7 @@ def trx_list(): @pytest.fixture(scope="session") -def erc20(erc20_spl_mintable, web3_client, faucet, sol_client, solana_account, bob): +def erc20(web3_client, faucet, sol_client, solana_account, bob): contract_address = os.environ.get("ERC20_ADDRESS") if contract_address: @@ -82,7 +84,7 @@ def erc20(erc20_spl_mintable, web3_client, faucet, sol_client, solana_account, b @pytest.fixture(scope="session") -def erc721(web3_client, erc20_spl_mintable, faucet, bob): +def erc721(web3_client, faucet, bob): contract_address = os.environ.get("ERC721_ADDRESS") if contract_address: erc721 = ERC721ForMetaplex(web3_client, faucet, account=bob, contract_address=contract_address) @@ -105,28 +107,30 @@ def test_transfers(alice, bob, accounts, web3_client, trx_list): assert receipt["status"] == 1 -def test_contract_deploy_and_interact(web3_client, alice, bob, trx_list): +def test_contract_deploy_and_interact(web3_client, accounts, trx_list): + acc1 = accounts[7] + acc2 = accounts[8] contract_a, receipt = web3_client.deploy_and_get_contract( - "common/NestedCallsChecker", "0.8.12", bob, contract_name="A" + "common/NestedCallsChecker", "0.8.12", acc2, contract_name="A" ) trx_list.append(receipt["transactionHash"]) contract_b, receipt = web3_client.deploy_and_get_contract( - "common/NestedCallsChecker", "0.8.12", alice, contract_name="B" + "common/NestedCallsChecker", "0.8.12", acc2, contract_name="B" ) trx_list.append(receipt["transactionHash"]) contract_c, receipt = web3_client.deploy_and_get_contract( - "common/NestedCallsChecker", "0.8.12", alice, contract_name="C" + "common/NestedCallsChecker", "0.8.12", acc1, contract_name="C" ) trx_list.append(receipt["transactionHash"]) tx = { - "from": alice.address, - "nonce": web3_client.eth.get_transaction_count(alice.address), + "from": acc1.address, + "nonce": web3_client.eth.get_transaction_count(acc1.address), "gasPrice": web3_client.gas_price(), } instruction_tx = contract_a.functions.method1(contract_b.address, contract_c.address).build_transaction(tx) - resp = web3_client.send_transaction(alice, instruction_tx) + resp = web3_client.send_transaction(acc1, instruction_tx) trx_list.append(resp["transactionHash"]) event_a1_logs = contract_a.events.EventA1().process_receipt(resp, errors=DISCARD) From 2f9c39b2eda20928c95e569d4b1ff401499f4556 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Tue, 14 Nov 2023 11:51:25 +0100 Subject: [PATCH 17/56] fix image build workflow and added test --- .../actions/dockerize-neon-tests/action.yml | 8 +- .github/workflows/dockerize_neon_tests.yml | 11 +- integration/tests/basic/helpers/chains.py | 5 + integration/tests/basic/test_deposit.py | 46 ++--- .../basic/test_payment_in_different_tokens.py | 175 +++++++++--------- integration/tests/conftest.py | 37 +++- utils/solana_client.py | 43 ++++- utils/web3client.py | 8 + 8 files changed, 190 insertions(+), 143 deletions(-) create mode 100644 integration/tests/basic/helpers/chains.py diff --git a/.github/actions/dockerize-neon-tests/action.yml b/.github/actions/dockerize-neon-tests/action.yml index 19f262a1bd..d86047baf2 100644 --- a/.github/actions/dockerize-neon-tests/action.yml +++ b/.github/actions/dockerize-neon-tests/action.yml @@ -8,8 +8,8 @@ inputs: description: 'branch name for oz tests' required: true default: master - version_tag: - description: 'neon-tests version tag' + branch_tag: + description: 'tag for feature or version branch' required: false default: '' docker_username: @@ -30,8 +30,8 @@ runs: delimeter=$(printf "%0.s-" {1..30}) echo " ${delimeter} Build new docker image ${image_id} ${delimeter}" docker build . --no-cache --tag ${image_id}:${{ inputs.image_tag }} --build-arg OZ_BRANCH='${{ inputs.oz_branch }}' - if [[ "${{ inputs.version_tag }}" != '' ]]; then - docker tag ${image_id}:${{ inputs.image_tag }} ${image_id}:${{ inputs.version_tag }} + if [[ "${{ inputs.branch_tag }}" != '' ]]; then + docker tag ${image_id}:${{ inputs.image_tag }} ${image_id}:${{ inputs.branch_tag }} fi; echo "${delimeter} Login into Docker registry as ${{ inputs.docker_username }} ${delimeter}" echo "${{ inputs.docker_password }}" | docker login -u ${{ inputs.docker_username }} --password-stdin diff --git a/.github/workflows/dockerize_neon_tests.yml b/.github/workflows/dockerize_neon_tests.yml index fccbc66fcb..59aad452e7 100644 --- a/.github/workflows/dockerize_neon_tests.yml +++ b/.github/workflows/dockerize_neon_tests.yml @@ -35,21 +35,18 @@ jobs: echo "oz_branch=${oz_branch}" echo "tag=${tag}" >> $GITHUB_OUTPUT echo "oz_branch=${oz_branch}" >> $GITHUB_OUTPUT - - name: Define version branch - id: version_branch + - name: Define feature of version branch + id: feature_branch + if: github.ref_name !='develop' || github.ref_name !='master' run: | - if [[ "${{ github.ref }}" =~ "refs/heads/"[vt][0-9]+\.[0-9]+\.x ]]; then value=${{ github.ref_name }} - else - value="" - fi echo "value=${value}" echo "value=${value}" >> $GITHUB_OUTPUT - name: "Dockerize neon tests" uses: ./.github/actions/dockerize-neon-tests with: image_tag: ${{ steps.define-env.outputs.tag }} - version_tag: ${{ steps.version_branch.outputs.value }} + branch_tag: ${{ steps.feature_branch.outputs.value }} docker_username: ${{ secrets.DOCKER_USERNAME }} docker_password: ${{ secrets.DOCKER_PASSWORD }} oz_branch: ${{ steps.define-env.outputs.oz_branch }} diff --git a/integration/tests/basic/helpers/chains.py b/integration/tests/basic/helpers/chains.py new file mode 100644 index 0000000000..2de55d9c50 --- /dev/null +++ b/integration/tests/basic/helpers/chains.py @@ -0,0 +1,5 @@ +def make_nonce_the_biggest_for_chain(account, client, rest_clients): + # to avoid error "EVM Error. Attempt to deploy to existing account 0x..." + new_account = client.create_account() + while client.get_nonce(account.address) < max([c.get_nonce(account.address) for c in rest_clients]): + client.send_tokens(account, new_account.address, 10) diff --git a/integration/tests/basic/test_deposit.py b/integration/tests/basic/test_deposit.py index 74b3cc358c..47179809ea 100644 --- a/integration/tests/basic/test_deposit.py +++ b/integration/tests/basic/test_deposit.py @@ -65,45 +65,25 @@ def test_transfer_neon_from_solana_to_neon(self, new_account, solana_account, py neon_balance_after = self.get_balance_from_wei(new_account.address) assert neon_balance_after == neon_balance_before + amount + @pytest.mark.multipletokens def test_create_and_transfer_new_token_from_solana_to_neon( - self, new_account, solana_account, pytestconfig: Config, neon_mint, web3_client_abc, operator + self, + new_account, + solana_account, + pytestconfig: Config, + neon_mint, + web3_client_abc, + operator_keypair, + evm_loader_keypair, ): amount = 100000000 evm_loader_id = pytestconfig.environment.evm_loader - - balance_pubkey = self.sol_client.ether2balance(new_account.address, web3_client_abc.eth.chain_id, evm_loader_id) - - - with open("operator-keypair.json", "r") as key: - secret_key = json.load(key)[:32] - operator = Keypair.from_secret_key(secret_key) - - with open("evm_loader-keypair.json", "r") as key: - secret_key = json.load(key)[:32] - evm_loader_keypair = Keypair.from_secret_key(secret_key) - - spl_neon_token = SplToken(self.sol_client, neon_mint, TOKEN_PROGRAM_ID, payer=operator) - associated_token_address = spl_neon_token.create_associated_token_account(solana_account.public_key) - - tx = mint_tx(amount=amount, dest=associated_token_address, neon_mint=neon_mint, - mint_authority=evm_loader_keypair.public_key) - tx.fee_payer = operator.public_key - - self.sol_client.send_tx_and_check_status_ok(tx, operator, evm_loader_keypair) - - tx = token_from_solana_to_neon_tx( - solana_account, - balance_pubkey, - neon_mint, - new_account, - amount, - evm_loader_id, - web3_client_abc.eth.chain_id, - ) - self.sol_client.send_tx_and_check_status_ok(tx, solana_account) + self.sol_client.deposit_neon_like_tokens_from_solana_to_neon(neon_mint, solana_account, new_account, + web3_client_abc.eth.chain_id, operator_keypair, + evm_loader_keypair, evm_loader_id, amount) abc_balance_after = web3_client_abc.get_balance(new_account) - assert abc_balance_after == amount * 100000000 + assert abc_balance_after == amount * 1000000000 def test_transfer_spl_token_from_solana_to_neon(self, solana_account, new_account, pytestconfig: Config, erc20_spl): evm_loader_id = pytestconfig.environment.evm_loader diff --git a/integration/tests/basic/test_payment_in_different_tokens.py b/integration/tests/basic/test_payment_in_different_tokens.py index 4dc7ccfd34..8d6f3e9810 100644 --- a/integration/tests/basic/test_payment_in_different_tokens.py +++ b/integration/tests/basic/test_payment_in_different_tokens.py @@ -4,27 +4,19 @@ import pytest from integration.tests.basic.helpers.basic import BaseMixin -from utils.consts import LAMPORT_PER_SOL +from integration.tests.basic.helpers.chains import make_nonce_the_biggest_for_chain @allure.feature("Multiply token") -@allure.story("Payments in sol tokens") -class TestSolChain(BaseMixin): +@allure.story("Payments in different tokens") +class TestMultiplyChain(BaseMixin): @pytest.fixture(scope="class") def bob(self, class_account_sol_chain): return class_account_sol_chain @pytest.fixture(scope="class") - def alice(self, sol_client, web3_client, web3_client_sol, - faucet, eth_bank_account, solana_account, pytestconfig): - account = web3_client.create_account_with_balance(faucet, bank_account=eth_bank_account) - sol_client.request_airdrop(solana_account.public_key, 1 * LAMPORT_PER_SOL) - sol_client.deposit_wrapped_sol_from_solana_to_neon(solana_account, - account, - web3_client_sol.eth.chain_id, - pytestconfig.environment.evm_loader, - 1 * LAMPORT_PER_SOL) - return account + def alice(self, sol_client, account_with_all_tokens): + return account_with_all_tokens @pytest.fixture(scope="function") def check_neon_balance_does_not_changed(self, alice, bob, web3_client): @@ -37,8 +29,7 @@ def check_neon_balance_does_not_changed(self, alice, bob, web3_client): assert bob_balance_after == bob_balance_before @pytest.mark.multipletokens - def test_user_to_user_trx(self, web3_client_sol, alice, bob, - check_neon_balance_does_not_changed): + def test_user_to_user_trx(self, web3_client_sol, alice, bob, check_neon_balance_does_not_changed): bob_sol_balance_before = web3_client_sol.get_balance(bob) alice_sol_balance_before = web3_client_sol.get_balance(alice) value = 1000 @@ -50,9 +41,9 @@ def test_user_to_user_trx(self, web3_client_sol, alice, bob, assert bob_sol_balance_after < bob_sol_balance_before - value @pytest.mark.multipletokens - def test_user_to_contract_and_contract_to_user_trx(self, web3_client_sol, bob, - check_neon_balance_does_not_changed, - wsol): + def test_user_to_contract_and_contract_to_user_trx( + self, web3_client_sol, bob, check_neon_balance_does_not_changed, wsol + ): bob_sol_balance_before = web3_client_sol.get_balance(bob) contract_sol_balance_initial = web3_client_sol.get_balance(wsol.address) amount = 10 @@ -63,19 +54,18 @@ def test_user_to_contract_and_contract_to_user_trx(self, web3_client_sol, bob, bob_sol_balance_after_deposit = web3_client_sol.get_balance(bob) contract_sol_balance_after_deposit = web3_client_sol.get_balance(wsol.address) assert contract_sol_balance_after_deposit == contract_sol_balance_initial + web3_client_sol.to_main_currency( - amount) + amount + ) assert bob_sol_balance_after_deposit < bob_sol_balance_before - web3_client_sol.to_main_currency(amount) tx = self.make_contract_tx_object(bob.address, web3_client=web3_client_sol) - instruction_tx = wsol.functions.withdraw( - web3_client_sol.to_main_currency(10)).build_transaction(tx) + instruction_tx = wsol.functions.withdraw(web3_client_sol.to_main_currency(10)).build_transaction(tx) receipt = self.web3_client_sol.send_transaction(bob, instruction_tx) assert receipt["status"] == 1 bob_sol_balance_after_withdraw = web3_client_sol.get_balance(bob) contract_sol_balance_after_withdraw = web3_client_sol.get_balance(wsol.address) assert contract_sol_balance_after_withdraw == contract_sol_balance_initial - assert bob_sol_balance_after_withdraw < bob_sol_balance_after_deposit + \ - web3_client_sol.to_main_currency(amount) + assert bob_sol_balance_after_withdraw < bob_sol_balance_after_deposit + web3_client_sol.to_main_currency(amount) @pytest.mark.multipletokens def test_contract_to_contract_trx(self, web3_client_sol, bob): @@ -84,8 +74,11 @@ def test_contract_to_contract_trx(self, web3_client_sol, bob): value = web3_client_sol.to_main_currency(amount) bob_sol_balance_before = web3_client_sol.get_balance(bob) wsol_contract_caller, resp = web3_client_sol.deploy_and_get_contract( - contract="common/WNativeChainToken", version="0.8.12", - contract_name="WNativeChainTokenCaller", account=bob, value=value + contract="common/WNativeChainToken", + version="0.8.12", + contract_name="WNativeChainTokenCaller", + account=bob, + value=value, ) wrapper_address = wsol_contract_caller.events.Log().process_receipt(resp)[0].args["addr"] assert web3_client_sol.get_balance(wrapper_address) == value @@ -101,33 +94,36 @@ def test_contract_to_contract_trx(self, web3_client_sol, bob): assert bob_sol_balance_after < bob_sol_balance_before - value * 2 @pytest.mark.multipletokens - def test_user_to_contract_wrong_chain_id_trx(self, web3_client_sol, bob, - check_neon_balance_does_not_changed, - event_caller_contract): + def test_user_to_contract_wrong_chain_id_trx( + self, web3_client_sol, bob, check_neon_balance_does_not_changed, event_caller_contract + ): tx = self.make_contract_tx_object(bob.address, amount=1) instruction_tx = event_caller_contract.functions.unnamedArg("hello").build_transaction(tx) with pytest.raises(ValueError, match="wrong chain id"): self.web3_client_sol.send_transaction(bob, instruction_tx) @pytest.mark.multipletokens - def test_deploy_contract(self, web3_client_sol, alice, - check_neon_balance_does_not_changed): + def test_deploy_contract(self, web3_client_sol, alice, check_neon_balance_does_not_changed): sol_balance_before = web3_client_sol.get_balance(alice) contract, _ = web3_client_sol.deploy_and_get_contract( - contract="common/Common", version="0.8.12", - contract_name="Common", account=alice, + contract="common/Common", + version="0.8.12", + contract_name="Common", + account=alice, ) sol_balance_after = web3_client_sol.get_balance(alice) assert sol_balance_after < sol_balance_before @pytest.mark.multipletokens - def test_deploy_contract_with_sending_tokens(self, web3_client_sol, alice, - check_neon_balance_does_not_changed): + def test_deploy_contract_with_sending_tokens(self, web3_client_sol, alice, check_neon_balance_does_not_changed): sol_alice_balance_before = web3_client_sol.get_balance(alice) value = 1000 contract, receipt = web3_client_sol.deploy_and_get_contract( - contract="common/WNativeChainToken", version="0.8.12", - contract_name="WNativeChainToken", account=alice, value=value + contract="common/WNativeChainToken", + version="0.8.12", + contract_name="WNativeChainToken", + account=alice, + value=value, ) assert receipt["status"] == 1 sol_alice_balance_after = web3_client_sol.get_balance(alice) @@ -136,20 +132,18 @@ def test_deploy_contract_with_sending_tokens(self, web3_client_sol, alice, assert sol_alice_balance_after < sol_alice_balance_before - value @pytest.mark.multipletokens - def test_deploy_contract_by_one_user_to_different_chain(self, web3_client_sol, new_account, - solana_account, alice, - web3_client, pytestconfig): + def test_deploy_contract_by_one_user_to_different_chain( + self, web3_client_sol, new_account, solana_account, alice, web3_client, pytestconfig + ): def deploy_contract(w3_client): _, rcpt = w3_client.deploy_and_get_contract( - contract="common/Common", version="0.8.12", - contract_name="Common", account=new_account + contract="common/Common", version="0.8.12", contract_name="Common", account=new_account ) return rcpt - self.sol_client.deposit_wrapped_sol_from_solana_to_neon(solana_account, - new_account, - web3_client_sol.eth.chain_id, - pytestconfig.environment.evm_loader) + self.sol_client.deposit_wrapped_sol_from_solana_to_neon( + solana_account, new_account, web3_client_sol.eth.chain_id, pytestconfig.environment.evm_loader + ) deploy_contract(web3_client_sol) @@ -163,12 +157,11 @@ def deploy_contract(w3_client): assert receipt["status"] == 1 @pytest.mark.multipletokens - def test_interact_with_contract_from_another_chain(self, web3_client_sol, bob, - check_neon_balance_does_not_changed, - common_contract): + def test_interact_with_contract_from_another_chain( + self, web3_client_sol, bob, check_neon_balance_does_not_changed, common_contract + ): tx = self.make_contract_tx_object(bob.address, web3_client=web3_client_sol) - common_contract_sol_chain = web3_client_sol.get_deployed_contract( - common_contract.address, "common/Common") + common_contract_sol_chain = web3_client_sol.get_deployed_contract(common_contract.address, "common/Common") number = random.randint(0, 1000000) instruction_tx = common_contract_sol_chain.functions.setNumber(number).build_transaction(tx) @@ -186,8 +179,7 @@ def test_transfer_neons_in_sol_chain(self, web3_client_sol, web3_client, bob, al instruction_tx = wneon.functions.deposit().build_transaction(tx) self.web3_client.send_transaction(bob, instruction_tx) - wneon_sol_chain = web3_client_sol.get_deployed_contract( - wneon.address, "common/WNeon", "WNEON", "0.4.26") + wneon_sol_chain = web3_client_sol.get_deployed_contract(wneon.address, "common/WNeon", "WNEON", "0.4.26") tx = self.make_contract_tx_object(bob.address, web3_client=web3_client_sol) neon_balance_before = web3_client.get_balance(alice.address) @@ -212,8 +204,7 @@ def test_transfer_sol_in_neon_chain(self, web3_client_sol, web3_client, bob, ali instruction_tx = wsol.functions.deposit().build_transaction(tx) self.web3_client_sol.send_transaction(bob, instruction_tx) - wsol_neon_chain = web3_client.get_deployed_contract( - wsol.address, "common/WNativeChainToken") + wsol_neon_chain = web3_client.get_deployed_contract(wsol.address, "common/WNativeChainToken") tx = self.make_contract_tx_object(bob.address, web3_client=web3_client) sol_balance_before = web3_client_sol.get_balance(alice.address) @@ -229,36 +220,52 @@ def test_transfer_sol_in_neon_chain(self, web3_client_sol, web3_client, bob, ali assert web3_client_sol.get_balance(alice.address) == sol_balance_before + value @pytest.mark.multipletokens - def test_call_different_chains_contracts_in_one_transaction(self, alice, common_contract, - web3_client_sol, class_account_sol_chain): - bunch_contract_neon, _ = self.web3_client.deploy_and_get_contract( - contract="common/Common", version="0.8.12", - contract_name="BunchActions", account=alice + def test_call_different_chains_contracts_in_one_transaction( + self, + alice, + common_contract, + web3_client, + web3_client_sol, + web3_client_abc, + web3_client_def, + class_account_sol_chain, + ): + bunch_contract_neon, _ = web3_client.deploy_and_get_contract( + contract="common/Common", version="0.8.12", contract_name="BunchActions", account=alice ) - bunch_contract_sol = web3_client_sol.get_deployed_contract( - bunch_contract_neon.address, "common/Common", contract_name="BunchActions") - common_contract_sol, _ = web3_client_sol.deploy_and_get_contract( - contract="common/Common", - version="0.8.12", - account=class_account_sol_chain, - ) - - common_contract_neon = common_contract + chains = { + "neon": {"client": web3_client}, + "sol": {"client": web3_client_sol}, + "abc": {"client": web3_client_abc}, + "def": {"client": web3_client_def}, + } + + for chain in chains: + bunch_contract = chains[chain]["client"].get_deployed_contract( + bunch_contract_neon.address, "common/Common", contract_name="BunchActions" + ) + chains[chain]["bunch_contract"] = bunch_contract + make_nonce_the_biggest_for_chain( + alice, chains[chain]["client"], [item["client"] for item in chains.values()] + ) - tx = self.make_contract_tx_object(alice.address) - instruction_tx = bunch_contract_neon.functions.setNumber([common_contract_sol.address, - common_contract_neon.address], - [1, 2]).build_transaction(tx) - receipt = self.web3_client.send_transaction(alice, instruction_tx) - assert receipt["status"] == 1 - assert common_contract_sol.functions.getNumber().call() == 1 - assert common_contract_neon.functions.getNumber().call() == 2 + common_contract, _ = chains[chain]["client"].deploy_and_get_contract( + contract="common/Common", + version="0.8.12", + account=alice, + ) + chains[chain]["common_contract"] = common_contract + + for chain in chains: + tx = self.make_contract_tx_object(alice.address, web3_client=chains[chain]["client"]) + numbers = [random.randint(0, 1000000) for i in range(len(chains))] + instruction_tx = ( + chains[chain]["bunch_contract"] + .functions.setNumber([item["common_contract"].address for item in chains.values()], numbers) + .build_transaction(tx) + ) + receipt = chains[chain]["client"].send_transaction(alice, instruction_tx) + assert receipt["status"] == 1 - tx = self.make_contract_tx_object(alice.address, web3_client=web3_client_sol) - instruction_tx = bunch_contract_sol.functions.setNumber([common_contract_sol.address, - common_contract_neon.address], - [3, 4]).build_transaction(tx) - receipt = self.web3_client_sol.send_transaction(alice, instruction_tx) - assert receipt["status"] == 1 - assert common_contract_sol.functions.getNumber().call() == 3 - assert common_contract_neon.functions.getNumber().call() == 4 + for i, item in enumerate(chains.values()): + assert item["common_contract"].functions.getNumber().call() == numbers[i] diff --git a/integration/tests/conftest.py b/integration/tests/conftest.py index 6f9b3116ec..c7ed8dbdb2 100644 --- a/integration/tests/conftest.py +++ b/integration/tests/conftest.py @@ -105,14 +105,14 @@ def prepare_account(operator, faucet, web3_client: NeonChainWeb3Client): start_neon_balance = operator.get_neon_balance() start_sol_balance = operator.get_solana_balance() with allure.step( - f"Operator initial balance: {start_neon_balance / LAMPORT_PER_SOL} NEON {start_sol_balance / LAMPORT_PER_SOL} SOL" + f"Operator initial balance: {start_neon_balance / LAMPORT_PER_SOL} NEON {start_sol_balance / LAMPORT_PER_SOL} SOL" ): pass yield acc end_neon_balance = operator.get_neon_balance() end_sol_balance = operator.get_solana_balance() with allure.step( - f"Operator end balance: {end_neon_balance / LAMPORT_PER_SOL} NEON {end_sol_balance / LAMPORT_PER_SOL} SOL" + f"Operator end balance: {end_neon_balance / LAMPORT_PER_SOL} NEON {end_sol_balance / LAMPORT_PER_SOL} SOL" ): pass with allure.step(f"Account end balance: {web3_client.get_balance(acc)} NEON"): @@ -155,11 +155,11 @@ def solana_account(bank_account, pytestconfig: Config, sol_client): @pytest.fixture(scope="session") def erc20_spl( - web3_client: NeonChainWeb3Client, - faucet, - pytestconfig: Config, - sol_client, - solana_account, + web3_client: NeonChainWeb3Client, + faucet, + pytestconfig: Config, + sol_client, + solana_account, ): symbol = "".join([random.choice(string.ascii_uppercase) for _ in range(3)]) erc20 = ERC20Wrapper( @@ -224,7 +224,7 @@ def class_account(web3_client, faucet, eth_bank_account, solana_account, sol_cli @pytest.fixture(scope="class") def class_account_sol_chain( - sol_client, solana_account, web3_client, web3_client_sol, pytestconfig, faucet, eth_bank_account + sol_client, solana_account, web3_client, web3_client_sol, pytestconfig, faucet, eth_bank_account ): account = web3_client.create_account_with_balance(faucet, bank_account=eth_bank_account) sol_client.request_airdrop(solana_account.public_key, 1 * LAMPORT_PER_SOL) @@ -234,6 +234,27 @@ def class_account_sol_chain( return account +@pytest.fixture(scope="class") +def account_with_all_tokens( + sol_client, solana_account, web3_client, web3_client_abc, web3_client_def, web3_client_sol, pytestconfig, faucet, + eth_bank_account, neon_mint, operator_keypair, evm_loader_keypair +): + account = web3_client.create_account_with_balance(faucet, bank_account=eth_bank_account) + sol_client.request_airdrop(solana_account.public_key, 1 * LAMPORT_PER_SOL) + sol_client.deposit_wrapped_sol_from_solana_to_neon( + solana_account, account, web3_client_sol.eth.chain_id, pytestconfig.environment.evm_loader, 1 * LAMPORT_PER_SOL + ) + for client in [ web3_client_abc, web3_client_def]: + new_sol_account = Keypair.generate() + sol_client.send_sol(solana_account, new_sol_account.public_key, 5000000) + sol_client.deposit_neon_like_tokens_from_solana_to_neon(neon_mint, new_sol_account, account, + client.eth.chain_id, + operator_keypair, + evm_loader_keypair, + pytestconfig.environment.evm_loader, 1000000000000000000) + return account + + @pytest.fixture(scope="function") def new_account_zero_balance(web3_client): new_acc = web3_client.create_account() diff --git a/utils/solana_client.py b/utils/solana_client.py index abafa7a7cb..7895c56001 100644 --- a/utils/solana_client.py +++ b/utils/solana_client.py @@ -13,12 +13,13 @@ from solders.rpc.errors import InternalErrorMessage from solders.rpc.responses import RequestAirdropResp from spl.token.instructions import get_associated_token_address, create_associated_token_account, mint_to, MintToParams +from spl.token.client import Token as SplToken from utils.consts import LAMPORT_PER_SOL, wSOL from utils.helpers import wait_condition from spl.token.constants import TOKEN_PROGRAM_ID -from utils.transfers_inter_networks import wSOL_tx, token_from_solana_to_neon_tx +from utils.transfers_inter_networks import wSOL_tx, token_from_solana_to_neon_tx, mint_tx class SolanaClient(solana.rpc.api.Client): @@ -29,10 +30,10 @@ def __init__(self, endpoint, account_seed_version="\3"): ) def request_airdrop( - self, - pubkey: PublicKey, - lamports: int, - commitment: tp.Optional[Commitment] = None, + self, + pubkey: PublicKey, + lamports: int, + commitment: tp.Optional[Commitment] = None, ) -> RequestAirdropResp: airdrop_resp = None for _ in range(5): @@ -113,9 +114,8 @@ def create_ata(self, solana_account, neon_mint): opts = TxOpts(skip_preflight=True, skip_confirmation=False) self.send_transaction(trx, solana_account, opts=opts) - def deposit_wrapped_sol_from_solana_to_neon( - self, solana_account, neon_account, chain_id, evm_loader_id, full_amount=None + self, solana_account, neon_account, chain_id, evm_loader_id, full_amount=None ): if not full_amount: full_amount = int(0.1 * LAMPORT_PER_SOL) @@ -135,3 +135,32 @@ def deposit_wrapped_sol_from_solana_to_neon( ) self.send_tx_and_check_status_ok(tx, solana_account) + + + def deposit_neon_like_tokens_from_solana_to_neon(self, neon_mint, solana_account, neon_account, chain_id, + operator_keypair, evm_loader_keypair, evm_loader_id, amount): + balance_pubkey = self.ether2balance(neon_account.address, chain_id, evm_loader_id) + + spl_neon_token = SplToken(self, neon_mint, TOKEN_PROGRAM_ID, payer=operator_keypair) + associated_token_address = spl_neon_token.create_associated_token_account(solana_account.public_key) + + tx = mint_tx( + amount=amount, + dest=associated_token_address, + neon_mint=neon_mint, + mint_authority=evm_loader_keypair.public_key, + ) + tx.fee_payer = operator_keypair.public_key + + self.send_tx_and_check_status_ok(tx, operator_keypair, evm_loader_keypair) + + tx = token_from_solana_to_neon_tx( + solana_account, + balance_pubkey, + neon_mint, + neon_account, + amount, + evm_loader_id, + chain_id, + ) + self.send_tx_and_check_status_ok(tx, solana_account) diff --git a/utils/web3client.py b/utils/web3client.py index e044088cfe..dc7d53c761 100644 --- a/utils/web3client.py +++ b/utils/web3client.py @@ -333,3 +333,11 @@ def __init__(self, proxy_url: str): @staticmethod def to_main_currency(amount): return amount * 1_000_000_000 + +class NeonLikeChainWeb3Client(Web3Client): + def __init__(self, proxy_url: str, prefix: str): + super().__init__(f"{proxy_url}/{prefix}") + + @staticmethod + def to_main_currency(amount): + return web3.Web3.to_wei(amount, "ether") From dd6379a87139cd244fc60c65b7aeb29fe8915bdf Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Tue, 14 Nov 2023 18:34:15 +0100 Subject: [PATCH 18/56] Update conftest.py --- conftest.py | 56 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/conftest.py b/conftest.py index 284a218515..5fad18b04c 100644 --- a/conftest.py +++ b/conftest.py @@ -8,9 +8,10 @@ import pytest from _pytest.config import Config from _pytest.runner import runtestprotocol +from solana.keypair import Keypair from clickfile import create_allure_environment_opts -from utils.web3client import NeonChainWeb3Client, SolChainWeb3Client +from utils.web3client import NeonChainWeb3Client, SolChainWeb3Client, Web3Client, NeonLikeChainWeb3Client pytest_plugins = ["ui.plugins.browser"] @@ -35,18 +36,14 @@ class EnvironmentConfig: def pytest_addoption(parser): - parser.addoption( - "--network", action="store", default="night-stand", help="Which stand use" - ) + parser.addoption("--network", action="store", default="local", help="Which stand use") parser.addoption( "--make-report", action="store_true", default=False, help="Store tests result to file", ) - parser.addoption( - "--envs", action="store", default="envs.json", help="Filename with environments" - ) + parser.addoption("--envs", action="store", default="envs.json", help="Filename with environments") def pytest_sessionstart(session): @@ -76,9 +73,7 @@ def pytest_configure(config: Config): envs_file = config.getoption("--envs") with open(pathlib.Path().parent.parent / envs_file, "r+") as f: environments = json.load(f) - assert ( - network_name in environments - ), f"Environment {network_name} doesn't exist in envs.json" + assert network_name in environments, f"Environment {network_name} doesn't exist in envs.json" env = environments[network_name] if network_name == "devnet": for solana_env_var in solana_url_env_vars: @@ -92,18 +87,26 @@ def pytest_configure(config: Config): if "eth_bank_account" not in env: env["eth_bank_account"] = "" if network_name == "aws": - env["solana_url"] = env["solana_url"].replace( - "", os.environ.get("SOLANA_IP") - ) - env["proxy_url"] = env["proxy_url"].replace( - "", os.environ.get("PROXY_IP") - ) - env["faucet_url"] = env["faucet_url"].replace( - "", os.environ.get("PROXY_IP") - ) + env["solana_url"] = env["solana_url"].replace("", os.environ.get("SOLANA_IP")) + env["proxy_url"] = env["proxy_url"].replace("", os.environ.get("PROXY_IP")) + env["faucet_url"] = env["faucet_url"].replace("", os.environ.get("PROXY_IP")) config.environment = EnvironmentConfig(**env) +@pytest.fixture(scope="session") +def operator_keypair(): + with open("operator-keypair.json", "r") as key: + secret_key = json.load(key)[:32] + return Keypair.from_secret_key(secret_key) + + +@pytest.fixture(scope="session") +def evm_loader_keypair(): + with open("evm_loader-keypair.json", "r") as key: + secret_key = json.load(key)[:32] + return Keypair.from_secret_key(secret_key) + + @pytest.fixture(scope="session", autouse=True) def allure_environment(pytestconfig: Config, web3_client: NeonChainWeb3Client): opts = {} @@ -156,3 +159,18 @@ def web3_client_sol(pytestconfig: Config) -> SolChainWeb3Client: pytestconfig.environment.proxy_url, ) return client + + +@pytest.fixture(scope="session", autouse=True) +def web3_client_abc(pytestconfig: Config) -> Web3Client: + client = NeonLikeChainWeb3Client( + pytestconfig.environment.proxy_url, + "abc", + ) + return client + + +@pytest.fixture(scope="session", autouse=True) +def web3_client_def(pytestconfig: Config) -> Web3Client: + client = NeonLikeChainWeb3Client(pytestconfig.environment.proxy_url, "def") + return client From 3e3da0e08109840c267fb71b0a1a3c8f0c7faade Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Tue, 14 Nov 2023 18:52:43 +0100 Subject: [PATCH 19/56] added contract_pubkey to instructions --- envs.json | 10 +- .../tests/basic/rpc/test_rpc_base_calls.py | 1 - integration/tests/basic/test_deposit.py | 15 +- utils/instructions.py | 6 +- utils/solana_client.py | 44 ++++-- utils/transfers_inter_networks.py | 135 ++++++++++-------- 6 files changed, 126 insertions(+), 85 deletions(-) diff --git a/envs.json b/envs.json index 00028101e0..8d52050eaa 100644 --- a/envs.json +++ b/envs.json @@ -135,7 +135,10 @@ "evm_loader": "53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io", "proxy_url": "http://127.0.0.1:9090/solana", "network_ids": { - "neon": 111 + "neon": 111, + "sol": 112, + "abc": 113, + "def": 114 }, "solana_url": "http://127.0.0.1:8899/", "faucet_url": "http://127.0.0.1:3333/", @@ -211,7 +214,10 @@ "evm_loader": "53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io", "proxy_url": "http://:9090/solana", "network_ids": { - "neon": 111 + "neon": 111, + "sol": 112, + "abc": 113, + "def": 114 }, "solana_url": "http://:8899/", "faucet_url": "http://:3333/", diff --git a/integration/tests/basic/rpc/test_rpc_base_calls.py b/integration/tests/basic/rpc/test_rpc_base_calls.py index 4e762be744..41b962580b 100644 --- a/integration/tests/basic/rpc/test_rpc_base_calls.py +++ b/integration/tests/basic/rpc/test_rpc_base_calls.py @@ -337,7 +337,6 @@ def test_get_evm_params(self): "NEON_GAS_LIMIT_MULTIPLIER_NO_CHAINID", "NEON_HOLDER_MSG_SIZE", "NEON_OPERATOR_PRIORITY_SLOTS", - "NEON_PAYMENT_TO_DEPOSIT", "NEON_PAYMENT_TO_TREASURE", "NEON_STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT", "NEON_TREASURY_POOL_COUNT", diff --git a/integration/tests/basic/test_deposit.py b/integration/tests/basic/test_deposit.py index 47179809ea..2e0c0c507d 100644 --- a/integration/tests/basic/test_deposit.py +++ b/integration/tests/basic/test_deposit.py @@ -42,18 +42,14 @@ def test_transfer_neon_from_solana_to_neon(self, new_account, solana_account, py full_amount = self.web3_client.to_main_currency(amount) evm_loader_id = pytestconfig.environment.evm_loader - balance_pubkey = self.sol_client.ether2balance( - new_account.address, self.web3_client.eth.chain_id, evm_loader_id - ) - neon_balance_before = self.get_balance_from_wei(new_account.address) self.sol_client.create_ata(solana_account, neon_mint) self.withdraw_neon(solana_account, amount) # insufficient funds tx = token_from_solana_to_neon_tx( + self.sol_client, solana_account, - balance_pubkey, neon_mint, new_account, full_amount, @@ -76,8 +72,11 @@ def test_create_and_transfer_new_token_from_solana_to_neon( operator_keypair, evm_loader_keypair, ): - amount = 100000000 + amount = 5000000 evm_loader_id = pytestconfig.environment.evm_loader + new_sol_account = Keypair.generate() + self.sol_client.send_sol(solana_account, new_sol_account.public_key, amount) + self.sol_client.deposit_neon_like_tokens_from_solana_to_neon(neon_mint, solana_account, new_account, web3_client_abc.eth.chain_id, operator_keypair, evm_loader_keypair, evm_loader_id, amount) @@ -133,11 +132,9 @@ def test_transfer_wrapped_sol_token_from_solana_to_neon( wrap_sol_tx = wSOL_tx(wSOL_account, wSOL, full_amount, solana_account.public_key, ata_address) self.sol_client.send_tx_and_check_status_ok(wrap_sol_tx, solana_account) - balance_pubkey = self.sol_client.ether2balance(new_account.address, web3_client_sol.eth.chain_id, evm_loader_id) - tx = token_from_solana_to_neon_tx( + self.sol_client, solana_account, - balance_pubkey, wSOL["address_spl"], new_account, full_amount, diff --git a/utils/instructions.py b/utils/instructions.py index ce155373f7..a9e79d6964 100644 --- a/utils/instructions.py +++ b/utils/instructions.py @@ -15,7 +15,7 @@ class Instruction: @staticmethod - def balance_account(solana_wallet, account_pubkey, + def balance_account(solana_wallet, account_pubkey, contract_pubkey, neon_wallet, evm_loader_id, chain_id) -> TransactionInstruction: keys = [ @@ -25,6 +25,8 @@ def balance_account(solana_wallet, account_pubkey, is_signer=False, is_writable=False), AccountMeta(pubkey=account_pubkey, is_signer=False, is_writable=True), + AccountMeta(pubkey=contract_pubkey, + is_signer=False, is_writable=True), ] data = bytes.fromhex('2D') + bytes.fromhex(str(neon_wallet)[2:]) + chain_id.to_bytes(8, 'little') @@ -44,6 +46,7 @@ def deposit( ether_address: bytes, chain_id: int, balance_account: PublicKey, + contract_account: PublicKey, mint: PublicKey, source: PublicKey, pool: PublicKey, @@ -56,6 +59,7 @@ def deposit( AccountMeta(pubkey=source, is_signer=False, is_writable=True), AccountMeta(pubkey=pool, is_signer=False, is_writable=True), AccountMeta(pubkey=balance_account, is_signer=False, is_writable=True), + AccountMeta(pubkey=contract_account, is_signer=False, is_writable=True), AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), AccountMeta(pubkey=operator_pubkey, is_signer=True, is_writable=True), AccountMeta(pubkey=SYS_PROGRAM_ID, is_signer=False, is_writable=False), diff --git a/utils/solana_client.py b/utils/solana_client.py index 7895c56001..62e5d3c342 100644 --- a/utils/solana_client.py +++ b/utils/solana_client.py @@ -30,10 +30,10 @@ def __init__(self, endpoint, account_seed_version="\3"): ) def request_airdrop( - self, - pubkey: PublicKey, - lamports: int, - commitment: tp.Optional[Commitment] = None, + self, + pubkey: PublicKey, + lamports: int, + commitment: tp.Optional[Commitment] = None, ) -> RequestAirdropResp: airdrop_resp = None for _ in range(5): @@ -69,6 +69,20 @@ def ether2balance(self, address: tp.Union[str, bytes], chain_id: int, evm_loader [self.account_seed_version, address_bytes, chain_id_bytes], PublicKey(evm_loader_id) )[0] + @staticmethod + def ether2bytes(ether: tp.Union[str, bytes]): + if isinstance(ether, str): + if ether.startswith("0x"): + return bytes.fromhex(ether[2:]) + return bytes.fromhex(ether) + return ether + + def ether2program(self, ether: tp.Union[str, bytes], evm_loader_id: str) -> tp.Tuple[str, int]: + items = PublicKey.find_program_address( + [self.account_seed_version, self.ether2bytes(ether)], PublicKey(evm_loader_id) + ) + return str(items[0]), items[1] + def get_erc_auth_address(self, neon_account_address: str, token_address: str, evm_loader_id: str): neon_account_addressbytes = bytes(12) + bytes.fromhex(neon_account_address[2:]) if token_address.startswith("0x"): @@ -115,7 +129,7 @@ def create_ata(self, solana_account, neon_mint): self.send_transaction(trx, solana_account, opts=opts) def deposit_wrapped_sol_from_solana_to_neon( - self, solana_account, neon_account, chain_id, evm_loader_id, full_amount=None + self, solana_account, neon_account, chain_id, evm_loader_id, full_amount=None ): if not full_amount: full_amount = int(0.1 * LAMPORT_PER_SOL) @@ -129,17 +143,23 @@ def deposit_wrapped_sol_from_solana_to_neon( wrap_sol_tx = wSOL_tx(wSOL_account, wSOL, full_amount, solana_account.public_key, ata_address) self.send_tx_and_check_status_ok(wrap_sol_tx, solana_account) - balance_pubkey = self.ether2balance(neon_account.address, chain_id, evm_loader_id) tx = token_from_solana_to_neon_tx( - solana_account, balance_pubkey, wSOL["address_spl"], neon_account, full_amount, evm_loader_id, chain_id + self, solana_account, wSOL["address_spl"], neon_account, full_amount, evm_loader_id, chain_id ) self.send_tx_and_check_status_ok(tx, solana_account) - - def deposit_neon_like_tokens_from_solana_to_neon(self, neon_mint, solana_account, neon_account, chain_id, - operator_keypair, evm_loader_keypair, evm_loader_id, amount): - balance_pubkey = self.ether2balance(neon_account.address, chain_id, evm_loader_id) + def deposit_neon_like_tokens_from_solana_to_neon( + self, + neon_mint, + solana_account, + neon_account, + chain_id, + operator_keypair, + evm_loader_keypair, + evm_loader_id, + amount, + ): spl_neon_token = SplToken(self, neon_mint, TOKEN_PROGRAM_ID, payer=operator_keypair) associated_token_address = spl_neon_token.create_associated_token_account(solana_account.public_key) @@ -155,8 +175,8 @@ def deposit_neon_like_tokens_from_solana_to_neon(self, neon_mint, solana_account self.send_tx_and_check_status_ok(tx, operator_keypair, evm_loader_keypair) tx = token_from_solana_to_neon_tx( + self, solana_account, - balance_pubkey, neon_mint, neon_account, amount, diff --git a/utils/transfers_inter_networks.py b/utils/transfers_inter_networks.py index a0fb6525bf..8ce960dd84 100644 --- a/utils/transfers_inter_networks.py +++ b/utils/transfers_inter_networks.py @@ -2,49 +2,59 @@ from solana.system_program import TransferParams, transfer from solana.transaction import Transaction from spl.token.constants import TOKEN_PROGRAM_ID -from spl.token.instructions import (ApproveParams, approve, - get_associated_token_address, MintToParams, mint_to) +from spl.token.instructions import ApproveParams, approve, get_associated_token_address, MintToParams, mint_to from utils.instructions import Instruction, get_solana_wallet_signer -def token_from_solana_to_neon_tx(solana_account, neon_wallet, mint, neon_account, - amount, evm_loader_id, chain_id): - '''Transfer any token from solana to neon transaction''' +def token_from_solana_to_neon_tx(sol_client, solana_account, mint, neon_account, amount, evm_loader_id, chain_id): + """Transfer any token from solana to neon transaction""" + balance_pubkey = sol_client.ether2balance(neon_account.address, chain_id, evm_loader_id) + contract_pubkey = PublicKey(sol_client.ether2program(neon_account.address, evm_loader_id)[0]) + tx = Transaction(fee_payer=solana_account.public_key) - associated_token_address = get_associated_token_address( - solana_account.public_key, mint) - tx.add(approve( - ApproveParams( - program_id=TOKEN_PROGRAM_ID, - source=associated_token_address, - delegate=neon_wallet, - owner=solana_account.public_key, - amount=amount))) - - authority_pool = get_authority_pool_address( - evm_loader_id) + associated_token_address = get_associated_token_address(solana_account.public_key, mint) + tx.add( + approve( + ApproveParams( + program_id=TOKEN_PROGRAM_ID, + source=associated_token_address, + delegate=balance_pubkey, + owner=solana_account.public_key, + amount=amount, + ) + ) + ) + + authority_pool = get_authority_pool_address(evm_loader_id) pool = get_associated_token_address(authority_pool, mint) - tx.add(Instruction.deposit( - bytes.fromhex(neon_account.address[2:]), - chain_id, - neon_wallet, - mint, - associated_token_address, - pool, - solana_account.public_key, - evm_loader_id)) + tx.add( + Instruction.deposit( + bytes.fromhex(neon_account.address[2:]), + chain_id, + balance_pubkey, + contract_pubkey, + mint, + associated_token_address, + pool, + solana_account.public_key, + evm_loader_id, + ) + ) return tx def wSOL_tx(wSOL_account, spl_token, amount, solana_wallet, ata_address): - mint_pubkey = PublicKey(spl_token['address_spl']) + mint_pubkey = PublicKey(spl_token["address_spl"]) tx = Transaction(fee_payer=solana_wallet) - if (wSOL_account is None): - tx.add(Instruction.associated_token_account( - solana_wallet, ata_address, solana_wallet, mint_pubkey, instruction_data=bytes(0))) + if wSOL_account is None: + tx.add( + Instruction.associated_token_account( + solana_wallet, ata_address, solana_wallet, mint_pubkey, instruction_data=bytes(0) + ) + ) tx.add(transfer(TransferParams(solana_wallet, ata_address, amount))) tx.add(Instruction.sync_native(ata_address)) @@ -60,56 +70,61 @@ def mint_tx(amount, dest, neon_mint, mint_authority): return trx -def neon_transfer_tx(web3_client, sol_client, amount, spl_token, solana_account, - neon_account, erc20_spl, evm_loader_id): +def neon_transfer_tx( + web3_client, sol_client, amount, spl_token, solana_account, neon_account, erc20_spl, evm_loader_id +): chain_id = web3_client.eth.chain_id delegate_pda = sol_client.ether2balance(neon_account.address, chain_id, evm_loader_id) + contract_pubkey = PublicKey(sol_client.ether2program(neon_account.address, evm_loader_id)[0]) - emulate_signer = get_solana_wallet_signer( - solana_account, neon_account, web3_client) + emulate_signer = get_solana_wallet_signer(solana_account, neon_account, web3_client) emulated_signer_pda = sol_client.ether2balance(emulate_signer.address, chain_id, evm_loader_id) + emulated_contract_pubkey = PublicKey(sol_client.ether2program(emulate_signer.address, evm_loader_id)[0]) solana_wallet = solana_account.public_key - ata_address = get_associated_token_address( - solana_wallet, PublicKey(spl_token['address_spl'])) + ata_address = get_associated_token_address(solana_wallet, PublicKey(spl_token["address_spl"])) - neon_transaction, neon_keys = Instruction.claim(neon_account, - spl_token['address'], - amount, - web3_client, - ata_address, - emulate_signer, - erc20_spl) + neon_transaction, neon_keys = Instruction.claim( + neon_account, spl_token["address"], amount, web3_client, ata_address, emulate_signer, erc20_spl + ) tx = Transaction(fee_payer=solana_wallet) - compute_budget_instruction = Instruction.compute_budget_utils( - solana_account) + compute_budget_instruction = Instruction.compute_budget_utils(solana_account) tx.add(compute_budget_instruction) heap_frame_instruction = Instruction.request_heap_frame(solana_account) tx.add(heap_frame_instruction) - tx.add(approve( - ApproveParams( - program_id=TOKEN_PROGRAM_ID, - source=ata_address, - delegate=delegate_pda, - owner=solana_account.public_key, - amount=amount))) + tx.add( + approve( + ApproveParams( + program_id=TOKEN_PROGRAM_ID, + source=ata_address, + delegate=delegate_pda, + owner=solana_account.public_key, + amount=amount, + ) + ) + ) - tx.add(Instruction.balance_account(solana_wallet, delegate_pda, - neon_account.address, evm_loader_id, chain_id)) + tx.add( + Instruction.balance_account(solana_wallet, delegate_pda, contract_pubkey, neon_account.address, evm_loader_id, + chain_id)) - tx.add(Instruction.balance_account(solana_wallet, emulated_signer_pda, - emulate_signer.address, evm_loader_id, chain_id)) + tx.add( + Instruction.balance_account(solana_wallet, emulated_signer_pda, emulated_contract_pubkey, + emulate_signer.address, evm_loader_id, chain_id) + ) - tx.add(Instruction.build_tx_instruction(solana_wallet, delegate_pda, - neon_transaction.rawTransaction, neon_keys, - evm_loader_id)) + tx.add( + Instruction.build_tx_instruction( + solana_wallet, delegate_pda, neon_transaction.rawTransaction, neon_keys, evm_loader_id + ) + ) return tx def get_authority_pool_address(evm_loader_id: str): - text = 'Deposit' + text = "Deposit" return PublicKey.find_program_address([text.encode()], PublicKey(evm_loader_id))[0] From fa2c90b4cba22fe96663fad838de80a01bad714c Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Wed, 15 Nov 2023 12:42:16 +0100 Subject: [PATCH 20/56] fix tests --- .../basic/evm/opcodes/test_selfdestruct.py | 27 ++++---- integration/tests/basic/test_deposit.py | 62 ++++++++++--------- .../basic/test_payment_in_different_tokens.py | 24 ++++--- integration/tests/conftest.py | 2 +- pyproject.toml | 3 +- 5 files changed, 60 insertions(+), 58 deletions(-) diff --git a/integration/tests/basic/evm/opcodes/test_selfdestruct.py b/integration/tests/basic/evm/opcodes/test_selfdestruct.py index d73aa4a712..4299e414fa 100644 --- a/integration/tests/basic/evm/opcodes/test_selfdestruct.py +++ b/integration/tests/basic/evm/opcodes/test_selfdestruct.py @@ -35,12 +35,12 @@ def destroy(self, destroyable_contract, sender, funds_recipient, amount=None): receipt = self.web3_client.send_transaction(sender, instruction_tx) assert receipt["status"] == 1 - def check_contract_code_is_empty(self, contract_address): + def check_contract_code_is_not_empty(self, contract_address): response = self.proxy_api.send_rpc( "eth_getCode", params=[contract_address, "latest"] ) - assert response["result"] == "0x" + assert response["result"] != "0x" def test_destroy(self, destroyable_contract): self.deposit(destroyable_contract, self.sender_account, 1) @@ -59,10 +59,7 @@ def test_destroy(self, destroyable_contract): instruction_tx = destroyable_contract.functions.anyFunction().build_transaction(tx) receipt = self.web3_client.send_transaction(self.sender_account, instruction_tx) assert receipt["status"] == 1 - - event_logs = destroyable_contract.events.FunctionCalled().process_receipt(receipt) - assert event_logs == () - self.check_contract_code_is_empty(destroyable_contract.address) + self.check_contract_code_is_not_empty(destroyable_contract.address) def test_destroy_contract_with_contract_address_as_target(self, destroyable_contract): self.deposit(destroyable_contract, self.recipient_account, 1) @@ -72,7 +69,7 @@ def test_destroy_contract_with_contract_address_as_target(self, destroyable_cont contract_balance_after = self.get_balance_from_wei(destroyable_contract.address) assert contract_balance_after == contract_balance_before - self.check_contract_code_is_empty(destroyable_contract.address) + self.check_contract_code_is_not_empty(destroyable_contract.address) def test_destroy_contract_and_sent_neons_to_contract(self, destroyable_contract): self.deposit(destroyable_contract, self.sender_account, 1) @@ -84,7 +81,7 @@ def test_destroy_contract_and_sent_neons_to_contract(self, destroyable_contract) contract_balance_after = self.get_balance_from_wei(destroyable_contract.address) assert contract_balance_after == amount - self.check_contract_code_is_empty(destroyable_contract.address) + self.check_contract_code_is_not_empty(destroyable_contract.address) def test_destroy_contract_by_call_from_second_contract(self, destroyable_contract, contract_caller): self.deposit(destroyable_contract, self.sender_account, 2) @@ -96,7 +93,7 @@ def test_destroy_contract_by_call_from_second_contract(self, destroyable_contrac recipient_balance_after = self.get_balance_from_wei(self.recipient_account.address) assert receipt["status"] == 1 assert recipient_balance_after - recipient_balance_before == 2 - self.check_contract_code_is_empty(destroyable_contract.address) + self.check_contract_code_is_not_empty(destroyable_contract.address) def test_destroy_contract_and_sent_neon_from_contract_in_one_trx(self, destroyable_contract, contract_caller): self.deposit(destroyable_contract, self.sender_account, 2) @@ -108,7 +105,7 @@ def test_destroy_contract_and_sent_neon_from_contract_in_one_trx(self, destroyab recipient_balance_after = self.get_balance_from_wei(self.recipient_account.address) assert receipt["status"] == 1 assert recipient_balance_after - recipient_balance_before == 2 - self.check_contract_code_is_empty(destroyable_contract.address) + self.check_contract_code_is_not_empty(destroyable_contract.address) def test_sent_neon_from_contract_and_destroy_contract_in_one_trx(self, destroyable_contract, contract_caller): self.deposit(destroyable_contract, self.sender_account, 2) @@ -125,7 +122,7 @@ def test_sent_neon_from_contract_and_destroy_contract_in_one_trx(self, destroyab assert self.get_balance_from_wei(destroyable_contract.address) == 0 assert recipient_balance_after - recipient_balance_before == 2 - self.check_contract_code_is_empty(destroyable_contract.address) + self.check_contract_code_is_not_empty(destroyable_contract.address) def test_destroy_contract_and_sent_neon_to_contract_in_one_trx(self, destroyable_contract): self.deposit(destroyable_contract, self.recipient_account, 1) @@ -134,7 +131,7 @@ def test_destroy_contract_and_sent_neon_to_contract_in_one_trx(self, destroyable self.destroy(destroyable_contract, self.sender_account, self.recipient_account, amount=3) balance_after = self.get_balance_from_wei(self.recipient_account.address) assert balance_after - balance_before == 4 - self.check_contract_code_is_empty(destroyable_contract.address) + self.check_contract_code_is_not_empty(destroyable_contract.address) def test_destroy_contract_2_times_in_one_trx(self, destroyable_contract, contract_caller): self.deposit(destroyable_contract, self.recipient_account, 1) @@ -142,7 +139,7 @@ def test_destroy_contract_2_times_in_one_trx(self, destroyable_contract, contrac instruction_tx = contract_caller.functions.callDestroyTwice(self.sender_account.address).build_transaction(tx) receipt = self.web3_client.send_transaction(self.sender_account, instruction_tx) assert receipt["status"] == 1 - self.check_contract_code_is_empty(destroyable_contract.address) + self.check_contract_code_is_not_empty(destroyable_contract.address) def test_destroy_contract_via_delegatecall(self, destroyable_contract, contract_caller): # contract_caller should be destroyed instead of destroyable_contract @@ -153,7 +150,7 @@ def test_destroy_contract_via_delegatecall(self, destroyable_contract, contract_ assert receipt["status"] == 1 assert self.web3_client.eth.get_code(destroyable_contract.address) != "0x" - self.check_contract_code_is_empty(contract_caller.address) + self.check_contract_code_is_not_empty(contract_caller.address) def test_destroy_contract_via_delegatecall_and_create_new_contract(self, destroyable_contract, contract_caller): tx = self.create_contract_call_tx_object(self.sender_account) @@ -163,4 +160,4 @@ def test_destroy_contract_via_delegatecall_and_create_new_contract(self, destroy assert receipt["status"] == 1 assert self.web3_client.eth.get_code(destroyable_contract.address) != "0x" - self.check_contract_code_is_empty(contract_caller.address) + self.check_contract_code_is_not_empty(contract_caller.address) diff --git a/integration/tests/basic/test_deposit.py b/integration/tests/basic/test_deposit.py index 2e0c0c507d..302a3efe6d 100644 --- a/integration/tests/basic/test_deposit.py +++ b/integration/tests/basic/test_deposit.py @@ -31,22 +31,20 @@ def withdraw_neon(self, dest_acc, move_amount): "precompiled/NeonToken", "0.8.10", account=self.sender_account ) tx = self.create_contract_call_tx_object(amount=move_amount) - instruction_tx = contract.functions.withdraw(bytes(dest_acc.public_key)).build_transaction(tx) receipt = self.web3_client.send_transaction(self.sender_account, instruction_tx) assert receipt["status"] == 1 def test_transfer_neon_from_solana_to_neon(self, new_account, solana_account, pytestconfig: Config, neon_mint): """Transfer Neon from Solana -> Neon""" - amount = 1 - full_amount = self.web3_client.to_main_currency(amount) + amount = 0.1 + full_amount = int(amount * LAMPORT_PER_SOL) evm_loader_id = pytestconfig.environment.evm_loader neon_balance_before = self.get_balance_from_wei(new_account.address) self.sol_client.create_ata(solana_account, neon_mint) - self.withdraw_neon(solana_account, amount) # insufficient funds - + self.withdraw_neon(solana_account, amount) tx = token_from_solana_to_neon_tx( self.sol_client, solana_account, @@ -63,23 +61,30 @@ def test_transfer_neon_from_solana_to_neon(self, new_account, solana_account, py @pytest.mark.multipletokens def test_create_and_transfer_new_token_from_solana_to_neon( - self, - new_account, - solana_account, - pytestconfig: Config, - neon_mint, - web3_client_abc, - operator_keypair, - evm_loader_keypair, + self, + new_account, + solana_account, + pytestconfig: Config, + neon_mint, + web3_client_abc, + operator_keypair, + evm_loader_keypair, ): amount = 5000000 evm_loader_id = pytestconfig.environment.evm_loader new_sol_account = Keypair.generate() self.sol_client.send_sol(solana_account, new_sol_account.public_key, amount) - self.sol_client.deposit_neon_like_tokens_from_solana_to_neon(neon_mint, solana_account, new_account, - web3_client_abc.eth.chain_id, operator_keypair, - evm_loader_keypair, evm_loader_id, amount) + self.sol_client.deposit_neon_like_tokens_from_solana_to_neon( + neon_mint, + new_sol_account, + new_account, + web3_client_abc.eth.chain_id, + operator_keypair, + evm_loader_keypair, + evm_loader_id, + amount, + ) abc_balance_after = web3_client_abc.get_balance(new_account) assert abc_balance_after == amount * 1000000000 @@ -114,7 +119,7 @@ def test_transfer_spl_token_from_solana_to_neon(self, solana_account, new_accoun @pytest.mark.multipletokens def test_transfer_wrapped_sol_token_from_solana_to_neon( - self, solana_account, pytestconfig: Config, web3_client_sol + self, solana_account, pytestconfig: Config, web3_client_sol ): new_account = self.web3_client.create_account() @@ -164,7 +169,7 @@ def withdraw(self, dest_acc, move_amount, withdraw_contract): @pytest.mark.only_stands def test_success_withdraw_to_non_existing_account( - self, pytestconfig: Config, withdraw_contract, neon_mint, solana_account + self, pytestconfig: Config, withdraw_contract, neon_mint, solana_account ): """Should successfully withdraw NEON tokens to previously non-existing Associated Token Account""" dest_acc = Keypair.generate() @@ -187,7 +192,7 @@ def test_success_withdraw_to_non_existing_account( assert int(destination_balance_after.value.amount) == int(move_amount / 1_000_000_000) def test_success_withdraw_to_existing_account( - self, pytestconfig: Config, withdraw_contract, neon_mint, solana_account + self, pytestconfig: Config, withdraw_contract, neon_mint, solana_account ): """Should successfully withdraw NEON tokens to existing Associated Token Account""" dest_acc = solana_account @@ -207,15 +212,16 @@ def test_success_withdraw_to_existing_account( spl_neon_token = SplToken(self.sol_client, neon_mint, TOKEN_PROGRAM_ID, dest_acc) destination_balance_before = spl_neon_token.get_balance(dest_token_acc, commitment=Commitment("confirmed")) - assert int(destination_balance_before.value.amount) == 0 self.withdraw(dest_acc, move_amount_alan, withdraw_contract) destination_balance_after = spl_neon_token.get_balance(dest_token_acc, commitment=Commitment("confirmed")) - assert int(destination_balance_after.value.amount) == move_amount_galan + assert int(destination_balance_after.value.amount) == move_amount_galan + int( + destination_balance_before.value.amount + ) def test_failed_withdraw_non_divisible_amount( - self, pytestconfig: Config, withdraw_contract, neon_mint, solana_account + self, pytestconfig: Config, withdraw_contract, neon_mint, solana_account ): dest_acc = solana_account @@ -236,12 +242,12 @@ def test_failed_withdraw_non_divisible_amount( @pytest.mark.parametrize("move_amount", [11000, 10000]) def test_failed_withdraw_insufficient_balance( - self, - pytestconfig: Config, - move_amount, - withdraw_contract, - neon_mint, - solana_account, + self, + pytestconfig: Config, + move_amount, + withdraw_contract, + neon_mint, + solana_account, ): dest_acc = solana_account diff --git a/integration/tests/basic/test_payment_in_different_tokens.py b/integration/tests/basic/test_payment_in_different_tokens.py index 8d6f3e9810..eef642bdb9 100644 --- a/integration/tests/basic/test_payment_in_different_tokens.py +++ b/integration/tests/basic/test_payment_in_different_tokens.py @@ -133,26 +133,21 @@ def test_deploy_contract_with_sending_tokens(self, web3_client_sol, alice, check @pytest.mark.multipletokens def test_deploy_contract_by_one_user_to_different_chain( - self, web3_client_sol, new_account, solana_account, alice, web3_client, pytestconfig + self, web3_client_sol, solana_account, web3_client, pytestconfig, alice ): def deploy_contract(w3_client): _, rcpt = w3_client.deploy_and_get_contract( - contract="common/Common", version="0.8.12", contract_name="Common", account=new_account + contract="common/Common", version="0.8.12", contract_name="Common", account=alice ) return rcpt - - self.sol_client.deposit_wrapped_sol_from_solana_to_neon( - solana_account, new_account, web3_client_sol.eth.chain_id, pytestconfig.environment.evm_loader - ) - + + make_nonce_the_biggest_for_chain(alice, web3_client_sol, [web3_client]) deploy_contract(web3_client_sol) with pytest.raises(ValueError, match="EVM Error. Attempt to deploy to existing account"): deploy_contract(web3_client) - # any transaction to raise the nonce - web3_client.send_neon(new_account, alice, amount=1) - + make_nonce_the_biggest_for_chain(alice, web3_client, [web3_client_sol]) receipt = deploy_contract(web3_client) assert receipt["status"] == 1 @@ -230,9 +225,6 @@ def test_call_different_chains_contracts_in_one_transaction( web3_client_def, class_account_sol_chain, ): - bunch_contract_neon, _ = web3_client.deploy_and_get_contract( - contract="common/Common", version="0.8.12", contract_name="BunchActions", account=alice - ) chains = { "neon": {"client": web3_client}, "sol": {"client": web3_client_sol}, @@ -240,6 +232,12 @@ def test_call_different_chains_contracts_in_one_transaction( "def": {"client": web3_client_def}, } + make_nonce_the_biggest_for_chain(alice, web3_client, [item["client"] for item in chains.values()]) + + bunch_contract_neon, _ = web3_client.deploy_and_get_contract( + contract="common/Common", version="0.8.12", contract_name="BunchActions", account=alice + ) + for chain in chains: bunch_contract = chains[chain]["client"].get_deployed_contract( bunch_contract_neon.address, "common/Common", contract_name="BunchActions" diff --git a/integration/tests/conftest.py b/integration/tests/conftest.py index 39c7bc9422..26d03d4a8a 100644 --- a/integration/tests/conftest.py +++ b/integration/tests/conftest.py @@ -2,7 +2,7 @@ import random import string import pathlib - +import json import typing import allure diff --git a/pyproject.toml b/pyproject.toml index 7e751e804a..7cd2b264a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,8 @@ profile="black" timeout = 300 markers = [ "only_stands: this tests works only on test stands", - "only_devnet: this tests works only on devnet env" + "only_devnet: this tests works only on devnet env", + "multipletokens : this tests works only on stands with several chains" ] asyncio_mode = "auto" addopts = "--alluredir=allure-results" From e5fcb655f5c226ca562b5cb33c67d91f3c858864 Mon Sep 17 00:00:00 2001 From: romanova-natasha Date: Fri, 17 Nov 2023 11:37:50 +0300 Subject: [PATCH 21/56] add test for debug_getRawHeader and debug_getModifiedAccountsByNumber(Hash) add test for debug_getRawHeader and debug_getModifiedAccountsByNumber(Hash) --- .../tests/tracer/test_tracer_debug_methods.py | 161 +++++++++++++++++- 1 file changed, 160 insertions(+), 1 deletion(-) diff --git a/integration/tests/tracer/test_tracer_debug_methods.py b/integration/tests/tracer/test_tracer_debug_methods.py index 9f830c4061..af6865d710 100644 --- a/integration/tests/tracer/test_tracer_debug_methods.py +++ b/integration/tests/tracer/test_tracer_debug_methods.py @@ -1,6 +1,7 @@ import json import pathlib import random +import re from jsonschema import Draft4Validator @@ -254,4 +255,162 @@ def test_debug_trace_block_by_non_existent_hash(self): method="debug_traceBlockByHash", params=['0xd97ff4869d52c4add6f5bcb1ba96020dd7877244b4cbf49044f49f002015ea85']) assert "error" in response, "No errors in response" assert response["error"]["code"] == -32603, "Invalid error code" - assert response["error"]["message"] == "eth_getBlockByHash returns None for '\"0xd97ff4869d52c4add6f5bcb1ba96020dd7877244b4cbf49044f49f002015ea85\"' block" \ No newline at end of file + assert response["error"]["message"] == "eth_getBlockByHash returns None for '\"0xd97ff4869d52c4add6f5bcb1ba96020dd7877244b4cbf49044f49f002015ea85\"' block" + + def test_getRawHeader_by_block_number(self): + receipt = self.send_neon( + self.sender_account, self.recipient_account, 0.1) + assert receipt["status"] == 1 + wait_condition(lambda: self.tracer_api.send_rpc(method="debug_getRawHeader", + params=[hex(receipt["blockNumber"])])["result"] is not None, + timeout_sec=120) + + response = self.tracer_api.send_rpc( + method="debug_getRawHeader", params=[hex(receipt["blockNumber"])]) + assert "error" not in response, "Error in response" + assert response["result"] is not None and response["result"] != "" + assert isinstance(response["result"], str) + + def test_getRawHeader_by_invalid_block_number(self): + response = self.tracer_api.send_rpc( + method="debug_getRawHeader", params=["0f98e"]) + assert "error" in response, "No errors in response" + assert response["error"]["code"] == -32602, "Invalid error code" + assert response["error"]["message"] == "Invalid params" + + def test_getRawHeader_by_block_hash(self): + receipt = self.send_neon( + self.sender_account, self.recipient_account, 0.1) + assert receipt["status"] == 1 + wait_condition(lambda: self.tracer_api.send_rpc(method="debug_getRawHeader", + params=[receipt["blockHash"].hex()])["result"] is not None, + timeout_sec=120) + + response = self.tracer_api.send_rpc( + method="debug_getRawHeader", params=[receipt["blockHash"].hex()]) + assert "error" not in response, "Error in response" + assert response["result"] is not None and response["result"] != "" + assert isinstance(response["result"], str) + + def test_getRawHeader_by_invalid_block_hash(self): + response = self.tracer_api.send_rpc( + method="debug_getRawHeader", params=["0f98e"]) + assert "error" in response, "No errors in response" + assert response["error"]["code"] == -32602, "Invalid error code" + assert response["error"]["message"] == "Invalid params" + + def check_modified_accounts_response(self, response): + assert "error" not in response, "Error in response" + assert response["result"] is not None and response["result"] != [] + assert isinstance(response["result"], list) + assert self.sender_account.address in response["result"] + for item in response["result"]: + assert re.match('^0x[a-fA-F\d]{64}$', item) + + def test_debug_get_modified_accounts_by_number(self): + receipt_start = self.send_neon( + self.sender_account, self.recipient_account, 0.1) + receipt_end = self.send_neon( + self.sender_account, self.recipient_account, 0.1) + assert receipt_start["status"] == 1 + assert receipt_end["status"] == 1 + + wait_condition(lambda: self.tracer_api.send_rpc(method="debug_getModifiedAccountsByNumber", + params=[hex(receipt_start["blockNumber"]), hex(receipt_end["blockNumber"])])["result"] is not None, + timeout_sec=120) + + response = self.tracer_api.send_rpc( + method="debug_getModifiedAccountsByNumber", params=[hex(receipt_start["blockNumber"]), hex(receipt_end["blockNumber"])]) + self.check_modified_accounts_response(response) + + def test_debug_get_modified_accounts_by_same_number(self): + receipt = self.send_neon( + self.sender_account, self.recipient_account, 0.1) + assert receipt["status"] == 1 + + wait_condition(lambda: self.tracer_api.send_rpc(method="debug_getModifiedAccountsByNumber", + params=[hex(receipt["blockNumber"]), hex(receipt["blockNumber"])])["result"] is not None, + timeout_sec=120) + + response = self.tracer_api.send_rpc( + method="debug_getModifiedAccountsByNumber", params=[hex(receipt["blockNumber"]), hex(receipt["blockNumber"])]) + self.check_modified_accounts_response(response) + + def test_debug_get_modified_accounts_by_only_one_number(self): + receipt = self.send_neon( + self.sender_account, self.recipient_account, 0.1) + assert receipt["status"] == 1 + + wait_condition(lambda: self.tracer_api.send_rpc(method="debug_getModifiedAccountsByNumber", + params=[hex(receipt["blockNumber"])])["result"] is not None, + timeout_sec=120) + + response = self.tracer_api.send_rpc( + method="debug_getModifiedAccountsByNumber", params=[hex(receipt["blockNumber"])]) + self.check_modified_accounts_response(response) + + @pytest.mark.parametrize("difference", [1, 50, 199, 200]) + def test_debug_get_modified_accounts_by_number_blocks_difference_less_or_equal_200(self, difference): + receipt = self.send_neon( + self.sender_account, self.recipient_account, 0.1) + assert receipt["status"] == 1 + start_number = hex(receipt["blockNumber"] - difference) + end_number= hex(receipt["blockNumber"]) + wait_condition(lambda: self.tracer_api.send_rpc(method="debug_getModifiedAccountsByNumber", + params=[start_number, end_number])["result"] is not None, + timeout_sec=120) + + response = self.tracer_api.send_rpc( + method="debug_getModifiedAccountsByNumber", params=[start_number, end_number]) + self.check_modified_accounts_response(response) + + def test_debug_get_modified_accounts_by_number_201_blocks_difference(self): + receipt = self.send_neon( + self.sender_account, self.recipient_account, 0.1) + assert receipt["status"] == 1 + start_number = hex(receipt["blockNumber"] - 201) + end_number= hex(receipt["blockNumber"]) + + response = self.tracer_api.send_rpc( + method="debug_getModifiedAccountsByNumber", params=[start_number, end_number]) + assert "error" in response, "No errors in response" + assert response["error"]["code"] == -32603, "Invalid error code" + assert response["error"]["message"] == "Requested range (201) is too big, maximum allowed range is 200 blocks" + + @pytest.mark.parametrize("params", [[1, 124], ["94f3e", 12], ["1a456", "0x0"], ["183b8e", "183b8e"]]) + def test_debug_get_modified_accounts_by_invalid_numbers(self, params): + response = self.tracer_api.send_rpc( + method="debug_getModifiedAccountsByNumber", params=params) + assert "error" in response, "No errors in response" + assert response["error"]["code"] == -32602, "Invalid error code" + assert response["error"]["message"] == "Invalid params" + + def test_debug_get_modified_accounts_by_hash(self): + receipt_start = self.send_neon( + self.sender_account, self.recipient_account, 0.1) + receipt_end = self.send_neon( + self.sender_account, self.recipient_account, 0.1) + assert receipt_start["status"] == 1 + assert receipt_end["status"] == 1 + + wait_condition(lambda: self.tracer_api.send_rpc(method="debug_getModifiedAccountsByHash", + params=[receipt_start["blockHash"].hex(), receipt_end["blockHash"].hex()])["result"] is not None, + timeout_sec=120) + + response = self.tracer_api.send_rpc( + method="debug_getModifiedAccountsByHash", params=[receipt_start["blockHash"].hex(), receipt_end["blockHash"].hex()]) + self.check_modified_accounts_response(response) + + @pytest.mark.parametrize("params", [[1, 124], ["0x94f3e00000000800000000", 12], ["0x1a456", "0x000000000001"], ["0x183b8e", "183b8e"]]) + def test_debug_get_modified_accounts_by_invalid_hash(self, params): + response = self.tracer_api.send_rpc( + method="debug_getModifiedAccountsByHash", params=params) + assert "error" in response, "No errors in response" + assert response["error"]["code"] == -32602, "Invalid error code" + assert response["error"]["message"] == "Invalid params" + + def test_get_raw_transaction(self): + pass + + def test_get_raw_transaction_invalid_tx_hash(self): + pass \ No newline at end of file From b30c55a6fb383648fb824c387b0de1f6dd4f5811 Mon Sep 17 00:00:00 2001 From: romanova-natasha Date: Fri, 17 Nov 2023 11:39:07 +0300 Subject: [PATCH 22/56] skip tests due to bug skip tests due to bug --- integration/tests/tracer/test_tracer_debug_methods.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/integration/tests/tracer/test_tracer_debug_methods.py b/integration/tests/tracer/test_tracer_debug_methods.py index af6865d710..35476b3de9 100644 --- a/integration/tests/tracer/test_tracer_debug_methods.py +++ b/integration/tests/tracer/test_tracer_debug_methods.py @@ -409,8 +409,10 @@ def test_debug_get_modified_accounts_by_invalid_hash(self, params): assert response["error"]["code"] == -32602, "Invalid error code" assert response["error"]["message"] == "Invalid params" + @pytest.mark.skip(reason="NDEV-2367") def test_get_raw_transaction(self): pass - + + @pytest.mark.skip(reason="NDEV-2367") def test_get_raw_transaction_invalid_tx_hash(self): pass \ No newline at end of file From 9ad3a58ff1061844b29ba392e867976f09258020 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Fri, 17 Nov 2023 10:14:38 +0100 Subject: [PATCH 23/56] fix dapps prepare step --- .github/workflows/dapps.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dapps.yml b/.github/workflows/dapps.yml index 767345669a..52ea75f326 100644 --- a/.github/workflows/dapps.yml +++ b/.github/workflows/dapps.yml @@ -124,7 +124,7 @@ jobs: proxy_url=`python ./clickfile.py infra print-network-param -n '${{steps.stand.outputs.network}}' -p 'proxy_url'` solana_url=`python ./clickfile.py infra print-network-param -n '${{steps.stand.outputs.network}}' -p 'solana_url'` faucet_url=`python ./clickfile.py infra print-network-param -n '${{steps.stand.outputs.network}}' -p 'faucet_url'` - network_id=`python ./clickfile.py infra print-network-param -n '${{steps.stand.outputs.network}}' -p 'network_id'` + network_id=`python ./clickfile.py infra print-network-param -n '${{steps.stand.outputs.network}}' -p 'network_ids.neon'` fi; if [[ "${{steps.stand.outputs.network}}" == "devnet" ]]; then From af328bd139f58c55f6ef3866fb2dd58d5de6492c Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Fri, 17 Nov 2023 18:08:26 +0100 Subject: [PATCH 24/56] operator tests for second chain --- .github/workflows/dockerize_neon_tests.yml | 2 +- clickfile.py | 4 +- conftest.py | 35 +- integration/tests/base.py | 6 +- .../test_chain_id_dependent_opcodes.py | 6 +- integration/tests/basic/helpers/chains.py | 3 +- .../basic/test_payment_in_different_tokens.py | 53 +- .../transfer/test_transaction_validation.py | 1 + integration/tests/conftest.py | 34 +- integration/tests/economy/conftest.py | 54 ++ integration/tests/economy/test_economics.py | 875 +++++++----------- .../migrations/test_account_migration.py | 4 +- loadtesting/proxy/common/events.py | 2 +- utils/operator.py | 44 +- utils/web3client.py | 113 +-- 15 files changed, 526 insertions(+), 710 deletions(-) diff --git a/.github/workflows/dockerize_neon_tests.yml b/.github/workflows/dockerize_neon_tests.yml index 59aad452e7..fac8dca7c0 100644 --- a/.github/workflows/dockerize_neon_tests.yml +++ b/.github/workflows/dockerize_neon_tests.yml @@ -35,7 +35,7 @@ jobs: echo "oz_branch=${oz_branch}" echo "tag=${tag}" >> $GITHUB_OUTPUT echo "oz_branch=${oz_branch}" >> $GITHUB_OUTPUT - - name: Define feature of version branch + - name: Define feature or version branch id: feature_branch if: github.ref_name !='develop' || github.ref_name !='master' run: | diff --git a/clickfile.py b/clickfile.py index 9da0408922..5f9f64e81b 100755 --- a/clickfile.py +++ b/clickfile.py @@ -123,7 +123,7 @@ def wrapper(*args, **kwargs) -> None: def get_tokens_balances(operator: Operator) -> tp.Dict: """Return tokens balances""" return dict( - neon=operator.get_neon_balance(), + neon=operator.get_token_balance(), sol=operator.get_solana_balance() / 1_000_000_000, ) @@ -853,7 +853,7 @@ def get_operator_balances(network: str): net["spl_neon_mint"], net["operator_keys"], ) - neon_balance = operator.get_neon_balance() + neon_balance = operator.get_token_balance() sol_balance = operator.get_solana_balance() print( f'Operator balances ({len(net["operator_keys"])}):\n' diff --git a/conftest.py b/conftest.py index 5fad18b04c..29762d01da 100644 --- a/conftest.py +++ b/conftest.py @@ -11,7 +11,7 @@ from solana.keypair import Keypair from clickfile import create_allure_environment_opts -from utils.web3client import NeonChainWeb3Client, SolChainWeb3Client, Web3Client, NeonLikeChainWeb3Client +from utils.web3client import NeonChainWeb3Client, Web3Client pytest_plugins = ["ui.plugins.browser"] @@ -154,23 +154,26 @@ def web3_client(pytestconfig: Config) -> NeonChainWeb3Client: @pytest.fixture(scope="session", autouse=True) -def web3_client_sol(pytestconfig: Config) -> SolChainWeb3Client: - client = SolChainWeb3Client( - pytestconfig.environment.proxy_url, - ) - return client +def web3_client_sol(pytestconfig: Config) -> tp.Union[Web3Client, None]: + if "sol" in pytestconfig.environment.network_ids: + client = Web3Client( + f"{pytestconfig.environment.proxy_url}/sol" + ) + return client + else: + return None @pytest.fixture(scope="session", autouse=True) -def web3_client_abc(pytestconfig: Config) -> Web3Client: - client = NeonLikeChainWeb3Client( - pytestconfig.environment.proxy_url, - "abc", - ) - return client - +def web3_client_abc(pytestconfig: Config) -> tp.Union[Web3Client, None]: + if "abc" in pytestconfig.environment.network_ids: + return Web3Client(f"{pytestconfig.environment.proxy_url}/abc") + else: + return None @pytest.fixture(scope="session", autouse=True) -def web3_client_def(pytestconfig: Config) -> Web3Client: - client = NeonLikeChainWeb3Client(pytestconfig.environment.proxy_url, "def") - return client +def web3_client_def(pytestconfig: Config) -> tp.Union[Web3Client, None]: + if "def" in pytestconfig.environment.network_ids: + return Web3Client(f"{pytestconfig.environment.proxy_url}/def") + else: + return None \ No newline at end of file diff --git a/integration/tests/base.py b/integration/tests/base.py index 1aacc48a19..4af8bf9538 100644 --- a/integration/tests/base.py +++ b/integration/tests/base.py @@ -6,7 +6,7 @@ from utils.faucet import Faucet from utils.operator import Operator from utils.solana_client import SolanaClient -from utils.web3client import NeonChainWeb3Client, SolChainWeb3Client +from utils.web3client import NeonChainWeb3Client, Web3Client class BaseTests: @@ -14,7 +14,7 @@ class BaseTests: operator: Operator faucet: Faucet web3_client: NeonChainWeb3Client - web3_client_sol: SolChainWeb3Client + web3_client_sol: Web3Client sol_client: SolanaClient sol_price: float @@ -49,7 +49,7 @@ def create_tx_object(self, sender, recipient=None, amount=0, nonce=None, gas=Non transaction["gas"] = gas if amount is not None: - transaction["value"] = web3_client.to_main_currency(amount) + transaction["value"] = web3_client.to_atomic_currency(amount) if recipient is not None: transaction["to"] = recipient diff --git a/integration/tests/basic/evm/opcodes/test_chain_id_dependent_opcodes.py b/integration/tests/basic/evm/opcodes/test_chain_id_dependent_opcodes.py index bd474811bb..45d8c20025 100644 --- a/integration/tests/basic/evm/opcodes/test_chain_id_dependent_opcodes.py +++ b/integration/tests/basic/evm/opcodes/test_chain_id_dependent_opcodes.py @@ -59,8 +59,7 @@ def test_balance_by_sol_contract(self, contract_sol, class_account_sol_chain, we # sol contract calls sol contract in neon chain == sol balance expected_balance_sol = web3_client_sol.get_balance(class_account_sol_chain.address) - expected_balance_neon = self.web3_client.to_main_currency( - web3_client.get_balance(class_account_sol_chain.address)) + expected_balance_neon = web3_client.get_balance(class_account_sol_chain.address) balance = contract_sol.functions.getBalance(class_account_sol_chain.address).call() assert balance == expected_balance_sol @@ -90,8 +89,7 @@ def test_balance_by_neon_contract(self, contract_neon, sol_client, class_account # neon contract calls neon contract in sol chain == neon balance expected_balance_sol = web3_client_sol.get_balance(class_account.address) - expected_balance_neon = self.web3_client.to_main_currency( - web3_client.get_balance(class_account.address)) + expected_balance_neon = web3_client.get_balance(class_account.address) balance = contract_neon.functions.getBalance(class_account.address).call() assert balance == expected_balance_neon diff --git a/integration/tests/basic/helpers/chains.py b/integration/tests/basic/helpers/chains.py index 2de55d9c50..f7437e7202 100644 --- a/integration/tests/basic/helpers/chains.py +++ b/integration/tests/basic/helpers/chains.py @@ -1,5 +1,6 @@ def make_nonce_the_biggest_for_chain(account, client, rest_clients): # to avoid error "EVM Error. Attempt to deploy to existing account 0x..." + clients = [c for c in rest_clients if c] new_account = client.create_account() - while client.get_nonce(account.address) < max([c.get_nonce(account.address) for c in rest_clients]): + while client.get_nonce(account.address) < max([c.get_nonce(account.address) for c in clients]): client.send_tokens(account, new_account.address, 10) diff --git a/integration/tests/basic/test_payment_in_different_tokens.py b/integration/tests/basic/test_payment_in_different_tokens.py index eef642bdb9..195ac211bf 100644 --- a/integration/tests/basic/test_payment_in_different_tokens.py +++ b/integration/tests/basic/test_payment_in_different_tokens.py @@ -9,7 +9,7 @@ @allure.feature("Multiply token") @allure.story("Payments in different tokens") -class TestMultiplyChain(BaseMixin): +class TestMultiplyChains(BaseMixin): @pytest.fixture(scope="class") def bob(self, class_account_sol_chain): return class_account_sol_chain @@ -42,36 +42,37 @@ def test_user_to_user_trx(self, web3_client_sol, alice, bob, check_neon_balance_ @pytest.mark.multipletokens def test_user_to_contract_and_contract_to_user_trx( - self, web3_client_sol, bob, check_neon_balance_does_not_changed, wsol + self, web3_client_sol, bob, check_neon_balance_does_not_changed, wsol ): bob_sol_balance_before = web3_client_sol.get_balance(bob) contract_sol_balance_initial = web3_client_sol.get_balance(wsol.address) - amount = 10 + amount = 0.001 + value = web3_client_sol.to_atomic_currency(amount) tx = self.make_contract_tx_object(bob.address, amount=amount, web3_client=web3_client_sol) instruction_tx = wsol.functions.deposit().build_transaction(tx) receipt = self.web3_client_sol.send_transaction(bob, instruction_tx) assert receipt["status"] == 1 bob_sol_balance_after_deposit = web3_client_sol.get_balance(bob) contract_sol_balance_after_deposit = web3_client_sol.get_balance(wsol.address) - assert contract_sol_balance_after_deposit == contract_sol_balance_initial + web3_client_sol.to_main_currency( + assert contract_sol_balance_after_deposit == contract_sol_balance_initial + web3_client_sol.to_atomic_currency( amount ) - assert bob_sol_balance_after_deposit < bob_sol_balance_before - web3_client_sol.to_main_currency(amount) + assert bob_sol_balance_after_deposit < bob_sol_balance_before - value tx = self.make_contract_tx_object(bob.address, web3_client=web3_client_sol) - instruction_tx = wsol.functions.withdraw(web3_client_sol.to_main_currency(10)).build_transaction(tx) + instruction_tx = wsol.functions.withdraw(value).build_transaction(tx) receipt = self.web3_client_sol.send_transaction(bob, instruction_tx) assert receipt["status"] == 1 bob_sol_balance_after_withdraw = web3_client_sol.get_balance(bob) contract_sol_balance_after_withdraw = web3_client_sol.get_balance(wsol.address) assert contract_sol_balance_after_withdraw == contract_sol_balance_initial - assert bob_sol_balance_after_withdraw < bob_sol_balance_after_deposit + web3_client_sol.to_main_currency(amount) + assert bob_sol_balance_after_withdraw < bob_sol_balance_after_deposit + value @pytest.mark.multipletokens def test_contract_to_contract_trx(self, web3_client_sol, bob): # contract to new contract - amount = 1 - value = web3_client_sol.to_main_currency(amount) + amount = 0.0001 + value = web3_client_sol.to_atomic_currency(amount) bob_sol_balance_before = web3_client_sol.get_balance(bob) wsol_contract_caller, resp = web3_client_sol.deploy_and_get_contract( contract="common/WNativeChainToken", @@ -95,7 +96,7 @@ def test_contract_to_contract_trx(self, web3_client_sol, bob): @pytest.mark.multipletokens def test_user_to_contract_wrong_chain_id_trx( - self, web3_client_sol, bob, check_neon_balance_does_not_changed, event_caller_contract + self, web3_client_sol, bob, check_neon_balance_does_not_changed, event_caller_contract ): tx = self.make_contract_tx_object(bob.address, amount=1) instruction_tx = event_caller_contract.functions.unnamedArg("hello").build_transaction(tx) @@ -133,14 +134,14 @@ def test_deploy_contract_with_sending_tokens(self, web3_client_sol, alice, check @pytest.mark.multipletokens def test_deploy_contract_by_one_user_to_different_chain( - self, web3_client_sol, solana_account, web3_client, pytestconfig, alice + self, web3_client_sol, solana_account, web3_client, pytestconfig, alice ): def deploy_contract(w3_client): _, rcpt = w3_client.deploy_and_get_contract( contract="common/Common", version="0.8.12", contract_name="Common", account=alice ) return rcpt - + make_nonce_the_biggest_for_chain(alice, web3_client_sol, [web3_client]) deploy_contract(web3_client_sol) @@ -153,7 +154,7 @@ def deploy_contract(w3_client): @pytest.mark.multipletokens def test_interact_with_contract_from_another_chain( - self, web3_client_sol, bob, check_neon_balance_does_not_changed, common_contract + self, web3_client_sol, bob, check_neon_balance_does_not_changed, common_contract ): tx = self.make_contract_tx_object(bob.address, web3_client=web3_client_sol) common_contract_sol_chain = web3_client_sol.get_deployed_contract(common_contract.address, "common/Common") @@ -166,7 +167,7 @@ def test_interact_with_contract_from_another_chain( @pytest.mark.multipletokens def test_transfer_neons_in_sol_chain(self, web3_client_sol, web3_client, bob, alice, wneon): - amount = 12 + amount = 1 value = self.web3_client._web3.to_wei(amount, "ether") tx = self.make_contract_tx_object(bob.address, amount=amount) @@ -187,12 +188,12 @@ def test_transfer_neons_in_sol_chain(self, web3_client_sol, web3_client, bob, al receipt = self.web3_client_sol.send_transaction(alice, instruction_tx) assert receipt["status"] == 1 - assert web3_client.get_balance(alice.address) == neon_balance_before + amount + assert web3_client.get_balance(alice.address) == neon_balance_before + web3_client.to_atomic_currency(amount) @pytest.mark.multipletokens def test_transfer_sol_in_neon_chain(self, web3_client_sol, web3_client, bob, alice, wsol): - amount = 12 - value = web3_client_sol.to_main_currency(amount) + amount = 0.001 + value = web3_client_sol.to_atomic_currency(amount) tx = self.make_contract_tx_object(bob.address, amount=amount, web3_client=web3_client_sol) @@ -216,14 +217,14 @@ def test_transfer_sol_in_neon_chain(self, web3_client_sol, web3_client, bob, ali @pytest.mark.multipletokens def test_call_different_chains_contracts_in_one_transaction( - self, - alice, - common_contract, - web3_client, - web3_client_sol, - web3_client_abc, - web3_client_def, - class_account_sol_chain, + self, + alice, + common_contract, + web3_client, + web3_client_sol, + web3_client_abc, + web3_client_def, + class_account_sol_chain, ): chains = { "neon": {"client": web3_client}, @@ -256,7 +257,7 @@ def test_call_different_chains_contracts_in_one_transaction( for chain in chains: tx = self.make_contract_tx_object(alice.address, web3_client=chains[chain]["client"]) - numbers = [random.randint(0, 1000000) for i in range(len(chains))] + numbers = [random.randint(0, 1000000) for _ in range(len(chains))] instruction_tx = ( chains[chain]["bunch_contract"] .functions.setNumber([item["common_contract"].address for item in chains.values()], numbers) diff --git a/integration/tests/basic/transfer/test_transaction_validation.py b/integration/tests/basic/transfer/test_transaction_validation.py index e7111b158b..cb344a1ec7 100644 --- a/integration/tests/basic/transfer/test_transaction_validation.py +++ b/integration/tests/basic/transfer/test_transaction_validation.py @@ -98,6 +98,7 @@ def test_send_transaction_with_small_gas_price(self, new_account): response = self.proxy_api.send_rpc( "eth_sendRawTransaction", [signed_tx.rawTransaction.hex()] ) + print(response) assert is_hex(response['result']) time.sleep(5) receipt = self.proxy_api.send_rpc(method="eth_getTransactionReceipt", params=[response["result"]]) diff --git a/integration/tests/conftest.py b/integration/tests/conftest.py index 69dd210436..e9ff91bd1e 100644 --- a/integration/tests/conftest.py +++ b/integration/tests/conftest.py @@ -101,21 +101,21 @@ def prepare_account(operator, faucet, web3_client: NeonChainWeb3Client): acc = web3_client.eth.account.create() with allure.step(f"Request {NEON_AIRDROP_AMOUNT} NEON from faucet for {acc.address}"): faucet.request_neon(acc.address, NEON_AIRDROP_AMOUNT) - assert web3_client.get_balance(acc) == NEON_AIRDROP_AMOUNT - start_neon_balance = operator.get_neon_balance() + assert web3_client.get_ether_balance(acc) == NEON_AIRDROP_AMOUNT + start_neon_balance = operator.get_token_balance() start_sol_balance = operator.get_solana_balance() with allure.step( f"Operator initial balance: {start_neon_balance / LAMPORT_PER_SOL} NEON {start_sol_balance / LAMPORT_PER_SOL} SOL" ): pass yield acc - end_neon_balance = operator.get_neon_balance() + end_neon_balance = operator.get_token_balance() end_sol_balance = operator.get_solana_balance() with allure.step( f"Operator end balance: {end_neon_balance / LAMPORT_PER_SOL} NEON {end_sol_balance / LAMPORT_PER_SOL} SOL" ): pass - with allure.step(f"Account end balance: {web3_client.get_balance(acc)} NEON"): + with allure.step(f"Account end balance: {web3_client.get_ether_balance(acc)} NEON"): pass @@ -240,18 +240,20 @@ def account_with_all_tokens( eth_bank_account, neon_mint, operator_keypair, evm_loader_keypair ): account = web3_client.create_account_with_balance(faucet, bank_account=eth_bank_account) - sol_client.request_airdrop(solana_account.public_key, 1 * LAMPORT_PER_SOL) - sol_client.deposit_wrapped_sol_from_solana_to_neon( - solana_account, account, web3_client_sol.eth.chain_id, pytestconfig.environment.evm_loader, 1 * LAMPORT_PER_SOL - ) - for client in [ web3_client_abc, web3_client_def]: - new_sol_account = Keypair.generate() - sol_client.send_sol(solana_account, new_sol_account.public_key, 5000000) - sol_client.deposit_neon_like_tokens_from_solana_to_neon(neon_mint, new_sol_account, account, - client.eth.chain_id, - operator_keypair, - evm_loader_keypair, - pytestconfig.environment.evm_loader, 1000000000000000000) + if web3_client_sol: + sol_client.request_airdrop(solana_account.public_key, 1 * LAMPORT_PER_SOL) + sol_client.deposit_wrapped_sol_from_solana_to_neon( + solana_account, account, web3_client_sol.eth.chain_id, pytestconfig.environment.evm_loader, 1 * LAMPORT_PER_SOL + ) + for client in [web3_client_abc, web3_client_def]: + if client: + new_sol_account = Keypair.generate() + sol_client.send_sol(solana_account, new_sol_account.public_key, 5000000) + sol_client.deposit_neon_like_tokens_from_solana_to_neon(neon_mint, new_sol_account, account, + client.eth.chain_id, + operator_keypair, + evm_loader_keypair, + pytestconfig.environment.evm_loader, 1000000000000000000) return account diff --git a/integration/tests/economy/conftest.py b/integration/tests/economy/conftest.py index 8085179ef7..32ca96e679 100644 --- a/integration/tests/economy/conftest.py +++ b/integration/tests/economy/conftest.py @@ -4,6 +4,8 @@ from pythclient.solana import SolanaClient from _pytest.config import Config +from integration.tests.basic.helpers.chains import make_nonce_the_biggest_for_chain +from utils.erc20wrapper import ERC20Wrapper from utils.prices import get_sol_price, get_neon_price @@ -31,3 +33,55 @@ def sol_client_tx_v2(pytestconfig: Config): pytestconfig.environment.account_seed_version, ) return client + + +# @pytest.fixture(scope="class") +# def temp_acc(web3_client): +# key = "0x931babf4129096d628e0d5e642bd5768fd1bcfb79c6f5b95ffa471c350da4207" +# return web3_client.eth.account.from_key(key) + + +@pytest.fixture(scope="class") +def counter_contract(account_with_all_tokens, client_and_price, web3_client_sol, web3_client): + w3_client, _ = client_and_price + make_nonce_the_biggest_for_chain(account_with_all_tokens, w3_client, [web3_client, web3_client_sol]) + contract, _ = w3_client.deploy_and_get_contract("common/Counter", "0.8.10", account=account_with_all_tokens) + return contract + + +@pytest.fixture(scope="class", params=["neon", "sol"]) +def client_and_price(web3_client, web3_client_sol, sol_price, neon_price, request, pytestconfig): + if request.param == "neon": + return web3_client, neon_price + elif request.param == "sol": + if "sol" in pytestconfig.environment.network_ids: + return web3_client_sol, sol_price + pytest.skip(f"{request.param} chain is not available") + + +@pytest.fixture(scope="class") +def erc20_wrapper( + erc20_spl_mintable, + account_with_all_tokens, + client_and_price, + faucet, + solana_account, + sol_client, + web3_client_sol, + web3_client, +): + client, _ = client_and_price + make_nonce_the_biggest_for_chain(account_with_all_tokens, client, [web3_client, web3_client_sol]) + contract = ERC20Wrapper( + client, + faucet, + "Test AAA", + "AAA", + sol_client, + account=account_with_all_tokens, + solana_account=solana_account, + mintable=True, + ) + contract.mint_tokens(account_with_all_tokens, contract.account.address) + return contract + diff --git a/integration/tests/economy/test_economics.py b/integration/tests/economy/test_economics.py index f0d7355ae5..9fa02eedd1 100644 --- a/integration/tests/economy/test_economics.py +++ b/integration/tests/economy/test_economics.py @@ -26,6 +26,7 @@ from utils.helpers import wait_condition from ..base import BaseTests +from ..basic.helpers.chains import make_nonce_the_biggest_for_chain TX_COST = 5000 @@ -83,27 +84,24 @@ def save_token_costs(self, sol_price, neon_price): self.neon_price = neon_price @allure.step("Verify operator profit") - def assert_profit(self, sol_diff, neon_diff): + def assert_profit(self, sol_diff, token_diff, token_price, token_name): sol_amount = sol_diff / LAMPORT_PER_SOL - if neon_diff < 0: - raise AssertionError(f"NEON has negative difference {neon_diff}") - neon_amount = neon_diff - sol_cost = Decimal(sol_amount, DECIMAL_CONTEXT) * Decimal( - self.sol_price, DECIMAL_CONTEXT - ) - neon_cost = Decimal(neon_amount, DECIMAL_CONTEXT) * Decimal( - self.neon_price, DECIMAL_CONTEXT - ) - - msg = "Operator receive {:.9f} NEON ({:.2f} $) and spend {:.9f} SOL ({:.2f} $), profit - {:.9f}% ".format( - neon_amount, - neon_cost, + if token_diff < 0: + raise AssertionError(f"NEON has negative difference {token_diff}") + sol_cost = Decimal(sol_amount, DECIMAL_CONTEXT) * Decimal(self.sol_price, DECIMAL_CONTEXT) + token_cost = Decimal(token_diff, DECIMAL_CONTEXT) * Decimal(token_price, DECIMAL_CONTEXT) + + msg = "Operator receive {:.9f} {} ({:.2f} $) and spend {:.9f} SOL ({:.2f} $), profit - {:.9f}% ".format( + token_diff, + token_name, + token_cost, sol_amount, sol_cost, - ((neon_cost - sol_cost) / sol_cost * 100), + ((token_cost - sol_cost) / sol_cost * 100), ) + print(msg) with allure.step(msg): - assert neon_cost > sol_cost, msg + assert token_cost > sol_cost, msg @allure.step("Get single transaction gas") def get_single_transaction_gas(self): @@ -112,9 +110,7 @@ def get_single_transaction_gas(self): @allure.step("Check transaction used ALT") def check_alt_on(self, sol_client, receipt, accounts_quantity): - solana_trx = self.web3_client.get_solana_trx_by_neon( - receipt["transactionHash"].hex() - ) + solana_trx = self.web3_client.get_solana_trx_by_neon(receipt["transactionHash"].hex()) wait_condition( lambda: sol_client.get_transaction( Signature.from_string(solana_trx["result"][0]), @@ -146,100 +142,100 @@ def get_gas_used_percent(self, receipt): pass @pytest.mark.only_stands - def test_account_creation(self): + def test_account_creation(self, client_and_price): """Verify account creation spend SOL""" + w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() - acc = self.web3_client.eth.account.create() - assert self.web3_client.eth.get_balance(acc.address) == Decimal(0) + neon_balance_before = self.operator.get_token_balance(w3_client) + acc = w3_client.eth.account.create() + assert w3_client.get_balance(acc.address) == Decimal(0) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + neon_balance_after = self.operator.get_token_balance(w3_client) assert neon_balance_after == neon_balance_before assert sol_balance_after == sol_balance_before - def test_send_neon_to_unexist_account(self): - """Verify how many cost neon send to new user""" + def test_send_neon_to_unexist_account(self, account_with_all_tokens, client_and_price): + """Verify how many cost transfer of native chain token to new user""" + w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() - acc2 = self.web3_client.create_account() - receipt = self.web3_client.send_neon(self.acc, acc2, 5) - - assert self.web3_client.get_balance(acc2) == 5 + token_balance_before = self.operator.get_token_balance(w3_client) + transfer_value = 50000000 + acc2 = w3_client.create_account() + receipt = w3_client.send_tokens(account_with_all_tokens, acc2, transfer_value) + assert w3_client.get_balance(acc2) == transfer_value sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) sol_diff = sol_balance_before - sol_balance_after assert sol_balance_before > sol_balance_after, "Operator SOL balance incorrect" - self.assert_profit(sol_diff, neon_balance_after - neon_balance_before) + token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) + self.assert_profit(sol_diff, token_diff, token_price, w3_client.native_token_name) self.get_gas_used_percent(receipt) - def test_send_neon_to_exist_account(self): - """Verify how many cost neon send to use who was already initialized""" - acc2 = self.web3_client.create_account() - self.web3_client.send_neon(self.acc, acc2, 1) + def test_send_tokens_to_exist_account(self, account_with_all_tokens, client_and_price): + """Verify how many cost token send to use who was already initialized""" + w3_client, token_price = client_and_price + acc2 = w3_client.create_account() + transfer_value = 5000 + w3_client.send_tokens(account_with_all_tokens, acc2, transfer_value // 2) - assert self.web3_client.get_balance(acc2) == 1 + assert w3_client.get_balance(acc2) == transfer_value // 2 sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() - receipt = self.web3_client.send_neon(self.acc, acc2, 5) + token_balance_before = self.operator.get_token_balance(w3_client) + receipt = w3_client.send_tokens(account_with_all_tokens, acc2, transfer_value // 2) - assert self.web3_client.get_balance(acc2) == 6 + assert w3_client.get_balance(acc2) == transfer_value sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) sol_diff = sol_balance_before - sol_balance_after self.get_gas_used_percent(receipt) - assert ( - sol_balance_before > sol_balance_after - ), "Operator balance after send tx doesn't changed" - self.assert_profit(sol_diff, neon_balance_after - neon_balance_before) - - def test_send_when_not_enough_neon_to_gas(self): - acc2 = self.web3_client.create_account() + assert sol_balance_before > sol_balance_after, "Operator balance after send tx doesn't changed" + token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) + self.assert_profit(sol_diff, token_diff, token_price, w3_client.native_token_name) - assert self.web3_client.get_balance(acc2) == 0 + def test_send_when_not_enough_tokens_to_gas(self, client_and_price, account_with_all_tokens): + w3_client, token_price = client_and_price + acc2 = w3_client.create_account() - self.web3_client.send_neon(self.acc, acc2, 1) + assert w3_client.get_balance(acc2) == 0 + transfer_amount = 5000 + w3_client.send_tokens(account_with_all_tokens, acc2, transfer_amount) sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + token_balance_before = self.operator.get_token_balance(w3_client) - acc3 = self.web3_client.create_account() + acc3 = w3_client.create_account() with pytest.raises(ValueError, match=INSUFFICIENT_FUNDS_ERROR) as e: - self.web3_client.send_neon(acc2, acc3, 1) + w3_client.send_tokens(acc2, acc3, transfer_amount) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) assert sol_balance_before == sol_balance_after - assert neon_balance_before == neon_balance_after + assert token_balance_before == token_balance_after - def test_erc20wrapper_transfer(self, erc20_spl_mintable): + def test_erc20wrapper_transfer(self, erc20_wrapper, client_and_price): + w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() - - assert ( - erc20_spl_mintable.contract.functions.balanceOf(self.acc.address).call() - == 0 - ) - transfer_tx = erc20_spl_mintable.transfer(erc20_spl_mintable.account, self.acc, 25) - - assert ( - erc20_spl_mintable.contract.functions.balanceOf(self.acc.address).call() - == 25 - ) + token_balance_before = self.operator.get_token_balance(w3_client) + assert erc20_wrapper.contract.functions.balanceOf(self.acc.address).call() == 0 + transfer_tx = erc20_wrapper.transfer(erc20_wrapper.account, self.acc, 25) + assert erc20_wrapper.contract.functions.balanceOf(self.acc.address).call() == 25 + wait_condition(lambda: sol_balance_before > self.operator.get_solana_balance()) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) sol_diff = sol_balance_before - sol_balance_after assert sol_balance_before > sol_balance_after + token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) - self.assert_profit(sol_diff, neon_balance_after - neon_balance_before) + self.assert_profit(sol_diff, token_diff, token_price, w3_client.native_token_name) self.get_gas_used_percent(transfer_tx) @@ -248,18 +244,14 @@ def test_withdraw_neon_unexisting_ata(self, pytestconfig: Config): self.sol_client.request_airdrop(sol_user.public_key, 5 * LAMPORT_PER_SOL) sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + neon_balance_before = self.operator.get_token_balance(self.web3_client) user_neon_balance_before = self.web3_client.get_balance(self.acc) move_amount = self.web3_client._web3.to_wei(5, "ether") - contract, _ = self.web3_client.deploy_and_get_contract( - "precompiled/NeonToken", "0.8.10", account=self.acc - ) + contract, _ = self.web3_client.deploy_and_get_contract("precompiled/NeonToken", "0.8.10", account=self.acc) - instruction_tx = contract.functions.withdraw( - bytes(sol_user.public_key) - ).build_transaction( + instruction_tx = contract.functions.withdraw(bytes(sol_user.public_key)).build_transaction( { "from": self.acc.address, "nonce": self.web3_client.eth.get_transaction_count(self.acc.address), @@ -272,20 +264,21 @@ def test_withdraw_neon_unexisting_ata(self, pytestconfig: Config): assert (user_neon_balance_before - self.web3_client.get_balance(self.acc)) > 5 - balance = self.sol_client.get_account_info_json_parsed( - sol_user.public_key, commitment=Commitment("confirmed") - ) + balance = self.sol_client.get_account_info_json_parsed(sol_user.public_key, commitment=Commitment("confirmed")) assert int(balance.value.lamports) == int(move_amount / 1_000_000_000) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + neon_balance_after = self.operator.get_token_balance(self.web3_client) assert sol_balance_before > sol_balance_after assert neon_balance_after > neon_balance_before + neon_diff = self.web3_client.to_main_currency(neon_balance_after - neon_balance_before) self.assert_profit( sol_balance_before - sol_balance_after, - neon_balance_after - neon_balance_before, + neon_diff, + self.neon_price, + self.web3_client.native_token_name, ) self.get_gas_used_percent(receipt) @@ -297,11 +290,7 @@ def test_withdraw_neon_existing_ata(self, pytestconfig, neon_mint): wait_condition(lambda: self.sol_client.get_balance(sol_user.public_key) != 0) trx = Transaction() - trx.add( - create_associated_token_account( - sol_user.public_key, sol_user.public_key, neon_mint - ) - ) + trx.add(create_associated_token_account(sol_user.public_key, sol_user.public_key, neon_mint)) opts = TxOpts(skip_preflight=True, skip_confirmation=False) self.sol_client.send_transaction(trx, sol_user, opts=opts) @@ -309,18 +298,14 @@ def test_withdraw_neon_existing_ata(self, pytestconfig, neon_mint): dest_token_acc = get_associated_token_address(sol_user.public_key, neon_mint) sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + neon_balance_before = self.operator.get_token_balance(self.web3_client) user_neon_balance_before = self.web3_client.get_balance(self.acc) move_amount = self.web3_client._web3.to_wei(5, "ether") - contract, _ = self.web3_client.deploy_and_get_contract( - "precompiled/NeonToken", "0.8.10", account=self.acc - ) + contract, _ = self.web3_client.deploy_and_get_contract("precompiled/NeonToken", "0.8.10", account=self.acc) - instruction_tx = contract.functions.withdraw( - bytes(sol_user.public_key) - ).build_transaction( + instruction_tx = contract.functions.withdraw(bytes(sol_user.public_key)).build_transaction( { "from": self.acc.address, "nonce": self.web3_client.eth.get_transaction_count(self.acc.address), @@ -334,194 +319,185 @@ def test_withdraw_neon_existing_ata(self, pytestconfig, neon_mint): assert (user_neon_balance_before - self.web3_client.get_balance(self.acc)) > 5 balances = json.loads( - self.sol_client.get_token_account_balance( - dest_token_acc, Commitment("confirmed") - ).to_json() - ) - assert int(balances["result"]["value"]["amount"]) == int( - move_amount / 1_000_000_000 + self.sol_client.get_token_account_balance(dest_token_acc, Commitment("confirmed")).to_json() ) + assert int(balances["result"]["value"]["amount"]) == int(move_amount / 1_000_000_000) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + neon_balance_after = self.operator.get_token_balance(self.web3_client) assert sol_balance_before > sol_balance_after assert neon_balance_after > neon_balance_before + neon_diff = self.web3_client.to_main_currency(neon_balance_after - neon_balance_before) self.assert_profit( sol_balance_before - sol_balance_after, - neon_balance_after - neon_balance_before, + neon_diff, + self.neon_price, + self.web3_client.native_token_name, ) self.get_gas_used_percent(receipt) - def test_erc20_contract(self): - """Verify ERC20 token send""" + def test_erc20_contract_deploy(self, client_and_price, account_with_all_tokens, web3_client_sol, web3_client): + """Verify ERC20 contract deploy""" + w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + token_balance_before = self.operator.get_token_balance(w3_client) - contract, contract_deploy_tx = self.web3_client.deploy_and_get_contract( + make_nonce_the_biggest_for_chain(account_with_all_tokens, w3_client, [web3_client, web3_client_sol]) + + contract, contract_deploy_tx = w3_client.deploy_and_get_contract( "EIPs/ERC20/ERC20.sol", "0.8.8", - self.acc, + account_with_all_tokens, constructor_args=["Test Token", "TT", 1000], ) - assert contract.functions.balanceOf(self.acc.address).call() == 1000 + assert contract.functions.balanceOf(account_with_all_tokens.address).call() == 1000 sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) sol_diff = sol_balance_before - sol_balance_after assert sol_balance_before > sol_balance_after - self.assert_profit(sol_diff, neon_balance_after - neon_balance_before) + token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) + self.assert_profit(sol_diff, token_diff, token_price, w3_client.native_token_name) self.get_gas_used_percent(contract_deploy_tx) - def test_erc20_transfer(self): + def test_erc20_transfer(self, client_and_price, account_with_all_tokens, web3_client_sol, web3_client): """Verify ERC20 token send""" - - contract = ERC20(self.web3_client, self.faucet, owner=self.acc) + w3_client, token_price = client_and_price + make_nonce_the_biggest_for_chain(account_with_all_tokens, w3_client, [web3_client, web3_client_sol]) + contract = ERC20(w3_client, self.faucet, owner=account_with_all_tokens) sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + token_balance_before = self.operator.get_token_balance(w3_client) - acc2 = self.web3_client.create_account() + acc2 = w3_client.create_account() - transfer_tx = contract.transfer( self.acc, acc2, 25) + transfer_tx = contract.transfer(account_with_all_tokens, acc2, 25) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) sol_diff = sol_balance_before - sol_balance_after assert sol_balance_before > sol_balance_after - assert neon_balance_after > neon_balance_before + assert token_balance_after > token_balance_before - self.assert_profit(sol_diff, neon_balance_after - neon_balance_before) + token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) + self.assert_profit(sol_diff, token_diff, token_price, w3_client.native_token_name) self.get_gas_used_percent(transfer_tx) - def test_deploy_small_contract_less_100tx(self, sol_price): + def test_deploy_small_contract_less_100tx(self, account_with_all_tokens, client_and_price, web3_client_sol, + web3_client): """Verify we are bill minimum for 100 instruction""" + w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + token_balance_before = self.operator.get_token_balance(w3_client) - contract, contract_deploy_tx = self.web3_client.deploy_and_get_contract( - "common/Counter", "0.8.10", account=self.acc - ) + make_nonce_the_biggest_for_chain(account_with_all_tokens, w3_client, [web3_client, web3_client_sol]) + contract, _ = w3_client.deploy_and_get_contract("common/Counter", "0.8.10", account=account_with_all_tokens) sol_balance_after_deploy = self.operator.get_solana_balance() - neon_balance_after_deploy = self.operator.get_neon_balance() - - inc_tx = contract.functions.inc().build_transaction( - { - "from": self.acc.address, - "nonce": self.web3_client.eth.get_transaction_count(self.acc.address), - "gasPrice": self.web3_client.gas_price(), - } - ) + token_balance_after_deploy = self.operator.get_token_balance(w3_client) + tx = self.create_tx_object(account_with_all_tokens.address, estimate_gas=False, web3_client=w3_client) + inc_tx = contract.functions.inc().build_transaction(tx) assert contract.functions.get().call() == 0 - receipt = self.web3_client.send_transaction(self.acc, inc_tx) + receipt = w3_client.send_transaction(account_with_all_tokens, inc_tx) assert contract.functions.get().call() == 1 sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) assert sol_balance_before > sol_balance_after_deploy > sol_balance_after - assert neon_balance_after > neon_balance_after_deploy > neon_balance_before - self.assert_profit( - sol_balance_before - sol_balance_after, - neon_balance_after - neon_balance_before, - ) + assert token_balance_after > token_balance_after_deploy > token_balance_before + token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) + self.assert_profit(sol_balance_before - sol_balance_after, token_diff, token_price, w3_client.native_token_name) self.get_gas_used_percent(receipt) - def test_deploy_small_contract_less_gas(self): - sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + def test_deploy_small_contract_less_gas(self, account_with_all_tokens, client_and_price): + w3_client, token_price = client_and_price + sol_balance_before = self.operator.get_solana_balance() + token_balance_before = self.operator.get_token_balance(w3_client) with pytest.raises(ValueError, match=GAS_LIMIT_ERROR): - self.web3_client.deploy_and_get_contract( - "common/Counter", "0.8.10", gas=1000, account=self.acc - ) + w3_client.deploy_and_get_contract("common/Counter", "0.8.10", gas=1000, account=account_with_all_tokens) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) assert sol_balance_before == sol_balance_after - assert neon_balance_after == neon_balance_before + assert token_balance_after == token_balance_before - def test_deploy_small_contract_less_neon(self): - acc2 = self.web3_client.create_account() - self.web3_client.send_neon(self.acc, acc2, 0.001) + def test_deploy_small_contract_less_tokens(self, account_with_all_tokens, client_and_price): + w3_client, token_price = client_and_price + acc2 = w3_client.create_account() + w3_client.send_tokens(account_with_all_tokens, acc2, 10) sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + token_balance_before = self.operator.get_token_balance(w3_client) with pytest.raises(ValueError, match=INSUFFICIENT_FUNDS_ERROR): - self.web3_client.deploy_and_get_contract("common/Counter", "0.8.10", account=acc2) + w3_client.deploy_and_get_contract("common/Counter", "0.8.10", account=acc2) sol_balance_after_deploy = self.operator.get_solana_balance() - neon_balance_after_deploy = self.operator.get_neon_balance() + token_balance_after_deploy = self.operator.get_token_balance(w3_client) assert sol_balance_before == sol_balance_after_deploy - assert neon_balance_before == neon_balance_after_deploy + assert token_balance_before == token_balance_after_deploy - def test_deploy_to_losted_contract_account(self): + def test_deploy_to_losted_contract_account(self, account_with_all_tokens, client_and_price): + w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + token_balance_before = self.operator.get_token_balance(w3_client) - acc2 = self.web3_client.create_account() - self.web3_client.send_neon(self.acc, acc2, 0.001) + acc2 = w3_client.create_account() + w3_client.send_tokens(account_with_all_tokens, acc2, 1) with pytest.raises(ValueError, match=INSUFFICIENT_FUNDS_ERROR): - self.web3_client.deploy_and_get_contract("common/Counter", "0.8.10", account=acc2) - - self.web3_client.send_neon(self.acc, acc2, 50) - contract, contract_deploy_tx = self.web3_client.deploy_and_get_contract( - "common/Counter", "0.8.10", account=acc2 - ) + w3_client.deploy_and_get_contract("common/Counter", "0.8.10", account=acc2) + w3_client.send_tokens(account_with_all_tokens, acc2, int(w3_client.get_balance(account_with_all_tokens) // 10)) + contract, contract_deploy_tx = w3_client.deploy_and_get_contract("common/Counter", "0.8.10", account=acc2) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) assert sol_balance_before > sol_balance_after - assert neon_balance_after > neon_balance_before + assert token_balance_after > token_balance_before - self.assert_profit( - sol_balance_before - sol_balance_after, - neon_balance_after - neon_balance_before, - ) + token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) + self.assert_profit(sol_balance_before - sol_balance_after, token_diff, token_price, w3_client.native_token_name) self.get_gas_used_percent(contract_deploy_tx) - def test_contract_get_is_free(self): + def test_contract_get_is_free(self, counter_contract, client_and_price, account_with_all_tokens): """Verify that get contract calls is free""" - contract, contract_deploy_tx = self.web3_client.deploy_and_get_contract( - "common/Counter", "0.8.10", account=self.acc - ) - + w3_client, token_price = client_and_price sol_balance_after_deploy = self.operator.get_solana_balance() - neon_balance_after_deploy = self.operator.get_neon_balance() + token_balance_after_deploy = self.operator.get_token_balance(w3_client) - user_balance_before = self.web3_client.get_balance(self.acc) - assert contract.functions.get().call() == 0 + user_balance_before = w3_client.get_balance(account_with_all_tokens) + assert counter_contract.functions.get().call() == 0 - assert self.web3_client.get_balance(self.acc) == user_balance_before + assert w3_client.get_balance(account_with_all_tokens) == user_balance_before sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) assert sol_balance_after_deploy == sol_balance_after - assert neon_balance_after_deploy == neon_balance_after + assert token_balance_after_deploy == token_balance_after @pytest.mark.xfail(reason="https://neonlabs.atlassian.net/browse/NDEV-699") def test_cost_resize_account(self): """Verify how much cost account resize""" sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + neon_balance_before = self.operator.get_token_balance(self.web3_client) contract, contract_deploy_tx = self.web3_client.deploy_and_get_contract( "common/IncreaseStorage", "0.8.10", account=self.acc ) sol_balance_before_increase = self.operator.get_solana_balance() - neon_balance_before_increase = self.operator.get_neon_balance() + neon_balance_before_increase = self.operator.get_token_balance(self.web3_client) inc_tx = contract.functions.inc().build_transaction( { @@ -534,470 +510,330 @@ def test_cost_resize_account(self): instruction_receipt = self.web3_client.send_transaction(self.acc, inc_tx) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + neon_balance_after = self.operator.get_token_balance(self.web3_client) - assert ( - sol_balance_before > sol_balance_before_increase > sol_balance_after - ), "SOL Balance not changed" - assert ( - neon_balance_after > neon_balance_before_increase > neon_balance_before - ), "NEON Balance incorrect" + assert sol_balance_before > sol_balance_before_increase > sol_balance_after, "SOL Balance not changed" + assert neon_balance_after > neon_balance_before_increase > neon_balance_before, "NEON Balance incorrect" + neon_diff = self.web3_client.to_main_currency(neon_balance_after - neon_balance_before) self.assert_profit( sol_balance_before - sol_balance_after, - neon_balance_after - neon_balance_before, + neon_diff, + self.neon_price, + self.web3_client.native_token_name, ) self.get_gas_used_percent(instruction_receipt) - def test_cost_resize_account_less_neon(self): + def test_cost_resize_account_less_tokens(self, account_with_all_tokens, client_and_price): """Verify how much cost account resize""" - contract, contract_deploy_tx = self.web3_client.deploy_and_get_contract( - "common/IncreaseStorage", "0.8.10", account=self.acc + w3_client, token_price = client_and_price + make_nonce_the_biggest_for_chain(account_with_all_tokens, w3_client, [self.web3_client, self.web3_client_sol]) + contract, contract_deploy_tx = w3_client.deploy_and_get_contract( + "common/IncreaseStorage", "0.8.10", account=account_with_all_tokens ) - acc2 = self.web3_client.create_account() - self.web3_client.send_neon(self.acc, acc2, 0.001) + acc2 = w3_client.create_account() + w3_client.send_tokens(account_with_all_tokens, acc2, 1000) sol_balance_before_increase = self.operator.get_solana_balance() - neon_balance_before_increase = self.operator.get_neon_balance() + token_balance_before_increase = self.operator.get_token_balance(w3_client) - inc_tx = contract.functions.inc().build_transaction( - { - "from": acc2.address, - "nonce": self.web3_client.eth.get_transaction_count(acc2.address), - "gasPrice": self.web3_client.gas_price(), - } - ) + tx = self.create_tx_object(acc2.address, estimate_gas=False, web3_client=w3_client) + inc_tx = contract.functions.inc().build_transaction(tx) with pytest.raises(ValueError, match=INSUFFICIENT_FUNDS_ERROR): - self.web3_client.send_transaction(acc2, inc_tx) + w3_client.send_transaction(acc2, inc_tx) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) - assert ( - sol_balance_before_increase == sol_balance_after - ), "SOL Balance not changed" - assert ( - neon_balance_after == neon_balance_before_increase - ), "NEON Balance incorrect" + assert sol_balance_before_increase == sol_balance_after, "SOL Balance not changed" + assert token_balance_after == token_balance_before_increase, "TOKEN Balance incorrect" - def test_failed_tx_when_less_gas(self): + def test_failed_tx_when_less_gas(self, account_with_all_tokens, client_and_price): """Don't get money from user if tx failed""" + w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + token_balance_before = self.operator.get_token_balance(w3_client) - acc2 = self.web3_client.create_account() + acc2 = w3_client.create_account() - user_balance_before = self.web3_client.get_balance(self.acc) + user_balance_before = w3_client.get_balance(account_with_all_tokens) with pytest.raises(ValueError, match=GAS_LIMIT_ERROR): - receipt = self.web3_client.send_neon(self.acc, acc2, 5, gas=100) + w3_client.send_tokens(account_with_all_tokens, acc2, 5000, gas=100) - assert user_balance_before == self.web3_client.get_balance(self.acc) + assert user_balance_before == w3_client.get_balance(account_with_all_tokens) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) assert sol_balance_before == sol_balance_after - assert neon_balance_after == neon_balance_before - # self.assert_profit(sol_balance_before - sol_balance_after, neon_balance_after - neon_balance_before) + assert token_balance_after == token_balance_before - def test_contract_interact_more_500_steps(self): + def test_contract_interact_more_500_steps(self, counter_contract, client_and_price, account_with_all_tokens): """Deploy a contract with more 500 instructions""" - sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() - - contract, contract_deploy_tx = self.web3_client.deploy_and_get_contract( - "common/Counter", "0.8.10", account=self.acc - ) + w3_client, token_price = client_and_price - sol_balance_before_instruction = self.operator.get_solana_balance() - neon_balance_before_instruction = self.operator.get_neon_balance() - - instruction_tx = contract.functions.moreInstruction( - 0, 100 - ).build_transaction( # 1086 steps in evm - { - "from": self.acc.address, - "nonce": self.web3_client.eth.get_transaction_count(self.acc.address), - "gasPrice": self.web3_client.gas_price(), - } - ) - instruction_receipt = self.web3_client.send_transaction( - self.acc, instruction_tx - ) + sol_balance_before = self.operator.get_solana_balance() + token_balance_before = self.operator.get_token_balance(w3_client) + tx = self.create_tx_object(account_with_all_tokens.address, estimate_gas=False, web3_client=w3_client) + instruction_tx = counter_contract.functions.moreInstruction(0, 100).build_transaction(tx) # 1086 steps in evm + instruction_receipt = w3_client.send_transaction(account_with_all_tokens, instruction_tx) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) - assert ( - sol_balance_before > sol_balance_before_instruction > sol_balance_after - ), "SOL Balance not changed" - assert ( - neon_balance_after > neon_balance_before_instruction > neon_balance_before - ), "NEON Balance incorrect" - self.assert_profit( - sol_balance_before_instruction - sol_balance_after, - neon_balance_after - neon_balance_before_instruction, - ) + assert sol_balance_before > sol_balance_after, "SOL Balance not changed" + assert token_balance_after > token_balance_before, "TOKEN Balance incorrect" + token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) + self.assert_profit(sol_balance_before - sol_balance_after, token_diff, token_price, w3_client.native_token_name) self.get_gas_used_percent(instruction_receipt) - def test_contract_interact_more_steps(self): + def test_contract_interact_more_steps(self, counter_contract, client_and_price, account_with_all_tokens): """Deploy a contract with more 500000 bpf""" - contract, contract_deploy_tx = self.web3_client.deploy_and_get_contract( - "common/Counter", "0.8.10", account=self.acc - ) + w3_client, token_price = client_and_price - sol_balance_before_instruction = self.operator.get_solana_balance() - neon_balance_before_instruction = self.operator.get_neon_balance() - - instruction_tx = contract.functions.moreInstruction(0, 1500).build_transaction( - { - "from": self.acc.address, - "nonce": self.web3_client.eth.get_transaction_count(self.acc.address), - "gasPrice": self.web3_client.gas_price(), - } - ) + sol_balance_before = self.operator.get_solana_balance() + token_balance_before = self.operator.get_token_balance(w3_client) + tx = self.create_tx_object(account_with_all_tokens.address, estimate_gas=False, web3_client=w3_client) + instruction_tx = counter_contract.functions.moreInstruction(0, 1500).build_transaction(tx) - instruction_receipt = self.web3_client.send_transaction( - self.acc, instruction_tx - ) + instruction_receipt = w3_client.send_transaction(account_with_all_tokens, instruction_tx) + wait_condition(lambda: sol_balance_before > self.operator.get_solana_balance()) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) - assert ( - sol_balance_before_instruction > sol_balance_after - ), "SOL Balance not changed" - assert ( - neon_balance_after > neon_balance_before_instruction - ), "NEON Balance incorrect" + assert sol_balance_before > sol_balance_after, "SOL Balance not changed" + assert token_balance_after > token_balance_before, "TOKEN Balance incorrect" + token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) self.assert_profit( - sol_balance_before_instruction - sol_balance_after, - neon_balance_after - neon_balance_before_instruction, + sol_balance_before - sol_balance_after, + token_diff, token_price, w3_client.native_token_name ) self.get_gas_used_percent(instruction_receipt) - def test_contract_interact_more_steps_less_gas(self): - """Deploy a contract with more 500000 bpf""" - contract, contract_deploy_tx = self.web3_client.deploy_and_get_contract( - "common/Counter", "0.8.10", account=self.acc - ) + def test_contract_interact_more_steps_less_gas(self, counter_contract, client_and_price, account_with_all_tokens): + """Interact a contract with more 500000 bpf and small amount of gas""" + w3_client, token_price = client_and_price - sol_balance_before_instruction = self.operator.get_solana_balance() - neon_balance_before_instruction = self.operator.get_neon_balance() + sol_balance_before = self.operator.get_solana_balance() + token_balance_before = self.operator.get_token_balance(w3_client) + + tx = self.create_tx_object(account_with_all_tokens.address, estimate_gas=False, web3_client=w3_client, gas=1000) + instruction_tx = counter_contract.functions.moreInstruction(0, 1500).build_transaction(tx) - instruction_tx = contract.functions.moreInstruction(0, 1500).build_transaction( - { - "from": self.acc.address, - "nonce": self.web3_client.eth.get_transaction_count(self.acc.address), - "gasPrice": self.web3_client.gas_price(), - "gas": 1000, - } - ) with pytest.raises(ValueError, match=GAS_LIMIT_ERROR): - self.web3_client.send_transaction(self.acc, instruction_tx) + w3_client.send_transaction(account_with_all_tokens, instruction_tx) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) - assert ( - sol_balance_after == sol_balance_before_instruction - ), "SOL Balance changes" - assert ( - neon_balance_after == neon_balance_before_instruction - ), "NEON Balance incorrect" + assert sol_balance_after == sol_balance_before, "SOL Balance changes" + assert token_balance_after == token_balance_before, "TOKEN Balance incorrect" - def test_contract_interact_more_steps_less_neon(self): + def test_contract_interact_more_steps_less_tokens(self, counter_contract, client_and_price, account_with_all_tokens): """Deploy a contract with more 500000 bpf""" - contract, contract_deploy_tx = self.web3_client.deploy_and_get_contract( - "common/Counter", "0.8.10", account=self.acc - ) - - acc2 = self.web3_client.create_account() - self.web3_client.send_neon(self.acc, acc2, 0.001) + w3_client, token_price = client_and_price + acc2 = w3_client.create_account() + w3_client.send_tokens(account_with_all_tokens, acc2, 100) - sol_balance_before_instruction = self.operator.get_solana_balance() - neon_balance_before_instruction = self.operator.get_neon_balance() + sol_balance_before = self.operator.get_solana_balance() + token_balance_before = self.operator.get_token_balance(w3_client) - instruction_tx = contract.functions.moreInstruction(0, 1500).build_transaction( - { - "from": acc2.address, - "nonce": self.web3_client.eth.get_transaction_count(acc2.address), - "gasPrice": self.web3_client.gas_price(), - } - ) + tx = self.create_tx_object(acc2.address, estimate_gas=False, web3_client=w3_client) + instruction_tx = counter_contract.functions.moreInstruction(0, 1500).build_transaction(tx) with pytest.raises(ValueError, match=INSUFFICIENT_FUNDS_ERROR): - self.web3_client.send_transaction(acc2, instruction_tx) + w3_client.send_transaction(acc2, instruction_tx) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) - assert ( - sol_balance_before_instruction == sol_balance_after - ), "SOL Balance changed" - assert ( - neon_balance_after == neon_balance_before_instruction - ), "NEON Balance incorrect" + assert sol_balance_before == sol_balance_after, "SOL Balance changed" + assert token_balance_after == token_balance_before, "TOKEN Balance incorrect" # @pytest.mark.xfail(reason="Unprofitable transaction, because we create account not in evm (will be fixed)") - def test_tx_interact_more_1kb(self): + def test_tx_interact_more_1kb(self, counter_contract, client_and_price, account_with_all_tokens): """Send to contract a big text (tx more than 1 kb)""" + w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + token_balance_before = self.operator.get_token_balance(w3_client) - contract, contract_deploy_tx = self.web3_client.deploy_and_get_contract( - "common/Counter", "0.8.10", account=self.acc - ) + tx = self.create_tx_object(account_with_all_tokens.address, estimate_gas=False, web3_client=w3_client) + instruction_tx = counter_contract.functions.bigString(BIG_STRING).build_transaction(tx) - sol_balance_before_instruction = self.operator.get_solana_balance() - neon_balance_before_instruction = self.operator.get_neon_balance() - - instruction_tx = contract.functions.bigString(BIG_STRING).build_transaction( - { - "from": self.acc.address, - "nonce": self.web3_client.eth.get_transaction_count(self.acc.address), - "gasPrice": self.web3_client.gas_price(), - } - ) - - instruction_receipt = self.web3_client.send_transaction( - self.acc, instruction_tx - ) + instruction_receipt = w3_client.send_transaction(account_with_all_tokens, instruction_tx) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) - assert ( - sol_balance_before > sol_balance_before_instruction > sol_balance_after - ), "SOL Balance not changed" - assert ( - neon_balance_after > neon_balance_before_instruction > neon_balance_before - ), "NEON Balance incorrect" + assert sol_balance_before > sol_balance_after, "SOL Balance not changed" + assert token_balance_after > token_balance_before, "TOKEN Balance incorrect" + token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) self.assert_profit( - sol_balance_before_instruction - sol_balance_after, - neon_balance_after - neon_balance_before_instruction, + sol_balance_before - sol_balance_after, + token_diff, token_price, w3_client.native_token_name ) self.get_gas_used_percent(instruction_receipt) - def test_tx_interact_more_1kb_less_neon(self): - """Send to contract a big text (tx more than 1 kb) when less neon""" - contract, contract_deploy_tx = self.web3_client.deploy_and_get_contract( - "common/Counter", "0.8.10", account=self.acc - ) + def test_tx_interact_more_1kb_less_tokens(self, counter_contract, client_and_price, account_with_all_tokens): + """Send to contract a big text (tx more than 1 kb) when less tokens""" + w3_client, token_price = client_and_price acc2 = self.web3_client.create_account() - self.web3_client.send_neon(self.acc, acc2, 0.001) + w3_client.send_tokens(account_with_all_tokens, acc2, 1000) - sol_balance_before_instruction = self.operator.get_solana_balance() - neon_balance_before_instruction = self.operator.get_neon_balance() + sol_balance_before = self.operator.get_solana_balance() + token_balance_before = self.operator.get_token_balance(w3_client) - instruction_tx = contract.functions.bigString(BIG_STRING).build_transaction( - { - "from": acc2.address, - "nonce": self.web3_client.eth.get_transaction_count(acc2.address), - "gasPrice": self.web3_client.gas_price(), - } - ) + tx = self.create_tx_object(acc2.address, estimate_gas=False, web3_client=w3_client) + instruction_tx = counter_contract.functions.bigString(BIG_STRING).build_transaction(tx) with pytest.raises(ValueError, match=INSUFFICIENT_FUNDS_ERROR): - instruction_receipt = self.web3_client.send_transaction( - acc2, instruction_tx - ) + w3_client.send_transaction(acc2, instruction_tx) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) - assert ( - sol_balance_before_instruction == sol_balance_after - ), "SOL Balance changed" - assert ( - neon_balance_after == neon_balance_before_instruction - ), "NEON Balance incorrect" + assert sol_balance_before == sol_balance_after, "SOL Balance changed" + assert token_balance_after == token_balance_before, "TOKEN Balance incorrect" - def test_tx_interact_more_1kb_less_gas(self): + def test_tx_interact_more_1kb_less_gas(self, counter_contract, client_and_price, account_with_all_tokens): """Send to contract a big text (tx more than 1 kb)""" - contract, contract_deploy_tx = self.web3_client.deploy_and_get_contract( - "common/Counter", "0.8.10", account=self.acc - ) + w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + token_balance_before = self.operator.get_token_balance(w3_client) - instruction_tx = contract.functions.bigString(BIG_STRING).build_transaction( - { - "from": self.acc.address, - "nonce": self.web3_client.eth.get_transaction_count(self.acc.address), - "gasPrice": self.web3_client.gas_price(), - "gas": 100, - } - ) + tx = self.create_tx_object(account_with_all_tokens.address, estimate_gas=False, web3_client=w3_client, gas=100) + instruction_tx = counter_contract.functions.bigString(BIG_STRING).build_transaction(tx) with pytest.raises(ValueError, match=GAS_LIMIT_ERROR): - receipt = self.web3_client.send_transaction(self.acc, instruction_tx) + w3_client.send_transaction(account_with_all_tokens, instruction_tx) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) assert sol_balance_before == sol_balance_after, "SOL Balance changed" - assert neon_balance_after == neon_balance_before, "NEON Balance incorrect" + assert token_balance_after == token_balance_before, "TOKEN Balance incorrect" + + def test_deploy_contract_more_1kb(self, client_and_price, account_with_all_tokens, web3_client, web3_client_sol): + w3_client, token_price = client_and_price - def test_deploy_contract_more_1kb(self): + make_nonce_the_biggest_for_chain(account_with_all_tokens, w3_client, [web3_client, web3_client_sol]) sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + token_balance_before = self.operator.get_token_balance(w3_client) - contract, contract_deploy_tx = self.web3_client.deploy_and_get_contract( - "common/Fat", "0.8.10", account=self.acc + contract, contract_deploy_tx = w3_client.deploy_and_get_contract( + "common/Fat", "0.8.10", account=account_with_all_tokens ) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) assert sol_balance_before > sol_balance_after - assert neon_balance_after > neon_balance_before + assert token_balance_after > token_balance_before + token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) self.assert_profit( sol_balance_before - sol_balance_after, - neon_balance_after - neon_balance_before, + token_diff, token_price, w3_client.native_token_name ) self.get_gas_used_percent(contract_deploy_tx) - def test_deploy_contract_more_1kb_less_neon(self): - acc2 = self.web3_client.create_account() - self.web3_client.send_neon(self.acc, acc2, 0.001) + def test_deploy_contract_more_1kb_less_tokens(self, client_and_price, account_with_all_tokens): + w3_client, token_price = client_and_price + acc2 = w3_client.create_account() + w3_client.send_tokens(account_with_all_tokens, acc2, 100) sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + token_balance_before = self.operator.get_token_balance(w3_client) with pytest.raises(ValueError, match=INSUFFICIENT_FUNDS_ERROR): - _, _ = self.web3_client.deploy_and_get_contract( - "common/Fat", "0.8.10", account=acc2 - ) + w3_client.deploy_and_get_contract("common/Fat", "0.8.10", account=acc2) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) assert sol_balance_before == sol_balance_after - assert neon_balance_after == neon_balance_before + assert token_balance_after == token_balance_before - def test_deploy_contract_more_1kb_less_gas(self): + def test_deploy_contract_more_1kb_less_gas(self, client_and_price, account_with_all_tokens): + w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + token_balance_before = self.operator.get_token_balance(w3_client) with pytest.raises(ValueError, match=GAS_LIMIT_ERROR): - self.web3_client.deploy_and_get_contract( - "common/Fat", "0.8.10", account=self.acc, gas=1000 - ) + w3_client.deploy_and_get_contract("common/Fat", "0.8.10", account=account_with_all_tokens, gas=1000) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) assert sol_balance_before == sol_balance_after - assert neon_balance_after == neon_balance_before + assert token_balance_after == token_balance_before - def test_deploy_contract_to_payed(self): - acc2 = self.web3_client.create_account() - self.web3_client.send_neon(self.acc, acc2, 10) + def test_deploy_contract_to_payed(self, client_and_price, account_with_all_tokens, web3_client, web3_client_sol): + w3_client, token_price = client_and_price + make_nonce_the_biggest_for_chain(account_with_all_tokens, w3_client, [web3_client, web3_client_sol]) + nonce = w3_client.eth.get_transaction_count(account_with_all_tokens.address) + contract_address = w3_client.keccak(rlp.encode((bytes.fromhex(self.acc.address[2:]), nonce)))[-20:] - nonce = self.web3_client.eth.get_transaction_count(self.acc.address) - contract_address = self.web3_client.keccak( - rlp.encode((bytes.fromhex(self.acc.address[2:]), nonce)) - )[-20:] - - self.web3_client.send_neon( - acc2, self.web3_client.to_checksum_address(contract_address.hex()), 0.5 - ) + w3_client.send_tokens(account_with_all_tokens, w3_client.to_checksum_address(contract_address.hex()), 5000) sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + token_balance_before = self.operator.get_token_balance(w3_client) - contract, contract_deploy_tx = self.web3_client.deploy_and_get_contract( - "common/Counter", "0.8.10", account=self.acc + contract, contract_deploy_tx = w3_client.deploy_and_get_contract( + "common/Counter", "0.8.10", account=account_with_all_tokens ) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) assert sol_balance_before > sol_balance_after, "SOL Balance not changed" - assert neon_balance_after > neon_balance_before, "NEON Balance incorrect" + assert token_balance_after > token_balance_before, "TOKEN Balance incorrect" + token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) self.assert_profit( sol_balance_before - sol_balance_after, - neon_balance_after - neon_balance_before, + token_diff, + token_price, w3_client.native_token_name ) self.get_gas_used_percent(contract_deploy_tx) - def test_deploy_contract_to_exist_unpayed(self): - acc2 = self.web3_client.create_account() - self.web3_client.send_neon(self.acc, acc2, 50) + def test_deploy_contract_to_exist_unpayed(self, client_and_price, account_with_all_tokens, web3_client, web3_client_sol): + w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + token_balance_before = self.operator.get_token_balance(w3_client) - nonce = self.web3_client.eth.get_transaction_count(acc2.address) - contract_address = self.web3_client.to_checksum_address( - self.web3_client.keccak( - rlp.encode((bytes.fromhex(acc2.address[2:]), nonce)) - )[-20:].hex() + make_nonce_the_biggest_for_chain(account_with_all_tokens, w3_client, [web3_client, web3_client_sol]) + nonce = w3_client.eth.get_transaction_count(account_with_all_tokens.address) + contract_address = w3_client.to_checksum_address( + w3_client.keccak(rlp.encode((bytes.fromhex(account_with_all_tokens.address[2:]), nonce)))[-20:].hex() ) with pytest.raises(ValueError, match=GAS_LIMIT_ERROR): - self.web3_client.send_neon(acc2, contract_address, 1, gas=1) + w3_client.send_tokens(account_with_all_tokens, contract_address, 100, gas=1) - _, contract_deploy_tx = self.web3_client.deploy_and_get_contract( - "common/Counter", "0.8.10", account=acc2 - ) + _, contract_deploy_tx = w3_client.deploy_and_get_contract("common/Counter", "0.8.10", account=account_with_all_tokens) sol_balance_after_deploy = self.operator.get_solana_balance() - neon_balance_after_deploy = self.operator.get_neon_balance() + token_balance_after_deploy = self.operator.get_token_balance(w3_client) assert sol_balance_before > sol_balance_after_deploy - assert neon_balance_after_deploy > neon_balance_before + assert token_balance_after_deploy > token_balance_before + token_diff = w3_client.to_main_currency(token_balance_after_deploy - token_balance_before) self.assert_profit( sol_balance_before - sol_balance_after_deploy, - neon_balance_after_deploy - neon_balance_before, + token_diff, + token_price, w3_client.native_token_name ) self.get_gas_used_percent(contract_deploy_tx) - def test_interact_with_contract_from_non_payed_user(self): - acc2 = self.web3_client.create_account() - self.faucet.request_neon(acc2.address, 10) - - sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() - - contract, contract_deploy_tx = self.web3_client.deploy_and_get_contract( - "common/Counter", "0.8.10", account=self.acc - ) - - sol_balance_after_deploy = self.operator.get_solana_balance() - neon_balance_after_deploy = self.operator.get_neon_balance() - - inc_tx = contract.functions.inc().build_transaction( - { - "from": acc2.address, - "nonce": self.web3_client.eth.get_transaction_count(acc2.address), - "gasPrice": self.web3_client.gas_price(), - } - ) - - self.web3_client.send_transaction(acc2, inc_tx) - - assert contract.functions.get().call() == 1 - - sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() - - assert sol_balance_before > sol_balance_after_deploy > sol_balance_after - assert neon_balance_after > neon_balance_after_deploy > neon_balance_before - self.assert_profit( - sol_balance_before - sol_balance_after, - neon_balance_after - neon_balance_before, - ) - @pytest.mark.timeout(960) def test_deploy_contract_alt_on(self, sol_client): """Trigger transaction than requires more than 30 accounts""" accounts_quantity = random.randint(31, 45) sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + neon_balance_before = self.operator.get_token_balance(self.web3_client) contract, _ = self.web3_client.deploy_and_get_contract( "common/ALT", "0.8.10", account=self.acc, constructor_args=[8] @@ -1012,9 +848,7 @@ def test_deploy_contract_alt_on(self, sol_client): ) receipt = self.web3_client.send_transaction(self.acc, tx) self.check_alt_on(sol_client, receipt, accounts_quantity) - solana_trx = self.web3_client.get_solana_trx_by_neon( - receipt["transactionHash"].hex() - ) + solana_trx = self.web3_client.get_solana_trx_by_neon(receipt["transactionHash"].hex()) sol_trx_with_alt = None for trx in solana_trx["result"]: @@ -1028,15 +862,9 @@ def test_deploy_contract_alt_on(self, sol_client): if not sol_trx_with_alt: raise ValueError(f"There are no lookup table for {solana_trx}") - operator = PublicKey( - sol_trx_with_alt.value.transaction.transaction.message.account_keys[0] - ) + operator = PublicKey(sol_trx_with_alt.value.transaction.transaction.message.account_keys[0]) - alt_address = ( - sol_trx_with_alt.value.transaction.transaction.message.address_table_lookups[ - 0 - ].account_key - ) + alt_address = sol_trx_with_alt.value.transaction.transaction.message.address_table_lookups[0].account_key alt_balance = sol_client.get_balance(PublicKey(alt_address)).value operator_balance = sol_client.get_balance(operator).value @@ -1045,13 +873,15 @@ def test_deploy_contract_alt_on(self, sol_client): timeout_sec=120, ) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + neon_balance_after = self.operator.get_token_balance(self.web3_client) assert sol_balance_before > sol_balance_after assert neon_balance_after > neon_balance_before + neon_diff = self.web3_client.to_main_currency(neon_balance_after - neon_balance_before) self.assert_profit( sol_balance_before - sol_balance_after - alt_balance, - neon_balance_after - neon_balance_before, + neon_diff, + self.neon_price, self.web3_client.native_token_name ) # the charge for alt creating should be returned wait_condition( @@ -1061,45 +891,42 @@ def test_deploy_contract_alt_on(self, sol_client): ) assert ( - operator_balance + alt_balance - TX_COST * 2 - == sol_client.get_balance(operator).value + operator_balance + alt_balance - TX_COST * 2 == sol_client.get_balance(operator).value ), "Operator balance after the return of the alt creation fee is not correct" self.get_gas_used_percent(receipt) - @pytest.mark.parametrize("accounts_quantity", [10]) - def test_deploy_contract_alt_off(self, sol_client, accounts_quantity): + def test_deploy_contract_alt_off(self, sol_client, client_and_price, account_with_all_tokens, web3_client, web3_client_sol): """Trigger transaction than requires less than 30 accounts""" + accounts_quantity = 10 + w3_client, token_price = client_and_price + make_nonce_the_biggest_for_chain(account_with_all_tokens, w3_client, [web3_client, web3_client_sol]) sol_balance_before = self.operator.get_solana_balance() - neon_balance_before = self.operator.get_neon_balance() + token_balance_before = self.operator.get_token_balance(w3_client) - contract, _ = self.web3_client.deploy_and_get_contract( - "common/ALT", "0.8.10", account=self.acc, constructor_args=[8] + contract, _ = w3_client.deploy_and_get_contract( + "common/ALT", "0.8.10", account=account_with_all_tokens, constructor_args=[8] ) sol_balance_after_deploy = self.operator.get_solana_balance() - neon_balance_after_deploy = self.operator.get_neon_balance() + token_balance_after_deploy = self.operator.get_token_balance(w3_client) - tx = contract.functions.fill(accounts_quantity).build_transaction( - { - "from": self.acc.address, - "nonce": self.web3_client.eth.get_transaction_count(self.acc.address), - "gasPrice": self.web3_client.gas_price(), - } - ) - receipt = self.web3_client.send_transaction(self.acc, tx) + tx = self.create_tx_object(account_with_all_tokens.address, estimate_gas=False, web3_client=w3_client) + intr = contract.functions.fill(accounts_quantity).build_transaction(tx) + receipt = w3_client.send_transaction(account_with_all_tokens, intr) block = int(receipt["blockNumber"]) response = wait_for_block(sol_client, block) self.check_alt_off(response) sol_balance_after = self.operator.get_solana_balance() - neon_balance_after = self.operator.get_neon_balance() + token_balance_after = self.operator.get_token_balance(w3_client) assert sol_balance_before > sol_balance_after_deploy > sol_balance_after - assert neon_balance_after > neon_balance_after_deploy > neon_balance_before + assert token_balance_after > token_balance_after_deploy > token_balance_before + token_diff = w3_client.to_main_currency(token_balance_after - token_balance_after_deploy) self.assert_profit( - sol_balance_before - sol_balance_after, - neon_balance_after - neon_balance_before, + sol_balance_after_deploy - sol_balance_after, + token_diff, token_price, w3_client.native_token_name ) self.get_gas_used_percent(receipt) diff --git a/integration/tests/migrations/test_account_migration.py b/integration/tests/migrations/test_account_migration.py index 91fa510328..8fd0f69c60 100644 --- a/integration/tests/migrations/test_account_migration.py +++ b/integration/tests/migrations/test_account_migration.py @@ -19,14 +19,14 @@ def accounts(web3_client): print("Before testing:") for acc in accounts: print( - f"Balance for {acc.address}: {web3_client.to_main_currency(web3_client.get_balance(acc.address))}, nonce: {web3_client.eth.get_transaction_count(acc.address)}" + f"Balance for {acc.address}: {web3_client.to_atomic_currency(web3_client.get_balance(acc.address))}, nonce: {web3_client.eth.get_transaction_count(acc.address)}" ) yield accounts print("After testing:") for acc in accounts: print( - f"Balance for {acc.address}: {web3_client.to_main_currency(web3_client.get_balance(acc.address))}, nonce: {web3_client.eth.get_transaction_count(acc.address)}" + f"Balance for {acc.address}: {web3_client.to_atomic_currency(web3_client.get_balance(acc.address))}, nonce: {web3_client.eth.get_transaction_count(acc.address)}" ) diff --git a/loadtesting/proxy/common/events.py b/loadtesting/proxy/common/events.py index f7606d04a9..57c289461f 100644 --- a/loadtesting/proxy/common/events.py +++ b/loadtesting/proxy/common/events.py @@ -28,7 +28,7 @@ def get_token_balance(op: operator.Operator) -> tp.Dict: """Return tokens balance""" - return dict(neon=op.get_neon_balance(), sol=op.get_solana_balance()) + return dict(neon=op.get_token_balance(), sol=op.get_solana_balance()) def execute_before(*attrs) -> tp.Callable: diff --git a/utils/operator.py b/utils/operator.py index 2969e29c7d..b4d6c417db 100644 --- a/utils/operator.py +++ b/utils/operator.py @@ -1,10 +1,8 @@ -import time import typing as tp import solana.rpc.api from solana.publickey import PublicKey from solana.rpc.commitment import Confirmed -from solana.rpc.types import TokenAccountOpts from utils.web3client import NeonChainWeb3Client @@ -40,45 +38,11 @@ def get_solana_balance(self): balances.append(balance) return sum(balances) - def get_neon_balance(self): + def get_token_balance(self, w3_client=None): + if w3_client is None: + w3_client = self.web3 balances = [] if len(self._operator_neon_rewards_address) > 0: for addr in self._operator_neon_rewards_address: - balances.append(self.web3.get_balance(self.web3.to_checksum_address(addr.lower()))) - else: - for key in self._operator_keys: - if self._operator_keys[key] is None: - accounts = self.sol.get_token_accounts_by_owner_json_parsed( - PublicKey(key), TokenAccountOpts(mint=PublicKey(self._neon_token_mint)) - ) - self._operator_keys[key] = accounts.value[0]["pubkey"] - balances.append( - int( - self.sol.get_token_account_balance(PublicKey(self._operator_keys[key]), commitment=Confirmed).to_json()[ - "result" - ]["value"]["amount"] - ) - ) + balances.append(w3_client.get_balance(w3_client.to_checksum_address(addr.lower()))) return sum(balances) - - def wait_solana_balance_changed(self, current_balance, timeout=90): - """solana change balance only when blocks confirmed""" - started = time.time() - - while (time.time() - started) < timeout: - balance = self.get_solana_balance() - if balance != current_balance: - return balance - time.sleep(5) - raise TimeoutError(f"Operator solana balance didn't change for {timeout} seconds") - - def wait_neon_balance_changed(self, current_balance, timeout=90): - """solana change balance only when blocks confirmed""" - started = time.time() - - while (time.time() - started) < timeout: - balance = self.get_neon_balance() - if balance != current_balance: - return balance - time.sleep(5) - raise TimeoutError(f"Operator neon balance didn't change for {timeout} seconds") diff --git a/utils/web3client.py b/utils/web3client.py index 668e8f4dff..bc892a7b56 100644 --- a/utils/web3client.py +++ b/utils/web3client.py @@ -4,7 +4,6 @@ import typing as tp from decimal import Decimal -import solcx import web3 import web3.types import requests @@ -27,16 +26,18 @@ def __init__( self._proxy_url = proxy_url self._tracer_url = tracer_url self._chain_id = None - self._web3 = web3.Web3( - web3.HTTPProvider( - proxy_url, session=session, request_kwargs={"timeout": 30} - ) - ) - + self._web3 = web3.Web3(web3.HTTPProvider(proxy_url, session=session, request_kwargs={"timeout": 30})) def __getattr__(self, item): return getattr(self._web3, item) + @property + def native_token_name(self): + if self._proxy_url.split("/")[-1] != "solana": + return self._proxy_url.split("/")[-1].upper() + else: + return "NEON" + @property def chain_id(self): if self._chain_id is None: @@ -121,7 +122,7 @@ def deploy_contract( gas: tp.Optional[int] = 0, gas_price: tp.Optional[int] = None, constructor_args: tp.Optional[tp.List] = None, - value = 0 + value=0, ) -> web3.types.TxReceipt: """Proxy doesn't support send_transaction""" gas_price = gas_price or self.gas_price() @@ -150,9 +151,7 @@ def send_transaction( self, account: eth_account.signers.local.LocalAccount, transaction: tp.Dict, - gas_multiplier: tp.Optional[ - float - ] = None, # fix for some event depends transactions + gas_multiplier: tp.Optional[float] = None, # fix for some event depends transactions ) -> web3.types.TxReceipt: if "gasPrice" not in transaction: transaction["gasPrice"] = self.gas_price() @@ -162,9 +161,7 @@ def send_transaction( transaction["nonce"] = self.get_nonce(account) if gas_multiplier is not None: transaction["gas"] = int(transaction["gas"] * gas_multiplier) - instruction_tx = self._web3.eth.account.sign_transaction( - transaction, account.key - ) + instruction_tx = self._web3.eth.account.sign_transaction(transaction, account.key) signature = self._web3.eth.send_raw_transaction(instruction_tx.rawTransaction) return self._web3.eth.wait_for_transaction_receipt(signature) @@ -177,7 +174,7 @@ def deploy_and_get_contract( constructor_args: tp.Optional[tp.Any] = None, import_remapping: tp.Optional[dict] = None, gas: tp.Optional[int] = 0, - value = 0 + value=0, ) -> tp.Tuple[tp.Any, web3.types.TxReceipt]: contract_interface = helpers.get_contract_interface( contract, @@ -192,26 +189,20 @@ def deploy_and_get_contract( bytecode=contract_interface["bin"], constructor_args=constructor_args, gas=gas, - value=value + value=value, ) - contract = self.eth.contract( - address=contract_deploy_tx["contractAddress"], abi=contract_interface["abi"] - ) + contract = self.eth.contract(address=contract_deploy_tx["contractAddress"], abi=contract_interface["abi"]) return contract, contract_deploy_tx - def compile_by_vyper_and_deploy( - self, account, contract_name, constructor_args=None - ): + def compile_by_vyper_and_deploy(self, account, contract_name, constructor_args=None): import vyper # Import here because vyper prevent override decimal precision (uses in economy tests) contract_path = pathlib.Path.cwd() / "contracts" / "vyper" with open(contract_path / f"{contract_name}.vy") as f: contract_code = f.read() - contract_interface = vyper.compile_code( - contract_code, output_formats=["abi", "bytecode"] - ) + contract_interface = vyper.compile_code(contract_code, output_formats=["abi", "bytecode"]) contract_deploy_tx = self.deploy_contract( account, @@ -219,9 +210,7 @@ def compile_by_vyper_and_deploy( bytecode=contract_interface["bytecode"], constructor_args=constructor_args, ) - return self.eth.contract( - address=contract_deploy_tx["contractAddress"], abi=contract_interface["abi"] - ) + return self.eth.contract(address=contract_deploy_tx["contractAddress"], abi=contract_interface["abi"]) @staticmethod def text_to_bytes32(text: str) -> bytes: @@ -236,26 +225,24 @@ def call_function_at_address(self, contract_address, signature, args, result_typ result = self._web3.eth.call(tx) return abi.decode(result_types, result)[0] - def get_balance( - self, address: tp.Union[str, eth_account.signers.local.LocalAccount] - ): + def get_balance(self, address: tp.Union[str, eth_account.signers.local.LocalAccount]): if not isinstance(address, str): address = address.address return self._web3.eth.get_balance(address, "pending") - def get_deployed_contract(self, address, contract_file, contract_name=None, solc_version = "0.8.12"): + def get_deployed_contract(self, address, contract_file, contract_name=None, solc_version="0.8.12"): contract_interface = helpers.get_contract_interface(contract_file, solc_version, contract_name) contract = self.eth.contract(address=address, abi=contract_interface["abi"]) return contract def send_tokens( - self, - from_: eth_account.signers.local.LocalAccount, - to: tp.Union[str, eth_account.signers.local.LocalAccount], - value: int, - gas: tp.Optional[int] = 0, - gas_price: tp.Optional[int] = None, - nonce: int = None, + self, + from_: eth_account.signers.local.LocalAccount, + to: tp.Union[str, eth_account.signers.local.LocalAccount], + value: int, + gas: tp.Optional[int] = 0, + gas_price: tp.Optional[int] = None, + nonce: int = None, ) -> web3.types.TxReceipt: to_addr = to if isinstance(to, str) else to.address if nonce is None: @@ -267,7 +254,7 @@ def send_tokens( "gasPrice": gas_price or self.gas_price(), "gas": gas, "nonce": nonce, - "chainId": self.eth.chain_id + "chainId": self.eth.chain_id, } if transaction["gas"] == 0: transaction["gas"] = self.eth.estimate_gas(transaction) @@ -275,6 +262,13 @@ def send_tokens( tx = self.eth.send_raw_transaction(signed_tx.rawTransaction) return self.eth.wait_for_transaction_receipt(tx) + @staticmethod + def to_atomic_currency(amount): + return web3.Web3.to_wei(amount, "ether") + + def to_main_currency(self, value): + return web3.Web3.from_wei(value, "ether") + class NeonChainWeb3Client(Web3Client): def __init__( @@ -293,24 +287,18 @@ def create_account_with_balance( ): """Creates a new account with balance""" account = self.create_account() - balance_before = float( - self.from_wei(self.eth.get_balance(account.address), Unit.ETHER) - ) + balance_before = float(self.from_wei(self.eth.get_balance(account.address), Unit.ETHER)) if bank_account is not None: self.send_neon(bank_account, account, amount) else: faucet.request_neon(account.address, amount=amount) for _ in range(20): - if float( - self.from_wei(self.eth.get_balance(account.address), Unit.ETHER) - ) >= (balance_before + amount): + if float(self.from_wei(self.eth.get_balance(account.address), Unit.ETHER)) >= (balance_before + amount): break time.sleep(1) else: - raise AssertionError( - f"Balance didn't changed after 20 seconds ({account.address})" - ) + raise AssertionError(f"Balance didn't changed after 20 seconds ({account.address})") return account def send_neon( @@ -322,31 +310,8 @@ def send_neon( gas_price: tp.Optional[int] = None, nonce: int = None, ) -> web3.types.TxReceipt: - value = web3.Web3.to_wei(amount, "ether") - return self.send_tokens(from_, to, value,gas, gas_price, nonce) + return self.send_tokens(from_, to, value, gas, gas_price, nonce) - def get_balance( - self, address: tp.Union[str, eth_account.signers.local.LocalAccount] - ): + def get_ether_balance(self, address: tp.Union[str, eth_account.signers.local.LocalAccount]): return web3.Web3.from_wei(super().get_balance(address), "ether") - @staticmethod - def to_main_currency(amount): - return web3.Web3.to_wei(amount, "ether") - - -class SolChainWeb3Client(Web3Client): - def __init__(self, proxy_url: str): - super().__init__(f"{proxy_url}/sol") - - @staticmethod - def to_main_currency(amount): - return amount * 1_000_000_000 - -class NeonLikeChainWeb3Client(Web3Client): - def __init__(self, proxy_url: str, prefix: str): - super().__init__(f"{proxy_url}/{prefix}") - - @staticmethod - def to_main_currency(amount): - return web3.Web3.to_wei(amount, "ether") From 1a517351c2bd6bcca0ee23ad255a727c75ef90b4 Mon Sep 17 00:00:00 2001 From: romanova-natasha Date: Mon, 20 Nov 2023 10:04:30 +0300 Subject: [PATCH 25/56] add lost param in json schema add lost param in json schema --- integration/tests/tracer/schemas/debug_traceCall.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integration/tests/tracer/schemas/debug_traceCall.json b/integration/tests/tracer/schemas/debug_traceCall.json index b204a461dd..7a43330eb8 100644 --- a/integration/tests/tracer/schemas/debug_traceCall.json +++ b/integration/tests/tracer/schemas/debug_traceCall.json @@ -46,7 +46,8 @@ "type": [ "string", "null" - ] + ], + "items": {} }, "stack": { "type": [ From e831c898a73ded62717f36da2154a0b26c58515b Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Mon, 20 Nov 2023 12:23:45 +0100 Subject: [PATCH 26/56] fixes after review + test_send_transaction_with_small_gas_price is skipped --- conftest.py | 2 +- integration/tests/basic/helpers/errors.py | 1 + integration/tests/basic/rpc/test_rpc_get_transaction.py | 4 ++-- .../tests/basic/transfer/test_transaction_validation.py | 3 ++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/conftest.py b/conftest.py index 29762d01da..3877eef91f 100644 --- a/conftest.py +++ b/conftest.py @@ -164,7 +164,7 @@ def web3_client_sol(pytestconfig: Config) -> tp.Union[Web3Client, None]: return None -@pytest.fixture(scope="session", autouse=True) +@pytest.fixture(scope="session") def web3_client_abc(pytestconfig: Config) -> tp.Union[Web3Client, None]: if "abc" in pytestconfig.environment.network_ids: return Web3Client(f"{pytestconfig.environment.proxy_url}/abc") diff --git a/integration/tests/basic/helpers/errors.py b/integration/tests/basic/helpers/errors.py index 62542df349..c0d42831ff 100644 --- a/integration/tests/basic/helpers/errors.py +++ b/integration/tests/basic/helpers/errors.py @@ -18,3 +18,4 @@ class Error32602: BAD_FROM_ADDRESS = "bad from-address" BAD_TOPIC = "bad topic" BAD_TRANSACTION_ID_FORMAT = "bad transaction-id format" + INVALID_NONCE = "invalid nonce: value" diff --git a/integration/tests/basic/rpc/test_rpc_get_transaction.py b/integration/tests/basic/rpc/test_rpc_get_transaction.py index d86c987494..8c90bce4d5 100644 --- a/integration/tests/basic/rpc/test_rpc_get_transaction.py +++ b/integration/tests/basic/rpc/test_rpc_get_transaction.py @@ -237,8 +237,8 @@ def test_eth_get_transaction_receipt_when_hash_doesnt_exist(self, method): (["0x874E87B5ccb467f07Ca42cF82e11aD44c7be159F"], Error32000.CODE, Error32000.MISSING_ARGUMENT), ([None, 10], Error32602.CODE, Error32602.BAD_ADDRESS), (["123345", 10], Error32602.CODE, Error32602.BAD_ADDRESS), - (["0x874E87B5ccb467f07Ca42cF82e11aD44c7be159F", None], Error32000.CODE, - Error32000.OBJECT_CANT_BE_INTERPRETED_AS_INT) + (["0x874E87B5ccb467f07Ca42cF82e11aD44c7be159F", None], Error32602.CODE, + Error32602.INVALID_NONCE) ], ) def test_neon_get_transaction_by_sender_nonce_negative(self, params, error_code, error_message): diff --git a/integration/tests/basic/transfer/test_transaction_validation.py b/integration/tests/basic/transfer/test_transaction_validation.py index cb344a1ec7..dcec35dac7 100644 --- a/integration/tests/basic/transfer/test_transaction_validation.py +++ b/integration/tests/basic/transfer/test_transaction_validation.py @@ -88,6 +88,8 @@ def test_send_too_big_transaction(self): assert ErrorMessage.TOO_BIG_TRANSACTION.value in response["error"]["message"] assert response["error"]["code"] == -32000 + @pytest.mark.skip(reason="Test doesn't work with MINIMAL_GAS_PRICE in config. It should be fixed after adding " + "different parameters for different chains") def test_send_transaction_with_small_gas_price(self, new_account): """Check that transaction can't be accepted if gas value is too small""" gas_price = self.web3_client.gas_price() @@ -98,7 +100,6 @@ def test_send_transaction_with_small_gas_price(self, new_account): response = self.proxy_api.send_rpc( "eth_sendRawTransaction", [signed_tx.rawTransaction.hex()] ) - print(response) assert is_hex(response['result']) time.sleep(5) receipt = self.proxy_api.send_rpc(method="eth_getTransactionReceipt", params=[response["result"]]) From f7a76ea539881134f8e6ca69e5ac8a71f17a132c Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Mon, 20 Nov 2023 23:23:33 +0100 Subject: [PATCH 27/56] added new test description --- .../test_chain_id_dependent_opcodes.md | 12 ++++++++++++ docs/tests/audit/test_deposit.md | 19 +++++++++++-------- docs/tests/audit/test_nonce.md | 1 + .../audit/test_payment_in_different_tokens.md | 19 +++++++++++++++++++ .../transfer/test_transaction_validation.md | 14 +++++++------- .../tests/basic/rpc/test_rpc_get_logs.py | 1 + .../basic/test_payment_in_different_tokens.py | 2 +- .../transfer/test_transaction_validation.py | 6 ++---- 8 files changed, 54 insertions(+), 20 deletions(-) create mode 100644 docs/tests/audit/evm/opcodes/test_chain_id_dependent_opcodes.md create mode 100644 docs/tests/audit/test_payment_in_different_tokens.md diff --git a/docs/tests/audit/evm/opcodes/test_chain_id_dependent_opcodes.md b/docs/tests/audit/evm/opcodes/test_chain_id_dependent_opcodes.md new file mode 100644 index 0000000000..772196d992 --- /dev/null +++ b/docs/tests/audit/evm/opcodes/test_chain_id_dependent_opcodes.md @@ -0,0 +1,12 @@ +# Overview + +Verify EIP-1052: EXTCODEHASH opcode + +# Tests list + +| Test case | Description | XFailed | +|-------------------------------------------------------------------------------|------------------------------------------------------|---------| +| TestChainIdDependentOpcodes::test_chain_id_sol | Call 'chainid' opcode in sol chain contract | | +| TestChainIdDependentOpcodes::test_chain_id_neon | Call 'chainid' opcode in neon chain contract | | +| TestChainIdDependentOpcodes::test_balance_by_sol_contract | Call 'balance' opcode from sol-native chain contract | | +| TestChainIdDependentOpcodes::test_balance_by_neon_contract | Call 'balance' opcode from neon-native chain contract | | diff --git a/docs/tests/audit/test_deposit.md b/docs/tests/audit/test_deposit.md index d2b40e2b92..70c69869a4 100644 --- a/docs/tests/audit/test_deposit.md +++ b/docs/tests/audit/test_deposit.md @@ -4,11 +4,14 @@ Tests for check transfer tokens from solana->neon and neon->solana # Tests list -| Test case | Description | XFailed | -|-------------------------------------------------------------|---------------------------------------------------------|---------| -| TestDeposit::test_transfer_neon_from_solana_to_neon | Verify transfer neon solana -> neon | | -| TestDeposit::test_transfer_spl_token_from_solana_to_neon | Verify transfer spl token solana -> neon | | -| TestWithdraw::test_success_withdraw_to_non_existing_account | Transfer Neon from Neon -> Solana to unexisting account | | -| TestWithdraw::test_success_withdraw_to_existing_account | Transfer Neon from Neon -> Solana to existing account | | -| TestWithdraw::test_failed_withdraw_non_divisible_amount | Failed case to withdraw with bad amount | | -| TestWithdraw::test_failed_withdraw_insufficient_balance | Failed case to withdraw with bad amount | | +| Test case | Description | XFailed | +|---------------------------------------------------------------------|---------------------------------------------------------|---------| +| TestDeposit::test_transfer_neon_from_solana_to_neon | Verify transfer neon solana -> neon | | +| TestDeposit::test_transfer_spl_token_from_solana_to_neon | Verify transfer spl token solana -> neon | | +| TestDeposit::test_transfer_wrapped_sol_token_from_solana_to_neon | Verify transfer wrapped spl token solana -> neon | | +| TestDeposit::test_transfer_neon_from_solana_to_neon | Verify transfer neon tokens solana -> neon | | +| TestDeposit::test_create_and_transfer_new_token_from_solana_to_neon | Verify transfer created tokens solana -> neon | | +| TestWithdraw::test_success_withdraw_to_non_existing_account | Transfer Neon from Neon -> Solana to unexisting account | | +| TestWithdraw::test_success_withdraw_to_existing_account | Transfer Neon from Neon -> Solana to existing account | | +| TestWithdraw::test_failed_withdraw_non_divisible_amount | Failed case to withdraw with bad amount | | +| TestWithdraw::test_failed_withdraw_insufficient_balance | Failed case to withdraw with bad amount | | diff --git a/docs/tests/audit/test_nonce.md b/docs/tests/audit/test_nonce.md index 1919b72d42..be473622c2 100644 --- a/docs/tests/audit/test_nonce.md +++ b/docs/tests/audit/test_nonce.md @@ -16,3 +16,4 @@ Verify mempool work and how proxy handles it | TestNonce::test_send_the_same_transactions_if_accepted | Send one transaction twice | | | TestNonce::test_send_the_same_transactions_if_not_accepted | Send one transaction twice but first not accepted | | | TestNonce::test_send_transaction_with_old_nonce | Send transaction with old nonce | | +| TestNonce::test_nonce_with_several_chains | Check that nonces on different chains are independent | | diff --git a/docs/tests/audit/test_payment_in_different_tokens.md b/docs/tests/audit/test_payment_in_different_tokens.md new file mode 100644 index 0000000000..89337ced12 --- /dev/null +++ b/docs/tests/audit/test_payment_in_different_tokens.md @@ -0,0 +1,19 @@ +# Overview + +Verify mempool work and how proxy handles it + +# Tests list + +| Test case | Description | XFailed | +|-----------------------------------------------------------------------------|-------------------------------------------------------------------------------------------|---------| +| TestMultiplyChains::test_user_to_user_trx | Check transfer tokens between users on second chain | | +| TestMultiplyChains::test_user_to_contract_and_contract_to_user_trx | Check transfer tokens between contracts and users on second chain | | +| TestMultiplyChains::test_contract_to_contract_trx | Check transfer tokens between contracts | | +| TestMultiplyChains::test_user_to_contract_wrong_chain_id_trx | Check transfer tokens to contract deployed on different chain | | +| TestMultiplyChains::test_deploy_contract | Check deploy contracts on second chain | | +| TestMultiplyChains::test_deploy_contract_with_sending_tokens | Check deploy contracts with sending tokens on second chain | | +| TestMultiplyChains::test_deploy_contract_by_one_user_to_different_chains | Check deploy contracts on different chains by one user | | +| TestMultiplyChains::test_interact_with_contract_from_another_chain | Check interracting with contracts deployed on another chain | | +| TestMultiplyChains::test_transfer_neons_in_sol_chain | Check transfer neons through sol chain | | +| TestMultiplyChains::test_transfer_sol_in_neon_chain | Check transfer sol through neon chain | | +| TestMultiplyChains::test_call_different_chains_contracts_in_one_transaction | Check interracting with several contracts deployed on different chains in one transaction | | diff --git a/docs/tests/audit/transfer/test_transaction_validation.md b/docs/tests/audit/transfer/test_transaction_validation.md index 299c7b9f6e..70d8dbe856 100644 --- a/docs/tests/audit/transfer/test_transaction_validation.md +++ b/docs/tests/audit/transfer/test_transaction_validation.md @@ -4,10 +4,10 @@ Tests for transfer transactions validation # Tests list -| Test case | Description | XFailed | -|------------------------------------------------------------------------|-----------------------------------------------------|---------------------| -| TestTransactionsValidation::test_generate_bad_sign | Send transaction with invalid sign and got an error | | -| TestTransactionsValidation::test_send_underpriced_transaction | Send transaction with not enough gas_price | | -| TestTransactionsValidation::test_send_too_big_transaction | Send a big transaction 256*1024 in data | | -| TestTransactionsValidation::test_send_transaction_with_small_gas_price | Send a transaction with small gas price | | -| TestTransactionsValidation::test_big_memory_value | Check memory overflow | | \ No newline at end of file +| Test case | Description | XFailed | +|------------------------------------------------------------------------|-----------------------------------------------------|-----------| +| TestTransactionsValidation::test_generate_bad_sign | Send transaction with invalid sign and got an error | | +| TestTransactionsValidation::test_send_underpriced_transaction | Send transaction with not enough gas_price | | +| TestTransactionsValidation::test_send_too_big_transaction | Send a big transaction 256*1024 in data | | +| TestTransactionsValidation::test_send_transaction_with_small_gas_price | Send a transaction with small gas price | NDEV-2386 | +| TestTransactionsValidation::test_big_memory_value | Check memory overflow | | \ No newline at end of file diff --git a/integration/tests/basic/rpc/test_rpc_get_logs.py b/integration/tests/basic/rpc/test_rpc_get_logs.py index f510ae7dc9..73477031b8 100644 --- a/integration/tests/basic/rpc/test_rpc_get_logs.py +++ b/integration/tests/basic/rpc/test_rpc_get_logs.py @@ -212,6 +212,7 @@ def test_get_logs(self, method, event_caller_contract, param_fields, tag1, tag2) assert response["result"][0]["address"] == receipt["to"], ( f"address from response {response['result'][0]['address']} " f"is not equal to address from receipt {receipt['to']}" + ) assert_fields_are_hex(result, self.ETH_HEX_FIELDS) assert_fields_are_specified_type(bool, result, self.ETH_BOOL_FIELDS) diff --git a/integration/tests/basic/test_payment_in_different_tokens.py b/integration/tests/basic/test_payment_in_different_tokens.py index 195ac211bf..7be6cbaff8 100644 --- a/integration/tests/basic/test_payment_in_different_tokens.py +++ b/integration/tests/basic/test_payment_in_different_tokens.py @@ -133,7 +133,7 @@ def test_deploy_contract_with_sending_tokens(self, web3_client_sol, alice, check assert sol_alice_balance_after < sol_alice_balance_before - value @pytest.mark.multipletokens - def test_deploy_contract_by_one_user_to_different_chain( + def test_deploy_contract_by_one_user_to_different_chains( self, web3_client_sol, solana_account, web3_client, pytestconfig, alice ): def deploy_contract(w3_client): diff --git a/integration/tests/basic/transfer/test_transaction_validation.py b/integration/tests/basic/transfer/test_transaction_validation.py index dcec35dac7..d92ee27dce 100644 --- a/integration/tests/basic/transfer/test_transaction_validation.py +++ b/integration/tests/basic/transfer/test_transaction_validation.py @@ -1,6 +1,5 @@ import random import re -import time import allure import pytest @@ -88,8 +87,7 @@ def test_send_too_big_transaction(self): assert ErrorMessage.TOO_BIG_TRANSACTION.value in response["error"]["message"] assert response["error"]["code"] == -32000 - @pytest.mark.skip(reason="Test doesn't work with MINIMAL_GAS_PRICE in config. It should be fixed after adding " - "different parameters for different chains") + @pytest.mark.skip(reason="Test doesn't work with MINIMAL_GAS_PRICE in config. NDEV-2386") def test_send_transaction_with_small_gas_price(self, new_account): """Check that transaction can't be accepted if gas value is too small""" gas_price = self.web3_client.gas_price() @@ -101,7 +99,7 @@ def test_send_transaction_with_small_gas_price(self, new_account): "eth_sendRawTransaction", [signed_tx.rawTransaction.hex()] ) assert is_hex(response['result']) - time.sleep(5) + self.wait_transaction_accepted(response["result"]) receipt = self.proxy_api.send_rpc(method="eth_getTransactionReceipt", params=[response["result"]]) assert receipt['result'] is None From f59758fe1ebf27cd03227608ad7d220cc1bf212d Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Tue, 21 Nov 2023 14:19:37 +0100 Subject: [PATCH 28/56] fixed instruction ids --- utils/instructions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/instructions.py b/utils/instructions.py index a9e79d6964..ae22ce2381 100644 --- a/utils/instructions.py +++ b/utils/instructions.py @@ -29,7 +29,7 @@ def balance_account(solana_wallet, account_pubkey, contract_pubkey, is_signer=False, is_writable=True), ] - data = bytes.fromhex('2D') + bytes.fromhex(str(neon_wallet)[2:]) + chain_id.to_bytes(8, 'little') + data = bytes.fromhex('30') + bytes.fromhex(str(neon_wallet)[2:]) + chain_id.to_bytes(8, 'little') return TransactionInstruction( program_id=PublicKey(evm_loader_id), keys=keys, @@ -53,7 +53,7 @@ def deposit( operator_pubkey: PublicKey, evm_loader_id ) -> TransactionInstruction: - data = bytes.fromhex('27') + ether_address + chain_id.to_bytes(8, 'little') + data = bytes.fromhex('31') + ether_address + chain_id.to_bytes(8, 'little') accounts = [ AccountMeta(pubkey=mint, is_signer=False, is_writable=True), AccountMeta(pubkey=source, is_signer=False, is_writable=True), @@ -160,7 +160,7 @@ def build_tx_instruction(solana_wallet, neon_wallet, neon_raw_transaction, treasure_pool_address = get_collateral_pool_address( treasure_pool_index, evm_loader_id) - data = bytes.fromhex('1f') + treasure_pool_index.to_bytes(4, 'little') + \ + data = bytes.fromhex('32') + treasure_pool_index.to_bytes(4, 'little') + \ bytes.fromhex(str(neon_raw_transaction.hex())[2:]) keys = [AccountMeta(pubkey=solana_wallet, is_signer=True, is_writable=True), AccountMeta(pubkey=treasure_pool_address, From e8f04f84bcede80cd736398ecf74af0c497f73ce Mon Sep 17 00:00:00 2001 From: romanova-natasha Date: Wed, 22 Nov 2023 12:01:06 +0400 Subject: [PATCH 29/56] skip tests due to bug NDEV-2374 skip tests due to bug NDEV-2374 --- .../tests/tracer/test_tracer_debug_methods.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/integration/tests/tracer/test_tracer_debug_methods.py b/integration/tests/tracer/test_tracer_debug_methods.py index 35476b3de9..b6364db5d6 100644 --- a/integration/tests/tracer/test_tracer_debug_methods.py +++ b/integration/tests/tracer/test_tracer_debug_methods.py @@ -307,6 +307,7 @@ def check_modified_accounts_response(self, response): for item in response["result"]: assert re.match('^0x[a-fA-F\d]{64}$', item) + @pytest.mark.skip(reason="bug NDEV-2375") def test_debug_get_modified_accounts_by_number(self): receipt_start = self.send_neon( self.sender_account, self.recipient_account, 0.1) @@ -322,7 +323,8 @@ def test_debug_get_modified_accounts_by_number(self): response = self.tracer_api.send_rpc( method="debug_getModifiedAccountsByNumber", params=[hex(receipt_start["blockNumber"]), hex(receipt_end["blockNumber"])]) self.check_modified_accounts_response(response) - + + @pytest.mark.skip(reason="bug NDEV-2375") def test_debug_get_modified_accounts_by_same_number(self): receipt = self.send_neon( self.sender_account, self.recipient_account, 0.1) @@ -335,7 +337,8 @@ def test_debug_get_modified_accounts_by_same_number(self): response = self.tracer_api.send_rpc( method="debug_getModifiedAccountsByNumber", params=[hex(receipt["blockNumber"]), hex(receipt["blockNumber"])]) self.check_modified_accounts_response(response) - + + @pytest.mark.skip(reason="bug NDEV-2375") def test_debug_get_modified_accounts_by_only_one_number(self): receipt = self.send_neon( self.sender_account, self.recipient_account, 0.1) @@ -348,7 +351,8 @@ def test_debug_get_modified_accounts_by_only_one_number(self): response = self.tracer_api.send_rpc( method="debug_getModifiedAccountsByNumber", params=[hex(receipt["blockNumber"])]) self.check_modified_accounts_response(response) - + + @pytest.mark.skip(reason="bug NDEV-2375") @pytest.mark.parametrize("difference", [1, 50, 199, 200]) def test_debug_get_modified_accounts_by_number_blocks_difference_less_or_equal_200(self, difference): receipt = self.send_neon( @@ -364,6 +368,7 @@ def test_debug_get_modified_accounts_by_number_blocks_difference_less_or_equal_2 method="debug_getModifiedAccountsByNumber", params=[start_number, end_number]) self.check_modified_accounts_response(response) + @pytest.mark.skip(reason="bug NDEV-2375") def test_debug_get_modified_accounts_by_number_201_blocks_difference(self): receipt = self.send_neon( self.sender_account, self.recipient_account, 0.1) @@ -376,7 +381,8 @@ def test_debug_get_modified_accounts_by_number_201_blocks_difference(self): assert "error" in response, "No errors in response" assert response["error"]["code"] == -32603, "Invalid error code" assert response["error"]["message"] == "Requested range (201) is too big, maximum allowed range is 200 blocks" - + + @pytest.mark.skip(reason="bug NDEV-2375") @pytest.mark.parametrize("params", [[1, 124], ["94f3e", 12], ["1a456", "0x0"], ["183b8e", "183b8e"]]) def test_debug_get_modified_accounts_by_invalid_numbers(self, params): response = self.tracer_api.send_rpc( @@ -384,7 +390,8 @@ def test_debug_get_modified_accounts_by_invalid_numbers(self, params): assert "error" in response, "No errors in response" assert response["error"]["code"] == -32602, "Invalid error code" assert response["error"]["message"] == "Invalid params" - + + @pytest.mark.skip(reason="bug NDEV-2375") def test_debug_get_modified_accounts_by_hash(self): receipt_start = self.send_neon( self.sender_account, self.recipient_account, 0.1) @@ -400,7 +407,8 @@ def test_debug_get_modified_accounts_by_hash(self): response = self.tracer_api.send_rpc( method="debug_getModifiedAccountsByHash", params=[receipt_start["blockHash"].hex(), receipt_end["blockHash"].hex()]) self.check_modified_accounts_response(response) - + + @pytest.mark.skip(reason="bug NDEV-2375") @pytest.mark.parametrize("params", [[1, 124], ["0x94f3e00000000800000000", 12], ["0x1a456", "0x000000000001"], ["0x183b8e", "183b8e"]]) def test_debug_get_modified_accounts_by_invalid_hash(self, params): response = self.tracer_api.send_rpc( @@ -408,11 +416,3 @@ def test_debug_get_modified_accounts_by_invalid_hash(self, params): assert "error" in response, "No errors in response" assert response["error"]["code"] == -32602, "Invalid error code" assert response["error"]["message"] == "Invalid params" - - @pytest.mark.skip(reason="NDEV-2367") - def test_get_raw_transaction(self): - pass - - @pytest.mark.skip(reason="NDEV-2367") - def test_get_raw_transaction_invalid_tx_hash(self): - pass \ No newline at end of file From f4f42e6d658843551da0cffc63c531b3e4eb7515 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Wed, 22 Nov 2023 10:27:58 +0100 Subject: [PATCH 30/56] fix error message --- integration/tests/basic/test_deposit.py | 55 +++++++++---------- .../basic/test_payment_in_different_tokens.py | 3 +- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/integration/tests/basic/test_deposit.py b/integration/tests/basic/test_deposit.py index 302a3efe6d..f25dd2a6f6 100644 --- a/integration/tests/basic/test_deposit.py +++ b/integration/tests/basic/test_deposit.py @@ -1,9 +1,8 @@ import json -import time import pytest import allure -import solana +import web3 from _pytest.config import Config from solana.keypair import Keypair from solana.rpc.commitment import Commitment @@ -167,6 +166,32 @@ def withdraw(self, dest_acc, move_amount, withdraw_contract): receipt = self.web3_client.send_transaction(self.acc, instruction_tx) assert receipt["status"] == 1 + @pytest.mark.parametrize("move_amount, error", [(11000, web3.exceptions.ContractLogicError), (10000, ValueError)]) + def test_failed_withdraw_insufficient_balance( + self, + pytestconfig: Config, + move_amount, + error, + withdraw_contract, + neon_mint, + solana_account, + ): + dest_acc = solana_account + + spl_neon_token = SplToken(self.sol_client, neon_mint, TOKEN_PROGRAM_ID, dest_acc.public_key) + + amount = move_amount * pow(10, 18) + dest_token_acc = get_associated_token_address(dest_acc.public_key, neon_mint) + + response = spl_neon_token.get_balance(dest_token_acc, commitment=Commitment("confirmed")) + assert json.loads(response.to_json())["message"] == "Invalid param: could not find account" + + with pytest.raises(error): + self.withdraw(dest_acc, amount, withdraw_contract) + + response = spl_neon_token.get_balance(dest_token_acc, commitment=Commitment("confirmed")) + assert json.loads(response.to_json())["message"] == "Invalid param: could not find account" + @pytest.mark.only_stands def test_success_withdraw_to_non_existing_account( self, pytestconfig: Config, withdraw_contract, neon_mint, solana_account @@ -239,29 +264,3 @@ def test_failed_withdraw_non_divisible_amount( destination_balance_after = spl_neon_token.get_balance(dest_acc.public_key, commitment=Commitment("confirmed")) with pytest.raises(AttributeError): _ = destination_balance_after.value - - @pytest.mark.parametrize("move_amount", [11000, 10000]) - def test_failed_withdraw_insufficient_balance( - self, - pytestconfig: Config, - move_amount, - withdraw_contract, - neon_mint, - solana_account, - ): - dest_acc = solana_account - - spl_neon_token = SplToken(self.sol_client, neon_mint, TOKEN_PROGRAM_ID, dest_acc.public_key) - - amount = move_amount * pow(10, 18) - - destination_balance_before = spl_neon_token.get_balance(dest_acc.public_key, commitment=Commitment("confirmed")) - with pytest.raises(AttributeError): - _ = destination_balance_before.value - - with pytest.raises(ValueError): - self.withdraw(dest_acc, amount, withdraw_contract) - - destination_balance_after = spl_neon_token.get_balance(dest_acc.public_key, commitment=Commitment("confirmed")) - with pytest.raises(AttributeError): - _ = destination_balance_after.value diff --git a/integration/tests/basic/test_payment_in_different_tokens.py b/integration/tests/basic/test_payment_in_different_tokens.py index 7be6cbaff8..0dcb290f2c 100644 --- a/integration/tests/basic/test_payment_in_different_tokens.py +++ b/integration/tests/basic/test_payment_in_different_tokens.py @@ -2,6 +2,7 @@ import allure import pytest +import web3 from integration.tests.basic.helpers.basic import BaseMixin from integration.tests.basic.helpers.chains import make_nonce_the_biggest_for_chain @@ -145,7 +146,7 @@ def deploy_contract(w3_client): make_nonce_the_biggest_for_chain(alice, web3_client_sol, [web3_client]) deploy_contract(web3_client_sol) - with pytest.raises(ValueError, match="EVM Error. Attempt to deploy to existing account"): + with pytest.raises(web3.exceptions.ContractLogicError, match="Attempt to deploy to existing account"): deploy_contract(web3_client) make_nonce_the_biggest_for_chain(alice, web3_client, [web3_client_sol]) From 8a47f889e768ff7db1afd3673905250c2acbffd8 Mon Sep 17 00:00:00 2001 From: romanova-natasha Date: Thu, 23 Nov 2023 11:46:09 +0400 Subject: [PATCH 31/56] add raw header body check add raw header body check --- deploy/requirements/click.txt | 3 +- .../tests/tracer/test_tracer_debug_methods.py | 29 ++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/deploy/requirements/click.txt b/deploy/requirements/click.txt index 0aecf6230b..3338c97839 100644 --- a/deploy/requirements/click.txt +++ b/deploy/requirements/click.txt @@ -8,4 +8,5 @@ web3==6.9.0 solana==0.28.0 py-solc-x==1.1.1 pythclient==0.0.2 -boto3==1.28.54 \ No newline at end of file +boto3==1.28.54 +rlp==3.0.0 diff --git a/integration/tests/tracer/test_tracer_debug_methods.py b/integration/tests/tracer/test_tracer_debug_methods.py index b6364db5d6..8592c07e1a 100644 --- a/integration/tests/tracer/test_tracer_debug_methods.py +++ b/integration/tests/tracer/test_tracer_debug_methods.py @@ -4,6 +4,8 @@ import re from jsonschema import Draft4Validator +from rlp import decode +from rlp.sedes import List, big_endian_int, binary import allure import pytest @@ -257,6 +259,10 @@ def test_debug_trace_block_by_non_existent_hash(self): assert response["error"]["code"] == -32603, "Invalid error code" assert response["error"]["message"] == "eth_getBlockByHash returns None for '\"0xd97ff4869d52c4add6f5bcb1ba96020dd7877244b4cbf49044f49f002015ea85\"' block" + def decode_raw_header(self, header: bytes): + sedes = List([big_endian_int, binary, binary, binary, binary]) + return decode(header, sedes) + def test_getRawHeader_by_block_number(self): receipt = self.send_neon( self.sender_account, self.recipient_account, 0.1) @@ -268,8 +274,16 @@ def test_getRawHeader_by_block_number(self): response = self.tracer_api.send_rpc( method="debug_getRawHeader", params=[hex(receipt["blockNumber"])]) assert "error" not in response, "Error in response" - assert response["result"] is not None and response["result"] != "" - assert isinstance(response["result"], str) + assert response["result"] is not None + + header = self.decode_raw_header(bytes.fromhex(response["result"])) + block_info = self.web3_client.eth.get_block(receipt["blockNumber"]) + assert header[0] == block_info["number"] + assert header[1].hex() == '' + assert header[2].hex() == block_info["parentHash"].hex()[2:] + assert header[3].hex() == block_info["stateRoot"].hex()[2:] + assert header[4].hex() == block_info["receiptsRoot"].hex()[2:] + def test_getRawHeader_by_invalid_block_number(self): response = self.tracer_api.send_rpc( @@ -289,8 +303,15 @@ def test_getRawHeader_by_block_hash(self): response = self.tracer_api.send_rpc( method="debug_getRawHeader", params=[receipt["blockHash"].hex()]) assert "error" not in response, "Error in response" - assert response["result"] is not None and response["result"] != "" - assert isinstance(response["result"], str) + assert response["result"] is not None + + header = self.decode_raw_header(bytes.fromhex(response["result"])) + block_info = self.web3_client.eth.get_block(receipt["blockNumber"]) + assert header[0] == block_info["number"] + assert header[1].hex() == '' + assert header[2].hex() == block_info["parentHash"].hex()[2:] + assert header[3].hex() == block_info["stateRoot"].hex()[2:] + assert header[4].hex() == block_info["receiptsRoot"].hex()[2:] def test_getRawHeader_by_invalid_block_hash(self): response = self.tracer_api.send_rpc( From 322bdff27d619a1626d0c5696aac84d933c89735 Mon Sep 17 00:00:00 2001 From: romanova-natasha Date: Thu, 23 Nov 2023 13:38:56 +0400 Subject: [PATCH 32/56] restore scope for fixture restore scope for fixture --- integration/tests/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integration/tests/conftest.py b/integration/tests/conftest.py index e9ff91bd1e..92c93569cc 100644 --- a/integration/tests/conftest.py +++ b/integration/tests/conftest.py @@ -316,6 +316,8 @@ def wneon(web3_client, faucet, class_account): ) return contract + +@pytest.fixture(scope="class") def storage_contract(web3_client, class_account) -> typing.Any: contract, _ = web3_client.deploy_and_get_contract( "common/StorageSoliditySource", @@ -325,4 +327,3 @@ def storage_contract(web3_client, class_account) -> typing.Any: constructor_args=[] ) yield contract - From 5e54535fee535e267b0feb90e06bdc4aa932bdfb Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Thu, 23 Nov 2023 17:23:25 +0100 Subject: [PATCH 33/56] operator tests refactoring --- contracts/common/Common.sol | 14 + integration/tests/conftest.py | 18 + integration/tests/economy/conftest.py | 46 +- integration/tests/economy/const.py | 30 + integration/tests/economy/steps.py | 85 +++ integration/tests/economy/test_economics.py | 548 ++++++------------ .../migrations/test_account_migration.py | 272 +++++---- 7 files changed, 505 insertions(+), 508 deletions(-) create mode 100644 integration/tests/economy/const.py create mode 100644 integration/tests/economy/steps.py diff --git a/contracts/common/Common.sol b/contracts/common/Common.sol index cc940dc860..b9b1c499c8 100644 --- a/contracts/common/Common.sol +++ b/contracts/common/Common.sol @@ -43,4 +43,18 @@ contract BunchActions { Common(addresses[i]).setNumber(_numbers[i]); } } +} + + +contract MappingActions { + mapping(uint256 => string) public stringMapping; + + // Function to replace n values in the mapping + function replaceValues(uint256 n) external { + + for (uint256 i = 0; i < n; i++) { + stringMapping[i] = "UpdatedString"; + } + + } } \ No newline at end of file diff --git a/integration/tests/conftest.py b/integration/tests/conftest.py index 92c93569cc..bc333d0b0d 100644 --- a/integration/tests/conftest.py +++ b/integration/tests/conftest.py @@ -22,6 +22,7 @@ from utils.operator import Operator from utils.solana_client import SolanaClient from utils.web3client import NeonChainWeb3Client +from utils.prices import get_sol_price, get_neon_price NEON_AIRDROP_AMOUNT = 10_000 @@ -327,3 +328,20 @@ def storage_contract(web3_client, class_account) -> typing.Any: constructor_args=[] ) yield contract + + +@pytest.fixture(scope="session") +def sol_price() -> float: + """Get SOL price from Solana mainnet""" + price = get_sol_price() + with allure.step(f"SOL price {price}$"): + return price + + +@pytest.fixture(scope="session") +def neon_price() -> float: + """Get SOL price from Solana mainnet""" + price = get_neon_price() + with allure.step(f"NEON price {price}$"): + return price + diff --git a/integration/tests/economy/conftest.py b/integration/tests/economy/conftest.py index 32ca96e679..1277b6361f 100644 --- a/integration/tests/economy/conftest.py +++ b/integration/tests/economy/conftest.py @@ -1,4 +1,3 @@ -import allure import pytest from pythclient.solana import SolanaClient @@ -6,23 +5,8 @@ from integration.tests.basic.helpers.chains import make_nonce_the_biggest_for_chain from utils.erc20wrapper import ERC20Wrapper -from utils.prices import get_sol_price, get_neon_price - - -@pytest.fixture(scope="session") -def sol_price() -> float: - """Get SOL price from Solana mainnet""" - price = get_sol_price() - with allure.step(f"SOL price {price}$"): - return price - - -@pytest.fixture(scope="session") -def neon_price() -> float: - """Get SOL price from Solana mainnet""" - price = get_neon_price() - with allure.step(f"NEON price {price}$"): - return price +from utils.erc721ForMetaplex import ERC721ForMetaplex +from utils.web3client import NeonChainWeb3Client @pytest.fixture(scope="session") @@ -35,12 +19,6 @@ def sol_client_tx_v2(pytestconfig: Config): return client -# @pytest.fixture(scope="class") -# def temp_acc(web3_client): -# key = "0x931babf4129096d628e0d5e642bd5768fd1bcfb79c6f5b95ffa471c350da4207" -# return web3_client.eth.account.from_key(key) - - @pytest.fixture(scope="class") def counter_contract(account_with_all_tokens, client_and_price, web3_client_sol, web3_client): w3_client, _ = client_and_price @@ -61,7 +39,6 @@ def client_and_price(web3_client, web3_client_sol, sol_price, neon_price, reques @pytest.fixture(scope="class") def erc20_wrapper( - erc20_spl_mintable, account_with_all_tokens, client_and_price, faucet, @@ -85,3 +62,22 @@ def erc20_wrapper( contract.mint_tokens(account_with_all_tokens, contract.account.address) return contract + +@pytest.fixture(scope="class") +def erc721_neon_chain(web3_client: NeonChainWeb3Client, faucet, pytestconfig: Config, account_with_all_tokens): + contract = ERC721ForMetaplex(web3_client, faucet, account_with_all_tokens) + return contract + + +@pytest.fixture(scope="class") +def erc721( + erc721_neon_chain, + client_and_price, + faucet, + account_with_all_tokens +): + client, _ = client_and_price + contract = ERC721ForMetaplex(client, faucet, account=account_with_all_tokens, + contract_address=erc721_neon_chain.contract.address) + + return contract diff --git a/integration/tests/economy/const.py b/integration/tests/economy/const.py new file mode 100644 index 0000000000..5bffb2264f --- /dev/null +++ b/integration/tests/economy/const.py @@ -0,0 +1,30 @@ +import logging +from decimal import getcontext + +TX_COST = 5000 + +DECIMAL_CONTEXT = getcontext() +DECIMAL_CONTEXT.prec = 9 + +SOLCX_VERSIONS = ["0.6.6", "0.8.6", "0.8.10"] + +INSUFFICIENT_FUNDS_ERROR = "insufficient funds for" +GAS_LIMIT_ERROR = "gas limit reached" +LOGGER = logging.getLogger(__name__) +BIG_STRING = ( + "But I must explain to you how all this mistaken idea of denouncing pleasure and " + "praising pain was born and I will give you a complete account of the system, and " + "expound the actual teachings of the great explorer of the truth, the master-builder " + "of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it" + " is pleasure, but because those who do not know how to pursue pleasure rationally" + " encounter consequences that are extremely painful. Nor again is there anyone who" + " loves or pursues or desires to obtain pain of itself, because it is pain, but" + " because occasionally circumstances occur in which toil and pain can procure him" + " some great pleasure. To take a trivial example, which of us ever undertakes laborious" + " physical exercise, except to obtain some advantage from it? But who has any right to" + " find fault with a man who chooses to enjoy a pleasure that has no annoying consequences," + " or one who avoids a pain that produces no resultant pleasure? On the other hand," + " we denounce with righteous indigna" + " some great pleasure. To take a trivial example, which of us ever undertakes laborious" + " physical exercise, except to obtain some advantage from it? But who has any right to" +) diff --git a/integration/tests/economy/steps.py b/integration/tests/economy/steps.py new file mode 100644 index 0000000000..9a90e52719 --- /dev/null +++ b/integration/tests/economy/steps.py @@ -0,0 +1,85 @@ +import time +from decimal import Decimal + +import allure +from solana.rpc.core import RPCException +from solders.rpc.responses import GetTransactionResp +from solders.signature import Signature + +from integration.tests.economy.const import DECIMAL_CONTEXT, TX_COST +from utils.consts import LAMPORT_PER_SOL +from utils.helpers import wait_condition + + +@allure.step("Verify operator profit") +def assert_profit(sol_diff, sol_price, token_diff, token_price, token_name): + sol_amount = sol_diff / LAMPORT_PER_SOL + if token_diff < 0: + raise AssertionError(f"NEON has negative difference {token_diff}") + sol_cost = Decimal(sol_amount, DECIMAL_CONTEXT) * Decimal(sol_price, DECIMAL_CONTEXT) + token_cost = Decimal(token_diff, DECIMAL_CONTEXT) * Decimal(token_price, DECIMAL_CONTEXT) + + msg = "Operator receive {:.9f} {} ({:.2f} $) and spend {:.9f} SOL ({:.2f} $), profit - {:.9f}% ".format( + token_diff, + token_name, + token_cost, + sol_amount, + sol_cost, + ((token_cost - sol_cost) / sol_cost * 100), + ) + with allure.step(msg): + assert token_cost > sol_cost, msg + + +@allure.step("Get single transaction gas") +def get_single_transaction_gas(): + """One TX_COST to verify Solana signature plus another one TX_COST to pay to Governance""" + return TX_COST * 2 + + +@allure.step("Check transaction used ALT") +def check_alt_on(web3_client, sol_client, receipt, accounts_quantity): + solana_trx = web3_client.get_solana_trx_by_neon(receipt["transactionHash"].hex()) + wait_condition( + lambda: sol_client.get_transaction( + Signature.from_string(solana_trx["result"][0]), + max_supported_transaction_version=0, + ) + != GetTransactionResp(None) + ) + trx = sol_client.get_transaction( + Signature.from_string(solana_trx["result"][0]), + max_supported_transaction_version=0, + ) + alt = trx.value.transaction.transaction.message.address_table_lookups + accounts = alt[0].writable_indexes + alt[0].readonly_indexes + assert len(accounts) == accounts_quantity - 2 + + +@allure.step("Check block for not using ALT") +def check_alt_off(block): + txs = block.value.transactions + for tx in txs: + if tx.version == 0 and tx.transaction.message.address_table_lookups: + raise AssertionError("ALT should not be used") + + +@allure.step("Get gas used percent") +def get_gas_used_percent(web3_client, receipt): + trx = web3_client.eth.get_transaction(receipt["transactionHash"]) + estimated_gas = trx["gas"] + percent = round(receipt["gasUsed"] / estimated_gas * 100, 2) + with allure.step(f"Gas used percent: {percent}%"): + pass + + +@allure.step("Wait for block") +def wait_for_block(client, block, timeout=60): + started = time.time() + while (time.time() - started) < timeout: + try: + return client.get_block(block, max_supported_transaction_version=2) + except RPCException: + time.sleep(3) + time.sleep(3) + raise TimeoutError("Block not available for slot") diff --git a/integration/tests/economy/test_economics.py b/integration/tests/economy/test_economics.py index 9fa02eedd1..afb0ef84ed 100644 --- a/integration/tests/economy/test_economics.py +++ b/integration/tests/economy/test_economics.py @@ -1,8 +1,6 @@ import json -import logging import random -import time -from decimal import Decimal, getcontext +from decimal import Decimal import allure import pytest @@ -11,10 +9,8 @@ from _pytest.config import Config from solana.keypair import Keypair as SolanaAccount from solana.publickey import PublicKey -from solana.rpc.core import RPCException from solana.rpc.types import Commitment, TxOpts from solana.transaction import Transaction -from solders.rpc.responses import GetTransactionResp from solders.signature import Signature from spl.token.instructions import ( create_associated_token_account, @@ -23,39 +19,13 @@ from utils.consts import LAMPORT_PER_SOL from utils.erc20 import ERC20 -from utils.helpers import wait_condition +from utils.helpers import wait_condition, gen_hash_of_block +from .const import SOLCX_VERSIONS, INSUFFICIENT_FUNDS_ERROR, GAS_LIMIT_ERROR, BIG_STRING, TX_COST +from .steps import wait_for_block, assert_profit, get_gas_used_percent, check_alt_on, check_alt_off from ..base import BaseTests from ..basic.helpers.chains import make_nonce_the_biggest_for_chain -TX_COST = 5000 - -DECIMAL_CONTEXT = getcontext() -DECIMAL_CONTEXT.prec = 9 - -SOLCX_VERSIONS = ["0.6.6", "0.8.6", "0.8.10"] - -INSUFFICIENT_FUNDS_ERROR = "insufficient funds for" -GAS_LIMIT_ERROR = "gas limit reached" -LOGGER = logging.getLogger(__name__) -BIG_STRING = ( - "But I must explain to you how all this mistaken idea of denouncing pleasure and " - "praising pain was born and I will give you a complete account of the system, and " - "expound the actual teachings of the great explorer of the truth, the master-builder " - "of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it" - " is pleasure, but because those who do not know how to pursue pleasure rationally" - " encounter consequences that are extremely painful. Nor again is there anyone who" - " loves or pursues or desires to obtain pain of itself, because it is pain, but" - " because occasionally circumstances occur in which toil and pain can procure him" - " some great pleasure. To take a trivial example, which of us ever undertakes laborious" - " physical exercise, except to obtain some advantage from it? But who has any right to" - " find fault with a man who chooses to enjoy a pleasure that has no annoying consequences," - " or one who avoids a pain that produces no resultant pleasure? On the other hand," - " we denounce with righteous indigna" - " some great pleasure. To take a trivial example, which of us ever undertakes laborious" - " physical exercise, except to obtain some advantage from it? But who has any right to" -) - # @pytest.fixture(scope="session", autouse=True) # def heat_stand(web3_client: web3client.NeonChainWeb3Client, faucet): @@ -78,69 +48,6 @@ def install_solcx_versions(): class TestEconomics(BaseTests): acc = None - @pytest.fixture(autouse=True) - def save_token_costs(self, sol_price, neon_price): - self.sol_price = sol_price - self.neon_price = neon_price - - @allure.step("Verify operator profit") - def assert_profit(self, sol_diff, token_diff, token_price, token_name): - sol_amount = sol_diff / LAMPORT_PER_SOL - if token_diff < 0: - raise AssertionError(f"NEON has negative difference {token_diff}") - sol_cost = Decimal(sol_amount, DECIMAL_CONTEXT) * Decimal(self.sol_price, DECIMAL_CONTEXT) - token_cost = Decimal(token_diff, DECIMAL_CONTEXT) * Decimal(token_price, DECIMAL_CONTEXT) - - msg = "Operator receive {:.9f} {} ({:.2f} $) and spend {:.9f} SOL ({:.2f} $), profit - {:.9f}% ".format( - token_diff, - token_name, - token_cost, - sol_amount, - sol_cost, - ((token_cost - sol_cost) / sol_cost * 100), - ) - print(msg) - with allure.step(msg): - assert token_cost > sol_cost, msg - - @allure.step("Get single transaction gas") - def get_single_transaction_gas(self): - """One TX_COST to verify Solana signature plus another one TX_COST to pay to Governance""" - return TX_COST * 2 - - @allure.step("Check transaction used ALT") - def check_alt_on(self, sol_client, receipt, accounts_quantity): - solana_trx = self.web3_client.get_solana_trx_by_neon(receipt["transactionHash"].hex()) - wait_condition( - lambda: sol_client.get_transaction( - Signature.from_string(solana_trx["result"][0]), - max_supported_transaction_version=0, - ) - != GetTransactionResp(None) - ) - trx = sol_client.get_transaction( - Signature.from_string(solana_trx["result"][0]), - max_supported_transaction_version=0, - ) - alt = trx.value.transaction.transaction.message.address_table_lookups - accounts = alt[0].writable_indexes + alt[0].readonly_indexes - assert len(accounts) == accounts_quantity - 2 - - @allure.step("Check block for not using ALT") - def check_alt_off(self, block): - txs = block.value.transactions - for tx in txs: - if tx.version == 0 and tx.transaction.message.address_table_lookups: - raise AssertionError("ALT should not be used") - - @allure.step("Get gas used percent") - def get_gas_used_percent(self, receipt): - trx = self.web3_client.eth.get_transaction(receipt["transactionHash"]) - estimated_gas = trx["gas"] - percent = round(receipt["gasUsed"] / estimated_gas * 100, 2) - with allure.step(f"Gas used percent: {percent}%"): - pass - @pytest.mark.only_stands def test_account_creation(self, client_and_price): """Verify account creation spend SOL""" @@ -154,7 +61,7 @@ def test_account_creation(self, client_and_price): assert neon_balance_after == neon_balance_before assert sol_balance_after == sol_balance_before - def test_send_neon_to_unexist_account(self, account_with_all_tokens, client_and_price): + def test_send_neon_to_unexist_account(self, account_with_all_tokens, client_and_price, sol_price): """Verify how many cost transfer of native chain token to new user""" w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() @@ -170,10 +77,10 @@ def test_send_neon_to_unexist_account(self, account_with_all_tokens, client_and_ assert sol_balance_before > sol_balance_after, "Operator SOL balance incorrect" token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) - self.assert_profit(sol_diff, token_diff, token_price, w3_client.native_token_name) - self.get_gas_used_percent(receipt) + assert_profit(sol_diff, sol_price, token_diff, token_price, w3_client.native_token_name) + get_gas_used_percent(w3_client, receipt) - def test_send_tokens_to_exist_account(self, account_with_all_tokens, client_and_price): + def test_send_tokens_to_exist_account(self, account_with_all_tokens, client_and_price, sol_price): """Verify how many cost token send to use who was already initialized""" w3_client, token_price = client_and_price acc2 = w3_client.create_account() @@ -191,11 +98,32 @@ def test_send_tokens_to_exist_account(self, account_with_all_tokens, client_and_ sol_balance_after = self.operator.get_solana_balance() token_balance_after = self.operator.get_token_balance(w3_client) sol_diff = sol_balance_before - sol_balance_after - self.get_gas_used_percent(receipt) + get_gas_used_percent(w3_client, receipt) assert sol_balance_before > sol_balance_after, "Operator balance after send tx doesn't changed" token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) - self.assert_profit(sol_diff, token_diff, token_price, w3_client.native_token_name) + assert_profit(sol_diff, sol_price, token_diff, token_price, w3_client.native_token_name) + + def test_send_tokens_without_chain_id(self, account_with_all_tokens, client_and_price, web3_client, sol_price): + # for transactions without chain_id NEONs would be sent (even for sol chain) + w3_client, token_price = client_and_price + acc2 = w3_client.create_account() + sol_balance_before = self.operator.get_solana_balance() + token_balance_before = self.operator.get_token_balance(web3_client) + + instruction_tx = self.create_tx_object( + account_with_all_tokens.address, acc2.address, 0.1, web3_client=w3_client + ) + instruction_tx.pop("chainId") + + w3_client.send_transaction(account_with_all_tokens, instruction_tx) + + sol_balance_after = self.operator.get_solana_balance() + token_balance_after = self.operator.get_token_balance(web3_client) + sol_diff = sol_balance_before - sol_balance_after + + token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) + assert_profit(sol_diff, sol_price, token_diff, token_price, w3_client.native_token_name) def test_send_when_not_enough_tokens_to_gas(self, client_and_price, account_with_all_tokens): w3_client, token_price = client_and_price @@ -204,7 +132,6 @@ def test_send_when_not_enough_tokens_to_gas(self, client_and_price, account_with assert w3_client.get_balance(acc2) == 0 transfer_amount = 5000 w3_client.send_tokens(account_with_all_tokens, acc2, transfer_amount) - sol_balance_before = self.operator.get_solana_balance() token_balance_before = self.operator.get_token_balance(w3_client) @@ -219,7 +146,7 @@ def test_send_when_not_enough_tokens_to_gas(self, client_and_price, account_with assert sol_balance_before == sol_balance_after assert token_balance_before == token_balance_after - def test_erc20wrapper_transfer(self, erc20_wrapper, client_and_price): + def test_erc20wrapper_transfer(self, erc20_wrapper, client_and_price, sol_price): w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() token_balance_before = self.operator.get_token_balance(w3_client) @@ -235,11 +162,28 @@ def test_erc20wrapper_transfer(self, erc20_wrapper, client_and_price): assert sol_balance_before > sol_balance_after token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) - self.assert_profit(sol_diff, token_diff, token_price, w3_client.native_token_name) + assert_profit(sol_diff, sol_price, token_diff, token_price, w3_client.native_token_name) + + get_gas_used_percent(w3_client, transfer_tx) + + def test_erc721_mint(self, erc721, client_and_price, account_with_all_tokens, sol_price): + w3_client, token_price = client_and_price + sol_balance_before = self.operator.get_solana_balance() + token_balance_before = self.operator.get_token_balance(w3_client) + seed = self.web3_client.text_to_bytes32(gen_hash_of_block(8)) + + erc721.mint(seed, account_with_all_tokens.address, "uri") - self.get_gas_used_percent(transfer_tx) + wait_condition(lambda: sol_balance_before > self.operator.get_solana_balance()) + sol_balance_after = self.operator.get_solana_balance() + token_balance_after = self.operator.get_token_balance(w3_client) + sol_diff = sol_balance_before - sol_balance_after + + assert sol_balance_before > sol_balance_after + token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) + assert_profit(sol_diff, sol_price, token_diff, token_price, w3_client.native_token_name) - def test_withdraw_neon_unexisting_ata(self, pytestconfig: Config): + def test_withdraw_neon_unexisting_ata(self, pytestconfig: Config, neon_price, sol_price): sol_user = SolanaAccount() self.sol_client.request_airdrop(sol_user.public_key, 5 * LAMPORT_PER_SOL) @@ -274,16 +218,17 @@ def test_withdraw_neon_unexisting_ata(self, pytestconfig: Config): assert neon_balance_after > neon_balance_before neon_diff = self.web3_client.to_main_currency(neon_balance_after - neon_balance_before) - self.assert_profit( + assert_profit( sol_balance_before - sol_balance_after, + sol_price, neon_diff, - self.neon_price, + neon_price, self.web3_client.native_token_name, ) - self.get_gas_used_percent(receipt) + get_gas_used_percent(self.web3_client, receipt) - def test_withdraw_neon_existing_ata(self, pytestconfig, neon_mint): + def test_withdraw_neon_existing_ata(self, pytestconfig, neon_mint, neon_price, sol_price): sol_user = SolanaAccount() self.sol_client.request_airdrop(sol_user.public_key, 5 * LAMPORT_PER_SOL) @@ -330,41 +275,16 @@ def test_withdraw_neon_existing_ata(self, pytestconfig, neon_mint): assert neon_balance_after > neon_balance_before neon_diff = self.web3_client.to_main_currency(neon_balance_after - neon_balance_before) - self.assert_profit( + assert_profit( sol_balance_before - sol_balance_after, + sol_price, neon_diff, - self.neon_price, + neon_price, self.web3_client.native_token_name, ) - self.get_gas_used_percent(receipt) - - def test_erc20_contract_deploy(self, client_and_price, account_with_all_tokens, web3_client_sol, web3_client): - """Verify ERC20 contract deploy""" - w3_client, token_price = client_and_price - sol_balance_before = self.operator.get_solana_balance() - token_balance_before = self.operator.get_token_balance(w3_client) - - make_nonce_the_biggest_for_chain(account_with_all_tokens, w3_client, [web3_client, web3_client_sol]) - - contract, contract_deploy_tx = w3_client.deploy_and_get_contract( - "EIPs/ERC20/ERC20.sol", - "0.8.8", - account_with_all_tokens, - constructor_args=["Test Token", "TT", 1000], - ) - assert contract.functions.balanceOf(account_with_all_tokens.address).call() == 1000 - - sol_balance_after = self.operator.get_solana_balance() - token_balance_after = self.operator.get_token_balance(w3_client) - sol_diff = sol_balance_before - sol_balance_after + get_gas_used_percent(self.web3_client, receipt) - assert sol_balance_before > sol_balance_after - - token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) - self.assert_profit(sol_diff, token_diff, token_price, w3_client.native_token_name) - self.get_gas_used_percent(contract_deploy_tx) - - def test_erc20_transfer(self, client_and_price, account_with_all_tokens, web3_client_sol, web3_client): + def test_erc20_transfer(self, client_and_price, account_with_all_tokens, web3_client_sol, web3_client, sol_price): """Verify ERC20 token send""" w3_client, token_price = client_and_price make_nonce_the_biggest_for_chain(account_with_all_tokens, w3_client, [web3_client, web3_client_sol]) @@ -385,11 +305,12 @@ def test_erc20_transfer(self, client_and_price, account_with_all_tokens, web3_cl assert token_balance_after > token_balance_before token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) - self.assert_profit(sol_diff, token_diff, token_price, w3_client.native_token_name) - self.get_gas_used_percent(transfer_tx) + assert_profit(sol_diff, sol_price, token_diff, token_price, w3_client.native_token_name) + get_gas_used_percent(w3_client, transfer_tx) - def test_deploy_small_contract_less_100tx(self, account_with_all_tokens, client_and_price, web3_client_sol, - web3_client): + def test_deploy_small_contract_less_100tx( + self, account_with_all_tokens, client_and_price, web3_client_sol, web3_client, sol_price + ): """Verify we are bill minimum for 100 instruction""" w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() @@ -413,41 +334,12 @@ def test_deploy_small_contract_less_100tx(self, account_with_all_tokens, client_ assert sol_balance_before > sol_balance_after_deploy > sol_balance_after assert token_balance_after > token_balance_after_deploy > token_balance_before token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) - self.assert_profit(sol_balance_before - sol_balance_after, token_diff, token_price, w3_client.native_token_name) - self.get_gas_used_percent(receipt) - - def test_deploy_small_contract_less_gas(self, account_with_all_tokens, client_and_price): - w3_client, token_price = client_and_price - - sol_balance_before = self.operator.get_solana_balance() - token_balance_before = self.operator.get_token_balance(w3_client) - with pytest.raises(ValueError, match=GAS_LIMIT_ERROR): - w3_client.deploy_and_get_contract("common/Counter", "0.8.10", gas=1000, account=account_with_all_tokens) - - sol_balance_after = self.operator.get_solana_balance() - token_balance_after = self.operator.get_token_balance(w3_client) - - assert sol_balance_before == sol_balance_after - assert token_balance_after == token_balance_before - - def test_deploy_small_contract_less_tokens(self, account_with_all_tokens, client_and_price): - w3_client, token_price = client_and_price - acc2 = w3_client.create_account() - w3_client.send_tokens(account_with_all_tokens, acc2, 10) - - sol_balance_before = self.operator.get_solana_balance() - token_balance_before = self.operator.get_token_balance(w3_client) - - with pytest.raises(ValueError, match=INSUFFICIENT_FUNDS_ERROR): - w3_client.deploy_and_get_contract("common/Counter", "0.8.10", account=acc2) - - sol_balance_after_deploy = self.operator.get_solana_balance() - token_balance_after_deploy = self.operator.get_token_balance(w3_client) - - assert sol_balance_before == sol_balance_after_deploy - assert token_balance_before == token_balance_after_deploy + assert_profit( + sol_balance_before - sol_balance_after, sol_price, token_diff, token_price, w3_client.native_token_name + ) + get_gas_used_percent(w3_client, receipt) - def test_deploy_to_losted_contract_account(self, account_with_all_tokens, client_and_price): + def test_deploy_to_losted_contract_account(self, account_with_all_tokens, client_and_price, sol_price): w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() token_balance_before = self.operator.get_token_balance(w3_client) @@ -467,8 +359,10 @@ def test_deploy_to_losted_contract_account(self, account_with_all_tokens, client assert token_balance_after > token_balance_before token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) - self.assert_profit(sol_balance_before - sol_balance_after, token_diff, token_price, w3_client.native_token_name) - self.get_gas_used_percent(contract_deploy_tx) + assert_profit( + sol_balance_before - sol_balance_after, sol_price, token_diff, token_price, w3_client.native_token_name + ) + get_gas_used_percent(w3_client, contract_deploy_tx) def test_contract_get_is_free(self, counter_contract, client_and_price, account_with_all_tokens): """Verify that get contract calls is free""" @@ -487,7 +381,7 @@ def test_contract_get_is_free(self, counter_contract, client_and_price, account_ assert token_balance_after_deploy == token_balance_after @pytest.mark.xfail(reason="https://neonlabs.atlassian.net/browse/NDEV-699") - def test_cost_resize_account(self): + def test_cost_resize_account(self, neon_price, sol_price): """Verify how much cost account resize""" sol_balance_before = self.operator.get_solana_balance() neon_balance_before = self.operator.get_token_balance(self.web3_client) @@ -515,61 +409,16 @@ def test_cost_resize_account(self): assert sol_balance_before > sol_balance_before_increase > sol_balance_after, "SOL Balance not changed" assert neon_balance_after > neon_balance_before_increase > neon_balance_before, "NEON Balance incorrect" neon_diff = self.web3_client.to_main_currency(neon_balance_after - neon_balance_before) - self.assert_profit( + assert_profit( sol_balance_before - sol_balance_after, + sol_price, neon_diff, - self.neon_price, + neon_price, self.web3_client.native_token_name, ) - self.get_gas_used_percent(instruction_receipt) - - def test_cost_resize_account_less_tokens(self, account_with_all_tokens, client_and_price): - """Verify how much cost account resize""" - w3_client, token_price = client_and_price - make_nonce_the_biggest_for_chain(account_with_all_tokens, w3_client, [self.web3_client, self.web3_client_sol]) - contract, contract_deploy_tx = w3_client.deploy_and_get_contract( - "common/IncreaseStorage", "0.8.10", account=account_with_all_tokens - ) - - acc2 = w3_client.create_account() - w3_client.send_tokens(account_with_all_tokens, acc2, 1000) - - sol_balance_before_increase = self.operator.get_solana_balance() - token_balance_before_increase = self.operator.get_token_balance(w3_client) - - tx = self.create_tx_object(acc2.address, estimate_gas=False, web3_client=w3_client) - inc_tx = contract.functions.inc().build_transaction(tx) - - with pytest.raises(ValueError, match=INSUFFICIENT_FUNDS_ERROR): - w3_client.send_transaction(acc2, inc_tx) - - sol_balance_after = self.operator.get_solana_balance() - token_balance_after = self.operator.get_token_balance(w3_client) - - assert sol_balance_before_increase == sol_balance_after, "SOL Balance not changed" - assert token_balance_after == token_balance_before_increase, "TOKEN Balance incorrect" - - def test_failed_tx_when_less_gas(self, account_with_all_tokens, client_and_price): - """Don't get money from user if tx failed""" - w3_client, token_price = client_and_price - sol_balance_before = self.operator.get_solana_balance() - token_balance_before = self.operator.get_token_balance(w3_client) - - acc2 = w3_client.create_account() + get_gas_used_percent(self.web3_client, instruction_receipt) - user_balance_before = w3_client.get_balance(account_with_all_tokens) - with pytest.raises(ValueError, match=GAS_LIMIT_ERROR): - w3_client.send_tokens(account_with_all_tokens, acc2, 5000, gas=100) - - assert user_balance_before == w3_client.get_balance(account_with_all_tokens) - - sol_balance_after = self.operator.get_solana_balance() - token_balance_after = self.operator.get_token_balance(w3_client) - - assert sol_balance_before == sol_balance_after - assert token_balance_after == token_balance_before - - def test_contract_interact_more_500_steps(self, counter_contract, client_and_price, account_with_all_tokens): + def test_contract_interact_1000_steps(self, counter_contract, client_and_price, account_with_all_tokens, sol_price): """Deploy a contract with more 500 instructions""" w3_client, token_price = client_and_price @@ -585,10 +434,14 @@ def test_contract_interact_more_500_steps(self, counter_contract, client_and_pri assert sol_balance_before > sol_balance_after, "SOL Balance not changed" assert token_balance_after > token_balance_before, "TOKEN Balance incorrect" token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) - self.assert_profit(sol_balance_before - sol_balance_after, token_diff, token_price, w3_client.native_token_name) - self.get_gas_used_percent(instruction_receipt) + assert_profit( + sol_balance_before - sol_balance_after, sol_price, token_diff, token_price, w3_client.native_token_name + ) + get_gas_used_percent(w3_client, instruction_receipt) - def test_contract_interact_more_steps(self, counter_contract, client_and_price, account_with_all_tokens): + def test_contract_interact_500000_steps( + self, counter_contract, client_and_price, account_with_all_tokens, sol_price + ): """Deploy a contract with more 500000 bpf""" w3_client, token_price = client_and_price @@ -607,21 +460,20 @@ def test_contract_interact_more_steps(self, counter_contract, client_and_price, assert token_balance_after > token_balance_before, "TOKEN Balance incorrect" token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) - self.assert_profit( - sol_balance_before - sol_balance_after, - token_diff, token_price, w3_client.native_token_name + assert_profit( + sol_balance_before - sol_balance_after, sol_price, token_diff, token_price, w3_client.native_token_name ) - self.get_gas_used_percent(instruction_receipt) + get_gas_used_percent(w3_client, instruction_receipt) - def test_contract_interact_more_steps_less_gas(self, counter_contract, client_and_price, account_with_all_tokens): - """Interact a contract with more 500000 bpf and small amount of gas""" + def test_send_transaction_with_gas_limit_reached(self, counter_contract, client_and_price, account_with_all_tokens): + """Transaction with small amount of gas""" w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() token_balance_before = self.operator.get_token_balance(w3_client) tx = self.create_tx_object(account_with_all_tokens.address, estimate_gas=False, web3_client=w3_client, gas=1000) - instruction_tx = counter_contract.functions.moreInstruction(0, 1500).build_transaction(tx) + instruction_tx = counter_contract.functions.moreInstruction(0, 100).build_transaction(tx) with pytest.raises(ValueError, match=GAS_LIMIT_ERROR): w3_client.send_transaction(account_with_all_tokens, instruction_tx) @@ -632,8 +484,10 @@ def test_contract_interact_more_steps_less_gas(self, counter_contract, client_an assert sol_balance_after == sol_balance_before, "SOL Balance changes" assert token_balance_after == token_balance_before, "TOKEN Balance incorrect" - def test_contract_interact_more_steps_less_tokens(self, counter_contract, client_and_price, account_with_all_tokens): - """Deploy a contract with more 500000 bpf""" + def test_send_transaction_with_insufficient_funds( + self, counter_contract, client_and_price, account_with_all_tokens + ): + """Transaction with insufficient funds on balance""" w3_client, token_price = client_and_price acc2 = w3_client.create_account() w3_client.send_tokens(account_with_all_tokens, acc2, 100) @@ -652,8 +506,7 @@ def test_contract_interact_more_steps_less_tokens(self, counter_contract, client assert sol_balance_before == sol_balance_after, "SOL Balance changed" assert token_balance_after == token_balance_before, "TOKEN Balance incorrect" - # @pytest.mark.xfail(reason="Unprofitable transaction, because we create account not in evm (will be fixed)") - def test_tx_interact_more_1kb(self, counter_contract, client_and_price, account_with_all_tokens): + def test_tx_interact_more_1kb(self, counter_contract, client_and_price, account_with_all_tokens, sol_price): """Send to contract a big text (tx more than 1 kb)""" w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() @@ -671,52 +524,14 @@ def test_tx_interact_more_1kb(self, counter_contract, client_and_price, account_ assert token_balance_after > token_balance_before, "TOKEN Balance incorrect" token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) - self.assert_profit( - sol_balance_before - sol_balance_after, - token_diff, token_price, w3_client.native_token_name + assert_profit( + sol_balance_before - sol_balance_after, sol_price, token_diff, token_price, w3_client.native_token_name ) - self.get_gas_used_percent(instruction_receipt) - - def test_tx_interact_more_1kb_less_tokens(self, counter_contract, client_and_price, account_with_all_tokens): - """Send to contract a big text (tx more than 1 kb) when less tokens""" - w3_client, token_price = client_and_price - - acc2 = self.web3_client.create_account() - w3_client.send_tokens(account_with_all_tokens, acc2, 1000) - - sol_balance_before = self.operator.get_solana_balance() - token_balance_before = self.operator.get_token_balance(w3_client) - - tx = self.create_tx_object(acc2.address, estimate_gas=False, web3_client=w3_client) - instruction_tx = counter_contract.functions.bigString(BIG_STRING).build_transaction(tx) - with pytest.raises(ValueError, match=INSUFFICIENT_FUNDS_ERROR): - w3_client.send_transaction(acc2, instruction_tx) - - sol_balance_after = self.operator.get_solana_balance() - token_balance_after = self.operator.get_token_balance(w3_client) - - assert sol_balance_before == sol_balance_after, "SOL Balance changed" - assert token_balance_after == token_balance_before, "TOKEN Balance incorrect" + get_gas_used_percent(w3_client, instruction_receipt) - def test_tx_interact_more_1kb_less_gas(self, counter_contract, client_and_price, account_with_all_tokens): - """Send to contract a big text (tx more than 1 kb)""" - w3_client, token_price = client_and_price - - sol_balance_before = self.operator.get_solana_balance() - token_balance_before = self.operator.get_token_balance(w3_client) - - tx = self.create_tx_object(account_with_all_tokens.address, estimate_gas=False, web3_client=w3_client, gas=100) - instruction_tx = counter_contract.functions.bigString(BIG_STRING).build_transaction(tx) - with pytest.raises(ValueError, match=GAS_LIMIT_ERROR): - w3_client.send_transaction(account_with_all_tokens, instruction_tx) - - sol_balance_after = self.operator.get_solana_balance() - token_balance_after = self.operator.get_token_balance(w3_client) - - assert sol_balance_before == sol_balance_after, "SOL Balance changed" - assert token_balance_after == token_balance_before, "TOKEN Balance incorrect" - - def test_deploy_contract_more_1kb(self, client_and_price, account_with_all_tokens, web3_client, web3_client_sol): + def test_deploy_contract_more_1kb( + self, client_and_price, account_with_all_tokens, web3_client, web3_client_sol, sol_price + ): w3_client, token_price = client_and_price make_nonce_the_biggest_for_chain(account_with_all_tokens, w3_client, [web3_client, web3_client_sol]) @@ -734,44 +549,14 @@ def test_deploy_contract_more_1kb(self, client_and_price, account_with_all_token assert token_balance_after > token_balance_before token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) - self.assert_profit( - sol_balance_before - sol_balance_after, - token_diff, token_price, w3_client.native_token_name + assert_profit( + sol_balance_before - sol_balance_after, sol_price, token_diff, token_price, w3_client.native_token_name ) - self.get_gas_used_percent(contract_deploy_tx) + get_gas_used_percent(w3_client, contract_deploy_tx) - def test_deploy_contract_more_1kb_less_tokens(self, client_and_price, account_with_all_tokens): - w3_client, token_price = client_and_price - acc2 = w3_client.create_account() - w3_client.send_tokens(account_with_all_tokens, acc2, 100) - - sol_balance_before = self.operator.get_solana_balance() - token_balance_before = self.operator.get_token_balance(w3_client) - - with pytest.raises(ValueError, match=INSUFFICIENT_FUNDS_ERROR): - w3_client.deploy_and_get_contract("common/Fat", "0.8.10", account=acc2) - - sol_balance_after = self.operator.get_solana_balance() - token_balance_after = self.operator.get_token_balance(w3_client) - - assert sol_balance_before == sol_balance_after - assert token_balance_after == token_balance_before - - def test_deploy_contract_more_1kb_less_gas(self, client_and_price, account_with_all_tokens): - w3_client, token_price = client_and_price - sol_balance_before = self.operator.get_solana_balance() - token_balance_before = self.operator.get_token_balance(w3_client) - - with pytest.raises(ValueError, match=GAS_LIMIT_ERROR): - w3_client.deploy_and_get_contract("common/Fat", "0.8.10", account=account_with_all_tokens, gas=1000) - - sol_balance_after = self.operator.get_solana_balance() - token_balance_after = self.operator.get_token_balance(w3_client) - - assert sol_balance_before == sol_balance_after - assert token_balance_after == token_balance_before - - def test_deploy_contract_to_payed(self, client_and_price, account_with_all_tokens, web3_client, web3_client_sol): + def test_deploy_contract_to_payed( + self, client_and_price, account_with_all_tokens, web3_client, web3_client_sol, sol_price + ): w3_client, token_price = client_and_price make_nonce_the_biggest_for_chain(account_with_all_tokens, w3_client, [web3_client, web3_client_sol]) nonce = w3_client.eth.get_transaction_count(account_with_all_tokens.address) @@ -792,14 +577,14 @@ def test_deploy_contract_to_payed(self, client_and_price, account_with_all_token assert sol_balance_before > sol_balance_after, "SOL Balance not changed" assert token_balance_after > token_balance_before, "TOKEN Balance incorrect" token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) - self.assert_profit( - sol_balance_before - sol_balance_after, - token_diff, - token_price, w3_client.native_token_name + assert_profit( + sol_balance_before - sol_balance_after, sol_price, token_diff, token_price, w3_client.native_token_name ) - self.get_gas_used_percent(contract_deploy_tx) + get_gas_used_percent(w3_client, contract_deploy_tx) - def test_deploy_contract_to_exist_unpayed(self, client_and_price, account_with_all_tokens, web3_client, web3_client_sol): + def test_deploy_contract_to_exist_unpayed( + self, client_and_price, account_with_all_tokens, web3_client, web3_client_sol, sol_price + ): w3_client, token_price = client_and_price sol_balance_before = self.operator.get_solana_balance() @@ -813,7 +598,9 @@ def test_deploy_contract_to_exist_unpayed(self, client_and_price, account_with_a with pytest.raises(ValueError, match=GAS_LIMIT_ERROR): w3_client.send_tokens(account_with_all_tokens, contract_address, 100, gas=1) - _, contract_deploy_tx = w3_client.deploy_and_get_contract("common/Counter", "0.8.10", account=account_with_all_tokens) + _, contract_deploy_tx = w3_client.deploy_and_get_contract( + "common/Counter", "0.8.10", account=account_with_all_tokens + ) sol_balance_after_deploy = self.operator.get_solana_balance() token_balance_after_deploy = self.operator.get_token_balance(w3_client) @@ -821,15 +608,17 @@ def test_deploy_contract_to_exist_unpayed(self, client_and_price, account_with_a assert sol_balance_before > sol_balance_after_deploy assert token_balance_after_deploy > token_balance_before token_diff = w3_client.to_main_currency(token_balance_after_deploy - token_balance_before) - self.assert_profit( + assert_profit( sol_balance_before - sol_balance_after_deploy, + sol_price, token_diff, - token_price, w3_client.native_token_name + token_price, + w3_client.native_token_name, ) - self.get_gas_used_percent(contract_deploy_tx) + get_gas_used_percent(w3_client, contract_deploy_tx) @pytest.mark.timeout(960) - def test_deploy_contract_alt_on(self, sol_client): + def test_deploy_contract_alt_on(self, sol_client, neon_price, sol_price): """Trigger transaction than requires more than 30 accounts""" accounts_quantity = random.randint(31, 45) sol_balance_before = self.operator.get_solana_balance() @@ -847,7 +636,7 @@ def test_deploy_contract_alt_on(self, sol_client): } ) receipt = self.web3_client.send_transaction(self.acc, tx) - self.check_alt_on(sol_client, receipt, accounts_quantity) + check_alt_on(self.web3_client, sol_client, receipt, accounts_quantity) solana_trx = self.web3_client.get_solana_trx_by_neon(receipt["transactionHash"].hex()) sol_trx_with_alt = None @@ -878,10 +667,12 @@ def test_deploy_contract_alt_on(self, sol_client): assert sol_balance_before > sol_balance_after assert neon_balance_after > neon_balance_before neon_diff = self.web3_client.to_main_currency(neon_balance_after - neon_balance_before) - self.assert_profit( + assert_profit( sol_balance_before - sol_balance_after - alt_balance, + sol_price, neon_diff, - self.neon_price, self.web3_client.native_token_name + neon_price, + self.web3_client.native_token_name, ) # the charge for alt creating should be returned wait_condition( @@ -893,9 +684,11 @@ def test_deploy_contract_alt_on(self, sol_client): assert ( operator_balance + alt_balance - TX_COST * 2 == sol_client.get_balance(operator).value ), "Operator balance after the return of the alt creation fee is not correct" - self.get_gas_used_percent(receipt) + get_gas_used_percent(self.web3_client, receipt) - def test_deploy_contract_alt_off(self, sol_client, client_and_price, account_with_all_tokens, web3_client, web3_client_sol): + def test_deploy_contract_alt_off( + self, sol_client, client_and_price, account_with_all_tokens, web3_client, web3_client_sol, sol_price + ): """Trigger transaction than requires less than 30 accounts""" accounts_quantity = 10 w3_client, token_price = client_and_price @@ -911,12 +704,12 @@ def test_deploy_contract_alt_off(self, sol_client, client_and_price, account_wit token_balance_after_deploy = self.operator.get_token_balance(w3_client) tx = self.create_tx_object(account_with_all_tokens.address, estimate_gas=False, web3_client=w3_client) - intr = contract.functions.fill(accounts_quantity).build_transaction(tx) - receipt = w3_client.send_transaction(account_with_all_tokens, intr) + instr = contract.functions.fill(accounts_quantity).build_transaction(tx) + receipt = w3_client.send_transaction(account_with_all_tokens, instr) block = int(receipt["blockNumber"]) response = wait_for_block(sol_client, block) - self.check_alt_off(response) + check_alt_off(response) sol_balance_after = self.operator.get_solana_balance() token_balance_after = self.operator.get_token_balance(w3_client) @@ -924,19 +717,54 @@ def test_deploy_contract_alt_off(self, sol_client, client_and_price, account_wit assert sol_balance_before > sol_balance_after_deploy > sol_balance_after assert token_balance_after > token_balance_after_deploy > token_balance_before token_diff = w3_client.to_main_currency(token_balance_after - token_balance_after_deploy) - self.assert_profit( + assert_profit( sol_balance_after_deploy - sol_balance_after, - token_diff, token_price, w3_client.native_token_name + sol_price, + token_diff, + token_price, + w3_client.native_token_name, + ) + get_gas_used_percent(w3_client, receipt) + + def test_deploy_big_contract_with_structures(self, client_and_price, account_with_all_tokens, sol_price): + w3_client, token_price = client_and_price + + sol_balance_before = self.operator.get_solana_balance() + token_balance_before = self.operator.get_token_balance(w3_client) + + contract, receipt = w3_client.deploy_and_get_contract("EIPs/ERC3475", "0.8.10", account_with_all_tokens) + + sol_balance_after = self.operator.get_solana_balance() + token_balance_after = self.operator.get_token_balance(w3_client) + token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) + assert_profit( + sol_balance_before - sol_balance_after, sol_price, token_diff, token_price, w3_client.native_token_name + ) + get_gas_used_percent(w3_client, receipt) + + @pytest.mark.parametrize("value", [20, 25, 55]) + def test_call_contract_with_mapping_updating( + self, client_and_price, account_with_all_tokens, sol_price, web3_client, web3_client_sol, value + ): + w3_client, token_price = client_and_price + make_nonce_the_biggest_for_chain(account_with_all_tokens, w3_client, [web3_client, web3_client_sol]) + contract, _ = w3_client.deploy_and_get_contract( + contract="common/Common", version="0.8.12", contract_name="MappingActions", account=account_with_all_tokens + ) + + sol_balance_before = self.operator.get_solana_balance() + token_balance_before = self.operator.get_token_balance(w3_client) + + tx = self.create_tx_object(account_with_all_tokens.address, estimate_gas=False, web3_client=w3_client) + + instruction_tx = contract.functions.replaceValues(value).build_transaction(tx) + receipt = w3_client.send_transaction(account_with_all_tokens, instruction_tx) + assert receipt["status"] == 1 + wait_condition(lambda: sol_balance_before != self.operator.get_solana_balance()) + + sol_balance_after = self.operator.get_solana_balance() + token_balance_after = self.operator.get_token_balance(w3_client) + token_diff = w3_client.to_main_currency(token_balance_after - token_balance_before) + assert_profit( + sol_balance_before - sol_balance_after, sol_price, token_diff, token_price, w3_client.native_token_name ) - self.get_gas_used_percent(receipt) - - -def wait_for_block(client, block, timeout=60): - started = time.time() - while (time.time() - started) < timeout: - try: - return client.get_block(block, max_supported_transaction_version=2) - except RPCException: - time.sleep(3) - time.sleep(3) - raise TimeoutError("Block not available for slot") diff --git a/integration/tests/migrations/test_account_migration.py b/integration/tests/migrations/test_account_migration.py index 8fd0f69c60..846783f39d 100644 --- a/integration/tests/migrations/test_account_migration.py +++ b/integration/tests/migrations/test_account_migration.py @@ -5,6 +5,8 @@ import pytest from web3.logs import DISCARD +from integration.tests.base import BaseTests +from integration.tests.economy.steps import assert_profit from utils.erc20wrapper import ERC20Wrapper from utils.erc721ForMetaplex import ERC721ForMetaplex from utils.helpers import gen_hash_of_block @@ -96,154 +98,178 @@ def erc721(web3_client, faucet, bob): return erc721 -def test_transfers(alice, bob, accounts, web3_client, trx_list): - web3_client.send_neon(alice, bob, 5) - for i in range(5): - receipt = web3_client.send_neon(alice, accounts[i + 1], 5) +class TestAccountMigration(BaseTests): + @pytest.fixture(scope="function") + def check_operator_balance( + self, web3_client, sol_client, operator_keypair, solana_account, neon_price, sol_price, request + ): + print("test case name:", request.node.name) + sol_balance_before = self.operator.get_solana_balance() + token_balance_before = self.operator.get_token_balance(web3_client) + + yield + sol_balance_after = self.operator.get_solana_balance() + token_balance_after = self.operator.get_token_balance(web3_client) + sol_diff = sol_balance_before - sol_balance_after + + assert sol_balance_before > sol_balance_after + assert token_balance_after > token_balance_before + + token_diff = web3_client.to_main_currency(token_balance_after - token_balance_before) + assert_profit(sol_diff, sol_price, token_diff, neon_price, web3_client.native_token_name) + + def test_transfers(self, alice, bob, accounts, web3_client, trx_list, check_operator_balance): + web3_client.send_neon(alice, bob, 5) + for i in range(5): + receipt = web3_client.send_neon(alice, accounts[i + 1], 5) + trx_list.append(receipt["transactionHash"]) + assert receipt["status"] == 1 + receipt = web3_client.send_neon(bob, accounts[i + 2], 1) + trx_list.append(receipt["transactionHash"]) + assert receipt["status"] == 1 + + def test_contract_deploy_economics(self, alice, bob, web3_client, check_operator_balance): + web3_client.deploy_and_get_contract("common/EventCaller", "0.8.12", bob) + + def test_contract_deploy_and_interact(self, web3_client, accounts, trx_list, check_operator_balance): + acc1 = accounts[7] + acc2 = accounts[8] + contract_a, receipt = web3_client.deploy_and_get_contract( + "common/NestedCallsChecker", "0.8.12", acc2, contract_name="A" + ) trx_list.append(receipt["transactionHash"]) - assert receipt["status"] == 1 - receipt = web3_client.send_neon(bob, accounts[i + 2], 1) + contract_b, receipt = web3_client.deploy_and_get_contract( + "common/NestedCallsChecker", "0.8.12", acc2, contract_name="B" + ) trx_list.append(receipt["transactionHash"]) - assert receipt["status"] == 1 - - -def test_contract_deploy_and_interact(web3_client, accounts, trx_list): - acc1 = accounts[7] - acc2 = accounts[8] - contract_a, receipt = web3_client.deploy_and_get_contract( - "common/NestedCallsChecker", "0.8.12", acc2, contract_name="A" - ) - trx_list.append(receipt["transactionHash"]) - contract_b, receipt = web3_client.deploy_and_get_contract( - "common/NestedCallsChecker", "0.8.12", acc2, contract_name="B" - ) - trx_list.append(receipt["transactionHash"]) - contract_c, receipt = web3_client.deploy_and_get_contract( - "common/NestedCallsChecker", "0.8.12", acc1, contract_name="C" - ) - trx_list.append(receipt["transactionHash"]) - - tx = { - "from": acc1.address, - "nonce": web3_client.eth.get_transaction_count(acc1.address), - "gasPrice": web3_client.gas_price(), - } - - instruction_tx = contract_a.functions.method1(contract_b.address, contract_c.address).build_transaction(tx) - resp = web3_client.send_transaction(acc1, instruction_tx) - trx_list.append(resp["transactionHash"]) - - event_a1_logs = contract_a.events.EventA1().process_receipt(resp, errors=DISCARD) - assert len(event_a1_logs) == 1 - event_b1_logs = contract_b.events.EventB1().process_receipt(resp, errors=DISCARD) - assert len(event_b1_logs) == 1 - event_b2_logs = contract_b.events.EventB2().process_receipt(resp, errors=DISCARD) - event_c1_logs = contract_c.events.EventC1().process_receipt(resp, errors=DISCARD) - event_c2_logs = contract_c.events.EventC2().process_receipt(resp, errors=DISCARD) - for log in (event_b2_logs, event_c1_logs, event_c2_logs): - assert log == (), f"Trx shouldn't contain logs for the events: eventB2, eventC1, eventC2_log0. Log: {log}" - - -def test_erc721_interaction(erc721, web3_client, bob, alice, accounts, trx_list): - seed = web3_client.text_to_bytes32(gen_hash_of_block(8)) - token_id = erc721.mint(seed, erc721.account.address, "uri") - - balance_usr1_before = erc721.contract.functions.balanceOf(erc721.account.address).call() - balance_usr2_before = erc721.contract.functions.balanceOf(alice.address).call() - - resp = erc721.approve(alice.address, token_id, erc721.account) - trx_list.append(resp["transactionHash"]) - - resp = erc721.transfer_from(erc721.account.address, alice.address, token_id, alice) - trx_list.append(resp["transactionHash"]) - - balance_usr1_after = erc721.contract.functions.balanceOf(erc721.account.address).call() - balance_usr2_after = erc721.contract.functions.balanceOf(alice.address).call() - - assert balance_usr1_after - balance_usr1_before == -1 - assert balance_usr2_after - balance_usr2_before == 1 - - token_ids = [] - for _ in range(5): + contract_c, receipt = web3_client.deploy_and_get_contract( + "common/NestedCallsChecker", "0.8.12", acc1, contract_name="C" + ) + trx_list.append(receipt["transactionHash"]) + + tx = { + "from": acc1.address, + "nonce": web3_client.eth.get_transaction_count(acc1.address), + "gasPrice": web3_client.gas_price(), + } + + instruction_tx = contract_a.functions.method1(contract_b.address, contract_c.address).build_transaction(tx) + resp = web3_client.send_transaction(acc1, instruction_tx) + trx_list.append(resp["transactionHash"]) + + event_a1_logs = contract_a.events.EventA1().process_receipt(resp, errors=DISCARD) + assert len(event_a1_logs) == 1 + event_b1_logs = contract_b.events.EventB1().process_receipt(resp, errors=DISCARD) + assert len(event_b1_logs) == 1 + event_b2_logs = contract_b.events.EventB2().process_receipt(resp, errors=DISCARD) + event_c1_logs = contract_c.events.EventC1().process_receipt(resp, errors=DISCARD) + event_c2_logs = contract_c.events.EventC2().process_receipt(resp, errors=DISCARD) + for log in (event_b2_logs, event_c1_logs, event_c2_logs): + assert log == (), f"Trx shouldn't contain logs for the events: eventB2, eventC1, eventC2_log0. Log: {log}" + + def test_economics_for_erc721_mint(self, erc721, web3_client, check_operator_balance): seed = web3_client.text_to_bytes32(gen_hash_of_block(8)) - token_ids.append(erc721.mint(seed, erc721.account.address, "uri")) + erc721.mint(seed, erc721.account.address, "uri") - for i in range(5): - recipient = accounts[i + 3] + def test_erc721_interaction(self, erc721, web3_client, bob, alice, accounts, trx_list, check_operator_balance): + seed = web3_client.text_to_bytes32(gen_hash_of_block(8)) + token_id = erc721.mint(seed, erc721.account.address, "uri") balance_usr1_before = erc721.contract.functions.balanceOf(erc721.account.address).call() - balance_usr2_before = erc721.contract.functions.balanceOf(recipient.address).call() + balance_usr2_before = erc721.contract.functions.balanceOf(alice.address).call() - resp = erc721.transfer_from(erc721.account.address, recipient.address, token_ids[i], erc721.account) + resp = erc721.approve(alice.address, token_id, erc721.account) + trx_list.append(resp["transactionHash"]) + + resp = erc721.transfer_from(erc721.account.address, alice.address, token_id, alice) trx_list.append(resp["transactionHash"]) balance_usr1_after = erc721.contract.functions.balanceOf(erc721.account.address).call() - balance_usr2_after = erc721.contract.functions.balanceOf(recipient.address).call() + balance_usr2_after = erc721.contract.functions.balanceOf(alice.address).call() assert balance_usr1_after - balance_usr1_before == -1 assert balance_usr2_after - balance_usr2_before == 1 + token_ids = [] + for _ in range(5): + seed = web3_client.text_to_bytes32(gen_hash_of_block(8)) + token_ids.append(erc721.mint(seed, erc721.account.address, "uri")) -def test_erc20_interaction(erc20, web3_client, bob, alice, accounts, trx_list): - balance_before = erc20.contract.functions.balanceOf(erc20.account.address).call() - amount = 500 - resp = erc20.mint_tokens(erc20.account, erc20.account.address, amount) - trx_list.append(resp["transactionHash"]) + for i in range(5): + recipient = accounts[i + 3] - balance_after = erc20.contract.functions.balanceOf(erc20.account.address).call() - assert balance_after == balance_before + amount + balance_usr1_before = erc721.contract.functions.balanceOf(erc721.account.address).call() + balance_usr2_before = erc721.contract.functions.balanceOf(recipient.address).call() - tom = accounts[9] - balance_before = erc20.contract.functions.balanceOf(tom.address).call() - resp = erc20.mint_tokens(erc20.account, tom.address, amount) - trx_list.append(resp["transactionHash"]) + resp = erc721.transfer_from(erc721.account.address, recipient.address, token_ids[i], erc721.account) + trx_list.append(resp["transactionHash"]) - balance_after = erc20.contract.functions.balanceOf(tom.address).call() - assert balance_after == amount + balance_before + balance_usr1_after = erc721.contract.functions.balanceOf(erc721.account.address).call() + balance_usr2_after = erc721.contract.functions.balanceOf(recipient.address).call() - balance_before = erc20.contract.functions.balanceOf(tom.address).call() - total_before = erc20.contract.functions.totalSupply().call() - resp = erc20.burn(tom, tom.address, amount) - trx_list.append(resp["transactionHash"]) + assert balance_usr1_after - balance_usr1_before == -1 + assert balance_usr2_after - balance_usr2_before == 1 - balance_after = erc20.contract.functions.balanceOf(tom.address).call() - total_after = erc20.contract.functions.totalSupply().call() + def test_erc20_interaction(self, erc20, web3_client, bob, alice, accounts, trx_list, check_operator_balance): + balance_before = erc20.contract.functions.balanceOf(erc20.account.address).call() + amount = 500 + resp = erc20.mint_tokens(erc20.account, erc20.account.address, amount) + trx_list.append(resp["transactionHash"]) - assert balance_after == balance_before - amount - assert total_after == total_before - amount + balance_after = erc20.contract.functions.balanceOf(erc20.account.address).call() + assert balance_after == balance_before + amount - amount = 1000000000000000 - resp = erc20.mint_tokens(erc20.account, accounts[9].address, amount) - trx_list.append(resp["transactionHash"]) + tom = accounts[9] + balance_before = erc20.contract.functions.balanceOf(tom.address).call() + resp = erc20.mint_tokens(erc20.account, tom.address, amount) + trx_list.append(resp["transactionHash"]) - amount = 500 - for i in range(8): - recipient = accounts[i] - sender = accounts[9] - balance_acc1_before = erc20.contract.functions.balanceOf(sender.address).call() - balance_acc2_before = erc20.contract.functions.balanceOf(recipient.address).call() + balance_after = erc20.contract.functions.balanceOf(tom.address).call() + assert balance_after == amount + balance_before + + balance_before = erc20.contract.functions.balanceOf(tom.address).call() total_before = erc20.contract.functions.totalSupply().call() - resp = erc20.transfer(sender, recipient.address, amount) + resp = erc20.burn(tom, tom.address, amount) trx_list.append(resp["transactionHash"]) - balance_acc1_after = erc20.contract.functions.balanceOf(sender.address).call() - balance_acc2_after = erc20.contract.functions.balanceOf(recipient.address).call() + balance_after = erc20.contract.functions.balanceOf(tom.address).call() total_after = erc20.contract.functions.totalSupply().call() - assert balance_acc1_after == balance_acc1_before - amount - assert balance_acc2_after == balance_acc2_before + amount - assert total_before == total_after - - for i in range(7): - recipient = accounts[8] - sender = accounts[i] - balance_acc1_before = erc20.contract.functions.balanceOf(sender.address).call() - balance_acc2_before = erc20.contract.functions.balanceOf(recipient.address).call() - total_before = erc20.contract.functions.totalSupply().call() - resp = erc20.transfer(sender, recipient.address, amount) + + assert balance_after == balance_before - amount + assert total_after == total_before - amount + + amount = 1000000000000000 + resp = erc20.mint_tokens(erc20.account, accounts[9].address, amount) trx_list.append(resp["transactionHash"]) - balance_acc1_after = erc20.contract.functions.balanceOf(sender.address).call() - balance_acc2_after = erc20.contract.functions.balanceOf(recipient.address).call() - total_after = erc20.contract.functions.totalSupply().call() - assert balance_acc1_after == balance_acc1_before - amount - assert balance_acc2_after == balance_acc2_before + amount - assert total_before == total_after + + amount = 500 + for i in range(8): + recipient = accounts[i] + sender = accounts[9] + balance_acc1_before = erc20.contract.functions.balanceOf(sender.address).call() + balance_acc2_before = erc20.contract.functions.balanceOf(recipient.address).call() + total_before = erc20.contract.functions.totalSupply().call() + resp = erc20.transfer(sender, recipient.address, amount) + trx_list.append(resp["transactionHash"]) + + balance_acc1_after = erc20.contract.functions.balanceOf(sender.address).call() + balance_acc2_after = erc20.contract.functions.balanceOf(recipient.address).call() + total_after = erc20.contract.functions.totalSupply().call() + assert balance_acc1_after == balance_acc1_before - amount + assert balance_acc2_after == balance_acc2_before + amount + assert total_before == total_after + + for i in range(7): + recipient = accounts[8] + sender = accounts[i] + balance_acc1_before = erc20.contract.functions.balanceOf(sender.address).call() + balance_acc2_before = erc20.contract.functions.balanceOf(recipient.address).call() + total_before = erc20.contract.functions.totalSupply().call() + resp = erc20.transfer(sender, recipient.address, amount) + trx_list.append(resp["transactionHash"]) + balance_acc1_after = erc20.contract.functions.balanceOf(sender.address).call() + balance_acc2_after = erc20.contract.functions.balanceOf(recipient.address).call() + total_after = erc20.contract.functions.totalSupply().call() + assert balance_acc1_after == balance_acc1_before - amount + assert balance_acc2_after == balance_acc2_before + amount + assert total_before == total_after From 9f30cd233e540bbde202613d763df0eb4d515368 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Wed, 29 Nov 2023 10:27:26 +0100 Subject: [PATCH 34/56] Update clickfile.py --- clickfile.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clickfile.py b/clickfile.py index 5f9f64e81b..fc08499a8d 100755 --- a/clickfile.py +++ b/clickfile.py @@ -425,6 +425,8 @@ def get_evm_pinned_version(branch): tag = pipeline_file["env"]["NEON_EVM_TAG"] if tag == "latest": return "develop" + if re.match(r"[vt]{1}\d{1,2}\.\d{1,2}.*", tag) is not None: + tag = re.sub(r"\.\d+$", ".x", tag) return tag From 5cd51a4243e1acf8e3b7033a764d1f25633b3d8d Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Wed, 29 Nov 2023 18:04:11 +0100 Subject: [PATCH 35/56] added github actions removed print Update dapps.yml fix dapps custom run more logs Update dapps.yml Update dapps.yml oz fix Update openzeppelin.yml fix url fix env fix fix fix Update openzeppelin.yml Update openzeppelin.yml fix checkout step fix job needs Update openzeppelin.yml added destroy job Update openzeppelin.yml fix TF_VAR_proxy_model_commit fix envs name fix private key path Update action.yml Update action.yml Update dapps.py Update dapps.py Update action.yml Update action.yml added AWS keys Update action.yml added logs Update openzeppelin.yml Update openzeppelin.yml Update action.yml Update openzeppelin.yml add terraform env params fix file path fix python-terraform fix devnet_solana_url fix ci_stands_key_hcloud Update openzeppelin.yml fix stand name added hetzner env Update old_openzeppelin.yml test oz new version --- .github/actions/destroy-tf-stand/action.yml | 38 ++++ .github/actions/prepare-stand/action.yml | 120 +++++++++++ .github/workflows/dapps.yml | 215 +++++++------------- .github/workflows/openzeppelin.yml | 109 ++++++---- clickfile.py | 14 +- conftest.py | 2 +- deploy/aws/data.tf | 62 ++++++ deploy/aws/provider.tf | 16 ++ deploy/cli/dapps.py | 153 ++++++-------- deploy/cli/network_manager.py | 8 + deploy/hetzner/data.tf | 62 ++++++ deploy/hetzner/main.tf | 81 ++++++++ deploy/hetzner/output.tf | 11 + deploy/hetzner/provider.tf | 16 ++ deploy/hetzner/proxy_init.sh | 133 ++++++++++++ deploy/hetzner/solana_init.sh | 64 ++++++ deploy/hetzner/terraform.auto.tfvars | 1 + deploy/hetzner/vars.tf | 20 ++ deploy/requirements/click.txt | 1 + envs.json | 2 +- 20 files changed, 847 insertions(+), 281 deletions(-) create mode 100644 .github/actions/destroy-tf-stand/action.yml create mode 100644 .github/actions/prepare-stand/action.yml create mode 100644 deploy/aws/data.tf create mode 100644 deploy/aws/provider.tf create mode 100644 deploy/hetzner/data.tf create mode 100644 deploy/hetzner/main.tf create mode 100644 deploy/hetzner/output.tf create mode 100644 deploy/hetzner/provider.tf create mode 100644 deploy/hetzner/proxy_init.sh create mode 100644 deploy/hetzner/solana_init.sh create mode 100644 deploy/hetzner/terraform.auto.tfvars create mode 100644 deploy/hetzner/vars.tf diff --git a/.github/actions/destroy-tf-stand/action.yml b/.github/actions/destroy-tf-stand/action.yml new file mode 100644 index 0000000000..0209273667 --- /dev/null +++ b/.github/actions/destroy-tf-stand/action.yml @@ -0,0 +1,38 @@ +name: "Destroy stand" +description: "Destroy stand created by terraform" +inputs: + ci_stands_key_hcloud: + description: 'Private key for hcloud' + required: true + devnet_solana_url: + description: 'Solana url for devnet' + required: true +runs: + using: composite + steps: + - name: Install python requirements + if: always() + id: requirements + uses: ./.github/actions/python-requirements + - name: Prepare server for destroy terraform stand + if: always() + shell: bash + run: | + ssh_key=/tmp/ci-stands + echo "${{ inputs.ci_stands_key_hcloud }}" >> ${ssh_key} && chmod 400 ${ssh_key} + - name: Download docker logs + shell: bash + run: python3 ./clickfile.py infra download-logs + - name: Destroy stand with terraform + shell: bash + id: destroy + env: + TF_VAR_ci_pp_solana_url: ${{inputs.devnet_solana_url}} + if: always() + run: | + python3 ./clickfile.py infra destroy + - uses: actions/upload-artifact@v3 + if: always() + with: + name: AWS docker logs + path: ./logs/* \ No newline at end of file diff --git a/.github/actions/prepare-stand/action.yml b/.github/actions/prepare-stand/action.yml new file mode 100644 index 0000000000..4a457c91ca --- /dev/null +++ b/.github/actions/prepare-stand/action.yml @@ -0,0 +1,120 @@ +name: "Prepare stand" +description: "Prepare environments for running tests and create terraform stand if it needed" +outputs: + solana_url: + description: "solana url" + value: ${{ steps.share.outputs.solana_url }} + proxy_url: + description: "proxy url" + value: ${{ steps.share.outputs.proxy_url }} + faucet_url: + description: "faucet url" + value: ${{ steps.share.outputs.faucet_url }} + network_id: + description: "network id" + value: ${{ steps.share.outputs.network_id }} + proxy_ip: + description: "proxy ip" + value: ${{ steps.share.outputs.proxy_ip }} + solana_ip: + description: "solana ip" + value: ${{ steps.share.outputs.solana_ip }} +inputs: + network: + description: 'Stand name' + required: true + ci_stands_key_hcloud: + description: 'Private key for hcloud' + required: false + devnet_solana_url: + description: 'Solana url for devnet' + required: false + tfstate_bucket: + description: 'Terraform state bucket' + required: false + tfstate_key_prefix: + description: 'Terraform state key prefix' + required: false + tfstate_region: + description: 'Terraform state region' + required: false + proxy_url: + description: 'Proxy url if network is custom' + required: false + faucet_url: + description: 'Faucet url if network is custom' + required: false + solana_url: + description: 'Solana url if network is custom' + required: false + network_id: + description: 'Network id if network is custom' + required: false +runs: + using: composite + + steps: + - name: Install python requirements + id: requirements + uses: ./.github/actions/python-requirements + - name: Deploy stand with terraform + id: deploy + env: + TF_VAR_ci_pp_solana_url: ${{inputs.devnet_solana_url}} + shell: bash + if: inputs.network == 'terraform' + run: | + ssh_key='/tmp/ci-stands' + echo "${{ inputs.ci_stands_key_hcloud }}" >> ${ssh_key} && chmod 400 ${ssh_key} + python3 ./clickfile.py infra deploy + - uses: actions/upload-artifact@v3 + if: inputs.network == 'terraform' + with: + name: tf-state + path: deploy/hetzner/ + + - name: Set outputs + id: share + env: + SOLANA_IP: ${{ env.SOLANA_IP }} + PROXY_IP: ${{ env.PROXY_IP }} + TFSTATE_BUCKET: ${{ inputs.tfstate_bucket }} + TFSTATE_KEY_PREFIX: ${{ inputs.tfstate_key_prefix }} + TFSTATE_REGION: ${{ inputs.tfstate_region }} + shell: bash + run: | + if [[ "${{inputs.network}}" == "custom" ]]; then + proxy_url=${{ inputs.proxy_url }} + faucet_url=${{ inputs.faucet_url }} + solana_url=${{ inputs.solana_url }} + network_id=${{ inputs.network_id }} + else + proxy_url=`python ./clickfile.py infra print-network-param -n '${{inputs.network}}' -p 'proxy_url'` + solana_url=`python ./clickfile.py infra print-network-param -n '${{inputs.network}}' -p 'solana_url'` + faucet_url=`python ./clickfile.py infra print-network-param -n '${{inputs.network}}' -p 'faucet_url'` + network_id=`python ./clickfile.py infra print-network-param -n '${{inputs.network}}' -p 'network_ids.neon'` + fi; + + if [[ "${{ inputs.network }}" == "devnet" ]]; then + solana_url="${{ inputs.devnet_solana_url }}" + fi; + echo "proxy_url=$proxy_url" + echo "solana_url=$solana_url" + echo "faucet_url=$faucet_url" + echo "network_id=$network_id" + echo "solana_ip=${{ env.SOLANA_IP }}" + echo "proxy_ip=${{ env.PROXY_IP }}" + echo "proxy_url=$proxy_url" >> $GITHUB_OUTPUT + echo "solana_url=$solana_url" >> $GITHUB_OUTPUT + echo "faucet_url=$faucet_url" >> $GITHUB_OUTPUT + echo "network_id=$network_id" >> $GITHUB_OUTPUT + echo "proxy_ip=${{ env.PROXY_IP }}" >> $GITHUB_OUTPUT + echo "solana_ip=${{ env.SOLANA_IP }}" >> $GITHUB_OUTPUT + + - name: Wait until proxy is ready + shell: bash + run: | + while [[ "$(curl -s -X POST -o /dev/null -w ''%{http_code}'' ${{ steps.share.outputs.proxy_url }})" != "200" ]]; + do echo "Proxy is not ready yet. Waiting 5 seconds..."; + sleep 5; + done diff --git a/.github/workflows/dapps.yml b/.github/workflows/dapps.yml index 52ea75f326..bfcc838806 100644 --- a/.github/workflows/dapps.yml +++ b/.github/workflows/dapps.yml @@ -14,7 +14,7 @@ on: options: - night-stand - devnet - - aws + - terraform - custom dapps: type: string @@ -41,6 +41,7 @@ on: required: false description: "Url to send the report as comment for PR" env: + NETWORK: ${{ github.event.inputs.network || 'night-stand' }} AWS_ACCESS_KEY_ID: ${{ secrets.DAPPS_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.DAPPS_AWS_SECRET_ACCESS_KEY }} AWS_REGION: us-east-2 @@ -63,95 +64,39 @@ jobs: echo "list=${list}" echo "list=${list}" >> $GITHUB_OUTPUT - - name: Define network - id: stand - run: | - if [[ "${{ github.event.inputs.network }}" != "" ]]; then - network=${{ github.event.inputs.network }} - else - network=aws - fi; - echo "network=${network}" - echo "network=${network}" >> $GITHUB_OUTPUT - - - name: Install terraform - id: terraform - if: steps.stand.outputs.network == 'aws' - run: | - sudo apt-get update && sudo apt-get install -y python-dev gnupg software-properties-common wget jq - wget -O- https://apt.releases.hashicorp.com/gpg | \ - gpg --dearmor | \ - sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg - gpg --no-default-keyring \ - --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg \ - --fingerprint - echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \ - https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \ - sudo tee /etc/apt/sources.list.d/hashicorp.list - sudo apt update - sudo apt-get install terraform - - - - name: Install python requirements - id: requirements - uses: ./.github/actions/python-requirements - - - name: Deploy stand with terraform - id: deploy - if: steps.stand.outputs.network == 'aws' - run: | - ssh_key=/tmp/dapps-stand - echo "${{ secrets.SSH_DAPPS_PRIVATE_KEY }}" >> ${ssh_key} && chmod 400 ${ssh_key} - python3 ./clickfile.py infra deploy - - uses: actions/upload-artifact@v3 - if: steps.stand.outputs.network == 'aws' + - name: "Prepare stand" + id: prepare_stand + env: + AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} + AWS_DEFAULT_REGION: ${{secrets.AWS_DEFAULT_REGION}} + AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} + HCLOUD_TOKEN: ${{secrets.HCLOUD_TOKEN}} + TFSTATE_BUCKET: ${{vars.TFSTATE_BUCKET}} + TFSTATE_KEY_PREFIX: ${{vars.TFSTATE_KEY_PREFIX}} + TFSTATE_REGION: ${{vars.TFSTATE_REGION}} + uses: ./.github/actions/prepare-stand with: - name: tf-state - path: deploy/aws/ - + network: ${{ env.NETWORK }} + ci_stands_key_hcloud: ${{ secrets.CI_STANDS_KEY_HCLOUD }} + devnet_solana_url: ${{ secrets.SOLANA_URL }} + proxy_url: ${{ github.event.inputs.proxy_url }} + solana_url: ${{ github.event.inputs.solana_url }} + faucet_url: ${{ github.event.inputs.faucet_url }} + network_id: ${{ github.event.inputs.network_id }} - name: Set outputs - id: share - env: - SOLANA_IP: ${{ env.SOLANA_IP }} - PROXY_IP: ${{ env.PROXY_IP }} + id: vars run: | - if [[ "${{steps.stand.outputs.network}}" == "custom" ]]; then - proxy_url=${{ github.event.inputs.proxy_url }} - faucet_url=${{ github.event.inputs.faucet_url }} - solana_url=${{ github.event.inputs.solana_url }} - network_id=${{ github.event.inputs.network_id }} - else - proxy_url=`python ./clickfile.py infra print-network-param -n '${{steps.stand.outputs.network}}' -p 'proxy_url'` - solana_url=`python ./clickfile.py infra print-network-param -n '${{steps.stand.outputs.network}}' -p 'solana_url'` - faucet_url=`python ./clickfile.py infra print-network-param -n '${{steps.stand.outputs.network}}' -p 'faucet_url'` - network_id=`python ./clickfile.py infra print-network-param -n '${{steps.stand.outputs.network}}' -p 'network_ids.neon'` - fi; - - if [[ "${{steps.stand.outputs.network}}" == "devnet" ]]; then - solana_url="${{ secrets.SOLANA_URL }}" - fi; - echo "proxy_url=$proxy_url" - echo "solana_url=$solana_url" - echo "faucet_url=$faucet_url" - echo "network_id=$network_id" - echo "proxy_url=$proxy_url" >> $GITHUB_OUTPUT - echo "solana_url=$solana_url" >> $GITHUB_OUTPUT - echo "faucet_url=$faucet_url" >> $GITHUB_OUTPUT - echo "network_id=$network_id" >> $GITHUB_OUTPUT - - - name: Wait until proxy is ready - timeout-minutes: 5 - run: | - while [[ "$(curl -s -X POST -o /dev/null -w ''%{http_code}'' ${{ steps.share.outputs.proxy_url }})" != "200" ]]; - do echo "Proxy is not ready yet. Waiting 5 seconds..."; - sleep 5; - done + echo "runner=${{ env.RUNNER }}" >> $GITHUB_OUTPUT + # echo "network=${{ env.NETWORK }}" >> $GITHUB_OUTPUT outputs: - solana_url: ${{ steps.share.outputs.solana_url }} - proxy_url: ${{ steps.share.outputs.proxy_url }} - faucet_url: ${{ steps.share.outputs.faucet_url }} - network_id: ${{ steps.share.outputs.network_id }} - network: ${{ steps.stand.outputs.network }} + runner: ${{ steps.vars.outputs.runner }} + # network: ${{ steps.vars.outputs.network }} + solana_url: ${{ steps.prepare_stand.outputs.solana_url }} + proxy_url: ${{ steps.prepare_stand.outputs.proxy_url }} + faucet_url: ${{ steps.prepare_stand.outputs.faucet_url }} + network_id: ${{ steps.prepare_stand.outputs.network_id }} + proxy_ip: ${{ steps.prepare_stand.outputs.proxy_ip }} + solana_ip: ${{ steps.prepare_stand.outputs.solana_ip }} dapps: ${{ steps.dapps.outputs.list }} uniswap-v2: @@ -164,7 +109,8 @@ jobs: PROXY_URL: ${{ needs.prepare.outputs.proxy_url }} FAUCET_URL: ${{ needs.prepare.outputs.faucet_url }} NETWORK_ID: ${{ needs.prepare.outputs.network_id }} - NETWORK_NAME: ${{ needs.prepare.outputs.network }} + SOLANA_IP: ${{ needs.prepare.outputs.solana_ip }} + PROXY_IP: ${{ needs.prepare.outputs.proxy_ip }} DUMP_ENVS: True steps: - uses: actions/checkout@v3 @@ -175,7 +121,7 @@ jobs: - name: Prepare accounts id: accounts run: | - python3 ./clickfile.py infra gen-accounts -c 3 -a 10000 -n ${{env.NETWORK_NAME}} + python3 ./clickfile.py infra gen-accounts -c 3 -a 10000 -n ${{env.NETWORK}} - name: Launch uniswap v2 tests if: ${{ steps.accounts.outcome == 'success' }} @@ -208,11 +154,12 @@ jobs: needs: - prepare env: + SOLANA_IP: ${{ needs.prepare.outputs.solana_ip }} + PROXY_IP: ${{ needs.prepare.outputs.proxy_ip }} SOLANA_URL: ${{ needs.prepare.outputs.solana_url }} PROXY_URL: ${{ needs.prepare.outputs.proxy_url }} FAUCET_URL: ${{ needs.prepare.outputs.faucet_url }} NETWORK_ID: ${{ needs.prepare.outputs.network_id }} - NETWORK_NAME: ${{ needs.prepare.outputs.network }} DUMP_ENVS: True steps: - uses: actions/checkout@v3 @@ -223,7 +170,7 @@ jobs: - name: Prepare accounts id: accounts run: | - python3 ./clickfile.py infra gen-accounts -c 3 -a 10000 -n ${{env.NETWORK_NAME}} + python3 ./clickfile.py infra gen-accounts -c 3 -a 10000 -n ${{env.NETWORK}} - name: Launch uniswap v3 tests if: ${{ steps.accounts.outcome == 'success'}} @@ -259,7 +206,8 @@ jobs: PROXY_URL: ${{ needs.prepare.outputs.proxy_url }} FAUCET_URL: ${{ needs.prepare.outputs.faucet_url }} NETWORK_ID: ${{ needs.prepare.outputs.network_id }} - NETWORK_NAME: ${{ needs.prepare.outputs.network }} + SOLANA_IP: ${{ needs.prepare.outputs.solana_ip }} + PROXY_IP: ${{ needs.prepare.outputs.proxy_ip }} DUMP_ENVS: True steps: - uses: actions/checkout@v3 @@ -270,7 +218,7 @@ jobs: - name: Prepare accounts id: accounts run: | - python3 ./clickfile.py infra gen-accounts -c 4 -a 10000 -n ${{env.NETWORK_NAME}} + python3 ./clickfile.py infra gen-accounts -c 4 -a 10000 -n ${{env.NETWORK}} - name: Launch saddle tests if: ${{ steps.accounts.outcome == 'success' }} @@ -307,7 +255,8 @@ jobs: PROXY_URL: ${{ needs.prepare.outputs.proxy_url }} FAUCET_URL: ${{ needs.prepare.outputs.faucet_url }} NETWORK_ID: ${{ needs.prepare.outputs.network_id }} - NETWORK_NAME: ${{ needs.prepare.outputs.network }} + SOLANA_IP: ${{ needs.prepare.outputs.solana_ip }} + PROXY_IP: ${{ needs.prepare.outputs.proxy_ip }} DUMP_ENVS: True steps: - uses: actions/checkout@v3 @@ -318,7 +267,7 @@ jobs: - name: Prepare accounts id: accounts run: | - python3 ./clickfile.py infra gen-accounts -c 5 -a 20000 -n ${{env.NETWORK_NAME}} + python3 ./clickfile.py infra gen-accounts -c 5 -a 20000 -n ${{env.NETWORK}} - name: Launch aave tests if: ${{ steps.accounts.outcome == 'success' }} @@ -348,7 +297,8 @@ jobs: PROXY_URL: ${{ needs.prepare.outputs.proxy_url }} FAUCET_URL: ${{ needs.prepare.outputs.faucet_url }} NETWORK_ID: ${{ needs.prepare.outputs.network_id }} - NETWORK_NAME: ${{ needs.prepare.outputs.network }} + SOLANA_IP: ${{ needs.prepare.outputs.solana_ip }} + PROXY_IP: ${{ needs.prepare.outputs.proxy_ip }} DUMP_ENVS: True steps: - uses: actions/checkout@v3 @@ -359,7 +309,7 @@ jobs: - name: Prepare accounts id: accounts run: | - python3 ./clickfile.py infra gen-accounts -c 8 -a 10000 -n ${{env.NETWORK_NAME}} + python3 ./clickfile.py infra gen-accounts -c 8 -a 10000 -n ${{env.NETWORK}} - name: Launch curve tests id: curve @@ -394,7 +344,8 @@ jobs: PROXY_URL: ${{ needs.prepare.outputs.proxy_url }} FAUCET_URL: ${{ needs.prepare.outputs.faucet_url }} NETWORK_ID: ${{ needs.prepare.outputs.network_id }} - NETWORK_NAME: ${{ needs.prepare.outputs.network }} + SOLANA_IP: ${{ needs.prepare.outputs.solana_ip }} + PROXY_IP: ${{ needs.prepare.outputs.proxy_ip }} DUMP_ENVS: True steps: - uses: actions/checkout@v3 @@ -423,7 +374,8 @@ jobs: PROXY_URL: ${{ needs.prepare.outputs.proxy_url }} FAUCET_URL: ${{ needs.prepare.outputs.faucet_url }} NETWORK_ID: ${{ needs.prepare.outputs.network_id }} - NETWORK_NAME: ${{ needs.prepare.outputs.network }} + SOLANA_IP: ${{ needs.prepare.outputs.solana_ip }} + PROXY_IP: ${{ needs.prepare.outputs.proxy_ip }} DUMP_ENVS: True steps: - uses: actions/checkout@v3 @@ -434,7 +386,7 @@ jobs: - name: Prepare accounts id: accounts run: | - python3 ./clickfile.py infra gen-accounts -c 2 -a 10000 -n ${{env.NETWORK_NAME}} + python3 ./clickfile.py infra gen-accounts -c 2 -a 10000 -n ${{env.NETWORK}} - name: Launch compound tests timeout-minutes: 30 @@ -469,7 +421,8 @@ jobs: PROXY_URL: ${{ needs.prepare.outputs.proxy_url }} FAUCET_URL: ${{ needs.prepare.outputs.faucet_url }} NETWORK_ID: ${{ needs.prepare.outputs.network_id }} - NETWORK_NAME: ${{ needs.prepare.outputs.network }} + SOLANA_IP: ${{ needs.prepare.outputs.solana_ip }} + PROXY_IP: ${{ needs.prepare.outputs.proxy_ip }} DUMP_ENVS: True steps: - uses: actions/checkout@v3 @@ -480,7 +433,7 @@ jobs: - name: Prepare accounts id: accounts run: | - python3 ./clickfile.py infra gen-accounts -c 3 -a 10000 -n ${{env.NETWORK_NAME}} + python3 ./clickfile.py infra gen-accounts -c 3 -a 10000 -n ${{env.NETWORK}} - name: Launch robonomics tests if: ${{ steps.accounts.outcome == 'success' }} @@ -513,7 +466,8 @@ jobs: PROXY_URL: ${{ needs.prepare.outputs.proxy_url }} FAUCET_URL: ${{ needs.prepare.outputs.faucet_url }} NETWORK_ID: ${{ needs.prepare.outputs.network_id }} - NETWORK_NAME: ${{ needs.prepare.outputs.network }} + SOLANA_IP: ${{ needs.prepare.outputs.solana_ip }} + PROXY_IP: ${{ needs.prepare.outputs.proxy_ip }} DUMP_ENVS: True steps: - uses: actions/checkout@v3 @@ -524,7 +478,7 @@ jobs: - name: Prepare accounts id: accounts run: | - python3 ./clickfile.py infra gen-accounts -c 10 -a 10000 -n ${{env.NETWORK_NAME}} + python3 ./clickfile.py infra gen-accounts -c 10 -a 10000 -n ${{env.NETWORK}} - name: Launch yearn tests id: yearn @@ -560,6 +514,7 @@ jobs: PROXY_URL: ${{ needs.prepare.outputs.proxy_url }} NETWORK_ID: ${{ needs.prepare.outputs.network_id }} NETWORK: ${{ needs.prepare.outputs.network }} + steps: - uses: actions/checkout@v3 - name: Install python requirements @@ -618,46 +573,26 @@ jobs: destroy: runs-on: ubuntu-20.04 - needs: [prepare, uniswap-v2, uniswap-v3, saddle, curve, aave, yearn, robonomics, swap-report, compound, curve-factory] - if: always() && needs.prepare.outputs.network == 'aws' + needs: [ prepare, uniswap-v2, uniswap-v3, saddle, curve, aave, yearn, robonomics, swap-report, compound, curve-factory ] + if: always() && needs.prepare.outputs.network == 'terraform' steps: - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 - with: - name: tf-state - - name: Install python requirements - if: always() - id: requirements - uses: ./.github/actions/python-requirements - - name: Prepare server for destroy terraform stand - if: always() - run: | - sudo apt-get update && sudo apt-get install -y python-dev gnupg software-properties-common wget - wget -O- https://apt.releases.hashicorp.com/gpg | \ - gpg --dearmor | \ - sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg - gpg --no-default-keyring \ - --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg \ - --fingerprint - echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \ - https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \ - sudo tee /etc/apt/sources.list.d/hashicorp.list - sudo apt update - sudo apt-get install terraform - ssh_key=/tmp/dapps-stand - echo "${{ secrets.SSH_DAPPS_PRIVATE_KEY }}" >> ${ssh_key} && chmod 400 ${ssh_key} - - name: Download docker logs - run: python3 ./clickfile.py infra download-logs - - name: Destroy stand with terraform - id: destroy - if: always() - run: | - python3 ./clickfile.py infra destroy - - uses: actions/upload-artifact@v3 - if: always() + - name: "Destroy stand" + env: + AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} + AWS_DEFAULT_REGION: ${{secrets.AWS_DEFAULT_REGION}} + AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} + HCLOUD_TOKEN: ${{secrets.HCLOUD_TOKEN}} + TFSTATE_BUCKET: ${{vars.TFSTATE_BUCKET}} + TFSTATE_KEY_PREFIX: ${{vars.TFSTATE_KEY_PREFIX}} + TFSTATE_REGION: ${{vars.TFSTATE_REGION}} + PROXY_IP: ${{ needs.prepare.outputs.proxy_ip }} + SOLANA_IP: ${{ needs.prepare.outputs.solana_ip }} + uses: ./.github/actions/destroy-tf-stand with: - name: AWS docker logs - path: ./logs/* + ci_stands_key_hcloud: ${{ secrets.CI_STANDS_KEY_HCLOUD }} + devnet_solana_url: ${{ secrets.SOLANA_URL }} + notify: runs-on: ubuntu-20.04 diff --git a/.github/workflows/openzeppelin.yml b/.github/workflows/openzeppelin.yml index 969c3e34e3..9dfb0b28bd 100644 --- a/.github/workflows/openzeppelin.yml +++ b/.github/workflows/openzeppelin.yml @@ -13,6 +13,7 @@ on: options: - night-stand - devnet + - terraform runner: type: choice default: neon-hosted @@ -26,18 +27,22 @@ on: description: "Count of parallel jobs" required: true default: "8" + oz_branch: + description: "Which OZ branch to use (if it is empty 'master', branch will be used)" + required: false + default: master env: - JOBS_NUMBER: "8" - NETWORK: night-stand - RUNNER: neon-hosted + JOBS_NUMBER: ${{ github.event.inputs.jobsNumber || '8' }} + NETWORK: ${{ github.event.inputs.network || 'night-stand' }} + RUNNER: ${{github.event.inputs.runner || 'neon-hosted' }} IMAGE: neonlabsorg/neon_tests CONTAINER: oz-${{ github.run_id }} BUILD_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" jobs: dockerize: - if: ${{ github.ref_name != 'develop'}} + if: github.ref_name != 'develop' || github.event.inputs.oz_branch !='' runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 @@ -47,49 +52,46 @@ jobs: image_tag: ${{ github.sha }} docker_username: ${{ secrets.DOCKER_USERNAME }} docker_password: ${{ secrets.DOCKER_PASSWORD }} + oz_branch: ${{ github.event.inputs.oz_branch || 'master' }} prepare-env: runs-on: ubuntu-20.04 steps: - - name: Setup env - id: setup + - uses: actions/checkout@v3 + - name: "Prepare stand" + id: prepare_stand + env: + AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} + AWS_DEFAULT_REGION: ${{secrets.AWS_DEFAULT_REGION}} + AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} + HCLOUD_TOKEN: ${{secrets.HCLOUD_TOKEN}} + TFSTATE_BUCKET: ${{vars.TFSTATE_BUCKET}} + TFSTATE_KEY_PREFIX: ${{vars.TFSTATE_KEY_PREFIX}} + TFSTATE_REGION: ${{vars.TFSTATE_REGION}} + uses: ./.github/actions/prepare-stand + with: + network: ${{ env.NETWORK }} + ci_stands_key_hcloud: ${{ secrets.CI_STANDS_KEY_HCLOUD }} + devnet_solana_url: ${{ secrets.SOLANA_URL }} + - name: Set outputs + id: vars run: | - # $1 - inputs - # $2 - env.VAR - function setVar { - if [ -z "$1" ] - then - RESULT="$2" - else - RESULT="$1" - fi - echo $RESULT - } - - NETWORK=$( setVar "${{ github.event.inputs.network }}" "${{ env.NETWORK }}" ) - RUNNER=$( setVar "${{ github.event.inputs.runner }}" "${{ env.RUNNER }}" ) - JOBS_NUMBER=$( setVar "${{ github.event.inputs.jobsNumber }}" "${{ env.JOBS_NUMBER }}" ) - - echo "Network: ${NETWORK}" - echo "Runner: ${RUNNER}" - echo "Jobs: ${JOBS_NUMBER}" - - echo "network=${NETWORK}" >> $GITHUB_OUTPUT - echo "runner=${RUNNER}" >> $GITHUB_OUTPUT - echo "jobs=${JOBS_NUMBER}" >> $GITHUB_OUTPUT + echo "runner=${{ env.RUNNER }}" >> $GITHUB_OUTPUT + echo "network=${{ env.NETWORK }}" >> $GITHUB_OUTPUT outputs: - network: ${{ steps.setup.outputs.network }} - runner: ${{ steps.setup.outputs.runner }} - jobs: ${{ steps.setup.outputs.jobs }} + runner: ${{ steps.vars.outputs.runner }} + network: ${{ steps.vars.outputs.network }} + solana_url: ${{ steps.prepare_stand.outputs.solana_url }} + proxy_url: ${{ steps.prepare_stand.outputs.proxy_url }} + faucet_url: ${{ steps.prepare_stand.outputs.faucet_url }} + network_id: ${{ steps.prepare_stand.outputs.network_id }} + proxy_ip: ${{ steps.prepare_stand.outputs.proxy_ip }} + solana_ip: ${{ steps.prepare_stand.outputs.solana_ip }} tests: name: OpenZeppelin tests needs: - prepare-env - dockerize - if: | - always() && - !contains(needs.*.result, 'failure') && - !contains(needs.*.result, 'cancelled') runs-on: ${{ needs.prepare-env.outputs.runner }} env: AWS_ACCESS_KEY_ID: "${{ secrets.AWS_ACCESS_KEY_ID }}" @@ -107,9 +109,14 @@ jobs: - name: Run OpenZeppelin tests timeout-minutes: 150 run: | - docker exec -i -e OZ_BALANCES_REPORT_FLAG=1 ${{ env.CONTAINER }} python3 ./clickfile.py run oz \ - --network ${{ needs.prepare-env.outputs.network }} \ - --jobs ${{ needs.prepare-env.outputs.jobs }} \ + env='' + if [ "${{ env.NETWORK }}" == "terraform" ]; then + env="-e PROXY_IP=${{ needs.prepare-env.outputs.proxy_ip }} -e SOLANA_IP=${{ needs.prepare-env.outputs.solana_ip }}" + fi + echo "env=${env}" + docker exec -i -e OZ_BALANCES_REPORT_FLAG=1 ${env} ${{ env.CONTAINER }} python3 ./clickfile.py run oz \ + --network ${{ env.NETWORK }} \ + --jobs ${{ env.JOBS_NUMBER }} \ --users 10 - name: Print OpenZeppelin report run: | @@ -130,7 +137,29 @@ jobs: run: | docker exec -i ${{ env.CONTAINER }} \ python3 ./clickfile.py send-notification -u ${{ secrets.SLACK_QA_CHANNEL_URL }} \ - -b ${{ env.BUILD_URL }} -n ${{ needs.prepare-env.outputs.network }} + -b ${{ env.BUILD_URL }} -n ${{ env.NETWORK }} - name: Remove docker container if: always() run: docker rm -f ${{ env.CONTAINER }} + + destroy: + runs-on: ubuntu-20.04 + needs: [tests, prepare-env] + if: always() && needs.prepare-env.outputs.network == 'terraform' + steps: + - uses: actions/checkout@v3 + - name: "Destroy stand" + env: + AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} + AWS_DEFAULT_REGION: ${{secrets.AWS_DEFAULT_REGION}} + AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} + HCLOUD_TOKEN: ${{secrets.HCLOUD_TOKEN}} + TFSTATE_BUCKET: ${{vars.TFSTATE_BUCKET}} + TFSTATE_KEY_PREFIX: ${{vars.TFSTATE_KEY_PREFIX}} + TFSTATE_REGION: ${{vars.TFSTATE_REGION}} + PROXY_IP: ${{ needs.prepare-env.outputs.proxy_ip }} + SOLANA_IP: ${{ needs.prepare-env.outputs.solana_ip }} + uses: ./.github/actions/destroy-tf-stand + with: + ci_stands_key_hcloud: ${{ secrets.CI_STANDS_KEY_HCLOUD }} + devnet_solana_url: ${{ secrets.SOLANA_URL }} diff --git a/clickfile.py b/clickfile.py index 5f9f64e81b..797c2db56f 100755 --- a/clickfile.py +++ b/clickfile.py @@ -10,6 +10,7 @@ import subprocess import sys import typing as tp +from pathlib import Path from multiprocessing.dummy import Pool from urllib.parse import urlparse @@ -131,7 +132,7 @@ def float_2_str(d): return dict(map(lambda i: (i[0], str(i[1])), d.items())) if os.environ.get("OZ_BALANCES_REPORT_FLAG") is not None: - network = network_manager.networks[args[0]] + network = network_manager.get_network_object(args[0]) op = Operator( network["proxy_url"], network["solana_url"], @@ -177,17 +178,12 @@ def run_openzeppelin_tests(network, jobs=8, amount=20000, users=8): ) (cwd.parent / "results").mkdir(parents=True, exist_ok=True) keys_env = [ - faucet_cli.prepare_wallets_with_balance( - network_manager.networks[network], count=users, airdrop_amount=amount - ) + dapps_cli.prepare_accounts(network, users, amount) for i in range(jobs) ] - tests = ( - subprocess.check_output("find \"test\" -name '*.test.js'", shell=True, cwd=cwd) - .decode() - .splitlines() - ) + tests = list(Path(f"{cwd}/test").rglob('*.test.js')) + tests = [str(test) for test in tests if 'test' in test.read_text()] def run_oz_file(file_name): print(f"Run {file_name}") diff --git a/conftest.py b/conftest.py index 3877eef91f..fdd42cfbb0 100644 --- a/conftest.py +++ b/conftest.py @@ -86,7 +86,7 @@ def pytest_configure(config: Config): env["use_bank"] = False if "eth_bank_account" not in env: env["eth_bank_account"] = "" - if network_name == "aws": + if network_name == "terraform": env["solana_url"] = env["solana_url"].replace("", os.environ.get("SOLANA_IP")) env["proxy_url"] = env["proxy_url"].replace("", os.environ.get("PROXY_IP")) env["faucet_url"] = env["faucet_url"].replace("", os.environ.get("PROXY_IP")) diff --git a/deploy/aws/data.tf b/deploy/aws/data.tf new file mode 100644 index 0000000000..b4185846ae --- /dev/null +++ b/deploy/aws/data.tf @@ -0,0 +1,62 @@ +data "hcloud_image" "ci-image" { + name = "ubuntu-22.04" + with_architecture = "x86" +} + +data "hcloud_network" "ci-network" { + name = "ci-network" +} + +data "hcloud_ssh_key" "ci-ssh-key" { + name = "hcloud-ci-stands" +} + +variable "branch" { + type = string +} + + +variable "proxy_model_commit" { + type = string +} + +variable "proxy_image_tag" { + type = string +} + +variable "neon_evm_commit" { + type = string +} + + +variable "faucet_model_commit" { + type = string +} + +data "template_file" "solana_init" { + template = file("solana_init.sh") + + vars = { + branch = "${var.branch}" + proxy_model_commit = "${var.proxy_model_commit}" + proxy_image_tag = "${var.proxy_image_tag}" + neon_evm_commit = "${var.neon_evm_commit}" + faucet_model_commit = "${var.faucet_model_commit}" + dockerhub_org_name = "${var.dockerhub_org_name}" + } +} + +data "template_file" "proxy_init" { + template = file("proxy_init.sh") + + vars = { + branch = "${var.branch}" + proxy_model_commit = "${var.proxy_model_commit}" + proxy_image_tag = "${var.proxy_image_tag}" + solana_ip = hcloud_server.solana.network.*.ip[0] + neon_evm_commit = "${var.neon_evm_commit}" + faucet_model_commit = "${var.faucet_model_commit}" + ci_pp_solana_url = "${var.ci_pp_solana_url}" + dockerhub_org_name = "${var.dockerhub_org_name}" + } +} diff --git a/deploy/aws/provider.tf b/deploy/aws/provider.tf new file mode 100644 index 0000000000..5fbd1132e7 --- /dev/null +++ b/deploy/aws/provider.tf @@ -0,0 +1,16 @@ +terraform { + required_providers { + hcloud = { + source = "hetznercloud/hcloud" + version = "1.44.1" + } + } + + backend "s3" { + // Must be set from environment + } +} + +provider "hcloud" { + # Configuration options +} diff --git a/deploy/cli/dapps.py b/deploy/cli/dapps.py index 2fc2b66859..e46fc087b6 100644 --- a/deploy/cli/dapps.py +++ b/deploy/cli/dapps.py @@ -1,9 +1,12 @@ import os import glob import json +import re import subprocess +import sys import typing as tp import pathlib +import logging import tabulate from paramiko.client import SSHClient @@ -16,29 +19,25 @@ from utils.web3client import NeonChainWeb3Client from utils.solana_client import SolanaClient from utils.prices import get_neon_price - - -TF_CWD = "deploy/aws" - -TF_ENV = { - "AWS_SECRET_ACCESS_KEY": os.environ.get("AWS_SECRET_ACCESS_KEY"), - "AWS_ACCESS_KEY_ID": os.environ.get("AWS_ACCESS_KEY_ID"), - "AWS_S3_BUCKET": os.environ.get("AWS_S3_BUCKET", "neon-tests-dapps"), - "AWS_REGION": os.environ.get("AWS_REGION", "us-east-2"), - "TF_VAR_branch": "develop", - "TF_VAR_proxy_container_tag": os.environ.get("NEON_PROXY_TAG", "latest"), - "TF_VAR_neon_evm_container_tag": os.environ.get("NEON_EVM_TAG", "latest"), - "TF_VAR_faucet_container_tag": os.environ.get("NEON_FAUCET_TAG", "latest"), - "TF_STATE_KEY": f"neon-tests/{os.environ.get('GITHUB_RUN_ID', '0')}", -} - -TF_ENV.update( - { - "TF_BACKEND_CONFIG": f"-backend-config=\"bucket={TF_ENV['AWS_S3_BUCKET']}\" " - f"-backend-config=\"key={TF_ENV['TF_STATE_KEY']}\" " - f"-backend-config=\"region={TF_ENV['AWS_REGION']}\" ", - } -) +from python_terraform import Terraform + + +TFSTATE_BUCKET = os.environ.get("TFSTATE_BUCKET") +TFSTATE_KEY_PREFIX = os.environ.get("TFSTATE_KEY_PREFIX") +TFSTATE_REGION = os.environ.get("TFSTATE_REGION") +TF_STATE_KEY = f"{TFSTATE_KEY_PREFIX}neon-tests-{os.environ.get('GITHUB_RUN_ID', '0')}" +TF_BACKEND_CONFIG = {"bucket": TFSTATE_BUCKET, + "key": TF_STATE_KEY, "region": TFSTATE_REGION} + +os.environ["TF_VAR_branch"] = 'develop' +os.environ["TF_VAR_proxy_image_tag"] = os.environ.get("NEON_PROXY_TAG", "latest") +os.environ["TF_VAR_proxy_model_commit"] = os.environ.get("PROXY_MODEL_COMMIT", "develop") +os.environ["TF_VAR_run_number"] = os.environ.get("GITHUB_RUN_ID", "0") +os.environ["TF_VAR_neon_evm_commit"] = os.environ.get("NEON_EVM_TAG", "latest") +os.environ["TF_VAR_faucet_model_commit"] = os.environ.get("NEON_FAUCET_TAG", "latest") +os.environ["TF_VAR_dockerhub_org_name"] = os.environ.get("DOCKERHUB_ORG_NAME", "neonlabsorg") +terraform = Terraform(working_dir=pathlib.Path( + __file__).parent.parent / "hetzner") WEB3_CLIENT = NeonChainWeb3Client(os.environ.get("PROXY_URL")) REPORT_HEADERS = ["Action", "Fee", "Cost in $", "Accounts", "TRx", "Estimated Gas", "Used Gas", "Used % of EG"] @@ -52,76 +51,57 @@ def set_github_env(envs: tp.Dict, upper=True) -> None: env_file.write(f"\n{key.upper() if upper else key}={str(value)}") -def get_solana_ip() -> str: - solana_ip = subprocess.run( - "terraform output --json | jq -r '.solana_ip.value'", - shell=True, - env=TF_ENV, - cwd="deploy/aws", - stdout=subprocess.PIPE, - text=True, - ).stdout.strip() - if isinstance(solana_ip, bytes): - solana_ip = solana_ip.decode() - return solana_ip - - -def get_proxy_ip() -> str: - proxy_ip = subprocess.run( - "terraform output --json | jq -r '.proxy_ip.value'", - shell=True, - env=TF_ENV, - cwd="deploy/aws", - stdout=subprocess.PIPE, - text=True, - ).stdout.strip() - if isinstance(proxy_ip, bytes): - proxy_ip = proxy_ip.decode() - return proxy_ip +def deploy_infrastructure() -> dict: + terraform.init(backend_config=TF_BACKEND_CONFIG) + return_code, stdout, stderr = terraform.apply(skip_plan=True) + print(f"code: {return_code}") + print(f"stdout: {stdout}") + print(f"stderr: {stderr}") + with open(f"terraform.log", "w") as file: + file.write(stdout) + file.write(stderr) + if return_code != 0: + print("Terraform infrastructure is not built correctly") + sys.exit(1) + output = terraform.output(json=True) + print(f"output: {output}") + proxy_ip = output["proxy_ip"]["value"] + solana_ip = output["solana_ip"]["value"] -def deploy_infrastructure() -> dict: - subprocess.check_call( - f"terraform init {TF_ENV['TF_BACKEND_CONFIG']}", - shell=True, - env=TF_ENV, - cwd=TF_CWD, - ) - subprocess.check_call( - "terraform apply --auto-approve=true", shell=True, env=TF_ENV, cwd=TF_CWD - ) - proxy_ip = get_proxy_ip() - solana_ip = get_solana_ip() infra = dict(solana_ip=solana_ip, proxy_ip=proxy_ip) set_github_env(infra) return infra def destroy_infrastructure(): - subprocess.run( - f"terraform init {TF_ENV['TF_BACKEND_CONFIG']}", - shell=True, - env=TF_ENV, - cwd=TF_CWD, - ) - subprocess.run( - "terraform destroy --auto-approve=true", shell=True, env=TF_ENV, cwd=TF_CWD - ) + log = logging.getLogger() + log.handlers = [] + handler = logging.StreamHandler(sys.stdout) + formatter = logging.Formatter( + '%(asctime)4s %(name)4s [%(filename)s:%(lineno)s - %(funcName)s()] %(levelname)4s %(message)4s') + handler.setFormatter(formatter) + log.addHandler(handler) + log.setLevel(logging.INFO) + + def format_tf_output(output): + return re.sub(r'(?m)^', ' ' * TF_OUTPUT_OFFSET, str(output)) + + TF_OUTPUT_OFFSET = 16 + terraform.init(backend_config=TF_BACKEND_CONFIG) + tf_destroy = terraform.apply('-destroy', skip_plan=True) + log.info(format_tf_output(tf_destroy)) + + def download_remote_docker_logs(): - subprocess.run( - f"terraform init {TF_ENV['TF_BACKEND_CONFIG']}", - shell=True, - env=TF_ENV, - cwd=TF_CWD, - ) - proxy_ip = get_proxy_ip() - solana_ip = get_solana_ip() + proxy_ip = os.environ.get("PROXY_IP") + solana_ip = os.environ.get("SOLANA_IP") home_path = os.environ.get("HOME") artifact_logs = "./logs" - ssh_key = "/tmp/dapps-stand" + ssh_key = "/tmp/ci-stands" os.mkdir(artifact_logs) if not os.path.exists(f"{home_path}/.ssh"): os.mkdir(f"{home_path}/.ssh") @@ -132,13 +112,14 @@ def download_remote_docker_logs(): subprocess.run( f"ssh-keyscan -H {proxy_ip} >> {home_path}/.ssh/known_hosts", shell=True ) + ssh_client = SSHClient() ssh_client.load_system_host_keys() - ssh_client.connect(solana_ip, username="ubuntu", key_filename=ssh_key, timeout=120) + ssh_client.connect(solana_ip, username="root", key_filename=ssh_key, timeout=120) upload_service_logs(ssh_client, "solana", artifact_logs) - ssh_client.connect(proxy_ip, username="ubuntu", key_filename=ssh_key, timeout=120) + ssh_client.connect(proxy_ip, username="root", key_filename=ssh_key, timeout=120) services = ["postgres", "dbcreation", "indexer", "proxy", "faucet"] for service in services: upload_service_logs(ssh_client, service, artifact_logs) @@ -158,15 +139,7 @@ def upload_service_logs(ssh_client, service, artifact_logs): def prepare_accounts(network_name, count, amount) -> tp.List: network_manager = NetworkManager() - if network_name == "aws": - network = { - "proxy_url": os.environ.get("PROXY_URL"), - "solana_url": os.environ.get("SOLANA_URL"), - "faucet_url": os.environ.get("FAUCET_URL"), - "network_ids": network_manager.get_network_param(network_name, "network_ids") - } - else: - network = network_manager.get_network_param(network_name) + network = network_manager.get_network_object(network_name) accounts = faucet_cli.prepare_wallets_with_balance(network, count, amount) if os.environ.get("CI"): set_github_env(dict(accounts=",".join(accounts))) diff --git a/deploy/cli/network_manager.py b/deploy/cli/network_manager.py index 61b117ed84..813c867b8a 100644 --- a/deploy/cli/network_manager.py +++ b/deploy/cli/network_manager.py @@ -37,3 +37,11 @@ def get_network_param(self, network, params=None): if os.environ.get("PROXY_IP"): value = value.replace("", os.environ.get("PROXY_IP")) return value + + def get_network_object(self, network_name): + network = self.get_network_param(network_name) + if network_name == "terraform": + network["proxy_url"] = self.get_network_param(network_name, "proxy_url") + network["solana_url"] = self.get_network_param(network_name, "solana_url") + network["faucet_url"] = self.get_network_param(network_name, "faucet_url") + return network diff --git a/deploy/hetzner/data.tf b/deploy/hetzner/data.tf new file mode 100644 index 0000000000..b4185846ae --- /dev/null +++ b/deploy/hetzner/data.tf @@ -0,0 +1,62 @@ +data "hcloud_image" "ci-image" { + name = "ubuntu-22.04" + with_architecture = "x86" +} + +data "hcloud_network" "ci-network" { + name = "ci-network" +} + +data "hcloud_ssh_key" "ci-ssh-key" { + name = "hcloud-ci-stands" +} + +variable "branch" { + type = string +} + + +variable "proxy_model_commit" { + type = string +} + +variable "proxy_image_tag" { + type = string +} + +variable "neon_evm_commit" { + type = string +} + + +variable "faucet_model_commit" { + type = string +} + +data "template_file" "solana_init" { + template = file("solana_init.sh") + + vars = { + branch = "${var.branch}" + proxy_model_commit = "${var.proxy_model_commit}" + proxy_image_tag = "${var.proxy_image_tag}" + neon_evm_commit = "${var.neon_evm_commit}" + faucet_model_commit = "${var.faucet_model_commit}" + dockerhub_org_name = "${var.dockerhub_org_name}" + } +} + +data "template_file" "proxy_init" { + template = file("proxy_init.sh") + + vars = { + branch = "${var.branch}" + proxy_model_commit = "${var.proxy_model_commit}" + proxy_image_tag = "${var.proxy_image_tag}" + solana_ip = hcloud_server.solana.network.*.ip[0] + neon_evm_commit = "${var.neon_evm_commit}" + faucet_model_commit = "${var.faucet_model_commit}" + ci_pp_solana_url = "${var.ci_pp_solana_url}" + dockerhub_org_name = "${var.dockerhub_org_name}" + } +} diff --git a/deploy/hetzner/main.tf b/deploy/hetzner/main.tf new file mode 100644 index 0000000000..304717267b --- /dev/null +++ b/deploy/hetzner/main.tf @@ -0,0 +1,81 @@ + +resource "hcloud_server" "proxy" { + name = "proxy-${var.run_number}-${var.branch}" + image = data.hcloud_image.ci-image.id + server_type = var.server_type + location = var.location + ssh_keys = [ + data.hcloud_ssh_key.ci-ssh-key.id + ] + + public_net { + ipv4_enabled = true + ipv6_enabled = false + } + + network { + network_id = data.hcloud_network.ci-network.id + } + + provisioner "file" { + content = data.template_file.proxy_init.rendered + destination = "/tmp/proxy_init.sh" + + connection { + type = "ssh" + user = "root" + host = hcloud_server.proxy.ipv4_address + private_key = file("/tmp/ci-stands") + } + + } + + + provisioner "remote-exec" { + inline = [ + "echo '${hcloud_server.solana.network.*.ip[0]}' > /tmp/solana_host", + "chmod a+x /tmp/proxy_init.sh", + "sudo /tmp/proxy_init.sh" + ] + connection { + type = "ssh" + user = "root" + host = hcloud_server.proxy.ipv4_address + private_key = file("/tmp/ci-stands") + } + + } + + labels = { + environment = "ci" + purpose = "ci-oz-full-tests" + } + depends_on = [ + hcloud_server.solana + ] +} + +resource "hcloud_server" "solana" { + name = "solana-${var.run_number}-${var.branch}" + image = data.hcloud_image.ci-image.id + server_type = var.server_type + location = var.location + ssh_keys = [ + data.hcloud_ssh_key.ci-ssh-key.id + ] + + public_net { + ipv4_enabled = true + ipv6_enabled = false + } + + network { + network_id = data.hcloud_network.ci-network.id + } + + user_data = data.template_file.solana_init.rendered + + labels = { + environment = "ci" + } +} diff --git a/deploy/hetzner/output.tf b/deploy/hetzner/output.tf new file mode 100644 index 0000000000..cd61f5fb93 --- /dev/null +++ b/deploy/hetzner/output.tf @@ -0,0 +1,11 @@ +output "solana_ip" { + value = hcloud_server.solana.ipv4_address +} + +output "proxy_ip" { + value = hcloud_server.proxy.ipv4_address +} + +output "branch" { + value = var.branch +} diff --git a/deploy/hetzner/provider.tf b/deploy/hetzner/provider.tf new file mode 100644 index 0000000000..5fbd1132e7 --- /dev/null +++ b/deploy/hetzner/provider.tf @@ -0,0 +1,16 @@ +terraform { + required_providers { + hcloud = { + source = "hetznercloud/hcloud" + version = "1.44.1" + } + } + + backend "s3" { + // Must be set from environment + } +} + +provider "hcloud" { + # Configuration options +} diff --git a/deploy/hetzner/proxy_init.sh b/deploy/hetzner/proxy_init.sh new file mode 100644 index 0000000000..f6e0fe5612 --- /dev/null +++ b/deploy/hetzner/proxy_init.sh @@ -0,0 +1,133 @@ +#!/bin/bash + + +# Install docker +sudo apt-get remove docker docker-engine docker.io containerd runc +sudo apt-get update +sudo apt-get -y install ca-certificates curl gnupg lsb-release +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg +echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null +sudo apt-get update +sudo apt-get -y install docker-ce docker-ce-cli containerd.io + +sudo apt-get -y install pbzip2 + +# Install docker-compose +sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose +sudo chmod +x /usr/local/bin/docker-compose + + +# Get docker-compose file +cd /opt +curl -O https://raw.githubusercontent.com/neonlabsorg/proxy-model.py/${proxy_model_commit}/docker-compose/docker-compose-ci.yml + + +# Set required environment variables +export REVISION=${proxy_image_tag} +export SOLANA_URL=http:\/\/${solana_ip}:8899 +export NEON_EVM_COMMIT=${neon_evm_commit} +export FAUCET_COMMIT=${faucet_model_commit} +export CI_PP_SOLANA_URL=${ci_pp_solana_url} +export DOCKERHUB_ORG_NAME=${dockerhub_org_name} + + + +# Generate docker-compose override file +cat > docker-compose-ci.override.yml <&2 + local CHECK_COMMAND_RESULT=$(eval $CHECK_COMMAND) + echo $CHECK_COMMAND_RESULT >> /tmp/output.txt + if [[ "$CHECK_COMMAND_RESULT" == "1" ]]; then + echo "$SERVICE is up" 1>&2 + break + fi + + ((CURRENT_ATTEMPT=CURRENT_ATTEMPT+1)) + sleep 2 + done; +} + +# Check if Solana is available +SOLANA_DATA='{"jsonrpc":"2.0","id":1,"method":"getHealth"}' +SOLANA_RESULT='"ok"' +wait_service "solana" $SOLANA_URL $SOLANA_DATA $SOLANA_RESULT + +# Up all services +docker-compose -f docker-compose-ci.yml -f docker-compose-ci.override.yml up -d $SERVICES + + +# Check if Proxy is available +PROXY_URL="http://localhost:9090/solana" +PROXY_DATA='{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' +PROXY_RESULT='"result"' +wait_service "proxy" $PROXY_URL $PROXY_DATA $PROXY_RESULT + + +docker rm -f opt_solana_1 diff --git a/deploy/hetzner/solana_init.sh b/deploy/hetzner/solana_init.sh new file mode 100644 index 0000000000..9ab027d7b7 --- /dev/null +++ b/deploy/hetzner/solana_init.sh @@ -0,0 +1,64 @@ +#!/bin/bash + + +# Install docker +sudo apt-get remove docker docker-engine docker.io containerd runc +sudo apt-get update +sudo apt-get -y install ca-certificates curl gnupg lsb-release pbzip2 +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg +echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null +sudo apt-get update + + +# Tune instance for Solana requirements(must be applied before start services) +sudo bash -c "cat >/etc/sysctl.d/20-solana-udp-buffers.conf</etc/sysctl.d/20-solana-mmaps.conf</etc/security/limits.d/90-solana-nofiles.conf< docker-compose-ci.override.yml<:9090/solana", "network_ids": { From 338dd078ec25f5911bf0b14497d9fa819ced39b6 Mon Sep 17 00:00:00 2001 From: Andrei Silviu Dragnea Date: Fri, 8 Dec 2023 11:14:08 +0200 Subject: [PATCH 36/56] NDEV-2428: Run tracer tests concurrently --- clickfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clickfile.py b/clickfile.py index fc08499a8d..f0c65440f4 100755 --- a/clickfile.py +++ b/clickfile.py @@ -507,7 +507,7 @@ def run(name, jobs, numprocesses, ui_item, amount, users, network): if numprocesses: command = f"{command} --numprocesses {numprocesses} --dist loadgroup" elif name == "tracer": - command = "py.test integration/tests/tracer" + command = "py.test -n 50 integration/tests/tracer" elif name == "services": command = "py.test integration/tests/services" if numprocesses: From 5d08123a3154a3bea6174045074bf9ffb6ecc87c Mon Sep 17 00:00:00 2001 From: Andrei Silviu Dragnea Date: Fri, 8 Dec 2023 11:14:35 +0200 Subject: [PATCH 37/56] NDEV-2428: Fix tracer tests --- integration/tests/tracer/schemas/debug_traceCall.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/integration/tests/tracer/schemas/debug_traceCall.json b/integration/tests/tracer/schemas/debug_traceCall.json index 7a43330eb8..b8841578e3 100644 --- a/integration/tests/tracer/schemas/debug_traceCall.json +++ b/integration/tests/tracer/schemas/debug_traceCall.json @@ -81,9 +81,7 @@ "gas", "gasCost", "op", - "pc", - "returnData", - "stack" + "pc" ] } ] @@ -95,4 +93,4 @@ "returnValue", "structLogs" ] -} \ No newline at end of file +} From e0ccd1df7e9b1a9d9dcc42a0244f7a16981355eb Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Mon, 11 Dec 2023 16:18:58 +0100 Subject: [PATCH 38/56] moved neon_evm tests --- clickfile.py | 6 +- conftest.py | 5 +- contracts/neon_evm/calculator.sol | 22 + contracts/neon_evm/hello_world.sol | 10 + contracts/neon_evm/rw_lock.sol | 69 ++ contracts/neon_evm/small.sol | 6 + contracts/neon_evm/string_setter.sol | 14 + deploy/requirements/prod.txt | 2 + integration/tests/neon_evm/README.md | 105 +++ integration/tests/neon_evm/__init__.py | 0 integration/tests/neon_evm/conftest.py | 169 +++++ .../tests/neon_evm/operator-keypairs/id.json | 1 + .../neon_evm/operator-keypairs/id10.json | 1 + .../neon_evm/operator-keypairs/id11.json | 1 + .../neon_evm/operator-keypairs/id12.json | 1 + .../neon_evm/operator-keypairs/id13.json | 1 + .../neon_evm/operator-keypairs/id14.json | 1 + .../neon_evm/operator-keypairs/id15.json | 1 + .../neon_evm/operator-keypairs/id16.json | 1 + .../neon_evm/operator-keypairs/id17.json | 1 + .../neon_evm/operator-keypairs/id18.json | 1 + .../neon_evm/operator-keypairs/id19.json | 1 + .../tests/neon_evm/operator-keypairs/id2.json | 1 + .../neon_evm/operator-keypairs/id20.json | 1 + .../neon_evm/operator-keypairs/id21.json | 1 + .../neon_evm/operator-keypairs/id22.json | 1 + .../neon_evm/operator-keypairs/id23.json | 1 + .../neon_evm/operator-keypairs/id24.json | 1 + .../neon_evm/operator-keypairs/id25.json | 1 + .../neon_evm/operator-keypairs/id26.json | 1 + .../neon_evm/operator-keypairs/id27.json | 1 + .../neon_evm/operator-keypairs/id28.json | 1 + .../neon_evm/operator-keypairs/id29.json | 1 + .../tests/neon_evm/operator-keypairs/id3.json | 1 + .../neon_evm/operator-keypairs/id30.json | 1 + .../tests/neon_evm/operator-keypairs/id4.json | 1 + .../tests/neon_evm/operator-keypairs/id5.json | 1 + .../tests/neon_evm/operator-keypairs/id6.json | 1 + .../tests/neon_evm/operator-keypairs/id7.json | 1 + .../tests/neon_evm/operator-keypairs/id8.json | 1 + .../tests/neon_evm/operator-keypairs/id9.json | 1 + integration/tests/neon_evm/solana_utils.py | 600 +++++++++++++++++ integration/tests/neon_evm/test_cancel_trx.py | 42 ++ .../test_execute_trx_from_instruction.py | 328 ++++++++++ .../tests/neon_evm/test_holder_account.py | 163 +++++ .../tests/neon_evm/test_neon_core_api.py | 58 ++ integration/tests/neon_evm/test_parallel.py | 165 +++++ .../test_step_instructions_work_the_same.py | 60 ++ .../test_transaction_step_from_account.py | 556 ++++++++++++++++ ...ransaction_step_from_account_no_chainid.py | 120 ++++ .../test_transaction_step_from_instruction.py | 609 ++++++++++++++++++ integration/tests/neon_evm/types/__init__.py | 0 integration/tests/neon_evm/types/types.py | 26 + integration/tests/neon_evm/utils/__init__.py | 0 .../tests/neon_evm/utils/assert_messages.py | 18 + integration/tests/neon_evm/utils/constants.py | 26 + integration/tests/neon_evm/utils/contract.py | 137 ++++ .../tests/neon_evm/utils/eth_tx_utils.py | 225 +++++++ integration/tests/neon_evm/utils/ethereum.py | 45 ++ .../tests/neon_evm/utils/instructions.py | 244 +++++++ integration/tests/neon_evm/utils/layouts.py | 50 ++ .../tests/neon_evm/utils/neon_api_client.py | 68 ++ integration/tests/neon_evm/utils/storage.py | 47 ++ .../neon_evm/utils/transaction_checks.py | 28 + 64 files changed, 4050 insertions(+), 3 deletions(-) create mode 100644 contracts/neon_evm/calculator.sol create mode 100644 contracts/neon_evm/hello_world.sol create mode 100644 contracts/neon_evm/rw_lock.sol create mode 100644 contracts/neon_evm/small.sol create mode 100644 contracts/neon_evm/string_setter.sol create mode 100644 integration/tests/neon_evm/README.md create mode 100644 integration/tests/neon_evm/__init__.py create mode 100644 integration/tests/neon_evm/conftest.py create mode 100644 integration/tests/neon_evm/operator-keypairs/id.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id10.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id11.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id12.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id13.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id14.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id15.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id16.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id17.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id18.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id19.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id2.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id20.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id21.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id22.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id23.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id24.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id25.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id26.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id27.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id28.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id29.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id3.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id30.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id4.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id5.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id6.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id7.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id8.json create mode 100644 integration/tests/neon_evm/operator-keypairs/id9.json create mode 100644 integration/tests/neon_evm/solana_utils.py create mode 100644 integration/tests/neon_evm/test_cancel_trx.py create mode 100644 integration/tests/neon_evm/test_execute_trx_from_instruction.py create mode 100644 integration/tests/neon_evm/test_holder_account.py create mode 100644 integration/tests/neon_evm/test_neon_core_api.py create mode 100644 integration/tests/neon_evm/test_parallel.py create mode 100644 integration/tests/neon_evm/test_step_instructions_work_the_same.py create mode 100644 integration/tests/neon_evm/test_transaction_step_from_account.py create mode 100644 integration/tests/neon_evm/test_transaction_step_from_account_no_chainid.py create mode 100644 integration/tests/neon_evm/test_transaction_step_from_instruction.py create mode 100644 integration/tests/neon_evm/types/__init__.py create mode 100644 integration/tests/neon_evm/types/types.py create mode 100644 integration/tests/neon_evm/utils/__init__.py create mode 100644 integration/tests/neon_evm/utils/assert_messages.py create mode 100644 integration/tests/neon_evm/utils/constants.py create mode 100644 integration/tests/neon_evm/utils/contract.py create mode 100644 integration/tests/neon_evm/utils/eth_tx_utils.py create mode 100644 integration/tests/neon_evm/utils/ethereum.py create mode 100644 integration/tests/neon_evm/utils/instructions.py create mode 100644 integration/tests/neon_evm/utils/layouts.py create mode 100644 integration/tests/neon_evm/utils/neon_api_client.py create mode 100644 integration/tests/neon_evm/utils/storage.py create mode 100644 integration/tests/neon_evm/utils/transaction_checks.py diff --git a/clickfile.py b/clickfile.py index 2aa58a8704..105f6ce071 100755 --- a/clickfile.py +++ b/clickfile.py @@ -486,7 +486,7 @@ def update_contracts(branch): "name", required=True, type=click.Choice( - ["economy", "basic", "tracer", "services", "oz", "ui", "compiler_compatibility"] + ["economy", "basic", "tracer", "services", "oz", "ui", "evm", "compiler_compatibility"] ), ) @catch_traceback @@ -512,6 +512,10 @@ def run(name, jobs, numprocesses, ui_item, amount, users, network): command = "py.test integration/tests/compiler_compatibility" if numprocesses: command = f"{command} --numprocesses {numprocesses} --dist loadscope" + elif name == "evm": + command = "py.test integration/tests/neon_evm" + if numprocesses: + command = f"{command} --numprocesses {numprocesses}" elif name == "oz": run_openzeppelin_tests( network, jobs=int(jobs), amount=int(amount), users=int(users) diff --git a/conftest.py b/conftest.py index fdd42cfbb0..8609043f9e 100644 --- a/conftest.py +++ b/conftest.py @@ -108,9 +108,10 @@ def evm_loader_keypair(): @pytest.fixture(scope="session", autouse=True) -def allure_environment(pytestconfig: Config, web3_client: NeonChainWeb3Client): +def allure_environment(pytestconfig: Config, web3_client: NeonChainWeb3Client, request): opts = {} - if pytestconfig.getoption("--network") != "geth": + + if pytestconfig.getoption("--network") != "geth" and "neon_evm" not in os.getenv('PYTEST_CURRENT_TEST'): opts = { "Network": pytestconfig.environment.proxy_url, "Proxy.Version": web3_client.get_proxy_version()["result"], diff --git a/contracts/neon_evm/calculator.sol b/contracts/neon_evm/calculator.sol new file mode 100644 index 0000000000..02185b35cc --- /dev/null +++ b/contracts/neon_evm/calculator.sol @@ -0,0 +1,22 @@ +pragma solidity >=0.5.12; + +contract calculator { + uint public x = 20; + uint public y = 20; + + function getSum() public view returns (uint256) { + return x + y; + } +} + +contract calculatorCaller { + calculator calc; + + constructor(address _calc) { + calc = calculator(_calc); + } + + function callCalculator() public view returns (uint sum) { + sum = calc.getSum(); + } +} diff --git a/contracts/neon_evm/hello_world.sol b/contracts/neon_evm/hello_world.sol new file mode 100644 index 0000000000..8248f63a50 --- /dev/null +++ b/contracts/neon_evm/hello_world.sol @@ -0,0 +1,10 @@ +pragma solidity >=0.5.12; + +contract hello_world { + uint public num = 5; + string public text = "Hello World!"; + + function call_hello_world() public view returns (string memory) { + return text; + } +} \ No newline at end of file diff --git a/contracts/neon_evm/rw_lock.sol b/contracts/neon_evm/rw_lock.sol new file mode 100644 index 0000000000..f9cf104c9e --- /dev/null +++ b/contracts/neon_evm/rw_lock.sol @@ -0,0 +1,69 @@ +pragma solidity >=0.5.12; + +import "./hello_world.sol"; +contract rw_lock { + mapping(address => mapping(uint256 => uint256)) public data; + uint len = 0; + string public text; + + function unchange_storage(uint8 x, uint8 y) public pure returns(uint8) { + return x + y; + } + + function update_storage(uint resize) public { + uint n = 0; + + while (n < resize){ + data[msg.sender][len+n] = uint256(len+n); + n = n + 1; + } + len = len + resize; + } + + function update_storage_str(string memory new_text) public { + text = new_text; + } + + function update_storage_map(uint resize) public { + uint n = 0; + while (n < resize){ + data[msg.sender][n] = uint256(n); + n = n + 1; + } + } + + function get_text() public view returns (string memory) { + return text; + } + + function deploy_contract() public returns(address){ + hello_world hello = new hello_world(); + hello.call_hello_world(); + return address(hello); + } +} + + +contract rw_lock_caller { + rw_lock rw; + + constructor(address rw_lock_address) { + rw = rw_lock(rw_lock_address); + } + + function unchange_storage(uint8 x, uint8 y) public view returns(uint8) { + return rw.unchange_storage(x, y); + } + + function update_storage_str(string memory new_text) public { + rw.update_storage_str(new_text); + } + + function update_storage_map(uint resize) public { + rw.update_storage_map(resize); + } + + function get_text() public view returns (string memory) { + return rw.get_text(); + } +} \ No newline at end of file diff --git a/contracts/neon_evm/small.sol b/contracts/neon_evm/small.sol new file mode 100644 index 0000000000..1e39854656 --- /dev/null +++ b/contracts/neon_evm/small.sol @@ -0,0 +1,6 @@ +pragma solidity >=0.5.12; +contract small { + function call_hello() public view returns (string memory) { + return "Hi"; + } +} diff --git a/contracts/neon_evm/string_setter.sol b/contracts/neon_evm/string_setter.sol new file mode 100644 index 0000000000..6c3d03566c --- /dev/null +++ b/contracts/neon_evm/string_setter.sol @@ -0,0 +1,14 @@ +pragma solidity >=0.5.12; + +contract string_setter { + string public text; + + + function get() public view returns (string memory) { + return text; + } + + function set(string memory new_text) public payable { + text = new_text; + } +} \ No newline at end of file diff --git a/deploy/requirements/prod.txt b/deploy/requirements/prod.txt index 9ec720e429..b5b4e54462 100644 --- a/deploy/requirements/prod.txt +++ b/deploy/requirements/prod.txt @@ -6,3 +6,5 @@ pytest-xdist==3.3.1 pytest-timeout pytest-asyncio==0.17 vyper==0.3.7 +typing-extensions==4.2.0 +pysha3==1.0.2 \ No newline at end of file diff --git a/integration/tests/neon_evm/README.md b/integration/tests/neon_evm/README.md new file mode 100644 index 0000000000..bc894e7425 --- /dev/null +++ b/integration/tests/neon_evm/README.md @@ -0,0 +1,105 @@ +Intro +====== + +In this directory are located tests for the evm which sends transactions direct to the evm. +Tests are using python and py.test library for run tests. + + +Installation +------------ + +```bash +pip install -r requirements.txt +py.test ./ -s -v +``` + +Moreover, we can use additional command line keys: + +1. --neon-api-uri - neon_core_api url (by default 'http://neon_api:8085/api') + +Also we can configure some variables from environment variables: + +1. SOLANA_URL - by default http://solana:8899 +2. EVM_LOADER - set evm loader address +3. NEON_TOKEN_MINT - ethereum token mint address + + +How to write tests +================== + +Structure +--------- + +Test has a several helper directories and files: + +1. contracts - place for all Solidity contracts used in tests +2. utils - place for utilities which divided by logic parts +3. conftest.py - common fixtures for all tests + + +Common fixtures +--------------- + +For more information about fixtures see [pytest-fixture](https://docs.pytest.org/en/latest/fixture.html). +In several words, fixtures are functions which are called before and after each test and has a scope parameter which setup how often this function will be called. +For example: + +```python +import pytest + +@pytest.fixture(scope="function") +def fixture1(): + print("Call fixture1") + return "fixture1" + + +def test_one(fixture1): + pass + + +def test_two(fixture1): + pass +``` + +In this case "fixture1" will be called before each test. + +```python +import pytest + +@pytest.fixture(scope="session") +def fixture1(): + print("Call fixture1") + return "fixture1" + + +def test_one(fixture1): + pass + + +def test_two(fixture1): + pass +``` + +In this case "fixture1" will be called only once before all tests. + +We have a several common fixtures: + +1. evm_loader - fixture for evm loader object +2. operator_keypair - solana Keypair for operator key +3. treasury_pool - created treasury pool +4. user_account - created user account with ethereum account + + +Tips +==== + +Generate eth contract function call data +--------------------------------------- + +```python +from eth_utils import abi +func_name = abi.function_signature_to_4byte_selector('unchange_storage(uint8,uint8)') +data = (func_name + bytes.fromhex("%064x" % 0x01) + bytes.fromhex("%064x" % 0x01)) +``` + +uint8 parameters must be 64 bytes long diff --git a/integration/tests/neon_evm/__init__.py b/integration/tests/neon_evm/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/integration/tests/neon_evm/conftest.py b/integration/tests/neon_evm/conftest.py new file mode 100644 index 0000000000..6df35a37fe --- /dev/null +++ b/integration/tests/neon_evm/conftest.py @@ -0,0 +1,169 @@ +import json +import pathlib + +import eth_abi +import pytest + +from solana.keypair import Keypair +from eth_keys import keys as eth_keys +from solana.publickey import PublicKey +from solana.rpc.commitment import Confirmed + +from .solana_utils import EvmLoader, create_treasury_pool_address, make_new_user, \ + deposit_neon, solana_client, wait_for_account_to_exists +from .utils.contract import deploy_contract +from .utils.storage import create_holder +from .types.types import TreasuryPool, Caller, Contract +from .utils.neon_api_client import NeonApiClient + +KEY_PATH = pathlib.Path(__file__).parent / "operator-keypairs" + + +def pytest_addoption(parser): + parser.addoption( + "--neon-api-uri", action="store", default="http://localhost:8085/api", + help="" + ) + + +@pytest.fixture(scope="session") +def evm_loader(operator_keypair: Keypair) -> EvmLoader: + loader = EvmLoader(operator_keypair) + return loader + + +def prepare_operator(key_file): + with open(key_file, "r") as key: + secret_key = json.load(key)[:32] + account = Keypair.from_secret_key(secret_key) + + solana_client.request_airdrop(account.public_key, 1000 * 10 ** 9, commitment=Confirmed) + wait_for_account_to_exists(solana_client, account.public_key) + + a = solana_client.get_account_info(account.public_key, commitment=Confirmed) + print(f"{a}") + + operator_ether = eth_keys.PrivateKey(account.secret_key[:32]).public_key.to_canonical_address() + + evm_loader = EvmLoader(account) + ether_balance_pubkey = evm_loader.ether2balance(operator_ether) + acc_info = solana_client.get_account_info(ether_balance_pubkey, commitment=Confirmed) + if acc_info.value is None: + evm_loader.create_balance_account(operator_ether) + + return account + +@pytest.fixture(scope="session") +def default_operator_keypair() -> Keypair: + """ + Initialized solana keypair with balance. Get private keys from ci/operator-keypairs/id.json + """ + key_file = KEY_PATH / "id.json" + return prepare_operator(key_file) + +@pytest.fixture(scope="session") +def operator_keypair(worker_id) -> Keypair: + """ + Initialized solana keypair with balance. Get private keys from ci/operator-keypairs + """ + if worker_id in ("master", "gw1"): + key_file = KEY_PATH / "id.json" + else: + file_id = int(worker_id[-1]) + 2 + key_file = KEY_PATH / f"id{file_id}.json" + return prepare_operator(key_file) + + +@pytest.fixture(scope="session") +def second_operator_keypair(worker_id) -> Keypair: + """ + Initialized solana keypair with balance. Get private key from cli or ./ci/operator-keypairs + """ + if worker_id in ("master", "gw1"): + key_file = KEY_PATH / "id20.json" + else: + file_id = 20 + int(worker_id[-1]) + 2 + key_file = KEY_PATH / f"id{file_id}.json" + + return prepare_operator(key_file) + + +@pytest.fixture(scope="session") +def treasury_pool(evm_loader) -> TreasuryPool: + index = 2 + address = create_treasury_pool_address(index) + index_buf = index.to_bytes(4, 'little') + return TreasuryPool(index, address, index_buf) + + +@pytest.fixture(scope="function") +def user_account(evm_loader) -> Caller: + return make_new_user(evm_loader) + + +@pytest.fixture(scope="session") +def session_user(evm_loader) -> Caller: + return make_new_user(evm_loader) + + +@pytest.fixture(scope="session") +def second_session_user(evm_loader) -> Caller: + return make_new_user(evm_loader) + + +@pytest.fixture(scope="session") +def sender_with_tokens(evm_loader, operator_keypair) -> Caller: + user = make_new_user(evm_loader) + deposit_neon(evm_loader, operator_keypair, user.eth_address, 100000) + return user + + +@pytest.fixture(scope="session") +def holder_acc(operator_keypair) -> PublicKey: + return create_holder(operator_keypair) + + +@pytest.fixture(scope="function") +def new_holder_acc(operator_keypair) -> PublicKey: + return create_holder(operator_keypair) + + +@pytest.fixture(scope="function") +def rw_lock_contract(evm_loader: EvmLoader, operator_keypair: Keypair, session_user: Caller, + treasury_pool) -> Contract: + return deploy_contract(operator_keypair, session_user, "rw_lock", evm_loader, treasury_pool) + + +@pytest.fixture(scope="function") +def rw_lock_caller(evm_loader: EvmLoader, operator_keypair: Keypair, + session_user: Caller, treasury_pool: TreasuryPool, rw_lock_contract: Contract) -> Contract: + constructor_args = eth_abi.encode(['address'], [rw_lock_contract.eth_address.hex()]) + return deploy_contract(operator_keypair, session_user, "rw_lock", evm_loader, + treasury_pool, encoded_args=constructor_args, contract_name="rw_lock_caller") + + +@pytest.fixture(scope="function") +def string_setter_contract(evm_loader: EvmLoader, operator_keypair: Keypair, session_user: Caller, + treasury_pool) -> Contract: + return deploy_contract(operator_keypair, session_user, "string_setter", evm_loader, treasury_pool) + + +@pytest.fixture(scope="session") +def calculator_contract(evm_loader: EvmLoader, operator_keypair: Keypair, session_user: Caller, + treasury_pool) -> Contract: + return deploy_contract(operator_keypair, session_user, "calculator", evm_loader, treasury_pool) + + +@pytest.fixture(scope="session") +def calculator_caller_contract(evm_loader: EvmLoader, operator_keypair: Keypair, session_user: Caller, + treasury_pool, calculator_contract) -> Contract: + constructor_args = eth_abi.encode(['address'], [calculator_contract.eth_address.hex()]) + + return deploy_contract(operator_keypair, session_user, "calculator", evm_loader, treasury_pool, + encoded_args=constructor_args, contract_name="calculatorCaller") + + +@pytest.fixture(scope="session") +def neon_api_client(request): + client = NeonApiClient(url=request.config.getoption("--neon-api-uri")) + return client diff --git a/integration/tests/neon_evm/operator-keypairs/id.json b/integration/tests/neon_evm/operator-keypairs/id.json new file mode 100644 index 0000000000..54e7bbcc6b --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id.json @@ -0,0 +1 @@ +[161,247,66,57,203,188,141,236,124,123,200,192,255,23,161,34,116,202,70,182,176,94,195,68,185,32,61,42,203,57,245,190,153,233,189,58,187,209,15,232,83,112,145,116,89,187,201,44,36,255,81,102,84,101,62,1,180,217,194,37,147,74,7,170] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id10.json b/integration/tests/neon_evm/operator-keypairs/id10.json new file mode 100644 index 0000000000..0775610ce3 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id10.json @@ -0,0 +1 @@ +[95,1,219,168,183,165,35,48,232,59,7,206,250,251,243,138,175,212,58,154,252,147,193,16,240,56,129,24,104,220,21,244,70,237,248,21,84,35,91,157,114,197,241,253,93,33,93,230,14,255,229,131,73,153,27,16,86,172,34,220,16,128,14,26] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id11.json b/integration/tests/neon_evm/operator-keypairs/id11.json new file mode 100644 index 0000000000..db71430291 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id11.json @@ -0,0 +1 @@ +[112,233,194,170,175,217,98,186,160,118,143,249,88,40,179,77,222,62,121,149,205,155,152,1,34,243,188,44,66,231,63,202,143,44,91,212,211,86,255,54,80,56,164,192,211,49,40,167,211,153,130,207,41,220,90,64,169,45,26,169,42,202,231,252] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id12.json b/integration/tests/neon_evm/operator-keypairs/id12.json new file mode 100644 index 0000000000..3cfb114dc4 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id12.json @@ -0,0 +1 @@ +[107,242,59,236,113,145,60,13,84,146,102,174,36,95,255,207,104,129,118,70,114,139,210,54,17,195,176,206,202,153,82,111,25,231,254,200,204,102,17,163,119,42,14,188,149,225,220,221,195,14,163,251,62,228,171,180,124,174,161,152,231,162,221,54] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id13.json b/integration/tests/neon_evm/operator-keypairs/id13.json new file mode 100644 index 0000000000..e5e633ee39 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id13.json @@ -0,0 +1 @@ +[132,30,178,226,98,41,184,97,30,124,214,218,107,138,88,82,206,12,27,217,25,6,146,118,146,174,17,129,255,96,33,56,204,58,207,78,57,180,19,42,180,42,245,237,110,83,156,150,192,15,157,91,246,49,103,146,168,79,122,156,136,146,247,152] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id14.json b/integration/tests/neon_evm/operator-keypairs/id14.json new file mode 100644 index 0000000000..3258026f32 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id14.json @@ -0,0 +1 @@ +[77,155,208,78,141,49,104,72,43,252,108,43,240,125,166,156,235,146,195,221,21,235,48,39,224,133,233,174,110,10,211,154,123,240,134,8,166,118,208,116,229,35,39,53,203,206,97,95,22,107,236,62,70,200,191,63,221,34,154,91,159,153,180,171] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id15.json b/integration/tests/neon_evm/operator-keypairs/id15.json new file mode 100644 index 0000000000..317b10c65e --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id15.json @@ -0,0 +1 @@ +[176,43,81,75,52,27,223,223,223,150,148,199,16,151,195,121,143,174,242,189,90,208,164,198,92,28,77,104,72,230,11,154,212,64,129,103,42,57,174,119,63,101,167,173,184,97,146,208,207,208,81,239,29,214,66,101,101,151,247,130,197,17,172,170] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id16.json b/integration/tests/neon_evm/operator-keypairs/id16.json new file mode 100644 index 0000000000..a2ea6fa616 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id16.json @@ -0,0 +1 @@ +[56,132,45,132,252,89,229,88,99,111,37,90,53,175,128,227,57,228,18,166,56,5,168,23,33,127,37,69,154,150,67,213,227,9,242,48,173,228,249,65,55,107,84,219,87,138,241,192,218,29,149,67,43,239,9,132,93,209,218,167,27,138,112,46] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id17.json b/integration/tests/neon_evm/operator-keypairs/id17.json new file mode 100644 index 0000000000..5cbbbd54df --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id17.json @@ -0,0 +1 @@ +[169,132,11,32,251,137,85,119,180,62,4,36,213,240,247,29,61,194,252,19,134,98,188,82,22,213,206,195,39,133,36,162,202,14,212,69,151,114,40,66,173,213,100,145,235,70,34,247,101,166,227,86,18,81,56,17,58,103,152,200,117,163,88,236] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id18.json b/integration/tests/neon_evm/operator-keypairs/id18.json new file mode 100644 index 0000000000..5a2ad7abc1 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id18.json @@ -0,0 +1 @@ +[34,136,232,16,199,6,212,178,9,245,202,238,151,70,70,205,173,92,116,43,176,222,250,193,7,242,79,227,3,252,57,47,171,52,75,200,195,215,71,197,31,185,128,5,62,168,237,61,5,25,229,26,19,201,190,9,149,80,104,95,157,123,207,40] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id19.json b/integration/tests/neon_evm/operator-keypairs/id19.json new file mode 100644 index 0000000000..716b611d62 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id19.json @@ -0,0 +1 @@ +[107,48,148,246,46,55,52,252,132,177,71,135,119,118,187,16,236,80,237,198,239,54,112,107,225,26,36,200,242,63,40,71,143,117,14,174,182,27,199,155,93,167,196,216,69,150,100,195,216,48,222,143,240,185,186,37,202,137,179,232,239,26,85,242] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id2.json b/integration/tests/neon_evm/operator-keypairs/id2.json new file mode 100644 index 0000000000..9b1bd57b61 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id2.json @@ -0,0 +1 @@ +[27,180,96,29,186,81,246,40,92,95,84,188,13,66,153,230,110,37,231,213,185,160,128,146,175,193,199,187,131,108,146,211,129,250,211,63,136,192,175,63,138,71,161,34,135,75,55,3,149,61,84,23,252,239,203,80,170,175,222,166,136,217,173,126] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id20.json b/integration/tests/neon_evm/operator-keypairs/id20.json new file mode 100644 index 0000000000..03a27c45eb --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id20.json @@ -0,0 +1 @@ +[203,226,8,46,184,170,110,207,167,123,46,136,82,48,44,123,212,127,198,109,113,6,103,190,9,144,89,120,215,45,75,17,134,73,9,238,232,115,234,164,179,96,197,67,31,175,86,222,149,202,209,164,83,170,133,179,189,178,99,158,15,3,152,135] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id21.json b/integration/tests/neon_evm/operator-keypairs/id21.json new file mode 100644 index 0000000000..caa4fb7332 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id21.json @@ -0,0 +1 @@ +[218,78,39,37,233,34,133,207,6,61,140,213,98,27,8,7,255,50,100,179,83,154,114,120,52,199,178,29,32,131,193,9,46,69,220,188,192,48,86,70,174,193,58,32,171,172,54,169,134,56,245,54,239,253,235,32,155,211,137,90,163,61,54,57] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id22.json b/integration/tests/neon_evm/operator-keypairs/id22.json new file mode 100644 index 0000000000..7966c4ba46 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id22.json @@ -0,0 +1 @@ +[213,38,17,156,63,219,208,33,176,13,185,208,204,212,179,20,63,74,116,6,104,176,68,242,100,10,76,138,124,95,99,168,91,253,251,27,103,97,112,214,38,187,136,19,100,149,58,106,184,17,85,214,7,150,172,139,155,133,93,93,168,107,139,174] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id23.json b/integration/tests/neon_evm/operator-keypairs/id23.json new file mode 100644 index 0000000000..e2c659a57c --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id23.json @@ -0,0 +1 @@ +[161,138,142,31,77,141,153,110,193,211,60,52,240,89,17,162,42,125,158,78,243,214,203,144,85,66,34,231,222,87,106,54,184,10,234,92,109,70,63,141,23,221,150,4,181,90,234,216,132,7,213,115,220,202,207,31,31,191,66,27,76,106,139,28] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id24.json b/integration/tests/neon_evm/operator-keypairs/id24.json new file mode 100644 index 0000000000..ab53a2b489 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id24.json @@ -0,0 +1 @@ +[111,225,67,67,201,129,87,214,46,150,197,209,142,102,243,6,197,103,130,19,12,11,205,90,228,216,232,212,42,87,217,246,20,32,169,144,96,175,129,11,93,237,7,215,62,60,254,34,29,15,133,117,199,162,8,140,144,35,233,179,139,198,54,30] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id25.json b/integration/tests/neon_evm/operator-keypairs/id25.json new file mode 100644 index 0000000000..c3cd09b2a8 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id25.json @@ -0,0 +1 @@ +[124,147,121,70,22,245,195,2,113,226,67,159,168,33,247,202,149,42,5,18,192,26,192,41,21,255,15,1,115,38,184,182,28,114,114,195,60,79,241,252,192,204,205,176,152,170,117,102,255,74,145,69,85,136,64,214,177,134,255,221,184,119,141,125] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id26.json b/integration/tests/neon_evm/operator-keypairs/id26.json new file mode 100644 index 0000000000..11980bcaad --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id26.json @@ -0,0 +1 @@ +[194,43,141,149,241,214,135,212,243,123,229,238,68,168,5,2,148,253,171,107,40,251,133,240,237,248,82,44,45,252,177,157,9,157,56,52,149,119,152,127,172,5,132,184,12,65,237,196,211,249,93,75,246,23,49,187,1,196,138,243,254,148,127,200] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id27.json b/integration/tests/neon_evm/operator-keypairs/id27.json new file mode 100644 index 0000000000..00c30dea20 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id27.json @@ -0,0 +1 @@ +[208,83,61,173,206,206,193,114,39,85,114,190,81,55,97,76,94,156,47,248,193,163,230,202,176,121,89,43,16,210,146,36,208,251,160,86,224,180,79,140,119,14,118,76,60,79,81,96,160,123,179,88,201,27,173,141,126,239,109,213,242,106,36,246] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id28.json b/integration/tests/neon_evm/operator-keypairs/id28.json new file mode 100644 index 0000000000..4b858ba337 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id28.json @@ -0,0 +1 @@ +[209,78,176,59,205,21,225,147,100,16,206,202,223,36,96,43,85,159,238,98,244,52,0,49,98,111,122,225,225,165,9,112,104,103,35,237,229,14,253,80,210,177,222,83,166,131,106,170,212,72,208,227,125,135,92,255,38,250,183,162,139,32,85,50] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id29.json b/integration/tests/neon_evm/operator-keypairs/id29.json new file mode 100644 index 0000000000..454b40b636 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id29.json @@ -0,0 +1 @@ +[139,25,69,250,8,210,132,135,49,210,33,168,172,48,73,239,226,151,2,49,128,127,136,222,160,209,112,172,83,153,57,59,231,21,48,207,152,236,233,187,223,100,82,93,7,113,26,194,124,70,245,140,6,215,63,170,178,46,130,201,93,40,215,178] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id3.json b/integration/tests/neon_evm/operator-keypairs/id3.json new file mode 100644 index 0000000000..7bc96cdf71 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id3.json @@ -0,0 +1 @@ +[109,212,150,39,229,176,85,103,100,217,115,40,197,125,185,154,137,194,7,69,141,239,74,113,237,187,26,120,169,5,255,32,35,79,159,50,228,49,216,5,51,156,105,212,248,146,245,190,201,70,233,200,237,253,215,78,68,113,248,0,238,15,136,200] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id30.json b/integration/tests/neon_evm/operator-keypairs/id30.json new file mode 100644 index 0000000000..b6b4a9b641 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id30.json @@ -0,0 +1 @@ +[164,184,180,94,178,30,255,212,198,57,93,119,61,202,55,97,47,134,31,115,120,201,199,32,163,108,202,131,121,135,128,6,68,231,110,177,230,42,56,26,247,172,232,205,110,76,106,16,153,106,234,56,11,247,17,237,241,11,56,21,145,232,254,153] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id4.json b/integration/tests/neon_evm/operator-keypairs/id4.json new file mode 100644 index 0000000000..f0ca8465aa --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id4.json @@ -0,0 +1 @@ +[214,210,14,130,12,222,73,66,181,175,176,156,48,43,71,4,216,168,159,87,124,129,219,255,7,252,25,161,46,177,45,113,108,56,97,99,254,82,6,60,29,65,233,96,140,158,29,212,193,138,51,31,126,138,226,116,105,127,95,121,156,23,55,54] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id5.json b/integration/tests/neon_evm/operator-keypairs/id5.json new file mode 100644 index 0000000000..1a5e67907c --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id5.json @@ -0,0 +1 @@ +[135,36,157,212,37,148,54,141,6,153,179,125,174,108,39,176,152,84,179,2,79,124,113,176,10,83,101,69,165,219,43,89,7,50,172,103,235,215,62,192,98,108,223,67,30,114,241,40,45,61,189,107,162,9,86,31,11,124,184,245,39,12,101,13] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id6.json b/integration/tests/neon_evm/operator-keypairs/id6.json new file mode 100644 index 0000000000..e367a53739 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id6.json @@ -0,0 +1 @@ +[245,253,29,158,248,242,248,20,31,201,243,72,241,50,136,180,229,63,143,99,92,29,230,5,181,107,230,147,253,103,22,247,1,192,248,239,196,211,107,121,216,2,30,108,163,95,239,105,245,93,161,171,252,153,27,40,132,14,136,73,52,195,251,158] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id7.json b/integration/tests/neon_evm/operator-keypairs/id7.json new file mode 100644 index 0000000000..088191ba97 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id7.json @@ -0,0 +1 @@ +[103,137,235,222,211,158,252,227,63,199,231,174,155,23,246,93,31,194,94,222,156,0,159,205,93,110,243,4,179,21,145,13,236,212,22,139,247,96,23,122,167,37,106,151,101,126,240,202,104,15,176,243,57,143,103,132,64,211,83,237,3,68,189,154] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id8.json b/integration/tests/neon_evm/operator-keypairs/id8.json new file mode 100644 index 0000000000..515cd77c55 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id8.json @@ -0,0 +1 @@ +[43,222,228,254,6,34,211,192,249,60,124,254,19,117,183,192,163,79,160,118,73,194,91,40,166,32,139,101,161,204,226,199,202,125,95,169,241,101,72,248,194,7,102,135,184,165,73,207,166,48,108,180,62,205,193,52,91,86,120,152,102,112,62,105] \ No newline at end of file diff --git a/integration/tests/neon_evm/operator-keypairs/id9.json b/integration/tests/neon_evm/operator-keypairs/id9.json new file mode 100644 index 0000000000..02f1125299 --- /dev/null +++ b/integration/tests/neon_evm/operator-keypairs/id9.json @@ -0,0 +1 @@ +[76,252,69,52,51,62,9,220,165,183,52,47,149,238,49,198,68,226,22,251,245,70,58,59,205,49,212,77,31,191,81,94,131,180,89,79,237,117,30,106,190,97,233,53,235,163,61,130,254,35,88,148,254,230,240,235,118,222,84,106,92,2,89,155] \ No newline at end of file diff --git a/integration/tests/neon_evm/solana_utils.py b/integration/tests/neon_evm/solana_utils.py new file mode 100644 index 0000000000..0530f7a454 --- /dev/null +++ b/integration/tests/neon_evm/solana_utils.py @@ -0,0 +1,600 @@ +import json +import math +import os +import subprocess +import time +import typing +from hashlib import sha256 +from typing import Tuple, Union + +import spl.token.instructions +from base58 import b58encode +from eth_account.datastructures import SignedTransaction +from eth_keys import keys as eth_keys +from hexbytes import HexBytes +import solana.system_program as sp + +from solana.keypair import Keypair +from solana.publickey import PublicKey +from solana.rpc.api import Client +from solana.rpc.commitment import Confirmed +from solana.rpc.types import TxOpts +from solana.transaction import AccountMeta, TransactionInstruction, Transaction +from solders.rpc.responses import SendTransactionResp, GetTransactionResp +from spl.token.constants import TOKEN_PROGRAM_ID +from spl.token.instructions import get_associated_token_address, ApproveParams, MintToParams + +from .utils.constants import CHAIN_ID + +from .utils.constants import EVM_LOADER, SOLANA_URL, SYSTEM_ADDRESS, NEON_TOKEN_MINT_ID, \ + ACCOUNT_SEED_VERSION, TREASURY_POOL_SEED +from .utils.instructions import make_DepositV03, make_Cancel, make_WriteHolder, make_ExecuteTrxFromInstruction, \ + TransactionWithComputeBudget, make_PartialCallOrContinueFromRawEthereumTX, \ + make_ExecuteTrxFromAccountDataIterativeOrContinue, make_CreateAssociatedTokenIdempotent +from .utils.layouts import BALANCE_ACCOUNT_LAYOUT +from .types.types import Caller, Contract + +EVM_LOADER_SO = os.environ.get("EVM_LOADER_SO", 'target/bpfel-unknown-unknown/release/evm_loader.so') +solana_client = Client(SOLANA_URL, commitment=Confirmed) +path_to_solana = 'solana' + +# amount of gas per 1 byte evm_storage +EVM_BYTE_COST = 6960 # 1_000_000_000/ 100 * 365 / (1024*1024) * 2 +# number of evm steps per transaction +EVM_STEPS = 500 +# the message size that is used to holder-account filling +HOLDER_MSG_SIZE = 950 +# Ethereum account allocated data size +ACCOUNT_MAX_SIZE = 256 +# spl-token account allocated data size +SPL_TOKEN_ACCOUNT_SIZE = 165 +# payment to treasure +PAYMENT_TO_TREASURE = 5000 +# payment for solana signature verification +LAMPORTS_PER_SIGNATURE = 5000 +# account storage overhead for calculation of base rent +ACCOUNT_STORAGE_OVERHEAD = 128 + + + +def create_treasury_pool_address(pool_index, evm_loader=EVM_LOADER): + return PublicKey.find_program_address( + [bytes(TREASURY_POOL_SEED, 'utf8'), pool_index.to_bytes(4, 'little')], + PublicKey(evm_loader) + )[0] + + +def wait_for_account_to_exists(http_client: Client, account: PublicKey, timeout = 30, sleep_time = 0.4): + elapsed_time = 0 + while elapsed_time < timeout: + resp = http_client.get_account_info(account, commitment=Confirmed) + if resp.value is not None: + return + + time.sleep(sleep_time) + elapsed_time += sleep_time + + raise RuntimeError(f"Account {account} not exists after {timeout} seconds") + + +def account_with_seed(base, seed, program) -> PublicKey: + return PublicKey(sha256(bytes(base) + bytes(seed, 'utf8') + bytes(program)).digest()) + + +def create_account_with_seed(funding, base, seed, lamports, space, program=PublicKey(EVM_LOADER)): + created = account_with_seed(base, seed, program) + print(f"Created: {created}") + return sp.create_account_with_seed(sp.CreateAccountWithSeedParams( + from_pubkey=funding, + new_account_pubkey=created, + base_pubkey=base, + seed=seed, + lamports=lamports, + space=space, + program_id=program + )) + + +def create_holder_account(account, operator, seed): + return TransactionInstruction( + keys=[ + AccountMeta(pubkey=account, is_signer=False, is_writable=True), + AccountMeta(pubkey=operator, is_signer=True, is_writable=False), + ], + program_id=PublicKey(EVM_LOADER), + data=bytes.fromhex("24") + + len(seed).to_bytes(8, 'little') + seed + ) + + +class solana_cli: + def __init__(self, acc=None): + self.acc = acc + + def call(self, arguments): + if self.acc is None: + cmd = '{} --url {} {}'.format(path_to_solana, SOLANA_URL, arguments) + else: + cmd = '{} --keypair {} --url {} {}'.format(path_to_solana, self.acc.get_path(), SOLANA_URL, arguments) + try: + return subprocess.check_output(cmd, shell=True, universal_newlines=True) + except subprocess.CalledProcessError as err: + print(f"ERR: solana error {err}") + raise + + +class neon_cli: + def __init__(self, verbose_flags=''): + self.verbose_flags = verbose_flags + + def call(self, arguments): + cmd = 'neon-cli {} --loglevel debug --commitment=processed --url {} {}'.format(self.verbose_flags, SOLANA_URL, arguments) + proc_result = subprocess.run(cmd, shell=True, text=True, stdout=subprocess.PIPE, universal_newlines=True) + result = json.loads(proc_result.stdout) + if result["result"] == "error": + error = result["error"] + raise Exception(f"ERR: neon-cli error {error}") + + proc_result.check_returncode() + return result["value"] + + # def emulate(self, loader_id, sender, contract, data): + # cmd = ["neon-cli", + # "--commitment=confirmed", + # "--url", SOLANA_URL, + # f"--evm_loader={loader_id}", + # "emulate" + # ] + # print('cmd:', cmd) + # print("data:", data) + # + # body = json.dumps({ + # "tx": { + # "from": sender, + # "to": contract, + # "data": data + # }, + # "accounts": [] + # }) + # + # proc_result = subprocess.run(cmd, input=body, text=True, stdout=subprocess.PIPE, universal_newlines=True) + # + # result = json.loads(proc_result.stdout) + # print("EMULATOR RESULT: ") + # print(json.dumps(result)) + # + # if result["result"] == "error": + # error = result["error"] + # raise Exception(f"ERR: neon-cli error {error}") + # + # proc_result.check_returncode() + # return result["value"] + + # def call_contract_get_function(self, evm_loader, sender, contract, function_signature: str, constructor_args=None): + # data = abi.function_signature_to_4byte_selector(function_signature) + # if constructor_args is not None: + # data += constructor_args + # result = self.emulate(evm_loader.loader_id, sender.eth_address.hex(), contract.eth_address.hex(), data.hex()) + # return result["result"] + + def get_steps_count(self, evm_loader, from_acc, to, data): + if isinstance(to, (Caller, Contract)): + to = to.eth_address.hex() + + result = neon_cli().emulate( + evm_loader.loader_id, + from_acc.eth_address.hex(), + to, + data + ) + + return result["steps_executed"] + + +class RandomAccount: + def __init__(self, path=None): + if path is None: + self.make_random_path() + print(f"New keypair file: {self.path}") + self.generate_key() + else: + self.path = path + self.retrieve_keys() + print('New Public key:', self.acc.public_key()) + print('Private:', self.acc.secret_key()) + + def make_random_path(self): + self.path = os.urandom(5).hex() + ".json" + + def generate_key(self): + cmd_generate = 'solana-keygen new --no-passphrase --outfile {}'.format(self.path) + try: + return subprocess.check_output(cmd_generate, shell=True, universal_newlines=True) + except subprocess.CalledProcessError as err: + print(f"ERR: solana error {err}") + raise + + def retrieve_keys(self): + with open(self.path) as f: + d = json.load(f) + self.acc = Keypair(d[0:32]) + + def get_path(self): + return self.path + + def get_acc(self): + return self.acc + + +class WalletAccount(RandomAccount): + def __init__(self, path): + self.path = path + self.retrieve_keys() + print('Wallet public key:', self.acc.public_key()) + + +class EvmLoader: + def __init__(self, acc: Keypair, program_id=EVM_LOADER): + if program_id is None: + print(f"EVM Loader program address is empty, deploy it") + result = json.loads(solana_cli(acc).call('deploy {}'.format(EVM_LOADER_SO))) + program_id = result['programId'] + EvmLoader.loader_id = PublicKey(program_id) + print("Done\n") + + self.loader_id = EvmLoader.loader_id + self.acc = acc + print("Evm loader program: {}".format(self.loader_id)) + + def create_balance_account(self, ether: Union[str, bytes]) -> PublicKey: + account_pubkey = self.ether2balance(ether) + contract_pubkey = PublicKey(self.ether2program(ether)[0]) + print('createBalanceAccount: {} => {}'.format(ether, account_pubkey)) + + data = bytes([0x30]) + self.ether2bytes(ether) + CHAIN_ID.to_bytes(8, 'little') + trx = Transaction() + trx.add(TransactionInstruction( + program_id=self.loader_id, + data=data, + keys=[ + AccountMeta(pubkey=self.acc.public_key, is_signer=True, is_writable=True), + AccountMeta(pubkey=PublicKey(SYSTEM_ADDRESS), is_signer=False, is_writable=False), + AccountMeta(pubkey=account_pubkey, is_signer=False, is_writable=True), + AccountMeta(pubkey=contract_pubkey, is_signer=False, is_writable=True), + ])) + + send_transaction(solana_client, trx, self.acc) + return account_pubkey + + @staticmethod + def ether2hex(ether: Union[str, bytes]): + if isinstance(ether, str): + if ether.startswith('0x'): + return ether[2:] + return ether + return ether.hex() + + @staticmethod + def ether2bytes(ether: Union[str, bytes]): + if isinstance(ether, str): + if ether.startswith('0x'): + return bytes.fromhex(ether[2:]) + return bytes.fromhex(ether) + return ether + + def ether2seed(self, ether: Union[str, bytes]): + seed = b58encode(ACCOUNT_SEED_VERSION + self.ether2bytes(ether)).decode('utf8') + acc = account_with_seed(self.acc.public_key, seed, self.loader_id) + print('ether2program: {} {} => {}'.format(self.ether2hex(ether), 255, acc)) + return acc, 255 + + def ether2program(self, ether: Union[str, bytes]) -> Tuple[str, int]: + items = PublicKey.find_program_address([ACCOUNT_SEED_VERSION, self.ether2bytes(ether)], PublicKey(EVM_LOADER)) + return str(items[0]), items[1] + + def ether2balance(self, address: Union[str, bytes]) -> PublicKey: + address_bytes = self.ether2bytes(address) + chain_id_bytes = CHAIN_ID.to_bytes(32, 'big') + return PublicKey.find_program_address( + [ACCOUNT_SEED_VERSION, address_bytes, chain_id_bytes], + PublicKey(EVM_LOADER) + )[0] + + def check_account(self, solana): + info = solana_client.get_account_info(solana) + print("checkAccount({}): {}".format(solana, info)) + + def get_neon_nonce(self, account: Union[str, bytes, Caller]) -> int: + if isinstance(account, Caller): + return self.get_neon_nonce(account.eth_address) + + solana_address = self.ether2balance(account) + + info: bytes = get_solana_account_data(solana_client, solana_address, BALANCE_ACCOUNT_LAYOUT.sizeof()) + layout = BALANCE_ACCOUNT_LAYOUT.parse(info) + + return layout.trx_count + + def get_neon_balance(self, account: Union[str, bytes, Caller]) -> int: + if isinstance(account, Caller): + return self.get_neon_balance(account.eth_address) + + solana_address = self.ether2balance(account) + + info: bytes = get_solana_account_data(solana_client, solana_address, BALANCE_ACCOUNT_LAYOUT.sizeof()) + layout = BALANCE_ACCOUNT_LAYOUT.parse(info) + + return int.from_bytes(layout.balance, byteorder="little") + + +def get_solana_balance(account): + return solana_client.get_balance(account, commitment=Confirmed).value + + +def get_solana_account_data(solana_client: Client, account: Union[str, PublicKey, Keypair], expected_length: int) -> bytes: + if isinstance(account, Keypair): + account = account.public_key + print(f"Get account data for {account}") + info = solana_client.get_account_info(account, commitment=Confirmed) + print(f"Result: {info}") + info = info.value + if info is None: + raise Exception("Can't get information about {}".format(account)) + if len(info.data) < expected_length: + print("len(data)({}) < expected_length({})".format(len(info.data), expected_length)) + raise Exception("Wrong data length for account data {}".format(account)) + return info.data + +def send_transaction(client: Client, trx: Transaction, *signers: Keypair, wait_status=Confirmed): + print("Send trx") + result = client.send_transaction(trx, *signers, opts=TxOpts(skip_confirmation=True, preflight_commitment=wait_status)) + print("Result: {}".format(result)) + client.confirm_transaction(result.value, commitment=Confirmed) + return client.get_transaction(result.value, commitment=Confirmed) + + +def evm_step_cost(): + operator_expences = PAYMENT_TO_TREASURE + LAMPORTS_PER_SIGNATURE + return math.floor(operator_expences / EVM_STEPS) + + +def make_new_user(evm_loader: EvmLoader) -> Caller: + key = Keypair.generate() + if get_solana_balance(key.public_key) == 0: + solana_client.request_airdrop(key.public_key, 1000 * 10 ** 9, commitment=Confirmed) + wait_for_account_to_exists(solana_client, key.public_key) + + caller_ether = eth_keys.PrivateKey(key.secret_key[:32]).public_key.to_canonical_address() + caller_solana = evm_loader.ether2program(caller_ether)[0] + caller_balance = evm_loader.ether2balance(caller_ether) + caller_token = get_associated_token_address(caller_balance, NEON_TOKEN_MINT_ID) + + if get_solana_balance(caller_balance) == 0: + print(f"Create Neon account {caller_ether} for user {caller_balance}") + evm_loader.create_balance_account(caller_ether) + + print('Account solana address:', key.public_key) + print(f'Account ether address: {caller_ether.hex()}', ) + print(f'Account solana address: {caller_balance}') + return Caller(key, PublicKey(caller_solana), caller_balance, caller_ether, caller_token) + + +def deposit_neon(evm_loader: EvmLoader, operator_keypair: Keypair, ether_address: Union[str, bytes], amount: int): + balance_pubkey = evm_loader.ether2balance(ether_address) + contract_pubkey = PublicKey(evm_loader.ether2program(ether_address)[0]) + + evm_token_authority = PublicKey.find_program_address([b"Deposit"], evm_loader.loader_id)[0] + evm_pool_key = get_associated_token_address(evm_token_authority, NEON_TOKEN_MINT_ID) + + token_pubkey = get_associated_token_address(operator_keypair.public_key, NEON_TOKEN_MINT_ID) + + with open("evm_loader-keypair.json", "r") as key: + secret_key = json.load(key)[:32] + mint_authority = Keypair.from_secret_key(secret_key) + + trx = Transaction() + trx.add( + make_CreateAssociatedTokenIdempotent( + operator_keypair.public_key, + operator_keypair.public_key, + NEON_TOKEN_MINT_ID + ), + spl.token.instructions.mint_to( + MintToParams( + spl.token.constants.TOKEN_PROGRAM_ID, + NEON_TOKEN_MINT_ID, + token_pubkey, + mint_authority.public_key, + amount + ) + ), + spl.token.instructions.approve( + ApproveParams( + spl.token.constants.TOKEN_PROGRAM_ID, + token_pubkey, + balance_pubkey, + operator_keypair.public_key, + amount, + ) + ), + make_DepositV03( + evm_loader.ether2bytes(ether_address), + CHAIN_ID, + balance_pubkey, + contract_pubkey, + NEON_TOKEN_MINT_ID, + token_pubkey, + evm_pool_key, + spl.token.constants.TOKEN_PROGRAM_ID, + operator_keypair.public_key, + ) + ) + + receipt = send_transaction(solana_client, trx, operator_keypair, mint_authority) + + return receipt + + +def cancel_transaction( + evm_loader: EvmLoader, + tx_hash: HexBytes, + holder_acc: PublicKey, + operator_keypair: Keypair, + additional_accounts: typing.List[PublicKey], +): + # Cancel deployment transaction: + trx = Transaction() + trx.add( + make_Cancel( + evm_loader, + holder_acc, + operator_keypair, + tx_hash, + additional_accounts, + ) + ) + + cancel_receipt = send_transaction(solana_client, trx, operator_keypair) + + print("Cancel receipt:", cancel_receipt) + assert cancel_receipt.value.transaction.meta.err is None + return cancel_receipt + + +def write_transaction_to_holder_account( + signed_tx: SignedTransaction, + holder_account: PublicKey, + operator: Keypair, +): + # Write transaction to transaction holder account + offset = 0 + receipts = [] + rest = signed_tx.rawTransaction + while len(rest): + (part, rest) = (rest[:920], rest[920:]) + trx = Transaction() + trx.add(make_WriteHolder(operator.public_key, holder_account, signed_tx.hash, offset, part)) + receipts.append( + solana_client.send_transaction( + trx, + operator, + opts=TxOpts(skip_confirmation=True, preflight_commitment=Confirmed), + ) + ) + offset += len(part) + + for rcpt in receipts: + solana_client.confirm_transaction(rcpt.value, commitment=Confirmed) + + +def execute_trx_from_instruction(operator: Keypair, evm_loader: EvmLoader, treasury_address: PublicKey, treasury_buffer: bytes, + instruction: SignedTransaction, + additional_accounts, signer: Keypair, + system_program=sp.SYS_PROGRAM_ID) -> SendTransactionResp: + trx = TransactionWithComputeBudget(operator) + trx.add(make_ExecuteTrxFromInstruction(operator, evm_loader, treasury_address, + treasury_buffer, instruction.rawTransaction, additional_accounts, + system_program)) + + return solana_client.send_transaction(trx, signer, opts=TxOpts(skip_preflight=False, skip_confirmation=False, preflight_commitment=Confirmed)) + + +def send_transaction_step_from_instruction(operator: Keypair, evm_loader: EvmLoader, treasury, storage_account, + instruction: SignedTransaction, + additional_accounts, steps_count, signer: Keypair, + system_program=sp.SYS_PROGRAM_ID, index=0) -> SendTransactionResp: + trx = TransactionWithComputeBudget(operator) + trx.add( + make_PartialCallOrContinueFromRawEthereumTX( + index, steps_count, instruction.rawTransaction, + operator, evm_loader, storage_account, treasury, + additional_accounts, system_program + ) + ) + + return solana_client.send_transaction(trx, signer, opts=TxOpts(skip_preflight=False, skip_confirmation=False, preflight_commitment=Confirmed)) + + +def execute_transaction_steps_from_instruction(operator: Keypair, evm_loader: EvmLoader, treasury, storage_account, + instruction: SignedTransaction, + additional_accounts, steps_count=EVM_STEPS, + signer: Keypair = None) -> SendTransactionResp: + signer = operator if signer is None else signer + + index = 0 + done = False + while not done: + response = send_transaction_step_from_instruction(operator, evm_loader, treasury, storage_account, instruction, additional_accounts, EVM_STEPS, signer, index=index) + index += 1 + + receipt = solana_client.get_transaction(response.value, commitment=Confirmed) + if receipt.value.transaction.meta.err: + raise AssertionError(f"Can't deploy contract: {receipt.value.transaction.meta.err}") + for log in receipt.value.transaction.meta.log_messages: + if "exit_status" in log: + done = True + break + if "ExitError" in log: + raise AssertionError(f"EVM Return error in logs: {receipt}") + + return response + + +def send_transaction_step_from_account(operator: Keypair, evm_loader: EvmLoader, treasury, storage_account, + additional_accounts, steps_count, signer: Keypair, + system_program=sp.SYS_PROGRAM_ID, + tag=0x35, index=0) -> GetTransactionResp: + trx = TransactionWithComputeBudget(operator) + trx.add( + make_ExecuteTrxFromAccountDataIterativeOrContinue( + index, steps_count, + operator, evm_loader, storage_account, treasury, + additional_accounts, system_program, tag + ) + ) + return send_transaction(solana_client, trx, signer) + + +def execute_transaction_steps_from_account(operator: Keypair, evm_loader: EvmLoader, treasury, storage_account, + additional_accounts, steps_count=EVM_STEPS, signer: Keypair = None) -> GetTransactionResp: + signer = operator if signer is None else signer + + index = 0 + done = False + while not done: + receipt = send_transaction_step_from_account(operator, evm_loader, treasury, storage_account, additional_accounts, EVM_STEPS, signer, index=index) + index += 1 + + if receipt.value.transaction.meta.err: + raise AssertionError(f"Can't deploy contract: {receipt.value.transaction.meta.err}") + for log in receipt.value.transaction.meta.log_messages: + if "exit_status" in log: + done = True + break + if "ExitError" in log: + raise AssertionError(f"EVM Return error in logs: {receipt}") + + return receipt + + +def execute_transaction_steps_from_account_no_chain_id(operator: Keypair, evm_loader: EvmLoader, treasury, storage_account, + additional_accounts, steps_count=EVM_STEPS, + signer: Keypair = None) -> GetTransactionResp: + signer = operator if signer is None else signer + + index = 0 + done = False + while not done: + receipt = send_transaction_step_from_account(operator, evm_loader, treasury, storage_account, additional_accounts, EVM_STEPS, signer, tag=0x36, index=index) + index += 1 + + if receipt.value.transaction.meta.err: + raise AssertionError(f"Can't deploy contract: {receipt.value.transaction.meta.err}") + for log in receipt.value.transaction.meta.log_messages: + if "exit_status" in log: + done = True + break + if "ExitError" in log: + raise AssertionError(f"EVM Return error in logs: {receipt}") + + return receipt diff --git a/integration/tests/neon_evm/test_cancel_trx.py b/integration/tests/neon_evm/test_cancel_trx.py new file mode 100644 index 0000000000..cbb683a9a4 --- /dev/null +++ b/integration/tests/neon_evm/test_cancel_trx.py @@ -0,0 +1,42 @@ +from solana.transaction import Transaction + +from .solana_utils import send_transaction, solana_client, \ + send_transaction_step_from_instruction +from .utils.constants import TAG_FINALIZED_STATE, TAG_STATE +from .utils.contract import make_contract_call_trx +from .utils.storage import create_holder +from .utils.instructions import make_Cancel +from .utils.layouts import FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, STORAGE_ACCOUNT_INFO_LAYOUT +from .utils.transaction_checks import check_holder_account_tag + + +# We need test here two types of transaction +class TestCancelTrx: + + def test_cancel_trx(self, operator_keypair, rw_lock_contract, user_account, treasury_pool, evm_loader): + """EVM can cancel transaction and finalize storage account""" + signed_tx = make_contract_call_trx(user_account, rw_lock_contract, "unchange_storage(uint8,uint8)", [1, 1]) + + storage_account = create_holder(operator_keypair) + trx = send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, storage_account, + signed_tx, + [rw_lock_contract.solana_address, + rw_lock_contract.balance_account_address, + user_account.balance_account_address], + 1, operator_keypair) + + receipt = solana_client.get_transaction(trx.value) + assert receipt.value.transaction.meta.err is None + check_holder_account_tag(storage_account, STORAGE_ACCOUNT_INFO_LAYOUT, TAG_STATE) + + user_nonce = evm_loader.get_neon_nonce(user_account.eth_address) + trx = Transaction() + trx.add( + make_Cancel(evm_loader, storage_account, operator_keypair, signed_tx.hash, + [rw_lock_contract.solana_address, + rw_lock_contract.balance_account_address, + user_account.balance_account_address]) + ) + send_transaction(solana_client, trx, operator_keypair) + check_holder_account_tag(storage_account, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + assert user_nonce < evm_loader.get_neon_nonce(user_account.eth_address) diff --git a/integration/tests/neon_evm/test_execute_trx_from_instruction.py b/integration/tests/neon_evm/test_execute_trx_from_instruction.py new file mode 100644 index 0000000000..abf72ddbed --- /dev/null +++ b/integration/tests/neon_evm/test_execute_trx_from_instruction.py @@ -0,0 +1,328 @@ +import random +import string + +import pytest +import solana +import eth_abi +from eth_account.datastructures import SignedTransaction +from eth_keys import keys as eth_keys +from eth_utils import abi, to_text +from hexbytes import HexBytes +from solana.keypair import Keypair +from solana.publickey import PublicKey +from solana.rpc.commitment import Confirmed +from spl.token.instructions import get_associated_token_address + +from .solana_utils import execute_trx_from_instruction +from .utils.assert_messages import InstructionAsserts +from .utils.constants import NEON_TOKEN_MINT_ID +from .utils.contract import make_contract_call_trx +from .utils.ethereum import make_eth_transaction +from .utils.transaction_checks import check_transaction_logs_have_text +from .types.types import Caller, Contract + + +class TestExecuteTrxFromInstruction: + + def test_simple_transfer_transaction(self, operator_keypair, treasury_pool, + sender_with_tokens: Caller, session_user: Caller, + evm_loader): + amount = 10 + sender_balance_before = evm_loader.get_neon_balance(sender_with_tokens) + recipient_balance_before = evm_loader.get_neon_balance(session_user) + + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, amount) + resp = execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, + signed_tx, + [sender_with_tokens.balance_account_address, + session_user.balance_account_address, + session_user.solana_account_address], + operator_keypair) + sender_balance_after = evm_loader.get_neon_balance(sender_with_tokens) + recipient_balance_after = evm_loader.get_neon_balance(session_user) + assert sender_balance_before - amount == sender_balance_after + assert recipient_balance_before + amount == recipient_balance_after + check_transaction_logs_have_text(resp.value, "exit_status=0x11") + + def test_transfer_transaction_with_non_existing_recipient(self, operator_keypair, treasury_pool, + sender_with_tokens: Caller, + evm_loader): + # recipient account should be created + recipient = Keypair.generate() + + recipient_ether = eth_keys.PrivateKey(recipient.secret_key[:32]).public_key.to_canonical_address() + recipient_solana_address, _ = evm_loader.ether2program(recipient_ether) + recipient_balance_address = evm_loader.ether2balance(recipient_ether) + amount = 10 + signed_tx = make_eth_transaction(recipient_ether, None, sender_with_tokens, amount) + resp = execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, + signed_tx, + [sender_with_tokens.balance_account_address, + recipient_balance_address, + PublicKey(recipient_solana_address)], + operator_keypair) + + recipient_balance_after = evm_loader.get_neon_balance(recipient_ether) + check_transaction_logs_have_text(resp.value, "exit_status=0x11") + + assert recipient_balance_after == amount + + def test_call_contract_function_without_neon_transfer(self, operator_keypair, treasury_pool, + sender_with_tokens: Caller, string_setter_contract: Contract, + evm_loader, neon_api_client): + text = ''.join(random.choice(string.ascii_letters) for _ in range(10)) + signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text]) + + resp = execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, + signed_tx, + [sender_with_tokens.balance_account_address, + string_setter_contract.solana_address], + operator_keypair) + + check_transaction_logs_have_text(resp.value, "exit_status=0x11") + assert text in to_text( + neon_api_client.call_contract_get_function(sender_with_tokens, string_setter_contract, + "get()")) + + def test_call_contract_function_with_neon_transfer(self, operator_keypair, treasury_pool, + sender_with_tokens: Caller, + evm_loader, neon_api_client, string_setter_contract): + transfer_amount = random.randint(1, 1000) + + sender_balance_before = evm_loader.get_neon_balance(sender_with_tokens) + contract_balance_before = evm_loader.get_neon_balance(string_setter_contract.eth_address) + + text = ''.join(random.choice(string.ascii_letters) for i in range(10)) + func_name = abi.function_signature_to_4byte_selector('set(string)') + data = func_name + eth_abi.encode(['string'], [text]) + signed_tx = make_eth_transaction(string_setter_contract.eth_address, data, sender_with_tokens, transfer_amount) + resp = execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, + signed_tx, + [sender_with_tokens.balance_account_address, + string_setter_contract.balance_account_address, + string_setter_contract.solana_address], + operator_keypair) + + check_transaction_logs_have_text(resp.value, "exit_status=0x11") + + assert text in to_text(neon_api_client.call_contract_get_function(sender_with_tokens, + string_setter_contract, + "get()")) + + sender_balance_after = evm_loader.get_neon_balance(sender_with_tokens) + contract_balance_after = evm_loader.get_neon_balance(string_setter_contract.eth_address) + assert sender_balance_before - transfer_amount == sender_balance_after + assert contract_balance_before + transfer_amount == contract_balance_after + + def test_incorrect_chain_id(self, operator_keypair, treasury_pool, + sender_with_tokens: Caller, session_user: Caller, + evm_loader): + amount = 1 + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, amount, chain_id=1) + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_CHAIN_ID): + execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, + signed_tx, + [sender_with_tokens.balance_account_address, + session_user.balance_account_address, + session_user.solana_account_address], + operator_keypair) + + def test_incorrect_nonce(self, operator_keypair, treasury_pool, + sender_with_tokens: Caller, session_user: Caller, + evm_loader): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + + execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, + signed_tx, + [sender_with_tokens.balance_account_address, + session_user.balance_account_address, + session_user.solana_account_address], + operator_keypair) + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_NONCE): + execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, + signed_tx, + [sender_with_tokens.balance_account_address, + session_user.balance_account_address, + session_user.solana_account_address], + operator_keypair) + + def test_insufficient_funds(self, operator_keypair, treasury_pool, evm_loader, + sender_with_tokens: Caller, session_user: Caller): + user_balance = evm_loader.get_neon_balance(session_user) + + signed_tx = make_eth_transaction(sender_with_tokens.eth_address, None, session_user, user_balance + 1) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INSUFFICIENT_FUNDS): + execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, + signed_tx, + [sender_with_tokens.balance_account_address, + session_user.balance_account_address, + session_user.solana_account_address], + operator_keypair) + + def test_gas_limit_reached(self, operator_keypair, treasury_pool, + session_user: Caller, sender_with_tokens: Caller, + evm_loader): + amount = 10 + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, amount, gas=1) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.OUT_OF_GAS): + execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, + signed_tx, + [sender_with_tokens.balance_account_address, + session_user.balance_account_address, + session_user.solana_account_address], + operator_keypair) + + def test_sender_missed_in_remaining_accounts(self, operator_keypair, treasury_pool, + session_user: Caller, sender_with_tokens: Caller, + evm_loader): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ADDRESS_MUST_BE_PRESENT): + execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, + signed_tx, + [session_user.balance_account_address, + session_user.solana_account_address], + operator_keypair) + + def test_recipient_missed_in_remaining_accounts(self, operator_keypair, treasury_pool, + sender_with_tokens: Caller, session_user: Caller, + evm_loader): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ADDRESS_MUST_BE_PRESENT): + execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, + signed_tx, + [sender_with_tokens.balance_account_address], + operator_keypair) + + def test_incorrect_treasure_pool(self, operator_keypair, + sender_with_tokens: Caller, session_user: Caller, + evm_loader): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + + treasury_buffer = b'\x02\x00\x00\x00' + treasury_pool = Keypair().public_key + + error = str.format(InstructionAsserts.INVALID_ACCOUNT, treasury_pool) + with pytest.raises(solana.rpc.core.RPCException, match=error): + execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool, treasury_buffer, + signed_tx, + [], + operator_keypair) + + def test_incorrect_treasure_index(self, operator_keypair, treasury_pool, + sender_with_tokens: Caller, session_user: Caller, + evm_loader): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + treasury_buffer = b'\x03\x00\x00\x00' + + error = str.format(InstructionAsserts.INVALID_ACCOUNT, treasury_pool.account) + with pytest.raises(solana.rpc.core.RPCException, match=error): + execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_buffer, + signed_tx, + [], + operator_keypair) + + def test_incorrect_operator_account(self, evm_loader, treasury_pool, + session_user: Caller, sender_with_tokens: Caller): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + fake_operator = Keypair() + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ACC_NOT_FOUND): + execute_trx_from_instruction(fake_operator, evm_loader, treasury_pool.account, treasury_pool.buffer, + signed_tx, + [sender_with_tokens.balance_account_address, + session_user.balance_account_address, + session_user.solana_account_address], + fake_operator) + + def test_operator_is_not_in_white_list(self, sender_with_tokens, evm_loader, treasury_pool, + session_user): + # now any user can send transactions through "execute transaction from instruction" instruction + + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + resp = execute_trx_from_instruction(sender_with_tokens.solana_account, evm_loader, + treasury_pool.account, + treasury_pool.buffer, + signed_tx, + [sender_with_tokens.balance_account_address, + session_user.balance_account_address, + session_user.solana_account_address], + sender_with_tokens.solana_account) + check_transaction_logs_have_text(resp.value, "exit_status=0x11") + + def test_incorrect_system_program(self, sender_with_tokens, operator_keypair, evm_loader, treasury_pool, + session_user): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + fake_sys_program_id = Keypair().public_key + with pytest.raises(solana.rpc.core.RPCException, + match=str.format(InstructionAsserts.NOT_SYSTEM_PROGRAM, fake_sys_program_id)): + execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, + treasury_pool.buffer, + signed_tx, + [], + operator_keypair, system_program=fake_sys_program_id) + + def test_operator_does_not_have_enough_founds(self, evm_loader, treasury_pool, + session_user: Caller, sender_with_tokens: Caller): + key = Keypair.generate() + caller_ether = eth_keys.PrivateKey(key.secret_key[:32]).public_key.to_canonical_address() + caller, caller_nonce = evm_loader.ether2program(caller_ether) + caller_token = get_associated_token_address(PublicKey(caller), NEON_TOKEN_MINT_ID) + evm_loader.create_balance_account(caller_ether) + + operator_without_money = Caller(key, PublicKey(caller), caller_ether, caller_nonce, caller_token) + + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + with pytest.raises(solana.rpc.core.RPCException, + match="Attempt to debit an account but found no record of a prior credit"): + execute_trx_from_instruction(operator_without_money.solana_account, evm_loader, treasury_pool.account, + treasury_pool.buffer, + signed_tx, + [sender_with_tokens.balance_account_address, + session_user.balance_account_address, + session_user.solana_account_address], + operator_without_money.solana_account) + + def test_transaction_with_access_list(self, operator_keypair, treasury_pool, sender_with_tokens, + evm_loader, calculator_contract, calculator_caller_contract): + access_list = ( + { + "address": '0x' + calculator_contract.eth_address.hex(), + "storageKeys": ( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001", + ) + }, + ) + signed_tx = make_contract_call_trx(sender_with_tokens, calculator_caller_contract, "callCalculator()", [], + access_list=access_list) + + resp = execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, + signed_tx, + [sender_with_tokens.balance_account_address, + calculator_caller_contract.solana_address, + calculator_contract.solana_address], + operator_keypair) + + check_transaction_logs_have_text(resp.value, "exit_status=0x12") + + def test_old_trx_type_with_leading_zeros(self, sender_with_tokens, operator_keypair, evm_loader, + calculator_caller_contract, calculator_contract, treasury_pool): + signed_tx = make_contract_call_trx(sender_with_tokens, calculator_caller_contract, "callCalculator()", []) + new_raw_trx = HexBytes(bytes([0]) + signed_tx.rawTransaction) + + signed_tx_new = SignedTransaction( + rawTransaction=new_raw_trx, + hash=signed_tx.hash, + r=signed_tx.r, + s=signed_tx.s, + v=signed_tx.v, + ) + + resp = execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, + signed_tx_new, + [sender_with_tokens.balance_account_address, + calculator_caller_contract.solana_address, + calculator_contract.solana_address], + operator_keypair) + check_transaction_logs_have_text(resp.value, "exit_status=0x12") diff --git a/integration/tests/neon_evm/test_holder_account.py b/integration/tests/neon_evm/test_holder_account.py new file mode 100644 index 0000000000..807b567b92 --- /dev/null +++ b/integration/tests/neon_evm/test_holder_account.py @@ -0,0 +1,163 @@ +from hashlib import sha256 +from random import randrange + +import pytest +import solana +from solana.publickey import PublicKey +from solana.rpc.commitment import Confirmed +from solana.rpc.types import TxOpts +from solana.transaction import Transaction + +from . import solana_utils +from .solana_utils import solana_client, write_transaction_to_holder_account, \ + send_transaction_step_from_account, get_solana_balance, execute_transaction_steps_from_account +from .utils.assert_messages import InstructionAsserts +from .utils.constants import EVM_LOADER, TAG_STATE +from .utils.contract import make_deployment_transaction, make_contract_call_trx +from .utils.ethereum import make_eth_transaction +from .utils.instructions import make_WriteHolder +from .utils.layouts import STORAGE_ACCOUNT_INFO_LAYOUT, HOLDER_ACCOUNT_INFO_LAYOUT +from .utils.storage import create_holder, delete_holder + + +def transaction_from_holder(key: PublicKey): + data = solana_client.get_account_info(key, commitment=Confirmed).value.data + header = HOLDER_ACCOUNT_INFO_LAYOUT.parse(data) + + return data[HOLDER_ACCOUNT_INFO_LAYOUT.sizeof():][:header.len] + + +def test_create_holder_account(operator_keypair): + holder_acc = create_holder(operator_keypair) + info = solana_client.get_account_info(holder_acc, commitment=Confirmed) + assert info.value is not None, "Holder account is not created" + assert info.value.lamports == 1000000000, "Account balance is not correct" + + +def test_create_the_same_holder_account_by_another_user(operator_keypair, session_user): + seed = str(randrange(1000000)) + storage = PublicKey( + sha256(bytes(operator_keypair.public_key) + bytes(seed, 'utf8') + bytes(PublicKey(EVM_LOADER))).digest()) + create_holder(operator_keypair, seed=seed, storage=storage) + + trx = Transaction() + trx.add( + solana_utils.create_account_with_seed(session_user.solana_account.public_key, + session_user.solana_account.public_key, seed, 10 ** 9, 128 * 1024), + solana_utils.create_holder_account(storage, session_user.solana_account.public_key, bytes(seed, 'utf8')) + ) + + error = str.format(InstructionAsserts.INVALID_ACCOUNT, storage) + with pytest.raises(solana.rpc.core.RPCException, match=error): + solana_utils.send_transaction(solana_client, trx, session_user.solana_account) + + +def test_write_tx_to_holder(operator_keypair, session_user, second_session_user, evm_loader): + holder_acc = create_holder(operator_keypair) + signed_tx = make_eth_transaction(second_session_user.eth_address, None, session_user, 10) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + assert signed_tx.rawTransaction == transaction_from_holder(holder_acc), \ + "Account data is not correct" + + +def test_write_tx_to_holder_in_parts(operator_keypair, session_user): + holder_acc = create_holder(operator_keypair) + + signed_tx = make_deployment_transaction(session_user, "erc20_for_spl_factory", "ERC20ForSplFactory") + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + assert signed_tx.rawTransaction == transaction_from_holder(holder_acc), \ + "Account data is not correct" + + +def test_write_tx_to_holder_by_no_owner(operator_keypair, session_user, second_session_user, evm_loader): + holder_acc = create_holder(operator_keypair) + + signed_tx = make_eth_transaction(second_session_user.eth_address, None, session_user, 10) + with pytest.raises(solana.rpc.core.RPCException, match="invalid owner"): + write_transaction_to_holder_account(signed_tx, holder_acc, session_user.solana_account) + + +def test_delete_holder(operator_keypair): + holder_acc = create_holder(operator_keypair) + delete_holder(holder_acc, operator_keypair, operator_keypair) + info = solana_client.get_account_info(holder_acc, commitment=Confirmed) + assert info.value is None, "Holder account isn't deleted" + + +def test_success_refund_after_holder_deliting(operator_keypair): + holder_acc = create_holder(operator_keypair) + + pre_storage = get_solana_balance(holder_acc) + pre_acc = get_solana_balance(operator_keypair.public_key) + + delete_holder(holder_acc, operator_keypair, operator_keypair) + + post_acc = get_solana_balance(operator_keypair.public_key) + + assert pre_storage + pre_acc, post_acc + 5000 + + +def test_delete_holder_by_no_owner(operator_keypair, user_account): + holder_acc = create_holder(operator_keypair) + with pytest.raises(solana.rpc.core.RPCException, match="invalid owner"): + delete_holder(holder_acc, user_account.solana_account, user_account.solana_account) + + +def test_write_to_not_finalized_holder(rw_lock_contract, user_account, evm_loader, operator_keypair, treasury_pool, + new_holder_acc): + signed_tx = make_contract_call_trx(user_account, rw_lock_contract, "unchange_storage(uint8,uint8)", [1, 1]) + write_transaction_to_holder_account(signed_tx, new_holder_acc, operator_keypair) + + send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, new_holder_acc, + [user_account.solana_account_address, + user_account.balance_account_address, + rw_lock_contract.solana_address], 1, operator_keypair) + account_data = solana_client.get_account_info(new_holder_acc, commitment=Confirmed).value.data + parsed_data = STORAGE_ACCOUNT_INFO_LAYOUT.parse(account_data) + assert parsed_data.tag == TAG_STATE + + signed_tx2 = make_contract_call_trx(user_account, rw_lock_contract, "unchange_storage(uint8,uint8)", [1, 1]) + + with pytest.raises(solana.rpc.core.RPCException, match="invalid tag"): + write_transaction_to_holder_account(signed_tx2, new_holder_acc, operator_keypair) + + +def test_write_to_finalized_holder(rw_lock_contract, session_user, evm_loader, operator_keypair, treasury_pool, + new_holder_acc): + signed_tx = make_contract_call_trx(session_user, rw_lock_contract, "unchange_storage(uint8,uint8)", [1, 1]) + write_transaction_to_holder_account(signed_tx, new_holder_acc, operator_keypair) + + execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, new_holder_acc, + [session_user.solana_account_address, + session_user.balance_account_address, + rw_lock_contract.solana_address]) + signed_tx2 = make_contract_call_trx(session_user, rw_lock_contract, "unchange_storage(uint8,uint8)", [1, 1]) + + write_transaction_to_holder_account(signed_tx2, new_holder_acc, operator_keypair) + assert signed_tx2.rawTransaction == transaction_from_holder(new_holder_acc), \ + "Account data is not correct" + + +def test_holder_write_integer_overflow(operator_keypair, holder_acc): + overflow_offset = int(0xFFFFFFFFFFFFFFFF) + + trx = Transaction() + trx.add(make_WriteHolder(operator_keypair.public_key, holder_acc, b"\x00" * 32, overflow_offset, b"\x00" * 1)) + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.HOLDER_OVERFLOW): + solana_client.send_transaction( + trx, + operator_keypair, + opts=TxOpts(skip_confirmation=True, preflight_commitment=Confirmed), + ) + +def test_holder_write_account_size_overflow(operator_keypair, holder_acc): + overflow_offset = int(0xFFFFFFFF) + + trx = Transaction() + trx.add(make_WriteHolder(operator_keypair.public_key, holder_acc, b"\x00" * 32, overflow_offset, b"\x00" * 1)) + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.HOLDER_INSUFFICIENT_SIZE): + solana_client.send_transaction( + trx, + operator_keypair, + opts=TxOpts(skip_confirmation=True, preflight_commitment=Confirmed), + ) diff --git a/integration/tests/neon_evm/test_neon_core_api.py b/integration/tests/neon_evm/test_neon_core_api.py new file mode 100644 index 0000000000..ec66dd4f67 --- /dev/null +++ b/integration/tests/neon_evm/test_neon_core_api.py @@ -0,0 +1,58 @@ +from eth_utils import abi, to_text + +from .utils.contract import deploy_contract, get_contract_bin +from .solana_utils import solana_client + + +def test_get_storage_at(neon_api_client, operator_keypair, user_account, evm_loader, treasury_pool): + contract = deploy_contract(operator_keypair, user_account, "hello_world", evm_loader, treasury_pool) + storage = neon_api_client.get_storage_at(contract.eth_address.hex())["value"] + zero_array = [0 for _ in range(31)] + assert storage == zero_array + [5] + + storage = neon_api_client.get_storage_at(contract.eth_address.hex(), index='0x2')["value"] + assert storage == zero_array + [0] + + +def test_get_ether_account_data(neon_api_client, user_account): + result = neon_api_client.get_ether_account_data(user_account.eth_address.hex())['value'] + assert str(user_account.balance_account_address) == result[0]["solana_address"] + assert solana_client.get_account_info(user_account.solana_account.public_key).value is not None + + +def test_emulate_transfer(neon_api_client, user_account, session_user): + result = neon_api_client.emulate(user_account.eth_address.hex(), + session_user.eth_address.hex()) + assert result['exit_status'] == 'succeed', f"The 'exit_status' field is not succeed. Result: {result}" + assert result['steps_executed'] == 1, f"Steps executed amount is not 1. Result: {result}" + assert result['used_gas'] > 0, f"Used gas is less than 0. Result: {result}" + + +def test_emulate_contract_deploy(neon_api_client, user_account): + contract_code = get_contract_bin("hello_world") + result = neon_api_client.emulate(user_account.eth_address.hex(), + contract=None, data=contract_code) + assert result['exit_status'] == 'succeed', f"The 'exit_status' field is not succeed. Result: {result}" + assert result['steps_executed'] > 100, f"Steps executed amount is wrong. Result: {result}" + assert result['used_gas'] > 0, f"Used gas is less than 0. Result: {result}" + + +def test_emulate_call_contract_function(neon_api_client, operator_keypair, treasury_pool, evm_loader, user_account): + contract = deploy_contract(operator_keypair, user_account, "hello_world", evm_loader, treasury_pool) + assert contract.eth_address + data = abi.function_signature_to_4byte_selector('call_hello_world()') + + result = neon_api_client.emulate(user_account.eth_address.hex(), + contract=contract.eth_address.hex(), data=data) + + assert result['exit_status'] == 'succeed', f"The 'exit_status' field is not succeed. Result: {result}" + assert result['steps_executed'] > 0, f"Steps executed amount is 0. Result: {result}" + assert result['used_gas'] > 0, f"Used gas is less than 0. Result: {result}" + assert "Hello World" in to_text(result["result"]) + + +def test_emulate_with_small_amount_of_steps(neon_api_client, evm_loader, user_account): + contract_code = get_contract_bin("hello_world") + result = neon_api_client.emulate(user_account.eth_address.hex(), + contract=None, data=contract_code, max_steps_to_execute=10) + assert result['error'] == 'Too many steps' diff --git a/integration/tests/neon_evm/test_parallel.py b/integration/tests/neon_evm/test_parallel.py new file mode 100644 index 0000000000..856ef47369 --- /dev/null +++ b/integration/tests/neon_evm/test_parallel.py @@ -0,0 +1,165 @@ +from typing import Any +from unittest import TestCase + +from _pytest.fixtures import fixture +from solana.keypair import Keypair +from solana.publickey import PublicKey +from solana.rpc.core import RPCException + +from .solana_utils import EvmLoader, solana_client, make_new_user, deposit_neon, \ + cancel_transaction, send_transaction_step_from_account, execute_trx_from_instruction +from .utils.contract import write_transaction_to_holder_account, make_deployment_transaction +from .utils.ethereum import create_contract_address, make_eth_transaction +from .utils.storage import create_holder +from .types.types import Caller, TreasuryPool + +EVM_STEPS_COUNT = 0xFFFFFFFF +ONE_TOKEN = 10 ** 9 +MAX_PERMITTED_DATA_INCREASE = 10240 + + +class ParallelTransactionsTest(TestCase): + @fixture(autouse=True) + def prepare_fixture( + self, + user_account: Caller, + evm_loader: EvmLoader, + operator_keypair: Keypair, + treasury_pool: TreasuryPool, + ): + self.user_account = user_account + self.evm_loader = evm_loader + self.operator_keypair = operator_keypair + self.treasury_pool = treasury_pool + self.second_account = make_new_user(evm_loader) + + def test_create_same_accounts(self): + cases = [ + [2], + [3], + [4], + ] + + for case in cases: + iterations = case[0] + with self.subTest(iterations=iterations): + self.create_same_accounts_subtest(iterations) + + def create_same_accounts_subtest(self, iterations: int): + deposit_neon(self.evm_loader, self.operator_keypair, self.user_account.eth_address, ONE_TOKEN) + deposit_neon(self.evm_loader, self.operator_keypair, self.second_account.eth_address, ONE_TOKEN) + + contract = create_contract_address(self.user_account, self.evm_loader) + holder_acc = create_holder(self.operator_keypair) + deployment_tx = make_deployment_transaction(self.user_account, "big_contract", "BigContract") + write_transaction_to_holder_account(deployment_tx, holder_acc, self.operator_keypair) + + # First N iterations + for i in range(iterations): + deployment_receipt = send_transaction_step_from_account(self.operator_keypair, + self.evm_loader, + self.treasury_pool, + holder_acc, + [contract.balance_account_address, + contract.solana_address, + self.user_account.balance_account_address, + self.user_account.solana_account_address], + EVM_STEPS_COUNT, + self.operator_keypair, + index=i) + + assert not ParallelTransactionsTest.check_iteration_deployed(deployment_receipt) + + # Transferring to the same account in order to break deployment + ParallelTransactionsTest.transfer( + self.second_account, + contract.eth_address, + ONE_TOKEN, + self.evm_loader, + self.operator_keypair, + self.treasury_pool, + ) + + # Trying to finish deployment (expected to fail) + try: + send_transaction_step_from_account(self.operator_keypair, + self.evm_loader, + self.treasury_pool, + holder_acc, + [contract.balance_account_address, + contract.solana_address, + self.user_account.balance_account_address, + self.user_account.solana_account_address], + EVM_STEPS_COUNT, + self.operator_keypair) + + assert False, 'Deployment expected to fail' + except RPCException as e: + ParallelTransactionsTest.check_account_initialized_in_another_trx_exception(e, + contract.balance_account_address) + + # Cancel deployment transaction: + cancel_transaction( + self.evm_loader, + deployment_tx.hash, + holder_acc, + self.operator_keypair, + [contract.balance_account_address, + contract.solana_address, + self.user_account.balance_account_address, + self.user_account.solana_account_address], + ) + + @staticmethod + def check_iteration_deployed(receipt: Any) -> bool: + if receipt.value.transaction.meta.err: + raise AssertionError(f"Can't deploy contract: {receipt.value.transaction.meta.err}") + + for log in receipt.value.transaction.meta.log_messages: + if "exit_status" in log: + return True + if "ExitError" in log: + raise AssertionError(f"EVM Return error in logs: {receipt}") + return False + + @staticmethod + def transfer( + src_account: Caller, + dst_addr: bytes, + value: int, + evm_loader: EvmLoader, + operator_keypair: Keypair, + treasury_pool: TreasuryPool, + ): + message = make_eth_transaction( + dst_addr, + bytes(), + src_account, + value, + ) + + dst_solana_account, _ = evm_loader.ether2program(dst_addr) + dst_balance_account = evm_loader.ether2balance(dst_addr) + + trx = execute_trx_from_instruction(operator_keypair, evm_loader, treasury_pool.account, treasury_pool.buffer, + message, + [dst_balance_account, + PublicKey(dst_solana_account), + src_account.balance_account_address], + operator_keypair) + receipt = solana_client.get_transaction(trx.value) + print("Transfer receipt:", receipt) + assert receipt.value.transaction.meta.err is None + + return receipt + + @staticmethod + def check_account_initialized_in_another_trx_exception(exception: RPCException, solana_address: PublicKey): + error = exception.args[0] + print("error:", error) + + for log in error.data.logs: + if f'Account {solana_address} - was empty, created by another transaction' in log: + return + + assert False, "Search string not found in Solana logs" diff --git a/integration/tests/neon_evm/test_step_instructions_work_the_same.py b/integration/tests/neon_evm/test_step_instructions_work_the_same.py new file mode 100644 index 0000000000..be39c904d8 --- /dev/null +++ b/integration/tests/neon_evm/test_step_instructions_work_the_same.py @@ -0,0 +1,60 @@ +from .solana_utils import solana_client, execute_transaction_steps_from_account, write_transaction_to_holder_account, \ + execute_transaction_steps_from_instruction +from .utils.contract import make_deployment_transaction +from .utils.ethereum import make_eth_transaction, create_contract_address +from .utils.storage import create_holder + + +class TestTransactionStepFromAccount: + def test_simple_transfer_transaction(self, operator_keypair, treasury_pool, evm_loader, + sender_with_tokens, session_user, holder_acc): + amount = 10 + + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, amount) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + resp_from_acc = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0).value + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, amount) + signature = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], + 0) + resp_from_inst = solana_client.get_transaction(signature.value).value + assert resp_from_acc.transaction.meta.fee == resp_from_inst.transaction.meta.fee + assert resp_from_acc.transaction.meta.inner_instructions == resp_from_inst.transaction.meta.inner_instructions + for i in range(len(resp_from_acc.transaction.meta.post_balances)): + assert resp_from_acc.transaction.meta.post_balances[i] - resp_from_acc.transaction.meta.pre_balances[i] == \ + resp_from_inst.transaction.meta.post_balances[i] - resp_from_inst.transaction.meta.pre_balances[i] + + def test_deploy_contract(self, operator_keypair, holder_acc, treasury_pool, evm_loader, sender_with_tokens): + contract_filename = "small" + contract = create_contract_address(sender_with_tokens, evm_loader) + + signed_tx = make_deployment_transaction(sender_with_tokens, contract_filename) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + resp_from_acc = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [contract.solana_address, + contract.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address]).value + signed_tx = make_deployment_transaction(sender_with_tokens, contract_filename) + holder_acc = create_holder(operator_keypair) + contract = create_contract_address(sender_with_tokens, evm_loader) + + signature = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, [contract.solana_address, + contract.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address]) + resp_from_inst = solana_client.get_transaction(signature.value).value + assert resp_from_acc.transaction.meta.fee == resp_from_inst.transaction.meta.fee + assert len(resp_from_acc.transaction.meta.inner_instructions) == len( + resp_from_inst.transaction.meta.inner_instructions) + assert len(resp_from_acc.transaction.transaction.message.account_keys) == len( + resp_from_acc.transaction.transaction.message.account_keys) diff --git a/integration/tests/neon_evm/test_transaction_step_from_account.py b/integration/tests/neon_evm/test_transaction_step_from_account.py new file mode 100644 index 0000000000..0dc4e50d76 --- /dev/null +++ b/integration/tests/neon_evm/test_transaction_step_from_account.py @@ -0,0 +1,556 @@ +import random +import string +import time + +import eth_abi +import pytest +import solana +from eth_keys import keys as eth_keys +from eth_utils import abi, to_text, to_int +from solana.keypair import Keypair +from solana.publickey import PublicKey +from solana.rpc.commitment import Processed, Confirmed +from solana.rpc.types import TxOpts + +from .solana_utils import solana_client, execute_transaction_steps_from_account, \ + write_transaction_to_holder_account, create_treasury_pool_address, send_transaction_step_from_account +from utils.helpers import gen_hash_of_block +from .utils.assert_messages import InstructionAsserts +from .utils.constants import TAG_FINALIZED_STATE +from .utils.contract import make_deployment_transaction, make_contract_call_trx, deploy_contract, get_contract_bin +from .utils.ethereum import make_eth_transaction, create_contract_address +from .utils.instructions import TransactionWithComputeBudget, make_ExecuteTrxFromAccountDataIterativeOrContinue +from .utils.layouts import FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT +from .utils.storage import create_holder +from .utils.transaction_checks import check_transaction_logs_have_text, check_holder_account_tag +from .types.types import TreasuryPool + + +def generate_access_lists(): + addr1 = gen_hash_of_block(20) + addr2 = gen_hash_of_block(20) + key1, key2, key3, key4 = (f"0x000000000000000000000000000000000000000000000000000000000000000{item}" for item in + (0, 1, 2, 3)) + return (({"address": addr1, "storageKeys": []},), + ({"address": addr1, "storageKeys": (key1, key2, key3, key4)},), + ({"address": addr1, "storageKeys": (key1, key2)}, {"address": addr2, "storageKeys": []}), + ({"address": addr1, "storageKeys": (key1, key2)}, {"address": addr2, "storageKeys": (key3,)})) + + +class TestTransactionStepFromAccount: + + def test_simple_transfer_transaction(self, operator_keypair, treasury_pool, evm_loader, + sender_with_tokens, session_user, holder_acc): + amount = 10 + sender_balance_before = evm_loader.get_neon_balance(sender_with_tokens) + recipient_balance_before = evm_loader.get_neon_balance(session_user) + + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, amount) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + sender_balance_after = evm_loader.get_neon_balance(sender_with_tokens) + recipient_balance_after = evm_loader.get_neon_balance(session_user) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") + assert sender_balance_before - amount == sender_balance_after + assert recipient_balance_before + amount == recipient_balance_after + + def test_deploy_contract(self, operator_keypair, holder_acc, treasury_pool, evm_loader, sender_with_tokens, + neon_api_client): + contract_filename = "hello_world" + contract = create_contract_address(sender_with_tokens, evm_loader) + + signed_tx = make_deployment_transaction(sender_with_tokens, contract_filename) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + contract_code = get_contract_bin("hello_world") + + steps_count = neon_api_client.get_steps_count(sender_with_tokens, None, contract_code) + resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [contract.solana_address, + contract.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], + steps_count) + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x12") + + def test_call_contract_function_without_neon_transfer(self, operator_keypair, holder_acc, treasury_pool, + sender_with_tokens, evm_loader, string_setter_contract, + neon_api_client): + text = ''.join(random.choice(string.ascii_letters) for _ in range(10)) + signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text]) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [string_setter_contract.solana_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address]) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") + + assert text in to_text( + neon_api_client.call_contract_get_function(sender_with_tokens, string_setter_contract, + "get()")) + + def test_call_contract_function_with_neon_transfer(self, operator_keypair, treasury_pool, + sender_with_tokens, string_setter_contract, holder_acc, + evm_loader, neon_api_client): + transfer_amount = random.randint(1, 1000) + + sender_balance_before = evm_loader.get_neon_balance(sender_with_tokens) + contract_balance_before = evm_loader.get_neon_balance(string_setter_contract.eth_address) + + text = ''.join(random.choice(string.ascii_letters) for _ in range(10)) + + signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text], + value=transfer_amount) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [string_setter_contract.solana_address, + string_setter_contract.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address]) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") + + sender_balance_after = evm_loader.get_neon_balance(sender_with_tokens) + contract_balance_after = evm_loader.get_neon_balance(string_setter_contract.eth_address) + assert sender_balance_before - transfer_amount == sender_balance_after + assert contract_balance_before + transfer_amount == contract_balance_after + + assert text in to_text( + neon_api_client.call_contract_get_function(sender_with_tokens, string_setter_contract, + "get()")) + + def test_transfer_transaction_with_non_existing_recipient(self, operator_keypair, holder_acc, treasury_pool, + sender_with_tokens, evm_loader): + # recipient account should be created + recipient = Keypair.generate() + recipient_ether = eth_keys.PrivateKey(recipient.secret_key[:32]).public_key.to_canonical_address() + recipient_solana_address, _ = evm_loader.ether2program(recipient_ether) + recipient_balance_address = evm_loader.ether2balance(recipient_ether) + amount = 10 + signed_tx = make_eth_transaction(recipient_ether, None, sender_with_tokens, amount) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [PublicKey(recipient_solana_address), + recipient_balance_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + recipient_balance_after = evm_loader.get_neon_balance(recipient_ether) + check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") + + assert recipient_balance_after == amount + + def test_incorrect_chain_id(self, operator_keypair, holder_acc, treasury_pool, + sender_with_tokens, session_user, evm_loader): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1, chain_id=1) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_CHAIN_ID): + execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + def test_incorrect_nonce(self, operator_keypair, treasury_pool, sender_with_tokens, evm_loader, session_user, + holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_NONCE): + execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + def test_run_finalized_transaction(self, operator_keypair, treasury_pool, sender_with_tokens, evm_loader, + session_user, holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.TRX_ALREADY_FINALIZED): + execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + def test_insufficient_funds(self, operator_keypair, treasury_pool, evm_loader, session_user, + holder_acc, sender_with_tokens): + user_balance = evm_loader.get_neon_balance(session_user) + + signed_tx = make_eth_transaction(sender_with_tokens.eth_address, None, session_user, user_balance + 1) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INSUFFICIENT_FUNDS): + execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + def test_gas_limit_reached(self, operator_keypair, treasury_pool, session_user, evm_loader, sender_with_tokens, + holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 10, gas=1) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.OUT_OF_GAS): + execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + def test_sender_missed_in_remaining_accounts(self, operator_keypair, treasury_pool, session_user, + sender_with_tokens, evm_loader, holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ADDRESS_MUST_BE_PRESENT): + execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [session_user.solana_account_address, + session_user.balance_account_address], 0) + + def test_recipient_missed_in_remaining_accounts(self, operator_keypair, treasury_pool, session_user, + sender_with_tokens, evm_loader, holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ADDRESS_MUST_BE_PRESENT): + execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + def test_incorrect_treasure_pool(self, operator_keypair, sender_with_tokens, evm_loader, session_user, holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + index = 2 + treasury = TreasuryPool(index, Keypair().generate().public_key, index.to_bytes(4, 'little')) + + error = str.format(InstructionAsserts.INVALID_ACCOUNT, treasury.account) + with pytest.raises(solana.rpc.core.RPCException, match=error): + execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury, holder_acc, [], 0) + + def test_incorrect_treasure_index(self, operator_keypair, sender_with_tokens, evm_loader, + session_user, holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + index = 2 + treasury = TreasuryPool(index, create_treasury_pool_address(index), (index + 1).to_bytes(4, 'little')) + + error = str.format(InstructionAsserts.INVALID_ACCOUNT, treasury.account) + with pytest.raises(solana.rpc.core.RPCException, match=error): + execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury, holder_acc, [], 0) + + def test_incorrect_operator_account(self, operator_keypair, sender_with_tokens, evm_loader, treasury_pool, + session_user, holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + fake_operator = Keypair() + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ACC_NOT_FOUND): + execute_transaction_steps_from_account(fake_operator, evm_loader, treasury_pool, holder_acc, + [sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address, + session_user.solana_account_address, + session_user.balance_account_address], 0) + + def test_operator_is_not_in_white_list(self, sender_with_tokens, operator_keypair, evm_loader, treasury_pool, + session_user, holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.NOT_AUTHORIZED_OPERATOR): + execute_transaction_steps_from_account(sender_with_tokens.solana_account, evm_loader, treasury_pool, + holder_acc, + [sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address, + session_user.solana_account_address, + session_user.balance_account_address], 0, + signer=sender_with_tokens.solana_account) + + def test_incorrect_system_program(self, sender_with_tokens, operator_keypair, evm_loader, treasury_pool, + session_user, holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + fake_sys_program_id = Keypair().public_key + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + error = str.format(InstructionAsserts.NOT_SYSTEM_PROGRAM, fake_sys_program_id) + with pytest.raises(solana.rpc.core.RPCException, match=error): + send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [], 1, operator_keypair, system_program=fake_sys_program_id) + + def test_incorrect_holder_account(self, operator_keypair, evm_loader, treasury_pool): + fake_holder_acc = Keypair.generate().public_key + + error = str.format(InstructionAsserts.NOT_PROGRAM_OWNED, fake_holder_acc) + with pytest.raises(solana.rpc.core.RPCException, match=error): + send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, fake_holder_acc, [], 1, + operator_keypair) + + def test_transaction_with_access_list(self, operator_keypair, holder_acc, treasury_pool, + sender_with_tokens, evm_loader, calculator_contract, + calculator_caller_contract): + access_list = ( + { + "address": '0x' + calculator_contract.eth_address.hex(), + "storageKeys": ( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001", + ) + }, + ) + signed_tx = make_contract_call_trx(sender_with_tokens, calculator_caller_contract, "callCalculator()", [], + access_list=access_list) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [calculator_caller_contract.solana_address, + calculator_contract.solana_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address]) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x12") + + @pytest.mark.parametrize("access_list", generate_access_lists()) + def test_access_list_structure(self, operator_keypair, holder_acc, treasury_pool, evm_loader, + sender_with_tokens, string_setter_contract, access_list, neon_api_client): + text = ''.join(random.choice(string.ascii_letters) for _ in range(10)) + + signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text], + value=10, access_list=access_list) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [string_setter_contract.solana_address, + string_setter_contract.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address]) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") + + assert text in to_text( + neon_api_client.call_contract_get_function(sender_with_tokens, string_setter_contract, + "get()")) + + +class TestAccountStepContractCallContractInteractions: + def test_contract_call_unchange_storage_function(self, rw_lock_contract, rw_lock_caller, session_user, evm_loader, + operator_keypair, treasury_pool, holder_acc): + signed_tx = make_contract_call_trx(session_user, rw_lock_caller, 'unchange_storage(uint8,uint8)', [1, 1]) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [rw_lock_caller.solana_address, + rw_lock_contract.solana_address, + session_user.solana_account_address, + session_user.balance_account_address]) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x12") + + def test_contract_call_set_function(self, rw_lock_contract, session_user, evm_loader, operator_keypair, + treasury_pool, holder_acc, rw_lock_caller, neon_api_client): + signed_tx = make_contract_call_trx(session_user, rw_lock_caller, 'update_storage_str(string)', ['hello']) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [rw_lock_caller.solana_address, + rw_lock_contract.solana_address, + session_user.solana_account_address, + session_user.balance_account_address], 1000) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") + + assert 'hello' in to_text(neon_api_client.call_contract_get_function(session_user, rw_lock_contract, + "get_text()")) + + def test_contract_call_get_function(self, rw_lock_contract, session_user, evm_loader, operator_keypair, + treasury_pool, holder_acc, rw_lock_caller): + signed_tx = make_contract_call_trx(session_user, rw_lock_caller, 'get_text()') + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + [rw_lock_caller.solana_address, + rw_lock_contract.solana_address, + session_user.solana_account_address, + session_user.balance_account_address], 1000) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x12") + + def test_contract_call_update_storage_map_function(self, rw_lock_contract, session_user, evm_loader, + operator_keypair, rw_lock_caller, + treasury_pool, holder_acc, neon_api_client): + signed_tx = make_contract_call_trx(session_user, rw_lock_caller, 'update_storage_map(uint256)', [3]) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + func_name = abi.function_signature_to_4byte_selector('update_storage_map(uint256)') + data = func_name + eth_abi.encode(['uint256'], [3]) + result = neon_api_client.emulate(session_user.eth_address.hex(), + rw_lock_caller.eth_address.hex(), + data.hex()) + additional_accounts = [session_user.solana_account_address, + session_user.balance_account_address, + rw_lock_contract.solana_address, + rw_lock_caller.solana_address] + print(result) + for acc in result['solana_accounts']: + additional_accounts.append(PublicKey(acc['pubkey'])) + + resp = execute_transaction_steps_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc, + additional_accounts) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") + + constructor_args = eth_abi.encode(['address', 'uint256'], [rw_lock_caller.eth_address.hex(), 2]) + actual_data = neon_api_client.call_contract_get_function(session_user, rw_lock_contract, + "data(address,uint256)", constructor_args) + assert to_int(hexstr=actual_data) == 2, "Contract data is not correct" + + +class TestTransactionStepFromAccountParallelRuns: + + def test_one_user_call_2_contracts(self, rw_lock_contract, string_setter_contract, user_account, evm_loader, + operator_keypair, treasury_pool, new_holder_acc): + signed_tx = make_contract_call_trx(user_account, rw_lock_contract, 'unchange_storage(uint8,uint8)', [1, 1]) + write_transaction_to_holder_account(signed_tx, new_holder_acc, operator_keypair) + + send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, new_holder_acc, + [user_account.balance_account_address, + user_account.solana_account_address, + rw_lock_contract.solana_address], 1, operator_keypair) + + signed_tx2 = make_contract_call_trx(user_account, string_setter_contract, 'get()') + + holder_acc2 = create_holder(operator_keypair) + write_transaction_to_holder_account(signed_tx2, holder_acc2, operator_keypair) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.LOCKED_ACC): + send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc2, + [user_account.balance_account_address, + user_account.solana_account_address, + string_setter_contract.solana_address], 1, operator_keypair) + + def test_2_users_call_the_same_contract(self, rw_lock_contract, user_account, + session_user, evm_loader, operator_keypair, + treasury_pool, new_holder_acc): + signed_tx = make_contract_call_trx(user_account, rw_lock_contract, 'unchange_storage(uint8,uint8)', [1, 1]) + write_transaction_to_holder_account(signed_tx, new_holder_acc, operator_keypair) + + send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, new_holder_acc, + [user_account.solana_account_address, + user_account.balance_account_address, + rw_lock_contract.solana_address], 1, operator_keypair) + + signed_tx2 = make_contract_call_trx(session_user, rw_lock_contract, 'get_text()') + + holder_acc2 = create_holder(operator_keypair) + write_transaction_to_holder_account(signed_tx2, holder_acc2, operator_keypair) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.LOCKED_ACC): + send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc2, + [session_user.solana_account_address, + session_user.balance_account_address, + rw_lock_contract.solana_address], 1, operator_keypair) + + def test_two_contracts_call_same_contract(self, rw_lock_contract, user_account, + session_user, evm_loader, operator_keypair, + treasury_pool, new_holder_acc): + constructor_args = eth_abi.encode(['address'], [rw_lock_contract.eth_address.hex()]) + + contract1 = deploy_contract(operator_keypair, session_user, "rw_lock", evm_loader, treasury_pool, + encoded_args=constructor_args, contract_name="rw_lock_caller") + contract2 = deploy_contract(operator_keypair, session_user, "rw_lock", evm_loader, treasury_pool, + encoded_args=constructor_args, contract_name="rw_lock_caller") + + signed_tx1 = make_contract_call_trx(user_account, contract1, 'unchange_storage(uint8,uint8)', [1, 1]) + signed_tx2 = make_contract_call_trx(session_user, contract2, 'get_text()') + write_transaction_to_holder_account(signed_tx1, new_holder_acc, operator_keypair) + + send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, new_holder_acc, + [user_account.solana_account_address, + user_account.balance_account_address, + rw_lock_contract.solana_address, + contract1.solana_address], 1, operator_keypair) + + holder_acc2 = create_holder(operator_keypair) + write_transaction_to_holder_account(signed_tx2, holder_acc2, operator_keypair) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.LOCKED_ACC): + send_transaction_step_from_account(operator_keypair, evm_loader, treasury_pool, holder_acc2, + [session_user.solana_account_address, + session_user.balance_account_address, + rw_lock_contract.solana_address, + contract2.solana_address], 1, + operator_keypair) + + +class TestStepFromAccountChangingOperatorsDuringTrxRun: + def test_next_operator_can_continue_trx_after_some_time(self, rw_lock_contract, user_account, evm_loader, + operator_keypair, second_operator_keypair, treasury_pool, + new_holder_acc): + signed_tx = make_contract_call_trx(user_account, rw_lock_contract, 'update_storage_str(string)', ['text']) + write_transaction_to_holder_account(signed_tx, new_holder_acc, operator_keypair) + + trx = TransactionWithComputeBudget(operator_keypair) + trx.add( + make_ExecuteTrxFromAccountDataIterativeOrContinue( + 0, 1, + operator_keypair, evm_loader, new_holder_acc, treasury_pool, + [user_account.solana_account_address, + user_account.balance_account_address, + rw_lock_contract.solana_address] + ) + ) + solana_client.send_transaction(trx, operator_keypair, + opts=TxOpts(skip_confirmation=True, preflight_commitment=Confirmed)) + + # next operator can't continue trx during OPERATOR_PRIORITY_SLOTS*0.4 + error = rf"{InstructionAsserts.INVALID_OPERATOR_KEY}|{InstructionAsserts.INVALID_HOLDER_OWNER}" + with pytest.raises(solana.rpc.core.RPCException, match=error): + send_transaction_step_from_account( + second_operator_keypair, evm_loader, treasury_pool, new_holder_acc, + [user_account.solana_account_address, + user_account.balance_account_address, + rw_lock_contract.solana_address], + 500, second_operator_keypair + ) + + time.sleep(15) + send_transaction_step_from_account(second_operator_keypair, evm_loader, treasury_pool, new_holder_acc, + [user_account.solana_account_address, + user_account.balance_account_address, + rw_lock_contract.solana_address], 500, second_operator_keypair) + resp = send_transaction_step_from_account(second_operator_keypair, evm_loader, treasury_pool, new_holder_acc, + [user_account.solana_account_address, + user_account.balance_account_address, + rw_lock_contract.solana_address], 1, second_operator_keypair) + check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") diff --git a/integration/tests/neon_evm/test_transaction_step_from_account_no_chainid.py b/integration/tests/neon_evm/test_transaction_step_from_account_no_chainid.py new file mode 100644 index 0000000000..d9115e5360 --- /dev/null +++ b/integration/tests/neon_evm/test_transaction_step_from_account_no_chainid.py @@ -0,0 +1,120 @@ +import random +import re +import string +import solana + +import pytest +from eth_utils import to_text + +from .solana_utils import write_transaction_to_holder_account, \ + execute_transaction_steps_from_account_no_chain_id +from .utils.constants import TAG_FINALIZED_STATE +from .utils.contract import make_deployment_transaction, make_contract_call_trx, get_contract_bin +from .utils.ethereum import make_eth_transaction, create_contract_address +from .utils.layouts import FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT +from .utils.transaction_checks import check_holder_account_tag, check_transaction_logs_have_text + + +class TestTransactionStepFromAccountNoChainId: + + def test_simple_transfer_transaction(self, operator_keypair, treasury_pool, evm_loader, + sender_with_tokens, session_user, holder_acc): + amount = 10 + sender_balance_before = evm_loader.get_neon_balance(sender_with_tokens) + recipient_balance_before = evm_loader.get_neon_balance(session_user) + + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, amount, chain_id=None) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + resp = execute_transaction_steps_from_account_no_chain_id(operator_keypair, evm_loader, treasury_pool, + holder_acc, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.balance_account_address, + sender_with_tokens.solana_account_address], 0) + + sender_balance_after = evm_loader.get_neon_balance(sender_with_tokens) + recipient_balance_after = evm_loader.get_neon_balance(session_user) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") + assert sender_balance_before - amount == sender_balance_after + assert recipient_balance_before + amount == recipient_balance_after + + def test_deploy_contract(self, operator_keypair, holder_acc, treasury_pool, evm_loader, sender_with_tokens, + neon_api_client): + contract_filename = "hello_world" + contract = create_contract_address(sender_with_tokens, evm_loader) + + signed_tx = make_deployment_transaction(sender_with_tokens, contract_filename, chain_id=None) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + contract_code = get_contract_bin(contract_filename) + + steps_count = neon_api_client.get_steps_count(sender_with_tokens, None, contract_code) + resp = execute_transaction_steps_from_account_no_chain_id(operator_keypair, evm_loader, treasury_pool, + holder_acc, + [contract.solana_address, + contract.balance_account_address, + sender_with_tokens.balance_account_address, + sender_with_tokens.solana_account_address], + steps_count) + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x12") + + def test_call_contract_function_with_neon_transfer(self, operator_keypair, treasury_pool, + sender_with_tokens, string_setter_contract, holder_acc, + evm_loader, neon_api_client): + transfer_amount = random.randint(1, 1000) + + sender_balance_before = evm_loader.get_neon_balance(sender_with_tokens) + contract_balance_before = evm_loader.get_neon_balance(string_setter_contract.eth_address) + + text = ''.join(random.choice(string.ascii_letters) for _ in range(10)) + + signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text], + value=transfer_amount, chain_id=None) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + resp = execute_transaction_steps_from_account_no_chain_id(operator_keypair, evm_loader, treasury_pool, + holder_acc, + [string_setter_contract.solana_address, + string_setter_contract.balance_account_address, + sender_with_tokens.balance_account_address, + sender_with_tokens.solana_account_address] + ) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value.transaction.transaction.signatures[0], "exit_status=0x11") + + sender_balance_after = evm_loader.get_neon_balance(sender_with_tokens) + contract_balance_after = evm_loader.get_neon_balance(string_setter_contract.eth_address) + assert sender_balance_before - transfer_amount == sender_balance_after + assert contract_balance_before + transfer_amount == contract_balance_after + + assert text in to_text( + neon_api_client.call_contract_get_function(sender_with_tokens, string_setter_contract, "get()") + ) + + def test_transaction_with_access_list(self, operator_keypair, treasury_pool, + sender_with_tokens, calculator_contract, calculator_caller_contract, + holder_acc, evm_loader): + access_list = ( + { + "address": '0x' + calculator_contract.eth_address.hex(), + "storageKeys": ( + "0x0000000000000000000000000000000000000000000000000000000000000000", + ) + }, + ) + signed_tx = make_contract_call_trx(sender_with_tokens, calculator_caller_contract, "callCalculator()", [], + chain_id=None, access_list=access_list) + write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + + error = re.escape("assertion failed: trx.chain_id().is_none()") + with pytest.raises(solana.rpc.core.RPCException, match=error): + execute_transaction_steps_from_account_no_chain_id(operator_keypair, evm_loader, treasury_pool, + holder_acc, + [calculator_contract.solana_address, + calculator_caller_contract.solana_address, + sender_with_tokens.balance_account_address, + sender_with_tokens.solana_account_address] + ) diff --git a/integration/tests/neon_evm/test_transaction_step_from_instruction.py b/integration/tests/neon_evm/test_transaction_step_from_instruction.py new file mode 100644 index 0000000000..65ad2ccb44 --- /dev/null +++ b/integration/tests/neon_evm/test_transaction_step_from_instruction.py @@ -0,0 +1,609 @@ +import random +import string +import time + +import eth_abi +import pytest +import rlp +import solana +from eth_account.datastructures import SignedTransaction +from eth_keys import keys as eth_keys +from eth_utils import abi, to_text, to_int +from hexbytes import HexBytes +from solana.keypair import Keypair +from solana.publickey import PublicKey +from solana.rpc.commitment import Confirmed +from solana.rpc.core import RPCException + +from .solana_utils import execute_transaction_steps_from_instruction, \ + create_treasury_pool_address, send_transaction_step_from_instruction +from .utils.assert_messages import InstructionAsserts +from .utils.constants import TAG_FINALIZED_STATE +from .utils.contract import make_deployment_transaction, make_contract_call_trx, deploy_contract, get_contract_bin +from .utils.ethereum import make_eth_transaction, create_contract_address +from .utils.layouts import FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT +from .utils.storage import create_holder +from .utils.transaction_checks import check_transaction_logs_have_text, check_holder_account_tag +from .types.types import TreasuryPool + + +class TestTransactionStepFromInstruction: + + def test_simple_transfer_transaction(self, operator_keypair, treasury_pool, evm_loader, + sender_with_tokens, session_user, holder_acc): + amount = 10 + sender_balance_before = evm_loader.get_neon_balance(sender_with_tokens) + recipient_balance_before = evm_loader.get_neon_balance(session_user) + + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, amount) + + resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + sender_balance_after = evm_loader.get_neon_balance(sender_with_tokens) + recipient_balance_after = evm_loader.get_neon_balance(session_user) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value, "exit_status=0x11") + assert sender_balance_before - amount == sender_balance_after + assert recipient_balance_before + amount == recipient_balance_after + + @pytest.mark.parametrize("chain_id", [None, 111]) + def test_deploy_contract(self, operator_keypair, holder_acc, treasury_pool, evm_loader, sender_with_tokens, + chain_id, neon_api_client): + contract_filename = "small" + + signed_tx = make_deployment_transaction(sender_with_tokens, contract_filename, chain_id=chain_id) + contract = create_contract_address(sender_with_tokens, evm_loader) + + contract_code = get_contract_bin("small") + + steps_count = neon_api_client.get_steps_count(sender_with_tokens, None, contract_code) + resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, [contract.solana_address, + contract.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], + steps_count) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value, "exit_status=0x12") + + def test_call_contract_function_without_neon_transfer(self, operator_keypair, treasury_pool, sender_with_tokens, + evm_loader, holder_acc, string_setter_contract, + neon_api_client): + text = ''.join(random.choice(string.ascii_letters) for _ in range(10)) + signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text]) + + resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, [string_setter_contract.solana_address, + string_setter_contract.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address] + ) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value, "exit_status=0x11") + + assert text in to_text( + neon_api_client.call_contract_get_function(sender_with_tokens, string_setter_contract, + "get()")) + + def test_call_contract_function_with_neon_transfer(self, operator_keypair, treasury_pool, sender_with_tokens, + evm_loader, holder_acc, string_setter_contract, + neon_api_client): + transfer_amount = random.randint(1, 1000) + + sender_balance_before = evm_loader.get_neon_balance(sender_with_tokens) + contract_balance_before = evm_loader.get_neon_balance(string_setter_contract.eth_address) + + text = ''.join(random.choice(string.ascii_letters) for i in range(10)) + signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text], + value=transfer_amount) + + resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, [string_setter_contract.solana_address, + string_setter_contract.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address] + ) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value, "exit_status=0x11") + + sender_balance_after = evm_loader.get_neon_balance(sender_with_tokens) + contract_balance_after = evm_loader.get_neon_balance(string_setter_contract.eth_address) + assert sender_balance_before - transfer_amount == sender_balance_after + assert contract_balance_before + transfer_amount == contract_balance_after + + assert text in to_text( + neon_api_client.call_contract_get_function(sender_with_tokens, string_setter_contract, + "get()")) + + def test_transfer_transaction_with_non_existing_recipient(self, operator_keypair, treasury_pool, sender_with_tokens, + evm_loader, holder_acc): + # recipient account should be created + recipient = Keypair.generate() + recipient_ether = eth_keys.PrivateKey(recipient.secret_key[:32]).public_key.to_canonical_address() + recipient_solana_address, _ = evm_loader.ether2program(recipient_ether) + recipient_balance_address = evm_loader.ether2balance(recipient_ether) + amount = 10 + signed_tx = make_eth_transaction(recipient_ether, None, sender_with_tokens, amount) + + resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, [PublicKey(recipient_solana_address), + recipient_balance_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + recipient_balance_after = evm_loader.get_neon_balance(recipient_ether) + check_transaction_logs_have_text(resp.value, "exit_status=0x11") + + assert recipient_balance_after == amount + + def test_incorrect_chain_id(self, operator_keypair, holder_acc, treasury_pool, + sender_with_tokens, session_user, evm_loader): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1, chain_id=1) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_CHAIN_ID): + execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + def test_incorrect_nonce(self, operator_keypair, treasury_pool, sender_with_tokens, evm_loader, session_user, + holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + new_holder_acc = create_holder(operator_keypair) + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INVALID_NONCE): + execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, new_holder_acc, + signed_tx, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + def test_run_finalized_transaction(self, operator_keypair, treasury_pool, sender_with_tokens, evm_loader, + session_user, holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.TRX_ALREADY_FINALIZED): + execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + def test_insufficient_funds(self, operator_keypair, treasury_pool, evm_loader, session_user, + holder_acc, sender_with_tokens): + user_balance = evm_loader.get_neon_balance(session_user) + + signed_tx = make_eth_transaction(sender_with_tokens.eth_address, None, session_user, user_balance + 1) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.INSUFFICIENT_FUNDS): + execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + def test_gas_limit_reached(self, operator_keypair, treasury_pool, session_user, evm_loader, sender_with_tokens, + holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 10, gas=1) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.OUT_OF_GAS): + execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + def test_sender_missed_in_remaining_accounts(self, operator_keypair, treasury_pool, session_user, + sender_with_tokens, evm_loader, holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ADDRESS_MUST_BE_PRESENT): + execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, + [session_user.solana_account_address, + session_user.balance_account_address], 0) + + def test_recipient_missed_in_remaining_accounts(self, operator_keypair, treasury_pool, session_user, + sender_with_tokens, evm_loader, holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ADDRESS_MUST_BE_PRESENT): + execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, + [session_user.solana_account_address, + session_user.balance_account_address], 0) + + def test_incorrect_treasure_pool(self, operator_keypair, sender_with_tokens, evm_loader, session_user, holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + index = 2 + treasury = TreasuryPool(index, Keypair().generate().public_key, index.to_bytes(4, 'little')) + + error = str.format(InstructionAsserts.INVALID_ACCOUNT, treasury.account) + with pytest.raises(solana.rpc.core.RPCException, match=error): + execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury, holder_acc, + signed_tx, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + def test_incorrect_treasure_index(self, operator_keypair, sender_with_tokens, evm_loader, + session_user, holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + index = 2 + treasury = TreasuryPool(index, create_treasury_pool_address(index), (index + 1).to_bytes(4, 'little')) + + error = str.format(InstructionAsserts.INVALID_ACCOUNT, treasury.account) + with pytest.raises(solana.rpc.core.RPCException, match=error): + execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury, holder_acc, + signed_tx, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + def test_incorrect_operator_account(self, sender_with_tokens, evm_loader, treasury_pool, session_user, holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + fake_operator = Keypair().generate() + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.ACC_NOT_FOUND): + execute_transaction_steps_from_instruction(fake_operator, evm_loader, treasury_pool, holder_acc, + signed_tx, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0) + + def test_operator_is_not_in_white_list(self, sender_with_tokens, evm_loader, treasury_pool, + session_user, holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.NOT_AUTHORIZED_OPERATOR): + execute_transaction_steps_from_instruction(sender_with_tokens.solana_account, evm_loader, treasury_pool, + holder_acc, + signed_tx, + [session_user.solana_account_address, + session_user.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], 0, + signer=sender_with_tokens.solana_account) + + def test_incorrect_system_program(self, sender_with_tokens, operator_keypair, evm_loader, treasury_pool, + session_user, holder_acc): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + fake_sys_program_id = Keypair().generate().public_key + + with pytest.raises(solana.rpc.core.RPCException, + match=str.format(InstructionAsserts.NOT_SYSTEM_PROGRAM, fake_sys_program_id)): + send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, + [sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address, + session_user.solana_account_address, + session_user.balance_account_address], 1, operator_keypair, + system_program=fake_sys_program_id) + + def test_incorrect_holder_account(self, sender_with_tokens, operator_keypair, evm_loader, treasury_pool, + session_user): + signed_tx = make_eth_transaction(session_user.eth_address, None, sender_with_tokens, 1) + fake_holder_acc = Keypair.generate().public_key + with pytest.raises(solana.rpc.core.RPCException, + match=str.format(InstructionAsserts.NOT_PROGRAM_OWNED, fake_holder_acc)): + send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, fake_holder_acc, + signed_tx, + [sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address, + session_user.solana_account_address, + session_user.balance_account_address], 1, operator_keypair) + + @pytest.mark.parametrize("value", [0, 10]) + def test_transaction_with_access_list(self, operator_keypair, treasury_pool, sender_with_tokens, + evm_loader, holder_acc, + string_setter_contract, value): + access_list = ( + { + "address": '0x' + string_setter_contract.eth_address.hex(), + "storageKeys": ( + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001", + ) + }, + ) + signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", ["text"], + value=value, access_list=access_list) + resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, [string_setter_contract.solana_address, + string_setter_contract.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address] + ) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value, "exit_status=0x11") + + def test_deploy_contract_with_access_list(self, operator_keypair, holder_acc, treasury_pool, evm_loader, + sender_with_tokens, neon_api_client): + contract_filename = "small" + contract = create_contract_address(sender_with_tokens, evm_loader) + + access_list = ( + { + "address": contract.eth_address.hex(), + "storageKeys": ( + "0x0000000000000000000000000000000000000000000000000000000000000000", + ) + }, + ) + signed_tx = make_deployment_transaction(sender_with_tokens, contract_filename, access_list=access_list) + + contract_code = get_contract_bin(contract_filename) + + steps_count = neon_api_client.get_steps_count(sender_with_tokens, None, contract_code) + resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, [contract.solana_address, + contract.balance_account_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address], + steps_count) + check_transaction_logs_have_text(resp.value, "exit_status=0x12") + + +class TestInstructionStepContractCallContractInteractions: + def test_contract_call_unchange_storage_function(self, rw_lock_contract, session_user, evm_loader, operator_keypair, + treasury_pool, holder_acc, rw_lock_caller): + signed_tx = make_contract_call_trx(session_user, rw_lock_caller, 'unchange_storage(uint8,uint8)', [1, 1]) + resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, + [rw_lock_caller.solana_address, + rw_lock_contract.solana_address, + session_user.solana_account_address, + session_user.balance_account_address]) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value, "exit_status=0x12") + + def test_contract_call_set_function(self, rw_lock_contract, session_user, evm_loader, operator_keypair, + treasury_pool, holder_acc, rw_lock_caller, neon_api_client): + signed_tx = make_contract_call_trx(session_user, rw_lock_caller, 'update_storage_str(string)', ['hello']) + + resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, + [rw_lock_caller.solana_address, + rw_lock_contract.solana_address, + session_user.solana_account_address, + session_user.balance_account_address], 1000) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value, "exit_status=0x11") + + assert 'hello' in to_text(neon_api_client.call_contract_get_function(session_user, rw_lock_contract, + "get_text()")) + + def test_contract_call_get_function(self, rw_lock_contract, session_user, evm_loader, operator_keypair, + treasury_pool, holder_acc, rw_lock_caller): + signed_tx = make_contract_call_trx(session_user, rw_lock_caller, 'get_text()') + resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, + [rw_lock_caller.solana_address, + rw_lock_contract.solana_address, + session_user.solana_account_address, + session_user.balance_account_address], 1000) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value, "exit_status=0x12") + + def test_contract_call_update_storage_map_function(self, rw_lock_contract, session_user, evm_loader, + operator_keypair, rw_lock_caller, + treasury_pool, holder_acc, neon_api_client): + signed_tx = make_contract_call_trx(session_user, rw_lock_caller, 'update_storage_map(uint256)', [3]) + + func_name = abi.function_signature_to_4byte_selector('update_storage_map(uint256)') + data = func_name + eth_abi.encode(['uint256'], [3]) + result = neon_api_client.emulate(session_user.eth_address.hex(), + rw_lock_caller.eth_address.hex(), + data) + additional_accounts = [session_user.solana_account_address, + session_user.balance_account_address, + rw_lock_contract.solana_address, + rw_lock_caller.solana_address] + for acc in result['solana_accounts']: + additional_accounts.append(PublicKey(acc['pubkey'])) + + resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx, additional_accounts) + + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + check_transaction_logs_have_text(resp.value, "exit_status=0x11") + + constructor_args = eth_abi.encode(['address', 'uint256'], [rw_lock_caller.eth_address.hex(), 2]) + actual_data = neon_api_client.call_contract_get_function(session_user, rw_lock_contract, + "data(address,uint256)", constructor_args) + assert to_int(hexstr=actual_data) == 2, "Contract data is not correct" + + +class TestTransactionStepFromInstructionParallelRuns: + + def test_one_user_call_2_contracts(self, rw_lock_contract, string_setter_contract, user_account, evm_loader, + operator_keypair, treasury_pool, new_holder_acc): + signed_tx = make_contract_call_trx(user_account, rw_lock_contract, 'unchange_storage(uint8,uint8)', [1, 1]) + send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, new_holder_acc, signed_tx, + [user_account.solana_account_address, + user_account.balance_account_address, + rw_lock_contract.solana_address], 1, operator_keypair) + + signed_tx2 = make_contract_call_trx(user_account, string_setter_contract, 'get()') + holder_acc2 = create_holder(operator_keypair) + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.LOCKED_ACC): + send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc2, signed_tx2, + [user_account.solana_account_address, + user_account.balance_account_address, + string_setter_contract.solana_address], 1, operator_keypair) + + def test_2_users_call_the_same_contract(self, rw_lock_contract, user_account, + session_user, evm_loader, operator_keypair, + treasury_pool, new_holder_acc): + signed_tx = make_contract_call_trx(user_account, rw_lock_contract, 'unchange_storage(uint8,uint8)', [1, 1]) + + send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, new_holder_acc, signed_tx, + [user_account.solana_account_address, + user_account.balance_account_address, + rw_lock_contract.solana_address], 1, operator_keypair) + + signed_tx2 = make_contract_call_trx(session_user, rw_lock_contract, 'get_text()') + holder_acc2 = create_holder(operator_keypair) + + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.LOCKED_ACC): + send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc2, signed_tx2, + [session_user.solana_account_address, + session_user.balance_account_address, + rw_lock_contract.solana_address], 1, operator_keypair) + + def test_two_contracts_call_same_contract(self, rw_lock_contract, user_account, + session_user, evm_loader, operator_keypair, + treasury_pool, new_holder_acc): + constructor_args = eth_abi.encode(['address'], [rw_lock_contract.eth_address.hex()]) + + contract1 = deploy_contract(operator_keypair, session_user, "rw_lock", evm_loader, treasury_pool, + encoded_args=constructor_args, contract_name="rw_lock_caller") + contract2 = deploy_contract(operator_keypair, session_user, "rw_lock", evm_loader, treasury_pool, + encoded_args=constructor_args, contract_name="rw_lock_caller") + + signed_tx1 = make_contract_call_trx(user_account, contract1, 'unchange_storage(uint8,uint8)', [1, 1]) + signed_tx2 = make_contract_call_trx(session_user, contract2, 'get_text()') + + send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, new_holder_acc, signed_tx1, + [user_account.solana_account_address, + user_account.balance_account_address, + rw_lock_contract.solana_address, + contract1.solana_address], 1, operator_keypair) + + holder_acc2 = create_holder(operator_keypair) + with pytest.raises(solana.rpc.core.RPCException, match=InstructionAsserts.LOCKED_ACC): + send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc2, signed_tx2, + [session_user.solana_account_address, + session_user.balance_account_address, + rw_lock_contract.solana_address, + contract2.solana_address], 1, + operator_keypair) + + +class TestStepFromInstructionChangingOperatorsDuringTrxRun: + def test_next_operator_can_continue_trx_after_some_time(self, rw_lock_contract, user_account, evm_loader, + operator_keypair, second_operator_keypair, treasury_pool, + new_holder_acc): + signed_tx = make_contract_call_trx(user_account, rw_lock_contract, 'update_storage_str(string)', ['text']) + + send_transaction_step_from_instruction(operator_keypair, evm_loader, treasury_pool, new_holder_acc, + signed_tx, + [user_account.solana_account_address, + user_account.balance_account_address, + rw_lock_contract.solana_address], 1, operator_keypair) + # next operator can't continue trx during OPERATOR_PRIORITY_SLOTS*0.4 + with pytest.raises(solana.rpc.core.RPCException, + match=rf"{InstructionAsserts.INVALID_OPERATOR_KEY}|{InstructionAsserts.INVALID_HOLDER_OWNER}"): + send_transaction_step_from_instruction(second_operator_keypair, evm_loader, treasury_pool, new_holder_acc, + signed_tx, + [user_account.solana_account_address, + user_account.balance_account_address, + rw_lock_contract.solana_address], 500, second_operator_keypair) + + time.sleep(15) + send_transaction_step_from_instruction(second_operator_keypair, evm_loader, treasury_pool, new_holder_acc, + signed_tx, + [user_account.solana_account_address, + user_account.balance_account_address, + rw_lock_contract.solana_address], 500, second_operator_keypair) + resp = send_transaction_step_from_instruction(second_operator_keypair, evm_loader, treasury_pool, + new_holder_acc, signed_tx, + [user_account.solana_account_address, + user_account.balance_account_address, + rw_lock_contract.solana_address], 1, second_operator_keypair) + check_transaction_logs_have_text(resp.value, "exit_status=0x11") + + +class TestStepFromInstructionWithChangedRLPTrx: + def test_add_waste_to_trx(self, sender_with_tokens, operator_keypair, treasury_pool, evm_loader, holder_acc, + string_setter_contract): + text = ''.join(random.choice(string.ascii_letters) for _ in range(10)) + signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text]) + decoded_tx = rlp.decode(signed_tx.rawTransaction) + decoded_tx.insert(6, HexBytes(b'\x19p\x16l\xc0')) + new_trx = HexBytes(rlp.encode(decoded_tx)) + + signed_tx_new = SignedTransaction( + rawTransaction=new_trx, + hash=signed_tx.hash, + r=signed_tx.r, + s=signed_tx.s, + v=signed_tx.v, + ) + with pytest.raises(RPCException, match="Program log: RLP error: RlpIncorrectListLen"): + execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx_new, + [sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address, + string_setter_contract.solana_address]) + + def test_add_waste_to_trx_without_decoding(self, sender_with_tokens, operator_keypair, treasury_pool, evm_loader, + holder_acc, + string_setter_contract): + text = ''.join(random.choice(string.ascii_letters) for _ in range(10)) + signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text]) + signed_tx_new = SignedTransaction( + rawTransaction=signed_tx.rawTransaction + HexBytes(b'\x19p\x16l\xc0'), + hash=signed_tx.hash, + r=signed_tx.r, + s=signed_tx.s, + v=signed_tx.v, + ) + with pytest.raises(RPCException, match="Program log: RLP error: RlpInconsistentLengthAndData"): + execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx_new, + [sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address, + string_setter_contract.solana_address]) + + def test_old_trx_type_with_leading_zeros(self, sender_with_tokens, operator_keypair, evm_loader, + string_setter_contract, treasury_pool, holder_acc): + text = ''.join(random.choice(string.ascii_letters) for _ in range(10)) + + signed_tx = make_contract_call_trx(sender_with_tokens, string_setter_contract, "set(string)", [text]) + new_raw_trx = HexBytes(bytes([0]) + signed_tx.rawTransaction) + + signed_tx_new = SignedTransaction( + rawTransaction=new_raw_trx, + hash=signed_tx.hash, + r=signed_tx.r, + s=signed_tx.s, + v=signed_tx.v, + ) + + resp = execute_transaction_steps_from_instruction(operator_keypair, evm_loader, treasury_pool, holder_acc, + signed_tx_new, [string_setter_contract.solana_address, + sender_with_tokens.solana_account_address, + sender_with_tokens.balance_account_address] + ) + check_transaction_logs_have_text(resp.value, "exit_status=0x11") diff --git a/integration/tests/neon_evm/types/__init__.py b/integration/tests/neon_evm/types/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/integration/tests/neon_evm/types/types.py b/integration/tests/neon_evm/types/types.py new file mode 100644 index 0000000000..e1001f6e58 --- /dev/null +++ b/integration/tests/neon_evm/types/types.py @@ -0,0 +1,26 @@ +from dataclasses import dataclass +from solana.publickey import PublicKey +from solana.keypair import Keypair + + +@dataclass +class TreasuryPool: + index: int + account: PublicKey + buffer: bytes + + +@dataclass +class Caller: + solana_account: Keypair + solana_account_address: PublicKey + balance_account_address: PublicKey + eth_address: bytes + token_address: PublicKey + + +@dataclass +class Contract: + eth_address: bytes + solana_address: PublicKey + balance_account_address: PublicKey diff --git a/integration/tests/neon_evm/utils/__init__.py b/integration/tests/neon_evm/utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/integration/tests/neon_evm/utils/assert_messages.py b/integration/tests/neon_evm/utils/assert_messages.py new file mode 100644 index 0000000000..43511cd81c --- /dev/null +++ b/integration/tests/neon_evm/utils/assert_messages.py @@ -0,0 +1,18 @@ +class InstructionAsserts: + LOCKED_ACC = "trying to execute transaction on rw locked account" + INVALID_CHAIN_ID = "Invalid Chain ID" + INVALID_NONCE = "Invalid Nonce" + TRX_ALREADY_FINALIZED = "Transaction already finalized" + INSUFFICIENT_FUNDS = "Insufficient balance" + OUT_OF_GAS = "Out of Gas" + ADDRESS_MUST_BE_PRESENT = r"address .* must be present in the transaction" + INVALID_ACCOUNT = "Account {} - invalid public key" + ACC_NOT_FOUND = "AccountNotFound" + NOT_AUTHORIZED_OPERATOR = "Operator is not authorized" + NOT_SYSTEM_PROGRAM = "Account {} - is not system program" + NOT_NEON_PROGRAM = "Account {} - is not Neon program" + NOT_PROGRAM_OWNED = "Account {} - invalid owner" + INVALID_HOLDER_OWNER = "Holder Account - invalid owner" + INVALID_OPERATOR_KEY = "operator.key != storage.operator" + HOLDER_OVERFLOW = "Checked Integer Math Overflow" + HOLDER_INSUFFICIENT_SIZE = "Holder Account - insufficient size" diff --git a/integration/tests/neon_evm/utils/constants.py b/integration/tests/neon_evm/utils/constants.py new file mode 100644 index 0000000000..6e38ce18b0 --- /dev/null +++ b/integration/tests/neon_evm/utils/constants.py @@ -0,0 +1,26 @@ +import os +from solana.publickey import PublicKey + + +SYSTEM_ADDRESS = "11111111111111111111111111111111" +TOKEN_KEG_ADDRESS = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" +SYSVAR_CLOCK_ADDRESS = "SysvarC1ock11111111111111111111111111111111" +SYS_INSTRUCT_ADDRESS = "Sysvar1nstructions1111111111111111111111111" +KECCAKPROG_ADDRESS = "KeccakSecp256k11111111111111111111111111111" +RENT_ID_ADDRESS = "SysvarRent111111111111111111111111111111111" +INCINERATOR_ADDRESS = "1nc1nerator11111111111111111111111111111111" +TREASURY_POOL_SEED = os.environ.get("NEON_TREASURY_POOL_SEED", "treasury_pool") +TREASURY_POOL_COUNT = os.environ.get("NEON_TREASURY_POOL_COUNT", 128) +COMPUTE_BUDGET_ID: PublicKey = PublicKey("ComputeBudget111111111111111111111111111111") + +ACCOUNT_SEED_VERSION = b'\3' + +TAG_EMPTY = 0 +TAG_STATE = 23 +TAG_FINALIZED_STATE = 32 +TAG_HOLDER = 52 + +SOLANA_URL = os.environ.get("SOLANA_URL", "http://solana:8899") +EVM_LOADER = os.environ.get("EVM_LOADER", "53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io") +NEON_TOKEN_MINT_ID: PublicKey = PublicKey(os.environ.get("NEON_TOKEN_MINT", "HPsV9Deocecw3GeZv1FkAPNCBRfuVyfw9MMwjwRe1xaU")) +CHAIN_ID = int(os.environ.get("NEON_CHAIN_ID", 111)) diff --git a/integration/tests/neon_evm/utils/contract.py b/integration/tests/neon_evm/utils/contract.py new file mode 100644 index 0000000000..963e2c9337 --- /dev/null +++ b/integration/tests/neon_evm/utils/contract.py @@ -0,0 +1,137 @@ +import typing as tp +import pathlib + +import eth_abi +import solcx +from eth_account.datastructures import SignedTransaction +from eth_utils import abi +from solana.keypair import Keypair + +from ..types.types import Caller, Contract, TreasuryPool +from ..solana_utils import \ + EvmLoader, write_transaction_to_holder_account, \ + send_transaction_step_from_account +from .storage import create_holder +from .ethereum import create_contract_address, make_eth_transaction + +from web3.auto import w3 + + +def get_contract_bin( + contract: str, + contract_name: tp.Optional[str] = None, +): + version = '0.7.6' + if not contract.endswith(".sol"): + contract += ".sol" + if contract_name is None: + if "/" in contract: + contract_name = contract.rsplit("/", 1)[1].rsplit(".", 1)[0] + else: + contract_name = contract.rsplit(".", 1)[0] + + if version not in [str(v) for v in solcx.get_installed_solc_versions()]: + solcx.install_solc(version) + + contract_path = (pathlib.Path.cwd() / "contracts" / "neon_evm" / f"{contract}").absolute() + if not contract_path.exists(): + contract_path = (pathlib.Path.cwd() / "contracts" / "external" / f"{contract}").absolute() + + assert contract_path.exists(), f"Can't found contract: {contract_path}" + + compiled = solcx.compile_files( + [contract_path], + output_values=["abi", "bin"], + solc_version=version, + allow_paths=["."], + optimize=True, + ) + contract_abi = None + for key in compiled.keys(): + if contract_name == key.rsplit(":")[-1]: + contract_abi = compiled[key] + break + + return contract_abi['bin'] + + +def make_deployment_transaction( + user: Caller, + contract_file_name: tp.Union[pathlib.Path, str], + contract_name: tp.Optional[str] = None, + encoded_args=None, + gas: int = 999999999, chain_id=111, access_list=None +) -> SignedTransaction: + data = get_contract_bin(contract_file_name, contract_name) + if encoded_args is not None: + data = data + encoded_args.hex() + + nonce = EvmLoader(user.solana_account).get_neon_nonce(user.eth_address) + tx = { + 'to': None, + 'value': 0, + 'gas': gas, + 'gasPrice': 0, + 'nonce': nonce, + 'data': data + } + if chain_id: + tx['chainId'] = chain_id + if access_list: + tx['accessList'] = access_list + tx['type'] = 1 + + return w3.eth.account.sign_transaction(tx, user.solana_account.secret_key[:32]) + + +def make_contract_call_trx(user, contract, function_signature, params=None, value=0, chain_id=111, access_list=None, + trx_type=None): + data = abi.function_signature_to_4byte_selector(function_signature) + + if params is not None: + for param in params: + if isinstance(param, int): + data += eth_abi.encode(['uint256'], [param]) + elif isinstance(param, str): + data += eth_abi.encode(['string'], [param]) + + signed_tx = make_eth_transaction(contract.eth_address, data, user, value=value, + chain_id=chain_id, access_list=access_list, type=trx_type) + return signed_tx + + +def deploy_contract( + operator: Keypair, + user: Caller, + contract_file_name: tp.Union[pathlib.Path, str], + evm_loader: EvmLoader, + treasury_pool: TreasuryPool, + step_count: int = 1000, + encoded_args=None, + contract_name: tp.Optional[str] = None +): + print("Deploying contract") + contract: Contract = create_contract_address(user, evm_loader) + holder_acc = create_holder(operator) + signed_tx = make_deployment_transaction(user, contract_file_name, contract_name, encoded_args=encoded_args) + write_transaction_to_holder_account(signed_tx, holder_acc, operator) + + index = 0 + contract_deployed = False + while not contract_deployed: + receipt = send_transaction_step_from_account(operator, evm_loader, treasury_pool, holder_acc, + [contract.solana_address, + contract.balance_account_address, + user.balance_account_address], + step_count, operator, index=index) + index += 1 + + if receipt.value.transaction.meta.err: + raise AssertionError(f"Can't deploy contract: {receipt.value.transaction.meta.err}") + for log in receipt.value.transaction.meta.log_messages: + if "exit_status" in log: + contract_deployed = True + break + if "ExitError" in log: + raise AssertionError(f"EVM Return error in logs: {receipt}") + return contract diff --git a/integration/tests/neon_evm/utils/eth_tx_utils.py b/integration/tests/neon_evm/utils/eth_tx_utils.py new file mode 100644 index 0000000000..c68d8f277c --- /dev/null +++ b/integration/tests/neon_evm/utils/eth_tx_utils.py @@ -0,0 +1,225 @@ +from sha3 import keccak_256 +import json +from web3.auto import w3 +from eth_keys import keys +import struct + + +def unpack(data): + ch = data[0] + if ch <= 0x7F: + return ch, data[1:] + elif ch == 0x80: + return None, data[1:] + elif ch <= 0xB7: + l = ch - 0x80 + return data[1:1 + l].tobytes(), data[1 + l:] + elif ch <= 0xBF: + lLen = ch - 0xB7 + l = int.from_bytes(data[1:1 + lLen], byteorder='big') + return data[1 + lLen:1 + lLen + l].tobytes(), data[1 + lLen + l:] + elif ch == 0xC0: + return (), data[1:] + elif ch <= 0xF7: + l = ch - 0xC0 + lst = list() + sub = data[1:1 + l] + while len(sub): + (item, sub) = unpack(sub) + lst.append(item) + return lst, data[1 + l:] + else: + lLen = ch - 0xF7 + l = int.from_bytes(data[1:1 + lLen], byteorder='big') + lst = list() + sub = data[1 + lLen:1 + lLen + l] + while len(sub): + (item, sub) = unpack(sub) + lst.append(item) + return lst, data[1 + lLen + l:] + + +def pack(data): + if data is None: + return (0x80).to_bytes(1, 'big') + if isinstance(data, str): + return pack(data.encode('utf8')) + elif isinstance(data, bytes): + if len(data) <= 55: + return (len(data) + 0x80).to_bytes(1, 'big') + data + else: + l = len(data) + lLen = (l.bit_length() + 7) // 8 + return (0xB7 + lLen).to_bytes(1, 'big') + l.to_bytes(lLen, 'big') + data + elif isinstance(data, int): + if data < 0x80: + return data.to_bytes(1, 'big') + else: + l = (data.bit_length() + 7) // 8 + return (l + 0x80).to_bytes(1, 'big') + data.to_bytes(l, 'big') + pass + elif isinstance(data, list) or isinstance(data, tuple): + if len(data) == 0: + return (0xC0).to_bytes(1, 'big') + else: + res = bytearray() + for d in data: + res += pack(d) + l = len(res) + if l <= 55: + return (l + 0xC0).to_bytes(1, 'big') + res + else: + lLen = (l.bit_length() + 7) // 8 + return (lLen + 0xF7).to_bytes(1, 'big') + l.to_bytes(lLen, 'big') + res + else: + raise Exception("Unknown type {} of data".format(str(type(data)))) + + +def get_int(a): + if isinstance(a, int): + return a + if isinstance(a, bytes): + return int.from_bytes(a, 'big') + if a is None: + return a + raise Exception("Invalid convertion from {} to int".format(a)) + + +class Trx: + def __init__(self): + self.nonce = None + self.gasPrice = None + self.gasLimit = None + self.toAddress = None + self.value = None + self.callData = None + self.v = None + self.r = None + self.s = None + + @classmethod + def from_string(cls, s): + t = Trx() + (unpacked, data) = unpack(memoryview(s)) + (nonce, gasPrice, gasLimit, toAddress, value, callData, v, r, s) = unpacked + t.nonce = get_int(nonce) + t.gasPrice = get_int(gasPrice) + t.gasLimit = get_int(gasLimit) + t.toAddress = toAddress + t.value = get_int(value) + t.callData = callData + t.v = get_int(v) + t.r = get_int(r) + t.s = get_int(s) + return t + + def chain_id(self): + # chainid*2 + 35 xxxxx0 + 100011 xxxx0 + 100010 +1 + # chainid*2 + 36 xxxxx0 + 100100 xxxx0 + 100011 +1 + return (self.v - 1) // 2 - 17 + + def __str__(self): + return pack(( + self.nonce, + self.gasPrice, + self.gasLimit, + self.toAddress, + self.value, + self.callData, + self.v, + self.r.to_bytes(32, 'big') if self.r else None, + self.s.to_bytes(32, 'big') if self.s else None) + ).hex() + + def get_msg(self, chain_id=None): + return pack(( + self.nonce, + self.gasPrice, + self.gasLimit, + self.toAddress, + self.value, + self.callData, + chain_id or self.chain_id(), None, None)) + + def hash(self, chain_id=None): + trx = pack(( + self.nonce, + self.gasPrice, + self.gasLimit, + self.toAddress, + self.value, + self.callData, + chain_id or self.chain_id(), None, None)) + return keccak_256(trx).digest() + + def sender(self): + msg_hash = self.hash() + sig = keys.Signature(vrs=[1 if self.v % 2 == 0 else 0, self.r, self.s]) + pub = sig.recover_public_key_from_msg_hash(msg_hash) + return pub.to_canonical_address().hex() + + +class JsonEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, bytes): + return obj.hex() + return json.JSONEncoder.default(obj) + + +def make_instruction_data_from_tx(instruction, private_key=None): + if isinstance(instruction, dict): + if instruction['chainId'] is None: + raise Exception("chainId value is needed in input dict") + if private_key is None: + raise Exception("Needed private key for transaction creation from fields") + + signed_tx = w3.eth.account.sign_transaction(instruction, private_key) + _trx = Trx.from_string(signed_tx.rawTransaction) + + raw_msg = _trx.get_msg(instruction['chainId']) + sig = keys.Signature(vrs=[1 if _trx.v % 2 == 0 else 0, _trx.r, _trx.s]) + pub = sig.recover_public_key_from_msg_hash(_trx.hash()) + + + return pub.to_canonical_address(), sig.to_bytes(), raw_msg + elif isinstance(instruction, str): + if instruction[:2] == "0x": + instruction = instruction[2:] + + _trx = Trx.from_string(bytearray.fromhex(instruction)) + # print(json.dumps(_trx.__dict__, cls=JsonEncoder, indent=3)) + + raw_msg = _trx.get_msg() + sig = keys.Signature(vrs=[1 if _trx.v % 2 == 0 else 0, _trx.r, _trx.s]) + pub = sig.recover_public_key_from_msg_hash(_trx.hash()) + + data = pub.to_canonical_address() + data += sig.to_bytes() + data += raw_msg + + return pub.to_canonical_address(), sig.to_bytes(), raw_msg + else: + raise Exception("function gets ") + + +def make_keccak_instruction_data(check_instruction_index, msg_len, data_start): + if 255 < check_instruction_index < 0: + raise Exception("Invalid index for instruction - {}".format(check_instruction_index)) + + check_count = 1 + eth_address_size = 20 + signature_size = 65 + eth_address_offset = data_start + signature_offset = eth_address_offset + eth_address_size + message_data_offset = signature_offset + signature_size + + data = struct.pack("B", check_count) + data += struct.pack(" Contract: + # Create contract address from (caller_address, nonce) + user_nonce = evm_loader.get_neon_nonce(user.eth_address) + contract_eth_address = keccak_256(pack([user.eth_address, user_nonce or None])).digest()[-20:] + contract_solana_address, _ = evm_loader.ether2program(contract_eth_address) + contract_neon_address = evm_loader.ether2balance(contract_eth_address) + + print(f"Contract addresses: " + f" eth {contract_eth_address.hex()}, " + f" solana {contract_solana_address}") + + return Contract(contract_eth_address, PublicKey(contract_solana_address), contract_neon_address) + + +def make_eth_transaction(to_addr: bytes, data: Union[bytes, None], caller: Caller, + value: int = 0, chain_id=CHAIN_ID, gas=9999999999, access_list=None, type=None): + + nonce = EvmLoader(caller.solana_account).get_neon_nonce(caller.eth_address) + tx = {'to': to_addr, 'value': value, 'gas': gas, 'gasPrice': 0, + 'nonce': nonce} + + if chain_id is not None: + tx['chainId'] = chain_id + + if data is not None: + tx['data'] = data + + if access_list is not None: + tx['accessList'] = access_list + if type is not None: + tx['type'] = type + return w3.eth.account.sign_transaction(tx, caller.solana_account.secret_key[:32]) diff --git a/integration/tests/neon_evm/utils/instructions.py b/integration/tests/neon_evm/utils/instructions.py new file mode 100644 index 0000000000..982192843c --- /dev/null +++ b/integration/tests/neon_evm/utils/instructions.py @@ -0,0 +1,244 @@ +import typing as tp + +from eth_keys import keys as eth_keys +from solana.keypair import Keypair +from solana.publickey import PublicKey +import solana.system_program as sp +from solana.transaction import AccountMeta, TransactionInstruction, Transaction + +from .constants import EVM_LOADER +from solana.system_program import SYS_PROGRAM_ID +from solana.sysvar import SYSVAR_RENT_PUBKEY +from spl.token.constants import ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID +from spl.token.instructions import get_associated_token_address + +from ..types.types import TreasuryPool + +DEFAULT_UNITS = 1_400_000 +DEFAULT_HEAP_FRAME = 256 * 1024 +DEFAULT_ADDITIONAL_FEE = 0 +COMPUTE_BUDGET_ID: PublicKey = PublicKey("ComputeBudget111111111111111111111111111111") + + +class ComputeBudget: + @staticmethod + def request_units(operator, units, additional_fee): + return TransactionInstruction( + program_id=COMPUTE_BUDGET_ID, + keys=[AccountMeta(PublicKey(operator.public_key), is_signer=True, is_writable=False)], + data=bytes.fromhex("02") + units.to_bytes(4, "little") # + additional_fee.to_bytes(4, "little") + ) + + @staticmethod + def request_heap_frame(operator, heap_frame): + return TransactionInstruction( + program_id=COMPUTE_BUDGET_ID, + keys=[AccountMeta(PublicKey(operator.public_key), is_signer=True, is_writable=False)], + data=bytes.fromhex("01") + heap_frame.to_bytes(4, "little") + ) + + +class TransactionWithComputeBudget(Transaction): + def __init__(self, + operator: Keypair, + units=DEFAULT_UNITS, + additional_fee=DEFAULT_ADDITIONAL_FEE, + heap_frame=DEFAULT_HEAP_FRAME, + *args, **kwargs): + super().__init__(*args, **kwargs) + if units: + self.add(ComputeBudget.request_units(operator, units, additional_fee)) + if heap_frame: + self.add(ComputeBudget.request_heap_frame(operator, heap_frame)) + + +def write_holder_layout(hash: bytes, offset: int, data: bytes): + assert (len(hash) == 32) + return ( + bytes([0x26]) + + hash + + offset.to_bytes(8, byteorder="little") + + data + ) + + +def make_WriteHolder(operator: PublicKey, holder_account: PublicKey, hash: bytes, offset: int, payload: bytes): + d = write_holder_layout(hash, offset, payload) + + return TransactionInstruction( + program_id=PublicKey(EVM_LOADER), + data=d, + keys=[ + AccountMeta(pubkey=holder_account, is_signer=False, is_writable=True), + AccountMeta(pubkey=operator, is_signer=True, is_writable=False), + ]) + + +def make_ExecuteTrxFromInstruction( + operator: Keypair, + evm_loader: "EvmLoader", + treasury_address: PublicKey, + treasury_buffer: bytes, + message: bytes, + additional_accounts: tp.List[PublicKey], + system_program=sp.SYS_PROGRAM_ID, +): + data = bytes([0x32]) + treasury_buffer + message + operator_ether = eth_keys.PrivateKey(operator.secret_key[:32]).public_key.to_canonical_address() + print("make_ExecuteTrxFromInstruction accounts") + print("Operator: ", operator.public_key) + print("Treasury: ", treasury_address) + print("Operator ether: ", operator_ether.hex()) + print("Operator eth solana: ", evm_loader.ether2balance(operator_ether)) + accounts = [ + AccountMeta(pubkey=operator.public_key, is_signer=True, is_writable=True), + AccountMeta(pubkey=treasury_address, is_signer=False, is_writable=True), + AccountMeta(pubkey=PublicKey(evm_loader.ether2balance(operator_ether)), is_signer=False, is_writable=True), + AccountMeta(system_program, is_signer=False, is_writable=True), + ] + for acc in additional_accounts: + print("Additional acc ", acc) + accounts.append(AccountMeta(acc, is_signer=False, is_writable=True), ) + + return TransactionInstruction( + program_id=PublicKey(EVM_LOADER), + data=data, + keys=accounts + ) + + +def make_ExecuteTrxFromAccountDataIterativeOrContinue( + index: int, + step_count: int, + operator: Keypair, + evm_loader: "EvmLoader", + holder_address: PublicKey, + treasury: TreasuryPool, + additional_accounts: tp.List[PublicKey], + sys_program_id=sp.SYS_PROGRAM_ID, + tag=0x35): + # 0x35 - TransactionStepFromAccount + # 0x36 - TransactionStepFromAccountNoChainId + data = tag.to_bytes(1, "little") + treasury.buffer + step_count.to_bytes(4, "little") + index.to_bytes(4, "little") + operator_ether = eth_keys.PrivateKey(operator.secret_key[:32]).public_key.to_canonical_address() + print("make_ExecuteTrxFromAccountDataIterativeOrContinue accounts") + print("Holder: ", holder_address) + print("Operator: ", operator.public_key) + print("Treasury: ", treasury.account) + print("Operator ether: ", operator_ether.hex()) + print("Operator eth solana: ", evm_loader.ether2balance(operator_ether)) + accounts = [ + AccountMeta(pubkey=holder_address, is_signer=False, is_writable=True), + AccountMeta(pubkey=operator.public_key, is_signer=True, is_writable=True), + AccountMeta(pubkey=treasury.account, is_signer=False, is_writable=True), + AccountMeta(pubkey=PublicKey(evm_loader.ether2balance(operator_ether)), is_signer=False, is_writable=True), + AccountMeta(sys_program_id, is_signer=False, is_writable=True), + ] + + for acc in additional_accounts: + print("Additional acc ", acc) + accounts.append(AccountMeta(acc, is_signer=False, is_writable=True), ) + + return TransactionInstruction( + program_id=PublicKey(EVM_LOADER), + data=data, + keys=accounts + ) + + +def make_PartialCallOrContinueFromRawEthereumTX( + index: int, + step_count: int, + instruction: bytes, + operator: Keypair, + evm_loader: "EvmLoader", + storage_address: PublicKey, + treasury: TreasuryPool, + additional_accounts: tp.List[PublicKey], + system_program=sp.SYS_PROGRAM_ID): + data = bytes([0x34]) + treasury.buffer + step_count.to_bytes(4, "little") + index.to_bytes(4, "little") + instruction + operator_ether = eth_keys.PrivateKey(operator.secret_key[:32]).public_key.to_canonical_address() + + accounts = [ + AccountMeta(pubkey=storage_address, is_signer=False, is_writable=True), + AccountMeta(pubkey=operator.public_key, is_signer=True, is_writable=True), + AccountMeta(pubkey=treasury.account, is_signer=False, is_writable=True), + AccountMeta(pubkey=evm_loader.ether2balance(operator_ether), is_signer=False, is_writable=True), + AccountMeta(system_program, is_signer=False, is_writable=True), + ] + for acc in additional_accounts: + accounts.append(AccountMeta(acc, is_signer=False, is_writable=True), ) + + return TransactionInstruction( + program_id=PublicKey(EVM_LOADER), + data=data, + keys=accounts + ) + + +def make_Cancel(evm_loader: "EvmLoader", storage_address: PublicKey, operator: Keypair, hash: bytes, additional_accounts: tp.List[PublicKey]): + data = bytes([0x37]) + hash + operator_ether = eth_keys.PrivateKey(operator.secret_key[:32]).public_key.to_canonical_address() + + accounts = [ + AccountMeta(pubkey=storage_address, is_signer=False, is_writable=True), + AccountMeta(pubkey=operator.public_key, is_signer=True, is_writable=True), + AccountMeta(pubkey=evm_loader.ether2balance(operator_ether), is_signer=False, is_writable=True), + ] + + for acc in additional_accounts: + accounts.append(AccountMeta(acc, is_signer=False, is_writable=True), ) + + return TransactionInstruction( + program_id=PublicKey(EVM_LOADER), + data=data, + keys=accounts + ) + + +def make_DepositV03( + ether_address: bytes, + chain_id: int, + balance_account: PublicKey, + contract_account: PublicKey, + mint: PublicKey, + source: PublicKey, + pool: PublicKey, + token_program: PublicKey, + operator_pubkey: PublicKey, +) -> TransactionInstruction: + data = bytes([0x31]) + ether_address + chain_id.to_bytes(8, 'little') + + accounts = [ + AccountMeta(pubkey=mint, is_signer=False, is_writable=True), + AccountMeta(pubkey=source, is_signer=False, is_writable=True), + AccountMeta(pubkey=pool, is_signer=False, is_writable=True), + AccountMeta(pubkey=balance_account, is_signer=False, is_writable=True), + AccountMeta(pubkey=contract_account, is_signer=False, is_writable=True), + AccountMeta(pubkey=token_program, is_signer=False, is_writable=False), + AccountMeta(pubkey=operator_pubkey, is_signer=True, is_writable=True), + AccountMeta(pubkey=sp.SYS_PROGRAM_ID, is_signer=False, is_writable=False), + ] + + return TransactionInstruction(program_id=PublicKey(EVM_LOADER), data=data, keys=accounts) + +def make_CreateAssociatedTokenIdempotent(payer: PublicKey, owner: PublicKey, mint: PublicKey) -> TransactionInstruction: + """Creates a transaction instruction to create an associated token account. + + Returns: + The instruction to create the associated token account. + """ + associated_token_address = get_associated_token_address(owner, mint) + return TransactionInstruction( + data=bytes([1]), + keys=[ + AccountMeta(pubkey=payer, is_signer=True, is_writable=True), + AccountMeta(pubkey=associated_token_address, is_signer=False, is_writable=True), + AccountMeta(pubkey=owner, is_signer=False, is_writable=False), + AccountMeta(pubkey=mint, is_signer=False, is_writable=False), + AccountMeta(pubkey=SYS_PROGRAM_ID, is_signer=False, is_writable=False), + AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), + AccountMeta(pubkey=SYSVAR_RENT_PUBKEY, is_signer=False, is_writable=False), + ], + program_id=ASSOCIATED_TOKEN_PROGRAM_ID, + ) \ No newline at end of file diff --git a/integration/tests/neon_evm/utils/layouts.py b/integration/tests/neon_evm/utils/layouts.py new file mode 100644 index 0000000000..461358bd0b --- /dev/null +++ b/integration/tests/neon_evm/utils/layouts.py @@ -0,0 +1,50 @@ +from construct import Bytes, Int8ul, Struct, Int64ul, Int32ul + +STORAGE_ACCOUNT_INFO_LAYOUT = Struct( + "tag" / Int8ul, + "blocked" / Int8ul, + "owner" / Bytes(32), + "hash" / Bytes(32), + "caller" / Bytes(20), + "chain_id" / Int64ul, + "gas_limit" / Bytes(32), + "gas_price" / Bytes(32), + "gas_used" / Bytes(32), + "operator" / Bytes(32), + "slot" / Int64ul, + "account_list_len" / Int64ul, +) + +HOLDER_ACCOUNT_INFO_LAYOUT = Struct( + "tag" / Int8ul, + "blocked" / Int8ul, + "owner" / Bytes(32), + "hash" / Bytes(32), + "len" / Int64ul +) + + +FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT = Struct( + "tag" / Int8ul, + "blocked" / Int8ul, + "owner" / Bytes(32), + "hash" / Bytes(32), +) + + +CONTRACT_ACCOUNT_LAYOUT = Struct( + "type" / Int8ul, + "blocked" / Int8ul, + "address" / Bytes(20), + "chain_id" / Int64ul, + "generation" / Int32ul, +) + +BALANCE_ACCOUNT_LAYOUT = Struct( + "type" / Int8ul, + "blocked" / Int8ul, + "address" / Bytes(20), + "chain_id" / Int64ul, + "trx_count" / Int64ul, + "balance" / Bytes(32), +) \ No newline at end of file diff --git a/integration/tests/neon_evm/utils/neon_api_client.py b/integration/tests/neon_evm/utils/neon_api_client.py new file mode 100644 index 0000000000..716c117b1d --- /dev/null +++ b/integration/tests/neon_evm/utils/neon_api_client.py @@ -0,0 +1,68 @@ +import requests +from eth_utils import abi + +from .constants import CHAIN_ID +from ..types.types import Caller, Contract + + +class NeonApiClient: + def __init__(self, url): + self.url = url + self.headers = {"Content-Type": "application/json"} + + def emulate(self, sender, contract, data=bytes(), chain_id=CHAIN_ID, value='0x0', max_steps_to_execute=500000): + if isinstance(data, bytes): + data = data.hex() + body = { + "step_limit": max_steps_to_execute, + "tx": { + "from": sender, + "to": contract, + "data": data, + "chain_id": chain_id, + "value": value + }, + "accounts": [] + } + print(body) + resp = requests.post(url=f"{self.url}/emulate", json=body, headers=self.headers) + print(resp.text) + if resp.status_code == 200: + return resp.json()["value"] + else: + return resp.json() + + + def get_storage_at(self, contract_id, index="0x0"): + body = { + "contract": contract_id, + "index": index + } + return requests.post(url=f"{self.url}/storage", json=body, headers=self.headers).json() + + def get_ether_account_data(self, ether, chain_id = CHAIN_ID): + body = { + "account": [ + { "address": ether, "chain_id": chain_id } + ] + } + return requests.post(url=f"{self.url}/balance", json=body, headers=self.headers).json() + + def call_contract_get_function(self, sender, contract, function_signature: str, + constructor_args=None): + data = abi.function_signature_to_4byte_selector(function_signature) + if constructor_args is not None: + data += constructor_args + result = self.emulate(sender.eth_address.hex(), contract.eth_address.hex(), data) + print(result) + return result["result"] + + def get_steps_count(self, from_acc, to, data): + if isinstance(to, (Caller, Contract)): + to = to.eth_address.hex() + result = self.emulate( + from_acc.eth_address.hex(), + to, + data + ) + return result["steps_executed"] diff --git a/integration/tests/neon_evm/utils/storage.py b/integration/tests/neon_evm/utils/storage.py new file mode 100644 index 0000000000..4876d459bb --- /dev/null +++ b/integration/tests/neon_evm/utils/storage.py @@ -0,0 +1,47 @@ +from hashlib import sha256 +from random import randrange + +from solana.publickey import PublicKey +from solana.keypair import Keypair +from ..solana_utils import create_holder_account, get_solana_balance, create_account_with_seed, \ + send_transaction, solana_client +from solana.transaction import Transaction, TransactionInstruction, AccountMeta +from .constants import EVM_LOADER + + +def create_holder(signer: Keypair, seed: str = None, size: int = None, fund: int = None, + storage: PublicKey = None) -> PublicKey: + if size is None: + size = 128 * 1024 + if fund is None: + fund = 10 ** 9 + if seed is None: + seed = str(randrange(1000000)) + if storage is None: + storage = PublicKey( + sha256(bytes(signer.public_key) + bytes(seed, 'utf8') + bytes(PublicKey(EVM_LOADER))).digest()) + + print(f"Create holder account with seed: {seed}") + + if get_solana_balance(storage) == 0: + trx = Transaction() + trx.add( + create_account_with_seed(signer.public_key, signer.public_key, seed, fund, size), + create_holder_account(storage, signer.public_key, bytes(seed, 'utf8')) + ) + send_transaction(solana_client, trx, signer) + print(f"Created holder account: {storage}") + return storage + + +def delete_holder(del_key: PublicKey, acc: Keypair, signer: Keypair): + trx = Transaction() + + trx.add(TransactionInstruction( + program_id=PublicKey(EVM_LOADER), + data=bytes.fromhex("25"), + keys=[ + AccountMeta(pubkey=del_key, is_signer=False, is_writable=True), + AccountMeta(pubkey=acc.public_key, is_signer=(signer == acc), is_writable=True), + ])) + return send_transaction(solana_client, trx, signer) diff --git a/integration/tests/neon_evm/utils/transaction_checks.py b/integration/tests/neon_evm/utils/transaction_checks.py new file mode 100644 index 0000000000..60e2b8f9c4 --- /dev/null +++ b/integration/tests/neon_evm/utils/transaction_checks.py @@ -0,0 +1,28 @@ +import base64 + +from solana.rpc.commitment import Confirmed + +from ..solana_utils import solana_client + + +def check_transaction_logs_have_text(trx_hash, text): + + receipt = solana_client.get_transaction(trx_hash) + logs = "" + for log in receipt.value.transaction.meta.log_messages: + if "Program data:" in log: + logs += "Program data: " + encoded_part = log.replace("Program data: ", "") + for item in encoded_part.split(" "): + logs += " " + str(base64.b64decode(item)) + else: + logs += log + logs += " " + assert text in logs, f"Transaction logs don't contain '{text}'. Logs: {logs}" + + +def check_holder_account_tag(storage_account, layout, expected_tag): + account_data = solana_client.get_account_info(storage_account, commitment=Confirmed).value.data + parsed_data = layout.parse(account_data) + assert parsed_data.tag == expected_tag, f"Account tag {account_data[0]} != expected {expected_tag}" + From 39410cd13cc2cf7c1689fbb86bd999d97f845a88 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Mon, 11 Dec 2023 17:03:26 +0100 Subject: [PATCH 39/56] Update prod.txt --- deploy/requirements/prod.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deploy/requirements/prod.txt b/deploy/requirements/prod.txt index b5b4e54462..2bd617cf8c 100644 --- a/deploy/requirements/prod.txt +++ b/deploy/requirements/prod.txt @@ -6,5 +6,4 @@ pytest-xdist==3.3.1 pytest-timeout pytest-asyncio==0.17 vyper==0.3.7 -typing-extensions==4.2.0 -pysha3==1.0.2 \ No newline at end of file +typing-extensions==4.2.0 \ No newline at end of file From 6044f6764672b8816c98990f4d3aebec784c5212 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Mon, 11 Dec 2023 18:04:14 +0100 Subject: [PATCH 40/56] Update prod.txt --- deploy/requirements/prod.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deploy/requirements/prod.txt b/deploy/requirements/prod.txt index 2bd617cf8c..5140f9156b 100644 --- a/deploy/requirements/prod.txt +++ b/deploy/requirements/prod.txt @@ -6,4 +6,5 @@ pytest-xdist==3.3.1 pytest-timeout pytest-asyncio==0.17 vyper==0.3.7 -typing-extensions==4.2.0 \ No newline at end of file +typing-extensions==4.2.0 +safe-pysha3==1.0.4 \ No newline at end of file From 658f6cca1637eb23e29a056ab2cf26aac81b7562 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Tue, 12 Dec 2023 11:02:52 +0100 Subject: [PATCH 41/56] Update network_manager.py --- deploy/cli/network_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/cli/network_manager.py b/deploy/cli/network_manager.py index 813c867b8a..7bd73b12d1 100644 --- a/deploy/cli/network_manager.py +++ b/deploy/cli/network_manager.py @@ -3,7 +3,7 @@ import pathlib from collections import defaultdict -NETWORK_NAME = os.environ.get("NETWORK_NAME", "full_test_suite") +NETWORK_NAME = os.environ.get("NETWORK", "full_test_suite") EXPANDED_ENVS = [ "PROXY_URL", "FAUCET_URL", From 5a9a3936e36a6fc1a550c78e8d5b33534172abb2 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Tue, 12 Dec 2023 11:54:04 +0100 Subject: [PATCH 42/56] replaced sha3 library --- deploy/requirements/prod.txt | 2 +- integration/tests/neon_evm/utils/eth_tx_utils.py | 4 ++-- integration/tests/neon_evm/utils/ethereum.py | 10 ++++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/deploy/requirements/prod.txt b/deploy/requirements/prod.txt index 5140f9156b..4f6464475a 100644 --- a/deploy/requirements/prod.txt +++ b/deploy/requirements/prod.txt @@ -7,4 +7,4 @@ pytest-timeout pytest-asyncio==0.17 vyper==0.3.7 typing-extensions==4.2.0 -safe-pysha3==1.0.4 \ No newline at end of file +PyCryptodome==3.19.0 diff --git a/integration/tests/neon_evm/utils/eth_tx_utils.py b/integration/tests/neon_evm/utils/eth_tx_utils.py index c68d8f277c..5c32846ec7 100644 --- a/integration/tests/neon_evm/utils/eth_tx_utils.py +++ b/integration/tests/neon_evm/utils/eth_tx_utils.py @@ -1,4 +1,4 @@ -from sha3 import keccak_256 +from Crypto.Hash import keccak import json from web3.auto import w3 from eth_keys import keys @@ -150,7 +150,7 @@ def hash(self, chain_id=None): self.value, self.callData, chain_id or self.chain_id(), None, None)) - return keccak_256(trx).digest() + return keccak.new(digest_bits=256).update(trx).digest() def sender(self): msg_hash = self.hash() diff --git a/integration/tests/neon_evm/utils/ethereum.py b/integration/tests/neon_evm/utils/ethereum.py index be3ab45901..6f44eb56b5 100644 --- a/integration/tests/neon_evm/utils/ethereum.py +++ b/integration/tests/neon_evm/utils/ethereum.py @@ -1,6 +1,7 @@ from typing import Union -from sha3 import keccak_256 +# from sha3 import keccak_256 +from Crypto.Hash import keccak from solana.publickey import PublicKey from web3.auto import w3 @@ -14,10 +15,12 @@ def create_contract_address(user: Caller, evm_loader: EvmLoader) -> Contract: # Create contract address from (caller_address, nonce) user_nonce = evm_loader.get_neon_nonce(user.eth_address) - contract_eth_address = keccak_256(pack([user.eth_address, user_nonce or None])).digest()[-20:] + contract_eth_address = keccak.new(digest_bits=256).update(pack([user.eth_address, + user_nonce or None])).digest()[-20:] + contract_solana_address, _ = evm_loader.ether2program(contract_eth_address) contract_neon_address = evm_loader.ether2balance(contract_eth_address) - + print(f"Contract addresses: " f" eth {contract_eth_address.hex()}, " f" solana {contract_solana_address}") @@ -27,7 +30,6 @@ def create_contract_address(user: Caller, evm_loader: EvmLoader) -> Contract: def make_eth_transaction(to_addr: bytes, data: Union[bytes, None], caller: Caller, value: int = 0, chain_id=CHAIN_ID, gas=9999999999, access_list=None, type=None): - nonce = EvmLoader(caller.solana_account).get_neon_nonce(caller.eth_address) tx = {'to': to_addr, 'value': value, 'gas': gas, 'gasPrice': 0, 'nonce': nonce} From 4d46344f0467f46cc76f46488310ccdf5261f654 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Tue, 12 Dec 2023 13:40:01 +0100 Subject: [PATCH 43/56] fix getting network --- clickfile.py | 6 +++--- deploy/cli/network_manager.py | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/clickfile.py b/clickfile.py index 2aa58a8704..ff822c868b 100755 --- a/clickfile.py +++ b/clickfile.py @@ -222,7 +222,7 @@ def run_oz_file(file_name): pool.close() pool.join() # Add allure environment - settings = network_manager.networks[network] + settings = network_manager.get_network_object(network) web3_client = web3client.NeonChainWeb3Client( settings["proxy_url"]) opts = { @@ -327,7 +327,7 @@ def create_allure_environment_opts(opts: dict): def generate_allure_environment(network_name: str): - network = network_manager.networks[network_name] + network = network_manager.get_network_object(network_name) env = os.environ.copy() env["NETWORK_ID"] = str(network["network_ids"]["neon"]) @@ -843,7 +843,7 @@ def send_notification(url, build_url, traceback, network): "-n", "--network", default="night-stand", type=str, help="In which stand run tests" ) def get_operator_balances(network: str): - net = network_manager.networks[network] + net = network_manager.get_network_object(network) operator = Operator( net["proxy_url"], net["solana_url"], diff --git a/deploy/cli/network_manager.py b/deploy/cli/network_manager.py index 7bd73b12d1..4a8c166f87 100644 --- a/deploy/cli/network_manager.py +++ b/deploy/cli/network_manager.py @@ -13,21 +13,21 @@ class NetworkManager(): def __init__(self): - self.networks = {} + self._networks = {} with open(pathlib.Path.cwd() / "envs.json", "r") as f: - self.networks = json.load(f) - if NETWORK_NAME not in self.networks.keys() and os.environ.get("DUMP_ENVS"): + self._networks = json.load(f) + if NETWORK_NAME not in self._networks.keys() and os.environ.get("DUMP_ENVS"): environments = defaultdict(dict) for var in EXPANDED_ENVS: environments[NETWORK_NAME].update({var.lower(): os.environ.get(var, "")}) environments[NETWORK_NAME]['network_ids'] = {'neon': os.environ.get('NETWORK_ID', "")} - self.networks.update(environments) + self._networks.update(environments) def get_network_param(self, network, params=None): value = "" - if network in self.networks: - value = self.networks[network] + if network in self._networks: + value = self._networks[network] if params: for item in params.split('.'): value = value[item] From 62af283306134eb98642a884a2eda42ff1dee11d Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Tue, 12 Dec 2023 14:15:01 +0100 Subject: [PATCH 44/56] trigger dockerize --- .github/workflows/dockerize_neon_tests.yml | 3 ++- deploy/cli/dapps.py | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dockerize_neon_tests.yml b/.github/workflows/dockerize_neon_tests.yml index fac8dca7c0..60ba962915 100644 --- a/.github/workflows/dockerize_neon_tests.yml +++ b/.github/workflows/dockerize_neon_tests.yml @@ -2,7 +2,8 @@ name: Docker Image for all neon tests on: push: - branches: ["develop"] + branches: + - "**" workflow_dispatch: inputs: oz_branch: diff --git a/deploy/cli/dapps.py b/deploy/cli/dapps.py index e46fc087b6..a7a5f6cee5 100644 --- a/deploy/cli/dapps.py +++ b/deploy/cli/dapps.py @@ -93,8 +93,6 @@ def format_tf_output(output): log.info(format_tf_output(tf_destroy)) - - def download_remote_docker_logs(): proxy_ip = os.environ.get("PROXY_IP") solana_ip = os.environ.get("SOLANA_IP") From 1d473ae858a0c39dab4dcba05b3ed1eed3b1e7ff Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Tue, 12 Dec 2023 14:34:51 +0100 Subject: [PATCH 45/56] added NEON_CORE_API_URL --- integration/tests/neon_evm/README.md | 9 +++------ integration/tests/neon_evm/conftest.py | 14 +++++--------- integration/tests/neon_evm/utils/constants.py | 1 + 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/integration/tests/neon_evm/README.md b/integration/tests/neon_evm/README.md index bc894e7425..e7f30ec823 100644 --- a/integration/tests/neon_evm/README.md +++ b/integration/tests/neon_evm/README.md @@ -13,15 +13,12 @@ pip install -r requirements.txt py.test ./ -s -v ``` -Moreover, we can use additional command line keys: - -1. --neon-api-uri - neon_core_api url (by default 'http://neon_api:8085/api') - Also we can configure some variables from environment variables: 1. SOLANA_URL - by default http://solana:8899 -2. EVM_LOADER - set evm loader address -3. NEON_TOKEN_MINT - ethereum token mint address +2. NEON_CORE_API_URL - by default http://neon_api:8085 +3. EVM_LOADER - set evm loader address +4. NEON_TOKEN_MINT - ethereum token mint address How to write tests diff --git a/integration/tests/neon_evm/conftest.py b/integration/tests/neon_evm/conftest.py index 6df35a37fe..feb69afb17 100644 --- a/integration/tests/neon_evm/conftest.py +++ b/integration/tests/neon_evm/conftest.py @@ -11,6 +11,7 @@ from .solana_utils import EvmLoader, create_treasury_pool_address, make_new_user, \ deposit_neon, solana_client, wait_for_account_to_exists +from .utils.constants import NEON_CORE_API_URL from .utils.contract import deploy_contract from .utils.storage import create_holder from .types.types import TreasuryPool, Caller, Contract @@ -19,13 +20,6 @@ KEY_PATH = pathlib.Path(__file__).parent / "operator-keypairs" -def pytest_addoption(parser): - parser.addoption( - "--neon-api-uri", action="store", default="http://localhost:8085/api", - help="" - ) - - @pytest.fixture(scope="session") def evm_loader(operator_keypair: Keypair) -> EvmLoader: loader = EvmLoader(operator_keypair) @@ -53,6 +47,7 @@ def prepare_operator(key_file): return account + @pytest.fixture(scope="session") def default_operator_keypair() -> Keypair: """ @@ -61,6 +56,7 @@ def default_operator_keypair() -> Keypair: key_file = KEY_PATH / "id.json" return prepare_operator(key_file) + @pytest.fixture(scope="session") def operator_keypair(worker_id) -> Keypair: """ @@ -164,6 +160,6 @@ def calculator_caller_contract(evm_loader: EvmLoader, operator_keypair: Keypair, @pytest.fixture(scope="session") -def neon_api_client(request): - client = NeonApiClient(url=request.config.getoption("--neon-api-uri")) +def neon_api_client(): + client = NeonApiClient(url=NEON_CORE_API_URL) return client diff --git a/integration/tests/neon_evm/utils/constants.py b/integration/tests/neon_evm/utils/constants.py index 6e38ce18b0..44c4568e8a 100644 --- a/integration/tests/neon_evm/utils/constants.py +++ b/integration/tests/neon_evm/utils/constants.py @@ -21,6 +21,7 @@ TAG_HOLDER = 52 SOLANA_URL = os.environ.get("SOLANA_URL", "http://solana:8899") +NEON_CORE_API_URL = os.environ.get("NEON_CORE_API_URL", "http://neon_api:8085") EVM_LOADER = os.environ.get("EVM_LOADER", "53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io") NEON_TOKEN_MINT_ID: PublicKey = PublicKey(os.environ.get("NEON_TOKEN_MINT", "HPsV9Deocecw3GeZv1FkAPNCBRfuVyfw9MMwjwRe1xaU")) CHAIN_ID = int(os.environ.get("NEON_CHAIN_ID", 111)) From 6a8c998ebc67031e72faa1a0694f7ffd17d77c4c Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Tue, 12 Dec 2023 15:21:55 +0100 Subject: [PATCH 46/56] fix default value --- integration/tests/neon_evm/README.md | 2 +- integration/tests/neon_evm/utils/constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/tests/neon_evm/README.md b/integration/tests/neon_evm/README.md index e7f30ec823..60806109fe 100644 --- a/integration/tests/neon_evm/README.md +++ b/integration/tests/neon_evm/README.md @@ -16,7 +16,7 @@ py.test ./ -s -v Also we can configure some variables from environment variables: 1. SOLANA_URL - by default http://solana:8899 -2. NEON_CORE_API_URL - by default http://neon_api:8085 +2. NEON_CORE_API_URL - by default http://neon_api:8085/api 3. EVM_LOADER - set evm loader address 4. NEON_TOKEN_MINT - ethereum token mint address diff --git a/integration/tests/neon_evm/utils/constants.py b/integration/tests/neon_evm/utils/constants.py index 44c4568e8a..af9c1119fc 100644 --- a/integration/tests/neon_evm/utils/constants.py +++ b/integration/tests/neon_evm/utils/constants.py @@ -21,7 +21,7 @@ TAG_HOLDER = 52 SOLANA_URL = os.environ.get("SOLANA_URL", "http://solana:8899") -NEON_CORE_API_URL = os.environ.get("NEON_CORE_API_URL", "http://neon_api:8085") +NEON_CORE_API_URL = os.environ.get("NEON_CORE_API_URL", "http://neon_api:8085/api") EVM_LOADER = os.environ.get("EVM_LOADER", "53DfF883gyixYNXnM7s5xhdeyV8mVk9T4i2hGV9vG9io") NEON_TOKEN_MINT_ID: PublicKey = PublicKey(os.environ.get("NEON_TOKEN_MINT", "HPsV9Deocecw3GeZv1FkAPNCBRfuVyfw9MMwjwRe1xaU")) CHAIN_ID = int(os.environ.get("NEON_CHAIN_ID", 111)) From 5ecfa3f16b89996363472185d83e2c53b40ebadb Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Wed, 13 Dec 2023 10:00:24 +0100 Subject: [PATCH 47/56] Update how_to_run.md --- docs/how_to_run.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/how_to_run.md b/docs/how_to_run.md index ffb577aa2d..5f67038ae2 100644 --- a/docs/how_to_run.md +++ b/docs/how_to_run.md @@ -38,11 +38,21 @@ To download test contracts from the Neon EVM repo, you need to use: ## Run OpenZeppelin tests -To run OpenZeppelin tests, just use the next command: +To run OpenZeppelin tests just use the next command: ```bash ./clickfile.py run oz --network --jobs 8 ``` +## Run neon evm tests + +To run neon evm tests: +1. set environment variables: + SOLANA_URL: by default http://solana:8899 + NEON_CORE_API_URL: by default http://neon_api:8085/api +2. run the next command: +```bash +./clickfile.py run evm --numprocesses 6 +``` ## Run tests manual From 6d4596e1527e73ee6f33a733ec7bc77760d8a522 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Wed, 13 Dec 2023 10:00:58 +0100 Subject: [PATCH 48/56] fixing oz ci --- .github/workflows/openzeppelin.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/openzeppelin.yml b/.github/workflows/openzeppelin.yml index 9dfb0b28bd..d5d6432860 100644 --- a/.github/workflows/openzeppelin.yml +++ b/.github/workflows/openzeppelin.yml @@ -93,6 +93,7 @@ jobs: - prepare-env - dockerize runs-on: ${{ needs.prepare-env.outputs.runner }} + if: always() && contains(fromJSON('["success", "skipped"]'), needs.dockerize.result) env: AWS_ACCESS_KEY_ID: "${{ secrets.AWS_ACCESS_KEY_ID }}" AWS_SECRET_ACCESS_KEY: "${{ secrets.AWS_SECRET_ACCESS_KEY }}" From 42637e6a7e5d7ad159dcad9b7e65ff53fd7ced19 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Wed, 13 Dec 2023 11:31:13 +0100 Subject: [PATCH 49/56] fix list of oz tests --- clickfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clickfile.py b/clickfile.py index ff822c868b..8e9542f92d 100755 --- a/clickfile.py +++ b/clickfile.py @@ -183,7 +183,7 @@ def run_openzeppelin_tests(network, jobs=8, amount=20000, users=8): ] tests = list(Path(f"{cwd}/test").rglob('*.test.js')) - tests = [str(test) for test in tests if 'test' in test.read_text()] + tests = [str(test) for test in tests] def run_oz_file(file_name): print(f"Run {file_name}") From 0299d80052f6a09d5e27faddf4c61c6b8b994b74 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Wed, 13 Dec 2023 15:31:57 +0100 Subject: [PATCH 50/56] Update openzeppelin.yml --- .github/workflows/openzeppelin.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/openzeppelin.yml b/.github/workflows/openzeppelin.yml index d5d6432860..b06a1cf5b1 100644 --- a/.github/workflows/openzeppelin.yml +++ b/.github/workflows/openzeppelin.yml @@ -101,7 +101,13 @@ jobs: - uses: actions/checkout@v3 - name: Define image tag id: image_tag - uses: ./.github/actions/define-image-tag + run: | + if [[ "${{ needs.dockerize.result }}" != "skipped" ]]; then + tag=${{ github.sha }} + else + tag='latest' + echo "tag=${tag}" + echo "tag=${tag}" >> $GITHUB_OUTPUT - name: Pull docker image run: docker pull ${{ env.IMAGE }}:${{ steps.image_tag.outputs.tag }} - name: Run docker container From 0fcc0c4ea3fb4aa7c1a6a124958024a968ebe50d Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Wed, 13 Dec 2023 15:53:26 +0100 Subject: [PATCH 51/56] Update openzeppelin.yml --- .github/workflows/openzeppelin.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/openzeppelin.yml b/.github/workflows/openzeppelin.yml index b06a1cf5b1..45fc752d75 100644 --- a/.github/workflows/openzeppelin.yml +++ b/.github/workflows/openzeppelin.yml @@ -106,6 +106,7 @@ jobs: tag=${{ github.sha }} else tag='latest' + fi echo "tag=${tag}" echo "tag=${tag}" >> $GITHUB_OUTPUT - name: Pull docker image From 49d6e632b4050c3adf818fbdb08182dd2fd541c3 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Thu, 14 Dec 2023 18:45:01 +0100 Subject: [PATCH 52/56] try patch web3 hardhat lib --- Dockerfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3d9f9a96e7..7cbcd2bba7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -79,4 +79,8 @@ RUN mkdir -p ${DOWNLOAD_PATH} && \ COPY deploy/infra/compile_contracts.sh compatibility/openzeppelin-contracts RUN cd compatibility/openzeppelin-contracts npm set audit false RUN cd compatibility/openzeppelin-contracts && npm ci -RUN cd compatibility/openzeppelin-contracts && ./compile_contracts.sh \ No newline at end of file +RUN cd compatibility/openzeppelin-contracts && ./compile_contracts.sh + + +COPY compatibility/openzeppelin-contracts/neon-web3-hardhat.patch /tmp/ +RUN cd compatibility/openzeppelin-contracts/node_modules && patch -p0 < /tmp/neon-web3-hardhat.patch From bb0bf05750bbc3a56781dda01ff26e721c91ed09 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Thu, 14 Dec 2023 19:29:44 +0100 Subject: [PATCH 53/56] Update Dockerfile --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7cbcd2bba7..5a20e1ac01 100644 --- a/Dockerfile +++ b/Dockerfile @@ -81,6 +81,5 @@ RUN cd compatibility/openzeppelin-contracts npm set audit false RUN cd compatibility/openzeppelin-contracts && npm ci RUN cd compatibility/openzeppelin-contracts && ./compile_contracts.sh - -COPY compatibility/openzeppelin-contracts/neon-web3-hardhat.patch /tmp/ +COPY neon-web3-hardhat.patch /tmp/ RUN cd compatibility/openzeppelin-contracts/node_modules && patch -p0 < /tmp/neon-web3-hardhat.patch From 2032bd05fdb1ad7f7004bc0278f502077f9b0e33 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Thu, 14 Dec 2023 19:30:49 +0100 Subject: [PATCH 54/56] Create neon-web3-hardhat.patch --- neon-web3-hardhat.patch | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 neon-web3-hardhat.patch diff --git a/neon-web3-hardhat.patch b/neon-web3-hardhat.patch new file mode 100644 index 0000000000..1ff3f62176 --- /dev/null +++ b/neon-web3-hardhat.patch @@ -0,0 +1,19 @@ +--- web3-core-method.orig/lib/index.js 2023-12-14 19:47:57.802000000 +0400 ++++ web3-core-method/lib/index.js 2023-12-14 19:53:55.993043900 +0400 +@@ -44,11 +44,11 @@ + this.accounts = options.accounts; + this.defaultBlock = options.defaultBlock || 'latest'; + this.defaultAccount = options.defaultAccount || null; +- this.transactionBlockTimeout = options.transactionBlockTimeout || 50; +- this.transactionConfirmationBlocks = options.transactionConfirmationBlocks || 24; +- this.transactionPollingTimeout = options.transactionPollingTimeout || 750; +- this.transactionPollingInterval = options.transactionPollingInterval || 1000; +- this.blockHeaderTimeout = options.blockHeaderTimeout || 10; // 10 seconds ++ this.transactionBlockTimeout = 1; ++ this.transactionConfirmationBlocks = 1; ++ this.transactionPollingTimeout = 1; ++ this.transactionPollingInterval = 1; ++ this.blockHeaderTimeout = 1; + this.defaultCommon = options.defaultCommon; + this.defaultChain = options.decd faultChain; + this.defaultHardfork = options.defaultHardfork; \ No newline at end of file From e166b0dc3ba63b4ff71b2a917023a0045be566c3 Mon Sep 17 00:00:00 2001 From: kristinaNikolaeva Date: Fri, 15 Dec 2023 17:22:44 +0100 Subject: [PATCH 55/56] remove experement files --- Dockerfile | 3 --- neon-web3-hardhat.patch | 19 ------------------- 2 files changed, 22 deletions(-) delete mode 100644 neon-web3-hardhat.patch diff --git a/Dockerfile b/Dockerfile index 5a20e1ac01..fb795e1f7a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -80,6 +80,3 @@ COPY deploy/infra/compile_contracts.sh compatibility/openzeppelin-contracts RUN cd compatibility/openzeppelin-contracts npm set audit false RUN cd compatibility/openzeppelin-contracts && npm ci RUN cd compatibility/openzeppelin-contracts && ./compile_contracts.sh - -COPY neon-web3-hardhat.patch /tmp/ -RUN cd compatibility/openzeppelin-contracts/node_modules && patch -p0 < /tmp/neon-web3-hardhat.patch diff --git a/neon-web3-hardhat.patch b/neon-web3-hardhat.patch deleted file mode 100644 index 1ff3f62176..0000000000 --- a/neon-web3-hardhat.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- web3-core-method.orig/lib/index.js 2023-12-14 19:47:57.802000000 +0400 -+++ web3-core-method/lib/index.js 2023-12-14 19:53:55.993043900 +0400 -@@ -44,11 +44,11 @@ - this.accounts = options.accounts; - this.defaultBlock = options.defaultBlock || 'latest'; - this.defaultAccount = options.defaultAccount || null; -- this.transactionBlockTimeout = options.transactionBlockTimeout || 50; -- this.transactionConfirmationBlocks = options.transactionConfirmationBlocks || 24; -- this.transactionPollingTimeout = options.transactionPollingTimeout || 750; -- this.transactionPollingInterval = options.transactionPollingInterval || 1000; -- this.blockHeaderTimeout = options.blockHeaderTimeout || 10; // 10 seconds -+ this.transactionBlockTimeout = 1; -+ this.transactionConfirmationBlocks = 1; -+ this.transactionPollingTimeout = 1; -+ this.transactionPollingInterval = 1; -+ this.blockHeaderTimeout = 1; - this.defaultCommon = options.defaultCommon; - this.defaultChain = options.decd faultChain; - this.defaultHardfork = options.defaultHardfork; \ No newline at end of file From a38d7b46685fb764a4193268ac61b8d19fb24756 Mon Sep 17 00:00:00 2001 From: Andrey Falaleev Date: Wed, 13 Dec 2023 00:10:17 +0700 Subject: [PATCH 56/56] Refactor neon_getTransactionReceipt --- .../tests/basic/evm/opcodes/test_extcodehash.py | 4 ++-- integration/tests/basic/helpers/rpc_checks.py | 10 +++++----- .../tests/basic/rpc/test_rpc_get_transaction.py | 6 +++++- integration/tests/basic/test_trx_rlp_decoding.py | 2 +- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/integration/tests/basic/evm/opcodes/test_extcodehash.py b/integration/tests/basic/evm/opcodes/test_extcodehash.py index 5da9e24846..f49ec48a1c 100644 --- a/integration/tests/basic/evm/opcodes/test_extcodehash.py +++ b/integration/tests/basic/evm/opcodes/test_extcodehash.py @@ -166,9 +166,9 @@ def test_extcodehash_for_reverted_destroyed_contract(self, eip1052_checker): receipt = self.web3_client.send_transaction(self.sender_account, instruction_tx) neon_logs = self.proxy_api.send_rpc( method="neon_getTransactionReceipt", - params=[receipt["transactionHash"].hex()], + params=[receipt["transactionHash"].hex(), "neon"], )["result"]["logs"] - data = [log["data"] for log in neon_logs if log["topics"] != []] + data = [log["data"] for log in neon_logs if log["neonEventType"] == "Log"] # TODO fix checking # assert data[0] != ZERO_HASH # assert len(data) == 3 diff --git a/integration/tests/basic/helpers/rpc_checks.py b/integration/tests/basic/helpers/rpc_checks.py index 4380dbbb62..519acde531 100644 --- a/integration/tests/basic/helpers/rpc_checks.py +++ b/integration/tests/basic/helpers/rpc_checks.py @@ -104,11 +104,11 @@ def assert_block_fields(block: dict, full_trx: bool, tx_receipt: tp.Optional[typ def assert_log_field_in_neon_trx_receipt(response, events_count): - expected_event_types = ["ENTER CALL"] + expected_event_types = ["EnterCall"] for i in range(events_count): - expected_event_types.append("LOG") - expected_event_types.append("EXIT STOP") - expected_event_types.append("RETURN") + expected_event_types.append("Log") + expected_event_types.append("ExitStop") + expected_event_types.append("Return") all_logs = [] for trx in response["result"]["solanaTransactions"]: @@ -138,7 +138,7 @@ def assert_log_field_in_neon_trx_receipt(response, events_count): assert neon_logs != [] for log in neon_logs: all_logs.append(log) - event_types = [log["neonEventType"] for log in sorted(all_logs, key=lambda x: x["neonEventOrder"])] + event_types = [log["neonEventType"] for log in sorted(all_logs, key=lambda x: int(x["neonEventOrder"], 16))] assert event_types == expected_event_types, f"Actual: {event_types}; Expected: {expected_event_types}" diff --git a/integration/tests/basic/rpc/test_rpc_get_transaction.py b/integration/tests/basic/rpc/test_rpc_get_transaction.py index 8c90bce4d5..fe7dff0aa6 100644 --- a/integration/tests/basic/rpc/test_rpc_get_transaction.py +++ b/integration/tests/basic/rpc/test_rpc_get_transaction.py @@ -202,7 +202,11 @@ def test_get_transaction_receipt(self, method): """Verify implemented rpc calls work with neon_getTransactionReceipt and eth_getTransactionReceipt""" tx_receipt = self.send_neon(self.sender_account, self.recipient_account, 10) transaction_hash = tx_receipt.transactionHash.hex() - response = self.proxy_api.send_rpc(method=method, params=transaction_hash) + params = [transaction_hash] + if method.startswith('neon_'): + params.append('ethereum') + response = self.proxy_api.send_rpc(method=method, params=params) +# response = self.proxy_api.send_rpc(method=method, params=transaction_hash) assert "error" not in response assert "result" in response, AssertMessage.DOES_NOT_CONTAIN_RESULT result = response["result"] diff --git a/integration/tests/basic/test_trx_rlp_decoding.py b/integration/tests/basic/test_trx_rlp_decoding.py index 779664346f..d11c1e5fd9 100644 --- a/integration/tests/basic/test_trx_rlp_decoding.py +++ b/integration/tests/basic/test_trx_rlp_decoding.py @@ -66,7 +66,7 @@ def test_modify_s(self, signed_tx, new_s, expected_error): @pytest.mark.parametrize("new_r, expected_error", [ (13237258775825350966557245051891674271982401474769237400875435660443279001850, - "failed to recover ECDSA public key"), + "Invalid signature"), (123, "insufficient funds for transfer"), ('', "Invalid signature values")]) def test_modify_r(self, signed_tx, new_r, expected_error):