github.com/core-coin/go-core/v2@v2.1.9/accounts/abi/bind/bind.go (about) 1 // Copyright 2016 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core 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-core 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-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 // Package bind generates Core contract Go bindings. 18 // 19 // Detailed usage document and tutorial available on the go-core Wiki page: 20 // https://github.com/core-coin/go-core/v2/wiki/Native-DApps:-Go-bindings-to-Core-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/core-coin/go-core/v2/accounts/abi" 33 "github.com/core-coin/go-core/v2/log" 34 ) 35 36 // Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant 37 // to be used as is in client code, but rather as an intermediate struct which 38 // enforces compile time type safety and naming convention opposed to having to 39 // manually maintain hard coded strings that break on runtime. 40 func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, libs map[string]string, aliases map[string]string) (string, error) { 41 var ( 42 // contracts is the map of each individual contract requested binding 43 contracts = make(map[string]*tmplContract) 44 45 // structs is the map of all redeclared structs shared by passed contracts. 46 structs = make(map[string]*tmplStruct) 47 48 // isLib is the map used to flag each encountered library as such 49 isLib = make(map[string]struct{}) 50 ) 51 for i := 0; i < len(types); i++ { 52 // Parse the actual ABI to generate the binding for 53 cvmABI, 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; events, struct definitions; and sort them alphabetically 66 var ( 67 calls = make(map[string]*tmplMethod) 68 transacts = make(map[string]*tmplMethod) 69 events = make(map[string]*tmplEvent) 70 fallback *tmplMethod 71 receive *tmplMethod 72 73 // identifiers are used to detect duplicated identifiers of functions 74 // and events. For all calls, transacts and events, abigen will generate 75 // corresponding bindings. However we have to ensure there is no 76 // identifier collisions in the bindings of these categories. 77 callIdentifiers = make(map[string]bool) 78 transactIdentifiers = make(map[string]bool) 79 eventIdentifiers = make(map[string]bool) 80 ) 81 for _, original := range cvmABI.Methods { 82 // Normalize the method for capital cases and non-anonymous inputs/outputs 83 normalized := original 84 normalizedName := abi.ToCamelCase(alias(aliases, original.Name)) 85 // Ensure there is no duplicated identifier 86 var identifiers = callIdentifiers 87 if !original.IsConstant() { 88 identifiers = transactIdentifiers 89 } 90 if identifiers[normalizedName] { 91 return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) 92 } 93 identifiers[normalizedName] = true 94 normalized.Name = normalizedName 95 normalized.Inputs = make([]abi.Argument, len(original.Inputs)) 96 copy(normalized.Inputs, original.Inputs) 97 for j, input := range normalized.Inputs { 98 if input.Name == "" { 99 normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) 100 } 101 if hasStruct(input.Type) { 102 bindStructType(input.Type, structs) 103 } 104 } 105 normalized.Outputs = make([]abi.Argument, len(original.Outputs)) 106 copy(normalized.Outputs, original.Outputs) 107 for j, output := range normalized.Outputs { 108 if output.Name != "" { 109 normalized.Outputs[j].Name = capitalise(output.Name) 110 } 111 if hasStruct(output.Type) { 112 bindStructType(output.Type, structs) 113 } 114 } 115 // Append the methods to the call or transact lists 116 if original.IsConstant() { 117 calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} 118 } else { 119 transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} 120 } 121 } 122 for _, original := range cvmABI.Events { 123 // Skip anonymous events as they don't support explicit filtering 124 if original.Anonymous { 125 continue 126 } 127 // Normalize the event for capital cases and non-anonymous outputs 128 normalized := original 129 130 // Ensure there is no duplicated identifier 131 normalizedName := abi.ToCamelCase(alias(aliases, original.Name)) 132 if eventIdentifiers[normalizedName] { 133 return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName) 134 } 135 eventIdentifiers[normalizedName] = true 136 normalized.Name = normalizedName 137 138 normalized.Inputs = make([]abi.Argument, len(original.Inputs)) 139 copy(normalized.Inputs, original.Inputs) 140 for j, input := range normalized.Inputs { 141 if input.Name == "" { 142 normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) 143 } 144 if hasStruct(input.Type) { 145 bindStructType(input.Type, structs) 146 } 147 } 148 // Append the event to the accumulator list 149 events[original.Name] = &tmplEvent{Original: original, Normalized: normalized} 150 } 151 // Add two special fallback functions if they exist 152 if cvmABI.HasFallback() { 153 fallback = &tmplMethod{Original: cvmABI.Fallback} 154 } 155 if cvmABI.HasReceive() { 156 receive = &tmplMethod{Original: cvmABI.Receive} 157 } 158 159 contracts[types[i]] = &tmplContract{ 160 Type: capitalise(types[i]), 161 InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1), 162 InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"), 163 Constructor: cvmABI.Constructor, 164 Calls: calls, 165 Transacts: transacts, 166 Fallback: fallback, 167 Receive: receive, 168 Events: events, 169 Libraries: make(map[string]string), 170 } 171 // Function 4-byte signatures are stored in the same sequence 172 // as types, if available. 173 if len(fsigs) > i { 174 contracts[types[i]].FuncSigs = fsigs[i] 175 } 176 // Parse library references. 177 for pattern, name := range libs { 178 matched, err := regexp.Match("__\\$"+pattern+"\\$__", []byte(contracts[types[i]].InputBin)) 179 if err != nil { 180 log.Error("Could not search for pattern", "pattern", pattern, "contract", contracts[types[i]], "err", err) 181 } 182 if matched { 183 contracts[types[i]].Libraries[pattern] = name 184 // keep track that this type is a library 185 if _, ok := isLib[name]; !ok { 186 isLib[name] = struct{}{} 187 } 188 } 189 } 190 } 191 // Check if that type has already been identified as a library 192 for i := 0; i < len(types); i++ { 193 _, ok := isLib[types[i]] 194 contracts[types[i]].Library = ok 195 } 196 // Generate the contract template data content and render it 197 data := &tmplData{ 198 Package: pkg, 199 Contracts: contracts, 200 Libraries: libs, 201 Structs: structs, 202 } 203 buffer := new(bytes.Buffer) 204 205 funcs := map[string]interface{}{ 206 "bindtype": bindType, 207 "bindtopictype": bindTopicType, 208 "capitalise": capitalise, 209 "decapitalise": decapitalise, 210 } 211 tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource)) 212 if err := tmpl.Execute(buffer, data); err != nil { 213 return "", err 214 } 215 code, err := format.Source(buffer.Bytes()) 216 if err != nil { 217 return "", fmt.Errorf("%v\n%s", err, buffer) 218 } 219 return string(code), nil 220 } 221 222 // bindBasicTypeGo converts basic ylem types(except array, slice and tuple) to Go ones. 223 func bindBasicTypeGo(kind abi.Type) string { 224 switch kind.T { 225 case abi.AddressTy: 226 return "common.Address" 227 case abi.IntTy, abi.UintTy: 228 parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(kind.String()) 229 switch parts[2] { 230 case "8", "16", "32", "64": 231 return fmt.Sprintf("%sint%s", parts[1], parts[2]) 232 } 233 return "*big.Int" 234 case abi.FixedBytesTy: 235 return fmt.Sprintf("[%d]byte", kind.Size) 236 case abi.BytesTy: 237 return "[]byte" 238 case abi.FunctionTy: 239 return "[26]byte" 240 default: 241 // string, bool types 242 return kind.String() 243 } 244 } 245 246 // bindType converts ylem types to Go ones. Since there is no clear mapping 247 // from all Ylem types to Go ones (e.g. uint17), those that cannot be exactly 248 // mapped will use an upscaled type (e.g. BigDecimal). 249 func bindType(kind abi.Type, structs map[string]*tmplStruct) string { 250 switch kind.T { 251 case abi.TupleTy: 252 return structs[kind.TupleRawName+kind.String()].Name 253 case abi.ArrayTy: 254 return fmt.Sprintf("[%d]", kind.Size) + bindType(*kind.Elem, structs) 255 case abi.SliceTy: 256 return "[]" + bindType(*kind.Elem, structs) 257 default: 258 return bindBasicTypeGo(kind) 259 } 260 } 261 262 // bindTopicType converts a Ylem topic type to a Go one. It is almost the same 263 // functionality as for simple types, but dynamic types get converted to hashes. 264 func bindTopicType(kind abi.Type, structs map[string]*tmplStruct) string { 265 bound := bindType(kind, structs) 266 267 // todo(raisty) according ylem documentation, indexed event 268 // parameters that are not value types i.e. arrays and structs are not 269 // stored directly but instead a SHA3-hash of an encoding is stored. 270 // 271 // We only convert stringS and bytes to hash, still need to deal with 272 // array(both fixed-size and dynamic-size) and struct. 273 if bound == "string" || bound == "[]byte" { 274 bound = "common.Hash" 275 } 276 return bound 277 } 278 279 // bindStructType converts a Ylem tuple type to a Go one and records the mapping 280 // in the given map. 281 // Notably, this function will resolve and record nested struct recursively. 282 func bindStructType(kind abi.Type, structs map[string]*tmplStruct) string { 283 switch kind.T { 284 case abi.TupleTy: 285 // We compose a raw struct name and a canonical parameter expression 286 // together here. The reason is before ylem v0.5.11, kind.TupleRawName 287 // is empty, so we use canonical parameter expression to distinguish 288 // different struct definition. From the consideration of backward 289 // compatibility, we concat these two together so that if kind.TupleRawName 290 // is not empty, it can have unique id. 291 id := kind.TupleRawName + kind.String() 292 if s, exist := structs[id]; exist { 293 return s.Name 294 } 295 var fields []*tmplField 296 for i, elem := range kind.TupleElems { 297 field := bindStructType(*elem, structs) 298 fields = append(fields, &tmplField{Type: field, Name: capitalise(kind.TupleRawNames[i]), SolKind: *elem}) 299 } 300 name := kind.TupleRawName 301 if name == "" { 302 name = fmt.Sprintf("Struct%d", len(structs)) 303 } 304 structs[id] = &tmplStruct{ 305 Name: name, 306 Fields: fields, 307 } 308 return name 309 case abi.ArrayTy: 310 return fmt.Sprintf("[%d]", kind.Size) + bindStructType(*kind.Elem, structs) 311 case abi.SliceTy: 312 return "[]" + bindStructType(*kind.Elem, structs) 313 default: 314 return bindBasicTypeGo(kind) 315 } 316 } 317 318 // alias returns an alias of the given string based on the aliasing rules 319 // or returns itself if no rule is matched. 320 func alias(aliases map[string]string, n string) string { 321 if alias, exist := aliases[n]; exist { 322 return alias 323 } 324 return n 325 } 326 327 // capitalise makes a camel-case string which starts with an upper case character. 328 var capitalise = abi.ToCamelCase 329 330 // decapitalise makes a camel-case string which starts with a lower case character. 331 func decapitalise(input string) string { 332 if len(input) == 0 { 333 return input 334 } 335 336 goForm := abi.ToCamelCase(input) 337 return strings.ToLower(goForm[:1]) + goForm[1:] 338 } 339 340 // structured checks whether a list of ABI data types has enough information to 341 // operate through a proper Go struct or if flat returns are needed. 342 func structured(args abi.Arguments) bool { 343 if len(args) < 2 { 344 return false 345 } 346 exists := make(map[string]bool) 347 for _, out := range args { 348 // If the name is anonymous, we can't organize into a struct 349 if out.Name == "" { 350 return false 351 } 352 // If the field name is empty when normalized or collides (var, Var, _var, _Var), 353 // we can't organize into a struct 354 field := capitalise(out.Name) 355 if field == "" || exists[field] { 356 return false 357 } 358 exists[field] = true 359 } 360 return true 361 } 362 363 // hasStruct returns an indicator whether the given type is struct, struct slice 364 // or struct array. 365 func hasStruct(t abi.Type) bool { 366 switch t.T { 367 case abi.SliceTy: 368 return hasStruct(*t.Elem) 369 case abi.ArrayTy: 370 return hasStruct(*t.Elem) 371 case abi.TupleTy: 372 return true 373 default: 374 return false 375 } 376 }