|
| 1 | +import os |
| 2 | +import math |
| 3 | +from web3.auto import w3 |
| 4 | + |
| 5 | +from solana.publickey import PublicKey |
| 6 | +from logged_groups import logged_group |
| 7 | +from typing import Tuple |
| 8 | + |
| 9 | +from ..common_neon.utils import get_holder_msg |
| 10 | +from ..common_neon.transaction_sender import NeonTxSender, NeonCreateContractTxStage, NeonCreateAccountTxStage |
| 11 | +from ..environment import EXTRA_GAS, EVM_STEPS, EVM_BYTE_COST, HOLDER_MSG_SIZE, LAMPORTS_PER_SIGNATURE, \ |
| 12 | + ACCOUNT_MAX_SIZE, SPL_TOKEN_ACCOUNT_SIZE, PAYMENT_TO_TREASURE, ACCOUNT_STORAGE_OVERHEAD |
| 13 | +from eth_keys import keys as eth_keys |
| 14 | +from .eth_proto import Trx as EthTrx |
| 15 | + |
| 16 | + |
| 17 | +def evm_step_cost(signature_cnt): |
| 18 | + operator_expences = PAYMENT_TO_TREASURE + LAMPORTS_PER_SIGNATURE * signature_cnt |
| 19 | + return math.ceil(operator_expences / EVM_STEPS) |
| 20 | + |
| 21 | + |
| 22 | +@logged_group("neon.Proxy") |
| 23 | +class GasEstimate: |
| 24 | + def __init__(self, request, db, client, evm_step_count): |
| 25 | + self.sender: bytes = bytes.fromhex(request.get('from', "0x%040x" % 0x0)[2:]) |
| 26 | + self.step_count = evm_step_count |
| 27 | + |
| 28 | + contract = request.get('to', None) |
| 29 | + contract = bytes.fromhex(contract[2:]) if contract else "" |
| 30 | + |
| 31 | + value = request.get('value', None) |
| 32 | + value = int(value, 16) if value else 0 |
| 33 | + |
| 34 | + data = request.get('data', None) |
| 35 | + data = data[2:] if data else "" |
| 36 | + |
| 37 | + unsigned_trx = { |
| 38 | + 'to': contract, |
| 39 | + 'value': value, |
| 40 | + 'gas': 999999999, |
| 41 | + 'gasPrice': 1_000_000_000, |
| 42 | + 'nonce': 0xffff, |
| 43 | + 'data': data, |
| 44 | + 'chainId': int('ffffffff', 16) |
| 45 | + } |
| 46 | + signed_trx = w3.eth.account.sign_transaction(unsigned_trx, eth_keys.PrivateKey(os.urandom(32))) |
| 47 | + trx = EthTrx.fromString(signed_trx.rawTransaction) |
| 48 | + |
| 49 | + self.tx_sender = NeonTxSender(db, client, trx, steps=evm_step_count) |
| 50 | + |
| 51 | + def iteration_info(self) -> Tuple[int, int]: |
| 52 | + if self.tx_sender.steps_emulated > 0: |
| 53 | + full_step_iterations = int(self.tx_sender.steps_emulated / self.step_count) |
| 54 | + final_steps = self.tx_sender.steps_emulated % self.step_count |
| 55 | + if final_steps > 0 and final_steps < EVM_STEPS: |
| 56 | + final_steps = EVM_STEPS |
| 57 | + else: |
| 58 | + full_step_iterations = 0 |
| 59 | + final_steps = EVM_STEPS |
| 60 | + return final_steps, full_step_iterations |
| 61 | + |
| 62 | + @logged_group("neon.Proxy") |
| 63 | + def simple_neon_tx_strategy(self, *, logger): |
| 64 | + gas = evm_step_cost(2) * (self.tx_sender.steps_emulated if self.tx_sender.steps_emulated > EVM_STEPS else EVM_STEPS) |
| 65 | + logger.debug(f'estimate simple_neon_tx_strategy: {gas}') |
| 66 | + return gas |
| 67 | + |
| 68 | + @logged_group("neon.Proxy") |
| 69 | + def iterative_neon_tx_strategy(self, *, logger): |
| 70 | + begin_iteration = 1 |
| 71 | + final_steps, full_step_iterations = self.iteration_info() |
| 72 | + steps = begin_iteration * EVM_STEPS + full_step_iterations * self.step_count + final_steps |
| 73 | + gas = steps * evm_step_cost(1) |
| 74 | + logger.debug(f'estimate iterative_neon_tx_strategy: {gas}') |
| 75 | + return gas |
| 76 | + |
| 77 | + @logged_group("neon.Proxy") |
| 78 | + def holder_neon_tx_strategy(self, *, logger): |
| 79 | + begin_iteration = 1 |
| 80 | + msg = get_holder_msg(self.tx_sender.eth_tx) |
| 81 | + holder_iterations = math.ceil(len(msg) / HOLDER_MSG_SIZE) |
| 82 | + final_steps, full_step_iterations = self.iteration_info() |
| 83 | + steps = (begin_iteration + holder_iterations) * EVM_STEPS + full_step_iterations * self.step_count + final_steps |
| 84 | + gas = steps * evm_step_cost(1) |
| 85 | + logger.debug(f'estimate holder_neon_tx_strategy: {gas}') |
| 86 | + return gas |
| 87 | + |
| 88 | + @logged_group("neon.Proxy") |
| 89 | + def allocated_space(self, *, logger): |
| 90 | + space = 0 |
| 91 | + for s in self.tx_sender._create_account_list: |
| 92 | + if s.NAME == NeonCreateContractTxStage.NAME: |
| 93 | + space += s.size + ACCOUNT_MAX_SIZE + SPL_TOKEN_ACCOUNT_SIZE + ACCOUNT_STORAGE_OVERHEAD*3 |
| 94 | + elif s.NAME == NeonCreateAccountTxStage.NAME: |
| 95 | + space += ACCOUNT_MAX_SIZE + SPL_TOKEN_ACCOUNT_SIZE + ACCOUNT_STORAGE_OVERHEAD * 2 |
| 96 | + |
| 97 | + space += self.tx_sender.unpaid_space |
| 98 | + logger.debug(f'allocated space: {space}') |
| 99 | + return space |
| 100 | + |
| 101 | + @logged_group("neon.Proxy") |
| 102 | + def estimate(self, *, logger): |
| 103 | + self.tx_sender.operator_key = PublicKey(os.urandom(32)) |
| 104 | + self.tx_sender._call_emulated(self.sender) |
| 105 | + self.tx_sender._parse_accounts_list(); |
| 106 | + |
| 107 | + gas_for_trx = max(self.simple_neon_tx_strategy(), self.iterative_neon_tx_strategy(), self.holder_neon_tx_strategy()) |
| 108 | + gas_for_space = self.allocated_space() * EVM_BYTE_COST |
| 109 | + gas = gas_for_trx + gas_for_space + EXTRA_GAS |
| 110 | + |
| 111 | + # TODO: MM restriction. Uncomment ? |
| 112 | + # if gas < 21000: |
| 113 | + # gas = 21000 |
| 114 | + |
| 115 | + logger.debug(f'extra_gas: {EXTRA_GAS}') |
| 116 | + logger.debug(f'gas_for_space: {gas_for_space}') |
| 117 | + logger.debug(f'gas_for_trx: {gas_for_trx}') |
| 118 | + logger.debug(f'estimated gas: {gas}') |
| 119 | + return hex(gas) |
0 commit comments