3

The Go bindings have a type called Log that has the following fields

type Log struct {
    // Consensus fields:
    // address of the contract that generated the event
    Address common.Address `json:"address" gencodec:"required"`
    // list of topics provided by the contract.
    Topics []common.Hash `json:"topics" gencodec:"required"`
    // supplied by the contract, usually ABI-encoded
    Data []byte `json:"data" gencodec:"required"`

    [...]   
}

Let's say I have an event like this event SomethingHappened(uint256 indexed id, address indexed participant1, address indexed participant2, uint256 value1, uint256 value2);

There are 4 topics and they are filled as follows, they are of type common.Hash:

  1. The first topic is the hash of the event signature: SHA3("SomethingHappened(uint256,address,address,uint256,uint256)")
  2. The second topic is the encoded uint256 that can be read using the big library: new(big.Int).SetBytes(log.Topics[1].Bytes())
  3. and 4. are the two addresses of the participants. Since common.Hash is 32 bytes long and common.Address is 20 bytes, you can get the address like this: common.BytesToAddress(log.Topics[2].Bytes()[12:32]).

Now, that's not that convenient to do, but it's still possible. When we try to access the last two arguments, however, I get lost. They are encoded somehow in the Data []byte field of the common.Log struct. The documentation mentions they are "ABI-encoded" but offers not way to do anything with that data. abigen also has no support for event variables, so I'm really lost on how to access those values.

1 Answer 1

0

The Data Log type field contains the unindexed event log arguments, so all you have to do is decode them into Go types.

So for example here's a simple smart contract that emits unindexed log entries:

pragma solidity ^0.4.24;

contract Store {
  event ItemSet(bytes32 key, bytes32 value);

  mapping (bytes32 => bytes32) public items;

  function setItem(bytes32 key, bytes32 value) external {
    items[key] = value;
    emit ItemSet(key, value);
  }
}

Then in your code, after fetching the event logs you call the Unpack method from the smart contract Go binding passing it the struct containing the event properties, the name of the smart contract log event, and lastly the actual log data.

for _, vLog := range logs {
  event := struct {
    Key   [32]byte
    Value [32]byte
  }{}
  err := contractAbi.Unpack(&event, "ItemSet", vLog.Data)
  if err != nil {
    log.Fatal(err)
  }

  fmt.Println(string(event.Key[:]))   // foo
  fmt.Println(string(event.Value[:])) // bar
}

Here's the full example of querying and decoding the unindexed logs of the example smart contract.

package main

import (
    "context"
    "fmt"
    "log"
    "math/big"
    "strings"

    "github.com/ethereum/go-ethereum"
    "github.com/ethereum/go-ethereum/accounts/abi"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/ethclient"

    store "./contracts" // for demo
)

func main() {
    client, err := ethclient.Dial("wss://rinkeby.infura.io/ws")
    if err != nil {
        log.Fatal(err)
    }

    contractAddress := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54")
    query := ethereum.FilterQuery{
        FromBlock: big.NewInt(2394201),
        ToBlock:   big.NewInt(2394201),
        Addresses: []common.Address{
            contractAddress,
        },
    }

    logs, err := client.FilterLogs(context.Background(), query)
    if err != nil {
        log.Fatal(err)
    }

    contractAbi, err := abi.JSON(strings.NewReader(string(store.StoreABI)))
    if err != nil {
        log.Fatal(err)
    }

    for _, vLog := range logs {
        event := struct {
            Key   [32]byte
            Value [32]byte
        }{}
        err := contractAbi.Unpack(&event, "ItemSet", vLog.Data)
        if err != nil {
            log.Fatal(err)
        }

        fmt.Println(string(event.Key[:]))   // foo
        fmt.Println(string(event.Value[:])) // bar

        var topics [4]string
        for i := range vLog.Topics {
            topics[i] = vLog.Topics[i].Hex()
        }

        fmt.Println(topics[0]) // 0xe79e73da417710ae99aa2088575580a60415d359acfad9cdd3382d59c80281d4
    }

    eventSignature := []byte("ItemSet(bytes32,bytes32)")
    hash := crypto.Keccak256Hash(eventSignature)
    fmt.Println(hash.Hex()) // 0xe79e73da417710ae99aa2088575580a60415d359acfad9cdd3382d59c80281d4
}

For indexed logs you simply use log.Topics. The first topic is always the method ID which is a hash of the event log function signature (method name and argument types).

Check out the Ethereum Development with Go guide book for more examples.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.