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 }