Rehabilitate transfer, approve

Signed-off-by: nolash <dev@holbrook.no>
This commit is contained in:
nolash 2021-03-20 13:58:45 +01:00
parent c245a29a6b
commit 3f61a2007e
10 changed files with 372 additions and 347 deletions

View File

@ -161,7 +161,7 @@ class NonceReservation(SessionBase):
o = q.first() o = q.first()
if o == None: if o == None:
raise IntegrityError('nonce {} for key {} address {}: {}'.format(nonce, key, address)) raise IntegrityError('nonce {} for key {} address {}'.format(nonce, key, address))
SessionBase.release_session(session) SessionBase.release_session(session)
session.delete(o) session.delete(o)

View File

@ -46,80 +46,8 @@ from cic_eth.queue.tx import (
register_tx, register_tx,
) )
#logg = logging.getLogger(__name__)
logg = logging.getLogger() logg = logging.getLogger()
celery_app = celery.current_app celery_app = celery.current_app
#celery_app.log.setup_task_loggers(loglevel=logging.DEBUG)
#celery_app.log.redirect_stdouts_to_logger(logg, loglevel=logging.DEBUG)
#class AccountTxFactory(TxFactory):
# """Factory for creating account index contract transactions
# """
# def add(
# self,
# address,
# chain_spec,
# uuid,
# session=None,
# ):
# """Register an Ethereum account address with the on-chain account registry
#
# :param address: Ethereum account address to add
# :type address: str, 0x-hex
# :param chain_spec: Chain to build transaction for
# :type chain_spec: cic_registry.chain.ChainSpec
# :returns: Unsigned "AccountRegistry.add" transaction in standard Ethereum format
# :rtype: dict
# """
#
# c = self.registry.get_contract(chain_spec, 'AccountRegistry')
# f = c.function('add')
# tx_add_buildable = f(
# address,
# )
# gas = c.gas('add')
# tx_add = tx_add_buildable.buildTransaction({
# 'from': self.address,
# 'gas': gas,
# 'gasPrice': self.gas_price,
# 'chainId': chain_spec.chain_id(),
# 'nonce': self.next_nonce(uuid, session=session),
# 'value': 0,
# })
# return tx_add
#
#
# def gift(
# self,
# address,
# chain_spec,
# uuid,
# session=None,
# ):
# """Trigger the on-chain faucet to disburse tokens to the provided Ethereum account
#
# :param address: Ethereum account address to gift to
# :type address: str, 0x-hex
# :param chain_spec: Chain to build transaction for
# :type chain_spec: cic_registry.chain.ChainSpec
# :returns: Unsigned "Faucet.giveTo" transaction in standard Ethereum format
# :rtype: dict
# """
#
# c = self.registry.get_contract(chain_spec, 'Faucet')
# f = c.function('giveTo')
# tx_add_buildable = f(address)
# gas = c.gas('add')
# tx_add = tx_add_buildable.buildTransaction({
# 'from': self.address,
# 'gas': gas,
# 'gasPrice': self.gas_price,
# 'chainId': chain_spec.chain_id(),
# 'nonce': self.next_nonce(uuid, session=session),
# 'value': 0,
# })
# return tx_add
def unpack_register(data): def unpack_register(data):
@ -183,16 +111,11 @@ def create(self, password, chain_str):
o = new_account() o = new_account()
a = conn.do(o) a = conn.do(o)
#try:
# a = c.w3.eth.personal.new_account(password)
#except FileNotFoundError:
# pass
if a == None: if a == None:
raise SignerError('create account') raise SignerError('create account')
logg.debug('created account {}'.format(a)) logg.debug('created account {}'.format(a))
# Initialize nonce provider record for account # Initialize nonce provider record for account
#session = SessionBase.create_session()
session = self.create_session() session = self.create_session()
Nonce.init(a, session=session) Nonce.init(a, session=session)
session.commit() session.commit()
@ -217,7 +140,6 @@ def register(self, account_address, chain_spec_dict, writer_address=None):
chain_spec = ChainSpec.from_dict(chain_spec_dict) chain_spec = ChainSpec.from_dict(chain_spec_dict)
session = self.create_session() session = self.create_session()
#session = SessionBase.create_session()
if writer_address == None: if writer_address == None:
writer_address = AccountRole.get_address('ACCOUNT_REGISTRY_WRITER', session=session) writer_address = AccountRole.get_address('ACCOUNT_REGISTRY_WRITER', session=session)
@ -243,7 +165,6 @@ def register(self, account_address, chain_spec_dict, writer_address=None):
(tx_hash_hex, tx_signed_raw_hex) = account_registry.add(account_registry_address, writer_address, account_address, tx_format=TxFormat.RLP_SIGNED) (tx_hash_hex, tx_signed_raw_hex) = account_registry.add(account_registry_address, writer_address, account_address, tx_format=TxFormat.RLP_SIGNED)
# TODO: if cache task fails, task chain will not return # TODO: if cache task fails, task chain will not return
cache_task = 'cic_eth.eth.account.cache_account_data' cache_task = 'cic_eth.eth.account.cache_account_data'
cache_task = None
# add transaction to queue # add transaction to queue
register_tx(tx_hash_hex, tx_signed_raw_hex, chain_spec, queue, cache_task=cache_task, session=session) register_tx(tx_hash_hex, tx_signed_raw_hex, chain_spec, queue, cache_task=cache_task, session=session)

