用Go來做以太坊開發⑤事件日志

事件

智能合約具有在執行期間“發出”事件的能力。 事件在以太坊中也稱為“日志”。 事件的輸出存儲在日志部分下的事務處理中。 事件已經在以太坊智能合約中被廣泛使用,以便在發生相對重要的動作時記錄,特別是在代幣合約(即ERC-20)中,以指示代幣轉賬已經發生。 這些部分將引導您完成從區塊鏈中讀取事件以及訂閱事件的過程,以便交易事務被礦工打包入塊的時候及時收到通知。

監聽事件日志

訂閱事件日志

為了訂閱事件日志,我們需要做的第一件事就是撥打啟用websocket的以太坊客戶端。 幸運的是,Infura支持websockets。

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

下一步是創建篩選查詢。 在這個例子中,我們將閱讀來自我們在之前課程中創建的示例合約中的所有事件。

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

我們接收事件的方式是通過Go channel。 讓我們從go-ethereumcore/types包創建一個類型為Log的channel。

logs := make(chan types.Log)

現在我們所要做的就是通過從客戶端調用SubscribeFilterLogs來訂閱,它接收查詢選項和輸出通道。 這將返回包含unsubscribe和error方法的訂閱結構。

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

最后,我們要做的就是使用select語句設置一個連續循環來讀入新的日志事件或訂閱錯誤。

for {
  select {
  case err := <-sub.Err():
    log.Fatal(err)
  case vLog := <-logs:
    fmt.Println(vLog) // pointer to event log
  }
}

我們會在下個章節介紹如何解析日志。

完整代碼

Commands

solc --abi Store.sol
solc --bin Store.sol
abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=Store.go

Store.sol

pragma solidity ^0.4.24;

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

  string public version;
  mapping (bytes32 => bytes32) public items;

  constructor(string _version) public {
    version = _version;
  }

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

event_subscribe.go

package main

import (
    "context"
    "fmt"
    "log"

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

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

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

    logs := make(chan types.Log)
    sub, err := client.SubscribeFilterLogs(context.Background(), query, logs)
    if err != nil {
        log.Fatal(err)
    }

    for {
        select {
        case err := <-sub.Err():
            log.Fatal(err)
        case vLog := <-logs:
            fmt.Println(vLog) // pointer to event log
        }
    }
}
$ solc --version
0.4.24+commit.e67f0147.Emscripten.clang

讀取事件日志

智能合約可以可選地釋放“事件”,其作為交易收據的一部分存儲日志。讀取這些事件相當簡單。首先我們需要構造一個過濾查詢。我們從go-ethereum包中導入FilterQuery結構體并用過濾選項初始化它。我們告訴它我們想過濾的區塊范圍并指定從中讀取此日志的合約地址。在示例中,我們將從在智能合約章節創建的智能合約中讀取特定區塊所有日志。

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

下一步是調用ethclient的FilterLogs,它接收我們的查詢并將返回所有的匹配事件日志。

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

返回的所有日志將是ABI編碼,因此它們本身不會非常易讀。為了解碼日志,我們需要導入我們智能合約的ABI。為此,我們導入編譯好的智能合約Go包,它將包含名稱格式為<Contract>ABI的外部屬性。之后,我們使用go-ethereum中的accounts/abi包的abi.JSON函數返回一個我們可以在Go應用程序中使用的解析過的ABI接口。

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

現在我們可以通過日志進行迭代并將它們解碼為我么可以使用的類型。若您回憶起我們的樣例合約釋放的日志在Solidity中是類型為bytes32,那么Go中的等價物將是[32]byte。我們可以使用這些類型創建一個匿名結構體,并將指針作為第一個參數傳遞給解析后的ABI接口的Unpack函數,以解碼原始的日志數據。第二個參數是我們嘗試解碼的事件名稱,最后一個參數是編碼的日志數據。

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
}

此外,日志結構體包含附加信息,例如,區塊摘要,區塊號和交易摘要。

