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  }