github.com/ethereum/go-ethereum@v1.16.1/accounts/abi/abigen/bindv2.go (about) 1 // Copyright 2024 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 abigen 18 19 import ( 20 "bytes" 21 "fmt" 22 "go/format" 23 "reflect" 24 "regexp" 25 "slices" 26 "sort" 27 "strings" 28 "text/template" 29 "unicode" 30 31 "github.com/ethereum/go-ethereum/accounts/abi" 32 ) 33 34 // underlyingBindType returns a string representation of the Go type 35 // that corresponds to the given ABI type, panicking if it is not a 36 // pointer. 37 func underlyingBindType(typ abi.Type) string { 38 goType := typ.GetType() 39 if goType.Kind() != reflect.Pointer { 40 panic("trying to retrieve underlying bind type of non-pointer type.") 41 } 42 return goType.Elem().String() 43 } 44 45 // isPointerType returns true if the underlying type is a pointer. 46 func isPointerType(typ abi.Type) bool { 47 return typ.GetType().Kind() == reflect.Pointer 48 } 49 50 // OLD: 51 // binder is used during the conversion of an ABI definition into Go bindings 52 // (as part of the execution of BindV2). In contrast to contractBinder, binder 53 // contains binding-generation-state that is shared between contracts: 54 // 55 // a global struct map of structs emitted by all contracts is tracked and expanded. 56 // Structs generated in the bindings are not prefixed with the contract name 57 // that uses them (to keep the generated bindings less verbose). 58 // 59 // This contrasts to other per-contract state (constructor/method/event/error, 60 // pack/unpack methods) which are guaranteed to be unique because of their 61 // association with the uniquely-named owning contract (whether prefixed in the 62 // generated symbol name, or as a member method on a contract struct). 63 // 64 // In addition, binder contains the input alias map. In BindV2, a binder is 65 // instantiated to produce a set of tmplContractV2 and tmplStruct objects from 66 // the provided ABI definition. These are used as part of the input to rendering 67 // the binding template. 68 69 // NEW: 70 // binder is used to translate an ABI definition into a set of data-structures 71 // that will be used to render the template and produce Go bindings. This can 72 // be thought of as the "backend" that sanitizes the ABI definition to a format 73 // that can be directly rendered with minimal complexity in the template. 74 // 75 // The input data to the template rendering consists of: 76 // - the set of all contracts requested for binding, each containing 77 // methods/events/errors to emit pack/unpack methods for. 78 // - the set of structures defined by the contracts, and created 79 // as part of the binding process. 80 type binder struct { 81 // contracts is the map of each individual contract requested binding. 82 // It is keyed by the contract name provided in the ABI definition. 83 contracts map[string]*tmplContractV2 84 85 // structs is the map of all emitted structs from contracts being bound. 86 // it is keyed by a unique identifier generated from the name of the owning contract 87 // and the solidity type signature of the struct 88 structs map[string]*tmplStruct 89 90 // aliases is a map for renaming instances of named events/functions/errors 91 // to specified values. it is keyed by source symbol name, and values are 92 // what the replacement name should be. 93 aliases map[string]string 94 } 95 96 // BindStructType registers the type to be emitted as a struct in the 97 // bindings. 98 func (b *binder) BindStructType(typ abi.Type) { 99 bindStructType(typ, b.structs) 100 } 101 102 // contractBinder holds state for binding of a single contract. It is a type 103 // registry for compiling maps of identifiers that will be emitted in generated 104 // bindings. 105 type contractBinder struct { 106 binder *binder 107 108 // all maps are keyed by the original (non-normalized) name of the symbol in question 109 // from the provided ABI definition. 110 calls map[string]*tmplMethod 111 events map[string]*tmplEvent 112 errors map[string]*tmplError 113 callIdentifiers map[string]bool 114 eventIdentifiers map[string]bool 115 errorIdentifiers map[string]bool 116 } 117 118 func newContractBinder(binder *binder) *contractBinder { 119 return &contractBinder{ 120 binder, 121 make(map[string]*tmplMethod), 122 make(map[string]*tmplEvent), 123 make(map[string]*tmplError), 124 make(map[string]bool), 125 make(map[string]bool), 126 make(map[string]bool), 127 } 128 } 129 130 // registerIdentifier applies alias renaming, name normalization (conversion 131 // from snake to camel-case), and registers the normalized name in the specified identifier map. 132 // It returns an error if the normalized name already exists in the map. 133 func (cb *contractBinder) registerIdentifier(identifiers map[string]bool, original string) (normalized string, err error) { 134 normalized = abi.ToCamelCase(alias(cb.binder.aliases, original)) 135 136 // Name shouldn't start with a digit. It will make the generated code invalid. 137 if len(normalized) > 0 && unicode.IsDigit(rune(normalized[0])) { 138 normalized = fmt.Sprintf("E%s", normalized) 139 normalized = abi.ResolveNameConflict(normalized, func(name string) bool { 140 _, ok := identifiers[name] 141 return ok 142 }) 143 } 144 if _, ok := identifiers[normalized]; ok { 145 return "", fmt.Errorf("duplicate symbol '%s'", normalized) 146 } 147 identifiers[normalized] = true 148 return normalized, nil 149 } 150 151 // bindMethod registers a method to be emitted in the bindings. The name, inputs 152 // and outputs are normalized. If any inputs are struct-type their structs are 153 // registered to be emitted in the bindings. Any methods that return more than 154 // one output have their results gathered into a struct. 155 func (cb *contractBinder) bindMethod(original abi.Method) error { 156 normalized := original 157 normalizedName, err := cb.registerIdentifier(cb.callIdentifiers, original.Name) 158 if err != nil { 159 return err 160 } 161 normalized.Name = normalizedName 162 163 normalized.Inputs = normalizeArgs(original.Inputs) 164 for _, input := range normalized.Inputs { 165 if hasStruct(input.Type) { 166 cb.binder.BindStructType(input.Type) 167 } 168 } 169 normalized.Outputs = normalizeArgs(original.Outputs) 170 for _, output := range normalized.Outputs { 171 if hasStruct(output.Type) { 172 cb.binder.BindStructType(output.Type) 173 } 174 } 175 176 var isStructured bool 177 // If the call returns multiple values, gather them into a struct 178 if len(normalized.Outputs) > 1 { 179 isStructured = true 180 } 181 cb.calls[original.Name] = &tmplMethod{ 182 Original: original, 183 Normalized: normalized, 184 Structured: isStructured, 185 } 186 return nil 187 } 188 189 // normalize a set of arguments by stripping underscores, giving a generic name 190 // in the case where the arg name collides with a reserved Go keyword, and finally 191 // converting to camel-case. 192 func normalizeArgs(args abi.Arguments) abi.Arguments { 193 args = slices.Clone(args) 194 used := make(map[string]bool) 195 196 for i, input := range args { 197 if isKeyWord(input.Name) { 198 args[i].Name = fmt.Sprintf("arg%d", i) 199 } 200 args[i].Name = abi.ToCamelCase(args[i].Name) 201 if args[i].Name == "" { 202 args[i].Name = fmt.Sprintf("arg%d", i) 203 } else { 204 args[i].Name = strings.ToLower(args[i].Name[:1]) + args[i].Name[1:] 205 } 206 207 for index := 0; ; index++ { 208 if !used[args[i].Name] { 209 used[args[i].Name] = true 210 break 211 } 212 args[i].Name = fmt.Sprintf("%s%d", args[i].Name, index) 213 } 214 } 215 return args 216 } 217 218 // normalizeErrorOrEventFields normalizes errors/events for emitting through 219 // bindings: Any anonymous fields are given generated names. 220 func (cb *contractBinder) normalizeErrorOrEventFields(originalInputs abi.Arguments) abi.Arguments { 221 normalizedArguments := normalizeArgs(originalInputs) 222 for _, input := range normalizedArguments { 223 if hasStruct(input.Type) { 224 cb.binder.BindStructType(input.Type) 225 } 226 } 227 return normalizedArguments 228 } 229 230 // bindEvent normalizes an event and registers it to be emitted in the bindings. 231 func (cb *contractBinder) bindEvent(original abi.Event) error { 232 // Skip anonymous events as they don't support explicit filtering 233 if original.Anonymous { 234 return nil 235 } 236 normalizedName, err := cb.registerIdentifier(cb.eventIdentifiers, original.Name) 237 if err != nil { 238 return err 239 } 240 241 normalized := original 242 normalized.Name = normalizedName 243 normalized.Inputs = cb.normalizeErrorOrEventFields(original.Inputs) 244 cb.events[original.Name] = &tmplEvent{Original: original, Normalized: normalized} 245 return nil 246 } 247 248 // bindError normalizes an error and registers it to be emitted in the bindings. 249 func (cb *contractBinder) bindError(original abi.Error) error { 250 normalizedName, err := cb.registerIdentifier(cb.errorIdentifiers, original.Name) 251 if err != nil { 252 return err 253 } 254 255 normalized := original 256 normalized.Name = normalizedName 257 normalized.Inputs = cb.normalizeErrorOrEventFields(original.Inputs) 258 cb.errors[original.Name] = &tmplError{Original: original, Normalized: normalized} 259 return nil 260 } 261 262 // parseLibraryDeps extracts references to library dependencies from the unlinked 263 // hex string deployment bytecode. 264 func parseLibraryDeps(unlinkedCode string) (res []string) { 265 reMatchSpecificPattern, err := regexp.Compile(`__\$([a-f0-9]+)\$__`) 266 if err != nil { 267 panic(err) 268 } 269 for _, match := range reMatchSpecificPattern.FindAllStringSubmatch(unlinkedCode, -1) { 270 res = append(res, match[1]) 271 } 272 return res 273 } 274 275 // iterSorted iterates the map in the lexicographic order of the keys calling 276 // onItem on each. If the callback returns an error, iteration is halted and 277 // the error is returned from iterSorted. 278 func iterSorted[V any](inp map[string]V, onItem func(string, V) error) error { 279 var sortedKeys []string 280 for key := range inp { 281 sortedKeys = append(sortedKeys, key) 282 } 283 sort.Strings(sortedKeys) 284 285 for _, key := range sortedKeys { 286 if err := onItem(key, inp[key]); err != nil { 287 return err 288 } 289 } 290 return nil 291 } 292 293 // BindV2 generates a Go wrapper around a contract ABI. This wrapper isn't meant 294 // to be used as is in client code, but rather as an intermediate struct which 295 // enforces compile time type safety and naming convention as opposed to having to 296 // manually maintain hard coded strings that break on runtime. 297 func BindV2(types []string, abis []string, bytecodes []string, pkg string, libs map[string]string, aliases map[string]string) (string, error) { 298 b := binder{ 299 contracts: make(map[string]*tmplContractV2), 300 structs: make(map[string]*tmplStruct), 301 aliases: aliases, 302 } 303 for i := 0; i < len(types); i++ { 304 // Parse the actual ABI to generate the binding for 305 evmABI, err := abi.JSON(strings.NewReader(abis[i])) 306 if err != nil { 307 return "", err 308 } 309 310 for _, input := range evmABI.Constructor.Inputs { 311 if hasStruct(input.Type) { 312 bindStructType(input.Type, b.structs) 313 } 314 } 315 316 cb := newContractBinder(&b) 317 err = iterSorted(evmABI.Methods, func(_ string, original abi.Method) error { 318 return cb.bindMethod(original) 319 }) 320 if err != nil { 321 return "", err 322 } 323 err = iterSorted(evmABI.Events, func(_ string, original abi.Event) error { 324 return cb.bindEvent(original) 325 }) 326 if err != nil { 327 return "", err 328 } 329 err = iterSorted(evmABI.Errors, func(_ string, original abi.Error) error { 330 return cb.bindError(original) 331 }) 332 if err != nil { 333 return "", err 334 } 335 b.contracts[types[i]] = newTmplContractV2(types[i], abis[i], bytecodes[i], evmABI.Constructor, cb) 336 } 337 338 invertedLibs := make(map[string]string) 339 for pattern, name := range libs { 340 invertedLibs[name] = pattern 341 } 342 data := tmplDataV2{ 343 Package: pkg, 344 Contracts: b.contracts, 345 Libraries: invertedLibs, 346 Structs: b.structs, 347 } 348 349 for typ, contract := range data.Contracts { 350 for _, depPattern := range parseLibraryDeps(contract.InputBin) { 351 data.Contracts[typ].Libraries[libs[depPattern]] = depPattern 352 } 353 } 354 buffer := new(bytes.Buffer) 355 funcs := map[string]interface{}{ 356 "bindtype": bindType, 357 "bindtopictype": bindTopicType, 358 "capitalise": abi.ToCamelCase, 359 "decapitalise": decapitalise, 360 "ispointertype": isPointerType, 361 "underlyingbindtype": underlyingBindType, 362 } 363 tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSourceV2)) 364 if err := tmpl.Execute(buffer, data); err != nil { 365 return "", err 366 } 367 // Pass the code through gofmt to clean it up 368 code, err := format.Source(buffer.Bytes()) 369 if err != nil { 370 return "", fmt.Errorf("%v\n%s", err, buffer) 371 } 372 return string(code), nil 373 }