Decoding Tx Data from View Call in Golang for Solidity Contracts

go-ethereumgolangsolidity

I have a view call in my Solidity code that looks like this:

function call1 (address[] calldata targets) external view returns(Return[] memory returnData)

And the struct from the array which is returned looks like this:

struct Return {
    bool success;
    address addr;
    bytes data;
}

My goal is to decode the function call results of this function in Golang. So far I could log the results partially with this code:

res, err := client.CallContract(context.Background(), callMsg, nil)
outputDataList := make([]interface{}, 10)
outputDataList, err = contractABI.Unpack("call1", res)
fmt.Println(outputDataList[0].(interface{}))

I could log the bool and address from the struct, however the bytes variable is still not encoded.

[{true 0x8840c6252e2e86e545defb6da98b2a0e26d8c1ba[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 24 75 122 0 63 109 85 204 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 15 215 247 135 154 188 2 164 101 11 198 216 164 41 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 100 139 110 44]} {true 0x8840c6252e2e86e545defb6da98b2a0e26d8c1ba[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 24 75 122 0 63 109 85 204 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 15 215 247 135 154 188 2 164 101 11 198 216 164 41 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 100 139 110 44]}]

My question is that how should I continue the decode process of this? I would like to have the results in an array of structs that have the same structure as the Solidity struct. What would be the most elegant way to do it? I am also no sure that using an array (outputDataList) is the best option or another solution should be used.

Best Answer

In Golang, the Go Ethereum library provides the abi package which has methods to unpack the transaction data. However, because the bytes data type in Solidity is dynamic, it needs to be decoded differently than fixed-size types like bool and address.

You are currently using the Unpack method which can handle the bool and address types but is not handling the bytes type correctly. To handle the bytes type, you would need to manually parse it.

Here's an example:

package main

import (
    "log"
    "math/big"

    "github.com/ethereum/go-ethereum/accounts/abi"
    "github.com/ethereum/go-ethereum/common"
)

type Return struct {
    Success bool
    Addr    common.Address
    Data    []byte
}

func main() {
    parsedABI, err := abi.JSON(strings.NewReader(contractABI))
    if err != nil {
        log.Fatalf("Failed to parse contract ABI: %v", err)
    }

    data, err := parsedABI.Pack("call1")
    if err != nil {
        log.Fatalf("Failed to pack data for call1: %v", err)
    }

    values := make([]interface{}, 0)
    if err := parsedABI.UnpackIntoInterface(&values, "call1", data); err != nil {
        log.Fatalf("Failed to unpack data: %v", err)
    }

    outputDataList := make([]Return, len(values))
    for i, v := range values {
        val, ok := v.([]interface{})
        if !ok {
            log.Fatalf("Failed to convert to interface slice")
        }

        outputDataList[i] = Return{
            Success: val[0].(*big.Int).Cmp(big.NewInt(1)) == 0,
            Addr:    common.HexToAddress(val[1].(string)),
            Data:    val[2].([]byte),
        }
    }

    log.Println(outputDataList)
}

This code creates a Go struct that mirrors the Solidity struct and then unpacks the values into it. The bytes data type is converted to a byte slice ([]byte) in Go. Note that this is just a basic example, and you might need to modify it based on your specific needs, especially the part that deals with the conversion of data types.

Please replace contractABI with the ABI of your contract and ensure that the function and field names match your contract's. If the UnpackIntoInterface function doesn't work as expected, you might need to do more manual decoding, especially for the bytes field. You can reference the Solidity ABI specification for how the data is encoded and decoded.