mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 15:47:21 +00:00
This PR implements a new version of the abigen utility (v2) which exists
along with the pre-existing v1 version.
Abigen is a utility command provided by go-ethereum that, given a
solidity contract ABI definition, will generate Go code to transact/call
the contract methods, converting the method parameters/results and
structures defined in the contract into corresponding Go types. This is
useful for preventing the need to write custom boilerplate code for
contract interactions.
Methods in the generated bindings perform encoding between Go types and
Solidity ABI-encoded packed bytecode, as well as some action (e.g.
`eth_call` or creating and submitting a transaction). This limits the
flexibility of how the generated bindings can be used, and prevents
easily adding new functionality, as it will make the generated bindings
larger for each feature added.
Abigen v2 was conceived of by the observation that the only
functionality that generated Go bindings ought to perform is conversion
between Go types and ABI-encoded packed data. Go-ethereum already
provides various APIs which in conjunction with conversion methods
generated in v2 bindings can cover all functionality currently provided
by v1, and facilitate all other previously-desired use-cases.
## Generating Bindings
To generate contract bindings using abigen v2, invoke the `abigen`
command with the `--v2` flag. The functionality of all other flags is
preserved between the v2 and v1 versions.
## What is Generated in the Bindings
The execution of `abigen --v2` generates Go code containing methods
which convert between Go types and corresponding ABI-encoded data
expected by the contract. For each input-accepting contract method and
the constructor, a "packing" method is generated in the binding which
converts from Go types to the corresponding packed solidity expected by
the contract. If a method returns output, an "unpacking" method is
generated to convert this output from ABI-encoded data to the
corresponding Go types.
For contracts which emit events, an unpacking method is defined for each
event to unpack the corresponding raw log to the Go type that it
represents.
Likewise, where custom errors are defined by contracts, an unpack method
is generated to unpack raw error data into a Go type.
## Using the Generated Bindings
For a smooth user-experience, abigen v2 comes with a number of utility
functions to be used in conjunction with the generated bindings for
performing common contract interaction use-cases. These include:
* filtering for historical logs of a given topic
* watching the chain for emission of logs with a given topic
* contract deployment methods
* Call/Transact methods
https://geth.ethereum.org will be updated to include a new tutorial page
for abigen v2 with full code examples. The page currently exists in a
PR: https://github.com/ethereum/go-ethereum/pull/31390 .
There are also extensive examples of interactions with contract bindings
in [test
cases](cc855c7ede/accounts/abi/bind/v2/lib_test.go)
provided with this PR.
---------
Co-authored-by: Sina Mahmoodi <itz.s1na@gmail.com>
Co-authored-by: Felix Lange <fjl@twurst.com>
244 lines
7.1 KiB
Go
244 lines
7.1 KiB
Go
// Copyright 2016 The go-ethereum Authors
|
|
// This file is part of go-ethereum.
|
|
//
|
|
// go-ethereum is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// go-ethereum is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi/abigen"
|
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
|
"github.com/ethereum/go-ethereum/common/compiler"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/internal/flags"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
var (
|
|
// Flags needed by abigen
|
|
abiFlag = &cli.StringFlag{
|
|
Name: "abi",
|
|
Usage: "Path to the Ethereum contract ABI json to bind, - for STDIN",
|
|
}
|
|
binFlag = &cli.StringFlag{
|
|
Name: "bin",
|
|
Usage: "Path to the Ethereum contract bytecode (generate deploy method)",
|
|
}
|
|
typeFlag = &cli.StringFlag{
|
|
Name: "type",
|
|
Usage: "Struct name for the binding (default = package name)",
|
|
}
|
|
jsonFlag = &cli.StringFlag{
|
|
Name: "combined-json",
|
|
Usage: "Path to the combined-json file generated by compiler, - for STDIN",
|
|
}
|
|
excFlag = &cli.StringFlag{
|
|
Name: "exc",
|
|
Usage: "Comma separated types to exclude from binding",
|
|
}
|
|
pkgFlag = &cli.StringFlag{
|
|
Name: "pkg",
|
|
Usage: "Package name to generate the binding into",
|
|
}
|
|
outFlag = &cli.StringFlag{
|
|
Name: "out",
|
|
Usage: "Output file for the generated binding (default = stdout)",
|
|
}
|
|
aliasFlag = &cli.StringFlag{
|
|
Name: "alias",
|
|
Usage: "Comma separated aliases for function and event renaming. If --v2 is set, errors are aliased as well. e.g. original1=alias1, original2=alias2",
|
|
}
|
|
v2Flag = &cli.BoolFlag{
|
|
Name: "v2",
|
|
Usage: "Generates v2 bindings",
|
|
}
|
|
)
|
|
|
|
var app = flags.NewApp("Ethereum ABI wrapper code generator")
|
|
|
|
func init() {
|
|
app.Name = "abigen"
|
|
app.Flags = []cli.Flag{
|
|
abiFlag,
|
|
binFlag,
|
|
typeFlag,
|
|
jsonFlag,
|
|
excFlag,
|
|
pkgFlag,
|
|
outFlag,
|
|
aliasFlag,
|
|
v2Flag,
|
|
}
|
|
app.Action = generate
|
|
}
|
|
|
|
func generate(c *cli.Context) error {
|
|
flags.CheckExclusive(c, abiFlag, jsonFlag) // Only one source can be selected.
|
|
|
|
if c.String(pkgFlag.Name) == "" {
|
|
utils.Fatalf("No destination package specified (--pkg)")
|
|
}
|
|
if c.String(abiFlag.Name) == "" && c.String(jsonFlag.Name) == "" {
|
|
utils.Fatalf("Either contract ABI source (--abi) or combined-json (--combined-json) are required")
|
|
}
|
|
// If the entire solidity code was specified, build and bind based on that
|
|
var (
|
|
abis []string
|
|
bins []string
|
|
types []string
|
|
sigs []map[string]string
|
|
libs = make(map[string]string)
|
|
aliases = make(map[string]string)
|
|
)
|
|
if c.String(abiFlag.Name) != "" {
|
|
// Load up the ABI, optional bytecode and type name from the parameters
|
|
var (
|
|
abi []byte
|
|
err error
|
|
)
|
|
input := c.String(abiFlag.Name)
|
|
if input == "-" {
|
|
abi, err = io.ReadAll(os.Stdin)
|
|
} else {
|
|
abi, err = os.ReadFile(input)
|
|
}
|
|
if err != nil {
|
|
utils.Fatalf("Failed to read input ABI: %v", err)
|
|
}
|
|
abis = append(abis, string(abi))
|
|
|
|
var bin []byte
|
|
if binFile := c.String(binFlag.Name); binFile != "" {
|
|
if bin, err = os.ReadFile(binFile); err != nil {
|
|
utils.Fatalf("Failed to read input bytecode: %v", err)
|
|
}
|
|
if strings.Contains(string(bin), "//") {
|
|
utils.Fatalf("Contract has additional library references, please use other mode(e.g. --combined-json) to catch library infos")
|
|
}
|
|
}
|
|
bins = append(bins, string(bin))
|
|
|
|
kind := c.String(typeFlag.Name)
|
|
if kind == "" {
|
|
kind = c.String(pkgFlag.Name)
|
|
}
|
|
types = append(types, kind)
|
|
} else {
|
|
// Generate the list of types to exclude from binding
|
|
var exclude *nameFilter
|
|
if c.IsSet(excFlag.Name) {
|
|
var err error
|
|
if exclude, err = newNameFilter(strings.Split(c.String(excFlag.Name), ",")...); err != nil {
|
|
utils.Fatalf("Failed to parse excludes: %v", err)
|
|
}
|
|
}
|
|
var contracts map[string]*compiler.Contract
|
|
|
|
if c.IsSet(jsonFlag.Name) {
|
|
var (
|
|
input = c.String(jsonFlag.Name)
|
|
jsonOutput []byte
|
|
err error
|
|
)
|
|
if input == "-" {
|
|
jsonOutput, err = io.ReadAll(os.Stdin)
|
|
} else {
|
|
jsonOutput, err = os.ReadFile(input)
|
|
}
|
|
if err != nil {
|
|
utils.Fatalf("Failed to read combined-json: %v", err)
|
|
}
|
|
contracts, err = compiler.ParseCombinedJSON(jsonOutput, "", "", "", "")
|
|
if err != nil {
|
|
utils.Fatalf("Failed to read contract information from json output: %v", err)
|
|
}
|
|
}
|
|
// Gather all non-excluded contract for binding
|
|
for name, contract := range contracts {
|
|
// fully qualified name is of the form <solFilePath>:<type>
|
|
nameParts := strings.Split(name, ":")
|
|
typeName := nameParts[len(nameParts)-1]
|
|
if exclude != nil && exclude.Matches(name) {
|
|
fmt.Fprintf(os.Stderr, "excluding: %v\n", name)
|
|
continue
|
|
}
|
|
abi, err := json.Marshal(contract.Info.AbiDefinition) // Flatten the compiler parse
|
|
if err != nil {
|
|
utils.Fatalf("Failed to parse ABIs from compiler output: %v", err)
|
|
}
|
|
abis = append(abis, string(abi))
|
|
bins = append(bins, contract.Code)
|
|
sigs = append(sigs, contract.Hashes)
|
|
types = append(types, typeName)
|
|
|
|
// Derive the library placeholder which is a 34 character prefix of the
|
|
// hex encoding of the keccak256 hash of the fully qualified library name.
|
|
// Note that the fully qualified library name is the path of its source
|
|
// file and the library name separated by ":".
|
|
libPattern := crypto.Keccak256Hash([]byte(name)).String()[2:36] // the first 2 chars are 0x
|
|
libs[libPattern] = typeName
|
|
}
|
|
}
|
|
// Extract all aliases from the flags
|
|
if c.IsSet(aliasFlag.Name) {
|
|
// We support multi-versions for aliasing
|
|
// e.g.
|
|
// foo=bar,foo2=bar2
|
|
// foo:bar,foo2:bar2
|
|
re := regexp.MustCompile(`(?:(\w+)[:=](\w+))`)
|
|
submatches := re.FindAllStringSubmatch(c.String(aliasFlag.Name), -1)
|
|
for _, match := range submatches {
|
|
aliases[match[1]] = match[2]
|
|
}
|
|
}
|
|
// Generate the contract binding
|
|
var (
|
|
code string
|
|
err error
|
|
)
|
|
if c.IsSet(v2Flag.Name) {
|
|
code, err = abigen.BindV2(types, abis, bins, c.String(pkgFlag.Name), libs, aliases)
|
|
} else {
|
|
code, err = abigen.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases)
|
|
}
|
|
if err != nil {
|
|
utils.Fatalf("Failed to generate ABI binding: %v", err)
|
|
}
|
|
// Either flush it out to a file or display on the standard output
|
|
if !c.IsSet(outFlag.Name) {
|
|
fmt.Printf("%s\n", code)
|
|
return nil
|
|
}
|
|
if err := os.WriteFile(c.String(outFlag.Name), []byte(code), 0600); err != nil {
|
|
utils.Fatalf("Failed to write ABI binding: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
|
|
|
|
if err := app.Run(os.Args); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|