概述: 用Go读取0x Protocol智能合约事件日志的教程

读取0x Protocol事件日志

要读取0x Protocol事件日志,我们必须首先将solidity智能合约编译为一个Go包。

安装solc版本0.4.11

  1. 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:

  1. pragma solidity 0.4.11;
  2. contract Exchange {
  3. event LogFill(
  4. address indexed maker,
  5. address taker,
  6. address indexed feeRecipient,
  7. address makerToken,
  8. address takerToken,
  9. uint filledMakerTokenAmount,
  10. uint filledTakerTokenAmount,
  11. uint paidMakerFee,
  12. uint paidTakerFee,
  13. bytes32 indexed tokens, // keccak256(makerToken, takerToken), allows subscribing to a token pair
  14. bytes32 orderHash
  15. );
  16. event LogCancel(
  17. address indexed maker,
  18. address indexed feeRecipient,
  19. address makerToken,
  20. address takerToken,
  21. uint cancelledMakerTokenAmount,
  22. uint cancelledTakerTokenAmount,
  23. bytes32 indexed tokens,
  24. bytes32 orderHash
  25. );
  26. event LogError(uint8 indexed errorId, bytes32 indexed orderHash);
  27. }

接着给定abi,使用abigen来创建Goexchange包:

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

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

现在在我们的Go应用程序中,让我们创建与0xProtocol事件日志签名类型匹配的结构体类型:

  1. type LogFill struct {
  2. Maker common.Address
  3. Taker common.Address
  4. FeeRecipient common.Address
  5. MakerToken common.Address
  6. TakerToken common.Address
  7. FilledMakerTokenAmount *big.Int
  8. FilledTakerTokenAmount *big.Int
  9. PaidMakerFee *big.Int
  10. PaidTakerFee *big.Int
  11. Tokens [32]byte
  12. OrderHash [32]byte
  13. }
  14. type LogCancel struct {
  15. Maker common.Address
  16. FeeRecipient common.Address
  17. MakerToken common.Address
  18. TakerToken common.Address
  19. CancelledMakerTokenAmount *big.Int
  20. CancelledTakerTokenAmount *big.Int
  21. Tokens [32]byte
  22. OrderHash [32]byte
  23. }
  24. type LogError struct {
  25. ErrorID uint8
  26. OrderHash [32]byte
  27. }

初始化以太坊客户端:

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

创建一个FilterQuery,并为其传递0x Protocol智能合约地址和所需的区块范围:

  1. // 0x Protocol Exchange smart contract address
  2. contractAddress := common.HexToAddress("0x12459C951127e0c374FF9105DdA097662A027093")
  3. query := ethereum.FilterQuery{
  4. FromBlock: big.NewInt(6383482),
  5. ToBlock: big.NewInt(6383488),
  6. Addresses: []common.Address{
  7. contractAddress,
  8. },
  9. }

FilterLogs查询日志:

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

接下来我们将解析JSON abi,我们后续将使用解压缩原始日志数据:

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

为了按某种日志类型过滤,我们需要知晓每个事件日志函数签名的keccak256摘要。正如我们很快所见到的那样,事件日志函数签名摘要总是topic[0]

  1. // NOTE: keccak256("LogFill(address,address,address,address,address,uint256,uint256,uint256,uint256,bytes32,bytes32)")
  2. logFillEvent := common.HexToHash("0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3")
  3. // NOTE: keccak256("LogCancel(address,address,address,address,uint256,uint256,bytes32,bytes32)")
  4. logCancelEvent := common.HexToHash("67d66f160bc93d925d05dae1794c90d2d6d6688b29b84ff069398a9b04587131")
  5. // NOTE: keccak256("LogError(uint8,bytes32)")
  6. logErrorEvent := common.HexToHash("36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e90")

