ethereum / eth-abi

Ethereum ABI utilities for python
MIT License
241 stars 268 forks source link

Unable to decode uint256 when response is 0 #91

Closed Netherdrake closed 6 years ago

Netherdrake commented 6 years ago

What was wrong?

Calling an ERC20 balanceOf will blow up if balance is 0.

_______________________________ test_historic_view_token_balance _______________________________

web3 = <web3.main.Web3 object at 0x7f2029a5b860>
address = '0xFBCe7c17608eBd5640313eCf4d2ff09B6726bAB9', normalizers = ()
function_identifier = 'balanceOf'
transaction = {'to': '0xFBCe7c17608eBd5640313eCf4d2ff09B6726bAB9'}, block_id = 5876634
contract_abi = [{'constant': True, 'inputs': [], 'name': 'totalSupply', 'outputs': [{'name': '', 'type': 'uint256'}], ...}, {'constan...s'}, {'name': 'guy', 'type': 'address'}], 'name': 'allowance', 'outputs': [{'name': '', 'type': 'uint256'}], ...}, ...]
fn_abi = {'constant': True, 'inputs': [{'name': 'src', 'type': 'address'}], 'name': 'balanceOf', 'outputs': [{'name': '', 'type': 'uint256'}], ...}
args = ('0xaAF3FFEE9d4C976aA8d0CB1bb84c3C90ee6E9118',), kwargs = {}
call_transaction = {'data': '0x70a08231000000000000000000000000aaf3ffee9d4c976aa8d0cb1bb84c3c90ee6e9118', 'to': '0xFBCe7c17608eBd5640313eCf4d2ff09B6726bAB9'}
return_data = HexBytes('0x'), output_types = ['uint256'], is_missing_code_error = False
msg = "Could not decode contract function call balanceOf return data b'' for output_types ['uint256']"

    def call_contract_function(
            web3,
            address,
            normalizers,
            function_identifier,
            transaction,
            block_id=None,
            contract_abi=None,
            fn_abi=None,
            *args,
            **kwargs):
        """
        Helper function for interacting with a contract function using the
        `eth_call` API.
        """
        call_transaction = prepare_transaction(
            address,
            web3,
            fn_identifier=function_identifier,
            contract_abi=contract_abi,
            fn_abi=fn_abi,
            transaction=transaction,
            fn_args=args,
            fn_kwargs=kwargs,
        )

        if block_id is None:
            return_data = web3.eth.call(call_transaction)
        else:
            return_data = web3.eth.call(call_transaction, block_identifier=block_id)

        if fn_abi is None:
            fn_abi = find_matching_fn_abi(contract_abi, function_identifier, args, kwargs)

        output_types = get_abi_output_types(fn_abi)

        try:
>           output_data = decode_abi(output_types, return_data)

../../../.pyenv/versions/3.6.5/envs/general/lib/python3.6/site-packages/web3/contract.py:1363: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

types = ['uint256'], data = HexBytes('0x')

    def decode_abi(types, data):
        if not is_bytes(data):
            raise TypeError("The `data` value must be of bytes type.  Got {0}".format(type(data)))

        decoders = [
            registry.get_decoder(type_str)
            for type_str in types
        ]

        decoder = TupleDecoder(decoders=decoders)
        stream = ContextFramesBytesIO(data)

>       return decoder(stream)

../../../.pyenv/versions/3.6.5/envs/general/lib/python3.6/site-packages/eth_abi/abi.py:96: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <eth_abi.decoding.TupleDecoder object at 0x7f2033b002b0>
stream = <eth_abi.decoding.ContextFramesBytesIO object at 0x7f2029cb2150>

    def __call__(self, stream):
>       return self.decode(stream)

../../../.pyenv/versions/3.6.5/envs/general/lib/python3.6/site-packages/eth_abi/decoding.py:118: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<eth_abi.decoding.TupleDecoder object at 0x7f2033b002b0>, <eth_abi.decoding.ContextFramesBytesIO object at 0x7f2029cb2150>)
kwargs = {}

    @functools.wraps(fn)
    def inner(*args, **kwargs):
>       return callback(fn(*args, **kwargs))

../../../.pyenv/versions/3.6.5/envs/general/lib/python3.6/site-packages/eth_utils/functional.py:22: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <eth_abi.decoding.TupleDecoder object at 0x7f2033b002b0>
stream = <eth_abi.decoding.ContextFramesBytesIO object at 0x7f2029cb2150>

    @to_tuple
    def decode(self, stream):
        for decoder in self.decoders:
>           yield decoder(stream)

../../../.pyenv/versions/3.6.5/envs/general/lib/python3.6/site-packages/eth_abi/decoding.py:164: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <eth_abi.decoding.UnsignedIntegerDecoder object at 0x7f2029a73518>
stream = <eth_abi.decoding.ContextFramesBytesIO object at 0x7f2029cb2150>

    def __call__(self, stream):
>       return self.decode(stream)

../../../.pyenv/versions/3.6.5/envs/general/lib/python3.6/site-packages/eth_abi/decoding.py:118: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <eth_abi.decoding.UnsignedIntegerDecoder object at 0x7f2029a73518>
stream = <eth_abi.decoding.ContextFramesBytesIO object at 0x7f2029cb2150>

    def decode(self, stream):