fmt.Println(vLog.BlockHash.Hex()) // 0x3404b8c050aa0aacd0223e91b5c32fee6400f357764771d0684fa7b3f448f1a8
fmt.Println(vLog.BlockNumber)     // 2394201
fmt.Println(vLog.TxHash.Hex())    // 0x280201eda63c9ff6f305fcee51d5eb86167fab40ca3108ec784e8652a0e2b1a6

主題(Topics)

若您的solidity事件包含indexed事件類型,那么它們將成為主題而不是日志的數據屬性的一部分。在solidity中您最多只能有4個主題,但只有3個可索引的事件類型。第一個主題總是事件的簽名。我們的示例合約不包含可索引的事件,但如果它確實包含,這是如何讀取事件主題。

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

這就是閱讀和解析日志的全部內容。要學習如何訂閱日志,閱讀上個章節。

完整代碼

命令

solc --abi Store.sol
solc --bin Store.sol
abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=Store.go

Store.sol

pragma solidity ^0.4.24;

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

  string public version;
  mapping (bytes32 => bytes32) public items;

  constructor(string _version) public {
    version = _version;
  }

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

event_read.go

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 {
        fmt.Println(vLog.BlockHash.Hex()) // 0x3404b8c050aa0aacd0223e91b5c32fee6400f357764771d0684fa7b3f448f1a8
        fmt.Println(vLog.BlockNumber)     // 2394201
        fmt.Println(vLog.TxHash.Hex())    // 0x280201eda63c9ff6f305fcee51d5eb86167fab40ca3108ec784e8652a0e2b1a6

        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
}
$ solc --version
0.4.24+commit.e67f0147.Emscripten.clang

讀取ERC-20代幣的事件日志

首先,創建ERC-20智能合約的事件日志的interface文件 erc20.sol:

pragma solidity ^0.4.24;

