cmd/abigen: refactor command line interface (#19797)

* cmd, common: refactor abigen command line interface

* cmd/abigen: address comment
This commit is contained in:
gary rong 2019-07-08 20:59:07 +08:00 committed by Guillaume Ballet
parent cdfe9a3a2a
commit 22060611fb
3 changed files with 171 additions and 103 deletions

@ -18,63 +18,129 @@ package main
import ( import (
"encoding/json" "encoding/json"
"flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"strings" "strings"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common/compiler" "github.com/ethereum/go-ethereum/common/compiler"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"gopkg.in/urfave/cli.v1"
)
const (
commandHelperTemplate = `{{.Name}}{{if .Subcommands}} command{{end}}{{if .Flags}} [command options]{{end}} [arguments...]
{{if .Description}}{{.Description}}
{{end}}{{if .Subcommands}}
SUBCOMMANDS:
{{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
{{end}}{{end}}{{if .Flags}}
OPTIONS:
{{range $.Flags}}{{"\t"}}{{.}}
{{end}}
{{end}}`
) )
var ( var (
abiFlag = flag.String("abi", "", "Path to the Ethereum contract ABI json to bind, - for STDIN") // Git SHA1 commit hash of the release (set via linker flags)
binFlag = flag.String("bin", "", "Path to the Ethereum contract bytecode (generate deploy method)") gitCommit = ""
typFlag = flag.String("type", "", "Struct name for the binding (default = package name)") gitDate = ""
solFlag = flag.String("sol", "", "Path to the Ethereum contract Solidity source to build and bind") app *cli.App
solcFlag = flag.String("solc", "solc", "Solidity compiler to use if source builds are requested")
excFlag = flag.String("exc", "", "Comma separated types to exclude from binding")
vyFlag = flag.String("vy", "", "Path to the Ethereum contract Vyper source to build and bind") // Flags needed by abigen
vyperFlag = flag.String("vyper", "vyper", "Vyper compiler to use if source builds are requested") abiFlag = cli.StringFlag{
Name: "abi",
pkgFlag = flag.String("pkg", "", "Package name to generate the binding into") Usage: "Path to the Ethereum contract ABI json to bind, - for STDIN",
outFlag = flag.String("out", "", "Output file for the generated binding (default = stdout)") }
langFlag = flag.String("lang", "go", "Destination language for the bindings (go, java, objc)") 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",
}
solFlag = cli.StringFlag{
Name: "sol",
Usage: "Path to the Ethereum contract Solidity source to build and bind",
}
solcFlag = cli.StringFlag{
Name: "solc",
Usage: "Solidity compiler to use if source builds are requested",
Value: "solc",
}
vyFlag = cli.StringFlag{
Name: "vy",
Usage: "Path to the Ethereum contract Vyper source to build and bind",
}
vyperFlag = cli.StringFlag{
Name: "vyper",
Usage: "Vyper compiler to use if source builds are requested",
Value: "vyper",
}
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)",
}
langFlag = cli.StringFlag{
Name: "lang",
Usage: "Destination language for the bindings (go, java, objc)",
Value: "go",
}
) )
func main() { func init() {
// Parse and ensure all needed inputs are specified app = utils.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool")
flag.Parse() app.Flags = []cli.Flag{
abiFlag,
if *abiFlag == "" && *solFlag == "" && *vyFlag == "" { binFlag,
fmt.Printf("No contract ABI (--abi), Solidity source (--sol), or Vyper source (--vy) specified\n") typeFlag,
os.Exit(-1) jsonFlag,
} else if (*abiFlag != "" || *binFlag != "" || *typFlag != "") && (*solFlag != "" || *vyFlag != "") { solFlag,
fmt.Printf("Contract ABI (--abi), bytecode (--bin) and type (--type) flags are mutually exclusive with the Solidity (--sol) and Vyper (--vy) flags\n") solcFlag,
os.Exit(-1) vyFlag,
} else if *solFlag != "" && *vyFlag != "" { vyperFlag,
fmt.Printf("Solidity (--sol) and Vyper (--vy) flags are mutually exclusive\n") excFlag,
os.Exit(-1) pkgFlag,
outFlag,
langFlag,
} }
if *pkgFlag == "" { app.Action = utils.MigrateFlags(abigen)
fmt.Printf("No destination package specified (--pkg)\n") cli.CommandHelpTemplate = commandHelperTemplate
os.Exit(-1) }
func abigen(c *cli.Context) error {
utils.CheckExclusive(c, abiFlag, jsonFlag, solFlag, vyFlag) // Only one source can be selected.
if c.GlobalString(pkgFlag.Name) == "" {
utils.Fatalf("No destination package specified (--pkg)")
} }
var lang bind.Lang var lang bind.Lang
switch *langFlag { switch c.GlobalString(langFlag.Name) {
case "go": case "go":
lang = bind.LangGo lang = bind.LangGo
case "java": case "java":
lang = bind.LangJava lang = bind.LangJava
case "objc": case "objc":
lang = bind.LangObjC lang = bind.LangObjC
utils.Fatalf("Objc binding generation is uncompleted")
default: default:
fmt.Printf("Unsupported destination language \"%s\" (--lang)\n", *langFlag) utils.Fatalf("Unsupported destination language \"%s\" (--lang)", c.GlobalString(langFlag.Name))
os.Exit(-1)
} }
// If the entire solidity code was specified, build and bind based on that // If the entire solidity code was specified, build and bind based on that
var ( var (
@ -84,34 +150,67 @@ func main() {
sigs []map[string]string sigs []map[string]string
libs = make(map[string]string) libs = make(map[string]string)
) )
if *solFlag != "" || *vyFlag != "" || *abiFlag == "-" { if c.GlobalString(abiFlag.Name) != "" {
// Load up the ABI, optional bytecode and type name from the parameters
var (
abi []byte
err error
)
input := c.GlobalString(abiFlag.Name)
if input == "-" {
abi, err = ioutil.ReadAll(os.Stdin)
} else {
abi, err = ioutil.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.GlobalString(binFlag.Name); binFile != "" {
if bin, err = ioutil.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.GlobalString(typeFlag.Name)
if kind == "" {
kind = c.GlobalString(pkgFlag.Name)
}
types = append(types, kind)
} else {
// Generate the list of types to exclude from binding // Generate the list of types to exclude from binding
exclude := make(map[string]bool) exclude := make(map[string]bool)
for _, kind := range strings.Split(*excFlag, ",") { for _, kind := range strings.Split(c.GlobalString(excFlag.Name), ",") {
exclude[strings.ToLower(kind)] = true exclude[strings.ToLower(kind)] = true
} }
var contracts map[string]*compiler.Contract
var err error var err error
var contracts map[string]*compiler.Contract
switch { switch {
case *solFlag != "": case c.GlobalIsSet(solFlag.Name):
contracts, err = compiler.CompileSolidity(*solcFlag, *solFlag) contracts, err = compiler.CompileSolidity(c.GlobalString(solcFlag.Name), c.GlobalString(solFlag.Name))
if err != nil { if err != nil {
fmt.Printf("Failed to build Solidity contract: %v\n", err) utils.Fatalf("Failed to build Solidity contract: %v", err)
os.Exit(-1)
} }
case *vyFlag != "": case c.GlobalIsSet(vyFlag.Name):
contracts, err = compiler.CompileVyper(*vyperFlag, *vyFlag) contracts, err = compiler.CompileVyper(c.GlobalString(vyperFlag.Name), c.GlobalString(vyFlag.Name))
if err != nil { if err != nil {
fmt.Printf("Failed to build Vyper contract: %v\n", err) utils.Fatalf("Failed to build Vyper contract: %v", err)
os.Exit(-1)
} }
default: case c.GlobalIsSet(jsonFlag.Name):
contracts, err = contractsFromStdin() jsonOutput, err := ioutil.ReadFile(c.GlobalString(jsonFlag.Name))
if err != nil { if err != nil {
fmt.Printf("Failed to read input ABIs from STDIN: %v\n", err) utils.Fatalf("Failed to read combined-json from compiler: %v", err)
os.Exit(-1) }
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 // Gather all non-excluded contract for binding
@ -121,65 +220,39 @@ func main() {
} }
abi, err := json.Marshal(contract.Info.AbiDefinition) // Flatten the compiler parse abi, err := json.Marshal(contract.Info.AbiDefinition) // Flatten the compiler parse
if err != nil { if err != nil {
fmt.Printf("Failed to parse ABIs from compiler output: %v\n", err) utils.Fatalf("Failed to parse ABIs from compiler output: %v", err)
os.Exit(-1)
} }
abis = append(abis, string(abi)) abis = append(abis, string(abi))
bins = append(bins, contract.Code) bins = append(bins, contract.Code)
sigs = append(sigs, contract.Hashes) sigs = append(sigs, contract.Hashes)
nameParts := strings.Split(name, ":") nameParts := strings.Split(name, ":")
types = append(types, nameParts[len(nameParts)-1]) types = append(types, nameParts[len(nameParts)-1])
libPattern := crypto.Keccak256Hash([]byte(name)).String()[2:36] libPattern := crypto.Keccak256Hash([]byte(name)).String()[2:36]
libs[libPattern] = nameParts[len(nameParts)-1] libs[libPattern] = nameParts[len(nameParts)-1]
} }
} else {
// Otherwise load up the ABI, optional bytecode and type name from the parameters
abi, err := ioutil.ReadFile(*abiFlag)
if err != nil {
fmt.Printf("Failed to read input ABI: %v\n", err)
os.Exit(-1)
}
abis = append(abis, string(abi))
var bin []byte
if *binFlag != "" {
if bin, err = ioutil.ReadFile(*binFlag); err != nil {
fmt.Printf("Failed to read input bytecode: %v\n", err)
os.Exit(-1)
}
}
bins = append(bins, string(bin))
kind := *typFlag
if kind == "" {
kind = *pkgFlag
}
types = append(types, kind)
} }
// Generate the contract binding // Generate the contract binding
code, err := bind.Bind(types, abis, bins, sigs, *pkgFlag, lang, libs) code, err := bind.Bind(types, abis, bins, sigs, c.GlobalString(pkgFlag.Name), lang, libs)
if err != nil { if err != nil {
fmt.Printf("Failed to generate ABI binding: %v\n", err) utils.Fatalf("Failed to generate ABI binding: %v", err)
os.Exit(-1)
} }
// Either flush it out to a file or display on the standard output // Either flush it out to a file or display on the standard output
if *outFlag == "" { if !c.GlobalIsSet(outFlag.Name) {
fmt.Printf("%s\n", code) fmt.Printf("%s\n", code)
return return nil
} }
if err := ioutil.WriteFile(*outFlag, []byte(code), 0600); err != nil { if err := ioutil.WriteFile(c.GlobalString(outFlag.Name), []byte(code), 0600); err != nil {
fmt.Printf("Failed to write ABI binding: %v\n", err) utils.Fatalf("Failed to write ABI binding: %v", err)
os.Exit(-1)
} }
return nil
} }
func contractsFromStdin() (map[string]*compiler.Contract, error) { func main() {
bytes, err := ioutil.ReadAll(os.Stdin) log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
if err != nil {
return nil, err if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
} }
return compiler.ParseCombinedJSON(bytes, "", "", "", "")
} }

@ -944,7 +944,7 @@ func setWS(ctx *cli.Context, cfg *node.Config) {
// setIPC creates an IPC path configuration from the set command line flags, // setIPC creates an IPC path configuration from the set command line flags,
// returning an empty string if IPC was explicitly disabled, or the set path. // returning an empty string if IPC was explicitly disabled, or the set path.
func setIPC(ctx *cli.Context, cfg *node.Config) { func setIPC(ctx *cli.Context, cfg *node.Config) {
checkExclusive(ctx, IPCDisabledFlag, IPCPathFlag) CheckExclusive(ctx, IPCDisabledFlag, IPCPathFlag)
switch { switch {
case ctx.GlobalBool(IPCDisabledFlag.Name): case ctx.GlobalBool(IPCDisabledFlag.Name):
cfg.IPCPath = "" cfg.IPCPath = ""
@ -1329,10 +1329,10 @@ func setWhitelist(ctx *cli.Context, cfg *eth.Config) {
} }
} }
// checkExclusive verifies that only a single instance of the provided flags was // CheckExclusive verifies that only a single instance of the provided flags was
// set by the user. Each flag might optionally be followed by a string type to // set by the user. Each flag might optionally be followed by a string type to
// specialize it further. // specialize it further.
func checkExclusive(ctx *cli.Context, args ...interface{}) { func CheckExclusive(ctx *cli.Context, args ...interface{}) {
set := make([]string, 0, 1) set := make([]string, 0, 1)
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
// Make sure the next argument is a flag and skip if not set // Make sure the next argument is a flag and skip if not set
@ -1386,10 +1386,10 @@ func SetShhConfig(ctx *cli.Context, stack *node.Node, cfg *whisper.Config) {
// SetEthConfig applies eth-related command line flags to the config. // SetEthConfig applies eth-related command line flags to the config.
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
// Avoid conflicting network flags // Avoid conflicting network flags
checkExclusive(ctx, DeveloperFlag, TestnetFlag, RinkebyFlag, GoerliFlag) CheckExclusive(ctx, DeveloperFlag, TestnetFlag, RinkebyFlag, GoerliFlag)
checkExclusive(ctx, LightServFlag, SyncModeFlag, "light") CheckExclusive(ctx, LightServFlag, SyncModeFlag, "light")
// Can't use both ephemeral unlocked and external signer // Can't use both ephemeral unlocked and external signer
checkExclusive(ctx, DeveloperFlag, ExternalSignerFlag) CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag)
var ks *keystore.KeyStore var ks *keystore.KeyStore
if keystores := stack.AccountManager().Backends(keystore.KeyStoreType); len(keystores) > 0 { if keystores := stack.AccountManager().Backends(keystore.KeyStoreType); len(keystores) > 0 {
ks = keystores[0].(*keystore.KeyStore) ks = keystores[0].(*keystore.KeyStore)

@ -142,7 +142,6 @@ func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion strin
if err := json.Unmarshal(combinedJSON, &output); err != nil { if err := json.Unmarshal(combinedJSON, &output); err != nil {
return nil, err return nil, err
} }
// Compilation succeeded, assemble and return the contracts. // Compilation succeeded, assemble and return the contracts.
contracts := make(map[string]*Contract) contracts := make(map[string]*Contract)
for name, info := range output.Contracts { for name, info := range output.Contracts {
@ -151,14 +150,10 @@ func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion strin
if err := json.Unmarshal([]byte(info.Abi), &abi); err != nil { if err := json.Unmarshal([]byte(info.Abi), &abi); err != nil {
return nil, fmt.Errorf("solc: error reading abi definition (%v)", err) return nil, fmt.Errorf("solc: error reading abi definition (%v)", err)
} }
var userdoc interface{} var userdoc, devdoc interface{}
if err := json.Unmarshal([]byte(info.Userdoc), &userdoc); err != nil { json.Unmarshal([]byte(info.Userdoc), &userdoc)
return nil, fmt.Errorf("solc: error reading user doc: %v", err) json.Unmarshal([]byte(info.Devdoc), &devdoc)
}
var devdoc interface{}
if err := json.Unmarshal([]byte(info.Devdoc), &devdoc); err != nil {
return nil, fmt.Errorf("solc: error reading dev doc: %v", err)
}
contracts[name] = &Contract{ contracts[name] = &Contract{
Code: "0x" + info.Bin, Code: "0x" + info.Bin,
RuntimeCode: "0x" + info.BinRuntime, RuntimeCode: "0x" + info.BinRuntime,