>       raw_data = self.read_data_from_stream(stream)

../../../.pyenv/versions/3.6.5/envs/general/lib/python3.6/site-packages/eth_abi/decoding.py:186: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <eth_abi.decoding.UnsignedIntegerDecoder object at 0x7f2029a73518>
stream = <eth_abi.decoding.ContextFramesBytesIO object at 0x7f2029cb2150>

    def read_data_from_stream(self, stream):
        data = stream.read(self.data_byte_size)

        if len(data) != self.data_byte_size:
            raise InsufficientDataBytes(
                "Tried to read {0} bytes.  Only got {1} bytes".format(
                    self.data_byte_size,
>                   len(data),
                )
            )
E           eth_abi.exceptions.InsufficientDataBytes: Tried to read 32 bytes.  Only got 0 bytes

../../../.pyenv/versions/3.6.5/envs/general/lib/python3.6/site-packages/eth_abi/decoding.py:279: InsufficientDataBytes

The above exception was the direct cause of the following exception:

    def test_historic_view_token_balance():
        assert ETH_CHAIN == 'kovan', 'This test was designed for Kovan chain.'

        address = '0xaAF3FFEE9d4C976aA8d0CB1bb84c3C90ee6E9118'
>       assert view_token_balance(address, block_num=5876634) == 0

tests/core/test_eth.py:36: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
src/core/eth.py:101: in view_token_balance
    return instance.functions.balanceOf(address).call(block_identifier=block_num)
../../../.pyenv/versions/3.6.5/envs/general/lib/python3.6/site-packages/web3/contract.py:1106: in call
    **self.kwargs
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

web3 = <web3.main.Web3 object at 0x7f2029a5b860>
address = '0xFBCe7c17608eBd5640313eCf4d2ff09B6726bAB9', normalizers = ()
function_identifier = 'balanceOf'
transaction = {'to': '0xFBCe7c17608eBd5640313eCf4d2ff09B6726bAB9'}, block_id = 5876634
contract_abi = [{'constant': True, 'inputs': [], 'name': 'totalSupply', 'outputs': [{'name': '', 'type': 'uint256'}], ...}, {'constan...s'}, {'name': 'guy', 'type': 'address'}], 'name': 'allowance', 'outputs': [{'name': '', 'type': 'uint256'}], ...}, ...]
fn_abi = {'constant': True, 'inputs': [{'name': 'src', 'type': 'address'}], 'name': 'balanceOf', 'outputs': [{'name': '', 'type': 'uint256'}], ...}
args = ('0xaAF3FFEE9d4C976aA8d0CB1bb84c3C90ee6E9118',), kwargs = {}
call_transaction = {'data': '0x70a08231000000000000000000000000aaf3ffee9d4c976aa8d0cb1bb84c3c90ee6e9118', 'to': '0xFBCe7c17608eBd5640313eCf4d2ff09B6726bAB9'}
return_data = HexBytes('0x'), output_types = ['uint256'], is_missing_code_error = False
msg = "Could not decode contract function call balanceOf return data b'' for output_types ['uint256']"

    def call_contract_function(
            web3,
            address,
            normalizers,
            function_identifier,
            transaction,
            block_id=None,
            contract_abi=None,
            fn_abi=None,
            *args,
            **kwargs):
        """
        Helper function for interacting with a contract function using the
        `eth_call` API.
        """
        call_transaction = prepare_transaction(
            address,
            web3,
            fn_identifier=function_identifier,
            contract_abi=contract_abi,
            fn_abi=fn_abi,
            transaction=transaction,
            fn_args=args,
            fn_kwargs=kwargs,
        )

        if block_id is None:
            return_data = web3.eth.call(call_transaction)
        else:
            return_data = web3.eth.call(call_transaction, block_identifier=block_id)

        if fn_abi is None:
            fn_abi = find_matching_fn_abi(contract_abi, function_identifier, args, kwargs)

        output_types = get_abi_output_types(fn_abi)

        try:
            output_data = decode_abi(output_types, return_data)
        except DecodingError as e:
            # Provide a more helpful error message than the one provided by
            # eth-abi-utils
            is_missing_code_error = (
                return_data in ACCEPTABLE_EMPTY_STRINGS and
                web3.eth.getCode(address) in ACCEPTABLE_EMPTY_STRINGS
            )
            if is_missing_code_error:
                msg = (
                    "Could not transact with/call contract function, is contract "
                    "deployed correctly and chain synced?"
                )
            else:
                msg = (
                    "Could not decode contract function call {} return data {} for "
                    "output_types {}".format(
                        function_identifier,
                        return_data,
                        output_types
                    )
                )
>           raise BadFunctionCallOutput(msg) from e
E           web3.exceptions.BadFunctionCallOutput: Could not decode contract function call balanceOf return data b'' for output_types ['uint256']

../../../.pyenv/versions/3.6.5/envs/general/lib/python3.6/site-packages/web3/contract.py:1385: BadFunctionCallOutput
Netherdrake commented 6 years ago

Actually, not an issue. I was mistakenly querying a block before erc20 creation.