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
8 changes: 5 additions & 3 deletions proxy/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
evm_loader_id = os.environ['EVM_LOADER']
faucet_url = os.environ['FAUCET_URL']
wrapper_whitelist = os.environ['INDEXER_ERC20_WRAPPER_WHITELIST'].split(',')
airdrop_amount = int(os.environ['AIRDROP_AMOUNT'])
log_level = os.environ['LOG_LEVEL']
price_update_interval = int(os.environ.get('PRICE_UPDATE_INTERVAL', '60'))
neon_decimals = int(os.environ.get('NEON_DECIMALS', '9'))
run_airdropper(solana_url,
evm_loader_id,
faucet_url,
wrapper_whitelist,
airdrop_amount,
log_level)
log_level,
price_update_interval,
neon_decimals)
else:
entry_point()
2 changes: 0 additions & 2 deletions proxy/common_neon/neon_instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@

obligatory_accounts = [
AccountMeta(pubkey=EVM_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),
AccountMeta(pubkey=SYSVAR_CLOCK_PUBKEY, is_signer=False, is_writable=False),
]


Expand Down
27 changes: 7 additions & 20 deletions proxy/common_neon/transaction_sender.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
from .emulator_interactor import call_emulated
from .layouts import ACCOUNT_INFO_LAYOUT
from .neon_instruction import NeonInstruction
from .solana_interactor import SolanaInteractor, check_if_continue_returned, check_for_errors,\
check_if_program_exceeded_instructions, check_if_storage_is_empty_error
from .solana_interactor import SolanaInteractor, check_if_continue_returned, \
check_if_program_exceeded_instructions, check_for_errors
from ..environment import EVM_LOADER_ID
from ..plugin.eth_proto import Trx as EthTrx

Expand Down Expand Up @@ -275,8 +275,6 @@ def create_account_list_by_emulate(self):
sender_sol = PublicKey(acc_desc["account"])
else:
add_keys_05.append(AccountMeta(pubkey=acc_desc["account"], is_signer=False, is_writable=True))
token_account = getTokenAddr(PublicKey(acc_desc["account"]))
add_keys_05.append(AccountMeta(pubkey=token_account, is_signer=False, is_writable=True))
if acc_desc["new"]:
if code_account:
add_keys_05.append(AccountMeta(pubkey=code_account, is_signer=False, is_writable=code_account_writable))
Expand All @@ -298,10 +296,8 @@ def create_account_list_by_emulate(self):

self.eth_accounts = [
AccountMeta(pubkey=contract_sol, is_signer=False, is_writable=True),
AccountMeta(pubkey=getTokenAddr(contract_sol), is_signer=False, is_writable=True),
] + ([AccountMeta(pubkey=code_sol, is_signer=False, is_writable=code_writable)] if code_sol != None else []) + [
AccountMeta(pubkey=sender_sol, is_signer=False, is_writable=True),
AccountMeta(pubkey=self.caller_token, is_signer=False, is_writable=True),
] + add_keys_05

self.steps_emulated = output_json["steps_executed"]
Expand Down Expand Up @@ -368,6 +364,7 @@ def create_accounts_for_trx(self):
result = self.sender.send_measured_transaction(precall_txs, self.eth_trx, 'CreateAccountsForTrx')
if check_for_errors(result):
raise Exception("Failed to create account for trx")
self.create_acc_trx = Transaction()


def write_trx_to_holder_account(self):
Expand Down Expand Up @@ -439,7 +436,7 @@ def call_continue_step(self):

logger.debug("Step count {}".format(step_count))
try:
result = self.sender.send_measured_transaction(trx, self.eth_trx, 'ContinueV02')
result = self.sender.send_measured_transaction(trx, self.eth_trx, self.instruction_type)
return result
except SendTransactionError as err:
if check_if_program_exceeded_instructions(err.result):
Expand All @@ -459,25 +456,15 @@ def call_cancel(self):

def call_continue_bucked(self):
logger.debug("Send bucked combined: %s", self.instruction_type)
steps = self.steps

receipts = []
for index in range(math.ceil(self.steps_emulated/self.steps) + self.addition_count()):
try:
trx = self.make_combined_trx(steps, index)
trx = self.make_combined_trx(self.steps, index)
receipts.append(self.sender.send_transaction_unconfirmed(trx))
except SendTransactionError as err:
logger.error(f"Failed to call continue bucked, error: {err.result}")
if check_if_storage_is_empty_error(err.result):
pass
elif check_if_program_exceeded_instructions(err.result):
steps = int(steps * 90 / 100)
else:
raise
except Exception as err:
logger.debug(str(err))
if str(err).startswith('failed to get recent blockhash'):
pass
if len(receipts) > 0:
break
else:
raise