现在我们迭代所有的日志并设置一个switch语句来按事件日志类型过滤:

  1. for _, vLog := range logs {
  2. fmt.Printf("Log Block Number: %d\n", vLog.BlockNumber)
  3. fmt.Printf("Log Index: %d\n", vLog.Index)
  4. switch vLog.Topics[0].Hex() {
  5. case logFillEvent.Hex():
  6. //
  7. case logCancelEvent.Hex():
  8. //
  9. case logErrorEvent.Hex():
  10. //
  11. }
  12. }

现在要解析LogFill,我们将使用abi.Unpack将原始数据类型解析为我们自定义的日志类型结构体。Unpack不会解析indexed事件类型,因为这些它们存储在topics下,所以对于那些我们必须单独解析,如下例所示:

  1. fmt.Printf("Log Name: LogFill\n")
  2. var fillEvent LogFill
  3. err := contractAbi.Unpack(&fillEvent, "LogFill", vLog.Data)
  4. if err != nil {
  5. log.Fatal(err)
  6. }
  7. fillEvent.Maker = common.HexToAddress(vLog.Topics[1].Hex())
  8. fillEvent.FeeRecipient = common.HexToAddress(vLog.Topics[2].Hex())
  9. fillEvent.Tokens = vLog.Topics[3]
  10. fmt.Printf("Maker: %s\n", fillEvent.Maker.Hex())
  11. fmt.Printf("Taker: %s\n", fillEvent.Taker.Hex())
  12. fmt.Printf("Fee Recipient: %s\n", fillEvent.FeeRecipient.Hex())
  13. fmt.Printf("Maker Token: %s\n", fillEvent.MakerToken.Hex())
  14. fmt.Printf("Taker Token: %s\n", fillEvent.TakerToken.Hex())
  15. fmt.Printf("Filled Maker Token Amount: %s\n", fillEvent.FilledMakerTokenAmount.String())
  16. fmt.Printf("Filled Taker Token Amount: %s\n", fillEvent.FilledTakerTokenAmount.String())
  17. fmt.Printf("Paid Maker Fee: %s\n", fillEvent.PaidMakerFee.String())
  18. fmt.Printf("Paid Taker Fee: %s\n", fillEvent.PaidTakerFee.String())
  19. fmt.Printf("Tokens: %s\n", hexutil.Encode(fillEvent.Tokens[:]))
  20. fmt.Printf("Order Hash: %s\n", hexutil.Encode(fillEvent.OrderHash[:]))

对于LogCancel类似:

  1. fmt.Printf("Log Name: LogCancel\n")
  2. var cancelEvent LogCancel
  3. err := contractAbi.Unpack(&cancelEvent, "LogCancel", vLog.Data)
  4. if err != nil {
  5. log.Fatal(err)
  6. }
  7. cancelEvent.Maker = common.HexToAddress(vLog.Topics[1].Hex())
  8. cancelEvent.FeeRecipient = common.HexToAddress(vLog.Topics[2].Hex())
  9. cancelEvent.Tokens = vLog.Topics[3]
  10. fmt.Printf("Maker: %s\n", cancelEvent.Maker.Hex())
  11. fmt.Printf("Fee Recipient: %s\n", cancelEvent.FeeRecipient.Hex())
  12. fmt.Printf("Maker Token: %s\n", cancelEvent.MakerToken.Hex())
  13. fmt.Printf("Taker Token: %s\n", cancelEvent.TakerToken.Hex())
  14. fmt.Printf("Cancelled Maker Token Amount: %s\n", cancelEvent.CancelledMakerTokenAmount.String())
  15. fmt.Printf("Cancelled Taker Token Amount: %s\n", cancelEvent.CancelledTakerTokenAmount.String())
  16. fmt.Printf("Tokens: %s\n", hexutil.Encode(cancelEvent.Tokens[:]))
  17. fmt.Printf("Order Hash: %s\n", hexutil.Encode(cancelEvent.OrderHash[:]))

