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