Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ RUN apt update && \
pip3 install --upgrade pip && \
/bin/bash -c "source venv/bin/activate" && \
pip install -r requirements.txt && \
pip3 install py-solc-x && \
python3 -c "import solcx; solcx.install_solc(version='0.7.6')" && \
apt remove -y git && \
pip install py-solc-x && \
rm -rf /var/lib/apt/lists/*

COPY --from=cli /opt/solana/bin/solana \
Expand Down
6 changes: 1 addition & 5 deletions proxy/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@
import os
from .indexer.airdropper import run_airdropper
from .indexer.indexer import run_indexer
from solana.rpc.api import Client

if __name__ == '__main__':
airdropper_mode = os.environ.get('AIRDROPPER_MODE', 'False').lower() in [1, 'true', 'True']
indexer_mode = os.environ.get('INDEXER_MODE', 'False').lower() in [1, 'true', 'True']
if airdropper_mode:
print("Will run in airdropper mode")
solana_url = os.environ['SOLANA_URL']
evm_loader_id = os.environ['EVM_LOADER']
pyth_mapping_account = PublicKey(os.environ['PYTH_MAPPING_ACCOUNT'])
faucet_url = os.environ['FAUCET_URL']
wrapper_whitelist = os.environ['INDEXER_ERC20_WRAPPER_WHITELIST']
Expand All @@ -34,7 +32,6 @@
max_conf = float(os.environ.get('MAX_CONFIDENCE_INTERVAL', 0.02))

run_airdropper(solana_url,
evm_loader_id,
pyth_mapping_account,
faucet_url,
wrapper_whitelist,
Expand All @@ -45,8 +42,7 @@
print("Will run in indexer mode")

solana_url = os.environ['SOLANA_URL']
evm_loader_id = os.environ['EVM_LOADER']

run_indexer(solana_url, evm_loader_id)
run_indexer(solana_url)
else:
entry_point()
54 changes: 23 additions & 31 deletions proxy/common_neon/account_whitelist.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import traceback
from datetime import datetime
import time
from proxy.environment import ELF_PARAMS, GET_WHITE_LIST_BALANCE_MAX_RETRIES, GET_WHITE_LIST_BALANCE_RETRY_INTERVAL_S
from proxy.environment import ELF_PARAMS
from proxy.common_neon.permission_token import PermissionToken
from solana.publickey import PublicKey
from solana.rpc.api import Client as SolanaClient
from solana.account import Account as SolanaAccount
from typing import Union
from proxy.common_neon.address import EthereumAddress
from logged_groups import logged_group
from ..common_neon.solana_interactor import SolanaInteractor

NEON_MINIMAL_CLIENT_ALLOWANCE_BALANCE = int(ELF_PARAMS.get("NEON_MINIMAL_CLIENT_ALLOWANCE_BALANCE", 0))
NEON_MINIMAL_CONTRACT_ALLOWANCE_BALANCE = int(ELF_PARAMS.get("NEON_MINIMAL_CONTRACT_ALLOWANCE_BALANCE", 0))
Expand All @@ -18,10 +17,7 @@

@logged_group("neon.AccountWhitelist")
class AccountWhitelist:
def __init__(self, solana: SolanaClient, payer: SolanaAccount, permission_update_int: int):
self.info(f'GET_WHITE_LIST_BALANCE_MAX_RETRIES={GET_WHITE_LIST_BALANCE_MAX_RETRIES}')
self.info(f'GET_WHITE_LIST_BALANCE_RETRY_INTERVAL_S={GET_WHITE_LIST_BALANCE_RETRY_INTERVAL_S} seconds')
self.info(f'permission_update_int={permission_update_int}')
def __init__(self, solana: SolanaInteractor, payer: SolanaAccount, permission_update_int: int):
self.solana = solana
self.account_cache = {}
self.permission_update_int = permission_update_int
Expand All @@ -43,9 +39,15 @@ def __init__(self, solana: SolanaClient, payer: SolanaAccount, permission_update
PublicKey(DENIAL_TOKEN_ADDR),
payer)

def read_balance_diff(self, ether_addr: Union[str, EthereumAddress]):
allowance_balance = self.allowance_token.get_balance(ether_addr)
denial_balance = self.denial_token.get_balance(ether_addr)
def read_balance_diff(self, ether_addr: Union[str, EthereumAddress]) -> int:
token_list = [
self.allowance_token.get_token_account_address(ether_addr),
self.denial_token.get_token_account_address(ether_addr)
]

balance_list = self.solana.get_token_account_balance_list(token_list)
allowance_balance = balance_list[0]
denial_balance = balance_list[1]
return allowance_balance - denial_balance

def grant_permissions(self, ether_addr: Union[str, EthereumAddress], min_balance: int):
Expand Down Expand Up @@ -106,27 +108,17 @@ def has_permission(self, ether_addr: Union[str, EthereumAddress], min_balance: i
if diff < self.permission_update_int:
return cached['diff'] >= min_balance

num_retries = GET_WHITE_LIST_BALANCE_MAX_RETRIES

while True:
try:
diff = self.read_balance_diff(ether_addr)
self.account_cache[ether_addr] = {
'last_update': current_time,
'diff': diff
}
return diff >= min_balance
except Exception as err:
err_tb = "".join(traceback.format_tb(err.__traceback__))
self.error(f'Failed to read permissions for {ether_addr}: ' +
f'Type(err): {type(err)}, Error: {err}, Traceback: {err_tb}')
num_retries -= 1
if num_retries == 0:
# This error should be forwarded to client
raise RuntimeError('Failed to read account permissions. Try to repeat later')

self.info(f'Will retry getting whitelist balance after {GET_WHITE_LIST_BALANCE_RETRY_INTERVAL_S} seconds')
time.sleep(GET_WHITE_LIST_BALANCE_RETRY_INTERVAL_S)
try:
diff = self.read_balance_diff(ether_addr)
self.account_cache[ether_addr] = {
'last_update': current_time,
'diff': diff
}
return diff >= min_balance
except Exception as err:
err_tb = "".join(traceback.format_tb(err.__traceback__))
self.error(f'Failed to read permissions for {ether_addr}: ' +
f'Type(err): {type(err)}, Error: {err}, Traceback: {err_tb}')

def has_client_permission(self, ether_addr: Union[str, EthereumAddress]):
return self.has_permission(ether_addr, NEON_MINIMAL_CLIENT_ALLOWANCE_BALANCE)
Expand Down
38 changes: 10 additions & 28 deletions proxy/common_neon/address.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import random
import base64

from eth_keys import keys as eth_keys
from hashlib import sha256
Expand All @@ -8,9 +9,8 @@
from typing import NamedTuple

from .layouts import ACCOUNT_INFO_LAYOUT
from ..environment import neon_cli, ETH_TOKEN_MINT_ID, EVM_LOADER_ID
from ..environment import ETH_TOKEN_MINT_ID, EVM_LOADER_ID
from .constants import ACCOUNT_SEED_VERSION
from solana.rpc.commitment import Confirmed


class EthereumAddress:
Expand Down Expand Up @@ -60,35 +60,17 @@ def getTokenAddr(account):
return get_associated_token_address(PublicKey(account), ETH_TOKEN_MINT_ID)


class AccountInfo(NamedTuple):
class AccountInfoLayout(NamedTuple):
ether: eth_keys.PublicKey
trx_count: int
code_account: PublicKey
state: int

def is_payed(self) -> bool:
return self.state != 0

@staticmethod
def frombytes(data):
def frombytes(data) -> AccountInfoLayout:
cont = ACCOUNT_INFO_LAYOUT.parse(data)
return AccountInfo(cont.ether, int.from_bytes(cont.trx_count, 'little'), PublicKey(cont.code_account), cont.state)


def _getAccountData(client, account, expected_length, owner=None):
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:
raise Exception("Wrong data length for account data {}".format(account))
return data


def getAccountInfo(client, eth_account: EthereumAddress):
account_sol, nonce = ether2program(eth_account)
info = _getAccountData(client, account_sol, ACCOUNT_INFO_LAYOUT.sizeof())
return AccountInfo.frombytes(info)


def isPayed(client, address: str):
acc_info: AccountInfo = getAccountInfo(client, EthereumAddress(address))
return acc_info.state != 0
return AccountInfoLayout(cont.ether, int.from_bytes(cont.trx_count, 'little'),
PublicKey(cont.code_account), cont.state)
2 changes: 1 addition & 1 deletion proxy/common_neon/costs.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import base58

from ..environment import EVM_LOADER_ID
from ..indexer.utils import BaseDB
from ..indexer.base_db import BaseDB


class SQLCost(BaseDB):
Expand Down
37 changes: 16 additions & 21 deletions proxy/common_neon/estimate.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def evm_step_cost(signature_cnt):

@logged_group("neon.Proxy")
class GasEstimate:
def __init__(self, request, db, client, evm_step_count):
def __init__(self, request, db, solana, evm_step_count):
self.sender: bytes = bytes.fromhex(request.get('from', "0x%040x" % 0x0)[2:])
self.step_count = evm_step_count

Expand All @@ -46,7 +46,7 @@ def __init__(self, request, db, client, evm_step_count):
signed_trx = w3.eth.account.sign_transaction(unsigned_trx, eth_keys.PrivateKey(os.urandom(32)))
trx = EthTrx.fromString(signed_trx.rawTransaction)

self.tx_sender = NeonTxSender(db, client, trx, steps=evm_step_count)
self.tx_sender = NeonTxSender(db, solana, trx, steps=evm_step_count)

def iteration_info(self) -> Tuple[int, int]:
if self.tx_sender.steps_emulated > 0:
Expand All @@ -59,34 +59,30 @@ def iteration_info(self) -> Tuple[int, int]:
final_steps = EVM_STEPS
return final_steps, full_step_iterations

@logged_group("neon.Proxy")
def simple_neon_tx_strategy(self, *, logger):
def simple_neon_tx_strategy(self):
gas = evm_step_cost(2) * (self.tx_sender.steps_emulated if self.tx_sender.steps_emulated > EVM_STEPS else EVM_STEPS)
logger.debug(f'estimate simple_neon_tx_strategy: {gas}')
self.debug(f'estimate simple_neon_tx_strategy: {gas}')
return gas

@logged_group("neon.Proxy")
def iterative_neon_tx_strategy(self, *, logger):
def iterative_neon_tx_strategy(self):
begin_iteration = 1
final_steps, full_step_iterations = self.iteration_info()
steps = begin_iteration * EVM_STEPS + full_step_iterations * self.step_count + final_steps
gas = steps * evm_step_cost(1)
logger.debug(f'estimate iterative_neon_tx_strategy: {gas}')
self.debug(f'estimate iterative_neon_tx_strategy: {gas}')
return gas

@logged_group("neon.Proxy")
def holder_neon_tx_strategy(self, *, logger):
def holder_neon_tx_strategy(self):
begin_iteration = 1
msg = get_holder_msg(self.tx_sender.eth_tx)
holder_iterations = math.ceil(len(msg) / HOLDER_MSG_SIZE)
final_steps, full_step_iterations = self.iteration_info()
steps = (begin_iteration + holder_iterations) * EVM_STEPS + full_step_iterations * self.step_count + final_steps
gas = steps * evm_step_cost(1)
logger.debug(f'estimate holder_neon_tx_strategy: {gas}')
self.debug(f'estimate holder_neon_tx_strategy: {gas}')
return gas

@logged_group("neon.Proxy")
def allocated_space(self, *, logger):
def allocated_space(self):
space = 0
for s in self.tx_sender._create_account_list:
if s.NAME == NeonCreateContractTxStage.NAME:
Expand All @@ -95,14 +91,13 @@ def allocated_space(self, *, logger):
space += ACCOUNT_MAX_SIZE + SPL_TOKEN_ACCOUNT_SIZE + ACCOUNT_STORAGE_OVERHEAD * 2

space += self.tx_sender.unpaid_space
logger.debug(f'allocated space: {space}')
self.debug(f'allocated space: {space}')
return space

@logged_group("neon.Proxy")
def estimate(self, *, logger):
def estimate(self):
self.tx_sender.operator_key = PublicKey(os.urandom(32))
self.tx_sender._call_emulated(self.sender)
self.tx_sender._parse_accounts_list();
self.tx_sender._parse_accounts_list()

gas_for_trx = max(self.simple_neon_tx_strategy(), self.iterative_neon_tx_strategy(), self.holder_neon_tx_strategy())
gas_for_space = self.allocated_space() * EVM_BYTE_COST
Expand All @@ -112,8 +107,8 @@ def estimate(self, *, logger):
# if gas < 21000:
# gas = 21000

logger.debug(f'extra_gas: {EXTRA_GAS}')
logger.debug(f'gas_for_space: {gas_for_space}')
logger.debug(f'gas_for_trx: {gas_for_trx}')
logger.debug(f'estimated gas: {gas}')
self.debug(f'extra_gas: {EXTRA_GAS}')
self.debug(f'gas_for_space: {gas_for_space}')
self.debug(f'gas_for_trx: {gas_for_trx}')
self.debug(f'estimated gas: {gas}')
return hex(gas)
36 changes: 12 additions & 24 deletions proxy/common_neon/permission_token.py
Original file line number Diff line number Diff line change
@@ -1,66 +1,54 @@
from lib2to3.pgen2 import token
from spl.token.client import Token as SplToken
from solana.publickey import PublicKey
from solana.rpc.api import Client as SolanaClient
from solana.account import Account as SolanaAccount
from spl.token.constants import TOKEN_PROGRAM_ID
from spl.token.instructions import get_associated_token_address
from proxy.common_neon.address import EthereumAddress, ether2program
from typing import Union
from solana.rpc.commitment import Confirmed
from solana.transaction import Transaction
from solana.rpc.types import TxOpts
import spl.token.instructions as spl_token
from proxy.common_neon.utils import get_from_dict
from proxy.common_neon.solana_interactor import SolanaInteractor, SolTxListSender
from decimal import Decimal
import os

class PermissionToken:
def __init__(self,
solana: SolanaClient,
solana: SolanaInteractor,
token_mint: PublicKey,
payer: SolanaAccount):
self.solana = solana
self.signer = payer
self.waiter = None
self.token_mint = token_mint
self.payer = payer
self.token = SplToken(self.solana,
self.token_mint,
TOKEN_PROGRAM_ID,
payer)

def get_token_account_address(self, ether_addr: Union[str, EthereumAddress]):
sol_addr = PublicKey(ether2program(ether_addr)[0])
return get_associated_token_address(sol_addr, self.token.pubkey)
return get_associated_token_address(sol_addr, self.token_mint)

def get_balance(self, ether_addr: Union[str, EthereumAddress]):
token_account = self.get_token_account_address(ether_addr)
result = self.token.get_balance(token_account).get('result', None)
if result is None:
return 0
return int(result['value']['amount'])
return self.solana.get_token_account_balance(token_account)

def create_account_if_needed(self,
ether_addr: Union[str, EthereumAddress]):
token_account = self.get_token_account_address(ether_addr)
response = self.solana.get_account_info(token_account, Confirmed)
if get_from_dict(response, 'result', 'value') is not None:
info = self.solana.get_account_info(token_account)
if info is not None:
return token_account

txn = Transaction()
create_txn = spl_token.create_associated_token_account(
payer=self.payer.public_key(),
payer=self.signer.public_key(),
owner=PublicKey(ether2program(ether_addr)[0]),
mint=self.token.pubkey
mint=self.token_mint
)
txn.add(create_txn)
self.token._conn.send_transaction(txn, self.payer, opts=TxOpts(skip_preflight=True, skip_confirmation=False))
SolTxListSender(self, [txn], 'CreateAssociatedTokenAccount(1)', skip_preflight=True).send()
return token_account

def mint_to(self,
amount: int,
ether_addr: Union[str, EthereumAddress],
mint_authority_file: str):
token_account = self.create_account_if_needed(ether_addr)
mint_command = f'spl-token mint "{str(self.token.pubkey)}" {Decimal(amount) * pow(Decimal(10), -9)}'
mint_command = f'spl-token mint "{str(self.token_mint)}" {Decimal(amount) * pow(Decimal(10), -9)}'
mint_command += f' --owner {mint_authority_file} -- "{str(token_account)}"'
os.system(mint_command)
Loading