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