github.com/abschain-develop/go-abs@v2.0.3+incompatible/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/abschain-develop/go-abs/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts 21 package bind 22 23 import ( 24 "bytes" 25 "fmt" 26 "go/format" 27 "regexp" 28 "strings" 29 "text/template" 30 "unicode" 31 32 "github.com/abschain-develop/go-abs/accounts/abi" 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 gofmt to clean it up 149 if lang == LangGo { 150 code, err := format.Source(buffer.Bytes()) 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 // (since the inner type is a prefix of the total type declaration), 170 // looks for valid arrays (possibly a dynamic one) wrapping the inner type, 171 // and returns the sizes of these arrays. 172 // 173 // Returned array sizes are in the same order as solidity signatures; inner array size first. 174 // Array sizes may also be "", indicating a dynamic array. 175 func wrapArray(stringKind string, innerLen int, innerMapping string) (string, []string) { 176 remainder := stringKind[innerLen:] 177 //find all the sizes 178 matches := regexp.MustCompile(`\[(\d*)\]`).FindAllStringSubmatch(remainder, -1) 179 parts := make([]string, 0, len(matches)) 180 for _, match := range matches { 181 //get group 1 from the regex match 182 parts = append(parts, match[1]) 183 } 184 return innerMapping, parts 185 } 186 187 // Translates the array sizes to a Go-lang declaration of a (nested) array of the inner type. 188 // Simply returns the inner type if arraySizes is empty. 189 func arrayBindingGo(inner string, arraySizes []string) string { 190 out := "" 191 //prepend all array sizes, from outer (end arraySizes) to inner (start arraySizes) 192 for i := len(arraySizes) - 1; i >= 0; i-- { 193 out += "[" + arraySizes[i] + "]" 194 } 195 out += inner 196 return out 197 } 198 199 // bindTypeGo converts a Solidity type to a Go one. Since there is no clear mapping 200 // from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly 201 // mapped will use an upscaled type (e.g. *big.Int). 202 func bindTypeGo(kind abi.Type) string { 203 stringKind := kind.String() 204 innerLen, innerMapping := bindUnnestedTypeGo(stringKind) 205 return arrayBindingGo(wrapArray(stringKind, innerLen, innerMapping)) 206 } 207 208 // The inner function of bindTypeGo, this finds the inner type of stringKind. 209 // (Or just the type itself if it is not an array or slice) 210 // The length of the matched part is returned, with the translated type. 211 func bindUnnestedTypeGo(stringKind string) (int, string) { 212 213 switch { 214 case strings.HasPrefix(stringKind, "address"): 215 return len("address"), "common.Address" 216 217 case strings.HasPrefix(stringKind, "bytes"): 218 parts := regexp.MustCompile(`bytes([0-9]*)`).FindStringSubmatch(stringKind) 219 return len(parts[0]), fmt.Sprintf("[%s]byte", parts[1]) 220 221 case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"): 222 parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(stringKind) 223 switch parts[2] { 224 case "8", "16", "32", "64": 225 return len(parts[0]), fmt.Sprintf("%sint%s", parts[1], parts[2]) 226 } 227 return len(parts[0]), "*big.Int" 228 229 case strings.HasPrefix(stringKind, "bool"): 230 return len("bool"), "bool" 231 232 case strings.HasPrefix(stringKind, "string"): 233 return len("string"), "string" 234 235 default: 236 return len(stringKind), stringKind 237 } 238 } 239 240 // Translates the array sizes to a Java declaration of a (nested) array of the inner type. 241 // Simply returns the inner type if arraySizes is empty. 242 func arrayBindingJava(inner string, arraySizes []string) string { 243 // Java array type declarations do not include the length. 244 return inner + strings.Repeat("[]", len(arraySizes)) 245 } 246 247 // bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping 248 // from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly 249 // mapped will use an upscaled type (e.g. BigDecimal). 250 func bindTypeJava(kind abi.Type) string { 251 stringKind := kind.String() 252 innerLen, innerMapping := bindUnnestedTypeJava(stringKind) 253 return arrayBindingJava(wrapArray(stringKind, innerLen, innerMapping)) 254 } 255 256 // The inner function of bindTypeJava, this finds the inner type of stringKind. 257 // (Or just the type itself if it is not an array or slice) 258 // The length of the matched part is returned, with the translated type. 259 func bindUnnestedTypeJava(stringKind string) (int, string) { 260 261 switch { 262 case strings.HasPrefix(stringKind, "address"): 263 parts := regexp.MustCompile(`address(\[[0-9]*\])?`).FindStringSubmatch(stringKind) 264 if len(parts) != 2 { 265 return len(stringKind), stringKind 266 } 267 if parts[1] == "" { 268 return len("address"), "Address" 269 } 270 return len(parts[0]), "Addresses" 271 272 case strings.HasPrefix(stringKind, "bytes"): 273 parts := regexp.MustCompile(`bytes([0-9]*)`).FindStringSubmatch(stringKind) 274 if len(parts) != 2 { 275 return len(stringKind), stringKind 276 } 277 return len(parts[0]), "byte[]" 278 279 case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"): 280 //Note that uint and int (without digits) are also matched, 281 // these are size 256, and will translate to BigInt (the default). 282 parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(stringKind) 283 if len(parts) != 3 { 284 return len(stringKind), stringKind 285 } 286 287 namedSize := map[string]string{ 288 "8": "byte", 289 "16": "short", 290 "32": "int", 291 "64": "long", 292 }[parts[2]] 293 294 //default to BigInt 295 if namedSize == "" { 296 namedSize = "BigInt" 297 } 298 return len(parts[0]), namedSize 299 300 case strings.HasPrefix(stringKind, "bool"): 301 return len("bool"), "boolean" 302 303 case strings.HasPrefix(stringKind, "string"): 304 return len("string"), "String" 305 306 default: 307 return len(stringKind), stringKind 308 } 309 } 310 311 // bindTopicType is a set of type binders that convert Solidity types to some 312 // supported programming language topic types. 313 var bindTopicType = map[Lang]func(kind abi.Type) string{ 314 LangGo: bindTopicTypeGo, 315 LangJava: bindTopicTypeJava, 316 } 317 318 // bindTypeGo converts a Solidity topic type to a Go one. It is almost the same 319 // funcionality as for simple types, but dynamic types get converted to hashes. 320 func bindTopicTypeGo(kind abi.Type) string { 321 bound := bindTypeGo(kind) 322 if bound == "string" || bound == "[]byte" { 323 bound = "common.Hash" 324 } 325 return bound 326 } 327 328 // bindTypeGo converts a Solidity topic type to a Java one. It is almost the same 329 // funcionality as for simple types, but dynamic types get converted to hashes. 330 func bindTopicTypeJava(kind abi.Type) string { 331 bound := bindTypeJava(kind) 332 if bound == "String" || bound == "Bytes" { 333 bound = "Hash" 334 } 335 return bound 336 } 337 338 // namedType is a set of functions that transform language specific types to 339 // named versions that my be used inside method names. 340 var namedType = map[Lang]func(string, abi.Type) string{ 341 LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") }, 342 LangJava: namedTypeJava, 343 } 344 345 // namedTypeJava converts some primitive data types to named variants that can 346 // be used as parts of method names. 347 func namedTypeJava(javaKind string, solKind abi.Type) string { 348 switch javaKind { 349 case "byte[]": 350 return "Binary" 351 case "byte[][]": 352 return "Binaries" 353 case "string": 354 return "String" 355 case "string[]": 356 return "Strings" 357 case "boolean": 358 return "Bool" 359 case "boolean[]": 360 return "Bools" 361 case "BigInt[]": 362 return "BigInts" 363 default: 364 parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(solKind.String()) 365 if len(parts) != 4 { 366 return javaKind 367 } 368 switch parts[2] { 369 case "8", "16", "32", "64": 370 if parts[3] == "" { 371 return capitalise(fmt.Sprintf("%sint%s", parts[1], parts[2])) 372 } 373 return capitalise(fmt.Sprintf("%sint%ss", parts[1], parts[2])) 374 375 default: 376 return javaKind 377 } 378 } 379 } 380 381 // methodNormalizer is a name transformer that modifies Solidity method names to 382 // conform to target language naming concentions. 383 var methodNormalizer = map[Lang]func(string) string{ 384 LangGo: abi.ToCamelCase, 385 LangJava: decapitalise, 386 } 387 388 // capitalise makes a camel-case string which starts with an upper case character. 389 func capitalise(input string) string { 390 return abi.ToCamelCase(input) 391 } 392 393 // decapitalise makes a camel-case string which starts with a lower case character. 394 func decapitalise(input string) string { 395 if len(input) == 0 { 396 return input 397 } 398 399 goForm := abi.ToCamelCase(input) 400 return strings.ToLower(goForm[:1]) + goForm[1:] 401 } 402 403 // structured checks whether a list of ABI data types has enough information to 404 // operate through a proper Go struct or if flat returns are needed. 405 func structured(args abi.Arguments) bool { 406 if len(args) < 2 { 407 return false 408 } 409 exists := make(map[string]bool) 410 for _, out := range args { 411 // If the name is anonymous, we can't organize into a struct 412 if out.Name == "" { 413 return false 414 } 415 // If the field name is empty when normalized or collides (var, Var, _var, _Var), 416 // we can't organize into a struct 417 field := capitalise(out.Name) 418 if field == "" || exists[field] { 419 return false 420 } 421 exists[field] = true 422 } 423 return true 424 }