ethereum / eth-abi

Ethereum ABI utilities for python
MIT License
248 stars 269 forks source link

encoding tuple with nested tuple containing dynamic types #199

Closed ryandotsmith closed 1 year ago

ryandotsmith commented 1 year ago

What was wrong?

It's likely that I'm not fully understanding the ABI spec, but given a nested tuple where the outer tuple contains a static field and a dynamic nested tuple, I would expect for the outer tuple's static field to not be offset.

Code that produced the error

encode(['(uint8,(uint8,bytes))'], [(42, (43, b'foo'))])

0000000000000000000000000000000000000000000000000000000000000020
000000000000000000000000000000000000000000000000000000000000002a
0000000000000000000000000000000000000000000000000000000000000040
000000000000000000000000000000000000000000000000000000000000002b
0000000000000000000000000000000000000000000000000000000000000040
0000000000000000000000000000000000000000000000000000000000000003
666f6f0000000000000000000000000000000000000000000000000000000000

Expected Result

000000000000000000000000000000000000000000000000000000000000002a
0000000000000000000000000000000000000000000000000000000000000040
000000000000000000000000000000000000000000000000000000000000002b
0000000000000000000000000000000000000000000000000000000000000040
0000000000000000000000000000000000000000000000000000000000000003
666f6f0000000000000000000000000000000000000000000000000000000000

Environment

% python3.10 -m eth_utils
Python version:
3.10.6 (v3.10.6:9c7b4bd164, Aug  1 2022, 17:13:48) [Clang 13.0.0 (clang-1300.0.29.30)]

Operating System: macOS-13.1-arm64-arm-64bit

pip freeze result:
asn1crypto==1.5.1
cffi==1.15.1
coincurve==17.0.0
cytoolz==0.12.0
distlib==0.3.6
eciespy==0.3.13
eth-abi==3.0.1
eth-hash==0.3.3
eth-keys==0.4.0
eth-typing==3.2.0
eth-utils==2.0.0
filelock==3.8.0
packaging==21.3
parsimonious==0.8.1
platformdirs==2.5.4
pluggy==1.0.0
py==1.11.0
pycparser==2.21
pycryptodome==3.15.0
pyparsing==3.0.9
pysha3==1.0.2
six==1.16.0
tomli==2.0.1
toolz==0.12.0
virtualenv==20.16.7
fselmo commented 1 year ago

@ryandotsmith I guess when in doubt, compile the example in Solidity.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;  

contract Contract {
    struct NestedTuple {
        uint8 a;
        bytes b;
    }
    struct Tuple {
        uint8 _a;
        NestedTuple _nt;
    }

    uint8 a = 42;
    uint8 _a = 43;
    bytes b = "foo";    
    NestedTuple _nt = NestedTuple(_a, b);

    function encodeTuple() public view returns (bytes memory) {
        return abi.encode(Tuple(a, _nt));
    }
}

Compiling the above in remix using the latest Solidity version 0.8.17 yields the same result as the eth-abi result

I'm going to close this but if you believe there's something I've missed and it should be re-opened, don't hesitate to ask to re-open. Best of luck.

ryandotsmith commented 1 year ago

Good call on the solidity example! Thanks for the quick reply.

ryandotsmith commented 1 year ago

I think I see where I got confused. My assumption was that I was encoding: tuple(uint8, tuple(uint8, bytes)). But when you call abi.encode, the arguments are coalesced into a tuple so the result looks like this: tuple(tuple(uint8, tuple(uint8, bytes)))