Expand Down
51 changes: 38 additions & 13 deletions proxy/indexer/airdropper.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from proxy.indexer.indexer_base import IndexerBase, logger
from proxy.indexer.price_provider import PriceProvider, mainnet_solana, mainnet_price_accounts
import os
import requests
import base58
Expand All @@ -12,23 +13,34 @@
from .utils import check_error
from .sql_dict import SQLDict

ACCOUNT_CREATION_PRICE_SOL = 0.00472692
AIRDROP_AMOUNT_SOL = ACCOUNT_CREATION_PRICE_SOL / 2
NEON_PRICE_USD = 0.25

class Airdropper(IndexerBase):
def __init__(self,
solana_url,
evm_loader_id,
faucet_url = '',
wrapper_whitelist = [],
airdrop_amount = 10,
log_level = 'INFO'):
log_level = 'INFO',
price_upd_interval=60,
neon_decimals = 9):
IndexerBase.__init__(self, solana_url, evm_loader_id, log_level)

# collection of eth-address-to-create-accout-trx mappings
# for every addresses that was already funded with airdrop
self.airdrop_ready = SQLDict(tablename="airdrop_ready")
self.wrapper_whitelist = wrapper_whitelist
self.airdrop_amount = airdrop_amount
self.faucet_url = faucet_url

# Price provider need pyth.network be deployed onto solana
# so using mainnet solana for simplicity
self.price_provider = PriceProvider(mainnet_solana,
price_upd_interval,
mainnet_price_accounts)
self.neon_decimals = neon_decimals


# helper function checking if given contract address is in whitelist
def _is_allowed_wrapper_contract(self, contract_addr):
Expand Down Expand Up @@ -59,18 +71,28 @@ def _check_transfer(self, account_keys, create_token_acc, token_transfer) -> boo

def _airdrop_to(self, create_acc):
eth_address = "0x" + bytearray(base58.b58decode(create_acc['data'])[20:][:20]).hex()

if eth_address in self.airdrop_ready: # transaction already processed
return

logger.info(f"Airdrop to address: {eth_address}")
sol_price_usd = self.price_provider.get_price('SOL/USD')
if sol_price_usd is None:
logger.warning("Failed to get SOL/USD price")
return

logger.info(f'SOL/USD = ${sol_price_usd}')
airdrop_amount_usd = AIRDROP_AMOUNT_SOL * sol_price_usd
logger.info(f"Airdrop amount: ${airdrop_amount_usd}")
logger.info(f"NEON price: ${NEON_PRICE_USD}")
airdrop_amount_neon = airdrop_amount_usd / NEON_PRICE_USD
logger.info(f"Airdrop {airdrop_amount_neon} NEONs to address: {eth_address}")
airdrop_galans = int(airdrop_amount_neon * pow(10, self.neon_decimals))

json_data = { 'wallet': eth_address, 'amount': self.airdrop_amount }
resp = requests.post(self.faucet_url + '/request_eth_token', json = json_data)
json_data = { 'wallet': eth_address, 'amount': airdrop_galans }
resp = requests.post(self.faucet_url + '/request_neon_in_galans', json = json_data)
if not resp.ok:
logger.warning(f'Failed to airdrop: {resp.status_code}')
return

self.airdrop_ready[eth_address] = create_acc