contract ERC20 {
    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

然后在給定abi使用abigen創建Go包

solc --abi erc20.sol
abigen --abi=erc20_sol_ERC20.abi --pkg=token --out=erc20.go

現在在我們的Go應用程序中,讓我們創建與ERC-20事件日志簽名類型相匹配的結構類型:

type LogTransfer struct {
    From   common.Address
    To     common.Address
    Tokens *big.Int
}

type LogApproval struct {
    TokenOwner common.Address
    Spender    common.Address
    Tokens     *big.Int
}

初始化以太坊客戶端

client, err := ethclient.Dial("https://mainnet.infura.io")
if err != nil {
    log.Fatal(err)
}

按照ERC-20智能合約地址和所需的塊范圍創建一個“FilterQuery”。這個例子我們會用ZRX 代幣:

// 0x Protocol (ZRX) token address
contractAddress := common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498")
query := ethereum.FilterQuery{
    FromBlock: big.NewInt(6383820),
    ToBlock:   big.NewInt(6383840),
    Addresses: []common.Address{
        contractAddress,
    },
}

FilterLogs來過濾日志:

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

接下來我們將解析JSON abi,稍后我們將使用解壓縮原始日志數據:

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

為了按某種日志類型進行過濾,我們需要弄清楚每個事件日志函數簽名的keccak256哈希值。 事件日志函數簽名哈希始終是topic [0],我們很快就會看到。 以下是使用go-ethereumcrypto包計算keccak256哈希的方法:

logTransferSig := []byte("Transfer(address,address,uint256)")
LogApprovalSig := []byte("Approval(address,address,uint256)")
logTransferSigHash := crypto.Keccak256Hash(logTransferSig)
logApprovalSigHash := crypto.Keccak256Hash(LogApprovalSig)

現在我們將遍歷所有日志并設置switch語句以按事件日志類型進行過濾:

for _, vLog := range logs {
    fmt.Printf("Log Block Number: %d\n", vLog.BlockNumber)
    fmt.Printf("Log Index: %d\n", vLog.Index)

    switch vLog.Topics[0].Hex() {
    case logTransferSigHash.Hex():
        //
    case logApprovalSigHash.Hex():
        //
    }
}

現在要解析Transfer事件日志,我們將使用abi.Unpack將原始日志數據解析為我們的日志類型結構。 解包不會解析indexed事件類型,因為它們存儲在topics下,所以對于那些我們必須單獨解析,如下例所示:

fmt.Printf("Log Name: Transfer\n")

var transferEvent LogTransfer

err := contractAbi.Unpack(&transferEvent, "Transfer", vLog.Data)
if err != nil {
    log.Fatal(err)
}

transferEvent.From = common.HexToAddress(vLog.Topics[1].Hex())
transferEvent.To = common.HexToAddress(vLog.Topics[2].Hex())

fmt.Printf("From: %s\n", transferEvent.From.Hex())
fmt.Printf("To: %s\n", transferEvent.To.Hex())
fmt.Printf("Tokens: %s\n", transferEvent.Tokens.String())

Approval 日志也是類似的方法:

fmt.Printf("Log Name: Approval\n")

var approvalEvent LogApproval

err := contractAbi.Unpack(&approvalEvent, "Approval", vLog.Data)
if err != nil {
  log.Fatal(err)
}

approvalEvent.TokenOwner = common.HexToAddress(vLog.Topics[1].Hex())
approvalEvent.Spender = common.HexToAddress(vLog.Topics[2].Hex())

fmt.Printf("Token Owner: %s\n", approvalEvent.TokenOwner.Hex())
fmt.Printf("Spender: %s\n", approvalEvent.Spender.Hex())
fmt.Printf("Tokens: %s\n", approvalEvent.Tokens.String())

最后,把所有的步驟放一起:

Log Block Number: 6383829
Log Index: 20
Log Name: Transfer
From: 0xd03dB9CF89A9b1f856a8E1650cFD78FAF2338eB2
To: 0x924CD9b60F4173DCDd5254ddD38C4F9CAB68FE6b
Tokens: 2804000000000000000000

Log Block Number: 6383831
Log Index: 62
Log Name: Approval
Token Owner: 0xDD3b9186Da521AbE707B48B8f805Fb3Cd5EEe0EE
Spender: 0xCf67d7A481CEEca0a77f658991A00366FED558F7
Tokens: 10000000000000000000000000000000000000000000000000000000000000000

Log Block Number: 6383838
Log Index: 13
Log Name: Transfer
From: 0xBA826fEc90CEFdf6706858E5FbaFcb27A290Fbe0
To: 0x4aEE792A88eDDA29932254099b9d1e06D537883f
Tokens: 2863452144424379687066

我們可以把解析的日志與etherscan的數據對比: https://etherscan.io/tx/0x0c3b6cf604275c7e44dc7db400428c1a39f33f0c6cbc19ff625f6057a5cb32c0#eventlog

完整代碼

Commands

solc --abi erc20.sol
abigen --abi=erc20_sol_ERC20.abi --pkg=token --out=erc20.go

erc20.sol

pragma solidity ^0.4.24;

contract ERC20 {
    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}

event_read_erc20.go

package main

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

    token "./contracts_erc20" // for demo
    "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"
)

// LogTransfer ..
type LogTransfer struct {
    From   common.Address
    To     common.Address
    Tokens *big.Int
}

