Solidity Structs – How to Encode a Solidity Struct in Python?

abiencoderv2pythonsolidity

I have in solidity:

struct MyStruct {
    string data;
    address issuer;
}

function getHash(MyStruct calldata myStruct) public pure returns (bytes32) {
    return keccak256(abi.encode(myStruct));
}

and in python:

from eth_utils import keccak
from eth_abi import encode

myStruct = {
    'data': 'Hello',
    'issuer': '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4'  # Example address
}

def getHash(struct):
    return keccak(encode(<NO MATTER WHAT I PUT HERE I CANT GET WHAT I WANT>))

CONTEXT: I need to have some data in solidity with an address. The sender of this data previously must have sent the keccak of said data (with the address embedded somehow), for which he must have previously calculated the hash in python. When he later sends the full data, I need the contract to hash that data and compare it to the hash sent by the user in the first place. Then I need to replace the address (in this example the issuer field), to something like address(0x0) or maybe address(this), (meaning something constant regardless of the transaction sender), and finally store the "unsigned" hash. This is the reason I didnt just use a json string for the data in solidity, as I need to manipulate the address and I dont want to be dealing with concatenation in solidity as its a hassle.

I've googled this doubt in multiple ways, and I've even asked ChatGPT and Bard, but no matter what I do, I cant find a way to encode the data as a struct in python. Either I separate the elements and encode them like eth_abi.encode(['string', 'address'], [struct['data'], struct['issuer']]), which yields the equivalent in solidity of doing abi.encode(myStruct.data, myStruct.issuer). This does not work for me, as I it yields a different set of bytes than encoding the struct directly (which has additional data about the types). In my production code, myStruct is much more complex and has a lot of members, which may change over time, so I would rather not have to manually unpack its members to encode it, and rebuild the struct every time I want to decode it.

No matter what I do, I cant find a way to do something like encode(["string", "address"], [myStruct]). I have also tried:

myStruct = {
    'data': 'Hello',
    'issuer': '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4'  # Example address
}

struct_types = {
    'data': 'string',
    'issuer': 'address'
}

struct_types2 = {
    'string': 'data',
    'address': 'issuer'
}

encode(struct_types, myStruct)
encode([struct_types], myStruct)
encode(struct_types, [myStruct])
encode([struct_types], [myStruct])
encode(struct_types2, myStruct)
encode([struct_types2], myStruct)
encode(struct_types2, [myStruct])
encode([struct_types2], [myStruct])

NONE OF THE ABOVE DO WHAT I NEED. I'm starting to question whether this is even possible, but it shouldnt be that hard, solidity does it easily, it would be surprissing to find out that there is no equivalent way of doing it in python, given the fact that you can easily encode the elements separately, but I cant seem to find a way to add the "struct" part to the encoding, like abi.encode(myStruct) does vs abi.encode(myStruct.data, myStruct.issuer) in solidity.

Best Answer

Adjusted from your example, the following works:

from eth_utils import keccak
from eth_abi import encode

myStruct = {
    'data': 'Hello',
    'issuer': '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4'  # Example address
}

def getHash(struct):
    return keccak(encode(['(string,address)'], [tuple(struct.values())]))

It does rely on the order of of the dict being the same as the tuple in the first argument to encode. A struct in Solidity is the same as a tuple as far encoding/decoding here is concerned.

eth_abi does require that you break everything down to its individual values. If you had an additional myStructTypes available, you use something like this for the first argument to encode:

str(tuple(myStructTypes.values())).replace(' ', '')

If you think a helper function or other solution, or even a docs example, would be useful, you can file an issue for eth-abi here

Related Topic