github.com/ava-labs/subnet-evm@v0.6.4/accounts/abi/bind/precompilebind/precompile_bind.go (about)

     1  // (c) 2019-2020, Ava Labs, Inc.
     2  //
     3  // This file is a derived work, based on the go-ethereum library whose original
     4  // notices appear below.
     5  //
     6  // It is distributed under a license compatible with the licensing terms of the
     7  // original code from which it is derived.
     8  //
     9  // Much love to the original authors for their work.
    10  // **********
    11  // Copyright 2016 The go-ethereum Authors
    12  // This file is part of the go-ethereum library.
    13  //
    14  // The go-ethereum library is free software: you can redistribute it and/or modify
    15  // it under the terms of the GNU Lesser General Public License as published by
    16  // the Free Software Foundation, either version 3 of the License, or
    17  // (at your option) any later version.
    18  //
    19  // The go-ethereum library is distributed in the hope that it will be useful,
    20  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    21  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    22  // GNU Lesser General Public License for more details.
    23  //
    24  // You should have received a copy of the GNU Lesser General Public License
    25  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    26  
    27  // Package bind generates Ethereum contract Go bindings.
    28  //
    29  // Detailed usage document and tutorial available on the go-ethereum Wiki page:
    30  // https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts
    31  package precompilebind
    32  
    33  import (
    34  	"errors"
    35  	"fmt"
    36  	"strings"
    37  
    38  	"github.com/ava-labs/subnet-evm/accounts/abi"
    39  	"github.com/ava-labs/subnet-evm/accounts/abi/bind"
    40  	"github.com/ava-labs/subnet-evm/precompile/allowlist"
    41  )
    42  
    43  var errNoAnonymousEvent = errors.New("event type must not be anonymous")
    44  
    45  const (
    46  	ContractFileName     = "contract.go"
    47  	ConfigFileName       = "config.go"
    48  	ModuleFileName       = "module.go"
    49  	EventFileName        = "event.go"
    50  	ContractTestFileName = "contract_test.go"
    51  	ConfigTestFileName   = "config_test.go"
    52  )
    53  
    54  type PrecompileBindFile struct {
    55  	// FileName is the name of the file to be generated.
    56  	FileName string
    57  	// Content is the content of the file to be generated.
    58  	Content string
    59  	// IsTest indicates whether the file is a test file.
    60  	IsTest bool
    61  }
    62  
    63  func NewPrecompileBindFile(fileName string, content string, isTest bool) PrecompileBindFile {
    64  	return PrecompileBindFile{
    65  		FileName: fileName,
    66  		Content:  content,
    67  		IsTest:   isTest,
    68  	}
    69  }
    70  
    71  // PrecompileBind generates a Go binding for a precompiled contract. It returns a slice of
    72  // PrecompileBindFile structs containing the file name and its contents.
    73  func PrecompileBind(types []string, abiData string, bytecodes []string, fsigs []map[string]string, pkg string, lang bind.Lang, libs map[string]string, aliases map[string]string, abifilename string, generateTests bool) ([]PrecompileBindFile, error) {
    74  	// create hooks
    75  	configHook := createPrecompileHook(abifilename, tmplSourcePrecompileConfigGo)
    76  	contractHook := createPrecompileHook(abifilename, tmplSourcePrecompileContractGo)
    77  	moduleHook := createPrecompileHook(abifilename, tmplSourcePrecompileModuleGo)
    78  	eventHook := createPrecompileHook(abifilename, tmplSourcePrecompileEventGo)
    79  	configTestHook := createPrecompileHook(abifilename, tmplSourcePrecompileConfigTestGo)
    80  	contractTestHook := createPrecompileHook(abifilename, tmplSourcePrecompileContractTestGo)
    81  
    82  	if err := verifyABI(abiData); err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	abis := []string{abiData}
    87  
    88  	configBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, configHook)
    89  	if err != nil {
    90  		return nil, fmt.Errorf("failed to generate config binding: %w", err)
    91  	}
    92  	contractBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, contractHook)
    93  	if err != nil {
    94  		return nil, fmt.Errorf("failed to generate contract binding: %w", err)
    95  	}
    96  	moduleBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, moduleHook)
    97  	if err != nil {
    98  		return nil, fmt.Errorf("failed to generate module binding: %w", err)
    99  	}
   100  	eventBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, eventHook)
   101  	if err != nil {
   102  		return nil, fmt.Errorf("failed to generate event binding: %w", err)
   103  	}
   104  
   105  	var result []PrecompileBindFile
   106  	result = append(result, NewPrecompileBindFile(ConfigFileName, configBind, false))
   107  	result = append(result, NewPrecompileBindFile(ContractFileName, contractBind, false))
   108  	result = append(result, NewPrecompileBindFile(ModuleFileName, moduleBind, false))
   109  	result = append(result, NewPrecompileBindFile(EventFileName, eventBind, false))
   110  
   111  	if generateTests {
   112  		configTestBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, configTestHook)
   113  		if err != nil {
   114  			return nil, fmt.Errorf("failed to generate config test binding: %w", err)
   115  		}
   116  		result = append(result, NewPrecompileBindFile(ConfigTestFileName, configTestBind, true))
   117  
   118  		contractTestBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, contractTestHook)
   119  		if err != nil {
   120  			return nil, fmt.Errorf("failed to generate contract test binding: %w", err)
   121  		}
   122  		result = append(result, NewPrecompileBindFile(ContractTestFileName, contractTestBind, true))
   123  	}
   124  
   125  	return result, nil
   126  }
   127  
   128  // createPrecompileHook creates a bind hook for precompiled contracts.
   129  func createPrecompileHook(abifilename string, template string) bind.BindHook {
   130  	return func(lang bind.Lang, pkg string, types []string, contracts map[string]*bind.TmplContract, structs map[string]*bind.TmplStruct) (interface{}, string, error) {
   131  		// verify first
   132  		if lang != bind.LangGo {
   133  			return nil, "", errors.New("only GoLang binding for precompiled contracts is supported yet")
   134  		}
   135  
   136  		if len(types) != 1 {
   137  			return nil, "", errors.New("cannot generate more than 1 contract")
   138  		}
   139  		funcs := make(map[string]*bind.TmplMethod)
   140  
   141  		contract := contracts[types[0]]
   142  
   143  		for k, v := range contract.Transacts {
   144  			funcs[k] = v
   145  		}
   146  
   147  		for k, v := range contract.Calls {
   148  			funcs[k] = v
   149  		}
   150  		isAllowList := allowListEnabled(funcs)
   151  		if isAllowList {
   152  			// these functions are not needed for binded contract.
   153  			// AllowList struct can provide the same functionality,
   154  			// so we don't need to generate them.
   155  			for key := range allowlist.AllowListABI.Methods {
   156  				delete(funcs, key)
   157  			}
   158  			for events := range allowlist.AllowListABI.Events {
   159  				delete(contract.Events, events)
   160  			}
   161  		}
   162  
   163  		precompileContract := &tmplPrecompileContract{
   164  			TmplContract: contract,
   165  			AllowList:    isAllowList,
   166  			Funcs:        funcs,
   167  			ABIFilename:  abifilename,
   168  		}
   169  
   170  		data := &tmplPrecompileData{
   171  			Contract: precompileContract,
   172  			Structs:  structs,
   173  			Package:  pkg,
   174  		}
   175  		return data, template, nil
   176  	}
   177  }
   178  
   179  func allowListEnabled(funcs map[string]*bind.TmplMethod) bool {
   180  	for key := range allowlist.AllowListABI.Methods {
   181  		if _, ok := funcs[key]; !ok {
   182  			return false
   183  		}
   184  	}
   185  	return true
   186  }
   187  
   188  func verifyABI(abiData string) error {
   189  	// check abi first
   190  	evmABI, err := abi.JSON(strings.NewReader(abiData))
   191  	if err != nil {
   192  		return err
   193  	}
   194  	if len(evmABI.Methods) == 0 {
   195  		return errors.New("no ABI methods found")
   196  	}
   197  
   198  	for _, event := range evmABI.Events {
   199  		if event.Anonymous {
   200  			return fmt.Errorf("%w: %s", errNoAnonymousEvent, event.Name)
   201  		}
   202  		eventNames := make(map[string]bool)
   203  		for _, arg := range event.Inputs {
   204  			if bind.IsKeyWord(arg.Name) {
   205  				return fmt.Errorf("event input name %s is a keyword", arg.Name)
   206  			}
   207  			name := abi.ToCamelCase(arg.Name)
   208  			if eventNames[name] {
   209  				return fmt.Errorf("normalized event input name is duplicated: %s", name)
   210  			}
   211  			eventNames[name] = true
   212  		}
   213  	}
   214  
   215  	for _, method := range evmABI.Methods {
   216  		names := make(map[string]bool)
   217  		for _, input := range method.Inputs {
   218  			if bind.IsKeyWord(input.Name) {
   219  				return fmt.Errorf("input name %s is a keyword", input.Name)
   220  			}
   221  			name := abi.ToCamelCase(input.Name)
   222  			if names[name] {
   223  				return fmt.Errorf("normalized input name is duplicated: %s", name)
   224  			}
   225  			names[name] = true
   226  		}
   227  		names = make(map[string]bool)
   228  		for _, output := range method.Outputs {
   229  			if output.Name == "" {
   230  				return fmt.Errorf("ABI outputs for %s require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs", method.Name)
   231  			}
   232  			if bind.IsKeyWord(output.Name) {
   233  				return fmt.Errorf("output name %s is a keyword", output.Name)
   234  			}
   235  			name := abi.ToCamelCase(output.Name)
   236  			if names[name] {
   237  				return fmt.Errorf("normalized output name is duplicated: %s", name)
   238  			}
   239  			names[name] = true
   240  		}
   241  	}
   242  
   243  	return nil
   244  }