ethereum / eth-abi

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

"Padding bytes were not empty" when parsing bytes16 argument #116

Closed Destiner closed 5 years ago

Destiner commented 5 years ago

What was wrong?

I tried to parse input of transaction and it gave me an error. Tx hash is 0xfc70289a7d33238c1d45fa62b7cd1f150e8951264025f782f87c5ad09b5838c9.

Source of this contract is publicly known, and both Etherscan and 4bytes show the same signature for that method_id (0x35adc0c5).

However, when I try to decode that particular transaction with that ABI (bytes16,address,address,uint256,uint16), the eth-abi gives me an error. I believe that this is due to the fact that the first argument is bytes16 while the input looks like bytes32. Moreover, replacing last 16 bytes with zeroes solves the problem.

from eth_abi import decode_abi

seller_cancel_abi = ["bytes16","address","address","uint256","uint16"]
input_data = '0x35adc0c53039616566623164303162643464343438333039393266386631323132303631000000000000000000000000e144447b7cfc65a1f859a50df6e134548b5a084b00000000000000000000000014965de4057658ca30c4bc9d0411dd9d97b1392e000000000000000000000000000000000000000000000000712d5ff393d900000000000000000000000000000000000000000000000000000000000000000064'
input_method_data = input_data[10:]
input_data_bytes = bytes.fromhex(input_method_data)
decode_abi(seller_cancel_abi, input_data_bytes) # fails

fixed_input_data = '0x35adc0c53039616566623164303162643464343400000000000000000000000000000000000000000000000000000000e144447b7cfc65a1f859a50df6e134548b5a084b00000000000000000000000014965de4057658ca30c4bc9d0411dd9d97b1392e000000000000000000000000000000000000000000000000712d5ff393d900000000000000000000000000000000000000000000000000000000000000000064'
fixed_input_method_data = fixed_input_data[10:]
fixed_input_data_bytes = bytes.fromhex(fixed_input_method_data)
decode_abi(seller_cancel_abi, fixed_input_data_bytes) # works fine
Click to expand --------------------------------------------------------------------------- NonEmptyPaddingBytes Traceback (most recent call last) ``` in () 5 input_method_data = input_data[10:] 6 input_data_bytes = bytes.fromhex(input_method_data) ----> 7 decode_abi(seller_cancel_abi, input_data_bytes) ~/Files/research/env/lib/python3.7/site-packages/eth_abi/codec.py in decode_abi(self, types, data) 163 stream = ContextFramesBytesIO(data) 164 --> 165 return decoder(stream) 166 167 ~/Files/research/env/lib/python3.7/site-packages/eth_abi/decoding.py in __call__(self, stream) 125 126 def __call__(self, stream: ContextFramesBytesIO) -> Any: --> 127 return self.decode(stream) 128 129 ~/Files/research/env/lib/python3.7/site-packages/eth_utils/functional.py in inner(*args, **kwargs) 44 @functools.wraps(fn) 45 def inner(*args, **kwargs) -> T: # type: ignore ---> 46 return callback(fn(*args, **kwargs)) 47 48 return inner ~/Files/research/env/lib/python3.7/site-packages/eth_abi/decoding.py in decode(self, stream) 171 def decode(self, stream): 172 for decoder in self.decoders: --> 173 yield decoder(stream) 174 175 @parse_tuple_type_str ~/Files/research/env/lib/python3.7/site-packages/eth_abi/decoding.py in __call__(self, stream) 125 126 def __call__(self, stream: ContextFramesBytesIO) -> Any: --> 127 return self.decode(stream) 128 129 ~/Files/research/env/lib/python3.7/site-packages/eth_abi/decoding.py in decode(self, stream) 196 data, padding_bytes = self.split_data_and_padding(raw_data) 197 value = self.decoder_fn(data) --> 198 self.validate_padding_bytes(value, padding_bytes) 199 200 return value ~/Files/research/env/lib/python3.7/site-packages/eth_abi/decoding.py in validate_padding_bytes(self, value, padding_bytes) 328 if padding_bytes != b'\x00' * padding_size: 329 raise NonEmptyPaddingBytes( --> 330 "Padding bytes were not empty: {0}".format(repr(padding_bytes)) 331 ) 332 **NonEmptyPaddingBytes: Padding bytes were not empty: b'830992f8f1212061'** ```

Here's another transaction from the same contract that can't be parsed: 0x035b1e59b573ab7f4ec05f235c01b8ac0304c995e542d52ad321b4e2ea796315

Here's another transaction from similar contract that can't be parsed: 0x7977c27c9312ef4d98f5e42ff0e2774157af4fc3664e5211589721cacd4fbce0

All other transactions from these two contracts work fine.

How can it be fixed?

Seems like eth-abi should accept that input even though it seems malformed.

carver commented 5 years ago

I suspect that these transactions were simply buggy and Solidity allowed them to execute because it ignores the data that should be 0s. In fact, I think it's valuable that eth-abi noticed that these are malformed.

In this example, I would bet that the _tradeID was incorrectly encoded in this example transaction. Note that if you take the 32 bytes supplied and decode them as if they were UTF-8, you get this:

In [9]: Web3.toText(0x3039616566623164303162643464343438333039393266386631323132303631)                                                                                                                                                       
Out[9]: '09aefb1d01bd4d44830992f8f1212061'

It's extremely unlikely that a set of random data would decode to a valid hex string. (and since it started as 32 bytes, once it was decoded, you get 16 bytes) So I think that eth-abi actually helped you spot a bug in someone's transaction.

The transaction didn't fail because it shortcuts and exits cleanly if the tradeId is missing. So however Solidity decided to interpret that first argument, chances are it was simply discarded as an invalid trade id and returned immediately.

davesque commented 5 years ago

@carver 🥇 Yes, what you said. Sorry I didn't see this until today. @Destiner If a value is bytes16, then it must have 16 trailing padding bytes of \x00 in order to be spec compliant. This implies there must be some bug elsewhere in the software stack that is supporting that contract. I'll go ahead and close this since it's not an issue with eth-abi.