// LogApproval ..
type LogApproval struct {
    TokenOwner common.Address
    Spender    common.Address
    Tokens     *big.Int
}

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

    // 0x Protocol (ZRX) token address
    contractAddress := common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498")
    query := ethereum.FilterQuery{
        FromBlock: big.NewInt(6383820),
        ToBlock:   big.NewInt(6383840),
        Addresses: []common.Address{
            contractAddress,
        },
    }

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

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

    logTransferSig := []byte("Transfer(address,address,uint256)")
    LogApprovalSig := []byte("Approval(address,address,uint256)")
    logTransferSigHash := crypto.Keccak256Hash(logTransferSig)
    logApprovalSigHash := crypto.Keccak256Hash(LogApprovalSig)

    for _, vLog := range logs {
        fmt.Printf("Log Block Number: %d\n", vLog.BlockNumber)
        fmt.Printf("Log Index: %d\n", vLog.Index)

        switch vLog.Topics[0].Hex() {
        case logTransferSigHash.Hex():
            fmt.Printf("Log Name: Transfer\n")

            var transferEvent LogTransfer

            err := contractAbi.Unpack(&transferEvent, "Transfer", vLog.Data)
            if err != nil {
                log.Fatal(err)
            }

            transferEvent.From = common.HexToAddress(vLog.Topics[1].Hex())
            transferEvent.To = common.HexToAddress(vLog.Topics[2].Hex())

            fmt.Printf("From: %s\n", transferEvent.From.Hex())
            fmt.Printf("To: %s\n", transferEvent.To.Hex())
            fmt.Printf("Tokens: %s\n", transferEvent.Tokens.String())

        case logApprovalSigHash.Hex():
            fmt.Printf("Log Name: Approval\n")

            var approvalEvent LogApproval

            err := contractAbi.Unpack(&approvalEvent, "Approval", vLog.Data)
            if err != nil {
                log.Fatal(err)
            }

            approvalEvent.TokenOwner = common.HexToAddress(vLog.Topics[1].Hex())
            approvalEvent.Spender = common.HexToAddress(vLog.Topics[2].Hex())

            fmt.Printf("Token Owner: %s\n", approvalEvent.TokenOwner.Hex())
            fmt.Printf("Spender: %s\n", approvalEvent.Spender.Hex())
            fmt.Printf("Tokens: %s\n", approvalEvent.Tokens.String())
        }

        fmt.Printf("\n\n")
    }
}

solc version used for these examples

$ solc --version
0.4.24+commit.e67f0147.Emscripten.clang

讀取0x Protocol事件日志

要讀取0x Protocol事件日志,我們必須首先將solidity智能合約編譯為一個Go包。

安裝solc版本0.4.11

npm i -g solc@0.4.11

為例如Exchange.sol的事件日志創建0x Protocol交易所智能合約接口:

Create the 0x protocol exchange smart contract interface for event logs as Exchange.sol:

pragma solidity 0.4.11;

contract Exchange {
    event LogFill(
        address indexed maker,
        address taker,
        address indexed feeRecipient,
        address makerToken,
        address takerToken,
        uint filledMakerTokenAmount,
        uint filledTakerTokenAmount,
        uint paidMakerFee,
        uint paidTakerFee,
        bytes32 indexed tokens, // keccak256(makerToken, takerToken), allows subscribing to a token pair
        bytes32 orderHash
    );

    event LogCancel(
        address indexed maker,
        address indexed feeRecipient,
        address makerToken,
        address takerToken,
        uint cancelledMakerTokenAmount,
        uint cancelledTakerTokenAmount,
        bytes32 indexed tokens,
        bytes32 orderHash
    );

    event LogError(uint8 indexed errorId, bytes32 indexed orderHash);
}

接著給定abi,使用abigen來創建Goexchange包:

Then use abigen to create the Go exchange package given the abi:

solc --abi Exchange.sol
abigen --abi="Exchange.sol:Exchange.abi" --pkg=exchange --out=Exchange.go

現在在我們的Go應用程序中,讓我們創建與0xProtocol事件日志簽名類型匹配的結構體類型:

type LogFill struct {
    Maker                  common.Address
    Taker                  common.Address
    FeeRecipient           common.Address
    MakerToken             common.Address
    TakerToken             common.Address
    FilledMakerTokenAmount *big.Int
    FilledTakerTokenAmount *big.Int
    PaidMakerFee           *big.Int
    PaidTakerFee           *big.Int
    Tokens                 [32]byte
    OrderHash              [32]byte
}

