github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/accounts/abi/bind/bind.go (about) 1 // Copyright 2016 The Spectrum Authors 2 // This file is part of the Spectrum library. 3 // 4 // The Spectrum 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 Spectrum 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 Spectrum 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 Spectrum Wiki page: 20 // https://github.com/SmartMeshFoundation/Spectrum/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 30 "github.com/SmartMeshFoundation/Spectrum/accounts/abi" 31 "golang.org/x/tools/imports" 32 ) 33 34 // Lang is a target programming language selector to generate bindings for. 35 type Lang int 36 37 const ( 38 LangGo Lang = iota 39 LangJava 40 LangObjC 41 ) 42 43 // Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant 44 // to be used as is in client code, but rather as an intermediate struct which 45 // enforces compile time type safety and naming convention opposed to having to 46 // manually maintain hard coded strings that break on runtime. 47 func Bind(types []string, abis []string, bytecodes []string, pkg string, lang Lang) (string, error) { 48 // Process each individual contract requested binding 49 contracts := make(map[string]*tmplContract) 50 51 for i := 0; i < len(types); i++ { 52 // Parse the actual ABI to generate the binding for 53 evmABI, err := abi.JSON(strings.NewReader(abis[i])) 54 if err != nil { 55 return "", err 56 } 57 // Strip any whitespace from the JSON ABI 58 //strippedABI := strings.Map(func(r rune) rune { 59 // if unicode.IsSpace(r) { 60 // return -1 61 // } 62 // return r 63 //}, abis[i]) 64 65 // Extract the call and transact methods, and sort them alphabetically 66 var ( 67 calls = make(map[string]*tmplMethod) 68 transacts = make(map[string]*tmplMethod) 69 ) 70 for _, original := range evmABI.Methods { 71 skipMethodWithUserType := false 72 for _, input := range original.Inputs { 73 if input.Type.T == abi.UserTy { 74 skipMethodWithUserType = true 75 break 76 } 77 } 78 if skipMethodWithUserType { 79 continue //skip this method 80 } 81 // Normalize the method for capital cases and non-anonymous inputs/outputs 82 normalized := original 83 normalized.Name = methodNormalizer[lang](original.Name) 84 85 normalized.Inputs = make([]abi.Argument, len(original.Inputs)) 86 copy(normalized.Inputs, original.Inputs) 87 for j, input := range normalized.Inputs { 88 if input.Name == "" { 89 normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) 90 } 91 } 92 normalized.Outputs = make([]abi.Argument, len(original.Outputs)) 93 copy(normalized.Outputs, original.Outputs) 94 for j, output := range normalized.Outputs { 95 if output.Name != "" { 96 normalized.Outputs[j].Name = capitalise(output.Name) 97 } 98 } 99 // Append the methods to the call or transact lists 100 if original.Const { 101 calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)} 102 } else { 103 transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)} 104 } 105 } 106 contracts[types[i]] = &tmplContract{ 107 Type: capitalise(types[i]), 108 InputABI: strings.Replace(abis[i], "\"", "\\\"", -1), 109 InputBin: strings.TrimSpace(bytecodes[i]), 110 Constructor: evmABI.Constructor, 111 Calls: calls, 112 Transacts: transacts, 113 } 114 } 115 // Generate the contract template data content and render it 116 data := &tmplData{ 117 Package: pkg, 118 Contracts: contracts, 119 } 120 buffer := new(bytes.Buffer) 121 122 funcs := map[string]interface{}{ 123 "bindtype": bindType[lang], 124 "namedtype": namedType[lang], 125 "capitalise": capitalise, 126 "decapitalise": decapitalise, 127 } 128 tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang])) 129 if err := tmpl.Execute(buffer, data); err != nil { 130 return "", err 131 } 132 // For Go bindings pass the code through goimports to clean it up and double check 133 if lang == LangGo { 134 code, err := imports.Process(".", buffer.Bytes(), nil) 135 if err != nil { 136 return "", fmt.Errorf("%v\n%s", err, buffer) 137 } 138 return string(code), nil 139 } 140 // For all others just return as is for now 141 return buffer.String(), nil 142 } 143 144 // bindType is a set of type binders that convert Solidity types to some supported 145 // programming language. 146 var bindType = map[Lang]func(kind abi.Type) string{ 147 LangGo: bindTypeGo, 148 LangJava: bindTypeJava, 149 } 150 151 // bindTypeGo converts a Solidity type to a Go one. Since there is no clear mapping 152 // from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly 153 // mapped will use an upscaled type (e.g. *big.Int). 154 func bindTypeGo(kind abi.Type) string { 155 stringKind := kind.String() 156 157 switch { 158 case strings.HasPrefix(stringKind, "address"): 159 parts := regexp.MustCompile(`address(\[[0-9]*\])?`).FindStringSubmatch(stringKind) 160 if len(parts) != 2 { 161 return stringKind 162 } 163 return fmt.Sprintf("%scommon.Address", parts[1]) 164 165 case strings.HasPrefix(stringKind, "bytes"): 166 parts := regexp.MustCompile(`bytes([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(stringKind) 167 if len(parts) != 3 { 168 return stringKind 169 } 170 return fmt.Sprintf("%s[%s]byte", parts[2], parts[1]) 171 172 case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"): 173 parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(stringKind) 174 if len(parts) != 4 { 175 return stringKind 176 } 177 switch parts[2] { 178 case "8", "16", "32", "64": 179 return fmt.Sprintf("%s%sint%s", parts[3], parts[1], parts[2]) 180 } 181 return fmt.Sprintf("%s*big.Int", parts[3]) 182 183 case strings.HasPrefix(stringKind, "bool") || strings.HasPrefix(stringKind, "string"): 184 parts := regexp.MustCompile(`([a-z]+)(\[[0-9]*\])?`).FindStringSubmatch(stringKind) 185 if len(parts) != 3 { 186 return stringKind 187 } 188 return fmt.Sprintf("%s%s", parts[2], parts[1]) 189 190 default: 191 return stringKind 192 } 193 } 194 195 // bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping 196 // from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly 197 // mapped will use an upscaled type (e.g. BigDecimal). 198 func bindTypeJava(kind abi.Type) string { 199 stringKind := kind.String() 200 201 switch { 202 case strings.HasPrefix(stringKind, "address"): 203 parts := regexp.MustCompile(`address(\[[0-9]*\])?`).FindStringSubmatch(stringKind) 204 if len(parts) != 2 { 205 return stringKind 206 } 207 if parts[1] == "" { 208 return fmt.Sprintf("Address") 209 } 210 return fmt.Sprintf("Addresses") 211 212 case strings.HasPrefix(stringKind, "bytes"): 213 parts := regexp.MustCompile(`bytes([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(stringKind) 214 if len(parts) != 3 { 215 return stringKind 216 } 217 if parts[2] != "" { 218 return "byte[][]" 219 } 220 return "byte[]" 221 222 case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"): 223 parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(stringKind) 224 if len(parts) != 4 { 225 return stringKind 226 } 227 switch parts[2] { 228 case "8", "16", "32", "64": 229 if parts[1] == "" { 230 if parts[3] == "" { 231 return fmt.Sprintf("int%s", parts[2]) 232 } 233 return fmt.Sprintf("int%s[]", parts[2]) 234 } 235 } 236 if parts[3] == "" { 237 return fmt.Sprintf("BigInt") 238 } 239 return fmt.Sprintf("BigInts") 240 241 case strings.HasPrefix(stringKind, "bool"): 242 parts := regexp.MustCompile(`bool(\[[0-9]*\])?`).FindStringSubmatch(stringKind) 243 if len(parts) != 2 { 244 return stringKind 245 } 246 if parts[1] == "" { 247 return fmt.Sprintf("bool") 248 } 249 return fmt.Sprintf("bool[]") 250 251 case strings.HasPrefix(stringKind, "string"): 252 parts := regexp.MustCompile(`string(\[[0-9]*\])?`).FindStringSubmatch(stringKind) 253 if len(parts) != 2 { 254 return stringKind 255 } 256 if parts[1] == "" { 257 return fmt.Sprintf("String") 258 } 259 return fmt.Sprintf("String[]") 260 261 default: 262 return stringKind 263 } 264 } 265 266 // namedType is a set of functions that transform language specific types to 267 // named versions that my be used inside method names. 268 var namedType = map[Lang]func(string, abi.Type) string{ 269 LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") }, 270 LangJava: namedTypeJava, 271 } 272 273 // namedTypeJava converts some primitive data types to named variants that can 274 // be used as parts of method names. 275 func namedTypeJava(javaKind string, solKind abi.Type) string { 276 switch javaKind { 277 case "byte[]": 278 return "Binary" 279 case "byte[][]": 280 return "Binaries" 281 case "string": 282 return "String" 283 case "string[]": 284 return "Strings" 285 case "bool": 286 return "Bool" 287 case "bool[]": 288 return "Bools" 289 case "BigInt": 290 parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(solKind.String()) 291 if len(parts) != 4 { 292 return javaKind 293 } 294 switch parts[2] { 295 case "8", "16", "32", "64": 296 if parts[3] == "" { 297 return capitalise(fmt.Sprintf("%sint%s", parts[1], parts[2])) 298 } 299 return capitalise(fmt.Sprintf("%sint%ss", parts[1], parts[2])) 300 301 default: 302 return javaKind 303 } 304 default: 305 return javaKind 306 } 307 } 308 309 // methodNormalizer is a name transformer that modifies Solidity method names to 310 // conform to target language naming concentions. 311 var methodNormalizer = map[Lang]func(string) string{ 312 LangGo: capitalise, 313 LangJava: decapitalise, 314 } 315 316 // capitalise makes the first character of a string upper case, also removing any 317 // prefixing underscores from the variable names. 318 func capitalise(input string) string { 319 for len(input) > 0 && input[0] == '_' { 320 input = input[1:] 321 } 322 if len(input) == 0 { 323 return "" 324 } 325 return strings.ToUpper(input[:1]) + input[1:] 326 } 327 328 // decapitalise makes the first character of a string lower case. 329 func decapitalise(input string) string { 330 return strings.ToLower(input[:1]) + input[1:] 331 } 332 333 // structured checks whether a method has enough information to return a proper 334 // Go struct or if flat returns are needed. 335 func structured(method abi.Method) bool { 336 if len(method.Outputs) < 2 { 337 return false 338 } 339 exists := make(map[string]bool) 340 for _, out := range method.Outputs { 341 // If the name is anonymous, we can't organize into a struct 342 if out.Name == "" { 343 return false 344 } 345 // If the field name is empty when normalized or collides (var, Var, _var, _Var), 346 // we can't organize into a struct 347 field := capitalise(out.Name) 348 if field == "" || exists[field] { 349 return false 350 } 351 exists[field] = true 352 } 353 return true 354 }