diff --git a/contracts/common/StorageSoliditySource.sol b/contracts/common/StorageSoliditySource.sol index 11d925d59f..78c7307f23 100644 --- a/contracts/common/StorageSoliditySource.sol +++ b/contracts/common/StorageSoliditySource.sol @@ -23,7 +23,7 @@ contract Storage { return msg.sender.balance; } - function storeSumOfNumbers(uint256 num1, uint256 num2) public view returns (uint256) { + function storeSumOfNumbers(uint256 num1, uint256 num2) public view returns (uint256) { if (number == 101) { num1 = 0; } diff --git a/contracts/precompiled/CallSolanaCaller.sol b/contracts/precompiled/CallSolanaCaller.sol index 7a47095512..f52cdedb43 100644 --- a/contracts/precompiled/CallSolanaCaller.sol +++ b/contracts/precompiled/CallSolanaCaller.sol @@ -2,11 +2,17 @@ pragma solidity >=0.7.0 <0.9.0; pragma abicoder v2; import "../external/neon-evm/call_solana.sol"; - +import "../common/StorageSoliditySource.sol"; contract CallSolanaCaller { + CallSolana constant _callSolana = + CallSolana(0xFF00000000000000000000000000000000000006); + struct Data { + uint256 value1; + uint256 value2; + } + mapping(uint256 => Data) public dataMap; - CallSolana constant _callSolana = CallSolana(0xFF00000000000000000000000000000000000006); struct ExecuteArgs { uint64 lamports; bytes instruction; @@ -17,73 +23,173 @@ contract CallSolanaCaller { bytes instruction; } - event LogBytes(bytes32 value); event LogStr(string value); + event LogAddress(address value); event LogData(bytes32 program, bytes value); - function getNeonAddress(address addr) public returns (bytes32){ + function getNeonAddress(address addr) public returns (bytes32) { bytes32 solanaAddr = _callSolana.getNeonAddress(addr); return solanaAddr; } function execute(uint64 lamports, bytes calldata instruction) public { - bytes32 returnData = bytes32(_callSolana.execute(lamports, instruction)); + bytes32 returnData = bytes32( + _callSolana.execute(lamports, instruction) + ); emit LogBytes(returnData); + } + + function executeInIterativeMode( + uint256 actionsNumber, + uint64 lamports, + bytes calldata instruction + ) public { + doIterativeActions(actionsNumber); + execute(lamports, instruction); + } + + function executeAndDoSomeIterativeActions( + uint256 actionsNumber, + uint64 lamports, + bytes calldata instruction + ) public { + execute(lamports, instruction); + doIterativeActions(actionsNumber); + } + + function executeMultipleInIterativeMode( + uint256 calls, + uint256 actionsNumber, + uint64 lamports, + bytes calldata instruction + ) public { + doIterativeActions(actionsNumber); + for (uint256 i = 0; i < calls; i++) { + execute(lamports, instruction); + } + } + + function batchExecuteInIterativeMode( + uint256 actionsNumber, + ExecuteArgs[] memory _args + ) public { + doIterativeActions(actionsNumber); + batchExecute(_args); + } + + function sendTokensAndExecuteInIterativeMode( + uint256 actionsNumber, + uint64 lamports, + bytes calldata instruction + ) public payable { + executeInIterativeMode(actionsNumber, lamports, instruction); + } + + function doSomeIterativeActions(uint256 actionsNumber) public { + doIterativeActions(actionsNumber); + emit LogStr("iterative actions status: done"); + } + + function doIterativeActions(uint actionsNumber) public { + // some actions to make the call iterative + for (uint256 i = 0; i < actionsNumber; i++) { + Data memory newData = Data({value1: 1, value2: 2}); + dataMap[i] = newData; + } + } + function deployStorageAndCallSolana( + string memory message, + uint64 lamports, + bytes calldata instruction + ) public { + Storage storageContract = new Storage(); + storageContract.store(10); + require(storageContract.retrieve() == 10); + execute(lamports, instruction); + emit LogAddress(address(storageContract)); } - function execute_with_get_return_data(uint64 lamports, bytes calldata instruction) public { + function execute_with_get_return_data( + uint64 lamports, + bytes calldata instruction + ) public { _callSolana.execute(lamports, instruction); - (bytes32 program, bytes memory returnData) = _callSolana.getReturnData(); + (bytes32 program, bytes memory returnData) = _callSolana + .getReturnData(); emit LogData(program, returnData); } function batchExecute(ExecuteArgs[] memory _args) public { - for(uint i = 0; i < _args.length; i++) { + for (uint i = 0; i < _args.length; i++) { _callSolana.execute(_args[i].lamports, _args[i].instruction); } - (bytes32 program, bytes memory returnData) = _callSolana.getReturnData(); + (bytes32 program, bytes memory returnData) = _callSolana + .getReturnData(); emit LogData(program, returnData); } - function getPayer() public returns (bytes32){ + function getPayer() public returns (bytes32) { bytes32 payer = _callSolana.getPayer(); return payer; } - function createResource(bytes32 salt, uint64 space, uint64 lamports, bytes32 owner) external returns (bytes32){ - bytes32 resource = _callSolana.createResource(salt, space, lamports, owner); + function createResource( + bytes32 salt, + uint64 space, + uint64 lamports, + bytes32 owner + ) external returns (bytes32) { + bytes32 resource = _callSolana.createResource( + salt, + space, + lamports, + owner + ); return resource; } - function getResourceAddress(bytes32 salt) external returns (bytes32){ + function getResourceAddress(bytes32 salt) external returns (bytes32) { bytes32 resource = _callSolana.getResourceAddress(salt); return resource; } - function getSolanaPDA(bytes32 program_id, bytes memory seeds) external returns (bytes32){ + function getSolanaPDA( + bytes32 program_id, + bytes memory seeds + ) external returns (bytes32) { bytes32 pda = _callSolana.getSolanaPDA(program_id, seeds); return pda; } - function getExtAuthority(bytes32 salt) external returns (bytes32){ + function getExtAuthority(bytes32 salt) external returns (bytes32) { bytes32 authority = _callSolana.getExtAuthority(salt); return authority; } - function executeWithSeed(uint64 lamports, bytes32 salt, bytes calldata instruction) public { - bytes32 returnData = bytes32(_callSolana.executeWithSeed(lamports, salt, instruction)); + function executeWithSeed( + uint64 lamports, + bytes32 salt, + bytes calldata instruction + ) public { + bytes32 returnData = bytes32( + _callSolana.executeWithSeed(lamports, salt, instruction) + ); emit LogBytes(returnData); } - function getReturnData() public returns (bytes32, bytes memory){ + function getReturnData() public returns (bytes32, bytes memory) { return _callSolana.getReturnData(); } function batchExecuteWithSeed(ExecuteWithSeedArgs[] memory _args) public { - for(uint i = 0; i < _args.length; i++) { - _callSolana.executeWithSeed(_args[i].lamports, _args[i].salt, _args[i].instruction); + for (uint i = 0; i < _args.length; i++) { + _callSolana.executeWithSeed( + _args[i].lamports, + _args[i].salt, + _args[i].instruction + ); } } -} \ No newline at end of file +} diff --git a/integration/tests/basic/evm/test_solana_interoperability.py b/integration/tests/basic/evm/test_solana_interoperability.py index 872c95f9c7..3d1cf2bd87 100644 --- a/integration/tests/basic/evm/test_solana_interoperability.py +++ b/integration/tests/basic/evm/test_solana_interoperability.py @@ -1,4 +1,5 @@ import typing as tp +import web3.exceptions import pytest import spl @@ -16,11 +17,12 @@ ) import allure +from utils.types import TransactionType from utils.accounts import EthAccounts from utils.consts import COUNTER_ID, TRANSFER_TOKENS_ID, wSOL -from utils.helpers import bytes32_to_solana_pubkey, serialize_instruction +from utils.helpers import bytes32_to_solana_pubkey, serialize_instruction, wait_condition from utils.instructions import make_wSOL -from utils.web3client import NeonChainWeb3Client +from utils.web3client import NeonChainWeb3Client, Web3Client @pytest.fixture(scope="session") @@ -41,6 +43,61 @@ class TestSolanaInteroperability: accounts: EthAccounts web3_client: NeonChainWeb3Client + @pytest.fixture(scope="class") + def account_sol_network(self, class_account_sol_chain): + return class_account_sol_chain + + @pytest.fixture(scope="class") + def call_solana_caller_sol_network(self, account_sol_network, web3_client_sol): + contract, _ = web3_client_sol.deploy_and_get_contract( + contract="precompiled/CallSolanaCaller.sol", + version="0.8.10", + contract_name="CallSolanaCaller", + account=account_sol_network, + ) + return contract + + def get_transfer_instruction(self, sol_client, from_wallet, to_wallet, amount, contract, is_set_authority=True): + mint = spl.token.client.Token.create_mint( + conn=sol_client, + payer=from_wallet, + mint_authority=from_wallet.pubkey(), + decimals=9, + program_id=TOKEN_PROGRAM_ID, + ) + mint.payer = from_wallet + from_token_account = mint.create_associated_token_account(from_wallet.pubkey()) + to_token_account = mint.create_associated_token_account(to_wallet.pubkey()) + mint.mint_to( + dest=from_token_account, + mint_authority=from_wallet, + amount=amount, + opts=TxOpts(skip_confirmation=False, skip_preflight=True), + ) + + authority_pubkey: bytes = contract.functions.getSolanaPDA(bytes(TRANSFER_TOKENS_ID), b"authority").call() + if is_set_authority: + mint.set_authority( + from_token_account, + from_wallet, + spl.token.instructions.AuthorityType.ACCOUNT_OWNER, + Pubkey(authority_pubkey), + opts=TxOpts(skip_confirmation=False, skip_preflight=True), + ) + + instruction = Instruction( + program_id=TRANSFER_TOKENS_ID, + accounts=[ + AccountMeta(from_token_account, is_signer=False, is_writable=True), + AccountMeta(mint.pubkey, is_signer=False, is_writable=True), + AccountMeta(to_token_account, is_signer=False, is_writable=True), + AccountMeta(Pubkey(authority_pubkey), is_signer=False, is_writable=True), + AccountMeta(TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), + ], + data=bytes([0x0]), + ) + return serialize_instruction(TRANSFER_TOKENS_ID, instruction), mint, [from_token_account, to_token_account] + def test_counter_execute_with_get_return_data( self, call_solana_caller, counter_resource_address: bytes, get_counter_value ): @@ -66,6 +123,58 @@ def test_counter_execute_with_get_return_data( assert int.from_bytes(event_logs[0].args.value, byteorder="little") == next(get_counter_value) assert bytes32_to_solana_pubkey(event_logs[0].args.program.hex()) == COUNTER_ID + def test_iterative_actions_and_multiple_solana_calls_instructions_limit(self, counter_resource_address, call_solana_caller): + solana_calls = 15 + iterations = 35 + sender = self.accounts[0] + lamports = 0 + + instruction = Instruction( + program_id=COUNTER_ID, + accounts=[ + AccountMeta(Pubkey(counter_resource_address), is_signer=False, is_writable=True), + ], + data=bytes([0x1]), + ) + serialized = serialize_instruction(COUNTER_ID, instruction) + + tx = self.web3_client.make_raw_tx(sender.address) + instruction_tx = call_solana_caller.functions.executeMultipleInIterativeMode(solana_calls, + iterations, + lamports, + serialized).build_transaction(tx) + resp = self.web3_client.send_transaction(sender, instruction_tx) + assert resp["status"] == 0 + + def test_transfer_with_pda_signature_iterative_tx_eip_1559(self, call_solana_caller, sol_client, solana_account, pytestconfig, bank_account): + iterations = 20 + sender = self.accounts[0] + from_wallet = Keypair() + to_wallet = Keypair() + amount = 100000 + if pytestconfig.environment.use_bank: + sol_client.send_sol(bank_account, from_wallet.pubkey(), int(0.5 * 10**9)) + else: + sol_client.request_airdrop(from_wallet.pubkey(), 1000 * 10**9, commitment=Confirmed) + + serialized, mint, accounts_list = self.get_transfer_instruction(sol_client, from_wallet, to_wallet, amount, call_solana_caller) + tx = self.web3_client.make_raw_tx(from_=sender.address, amount=None, data=None, tx_type=TransactionType.EIP_1559) + + instruction_tx = call_solana_caller.functions.executeInIterativeMode(iterations, 0, serialized).build_transaction(tx) + + resp = self.web3_client.send_transaction(sender, instruction_tx) + assert resp["status"] == 1 + assert resp.type == 2 + + assert int(mint.get_balance(accounts_list[1], commitment=Confirmed).value.amount) == amount + event_logs = call_solana_caller.events.LogBytes().process_receipt(resp) + assert int.from_bytes(event_logs[0].args.value, byteorder="little") == 0 + + wait_condition( + lambda: self.web3_client.is_trx_iterative(resp["transactionHash"].hex()) is True, + timeout_sec=120, + ) + def test_counter_with_seed(self, call_solana_caller, counter_resource_address: bytes, get_counter_value): sender = self.accounts[0] lamports = 0 @@ -290,7 +399,7 @@ def test_limit_of_simple_instr_in_one_trx(self, call_solana_caller, counter_reso for _ in range(24): instruction = Instruction( - program_id=COUNTER_ID, + program_id=COUNTER_ID, accounts=[ AccountMeta(Pubkey(counter_resource_address), is_signer=False, is_writable=True), ], @@ -303,3 +412,251 @@ def test_limit_of_simple_instr_in_one_trx(self, call_solana_caller, counter_reso instruction_tx = call_solana_caller.functions.batchExecute(call_params).build_transaction(tx) resp = self.web3_client.send_transaction(sender, instruction_tx) assert resp["status"] == 0 + + + def test_solana_call_after_iterative_actions_sol_network(self, web3_client_sol, + counter_resource_address, + call_solana_caller_sol_network, + get_counter_value, + account_sol_network): + iterations = 29 + sender = account_sol_network + lamports = 0 + + instruction = Instruction( + program_id=COUNTER_ID, + accounts=[ + AccountMeta(Pubkey(counter_resource_address), is_signer=False, is_writable=True), + ], + data=bytes([0x1]), + ) + serialized = serialize_instruction(COUNTER_ID, instruction) + + tx = web3_client_sol.make_raw_tx(sender.address) + instruction_tx = call_solana_caller_sol_network.functions.executeInIterativeMode(iterations, lamports, serialized).build_transaction(tx) + resp = web3_client_sol.send_transaction(sender, instruction_tx) + assert resp["status"] == 1 + event_logs = call_solana_caller_sol_network.events.LogBytes().process_receipt(resp) + assert int.from_bytes(event_logs[0].args.value, byteorder="little") == next(get_counter_value) + + def test_solana_call_after_iterative_actions(self, counter_resource_address, call_solana_caller, get_counter_value): + iterations = 29 + sender = self.accounts[0] + lamports = 0 + + instruction = Instruction( + program_id=COUNTER_ID, + accounts=[ + AccountMeta(Pubkey(counter_resource_address), is_signer=False, is_writable=True), + ], + data=bytes([0x1]), + ) + serialized = serialize_instruction(COUNTER_ID, instruction) + + tx = self.web3_client.make_raw_tx(sender.address) + instruction_tx = call_solana_caller.functions.executeInIterativeMode(iterations, lamports, serialized).build_transaction(tx) + resp = self.web3_client.send_transaction(sender, instruction_tx) + assert resp["status"] == 1 + event_logs = call_solana_caller.events.LogBytes().process_receipt(resp) + assert int.from_bytes(event_logs[0].args.value, byteorder="little") == next(get_counter_value) + + def test_failed_solana_call_after_iterative_actions(self, call_solana_caller, sol_client, solana_account, pytestconfig, bank_account): + iterations = 29 + sender = self.accounts[0] + from_wallet = Keypair() + to_wallet = Keypair() + amount = 100000 + if pytestconfig.environment.use_bank: + sol_client.send_sol(bank_account, from_wallet.pubkey(), int(0.5 * 10**9)) + else: + sol_client.request_airdrop(from_wallet.pubkey(), 1000 * 10**9, commitment=Confirmed) + + serialized, _, _ = self.get_transfer_instruction(sol_client, from_wallet, to_wallet, amount, call_solana_caller, False) + + tx = self.web3_client.make_raw_tx(from_=sender.address, estimate_gas=True) + + instruction_tx = call_solana_caller.functions.executeInIterativeMode(iterations, 0, serialized).build_transaction(tx) + + resp = self.web3_client.send_transaction(sender, instruction_tx) + assert resp["status"] == 0 + + event_logs = call_solana_caller.events.LogStr().process_receipt(resp) + assert len(event_logs) == 0 + + + def test_solana_call_after_iterative_actions_exceed_accounts_limit(self, counter_resource_address, call_solana_caller): + iterations = 53 + sender = self.accounts[0] + lamports = 0 + + instruction = Instruction( + program_id=COUNTER_ID, + accounts=[ + AccountMeta(Pubkey(counter_resource_address), is_signer=False, is_writable=True), + ], + data=bytes([0x1]), + ) + serialized = serialize_instruction(COUNTER_ID, instruction) + + tx = self.web3_client.make_raw_tx(sender.address) + + with pytest.raises( + web3.exceptions.ContractLogicError, + match="too many accounts: 65 > 64", + ): + call_solana_caller.functions.executeInIterativeMode(iterations, lamports, serialized).build_transaction(tx) + + def test_solana_call_before_iterative_actions(self, counter_resource_address, call_solana_caller, get_counter_value): + iterations = 29 + sender = self.accounts[0] + lamports = 0 + + instruction = Instruction( + program_id=COUNTER_ID, + accounts=[ + AccountMeta(Pubkey(counter_resource_address), is_signer=False, is_writable=True), + ], + data=bytes([0x1]), + ) + serialized = serialize_instruction(COUNTER_ID, instruction) + + tx = self.web3_client.make_raw_tx(sender.address) + instruction_tx = call_solana_caller.functions.executeAndDoSomeIterativeActions(iterations, + lamports, + serialized).build_transaction(tx) + resp = self.web3_client.send_transaction(sender, instruction_tx) + assert resp["status"] == 1 + event_logs = call_solana_caller.events.LogBytes().process_receipt(resp) + assert int.from_bytes(event_logs[0].args.value, byteorder="little") == next(get_counter_value) + + def test_iterative_actions_after_solana_call_exceed_accounts_limit(self, counter_resource_address, call_solana_caller): + iterations = 53 + sender = self.accounts[0] + lamports = 0 + + instruction = Instruction( + program_id=COUNTER_ID, + accounts=[ + AccountMeta(Pubkey(counter_resource_address), is_signer=False, is_writable=True), + ], + data=bytes([0x1]), + ) + serialized = serialize_instruction(COUNTER_ID, instruction) + + tx = self.web3_client.make_raw_tx(sender.address) + + with pytest.raises( + web3.exceptions.ContractLogicError, + match="too many accounts: 65 > 64", + ): + call_solana_caller.functions.executeAndDoSomeIterativeActions(iterations, lamports, serialized).build_transaction(tx) + + def test_iterative_actions_and_multiple_solana_calls(self, counter_resource_address, call_solana_caller): + solana_calls = 5 + iterations = 20 + sender = self.accounts[0] + lamports = 0 + instruction = Instruction( + program_id=COUNTER_ID, + accounts=[ + AccountMeta(Pubkey(counter_resource_address), is_signer=False, is_writable=True), + ], + data=bytes([0x1]), + ) + serialized = serialize_instruction(COUNTER_ID, instruction) + + tx = self.web3_client.make_raw_tx(sender.address) + instruction_tx = call_solana_caller.functions.executeMultipleInIterativeMode(solana_calls, + iterations, + lamports, + serialized).build_transaction(tx) + resp = self.web3_client.send_transaction(sender, instruction_tx) + assert resp["status"] == 1 + assert len(call_solana_caller.events.LogBytes().process_receipt(resp)) == solana_calls + + def test_deploy_contract_and_call_solana(self, counter_resource_address, call_solana_caller): + sender = self.accounts[0] + lamports = 0 + + instruction = Instruction( + program_id=COUNTER_ID, + accounts=[ + AccountMeta(Pubkey(counter_resource_address), is_signer=False, is_writable=True), + ], + data=bytes([0x1]), + ) + serialized = serialize_instruction(COUNTER_ID, instruction) + + tx = self.web3_client.make_raw_tx(sender.address) + instruction_tx = call_solana_caller.functions.deployStorageAndCallSolana("deploy contracts status: done", + lamports, + serialized).build_transaction(tx) + resp = self.web3_client.send_transaction(sender, instruction_tx) + assert resp["status"] == 1 + + event_logs = call_solana_caller.events.LogAddress().process_receipt(resp) + assert event_logs[0].args.value is not None + + + def test_iterative_tx_with_send_tokens(self, counter_resource_address, call_solana_caller, get_counter_value): + iterations = 29 + sender = self.accounts[0] + lamports = 0 + balance_before = self.web3_client.get_balance(call_solana_caller.address) + + instruction = Instruction( + program_id=COUNTER_ID, + accounts=[ + AccountMeta(Pubkey(counter_resource_address), is_signer=False, is_writable=True), + ], + data=bytes([0x1]), + ) + serialized = serialize_instruction(COUNTER_ID, instruction) + + tx = self.web3_client.make_raw_tx(from_=sender.address, amount=10) + + instruction_tx = call_solana_caller.functions.sendTokensAndExecuteInIterativeMode(iterations, + lamports, + serialized).build_transaction(tx) + resp = self.web3_client.send_transaction(sender, instruction_tx) + assert resp["status"] == 1 + + balance_after = self.web3_client.get_balance(call_solana_caller.address) + assert balance_after == balance_before + 10 + + def test_solana_call_of_two_programs_in_one_iterative_tx(self, counter_resource_address, call_solana_caller, sol_client, solana_account, pytestconfig, bank_account): + iterations = 10 + sender = self.accounts[0] + from_wallet = Keypair() + to_wallet = Keypair() + amount = 100000 + if pytestconfig.environment.use_bank: + sol_client.send_sol(bank_account, from_wallet.pubkey(), int(0.5 * 10**9)) + else: + sol_client.request_airdrop(from_wallet.pubkey(), 1000 * 10**9, commitment=Confirmed) + + serialized, mint, accounts_list = self.get_transfer_instruction(sol_client, from_wallet, to_wallet, amount, call_solana_caller) + + tx = self.web3_client.make_raw_tx(sender.address) + + instruction_counter = Instruction( + program_id=COUNTER_ID, + accounts=[ + AccountMeta(Pubkey(counter_resource_address), is_signer=False, is_writable=True), + ], + data=bytes([0x1]), + ) + serialized_counter = serialize_instruction(COUNTER_ID, instruction_counter) + + + instruction_tx = call_solana_caller.functions.batchExecuteInIterativeMode(iterations, + [(0, serialized), (0, serialized_counter)]).build_transaction(tx) + + resp = self.web3_client.send_transaction(sender, instruction_tx) + assert resp["status"] == 1 + assert int(mint.get_balance(accounts_list[1], commitment=Confirmed).value.amount) == amount + + wait_condition( + lambda: self.web3_client.is_trx_iterative(resp["transactionHash"].hex()) is True, + timeout_sec=60, + ) diff --git a/integration/tests/neon_evm/test_interoperability.py b/integration/tests/neon_evm/test_interoperability.py index 0dacade7b4..71a8f4aa94 100644 --- a/integration/tests/neon_evm/test_interoperability.py +++ b/integration/tests/neon_evm/test_interoperability.py @@ -15,6 +15,9 @@ from solders.system_program import ID as SYS_PROGRAM_ID from solana.transaction import Instruction, AccountMeta from spl.token.instructions import create_associated_token_account, TransferParams, transfer +from utils.layouts import FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT +from .utils.constants import TAG_FINALIZED_STATE +from .utils.transaction_checks import check_holder_account_tag from integration.tests.neon_evm.utils.call_solana import SolanaCaller @@ -64,6 +67,18 @@ def _create_mint_and_accounts(evm_loader, from_wallet, to_wallet, amount) -> tup return mint, from_token_account, to_token_account +@pytest.fixture(scope="class") +def call_solana_test_contract(operator_keypair, sender_with_tokens, evm_loader, treasury_pool): + return deploy_contract( + operator_keypair, + sender_with_tokens, + "precompiled/call_solana_test", + evm_loader, + treasury_pool, + contract_name="Test", + ) + + class TestInteroperability: @pytest.fixture(scope="function") def solana_caller( @@ -106,19 +121,18 @@ def test_execute_from_instruction_for_compute_budget(self, sender_with_tokens, s check_transaction_logs_have_text(resp, "exit_status=0x11") def test_execute_from_instruction_for_call_memo( - self, sender_with_tokens, neon_api_client, operator_keypair, evm_loader, treasury_pool, sol_client, holder_acc + self, + sender_with_tokens, + neon_api_client, + operator_keypair, + evm_loader, + treasury_pool, + sol_client, + holder_acc, + call_solana_test_contract, ): - contract = deploy_contract( - operator_keypair, - sender_with_tokens, - "precompiled/call_solana_test", - evm_loader, - treasury_pool, - contract_name="Test", - ) - data = abi.function_signature_to_4byte_selector("call_memo()") - signed_tx = make_eth_transaction(evm_loader, contract.eth_address, data, sender_with_tokens) + signed_tx = make_eth_transaction(evm_loader, call_solana_test_contract.eth_address, data, sender_with_tokens) resp = evm_loader.execute_trx_from_instruction_with_solana_call( operator_keypair, @@ -130,8 +144,8 @@ def test_execute_from_instruction_for_call_memo( sender_with_tokens.balance_account_address, SOLANA_CALL_PRECOMPILED_ID, MEMO_PROGRAM_ID, - contract.balance_account_address, - contract.solana_address, + call_solana_test_contract.balance_account_address, + call_solana_test_contract.solana_address, ], ) check_transaction_logs_have_text(resp, "exit_status=0x11") @@ -418,3 +432,92 @@ def test_call_neon_instruction_by_neon_instruction( assert "Program not allowed to call itself" in decode_logs(err.args[0].data.logs) else: assert False, f"Expected error but got {resp}" + + def test_step_from_account_for_call_memo( + self, + sender_with_tokens, + neon_api_client, + operator_keypair, + evm_loader, + treasury_pool, + sol_client, + holder_acc, + call_solana_test_contract, + ): + data = abi.function_signature_to_4byte_selector("call_memo()") + signed_tx = make_eth_transaction(evm_loader, call_solana_test_contract.eth_address, data, sender_with_tokens) + evm_loader.write_transaction_to_holder_account(signed_tx, holder_acc, operator_keypair) + resp = evm_loader.execute_transaction_steps_from_account( + operator_keypair, + treasury_pool, + holder_acc, + [ + sender_with_tokens.balance_account_address, + SOLANA_CALL_PRECOMPILED_ID, + MEMO_PROGRAM_ID, + call_solana_test_contract.balance_account_address, + call_solana_test_contract.solana_address, + ], + ) + check_transaction_logs_have_text(resp, "exit_status=0x11") + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + + def test_step_from_instruction_for_call_memo( + self, + sender_with_tokens, + neon_api_client, + operator_keypair, + evm_loader, + treasury_pool, + sol_client, + holder_acc, + call_solana_test_contract, + ): + data = abi.function_signature_to_4byte_selector("call_memo()") + signed_tx = make_eth_transaction(evm_loader, call_solana_test_contract.eth_address, data, sender_with_tokens) + resp = evm_loader.execute_transaction_steps_from_instruction( + operator_keypair, + treasury_pool, + holder_acc, + signed_tx, + [ + sender_with_tokens.balance_account_address, + SOLANA_CALL_PRECOMPILED_ID, + MEMO_PROGRAM_ID, + call_solana_test_contract.balance_account_address, + call_solana_test_contract.solana_address, + ], + ) + check_transaction_logs_have_text(resp, "exit_status=0x11") + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) + + + def test_step_from_instruction_for_counter(self, neon_api_client, sender_with_tokens, solana_caller, evm_loader, holder_acc): + iterations = 21 + resource_addr = solana_caller.create_resource(sender_with_tokens, b"123", 8, 1000000000, COUNTER_ID) + + instruction = Instruction( + program_id=COUNTER_ID, + accounts=[ + AccountMeta(resource_addr, is_signer=False, is_writable=True), + ], + data=bytes([0x1]), + ) + + serialized_instructions = serialize_instruction(COUNTER_ID, instruction) + + emulate_result = neon_api_client.emulate_contract_call( + sender_with_tokens.eth_address.hex(), + solana_caller.contract.eth_address.hex(), + "executeInIterativeMode(uint256,uint64,bytes)", + [iterations, 0, serialized_instructions], + ) + additional_accounts = [Pubkey.from_string(item["pubkey"]) for item in emulate_result["solana_accounts"]] + + resp = solana_caller.execute_iterative(COUNTER_ID, instruction, iterations, 0, holder_acc, sender_with_tokens, additional_accounts) + + check_transaction_logs_have_text(resp, "exit_status=0x11") + info: bytes = evm_loader.get_solana_account_data(resource_addr, COUNTER_ACCOUNT_LAYOUT.sizeof()) + layout = COUNTER_ACCOUNT_LAYOUT.parse(info) + assert layout.count == 1 + check_holder_account_tag(holder_acc, FINALIZED_STORAGE_ACCOUNT_INFO_LAYOUT, TAG_FINALIZED_STATE) diff --git a/integration/tests/neon_evm/utils/call_solana.py b/integration/tests/neon_evm/utils/call_solana.py index dea120b769..22f8e668d8 100644 --- a/integration/tests/neon_evm/utils/call_solana.py +++ b/integration/tests/neon_evm/utils/call_solana.py @@ -83,6 +83,24 @@ def execute(self, program_id, instruction, lamports=0, holder_acc=None, sender=N ) return resp + def execute_iterative(self, program_id, instruction, iterations, lamports=0, holder_acc=None, sender=None, additional_accounts=None): + sender = self.owner if sender is None else sender + holder_acc = self.holder_acc if holder_acc is None else holder_acc + serialized_instructions = serialize_instruction(program_id, instruction) + signed_tx = make_contract_call_trx(self.evm_loader, + sender, self.contract, "executeInIterativeMode(uint256,uint64,bytes)", [iterations, lamports, serialized_instructions] + ) + self.evm_loader.write_transaction_to_holder_account(signed_tx, holder_acc, self.operator_keypair) + + resp = self.evm_loader.execute_transaction_steps_from_account( + self.operator_keypair, + self.treasury_pool, + holder_acc, + additional_accounts + + self._get_all_pubkeys_from_instructions([instruction]), + ) + return resp + def execute_with_seed(self, program_id, instruction, seed, lamports=0, holder_acc=None, sender=None, additional_accounts=None): sender = self.owner if sender is None else sender holder_acc = self.holder_acc if holder_acc is None else holder_acc @@ -174,7 +192,8 @@ def create_resource(self, sender, salt, space, lamports, owner): resource_address_pubkey, SYSTEM_PROGRAM_ID - ] ) + ] + ) check_transaction_logs_have_text(resp, "exit_status=0x12") return resource_address_pubkey