Rehabilitate erc20 balance task
This commit is contained in:
parent
871cbdcaeb
commit
c245a29a6b
@ -1,26 +1,27 @@
|
||||
# standard imports
|
||||
import logging
|
||||
|
||||
# third-party imports
|
||||
# external imports
|
||||
import celery
|
||||
import requests
|
||||
import web3
|
||||
from cic_registry import zero_address
|
||||
from cic_registry.chain import ChainSpec
|
||||
from hexathon import strip_0x
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
from chainlib.chain import ChainSpec
|
||||
from chainlib.status import Status as TxStatus
|
||||
from chainlib.connection import RPCConnection
|
||||
from chainlib.eth.erc20 import ERC20
|
||||
from cic_eth_registry.erc20 import ERC20Token
|
||||
from hexathon import strip_0x
|
||||
|
||||
# platform imports
|
||||
# local imports
|
||||
from cic_eth.registry import safe_registry
|
||||
from cic_eth.db.models.tx import TxCache
|
||||
from cic_eth.db.models.base import SessionBase
|
||||
from cic_eth.eth import RpcClient
|
||||
from cic_eth.error import TokenCountError, PermanentTxError, OutOfGasError, NotLocalTxError
|
||||
from cic_eth.eth.task import (
|
||||
register_tx,
|
||||
create_check_gas_task,
|
||||
)
|
||||
from cic_eth.eth.factory import TxFactory
|
||||
from cic_eth.queue.tx import register_tx
|
||||
from cic_eth.eth.gas import create_check_gas_task
|
||||
#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.task import (
|
||||
@ -32,96 +33,96 @@ from cic_eth.task import (
|
||||
celery_app = celery.current_app
|
||||
logg = logging.getLogger()
|
||||
|
||||
# TODO: fetch from cic-contracts instead when implemented
|
||||
contract_function_signatures = {
|
||||
'transfer': 'a9059cbb',
|
||||
'approve': '095ea7b3',
|
||||
'transferfrom': '23b872dd',
|
||||
}
|
||||
|
||||
|
||||
class TokenTxFactory(TxFactory):
|
||||
"""Factory for creating ERC20 token transactions.
|
||||
"""
|
||||
def approve(
|
||||
self,
|
||||
token_address,
|
||||
spender_address,
|
||||
amount,
|
||||
chain_spec,
|
||||
uuid,
|
||||
session=None,
|
||||
):
|
||||
"""Create an ERC20 "approve" transaction
|
||||
|
||||
:param token_address: ERC20 contract address
|
||||
:type token_address: str, 0x-hex
|
||||
:param spender_address: Address to approve spending for
|
||||
:type spender_address: str, 0x-hex
|
||||
:param amount: Amount of tokens to approve
|
||||
:type amount: int
|
||||
:param chain_spec: Chain spec
|
||||
:type chain_spec: cic_registry.chain.ChainSpec
|
||||
:returns: Unsigned "approve" transaction in standard Ethereum format
|
||||
:rtype: dict
|
||||
"""
|
||||
source_token = self.registry.get_address(chain_spec, token_address)
|
||||
source_token_contract = source_token.contract
|
||||
tx_approve_buildable = source_token_contract.functions.approve(
|
||||
spender_address,
|
||||
amount,
|
||||
)
|
||||
source_token_gas = source_token.gas('transfer')
|
||||
|
||||
tx_approve = tx_approve_buildable.buildTransaction({
|
||||
'from': self.address,
|
||||
'gas': source_token_gas,
|
||||
'gasPrice': self.gas_price,
|
||||
'chainId': chain_spec.chain_id(),
|
||||
'nonce': self.next_nonce(uuid, session=session),
|
||||
})
|
||||
return tx_approve
|
||||
|
||||
|
||||
def transfer(
|
||||
self,
|
||||
token_address,
|
||||
receiver_address,
|
||||
value,
|
||||
chain_spec,
|
||||
uuid,
|
||||
session=None,
|
||||
):
|
||||
"""Create an ERC20 "transfer" transaction
|
||||
|
||||
:param token_address: ERC20 contract address
|
||||
:type token_address: str, 0x-hex
|
||||
:param receiver_address: Address to send tokens to
|
||||
:type receiver_address: str, 0x-hex
|
||||
:param amount: Amount of tokens to send
|
||||
:type amount: int
|
||||
:param chain_spec: Chain spec
|
||||
:type chain_spec: cic_registry.chain.ChainSpec
|
||||
:returns: Unsigned "transfer" transaction in standard Ethereum format
|
||||
:rtype: dict
|
||||
"""
|
||||
source_token = self.registry.get_address(chain_spec, token_address)
|
||||
source_token_contract = source_token.contract
|
||||
transfer_buildable = source_token_contract.functions.transfer(
|
||||
receiver_address,
|
||||
value,
|
||||
)
|
||||
source_token_gas = source_token.gas('transfer')
|
||||
|
||||
tx_transfer = transfer_buildable.buildTransaction(
|
||||
{
|
||||
'from': self.address,
|
||||
'gas': source_token_gas,
|
||||
'gasPrice': self.gas_price,
|
||||
'chainId': chain_spec.chain_id(),
|
||||
'nonce': self.next_nonce(uuid, session=session),
|
||||
})
|
||||
return tx_transfer
|
||||
## TODO: fetch from cic-contracts instead when implemented
|
||||
#contract_function_signatures = {
|
||||
# 'transfer': 'a9059cbb',
|
||||
# 'approve': '095ea7b3',
|
||||
# 'transferfrom': '23b872dd',
|
||||
# }
|
||||
#
|
||||
#
|
||||
#class TokenTxFactory(TxFactory):
|
||||
# """Factory for creating ERC20 token transactions.
|
||||
# """
|
||||
# def approve(
|
||||
# self,
|
||||
# token_address,
|
||||
# spender_address,
|
||||
# amount,
|
||||
# chain_spec,
|
||||
# uuid,
|
||||
# session=None,
|
||||
# ):
|
||||
# """Create an ERC20 "approve" transaction
|
||||
#
|
||||
# :param token_address: ERC20 contract address
|
||||
# :type token_address: str, 0x-hex
|
||||
# :param spender_address: Address to approve spending for
|
||||
# :type spender_address: str, 0x-hex
|
||||
# :param amount: Amount of tokens to approve
|
||||
# :type amount: int
|
||||
# :param chain_spec: Chain spec
|
||||
# :type chain_spec: cic_registry.chain.ChainSpec
|
||||
# :returns: Unsigned "approve" transaction in standard Ethereum format
|
||||
# :rtype: dict
|
||||
# """
|
||||
# source_token = self.registry.get_address(chain_spec, token_address)
|
||||
# source_token_contract = source_token.contract
|
||||
# tx_approve_buildable = source_token_contract.functions.approve(
|
||||
# spender_address,
|
||||
# amount,
|
||||
# )
|
||||
# source_token_gas = source_token.gas('transfer')
|
||||
#
|
||||
# tx_approve = tx_approve_buildable.buildTransaction({
|
||||
# 'from': self.address,
|
||||
# 'gas': source_token_gas,
|
||||
# 'gasPrice': self.gas_price,
|
||||
# 'chainId': chain_spec.chain_id(),
|
||||
# 'nonce': self.next_nonce(uuid, session=session),
|
||||
# })
|
||||
# return tx_approve
|
||||
#
|
||||
#
|
||||
# def transfer(
|
||||
# self,
|
||||
# token_address,
|
||||
# receiver_address,
|
||||
# value,
|
||||
# chain_spec,
|
||||
# uuid,
|
||||
# session=None,
|
||||
# ):
|
||||
# """Create an ERC20 "transfer" transaction
|
||||
#
|
||||
# :param token_address: ERC20 contract address
|
||||
# :type token_address: str, 0x-hex
|
||||
# :param receiver_address: Address to send tokens to
|
||||
# :type receiver_address: str, 0x-hex
|
||||
# :param amount: Amount of tokens to send
|
||||
# :type amount: int
|
||||
# :param chain_spec: Chain spec
|
||||
# :type chain_spec: cic_registry.chain.ChainSpec
|
||||
# :returns: Unsigned "transfer" transaction in standard Ethereum format
|
||||
# :rtype: dict
|
||||
# """
|
||||
# source_token = self.registry.get_address(chain_spec, token_address)
|
||||
# source_token_contract = source_token.contract
|
||||
# transfer_buildable = source_token_contract.functions.transfer(
|
||||
# receiver_address,
|
||||
# value,
|
||||
# )
|
||||
# source_token_gas = source_token.gas('transfer')
|
||||
#
|
||||
# tx_transfer = transfer_buildable.buildTransaction(
|
||||
# {
|
||||
# 'from': self.address,
|
||||
# 'gas': source_token_gas,
|
||||
# 'gasPrice': self.gas_price,
|
||||
# 'chainId': chain_spec.chain_id(),
|
||||
# 'nonce': self.next_nonce(uuid, session=session),
|
||||
# })
|
||||
# return tx_transfer
|
||||
|
||||
|
||||
def unpack_transfer(data):
|
||||
@ -189,7 +190,7 @@ def unpack_approve(data):
|
||||
|
||||
|
||||
@celery_app.task(base=CriticalWeb3Task)
|
||||
def balance(tokens, holder_address, chain_str):
|
||||
def balance(tokens, holder_address, chain_spec_dict):
|
||||
"""Return token balances for a list of tokens for given address
|
||||
|
||||
:param tokens: Token addresses
|
||||
@ -201,14 +202,17 @@ def balance(tokens, holder_address, chain_str):
|
||||
:return: List of balances
|
||||
:rtype: list of int
|
||||
"""
|
||||
#abi = ContractRegistry.abi('ERC20Token')
|
||||
chain_spec = ChainSpec.from_chain_str(chain_str)
|
||||
c = RpcClient(chain_spec)
|
||||
registry = safe_registry(c.w3)
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
rpc = RPCConnection.connect(chain_spec, 'default')
|
||||
caller_address = ERC20Token.caller_address
|
||||
|
||||
for t in tokens:
|
||||
o = registry.get_address(chain_spec, t['address']).contract
|
||||
b = o.functions.balanceOf(holder_address).call()
|
||||
t['balance_network'] = b
|
||||
address = t['address']
|
||||
token = ERC20Token(rpc, address)
|
||||
c = ERC20()
|
||||
o = c.balance_of(address, holder_address, sender_address=caller_address)
|
||||
r = rpc.do(o)
|
||||
t['balance_network'] = c.parse_balance(r)
|
||||
|
||||
return tokens
|
||||
|
||||
@ -487,8 +491,8 @@ class ExtendedTx:
|
||||
self.recipient_label = None
|
||||
self.source_token_value = 0
|
||||
self.destination_token_value = 0
|
||||
self.source_token = zero_address
|
||||
self.destination_token = zero_address
|
||||
self.source_token = ZERO_ADDRESS
|
||||
self.destination_token = ZERO_ADDRESS
|
||||
self.source_token_symbol = ''
|
||||
self.destination_token_symbol = ''
|
||||
self.source_token_decimals = ExtendedTx._default_decimals
|
@ -1,42 +0,0 @@
|
||||
# standard imports
|
||||
import logging
|
||||
|
||||
# local imports
|
||||
from cic_registry import CICRegistry
|
||||
from cic_eth.eth.nonce import NonceOracle
|
||||
from cic_eth.eth import RpcClient
|
||||
|
||||
logg = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TxFactory:
|
||||
"""Base class for transaction factory classes.
|
||||
|
||||
:param from_address: Signer address to create transaction on behalf of
|
||||
:type from_address: str, 0x-hex
|
||||
:param rpc_client: RPC connection object to use to acquire account nonce if no record in nonce cache
|
||||
:type rpc_client: cic_eth.eth.rpc.RpcClient
|
||||
"""
|
||||
|
||||
gas_price = 100
|
||||
"""Gas price, updated between batches"""
|
||||
|
||||
|
||||
def __init__(self, from_address, rpc_client, registry=CICRegistry):
|
||||
self.address = from_address
|
||||
self.registry = registry
|
||||
|
||||
self.default_nonce = rpc_client.w3.eth.getTransactionCount(from_address, 'pending')
|
||||
self.nonce_oracle = NonceOracle(from_address, self.default_nonce)
|
||||
|
||||
TxFactory.gas_price = rpc_client.gas_price()
|
||||
logg.debug('txfactory instance address {} gas price'.format(self.address, self.gas_price))
|
||||
|
||||
|
||||
def next_nonce(self, uuid, session=None):
|
||||
"""Returns the current reserved nonce value, and increments it for next transaction.
|
||||
|
||||
:returns: Nonce
|
||||
:rtype: number
|
||||
"""
|
||||
return self.nonce_oracle.next_by_task_uuid(uuid, session=session)
|
@ -215,8 +215,6 @@ def hashes_to_txs(self, tx_hashes):
|
||||
|
||||
queue = self.request.delivery_info['routing_key']
|
||||
|
||||
#otxs = ','.format("'{}'".format(tx_hash) for tx_hash in tx_hashes)
|
||||
|
||||
session = SessionBase.create_session()
|
||||
q = session.query(Otx.signed_tx)
|
||||
q = q.filter(Otx.tx_hash.in_(tx_hashes))
|
||||
@ -282,16 +280,7 @@ def send(self, txs, chain_spec_dict):
|
||||
|
||||
o = raw(tx_hex)
|
||||
conn = RPCConnection.connect(chain_spec, 'default')
|
||||
#try:
|
||||
#r = c.w3.eth.send_raw_transaction(tx_hex)
|
||||
#r = c.w3.eth.sendRawTransaction(tx_hex)
|
||||
conn.do(o)
|
||||
#except requests.exceptions.ConnectionError as e:
|
||||
# raise(e)
|
||||
# except Exception as e:
|
||||
# raiser = ParityNodeHandler(chain_spec, queue)
|
||||
# (t, e, m) = raiser.handle(e, tx_hash_hex, tx_hex)
|
||||
# raise e(m)
|
||||
s_set_sent.apply_async()
|
||||
|
||||
tx_tail = txs[1:]
|
||||
@ -320,8 +309,13 @@ def refill_gas(self, recipient_address, chain_spec_dict):
|
||||
:returns: Transaction hash.
|
||||
:rtype: str, 0x-hex
|
||||
"""
|
||||
# essentials
|
||||
chain_spec = ChainSpec.from_dict(chain_spec_dict)
|
||||
queue = self.request.delivery_info.get('routing_key')
|
||||
|
||||
# Determine value of gas tokens to send
|
||||
# if an uncompleted gas refill for the same recipient already exists, we still need to spend the nonce
|
||||
# however, we will perform a 0-value transaction instead
|
||||
zero_amount = False
|
||||
session = SessionBase.create_session()
|
||||
status_filter = StatusBits.FINAL | StatusBits.NODE_ERROR | StatusBits.NETWORK_ERROR | StatusBits.UNKNOWN_ERROR
|
||||
@ -336,56 +330,32 @@ def refill_gas(self, recipient_address, chain_spec_dict):
|
||||
zero_amount = True
|
||||
session.flush()
|
||||
|
||||
queue = self.request.delivery_info.get('routing_key')
|
||||
|
||||
#c = RpcClient(chain_spec)
|
||||
rpc = RPCConnection.connect(chain_spec, 'default')
|
||||
|
||||
gas_provider = AccountRole.get_address('GAS_GIFTER', session=session)
|
||||
session.flush()
|
||||
|
||||
# Get default nonce to use from network if no nonce has been set
|
||||
# TODO: This step may be redundant as nonce entry is set at account creation time
|
||||
#default_nonce = c.w3.eth.getTransactionCount(c.gas_provider(), 'pending')
|
||||
#o = count_pending(gas_provider)
|
||||
#default_nonce = conn.do(o)
|
||||
|
||||
nonce_oracle = CustodialTaskNonceOracle(gas_provider, self.request.root_id, session=session) #, default_nonce)
|
||||
#nonce = nonce_generator.next(session=session)
|
||||
#nonce = nonce_generator.next_by_task_uuid(self.request.root_id, session=session)
|
||||
rpc_signer = RPCConnection.connect(chain_spec, 'signer')
|
||||
gas_oracle = self.create_gas_oracle(rpc)
|
||||
c = Gas(signer=rpc_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle, chain_id=chain_spec.chain_id())
|
||||
#gas_price = c.gas_price()
|
||||
#gas_limit = c.default_gas_limit
|
||||
# finally determine the value to send
|
||||
refill_amount = 0
|
||||
if not zero_amount:
|
||||
refill_amount = self.safe_gas_refill_amount
|
||||
|
||||
logg.debug('tx send gas amount {} from provider {} to {}'.format(refill_amount, gas_provider, recipient_address))
|
||||
# # create and sign transaction
|
||||
# tx_send_gas = {
|
||||
# 'from': c.gas_provider(),
|
||||
# 'to': recipient_address,
|
||||
# 'gas': gas_limit,
|
||||
# 'gasPrice': gas_price,
|
||||
# 'chainId': chain_spec.chain_id(),
|
||||
# 'nonce': nonce,
|
||||
# 'value': refill_amount,
|
||||
# 'data': '',
|
||||
# }
|
||||
# #tx_send_gas_signed = c.w3.eth.sign_transaction(tx_send_gas)
|
||||
# #tx_hash = web3.Web3.keccak(hexstr=tx_send_gas_signed['raw'])
|
||||
# #tx_hash_hex = tx_hash.hex()
|
||||
# (tx_hash_hex, tx_send_gas_signed) = sign_tx(tx_send_gas)
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(gas_provider, recipient_address, refill_amount, tx_format=TxFormat.RLP_SIGNED)
|
||||
# determine sender
|
||||
gas_provider = AccountRole.get_address('GAS_GIFTER', session=session)
|
||||
session.flush()
|
||||
|
||||
# TODO: route this through sign_and_register_tx instead
|
||||
# set up evm RPC connection
|
||||
rpc = RPCConnection.connect(chain_spec, 'default')
|
||||
|
||||
# set up transaction builder
|
||||
nonce_oracle = CustodialTaskNonceOracle(gas_provider, self.request.root_id, session=session)
|
||||
gas_oracle = self.create_gas_oracle(rpc)
|
||||
rpc_signer = RPCConnection.connect(chain_spec, 'signer')
|
||||
c = Gas(signer=rpc_signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle, chain_id=chain_spec.chain_id())
|
||||
|
||||
# build and add transaction
|
||||
logg.debug('tx send gas amount {} from provider {} to {}'.format(refill_amount, gas_provider, recipient_address))
|
||||
(tx_hash_hex, tx_signed_raw_hex) = c.create(gas_provider, recipient_address, refill_amount, tx_format=TxFormat.RLP_SIGNED)
|
||||
logg.debug('adding queue refill gas tx {}'.format(tx_hash_hex))
|
||||
#cache_task = 'cic_eth.eth.tx.cache_gas_refill_data'
|
||||
cache_task = 'cic_eth.eth.tx.otx_cache_parse_tx'
|
||||
register_tx(tx_hash_hex, tx_signed_raw_hex, chain_spec, queue, cache_task=cache_task, session=session)
|
||||
|
||||
# add transaction to send queue
|
||||
s_status = celery.signature(
|
||||
'cic_eth.queue.tx.set_ready',
|
||||
[
|
||||
|
@ -13,3 +13,4 @@ from tests.fixtures_role import *
|
||||
from chainlib.eth.pytest import *
|
||||
from contract_registry.pytest import *
|
||||
from cic_eth_registry.pytest.fixtures_contracts import *
|
||||
from cic_eth_registry.pytest.fixtures_tokens import *
|
||||
|
@ -13,7 +13,7 @@ logg = logging.getLogger()
|
||||
def celery_includes():
|
||||
return [
|
||||
# 'cic_eth.eth.bancor',
|
||||
# 'cic_eth.eth.token',
|
||||
'cic_eth.eth.erc20',
|
||||
'cic_eth.eth.tx',
|
||||
# 'cic_eth.ext.tx',
|
||||
'cic_eth.queue.tx',
|
||||
|
43
apps/cic-eth/tests/task/test_task_erc20.py
Normal file
43
apps/cic-eth/tests/task/test_task_erc20.py
Normal file
@ -0,0 +1,43 @@
|
||||
# external imports
|
||||
import celery
|
||||
from chainlib.eth.erc20 import ERC20
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.tx import receipt
|
||||
|
||||
|
||||
def test_erc20_balance(
|
||||
default_chain_spec,
|
||||
foo_token,
|
||||
token_roles,
|
||||
agent_roles,
|
||||
eth_signer,
|
||||
eth_rpc,
|
||||
celery_worker,
|
||||
):
|
||||
|
||||
nonce_oracle = RPCNonceOracle(token_roles['FOO_TOKEN_OWNER'], eth_rpc)
|
||||
c = ERC20(signer=eth_signer, nonce_oracle=nonce_oracle)
|
||||
transfer_value = 100 * (10**6)
|
||||
(tx_hash_hex, o) = c.transfer(foo_token, token_roles['FOO_TOKEN_OWNER'], agent_roles['ALICE'], transfer_value)
|
||||
eth_rpc.do(o)
|
||||
|
||||
o = receipt(tx_hash_hex)
|
||||
r = eth_rpc.do(o)
|
||||
assert r['status'] == 1
|
||||
|
||||
token_object = {
|
||||
'address': foo_token,
|
||||
}
|
||||
s = celery.signature(
|
||||
'cic_eth.eth.erc20.balance',
|
||||
[
|
||||
[token_object],
|
||||
agent_roles['ALICE'],
|
||||
default_chain_spec.asdict(),
|
||||
],
|
||||
queue=None,
|
||||
)
|
||||
t = s.apply_async()
|
||||
r = t.get()
|
||||
assert r[0]['balance_network'] == transfer_value
|
||||
|
Loading…
Reference in New Issue
Block a user