github.com/0xsequence/ethkit@v1.25.0/go-ethereum/accounts/abi/bind/bind.go (about) 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 21 package bind 22 23 import ( 24 "bytes" 25 "errors" 26 "fmt" 27 "go/format" 28 "regexp" 29 "strings" 30 "text/template" 31 "unicode" 32 33 "github.com/0xsequence/ethkit/go-ethereum/accounts/abi" 34 "github.com/0xsequence/ethkit/go-ethereum/log" 35 ) 36 37 // Lang is a target programming language selector to generate bindings for. 38 type Lang int 39 40 const ( 41 LangGo Lang = iota 42 LangJava 43 LangObjC 44 ) 45 46 func isKeyWord(arg string) bool { 47 switch arg { 48 case "break": 49 case "case": 50 case "chan": 51 case "const": 52 case "continue": 53 case "default": 54 case "defer": 55 case "else": 56 case "fallthrough": 57 case "for": 58 case "func": 59 case "go": 60 case "goto": 61 case "if": 62 case "import": 63 case "interface": 64 case "iota": 65 case "map": 66 case "make": 67 case "new": 68 case "package": 69 case "range": 70 case "return": 71 case "select": 72 case "struct": 73 case "switch": 74 case "type": 75 case "var": 76 default: 77 return false 78 } 79 80 return true 81 } 82 83 // Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant 84 // to be used as is in client code, but rather as an intermediate struct which 85 // enforces compile time type safety and naming convention opposed to having to 86 // manually maintain hard coded strings that break on runtime. 87 func Bind(types []string, abis []string, bytecodes []string, deployedBytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) { 88 var ( 89 // contracts is the map of each individual contract requested binding 90 contracts = make(map[string]*tmplContract) 91 92 // structs is the map of all redeclared structs shared by passed contracts. 93 structs = make(map[string]*tmplStruct) 94 95 // isLib is the map used to flag each encountered library as such 96 isLib = make(map[string]struct{}) 97 ) 98 for i := 0; i < len(types); i++ { 99 // Parse the actual ABI to generate the binding for 100 evmABI, err := abi.JSON(strings.NewReader(abis[i])) 101 if err != nil { 102 return "", err 103 } 104 // Strip any whitespace from the JSON ABI 105 strippedABI := strings.Map(func(r rune) rune { 106 if unicode.IsSpace(r) { 107 return -1 108 } 109 return r 110 }, abis[i]) 111 112 // Extract the call and transact methods; events, struct definitions; and sort them alphabetically 113 var ( 114 calls = make(map[string]*tmplMethod) 115 transacts = make(map[string]*tmplMethod) 116 events = make(map[string]*tmplEvent) 117 fallback *tmplMethod 118 receive *tmplMethod 119 120 // identifiers are used to detect duplicated identifiers of functions 121 // and events. For all calls, transacts and events, abigen will generate 122 // corresponding bindings. However we have to ensure there is no 123 // identifier collisions in the bindings of these categories. 124 callIdentifiers = make(map[string]bool) 125 transactIdentifiers = make(map[string]bool) 126 eventIdentifiers = make(map[string]bool) 127 ) 128 129 for _, input := range evmABI.Constructor.Inputs { 130 if hasStruct(input.Type) { 131 bindStructType[lang](input.Type, structs) 132 } 133 } 134 135 for _, original := range evmABI.Methods { 136 // Normalize the method for capital cases and non-anonymous inputs/outputs 137 normalized := original 138 normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) 139 140 // Ensure there is no duplicated identifier 141 var identifiers = callIdentifiers 142 if !original.IsConstant() { 143 identifiers = transactIdentifiers 144 } 145 if identifiers[normalizedName] { 146 return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) 147 } 148 identifiers[normalizedName] = true 149 150 normalized.Name = normalizedName 151 normalized.Inputs = make([]abi.Argument, len(original.Inputs)) 152 copy(normalized.Inputs, original.Inputs) 153 for j, input := range normalized.Inputs { 154 if input.Name == "" || isKeyWord(input.Name) { 155 normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) 156 } 157 if hasStruct(input.Type) { 158 bindStructType[lang](input.Type, structs) 159 } 160 } 161 normalized.Outputs = make([]abi.Argument, len(original.Outputs)) 162 copy(normalized.Outputs, original.Outputs) 163 for j, output := range normalized.Outputs { 164 if output.Name != "" { 165 normalized.Outputs[j].Name = capitalise(output.Name) 166 } 167 if hasStruct(output.Type) { 168 bindStructType[lang](output.Type, structs) 169 } 170 } 171 // Append the methods to the call or transact lists 172 if original.IsConstant() { 173 calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} 174 } else { 175 transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} 176 } 177 } 178 for _, original := range evmABI.Events { 179 // Skip anonymous events as they don't support explicit filtering 180 if original.Anonymous { 181 continue 182 } 183 // Normalize the event for capital cases and non-anonymous outputs 184 normalized := original 185 186 // Ensure there is no duplicated identifier 187 normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) 188 if eventIdentifiers[normalizedName] { 189 return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) 190 } 191 eventIdentifiers[normalizedName] = true 192 normalized.Name = normalizedName 193 194 used := make(map[string]bool) 195 normalized.Inputs = make([]abi.Argument, len(original.Inputs)) 196 copy(normalized.Inputs, original.Inputs) 197 for j, input := range normalized.Inputs { 198 if input.Name == "" || isKeyWord(input.Name) { 199 normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) 200 } 201 // Event is a bit special, we need to define event struct in binding, 202 // ensure there is no camel-case-style name conflict. 203 for index := 0; ; index++ { 204 if !used[capitalise(normalized.Inputs[j].Name)] { 205 used[capitalise(normalized.Inputs[j].Name)] = true 206 break 207 } 208 normalized.Inputs[j].Name = fmt.Sprintf("%s%d", normalized.Inputs[j].Name, index) 209 } 210 if hasStruct(input.Type) { 211 bindStructType[lang](input.Type, structs) 212 } 213 } 214 // Append the event to the accumulator list 215 events[original.Name] = &tmplEvent{Original: original, Normalized: normalized} 216 } 217 // Add two special fallback functions if they exist 218 if evmABI.HasFallback() { 219 fallback = &tmplMethod{Original: evmABI.Fallback} 220 } 221 if evmABI.HasReceive() { 222 receive = &tmplMethod{Original: evmABI.Receive} 223 } 224 // There is no easy way to pass arbitrary java objects to the Go side. 225 if len(structs) > 0 && lang == LangJava { 226 return "", errors.New("java binding for tuple arguments is not supported yet") 227 } 228 229 contracts[types[i]] = &tmplContract{ 230 Type: capitalise(types[i]), 231 InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""), 232 InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"), 233 DeployedBin: strings.TrimPrefix(strings.TrimSpace(deployedBytecodes[i]), "0x"), 234 Constructor: evmABI.Constructor, 235 Calls: calls, 236 Transacts: transacts, 237 Fallback: fallback, 238 Receive: receive, 239 Events: events, 240 Libraries: make(map[string]string), 241 } 242 // Function 4-byte signatures are stored in the same sequence 243 // as types, if available. 244 if len(fsigs) > i { 245 contracts[types[i]].FuncSigs = fsigs[i] 246 } 247 // Parse library references. 248 for pattern, name := range libs { 249 matched, err := regexp.Match("__\\$"+pattern+"\\$__", []byte(contracts[types[i]].InputBin)) 250 if err != nil { 251 log.Error("Could not search for pattern", "pattern", pattern, "contract", contracts[types[i]], "err", err) 252 } 253 if matched { 254 contracts[types[i]].Libraries[pattern] = name 255 // keep track that this type is a library 256 if _, ok := isLib[name]; !ok { 257 isLib[name] = struct{}{} 258 } 259 } 260 } 261 } 262 // Check if that type has already been identified as a library 263 for i := 0; i < len(types); i++ { 264 _, ok := isLib[types[i]] 265 contracts[types[i]].Library = ok 266 } 267 // Generate the contract template data content and render it 268 data := &tmplData{ 269 Package: pkg, 270 Contracts: contracts, 271 Libraries: libs, 272 Structs: structs, 273 } 274 buffer := new(bytes.Buffer) 275 276 funcs := map[string]interface{}{ 277 "bindtype": bindType[lang], 278 "bindtopictype": bindTopicType[lang], 279 "namedtype": namedType[lang], 280 "capitalise": capitalise, 281 "decapitalise": decapitalise, 282 } 283 tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang])) 284 if err := tmpl.Execute(buffer, data); err != nil { 285 return "", err 286 } 287 // For Go bindings pass the code through gofmt to clean it up 288 if lang == LangGo { 289 code, err := format.Source(buffer.Bytes()) 290 if err != nil { 291 return "", fmt.Errorf("%v\n%s", err, buffer) 292 } 293 return string(code), nil 294 } 295 // For all others just return as is for now 296 return buffer.String(), nil 297 } 298 299 // bindType is a set of type binders that convert Solidity types to some supported 300 // programming language types. 301 var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ 302 LangGo: bindTypeGo, 303 LangJava: bindTypeJava, 304 } 305 306 // bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones. 307 func bindBasicTypeGo(kind abi.Type) string { 308 switch kind.T { 309 case abi.AddressTy: 310 return "common.Address" 311 case abi.IntTy, abi.UintTy: 312 parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(kind.String()) 313 switch parts[2] { 314 case "8", "16", "32", "64": 315 return fmt.Sprintf("%sint%s", parts[1], parts[2]) 316 } 317 return "*big.Int" 318 case abi.FixedBytesTy: 319 return fmt.Sprintf("[%d]byte", kind.Size) 320 case abi.BytesTy: 321 return "[]byte" 322 case abi.FunctionTy: 323 return "[24]byte" 324 default: 325 // string, bool types 326 return kind.String() 327 } 328 } 329 330 // bindTypeGo converts solidity types to Go ones. Since there is no clear mapping 331 // from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly 332 // mapped will use an upscaled type (e.g. BigDecimal). 333 func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { 334 switch kind.T { 335 case abi.TupleTy: 336 return structs[kind.TupleRawName+kind.String()].Name 337 case abi.ArrayTy: 338 return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs) 339 case abi.SliceTy: 340 return "[]" + bindTypeGo(*kind.Elem, structs) 341 default: 342 return bindBasicTypeGo(kind) 343 } 344 } 345 346 // bindBasicTypeJava converts basic solidity types(except array, slice and tuple) to Java ones. 347 func bindBasicTypeJava(kind abi.Type) string { 348 switch kind.T { 349 case abi.AddressTy: 350 return "Address" 351 case abi.IntTy, abi.UintTy: 352 // Note that uint and int (without digits) are also matched, 353 // these are size 256, and will translate to BigInt (the default). 354 parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(kind.String()) 355 if len(parts) != 3 { 356 return kind.String() 357 } 358 // All unsigned integers should be translated to BigInt since gomobile doesn't 359 // support them. 360 if parts[1] == "u" { 361 return "BigInt" 362 } 363 364 namedSize := map[string]string{ 365 "8": "byte", 366 "16": "short", 367 "32": "int", 368 "64": "long", 369 }[parts[2]] 370 371 // default to BigInt 372 if namedSize == "" { 373 namedSize = "BigInt" 374 } 375 return namedSize 376 case abi.FixedBytesTy, abi.BytesTy: 377 return "byte[]" 378 case abi.BoolTy: 379 return "boolean" 380 case abi.StringTy: 381 return "String" 382 case abi.FunctionTy: 383 return "byte[24]" 384 default: 385 return kind.String() 386 } 387 } 388 389 // pluralizeJavaType explicitly converts multidimensional types to predefined 390 // types in go side. 391 func pluralizeJavaType(typ string) string { 392 switch typ { 393 case "boolean": 394 return "Bools" 395 case "String": 396 return "Strings" 397 case "Address": 398 return "Addresses" 399 case "byte[]": 400 return "Binaries" 401 case "BigInt": 402 return "BigInts" 403 } 404 return typ + "[]" 405 } 406 407 // bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping 408 // from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly 409 // mapped will use an upscaled type (e.g. BigDecimal). 410 func bindTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { 411 switch kind.T { 412 case abi.TupleTy: 413 return structs[kind.TupleRawName+kind.String()].Name 414 case abi.ArrayTy, abi.SliceTy: 415 return pluralizeJavaType(bindTypeJava(*kind.Elem, structs)) 416 default: 417 return bindBasicTypeJava(kind) 418 } 419 } 420 421 // bindTopicType is a set of type binders that convert Solidity types to some 422 // supported programming language topic types. 423 var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ 424 LangGo: bindTopicTypeGo, 425 LangJava: bindTopicTypeJava, 426 } 427 428 // bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same 429 // functionality as for simple types, but dynamic types get converted to hashes. 430 func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { 431 bound := bindTypeGo(kind, structs) 432 433 // todo(rjl493456442) according solidity documentation, indexed event 434 // parameters that are not value types i.e. arrays and structs are not 435 // stored directly but instead a keccak256-hash of an encoding is stored. 436 // 437 // We only convert stringS and bytes to hash, still need to deal with 438 // array(both fixed-size and dynamic-size) and struct. 439 if bound == "string" || bound == "[]byte" { 440 bound = "common.Hash" 441 } 442 return bound 443 } 444 445 // bindTopicTypeJava converts a Solidity topic type to a Java one. It is almost the same 446 // functionality as for simple types, but dynamic types get converted to hashes. 447 func bindTopicTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { 448 bound := bindTypeJava(kind, structs) 449 450 // todo(rjl493456442) according solidity documentation, indexed event 451 // parameters that are not value types i.e. arrays and structs are not 452 // stored directly but instead a keccak256-hash of an encoding is stored. 453 // 454 // We only convert strings and bytes to hash, still need to deal with 455 // array(both fixed-size and dynamic-size) and struct. 456 if bound == "String" || bound == "byte[]" { 457 bound = "Hash" 458 } 459 return bound 460 } 461 462 // bindStructType is a set of type binders that convert Solidity tuple types to some supported 463 // programming language struct definition. 464 var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ 465 LangGo: bindStructTypeGo, 466 LangJava: bindStructTypeJava, 467 } 468 469 // bindStructTypeGo converts a Solidity tuple type to a Go one and records the mapping 470 // in the given map. 471 // Notably, this function will resolve and record nested struct recursively. 472 func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { 473 switch kind.T { 474 case abi.TupleTy: 475 // We compose a raw struct name and a canonical parameter expression 476 // together here. The reason is before solidity v0.5.11, kind.TupleRawName 477 // is empty, so we use canonical parameter expression to distinguish 478 // different struct definition. From the consideration of backward 479 // compatibility, we concat these two together so that if kind.TupleRawName 480 // is not empty, it can have unique id. 481 id := kind.TupleRawName + kind.String() 482 if s, exist := structs[id]; exist { 483 return s.Name 484 } 485 var ( 486 names = make(map[string]bool) 487 fields []*tmplField 488 ) 489 for i, elem := range kind.TupleElems { 490 name := capitalise(kind.TupleRawNames[i]) 491 name = abi.ResolveNameConflict(name, func(s string) bool { return names[s] }) 492 names[name] = true 493 fields = append(fields, &tmplField{Type: bindStructTypeGo(*elem, structs), Name: name, SolKind: *elem}) 494 } 495 name := kind.TupleRawName 496 if name == "" { 497 name = fmt.Sprintf("Struct%d", len(structs)) 498 } 499 name = capitalise(name) 500 501 structs[id] = &tmplStruct{ 502 Name: name, 503 Fields: fields, 504 } 505 return name 506 case abi.ArrayTy: 507 return fmt.Sprintf("[%d]", kind.Size) + bindStructTypeGo(*kind.Elem, structs) 508 case abi.SliceTy: 509 return "[]" + bindStructTypeGo(*kind.Elem, structs) 510 default: 511 return bindBasicTypeGo(kind) 512 } 513 } 514 515 // bindStructTypeJava converts a Solidity tuple type to a Java one and records the mapping 516 // in the given map. 517 // Notably, this function will resolve and record nested struct recursively. 518 func bindStructTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { 519 switch kind.T { 520 case abi.TupleTy: 521 // We compose a raw struct name and a canonical parameter expression 522 // together here. The reason is before solidity v0.5.11, kind.TupleRawName 523 // is empty, so we use canonical parameter expression to distinguish 524 // different struct definition. From the consideration of backward 525 // compatibility, we concat these two together so that if kind.TupleRawName 526 // is not empty, it can have unique id. 527 id := kind.TupleRawName + kind.String() 528 if s, exist := structs[id]; exist { 529 return s.Name 530 } 531 var fields []*tmplField 532 for i, elem := range kind.TupleElems { 533 field := bindStructTypeJava(*elem, structs) 534 fields = append(fields, &tmplField{Type: field, Name: decapitalise(kind.TupleRawNames[i]), SolKind: *elem}) 535 } 536 name := kind.TupleRawName 537 if name == "" { 538 name = fmt.Sprintf("Class%d", len(structs)) 539 } 540 structs[id] = &tmplStruct{ 541 Name: name, 542 Fields: fields, 543 } 544 return name 545 case abi.ArrayTy, abi.SliceTy: 546 return pluralizeJavaType(bindStructTypeJava(*kind.Elem, structs)) 547 default: 548 return bindBasicTypeJava(kind) 549 } 550 } 551 552 // namedType is a set of functions that transform language specific types to 553 // named versions that may be used inside method names. 554 var namedType = map[Lang]func(string, abi.Type) string{ 555 LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") }, 556 LangJava: namedTypeJava, 557 } 558 559 // namedTypeJava converts some primitive data types to named variants that can 560 // be used as parts of method names. 561 func namedTypeJava(javaKind string, solKind abi.Type) string { 562 switch javaKind { 563 case "byte[]": 564 return "Binary" 565 case "boolean": 566 return "Bool" 567 default: 568 parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(solKind.String()) 569 if len(parts) != 4 { 570 return javaKind 571 } 572 switch parts[2] { 573 case "8", "16", "32", "64": 574 if parts[3] == "" { 575 return capitalise(fmt.Sprintf("%sint%s", parts[1], parts[2])) 576 } 577 return capitalise(fmt.Sprintf("%sint%ss", parts[1], parts[2])) 578 579 default: 580 return javaKind 581 } 582 } 583 } 584 585 // alias returns an alias of the given string based on the aliasing rules 586 // or returns itself if no rule is matched. 587 func alias(aliases map[string]string, n string) string { 588 if alias, exist := aliases[n]; exist { 589 return alias 590 } 591 return n 592 } 593 594 // methodNormalizer is a name transformer that modifies Solidity method names to 595 // conform to target language naming conventions. 596 var methodNormalizer = map[Lang]func(string) string{ 597 LangGo: abi.ToCamelCase, 598 LangJava: decapitalise, 599 } 600 601 // capitalise makes a camel-case string which starts with an upper case character. 602 var capitalise = abi.ToCamelCase 603 604 // decapitalise makes a camel-case string which starts with a lower case character. 605 func decapitalise(input string) string { 606 if len(input) == 0 { 607 return input 608 } 609 610 goForm := abi.ToCamelCase(input) 611 return strings.ToLower(goForm[:1]) + goForm[1:] 612 } 613 614 // structured checks whether a list of ABI data types has enough information to 615 // operate through a proper Go struct or if flat returns are needed. 616 func structured(args abi.Arguments) bool { 617 if len(args) < 2 { 618 return false 619 } 620 exists := make(map[string]bool) 621 for _, out := range args { 622 // If the name is anonymous, we can't organize into a struct 623 if out.Name == "" { 624 return false 625 } 626 // If the field name is empty when normalized or collides (var, Var, _var, _Var), 627 // we can't organize into a struct 628 field := capitalise(out.Name) 629 if field == "" || exists[field] { 630 return false 631 } 632 exists[field] = true 633 } 634 return true 635 } 636 637 // hasStruct returns an indicator whether the given type is struct, struct slice 638 // or struct array. 639 func hasStruct(t abi.Type) bool { 640 switch t.T { 641 case abi.SliceTy: 642 return hasStruct(*t.Elem) 643 case abi.ArrayTy: 644 return hasStruct(*t.Elem) 645 case abi.TupleTy: 646 return true 647 default: 648 return false 649 } 650 }