最后是LogError

  1. fmt.Printf("Log Name: LogError\n")
  2. errorID, err := strconv.ParseInt(vLog.Topics[1].Hex(), 16, 64)
  3. if err != nil {
  4. log.Fatal(err)
  5. }
  6. errorEvent := &LogError{
  7. ErrorID: uint8(errorID),
  8. OrderHash: vLog.Topics[2],
  9. }
  10. fmt.Printf("Error ID: %d\n", errorEvent.ErrorID)
  11. fmt.Printf("Order Hash: %s\n", hexutil.Encode(errorEvent.OrderHash[:]))

将它们放在一起并运行我们将看到以下输出:

  1. Log Block Number: 6383482
  2. Log Index: 35
  3. Log Name: LogFill
  4. Maker: 0x8dd688660ec0BaBD0B8a2f2DE3232645F73cC5eb
  5. Taker: 0xe269E891A2Ec8585a378882fFA531141205e92E9
  6. Fee Recipient: 0xe269E891A2Ec8585a378882fFA531141205e92E9
  7. Maker Token: 0xD7732e3783b0047aa251928960063f863AD022D8
  8. Taker Token: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
  9. Filled Maker Token Amount: 240000000000000000000000
  10. Filled Taker Token Amount: 6930282000000000000
  11. Paid Maker Fee: 0
  12. Paid Taker Fee: 0
  13. Tokens: 0xf08499c9e419ea8c08c4b991f88632593fb36baf4124c62758acb21898711088
  14. Order Hash: 0x306a9a7ecbd9446559a2c650b4cfc16d1fb615aa2b3f4f63078da6d021268440
  15. Log Block Number: 6383482
  16. Log Index: 38
  17. Log Name: LogFill
  18. Maker: 0x04aa059b2e31B5898fAB5aB24761e67E8a196AB8
  19. Taker: 0xe269E891A2Ec8585a378882fFA531141205e92E9
  20. Fee Recipient: 0xe269E891A2Ec8585a378882fFA531141205e92E9
  21. Maker Token: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
  22. Taker Token: 0xD7732e3783b0047aa251928960063f863AD022D8
  23. Filled Maker Token Amount: 6941718000000000000
  24. Filled Taker Token Amount: 240000000000000000000000
  25. Paid Maker Fee: 0
  26. Paid Taker Fee: 0
  27. Tokens: 0x97ef123f2b566f36ab1e6f5d462a8079fbe34fa667b4eae67194b3f9cce60f2a
  28. Order Hash: 0xac270e88ce27b6bb78ee5b68ebaef666a77195020a6ab8922834f07bc9e0d524
  29. Log Block Number: 6383488
  30. Log Index: 43
  31. Log Name: LogCancel
  32. Maker: 0x0004E79C978B95974dCa16F56B516bE0c50CC652
  33. Fee Recipient: 0xA258b39954ceF5cB142fd567A46cDdB31a670124
  34. Maker Token: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
  35. Taker Token: 0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359
  36. Cancelled Maker Token Amount: 30000000000000000000
  37. Cancelled Taker Token Amount: 7274848425000000000000
  38. Tokens: 0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391
  39. Order Hash: 0xe43eff38dc27af046bfbd431926926c072bbc7a509d56f6f1a7ae1f5ad7efe4f

将解析后的日志输出与etherscan上的内容进行比较:https://etherscan.io/tx/0xb73a4492c5db1f67930b25ce3869c1e6b9bdbccb239a23b6454925a5bc0e03c5


完整代码

命令

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