Expand Down Expand Up @@ -138,8 +160,9 @@ def run_airdropper(solana_url,
evm_loader_id,
faucet_url = '',
wrapper_whitelist = [],
airdrop_amount = 10,
log_level = 'INFO'):
log_level = 'INFO',
price_update_interval = 60,
neon_decimals = 9):
logging.basicConfig(format='%(asctime)s - pid:%(process)d [%(levelname)-.1s] %(funcName)s:%(lineno)d - %(message)s')
logger.setLevel(logging.DEBUG)
logger.info(f"""Running indexer with params:
Expand All @@ -148,12 +171,14 @@ def run_airdropper(solana_url,
log_level: {log_level},
faucet_url: {faucet_url},
wrapper_whitelist: {wrapper_whitelist},
airdrop_amount: {airdrop_amount}""")
price update interval: {price_update_interval},
NEON decimals: {neon_decimals}""")

airdropper = Airdropper(solana_url,
evm_loader_id,
faucet_url,
wrapper_whitelist,
airdrop_amount,
log_level)
log_level,
price_update_interval,
neon_decimals)
airdropper.run()
117 changes: 117 additions & 0 deletions proxy/indexer/price_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
from solana.rpc.api import Client
from solana.publickey import PublicKey
import base64
import base58
import struct
from datetime import datetime
from logging import Logger

logger = Logger(__name__)

field_info = {
'expo': { 'pos': 20, 'len': 4, 'format': '<i' },
'agg.price': { 'pos': 208, 'len': 8, 'format': '<q' },
'agg.conf': { 'pos': 216, 'len': 8, 'format': '<Q' },
'agg.status': { 'pos': 224, 'len': 4, 'format': '<I' },
}


def _unpack_field(raw_data: bytes, field_name: str):
field = field_info.get(field_name, None)
if field is None:
raise Exception(f'Unknown field name: {field_name}')

start_idx = field['pos']
stop_idx = field['pos'] + field['len']
return struct.unpack(field['format'], raw_data[start_idx:stop_idx])[0]


mainnet_solana = "https://api.mainnet-beta.solana.com"
devnet_solana = "https://api.devnet.solana.com"
testnet_solana = "https://api.testnet.solana.com"


# See available price accounts at https://pyth.network/developers/accounts/
mainnet_price_accounts = { 'SOL/USD': 'H6ARHf6YXhGYeQfUzQNGk6rDNnLBQKrenN712K4AQJEG' }
devnet_price_accounts = { 'SOL/USD': "J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix" }
testnet_price_accounts = { 'SOL/USD': "7VJsBtJzgTftYzEeooSDYyjKXvYRWJHdwvbwfBvTg9K" }


PRICE_STATUS_UNKNOWN = 0
PRICE_STATUS_TRADING = 1


class PriceProvider:
def __init__(self, solana_url: str, default_upd_int: int, price_accounts=None):
self.client = Client(solana_url)
self.default_upd_int = default_upd_int
self.prices = {}
if price_accounts is not None:
self.price_accounts = price_accounts
else:
self.price_accounts = {}


def _get_current_time(self):
return datetime.now().timestamp()


def _read_price(self, pairname):
acc_id = self.price_accounts.get(pairname, None)
if acc_id is None:
logger.warning(f'No account found for pair {pairname}')
return None

response = self.client.get_account_info(PublicKey(acc_id))
result = response.get('result', None)
if result is None:
logger.warning(f'Failed to read account data for account {acc_id}')
return None

value = result.get('value', None)
if value is None:
logger.warning(f'Failed to read account data for account {acc_id}')
return None

data = value.get('data', None)
if not isinstance(data, list) or len(data) != 2:
logger.warning(f'Failed to read account data for account {acc_id}')
return None

encoding = data[1]
if encoding == 'base58':
data = base58.b58decode(data[0])
elif encoding == 'base64':
data = base64.b64decode(data[0])
else:
logger.warning(f'Unknown encoding {encoding}')
return None

status = _unpack_field(data, 'agg.status')
if status != PRICE_STATUS_TRADING: # not Trading
logger.warning(f'Price status is {status}')
return None

expo = _unpack_field(data, 'expo')
price = _unpack_field(data, 'agg.price')
return price * pow(10, expo)


def get_price(self, pairname):
price_data = self.prices.get(pairname, None)
current_time = self._get_current_time()

if price_data == None or current_time - price_data['last_update'] >= self.default_upd_int:
current_price = self._read_price(pairname)
if current_price is not None:
self.prices[pairname] = { 'price': current_price, 'last_update': current_time }
return current_price

if price_data is not None:
return price_data['price']
else:
return None
# price_data is not None and current_time - price_data['last_update'] < self.default_upd_int
return price_data['price']


2 changes: 1 addition & 1 deletion proxy/plugin/solana_rest_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
modelInstanceLock = threading.Lock()
modelInstance = None

NEON_PROXY_PKG_VERSION = '0.5.0'
NEON_PROXY_PKG_VERSION = '0.5.1'
NEON_PROXY_REVISION = 'NEON_PROXY_REVISION_TO_BE_REPLACED'

class EthereumModel:
Expand Down
Loading