github.com/luckypickle/go-ethereum-vet@v1.14.2/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/luckypickle/go-ethereum-vet/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts 21 package bind 22 23 import ( 24 "bytes" 25 "fmt" 26 "regexp" 27 "strings" 28 "text/template" 29 "unicode" 30 31 "github.com/luckypickle/go-ethereum-vet/accounts/abi" 32 "golang.org/x/tools/imports" 33 ) 34 35 // Lang is a target programming language selector to generate bindings for. 36 type Lang int 37 38 const ( 39 LangGo Lang = iota 40 LangJava 41 LangObjC 42 ) 43 44 // Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant 45 // to be used as is in client code, but rather as an intermediate struct which 46 // enforces compile time type safety and naming convention opposed to having to 47 // manually maintain hard coded strings that break on runtime. 48 func Bind(types []string, abis []string, bytecodes []string, pkg string, lang Lang) (string, error) { 49 // Process each individual contract requested binding 50 contracts := make(map[string]*tmplContract) 51 52 for i := 0; i < len(types); i++ { 53 // Parse the actual ABI to generate the binding for 54 evmABI, err := abi.JSON(strings.NewReader(abis[i])) 55 if err != nil { 56 return "", err 57 } 58 // Strip any whitespace from the JSON ABI 59 strippedABI := strings.Map(func(r rune) rune { 60 if unicode.IsSpace(r) { 61 return -1 62 } 63 return r 64 }, abis[i]) 65 66 // Extract the call and transact methods; events; and sort them alphabetically 67 var ( 68 calls = make(map[string]*tmplMethod) 69 transacts = make(map[string]*tmplMethod) 70 events = make(map[string]*tmplEvent) 71 ) 72 for _, original := range evmABI.Methods { 73 // Normalize the method for capital cases and non-anonymous inputs/outputs 74 normalized := original 75 normalized.Name = methodNormalizer[lang](original.Name) 76 77 normalized.Inputs = make([]abi.Argument, len(original.Inputs)) 78 copy(normalized.Inputs, original.Inputs) 79 for j, input := range normalized.Inputs { 80 if input.Name == "" { 81 normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) 82 } 83 } 84 normalized.Outputs = make([]abi.Argument, len(original.Outputs)) 85 copy(normalized.Outputs, original.Outputs) 86 for j, output := range normalized.Outputs { 87 if output.Name != "" { 88 normalized.Outputs[j].Name = capitalise(output.Name) 89 } 90 } 91 // Append the methods to the call or transact lists 92 if original.Const { 93 calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} 94 } else { 95 transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} 96 } 97 } 98 for _, original := range evmABI.Events { 99 // Skip anonymous events as they don't support explicit filtering 100 if original.Anonymous { 101 continue 102 } 103 // Normalize the event for capital cases and non-anonymous outputs 104 normalized := original 105 normalized.Name = methodNormalizer[lang](original.Name) 106 107 normalized.Inputs = make([]abi.Argument, len(original.Inputs)) 108 copy(normalized.Inputs, original.Inputs) 109 for j, input := range normalized.Inputs { 110 // Indexed fields are input, non-indexed ones are outputs 111 if input.Indexed { 112 if input.Name == "" { 113 normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) 114 } 115 } 116 } 117 // Append the event to the accumulator list 118 events[original.Name] = &tmplEvent{Original: original, Normalized: normalized} 119 } 120 contracts[types[i]] = &tmplContract{ 121 Type: capitalise(types[i]), 122 InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1), 123 InputBin: strings.TrimSpace(bytecodes[i]), 124 Constructor: evmABI.Constructor, 125 Calls: calls, 126 Transacts: transacts, 127 Events: events, 128 } 129 } 130 // Generate the contract template data content and render it 131 data := &tmplData{ 132 Package: pkg, 133 Contracts: contracts, 134 } 135 buffer := new(bytes.Buffer) 136 137 funcs := map[string]interface{}{ 138 "bindtype": bindType[lang], 139 "bindtopictype": bindTopicType[lang], 140 "namedtype": namedType[lang], 141 "capitalise": capitalise, 142 "decapitalise": decapitalise, 143 } 144 tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang])) 145 if err := tmpl.Execute(buffer, data); err != nil { 146 return "", err 147 } 148 // For Go bindings pass the code through goimports to clean it up and double check 149 if lang == LangGo { 150 code, err := imports.Process(".", buffer.Bytes(), nil) 151 if err != nil { 152 return "", fmt.Errorf("%v\n%s", err, buffer) 153 } 154 return string(code), nil 155 } 156 // For all others just return as is for now 157 return buffer.String(), nil 158 } 159 160 // bindType is a set of type binders that convert Solidity types to some supported 161 // programming language types. 162 var bindType = map[Lang]func(kind abi.Type) string{ 163 LangGo: bindTypeGo, 164 LangJava: bindTypeJava, 165 } 166 167 // Helper function for the binding generators. 168 // It reads the unmatched characters after the inner type-match, 169 // 170 // (since the inner type is a prefix of the total type declaration), 171 // looks for valid arrays (possibly a dynamic one) wrapping the inner type, 172 // and returns the sizes of these arrays. 173 // 174 // Returned array sizes are in the same order as solidity signatures; inner array size first. 175 // Array sizes may also be "", indicating a dynamic array. 176 func wrapArray(stringKind string, innerLen int, innerMapping string) (string, []string) { 177 remainder := stringKind[innerLen:] 178 //find all the sizes 179 matches := regexp.MustCompile(`\[(\d*)\]`).FindAllStringSubmatch(remainder, -1) 180 parts := make([]string, 0, len(matches)) 181 for _, match := range matches { 182 //get group 1 from the regex match 183 parts = append(parts, match[1]) 184 } 185 return innerMapping, parts 186 } 187 188 // Translates the array sizes to a Go-lang declaration of a (nested) array of the inner type. 189 // Simply returns the inner type if arraySizes is empty. 190 func arrayBindingGo(inner string, arraySizes []string) string { 191 out := "" 192 //prepend all array sizes, from outer (end arraySizes) to inner (start arraySizes) 193 for i := len(arraySizes) - 1; i >= 0; i-- { 194 out += "[" + arraySizes[i] + "]" 195 } 196 out += inner 197 return out 198 } 199 200 // bindTypeGo converts a Solidity type to a Go one. Since there is no clear mapping 201 // from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly 202 // mapped will use an upscaled type (e.g. *big.Int). 203 func bindTypeGo(kind abi.Type) string { 204 stringKind := kind.String() 205 innerLen, innerMapping := bindUnnestedTypeGo(stringKind) 206 return arrayBindingGo(wrapArray(stringKind, innerLen, innerMapping)) 207 } 208 209 // The inner function of bindTypeGo, this finds the inner type of stringKind. 210 // (Or just the type itself if it is not an array or slice) 211 // The length of the matched part is returned, with the the translated type. 212 func bindUnnestedTypeGo(stringKind string) (int, string) { 213 214 switch { 215 case strings.HasPrefix(stringKind, "address"): 216 return len("address"), "common.Address" 217 218 case strings.HasPrefix(stringKind, "bytes"): 219 parts := regexp.MustCompile(`bytes([0-9]*)`).FindStringSubmatch(stringKind) 220 return len(parts[0]), fmt.Sprintf("[%s]byte", parts[1]) 221 222 case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"): 223 parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(stringKind) 224 switch parts[2] { 225 case "8", "16", "32", "64": 226 return len(parts[0]), fmt.Sprintf("%sint%s", parts[1], parts[2]) 227 } 228 return len(parts[0]), "*big.Int" 229 230 case strings.HasPrefix(stringKind, "bool"): 231 return len("bool"), "bool" 232 233 case strings.HasPrefix(stringKind, "string"): 234 return len("string"), "string" 235 236 default: 237 return len(stringKind), stringKind 238 } 239 } 240 241 // Translates the array sizes to a Java declaration of a (nested) array of the inner type. 242 // Simply returns the inner type if arraySizes is empty. 243 func arrayBindingJava(inner string, arraySizes []string) string { 244 // Java array type declarations do not include the length. 245 return inner + strings.Repeat("[]", len(arraySizes)) 246 } 247 248 // bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping 249 // from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly 250 // mapped will use an upscaled type (e.g. BigDecimal). 251 func bindTypeJava(kind abi.Type) string { 252 stringKind := kind.String() 253 innerLen, innerMapping := bindUnnestedTypeJava(stringKind) 254 return arrayBindingJava(wrapArray(stringKind, innerLen, innerMapping)) 255 } 256 257 // The inner function of bindTypeJava, this finds the inner type of stringKind. 258 // (Or just the type itself if it is not an array or slice) 259 // The length of the matched part is returned, with the the translated type. 260 func bindUnnestedTypeJava(stringKind string) (int, string) { 261 262 switch { 263 case strings.HasPrefix(stringKind, "address"): 264 parts := regexp.MustCompile(`address(\[[0-9]*\])?`).FindStringSubmatch(stringKind) 265 if len(parts) != 2 { 266 return len(stringKind), stringKind 267 } 268 if parts[1] == "" { 269 return len("address"), "Address" 270 } 271 return len(parts[0]), "Addresses" 272 273 case strings.HasPrefix(stringKind, "bytes"): 274 parts := regexp.MustCompile(`bytes([0-9]*)`).FindStringSubmatch(stringKind) 275 if len(parts) != 2 { 276 return len(stringKind), stringKind 277 } 278 return len(parts[0]), "byte[]" 279 280 case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"): 281 //Note that uint and int (without digits) are also matched, 282 // these are size 256, and will translate to BigInt (the default). 283 parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(stringKind) 284 if len(parts) != 3 { 285 return len(stringKind), stringKind 286 } 287 288 namedSize := map[string]string{ 289 "8": "byte", 290 "16": "short", 291 "32": "int", 292 "64": "long", 293 }[parts[2]] 294 295 //default to BigInt 296 if namedSize == "" { 297 namedSize = "BigInt" 298 } 299 return len(parts[0]), namedSize 300 301 case strings.HasPrefix(stringKind, "bool"): 302 return len("bool"), "boolean" 303 304 case strings.HasPrefix(stringKind, "string"): 305 return len("string"), "String" 306 307 default: 308 return len(stringKind), stringKind 309 } 310 } 311 312 // bindTopicType is a set of type binders that convert Solidity types to some 313 // supported programming language topic types. 314 var bindTopicType = map[Lang]func(kind abi.Type) string{ 315 LangGo: bindTopicTypeGo, 316 LangJava: bindTopicTypeJava, 317 } 318 319 // bindTypeGo converts a Solidity topic type to a Go one. It is almost the same 320 // funcionality as for simple types, but dynamic types get converted to hashes. 321 func bindTopicTypeGo(kind abi.Type) string { 322 bound := bindTypeGo(kind) 323 if bound == "string" || bound == "[]byte" { 324 bound = "common.Hash" 325 } 326 return bound 327 } 328 329 // bindTypeGo converts a Solidity topic type to a Java one. It is almost the same 330 // funcionality as for simple types, but dynamic types get converted to hashes. 331 func bindTopicTypeJava(kind abi.Type) string { 332 bound := bindTypeJava(kind) 333 if bound == "String" || bound == "Bytes" { 334 bound = "Hash" 335 } 336 return bound 337 } 338 339 // namedType is a set of functions that transform language specific types to 340 // named versions that my be used inside method names. 341 var namedType = map[Lang]func(string, abi.Type) string{ 342 LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") }, 343 LangJava: namedTypeJava, 344 } 345 346 // namedTypeJava converts some primitive data types to named variants that can 347 // be used as parts of method names. 348 func namedTypeJava(javaKind string, solKind abi.Type) string { 349 switch javaKind { 350 case "byte[]": 351 return "Binary" 352 case "byte[][]": 353 return "Binaries" 354 case "string": 355 return "String" 356 case "string[]": 357 return "Strings" 358 case "boolean": 359 return "Bool" 360 case "boolean[]": 361 return "Bools" 362 case "BigInt[]": 363 return "BigInts" 364 default: 365 parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(solKind.String()) 366 if len(parts) != 4 { 367 return javaKind 368 } 369 switch parts[2] { 370 case "8", "16", "32", "64": 371 if parts[3] == "" { 372 return capitalise(fmt.Sprintf("%sint%s", parts[1], parts[2])) 373 } 374 return capitalise(fmt.Sprintf("%sint%ss", parts[1], parts[2])) 375 376 default: 377 return javaKind 378 } 379 } 380 } 381 382 // methodNormalizer is a name transformer that modifies Solidity method names to 383 // conform to target language naming concentions. 384 var methodNormalizer = map[Lang]func(string) string{ 385 LangGo: capitalise, 386 LangJava: decapitalise, 387 } 388 389 // capitalise makes a camel-case string which starts with an upper case character. 390 func capitalise(input string) string { 391 for len(input) > 0 && input[0] == '_' { 392 input = input[1:] 393 } 394 if len(input) == 0 { 395 return "" 396 } 397 return toCamelCase(strings.ToUpper(input[:1]) + input[1:]) 398 } 399 400 // decapitalise makes a camel-case string which starts with a lower case character. 401 func decapitalise(input string) string { 402 for len(input) > 0 && input[0] == '_' { 403 input = input[1:] 404 } 405 if len(input) == 0 { 406 return "" 407 } 408 return toCamelCase(strings.ToLower(input[:1]) + input[1:]) 409 } 410 411 // toCamelCase converts an under-score string to a camel-case string 412 func toCamelCase(input string) string { 413 toupper := false 414 415 result := "" 416 for k, v := range input { 417 switch { 418 case k == 0: 419 result = strings.ToUpper(string(input[0])) 420 421 case toupper: 422 result += strings.ToUpper(string(v)) 423 toupper = false 424 425 case v == '_': 426 toupper = true 427 428 default: 429 result += string(v) 430 } 431 } 432 return result 433 } 434 435 // structured checks whether a list of ABI data types has enough information to 436 // operate through a proper Go struct or if flat returns are needed. 437 func structured(args abi.Arguments) bool { 438 if len(args) < 2 { 439 return false 440 } 441 exists := make(map[string]bool) 442 for _, out := range args { 443 // If the name is anonymous, we can't organize into a struct 444 if out.Name == "" { 445 return false 446 } 447 // If the field name is empty when normalized or collides (var, Var, _var, _Var), 448 // we can't organize into a struct 449 field := capitalise(out.Name) 450 if field == "" || exists[field] { 451 return false 452 } 453 exists[field] = true 454 } 455 return true 456 }