diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 82d5be5488..9dbaed10c9 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -51,6 +51,8 @@ const ( readAllowListFuncKey = "readAllowList" ) +type BindHook func(lang Lang, types []string, contracts map[string]*tmplContract, structs map[string]*tmplStruct) (data interface{}, templateSource string, err error) + // Lang is a target programming language selector to generate bindings for. type Lang int @@ -101,7 +103,11 @@ func isKeyWord(arg string) bool { // to be used as is in client code, but rather as an intermediate struct which // enforces compile time type safety and naming convention opposed to having to // manually maintain hard coded strings that break on runtime. -func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string, isPrecompile bool) (string, error) { +func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) { + return BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, nil) +} + +func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string, bindHook BindHook) (string, error) { var ( // contracts is the map of each individual contract requested binding contracts = make(map[string]*tmplContract) @@ -178,11 +184,6 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] normalized.Outputs = make([]abi.Argument, len(original.Outputs)) copy(normalized.Outputs, original.Outputs) for j, output := range normalized.Outputs { - if isPrecompile { - if output.Name == "" { - 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", normalized.Name) - } - } if output.Name != "" { normalized.Outputs[j].Name = capitalise(output.Name) } @@ -291,19 +292,14 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] templateSource string ) - // Generate the contract template data according to contract type (precompile/non) - if isPrecompile { - if lang != LangGo { - return "", errors.New("only GoLang binding for precompiled contracts is supported yet") - } - - if len(contracts) != 1 { - return "", errors.New("cannot generate more than 1 contract") + // Generate the contract template data according to hook + if bindHook != nil { + var err error + data, templateSource, err = bindHook(lang, types, contracts, structs) + if err != nil { + return "", err } - precompileType := types[0] - firstContract := contracts[precompileType] - data, templateSource = createPrecompileDataAndTemplate(firstContract, structs) - } else { + } else { // default to generate contract binding templateSource = tmplSource[lang] data = &tmplData{ Package: pkg, @@ -710,45 +706,3 @@ func hasStruct(t abi.Type) bool { return false } } - -func createPrecompileDataAndTemplate(contract *tmplContract, structs map[string]*tmplStruct) (interface{}, string) { - funcs := make(map[string]*tmplMethod) - - for k, v := range contract.Transacts { - funcs[k] = v - } - - for k, v := range contract.Calls { - funcs[k] = v - } - isAllowList := allowListEnabled(funcs) - if isAllowList { - // remove these functions as we will directly inherit AllowList - delete(funcs, readAllowListFuncKey) - delete(funcs, setAdminFuncKey) - delete(funcs, setEnabledFuncKey) - delete(funcs, setNoneFuncKey) - } - - precompileContract := &tmplPrecompileContract{ - tmplContract: contract, - AllowList: isAllowList, - Funcs: funcs, - } - - data := &tmplPrecompileData{ - Contract: precompileContract, - Structs: structs, - } - return data, tmplSourcePrecompileGo -} - -func allowListEnabled(funcs map[string]*tmplMethod) bool { - keys := []string{readAllowListFuncKey, setAdminFuncKey, setEnabledFuncKey, setNoneFuncKey} - for _, key := range keys { - if _, ok := funcs[key]; !ok { - return false - } - } - return true -} diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 1e1a7c538b..78a6714e3d 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -2099,7 +2099,7 @@ func golangBindings(t *testing.T, overload bool) { types = []string{tt.name} } // Generate the binding and create a Go source file in the workspace - bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases, false) + bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases) if err != nil { t.Fatalf("test %d: failed to generate binding: %v", i, err) } @@ -2529,7 +2529,7 @@ public class Test { }, } for i, c := range cases { - binding, err := Bind([]string{c.name}, []string{c.abi}, []string{c.bytecode}, nil, "bindtest", LangJava, nil, nil, false) + binding, err := Bind([]string{c.name}, []string{c.abi}, []string{c.bytecode}, nil, "bindtest", LangJava, nil, nil) if err != nil { t.Fatalf("test %d: failed to generate binding: %v", i, err) } diff --git a/accounts/abi/bind/precompile_bind.go b/accounts/abi/bind/precompile_bind.go new file mode 100644 index 0000000000..ddc940eedc --- /dev/null +++ b/accounts/abi/bind/precompile_bind.go @@ -0,0 +1,109 @@ +// (c) 2019-2020, Ava Labs, Inc. +// +// This file is a derived work, based on the go-ethereum library whose original +// notices appear below. +// +// It is distributed under a license compatible with the licensing terms of the +// original code from which it is derived. +// +// Much love to the original authors for their work. +// ********** +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package bind generates Ethereum contract Go bindings. +// +// Detailed usage document and tutorial available on the go-ethereum Wiki page: +// https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts +package bind + +import ( + "errors" + "fmt" +) + +func PrecompileBind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string, abifilename string) (string, error) { + // create hook + var createPrecompileFunc BindHook = func(lang Lang, types []string, contracts map[string]*tmplContract, structs map[string]*tmplStruct) (interface{}, string, error) { + // verify first + if lang != LangGo { + return nil, "", errors.New("only GoLang binding for precompiled contracts is supported yet") + } + + if len(types) != 1 { + return nil, "", errors.New("cannot generate more than 1 contract") + } + funcs := make(map[string]*tmplMethod) + + contract := contracts[types[0]] + + for k, v := range contract.Transacts { + if err := checkOutputName(*v); err != nil { + return nil, "", err + } + funcs[k] = v + } + + for k, v := range contract.Calls { + if err := checkOutputName(*v); err != nil { + return nil, "", err + } + funcs[k] = v + } + isAllowList := allowListEnabled(funcs) + if isAllowList { + // remove these functions as we will directly inherit AllowList + delete(funcs, readAllowListFuncKey) + delete(funcs, setAdminFuncKey) + delete(funcs, setEnabledFuncKey) + delete(funcs, setNoneFuncKey) + } + + precompileContract := &tmplPrecompileContract{ + tmplContract: contract, + AllowList: isAllowList, + Funcs: funcs, + ABIFilename: abifilename, + } + + data := &tmplPrecompileData{ + Contract: precompileContract, + Structs: structs, + } + return data, tmplSourcePrecompileGo, nil + } + + return BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, createPrecompileFunc) +} + +func allowListEnabled(funcs map[string]*tmplMethod) bool { + keys := []string{readAllowListFuncKey, setAdminFuncKey, setEnabledFuncKey, setNoneFuncKey} + for _, key := range keys { + if _, ok := funcs[key]; !ok { + return false + } + } + return true +} + +func checkOutputName(method tmplMethod) error { + for _, output := range method.Original.Outputs { + if output.Name == "" { + 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.Original.Name) + } + } + return nil +} diff --git a/accounts/abi/bind/bind_precompile_test.go b/accounts/abi/bind/precompile_bind_test.go similarity index 96% rename from accounts/abi/bind/bind_precompile_test.go rename to accounts/abi/bind/precompile_bind_test.go index 03cbcebc09..b5cab4b257 100644 --- a/accounts/abi/bind/bind_precompile_test.go +++ b/accounts/abi/bind/precompile_bind_test.go @@ -96,7 +96,7 @@ func golangBindingsFailure(t *testing.T) { for i, tt := range bindFailedTests { t.Run(tt.name, func(t *testing.T) { // Generate the binding - _, err := Bind([]string{tt.name}, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases, true) + _, err := PrecompileBind([]string{tt.name}, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases, "") if err == nil { t.Fatalf("test %d: no error occurred but was expected", i) } diff --git a/accounts/abi/bind/precompile_template.go b/accounts/abi/bind/precompile_template.go index 58c96f35ab..2f4cd39ad1 100644 --- a/accounts/abi/bind/precompile_template.go +++ b/accounts/abi/bind/precompile_template.go @@ -11,8 +11,9 @@ type tmplPrecompileData struct { // tmplPrecompileContract contains the data needed to generate an individual contract binding. type tmplPrecompileContract struct { *tmplContract - AllowList bool // Indicator whether the contract uses AllowList precompile - Funcs map[string]*tmplMethod // Contract functions that include both Calls + Transacts in tmplContract + AllowList bool // Indicator whether the contract uses AllowList precompile + Funcs map[string]*tmplMethod // Contract functions that include both Calls + Transacts in tmplContract + ABIFilename string // Path to the ABI file } // tmplSourcePrecompileGo is the Go precompiled source template. @@ -53,6 +54,8 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/vmerrs" + _ "embed" + "github.com/ethereum/go-ethereum/common" ) @@ -63,9 +66,6 @@ const ( {{- if .Contract.Fallback}} {{.Contract.Type}}FallbackGasCost uint64 = 0 // SET A GAS COST LESS THAN 2300 HERE {{- end}} - - // {{.Contract.Type}}RawABI contains the raw ABI of {{.Contract.Type}} contract. - {{.Contract.Type}}RawABI = "{{.Contract.InputABI}}" ) // CUSTOM CODE STARTS HERE @@ -94,6 +94,13 @@ var ( Err{{.Contract.Type}}CannotFallback = errors.New("non-enabled cannot call fallback function") {{- end}} + // {{.Contract.Type}}RawABI contains the raw ABI of {{.Contract.Type}} contract. + {{- if .Contract.ABIFilename | eq ""}} + {{.Contract.Type}}RawABI = "{{.Contract.InputABI}}" + {{- else}} + //go:embed {{.Contract.ABIFilename}} + {{.Contract.Type}}RawABI string + {{- end}} {{.Contract.Type}}ABI abi.ABI // will be initialized by init function {{.Contract.Type}}Precompile StatefulPrecompiledContract // will be initialized by init function diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go index 5efc6c95f2..08900279d0 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -232,7 +232,7 @@ func abigen(c *cli.Context) error { } } // Generate the contract binding - code, err := bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs, aliases, false) + code, err := bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs, aliases) if err != nil { utils.Fatalf("Failed to generate ABI binding: %v", err) } diff --git a/cmd/precompilegen/main.go b/cmd/precompilegen/main.go index 99aed93c52..454a560bc5 100644 --- a/cmd/precompilegen/main.go +++ b/cmd/precompilegen/main.go @@ -124,21 +124,35 @@ func precompilegen(c *cli.Context) error { kind = strings.TrimSpace(kind) } types = append(types, kind) - + outFlagSet := c.IsSet(outFlag.Name) + outFlag := c.String(outFlag.Name) + abifilename := "" + abipath := "" + // we should not generate the abi file if output is set to stdout + if outFlagSet { + // get file name from the output path + pathNoExt := strings.TrimSuffix(outFlag, filepath.Ext(outFlag)) + abipath = pathNoExt + ".abi" + abifilename = filepath.Base(abipath) + } // Generate the contract precompile - code, err := bind.Bind(types, abis, bins, sigs, pkg, lang, libs, aliases, true) + code, err := bind.PrecompileBind(types, abis, bins, sigs, pkg, lang, libs, aliases, abifilename) if err != nil { utils.Fatalf("Failed to generate ABI precompile: %v", err) } // Either flush it out to a file or display on the standard output - if !c.IsSet(outFlag.Name) { + if !outFlagSet { fmt.Printf("%s\n", code) return nil } - if err := os.WriteFile(c.String(outFlag.Name), []byte(code), 0o600); err != nil { - utils.Fatalf("Failed to write ABI precompile: %v", err) + if err := os.WriteFile(outFlag, []byte(code), 0o600); err != nil { + utils.Fatalf("Failed to write generated precompile: %v", err) + } + + if err := os.WriteFile(abipath, []byte(abis[0]), 0o600); err != nil { + utils.Fatalf("Failed to write ABI: %v", err) } fmt.Println("Precompile Generation was a success!") diff --git a/contract-examples/test/ExampleRewardManager.ts b/contract-examples/test/ExampleRewardManager.ts index d2d51d0967..f5b4049ba1 100644 --- a/contract-examples/test/ExampleRewardManager.ts +++ b/contract-examples/test/ExampleRewardManager.ts @@ -140,7 +140,10 @@ describe("ExampleRewardManager", function () { expect(balance.gt(previousBalance)).to.be.true }) +<<<<<<< HEAD +======= +>>>>>>> master it("should return false for allowFeeRecipients check", async function () { let res = await contract.areFeeRecipientsAllowed(); expect(res).to.be.false diff --git a/miner/worker.go b/miner/worker.go index 050cf663be..b93a30eb9e 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -160,6 +160,7 @@ func (w *worker) commitNewWork() (*types.Block, error) { if w.coinbase == (common.Address{}) { return nil, errors.New("cannot mine without etherbase") } + header.Coinbase = w.coinbase configuredCoinbase, isAllowFeeRecipient, err := w.chain.GetCoinbaseAt(parent.Header()) diff --git a/precompile/reward_manager.abi b/precompile/reward_manager.abi new file mode 100644 index 0000000000..d21d5bdc6b --- /dev/null +++ b/precompile/reward_manager.abi @@ -0,0 +1 @@ +[{"inputs":[],"name":"allowFeeRecipients","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"areFeeRecipientsAllowed","outputs":[{"internalType":"bool","name":"isAllowed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentRewardAddress","outputs":[{"internalType":"address","name":"rewardAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setRewardAddress","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go index f887fa2f37..f57b0f72a8 100644 --- a/precompile/reward_manager.go +++ b/precompile/reward_manager.go @@ -17,6 +17,8 @@ import ( "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/vmerrs" + _ "embed" + "github.com/ethereum/go-ethereum/common" ) @@ -26,9 +28,6 @@ const ( CurrentRewardAddressGasCost uint64 = readGasCostPerSlot DisableRewardsGasCost uint64 = (writeGasCostPerSlot) + ReadAllowListGasCost // write 1 slot + read allow list SetRewardAddressGasCost uint64 = (writeGasCostPerSlot) + ReadAllowListGasCost // write 1 slot + read allow list - - // RewardManagerRawABI contains the raw ABI of RewardManager contract. - RewardManagerRawABI = "[{\"inputs\":[],\"name\":\"allowFeeRecipients\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"areFeeRecipientsAllowed\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isAllowed\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"currentRewardAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"rewardAddress\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"disableRewards\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"readAllowList\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"role\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setEnabled\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setNone\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setRewardAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" ) // Singleton StatefulPrecompiledContract and signatures. @@ -44,6 +43,10 @@ var ( ErrCannotEnableBothRewards = errors.New("cannot enable both fee recipients and reward address at the same time") ErrEmptyRewardAddress = errors.New("reward address cannot be empty") + // RewardManagerRawABI contains the raw ABI of RewardManager contract. + //go:embed reward_manager.abi + RewardManagerRawABI string + RewardManagerABI abi.ABI // will be initialized by init function RewardManagerPrecompile StatefulPrecompiledContract // will be initialized by init function