type LogCancel struct {
    Maker                     common.Address
    FeeRecipient              common.Address
    MakerToken                common.Address
    TakerToken                common.Address
    CancelledMakerTokenAmount *big.Int
    CancelledTakerTokenAmount *big.Int
    Tokens                    [32]byte
    OrderHash                 [32]byte
}

type LogError struct {
    ErrorID   uint8
    OrderHash [32]byte
}

初始化以太坊客戶端:

client, err := ethclient.Dial("https://mainnet.infura.io")
if err != nil {
    log.Fatal(err)
}

創建一個FilterQuery,并為其傳遞0x Protocol智能合約地址和所需的區塊范圍:

// 0x Protocol Exchange smart contract address
contractAddress := common.HexToAddress("0x12459C951127e0c374FF9105DdA097662A027093")
query := ethereum.FilterQuery{
    FromBlock: big.NewInt(6383482),
    ToBlock:   big.NewInt(6383488),
    Addresses: []common.Address{
        contractAddress,
    },
}

FilterLogs查詢日志:

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

接下來我們將解析JSON abi,我們后續將使用解壓縮原始日志數據:

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

為了按某種日志類型過濾,我們需要知曉每個事件日志函數簽名的keccak256摘要。正如我們很快所見到的那樣,事件日志函數簽名摘要總是topic[0]

// NOTE: keccak256("LogFill(address,address,address,address,address,uint256,uint256,uint256,uint256,bytes32,bytes32)")
logFillEvent := common.HexToHash("0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3")

// NOTE: keccak256("LogCancel(address,address,address,address,uint256,uint256,bytes32,bytes32)")
logCancelEvent := common.HexToHash("67d66f160bc93d925d05dae1794c90d2d6d6688b29b84ff069398a9b04587131")

// NOTE: keccak256("LogError(uint8,bytes32)")
logErrorEvent := common.HexToHash("36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e90")

現在我們迭代所有的日志并設置一個switch語句來按事件日志類型過濾:

for _, vLog := range logs {
  fmt.Printf("Log Block Number: %d\n", vLog.BlockNumber)
  fmt.Printf("Log Index: %d\n", vLog.Index)

  switch vLog.Topics[0].Hex() {
  case logFillEvent.Hex():
    //
  case logCancelEvent.Hex():
    //
  case logErrorEvent.Hex():
    //
  }
}

現在要解析LogFill,我們將使用abi.Unpack將原始數據類型解析為我們自定義的日志類型結構體。Unpack不會解析indexed事件類型,因為這些它們存儲在topics下,所以對于那些我們必須單獨解析,如下例所示:

fmt.Printf("Log Name: LogFill\n")

var fillEvent LogFill

err := contractAbi.Unpack(&fillEvent, "LogFill", vLog.Data)
if err != nil {
  log.Fatal(err)
}

fillEvent.Maker = common.HexToAddress(vLog.Topics[1].Hex())
fillEvent.FeeRecipient = common.HexToAddress(vLog.Topics[2].Hex())
fillEvent.Tokens = vLog.Topics[3]

fmt.Printf("Maker: %s\n", fillEvent.Maker.Hex())
fmt.Printf("Taker: %s\n", fillEvent.Taker.Hex())
fmt.Printf("Fee Recipient: %s\n", fillEvent.FeeRecipient.Hex())
fmt.Printf("Maker Token: %s\n", fillEvent.MakerToken.Hex())
fmt.Printf("Taker Token: %s\n", fillEvent.TakerToken.Hex())
fmt.Printf("Filled Maker Token Amount: %s\n", fillEvent.FilledMakerTokenAmount.String())
fmt.Printf("Filled Taker Token Amount: %s\n", fillEvent.FilledTakerTokenAmount.String())
fmt.Printf("Paid Maker Fee: %s\n", fillEvent.PaidMakerFee.String())
fmt.Printf("Paid Taker Fee: %s\n", fillEvent.PaidTakerFee.String())
fmt.Printf("Tokens: %s\n", hexutil.Encode(fillEvent.Tokens[:]))
fmt.Printf("Order Hash: %s\n", hexutil.Encode(fillEvent.OrderHash[:]))

