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