github.com/ethereum/go-ethereum@v1.16.1/cmd/abigen/main.go (about)

     1  // Copyright 2016 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // go-ethereum is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package main
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"io"
    23  	"os"
    24  	"regexp"
    25  	"strings"
    26  
    27  	"github.com/ethereum/go-ethereum/accounts/abi/abigen"
    28  	"github.com/ethereum/go-ethereum/cmd/utils"
    29  	"github.com/ethereum/go-ethereum/common/compiler"
    30  	"github.com/ethereum/go-ethereum/crypto"
    31  	"github.com/ethereum/go-ethereum/internal/flags"
    32  	"github.com/ethereum/go-ethereum/log"
    33  	"github.com/urfave/cli/v2"
    34  )
    35  
    36  var (
    37  	// Flags needed by abigen
    38  	abiFlag = &cli.StringFlag{
    39  		Name:  "abi",
    40  		Usage: "Path to the Ethereum contract ABI json to bind, - for STDIN",
    41  	}
    42  	binFlag = &cli.StringFlag{
    43  		Name:  "bin",
    44  		Usage: "Path to the Ethereum contract bytecode (generate deploy method)",
    45  	}
    46  	typeFlag = &cli.StringFlag{
    47  		Name:  "type",
    48  		Usage: "Struct name for the binding (default = package name)",
    49  	}
    50  	jsonFlag = &cli.StringFlag{
    51  		Name:  "combined-json",
    52  		Usage: "Path to the combined-json file generated by compiler, - for STDIN",
    53  	}
    54  	excFlag = &cli.StringFlag{
    55  		Name:  "exc",
    56  		Usage: "Comma separated types to exclude from binding",
    57  	}
    58  	pkgFlag = &cli.StringFlag{
    59  		Name:  "pkg",
    60  		Usage: "Package name to generate the binding into",
    61  	}
    62  	outFlag = &cli.StringFlag{
    63  		Name:  "out",
    64  		Usage: "Output file for the generated binding (default = stdout)",
    65  	}
    66  	aliasFlag = &cli.StringFlag{
    67  		Name:  "alias",
    68  		Usage: "Comma separated aliases for function and event renaming.  If --v2 is set, errors are aliased as well. e.g. original1=alias1, original2=alias2",
    69  	}
    70  	v2Flag = &cli.BoolFlag{
    71  		Name:  "v2",
    72  		Usage: "Generates v2 bindings",
    73  	}
    74  )
    75  
    76  var app = flags.NewApp("Ethereum ABI wrapper code generator")
    77  
    78  func init() {
    79  	app.Name = "abigen"
    80  	app.Flags = []cli.Flag{
    81  		abiFlag,
    82  		binFlag,
    83  		typeFlag,
    84  		jsonFlag,
    85  		excFlag,
    86  		pkgFlag,
    87  		outFlag,
    88  		aliasFlag,
    89  		v2Flag,
    90  	}
    91  	app.Action = generate
    92  }
    93  
    94  func generate(c *cli.Context) error {
    95  	flags.CheckExclusive(c, abiFlag, jsonFlag) // Only one source can be selected.
    96  
    97  	if c.String(pkgFlag.Name) == "" {
    98  		utils.Fatalf("No destination package specified (--pkg)")
    99  	}
   100  	if c.String(abiFlag.Name) == "" && c.String(jsonFlag.Name) == "" {
   101  		utils.Fatalf("Either contract ABI source (--abi) or combined-json (--combined-json) are required")
   102  	}
   103  	// If the entire solidity code was specified, build and bind based on that
   104  	var (
   105  		abis    []string
   106  		bins    []string
   107  		types   []string
   108  		sigs    []map[string]string
   109  		libs    = make(map[string]string)
   110  		aliases = make(map[string]string)
   111  	)
   112  	if c.String(abiFlag.Name) != "" {
   113  		// Load up the ABI, optional bytecode and type name from the parameters
   114  		var (
   115  			abi []byte
   116  			err error
   117  		)
   118  		input := c.String(abiFlag.Name)
   119  		if input == "-" {
   120  			abi, err = io.ReadAll(os.Stdin)
   121  		} else {
   122  			abi, err = os.ReadFile(input)
   123  		}
   124  		if err != nil {
   125  			utils.Fatalf("Failed to read input ABI: %v", err)
   126  		}
   127  		abis = append(abis, string(abi))
   128  
   129  		var bin []byte
   130  		if binFile := c.String(binFlag.Name); binFile != "" {
   131  			if bin, err = os.ReadFile(binFile); err != nil {
   132  				utils.Fatalf("Failed to read input bytecode: %v", err)
   133  			}
   134  			if strings.Contains(string(bin), "//") {
   135  				utils.Fatalf("Contract has additional library references, please use other mode(e.g. --combined-json) to catch library infos")
   136  			}
   137  		}
   138  		bins = append(bins, string(bin))
   139  
   140  		kind := c.String(typeFlag.Name)
   141  		if kind == "" {
   142  			kind = c.String(pkgFlag.Name)
   143  		}
   144  		types = append(types, kind)
   145  	} else {
   146  		// Generate the list of types to exclude from binding
   147  		var exclude *nameFilter
   148  		if c.IsSet(excFlag.Name) {
   149  			var err error
   150  			if exclude, err = newNameFilter(strings.Split(c.String(excFlag.Name), ",")...); err != nil {
   151  				utils.Fatalf("Failed to parse excludes: %v", err)
   152  			}
   153  		}
   154  		var contracts map[string]*compiler.Contract
   155  
   156  		if c.IsSet(jsonFlag.Name) {
   157  			var (
   158  				input      = c.String(jsonFlag.Name)
   159  				jsonOutput []byte
   160  				err        error
   161  			)
   162  			if input == "-" {
   163  				jsonOutput, err = io.ReadAll(os.Stdin)
   164  			} else {
   165  				jsonOutput, err = os.ReadFile(input)
   166  			}
   167  			if err != nil {
   168  				utils.Fatalf("Failed to read combined-json: %v", err)
   169  			}
   170  			contracts, err = compiler.ParseCombinedJSON(jsonOutput, "", "", "", "")
   171  			if err != nil {
   172  				utils.Fatalf("Failed to read contract information from json output: %v", err)
   173  			}
   174  		}
   175  		// Gather all non-excluded contract for binding
   176  		for name, contract := range contracts {
   177  			// fully qualified name is of the form <solFilePath>:<type>
   178  			nameParts := strings.Split(name, ":")
   179  			typeName := nameParts[len(nameParts)-1]
   180  			if exclude != nil && exclude.Matches(name) {
   181  				fmt.Fprintf(os.Stderr, "excluding: %v\n", name)
   182  				continue
   183  			}
   184  			abi, err := json.Marshal(contract.Info.AbiDefinition) // Flatten the compiler parse
   185  			if err != nil {
   186  				utils.Fatalf("Failed to parse ABIs from compiler output: %v", err)
   187  			}
   188  			abis = append(abis, string(abi))
   189  			bins = append(bins, contract.Code)
   190  			sigs = append(sigs, contract.Hashes)
   191  			types = append(types, typeName)
   192  
   193  			// Derive the library placeholder which is a 34 character prefix of the
   194  			// hex encoding of the keccak256 hash of the fully qualified library name.
   195  			// Note that the fully qualified library name is the path of its source
   196  			// file and the library name separated by ":".
   197  			libPattern := crypto.Keccak256Hash([]byte(name)).String()[2:36] // the first 2 chars are 0x
   198  			libs[libPattern] = typeName
   199  		}
   200  	}
   201  	// Extract all aliases from the flags
   202  	if c.IsSet(aliasFlag.Name) {
   203  		// We support multi-versions for aliasing
   204  		// e.g.
   205  		//      foo=bar,foo2=bar2
   206  		//      foo:bar,foo2:bar2
   207  		re := regexp.MustCompile(`(?:(\w+)[:=](\w+))`)
   208  		submatches := re.FindAllStringSubmatch(c.String(aliasFlag.Name), -1)
   209  		for _, match := range submatches {
   210  			aliases[match[1]] = match[2]
   211  		}
   212  	}
   213  	// Generate the contract binding
   214  	var (
   215  		code string
   216  		err  error
   217  	)
   218  	if c.IsSet(v2Flag.Name) {
   219  		code, err = abigen.BindV2(types, abis, bins, c.String(pkgFlag.Name), libs, aliases)
   220  	} else {
   221  		code, err = abigen.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), libs, aliases)
   222  	}
   223  	if err != nil {
   224  		utils.Fatalf("Failed to generate ABI binding: %v", err)
   225  	}
   226  	// Either flush it out to a file or display on the standard output
   227  	if !c.IsSet(outFlag.Name) {
   228  		fmt.Printf("%s\n", code)
   229  		return nil
   230  	}
   231  	if err := os.WriteFile(c.String(outFlag.Name), []byte(code), 0600); err != nil {
   232  		utils.Fatalf("Failed to write ABI binding: %v", err)
   233  	}
   234  	return nil
   235  }
   236  
   237  func main() {
   238  	log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
   239  
   240  	if err := app.Run(os.Args); err != nil {
   241  		fmt.Fprintln(os.Stderr, err)
   242  		os.Exit(1)
   243  	}
   244  }