對于LogCancel類似:

fmt.Printf("Log Name: LogError\n")

errorID, err := strconv.ParseInt(vLog.Topics[1].Hex(), 16, 64)
if err != nil {
  log.Fatal(err)
}

errorEvent := &LogError{
  ErrorID:   uint8(errorID),
  OrderHash: vLog.Topics[2],
}

fmt.Printf("Error ID: %d\n", errorEvent.ErrorID)
fmt.Printf("Order Hash: %s\n", hexutil.Encode(errorEvent.OrderHash[:]))

最后是LogError

fmt.Printf("Log Name: LogCancel\n")

var cancelEvent LogCancel

err := contractAbi.Unpack(&cancelEvent, "LogCancel", vLog.Data)
if err != nil {
  log.Fatal(err)
}

cancelEvent.Maker = common.HexToAddress(vLog.Topics[1].Hex())
cancelEvent.FeeRecipient = common.HexToAddress(vLog.Topics[2].Hex())
cancelEvent.Tokens = vLog.Topics[3]

fmt.Printf("Maker: %s\n", cancelEvent.Maker.Hex())
fmt.Printf("Fee Recipient: %s\n", cancelEvent.FeeRecipient.Hex())
fmt.Printf("Maker Token: %s\n", cancelEvent.MakerToken.Hex())
fmt.Printf("Taker Token: %s\n", cancelEvent.TakerToken.Hex())
fmt.Printf("Cancelled Maker Token Amount: %s\n", cancelEvent.CancelledMakerTokenAmount.String())
fmt.Printf("Cancelled Taker Token Amount: %s\n", cancelEvent.CancelledTakerTokenAmount.String())
fmt.Printf("Tokens: %s\n", hexutil.Encode(cancelEvent.Tokens[:]))
fmt.Printf("Order Hash: %s\n", hexutil.Encode(cancelEvent.OrderHash[:]))

將它們放在一起并運行我們將看到以下輸出:

Log Block Number: 6383482
Log Index: 35
Log Name: LogFill
Maker: 0x8dd688660ec0BaBD0B8a2f2DE3232645F73cC5eb
Taker: 0xe269E891A2Ec8585a378882fFA531141205e92E9
Fee Recipient: 0xe269E891A2Ec8585a378882fFA531141205e92E9
Maker Token: 0xD7732e3783b0047aa251928960063f863AD022D8
Taker Token: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
Filled Maker Token Amount: 240000000000000000000000
Filled Taker Token Amount: 6930282000000000000
Paid Maker Fee: 0
Paid Taker Fee: 0
Tokens: 0xf08499c9e419ea8c08c4b991f88632593fb36baf4124c62758acb21898711088
Order Hash: 0x306a9a7ecbd9446559a2c650b4cfc16d1fb615aa2b3f4f63078da6d021268440


Log Block Number: 6383482
Log Index: 38
Log Name: LogFill
Maker: 0x04aa059b2e31B5898fAB5aB24761e67E8a196AB8
Taker: 0xe269E891A2Ec8585a378882fFA531141205e92E9
Fee Recipient: 0xe269E891A2Ec8585a378882fFA531141205e92E9
Maker Token: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
Taker Token: 0xD7732e3783b0047aa251928960063f863AD022D8
Filled Maker Token Amount: 6941718000000000000
Filled Taker Token Amount: 240000000000000000000000
Paid Maker Fee: 0
Paid Taker Fee: 0
Tokens: 0x97ef123f2b566f36ab1e6f5d462a8079fbe34fa667b4eae67194b3f9cce60f2a
Order Hash: 0xac270e88ce27b6bb78ee5b68ebaef666a77195020a6ab8922834f07bc9e0d524


