1// Copyright 2016 The go-ethereum Authors 2// This file is part of the go-ethereum library. 3// 4// The go-ethereum library is free software: you can redistribute it and/or modify 5// it under the terms of the GNU Lesser 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// The go-ethereum library 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 Lesser General Public License for more details. 13// 14// You should have received a copy of the GNU Lesser General Public License 15// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17// Package bind generates Ethereum contract Go bindings. 18// 19// Detailed usage document and tutorial available on the go-ethereum Wiki page: 20// https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts 21package bind 22 23import ( 24 "bytes" 25 "fmt" 26 "regexp" 27 "strings" 28 "text/template" 29 "unicode" 30 31 "github.com/ethereum/go-ethereum/accounts/abi" 32 "golang.org/x/tools/imports" 33) 34 35// Lang is a target programming language selector to generate bindings for. 36type Lang int 37 38const ( 39 LangGo Lang = iota 40 LangJava 41 LangObjC 42) 43 44// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant 45// to be used as is in client code, but rather as an intermediate struct which 46// enforces compile time type safety and naming convention opposed to having to 47// manually maintain hard coded strings that break on runtime. 48func Bind(types []string, abis []string, bytecodes []string, pkg string, lang Lang) (string, error) { 49 // Process each individual contract requested binding 50 contracts := make(map[string]*tmplContract) 51 52 for i := 0; i < len(types); i++ { 53 // Parse the actual ABI to generate the binding for 54 evmABI, err := abi.JSON(strings.NewReader(abis[i])) 55 if err != nil { 56 return "", err 57 } 58 // Strip any whitespace from the JSON ABI 59 strippedABI := strings.Map(func(r rune) rune { 60 if unicode.IsSpace(r) { 61 return -1 62 } 63 return r 64 }, abis[i]) 65 66 // Extract the call and transact methods; events; and sort them alphabetically 67 var ( 68 calls = make(map[string]*tmplMethod) 69 transacts = make(map[string]*tmplMethod) 70 events = make(map[string]*tmplEvent) 71 ) 72 for _, original := range evmABI.Methods { 73 // Normalize the method for capital cases and non-anonymous inputs/outputs 74 normalized := original 75 normalized.Name = methodNormalizer[lang](original.Name) 76 77 normalized.Inputs = make([]abi.Argument, len(original.Inputs)) 78 copy(normalized.Inputs, original.Inputs) 79 for j, input := range normalized.Inputs { 80 if input.Name == "" { 81 normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) 82 } 83 } 84 normalized.Outputs = make([]abi.Argument, len(original.Outputs)) 85 copy(normalized.Outputs, original.Outputs) 86 for j, output := range normalized.Outputs { 87 if output.Name != "" { 88 normalized.Outputs[j].Name = capitalise(output.Name) 89 } 90 } 91 // Append the methods to the call or transact lists 92 if original.Const { 93 calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} 94 } else { 95 transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} 96 } 97 } 98 for _, original := range evmABI.Events { 99 // Skip anonymous events as they don't support explicit filtering 100 if original.Anonymous { 101 continue 102 } 103 // Normalize the event for capital cases and non-anonymous outputs 104 normalized := original 105 normalized.Name = methodNormalizer[lang](original.Name) 106 107 normalized.Inputs = make([]abi.Argument, len(original.Inputs)) 108 copy(normalized.Inputs, original.Inputs) 109 for j, input := range normalized.Inputs { 110 // Indexed fields are input, non-indexed ones are outputs 111 if input.Indexed { 112 if input.Name == "" { 113 normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) 114 } 115 } 116 } 117 // Append the event to the accumulator list 118 events[original.Name] = &tmplEvent{Original: original, Normalized: normalized} 119 } 120 contracts[types[i]] = &tmplContract{ 121 Type: capitalise(types[i]), 122 InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1), 123 InputBin: strings.TrimSpace(bytecodes[i]), 124 Constructor: evmABI.Constructor, 125 Calls: calls, 126 Transacts: transacts, 127 Events: events, 128 } 129 } 130 // Generate the contract template data content and render it 131 data := &tmplData{ 132 Package: pkg, 133 Contracts: contracts, 134 } 135 buffer := new(bytes.Buffer) 136 137 funcs := map[string]interface{}{ 138 "bindtype": bindType[lang], 139 "bindtopictype": bindTopicType[lang], 140 "namedtype": namedType[lang], 141 "capitalise": capitalise, 142 "decapitalise": decapitalise, 143 } 144 tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang])) 145 if err := tmpl.Execute(buffer, data); err != nil { 146 return "", err 147 } 148 // For Go bindings pass the code through goimports to clean it up and double check 149 if lang == LangGo { 150 code, err := imports.Process(".", buffer.Bytes(), nil) 151 if err != nil { 152 return "", fmt.Errorf("%v\n%s", err, buffer) 153 } 154 return string(code), nil 155 } 156 // For all others just return as is for now 157 return buffer.String(), nil 158} 159 160// bindType is a set of type binders that convert Solidity types to some supported 161// programming language types. 162var bindType = map[Lang]func(kind abi.Type) string{ 163 LangGo: bindTypeGo, 164 LangJava: bindTypeJava, 165} 166 167// Helper function for the binding generators. 168// It reads the unmatched characters after the inner type-match, 169// (since the inner type is a prefix of the total type declaration), 170// looks for valid arrays (possibly a dynamic one) wrapping the inner type, 171// and returns the sizes of these arrays. 172// 173// Returned array sizes are in the same order as solidity signatures; inner array size first. 174// Array sizes may also be "", indicating a dynamic array. 175func wrapArray(stringKind string, innerLen int, innerMapping string) (string, []string) { 176 remainder := stringKind[innerLen:] 177 //find all the sizes 178 matches := regexp.MustCompile(`\[(\d*)\]`).FindAllStringSubmatch(remainder, -1) 179 parts := make([]string, 0, len(matches)) 180 for _, match := range matches { 181 //get group 1 from the regex match 182 parts = append(parts, match[1]) 183 } 184 return innerMapping, parts 185} 186 187// Translates the array sizes to a Go-lang declaration of a (nested) array of the inner type. 188// Simply returns the inner type if arraySizes is empty. 189func arrayBindingGo(inner string, arraySizes []string) string { 190 out := "" 191 //prepend all array sizes, from outer (end arraySizes) to inner (start arraySizes) 192 for i := len(arraySizes) - 1; i >= 0; i-- { 193 out += "[" + arraySizes[i] + "]" 194 } 195 out += inner 196 return out 197} 198 199// bindTypeGo converts a Solidity type to a Go one. Since there is no clear mapping 200// from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly 201// mapped will use an upscaled type (e.g. *big.Int). 202func bindTypeGo(kind abi.Type) string { 203 stringKind := kind.String() 204 innerLen, innerMapping := bindUnnestedTypeGo(stringKind) 205 return arrayBindingGo(wrapArray(stringKind, innerLen, innerMapping)) 206} 207 208// The inner function of bindTypeGo, this finds the inner type of stringKind. 209// (Or just the type itself if it is not an array or slice) 210// The length of the matched part is returned, with the translated type. 211func bindUnnestedTypeGo(stringKind string) (int, string) { 212 213 switch { 214 case strings.HasPrefix(stringKind, "address"): 215 return len("address"), "common.Address" 216 217 case strings.HasPrefix(stringKind, "bytes"): 218 parts := regexp.MustCompile(`bytes([0-9]*)`).FindStringSubmatch(stringKind) 219 return len(parts[0]), fmt.Sprintf("[%s]byte", parts[1]) 220 221 case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"): 222 parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(stringKind) 223 switch parts[2] { 224 case "8", "16", "32", "64": 225 return len(parts[0]), fmt.Sprintf("%sint%s", parts[1], parts[2]) 226 } 227 return len(parts[0]), "*big.Int" 228 229 case strings.HasPrefix(stringKind, "bool"): 230 return len("bool"), "bool" 231 232 case strings.HasPrefix(stringKind, "string"): 233 return len("string"), "string" 234 235 default: 236 return len(stringKind), stringKind 237 } 238} 239 240// Translates the array sizes to a Java declaration of a (nested) array of the inner type. 241// Simply returns the inner type if arraySizes is empty. 242func arrayBindingJava(inner string, arraySizes []string) string { 243 // Java array type declarations do not include the length. 244 return inner + strings.Repeat("[]", len(arraySizes)) 245} 246 247// bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping 248// from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly 249// mapped will use an upscaled type (e.g. BigDecimal). 250func bindTypeJava(kind abi.Type) string { 251 stringKind := kind.String() 252 innerLen, innerMapping := bindUnnestedTypeJava(stringKind) 253 return arrayBindingJava(wrapArray(stringKind, innerLen, innerMapping)) 254} 255 256// The inner function of bindTypeJava, this finds the inner type of stringKind. 257// (Or just the type itself if it is not an array or slice) 258// The length of the matched part is returned, with the translated type. 259func bindUnnestedTypeJava(stringKind string) (int, string) { 260 261 switch { 262 case strings.HasPrefix(stringKind, "address"): 263 parts := regexp.MustCompile(`address(\[[0-9]*\])?`).FindStringSubmatch(stringKind) 264 if len(parts) != 2 { 265 return len(stringKind), stringKind 266 } 267 if parts[1] == "" { 268 return len("address"), "Address" 269 } 270 return len(parts[0]), "Addresses" 271 272 case strings.HasPrefix(stringKind, "bytes"): 273 parts := regexp.MustCompile(`bytes([0-9]*)`).FindStringSubmatch(stringKind) 274 if len(parts) != 2 { 275 return len(stringKind), stringKind 276 } 277 return len(parts[0]), "byte[]" 278 279 case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"): 280 //Note that uint and int (without digits) are also matched, 281 // these are size 256, and will translate to BigInt (the default). 282 parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(stringKind) 283 if len(parts) != 3 { 284 return len(stringKind), stringKind 285 } 286 287 namedSize := map[string]string{ 288 "8": "byte", 289 "16": "short", 290 "32": "int", 291 "64": "long", 292 }[parts[2]] 293 294 //default to BigInt 295 if namedSize == "" { 296 namedSize = "BigInt" 297 } 298 return len(parts[0]), namedSize 299 300 case strings.HasPrefix(stringKind, "bool"): 301 return len("bool"), "boolean" 302 303 case strings.HasPrefix(stringKind, "string"): 304 return len("string"), "String" 305 306 default: 307 return len(stringKind), stringKind 308 } 309} 310 311// bindTopicType is a set of type binders that convert Solidity types to some 312// supported programming language topic types. 313var bindTopicType = map[Lang]func(kind abi.Type) string{ 314 LangGo: bindTopicTypeGo, 315 LangJava: bindTopicTypeJava, 316} 317 318// bindTypeGo converts a Solidity topic type to a Go one. It is almost the same 319// funcionality as for simple types, but dynamic types get converted to hashes. 320func bindTopicTypeGo(kind abi.Type) string { 321 bound := bindTypeGo(kind) 322 if bound == "string" || bound == "[]byte" { 323 bound = "common.Hash" 324 } 325 return bound 326} 327 328// bindTypeGo converts a Solidity topic type to a Java one. It is almost the same 329// funcionality as for simple types, but dynamic types get converted to hashes. 330func bindTopicTypeJava(kind abi.Type) string { 331 bound := bindTypeJava(kind) 332 if bound == "String" || bound == "Bytes" { 333 bound = "Hash" 334 } 335 return bound 336} 337 338// namedType is a set of functions that transform language specific types to 339// named versions that my be used inside method names. 340var namedType = map[Lang]func(string, abi.Type) string{ 341 LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") }, 342 LangJava: namedTypeJava, 343} 344 345// namedTypeJava converts some primitive data types to named variants that can 346// be used as parts of method names. 347func namedTypeJava(javaKind string, solKind abi.Type) string { 348 switch javaKind { 349 case "byte[]": 350 return "Binary" 351 case "byte[][]": 352 return "Binaries" 353 case "string": 354 return "String" 355 case "string[]": 356 return "Strings" 357 case "boolean": 358 return "Bool" 359 case "boolean[]": 360 return "Bools" 361 case "BigInt[]": 362 return "BigInts" 363 default: 364 parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(solKind.String()) 365 if len(parts) != 4 { 366 return javaKind 367 } 368 switch parts[2] { 369 case "8", "16", "32", "64": 370 if parts[3] == "" { 371 return capitalise(fmt.Sprintf("%sint%s", parts[1], parts[2])) 372 } 373 return capitalise(fmt.Sprintf("%sint%ss", parts[1], parts[2])) 374 375 default: 376 return javaKind 377 } 378 } 379} 380 381// methodNormalizer is a name transformer that modifies Solidity method names to 382// conform to target language naming concentions. 383var methodNormalizer = map[Lang]func(string) string{ 384 LangGo: capitalise, 385 LangJava: decapitalise, 386} 387 388// capitalise makes a camel-case string which starts with an upper case character. 389func capitalise(input string) string { 390 for len(input) > 0 && input[0] == '_' { 391 input = input[1:] 392 } 393 if len(input) == 0 { 394 return "" 395 } 396 return toCamelCase(strings.ToUpper(input[:1]) + input[1:]) 397} 398 399// decapitalise makes a camel-case string which starts with a lower case character. 400func decapitalise(input string) string { 401 for len(input) > 0 && input[0] == '_' { 402 input = input[1:] 403 } 404 if len(input) == 0 { 405 return "" 406 } 407 return toCamelCase(strings.ToLower(input[:1]) + input[1:]) 408} 409 410// toCamelCase converts an under-score string to a camel-case string 411func toCamelCase(input string) string { 412 toupper := false 413 414 result := "" 415 for k, v := range input { 416 switch { 417 case k == 0: 418 result = strings.ToUpper(string(input[0])) 419 420 case toupper: 421 result += strings.ToUpper(string(v)) 422 toupper = false 423 424 case v == '_': 425 toupper = true 426 427 default: 428 result += string(v) 429 } 430 } 431 return result 432} 433 434// structured checks whether a list of ABI data types has enough information to 435// operate through a proper Go struct or if flat returns are needed. 436func structured(args abi.Arguments) bool { 437 if len(args) < 2 { 438 return false 439 } 440 exists := make(map[string]bool) 441 for _, out := range args { 442 // If the name is anonymous, we can't organize into a struct 443 if out.Name == "" { 444 return false 445 } 446 // If the field name is empty when normalized or collides (var, Var, _var, _Var), 447 // we can't organize into a struct 448 field := capitalise(out.Name) 449 if field == "" || exists[field] { 450 return false 451 } 452 exists[field] = true 453 } 454 return true 455} 456