View File

@ -10,6 +10,10 @@ from chainlib.chain import ChainSpec
from chainlib.status import Status as TxStatus from chainlib.status import Status as TxStatus
from chainlib.connection import RPCConnection from chainlib.connection import RPCConnection
from chainlib.eth.erc20 import ERC20 from chainlib.eth.erc20 import ERC20
from chainlib.eth.tx import (
TxFormat,
unpack,
)
from cic_eth_registry.erc20 import ERC20Token from cic_eth_registry.erc20 import ERC20Token
from hexathon import strip_0x from hexathon import strip_0x
@ -20,15 +24,18 @@ from cic_eth.db.models.base import SessionBase
from cic_eth.eth import RpcClient from cic_eth.eth import RpcClient
from cic_eth.error import TokenCountError, PermanentTxError, OutOfGasError, NotLocalTxError from cic_eth.error import TokenCountError, PermanentTxError, OutOfGasError, NotLocalTxError
from cic_eth.queue.tx import register_tx from cic_eth.queue.tx import register_tx
from cic_eth.eth.gas import create_check_gas_task from cic_eth.eth.gas import (
create_check_gas_task,
MaxGasOracle,
)
#from cic_eth.eth.factory import TxFactory #from cic_eth.eth.factory import TxFactory
from cic_eth.eth.util import unpack_signed_raw_tx
from cic_eth.ext.address import translate_address from cic_eth.ext.address import translate_address
from cic_eth.task import ( from cic_eth.task import (
CriticalSQLAlchemyTask, CriticalSQLAlchemyTask,
CriticalWeb3Task, CriticalWeb3Task,
CriticalSQLAlchemyAndSignerTask, CriticalSQLAlchemyAndSignerTask,
) )
from cic_eth.eth.nonce import CustodialTaskNonceOracle
celery_app = celery.current_app celery_app = celery.current_app
logg = logging.getLogger() logg = logging.getLogger()
@ -87,7 +94,7 @@ logg = logging.getLogger()
# def transfer( # def transfer(
# self, # self,
# token_address, # token_address,
# receiver_address, # Receiver_address,
# value, # value,
# chain_spec, # chain_spec,
# uuid, # uuid,
@ -125,68 +132,68 @@ logg = logging.getLogger()
# return tx_transfer # return tx_transfer
def unpack_transfer(data): #def unpack_transfer(data):
"""Verifies that a transaction is an "ERC20.transfer" transaction, and extracts call parameters from it. # """Verifies that a transaction is an "ERC20.transfer" transaction, and extracts call parameters from it.
#
:param data: Raw input data from Ethereum transaction. # :param data: Raw input data from Ethereum transaction.
:type data: str, 0x-hex # :type data: str, 0x-hex
:raises ValueError: Function signature does not match AccountRegister.add # :raises ValueError: Function signature does not match AccountRegister.add
:returns: Parsed parameters # :returns: Parsed parameters
:rtype: dict # :rtype: dict
""" # """
data = strip_0x(data) # data = strip_0x(data)
f = data[:8] # f = data[:8]
if f != contract_function_signatures['transfer']: # if f != contract_function_signatures['transfer']:
raise ValueError('Invalid transfer data ({})'.format(f)) # raise ValueError('Invalid transfer data ({})'.format(f))
#
d = data[8:] # d = data[8:]
return { # return {
'to': web3.Web3.toChecksumAddress('0x' + d[64-40:64]), # 'to': web3.Web3.toChecksumAddress('0x' + d[64-40:64]),
'amount': int(d[64:], 16) # 'amount': int(d[64:], 16)
} # }
def unpack_transferfrom(data): #def unpack_transferfrom(data):
"""Verifies that a transaction is an "ERC20.transferFrom" transaction, and extracts call parameters from it. # """Verifies that a transaction is an "ERC20.transferFrom" transaction, and extracts call parameters from it.
#
:param data: Raw input data from Ethereum transaction. # :param data: Raw input data from Ethereum transaction.
:type data: str, 0x-hex # :type data: str, 0x-hex
:raises ValueError: Function signature does not match AccountRegister.add # :raises ValueError: Function signature does not match AccountRegister.add
:returns: Parsed parameters # :returns: Parsed parameters
:rtype: dict # :rtype: dict
""" # """
data = strip_0x(data) # data = strip_0x(data)
f = data[:8] # f = data[:8]
if f != contract_function_signatures['transferfrom']: # if f != contract_function_signatures['transferfrom']:
raise ValueError('Invalid transferFrom data ({})'.format(f)) # raise ValueError('Invalid transferFrom data ({})'.format(f))
#
d = data[8:] # d = data[8:]
return { # return {
'from': web3.Web3.toChecksumAddress('0x' + d[64-40:64]), # 'from': web3.Web3.toChecksumAddress('0x' + d[64-40:64]),
'to': web3.Web3.toChecksumAddress('0x' + d[128-40:128]), # 'to': web3.Web3.toChecksumAddress('0x' + d[128-40:128]),
'amount': int(d[128:], 16) # 'amount': int(d[128:], 16)
} # }
#
#
def unpack_approve(data): #def unpack_approve(data):
"""Verifies that a transaction is an "ERC20.approve" transaction, and extracts call parameters from it. # """Verifies that a transaction is an "ERC20.approve" transaction, and extracts call parameters from it.
#
:param data: Raw input data from Ethereum transaction. # :param data: Raw input data from Ethereum transaction.
:type data: str, 0x-hex # :type data: str, 0x-hex
:raises ValueError: Function signature does not match AccountRegister.add # :raises ValueError: Function signature does not match AccountRegister.add
:returns: Parsed parameters # :returns: Parsed parameters
:rtype: dict # :rtype: dict
""" # """
data = strip_0x(data) # data = strip_0x(data)
f = data[:8] # f = data[:8]
if f != contract_function_signatures['approve']: # if f != contract_function_signatures['approve']:
raise ValueError('Invalid approval data ({})'.format(f)) # raise ValueError('Invalid approval data ({})'.format(f))
#
d = data[8:] # d = data[8:]
return { # return {
'to': web3.Web3.toChecksumAddress('0x' + d[64-40:64]), # 'to': web3.Web3.toChecksumAddress('0x' + d[64-40:64]),
'amount': int(d[64:], 16) # 'amount': int(d[64:], 16)
} # }
@celery_app.task(base=CriticalWeb3Task) @celery_app.task(base=CriticalWeb3Task)
@ -197,8 +204,8 @@ def balance(tokens, holder_address, chain_spec_dict):
:type tokens: list of str, 0x-hex :type tokens: list of str, 0x-hex
:param holder_address: Token holder address :param holder_address: Token holder address
:type holder_address: str, 0x-hex :type holder_address: str, 0x-hex
:param chain_str: Chain spec string representation :param chain_spec_dict: Chain spec string representation
:type chain_str: str :type chain_spec_dict: str
:return: List of balances :return: List of balances
:rtype: list of int :rtype: list of int
""" """
@ -218,7 +225,7 @@ def balance(tokens, holder_address, chain_spec_dict):
@celery_app.task(bind=True, base=CriticalSQLAlchemyAndSignerTask) @celery_app.task(bind=True, base=CriticalSQLAlchemyAndSignerTask)
def transfer(self, tokens, holder_address, receiver_address, value, chain_str): def transfer(self, tokens, holder_address, receiver_address, value, chain_spec_dict):
"""Transfer ERC20 tokens between addresses """Transfer ERC20 tokens between addresses
First argument is a list of tokens, to enable the task to be chained to the symbol to token address resolver function. However, it accepts only one token as argument. First argument is a list of tokens, to enable the task to be chained to the symbol to token address resolver function. However, it accepts only one token as argument.
@ -242,29 +249,31 @@ def transfer(self, tokens, holder_address, receiver_address, value, chain_str):
# we only allow one token, one transfer # we only allow one token, one transfer
if len(tokens) != 1: if len(tokens) != 1:
raise TokenCountError raise TokenCountError
chain_spec = ChainSpec.from_chain_str(chain_str)
queue = self.request.delivery_info['routing_key']
# retrieve the token interface
t = tokens[0] t = tokens[0]
chain_spec = ChainSpec.from_dict(chain_spec_dict)
queue = self.request.delivery_info.get('routing_key')
c = RpcClient(chain_spec, holder_address=holder_address) rpc = RPCConnection.connect(chain_spec, 'default')
registry = safe_registry(c.w3) rpc_signer = RPCConnection.connect(chain_spec, 'signer')
txf = TokenTxFactory(holder_address, c, registry=registry) session = self.create_session()
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
session = SessionBase.create_session() gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
tx_transfer = txf.transfer(t['address'], receiver_address, value, chain_spec, self.request.root_id, session=session) c = ERC20(signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle, chain_id=chain_spec.chain_id())
(tx_hash_hex, tx_signed_raw_hex) = sign_and_register_tx(tx_transfer, chain_str, queue, cache_task='cic_eth.eth.token.otx_cache_transfer', session=session) (tx_hash_hex, tx_signed_raw_hex) = c.transfer(t['address'], holder_address, receiver_address, value, tx_format=TxFormat.RLP_SIGNED)
cache_task = 'cic_eth.eth.erc20.cache_transfer_data'
register_tx(tx_hash_hex, tx_signed_raw_hex, chain_spec, queue, cache_task=cache_task, session=session)
session.commit()
session.close() session.close()
gas_budget = tx_transfer['gas'] * tx_transfer['gasPrice'] gas_pair = gas_oracle.get_gas(tx_signed_raw_hex)
gas_budget = gas_pair[0] * gas_pair[1]
logg.debug('transfer tx {} {} {}'.format(tx_hash_hex, queue, gas_budget))
s = create_check_gas_and_send_task( s = create_check_gas_task(
[tx_signed_raw_hex], [tx_signed_raw_hex],
chain_str, chain_spec,
holder_address, holder_address,
gas_budget, gas_budget,
[tx_hash_hex], [tx_hash_hex],
@ -275,7 +284,7 @@ def transfer(self, tokens, holder_address, receiver_address, value, chain_str):
@celery_app.task(bind=True, base=CriticalSQLAlchemyAndSignerTask) @celery_app.task(bind=True, base=CriticalSQLAlchemyAndSignerTask)
def approve(self, tokens, holder_address, spender_address, value, chain_str): def approve(self, tokens, holder_address, spender_address, value, chain_spec_dict):
"""Approve ERC20 transfer on behalf of holder address """Approve ERC20 transfer on behalf of holder address
First argument is a list of tokens, to enable the task to be chained to the symbol to token address resolver function. However, it accepts only one token as argument. First argument is a list of tokens, to enable the task to be chained to the symbol to token address resolver function. However, it accepts only one token as argument.
@ -299,29 +308,30 @@ def approve(self, tokens, holder_address, spender_address, value, chain_str):
# we only allow one token, one transfer # we only allow one token, one transfer
if len(tokens) != 1: if len(tokens) != 1:
raise TokenCountError raise TokenCountError
chain_spec = ChainSpec.from_chain_str(chain_str)
queue = self.request.delivery_info['routing_key']
# retrieve the token interface
t = tokens[0] t = tokens[0]
chain_spec = ChainSpec.from_dict(chain_spec_dict)
queue = self.request.delivery_info.get('routing_key')
c = RpcClient(chain_spec, holder_address=holder_address) rpc = RPCConnection.connect(chain_spec, 'default')
registry = safe_registry(c.w3) rpc_signer = RPCConnection.connect(chain_spec, 'signer')
txf = TokenTxFactory(holder_address, c, registry=registry) session = self.create_session()
nonce_oracle = CustodialTaskNonceOracle(holder_address, self.request.root_id, session=session)
gas_oracle = self.create_gas_oracle(rpc, MaxGasOracle.gas)
c = ERC20(signer=rpc_signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle, chain_id=chain_spec.chain_id())
(tx_hash_hex, tx_signed_raw_hex) = c.approve(t['address'], holder_address, spender_address, value, tx_format=TxFormat.RLP_SIGNED)
cache_task = 'cic_eth.eth.erc20.cache_approve_data'
session = SessionBase.create_session() register_tx(tx_hash_hex, tx_signed_raw_hex, chain_spec, queue, cache_task=cache_task, session=session)
tx_transfer = txf.approve(t['address'], spender_address, value, chain_spec, self.request.root_id, session=session) session.commit()
(tx_hash_hex, tx_signed_raw_hex) = sign_and_register_tx(tx_transfer, chain_str, queue, cache_task='cic_eth.eth.token.otx_cache_approve', session=session)
session.close() session.close()
gas_budget = tx_transfer['gas'] * tx_transfer['gasPrice'] gas_pair = gas_oracle.get_gas(tx_signed_raw_hex)
gas_budget = gas_pair[0] * gas_pair[1]
s = create_check_gas_and_send_task( s = create_check_gas_task(
[tx_signed_raw_hex], [tx_signed_raw_hex],
chain_str, chain_spec,
holder_address, holder_address,
gas_budget, gas_budget,
[tx_hash_hex], [tx_hash_hex],
@ -356,34 +366,11 @@ def resolve_tokens_by_symbol(token_symbols, chain_str):
return tokens return tokens
@celery_app.task(base=CriticalSQLAlchemyTask)
def otx_cache_transfer(
tx_hash_hex,
tx_signed_raw_hex,
chain_str,
):
"""Generates and commits transaction cache metadata for an ERC20.transfer or ERC20.transferFrom transaction
:param tx_hash_hex: Transaction hash
:type tx_hash_hex: str, 0x-hex
:param tx_signed_raw_hex: Raw signed transaction
:type tx_signed_raw_hex: str, 0x-hex
:param chain_str: Chain spec string representation
:type chain_str: str
:returns: Transaction hash and id of cache element in storage backend, respectively
:rtype: tuple
"""
chain_spec = ChainSpec.from_chain_str(chain_str)
tx_signed_raw_bytes = bytes.fromhex(tx_signed_raw_hex[2:])
tx = unpack_signed_raw_tx(tx_signed_raw_bytes, chain_spec.chain_id())
(txc, cache_id) = cache_transfer_data(tx_hash_hex, tx)
return txc
@celery_app.task(base=CriticalSQLAlchemyTask) @celery_app.task(base=CriticalSQLAlchemyTask)
def cache_transfer_data( def cache_transfer_data(
tx_hash_hex, tx_hash_hex,
tx, tx_signed_raw_hex,
chain_spec_dict,
): ):
"""Helper function for otx_cache_transfer """Helper function for otx_cache_transfer
@ -394,19 +381,23 @@ def cache_transfer_data(
:returns: Transaction hash and id of cache element in storage backend, respectively :returns: Transaction hash and id of cache element in storage backend, respectively
:rtype: tuple :rtype: tuple
""" """
tx_data = unpack_transfer(tx['data']) chain_spec = ChainSpec.from_dict(chain_spec_dict)
logg.debug('tx data {}'.format(tx_data)) tx_signed_raw_bytes = bytes.fromhex(strip_0x(tx_signed_raw_hex))
logg.debug('tx {}'.format(tx)) tx = unpack(tx_signed_raw_bytes, chain_spec.chain_id())
tx_data = ERC20.parse_transfer_request(tx['data'])
recipient_address = tx_data[0]
token_value = tx_data[1]
session = SessionBase.create_session() session = SessionBase.create_session()
tx_cache = TxCache( tx_cache = TxCache(
tx_hash_hex, tx_hash_hex,
tx['from'], tx['from'],
tx_data['to'], recipient_address,
tx['to'], tx['to'],
tx['to'], tx['to'],
tx_data['amount'], token_value,
tx_data['amount'], token_value,
session=session, session=session,
) )
session.add(tx_cache) session.add(tx_cache)
@ -416,34 +407,11 @@ def cache_transfer_data(
return (tx_hash_hex, cache_id) return (tx_hash_hex, cache_id)
@celery_app.task(base=CriticalSQLAlchemyTask)
def otx_cache_approve(
tx_hash_hex,
tx_signed_raw_hex,
chain_str,
):
"""Generates and commits transaction cache metadata for an ERC20.approve transaction
:param tx_hash_hex: Transaction hash
:type tx_hash_hex: str, 0x-hex
:param tx_signed_raw_hex: Raw signed transaction
:type tx_signed_raw_hex: str, 0x-hex
:param chain_str: Chain spec string representation
:type chain_str: str
:returns: Transaction hash and id of cache element in storage backend, respectively
:rtype: tuple
"""
chain_spec = ChainSpec.from_chain_str(chain_str)
tx_signed_raw_bytes = bytes.fromhex(tx_signed_raw_hex[2:])
tx = unpack_signed_raw_tx(tx_signed_raw_bytes, chain_spec.chain_id())
(txc, cache_id) = cache_approve_data(tx_hash_hex, tx)
return txc
@celery_app.task(base=CriticalSQLAlchemyTask) @celery_app.task(base=CriticalSQLAlchemyTask)
def cache_approve_data( def cache_approve_data(
tx_hash_hex, tx_hash_hex,
tx, tx_signed_raw_hex,
chain_spec_dict,
): ):
"""Helper function for otx_cache_approve """Helper function for otx_cache_approve
@ -454,19 +422,23 @@ def cache_approve_data(
:returns: Transaction hash and id of cache element in storage backend, respectively :returns: Transaction hash and id of cache element in storage backend, respectively
:rtype: tuple :rtype: tuple
""" """
tx_data = unpack_approve(tx['data']) chain_spec = ChainSpec.from_dict(chain_spec_dict)
logg.debug('tx data {}'.format(tx_data)) tx_signed_raw_bytes = bytes.fromhex(strip_0x(tx_signed_raw_hex))
logg.debug('tx {}'.format(tx)) tx = unpack(tx_signed_raw_bytes, chain_spec.chain_id())
tx_data = ERC20.parse_approve_request(tx['data'])
recipient_address = tx_data[0]
token_value = tx_data[1]
session = SessionBase.create_session() session = SessionBase.create_session()
tx_cache = TxCache( tx_cache = TxCache(
tx_hash_hex, tx_hash_hex,
tx['from'], tx['from'],
tx_data['to'], recipient_address,
tx['to'], tx['to'],
tx['to'], tx['to'],
tx_data['amount'], token_value,
tx_data['amount'], token_value,
session=session, session=session,
) )
session.add(tx_cache) session.add(tx_cache)

View File

@ -12,77 +12,83 @@ from cic_eth.db.models.base import SessionBase
logg = logging.getLogger() logg = logging.getLogger()
#
class GasOracle(): #class GasOracle():
"""Provides gas pricing for transactions. # """Provides gas pricing for transactions.
#
:param w3: Web3 object # :param w3: Web3 object
:type w3: web3.Web3 # :type w3: web3.Web3
""" # """
#
__safe_threshold_amount_value = 2000000000 * 60000 * 3 # __safe_threshold_amount_value = 2000000000 * 60000 * 3
__refill_amount_value = __safe_threshold_amount_value * 5 # __refill_amount_value = __safe_threshold_amount_value * 5
default_gas_limit = 21000 # default_gas_limit = 21000
#
def __init__(self, conn): # def __init__(self, conn):
o = price() # o = price()
r = conn.do(o) # r = conn.do(o)
b = bytes.from_hex(strip_0x(r)) # b = bytes.from_hex(strip_0x(r))
self.gas_price_current = int.from_bytes(b, 'big') # self.gas_price_current = int.from_bytes(b, 'big')
#
#self.w3 = w3 # #self.w3 = w3
#self.gas_price_current = w3.eth.gas_price() # #self.gas_price_current = w3.eth.gas_price()
#
#
# def safe_threshold_amount(self):
# """The gas balance threshold under which a new gas refill transaction should be initiated.
#
# :returns: Gas token amount
# :rtype: number
# """
# g = GasOracle.__safe_threshold_amount_value
# logg.warning('gas safe threshold is currently hardcoded to {}'.format(g))
# return g
#
#
# def refill_amount(self):
# """The amount of gas tokens to send in a gas refill transaction.
#
# :returns: Gas token amount
# :rtype: number
# """
# g = GasOracle.__refill_amount_value
# logg.warning('gas refill amount is currently hardcoded to {}'.format(g))
# return g
#
#
# def gas_provider(self):
# """Gas provider address.
#
# :returns: Etheerum account address
# :rtype: str, 0x-hex
# """
# session = SessionBase.create_session()
# a = AccountRole.get_address('GAS_GIFTER', session)
# logg.debug('gasgifter {}'.format(a))
# session.close()
# return a
#
#
# def gas_price(self, category='safe'):
# """Get projected gas price to use for a transaction at the current moment.
#
# When the category parameter is implemented, it can be used to control the priority of a transaction in the network.
#
# :param category: Bid level category to return price for. Currently has no effect.
# :type category: str
# :returns: Gas price
# :rtype: number
# """
# #logg.warning('gas price hardcoded to category "safe"')
# #g = 100
# #return g
# return self.gas_price_current
def safe_threshold_amount(self): class MaxGasOracle:
"""The gas balance threshold under which a new gas refill transaction should be initiated.
:returns: Gas token amount def gas(code=None):
:rtype: number return 8000000
"""
g = GasOracle.__safe_threshold_amount_value
logg.warning('gas safe threshold is currently hardcoded to {}'.format(g))
return g
def refill_amount(self):
"""The amount of gas tokens to send in a gas refill transaction.
:returns: Gas token amount
:rtype: number
"""
g = GasOracle.__refill_amount_value
logg.warning('gas refill amount is currently hardcoded to {}'.format(g))
return g
def gas_provider(self):
"""Gas provider address.
:returns: Etheerum account address
:rtype: str, 0x-hex
"""
session = SessionBase.create_session()
a = AccountRole.get_address('GAS_GIFTER', session)
logg.debug('gasgifter {}'.format(a))
session.close()
return a
def gas_price(self, category='safe'):
"""Get projected gas price to use for a transaction at the current moment.
When the category parameter is implemented, it can be used to control the priority of a transaction in the network.
:param category: Bid level category to return price for. Currently has no effect.
:type category: str
:returns: Gas price
:rtype: number
"""
#logg.warning('gas price hardcoded to category "safe"')
#g = 100
#return g
return self.gas_price_current
def create_check_gas_task(tx_signed_raws_hex, chain_spec, holder_address, gas=None, tx_hashes_hex=None, queue=None): def create_check_gas_task(tx_signed_raws_hex, chain_spec, holder_address, gas=None, tx_hashes_hex=None, queue=None):

View File

@ -1,39 +1,39 @@
# standard imports # standard imports
import logging import logging
# local imports
from cic_eth.eth.gas import GasOracle
logg = logging.getLogger() logg = logging.getLogger()
class RpcClient(GasOracle): class RpcClient:
"""RPC wrapper for web3 enabling gas calculation helpers and signer middleware. pass
:param chain_spec: Chain spec #class RpcClient(GasOracle):
:type chain_spec: cic_registry.chain.ChainSpec # """RPC wrapper for web3 enabling gas calculation helpers and signer middleware.
:param holder_address: DEPRECATED Address of subject of the session. #
:type holder_address: str, 0x-hex # :param chain_spec: Chain spec
""" # :type chain_spec: cic_registry.chain.ChainSpec
# :param holder_address: DEPRECATED Address of subject of the session.
signer_ipc_path = None # :type holder_address: str, 0x-hex
"""Unix socket path to JSONRPC signer and keystore""" # """
#
web3_constructor = None # signer_ipc_path = None
"""Custom function to build a web3 object with middleware plugins""" # """Unix socket path to JSONRPC signer and keystore"""
#
# web3_constructor = None
def __init__(self, chain_spec, holder_address=None): # """Custom function to build a web3 object with middleware plugins"""
(self.provider, w3) = RpcClient.web3_constructor() #
super(RpcClient, self).__init__(w3) #
self.chain_spec = chain_spec # def __init__(self, chain_spec, holder_address=None):
if holder_address != None: # (self.provider, w3) = RpcClient.web3_constructor()
self.holder_address = holder_address # super(RpcClient, self).__init__(w3)
logg.info('gasprice {}'.format(self.gas_price())) # self.chain_spec = chain_spec
# if holder_address != None:
# self.holder_address = holder_address
@staticmethod # logg.info('gasprice {}'.format(self.gas_price()))
def set_constructor(web3_constructor): #
"""Sets the constructor to use for building the web3 object. #
""" # @staticmethod
RpcClient.web3_constructor = web3_constructor # def set_constructor(web3_constructor):
# """Sets the constructor to use for building the web3 object.
# """
# RpcClient.web3_constructor = web3_constructor

View File

@ -1,7 +1,6 @@
pytest==6.0.1 pytest==6.0.1
pytest-celery==0.0.0a1 pytest-celery==0.0.0a1
pytest-mock==3.3.1 pytest-mock==3.3.1
py-eth==0.1.1
pytest-cov==2.10.1 pytest-cov==2.10.1
eth-tester==0.5.0b3 eth-tester==0.5.0b3
py-evm==0.3.0a20 py-evm==0.3.0a20

View File

@ -9,6 +9,7 @@ sys.path.insert(0, root_dir)
from tests.fixtures_config import * from tests.fixtures_config import *
from tests.fixtures_database import * from tests.fixtures_database import *
from tests.fixtures_celery import *
from tests.fixtures_role import * from tests.fixtures_role import *
from chainlib.eth.pytest import * from chainlib.eth.pytest import *
from contract_registry.pytest import * from contract_registry.pytest import *

View File

@ -11,6 +11,7 @@ logg = logging.getLogger()
# celery fixtures # celery fixtures
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def celery_includes(): def celery_includes():
logg.debug('foooooooooooo ')
return [ return [
# 'cic_eth.eth.bancor', # 'cic_eth.eth.bancor',
'cic_eth.eth.erc20', 'cic_eth.eth.erc20',

View File

@ -18,14 +18,15 @@ logg = logging.getLogger()
@pytest.fixture(scope='function') @pytest.fixture(scope='function')
def custodial_roles( def custodial_roles(
contract_roles, contract_roles,
token_roles,
eth_accounts, eth_accounts,
init_database, init_database,
): ):
r = {} r = {}
r.update(contract_roles) r.update(contract_roles)
r.update({ r.update({
'DEFAULT': eth_accounts[0], 'GAS_GIFTER': eth_accounts[10],
'GAS_GIFTER': eth_accounts[3], 'FOO_TOKEN_GIFTER': token_roles['FOO_TOKEN_OWNER'],
}) })
for k in r.keys(): for k in r.keys():
role = AccountRole.set(k, r[k]) role = AccountRole.set(k, r[k])

View File

@ -1,22 +1,65 @@
# standard imports
import logging
# external imports # external imports
import pytest
import celery import celery
from chainlib.eth.erc20 import ERC20 from chainlib.eth.erc20 import ERC20
from chainlib.eth.nonce import RPCNonceOracle from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.tx import receipt from chainlib.eth.tx import (
receipt,
TxFormat,
)
# local imports
from cic_eth.queue.tx import register_tx
logg = logging.getLogger()
def test_erc20_balance( def test_otx_cache_transfer(
default_chain_spec, default_chain_spec,
foo_token, foo_token,
token_roles, token_roles,
agent_roles, agent_roles,
eth_signer, eth_signer,
eth_rpc, eth_rpc,
celery_worker, init_database,
celery_session_worker,
):
nonce_oracle = RPCNonceOracle(token_roles['FOO_TOKEN_OWNER'], eth_rpc)
c = ERC20(signer=eth_signer, nonce_oracle=nonce_oracle, chain_id=default_chain_spec.chain_id())
transfer_value = 100 * (10**6)
(tx_hash_hex, tx_signed_raw_hex) = c.transfer(foo_token, token_roles['FOO_TOKEN_OWNER'], agent_roles['ALICE'], transfer_value, tx_format=TxFormat.RLP_SIGNED)
register_tx(tx_hash_hex, tx_signed_raw_hex, default_chain_spec, None, session=init_database)
s = celery.signature(
'cic_eth.eth.erc20.cache_transfer_data',
[
tx_hash_hex,
tx_signed_raw_hex,
default_chain_spec.asdict(),
],
queue=None,
)
t = s.apply_async()
r = t.get()
assert r[0] == tx_hash_hex
def test_erc20_balance_task(
default_chain_spec,
foo_token,
token_roles,
agent_roles,
eth_signer,
eth_rpc,
celery_session_worker,
): ):
nonce_oracle = RPCNonceOracle(token_roles['FOO_TOKEN_OWNER'], eth_rpc) nonce_oracle = RPCNonceOracle(token_roles['FOO_TOKEN_OWNER'], eth_rpc)
c = ERC20(signer=eth_signer, nonce_oracle=nonce_oracle) c = ERC20(signer=eth_signer, nonce_oracle=nonce_oracle, chain_id=default_chain_spec.chain_id())
transfer_value = 100 * (10**6) transfer_value = 100 * (10**6)
(tx_hash_hex, o) = c.transfer(foo_token, token_roles['FOO_TOKEN_OWNER'], agent_roles['ALICE'], transfer_value) (tx_hash_hex, o) = c.transfer(foo_token, token_roles['FOO_TOKEN_OWNER'], agent_roles['ALICE'], transfer_value)
eth_rpc.do(o) eth_rpc.do(o)
@ -41,3 +84,84 @@ def test_erc20_balance(
r = t.get() r = t.get()
assert r[0]['balance_network'] == transfer_value assert r[0]['balance_network'] == transfer_value
def test_erc20_transfer_task(
default_chain_spec,
foo_token,
agent_roles,
custodial_roles,
eth_signer,
eth_rpc,
init_database,
celery_session_worker,
):
token_object = {
'address': foo_token,
}
transfer_value = 100 * (10 ** 6)
s_nonce = celery.signature(
'cic_eth.eth.tx.reserve_nonce',
[
[token_object],
custodial_roles['FOO_TOKEN_GIFTER'],
],
queue=None,
)
s_transfer = celery.signature(
'cic_eth.eth.erc20.transfer',
[
custodial_roles['FOO_TOKEN_GIFTER'],
agent_roles['ALICE'],
transfer_value,
default_chain_spec.asdict(),
],
queue=None,
)
s_nonce.link(s_transfer)
t = s_nonce.apply_async()
r = t.get_leaf()
logg.debug('result {}'.format(r))
def test_erc20_approve_task(
default_chain_spec,
foo_token,
agent_roles,
custodial_roles,
eth_signer,
eth_rpc,
init_database,
celery_session_worker,
):
token_object = {
'address': foo_token,
}
transfer_value = 100 * (10 ** 6)
s_nonce = celery.signature(
'cic_eth.eth.tx.reserve_nonce',
[
[token_object],
custodial_roles['FOO_TOKEN_GIFTER'],
],
queue=None,
)
s_transfer = celery.signature(
'cic_eth.eth.erc20.approve',
[
custodial_roles['FOO_TOKEN_GIFTER'],
agent_roles['ALICE'],
transfer_value,
default_chain_spec.asdict(),
],
queue=None,
)
s_nonce.link(s_transfer)
t = s_nonce.apply_async()
r = t.get_leaf()
logg.debug('result {}'.format(r))