206 lines
9.7 KiB
Python
206 lines
9.7 KiB
Python
import logging
|
|
from typing import List
|
|
|
|
import pytest
|
|
import requests
|
|
import time
|
|
from json import JSONDecodeError
|
|
from decimal import Decimal
|
|
from os import environ
|
|
import tests
|
|
|
|
|
|
class NetworkApi(object):
|
|
def __init__(self):
|
|
self.network_url = 'http://api-goerli.etherscan.io/api?'
|
|
self.headers = {
|
|
'User-Agent':"Mozilla\\5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit\\537.36 (KHTML, like Gecko) Chrome\\7"
|
|
"7.0.3865.90 Safari\\537.36", }
|
|
self.chat_bot_url = 'http://offsite.chat:8099'
|
|
self.api_key = environ.get('ETHERSCAN_API_KEY')
|
|
|
|
def log(self, text: str):
|
|
tests.test_suite_data.current_test.testruns[-1].steps.append(text)
|
|
logging.info(text)
|
|
|
|
def send_etherscan_request(self, method, extracted_param: str):
|
|
for attempt in range(3):
|
|
try:
|
|
response = requests.request('GET', url=method, headers=self.headers).json()
|
|
if response:
|
|
return response[extracted_param]
|
|
except TypeError as e:
|
|
self.log("Check response from etherscan API. Returned values do not match expected. %s" % str(e))
|
|
except JSONDecodeError as e:
|
|
self.log("No valid JSON response from Etherscan: %s " % str(e))
|
|
pass
|
|
time.sleep(30)
|
|
|
|
def get_token_transactions(self, address: str) -> List[dict]:
|
|
method = self.network_url + 'module=account&action=tokentx&address=0x%s&sort=desc&apikey=%s' % (
|
|
address, self.api_key)
|
|
return self.send_etherscan_request(method, 'result')
|
|
|
|
def get_transactions(self, address: str) -> List[dict]:
|
|
method = self.network_url + 'module=account&action=txlist&address=0x%s&sort=desc&apikey=%s' % (address, self.api_key)
|
|
return self.send_etherscan_request(method, 'result')
|
|
|
|
def is_transaction_successful(self, transaction_hash: str) -> int:
|
|
method = self.network_url + 'module=transaction&action=getstatus&txhash=%s' % transaction_hash
|
|
return not int(requests.request('GET', url=method, headers=self.headers).json()['result']['isError'])
|
|
|
|
def get_balance(self, address):
|
|
address = '0x' + address
|
|
method = self.network_url + 'module=account&action=balance&address=%s&tag=latest&apikey=%s' % (
|
|
address, self.api_key)
|
|
balance = self.send_etherscan_request(method, 'result')
|
|
if balance:
|
|
self.log('Balance is %s Gwei' % balance)
|
|
return int(balance)
|
|
else:
|
|
self.log('Cannot extract balance!')
|
|
|
|
def get_latest_block_number(self) -> int:
|
|
method = self.network_url + 'module=proxy&action=eth_blockNumber'
|
|
return int(requests.request('GET', url=method).json()['result'], 0)
|
|
|
|
def find_transaction_by_hash(self, transaction_hash: str):
|
|
method = self.network_url + 'module=transaction&action=gettxreceiptstatus&txhash=%s&apikey=%s' % (
|
|
transaction_hash, self.api_key)
|
|
result = self.send_etherscan_request(method, 'result')
|
|
|
|
if result:
|
|
final_status = True
|
|
if result['status'] == '1':
|
|
self.log("TX %s is found and confirmed" % transaction_hash)
|
|
elif result['status'] == '0':
|
|
self.log("TX %s is found and failed: " % transaction_hash)
|
|
else:
|
|
final_status = False
|
|
self.log("TX %s is not found!" % transaction_hash)
|
|
return final_status
|
|
|
|
def find_transaction_by_unique_amount(self, address, amount, token=False, decimals=18, wait_time=300):
|
|
additional_info = 'token transactions' if token else 'ETH transactions'
|
|
counter = 0
|
|
while True:
|
|
if counter >= wait_time:
|
|
for entry in range(0, 5):
|
|
self.log('Transaction #%s, amount is %s' %(entry+1, float(int(transactions[entry]['value']) / 10 ** decimals)))
|
|
self.log(str(transactions[entry]))
|
|
pytest.fail(
|
|
'Transaction with amount %s is not found in list of %s, address is %s during %ss' %
|
|
(amount, additional_info, address, wait_time))
|
|
else:
|
|
self.log("Finding tx in %s, attempt #%s" % (additional_info, str(int(counter / 30)+1)))
|
|
try:
|
|
if token:
|
|
transactions = self.get_token_transactions(address)
|
|
else:
|
|
transactions = self.get_transactions(address)
|
|
counter += 30
|
|
time.sleep(30)
|
|
except JSONDecodeError as e:
|
|
self.log("No valid JSON response from Etherscan: %s " % str(e))
|
|
continue
|
|
try:
|
|
for transaction in transactions:
|
|
if float(int(transaction['value']) / 10 ** decimals) == float(amount):
|
|
self.log("Tx is found: %s (etherscan API)" % transaction['hash'])
|
|
return transaction
|
|
except TypeError as e:
|
|
self.log("Failed iterate transactions(Etherscan unexpected error): " + str(e))
|
|
continue
|
|
|
|
def wait_for_confirmation_of_transaction(self, address, amount, confirmations=6, token=False):
|
|
start_time = time.time()
|
|
if token:
|
|
token_info = "token transaction"
|
|
else:
|
|
token_info = "ETH transaction"
|
|
self.log('Waiting %s %s for %s to have %s confirmations' % (amount, token_info, address, confirmations))
|
|
while round(time.time() - start_time, ndigits=2) < 600: # should be < idleTimeout capability
|
|
transaction = self.find_transaction_by_unique_amount(address, amount, token)
|
|
self.log(
|
|
'Expected amount of confirmations is %s, in fact %s' % (confirmations, transaction['confirmations']))
|
|
if int(transaction['confirmations']) >= confirmations:
|
|
return
|
|
time.sleep(20)
|
|
pytest.fail('Transaction with amount %s was not confirmed, address is %s, still has %s confirmations' % (amount, address, int(transaction['confirmations'])))
|
|
|
|
def verify_balance_is_updated(self, initial_balance, recipient_address, wait_time=360):
|
|
counter = 0
|
|
while True:
|
|
if counter >= wait_time:
|
|
pytest.fail('Balance is not changed during %s seconds' % wait_time)
|
|
elif initial_balance == self.get_balance(recipient_address):
|
|
counter += 10
|
|
time.sleep(10)
|
|
self.log('Waiting %s seconds for for changing account balance from %s' % (counter, initial_balance))
|
|
else:
|
|
self.log('Balance is updated!')
|
|
return
|
|
|
|
def verify_balance_is(self, expected_balance: int, recipient_address: str, errors: list):
|
|
balance = self.get_balance(recipient_address)
|
|
if balance / 1000000000000000000 != expected_balance:
|
|
errors.append('Recipients balance is not updated on etherscan')
|
|
|
|
# Do not use until web3 update
|
|
# def faucet(self, address):
|
|
# try:
|
|
# self.log("Trying to get funds from %s" % self.faucet_url)
|
|
# return requests.request('GET', '%s/0x%s' % (self.faucet_url, address)).json()
|
|
# except JSONDecodeError as e:
|
|
# self.log("No valid JSON response from Etherscan: %s " % str(e))
|
|
# pass
|
|
|
|
# def faucet_backup(self, address):
|
|
# self.log("Trying to get funds from %s" % self.faucet_backup_address)
|
|
# address = "0x" + address
|
|
# w3.donate_testnet_eth(address=address, amount=0.01, inscrease_default_gas_price=10)
|
|
|
|
|
|
# def get_donate(self, address, external_faucet=False, wait_time=300):
|
|
# initial_balance = self.get_balance(address)
|
|
# counter = 0
|
|
# if initial_balance < 1000000000000000000:
|
|
# if external_faucet:
|
|
# self.faucet_backup(address)
|
|
# else:
|
|
# self.faucet(address)
|
|
# while True:
|
|
# if counter >= wait_time:
|
|
# pytest.fail("Donation was not received during %s seconds!" % wait_time)
|
|
# elif self.get_balance(address) == initial_balance:
|
|
# counter += 10
|
|
# time.sleep(10)
|
|
# self.log('Waiting %s seconds for donation' % counter)
|
|
# else:
|
|
# self.log('Got %s Gwei for %s' % (self.get_balance(address), address))
|
|
# return
|
|
|
|
def start_chat_bot(self, chat_name: str, messages_number: int, interval: int = 1) -> list:
|
|
url = '%s/ping/%s?count=%s&interval=%s' % (self.chat_bot_url, chat_name, messages_number, interval)
|
|
text = requests.request('GET', url).text
|
|
return [i.split(maxsplit=5)[-1].strip('*') for i in text.splitlines()]
|
|
|
|
def get_rounded_balance(self, fetched_balance, actual_balance):
|
|
fetched_balance, actual_balance = str(fetched_balance), str(actual_balance)
|
|
# get actual number of decimals on account balance
|
|
decimals = abs(Decimal(fetched_balance).as_tuple().exponent)
|
|
rounded_balance = round(float(actual_balance), decimals)
|
|
return rounded_balance
|
|
|
|
def get_tx_param_by_hash(self, hash: str, param: str):
|
|
method = self.network_url + 'module=proxy&action=eth_getTransactionByHash&txhash=%s&apikey=%s' % (
|
|
hash, self.api_key)
|
|
res = self.send_etherscan_request(method, 'result')
|
|
return int(res[param], 16)
|
|
|
|
def get_custom_fee_tx_params(self, hash: str):
|
|
return {
|
|
'fee_cap': str(self.get_tx_param_by_hash(hash, 'maxFeePerGas')/1000000000),
|
|
'tip_cap': str(self.get_tx_param_by_hash(hash, 'maxPriorityFeePerGas')/1000000000),
|
|
'gas_limit': str(self.get_tx_param_by_hash(hash, 'gas'))
|
|
} |