How to correctly send RPC call using Golang to get smart-contract owner?

Viewed 3305

Update

Since I'm not able to achieve this using the approach in this question, I created my own library to do the same thing (link). It doesn't rely on go-ethereum package but use the normal net/http package to do JSON RPC request.

I still love to know what I did wrong in my approach below.


Definitions:

  • owner = public variable in contract with address type
  • contract = smart-contract that has owner

This is the curl request to get the owner of a contract. I managed to get the owner. (JSON RPC docs)

curl localhost:8545 -X POST \
--header 'Content-type: application/json' \
--data '{"jsonrpc":"2.0", "method":"eth_call", "params":[{"to": "0x_MY_CONTRACT_ADDRESS", "data": "0x8da5cb5b"}, "latest"], "id":1}'

{"jsonrpc":"2.0","id":1,"result":"0x000000000000000000000000_OWNER"}

But when I try to replicate it in Golang (code below), I got json: cannot unmarshal string into Go value of type main.response error. (go-ethereum code that I use)

package main

import (
    "fmt"
    "log"
    "os"

    "github.com/ethereum/go-ethereum/rpc"
)

func main() {
    client, err := rpc.DialHTTP(os.Getenv("RPC_SERVER"))
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    type request struct {
        To   string `json:"to"`
        Data string `json:"data"`
    }

    type response struct {
        Result string
    }

    req := request{"0x_MY_CONTRACT_ADDRESS", "0x8da5cb5b"}
    var resp response
    if err := client.Call(&resp, "eth_call", req, "latest"); err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%v\n", resp)
}

What did I miss here?

Expected result:

Address in string format. E.g. 0x3ab17372b25154400738C04B04f755321bB5a94b

P/S — I'm aware of abigen and I know it's better and easier to do this using abigen. But I'm trying to solve this specific issue without using abigen method.

4 Answers

You can solve the problem best using the go-ethereum/ethclient:

package main

import (
    "context"
    "log"

    "github.com/ethereum/go-ethereum"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/ethclient"
)

func main() {
    client, _ := ethclient.Dial("https://mainnet.infura.io")
    defer client.Close()

    contractAddr := common.HexToAddress("0xCc13Fc627EFfd6E35D2D2706Ea3C4D7396c610ea")
    callMsg := ethereum.CallMsg{
        To:   &contractAddr,
        Data: common.FromHex("0x8da5cb5b"),
    }

    res, err := client.CallContract(context.Background(), callMsg, nil)
    if err != nil {
        log.Fatalf("Error calling contract: %v", err)
    }
    log.Printf("Owner: %s", common.BytesToAddress(res).Hex())
}

If you look at the client library code, you'll see that the JSON RPC response object is already disassembled and either an error is returned on failure, or the actual result parsed: https://github.com/ethereum/go-ethereum/blob/master/rpc/client.go#L277

The parser however already unwrapped the containing "result" field. Your type still wants to do an additional unwrap:

type response struct {
    Result string
}

Drop the outer struct, simply pass a string pointer to the client.Call's first parameter.

Your response struct doesn't show the data that the json of the response has

try this

type response struct {
    Jsonrpc string `json:"jsonrpc"`
    ID      int    `json:"id"`
    Result  string `json:"result"`
}
Related