Log Block Number: 6383488
Log Index: 43
Log Name: LogCancel
Maker: 0x0004E79C978B95974dCa16F56B516bE0c50CC652
Fee Recipient: 0xA258b39954ceF5cB142fd567A46cDdB31a670124
Maker Token: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
Taker Token: 0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359
Cancelled Maker Token Amount: 30000000000000000000
Cancelled Taker Token Amount: 7274848425000000000000
Tokens: 0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391
Order Hash: 0xe43eff38dc27af046bfbd431926926c072bbc7a509d56f6f1a7ae1f5ad7efe4f

將解析后的日志輸出與etherscan上的內容進行比較:https://etherscan.io/tx/0xb73a4492c5db1f67930b25ce3869c1e6b9bdbccb239a23b6454925a5bc0e03c5

完整代碼

命令

solc --abi Exchange.sol
abigen --abi="Exchange.sol:Exchange.abi" --pkg=exchange --out=Exchange.go

Exchange.sol

pragma solidity 0.4.11;

contract Exchange {
    event LogFill(
        address indexed maker,
        address taker,
        address indexed feeRecipient,
        address makerToken,
        address takerToken,
        uint filledMakerTokenAmount,
        uint filledTakerTokenAmount,
        uint paidMakerFee,
        uint paidTakerFee,
        bytes32 indexed tokens, // keccak256(makerToken, takerToken), allows subscribing to a token pair
        bytes32 orderHash
    );

    event LogCancel(
        address indexed maker,
        address indexed feeRecipient,
        address makerToken,
        address takerToken,
        uint cancelledMakerTokenAmount,
        uint cancelledTakerTokenAmount,
        bytes32 indexed tokens,
        bytes32 orderHash
    );

    event LogError(uint8 indexed errorId, bytes32 indexed orderHash);
}

event_read_0xprotocol.go

package main

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

    exchange "./contracts_0xprotocol" // for demo
    "github.com/ethereum/go-ethereum"
    "github.com/ethereum/go-ethereum/accounts/abi"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/common/hexutil"
    "github.com/ethereum/go-ethereum/ethclient"
)

// LogFill ...
type LogFill struct {
    Maker                  common.Address
    Taker                  common.Address
    FeeRecipient           common.Address
    MakerToken             common.Address
    TakerToken             common.Address
    FilledMakerTokenAmount *big.Int
    FilledTakerTokenAmount *big.Int
    PaidMakerFee           *big.Int
    PaidTakerFee           *big.Int
    Tokens                 [32]byte
    OrderHash              [32]byte
}

// LogCancel ...
type LogCancel struct {
    Maker                     common.Address
    FeeRecipient              common.Address
    MakerToken                common.Address
    TakerToken                common.Address
    CancelledMakerTokenAmount *big.Int
    CancelledTakerTokenAmount *big.Int
    Tokens                    [32]byte
    OrderHash                 [32]byte
}

