Nova Kwok's Awesome Blog

用 Go Ethereum 在链上直接读取 ERC20 智能合约信息

我也不知道这个有什么用,只是感觉做起来比较好玩。

在看 Görli Testnet 的 Etherscan 的时候,比如这个链接: https://goerli.etherscan.io/token/0x3ffc03f05d1869f493c7dbf913e636c6280e0ff9#readContract ,可以直接看到一个合约的一些基本信息,比如 Decimals,Max Total Supply 之类的

于是好奇有没有什么合理的办法直接从链上读到这些数据(而不是基于别人的 API 套娃整一个新的 API 出来)。

Why

其实网上有一些类似的文章(可以参考本文底部的 References),但是有一些问题,比如大家的文章的 solidity 都是一个非常上古的版本:

pragma solidity ^0.4.24;

而且文章中要查询 ERC20 的合约都是自己写了一个 interface,但是后来我发现对于这种 Public Interface 完全可以使用 OpenZeppelin 写好的, 比如 ERC20 的在 https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol 这里。

Get Started

为了解决这个问题,我们需要以下工具:

  1. 一个 Eth 的节点(或者一个 Ethereum Gateway 也可以,比如 Infura 或者 Cloudflare 的免费服务)
  2. Go Ethernum 包(包含 abigen 啥的)
  3. Solidity compiler(比如 solc-js 或者 solc)
  4. nodejs (这个我相信大家电脑上都有,如果没有的话可以去 nodesource 整一个)

Prepare Env

Eth Node

对于 Eth 节点我们直接用公共服务就好,或者如果实在想自建一个的话可以直接用下方 docker-compose.yml 快速启动一个:

version: "3"
services:
  geth:
    image: ethereum/client-go:latest
    restart: unless-stopped
    ports:
      - 30303:30303
      - 30303:30303/udp
      - 127.0.0.1:8545:8545
      - 127.0.0.1:8546:8546
      - 127.0.0.1:8551:8551
    volumes:
      - ./data:/root/.ethereum
    healthcheck:
      test: [ "CMD-SHELL", "geth attach --exec eth.blockNumber" ]
      interval: 10s
      timeout: 5s
      retries: 5
    command:
      - --http
      - --goerli
      - --cache=8192
      - --http.api=eth,net,web3,engine,admin
      - --http.addr=0.0.0.0
      - --http.vhosts=*
      - --http.corsdomain=*
      - --maxpeers=200
      - --ws
      - --ws.origins=*
      - --ws.addr=0.0.0.0
      - --ws.api=eth,net,web3
      - --graphql
      - --graphql.corsdomain=*
      - --graphql.vhosts=*
      - --authrpc.addr=0.0.0.0
      - --authrpc.jwtsecret=/root/.ethereum/jwt.hex
      - --authrpc.vhosts=*
      - --authrpc.port=8551
      - --txlookuplimit=0

  prysm:
    image: gcr.io/prysmaticlabs/prysm/beacon-chain
    pull_policy: always
    container_name: beacon
    restart: unless-stopped
    stop_grace_period: 2m
    volumes:
      - ./prysm_data:/data
      - ./data:/geth
    depends_on:
      geth:
        condition: service_healthy
    ports:
      - 127.0.0.1:4000:4000
      - 127.0.0.1:3500:3500
    command:
      - --accept-terms-of-use
      - --datadir=/data
      - --disable-monitoring
      - --rpc-host=0.0.0.0
      - --execution-endpoint=http://geth:8551
      - --jwt-secret=/geth/jwt.hex
      - --rpc-host=0.0.0.0
      - --rpc-port=4000
      - --grpc-gateway-corsdomain=*
      - --grpc-gateway-host=0.0.0.0
      - --grpc-gateway-port=3500

Go Ethernum

在我所用的系统上似乎没有合适的包,既然都是一堆 Binary,不如直接下载然后解压到 /usr/bin 下:

wget https://gethstore.blob.core.windows.net/builds/geth-alltools-linux-amd64-1.10.18-de23cf91.tar.gz
tar -xvf geth-alltools-linux-amd64-1.10.18-de23cf91.tar.gz
cd geth-alltools-linux-amd64-1.10.18-de23cf91
sudo mv abigen /usr/bin/
sudo mv geth /usr/bin

Solc

这里一开始我跟着某个教程 npm install solc -g,然后发现…

The commandline options of solcjs are not compatible with solc and tools (such as geth) expecting the behaviour of solc will not work with solcjs.

让我非常冒火,于是本着能找 Binary 就不要给自己找事情的原则,去找了 Binary 然后解压到 /usr/bin 下:

wget https://github.com/ethereum/solidity/releases/download/v0.8.14/solc-static-linux
chmod +x solc-static-linux
mv solc-static-linux /usr/bin/solc

Gen ABI

由于我们打算用 Go 来写一整套东西,所以需要用 Go Ethernum 来读取,而要让 Go Ethernum 能读取合约相关的信息,需要对应的 ABI,大概流程类似如下:

Solidity(ERC20.sol) –(solc)–> ABI(ERC20.abi) –(abigen)–> Go Package(erc20.go)

这里 ERC20.sol 我们就不像网上一些文章一样手搓了,而是直接使用 OpenZeppelin 提供的,关于 OpenZeppelin 的一点介绍如下:

A library for secure smart contract development. Build on a solid foundation of community-vetted code.

https://docs.openzeppelin.com/contracts/4.x/

要使用他们已经写好的合约信息,找一个空白目录,然后直接 npm install @openzeppelin/contracts 即可在本地 node_modules 下拿到 OpenZeppelin 的所有合约,然后我们在 node_modules/@openzeppelin/contracts/token/ERC20 下可以看到我们想要的 ERC20.sol 文件,这个时候我们构建 abi

solc --abi /path/to/node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol --base-path /path/to/node_modules --output-dir /path/to/

这时你可以在当前目录下找到一个 ERC20.abi 文件,然后我们用 abigen 拿到对应的 erc20.go

abigen --abi=ERC20.abi --pkg=token --out=erc20.go

Read Contract

有了上面拿到的 erc20.go 文件之后,我们在同目录下搞个 read.go 用来驱动,可以这么写:

package main

import (
	"fmt"
	"log"

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

func main() {
	client, err := ethclient.Dial("https://goerli.knat.network")
	if err != nil {
		log.Fatal(err)
	}
	contract_address := common.HexToAddress("0x3ffc03f05d1869f493c7dbf913e636c6280e0ff9")

	// use erc20.go to init instance
	contract, err := NewToken(contract_address, client)
	decimals, err := contract.Decimals(nil)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("decimals:", decimals)
	symbol, err := contract.Symbol(&bind.CallOpts{})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("symbol:", symbol) 
	total_supply, err := contract.TotalSupply(nil)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("total_supply:", total_supply)
}

然后就可以拿到我们要的信息了~

go run .
decimals: 18
symbol: TEST
total_supply: 1000000000000000000000000000000001300115100103598665898811424167730937505693

🤔 接下来就是找个 DB 写爆咯?

References

  1. 查询ERC20代币智能合约
  2. 智能合约 - 查询ERC20代币智能合约 - 《用Go来做以太坊开发》 - 书栈网 · BookStack
  3. ERC20 代币作为 Hyperledger Fabric Golang 链码

#Chinese #Ethereum