From 4e666d5db80d4ccbf6dbbc37f896f4bc17e13b7f Mon Sep 17 00:00:00 2001 From: Anton Lisanin Date: Fri, 11 Feb 2022 14:59:29 +0300 Subject: [PATCH 01/15] #308 Support AccountV2 --- proxy/common_neon/address.py | 24 ++-- proxy/common_neon/layouts.py | 11 +- proxy/common_neon/neon_instruction.py | 73 ++++-------- proxy/common_neon/transaction_sender.py | 104 ++++++++++-------- proxy/environment.py | 10 ++ proxy/indexer/airdropper.py | 11 +- proxy/indexer/canceller.py | 27 +---- proxy/indexer/indexer.py | 37 ++++++- proxy/indexer/utils.py | 8 +- proxy/plugin/solana_rest_api.py | 13 ++- proxy/plugin/solana_rest_api_tools.py | 36 +----- proxy/testing/test_airdropper_integration.py | 10 +- proxy/testing/test_eth_sendRawTransaction.py | 10 +- proxy/testing/test_indexer_work.py | 14 +-- .../testing/test_retry_on_blocked_accounts.py | 13 +-- proxy/testing/transactions.py | 4 +- 16 files changed, 180 insertions(+), 225 deletions(-) diff --git a/proxy/common_neon/address.py b/proxy/common_neon/address.py index 8d49f4bb0..48c0e5469 100644 --- a/proxy/common_neon/address.py +++ b/proxy/common_neon/address.py @@ -3,13 +3,8 @@ from eth_keys import keys as eth_keys from hashlib import sha256 from solana.publickey import PublicKey -from spl.token.instructions import get_associated_token_address from typing import NamedTuple -from .layouts import ACCOUNT_INFO_LAYOUT -from ..environment import neon_cli, ETH_TOKEN_MINT_ID, EVM_LOADER_ID -from .constants import ACCOUNT_SEED_VERSION - class EthereumAddress: def __init__(self, data, private=None): if isinstance(data, str): @@ -34,11 +29,16 @@ def __bytes__(self): return self.data def accountWithSeed(base, seed): + from ..environment import EVM_LOADER_ID + result = PublicKey(sha256(bytes(base) + bytes(seed) + bytes(PublicKey(EVM_LOADER_ID))).digest()) return result def ether2program(ether): + from .constants import ACCOUNT_SEED_VERSION + from ..environment import EVM_LOADER_ID + if isinstance(ether, str): pass elif isinstance(ether, EthereumAddress): @@ -53,16 +53,20 @@ def ether2program(ether): return str(pda), nonce -def getTokenAddr(account): - return get_associated_token_address(PublicKey(account), ETH_TOKEN_MINT_ID) - - class AccountInfo(NamedTuple): ether: eth_keys.PublicKey + balance: int trx_count: int code_account: PublicKey @staticmethod def frombytes(data): + from .layouts import ACCOUNT_INFO_LAYOUT + cont = ACCOUNT_INFO_LAYOUT.parse(data) - return AccountInfo(cont.ether, cont.trx_count, PublicKey(cont.code_account)) + return AccountInfo( + ether=cont.ether, + balance=int.from_bytes(cont.balance, "little"), + trx_count=int.from_bytes(cont.trx_count, "little"), + code_account=PublicKey(cont.code_account) + ) diff --git a/proxy/common_neon/layouts.py b/proxy/common_neon/layouts.py index 73ff652ad..052643955 100644 --- a/proxy/common_neon/layouts.py +++ b/proxy/common_neon/layouts.py @@ -6,14 +6,14 @@ # "tag" / Int8ul, "caller" / Bytes(20), "nonce" / Int64ul, - "gas_limit" / Int64ul, - "gas_price" / Int64ul, + "gas_limit" / Bytes(32), + "gas_price" / Bytes(32), "slot" / Int64ul, "operator" / Bytes(32), "accounts_len" / Int64ul, "executor_data_size" / Int64ul, "evm_data_size" / Int64ul, - "gas_used_and_paid" / Int64ul, + "gas_used_and_paid" / Bytes(32), "number_of_payments" / Int64ul, "sign" / Bytes(65), ) @@ -23,10 +23,9 @@ "ether" / Bytes(20), "nonce" / Int8ul, "trx_count" / Bytes(8), + "balance" / Bytes(32), "code_account" / Bytes(32), "is_rw_blocked" / Int8ul, - "rw_blocked_acc" / Bytes(32), - "eth_token_account" / Bytes(32), "ro_blocked_cnt" / Int8ul, ) @@ -38,8 +37,6 @@ CREATE_ACCOUNT_LAYOUT = Struct( - "lamports" / Int64ul, - "space" / Int64ul, "ether" / Bytes(20), "nonce" / Int8ul ) diff --git a/proxy/common_neon/neon_instruction.py b/proxy/common_neon/neon_instruction.py index b1c6cb1f3..aa05cbdd5 100644 --- a/proxy/common_neon/neon_instruction.py +++ b/proxy/common_neon/neon_instruction.py @@ -1,25 +1,22 @@ -import eth_utils import struct from sha3 import keccak_256 from solana._layouts.system_instructions import SYSTEM_INSTRUCTIONS_LAYOUT, InstructionType from solana.publickey import PublicKey from solana.system_program import SYS_PROGRAM_ID -from solana.sysvar import SYSVAR_CLOCK_PUBKEY, SYSVAR_RENT_PUBKEY +from solana.sysvar import SYSVAR_RENT_PUBKEY from solana.transaction import AccountMeta, TransactionInstruction, Transaction -from spl.token.constants import ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID -from spl.token.instructions import transfer2, Transfer2Params -from typing import Tuple +from spl.token.constants import TOKEN_PROGRAM_ID +from typing import Tuple, Optional from logged_groups import logged_group -from .address import accountWithSeed, ether2program, getTokenAddr, EthereumAddress +from .address import accountWithSeed, ether2program, EthereumAddress from .constants import SYSVAR_INSTRUCTION_PUBKEY, INCINERATOR_PUBKEY, KECCAK_PROGRAM, COLLATERALL_POOL_MAX from .layouts import CREATE_ACCOUNT_LAYOUT -from ..environment import EVM_LOADER_ID, ETH_TOKEN_MINT_ID , COLLATERAL_POOL_BASE +from ..environment import EVM_LOADER_ID, COLLATERAL_POOL_BASE obligatory_accounts = [ - AccountMeta(pubkey=EVM_LOADER_ID, is_signer=False, is_writable=False), AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), ] @@ -39,10 +36,8 @@ def create_account_with_seed_layout(base, seed, lamports, space): ) -def create_account_layout(lamports, space, ether, nonce): - return bytes.fromhex("02000000")+CREATE_ACCOUNT_LAYOUT.build(dict( - lamports=lamports, - space=space, +def create_account_layout(ether, nonce): + return bytes.fromhex("18")+CREATE_ACCOUNT_LAYOUT.build(dict( ether=ether, nonce=nonce )) @@ -81,13 +76,12 @@ def make_keccak_instruction_data(check_instruction_index, msg_len, data_start): @logged_group("neon.Proxy") class NeonInstruction: - def __init__(self, operator): + def __init__(self, operator: PublicKey, operator_ether: Optional[EthereumAddress] = None): self.operator_account = operator - self.operator_neon_address = getTokenAddr(self.operator_account) + self.operator_neon_address = ether2program(operator_ether)[0] if operator_ether else None - def init_eth_trx(self, eth_trx, eth_accounts, caller_token): + def init_eth_trx(self, eth_trx, eth_accounts): self.eth_accounts = eth_accounts - self.caller_token = caller_token self.eth_trx = eth_trx @@ -126,15 +120,14 @@ def create_account_with_seed_trx(self, account, seed, lamports, space): data=create_account_with_seed_layout(self.operator_account, seed_str, lamports, space) ) - def make_create_eth_account_trx(self, eth_address: EthereumAddress, code_acc=None) -> Tuple[Transaction, PublicKey]: + def make_create_eth_account_trx(self, eth_address: EthereumAddress, code_acc=None) -> Transaction: if isinstance(eth_address, str): eth_address = EthereumAddress(eth_address) pda_account, nonce = ether2program(eth_address) - neon_token_account = getTokenAddr(PublicKey(pda_account)) - self.debug(f'Create eth account: {eth_address}, sol account: {pda_account}, neon_token_account: {neon_token_account}, nonce: {nonce}') + self.debug(f'Create eth account: {eth_address}, sol account: {pda_account}, nonce: {nonce}') base = self.operator_account - data = create_account_layout(0, 0, bytes(eth_address), nonce) + data = create_account_layout(bytes(eth_address), nonce) trx = Transaction() if code_acc is None: trx.add(TransactionInstruction( @@ -142,13 +135,8 @@ def make_create_eth_account_trx(self, eth_address: EthereumAddress, code_acc=Non data=data, keys=[ AccountMeta(pubkey=base, is_signer=True, is_writable=True), - AccountMeta(pubkey=PublicKey(pda_account), is_signer=False, is_writable=True), - AccountMeta(pubkey=neon_token_account, is_signer=False, is_writable=True), AccountMeta(pubkey=SYS_PROGRAM_ID, is_signer=False, is_writable=False), - AccountMeta(pubkey=ETH_TOKEN_MINT_ID, is_signer=False, is_writable=False), - AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), - AccountMeta(pubkey=ASSOCIATED_TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), - AccountMeta(pubkey=SYSVAR_RENT_PUBKEY, is_signer=False, is_writable=False), + AccountMeta(pubkey=PublicKey(pda_account), is_signer=False, is_writable=True), ])) else: trx.add(TransactionInstruction( @@ -156,16 +144,11 @@ def make_create_eth_account_trx(self, eth_address: EthereumAddress, code_acc=Non data=data, keys=[ AccountMeta(pubkey=base, is_signer=True, is_writable=True), + AccountMeta(pubkey=SYS_PROGRAM_ID, is_signer=False, is_writable=False), AccountMeta(pubkey=PublicKey(pda_account), is_signer=False, is_writable=True), - AccountMeta(pubkey=neon_token_account, is_signer=False, is_writable=True), AccountMeta(pubkey=PublicKey(code_acc), is_signer=False, is_writable=True), - AccountMeta(pubkey=SYS_PROGRAM_ID, is_signer=False, is_writable=False), - AccountMeta(pubkey=ETH_TOKEN_MINT_ID, is_signer=False, is_writable=False), - AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), - AccountMeta(pubkey=ASSOCIATED_TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), - AccountMeta(pubkey=SYSVAR_RENT_PUBKEY, is_signer=False, is_writable=False), ])) - return trx, neon_token_account + return trx def createERC20TokenAccountTrx(self, token_info) -> Transaction: trx = Transaction() @@ -230,9 +213,8 @@ def make_05_call_instruction(self) -> TransactionInstruction: AccountMeta(pubkey=self.operator_account, is_signer=True, is_writable=True), AccountMeta(pubkey=self.collateral_pool_address, is_signer=False, is_writable=True), AccountMeta(pubkey=self.operator_neon_address, is_signer=False, is_writable=True), - AccountMeta(pubkey=self.caller_token, is_signer=False, is_writable=True), AccountMeta(pubkey=SYS_PROGRAM_ID, is_signer=False, is_writable=False), - + AccountMeta(pubkey=EVM_LOADER_ID, is_signer=False, is_writable=False), ] + self.eth_accounts + obligatory_accounts ) @@ -248,7 +230,6 @@ def make_cancel_transaction(self, cancel_keys = None) -> Transaction: append_keys = cancel_keys else: append_keys = self.eth_accounts - append_keys.append(AccountMeta(pubkey=SYSVAR_INSTRUCTION_PUBKEY, is_signer=False, is_writable=False)) append_keys += obligatory_accounts return Transaction().add(TransactionInstruction( program_id = EVM_LOADER_ID, @@ -256,11 +237,7 @@ def make_cancel_transaction(self, cancel_keys = None) -> Transaction: keys = [ AccountMeta(pubkey=self.storage, is_signer=False, is_writable=True), AccountMeta(pubkey=self.operator_account, is_signer=True, is_writable=True), - AccountMeta(pubkey=self.operator_neon_address, is_signer=False, is_writable=True), - AccountMeta(pubkey=self.caller_token, is_signer=False, is_writable=True), AccountMeta(pubkey=INCINERATOR_PUBKEY, is_signer=False, is_writable=True), - AccountMeta(pubkey=SYS_PROGRAM_ID, is_signer=False, is_writable=False), - ] + append_keys )) @@ -276,13 +253,9 @@ def make_partial_call_or_continue_instruction(self, steps=0) -> TransactionInstr AccountMeta(pubkey=self.operator_account, is_signer=True, is_writable=True), AccountMeta(pubkey=self.collateral_pool_address, is_signer=False, is_writable=True), AccountMeta(pubkey=self.operator_neon_address, is_signer=False, is_writable=True), - AccountMeta(pubkey=self.caller_token, is_signer=False, is_writable=True), AccountMeta(pubkey=SYS_PROGRAM_ID, is_signer=False, is_writable=False), - - ] + self.eth_accounts + [ - - AccountMeta(pubkey=SYSVAR_INSTRUCTION_PUBKEY, is_signer=False, is_writable=False), - ] + obligatory_accounts + AccountMeta(pubkey=EVM_LOADER_ID, is_signer=False, is_writable=False), + ] + self.eth_accounts + obligatory_accounts ) def make_partial_call_or_continue_transaction(self, steps=0, length_before=0) -> Transaction: @@ -305,11 +278,7 @@ def make_partial_call_or_continue_from_account_data(self, steps, index=0) -> Tra AccountMeta(pubkey=self.operator_account, is_signer=True, is_writable=True), AccountMeta(pubkey=self.collateral_pool_address, is_signer=False, is_writable=True), AccountMeta(pubkey=self.operator_neon_address, is_signer=False, is_writable=True), - AccountMeta(pubkey=self.caller_token, is_signer=False, is_writable=True), AccountMeta(pubkey=SYS_PROGRAM_ID, is_signer=False, is_writable=False), - - ] + self.eth_accounts + [ - - AccountMeta(pubkey=SYSVAR_INSTRUCTION_PUBKEY, is_signer=False, is_writable=False), - ] + obligatory_accounts + AccountMeta(pubkey=EVM_LOADER_ID, is_signer=False, is_writable=False), + ] + self.eth_accounts + obligatory_accounts )) diff --git a/proxy/common_neon/transaction_sender.py b/proxy/common_neon/transaction_sender.py index 1b0ab57ff..f973c036e 100644 --- a/proxy/common_neon/transaction_sender.py +++ b/proxy/common_neon/transaction_sender.py @@ -11,14 +11,14 @@ import multiprocessing from logged_groups import logged_group -from typing import Optional +from typing import Dict, Optional from solana.transaction import AccountMeta, Transaction, PublicKey from solana.blockhash import Blockhash from solana.account import Account as SolanaAccount from solana.rpc.api import Client as SolanaClient -from ..common_neon.address import accountWithSeed, getTokenAddr, EthereumAddress +from ..common_neon.address import accountWithSeed, EthereumAddress, ether2program from ..common_neon.errors import EthereumError from .constants import STORAGE_SIZE, EMPTY_STORAGE_TAG, FINALIZED_STORAGE_TAG, ACCOUNT_SEED_VERSION from .emulator_interactor import call_emulated @@ -32,7 +32,7 @@ from ..common_neon.utils import NeonTxResultInfo, NeonTxInfo from ..environment import RETRY_ON_FAIL, EVM_LOADER_ID, PERM_ACCOUNT_LIMIT, ACCOUNT_PERMISSION_UPDATE_INT, MIN_OPERATOR_BALANCE_TO_WARN, MIN_OPERATOR_BALANCE_TO_ERR from ..memdb.memdb import MemDB, NeonPendingTxInfo -from ..environment import get_solana_accounts +from ..environment import get_solana_accounts, get_operator_ethereum_accounts from ..common_neon.account_whitelist import AccountWhitelist @@ -114,12 +114,12 @@ class NeonCreateAccountTxStage(NeonTxStage): def __init__(self, sender, account_desc): NeonTxStage.__init__(self, sender) self._address = account_desc['address'] - self.size = 256 + self.size = 95 self.balance = 0 def _create_account(self): assert self.balance > 0 - return self.s.builder.make_create_eth_account_trx(self._address)[0] + return self.s.builder.make_create_eth_account_trx(self._address) def build(self): assert self._is_empty() @@ -168,7 +168,7 @@ def __init__(self, sender, account_desc): def _create_account(self): assert self.sol_account - return self.s.builder.make_create_eth_account_trx(self._address, self.sol_account)[0] + return self.s.builder.make_create_eth_account_trx(self._address, self.sol_account) def build(self): assert self._is_empty() @@ -209,10 +209,12 @@ def build(self): class OperatorResourceInfo: - def __init__(self, signer: SolanaAccount, rid: int, idx: int): + def __init__(self, signer: SolanaAccount, ether: EthereumAddress, rid: int, idx: int): self.signer = signer + self.ether = ether self.rid = rid self.idx = idx + self.ether_key: PublicKey = None self.storage = None self.holder = None @@ -230,7 +232,7 @@ class OperatorResourceList: def __init__(self, sender: NeonTxSender): self._s = sender - self._resource = None + self._resource: Optional[OperatorResourceInfo] = None def _init_resource_list(self): if len(self._resource_list): @@ -238,9 +240,10 @@ def _init_resource_list(self): idx = 0 signer_list = get_solana_accounts() + ether_list = get_operator_ethereum_accounts() for rid in range(PERM_ACCOUNT_LIMIT): - for signer in signer_list: - info = OperatorResourceInfo(signer=signer, rid=rid, idx=idx) + for signer, ether in zip(signer_list, ether_list): + info = OperatorResourceInfo(signer=signer, ether=ether, rid=rid, idx=idx) self._resource_list.append(info) idx += 1 @@ -292,7 +295,8 @@ def _init_perm_accounts(self) -> bool: if self._check_operator_balance() is False: self._resource_list_len_glob.value -= 1 return False - if self._resource and self._resource.storage and self._resource.holder: + + if self._resource.storage and self._resource.holder and self._resource.ether_key: return True rid = self._resource.rid @@ -301,6 +305,7 @@ def _init_perm_accounts(self) -> bool: seed_list = [prefix + aid for prefix in [b"storage", b"holder"]] try: storage, holder = self._create_perm_accounts(seed_list) + self._resource.ether_key = self._create_ether_account() self._resource.storage = storage self._resource.holder = holder return True @@ -329,6 +334,31 @@ def _check_operator_balance(self): self.warning(f'Operator account {self._resource.public_key()} SOLs are running out; balance = {sol_balance}; min_operator_balance_to_warn = {min_operator_balance_to_warn}; min_operator_balance_to_err = {min_operator_balance_to_err}; ') return True + def _create_ether_account(self) -> PublicKey: + if self._resource.ether_key is not None: + return self._resource.ether_key + + rid = self._resource.rid + opkey = str(self._resource.public_key()) + + ether_address = self._resource.ether + solana_address = ether2program(ether_address)[0] + + account_info = self._s.solana.get_multiple_accounts_info([solana_address])[0] + if account_info is not None: + self.debug(f"Use existing ether account for resource {opkey}:{rid}") + return solana_address + + + stage = NeonCreateAccountTxStage(self._s, { "address": ether_address }) + stage.balance = self._s.solana.get_multiple_rent_exempt_balances_for_size([stage.size])[0] + stage.build() + + self.debug(f"Create new accounts for resource {opkey}:{rid}") + SolTxListSender(self._s, [stage.tx], NeonCreateAccountTxStage.NAME).send() + + return solana_address + def _create_perm_accounts(self, seed_list): tx = Transaction() @@ -379,12 +409,6 @@ def free_resource_info(self): self._free_resource_list_glob.append(resource.idx) - -def EthMeta(pubkey, is_writable) -> AccountMeta: - """The difference with AccountMeta that is_signer = False""" - return AccountMeta(pubkey=pubkey, is_signer=False, is_writable=is_writable) - - @logged_group("neon.Proxy") class NeonTxSender: def __init__(self, db: MemDB, client: SolanaClient, eth_tx: EthTx, steps: int): @@ -413,7 +437,7 @@ def __init__(self, db: MemDB, client: SolanaClient, eth_tx: EthTx, steps: int): self.account_txs_name = '' self._resize_contract_list = [] self._create_account_list = [] - self._eth_meta_list = [] + self._eth_meta_dict: Dict[str, AccountMeta] = dict() def execute(self) -> NeonTxResultInfo: try: @@ -426,7 +450,7 @@ def execute(self) -> NeonTxResultInfo: def set_resource(self, resource: Optional[OperatorResourceInfo]): self.resource = resource self.operator_key = resource.public_key() - self.builder = NeonIxBuilder(self.operator_key) + self.builder = NeonIxBuilder(self.operator_key, resource.ether) def clear_resource(self): self.resource = None @@ -461,9 +485,8 @@ def _validate_tx_count(self): if not info: return - nonce = int.from_bytes(info.trx_count, 'little') tx_nonce = int(self.eth_tx.nonce) - if nonce == tx_nonce: + if info.trx_count == tx_nonce: return raise EthereumError( @@ -471,7 +494,7 @@ def _validate_tx_count(self): 'Verifying nonce before send transaction: Error processing Instruction 1: invalid program argument', { 'logs': [ - f'/src/entrypoint.rs Invalid Ethereum transaction nonce: acc {nonce}, trx {tx_nonce}', + f'/src/entrypoint.rs Invalid Ethereum transaction nonce: acc {info.trx_count}, trx {tx_nonce}', ] } ) @@ -521,12 +544,13 @@ def _prepare_execution(self): self._parse_token_list() self._parse_solana_list() - self.debug('metas: ' + ', '.join([f'{m.pubkey, m.is_signer, m.is_writable}' for m in self._eth_meta_list])) + eth_meta_list = list(self._eth_meta_dict.values()) + self.debug('metas: ' + ', '.join([f'{m.pubkey, m.is_signer, m.is_writable}' for m in eth_meta_list])) # Build all instructions self._build_txs() - self.builder.init_eth_trx(self.eth_tx, self._eth_meta_list, self._caller_token) + self.builder.init_eth_trx(self.eth_tx, eth_meta_list) self.builder.init_iterative(self.resource.storage, self.resource.holder, self.resource.rid) def _call_emulated(self): @@ -545,15 +569,14 @@ def _call_emulated(self): self.steps_emulated = self._emulator_json['steps_executed'] - def _add_meta(self, pubkey: PublicKey, is_writable: bool, is_signer=False): - self._eth_meta_list.append(AccountMeta(pubkey=pubkey, is_signer=is_signer, is_writable=is_writable)) + def _add_meta(self, pubkey: PublicKey, is_writable: bool): + key = str(pubkey) + if key in self._eth_meta_dict: + self._eth_meta_dict[key].is_writable |= is_writable + else: + self._eth_meta_dict[key] = AccountMeta(pubkey=pubkey, is_signer=False, is_writable=is_writable) def _parse_accounts_list(self): - src_address = self.eth_sender - dst_address = (self.deployed_contract or self.to_address) - src_meta_list = [] - dst_meta_list = [] - for account_desc in self._emulator_json['accounts']: if account_desc['new']: if account_desc['code_size']: @@ -565,20 +588,9 @@ def _parse_accounts_list(self): elif account_desc['code_size'] and (account_desc['code_size_current'] < account_desc['code_size']): self._resize_contract_list.append(NeonResizeContractTxStage(self, account_desc)) - eth_address = account_desc['address'] - sol_account = account_desc["account"] - sol_contract = account_desc['contract'] - - if eth_address == src_address: - src_meta_list = [EthMeta(sol_account, True)] - self._caller_token = getTokenAddr(sol_account) - else: - meta_list = dst_meta_list if eth_address == dst_address else self._eth_meta_list - meta_list.append(EthMeta(sol_account, True)) - if sol_contract: - meta_list.append(EthMeta(sol_contract, account_desc['writable'])) - - self._eth_meta_list = dst_meta_list + src_meta_list + self._eth_meta_list + self._add_meta(account_desc['account'], True) + if account_desc['contract']: + self._add_meta(account_desc['contract'], account_desc['writable']) def _parse_token_list(self): for token_account in self._emulator_json['token_accounts']: @@ -588,7 +600,7 @@ def _parse_token_list(self): def _parse_solana_list(self): for account_desc in self._emulator_json['solana_accounts']: - self._add_meta(account_desc['pubkey'], account_desc['is_writable'], account_desc['is_signer']) + self._add_meta(account_desc['pubkey'], account_desc['is_writable']) def _build_txs(self): all_stages = self._create_account_list + self._resize_contract_list diff --git a/proxy/environment.py b/proxy/environment.py index e6f76ddb4..8c5009d9c 100644 --- a/proxy/environment.py +++ b/proxy/environment.py @@ -8,6 +8,8 @@ from solana.account import Account as SolanaAccount from typing import Optional, List +from proxy.common_neon.address import EthereumAddress + SOLANA_URL = os.environ.get("SOLANA_URL", "http://localhost:8899") PP_SOLANA_URL = os.environ.get("PP_SOLANA_URL", SOLANA_URL) EVM_LOADER_ID = os.environ.get("EVM_LOADER") @@ -115,6 +117,14 @@ def read_sol_account(name) -> Optional[SolanaAccount]: return signer_list +@logged_group("neon.Proxy") +def get_operator_ethereum_accounts(*, logger) -> [EthereumAddress]: + list = [] # TODO read from config + for _ in range(1, 15): + account = EthereumAddress.random() + list.append(account) + + return list @logged_group("neon.Proxy") class neon_cli(CliBase): diff --git a/proxy/indexer/airdropper.py b/proxy/indexer/airdropper.py index bce74c661..80f73ef40 100644 --- a/proxy/indexer/airdropper.py +++ b/proxy/indexer/airdropper.py @@ -151,13 +151,10 @@ def is_allowed_wrapper_contract(self, contract_addr): # helper function checking if given 'create account' corresponds to 'create erc20 token account' instruction def check_create_instr(self, account_keys, create_acc, create_token_acc): # Must use the same Ethereum account - if account_keys[create_acc['accounts'][1]] != account_keys[create_token_acc['accounts'][2]]: - return False - # Must use the same token program - if account_keys[create_acc['accounts'][5]] != account_keys[create_token_acc['accounts'][6]]: + if account_keys[create_acc['accounts'][2]] != account_keys[create_token_acc['accounts'][2]]: return False # Token program must be system token program - if account_keys[create_acc['accounts'][5]] != 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA': + if account_keys[create_token_acc['accounts'][6]] != 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA': return False # CreateERC20TokenAccount instruction must use ERC20-wrapper from whitelist if not self.is_allowed_wrapper_contract(account_keys[create_token_acc['accounts'][3]]): @@ -196,7 +193,7 @@ def find_instructions(trx, predicate): # neon.CreateAccount -> neon.CreateERC20TokenAccount -> spl.Transfer (maybe shuffled) # First: select all instructions that can form such chains predicate = lambda instr: account_keys[instr['programIdIndex']] == self.evm_loader_id \ - and base58.b58decode(instr['data'])[0] == 0x02 + and base58.b58decode(instr['data'])[0] == 0x18 create_acc_list = find_instructions(trx, predicate) predicate = lambda instr: account_keys[instr['programIdIndex']] == self.evm_loader_id \ @@ -254,7 +251,7 @@ def get_airdrop_amount_galans(self): def schedule_airdrop(self, create_acc): - eth_address = "0x" + bytearray(base58.b58decode(create_acc['data'])[20:][:20]).hex() + eth_address = "0x" + bytearray(base58.b58decode(create_acc['data'])[1:][:20]).hex() if self.airdrop_ready.is_airdrop_ready(eth_address) or eth_address in self.airdrop_scheduled: # Target account already supplied with airdrop or airdrop already scheduled return diff --git a/proxy/indexer/canceller.py b/proxy/indexer/canceller.py index 6f9dfc15e..a86b16d13 100644 --- a/proxy/indexer/canceller.py +++ b/proxy/indexer/canceller.py @@ -2,41 +2,21 @@ import traceback from logged_groups import logged_group -from solana.publickey import PublicKey from solana.rpc.api import Client -from solana.system_program import SYS_PROGRAM_ID -from solana.sysvar import SYSVAR_CLOCK_PUBKEY, SYSVAR_RENT_PUBKEY from solana.transaction import AccountMeta -from spl.token.constants import TOKEN_PROGRAM_ID -from spl.token.instructions import get_associated_token_address - -from proxy.common_neon.constants import INCINERATOR_PUBKEY, KECCAK_PROGRAM, SYSVAR_INSTRUCTION_PUBKEY from proxy.common_neon.neon_instruction import NeonInstruction from proxy.common_neon.solana_interactor import SolanaInteractor from proxy.common_neon.utils import get_from_dict -from proxy.environment import ETH_TOKEN_MINT_ID, EVM_LOADER_ID, SOLANA_URL, get_solana_accounts +from proxy.environment import SOLANA_URL, get_solana_accounts @logged_group("neon.Indexer") class Canceller: - readonly_accs = [ - PublicKey(EVM_LOADER_ID), - PublicKey(ETH_TOKEN_MINT_ID), - PublicKey(TOKEN_PROGRAM_ID), - PublicKey(SYSVAR_CLOCK_PUBKEY), - PublicKey(SYSVAR_INSTRUCTION_PUBKEY), - PublicKey(KECCAK_PROGRAM), - PublicKey(SYSVAR_RENT_PUBKEY), - PublicKey(INCINERATOR_PUBKEY), - PublicKey(SYS_PROGRAM_ID), - ] - def __init__(self): # Initialize user account self.signer = get_solana_accounts()[0] self._operator = self.signer.public_key() self._client = Client(SOLANA_URL) - self.operator_token = get_associated_token_address(PublicKey(self._operator), ETH_TOKEN_MINT_ID) self.solana = SolanaInteractor(self._client) self.builder = NeonInstruction(self._operator) @@ -49,11 +29,10 @@ def unlock_accounts(self, blocked_storages): self.error(f"Empty blocked accounts for the Neon tx {neon_tx}.") else: keys = [] - for acc in blocked_accounts: - is_writable = False if PublicKey(acc) in self.readonly_accs else True + for is_writable, acc in blocked_accounts: keys.append(AccountMeta(pubkey=acc, is_signer=False, is_writable=is_writable)) - self.builder.init_eth_trx(neon_tx.tx, None, self.operator_token) + self.builder.init_eth_trx(neon_tx.tx, None) self.builder.init_iterative(storage, None, 0) trx = self.builder.make_cancel_transaction(keys) diff --git a/proxy/indexer/indexer.py b/proxy/indexer/indexer.py index 199d8548c..2bf5fd16d 100644 --- a/proxy/indexer/indexer.py +++ b/proxy/indexer/indexer.py @@ -477,6 +477,29 @@ def execute(self) -> bool: self.state.add_account_to_db(neon_account, pda_account, code_account, self.ix.sign.slot) return True +class CreateAccount2IxDecoder(DummyIxDecoder): + def __init__(self, state: ReceiptsParserState): + DummyIxDecoder.__init__(self, 'CreateAccount2', state) + + def execute(self) -> bool: + self._decoding_start() + + if check_error(self.ix.tx): + return self._decoding_skip("Ignore failed create account") + + if len(self.ix.ix_data) < 21: + return self._decoding_skip(f'not enough data to get the Neon account {len(self.ix.ix_data)}') + + neon_account = "0x" + self.ix.ix_data[1:][:20].hex() + pda_account = self.ix.get_account(2) + code_account = self.ix.get_account(3) + if code_account == '': + code_account = None + + self.debug(f"neon_account({neon_account}), pda_account({pda_account}), code_account({code_account}), slot({self.ix.sign.slot})") + + self.state.add_account_to_db(neon_account, pda_account, code_account, self.ix.sign.slot) + return True class ResizeStorageAccountIxDecoder(DummyIxDecoder): def __init__(self, state: ReceiptsParserState): @@ -700,7 +723,10 @@ def __init__(self, solana_url, evm_loader_id): 0x13: PartialCallV02IxDecoder(self.state), 0x14: ContinueV02IxDecoder(self.state), 0x15: CancelV02IxDecoder(self.state), - 0x16: ExecuteTrxFromAccountV02IxDecoder(self.state) + 0x16: ExecuteTrxFromAccountV02IxDecoder(self.state), + 0x17: DummyIxDecoder('UpdateValidsTable', self.state), + 0x18: CreateAccount2IxDecoder(self.state), + 0x19: DummyIxDecoder('Deposit', self.state), } self.def_decoder = DummyIxDecoder('Unknown', self.state) @@ -791,12 +817,17 @@ def unlock_accounts(self, tx) -> bool: self.warning(f"Transaction {tx.neon_tx} has empty storage.") return False - if storage_accounts_list != tx.blocked_accounts: + if len(storage_accounts_list) != len(tx.blocked_accounts): self.warning(f"Transaction {tx.neon_tx} has another list of accounts than storage.") return False + for (writable, account), tx_account in zip(storage_accounts_list, tx.blocked_accounts): + if account != tx_account: + self.warning(f"Transaction {tx.neon_tx} has another list of accounts than storage.") + return False + self.debug(f'Neon tx is blocked: storage {tx.storage_account}, {tx.neon_tx}') - self.blocked_storages[tx.storage_account] = (tx.neon_tx, tx.blocked_accounts) + self.blocked_storages[tx.storage_account] = (tx.neon_tx, storage_accounts_list) tx.canceled = True return True diff --git a/proxy/indexer/utils.py b/proxy/indexer/utils.py index 4cc8c1610..705a27b79 100644 --- a/proxy/indexer/utils.py +++ b/proxy/indexer/utils.py @@ -46,7 +46,7 @@ def get_req_id(self): @logged_group("neon.Indexer") -def get_accounts_from_storage(client, storage_account, *, logger): +def get_accounts_from_storage(client, storage_account, *, logger) -> [(bool, str)]: opts = { "encoding": "base64", "commitment": "confirmed", @@ -75,10 +75,14 @@ def get_accounts_from_storage(client, storage_account, *, logger): storage = STORAGE_ACCOUNT_INFO_LAYOUT.parse(data[1:]) offset = 1 + STORAGE_ACCOUNT_INFO_LAYOUT.sizeof() for _ in range(storage.accounts_len): + writable = (data[offset] > 0) + offset += 1 + some_pubkey = PublicKey(data[offset:offset + 32]) - acc_list.append(str(some_pubkey)) offset += 32 + acc_list.append((writable, str(some_pubkey))) + return acc_list else: logger.debug("Not empty other") diff --git a/proxy/plugin/solana_rest_api.py b/proxy/plugin/solana_rest_api.py index 216836271..6b1f5836a 100644 --- a/proxy/plugin/solana_rest_api.py +++ b/proxy/plugin/solana_rest_api.py @@ -27,7 +27,7 @@ from solana.rpc.api import Client as SolanaClient from typing import List, Tuple -from .solana_rest_api_tools import neon_config_load, get_token_balance_or_zero, estimate_gas +from .solana_rest_api_tools import neon_config_load, estimate_gas from ..common_neon.transaction_sender import NeonTxSender from ..common_neon.solana_interactor import SolanaInteractor from ..common_neon.address import EthereumAddress @@ -134,8 +134,13 @@ def eth_getBalance(self, account, tag): """ eth_acc = EthereumAddress(account) self.debug(f'eth_getBalance: {account} {eth_acc}') - balance = get_token_balance_or_zero(self._client, eth_acc) - return hex(balance * eth_utils.denoms.gwei) + try: + solana = SolanaInteractor(self._client) + acc_info = solana.get_neon_account_info(eth_acc) + return hex(acc_info.balance) + except Exception as err: + self.debug(f"eth_getBalance: Can't get account info: {err}") + return hex(0) def eth_getLogs(self, obj): def to_list(items): @@ -276,7 +281,7 @@ def eth_getTransactionCount(self, account, tag): try: solana = SolanaInteractor(self._client) acc_info = solana.get_neon_account_info(EthereumAddress(account)) - return hex(int.from_bytes(acc_info.trx_count, 'little')) + return hex(acc_info.trx_count) except Exception as err: self.debug(f"eth_getTransactionCount: Can't get account info: {err}") return hex(0) diff --git a/proxy/plugin/solana_rest_api_tools.py b/proxy/plugin/solana_rest_api_tools.py index 71676d034..f1a964df0 100644 --- a/proxy/plugin/solana_rest_api_tools.py +++ b/proxy/plugin/solana_rest_api_tools.py @@ -7,8 +7,7 @@ from solana.rpc.commitment import Confirmed from logged_groups import logged_group -from ..common_neon.address import ether2program, getTokenAddr, EthereumAddress -from ..common_neon.errors import SolanaAccountNotFoundError, SolanaErrors +from ..common_neon.address import ether2program, EthereumAddress from ..common_neon.emulator_interactor import call_emulated from ..common_neon.utils import get_from_dict from ..environment import read_elf_params, TIMEOUT_TO_RELOAD_NEON_CONFIG, EXTRA_GAS @@ -37,39 +36,6 @@ def neon_config_load(ethereum_model, *, logger): logger.debug(ethereum_model.neon_config_dict) -@logged_group("neon.Proxy") -def get_token_balance_gwei(client: SolanaClient, pda_account: str, *, logger) -> int: - neon_token_account = getTokenAddr(PublicKey(pda_account)) - rpc_response = client.get_token_account_balance(neon_token_account, commitment=Confirmed) - error = rpc_response.get('error') - if error is not None: - message = error.get("message") - if message == SolanaErrors.AccountNotFound.value: - raise SolanaAccountNotFoundError() - logger.error(f"Failed to get_token_balance_gwei by neon_token_account: {neon_token_account}, " - f"got get_token_account_balance error: \"{message}\"") - raise Exception("Getting balance error") - - balance = get_from_dict(rpc_response, "result", "value", "amount") - if balance is None: - logger.error( - f"Failed to get_token_balance_gwei by neon_token_account: {neon_token_account}, response: {rpc_response}") - raise Exception("Unexpected get_balance response") - return int(balance) - - -@logged_group("neon.Proxy") -def get_token_balance_or_zero(client: SolanaClient, eth_account: EthereumAddress, *, logger) -> int: - solana_account, nonce = ether2program(eth_account) - logger.debug(f"Get balance for eth account: {eth_account} aka: {solana_account}") - - try: - return get_token_balance_gwei(client, solana_account) - except SolanaAccountNotFoundError: - logger.debug(f"Account not found: {eth_account} aka: {solana_account} - return airdrop amount") - return 0 - - def is_account_exists(client: SolanaClient, eth_account: EthereumAddress) -> bool: pda_account, nonce = ether2program(eth_account) info = client.get_account_info(pda_account, commitment=Confirmed) diff --git a/proxy/testing/test_airdropper_integration.py b/proxy/testing/test_airdropper_integration.py index 75be3338a..74938e781 100644 --- a/proxy/testing/test_airdropper_integration.py +++ b/proxy/testing/test_airdropper_integration.py @@ -75,19 +75,13 @@ def deploy_erc20_wrapper_contract(self): def create_account_instruction(self, eth_address: str, payer: PublicKey): dest_address_solana, nonce = get_evm_loader_account_address(eth_address) - neon_token_account = get_associated_token_address(dest_address_solana, ETH_TOKEN_MINT_ID) return TransactionInstruction( program_id=EVM_LOADER_ID, - data=create_account_layout(0, 0, bytes.fromhex(eth_address[2:]), nonce), + data=create_account_layout(bytes.fromhex(eth_address[2:]), nonce), keys=[ AccountMeta(pubkey=payer, is_signer=True, is_writable=True), - AccountMeta(pubkey=dest_address_solana, is_signer=False, is_writable=True), - AccountMeta(pubkey=neon_token_account, is_signer=False, is_writable=True), AccountMeta(pubkey=SYS_PROGRAM_ID, is_signer=False, is_writable=False), - AccountMeta(pubkey=ETH_TOKEN_MINT_ID, is_signer=False, is_writable=False), - AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), - AccountMeta(pubkey=ASSOCIATED_TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), - AccountMeta(pubkey=SYSVAR_RENT_PUBKEY, is_signer=False, is_writable=False), + AccountMeta(pubkey=dest_address_solana, is_signer=False, is_writable=True), ]) def create_sol_account(self): diff --git a/proxy/testing/test_eth_sendRawTransaction.py b/proxy/testing/test_eth_sendRawTransaction.py index d06e51be6..4231922be 100644 --- a/proxy/testing/test_eth_sendRawTransaction.py +++ b/proxy/testing/test_eth_sendRawTransaction.py @@ -327,7 +327,7 @@ def test_06_transfer_one_and_a_half_gweis(self): gas=987654321, gasPrice=0, to=eth_account_alice.address, - value=eth_utils.denoms.gwei), + value=2 * eth_utils.denoms.gwei), eth_account.key ) @@ -344,7 +344,7 @@ def test_06_transfer_one_and_a_half_gweis(self): gas=987654321, gasPrice=0, to=eth_account_bob.address, - value=eth_utils.denoms.gwei), + value=2 * eth_utils.denoms.gwei), eth_account.key ) @@ -381,10 +381,8 @@ def test_06_transfer_one_and_a_half_gweis(self): bob_balance_after_transfer = proxy.eth.get_balance(eth_account_bob.address) print('alice_balance_after_transfer:', alice_balance_after_transfer) print('bob_balance_after_transfer:', bob_balance_after_transfer) - print('check https://github.com/neonlabsorg/neon-evm/issues/210') - print('one_gwei:', eth_utils.denoms.gwei) - self.assertEqual(alice_balance_after_transfer, alice_balance_before_transfer - eth_utils.denoms.gwei) - self.assertEqual(bob_balance_after_transfer, bob_balance_before_transfer + eth_utils.denoms.gwei) + self.assertEqual(alice_balance_after_transfer, alice_balance_before_transfer - one_and_a_half_gweis) + self.assertEqual(bob_balance_after_transfer, bob_balance_before_transfer + one_and_a_half_gweis) @unittest.skip("a.i.") def test_07_execute_long_transaction(self): diff --git a/proxy/testing/test_indexer_work.py b/proxy/testing/test_indexer_work.py index 85b2ff7f6..317d1a585 100644 --- a/proxy/testing/test_indexer_work.py +++ b/proxy/testing/test_indexer_work.py @@ -190,22 +190,16 @@ def sol_instr_19_partial_call(self, storage_account, step_count, evm_instruction AccountMeta(pubkey=self.acc.public_key(), is_signer=True, is_writable=True), # Collateral pool address: AccountMeta(pubkey=self.collateral_pool_address, is_signer=False, is_writable=True), - # Operator's NEON token account: - AccountMeta(pubkey=get_associated_token_address(self.acc.public_key(), ETH_TOKEN_MINT_ID), is_signer=False, is_writable=True), - # User's NEON token account: - AccountMeta(pubkey=self.caller_token, is_signer=False, is_writable=True), + # Operator's NEON token account: pay gas to caller + AccountMeta(pubkey=self.caller, is_signer=False, is_writable=True), # System program account: AccountMeta(pubkey=PublicKey(SYS_PROGRAM_ID), is_signer=False, is_writable=False), + # NeonEVM program account: + AccountMeta(pubkey=self.loader.loader_id, is_signer=False, is_writable=False), AccountMeta(pubkey=self.reId, is_signer=False, is_writable=True), - AccountMeta(pubkey=self.reId_token, is_signer=False, is_writable=True), AccountMeta(pubkey=self.re_code, is_signer=False, is_writable=True), AccountMeta(pubkey=self.caller, is_signer=False, is_writable=True), - AccountMeta(pubkey=self.caller_token, is_signer=False, is_writable=True), - - AccountMeta(pubkey=self.loader.loader_id, is_signer=False, is_writable=False), - AccountMeta(pubkey=ETH_TOKEN_MINT_ID, is_signer=False, is_writable=False), - AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), ]) def call_begin(self, storage, steps, msg, instruction): diff --git a/proxy/testing/test_retry_on_blocked_accounts.py b/proxy/testing/test_retry_on_blocked_accounts.py index ac0d4601a..45ed532d4 100644 --- a/proxy/testing/test_retry_on_blocked_accounts.py +++ b/proxy/testing/test_retry_on_blocked_accounts.py @@ -206,21 +206,16 @@ def sol_instr_partial_call_or_continue(self, storage_account, step_count, evm_in AccountMeta(pubkey=self.acc.public_key(), is_signer=True, is_writable=True), # Collateral pool address: AccountMeta(pubkey=self.collateral_pool_address, is_signer=False, is_writable=True), - # Operator's NEON token account: - AccountMeta(pubkey=get_associated_token_address(self.acc.public_key(), ETH_TOKEN_MINT_ID), is_signer=False, is_writable=True), - # User's NEON token account: - AccountMeta(pubkey=self.caller_token, is_signer=False, is_writable=True), + # Operator's NEON token account: pay gas to caller + AccountMeta(pubkey=self.caller, is_signer=False, is_writable=True), # System program account: AccountMeta(pubkey=PublicKey(SYS_PROGRAM_ID), is_signer=False, is_writable=False), + # NeonEVM program account: + AccountMeta(pubkey=self.loader.loader_id, is_signer=False, is_writable=False), AccountMeta(pubkey=self.reId, is_signer=False, is_writable=True), AccountMeta(pubkey=self.re_code, is_signer=False, is_writable=True), AccountMeta(pubkey=self.caller, is_signer=False, is_writable=True), - AccountMeta(pubkey=self.caller_token, is_signer=False, is_writable=True), - - AccountMeta(pubkey=self.loader.loader_id, is_signer=False, is_writable=False), - AccountMeta(pubkey=ETH_TOKEN_MINT_ID, is_signer=False, is_writable=False), - AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), ]) diff --git a/proxy/testing/transactions.py b/proxy/testing/transactions.py index b5eec0e2d..6a756fa0c 100644 --- a/proxy/testing/transactions.py +++ b/proxy/testing/transactions.py @@ -181,8 +181,8 @@ }, 'instructions': [ { - 'accounts': [0, 1, 2, 7, 8, 9, 10, 11], - 'data': 'SSX8YzB3JHrjo6vdi3AMoi7zpcPrQv1EF4uXaNUgBSq2E3sfp7PKeAH', + 'accounts': [0, 7, 1, 3], + 'data': '7mzxmWe9X6hGhfpmntNbEtuDfUgC9T', 'programIdIndex': 12 }, { From 60073eda9f1845d413e4a7571b85ee1d2c6e7c7a Mon Sep 17 00:00:00 2001 From: Anton Lisanin Date: Fri, 11 Feb 2022 15:00:08 +0300 Subject: [PATCH 02/15] Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 95ae8fcdf..cdb744ce1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ ARG SOLANA_REVISION=v1.8.12-testnet -ARG EVM_LOADER_REVISION=latest +ARG EVM_LOADER_REVISION=ci-308-account_v2 FROM neonlabsorg/solana:${SOLANA_REVISION} AS cli From 0f40e4b6dd8723c2f06ef93e214e4df6fc6d038e Mon Sep 17 00:00:00 2001 From: Ivan Loboda Date: Mon, 14 Feb 2022 20:17:59 +0300 Subject: [PATCH 03/15] add test file --- proxy/testing/test_neon_token.py | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 proxy/testing/test_neon_token.py diff --git a/proxy/testing/test_neon_token.py b/proxy/testing/test_neon_token.py new file mode 100644 index 000000000..19cc57a88 --- /dev/null +++ b/proxy/testing/test_neon_token.py @@ -0,0 +1,52 @@ +import unittest +from solcx import compile_source +from web3 import Web3 +import os +from .testing_helpers import request_airdrop + +NEON_TOKEN_CONTRACT = ''' +// SPDX-License-Identifier: MIT +pragma solidity >=0.5.12; + +interface INeon { + function withdraw(bytes32 spender) external payable returns (bool); +} + +contract NeonToken is INeon { + address constant NeonPrecompiled = 0xFF00000000000000000000000000000000000003; + + function withdraw(bytes32 spender) public override payable returns (bool) { + return INeon(NeonPrecompiled).withdraw{value: msg.value}(spender); + } +} +''' + +proxy_url = os.environ.get('PROXY_URL', 'http://127.0.0.1:9090/solana') +proxy = Web3(Web3.HTTPProvider(proxy_url)) +SEED = 'TestNeonToken' +eth_account = proxy.eth.account.create(SEED) +proxy.eth.default_account = eth_account.address + +class TestNeonToken(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + artifacts = compile_source(NEON_TOKEN_CONTRACT) + _, cls.neon_token = artifacts.popitem() + cls.deploy_contract(cls) + + def deploy_contract(self): + erc20 = proxy.eth.contract(abi=self.neon_token['abi'], bytecode=self.neon_token['bin']) + nonce = proxy.eth.get_transaction_count(proxy.eth.default_account) + tx = {'nonce': nonce} + tx_constructor = erc20.constructor().buildTransaction(tx) + tx_deploy = proxy.eth.account.sign_transaction(tx_constructor, eth_account.key) + tx_deploy_hash = proxy.eth.send_raw_transaction(tx_deploy.rawTransaction) + self.debug(f'tx_deploy_hash: {tx_deploy_hash.hex()}') + tx_deploy_receipt = proxy.eth.wait_for_transaction_receipt(tx_deploy_hash) + self.debug(f'tx_deploy_receipt: {tx_deploy_receipt}') + self.debug(f'deploy status: {tx_deploy_receipt.status}') + self.neon_token_address = tx_deploy_receipt.contractAddress + self.debug(f'NeonToken contract address is: {self.neon_token_address}') + + def test_success_call_withdraw(self): + print('IMPLEMENT ME!') \ No newline at end of file From 6c1c7e9b570bdb15b2ac1d74755d4a3acb3a955a Mon Sep 17 00:00:00 2001 From: Ivan Loboda Date: Tue, 15 Feb 2022 10:11:39 +0300 Subject: [PATCH 04/15] remove not used file --- proxy/testing/test_price_provider.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 proxy/testing/test_price_provider.py diff --git a/proxy/testing/test_price_provider.py b/proxy/testing/test_price_provider.py deleted file mode 100644 index e69de29bb..000000000 From 3052ea86a1d42488b185ed620264b39597c767c0 Mon Sep 17 00:00:00 2001 From: Ivan Loboda Date: Tue, 15 Feb 2022 22:28:10 +0300 Subject: [PATCH 05/15] just compiles and executes emulator --- proxy/testing/test_neon_token.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/proxy/testing/test_neon_token.py b/proxy/testing/test_neon_token.py index 19cc57a88..6718c3f4b 100644 --- a/proxy/testing/test_neon_token.py +++ b/proxy/testing/test_neon_token.py @@ -3,24 +3,23 @@ from web3 import Web3 import os from .testing_helpers import request_airdrop +from solana.account import Account as SolanaAccount NEON_TOKEN_CONTRACT = ''' // SPDX-License-Identifier: MIT pragma solidity >=0.5.12; -interface INeon { - function withdraw(bytes32 spender) external payable returns (bool); -} - -contract NeonToken is INeon { +contract NeonToken { address constant NeonPrecompiled = 0xFF00000000000000000000000000000000000003; - function withdraw(bytes32 spender) public override payable returns (bool) { - return INeon(NeonPrecompiled).withdraw{value: msg.value}(spender); + function withdraw(bytes32 spender) override public payable returns (bool) { + (bool success, bytes memory returnData) = NeonPrecompiled.delegatecall(abi.encodeWithSignature("withdraw(bytes32)", spender)); + return success; } } ''' + proxy_url = os.environ.get('PROXY_URL', 'http://127.0.0.1:9090/solana') proxy = Web3(Web3.HTTPProvider(proxy_url)) SEED = 'TestNeonToken' @@ -33,6 +32,8 @@ def setUpClass(cls) -> None: artifacts = compile_source(NEON_TOKEN_CONTRACT) _, cls.neon_token = artifacts.popitem() cls.deploy_contract(cls) + print(f"default eth account: {eth_account.address}") + request_airdrop(eth_account.address) def deploy_contract(self): erc20 = proxy.eth.contract(abi=self.neon_token['abi'], bytecode=self.neon_token['bin']) @@ -41,12 +42,18 @@ def deploy_contract(self): tx_constructor = erc20.constructor().buildTransaction(tx) tx_deploy = proxy.eth.account.sign_transaction(tx_constructor, eth_account.key) tx_deploy_hash = proxy.eth.send_raw_transaction(tx_deploy.rawTransaction) - self.debug(f'tx_deploy_hash: {tx_deploy_hash.hex()}') + print(f'tx_deploy_hash: {tx_deploy_hash.hex()}') tx_deploy_receipt = proxy.eth.wait_for_transaction_receipt(tx_deploy_hash) - self.debug(f'tx_deploy_receipt: {tx_deploy_receipt}') - self.debug(f'deploy status: {tx_deploy_receipt.status}') + print(f'tx_deploy_receipt: {tx_deploy_receipt}') + print(f'deploy status: {tx_deploy_receipt.status}') self.neon_token_address = tx_deploy_receipt.contractAddress - self.debug(f'NeonToken contract address is: {self.neon_token_address}') + print(f'NeonToken contract address is: {self.neon_token_address}') + self.neon_contract = proxy.eth.contract(address=self.neon_token_address, abi=self.neon_token['abi']) def test_success_call_withdraw(self): - print('IMPLEMENT ME!') \ No newline at end of file + dest_acc = SolanaAccount() + print(f"Try to withdraw NEON tokens to solana account {dest_acc.public_key()}") + amount = pow(10, 18) # 1 NEON + withdraw_func = self.neon_contract.functions.withdraw(bytes(dest_acc.public_key())) + result = withdraw_func.transact({ "value": amount }) + print(result) \ No newline at end of file From bdf37c087b7cb5f47712c6f43a1473686fabee10 Mon Sep 17 00:00:00 2001 From: Ivan Loboda Date: Tue, 15 Feb 2022 22:42:19 +0300 Subject: [PATCH 06/15] Fix NeonToken account, send method call transaction with amount --- proxy/testing/test_neon_token.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/proxy/testing/test_neon_token.py b/proxy/testing/test_neon_token.py index 6718c3f4b..3bfeb4a6e 100644 --- a/proxy/testing/test_neon_token.py +++ b/proxy/testing/test_neon_token.py @@ -12,7 +12,7 @@ contract NeonToken { address constant NeonPrecompiled = 0xFF00000000000000000000000000000000000003; - function withdraw(bytes32 spender) override public payable returns (bool) { + function withdraw(bytes32 spender) public payable returns (bool) { (bool success, bytes memory returnData) = NeonPrecompiled.delegatecall(abi.encodeWithSignature("withdraw(bytes32)", spender)); return success; } @@ -37,7 +37,7 @@ def setUpClass(cls) -> None: def deploy_contract(self): erc20 = proxy.eth.contract(abi=self.neon_token['abi'], bytecode=self.neon_token['bin']) - nonce = proxy.eth.get_transaction_count(proxy.eth.default_account) + nonce = proxy.eth.get_transaction_count(eth_account.address) tx = {'nonce': nonce} tx_constructor = erc20.constructor().buildTransaction(tx) tx_deploy = proxy.eth.account.sign_transaction(tx_constructor, eth_account.key) @@ -54,6 +54,13 @@ def test_success_call_withdraw(self): dest_acc = SolanaAccount() print(f"Try to withdraw NEON tokens to solana account {dest_acc.public_key()}") amount = pow(10, 18) # 1 NEON - withdraw_func = self.neon_contract.functions.withdraw(bytes(dest_acc.public_key())) - result = withdraw_func.transact({ "value": amount }) - print(result) \ No newline at end of file + + nonce = proxy.eth.get_transaction_count(eth_account.address) + tx = {'value': amount, 'nonce': nonce} + withdraw_tx_dict = self.neon_contract.functions.withdraw(bytes(dest_acc.public_key())).buildTransaction(tx) + withdraw_tx = proxy.eth.account.sign_transaction(withdraw_tx_dict, eth_account.key) + withdraw_tx_hash = proxy.eth.send_raw_transaction(withdraw_tx.rawTransaction) + print(f'withdraw_tx_hash: {withdraw_tx_hash.hex()}') + withdraw_tx_receipt = proxy.eth.wait_for_transaction_receipt(withdraw_tx_hash) + print(f'withdraw_tx_receipt: {withdraw_tx_receipt}') + print(f'deploy status: {withdraw_tx_receipt.status}') \ No newline at end of file From 33d6147d3cfeb9104276bbed546ab99595de4388 Mon Sep 17 00:00:00 2001 From: Ivan Loboda Date: Wed, 16 Feb 2022 01:06:30 +0300 Subject: [PATCH 07/15] check balances before and after --- proxy/testing/test_neon_token.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/proxy/testing/test_neon_token.py b/proxy/testing/test_neon_token.py index 3bfeb4a6e..cc3c630e3 100644 --- a/proxy/testing/test_neon_token.py +++ b/proxy/testing/test_neon_token.py @@ -4,6 +4,11 @@ import os from .testing_helpers import request_airdrop from solana.account import Account as SolanaAccount +from solana.rpc.api import Client as SolanaClient +from spl.token.client import Token as SplToken +from proxy.environment import ETH_TOKEN_MINT_ID +from spl.token.constants import TOKEN_PROGRAM_ID +from solana.rpc.commitment import Confirmed NEON_TOKEN_CONTRACT = ''' // SPDX-License-Identifier: MIT @@ -21,7 +26,9 @@ proxy_url = os.environ.get('PROXY_URL', 'http://127.0.0.1:9090/solana') +solana_url = os.environ.get('SOLANA_URL', 'http://solana:8899/') proxy = Web3(Web3.HTTPProvider(proxy_url)) +solana = SolanaClient(solana_url) SEED = 'TestNeonToken' eth_account = proxy.eth.account.create(SEED) proxy.eth.default_account = eth_account.address @@ -29,17 +36,21 @@ class TestNeonToken(unittest.TestCase): @classmethod def setUpClass(cls) -> None: - artifacts = compile_source(NEON_TOKEN_CONTRACT) - _, cls.neon_token = artifacts.popitem() + cls.sol_payer = SolanaAccount() cls.deploy_contract(cls) + cls.spl_neon_token = SplToken(solana, ETH_TOKEN_MINT_ID, TOKEN_PROGRAM_ID, cls.sol_payer) print(f"default eth account: {eth_account.address}") request_airdrop(eth_account.address) def deploy_contract(self): - erc20 = proxy.eth.contract(abi=self.neon_token['abi'], bytecode=self.neon_token['bin']) + artifacts = compile_source(NEON_TOKEN_CONTRACT) + _, self.neon_token_iface = artifacts.popitem() + + self.neon_contract = proxy.eth.contract(abi=self.neon_token_iface['abi'], + bytecode=self.neon_token_iface['bin']) nonce = proxy.eth.get_transaction_count(eth_account.address) tx = {'nonce': nonce} - tx_constructor = erc20.constructor().buildTransaction(tx) + tx_constructor = self.neon_contract.constructor().buildTransaction(tx) tx_deploy = proxy.eth.account.sign_transaction(tx_constructor, eth_account.key) tx_deploy_hash = proxy.eth.send_raw_transaction(tx_deploy.rawTransaction) print(f'tx_deploy_hash: {tx_deploy_hash.hex()}') @@ -48,13 +59,17 @@ def deploy_contract(self): print(f'deploy status: {tx_deploy_receipt.status}') self.neon_token_address = tx_deploy_receipt.contractAddress print(f'NeonToken contract address is: {self.neon_token_address}') - self.neon_contract = proxy.eth.contract(address=self.neon_token_address, abi=self.neon_token['abi']) + self.neon_contract = proxy.eth.contract(address=self.neon_token_address, + abi=self.neon_token_iface['abi']) def test_success_call_withdraw(self): dest_acc = SolanaAccount() print(f"Try to withdraw NEON tokens to solana account {dest_acc.public_key()}") amount = pow(10, 18) # 1 NEON + print(f'Source account balance before: {proxy.eth.get_balance(eth_account.address)}') + print(f'Destination account balance before: {self.spl_neon_token.get_balance(dest_acc.public_key(), commitment=Confirmed)}') + nonce = proxy.eth.get_transaction_count(eth_account.address) tx = {'value': amount, 'nonce': nonce} withdraw_tx_dict = self.neon_contract.functions.withdraw(bytes(dest_acc.public_key())).buildTransaction(tx) @@ -63,4 +78,7 @@ def test_success_call_withdraw(self): print(f'withdraw_tx_hash: {withdraw_tx_hash.hex()}') withdraw_tx_receipt = proxy.eth.wait_for_transaction_receipt(withdraw_tx_hash) print(f'withdraw_tx_receipt: {withdraw_tx_receipt}') - print(f'deploy status: {withdraw_tx_receipt.status}') \ No newline at end of file + print(f'deploy status: {withdraw_tx_receipt.status}') + + print(f'Source account balance after: {proxy.eth.get_balance(eth_account.address)}') + print(f'Destination account balance after: {self.spl_neon_token.get_balance(dest_acc.public_key(), commitment=Confirmed)}') \ No newline at end of file From ee250bbc7c3f3806851bf74d55fd8b76f27762db Mon Sep 17 00:00:00 2001 From: Ivan Loboda Date: Thu, 17 Feb 2022 23:09:59 +0300 Subject: [PATCH 08/15] add some logging --- proxy/testing/test_neon_token.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/proxy/testing/test_neon_token.py b/proxy/testing/test_neon_token.py index cc3c630e3..5fa2049c8 100644 --- a/proxy/testing/test_neon_token.py +++ b/proxy/testing/test_neon_token.py @@ -6,6 +6,7 @@ from solana.account import Account as SolanaAccount from solana.rpc.api import Client as SolanaClient from spl.token.client import Token as SplToken +from spl.token.instructions import get_associated_token_address from proxy.environment import ETH_TOKEN_MINT_ID from spl.token.constants import TOKEN_PROGRAM_ID from solana.rpc.commitment import Confirmed @@ -62,16 +63,21 @@ def deploy_contract(self): self.neon_contract = proxy.eth.contract(address=self.neon_token_address, abi=self.neon_token_iface['abi']) - def test_success_call_withdraw(self): + def test_success_withdraw(self): dest_acc = SolanaAccount() print(f"Try to withdraw NEON tokens to solana account {dest_acc.public_key()}") - amount = pow(10, 18) # 1 NEON + # creating destination accout by airdropping SOL + solana.request_airdrop(dest_acc.public_key(), 1000_000_000_000) + dest_token_acc = get_associated_token_address(dest_acc.public_key(), ETH_TOKEN_MINT_ID) + print(f"Destination token account: {dest_token_acc}") + + withdraw_amount = pow(10, 18) # 1 NEON print(f'Source account balance before: {proxy.eth.get_balance(eth_account.address)}') - print(f'Destination account balance before: {self.spl_neon_token.get_balance(dest_acc.public_key(), commitment=Confirmed)}') + print(f'Destination account balance before: {self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed)}') nonce = proxy.eth.get_transaction_count(eth_account.address) - tx = {'value': amount, 'nonce': nonce} + tx = {'value': withdraw_amount, 'nonce': nonce} withdraw_tx_dict = self.neon_contract.functions.withdraw(bytes(dest_acc.public_key())).buildTransaction(tx) withdraw_tx = proxy.eth.account.sign_transaction(withdraw_tx_dict, eth_account.key) withdraw_tx_hash = proxy.eth.send_raw_transaction(withdraw_tx.rawTransaction) @@ -81,4 +87,4 @@ def test_success_call_withdraw(self): print(f'deploy status: {withdraw_tx_receipt.status}') print(f'Source account balance after: {proxy.eth.get_balance(eth_account.address)}') - print(f'Destination account balance after: {self.spl_neon_token.get_balance(dest_acc.public_key(), commitment=Confirmed)}') \ No newline at end of file + print(f'Destination account balance after: {self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed)}') \ No newline at end of file From dba7a201addb0d00fa69ec390e79d143d61f3f18 Mon Sep 17 00:00:00 2001 From: Ivan Loboda Date: Mon, 21 Feb 2022 18:35:10 +0300 Subject: [PATCH 09/15] Improve test --- proxy/testing/test_neon_token.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/proxy/testing/test_neon_token.py b/proxy/testing/test_neon_token.py index 5fa2049c8..acaaabab9 100644 --- a/proxy/testing/test_neon_token.py +++ b/proxy/testing/test_neon_token.py @@ -71,13 +71,18 @@ def test_success_withdraw(self): dest_token_acc = get_associated_token_address(dest_acc.public_key(), ETH_TOKEN_MINT_ID) print(f"Destination token account: {dest_token_acc}") - withdraw_amount = pow(10, 18) # 1 NEON + withdraw_amount_alan = pow(10, 18) # 1 NEON + withdraw_amount_galan = int(withdraw_amount_alan / 1_000_000_000) - print(f'Source account balance before: {proxy.eth.get_balance(eth_account.address)}') - print(f'Destination account balance before: {self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed)}') + source_balance_before_alan = proxy.eth.get_balance(eth_account.address) + destination_balance_before_galan = self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed) + print(f'Source account balance before (Alan): {source_balance_before_alan}') + print(f'Destination account balance before (Galan): {destination_balance_before_galan}') + + self.assertTrue(destination_balance_before_galan['error'] is not None) nonce = proxy.eth.get_transaction_count(eth_account.address) - tx = {'value': withdraw_amount, 'nonce': nonce} + tx = {'value': withdraw_amount_alan, 'nonce': nonce} withdraw_tx_dict = self.neon_contract.functions.withdraw(bytes(dest_acc.public_key())).buildTransaction(tx) withdraw_tx = proxy.eth.account.sign_transaction(withdraw_tx_dict, eth_account.key) withdraw_tx_hash = proxy.eth.send_raw_transaction(withdraw_tx.rawTransaction) @@ -86,5 +91,9 @@ def test_success_withdraw(self): print(f'withdraw_tx_receipt: {withdraw_tx_receipt}') print(f'deploy status: {withdraw_tx_receipt.status}') - print(f'Source account balance after: {proxy.eth.get_balance(eth_account.address)}') - print(f'Destination account balance after: {self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed)}') \ No newline at end of file + source_balance_after_alan = proxy.eth.get_balance(eth_account.address) + destination_balance_after_galan = int(self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed)['result']['value']['amount']) + print(f'Source account balance after (Alan): {source_balance_after_alan}') + print(f'Destination account balance after (Galan): {destination_balance_after_galan}') + self.assertEqual(source_balance_after_alan, source_balance_before_alan - withdraw_amount_alan) + self.assertEqual(destination_balance_after_galan, withdraw_amount_galan) \ No newline at end of file From 0ab8c12b94caf6c5b4f9c758f05863fc97d703c0 Mon Sep 17 00:00:00 2001 From: Ivan Loboda Date: Mon, 21 Feb 2022 20:11:01 +0300 Subject: [PATCH 10/15] Add test case --- proxy/testing/test_neon_token.py | 124 +++++++++++++++++++++++++++---- 1 file changed, 110 insertions(+), 14 deletions(-) diff --git a/proxy/testing/test_neon_token.py b/proxy/testing/test_neon_token.py index acaaabab9..7ddf1d17f 100644 --- a/proxy/testing/test_neon_token.py +++ b/proxy/testing/test_neon_token.py @@ -5,8 +5,11 @@ from .testing_helpers import request_airdrop from solana.account import Account as SolanaAccount from solana.rpc.api import Client as SolanaClient +from solana.transaction import Transaction +from solana.rpc.types import TxOpts +from solana.rpc.commitment import Confirmed from spl.token.client import Token as SplToken -from spl.token.instructions import get_associated_token_address +from spl.token.instructions import get_associated_token_address, create_associated_token_account from proxy.environment import ETH_TOKEN_MINT_ID from spl.token.constants import TOKEN_PROGRAM_ID from solana.rpc.commitment import Confirmed @@ -63,7 +66,21 @@ def deploy_contract(self): self.neon_contract = proxy.eth.contract(address=self.neon_token_address, abi=self.neon_token_iface['abi']) - def test_success_withdraw(self): + def withdraw(self, dest_acc: SolanaAccount, withdraw_amount_alan: int): + nonce = proxy.eth.get_transaction_count(eth_account.address) + tx = {'value': withdraw_amount_alan, 'nonce': nonce} + withdraw_tx_dict = self.neon_contract.functions.withdraw(bytes(dest_acc.public_key())).buildTransaction(tx) + withdraw_tx = proxy.eth.account.sign_transaction(withdraw_tx_dict, eth_account.key) + withdraw_tx_hash = proxy.eth.send_raw_transaction(withdraw_tx.rawTransaction) + print(f'withdraw_tx_hash: {withdraw_tx_hash.hex()}') + withdraw_tx_receipt = proxy.eth.wait_for_transaction_receipt(withdraw_tx_hash) + print(f'withdraw_tx_receipt: {withdraw_tx_receipt}') + print(f'deploy status: {withdraw_tx_receipt.status}') + + def test_success_withdraw_to_non_existing_account(self): + """ + Should succesfully withdraw NEON tokens to previously non-existing Associated Token Account + """ dest_acc = SolanaAccount() print(f"Try to withdraw NEON tokens to solana account {dest_acc.public_key()}") # creating destination accout by airdropping SOL @@ -74,26 +91,105 @@ def test_success_withdraw(self): withdraw_amount_alan = pow(10, 18) # 1 NEON withdraw_amount_galan = int(withdraw_amount_alan / 1_000_000_000) + # Check source balance source_balance_before_alan = proxy.eth.get_balance(eth_account.address) + print(f'Source account balance before (Alan): {source_balance_before_alan}') + + # Check destination balance (must not exist) destination_balance_before_galan = self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed) + print(f'Destination account balance before (Galan): {destination_balance_before_galan}') + self.assertTrue(destination_balance_before_galan['error'] is not None) + + self.withdraw(dest_acc, withdraw_amount_alan) + + # Check source balance + source_balance_after_alan = proxy.eth.get_balance(eth_account.address) + print(f'Source account balance after (Alan): {source_balance_after_alan}') + self.assertEqual(source_balance_after_alan, source_balance_before_alan - withdraw_amount_alan) + + # Check destination balance + destination_balance_after_galan = self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed) + print(f'Destination account balance after (Galan): {destination_balance_after_galan}') + self.assertEqual(int(destination_balance_after_galan['result']['value']['amount']), withdraw_amount_galan) + + def test_success_withdraw_to_existing_account(self): + """ + Should succesfully withdraw NEON tokens to existing Associated Token Account + """ + dest_acc = SolanaAccount() + print(f"Try to withdraw NEON tokens to solana account {dest_acc.public_key()}") + # creating destination accout by airdropping SOL + solana.request_airdrop(dest_acc.public_key(), 1000_000_000_000) + + # Creating destination Associated Token Account + trx = Transaction() + trx.add( + create_associated_token_account( + dest_acc.public_key(), + dest_acc.public_key(), + ETH_TOKEN_MINT_ID + ) + ) + opts = TxOpts(skip_preflight=True, skip_confirmation=False) + solana.send_transaction(trx, dest_acc, opts=opts) + + dest_token_acc = get_associated_token_address(dest_acc.public_key(), ETH_TOKEN_MINT_ID) + print(f"Destination token account: {dest_token_acc}") + + withdraw_amount_alan = pow(10, 18) # 1 NEON + withdraw_amount_galan = int(withdraw_amount_alan / 1_000_000_000) + + # Check source balance + source_balance_before_alan = proxy.eth.get_balance(eth_account.address) print(f'Source account balance before (Alan): {source_balance_before_alan}') + + # Check destination balance (must exist with zero balance) + destination_balance_before_galan = int(self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed)['result']['value']['amount']) print(f'Destination account balance before (Galan): {destination_balance_before_galan}') + self.assertEqual(destination_balance_before_galan, 0) + + self.withdraw(dest_acc, withdraw_amount_alan) + + # Check source balance + source_balance_after_alan = proxy.eth.get_balance(eth_account.address) + print(f'Source account balance after (Alan): {source_balance_after_alan}') + self.assertEqual(source_balance_after_alan, source_balance_before_alan - withdraw_amount_alan) + + # Check destination balance + destination_balance_after_galan = int(self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed)['result']['value']['amount']) + print(f'Destination account balance after (Galan): {destination_balance_after_galan}') + self.assertEqual(destination_balance_after_galan, withdraw_amount_galan) + + def test_failed_withdraw_non_divisible_amount(self): + """ + Should fail withdrawal because amount not divised by 1 billion + """ + dest_acc = SolanaAccount() + print(f"Try to withdraw NEON tokens to solana account {dest_acc.public_key()}") + # creating destination accout by airdropping SOL + solana.request_airdrop(dest_acc.public_key(), 1000_000_000_000) + dest_token_acc = get_associated_token_address(dest_acc.public_key(), ETH_TOKEN_MINT_ID) + print(f"Destination token account: {dest_token_acc}") + + withdraw_amount_alan = pow(10, 18) + 123 # NEONs + # Check source balance + source_balance_before_alan = proxy.eth.get_balance(eth_account.address) + print(f'Source account balance before (Alan): {source_balance_before_alan}') + + # Check destination balance (must not exist) + destination_balance_before_galan = self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed) + print(f'Destination account balance before (Galan): {destination_balance_before_galan}') self.assertTrue(destination_balance_before_galan['error'] is not None) - nonce = proxy.eth.get_transaction_count(eth_account.address) - tx = {'value': withdraw_amount_alan, 'nonce': nonce} - withdraw_tx_dict = self.neon_contract.functions.withdraw(bytes(dest_acc.public_key())).buildTransaction(tx) - withdraw_tx = proxy.eth.account.sign_transaction(withdraw_tx_dict, eth_account.key) - withdraw_tx_hash = proxy.eth.send_raw_transaction(withdraw_tx.rawTransaction) - print(f'withdraw_tx_hash: {withdraw_tx_hash.hex()}') - withdraw_tx_receipt = proxy.eth.wait_for_transaction_receipt(withdraw_tx_hash) - print(f'withdraw_tx_receipt: {withdraw_tx_receipt}') - print(f'deploy status: {withdraw_tx_receipt.status}') + self.withdraw(dest_acc, withdraw_amount_alan) + # Check source balance source_balance_after_alan = proxy.eth.get_balance(eth_account.address) - destination_balance_after_galan = int(self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed)['result']['value']['amount']) print(f'Source account balance after (Alan): {source_balance_after_alan}') + self.assertEqual(source_balance_after_alan, source_balance_before_alan) + + # Check destination balance + destination_balance_after_galan = self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed) print(f'Destination account balance after (Galan): {destination_balance_after_galan}') - self.assertEqual(source_balance_after_alan, source_balance_before_alan - withdraw_amount_alan) - self.assertEqual(destination_balance_after_galan, withdraw_amount_galan) \ No newline at end of file + self.assertTrue(destination_balance_after_galan['error'] is not None) From a883a16cdb040fa6cc035ba246473fd7b1918dd2 Mon Sep 17 00:00:00 2001 From: Ivan Loboda Date: Mon, 7 Mar 2022 12:33:16 +0300 Subject: [PATCH 11/15] add require to neon contract --- proxy/testing/test_neon_token.py | 1 + 1 file changed, 1 insertion(+) diff --git a/proxy/testing/test_neon_token.py b/proxy/testing/test_neon_token.py index 7ddf1d17f..ea1081dee 100644 --- a/proxy/testing/test_neon_token.py +++ b/proxy/testing/test_neon_token.py @@ -23,6 +23,7 @@ function withdraw(bytes32 spender) public payable returns (bool) { (bool success, bytes memory returnData) = NeonPrecompiled.delegatecall(abi.encodeWithSignature("withdraw(bytes32)", spender)); + require(success); return success; } } From da70d47127d33a2b01428c6c0a2bcf369a027609 Mon Sep 17 00:00:00 2001 From: Ivan Loboda Date: Mon, 7 Mar 2022 16:02:02 +0300 Subject: [PATCH 12/15] Fix tests --- proxy/testing/test_neon_token.py | 38 ++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/proxy/testing/test_neon_token.py b/proxy/testing/test_neon_token.py index ea1081dee..10acb3beb 100644 --- a/proxy/testing/test_neon_token.py +++ b/proxy/testing/test_neon_token.py @@ -10,9 +10,11 @@ from solana.rpc.commitment import Confirmed from spl.token.client import Token as SplToken from spl.token.instructions import get_associated_token_address, create_associated_token_account -from proxy.environment import ETH_TOKEN_MINT_ID +from proxy.environment import NEON_TOKEN_MINT from spl.token.constants import TOKEN_PROGRAM_ID from solana.rpc.commitment import Confirmed +from time import sleep +from web3 import exceptions as web3_exceptions NEON_TOKEN_CONTRACT = ''' // SPDX-License-Identifier: MIT @@ -21,10 +23,9 @@ contract NeonToken { address constant NeonPrecompiled = 0xFF00000000000000000000000000000000000003; - function withdraw(bytes32 spender) public payable returns (bool) { + function withdraw(bytes32 spender) public payable { (bool success, bytes memory returnData) = NeonPrecompiled.delegatecall(abi.encodeWithSignature("withdraw(bytes32)", spender)); require(success); - return success; } } ''' @@ -42,10 +43,10 @@ class TestNeonToken(unittest.TestCase): @classmethod def setUpClass(cls) -> None: cls.sol_payer = SolanaAccount() + request_airdrop(eth_account.address) cls.deploy_contract(cls) - cls.spl_neon_token = SplToken(solana, ETH_TOKEN_MINT_ID, TOKEN_PROGRAM_ID, cls.sol_payer) + cls.spl_neon_token = SplToken(solana, NEON_TOKEN_MINT, TOKEN_PROGRAM_ID, cls.sol_payer) print(f"default eth account: {eth_account.address}") - request_airdrop(eth_account.address) def deploy_contract(self): artifacts = compile_source(NEON_TOKEN_CONTRACT) @@ -86,7 +87,7 @@ def test_success_withdraw_to_non_existing_account(self): print(f"Try to withdraw NEON tokens to solana account {dest_acc.public_key()}") # creating destination accout by airdropping SOL solana.request_airdrop(dest_acc.public_key(), 1000_000_000_000) - dest_token_acc = get_associated_token_address(dest_acc.public_key(), ETH_TOKEN_MINT_ID) + dest_token_acc = get_associated_token_address(dest_acc.public_key(), NEON_TOKEN_MINT) print(f"Destination token account: {dest_token_acc}") withdraw_amount_alan = pow(10, 18) # 1 NEON @@ -106,13 +107,13 @@ def test_success_withdraw_to_non_existing_account(self): # Check source balance source_balance_after_alan = proxy.eth.get_balance(eth_account.address) print(f'Source account balance after (Alan): {source_balance_after_alan}') - self.assertEqual(source_balance_after_alan, source_balance_before_alan - withdraw_amount_alan) + self.assertLess(source_balance_after_alan, source_balance_before_alan - withdraw_amount_alan) # Check destination balance destination_balance_after_galan = self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed) print(f'Destination account balance after (Galan): {destination_balance_after_galan}') self.assertEqual(int(destination_balance_after_galan['result']['value']['amount']), withdraw_amount_galan) - + def test_success_withdraw_to_existing_account(self): """ Should succesfully withdraw NEON tokens to existing Associated Token Account @@ -128,16 +129,16 @@ def test_success_withdraw_to_existing_account(self): create_associated_token_account( dest_acc.public_key(), dest_acc.public_key(), - ETH_TOKEN_MINT_ID + NEON_TOKEN_MINT ) ) opts = TxOpts(skip_preflight=True, skip_confirmation=False) solana.send_transaction(trx, dest_acc, opts=opts) - dest_token_acc = get_associated_token_address(dest_acc.public_key(), ETH_TOKEN_MINT_ID) + dest_token_acc = get_associated_token_address(dest_acc.public_key(), NEON_TOKEN_MINT) print(f"Destination token account: {dest_token_acc}") - withdraw_amount_alan = pow(10, 18) # 1 NEON + withdraw_amount_alan = 2_123_000_321_000_000_000 withdraw_amount_galan = int(withdraw_amount_alan / 1_000_000_000) # Check source balance @@ -145,7 +146,8 @@ def test_success_withdraw_to_existing_account(self): print(f'Source account balance before (Alan): {source_balance_before_alan}') # Check destination balance (must exist with zero balance) - destination_balance_before_galan = int(self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed)['result']['value']['amount']) + resp = self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed) + destination_balance_before_galan = int(resp['result']['value']['amount']) print(f'Destination account balance before (Galan): {destination_balance_before_galan}') self.assertEqual(destination_balance_before_galan, 0) @@ -154,13 +156,14 @@ def test_success_withdraw_to_existing_account(self): # Check source balance source_balance_after_alan = proxy.eth.get_balance(eth_account.address) print(f'Source account balance after (Alan): {source_balance_after_alan}') - self.assertEqual(source_balance_after_alan, source_balance_before_alan - withdraw_amount_alan) + self.assertLess(source_balance_after_alan, source_balance_before_alan - withdraw_amount_alan) # Check destination balance - destination_balance_after_galan = int(self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed)['result']['value']['amount']) + resp = self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed) + destination_balance_after_galan = int(resp['result']['value']['amount']) print(f'Destination account balance after (Galan): {destination_balance_after_galan}') self.assertEqual(destination_balance_after_galan, withdraw_amount_galan) - + def test_failed_withdraw_non_divisible_amount(self): """ Should fail withdrawal because amount not divised by 1 billion @@ -169,7 +172,7 @@ def test_failed_withdraw_non_divisible_amount(self): print(f"Try to withdraw NEON tokens to solana account {dest_acc.public_key()}") # creating destination accout by airdropping SOL solana.request_airdrop(dest_acc.public_key(), 1000_000_000_000) - dest_token_acc = get_associated_token_address(dest_acc.public_key(), ETH_TOKEN_MINT_ID) + dest_token_acc = get_associated_token_address(dest_acc.public_key(), NEON_TOKEN_MINT) print(f"Destination token account: {dest_token_acc}") withdraw_amount_alan = pow(10, 18) + 123 # NEONs @@ -183,7 +186,8 @@ def test_failed_withdraw_non_divisible_amount(self): print(f'Destination account balance before (Galan): {destination_balance_before_galan}') self.assertTrue(destination_balance_before_galan['error'] is not None) - self.withdraw(dest_acc, withdraw_amount_alan) + with self.assertRaises(web3_exceptions.ContractLogicError) as er: + self.withdraw(dest_acc, withdraw_amount_alan) # Check source balance source_balance_after_alan = proxy.eth.get_balance(eth_account.address) From 7dd595c36330cb3c9c6ee2aa595ce252e7c3284d Mon Sep 17 00:00:00 2001 From: Ivan Loboda Date: Mon, 7 Mar 2022 17:53:26 +0300 Subject: [PATCH 13/15] fix user stories test --- proxy/testing/test_user_stories.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxy/testing/test_user_stories.py b/proxy/testing/test_user_stories.py index 08ce8afa7..ebfc0dc10 100644 --- a/proxy/testing/test_user_stories.py +++ b/proxy/testing/test_user_stories.py @@ -42,7 +42,7 @@ def test_01_check_eth_estimateGas_on_deploying_a_contract(self): print('response:', response) estimated_gas = response['result'] - self.assertEqual(estimated_gas, "0x19f91f0") + self.assertEqual(estimated_gas, "0x19fb9f0") def test_02_check_eth_estimateGas_on_deploying_a_contract_with_the_empty_value(self): print("https://github.com/neonlabsorg/proxy-model.py/issues/122") @@ -58,7 +58,7 @@ def test_02_check_eth_estimateGas_on_deploying_a_contract_with_the_empty_value(s print('response:', response) estimated_gas = response['result'] - self.assertEqual(estimated_gas, "0x19f91f0") + self.assertEqual(estimated_gas, "0x19fb9f0") def test_03_check_eth_estimateGas_on_deploying_a_contract_with_the_empty_data(self): print("https://github.com/neonlabsorg/proxy-model.py/issues/122") From 3d1ad109c8c92f5d69be8292f1b3b3e34fee5c30 Mon Sep 17 00:00:00 2001 From: Ivan Loboda Date: Wed, 9 Mar 2022 09:55:20 +0300 Subject: [PATCH 14/15] rollback user stories tests --- proxy/testing/test_user_stories.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxy/testing/test_user_stories.py b/proxy/testing/test_user_stories.py index ebfc0dc10..08ce8afa7 100644 --- a/proxy/testing/test_user_stories.py +++ b/proxy/testing/test_user_stories.py @@ -42,7 +42,7 @@ def test_01_check_eth_estimateGas_on_deploying_a_contract(self): print('response:', response) estimated_gas = response['result'] - self.assertEqual(estimated_gas, "0x19fb9f0") + self.assertEqual(estimated_gas, "0x19f91f0") def test_02_check_eth_estimateGas_on_deploying_a_contract_with_the_empty_value(self): print("https://github.com/neonlabsorg/proxy-model.py/issues/122") @@ -58,7 +58,7 @@ def test_02_check_eth_estimateGas_on_deploying_a_contract_with_the_empty_value(s print('response:', response) estimated_gas = response['result'] - self.assertEqual(estimated_gas, "0x19fb9f0") + self.assertEqual(estimated_gas, "0x19f91f0") def test_03_check_eth_estimateGas_on_deploying_a_contract_with_the_empty_data(self): print("https://github.com/neonlabsorg/proxy-model.py/issues/122") From 3a8b4d39a5aba2ba473281946f6eb35cd1b90a8b Mon Sep 17 00:00:00 2001 From: Ivan Loboda Date: Thu, 10 Mar 2022 13:19:54 +0300 Subject: [PATCH 15/15] Fix review issues --- Dockerfile | 2 +- proxy/testing/test_neon_token.py | 151 ++++++++++++++++++++++++------- proxy/testing/testing_helpers.py | 4 +- 3 files changed, 119 insertions(+), 38 deletions(-) diff --git a/Dockerfile b/Dockerfile index 66f98dda7..b2308b668 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ ARG SOLANA_REVISION=v1.8.12-testnet -ARG EVM_LOADER_REVISION=ci-308-account_v2 +ARG EVM_LOADER_REVISION=develop FROM neonlabsorg/solana:${SOLANA_REVISION} AS cli diff --git a/proxy/testing/test_neon_token.py b/proxy/testing/test_neon_token.py index 10acb3beb..30cb27d5d 100644 --- a/proxy/testing/test_neon_token.py +++ b/proxy/testing/test_neon_token.py @@ -15,6 +15,8 @@ from solana.rpc.commitment import Confirmed from time import sleep from web3 import exceptions as web3_exceptions +from random import uniform +from eth_account.signers.local import LocalAccount as NeonAccount NEON_TOKEN_CONTRACT = ''' // SPDX-License-Identifier: MIT @@ -31,22 +33,30 @@ ''' -proxy_url = os.environ.get('PROXY_URL', 'http://127.0.0.1:9090/solana') -solana_url = os.environ.get('SOLANA_URL', 'http://solana:8899/') -proxy = Web3(Web3.HTTPProvider(proxy_url)) -solana = SolanaClient(solana_url) -SEED = 'TestNeonToken' -eth_account = proxy.eth.account.create(SEED) -proxy.eth.default_account = eth_account.address +PROXY_URL = os.environ.get('PROXY_URL', 'http://127.0.0.1:9090/solana') +SOLANA_URL = os.environ.get('SOLANA_URL', 'http://solana:8899/') +proxy = Web3(Web3.HTTPProvider(PROXY_URL)) +solana = SolanaClient(SOLANA_URL) class TestNeonToken(unittest.TestCase): @classmethod def setUpClass(cls) -> None: cls.sol_payer = SolanaAccount() - request_airdrop(eth_account.address) cls.deploy_contract(cls) cls.spl_neon_token = SplToken(solana, NEON_TOKEN_MINT, TOKEN_PROGRAM_ID, cls.sol_payer) - print(f"default eth account: {eth_account.address}") + + def create_eth_account(self, balance): + seed = f'TestAccount{uniform(0, 10000)}' + new_neon_acc = proxy.eth.account.create(seed) + request_airdrop(new_neon_acc.address, balance) + print(f"New Neon account {new_neon_acc.address} with balance {balance}") + return new_neon_acc + + def create_sol_account(self, balance = 1000_000_000_000): + new_sol_acc = SolanaAccount() + print(f"New Solana account {new_sol_acc.public_key()} with balance {balance}") + solana.request_airdrop(new_sol_acc.public_key(), balance) + return new_sol_acc def deploy_contract(self): artifacts = compile_source(NEON_TOKEN_CONTRACT) @@ -54,10 +64,14 @@ def deploy_contract(self): self.neon_contract = proxy.eth.contract(abi=self.neon_token_iface['abi'], bytecode=self.neon_token_iface['bin']) - nonce = proxy.eth.get_transaction_count(eth_account.address) + + deployer = self.create_eth_account(self, 100) + proxy.eth.default_account = deployer.address + + nonce = proxy.eth.get_transaction_count(deployer.address) tx = {'nonce': nonce} tx_constructor = self.neon_contract.constructor().buildTransaction(tx) - tx_deploy = proxy.eth.account.sign_transaction(tx_constructor, eth_account.key) + tx_deploy = proxy.eth.account.sign_transaction(tx_constructor, deployer.key) tx_deploy_hash = proxy.eth.send_raw_transaction(tx_deploy.rawTransaction) print(f'tx_deploy_hash: {tx_deploy_hash.hex()}') tx_deploy_receipt = proxy.eth.wait_for_transaction_receipt(tx_deploy_hash) @@ -68,11 +82,11 @@ def deploy_contract(self): self.neon_contract = proxy.eth.contract(address=self.neon_token_address, abi=self.neon_token_iface['abi']) - def withdraw(self, dest_acc: SolanaAccount, withdraw_amount_alan: int): - nonce = proxy.eth.get_transaction_count(eth_account.address) + def withdraw(self, source_acc: NeonAccount, dest_acc: SolanaAccount, withdraw_amount_alan: int): + nonce = proxy.eth.get_transaction_count(source_acc.address) tx = {'value': withdraw_amount_alan, 'nonce': nonce} withdraw_tx_dict = self.neon_contract.functions.withdraw(bytes(dest_acc.public_key())).buildTransaction(tx) - withdraw_tx = proxy.eth.account.sign_transaction(withdraw_tx_dict, eth_account.key) + withdraw_tx = proxy.eth.account.sign_transaction(withdraw_tx_dict, source_acc.key) withdraw_tx_hash = proxy.eth.send_raw_transaction(withdraw_tx.rawTransaction) print(f'withdraw_tx_hash: {withdraw_tx_hash.hex()}') withdraw_tx_receipt = proxy.eth.wait_for_transaction_receipt(withdraw_tx_hash) @@ -83,10 +97,9 @@ def test_success_withdraw_to_non_existing_account(self): """ Should succesfully withdraw NEON tokens to previously non-existing Associated Token Account """ - dest_acc = SolanaAccount() - print(f"Try to withdraw NEON tokens to solana account {dest_acc.public_key()}") - # creating destination accout by airdropping SOL - solana.request_airdrop(dest_acc.public_key(), 1000_000_000_000) + source_acc = self.create_eth_account(10) + dest_acc = self.create_sol_account() + dest_token_acc = get_associated_token_address(dest_acc.public_key(), NEON_TOKEN_MINT) print(f"Destination token account: {dest_token_acc}") @@ -94,7 +107,7 @@ def test_success_withdraw_to_non_existing_account(self): withdraw_amount_galan = int(withdraw_amount_alan / 1_000_000_000) # Check source balance - source_balance_before_alan = proxy.eth.get_balance(eth_account.address) + source_balance_before_alan = proxy.eth.get_balance(source_acc.address) print(f'Source account balance before (Alan): {source_balance_before_alan}') # Check destination balance (must not exist) @@ -102,10 +115,10 @@ def test_success_withdraw_to_non_existing_account(self): print(f'Destination account balance before (Galan): {destination_balance_before_galan}') self.assertTrue(destination_balance_before_galan['error'] is not None) - self.withdraw(dest_acc, withdraw_amount_alan) + self.withdraw(source_acc, dest_acc, withdraw_amount_alan) # Check source balance - source_balance_after_alan = proxy.eth.get_balance(eth_account.address) + source_balance_after_alan = proxy.eth.get_balance(source_acc.address) print(f'Source account balance after (Alan): {source_balance_after_alan}') self.assertLess(source_balance_after_alan, source_balance_before_alan - withdraw_amount_alan) @@ -118,10 +131,8 @@ def test_success_withdraw_to_existing_account(self): """ Should succesfully withdraw NEON tokens to existing Associated Token Account """ - dest_acc = SolanaAccount() - print(f"Try to withdraw NEON tokens to solana account {dest_acc.public_key()}") - # creating destination accout by airdropping SOL - solana.request_airdrop(dest_acc.public_key(), 1000_000_000_000) + source_acc = self.create_eth_account(10) + dest_acc = self.create_sol_account() # Creating destination Associated Token Account trx = Transaction() @@ -142,7 +153,7 @@ def test_success_withdraw_to_existing_account(self): withdraw_amount_galan = int(withdraw_amount_alan / 1_000_000_000) # Check source balance - source_balance_before_alan = proxy.eth.get_balance(eth_account.address) + source_balance_before_alan = proxy.eth.get_balance(source_acc.address) print(f'Source account balance before (Alan): {source_balance_before_alan}') # Check destination balance (must exist with zero balance) @@ -151,10 +162,10 @@ def test_success_withdraw_to_existing_account(self): print(f'Destination account balance before (Galan): {destination_balance_before_galan}') self.assertEqual(destination_balance_before_galan, 0) - self.withdraw(dest_acc, withdraw_amount_alan) + self.withdraw(source_acc, dest_acc, withdraw_amount_alan) # Check source balance - source_balance_after_alan = proxy.eth.get_balance(eth_account.address) + source_balance_after_alan = proxy.eth.get_balance(source_acc.address) print(f'Source account balance after (Alan): {source_balance_after_alan}') self.assertLess(source_balance_after_alan, source_balance_before_alan - withdraw_amount_alan) @@ -168,17 +179,16 @@ def test_failed_withdraw_non_divisible_amount(self): """ Should fail withdrawal because amount not divised by 1 billion """ - dest_acc = SolanaAccount() - print(f"Try to withdraw NEON tokens to solana account {dest_acc.public_key()}") - # creating destination accout by airdropping SOL - solana.request_airdrop(dest_acc.public_key(), 1000_000_000_000) + source_acc = self.create_eth_account(10) + dest_acc = self.create_sol_account() + dest_token_acc = get_associated_token_address(dest_acc.public_key(), NEON_TOKEN_MINT) print(f"Destination token account: {dest_token_acc}") withdraw_amount_alan = pow(10, 18) + 123 # NEONs # Check source balance - source_balance_before_alan = proxy.eth.get_balance(eth_account.address) + source_balance_before_alan = proxy.eth.get_balance(source_acc.address) print(f'Source account balance before (Alan): {source_balance_before_alan}') # Check destination balance (must not exist) @@ -187,10 +197,81 @@ def test_failed_withdraw_non_divisible_amount(self): self.assertTrue(destination_balance_before_galan['error'] is not None) with self.assertRaises(web3_exceptions.ContractLogicError) as er: - self.withdraw(dest_acc, withdraw_amount_alan) + self.withdraw(source_acc, dest_acc, withdraw_amount_alan) + print(f'Exception occured: {er.exception}') + + # Check source balance + source_balance_after_alan = proxy.eth.get_balance(source_acc.address) + print(f'Source account balance after (Alan): {source_balance_after_alan}') + self.assertEqual(source_balance_after_alan, source_balance_before_alan) + + # Check destination balance + destination_balance_after_galan = self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed) + print(f'Destination account balance after (Galan): {destination_balance_after_galan}') + self.assertTrue(destination_balance_after_galan['error'] is not None) + + def test_failed_withdraw_insufficient_balance(self): + """ + Should fail withdrawal because of insufficient balance + """ + source_acc = self.create_eth_account(1) + dest_acc = self.create_sol_account() + + dest_token_acc = get_associated_token_address(dest_acc.public_key(), NEON_TOKEN_MINT) + print(f"Destination token account: {dest_token_acc}") + + withdraw_amount_alan = 2 * pow(10, 18) # 2 NEONs + + # Check source balance + source_balance_before_alan = proxy.eth.get_balance(source_acc.address) + print(f'Source account balance before (Alan): {source_balance_before_alan}') + + # Check destination balance (must not exist) + destination_balance_before_galan = self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed) + print(f'Destination account balance before (Galan): {destination_balance_before_galan}') + self.assertTrue(destination_balance_before_galan['error'] is not None) + + with self.assertRaises(ValueError) as er: + self.withdraw(source_acc, dest_acc, withdraw_amount_alan) + print(f'Exception occured: {er.exception}') + + # Check source balance + source_balance_after_alan = proxy.eth.get_balance(source_acc.address) + print(f'Source account balance after (Alan): {source_balance_after_alan}') + self.assertEqual(source_balance_after_alan, source_balance_before_alan) + + # Check destination balance + destination_balance_after_galan = self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed) + print(f'Destination account balance after (Galan): {destination_balance_after_galan}') + self.assertTrue(destination_balance_after_galan['error'] is not None) + + def test_failed_withdraw_all_balance(self): + """ + Should fail withdrawal all balance + """ + source_acc = self.create_eth_account(1) # 1 NEON + dest_acc = self.create_sol_account() + + dest_token_acc = get_associated_token_address(dest_acc.public_key(), NEON_TOKEN_MINT) + print(f"Destination token account: {dest_token_acc}") + + withdraw_amount_alan = 1_000_000_000_000_000_000 # 1 NEON + + # Check source balance + source_balance_before_alan = proxy.eth.get_balance(source_acc.address) + print(f'Source account balance before (Alan): {source_balance_before_alan}') + + # Check destination balance (must not exist) + destination_balance_before_galan = self.spl_neon_token.get_balance(dest_token_acc, commitment=Confirmed) + print(f'Destination account balance before (Galan): {destination_balance_before_galan}') + self.assertTrue(destination_balance_before_galan['error'] is not None) + + with self.assertRaises(ValueError) as er: + self.withdraw(source_acc, dest_acc, withdraw_amount_alan) + print(f'Exception occured: {er.exception}') # Check source balance - source_balance_after_alan = proxy.eth.get_balance(eth_account.address) + source_balance_after_alan = proxy.eth.get_balance(source_acc.address) print(f'Source account balance after (Alan): {source_balance_after_alan}') self.assertEqual(source_balance_after_alan, source_balance_before_alan) diff --git a/proxy/testing/testing_helpers.py b/proxy/testing/testing_helpers.py index f7411876c..a61230fbe 100644 --- a/proxy/testing/testing_helpers.py +++ b/proxy/testing/testing_helpers.py @@ -48,10 +48,10 @@ def web3(self) -> Web3: return self._web3 -def request_airdrop(address): +def request_airdrop(address, amount: int = 10): FAUCET_URL = os.environ.get('FAUCET_URL', 'http://faucet:3333') url = FAUCET_URL + '/request_neon' - data = '{"wallet": "' + address + '", "amount": 10}' + data = f'{{"wallet": "{address}", "amount": {amount}}}' r = requests.post(url, data=data) if not r.ok: print()