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 }