github.com/ethereumproject/go-ethereum@v5.5.2+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/ethereum/go-ethereum/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/ethereumproject/go-ethereum/accounts/abi" 32 "golang.org/x/tools/imports" 33 ) 34 35 // Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant 36 // to be used as is in client code, but rather as an intermediate struct which 37 // enforces compile time type safety and naming convention opposed to having to 38 // manually maintain hard coded strings that break on runtime. 39 func Bind(types []string, abis []string, bytecodes []string, pkg string) (string, error) { 40 // Process each individual contract requested binding 41 contracts := make(map[string]*tmplContract) 42 43 for i := 0; i < len(types); i++ { 44 // Parse the actual ABI to generate the binding for 45 evmABI, err := abi.JSON(strings.NewReader(abis[i])) 46 if err != nil { 47 return "", err 48 } 49 // Strip any whitespace from the JSON ABI 50 strippedABI := strings.Map(func(r rune) rune { 51 if unicode.IsSpace(r) { 52 return -1 53 } 54 return r 55 }, abis[i]) 56 57 // Extract the call and transact methods, and sort them alphabetically 58 var ( 59 calls = make(map[string]*tmplMethod) 60 transacts = make(map[string]*tmplMethod) 61 ) 62 for _, original := range evmABI.Methods { 63 // Normalize the method for capital cases and non-anonymous inputs/outputs 64 normalized := original 65 normalized.Name = capitalise(original.Name) 66 67 normalized.Inputs = make([]abi.Argument, len(original.Inputs)) 68 copy(normalized.Inputs, original.Inputs) 69 for j, input := range normalized.Inputs { 70 if input.Name == "" { 71 normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) 72 } 73 } 74 normalized.Outputs = make([]abi.Argument, len(original.Outputs)) 75 copy(normalized.Outputs, original.Outputs) 76 for j, output := range normalized.Outputs { 77 if output.Name != "" { 78 normalized.Outputs[j].Name = capitalise(output.Name) 79 } 80 } 81 // Append the methos to the call or transact lists 82 if original.Const { 83 calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)} 84 } else { 85 transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)} 86 } 87 } 88 contracts[types[i]] = &tmplContract{ 89 Type: capitalise(types[i]), 90 InputABI: strippedABI, 91 InputBin: strings.TrimSpace(bytecodes[i]), 92 Constructor: evmABI.Constructor, 93 Calls: calls, 94 Transacts: transacts, 95 } 96 } 97 // Generate the contract template data content and render it 98 data := &tmplData{ 99 Package: pkg, 100 Contracts: contracts, 101 } 102 buffer := new(bytes.Buffer) 103 104 funcs := map[string]interface{}{ 105 "bindtype": bindType, 106 } 107 tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource)) 108 if err := tmpl.Execute(buffer, data); err != nil { 109 return "", err 110 } 111 // Pass the code through goimports to clean it up and double check 112 code, err := imports.Process("", buffer.Bytes(), nil) 113 if err != nil { 114 return "", fmt.Errorf("%v\n%s", err, buffer) 115 } 116 return string(code), nil 117 } 118 119 // bindType converts a Solidity type to a Go one. Since there is no clear mapping 120 // from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly 121 // mapped will use an upscaled type (e.g. *big.Int). 122 func bindType(kind abi.Type) string { 123 stringKind := kind.String() 124 125 switch { 126 case strings.HasPrefix(stringKind, "address"): 127 parts := regexp.MustCompile("address(\\[[0-9]*\\])?").FindStringSubmatch(stringKind) 128 if len(parts) != 2 { 129 return stringKind 130 } 131 return fmt.Sprintf("%scommon.Address", parts[1]) 132 133 case strings.HasPrefix(stringKind, "bytes"): 134 parts := regexp.MustCompile("bytes([0-9]*)(\\[[0-9]*\\])?").FindStringSubmatch(stringKind) 135 if len(parts) != 3 { 136 return stringKind 137 } 138 return fmt.Sprintf("%s[%s]byte", parts[2], parts[1]) 139 140 case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"): 141 parts := regexp.MustCompile("(u)?int([0-9]*)(\\[[0-9]*\\])?").FindStringSubmatch(stringKind) 142 if len(parts) != 4 { 143 return stringKind 144 } 145 switch parts[2] { 146 case "8", "16", "32", "64": 147 return fmt.Sprintf("%s%sint%s", parts[3], parts[1], parts[2]) 148 } 149 return fmt.Sprintf("%s*big.Int", parts[3]) 150 151 case strings.HasPrefix(stringKind, "bool") || strings.HasPrefix(stringKind, "string"): 152 parts := regexp.MustCompile("([a-z]+)(\\[[0-9]*\\])?").FindStringSubmatch(stringKind) 153 if len(parts) != 3 { 154 return stringKind 155 } 156 return fmt.Sprintf("%s%s", parts[2], parts[1]) 157 158 default: 159 return stringKind 160 } 161 } 162 163 // capitalise makes the first character of a string upper case. 164 func capitalise(input string) string { 165 return strings.ToUpper(input[:1]) + input[1:] 166 } 167 168 // structured checks whether a method has enough information to return a proper 169 // Go struct ot if flat returns are needed. 170 func structured(method abi.Method) bool { 171 if len(method.Outputs) < 2 { 172 return false 173 } 174 for _, out := range method.Outputs { 175 if out.Name == "" { 176 return false 177 } 178 } 179 return true 180 }