From 219b4638d7473a6c653fc3a60c726e19a38f1e09 Mon Sep 17 00:00:00 2001 From: gigimon Date: Mon, 13 Jun 2022 13:10:54 +0300 Subject: [PATCH 1/6] * copy libs for tests from evm --- Dockerfile | 2 - proxy/testing/eth_tx_utils.py | 251 +++++++++++ proxy/testing/solana_utils.py | 801 ++++++++++++++++++++++++++++++++++ 3 files changed, 1052 insertions(+), 2 deletions(-) create mode 100644 proxy/testing/eth_tx_utils.py create mode 100644 proxy/testing/solana_utils.py diff --git a/Dockerfile b/Dockerfile index 13c9e2b86..797e18341 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,8 +28,6 @@ COPY --from=spl /opt/solana/bin/solana \ COPY --from=spl /opt/spl-token \ /opt/create-test-accounts.sh \ /opt/neon-cli \ - /opt/solana_utils.py \ - /opt/eth_tx_utils.py \ /opt/evm_loader-keypair.json \ /spl/bin/ diff --git a/proxy/testing/eth_tx_utils.py b/proxy/testing/eth_tx_utils.py new file mode 100644 index 000000000..c316de84c --- /dev/null +++ b/proxy/testing/eth_tx_utils.py @@ -0,0 +1,251 @@ +from sha3 import keccak_256 +import json +from web3.auto import w3 +from eth_keys import keys +import struct + + +def unpack(data): + ch = data[0] + if (ch <= 0x7F): + return (ch, data[1:]) + elif (ch == 0x80): + return (None, data[1:]) + elif (ch <= 0xB7): + l = ch - 0x80 + return (data[1:1 + l].tobytes(), data[1 + l:]) + elif (ch <= 0xBF): + lLen = ch - 0xB7 + l = int.from_bytes(data[1:1 + lLen], byteorder='big') + return (data[1 + lLen:1 + lLen + l].tobytes(), data[1 + lLen + l:]) + elif (ch == 0xC0): + return ((), data[1:]) + elif (ch <= 0xF7): + l = ch - 0xC0 + lst = list() + sub = data[1:1 + l] + while len(sub): + (item, sub) = unpack(sub) + lst.append(item) + return (lst, data[1 + l:]) + else: + lLen = ch - 0xF7 + l = int.from_bytes(data[1:1 + lLen], byteorder='big') + lst = list() + sub = data[1 + lLen:1 + lLen + l] + while len(sub): + (item, sub) = unpack(sub) + lst.append(item) + return (lst, data[1 + lLen + l:]) + + +def pack(data): + if data == None: + return (0x80).to_bytes(1, 'big') + if isinstance(data, str): + return pack(data.encode('utf8')) + elif isinstance(data, bytes): + if len(data) <= 55: + return (len(data) + 0x80).to_bytes(1, 'big') + data + else: + l = len(data) + lLen = (l.bit_length() + 7) // 8 + return (0xB7 + lLen).to_bytes(1, 'big') + l.to_bytes(lLen, 'big') + data + elif isinstance(data, int): + if data < 0x80: + return data.to_bytes(1, 'big') + else: + l = (data.bit_length() + 7) // 8 + return (l + 0x80).to_bytes(1, 'big') + data.to_bytes(l, 'big') + pass + elif isinstance(data, list) or isinstance(data, tuple): + if len(data) == 0: + return (0xC0).to_bytes(1, 'big') + else: + res = bytearray() + for d in data: + res += pack(d) + l = len(res) + if l <= 55: + return (l + 0xC0).to_bytes(1, 'big') + res + else: + lLen = (l.bit_length() + 7) // 8 + return (lLen + 0xF7).to_bytes(1, 'big') + l.to_bytes(lLen, 'big') + res + else: + raise Exception("Unknown type {} of data".format(str(type(data)))) + + +def getInt(a): + if isinstance(a, int): return a + if isinstance(a, bytes): return int.from_bytes(a, 'big') + if a == None: return a + raise Exception("Invalid convertion from {} to int".format(a)) + + +class Trx: + def __init__(self): + self.nonce = None + self.gasPrice = None + self.gasLimit = None + self.toAddress = None + self.value = None + self.callData = None + self.v = None + self.r = None + self.s = None + + @classmethod + def fromString(cls, s): + t = Trx() + (unpacked, data) = unpack(memoryview(s)) + (nonce, gasPrice, gasLimit, toAddress, value, callData, v, r, s) = unpacked + t.nonce = getInt(nonce) + t.gasPrice = getInt(gasPrice) + t.gasLimit = getInt(gasLimit) + t.toAddress = toAddress + t.value = getInt(value) + t.callData = callData + t.v = getInt(v) + t.r = getInt(r) + t.s = getInt(s) + return t + + def chainId(self): + # chainid*2 + 35 xxxxx0 + 100011 xxxx0 + 100010 +1 + # chainid*2 + 36 xxxxx0 + 100100 xxxx0 + 100011 +1 + return (self.v - 1) // 2 - 17 + + def __str__(self): + return pack(( + self.nonce, + self.gasPrice, + self.gasLimit, + self.toAddress, + self.value, + self.callData, + self.v, + self.r.to_bytes(32, 'big') if self.r else None, + self.s.to_bytes(32, 'big') if self.s else None) + ).hex() + + def get_msg(self, chainId=None): + return pack(( + self.nonce, + self.gasPrice, + self.gasLimit, + self.toAddress, + self.value, + self.callData, + chainId or self.chainId(), None, None)) + + def hash(self, chainId=None): + trx = pack(( + self.nonce, + self.gasPrice, + self.gasLimit, + self.toAddress, + self.value, + self.callData, + chainId or self.chainId(), None, None)) + return keccak_256(trx).digest() + + def sender(self): + msgHash = self.hash() + sig = keys.Signature(vrs=[1 if self.v % 2 == 0 else 0, self.r, self.s]) + pub = sig.recover_public_key_from_msg_hash(msgHash) + return pub.to_canonical_address().hex() + + +class JsonEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, bytes): + return obj.hex() + return json.JSONEncoder.default(self.obj) + + +def make_instruction_data_from_tx(instruction, private_key=None): + if isinstance(instruction, dict): + if instruction['chainId'] == None: + raise Exception("chainId value is needed in input dict") + if private_key == None: + raise Exception("Needed private key for transaction creation from fields") + + signed_tx = w3.eth.account.sign_transaction(instruction, private_key) + # print(signed_tx.rawTransaction.hex()) + _trx = Trx.fromString(signed_tx.rawTransaction) + # print(json.dumps(_trx.__dict__, cls=JsonEncoder, indent=3)) + + raw_msg = _trx.get_msg(instruction['chainId']) + sig = keys.Signature(vrs=[1 if _trx.v % 2 == 0 else 0, _trx.r, _trx.s]) + pub = sig.recover_public_key_from_msg_hash(_trx.hash()) + + # print(pub.to_hex()) + + return (pub.to_canonical_address(), sig.to_bytes(), raw_msg) + elif isinstance(instruction, str): + if instruction[:2] == "0x": + instruction = instruction[2:] + + _trx = Trx.fromString(bytearray.fromhex(instruction)) + # print(json.dumps(_trx.__dict__, cls=JsonEncoder, indent=3)) + + raw_msg = _trx.get_msg() + sig = keys.Signature(vrs=[1 if _trx.v % 2 == 0 else 0, _trx.r, _trx.s]) + pub = sig.recover_public_key_from_msg_hash(_trx.hash()) + + data = pub.to_canonical_address() + data += sig.to_bytes() + data += raw_msg + + return (pub.to_canonical_address(), sig.to_bytes(), raw_msg) + else: + raise Exception("function gets ") + + +def make_keccak_instruction_data(check_instruction_index, msg_len, data_start): + if 255 < check_instruction_index < 0: + raise Exception("Invalid index for instruction - {}".format(check_instruction_index)) + + check_count = 1 + eth_address_size = 20 + signature_size = 65 + eth_address_offset = data_start + signature_offset = eth_address_offset + eth_address_size + message_data_offset = signature_offset + signature_size + + data = struct.pack("B", check_count) + data += struct.pack("= confirmations): + return + sleep_time = 0.1 + time.sleep(sleep_time) + elapsed_time += sleep_time + raise RuntimeError("could not confirm transaction: ", tx_sig) + + +def accountWithSeed(base, seed, program): + # print(type(base), type(seed), type(program)) + return PublicKey(sha256(bytes(base) + bytes(seed, 'utf8') + bytes(program)).digest()) + + +def createAccountWithSeed(funding, base, seed, lamports, space, program): + data = SYSTEM_INSTRUCTIONS_LAYOUT.build( + dict( + instruction_type=SystemInstructionType.CREATE_ACCOUNT_WITH_SEED, + args=dict( + base=bytes(base), + seed=dict(length=len(seed), chars=seed), + lamports=lamports, + space=space, + program_id=bytes(program) + ) + ) + ) + print("createAccountWithSeed", data.hex()) + created = accountWithSeed(base, seed, program) + print("created", created) + return TransactionInstruction( + keys=[ + AccountMeta(pubkey=funding, is_signer=True, is_writable=True), + AccountMeta(pubkey=created, is_signer=False, is_writable=True), + AccountMeta(pubkey=base, is_signer=True, is_writable=False), + ], + program_id=system, + data=data + ) + + +class solana_cli: + def __init__(self, acc=None): + self.acc = acc + + def call(self, arguments): + cmd = "" + if self.acc == None: + cmd = '{} --url {} {}'.format(path_to_solana, solana_url, arguments) + else: + cmd = '{} --keypair {} --url {} {}'.format(path_to_solana, self.acc.get_path(), solana_url, arguments) + try: + return subprocess.check_output(cmd, shell=True, universal_newlines=True) + except subprocess.CalledProcessError as err: + import sys + print("ERR: solana error {}".format(err)) + raise + + +class neon_cli: + def __init__(self, verbose_flags=''): + self.verbose_flags = verbose_flags + + def call(self, arguments): + cmd = 'neon-cli {} --commitment=processed --url {} {} -vvv'.format(self.verbose_flags, solana_url, arguments) + try: + return subprocess.check_output(cmd, shell=True, universal_newlines=True) + except subprocess.CalledProcessError as err: + import sys + print("ERR: neon-cli error {}".format(err)) + raise + + def emulate(self, loader_id, arguments): + cmd = 'neon-cli {} --commitment=processed --evm_loader {} --url {} emulate {}'.format(self.verbose_flags, + loader_id, + solana_url, + arguments) + print('cmd:', cmd) + try: + output = subprocess.check_output(cmd, shell=True, universal_newlines=True) + without_empty_lines = os.linesep.join([s for s in output.splitlines() if s]) + last_line = without_empty_lines.splitlines()[-1] + return last_line + except subprocess.CalledProcessError as err: + import sys + print("ERR: neon-cli error {}".format(err)) + raise + + +class RandomAccount: + def __init__(self, path=None): + if path == None: + self.make_random_path() + print("New keypair file: {}".format(self.path)) + self.generate_key() + else: + self.path = path + self.retrieve_keys() + print('New Public key:', self.acc.public_key()) + print('Private:', self.acc.secret_key()) + + def make_random_path(self): + self.path = os.urandom(5).hex() + ".json" + + def generate_key(self): + cmd_generate = 'solana-keygen new --no-passphrase --outfile {}'.format(self.path) + try: + return subprocess.check_output(cmd_generate, shell=True, universal_newlines=True) + except subprocess.CalledProcessError as err: + import sys + print("ERR: solana error {}".format(err)) + raise + + def retrieve_keys(self): + with open(self.path) as f: + d = json.load(f) + self.acc = Account(d[0:32]) + + def get_path(self): + return self.path + + def get_acc(self): + return self.acc + + +class WalletAccount(RandomAccount): + def __init__(self, path): + self.path = path + self.retrieve_keys() + print('Wallet public key:', self.acc.public_key()) + + +class OperatorAccount: + def __init__(self, path=None): + if path == None: + self.path = operator1_keypair_path() + else: + self.path = path + self.retrieve_keys() + print('Public key:', self.acc.public_key()) + print('Private key:', self.acc.secret_key().hex()) + + def retrieve_keys(self): + with open(self.path) as f: + d = json.load(f) + self.acc = Account(d[0:32]) + + def get_path(self): + return self.path + + def get_acc(self): + return self.acc + + +class EvmLoader: + def __init__(self, acc: OperatorAccount, programId=EVM_LOADER): + if programId == None: + print("Load EVM loader...") + result = json.loads(solana_cli(acc).call('deploy {}'.format(EVM_LOADER_SO))) + programId = result['programId'] + EvmLoader.loader_id = programId + print("Done\n") + + self.loader_id = EvmLoader.loader_id + self.acc = acc + print("Evm loader program: {}".format(self.loader_id)) + + def airdropNeonTokens(self, user_ether_address: Union[str, bytes], amount: int) -> None: + operator = self.acc.get_acc() + + (neon_evm_authority, _) = PublicKey.find_program_address([b"Deposit"], PublicKey(self.loader_id)) + pool_token_account = get_associated_token_address(neon_evm_authority, ETH_TOKEN_MINT_ID) + source_token_account = get_associated_token_address(operator.public_key(), ETH_TOKEN_MINT_ID) + (user_solana_address, _) = self.ether2program(user_ether_address) + + pool_account_exists = client.get_account_info(pool_token_account, commitment="processed")["result"][ + "value"] is not None + print("Pool Account Exists: ", pool_account_exists) + + trx = TransactionWithComputeBudget() + if not pool_account_exists: + trx.add(create_associated_token_account(operator.public_key(), neon_evm_authority, ETH_TOKEN_MINT_ID)) + + trx.add(approve(ApproveParams( + program_id=TOKEN_PROGRAM_ID, + source=source_token_account, + delegate=neon_evm_authority, + owner=operator.public_key(), + amount=amount * (10 ** 9), + ))) + trx.add(TransactionInstruction( + program_id=self.loader_id, + data=bytes.fromhex("19"), + keys=[ + AccountMeta(pubkey=source_token_account, is_signer=False, is_writable=True), + AccountMeta(pubkey=pool_token_account, is_signer=False, is_writable=True), + AccountMeta(pubkey=user_solana_address, is_signer=False, is_writable=True), + AccountMeta(pubkey=neon_evm_authority, is_signer=False, is_writable=False), + AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), + ] + )) + result = send_transaction(client, trx, operator) + print("Airdrop transaction: ", result) + + def deploy(self, contract_path, config=None): + print('deploy contract') + if config == None: + output = neon_cli().call("deploy --evm_loader {} {}".format(self.loader_id, contract_path)) + else: + output = neon_cli().call("deploy --evm_loader {} --config {} {}".format(self.loader_id, config, + contract_path)) + print(type(output), output) + result = json.loads(output.splitlines()[-1]) + return result + + def createEtherAccount(self, ether): + (trx, sol) = self.createEtherAccountTrx(ether) + result = send_transaction(client, trx, self.acc.get_acc()) + print('result:', result) + return sol + + def ether2seed(self, ether): + if isinstance(ether, str): + if ether.startswith('0x'): ether = ether[2:] + else: + ether = ether.hex() + seed = b58encode(ACCOUNT_SEED_VERSION + bytes.fromhex(ether)).decode('utf8') + acc = accountWithSeed(self.acc.get_acc().public_key(), seed, PublicKey(self.loader_id)) + print('ether2program: {} {} => {}'.format(ether, 255, acc)) + return (acc, 255) + + def ether2program(self, ether): + if isinstance(ether, str): + if ether.startswith('0x'): ether = ether[2:] + else: + ether = ether.hex() + output = neon_cli().call("create-program-address --evm_loader {} {}".format(self.loader_id, ether)) + items = output.rstrip().split(' ') + return items[0], int(items[1]) + + def checkAccount(self, solana): + info = client.get_account_info(solana) + print("checkAccount({}): {}".format(solana, info)) + + def deployChecked(self, location, caller, caller_ether): + trx_count = getTransactionCount(client, caller) + ether = keccak_256(rlp.encode((caller_ether, trx_count))).digest()[-20:] + + program = self.ether2program(ether) + code = self.ether2seed(ether) + info = client.get_account_info(program[0]) + if info['result']['value'] is None: + res = self.deploy(location) + return res['programId'], bytes.fromhex(res['ethereum'][2:]), res['codeId'] + elif info['result']['value']['owner'] != self.loader_id: + raise Exception("Invalid owner for account {}".format(program)) + else: + return program[0], ether, code[0] + + def createEtherAccountTrx(self, ether: Union[str, bytes], code_acc=None) -> Tuple[Transaction, str]: + if isinstance(ether, str): + if ether.startswith('0x'): ether = ether[2:] + else: + ether = ether.hex() + + (sol, nonce) = self.ether2program(ether) + print('createEtherAccount: {} {} => {}'.format(ether, nonce, sol)) + + base = self.acc.get_acc().public_key() + data = bytes.fromhex('18') + CREATE_ACCOUNT_LAYOUT.build(dict(ether=bytes.fromhex(ether), nonce=nonce)) + trx = TransactionWithComputeBudget() + if code_acc is None: + trx.add(TransactionInstruction( + program_id=self.loader_id, + data=data, + keys=[ + AccountMeta(pubkey=base, is_signer=True, is_writable=True), + AccountMeta(pubkey=system, is_signer=False, is_writable=False), + AccountMeta(pubkey=PublicKey(sol), is_signer=False, is_writable=True), + ])) + else: + trx.add(TransactionInstruction( + program_id=self.loader_id, + data=data, + keys=[ + AccountMeta(pubkey=base, is_signer=True, is_writable=True), + AccountMeta(pubkey=system, is_signer=False, is_writable=False), + AccountMeta(pubkey=PublicKey(sol), is_signer=False, is_writable=True), + AccountMeta(pubkey=PublicKey(code_acc), is_signer=False, is_writable=True), + ])) + return (trx, sol) + + +def getBalance(account): + return client.get_balance(account, commitment=Confirmed)['result']['value'] + + +ACCOUNT_INFO_LAYOUT = cStruct( + "type" / Int8ul, + "ether" / Bytes(20), + "nonce" / Int8ul, + "trx_count" / Bytes(8), + "balance" / Bytes(32), + "code_account" / Bytes(32), + "is_rw_blocked" / Int8ul, + "ro_blocked_cnt" / Int8ul, +) + + +class AccountInfo(NamedTuple): + ether: eth_keys.PublicKey + trx_count: int + code_account: PublicKey + + @staticmethod + def frombytes(data: bytes): + cont = ACCOUNT_INFO_LAYOUT.parse(data) + return AccountInfo(cont.ether, cont.trx_count, PublicKey(cont.code_account)) + + +def getAccountData(client: Client, account: Union[str, PublicKey], expected_length: int) -> bytes: + info = client.get_account_info(account, commitment=Confirmed)['result']['value'] + if info is None: + raise Exception("Can't get information about {}".format(account)) + + data = base64.b64decode(info['data'][0]) + if len(data) < expected_length: + print("len(data)({}) < expected_length({})".format(len(data), expected_length)) + raise Exception("Wrong data length for account data {}".format(account)) + return data + + +def getTransactionCount(client: Client, sol_account: Union[str, PublicKey]) -> int: + info = getAccountData(client, sol_account, ACCOUNT_INFO_LAYOUT.sizeof()) + acc_info = AccountInfo.frombytes(info) + res = int.from_bytes(acc_info.trx_count, 'little') + print('getTransactionCount {}: {}'.format(sol_account, res)) + return res + + +def getNeonBalance(client: Client, sol_account: Union[str, PublicKey]) -> int: + info = getAccountData(client, sol_account, ACCOUNT_INFO_LAYOUT.sizeof()) + account = ACCOUNT_INFO_LAYOUT.parse(info) + balance = int.from_bytes(account.balance, byteorder="little") + print('getNeonBalance {}: {}'.format(sol_account, balance)) + return balance + + +def wallet_path(): + res = solana_cli().call("config get") + substr = "Keypair Path: " + for line in res.splitlines(): + if line.startswith(substr): + return line[len(substr):].strip() + raise Exception("cannot get keypair path") + + +def operator1_keypair_path(): + res = solana_cli().call("config get") + substr = "Keypair Path: " + for line in res.splitlines(): + if line.startswith(substr): + return line[len(substr):].strip() + raise Exception("cannot get keypair path") + + +def operator2_keypair_path(): + return "/root/.config/solana/id2.json" + + +def send_transaction(client, trx, acc): + result = client.send_transaction(trx, acc, opts=TxOpts(skip_confirmation=True, preflight_commitment="confirmed")) + confirm_transaction(client, result["result"]) + result = client.get_confirmed_transaction(result["result"]) + return result + + +def create_neon_evm_instr_05_single(evm_loader_program_id, + caller_sol_acc, + operator_sol_acc, + contract_sol_acc, + code_sol_acc, + collateral_pool_index_buf, + collateral_pool_address, + evm_instruction, + add_meta=[]): + return TransactionInstruction( + program_id=evm_loader_program_id, + data=bytearray.fromhex("05") + collateral_pool_index_buf + evm_instruction, + keys=[ + # System instructions account: + AccountMeta(pubkey=PublicKey(sysinstruct), is_signer=False, is_writable=False), + + # Operator's SOL account: + AccountMeta(pubkey=operator_sol_acc, is_signer=True, is_writable=True), + # Collateral pool address: + AccountMeta(pubkey=collateral_pool_address, is_signer=False, is_writable=True), + # Operator's NEON account: + AccountMeta(pubkey=caller_sol_acc, is_signer=False, is_writable=True), + # System program account: + AccountMeta(pubkey=PublicKey(system), is_signer=False, is_writable=False), + # NeonEVM program account + AccountMeta(pubkey=evm_loader_program_id, is_signer=False, is_writable=False), + + AccountMeta(pubkey=contract_sol_acc, is_signer=False, is_writable=True), + AccountMeta(pubkey=code_sol_acc, is_signer=False, is_writable=True), + AccountMeta(pubkey=caller_sol_acc, is_signer=False, is_writable=True), + + ] + add_meta + [ + AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), + ]) + + +def create_neon_evm_instr_13_partial_call_or_continue(evm_loader_program_id, + caller_sol_acc, + operator_sol_acc, + storage_sol_acc, + contract_sol_acc, + code_sol_acc, + collateral_pool_index_buf, + collateral_pool_address, + step_count, + evm_instruction, + writable_code=True, + add_meta=[]): + return TransactionInstruction( + program_id=evm_loader_program_id, + data=bytearray.fromhex("0D") + collateral_pool_index_buf + step_count.to_bytes(8, + byteorder='little') + evm_instruction, + keys=[ + AccountMeta(pubkey=storage_sol_acc, is_signer=False, is_writable=True), + # System instructions account: + AccountMeta(pubkey=PublicKey(sysinstruct), is_signer=False, is_writable=False), + + # Operator's SOL account: + AccountMeta(pubkey=operator_sol_acc, is_signer=True, is_writable=True), + # Collateral pool address: + AccountMeta(pubkey=collateral_pool_address, is_signer=False, is_writable=True), + # Operator's NEON account: + AccountMeta(pubkey=caller_sol_acc, is_signer=False, is_writable=True), + # System program account: + AccountMeta(pubkey=PublicKey(system), is_signer=False, is_writable=False), + # NeonEVM program account + AccountMeta(pubkey=evm_loader_program_id, is_signer=False, is_writable=False), + + AccountMeta(pubkey=contract_sol_acc, is_signer=False, is_writable=True), + AccountMeta(pubkey=code_sol_acc, is_signer=False, is_writable=writable_code), + AccountMeta(pubkey=caller_sol_acc, is_signer=False, is_writable=True), + + AccountMeta(pubkey=PublicKey(sysinstruct), is_signer=False, is_writable=False), + ] + add_meta + [ + AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), + ]) + + +def create_neon_evm_instr_19_partial_call(evm_loader_program_id, + caller_sol_acc, + operator_sol_acc, + storage_sol_acc, + contract_sol_acc, + code_sol_acc, + collateral_pool_index_buf, + collateral_pool_address, + step_count, + evm_instruction, + writable_code=True, + add_meta=[]): + return TransactionInstruction( + program_id=evm_loader_program_id, + data=bytearray.fromhex("13") + collateral_pool_index_buf + step_count.to_bytes(8, + byteorder='little') + evm_instruction, + keys=[ + AccountMeta(pubkey=storage_sol_acc, is_signer=False, is_writable=True), + # System instructions account: + AccountMeta(pubkey=PublicKey(sysinstruct), is_signer=False, is_writable=False), + + # Operator's SOL account: + AccountMeta(pubkey=operator_sol_acc, is_signer=True, is_writable=True), + # Collateral pool address: + AccountMeta(pubkey=collateral_pool_address, is_signer=False, is_writable=True), + # Operator's NEON account: + AccountMeta(pubkey=caller_sol_acc, is_signer=False, is_writable=True), + # System program account: + AccountMeta(pubkey=PublicKey(system), is_signer=False, is_writable=False), + # NeonEVM program account + AccountMeta(pubkey=evm_loader_program_id, is_signer=False, is_writable=False), + + AccountMeta(pubkey=contract_sol_acc, is_signer=False, is_writable=True), + AccountMeta(pubkey=code_sol_acc, is_signer=False, is_writable=writable_code), + AccountMeta(pubkey=caller_sol_acc, is_signer=False, is_writable=True), + ] + add_meta + [ + AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), + ]) + + +def create_neon_evm_instr_20_continue(evm_loader_program_id, + caller_sol_acc, + operator_sol_acc, + storage_sol_acc, + contract_sol_acc, + code_sol_acc, + collateral_pool_index_buf, + collateral_pool_address, + step_count, + writable_code=True, + add_meta=[]): + return TransactionInstruction( + program_id=evm_loader_program_id, + data=bytearray.fromhex("14") + collateral_pool_index_buf + step_count.to_bytes(8, byteorder='little'), + keys=[ + # Operator's storage account: + AccountMeta(pubkey=storage_sol_acc, is_signer=False, is_writable=True), + # Operator's SOL account: + AccountMeta(pubkey=operator_sol_acc, is_signer=True, is_writable=True), + # Collateral pool address: + AccountMeta(pubkey=collateral_pool_address, is_signer=False, is_writable=True), + # Operator's NEON account: + AccountMeta(pubkey=caller_sol_acc, is_signer=False, is_writable=True), + # System program account: + AccountMeta(pubkey=PublicKey(system), is_signer=False, is_writable=False), + # NeonEVM program account + AccountMeta(pubkey=evm_loader_program_id, is_signer=False, is_writable=False), + + AccountMeta(pubkey=contract_sol_acc, is_signer=False, is_writable=True), + AccountMeta(pubkey=code_sol_acc, is_signer=False, is_writable=writable_code), + AccountMeta(pubkey=caller_sol_acc, is_signer=False, is_writable=True), + ] + add_meta + [ + AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), + ]) + + +def create_neon_evm_instr_22_begin(evm_loader_program_id, + caller_sol_acc, + operator_sol_acc, + storage_sol_acc, + holder_sol_acc, + contract_sol_acc, + code_sol_acc, + collateral_pool_index_buf, + collateral_pool_address, + step_count): + return TransactionInstruction( + program_id=evm_loader_program_id, + data=bytearray.fromhex("16") + collateral_pool_index_buf + step_count.to_bytes(8, byteorder='little'), + keys=[ + AccountMeta(pubkey=holder_sol_acc, is_signer=False, is_writable=True), + AccountMeta(pubkey=storage_sol_acc, is_signer=False, is_writable=True), + + # Operator's SOL account: + AccountMeta(pubkey=operator_sol_acc, is_signer=True, is_writable=True), + # Collateral pool address: + AccountMeta(pubkey=collateral_pool_address, is_signer=False, is_writable=True), + # Operator's NEON token account: + AccountMeta(pubkey=caller_sol_acc, is_signer=False, is_writable=True), + # System program account: + AccountMeta(pubkey=PublicKey(system), is_signer=False, is_writable=False), + # NeonEVM program account + AccountMeta(pubkey=evm_loader_program_id, is_signer=False, is_writable=False), + + AccountMeta(pubkey=contract_sol_acc, is_signer=False, is_writable=True), + AccountMeta(pubkey=code_sol_acc, is_signer=False, is_writable=True), + AccountMeta(pubkey=caller_sol_acc, is_signer=False, is_writable=True), + + AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), + ]) + + +def create_neon_evm_instr_21_cancel(evm_loader_program_id, + caller_sol_acc, + operator_sol_acc, + storage_sol_acc, + contract_sol_acc, + code_sol_acc, + nonce): + return TransactionInstruction( + program_id=evm_loader_program_id, + data=bytearray.fromhex("15") + nonce.to_bytes(8, 'little'), + keys=[ + AccountMeta(pubkey=storage_sol_acc, is_signer=False, is_writable=True), + + # Operator's SOL account: + AccountMeta(pubkey=operator_sol_acc, is_signer=True, is_writable=True), + # Incinerator + AccountMeta(pubkey=PublicKey(incinerator), is_signer=False, is_writable=True), + + AccountMeta(pubkey=contract_sol_acc, is_signer=False, is_writable=True), + AccountMeta(pubkey=code_sol_acc, is_signer=False, is_writable=True), + AccountMeta(pubkey=caller_sol_acc, is_signer=False, is_writable=True), + + AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), + ]) + + +def create_neon_evm_instr_14_combined_continue(evm_loader_program_id, + caller_sol_acc, + operator_sol_acc, + storage_sol_acc, + holder_sol_acc, + contract_sol_acc, + code_sol_acc, + collateral_pool_index_buf, + collateral_pool_address, + step_count): + return TransactionInstruction( + program_id=evm_loader_program_id, + data=bytearray.fromhex("0E") + collateral_pool_index_buf + step_count.to_bytes(8, byteorder='little'), + keys=[ + AccountMeta(pubkey=holder_sol_acc, is_signer=False, is_writable=True), + AccountMeta(pubkey=storage_sol_acc, is_signer=False, is_writable=True), + + # Operator's SOL account: + AccountMeta(pubkey=operator_sol_acc, is_signer=True, is_writable=True), + # Collateral pool address: + AccountMeta(pubkey=collateral_pool_address, is_signer=False, is_writable=True), + # Operator's NEON account: + AccountMeta(pubkey=caller_sol_acc, is_signer=False, is_writable=True), + # System program account: + AccountMeta(pubkey=PublicKey(system), is_signer=False, is_writable=False), + # NeonEVM program account + AccountMeta(pubkey=evm_loader_program_id, is_signer=False, is_writable=False), + + AccountMeta(pubkey=contract_sol_acc, is_signer=False, is_writable=True), + AccountMeta(pubkey=code_sol_acc, is_signer=False, is_writable=True), + AccountMeta(pubkey=caller_sol_acc, is_signer=False, is_writable=True), + + AccountMeta(pubkey=TOKEN_PROGRAM_ID, is_signer=False, is_writable=False), + ]) + + +def evm_step_cost(): + operator_expences = PAYMENT_TO_TREASURE + LAMPORTS_PER_SIGNATURE + return math.floor(operator_expences / EVM_STEPS) + + +class ComputeBudget(): + @staticmethod + def requestUnits(units, additional_fee): + return TransactionInstruction( + program_id=COMPUTE_BUDGET_ID, + keys=[], + data=bytes.fromhex("00") + units.to_bytes(4, "little") + additional_fee.to_bytes(4, "little") + ) + + @staticmethod + def requestHeapFrame(heapFrame): + return TransactionInstruction( + program_id=COMPUTE_BUDGET_ID, + keys=[], + data=bytes.fromhex("01") + heapFrame.to_bytes(4, "little") + ) + + +def TransactionWithComputeBudget(units=DEFAULT_UNITS, additional_fee=DEFAULT_ADDITIONAL_FEE, + heapFrame=DEFAULT_HEAP_FRAME, **args): + trx = Transaction(**args) + if units: trx.add(ComputeBudget.requestUnits(units, additional_fee)) + if heapFrame: trx.add(ComputeBudget.requestHeapFrame(heapFrame)) + return trx From 5c0d67e93bdd2330764f9a3318e15ac9ce4921c2 Mon Sep 17 00:00:00 2001 From: gigimon Date: Mon, 13 Jun 2022 13:27:28 +0300 Subject: [PATCH 2/6] * copy libs for tests from evm --- proxy/testing/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/proxy/testing/__init__.py b/proxy/testing/__init__.py index c46530e2f..3cd943ae1 100644 --- a/proxy/testing/__init__.py +++ b/proxy/testing/__init__.py @@ -8,8 +8,5 @@ :copyright: (c) 2013-present by Abhinav Singh and contributors. :license: BSD, see LICENSE for more details. """ -import sys -sys.path.append("/spl/bin/") # TODO: get rid off this workaround all related modules should either be installed as package or be linked from submodule - from solcx import install_solc install_solc(version='0.7.6') From 9d91a995ced3000ce4c4fcf90af09aaf3bd31fcb Mon Sep 17 00:00:00 2001 From: gigimon Date: Mon, 13 Jun 2022 13:35:35 +0300 Subject: [PATCH 3/6] * debug bump --- proxy/deploy-test.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proxy/deploy-test.sh b/proxy/deploy-test.sh index 94ce21517..116f4e149 100755 --- a/proxy/deploy-test.sh +++ b/proxy/deploy-test.sh @@ -23,6 +23,8 @@ export ETH_TOKEN_MINT=$NEON_TOKEN_MINT export TEST_PROGRAM=$(solana address -k /spl/bin/proxy_program-keypair.json) # python3 -m unittest discover -v -p "test_${TESTNAME}.py" +echo "DIRECTORIES" +ls -la find . -name "test_${TESTNAME}.py" -printf "%f\n" | sort | parallel --halt now,fail=1 --jobs 4 python3 -m unittest discover -v -p {} echo "Deploy test success" From 4ca8ea9c467fdf0b852ade360e5332c01af806c2 Mon Sep 17 00:00:00 2001 From: gigimon Date: Mon, 13 Jun 2022 14:15:53 +0300 Subject: [PATCH 4/6] * debug bump --- proxy/deploy-test.sh | 2 -- proxy/testing/solana_utils.py | 1 - proxy/testing/test_indexer_work.py | 4 ++-- proxy/testing/test_operator_spending.py | 2 +- proxy/testing/test_retry_on_blocked_accounts.py | 3 +-- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/proxy/deploy-test.sh b/proxy/deploy-test.sh index 116f4e149..94ce21517 100755 --- a/proxy/deploy-test.sh +++ b/proxy/deploy-test.sh @@ -23,8 +23,6 @@ export ETH_TOKEN_MINT=$NEON_TOKEN_MINT export TEST_PROGRAM=$(solana address -k /spl/bin/proxy_program-keypair.json) # python3 -m unittest discover -v -p "test_${TESTNAME}.py" -echo "DIRECTORIES" -ls -la find . -name "test_${TESTNAME}.py" -printf "%f\n" | sort | parallel --halt now,fail=1 --jobs 4 python3 -m unittest discover -v -p {} echo "Deploy test success" diff --git a/proxy/testing/solana_utils.py b/proxy/testing/solana_utils.py index 5861a1256..c6a3a4747 100644 --- a/proxy/testing/solana_utils.py +++ b/proxy/testing/solana_utils.py @@ -22,7 +22,6 @@ from solana.rpc.types import TxOpts from solana.transaction import AccountMeta, TransactionInstruction, Transaction -from eth_tx_utils import make_keccak_instruction_data, make_instruction_data_from_tx from spl.token.constants import TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, ACCOUNT_LEN from spl.token.instructions import get_associated_token_address, approve, ApproveParams, create_associated_token_account import base58 diff --git a/proxy/testing/test_indexer_work.py b/proxy/testing/test_indexer_work.py index 8df52976b..93f7fbb74 100644 --- a/proxy/testing/test_indexer_work.py +++ b/proxy/testing/test_indexer_work.py @@ -11,7 +11,7 @@ import unittest import rlp -from eth_tx_utils import (make_instruction_data_from_tx, +from .eth_tx_utils import (make_instruction_data_from_tx, make_keccak_instruction_data) from eth_utils import big_endian_to_int from ethereum.transactions import Transaction as EthTrx @@ -22,7 +22,7 @@ from solana.rpc.types import TxOpts from solana.system_program import SYS_PROGRAM_ID from solana.transaction import AccountMeta, TransactionInstruction, Transaction -from solana_utils import * +from .solana_utils import * from solcx import compile_source from web3 import Web3 from web3.auto.gethdev import w3 diff --git a/proxy/testing/test_operator_spending.py b/proxy/testing/test_operator_spending.py index f458bb3c1..76926ef6b 100644 --- a/proxy/testing/test_operator_spending.py +++ b/proxy/testing/test_operator_spending.py @@ -4,7 +4,7 @@ from solcx import compile_source from solana.rpc.api import Client from solana.rpc.commitment import Confirmed -from solana_utils import * +from .solana_utils import * from proxy.testing.testing_helpers import request_airdrop diff --git a/proxy/testing/test_retry_on_blocked_accounts.py b/proxy/testing/test_retry_on_blocked_accounts.py index 705b97199..c5df07553 100644 --- a/proxy/testing/test_retry_on_blocked_accounts.py +++ b/proxy/testing/test_retry_on_blocked_accounts.py @@ -13,7 +13,6 @@ import multiprocessing import unittest import rlp -from eth_tx_utils import make_instruction_data_from_tx, make_keccak_instruction_data from eth_utils import big_endian_to_int from ethereum.transactions import Transaction as EthTrx from ethereum.utils import sha3 @@ -22,7 +21,7 @@ from solana.rpc.commitment import Confirmed from solana.system_program import SYS_PROGRAM_ID from solana.transaction import AccountMeta, Transaction, TransactionInstruction -from solana_utils import * +from .solana_utils import * from spl.token.constants import TOKEN_PROGRAM_ID from spl.token.instructions import get_associated_token_address from web3 import Web3 From 784a0487013b12c8ccf7cf349bd28250311735ca Mon Sep 17 00:00:00 2001 From: gigimon Date: Mon, 13 Jun 2022 14:22:30 +0300 Subject: [PATCH 5/6] * debug bump --- proxy/testing/test_retry_on_blocked_accounts.py | 1 + 1 file changed, 1 insertion(+) diff --git a/proxy/testing/test_retry_on_blocked_accounts.py b/proxy/testing/test_retry_on_blocked_accounts.py index c5df07553..2fccf7d06 100644 --- a/proxy/testing/test_retry_on_blocked_accounts.py +++ b/proxy/testing/test_retry_on_blocked_accounts.py @@ -13,6 +13,7 @@ import multiprocessing import unittest import rlp +from eth_tx_utils import make_instruction_data_from_tx, make_keccak_instruction_data from eth_utils import big_endian_to_int from ethereum.transactions import Transaction as EthTrx from ethereum.utils import sha3 From e8eec01e7f96a9bfe2bc774b7170c7563ba82f5e Mon Sep 17 00:00:00 2001 From: gigimon Date: Mon, 13 Jun 2022 14:38:25 +0300 Subject: [PATCH 6/6] * debug bump --- proxy/testing/test_retry_on_blocked_accounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/testing/test_retry_on_blocked_accounts.py b/proxy/testing/test_retry_on_blocked_accounts.py index 2fccf7d06..624515831 100644 --- a/proxy/testing/test_retry_on_blocked_accounts.py +++ b/proxy/testing/test_retry_on_blocked_accounts.py @@ -13,7 +13,7 @@ import multiprocessing import unittest import rlp -from eth_tx_utils import make_instruction_data_from_tx, make_keccak_instruction_data +from .eth_tx_utils import make_instruction_data_from_tx, make_keccak_instruction_data from eth_utils import big_endian_to_int from ethereum.transactions import Transaction as EthTrx from ethereum.utils import sha3