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