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