Exchange.sol

  1. pragma solidity 0.4.11;
  2. contract Exchange {
  3. event LogFill(
  4. address indexed maker,
  5. address taker,
  6. address indexed feeRecipient,
  7. address makerToken,
  8. address takerToken,
  9. uint filledMakerTokenAmount,
  10. uint filledTakerTokenAmount,
  11. uint paidMakerFee,
  12. uint paidTakerFee,
  13. bytes32 indexed tokens, // keccak256(makerToken, takerToken), allows subscribing to a token pair
  14. bytes32 orderHash
  15. );
  16. event LogCancel(
  17. address indexed maker,
  18. address indexed feeRecipient,
  19. address makerToken,
  20. address takerToken,
  21. uint cancelledMakerTokenAmount,
  22. uint cancelledTakerTokenAmount,
  23. bytes32 indexed tokens,
  24. bytes32 orderHash
  25. );
  26. event LogError(uint8 indexed errorId, bytes32 indexed orderHash);
  27. }

event_read_0xprotocol.go

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "log"
  6. "math/big"
  7. "strconv"
  8. "strings"
  9. exchange "./contracts_0xprotocol" // for demo
  10. "github.com/ethereum/go-ethereum"
  11. "github.com/ethereum/go-ethereum/accounts/abi"
  12. "github.com/ethereum/go-ethereum/common"
  13. "github.com/ethereum/go-ethereum/common/hexutil"
  14. "github.com/ethereum/go-ethereum/ethclient"
  15. )
  16. // LogFill ...
  17. type LogFill struct {
  18. Maker common.Address
  19. Taker common.Address
  20. FeeRecipient common.Address
  21. MakerToken common.Address
  22. TakerToken common.Address
  23. FilledMakerTokenAmount *big.Int
  24. FilledTakerTokenAmount *big.Int
  25. PaidMakerFee *big.Int
  26. PaidTakerFee *big.Int
  27. Tokens [32]byte
  28. OrderHash [32]byte
  29. }
  30. // LogCancel ...
  31. type LogCancel struct {
  32. Maker common.Address
  33. FeeRecipient common.Address
  34. MakerToken common.Address
  35. TakerToken common.Address
  36. CancelledMakerTokenAmount *big.Int
  37. CancelledTakerTokenAmount *big.Int
  38. Tokens [32]byte
  39. OrderHash [32]byte
  40. }
  41. // LogError ...
  42. type LogError struct {
  43. ErrorID uint8
  44. OrderHash [32]byte
  45. }
  46. func main() {
  47. client, err := ethclient.Dial("https://mainnet.infura.io")
  48. if err != nil {
  49. log.Fatal(err)
  50. }
  51. // 0x Protocol Exchange smart contract address
  52. contractAddress := common.HexToAddress("0x12459C951127e0c374FF9105DdA097662A027093")
  53. query := ethereum.FilterQuery{
  54. FromBlock: big.NewInt(6383482),
  55. ToBlock: big.NewInt(6383488),
  56. Addresses: []common.Address{
  57. contractAddress,
  58. },
  59. }
  60. logs, err := client.FilterLogs(context.Background(), query)
  61. if err != nil {
  62. log.Fatal(err)
  63. }
  64. contractAbi, err := abi.JSON(strings.NewReader(string(exchange.ExchangeABI)))
  65. if err != nil {
  66. log.Fatal(err)
  67. }
  68. // NOTE: keccak256("LogFill(address,address,address,address,address,uint256,uint256,uint256,uint256,bytes32,bytes32)")
  69. logFillEvent := common.HexToHash("0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3")
  70. // NOTE: keccak256("LogCancel(address,address,address,address,uint256,uint256,bytes32,bytes32)")
  71. logCancelEvent := common.HexToHash("67d66f160bc93d925d05dae1794c90d2d6d6688b29b84ff069398a9b04587131")
  72. // NOTE: keccak256("LogError(uint8,bytes32)")
  73. logErrorEvent := common.HexToHash("36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e90")
  74. for _, vLog := range logs {
  75. fmt.Printf("Log Block Number: %d\n", vLog.BlockNumber)
  76. fmt.Printf("Log Index: %d\n", vLog.Index)
  77. switch vLog.Topics[0].Hex() {
  78. case logFillEvent.Hex():
  79. fmt.Printf("Log Name: LogFill\n")
  80. var fillEvent LogFill
  81. err := contractAbi.Unpack(&fillEvent, "LogFill", vLog.Data)
  82. if err != nil {
  83. log.Fatal(err)
  84. }
  85. fillEvent.Maker = common.HexToAddress(vLog.Topics[1].Hex())
  86. fillEvent.FeeRecipient = common.HexToAddress(vLog.Topics[2].Hex())
  87. fillEvent.Tokens = vLog.Topics[3]
  88. fmt.Printf("Maker: %s\n", fillEvent.Maker.Hex())
  89. fmt.Printf("Taker: %s\n", fillEvent.Taker.Hex())
  90. fmt.Printf("Fee Recipient: %s\n", fillEvent.FeeRecipient.Hex())
  91. fmt.Printf("Maker Token: %s\n", fillEvent.MakerToken.Hex())
  92. fmt.Printf("Taker Token: %s\n", fillEvent.TakerToken.Hex())
  93. fmt.Printf("Filled Maker Token Amount: %s\n", fillEvent.FilledMakerTokenAmount.String())
  94. fmt.Printf("Filled Taker Token Amount: %s\n", fillEvent.FilledTakerTokenAmount.String())
  95. fmt.Printf("Paid Maker Fee: %s\n", fillEvent.PaidMakerFee.String())
  96. fmt.Printf("Paid Taker Fee: %s\n", fillEvent.PaidTakerFee.String())
  97. fmt.Printf("Tokens: %s\n", hexutil.Encode(fillEvent.Tokens[:]))
  98. fmt.Printf("Order Hash: %s\n", hexutil.Encode(fillEvent.OrderHash[:]))
  99. case logCancelEvent.Hex():
  100. fmt.Printf("Log Name: LogCancel\n")
  101. var cancelEvent LogCancel
  102. err := contractAbi.Unpack(&cancelEvent, "LogCancel", vLog.Data)
  103. if err != nil {
  104. log.Fatal(err)
  105. }
  106. cancelEvent.Maker = common.HexToAddress(vLog.Topics[1].Hex())
  107. cancelEvent.FeeRecipient = common.HexToAddress(vLog.Topics[2].Hex())
  108. cancelEvent.Tokens = vLog.Topics[3]
  109. fmt.Printf("Maker: %s\n", cancelEvent.Maker.Hex())
  110. fmt.Printf("Fee Recipient: %s\n", cancelEvent.FeeRecipient.Hex())
  111. fmt.Printf("Maker Token: %s\n", cancelEvent.MakerToken.Hex())
  112. fmt.Printf("Taker Token: %s\n", cancelEvent.TakerToken.Hex())
  113. fmt.Printf("Cancelled Maker Token Amount: %s\n", cancelEvent.CancelledMakerTokenAmount.String())
  114. fmt.Printf("Cancelled Taker Token Amount: %s\n", cancelEvent.CancelledTakerTokenAmount.String())
  115. fmt.Printf("Tokens: %s\n", hexutil.Encode(cancelEvent.Tokens[:]))
  116. fmt.Printf("Order Hash: %s\n", hexutil.Encode(cancelEvent.OrderHash[:]))
  117. case logErrorEvent.Hex():
  118. fmt.Printf("Log Name: LogError\n")
  119. errorID, err := strconv.ParseInt(vLog.Topics[1].Hex(), 16, 64)
  120. if err != nil {
  121. log.Fatal(err)
  122. }
  123. errorEvent := &LogError{
  124. ErrorID: uint8(errorID),
  125. OrderHash: vLog.Topics[2],
  126. }
  127. fmt.Printf("Error ID: %d\n", errorEvent.ErrorID)
  128. fmt.Printf("Order Hash: %s\n", hexutil.Encode(errorEvent.OrderHash[:]))
  129. }
  130. fmt.Printf("\n\n")
  131. }
  132. }

这些示例使用的solc版本

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