Cómo decodificar Log.Data en Go

Estoy obteniendo los registros usando client.SubscribeFilterLogs. Los parámetros del evento están codificados en types.Log.Data. ¿Cómo puedo decodificarlo en go?

Sí, el paquete go-ethereum (las bibliotecas geth) recibió la contribución para agregar el desempaquetado de eventos. Escribiré más sobre esto en la respuesta a continuación.

Respuestas (4)

El paquete go-ethereum abi en octubre de 2017 recibió la actualización para desempaquetar la salida del evento. Inicialmente, solo podía desempaquetar la salida del método.

Toda la funcionalidad se entrega a través del abi.ABIobjeto. Para usarlo, debe tener Event ABI (cadena JSON). Luego use func (abi *ABI) UnmarshalJSON para construir el ABIobjeto. Desde allí, puede usar el Unpackmétodo usando Datadesde su objeto de registro.

Tenga en cuenta que los atributos indexados van al registro en Topiclugar de Data.

Para obtener más detalles de uso, puede seguir estos ejemplos:

NOTA: En la implementación actual (2017-11-29) hay un error con los atributos indexados. Envié un PR para eso y todavía estoy esperando la aprobación final.

Aquí hay un ejemplo de código completo para cualquiera que todavía esté confundido (gracias a la respuesta de @Robert Zaremba)

package main

import (
    "context"

    "log"
    "math/big"
    "strings"

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

    "github.com/myorg/myrepo/go-contracts/token"
    "github.com/ethereum/go-ethereum/core/types"
)


func main() {
    contractAddress := common.HexToAddress("0x0d8775f648430679a709e98d2b0cb6250d2887ef")

    query := ethereum.FilterQuery{
        Addresses: []common.Address{contractAddress},
    }

    var ch = make(chan types.Log)
    ctx := context.Background()

    sub, err := Client.SubscribeFilterLogs(ctx, query, ch)

    if err != nil {
        log.Fatal(err)
    }

    tokenAbi, err := abi.JSON(strings.NewReader(string(token.TokenABI)))

    if err != nil {
        log.Fatal(err)
    }

    for {
        select {
        case err := <-sub.Err():
            log.Fatal(err)
        case eventLog := <-ch:
            var transferEvent struct {
                From  common.Address
                To    common.Address
                Value *big.Int
            }

            err = tokenAbi.Unpack(&transferEvent, "Transfer", eventLog.Data)

            if err != nil {
                log.Println("Failed to unpack")
                continue
            }

            transferEvent.From = common.BytesToAddress(eventLog.Topics[1].Bytes())
            transferEvent.To = common.BytesToAddress(eventLog.Topics[2].Bytes())

            log.Println("From", transferEvent.From.Hex())
            log.Println("To", transferEvent.To.Hex())
            log.Println("Value", transferEvent.Value)
        }
    }
}
Una pregunta: ¿cómo saber si los datos contienen un evento de transferencia o un evento de aprobación? Porque puede recibir ambos, o incluso puede recibir otros eventos, como burno mint, entonces, ¿cómo sabe de antemano qué tipo de evento tiene que desempacar?
@Nulik gran pregunta; entonces topic[0] será la firma del evento, keccak256(eventName(arg1,arg2))por lo que si se trata de un evento de transferencia, topic[0] será keccak256(Transfer(address,address,uint256))=> ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. En resumen: conoce el tipo de evento filtrando por tema [0], que es el hash de la firma del evento. Consulte esta guía Ethereum Development with Go para ver ejemplos goethereumbook.org
@Nulik aquí hay un registro de eventos de ejemplo de un evento de transferencia etherscan.io/tx/… . Como puede ver, el tema [0] es keccak256(Transfer(address,address,uint256))=>ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
@Nulik aquí hay un registro de eventos de ejemplo de un evento de aprobación etherscan.io/tx/… . Como puede ver, el tema [0] es keccak256(Approval(address,address,uint256))=>8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925
Gracias, lo tengo ahora. Por cierto, ¿conoces algún otro evento estándar? Transfer() y Approval() son los habituales, pero muchos tokens agregan eventos de acuñación o grabación. ¿Conoces algún otro evento que se esté volviendo estándar también para los tokens erc20?
@Nulik no está seguro, consulte el popular EIPS de ethereum github.com/ethereum/EIPs/issues
¿Puedo preguntar qué gethversión se usó con este código?
@NulikVersion: 1.8.10-stable
Gracias. Esto explica por qué mi 1.7.2 no funciona. El abi.Unpack() aún no administra eventos. Supongo que esta función se agregó desde 1.8

Así es como lo hice usando el ABI:

path, _ := filepath.Abs("./resources/etherdelta.abi")
file, err := ioutil.ReadFile(path)
if err != nil {
    fmt.Println("Failed to read file:", err)
}
edabi, err := abi.JSON(strings.NewReader(string(file)))
if err != nil {
    fmt.Println("Invalid abi:", err)
}
var orderStruct struct {
    TokenGet   common.Address
    AmountGet  *big.Int
    TokenGive  common.Address
    AmountGive *big.Int
    Expires    *big.Int
    Nonce      *big.Int
    User       common.Address
}
err = edabi.Unpack(&orderStruct, "Order", log.Data)
if err != nil {
    fmt.Println("Failed to unpack:", err)
}
fmt.Println("TokenGet:", orderStruct.TokenGet.Hex())
fmt.Println("AmountGet:", orderStruct.AmountGet.Hex())
data := common.TrimLeftZeroes(log.Data)
hex := common.Bytes2Hex(data)
hex = TrimLeftZeroes(hex)
if hex != "" {
    erc20Amount, err := hexutil.DecodeBig("0x" + hex)
}



func TrimLeftZeroes(hex string) string {
    idx := 0
    for ; idx < len(hex); idx++ {
        if hex[idx] != '0' {
            break
        }
    }
    return hex[idx:]
}
Explica tu código.
Esta es la respuesta a la pregunta, no me estés tomando el pelo.