// LogError ...
type LogError struct {
    ErrorID   uint8
    OrderHash [32]byte
}

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

    // 0x Protocol Exchange smart contract address
    contractAddress := common.HexToAddress("0x12459C951127e0c374FF9105DdA097662A027093")
    query := ethereum.FilterQuery{
        FromBlock: big.NewInt(6383482),
        ToBlock:   big.NewInt(6383488),
        Addresses: []common.Address{
            contractAddress,
        },
    }

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

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

    // NOTE: keccak256("LogFill(address,address,address,address,address,uint256,uint256,uint256,uint256,bytes32,bytes32)")
    logFillEvent := common.HexToHash("0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3")

    // NOTE: keccak256("LogCancel(address,address,address,address,uint256,uint256,bytes32,bytes32)")
    logCancelEvent := common.HexToHash("67d66f160bc93d925d05dae1794c90d2d6d6688b29b84ff069398a9b04587131")

    // NOTE: keccak256("LogError(uint8,bytes32)")
    logErrorEvent := common.HexToHash("36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e90")

    for _, vLog := range logs {
        fmt.Printf("Log Block Number: %d\n", vLog.BlockNumber)
        fmt.Printf("Log Index: %d\n", vLog.Index)

        switch vLog.Topics[0].Hex() {
        case logFillEvent.Hex():
            fmt.Printf("Log Name: LogFill\n")

            var fillEvent LogFill

            err := contractAbi.Unpack(&fillEvent, "LogFill", vLog.Data)
            if err != nil {
                log.Fatal(err)
            }

            fillEvent.Maker = common.HexToAddress(vLog.Topics[1].Hex())
            fillEvent.FeeRecipient = common.HexToAddress(vLog.Topics[2].Hex())
            fillEvent.Tokens = vLog.Topics[3]

            fmt.Printf("Maker: %s\n", fillEvent.Maker.Hex())
            fmt.Printf("Taker: %s\n", fillEvent.Taker.Hex())
            fmt.Printf("Fee Recipient: %s\n", fillEvent.FeeRecipient.Hex())
            fmt.Printf("Maker Token: %s\n", fillEvent.MakerToken.Hex())
            fmt.Printf("Taker Token: %s\n", fillEvent.TakerToken.Hex())
            fmt.Printf("Filled Maker Token Amount: %s\n", fillEvent.FilledMakerTokenAmount.String())
            fmt.Printf("Filled Taker Token Amount: %s\n", fillEvent.FilledTakerTokenAmount.String())
            fmt.Printf("Paid Maker Fee: %s\n", fillEvent.PaidMakerFee.String())
            fmt.Printf("Paid Taker Fee: %s\n", fillEvent.PaidTakerFee.String())
            fmt.Printf("Tokens: %s\n", hexutil.Encode(fillEvent.Tokens[:]))
            fmt.Printf("Order Hash: %s\n", hexutil.Encode(fillEvent.OrderHash[:]))

        case logCancelEvent.Hex():
            fmt.Printf("Log Name: LogCancel\n")

            var cancelEvent LogCancel

            err := contractAbi.Unpack(&cancelEvent, "LogCancel", vLog.Data)
            if err != nil {
                log.Fatal(err)
            }

            cancelEvent.Maker = common.HexToAddress(vLog.Topics[1].Hex())
            cancelEvent.FeeRecipient = common.HexToAddress(vLog.Topics[2].Hex())
            cancelEvent.Tokens = vLog.Topics[3]

            fmt.Printf("Maker: %s\n", cancelEvent.Maker.Hex())
            fmt.Printf("Fee Recipient: %s\n", cancelEvent.FeeRecipient.Hex())
            fmt.Printf("Maker Token: %s\n", cancelEvent.MakerToken.Hex())
            fmt.Printf("Taker Token: %s\n", cancelEvent.TakerToken.Hex())
            fmt.Printf("Cancelled Maker Token Amount: %s\n", cancelEvent.CancelledMakerTokenAmount.String())
            fmt.Printf("Cancelled Taker Token Amount: %s\n", cancelEvent.CancelledTakerTokenAmount.String())
            fmt.Printf("Tokens: %s\n", hexutil.Encode(cancelEvent.Tokens[:]))
            fmt.Printf("Order Hash: %s\n", hexutil.Encode(cancelEvent.OrderHash[:]))

        case logErrorEvent.Hex():
            fmt.Printf("Log Name: LogError\n")

            errorID, err := strconv.ParseInt(vLog.Topics[1].Hex(), 16, 64)
            if err != nil {
                log.Fatal(err)
            }

            errorEvent := &LogError{
                ErrorID:   uint8(errorID),
                OrderHash: vLog.Topics[2],
            }

            fmt.Printf("Error ID: %d\n", errorEvent.ErrorID)
            fmt.Printf("Order Hash: %s\n", hexutil.Encode(errorEvent.OrderHash[:]))
        }

        fmt.Printf("\n\n")
    }
}

這些示例使用的solc版本

$ solc --version
0.4.11+commit.68ef5810.Emscripten.clang

添加小編微信:grey0805,加入知識學習小分隊~!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容