From 2d47fde5b2e556ffe8af8bd839c6a2c56acfee6d Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 2 Dec 2022 18:32:20 +0300 Subject: [PATCH 01/23] move inline string ABIs to separate files and embed them (#383) * move inline string ABIs to separate files and embed them * fix tests * fix tests * unexport function * Update accounts/abi/bind/bind.go Co-authored-by: Darioush Jalali * fix func name Co-authored-by: Darioush Jalali --- accounts/abi/bind/bind.go | 76 +++--------- accounts/abi/bind/bind_test.go | 4 +- accounts/abi/bind/precompile_bind.go | 109 ++++++++++++++++++ ...ompile_test.go => precompile_bind_test.go} | 8 +- accounts/abi/bind/precompile_template.go | 17 ++- cmd/abigen/main.go | 2 +- cmd/precompilegen/main.go | 24 +++- precompile/reward_manager.abi | 1 + precompile/reward_manager.go | 9 +- 9 files changed, 169 insertions(+), 81 deletions(-) create mode 100644 accounts/abi/bind/precompile_bind.go rename accounts/abi/bind/{bind_precompile_test.go => precompile_bind_test.go} (90%) create mode 100644 precompile/reward_manager.abi diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 82d5be5488..c2be8309e1 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) @@ -155,7 +161,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) // Ensure there is no duplicated identifier - var identifiers = callIdentifiers + identifiers := callIdentifiers if !original.IsConstant() { identifiers = transactIdentifiers } @@ -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..3b33478b43 --- /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 90% rename from accounts/abi/bind/bind_precompile_test.go rename to accounts/abi/bind/precompile_bind_test.go index 03cbcebc09..8ac85fb349 100644 --- a/accounts/abi/bind/bind_precompile_test.go +++ b/accounts/abi/bind/precompile_bind_test.go @@ -49,7 +49,7 @@ var bindFailedTests = []struct { {"type":"function","name":"anonOutput","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"}]} ] `}, - "ABI outputs for AnonOutput require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs", + "ABI outputs for anonOutput require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs", nil, nil, nil, @@ -64,7 +64,7 @@ var bindFailedTests = []struct { {"type":"function","name":"anonOutputs","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"},{"name":"","type":"string"}]} ] `}, - "ABI outputs for AnonOutputs require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs", + "ABI outputs for anonOutputs require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs", nil, nil, nil, @@ -79,7 +79,7 @@ var bindFailedTests = []struct { {"type":"function","name":"mixedOutputs","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"},{"name":"str","type":"string"}]} ] `}, - "ABI outputs for MixedOutputs require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs", + "ABI outputs for mixedOutputs require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs", nil, nil, nil, @@ -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/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 From 77ce3c384355c851e4f7ba5853a12d9cedd4231f Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 22 Dec 2022 20:23:53 +0300 Subject: [PATCH 02/23] replace getByKey with getByAddress (#395) --- consensus/dummy/consensus.go | 3 +- core/blockchain_reader.go | 4 +- core/state_transition.go | 2 +- core/tx_pool.go | 4 +- core/vm/evm.go | 2 +- eth/gasprice/gasprice.go | 3 +- internal/ethapi/api.go | 3 +- params/config.go | 63 ++-------- params/precompile_config.go | 191 +++++++++---------------------- params/precompile_config_test.go | 8 +- plugin/evm/block_verification.go | 3 +- precompile/upgradeable.go | 1 + 12 files changed, 82 insertions(+), 205 deletions(-) diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index eb1fdd4930..8ab2f1c399 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -15,6 +15,7 @@ import ( "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/precompile" "github.com/ava-labs/subnet-evm/trie" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" @@ -198,7 +199,7 @@ func (self *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header } // Ensure that coinbase is valid if reward manager is enabled // If reward manager is disabled, this will be handled in syntactic verification - if config.IsRewardManager(timestamp) { + if config.IsPrecompileEnabled(precompile.RewardManagerAddress, timestamp) { if err := self.verifyCoinbase(config, header, parent, chain); err != nil { return err } diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index 130fe0e34d..35be0d65c1 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -348,7 +348,7 @@ func (bc *BlockChain) SubscribeAcceptedTransactionEvent(ch chan<- NewTxsEvent) e func (bc *BlockChain) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) { config := bc.Config() bigTime := new(big.Int).SetUint64(parent.Time) - if !config.IsFeeConfigManager(bigTime) { + if !config.IsPrecompileEnabled(precompile.FeeConfigManagerAddress, bigTime) { return config.FeeConfig, common.Big0, nil } @@ -390,7 +390,7 @@ func (bc *BlockChain) GetCoinbaseAt(parent *types.Header) (common.Address, bool, return constants.BlackholeAddr, false, nil } - if !config.IsRewardManager(bigTime) { + if !config.IsPrecompileEnabled(precompile.RewardManagerAddress, bigTime) { if bc.chainConfig.AllowFeeRecipients { return common.Address{}, true, nil } else { diff --git a/core/state_transition.go b/core/state_transition.go index d039d0694c..acd81569e2 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -249,7 +249,7 @@ func (st *StateTransition) preCheck() error { } // Check that the sender is on the tx allow list if enabled - if st.evm.ChainConfig().IsTxAllowList(st.evm.Context.Time) { + if st.evm.ChainConfig().IsPrecompileEnabled(precompile.TxAllowListAddress, st.evm.Context.Time) { txAllowListRole := precompile.GetTxAllowListStatus(st.state, st.msg.From()) if !txAllowListRole.IsEnabled() { return fmt.Errorf("%w: %s", precompile.ErrSenderAddressNotAllowListed, st.msg.From()) diff --git a/core/tx_pool.go b/core/tx_pool.go index ed5502d94b..f927cb9589 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -693,7 +693,7 @@ func (pool *TxPool) checkTxState(from common.Address, tx *types.Transaction) err // If the tx allow list is enabled, return an error if the from address is not allow listed. headTimestamp := big.NewInt(int64(pool.currentHead.Time)) - if pool.chainconfig.IsTxAllowList(headTimestamp) { + if pool.chainconfig.IsPrecompileEnabled(precompile.TxAllowListAddress, headTimestamp) { txAllowListRole := precompile.GetTxAllowListStatus(pool.currentState, from) if !txAllowListRole.IsEnabled() { return fmt.Errorf("%w: %s", precompile.ErrSenderAddressNotAllowListed, from) @@ -1441,7 +1441,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { // when we reset txPool we should explicitly check if fee struct for min base fee has changed // so that we can correctly drop txs with < minBaseFee from tx pool. - if pool.chainconfig.IsFeeConfigManager(new(big.Int).SetUint64(newHead.Time)) { + if pool.chainconfig.IsPrecompileEnabled(precompile.FeeConfigManagerAddress, new(big.Int).SetUint64(newHead.Time)) { feeConfig, _, err := pool.chain.GetFeeConfigAt(newHead) if err != nil { log.Error("Failed to get fee config state", "err", err, "root", newHead.Root) diff --git a/core/vm/evm.go b/core/vm/evm.go index c13db0def9..bb4b14b3e8 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -507,7 +507,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, return nil, common.Address{}, 0, vmerrs.ErrContractAddressCollision } // If the allow list is enabled, check that [evm.TxContext.Origin] has permission to deploy a contract. - if evm.chainRules.IsContractDeployerAllowListEnabled { + if evm.chainRules.IsPrecompileEnabled(precompile.ContractDeployerAllowListAddress) { allowListRole := precompile.GetContractDeployerAllowListStatus(evm.StateDB, evm.TxContext.Origin) if !allowListRole.IsEnabled() { return nil, common.Address{}, 0, fmt.Errorf("tx.origin %s is not authorized to deploy a contract", evm.TxContext.Origin) diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 1554073693..48e3b938ea 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -39,6 +39,7 @@ import ( "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/precompile" "github.com/ava-labs/subnet-evm/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" @@ -317,7 +318,7 @@ func (oracle *Oracle) suggestDynamicFees(ctx context.Context) (*big.Int, *big.In feeLastChangedAt *big.Int feeConfig commontype.FeeConfig ) - if oracle.backend.ChainConfig().IsFeeConfigManager(new(big.Int).SetUint64(head.Time)) { + if oracle.backend.ChainConfig().IsPrecompileEnabled(precompile.FeeConfigManagerAddress, new(big.Int).SetUint64(head.Time)) { feeConfig, feeLastChangedAt, err = oracle.backend.GetFeeConfigAt(head) if err != nil { return nil, nil, err diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index ebf872015d..f2a109677f 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -625,12 +625,13 @@ func (api *BlockChainAPI) ChainId() *hexutil.Big { return (*hexutil.Big)(api.b.ChainConfig().ChainID) } +// GetActivePrecompilesAt returns the active precompile configs at the given block timestamp. func (s *BlockChainAPI) GetActivePrecompilesAt(ctx context.Context, blockTimestamp *big.Int) params.PrecompileUpgrade { if blockTimestamp == nil { blockTimestampInt := s.b.CurrentHeader().Time blockTimestamp = new(big.Int).SetUint64(blockTimestampInt) } - return s.b.ChainConfig().GetActivePrecompiles(blockTimestamp) + return s.b.ChainConfig().GetActivePrecompileUpgrade(blockTimestamp) } type FeeConfigResult struct { diff --git a/params/config.go b/params/config.go index 7a3f013dcd..1407ce1bb8 100644 --- a/params/config.go +++ b/params/config.go @@ -272,46 +272,12 @@ func (c *ChainConfig) IsSubnetEVM(blockTimestamp *big.Int) bool { return utils.IsForked(c.getNetworkUpgrades().SubnetEVMTimestamp, blockTimestamp) } -// PRECOMPILE UPGRADES START HERE - -// IsContractDeployerAllowList returns whether [blockTimestamp] is either equal to the ContractDeployerAllowList fork block timestamp or greater. -func (c *ChainConfig) IsContractDeployerAllowList(blockTimestamp *big.Int) bool { - config := c.GetContractDeployerAllowListConfig(blockTimestamp) - return config != nil && !config.Disable -} - -// IsContractNativeMinter returns whether [blockTimestamp] is either equal to the NativeMinter fork block timestamp or greater. -func (c *ChainConfig) IsContractNativeMinter(blockTimestamp *big.Int) bool { - config := c.GetContractNativeMinterConfig(blockTimestamp) - return config != nil && !config.Disable -} - -// IsTxAllowList returns whether [blockTimestamp] is either equal to the TxAllowList fork block timestamp or greater. -func (c *ChainConfig) IsTxAllowList(blockTimestamp *big.Int) bool { - config := c.GetTxAllowListConfig(blockTimestamp) - return config != nil && !config.Disable -} - -// IsFeeConfigManager returns whether [blockTimestamp] is either equal to the FeeConfigManager fork block timestamp or greater. -func (c *ChainConfig) IsFeeConfigManager(blockTimestamp *big.Int) bool { - config := c.GetFeeConfigManagerConfig(blockTimestamp) - return config != nil && !config.Disable +// IsPrecompileEnabled returns whether precompile with [address] is enabled at [blockTimestamp]. +func (c *ChainConfig) IsPrecompileEnabled(address common.Address, blockTimestamp *big.Int) bool { + config := c.GetPrecompileConfig(address, blockTimestamp) + return config != nil && !config.IsDisabled() } -// IsRewardManager returns whether [blockTimestamp] is either equal to the RewardManager fork block timestamp or greater. -func (c *ChainConfig) IsRewardManager(blockTimestamp *big.Int) bool { - config := c.GetRewardManagerConfig(blockTimestamp) - return config != nil && !config.Disable -} - -// ADD YOUR PRECOMPILE HERE -/* -func (c *ChainConfig) Is{YourPrecompile}(blockTimestamp *big.Int) bool { - config := c.Get{YourPrecompile}Config(blockTimestamp) - return config != nil && !config.Disable -} -*/ - // CheckCompatible checks whether scheduled fork transitions have been imported // with a mismatching chain configuration. func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, timestamp uint64) *ConfigCompatError { @@ -539,15 +505,6 @@ type Rules struct { // Rules for Avalanche releases IsSubnetEVM bool - // Optional stateful precompile rules - IsContractDeployerAllowListEnabled bool - IsContractNativeMinterEnabled bool - IsTxAllowListEnabled bool - IsFeeConfigManagerEnabled bool - IsRewardManagerEnabled bool - // ADD YOUR PRECOMPILE HERE - // Is{YourPrecompile}Enabled bool - // Precompiles maps addresses to stateful precompiled contracts that are enabled // for this rule set. // Note: none of these addresses should conflict with the address space used by @@ -555,6 +512,11 @@ type Rules struct { Precompiles map[common.Address]precompile.StatefulPrecompiledContract } +func (r *Rules) IsPrecompileEnabled(addr common.Address) bool { + _, ok := r.Precompiles[addr] + return ok +} + // Rules ensures c's ChainID is not nil. func (c *ChainConfig) rules(num *big.Int) Rules { chainID := c.ChainID @@ -580,13 +542,6 @@ func (c *ChainConfig) AvalancheRules(blockNum, blockTimestamp *big.Int) Rules { rules := c.rules(blockNum) rules.IsSubnetEVM = c.IsSubnetEVM(blockTimestamp) - rules.IsContractDeployerAllowListEnabled = c.IsContractDeployerAllowList(blockTimestamp) - rules.IsContractNativeMinterEnabled = c.IsContractNativeMinter(blockTimestamp) - rules.IsTxAllowListEnabled = c.IsTxAllowList(blockTimestamp) - rules.IsFeeConfigManagerEnabled = c.IsFeeConfigManager(blockTimestamp) - rules.IsRewardManagerEnabled = c.IsRewardManager(blockTimestamp) - // ADD YOUR PRECOMPILE HERE - // rules.Is{YourPrecompile}Enabled = c.{IsYourPrecompile}(blockTimestamp) // Initialize the stateful precompiles that should be enabled at [blockTimestamp]. rules.Precompiles = make(map[common.Address]precompile.StatefulPrecompiledContract) diff --git a/params/precompile_config.go b/params/precompile_config.go index daa457b03b..362b048bd7 100644 --- a/params/precompile_config.go +++ b/params/precompile_config.go @@ -9,49 +9,10 @@ import ( "github.com/ava-labs/subnet-evm/precompile" "github.com/ava-labs/subnet-evm/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) -// precompileKey is a helper type used to reference each of the -// possible stateful precompile types that can be activated -// as a network upgrade. -type precompileKey int - -const ( - contractDeployerAllowListKey precompileKey = iota + 1 - contractNativeMinterKey - txAllowListKey - feeManagerKey - rewardManagerKey - // ADD YOUR PRECOMPILE HERE - // {yourPrecompile}Key -) - -// TODO: Move this to the interface or PrecompileConfig struct -func (k precompileKey) String() string { - switch k { - case contractDeployerAllowListKey: - return "contractDeployerAllowList" - case contractNativeMinterKey: - return "contractNativeMinter" - case txAllowListKey: - return "txAllowList" - case feeManagerKey: - return "feeManager" - case rewardManagerKey: - return "rewardManager" - // ADD YOUR PRECOMPILE HERE - /* - case {yourPrecompile}Key: - return "{yourPrecompile}" - */ - } - return "unknown" -} - -// ADD YOUR PRECOMPILE HERE -var precompileKeys = []precompileKey{contractDeployerAllowListKey, contractNativeMinterKey, txAllowListKey, feeManagerKey, rewardManagerKey /* {yourPrecompile}Key */} - // PrecompileUpgrade is a helper struct embedded in UpgradeConfig, representing // each of the possible stateful precompile types that can be activated // as a network upgrade. @@ -65,25 +26,26 @@ type PrecompileUpgrade struct { // {YourPrecompile}Config *precompile.{YourPrecompile}Config `json:"{yourPrecompile}Config,omitempty"` } -func (p *PrecompileUpgrade) getByKey(key precompileKey) (precompile.StatefulPrecompileConfig, bool) { - switch key { - case contractDeployerAllowListKey: +// getByAddress returns the precompile config for the given address. +func (p *PrecompileUpgrade) getByAddress(address common.Address) (precompile.StatefulPrecompileConfig, bool) { + switch address { + case precompile.ContractDeployerAllowListAddress: return p.ContractDeployerAllowListConfig, p.ContractDeployerAllowListConfig != nil - case contractNativeMinterKey: + case precompile.ContractNativeMinterAddress: return p.ContractNativeMinterConfig, p.ContractNativeMinterConfig != nil - case txAllowListKey: + case precompile.TxAllowListAddress: return p.TxAllowListConfig, p.TxAllowListConfig != nil - case feeManagerKey: + case precompile.FeeConfigManagerAddress: return p.FeeManagerConfig, p.FeeManagerConfig != nil - case rewardManagerKey: + case precompile.RewardManagerAddress: return p.RewardManagerConfig, p.RewardManagerConfig != nil // ADD YOUR PRECOMPILE HERE /* - case {yourPrecompile}Key: - return p.{YourPrecompile}Config , p.{YourPrecompile}Config != nil + case precompile.{YourPrecompile}Address: + return p.{YourPrecompile}Config, p.{YourPrecompile}Config != nil */ default: - panic(fmt.Sprintf("unknown upgrade key: %v", key)) + panic(fmt.Sprintf("unknown precompile address: %v", address)) } } @@ -98,8 +60,8 @@ func (c *ChainConfig) verifyPrecompileUpgrades() error { for i, upgrade := range c.PrecompileUpgrades { hasKey := false // used to verify if there is only one key per Upgrade - for _, key := range precompileKeys { - config, ok := upgrade.getByKey(key) + for _, address := range precompile.UsedAddresses { + config, ok := upgrade.getByAddress(address) if !ok { continue } @@ -123,13 +85,13 @@ func (c *ChainConfig) verifyPrecompileUpgrades() error { } } - for _, key := range precompileKeys { + for _, address := range precompile.UsedAddresses { var ( lastUpgraded *big.Int disabled bool ) // check the genesis chain config for any enabled upgrade - if config, ok := c.PrecompileUpgrade.getByKey(key); ok { + if config, ok := c.PrecompileUpgrade.getByAddress(address); ok { if err := config.Verify(); err != nil { return err } @@ -140,8 +102,8 @@ func (c *ChainConfig) verifyPrecompileUpgrades() error { } // next range over upgrades to verify correct use of disabled and blockTimestamps. for i, upgrade := range c.PrecompileUpgrades { - config, ok := upgrade.getByKey(key) - // Skip the upgrade if it's not relevant to [key]. + config, ok := upgrade.getByAddress(address) + // Skip the upgrade if it's not relevant to [address]. if !ok { continue } @@ -165,10 +127,10 @@ func (c *ChainConfig) verifyPrecompileUpgrades() error { return nil } -// getActivePrecompileConfig returns the most recent precompile config corresponding to [key]. +// getActivePrecompileConfig returns the most recent precompile config corresponding to [address]. // If none have occurred, returns nil. -func (c *ChainConfig) getActivePrecompileConfig(blockTimestamp *big.Int, key precompileKey, upgrades []PrecompileUpgrade) precompile.StatefulPrecompileConfig { - configs := c.getActivatingPrecompileConfigs(nil, blockTimestamp, key, upgrades) +func (c *ChainConfig) getActivePrecompileConfig(blockTimestamp *big.Int, address common.Address, upgrades []PrecompileUpgrade) precompile.StatefulPrecompileConfig { + configs := c.getActivatingPrecompileConfigs(nil, blockTimestamp, address, upgrades) if len(configs) == 0 { return nil } @@ -177,18 +139,18 @@ func (c *ChainConfig) getActivePrecompileConfig(blockTimestamp *big.Int, key pre // getActivatingPrecompileConfigs returns all forks configured to activate during the state transition from a block with timestamp [from] // to a block with timestamp [to]. -func (c *ChainConfig) getActivatingPrecompileConfigs(from *big.Int, to *big.Int, key precompileKey, upgrades []PrecompileUpgrade) []precompile.StatefulPrecompileConfig { +func (c *ChainConfig) getActivatingPrecompileConfigs(from *big.Int, to *big.Int, address common.Address, upgrades []PrecompileUpgrade) []precompile.StatefulPrecompileConfig { configs := make([]precompile.StatefulPrecompileConfig, 0) // First check the embedded [upgrade] for precompiles configured // in the genesis chain config. - if config, ok := c.PrecompileUpgrade.getByKey(key); ok { + if config, ok := c.PrecompileUpgrade.getByAddress(address); ok { if utils.IsForkTransition(config.Timestamp(), from, to) { configs = append(configs, config) } } // Loop over all upgrades checking for the requested precompile config. for _, upgrade := range upgrades { - if config, ok := upgrade.getByKey(key); ok { + if config, ok := upgrade.getByAddress(address); ok { // Check if the precompile activates in the specified range. if utils.IsForkTransition(config.Timestamp(), from, to) { configs = append(configs, config) @@ -198,80 +160,35 @@ func (c *ChainConfig) getActivatingPrecompileConfigs(from *big.Int, to *big.Int, return configs } -// GetContractDeployerAllowListConfig returns the latest forked ContractDeployerAllowListConfig -// specified by [c] or nil if it was never enabled. -func (c *ChainConfig) GetContractDeployerAllowListConfig(blockTimestamp *big.Int) *precompile.ContractDeployerAllowListConfig { - if val := c.getActivePrecompileConfig(blockTimestamp, contractDeployerAllowListKey, c.PrecompileUpgrades); val != nil { - return val.(*precompile.ContractDeployerAllowListConfig) - } - return nil -} - -// GetContractNativeMinterConfig returns the latest forked ContractNativeMinterConfig -// specified by [c] or nil if it was never enabled. -func (c *ChainConfig) GetContractNativeMinterConfig(blockTimestamp *big.Int) *precompile.ContractNativeMinterConfig { - if val := c.getActivePrecompileConfig(blockTimestamp, contractNativeMinterKey, c.PrecompileUpgrades); val != nil { - return val.(*precompile.ContractNativeMinterConfig) - } - return nil -} - -// GetTxAllowListConfig returns the latest forked TxAllowListConfig -// specified by [c] or nil if it was never enabled. -func (c *ChainConfig) GetTxAllowListConfig(blockTimestamp *big.Int) *precompile.TxAllowListConfig { - if val := c.getActivePrecompileConfig(blockTimestamp, txAllowListKey, c.PrecompileUpgrades); val != nil { - return val.(*precompile.TxAllowListConfig) - } - return nil -} - -// GetFeeConfigManagerConfig returns the latest forked FeeManagerConfig -// specified by [c] or nil if it was never enabled. -func (c *ChainConfig) GetFeeConfigManagerConfig(blockTimestamp *big.Int) *precompile.FeeConfigManagerConfig { - if val := c.getActivePrecompileConfig(blockTimestamp, feeManagerKey, c.PrecompileUpgrades); val != nil { - return val.(*precompile.FeeConfigManagerConfig) +func (c *ChainConfig) GetPrecompileConfig(address common.Address, blockTimestamp *big.Int) precompile.StatefulPrecompileConfig { + if val := c.getActivePrecompileConfig(blockTimestamp, address, c.PrecompileUpgrades); val != nil { + return val } return nil } -// GetRewardManagerConfig returns the latest forked RewardManagerConfig -// specified by [c] or nil if it was never enabled. -func (c *ChainConfig) GetRewardManagerConfig(blockTimestamp *big.Int) *precompile.RewardManagerConfig { - if val := c.getActivePrecompileConfig(blockTimestamp, rewardManagerKey, c.PrecompileUpgrades); val != nil { - return val.(*precompile.RewardManagerConfig) - } - return nil -} - -/* ADD YOUR PRECOMPILE HERE -func (c *ChainConfig) Get{YourPrecompile}Config(blockTimestamp *big.Int) *precompile.{YourPrecompile}Config { - if val := c.getActivePrecompileConfig(blockTimestamp, {yourPrecompile}Key, c.PrecompileUpgrades); val != nil { - return val.(*precompile.{YourPrecompile}Config) - } - return nil -} -*/ - -func (c *ChainConfig) GetActivePrecompiles(blockTimestamp *big.Int) PrecompileUpgrade { +// TODO: remove this +func (c *ChainConfig) GetActivePrecompileUpgrade(blockTimestamp *big.Int) PrecompileUpgrade { pu := PrecompileUpgrade{} - if config := c.GetContractDeployerAllowListConfig(blockTimestamp); config != nil && !config.Disable { - pu.ContractDeployerAllowListConfig = config + if config := c.GetPrecompileConfig(precompile.ContractDeployerAllowListAddress, blockTimestamp); config != nil && !config.IsDisabled() { + pu.ContractDeployerAllowListConfig = config.(*precompile.ContractDeployerAllowListConfig) } - if config := c.GetContractNativeMinterConfig(blockTimestamp); config != nil && !config.Disable { - pu.ContractNativeMinterConfig = config + if config := c.GetPrecompileConfig(precompile.ContractNativeMinterAddress, blockTimestamp); config != nil && !config.IsDisabled() { + pu.ContractNativeMinterConfig = config.(*precompile.ContractNativeMinterConfig) } - if config := c.GetTxAllowListConfig(blockTimestamp); config != nil && !config.Disable { - pu.TxAllowListConfig = config + if config := c.GetPrecompileConfig(precompile.TxAllowListAddress, blockTimestamp); config != nil && !config.IsDisabled() { + pu.TxAllowListConfig = config.(*precompile.TxAllowListConfig) } - if config := c.GetFeeConfigManagerConfig(blockTimestamp); config != nil && !config.Disable { - pu.FeeManagerConfig = config + if config := c.GetPrecompileConfig(precompile.FeeConfigManagerAddress, blockTimestamp); config != nil && !config.IsDisabled() { + pu.FeeManagerConfig = config.(*precompile.FeeConfigManagerConfig) } - if config := c.GetRewardManagerConfig(blockTimestamp); config != nil && !config.Disable { - pu.RewardManagerConfig = config + if config := c.GetPrecompileConfig(precompile.RewardManagerAddress, blockTimestamp); config != nil && !config.IsDisabled() { + pu.RewardManagerConfig = config.(*precompile.RewardManagerConfig) } + // ADD YOUR PRECOMPILE HERE - // if config := c.{YourPrecompile}Config(blockTimestamp); config != nil && !config.Disable { - // pu.{YourPrecompile}Config = config + // if config := c.GetPrecompileConfig(precompile.{YourPrecompile}Address, blockTimestamp); config != nil && !config.IsDisabled() { + // pu.{YourPrecompile}Config = config.(*precompile.{YourPrecompile}Config) // } return pu @@ -284,8 +201,8 @@ func (c *ChainConfig) GetActivePrecompiles(blockTimestamp *big.Int) PrecompileUp // Assumes given timestamp is the last accepted block timestamp. // This ensures that as long as the node has not accepted a block with a different rule set it will allow a new upgrade to be applied as long as it activates after the last accepted block. func (c *ChainConfig) CheckPrecompilesCompatible(precompileUpgrades []PrecompileUpgrade, lastTimestamp *big.Int) *ConfigCompatError { - for _, key := range precompileKeys { - if err := c.checkPrecompileCompatible(key, precompileUpgrades, lastTimestamp); err != nil { + for _, address := range precompile.UsedAddresses { + if err := c.checkPrecompileCompatible(address, precompileUpgrades, lastTimestamp); err != nil { return err } } @@ -293,13 +210,13 @@ func (c *ChainConfig) CheckPrecompilesCompatible(precompileUpgrades []Precompile return nil } -// checkPrecompileCompatible verifies that the precompile specified by [key] is compatible between [c] and [precompileUpgrades] at [headTimestamp]. +// checkPrecompileCompatible verifies that the precompile specified by [address] is compatible between [c] and [precompileUpgrades] at [headTimestamp]. // Returns an error if upgrades already forked at [headTimestamp] are missing from [precompileUpgrades]. // Upgrades that have already gone into effect cannot be modified or absent from [precompileUpgrades]. -func (c *ChainConfig) checkPrecompileCompatible(key precompileKey, precompileUpgrades []PrecompileUpgrade, lastTimestamp *big.Int) *ConfigCompatError { +func (c *ChainConfig) checkPrecompileCompatible(address common.Address, precompileUpgrades []PrecompileUpgrade, lastTimestamp *big.Int) *ConfigCompatError { // all active upgrades must match - activeUpgrades := c.getActivatingPrecompileConfigs(nil, lastTimestamp, key, c.PrecompileUpgrades) - newUpgrades := c.getActivatingPrecompileConfigs(nil, lastTimestamp, key, precompileUpgrades) + activeUpgrades := c.getActivatingPrecompileConfigs(nil, lastTimestamp, address, c.PrecompileUpgrades) + newUpgrades := c.getActivatingPrecompileConfigs(nil, lastTimestamp, address, precompileUpgrades) // first, check existing upgrades are there for i, upgrade := range activeUpgrades { @@ -337,8 +254,8 @@ func (c *ChainConfig) checkPrecompileCompatible(key precompileKey, precompileUpg // have been activated through an upgrade. func (c *ChainConfig) EnabledStatefulPrecompiles(blockTimestamp *big.Int) []precompile.StatefulPrecompileConfig { statefulPrecompileConfigs := make([]precompile.StatefulPrecompileConfig, 0) - for _, key := range precompileKeys { - if config := c.getActivePrecompileConfig(blockTimestamp, key, c.PrecompileUpgrades); config != nil { + for _, address := range precompile.UsedAddresses { + if config := c.getActivePrecompileConfig(blockTimestamp, address, c.PrecompileUpgrades); config != nil { statefulPrecompileConfigs = append(statefulPrecompileConfigs, config) } } @@ -354,12 +271,12 @@ func (c *ChainConfig) EnabledStatefulPrecompiles(blockTimestamp *big.Int) []prec // - during block processing to update the state before processing the given block. func (c *ChainConfig) CheckConfigurePrecompiles(parentTimestamp *big.Int, blockContext precompile.BlockContext, statedb precompile.StateDB) { blockTimestamp := blockContext.Timestamp() - for _, key := range precompileKeys { // Note: configure precompiles in a deterministic order. - for _, config := range c.getActivatingPrecompileConfigs(parentTimestamp, blockTimestamp, key, c.PrecompileUpgrades) { + for _, address := range precompile.UsedAddresses { // Note: configure precompiles in a deterministic order. + for _, config := range c.getActivatingPrecompileConfigs(parentTimestamp, blockTimestamp, address, c.PrecompileUpgrades) { // If this transition activates the upgrade, configure the stateful precompile. // (or deconfigure it if it is being disabled.) if config.IsDisabled() { - log.Info("Disabling precompile", "name", key) + log.Info("Disabling precompile", "precompileAddress", address) // TODO: use proper names for precompiles statedb.Suicide(config.Address()) // Calling Finalise here effectively commits Suicide call and wipes the contract state. // This enables re-configuration of the same contract state in the same block. @@ -367,7 +284,7 @@ func (c *ChainConfig) CheckConfigurePrecompiles(parentTimestamp *big.Int, blockC // since Suicide will be committed after the reconfiguration. statedb.Finalise(true) } else { - log.Info("Activating new precompile", "name", key, "config", config) + log.Info("Activating new precompile", "precompileAddress", address, "config", config) precompile.Configure(c, blockContext, config, statedb) } } diff --git a/params/precompile_config_test.go b/params/precompile_config_test.go index 55b138b65a..3ac584aec4 100644 --- a/params/precompile_config_test.go +++ b/params/precompile_config_test.go @@ -203,15 +203,15 @@ func TestGetPrecompileConfig(t *testing.T) { ContractDeployerAllowListConfig: precompile.NewContractDeployerAllowListConfig(big.NewInt(10), nil, nil), } - deployerConfig := config.GetContractDeployerAllowListConfig(big.NewInt(0)) + deployerConfig := config.GetPrecompileConfig(precompile.ContractDeployerAllowListAddress, big.NewInt(0)) assert.Nil(deployerConfig) - deployerConfig = config.GetContractDeployerAllowListConfig(big.NewInt(10)) + deployerConfig = config.GetPrecompileConfig(precompile.ContractDeployerAllowListAddress, big.NewInt(10)) assert.NotNil(deployerConfig) - deployerConfig = config.GetContractDeployerAllowListConfig(big.NewInt(11)) + deployerConfig = config.GetPrecompileConfig(precompile.ContractDeployerAllowListAddress, big.NewInt(11)) assert.NotNil(deployerConfig) - txAllowListConfig := config.GetTxAllowListConfig(big.NewInt(0)) + txAllowListConfig := config.GetPrecompileConfig(precompile.TxAllowListAddress, big.NewInt(0)) assert.Nil(txAllowListConfig) } diff --git a/plugin/evm/block_verification.go b/plugin/evm/block_verification.go index e6d1fc4638..9e96553d52 100644 --- a/plugin/evm/block_verification.go +++ b/plugin/evm/block_verification.go @@ -12,6 +12,7 @@ import ( "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/precompile" "github.com/ava-labs/subnet-evm/trie" "github.com/ava-labs/subnet-evm/vmerrs" ) @@ -98,7 +99,7 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { } switch { - case rules.IsRewardManagerEnabled: + case rules.IsPrecompileEnabled(precompile.RewardManagerAddress): // if reward manager is enabled, Coinbase depends on state. // State is not available here, so skip checking the coinbase here. case rules.IsSubnetEVM && b.vm.chainConfig.AllowFeeRecipients: diff --git a/precompile/upgradeable.go b/precompile/upgradeable.go index b835585b8b..a35d575de0 100644 --- a/precompile/upgradeable.go +++ b/precompile/upgradeable.go @@ -12,6 +12,7 @@ import ( // UpgradeableConfig contains the timestamp for the upgrade along with // a boolean [Disable]. If [Disable] is set, the upgrade deactivates // the precompile and resets its storage. +// TODO: convert to interface type UpgradeableConfig struct { BlockTimestamp *big.Int `json:"blockTimestamp"` Disable bool `json:"disable,omitempty"` From 9e5c8c4f4df7efe4a7138d5f0f9fb5e088ca012c Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 27 Dec 2022 21:02:18 +0300 Subject: [PATCH 03/23] rework on panics in precompiles (#418) * rework on panics in precompiles * Update precompile/allow_list.go Co-authored-by: aaronbuchwald * Update precompile/fee_config_manager.go Co-authored-by: aaronbuchwald * Update precompile/fee_config_manager.go Co-authored-by: aaronbuchwald * fix reviews * wrap errors in ConfigurePrecompiles * cleaner errors * Update utils.go * Update miner/worker.go Co-authored-by: aaronbuchwald * Update core/state_processor.go Co-authored-by: aaronbuchwald Co-authored-by: aaronbuchwald --- accounts/abi/bind/precompile_template.go | 35 ++++++++---- core/genesis.go | 5 +- core/state_processor.go | 7 ++- miner/worker.go | 6 +- params/precompile_config.go | 9 ++- precompile/allow_list.go | 10 +++- precompile/contract.go | 8 +-- precompile/contract_deployer_allow_list.go | 4 +- precompile/contract_native_minter.go | 15 +++-- precompile/fee_config_manager.go | 34 ++++++----- precompile/reward_manager.go | 65 +++++++++------------- precompile/stateful_precompile_config.go | 6 +- precompile/tx_allow_list.go | 4 +- precompile/utils.go | 9 +-- 14 files changed, 125 insertions(+), 92 deletions(-) diff --git a/accounts/abi/bind/precompile_template.go b/accounts/abi/bind/precompile_template.go index 2f4cd39ad1..596d96c351 100644 --- a/accounts/abi/bind/precompile_template.go +++ b/accounts/abi/bind/precompile_template.go @@ -46,6 +46,7 @@ Typically, custom codes are required in only those areas. package precompile import ( + "encoding/json" "math/big" "errors" "fmt" @@ -75,6 +76,7 @@ var ( _ = big.NewInt _ = strings.NewReader _ = fmt.Printf + _ = json.Unmarshal ) {{$contract := .Contract}} @@ -148,7 +150,10 @@ func init() { } {{.Contract.Type}}ABI = parsed - {{.Contract.Type}}Precompile = create{{.Contract.Type}}Precompile({{.Contract.Type}}Address) + {{.Contract.Type}}Precompile, err = create{{.Contract.Type}}Precompile({{.Contract.Type}}Address) + if err != nil { + panic(err) + } } // New{{.Contract.Type}}Config returns a config for a network upgrade at [blockTimestamp] that enables @@ -198,9 +203,10 @@ func (c *{{.Contract.Type}}Config) Address() common.Address { } // Configure configures [state] with the initial configuration. -func (c *{{.Contract.Type}}Config) Configure(_ ChainConfig, state StateDB, _ BlockContext) { +func (c *{{.Contract.Type}}Config) Configure(_ ChainConfig, state StateDB, _ BlockContext) error { {{if .Contract.AllowList}}c.AllowListConfig.Configure(state, {{.Contract.Type}}Address){{end}} // CUSTOM CODE STARTS HERE + return nil } // Contract returns the singleton stateful precompiled contract to be used for {{.Contract.Type}}. @@ -404,27 +410,32 @@ func {{decapitalise $contract.Type}}Fallback (accessibleState PrecompileAccessib // create{{.Contract.Type}}Precompile returns a StatefulPrecompiledContract with getters and setters for the precompile. {{if .Contract.AllowList}} // Access to the getters/setters is controlled by an allow list for [precompileAddr].{{end}} -func create{{.Contract.Type}}Precompile(precompileAddr common.Address) StatefulPrecompiledContract { +func create{{.Contract.Type}}Precompile(precompileAddr common.Address) (StatefulPrecompiledContract, error) { var functions []*statefulPrecompileFunction {{- if .Contract.AllowList}} functions = append(functions, createAllowListFunctions(precompileAddr)...) {{- end}} - {{range .Contract.Funcs}} - method{{.Normalized.Name}}, ok := {{$contract.Type}}ABI.Methods["{{.Original.Name}}"] - if !ok{ - panic("given method does not exist in the ABI") + abiFunctionMap := map[string]RunStatefulPrecompileFunc{ + {{- range .Contract.Funcs}} + "{{.Original.Name}}": {{decapitalise .Normalized.Name}}, + {{- end}} + } + + for name, function := range abiFunctionMap { + method, ok := {{$contract.Type}}ABI.Methods[name] + if !ok { + return nil, fmt.Errorf("given method (%s) does not exist in the ABI", name) + } + functions = append(functions, newStatefulPrecompileFunction(method.ID, function)) } - functions = append(functions, newStatefulPrecompileFunction(method{{.Normalized.Name}}.ID, {{decapitalise .Normalized.Name}})) - {{end}} {{- if .Contract.Fallback}} // Construct the contract with the fallback function. - contract := newStatefulPrecompileWithFunctionSelectors({{decapitalise $contract.Type}}Fallback, functions) + return NewStatefulPrecompileContract({{decapitalise $contract.Type}}Fallback, functions) {{- else}} // Construct the contract with no fallback function. - contract := newStatefulPrecompileWithFunctionSelectors(nil, functions) + return NewStatefulPrecompileContract(nil, functions) {{- end}} - return contract } ` diff --git a/core/genesis.go b/core/genesis.go index eb88bc82f4..1ab970ad2e 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -307,7 +307,10 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { } // Configure any stateful precompiles that should be enabled in the genesis. - g.Config.CheckConfigurePrecompiles(nil, types.NewBlockWithHeader(head), statedb) + err = g.Config.ConfigurePrecompiles(nil, types.NewBlockWithHeader(head), statedb) + if err != nil { + panic(fmt.Sprintf("unable to configure precompiles in genesis block: %v", err)) + } // Do custom allocation after airdrop in case an address shows up in standard // allocation diff --git a/core/state_processor.go b/core/state_processor.go index 2b28f8b082..58d7df6fd7 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -37,6 +37,7 @@ import ( "github.com/ava-labs/subnet-evm/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" ) // StateProcessor is a basic Processor, which takes care of transitioning @@ -78,7 +79,11 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state ) // Configure any stateful precompiles that should go into effect during this block. - p.config.CheckConfigurePrecompiles(new(big.Int).SetUint64(parent.Time), block, statedb) + err := p.config.ConfigurePrecompiles(new(big.Int).SetUint64(parent.Time), block, statedb) + if err != nil { + log.Error("failed to configure precompiles processing block", "hash", block.Hash(), "number", block.NumberU64(), "timestamp", block.Time(), "err", err) + return nil, nil, 0, err + } blockContext := NewEVMBlockContext(header, p.bc, nil) vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) diff --git a/miner/worker.go b/miner/worker.go index d56c06cafd..32453353cf 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -186,7 +186,11 @@ func (w *worker) commitNewWork() (*types.Block, error) { return nil, fmt.Errorf("failed to create new current environment: %w", err) } // Configure any stateful precompiles that should go into effect during this block. - w.chainConfig.CheckConfigurePrecompiles(new(big.Int).SetUint64(parent.Time()), types.NewBlockWithHeader(header), env.state) + err = w.chainConfig.ConfigurePrecompiles(new(big.Int).SetUint64(parent.Time()), types.NewBlockWithHeader(header), env.state) + if err != nil { + log.Error("failed to configure precompiles mining new block", "parent", parent.Hash(), "number", header.Number, "timestamp", header.Time, "err", err) + return nil, err + } // Fill the block with all available pending transactions. pending := w.eth.TxPool().Pending(true) diff --git a/params/precompile_config.go b/params/precompile_config.go index 362b048bd7..cd629ced55 100644 --- a/params/precompile_config.go +++ b/params/precompile_config.go @@ -263,13 +263,13 @@ func (c *ChainConfig) EnabledStatefulPrecompiles(blockTimestamp *big.Int) []prec return statefulPrecompileConfigs } -// CheckConfigurePrecompiles checks if any of the precompiles specified by the chain config are enabled or disabled by the block +// ConfigurePrecompiles checks if any of the precompiles specified by the chain config are enabled or disabled by the block // transition from [parentTimestamp] to the timestamp set in [blockContext]. If this is the case, it calls [Configure] // or [Deconfigure] to apply the necessary state transitions for the upgrade. // This function is called: // - within genesis setup to configure the starting state for precompiles enabled at genesis, // - during block processing to update the state before processing the given block. -func (c *ChainConfig) CheckConfigurePrecompiles(parentTimestamp *big.Int, blockContext precompile.BlockContext, statedb precompile.StateDB) { +func (c *ChainConfig) ConfigurePrecompiles(parentTimestamp *big.Int, blockContext precompile.BlockContext, statedb precompile.StateDB) error { blockTimestamp := blockContext.Timestamp() for _, address := range precompile.UsedAddresses { // Note: configure precompiles in a deterministic order. for _, config := range c.getActivatingPrecompileConfigs(parentTimestamp, blockTimestamp, address, c.PrecompileUpgrades) { @@ -285,8 +285,11 @@ func (c *ChainConfig) CheckConfigurePrecompiles(parentTimestamp *big.Int, blockC statedb.Finalise(true) } else { log.Info("Activating new precompile", "precompileAddress", address, "config", config) - precompile.Configure(c, blockContext, config, statedb) + if err := precompile.Configure(c, blockContext, config, statedb); err != nil { + return fmt.Errorf("could not configure precompile, precompileAddress: %s, reason: %w", address, err) + } } } } + return nil } diff --git a/precompile/allow_list.go b/precompile/allow_list.go index e411b5eba4..b4f75becdb 100644 --- a/precompile/allow_list.go +++ b/precompile/allow_list.go @@ -46,13 +46,14 @@ type AllowListConfig struct { // Configure initializes the address space of [precompileAddr] by initializing the role of each of // the addresses in [AllowListAdmins]. -func (c *AllowListConfig) Configure(state StateDB, precompileAddr common.Address) { +func (c *AllowListConfig) Configure(state StateDB, precompileAddr common.Address) error { for _, enabledAddr := range c.EnabledAddresses { setAllowListRole(state, precompileAddr, enabledAddr, AllowListEnabled) } for _, adminAddr := range c.AllowListAdmins { setAllowListRole(state, precompileAddr, adminAddr, AllowListAdmin) } + return nil } // Equal returns true iff [other] has the same admins in the same order in its allow list. @@ -219,7 +220,12 @@ func createReadAllowList(precompileAddr common.Address) RunStatefulPrecompileFun func createAllowListPrecompile(precompileAddr common.Address) StatefulPrecompiledContract { // Construct the contract with no fallback function. allowListFuncs := createAllowListFunctions(precompileAddr) - contract := newStatefulPrecompileWithFunctionSelectors(nil, allowListFuncs) + contract, err := NewStatefulPrecompileContract(nil, allowListFuncs) + // TODO Change this to be returned as an error after refactoring this precompile + // to use the new precompile template. + if err != nil { + panic(err) + } return contract } diff --git a/precompile/contract.go b/precompile/contract.go index 0e2d720273..69ed968641 100644 --- a/precompile/contract.go +++ b/precompile/contract.go @@ -98,9 +98,9 @@ type statefulPrecompileWithFunctionSelectors struct { functions map[string]*statefulPrecompileFunction } -// newStatefulPrecompileWithFunctionSelectors generates new StatefulPrecompile using [functions] as the available functions and [fallback] +// NewStatefulPrecompileContract generates new StatefulPrecompile using [functions] as the available functions and [fallback] // as an optional fallback if there is no input data. Note: the selector of [fallback] will be ignored, so it is required to be left empty. -func newStatefulPrecompileWithFunctionSelectors(fallback RunStatefulPrecompileFunc, functions []*statefulPrecompileFunction) StatefulPrecompiledContract { +func NewStatefulPrecompileContract(fallback RunStatefulPrecompileFunc, functions []*statefulPrecompileFunction) (StatefulPrecompiledContract, error) { // Construct the contract and populate [functions]. contract := &statefulPrecompileWithFunctionSelectors{ fallback: fallback, @@ -109,12 +109,12 @@ func newStatefulPrecompileWithFunctionSelectors(fallback RunStatefulPrecompileFu for _, function := range functions { _, exists := contract.functions[string(function.selector)] if exists { - panic(fmt.Errorf("cannot create stateful precompile with duplicated function selector: %q", function.selector)) + return nil, fmt.Errorf("cannot create stateful precompile with duplicated function selector: %q", function.selector) } contract.functions[string(function.selector)] = function } - return contract + return contract, nil } // Run selects the function using the 4 byte function selector at the start of the input and executes the underlying function on the diff --git a/precompile/contract_deployer_allow_list.go b/precompile/contract_deployer_allow_list.go index 7d42a270b9..11df6ff3fe 100644 --- a/precompile/contract_deployer_allow_list.go +++ b/precompile/contract_deployer_allow_list.go @@ -52,8 +52,8 @@ func (c *ContractDeployerAllowListConfig) Address() common.Address { } // Configure configures [state] with the desired admins based on [c]. -func (c *ContractDeployerAllowListConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) { - c.AllowListConfig.Configure(state, ContractDeployerAllowListAddress) +func (c *ContractDeployerAllowListConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) error { + return c.AllowListConfig.Configure(state, ContractDeployerAllowListAddress) } // Contract returns the singleton stateful precompiled contract to be used for the allow list. diff --git a/precompile/contract_native_minter.go b/precompile/contract_native_minter.go index 2da1b3bbaa..26ccaf8e68 100644 --- a/precompile/contract_native_minter.go +++ b/precompile/contract_native_minter.go @@ -71,7 +71,7 @@ func (c *ContractNativeMinterConfig) Address() common.Address { } // Configure configures [state] with the desired admins based on [c]. -func (c *ContractNativeMinterConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) { +func (c *ContractNativeMinterConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) error { for to, amount := range c.InitialMint { if amount != nil { bigIntAmount := (*big.Int)(amount) @@ -79,7 +79,7 @@ func (c *ContractNativeMinterConfig) Configure(_ ChainConfig, state StateDB, _ B } } - c.AllowListConfig.Configure(state, ContractNativeMinterAddress) + return c.AllowListConfig.Configure(state, ContractNativeMinterAddress) } // Contract returns the singleton stateful precompiled contract to be used for the native minter. @@ -157,12 +157,12 @@ func SetContractNativeMinterStatus(stateDB StateDB, address common.Address, role func PackMintInput(address common.Address, amount *big.Int) ([]byte, error) { // function selector (4 bytes) + input(hash for address + hash for amount) res := make([]byte, selectorLen+mintInputLen) - packOrderedHashesWithSelector(res, mintSignature, []common.Hash{ + err := packOrderedHashesWithSelector(res, mintSignature, []common.Hash{ address.Hash(), common.BigToHash(amount), }) - return res, nil + return res, err } // UnpackMintInput attempts to unpack [input] into the arguments to the mint precompile @@ -217,6 +217,11 @@ func createNativeMinterPrecompile(precompileAddr common.Address) StatefulPrecomp enabledFuncs = append(enabledFuncs, mintFunc) // Construct the contract with no fallback function. - contract := newStatefulPrecompileWithFunctionSelectors(nil, enabledFuncs) + contract, err := NewStatefulPrecompileContract(nil, enabledFuncs) + // Change this to be returned as an error after refactoring this precompile + // to use the new precompile template. + if err != nil { + panic(err) + } return contract } diff --git a/precompile/fee_config_manager.go b/precompile/fee_config_manager.go index 3436ba360c..56567d840a 100644 --- a/precompile/fee_config_manager.go +++ b/precompile/fee_config_manager.go @@ -109,20 +109,20 @@ func (c *FeeConfigManagerConfig) Equal(s StatefulPrecompileConfig) bool { } // Configure configures [state] with the desired admins based on [c]. -func (c *FeeConfigManagerConfig) Configure(chainConfig ChainConfig, state StateDB, blockContext BlockContext) { +func (c *FeeConfigManagerConfig) Configure(chainConfig ChainConfig, state StateDB, blockContext BlockContext) error { // Store the initial fee config into the state when the fee config manager activates. if c.InitialFeeConfig != nil { if err := StoreFeeConfig(state, *c.InitialFeeConfig, blockContext); err != nil { // This should not happen since we already checked this config with Verify() - panic(fmt.Sprintf("invalid feeConfig provided: %s", err)) + return fmt.Errorf("cannot configure given initial fee config: %w", err) } } else { if err := StoreFeeConfig(state, chainConfig.GetFeeConfig(), blockContext); err != nil { // This should not happen since we already checked the chain config in the genesis creation. - panic(fmt.Sprintf("fee config should have been verified in genesis: %s", err)) + return fmt.Errorf("cannot configure fee config in chain config: %w", err) } } - c.AllowListConfig.Configure(state, FeeConfigManagerAddress) + return c.AllowListConfig.Configure(state, FeeConfigManagerAddress) } // Contract returns the singleton stateful precompiled contract to be used for the fee manager. @@ -171,16 +171,16 @@ func PackGetLastChangedAtInput() []byte { // PackFeeConfig packs [feeConfig] without the selector into the appropriate arguments for fee config operations. func PackFeeConfig(feeConfig commontype.FeeConfig) ([]byte, error) { // input(feeConfig) - return packFeeConfigHelper(feeConfig, false), nil + return packFeeConfigHelper(feeConfig, false) } // PackSetFeeConfig packs [feeConfig] with the selector into the appropriate arguments for setting fee config operations. func PackSetFeeConfig(feeConfig commontype.FeeConfig) ([]byte, error) { // function selector (4 bytes) + input(feeConfig) - return packFeeConfigHelper(feeConfig, true), nil + return packFeeConfigHelper(feeConfig, true) } -func packFeeConfigHelper(feeConfig commontype.FeeConfig, useSelector bool) []byte { +func packFeeConfigHelper(feeConfig commontype.FeeConfig, useSelector bool) ([]byte, error) { hashes := []common.Hash{ common.BigToHash(feeConfig.GasLimit), common.BigToHash(new(big.Int).SetUint64(feeConfig.TargetBlockRate)), @@ -194,13 +194,13 @@ func packFeeConfigHelper(feeConfig commontype.FeeConfig, useSelector bool) []byt if useSelector { res := make([]byte, len(setFeeConfigSignature)+feeConfigInputLen) - packOrderedHashesWithSelector(res, setFeeConfigSignature, hashes) - return res + err := packOrderedHashesWithSelector(res, setFeeConfigSignature, hashes) + return res, err } res := make([]byte, len(hashes)*common.HashLength) - packOrderedHashes(res, hashes) - return res + err := packOrderedHashes(res, hashes) + return res, err } // UnpackFeeConfigInput attempts to unpack [input] into the arguments to the fee config precompile @@ -231,6 +231,7 @@ func UnpackFeeConfigInput(input []byte) (commontype.FeeConfig, error) { case blockGasCostStepKey: feeConfig.BlockGasCostStep = new(big.Int).SetBytes(packedElement) default: + // This should never encounter an unknown fee config key panic(fmt.Sprintf("unknown fee config key: %d", i)) } } @@ -260,6 +261,7 @@ func GetStoredFeeConfig(stateDB StateDB) commontype.FeeConfig { case blockGasCostStepKey: feeConfig.BlockGasCostStep = new(big.Int).Set(val.Big()) default: + // This should never encounter an unknown fee config key panic(fmt.Sprintf("unknown fee config key: %d", i)) } } @@ -275,7 +277,7 @@ func GetFeeConfigLastChangedAt(stateDB StateDB) *big.Int { // A validation on [feeConfig] is done before storing. func StoreFeeConfig(stateDB StateDB, feeConfig commontype.FeeConfig, blockContext BlockContext) error { if err := feeConfig.Verify(); err != nil { - return err + return fmt.Errorf("cannot verify fee config: %w", err) } for i := minFeeConfigFieldKey; i <= numFeeConfigField; i++ { @@ -298,6 +300,7 @@ func StoreFeeConfig(stateDB StateDB, feeConfig commontype.FeeConfig, blockContex case blockGasCostStepKey: input = common.BigToHash(feeConfig.BlockGasCostStep) default: + // This should never encounter an unknown fee config key panic(fmt.Sprintf("unknown fee config key: %d", i)) } stateDB.SetState(FeeConfigManagerAddress, common.Hash{byte(i)}, input) @@ -386,6 +389,11 @@ func createFeeConfigManagerPrecompile(precompileAddr common.Address) StatefulPre feeConfigManagerFunctions = append(feeConfigManagerFunctions, setFeeConfigFunc, getFeeConfigFunc, getFeeConfigLastChangedAtFunc) // Construct the contract with no fallback function. - contract := newStatefulPrecompileWithFunctionSelectors(nil, feeConfigManagerFunctions) + contract, err := NewStatefulPrecompileContract(nil, feeConfigManagerFunctions) + // TODO Change this to be returned as an error after refactoring this precompile + // to use the new precompile template. + if err != nil { + panic(err) + } return contract } diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go index f57b0f72a8..7262efa0b8 100644 --- a/precompile/reward_manager.go +++ b/precompile/reward_manager.go @@ -76,7 +76,7 @@ func (c *InitialRewardConfig) Equal(other *InitialRewardConfig) bool { return c.AllowFeeRecipients == other.AllowFeeRecipients && c.RewardAddress == other.RewardAddress } -func (i *InitialRewardConfig) Configure(state StateDB) { +func (i *InitialRewardConfig) Configure(state StateDB) error { // enable allow fee recipients if i.AllowFeeRecipients { EnableAllowFeeRecipients(state) @@ -86,10 +86,9 @@ func (i *InitialRewardConfig) Configure(state StateDB) { DisableFeeRewards(state) } else { // set reward address - if err := StoreRewardAddress(state, i.RewardAddress); err != nil { - panic(err) - } + return StoreRewardAddress(state, i.RewardAddress) } + return nil } // RewardManagerConfig implements the StatefulPrecompileConfig @@ -106,7 +105,10 @@ func init() { panic(err) } RewardManagerABI = parsed - RewardManagerPrecompile = createRewardManagerPrecompile(RewardManagerAddress) + RewardManagerPrecompile, err = createRewardManagerPrecompile(RewardManagerAddress) + if err != nil { + panic(err) + } } // NewRewardManagerConfig returns a config for a network upgrade at [blockTimestamp] that enables @@ -161,11 +163,11 @@ func (c *RewardManagerConfig) Address() common.Address { } // Configure configures [state] with the initial configuration. -func (c *RewardManagerConfig) Configure(chainConfig ChainConfig, state StateDB, _ BlockContext) { +func (c *RewardManagerConfig) Configure(chainConfig ChainConfig, state StateDB, _ BlockContext) error { c.AllowListConfig.Configure(state, RewardManagerAddress) // configure the RewardManager with the given initial configuration if c.InitialRewardConfig != nil { - c.InitialRewardConfig.Configure(state) + return c.InitialRewardConfig.Configure(state) } else if chainConfig.AllowedFeeRecipients() { // configure the RewardManager according to chainConfig EnableAllowFeeRecipients(state) @@ -175,6 +177,7 @@ func (c *RewardManagerConfig) Configure(chainConfig ChainConfig, state StateDB, // default to disabling rewards DisableFeeRewards(state) } + return nil } // Contract returns the singleton stateful precompiled contract to be used for RewardManager. @@ -419,41 +422,25 @@ func disableRewards(accessibleState PrecompileAccessibleState, caller common.Add // createRewardManagerPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile. // Access to the getters/setters is controlled by an allow list for [precompileAddr]. -func createRewardManagerPrecompile(precompileAddr common.Address) StatefulPrecompiledContract { +func createRewardManagerPrecompile(precompileAddr common.Address) (StatefulPrecompiledContract, error) { var functions []*statefulPrecompileFunction functions = append(functions, createAllowListFunctions(precompileAddr)...) - - methodAllowFeeRecipients, ok := RewardManagerABI.Methods["allowFeeRecipients"] - if !ok { - panic("given method does not exist in the ABI") - } - functions = append(functions, newStatefulPrecompileFunction(methodAllowFeeRecipients.ID, allowFeeRecipients)) - - methodAreFeeRecipientsAllowed, ok := RewardManagerABI.Methods["areFeeRecipientsAllowed"] - if !ok { - panic("given method does not exist in the ABI") - } - functions = append(functions, newStatefulPrecompileFunction(methodAreFeeRecipientsAllowed.ID, areFeeRecipientsAllowed)) - - methodCurrentRewardAddress, ok := RewardManagerABI.Methods["currentRewardAddress"] - if !ok { - panic("given method does not exist in the ABI") - } - functions = append(functions, newStatefulPrecompileFunction(methodCurrentRewardAddress.ID, currentRewardAddress)) - - methodDisableRewards, ok := RewardManagerABI.Methods["disableRewards"] - if !ok { - panic("given method does not exist in the ABI") - } - functions = append(functions, newStatefulPrecompileFunction(methodDisableRewards.ID, disableRewards)) - - methodSetRewardAddress, ok := RewardManagerABI.Methods["setRewardAddress"] - if !ok { - panic("given method does not exist in the ABI") + abiFunctionMap := map[string]RunStatefulPrecompileFunc{ + "allowFeeRecipients": allowFeeRecipients, + "areFeeRecipientsAllowed": areFeeRecipientsAllowed, + "currentRewardAddress": currentRewardAddress, + "disableRewards": disableRewards, + "setRewardAddress": setRewardAddress, + } + + for name, function := range abiFunctionMap { + method, ok := RewardManagerABI.Methods[name] + if !ok { + return nil, fmt.Errorf("given method (%s) does not exist in the ABI", name) + } + functions = append(functions, newStatefulPrecompileFunction(method.ID, function)) } - functions = append(functions, newStatefulPrecompileFunction(methodSetRewardAddress.ID, setRewardAddress)) // Construct the contract with no fallback function. - contract := newStatefulPrecompileWithFunctionSelectors(nil, functions) - return contract + return NewStatefulPrecompileContract(nil, functions) } diff --git a/precompile/stateful_precompile_config.go b/precompile/stateful_precompile_config.go index 7db30060e4..6955b8d448 100644 --- a/precompile/stateful_precompile_config.go +++ b/precompile/stateful_precompile_config.go @@ -32,7 +32,7 @@ type StatefulPrecompileConfig interface { // Configure is called on the first block where the stateful precompile should be enabled. This // provides the config the ability to set its initial state and should only modify the state within // its own address space. - Configure(ChainConfig, StateDB, BlockContext) + Configure(ChainConfig, StateDB, BlockContext) error // Contract returns a thread-safe singleton that can be used as the StatefulPrecompiledContract when // this config is enabled. Contract() StatefulPrecompiledContract @@ -45,7 +45,7 @@ type StatefulPrecompileConfig interface { // Configure sets the nonce and code to non-empty values then calls Configure on [precompileConfig] to make the necessary // state update to enable the StatefulPrecompile. // Assumes that [precompileConfig] is non-nil. -func Configure(chainConfig ChainConfig, blockContext BlockContext, precompileConfig StatefulPrecompileConfig, state StateDB) { +func Configure(chainConfig ChainConfig, blockContext BlockContext, precompileConfig StatefulPrecompileConfig, state StateDB) error { // Set the nonce of the precompile's address (as is done when a contract is created) to ensure // that it is marked as non-empty and will not be cleaned up when the statedb is finalized. state.SetNonce(precompileConfig.Address(), 1) @@ -53,5 +53,5 @@ func Configure(chainConfig ChainConfig, blockContext BlockContext, precompileCon // can be called from within Solidity contracts. Solidity adds a check before invoking a contract to ensure // that it does not attempt to invoke a non-existent contract. state.SetCode(precompileConfig.Address(), []byte{0x1}) - precompileConfig.Configure(chainConfig, state, blockContext) + return precompileConfig.Configure(chainConfig, state, blockContext) } diff --git a/precompile/tx_allow_list.go b/precompile/tx_allow_list.go index c67e07011f..4ab39a8f04 100644 --- a/precompile/tx_allow_list.go +++ b/precompile/tx_allow_list.go @@ -55,8 +55,8 @@ func (c *TxAllowListConfig) Address() common.Address { } // Configure configures [state] with the desired admins based on [c]. -func (c *TxAllowListConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) { - c.AllowListConfig.Configure(state, TxAllowListAddress) +func (c *TxAllowListConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) error { + return c.AllowListConfig.Configure(state, TxAllowListAddress) } // Contract returns the singleton stateful precompiled contract to be used for the allow list. diff --git a/precompile/utils.go b/precompile/utils.go index 2476a97e34..6da328f9c8 100644 --- a/precompile/utils.go +++ b/precompile/utils.go @@ -36,16 +36,16 @@ func deductGas(suppliedGas uint64, requiredGas uint64) (uint64, error) { // packOrderedHashesWithSelector packs the function selector and ordered list of hashes into [dst] // byte slice. // assumes that [dst] has sufficient room for [functionSelector] and [hashes]. -func packOrderedHashesWithSelector(dst []byte, functionSelector []byte, hashes []common.Hash) { +func packOrderedHashesWithSelector(dst []byte, functionSelector []byte, hashes []common.Hash) error { copy(dst[:len(functionSelector)], functionSelector) - packOrderedHashes(dst[len(functionSelector):], hashes) + return packOrderedHashes(dst[len(functionSelector):], hashes) } // packOrderedHashes packs the ordered list of [hashes] into the [dst] byte buffer. // assumes that [dst] has sufficient space to pack [hashes] or else this function will panic. -func packOrderedHashes(dst []byte, hashes []common.Hash) { +func packOrderedHashes(dst []byte, hashes []common.Hash) error { if len(dst) != len(hashes)*common.HashLength { - panic(fmt.Sprintf("destination byte buffer has insufficient length (%d) for %d hashes", len(dst), len(hashes))) + return fmt.Errorf("destination byte buffer has insufficient length (%d) for %d hashes", len(dst), len(hashes)) } var ( @@ -57,6 +57,7 @@ func packOrderedHashes(dst []byte, hashes []common.Hash) { start += common.HashLength end += common.HashLength } + return nil } // returnPackedHash returns packed the byte slice with common.HashLength from [packed] From 8868a99c57997eab733e93184249068c7bcb9ea3 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 6 Jan 2023 12:29:03 +0300 Subject: [PATCH 04/23] Precompile Specific Packages (#420) * rework on panics in precompiles * Update precompile/allow_list.go Co-authored-by: aaronbuchwald * Update precompile/fee_config_manager.go Co-authored-by: aaronbuchwald * Update precompile/fee_config_manager.go Co-authored-by: aaronbuchwald * fix reviews * wrap errors in ConfigurePrecompiles * cleaner errors * move reward manager precompile to package (WIP) * rename files * fix abi path * move typecheck * move precompiles to their own packages * refactor precompile template * remove test file * upate comments * rm test files * new allowlist package * Update precompile/utils.go Co-authored-by: Darioush Jalali * Update precompile/nativeminter/contract_native_minter.go Co-authored-by: Darioush Jalali * Update precompile/nativeminter/contract_native_minter.go Co-authored-by: Darioush Jalali * Update precompile/utils.go Co-authored-by: Darioush Jalali * Update precompile/nativeminter/contract_native_minter.go Co-authored-by: Darioush Jalali * fix nits Co-authored-by: aaronbuchwald Co-authored-by: Darioush Jalali --- accounts/abi/bind/bind.go | 4 +- accounts/abi/bind/precompile_bind.go | 26 +- accounts/abi/bind/precompile_bind_test.go | 2 +- .../abi/bind/precompile_config_template.go | 151 ++++++ ...ate.go => precompile_contract_template.go} | 140 ++--- cmd/precompilegen/main.go | 61 ++- core/blockchain_reader.go | 8 +- core/genesis_test.go | 8 +- core/state_processor_test.go | 4 +- core/state_transition.go | 5 +- core/stateful_precompile_test.go | 490 +++++++++--------- core/test_blockchain.go | 45 +- core/tx_pool.go | 5 +- core/vm/evm.go | 3 +- eth/gasprice/gasprice_test.go | 5 +- params/precompile_config.go | 25 +- params/precompile_config_test.go | 37 +- params/upgrade_config_test.go | 63 +-- plugin/evm/vm_test.go | 75 +-- plugin/evm/vm_upgrade_bytes_test.go | 10 +- precompile/{ => allowlist}/allow_list.go | 69 +-- precompile/{ => allowlist}/allow_list_role.go | 2 +- precompile/config_test.go | 462 ----------------- precompile/contract.go | 76 +-- precompile/contract_native_minter.go | 227 -------- .../config.go} | 43 +- precompile/deployerallowlist/config_test.go | 95 ++++ precompile/deployerallowlist/contract.go | 26 + precompile/feemanager/config.go | 110 ++++ precompile/feemanager/config_test.go | 128 +++++ .../contract.go} | 170 ++---- precompile/interface.go | 61 +++ precompile/mock_interface.go | 115 ++++ precompile/nativeminter/config.go | 126 +++++ precompile/nativeminter/config_test.go | 149 ++++++ precompile/nativeminter/contract.go | 118 +++++ precompile/params.go | 4 +- precompile/rewardmanager/config.go | 153 ++++++ precompile/rewardmanager/config_test.go | 121 +++++ .../contract.abi} | 0 .../contract.go} | 223 ++------ precompile/stateful_precompile_config.go | 4 +- .../config.go} | 46 +- precompile/txallowlist/config_test.go | 105 ++++ precompile/txallowlist/contract.go | 33 ++ precompile/utils.go | 19 +- tests/e2e/utils/evm_client.go | 4 +- 47 files changed, 2167 insertions(+), 1689 deletions(-) create mode 100644 accounts/abi/bind/precompile_config_template.go rename accounts/abi/bind/{precompile_template.go => precompile_contract_template.go} (69%) rename precompile/{ => allowlist}/allow_list.go (68%) rename precompile/{ => allowlist}/allow_list_role.go (98%) delete mode 100644 precompile/config_test.go delete mode 100644 precompile/contract_native_minter.go rename precompile/{contract_deployer_allow_list.go => deployerallowlist/config.go} (58%) create mode 100644 precompile/deployerallowlist/config_test.go create mode 100644 precompile/deployerallowlist/contract.go create mode 100644 precompile/feemanager/config.go create mode 100644 precompile/feemanager/config_test.go rename precompile/{fee_config_manager.go => feemanager/contract.go} (56%) create mode 100644 precompile/interface.go create mode 100644 precompile/mock_interface.go create mode 100644 precompile/nativeminter/config.go create mode 100644 precompile/nativeminter/config_test.go create mode 100644 precompile/nativeminter/contract.go create mode 100644 precompile/rewardmanager/config.go create mode 100644 precompile/rewardmanager/config_test.go rename precompile/{reward_manager.abi => rewardmanager/contract.abi} (100%) rename precompile/{reward_manager.go => rewardmanager/contract.go} (52%) rename precompile/{tx_allow_list.go => txallowlist/config.go} (56%) create mode 100644 precompile/txallowlist/config_test.go create mode 100644 precompile/txallowlist/contract.go diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index c2be8309e1..d15d452f89 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -51,7 +51,7 @@ const ( readAllowListFuncKey = "readAllowList" ) -type BindHook func(lang Lang, types []string, contracts map[string]*tmplContract, structs map[string]*tmplStruct) (data interface{}, templateSource string, err error) +type BindHook func(lang Lang, pkg string, 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 @@ -295,7 +295,7 @@ func bindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s // Generate the contract template data according to hook if bindHook != nil { var err error - data, templateSource, err = bindHook(lang, types, contracts, structs) + data, templateSource, err = bindHook(lang, pkg, types, contracts, structs) if err != nil { return "", err } diff --git a/accounts/abi/bind/precompile_bind.go b/accounts/abi/bind/precompile_bind.go index 3b33478b43..d31d9d0fef 100644 --- a/accounts/abi/bind/precompile_bind.go +++ b/accounts/abi/bind/precompile_bind.go @@ -35,9 +35,23 @@ import ( "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) { +// PrecompileBind generates a Go binding for a precompiled contract. It returns config binding and contract binding. +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, string, error) { + // create hooks + configHook := createPrecompileHook(abifilename, tmplSourcePrecompileConfigGo) + contractHook := createPrecompileHook(abifilename, tmplSourcePrecompileContractGo) + + configBind, err := bindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, configHook) + if err != nil { + return "", "", err + } + contractBind, err := bindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, contractHook) + + return configBind, contractBind, err +} + +func createPrecompileHook(abifilename string, template string) BindHook { + var bindHook BindHook = func(lang Lang, pkg string, 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") @@ -82,11 +96,11 @@ func PrecompileBind(types []string, abis []string, bytecodes []string, fsigs []m data := &tmplPrecompileData{ Contract: precompileContract, Structs: structs, + Package: pkg, } - return data, tmplSourcePrecompileGo, nil + return data, template, nil } - - return bindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, createPrecompileFunc) + return bindHook } func allowListEnabled(funcs map[string]*tmplMethod) bool { diff --git a/accounts/abi/bind/precompile_bind_test.go b/accounts/abi/bind/precompile_bind_test.go index 8ac85fb349..23f1503142 100644 --- a/accounts/abi/bind/precompile_bind_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 := PrecompileBind([]string{tt.name}, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases, "") + _, _, 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_config_template.go b/accounts/abi/bind/precompile_config_template.go new file mode 100644 index 0000000000..52ff02d268 --- /dev/null +++ b/accounts/abi/bind/precompile_config_template.go @@ -0,0 +1,151 @@ +// (c) 2019-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package bind + +// tmplSourcePrecompileConfigGo is the Go precompiled config source template. +const tmplSourcePrecompileConfigGo = ` +// Code generated +// This file is a generated precompile contract config with stubbed abstract functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +// There are some must-be-done changes waiting in the file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. +// Additionally there are other files you need to edit to activate your precompile. +// These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". +// For testing take a look at other precompile tests in core/stateful_precompile_test.go and config_test.go in other precompile folders. + +/* General guidelines for precompile development: +1- Read the comment and set a suitable contract address in precompile/params.go. E.g: + {{.Contract.Type}}Address = common.HexToAddress("ASUITABLEHEXADDRESS") +2- Set gas costs in contract.go +3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. +Typically, custom codes are required in only those areas. +4- Add your upgradable config in params/precompile_config.go +5- Add your precompile upgrade in params/config.go +6- Add your config unit test in {generatedpkg}/config_test.go +7- Add your solidity interface and test contract to contract-examples/contracts +8- Write solidity tests for your precompile in contract-examples/test +9- Create your genesis with your precompile enabled in tests/e2e/genesis/ +10- Create e2e test for your solidity test in tests/e2e/solidity/suites.go +11- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' + +*/ + +package {{.Package}} + +import ( + "encoding/json" + "math/big" + + "github.com/ava-labs/subnet-evm/precompile" + + "github.com/ethereum/go-ethereum/common" +) + +{{$contract := .Contract}} +var ( + _ precompile.StatefulPrecompileConfig = &{{.Contract.Type}}Config{} +) + +// {{.Contract.Type}}Config implements the StatefulPrecompileConfig +// interface while adding in the {{.Contract.Type}} specific precompile address. +type {{.Contract.Type}}Config struct { + {{- if .Contract.AllowList}} + precompile.AllowListConfig + {{- end}} + precompile.UpgradeableConfig +} + +{{$structs := .Structs}} +{{range $structs}} + // {{.Name}} is an auto generated low-level Go binding around an user-defined struct. + type {{.Name}} struct { + {{range $field := .Fields}} + {{$field.Name}} {{$field.Type}}{{end}} + } +{{- end}} + +{{- range .Contract.Funcs}} +{{ if len .Normalized.Inputs | lt 1}} +type {{capitalise .Normalized.Name}}Input struct{ +{{range .Normalized.Inputs}} {{capitalise .Name}} {{bindtype .Type $structs}}; {{end}} +} +{{- end}} +{{ if len .Normalized.Outputs | lt 1}} +type {{capitalise .Normalized.Name}}Output struct{ +{{range .Normalized.Outputs}} {{capitalise .Name}} {{bindtype .Type $structs}}; {{end}} +} +{{- end}} +{{- end}} + +// New{{.Contract.Type}}Config returns a config for a network upgrade at [blockTimestamp] that enables +// {{.Contract.Type}} {{if .Contract.AllowList}} with the given [admins] as members of the allowlist {{end}}. +func New{{.Contract.Type}}Config(blockTimestamp *big.Int{{if .Contract.AllowList}}, admins []common.Address{{end}}) *{{.Contract.Type}}Config { + return &{{.Contract.Type}}Config{ + {{if .Contract.AllowList}}AllowListConfig: precompile.AllowListConfig{AllowListAdmins: admins},{{end}} + UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: blockTimestamp}, + } +} + +// NewDisable{{.Contract.Type}}Config returns config for a network upgrade at [blockTimestamp] +// that disables {{.Contract.Type}}. +func NewDisable{{.Contract.Type}}Config(blockTimestamp *big.Int) *{{.Contract.Type}}Config { + return &{{.Contract.Type}}Config{ + UpgradeableConfig: precompile.UpgradeableConfig{ + BlockTimestamp: blockTimestamp, + Disable: true, + }, + } +} + +// Verify tries to verify {{.Contract.Type}}Config and returns an error accordingly. +func (c *{{.Contract.Type}}Config) Verify() error { + {{if .Contract.AllowList}} + // Verify AllowList first + if err := c.AllowListConfig.Verify(); err != nil { + return err + } + {{end}} + // CUSTOM CODE STARTS HERE + // Add your own custom verify code for {{.Contract.Type}}Config here + // and return an error accordingly + return nil +} + +// Equal returns true if [s] is a [*{{.Contract.Type}}Config] and it has been configured identical to [c]. +func (c *{{.Contract.Type}}Config) Equal(s precompile.StatefulPrecompileConfig) bool { + // typecast before comparison + other, ok := (s).(*{{.Contract.Type}}Config) + if !ok { + return false + } + // CUSTOM CODE STARTS HERE + // modify this boolean accordingly with your custom {{.Contract.Type}}Config, to check if [other] and the current [c] are equal + // if {{.Contract.Type}}Config contains only UpgradeableConfig {{if .Contract.AllowList}} and AllowListConfig {{end}} you can skip modifying it. + equals := c.UpgradeableConfig.Equal(&other.UpgradeableConfig) {{if .Contract.AllowList}} && c.AllowListConfig.Equal(&other.AllowListConfig) {{end}} + return equals +} + +// Address returns the address of the {{.Contract.Type}}. Addresses reside under the precompile/params.go +// Select a non-conflicting address and set it in the params.go. +func (c *{{.Contract.Type}}Config) Address() common.Address { + return {{.Contract.Type}}Address +} + +// Configure configures [state] with the initial configuration. +func (c *{{.Contract.Type}}Config) Configure(_ precompile.ChainConfig, state precompile.StateDB, _ precompile.BlockContext) error { + {{if .Contract.AllowList}}c.AllowListConfig.Configure(state, {{.Contract.Type}}Address){{end}} + // CUSTOM CODE STARTS HERE + return nil +} + +// Contract returns the singleton stateful precompiled contract to be used for {{.Contract.Type}}. +func (c *{{.Contract.Type}}Config) Contract() precompile.StatefulPrecompiledContract { + return {{.Contract.Type}}Precompile +} + +// String returns a string representation of the {{.Contract.Type}}Config. +func (c *{{.Contract.Type}}Config) String() string { + bytes, _ := json.Marshal(c) + return string(bytes) +} +` diff --git a/accounts/abi/bind/precompile_template.go b/accounts/abi/bind/precompile_contract_template.go similarity index 69% rename from accounts/abi/bind/precompile_template.go rename to accounts/abi/bind/precompile_contract_template.go index 596d96c351..6dd7967b3e 100644 --- a/accounts/abi/bind/precompile_template.go +++ b/accounts/abi/bind/precompile_contract_template.go @@ -4,6 +4,7 @@ package bind // tmplPrecompileData is the data structure required to fill the binding template. type tmplPrecompileData struct { + Package string Contract *tmplPrecompileContract // The contract to generate into this file Structs map[string]*tmplStruct // Contract struct type definitions } @@ -16,8 +17,8 @@ type tmplPrecompileContract struct { ABIFilename string // Path to the ABI file } -// tmplSourcePrecompileGo is the Go precompiled source template. -const tmplSourcePrecompileGo = ` +// tmplSourcePrecompileContractGo is the Go precompiled contract source template. +const tmplSourcePrecompileContractGo = ` // Code generated // This file is a generated precompile contract with stubbed abstract functions. // The file is generated by a template. Please inspect every code and comment in this file before use. @@ -30,20 +31,21 @@ const tmplSourcePrecompileGo = ` /* General guidelines for precompile development: 1- Read the comment and set a suitable contract address in precompile/params.go. E.g: {{.Contract.Type}}Address = common.HexToAddress("ASUITABLEHEXADDRESS") -2- Set gas costs here +2- Set gas costs in contract.go 3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. Typically, custom codes are required in only those areas. 4- Add your upgradable config in params/precompile_config.go 5- Add your precompile upgrade in params/config.go -6- Add your solidity interface and test contract to contract-examples/contracts -7- Write solidity tests for your precompile in contract-examples/test -8- Create your genesis with your precompile enabled in tests/e2e/genesis/ -9- Create e2e test for your solidity test in tests/e2e/solidity/suites.go -10- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' +6- Add your config unit test in {generatedpkg}/config_test.go +7- Add your solidity interface and test contract to contract-examples/contracts +8- Write solidity tests for your precompile in contract-examples/test +9- Create your genesis with your precompile enabled in tests/e2e/genesis/ +10- Create e2e test for your solidity test in tests/e2e/solidity/suites.go +11- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' */ -package precompile +package {{.Package}} import ( "encoding/json" @@ -53,6 +55,7 @@ import ( "strings" "github.com/ava-labs/subnet-evm/accounts/abi" + "github.com/ava-labs/subnet-evm/precompile" "github.com/ava-labs/subnet-evm/vmerrs" _ "embed" @@ -82,8 +85,6 @@ var ( {{$contract := .Contract}} // Singleton StatefulPrecompiledContract and signatures. var ( - _ StatefulPrecompileConfig = &{{.Contract.Type}}Config{} - {{- range .Contract.Funcs}} {{- if not .Original.IsConstant | and $contract.AllowList}} @@ -105,22 +106,13 @@ var ( {{- end}} {{.Contract.Type}}ABI abi.ABI // will be initialized by init function - {{.Contract.Type}}Precompile StatefulPrecompiledContract // will be initialized by init function + {{.Contract.Type}}Precompile precompile.StatefulPrecompiledContract // will be initialized by init function // CUSTOM CODE STARTS HERE // THIS SHOULD BE MOVED TO precompile/params.go with a suitable hex address. {{.Contract.Type}}Address = common.HexToAddress("ASUITABLEHEXADDRESS") ) -// {{.Contract.Type}}Config implements the StatefulPrecompileConfig -// interface while adding in the {{.Contract.Type}} specific precompile address. -type {{.Contract.Type}}Config struct { - {{- if .Contract.AllowList}} - AllowListConfig - {{- end}} - UpgradeableConfig -} - {{$structs := .Structs}} {{range $structs}} // {{.Name}} is an auto generated low-level Go binding around an user-defined struct. @@ -156,82 +148,10 @@ func init() { } } -// New{{.Contract.Type}}Config returns a config for a network upgrade at [blockTimestamp] that enables -// {{.Contract.Type}} {{if .Contract.AllowList}} with the given [admins] as members of the allowlist {{end}}. -func New{{.Contract.Type}}Config(blockTimestamp *big.Int{{if .Contract.AllowList}}, admins []common.Address{{end}}) *{{.Contract.Type}}Config { - return &{{.Contract.Type}}Config{ - {{if .Contract.AllowList}}AllowListConfig: AllowListConfig{AllowListAdmins: admins},{{end}} - UpgradeableConfig: UpgradeableConfig{BlockTimestamp: blockTimestamp}, - } -} - -// NewDisable{{.Contract.Type}}Config returns config for a network upgrade at [blockTimestamp] -// that disables {{.Contract.Type}}. -func NewDisable{{.Contract.Type}}Config(blockTimestamp *big.Int) *{{.Contract.Type}}Config { - return &{{.Contract.Type}}Config{ - UpgradeableConfig: UpgradeableConfig{ - BlockTimestamp: blockTimestamp, - Disable: true, - }, - } -} - -// Equal returns true if [s] is a [*{{.Contract.Type}}Config] and it has been configured identical to [c]. -func (c *{{.Contract.Type}}Config) Equal(s StatefulPrecompileConfig) bool { - // typecast before comparison - other, ok := (s).(*{{.Contract.Type}}Config) - if !ok { - return false - } - // CUSTOM CODE STARTS HERE - // modify this boolean accordingly with your custom {{.Contract.Type}}Config, to check if [other] and the current [c] are equal - // if {{.Contract.Type}}Config contains only UpgradeableConfig {{if .Contract.AllowList}} and AllowListConfig {{end}} you can skip modifying it. - equals := c.UpgradeableConfig.Equal(&other.UpgradeableConfig) {{if .Contract.AllowList}} && c.AllowListConfig.Equal(&other.AllowListConfig) {{end}} - return equals -} - -// String returns a string representation of the {{.Contract.Type}}Config. -func (c *{{.Contract.Type}}Config) String() string { - bytes, _ := json.Marshal(c) - return string(bytes) -} - -// Address returns the address of the {{.Contract.Type}}. Addresses reside under the precompile/params.go -// Select a non-conflicting address and set it in the params.go. -func (c *{{.Contract.Type}}Config) Address() common.Address { - return {{.Contract.Type}}Address -} - -// Configure configures [state] with the initial configuration. -func (c *{{.Contract.Type}}Config) Configure(_ ChainConfig, state StateDB, _ BlockContext) error { - {{if .Contract.AllowList}}c.AllowListConfig.Configure(state, {{.Contract.Type}}Address){{end}} - // CUSTOM CODE STARTS HERE - return nil -} - -// Contract returns the singleton stateful precompiled contract to be used for {{.Contract.Type}}. -func (c *{{.Contract.Type}}Config) Contract() StatefulPrecompiledContract { - return {{.Contract.Type}}Precompile -} - -// Verify tries to verify {{.Contract.Type}}Config and returns an error accordingly. -func (c *{{.Contract.Type}}Config) Verify() error { - {{if .Contract.AllowList}} - // Verify AllowList first - if err := c.AllowListConfig.Verify(); err != nil { - return err - } - {{end}} - // CUSTOM CODE STARTS HERE - // Add your own custom verify code for {{.Contract.Type}}Config here - // and return an error accordingly - return nil -} - {{if .Contract.AllowList}} // Get{{.Contract.Type}}AllowListStatus returns the role of [address] for the {{.Contract.Type}} list. -func Get{{.Contract.Type}}AllowListStatus(stateDB StateDB, address common.Address) AllowListRole { - return getAllowListStatus(stateDB, {{.Contract.Type}}Address, address) +func Get{{.Contract.Type}}AllowListStatus(stateDB precompile.StateDB, address common.Address) precompile.AllowListRole { + return precompile.GetAllowListStatus(stateDB, {{.Contract.Type}}Address, address) } // Set{{.Contract.Type}}AllowListStatus sets the permissions of [address] to [role] for the @@ -240,8 +160,8 @@ func Get{{.Contract.Type}}AllowListStatus(stateDB StateDB, address common.Addres // and [address] hash. It means that any reusage of the [address] key for different value // conflicts with the same slot [role] is stored. // Precompile implementations must use a different key than [address] for their storage. -func Set{{.Contract.Type}}AllowListStatus(stateDB StateDB, address common.Address, role AllowListRole) { - setAllowListRole(stateDB, {{.Contract.Type}}Address, address, role) +func Set{{.Contract.Type}}AllowListStatus(stateDB precompile.StateDB, address common.Address, role precompile.AllowListRole) { + precompile.SetAllowListRole(stateDB, {{.Contract.Type}}Address, address, role) } {{end}} @@ -309,8 +229,8 @@ func Pack{{$method.Normalized.Name}}Output ({{decapitalise $output.Name}} {{bind } {{end}} -func {{decapitalise .Normalized.Name}}(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = deductGas(suppliedGas, {{.Normalized.Name}}GasCost); err != nil { +func {{decapitalise .Normalized.Name}}(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = precompile.DeductGas(suppliedGas, {{.Normalized.Name}}GasCost); err != nil { return nil, 0, err } @@ -338,7 +258,7 @@ func {{decapitalise .Normalized.Name}}(accessibleState PrecompileAccessibleState // You can modify/delete this code if you don't want this function to be restricted by the allow list. stateDB := accessibleState.GetStateDB() // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := getAllowListStatus(stateDB, {{$contract.Type}}Address, caller) + callerStatus := precompile.GetAllowListStatus(stateDB, {{$contract.Type}}Address, caller) if !callerStatus.IsEnabled() { return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannot{{.Normalized.Name}}, caller) } @@ -374,8 +294,8 @@ func {{decapitalise .Normalized.Name}}(accessibleState PrecompileAccessibleState {{- with .Contract.Fallback}} // {{decapitalise $contract.Type}}Fallback executed if a function identifier does not match any of the available functions in a smart contract. // This function cannot take an input or return an output. -func {{decapitalise $contract.Type}}Fallback (accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, _ []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = deductGas(suppliedGas, {{$contract.Type}}FallbackGasCost); err != nil { +func {{decapitalise $contract.Type}}Fallback (accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, _ []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = precompile.DeductGas(suppliedGas, {{$contract.Type}}FallbackGasCost); err != nil { return nil, 0, err } @@ -389,7 +309,7 @@ func {{decapitalise $contract.Type}}Fallback (accessibleState PrecompileAccessib // You can modify/delete this code if you don't want this function to be restricted by the allow list. stateDB := accessibleState.GetStateDB() // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := getAllowListStatus(stateDB, {{$contract.Type}}Address, caller) + callerStatus := precompile.GetAllowListStatus(stateDB, {{$contract.Type}}Address, caller) if !callerStatus.IsEnabled() { return nil, remainingGas, fmt.Errorf("%w: %s", Err{{$contract.Type}}CannotFallback, caller) } @@ -410,13 +330,13 @@ func {{decapitalise $contract.Type}}Fallback (accessibleState PrecompileAccessib // create{{.Contract.Type}}Precompile returns a StatefulPrecompiledContract with getters and setters for the precompile. {{if .Contract.AllowList}} // Access to the getters/setters is controlled by an allow list for [precompileAddr].{{end}} -func create{{.Contract.Type}}Precompile(precompileAddr common.Address) (StatefulPrecompiledContract, error) { - var functions []*statefulPrecompileFunction +func create{{.Contract.Type}}Precompile(precompileAddr common.Address) (precompile.StatefulPrecompiledContract, error) { + var functions []*precompile.StatefulPrecompileFunction {{- if .Contract.AllowList}} - functions = append(functions, createAllowListFunctions(precompileAddr)...) + functions = append(functions, precompile.CreateAllowListFunctions(precompileAddr)...) {{- end}} - abiFunctionMap := map[string]RunStatefulPrecompileFunc{ + abiFunctionMap := map[string]precompile.RunStatefulPrecompileFunc{ {{- range .Contract.Funcs}} "{{.Original.Name}}": {{decapitalise .Normalized.Name}}, {{- end}} @@ -427,15 +347,15 @@ func create{{.Contract.Type}}Precompile(precompileAddr common.Address) (Stateful if !ok { return nil, fmt.Errorf("given method (%s) does not exist in the ABI", name) } - functions = append(functions, newStatefulPrecompileFunction(method.ID, function)) + functions = append(functions, precompile.NewStatefulPrecompileFunction(method.ID, function)) } {{- if .Contract.Fallback}} // Construct the contract with the fallback function. - return NewStatefulPrecompileContract({{decapitalise $contract.Type}}Fallback, functions) + return precompile.NewStatefulPrecompileContract({{decapitalise $contract.Type}}Fallback, functions) {{- else}} // Construct the contract with no fallback function. - return NewStatefulPrecompileContract(nil, functions) + return precompile.NewStatefulPrecompileContract(nil, functions) {{- end}} } ` diff --git a/cmd/precompilegen/main.go b/cmd/precompilegen/main.go index 454a560bc5..0a60ed187f 100644 --- a/cmd/precompilegen/main.go +++ b/cmd/precompilegen/main.go @@ -56,15 +56,15 @@ var ( } typeFlag = &cli.StringFlag{ Name: "type", - Usage: "Struct name for the precompile (default = ABI name)", + Usage: "Struct name for the precompile (default = {ABI name})", } pkgFlag = &cli.StringFlag{ Name: "pkg", - Usage: "Package name to generate the precompile into (default = precompile)", + Usage: "Package name to generate the precompile into (default = {type})", } outFlag = &cli.StringFlag{ Name: "out", - Usage: "Output file for the generated precompile (default = STDOUT)", + Usage: "Output folder for the generated precompile files, - for STDOUT (default = ./{pkg})", } ) @@ -81,13 +81,12 @@ func init() { } func precompilegen(c *cli.Context) error { - if !c.IsSet(outFlag.Name) && !c.IsSet(typeFlag.Name) { + outFlagStr := c.String(outFlag.Name) + isOutStdout := outFlagStr == "-" + + if isOutStdout && !c.IsSet(typeFlag.Name) { utils.Fatalf("type (--type) should be set explicitly for STDOUT ") } - pkg := pkgFlag.Name - if pkg == "" { - pkg = "precompile" - } lang := bind.LangGo // If the entire solidity code was specified, build and bind based on that var ( @@ -106,6 +105,7 @@ func precompilegen(c *cli.Context) error { abi []byte err error ) + input := c.String(abiFlag.Name) if input == "-" { abi, err = io.ReadAll(os.Stdin) @@ -116,7 +116,9 @@ func precompilegen(c *cli.Context) error { utils.Fatalf("Failed to read input ABI: %v", err) } abis = append(abis, string(abi)) + bins = append(bins, "") + kind := c.String(typeFlag.Name) if kind == "" { fn := filepath.Base(input) @@ -124,30 +126,51 @@ func precompilegen(c *cli.Context) error { kind = strings.TrimSpace(kind) } types = append(types, kind) - outFlagSet := c.IsSet(outFlag.Name) - outFlag := c.String(outFlag.Name) + + pkg := c.String(pkgFlag.Name) + if pkg == "" { + pkg = strings.ToLower(kind) + } + + if outFlagStr == "" { + outFlagStr = filepath.Join("./", pkg) + } + abifilename := "" abipath := "" // we should not generate the abi file if output is set to stdout - if outFlagSet { + if !isOutStdout { // get file name from the output path - pathNoExt := strings.TrimSuffix(outFlag, filepath.Ext(outFlag)) - abipath = pathNoExt + ".abi" - abifilename = filepath.Base(abipath) + abifilename = "contract.abi" + abipath = filepath.Join(outFlagStr, abifilename) } // Generate the contract precompile - code, err := bind.PrecompileBind(types, abis, bins, sigs, pkg, lang, libs, aliases, abifilename) + configCode, contractCode, 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 !outFlagSet { - fmt.Printf("%s\n", code) + if isOutStdout { + fmt.Print("-----Config Code-----\n") + fmt.Printf("%s\n", configCode) + fmt.Print("-----Contract Code-----\n") + fmt.Printf("%s\n", contractCode) return nil } - if err := os.WriteFile(outFlag, []byte(code), 0o600); err != nil { + if _, err := os.Stat(outFlagStr); os.IsNotExist(err) { + os.MkdirAll(outFlagStr, 0700) // Create your file + } + configCodeOut := filepath.Join(outFlagStr, "config.go") + + if err := os.WriteFile(configCodeOut, []byte(configCode), 0o600); err != nil { + utils.Fatalf("Failed to write generated precompile: %v", err) + } + + contractCodeOut := filepath.Join(outFlagStr, "contract.go") + + if err := os.WriteFile(contractCodeOut, []byte(contractCode), 0o600); err != nil { utils.Fatalf("Failed to write generated precompile: %v", err) } @@ -155,7 +178,7 @@ func precompilegen(c *cli.Context) error { utils.Fatalf("Failed to write ABI: %v", err) } - fmt.Println("Precompile Generation was a success!") + fmt.Println("Precompile files generated successfully at: ", outFlagStr) return nil } diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index f17527c82e..5b0b943269 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -40,6 +40,8 @@ import ( "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/feemanager" + "github.com/ava-labs/subnet-evm/precompile/rewardmanager" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" ) @@ -366,7 +368,7 @@ func (bc *BlockChain) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig return commontype.EmptyFeeConfig, nil, err } - storedFeeConfig := precompile.GetStoredFeeConfig(stateDB) + storedFeeConfig := feemanager.GetStoredFeeConfig(stateDB) // this should not return an invalid fee config since it's assumed that // StoreFeeConfig returns an error when an invalid fee config is attempted to be stored. // However an external stateDB call can modify the contract state. @@ -374,7 +376,7 @@ func (bc *BlockChain) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig if err := storedFeeConfig.Verify(); err != nil { return commontype.EmptyFeeConfig, nil, err } - lastChangedAt := precompile.GetFeeConfigLastChangedAt(stateDB) + lastChangedAt := feemanager.GetFeeConfigLastChangedAt(stateDB) cacheable := &cacheableFeeConfig{feeConfig: storedFeeConfig, lastChangedAt: lastChangedAt} // add it to the cache bc.feeConfigCache.Add(parent.Root, cacheable) @@ -413,7 +415,7 @@ func (bc *BlockChain) GetCoinbaseAt(parent *types.Header) (common.Address, bool, if err != nil { return common.Address{}, false, err } - rewardAddress, feeRecipients := precompile.GetStoredRewardAddress(stateDB) + rewardAddress, feeRecipients := rewardmanager.GetStoredRewardAddress(stateDB) cacheable := &cacheableCoinbaseConfig{coinbaseAddress: rewardAddress, allowFeeRecipients: feeRecipients} bc.coinbaseConfigCache.Add(parent.Root, cacheable) diff --git a/core/genesis_test.go b/core/genesis_test.go index 920e5337b6..61baeed669 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -39,6 +39,8 @@ import ( "github.com/ava-labs/subnet-evm/ethdb" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/deployerallowlist" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" @@ -190,11 +192,11 @@ func TestStatefulPrecompilesConfigure(t *testing.T) { "allow list enabled in genesis": { getConfig: func() *params.ChainConfig { config := *params.TestChainConfig - config.ContractDeployerAllowListConfig = precompile.NewContractDeployerAllowListConfig(big.NewInt(0), []common.Address{addr}, nil) + config.ContractDeployerAllowListConfig = deployerallowlist.NewContractDeployerAllowListConfig(big.NewInt(0), []common.Address{addr}, nil) return &config }, assertState: func(t *testing.T, sdb *state.StateDB) { - assert.Equal(t, precompile.AllowListAdmin, precompile.GetContractDeployerAllowListStatus(sdb, addr), "unexpected allow list status for modified address") + assert.Equal(t, allowlist.AllowListAdmin, deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr), "unexpected allow list status for modified address") assert.Equal(t, uint64(1), sdb.GetNonce(precompile.ContractDeployerAllowListAddress)) }, }, @@ -265,7 +267,7 @@ func TestPrecompileActivationAfterHeaderBlock(t *testing.T) { require.Greater(block.Time(), bc.lastAccepted.Time()) activatedGenesis := customg - contractDeployerConfig := precompile.NewContractDeployerAllowListConfig(big.NewInt(51), nil, nil) + contractDeployerConfig := deployerallowlist.NewContractDeployerAllowListConfig(big.NewInt(51), nil, nil) activatedGenesis.Config.UpgradeConfig.PrecompileUpgrades = []params.PrecompileUpgrade{ { // Enable ContractDeployerAllowList at timestamp 50 diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 6bf05057d9..0c5ce0f50b 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -36,7 +36,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/txallowlist" "github.com/ava-labs/subnet-evm/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -316,7 +316,7 @@ func TestBadTxAllowListBlock(t *testing.T) { SubnetEVMTimestamp: big.NewInt(0), }, PrecompileUpgrade: params.PrecompileUpgrade{ - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(0), nil, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(0), nil, nil), }, } signer = types.LatestSigner(config) diff --git a/core/state_transition.go b/core/state_transition.go index acd81569e2..5fcfa642b5 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -37,6 +37,7 @@ import ( "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/txallowlist" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" ) @@ -250,9 +251,9 @@ func (st *StateTransition) preCheck() error { // Check that the sender is on the tx allow list if enabled if st.evm.ChainConfig().IsPrecompileEnabled(precompile.TxAllowListAddress, st.evm.Context.Time) { - txAllowListRole := precompile.GetTxAllowListStatus(st.state, st.msg.From()) + txAllowListRole := txallowlist.GetTxAllowListStatus(st.state, st.msg.From()) if !txAllowListRole.IsEnabled() { - return fmt.Errorf("%w: %s", precompile.ErrSenderAddressNotAllowListed, st.msg.From()) + return fmt.Errorf("%w: %s", txallowlist.ErrSenderAddressNotAllowListed, st.msg.From()) } } } diff --git a/core/stateful_precompile_test.go b/core/stateful_precompile_test.go index 702c8715b4..0fee8976d3 100644 --- a/core/stateful_precompile_test.go +++ b/core/stateful_precompile_test.go @@ -14,12 +14,20 @@ import ( "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/deployerallowlist" + "github.com/ava-labs/subnet-evm/precompile/feemanager" + "github.com/ava-labs/subnet-evm/precompile/nativeminter" + "github.com/ava-labs/subnet-evm/precompile/rewardmanager" + "github.com/ava-labs/subnet-evm/precompile/txallowlist" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/stretchr/testify/require" ) +// TODO: move this to precompile package once cross-import is resolved + var ( _ precompile.BlockContext = &mockBlockContext{} _ precompile.PrecompileAccessibleState = &mockAccessibleState{} @@ -86,147 +94,147 @@ func TestContractDeployerAllowListRun(t *testing.T) { "set admin": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListAdmin) + input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListAdmin) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetContractDeployerAllowListStatus(state, noRoleAddr) - require.Equal(t, precompile.AllowListAdmin, res) + res := deployerallowlist.GetContractDeployerAllowListStatus(state, noRoleAddr) + require.Equal(t, allowlist.AllowListAdmin, res) }, }, "set deployer": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) + input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListEnabled) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetContractDeployerAllowListStatus(state, noRoleAddr) - require.Equal(t, precompile.AllowListEnabled, res) + res := deployerallowlist.GetContractDeployerAllowListStatus(state, noRoleAddr) + require.Equal(t, allowlist.AllowListEnabled, res) }, }, "set no role": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListNoRole) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetContractDeployerAllowListStatus(state, adminAddr) - require.Equal(t, precompile.AllowListNoRole, res) + res := deployerallowlist.GetContractDeployerAllowListStatus(state, adminAddr) + require.Equal(t, allowlist.AllowListNoRole, res) }, }, "set no role from non-admin": { caller: noRoleAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListNoRole) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, - expectedErr: precompile.ErrCannotModifyAllowList.Error(), + expectedErr: allowlist.ErrCannotModifyAllowList.Error(), }, "set deployer from non-admin": { caller: noRoleAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListEnabled) + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListEnabled) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, - expectedErr: precompile.ErrCannotModifyAllowList.Error(), + expectedErr: allowlist.ErrCannotModifyAllowList.Error(), }, "set admin from non-admin": { caller: noRoleAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListAdmin) + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListAdmin) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, - expectedErr: precompile.ErrCannotModifyAllowList.Error(), + expectedErr: allowlist.ErrCannotModifyAllowList.Error(), }, "set no role with readOnly enabled": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListNoRole) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: true, expectedErr: vmerrs.ErrWriteProtection.Error(), }, "set no role insufficient gas": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListNoRole) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost - 1, + suppliedGas: allowlist.ModifyAllowListGasCost - 1, readOnly: false, expectedErr: vmerrs.ErrOutOfGas.Error(), }, "read allow list no role": { caller: noRoleAddr, input: func() []byte { - return precompile.PackReadAllowList(noRoleAddr) + return allowlist.PackReadAllowList(noRoleAddr) }, - suppliedGas: precompile.ReadAllowListGasCost, + suppliedGas: allowlist.ReadAllowListGasCost, readOnly: false, - expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(), + expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), assertState: nil, }, "read allow list admin role": { caller: adminAddr, input: func() []byte { - return precompile.PackReadAllowList(noRoleAddr) + return allowlist.PackReadAllowList(noRoleAddr) }, - suppliedGas: precompile.ReadAllowListGasCost, + suppliedGas: allowlist.ReadAllowListGasCost, readOnly: false, - expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(), + expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), assertState: nil, }, "read allow list with readOnly enabled": { caller: adminAddr, input: func() []byte { - return precompile.PackReadAllowList(noRoleAddr) + return allowlist.PackReadAllowList(noRoleAddr) }, - suppliedGas: precompile.ReadAllowListGasCost, + suppliedGas: allowlist.ReadAllowListGasCost, readOnly: true, - expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(), + expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), assertState: nil, }, "read allow list out of gas": { caller: adminAddr, input: func() []byte { - return precompile.PackReadAllowList(noRoleAddr) + return allowlist.PackReadAllowList(noRoleAddr) }, - suppliedGas: precompile.ReadAllowListGasCost - 1, + suppliedGas: allowlist.ReadAllowListGasCost - 1, readOnly: true, expectedErr: vmerrs.ErrOutOfGas.Error(), }, @@ -237,13 +245,13 @@ func TestContractDeployerAllowListRun(t *testing.T) { require.NoError(t, err) // Set up the state so that each address has the expected permissions at the start. - precompile.SetContractDeployerAllowListStatus(state, adminAddr, precompile.AllowListAdmin) - precompile.SetContractDeployerAllowListStatus(state, noRoleAddr, precompile.AllowListNoRole) - require.Equal(t, precompile.AllowListAdmin, precompile.GetContractDeployerAllowListStatus(state, adminAddr)) - require.Equal(t, precompile.AllowListNoRole, precompile.GetContractDeployerAllowListStatus(state, noRoleAddr)) + deployerallowlist.SetContractDeployerAllowListStatus(state, adminAddr, allowlist.AllowListAdmin) + deployerallowlist.SetContractDeployerAllowListStatus(state, noRoleAddr, allowlist.AllowListNoRole) + require.Equal(t, allowlist.AllowListAdmin, deployerallowlist.GetContractDeployerAllowListStatus(state, adminAddr)) + require.Equal(t, allowlist.AllowListNoRole, deployerallowlist.GetContractDeployerAllowListStatus(state, noRoleAddr)) blockContext := &mockBlockContext{blockNumber: common.Big0} - ret, remainingGas, err := precompile.ContractDeployerAllowListPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext, snowContext: snow.DefaultContextTest()}, test.caller, precompile.ContractDeployerAllowListAddress, test.input(), test.suppliedGas, test.readOnly) + ret, remainingGas, err := deployerallowlist.ContractDeployerAllowListPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext, snowContext: snow.DefaultContextTest()}, test.caller, precompile.ContractDeployerAllowListAddress, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { require.ErrorContains(t, err, test.expectedErr) } else { @@ -281,148 +289,148 @@ func TestTxAllowListRun(t *testing.T) { "set admin": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListAdmin) + input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListAdmin) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetTxAllowListStatus(state, noRoleAddr) - require.Equal(t, precompile.AllowListAdmin, res) + res := txallowlist.GetTxAllowListStatus(state, noRoleAddr) + require.Equal(t, allowlist.AllowListAdmin, res) }, }, "set allowed": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) + input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListEnabled) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetTxAllowListStatus(state, noRoleAddr) - require.Equal(t, precompile.AllowListEnabled, res) + res := txallowlist.GetTxAllowListStatus(state, noRoleAddr) + require.Equal(t, allowlist.AllowListEnabled, res) }, }, "set no role": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListNoRole) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetTxAllowListStatus(state, adminAddr) - require.Equal(t, precompile.AllowListNoRole, res) + res := txallowlist.GetTxAllowListStatus(state, adminAddr) + require.Equal(t, allowlist.AllowListNoRole, res) }, }, "set no role from non-admin": { caller: noRoleAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListNoRole) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, - expectedErr: precompile.ErrCannotModifyAllowList.Error(), + expectedErr: allowlist.ErrCannotModifyAllowList.Error(), }, "set allowed from non-admin": { caller: noRoleAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListEnabled) + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListEnabled) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, - expectedErr: precompile.ErrCannotModifyAllowList.Error(), + expectedErr: allowlist.ErrCannotModifyAllowList.Error(), }, "set admin from non-admin": { caller: noRoleAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListAdmin) + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListAdmin) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, - expectedErr: precompile.ErrCannotModifyAllowList.Error(), + expectedErr: allowlist.ErrCannotModifyAllowList.Error(), }, "set no role with readOnly enabled": { caller: adminAddr, precompileAddr: precompile.TxAllowListAddress, input: func() []byte { - input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListNoRole) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: true, expectedErr: vmerrs.ErrWriteProtection.Error(), }, "set no role insufficient gas": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListNoRole) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost - 1, + suppliedGas: allowlist.ModifyAllowListGasCost - 1, readOnly: false, expectedErr: vmerrs.ErrOutOfGas.Error(), }, "read allow list no role": { caller: noRoleAddr, input: func() []byte { - return precompile.PackReadAllowList(noRoleAddr) + return allowlist.PackReadAllowList(noRoleAddr) }, - suppliedGas: precompile.ReadAllowListGasCost, + suppliedGas: allowlist.ReadAllowListGasCost, readOnly: false, - expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(), + expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), assertState: nil, }, "read allow list admin role": { caller: adminAddr, input: func() []byte { - return precompile.PackReadAllowList(noRoleAddr) + return allowlist.PackReadAllowList(noRoleAddr) }, - suppliedGas: precompile.ReadAllowListGasCost, + suppliedGas: allowlist.ReadAllowListGasCost, readOnly: false, - expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(), + expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), assertState: nil, }, "read allow list with readOnly enabled": { caller: adminAddr, input: func() []byte { - return precompile.PackReadAllowList(noRoleAddr) + return allowlist.PackReadAllowList(noRoleAddr) }, - suppliedGas: precompile.ReadAllowListGasCost, + suppliedGas: allowlist.ReadAllowListGasCost, readOnly: true, - expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(), + expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), assertState: nil, }, "read allow list out of gas": { caller: adminAddr, input: func() []byte { - return precompile.PackReadAllowList(noRoleAddr) + return allowlist.PackReadAllowList(noRoleAddr) }, - suppliedGas: precompile.ReadAllowListGasCost - 1, + suppliedGas: allowlist.ReadAllowListGasCost - 1, readOnly: true, expectedErr: vmerrs.ErrOutOfGas.Error(), }, @@ -433,11 +441,11 @@ func TestTxAllowListRun(t *testing.T) { require.NoError(t, err) // Set up the state so that each address has the expected permissions at the start. - precompile.SetTxAllowListStatus(state, adminAddr, precompile.AllowListAdmin) - require.Equal(t, precompile.AllowListAdmin, precompile.GetTxAllowListStatus(state, adminAddr)) + txallowlist.SetTxAllowListStatus(state, adminAddr, allowlist.AllowListAdmin) + require.Equal(t, allowlist.AllowListAdmin, txallowlist.GetTxAllowListStatus(state, adminAddr)) blockContext := &mockBlockContext{blockNumber: common.Big0} - ret, remainingGas, err := precompile.TxAllowListPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext, snowContext: snow.DefaultContextTest()}, test.caller, precompile.TxAllowListAddress, test.input(), test.suppliedGas, test.readOnly) + ret, remainingGas, err := txallowlist.TxAllowListPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext, snowContext: snow.DefaultContextTest()}, test.caller, precompile.TxAllowListAddress, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { require.ErrorContains(t, err, test.expectedErr) } else { @@ -460,7 +468,7 @@ func TestContractNativeMinterRun(t *testing.T) { input func() []byte suppliedGas uint64 readOnly bool - config *precompile.ContractNativeMinterConfig + config *nativeminter.ContractNativeMinterConfig expectedRes []byte expectedErr string @@ -477,24 +485,24 @@ func TestContractNativeMinterRun(t *testing.T) { "mint funds from no role fails": { caller: noRoleAddr, input: func() []byte { - input, err := precompile.PackMintInput(noRoleAddr, common.Big1) + input, err := nativeminter.PackMintInput(noRoleAddr, common.Big1) require.NoError(t, err) return input }, - suppliedGas: precompile.MintGasCost, + suppliedGas: nativeminter.MintGasCost, readOnly: false, - expectedErr: precompile.ErrCannotMint.Error(), + expectedErr: nativeminter.ErrCannotMint.Error(), }, "mint funds from enabled address": { caller: enabledAddr, input: func() []byte { - input, err := precompile.PackMintInput(enabledAddr, common.Big1) + input, err := nativeminter.PackMintInput(enabledAddr, common.Big1) require.NoError(t, err) return input }, - suppliedGas: precompile.MintGasCost, + suppliedGas: nativeminter.MintGasCost, readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { @@ -504,31 +512,31 @@ func TestContractNativeMinterRun(t *testing.T) { "enabled role by config": { caller: noRoleAddr, input: func() []byte { - return precompile.PackReadAllowList(testAddr) + return allowlist.PackReadAllowList(testAddr) }, - suppliedGas: precompile.ReadAllowListGasCost, + suppliedGas: allowlist.ReadAllowListGasCost, readOnly: false, - expectedRes: common.Hash(precompile.AllowListEnabled).Bytes(), + expectedRes: common.Hash(allowlist.AllowListEnabled).Bytes(), assertState: func(t *testing.T, state *state.StateDB) { - require.Equal(t, precompile.AllowListEnabled, precompile.GetContractNativeMinterStatus(state, testAddr)) + require.Equal(t, allowlist.AllowListEnabled, nativeminter.GetContractNativeMinterStatus(state, testAddr)) }, - config: &precompile.ContractNativeMinterConfig{ - AllowListConfig: precompile.AllowListConfig{EnabledAddresses: []common.Address{testAddr}}, + config: &nativeminter.ContractNativeMinterConfig{ + AllowListConfig: allowlist.AllowListConfig{EnabledAddresses: []common.Address{testAddr}}, }, }, "initial mint funds": { caller: enabledAddr, - config: &precompile.ContractNativeMinterConfig{ + config: &nativeminter.ContractNativeMinterConfig{ InitialMint: map[common.Address]*math.HexOrDecimal256{ enabledAddr: math.NewHexOrDecimal256(2), }, }, input: func() []byte { - return precompile.PackReadAllowList(noRoleAddr) + return allowlist.PackReadAllowList(noRoleAddr) }, - suppliedGas: precompile.ReadAllowListGasCost, + suppliedGas: allowlist.ReadAllowListGasCost, readOnly: false, - expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(), + expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), assertState: func(t *testing.T, state *state.StateDB) { require.Equal(t, common.Big2, state.GetBalance(enabledAddr), "expected minted funds") }, @@ -536,12 +544,12 @@ func TestContractNativeMinterRun(t *testing.T) { "mint funds from admin address": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackMintInput(adminAddr, common.Big1) + input, err := nativeminter.PackMintInput(adminAddr, common.Big1) require.NoError(t, err) return input }, - suppliedGas: precompile.MintGasCost, + suppliedGas: nativeminter.MintGasCost, readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { @@ -551,12 +559,12 @@ func TestContractNativeMinterRun(t *testing.T) { "mint max big funds": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackMintInput(adminAddr, math.MaxBig256) + input, err := nativeminter.PackMintInput(adminAddr, math.MaxBig256) require.NoError(t, err) return input }, - suppliedGas: precompile.MintGasCost, + suppliedGas: nativeminter.MintGasCost, readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { @@ -566,107 +574,107 @@ func TestContractNativeMinterRun(t *testing.T) { "readOnly mint with noRole fails": { caller: noRoleAddr, input: func() []byte { - input, err := precompile.PackMintInput(adminAddr, common.Big1) + input, err := nativeminter.PackMintInput(adminAddr, common.Big1) require.NoError(t, err) return input }, - suppliedGas: precompile.MintGasCost, + suppliedGas: nativeminter.MintGasCost, readOnly: true, expectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly mint with allow role fails": { caller: enabledAddr, input: func() []byte { - input, err := precompile.PackMintInput(enabledAddr, common.Big1) + input, err := nativeminter.PackMintInput(enabledAddr, common.Big1) require.NoError(t, err) return input }, - suppliedGas: precompile.MintGasCost, + suppliedGas: nativeminter.MintGasCost, readOnly: true, expectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly mint with admin role fails": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackMintInput(adminAddr, common.Big1) + input, err := nativeminter.PackMintInput(adminAddr, common.Big1) require.NoError(t, err) return input }, - suppliedGas: precompile.MintGasCost, + suppliedGas: nativeminter.MintGasCost, readOnly: true, expectedErr: vmerrs.ErrWriteProtection.Error(), }, "insufficient gas mint from admin": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackMintInput(enabledAddr, common.Big1) + input, err := nativeminter.PackMintInput(enabledAddr, common.Big1) require.NoError(t, err) return input }, - suppliedGas: precompile.MintGasCost - 1, + suppliedGas: nativeminter.MintGasCost - 1, readOnly: false, expectedErr: vmerrs.ErrOutOfGas.Error(), }, "read from noRole address": { caller: noRoleAddr, input: func() []byte { - return precompile.PackReadAllowList(noRoleAddr) + return allowlist.PackReadAllowList(noRoleAddr) }, - suppliedGas: precompile.ReadAllowListGasCost, + suppliedGas: allowlist.ReadAllowListGasCost, readOnly: false, - expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(), + expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), assertState: func(t *testing.T, state *state.StateDB) {}, }, "read from noRole address readOnly enabled": { caller: noRoleAddr, input: func() []byte { - return precompile.PackReadAllowList(noRoleAddr) + return allowlist.PackReadAllowList(noRoleAddr) }, - suppliedGas: precompile.ReadAllowListGasCost, + suppliedGas: allowlist.ReadAllowListGasCost, readOnly: true, - expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(), + expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), assertState: func(t *testing.T, state *state.StateDB) {}, }, "read from noRole address with insufficient gas": { caller: noRoleAddr, input: func() []byte { - return precompile.PackReadAllowList(noRoleAddr) + return allowlist.PackReadAllowList(noRoleAddr) }, - suppliedGas: precompile.ReadAllowListGasCost - 1, + suppliedGas: allowlist.ReadAllowListGasCost - 1, readOnly: false, expectedErr: vmerrs.ErrOutOfGas.Error(), }, "set allow role from admin": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) + input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListEnabled) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetContractNativeMinterStatus(state, noRoleAddr) - require.Equal(t, precompile.AllowListEnabled, res) + res := nativeminter.GetContractNativeMinterStatus(state, noRoleAddr) + require.Equal(t, allowlist.AllowListEnabled, res) }, }, "set allow role from non-admin fails": { caller: enabledAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) + input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListEnabled) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, - expectedErr: precompile.ErrCannotModifyAllowList.Error(), + expectedErr: allowlist.ErrCannotModifyAllowList.Error(), }, } { t.Run(name, func(t *testing.T) { @@ -675,18 +683,18 @@ func TestContractNativeMinterRun(t *testing.T) { require.NoError(t, err) // Set up the state so that each address has the expected permissions at the start. - precompile.SetContractNativeMinterStatus(state, adminAddr, precompile.AllowListAdmin) - precompile.SetContractNativeMinterStatus(state, enabledAddr, precompile.AllowListEnabled) - precompile.SetContractNativeMinterStatus(state, noRoleAddr, precompile.AllowListNoRole) - require.Equal(t, precompile.AllowListAdmin, precompile.GetContractNativeMinterStatus(state, adminAddr)) - require.Equal(t, precompile.AllowListEnabled, precompile.GetContractNativeMinterStatus(state, enabledAddr)) - require.Equal(t, precompile.AllowListNoRole, precompile.GetContractNativeMinterStatus(state, noRoleAddr)) + nativeminter.SetContractNativeMinterStatus(state, adminAddr, allowlist.AllowListAdmin) + nativeminter.SetContractNativeMinterStatus(state, enabledAddr, allowlist.AllowListEnabled) + nativeminter.SetContractNativeMinterStatus(state, noRoleAddr, allowlist.AllowListNoRole) + require.Equal(t, allowlist.AllowListAdmin, nativeminter.GetContractNativeMinterStatus(state, adminAddr)) + require.Equal(t, allowlist.AllowListEnabled, nativeminter.GetContractNativeMinterStatus(state, enabledAddr)) + require.Equal(t, allowlist.AllowListNoRole, nativeminter.GetContractNativeMinterStatus(state, noRoleAddr)) blockContext := &mockBlockContext{blockNumber: common.Big0} if test.config != nil { test.config.Configure(params.TestChainConfig, state, blockContext) } - ret, remainingGas, err := precompile.ContractNativeMinterPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext, snowContext: snow.DefaultContextTest()}, test.caller, precompile.ContractNativeMinterAddress, test.input(), test.suppliedGas, test.readOnly) + ret, remainingGas, err := nativeminter.ContractNativeMinterPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext, snowContext: snow.DefaultContextTest()}, test.caller, precompile.ContractNativeMinterAddress, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { require.ErrorContains(t, err, test.expectedErr) } else { @@ -710,7 +718,7 @@ func TestFeeConfigManagerRun(t *testing.T) { input func() []byte suppliedGas uint64 readOnly bool - config *precompile.FeeConfigManagerConfig + config *feemanager.FeeConfigManagerConfig expectedRes []byte expectedErr string @@ -726,28 +734,28 @@ func TestFeeConfigManagerRun(t *testing.T) { "set config from no role fails": { caller: noRoleAddr, input: func() []byte { - input, err := precompile.PackSetFeeConfig(testFeeConfig) + input, err := feemanager.PackSetFeeConfig(testFeeConfig) require.NoError(t, err) return input }, - suppliedGas: precompile.SetFeeConfigGasCost, + suppliedGas: feemanager.SetFeeConfigGasCost, readOnly: false, - expectedErr: precompile.ErrCannotChangeFee.Error(), + expectedErr: feemanager.ErrCannotChangeFee.Error(), }, "set config from enabled address": { caller: enabledAddr, input: func() []byte { - input, err := precompile.PackSetFeeConfig(testFeeConfig) + input, err := feemanager.PackSetFeeConfig(testFeeConfig) require.NoError(t, err) return input }, - suppliedGas: precompile.SetFeeConfigGasCost, + suppliedGas: feemanager.SetFeeConfigGasCost, readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - feeConfig := precompile.GetStoredFeeConfig(state) + feeConfig := feemanager.GetStoredFeeConfig(state) require.Equal(t, testFeeConfig, feeConfig) }, }, @@ -756,60 +764,60 @@ func TestFeeConfigManagerRun(t *testing.T) { input: func() []byte { feeConfig := testFeeConfig feeConfig.MinBlockGasCost = new(big.Int).Mul(feeConfig.MaxBlockGasCost, common.Big2) - input, err := precompile.PackSetFeeConfig(feeConfig) + input, err := feemanager.PackSetFeeConfig(feeConfig) require.NoError(t, err) return input }, - suppliedGas: precompile.SetFeeConfigGasCost, + suppliedGas: feemanager.SetFeeConfigGasCost, readOnly: false, expectedRes: nil, - config: &precompile.FeeConfigManagerConfig{ + config: &feemanager.FeeConfigManagerConfig{ InitialFeeConfig: &testFeeConfig, }, expectedErr: "cannot be greater than maxBlockGasCost", assertState: func(t *testing.T, state *state.StateDB) { - feeConfig := precompile.GetStoredFeeConfig(state) + feeConfig := feemanager.GetStoredFeeConfig(state) require.Equal(t, testFeeConfig, feeConfig) }, }, "set config from admin address": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackSetFeeConfig(testFeeConfig) + input, err := feemanager.PackSetFeeConfig(testFeeConfig) require.NoError(t, err) return input }, - suppliedGas: precompile.SetFeeConfigGasCost, + suppliedGas: feemanager.SetFeeConfigGasCost, readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - feeConfig := precompile.GetStoredFeeConfig(state) + feeConfig := feemanager.GetStoredFeeConfig(state) require.Equal(t, testFeeConfig, feeConfig) - lastChangedAt := precompile.GetFeeConfigLastChangedAt(state) + lastChangedAt := feemanager.GetFeeConfigLastChangedAt(state) require.EqualValues(t, testBlockNumber, lastChangedAt) }, }, "get fee config from non-enabled address": { caller: noRoleAddr, preCondition: func(t *testing.T, state *state.StateDB) { - err := precompile.StoreFeeConfig(state, testFeeConfig, &mockBlockContext{blockNumber: big.NewInt(6)}) + err := feemanager.StoreFeeConfig(state, testFeeConfig, &mockBlockContext{blockNumber: big.NewInt(6)}) require.NoError(t, err) }, input: func() []byte { - return precompile.PackGetFeeConfigInput() + return feemanager.PackGetFeeConfigInput() }, - suppliedGas: precompile.GetFeeConfigGasCost, + suppliedGas: feemanager.GetFeeConfigGasCost, readOnly: true, expectedRes: func() []byte { - res, err := precompile.PackFeeConfig(testFeeConfig) + res, err := feemanager.PackFeeConfig(testFeeConfig) require.NoError(t, err) return res }(), assertState: func(t *testing.T, state *state.StateDB) { - feeConfig := precompile.GetStoredFeeConfig(state) - lastChangedAt := precompile.GetFeeConfigLastChangedAt(state) + feeConfig := feemanager.GetStoredFeeConfig(state) + lastChangedAt := feemanager.GetFeeConfigLastChangedAt(state) require.Equal(t, testFeeConfig, feeConfig) require.EqualValues(t, big.NewInt(6), lastChangedAt) }, @@ -817,21 +825,21 @@ func TestFeeConfigManagerRun(t *testing.T) { "get initial fee config": { caller: noRoleAddr, input: func() []byte { - return precompile.PackGetFeeConfigInput() + return feemanager.PackGetFeeConfigInput() }, - suppliedGas: precompile.GetFeeConfigGasCost, - config: &precompile.FeeConfigManagerConfig{ + suppliedGas: feemanager.GetFeeConfigGasCost, + config: &feemanager.FeeConfigManagerConfig{ InitialFeeConfig: &testFeeConfig, }, readOnly: true, expectedRes: func() []byte { - res, err := precompile.PackFeeConfig(testFeeConfig) + res, err := feemanager.PackFeeConfig(testFeeConfig) require.NoError(t, err) return res }(), assertState: func(t *testing.T, state *state.StateDB) { - feeConfig := precompile.GetStoredFeeConfig(state) - lastChangedAt := precompile.GetFeeConfigLastChangedAt(state) + feeConfig := feemanager.GetStoredFeeConfig(state) + lastChangedAt := feemanager.GetFeeConfigLastChangedAt(state) require.Equal(t, testFeeConfig, feeConfig) require.EqualValues(t, testBlockNumber, lastChangedAt) }, @@ -839,18 +847,18 @@ func TestFeeConfigManagerRun(t *testing.T) { "get last changed at from non-enabled address": { caller: noRoleAddr, preCondition: func(t *testing.T, state *state.StateDB) { - err := precompile.StoreFeeConfig(state, testFeeConfig, &mockBlockContext{blockNumber: testBlockNumber}) + err := feemanager.StoreFeeConfig(state, testFeeConfig, &mockBlockContext{blockNumber: testBlockNumber}) require.NoError(t, err) }, input: func() []byte { - return precompile.PackGetLastChangedAtInput() + return feemanager.PackGetLastChangedAtInput() }, - suppliedGas: precompile.GetLastChangedAtGasCost, + suppliedGas: feemanager.GetLastChangedAtGasCost, readOnly: true, expectedRes: common.BigToHash(testBlockNumber).Bytes(), assertState: func(t *testing.T, state *state.StateDB) { - feeConfig := precompile.GetStoredFeeConfig(state) - lastChangedAt := precompile.GetFeeConfigLastChangedAt(state) + feeConfig := feemanager.GetStoredFeeConfig(state) + lastChangedAt := feemanager.GetFeeConfigLastChangedAt(state) require.Equal(t, testFeeConfig, feeConfig) require.Equal(t, testBlockNumber, lastChangedAt) }, @@ -858,78 +866,78 @@ func TestFeeConfigManagerRun(t *testing.T) { "readOnly setFeeConfig with noRole fails": { caller: noRoleAddr, input: func() []byte { - input, err := precompile.PackSetFeeConfig(testFeeConfig) + input, err := feemanager.PackSetFeeConfig(testFeeConfig) require.NoError(t, err) return input }, - suppliedGas: precompile.SetFeeConfigGasCost, + suppliedGas: feemanager.SetFeeConfigGasCost, readOnly: true, expectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly setFeeConfig with allow role fails": { caller: enabledAddr, input: func() []byte { - input, err := precompile.PackSetFeeConfig(testFeeConfig) + input, err := feemanager.PackSetFeeConfig(testFeeConfig) require.NoError(t, err) return input }, - suppliedGas: precompile.SetFeeConfigGasCost, + suppliedGas: feemanager.SetFeeConfigGasCost, readOnly: true, expectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly setFeeConfig with admin role fails": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackSetFeeConfig(testFeeConfig) + input, err := feemanager.PackSetFeeConfig(testFeeConfig) require.NoError(t, err) return input }, - suppliedGas: precompile.SetFeeConfigGasCost, + suppliedGas: feemanager.SetFeeConfigGasCost, readOnly: true, expectedErr: vmerrs.ErrWriteProtection.Error(), }, "insufficient gas setFeeConfig from admin": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackSetFeeConfig(testFeeConfig) + input, err := feemanager.PackSetFeeConfig(testFeeConfig) require.NoError(t, err) return input }, - suppliedGas: precompile.SetFeeConfigGasCost - 1, + suppliedGas: feemanager.SetFeeConfigGasCost - 1, readOnly: false, expectedErr: vmerrs.ErrOutOfGas.Error(), }, "set allow role from admin": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) + input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListEnabled) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetFeeConfigManagerStatus(state, noRoleAddr) - require.Equal(t, precompile.AllowListEnabled, res) + res := feemanager.GetFeeConfigManagerStatus(state, noRoleAddr) + require.Equal(t, allowlist.AllowListEnabled, res) }, }, "set allow role from non-admin fails": { caller: enabledAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) + input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListEnabled) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, - expectedErr: precompile.ErrCannotModifyAllowList.Error(), + expectedErr: allowlist.ErrCannotModifyAllowList.Error(), }, } { t.Run(name, func(t *testing.T) { @@ -938,9 +946,9 @@ func TestFeeConfigManagerRun(t *testing.T) { require.NoError(t, err) // Set up the state so that each address has the expected permissions at the start. - precompile.SetFeeConfigManagerStatus(state, adminAddr, precompile.AllowListAdmin) - precompile.SetFeeConfigManagerStatus(state, enabledAddr, precompile.AllowListEnabled) - precompile.SetFeeConfigManagerStatus(state, noRoleAddr, precompile.AllowListNoRole) + feemanager.SetFeeConfigManagerStatus(state, adminAddr, allowlist.AllowListAdmin) + feemanager.SetFeeConfigManagerStatus(state, enabledAddr, allowlist.AllowListEnabled) + feemanager.SetFeeConfigManagerStatus(state, noRoleAddr, allowlist.AllowListNoRole) if test.preCondition != nil { test.preCondition(t, state) @@ -950,7 +958,7 @@ func TestFeeConfigManagerRun(t *testing.T) { if test.config != nil { test.config.Configure(params.TestChainConfig, state, blockContext) } - ret, remainingGas, err := precompile.FeeConfigManagerPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext, snowContext: snow.DefaultContextTest()}, test.caller, precompile.FeeConfigManagerAddress, test.input(), test.suppliedGas, test.readOnly) + ret, remainingGas, err := feemanager.FeeConfigManagerPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext, snowContext: snow.DefaultContextTest()}, test.caller, precompile.FeeConfigManagerAddress, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { require.ErrorContains(t, err, test.expectedErr) } else { @@ -974,7 +982,7 @@ func TestRewardManagerRun(t *testing.T) { input func() []byte suppliedGas uint64 readOnly bool - config *precompile.RewardManagerConfig + config *rewardmanager.RewardManagerConfig expectedRes []byte expectedErr string @@ -991,68 +999,68 @@ func TestRewardManagerRun(t *testing.T) { "set allow fee recipients from no role fails": { caller: noRoleAddr, input: func() []byte { - input, err := precompile.PackAllowFeeRecipients() + input, err := rewardmanager.PackAllowFeeRecipients() require.NoError(t, err) return input }, - suppliedGas: precompile.AllowFeeRecipientsGasCost, + suppliedGas: rewardmanager.AllowFeeRecipientsGasCost, readOnly: false, - expectedErr: precompile.ErrCannotAllowFeeRecipients.Error(), + expectedErr: rewardmanager.ErrCannotAllowFeeRecipients.Error(), }, "set reward address from no role fails": { caller: noRoleAddr, input: func() []byte { - input, err := precompile.PackSetRewardAddress(testAddr) + input, err := rewardmanager.PackSetRewardAddress(testAddr) require.NoError(t, err) return input }, - suppliedGas: precompile.SetRewardAddressGasCost, + suppliedGas: rewardmanager.SetRewardAddressGasCost, readOnly: false, - expectedErr: precompile.ErrCannotSetRewardAddress.Error(), + expectedErr: rewardmanager.ErrCannotSetRewardAddress.Error(), }, "disable rewards from no role fails": { caller: noRoleAddr, input: func() []byte { - input, err := precompile.PackDisableRewards() + input, err := rewardmanager.PackDisableRewards() require.NoError(t, err) return input }, - suppliedGas: precompile.DisableRewardsGasCost, + suppliedGas: rewardmanager.DisableRewardsGasCost, readOnly: false, - expectedErr: precompile.ErrCannotDisableRewards.Error(), + expectedErr: rewardmanager.ErrCannotDisableRewards.Error(), }, "set allow fee recipients from enabled succeeds": { caller: enabledAddr, input: func() []byte { - input, err := precompile.PackAllowFeeRecipients() + input, err := rewardmanager.PackAllowFeeRecipients() require.NoError(t, err) return input }, - suppliedGas: precompile.AllowFeeRecipientsGasCost, + suppliedGas: rewardmanager.AllowFeeRecipientsGasCost, readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - _, isFeeRecipients := precompile.GetStoredRewardAddress(state) + _, isFeeRecipients := rewardmanager.GetStoredRewardAddress(state) require.True(t, isFeeRecipients) }, }, "set reward address from enabled succeeds": { caller: enabledAddr, input: func() []byte { - input, err := precompile.PackSetRewardAddress(testAddr) + input, err := rewardmanager.PackSetRewardAddress(testAddr) require.NoError(t, err) return input }, - suppliedGas: precompile.SetRewardAddressGasCost, + suppliedGas: rewardmanager.SetRewardAddressGasCost, readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - address, isFeeRecipients := precompile.GetStoredRewardAddress(state) + address, isFeeRecipients := rewardmanager.GetStoredRewardAddress(state) require.Equal(t, testAddr, address) require.False(t, isFeeRecipients) }, @@ -1060,16 +1068,16 @@ func TestRewardManagerRun(t *testing.T) { "disable rewards from enabled succeeds": { caller: enabledAddr, input: func() []byte { - input, err := precompile.PackDisableRewards() + input, err := rewardmanager.PackDisableRewards() require.NoError(t, err) return input }, - suppliedGas: precompile.DisableRewardsGasCost, + suppliedGas: rewardmanager.DisableRewardsGasCost, readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - address, isFeeRecipients := precompile.GetStoredRewardAddress(state) + address, isFeeRecipients := rewardmanager.GetStoredRewardAddress(state) require.False(t, isFeeRecipients) require.Equal(t, constants.BlackholeAddr, address) }, @@ -1077,18 +1085,18 @@ func TestRewardManagerRun(t *testing.T) { "get current reward address from no role succeeds": { caller: noRoleAddr, preCondition: func(t *testing.T, state *state.StateDB) { - precompile.StoreRewardAddress(state, testAddr) + rewardmanager.StoreRewardAddress(state, testAddr) }, input: func() []byte { - input, err := precompile.PackCurrentRewardAddress() + input, err := rewardmanager.PackCurrentRewardAddress() require.NoError(t, err) return input }, - suppliedGas: precompile.CurrentRewardAddressGasCost, + suppliedGas: rewardmanager.CurrentRewardAddressGasCost, readOnly: false, expectedRes: func() []byte { - res, err := precompile.PackCurrentRewardAddressOutput(testAddr) + res, err := rewardmanager.PackCurrentRewardAddressOutput(testAddr) require.NoError(t, err) return res }(), @@ -1096,17 +1104,17 @@ func TestRewardManagerRun(t *testing.T) { "get are fee recipients allowed from no role succeeds": { caller: noRoleAddr, preCondition: func(t *testing.T, state *state.StateDB) { - precompile.EnableAllowFeeRecipients(state) + rewardmanager.EnableAllowFeeRecipients(state) }, input: func() []byte { - input, err := precompile.PackAreFeeRecipientsAllowed() + input, err := rewardmanager.PackAreFeeRecipientsAllowed() require.NoError(t, err) return input }, - suppliedGas: precompile.AreFeeRecipientsAllowedGasCost, + suppliedGas: rewardmanager.AreFeeRecipientsAllowedGasCost, readOnly: false, expectedRes: func() []byte { - res, err := precompile.PackAreFeeRecipientsAllowedOutput(true) + res, err := rewardmanager.PackAreFeeRecipientsAllowedOutput(true) require.NoError(t, err) return res }(), @@ -1114,19 +1122,19 @@ func TestRewardManagerRun(t *testing.T) { "get initial config with address": { caller: noRoleAddr, input: func() []byte { - input, err := precompile.PackCurrentRewardAddress() + input, err := rewardmanager.PackCurrentRewardAddress() require.NoError(t, err) return input }, - suppliedGas: precompile.CurrentRewardAddressGasCost, - config: &precompile.RewardManagerConfig{ - InitialRewardConfig: &precompile.InitialRewardConfig{ + suppliedGas: rewardmanager.CurrentRewardAddressGasCost, + config: &rewardmanager.RewardManagerConfig{ + InitialRewardConfig: &rewardmanager.InitialRewardConfig{ RewardAddress: testAddr, }, }, readOnly: false, expectedRes: func() []byte { - res, err := precompile.PackCurrentRewardAddressOutput(testAddr) + res, err := rewardmanager.PackCurrentRewardAddressOutput(testAddr) require.NoError(t, err) return res }(), @@ -1134,19 +1142,19 @@ func TestRewardManagerRun(t *testing.T) { "get initial config with allow fee recipients enabled": { caller: noRoleAddr, input: func() []byte { - input, err := precompile.PackAreFeeRecipientsAllowed() + input, err := rewardmanager.PackAreFeeRecipientsAllowed() require.NoError(t, err) return input }, - suppliedGas: precompile.AreFeeRecipientsAllowedGasCost, - config: &precompile.RewardManagerConfig{ - InitialRewardConfig: &precompile.InitialRewardConfig{ + suppliedGas: rewardmanager.AreFeeRecipientsAllowedGasCost, + config: &rewardmanager.RewardManagerConfig{ + InitialRewardConfig: &rewardmanager.InitialRewardConfig{ AllowFeeRecipients: true, }, }, readOnly: false, expectedRes: func() []byte { - res, err := precompile.PackAreFeeRecipientsAllowedOutput(true) + res, err := rewardmanager.PackAreFeeRecipientsAllowedOutput(true) require.NoError(t, err) return res }(), @@ -1154,102 +1162,102 @@ func TestRewardManagerRun(t *testing.T) { "readOnly allow fee recipients with allowed role fails": { caller: enabledAddr, input: func() []byte { - input, err := precompile.PackAllowFeeRecipients() + input, err := rewardmanager.PackAllowFeeRecipients() require.NoError(t, err) return input }, - suppliedGas: precompile.AllowFeeRecipientsGasCost, + suppliedGas: rewardmanager.AllowFeeRecipientsGasCost, readOnly: true, expectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly set reward addresss with allowed role fails": { caller: enabledAddr, input: func() []byte { - input, err := precompile.PackSetRewardAddress(testAddr) + input, err := rewardmanager.PackSetRewardAddress(testAddr) require.NoError(t, err) return input }, - suppliedGas: precompile.SetRewardAddressGasCost, + suppliedGas: rewardmanager.SetRewardAddressGasCost, readOnly: true, expectedErr: vmerrs.ErrWriteProtection.Error(), }, "insufficient gas set reward address from allowed role": { caller: enabledAddr, input: func() []byte { - input, err := precompile.PackSetRewardAddress(testAddr) + input, err := rewardmanager.PackSetRewardAddress(testAddr) require.NoError(t, err) return input }, - suppliedGas: precompile.SetRewardAddressGasCost - 1, + suppliedGas: rewardmanager.SetRewardAddressGasCost - 1, readOnly: false, expectedErr: vmerrs.ErrOutOfGas.Error(), }, "insufficient gas allow fee recipients from allowed role": { caller: enabledAddr, input: func() []byte { - input, err := precompile.PackAllowFeeRecipients() + input, err := rewardmanager.PackAllowFeeRecipients() require.NoError(t, err) return input }, - suppliedGas: precompile.AllowFeeRecipientsGasCost - 1, + suppliedGas: rewardmanager.AllowFeeRecipientsGasCost - 1, readOnly: false, expectedErr: vmerrs.ErrOutOfGas.Error(), }, "insufficient gas read current reward address from allowed role": { caller: enabledAddr, input: func() []byte { - input, err := precompile.PackCurrentRewardAddress() + input, err := rewardmanager.PackCurrentRewardAddress() require.NoError(t, err) return input }, - suppliedGas: precompile.CurrentRewardAddressGasCost - 1, + suppliedGas: rewardmanager.CurrentRewardAddressGasCost - 1, readOnly: false, expectedErr: vmerrs.ErrOutOfGas.Error(), }, "insufficient gas are fee recipients allowed from allowed role": { caller: enabledAddr, input: func() []byte { - input, err := precompile.PackAreFeeRecipientsAllowed() + input, err := rewardmanager.PackAreFeeRecipientsAllowed() require.NoError(t, err) return input }, - suppliedGas: precompile.AreFeeRecipientsAllowedGasCost - 1, + suppliedGas: rewardmanager.AreFeeRecipientsAllowedGasCost - 1, readOnly: false, expectedErr: vmerrs.ErrOutOfGas.Error(), }, "set allow role from admin": { caller: adminAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) + input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListEnabled) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetRewardManagerAllowListStatus(state, noRoleAddr) - require.Equal(t, precompile.AllowListEnabled, res) + res := rewardmanager.GetRewardManagerAllowListStatus(state, noRoleAddr) + require.Equal(t, allowlist.AllowListEnabled, res) }, }, "set allow role from non-admin fails": { caller: enabledAddr, input: func() []byte { - input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) + input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListEnabled) require.NoError(t, err) return input }, - suppliedGas: precompile.ModifyAllowListGasCost, + suppliedGas: allowlist.ModifyAllowListGasCost, readOnly: false, - expectedErr: precompile.ErrCannotModifyAllowList.Error(), + expectedErr: allowlist.ErrCannotModifyAllowList.Error(), }, } { t.Run(name, func(t *testing.T) { @@ -1258,9 +1266,9 @@ func TestRewardManagerRun(t *testing.T) { require.NoError(t, err) // Set up the state so that each address has the expected permissions at the start. - precompile.SetRewardManagerAllowListStatus(state, adminAddr, precompile.AllowListAdmin) - precompile.SetRewardManagerAllowListStatus(state, enabledAddr, precompile.AllowListEnabled) - precompile.SetRewardManagerAllowListStatus(state, noRoleAddr, precompile.AllowListNoRole) + rewardmanager.SetRewardManagerAllowListStatus(state, adminAddr, allowlist.AllowListAdmin) + rewardmanager.SetRewardManagerAllowListStatus(state, enabledAddr, allowlist.AllowListEnabled) + rewardmanager.SetRewardManagerAllowListStatus(state, noRoleAddr, allowlist.AllowListNoRole) if test.preCondition != nil { test.preCondition(t, state) @@ -1270,7 +1278,7 @@ func TestRewardManagerRun(t *testing.T) { if test.config != nil { test.config.Configure(params.TestChainConfig, state, blockContext) } - ret, remainingGas, err := precompile.RewardManagerPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext, snowContext: snow.DefaultContextTest()}, test.caller, precompile.RewardManagerAddress, test.input(), test.suppliedGas, test.readOnly) + ret, remainingGas, err := rewardmanager.RewardManagerPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext, snowContext: snow.DefaultContextTest()}, test.caller, precompile.RewardManagerAddress, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { require.ErrorContains(t, err, test.expectedErr) } else { diff --git a/core/test_blockchain.go b/core/test_blockchain.go index 29b465a22f..1f66a44b82 100644 --- a/core/test_blockchain.go +++ b/core/test_blockchain.go @@ -17,6 +17,9 @@ import ( "github.com/ava-labs/subnet-evm/ethdb" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/deployerallowlist" + "github.com/ava-labs/subnet-evm/precompile/feemanager" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/assert" @@ -1546,8 +1549,8 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, chainC genesisBalance := new(big.Int).Mul(big.NewInt(1000000), big.NewInt(params.Ether)) config := *params.TestChainConfig // Set all of the required config parameters - config.ContractDeployerAllowListConfig = precompile.NewContractDeployerAllowListConfig(big.NewInt(0), []common.Address{addr1}, nil) - config.FeeManagerConfig = precompile.NewFeeManagerConfig(big.NewInt(0), []common.Address{addr1}, nil, nil) + config.ContractDeployerAllowListConfig = deployerallowlist.NewContractDeployerAllowListConfig(big.NewInt(0), []common.Address{addr1}, nil) + config.FeeManagerConfig = feemanager.NewFeeManagerConfig(big.NewInt(0), []common.Address{addr1}, nil, nil) gspec := &Genesis{ Config: &config, Alloc: GenesisAlloc{addr1: {Balance: genesisBalance}}, @@ -1587,7 +1590,7 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, chainC "allow list": { addTx: func(gen *BlockGen) { feeCap := new(big.Int).Add(gen.BaseFee(), tip) - input, err := precompile.PackModifyAllowList(addr2, precompile.AllowListAdmin) + input, err := allowlist.PackModifyAllowList(addr2, allowlist.AllowListAdmin) if err != nil { t.Fatal(err) } @@ -1609,31 +1612,31 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, chainC gen.AddTx(signedTx) }, verifyState: func(sdb *state.StateDB) error { - res := precompile.GetContractDeployerAllowListStatus(sdb, addr1) - if precompile.AllowListAdmin != res { - return fmt.Errorf("unexpected allow list status for addr1 %s, expected %s", res, precompile.AllowListAdmin) + res := deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr1) + if allowlist.AllowListAdmin != res { + return fmt.Errorf("unexpected allow list status for addr1 %s, expected %s", res, allowlist.AllowListAdmin) } - res = precompile.GetContractDeployerAllowListStatus(sdb, addr2) - if precompile.AllowListAdmin != res { - return fmt.Errorf("unexpected allow list status for addr2 %s, expected %s", res, precompile.AllowListAdmin) + res = deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr2) + if allowlist.AllowListAdmin != res { + return fmt.Errorf("unexpected allow list status for addr2 %s, expected %s", res, allowlist.AllowListAdmin) } return nil }, verifyGenesis: func(sdb *state.StateDB) { - res := precompile.GetContractDeployerAllowListStatus(sdb, addr1) - if precompile.AllowListAdmin != res { - t.Fatalf("unexpected allow list status for addr1 %s, expected %s", res, precompile.AllowListAdmin) + res := deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr1) + if allowlist.AllowListAdmin != res { + t.Fatalf("unexpected allow list status for addr1 %s, expected %s", res, allowlist.AllowListAdmin) } - res = precompile.GetContractDeployerAllowListStatus(sdb, addr2) - if precompile.AllowListNoRole != res { - t.Fatalf("unexpected allow list status for addr2 %s, expected %s", res, precompile.AllowListNoRole) + res = deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr2) + if allowlist.AllowListNoRole != res { + t.Fatalf("unexpected allow list status for addr2 %s, expected %s", res, allowlist.AllowListNoRole) } }, }, "fee manager set config": { addTx: func(gen *BlockGen) { feeCap := new(big.Int).Add(gen.BaseFee(), tip) - input, err := precompile.PackSetFeeConfig(testFeeConfig) + input, err := feemanager.PackSetFeeConfig(testFeeConfig) if err != nil { t.Fatal(err) } @@ -1655,10 +1658,10 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, chainC gen.AddTx(signedTx) }, verifyState: func(sdb *state.StateDB) error { - res := precompile.GetFeeConfigManagerStatus(sdb, addr1) - assert.Equal(precompile.AllowListAdmin, res) + res := feemanager.GetFeeConfigManagerStatus(sdb, addr1) + assert.Equal(allowlist.AllowListAdmin, res) - storedConfig := precompile.GetStoredFeeConfig(sdb) + storedConfig := feemanager.GetStoredFeeConfig(sdb) assert.EqualValues(testFeeConfig, storedConfig) feeConfig, _, err := blockchain.GetFeeConfigAt(blockchain.CurrentHeader()) @@ -1667,8 +1670,8 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, chainC return nil }, verifyGenesis: func(sdb *state.StateDB) { - res := precompile.GetFeeConfigManagerStatus(sdb, addr1) - assert.Equal(precompile.AllowListAdmin, res) + res := feemanager.GetFeeConfigManagerStatus(sdb, addr1) + assert.Equal(allowlist.AllowListAdmin, res) feeConfig, _, err := blockchain.GetFeeConfigAt(blockchain.Genesis().Header()) assert.NoError(err) diff --git a/core/tx_pool.go b/core/tx_pool.go index e60e6a22ec..e6e49047b2 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -43,6 +43,7 @@ import ( "github.com/ava-labs/subnet-evm/metrics" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/txallowlist" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/prque" "github.com/ethereum/go-ethereum/event" @@ -694,9 +695,9 @@ func (pool *TxPool) checkTxState(from common.Address, tx *types.Transaction) err // If the tx allow list is enabled, return an error if the from address is not allow listed. headTimestamp := big.NewInt(int64(pool.currentHead.Time)) if pool.chainconfig.IsPrecompileEnabled(precompile.TxAllowListAddress, headTimestamp) { - txAllowListRole := precompile.GetTxAllowListStatus(pool.currentState, from) + txAllowListRole := txallowlist.GetTxAllowListStatus(pool.currentState, from) if !txAllowListRole.IsEnabled() { - return fmt.Errorf("%w: %s", precompile.ErrSenderAddressNotAllowListed, from) + return fmt.Errorf("%w: %s", txallowlist.ErrSenderAddressNotAllowListed, from) } } return nil diff --git a/core/vm/evm.go b/core/vm/evm.go index bb4b14b3e8..86849d05ad 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -36,6 +36,7 @@ import ( "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/deployerallowlist" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -508,7 +509,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } // If the allow list is enabled, check that [evm.TxContext.Origin] has permission to deploy a contract. if evm.chainRules.IsPrecompileEnabled(precompile.ContractDeployerAllowListAddress) { - allowListRole := precompile.GetContractDeployerAllowListStatus(evm.StateDB, evm.TxContext.Origin) + allowListRole := deployerallowlist.GetContractDeployerAllowListStatus(evm.StateDB, evm.TxContext.Origin) if !allowListRole.IsEnabled() { return nil, common.Address{}, 0, fmt.Errorf("tx.origin %s is not authorized to deploy a contract", evm.TxContext.Origin) } diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index 62e770564f..0ed41c7df9 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -41,6 +41,7 @@ import ( "github.com/ava-labs/subnet-evm/ethdb" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/feemanager" "github.com/ava-labs/subnet-evm/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -434,13 +435,13 @@ func TestSuggestGasPriceAfterFeeConfigUpdate(t *testing.T) { // create a chain config with fee manager enabled at genesis with [addr] as the admin chainConfig := *params.TestChainConfig - chainConfig.FeeManagerConfig = precompile.NewFeeManagerConfig(big.NewInt(0), []common.Address{addr}, nil, nil) + chainConfig.FeeManagerConfig = feemanager.NewFeeManagerConfig(big.NewInt(0), []common.Address{addr}, nil, nil) // create a fee config with higher MinBaseFee and prepare it for inclusion in a tx signer := types.LatestSigner(params.TestChainConfig) highFeeConfig := chainConfig.FeeConfig highFeeConfig.MinBaseFee = big.NewInt(28_000_000_000) - data, err := precompile.PackSetFeeConfig(highFeeConfig) + data, err := feemanager.PackSetFeeConfig(highFeeConfig) require.NoError(err) // before issuing the block changing the fee into the chain, the fee estimation should diff --git a/params/precompile_config.go b/params/precompile_config.go index cd629ced55..e19c3041f7 100644 --- a/params/precompile_config.go +++ b/params/precompile_config.go @@ -8,6 +8,11 @@ import ( "math/big" "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/deployerallowlist" + "github.com/ava-labs/subnet-evm/precompile/feemanager" + "github.com/ava-labs/subnet-evm/precompile/nativeminter" + "github.com/ava-labs/subnet-evm/precompile/rewardmanager" + "github.com/ava-labs/subnet-evm/precompile/txallowlist" "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -17,11 +22,11 @@ import ( // each of the possible stateful precompile types that can be activated // as a network upgrade. type PrecompileUpgrade struct { - ContractDeployerAllowListConfig *precompile.ContractDeployerAllowListConfig `json:"contractDeployerAllowListConfig,omitempty"` // Config for the contract deployer allow list precompile - ContractNativeMinterConfig *precompile.ContractNativeMinterConfig `json:"contractNativeMinterConfig,omitempty"` // Config for the native minter precompile - TxAllowListConfig *precompile.TxAllowListConfig `json:"txAllowListConfig,omitempty"` // Config for the tx allow list precompile - FeeManagerConfig *precompile.FeeConfigManagerConfig `json:"feeManagerConfig,omitempty"` // Config for the fee manager precompile - RewardManagerConfig *precompile.RewardManagerConfig `json:"rewardManagerConfig,omitempty"` // Config for the reward manager precompile + ContractDeployerAllowListConfig *deployerallowlist.ContractDeployerAllowListConfig `json:"contractDeployerAllowListConfig,omitempty"` // Config for the contract deployer allow list precompile + ContractNativeMinterConfig *nativeminter.ContractNativeMinterConfig `json:"contractNativeMinterConfig,omitempty"` // Config for the native minter precompile + TxAllowListConfig *txallowlist.TxAllowListConfig `json:"txAllowListConfig,omitempty"` // Config for the tx allow list precompile + FeeManagerConfig *feemanager.FeeConfigManagerConfig `json:"feeManagerConfig,omitempty"` // Config for the fee manager precompile + RewardManagerConfig *rewardmanager.RewardManagerConfig `json:"rewardManagerConfig,omitempty"` // Config for the reward manager precompile // ADD YOUR PRECOMPILE HERE // {YourPrecompile}Config *precompile.{YourPrecompile}Config `json:"{yourPrecompile}Config,omitempty"` } @@ -171,19 +176,19 @@ func (c *ChainConfig) GetPrecompileConfig(address common.Address, blockTimestamp func (c *ChainConfig) GetActivePrecompileUpgrade(blockTimestamp *big.Int) PrecompileUpgrade { pu := PrecompileUpgrade{} if config := c.GetPrecompileConfig(precompile.ContractDeployerAllowListAddress, blockTimestamp); config != nil && !config.IsDisabled() { - pu.ContractDeployerAllowListConfig = config.(*precompile.ContractDeployerAllowListConfig) + pu.ContractDeployerAllowListConfig = config.(*deployerallowlist.ContractDeployerAllowListConfig) } if config := c.GetPrecompileConfig(precompile.ContractNativeMinterAddress, blockTimestamp); config != nil && !config.IsDisabled() { - pu.ContractNativeMinterConfig = config.(*precompile.ContractNativeMinterConfig) + pu.ContractNativeMinterConfig = config.(*nativeminter.ContractNativeMinterConfig) } if config := c.GetPrecompileConfig(precompile.TxAllowListAddress, blockTimestamp); config != nil && !config.IsDisabled() { - pu.TxAllowListConfig = config.(*precompile.TxAllowListConfig) + pu.TxAllowListConfig = config.(*txallowlist.TxAllowListConfig) } if config := c.GetPrecompileConfig(precompile.FeeConfigManagerAddress, blockTimestamp); config != nil && !config.IsDisabled() { - pu.FeeManagerConfig = config.(*precompile.FeeConfigManagerConfig) + pu.FeeManagerConfig = config.(*feemanager.FeeConfigManagerConfig) } if config := c.GetPrecompileConfig(precompile.RewardManagerAddress, blockTimestamp); config != nil && !config.IsDisabled() { - pu.RewardManagerConfig = config.(*precompile.RewardManagerConfig) + pu.RewardManagerConfig = config.(*rewardmanager.RewardManagerConfig) } // ADD YOUR PRECOMPILE HERE diff --git a/params/precompile_config_test.go b/params/precompile_config_test.go index 3ac584aec4..4d7a114d14 100644 --- a/params/precompile_config_test.go +++ b/params/precompile_config_test.go @@ -9,6 +9,9 @@ import ( "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/deployerallowlist" + "github.com/ava-labs/subnet-evm/precompile/feemanager" + "github.com/ava-labs/subnet-evm/precompile/txallowlist" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -19,16 +22,16 @@ func TestVerifyWithChainConfig(t *testing.T) { baseConfig := *SubnetEVMDefaultChainConfig config := &baseConfig config.PrecompileUpgrade = PrecompileUpgrade{ - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(2), nil, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(2), nil, nil), } config.PrecompileUpgrades = []PrecompileUpgrade{ { // disable TxAllowList at timestamp 4 - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(4)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(4)), }, { // re-enable TxAllowList at timestamp 5 - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(5), admins, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(5), admins, nil), }, } @@ -41,7 +44,7 @@ func TestVerifyWithChainConfig(t *testing.T) { badConfig.PrecompileUpgrades = append( badConfig.PrecompileUpgrades, PrecompileUpgrade{ - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(5)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(5)), }, ) err = badConfig.Verify() @@ -52,7 +55,7 @@ func TestVerifyWithChainConfig(t *testing.T) { badConfig.PrecompileUpgrades = append( badConfig.PrecompileUpgrades, PrecompileUpgrade{ - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(5), admins, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(5), admins, nil), }, ) err = badConfig.Verify() @@ -70,10 +73,10 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { name: "enable and disable tx allow list", upgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(1), admins, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(1), admins, nil), }, { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(2)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(2)), }, }, expectedError: "", @@ -82,13 +85,13 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { name: "invalid allow list config in tx allowlist", upgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(1), admins, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(1), admins, nil), }, { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(2)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(2)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(3), admins, admins), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(3), admins, admins), }, }, expectedError: "cannot set address", @@ -97,7 +100,7 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { name: "invalid initial fee manager config", upgrades: []PrecompileUpgrade{ { - FeeManagerConfig: precompile.NewFeeManagerConfig(big.NewInt(3), admins, nil, + FeeManagerConfig: feemanager.NewFeeManagerConfig(big.NewInt(3), admins, nil, &commontype.FeeConfig{ GasLimit: big.NewInt(-1), }), @@ -109,7 +112,7 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { name: "invalid initial fee manager config gas limit 0", upgrades: []PrecompileUpgrade{ { - FeeManagerConfig: precompile.NewFeeManagerConfig(big.NewInt(3), admins, nil, + FeeManagerConfig: feemanager.NewFeeManagerConfig(big.NewInt(3), admins, nil, &commontype.FeeConfig{ GasLimit: big.NewInt(0), }), @@ -145,14 +148,14 @@ func TestVerifyPrecompiles(t *testing.T) { { name: "invalid allow list config in tx allowlist", upgrade: PrecompileUpgrade{ - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(3), admins, admins), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(3), admins, admins), }, expectedError: "cannot set address", }, { name: "invalid initial fee manager config", upgrade: PrecompileUpgrade{ - FeeManagerConfig: precompile.NewFeeManagerConfig(big.NewInt(3), admins, nil, + FeeManagerConfig: feemanager.NewFeeManagerConfig(big.NewInt(3), admins, nil, &commontype.FeeConfig{ GasLimit: big.NewInt(-1), }), @@ -183,10 +186,10 @@ func TestVerifyRequiresSortedTimestamps(t *testing.T) { config := &baseConfig config.PrecompileUpgrades = []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(2), admins, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(2), admins, nil), }, { - ContractDeployerAllowListConfig: precompile.NewContractDeployerAllowListConfig(big.NewInt(1), admins, nil), + ContractDeployerAllowListConfig: deployerallowlist.NewContractDeployerAllowListConfig(big.NewInt(1), admins, nil), }, } @@ -200,7 +203,7 @@ func TestGetPrecompileConfig(t *testing.T) { baseConfig := *SubnetEVMDefaultChainConfig config := &baseConfig config.PrecompileUpgrade = PrecompileUpgrade{ - ContractDeployerAllowListConfig: precompile.NewContractDeployerAllowListConfig(big.NewInt(10), nil, nil), + ContractDeployerAllowListConfig: deployerallowlist.NewContractDeployerAllowListConfig(big.NewInt(10), nil, nil), } deployerConfig := config.GetPrecompileConfig(precompile.ContractDeployerAllowListAddress, big.NewInt(0)) diff --git a/params/upgrade_config_test.go b/params/upgrade_config_test.go index 38c93f5386..e8e5fb9750 100644 --- a/params/upgrade_config_test.go +++ b/params/upgrade_config_test.go @@ -7,7 +7,8 @@ import ( "math/big" "testing" - "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/deployerallowlist" + "github.com/ava-labs/subnet-evm/precompile/txallowlist" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" ) @@ -15,7 +16,7 @@ import ( func TestVerifyUpgradeConfig(t *testing.T) { admins := []common.Address{{1}} chainConfig := *TestChainConfig - chainConfig.TxAllowListConfig = precompile.NewTxAllowListConfig(big.NewInt(1), admins, nil) + chainConfig.TxAllowListConfig = txallowlist.NewTxAllowListConfig(big.NewInt(1), admins, nil) type test struct { upgrades []PrecompileUpgrade @@ -27,7 +28,7 @@ func TestVerifyUpgradeConfig(t *testing.T) { expectedErrorString: "disable should be [true]", upgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(2), admins, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(2), admins, nil), }, }, }, @@ -35,7 +36,7 @@ func TestVerifyUpgradeConfig(t *testing.T) { expectedErrorString: "config timestamp (0) <= previous timestamp (1)", upgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(0)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(0)), }, }, }, @@ -43,7 +44,7 @@ func TestVerifyUpgradeConfig(t *testing.T) { expectedErrorString: "config timestamp (1) <= previous timestamp (1)", upgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(1)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(1)), }, }, }, @@ -70,8 +71,8 @@ func TestVerifyUpgradeConfig(t *testing.T) { func TestCheckCompatibleUpgradeConfigs(t *testing.T) { admins := []common.Address{{1}} chainConfig := *TestChainConfig - chainConfig.TxAllowListConfig = precompile.NewTxAllowListConfig(big.NewInt(1), admins, nil) - chainConfig.ContractDeployerAllowListConfig = precompile.NewContractDeployerAllowListConfig(big.NewInt(10), admins, nil) + chainConfig.TxAllowListConfig = txallowlist.NewTxAllowListConfig(big.NewInt(1), admins, nil) + chainConfig.ContractDeployerAllowListConfig = deployerallowlist.NewContractDeployerAllowListConfig(big.NewInt(10), admins, nil) type test struct { configs []*UpgradeConfig @@ -86,10 +87,10 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), admins, nil), }, }, }, @@ -101,20 +102,20 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), admins, nil), }, }, }, { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(8), admins, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(8), admins, nil), }, }, }, @@ -127,20 +128,20 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), admins, nil), }, }, }, { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(8), admins, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(8), admins, nil), }, }, }, @@ -152,17 +153,17 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), admins, nil), }, }, }, { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), }, }, }, @@ -175,17 +176,17 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), admins, nil), }, }, }, { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), }, }, }, @@ -198,21 +199,21 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), admins, nil), }, }, }, { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), }, { // uses a different (empty) admin list, not allowed - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), []common.Address{}, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), []common.Address{}, nil), }, }, }, @@ -224,20 +225,20 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), admins, nil), }, }, }, { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), admins, nil), }, }, }, diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index baf1a1c322..c96ce4b31b 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -20,6 +20,11 @@ import ( "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/metrics" "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/deployerallowlist" + "github.com/ava-labs/subnet-evm/precompile/feemanager" + "github.com/ava-labs/subnet-evm/precompile/rewardmanager" + "github.com/ava-labs/subnet-evm/precompile/txallowlist" "github.com/ava-labs/subnet-evm/trie" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" @@ -2116,7 +2121,7 @@ func TestBuildAllowListActivationBlock(t *testing.T) { if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { t.Fatal(err) } - genesis.Config.ContractDeployerAllowListConfig = precompile.NewContractDeployerAllowListConfig(big.NewInt(time.Now().Unix()), testEthAddrs, nil) + genesis.Config.ContractDeployerAllowListConfig = deployerallowlist.NewContractDeployerAllowListConfig(big.NewInt(time.Now().Unix()), testEthAddrs, nil) genesisJSON, err := genesis.MarshalJSON() if err != nil { @@ -2137,9 +2142,9 @@ func TestBuildAllowListActivationBlock(t *testing.T) { if err != nil { t.Fatal(err) } - role := precompile.GetContractDeployerAllowListStatus(genesisState, testEthAddrs[0]) - if role != precompile.AllowListNoRole { - t.Fatalf("Expected allow list status to be set to no role: %s, but found: %s", precompile.AllowListNoRole, role) + role := deployerallowlist.GetContractDeployerAllowListStatus(genesisState, testEthAddrs[0]) + if role != allowlist.AllowListNoRole { + t.Fatalf("Expected allow list status to be set to no role: %s, but found: %s", allowlist.AllowListNoRole, role) } // Send basic transaction to construct a simple block and confirm that the precompile state configuration in the worker behaves correctly. @@ -2167,9 +2172,9 @@ func TestBuildAllowListActivationBlock(t *testing.T) { if err != nil { t.Fatal(err) } - role = precompile.GetContractDeployerAllowListStatus(blkState, testEthAddrs[0]) - if role != precompile.AllowListAdmin { - t.Fatalf("Expected allow list status to be set to Admin: %s, but found: %s", precompile.AllowListAdmin, role) + role = deployerallowlist.GetContractDeployerAllowListStatus(blkState, testEthAddrs[0]) + if role != allowlist.AllowListAdmin { + t.Fatalf("Expected allow list status to be set to Admin: %s, but found: %s", allowlist.AllowListAdmin, role) } } @@ -2180,7 +2185,7 @@ func TestTxAllowListSuccessfulTx(t *testing.T) { if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { t.Fatal(err) } - genesis.Config.TxAllowListConfig = precompile.NewTxAllowListConfig(big.NewInt(0), testEthAddrs[0:1], nil) + genesis.Config.TxAllowListConfig = txallowlist.NewTxAllowListConfig(big.NewInt(0), testEthAddrs[0:1], nil) genesisJSON, err := genesis.MarshalJSON() if err != nil { t.Fatal(err) @@ -2202,13 +2207,13 @@ func TestTxAllowListSuccessfulTx(t *testing.T) { } // Check that address 0 is whitelisted and address 1 is not - role := precompile.GetTxAllowListStatus(genesisState, testEthAddrs[0]) - if role != precompile.AllowListAdmin { - t.Fatalf("Expected allow list status to be set to admin: %s, but found: %s", precompile.AllowListAdmin, role) + role := txallowlist.GetTxAllowListStatus(genesisState, testEthAddrs[0]) + if role != allowlist.AllowListAdmin { + t.Fatalf("Expected allow list status to be set to admin: %s, but found: %s", allowlist.AllowListAdmin, role) } - role = precompile.GetTxAllowListStatus(genesisState, testEthAddrs[1]) - if role != precompile.AllowListNoRole { - t.Fatalf("Expected allow list status to be set to no role: %s, but found: %s", precompile.AllowListNoRole, role) + role = txallowlist.GetTxAllowListStatus(genesisState, testEthAddrs[1]) + if role != allowlist.AllowListNoRole { + t.Fatalf("Expected allow list status to be set to no role: %s, but found: %s", allowlist.AllowListNoRole, role) } // Submit a successful transaction @@ -2229,7 +2234,7 @@ func TestTxAllowListSuccessfulTx(t *testing.T) { } errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) - if err := errs[0]; !errors.Is(err, precompile.ErrSenderAddressNotAllowListed) { + if err := errs[0]; !errors.Is(err, txallowlist.ErrSenderAddressNotAllowListed) { t.Fatalf("expected ErrSenderAddressNotAllowListed, got: %s", err) } @@ -2256,7 +2261,7 @@ func TestTxAllowListDisablePrecompile(t *testing.T) { t.Fatal(err) } enableAllowListTimestamp := time.Unix(0, 0) // enable at genesis - genesis.Config.TxAllowListConfig = precompile.NewTxAllowListConfig(big.NewInt(enableAllowListTimestamp.Unix()), testEthAddrs[0:1], nil) + genesis.Config.TxAllowListConfig = txallowlist.NewTxAllowListConfig(big.NewInt(enableAllowListTimestamp.Unix()), testEthAddrs[0:1], nil) genesisJSON, err := genesis.MarshalJSON() if err != nil { t.Fatal(err) @@ -2297,13 +2302,13 @@ func TestTxAllowListDisablePrecompile(t *testing.T) { } // Check that address 0 is whitelisted and address 1 is not - role := precompile.GetTxAllowListStatus(genesisState, testEthAddrs[0]) - if role != precompile.AllowListAdmin { - t.Fatalf("Expected allow list status to be set to admin: %s, but found: %s", precompile.AllowListAdmin, role) + role := txallowlist.GetTxAllowListStatus(genesisState, testEthAddrs[0]) + if role != allowlist.AllowListAdmin { + t.Fatalf("Expected allow list status to be set to admin: %s, but found: %s", allowlist.AllowListAdmin, role) } - role = precompile.GetTxAllowListStatus(genesisState, testEthAddrs[1]) - if role != precompile.AllowListNoRole { - t.Fatalf("Expected allow list status to be set to no role: %s, but found: %s", precompile.AllowListNoRole, role) + role = txallowlist.GetTxAllowListStatus(genesisState, testEthAddrs[1]) + if role != allowlist.AllowListNoRole { + t.Fatalf("Expected allow list status to be set to no role: %s, but found: %s", allowlist.AllowListNoRole, role) } // Submit a successful transaction @@ -2324,7 +2329,7 @@ func TestTxAllowListDisablePrecompile(t *testing.T) { } errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) - if err := errs[0]; !errors.Is(err, precompile.ErrSenderAddressNotAllowListed) { + if err := errs[0]; !errors.Is(err, txallowlist.ErrSenderAddressNotAllowListed) { t.Fatalf("expected ErrSenderAddressNotAllowListed, got: %s", err) } @@ -2368,7 +2373,7 @@ func TestFeeManagerChangeFee(t *testing.T) { if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { t.Fatal(err) } - genesis.Config.FeeManagerConfig = precompile.NewFeeManagerConfig(big.NewInt(0), testEthAddrs[0:1], nil, nil) + genesis.Config.FeeManagerConfig = feemanager.NewFeeManagerConfig(big.NewInt(0), testEthAddrs[0:1], nil, nil) // set a lower fee config now testLowFeeConfig := commontype.FeeConfig{ @@ -2406,12 +2411,12 @@ func TestFeeManagerChangeFee(t *testing.T) { } // Check that address 0 is whitelisted and address 1 is not - role := precompile.GetFeeConfigManagerStatus(genesisState, testEthAddrs[0]) - if role != precompile.AllowListAdmin { + role := feemanager.GetFeeConfigManagerStatus(genesisState, testEthAddrs[0]) + if role != allowlist.AllowListAdmin { t.Fatalf("Expected fee manager list status to be set to admin: %s, but found: %s", precompile.FeeConfigManagerAddress, role) } - role = precompile.GetFeeConfigManagerStatus(genesisState, testEthAddrs[1]) - if role != precompile.AllowListNoRole { + role = feemanager.GetFeeConfigManagerStatus(genesisState, testEthAddrs[1]) + if role != allowlist.AllowListNoRole { t.Fatalf("Expected fee manager list status to be set to no role: %s, but found: %s", precompile.FeeConfigManagerAddress, role) } // Contract is initialized but no preconfig is given, reader should return genesis fee config @@ -2424,7 +2429,7 @@ func TestFeeManagerChangeFee(t *testing.T) { testHighFeeConfig := testLowFeeConfig testHighFeeConfig.MinBaseFee = big.NewInt(28_000_000_000) - data, err := precompile.PackSetFeeConfig(testHighFeeConfig) + data, err := feemanager.PackSetFeeConfig(testHighFeeConfig) require.NoError(t, err) tx := types.NewTx(&types.DynamicFeeTx{ @@ -2608,7 +2613,7 @@ func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { genesis := &core.Genesis{} require.NoError(t, genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM))) - genesis.Config.RewardManagerConfig = precompile.NewRewardManagerConfig(common.Big0, testEthAddrs[0:1], nil, nil) + genesis.Config.RewardManagerConfig = rewardmanager.NewRewardManagerConfig(common.Big0, testEthAddrs[0:1], nil, nil) genesis.Config.AllowFeeRecipients = true // enable this in genesis to test if this is recognized by the reward manager genesisJSON, err := genesis.MarshalJSON() require.NoError(t, err) @@ -2649,10 +2654,10 @@ func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) testAddr := common.HexToAddress("0x9999991111") - data, err := precompile.PackSetRewardAddress(testAddr) + data, err := rewardmanager.PackSetRewardAddress(testAddr) require.NoError(t, err) - gas := 21000 + 240 + precompile.SetRewardAddressGasCost // 21000 for tx, 240 for tx data + gas := 21000 + 240 + rewardmanager.SetRewardAddressGasCost // 21000 for tx, 240 for tx data tx := types.NewTransaction(uint64(0), precompile.RewardManagerAddress, big.NewInt(1), gas, big.NewInt(testMinGasPrice), data) @@ -2748,7 +2753,7 @@ func TestRewardManagerPrecompileAllowFeeRecipients(t *testing.T) { genesis := &core.Genesis{} require.NoError(t, genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM))) - genesis.Config.RewardManagerConfig = precompile.NewRewardManagerConfig(common.Big0, testEthAddrs[0:1], nil, nil) + genesis.Config.RewardManagerConfig = rewardmanager.NewRewardManagerConfig(common.Big0, testEthAddrs[0:1], nil, nil) genesis.Config.AllowFeeRecipients = false // disable this in genesis genesisJSON, err := genesis.MarshalJSON() require.NoError(t, err) @@ -2785,10 +2790,10 @@ func TestRewardManagerPrecompileAllowFeeRecipients(t *testing.T) { newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) - data, err := precompile.PackAllowFeeRecipients() + data, err := rewardmanager.PackAllowFeeRecipients() require.NoError(t, err) - gas := 21000 + 240 + precompile.AllowFeeRecipientsGasCost // 21000 for tx, 240 for tx data + gas := 21000 + 240 + rewardmanager.AllowFeeRecipientsGasCost // 21000 for tx, 240 for tx data tx := types.NewTransaction(uint64(0), precompile.RewardManagerAddress, big.NewInt(1), gas, big.NewInt(testMinGasPrice), data) diff --git a/plugin/evm/vm_upgrade_bytes_test.go b/plugin/evm/vm_upgrade_bytes_test.go index 22c78b5800..2adca151fe 100644 --- a/plugin/evm/vm_upgrade_bytes_test.go +++ b/plugin/evm/vm_upgrade_bytes_test.go @@ -18,7 +18,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/metrics" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/txallowlist" "github.com/stretchr/testify/assert" ) @@ -28,7 +28,7 @@ func TestVMUpgradeBytesPrecompile(t *testing.T) { upgradeConfig := ¶ms.UpgradeConfig{ PrecompileUpgrades: []params.PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(enableAllowListTimestamp.Unix()), testEthAddrs[0:1], nil), + TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(enableAllowListTimestamp.Unix()), testEthAddrs[0:1], nil), }, }, } @@ -57,7 +57,7 @@ func TestVMUpgradeBytesPrecompile(t *testing.T) { t.Fatal(err) } errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) - if err := errs[0]; !errors.Is(err, precompile.ErrSenderAddressNotAllowListed) { + if err := errs[0]; !errors.Is(err, txallowlist.ErrSenderAddressNotAllowListed) { t.Fatalf("expected ErrSenderAddressNotAllowListed, got: %s", err) } @@ -71,7 +71,7 @@ func TestVMUpgradeBytesPrecompile(t *testing.T) { upgradeConfig.PrecompileUpgrades = append( upgradeConfig.PrecompileUpgrades, params.PrecompileUpgrade{ - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(disableAllowListTimestamp.Unix())), + TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(disableAllowListTimestamp.Unix())), }, ) upgradeBytesJSON, err = json.Marshal(upgradeConfig) @@ -108,7 +108,7 @@ func TestVMUpgradeBytesPrecompile(t *testing.T) { // Submit a rejected transaction, should throw an error errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) - if err := errs[0]; !errors.Is(err, precompile.ErrSenderAddressNotAllowListed) { + if err := errs[0]; !errors.Is(err, txallowlist.ErrSenderAddressNotAllowListed) { t.Fatalf("expected ErrSenderAddressNotAllowListed, got: %s", err) } diff --git a/precompile/allow_list.go b/precompile/allowlist/allow_list.go similarity index 68% rename from precompile/allow_list.go rename to precompile/allowlist/allow_list.go index b4f75becdb..ced37ed530 100644 --- a/precompile/allow_list.go +++ b/precompile/allowlist/allow_list.go @@ -1,13 +1,14 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package precompile +package allowlist import ( "errors" "fmt" "math/big" + "github.com/ava-labs/subnet-evm/precompile" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" ) @@ -18,8 +19,8 @@ const ( SetNoneFuncKey = "setNone" ReadAllowListFuncKey = "readAllowList" - ModifyAllowListGasCost = writeGasCostPerSlot - ReadAllowListGasCost = readGasCostPerSlot + ModifyAllowListGasCost = precompile.WriteGasCostPerSlot + ReadAllowListGasCost = precompile.ReadGasCostPerSlot ) var ( @@ -28,10 +29,10 @@ var ( AllowListAdmin AllowListRole = AllowListRole(common.BigToHash(big.NewInt(2))) // Admin - allowed to modify both the admin and deployer list as well as deploy contracts // AllowList function signatures - setAdminSignature = CalculateFunctionSelector("setAdmin(address)") - setEnabledSignature = CalculateFunctionSelector("setEnabled(address)") - setNoneSignature = CalculateFunctionSelector("setNone(address)") - readAllowListSignature = CalculateFunctionSelector("readAllowList(address)") + setAdminSignature = precompile.CalculateFunctionSelector("setAdmin(address)") + setEnabledSignature = precompile.CalculateFunctionSelector("setEnabled(address)") + setNoneSignature = precompile.CalculateFunctionSelector("setNone(address)") + readAllowListSignature = precompile.CalculateFunctionSelector("readAllowList(address)") // Error returned when an invalid write is attempted ErrCannotModifyAllowList = errors.New("non-admin cannot modify allow list") @@ -46,12 +47,12 @@ type AllowListConfig struct { // Configure initializes the address space of [precompileAddr] by initializing the role of each of // the addresses in [AllowListAdmins]. -func (c *AllowListConfig) Configure(state StateDB, precompileAddr common.Address) error { +func (c *AllowListConfig) Configure(state precompile.StateDB, precompileAddr common.Address) error { for _, enabledAddr := range c.EnabledAddresses { - setAllowListRole(state, precompileAddr, enabledAddr, AllowListEnabled) + SetAllowListRole(state, precompileAddr, enabledAddr, AllowListEnabled) } for _, adminAddr := range c.AllowListAdmins { - setAllowListRole(state, precompileAddr, adminAddr, AllowListAdmin) + SetAllowListRole(state, precompileAddr, adminAddr, AllowListAdmin) } return nil } @@ -112,18 +113,18 @@ func (c *AllowListConfig) Verify() error { return nil } -// getAllowListStatus returns the allow list role of [address] for the precompile +// GetAllowListStatus returns the allow list role of [address] for the precompile // at [precompileAddr] -func getAllowListStatus(state StateDB, precompileAddr common.Address, address common.Address) AllowListRole { +func GetAllowListStatus(state precompile.StateDB, precompileAddr common.Address, address common.Address) AllowListRole { // Generate the state key for [address] addressKey := address.Hash() return AllowListRole(state.GetState(precompileAddr, addressKey)) } -// setAllowListRole sets the permissions of [address] to [role] for the precompile +// SetAllowListRole sets the permissions of [address] to [role] for the precompile // at [precompileAddr]. // assumes [role] has already been verified as valid. -func setAllowListRole(stateDB StateDB, precompileAddr, address common.Address, role AllowListRole) { +func SetAllowListRole(stateDB precompile.StateDB, precompileAddr, address common.Address, role AllowListRole) { // Generate the state key for [address] addressKey := address.Hash() // Assign [role] to the address @@ -139,7 +140,7 @@ func setAllowListRole(stateDB StateDB, precompileAddr, address common.Address, r // selector that should be encoded in the input. func PackModifyAllowList(address common.Address, role AllowListRole) ([]byte, error) { // function selector (4 bytes) + hash for address - input := make([]byte, 0, selectorLen+common.HashLength) + input := make([]byte, 0, precompile.SelectorLen+common.HashLength) switch role { case AllowListAdmin: @@ -158,7 +159,7 @@ func PackModifyAllowList(address common.Address, role AllowListRole) ([]byte, er // PackReadAllowList packs [address] into the input data to the read allow list function func PackReadAllowList(address common.Address) []byte { - input := make([]byte, 0, selectorLen+common.HashLength) + input := make([]byte, 0, precompile.SelectorLen+common.HashLength) input = append(input, readAllowListSignature...) input = append(input, address.Hash().Bytes()...) return input @@ -166,9 +167,9 @@ func PackReadAllowList(address common.Address) []byte { // createAllowListRoleSetter returns an execution function for setting the allow list status of the input address argument to [role]. // This execution function is speciifc to [precompileAddr]. -func createAllowListRoleSetter(precompileAddr common.Address, role AllowListRole) RunStatefulPrecompileFunc { - return func(evm PrecompileAccessibleState, callerAddr, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = deductGas(suppliedGas, ModifyAllowListGasCost); err != nil { +func createAllowListRoleSetter(precompileAddr common.Address, role AllowListRole) precompile.RunStatefulPrecompileFunc { + return func(evm precompile.PrecompileAccessibleState, callerAddr, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = precompile.DeductGas(suppliedGas, ModifyAllowListGasCost); err != nil { return nil, 0, err } @@ -185,12 +186,12 @@ func createAllowListRoleSetter(precompileAddr common.Address, role AllowListRole stateDB := evm.GetStateDB() // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := getAllowListStatus(stateDB, precompileAddr, callerAddr) + callerStatus := GetAllowListStatus(stateDB, precompileAddr, callerAddr) if !callerStatus.IsAdmin() { return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotModifyAllowList, callerAddr) } - setAllowListRole(stateDB, precompileAddr, modifyAddress, role) + SetAllowListRole(stateDB, precompileAddr, modifyAddress, role) // Return an empty output and the remaining gas return []byte{}, remainingGas, nil } @@ -199,9 +200,9 @@ func createAllowListRoleSetter(precompileAddr common.Address, role AllowListRole // createReadAllowList returns an execution function that reads the allow list for the given [precompileAddr]. // The execution function parses the input into a single address and returns the 32 byte hash that specifies the // designated role of that address -func createReadAllowList(precompileAddr common.Address) RunStatefulPrecompileFunc { - return func(evm PrecompileAccessibleState, callerAddr common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = deductGas(suppliedGas, ReadAllowListGasCost); err != nil { +func createReadAllowList(precompileAddr common.Address) precompile.RunStatefulPrecompileFunc { + return func(evm precompile.PrecompileAccessibleState, callerAddr common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = precompile.DeductGas(suppliedGas, ReadAllowListGasCost); err != nil { return nil, 0, err } @@ -210,17 +211,17 @@ func createReadAllowList(precompileAddr common.Address) RunStatefulPrecompileFun } readAddress := common.BytesToAddress(input) - role := getAllowListStatus(evm.GetStateDB(), precompileAddr, readAddress) + role := GetAllowListStatus(evm.GetStateDB(), precompileAddr, readAddress) roleBytes := common.Hash(role).Bytes() return roleBytes, remainingGas, nil } } // createAllowListPrecompile returns a StatefulPrecompiledContract with R/W control of an allow list at [precompileAddr] -func createAllowListPrecompile(precompileAddr common.Address) StatefulPrecompiledContract { +func CreateAllowListPrecompile(precompileAddr common.Address) precompile.StatefulPrecompiledContract { // Construct the contract with no fallback function. - allowListFuncs := createAllowListFunctions(precompileAddr) - contract, err := NewStatefulPrecompileContract(nil, allowListFuncs) + allowListFuncs := CreateAllowListFunctions(precompileAddr) + contract, err := precompile.NewStatefulPrecompileContract(nil, allowListFuncs) // TODO Change this to be returned as an error after refactoring this precompile // to use the new precompile template. if err != nil { @@ -229,11 +230,11 @@ func createAllowListPrecompile(precompileAddr common.Address) StatefulPrecompile return contract } -func createAllowListFunctions(precompileAddr common.Address) []*statefulPrecompileFunction { - setAdmin := newStatefulPrecompileFunction(setAdminSignature, createAllowListRoleSetter(precompileAddr, AllowListAdmin)) - setEnabled := newStatefulPrecompileFunction(setEnabledSignature, createAllowListRoleSetter(precompileAddr, AllowListEnabled)) - setNone := newStatefulPrecompileFunction(setNoneSignature, createAllowListRoleSetter(precompileAddr, AllowListNoRole)) - read := newStatefulPrecompileFunction(readAllowListSignature, createReadAllowList(precompileAddr)) +func CreateAllowListFunctions(precompileAddr common.Address) []*precompile.StatefulPrecompileFunction { + setAdmin := precompile.NewStatefulPrecompileFunction(setAdminSignature, createAllowListRoleSetter(precompileAddr, AllowListAdmin)) + setEnabled := precompile.NewStatefulPrecompileFunction(setEnabledSignature, createAllowListRoleSetter(precompileAddr, AllowListEnabled)) + setNone := precompile.NewStatefulPrecompileFunction(setNoneSignature, createAllowListRoleSetter(precompileAddr, AllowListNoRole)) + read := precompile.NewStatefulPrecompileFunction(readAllowListSignature, createReadAllowList(precompileAddr)) - return []*statefulPrecompileFunction{setAdmin, setEnabled, setNone, read} + return []*precompile.StatefulPrecompileFunction{setAdmin, setEnabled, setNone, read} } diff --git a/precompile/allow_list_role.go b/precompile/allowlist/allow_list_role.go similarity index 98% rename from precompile/allow_list_role.go rename to precompile/allowlist/allow_list_role.go index 0c815d0819..7e59f8420e 100644 --- a/precompile/allow_list_role.go +++ b/precompile/allowlist/allow_list_role.go @@ -1,7 +1,7 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package precompile +package allowlist import "github.com/ethereum/go-ethereum/common" diff --git a/precompile/config_test.go b/precompile/config_test.go deleted file mode 100644 index a396cec543..0000000000 --- a/precompile/config_test.go +++ /dev/null @@ -1,462 +0,0 @@ -// (c) 2022 Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package precompile - -import ( - "math/big" - "testing" - - "github.com/ava-labs/subnet-evm/commontype" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" - "github.com/stretchr/testify/require" -) - -var validFeeConfig = commontype.FeeConfig{ - GasLimit: big.NewInt(8_000_000), - TargetBlockRate: 2, // in seconds - - MinBaseFee: big.NewInt(25_000_000_000), - TargetGas: big.NewInt(15_000_000), - BaseFeeChangeDenominator: big.NewInt(36), - - MinBlockGasCost: big.NewInt(0), - MaxBlockGasCost: big.NewInt(1_000_000), - BlockGasCostStep: big.NewInt(200_000), -} - -func TestVerifyPrecompileUpgrades(t *testing.T) { - admins := []common.Address{{1}} - enableds := []common.Address{{2}} - tests := []struct { - name string - config StatefulPrecompileConfig - expectedError string - }{ - { - name: "invalid allow list config in tx allowlist", - config: NewTxAllowListConfig(big.NewInt(3), admins, admins), - expectedError: "cannot set address", - }, - { - name: "nil member allow list config in tx allowlist", - config: NewTxAllowListConfig(big.NewInt(3), nil, nil), - expectedError: "", - }, - { - name: "empty member allow list config in tx allowlist", - config: NewTxAllowListConfig(big.NewInt(3), []common.Address{}, []common.Address{}), - expectedError: "", - }, - { - name: "valid allow list config in tx allowlist", - config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), - expectedError: "", - }, - { - name: "invalid allow list config in deployer allowlist", - config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, admins), - expectedError: "cannot set address", - }, - { - name: "invalid allow list config in native minter allowlist", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, admins, nil), - expectedError: "cannot set address", - }, - { - name: "duplicate admins in config in native minter allowlist", - config: NewContractNativeMinterConfig(big.NewInt(3), append(admins, admins[0]), enableds, nil), - expectedError: "duplicate address", - }, - { - name: "duplicate enableds in config in native minter allowlist", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, append(enableds, enableds[0]), nil), - expectedError: "duplicate address", - }, - { - name: "invalid allow list config in fee manager allowlist", - config: NewFeeManagerConfig(big.NewInt(3), admins, admins, nil), - expectedError: "cannot set address", - }, - { - name: "invalid initial fee manager config", - config: NewFeeManagerConfig(big.NewInt(3), admins, nil, - &commontype.FeeConfig{ - GasLimit: big.NewInt(0), - }), - expectedError: "gasLimit = 0 cannot be less than or equal to 0", - }, - { - name: "nil amount in native minter config", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, - map[common.Address]*math.HexOrDecimal256{ - common.HexToAddress("0x01"): math.NewHexOrDecimal256(123), - common.HexToAddress("0x02"): nil, - }), - expectedError: "initial mint cannot contain nil", - }, - { - name: "negative amount in native minter config", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, - map[common.Address]*math.HexOrDecimal256{ - common.HexToAddress("0x01"): math.NewHexOrDecimal256(123), - common.HexToAddress("0x02"): math.NewHexOrDecimal256(-1), - }), - expectedError: "initial mint cannot contain invalid amount", - }, - { - name: "duplicate enableds in config in reward manager allowlist", - config: NewRewardManagerConfig(big.NewInt(3), admins, append(enableds, enableds[0]), nil), - expectedError: "duplicate address", - }, - { - name: "both reward mechanisms should not be activated at the same time in reward manager", - config: NewRewardManagerConfig(big.NewInt(3), admins, enableds, &InitialRewardConfig{ - AllowFeeRecipients: true, - RewardAddress: common.HexToAddress("0x01"), - }), - expectedError: ErrCannotEnableBothRewards.Error(), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - - err := tt.config.Verify() - if tt.expectedError == "" { - require.NoError(err) - } else { - require.ErrorContains(err, tt.expectedError) - } - }) - } -} - -func TestEqualTxAllowListConfig(t *testing.T) { - admins := []common.Address{{1}} - enableds := []common.Address{{2}} - tests := []struct { - name string - config StatefulPrecompileConfig - other StatefulPrecompileConfig - expected bool - }{ - { - name: "non-nil config and nil other", - config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), - other: nil, - expected: false, - }, - { - name: "different type", - config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), - other: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), - expected: false, - }, - { - name: "different admin", - config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), - other: NewTxAllowListConfig(big.NewInt(3), []common.Address{{3}}, enableds), - expected: false, - }, - { - name: "different enabled", - config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), - other: NewTxAllowListConfig(big.NewInt(3), admins, []common.Address{{3}}), - expected: false, - }, - { - name: "different timestamp", - config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), - other: NewTxAllowListConfig(big.NewInt(4), admins, enableds), - expected: false, - }, - { - name: "same config", - config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), - other: NewTxAllowListConfig(big.NewInt(3), admins, enableds), - expected: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - - require.Equal(tt.expected, tt.config.Equal(tt.other)) - }) - } -} - -func TestEqualContractDeployerAllowListConfig(t *testing.T) { - admins := []common.Address{{1}} - enableds := []common.Address{{2}} - tests := []struct { - name string - config StatefulPrecompileConfig - other StatefulPrecompileConfig - expected bool - }{ - { - name: "non-nil config and nil other", - config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), - other: nil, - expected: false, - }, - { - name: "different type", - config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), - other: NewTxAllowListConfig(big.NewInt(3), admins, enableds), - expected: false, - }, - { - name: "different admin", - config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), - other: NewContractDeployerAllowListConfig(big.NewInt(3), []common.Address{{3}}, enableds), - expected: false, - }, - { - name: "different enabled", - config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), - other: NewContractDeployerAllowListConfig(big.NewInt(3), admins, []common.Address{{3}}), - expected: false, - }, - { - name: "different timestamp", - config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), - other: NewContractDeployerAllowListConfig(big.NewInt(4), admins, enableds), - expected: false, - }, - { - name: "same config", - config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), - other: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), - expected: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - - require.Equal(tt.expected, tt.config.Equal(tt.other)) - }) - } -} - -func TestEqualContractNativeMinterConfig(t *testing.T) { - admins := []common.Address{{1}} - enableds := []common.Address{{2}} - tests := []struct { - name string - config StatefulPrecompileConfig - other StatefulPrecompileConfig - expected bool - }{ - { - name: "non-nil config and nil other", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, enableds, nil), - other: nil, - expected: false, - }, - { - name: "different type", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, enableds, nil), - other: NewTxAllowListConfig(big.NewInt(3), []common.Address{{1}}, []common.Address{{2}}), - expected: false, - }, - { - name: "different timestamps", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, nil), - other: NewContractNativeMinterConfig(big.NewInt(4), admins, nil, nil), - expected: false, - }, - { - name: "different enabled", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, nil), - other: NewContractNativeMinterConfig(big.NewInt(3), admins, enableds, nil), - expected: false, - }, - { - name: "different initial mint amounts", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, - map[common.Address]*math.HexOrDecimal256{ - common.HexToAddress("0x01"): math.NewHexOrDecimal256(1), - }), - other: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, - map[common.Address]*math.HexOrDecimal256{ - common.HexToAddress("0x01"): math.NewHexOrDecimal256(2), - }), - expected: false, - }, - { - name: "different initial mint addresses", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, - map[common.Address]*math.HexOrDecimal256{ - common.HexToAddress("0x01"): math.NewHexOrDecimal256(1), - }), - other: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, - map[common.Address]*math.HexOrDecimal256{ - common.HexToAddress("0x02"): math.NewHexOrDecimal256(1), - }), - expected: false, - }, - - { - name: "same config", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, - map[common.Address]*math.HexOrDecimal256{ - common.HexToAddress("0x01"): math.NewHexOrDecimal256(1), - }), - other: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, - map[common.Address]*math.HexOrDecimal256{ - common.HexToAddress("0x01"): math.NewHexOrDecimal256(1), - }), - expected: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - - require.Equal(tt.expected, tt.config.Equal(tt.other)) - }) - } -} - -func TestEqualFeeConfigManagerConfig(t *testing.T) { - admins := []common.Address{{1}} - enableds := []common.Address{{2}} - tests := []struct { - name string - config StatefulPrecompileConfig - other StatefulPrecompileConfig - expected bool - }{ - { - name: "non-nil config and nil other", - config: NewFeeManagerConfig(big.NewInt(3), admins, enableds, nil), - other: nil, - expected: false, - }, - { - name: "different type", - config: NewFeeManagerConfig(big.NewInt(3), admins, enableds, nil), - other: NewTxAllowListConfig(big.NewInt(3), []common.Address{{1}}, []common.Address{{2}}), - expected: false, - }, - { - name: "different timestamp", - config: NewFeeManagerConfig(big.NewInt(3), admins, nil, nil), - other: NewFeeManagerConfig(big.NewInt(4), admins, nil, nil), - expected: false, - }, - { - name: "different enabled", - config: NewFeeManagerConfig(big.NewInt(3), admins, nil, nil), - other: NewFeeManagerConfig(big.NewInt(3), admins, enableds, nil), - expected: false, - }, - { - name: "non-nil initial config and nil initial config", - config: NewFeeManagerConfig(big.NewInt(3), admins, nil, &validFeeConfig), - other: NewFeeManagerConfig(big.NewInt(3), admins, nil, nil), - expected: false, - }, - { - name: "different initial config", - config: NewFeeManagerConfig(big.NewInt(3), admins, nil, &validFeeConfig), - other: NewFeeManagerConfig(big.NewInt(3), admins, nil, - func() *commontype.FeeConfig { - c := validFeeConfig - c.GasLimit = big.NewInt(123) - return &c - }()), - expected: false, - }, - { - name: "same config", - config: NewFeeManagerConfig(big.NewInt(3), admins, nil, &validFeeConfig), - other: NewFeeManagerConfig(big.NewInt(3), admins, nil, &validFeeConfig), - expected: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - - require.Equal(tt.expected, tt.config.Equal(tt.other)) - }) - } -} - -func TestEqualRewardManagerConfig(t *testing.T) { - admins := []common.Address{{1}} - enableds := []common.Address{{2}} - tests := []struct { - name string - config StatefulPrecompileConfig - other StatefulPrecompileConfig - expected bool - }{ - { - name: "non-nil config and nil other", - config: NewRewardManagerConfig(big.NewInt(3), admins, enableds, nil), - other: nil, - expected: false, - }, - { - name: "different type", - config: NewRewardManagerConfig(big.NewInt(3), admins, enableds, nil), - other: NewTxAllowListConfig(big.NewInt(3), []common.Address{{1}}, []common.Address{{2}}), - expected: false, - }, - { - name: "different timestamp", - config: NewRewardManagerConfig(big.NewInt(3), admins, nil, nil), - other: NewRewardManagerConfig(big.NewInt(4), admins, nil, nil), - expected: false, - }, - { - name: "different enabled", - config: NewRewardManagerConfig(big.NewInt(3), admins, nil, nil), - other: NewRewardManagerConfig(big.NewInt(3), admins, enableds, nil), - expected: false, - }, - { - name: "non-nil initial config and nil initial config", - config: NewRewardManagerConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ - AllowFeeRecipients: true, - }), - other: NewRewardManagerConfig(big.NewInt(3), admins, nil, nil), - expected: false, - }, - { - name: "different initial config", - config: NewRewardManagerConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ - RewardAddress: common.HexToAddress("0x01"), - }), - other: NewRewardManagerConfig(big.NewInt(3), admins, nil, - &InitialRewardConfig{ - RewardAddress: common.HexToAddress("0x02"), - }), - expected: false, - }, - { - name: "same config", - config: NewRewardManagerConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ - RewardAddress: common.HexToAddress("0x01"), - }), - other: NewRewardManagerConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ - RewardAddress: common.HexToAddress("0x01"), - }), - expected: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - - require.Equal(tt.expected, tt.config.Equal(tt.other)) - }) - } -} diff --git a/precompile/contract.go b/precompile/contract.go index 69ed968641..596ea6dc00 100644 --- a/precompile/contract.go +++ b/precompile/contract.go @@ -5,76 +5,24 @@ package precompile import ( "fmt" - "math/big" - "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ethereum/go-ethereum/common" ) const ( - selectorLen = 4 + SelectorLen = 4 ) type RunStatefulPrecompileFunc func(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) -// PrecompileAccessibleState defines the interface exposed to stateful precompile contracts -type PrecompileAccessibleState interface { - GetStateDB() StateDB - GetBlockContext() BlockContext - GetSnowContext() *snow.Context - CallFromPrecompile(caller common.Address, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) -} - -// BlockContext defines an interface that provides information to a stateful precompile -// about the block that activates the upgrade. The precompile can access this information -// to initialize its state. -type BlockContext interface { - Number() *big.Int - Timestamp() *big.Int -} - -// ChainContext defines an interface that provides information to a stateful precompile -// about the chain configuration. The precompile can access this information to initialize -// its state. -type ChainConfig interface { - // GetFeeConfig returns the original FeeConfig that was set in the genesis. - GetFeeConfig() commontype.FeeConfig - // AllowedFeeRecipients returns true if fee recipients are allowed in the genesis. - AllowedFeeRecipients() bool -} - -// StateDB is the interface for accessing EVM state -type StateDB interface { - GetState(common.Address, common.Hash) common.Hash - SetState(common.Address, common.Hash, common.Hash) - - SetCode(common.Address, []byte) - - SetNonce(common.Address, uint64) - GetNonce(common.Address) uint64 - - GetBalance(common.Address) *big.Int - AddBalance(common.Address, *big.Int) - SubBalance(common.Address, *big.Int) - - CreateAccount(common.Address) - Exist(common.Address) bool - - AddLog(addr common.Address, topics []common.Hash, data []byte, blockNumber uint64) - - Suicide(common.Address) bool - Finalise(deleteEmptyObjects bool) -} - // StatefulPrecompiledContract is the interface for executing a precompiled contract type StatefulPrecompiledContract interface { // Run executes the precompiled contract. Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) } -// statefulPrecompileFunction defines a function implemented by a stateful precompile -type statefulPrecompileFunction struct { +// StatefulPrecompileFunction defines a function implemented by a stateful precompile +type StatefulPrecompileFunction struct { // selector is the 4 byte function selector for this function // This should be calculated from the function signature using CalculateFunctionSelector selector []byte @@ -82,9 +30,9 @@ type statefulPrecompileFunction struct { execute RunStatefulPrecompileFunc } -// newStatefulPrecompileFunction creates a stateful precompile function with the given arguments -func newStatefulPrecompileFunction(selector []byte, execute RunStatefulPrecompileFunc) *statefulPrecompileFunction { - return &statefulPrecompileFunction{ +// NewStatefulPrecompileFunction creates a stateful precompile function with the given arguments +func NewStatefulPrecompileFunction(selector []byte, execute RunStatefulPrecompileFunc) *StatefulPrecompileFunction { + return &StatefulPrecompileFunction{ selector: selector, execute: execute, } @@ -95,16 +43,16 @@ func newStatefulPrecompileFunction(selector []byte, execute RunStatefulPrecompil // Note: because we only ever read from [functions] there no lock is required to make it thread-safe. type statefulPrecompileWithFunctionSelectors struct { fallback RunStatefulPrecompileFunc - functions map[string]*statefulPrecompileFunction + functions map[string]*StatefulPrecompileFunction } // NewStatefulPrecompileContract generates new StatefulPrecompile using [functions] as the available functions and [fallback] // as an optional fallback if there is no input data. Note: the selector of [fallback] will be ignored, so it is required to be left empty. -func NewStatefulPrecompileContract(fallback RunStatefulPrecompileFunc, functions []*statefulPrecompileFunction) (StatefulPrecompiledContract, error) { +func NewStatefulPrecompileContract(fallback RunStatefulPrecompileFunc, functions []*StatefulPrecompileFunction) (StatefulPrecompiledContract, error) { // Construct the contract and populate [functions]. contract := &statefulPrecompileWithFunctionSelectors{ fallback: fallback, - functions: make(map[string]*statefulPrecompileFunction), + functions: make(map[string]*StatefulPrecompileFunction), } for _, function := range functions { _, exists := contract.functions[string(function.selector)] @@ -126,13 +74,13 @@ func (s *statefulPrecompileWithFunctionSelectors) Run(accessibleState Precompile } // Otherwise, an unexpected input size will result in an error. - if len(input) < selectorLen { + if len(input) < SelectorLen { return nil, suppliedGas, fmt.Errorf("missing function selector to precompile - input length (%d)", len(input)) } // Use the function selector to grab the correct function - selector := input[:selectorLen] - functionInput := input[selectorLen:] + selector := input[:SelectorLen] + functionInput := input[SelectorLen:] function, ok := s.functions[string(selector)] if !ok { return nil, suppliedGas, fmt.Errorf("invalid function selector %#x", selector) diff --git a/precompile/contract_native_minter.go b/precompile/contract_native_minter.go deleted file mode 100644 index 26ccaf8e68..0000000000 --- a/precompile/contract_native_minter.go +++ /dev/null @@ -1,227 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package precompile - -import ( - "encoding/json" - "errors" - "fmt" - "math/big" - - "github.com/ava-labs/subnet-evm/utils" - "github.com/ava-labs/subnet-evm/vmerrs" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" -) - -const ( - mintInputAddressSlot = iota - mintInputAmountSlot - - mintInputLen = common.HashLength + common.HashLength - - MintGasCost = 30_000 -) - -var ( - _ StatefulPrecompileConfig = &ContractNativeMinterConfig{} - // Singleton StatefulPrecompiledContract for minting native assets by permissioned callers. - ContractNativeMinterPrecompile StatefulPrecompiledContract = createNativeMinterPrecompile(ContractNativeMinterAddress) - - mintSignature = CalculateFunctionSelector("mintNativeCoin(address,uint256)") // address, amount - ErrCannotMint = errors.New("non-enabled cannot mint") -) - -// ContractNativeMinterConfig wraps [AllowListConfig] and uses it to implement the StatefulPrecompileConfig -// interface while adding in the ContractNativeMinter specific precompile address. -type ContractNativeMinterConfig struct { - AllowListConfig - UpgradeableConfig - InitialMint map[common.Address]*math.HexOrDecimal256 `json:"initialMint,omitempty"` // initial mint config to be immediately minted -} - -// NewContractNativeMinterConfig returns a config for a network upgrade at [blockTimestamp] that enables -// ContractNativeMinter with the given [admins] and [enableds] as members of the allowlist. Also mints balances according to [initialMint] when the upgrade activates. -func NewContractNativeMinterConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialMint map[common.Address]*math.HexOrDecimal256) *ContractNativeMinterConfig { - return &ContractNativeMinterConfig{ - AllowListConfig: AllowListConfig{ - AllowListAdmins: admins, - EnabledAddresses: enableds, - }, - UpgradeableConfig: UpgradeableConfig{BlockTimestamp: blockTimestamp}, - InitialMint: initialMint, - } -} - -// NewDisableContractNativeMinterConfig returns config for a network upgrade at [blockTimestamp] -// that disables ContractNativeMinter. -func NewDisableContractNativeMinterConfig(blockTimestamp *big.Int) *ContractNativeMinterConfig { - return &ContractNativeMinterConfig{ - UpgradeableConfig: UpgradeableConfig{ - BlockTimestamp: blockTimestamp, - Disable: true, - }, - } -} - -// Address returns the address of the native minter contract. -func (c *ContractNativeMinterConfig) Address() common.Address { - return ContractNativeMinterAddress -} - -// Configure configures [state] with the desired admins based on [c]. -func (c *ContractNativeMinterConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) error { - for to, amount := range c.InitialMint { - if amount != nil { - bigIntAmount := (*big.Int)(amount) - state.AddBalance(to, bigIntAmount) - } - } - - return c.AllowListConfig.Configure(state, ContractNativeMinterAddress) -} - -// Contract returns the singleton stateful precompiled contract to be used for the native minter. -func (c *ContractNativeMinterConfig) Contract() StatefulPrecompiledContract { - return ContractNativeMinterPrecompile -} - -func (c *ContractNativeMinterConfig) Verify() error { - if err := c.AllowListConfig.Verify(); err != nil { - return err - } - // ensure that all of the initial mint values in the map are non-nil positive values - for addr, amount := range c.InitialMint { - if amount == nil { - return fmt.Errorf("initial mint cannot contain nil amount for address %s", addr) - } - bigIntAmount := (*big.Int)(amount) - if bigIntAmount.Sign() < 1 { - return fmt.Errorf("initial mint cannot contain invalid amount %v for address %s", bigIntAmount, addr) - } - } - return nil -} - -// Equal returns true if [s] is a [*ContractNativeMinterConfig] and it has been configured identical to [c]. -func (c *ContractNativeMinterConfig) Equal(s StatefulPrecompileConfig) bool { - // typecast before comparison - other, ok := (s).(*ContractNativeMinterConfig) - if !ok { - return false - } - eq := c.UpgradeableConfig.Equal(&other.UpgradeableConfig) && c.AllowListConfig.Equal(&other.AllowListConfig) - if !eq { - return false - } - - if len(c.InitialMint) != len(other.InitialMint) { - return false - } - - for address, amount := range c.InitialMint { - val, ok := other.InitialMint[address] - if !ok { - return false - } - bigIntAmount := (*big.Int)(amount) - bigIntVal := (*big.Int)(val) - if !utils.BigNumEqual(bigIntAmount, bigIntVal) { - return false - } - } - - return true -} - -// String returns a string representation of the ContractNativeMinterConfig. -func (c *ContractNativeMinterConfig) String() string { - bytes, _ := json.Marshal(c) - return string(bytes) -} - -// GetContractNativeMinterStatus returns the role of [address] for the minter list. -func GetContractNativeMinterStatus(stateDB StateDB, address common.Address) AllowListRole { - return getAllowListStatus(stateDB, ContractNativeMinterAddress, address) -} - -// SetContractNativeMinterStatus sets the permissions of [address] to [role] for the -// minter list. assumes [role] has already been verified as valid. -func SetContractNativeMinterStatus(stateDB StateDB, address common.Address, role AllowListRole) { - setAllowListRole(stateDB, ContractNativeMinterAddress, address, role) -} - -// PackMintInput packs [address] and [amount] into the appropriate arguments for minting operation. -// Assumes that [amount] can be represented by 32 bytes. -func PackMintInput(address common.Address, amount *big.Int) ([]byte, error) { - // function selector (4 bytes) + input(hash for address + hash for amount) - res := make([]byte, selectorLen+mintInputLen) - err := packOrderedHashesWithSelector(res, mintSignature, []common.Hash{ - address.Hash(), - common.BigToHash(amount), - }) - - return res, err -} - -// UnpackMintInput attempts to unpack [input] into the arguments to the mint precompile -// assumes that [input] does not include selector (omits first 4 bytes in PackMintInput) -func UnpackMintInput(input []byte) (common.Address, *big.Int, error) { - if len(input) != mintInputLen { - return common.Address{}, nil, fmt.Errorf("invalid input length for minting: %d", len(input)) - } - to := common.BytesToAddress(returnPackedHash(input, mintInputAddressSlot)) - assetAmount := new(big.Int).SetBytes(returnPackedHash(input, mintInputAmountSlot)) - return to, assetAmount, nil -} - -// mintNativeCoin checks if the caller is permissioned for minting operation. -// The execution function parses the [input] into native coin amount and receiver address. -func mintNativeCoin(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = deductGas(suppliedGas, MintGasCost); err != nil { - return nil, 0, err - } - - if readOnly { - return nil, remainingGas, vmerrs.ErrWriteProtection - } - - to, amount, err := UnpackMintInput(input) - if err != nil { - return nil, remainingGas, err - } - - stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := getAllowListStatus(stateDB, ContractNativeMinterAddress, caller) - if !callerStatus.IsEnabled() { - return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotMint, caller) - } - - // if there is no address in the state, create one. - if !stateDB.Exist(to) { - stateDB.CreateAccount(to) - } - - stateDB.AddBalance(to, amount) - // Return an empty output and the remaining gas - return []byte{}, remainingGas, nil -} - -// createNativeMinterPrecompile returns a StatefulPrecompiledContract with R/W control of an allow list at [precompileAddr] and a native coin minter. -func createNativeMinterPrecompile(precompileAddr common.Address) StatefulPrecompiledContract { - enabledFuncs := createAllowListFunctions(precompileAddr) - - mintFunc := newStatefulPrecompileFunction(mintSignature, mintNativeCoin) - - enabledFuncs = append(enabledFuncs, mintFunc) - // Construct the contract with no fallback function. - contract, err := NewStatefulPrecompileContract(nil, enabledFuncs) - // Change this to be returned as an error after refactoring this precompile - // to use the new precompile template. - if err != nil { - panic(err) - } - return contract -} diff --git a/precompile/contract_deployer_allow_list.go b/precompile/deployerallowlist/config.go similarity index 58% rename from precompile/contract_deployer_allow_list.go rename to precompile/deployerallowlist/config.go index 11df6ff3fe..07b8e27dc2 100644 --- a/precompile/contract_deployer_allow_list.go +++ b/precompile/deployerallowlist/config.go @@ -1,37 +1,35 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package precompile +package deployerallowlist import ( "encoding/json" "math/big" + "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ethereum/go-ethereum/common" ) -var ( - _ StatefulPrecompileConfig = &ContractDeployerAllowListConfig{} - // Singleton StatefulPrecompiledContract for W/R access to the contract deployer allow list. - ContractDeployerAllowListPrecompile StatefulPrecompiledContract = createAllowListPrecompile(ContractDeployerAllowListAddress) -) +var _ precompile.StatefulPrecompileConfig = &ContractDeployerAllowListConfig{} // ContractDeployerAllowListConfig wraps [AllowListConfig] and uses it to implement the StatefulPrecompileConfig // interface while adding in the contract deployer specific precompile address. type ContractDeployerAllowListConfig struct { - AllowListConfig - UpgradeableConfig + allowlist.AllowListConfig + precompile.UpgradeableConfig } // NewContractDeployerAllowListConfig returns a config for a network upgrade at [blockTimestamp] that enables // ContractDeployerAllowList with [admins] and [enableds] as members of the allowlist. func NewContractDeployerAllowListConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address) *ContractDeployerAllowListConfig { return &ContractDeployerAllowListConfig{ - AllowListConfig: AllowListConfig{ + AllowListConfig: allowlist.AllowListConfig{ AllowListAdmins: admins, EnabledAddresses: enableds, }, - UpgradeableConfig: UpgradeableConfig{BlockTimestamp: blockTimestamp}, + UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: blockTimestamp}, } } @@ -39,7 +37,7 @@ func NewContractDeployerAllowListConfig(blockTimestamp *big.Int, admins []common // that disables ContractDeployerAllowList. func NewDisableContractDeployerAllowListConfig(blockTimestamp *big.Int) *ContractDeployerAllowListConfig { return &ContractDeployerAllowListConfig{ - UpgradeableConfig: UpgradeableConfig{ + UpgradeableConfig: precompile.UpgradeableConfig{ BlockTimestamp: blockTimestamp, Disable: true, }, @@ -48,21 +46,21 @@ func NewDisableContractDeployerAllowListConfig(blockTimestamp *big.Int) *Contrac // Address returns the address of the contract deployer allow list. func (c *ContractDeployerAllowListConfig) Address() common.Address { - return ContractDeployerAllowListAddress + return precompile.ContractDeployerAllowListAddress } // Configure configures [state] with the desired admins based on [c]. -func (c *ContractDeployerAllowListConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) error { - return c.AllowListConfig.Configure(state, ContractDeployerAllowListAddress) +func (c *ContractDeployerAllowListConfig) Configure(_ precompile.ChainConfig, state precompile.StateDB, _ precompile.BlockContext) error { + return c.AllowListConfig.Configure(state, precompile.ContractDeployerAllowListAddress) } // Contract returns the singleton stateful precompiled contract to be used for the allow list. -func (c *ContractDeployerAllowListConfig) Contract() StatefulPrecompiledContract { +func (c *ContractDeployerAllowListConfig) Contract() precompile.StatefulPrecompiledContract { return ContractDeployerAllowListPrecompile } // Equal returns true if [s] is a [*ContractDeployerAllowListConfig] and it has been configured identical to [c]. -func (c *ContractDeployerAllowListConfig) Equal(s StatefulPrecompileConfig) bool { +func (c *ContractDeployerAllowListConfig) Equal(s precompile.StatefulPrecompileConfig) bool { // typecast before comparison other, ok := (s).(*ContractDeployerAllowListConfig) if !ok { @@ -76,16 +74,3 @@ func (c *ContractDeployerAllowListConfig) String() string { bytes, _ := json.Marshal(c) return string(bytes) } - -// GetContractDeployerAllowListStatus returns the role of [address] for the contract deployer -// allow list. -func GetContractDeployerAllowListStatus(stateDB StateDB, address common.Address) AllowListRole { - return getAllowListStatus(stateDB, ContractDeployerAllowListAddress, address) -} - -// SetContractDeployerAllowListStatus sets the permissions of [address] to [role] for the -// contract deployer allow list. -// assumes [role] has already been verified as valid. -func SetContractDeployerAllowListStatus(stateDB StateDB, address common.Address, role AllowListRole) { - setAllowListRole(stateDB, ContractDeployerAllowListAddress, address, role) -} diff --git a/precompile/deployerallowlist/config_test.go b/precompile/deployerallowlist/config_test.go new file mode 100644 index 0000000000..2ffb0be77c --- /dev/null +++ b/precompile/deployerallowlist/config_test.go @@ -0,0 +1,95 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package deployerallowlist + +import ( + "math/big" + "testing" + + "github.com/ava-labs/subnet-evm/precompile" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestVerifyContractDeployerConfig(t *testing.T) { + admins := []common.Address{{1}} + tests := []struct { + name string + config precompile.StatefulPrecompileConfig + expectedError string + }{ + { + name: "invalid allow list config in deployer allowlist", + config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, admins), + expectedError: "cannot set address", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + err := tt.config.Verify() + if tt.expectedError == "" { + require.NoError(err) + } else { + require.ErrorContains(err, tt.expectedError) + } + }) + } +} + +func TestEqualContractDeployerAllowListConfig(t *testing.T) { + admins := []common.Address{{1}} + enableds := []common.Address{{2}} + tests := []struct { + name string + config precompile.StatefulPrecompileConfig + other precompile.StatefulPrecompileConfig + expected bool + }{ + { + name: "non-nil config and nil other", + config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), + other: nil, + expected: false, + }, + { + name: "different type", + config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), + other: precompile.NewNoopStatefulPrecompileConfig(), + expected: false, + }, + { + name: "different admin", + config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), + other: NewContractDeployerAllowListConfig(big.NewInt(3), []common.Address{{3}}, enableds), + expected: false, + }, + { + name: "different enabled", + config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), + other: NewContractDeployerAllowListConfig(big.NewInt(3), admins, []common.Address{{3}}), + expected: false, + }, + { + name: "different timestamp", + config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), + other: NewContractDeployerAllowListConfig(big.NewInt(4), admins, enableds), + expected: false, + }, + { + name: "same config", + config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), + other: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), + expected: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + require.Equal(tt.expected, tt.config.Equal(tt.other)) + }) + } +} diff --git a/precompile/deployerallowlist/contract.go b/precompile/deployerallowlist/contract.go new file mode 100644 index 0000000000..4fb4ace7b9 --- /dev/null +++ b/precompile/deployerallowlist/contract.go @@ -0,0 +1,26 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package deployerallowlist + +import ( + "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ethereum/go-ethereum/common" +) + +// Singleton StatefulPrecompiledContract for W/R access to the contract deployer allow list. +var ContractDeployerAllowListPrecompile precompile.StatefulPrecompiledContract = allowlist.CreateAllowListPrecompile(precompile.ContractDeployerAllowListAddress) + +// GetContractDeployerAllowListStatus returns the role of [address] for the contract deployer +// allow list. +func GetContractDeployerAllowListStatus(stateDB precompile.StateDB, address common.Address) allowlist.AllowListRole { + return allowlist.GetAllowListStatus(stateDB, precompile.ContractDeployerAllowListAddress, address) +} + +// SetContractDeployerAllowListStatus sets the permissions of [address] to [role] for the +// contract deployer allow list. +// assumes [role] has already been verified as valid. +func SetContractDeployerAllowListStatus(stateDB precompile.StateDB, address common.Address, role allowlist.AllowListRole) { + allowlist.SetAllowListRole(stateDB, precompile.ContractDeployerAllowListAddress, address, role) +} diff --git a/precompile/feemanager/config.go b/precompile/feemanager/config.go new file mode 100644 index 0000000000..bee3acf785 --- /dev/null +++ b/precompile/feemanager/config.go @@ -0,0 +1,110 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package feemanager + +import ( + "encoding/json" + "fmt" + "math/big" + + "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ethereum/go-ethereum/common" +) + +// FeeConfigManagerConfig wraps [AllowListConfig] and uses it to implement the StatefulPrecompileConfig +// interface while adding in the FeeConfigManager specific precompile address. +type FeeConfigManagerConfig struct { + allowlist.AllowListConfig // Config for the fee config manager allow list + precompile.UpgradeableConfig + InitialFeeConfig *commontype.FeeConfig `json:"initialFeeConfig,omitempty"` // initial fee config to be immediately activated +} + +// NewFeeManagerConfig returns a config for a network upgrade at [blockTimestamp] that enables +// FeeConfigManager with the given [admins] and [enableds] as members of the allowlist with [initialConfig] as initial fee config if specified. +func NewFeeManagerConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialConfig *commontype.FeeConfig) *FeeConfigManagerConfig { + return &FeeConfigManagerConfig{ + AllowListConfig: allowlist.AllowListConfig{ + AllowListAdmins: admins, + EnabledAddresses: enableds, + }, + UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: blockTimestamp}, + InitialFeeConfig: initialConfig, + } +} + +// NewDisableFeeManagerConfig returns config for a network upgrade at [blockTimestamp] +// that disables FeeConfigManager. +func NewDisableFeeManagerConfig(blockTimestamp *big.Int) *FeeConfigManagerConfig { + return &FeeConfigManagerConfig{ + UpgradeableConfig: precompile.UpgradeableConfig{ + BlockTimestamp: blockTimestamp, + Disable: true, + }, + } +} + +// Address returns the address of the fee config manager contract. +func (c *FeeConfigManagerConfig) Address() common.Address { + return precompile.FeeConfigManagerAddress +} + +// Equal returns true if [s] is a [*FeeConfigManagerConfig] and it has been configured identical to [c]. +func (c *FeeConfigManagerConfig) Equal(s precompile.StatefulPrecompileConfig) bool { + // typecast before comparison + other, ok := (s).(*FeeConfigManagerConfig) + if !ok { + return false + } + eq := c.UpgradeableConfig.Equal(&other.UpgradeableConfig) && c.AllowListConfig.Equal(&other.AllowListConfig) + if !eq { + return false + } + + if c.InitialFeeConfig == nil { + return other.InitialFeeConfig == nil + } + + return c.InitialFeeConfig.Equal(other.InitialFeeConfig) +} + +// Configure configures [state] with the desired admins based on [c]. +func (c *FeeConfigManagerConfig) Configure(chainConfig precompile.ChainConfig, state precompile.StateDB, blockContext precompile.BlockContext) error { + // Store the initial fee config into the state when the fee config manager activates. + if c.InitialFeeConfig != nil { + if err := StoreFeeConfig(state, *c.InitialFeeConfig, blockContext); err != nil { + // This should not happen since we already checked this config with Verify() + return fmt.Errorf("cannot configure given initial fee config: %w", err) + } + } else { + if err := StoreFeeConfig(state, chainConfig.GetFeeConfig(), blockContext); err != nil { + // This should not happen since we already checked the chain config in the genesis creation. + return fmt.Errorf("cannot configure fee config in chain config: %w", err) + } + } + return c.AllowListConfig.Configure(state, precompile.FeeConfigManagerAddress) +} + +// Contract returns the singleton stateful precompiled contract to be used for the fee manager. +func (c *FeeConfigManagerConfig) Contract() precompile.StatefulPrecompiledContract { + return FeeConfigManagerPrecompile +} + +func (c *FeeConfigManagerConfig) Verify() error { + if err := c.AllowListConfig.Verify(); err != nil { + return err + } + if c.InitialFeeConfig == nil { + return nil + } + + return c.InitialFeeConfig.Verify() +} + +// String returns a string representation of the FeeConfigManagerConfig. +func (c *FeeConfigManagerConfig) String() string { + bytes, _ := json.Marshal(c) + return string(bytes) +} diff --git a/precompile/feemanager/config_test.go b/precompile/feemanager/config_test.go new file mode 100644 index 0000000000..ed45821d6a --- /dev/null +++ b/precompile/feemanager/config_test.go @@ -0,0 +1,128 @@ +// (c) 2022 Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package feemanager + +import ( + "math/big" + "testing" + + "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/precompile" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +var validFeeConfig = commontype.FeeConfig{ + GasLimit: big.NewInt(8_000_000), + TargetBlockRate: 2, // in seconds + + MinBaseFee: big.NewInt(25_000_000_000), + TargetGas: big.NewInt(15_000_000), + BaseFeeChangeDenominator: big.NewInt(36), + + MinBlockGasCost: big.NewInt(0), + MaxBlockGasCost: big.NewInt(1_000_000), + BlockGasCostStep: big.NewInt(200_000), +} + +func TestVerifyFeeManagerConfig(t *testing.T) { + admins := []common.Address{{1}} + tests := []struct { + name string + config precompile.StatefulPrecompileConfig + expectedError string + }{ + { + name: "invalid allow list config in fee manager allowlist", + config: NewFeeManagerConfig(big.NewInt(3), admins, admins, nil), + expectedError: "cannot set address", + }, + { + name: "invalid initial fee manager config", + config: NewFeeManagerConfig(big.NewInt(3), admins, nil, + &commontype.FeeConfig{ + GasLimit: big.NewInt(0), + }), + expectedError: "gasLimit = 0 cannot be less than or equal to 0", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + err := tt.config.Verify() + if tt.expectedError == "" { + require.NoError(err) + } else { + require.ErrorContains(err, tt.expectedError) + } + }) + } +} + +func TestEqualFeeConfigManagerConfig(t *testing.T) { + admins := []common.Address{{1}} + enableds := []common.Address{{2}} + tests := []struct { + name string + config precompile.StatefulPrecompileConfig + other precompile.StatefulPrecompileConfig + expected bool + }{ + { + name: "non-nil config and nil other", + config: NewFeeManagerConfig(big.NewInt(3), admins, enableds, nil), + other: nil, + expected: false, + }, + { + name: "different type", + config: NewFeeManagerConfig(big.NewInt(3), admins, enableds, nil), + other: precompile.NewNoopStatefulPrecompileConfig(), + expected: false, + }, + { + name: "different timestamp", + config: NewFeeManagerConfig(big.NewInt(3), admins, nil, nil), + other: NewFeeManagerConfig(big.NewInt(4), admins, nil, nil), + expected: false, + }, + { + name: "different enabled", + config: NewFeeManagerConfig(big.NewInt(3), admins, nil, nil), + other: NewFeeManagerConfig(big.NewInt(3), admins, enableds, nil), + expected: false, + }, + { + name: "non-nil initial config and nil initial config", + config: NewFeeManagerConfig(big.NewInt(3), admins, nil, &validFeeConfig), + other: NewFeeManagerConfig(big.NewInt(3), admins, nil, nil), + expected: false, + }, + { + name: "different initial config", + config: NewFeeManagerConfig(big.NewInt(3), admins, nil, &validFeeConfig), + other: NewFeeManagerConfig(big.NewInt(3), admins, nil, + func() *commontype.FeeConfig { + c := validFeeConfig + c.GasLimit = big.NewInt(123) + return &c + }()), + expected: false, + }, + { + name: "same config", + config: NewFeeManagerConfig(big.NewInt(3), admins, nil, &validFeeConfig), + other: NewFeeManagerConfig(big.NewInt(3), admins, nil, &validFeeConfig), + expected: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + require.Equal(tt.expected, tt.config.Equal(tt.other)) + }) + } +} diff --git a/precompile/fee_config_manager.go b/precompile/feemanager/contract.go similarity index 56% rename from precompile/fee_config_manager.go rename to precompile/feemanager/contract.go index 56567d840a..760303ecde 100644 --- a/precompile/fee_config_manager.go +++ b/precompile/feemanager/contract.go @@ -1,15 +1,16 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package precompile +package feemanager import ( - "encoding/json" "errors" "fmt" "math/big" "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" ) @@ -32,130 +33,35 @@ const ( // [numFeeConfigField] fields in FeeConfig struct feeConfigInputLen = common.HashLength * numFeeConfigField - SetFeeConfigGasCost = writeGasCostPerSlot * (numFeeConfigField + 1) // plus one for setting last changed at - GetFeeConfigGasCost = readGasCostPerSlot * numFeeConfigField - GetLastChangedAtGasCost = readGasCostPerSlot + SetFeeConfigGasCost = precompile.WriteGasCostPerSlot * (numFeeConfigField + 1) // plus one for setting last changed at + GetFeeConfigGasCost = precompile.ReadGasCostPerSlot * numFeeConfigField + GetLastChangedAtGasCost = precompile.ReadGasCostPerSlot ) var ( - _ StatefulPrecompileConfig = &FeeConfigManagerConfig{} + _ precompile.StatefulPrecompileConfig = &FeeConfigManagerConfig{} // Singleton StatefulPrecompiledContract for setting fee configs by permissioned callers. - FeeConfigManagerPrecompile StatefulPrecompiledContract = createFeeConfigManagerPrecompile(FeeConfigManagerAddress) + FeeConfigManagerPrecompile precompile.StatefulPrecompiledContract = createFeeConfigManagerPrecompile(precompile.FeeConfigManagerAddress) - setFeeConfigSignature = CalculateFunctionSelector("setFeeConfig(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)") - getFeeConfigSignature = CalculateFunctionSelector("getFeeConfig()") - getFeeConfigLastChangedAtSignature = CalculateFunctionSelector("getFeeConfigLastChangedAt()") + setFeeConfigSignature = precompile.CalculateFunctionSelector("setFeeConfig(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)") + getFeeConfigSignature = precompile.CalculateFunctionSelector("getFeeConfig()") + getFeeConfigLastChangedAtSignature = precompile.CalculateFunctionSelector("getFeeConfigLastChangedAt()") feeConfigLastChangedAtKey = common.Hash{'l', 'c', 'a'} ErrCannotChangeFee = errors.New("non-enabled cannot change fee config") ) -// FeeConfigManagerConfig wraps [AllowListConfig] and uses it to implement the StatefulPrecompileConfig -// interface while adding in the FeeConfigManager specific precompile address. -type FeeConfigManagerConfig struct { - AllowListConfig // Config for the fee config manager allow list - UpgradeableConfig - InitialFeeConfig *commontype.FeeConfig `json:"initialFeeConfig,omitempty"` // initial fee config to be immediately activated -} - -// NewFeeManagerConfig returns a config for a network upgrade at [blockTimestamp] that enables -// FeeConfigManager with the given [admins] and [enableds] as members of the allowlist with [initialConfig] as initial fee config if specified. -func NewFeeManagerConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialConfig *commontype.FeeConfig) *FeeConfigManagerConfig { - return &FeeConfigManagerConfig{ - AllowListConfig: AllowListConfig{ - AllowListAdmins: admins, - EnabledAddresses: enableds, - }, - UpgradeableConfig: UpgradeableConfig{BlockTimestamp: blockTimestamp}, - InitialFeeConfig: initialConfig, - } -} - -// NewDisableFeeManagerConfig returns config for a network upgrade at [blockTimestamp] -// that disables FeeConfigManager. -func NewDisableFeeManagerConfig(blockTimestamp *big.Int) *FeeConfigManagerConfig { - return &FeeConfigManagerConfig{ - UpgradeableConfig: UpgradeableConfig{ - BlockTimestamp: blockTimestamp, - Disable: true, - }, - } -} - -// Address returns the address of the fee config manager contract. -func (c *FeeConfigManagerConfig) Address() common.Address { - return FeeConfigManagerAddress -} - -// Equal returns true if [s] is a [*FeeConfigManagerConfig] and it has been configured identical to [c]. -func (c *FeeConfigManagerConfig) Equal(s StatefulPrecompileConfig) bool { - // typecast before comparison - other, ok := (s).(*FeeConfigManagerConfig) - if !ok { - return false - } - eq := c.UpgradeableConfig.Equal(&other.UpgradeableConfig) && c.AllowListConfig.Equal(&other.AllowListConfig) - if !eq { - return false - } - - if c.InitialFeeConfig == nil { - return other.InitialFeeConfig == nil - } - - return c.InitialFeeConfig.Equal(other.InitialFeeConfig) -} - -// Configure configures [state] with the desired admins based on [c]. -func (c *FeeConfigManagerConfig) Configure(chainConfig ChainConfig, state StateDB, blockContext BlockContext) error { - // Store the initial fee config into the state when the fee config manager activates. - if c.InitialFeeConfig != nil { - if err := StoreFeeConfig(state, *c.InitialFeeConfig, blockContext); err != nil { - // This should not happen since we already checked this config with Verify() - return fmt.Errorf("cannot configure given initial fee config: %w", err) - } - } else { - if err := StoreFeeConfig(state, chainConfig.GetFeeConfig(), blockContext); err != nil { - // This should not happen since we already checked the chain config in the genesis creation. - return fmt.Errorf("cannot configure fee config in chain config: %w", err) - } - } - return c.AllowListConfig.Configure(state, FeeConfigManagerAddress) -} - -// Contract returns the singleton stateful precompiled contract to be used for the fee manager. -func (c *FeeConfigManagerConfig) Contract() StatefulPrecompiledContract { - return FeeConfigManagerPrecompile -} - -func (c *FeeConfigManagerConfig) Verify() error { - if err := c.AllowListConfig.Verify(); err != nil { - return err - } - if c.InitialFeeConfig == nil { - return nil - } - - return c.InitialFeeConfig.Verify() -} - -// String returns a string representation of the FeeConfigManagerConfig. -func (c *FeeConfigManagerConfig) String() string { - bytes, _ := json.Marshal(c) - return string(bytes) -} - // GetFeeConfigManagerStatus returns the role of [address] for the fee config manager list. -func GetFeeConfigManagerStatus(stateDB StateDB, address common.Address) AllowListRole { - return getAllowListStatus(stateDB, FeeConfigManagerAddress, address) +func GetFeeConfigManagerStatus(stateDB precompile.StateDB, address common.Address) allowlist.AllowListRole { + return allowlist.GetAllowListStatus(stateDB, precompile.FeeConfigManagerAddress, address) } // SetFeeConfigManagerStatus sets the permissions of [address] to [role] for the // fee config manager list. assumes [role] has already been verified as valid. -func SetFeeConfigManagerStatus(stateDB StateDB, address common.Address, role AllowListRole) { - setAllowListRole(stateDB, FeeConfigManagerAddress, address, role) +func SetFeeConfigManagerStatus(stateDB precompile.StateDB, address common.Address, role allowlist.AllowListRole) { + allowlist.SetAllowListRole(stateDB, precompile.FeeConfigManagerAddress, address, role) } // PackGetFeeConfigInput packs the getFeeConfig signature @@ -194,12 +100,12 @@ func packFeeConfigHelper(feeConfig commontype.FeeConfig, useSelector bool) ([]by if useSelector { res := make([]byte, len(setFeeConfigSignature)+feeConfigInputLen) - err := packOrderedHashesWithSelector(res, setFeeConfigSignature, hashes) + err := precompile.PackOrderedHashesWithSelector(res, setFeeConfigSignature, hashes) return res, err } res := make([]byte, len(hashes)*common.HashLength) - err := packOrderedHashes(res, hashes) + err := precompile.PackOrderedHashes(res, hashes) return res, err } @@ -212,7 +118,7 @@ func UnpackFeeConfigInput(input []byte) (commontype.FeeConfig, error) { feeConfig := commontype.FeeConfig{} for i := minFeeConfigFieldKey; i <= numFeeConfigField; i++ { listIndex := i - 1 - packedElement := returnPackedHash(input, listIndex) + packedElement := precompile.PackedHash(input, listIndex) switch i { case gasLimitKey: feeConfig.GasLimit = new(big.Int).SetBytes(packedElement) @@ -239,10 +145,10 @@ func UnpackFeeConfigInput(input []byte) (commontype.FeeConfig, error) { } // GetStoredFeeConfig returns fee config from contract storage in given state -func GetStoredFeeConfig(stateDB StateDB) commontype.FeeConfig { +func GetStoredFeeConfig(stateDB precompile.StateDB) commontype.FeeConfig { feeConfig := commontype.FeeConfig{} for i := minFeeConfigFieldKey; i <= numFeeConfigField; i++ { - val := stateDB.GetState(FeeConfigManagerAddress, common.Hash{byte(i)}) + val := stateDB.GetState(precompile.FeeConfigManagerAddress, common.Hash{byte(i)}) switch i { case gasLimitKey: feeConfig.GasLimit = new(big.Int).Set(val.Big()) @@ -268,14 +174,14 @@ func GetStoredFeeConfig(stateDB StateDB) commontype.FeeConfig { return feeConfig } -func GetFeeConfigLastChangedAt(stateDB StateDB) *big.Int { - val := stateDB.GetState(FeeConfigManagerAddress, feeConfigLastChangedAtKey) +func GetFeeConfigLastChangedAt(stateDB precompile.StateDB) *big.Int { + val := stateDB.GetState(precompile.FeeConfigManagerAddress, feeConfigLastChangedAtKey) return val.Big() } // StoreFeeConfig stores given [feeConfig] and block number in the [blockContext] to the [stateDB]. // A validation on [feeConfig] is done before storing. -func StoreFeeConfig(stateDB StateDB, feeConfig commontype.FeeConfig, blockContext BlockContext) error { +func StoreFeeConfig(stateDB precompile.StateDB, feeConfig commontype.FeeConfig, blockContext precompile.BlockContext) error { if err := feeConfig.Verify(); err != nil { return fmt.Errorf("cannot verify fee config: %w", err) } @@ -303,22 +209,22 @@ func StoreFeeConfig(stateDB StateDB, feeConfig commontype.FeeConfig, blockContex // This should never encounter an unknown fee config key panic(fmt.Sprintf("unknown fee config key: %d", i)) } - stateDB.SetState(FeeConfigManagerAddress, common.Hash{byte(i)}, input) + stateDB.SetState(precompile.FeeConfigManagerAddress, common.Hash{byte(i)}, input) } blockNumber := blockContext.Number() if blockNumber == nil { return fmt.Errorf("blockNumber cannot be nil") } - stateDB.SetState(FeeConfigManagerAddress, feeConfigLastChangedAtKey, common.BigToHash(blockNumber)) + stateDB.SetState(precompile.FeeConfigManagerAddress, feeConfigLastChangedAtKey, common.BigToHash(blockNumber)) return nil } // setFeeConfig checks if the caller has permissions to set the fee config. // The execution function parses [input] into FeeConfig structure and sets contract storage accordingly. -func setFeeConfig(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = deductGas(suppliedGas, SetFeeConfigGasCost); err != nil { +func setFeeConfig(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = precompile.DeductGas(suppliedGas, SetFeeConfigGasCost); err != nil { return nil, 0, err } @@ -333,7 +239,7 @@ func setFeeConfig(accessibleState PrecompileAccessibleState, caller common.Addre stateDB := accessibleState.GetStateDB() // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := getAllowListStatus(stateDB, FeeConfigManagerAddress, caller) + callerStatus := allowlist.GetAllowListStatus(stateDB, precompile.FeeConfigManagerAddress, caller) if !callerStatus.IsEnabled() { return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotChangeFee, caller) } @@ -348,8 +254,8 @@ func setFeeConfig(accessibleState PrecompileAccessibleState, caller common.Addre // getFeeConfig returns the stored fee config as an output. // The execution function reads the contract state for the stored fee config and returns the output. -func getFeeConfig(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = deductGas(suppliedGas, GetFeeConfigGasCost); err != nil { +func getFeeConfig(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = precompile.DeductGas(suppliedGas, GetFeeConfigGasCost); err != nil { return nil, 0, err } @@ -366,8 +272,8 @@ func getFeeConfig(accessibleState PrecompileAccessibleState, caller common.Addre // getFeeConfigLastChangedAt returns the block number that fee config was last changed in. // The execution function reads the contract state for the stored block number and returns the output. -func getFeeConfigLastChangedAt(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = deductGas(suppliedGas, GetLastChangedAtGasCost); err != nil { +func getFeeConfigLastChangedAt(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = precompile.DeductGas(suppliedGas, GetLastChangedAtGasCost); err != nil { return nil, 0, err } @@ -380,16 +286,16 @@ func getFeeConfigLastChangedAt(accessibleState PrecompileAccessibleState, caller // createFeeConfigManagerPrecompile returns a StatefulPrecompiledContract // with getters and setters for the chain's fee config. Access to the getters/setters // is controlled by an allow list for [precompileAddr]. -func createFeeConfigManagerPrecompile(precompileAddr common.Address) StatefulPrecompiledContract { - feeConfigManagerFunctions := createAllowListFunctions(precompileAddr) +func createFeeConfigManagerPrecompile(precompileAddr common.Address) precompile.StatefulPrecompiledContract { + feeConfigManagerFunctions := allowlist.CreateAllowListFunctions(precompileAddr) - setFeeConfigFunc := newStatefulPrecompileFunction(setFeeConfigSignature, setFeeConfig) - getFeeConfigFunc := newStatefulPrecompileFunction(getFeeConfigSignature, getFeeConfig) - getFeeConfigLastChangedAtFunc := newStatefulPrecompileFunction(getFeeConfigLastChangedAtSignature, getFeeConfigLastChangedAt) + setFeeConfigFunc := precompile.NewStatefulPrecompileFunction(setFeeConfigSignature, setFeeConfig) + getFeeConfigFunc := precompile.NewStatefulPrecompileFunction(getFeeConfigSignature, getFeeConfig) + getFeeConfigLastChangedAtFunc := precompile.NewStatefulPrecompileFunction(getFeeConfigLastChangedAtSignature, getFeeConfigLastChangedAt) feeConfigManagerFunctions = append(feeConfigManagerFunctions, setFeeConfigFunc, getFeeConfigFunc, getFeeConfigLastChangedAtFunc) // Construct the contract with no fallback function. - contract, err := NewStatefulPrecompileContract(nil, feeConfigManagerFunctions) + contract, err := precompile.NewStatefulPrecompileContract(nil, feeConfigManagerFunctions) // TODO Change this to be returned as an error after refactoring this precompile // to use the new precompile template. if err != nil { diff --git a/precompile/interface.go b/precompile/interface.go new file mode 100644 index 0000000000..82be0f749a --- /dev/null +++ b/precompile/interface.go @@ -0,0 +1,61 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package precompile + +import ( + "math/big" + + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/subnet-evm/commontype" + "github.com/ethereum/go-ethereum/common" +) + +// PrecompileAccessibleState defines the interface exposed to stateful precompile contracts +type PrecompileAccessibleState interface { + GetStateDB() StateDB + GetBlockContext() BlockContext + GetSnowContext() *snow.Context + CallFromPrecompile(caller common.Address, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) +} + +// BlockContext defines an interface that provides information to a stateful precompile +// about the block that activates the upgrade. The precompile can access this information +// to initialize its state. +type BlockContext interface { + Number() *big.Int + Timestamp() *big.Int +} + +// ChainContext defines an interface that provides information to a stateful precompile +// about the chain configuration. The precompile can access this information to initialize +// its state. +type ChainConfig interface { + // GetFeeConfig returns the original FeeConfig that was set in the genesis. + GetFeeConfig() commontype.FeeConfig + // AllowedFeeRecipients returns true if fee recipients are allowed in the genesis. + AllowedFeeRecipients() bool +} + +// StateDB is the interface for accessing EVM state +type StateDB interface { + GetState(common.Address, common.Hash) common.Hash + SetState(common.Address, common.Hash, common.Hash) + + SetCode(common.Address, []byte) + + SetNonce(common.Address, uint64) + GetNonce(common.Address) uint64 + + GetBalance(common.Address) *big.Int + AddBalance(common.Address, *big.Int) + SubBalance(common.Address, *big.Int) + + CreateAccount(common.Address) + Exist(common.Address) bool + + AddLog(addr common.Address, topics []common.Hash, data []byte, blockNumber uint64) + + Suicide(common.Address) bool + Finalise(deleteEmptyObjects bool) +} diff --git a/precompile/mock_interface.go b/precompile/mock_interface.go new file mode 100644 index 0000000000..4d6ca13281 --- /dev/null +++ b/precompile/mock_interface.go @@ -0,0 +1,115 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package precompile + +import ( + "math/big" + + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/subnet-evm/commontype" + "github.com/ethereum/go-ethereum/common" +) + +// TODO: replace with gomock library + +var ( + _ BlockContext = &mockBlockContext{} + _ PrecompileAccessibleState = &mockAccessibleState{} + _ ChainConfig = &mockChainConfig{} + _ StatefulPrecompileConfig = &noopStatefulPrecompileConfig{} +) + +type mockBlockContext struct { + blockNumber *big.Int + timestamp uint64 +} + +func NewMockBlockContext(blockNumber *big.Int, timestamp uint64) *mockBlockContext { + return &mockBlockContext{ + blockNumber: blockNumber, + timestamp: timestamp, + } +} + +func (mb *mockBlockContext) Number() *big.Int { return mb.blockNumber } +func (mb *mockBlockContext) Timestamp() *big.Int { return new(big.Int).SetUint64(mb.timestamp) } + +type mockAccessibleState struct { + state StateDB + blockContext *mockBlockContext + snowContext *snow.Context +} + +func NewMockAccessibleState(state StateDB, blockContext *mockBlockContext, snowContext *snow.Context) *mockAccessibleState { + return &mockAccessibleState{ + state: state, + blockContext: blockContext, + snowContext: snowContext, + } +} + +func (m *mockAccessibleState) GetStateDB() StateDB { return m.state } + +func (m *mockAccessibleState) GetBlockContext() BlockContext { return m.blockContext } + +func (m *mockAccessibleState) GetSnowContext() *snow.Context { return m.snowContext } + +func (m *mockAccessibleState) CallFromPrecompile(caller common.Address, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { + return nil, 0, nil +} + +type mockChainConfig struct { + feeConfig commontype.FeeConfig + allowedFeeRecipients bool +} + +func NewMockChainConfig(feeConfig commontype.FeeConfig, allowedFeeRecipients bool) *mockChainConfig { + return &mockChainConfig{ + feeConfig: feeConfig, + allowedFeeRecipients: allowedFeeRecipients, + } +} + +func (m *mockChainConfig) GetFeeConfig() commontype.FeeConfig { return m.feeConfig } + +func (m *mockChainConfig) AllowedFeeRecipients() bool { return m.allowedFeeRecipients } + +type noopStatefulPrecompileConfig struct { +} + +func NewNoopStatefulPrecompileConfig() *noopStatefulPrecompileConfig { + return &noopStatefulPrecompileConfig{} +} + +func (n *noopStatefulPrecompileConfig) Address() common.Address { + return common.Address{} +} + +func (n *noopStatefulPrecompileConfig) Timestamp() *big.Int { + return new(big.Int) +} + +func (n *noopStatefulPrecompileConfig) IsDisabled() bool { + return false +} + +func (n *noopStatefulPrecompileConfig) Equal(StatefulPrecompileConfig) bool { + return false +} + +func (n *noopStatefulPrecompileConfig) Verify() error { + return nil +} + +func (n *noopStatefulPrecompileConfig) Configure(ChainConfig, StateDB, BlockContext) error { + return nil +} + +func (n *noopStatefulPrecompileConfig) Contract() StatefulPrecompiledContract { + return nil +} + +func (n *noopStatefulPrecompileConfig) String() string { + return "" +} diff --git a/precompile/nativeminter/config.go b/precompile/nativeminter/config.go new file mode 100644 index 0000000000..51194dcc88 --- /dev/null +++ b/precompile/nativeminter/config.go @@ -0,0 +1,126 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package nativeminter + +import ( + "encoding/json" + "fmt" + "math/big" + + "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" +) + +var _ precompile.StatefulPrecompileConfig = &ContractNativeMinterConfig{} + +// ContractNativeMinterConfig wraps [AllowListConfig] and uses it to implement the StatefulPrecompileConfig +// interface while adding in the ContractNativeMinter specific precompile address. +type ContractNativeMinterConfig struct { + allowlist.AllowListConfig + precompile.UpgradeableConfig + InitialMint map[common.Address]*math.HexOrDecimal256 `json:"initialMint,omitempty"` // initial mint config to be immediately minted +} + +// NewContractNativeMinterConfig returns a config for a network upgrade at [blockTimestamp] that enables +// ContractNativeMinter with the given [admins] and [enableds] as members of the allowlist. Also mints balances according to [initialMint] when the upgrade activates. +func NewContractNativeMinterConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialMint map[common.Address]*math.HexOrDecimal256) *ContractNativeMinterConfig { + return &ContractNativeMinterConfig{ + AllowListConfig: allowlist.AllowListConfig{ + AllowListAdmins: admins, + EnabledAddresses: enableds, + }, + UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: blockTimestamp}, + InitialMint: initialMint, + } +} + +// NewDisableContractNativeMinterConfig returns config for a network upgrade at [blockTimestamp] +// that disables ContractNativeMinter. +func NewDisableContractNativeMinterConfig(blockTimestamp *big.Int) *ContractNativeMinterConfig { + return &ContractNativeMinterConfig{ + UpgradeableConfig: precompile.UpgradeableConfig{ + BlockTimestamp: blockTimestamp, + Disable: true, + }, + } +} + +// Address returns the address of the native minter contract. +func (c *ContractNativeMinterConfig) Address() common.Address { + return precompile.ContractNativeMinterAddress +} + +// Configure configures [state] with the desired admins based on [c]. +func (c *ContractNativeMinterConfig) Configure(_ precompile.ChainConfig, state precompile.StateDB, _ precompile.BlockContext) error { + for to, amount := range c.InitialMint { + if amount != nil { + bigIntAmount := (*big.Int)(amount) + state.AddBalance(to, bigIntAmount) + } + } + + return c.AllowListConfig.Configure(state, precompile.ContractNativeMinterAddress) +} + +// Contract returns the singleton stateful precompiled contract to be used for the native minter. +func (c *ContractNativeMinterConfig) Contract() precompile.StatefulPrecompiledContract { + return ContractNativeMinterPrecompile +} + +func (c *ContractNativeMinterConfig) Verify() error { + if err := c.AllowListConfig.Verify(); err != nil { + return err + } + // ensure that all of the initial mint values in the map are non-nil positive values + for addr, amount := range c.InitialMint { + if amount == nil { + return fmt.Errorf("initial mint cannot contain nil amount for address %s", addr) + } + bigIntAmount := (*big.Int)(amount) + if bigIntAmount.Sign() < 1 { + return fmt.Errorf("initial mint cannot contain invalid amount %v for address %s", bigIntAmount, addr) + } + } + return nil +} + +// Equal returns true if [s] is a [*ContractNativeMinterConfig] and it has been configured identical to [c]. +func (c *ContractNativeMinterConfig) Equal(s precompile.StatefulPrecompileConfig) bool { + // typecast before comparison + other, ok := (s).(*ContractNativeMinterConfig) + if !ok { + return false + } + eq := c.UpgradeableConfig.Equal(&other.UpgradeableConfig) && c.AllowListConfig.Equal(&other.AllowListConfig) + if !eq { + return false + } + + if len(c.InitialMint) != len(other.InitialMint) { + return false + } + + for address, amount := range c.InitialMint { + val, ok := other.InitialMint[address] + if !ok { + return false + } + bigIntAmount := (*big.Int)(amount) + bigIntVal := (*big.Int)(val) + if !utils.BigNumEqual(bigIntAmount, bigIntVal) { + return false + } + } + + return true +} + +// String returns a string representation of the ContractNativeMinterConfig. +func (c *ContractNativeMinterConfig) String() string { + bytes, _ := json.Marshal(c) + return string(bytes) +} diff --git a/precompile/nativeminter/config_test.go b/precompile/nativeminter/config_test.go new file mode 100644 index 0000000000..9eb2441454 --- /dev/null +++ b/precompile/nativeminter/config_test.go @@ -0,0 +1,149 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package nativeminter + +import ( + "math/big" + "testing" + + "github.com/ava-labs/subnet-evm/precompile" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/stretchr/testify/require" +) + +func TestVerifyContractNativeMinterConfig(t *testing.T) { + admins := []common.Address{{1}} + enableds := []common.Address{{2}} + tests := []struct { + name string + config precompile.StatefulPrecompileConfig + expectedError string + }{ + { + name: "invalid allow list config in native minter allowlist", + config: NewContractNativeMinterConfig(big.NewInt(3), admins, admins, nil), + expectedError: "cannot set address", + }, + { + name: "duplicate admins in config in native minter allowlist", + config: NewContractNativeMinterConfig(big.NewInt(3), append(admins, admins[0]), enableds, nil), + expectedError: "duplicate address", + }, + { + name: "duplicate enableds in config in native minter allowlist", + config: NewContractNativeMinterConfig(big.NewInt(3), admins, append(enableds, enableds[0]), nil), + expectedError: "duplicate address", + }, + { + name: "nil amount in native minter config", + config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + map[common.Address]*math.HexOrDecimal256{ + common.HexToAddress("0x01"): math.NewHexOrDecimal256(123), + common.HexToAddress("0x02"): nil, + }), + expectedError: "initial mint cannot contain nil", + }, + { + name: "negative amount in native minter config", + config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + map[common.Address]*math.HexOrDecimal256{ + common.HexToAddress("0x01"): math.NewHexOrDecimal256(123), + common.HexToAddress("0x02"): math.NewHexOrDecimal256(-1), + }), + expectedError: "initial mint cannot contain invalid amount", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + err := tt.config.Verify() + if tt.expectedError == "" { + require.NoError(err) + } else { + require.ErrorContains(err, tt.expectedError) + } + }) + } +} + +func TestEqualContractNativeMinterConfig(t *testing.T) { + admins := []common.Address{{1}} + enableds := []common.Address{{2}} + tests := []struct { + name string + config precompile.StatefulPrecompileConfig + other precompile.StatefulPrecompileConfig + expected bool + }{ + { + name: "non-nil config and nil other", + config: NewContractNativeMinterConfig(big.NewInt(3), admins, enableds, nil), + other: nil, + expected: false, + }, + { + name: "different type", + config: NewContractNativeMinterConfig(big.NewInt(3), admins, enableds, nil), + other: precompile.NewNoopStatefulPrecompileConfig(), + expected: false, + }, + { + name: "different timestamps", + config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, nil), + other: NewContractNativeMinterConfig(big.NewInt(4), admins, nil, nil), + expected: false, + }, + { + name: "different enabled", + config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, nil), + other: NewContractNativeMinterConfig(big.NewInt(3), admins, enableds, nil), + expected: false, + }, + { + name: "different initial mint amounts", + config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + map[common.Address]*math.HexOrDecimal256{ + common.HexToAddress("0x01"): math.NewHexOrDecimal256(1), + }), + other: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + map[common.Address]*math.HexOrDecimal256{ + common.HexToAddress("0x01"): math.NewHexOrDecimal256(2), + }), + expected: false, + }, + { + name: "different initial mint addresses", + config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + map[common.Address]*math.HexOrDecimal256{ + common.HexToAddress("0x01"): math.NewHexOrDecimal256(1), + }), + other: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + map[common.Address]*math.HexOrDecimal256{ + common.HexToAddress("0x02"): math.NewHexOrDecimal256(1), + }), + expected: false, + }, + { + name: "same config", + config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + map[common.Address]*math.HexOrDecimal256{ + common.HexToAddress("0x01"): math.NewHexOrDecimal256(1), + }), + other: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + map[common.Address]*math.HexOrDecimal256{ + common.HexToAddress("0x01"): math.NewHexOrDecimal256(1), + }), + expected: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + require.Equal(tt.expected, tt.config.Equal(tt.other)) + }) + } +} diff --git a/precompile/nativeminter/contract.go b/precompile/nativeminter/contract.go new file mode 100644 index 0000000000..7098c252cb --- /dev/null +++ b/precompile/nativeminter/contract.go @@ -0,0 +1,118 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package nativeminter + +import ( + "errors" + "fmt" + "math/big" + + "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/vmerrs" + "github.com/ethereum/go-ethereum/common" +) + +const ( + mintInputAddressSlot = iota + mintInputAmountSlot + + mintInputLen = common.HashLength + common.HashLength + + MintGasCost = 30_000 +) + +var ( + // Singleton StatefulPrecompiledContract for minting native assets by permissioned callers. + ContractNativeMinterPrecompile precompile.StatefulPrecompiledContract = createNativeMinterPrecompile(precompile.ContractNativeMinterAddress) + + mintSignature = precompile.CalculateFunctionSelector("mintNativeCoin(address,uint256)") // address, amount + ErrCannotMint = errors.New("non-enabled cannot mint") +) + +// GetContractNativeMinterStatus returns the role of [address] for the minter list. +func GetContractNativeMinterStatus(stateDB precompile.StateDB, address common.Address) allowlist.AllowListRole { + return allowlist.GetAllowListStatus(stateDB, precompile.ContractNativeMinterAddress, address) +} + +// SetContractNativeMinterStatus sets the permissions of [address] to [role] for the +// minter list. assumes [role] has already been verified as valid. +func SetContractNativeMinterStatus(stateDB precompile.StateDB, address common.Address, role allowlist.AllowListRole) { + allowlist.SetAllowListRole(stateDB, precompile.ContractNativeMinterAddress, address, role) +} + +// PackMintInput packs [address] and [amount] into the appropriate arguments for minting operation. +// Assumes that [amount] can be represented by 32 bytes. +func PackMintInput(address common.Address, amount *big.Int) ([]byte, error) { + // function selector (4 bytes) + input(hash for address + hash for amount) + res := make([]byte, precompile.SelectorLen+mintInputLen) + err := precompile.PackOrderedHashesWithSelector(res, mintSignature, []common.Hash{ + address.Hash(), + common.BigToHash(amount), + }) + + return res, err +} + +// UnpackMintInput attempts to unpack [input] into the arguments to the mint precompile +// assumes that [input] does not include selector (omits first 4 bytes in PackMintInput) +func UnpackMintInput(input []byte) (common.Address, *big.Int, error) { + if len(input) != mintInputLen { + return common.Address{}, nil, fmt.Errorf("invalid input length for minting: %d", len(input)) + } + to := common.BytesToAddress(precompile.PackedHash(input, mintInputAddressSlot)) + assetAmount := new(big.Int).SetBytes(precompile.PackedHash(input, mintInputAmountSlot)) + return to, assetAmount, nil +} + +// mintNativeCoin checks if the caller is permissioned for minting operation. +// The execution function parses the [input] into native coin amount and receiver address. +func mintNativeCoin(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = precompile.DeductGas(suppliedGas, MintGasCost); err != nil { + return nil, 0, err + } + + if readOnly { + return nil, remainingGas, vmerrs.ErrWriteProtection + } + + to, amount, err := UnpackMintInput(input) + if err != nil { + return nil, remainingGas, err + } + + stateDB := accessibleState.GetStateDB() + // Verify that the caller is in the allow list + callerStatus := allowlist.GetAllowListStatus(stateDB, precompile.ContractNativeMinterAddress, caller) + if !callerStatus.IsEnabled() { + return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotMint, caller) + } + + // if there is no address in the state, create one. + if !stateDB.Exist(to) { + stateDB.CreateAccount(to) + } + + stateDB.AddBalance(to, amount) + // Return an empty output and the remaining gas + return []byte{}, remainingGas, nil +} + +// createNativeMinterPrecompile returns a StatefulPrecompiledContract for native coin minting. The precompile +// is accessed controlled by an allow list at [precompileAddr]. +func createNativeMinterPrecompile(precompileAddr common.Address) precompile.StatefulPrecompiledContract { + enabledFuncs := allowlist.CreateAllowListFunctions(precompileAddr) + + mintFunc := precompile.NewStatefulPrecompileFunction(mintSignature, mintNativeCoin) + + enabledFuncs = append(enabledFuncs, mintFunc) + // Construct the contract with no fallback function. + contract, err := precompile.NewStatefulPrecompileContract(nil, enabledFuncs) + // TODO: Change this to be returned as an error after refactoring this precompile + // to use the new precompile template. + if err != nil { + panic(err) + } + return contract +} diff --git a/precompile/params.go b/precompile/params.go index 965ab1df20..294ab328fa 100644 --- a/precompile/params.go +++ b/precompile/params.go @@ -11,8 +11,8 @@ import ( // Gas costs for stateful precompiles const ( - writeGasCostPerSlot = 20_000 - readGasCostPerSlot = 5_000 + WriteGasCostPerSlot = 20_000 + ReadGasCostPerSlot = 5_000 ) // Designated addresses of stateful precompiles diff --git a/precompile/rewardmanager/config.go b/precompile/rewardmanager/config.go new file mode 100644 index 0000000000..640198da30 --- /dev/null +++ b/precompile/rewardmanager/config.go @@ -0,0 +1,153 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Code generated +// This file is a generated precompile contract with stubbed abstract functions. + +package rewardmanager + +import ( + "encoding/json" + "math/big" + + "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ethereum/go-ethereum/common" +) + +var _ precompile.StatefulPrecompileConfig = &RewardManagerConfig{} + +type InitialRewardConfig struct { + AllowFeeRecipients bool `json:"allowFeeRecipients"` + RewardAddress common.Address `json:"rewardAddress,omitempty"` +} + +func (i *InitialRewardConfig) Verify() error { + switch { + case i.AllowFeeRecipients && i.RewardAddress != (common.Address{}): + return ErrCannotEnableBothRewards + default: + return nil + } +} + +func (i *InitialRewardConfig) Equal(other *InitialRewardConfig) bool { + if other == nil { + return false + } + + return i.AllowFeeRecipients == other.AllowFeeRecipients && i.RewardAddress == other.RewardAddress +} + +func (i *InitialRewardConfig) Configure(state precompile.StateDB) error { + // enable allow fee recipients + if i.AllowFeeRecipients { + EnableAllowFeeRecipients(state) + } else if i.RewardAddress == (common.Address{}) { + // if reward address is empty and allow fee recipients is false + // then disable rewards + DisableFeeRewards(state) + } else { + // set reward address + return StoreRewardAddress(state, i.RewardAddress) + } + return nil +} + +// RewardManagerConfig implements the StatefulPrecompileConfig +// interface while adding in the RewardManager specific precompile config. +type RewardManagerConfig struct { + allowlist.AllowListConfig + precompile.UpgradeableConfig + InitialRewardConfig *InitialRewardConfig `json:"initialRewardConfig,omitempty"` +} + +// NewRewardManagerConfig returns a config for a network upgrade at [blockTimestamp] that enables +// RewardManager with the given [admins] and [enableds] as members of the allowlist with [initialConfig] as initial rewards config if specified. +func NewRewardManagerConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialConfig *InitialRewardConfig) *RewardManagerConfig { + return &RewardManagerConfig{ + AllowListConfig: allowlist.AllowListConfig{ + AllowListAdmins: admins, + EnabledAddresses: enableds, + }, + UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: blockTimestamp}, + InitialRewardConfig: initialConfig, + } +} + +// NewDisableRewardManagerConfig returns config for a network upgrade at [blockTimestamp] +// that disables RewardManager. +func NewDisableRewardManagerConfig(blockTimestamp *big.Int) *RewardManagerConfig { + return &RewardManagerConfig{ + UpgradeableConfig: precompile.UpgradeableConfig{ + BlockTimestamp: blockTimestamp, + Disable: true, + }, + } +} + +// Equal returns true if [s] is a [*RewardManagerConfig] and it has been configured identical to [c]. +func (c *RewardManagerConfig) Equal(s precompile.StatefulPrecompileConfig) bool { + // typecast before comparison + other, ok := (s).(*RewardManagerConfig) + if !ok { + return false + } + // modify this boolean accordingly with your custom RewardManagerConfig, to check if [other] and the current [c] are equal + // if RewardManagerConfig contains only UpgradeableConfig and precompile.AllowListConfig you can skip modifying it. + equals := c.UpgradeableConfig.Equal(&other.UpgradeableConfig) && c.AllowListConfig.Equal(&other.AllowListConfig) + if !equals { + return false + } + + if c.InitialRewardConfig == nil { + return other.InitialRewardConfig == nil + } + + return c.InitialRewardConfig.Equal(other.InitialRewardConfig) +} + +// Address returns the address of the RewardManager. Addresses reside under the precompile/params.go +// Select a non-conflicting address and set it in the params.go. +func (c *RewardManagerConfig) Address() common.Address { + return precompile.RewardManagerAddress +} + +// Configure configures [state] with the initial configuration. +func (c *RewardManagerConfig) Configure(chainConfig precompile.ChainConfig, state precompile.StateDB, _ precompile.BlockContext) error { + c.AllowListConfig.Configure(state, precompile.RewardManagerAddress) + // configure the RewardManager with the given initial configuration + if c.InitialRewardConfig != nil { + return c.InitialRewardConfig.Configure(state) + } else if chainConfig.AllowedFeeRecipients() { + // configure the RewardManager according to chainConfig + EnableAllowFeeRecipients(state) + } else { + // chainConfig does not have any reward address + // if chainConfig does not enable fee recipients + // default to disabling rewards + DisableFeeRewards(state) + } + return nil +} + +// Contract returns the singleton stateful precompiled contract to be used for RewardManager. +func (c *RewardManagerConfig) Contract() precompile.StatefulPrecompiledContract { + return RewardManagerPrecompile +} + +func (c *RewardManagerConfig) Verify() error { + if err := c.AllowListConfig.Verify(); err != nil { + return err + } + if c.InitialRewardConfig != nil { + return c.InitialRewardConfig.Verify() + } + return nil +} + +// String returns a string representation of the RewardManagerConfig. +func (c *RewardManagerConfig) String() string { + bytes, _ := json.Marshal(c) + return string(bytes) +} diff --git a/precompile/rewardmanager/config_test.go b/precompile/rewardmanager/config_test.go new file mode 100644 index 0000000000..7e5fb5c19f --- /dev/null +++ b/precompile/rewardmanager/config_test.go @@ -0,0 +1,121 @@ +// (c) 2022 Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package rewardmanager + +import ( + "math/big" + "testing" + + "github.com/ava-labs/subnet-evm/precompile" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestVerifyRewardManagerConfig(t *testing.T) { + admins := []common.Address{{1}} + enableds := []common.Address{{2}} + tests := []struct { + name string + config precompile.StatefulPrecompileConfig + expectedError string + }{ + { + name: "duplicate enableds in config in reward manager allowlist", + config: NewRewardManagerConfig(big.NewInt(3), admins, append(enableds, enableds[0]), nil), + expectedError: "duplicate address", + }, + { + name: "both reward mechanisms should not be activated at the same time in reward manager", + config: NewRewardManagerConfig(big.NewInt(3), admins, enableds, &InitialRewardConfig{ + AllowFeeRecipients: true, + RewardAddress: common.HexToAddress("0x01"), + }), + expectedError: ErrCannotEnableBothRewards.Error(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + err := tt.config.Verify() + if tt.expectedError == "" { + require.NoError(err) + } else { + require.ErrorContains(err, tt.expectedError) + } + }) + } +} + +func TestEqualRewardManagerConfig(t *testing.T) { + admins := []common.Address{{1}} + enableds := []common.Address{{2}} + tests := []struct { + name string + config precompile.StatefulPrecompileConfig + other precompile.StatefulPrecompileConfig + expected bool + }{ + { + name: "non-nil config and nil other", + config: NewRewardManagerConfig(big.NewInt(3), admins, enableds, nil), + other: nil, + expected: false, + }, + { + name: "different type", + config: NewRewardManagerConfig(big.NewInt(3), admins, enableds, nil), + other: precompile.NewNoopStatefulPrecompileConfig(), + expected: false, + }, + { + name: "different timestamp", + config: NewRewardManagerConfig(big.NewInt(3), admins, nil, nil), + other: NewRewardManagerConfig(big.NewInt(4), admins, nil, nil), + expected: false, + }, + { + name: "different enabled", + config: NewRewardManagerConfig(big.NewInt(3), admins, nil, nil), + other: NewRewardManagerConfig(big.NewInt(3), admins, enableds, nil), + expected: false, + }, + { + name: "non-nil initial config and nil initial config", + config: NewRewardManagerConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ + AllowFeeRecipients: true, + }), + other: NewRewardManagerConfig(big.NewInt(3), admins, nil, nil), + expected: false, + }, + { + name: "different initial config", + config: NewRewardManagerConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ + RewardAddress: common.HexToAddress("0x01"), + }), + other: NewRewardManagerConfig(big.NewInt(3), admins, nil, + &InitialRewardConfig{ + RewardAddress: common.HexToAddress("0x02"), + }), + expected: false, + }, + { + name: "same config", + config: NewRewardManagerConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ + RewardAddress: common.HexToAddress("0x01"), + }), + other: NewRewardManagerConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ + RewardAddress: common.HexToAddress("0x01"), + }), + expected: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + require.Equal(tt.expected, tt.config.Equal(tt.other)) + }) + } +} diff --git a/precompile/reward_manager.abi b/precompile/rewardmanager/contract.abi similarity index 100% rename from precompile/reward_manager.abi rename to precompile/rewardmanager/contract.abi diff --git a/precompile/reward_manager.go b/precompile/rewardmanager/contract.go similarity index 52% rename from precompile/reward_manager.go rename to precompile/rewardmanager/contract.go index 7262efa0b8..0c95aee847 100644 --- a/precompile/reward_manager.go +++ b/precompile/rewardmanager/contract.go @@ -4,17 +4,17 @@ // Code generated // This file is a generated precompile contract with stubbed abstract functions. -package precompile +package rewardmanager import ( - "encoding/json" "errors" "fmt" - "math/big" "strings" "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/constants" + "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ava-labs/subnet-evm/vmerrs" _ "embed" @@ -23,17 +23,15 @@ import ( ) const ( - AllowFeeRecipientsGasCost uint64 = (writeGasCostPerSlot) + ReadAllowListGasCost // write 1 slot + read allow list - AreFeeRecipientsAllowedGasCost uint64 = readGasCostPerSlot - CurrentRewardAddressGasCost uint64 = readGasCostPerSlot - DisableRewardsGasCost uint64 = (writeGasCostPerSlot) + ReadAllowListGasCost // write 1 slot + read allow list - SetRewardAddressGasCost uint64 = (writeGasCostPerSlot) + ReadAllowListGasCost // write 1 slot + read allow list + AllowFeeRecipientsGasCost uint64 = (precompile.WriteGasCostPerSlot) + allowlist.ReadAllowListGasCost // write 1 slot + read allow list + AreFeeRecipientsAllowedGasCost uint64 = allowlist.ReadAllowListGasCost + CurrentRewardAddressGasCost uint64 = allowlist.ReadAllowListGasCost + DisableRewardsGasCost uint64 = (precompile.WriteGasCostPerSlot) + allowlist.ReadAllowListGasCost // write 1 slot + read allow list + SetRewardAddressGasCost uint64 = (precompile.WriteGasCostPerSlot) + allowlist.ReadAllowListGasCost // write 1 slot + read allow list ) // Singleton StatefulPrecompiledContract and signatures. var ( - _ StatefulPrecompileConfig = &RewardManagerConfig{} - ErrCannotAllowFeeRecipients = errors.New("non-enabled cannot call allowFeeRecipients") ErrCannotAreFeeRecipientsAllowed = errors.New("non-enabled cannot call areFeeRecipientsAllowed") ErrCannotCurrentRewardAddress = errors.New("non-enabled cannot call currentRewardAddress") @@ -44,172 +42,37 @@ var ( ErrEmptyRewardAddress = errors.New("reward address cannot be empty") // RewardManagerRawABI contains the raw ABI of RewardManager contract. - //go:embed reward_manager.abi + //go:embed contract.abi RewardManagerRawABI string - RewardManagerABI abi.ABI // will be initialized by init function - RewardManagerPrecompile StatefulPrecompiledContract // will be initialized by init function + RewardManagerABI abi.ABI // will be initialized by init function + RewardManagerPrecompile precompile.StatefulPrecompiledContract // will be initialized by init function rewardAddressStorageKey = common.Hash{'r', 'a', 's', 'k'} allowFeeRecipientsAddressValue = common.Hash{'a', 'f', 'r', 'a', 'v'} ) -type InitialRewardConfig struct { - AllowFeeRecipients bool `json:"allowFeeRecipients"` - RewardAddress common.Address `json:"rewardAddress,omitempty"` -} - -func (i *InitialRewardConfig) Verify() error { - switch { - case i.AllowFeeRecipients && i.RewardAddress != (common.Address{}): - return ErrCannotEnableBothRewards - default: - return nil - } -} - -func (c *InitialRewardConfig) Equal(other *InitialRewardConfig) bool { - if other == nil { - return false - } - - return c.AllowFeeRecipients == other.AllowFeeRecipients && c.RewardAddress == other.RewardAddress -} - -func (i *InitialRewardConfig) Configure(state StateDB) error { - // enable allow fee recipients - if i.AllowFeeRecipients { - EnableAllowFeeRecipients(state) - } else if i.RewardAddress == (common.Address{}) { - // if reward address is empty and allow fee recipients is false - // then disable rewards - DisableFeeRewards(state) - } else { - // set reward address - return StoreRewardAddress(state, i.RewardAddress) - } - return nil -} - -// RewardManagerConfig implements the StatefulPrecompileConfig -// interface while adding in the RewardManager specific precompile config. -type RewardManagerConfig struct { - AllowListConfig - UpgradeableConfig - InitialRewardConfig *InitialRewardConfig `json:"initialRewardConfig,omitempty"` -} - func init() { parsed, err := abi.JSON(strings.NewReader(RewardManagerRawABI)) if err != nil { panic(err) } RewardManagerABI = parsed - RewardManagerPrecompile, err = createRewardManagerPrecompile(RewardManagerAddress) + RewardManagerPrecompile, err = createRewardManagerPrecompile(precompile.RewardManagerAddress) if err != nil { panic(err) } } -// NewRewardManagerConfig returns a config for a network upgrade at [blockTimestamp] that enables -// RewardManager with the given [admins] and [enableds] as members of the allowlist with [initialConfig] as initial rewards config if specified. -func NewRewardManagerConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialConfig *InitialRewardConfig) *RewardManagerConfig { - return &RewardManagerConfig{ - AllowListConfig: AllowListConfig{ - AllowListAdmins: admins, - EnabledAddresses: enableds, - }, - UpgradeableConfig: UpgradeableConfig{BlockTimestamp: blockTimestamp}, - InitialRewardConfig: initialConfig, - } -} - -// NewDisableRewardManagerConfig returns config for a network upgrade at [blockTimestamp] -// that disables RewardManager. -func NewDisableRewardManagerConfig(blockTimestamp *big.Int) *RewardManagerConfig { - return &RewardManagerConfig{ - UpgradeableConfig: UpgradeableConfig{ - BlockTimestamp: blockTimestamp, - Disable: true, - }, - } -} - -// Equal returns true if [s] is a [*RewardManagerConfig] and it has been configured identical to [c]. -func (c *RewardManagerConfig) Equal(s StatefulPrecompileConfig) bool { - // typecast before comparison - other, ok := (s).(*RewardManagerConfig) - if !ok { - return false - } - // modify this boolean accordingly with your custom RewardManagerConfig, to check if [other] and the current [c] are equal - // if RewardManagerConfig contains only UpgradeableConfig and AllowListConfig you can skip modifying it. - equals := c.UpgradeableConfig.Equal(&other.UpgradeableConfig) && c.AllowListConfig.Equal(&other.AllowListConfig) - if !equals { - return false - } - - if c.InitialRewardConfig == nil { - return other.InitialRewardConfig == nil - } - - return c.InitialRewardConfig.Equal(other.InitialRewardConfig) -} - -// Address returns the address of the RewardManager. Addresses reside under the precompile/params.go -// Select a non-conflicting address and set it in the params.go. -func (c *RewardManagerConfig) Address() common.Address { - return RewardManagerAddress -} - -// Configure configures [state] with the initial configuration. -func (c *RewardManagerConfig) Configure(chainConfig ChainConfig, state StateDB, _ BlockContext) error { - c.AllowListConfig.Configure(state, RewardManagerAddress) - // configure the RewardManager with the given initial configuration - if c.InitialRewardConfig != nil { - return c.InitialRewardConfig.Configure(state) - } else if chainConfig.AllowedFeeRecipients() { - // configure the RewardManager according to chainConfig - EnableAllowFeeRecipients(state) - } else { - // chainConfig does not have any reward address - // if chainConfig does not enable fee recipients - // default to disabling rewards - DisableFeeRewards(state) - } - return nil -} - -// Contract returns the singleton stateful precompiled contract to be used for RewardManager. -func (c *RewardManagerConfig) Contract() StatefulPrecompiledContract { - return RewardManagerPrecompile -} - -func (c *RewardManagerConfig) Verify() error { - if err := c.AllowListConfig.Verify(); err != nil { - return err - } - if c.InitialRewardConfig != nil { - return c.InitialRewardConfig.Verify() - } - return nil -} - -// String returns a string representation of the RewardManagerConfig. -func (c *RewardManagerConfig) String() string { - bytes, _ := json.Marshal(c) - return string(bytes) -} - // GetRewardManagerAllowListStatus returns the role of [address] for the RewardManager list. -func GetRewardManagerAllowListStatus(stateDB StateDB, address common.Address) AllowListRole { - return getAllowListStatus(stateDB, RewardManagerAddress, address) +func GetRewardManagerAllowListStatus(stateDB precompile.StateDB, address common.Address) allowlist.AllowListRole { + return allowlist.GetAllowListStatus(stateDB, precompile.RewardManagerAddress, address) } // SetRewardManagerAllowListStatus sets the permissions of [address] to [role] for the // RewardManager list. Assumes [role] has already been verified as valid. -func SetRewardManagerAllowListStatus(stateDB StateDB, address common.Address, role AllowListRole) { - setAllowListRole(stateDB, RewardManagerAddress, address, role) +func SetRewardManagerAllowListStatus(stateDB precompile.StateDB, address common.Address, role allowlist.AllowListRole) { + allowlist.SetAllowListRole(stateDB, precompile.RewardManagerAddress, address, role) } // PackAllowFeeRecipients packs the function selector (first 4 func signature bytes). @@ -219,17 +82,17 @@ func PackAllowFeeRecipients() ([]byte, error) { } // EnableAllowFeeRecipients enables fee recipients. -func EnableAllowFeeRecipients(stateDB StateDB) { - stateDB.SetState(RewardManagerAddress, rewardAddressStorageKey, allowFeeRecipientsAddressValue) +func EnableAllowFeeRecipients(stateDB precompile.StateDB) { + stateDB.SetState(precompile.RewardManagerAddress, rewardAddressStorageKey, allowFeeRecipientsAddressValue) } // DisableRewardAddress disables rewards and burns them by sending to Blackhole Address. -func DisableFeeRewards(stateDB StateDB) { - stateDB.SetState(RewardManagerAddress, rewardAddressStorageKey, constants.BlackholeAddr.Hash()) +func DisableFeeRewards(stateDB precompile.StateDB) { + stateDB.SetState(precompile.RewardManagerAddress, rewardAddressStorageKey, constants.BlackholeAddr.Hash()) } -func allowFeeRecipients(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = deductGas(suppliedGas, AllowFeeRecipientsGasCost); err != nil { +func allowFeeRecipients(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = precompile.DeductGas(suppliedGas, AllowFeeRecipientsGasCost); err != nil { return nil, 0, err } if readOnly { @@ -242,7 +105,7 @@ func allowFeeRecipients(accessibleState PrecompileAccessibleState, caller common // You can modify/delete this code if you don't want this function to be restricted by the allow list. stateDB := accessibleState.GetStateDB() // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := getAllowListStatus(stateDB, RewardManagerAddress, caller) + callerStatus := allowlist.GetAllowListStatus(stateDB, precompile.RewardManagerAddress, caller) if !callerStatus.IsEnabled() { return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotAllowFeeRecipients, caller) } @@ -268,8 +131,8 @@ func PackAreFeeRecipientsAllowedOutput(isAllowed bool) ([]byte, error) { return RewardManagerABI.PackOutput("areFeeRecipientsAllowed", isAllowed) } -func areFeeRecipientsAllowed(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = deductGas(suppliedGas, AreFeeRecipientsAllowedGasCost); err != nil { +func areFeeRecipientsAllowed(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = precompile.DeductGas(suppliedGas, AreFeeRecipientsAllowedGasCost); err != nil { return nil, 0, err } // no input provided for this function @@ -301,18 +164,18 @@ func PackCurrentRewardAddressOutput(rewardAddress common.Address) ([]byte, error // GetStoredRewardAddress returns the current value of the address stored under rewardAddressStorageKey. // Returns an empty address and true if allow fee recipients is enabled, otherwise returns current reward address and false. -func GetStoredRewardAddress(stateDB StateDB) (common.Address, bool) { - val := stateDB.GetState(RewardManagerAddress, rewardAddressStorageKey) +func GetStoredRewardAddress(stateDB precompile.StateDB) (common.Address, bool) { + val := stateDB.GetState(precompile.RewardManagerAddress, rewardAddressStorageKey) return common.BytesToAddress(val.Bytes()), val == allowFeeRecipientsAddressValue } // StoredRewardAddress stores the given [val] under rewardAddressStorageKey. -func StoreRewardAddress(stateDB StateDB, val common.Address) error { +func StoreRewardAddress(stateDB precompile.StateDB, val common.Address) error { // if input is empty, return an error if val == (common.Address{}) { return ErrEmptyRewardAddress } - stateDB.SetState(RewardManagerAddress, rewardAddressStorageKey, val.Hash()) + stateDB.SetState(precompile.RewardManagerAddress, rewardAddressStorageKey, val.Hash()) return nil } @@ -334,8 +197,8 @@ func UnpackSetRewardAddressInput(input []byte) (common.Address, error) { return unpacked, nil } -func setRewardAddress(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = deductGas(suppliedGas, SetRewardAddressGasCost); err != nil { +func setRewardAddress(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = precompile.DeductGas(suppliedGas, SetRewardAddressGasCost); err != nil { return nil, 0, err } if readOnly { @@ -354,7 +217,7 @@ func setRewardAddress(accessibleState PrecompileAccessibleState, caller common.A // You can modify/delete this code if you don't want this function to be restricted by the allow list. stateDB := accessibleState.GetStateDB() // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := getAllowListStatus(stateDB, RewardManagerAddress, caller) + callerStatus := allowlist.GetAllowListStatus(stateDB, precompile.RewardManagerAddress, caller) if !callerStatus.IsEnabled() { return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotSetRewardAddress, caller) } @@ -370,8 +233,8 @@ func setRewardAddress(accessibleState PrecompileAccessibleState, caller common.A return packedOutput, remainingGas, nil } -func currentRewardAddress(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = deductGas(suppliedGas, CurrentRewardAddressGasCost); err != nil { +func currentRewardAddress(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = precompile.DeductGas(suppliedGas, CurrentRewardAddressGasCost); err != nil { return nil, 0, err } @@ -393,8 +256,8 @@ func PackDisableRewards() ([]byte, error) { return RewardManagerABI.Pack("disableRewards") } -func disableRewards(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = deductGas(suppliedGas, DisableRewardsGasCost); err != nil { +func disableRewards(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = precompile.DeductGas(suppliedGas, DisableRewardsGasCost); err != nil { return nil, 0, err } if readOnly { @@ -407,7 +270,7 @@ func disableRewards(accessibleState PrecompileAccessibleState, caller common.Add // You can modify/delete this code if you don't want this function to be restricted by the allow list. stateDB := accessibleState.GetStateDB() // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := getAllowListStatus(stateDB, RewardManagerAddress, caller) + callerStatus := allowlist.GetAllowListStatus(stateDB, precompile.RewardManagerAddress, caller) if !callerStatus.IsEnabled() { return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotDisableRewards, caller) } @@ -422,10 +285,10 @@ func disableRewards(accessibleState PrecompileAccessibleState, caller common.Add // createRewardManagerPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile. // Access to the getters/setters is controlled by an allow list for [precompileAddr]. -func createRewardManagerPrecompile(precompileAddr common.Address) (StatefulPrecompiledContract, error) { - var functions []*statefulPrecompileFunction - functions = append(functions, createAllowListFunctions(precompileAddr)...) - abiFunctionMap := map[string]RunStatefulPrecompileFunc{ +func createRewardManagerPrecompile(precompileAddr common.Address) (precompile.StatefulPrecompiledContract, error) { + var functions []*precompile.StatefulPrecompileFunction + functions = append(functions, allowlist.CreateAllowListFunctions(precompileAddr)...) + abiFunctionMap := map[string]precompile.RunStatefulPrecompileFunc{ "allowFeeRecipients": allowFeeRecipients, "areFeeRecipientsAllowed": areFeeRecipientsAllowed, "currentRewardAddress": currentRewardAddress, @@ -438,9 +301,9 @@ func createRewardManagerPrecompile(precompileAddr common.Address) (StatefulPreco if !ok { return nil, fmt.Errorf("given method (%s) does not exist in the ABI", name) } - functions = append(functions, newStatefulPrecompileFunction(method.ID, function)) + functions = append(functions, precompile.NewStatefulPrecompileFunction(method.ID, function)) } // Construct the contract with no fallback function. - return NewStatefulPrecompileContract(nil, functions) + return precompile.NewStatefulPrecompileContract(nil, functions) } diff --git a/precompile/stateful_precompile_config.go b/precompile/stateful_precompile_config.go index 6955b8d448..3a293cbb5f 100644 --- a/precompile/stateful_precompile_config.go +++ b/precompile/stateful_precompile_config.go @@ -23,6 +23,8 @@ type StatefulPrecompileConfig interface { IsDisabled() bool // Equal returns true if the provided argument configures the same precompile with the same parameters. Equal(StatefulPrecompileConfig) bool + // Verify is called on startup and an error is treated as fatal. Configure can assume the Config has passed verification. + Verify() error // Configure is called on the first block where the stateful precompile should be enabled. // This allows the stateful precompile to configure its own state via [StateDB] and [BlockContext] as necessary. // This function must be deterministic since it will impact the EVM state. If a change to the @@ -36,8 +38,6 @@ type StatefulPrecompileConfig interface { // Contract returns a thread-safe singleton that can be used as the StatefulPrecompiledContract when // this config is enabled. Contract() StatefulPrecompiledContract - // Verify is called on startup and an error is treated as fatal. Configure can assume the Config has passed verification. - Verify() error fmt.Stringer } diff --git a/precompile/tx_allow_list.go b/precompile/txallowlist/config.go similarity index 56% rename from precompile/tx_allow_list.go rename to precompile/txallowlist/config.go index 4ab39a8f04..88435c977b 100644 --- a/precompile/tx_allow_list.go +++ b/precompile/txallowlist/config.go @@ -1,40 +1,35 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package precompile +package txallowlist import ( "encoding/json" - "errors" "math/big" + "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ethereum/go-ethereum/common" ) -var ( - _ StatefulPrecompileConfig = &TxAllowListConfig{} - // Singleton StatefulPrecompiledContract for W/R access to the contract deployer allow list. - TxAllowListPrecompile StatefulPrecompiledContract = createAllowListPrecompile(TxAllowListAddress) - - ErrSenderAddressNotAllowListed = errors.New("cannot issue transaction from non-allow listed address") -) +var _ precompile.StatefulPrecompileConfig = &TxAllowListConfig{} // TxAllowListConfig wraps [AllowListConfig] and uses it to implement the StatefulPrecompileConfig // interface while adding in the TxAllowList specific precompile address. type TxAllowListConfig struct { - AllowListConfig - UpgradeableConfig + allowlist.AllowListConfig + precompile.UpgradeableConfig } // NewTxAllowListConfig returns a config for a network upgrade at [blockTimestamp] that enables // TxAllowList with the given [admins] and [enableds] as members of the allowlist. func NewTxAllowListConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address) *TxAllowListConfig { return &TxAllowListConfig{ - AllowListConfig: AllowListConfig{ + AllowListConfig: allowlist.AllowListConfig{ AllowListAdmins: admins, EnabledAddresses: enableds, }, - UpgradeableConfig: UpgradeableConfig{BlockTimestamp: blockTimestamp}, + UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: blockTimestamp}, } } @@ -42,7 +37,7 @@ func NewTxAllowListConfig(blockTimestamp *big.Int, admins []common.Address, enab // that disables TxAllowList. func NewDisableTxAllowListConfig(blockTimestamp *big.Int) *TxAllowListConfig { return &TxAllowListConfig{ - UpgradeableConfig: UpgradeableConfig{ + UpgradeableConfig: precompile.UpgradeableConfig{ BlockTimestamp: blockTimestamp, Disable: true, }, @@ -51,21 +46,21 @@ func NewDisableTxAllowListConfig(blockTimestamp *big.Int) *TxAllowListConfig { // Address returns the address of the contract deployer allow list. func (c *TxAllowListConfig) Address() common.Address { - return TxAllowListAddress + return precompile.TxAllowListAddress } // Configure configures [state] with the desired admins based on [c]. -func (c *TxAllowListConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) error { - return c.AllowListConfig.Configure(state, TxAllowListAddress) +func (c *TxAllowListConfig) Configure(_ precompile.ChainConfig, state precompile.StateDB, _ precompile.BlockContext) error { + return c.AllowListConfig.Configure(state, precompile.TxAllowListAddress) } // Contract returns the singleton stateful precompiled contract to be used for the allow list. -func (c *TxAllowListConfig) Contract() StatefulPrecompiledContract { +func (c *TxAllowListConfig) Contract() precompile.StatefulPrecompiledContract { return TxAllowListPrecompile } // Equal returns true if [s] is a [*TxAllowListConfig] and it has been configured identical to [c]. -func (c *TxAllowListConfig) Equal(s StatefulPrecompileConfig) bool { +func (c *TxAllowListConfig) Equal(s precompile.StatefulPrecompileConfig) bool { // typecast before comparison other, ok := (s).(*TxAllowListConfig) if !ok { @@ -79,16 +74,3 @@ func (c *TxAllowListConfig) String() string { bytes, _ := json.Marshal(c) return string(bytes) } - -// GetTxAllowListStatus returns the role of [address] for the contract deployer -// allow list. -func GetTxAllowListStatus(stateDB StateDB, address common.Address) AllowListRole { - return getAllowListStatus(stateDB, TxAllowListAddress, address) -} - -// SetTxAllowListStatus sets the permissions of [address] to [role] for the -// tx allow list. -// assumes [role] has already been verified as valid. -func SetTxAllowListStatus(stateDB StateDB, address common.Address, role AllowListRole) { - setAllowListRole(stateDB, TxAllowListAddress, address, role) -} diff --git a/precompile/txallowlist/config_test.go b/precompile/txallowlist/config_test.go new file mode 100644 index 0000000000..651bfa9ba2 --- /dev/null +++ b/precompile/txallowlist/config_test.go @@ -0,0 +1,105 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txallowlist + +import ( + "math/big" + "testing" + + "github.com/ava-labs/subnet-evm/precompile" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestVerifyTxAllowlistConfig(t *testing.T) { + admins := []common.Address{{1}} + enableds := []common.Address{{2}} + tests := []struct { + name string + config precompile.StatefulPrecompileConfig + expectedError string + }{ + { + name: "invalid allow list config in tx allowlist", + config: NewTxAllowListConfig(big.NewInt(3), admins, admins), + expectedError: "cannot set address", + }, + { + name: "nil member allow list config in tx allowlist", + config: NewTxAllowListConfig(big.NewInt(3), nil, nil), + expectedError: "", + }, + { + name: "empty member allow list config in tx allowlist", + config: NewTxAllowListConfig(big.NewInt(3), []common.Address{}, []common.Address{}), + expectedError: "", + }, + { + name: "valid allow list config in tx allowlist", + config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), + expectedError: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + err := tt.config.Verify() + if tt.expectedError == "" { + require.NoError(err) + } else { + require.ErrorContains(err, tt.expectedError) + } + }) + } +} + +func TestEqualTxAllowListConfig(t *testing.T) { + admins := []common.Address{{1}} + enableds := []common.Address{{2}} + tests := []struct { + name string + config precompile.StatefulPrecompileConfig + other precompile.StatefulPrecompileConfig + expected bool + }{ + { + name: "non-nil config and nil other", + config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), + other: nil, + expected: false, + }, + { + name: "different admin", + config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), + other: NewTxAllowListConfig(big.NewInt(3), []common.Address{{3}}, enableds), + expected: false, + }, + { + name: "different enabled", + config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), + other: NewTxAllowListConfig(big.NewInt(3), admins, []common.Address{{3}}), + expected: false, + }, + { + name: "different timestamp", + config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), + other: NewTxAllowListConfig(big.NewInt(4), admins, enableds), + expected: false, + }, + { + name: "same config", + config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), + other: NewTxAllowListConfig(big.NewInt(3), admins, enableds), + expected: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + require.Equal(tt.expected, tt.config.Equal(tt.other)) + }) + } +} diff --git a/precompile/txallowlist/contract.go b/precompile/txallowlist/contract.go new file mode 100644 index 0000000000..2917e9b9c4 --- /dev/null +++ b/precompile/txallowlist/contract.go @@ -0,0 +1,33 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txallowlist + +import ( + "errors" + + "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ethereum/go-ethereum/common" +) + +var ( + _ precompile.StatefulPrecompileConfig = &TxAllowListConfig{} + + // Singleton StatefulPrecompiledContract for W/R access to the tx allow list. + TxAllowListPrecompile precompile.StatefulPrecompiledContract = allowlist.CreateAllowListPrecompile(precompile.TxAllowListAddress) + + ErrSenderAddressNotAllowListed = errors.New("cannot issue transaction from non-allow listed address") +) + +// GetTxAllowListStatus returns the role of [address] for the allow list. +func GetTxAllowListStatus(stateDB precompile.StateDB, address common.Address) allowlist.AllowListRole { + return allowlist.GetAllowListStatus(stateDB, precompile.TxAllowListAddress, address) +} + +// SetTxAllowListStatus sets the permissions of [address] to [role] for the +// tx allow list. +// assumes [role] has already been verified as valid. +func SetTxAllowListStatus(stateDB precompile.StateDB, address common.Address, role allowlist.AllowListRole) { + allowlist.SetAllowListRole(stateDB, precompile.TxAllowListAddress, address, role) +} diff --git a/precompile/utils.go b/precompile/utils.go index 6da328f9c8..f03e89bc50 100644 --- a/precompile/utils.go +++ b/precompile/utils.go @@ -17,6 +17,7 @@ var functionSignatureRegex = regexp.MustCompile(`[\w]+\(((([\w]+)?)|((([\w]+),)+ // CalculateFunctionSelector returns the 4 byte function selector that results from [functionSignature] // Ex. the function setBalance(addr address, balance uint256) should be passed in as the string: // "setBalance(address,uint256)" +// TODO: remove this after moving to ABI based function selectors. func CalculateFunctionSelector(functionSignature string) []byte { if !functionSignatureRegex.MatchString(functionSignature) { panic(fmt.Errorf("invalid function signature: %q", functionSignature)) @@ -25,25 +26,25 @@ func CalculateFunctionSelector(functionSignature string) []byte { return hash[:4] } -// deductGas checks if [suppliedGas] is sufficient against [requiredGas] and deducts [requiredGas] from [suppliedGas]. -func deductGas(suppliedGas uint64, requiredGas uint64) (uint64, error) { +// DeductGas checks if [suppliedGas] is sufficient against [requiredGas] and deducts [requiredGas] from [suppliedGas]. +func DeductGas(suppliedGas uint64, requiredGas uint64) (uint64, error) { if suppliedGas < requiredGas { return 0, vmerrs.ErrOutOfGas } return suppliedGas - requiredGas, nil } -// packOrderedHashesWithSelector packs the function selector and ordered list of hashes into [dst] +// PackOrderedHashesWithSelector packs the function selector and ordered list of hashes into [dst] // byte slice. // assumes that [dst] has sufficient room for [functionSelector] and [hashes]. -func packOrderedHashesWithSelector(dst []byte, functionSelector []byte, hashes []common.Hash) error { +func PackOrderedHashesWithSelector(dst []byte, functionSelector []byte, hashes []common.Hash) error { copy(dst[:len(functionSelector)], functionSelector) - return packOrderedHashes(dst[len(functionSelector):], hashes) + return PackOrderedHashes(dst[len(functionSelector):], hashes) } -// packOrderedHashes packs the ordered list of [hashes] into the [dst] byte buffer. +// PackOrderedHashes packs the ordered list of [hashes] into the [dst] byte buffer. // assumes that [dst] has sufficient space to pack [hashes] or else this function will panic. -func packOrderedHashes(dst []byte, hashes []common.Hash) error { +func PackOrderedHashes(dst []byte, hashes []common.Hash) error { if len(dst) != len(hashes)*common.HashLength { return fmt.Errorf("destination byte buffer has insufficient length (%d) for %d hashes", len(dst), len(hashes)) } @@ -60,10 +61,10 @@ func packOrderedHashes(dst []byte, hashes []common.Hash) error { return nil } -// returnPackedHash returns packed the byte slice with common.HashLength from [packed] +// PackedHash returns packed the byte slice with common.HashLength from [packed] // at the given [index]. // Assumes that [packed] is composed entirely of packed 32 byte segments. -func returnPackedHash(packed []byte, index int) []byte { +func PackedHash(packed []byte, index int) []byte { start := common.HashLength * index end := start + common.HashLength return packed[start:end] diff --git a/tests/e2e/utils/evm_client.go b/tests/e2e/utils/evm_client.go index 65dbe1785f..6f8c0a3950 100644 --- a/tests/e2e/utils/evm_client.go +++ b/tests/e2e/utils/evm_client.go @@ -15,7 +15,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/ethclient" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/txallowlist" "github.com/ethereum/go-ethereum/common" ) @@ -167,7 +167,7 @@ func (ec *EvmClient) TransferTx( if err := ec.ethClient.SendTransaction(ctx, signedTx); err != nil { log.Printf("failed to send transaction: %v (key address %s)", err, sender) - if strings.Contains(err.Error(), precompile.ErrSenderAddressNotAllowListed.Error()) { + if strings.Contains(err.Error(), txallowlist.ErrSenderAddressNotAllowListed.Error()) { return nil, err } From 357d9e6a8f2b7a577f0ddbe8b4761156edc144d0 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 12 Jan 2023 18:20:53 +0300 Subject: [PATCH 05/23] rename fee manager config struct (#427) * rename struct * rename fee config managers to fee managers * fix comments --- commontype/fee_config.go | 2 +- contract-examples/README.md | 2 +- .../contracts/ExampleFeeManager.sol | 2 +- core/blockchain_reader.go | 4 +- core/stateful_precompile_test.go | 18 +++---- core/test_blockchain.go | 6 +-- core/tx_pool.go | 4 +- eth/gasprice/gasprice.go | 2 +- eth/gasprice/gasprice_test.go | 2 +- miner/worker.go | 2 +- params/precompile_config.go | 8 ++-- plugin/evm/vm_test.go | 12 ++--- precompile/feemanager/config.go | 48 +++++++++---------- precompile/feemanager/config_test.go | 2 +- precompile/feemanager/contract.go | 38 +++++++-------- precompile/params.go | 4 +- 16 files changed, 78 insertions(+), 78 deletions(-) diff --git a/commontype/fee_config.go b/commontype/fee_config.go index f09eceeff9..0e4179e0d1 100644 --- a/commontype/fee_config.go +++ b/commontype/fee_config.go @@ -16,7 +16,7 @@ import ( // // The dynamic fee algorithm simply increases fees when the network is operating at a utilization level above the target and decreases fees // when the network is operating at a utilization level below the target. -// This struct is used by params.Config and precompile.FeeConfigManager +// This struct is used by params.Config and precompile.FeeManager // any modification of this struct has direct affect on the precompiled contract // and changes should be carefully handled in the precompiled contract code. type FeeConfig struct { diff --git a/contract-examples/README.md b/contract-examples/README.md index 04b6f22c40..ace8d51138 100644 --- a/contract-examples/README.md +++ b/contract-examples/README.md @@ -44,7 +44,7 @@ $ yarn `ExampleDeployerList` shows how `ContractDeployerAllowList` precompile can be used in a smart contract. It uses `IAllowList` to interact with `ContractDeployerAllowList` precompile. When the precompile is activated only those allowed can deploy contracts. -`ExampleFeeManager` shows how a contract can change fee configuration with the `FeeConfigManager` precompile. +`ExampleFeeManager` shows how a contract can change fee configuration with the `FeeManager` precompile. All of these `NativeMinter`, `FeeManager` and `AllowList` contracts should be enabled by a chain config in genesis or as an upgrade. See the example genesis under [Tests](#tests) section. diff --git a/contract-examples/contracts/ExampleFeeManager.sol b/contract-examples/contracts/ExampleFeeManager.sol index dcc5828689..cd22b04f24 100644 --- a/contract-examples/contracts/ExampleFeeManager.sol +++ b/contract-examples/contracts/ExampleFeeManager.sol @@ -6,7 +6,7 @@ import "@openzeppelin/contracts/access/Ownable.sol"; import "./AllowList.sol"; import "./IFeeManager.sol"; -// ExampleFeeManager shows how FeeConfigManager precompile can be used in a smart contract +// ExampleFeeManager shows how FeeManager precompile can be used in a smart contract // All methods of [allowList] can be directly called. There are example calls as tasks in hardhat.config.ts file. contract ExampleFeeManager is AllowList { // Precompiled Fee Manager Contract Address diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index 5b0b943269..ca3dbc528f 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -344,13 +344,13 @@ func (bc *BlockChain) SubscribeAcceptedTransactionEvent(ch chan<- NewTxsEvent) e } // GetFeeConfigAt returns the fee configuration and the last changed block number at [parent]. -// If FeeConfigManager is activated at [parent], returns the fee config in the precompile contract state. +// If FeeManager is activated at [parent], returns the fee config in the precompile contract state. // Otherwise returns the fee config in the chain config. // Assumes that a valid configuration is stored when the precompile is activated. func (bc *BlockChain) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) { config := bc.Config() bigTime := new(big.Int).SetUint64(parent.Time) - if !config.IsPrecompileEnabled(precompile.FeeConfigManagerAddress, bigTime) { + if !config.IsPrecompileEnabled(precompile.FeeManagerAddress, bigTime) { return config.FeeConfig, common.Big0, nil } diff --git a/core/stateful_precompile_test.go b/core/stateful_precompile_test.go index 0fee8976d3..5f4f868918 100644 --- a/core/stateful_precompile_test.go +++ b/core/stateful_precompile_test.go @@ -711,14 +711,14 @@ func TestContractNativeMinterRun(t *testing.T) { } } -func TestFeeConfigManagerRun(t *testing.T) { +func TestFeeManagerRun(t *testing.T) { type test struct { caller common.Address preCondition func(t *testing.T, state *state.StateDB) input func() []byte suppliedGas uint64 readOnly bool - config *feemanager.FeeConfigManagerConfig + config *feemanager.FeeManagerConfig expectedRes []byte expectedErr string @@ -772,7 +772,7 @@ func TestFeeConfigManagerRun(t *testing.T) { suppliedGas: feemanager.SetFeeConfigGasCost, readOnly: false, expectedRes: nil, - config: &feemanager.FeeConfigManagerConfig{ + config: &feemanager.FeeManagerConfig{ InitialFeeConfig: &testFeeConfig, }, expectedErr: "cannot be greater than maxBlockGasCost", @@ -828,7 +828,7 @@ func TestFeeConfigManagerRun(t *testing.T) { return feemanager.PackGetFeeConfigInput() }, suppliedGas: feemanager.GetFeeConfigGasCost, - config: &feemanager.FeeConfigManagerConfig{ + config: &feemanager.FeeManagerConfig{ InitialFeeConfig: &testFeeConfig, }, readOnly: true, @@ -923,7 +923,7 @@ func TestFeeConfigManagerRun(t *testing.T) { readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := feemanager.GetFeeConfigManagerStatus(state, noRoleAddr) + res := feemanager.GetFeeManagerStatus(state, noRoleAddr) require.Equal(t, allowlist.AllowListEnabled, res) }, }, @@ -946,9 +946,9 @@ func TestFeeConfigManagerRun(t *testing.T) { require.NoError(t, err) // Set up the state so that each address has the expected permissions at the start. - feemanager.SetFeeConfigManagerStatus(state, adminAddr, allowlist.AllowListAdmin) - feemanager.SetFeeConfigManagerStatus(state, enabledAddr, allowlist.AllowListEnabled) - feemanager.SetFeeConfigManagerStatus(state, noRoleAddr, allowlist.AllowListNoRole) + feemanager.SetFeeManagerStatus(state, adminAddr, allowlist.AllowListAdmin) + feemanager.SetFeeManagerStatus(state, enabledAddr, allowlist.AllowListEnabled) + feemanager.SetFeeManagerStatus(state, noRoleAddr, allowlist.AllowListNoRole) if test.preCondition != nil { test.preCondition(t, state) @@ -958,7 +958,7 @@ func TestFeeConfigManagerRun(t *testing.T) { if test.config != nil { test.config.Configure(params.TestChainConfig, state, blockContext) } - ret, remainingGas, err := feemanager.FeeConfigManagerPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext, snowContext: snow.DefaultContextTest()}, test.caller, precompile.FeeConfigManagerAddress, test.input(), test.suppliedGas, test.readOnly) + ret, remainingGas, err := feemanager.FeeManagerPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext, snowContext: snow.DefaultContextTest()}, test.caller, precompile.FeeManagerAddress, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { require.ErrorContains(t, err, test.expectedErr) } else { diff --git a/core/test_blockchain.go b/core/test_blockchain.go index 1f66a44b82..2284fee91b 100644 --- a/core/test_blockchain.go +++ b/core/test_blockchain.go @@ -1643,7 +1643,7 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, chainC tx := types.NewTx(&types.DynamicFeeTx{ ChainID: params.TestChainConfig.ChainID, Nonce: gen.TxNonce(addr1), - To: &precompile.FeeConfigManagerAddress, + To: &precompile.FeeManagerAddress, Gas: 3_000_000, Value: common.Big0, GasFeeCap: feeCap, @@ -1658,7 +1658,7 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, chainC gen.AddTx(signedTx) }, verifyState: func(sdb *state.StateDB) error { - res := feemanager.GetFeeConfigManagerStatus(sdb, addr1) + res := feemanager.GetFeeManagerStatus(sdb, addr1) assert.Equal(allowlist.AllowListAdmin, res) storedConfig := feemanager.GetStoredFeeConfig(sdb) @@ -1670,7 +1670,7 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, chainC return nil }, verifyGenesis: func(sdb *state.StateDB) { - res := feemanager.GetFeeConfigManagerStatus(sdb, addr1) + res := feemanager.GetFeeManagerStatus(sdb, addr1) assert.Equal(allowlist.AllowListAdmin, res) feeConfig, _, err := blockchain.GetFeeConfigAt(blockchain.Genesis().Header()) diff --git a/core/tx_pool.go b/core/tx_pool.go index e6e49047b2..1f92bc0ce5 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -1443,10 +1443,10 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { // when we reset txPool we should explicitly check if fee struct for min base fee has changed // so that we can correctly drop txs with < minBaseFee from tx pool. // TODO: this should be checking IsSubnetEVM since we also support minimumFee for SubnetEVM - // without requiring FeeConfigManager is enabled. + // without requiring FeeManager is enabled. // This is already being set by SetMinFee when gas price updater starts. // However tests are currently failing if we change this check to IsSubnetEVM. - if pool.chainconfig.IsPrecompileEnabled(precompile.FeeConfigManagerAddress, new(big.Int).SetUint64(newHead.Time)) { + if pool.chainconfig.IsPrecompileEnabled(precompile.FeeManagerAddress, new(big.Int).SetUint64(newHead.Time)) { feeConfig, _, err := pool.chain.GetFeeConfigAt(newHead) if err != nil { log.Error("Failed to get fee config state", "err", err, "root", newHead.Root) diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 48e3b938ea..63d1cb3a59 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -318,7 +318,7 @@ func (oracle *Oracle) suggestDynamicFees(ctx context.Context) (*big.Int, *big.In feeLastChangedAt *big.Int feeConfig commontype.FeeConfig ) - if oracle.backend.ChainConfig().IsPrecompileEnabled(precompile.FeeConfigManagerAddress, new(big.Int).SetUint64(head.Time)) { + if oracle.backend.ChainConfig().IsPrecompileEnabled(precompile.FeeManagerAddress, new(big.Int).SetUint64(head.Time)) { feeConfig, feeLastChangedAt, err = oracle.backend.GetFeeConfigAt(head) if err != nil { return nil, nil, err diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index 0ed41c7df9..3acc68d20d 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -463,7 +463,7 @@ func TestSuggestGasPriceAfterFeeConfigUpdate(t *testing.T) { tx := types.NewTx(&types.DynamicFeeTx{ ChainID: chainConfig.ChainID, Nonce: b.TxNonce(addr), - To: &precompile.FeeConfigManagerAddress, + To: &precompile.FeeManagerAddress, Gas: chainConfig.FeeConfig.GasLimit.Uint64(), Value: common.Big0, GasFeeCap: chainConfig.FeeConfig.MinBaseFee, // give low fee, it should work since we still haven't applied high fees diff --git a/miner/worker.go b/miner/worker.go index 32453353cf..bed65cb814 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -128,7 +128,7 @@ func (w *worker) commitNewWork() (*types.Block, error) { bigTimestamp := new(big.Int).SetUint64(timestamp) var gasLimit uint64 - // The fee config manager relies on the state of the parent block to set the fee config + // The fee manager relies on the state of the parent block to set the fee config // because the fee config may be changed by the current block. feeConfig, _, err := w.chain.GetFeeConfigAt(parent.Header()) if err != nil { diff --git a/params/precompile_config.go b/params/precompile_config.go index e19c3041f7..a43133f3f9 100644 --- a/params/precompile_config.go +++ b/params/precompile_config.go @@ -25,7 +25,7 @@ type PrecompileUpgrade struct { ContractDeployerAllowListConfig *deployerallowlist.ContractDeployerAllowListConfig `json:"contractDeployerAllowListConfig,omitempty"` // Config for the contract deployer allow list precompile ContractNativeMinterConfig *nativeminter.ContractNativeMinterConfig `json:"contractNativeMinterConfig,omitempty"` // Config for the native minter precompile TxAllowListConfig *txallowlist.TxAllowListConfig `json:"txAllowListConfig,omitempty"` // Config for the tx allow list precompile - FeeManagerConfig *feemanager.FeeConfigManagerConfig `json:"feeManagerConfig,omitempty"` // Config for the fee manager precompile + FeeManagerConfig *feemanager.FeeManagerConfig `json:"feeManagerConfig,omitempty"` // Config for the fee manager precompile RewardManagerConfig *rewardmanager.RewardManagerConfig `json:"rewardManagerConfig,omitempty"` // Config for the reward manager precompile // ADD YOUR PRECOMPILE HERE // {YourPrecompile}Config *precompile.{YourPrecompile}Config `json:"{yourPrecompile}Config,omitempty"` @@ -40,7 +40,7 @@ func (p *PrecompileUpgrade) getByAddress(address common.Address) (precompile.Sta return p.ContractNativeMinterConfig, p.ContractNativeMinterConfig != nil case precompile.TxAllowListAddress: return p.TxAllowListConfig, p.TxAllowListConfig != nil - case precompile.FeeConfigManagerAddress: + case precompile.FeeManagerAddress: return p.FeeManagerConfig, p.FeeManagerConfig != nil case precompile.RewardManagerAddress: return p.RewardManagerConfig, p.RewardManagerConfig != nil @@ -184,8 +184,8 @@ func (c *ChainConfig) GetActivePrecompileUpgrade(blockTimestamp *big.Int) Precom if config := c.GetPrecompileConfig(precompile.TxAllowListAddress, blockTimestamp); config != nil && !config.IsDisabled() { pu.TxAllowListConfig = config.(*txallowlist.TxAllowListConfig) } - if config := c.GetPrecompileConfig(precompile.FeeConfigManagerAddress, blockTimestamp); config != nil && !config.IsDisabled() { - pu.FeeManagerConfig = config.(*feemanager.FeeConfigManagerConfig) + if config := c.GetPrecompileConfig(precompile.FeeManagerAddress, blockTimestamp); config != nil && !config.IsDisabled() { + pu.FeeManagerConfig = config.(*feemanager.FeeManagerConfig) } if config := c.GetPrecompileConfig(precompile.RewardManagerAddress, blockTimestamp); config != nil && !config.IsDisabled() { pu.RewardManagerConfig = config.(*rewardmanager.RewardManagerConfig) diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index c96ce4b31b..5010a810c4 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -2411,13 +2411,13 @@ func TestFeeManagerChangeFee(t *testing.T) { } // Check that address 0 is whitelisted and address 1 is not - role := feemanager.GetFeeConfigManagerStatus(genesisState, testEthAddrs[0]) + role := feemanager.GetFeeManagerStatus(genesisState, testEthAddrs[0]) if role != allowlist.AllowListAdmin { - t.Fatalf("Expected fee manager list status to be set to admin: %s, but found: %s", precompile.FeeConfigManagerAddress, role) + t.Fatalf("Expected fee manager list status to be set to admin: %s, but found: %s", precompile.FeeManagerAddress, role) } - role = feemanager.GetFeeConfigManagerStatus(genesisState, testEthAddrs[1]) + role = feemanager.GetFeeManagerStatus(genesisState, testEthAddrs[1]) if role != allowlist.AllowListNoRole { - t.Fatalf("Expected fee manager list status to be set to no role: %s, but found: %s", precompile.FeeConfigManagerAddress, role) + t.Fatalf("Expected fee manager list status to be set to no role: %s, but found: %s", precompile.FeeManagerAddress, role) } // Contract is initialized but no preconfig is given, reader should return genesis fee config feeConfig, lastChangedAt, err := vm.blockChain.GetFeeConfigAt(vm.blockChain.Genesis().Header()) @@ -2435,7 +2435,7 @@ func TestFeeManagerChangeFee(t *testing.T) { tx := types.NewTx(&types.DynamicFeeTx{ ChainID: genesis.Config.ChainID, Nonce: uint64(0), - To: &precompile.FeeConfigManagerAddress, + To: &precompile.FeeManagerAddress, Gas: testLowFeeConfig.GasLimit.Uint64(), Value: common.Big0, GasFeeCap: testLowFeeConfig.MinBaseFee, // give low fee, it should work since we still haven't applied high fees @@ -2471,7 +2471,7 @@ func TestFeeManagerChangeFee(t *testing.T) { tx2 := types.NewTx(&types.DynamicFeeTx{ ChainID: genesis.Config.ChainID, Nonce: uint64(1), - To: &precompile.FeeConfigManagerAddress, + To: &precompile.FeeManagerAddress, Gas: genesis.Config.FeeConfig.GasLimit.Uint64(), Value: common.Big0, GasFeeCap: testLowFeeConfig.MinBaseFee, // this is too low for applied config, should fail diff --git a/precompile/feemanager/config.go b/precompile/feemanager/config.go index bee3acf785..b36a411342 100644 --- a/precompile/feemanager/config.go +++ b/precompile/feemanager/config.go @@ -14,18 +14,18 @@ import ( "github.com/ethereum/go-ethereum/common" ) -// FeeConfigManagerConfig wraps [AllowListConfig] and uses it to implement the StatefulPrecompileConfig -// interface while adding in the FeeConfigManager specific precompile address. -type FeeConfigManagerConfig struct { - allowlist.AllowListConfig // Config for the fee config manager allow list +// FeeManagerConfig wraps [AllowListConfig] and uses it to implement the StatefulPrecompileConfig +// interface while adding in the FeeManager specific precompile address. +type FeeManagerConfig struct { + allowlist.AllowListConfig precompile.UpgradeableConfig InitialFeeConfig *commontype.FeeConfig `json:"initialFeeConfig,omitempty"` // initial fee config to be immediately activated } // NewFeeManagerConfig returns a config for a network upgrade at [blockTimestamp] that enables -// FeeConfigManager with the given [admins] and [enableds] as members of the allowlist with [initialConfig] as initial fee config if specified. -func NewFeeManagerConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialConfig *commontype.FeeConfig) *FeeConfigManagerConfig { - return &FeeConfigManagerConfig{ +// FeeManager with the given [admins] and [enableds] as members of the allowlist with [initialConfig] as initial fee config if specified. +func NewFeeManagerConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialConfig *commontype.FeeConfig) *FeeManagerConfig { + return &FeeManagerConfig{ AllowListConfig: allowlist.AllowListConfig{ AllowListAdmins: admins, EnabledAddresses: enableds, @@ -36,9 +36,9 @@ func NewFeeManagerConfig(blockTimestamp *big.Int, admins []common.Address, enabl } // NewDisableFeeManagerConfig returns config for a network upgrade at [blockTimestamp] -// that disables FeeConfigManager. -func NewDisableFeeManagerConfig(blockTimestamp *big.Int) *FeeConfigManagerConfig { - return &FeeConfigManagerConfig{ +// that disables FeeManager. +func NewDisableFeeManagerConfig(blockTimestamp *big.Int) *FeeManagerConfig { + return &FeeManagerConfig{ UpgradeableConfig: precompile.UpgradeableConfig{ BlockTimestamp: blockTimestamp, Disable: true, @@ -46,15 +46,15 @@ func NewDisableFeeManagerConfig(blockTimestamp *big.Int) *FeeConfigManagerConfig } } -// Address returns the address of the fee config manager contract. -func (c *FeeConfigManagerConfig) Address() common.Address { - return precompile.FeeConfigManagerAddress +// Address returns the address of the FeeManager contract. +func (c *FeeManagerConfig) Address() common.Address { + return precompile.FeeManagerAddress } -// Equal returns true if [s] is a [*FeeConfigManagerConfig] and it has been configured identical to [c]. -func (c *FeeConfigManagerConfig) Equal(s precompile.StatefulPrecompileConfig) bool { +// Equal returns true if [s] is a [*FeeManagerConfig] and it has been configured identical to [c]. +func (c *FeeManagerConfig) Equal(s precompile.StatefulPrecompileConfig) bool { // typecast before comparison - other, ok := (s).(*FeeConfigManagerConfig) + other, ok := (s).(*FeeManagerConfig) if !ok { return false } @@ -71,8 +71,8 @@ func (c *FeeConfigManagerConfig) Equal(s precompile.StatefulPrecompileConfig) bo } // Configure configures [state] with the desired admins based on [c]. -func (c *FeeConfigManagerConfig) Configure(chainConfig precompile.ChainConfig, state precompile.StateDB, blockContext precompile.BlockContext) error { - // Store the initial fee config into the state when the fee config manager activates. +func (c *FeeManagerConfig) Configure(chainConfig precompile.ChainConfig, state precompile.StateDB, blockContext precompile.BlockContext) error { + // Store the initial fee config into the state when the fee manager activates. if c.InitialFeeConfig != nil { if err := StoreFeeConfig(state, *c.InitialFeeConfig, blockContext); err != nil { // This should not happen since we already checked this config with Verify() @@ -84,15 +84,15 @@ func (c *FeeConfigManagerConfig) Configure(chainConfig precompile.ChainConfig, s return fmt.Errorf("cannot configure fee config in chain config: %w", err) } } - return c.AllowListConfig.Configure(state, precompile.FeeConfigManagerAddress) + return c.AllowListConfig.Configure(state, precompile.FeeManagerAddress) } // Contract returns the singleton stateful precompiled contract to be used for the fee manager. -func (c *FeeConfigManagerConfig) Contract() precompile.StatefulPrecompiledContract { - return FeeConfigManagerPrecompile +func (c *FeeManagerConfig) Contract() precompile.StatefulPrecompiledContract { + return FeeManagerPrecompile } -func (c *FeeConfigManagerConfig) Verify() error { +func (c *FeeManagerConfig) Verify() error { if err := c.AllowListConfig.Verify(); err != nil { return err } @@ -103,8 +103,8 @@ func (c *FeeConfigManagerConfig) Verify() error { return c.InitialFeeConfig.Verify() } -// String returns a string representation of the FeeConfigManagerConfig. -func (c *FeeConfigManagerConfig) String() string { +// String returns a string representation of the FeeManagerConfig. +func (c *FeeManagerConfig) String() string { bytes, _ := json.Marshal(c) return string(bytes) } diff --git a/precompile/feemanager/config_test.go b/precompile/feemanager/config_test.go index ed45821d6a..1761c3109d 100644 --- a/precompile/feemanager/config_test.go +++ b/precompile/feemanager/config_test.go @@ -61,7 +61,7 @@ func TestVerifyFeeManagerConfig(t *testing.T) { } } -func TestEqualFeeConfigManagerConfig(t *testing.T) { +func TestEqualFeeManagerConfig(t *testing.T) { admins := []common.Address{{1}} enableds := []common.Address{{2}} tests := []struct { diff --git a/precompile/feemanager/contract.go b/precompile/feemanager/contract.go index 760303ecde..5fdc3fb656 100644 --- a/precompile/feemanager/contract.go +++ b/precompile/feemanager/contract.go @@ -39,10 +39,10 @@ const ( ) var ( - _ precompile.StatefulPrecompileConfig = &FeeConfigManagerConfig{} + _ precompile.StatefulPrecompileConfig = &FeeManagerConfig{} // Singleton StatefulPrecompiledContract for setting fee configs by permissioned callers. - FeeConfigManagerPrecompile precompile.StatefulPrecompiledContract = createFeeConfigManagerPrecompile(precompile.FeeConfigManagerAddress) + FeeManagerPrecompile precompile.StatefulPrecompiledContract = createFeeManagerPrecompile(precompile.FeeManagerAddress) setFeeConfigSignature = precompile.CalculateFunctionSelector("setFeeConfig(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)") getFeeConfigSignature = precompile.CalculateFunctionSelector("getFeeConfig()") @@ -53,15 +53,15 @@ var ( ErrCannotChangeFee = errors.New("non-enabled cannot change fee config") ) -// GetFeeConfigManagerStatus returns the role of [address] for the fee config manager list. -func GetFeeConfigManagerStatus(stateDB precompile.StateDB, address common.Address) allowlist.AllowListRole { - return allowlist.GetAllowListStatus(stateDB, precompile.FeeConfigManagerAddress, address) +// GetFeeManagerStatus returns the role of [address] for the FeeManager allowlist. +func GetFeeManagerStatus(stateDB precompile.StateDB, address common.Address) allowlist.AllowListRole { + return allowlist.GetAllowListStatus(stateDB, precompile.FeeManagerAddress, address) } -// SetFeeConfigManagerStatus sets the permissions of [address] to [role] for the -// fee config manager list. assumes [role] has already been verified as valid. -func SetFeeConfigManagerStatus(stateDB precompile.StateDB, address common.Address, role allowlist.AllowListRole) { - allowlist.SetAllowListRole(stateDB, precompile.FeeConfigManagerAddress, address, role) +// SetFeeManagerStatus sets the permissions of [address] to [role] for the +// FeeManager allowlist. +func SetFeeManagerStatus(stateDB precompile.StateDB, address common.Address, role allowlist.AllowListRole) { + allowlist.SetAllowListRole(stateDB, precompile.FeeManagerAddress, address, role) } // PackGetFeeConfigInput packs the getFeeConfig signature @@ -148,7 +148,7 @@ func UnpackFeeConfigInput(input []byte) (commontype.FeeConfig, error) { func GetStoredFeeConfig(stateDB precompile.StateDB) commontype.FeeConfig { feeConfig := commontype.FeeConfig{} for i := minFeeConfigFieldKey; i <= numFeeConfigField; i++ { - val := stateDB.GetState(precompile.FeeConfigManagerAddress, common.Hash{byte(i)}) + val := stateDB.GetState(precompile.FeeManagerAddress, common.Hash{byte(i)}) switch i { case gasLimitKey: feeConfig.GasLimit = new(big.Int).Set(val.Big()) @@ -175,7 +175,7 @@ func GetStoredFeeConfig(stateDB precompile.StateDB) commontype.FeeConfig { } func GetFeeConfigLastChangedAt(stateDB precompile.StateDB) *big.Int { - val := stateDB.GetState(precompile.FeeConfigManagerAddress, feeConfigLastChangedAtKey) + val := stateDB.GetState(precompile.FeeManagerAddress, feeConfigLastChangedAtKey) return val.Big() } @@ -209,14 +209,14 @@ func StoreFeeConfig(stateDB precompile.StateDB, feeConfig commontype.FeeConfig, // This should never encounter an unknown fee config key panic(fmt.Sprintf("unknown fee config key: %d", i)) } - stateDB.SetState(precompile.FeeConfigManagerAddress, common.Hash{byte(i)}, input) + stateDB.SetState(precompile.FeeManagerAddress, common.Hash{byte(i)}, input) } blockNumber := blockContext.Number() if blockNumber == nil { return fmt.Errorf("blockNumber cannot be nil") } - stateDB.SetState(precompile.FeeConfigManagerAddress, feeConfigLastChangedAtKey, common.BigToHash(blockNumber)) + stateDB.SetState(precompile.FeeManagerAddress, feeConfigLastChangedAtKey, common.BigToHash(blockNumber)) return nil } @@ -239,7 +239,7 @@ func setFeeConfig(accessibleState precompile.PrecompileAccessibleState, caller c stateDB := accessibleState.GetStateDB() // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := allowlist.GetAllowListStatus(stateDB, precompile.FeeConfigManagerAddress, caller) + callerStatus := allowlist.GetAllowListStatus(stateDB, precompile.FeeManagerAddress, caller) if !callerStatus.IsEnabled() { return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotChangeFee, caller) } @@ -283,19 +283,19 @@ func getFeeConfigLastChangedAt(accessibleState precompile.PrecompileAccessibleSt return common.BigToHash(lastChangedAt).Bytes(), remainingGas, err } -// createFeeConfigManagerPrecompile returns a StatefulPrecompiledContract +// createFeeManagerPrecompile returns a StatefulPrecompiledContract // with getters and setters for the chain's fee config. Access to the getters/setters // is controlled by an allow list for [precompileAddr]. -func createFeeConfigManagerPrecompile(precompileAddr common.Address) precompile.StatefulPrecompiledContract { - feeConfigManagerFunctions := allowlist.CreateAllowListFunctions(precompileAddr) +func createFeeManagerPrecompile(precompileAddr common.Address) precompile.StatefulPrecompiledContract { + FeeManagerFunctions := allowlist.CreateAllowListFunctions(precompileAddr) setFeeConfigFunc := precompile.NewStatefulPrecompileFunction(setFeeConfigSignature, setFeeConfig) getFeeConfigFunc := precompile.NewStatefulPrecompileFunction(getFeeConfigSignature, getFeeConfig) getFeeConfigLastChangedAtFunc := precompile.NewStatefulPrecompileFunction(getFeeConfigLastChangedAtSignature, getFeeConfigLastChangedAt) - feeConfigManagerFunctions = append(feeConfigManagerFunctions, setFeeConfigFunc, getFeeConfigFunc, getFeeConfigLastChangedAtFunc) + FeeManagerFunctions = append(FeeManagerFunctions, setFeeConfigFunc, getFeeConfigFunc, getFeeConfigLastChangedAtFunc) // Construct the contract with no fallback function. - contract, err := precompile.NewStatefulPrecompileContract(nil, feeConfigManagerFunctions) + contract, err := precompile.NewStatefulPrecompileContract(nil, FeeManagerFunctions) // TODO Change this to be returned as an error after refactoring this precompile // to use the new precompile template. if err != nil { diff --git a/precompile/params.go b/precompile/params.go index 294ab328fa..bfa5e40a4f 100644 --- a/precompile/params.go +++ b/precompile/params.go @@ -31,7 +31,7 @@ var ( ContractDeployerAllowListAddress = common.HexToAddress("0x0200000000000000000000000000000000000000") ContractNativeMinterAddress = common.HexToAddress("0x0200000000000000000000000000000000000001") TxAllowListAddress = common.HexToAddress("0x0200000000000000000000000000000000000002") - FeeConfigManagerAddress = common.HexToAddress("0x0200000000000000000000000000000000000003") + FeeManagerAddress = common.HexToAddress("0x0200000000000000000000000000000000000003") RewardManagerAddress = common.HexToAddress("0x0200000000000000000000000000000000000004") // ADD YOUR PRECOMPILE HERE // {YourPrecompile}Address = common.HexToAddress("0x03000000000000000000000000000000000000??") @@ -40,7 +40,7 @@ var ( ContractDeployerAllowListAddress, ContractNativeMinterAddress, TxAllowListAddress, - FeeConfigManagerAddress, + FeeManagerAddress, RewardManagerAddress, // ADD YOUR PRECOMPILE HERE // YourPrecompileAddress From 1add41a0c8c89cf921913cbf2669c74532244d4b Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 16 Feb 2023 07:32:11 +0300 Subject: [PATCH 06/23] Generalized upgrades rb (#434) * introduce precompiles as registrable modules * add precompile specific contract tests * remove print debug * add unmarshal tests * remove unnecessary func * fix initial disabled value * register all modules in core/evm/contract_stateful * more refactor & test fix * sync template * fix more tests * rename file * add comment * rename * fix linter * use require error contains * remove whitespace * trim mock interface * sort steps * reviews * Update precompile/stateful_precompile_module.go * Update params/precompile_config.go * Update params/precompile_config.go * fix reviews * add new module to configs and group module functions * generalized-upgrades-rb review (#474) * keep genesis disabled fix * nits * nits * nit * review fixes * Update precompile/allowlist/allowlist.go * use address in map * fix linter for embedded keys * update err messages * more err update * remove unnecessary function (#478) * Start work on breaking cyclic dependency (#496) * Update core/state_processor.go * fix reviews * Update precompile/contracts/txallowlist/contract_test.go * Generalized upgrades rb nits0 (#512) * Minor improvements * restore readOnly * more updates * Add back readOnly to allow list tests --- accounts/abi/bind/precompile_bind.go | 8 +- accounts/abi/bind/precompile_bind_test.go | 7 +- .../abi/bind/precompile_config_template.go | 69 +- .../abi/bind/precompile_contract_template.go | 58 +- cmd/precompilegen/main.go | 6 +- .../contracts/ExampleTxAllowList.sol | 6 +- core/blockchain_reader.go | 9 +- core/genesis.go | 2 +- core/genesis_test.go | 16 +- core/state_processor.go | 52 +- core/state_processor_test.go | 6 +- core/state_transition.go | 7 +- core/stateful_precompile_test.go | 1296 ----------------- core/test_blockchain.go | 37 +- core/tx_pool.go | 12 +- core/tx_pool_test.go | 13 + core/vm/contracts.go | 24 +- core/vm/contracts_stateful.go | 8 +- core/vm/evm.go | 29 +- eth/gasprice/gasprice.go | 4 +- eth/gasprice/gasprice_test.go | 9 +- internal/ethapi/api.go | 8 +- miner/worker.go | 2 +- params/chain_config_precompiles.go | 36 + params/config.go | 84 +- params/config_test.go | 70 + params/precompile_config.go | 300 ---- params/precompile_config_test.go | 162 ++- params/precompile_upgrade.go | 260 ++++ ...fig_test.go => precompile_upgrade_test.go} | 72 +- plugin/evm/vm.go | 3 + plugin/evm/vm_test.go | 77 +- plugin/evm/vm_upgrade_bytes_test.go | 11 +- precompile/allowlist/allow_list.go | 240 --- precompile/allowlist/allow_list_test.go | 283 ++++ precompile/allowlist/allowlist.go | 172 +++ precompile/allowlist/config.go | 79 + precompile/allowlist/config_test.go | 97 ++ .../allowlist/{allow_list_role.go => role.go} | 34 +- precompile/config/config.go | 27 + precompile/config/mock_config.go | 45 + precompile/{ => config}/upgradeable.go | 21 +- precompile/{ => contract}/contract.go | 12 +- .../{interface.go => contract/interfaces.go} | 50 +- precompile/contract/mock_interfaces.go | 57 + precompile/{ => contract}/utils.go | 21 +- precompile/{ => contract}/utils_test.go | 2 +- .../contracts/deployerallowlist/config.go | 58 + .../deployerallowlist/config_test.go | 32 +- .../contracts/deployerallowlist/contract.go | 26 + .../deployerallowlist/contract_test.go | 216 +++ .../contracts/deployerallowlist/module.go | 49 + precompile/contracts/feemanager/config.go | 79 + .../{ => contracts}/feemanager/config_test.go | 38 +- .../{ => contracts}/feemanager/contract.go | 82 +- .../contracts/feemanager/contract_test.go | 273 ++++ precompile/contracts/feemanager/module.go | 61 + precompile/contracts/nativeminter/config.go | 95 ++ .../nativeminter/config_test.go | 44 +- .../{ => contracts}/nativeminter/contract.go | 38 +- .../contracts/nativeminter/contract_test.go | 193 +++ precompile/contracts/nativeminter/module.go | 57 + precompile/contracts/rewardmanager/config.go | 118 ++ .../rewardmanager/config_test.go | 38 +- .../rewardmanager/contract.abi | 0 .../{ => contracts}/rewardmanager/contract.go | 102 +- .../contracts/rewardmanager/contract_test.go | 318 ++++ precompile/contracts/rewardmanager/module.go | 61 + precompile/contracts/txallowlist/config.go | 56 + .../txallowlist/config_test.go | 34 +- precompile/contracts/txallowlist/contract.go | 25 + .../contracts/txallowlist/contract_test.go | 214 +++ precompile/contracts/txallowlist/module.go | 49 + precompile/deployerallowlist/config.go | 76 - precompile/deployerallowlist/contract.go | 26 - precompile/feemanager/config.go | 110 -- precompile/mock_interface.go | 115 -- precompile/modules/module.go | 37 + precompile/modules/registerer.go | 93 ++ precompile/modules/registerer_test.go | 44 + precompile/nativeminter/config.go | 126 -- precompile/params.go | 82 -- precompile/registry/registry.go | 41 + precompile/rewardmanager/config.go | 153 -- precompile/stateful_precompile_config.go | 57 - precompile/txallowlist/config.go | 76 - precompile/txallowlist/contract.go | 33 - tests/e2e/utils/evm_client.go | 4 +- .../address_range.go | 5 +- vmerrs/vmerrs.go | 31 +- 90 files changed, 4127 insertions(+), 3241 deletions(-) delete mode 100644 core/stateful_precompile_test.go create mode 100644 params/chain_config_precompiles.go delete mode 100644 params/precompile_config.go create mode 100644 params/precompile_upgrade.go rename params/{upgrade_config_test.go => precompile_upgrade_test.go} (64%) delete mode 100644 precompile/allowlist/allow_list.go create mode 100644 precompile/allowlist/allow_list_test.go create mode 100644 precompile/allowlist/allowlist.go create mode 100644 precompile/allowlist/config.go create mode 100644 precompile/allowlist/config_test.go rename precompile/allowlist/{allow_list_role.go => role.go} (56%) create mode 100644 precompile/config/config.go create mode 100644 precompile/config/mock_config.go rename precompile/{ => config}/upgradeable.go (59%) rename precompile/{ => contract}/contract.go (80%) rename precompile/{interface.go => contract/interfaces.go} (67%) create mode 100644 precompile/contract/mock_interfaces.go rename precompile/{ => contract}/utils.go (85%) rename precompile/{ => contract}/utils_test.go (98%) create mode 100644 precompile/contracts/deployerallowlist/config.go rename precompile/{ => contracts}/deployerallowlist/config_test.go (56%) create mode 100644 precompile/contracts/deployerallowlist/contract.go create mode 100644 precompile/contracts/deployerallowlist/contract_test.go create mode 100644 precompile/contracts/deployerallowlist/module.go create mode 100644 precompile/contracts/feemanager/config.go rename precompile/{ => contracts}/feemanager/config_test.go (65%) rename precompile/{ => contracts}/feemanager/contract.go (68%) create mode 100644 precompile/contracts/feemanager/contract_test.go create mode 100644 precompile/contracts/feemanager/module.go create mode 100644 precompile/contracts/nativeminter/config.go rename precompile/{ => contracts}/nativeminter/config_test.go (67%) rename precompile/{ => contracts}/nativeminter/contract.go (60%) create mode 100644 precompile/contracts/nativeminter/contract_test.go create mode 100644 precompile/contracts/nativeminter/module.go create mode 100644 precompile/contracts/rewardmanager/config.go rename precompile/{ => contracts}/rewardmanager/config_test.go (61%) rename precompile/{ => contracts}/rewardmanager/contract.abi (100%) rename precompile/{ => contracts}/rewardmanager/contract.go (67%) create mode 100644 precompile/contracts/rewardmanager/contract_test.go create mode 100644 precompile/contracts/rewardmanager/module.go create mode 100644 precompile/contracts/txallowlist/config.go rename precompile/{ => contracts}/txallowlist/config_test.go (60%) create mode 100644 precompile/contracts/txallowlist/contract.go create mode 100644 precompile/contracts/txallowlist/contract_test.go create mode 100644 precompile/contracts/txallowlist/module.go delete mode 100644 precompile/deployerallowlist/config.go delete mode 100644 precompile/deployerallowlist/contract.go delete mode 100644 precompile/feemanager/config.go delete mode 100644 precompile/mock_interface.go create mode 100644 precompile/modules/module.go create mode 100644 precompile/modules/registerer.go create mode 100644 precompile/modules/registerer_test.go delete mode 100644 precompile/nativeminter/config.go delete mode 100644 precompile/params.go create mode 100644 precompile/registry/registry.go delete mode 100644 precompile/rewardmanager/config.go delete mode 100644 precompile/stateful_precompile_config.go delete mode 100644 precompile/txallowlist/config.go delete mode 100644 precompile/txallowlist/contract.go rename precompile/reserved_range.go => utils/address_range.go (82%) diff --git a/accounts/abi/bind/precompile_bind.go b/accounts/abi/bind/precompile_bind.go index d31d9d0fef..20581b0336 100644 --- a/accounts/abi/bind/precompile_bind.go +++ b/accounts/abi/bind/precompile_bind.go @@ -43,11 +43,13 @@ func PrecompileBind(types []string, abis []string, bytecodes []string, fsigs []m configBind, err := bindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, configHook) if err != nil { - return "", "", err + return "", "", fmt.Errorf("failed to generate config binding: %w", err) } contractBind, err := bindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, contractHook) - - return configBind, contractBind, err + if err != nil { + return "", "", fmt.Errorf("failed to generate contract binding: %w", err) + } + return configBind, contractBind, nil } func createPrecompileHook(abifilename string, template string) BindHook { diff --git a/accounts/abi/bind/precompile_bind_test.go b/accounts/abi/bind/precompile_bind_test.go index 23f1503142..84e15b242a 100644 --- a/accounts/abi/bind/precompile_bind_test.go +++ b/accounts/abi/bind/precompile_bind_test.go @@ -28,6 +28,8 @@ package bind import ( "testing" + + "github.com/stretchr/testify/require" ) var bindFailedTests = []struct { @@ -100,10 +102,7 @@ func golangBindingsFailure(t *testing.T) { if err == nil { t.Fatalf("test %d: no error occurred but was expected", i) } - - if tt.errorMsg != err.Error() { - t.Fatalf("test %d: expected Err %s but got actual Err: %s", i, tt.errorMsg, err.Error()) - } + require.ErrorContains(t, err, tt.errorMsg) }) } } diff --git a/accounts/abi/bind/precompile_config_template.go b/accounts/abi/bind/precompile_config_template.go index 52ff02d268..dabd5fed86 100644 --- a/accounts/abi/bind/precompile_config_template.go +++ b/accounts/abi/bind/precompile_config_template.go @@ -14,14 +14,14 @@ const tmplSourcePrecompileConfigGo = ` // For testing take a look at other precompile tests in core/stateful_precompile_test.go and config_test.go in other precompile folders. /* General guidelines for precompile development: -1- Read the comment and set a suitable contract address in precompile/params.go. E.g: - {{.Contract.Type}}Address = common.HexToAddress("ASUITABLEHEXADDRESS") -2- Set gas costs in contract.go -3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. +1- Read the comment and set a suitable contract address in generated contract.go. E.g: + ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") +2- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. Typically, custom codes are required in only those areas. -4- Add your upgradable config in params/precompile_config.go -5- Add your precompile upgrade in params/config.go -6- Add your config unit test in {generatedpkg}/config_test.go +3- Set gas costs in generated contract.go +4- Register your precompile module in params/precompile_modules.go +5- Add your config unit tests under generated package config_test.go +6- Add your contract unit tests under tests/statefulprecompiles/{precompilename}_test.go 7- Add your solidity interface and test contract to contract-examples/contracts 8- Write solidity tests for your precompile in contract-examples/test 9- Create your genesis with your precompile enabled in tests/e2e/genesis/ @@ -33,24 +33,27 @@ Typically, custom codes are required in only those areas. package {{.Package}} import ( - "encoding/json" "math/big" "github.com/ava-labs/subnet-evm/precompile" + {{- if .Contract.AllowList}} + "github.com/ava-labs/subnet-evm/precompile/allowlist" + {{- end}} "github.com/ethereum/go-ethereum/common" ) -{{$contract := .Contract}} -var ( - _ precompile.StatefulPrecompileConfig = &{{.Contract.Type}}Config{} -) +var _ precompile.StatefulPrecompileConfig = &{{.Contract.Type}}Config{} + +// ConfigKey is the key used in json config files to specify this precompile config. +// Must be unique across all precompiles. +const ConfigKey = "{{decapitalise .Contract.Type}}Config" // {{.Contract.Type}}Config implements the StatefulPrecompileConfig // interface while adding in the {{.Contract.Type}} specific precompile address. type {{.Contract.Type}}Config struct { {{- if .Contract.AllowList}} - precompile.AllowListConfig + allowlist.AllowListConfig {{- end}} precompile.UpgradeableConfig } @@ -81,7 +84,7 @@ type {{capitalise .Normalized.Name}}Output struct{ // {{.Contract.Type}} {{if .Contract.AllowList}} with the given [admins] as members of the allowlist {{end}}. func New{{.Contract.Type}}Config(blockTimestamp *big.Int{{if .Contract.AllowList}}, admins []common.Address{{end}}) *{{.Contract.Type}}Config { return &{{.Contract.Type}}Config{ - {{if .Contract.AllowList}}AllowListConfig: precompile.AllowListConfig{AllowListAdmins: admins},{{end}} + {{if .Contract.AllowList}}AllowListConfig: allowlist.AllowListConfig{AdminAddresses: admins},{{end}} UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: blockTimestamp}, } } @@ -125,27 +128,41 @@ func (c *{{.Contract.Type}}Config) Equal(s precompile.StatefulPrecompileConfig) return equals } -// Address returns the address of the {{.Contract.Type}}. Addresses reside under the precompile/params.go -// Select a non-conflicting address and set it in the params.go. -func (c *{{.Contract.Type}}Config) Address() common.Address { - return {{.Contract.Type}}Address -} - // Configure configures [state] with the initial configuration. func (c *{{.Contract.Type}}Config) Configure(_ precompile.ChainConfig, state precompile.StateDB, _ precompile.BlockContext) error { - {{if .Contract.AllowList}}c.AllowListConfig.Configure(state, {{.Contract.Type}}Address){{end}} + {{if .Contract.AllowList}}c.AllowListConfig.Configure(state, ContractAddress){{end}} // CUSTOM CODE STARTS HERE return nil } + +// Required module functions for {{.Contract.Type}}Config +// These functions mostly do not require any custom code. + +// NewModule returns a new module for {{.Contract.Type}}. +func NewModule() precompile.StatefulPrecompileModule { + return &{{.Contract.Type}}Config{} +} + +// Address returns the address of the {{.Contract.Type}}. +// Select a non-conflicting address and set it in generated contract.go +func ({{.Contract.Type}}Config) Address() common.Address { + return ContractAddress +} + // Contract returns the singleton stateful precompiled contract to be used for {{.Contract.Type}}. -func (c *{{.Contract.Type}}Config) Contract() precompile.StatefulPrecompiledContract { +func ({{.Contract.Type}}Config) Contract() precompile.StatefulPrecompiledContract { return {{.Contract.Type}}Precompile } -// String returns a string representation of the {{.Contract.Type}}Config. -func (c *{{.Contract.Type}}Config) String() string { - bytes, _ := json.Marshal(c) - return string(bytes) +// Key returns the key used in json config files to specify this precompile config. +func ({{.Contract.Type}}Config) Key() string { + return ConfigKey +} + +// New returns a new {{.Contract.Type}}Config. +// This is used by the json parser to create a new instance of the {{.Contract.Type}}Config. +func ({{.Contract.Type}}Config) NewConfig() precompile.StatefulPrecompileConfig { + return new({{.Contract.Type}}Config) } ` diff --git a/accounts/abi/bind/precompile_contract_template.go b/accounts/abi/bind/precompile_contract_template.go index 6dd7967b3e..47c3a04188 100644 --- a/accounts/abi/bind/precompile_contract_template.go +++ b/accounts/abi/bind/precompile_contract_template.go @@ -20,23 +20,23 @@ type tmplPrecompileContract struct { // tmplSourcePrecompileContractGo is the Go precompiled contract source template. const tmplSourcePrecompileContractGo = ` // Code generated -// This file is a generated precompile contract with stubbed abstract functions. +// This file is a generated precompile contract config with stubbed abstract functions. // The file is generated by a template. Please inspect every code and comment in this file before use. // There are some must-be-done changes waiting in the file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. // Additionally there are other files you need to edit to activate your precompile. // These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". -// For testing take a look at other precompile tests in core/stateful_precompile_test.go +// For testing take a look at other precompile tests in tests/statefulprecompiles/ and config_test.go in other precompile folders. /* General guidelines for precompile development: -1- Read the comment and set a suitable contract address in precompile/params.go. E.g: - {{.Contract.Type}}Address = common.HexToAddress("ASUITABLEHEXADDRESS") -2- Set gas costs in contract.go +1- Read the comment and set a suitable contract address in generated contract.go. E.g: + ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") +2- Set gas costs in generated contract.go 3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. Typically, custom codes are required in only those areas. -4- Add your upgradable config in params/precompile_config.go -5- Add your precompile upgrade in params/config.go -6- Add your config unit test in {generatedpkg}/config_test.go +4- Register your precompile module in params/precompile_modules.go +5- Add your config unit tests under generated package config_test.go +6- Add your contract unit tests under tests/statefulprecompiles/{precompilename}_test.go 7- Add your solidity interface and test contract to contract-examples/contracts 8- Write solidity tests for your precompile in contract-examples/test 9- Create your genesis with your precompile enabled in tests/e2e/genesis/ @@ -48,7 +48,6 @@ Typically, custom codes are required in only those areas. package {{.Package}} import ( - "encoding/json" "math/big" "errors" "fmt" @@ -56,6 +55,9 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/precompile" + {{- if .Contract.AllowList}} + "github.com/ava-labs/subnet-evm/precompile/allowlist" + {{- end}} "github.com/ava-labs/subnet-evm/vmerrs" _ "embed" @@ -77,14 +79,16 @@ const ( var ( _ = errors.New _ = big.NewInt - _ = strings.NewReader - _ = fmt.Printf - _ = json.Unmarshal ) {{$contract := .Contract}} // Singleton StatefulPrecompiledContract and signatures. var ( + // ContractAddress is the defined address of the precompile contract. + // This should be unique across all precompile contracts. + // See params/precompile_modules.go for registered precompile contracts and more information. + ContractAddress = common.HexToAddress("{ASUITABLEHEXADDRESS}") // SET A SUITABLE HEX ADDRESS HERE + {{- range .Contract.Funcs}} {{- if not .Original.IsConstant | and $contract.AllowList}} @@ -107,10 +111,6 @@ var ( {{.Contract.Type}}ABI abi.ABI // will be initialized by init function {{.Contract.Type}}Precompile precompile.StatefulPrecompiledContract // will be initialized by init function - - // CUSTOM CODE STARTS HERE - // THIS SHOULD BE MOVED TO precompile/params.go with a suitable hex address. - {{.Contract.Type}}Address = common.HexToAddress("ASUITABLEHEXADDRESS") ) {{$structs := .Structs}} @@ -142,7 +142,7 @@ func init() { } {{.Contract.Type}}ABI = parsed - {{.Contract.Type}}Precompile, err = create{{.Contract.Type}}Precompile({{.Contract.Type}}Address) + {{.Contract.Type}}Precompile, err = create{{.Contract.Type}}Precompile() if err != nil { panic(err) } @@ -150,18 +150,18 @@ func init() { {{if .Contract.AllowList}} // Get{{.Contract.Type}}AllowListStatus returns the role of [address] for the {{.Contract.Type}} list. -func Get{{.Contract.Type}}AllowListStatus(stateDB precompile.StateDB, address common.Address) precompile.AllowListRole { - return precompile.GetAllowListStatus(stateDB, {{.Contract.Type}}Address, address) +func Get{{.Contract.Type}}AllowListStatus(stateDB precompile.StateDB, address common.Address) allowlist.AllowListRole { + return allowlist.GetAllowListStatus(stateDB, ContractAddress, address) } // Set{{.Contract.Type}}AllowListStatus sets the permissions of [address] to [role] for the // {{.Contract.Type}} list. Assumes [role] has already been verified as valid. -// This stores the [role] in the contract storage with address [{{.Contract.Type}}Address] +// This stores the [role] in the contract storage with address [ContractAddress] // and [address] hash. It means that any reusage of the [address] key for different value // conflicts with the same slot [role] is stored. // Precompile implementations must use a different key than [address] for their storage. -func Set{{.Contract.Type}}AllowListStatus(stateDB precompile.StateDB, address common.Address, role precompile.AllowListRole) { - precompile.SetAllowListRole(stateDB, {{.Contract.Type}}Address, address, role) +func Set{{.Contract.Type}}AllowListStatus(stateDB precompile.StateDB, address common.Address, role allowlist.AllowListRole) { + allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) } {{end}} @@ -257,8 +257,8 @@ func {{decapitalise .Normalized.Name}}(accessibleState precompile.PrecompileAcce // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. // You can modify/delete this code if you don't want this function to be restricted by the allow list. stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := precompile.GetAllowListStatus(stateDB, {{$contract.Type}}Address, caller) + // Verify that the caller is in the allow list and therefore has the right to call this function. + callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) if !callerStatus.IsEnabled() { return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannot{{.Normalized.Name}}, caller) } @@ -308,8 +308,8 @@ func {{decapitalise $contract.Type}}Fallback (accessibleState precompile.Precomp // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. // You can modify/delete this code if you don't want this function to be restricted by the allow list. stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := precompile.GetAllowListStatus(stateDB, {{$contract.Type}}Address, caller) + // Verify that the caller is in the allow list and therefore has the right to call this function. + callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) if !callerStatus.IsEnabled() { return nil, remainingGas, fmt.Errorf("%w: %s", Err{{$contract.Type}}CannotFallback, caller) } @@ -329,11 +329,11 @@ func {{decapitalise $contract.Type}}Fallback (accessibleState precompile.Precomp {{- end}} // create{{.Contract.Type}}Precompile returns a StatefulPrecompiledContract with getters and setters for the precompile. -{{if .Contract.AllowList}} // Access to the getters/setters is controlled by an allow list for [precompileAddr].{{end}} -func create{{.Contract.Type}}Precompile(precompileAddr common.Address) (precompile.StatefulPrecompiledContract, error) { +{{if .Contract.AllowList}} // Access to the getters/setters is controlled by an allow list for ContractAddress.{{end}} +func create{{.Contract.Type}}Precompile() (precompile.StatefulPrecompiledContract, error) { var functions []*precompile.StatefulPrecompileFunction {{- if .Contract.AllowList}} - functions = append(functions, precompile.CreateAllowListFunctions(precompileAddr)...) + functions = append(functions, allowlist.CreateAllowListFunctions(ContractAddress)...) {{- end}} abiFunctionMap := map[string]precompile.RunStatefulPrecompileFunc{ diff --git a/cmd/precompilegen/main.go b/cmd/precompilegen/main.go index 0a60ed187f..7679d2c3e6 100644 --- a/cmd/precompilegen/main.go +++ b/cmd/precompilegen/main.go @@ -147,7 +147,7 @@ func precompilegen(c *cli.Context) error { // Generate the contract precompile configCode, contractCode, err := bind.PrecompileBind(types, abis, bins, sigs, pkg, lang, libs, aliases, abifilename) if err != nil { - utils.Fatalf("Failed to generate ABI precompile: %v", err) + utils.Fatalf("Failed to generate precompile: %v", err) } // Either flush it out to a file or display on the standard output @@ -165,13 +165,13 @@ func precompilegen(c *cli.Context) error { configCodeOut := filepath.Join(outFlagStr, "config.go") if err := os.WriteFile(configCodeOut, []byte(configCode), 0o600); err != nil { - utils.Fatalf("Failed to write generated precompile: %v", err) + utils.Fatalf("Failed to write generated config code: %v", err) } contractCodeOut := filepath.Join(outFlagStr, "contract.go") if err := os.WriteFile(contractCodeOut, []byte(contractCode), 0o600); err != nil { - utils.Fatalf("Failed to write generated precompile: %v", err) + utils.Fatalf("Failed to write generated contract code: %v", err) } if err := os.WriteFile(abipath, []byte(abis[0]), 0o600); err != nil { diff --git a/contract-examples/contracts/ExampleTxAllowList.sol b/contract-examples/contracts/ExampleTxAllowList.sol index 5c17fe1169..9d5c80095f 100644 --- a/contract-examples/contracts/ExampleTxAllowList.sol +++ b/contract-examples/contracts/ExampleTxAllowList.sol @@ -4,11 +4,11 @@ pragma solidity ^0.8.0; import "@openzeppelin/contracts/access/Ownable.sol"; import "./AllowList.sol"; -// ExampleDeployerList shows how ContractDeployerAllowList precompile can be used in a smart contract +// ExampleTxAllowList shows how TxAllowList precompile can be used in a smart contract // All methods of [allowList] can be directly called. There are example calls as tasks in hardhat.config.ts file. contract ExampleTxAllowList is AllowList { // Precompiled Allow List Contract Address - address constant DEPLOYER_LIST = 0x0200000000000000000000000000000000000002; + address constant TX_ALLOW_LIST = 0x0200000000000000000000000000000000000002; - constructor() AllowList(DEPLOYER_LIST) {} + constructor() AllowList(TX_ALLOW_LIST) {} } diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index ca3dbc528f..6d1b56f77a 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -39,9 +39,8 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile" - "github.com/ava-labs/subnet-evm/precompile/feemanager" - "github.com/ava-labs/subnet-evm/precompile/rewardmanager" + "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" + "github.com/ava-labs/subnet-evm/precompile/contracts/rewardmanager" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" ) @@ -350,7 +349,7 @@ func (bc *BlockChain) SubscribeAcceptedTransactionEvent(ch chan<- NewTxsEvent) e func (bc *BlockChain) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) { config := bc.Config() bigTime := new(big.Int).SetUint64(parent.Time) - if !config.IsPrecompileEnabled(precompile.FeeManagerAddress, bigTime) { + if !config.IsPrecompileEnabled(feemanager.ContractAddress, bigTime) { return config.FeeConfig, common.Big0, nil } @@ -394,7 +393,7 @@ func (bc *BlockChain) GetCoinbaseAt(parent *types.Header) (common.Address, bool, return constants.BlackholeAddr, false, nil } - if !config.IsPrecompileEnabled(precompile.RewardManagerAddress, bigTime) { + if !config.IsPrecompileEnabled(rewardmanager.ContractAddress, bigTime) { if bc.chainConfig.AllowFeeRecipients { return common.Address{}, true, nil } else { diff --git a/core/genesis.go b/core/genesis.go index 1ab970ad2e..07a0ac1bc0 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -307,7 +307,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { } // Configure any stateful precompiles that should be enabled in the genesis. - err = g.Config.ConfigurePrecompiles(nil, types.NewBlockWithHeader(head), statedb) + err = ApplyPrecompileActivations(g.Config, nil, types.NewBlockWithHeader(head), statedb) if err != nil { panic(fmt.Sprintf("unable to configure precompiles in genesis block: %v", err)) } diff --git a/core/genesis_test.go b/core/genesis_test.go index 61baeed669..3c2b57d1c0 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -38,9 +38,8 @@ import ( "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/ethdb" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile" "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/deployerallowlist" + "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" @@ -192,12 +191,14 @@ func TestStatefulPrecompilesConfigure(t *testing.T) { "allow list enabled in genesis": { getConfig: func() *params.ChainConfig { config := *params.TestChainConfig - config.ContractDeployerAllowListConfig = deployerallowlist.NewContractDeployerAllowListConfig(big.NewInt(0), []common.Address{addr}, nil) + config.GenesisPrecompiles = params.ChainConfigPrecompiles{ + deployerallowlist.ConfigKey: deployerallowlist.NewConfig(big.NewInt(0), []common.Address{addr}, nil), + } return &config }, assertState: func(t *testing.T, sdb *state.StateDB) { - assert.Equal(t, allowlist.AllowListAdmin, deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr), "unexpected allow list status for modified address") - assert.Equal(t, uint64(1), sdb.GetNonce(precompile.ContractDeployerAllowListAddress)) + assert.Equal(t, allowlist.AdminRole, deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr), "unexpected allow list status for modified address") + assert.Equal(t, uint64(1), sdb.GetNonce(deployerallowlist.ContractAddress)) }, }, } { @@ -267,11 +268,10 @@ func TestPrecompileActivationAfterHeaderBlock(t *testing.T) { require.Greater(block.Time(), bc.lastAccepted.Time()) activatedGenesis := customg - contractDeployerConfig := deployerallowlist.NewContractDeployerAllowListConfig(big.NewInt(51), nil, nil) + contractDeployerConfig := deployerallowlist.NewConfig(big.NewInt(51), nil, nil) activatedGenesis.Config.UpgradeConfig.PrecompileUpgrades = []params.PrecompileUpgrade{ { - // Enable ContractDeployerAllowList at timestamp 50 - ContractDeployerAllowListConfig: contractDeployerConfig, + Config: contractDeployerConfig, }, } diff --git a/core/state_processor.go b/core/state_processor.go index 58d7df6fd7..99be0b20f6 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -35,6 +35,8 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/modules" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" @@ -79,7 +81,7 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state ) // Configure any stateful precompiles that should go into effect during this block. - err := p.config.ConfigurePrecompiles(new(big.Int).SetUint64(parent.Time), block, statedb) + err := ApplyPrecompileActivations(p.config, new(big.Int).SetUint64(parent.Time), block, statedb) if err != nil { log.Error("failed to configure precompiles processing block", "hash", block.Hash(), "number", block.NumberU64(), "timestamp", block.Time(), "err", err) return nil, nil, 0, err @@ -168,3 +170,51 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg) return applyTransaction(msg, config, author, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) } + +// ApplyPrecompileActivations checks if any of the precompiles specified by the chain config are enabled or disabled by the block +// transition from [parentTimestamp] to the timestamp set in [blockContext]. If this is the case, it calls [Configure] +// to apply the necessary state transitions for the upgrade. +// This function is called: +// - within genesis setup to configure the starting state for precompiles enabled at genesis, +// - during block processing to update the state before processing the given block. +// - during block producing to apply the precompile upgrades before producing the block. +func ApplyPrecompileActivations(c *params.ChainConfig, parentTimestamp *big.Int, blockContext contract.BlockContext, statedb *state.StateDB) error { + blockTimestamp := blockContext.Timestamp() + // Note: RegisteredModules returns precompiles sorted by module addresses. + // This ensures that the order we call Configure for each precompile is consistent. + // This ensures even if precompiles read/write state other than their own they will observe + // an identical global state in a deterministic order when as they are configured. + for _, module := range modules.RegisteredModules() { + key := module.ConfigKey + for _, activatingConfig := range c.GetActivatingPrecompileConfigs(module.Address, parentTimestamp, blockTimestamp, c.PrecompileUpgrades) { + // If this transition activates the upgrade, configure the stateful precompile. + // (or deconfigure it if it is being disabled.) + if activatingConfig.IsDisabled() { + log.Info("Disabling precompile", "name", key) + statedb.Suicide(module.Address) + // Calling Finalise here effectively commits Suicide call and wipes the contract state. + // This enables re-configuration of the same contract state in the same block. + // Without an immediate Finalise call after the Suicide, a reconfigured precompiled state can be wiped out + // since Suicide will be committed after the reconfiguration. + statedb.Finalise(true) + } else { + module, ok := modules.GetPrecompileModule(key) + if !ok { + return fmt.Errorf("could not find module for activating precompile, name: %s", key) + } + log.Info("Activating new precompile", "name", key, "config", activatingConfig) + // Set the nonce of the precompile's address (as is done when a contract is created) to ensure + // that it is marked as non-empty and will not be cleaned up when the statedb is finalized. + statedb.SetNonce(module.Address, 1) + // Set the code of the precompile's address to a non-zero length byte slice to ensure that the precompile + // can be called from within Solidity contracts. Solidity adds a check before invoking a contract to ensure + // that it does not attempt to invoke a non-existent contract. + statedb.SetCode(module.Address, []byte{0x1}) + if err := module.Configure(c, activatingConfig, statedb, blockContext); err != nil { + return fmt.Errorf("could not configure precompile, name: %s, reason: %w", key, err) + } + } + } + } + return nil +} diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 0c5ce0f50b..d8da0edda5 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -36,7 +36,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/txallowlist" + "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" "github.com/ava-labs/subnet-evm/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -315,8 +315,8 @@ func TestBadTxAllowListBlock(t *testing.T) { NetworkUpgrades: params.NetworkUpgrades{ SubnetEVMTimestamp: big.NewInt(0), }, - PrecompileUpgrade: params.PrecompileUpgrade{ - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(0), nil, nil), + GenesisPrecompiles: params.ChainConfigPrecompiles{ + txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(0), nil, nil), }, } signer = types.LatestSigner(config) diff --git a/core/state_transition.go b/core/state_transition.go index 5fcfa642b5..ccb72beeaa 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -36,8 +36,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile" - "github.com/ava-labs/subnet-evm/precompile/txallowlist" + "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" ) @@ -250,10 +249,10 @@ func (st *StateTransition) preCheck() error { } // Check that the sender is on the tx allow list if enabled - if st.evm.ChainConfig().IsPrecompileEnabled(precompile.TxAllowListAddress, st.evm.Context.Time) { + if st.evm.ChainConfig().IsPrecompileEnabled(txallowlist.ContractAddress, st.evm.Context.Time) { txAllowListRole := txallowlist.GetTxAllowListStatus(st.state, st.msg.From()) if !txAllowListRole.IsEnabled() { - return fmt.Errorf("%w: %s", txallowlist.ErrSenderAddressNotAllowListed, st.msg.From()) + return fmt.Errorf("%w: %s", vmerrs.ErrSenderAddressNotAllowListed, st.msg.From()) } } } diff --git a/core/stateful_precompile_test.go b/core/stateful_precompile_test.go deleted file mode 100644 index 5f4f868918..0000000000 --- a/core/stateful_precompile_test.go +++ /dev/null @@ -1,1296 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package core - -import ( - "math/big" - "testing" - - "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/constants" - "github.com/ava-labs/subnet-evm/core/rawdb" - "github.com/ava-labs/subnet-evm/core/state" - "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/deployerallowlist" - "github.com/ava-labs/subnet-evm/precompile/feemanager" - "github.com/ava-labs/subnet-evm/precompile/nativeminter" - "github.com/ava-labs/subnet-evm/precompile/rewardmanager" - "github.com/ava-labs/subnet-evm/precompile/txallowlist" - "github.com/ava-labs/subnet-evm/vmerrs" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" - "github.com/stretchr/testify/require" -) - -// TODO: move this to precompile package once cross-import is resolved - -var ( - _ precompile.BlockContext = &mockBlockContext{} - _ precompile.PrecompileAccessibleState = &mockAccessibleState{} - - testFeeConfig = commontype.FeeConfig{ - GasLimit: big.NewInt(8_000_000), - TargetBlockRate: 2, // in seconds - - MinBaseFee: big.NewInt(25_000_000_000), - TargetGas: big.NewInt(15_000_000), - BaseFeeChangeDenominator: big.NewInt(36), - - MinBlockGasCost: big.NewInt(0), - MaxBlockGasCost: big.NewInt(1_000_000), - BlockGasCostStep: big.NewInt(200_000), - } - - testBlockNumber = big.NewInt(7) -) - -type mockBlockContext struct { - blockNumber *big.Int - timestamp uint64 -} - -func (mb *mockBlockContext) Number() *big.Int { return mb.blockNumber } -func (mb *mockBlockContext) Timestamp() *big.Int { return new(big.Int).SetUint64(mb.timestamp) } - -type mockAccessibleState struct { - state *state.StateDB - blockContext *mockBlockContext - snowContext *snow.Context -} - -func (m *mockAccessibleState) GetStateDB() precompile.StateDB { return m.state } - -func (m *mockAccessibleState) GetBlockContext() precompile.BlockContext { return m.blockContext } - -func (m *mockAccessibleState) GetSnowContext() *snow.Context { return m.snowContext } - -func (m *mockAccessibleState) CallFromPrecompile(caller common.Address, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { - return nil, 0, nil -} - -// This test is added within the core package so that it can import all of the required code -// without creating any import cycles -func TestContractDeployerAllowListRun(t *testing.T) { - type test struct { - caller common.Address - input func() []byte - suppliedGas uint64 - readOnly bool - - expectedRes []byte - expectedErr string - - assertState func(t *testing.T, state *state.StateDB) - } - - adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") - noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") - - for name, test := range map[string]test{ - "set admin": { - caller: adminAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListAdmin) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - res := deployerallowlist.GetContractDeployerAllowListStatus(state, noRoleAddr) - require.Equal(t, allowlist.AllowListAdmin, res) - }, - }, - "set deployer": { - caller: adminAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListEnabled) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - res := deployerallowlist.GetContractDeployerAllowListStatus(state, noRoleAddr) - require.Equal(t, allowlist.AllowListEnabled, res) - }, - }, - "set no role": { - caller: adminAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListNoRole) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - res := deployerallowlist.GetContractDeployerAllowListStatus(state, adminAddr) - require.Equal(t, allowlist.AllowListNoRole, res) - }, - }, - "set no role from non-admin": { - caller: noRoleAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListNoRole) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedErr: allowlist.ErrCannotModifyAllowList.Error(), - }, - "set deployer from non-admin": { - caller: noRoleAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListEnabled) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedErr: allowlist.ErrCannotModifyAllowList.Error(), - }, - "set admin from non-admin": { - caller: noRoleAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListAdmin) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedErr: allowlist.ErrCannotModifyAllowList.Error(), - }, - "set no role with readOnly enabled": { - caller: adminAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListNoRole) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "set no role insufficient gas": { - caller: adminAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListNoRole) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "read allow list no role": { - caller: noRoleAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost, - readOnly: false, - expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), - assertState: nil, - }, - "read allow list admin role": { - caller: adminAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost, - readOnly: false, - expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), - assertState: nil, - }, - "read allow list with readOnly enabled": { - caller: adminAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost, - readOnly: true, - expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), - assertState: nil, - }, - "read allow list out of gas": { - caller: adminAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost - 1, - readOnly: true, - expectedErr: vmerrs.ErrOutOfGas.Error(), - }, - } { - t.Run(name, func(t *testing.T) { - db := rawdb.NewMemoryDatabase() - state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) - require.NoError(t, err) - - // Set up the state so that each address has the expected permissions at the start. - deployerallowlist.SetContractDeployerAllowListStatus(state, adminAddr, allowlist.AllowListAdmin) - deployerallowlist.SetContractDeployerAllowListStatus(state, noRoleAddr, allowlist.AllowListNoRole) - require.Equal(t, allowlist.AllowListAdmin, deployerallowlist.GetContractDeployerAllowListStatus(state, adminAddr)) - require.Equal(t, allowlist.AllowListNoRole, deployerallowlist.GetContractDeployerAllowListStatus(state, noRoleAddr)) - - blockContext := &mockBlockContext{blockNumber: common.Big0} - ret, remainingGas, err := deployerallowlist.ContractDeployerAllowListPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext, snowContext: snow.DefaultContextTest()}, test.caller, precompile.ContractDeployerAllowListAddress, test.input(), test.suppliedGas, test.readOnly) - if len(test.expectedErr) != 0 { - require.ErrorContains(t, err, test.expectedErr) - } else { - require.NoError(t, err) - } - - require.Equal(t, uint64(0), remainingGas) - require.Equal(t, test.expectedRes, ret) - - if test.assertState != nil { - test.assertState(t, state) - } - }) - } -} - -func TestTxAllowListRun(t *testing.T) { - type test struct { - caller common.Address - precompileAddr common.Address - input func() []byte - suppliedGas uint64 - readOnly bool - - expectedRes []byte - expectedErr string - - assertState func(t *testing.T, state *state.StateDB) - } - - adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") - noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") - - for name, test := range map[string]test{ - "set admin": { - caller: adminAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListAdmin) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - res := txallowlist.GetTxAllowListStatus(state, noRoleAddr) - require.Equal(t, allowlist.AllowListAdmin, res) - }, - }, - "set allowed": { - caller: adminAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListEnabled) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - res := txallowlist.GetTxAllowListStatus(state, noRoleAddr) - require.Equal(t, allowlist.AllowListEnabled, res) - }, - }, - "set no role": { - caller: adminAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListNoRole) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - res := txallowlist.GetTxAllowListStatus(state, adminAddr) - require.Equal(t, allowlist.AllowListNoRole, res) - }, - }, - "set no role from non-admin": { - caller: noRoleAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListNoRole) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedErr: allowlist.ErrCannotModifyAllowList.Error(), - }, - "set allowed from non-admin": { - caller: noRoleAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListEnabled) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedErr: allowlist.ErrCannotModifyAllowList.Error(), - }, - "set admin from non-admin": { - caller: noRoleAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListAdmin) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedErr: allowlist.ErrCannotModifyAllowList.Error(), - }, - "set no role with readOnly enabled": { - caller: adminAddr, - precompileAddr: precompile.TxAllowListAddress, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListNoRole) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "set no role insufficient gas": { - caller: adminAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AllowListNoRole) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "read allow list no role": { - caller: noRoleAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost, - readOnly: false, - expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), - assertState: nil, - }, - "read allow list admin role": { - caller: adminAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost, - readOnly: false, - expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), - assertState: nil, - }, - "read allow list with readOnly enabled": { - caller: adminAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost, - readOnly: true, - expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), - assertState: nil, - }, - "read allow list out of gas": { - caller: adminAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost - 1, - readOnly: true, - expectedErr: vmerrs.ErrOutOfGas.Error(), - }, - } { - t.Run(name, func(t *testing.T) { - db := rawdb.NewMemoryDatabase() - state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) - require.NoError(t, err) - - // Set up the state so that each address has the expected permissions at the start. - txallowlist.SetTxAllowListStatus(state, adminAddr, allowlist.AllowListAdmin) - require.Equal(t, allowlist.AllowListAdmin, txallowlist.GetTxAllowListStatus(state, adminAddr)) - - blockContext := &mockBlockContext{blockNumber: common.Big0} - ret, remainingGas, err := txallowlist.TxAllowListPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext, snowContext: snow.DefaultContextTest()}, test.caller, precompile.TxAllowListAddress, test.input(), test.suppliedGas, test.readOnly) - if len(test.expectedErr) != 0 { - require.ErrorContains(t, err, test.expectedErr) - } else { - require.NoError(t, err) - } - - require.Equal(t, uint64(0), remainingGas) - require.Equal(t, test.expectedRes, ret) - - if test.assertState != nil { - test.assertState(t, state) - } - }) - } -} - -func TestContractNativeMinterRun(t *testing.T) { - type test struct { - caller common.Address - input func() []byte - suppliedGas uint64 - readOnly bool - config *nativeminter.ContractNativeMinterConfig - - expectedRes []byte - expectedErr string - - assertState func(t *testing.T, state *state.StateDB) - } - - adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") - enabledAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") - noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") - testAddr := common.HexToAddress("0x123456789") - - for name, test := range map[string]test{ - "mint funds from no role fails": { - caller: noRoleAddr, - input: func() []byte { - input, err := nativeminter.PackMintInput(noRoleAddr, common.Big1) - require.NoError(t, err) - - return input - }, - suppliedGas: nativeminter.MintGasCost, - readOnly: false, - expectedErr: nativeminter.ErrCannotMint.Error(), - }, - "mint funds from enabled address": { - caller: enabledAddr, - input: func() []byte { - input, err := nativeminter.PackMintInput(enabledAddr, common.Big1) - require.NoError(t, err) - - return input - }, - suppliedGas: nativeminter.MintGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - require.Equal(t, common.Big1, state.GetBalance(enabledAddr), "expected minted funds") - }, - }, - "enabled role by config": { - caller: noRoleAddr, - input: func() []byte { - return allowlist.PackReadAllowList(testAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost, - readOnly: false, - expectedRes: common.Hash(allowlist.AllowListEnabled).Bytes(), - assertState: func(t *testing.T, state *state.StateDB) { - require.Equal(t, allowlist.AllowListEnabled, nativeminter.GetContractNativeMinterStatus(state, testAddr)) - }, - config: &nativeminter.ContractNativeMinterConfig{ - AllowListConfig: allowlist.AllowListConfig{EnabledAddresses: []common.Address{testAddr}}, - }, - }, - "initial mint funds": { - caller: enabledAddr, - config: &nativeminter.ContractNativeMinterConfig{ - InitialMint: map[common.Address]*math.HexOrDecimal256{ - enabledAddr: math.NewHexOrDecimal256(2), - }, - }, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost, - readOnly: false, - expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), - assertState: func(t *testing.T, state *state.StateDB) { - require.Equal(t, common.Big2, state.GetBalance(enabledAddr), "expected minted funds") - }, - }, - "mint funds from admin address": { - caller: adminAddr, - input: func() []byte { - input, err := nativeminter.PackMintInput(adminAddr, common.Big1) - require.NoError(t, err) - - return input - }, - suppliedGas: nativeminter.MintGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - require.Equal(t, common.Big1, state.GetBalance(adminAddr), "expected minted funds") - }, - }, - "mint max big funds": { - caller: adminAddr, - input: func() []byte { - input, err := nativeminter.PackMintInput(adminAddr, math.MaxBig256) - require.NoError(t, err) - - return input - }, - suppliedGas: nativeminter.MintGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - require.Equal(t, math.MaxBig256, state.GetBalance(adminAddr), "expected minted funds") - }, - }, - "readOnly mint with noRole fails": { - caller: noRoleAddr, - input: func() []byte { - input, err := nativeminter.PackMintInput(adminAddr, common.Big1) - require.NoError(t, err) - - return input - }, - suppliedGas: nativeminter.MintGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "readOnly mint with allow role fails": { - caller: enabledAddr, - input: func() []byte { - input, err := nativeminter.PackMintInput(enabledAddr, common.Big1) - require.NoError(t, err) - - return input - }, - suppliedGas: nativeminter.MintGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "readOnly mint with admin role fails": { - caller: adminAddr, - input: func() []byte { - input, err := nativeminter.PackMintInput(adminAddr, common.Big1) - require.NoError(t, err) - - return input - }, - suppliedGas: nativeminter.MintGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "insufficient gas mint from admin": { - caller: adminAddr, - input: func() []byte { - input, err := nativeminter.PackMintInput(enabledAddr, common.Big1) - require.NoError(t, err) - - return input - }, - suppliedGas: nativeminter.MintGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "read from noRole address": { - caller: noRoleAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost, - readOnly: false, - expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), - assertState: func(t *testing.T, state *state.StateDB) {}, - }, - "read from noRole address readOnly enabled": { - caller: noRoleAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost, - readOnly: true, - expectedRes: common.Hash(allowlist.AllowListNoRole).Bytes(), - assertState: func(t *testing.T, state *state.StateDB) {}, - }, - "read from noRole address with insufficient gas": { - caller: noRoleAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "set allow role from admin": { - caller: adminAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListEnabled) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - res := nativeminter.GetContractNativeMinterStatus(state, noRoleAddr) - require.Equal(t, allowlist.AllowListEnabled, res) - }, - }, - "set allow role from non-admin fails": { - caller: enabledAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListEnabled) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedErr: allowlist.ErrCannotModifyAllowList.Error(), - }, - } { - t.Run(name, func(t *testing.T) { - db := rawdb.NewMemoryDatabase() - state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) - require.NoError(t, err) - - // Set up the state so that each address has the expected permissions at the start. - nativeminter.SetContractNativeMinterStatus(state, adminAddr, allowlist.AllowListAdmin) - nativeminter.SetContractNativeMinterStatus(state, enabledAddr, allowlist.AllowListEnabled) - nativeminter.SetContractNativeMinterStatus(state, noRoleAddr, allowlist.AllowListNoRole) - require.Equal(t, allowlist.AllowListAdmin, nativeminter.GetContractNativeMinterStatus(state, adminAddr)) - require.Equal(t, allowlist.AllowListEnabled, nativeminter.GetContractNativeMinterStatus(state, enabledAddr)) - require.Equal(t, allowlist.AllowListNoRole, nativeminter.GetContractNativeMinterStatus(state, noRoleAddr)) - - blockContext := &mockBlockContext{blockNumber: common.Big0} - if test.config != nil { - test.config.Configure(params.TestChainConfig, state, blockContext) - } - ret, remainingGas, err := nativeminter.ContractNativeMinterPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext, snowContext: snow.DefaultContextTest()}, test.caller, precompile.ContractNativeMinterAddress, test.input(), test.suppliedGas, test.readOnly) - if len(test.expectedErr) != 0 { - require.ErrorContains(t, err, test.expectedErr) - } else { - require.NoError(t, err) - } - - require.Equal(t, uint64(0), remainingGas) - require.Equal(t, test.expectedRes, ret) - - if test.assertState != nil { - test.assertState(t, state) - } - }) - } -} - -func TestFeeManagerRun(t *testing.T) { - type test struct { - caller common.Address - preCondition func(t *testing.T, state *state.StateDB) - input func() []byte - suppliedGas uint64 - readOnly bool - config *feemanager.FeeManagerConfig - - expectedRes []byte - expectedErr string - - assertState func(t *testing.T, state *state.StateDB) - } - - adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") - enabledAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") - noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") - - for name, test := range map[string]test{ - "set config from no role fails": { - caller: noRoleAddr, - input: func() []byte { - input, err := feemanager.PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - - return input - }, - suppliedGas: feemanager.SetFeeConfigGasCost, - readOnly: false, - expectedErr: feemanager.ErrCannotChangeFee.Error(), - }, - "set config from enabled address": { - caller: enabledAddr, - input: func() []byte { - input, err := feemanager.PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - - return input - }, - suppliedGas: feemanager.SetFeeConfigGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - feeConfig := feemanager.GetStoredFeeConfig(state) - require.Equal(t, testFeeConfig, feeConfig) - }, - }, - "set invalid config from enabled address": { - caller: enabledAddr, - input: func() []byte { - feeConfig := testFeeConfig - feeConfig.MinBlockGasCost = new(big.Int).Mul(feeConfig.MaxBlockGasCost, common.Big2) - input, err := feemanager.PackSetFeeConfig(feeConfig) - require.NoError(t, err) - - return input - }, - suppliedGas: feemanager.SetFeeConfigGasCost, - readOnly: false, - expectedRes: nil, - config: &feemanager.FeeManagerConfig{ - InitialFeeConfig: &testFeeConfig, - }, - expectedErr: "cannot be greater than maxBlockGasCost", - assertState: func(t *testing.T, state *state.StateDB) { - feeConfig := feemanager.GetStoredFeeConfig(state) - require.Equal(t, testFeeConfig, feeConfig) - }, - }, - "set config from admin address": { - caller: adminAddr, - input: func() []byte { - input, err := feemanager.PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - - return input - }, - suppliedGas: feemanager.SetFeeConfigGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - feeConfig := feemanager.GetStoredFeeConfig(state) - require.Equal(t, testFeeConfig, feeConfig) - lastChangedAt := feemanager.GetFeeConfigLastChangedAt(state) - require.EqualValues(t, testBlockNumber, lastChangedAt) - }, - }, - "get fee config from non-enabled address": { - caller: noRoleAddr, - preCondition: func(t *testing.T, state *state.StateDB) { - err := feemanager.StoreFeeConfig(state, testFeeConfig, &mockBlockContext{blockNumber: big.NewInt(6)}) - require.NoError(t, err) - }, - input: func() []byte { - return feemanager.PackGetFeeConfigInput() - }, - suppliedGas: feemanager.GetFeeConfigGasCost, - readOnly: true, - expectedRes: func() []byte { - res, err := feemanager.PackFeeConfig(testFeeConfig) - require.NoError(t, err) - return res - }(), - assertState: func(t *testing.T, state *state.StateDB) { - feeConfig := feemanager.GetStoredFeeConfig(state) - lastChangedAt := feemanager.GetFeeConfigLastChangedAt(state) - require.Equal(t, testFeeConfig, feeConfig) - require.EqualValues(t, big.NewInt(6), lastChangedAt) - }, - }, - "get initial fee config": { - caller: noRoleAddr, - input: func() []byte { - return feemanager.PackGetFeeConfigInput() - }, - suppliedGas: feemanager.GetFeeConfigGasCost, - config: &feemanager.FeeManagerConfig{ - InitialFeeConfig: &testFeeConfig, - }, - readOnly: true, - expectedRes: func() []byte { - res, err := feemanager.PackFeeConfig(testFeeConfig) - require.NoError(t, err) - return res - }(), - assertState: func(t *testing.T, state *state.StateDB) { - feeConfig := feemanager.GetStoredFeeConfig(state) - lastChangedAt := feemanager.GetFeeConfigLastChangedAt(state) - require.Equal(t, testFeeConfig, feeConfig) - require.EqualValues(t, testBlockNumber, lastChangedAt) - }, - }, - "get last changed at from non-enabled address": { - caller: noRoleAddr, - preCondition: func(t *testing.T, state *state.StateDB) { - err := feemanager.StoreFeeConfig(state, testFeeConfig, &mockBlockContext{blockNumber: testBlockNumber}) - require.NoError(t, err) - }, - input: func() []byte { - return feemanager.PackGetLastChangedAtInput() - }, - suppliedGas: feemanager.GetLastChangedAtGasCost, - readOnly: true, - expectedRes: common.BigToHash(testBlockNumber).Bytes(), - assertState: func(t *testing.T, state *state.StateDB) { - feeConfig := feemanager.GetStoredFeeConfig(state) - lastChangedAt := feemanager.GetFeeConfigLastChangedAt(state) - require.Equal(t, testFeeConfig, feeConfig) - require.Equal(t, testBlockNumber, lastChangedAt) - }, - }, - "readOnly setFeeConfig with noRole fails": { - caller: noRoleAddr, - input: func() []byte { - input, err := feemanager.PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - - return input - }, - suppliedGas: feemanager.SetFeeConfigGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "readOnly setFeeConfig with allow role fails": { - caller: enabledAddr, - input: func() []byte { - input, err := feemanager.PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - - return input - }, - suppliedGas: feemanager.SetFeeConfigGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "readOnly setFeeConfig with admin role fails": { - caller: adminAddr, - input: func() []byte { - input, err := feemanager.PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - - return input - }, - suppliedGas: feemanager.SetFeeConfigGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "insufficient gas setFeeConfig from admin": { - caller: adminAddr, - input: func() []byte { - input, err := feemanager.PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - - return input - }, - suppliedGas: feemanager.SetFeeConfigGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "set allow role from admin": { - caller: adminAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListEnabled) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - res := feemanager.GetFeeManagerStatus(state, noRoleAddr) - require.Equal(t, allowlist.AllowListEnabled, res) - }, - }, - "set allow role from non-admin fails": { - caller: enabledAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListEnabled) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedErr: allowlist.ErrCannotModifyAllowList.Error(), - }, - } { - t.Run(name, func(t *testing.T) { - db := rawdb.NewMemoryDatabase() - state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) - require.NoError(t, err) - - // Set up the state so that each address has the expected permissions at the start. - feemanager.SetFeeManagerStatus(state, adminAddr, allowlist.AllowListAdmin) - feemanager.SetFeeManagerStatus(state, enabledAddr, allowlist.AllowListEnabled) - feemanager.SetFeeManagerStatus(state, noRoleAddr, allowlist.AllowListNoRole) - - if test.preCondition != nil { - test.preCondition(t, state) - } - - blockContext := &mockBlockContext{blockNumber: testBlockNumber} - if test.config != nil { - test.config.Configure(params.TestChainConfig, state, blockContext) - } - ret, remainingGas, err := feemanager.FeeManagerPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext, snowContext: snow.DefaultContextTest()}, test.caller, precompile.FeeManagerAddress, test.input(), test.suppliedGas, test.readOnly) - if len(test.expectedErr) != 0 { - require.ErrorContains(t, err, test.expectedErr) - } else { - require.NoError(t, err) - } - - require.Equal(t, uint64(0), remainingGas) - require.Equal(t, test.expectedRes, ret) - - if test.assertState != nil { - test.assertState(t, state) - } - }) - } -} - -func TestRewardManagerRun(t *testing.T) { - type test struct { - caller common.Address - preCondition func(t *testing.T, state *state.StateDB) - input func() []byte - suppliedGas uint64 - readOnly bool - config *rewardmanager.RewardManagerConfig - - expectedRes []byte - expectedErr string - - assertState func(t *testing.T, state *state.StateDB) - } - - adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") - enabledAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") - noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") - testAddr := common.HexToAddress("0x0123") - - for name, test := range map[string]test{ - "set allow fee recipients from no role fails": { - caller: noRoleAddr, - input: func() []byte { - input, err := rewardmanager.PackAllowFeeRecipients() - require.NoError(t, err) - - return input - }, - suppliedGas: rewardmanager.AllowFeeRecipientsGasCost, - readOnly: false, - expectedErr: rewardmanager.ErrCannotAllowFeeRecipients.Error(), - }, - "set reward address from no role fails": { - caller: noRoleAddr, - input: func() []byte { - input, err := rewardmanager.PackSetRewardAddress(testAddr) - require.NoError(t, err) - - return input - }, - suppliedGas: rewardmanager.SetRewardAddressGasCost, - readOnly: false, - expectedErr: rewardmanager.ErrCannotSetRewardAddress.Error(), - }, - "disable rewards from no role fails": { - caller: noRoleAddr, - input: func() []byte { - input, err := rewardmanager.PackDisableRewards() - require.NoError(t, err) - - return input - }, - suppliedGas: rewardmanager.DisableRewardsGasCost, - readOnly: false, - expectedErr: rewardmanager.ErrCannotDisableRewards.Error(), - }, - "set allow fee recipients from enabled succeeds": { - caller: enabledAddr, - input: func() []byte { - input, err := rewardmanager.PackAllowFeeRecipients() - require.NoError(t, err) - - return input - }, - suppliedGas: rewardmanager.AllowFeeRecipientsGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - _, isFeeRecipients := rewardmanager.GetStoredRewardAddress(state) - require.True(t, isFeeRecipients) - }, - }, - "set reward address from enabled succeeds": { - caller: enabledAddr, - input: func() []byte { - input, err := rewardmanager.PackSetRewardAddress(testAddr) - require.NoError(t, err) - - return input - }, - suppliedGas: rewardmanager.SetRewardAddressGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - address, isFeeRecipients := rewardmanager.GetStoredRewardAddress(state) - require.Equal(t, testAddr, address) - require.False(t, isFeeRecipients) - }, - }, - "disable rewards from enabled succeeds": { - caller: enabledAddr, - input: func() []byte { - input, err := rewardmanager.PackDisableRewards() - require.NoError(t, err) - - return input - }, - suppliedGas: rewardmanager.DisableRewardsGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - address, isFeeRecipients := rewardmanager.GetStoredRewardAddress(state) - require.False(t, isFeeRecipients) - require.Equal(t, constants.BlackholeAddr, address) - }, - }, - "get current reward address from no role succeeds": { - caller: noRoleAddr, - preCondition: func(t *testing.T, state *state.StateDB) { - rewardmanager.StoreRewardAddress(state, testAddr) - }, - input: func() []byte { - input, err := rewardmanager.PackCurrentRewardAddress() - require.NoError(t, err) - - return input - }, - suppliedGas: rewardmanager.CurrentRewardAddressGasCost, - readOnly: false, - expectedRes: func() []byte { - res, err := rewardmanager.PackCurrentRewardAddressOutput(testAddr) - require.NoError(t, err) - return res - }(), - }, - "get are fee recipients allowed from no role succeeds": { - caller: noRoleAddr, - preCondition: func(t *testing.T, state *state.StateDB) { - rewardmanager.EnableAllowFeeRecipients(state) - }, - input: func() []byte { - input, err := rewardmanager.PackAreFeeRecipientsAllowed() - require.NoError(t, err) - return input - }, - suppliedGas: rewardmanager.AreFeeRecipientsAllowedGasCost, - readOnly: false, - expectedRes: func() []byte { - res, err := rewardmanager.PackAreFeeRecipientsAllowedOutput(true) - require.NoError(t, err) - return res - }(), - }, - "get initial config with address": { - caller: noRoleAddr, - input: func() []byte { - input, err := rewardmanager.PackCurrentRewardAddress() - require.NoError(t, err) - return input - }, - suppliedGas: rewardmanager.CurrentRewardAddressGasCost, - config: &rewardmanager.RewardManagerConfig{ - InitialRewardConfig: &rewardmanager.InitialRewardConfig{ - RewardAddress: testAddr, - }, - }, - readOnly: false, - expectedRes: func() []byte { - res, err := rewardmanager.PackCurrentRewardAddressOutput(testAddr) - require.NoError(t, err) - return res - }(), - }, - "get initial config with allow fee recipients enabled": { - caller: noRoleAddr, - input: func() []byte { - input, err := rewardmanager.PackAreFeeRecipientsAllowed() - require.NoError(t, err) - return input - }, - suppliedGas: rewardmanager.AreFeeRecipientsAllowedGasCost, - config: &rewardmanager.RewardManagerConfig{ - InitialRewardConfig: &rewardmanager.InitialRewardConfig{ - AllowFeeRecipients: true, - }, - }, - readOnly: false, - expectedRes: func() []byte { - res, err := rewardmanager.PackAreFeeRecipientsAllowedOutput(true) - require.NoError(t, err) - return res - }(), - }, - "readOnly allow fee recipients with allowed role fails": { - caller: enabledAddr, - input: func() []byte { - input, err := rewardmanager.PackAllowFeeRecipients() - require.NoError(t, err) - - return input - }, - suppliedGas: rewardmanager.AllowFeeRecipientsGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "readOnly set reward addresss with allowed role fails": { - caller: enabledAddr, - input: func() []byte { - input, err := rewardmanager.PackSetRewardAddress(testAddr) - require.NoError(t, err) - - return input - }, - suppliedGas: rewardmanager.SetRewardAddressGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "insufficient gas set reward address from allowed role": { - caller: enabledAddr, - input: func() []byte { - input, err := rewardmanager.PackSetRewardAddress(testAddr) - require.NoError(t, err) - - return input - }, - suppliedGas: rewardmanager.SetRewardAddressGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "insufficient gas allow fee recipients from allowed role": { - caller: enabledAddr, - input: func() []byte { - input, err := rewardmanager.PackAllowFeeRecipients() - require.NoError(t, err) - - return input - }, - suppliedGas: rewardmanager.AllowFeeRecipientsGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "insufficient gas read current reward address from allowed role": { - caller: enabledAddr, - input: func() []byte { - input, err := rewardmanager.PackCurrentRewardAddress() - require.NoError(t, err) - - return input - }, - suppliedGas: rewardmanager.CurrentRewardAddressGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "insufficient gas are fee recipients allowed from allowed role": { - caller: enabledAddr, - input: func() []byte { - input, err := rewardmanager.PackAreFeeRecipientsAllowed() - require.NoError(t, err) - - return input - }, - suppliedGas: rewardmanager.AreFeeRecipientsAllowedGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "set allow role from admin": { - caller: adminAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListEnabled) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - res := rewardmanager.GetRewardManagerAllowListStatus(state, noRoleAddr) - require.Equal(t, allowlist.AllowListEnabled, res) - }, - }, - "set allow role from non-admin fails": { - caller: enabledAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AllowListEnabled) - require.NoError(t, err) - - return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedErr: allowlist.ErrCannotModifyAllowList.Error(), - }, - } { - t.Run(name, func(t *testing.T) { - db := rawdb.NewMemoryDatabase() - state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) - require.NoError(t, err) - - // Set up the state so that each address has the expected permissions at the start. - rewardmanager.SetRewardManagerAllowListStatus(state, adminAddr, allowlist.AllowListAdmin) - rewardmanager.SetRewardManagerAllowListStatus(state, enabledAddr, allowlist.AllowListEnabled) - rewardmanager.SetRewardManagerAllowListStatus(state, noRoleAddr, allowlist.AllowListNoRole) - - if test.preCondition != nil { - test.preCondition(t, state) - } - - blockContext := &mockBlockContext{blockNumber: testBlockNumber} - if test.config != nil { - test.config.Configure(params.TestChainConfig, state, blockContext) - } - ret, remainingGas, err := rewardmanager.RewardManagerPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext, snowContext: snow.DefaultContextTest()}, test.caller, precompile.RewardManagerAddress, test.input(), test.suppliedGas, test.readOnly) - if len(test.expectedErr) != 0 { - require.ErrorContains(t, err, test.expectedErr) - } else { - require.NoError(t, err) - } - - require.Equal(t, uint64(0), remainingGas) - require.Equal(t, test.expectedRes, ret) - - if test.assertState != nil { - test.assertState(t, state) - } - }) - } -} diff --git a/core/test_blockchain.go b/core/test_blockchain.go index 2284fee91b..2acbf65eae 100644 --- a/core/test_blockchain.go +++ b/core/test_blockchain.go @@ -16,10 +16,9 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/ethdb" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile" "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/deployerallowlist" - "github.com/ava-labs/subnet-evm/precompile/feemanager" + "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" + "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/assert" @@ -1549,8 +1548,10 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, chainC genesisBalance := new(big.Int).Mul(big.NewInt(1000000), big.NewInt(params.Ether)) config := *params.TestChainConfig // Set all of the required config parameters - config.ContractDeployerAllowListConfig = deployerallowlist.NewContractDeployerAllowListConfig(big.NewInt(0), []common.Address{addr1}, nil) - config.FeeManagerConfig = feemanager.NewFeeManagerConfig(big.NewInt(0), []common.Address{addr1}, nil, nil) + config.GenesisPrecompiles = params.ChainConfigPrecompiles{ + deployerallowlist.ConfigKey: deployerallowlist.NewConfig(big.NewInt(0), []common.Address{addr1}, nil), + feemanager.ConfigKey: feemanager.NewConfig(big.NewInt(0), []common.Address{addr1}, nil, nil), + } gspec := &Genesis{ Config: &config, Alloc: GenesisAlloc{addr1: {Balance: genesisBalance}}, @@ -1590,14 +1591,14 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, chainC "allow list": { addTx: func(gen *BlockGen) { feeCap := new(big.Int).Add(gen.BaseFee(), tip) - input, err := allowlist.PackModifyAllowList(addr2, allowlist.AllowListAdmin) + input, err := allowlist.PackModifyAllowList(addr2, allowlist.AdminRole) if err != nil { t.Fatal(err) } tx := types.NewTx(&types.DynamicFeeTx{ ChainID: params.TestChainConfig.ChainID, Nonce: gen.TxNonce(addr1), - To: &precompile.ContractDeployerAllowListAddress, + To: &deployerallowlist.ContractAddress, Gas: 3_000_000, Value: common.Big0, GasFeeCap: feeCap, @@ -1613,23 +1614,23 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, chainC }, verifyState: func(sdb *state.StateDB) error { res := deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr1) - if allowlist.AllowListAdmin != res { - return fmt.Errorf("unexpected allow list status for addr1 %s, expected %s", res, allowlist.AllowListAdmin) + if allowlist.AdminRole != res { + return fmt.Errorf("unexpected allow list status for addr1 %s, expected %s", res, allowlist.AdminRole) } res = deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr2) - if allowlist.AllowListAdmin != res { - return fmt.Errorf("unexpected allow list status for addr2 %s, expected %s", res, allowlist.AllowListAdmin) + if allowlist.AdminRole != res { + return fmt.Errorf("unexpected allow list status for addr2 %s, expected %s", res, allowlist.AdminRole) } return nil }, verifyGenesis: func(sdb *state.StateDB) { res := deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr1) - if allowlist.AllowListAdmin != res { - t.Fatalf("unexpected allow list status for addr1 %s, expected %s", res, allowlist.AllowListAdmin) + if allowlist.AdminRole != res { + t.Fatalf("unexpected allow list status for addr1 %s, expected %s", res, allowlist.AdminRole) } res = deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr2) - if allowlist.AllowListNoRole != res { - t.Fatalf("unexpected allow list status for addr2 %s, expected %s", res, allowlist.AllowListNoRole) + if allowlist.NoRole != res { + t.Fatalf("unexpected allow list status for addr2 %s, expected %s", res, allowlist.NoRole) } }, }, @@ -1643,7 +1644,7 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, chainC tx := types.NewTx(&types.DynamicFeeTx{ ChainID: params.TestChainConfig.ChainID, Nonce: gen.TxNonce(addr1), - To: &precompile.FeeManagerAddress, + To: &feemanager.ContractAddress, Gas: 3_000_000, Value: common.Big0, GasFeeCap: feeCap, @@ -1659,7 +1660,7 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, chainC }, verifyState: func(sdb *state.StateDB) error { res := feemanager.GetFeeManagerStatus(sdb, addr1) - assert.Equal(allowlist.AllowListAdmin, res) + assert.Equal(allowlist.AdminRole, res) storedConfig := feemanager.GetStoredFeeConfig(sdb) assert.EqualValues(testFeeConfig, storedConfig) @@ -1671,7 +1672,7 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, chainC }, verifyGenesis: func(sdb *state.StateDB) { res := feemanager.GetFeeManagerStatus(sdb, addr1) - assert.Equal(allowlist.AllowListAdmin, res) + assert.Equal(allowlist.AdminRole, res) feeConfig, _, err := blockchain.GetFeeConfigAt(blockchain.Genesis().Header()) assert.NoError(err) diff --git a/core/tx_pool.go b/core/tx_pool.go index 1f92bc0ce5..5d498fcddf 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -42,8 +42,10 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/metrics" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile" - "github.com/ava-labs/subnet-evm/precompile/txallowlist" + "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" + "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" + "github.com/ava-labs/subnet-evm/vmerrs" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/prque" "github.com/ethereum/go-ethereum/event" @@ -694,10 +696,10 @@ func (pool *TxPool) checkTxState(from common.Address, tx *types.Transaction) err // If the tx allow list is enabled, return an error if the from address is not allow listed. headTimestamp := big.NewInt(int64(pool.currentHead.Time)) - if pool.chainconfig.IsPrecompileEnabled(precompile.TxAllowListAddress, headTimestamp) { + if pool.chainconfig.IsPrecompileEnabled(txallowlist.ContractAddress, headTimestamp) { txAllowListRole := txallowlist.GetTxAllowListStatus(pool.currentState, from) if !txAllowListRole.IsEnabled() { - return fmt.Errorf("%w: %s", txallowlist.ErrSenderAddressNotAllowListed, from) + return fmt.Errorf("%w: %s", vmerrs.ErrSenderAddressNotAllowListed, from) } } return nil @@ -1446,7 +1448,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { // without requiring FeeManager is enabled. // This is already being set by SetMinFee when gas price updater starts. // However tests are currently failing if we change this check to IsSubnetEVM. - if pool.chainconfig.IsPrecompileEnabled(precompile.FeeManagerAddress, new(big.Int).SetUint64(newHead.Time)) { + if pool.chainconfig.IsPrecompileEnabled(feemanager.ContractAddress, new(big.Int).SetUint64(newHead.Time)) { feeConfig, _, err := pool.chain.GetFeeConfigAt(newHead) if err != nil { log.Error("Failed to get fee config state", "err", err, "root", newHead.Root) diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index de7bea7273..2bca2c372d 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -56,6 +56,19 @@ var ( // eip1559Config is a chain config with EIP-1559 enabled at block 0. eip1559Config *params.ChainConfig + + testFeeConfig = commontype.FeeConfig{ + GasLimit: big.NewInt(8_000_000), + TargetBlockRate: 2, // in seconds + + MinBaseFee: big.NewInt(25_000_000_000), + TargetGas: big.NewInt(15_000_000), + BaseFeeChangeDenominator: big.NewInt(36), + + MinBlockGasCost: big.NewInt(0), + MaxBlockGasCost: big.NewInt(1_000_000), + BlockGasCostStep: big.NewInt(200_000), + } ) func init() { diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 056f7af1ad..bdeb189fb5 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -35,7 +35,8 @@ import ( "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/modules" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" @@ -57,7 +58,7 @@ type PrecompiledContract interface { // PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum // contracts used in the Frontier and Homestead releases. -var PrecompiledContractsHomestead = map[common.Address]precompile.StatefulPrecompiledContract{ +var PrecompiledContractsHomestead = map[common.Address]contract.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), @@ -66,7 +67,7 @@ var PrecompiledContractsHomestead = map[common.Address]precompile.StatefulPrecom // PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum // contracts used in the Byzantium release. -var PrecompiledContractsByzantium = map[common.Address]precompile.StatefulPrecompiledContract{ +var PrecompiledContractsByzantium = map[common.Address]contract.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), @@ -79,7 +80,7 @@ var PrecompiledContractsByzantium = map[common.Address]precompile.StatefulPrecom // PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum // contracts used in the Istanbul release. -var PrecompiledContractsIstanbul = map[common.Address]precompile.StatefulPrecompiledContract{ +var PrecompiledContractsIstanbul = map[common.Address]contract.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), @@ -93,7 +94,7 @@ var PrecompiledContractsIstanbul = map[common.Address]precompile.StatefulPrecomp // PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum // contracts used in the Berlin release. -var PrecompiledContractsBerlin = map[common.Address]precompile.StatefulPrecompiledContract{ +var PrecompiledContractsBerlin = map[common.Address]contract.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), @@ -107,7 +108,7 @@ var PrecompiledContractsBerlin = map[common.Address]precompile.StatefulPrecompil // PrecompiledContractsBLS contains the set of pre-compiled Ethereum // contracts specified in EIP-2537. These are exported for testing purposes. -var PrecompiledContractsBLS = map[common.Address]precompile.StatefulPrecompiledContract{ +var PrecompiledContractsBLS = map[common.Address]contract.StatefulPrecompiledContract{ common.BytesToAddress([]byte{10}): newWrappedPrecompiledContract(&bls12381G1Add{}), common.BytesToAddress([]byte{11}): newWrappedPrecompiledContract(&bls12381G1Mul{}), common.BytesToAddress([]byte{12}): newWrappedPrecompiledContract(&bls12381G1MultiExp{}), @@ -158,12 +159,13 @@ func init() { // Ensure that this package will panic during init if there is a conflict present with the declared // precompile addresses. - for _, k := range precompile.UsedAddresses { - if _, ok := PrecompileAllNativeAddresses[k]; ok { - panic(fmt.Errorf("precompile address collides with existing native address: %s", k)) + for _, module := range modules.RegisteredModules() { + address := module.Address + if _, ok := PrecompileAllNativeAddresses[address]; ok { + panic(fmt.Errorf("precompile address collides with existing native address: %s", address)) } - if k == constants.BlackholeAddr { - panic(fmt.Errorf("cannot use address %s for stateful precompile - overlaps with blackhole address", k)) + if address == constants.BlackholeAddr { + panic(fmt.Errorf("cannot use address %s for stateful precompile - overlaps with blackhole address", address)) } } } diff --git a/core/vm/contracts_stateful.go b/core/vm/contracts_stateful.go index 49c1aef48d..dc04120979 100644 --- a/core/vm/contracts_stateful.go +++ b/core/vm/contracts_stateful.go @@ -4,7 +4,7 @@ package vm import ( - "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ethereum/go-ethereum/common" ) @@ -16,16 +16,16 @@ type wrappedPrecompiledContract struct { // newWrappedPrecompiledContract returns a wrapped version of [PrecompiledContract] to be executed according to the StatefulPrecompiledContract // interface. -func newWrappedPrecompiledContract(p PrecompiledContract) precompile.StatefulPrecompiledContract { +func newWrappedPrecompiledContract(p PrecompiledContract) contract.StatefulPrecompiledContract { return &wrappedPrecompiledContract{p: p} } // Run implements the StatefulPrecompiledContract interface -func (w *wrappedPrecompiledContract) Run(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { +func (w *wrappedPrecompiledContract) Run(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { return RunPrecompiledContract(w.p, input, suppliedGas) } // RunStatefulPrecompiledContract confirms runs [precompile] with the specified parameters. -func RunStatefulPrecompiledContract(precompile precompile.StatefulPrecompiledContract, accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { +func RunStatefulPrecompiledContract(precompile contract.StatefulPrecompiledContract, accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { return precompile.Run(accessibleState, caller, addr, input, suppliedGas, readOnly) } diff --git a/core/vm/evm.go b/core/vm/evm.go index 86849d05ad..764bce4e65 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -35,8 +35,9 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile" - "github.com/ava-labs/subnet-evm/precompile/deployerallowlist" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" + "github.com/ava-labs/subnet-evm/precompile/modules" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -44,8 +45,8 @@ import ( ) var ( - _ precompile.PrecompileAccessibleState = &EVM{} - _ precompile.BlockContext = &BlockContext{} + _ contract.AccessibleState = &EVM{} + _ contract.BlockContext = &BlockContext{} ) // IsProhibited returns true if [addr] is in the prohibited list of addresses which should @@ -55,7 +56,7 @@ func IsProhibited(addr common.Address) bool { return true } - return precompile.ReservedAddress(addr) + return modules.ReservedAddress(addr) } // emptyCodeHash is used by create to ensure deployment is disallowed to already @@ -72,8 +73,8 @@ type ( GetHashFunc func(uint64) common.Hash ) -func (evm *EVM) precompile(addr common.Address) (precompile.StatefulPrecompiledContract, bool) { - var precompiles map[common.Address]precompile.StatefulPrecompiledContract +func (evm *EVM) precompile(addr common.Address) (contract.StatefulPrecompiledContract, bool) { + var precompiles map[common.Address]contract.StatefulPrecompiledContract switch { case evm.chainRules.IsSubnetEVM: precompiles = PrecompiledContractsBerlin @@ -92,8 +93,12 @@ func (evm *EVM) precompile(addr common.Address) (precompile.StatefulPrecompiledC } // Otherwise, check the chain rules for the additionally configured precompiles. - p, ok = evm.chainRules.Precompiles[addr] - return p, ok + if _, ok = evm.chainRules.ActivePrecompiles[addr]; ok { + module, ok := modules.GetPrecompileModuleByAddress(addr) + return module.Contract, ok + } + + return nil, false } // BlockContext provides the EVM with auxiliary information. Once provided @@ -208,12 +213,12 @@ func (evm *EVM) GetSnowContext() *snow.Context { } // GetStateDB returns the evm's StateDB -func (evm *EVM) GetStateDB() precompile.StateDB { +func (evm *EVM) GetStateDB() contract.StateDB { return evm.StateDB } // GetBlockContext returns the evm's BlockContext -func (evm *EVM) GetBlockContext() precompile.BlockContext { +func (evm *EVM) GetBlockContext() contract.BlockContext { return &evm.Context } @@ -508,7 +513,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, return nil, common.Address{}, 0, vmerrs.ErrContractAddressCollision } // If the allow list is enabled, check that [evm.TxContext.Origin] has permission to deploy a contract. - if evm.chainRules.IsPrecompileEnabled(precompile.ContractDeployerAllowListAddress) { + if evm.chainRules.IsPrecompileEnabled(deployerallowlist.ContractAddress) { allowListRole := deployerallowlist.GetContractDeployerAllowListStatus(evm.StateDB, evm.TxContext.Origin) if !allowListRole.IsEnabled() { return nil, common.Address{}, 0, fmt.Errorf("tx.origin %s is not authorized to deploy a contract", evm.TxContext.Origin) diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 63d1cb3a59..253778c6af 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -39,7 +39,7 @@ import ( "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" "github.com/ava-labs/subnet-evm/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" @@ -318,7 +318,7 @@ func (oracle *Oracle) suggestDynamicFees(ctx context.Context) (*big.Int, *big.In feeLastChangedAt *big.Int feeConfig commontype.FeeConfig ) - if oracle.backend.ChainConfig().IsPrecompileEnabled(precompile.FeeManagerAddress, new(big.Int).SetUint64(head.Time)) { + if oracle.backend.ChainConfig().IsPrecompileEnabled(feemanager.ContractAddress, new(big.Int).SetUint64(head.Time)) { feeConfig, feeLastChangedAt, err = oracle.backend.GetFeeConfigAt(head) if err != nil { return nil, nil, err diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index 3acc68d20d..049c154127 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -40,8 +40,7 @@ import ( "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/ethdb" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile" - "github.com/ava-labs/subnet-evm/precompile/feemanager" + "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" "github.com/ava-labs/subnet-evm/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -435,7 +434,9 @@ func TestSuggestGasPriceAfterFeeConfigUpdate(t *testing.T) { // create a chain config with fee manager enabled at genesis with [addr] as the admin chainConfig := *params.TestChainConfig - chainConfig.FeeManagerConfig = feemanager.NewFeeManagerConfig(big.NewInt(0), []common.Address{addr}, nil, nil) + chainConfig.GenesisPrecompiles = params.ChainConfigPrecompiles{ + feemanager.ConfigKey: feemanager.NewConfig(big.NewInt(0), []common.Address{addr}, nil, nil), + } // create a fee config with higher MinBaseFee and prepare it for inclusion in a tx signer := types.LatestSigner(params.TestChainConfig) @@ -463,7 +464,7 @@ func TestSuggestGasPriceAfterFeeConfigUpdate(t *testing.T) { tx := types.NewTx(&types.DynamicFeeTx{ ChainID: chainConfig.ChainID, Nonce: b.TxNonce(addr), - To: &precompile.FeeManagerAddress, + To: &feemanager.ContractAddress, Gas: chainConfig.FeeConfig.GasLimit.Uint64(), Value: common.Big0, GasFeeCap: chainConfig.FeeConfig.MinBaseFee, // give low fee, it should work since we still haven't applied high fees diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index f2a109677f..9cdf0cf7f7 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -626,12 +626,12 @@ func (api *BlockChainAPI) ChainId() *hexutil.Big { } // GetActivePrecompilesAt returns the active precompile configs at the given block timestamp. -func (s *BlockChainAPI) GetActivePrecompilesAt(ctx context.Context, blockTimestamp *big.Int) params.PrecompileUpgrade { +func (s *BlockChainAPI) GetActivePrecompilesAt(ctx context.Context, blockTimestamp *big.Int) params.ChainConfigPrecompiles { if blockTimestamp == nil { - blockTimestampInt := s.b.CurrentHeader().Time - blockTimestamp = new(big.Int).SetUint64(blockTimestampInt) + blockTimestamp = new(big.Int).SetUint64(s.b.CurrentHeader().Time) } - return s.b.ChainConfig().GetActivePrecompileUpgrade(blockTimestamp) + + return s.b.ChainConfig().EnabledStatefulPrecompiles(blockTimestamp) } type FeeConfigResult struct { diff --git a/miner/worker.go b/miner/worker.go index bed65cb814..26a9c31168 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -186,7 +186,7 @@ func (w *worker) commitNewWork() (*types.Block, error) { return nil, fmt.Errorf("failed to create new current environment: %w", err) } // Configure any stateful precompiles that should go into effect during this block. - err = w.chainConfig.ConfigurePrecompiles(new(big.Int).SetUint64(parent.Time()), types.NewBlockWithHeader(header), env.state) + err = core.ApplyPrecompileActivations(w.chainConfig, new(big.Int).SetUint64(parent.Time()), types.NewBlockWithHeader(header), env.state) if err != nil { log.Error("failed to configure precompiles mining new block", "parent", parent.Hash(), "number", header.Number, "timestamp", header.Time, "err", err) return nil, err diff --git a/params/chain_config_precompiles.go b/params/chain_config_precompiles.go new file mode 100644 index 0000000000..5d60ab69b5 --- /dev/null +++ b/params/chain_config_precompiles.go @@ -0,0 +1,36 @@ +// (c) 2023 Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package params + +import ( + "encoding/json" + + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/modules" +) + +type ChainConfigPrecompiles map[string]config.Config + +// UnmarshalJSON parses the JSON-encoded data into the ChainConfigPrecompiles. +// ChainConfigPrecompiles is a map of precompile module keys to their +// configuration. +func (ccp *ChainConfigPrecompiles) UnmarshalJSON(data []byte) error { + raw := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + *ccp = make(ChainConfigPrecompiles) + for _, module := range modules.RegisteredModules() { + key := module.ConfigKey + if value, ok := raw[key]; ok { + conf := module.NewConfig() + if err := json.Unmarshal(value, conf); err != nil { + return err + } + (*ccp)[key] = conf + } + } + return nil +} diff --git a/params/config.go b/params/config.go index 1407ce1bb8..2a925084dd 100644 --- a/params/config.go +++ b/params/config.go @@ -34,7 +34,8 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/modules" "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" ) @@ -83,7 +84,7 @@ var ( PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0), - + GenesisPrecompiles: ChainConfigPrecompiles{}, NetworkUpgrades: NetworkUpgrades{ SubnetEVMTimestamp: big.NewInt(0), }, @@ -105,7 +106,7 @@ var ( IstanbulBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0), NetworkUpgrades: NetworkUpgrades{big.NewInt(0)}, - PrecompileUpgrade: PrecompileUpgrade{}, + GenesisPrecompiles: ChainConfigPrecompiles{}, UpgradeConfig: UpgradeConfig{}, } @@ -125,7 +126,7 @@ var ( IstanbulBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0), NetworkUpgrades: NetworkUpgrades{}, - PrecompileUpgrade: PrecompileUpgrade{}, + GenesisPrecompiles: ChainConfigPrecompiles{}, UpgradeConfig: UpgradeConfig{}, } ) @@ -157,9 +158,57 @@ type ChainConfig struct { IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul) MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated) - NetworkUpgrades // Config for timestamps that enable avalanche network upgrades - PrecompileUpgrade // Config for enabling precompiles from genesis - UpgradeConfig `json:"-"` // Config specified in upgradeBytes (avalanche network upgrades or enable/disabling precompiles). Skip encoding/decoding directly into ChainConfig. + NetworkUpgrades // Config for timestamps that enable avalanche network upgrades + GenesisPrecompiles ChainConfigPrecompiles `json:"-"` // Config for enabling precompiles from genesis. JSON encode/decode will be handled by the custom marshaler/unmarshaler. + UpgradeConfig `json:"-"` // Config specified in upgradeBytes (avalanche network upgrades or enable/disabling precompiles). Skip encoding/decoding directly into ChainConfig. +} + +// UnmarshalJSON parses the JSON-encoded data and stores the result in the +// object pointed to by c. +// This is a custom unmarshaler to handle the Precompiles field. +// Precompiles was presented as an inline object in the JSON. +// This custom unmarshaler ensures backwards compatibility with the old format. +func (c *ChainConfig) UnmarshalJSON(data []byte) error { + // Alias ChainConfig to avoid recursion + type _ChainConfig ChainConfig + tmp := _ChainConfig{} + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + + // At this point we have populated all fields except PrecompileUpgrade + *c = ChainConfig(tmp) + + // Unmarshal inlined PrecompileUpgrade + return json.Unmarshal(data, &c.GenesisPrecompiles) +} + +// MarshalJSON returns the JSON encoding of c. +// This is a custom marshaler to handle the Precompiles field. +func (c ChainConfig) MarshalJSON() ([]byte, error) { + // Alias ChainConfig to avoid recursion + type _ChainConfig ChainConfig + tmp, err := json.Marshal(_ChainConfig(c)) + if err != nil { + return nil, err + } + + // To include PrecompileUpgrades, we unmarshal the json representing c + // then directly add the corresponding keys to the json. + raw := make(map[string]json.RawMessage) + if err := json.Unmarshal(tmp, &raw); err != nil { + return nil, err + } + + for key, value := range c.GenesisPrecompiles { + conf, err := json.Marshal(value) + if err != nil { + return nil, err + } + raw[key] = conf + } + + return json.Marshal(raw) } // UpgradeConfig includes the following configs that may be specified in upgradeBytes: @@ -191,7 +240,7 @@ func (c *ChainConfig) String() string { if err != nil { networkUpgradesBytes = []byte("cannot marshal NetworkUpgrades") } - precompileUpgradeBytes, err := json.Marshal(c.PrecompileUpgrade) + precompileUpgradeBytes, err := json.Marshal(c.GenesisPrecompiles) if err != nil { precompileUpgradeBytes = []byte("cannot marshal PrecompileUpgrade") } @@ -274,7 +323,7 @@ func (c *ChainConfig) IsSubnetEVM(blockTimestamp *big.Int) bool { // IsPrecompileEnabled returns whether precompile with [address] is enabled at [blockTimestamp]. func (c *ChainConfig) IsPrecompileEnabled(address common.Address, blockTimestamp *big.Int) bool { - config := c.GetPrecompileConfig(address, blockTimestamp) + config := c.GetActivePrecompileConfig(address, blockTimestamp) return config != nil && !config.IsDisabled() } @@ -305,7 +354,7 @@ func (c *ChainConfig) Verify() error { // Verify the precompile upgrades are internally consistent given the existing chainConfig. if err := c.verifyPrecompileUpgrades(); err != nil { - return err + return fmt.Errorf("invalid precompile upgrades: %w", err) } return nil @@ -505,15 +554,15 @@ type Rules struct { // Rules for Avalanche releases IsSubnetEVM bool - // Precompiles maps addresses to stateful precompiled contracts that are enabled + // ActivePrecompiles maps addresses to stateful precompiled contracts that are enabled // for this rule set. // Note: none of these addresses should conflict with the address space used by // any existing precompiles. - Precompiles map[common.Address]precompile.StatefulPrecompiledContract + ActivePrecompiles map[common.Address]config.Config } func (r *Rules) IsPrecompileEnabled(addr common.Address) bool { - _, ok := r.Precompiles[addr] + _, ok := r.ActivePrecompiles[addr] return ok } @@ -544,12 +593,11 @@ func (c *ChainConfig) AvalancheRules(blockNum, blockTimestamp *big.Int) Rules { rules.IsSubnetEVM = c.IsSubnetEVM(blockTimestamp) // Initialize the stateful precompiles that should be enabled at [blockTimestamp]. - rules.Precompiles = make(map[common.Address]precompile.StatefulPrecompiledContract) - for _, config := range c.EnabledStatefulPrecompiles(blockTimestamp) { - if config.IsDisabled() { - continue + rules.ActivePrecompiles = make(map[common.Address]config.Config) + for _, module := range modules.RegisteredModules() { + if config := c.GetActivePrecompileConfig(module.Address, blockTimestamp); config != nil { + rules.ActivePrecompiles[module.Address] = config } - rules.Precompiles[config.Address()] = config.Contract() } return rules diff --git a/params/config_test.go b/params/config_test.go index 50487f159a..8e1c3f7ada 100644 --- a/params/config_test.go +++ b/params/config_test.go @@ -27,9 +27,15 @@ package params import ( + "encoding/json" "math/big" "reflect" "testing" + + "github.com/ava-labs/subnet-evm/precompile/contracts/nativeminter" + "github.com/ava-labs/subnet-evm/precompile/contracts/rewardmanager" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" ) func TestCheckCompatible(t *testing.T) { @@ -136,3 +142,67 @@ func TestCheckCompatible(t *testing.T) { } } } + +func TestConfigUnmarshalJSON(t *testing.T) { + require := require.New(t) + + testRewardManagerConfig := rewardmanager.NewConfig( + big.NewInt(1671542573), + []common.Address{common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC")}, + nil, + &rewardmanager.InitialRewardConfig{ + AllowFeeRecipients: true, + }) + + testContractNativeMinterConfig := nativeminter.NewConfig( + big.NewInt(0), + []common.Address{common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC")}, + nil, + nil, + ) + + config := []byte(` + { + "chainId": 43214, + "allowFeeRecipients": true, + "rewardManagerConfig": { + "blockTimestamp": 1671542573, + "adminAddresses": [ + "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" + ], + "initialRewardConfig": { + "allowFeeRecipients": true + } + }, + "contractNativeMinterConfig": { + "blockTimestamp": 0, + "adminAddresses": [ + "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" + ] + } + } + `) + c := ChainConfig{} + err := json.Unmarshal(config, &c) + require.NoError(err) + + require.Equal(c.ChainID, big.NewInt(43214)) + require.Equal(c.AllowFeeRecipients, true) + + rewardManagerConfig, ok := c.GenesisPrecompiles[rewardmanager.ConfigKey] + require.True(ok) + require.Equal(rewardManagerConfig.Key(), rewardmanager.ConfigKey) + require.True(rewardManagerConfig.Equal(testRewardManagerConfig)) + + nativeMinterConfig := c.GenesisPrecompiles[nativeminter.ConfigKey] + require.Equal(nativeMinterConfig.Key(), nativeminter.ConfigKey) + require.True(nativeMinterConfig.Equal(testContractNativeMinterConfig)) + + // Marshal and unmarshal again and check that the result is the same + marshaled, err := json.Marshal(c) + require.NoError(err) + c2 := ChainConfig{} + err = json.Unmarshal(marshaled, &c2) + require.NoError(err) + require.Equal(c, c2) +} diff --git a/params/precompile_config.go b/params/precompile_config.go deleted file mode 100644 index a43133f3f9..0000000000 --- a/params/precompile_config.go +++ /dev/null @@ -1,300 +0,0 @@ -// (c) 2022 Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package params - -import ( - "fmt" - "math/big" - - "github.com/ava-labs/subnet-evm/precompile" - "github.com/ava-labs/subnet-evm/precompile/deployerallowlist" - "github.com/ava-labs/subnet-evm/precompile/feemanager" - "github.com/ava-labs/subnet-evm/precompile/nativeminter" - "github.com/ava-labs/subnet-evm/precompile/rewardmanager" - "github.com/ava-labs/subnet-evm/precompile/txallowlist" - "github.com/ava-labs/subnet-evm/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" -) - -// PrecompileUpgrade is a helper struct embedded in UpgradeConfig, representing -// each of the possible stateful precompile types that can be activated -// as a network upgrade. -type PrecompileUpgrade struct { - ContractDeployerAllowListConfig *deployerallowlist.ContractDeployerAllowListConfig `json:"contractDeployerAllowListConfig,omitempty"` // Config for the contract deployer allow list precompile - ContractNativeMinterConfig *nativeminter.ContractNativeMinterConfig `json:"contractNativeMinterConfig,omitempty"` // Config for the native minter precompile - TxAllowListConfig *txallowlist.TxAllowListConfig `json:"txAllowListConfig,omitempty"` // Config for the tx allow list precompile - FeeManagerConfig *feemanager.FeeManagerConfig `json:"feeManagerConfig,omitempty"` // Config for the fee manager precompile - RewardManagerConfig *rewardmanager.RewardManagerConfig `json:"rewardManagerConfig,omitempty"` // Config for the reward manager precompile - // ADD YOUR PRECOMPILE HERE - // {YourPrecompile}Config *precompile.{YourPrecompile}Config `json:"{yourPrecompile}Config,omitempty"` -} - -// getByAddress returns the precompile config for the given address. -func (p *PrecompileUpgrade) getByAddress(address common.Address) (precompile.StatefulPrecompileConfig, bool) { - switch address { - case precompile.ContractDeployerAllowListAddress: - return p.ContractDeployerAllowListConfig, p.ContractDeployerAllowListConfig != nil - case precompile.ContractNativeMinterAddress: - return p.ContractNativeMinterConfig, p.ContractNativeMinterConfig != nil - case precompile.TxAllowListAddress: - return p.TxAllowListConfig, p.TxAllowListConfig != nil - case precompile.FeeManagerAddress: - return p.FeeManagerConfig, p.FeeManagerConfig != nil - case precompile.RewardManagerAddress: - return p.RewardManagerConfig, p.RewardManagerConfig != nil - // ADD YOUR PRECOMPILE HERE - /* - case precompile.{YourPrecompile}Address: - return p.{YourPrecompile}Config, p.{YourPrecompile}Config != nil - */ - default: - panic(fmt.Sprintf("unknown precompile address: %v", address)) - } -} - -// verifyPrecompileUpgrades checks [c.PrecompileUpgrades] is well formed: -// - [upgrades] must specify exactly one key per PrecompileUpgrade -// - the specified blockTimestamps must monotonically increase -// - the specified blockTimestamps must be compatible with those -// specified in the chainConfig by genesis. -// - check a precompile is disabled before it is re-enabled -func (c *ChainConfig) verifyPrecompileUpgrades() error { - var lastBlockTimestamp *big.Int - for i, upgrade := range c.PrecompileUpgrades { - hasKey := false // used to verify if there is only one key per Upgrade - - for _, address := range precompile.UsedAddresses { - config, ok := upgrade.getByAddress(address) - if !ok { - continue - } - if hasKey { - return fmt.Errorf("PrecompileUpgrades[%d] has more than one key set", i) - } - configTimestamp := config.Timestamp() - if configTimestamp == nil { - return fmt.Errorf("PrecompileUpgrades[%d] cannot have a nil timestamp", i) - } - // Verify specified timestamps are monotonically increasing across all precompile keys. - // Note: It is OK for multiple configs of different keys to specify the same timestamp. - if lastBlockTimestamp != nil && configTimestamp.Cmp(lastBlockTimestamp) < 0 { - return fmt.Errorf("PrecompileUpgrades[%d] config timestamp (%v) < previous timestamp (%v)", i, configTimestamp, lastBlockTimestamp) - } - lastBlockTimestamp = configTimestamp - hasKey = true - } - if !hasKey { - return fmt.Errorf("empty precompile upgrade at index %d", i) - } - } - - for _, address := range precompile.UsedAddresses { - var ( - lastUpgraded *big.Int - disabled bool - ) - // check the genesis chain config for any enabled upgrade - if config, ok := c.PrecompileUpgrade.getByAddress(address); ok { - if err := config.Verify(); err != nil { - return err - } - disabled = false - lastUpgraded = config.Timestamp() - } else { - disabled = true - } - // next range over upgrades to verify correct use of disabled and blockTimestamps. - for i, upgrade := range c.PrecompileUpgrades { - config, ok := upgrade.getByAddress(address) - // Skip the upgrade if it's not relevant to [address]. - if !ok { - continue - } - - if disabled == config.IsDisabled() { - return fmt.Errorf("PrecompileUpgrades[%d] disable should be [%v]", i, !disabled) - } - if lastUpgraded != nil && (config.Timestamp().Cmp(lastUpgraded) <= 0) { - return fmt.Errorf("PrecompileUpgrades[%d] config timestamp (%v) <= previous timestamp (%v)", i, config.Timestamp(), lastUpgraded) - } - - if err := config.Verify(); err != nil { - return err - } - - disabled = config.IsDisabled() - lastUpgraded = config.Timestamp() - } - } - - return nil -} - -// getActivePrecompileConfig returns the most recent precompile config corresponding to [address]. -// If none have occurred, returns nil. -func (c *ChainConfig) getActivePrecompileConfig(blockTimestamp *big.Int, address common.Address, upgrades []PrecompileUpgrade) precompile.StatefulPrecompileConfig { - configs := c.getActivatingPrecompileConfigs(nil, blockTimestamp, address, upgrades) - if len(configs) == 0 { - return nil - } - return configs[len(configs)-1] // return the most recent config -} - -// getActivatingPrecompileConfigs returns all forks configured to activate during the state transition from a block with timestamp [from] -// to a block with timestamp [to]. -func (c *ChainConfig) getActivatingPrecompileConfigs(from *big.Int, to *big.Int, address common.Address, upgrades []PrecompileUpgrade) []precompile.StatefulPrecompileConfig { - configs := make([]precompile.StatefulPrecompileConfig, 0) - // First check the embedded [upgrade] for precompiles configured - // in the genesis chain config. - if config, ok := c.PrecompileUpgrade.getByAddress(address); ok { - if utils.IsForkTransition(config.Timestamp(), from, to) { - configs = append(configs, config) - } - } - // Loop over all upgrades checking for the requested precompile config. - for _, upgrade := range upgrades { - if config, ok := upgrade.getByAddress(address); ok { - // Check if the precompile activates in the specified range. - if utils.IsForkTransition(config.Timestamp(), from, to) { - configs = append(configs, config) - } - } - } - return configs -} - -func (c *ChainConfig) GetPrecompileConfig(address common.Address, blockTimestamp *big.Int) precompile.StatefulPrecompileConfig { - if val := c.getActivePrecompileConfig(blockTimestamp, address, c.PrecompileUpgrades); val != nil { - return val - } - return nil -} - -// TODO: remove this -func (c *ChainConfig) GetActivePrecompileUpgrade(blockTimestamp *big.Int) PrecompileUpgrade { - pu := PrecompileUpgrade{} - if config := c.GetPrecompileConfig(precompile.ContractDeployerAllowListAddress, blockTimestamp); config != nil && !config.IsDisabled() { - pu.ContractDeployerAllowListConfig = config.(*deployerallowlist.ContractDeployerAllowListConfig) - } - if config := c.GetPrecompileConfig(precompile.ContractNativeMinterAddress, blockTimestamp); config != nil && !config.IsDisabled() { - pu.ContractNativeMinterConfig = config.(*nativeminter.ContractNativeMinterConfig) - } - if config := c.GetPrecompileConfig(precompile.TxAllowListAddress, blockTimestamp); config != nil && !config.IsDisabled() { - pu.TxAllowListConfig = config.(*txallowlist.TxAllowListConfig) - } - if config := c.GetPrecompileConfig(precompile.FeeManagerAddress, blockTimestamp); config != nil && !config.IsDisabled() { - pu.FeeManagerConfig = config.(*feemanager.FeeManagerConfig) - } - if config := c.GetPrecompileConfig(precompile.RewardManagerAddress, blockTimestamp); config != nil && !config.IsDisabled() { - pu.RewardManagerConfig = config.(*rewardmanager.RewardManagerConfig) - } - - // ADD YOUR PRECOMPILE HERE - // if config := c.GetPrecompileConfig(precompile.{YourPrecompile}Address, blockTimestamp); config != nil && !config.IsDisabled() { - // pu.{YourPrecompile}Config = config.(*precompile.{YourPrecompile}Config) - // } - - return pu -} - -// CheckPrecompilesCompatible checks if [precompileUpgrades] are compatible with [c] at [headTimestamp]. -// Returns a ConfigCompatError if upgrades already forked at [headTimestamp] are missing from -// [precompileUpgrades]. Upgrades not already forked may be modified or absent from [precompileUpgrades]. -// Returns nil if [precompileUpgrades] is compatible with [c]. -// Assumes given timestamp is the last accepted block timestamp. -// This ensures that as long as the node has not accepted a block with a different rule set it will allow a new upgrade to be applied as long as it activates after the last accepted block. -func (c *ChainConfig) CheckPrecompilesCompatible(precompileUpgrades []PrecompileUpgrade, lastTimestamp *big.Int) *ConfigCompatError { - for _, address := range precompile.UsedAddresses { - if err := c.checkPrecompileCompatible(address, precompileUpgrades, lastTimestamp); err != nil { - return err - } - } - - return nil -} - -// checkPrecompileCompatible verifies that the precompile specified by [address] is compatible between [c] and [precompileUpgrades] at [headTimestamp]. -// Returns an error if upgrades already forked at [headTimestamp] are missing from [precompileUpgrades]. -// Upgrades that have already gone into effect cannot be modified or absent from [precompileUpgrades]. -func (c *ChainConfig) checkPrecompileCompatible(address common.Address, precompileUpgrades []PrecompileUpgrade, lastTimestamp *big.Int) *ConfigCompatError { - // all active upgrades must match - activeUpgrades := c.getActivatingPrecompileConfigs(nil, lastTimestamp, address, c.PrecompileUpgrades) - newUpgrades := c.getActivatingPrecompileConfigs(nil, lastTimestamp, address, precompileUpgrades) - - // first, check existing upgrades are there - for i, upgrade := range activeUpgrades { - if len(newUpgrades) <= i { - // missing upgrade - return newCompatError( - fmt.Sprintf("missing PrecompileUpgrade[%d]", i), - upgrade.Timestamp(), - nil, - ) - } - // All upgrades that have forked must be identical. - if !upgrade.Equal(newUpgrades[i]) { - return newCompatError( - fmt.Sprintf("PrecompileUpgrade[%d]", i), - upgrade.Timestamp(), - newUpgrades[i].Timestamp(), - ) - } - } - // then, make sure newUpgrades does not have additional upgrades - // that are already activated. (cannot perform retroactive upgrade) - if len(newUpgrades) > len(activeUpgrades) { - return newCompatError( - fmt.Sprintf("cannot retroactively enable PrecompileUpgrade[%d]", len(activeUpgrades)), - nil, - newUpgrades[len(activeUpgrades)].Timestamp(), // this indexes to the first element in newUpgrades after the end of activeUpgrades - ) - } - - return nil -} - -// EnabledStatefulPrecompiles returns a slice of stateful precompile configs that -// have been activated through an upgrade. -func (c *ChainConfig) EnabledStatefulPrecompiles(blockTimestamp *big.Int) []precompile.StatefulPrecompileConfig { - statefulPrecompileConfigs := make([]precompile.StatefulPrecompileConfig, 0) - for _, address := range precompile.UsedAddresses { - if config := c.getActivePrecompileConfig(blockTimestamp, address, c.PrecompileUpgrades); config != nil { - statefulPrecompileConfigs = append(statefulPrecompileConfigs, config) - } - } - - return statefulPrecompileConfigs -} - -// ConfigurePrecompiles checks if any of the precompiles specified by the chain config are enabled or disabled by the block -// transition from [parentTimestamp] to the timestamp set in [blockContext]. If this is the case, it calls [Configure] -// or [Deconfigure] to apply the necessary state transitions for the upgrade. -// This function is called: -// - within genesis setup to configure the starting state for precompiles enabled at genesis, -// - during block processing to update the state before processing the given block. -func (c *ChainConfig) ConfigurePrecompiles(parentTimestamp *big.Int, blockContext precompile.BlockContext, statedb precompile.StateDB) error { - blockTimestamp := blockContext.Timestamp() - for _, address := range precompile.UsedAddresses { // Note: configure precompiles in a deterministic order. - for _, config := range c.getActivatingPrecompileConfigs(parentTimestamp, blockTimestamp, address, c.PrecompileUpgrades) { - // If this transition activates the upgrade, configure the stateful precompile. - // (or deconfigure it if it is being disabled.) - if config.IsDisabled() { - log.Info("Disabling precompile", "precompileAddress", address) // TODO: use proper names for precompiles - statedb.Suicide(config.Address()) - // Calling Finalise here effectively commits Suicide call and wipes the contract state. - // This enables re-configuration of the same contract state in the same block. - // Without an immediate Finalise call after the Suicide, a reconfigured precompiled state can be wiped out - // since Suicide will be committed after the reconfiguration. - statedb.Finalise(true) - } else { - log.Info("Activating new precompile", "precompileAddress", address, "config", config) - if err := precompile.Configure(c, blockContext, config, statedb); err != nil { - return fmt.Errorf("could not configure precompile, precompileAddress: %s, reason: %w", address, err) - } - } - } - } - return nil -} diff --git a/params/precompile_config_test.go b/params/precompile_config_test.go index 4d7a114d14..bb77d912e1 100644 --- a/params/precompile_config_test.go +++ b/params/precompile_config_test.go @@ -4,14 +4,16 @@ package params import ( + "encoding/json" "math/big" "testing" "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/precompile" - "github.com/ava-labs/subnet-evm/precompile/deployerallowlist" - "github.com/ava-labs/subnet-evm/precompile/feemanager" - "github.com/ava-labs/subnet-evm/precompile/txallowlist" + "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" + "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" + "github.com/ava-labs/subnet-evm/precompile/contracts/nativeminter" + "github.com/ava-labs/subnet-evm/precompile/contracts/rewardmanager" + "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -21,17 +23,17 @@ func TestVerifyWithChainConfig(t *testing.T) { admins := []common.Address{{1}} baseConfig := *SubnetEVMDefaultChainConfig config := &baseConfig - config.PrecompileUpgrade = PrecompileUpgrade{ - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(2), nil, nil), + config.GenesisPrecompiles = ChainConfigPrecompiles{ + txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(2), nil, nil), } config.PrecompileUpgrades = []PrecompileUpgrade{ { // disable TxAllowList at timestamp 4 - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(4)), + txallowlist.NewDisableConfig(big.NewInt(4)), }, { // re-enable TxAllowList at timestamp 5 - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(5), admins, nil), + txallowlist.NewConfig(big.NewInt(5), admins, nil), }, } @@ -44,18 +46,18 @@ func TestVerifyWithChainConfig(t *testing.T) { badConfig.PrecompileUpgrades = append( badConfig.PrecompileUpgrades, PrecompileUpgrade{ - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(5)), + Config: txallowlist.NewDisableConfig(big.NewInt(5)), }, ) err = badConfig.Verify() - assert.ErrorContains(t, err, "config timestamp (5) <= previous timestamp (5)") + assert.ErrorContains(t, err, "config block timestamp (5) <= previous timestamp (5) of same key") // cannot enable a precompile without disabling it first. badConfig = *config badConfig.PrecompileUpgrades = append( badConfig.PrecompileUpgrades, PrecompileUpgrade{ - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(5), admins, nil), + Config: txallowlist.NewConfig(big.NewInt(5), admins, nil), }, ) err = badConfig.Verify() @@ -73,10 +75,10 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { name: "enable and disable tx allow list", upgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(1), admins, nil), + Config: txallowlist.NewConfig(big.NewInt(1), admins, nil), }, { - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(2)), + Config: txallowlist.NewDisableConfig(big.NewInt(2)), }, }, expectedError: "", @@ -85,13 +87,13 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { name: "invalid allow list config in tx allowlist", upgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(1), admins, nil), + Config: txallowlist.NewConfig(big.NewInt(1), admins, nil), }, { - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(2)), + Config: txallowlist.NewDisableConfig(big.NewInt(2)), }, { - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(3), admins, admins), + Config: txallowlist.NewConfig(big.NewInt(3), admins, admins), }, }, expectedError: "cannot set address", @@ -100,7 +102,7 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { name: "invalid initial fee manager config", upgrades: []PrecompileUpgrade{ { - FeeManagerConfig: feemanager.NewFeeManagerConfig(big.NewInt(3), admins, nil, + Config: feemanager.NewConfig(big.NewInt(3), admins, nil, &commontype.FeeConfig{ GasLimit: big.NewInt(-1), }), @@ -112,7 +114,7 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { name: "invalid initial fee manager config gas limit 0", upgrades: []PrecompileUpgrade{ { - FeeManagerConfig: feemanager.NewFeeManagerConfig(big.NewInt(3), admins, nil, + Config: feemanager.NewConfig(big.NewInt(3), admins, nil, &commontype.FeeConfig{ GasLimit: big.NewInt(0), }), @@ -120,6 +122,42 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { }, expectedError: "gasLimit = 0 cannot be less than or equal to 0", }, + { + name: "different upgrades are allowed to configure same timestamp for different precompiles", + upgrades: []PrecompileUpgrade{ + { + Config: txallowlist.NewConfig(big.NewInt(1), admins, nil), + }, + { + Config: feemanager.NewConfig(big.NewInt(1), admins, nil, nil), + }, + }, + expectedError: "", + }, + { + name: "different upgrades must be monotonically increasing", + upgrades: []PrecompileUpgrade{ + { + Config: txallowlist.NewConfig(big.NewInt(2), admins, nil), + }, + { + Config: feemanager.NewConfig(big.NewInt(1), admins, nil, nil), + }, + }, + expectedError: "config block timestamp (1) < previous timestamp (2)", + }, + { + name: "upgrades with same keys are not allowed to configure same timestamp for same precompiles", + upgrades: []PrecompileUpgrade{ + { + Config: txallowlist.NewConfig(big.NewInt(1), admins, nil), + }, + { + Config: txallowlist.NewDisableConfig(big.NewInt(1)), + }, + }, + expectedError: " config block timestamp (1) <= previous timestamp (1) of same key", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -142,20 +180,20 @@ func TestVerifyPrecompiles(t *testing.T) { admins := []common.Address{{1}} tests := []struct { name string - upgrade PrecompileUpgrade + precompiles ChainConfigPrecompiles expectedError string }{ { name: "invalid allow list config in tx allowlist", - upgrade: PrecompileUpgrade{ - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(3), admins, admins), + precompiles: ChainConfigPrecompiles{ + txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(3), admins, admins), }, expectedError: "cannot set address", }, { name: "invalid initial fee manager config", - upgrade: PrecompileUpgrade{ - FeeManagerConfig: feemanager.NewFeeManagerConfig(big.NewInt(3), admins, nil, + precompiles: ChainConfigPrecompiles{ + feemanager.ConfigKey: feemanager.NewConfig(big.NewInt(3), admins, nil, &commontype.FeeConfig{ GasLimit: big.NewInt(-1), }), @@ -168,7 +206,7 @@ func TestVerifyPrecompiles(t *testing.T) { require := require.New(t) baseConfig := *SubnetEVMDefaultChainConfig config := &baseConfig - config.PrecompileUpgrade = tt.upgrade + config.GenesisPrecompiles = tt.precompiles err := config.Verify() if tt.expectedError == "" { @@ -186,35 +224,93 @@ func TestVerifyRequiresSortedTimestamps(t *testing.T) { config := &baseConfig config.PrecompileUpgrades = []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(2), admins, nil), + Config: txallowlist.NewConfig(big.NewInt(2), admins, nil), }, { - ContractDeployerAllowListConfig: deployerallowlist.NewContractDeployerAllowListConfig(big.NewInt(1), admins, nil), + Config: deployerallowlist.NewConfig(big.NewInt(1), admins, nil), }, } // block timestamps must be monotonically increasing, so this config is invalid err := config.Verify() - assert.ErrorContains(t, err, "config timestamp (1) < previous timestamp (2)") + assert.ErrorContains(t, err, "config block timestamp (1) < previous timestamp (2)") } func TestGetPrecompileConfig(t *testing.T) { assert := assert.New(t) baseConfig := *SubnetEVMDefaultChainConfig config := &baseConfig - config.PrecompileUpgrade = PrecompileUpgrade{ - ContractDeployerAllowListConfig: deployerallowlist.NewContractDeployerAllowListConfig(big.NewInt(10), nil, nil), + config.GenesisPrecompiles = ChainConfigPrecompiles{ + deployerallowlist.ConfigKey: deployerallowlist.NewConfig(big.NewInt(10), nil, nil), } - deployerConfig := config.GetPrecompileConfig(precompile.ContractDeployerAllowListAddress, big.NewInt(0)) + deployerConfig := config.GetActivePrecompileConfig(deployerallowlist.ContractAddress, big.NewInt(0)) assert.Nil(deployerConfig) - deployerConfig = config.GetPrecompileConfig(precompile.ContractDeployerAllowListAddress, big.NewInt(10)) + deployerConfig = config.GetActivePrecompileConfig(deployerallowlist.ContractAddress, big.NewInt(10)) assert.NotNil(deployerConfig) - deployerConfig = config.GetPrecompileConfig(precompile.ContractDeployerAllowListAddress, big.NewInt(11)) + deployerConfig = config.GetActivePrecompileConfig(deployerallowlist.ContractAddress, big.NewInt(11)) assert.NotNil(deployerConfig) - txAllowListConfig := config.GetPrecompileConfig(precompile.TxAllowListAddress, big.NewInt(0)) + txAllowListConfig := config.GetActivePrecompileConfig(txallowlist.ContractAddress, big.NewInt(0)) assert.Nil(txAllowListConfig) } + +func TestPrecompileUpgradeUnmarshalJSON(t *testing.T) { + require := require.New(t) + + upgradeBytes := []byte(` + { + "precompileUpgrades": [ + { + "rewardManagerConfig": { + "blockTimestamp": 1671542573, + "adminAddresses": [ + "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" + ], + "initialRewardConfig": { + "allowFeeRecipients": true + } + } + }, + { + "contractNativeMinterConfig": { + "blockTimestamp": 1671543172, + "disable": false + } + } + ] + } + `) + + var upgradeConfig UpgradeConfig + err := json.Unmarshal(upgradeBytes, &upgradeConfig) + require.NoError(err) + + require.Len(upgradeConfig.PrecompileUpgrades, 2) + + rewardManagerConf := upgradeConfig.PrecompileUpgrades[0] + require.Equal(rewardManagerConf.Key(), rewardmanager.ConfigKey) + testRewardManagerConfig := rewardmanager.NewConfig( + big.NewInt(1671542573), + []common.Address{common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC")}, + nil, + &rewardmanager.InitialRewardConfig{ + AllowFeeRecipients: true, + }) + require.True(rewardManagerConf.Equal(testRewardManagerConfig)) + + nativeMinterConfig := upgradeConfig.PrecompileUpgrades[1] + require.Equal(nativeMinterConfig.Key(), nativeminter.ConfigKey) + expectedNativeMinterConfig := nativeminter.NewConfig(big.NewInt(1671543172), nil, nil, nil) + require.True(nativeMinterConfig.Equal(expectedNativeMinterConfig)) + + // Marshal and unmarshal again and check that the result is the same + upgradeBytes2, err := json.Marshal(upgradeConfig) + require.NoError(err) + var upgradeConfig2 UpgradeConfig + err = json.Unmarshal(upgradeBytes2, &upgradeConfig2) + require.NoError(err) + require.Equal(upgradeConfig, upgradeConfig2) +} diff --git a/params/precompile_upgrade.go b/params/precompile_upgrade.go new file mode 100644 index 0000000000..1bcff4e6b4 --- /dev/null +++ b/params/precompile_upgrade.go @@ -0,0 +1,260 @@ +// (c) 2022 Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package params + +import ( + "encoding/json" + "errors" + "fmt" + "math/big" + + "github.com/ava-labs/subnet-evm/precompile/config" + precompileConfig "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ava-labs/subnet-evm/utils" + "github.com/ethereum/go-ethereum/common" +) + +var errNoKey = errors.New("PrecompileUpgrade cannot be empty") + +// PrecompileUpgrade is a helper struct embedded in UpgradeConfig. +// It is used to unmarshal the json into the correct precompile config type +// based on the key. Keys are defined in each precompile module, and registered in +// precompile/registry/registry.go. +type PrecompileUpgrade struct { + config.Config +} + +// UnmarshalJSON unmarshals the json into the correct precompile config type +// based on the key. Keys are defined in each precompile module, and registered in +// params/precompile_modules.go. +// precompile/registry/registry.go. +// Ex: {"feeManagerConfig": {...}} where "feeManagerConfig" is the key +func (u *PrecompileUpgrade) UnmarshalJSON(data []byte) error { + raw := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + if len(raw) == 0 { + return errNoKey + } + if len(raw) > 1 { + return fmt.Errorf("PrecompileUpgrade must have exactly one key, got %d", len(raw)) + } + for key, value := range raw { + module, ok := modules.GetPrecompileModule(key) + if !ok { + return fmt.Errorf("unknown precompile config: %s", key) + } + config := module.NewConfig() + if err := json.Unmarshal(value, config); err != nil { + return err + } + u.Config = config + } + return nil +} + +// MarshalJSON marshal the precompile config into json based on the precompile key. +// Ex: {"feeManagerConfig": {...}} where "feeManagerConfig" is the key +func (u *PrecompileUpgrade) MarshalJSON() ([]byte, error) { + res := make(map[string]precompileConfig.Config) + res[u.Key()] = u.Config + return json.Marshal(res) +} + +// verifyPrecompileUpgrades checks [c.PrecompileUpgrades] is well formed: +// - [upgrades] must specify exactly one key per PrecompileUpgrade +// - the specified blockTimestamps must monotonically increase +// - the specified blockTimestamps must be compatible with those +// specified in the chainConfig by genesis. +// - check a precompile is disabled before it is re-enabled +func (c *ChainConfig) verifyPrecompileUpgrades() error { + // Store this struct to keep track of the last upgrade for each precompile key. + // Required for timestamp and disabled checks. + type lastUpgradeData struct { + blockTimestamp *big.Int + disabled bool + } + + lastPrecompileUpgrades := make(map[string]lastUpgradeData) + + // verify genesis precompiles + for key, config := range c.GenesisPrecompiles { + if err := config.Verify(); err != nil { + return err + } + // if the precompile is disabled at genesis, skip it. + if config.Timestamp() == nil { + continue + } + // check the genesis chain config for any enabled upgrade + lastPrecompileUpgrades[key] = lastUpgradeData{ + disabled: false, + blockTimestamp: config.Timestamp(), + } + } + + // next range over upgrades to verify correct use of disabled and blockTimestamps. + // previousUpgradeTimestamp is used to verify monotonically increasing timestamps. + var previousUpgradeTimestamp *big.Int + for i, upgrade := range c.PrecompileUpgrades { + key := upgrade.Key() + + // lastUpgradeByKey is the previous processed upgrade for this precompile key. + lastUpgradeByKey, ok := lastPrecompileUpgrades[key] + var ( + disabled bool + lastTimestamp *big.Int + ) + if !ok { + disabled = true + lastTimestamp = nil + } else { + disabled = lastUpgradeByKey.disabled + lastTimestamp = lastUpgradeByKey.blockTimestamp + } + upgradeTimestamp := upgrade.Timestamp() + + if upgradeTimestamp == nil { + return fmt.Errorf("PrecompileUpgrade (%s) at [%d]: block timestamp cannot be nil ", key, i) + } + // Verify specified timestamps are monotonically increasing across all precompile keys. + // Note: It is OK for multiple configs of DIFFERENT keys to specify the same timestamp. + if previousUpgradeTimestamp != nil && upgradeTimestamp.Cmp(previousUpgradeTimestamp) < 0 { + return fmt.Errorf("PrecompileUpgrade (%s) at [%d]: config block timestamp (%v) < previous timestamp (%v)", key, i, upgradeTimestamp, previousUpgradeTimestamp) + } + + if disabled == upgrade.IsDisabled() { + return fmt.Errorf("PrecompileUpgrade (%s) at [%d]: disable should be [%v]", key, i, !disabled) + } + // Verify specified timestamps are monotonically increasing across same precompile keys. + // Note: It is NOT OK for multiple configs of the SAME key to specify the same timestamp. + if lastTimestamp != nil && (upgradeTimestamp.Cmp(lastTimestamp) <= 0) { + return fmt.Errorf("PrecompileUpgrade (%s) at [%d]: config block timestamp (%v) <= previous timestamp (%v) of same key", key, i, upgradeTimestamp, lastTimestamp) + } + + if err := upgrade.Verify(); err != nil { + return err + } + + lastPrecompileUpgrades[key] = lastUpgradeData{ + disabled: upgrade.IsDisabled(), + blockTimestamp: upgradeTimestamp, + } + + previousUpgradeTimestamp = upgradeTimestamp + } + + return nil +} + +// GetActivePrecompileConfig returns the most recent precompile config corresponding to [address]. +// If none have occurred, returns nil. +func (c *ChainConfig) GetActivePrecompileConfig(address common.Address, blockTimestamp *big.Int) config.Config { + configs := c.GetActivatingPrecompileConfigs(address, nil, blockTimestamp, c.PrecompileUpgrades) + if len(configs) == 0 { + return nil + } + return configs[len(configs)-1] // return the most recent config +} + +// GetActivatingPrecompileConfigs returns all upgrades configured to activate during the state transition from a block with timestamp [from] +// to a block with timestamp [to]. +func (c *ChainConfig) GetActivatingPrecompileConfigs(address common.Address, from *big.Int, to *big.Int, upgrades []PrecompileUpgrade) []config.Config { + // Get key from address. + module, ok := modules.GetPrecompileModuleByAddress(address) + if !ok { + return nil + } + configs := make([]config.Config, 0) + key := module.ConfigKey + // First check the embedded [upgrade] for precompiles configured + // in the genesis chain config. + if config, ok := c.GenesisPrecompiles[key]; ok { + if utils.IsForkTransition(config.Timestamp(), from, to) { + configs = append(configs, config) + } + } + // Loop over all upgrades checking for the requested precompile config. + for _, upgrade := range upgrades { + if upgrade.Key() == key { + // Check if the precompile activates in the specified range. + if utils.IsForkTransition(upgrade.Timestamp(), from, to) { + configs = append(configs, upgrade.Config) + } + } + } + return configs +} + +// CheckPrecompilesCompatible checks if [precompileUpgrades] are compatible with [c] at [headTimestamp]. +// Returns a ConfigCompatError if upgrades already activated at [headTimestamp] are missing from +// [precompileUpgrades]. Upgrades not already activated may be modified or absent from [precompileUpgrades]. +// Returns nil if [precompileUpgrades] is compatible with [c]. +// Assumes given timestamp is the last accepted block timestamp. +// This ensures that as long as the node has not accepted a block with a different rule set it will allow a +// new upgrade to be applied as long as it activates after the last accepted block. +func (c *ChainConfig) CheckPrecompilesCompatible(precompileUpgrades []PrecompileUpgrade, lastTimestamp *big.Int) *ConfigCompatError { + for _, module := range modules.RegisteredModules() { + if err := c.checkPrecompileCompatible(module.Address, precompileUpgrades, lastTimestamp); err != nil { + return err + } + } + + return nil +} + +// checkPrecompileCompatible verifies that the precompile specified by [address] is compatible between [c] +// and [precompileUpgrades] at [headTimestamp]. +// Returns an error if upgrades already activated at [headTimestamp] are missing from [precompileUpgrades]. +// Upgrades that have already gone into effect cannot be modified or absent from [precompileUpgrades]. +func (c *ChainConfig) checkPrecompileCompatible(address common.Address, precompileUpgrades []PrecompileUpgrade, lastTimestamp *big.Int) *ConfigCompatError { + // All active upgrades (from nil to [lastTimestamp]) must match. + activeUpgrades := c.GetActivatingPrecompileConfigs(address, nil, lastTimestamp, c.PrecompileUpgrades) + newUpgrades := c.GetActivatingPrecompileConfigs(address, nil, lastTimestamp, precompileUpgrades) + + // Check activated upgrades are still present. + for i, upgrade := range activeUpgrades { + if len(newUpgrades) <= i { + // missing upgrade + return newCompatError( + fmt.Sprintf("missing PrecompileUpgrade[%d]", i), + upgrade.Timestamp(), + nil, + ) + } + // All upgrades that have activated must be identical. + if !upgrade.Equal(newUpgrades[i]) { + return newCompatError( + fmt.Sprintf("PrecompileUpgrade[%d]", i), + upgrade.Timestamp(), + newUpgrades[i].Timestamp(), + ) + } + } + // then, make sure newUpgrades does not have additional upgrades + // that are already activated. (cannot perform retroactive upgrade) + if len(newUpgrades) > len(activeUpgrades) { + return newCompatError( + fmt.Sprintf("cannot retroactively enable PrecompileUpgrade[%d]", len(activeUpgrades)), + nil, + newUpgrades[len(activeUpgrades)].Timestamp(), // this indexes to the first element in newUpgrades after the end of activeUpgrades + ) + } + + return nil +} + +// EnabledStatefulPrecompiles returns current stateful precompile configs that are enabled at [blockTimestamp]. +func (c *ChainConfig) EnabledStatefulPrecompiles(blockTimestamp *big.Int) ChainConfigPrecompiles { + statefulPrecompileConfigs := make(ChainConfigPrecompiles) + for _, module := range modules.RegisteredModules() { + if config := c.GetActivePrecompileConfig(module.Address, blockTimestamp); config != nil && !config.IsDisabled() { + statefulPrecompileConfigs[module.ConfigKey] = config + } + } + + return statefulPrecompileConfigs +} diff --git a/params/upgrade_config_test.go b/params/precompile_upgrade_test.go similarity index 64% rename from params/upgrade_config_test.go rename to params/precompile_upgrade_test.go index e8e5fb9750..3048693f65 100644 --- a/params/upgrade_config_test.go +++ b/params/precompile_upgrade_test.go @@ -7,8 +7,8 @@ import ( "math/big" "testing" - "github.com/ava-labs/subnet-evm/precompile/deployerallowlist" - "github.com/ava-labs/subnet-evm/precompile/txallowlist" + "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" + "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" ) @@ -16,7 +16,9 @@ import ( func TestVerifyUpgradeConfig(t *testing.T) { admins := []common.Address{{1}} chainConfig := *TestChainConfig - chainConfig.TxAllowListConfig = txallowlist.NewTxAllowListConfig(big.NewInt(1), admins, nil) + chainConfig.GenesisPrecompiles = ChainConfigPrecompiles{ + txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(1), admins, nil), + } type test struct { upgrades []PrecompileUpgrade @@ -28,23 +30,23 @@ func TestVerifyUpgradeConfig(t *testing.T) { expectedErrorString: "disable should be [true]", upgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(2), admins, nil), + Config: txallowlist.NewConfig(big.NewInt(2), admins, nil), }, }, }, "upgrade bytes conflicts with genesis (disable before enable)": { - expectedErrorString: "config timestamp (0) <= previous timestamp (1)", + expectedErrorString: "config block timestamp (0) <= previous timestamp (1) of same key", upgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(0)), + Config: txallowlist.NewDisableConfig(big.NewInt(0)), }, }, }, "upgrade bytes conflicts with genesis (disable same time as enable)": { - expectedErrorString: "config timestamp (1) <= previous timestamp (1)", + expectedErrorString: "config block timestamp (1) <= previous timestamp (1) of same key", upgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(1)), + Config: txallowlist.NewDisableConfig(big.NewInt(1)), }, }, }, @@ -71,8 +73,10 @@ func TestVerifyUpgradeConfig(t *testing.T) { func TestCheckCompatibleUpgradeConfigs(t *testing.T) { admins := []common.Address{{1}} chainConfig := *TestChainConfig - chainConfig.TxAllowListConfig = txallowlist.NewTxAllowListConfig(big.NewInt(1), admins, nil) - chainConfig.ContractDeployerAllowListConfig = deployerallowlist.NewContractDeployerAllowListConfig(big.NewInt(10), admins, nil) + chainConfig.GenesisPrecompiles = ChainConfigPrecompiles{ + txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(1), admins, nil), + deployerallowlist.ConfigKey: deployerallowlist.NewConfig(big.NewInt(10), admins, nil), + } type test struct { configs []*UpgradeConfig @@ -87,10 +91,10 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), + Config: txallowlist.NewDisableConfig(big.NewInt(6)), }, { - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), admins, nil), + Config: txallowlist.NewConfig(big.NewInt(7), admins, nil), }, }, }, @@ -102,20 +106,20 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), + Config: txallowlist.NewDisableConfig(big.NewInt(6)), }, { - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), admins, nil), + Config: txallowlist.NewConfig(big.NewInt(7), admins, nil), }, }, }, { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), + Config: txallowlist.NewDisableConfig(big.NewInt(6)), }, { - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(8), admins, nil), + Config: txallowlist.NewConfig(big.NewInt(8), admins, nil), }, }, }, @@ -128,20 +132,20 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), + Config: txallowlist.NewDisableConfig(big.NewInt(6)), }, { - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), admins, nil), + Config: txallowlist.NewConfig(big.NewInt(7), admins, nil), }, }, }, { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), + Config: txallowlist.NewDisableConfig(big.NewInt(6)), }, { - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(8), admins, nil), + Config: txallowlist.NewConfig(big.NewInt(8), admins, nil), }, }, }, @@ -153,17 +157,17 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), + Config: txallowlist.NewDisableConfig(big.NewInt(6)), }, { - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), admins, nil), + Config: txallowlist.NewConfig(big.NewInt(7), admins, nil), }, }, }, { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), + Config: txallowlist.NewDisableConfig(big.NewInt(6)), }, }, }, @@ -176,17 +180,17 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), + Config: txallowlist.NewDisableConfig(big.NewInt(6)), }, { - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), admins, nil), + Config: txallowlist.NewConfig(big.NewInt(7), admins, nil), }, }, }, { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), + Config: txallowlist.NewDisableConfig(big.NewInt(6)), }, }, }, @@ -199,21 +203,21 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), + Config: txallowlist.NewDisableConfig(big.NewInt(6)), }, { - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), admins, nil), + Config: txallowlist.NewConfig(big.NewInt(7), admins, nil), }, }, }, { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), + Config: txallowlist.NewDisableConfig(big.NewInt(6)), }, { // uses a different (empty) admin list, not allowed - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), []common.Address{}, nil), + Config: txallowlist.NewConfig(big.NewInt(7), []common.Address{}, nil), }, }, }, @@ -225,20 +229,20 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), + Config: txallowlist.NewDisableConfig(big.NewInt(6)), }, { - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), admins, nil), + Config: txallowlist.NewConfig(big.NewInt(7), admins, nil), }, }, }, { PrecompileUpgrades: []PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(6)), + Config: txallowlist.NewDisableConfig(big.NewInt(6)), }, { - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(7), admins, nil), + Config: txallowlist.NewConfig(big.NewInt(7), admins, nil), }, }, }, diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index ae114d010b..1721ed1f25 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -46,6 +46,9 @@ import ( // inside of cmd/geth. _ "github.com/ava-labs/subnet-evm/eth/tracers/native" + // Force-load precompiles to trigger registration + _ "github.com/ava-labs/subnet-evm/precompile/registry" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 5010a810c4..d97e23811f 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -19,12 +19,11 @@ import ( "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/metrics" - "github.com/ava-labs/subnet-evm/precompile" "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/deployerallowlist" - "github.com/ava-labs/subnet-evm/precompile/feemanager" - "github.com/ava-labs/subnet-evm/precompile/rewardmanager" - "github.com/ava-labs/subnet-evm/precompile/txallowlist" + "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" + "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" + "github.com/ava-labs/subnet-evm/precompile/contracts/rewardmanager" + "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" "github.com/ava-labs/subnet-evm/trie" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" @@ -2121,7 +2120,9 @@ func TestBuildAllowListActivationBlock(t *testing.T) { if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { t.Fatal(err) } - genesis.Config.ContractDeployerAllowListConfig = deployerallowlist.NewContractDeployerAllowListConfig(big.NewInt(time.Now().Unix()), testEthAddrs, nil) + genesis.Config.GenesisPrecompiles = params.ChainConfigPrecompiles{ + deployerallowlist.ConfigKey: deployerallowlist.NewConfig(big.NewInt(time.Now().Unix()), testEthAddrs, nil), + } genesisJSON, err := genesis.MarshalJSON() if err != nil { @@ -2143,8 +2144,8 @@ func TestBuildAllowListActivationBlock(t *testing.T) { t.Fatal(err) } role := deployerallowlist.GetContractDeployerAllowListStatus(genesisState, testEthAddrs[0]) - if role != allowlist.AllowListNoRole { - t.Fatalf("Expected allow list status to be set to no role: %s, but found: %s", allowlist.AllowListNoRole, role) + if role != allowlist.NoRole { + t.Fatalf("Expected allow list status to be set to no role: %s, but found: %s", allowlist.NoRole, role) } // Send basic transaction to construct a simple block and confirm that the precompile state configuration in the worker behaves correctly. @@ -2173,8 +2174,8 @@ func TestBuildAllowListActivationBlock(t *testing.T) { t.Fatal(err) } role = deployerallowlist.GetContractDeployerAllowListStatus(blkState, testEthAddrs[0]) - if role != allowlist.AllowListAdmin { - t.Fatalf("Expected allow list status to be set to Admin: %s, but found: %s", allowlist.AllowListAdmin, role) + if role != allowlist.AdminRole { + t.Fatalf("Expected allow list status to be set role %s, but found: %s", allowlist.AdminRole, role) } } @@ -2185,7 +2186,9 @@ func TestTxAllowListSuccessfulTx(t *testing.T) { if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { t.Fatal(err) } - genesis.Config.TxAllowListConfig = txallowlist.NewTxAllowListConfig(big.NewInt(0), testEthAddrs[0:1], nil) + genesis.Config.GenesisPrecompiles = params.ChainConfigPrecompiles{ + txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(0), testEthAddrs[0:1], nil), + } genesisJSON, err := genesis.MarshalJSON() if err != nil { t.Fatal(err) @@ -2208,12 +2211,12 @@ func TestTxAllowListSuccessfulTx(t *testing.T) { // Check that address 0 is whitelisted and address 1 is not role := txallowlist.GetTxAllowListStatus(genesisState, testEthAddrs[0]) - if role != allowlist.AllowListAdmin { - t.Fatalf("Expected allow list status to be set to admin: %s, but found: %s", allowlist.AllowListAdmin, role) + if role != allowlist.AdminRole { + t.Fatalf("Expected allow list status to be set to admin: %s, but found: %s", allowlist.AdminRole, role) } role = txallowlist.GetTxAllowListStatus(genesisState, testEthAddrs[1]) - if role != allowlist.AllowListNoRole { - t.Fatalf("Expected allow list status to be set to no role: %s, but found: %s", allowlist.AllowListNoRole, role) + if role != allowlist.NoRole { + t.Fatalf("Expected allow list status to be set to no role: %s, but found: %s", allowlist.NoRole, role) } // Submit a successful transaction @@ -2234,7 +2237,7 @@ func TestTxAllowListSuccessfulTx(t *testing.T) { } errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) - if err := errs[0]; !errors.Is(err, txallowlist.ErrSenderAddressNotAllowListed) { + if err := errs[0]; !errors.Is(err, vmerrs.ErrSenderAddressNotAllowListed) { t.Fatalf("expected ErrSenderAddressNotAllowListed, got: %s", err) } @@ -2261,7 +2264,9 @@ func TestTxAllowListDisablePrecompile(t *testing.T) { t.Fatal(err) } enableAllowListTimestamp := time.Unix(0, 0) // enable at genesis - genesis.Config.TxAllowListConfig = txallowlist.NewTxAllowListConfig(big.NewInt(enableAllowListTimestamp.Unix()), testEthAddrs[0:1], nil) + genesis.Config.GenesisPrecompiles = params.ChainConfigPrecompiles{ + txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(enableAllowListTimestamp.Unix()), testEthAddrs[0:1], nil), + } genesisJSON, err := genesis.MarshalJSON() if err != nil { t.Fatal(err) @@ -2303,12 +2308,12 @@ func TestTxAllowListDisablePrecompile(t *testing.T) { // Check that address 0 is whitelisted and address 1 is not role := txallowlist.GetTxAllowListStatus(genesisState, testEthAddrs[0]) - if role != allowlist.AllowListAdmin { - t.Fatalf("Expected allow list status to be set to admin: %s, but found: %s", allowlist.AllowListAdmin, role) + if role != allowlist.AdminRole { + t.Fatalf("Expected allow list status to be set to admin: %s, but found: %s", allowlist.AdminRole, role) } role = txallowlist.GetTxAllowListStatus(genesisState, testEthAddrs[1]) - if role != allowlist.AllowListNoRole { - t.Fatalf("Expected allow list status to be set to no role: %s, but found: %s", allowlist.AllowListNoRole, role) + if role != allowlist.NoRole { + t.Fatalf("Expected allow list status to be set to no role: %s, but found: %s", allowlist.NoRole, role) } // Submit a successful transaction @@ -2329,7 +2334,7 @@ func TestTxAllowListDisablePrecompile(t *testing.T) { } errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) - if err := errs[0]; !errors.Is(err, txallowlist.ErrSenderAddressNotAllowListed) { + if err := errs[0]; !errors.Is(err, vmerrs.ErrSenderAddressNotAllowListed) { t.Fatalf("expected ErrSenderAddressNotAllowListed, got: %s", err) } @@ -2373,7 +2378,9 @@ func TestFeeManagerChangeFee(t *testing.T) { if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { t.Fatal(err) } - genesis.Config.FeeManagerConfig = feemanager.NewFeeManagerConfig(big.NewInt(0), testEthAddrs[0:1], nil, nil) + genesis.Config.GenesisPrecompiles = params.ChainConfigPrecompiles{ + feemanager.ConfigKey: feemanager.NewConfig(big.NewInt(0), testEthAddrs[0:1], nil, nil), + } // set a lower fee config now testLowFeeConfig := commontype.FeeConfig{ @@ -2412,12 +2419,12 @@ func TestFeeManagerChangeFee(t *testing.T) { // Check that address 0 is whitelisted and address 1 is not role := feemanager.GetFeeManagerStatus(genesisState, testEthAddrs[0]) - if role != allowlist.AllowListAdmin { - t.Fatalf("Expected fee manager list status to be set to admin: %s, but found: %s", precompile.FeeManagerAddress, role) + if role != allowlist.AdminRole { + t.Fatalf("Expected fee manager list status to be set to admin: %s, but found: %s", allowlist.AdminRole, role) } role = feemanager.GetFeeManagerStatus(genesisState, testEthAddrs[1]) - if role != allowlist.AllowListNoRole { - t.Fatalf("Expected fee manager list status to be set to no role: %s, but found: %s", precompile.FeeManagerAddress, role) + if role != allowlist.NoRole { + t.Fatalf("Expected fee manager list status to be set to no role: %s, but found: %s", allowlist.AdminRole, role) } // Contract is initialized but no preconfig is given, reader should return genesis fee config feeConfig, lastChangedAt, err := vm.blockChain.GetFeeConfigAt(vm.blockChain.Genesis().Header()) @@ -2435,7 +2442,7 @@ func TestFeeManagerChangeFee(t *testing.T) { tx := types.NewTx(&types.DynamicFeeTx{ ChainID: genesis.Config.ChainID, Nonce: uint64(0), - To: &precompile.FeeManagerAddress, + To: &feemanager.ContractAddress, Gas: testLowFeeConfig.GasLimit.Uint64(), Value: common.Big0, GasFeeCap: testLowFeeConfig.MinBaseFee, // give low fee, it should work since we still haven't applied high fees @@ -2471,7 +2478,7 @@ func TestFeeManagerChangeFee(t *testing.T) { tx2 := types.NewTx(&types.DynamicFeeTx{ ChainID: genesis.Config.ChainID, Nonce: uint64(1), - To: &precompile.FeeManagerAddress, + To: &feemanager.ContractAddress, Gas: genesis.Config.FeeConfig.GasLimit.Uint64(), Value: common.Big0, GasFeeCap: testLowFeeConfig.MinBaseFee, // this is too low for applied config, should fail @@ -2613,7 +2620,9 @@ func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { genesis := &core.Genesis{} require.NoError(t, genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM))) - genesis.Config.RewardManagerConfig = rewardmanager.NewRewardManagerConfig(common.Big0, testEthAddrs[0:1], nil, nil) + genesis.Config.GenesisPrecompiles = params.ChainConfigPrecompiles{ + rewardmanager.ConfigKey: rewardmanager.NewConfig(common.Big0, testEthAddrs[0:1], nil, nil), + } genesis.Config.AllowFeeRecipients = true // enable this in genesis to test if this is recognized by the reward manager genesisJSON, err := genesis.MarshalJSON() require.NoError(t, err) @@ -2659,7 +2668,7 @@ func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { gas := 21000 + 240 + rewardmanager.SetRewardAddressGasCost // 21000 for tx, 240 for tx data - tx := types.NewTransaction(uint64(0), precompile.RewardManagerAddress, big.NewInt(1), gas, big.NewInt(testMinGasPrice), data) + tx := types.NewTransaction(uint64(0), rewardmanager.ContractAddress, big.NewInt(1), gas, big.NewInt(testMinGasPrice), data) signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) require.NoError(t, err) @@ -2753,7 +2762,9 @@ func TestRewardManagerPrecompileAllowFeeRecipients(t *testing.T) { genesis := &core.Genesis{} require.NoError(t, genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM))) - genesis.Config.RewardManagerConfig = rewardmanager.NewRewardManagerConfig(common.Big0, testEthAddrs[0:1], nil, nil) + genesis.Config.GenesisPrecompiles = params.ChainConfigPrecompiles{ + rewardmanager.ConfigKey: rewardmanager.NewConfig(common.Big0, testEthAddrs[0:1], nil, nil), + } genesis.Config.AllowFeeRecipients = false // disable this in genesis genesisJSON, err := genesis.MarshalJSON() require.NoError(t, err) @@ -2795,7 +2806,7 @@ func TestRewardManagerPrecompileAllowFeeRecipients(t *testing.T) { gas := 21000 + 240 + rewardmanager.AllowFeeRecipientsGasCost // 21000 for tx, 240 for tx data - tx := types.NewTransaction(uint64(0), precompile.RewardManagerAddress, big.NewInt(1), gas, big.NewInt(testMinGasPrice), data) + tx := types.NewTransaction(uint64(0), rewardmanager.ContractAddress, big.NewInt(1), gas, big.NewInt(testMinGasPrice), data) signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) require.NoError(t, err) diff --git a/plugin/evm/vm_upgrade_bytes_test.go b/plugin/evm/vm_upgrade_bytes_test.go index 2adca151fe..a4ac725a59 100644 --- a/plugin/evm/vm_upgrade_bytes_test.go +++ b/plugin/evm/vm_upgrade_bytes_test.go @@ -18,7 +18,8 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/metrics" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/txallowlist" + "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" + "github.com/ava-labs/subnet-evm/vmerrs" "github.com/stretchr/testify/assert" ) @@ -28,7 +29,7 @@ func TestVMUpgradeBytesPrecompile(t *testing.T) { upgradeConfig := ¶ms.UpgradeConfig{ PrecompileUpgrades: []params.PrecompileUpgrade{ { - TxAllowListConfig: txallowlist.NewTxAllowListConfig(big.NewInt(enableAllowListTimestamp.Unix()), testEthAddrs[0:1], nil), + Config: txallowlist.NewConfig(big.NewInt(enableAllowListTimestamp.Unix()), testEthAddrs[0:1], nil), }, }, } @@ -57,7 +58,7 @@ func TestVMUpgradeBytesPrecompile(t *testing.T) { t.Fatal(err) } errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) - if err := errs[0]; !errors.Is(err, txallowlist.ErrSenderAddressNotAllowListed) { + if err := errs[0]; !errors.Is(err, vmerrs.ErrSenderAddressNotAllowListed) { t.Fatalf("expected ErrSenderAddressNotAllowListed, got: %s", err) } @@ -71,7 +72,7 @@ func TestVMUpgradeBytesPrecompile(t *testing.T) { upgradeConfig.PrecompileUpgrades = append( upgradeConfig.PrecompileUpgrades, params.PrecompileUpgrade{ - TxAllowListConfig: txallowlist.NewDisableTxAllowListConfig(big.NewInt(disableAllowListTimestamp.Unix())), + Config: txallowlist.NewDisableConfig(big.NewInt(disableAllowListTimestamp.Unix())), }, ) upgradeBytesJSON, err = json.Marshal(upgradeConfig) @@ -108,7 +109,7 @@ func TestVMUpgradeBytesPrecompile(t *testing.T) { // Submit a rejected transaction, should throw an error errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) - if err := errs[0]; !errors.Is(err, txallowlist.ErrSenderAddressNotAllowListed) { + if err := errs[0]; !errors.Is(err, vmerrs.ErrSenderAddressNotAllowListed) { t.Fatalf("expected ErrSenderAddressNotAllowListed, got: %s", err) } diff --git a/precompile/allowlist/allow_list.go b/precompile/allowlist/allow_list.go deleted file mode 100644 index ced37ed530..0000000000 --- a/precompile/allowlist/allow_list.go +++ /dev/null @@ -1,240 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package allowlist - -import ( - "errors" - "fmt" - "math/big" - - "github.com/ava-labs/subnet-evm/precompile" - "github.com/ava-labs/subnet-evm/vmerrs" - "github.com/ethereum/go-ethereum/common" -) - -const ( - SetAdminFuncKey = "setAdmin" - SetEnabledFuncKey = "setEnabled" - SetNoneFuncKey = "setNone" - ReadAllowListFuncKey = "readAllowList" - - ModifyAllowListGasCost = precompile.WriteGasCostPerSlot - ReadAllowListGasCost = precompile.ReadGasCostPerSlot -) - -var ( - AllowListNoRole AllowListRole = AllowListRole(common.BigToHash(big.NewInt(0))) // No role assigned - this is equivalent to common.Hash{} and deletes the key from the DB when set - AllowListEnabled AllowListRole = AllowListRole(common.BigToHash(big.NewInt(1))) // Deployers are allowed to create new contracts - AllowListAdmin AllowListRole = AllowListRole(common.BigToHash(big.NewInt(2))) // Admin - allowed to modify both the admin and deployer list as well as deploy contracts - - // AllowList function signatures - setAdminSignature = precompile.CalculateFunctionSelector("setAdmin(address)") - setEnabledSignature = precompile.CalculateFunctionSelector("setEnabled(address)") - setNoneSignature = precompile.CalculateFunctionSelector("setNone(address)") - readAllowListSignature = precompile.CalculateFunctionSelector("readAllowList(address)") - // Error returned when an invalid write is attempted - ErrCannotModifyAllowList = errors.New("non-admin cannot modify allow list") - - allowListInputLen = common.HashLength -) - -// AllowListConfig specifies the initial set of allow list admins. -type AllowListConfig struct { - AllowListAdmins []common.Address `json:"adminAddresses"` - EnabledAddresses []common.Address `json:"enabledAddresses"` // initial enabled addresses -} - -// Configure initializes the address space of [precompileAddr] by initializing the role of each of -// the addresses in [AllowListAdmins]. -func (c *AllowListConfig) Configure(state precompile.StateDB, precompileAddr common.Address) error { - for _, enabledAddr := range c.EnabledAddresses { - SetAllowListRole(state, precompileAddr, enabledAddr, AllowListEnabled) - } - for _, adminAddr := range c.AllowListAdmins { - SetAllowListRole(state, precompileAddr, adminAddr, AllowListAdmin) - } - return nil -} - -// Equal returns true iff [other] has the same admins in the same order in its allow list. -func (c *AllowListConfig) Equal(other *AllowListConfig) bool { - if other == nil { - return false - } - if !areEqualAddressLists(c.AllowListAdmins, other.AllowListAdmins) { - return false - } - - return areEqualAddressLists(c.EnabledAddresses, other.EnabledAddresses) -} - -// areEqualAddressLists returns true iff [a] and [b] have the same addresses in the same order. -func areEqualAddressLists(current []common.Address, other []common.Address) bool { - if len(current) != len(other) { - return false - } - for i, address := range current { - if address != other[i] { - return false - } - } - return true -} - -// Verify returns an error if there is an overlapping address between admin and enabled roles -func (c *AllowListConfig) Verify() error { - // return early if either list is empty - if len(c.EnabledAddresses) == 0 || len(c.AllowListAdmins) == 0 { - return nil - } - - addressMap := make(map[common.Address]bool) - for _, enabledAddr := range c.EnabledAddresses { - // check for duplicates - if _, ok := addressMap[enabledAddr]; ok { - return fmt.Errorf("duplicate address %s in enabled list", enabledAddr) - } - addressMap[enabledAddr] = false - } - - for _, adminAddr := range c.AllowListAdmins { - // check for overlap between enabled and admin lists - if inAdmin, ok := addressMap[adminAddr]; ok { - if inAdmin { - return fmt.Errorf("duplicate address %s in admin list", adminAddr) - } else { - return fmt.Errorf("cannot set address %s as both admin and enabled", adminAddr) - } - } - addressMap[adminAddr] = true - } - - return nil -} - -// GetAllowListStatus returns the allow list role of [address] for the precompile -// at [precompileAddr] -func GetAllowListStatus(state precompile.StateDB, precompileAddr common.Address, address common.Address) AllowListRole { - // Generate the state key for [address] - addressKey := address.Hash() - return AllowListRole(state.GetState(precompileAddr, addressKey)) -} - -// SetAllowListRole sets the permissions of [address] to [role] for the precompile -// at [precompileAddr]. -// assumes [role] has already been verified as valid. -func SetAllowListRole(stateDB precompile.StateDB, precompileAddr, address common.Address, role AllowListRole) { - // Generate the state key for [address] - addressKey := address.Hash() - // Assign [role] to the address - // This stores the [role] in the contract storage with address [precompileAddr] - // and [addressKey] hash. It means that any reusage of the [addressKey] for different value - // conflicts with the same slot [role] is stored. - // Precompile implementations must use a different key than [addressKey] - stateDB.SetState(precompileAddr, addressKey, common.Hash(role)) -} - -// PackModifyAllowList packs [address] and [role] into the appropriate arguments for modifying the allow list. -// Note: [role] is not packed in the input value returned, but is instead used as a selector for the function -// selector that should be encoded in the input. -func PackModifyAllowList(address common.Address, role AllowListRole) ([]byte, error) { - // function selector (4 bytes) + hash for address - input := make([]byte, 0, precompile.SelectorLen+common.HashLength) - - switch role { - case AllowListAdmin: - input = append(input, setAdminSignature...) - case AllowListEnabled: - input = append(input, setEnabledSignature...) - case AllowListNoRole: - input = append(input, setNoneSignature...) - default: - return nil, fmt.Errorf("cannot pack modify list input with invalid role: %s", role) - } - - input = append(input, address.Hash().Bytes()...) - return input, nil -} - -// PackReadAllowList packs [address] into the input data to the read allow list function -func PackReadAllowList(address common.Address) []byte { - input := make([]byte, 0, precompile.SelectorLen+common.HashLength) - input = append(input, readAllowListSignature...) - input = append(input, address.Hash().Bytes()...) - return input -} - -// createAllowListRoleSetter returns an execution function for setting the allow list status of the input address argument to [role]. -// This execution function is speciifc to [precompileAddr]. -func createAllowListRoleSetter(precompileAddr common.Address, role AllowListRole) precompile.RunStatefulPrecompileFunc { - return func(evm precompile.PrecompileAccessibleState, callerAddr, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = precompile.DeductGas(suppliedGas, ModifyAllowListGasCost); err != nil { - return nil, 0, err - } - - if len(input) != allowListInputLen { - return nil, remainingGas, fmt.Errorf("invalid input length for modifying allow list: %d", len(input)) - } - - modifyAddress := common.BytesToAddress(input) - - if readOnly { - return nil, remainingGas, vmerrs.ErrWriteProtection - } - - stateDB := evm.GetStateDB() - - // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := GetAllowListStatus(stateDB, precompileAddr, callerAddr) - if !callerStatus.IsAdmin() { - return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotModifyAllowList, callerAddr) - } - - SetAllowListRole(stateDB, precompileAddr, modifyAddress, role) - // Return an empty output and the remaining gas - return []byte{}, remainingGas, nil - } -} - -// createReadAllowList returns an execution function that reads the allow list for the given [precompileAddr]. -// The execution function parses the input into a single address and returns the 32 byte hash that specifies the -// designated role of that address -func createReadAllowList(precompileAddr common.Address) precompile.RunStatefulPrecompileFunc { - return func(evm precompile.PrecompileAccessibleState, callerAddr common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = precompile.DeductGas(suppliedGas, ReadAllowListGasCost); err != nil { - return nil, 0, err - } - - if len(input) != allowListInputLen { - return nil, remainingGas, fmt.Errorf("invalid input length for read allow list: %d", len(input)) - } - - readAddress := common.BytesToAddress(input) - role := GetAllowListStatus(evm.GetStateDB(), precompileAddr, readAddress) - roleBytes := common.Hash(role).Bytes() - return roleBytes, remainingGas, nil - } -} - -// createAllowListPrecompile returns a StatefulPrecompiledContract with R/W control of an allow list at [precompileAddr] -func CreateAllowListPrecompile(precompileAddr common.Address) precompile.StatefulPrecompiledContract { - // Construct the contract with no fallback function. - allowListFuncs := CreateAllowListFunctions(precompileAddr) - contract, err := precompile.NewStatefulPrecompileContract(nil, allowListFuncs) - // TODO Change this to be returned as an error after refactoring this precompile - // to use the new precompile template. - if err != nil { - panic(err) - } - return contract -} - -func CreateAllowListFunctions(precompileAddr common.Address) []*precompile.StatefulPrecompileFunction { - setAdmin := precompile.NewStatefulPrecompileFunction(setAdminSignature, createAllowListRoleSetter(precompileAddr, AllowListAdmin)) - setEnabled := precompile.NewStatefulPrecompileFunction(setEnabledSignature, createAllowListRoleSetter(precompileAddr, AllowListEnabled)) - setNone := precompile.NewStatefulPrecompileFunction(setNoneSignature, createAllowListRoleSetter(precompileAddr, AllowListNoRole)) - read := precompile.NewStatefulPrecompileFunction(readAllowListSignature, createReadAllowList(precompileAddr)) - - return []*precompile.StatefulPrecompileFunction{setAdmin, setEnabled, setNone, read} -} diff --git a/precompile/allowlist/allow_list_test.go b/precompile/allowlist/allow_list_test.go new file mode 100644 index 0000000000..3a9f4f2dd3 --- /dev/null +++ b/precompile/allowlist/allow_list_test.go @@ -0,0 +1,283 @@ +// (c) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package allowlist + +import ( + "testing" + + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/subnet-evm/core/rawdb" + "github.com/ava-labs/subnet-evm/core/state" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/vmerrs" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestAllowListRun(t *testing.T) { + type test struct { + caller common.Address + input func() []byte + suppliedGas uint64 + readOnly bool + + expectedRes []byte + expectedErr string + + config *Config + + assertState func(t *testing.T, state *state.StateDB) + } + + adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") + enabledAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") + noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") + dummyContractAddr := common.HexToAddress("0x0000000000000000000000000000000000000000") + testAllowListPrecompile := CreateAllowListPrecompile(dummyContractAddr) + + for name, test := range map[string]test{ + "set admin": { + caller: adminAddr, + input: func() []byte { + input, err := PackModifyAllowList(noRoleAddr, AdminRole) + require.NoError(t, err) + + return input + }, + suppliedGas: ModifyAllowListGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + res := GetAllowListStatus(state, dummyContractAddr, noRoleAddr) + require.Equal(t, AdminRole, res) + }, + }, + "set enabled": { + caller: adminAddr, + input: func() []byte { + input, err := PackModifyAllowList(noRoleAddr, EnabledRole) + require.NoError(t, err) + + return input + }, + suppliedGas: ModifyAllowListGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + res := GetAllowListStatus(state, dummyContractAddr, noRoleAddr) + require.Equal(t, EnabledRole, res) + }, + }, + "set no role": { + caller: adminAddr, + input: func() []byte { + input, err := PackModifyAllowList(enabledAddr, NoRole) + require.NoError(t, err) + + return input + }, + suppliedGas: ModifyAllowListGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + res := GetAllowListStatus(state, dummyContractAddr, enabledAddr) + require.Equal(t, NoRole, res) + }, + }, + "set no role from no role": { + caller: noRoleAddr, + input: func() []byte { + input, err := PackModifyAllowList(enabledAddr, NoRole) + require.NoError(t, err) + + return input + }, + suppliedGas: ModifyAllowListGasCost, + readOnly: false, + expectedErr: ErrCannotModifyAllowList.Error(), + }, + "set enabled from no role": { + caller: noRoleAddr, + input: func() []byte { + input, err := PackModifyAllowList(noRoleAddr, EnabledRole) + require.NoError(t, err) + + return input + }, + suppliedGas: ModifyAllowListGasCost, + readOnly: false, + expectedErr: ErrCannotModifyAllowList.Error(), + }, + "set admin from no role": { + caller: noRoleAddr, + input: func() []byte { + input, err := PackModifyAllowList(enabledAddr, AdminRole) + require.NoError(t, err) + + return input + }, + suppliedGas: ModifyAllowListGasCost, + readOnly: false, + expectedErr: ErrCannotModifyAllowList.Error(), + }, + "set no role from enabled": { + caller: enabledAddr, + input: func() []byte { + input, err := PackModifyAllowList(adminAddr, NoRole) + require.NoError(t, err) + + return input + }, + suppliedGas: ModifyAllowListGasCost, + readOnly: false, + expectedErr: ErrCannotModifyAllowList.Error(), + }, + "set enabled from enabled": { + caller: enabledAddr, + input: func() []byte { + input, err := PackModifyAllowList(noRoleAddr, EnabledRole) + require.NoError(t, err) + + return input + }, + suppliedGas: ModifyAllowListGasCost, + readOnly: false, + expectedErr: ErrCannotModifyAllowList.Error(), + }, + "set admin from enabled": { + caller: enabledAddr, + input: func() []byte { + input, err := PackModifyAllowList(noRoleAddr, AdminRole) + require.NoError(t, err) + + return input + }, + suppliedGas: ModifyAllowListGasCost, + readOnly: false, + expectedErr: ErrCannotModifyAllowList.Error(), + }, + "set no role with readOnly enabled": { + caller: adminAddr, + input: func() []byte { + input, err := PackModifyAllowList(enabledAddr, NoRole) + require.NoError(t, err) + + return input + }, + suppliedGas: ModifyAllowListGasCost, + readOnly: true, + expectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "set no role insufficient gas": { + caller: adminAddr, + input: func() []byte { + input, err := PackModifyAllowList(enabledAddr, NoRole) + require.NoError(t, err) + + return input + }, + suppliedGas: ModifyAllowListGasCost - 1, + readOnly: false, + expectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "read allow list no role": { + caller: noRoleAddr, + input: func() []byte { + return PackReadAllowList(noRoleAddr) + }, + suppliedGas: ReadAllowListGasCost, + readOnly: false, + expectedRes: common.Hash(NoRole).Bytes(), + assertState: nil, + }, + "read allow list admin role": { + caller: adminAddr, + input: func() []byte { + return PackReadAllowList(adminAddr) + }, + suppliedGas: ReadAllowListGasCost, + readOnly: false, + expectedRes: common.Hash(AdminRole).Bytes(), + assertState: nil, + }, + "read allow list with readOnly enabled": { + caller: adminAddr, + input: func() []byte { + return PackReadAllowList(noRoleAddr) + }, + suppliedGas: ReadAllowListGasCost, + readOnly: true, + expectedRes: common.Hash(NoRole).Bytes(), + assertState: nil, + }, + "read allow list out of gas": { + caller: adminAddr, + input: func() []byte { + return PackReadAllowList(noRoleAddr) + }, + suppliedGas: ReadAllowListGasCost - 1, + readOnly: true, + expectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "initial config sets admins": { + config: &Config{ + AdminAddresses: []common.Address{noRoleAddr, enabledAddr}, + }, + suppliedGas: 0, + readOnly: false, + expectedErr: "", + assertState: func(t *testing.T, state *state.StateDB) { + require.Equal(t, AdminRole, GetAllowListStatus(state, dummyContractAddr, noRoleAddr)) + require.Equal(t, AdminRole, GetAllowListStatus(state, dummyContractAddr, enabledAddr)) + }, + }, + "initial config sets enabled": { + config: &Config{ + EnabledAddresses: []common.Address{noRoleAddr, adminAddr}, + }, + suppliedGas: 0, + readOnly: false, + expectedErr: "", + assertState: func(t *testing.T, state *state.StateDB) { + require.Equal(t, EnabledRole, GetAllowListStatus(state, dummyContractAddr, adminAddr)) + require.Equal(t, EnabledRole, GetAllowListStatus(state, dummyContractAddr, noRoleAddr)) + }, + }, + } { + t.Run(name, func(t *testing.T) { + db := rawdb.NewMemoryDatabase() + state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) + require.NoError(t, err) + + // Set up the state so that each address has the expected permissions at the start. + SetAllowListRole(state, dummyContractAddr, adminAddr, AdminRole) + SetAllowListRole(state, dummyContractAddr, enabledAddr, EnabledRole) + require.Equal(t, AdminRole, GetAllowListStatus(state, dummyContractAddr, adminAddr)) + require.Equal(t, EnabledRole, GetAllowListStatus(state, dummyContractAddr, enabledAddr)) + + if test.config != nil { + test.config.Configure(state, dummyContractAddr) + } + + blockContext := contract.NewMockBlockContext(common.Big0, 0) + accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) + if test.input != nil { + ret, remainingGas, err := testAllowListPrecompile.Run(accesibleState, test.caller, dummyContractAddr, test.input(), test.suppliedGas, test.readOnly) + + if len(test.expectedErr) != 0 { + require.ErrorContains(t, err, test.expectedErr) + } else { + require.NoError(t, err) + } + + require.Equal(t, uint64(0), remainingGas) + require.Equal(t, test.expectedRes, ret) + } + + if test.assertState != nil { + test.assertState(t, state) + } + }) + } +} diff --git a/precompile/allowlist/allowlist.go b/precompile/allowlist/allowlist.go new file mode 100644 index 0000000000..357855b419 --- /dev/null +++ b/precompile/allowlist/allowlist.go @@ -0,0 +1,172 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package allowlist + +import ( + "errors" + "fmt" + + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/vmerrs" + "github.com/ethereum/go-ethereum/common" +) + +// AllowList is an abstraction that allows other precompiles to manage +// which addresses can call the precompile by maintaining an allowlist +// in the storage trie. Each account may have one of the following roles: +// 1. NoRole - this is equivalent to common.Hash{} and deletes the key from the DB when set +// 2. EnabledRole - allowed to call the precompile +// 3. Admin - allowed to modify both the allowlist and call the precompile + +const ( + SetAdminFuncKey = "setAdmin" + SetEnabledFuncKey = "setEnabled" + SetNoneFuncKey = "setNone" + ReadAllowListFuncKey = "readAllowList" + + ModifyAllowListGasCost = contract.WriteGasCostPerSlot + ReadAllowListGasCost = contract.ReadGasCostPerSlot +) + +const allowListInputLen = common.HashLength + +var ( + NoRole = Role(common.BigToHash(common.Big0)) // NoRole - this is equivalent to common.Hash{} and deletes the key from the DB when set + EnabledRole = Role(common.BigToHash(common.Big1)) // EnabledRole - allowed to call the precompile + AdminRole = Role(common.BigToHash(common.Big2)) // Admin - allowed to modify both the allowlist and call the precompile + + // AllowList function signatures + setAdminSignature = contract.CalculateFunctionSelector("setAdmin(address)") + setEnabledSignature = contract.CalculateFunctionSelector("setEnabled(address)") + setNoneSignature = contract.CalculateFunctionSelector("setNone(address)") + readAllowListSignature = contract.CalculateFunctionSelector("readAllowList(address)") + // Error returned when an invalid write is attempted + ErrCannotModifyAllowList = errors.New("non-admin cannot modify allow list") +) + +// GetAllowListStatus returns the allow list role of [address] for the precompile +// at [precompileAddr] +func GetAllowListStatus(state contract.StateDB, precompileAddr common.Address, address common.Address) Role { + // Generate the state key for [address] + addressKey := address.Hash() + return Role(state.GetState(precompileAddr, addressKey)) +} + +// SetAllowListRole sets the permissions of [address] to [role] for the precompile +// at [precompileAddr]. +// assumes [role] has already been verified as valid. +func SetAllowListRole(stateDB contract.StateDB, precompileAddr, address common.Address, role Role) { + // Generate the state key for [address] + addressKey := address.Hash() + // Assign [role] to the address + // This stores the [role] in the contract storage with address [precompileAddr] + // and [addressKey] hash. It means that any reusage of the [addressKey] for different value + // conflicts with the same slot [role] is stored. + // Precompile implementations must use a different key than [addressKey] + stateDB.SetState(precompileAddr, addressKey, common.Hash(role)) +} + +// PackModifyAllowList packs [address] and [role] into the appropriate arguments for modifying the allow list. +// Note: [role] is not packed in the input value returned, but is instead used as a selector for the function +// selector that should be encoded in the input. +func PackModifyAllowList(address common.Address, role Role) ([]byte, error) { + // function selector (4 bytes) + hash for address + input := make([]byte, 0, contract.SelectorLen+common.HashLength) + + switch role { + case AdminRole: + input = append(input, setAdminSignature...) + case EnabledRole: + input = append(input, setEnabledSignature...) + case NoRole: + input = append(input, setNoneSignature...) + default: + return nil, fmt.Errorf("cannot pack modify list input with invalid role: %s", role) + } + + input = append(input, address.Hash().Bytes()...) + return input, nil +} + +// PackReadAllowList packs [address] into the input data to the read allow list function +func PackReadAllowList(address common.Address) []byte { + input := make([]byte, 0, contract.SelectorLen+common.HashLength) + input = append(input, readAllowListSignature...) + input = append(input, address.Hash().Bytes()...) + return input +} + +// createAllowListRoleSetter returns an execution function for setting the allow list status of the input address argument to [role]. +// This execution function is speciifc to [precompileAddr]. +func createAllowListRoleSetter(precompileAddr common.Address, role Role) contract.RunStatefulPrecompileFunc { + return func(evm contract.AccessibleState, callerAddr, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, ModifyAllowListGasCost); err != nil { + return nil, 0, err + } + + if len(input) != allowListInputLen { + return nil, remainingGas, fmt.Errorf("invalid input length for modifying allow list: %d", len(input)) + } + + modifyAddress := common.BytesToAddress(input) + + if readOnly { + return nil, remainingGas, vmerrs.ErrWriteProtection + } + + stateDB := evm.GetStateDB() + + // Verify that the caller is an admin with permission to modify the allow list + callerStatus := GetAllowListStatus(stateDB, precompileAddr, callerAddr) + if !callerStatus.IsAdmin() { + return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotModifyAllowList, callerAddr) + } + + SetAllowListRole(stateDB, precompileAddr, modifyAddress, role) + // Return an empty output and the remaining gas + return []byte{}, remainingGas, nil + } +} + +// createReadAllowList returns an execution function that reads the allow list for the given [precompileAddr]. +// The execution function parses the input into a single address and returns the 32 byte hash that specifies the +// designated role of that address +func createReadAllowList(precompileAddr common.Address) contract.RunStatefulPrecompileFunc { + return func(evm contract.AccessibleState, callerAddr common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, ReadAllowListGasCost); err != nil { + return nil, 0, err + } + + if len(input) != allowListInputLen { + return nil, remainingGas, fmt.Errorf("invalid input length for read allow list: %d", len(input)) + } + + readAddress := common.BytesToAddress(input) + role := GetAllowListStatus(evm.GetStateDB(), precompileAddr, readAddress) + roleBytes := common.Hash(role).Bytes() + return roleBytes, remainingGas, nil + } +} + +// CreateAllowListPrecompile returns a StatefulPrecompiledContract with R/W control of an allow list at [precompileAddr] +func CreateAllowListPrecompile(precompileAddr common.Address) contract.StatefulPrecompiledContract { + // Construct the contract with no fallback function. + allowListFuncs := CreateAllowListFunctions(precompileAddr) + contract, err := contract.NewStatefulPrecompileContract(nil, allowListFuncs) + // TODO Change this to be returned as an error after refactoring this precompile + // to use the new precompile template. + if err != nil { + panic(err) + } + return contract +} + +func CreateAllowListFunctions(precompileAddr common.Address) []*contract.StatefulPrecompileFunction { + setAdmin := contract.NewStatefulPrecompileFunction(setAdminSignature, createAllowListRoleSetter(precompileAddr, AdminRole)) + setEnabled := contract.NewStatefulPrecompileFunction(setEnabledSignature, createAllowListRoleSetter(precompileAddr, EnabledRole)) + setNone := contract.NewStatefulPrecompileFunction(setNoneSignature, createAllowListRoleSetter(precompileAddr, NoRole)) + read := contract.NewStatefulPrecompileFunction(readAllowListSignature, createReadAllowList(precompileAddr)) + + return []*contract.StatefulPrecompileFunction{setAdmin, setEnabled, setNone, read} +} diff --git a/precompile/allowlist/config.go b/precompile/allowlist/config.go new file mode 100644 index 0000000000..c11f86afc2 --- /dev/null +++ b/precompile/allowlist/config.go @@ -0,0 +1,79 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package allowlist + +import ( + "fmt" + + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ethereum/go-ethereum/common" +) + +// Config specifies the initial set of addresses with Admin or Enabled roles. +type Config struct { + AdminAddresses []common.Address `json:"adminAddresses,omitempty"` // initial admin addresses + EnabledAddresses []common.Address `json:"enabledAddresses,omitempty"` // initial enabled addresses +} + +// Configure initializes the address space of [precompileAddr] by initializing the role of each of +// the addresses in [AllowListAdmins]. +func (c *Config) Configure(state contract.StateDB, precompileAddr common.Address) error { + for _, enabledAddr := range c.EnabledAddresses { + SetAllowListRole(state, precompileAddr, enabledAddr, EnabledRole) + } + for _, adminAddr := range c.AdminAddresses { + SetAllowListRole(state, precompileAddr, adminAddr, AdminRole) + } + return nil +} + +// Equal returns true iff [other] has the same admins in the same order in its allow list. +func (c *Config) Equal(other *Config) bool { + if other == nil { + return false + } + + return areEqualAddressLists(c.AdminAddresses, other.AdminAddresses) && + areEqualAddressLists(c.EnabledAddresses, other.EnabledAddresses) +} + +// areEqualAddressLists returns true iff [a] and [b] have the same addresses in the same order. +func areEqualAddressLists(current []common.Address, other []common.Address) bool { + if len(current) != len(other) { + return false + } + for i, address := range current { + if address != other[i] { + return false + } + } + return true +} + +// Verify returns an error if there is an overlapping address between admin and enabled roles +func (c *Config) Verify() error { + addressMap := make(map[common.Address]Role) // tracks which addresses we have seen and their role + + // check for duplicates in enabled list + for _, enabledAddr := range c.EnabledAddresses { + if _, ok := addressMap[enabledAddr]; ok { + return fmt.Errorf("duplicate address %s in enabled list", enabledAddr) + } + addressMap[enabledAddr] = EnabledRole + } + + // check for overlap between enabled and admin lists or duplicates in admin list + for _, adminAddr := range c.AdminAddresses { + if role, ok := addressMap[adminAddr]; ok { + if role == AdminRole { + return fmt.Errorf("duplicate address %s in admin list", adminAddr) + } else { + return fmt.Errorf("cannot set address %s as both admin and enabled", adminAddr) + } + } + addressMap[adminAddr] = AdminRole + } + + return nil +} diff --git a/precompile/allowlist/config_test.go b/precompile/allowlist/config_test.go new file mode 100644 index 0000000000..b9fdb24053 --- /dev/null +++ b/precompile/allowlist/config_test.go @@ -0,0 +1,97 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package allowlist + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestVerifyAllowlistConfig(t *testing.T) { + admins := []common.Address{{1}} + enableds := []common.Address{{2}} + tests := []struct { + name string + config Config + expectedError string + }{ + { + name: "invalid allow list config in allowlist", + config: Config{admins, admins}, + expectedError: "cannot set address", + }, + { + name: "nil member allow list config in allowlist", + config: Config{nil, nil}, + expectedError: "", + }, + { + name: "empty member allow list config in allowlist", + config: Config{[]common.Address{}, []common.Address{}}, + expectedError: "", + }, + { + name: "valid allow list config in allowlist", + config: Config{admins, enableds}, + expectedError: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + err := tt.config.Verify() + if tt.expectedError == "" { + require.NoError(err) + } else { + require.ErrorContains(err, tt.expectedError) + } + }) + } +} + +func TestEqualAllowListConfig(t *testing.T) { + admins := []common.Address{{1}} + enableds := []common.Address{{2}} + tests := []struct { + name string + config *Config + other *Config + expected bool + }{ + { + name: "non-nil config and nil other", + config: &Config{admins, enableds}, + other: nil, + expected: false, + }, + { + name: "different admin", + config: &Config{admins, enableds}, + other: &Config{[]common.Address{{3}}, enableds}, + expected: false, + }, + { + name: "different enabled", + config: &Config{admins, enableds}, + other: &Config{admins, []common.Address{{3}}}, + expected: false, + }, + { + name: "same config", + config: &Config{admins, enableds}, + other: &Config{admins, enableds}, + expected: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + require.Equal(tt.expected, tt.config.Equal(tt.other)) + }) + } +} diff --git a/precompile/allowlist/allow_list_role.go b/precompile/allowlist/role.go similarity index 56% rename from precompile/allowlist/allow_list_role.go rename to precompile/allowlist/role.go index 7e59f8420e..aa55007662 100644 --- a/precompile/allowlist/allow_list_role.go +++ b/precompile/allowlist/role.go @@ -5,13 +5,13 @@ package allowlist import "github.com/ethereum/go-ethereum/common" -// Enum constants for valid AllowListRole -type AllowListRole common.Hash +// Enum constants for valid Role +type Role common.Hash // Valid returns true iff [s] represents a valid role. -func (s AllowListRole) Valid() bool { +func (s Role) Valid() bool { switch s { - case AllowListNoRole, AllowListEnabled, AllowListAdmin: + case NoRole, EnabledRole, AdminRole: return true default: return false @@ -19,9 +19,9 @@ func (s AllowListRole) Valid() bool { } // IsNoRole returns true if [s] indicates no specific role. -func (s AllowListRole) IsNoRole() bool { +func (s Role) IsNoRole() bool { switch s { - case AllowListNoRole: + case NoRole: return true default: return false @@ -29,9 +29,9 @@ func (s AllowListRole) IsNoRole() bool { } // IsAdmin returns true if [s] indicates the permission to modify the allow list. -func (s AllowListRole) IsAdmin() bool { +func (s Role) IsAdmin() bool { switch s { - case AllowListAdmin: + case AdminRole: return true default: return false @@ -39,11 +39,25 @@ func (s AllowListRole) IsAdmin() bool { } // IsEnabled returns true if [s] indicates that it has permission to access the resource. -func (s AllowListRole) IsEnabled() bool { +func (s Role) IsEnabled() bool { switch s { - case AllowListAdmin, AllowListEnabled: + case AdminRole, EnabledRole: return true default: return false } } + +// String returns a string representation of [s]. +func (s Role) String() string { + switch s { + case NoRole: + return "NoRole" + case EnabledRole: + return "EnabledRole" + case AdminRole: + return "AdminRole" + default: + return "UnknownRole" + } +} diff --git a/precompile/config/config.go b/precompile/config/config.go new file mode 100644 index 0000000000..63ccf43dad --- /dev/null +++ b/precompile/config/config.go @@ -0,0 +1,27 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Defines the stateless interface for unmarshalling an arbitrary config of a precompile +package config + +import ( + "math/big" +) + +// StatefulPrecompileConfig defines the interface for a stateful precompile to +// be enabled via a network upgrade. +type Config interface { + // Key returns the unique key for the stateful precompile. + Key() string + // Timestamp returns the timestamp at which this stateful precompile should be enabled. + // 1) 0 indicates that the precompile should be enabled from genesis. + // 2) n indicates that the precompile should be enabled in the first block with timestamp >= [n]. + // 3) nil indicates that the precompile is never enabled. + Timestamp() *big.Int + // IsDisabled returns true if this network upgrade should disable the precompile. + IsDisabled() bool + // Equal returns true if the provided argument configures the same precompile with the same parameters. + Equal(Config) bool + // Verify is called on startup and an error is treated as fatal. Configure can assume the Config has passed verification. + Verify() error +} diff --git a/precompile/config/mock_config.go b/precompile/config/mock_config.go new file mode 100644 index 0000000000..cb1f1f9a91 --- /dev/null +++ b/precompile/config/mock_config.go @@ -0,0 +1,45 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// TODO: replace with gomock + +package config + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +var _ Config = &noopStatefulPrecompileConfig{} + +type noopStatefulPrecompileConfig struct { +} + +func NewNoopStatefulPrecompileConfig() *noopStatefulPrecompileConfig { + return &noopStatefulPrecompileConfig{} +} + +func (n *noopStatefulPrecompileConfig) Key() string { + return "" +} + +func (n *noopStatefulPrecompileConfig) Address() common.Address { + return common.Address{} +} + +func (n *noopStatefulPrecompileConfig) Timestamp() *big.Int { + return new(big.Int) +} + +func (n *noopStatefulPrecompileConfig) IsDisabled() bool { + return false +} + +func (n *noopStatefulPrecompileConfig) Equal(Config) bool { + return false +} + +func (n *noopStatefulPrecompileConfig) Verify() error { + return nil +} diff --git a/precompile/upgradeable.go b/precompile/config/upgradeable.go similarity index 59% rename from precompile/upgradeable.go rename to precompile/config/upgradeable.go index a35d575de0..66a4381694 100644 --- a/precompile/upgradeable.go +++ b/precompile/config/upgradeable.go @@ -1,7 +1,7 @@ // (c) 2022 Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package precompile +package config import ( "math/big" @@ -9,30 +9,29 @@ import ( "github.com/ava-labs/subnet-evm/utils" ) -// UpgradeableConfig contains the timestamp for the upgrade along with +// Upgrade contains the timestamp for the upgrade along with // a boolean [Disable]. If [Disable] is set, the upgrade deactivates -// the precompile and resets its storage. -// TODO: convert to interface -type UpgradeableConfig struct { +// the precompile and clears its storage. +type Upgrade struct { BlockTimestamp *big.Int `json:"blockTimestamp"` Disable bool `json:"disable,omitempty"` } // Timestamp returns the timestamp this network upgrade goes into effect. -func (c *UpgradeableConfig) Timestamp() *big.Int { - return c.BlockTimestamp +func (u *Upgrade) Timestamp() *big.Int { + return u.BlockTimestamp } // IsDisabled returns true if the network upgrade deactivates the precompile. -func (c *UpgradeableConfig) IsDisabled() bool { - return c.Disable +func (u *Upgrade) IsDisabled() bool { + return u.Disable } // Equal returns true iff [other] has the same blockTimestamp and has the // same on value for the Disable flag. -func (c *UpgradeableConfig) Equal(other *UpgradeableConfig) bool { +func (u *Upgrade) Equal(other *Upgrade) bool { if other == nil { return false } - return c.Disable == other.Disable && utils.BigNumEqual(c.BlockTimestamp, other.BlockTimestamp) + return u.Disable == other.Disable && utils.BigNumEqual(u.BlockTimestamp, other.BlockTimestamp) } diff --git a/precompile/contract.go b/precompile/contract/contract.go similarity index 80% rename from precompile/contract.go rename to precompile/contract/contract.go index 596ea6dc00..82bb5fe21c 100644 --- a/precompile/contract.go +++ b/precompile/contract/contract.go @@ -1,7 +1,7 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package precompile +package contract import ( "fmt" @@ -13,13 +13,7 @@ const ( SelectorLen = 4 ) -type RunStatefulPrecompileFunc func(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) - -// StatefulPrecompiledContract is the interface for executing a precompiled contract -type StatefulPrecompiledContract interface { - // Run executes the precompiled contract. - Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) -} +type RunStatefulPrecompileFunc func(accessibleState AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) // StatefulPrecompileFunction defines a function implemented by a stateful precompile type StatefulPrecompileFunction struct { @@ -67,7 +61,7 @@ func NewStatefulPrecompileContract(fallback RunStatefulPrecompileFunc, functions // Run selects the function using the 4 byte function selector at the start of the input and executes the underlying function on the // given arguments. -func (s *statefulPrecompileWithFunctionSelectors) Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { +func (s *statefulPrecompileWithFunctionSelectors) Run(accessibleState AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { // If there is no input data present, call the fallback function if present. if len(input) == 0 && s.fallback != nil { return s.fallback(accessibleState, caller, addr, nil, suppliedGas, readOnly) diff --git a/precompile/interface.go b/precompile/contract/interfaces.go similarity index 67% rename from precompile/interface.go rename to precompile/contract/interfaces.go index 82be0f749a..75f48980a1 100644 --- a/precompile/interface.go +++ b/precompile/contract/interfaces.go @@ -1,30 +1,22 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// (c) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package precompile +// Defines the interface for the configuration and execution of a precompile contract +package contract import ( "math/big" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ethereum/go-ethereum/common" ) -// PrecompileAccessibleState defines the interface exposed to stateful precompile contracts -type PrecompileAccessibleState interface { - GetStateDB() StateDB - GetBlockContext() BlockContext - GetSnowContext() *snow.Context - CallFromPrecompile(caller common.Address, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) -} - -// BlockContext defines an interface that provides information to a stateful precompile -// about the block that activates the upgrade. The precompile can access this information -// to initialize its state. -type BlockContext interface { - Number() *big.Int - Timestamp() *big.Int +// StatefulPrecompiledContract is the interface for executing a precompiled contract +type StatefulPrecompiledContract interface { + // Run executes the precompiled contract. + Run(accessibleState AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) } // ChainContext defines an interface that provides information to a stateful precompile @@ -59,3 +51,29 @@ type StateDB interface { Suicide(common.Address) bool Finalise(deleteEmptyObjects bool) } + +// AccessibleState defines the interface exposed to stateful precompile contracts +type AccessibleState interface { + GetStateDB() StateDB + GetBlockContext() BlockContext + GetSnowContext() *snow.Context + CallFromPrecompile(caller common.Address, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) +} + +// BlockContext defines an interface that provides information to a stateful precompile +// about the block that activates the upgrade. The precompile can access this information +// to initialize its state. +type BlockContext interface { + Number() *big.Int + Timestamp() *big.Int +} + +type Configurator interface { + NewConfig() config.Config + Configure( + chainConfig ChainConfig, + precompileConfig config.Config, + state StateDB, + blockContext BlockContext, + ) error +} diff --git a/precompile/contract/mock_interfaces.go b/precompile/contract/mock_interfaces.go new file mode 100644 index 0000000000..fe8cf5bf87 --- /dev/null +++ b/precompile/contract/mock_interfaces.go @@ -0,0 +1,57 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package contract + +import ( + "math/big" + + "github.com/ava-labs/avalanchego/snow" + "github.com/ethereum/go-ethereum/common" +) + +// TODO: replace with gomock library + +var ( + _ BlockContext = &mockBlockContext{} + _ AccessibleState = &mockAccessibleState{} +) + +type mockBlockContext struct { + blockNumber *big.Int + timestamp uint64 +} + +func NewMockBlockContext(blockNumber *big.Int, timestamp uint64) *mockBlockContext { + return &mockBlockContext{ + blockNumber: blockNumber, + timestamp: timestamp, + } +} + +func (mb *mockBlockContext) Number() *big.Int { return mb.blockNumber } +func (mb *mockBlockContext) Timestamp() *big.Int { return new(big.Int).SetUint64(mb.timestamp) } + +type mockAccessibleState struct { + state StateDB + blockContext *mockBlockContext + snowContext *snow.Context +} + +func NewMockAccessibleState(state StateDB, blockContext *mockBlockContext, snowContext *snow.Context) *mockAccessibleState { + return &mockAccessibleState{ + state: state, + blockContext: blockContext, + snowContext: snowContext, + } +} + +func (m *mockAccessibleState) GetStateDB() StateDB { return m.state } + +func (m *mockAccessibleState) GetBlockContext() BlockContext { return m.blockContext } + +func (m *mockAccessibleState) GetSnowContext() *snow.Context { return m.snowContext } + +func (m *mockAccessibleState) CallFromPrecompile(caller common.Address, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { + return nil, 0, nil +} diff --git a/precompile/utils.go b/precompile/contract/utils.go similarity index 85% rename from precompile/utils.go rename to precompile/contract/utils.go index f03e89bc50..9cc50d3155 100644 --- a/precompile/utils.go +++ b/precompile/contract/utils.go @@ -1,17 +1,25 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package precompile +package contract import ( "fmt" "regexp" + "strings" + "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" ) +// Gas costs for stateful precompiles +const ( + WriteGasCostPerSlot = 20_000 + ReadGasCostPerSlot = 5_000 +) + var functionSignatureRegex = regexp.MustCompile(`[\w]+\(((([\w]+)?)|((([\w]+),)+([\w]+)))\)`) // CalculateFunctionSelector returns the 4 byte function selector that results from [functionSignature] @@ -69,3 +77,14 @@ func PackedHash(packed []byte, index int) []byte { end := start + common.HashLength return packed[start:end] } + +// ParseABI parses the given ABI string and returns the parsed ABI. +// If the ABI is invalid, it panics. +func ParseABI(rawABI string) abi.ABI { + parsed, err := abi.JSON(strings.NewReader(rawABI)) + if err != nil { + panic(err) + } + + return parsed +} diff --git a/precompile/utils_test.go b/precompile/contract/utils_test.go similarity index 98% rename from precompile/utils_test.go rename to precompile/contract/utils_test.go index 3414bc341c..6220af95a8 100644 --- a/precompile/utils_test.go +++ b/precompile/contract/utils_test.go @@ -1,7 +1,7 @@ // (c) 2019-2020, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package precompile +package contract import ( "testing" diff --git a/precompile/contracts/deployerallowlist/config.go b/precompile/contracts/deployerallowlist/config.go new file mode 100644 index 0000000000..4f624de1f6 --- /dev/null +++ b/precompile/contracts/deployerallowlist/config.go @@ -0,0 +1,58 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package deployerallowlist + +import ( + "math/big" + + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ethereum/go-ethereum/common" +) + +var _ config.Config = &Config{} + +// Config contains the configuration for the ContractDeployerAllowList precompile, +// consisting of the initial allowlist and the timestamp for the network upgrade. +type Config struct { + allowlist.Config + config.Upgrade +} + +// NewConfig returns a config for a network upgrade at [blockTimestamp] that enables +// ContractDeployerAllowList with [admins] and [enableds] as members of the allowlist. +func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address) *Config { + return &Config{ + Config: allowlist.Config{ + AdminAddresses: admins, + EnabledAddresses: enableds, + }, + Upgrade: config.Upgrade{BlockTimestamp: blockTimestamp}, + } +} + +// NewDisableConfig returns config for a network upgrade at [blockTimestamp] +// that disables ContractDeployerAllowList. +func NewDisableConfig(blockTimestamp *big.Int) *Config { + return &Config{ + Upgrade: config.Upgrade{ + BlockTimestamp: blockTimestamp, + Disable: true, + }, + } +} + +func (*Config) Key() string { return ConfigKey } + +// Equal returns true if [cfg] is a [*ContractDeployerAllowListConfig] and it has been configured identical to [c]. +func (c *Config) Equal(cfg config.Config) bool { + // typecast before comparison + other, ok := (cfg).(*Config) + if !ok { + return false + } + return c.Upgrade.Equal(&other.Upgrade) && c.Config.Equal(&other.Config) +} + +func (c *Config) Verify() error { return c.Config.Verify() } diff --git a/precompile/deployerallowlist/config_test.go b/precompile/contracts/deployerallowlist/config_test.go similarity index 56% rename from precompile/deployerallowlist/config_test.go rename to precompile/contracts/deployerallowlist/config_test.go index 2ffb0be77c..c1245b0b09 100644 --- a/precompile/deployerallowlist/config_test.go +++ b/precompile/contracts/deployerallowlist/config_test.go @@ -7,7 +7,7 @@ import ( "math/big" "testing" - "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -16,12 +16,12 @@ func TestVerifyContractDeployerConfig(t *testing.T) { admins := []common.Address{{1}} tests := []struct { name string - config precompile.StatefulPrecompileConfig + config config.Config expectedError string }{ { name: "invalid allow list config in deployer allowlist", - config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, admins), + config: NewConfig(big.NewInt(3), admins, admins), expectedError: "cannot set address", }, } @@ -44,44 +44,44 @@ func TestEqualContractDeployerAllowListConfig(t *testing.T) { enableds := []common.Address{{2}} tests := []struct { name string - config precompile.StatefulPrecompileConfig - other precompile.StatefulPrecompileConfig + config config.Config + other config.Config expected bool }{ { name: "non-nil config and nil other", - config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), + config: NewConfig(big.NewInt(3), admins, enableds), other: nil, expected: false, }, { name: "different type", - config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), - other: precompile.NewNoopStatefulPrecompileConfig(), + config: NewConfig(big.NewInt(3), admins, enableds), + other: config.NewNoopStatefulPrecompileConfig(), expected: false, }, { name: "different admin", - config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), - other: NewContractDeployerAllowListConfig(big.NewInt(3), []common.Address{{3}}, enableds), + config: NewConfig(big.NewInt(3), admins, enableds), + other: NewConfig(big.NewInt(3), []common.Address{{3}}, enableds), expected: false, }, { name: "different enabled", - config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), - other: NewContractDeployerAllowListConfig(big.NewInt(3), admins, []common.Address{{3}}), + config: NewConfig(big.NewInt(3), admins, enableds), + other: NewConfig(big.NewInt(3), admins, []common.Address{{3}}), expected: false, }, { name: "different timestamp", - config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), - other: NewContractDeployerAllowListConfig(big.NewInt(4), admins, enableds), + config: NewConfig(big.NewInt(3), admins, enableds), + other: NewConfig(big.NewInt(4), admins, enableds), expected: false, }, { name: "same config", - config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), - other: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), + config: NewConfig(big.NewInt(3), admins, enableds), + other: NewConfig(big.NewInt(3), admins, enableds), expected: true, }, } diff --git a/precompile/contracts/deployerallowlist/contract.go b/precompile/contracts/deployerallowlist/contract.go new file mode 100644 index 0000000000..bb4b97e95b --- /dev/null +++ b/precompile/contracts/deployerallowlist/contract.go @@ -0,0 +1,26 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package deployerallowlist + +import ( + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ethereum/go-ethereum/common" +) + +// Singleton StatefulPrecompiledContract for W/R access to the contract deployer allow list. +var ContractDeployerAllowListPrecompile contract.StatefulPrecompiledContract = allowlist.CreateAllowListPrecompile(ContractAddress) + +// GetContractDeployerAllowListStatus returns the role of [address] for the contract deployer +// allow list. +func GetContractDeployerAllowListStatus(stateDB contract.StateDB, address common.Address) allowlist.Role { + return allowlist.GetAllowListStatus(stateDB, ContractAddress, address) +} + +// SetContractDeployerAllowListStatus sets the permissions of [address] to [role] for the +// contract deployer allow list. +// assumes [role] has already been verified as valid. +func SetContractDeployerAllowListStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) { + allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) +} diff --git a/precompile/contracts/deployerallowlist/contract_test.go b/precompile/contracts/deployerallowlist/contract_test.go new file mode 100644 index 0000000000..245fea438a --- /dev/null +++ b/precompile/contracts/deployerallowlist/contract_test.go @@ -0,0 +1,216 @@ +// (c) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package deployerallowlist + +import ( + "testing" + + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/subnet-evm/core/rawdb" + "github.com/ava-labs/subnet-evm/core/state" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/vmerrs" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +type precompileTest struct { + caller common.Address + input func() []byte + suppliedGas uint64 + readOnly bool + + config config.Config + + preCondition func(t *testing.T, state *state.StateDB) + assertState func(t *testing.T, state *state.StateDB) + + expectedRes []byte + expectedErr string +} + +func TestContractDeployerAllowListRun(t *testing.T) { + adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") + noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") + + for name, test := range map[string]precompileTest{ + "set admin": { + caller: adminAddr, + input: func() []byte { + input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AdminRole) + require.NoError(t, err) + + return input + }, + suppliedGas: allowlist.ModifyAllowListGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + res := GetContractDeployerAllowListStatus(state, noRoleAddr) + require.Equal(t, allowlist.AdminRole, res) + }, + }, + "set deployer": { + caller: adminAddr, + input: func() []byte { + input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.EnabledRole) + require.NoError(t, err) + + return input + }, + suppliedGas: allowlist.ModifyAllowListGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + res := GetContractDeployerAllowListStatus(state, noRoleAddr) + require.Equal(t, allowlist.EnabledRole, res) + }, + }, + "set no role": { + caller: adminAddr, + input: func() []byte { + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) + require.NoError(t, err) + + return input + }, + suppliedGas: allowlist.ModifyAllowListGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + res := GetContractDeployerAllowListStatus(state, adminAddr) + require.Equal(t, allowlist.EnabledRole, res) + }, + }, + "set no role from non-admin": { + caller: noRoleAddr, + input: func() []byte { + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) + require.NoError(t, err) + + return input + }, + suppliedGas: allowlist.ModifyAllowListGasCost, + readOnly: false, + expectedErr: allowlist.ErrCannotModifyAllowList.Error(), + }, + "set deployer from non-admin": { + caller: noRoleAddr, + input: func() []byte { + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) + require.NoError(t, err) + + return input + }, + suppliedGas: allowlist.ModifyAllowListGasCost, + readOnly: false, + expectedErr: allowlist.ErrCannotModifyAllowList.Error(), + }, + "set admin from non-admin": { + caller: noRoleAddr, + input: func() []byte { + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AdminRole) + require.NoError(t, err) + + return input + }, + suppliedGas: allowlist.ModifyAllowListGasCost, + readOnly: false, + expectedErr: allowlist.ErrCannotModifyAllowList.Error(), + }, + "set no role with readOnly enabled": { + caller: adminAddr, + input: func() []byte { + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) + require.NoError(t, err) + + return input + }, + suppliedGas: allowlist.ModifyAllowListGasCost, + readOnly: true, + expectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "set no role insufficient gas": { + caller: adminAddr, + input: func() []byte { + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) + require.NoError(t, err) + + return input + }, + suppliedGas: allowlist.ModifyAllowListGasCost - 1, + readOnly: false, + expectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "read allow list no role": { + caller: noRoleAddr, + input: func() []byte { + return allowlist.PackReadAllowList(noRoleAddr) + }, + suppliedGas: allowlist.ReadAllowListGasCost, + readOnly: false, + expectedRes: common.Hash(allowlist.EnabledRole).Bytes(), + assertState: nil, + }, + "read allow list admin role": { + caller: adminAddr, + input: func() []byte { + return allowlist.PackReadAllowList(noRoleAddr) + }, + suppliedGas: allowlist.ReadAllowListGasCost, + readOnly: false, + expectedRes: common.Hash(allowlist.EnabledRole).Bytes(), + assertState: nil, + }, + "read allow list with readOnly enabled": { + caller: adminAddr, + input: func() []byte { + return allowlist.PackReadAllowList(noRoleAddr) + }, + suppliedGas: allowlist.ReadAllowListGasCost, + readOnly: true, + expectedRes: common.Hash(allowlist.EnabledRole).Bytes(), + assertState: nil, + }, + "read allow list out of gas": { + caller: adminAddr, + input: func() []byte { + return allowlist.PackReadAllowList(noRoleAddr) + }, + suppliedGas: allowlist.ReadAllowListGasCost - 1, + readOnly: true, + expectedErr: vmerrs.ErrOutOfGas.Error(), + }, + } { + t.Run(name, func(t *testing.T) { + db := rawdb.NewMemoryDatabase() + state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) + require.NoError(t, err) + + // Set up the state so that each address has the expected permissions at the start. + SetContractDeployerAllowListStatus(state, adminAddr, allowlist.AdminRole) + SetContractDeployerAllowListStatus(state, noRoleAddr, allowlist.EnabledRole) + require.Equal(t, allowlist.AdminRole, GetContractDeployerAllowListStatus(state, adminAddr)) + require.Equal(t, allowlist.EnabledRole, GetContractDeployerAllowListStatus(state, noRoleAddr)) + + blockContext := contract.NewMockBlockContext(common.Big0, 0) + accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) + ret, remainingGas, err := ContractDeployerAllowListPrecompile.Run(accesibleState, test.caller, ContractAddress, test.input(), test.suppliedGas, test.readOnly) + if len(test.expectedErr) != 0 { + require.ErrorContains(t, err, test.expectedErr) + } else { + require.NoError(t, err) + } + + require.Equal(t, uint64(0), remainingGas) + require.Equal(t, test.expectedRes, ret) + + if test.assertState != nil { + test.assertState(t, state) + } + }) + } +} diff --git a/precompile/contracts/deployerallowlist/module.go b/precompile/contracts/deployerallowlist/module.go new file mode 100644 index 0000000000..47e4bc7269 --- /dev/null +++ b/precompile/contracts/deployerallowlist/module.go @@ -0,0 +1,49 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package deployerallowlist + +import ( + "fmt" + + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ethereum/go-ethereum/common" +) + +var _ contract.Configurator = &configurator{} + +// ConfigKey is the key used in json config files to specify this precompile config. +// must be unique across all precompiles. +const ConfigKey = "contractDeployerAllowListConfig" + +var ContractAddress = common.HexToAddress("0x0200000000000000000000000000000000000000") + +var Module = modules.Module{ + ConfigKey: ConfigKey, + Address: ContractAddress, + Contract: ContractDeployerAllowListPrecompile, + Configurator: &configurator{}, +} + +type configurator struct{} + +func init() { + if err := modules.RegisterModule(Module); err != nil { + panic(err) + } +} + +func (*configurator) NewConfig() config.Config { + return &Config{} +} + +// Configure configures [state] with the initial state for the precompile. +func (c *configurator) Configure(_ contract.ChainConfig, cfg config.Config, state contract.StateDB, _ contract.BlockContext) error { + config, ok := cfg.(*Config) + if !ok { + return fmt.Errorf("incorrect config %T: %v", config, config) + } + return config.Config.Configure(state, ContractAddress) +} diff --git a/precompile/contracts/feemanager/config.go b/precompile/contracts/feemanager/config.go new file mode 100644 index 0000000000..9db36ffeee --- /dev/null +++ b/precompile/contracts/feemanager/config.go @@ -0,0 +1,79 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package feemanager + +import ( + "math/big" + + "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ethereum/go-ethereum/common" +) + +var _ config.Config = &Config{} + +// Config implements the StatefulPrecompileConfig interface while adding in the +// FeeManager specific precompile config. +type Config struct { + allowlist.Config // Config for the fee config manager allow list + config.Upgrade + InitialFeeConfig *commontype.FeeConfig `json:"initialFeeConfig,omitempty"` // initial fee config to be immediately activated +} + +// NewConfig returns a config for a network upgrade at [blockTimestamp] that enables +// FeeManager with the given [admins] and [enableds] as members of the allowlist with [initialConfig] as initial fee config if specified. +func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialConfig *commontype.FeeConfig) *Config { + return &Config{ + Config: allowlist.Config{ + AdminAddresses: admins, + EnabledAddresses: enableds, + }, + Upgrade: config.Upgrade{BlockTimestamp: blockTimestamp}, + InitialFeeConfig: initialConfig, + } +} + +// NewDisableConfig returns config for a network upgrade at [blockTimestamp] +// that disables FeeManager. +func NewDisableConfig(blockTimestamp *big.Int) *Config { + return &Config{ + Upgrade: config.Upgrade{ + BlockTimestamp: blockTimestamp, + Disable: true, + }, + } +} + +func (*Config) Key() string { return ConfigKey } + +// Equal returns true if [cfg] is a [*FeeManagerConfig] and it has been configured identical to [c]. +func (c *Config) Equal(cfg config.Config) bool { + // typecast before comparison + other, ok := (cfg).(*Config) + if !ok { + return false + } + eq := c.Upgrade.Equal(&other.Upgrade) && c.Config.Equal(&other.Config) + if !eq { + return false + } + + if c.InitialFeeConfig == nil { + return other.InitialFeeConfig == nil + } + + return c.InitialFeeConfig.Equal(other.InitialFeeConfig) +} + +func (c *Config) Verify() error { + if err := c.Config.Verify(); err != nil { + return err + } + if c.InitialFeeConfig == nil { + return nil + } + + return c.InitialFeeConfig.Verify() +} diff --git a/precompile/feemanager/config_test.go b/precompile/contracts/feemanager/config_test.go similarity index 65% rename from precompile/feemanager/config_test.go rename to precompile/contracts/feemanager/config_test.go index 1761c3109d..861c713023 100644 --- a/precompile/feemanager/config_test.go +++ b/precompile/contracts/feemanager/config_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -30,17 +30,17 @@ func TestVerifyFeeManagerConfig(t *testing.T) { admins := []common.Address{{1}} tests := []struct { name string - config precompile.StatefulPrecompileConfig + config config.Config expectedError string }{ { name: "invalid allow list config in fee manager allowlist", - config: NewFeeManagerConfig(big.NewInt(3), admins, admins, nil), + config: NewConfig(big.NewInt(3), admins, admins, nil), expectedError: "cannot set address", }, { name: "invalid initial fee manager config", - config: NewFeeManagerConfig(big.NewInt(3), admins, nil, + config: NewConfig(big.NewInt(3), admins, nil, &commontype.FeeConfig{ GasLimit: big.NewInt(0), }), @@ -66,44 +66,44 @@ func TestEqualFeeManagerConfig(t *testing.T) { enableds := []common.Address{{2}} tests := []struct { name string - config precompile.StatefulPrecompileConfig - other precompile.StatefulPrecompileConfig + config config.Config + other config.Config expected bool }{ { name: "non-nil config and nil other", - config: NewFeeManagerConfig(big.NewInt(3), admins, enableds, nil), + config: NewConfig(big.NewInt(3), admins, enableds, nil), other: nil, expected: false, }, { name: "different type", - config: NewFeeManagerConfig(big.NewInt(3), admins, enableds, nil), - other: precompile.NewNoopStatefulPrecompileConfig(), + config: NewConfig(big.NewInt(3), admins, enableds, nil), + other: config.NewNoopStatefulPrecompileConfig(), expected: false, }, { name: "different timestamp", - config: NewFeeManagerConfig(big.NewInt(3), admins, nil, nil), - other: NewFeeManagerConfig(big.NewInt(4), admins, nil, nil), + config: NewConfig(big.NewInt(3), admins, nil, nil), + other: NewConfig(big.NewInt(4), admins, nil, nil), expected: false, }, { name: "different enabled", - config: NewFeeManagerConfig(big.NewInt(3), admins, nil, nil), - other: NewFeeManagerConfig(big.NewInt(3), admins, enableds, nil), + config: NewConfig(big.NewInt(3), admins, nil, nil), + other: NewConfig(big.NewInt(3), admins, enableds, nil), expected: false, }, { name: "non-nil initial config and nil initial config", - config: NewFeeManagerConfig(big.NewInt(3), admins, nil, &validFeeConfig), - other: NewFeeManagerConfig(big.NewInt(3), admins, nil, nil), + config: NewConfig(big.NewInt(3), admins, nil, &validFeeConfig), + other: NewConfig(big.NewInt(3), admins, nil, nil), expected: false, }, { name: "different initial config", - config: NewFeeManagerConfig(big.NewInt(3), admins, nil, &validFeeConfig), - other: NewFeeManagerConfig(big.NewInt(3), admins, nil, + config: NewConfig(big.NewInt(3), admins, nil, &validFeeConfig), + other: NewConfig(big.NewInt(3), admins, nil, func() *commontype.FeeConfig { c := validFeeConfig c.GasLimit = big.NewInt(123) @@ -113,8 +113,8 @@ func TestEqualFeeManagerConfig(t *testing.T) { }, { name: "same config", - config: NewFeeManagerConfig(big.NewInt(3), admins, nil, &validFeeConfig), - other: NewFeeManagerConfig(big.NewInt(3), admins, nil, &validFeeConfig), + config: NewConfig(big.NewInt(3), admins, nil, &validFeeConfig), + other: NewConfig(big.NewInt(3), admins, nil, &validFeeConfig), expected: true, }, } diff --git a/precompile/feemanager/contract.go b/precompile/contracts/feemanager/contract.go similarity index 68% rename from precompile/feemanager/contract.go rename to precompile/contracts/feemanager/contract.go index 5fdc3fb656..0d08338d36 100644 --- a/precompile/feemanager/contract.go +++ b/precompile/contracts/feemanager/contract.go @@ -9,8 +9,8 @@ import ( "math/big" "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/precompile" "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" ) @@ -33,35 +33,34 @@ const ( // [numFeeConfigField] fields in FeeConfig struct feeConfigInputLen = common.HashLength * numFeeConfigField - SetFeeConfigGasCost = precompile.WriteGasCostPerSlot * (numFeeConfigField + 1) // plus one for setting last changed at - GetFeeConfigGasCost = precompile.ReadGasCostPerSlot * numFeeConfigField - GetLastChangedAtGasCost = precompile.ReadGasCostPerSlot + SetFeeConfigGasCost = contract.WriteGasCostPerSlot * (numFeeConfigField + 1) // plus one for setting last changed at + GetFeeConfigGasCost = contract.ReadGasCostPerSlot * numFeeConfigField + GetLastChangedAtGasCost = contract.ReadGasCostPerSlot ) var ( - _ precompile.StatefulPrecompileConfig = &FeeManagerConfig{} // Singleton StatefulPrecompiledContract for setting fee configs by permissioned callers. - FeeManagerPrecompile precompile.StatefulPrecompiledContract = createFeeManagerPrecompile(precompile.FeeManagerAddress) + FeeManagerPrecompile contract.StatefulPrecompiledContract = createFeeManagerPrecompile() - setFeeConfigSignature = precompile.CalculateFunctionSelector("setFeeConfig(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)") - getFeeConfigSignature = precompile.CalculateFunctionSelector("getFeeConfig()") - getFeeConfigLastChangedAtSignature = precompile.CalculateFunctionSelector("getFeeConfigLastChangedAt()") + setFeeConfigSignature = contract.CalculateFunctionSelector("setFeeConfig(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)") + getFeeConfigSignature = contract.CalculateFunctionSelector("getFeeConfig()") + getFeeConfigLastChangedAtSignature = contract.CalculateFunctionSelector("getFeeConfigLastChangedAt()") feeConfigLastChangedAtKey = common.Hash{'l', 'c', 'a'} ErrCannotChangeFee = errors.New("non-enabled cannot change fee config") ) -// GetFeeManagerStatus returns the role of [address] for the FeeManager allowlist. -func GetFeeManagerStatus(stateDB precompile.StateDB, address common.Address) allowlist.AllowListRole { - return allowlist.GetAllowListStatus(stateDB, precompile.FeeManagerAddress, address) +// GetFeeManagerStatus returns the role of [address] for the fee config manager list. +func GetFeeManagerStatus(stateDB contract.StateDB, address common.Address) allowlist.Role { + return allowlist.GetAllowListStatus(stateDB, ContractAddress, address) } // SetFeeManagerStatus sets the permissions of [address] to [role] for the -// FeeManager allowlist. -func SetFeeManagerStatus(stateDB precompile.StateDB, address common.Address, role allowlist.AllowListRole) { - allowlist.SetAllowListRole(stateDB, precompile.FeeManagerAddress, address, role) +// fee config manager list. assumes [role] has already been verified as valid. +func SetFeeManagerStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) { + allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) } // PackGetFeeConfigInput packs the getFeeConfig signature @@ -100,12 +99,12 @@ func packFeeConfigHelper(feeConfig commontype.FeeConfig, useSelector bool) ([]by if useSelector { res := make([]byte, len(setFeeConfigSignature)+feeConfigInputLen) - err := precompile.PackOrderedHashesWithSelector(res, setFeeConfigSignature, hashes) + err := contract.PackOrderedHashesWithSelector(res, setFeeConfigSignature, hashes) return res, err } res := make([]byte, len(hashes)*common.HashLength) - err := precompile.PackOrderedHashes(res, hashes) + err := contract.PackOrderedHashes(res, hashes) return res, err } @@ -118,7 +117,7 @@ func UnpackFeeConfigInput(input []byte) (commontype.FeeConfig, error) { feeConfig := commontype.FeeConfig{} for i := minFeeConfigFieldKey; i <= numFeeConfigField; i++ { listIndex := i - 1 - packedElement := precompile.PackedHash(input, listIndex) + packedElement := contract.PackedHash(input, listIndex) switch i { case gasLimitKey: feeConfig.GasLimit = new(big.Int).SetBytes(packedElement) @@ -145,10 +144,10 @@ func UnpackFeeConfigInput(input []byte) (commontype.FeeConfig, error) { } // GetStoredFeeConfig returns fee config from contract storage in given state -func GetStoredFeeConfig(stateDB precompile.StateDB) commontype.FeeConfig { +func GetStoredFeeConfig(stateDB contract.StateDB) commontype.FeeConfig { feeConfig := commontype.FeeConfig{} for i := minFeeConfigFieldKey; i <= numFeeConfigField; i++ { - val := stateDB.GetState(precompile.FeeManagerAddress, common.Hash{byte(i)}) + val := stateDB.GetState(ContractAddress, common.Hash{byte(i)}) switch i { case gasLimitKey: feeConfig.GasLimit = new(big.Int).Set(val.Big()) @@ -174,14 +173,14 @@ func GetStoredFeeConfig(stateDB precompile.StateDB) commontype.FeeConfig { return feeConfig } -func GetFeeConfigLastChangedAt(stateDB precompile.StateDB) *big.Int { - val := stateDB.GetState(precompile.FeeManagerAddress, feeConfigLastChangedAtKey) +func GetFeeConfigLastChangedAt(stateDB contract.StateDB) *big.Int { + val := stateDB.GetState(ContractAddress, feeConfigLastChangedAtKey) return val.Big() } // StoreFeeConfig stores given [feeConfig] and block number in the [blockContext] to the [stateDB]. // A validation on [feeConfig] is done before storing. -func StoreFeeConfig(stateDB precompile.StateDB, feeConfig commontype.FeeConfig, blockContext precompile.BlockContext) error { +func StoreFeeConfig(stateDB contract.StateDB, feeConfig commontype.FeeConfig, blockContext contract.BlockContext) error { if err := feeConfig.Verify(); err != nil { return fmt.Errorf("cannot verify fee config: %w", err) } @@ -209,22 +208,21 @@ func StoreFeeConfig(stateDB precompile.StateDB, feeConfig commontype.FeeConfig, // This should never encounter an unknown fee config key panic(fmt.Sprintf("unknown fee config key: %d", i)) } - stateDB.SetState(precompile.FeeManagerAddress, common.Hash{byte(i)}, input) + stateDB.SetState(ContractAddress, common.Hash{byte(i)}, input) } blockNumber := blockContext.Number() if blockNumber == nil { return fmt.Errorf("blockNumber cannot be nil") } - stateDB.SetState(precompile.FeeManagerAddress, feeConfigLastChangedAtKey, common.BigToHash(blockNumber)) - + stateDB.SetState(ContractAddress, feeConfigLastChangedAtKey, common.BigToHash(blockNumber)) return nil } // setFeeConfig checks if the caller has permissions to set the fee config. // The execution function parses [input] into FeeConfig structure and sets contract storage accordingly. -func setFeeConfig(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = precompile.DeductGas(suppliedGas, SetFeeConfigGasCost); err != nil { +func setFeeConfig(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, SetFeeConfigGasCost); err != nil { return nil, 0, err } @@ -238,8 +236,8 @@ func setFeeConfig(accessibleState precompile.PrecompileAccessibleState, caller c } stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := allowlist.GetAllowListStatus(stateDB, precompile.FeeManagerAddress, caller) + // Verify that the caller is in the allow list and therefore has the right to call this function. + callerStatus := GetFeeManagerStatus(stateDB, caller) if !callerStatus.IsEnabled() { return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotChangeFee, caller) } @@ -254,8 +252,8 @@ func setFeeConfig(accessibleState precompile.PrecompileAccessibleState, caller c // getFeeConfig returns the stored fee config as an output. // The execution function reads the contract state for the stored fee config and returns the output. -func getFeeConfig(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = precompile.DeductGas(suppliedGas, GetFeeConfigGasCost); err != nil { +func getFeeConfig(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, GetFeeConfigGasCost); err != nil { return nil, 0, err } @@ -272,8 +270,8 @@ func getFeeConfig(accessibleState precompile.PrecompileAccessibleState, caller c // getFeeConfigLastChangedAt returns the block number that fee config was last changed in. // The execution function reads the contract state for the stored block number and returns the output. -func getFeeConfigLastChangedAt(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = precompile.DeductGas(suppliedGas, GetLastChangedAtGasCost); err != nil { +func getFeeConfigLastChangedAt(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, GetLastChangedAtGasCost); err != nil { return nil, 0, err } @@ -285,17 +283,17 @@ func getFeeConfigLastChangedAt(accessibleState precompile.PrecompileAccessibleSt // createFeeManagerPrecompile returns a StatefulPrecompiledContract // with getters and setters for the chain's fee config. Access to the getters/setters -// is controlled by an allow list for [precompileAddr]. -func createFeeManagerPrecompile(precompileAddr common.Address) precompile.StatefulPrecompiledContract { - FeeManagerFunctions := allowlist.CreateAllowListFunctions(precompileAddr) +// is controlled by an allow list for ContractAddress. +func createFeeManagerPrecompile() contract.StatefulPrecompiledContract { + feeManagerFunctions := allowlist.CreateAllowListFunctions(ContractAddress) - setFeeConfigFunc := precompile.NewStatefulPrecompileFunction(setFeeConfigSignature, setFeeConfig) - getFeeConfigFunc := precompile.NewStatefulPrecompileFunction(getFeeConfigSignature, getFeeConfig) - getFeeConfigLastChangedAtFunc := precompile.NewStatefulPrecompileFunction(getFeeConfigLastChangedAtSignature, getFeeConfigLastChangedAt) + setFeeConfigFunc := contract.NewStatefulPrecompileFunction(setFeeConfigSignature, setFeeConfig) + getFeeConfigFunc := contract.NewStatefulPrecompileFunction(getFeeConfigSignature, getFeeConfig) + getFeeConfigLastChangedAtFunc := contract.NewStatefulPrecompileFunction(getFeeConfigLastChangedAtSignature, getFeeConfigLastChangedAt) - FeeManagerFunctions = append(FeeManagerFunctions, setFeeConfigFunc, getFeeConfigFunc, getFeeConfigLastChangedAtFunc) + feeManagerFunctions = append(feeManagerFunctions, setFeeConfigFunc, getFeeConfigFunc, getFeeConfigLastChangedAtFunc) // Construct the contract with no fallback function. - contract, err := precompile.NewStatefulPrecompileContract(nil, FeeManagerFunctions) + contract, err := contract.NewStatefulPrecompileContract(nil, feeManagerFunctions) // TODO Change this to be returned as an error after refactoring this precompile // to use the new precompile template. if err != nil { diff --git a/precompile/contracts/feemanager/contract_test.go b/precompile/contracts/feemanager/contract_test.go new file mode 100644 index 0000000000..698fce802d --- /dev/null +++ b/precompile/contracts/feemanager/contract_test.go @@ -0,0 +1,273 @@ +// (c) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package feemanager + +import ( + "math/big" + "testing" + + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/core/rawdb" + "github.com/ava-labs/subnet-evm/core/state" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/vmerrs" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +type precompileTest struct { + caller common.Address + input func() []byte + suppliedGas uint64 + readOnly bool + + config config.Config + + preCondition func(t *testing.T, state *state.StateDB) + assertState func(t *testing.T, state *state.StateDB) + + expectedRes []byte + expectedErr string +} + +var testFeeConfig = commontype.FeeConfig{ + GasLimit: big.NewInt(8_000_000), + TargetBlockRate: 2, // in seconds + + MinBaseFee: big.NewInt(25_000_000_000), + TargetGas: big.NewInt(15_000_000), + BaseFeeChangeDenominator: big.NewInt(36), + + MinBlockGasCost: big.NewInt(0), + MaxBlockGasCost: big.NewInt(1_000_000), + BlockGasCostStep: big.NewInt(200_000), +} + +func TestFeeManagerRun(t *testing.T) { + testBlockNumber := big.NewInt(7) + + adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") + enabledAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") + noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") + + for name, test := range map[string]precompileTest{ + "set config from no role fails": { + caller: noRoleAddr, + input: func() []byte { + input, err := PackSetFeeConfig(testFeeConfig) + require.NoError(t, err) + + return input + }, + suppliedGas: SetFeeConfigGasCost, + readOnly: false, + expectedErr: ErrCannotChangeFee.Error(), + }, + "set config from enabled address": { + caller: enabledAddr, + input: func() []byte { + input, err := PackSetFeeConfig(testFeeConfig) + require.NoError(t, err) + + return input + }, + suppliedGas: SetFeeConfigGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + feeConfig := GetStoredFeeConfig(state) + require.Equal(t, testFeeConfig, feeConfig) + }, + }, + "set invalid config from enabled address": { + caller: enabledAddr, + input: func() []byte { + feeConfig := testFeeConfig + feeConfig.MinBlockGasCost = new(big.Int).Mul(feeConfig.MaxBlockGasCost, common.Big2) + input, err := PackSetFeeConfig(feeConfig) + require.NoError(t, err) + + return input + }, + suppliedGas: SetFeeConfigGasCost, + readOnly: false, + expectedRes: nil, + config: &Config{ + InitialFeeConfig: &testFeeConfig, + }, + expectedErr: "cannot be greater than maxBlockGasCost", + assertState: func(t *testing.T, state *state.StateDB) { + feeConfig := GetStoredFeeConfig(state) + require.Equal(t, testFeeConfig, feeConfig) + }, + }, + "set config from admin address": { + caller: adminAddr, + input: func() []byte { + input, err := PackSetFeeConfig(testFeeConfig) + require.NoError(t, err) + + return input + }, + suppliedGas: SetFeeConfigGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + feeConfig := GetStoredFeeConfig(state) + require.Equal(t, testFeeConfig, feeConfig) + lastChangedAt := GetFeeConfigLastChangedAt(state) + require.EqualValues(t, testBlockNumber, lastChangedAt) + }, + }, + "get fee config from non-enabled address": { + caller: noRoleAddr, + preCondition: func(t *testing.T, state *state.StateDB) { + err := StoreFeeConfig(state, testFeeConfig, contract.NewMockBlockContext(big.NewInt(6), 0)) + require.NoError(t, err) + }, + input: func() []byte { + return PackGetFeeConfigInput() + }, + suppliedGas: GetFeeConfigGasCost, + readOnly: true, + expectedRes: func() []byte { + res, err := PackFeeConfig(testFeeConfig) + require.NoError(t, err) + return res + }(), + assertState: func(t *testing.T, state *state.StateDB) { + feeConfig := GetStoredFeeConfig(state) + lastChangedAt := GetFeeConfigLastChangedAt(state) + require.Equal(t, testFeeConfig, feeConfig) + require.EqualValues(t, big.NewInt(6), lastChangedAt) + }, + }, + "get initial fee config": { + caller: noRoleAddr, + input: func() []byte { + return PackGetFeeConfigInput() + }, + suppliedGas: GetFeeConfigGasCost, + config: &Config{ + InitialFeeConfig: &testFeeConfig, + }, + readOnly: true, + expectedRes: func() []byte { + res, err := PackFeeConfig(testFeeConfig) + require.NoError(t, err) + return res + }(), + assertState: func(t *testing.T, state *state.StateDB) { + feeConfig := GetStoredFeeConfig(state) + lastChangedAt := GetFeeConfigLastChangedAt(state) + require.Equal(t, testFeeConfig, feeConfig) + require.EqualValues(t, testBlockNumber, lastChangedAt) + }, + }, + "get last changed at from non-enabled address": { + caller: noRoleAddr, + preCondition: func(t *testing.T, state *state.StateDB) { + err := StoreFeeConfig(state, testFeeConfig, contract.NewMockBlockContext(testBlockNumber, 0)) + require.NoError(t, err) + }, + input: func() []byte { + return PackGetLastChangedAtInput() + }, + suppliedGas: GetLastChangedAtGasCost, + readOnly: true, + expectedRes: common.BigToHash(testBlockNumber).Bytes(), + assertState: func(t *testing.T, state *state.StateDB) { + feeConfig := GetStoredFeeConfig(state) + lastChangedAt := GetFeeConfigLastChangedAt(state) + require.Equal(t, testFeeConfig, feeConfig) + require.Equal(t, testBlockNumber, lastChangedAt) + }, + }, + "readOnly setFeeConfig with noRole fails": { + caller: noRoleAddr, + input: func() []byte { + input, err := PackSetFeeConfig(testFeeConfig) + require.NoError(t, err) + + return input + }, + suppliedGas: SetFeeConfigGasCost, + readOnly: true, + expectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "readOnly setFeeConfig with allow role fails": { + caller: enabledAddr, + input: func() []byte { + input, err := PackSetFeeConfig(testFeeConfig) + require.NoError(t, err) + + return input + }, + suppliedGas: SetFeeConfigGasCost, + readOnly: true, + expectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "readOnly setFeeConfig with admin role fails": { + caller: adminAddr, + input: func() []byte { + input, err := PackSetFeeConfig(testFeeConfig) + require.NoError(t, err) + + return input + }, + suppliedGas: SetFeeConfigGasCost, + readOnly: true, + expectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "insufficient gas setFeeConfig from admin": { + caller: adminAddr, + input: func() []byte { + input, err := PackSetFeeConfig(testFeeConfig) + require.NoError(t, err) + + return input + }, + suppliedGas: SetFeeConfigGasCost - 1, + readOnly: false, + expectedErr: vmerrs.ErrOutOfGas.Error(), + }, + } { + t.Run(name, func(t *testing.T) { + db := rawdb.NewMemoryDatabase() + state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) + require.NoError(t, err) + + // Set up the state so that each address has the expected permissions at the start. + SetFeeManagerStatus(state, adminAddr, allowlist.AdminRole) + SetFeeManagerStatus(state, enabledAddr, allowlist.EnabledRole) + require.Equal(t, allowlist.AdminRole, GetFeeManagerStatus(state, adminAddr)) + require.Equal(t, allowlist.EnabledRole, GetFeeManagerStatus(state, enabledAddr)) + + if test.preCondition != nil { + test.preCondition(t, state) + } + blockContext := contract.NewMockBlockContext(testBlockNumber, 0) + accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) + if test.config != nil { + Module.Configure(nil, test.config, state, blockContext) + } + ret, remainingGas, err := FeeManagerPrecompile.Run(accesibleState, test.caller, ContractAddress, test.input(), test.suppliedGas, test.readOnly) + if len(test.expectedErr) != 0 { + require.ErrorContains(t, err, test.expectedErr) + } else { + require.NoError(t, err) + } + + require.Equal(t, uint64(0), remainingGas) + require.Equal(t, test.expectedRes, ret) + + if test.assertState != nil { + test.assertState(t, state) + } + }) + } +} diff --git a/precompile/contracts/feemanager/module.go b/precompile/contracts/feemanager/module.go new file mode 100644 index 0000000000..a725bb3f3d --- /dev/null +++ b/precompile/contracts/feemanager/module.go @@ -0,0 +1,61 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package feemanager + +import ( + "fmt" + + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ethereum/go-ethereum/common" +) + +var _ contract.Configurator = &configurator{} + +// ConfigKey is the key used in json config files to specify this precompile config. +// must be unique across all precompiles. +const ConfigKey = "feeManagerConfig" + +var ContractAddress = common.HexToAddress("0x0200000000000000000000000000000000000003") + +var Module = modules.Module{ + ConfigKey: ConfigKey, + Address: ContractAddress, + Contract: FeeManagerPrecompile, + Configurator: &configurator{}, +} + +type configurator struct{} + +func init() { + if err := modules.RegisterModule(Module); err != nil { + panic(err) + } +} + +func (*configurator) NewConfig() config.Config { + return &Config{} +} + +// Configure configures [state] with the desired admins based on [configIface]. +func (*configurator) Configure(chainConfig contract.ChainConfig, cfg config.Config, state contract.StateDB, blockContext contract.BlockContext) error { + config, ok := cfg.(*Config) + if !ok { + return fmt.Errorf("incorrect config %T: %v", config, config) + } + // Store the initial fee config into the state when the fee manager activates. + if config.InitialFeeConfig != nil { + if err := StoreFeeConfig(state, *config.InitialFeeConfig, blockContext); err != nil { + // This should not happen since we already checked this config with Verify() + return fmt.Errorf("cannot configure given initial fee config: %w", err) + } + } else { + if err := StoreFeeConfig(state, chainConfig.GetFeeConfig(), blockContext); err != nil { + // This should not happen since we already checked the chain config in the genesis creation. + return fmt.Errorf("cannot configure fee config in chain config: %w", err) + } + } + return config.Config.Configure(state, ContractAddress) +} diff --git a/precompile/contracts/nativeminter/config.go b/precompile/contracts/nativeminter/config.go new file mode 100644 index 0000000000..21278e8de6 --- /dev/null +++ b/precompile/contracts/nativeminter/config.go @@ -0,0 +1,95 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package nativeminter + +import ( + "fmt" + "math/big" + + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" +) + +var _ config.Config = &Config{} + +// Config implements the StatefulPrecompileConfig interface while adding in the +// ContractNativeMinter specific precompile config. +type Config struct { + allowlist.Config + config.Upgrade + InitialMint map[common.Address]*math.HexOrDecimal256 `json:"initialMint,omitempty"` // addresses to receive the initial mint mapped to the amount to mint +} + +// NewConfig returns a config for a network upgrade at [blockTimestamp] that enables +// ContractNativeMinter with the given [admins] and [enableds] as members of the allowlist. Also mints balances according to [initialMint] when the upgrade activates. +func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialMint map[common.Address]*math.HexOrDecimal256) *Config { + return &Config{ + Config: allowlist.Config{ + AdminAddresses: admins, + EnabledAddresses: enableds, + }, + Upgrade: config.Upgrade{BlockTimestamp: blockTimestamp}, + InitialMint: initialMint, + } +} + +// NewDisableConfig returns config for a network upgrade at [blockTimestamp] +// that disables ContractNativeMinter. +func NewDisableConfig(blockTimestamp *big.Int) *Config { + return &Config{ + Upgrade: config.Upgrade{ + BlockTimestamp: blockTimestamp, + Disable: true, + }, + } +} +func (*Config) Key() string { return ConfigKey } + +// Equal returns true if [cfg] is a [*ContractNativeMinterConfig] and it has been configured identical to [c]. +func (c *Config) Equal(cfg config.Config) bool { + // typecast before comparison + other, ok := (cfg).(*Config) + if !ok { + return false + } + eq := c.Upgrade.Equal(&other.Upgrade) && c.Config.Equal(&other.Config) + if !eq { + return false + } + + if len(c.InitialMint) != len(other.InitialMint) { + return false + } + + for address, amount := range c.InitialMint { + val, ok := other.InitialMint[address] + if !ok { + return false + } + bigIntAmount := (*big.Int)(amount) + bigIntVal := (*big.Int)(val) + if !utils.BigNumEqual(bigIntAmount, bigIntVal) { + return false + } + } + + return true +} + +func (c *Config) Verify() error { + // ensure that all of the initial mint values in the map are non-nil positive values + for addr, amount := range c.InitialMint { + if amount == nil { + return fmt.Errorf("initial mint cannot contain nil amount for address %s", addr) + } + bigIntAmount := (*big.Int)(amount) + if bigIntAmount.Sign() < 1 { + return fmt.Errorf("initial mint cannot contain invalid amount %v for address %s", bigIntAmount, addr) + } + } + return c.Config.Verify() +} diff --git a/precompile/nativeminter/config_test.go b/precompile/contracts/nativeminter/config_test.go similarity index 67% rename from precompile/nativeminter/config_test.go rename to precompile/contracts/nativeminter/config_test.go index 9eb2441454..d1232b9cc3 100644 --- a/precompile/nativeminter/config_test.go +++ b/precompile/contracts/nativeminter/config_test.go @@ -7,7 +7,7 @@ import ( "math/big" "testing" - "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/stretchr/testify/require" @@ -18,27 +18,27 @@ func TestVerifyContractNativeMinterConfig(t *testing.T) { enableds := []common.Address{{2}} tests := []struct { name string - config precompile.StatefulPrecompileConfig + config config.Config expectedError string }{ { name: "invalid allow list config in native minter allowlist", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, admins, nil), + config: NewConfig(big.NewInt(3), admins, admins, nil), expectedError: "cannot set address", }, { name: "duplicate admins in config in native minter allowlist", - config: NewContractNativeMinterConfig(big.NewInt(3), append(admins, admins[0]), enableds, nil), + config: NewConfig(big.NewInt(3), append(admins, admins[0]), enableds, nil), expectedError: "duplicate address", }, { name: "duplicate enableds in config in native minter allowlist", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, append(enableds, enableds[0]), nil), + config: NewConfig(big.NewInt(3), admins, append(enableds, enableds[0]), nil), expectedError: "duplicate address", }, { name: "nil amount in native minter config", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + config: NewConfig(big.NewInt(3), admins, nil, map[common.Address]*math.HexOrDecimal256{ common.HexToAddress("0x01"): math.NewHexOrDecimal256(123), common.HexToAddress("0x02"): nil, @@ -47,7 +47,7 @@ func TestVerifyContractNativeMinterConfig(t *testing.T) { }, { name: "negative amount in native minter config", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + config: NewConfig(big.NewInt(3), admins, nil, map[common.Address]*math.HexOrDecimal256{ common.HexToAddress("0x01"): math.NewHexOrDecimal256(123), common.HexToAddress("0x02"): math.NewHexOrDecimal256(-1), @@ -74,41 +74,41 @@ func TestEqualContractNativeMinterConfig(t *testing.T) { enableds := []common.Address{{2}} tests := []struct { name string - config precompile.StatefulPrecompileConfig - other precompile.StatefulPrecompileConfig + config config.Config + other config.Config expected bool }{ { name: "non-nil config and nil other", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, enableds, nil), + config: NewConfig(big.NewInt(3), admins, enableds, nil), other: nil, expected: false, }, { name: "different type", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, enableds, nil), - other: precompile.NewNoopStatefulPrecompileConfig(), + config: NewConfig(big.NewInt(3), admins, enableds, nil), + other: config.NewNoopStatefulPrecompileConfig(), expected: false, }, { name: "different timestamps", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, nil), - other: NewContractNativeMinterConfig(big.NewInt(4), admins, nil, nil), + config: NewConfig(big.NewInt(3), admins, nil, nil), + other: NewConfig(big.NewInt(4), admins, nil, nil), expected: false, }, { name: "different enabled", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, nil), - other: NewContractNativeMinterConfig(big.NewInt(3), admins, enableds, nil), + config: NewConfig(big.NewInt(3), admins, nil, nil), + other: NewConfig(big.NewInt(3), admins, enableds, nil), expected: false, }, { name: "different initial mint amounts", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + config: NewConfig(big.NewInt(3), admins, nil, map[common.Address]*math.HexOrDecimal256{ common.HexToAddress("0x01"): math.NewHexOrDecimal256(1), }), - other: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + other: NewConfig(big.NewInt(3), admins, nil, map[common.Address]*math.HexOrDecimal256{ common.HexToAddress("0x01"): math.NewHexOrDecimal256(2), }), @@ -116,11 +116,11 @@ func TestEqualContractNativeMinterConfig(t *testing.T) { }, { name: "different initial mint addresses", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + config: NewConfig(big.NewInt(3), admins, nil, map[common.Address]*math.HexOrDecimal256{ common.HexToAddress("0x01"): math.NewHexOrDecimal256(1), }), - other: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + other: NewConfig(big.NewInt(3), admins, nil, map[common.Address]*math.HexOrDecimal256{ common.HexToAddress("0x02"): math.NewHexOrDecimal256(1), }), @@ -128,11 +128,11 @@ func TestEqualContractNativeMinterConfig(t *testing.T) { }, { name: "same config", - config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + config: NewConfig(big.NewInt(3), admins, nil, map[common.Address]*math.HexOrDecimal256{ common.HexToAddress("0x01"): math.NewHexOrDecimal256(1), }), - other: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + other: NewConfig(big.NewInt(3), admins, nil, map[common.Address]*math.HexOrDecimal256{ common.HexToAddress("0x01"): math.NewHexOrDecimal256(1), }), diff --git a/precompile/nativeminter/contract.go b/precompile/contracts/nativeminter/contract.go similarity index 60% rename from precompile/nativeminter/contract.go rename to precompile/contracts/nativeminter/contract.go index 7098c252cb..63d6a624da 100644 --- a/precompile/nativeminter/contract.go +++ b/precompile/contracts/nativeminter/contract.go @@ -8,8 +8,8 @@ import ( "fmt" "math/big" - "github.com/ava-labs/subnet-evm/precompile" "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" ) @@ -25,29 +25,29 @@ const ( var ( // Singleton StatefulPrecompiledContract for minting native assets by permissioned callers. - ContractNativeMinterPrecompile precompile.StatefulPrecompiledContract = createNativeMinterPrecompile(precompile.ContractNativeMinterAddress) + ContractNativeMinterPrecompile contract.StatefulPrecompiledContract = createNativeMinterPrecompile() - mintSignature = precompile.CalculateFunctionSelector("mintNativeCoin(address,uint256)") // address, amount + mintSignature = contract.CalculateFunctionSelector("mintNativeCoin(address,uint256)") // address, amount ErrCannotMint = errors.New("non-enabled cannot mint") ) // GetContractNativeMinterStatus returns the role of [address] for the minter list. -func GetContractNativeMinterStatus(stateDB precompile.StateDB, address common.Address) allowlist.AllowListRole { - return allowlist.GetAllowListStatus(stateDB, precompile.ContractNativeMinterAddress, address) +func GetContractNativeMinterStatus(stateDB contract.StateDB, address common.Address) allowlist.Role { + return allowlist.GetAllowListStatus(stateDB, ContractAddress, address) } // SetContractNativeMinterStatus sets the permissions of [address] to [role] for the // minter list. assumes [role] has already been verified as valid. -func SetContractNativeMinterStatus(stateDB precompile.StateDB, address common.Address, role allowlist.AllowListRole) { - allowlist.SetAllowListRole(stateDB, precompile.ContractNativeMinterAddress, address, role) +func SetContractNativeMinterStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) { + allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) } // PackMintInput packs [address] and [amount] into the appropriate arguments for minting operation. // Assumes that [amount] can be represented by 32 bytes. func PackMintInput(address common.Address, amount *big.Int) ([]byte, error) { // function selector (4 bytes) + input(hash for address + hash for amount) - res := make([]byte, precompile.SelectorLen+mintInputLen) - err := precompile.PackOrderedHashesWithSelector(res, mintSignature, []common.Hash{ + res := make([]byte, contract.SelectorLen+mintInputLen) + err := contract.PackOrderedHashesWithSelector(res, mintSignature, []common.Hash{ address.Hash(), common.BigToHash(amount), }) @@ -61,15 +61,15 @@ func UnpackMintInput(input []byte) (common.Address, *big.Int, error) { if len(input) != mintInputLen { return common.Address{}, nil, fmt.Errorf("invalid input length for minting: %d", len(input)) } - to := common.BytesToAddress(precompile.PackedHash(input, mintInputAddressSlot)) - assetAmount := new(big.Int).SetBytes(precompile.PackedHash(input, mintInputAmountSlot)) + to := common.BytesToAddress(contract.PackedHash(input, mintInputAddressSlot)) + assetAmount := new(big.Int).SetBytes(contract.PackedHash(input, mintInputAmountSlot)) return to, assetAmount, nil } // mintNativeCoin checks if the caller is permissioned for minting operation. // The execution function parses the [input] into native coin amount and receiver address. -func mintNativeCoin(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = precompile.DeductGas(suppliedGas, MintGasCost); err != nil { +func mintNativeCoin(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, MintGasCost); err != nil { return nil, 0, err } @@ -83,8 +83,8 @@ func mintNativeCoin(accessibleState precompile.PrecompileAccessibleState, caller } stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list - callerStatus := allowlist.GetAllowListStatus(stateDB, precompile.ContractNativeMinterAddress, caller) + // Verify that the caller is in the allow list and therefore has the right to call this function. + callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) if !callerStatus.IsEnabled() { return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotMint, caller) } @@ -101,14 +101,14 @@ func mintNativeCoin(accessibleState precompile.PrecompileAccessibleState, caller // createNativeMinterPrecompile returns a StatefulPrecompiledContract for native coin minting. The precompile // is accessed controlled by an allow list at [precompileAddr]. -func createNativeMinterPrecompile(precompileAddr common.Address) precompile.StatefulPrecompiledContract { - enabledFuncs := allowlist.CreateAllowListFunctions(precompileAddr) +func createNativeMinterPrecompile() contract.StatefulPrecompiledContract { + enabledFuncs := allowlist.CreateAllowListFunctions(ContractAddress) - mintFunc := precompile.NewStatefulPrecompileFunction(mintSignature, mintNativeCoin) + mintFunc := contract.NewStatefulPrecompileFunction(mintSignature, mintNativeCoin) enabledFuncs = append(enabledFuncs, mintFunc) // Construct the contract with no fallback function. - contract, err := precompile.NewStatefulPrecompileContract(nil, enabledFuncs) + contract, err := contract.NewStatefulPrecompileContract(nil, enabledFuncs) // TODO: Change this to be returned as an error after refactoring this precompile // to use the new precompile template. if err != nil { diff --git a/precompile/contracts/nativeminter/contract_test.go b/precompile/contracts/nativeminter/contract_test.go new file mode 100644 index 0000000000..4eb3202495 --- /dev/null +++ b/precompile/contracts/nativeminter/contract_test.go @@ -0,0 +1,193 @@ +// (c) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package nativeminter + +import ( + "testing" + + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/subnet-evm/core/rawdb" + "github.com/ava-labs/subnet-evm/core/state" + "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/vmerrs" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/stretchr/testify/require" +) + +type precompileTest struct { + caller common.Address + input func() []byte + suppliedGas uint64 + readOnly bool + + config config.Config + + preCondition func(t *testing.T, state *state.StateDB) + assertState func(t *testing.T, state *state.StateDB) + + expectedRes []byte + expectedErr string +} + +func TestContractNativeMinterRun(t *testing.T) { + adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") + enabledAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") + noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") + + for name, test := range map[string]precompileTest{ + "mint funds from no role fails": { + caller: noRoleAddr, + input: func() []byte { + input, err := PackMintInput(noRoleAddr, common.Big1) + require.NoError(t, err) + + return input + }, + suppliedGas: MintGasCost, + readOnly: false, + expectedErr: ErrCannotMint.Error(), + }, + "mint funds from enabled address": { + caller: enabledAddr, + input: func() []byte { + input, err := PackMintInput(enabledAddr, common.Big1) + require.NoError(t, err) + + return input + }, + suppliedGas: MintGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + require.Equal(t, common.Big1, state.GetBalance(enabledAddr), "expected minted funds") + }, + }, + "initial mint funds": { + caller: enabledAddr, + config: &Config{ + InitialMint: map[common.Address]*math.HexOrDecimal256{ + enabledAddr: math.NewHexOrDecimal256(2), + }, + }, + assertState: func(t *testing.T, state *state.StateDB) { + require.Equal(t, common.Big2, state.GetBalance(enabledAddr), "expected minted funds") + }, + }, + "mint funds from admin address": { + caller: adminAddr, + input: func() []byte { + input, err := PackMintInput(adminAddr, common.Big1) + require.NoError(t, err) + + return input + }, + suppliedGas: MintGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + require.Equal(t, common.Big1, state.GetBalance(adminAddr), "expected minted funds") + }, + }, + "mint max big funds": { + caller: adminAddr, + input: func() []byte { + input, err := PackMintInput(adminAddr, math.MaxBig256) + require.NoError(t, err) + + return input + }, + suppliedGas: MintGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + require.Equal(t, math.MaxBig256, state.GetBalance(adminAddr), "expected minted funds") + }, + }, + "readOnly mint with noRole fails": { + caller: noRoleAddr, + input: func() []byte { + input, err := PackMintInput(adminAddr, common.Big1) + require.NoError(t, err) + + return input + }, + suppliedGas: MintGasCost, + readOnly: true, + expectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "readOnly mint with allow role fails": { + caller: enabledAddr, + input: func() []byte { + input, err := PackMintInput(enabledAddr, common.Big1) + require.NoError(t, err) + + return input + }, + suppliedGas: MintGasCost, + readOnly: true, + expectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "readOnly mint with admin role fails": { + caller: adminAddr, + input: func() []byte { + input, err := PackMintInput(adminAddr, common.Big1) + require.NoError(t, err) + + return input + }, + suppliedGas: MintGasCost, + readOnly: true, + expectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "insufficient gas mint from admin": { + caller: adminAddr, + input: func() []byte { + input, err := PackMintInput(enabledAddr, common.Big1) + require.NoError(t, err) + + return input + }, + suppliedGas: MintGasCost - 1, + readOnly: false, + expectedErr: vmerrs.ErrOutOfGas.Error(), + }, + } { + t.Run(name, func(t *testing.T) { + db := rawdb.NewMemoryDatabase() + state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) + require.NoError(t, err) + + // Set up the state so that each address has the expected permissions at the start. + SetContractNativeMinterStatus(state, adminAddr, allowlist.AdminRole) + SetContractNativeMinterStatus(state, enabledAddr, allowlist.EnabledRole) + require.Equal(t, allowlist.AdminRole, GetContractNativeMinterStatus(state, adminAddr)) + require.Equal(t, allowlist.EnabledRole, GetContractNativeMinterStatus(state, enabledAddr)) + + blockContext := contract.NewMockBlockContext(common.Big0, 0) + accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) + if test.config != nil { + Module.Configure(params.TestChainConfig, test.config, state, blockContext) + } + if test.input != nil { + ret, remainingGas, err := ContractNativeMinterPrecompile.Run(accesibleState, test.caller, ContractAddress, test.input(), test.suppliedGas, test.readOnly) + if len(test.expectedErr) != 0 { + require.ErrorContains(t, err, test.expectedErr) + } else { + require.NoError(t, err) + } + + require.Equal(t, uint64(0), remainingGas) + require.Equal(t, test.expectedRes, ret) + } + + if test.assertState != nil { + test.assertState(t, state) + } + }) + } +} diff --git a/precompile/contracts/nativeminter/module.go b/precompile/contracts/nativeminter/module.go new file mode 100644 index 0000000000..76987071cd --- /dev/null +++ b/precompile/contracts/nativeminter/module.go @@ -0,0 +1,57 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package nativeminter + +import ( + "fmt" + "math/big" + + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ethereum/go-ethereum/common" +) + +var _ contract.Configurator = &configurator{} + +// ConfigKey is the key used in json config files to specify this precompile config. +// must be unique across all precompiles. +const ConfigKey = "contractNativeMinterConfig" + +var ContractAddress = common.HexToAddress("0x0200000000000000000000000000000000000001") + +var Module = modules.Module{ + ConfigKey: ConfigKey, + Address: ContractAddress, + Contract: ContractNativeMinterPrecompile, + Configurator: &configurator{}, +} + +type configurator struct{} + +func init() { + if err := modules.RegisterModule(Module); err != nil { + panic(err) + } +} + +func (*configurator) NewConfig() config.Config { + return &Config{} +} + +// Configure configures [state] with the desired admins based on [cfg]. +func (*configurator) Configure(_ contract.ChainConfig, cfg config.Config, state contract.StateDB, _ contract.BlockContext) error { + config, ok := cfg.(*Config) + if !ok { + return fmt.Errorf("incorrect config %T: %v", config, config) + } + for to, amount := range config.InitialMint { + if amount != nil { + bigIntAmount := (*big.Int)(amount) + state.AddBalance(to, bigIntAmount) + } + } + + return config.Config.Configure(state, ContractAddress) +} diff --git a/precompile/contracts/rewardmanager/config.go b/precompile/contracts/rewardmanager/config.go new file mode 100644 index 0000000000..80adc93429 --- /dev/null +++ b/precompile/contracts/rewardmanager/config.go @@ -0,0 +1,118 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Code generated +// This file is a generated precompile contract with stubbed abstract functions. + +package rewardmanager + +import ( + "math/big" + + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ethereum/go-ethereum/common" +) + +var _ config.Config = &Config{} + +type InitialRewardConfig struct { + AllowFeeRecipients bool `json:"allowFeeRecipients"` + RewardAddress common.Address `json:"rewardAddress,omitempty"` +} + +func (i *InitialRewardConfig) Equal(other *InitialRewardConfig) bool { + if other == nil { + return false + } + + return i.AllowFeeRecipients == other.AllowFeeRecipients && i.RewardAddress == other.RewardAddress +} + +func (i *InitialRewardConfig) Verify() error { + switch { + case i.AllowFeeRecipients && i.RewardAddress != (common.Address{}): + return ErrCannotEnableBothRewards + default: + return nil + } +} + +func (i *InitialRewardConfig) Configure(state contract.StateDB) error { + // enable allow fee recipients + if i.AllowFeeRecipients { + EnableAllowFeeRecipients(state) + } else if i.RewardAddress == (common.Address{}) { + // if reward address is empty and allow fee recipients is false + // then disable rewards + DisableFeeRewards(state) + } else { + // set reward address + return StoreRewardAddress(state, i.RewardAddress) + } + return nil +} + +// Config implements the StatefulPrecompileConfig interface while adding in the +// RewardManager specific precompile config. +type Config struct { + allowlist.Config + config.Upgrade + InitialRewardConfig *InitialRewardConfig `json:"initialRewardConfig,omitempty"` +} + +// NewConfig returns a config for a network upgrade at [blockTimestamp] that enables +// RewardManager with the given [admins] and [enableds] as members of the allowlist with [initialConfig] as initial rewards config if specified. +func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialConfig *InitialRewardConfig) *Config { + return &Config{ + Config: allowlist.Config{ + AdminAddresses: admins, + EnabledAddresses: enableds, + }, + Upgrade: config.Upgrade{BlockTimestamp: blockTimestamp}, + InitialRewardConfig: initialConfig, + } +} + +// NewDisableConfig returns config for a network upgrade at [blockTimestamp] +// that disables RewardManager. +func NewDisableConfig(blockTimestamp *big.Int) *Config { + return &Config{ + Upgrade: config.Upgrade{ + BlockTimestamp: blockTimestamp, + Disable: true, + }, + } +} + +func (*Config) Key() string { return ConfigKey } + +func (c *Config) Verify() error { + if c.InitialRewardConfig != nil { + if err := c.InitialRewardConfig.Verify(); err != nil { + return err + } + } + return c.Config.Verify() +} + +// Equal returns true if [cfg] is a [*RewardManagerConfig] and it has been configured identical to [c]. +func (c *Config) Equal(cfg config.Config) bool { + // typecast before comparison + other, ok := (cfg).(*Config) + if !ok { + return false + } + + if c.InitialRewardConfig != nil { + if other.InitialRewardConfig == nil { + return false + } + if !c.InitialRewardConfig.Equal(other.InitialRewardConfig) { + return false + } + } + + return c.Upgrade.Equal(&other.Upgrade) && c.Config.Equal(&other.Config) +} diff --git a/precompile/rewardmanager/config_test.go b/precompile/contracts/rewardmanager/config_test.go similarity index 61% rename from precompile/rewardmanager/config_test.go rename to precompile/contracts/rewardmanager/config_test.go index 7e5fb5c19f..669ec1cb64 100644 --- a/precompile/rewardmanager/config_test.go +++ b/precompile/contracts/rewardmanager/config_test.go @@ -7,7 +7,7 @@ import ( "math/big" "testing" - "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -17,17 +17,17 @@ func TestVerifyRewardManagerConfig(t *testing.T) { enableds := []common.Address{{2}} tests := []struct { name string - config precompile.StatefulPrecompileConfig + config config.Config expectedError string }{ { name: "duplicate enableds in config in reward manager allowlist", - config: NewRewardManagerConfig(big.NewInt(3), admins, append(enableds, enableds[0]), nil), + config: NewConfig(big.NewInt(3), admins, append(enableds, enableds[0]), nil), expectedError: "duplicate address", }, { name: "both reward mechanisms should not be activated at the same time in reward manager", - config: NewRewardManagerConfig(big.NewInt(3), admins, enableds, &InitialRewardConfig{ + config: NewConfig(big.NewInt(3), admins, enableds, &InitialRewardConfig{ AllowFeeRecipients: true, RewardAddress: common.HexToAddress("0x01"), }), @@ -53,48 +53,48 @@ func TestEqualRewardManagerConfig(t *testing.T) { enableds := []common.Address{{2}} tests := []struct { name string - config precompile.StatefulPrecompileConfig - other precompile.StatefulPrecompileConfig + config config.Config + other config.Config expected bool }{ { name: "non-nil config and nil other", - config: NewRewardManagerConfig(big.NewInt(3), admins, enableds, nil), + config: NewConfig(big.NewInt(3), admins, enableds, nil), other: nil, expected: false, }, { name: "different type", - config: NewRewardManagerConfig(big.NewInt(3), admins, enableds, nil), - other: precompile.NewNoopStatefulPrecompileConfig(), + config: NewConfig(big.NewInt(3), admins, enableds, nil), + other: config.NewNoopStatefulPrecompileConfig(), expected: false, }, { name: "different timestamp", - config: NewRewardManagerConfig(big.NewInt(3), admins, nil, nil), - other: NewRewardManagerConfig(big.NewInt(4), admins, nil, nil), + config: NewConfig(big.NewInt(3), admins, nil, nil), + other: NewConfig(big.NewInt(4), admins, nil, nil), expected: false, }, { name: "different enabled", - config: NewRewardManagerConfig(big.NewInt(3), admins, nil, nil), - other: NewRewardManagerConfig(big.NewInt(3), admins, enableds, nil), + config: NewConfig(big.NewInt(3), admins, nil, nil), + other: NewConfig(big.NewInt(3), admins, enableds, nil), expected: false, }, { name: "non-nil initial config and nil initial config", - config: NewRewardManagerConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ + config: NewConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ AllowFeeRecipients: true, }), - other: NewRewardManagerConfig(big.NewInt(3), admins, nil, nil), + other: NewConfig(big.NewInt(3), admins, nil, nil), expected: false, }, { name: "different initial config", - config: NewRewardManagerConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ + config: NewConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ RewardAddress: common.HexToAddress("0x01"), }), - other: NewRewardManagerConfig(big.NewInt(3), admins, nil, + other: NewConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ RewardAddress: common.HexToAddress("0x02"), }), @@ -102,10 +102,10 @@ func TestEqualRewardManagerConfig(t *testing.T) { }, { name: "same config", - config: NewRewardManagerConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ + config: NewConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ RewardAddress: common.HexToAddress("0x01"), }), - other: NewRewardManagerConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ + other: NewConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ RewardAddress: common.HexToAddress("0x01"), }), expected: true, diff --git a/precompile/rewardmanager/contract.abi b/precompile/contracts/rewardmanager/contract.abi similarity index 100% rename from precompile/rewardmanager/contract.abi rename to precompile/contracts/rewardmanager/contract.abi diff --git a/precompile/rewardmanager/contract.go b/precompile/contracts/rewardmanager/contract.go similarity index 67% rename from precompile/rewardmanager/contract.go rename to precompile/contracts/rewardmanager/contract.go index 0c95aee847..8d28e0815d 100644 --- a/precompile/rewardmanager/contract.go +++ b/precompile/contracts/rewardmanager/contract.go @@ -7,27 +7,25 @@ package rewardmanager import ( + _ "embed" "errors" "fmt" - "strings" "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/constants" - "github.com/ava-labs/subnet-evm/precompile" "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/vmerrs" - _ "embed" - "github.com/ethereum/go-ethereum/common" ) const ( - AllowFeeRecipientsGasCost uint64 = (precompile.WriteGasCostPerSlot) + allowlist.ReadAllowListGasCost // write 1 slot + read allow list + AllowFeeRecipientsGasCost uint64 = (contract.WriteGasCostPerSlot) + allowlist.ReadAllowListGasCost // write 1 slot + read allow list AreFeeRecipientsAllowedGasCost uint64 = allowlist.ReadAllowListGasCost CurrentRewardAddressGasCost uint64 = allowlist.ReadAllowListGasCost - DisableRewardsGasCost uint64 = (precompile.WriteGasCostPerSlot) + allowlist.ReadAllowListGasCost // write 1 slot + read allow list - SetRewardAddressGasCost uint64 = (precompile.WriteGasCostPerSlot) + allowlist.ReadAllowListGasCost // write 1 slot + read allow list + DisableRewardsGasCost uint64 = (contract.WriteGasCostPerSlot) + allowlist.ReadAllowListGasCost // write 1 slot + read allow list + SetRewardAddressGasCost uint64 = (contract.WriteGasCostPerSlot) + allowlist.ReadAllowListGasCost // write 1 slot + read allow list ) // Singleton StatefulPrecompiledContract and signatures. @@ -45,34 +43,22 @@ var ( //go:embed contract.abi RewardManagerRawABI string - RewardManagerABI abi.ABI // will be initialized by init function - RewardManagerPrecompile precompile.StatefulPrecompiledContract // will be initialized by init function + RewardManagerABI = contract.ParseABI(RewardManagerRawABI) + RewardManagerPrecompile = createRewardManagerPrecompile() // will be initialized by init function rewardAddressStorageKey = common.Hash{'r', 'a', 's', 'k'} allowFeeRecipientsAddressValue = common.Hash{'a', 'f', 'r', 'a', 'v'} ) -func init() { - parsed, err := abi.JSON(strings.NewReader(RewardManagerRawABI)) - if err != nil { - panic(err) - } - RewardManagerABI = parsed - RewardManagerPrecompile, err = createRewardManagerPrecompile(precompile.RewardManagerAddress) - if err != nil { - panic(err) - } -} - // GetRewardManagerAllowListStatus returns the role of [address] for the RewardManager list. -func GetRewardManagerAllowListStatus(stateDB precompile.StateDB, address common.Address) allowlist.AllowListRole { - return allowlist.GetAllowListStatus(stateDB, precompile.RewardManagerAddress, address) +func GetRewardManagerAllowListStatus(stateDB contract.StateDB, address common.Address) allowlist.Role { + return allowlist.GetAllowListStatus(stateDB, ContractAddress, address) } // SetRewardManagerAllowListStatus sets the permissions of [address] to [role] for the // RewardManager list. Assumes [role] has already been verified as valid. -func SetRewardManagerAllowListStatus(stateDB precompile.StateDB, address common.Address, role allowlist.AllowListRole) { - allowlist.SetAllowListRole(stateDB, precompile.RewardManagerAddress, address, role) +func SetRewardManagerAllowListStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) { + allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) } // PackAllowFeeRecipients packs the function selector (first 4 func signature bytes). @@ -82,17 +68,17 @@ func PackAllowFeeRecipients() ([]byte, error) { } // EnableAllowFeeRecipients enables fee recipients. -func EnableAllowFeeRecipients(stateDB precompile.StateDB) { - stateDB.SetState(precompile.RewardManagerAddress, rewardAddressStorageKey, allowFeeRecipientsAddressValue) +func EnableAllowFeeRecipients(stateDB contract.StateDB) { + stateDB.SetState(ContractAddress, rewardAddressStorageKey, allowFeeRecipientsAddressValue) } // DisableRewardAddress disables rewards and burns them by sending to Blackhole Address. -func DisableFeeRewards(stateDB precompile.StateDB) { - stateDB.SetState(precompile.RewardManagerAddress, rewardAddressStorageKey, constants.BlackholeAddr.Hash()) +func DisableFeeRewards(stateDB contract.StateDB) { + stateDB.SetState(ContractAddress, rewardAddressStorageKey, constants.BlackholeAddr.Hash()) } -func allowFeeRecipients(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = precompile.DeductGas(suppliedGas, AllowFeeRecipientsGasCost); err != nil { +func allowFeeRecipients(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, AllowFeeRecipientsGasCost); err != nil { return nil, 0, err } if readOnly { @@ -104,8 +90,8 @@ func allowFeeRecipients(accessibleState precompile.PrecompileAccessibleState, ca // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. // You can modify/delete this code if you don't want this function to be restricted by the allow list. stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := allowlist.GetAllowListStatus(stateDB, precompile.RewardManagerAddress, caller) + // Verify that the caller is in the allow list and therefore has the right to call this function. + callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) if !callerStatus.IsEnabled() { return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotAllowFeeRecipients, caller) } @@ -131,8 +117,8 @@ func PackAreFeeRecipientsAllowedOutput(isAllowed bool) ([]byte, error) { return RewardManagerABI.PackOutput("areFeeRecipientsAllowed", isAllowed) } -func areFeeRecipientsAllowed(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = precompile.DeductGas(suppliedGas, AreFeeRecipientsAllowedGasCost); err != nil { +func areFeeRecipientsAllowed(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, AreFeeRecipientsAllowedGasCost); err != nil { return nil, 0, err } // no input provided for this function @@ -164,18 +150,18 @@ func PackCurrentRewardAddressOutput(rewardAddress common.Address) ([]byte, error // GetStoredRewardAddress returns the current value of the address stored under rewardAddressStorageKey. // Returns an empty address and true if allow fee recipients is enabled, otherwise returns current reward address and false. -func GetStoredRewardAddress(stateDB precompile.StateDB) (common.Address, bool) { - val := stateDB.GetState(precompile.RewardManagerAddress, rewardAddressStorageKey) +func GetStoredRewardAddress(stateDB contract.StateDB) (common.Address, bool) { + val := stateDB.GetState(ContractAddress, rewardAddressStorageKey) return common.BytesToAddress(val.Bytes()), val == allowFeeRecipientsAddressValue } // StoredRewardAddress stores the given [val] under rewardAddressStorageKey. -func StoreRewardAddress(stateDB precompile.StateDB, val common.Address) error { +func StoreRewardAddress(stateDB contract.StateDB, val common.Address) error { // if input is empty, return an error if val == (common.Address{}) { return ErrEmptyRewardAddress } - stateDB.SetState(precompile.RewardManagerAddress, rewardAddressStorageKey, val.Hash()) + stateDB.SetState(ContractAddress, rewardAddressStorageKey, val.Hash()) return nil } @@ -197,8 +183,8 @@ func UnpackSetRewardAddressInput(input []byte) (common.Address, error) { return unpacked, nil } -func setRewardAddress(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = precompile.DeductGas(suppliedGas, SetRewardAddressGasCost); err != nil { +func setRewardAddress(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, SetRewardAddressGasCost); err != nil { return nil, 0, err } if readOnly { @@ -216,8 +202,8 @@ func setRewardAddress(accessibleState precompile.PrecompileAccessibleState, call // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. // You can modify/delete this code if you don't want this function to be restricted by the allow list. stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := allowlist.GetAllowListStatus(stateDB, precompile.RewardManagerAddress, caller) + // Verify that the caller is in the allow list and therefore has the right to call this function. + callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) if !callerStatus.IsEnabled() { return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotSetRewardAddress, caller) } @@ -233,8 +219,8 @@ func setRewardAddress(accessibleState precompile.PrecompileAccessibleState, call return packedOutput, remainingGas, nil } -func currentRewardAddress(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = precompile.DeductGas(suppliedGas, CurrentRewardAddressGasCost); err != nil { +func currentRewardAddress(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, CurrentRewardAddressGasCost); err != nil { return nil, 0, err } @@ -256,8 +242,8 @@ func PackDisableRewards() ([]byte, error) { return RewardManagerABI.Pack("disableRewards") } -func disableRewards(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = precompile.DeductGas(suppliedGas, DisableRewardsGasCost); err != nil { +func disableRewards(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, DisableRewardsGasCost); err != nil { return nil, 0, err } if readOnly { @@ -269,8 +255,8 @@ func disableRewards(accessibleState precompile.PrecompileAccessibleState, caller // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. // You can modify/delete this code if you don't want this function to be restricted by the allow list. stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := allowlist.GetAllowListStatus(stateDB, precompile.RewardManagerAddress, caller) + // Verify that the caller is in the allow list and therefore has the right to call this function. + callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) if !callerStatus.IsEnabled() { return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotDisableRewards, caller) } @@ -285,10 +271,10 @@ func disableRewards(accessibleState precompile.PrecompileAccessibleState, caller // createRewardManagerPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile. // Access to the getters/setters is controlled by an allow list for [precompileAddr]. -func createRewardManagerPrecompile(precompileAddr common.Address) (precompile.StatefulPrecompiledContract, error) { - var functions []*precompile.StatefulPrecompileFunction - functions = append(functions, allowlist.CreateAllowListFunctions(precompileAddr)...) - abiFunctionMap := map[string]precompile.RunStatefulPrecompileFunc{ +func createRewardManagerPrecompile() contract.StatefulPrecompiledContract { + var functions []*contract.StatefulPrecompileFunction + functions = append(functions, allowlist.CreateAllowListFunctions(ContractAddress)...) + abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{ "allowFeeRecipients": allowFeeRecipients, "areFeeRecipientsAllowed": areFeeRecipientsAllowed, "currentRewardAddress": currentRewardAddress, @@ -299,11 +285,15 @@ func createRewardManagerPrecompile(precompileAddr common.Address) (precompile.St for name, function := range abiFunctionMap { method, ok := RewardManagerABI.Methods[name] if !ok { - return nil, fmt.Errorf("given method (%s) does not exist in the ABI", name) + panic(fmt.Errorf("given method (%s) does not exist in the ABI", name)) } - functions = append(functions, precompile.NewStatefulPrecompileFunction(method.ID, function)) + functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function)) } // Construct the contract with no fallback function. - return precompile.NewStatefulPrecompileContract(nil, functions) + statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions) + if err != nil { + panic(err) + } + return statefulContract } diff --git a/precompile/contracts/rewardmanager/contract_test.go b/precompile/contracts/rewardmanager/contract_test.go new file mode 100644 index 0000000000..e2db6b5d5e --- /dev/null +++ b/precompile/contracts/rewardmanager/contract_test.go @@ -0,0 +1,318 @@ +// (c) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package rewardmanager + +import ( + "math/big" + "testing" + + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/subnet-evm/constants" + "github.com/ava-labs/subnet-evm/core/rawdb" + "github.com/ava-labs/subnet-evm/core/state" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/vmerrs" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +var testBlockNumber = big.NewInt(7) + +type precompileTest struct { + caller common.Address + input func() []byte + suppliedGas uint64 + readOnly bool + + config config.Config + + preCondition func(t *testing.T, state *state.StateDB) + assertState func(t *testing.T, state *state.StateDB) + + expectedRes []byte + expectedErr string +} + +func TestRewardManagerRun(t *testing.T) { + adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") + enabledAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") + noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") + testAddr := common.HexToAddress("0x0123") + + for name, test := range map[string]precompileTest{ + "set allow fee recipients from no role fails": { + caller: noRoleAddr, + input: func() []byte { + input, err := PackAllowFeeRecipients() + require.NoError(t, err) + + return input + }, + suppliedGas: AllowFeeRecipientsGasCost, + readOnly: false, + expectedErr: ErrCannotAllowFeeRecipients.Error(), + }, + "set reward address from no role fails": { + caller: noRoleAddr, + input: func() []byte { + input, err := PackSetRewardAddress(testAddr) + require.NoError(t, err) + + return input + }, + suppliedGas: SetRewardAddressGasCost, + readOnly: false, + expectedErr: ErrCannotSetRewardAddress.Error(), + }, + "disable rewards from no role fails": { + caller: noRoleAddr, + input: func() []byte { + input, err := PackDisableRewards() + require.NoError(t, err) + + return input + }, + suppliedGas: DisableRewardsGasCost, + readOnly: false, + expectedErr: ErrCannotDisableRewards.Error(), + }, + "set allow fee recipients from enabled succeeds": { + caller: enabledAddr, + input: func() []byte { + input, err := PackAllowFeeRecipients() + require.NoError(t, err) + + return input + }, + suppliedGas: AllowFeeRecipientsGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + _, isFeeRecipients := GetStoredRewardAddress(state) + require.True(t, isFeeRecipients) + }, + }, + "set reward address from enabled succeeds": { + caller: enabledAddr, + input: func() []byte { + input, err := PackSetRewardAddress(testAddr) + require.NoError(t, err) + + return input + }, + suppliedGas: SetRewardAddressGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + address, isFeeRecipients := GetStoredRewardAddress(state) + require.Equal(t, testAddr, address) + require.False(t, isFeeRecipients) + }, + }, + "disable rewards from enabled succeeds": { + caller: enabledAddr, + input: func() []byte { + input, err := PackDisableRewards() + require.NoError(t, err) + + return input + }, + suppliedGas: DisableRewardsGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + address, isFeeRecipients := GetStoredRewardAddress(state) + require.False(t, isFeeRecipients) + require.Equal(t, constants.BlackholeAddr, address) + }, + }, + "get current reward address from no role succeeds": { + caller: noRoleAddr, + preCondition: func(t *testing.T, state *state.StateDB) { + StoreRewardAddress(state, testAddr) + }, + input: func() []byte { + input, err := PackCurrentRewardAddress() + require.NoError(t, err) + + return input + }, + suppliedGas: CurrentRewardAddressGasCost, + readOnly: false, + expectedRes: func() []byte { + res, err := PackCurrentRewardAddressOutput(testAddr) + require.NoError(t, err) + return res + }(), + }, + "get are fee recipients allowed from no role succeeds": { + caller: noRoleAddr, + preCondition: func(t *testing.T, state *state.StateDB) { + EnableAllowFeeRecipients(state) + }, + input: func() []byte { + input, err := PackAreFeeRecipientsAllowed() + require.NoError(t, err) + return input + }, + suppliedGas: AreFeeRecipientsAllowedGasCost, + readOnly: false, + expectedRes: func() []byte { + res, err := PackAreFeeRecipientsAllowedOutput(true) + require.NoError(t, err) + return res + }(), + }, + "get initial config with address": { + caller: noRoleAddr, + input: func() []byte { + input, err := PackCurrentRewardAddress() + require.NoError(t, err) + return input + }, + suppliedGas: CurrentRewardAddressGasCost, + config: &Config{ + InitialRewardConfig: &InitialRewardConfig{ + RewardAddress: testAddr, + }, + }, + readOnly: false, + expectedRes: func() []byte { + res, err := PackCurrentRewardAddressOutput(testAddr) + require.NoError(t, err) + return res + }(), + }, + "get initial config with allow fee recipients enabled": { + caller: noRoleAddr, + input: func() []byte { + input, err := PackAreFeeRecipientsAllowed() + require.NoError(t, err) + return input + }, + suppliedGas: AreFeeRecipientsAllowedGasCost, + config: &Config{ + InitialRewardConfig: &InitialRewardConfig{ + AllowFeeRecipients: true, + }, + }, + readOnly: false, + expectedRes: func() []byte { + res, err := PackAreFeeRecipientsAllowedOutput(true) + require.NoError(t, err) + return res + }(), + }, + "readOnly allow fee recipients with allowed role fails": { + caller: enabledAddr, + input: func() []byte { + input, err := PackAllowFeeRecipients() + require.NoError(t, err) + + return input + }, + suppliedGas: AllowFeeRecipientsGasCost, + readOnly: true, + expectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "readOnly set reward addresss with allowed role fails": { + caller: enabledAddr, + input: func() []byte { + input, err := PackSetRewardAddress(testAddr) + require.NoError(t, err) + + return input + }, + suppliedGas: SetRewardAddressGasCost, + readOnly: true, + expectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "insufficient gas set reward address from allowed role": { + caller: enabledAddr, + input: func() []byte { + input, err := PackSetRewardAddress(testAddr) + require.NoError(t, err) + + return input + }, + suppliedGas: SetRewardAddressGasCost - 1, + readOnly: false, + expectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "insufficient gas allow fee recipients from allowed role": { + caller: enabledAddr, + input: func() []byte { + input, err := PackAllowFeeRecipients() + require.NoError(t, err) + + return input + }, + suppliedGas: AllowFeeRecipientsGasCost - 1, + readOnly: false, + expectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "insufficient gas read current reward address from allowed role": { + caller: enabledAddr, + input: func() []byte { + input, err := PackCurrentRewardAddress() + require.NoError(t, err) + + return input + }, + suppliedGas: CurrentRewardAddressGasCost - 1, + readOnly: false, + expectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "insufficient gas are fee recipients allowed from allowed role": { + caller: enabledAddr, + input: func() []byte { + input, err := PackAreFeeRecipientsAllowed() + require.NoError(t, err) + + return input + }, + suppliedGas: AreFeeRecipientsAllowedGasCost - 1, + readOnly: false, + expectedErr: vmerrs.ErrOutOfGas.Error(), + }, + } { + t.Run(name, func(t *testing.T) { + db := rawdb.NewMemoryDatabase() + state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) + require.NoError(t, err) + + // Set up the state so that each address has the expected permissions at the start. + SetRewardManagerAllowListStatus(state, adminAddr, allowlist.AdminRole) + SetRewardManagerAllowListStatus(state, enabledAddr, allowlist.EnabledRole) + require.Equal(t, allowlist.AdminRole, GetRewardManagerAllowListStatus(state, adminAddr)) + require.Equal(t, allowlist.EnabledRole, GetRewardManagerAllowListStatus(state, enabledAddr)) + + if test.preCondition != nil { + test.preCondition(t, state) + } + + blockContext := contract.NewMockBlockContext(testBlockNumber, 0) + accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) + + if test.config != nil { + Module.Configure(nil, test.config, state, blockContext) + } + ret, remainingGas, err := RewardManagerPrecompile.Run(accesibleState, test.caller, ContractAddress, test.input(), test.suppliedGas, test.readOnly) + if len(test.expectedErr) != 0 { + require.ErrorContains(t, err, test.expectedErr) + } else { + require.NoError(t, err) + } + + require.Equal(t, uint64(0), remainingGas) + require.Equal(t, test.expectedRes, ret) + + if test.assertState != nil { + test.assertState(t, state) + } + }) + } +} diff --git a/precompile/contracts/rewardmanager/module.go b/precompile/contracts/rewardmanager/module.go new file mode 100644 index 0000000000..e5f18a7c2b --- /dev/null +++ b/precompile/contracts/rewardmanager/module.go @@ -0,0 +1,61 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package rewardmanager + +import ( + "fmt" + + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ethereum/go-ethereum/common" +) + +var _ contract.Configurator = &configurator{} + +// ConfigKey is the key used in json config files to specify this precompile config. +// must be unique across all precompiles. +const ConfigKey = "rewardManagerConfig" + +var ContractAddress = common.HexToAddress("0x0200000000000000000000000000000000000004") + +var Module = modules.Module{ + ConfigKey: ConfigKey, + Address: ContractAddress, + Contract: RewardManagerPrecompile, + Configurator: &configurator{}, +} + +type configurator struct{} + +func init() { + if err := modules.RegisterModule(Module); err != nil { + panic(err) + } +} + +func (*configurator) NewConfig() config.Config { + return &Config{} +} + +// Configure configures [state] with the initial state for the precompile. +func (*configurator) Configure(chainConfig contract.ChainConfig, cfg config.Config, state contract.StateDB, _ contract.BlockContext) error { + config, ok := cfg.(*Config) + if !ok { + return fmt.Errorf("incorrect config %T: %v", config, config) + } + // configure the RewardManager with the given initial configuration + if config.InitialRewardConfig != nil { + return config.InitialRewardConfig.Configure(state) + } else if chainConfig.AllowedFeeRecipients() { + // configure the RewardManager according to chainConfig + EnableAllowFeeRecipients(state) + } else { + // chainConfig does not have any reward address + // if chainConfig does not enable fee recipients + // default to disabling rewards + DisableFeeRewards(state) + } + return config.Config.Configure(state, ContractAddress) +} diff --git a/precompile/contracts/txallowlist/config.go b/precompile/contracts/txallowlist/config.go new file mode 100644 index 0000000000..93042c46da --- /dev/null +++ b/precompile/contracts/txallowlist/config.go @@ -0,0 +1,56 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txallowlist + +import ( + "math/big" + + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ethereum/go-ethereum/common" +) + +var _ config.Config = &Config{} + +// Config implements the StatefulPrecompileConfig interface while adding in the +// TxAllowList specific precompile config. +type Config struct { + allowlist.Config + config.Upgrade +} + +// NewConfig returns a config for a network upgrade at [blockTimestamp] that enables +// TxAllowList with the given [admins] and [enableds] as members of the allowlist. +func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address) *Config { + return &Config{ + Config: allowlist.Config{ + AdminAddresses: admins, + EnabledAddresses: enableds, + }, + Upgrade: config.Upgrade{BlockTimestamp: blockTimestamp}, + } +} + +// NewDisableConfig returns config for a network upgrade at [blockTimestamp] +// that disables TxAllowList. +func NewDisableConfig(blockTimestamp *big.Int) *Config { + return &Config{ + Upgrade: config.Upgrade{ + BlockTimestamp: blockTimestamp, + Disable: true, + }, + } +} + +func (c *Config) Key() string { return ConfigKey } + +// Equal returns true if [cfg] is a [*TxAllowListConfig] and it has been configured identical to [c]. +func (c *Config) Equal(cfg config.Config) bool { + // typecast before comparison + other, ok := (cfg).(*Config) + if !ok { + return false + } + return c.Upgrade.Equal(&other.Upgrade) && c.Config.Equal(&other.Config) +} diff --git a/precompile/txallowlist/config_test.go b/precompile/contracts/txallowlist/config_test.go similarity index 60% rename from precompile/txallowlist/config_test.go rename to precompile/contracts/txallowlist/config_test.go index 651bfa9ba2..51817d8959 100644 --- a/precompile/txallowlist/config_test.go +++ b/precompile/contracts/txallowlist/config_test.go @@ -7,7 +7,7 @@ import ( "math/big" "testing" - "github.com/ava-labs/subnet-evm/precompile" + "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -17,27 +17,27 @@ func TestVerifyTxAllowlistConfig(t *testing.T) { enableds := []common.Address{{2}} tests := []struct { name string - config precompile.StatefulPrecompileConfig + config config.Config expectedError string }{ { name: "invalid allow list config in tx allowlist", - config: NewTxAllowListConfig(big.NewInt(3), admins, admins), + config: NewConfig(big.NewInt(3), admins, admins), expectedError: "cannot set address", }, { name: "nil member allow list config in tx allowlist", - config: NewTxAllowListConfig(big.NewInt(3), nil, nil), + config: NewConfig(big.NewInt(3), nil, nil), expectedError: "", }, { name: "empty member allow list config in tx allowlist", - config: NewTxAllowListConfig(big.NewInt(3), []common.Address{}, []common.Address{}), + config: NewConfig(big.NewInt(3), []common.Address{}, []common.Address{}), expectedError: "", }, { name: "valid allow list config in tx allowlist", - config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), + config: NewConfig(big.NewInt(3), admins, enableds), expectedError: "", }, } @@ -60,38 +60,38 @@ func TestEqualTxAllowListConfig(t *testing.T) { enableds := []common.Address{{2}} tests := []struct { name string - config precompile.StatefulPrecompileConfig - other precompile.StatefulPrecompileConfig + config config.Config + other config.Config expected bool }{ { name: "non-nil config and nil other", - config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), + config: NewConfig(big.NewInt(3), admins, enableds), other: nil, expected: false, }, { name: "different admin", - config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), - other: NewTxAllowListConfig(big.NewInt(3), []common.Address{{3}}, enableds), + config: NewConfig(big.NewInt(3), admins, enableds), + other: NewConfig(big.NewInt(3), []common.Address{{3}}, enableds), expected: false, }, { name: "different enabled", - config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), - other: NewTxAllowListConfig(big.NewInt(3), admins, []common.Address{{3}}), + config: NewConfig(big.NewInt(3), admins, enableds), + other: NewConfig(big.NewInt(3), admins, []common.Address{{3}}), expected: false, }, { name: "different timestamp", - config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), - other: NewTxAllowListConfig(big.NewInt(4), admins, enableds), + config: NewConfig(big.NewInt(3), admins, enableds), + other: NewConfig(big.NewInt(4), admins, enableds), expected: false, }, { name: "same config", - config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), - other: NewTxAllowListConfig(big.NewInt(3), admins, enableds), + config: NewConfig(big.NewInt(3), admins, enableds), + other: NewConfig(big.NewInt(3), admins, enableds), expected: true, }, } diff --git a/precompile/contracts/txallowlist/contract.go b/precompile/contracts/txallowlist/contract.go new file mode 100644 index 0000000000..e93d53c6a1 --- /dev/null +++ b/precompile/contracts/txallowlist/contract.go @@ -0,0 +1,25 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txallowlist + +import ( + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ethereum/go-ethereum/common" +) + +// Singleton StatefulPrecompiledContract for W/R access to the tx allow list. +var TxAllowListPrecompile contract.StatefulPrecompiledContract = allowlist.CreateAllowListPrecompile(ContractAddress) + +// GetTxAllowListStatus returns the role of [address] for the tx allow list. +func GetTxAllowListStatus(stateDB contract.StateDB, address common.Address) allowlist.Role { + return allowlist.GetAllowListStatus(stateDB, ContractAddress, address) +} + +// SetTxAllowListStatus sets the permissions of [address] to [role] for the +// tx allow list. +// assumes [role] has already been verified as valid. +func SetTxAllowListStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) { + allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) +} diff --git a/precompile/contracts/txallowlist/contract_test.go b/precompile/contracts/txallowlist/contract_test.go new file mode 100644 index 0000000000..4fb3dfab74 --- /dev/null +++ b/precompile/contracts/txallowlist/contract_test.go @@ -0,0 +1,214 @@ +// (c) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txallowlist + +import ( + "testing" + + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/subnet-evm/core/rawdb" + "github.com/ava-labs/subnet-evm/core/state" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/vmerrs" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +type precompileTest struct { + caller common.Address + input func() []byte + suppliedGas uint64 + readOnly bool + + config config.Config + + preCondition func(t *testing.T, state *state.StateDB) + assertState func(t *testing.T, state *state.StateDB) + + expectedRes []byte + expectedErr string +} + +func TestTxAllowListRun(t *testing.T) { + adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") + noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") + + for name, test := range map[string]precompileTest{ + "set admin": { + caller: adminAddr, + input: func() []byte { + input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AdminRole) + require.NoError(t, err) + + return input + }, + suppliedGas: allowlist.ModifyAllowListGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + res := GetTxAllowListStatus(state, noRoleAddr) + require.Equal(t, allowlist.AdminRole, res) + }, + }, + "set allowed": { + caller: adminAddr, + input: func() []byte { + input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.EnabledRole) + require.NoError(t, err) + + return input + }, + suppliedGas: allowlist.ModifyAllowListGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + res := GetTxAllowListStatus(state, noRoleAddr) + require.Equal(t, allowlist.EnabledRole, res) + }, + }, + "set no role": { + caller: adminAddr, + input: func() []byte { + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.NoRole) + require.NoError(t, err) + + return input + }, + suppliedGas: allowlist.ModifyAllowListGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + res := GetTxAllowListStatus(state, adminAddr) + require.Equal(t, allowlist.NoRole, res) + }, + }, + "set no role from non-admin": { + caller: noRoleAddr, + input: func() []byte { + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.NoRole) + require.NoError(t, err) + + return input + }, + suppliedGas: allowlist.ModifyAllowListGasCost, + readOnly: false, + expectedErr: allowlist.ErrCannotModifyAllowList.Error(), + }, + "set allowed from non-admin": { + caller: noRoleAddr, + input: func() []byte { + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) + require.NoError(t, err) + + return input + }, + suppliedGas: allowlist.ModifyAllowListGasCost, + readOnly: false, + expectedErr: allowlist.ErrCannotModifyAllowList.Error(), + }, + "set admin from non-admin": { + caller: noRoleAddr, + input: func() []byte { + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AdminRole) + require.NoError(t, err) + + return input + }, + suppliedGas: allowlist.ModifyAllowListGasCost, + readOnly: false, + expectedErr: allowlist.ErrCannotModifyAllowList.Error(), + }, + "set no role with readOnly enabled": { + caller: adminAddr, + input: func() []byte { + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) + require.NoError(t, err) + + return input + }, + suppliedGas: allowlist.ModifyAllowListGasCost, + readOnly: true, + expectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "set no role insufficient gas": { + caller: adminAddr, + input: func() []byte { + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) + require.NoError(t, err) + + return input + }, + suppliedGas: allowlist.ModifyAllowListGasCost - 1, + readOnly: false, + expectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "read allow list with no role": { + caller: noRoleAddr, + input: func() []byte { + return allowlist.PackReadAllowList(noRoleAddr) + }, + suppliedGas: allowlist.ReadAllowListGasCost, + readOnly: false, + expectedRes: common.Hash(allowlist.NoRole).Bytes(), + assertState: nil, + }, + "read allow list with admin role": { + caller: adminAddr, + input: func() []byte { + return allowlist.PackReadAllowList(noRoleAddr) + }, + suppliedGas: allowlist.ReadAllowListGasCost, + readOnly: false, + expectedRes: common.Hash(allowlist.NoRole).Bytes(), + assertState: nil, + }, + "read allow list with readOnly enabled": { + caller: adminAddr, + input: func() []byte { + return allowlist.PackReadAllowList(noRoleAddr) + }, + suppliedGas: allowlist.ReadAllowListGasCost, + readOnly: true, + expectedRes: common.Hash(allowlist.NoRole).Bytes(), + assertState: nil, + }, + "read allow list out of gas": { + caller: adminAddr, + input: func() []byte { + return allowlist.PackReadAllowList(noRoleAddr) + }, + suppliedGas: allowlist.ReadAllowListGasCost - 1, + readOnly: true, + expectedErr: vmerrs.ErrOutOfGas.Error(), + }, + } { + t.Run(name, func(t *testing.T) { + db := rawdb.NewMemoryDatabase() + state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) + require.NoError(t, err) + + // Set up the state so that each address has the expected permissions at the start. + SetTxAllowListStatus(state, adminAddr, allowlist.AdminRole) + require.Equal(t, allowlist.AdminRole, GetTxAllowListStatus(state, adminAddr)) + + blockContext := contract.NewMockBlockContext(common.Big0, 0) + accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) + ret, remainingGas, err := TxAllowListPrecompile.Run(accesibleState, test.caller, ContractAddress, test.input(), test.suppliedGas, test.readOnly) + if len(test.expectedErr) != 0 { + require.ErrorContains(t, err, test.expectedErr) + } else { + require.NoError(t, err) + } + + require.Equal(t, uint64(0), remainingGas) + require.Equal(t, test.expectedRes, ret) + + if test.assertState != nil { + test.assertState(t, state) + } + }) + } +} diff --git a/precompile/contracts/txallowlist/module.go b/precompile/contracts/txallowlist/module.go new file mode 100644 index 0000000000..0da9721eb6 --- /dev/null +++ b/precompile/contracts/txallowlist/module.go @@ -0,0 +1,49 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package txallowlist + +import ( + "fmt" + + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ethereum/go-ethereum/common" +) + +var _ contract.Configurator = &configurator{} + +// ConfigKey is the key used in json config files to specify this precompile config. +// must be unique across all precompiles. +const ConfigKey = "txAllowListConfig" + +var ContractAddress = common.HexToAddress("0x0200000000000000000000000000000000000002") + +var Module = modules.Module{ + ConfigKey: ConfigKey, + Address: ContractAddress, + Contract: TxAllowListPrecompile, + Configurator: &configurator{}, +} + +type configurator struct{} + +func init() { + if err := modules.RegisterModule(Module); err != nil { + panic(err) + } +} + +func (*configurator) NewConfig() config.Config { + return &Config{} +} + +// Configure configures [state] with the initial state for the precompile. +func (*configurator) Configure(chainConfig contract.ChainConfig, cfg config.Config, state contract.StateDB, _ contract.BlockContext) error { + config, ok := cfg.(*Config) + if !ok { + return fmt.Errorf("incorrect config %T: %v", config, config) + } + return config.Config.Configure(state, ContractAddress) +} diff --git a/precompile/deployerallowlist/config.go b/precompile/deployerallowlist/config.go deleted file mode 100644 index 07b8e27dc2..0000000000 --- a/precompile/deployerallowlist/config.go +++ /dev/null @@ -1,76 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package deployerallowlist - -import ( - "encoding/json" - "math/big" - - "github.com/ava-labs/subnet-evm/precompile" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ethereum/go-ethereum/common" -) - -var _ precompile.StatefulPrecompileConfig = &ContractDeployerAllowListConfig{} - -// ContractDeployerAllowListConfig wraps [AllowListConfig] and uses it to implement the StatefulPrecompileConfig -// interface while adding in the contract deployer specific precompile address. -type ContractDeployerAllowListConfig struct { - allowlist.AllowListConfig - precompile.UpgradeableConfig -} - -// NewContractDeployerAllowListConfig returns a config for a network upgrade at [blockTimestamp] that enables -// ContractDeployerAllowList with [admins] and [enableds] as members of the allowlist. -func NewContractDeployerAllowListConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address) *ContractDeployerAllowListConfig { - return &ContractDeployerAllowListConfig{ - AllowListConfig: allowlist.AllowListConfig{ - AllowListAdmins: admins, - EnabledAddresses: enableds, - }, - UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: blockTimestamp}, - } -} - -// NewDisableContractDeployerAllowListConfig returns config for a network upgrade at [blockTimestamp] -// that disables ContractDeployerAllowList. -func NewDisableContractDeployerAllowListConfig(blockTimestamp *big.Int) *ContractDeployerAllowListConfig { - return &ContractDeployerAllowListConfig{ - UpgradeableConfig: precompile.UpgradeableConfig{ - BlockTimestamp: blockTimestamp, - Disable: true, - }, - } -} - -// Address returns the address of the contract deployer allow list. -func (c *ContractDeployerAllowListConfig) Address() common.Address { - return precompile.ContractDeployerAllowListAddress -} - -// Configure configures [state] with the desired admins based on [c]. -func (c *ContractDeployerAllowListConfig) Configure(_ precompile.ChainConfig, state precompile.StateDB, _ precompile.BlockContext) error { - return c.AllowListConfig.Configure(state, precompile.ContractDeployerAllowListAddress) -} - -// Contract returns the singleton stateful precompiled contract to be used for the allow list. -func (c *ContractDeployerAllowListConfig) Contract() precompile.StatefulPrecompiledContract { - return ContractDeployerAllowListPrecompile -} - -// Equal returns true if [s] is a [*ContractDeployerAllowListConfig] and it has been configured identical to [c]. -func (c *ContractDeployerAllowListConfig) Equal(s precompile.StatefulPrecompileConfig) bool { - // typecast before comparison - other, ok := (s).(*ContractDeployerAllowListConfig) - if !ok { - return false - } - return c.UpgradeableConfig.Equal(&other.UpgradeableConfig) && c.AllowListConfig.Equal(&other.AllowListConfig) -} - -// String returns a string representation of the ContractDeployerAllowListConfig. -func (c *ContractDeployerAllowListConfig) String() string { - bytes, _ := json.Marshal(c) - return string(bytes) -} diff --git a/precompile/deployerallowlist/contract.go b/precompile/deployerallowlist/contract.go deleted file mode 100644 index 4fb4ace7b9..0000000000 --- a/precompile/deployerallowlist/contract.go +++ /dev/null @@ -1,26 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package deployerallowlist - -import ( - "github.com/ava-labs/subnet-evm/precompile" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ethereum/go-ethereum/common" -) - -// Singleton StatefulPrecompiledContract for W/R access to the contract deployer allow list. -var ContractDeployerAllowListPrecompile precompile.StatefulPrecompiledContract = allowlist.CreateAllowListPrecompile(precompile.ContractDeployerAllowListAddress) - -// GetContractDeployerAllowListStatus returns the role of [address] for the contract deployer -// allow list. -func GetContractDeployerAllowListStatus(stateDB precompile.StateDB, address common.Address) allowlist.AllowListRole { - return allowlist.GetAllowListStatus(stateDB, precompile.ContractDeployerAllowListAddress, address) -} - -// SetContractDeployerAllowListStatus sets the permissions of [address] to [role] for the -// contract deployer allow list. -// assumes [role] has already been verified as valid. -func SetContractDeployerAllowListStatus(stateDB precompile.StateDB, address common.Address, role allowlist.AllowListRole) { - allowlist.SetAllowListRole(stateDB, precompile.ContractDeployerAllowListAddress, address, role) -} diff --git a/precompile/feemanager/config.go b/precompile/feemanager/config.go deleted file mode 100644 index b36a411342..0000000000 --- a/precompile/feemanager/config.go +++ /dev/null @@ -1,110 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package feemanager - -import ( - "encoding/json" - "fmt" - "math/big" - - "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/precompile" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ethereum/go-ethereum/common" -) - -// FeeManagerConfig wraps [AllowListConfig] and uses it to implement the StatefulPrecompileConfig -// interface while adding in the FeeManager specific precompile address. -type FeeManagerConfig struct { - allowlist.AllowListConfig - precompile.UpgradeableConfig - InitialFeeConfig *commontype.FeeConfig `json:"initialFeeConfig,omitempty"` // initial fee config to be immediately activated -} - -// NewFeeManagerConfig returns a config for a network upgrade at [blockTimestamp] that enables -// FeeManager with the given [admins] and [enableds] as members of the allowlist with [initialConfig] as initial fee config if specified. -func NewFeeManagerConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialConfig *commontype.FeeConfig) *FeeManagerConfig { - return &FeeManagerConfig{ - AllowListConfig: allowlist.AllowListConfig{ - AllowListAdmins: admins, - EnabledAddresses: enableds, - }, - UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: blockTimestamp}, - InitialFeeConfig: initialConfig, - } -} - -// NewDisableFeeManagerConfig returns config for a network upgrade at [blockTimestamp] -// that disables FeeManager. -func NewDisableFeeManagerConfig(blockTimestamp *big.Int) *FeeManagerConfig { - return &FeeManagerConfig{ - UpgradeableConfig: precompile.UpgradeableConfig{ - BlockTimestamp: blockTimestamp, - Disable: true, - }, - } -} - -// Address returns the address of the FeeManager contract. -func (c *FeeManagerConfig) Address() common.Address { - return precompile.FeeManagerAddress -} - -// Equal returns true if [s] is a [*FeeManagerConfig] and it has been configured identical to [c]. -func (c *FeeManagerConfig) Equal(s precompile.StatefulPrecompileConfig) bool { - // typecast before comparison - other, ok := (s).(*FeeManagerConfig) - if !ok { - return false - } - eq := c.UpgradeableConfig.Equal(&other.UpgradeableConfig) && c.AllowListConfig.Equal(&other.AllowListConfig) - if !eq { - return false - } - - if c.InitialFeeConfig == nil { - return other.InitialFeeConfig == nil - } - - return c.InitialFeeConfig.Equal(other.InitialFeeConfig) -} - -// Configure configures [state] with the desired admins based on [c]. -func (c *FeeManagerConfig) Configure(chainConfig precompile.ChainConfig, state precompile.StateDB, blockContext precompile.BlockContext) error { - // Store the initial fee config into the state when the fee manager activates. - if c.InitialFeeConfig != nil { - if err := StoreFeeConfig(state, *c.InitialFeeConfig, blockContext); err != nil { - // This should not happen since we already checked this config with Verify() - return fmt.Errorf("cannot configure given initial fee config: %w", err) - } - } else { - if err := StoreFeeConfig(state, chainConfig.GetFeeConfig(), blockContext); err != nil { - // This should not happen since we already checked the chain config in the genesis creation. - return fmt.Errorf("cannot configure fee config in chain config: %w", err) - } - } - return c.AllowListConfig.Configure(state, precompile.FeeManagerAddress) -} - -// Contract returns the singleton stateful precompiled contract to be used for the fee manager. -func (c *FeeManagerConfig) Contract() precompile.StatefulPrecompiledContract { - return FeeManagerPrecompile -} - -func (c *FeeManagerConfig) Verify() error { - if err := c.AllowListConfig.Verify(); err != nil { - return err - } - if c.InitialFeeConfig == nil { - return nil - } - - return c.InitialFeeConfig.Verify() -} - -// String returns a string representation of the FeeManagerConfig. -func (c *FeeManagerConfig) String() string { - bytes, _ := json.Marshal(c) - return string(bytes) -} diff --git a/precompile/mock_interface.go b/precompile/mock_interface.go deleted file mode 100644 index 4d6ca13281..0000000000 --- a/precompile/mock_interface.go +++ /dev/null @@ -1,115 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package precompile - -import ( - "math/big" - - "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/subnet-evm/commontype" - "github.com/ethereum/go-ethereum/common" -) - -// TODO: replace with gomock library - -var ( - _ BlockContext = &mockBlockContext{} - _ PrecompileAccessibleState = &mockAccessibleState{} - _ ChainConfig = &mockChainConfig{} - _ StatefulPrecompileConfig = &noopStatefulPrecompileConfig{} -) - -type mockBlockContext struct { - blockNumber *big.Int - timestamp uint64 -} - -func NewMockBlockContext(blockNumber *big.Int, timestamp uint64) *mockBlockContext { - return &mockBlockContext{ - blockNumber: blockNumber, - timestamp: timestamp, - } -} - -func (mb *mockBlockContext) Number() *big.Int { return mb.blockNumber } -func (mb *mockBlockContext) Timestamp() *big.Int { return new(big.Int).SetUint64(mb.timestamp) } - -type mockAccessibleState struct { - state StateDB - blockContext *mockBlockContext - snowContext *snow.Context -} - -func NewMockAccessibleState(state StateDB, blockContext *mockBlockContext, snowContext *snow.Context) *mockAccessibleState { - return &mockAccessibleState{ - state: state, - blockContext: blockContext, - snowContext: snowContext, - } -} - -func (m *mockAccessibleState) GetStateDB() StateDB { return m.state } - -func (m *mockAccessibleState) GetBlockContext() BlockContext { return m.blockContext } - -func (m *mockAccessibleState) GetSnowContext() *snow.Context { return m.snowContext } - -func (m *mockAccessibleState) CallFromPrecompile(caller common.Address, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { - return nil, 0, nil -} - -type mockChainConfig struct { - feeConfig commontype.FeeConfig - allowedFeeRecipients bool -} - -func NewMockChainConfig(feeConfig commontype.FeeConfig, allowedFeeRecipients bool) *mockChainConfig { - return &mockChainConfig{ - feeConfig: feeConfig, - allowedFeeRecipients: allowedFeeRecipients, - } -} - -func (m *mockChainConfig) GetFeeConfig() commontype.FeeConfig { return m.feeConfig } - -func (m *mockChainConfig) AllowedFeeRecipients() bool { return m.allowedFeeRecipients } - -type noopStatefulPrecompileConfig struct { -} - -func NewNoopStatefulPrecompileConfig() *noopStatefulPrecompileConfig { - return &noopStatefulPrecompileConfig{} -} - -func (n *noopStatefulPrecompileConfig) Address() common.Address { - return common.Address{} -} - -func (n *noopStatefulPrecompileConfig) Timestamp() *big.Int { - return new(big.Int) -} - -func (n *noopStatefulPrecompileConfig) IsDisabled() bool { - return false -} - -func (n *noopStatefulPrecompileConfig) Equal(StatefulPrecompileConfig) bool { - return false -} - -func (n *noopStatefulPrecompileConfig) Verify() error { - return nil -} - -func (n *noopStatefulPrecompileConfig) Configure(ChainConfig, StateDB, BlockContext) error { - return nil -} - -func (n *noopStatefulPrecompileConfig) Contract() StatefulPrecompiledContract { - return nil -} - -func (n *noopStatefulPrecompileConfig) String() string { - return "" -} diff --git a/precompile/modules/module.go b/precompile/modules/module.go new file mode 100644 index 0000000000..d0a047c94d --- /dev/null +++ b/precompile/modules/module.go @@ -0,0 +1,37 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package modules + +import ( + "bytes" + + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ethereum/go-ethereum/common" +) + +type Module struct { + // ConfigKey is the key used in json config files to specify this precompile config. + ConfigKey string + // Address returns the address where the stateful precompile is accessible. + Address common.Address + // Contract returns a thread-safe singleton that can be used as the StatefulPrecompiledContract when + // this config is enabled. + Contract contract.StatefulPrecompiledContract + // Configurator is used to configure the stateful precompile when the config is enabled. + contract.Configurator +} + +type moduleArray []Module + +func (u moduleArray) Len() int { + return len(u) +} + +func (u moduleArray) Swap(i, j int) { + u[i], u[j] = u[j], u[i] +} + +func (m moduleArray) Less(i, j int) bool { + return bytes.Compare(m[i].Address.Bytes(), m[j].Address.Bytes()) < 0 +} diff --git a/precompile/modules/registerer.go b/precompile/modules/registerer.go new file mode 100644 index 0000000000..5de73ec657 --- /dev/null +++ b/precompile/modules/registerer.go @@ -0,0 +1,93 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package modules + +import ( + "fmt" + "sort" + + "github.com/ava-labs/subnet-evm/utils" + "github.com/ethereum/go-ethereum/common" +) + +var ( + // registeredModules is a list of Module to preserve order + // for deterministic iteration + registeredModules = make([]Module, 0) + + reservedRanges = []utils.AddressRange{ + { + Start: common.HexToAddress("0x0100000000000000000000000000000000000000"), + End: common.HexToAddress("0x01000000000000000000000000000000000000ff"), + }, + { + Start: common.HexToAddress("0x0200000000000000000000000000000000000000"), + End: common.HexToAddress("0x02000000000000000000000000000000000000ff"), + }, + { + Start: common.HexToAddress("0x0300000000000000000000000000000000000000"), + End: common.HexToAddress("0x03000000000000000000000000000000000000ff"), + }, + } +) + +// ReservedAddress returns true if [addr] is in a reserved range for custom precompiles +func ReservedAddress(addr common.Address) bool { + for _, reservedRange := range reservedRanges { + if reservedRange.Contains(addr) { + return true + } + } + + return false +} + +// RegisterModule registers a stateful precompile module +func RegisterModule(stm Module) error { + address := stm.Address + key := stm.ConfigKey + if !ReservedAddress(address) { + return fmt.Errorf("address %s not in a reserved range", address) + } + + for _, registeredModule := range registeredModules { + if registeredModule.ConfigKey == key { + return fmt.Errorf("name %s already used by a stateful precompile", key) + } + if registeredModule.Address == address { + return fmt.Errorf("address %s already used by a stateful precompile", address) + } + } + // sort by address to ensure deterministic iteration + registeredModules = insertSortedByAddress(registeredModules, stm) + return nil +} + +func GetPrecompileModuleByAddress(address common.Address) (Module, bool) { + for _, stm := range registeredModules { + if stm.Address == address { + return stm, true + } + } + return Module{}, false +} + +func GetPrecompileModule(key string) (Module, bool) { + for _, stm := range registeredModules { + if stm.ConfigKey == key { + return stm, true + } + } + return Module{}, false +} + +func RegisteredModules() []Module { + return registeredModules +} + +func insertSortedByAddress(data []Module, stm Module) []Module { + data = append(data, stm) + sort.Sort(moduleArray(data)) + return data +} diff --git a/precompile/modules/registerer_test.go b/precompile/modules/registerer_test.go new file mode 100644 index 0000000000..b91e0b01ed --- /dev/null +++ b/precompile/modules/registerer_test.go @@ -0,0 +1,44 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package modules + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestRegisterModule(t *testing.T) { + data := make([]Module, 0) + // test that the module is registered in sorted order + module1 := Module{ + Address: common.BigToAddress(big.NewInt(1)), + } + data = insertSortedByAddress(data, module1) + + require.Equal(t, []Module{module1}, data) + + module0 := Module{ + Address: common.BigToAddress(big.NewInt(0)), + } + + data = insertSortedByAddress(data, module0) + require.Equal(t, []Module{module0, module1}, data) + + module3 := Module{ + Address: common.BigToAddress(big.NewInt(3)), + } + + data = insertSortedByAddress(data, module3) + require.Equal(t, []Module{module0, module1, module3}, data) + + module2 := Module{ + Address: common.BigToAddress(big.NewInt(2)), + } + + data = insertSortedByAddress(data, module2) + require.Equal(t, []Module{module0, module1, module2, module3}, data) +} diff --git a/precompile/nativeminter/config.go b/precompile/nativeminter/config.go deleted file mode 100644 index 51194dcc88..0000000000 --- a/precompile/nativeminter/config.go +++ /dev/null @@ -1,126 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package nativeminter - -import ( - "encoding/json" - "fmt" - "math/big" - - "github.com/ava-labs/subnet-evm/precompile" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" -) - -var _ precompile.StatefulPrecompileConfig = &ContractNativeMinterConfig{} - -// ContractNativeMinterConfig wraps [AllowListConfig] and uses it to implement the StatefulPrecompileConfig -// interface while adding in the ContractNativeMinter specific precompile address. -type ContractNativeMinterConfig struct { - allowlist.AllowListConfig - precompile.UpgradeableConfig - InitialMint map[common.Address]*math.HexOrDecimal256 `json:"initialMint,omitempty"` // initial mint config to be immediately minted -} - -// NewContractNativeMinterConfig returns a config for a network upgrade at [blockTimestamp] that enables -// ContractNativeMinter with the given [admins] and [enableds] as members of the allowlist. Also mints balances according to [initialMint] when the upgrade activates. -func NewContractNativeMinterConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialMint map[common.Address]*math.HexOrDecimal256) *ContractNativeMinterConfig { - return &ContractNativeMinterConfig{ - AllowListConfig: allowlist.AllowListConfig{ - AllowListAdmins: admins, - EnabledAddresses: enableds, - }, - UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: blockTimestamp}, - InitialMint: initialMint, - } -} - -// NewDisableContractNativeMinterConfig returns config for a network upgrade at [blockTimestamp] -// that disables ContractNativeMinter. -func NewDisableContractNativeMinterConfig(blockTimestamp *big.Int) *ContractNativeMinterConfig { - return &ContractNativeMinterConfig{ - UpgradeableConfig: precompile.UpgradeableConfig{ - BlockTimestamp: blockTimestamp, - Disable: true, - }, - } -} - -// Address returns the address of the native minter contract. -func (c *ContractNativeMinterConfig) Address() common.Address { - return precompile.ContractNativeMinterAddress -} - -// Configure configures [state] with the desired admins based on [c]. -func (c *ContractNativeMinterConfig) Configure(_ precompile.ChainConfig, state precompile.StateDB, _ precompile.BlockContext) error { - for to, amount := range c.InitialMint { - if amount != nil { - bigIntAmount := (*big.Int)(amount) - state.AddBalance(to, bigIntAmount) - } - } - - return c.AllowListConfig.Configure(state, precompile.ContractNativeMinterAddress) -} - -// Contract returns the singleton stateful precompiled contract to be used for the native minter. -func (c *ContractNativeMinterConfig) Contract() precompile.StatefulPrecompiledContract { - return ContractNativeMinterPrecompile -} - -func (c *ContractNativeMinterConfig) Verify() error { - if err := c.AllowListConfig.Verify(); err != nil { - return err - } - // ensure that all of the initial mint values in the map are non-nil positive values - for addr, amount := range c.InitialMint { - if amount == nil { - return fmt.Errorf("initial mint cannot contain nil amount for address %s", addr) - } - bigIntAmount := (*big.Int)(amount) - if bigIntAmount.Sign() < 1 { - return fmt.Errorf("initial mint cannot contain invalid amount %v for address %s", bigIntAmount, addr) - } - } - return nil -} - -// Equal returns true if [s] is a [*ContractNativeMinterConfig] and it has been configured identical to [c]. -func (c *ContractNativeMinterConfig) Equal(s precompile.StatefulPrecompileConfig) bool { - // typecast before comparison - other, ok := (s).(*ContractNativeMinterConfig) - if !ok { - return false - } - eq := c.UpgradeableConfig.Equal(&other.UpgradeableConfig) && c.AllowListConfig.Equal(&other.AllowListConfig) - if !eq { - return false - } - - if len(c.InitialMint) != len(other.InitialMint) { - return false - } - - for address, amount := range c.InitialMint { - val, ok := other.InitialMint[address] - if !ok { - return false - } - bigIntAmount := (*big.Int)(amount) - bigIntVal := (*big.Int)(val) - if !utils.BigNumEqual(bigIntAmount, bigIntVal) { - return false - } - } - - return true -} - -// String returns a string representation of the ContractNativeMinterConfig. -func (c *ContractNativeMinterConfig) String() string { - bytes, _ := json.Marshal(c) - return string(bytes) -} diff --git a/precompile/params.go b/precompile/params.go deleted file mode 100644 index bfa5e40a4f..0000000000 --- a/precompile/params.go +++ /dev/null @@ -1,82 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package precompile - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/common" -) - -// Gas costs for stateful precompiles -const ( - WriteGasCostPerSlot = 20_000 - ReadGasCostPerSlot = 5_000 -) - -// Designated addresses of stateful precompiles -// Note: it is important that none of these addresses conflict with each other or any other precompiles -// in core/vm/contracts.go. -// The first stateful precompiles were added in coreth to support nativeAssetCall and nativeAssetBalance. New stateful precompiles -// originating in coreth will continue at this prefix, so we reserve this range in subnet-evm so that they can be migrated into -// subnet-evm without issue. -// These start at the address: 0x0100000000000000000000000000000000000000 and will increment by 1. -// Optional precompiles implemented in subnet-evm start at 0x0200000000000000000000000000000000000000 and will increment by 1 -// from here to reduce the risk of conflicts. -// For forks of subnet-evm, users should start at 0x0300000000000000000000000000000000000000 to ensure -// that their own modifications do not conflict with stateful precompiles that may be added to subnet-evm -// in the future. -var ( - ContractDeployerAllowListAddress = common.HexToAddress("0x0200000000000000000000000000000000000000") - ContractNativeMinterAddress = common.HexToAddress("0x0200000000000000000000000000000000000001") - TxAllowListAddress = common.HexToAddress("0x0200000000000000000000000000000000000002") - FeeManagerAddress = common.HexToAddress("0x0200000000000000000000000000000000000003") - RewardManagerAddress = common.HexToAddress("0x0200000000000000000000000000000000000004") - // ADD YOUR PRECOMPILE HERE - // {YourPrecompile}Address = common.HexToAddress("0x03000000000000000000000000000000000000??") - - UsedAddresses = []common.Address{ - ContractDeployerAllowListAddress, - ContractNativeMinterAddress, - TxAllowListAddress, - FeeManagerAddress, - RewardManagerAddress, - // ADD YOUR PRECOMPILE HERE - // YourPrecompileAddress - } - reservedRanges = []AddressRange{ - { - common.HexToAddress("0x0100000000000000000000000000000000000000"), - common.HexToAddress("0x01000000000000000000000000000000000000ff"), - }, - { - common.HexToAddress("0x0200000000000000000000000000000000000000"), - common.HexToAddress("0x02000000000000000000000000000000000000ff"), - }, - { - common.HexToAddress("0x0300000000000000000000000000000000000000"), - common.HexToAddress("0x03000000000000000000000000000000000000ff"), - }, - } -) - -// UsedAddress returns true if [addr] is in a reserved range for custom precompiles -func ReservedAddress(addr common.Address) bool { - for _, reservedRange := range reservedRanges { - if reservedRange.Contains(addr) { - return true - } - } - - return false -} - -func init() { - // Ensure that every address used by a precompile is in a reserved range. - for _, addr := range UsedAddresses { - if !ReservedAddress(addr) { - panic(fmt.Errorf("address %s used for stateful precompile but not specified in any reserved range", addr)) - } - } -} diff --git a/precompile/registry/registry.go b/precompile/registry/registry.go new file mode 100644 index 0000000000..273ebbcde3 --- /dev/null +++ b/precompile/registry/registry.go @@ -0,0 +1,41 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Module to facilitate the registration of precompiles and their configuration. +package registry + +// Force imports of each precompile to ensure each precompile's init function runs and registers itself +// with the registry. +import ( + _ "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" + + _ "github.com/ava-labs/subnet-evm/precompile/contracts/nativeminter" + + _ "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" + + _ "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" + + _ "github.com/ava-labs/subnet-evm/precompile/contracts/rewardmanager" + // ADD YOUR PRECOMPILE HERE + // _ "github.com/ava-labs/subnet-evm/precompile/contracts/yourprecompile" +) + +// This list is kept just for reference. The actual addresses defined in respective packages of precompiles. +// Note: it is important that none of these addresses conflict with each other or any other precompiles +// in core/vm/contracts.go. +// The first stateful precompiles were added in coreth to support nativeAssetCall and nativeAssetBalance. New stateful precompiles +// originating in coreth will continue at this prefix, so we reserve this range in subnet-evm so that they can be migrated into +// subnet-evm without issue. +// These start at the address: 0x0100000000000000000000000000000000000000 and will increment by 1. +// Optional precompiles implemented in subnet-evm start at 0x0200000000000000000000000000000000000000 and will increment by 1 +// from here to reduce the risk of conflicts. +// For forks of subnet-evm, users should start at 0x0300000000000000000000000000000000000000 to ensure +// that their own modifications do not conflict with stateful precompiles that may be added to subnet-evm +// in the future. +// ContractDeployerAllowListAddress = common.HexToAddress("0x0200000000000000000000000000000000000000") +// ContractNativeMinterAddress = common.HexToAddress("0x0200000000000000000000000000000000000001") +// TxAllowListAddress = common.HexToAddress("0x0200000000000000000000000000000000000002") +// FeeManagerAddress = common.HexToAddress("0x0200000000000000000000000000000000000003") +// RewardManagerAddress = common.HexToAddress("0x0200000000000000000000000000000000000004") +// ADD YOUR PRECOMPILE HERE +// {YourPrecompile}Address = common.HexToAddress("0x03000000000000000000000000000000000000??") diff --git a/precompile/rewardmanager/config.go b/precompile/rewardmanager/config.go deleted file mode 100644 index 640198da30..0000000000 --- a/precompile/rewardmanager/config.go +++ /dev/null @@ -1,153 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Code generated -// This file is a generated precompile contract with stubbed abstract functions. - -package rewardmanager - -import ( - "encoding/json" - "math/big" - - "github.com/ava-labs/subnet-evm/precompile" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ethereum/go-ethereum/common" -) - -var _ precompile.StatefulPrecompileConfig = &RewardManagerConfig{} - -type InitialRewardConfig struct { - AllowFeeRecipients bool `json:"allowFeeRecipients"` - RewardAddress common.Address `json:"rewardAddress,omitempty"` -} - -func (i *InitialRewardConfig) Verify() error { - switch { - case i.AllowFeeRecipients && i.RewardAddress != (common.Address{}): - return ErrCannotEnableBothRewards - default: - return nil - } -} - -func (i *InitialRewardConfig) Equal(other *InitialRewardConfig) bool { - if other == nil { - return false - } - - return i.AllowFeeRecipients == other.AllowFeeRecipients && i.RewardAddress == other.RewardAddress -} - -func (i *InitialRewardConfig) Configure(state precompile.StateDB) error { - // enable allow fee recipients - if i.AllowFeeRecipients { - EnableAllowFeeRecipients(state) - } else if i.RewardAddress == (common.Address{}) { - // if reward address is empty and allow fee recipients is false - // then disable rewards - DisableFeeRewards(state) - } else { - // set reward address - return StoreRewardAddress(state, i.RewardAddress) - } - return nil -} - -// RewardManagerConfig implements the StatefulPrecompileConfig -// interface while adding in the RewardManager specific precompile config. -type RewardManagerConfig struct { - allowlist.AllowListConfig - precompile.UpgradeableConfig - InitialRewardConfig *InitialRewardConfig `json:"initialRewardConfig,omitempty"` -} - -// NewRewardManagerConfig returns a config for a network upgrade at [blockTimestamp] that enables -// RewardManager with the given [admins] and [enableds] as members of the allowlist with [initialConfig] as initial rewards config if specified. -func NewRewardManagerConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialConfig *InitialRewardConfig) *RewardManagerConfig { - return &RewardManagerConfig{ - AllowListConfig: allowlist.AllowListConfig{ - AllowListAdmins: admins, - EnabledAddresses: enableds, - }, - UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: blockTimestamp}, - InitialRewardConfig: initialConfig, - } -} - -// NewDisableRewardManagerConfig returns config for a network upgrade at [blockTimestamp] -// that disables RewardManager. -func NewDisableRewardManagerConfig(blockTimestamp *big.Int) *RewardManagerConfig { - return &RewardManagerConfig{ - UpgradeableConfig: precompile.UpgradeableConfig{ - BlockTimestamp: blockTimestamp, - Disable: true, - }, - } -} - -// Equal returns true if [s] is a [*RewardManagerConfig] and it has been configured identical to [c]. -func (c *RewardManagerConfig) Equal(s precompile.StatefulPrecompileConfig) bool { - // typecast before comparison - other, ok := (s).(*RewardManagerConfig) - if !ok { - return false - } - // modify this boolean accordingly with your custom RewardManagerConfig, to check if [other] and the current [c] are equal - // if RewardManagerConfig contains only UpgradeableConfig and precompile.AllowListConfig you can skip modifying it. - equals := c.UpgradeableConfig.Equal(&other.UpgradeableConfig) && c.AllowListConfig.Equal(&other.AllowListConfig) - if !equals { - return false - } - - if c.InitialRewardConfig == nil { - return other.InitialRewardConfig == nil - } - - return c.InitialRewardConfig.Equal(other.InitialRewardConfig) -} - -// Address returns the address of the RewardManager. Addresses reside under the precompile/params.go -// Select a non-conflicting address and set it in the params.go. -func (c *RewardManagerConfig) Address() common.Address { - return precompile.RewardManagerAddress -} - -// Configure configures [state] with the initial configuration. -func (c *RewardManagerConfig) Configure(chainConfig precompile.ChainConfig, state precompile.StateDB, _ precompile.BlockContext) error { - c.AllowListConfig.Configure(state, precompile.RewardManagerAddress) - // configure the RewardManager with the given initial configuration - if c.InitialRewardConfig != nil { - return c.InitialRewardConfig.Configure(state) - } else if chainConfig.AllowedFeeRecipients() { - // configure the RewardManager according to chainConfig - EnableAllowFeeRecipients(state) - } else { - // chainConfig does not have any reward address - // if chainConfig does not enable fee recipients - // default to disabling rewards - DisableFeeRewards(state) - } - return nil -} - -// Contract returns the singleton stateful precompiled contract to be used for RewardManager. -func (c *RewardManagerConfig) Contract() precompile.StatefulPrecompiledContract { - return RewardManagerPrecompile -} - -func (c *RewardManagerConfig) Verify() error { - if err := c.AllowListConfig.Verify(); err != nil { - return err - } - if c.InitialRewardConfig != nil { - return c.InitialRewardConfig.Verify() - } - return nil -} - -// String returns a string representation of the RewardManagerConfig. -func (c *RewardManagerConfig) String() string { - bytes, _ := json.Marshal(c) - return string(bytes) -} diff --git a/precompile/stateful_precompile_config.go b/precompile/stateful_precompile_config.go deleted file mode 100644 index 3a293cbb5f..0000000000 --- a/precompile/stateful_precompile_config.go +++ /dev/null @@ -1,57 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package precompile - -import ( - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/common" -) - -// StatefulPrecompileConfig defines the interface for a stateful precompile to -type StatefulPrecompileConfig interface { - // Address returns the address where the stateful precompile is accessible. - Address() common.Address - // Timestamp returns the timestamp at which this stateful precompile should be enabled. - // 1) 0 indicates that the precompile should be enabled from genesis. - // 2) n indicates that the precompile should be enabled in the first block with timestamp >= [n]. - // 3) nil indicates that the precompile is never enabled. - Timestamp() *big.Int - // IsDisabled returns true if this network upgrade should disable the precompile. - IsDisabled() bool - // Equal returns true if the provided argument configures the same precompile with the same parameters. - Equal(StatefulPrecompileConfig) bool - // Verify is called on startup and an error is treated as fatal. Configure can assume the Config has passed verification. - Verify() error - // Configure is called on the first block where the stateful precompile should be enabled. - // This allows the stateful precompile to configure its own state via [StateDB] and [BlockContext] as necessary. - // This function must be deterministic since it will impact the EVM state. If a change to the - // config causes a change to the state modifications made in Configure, then it cannot be safely - // made to the config after the network upgrade has gone into effect. - // - // Configure is called on the first block where the stateful precompile should be enabled. This - // provides the config the ability to set its initial state and should only modify the state within - // its own address space. - Configure(ChainConfig, StateDB, BlockContext) error - // Contract returns a thread-safe singleton that can be used as the StatefulPrecompiledContract when - // this config is enabled. - Contract() StatefulPrecompiledContract - - fmt.Stringer -} - -// Configure sets the nonce and code to non-empty values then calls Configure on [precompileConfig] to make the necessary -// state update to enable the StatefulPrecompile. -// Assumes that [precompileConfig] is non-nil. -func Configure(chainConfig ChainConfig, blockContext BlockContext, precompileConfig StatefulPrecompileConfig, state StateDB) error { - // Set the nonce of the precompile's address (as is done when a contract is created) to ensure - // that it is marked as non-empty and will not be cleaned up when the statedb is finalized. - state.SetNonce(precompileConfig.Address(), 1) - // Set the code of the precompile's address to a non-zero length byte slice to ensure that the precompile - // can be called from within Solidity contracts. Solidity adds a check before invoking a contract to ensure - // that it does not attempt to invoke a non-existent contract. - state.SetCode(precompileConfig.Address(), []byte{0x1}) - return precompileConfig.Configure(chainConfig, state, blockContext) -} diff --git a/precompile/txallowlist/config.go b/precompile/txallowlist/config.go deleted file mode 100644 index 88435c977b..0000000000 --- a/precompile/txallowlist/config.go +++ /dev/null @@ -1,76 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txallowlist - -import ( - "encoding/json" - "math/big" - - "github.com/ava-labs/subnet-evm/precompile" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ethereum/go-ethereum/common" -) - -var _ precompile.StatefulPrecompileConfig = &TxAllowListConfig{} - -// TxAllowListConfig wraps [AllowListConfig] and uses it to implement the StatefulPrecompileConfig -// interface while adding in the TxAllowList specific precompile address. -type TxAllowListConfig struct { - allowlist.AllowListConfig - precompile.UpgradeableConfig -} - -// NewTxAllowListConfig returns a config for a network upgrade at [blockTimestamp] that enables -// TxAllowList with the given [admins] and [enableds] as members of the allowlist. -func NewTxAllowListConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address) *TxAllowListConfig { - return &TxAllowListConfig{ - AllowListConfig: allowlist.AllowListConfig{ - AllowListAdmins: admins, - EnabledAddresses: enableds, - }, - UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: blockTimestamp}, - } -} - -// NewDisableTxAllowListConfig returns config for a network upgrade at [blockTimestamp] -// that disables TxAllowList. -func NewDisableTxAllowListConfig(blockTimestamp *big.Int) *TxAllowListConfig { - return &TxAllowListConfig{ - UpgradeableConfig: precompile.UpgradeableConfig{ - BlockTimestamp: blockTimestamp, - Disable: true, - }, - } -} - -// Address returns the address of the contract deployer allow list. -func (c *TxAllowListConfig) Address() common.Address { - return precompile.TxAllowListAddress -} - -// Configure configures [state] with the desired admins based on [c]. -func (c *TxAllowListConfig) Configure(_ precompile.ChainConfig, state precompile.StateDB, _ precompile.BlockContext) error { - return c.AllowListConfig.Configure(state, precompile.TxAllowListAddress) -} - -// Contract returns the singleton stateful precompiled contract to be used for the allow list. -func (c *TxAllowListConfig) Contract() precompile.StatefulPrecompiledContract { - return TxAllowListPrecompile -} - -// Equal returns true if [s] is a [*TxAllowListConfig] and it has been configured identical to [c]. -func (c *TxAllowListConfig) Equal(s precompile.StatefulPrecompileConfig) bool { - // typecast before comparison - other, ok := (s).(*TxAllowListConfig) - if !ok { - return false - } - return c.UpgradeableConfig.Equal(&other.UpgradeableConfig) && c.AllowListConfig.Equal(&other.AllowListConfig) -} - -// String returns a string representation of the TxAllowListConfig. -func (c *TxAllowListConfig) String() string { - bytes, _ := json.Marshal(c) - return string(bytes) -} diff --git a/precompile/txallowlist/contract.go b/precompile/txallowlist/contract.go deleted file mode 100644 index 2917e9b9c4..0000000000 --- a/precompile/txallowlist/contract.go +++ /dev/null @@ -1,33 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txallowlist - -import ( - "errors" - - "github.com/ava-labs/subnet-evm/precompile" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ethereum/go-ethereum/common" -) - -var ( - _ precompile.StatefulPrecompileConfig = &TxAllowListConfig{} - - // Singleton StatefulPrecompiledContract for W/R access to the tx allow list. - TxAllowListPrecompile precompile.StatefulPrecompiledContract = allowlist.CreateAllowListPrecompile(precompile.TxAllowListAddress) - - ErrSenderAddressNotAllowListed = errors.New("cannot issue transaction from non-allow listed address") -) - -// GetTxAllowListStatus returns the role of [address] for the allow list. -func GetTxAllowListStatus(stateDB precompile.StateDB, address common.Address) allowlist.AllowListRole { - return allowlist.GetAllowListStatus(stateDB, precompile.TxAllowListAddress, address) -} - -// SetTxAllowListStatus sets the permissions of [address] to [role] for the -// tx allow list. -// assumes [role] has already been verified as valid. -func SetTxAllowListStatus(stateDB precompile.StateDB, address common.Address, role allowlist.AllowListRole) { - allowlist.SetAllowListRole(stateDB, precompile.TxAllowListAddress, address, role) -} diff --git a/tests/e2e/utils/evm_client.go b/tests/e2e/utils/evm_client.go index 6f8c0a3950..e1b9eecd12 100644 --- a/tests/e2e/utils/evm_client.go +++ b/tests/e2e/utils/evm_client.go @@ -15,7 +15,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/ethclient" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/txallowlist" + "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" ) @@ -167,7 +167,7 @@ func (ec *EvmClient) TransferTx( if err := ec.ethClient.SendTransaction(ctx, signedTx); err != nil { log.Printf("failed to send transaction: %v (key address %s)", err, sender) - if strings.Contains(err.Error(), txallowlist.ErrSenderAddressNotAllowListed.Error()) { + if strings.Contains(err.Error(), vmerrs.ErrSenderAddressNotAllowListed.Error()) { return nil, err } diff --git a/precompile/reserved_range.go b/utils/address_range.go similarity index 82% rename from precompile/reserved_range.go rename to utils/address_range.go index 4bbc953b54..ee40d2bcca 100644 --- a/precompile/reserved_range.go +++ b/utils/address_range.go @@ -1,7 +1,7 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// (c) 2021-2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package precompile +package utils import ( "bytes" @@ -16,6 +16,7 @@ type AddressRange struct { } // Contains returns true iff [addr] is contained within the (inclusive) +// range of addresses defined by [a]. func (a *AddressRange) Contains(addr common.Address) bool { addrBytes := addr.Bytes() return bytes.Compare(addrBytes, a.Start[:]) >= 0 && bytes.Compare(addrBytes, a.End[:]) <= 0 diff --git a/vmerrs/vmerrs.go b/vmerrs/vmerrs.go index 5ab30248ff..ea3871852c 100644 --- a/vmerrs/vmerrs.go +++ b/vmerrs/vmerrs.go @@ -32,19 +32,20 @@ import ( // List evm execution errors var ( - ErrOutOfGas = errors.New("out of gas") - ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas") - ErrDepth = errors.New("max call depth exceeded") - ErrInsufficientBalance = errors.New("insufficient balance for transfer") - ErrContractAddressCollision = errors.New("contract address collision") - ErrExecutionReverted = errors.New("execution reverted") - ErrMaxCodeSizeExceeded = errors.New("max code size exceeded") - ErrInvalidJump = errors.New("invalid jump destination") - ErrWriteProtection = errors.New("write protection") - ErrReturnDataOutOfBounds = errors.New("return data out of bounds") - ErrGasUintOverflow = errors.New("gas uint64 overflow") - ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") - ErrNonceUintOverflow = errors.New("nonce uint64 overflow") - ErrAddrProhibited = errors.New("prohibited address cannot be sender or created contract address") - ErrInvalidCoinbase = errors.New("invalid coinbase") + ErrOutOfGas = errors.New("out of gas") + ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas") + ErrDepth = errors.New("max call depth exceeded") + ErrInsufficientBalance = errors.New("insufficient balance for transfer") + ErrContractAddressCollision = errors.New("contract address collision") + ErrExecutionReverted = errors.New("execution reverted") + ErrMaxCodeSizeExceeded = errors.New("max code size exceeded") + ErrInvalidJump = errors.New("invalid jump destination") + ErrWriteProtection = errors.New("write protection") + ErrReturnDataOutOfBounds = errors.New("return data out of bounds") + ErrGasUintOverflow = errors.New("gas uint64 overflow") + ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") + ErrNonceUintOverflow = errors.New("nonce uint64 overflow") + ErrAddrProhibited = errors.New("prohibited address cannot be sender or created contract address") + ErrInvalidCoinbase = errors.New("invalid coinbase") + ErrSenderAddressNotAllowListed = errors.New("cannot issue transaction from non-allow listed address") ) From 67705366b6fc406b33bb12e3720adcbc5b3013e1 Mon Sep 17 00:00:00 2001 From: aaronbuchwald Date: Thu, 16 Feb 2023 06:24:20 -0800 Subject: [PATCH 07/23] Precompile improvements merge (#513) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: aaronbuchwald Co-authored-by: Ceyhun Onur Co-authored-by: Darioush Jalali Co-authored-by: cam-schultz <78878559+cam-schultz@users.noreply.github.com> Co-authored-by: Matthew Lam Co-authored-by: cam-schultz Co-authored-by: omahs <73983677+omahs@users.noreply.github.com> Co-authored-by: Anusha <63559942+anusha-ctrl@users.noreply.github.com> Co-authored-by: Hagen Hübel Co-authored-by: minghinmatthewlam Fix: typos (#428) fix allow list comments (#469) fix CGO flags issue (#489) fix lint job (#499) --- .github/CODEOWNERS | 10 +- .github/CONTRIBUTING.md | 52 +- .github/ISSUE_TEMPLATE/release_checklist.md | 25 + .github/pull_request_template.md | 2 + .github/workflows/lint-tests-release.yml | 28 +- README.md | 642 +----------------- .../abi/bind/precompile_contract_template.go | 6 +- cmd/simulator/.gitignore | 34 + cmd/simulator/README.md | 89 +++ cmd/simulator/go.mod | 63 +- cmd/simulator/go.sum | 519 +------------- cmd/simulator/main.go | 73 +- cmd/simulator/metrics/metrics.go | 10 +- cmd/simulator/worker/worker.go | 13 +- commontype/fee_config.go | 17 + commontype/fee_config_test.go | 16 + compatibility.json | 2 + contract-examples/README.md | 51 +- contract-examples/hardhat.config.ts | 43 +- contract-examples/local_rpc.example.json | 4 - ...ist.ts => contract_deployer_allow_list.ts} | 0 ...iveMinter.ts => contract_native_minter.ts} | 0 .../{ExampleFeeManager.ts => fee_manager.ts} | 0 ...mpleRewardManager.ts => reward_manager.ts} | 0 ...ExampleTxAllowList.ts => tx_allow_list.ts} | 0 core/blockchain.go | 106 ++- core/blockchain_test.go | 139 ++++ core/rawdb/accessors_chain.go | 20 + core/rawdb/chain_iterator.go | 311 +++++++++ core/rawdb/chain_iterator_test.go | 218 ++++++ core/rawdb/database.go | 2 +- core/rawdb/schema.go | 3 + core/state/snapshot/snapshot.go | 1 + eth/backend.go | 1 + eth/ethconfig/config.go | 6 + eth/filters/bench_test.go | 4 +- go.mod | 46 +- go.sum | 120 ++-- params/precompile_config_test.go | 44 +- peer/client.go | 38 +- peer/network.go | 263 +++++-- peer/network_test.go | 191 +++++- peer/stats/stats.go | 10 +- peer/waiting_handler.go | 5 +- plugin/evm/config.go | 80 ++- plugin/evm/config_test.go | 28 + plugin/evm/gossiper.go | 4 +- plugin/evm/message/codec.go | 25 +- plugin/evm/message/cross_chain_handler.go | 73 ++ plugin/evm/message/eth_call_request.go | 33 + plugin/evm/message/handler.go | 27 +- plugin/evm/message/request.go | 10 + plugin/evm/message/signature_request.go | 33 + plugin/evm/message/signature_request_test.go | 60 ++ plugin/evm/message/syncable.go | 8 +- plugin/evm/network_handler.go | 60 ++ plugin/evm/syncervm_client.go | 12 +- plugin/evm/syncervm_test.go | 19 +- plugin/evm/version.go | 2 +- plugin/evm/vm.go | 90 ++- plugin/evm/vm_test.go | 251 ++++++- plugin/evm/vm_upgrade_bytes_test.go | 8 +- plugin/main.go | 7 +- .../contracts/feemanager/config_test.go | 9 +- scripts/build.sh | 8 +- scripts/build_image.sh | 10 +- scripts/build_test.sh | 4 +- scripts/constants.sh | 22 +- scripts/generate_precompile.sh | 21 + scripts/install_avalanchego_release.sh | 51 ++ scripts/lint_allowed_geth_imports.sh | 2 +- scripts/run.sh | 275 +------- scripts/run_ginkgo.sh | 34 + scripts/run_simulator.sh | 39 ++ scripts/versions.sh | 11 +- sync/client/client.go | 4 +- sync/client/mock_network.go | 8 +- sync/handlers/code_request_test.go | 2 +- sync/handlers/handler.go | 41 -- tests/curl.go | 181 ----- tests/curl_test.go | 45 -- tests/e2e/e2e_test.go | 300 -------- tests/e2e/ping/suites.go | 27 - tests/e2e/runner/runner.go | 226 ------ tests/e2e/solidity/suites.go | 119 ---- tests/e2e/utils/describe.go | 18 - tests/e2e/utils/out.go | 24 - tests/e2e/utils/params.go | 142 ---- tests/e2e/utils/parser.go | 80 --- tests/load/genesis/genesis.json | 45 ++ tests/load/load_test.go | 79 +++ .../contract_deployer_allow_list.json} | 0 .../genesis/contract_native_minter.json | 0 .../genesis/fee_manager.json | 0 .../genesis/reward_manager.json | 0 .../genesis/tx_allow_list.json | 0 tests/precompile/precompile_test.go | 54 ++ tests/precompile/solidity/suites.go | 77 +++ tests/{e2e => }/utils/command.go | 19 +- tests/utils/constants.go | 9 + tests/{e2e => }/utils/evm_client.go | 31 +- tests/utils/subnet.go | 110 +++ trie/database.go | 1 + warp/backend.go | 92 +++ warp/backend_test.go | 88 +++ warp/handlers/signature_request.go | 75 ++ warp/handlers/signature_request_test.go | 99 +++ warp/handlers/stats/stats.go | 95 +++ warp/warp_client.go | 45 ++ warp/warp_service.go | 26 + 110 files changed, 3595 insertions(+), 3110 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/release_checklist.md create mode 100644 cmd/simulator/.gitignore create mode 100644 cmd/simulator/README.md delete mode 100644 contract-examples/local_rpc.example.json rename contract-examples/test/{ExampleDeployerList.ts => contract_deployer_allow_list.ts} (100%) rename contract-examples/test/{ERC20NativeMinter.ts => contract_native_minter.ts} (100%) rename contract-examples/test/{ExampleFeeManager.ts => fee_manager.ts} (100%) rename contract-examples/test/{ExampleRewardManager.ts => reward_manager.ts} (100%) rename contract-examples/test/{ExampleTxAllowList.ts => tx_allow_list.ts} (100%) create mode 100644 core/rawdb/chain_iterator.go create mode 100644 core/rawdb/chain_iterator_test.go create mode 100644 plugin/evm/message/cross_chain_handler.go create mode 100644 plugin/evm/message/eth_call_request.go create mode 100644 plugin/evm/message/signature_request.go create mode 100644 plugin/evm/message/signature_request_test.go create mode 100644 plugin/evm/network_handler.go create mode 100755 scripts/generate_precompile.sh create mode 100755 scripts/install_avalanchego_release.sh create mode 100755 scripts/run_ginkgo.sh create mode 100755 scripts/run_simulator.sh delete mode 100644 tests/curl.go delete mode 100644 tests/curl_test.go delete mode 100644 tests/e2e/e2e_test.go delete mode 100644 tests/e2e/ping/suites.go delete mode 100644 tests/e2e/runner/runner.go delete mode 100644 tests/e2e/solidity/suites.go delete mode 100644 tests/e2e/utils/describe.go delete mode 100644 tests/e2e/utils/out.go delete mode 100644 tests/e2e/utils/params.go delete mode 100644 tests/e2e/utils/parser.go create mode 100644 tests/load/genesis/genesis.json create mode 100644 tests/load/load_test.go rename tests/{e2e/genesis/deployer_allow_list.json => precompile/genesis/contract_deployer_allow_list.json} (100%) rename tests/{e2e => precompile}/genesis/contract_native_minter.json (100%) rename tests/{e2e => precompile}/genesis/fee_manager.json (100%) rename tests/{e2e => precompile}/genesis/reward_manager.json (100%) rename tests/{e2e => precompile}/genesis/tx_allow_list.json (100%) create mode 100644 tests/precompile/precompile_test.go create mode 100644 tests/precompile/solidity/suites.go rename tests/{e2e => }/utils/command.go (56%) create mode 100644 tests/utils/constants.go rename tests/{e2e => }/utils/evm_client.go (85%) create mode 100644 tests/utils/subnet.go create mode 100644 warp/backend.go create mode 100644 warp/backend_test.go create mode 100644 warp/handlers/signature_request.go create mode 100644 warp/handlers/signature_request_test.go create mode 100644 warp/handlers/stats/stats.go create mode 100644 warp/warp_client.go create mode 100644 warp/warp_service.go diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 89c50fb963..cefba6cdf5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,12 +6,10 @@ # review whenever someone opens a pull request. -* @ceyonur @darioush @patrick-ogrady @aaronbuchwald +* @ceyonur @darioush @aaronbuchwald -# These owners will be the owner of the contract-examples sub-directory. - -contract-examples/ @dasconnor @anusha-ctrl - -# These owners will be the owner of the scripts sub-directory. +# Specific directories: +peer/ @anusha-ctrl +contract-examples/ @anusha-ctrl scripts/ @anusha-ctrl diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 9d43f6b5e1..d0c9fae378 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,35 +1,45 @@ # Contributing -Thank you for considering to help out with the source code! We welcome -contributions from anyone on the internet, and are grateful for even the +Thank you for considering to help out with the source code! We welcome +contributions from anyone on the internet, and are grateful for even the smallest of fixes! -If you'd like to contribute to coreth, please fork, fix, commit and send a +If you'd like to contribute to subnet-evm, please fork, fix, commit and send a pull request for the maintainers to review and merge into the main code base. If -you wish to submit more complex changes though, please check up with the core -devs first on [Discord](https://chat.avalabs.org) to -ensure those changes are in line with the general philosophy of the project +you wish to submit more complex changes though, please check up with the core +devs first on [Discord](https://chat.avalabs.org) to +ensure those changes are in line with the general philosophy of the project and/or get some early feedback which can make both your efforts much lighter as well as our review and merge procedures quick and simple. ## Coding guidelines -Please make sure your contributions adhere to our coding guidelines: - - * Code must adhere to the official Go -[formatting](https://golang.org/doc/effective_go.html#formatting) guidelines -(i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). - * Code must be documented adhering to the official Go -[commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. - * Pull requests need to be based on and opened against the `master` branch. - * Pull reuqests should include a detailed description - * Commits are required to be signed. See [here](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits) - for information on signing commits. - * Commit messages should be prefixed with the package(s) they modify. - * E.g. "eth, rpc: make trace configs optional" +Please make sure your contributions adhere to our coding and documentation +guidelines: + +- Code must adhere to the official Go + [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines + (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). +- Pull requests need to be based on and opened against the `master` branch. +- Pull reuqests should include a detailed description +- Commits are required to be signed. See [here](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits) + for information on signing commits. +- Commit messages should be prefixed with the package(s) they modify. + - E.g. "eth, rpc: make trace configs optional" + +## Documentation guidelines + +- Code should be well commented, so it is easier to read and maintain. + Any complex sections or invariants should be documented explicitly. +- Code must be documented adhering to the official Go + [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. +- Changes with user facing impact (e.g., addition or modification of flags and + options) should be accompanied by a link to a pull request to the [avalanche-docs](https://github.com/ava-labs/avalanche-docs) + repository. [example](https://github.com/ava-labs/avalanche-docs/pull/1119/files). +- Changes that modify a package significantly or add new features should + either update the existing or include a new `README.md` file in that package. ## Can I have feature X -Before you submit a feature request, please check and make sure that it isn't +Before you submit a feature request, please check and make sure that it isn't possible through some other means. - diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md new file mode 100644 index 0000000000..24a1848c87 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -0,0 +1,25 @@ +--- +name: Release Checklist +about: Create a ticket to track a release +title: '' +labels: release +assignees: '' + +--- + +**Release** +The release version and a description of the planned changes to be included in the release. + +**Issues** +Link the major issues planned to be included in the release. + +**Documentation** +Link the relevant documentation PRs for this release. + +**Checklist** +- [ ] Update version in scripts/versions.sh and plugin/evm/version.go +- [ ] Bump AvalancheGo dependency for RPCChainVM Compatibility +- [ ] Add new entry in compatibility.json for RPCChainVM Compatibility +- [ ] Update AvalancheGo compatibility in README +- [ ] Bump cmd/simulator go mod (if needed) +- [ ] Confirm goreleaser job has successfully generated binaries by checking the releases page diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8200c0597f..119cac2eb9 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -3,3 +3,5 @@ ## How this works ## How this was tested + +## How is this documented diff --git a/.github/workflows/lint-tests-release.yml b/.github/workflows/lint-tests-release.yml index 51bf338e0c..5862ddfc2f 100644 --- a/.github/workflows/lint-tests-release.yml +++ b/.github/workflows/lint-tests-release.yml @@ -11,7 +11,7 @@ on: jobs: lint_test: name: Lint - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - run: ./scripts/lint_allowed_geth_imports.sh @@ -63,29 +63,15 @@ jobs: - name: Yarn install run: yarn working-directory: ./contract-examples - - name: Run e2e tests + - name: Install AvalancheGo Release shell: bash - run: SKIP_NETWORK_RUNNER_START=true SKIP_NETWORK_RUNNER_SHUTDOWN=true ENABLE_SOLIDITY_TESTS=true scripts/run.sh - - simulator_test: - name: Load testing with simulator - runs-on: ubuntu-latest - steps: - - name: Git checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v3 - with: - go-version: 1.18 - - name: Install dependencies with go module + run: BASEDIR=/tmp/e2e-test AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego ./scripts/install_avalanchego_release.sh + - name: Build Subnet-EVM Plugin Binary shell: bash - run: go mod download - - name: Run simulator tests + run: ./scripts/build.sh /tmp/e2e-test/avalanchego/plugins/srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy + - name: Run E2E Tests shell: bash - # skip shutdown so external simulator binary can run against the existing cluster - run: SKIP_NETWORK_RUNNER_SHUTDOWN=true RUN_SIMULATOR=true scripts/run.sh + run: AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego DATA_DIR=/tmp/e2e-test/data ./scripts/run_ginkgo.sh release: # needs: [lint_test, unit_test, e2e_test, simulator_test] diff --git a/README.md b/README.md index c0443cdcf1..e1240d1757 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,10 @@ The Subnet EVM runs in a separate process from the main AvalancheGo process and [v0.4.2] AvalancheGo@v1.9.1 (Protocol Version: 18) [v0.4.3] AvalancheGo@v1.9.2-v1.9.3 (Protocol Version: 19) [v0.4.4] AvalancheGo@v1.9.2-v1.9.3 (Protocol Version: 19) +[v0.4.5] AvalancheGo@v1.9.4 (Protocol Version: 20) +[v0.4.6] AvalancheGo@v1.9.4 (Protocol Version: 20) +[v0.4.7] AvalancheGo@v1.9.5 (Protocol Version: 21) +[v0.4.8] AvalancheGo@v1.9.6-v1.9.7 (Protocol Version: 22) ``` ## API @@ -74,7 +78,7 @@ To support these changes, there have been a number of changes to the SubnetEVM b ### Clone Subnet-evm -First install Go 1.18.1 or later. Follow the instructions [here](https://golang.org/doc/install). You can verify by runing `go version`. +First install Go 1.18.1 or later. Follow the instructions [here](https://golang.org/doc/install). You can verify by running `go version`. Set `$GOPATH` environment variable properly for Go to look for Go Workspaces. Please read [this](https://go.dev/doc/gopath_code) for details. You can verify by running `echo $GOPATH`. @@ -94,637 +98,9 @@ This will clone and checkout to `master` branch. ### Run Local Network -[`scripts/run.sh`](https://github.com/ava-labs/subnet-evm/blob/master/scripts/run.sh) automatically installs `avalanchego`, sets up a local network, -and creates a `subnet-evm` genesis file. The usage of this script is +To run a local network, it is recommended to use the [avalanche-cli](https://github.com/ava-labs/avalanche-cli#avalanche-cli) to set up an instance of Subnet-EVM on an local Avalanche Network. -```bash -DEFAULT_ACCOUNT=[GENESIS_ADDRESS] ./scripts/run.sh [AVALANCHEGO VERSION] -``` - -```bash -# to startup a local cluster (good for development) -cd ${HOME}/go/src/github.com/ava-labs/subnet-evm -git pull - -# to use the latest version "scripts/versions.sh" -./scripts/run.sh - -# to specify the version and default account for genesis -DEFAULT_ACCOUNT=0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC ./scripts/run.sh 1.9.2 -``` - -Note: make sure you check the version compatibility above between AvalancheGo and Subnet-evm and use the proper version of AvalancheGo. - -Note that this ewoq address (`0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC`) is a prefunded address on the local network, see [here](https://docs.avax.network/quickstart/fund-a-local-test-network) for more info. The private key for this address is -`0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027`. - -With this command, `avalanchego`, `avalanche-network-runner` and GoLang packages will be downloaded and installed on a `/tmp` directory. Note: please make sure that your have fast internet connection to download these packages, otherwise, it will take a long time. - -Once the the network is started up, the following info will be printed to the -console: - -```bash -cluster is ready! - -Logs Directory: /var/folders/0h/v4nrbbsn1vvbr5h2wfrh5h500000gn/T/network-runner-root-data2328077371 - -EVM Chain ID: 99999 -Funded Address: 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC -RPC Endpoints: -- http://127.0.0.1:14463/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/rpc -- http://127.0.0.1:23930/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/rpc -- http://127.0.0.1:31984/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/rpc -- http://127.0.0.1:41274/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/rpc -- http://127.0.0.1:57529/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/rpc - -WS Endpoints: -- ws://127.0.0.1:14463/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/ws -- ws://127.0.0.1:23930/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/ws -- ws://127.0.0.1:31984/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/ws -- ws://127.0.0.1:41274/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/ws -- ws://127.0.0.1:57529/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/ws - -MetaMask Quick Start: -Funded Address: 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC -Network Name: Local EVM -RPC URL: http://127.0.0.1:14463/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/rpc -Chain ID: 99999 -Curreny Symbol: LEVM -network-runner RPC server is running on PID 79100... - -use the following command to terminate: - -pkill -P 79100 -kill -2 79100 -pkill -9 -f srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy -``` - -You can then ping the local cluster or add the network to MetaMask: - -```bash -curl --location --request POST 'http://127.0.0.1:14463/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/rpc' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - "jsonrpc": "2.0", - "method": "eth_blockNumber", - "params":[], - "id": 1 -}' -``` - -Response: - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "result": "0x0" -} -``` - -To terminate the cluster, run the following commands: - -```bash -pkill -P 79100 -kill -2 79100 -pkill -9 -f srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy -``` - -### Connect with Metamask - -Please use the value provided by `MetaMask Quick Start` to connect with Metamask. - -```text -MetaMask Quick Start: -Funded Address: 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC -Network Name: Local EVM -RPC URL: http://127.0.0.1:14463/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/rpc -Chain ID: 99999 -Curreny Symbol: LEVM -``` - -You can create a new metamask account by importing the private key `0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027` and start experiencing with this account. - -### Load Simulator - -When building developing your own blockchain using `subnet-evm`, you may want -to analyze how your fee paramterization behaves and/or how many resources your VM -uses under different load patterns. For this reason, we developed `cmd/simulator`. -`cmd/simulator` lets your drive arbitrary load across any number of [endpoints] -with a user-specified `concurrency`, `base-fee`, and `priority-fee`. - -To get started, open the directory `cmd/simulator` and add your network's endpoints to -the file at `.simulator/config.yml` (these will be provided after running -`./scripts/run.sh`. With the example above, the correct endpoints is `http://127.0.0.1:14463/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/rpc` to replace `http://localhost:9650/ext/bc/my-chain/rpc`.): - -```yaml -endpoints: - - http://localhost:9650/ext/bc/my-chain/rpc -base-fee: 25 -priority-fee: 1 -concurrency: 10 -``` +There are two options when using the Avalanche-CLI: -Once your config is specified, you can run the tool by either invoking `go run main.go` under the directory `cmd/simulator` or by installing the tool (`go install -v .`) and running the binary -(`simulator`). - -To make getting started easier, the ewoq key `0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC` -has been pre-added to the simulator key directory and can be added to genesis during local network -creation (`DEFAULT_ACCOUNT=[GENESIS_ADDRESS] ./scripts/run.sh [AVALANCHEGO VERSION]`). -If you do not add this key to genesis, you'll need to manually fund the -`master` account when prompted in the terminal. - -_The private key for the ewoq address (`0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC`) is -`0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027`._ - -If you followed the directions successfully, you should see the following: - -```bash -> go run main.go -go: downloading github.com/ava-labs/subnet-evm v0.1.2 -go: downloading github.com/spf13/viper v1.10.1 -2022/05/11 09:49:22 loaded config (endpoints=[http://127.0.0.1:14463/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/rpc] concurrency=25 base fee=1 priority fee=10) -2022/05/11 09:49:22 loaded worker 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC (balance=100000000000000000000000000 nonce=0) -2022/05/11 09:49:22 0xe8859AF6c05b512dF80A66b81dE89FDAB9fE5C1c requesting funds from master -2022/05/11 09:49:22 0xa2B32bcbA31d4dC7728aD73165cdeea5eCeD5e70 requesting funds from master -2022/05/11 09:49:22 0x837438175627A7A2ABbccf1727c5cA46fA7274b5 requesting funds from master -2022/05/11 09:49:22 0x14c908A82047C6bC66cd9282b4D68f3e003659f8 requesting funds from master -2022/05/11 09:49:22 0xbeE6DF853592d3699ac3292D134F59BEF278B048 requesting funds from master -2022/05/11 09:49:22 0x028Bc164dcC1c10f1Db5a1175c58eA84a7Fd34c9 requesting funds from master -2022/05/11 09:49:22 0x664D97348Bdb73fc3bC4447B4676573dbF6eEE5A requesting funds from master -2022/05/11 09:49:22 0x455aAB371261DC41a048e42Bf147ced4FaDE5fCF requesting funds from master -2022/05/11 09:49:22 0xA9b5C64E057F50730CA4Ba6205d55fa08C03ff75 requesting funds from master -2022/05/11 09:49:22 0x57645A2bdCEb6cFbC95e6a5Cac70F0c05B8d8515 requesting funds from master -2022/05/11 09:49:24 [block created] t: 2022-05-11 09:49:22 -0600 MDT index: 1 base fee: 1 block gas cost: 0 block txs: 1 gas used: 21000 -2022/05/11 09:49:24 [block created] t: 2022-05-11 09:49:24 -0600 MDT index: 2 base fee: 1 block gas cost: 0 block txs: 1 gas used: 21000 -2022/05/11 09:49:24 [stats] historical TPS: 1.00 last 10s TPS: 0.10 total txs: 2 historical GPS: 21000.0, last 10s GPS: 2100.0 elapsed: 2s -2022/05/11 09:49:26 [block created] t: 2022-05-11 09:49:26 -0600 MDT index: 3 base fee: 1 block gas cost: 0 block txs: 1 gas used: 21000 -2022/05/11 09:49:26 [stats] historical TPS: 0.75 last 10s TPS: 0.20 total txs: 3 historical GPS: 15750.0, last 10s GPS: 4200.0 elapsed: 4s -2022/05/11 09:49:28 [block created] t: 2022-05-11 09:49:28 -0600 MDT index: 4 base fee: 1 block gas cost: 0 block txs: 2 gas used: 42000 -2022/05/11 09:49:28 [stats] historical TPS: 0.83 last 10s TPS: 0.30 total txs: 5 historical GPS: 17500.0, last 10s GPS: 6300.0 elapsed: 6s -2022/05/11 09:49:30 [block created] t: 2022-05-11 09:49:30 -0600 MDT index: 5 base fee: 1 block gas cost: 0 block txs: 4 gas used: 84000 -2022/05/11 09:49:30 [stats] historical TPS: 1.12 last 10s TPS: 0.50 total txs: 9 historical GPS: 23625.0, last 10s GPS: 10500.0 elapsed: 8s -2022/05/11 09:49:32 [block created] t: 2022-05-11 09:49:32 -0600 MDT index: 6 base fee: 1 block gas cost: 0 block txs: 5 gas used: 105000 -2022/05/11 09:49:32 [stats] historical TPS: 1.40 last 10s TPS: 0.90 total txs: 14 historical GPS: 29400.0, last 10s GPS: 18900.0 elapsed: 10s -2022/05/11 09:49:34 [block created] t: 2022-05-11 09:49:34 -0600 MDT index: 7 base fee: 1 block gas cost: 0 block txs: 6 gas used: 126000 -2022/05/11 09:49:34 [stats] historical TPS: 1.67 last 10s TPS: 1.30 total txs: 20 historical GPS: 35000.0, last 10s GPS: 27300.0 elapsed: 12s -2022/05/11 09:49:36 [block created] t: 2022-05-11 09:49:36 -0600 MDT index: 8 base fee: 1 block gas cost: 0 block txs: 7 gas used: 147000 -2022/05/11 09:49:36 [stats] historical TPS: 1.93 last 10s TPS: 1.80 total txs: 27 historical GPS: 40500.0, last 10s GPS: 37800.0 elapsed: 14s -2022/05/11 09:49:38 [block created] t: 2022-05-11 09:49:38 -0600 MDT index: 9 base fee: 1 block gas cost: 0 block txs: 8 gas used: 168000 -2022/05/11 09:49:38 [stats] historical TPS: 2.19 last 10s TPS: 2.40 total txs: 35 historical GPS: 45937.5, last 10s GPS: 50400.0 elapsed: 16s -2022/05/11 09:49:40 [block created] t: 2022-05-11 09:49:40 -0600 MDT index: 10 base fee: 1 block gas cost: 0 block txs: 9 gas used: 189000 -2022/05/11 09:49:40 [stats] historical TPS: 2.44 last 10s TPS: 3.00 total txs: 44 historical GPS: 51333.3, last 10s GPS: 63000.0 elapsed: 18s -2022/05/11 09:49:42 [block created] t: 2022-05-11 09:49:42 -0600 MDT index: 11 base fee: 1 block gas cost: 0 block txs: 9 gas used: 189000 -2022/05/11 09:49:42 [stats] historical TPS: 2.65 last 10s TPS: 3.50 total txs: 53 historical GPS: 55650.0, last 10s GPS: 73500.0 elapsed: 20s -2022/05/11 09:49:44 [block created] t: 2022-05-11 09:49:44 -0600 MDT index: 12 base fee: 1 block gas cost: 0 block txs: 10 gas used: 210000 -2022/05/11 09:49:44 [stats] historical TPS: 2.86 last 10s TPS: 3.90 total txs: 63 historical GPS: 60136.4, last 10s GPS: 81900.0 elapsed: 22s -2022/05/11 09:49:46 [block created] t: 2022-05-11 09:49:46 -0600 MDT index: 13 base fee: 1 block gas cost: 0 block txs: 10 gas used: 210000 -2022/05/11 09:49:46 [stats] historical TPS: 3.04 last 10s TPS: 4.30 total txs: 73 historical GPS: 63875.0, last 10s GPS: 90300.0 elapsed: 24s -..... - -2022/05/11 09:55:51 [stats] historical TPS: 4.89 last 10s TPS: 5.00 total txs: 1896 historical GPS: 102618.6, last 10s GPS: 105000.0 elapsed: 6m28s -2022/05/11 09:55:52 0xa2B32bcbA31d4dC7728aD73165cdeea5eCeD5e70 requesting funds from master -2022/05/11 09:55:53 [block created] t: 2022-05-11 09:55:52 -0600 MDT index: 196 base fee: 1 block gas cost: 0 block txs: 11 gas used: 231000 -2022/05/11 09:55:53 [stats] historical TPS: 4.89 last 10s TPS: 5.10 total txs: 1907 historical GPS: 102684.6, last 10s GPS: 107100.0 elapsed: 6m30s -2022/05/11 09:55:54 0x14c908A82047C6bC66cd9282b4D68f3e003659f8 requesting funds from master -2022/05/11 09:55:55 [block created] t: 2022-05-11 09:55:54 -0600 MDT index: 197 base fee: 1 block gas cost: 0 block txs: 11 gas used: 231000 -2022/05/11 09:55:55 [stats] historical TPS: 4.89 last 10s TPS: 5.20 total txs: 1918 historical GPS: 102750.0, last 10s GPS: 109200.0 elapsed: 6m32s -2022/05/11 09:55:56 0xbeE6DF853592d3699ac3292D134F59BEF278B048 requesting funds from master -2022/05/11 09:55:57 [block created] t: 2022-05-11 09:55:56 -0600 MDT index: 198 base fee: 1 block gas cost: 0 block txs: 11 gas used: 231000 -2022/05/11 09:55:57 [stats] historical TPS: 4.90 last 10s TPS: 5.30 total txs: 1929 historical GPS: 102814.7, last 10s GPS: 111300.0 elapsed: 6m34s -2022/05/11 09:55:58 0x028Bc164dcC1c10f1Db5a1175c58eA84a7Fd34c9 requesting funds from master -2022/05/11 09:55:59 [block created] t: 2022-05-11 09:55:58 -0600 MDT index: 199 base fee: 1 block gas cost: 0 block txs: 11 gas used: 231000 -2022/05/11 09:55:59 [stats] historical TPS: 4.90 last 10s TPS: 5.40 total txs: 1940 historical GPS: 102878.8, last 10s GPS: 113400.0 elapsed: 6m36s -2022/05/11 09:56:00 0x664D97348Bdb73fc3bC4447B4676573dbF6eEE5A requesting funds from master -2022/05/11 09:56:01 [block created] t: 2022-05-11 09:56:00 -0600 MDT index: 200 base fee: 1 block gas cost: 0 block txs: 11 gas used: 231000 -2022/05/11 09:56:01 [stats] historical TPS: 4.90 last 10s TPS: 5.50 total txs: 1951 historical GPS: 102942.2, last 10s GPS: 115500.0 elapsed: 6m38s -2022/05/11 09:56:02 0x455aAB371261DC41a048e42Bf147ced4FaDE5fCF requesting funds from master -2022/05/11 09:56:03 [block created] t: 2022-05-11 09:56:02 -0600 MDT index: 201 base fee: 1 block gas cost: 0 block txs: 11 gas used: 231000 -2022/05/11 09:56:03 [stats] historical TPS: 4.91 last 10s TPS: 5.50 total txs: 1962 historical GPS: 103005.0, last 10s GPS: 115500.0 elapsed: 6m40s -2022/05/11 09:56:04 0xA9b5C64E057F50730CA4Ba6205d55fa08C03ff75 requesting funds from master -2022/05/11 09:56:05 [block created] t: 2022-05-11 09:56:04 -0600 MDT index: 202 base fee: 1 block gas cost: 0 block txs: 11 gas used: 231000 -2022/05/11 09:56:05 [stats] historical TPS: 4.91 last 10s TPS: 5.50 total txs: 1973 historical GPS: 103067.2, last 10s GPS: 115500.0 elapsed: 6m42s -2022/05/11 09:56:06 0x57645A2bdCEb6cFbC95e6a5Cac70F0c05B8d8515 requesting funds from master -2022/05/11 09:56:07 [block created] t: 2022-05-11 09:56:06 -0600 MDT index: 203 base fee: 1 block gas cost: 0 block txs: 11 gas used: 231000 -2022/05/11 09:56:07 [stats] historical TPS: 4.91 last 10s TPS: 5.50 total txs: 1984 historical GPS: 103128.7, last 10s GPS: 115500.0 elapsed: 6m44s -2022/05/11 09:56:09 [block created] t: 2022-05-11 09:56:08 -0600 MDT index: 204 base fee: 1 block gas cost: 0 block txs: 11 gas used: 231000 -2022/05/11 09:56:09 [stats] historical TPS: 4.91 last 10s TPS: 5.50 total txs: 1995 historical GPS: 103189.7, last 10s GPS: 115500.0 elapsed: 6m46s -2022/05/11 09:56:11 [block created] t: 2022-05-11 09:56:10 -0600 MDT index: 205 base fee: 1 block gas cost: 0 block txs: 10 gas used: 210000 -2022/05/11 09:56:11 [stats] historical TPS: 4.91 last 10s TPS: 5.50 total txs: 2005 historical GPS: 103198.5, last 10s GPS: 115500.0 elapsed: 6m48s -2022/05/11 09:56:13 [block created] t: 2022-05-11 09:56:12 -0600 MDT index: 206 base fee: 1 block gas cost: 0 block txs: 10 gas used: 210000 -2022/05/11 09:56:13 [stats] historical TPS: 4.91 last 10s TPS: 5.40 total txs: 2015 historical GPS: 103207.3, last 10s GPS: 113400.0 elapsed: 6m50s -``` - -## Create an EVM Subnet on Fuji Testnet - -See [this tutorial](https://docs.avax.network/subnets/create-a-fuji-subnet-subnet-cli). - -## Customize a Subnet - -- [Genesis](https://docs.avax.network/subnets/customize-a-subnet#genesis) -- [Precompile](https://docs.avax.network/subnets/customize-a-subnet#precompiles) -- [Priority Regossip](https://docs.avax.network/subnets/customize-a-subnet#priority-regossip) - -## Join the WAGMI Subnet Demo - -

- WAGMI -

- -_Thanks to the @0xNeonMonsters for the logo!_ - -The WAGMI ("We're All Going to Make It") Subnet Demo is a high throughput -testbed for EVM (Ethereum Virtual Machine) optimizations. It is parameterized -to run at a factor more capacity than Fuji/Mainnet C-Chain and will be used -to experiment with release candidates before they make it into an -official [`coreth`](https://github.com/ava-labs/coreth) release. - -We created a basic [WAGMI explorer](https://trywagmi.xyz) that surfaces -aggregated usage statistics about the subnet. If you'd like to see any other -stats added to this site, please send a DM to [@\_patrickogrady on Twitter](https://twitter.com/_patrickogrady). - -Everyone that has used the the C-Chain more than twice (~970k addresses) has -been airdropped 10 WGM tokens. With the current fee parameterization, this -should be enough for hundreds of txs. - -This is one of the first cases of using Avalanche Subnets as a proving ground -for changes in a production VM (coreth). Many underestimate how useful the isolation -of subnets is for performing complex VM testing on a live network (without impacting -the stability of the primary network). - -### Network Creation - -To create WAGMI, all we had to do was run the following command: - -```bash -subnet-cli wizard \ ---node-ids=NodeID-9TCq8np31pHjjhGaHtLjs6ptYYPEt3LGb,NodeID-BrYXghQSu6KKGjuzhs3nrkcB46Wc2yYHy,NodeID-89UCR1CsPzzEHuknxhJHKxuFPNCyPz7Bu,NodeID-Hfm8gpD4DpCz4KTzt2osJPfFvu7az3qiD,NodeID-LkdxkfYhg6nSw1EEUxDUSYPXPwmr2cUet \ ---vm-genesis-path=networks/11111/genesis.json \ ---vm-id=srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy \ ---chain-name=wagmi -``` - -This added these NodeIDs as validators on Fuji, created the WAGMI Subnet, added -all validators to the WAGMI subnet, and created the WAGMI chain. - -- SubnetID: [28nrH5T2BMvNrWecFcV3mfccjs6axM1TVyqe79MCv2Mhs8kxiY](https://testnet.avascan.info/blockchains?subnet=28nrH5T2BMvNrWecFcV3mfccjs6axM1TVyqe79MCv2Mhs8kxiY) -- ChainID: [2ebCneCbwthjQ1rYT41nhd7M76Hc6YmosMAQrTFhBq8qeqh6tt](https://testnet.avascan.info/blockchain/2ebCneCbwthjQ1rYT41nhd7M76Hc6YmosMAQrTFhBq8qeqh6tt) - -### Network Parameters - -```text -Network ID: 11111 -Chain ID: 11111 -Block Gas Limit: 20,000,000 (2.5x C-Chain) -10s Gas Target: 100,000,000 (~6.67x C-Chain) -Min Fee: 1 GWei (4% of C-Chain) -Target Block Rate: 2s (Same as C-Chain) -``` - -### Adding to MetaMask - -```text -Network Name: WAGMI -RPC URL: https://subnets.avax.network/wagmi/wagmi-chain-testnet/rpc -Chain ID: 11111 -Symbol: WGM -Explorer: https://subnets.avax.network/wagmi/wagmi-chain-testnet/explorer -``` - -![metamask_WAGMI](./imgs/metamask_WAGMI.png) - -### Wrapped WAGMI - -#### Info - -```text -Address: 0x3Ee7094DADda15810F191DD6AcF7E4FFa37571e4 -IPFS: /ipfs/QmVAuheeidjD2ktdX3sSHMQqSfcjtmca1g9jr7w9GQf7pU -``` - -#### Metadata - -```json -{ - "compiler": { "version": "0.5.17+commit.d19bba13" }, - "language": "Solidity", - "output": { - "abi": [ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "src", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "guy", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "wad", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "dst", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "wad", - "type": "uint256" - } - ], - "name": "Deposit", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "src", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "dst", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "wad", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "src", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "wad", - "type": "uint256" - } - ], - "name": "Withdrawal", - "type": "event" - }, - { "payable": true, "stateMutability": "payable", "type": "fallback" }, - { - "constant": true, - "inputs": [ - { "internalType": "address", "name": "", "type": "address" }, - { "internalType": "address", "name": "", "type": "address" } - ], - "name": "allowance", - "outputs": [ - { "internalType": "uint256", "name": "", "type": "uint256" } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "internalType": "address", "name": "guy", "type": "address" }, - { "internalType": "uint256", "name": "wad", "type": "uint256" } - ], - "name": "approve", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { "internalType": "address", "name": "", "type": "address" } - ], - "name": "balanceOf", - "outputs": [ - { "internalType": "uint256", "name": "", "type": "uint256" } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "deposit", - "outputs": [], - "payable": true, - "stateMutability": "payable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [{ "internalType": "string", "name": "", "type": "string" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { "internalType": "uint256", "name": "", "type": "uint256" } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "internalType": "address", "name": "dst", "type": "address" }, - { "internalType": "uint256", "name": "wad", "type": "uint256" } - ], - "name": "transfer", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "internalType": "address", "name": "src", "type": "address" }, - { "internalType": "address", "name": "dst", "type": "address" }, - { "internalType": "uint256", "name": "wad", "type": "uint256" } - ], - "name": "transferFrom", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "internalType": "uint256", "name": "wad", "type": "uint256" } - ], - "name": "withdraw", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - } - ], - "devdoc": { "methods": {} }, - "userdoc": { "methods": {} } - }, - "settings": { - "compilationTarget": { "contracts/wwagmi.sol": "WWAGMI" }, - "evmVersion": "istanbul", - "libraries": {}, - "optimizer": { "enabled": false, "runs": 200 }, - "remappings": [] - }, - "sources": { - "contracts/wwagmi.sol": { - "keccak256": "0x0a6ce5559225d3c99db4a5e24777049df3c84886ba9a08147f23afae4261b509", - "urls": [ - "bzz-raw://0aef254c65ae30b578256a7e2496ed18bf0cb68e97f5831050e17a2cf0192a7e", - "dweb:/ipfs/QmSwAbdnaYvrjDHTKnE3qBZ3smT7uipSSfSGBUiKWmNWEY" - ] - } - }, - "version": 1 -} -``` - -#### Code - -```solidity -// Copyright (C) 2015, 2016, 2017 Dapphub - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program 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 General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -// Contract name, token name, and token symbol modified by Ava Labs 2020 - -pragma solidity >=0.4.22 <0.6; - -contract WWAGMI{ - string public name = "Wrapped WAGMI"; - string public symbol = "WWAGMI"; - uint8 public decimals = 18; - - event Approval(address indexed src, address indexed guy, uint wad); - event Transfer(address indexed src, address indexed dst, uint wad); - event Deposit(address indexed dst, uint wad); - event Withdrawal(address indexed src, uint wad); - - mapping (address => uint) public balanceOf; - mapping (address => mapping (address => uint)) public allowance; - - function() external payable { - deposit(); - } - function deposit() public payable { - balanceOf[msg.sender] += msg.value; - emit Deposit(msg.sender, msg.value); - } - function withdraw(uint wad) public { - require(balanceOf[msg.sender] >= wad); - balanceOf[msg.sender] -= wad; - msg.sender.transfer(wad); - emit Withdrawal(msg.sender, wad); - } - - function totalSupply() public view returns (uint) { - return address(this).balance; - } - - function approve(address guy, uint wad) public returns (bool) { - allowance[msg.sender][guy] = wad; - emit Approval(msg.sender, guy, wad); - return true; - } - - function transfer(address dst, uint wad) public returns (bool) { - return transferFrom(msg.sender, dst, wad); - } - - function transferFrom(address src, address dst, uint wad) - public - returns (bool) - { - require(balanceOf[src] >= wad); - - if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) { - require(allowance[src][msg.sender] >= wad); - allowance[src][msg.sender] -= wad; - } - - balanceOf[src] -= wad; - balanceOf[dst] += wad; - - emit Transfer(src, dst, wad); - - return true; - } -} -``` +1. Use an official Subnet-EVM release: https://docs.avax.network/subnets/build-first-subnet +2. Build and deploy a locally built (and optionally modified) version of Subnet-EVM: https://docs.avax.network/subnets/create-custom-subnet diff --git a/accounts/abi/bind/precompile_contract_template.go b/accounts/abi/bind/precompile_contract_template.go index 47c3a04188..709393da6d 100644 --- a/accounts/abi/bind/precompile_contract_template.go +++ b/accounts/abi/bind/precompile_contract_template.go @@ -41,16 +41,16 @@ Typically, custom codes are required in only those areas. 8- Write solidity tests for your precompile in contract-examples/test 9- Create your genesis with your precompile enabled in tests/e2e/genesis/ 10- Create e2e test for your solidity test in tests/e2e/solidity/suites.go -11- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' - +11- Run your e2e precompile Solidity tests with './scripts/run_ginkgo.sh' */ package {{.Package}} import ( - "math/big" + "encoding/json" "errors" "fmt" + "math/big" "strings" "github.com/ava-labs/subnet-evm/accounts/abi" diff --git a/cmd/simulator/.gitignore b/cmd/simulator/.gitignore new file mode 100644 index 0000000000..c4408cd691 --- /dev/null +++ b/cmd/simulator/.gitignore @@ -0,0 +1,34 @@ +simulator + +*.log +*~ +.DS_Store + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.profile + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# ignore GoLand metafiles directory +.idea/ + +*logs/ + +.vscode* + +.coverage + +bin/ +build/ + +# goreleaser +dist/ diff --git a/cmd/simulator/README.md b/cmd/simulator/README.md new file mode 100644 index 0000000000..64a619dca0 --- /dev/null +++ b/cmd/simulator/README.md @@ -0,0 +1,89 @@ +# Load Simulator + +When building developing your own blockchain using `subnet-evm`, you may want to analyze how your fee parameterization behaves and/or how many resources your VM uses under different load patterns. For this reason, we developed `cmd/simulator`. `cmd/simulator` lets you drive arbitrary load across any number of [endpoints] with a user-specified `keys` directory (insecure) `timeout`, `concurrency`, `base-fee`, and `priority-fee`. + +## Building the Load Simulator + +To build the load simulator, navigate to the base of the simulator directory: + +```bash +cd $GOPATH/src/github.com/ava-labs/subnet-evm/cmd/simulator +``` + +Build the simulator: + +```bash +go build -o ./simulator *.go +``` + +To confirm that you built successfully, run the simulator and print the version: + +```bash +./simulator -v +``` + +This should give the following output: + +``` +v0.0.1 +``` + +To run the load simulator, you must first start an EVM based network. The load simulator works on both the C-Chain and Subnet-EVM, so we will start a single node network and run the load simulator on the C-Chain. + +To start a single node network, follow the instructions from the AvalancheGo [README](https://github.com/ava-labs/avalanchego#building-avalanchego) to build from source. + +Once you've built AvalancheGo, open the AvalancheGo directory in a separate terminal window and run a single node non-staking network with the following command: + +```bash +./build/avalanchego --staking-enabled=false --network-id=local +``` + +:::warning +The staking-enabled flag is only for local testing. Disabling staking serves two functions explicitly for testing purposes: + +1. Ignore stake weight on the P-Chain and count each connected peer as having a stake weight of 1 +2. Automatically opts in to validate every Subnet +::: + +Once you have AvalancheGo running locally, it will be running an HTTP Server on the default port `9650`. This means that the RPC Endpoint for the C-Chain will be http://127.0.0.1:9650/ext/bc/C/rpc. + +Now, we can run the simulator command to simulate some load on the local C-Chain for 30s: + +```bash +RPC_ENDPOINTS=http://127.0.0.1:9650/ext/bc/C/rpc +./simulator --rpc-endpoints=$RPC_ENDPOINTS --keys=./.simulator/keys --timeout=30s --concurrency=10 --base-fee=300 --priority-fee=100 +``` + +## Command Line Flags + +### `rpc-endpoints` (string) + +`rpc-endpoints` is a comma separated list of RPC endpoints to hit during the load test. + +### `keys` (string) + +`keys` specifies the directory to find the private keys to use throughout the test. The directory should contain files with the hex address as the name and the corresponding private key as the only content. + +If the test needs to generate more keys (to meet the number of workers specified by `concurrency`), it will save them in this directory to ensure that the private keys holding funds at the end of the test are preserved. + +:::warning +The `keys` directory is not a secure form of storage for private keys. This should only be used in local network and short lived network testing where losing or compromising the keys is not an issue. +::: + +Note: if none of the keys in this directory have any funds, then the simulator will log an address that it expects to receive funds in order to fund the load test and wait for those funds to arrive. + +### `timeout` (duration) + +`timeout` specifies the duration to simulate load on the network for this test. + +### `concurrency` (int) + +`concurrency` specifies the number of concurrent workers that should send transactions to the network throughout the test. Each worker in the load test is a pairing of a private key and an RPC Endpoint. The private key is used to generate a stream of transactions, which are issued to the RPC Endpoint. + +### `base-fee` (int) + +`base-fee` specifies the base fee (denominated in GWei) to use for every transaction generated during the load test (generates Dynamic Fee Transactions). + +### `priority-fee` (int) + +`priority-fee` specifies the priority fee (denominated in GWei) to use for every transaction generated during the load test (generates Dynamic Fee Transactions). diff --git a/cmd/simulator/go.mod b/cmd/simulator/go.mod index b5144e9a0b..df7d5c7240 100644 --- a/cmd/simulator/go.mod +++ b/cmd/simulator/go.mod @@ -3,78 +3,43 @@ module github.com/ava-labs/subnet-evm/cmd/simulator go 1.18 require ( - github.com/ava-labs/subnet-evm v0.0.0-00010101000000-000000000000 github.com/ethereum/go-ethereum v1.10.26 github.com/spf13/cobra v1.5.0 golang.org/x/sync v0.1.0 sigs.k8s.io/yaml v1.3.0 ) -// always depend on the local version -replace github.com/ava-labs/subnet-evm => ../.. - require ( github.com/VictoriaMetrics/fastcache v1.10.0 // indirect - github.com/ava-labs/avalanchego v1.9.5 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect - github.com/cenkalti/backoff/v4 v4.1.3 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/deckarep/golang-set v1.8.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect - github.com/go-logr/logr v1.2.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect + github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-stack/stack v1.8.0 // indirect - github.com/golang/mock v1.6.0 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/golang/snappy v0.0.4 // indirect - github.com/google/uuid v1.2.0 // indirect - github.com/gorilla/rpc v1.2.0 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect - github.com/mr-tron/base58 v1.2.0 // indirect - github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.13.0 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.37.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect + github.com/kr/pretty v0.1.0 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/prometheus/tsdb v0.10.0 // indirect github.com/rjeczalik/notify v0.9.2 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969 // indirect github.com/stretchr/testify v1.8.1 // indirect - github.com/supranational/blst v0.3.11-0.20220920110316-f72618070295 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/tklauser/numcpus v0.2.2 // indirect + github.com/tyler-smith/go-bip39 v1.0.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - go.opentelemetry.io/otel v1.11.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.0 // indirect - go.opentelemetry.io/otel/sdk v1.11.0 // indirect - go.opentelemetry.io/otel/trace v1.11.0 // indirect - go.opentelemetry.io/proto/otlp v0.19.0 // indirect - go.uber.org/atomic v1.10.0 // indirect - go.uber.org/multierr v1.8.0 // indirect - go.uber.org/zap v1.23.0 // indirect golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect - golang.org/x/exp v0.0.0-20220426173459-3bcf042a4bf5 // indirect - golang.org/x/net v0.1.0 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/term v0.1.0 // indirect - golang.org/x/text v0.4.0 // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/text v0.5.0 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect - gonum.org/v1/gonum v0.11.0 // indirect - google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c // indirect - google.golang.org/grpc v1.51.0-dev // indirect - google.golang.org/protobuf v1.28.1 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/cmd/simulator/go.sum b/cmd/simulator/go.sum index ad5ceadffe..4a262340f5 100644 --- a/cmd/simulator/go.sum +++ b/cmd/simulator/go.sum @@ -1,80 +1,22 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/VictoriaMetrics/fastcache v1.10.0 h1:5hDJnLsKLpnUEToub7ETuRu8RCkb40woBZAUiKonXzY= github.com/VictoriaMetrics/fastcache v1.10.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJaNxq6132xHICNP77w8= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/ava-labs/avalanchego v1.9.5 h1:0uykbcKFocUL7U7SO/PGrXSMLX9RSt8xo5rj84bI3YY= -github.com/ava-labs/avalanchego v1.9.5/go.mod h1:1f/z4CBcz/VhNlOTj607dQj5ZQQaZQO/RO8sEa0EgvA= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/btcsuite/btcd v0.23.1 h1:IB8cVQcC2X5mHbnfirLG5IZnkWYNTPlLZVrxUYSotbE= -github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= -github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= -github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= -github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -85,139 +27,60 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk= -github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0 h1:kr3j8iIMR4ywO/O0rvksXaJvauGGCMg2zAZIiNZ9uIQ= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0/go.mod h1:ummNFgdgLhhX7aIiy35vVmQNS0rWXknfPE0qe6fmFXg= github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -225,102 +88,70 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= -github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E= -github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU= -github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic= +github.com/prometheus/tsdb v0.10.0/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4= github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969 h1:Oo2KZNP70KE0+IUJSidPj/BFS/RXNHmKIJOdckzml2E= +github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/supranational/blst v0.3.11-0.20220920110316-f72618070295 h1:rVKS9JjtqE4/PscoIsP46sRnJhfq8YFbjlk0fUJTRnY= -github.com/supranational/blst v0.3.11-0.20220920110316-f72618070295/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= @@ -328,387 +159,103 @@ github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITn github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= github.com/tyler-smith/go-bip39 v1.0.2 h1:+t3w+KwLXO6154GNJY+qUtIxLTmFjfUmpguQT1OlOT8= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/otel v1.11.0 h1:kfToEGMDq6TrVrJ9Vht84Y8y9enykSZzDDZglV0kIEk= -go.opentelemetry.io/otel v1.11.0/go.mod h1:H2KtuEphyMvlhZ+F7tg9GRhAOe60moNx61Ex+WmiKkk= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.0 h1:0dly5et1i/6Th3WHn0M6kYiJfFNzhhxanrJ0bOfnjEo= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.0/go.mod h1:+Lq4/WkdCkjbGcBMVHHg2apTbv8oMBf29QCnyCCJjNQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.0 h1:eyJ6njZmH16h9dOKCi7lMswAnGsSOwgTqWzfxqcuNr8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.0/go.mod h1:FnDp7XemjN3oZ3xGunnfOUTVwd2XcvLbtRAuOSU3oc8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.0 h1:j2RFV0Qdt38XQ2Jvi4WIsQ56w8T7eSirYbMw19VXRDg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.0/go.mod h1:pILgiTEtrqvZpoiuGdblDgS5dbIaTgDrkIuKfEFkt+A= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.0 h1:v29I/NbVp7LXQYMFZhU6q17D0jSEbYOAVONlrO1oH5s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.0/go.mod h1:/RpLsmbQLDO1XCbWAM4S6TSwj8FKwwgyKKyqtvVfAnw= -go.opentelemetry.io/otel/sdk v1.11.0 h1:ZnKIL9V9Ztaq+ME43IUi/eo22mNsb6a7tGfzaOWB5fo= -go.opentelemetry.io/otel/sdk v1.11.0/go.mod h1:REusa8RsyKaq0OlyangWXaw97t2VogoO4SSEeKkSTAk= -go.opentelemetry.io/otel/trace v1.11.0 h1:20U/Vj42SX+mASlXLmSGBg6jpI1jQtv682lZtTAOVFI= -go.opentelemetry.io/otel/trace v1.11.0/go.mod h1:nyYjis9jy0gytE9LXGU+/m1sHTKbRY0fX0hulNNDP1U= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= -go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220426173459-3bcf042a4bf5 h1:rxKZ2gOnYxjfmakvUUqh9Gyb6KXfrj7JWTxORTYqb0E= -golang.org/x/exp v0.0.0-20220426173459-3bcf042a4bf5/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E= -gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c h1:QgY/XxIAIeccR+Ca/rDdKubLIU9rcJ3xfy1DC/Wd2Oo= -google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.51.0-dev h1:JIZpGUpbGAukP4rGiKJ/AnpK9BqMYV6Rdx94XWZckHY= -google.golang.org/grpc v1.51.0-dev/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/cmd/simulator/main.go b/cmd/simulator/main.go index 570e27121b..2063cf471c 100644 --- a/cmd/simulator/main.go +++ b/cmd/simulator/main.go @@ -15,7 +15,6 @@ import ( "github.com/ava-labs/subnet-evm/cmd/simulator/worker" "github.com/spf13/cobra" - "sigs.k8s.io/yaml" ) func init() { @@ -31,15 +30,24 @@ func main() { } var ( - timeout time.Duration - keysDir string + version = "v0.0.1" + versionFlag bool + timeout time.Duration + keysDir string - clusterInfoYamlPath string - rpcEndpoints []string + rpcEndpoints []string concurrency int baseFee uint64 priorityFee uint64 + + defaultLocalNetworkCChainEndpoints = []string{ + "http://127.0.0.1:9650/ext/bc/C/rpc", + "http://127.0.0.1:9652/ext/bc/C/rpc", + "http://127.0.0.1:9654/ext/bc/C/rpc", + "http://127.0.0.1:9656/ext/bc/C/rpc", + "http://127.0.0.1:9658/ext/bc/C/rpc", + } ) func newCommand() *cobra.Command { @@ -50,20 +58,25 @@ func newCommand() *cobra.Command { Run: runFunc, } + cmd.PersistentFlags().BoolVarP(&versionFlag, "version", "v", false, "Print the version of the simulator and exit.") cmd.PersistentFlags().DurationVarP(&timeout, "timeout", "t", time.Minute, "Duration to run simulator") - cmd.PersistentFlags().StringVarP(&keysDir, "keys", "k", ".simulator/keys", "Directory for key files") - cmd.PersistentFlags().StringVarP(&clusterInfoYamlPath, "cluster-info-yaml", "o", "", "If non-empty, it loads the endpoints from the YAML and overwrites --config") - cmd.PersistentFlags().StringSliceVarP(&rpcEndpoints, "endpoints", "e", nil, "If non-empty, it loads the endpoints from the YAML and overwrites --config") - cmd.PersistentFlags().IntVarP(&concurrency, "concurrency", "c", 10, "Concurrency") - cmd.PersistentFlags().Uint64VarP(&baseFee, "base-fee", "f", 25, "Base fee") - cmd.PersistentFlags().Uint64VarP(&priorityFee, "priority-fee", "p", 1, "Base fee") + cmd.PersistentFlags().StringVarP(&keysDir, "keys", "k", ".simulator/keys", "Directory to find key files") + cmd.PersistentFlags().StringSliceVarP(&rpcEndpoints, "rpc-endpoints", "e", defaultLocalNetworkCChainEndpoints, `Specifies a comma separated list of RPC Endpoints to use for the load test. Ex. "http://127.0.0.1:9650/ext/bc/C/rpc,http://127.0.0.1:9652/ext/bc/C/rpc". Defaults to the default RPC Endpoints for the C-Chain on a 5 Node local network.`) + cmd.PersistentFlags().IntVarP(&concurrency, "concurrency", "c", 10, "Number of concurrent workers to use during the load test") + cmd.PersistentFlags().Uint64VarP(&baseFee, "base-fee", "f", 25, "Base fee to use for each transaction issued into the load test") + cmd.PersistentFlags().Uint64VarP(&priorityFee, "priority-fee", "p", 1, "Priority fee to use for each transaction issued into the load test") return cmd } func runFunc(cmd *cobra.Command, args []string) { - log.Printf("launching simulator with rpc endpoints %q or cluster info yaml %q, timeout %v, concurrentcy %d, base fee %d, priority fee %d", - rpcEndpoints, clusterInfoYamlPath, timeout, concurrency, baseFee, priorityFee) + if versionFlag { + fmt.Printf("%s\n", version) + return + } + // TODO: use geth logger + log.Printf("launching simulator with rpc endpoints %q timeout %v, concurrency %d, base fee %d, priority fee %d", + rpcEndpoints, timeout, concurrency, baseFee, priorityFee) cfg := &worker.Config{ Endpoints: rpcEndpoints, @@ -72,35 +85,6 @@ func runFunc(cmd *cobra.Command, args []string) { PriorityFee: priorityFee, } - if clusterInfoYamlPath != "" { - log.Printf("loading cluster info yaml %q", clusterInfoYamlPath) - b, err := os.ReadFile(clusterInfoYamlPath) - if err != nil { - log.Fatalf("failed to read cluster info yaml %v", err) - } - var ci networkRunnerClusterInfo - if err = yaml.Unmarshal(b, &ci); err != nil { - log.Fatalf("failed to parse cluster info yaml %v", err) - } - - eps := make([]string, len(ci.URIs)) - for i := range eps { - /* - e.g., - - uris: - - http://127.0.0.1:32945 - - http://127.0.0.1:38948 - - http://127.0.0.1:47203 - - http://127.0.0.1:54708 - - http://127.0.0.1:64435 - endpoint: /ext/bc/oFzgVk4nzHApgcBAPXa7JLX5mhqAJnxQkiYD915tZ6LMPcPRu - */ - eps[i] = ci.URIs[i] + ci.Endpoint + "/rpc" - } - cfg.Endpoints = eps - } - ctx, cancel := context.WithTimeout(context.Background(), timeout) errc := make(chan error) go func() { @@ -120,8 +104,3 @@ func runFunc(cmd *cobra.Command, args []string) { } } } - -type networkRunnerClusterInfo struct { - URIs []string `json:"uris"` - Endpoint string `json:"endpoint"` -} diff --git a/cmd/simulator/metrics/metrics.go b/cmd/simulator/metrics/metrics.go index 821de7f28e..4df658164a 100644 --- a/cmd/simulator/metrics/metrics.go +++ b/cmd/simulator/metrics/metrics.go @@ -10,9 +10,9 @@ import ( "math/big" "time" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/ethclient" - "github.com/ava-labs/subnet-evm/params" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/params" ) const ( @@ -21,7 +21,7 @@ const ( // Monitor periodically prints metrics related to transaction activity on // a given network. -func Monitor(ctx context.Context, client ethclient.Client) error { +func Monitor(ctx context.Context, client *ethclient.Client) error { lastBlockNumber, err := client.BlockNumber(ctx) if err != nil { return fmt.Errorf("failed to get block number: %w", err) @@ -58,7 +58,7 @@ func Monitor(ctx context.Context, client ethclient.Client) error { gas := block.GasUsed() t := block.Time() - log.Printf("[block created] t: %v index: %d base fee: %d block gas cost: %d block txs: %d gas used: %d\n", time.Unix(int64(t), 0), i, block.BaseFee().Div(block.BaseFee(), big.NewInt(params.GWei)), block.BlockGasCost(), txs, gas) + log.Printf("[block created] t: %v index: %d base fee: %d block txs: %d gas used: %d\n", time.Unix(int64(t), 0), i, block.BaseFee().Div(block.BaseFee(), big.NewInt(params.GWei)), txs, gas) // Update Tx Count timeTxs[t] += txs diff --git a/cmd/simulator/worker/worker.go b/cmd/simulator/worker/worker.go index b398ec0988..80eacbce90 100644 --- a/cmd/simulator/worker/worker.go +++ b/cmd/simulator/worker/worker.go @@ -5,6 +5,7 @@ package worker import ( "context" + "errors" "fmt" "log" "math/big" @@ -13,10 +14,10 @@ import ( "github.com/ava-labs/subnet-evm/cmd/simulator/key" "github.com/ava-labs/subnet-evm/cmd/simulator/metrics" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/ethclient" - "github.com/ava-labs/subnet-evm/params" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/params" "golang.org/x/sync/errgroup" ) @@ -104,7 +105,7 @@ func createWorkers(ctx context.Context, keysDir string, endpoints []string, desi } type worker struct { - c ethclient.Client + c *ethclient.Client k *key.Key balance *big.Int @@ -112,6 +113,7 @@ type worker struct { } func newWorker(k *key.Key, endpoint string, keysDir string) (*worker, error) { + log.Printf("Creating worker endpoint %s, keysDir %s", endpoint, keysDir) client, err := ethclient.Dial(endpoint) if err != nil { return nil, fmt.Errorf("failed to create ethclient: %w", err) @@ -276,6 +278,9 @@ func (w *worker) confirmTransaction(ctx context.Context, tx common.Hash) (*big.I // Run attempts to apply load to a network specified in .simulator/config.yml // and periodically prints metrics about the traffic it generates. func Run(ctx context.Context, cfg *Config, keysDir string) error { + if len(cfg.Endpoints) == 0 { + return errors.New("cannot start worker with no endpoints") + } rclient, err := ethclient.Dial(cfg.Endpoints[0]) if err != nil { return err diff --git a/commontype/fee_config.go b/commontype/fee_config.go index 0e4179e0d1..d7d23f3f32 100644 --- a/commontype/fee_config.go +++ b/commontype/fee_config.go @@ -61,6 +61,23 @@ var EmptyFeeConfig = FeeConfig{} // Verify checks fields of this config to ensure a valid fee configuration is provided. func (f *FeeConfig) Verify() error { + switch { + case f.GasLimit == nil: + return fmt.Errorf("gasLimit cannot be nil") + case f.MinBaseFee == nil: + return fmt.Errorf("minBaseFee cannot be nil") + case f.TargetGas == nil: + return fmt.Errorf("targetGas cannot be nil") + case f.BaseFeeChangeDenominator == nil: + return fmt.Errorf("baseFeeChangeDenominator cannot be nil") + case f.MinBlockGasCost == nil: + return fmt.Errorf("minBlockGasCost cannot be nil") + case f.MaxBlockGasCost == nil: + return fmt.Errorf("maxBlockGasCost cannot be nil") + case f.BlockGasCostStep == nil: + return fmt.Errorf("blockGasCostStep cannot be nil") + } + switch { case f.GasLimit.Cmp(common.Big0) != 1: return fmt.Errorf("gasLimit = %d cannot be less than or equal to 0", f.GasLimit) diff --git a/commontype/fee_config_test.go b/commontype/fee_config_test.go index 57c8a58b06..997cb708e3 100644 --- a/commontype/fee_config_test.go +++ b/commontype/fee_config_test.go @@ -29,6 +29,22 @@ func TestVerify(t *testing.T) { config *FeeConfig expectedError string }{ + { + name: "nil gasLimit in FeeConfig", + config: &FeeConfig{ + // GasLimit: big.NewInt(8_000_000) + TargetBlockRate: 2, // in seconds + + MinBaseFee: big.NewInt(25_000_000_000), + TargetGas: big.NewInt(15_000_000), + BaseFeeChangeDenominator: big.NewInt(36), + + MinBlockGasCost: big.NewInt(0), + MaxBlockGasCost: big.NewInt(1_000_000), + BlockGasCostStep: big.NewInt(200_000), + }, + expectedError: "gasLimit cannot be nil", + }, { name: "invalid GasLimit in FeeConfig", config: func() *FeeConfig { c := validFeeConfig; c.GasLimit = big.NewInt(0); return &c }(), diff --git a/compatibility.json b/compatibility.json index 7caa22e49e..f9d90ad393 100644 --- a/compatibility.json +++ b/compatibility.json @@ -1,5 +1,7 @@ { "rpcChainVMProtocolVersion": { + "v0.4.9": 22, + "v0.4.8": 22, "v0.4.7": 21, "v0.4.6": 20, "v0.4.5": 20, diff --git a/contract-examples/README.md b/contract-examples/README.md index ace8d51138..1e4b2c7cb0 100644 --- a/contract-examples/README.md +++ b/contract-examples/README.md @@ -54,13 +54,52 @@ For more information about precompiles see [subnet-evm precompiles](https://gith Hardhat uses `hardhat.config.js` as the configuration file. You can define tasks, networks, compilers and more in that file. For more information see [here](https://hardhat.org/config/). -In our repository we use a pre-configured file [hardhat.config.ts](https://github.com/ava-labs/avalanche-smart-contract-quickstart/blob/main/hardhat.config.ts). This file configures necessary network information to provide smooth interaction with Avalanche. There are two networks in the hardhat config: `e2e` and `local`. `e2e` network is used for e2e tests and should not be changed. `local` network is used by tasks and for local deployments. There are also some pre-defined private keys for these networks. Each chain in subnets has their own RPC URL. Subnet EVM's RPC URL is in form of: `"http://{ip}:{port}/ext/bc/{chainID}/rpc`. When you create your own subnet and Subnet EVM chain `{chainID}` will be different. You can change `local` network RPC url with the `local_rpc.json`. There is an example file named with `local_rpc_example.json`. You can copy & rename this file to customize the url: +In Subnet-EVM, we provide a pre-configured file [hardhat.config.ts](https://github.com/ava-labs/avalanche-smart-contract-quickstart/blob/main/hardhat.config.ts). +The HardHat config file includes a single network configuration: `local`. `local` defaults to using the following values for the RPC URL and the Chain ID: + +``` +var local_rpc_uri = process.env.RPC_URI || "http://127.0.0.1:9650/ext/bc/C/rpc" +var local_chain_id = process.env.CHAIN_ID || 99999 ``` -cp local_rpc.example.json local_rpc.json + +You can use this network configuration by providing the environment variables and specifying the `--network` flag, as Subnet-EVM does in its testing suite: + +```bash +RPC_URI=http://127.0.0.1:9650/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/rpc CHAIN_ID=77777 npx hardhat test --network local ``` -Do not forget to set correct URL in the `local_rpc.json` file. +Alternatively, you can copy and paste the `local` network configuration to create a new network configuration for your own local testing. For example, you can copy and paste the `local` network configuration to create your own network and fill in the required details: + +```json +{ + networks: { + mynetwork: { + url: "http://127.0.0.1:9650/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/rpc", + chainId: 33333, + accounts: [ + "0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027", + "0x7b4198529994b0dc604278c99d153cfd069d594753d471171a1d102a10438e07", + "0x15614556be13730e9e8d6eacc1603143e7b96987429df8726384c2ec4502ef6e", + "0x31b571bf6894a248831ff937bb49f7754509fe93bbd2517c9c73c4144c0e97dc", + "0x6934bef917e01692b789da754a0eae31a8536eb465e7bff752ea291dad88c675", + "0xe700bdbdbc279b808b1ec45f8c2370e4616d3a02c336e68d85d4668e08f53cff", + "0xbbc2865b76ba28016bc2255c7504d000e046ae01934b04c694592a6276988630", + "0xcdbfd34f687ced8c6968854f8a99ae47712c4f4183b78dcc4a903d1bfe8cbf60", + "0x86f78c5416151fe3546dece84fda4b4b1e36089f2dbc48496faf3a950f16157c", + "0x750839e9dbbd2a0910efe40f50b2f3b2f2f59f5580bb4b83bd8c1201cf9a010a" + ], + pollingInterval: "1s" + }, + } +} +``` + +By creating your own network configuration in the HardHat config, you can run HardHat commands directly on your subnet: + +```bash +npx hardhat accounts --network mynetwork +``` ## Hardhat Tasks @@ -68,7 +107,11 @@ You can define custom hardhat tasks in [tasks.ts](https://github.com/ava-labs/av ## Tests -Tests are written for a local network which runs a Subnet-EVM chain. E.g `npx hardhat test --network local`. Subnet-EVM must activate required precompiles with following genesis: +Tests are written for a local network which runs a Subnet-EVM Blockchain. + +E.g `RPC_URI=http://127.0.0.1:9650/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/rpc CHAIN_ID=77777 npx hardhat test --network local`. + +Subnet-EVM must activate any precompiles used in the test in the genesis: ```json { diff --git a/contract-examples/hardhat.config.ts b/contract-examples/hardhat.config.ts index ab17286bcf..1e4c995b7d 100644 --- a/contract-examples/hardhat.config.ts +++ b/contract-examples/hardhat.config.ts @@ -1,19 +1,10 @@ import "@nomiclabs/hardhat-waffle" import "./tasks.ts" -import { existsSync } from "fs" -// Import the dynamic rpc url if the file exists -let testRpc = "" -if (existsSync("./dynamic_rpc.json")) { - const importedRpc = require("./dynamic_rpc.json") - testRpc = importedRpc.rpc -} - -var localConf -if (existsSync("./local_rpc.json")) { - const importedRpc = require("./local_rpc.json") - localConf = importedRpc -} +// HardHat users must populate these environment variables in order to connect to their subnet-evm instance +// Since the blockchainID is not known in advance, there's no good default to use and we use the C-Chain here. +var local_rpc_uri = process.env.RPC_URI || "http://127.0.0.1:9650/ext/bc/C/rpc" +var local_chain_id = parseInt(process.env.CHAIN_ID,10) || 99999 export default { solidity: { @@ -38,26 +29,9 @@ export default { networks: { local: { //"http://{ip}:{port}/ext/bc/{chainID}/rpc - // modify this in the local_rpc.json - url: localConf.rpc, - chainId: localConf.chainId, - accounts: [ - "0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027", - "0x7b4198529994b0dc604278c99d153cfd069d594753d471171a1d102a10438e07", - "0x15614556be13730e9e8d6eacc1603143e7b96987429df8726384c2ec4502ef6e", - "0x31b571bf6894a248831ff937bb49f7754509fe93bbd2517c9c73c4144c0e97dc", - "0x6934bef917e01692b789da754a0eae31a8536eb465e7bff752ea291dad88c675", - "0xe700bdbdbc279b808b1ec45f8c2370e4616d3a02c336e68d85d4668e08f53cff", - "0xbbc2865b76ba28016bc2255c7504d000e046ae01934b04c694592a6276988630", - "0xcdbfd34f687ced8c6968854f8a99ae47712c4f4183b78dcc4a903d1bfe8cbf60", - "0x86f78c5416151fe3546dece84fda4b4b1e36089f2dbc48496faf3a950f16157c", - "0x750839e9dbbd2a0910efe40f50b2f3b2f2f59f5580bb4b83bd8c1201cf9a010a" - ] - }, - e2e: { - //"http://{ip}:{port}/ext/bc/{chainID}/rpc - url: testRpc, - chainId: 99999, + // expected to be populated by the environment variables above + url: local_rpc_uri, + chainId: local_chain_id, accounts: [ "0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027", "0x7b4198529994b0dc604278c99d153cfd069d594753d471171a1d102a10438e07", @@ -69,7 +43,8 @@ export default { "0xcdbfd34f687ced8c6968854f8a99ae47712c4f4183b78dcc4a903d1bfe8cbf60", "0x86f78c5416151fe3546dece84fda4b4b1e36089f2dbc48496faf3a950f16157c", "0x750839e9dbbd2a0910efe40f50b2f3b2f2f59f5580bb4b83bd8c1201cf9a010a" - ] + ], + pollingInterval: "1s" }, } } diff --git a/contract-examples/local_rpc.example.json b/contract-examples/local_rpc.example.json deleted file mode 100644 index bcd2516399..0000000000 --- a/contract-examples/local_rpc.example.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "rpc": "http://127.0.0.1:9650/ext/bc/dRTfPJh4jEaRZoGkPc7xreeYbDGBrGWRV48WAYVyUgApsmzGo/rpc", - "chainId": 43214 -} diff --git a/contract-examples/test/ExampleDeployerList.ts b/contract-examples/test/contract_deployer_allow_list.ts similarity index 100% rename from contract-examples/test/ExampleDeployerList.ts rename to contract-examples/test/contract_deployer_allow_list.ts diff --git a/contract-examples/test/ERC20NativeMinter.ts b/contract-examples/test/contract_native_minter.ts similarity index 100% rename from contract-examples/test/ERC20NativeMinter.ts rename to contract-examples/test/contract_native_minter.ts diff --git a/contract-examples/test/ExampleFeeManager.ts b/contract-examples/test/fee_manager.ts similarity index 100% rename from contract-examples/test/ExampleFeeManager.ts rename to contract-examples/test/fee_manager.ts diff --git a/contract-examples/test/ExampleRewardManager.ts b/contract-examples/test/reward_manager.ts similarity index 100% rename from contract-examples/test/ExampleRewardManager.ts rename to contract-examples/test/reward_manager.ts diff --git a/contract-examples/test/ExampleTxAllowList.ts b/contract-examples/test/tx_allow_list.ts similarity index 100% rename from contract-examples/test/ExampleTxAllowList.ts rename to contract-examples/test/tx_allow_list.ts diff --git a/core/blockchain.go b/core/blockchain.go index 226e32a96e..50e688e473 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -85,9 +85,13 @@ var ( acceptedBlockGasUsedCounter = metrics.NewRegisteredCounter("chain/block/gas/used/accepted", nil) badBlockCounter = metrics.NewRegisteredCounter("chain/block/bad/count", nil) + txUnindexTimer = metrics.NewRegisteredCounter("chain/txs/unindex", nil) acceptedTxsCounter = metrics.NewRegisteredCounter("chain/txs/accepted", nil) processedTxsCounter = metrics.NewRegisteredCounter("chain/txs/processed", nil) + acceptedLogsCounter = metrics.NewRegisteredCounter("chain/logs/accepted", nil) + processedLogsCounter = metrics.NewRegisteredCounter("chain/logs/processed", nil) + ErrRefuseToCorruptArchiver = errors.New("node has operated with pruning disabled, shutting down to prevent missing tries") errFutureBlockUnsupported = errors.New("future block insertion not supported") @@ -102,7 +106,6 @@ const ( feeConfigCacheLimit = 256 coinbaseConfigCacheLimit = 256 badBlockLimit = 10 - TriesInMemory = 128 // BlockChainVersion ensures that an incompatible database forces a resync from scratch. // @@ -173,6 +176,7 @@ type CacheConfig struct { SkipSnapshotRebuild bool // Whether to skip rebuilding the snapshot in favor of returning an error (only set to true for tests) Preimages bool // Whether to store preimage of trie key to the disk AcceptedCacheSize int // Depth of accepted headers cache and accepted logs cache at the accepted tip + TxLookupLimit uint64 // Number of recent blocks for which to maintain transaction lookup indices } var DefaultCacheConfig = &CacheConfig{ @@ -269,9 +273,8 @@ type BlockChain struct { // during shutdown and in tests. acceptorWg sync.WaitGroup - // [rejournalWg] is used to wait for the trie clean rejournaling to complete. - // This is used during shutdown. - rejournalWg sync.WaitGroup + // [wg] is used to wait for the async blockchain processes to finish on shutdown. + wg sync.WaitGroup // quit channel is used to listen for when the blockchain is shut down to close // async processes. @@ -354,6 +357,13 @@ func NewBlockChain( // Create the state manager bc.stateManager = NewTrieWriter(bc.stateCache.TrieDB(), cacheConfig) + // loadLastState writes indices, so we should start the tx indexer after that. + // Start tx indexer/unindexer here. + if bc.cacheConfig.TxLookupLimit != 0 { + bc.wg.Add(1) + go bc.dispatchTxUnindexer() + } + // Re-generate current block state if it is missing if err := bc.loadLastState(lastAcceptedHash); err != nil { return nil, err @@ -401,9 +411,9 @@ func NewBlockChain( log.Info("Starting to save trie clean cache periodically", "journalDir", bc.cacheConfig.TrieCleanJournal, "freq", bc.cacheConfig.TrieCleanRejournal) triedb := bc.stateCache.TrieDB() - bc.rejournalWg.Add(1) + bc.wg.Add(1) go func() { - defer bc.rejournalWg.Done() + defer bc.wg.Done() triedb.SaveCachePeriodically(bc.cacheConfig.TrieCleanJournal, bc.cacheConfig.TrieCleanRejournal, bc.quit) }() } @@ -411,6 +421,72 @@ func NewBlockChain( return bc, nil } +// dispatchTxUnindexer is responsible for the deletion of the +// transaction index. +// Invariant: If TxLookupLimit is 0, it means all tx indices will be preserved. +// Meaning that this function should never be called. +func (bc *BlockChain) dispatchTxUnindexer() { + defer bc.wg.Done() + txLookupLimit := bc.cacheConfig.TxLookupLimit + + // If the user just upgraded to a new version which supports transaction + // index pruning, write the new tail and remove anything older. + if rawdb.ReadTxIndexTail(bc.db) == nil { + rawdb.WriteTxIndexTail(bc.db, 0) + } + + // unindexes transactions depending on user configuration + unindexBlocks := func(tail uint64, head uint64, done chan struct{}) { + start := time.Now() + defer func() { + txUnindexTimer.Inc(time.Since(start).Milliseconds()) + done <- struct{}{} + }() + + // Update the transaction index to the new chain state + if head-txLookupLimit+1 >= tail { + // Unindex a part of stale indices and forward index tail to HEAD-limit + rawdb.UnindexTransactions(bc.db, tail, head-txLookupLimit+1, bc.quit) + } + } + // Any reindexing done, start listening to chain events and moving the index window + var ( + done chan struct{} // Non-nil if background unindexing or reindexing routine is active. + headCh = make(chan ChainEvent, 1) // Buffered to avoid locking up the event feed + ) + sub := bc.SubscribeChainAcceptedEvent(headCh) + if sub == nil { + log.Warn("could not create chain accepted subscription to unindex txs") + return + } + defer sub.Unsubscribe() + + for { + select { + case head := <-headCh: + headNum := head.Block.NumberU64() + if headNum < txLookupLimit { + break + } + + if done == nil { + done = make(chan struct{}) + // Note: tail will not be nil since it is initialized in this function. + tail := rawdb.ReadTxIndexTail(bc.db) + go unindexBlocks(*tail, headNum, done) + } + case <-done: + done = nil + case <-bc.quit: + if done != nil { + log.Info("Waiting background transaction indexer to exit") + <-done + } + return + } + } +} + // writeBlockAcceptedIndices writes any indices that must be persisted for accepted block. // This includes the following: // - transaction lookup indices @@ -532,6 +608,9 @@ func (bc *BlockChain) startAcceptor() { acceptorWorkTimer.Inc(time.Since(start).Milliseconds()) acceptorWorkCount.Inc(1) + // Note: in contrast to most accepted metrics, we increment the accepted log metrics in the acceptor queue because + // the logs are already processed in the acceptor queue. + acceptedLogsCounter.Inc(int64(len(logs))) } } @@ -555,8 +634,8 @@ func (bc *BlockChain) addAcceptorQueue(b *types.Block) { // DrainAcceptorQueue blocks until all items in [acceptorQueue] have been // processed. func (bc *BlockChain) DrainAcceptorQueue() { - bc.acceptorClosingLock.Lock() - defer bc.acceptorClosingLock.Unlock() + bc.acceptorClosingLock.RLock() + defer bc.acceptorClosingLock.RUnlock() if bc.acceptorClosed { return @@ -782,7 +861,8 @@ func (bc *BlockChain) ValidateCanonicalChain() error { // Transactions are only indexed beneath the last accepted block, so we only check // that the transactions have been indexed, if we are checking below the last accepted // block. - if current.NumberU64() <= bc.lastAccepted.NumberU64() { + shouldIndexTxs := bc.cacheConfig.TxLookupLimit == 0 || bc.lastAccepted.NumberU64() < current.NumberU64()+bc.cacheConfig.TxLookupLimit + if current.NumberU64() <= bc.lastAccepted.NumberU64() && shouldIndexTxs { // Ensure that all of the transactions have been stored correctly in the canonical // chain for txIndex, tx := range txs { @@ -840,7 +920,6 @@ func (bc *BlockChain) Stop() { return } - // Wait for accepted feed to process all remaining items log.Info("Closing quit channel") close(bc.quit) // Wait for accepted feed to process all remaining items @@ -868,9 +947,9 @@ func (bc *BlockChain) Stop() { log.Info("Closing scope") bc.scope.Close() - // Waiting for clean trie re-journal to complete - log.Info("Waiting for trie re-journal to complete") - bc.rejournalWg.Wait() + // Waiting for background processes to complete + log.Info("Waiting for background processes to complete") + bc.wg.Wait() log.Info("Blockchain stopped") } @@ -1313,6 +1392,7 @@ func (bc *BlockChain) insertBlock(block *types.Block, writes bool) error { processedBlockGasUsedCounter.Inc(int64(block.GasUsed())) processedTxsCounter.Inc(int64(block.Transactions().Len())) + processedLogsCounter.Inc(int64(len(logs))) blockInsertCount.Inc(1) return nil } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 270eef14df..e4eb86c3d0 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -745,3 +745,142 @@ func TestCanonicalHashMarker(t *testing.T) { } } } + +func TestTransactionIndices(t *testing.T) { + // Configure and generate a sample block chain + require := require.New(t) + var ( + gendb = rawdb.NewMemoryDatabase() + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + funds = big.NewInt(10000000000000) + gspec = &Genesis{ + Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, + Alloc: GenesisAlloc{addr1: {Balance: funds}}, + } + genesis = gspec.MustCommit(gendb) + signer = types.LatestSigner(gspec.Config) + ) + height := uint64(128) + blocks, _, err := GenerateChain(gspec.Config, genesis, dummy.NewFaker(), gendb, int(height), 10, func(i int, block *BlockGen) { + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) + require.NoError(err) + block.AddTx(tx) + }) + require.NoError(err) + + blocks2, _, err := GenerateChain(gspec.Config, blocks[len(blocks)-1], dummy.NewFaker(), gendb, 10, 10, nil) + require.NoError(err) + + check := func(tail *uint64, chain *BlockChain) { + stored := rawdb.ReadTxIndexTail(chain.db) + require.EqualValues(tail, stored) + + if tail == nil { + return + } + for i := *tail; i <= chain.CurrentBlock().NumberU64(); i++ { + block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i) + if block.Transactions().Len() == 0 { + continue + } + for _, tx := range block.Transactions() { + index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()) + require.NotNilf(index, "Miss transaction indices, number %d hash %s", i, tx.Hash().Hex()) + } + } + + for i := uint64(0); i < *tail; i++ { + block := rawdb.ReadBlock(chain.db, rawdb.ReadCanonicalHash(chain.db, i), i) + if block.Transactions().Len() == 0 { + continue + } + for _, tx := range block.Transactions() { + index := rawdb.ReadTxLookupEntry(chain.db, tx.Hash()) + require.Nilf(index, "Transaction indices should be deleted, number %d hash %s", i, tx.Hash().Hex()) + } + } + } + + conf := &CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieDirtyCommitTarget: 20, + Pruning: true, + CommitInterval: 4096, + SnapshotLimit: 256, + SkipSnapshotRebuild: true, // Ensure the test errors if snapshot initialization fails + AcceptorQueueLimit: 64, + } + + // Init block chain and check all needed indices has been indexed. + chainDB := rawdb.NewMemoryDatabase() + gspec.MustCommit(chainDB) + + chain, err := createBlockChain(chainDB, conf, gspec.Config, common.Hash{}) + require.NoError(err) + + _, err = chain.InsertChain(blocks) + require.NoError(err) + + for _, block := range blocks { + err := chain.Accept(block) + require.NoError(err) + } + chain.DrainAcceptorQueue() + + chain.Stop() + check(nil, chain) // check all indices has been indexed + + lastAcceptedHash := chain.CurrentHeader().Hash() + + // Reconstruct a block chain which only reserves limited tx indices + // 128 blocks were previously indexed. Now we add a new block at each test step. + limit := []uint64{130 /* 129 + 1 reserve all */, 64 /* drop stale */, 32 /* shorten history */} + tails := []uint64{0 /* reserve all */, 67 /* 130 - 64 + 1 */, 100 /* 131 - 32 + 1 */} + for i, l := range limit { + conf.TxLookupLimit = l + + chain, err := createBlockChain(chainDB, conf, gspec.Config, lastAcceptedHash) + require.NoError(err) + + newBlks := blocks2[i : i+1] + _, err = chain.InsertChain(newBlks) // Feed chain a higher block to trigger indices updater. + require.NoError(err) + + err = chain.Accept(newBlks[0]) // Accept the block to trigger indices updater. + require.NoError(err) + + chain.DrainAcceptorQueue() + time.Sleep(50 * time.Millisecond) // Wait for indices initialisation + + chain.Stop() + check(&tails[i], chain) + + lastAcceptedHash = chain.CurrentHeader().Hash() + } +} + +func TestTxLookupBlockChain(t *testing.T) { + cacheConf := &CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieDirtyCommitTarget: 20, + Pruning: true, + CommitInterval: 4096, + SnapshotLimit: 256, + SkipSnapshotRebuild: true, // Ensure the test errors if snapshot initialization fails + AcceptorQueueLimit: 64, // ensure channel doesn't block + TxLookupLimit: 5, + } + createTxLookupBlockChain := func(db ethdb.Database, chainConfig *params.ChainConfig, lastAcceptedHash common.Hash) (*BlockChain, error) { + return createBlockChain(db, cacheConf, chainConfig, lastAcceptedHash) + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + tt.testFunc(t, createTxLookupBlockChain) + }) + } +} diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index d7add47ed8..8b4829139c 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -588,3 +588,23 @@ func ReadHeadBlock(db ethdb.Reader) *types.Block { } return ReadBlock(db, headBlockHash, *headBlockNumber) } + +// ReadTxIndexTail retrieves the number of oldest indexed block +// whose transaction indices has been indexed. If the corresponding entry +// is non-existent in database it means the indexing has been finished. +func ReadTxIndexTail(db ethdb.KeyValueReader) *uint64 { + data, _ := db.Get(txIndexTailKey) + if len(data) != 8 { + return nil + } + number := binary.BigEndian.Uint64(data) + return &number +} + +// WriteTxIndexTail stores the number of oldest indexed block +// into database. +func WriteTxIndexTail(db ethdb.KeyValueWriter, number uint64) { + if err := db.Put(txIndexTailKey, encodeBlockNumber(number)); err != nil { + log.Crit("Failed to store the transaction index tail", "err", err) + } +} diff --git a/core/rawdb/chain_iterator.go b/core/rawdb/chain_iterator.go new file mode 100644 index 0000000000..cba39b57c6 --- /dev/null +++ b/core/rawdb/chain_iterator.go @@ -0,0 +1,311 @@ +// (c) 2019-2022, 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 2020 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 rawdb + +import ( + "runtime" + "sync/atomic" + "time" + + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/ethdb" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/prque" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" +) + +type blockTxHashes struct { + number uint64 + hashes []common.Hash +} + +// iterateTransactions iterates over all transactions in the (canon) block +// number(s) given, and yields the hashes on a channel. If there is a signal +// received from interrupt channel, the iteration will be aborted and result +// channel will be closed. +// Iterates blocks in the range [from, to) +func iterateTransactions(db ethdb.Database, from uint64, to uint64, reverse bool, interrupt chan struct{}) chan *blockTxHashes { + // One thread sequentially reads data from db + type numberRlp struct { + number uint64 + rlp rlp.RawValue + } + if to == from { + return nil + } + threads := to - from + if cpus := runtime.NumCPU(); threads > uint64(cpus) { + threads = uint64(cpus) + } + var ( + rlpCh = make(chan *numberRlp, threads*2) // we send raw rlp over this channel + hashesCh = make(chan *blockTxHashes, threads*2) // send hashes over hashesCh + ) + // lookup runs in one instance + lookup := func() { + n, end := from, to + if reverse { + n, end = to-1, from-1 + } + defer close(rlpCh) + for n != end { + data := ReadCanonicalBodyRLP(db, n) + // Feed the block to the aggregator, or abort on interrupt + select { + case rlpCh <- &numberRlp{n, data}: + case <-interrupt: + return + } + if reverse { + n-- + } else { + n++ + } + } + } + // process runs in parallel + nThreadsAlive := int32(threads) + process := func() { + defer func() { + // Last processor closes the result channel + if atomic.AddInt32(&nThreadsAlive, -1) == 0 { + close(hashesCh) + } + }() + for data := range rlpCh { + var body types.Body + if err := rlp.DecodeBytes(data.rlp, &body); err != nil { + log.Warn("Failed to decode block body", "block", data.number, "error", err) + return + } + var hashes []common.Hash + for _, tx := range body.Transactions { + hashes = append(hashes, tx.Hash()) + } + result := &blockTxHashes{ + hashes: hashes, + number: data.number, + } + // Feed the block to the aggregator, or abort on interrupt + select { + case hashesCh <- result: + case <-interrupt: + return + } + } + } + go lookup() // start the sequential db accessor + for i := 0; i < int(threads); i++ { + go process() + } + return hashesCh +} + +// indexTransactions creates txlookup indices of the specified block range. +// +// This function iterates canonical chain in reverse order, it has one main advantage: +// We can write tx index tail flag periodically even without the whole indexing +// procedure is finished. So that we can resume indexing procedure next time quickly. +// +// There is a passed channel, the whole procedure will be interrupted if any +// signal received. +func indexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) { + // short circuit for invalid range + if from >= to { + return + } + var ( + hashesCh = iterateTransactions(db, from, to, true, interrupt) + batch = db.NewBatch() + start = time.Now() + logged = start.Add(-7 * time.Second) + // Since we iterate in reverse, we expect the first number to come + // in to be [to-1]. Therefore, setting lastNum to means that the + // prqueue gap-evaluation will work correctly + lastNum = to + queue = prque.New(nil) + // for stats reporting + blocks, txs = 0, 0 + ) + for chanDelivery := range hashesCh { + // Push the delivery into the queue and process contiguous ranges. + // Since we iterate in reverse, so lower numbers have lower prio, and + // we can use the number directly as prio marker + queue.Push(chanDelivery, int64(chanDelivery.number)) + for !queue.Empty() { + // If the next available item is gapped, return + if _, priority := queue.Peek(); priority != int64(lastNum-1) { + break + } + // For testing + if hook != nil && !hook(lastNum-1) { + break + } + // Next block available, pop it off and index it + delivery := queue.PopItem().(*blockTxHashes) + lastNum = delivery.number + WriteTxLookupEntries(batch, delivery.number, delivery.hashes) + blocks++ + txs += len(delivery.hashes) + // If enough data was accumulated in memory or we're at the last block, dump to disk + if batch.ValueSize() > ethdb.IdealBatchSize { + WriteTxIndexTail(batch, lastNum) // Also write the tail here + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return + } + batch.Reset() + } + // If we've spent too much time already, notify the user of what we're doing + if time.Since(logged) > 8*time.Second { + log.Info("Indexing transactions", "blocks", blocks, "txs", txs, "tail", lastNum, "total", to-from, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + } + } + // Flush the new indexing tail and the last committed data. It can also happen + // that the last batch is empty because nothing to index, but the tail has to + // be flushed anyway. + WriteTxIndexTail(batch, lastNum) + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return + } + select { + case <-interrupt: + log.Debug("Transaction indexing interrupted", "blocks", blocks, "txs", txs, "tail", lastNum, "elapsed", common.PrettyDuration(time.Since(start))) + default: + log.Info("Indexed transactions", "blocks", blocks, "txs", txs, "tail", lastNum, "elapsed", common.PrettyDuration(time.Since(start))) + } +} + +// // IndexTransactions creates txlookup indices of the specified block range. The from +// // is included while to is excluded. +// // +// // This function iterates canonical chain in reverse order, it has one main advantage: +// // We can write tx index tail flag periodically even without the whole indexing +// // procedure is finished. So that we can resume indexing procedure next time quickly. +// // +// // There is a passed channel, the whole procedure will be interrupted if any +// // signal received. +// func IndexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}) { +// indexTransactions(db, from, to, interrupt, nil) +// } + +// indexTransactionsForTesting is the internal debug version with an additional hook. +func indexTransactionsForTesting(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) { + indexTransactions(db, from, to, interrupt, hook) +} + +// unindexTransactions removes txlookup indices of the specified block range. +// +// There is a passed channel, the whole procedure will be interrupted if any +// signal received. +func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) { + // short circuit for invalid range + if from >= to { + return + } + var ( + hashesCh = iterateTransactions(db, from, to, false, interrupt) + batch = db.NewBatch() + start = time.Now() + logged = start.Add(-7 * time.Second) + // we expect the first number to come in to be [from]. Therefore, setting + // nextNum to from means that the prqueue gap-evaluation will work correctly + nextNum = from + queue = prque.New(nil) + // for stats reporting + blocks, txs = 0, 0 + ) + // Otherwise spin up the concurrent iterator and unindexer + for delivery := range hashesCh { + // Push the delivery into the queue and process contiguous ranges. + queue.Push(delivery, -int64(delivery.number)) + for !queue.Empty() { + // If the next available item is gapped, return + if _, priority := queue.Peek(); -priority != int64(nextNum) { + break + } + // For testing + if hook != nil && !hook(nextNum) { + break + } + delivery := queue.PopItem().(*blockTxHashes) + nextNum = delivery.number + 1 + DeleteTxLookupEntries(batch, delivery.hashes) + txs += len(delivery.hashes) + blocks++ + + // If enough data was accumulated in memory or we're at the last block, dump to disk + // A batch counts the size of deletion as '1', so we need to flush more + // often than that. + if blocks%1000 == 0 { + WriteTxIndexTail(batch, nextNum) + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return + } + batch.Reset() + } + // If we've spent too much time already, notify the user of what we're doing + if time.Since(logged) > 8*time.Second { + log.Info("Unindexing transactions", "blocks", blocks, "txs", txs, "total", to-from, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + } + } + // Flush the new indexing tail and the last committed data. It can also happen + // that the last batch is empty because nothing to unindex, but the tail has to + // be flushed anyway. + WriteTxIndexTail(batch, nextNum) + if err := batch.Write(); err != nil { + log.Crit("Failed writing batch to db", "error", err) + return + } + select { + case <-interrupt: + log.Debug("Transaction unindexing interrupted", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start))) + default: + log.Info("Unindexed transactions", "blocks", blocks, "txs", txs, "tail", to, "elapsed", common.PrettyDuration(time.Since(start))) + } +} + +// UnindexTransactions removes txlookup indices of the specified block range. +// The from is included while to is excluded. +// +// There is a passed channel, the whole procedure will be interrupted if any +// signal received. +func UnindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}) { + unindexTransactions(db, from, to, interrupt, nil) +} + +// unindexTransactionsForTesting is the internal debug version with an additional hook. +func unindexTransactionsForTesting(db ethdb.Database, from uint64, to uint64, interrupt chan struct{}, hook func(uint64) bool) { + unindexTransactions(db, from, to, interrupt, hook) +} diff --git a/core/rawdb/chain_iterator_test.go b/core/rawdb/chain_iterator_test.go new file mode 100644 index 0000000000..0873dd3667 --- /dev/null +++ b/core/rawdb/chain_iterator_test.go @@ -0,0 +1,218 @@ +// (c) 2019-2022, 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 2020 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 rawdb + +import ( + "math/big" + "reflect" + "sort" + "sync" + "testing" + + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ethereum/go-ethereum/common" +) + +func TestChainIterator(t *testing.T) { + // Construct test chain db + chainDb := NewMemoryDatabase() + + var block *types.Block + var txs []*types.Transaction + to := common.BytesToAddress([]byte{0x11}) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newHasher()) // Empty genesis block + WriteBlock(chainDb, block) + WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) + for i := uint64(1); i <= 10; i++ { + var tx *types.Transaction + if i%2 == 0 { + tx = types.NewTx(&types.LegacyTx{ + Nonce: i, + GasPrice: big.NewInt(11111), + Gas: 1111, + To: &to, + Value: big.NewInt(111), + Data: []byte{0x11, 0x11, 0x11}, + }) + } else { + tx = types.NewTx(&types.AccessListTx{ + ChainID: big.NewInt(1337), + Nonce: i, + GasPrice: big.NewInt(11111), + Gas: 1111, + To: &to, + Value: big.NewInt(111), + Data: []byte{0x11, 0x11, 0x11}, + }) + } + txs = append(txs, tx) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher()) + WriteBlock(chainDb, block) + WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) + } + + cases := []struct { + from, to uint64 + reverse bool + expect []int + }{ + {0, 11, true, []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}}, + {0, 0, true, nil}, + {0, 5, true, []int{4, 3, 2, 1, 0}}, + {10, 11, true, []int{10}}, + {0, 11, false, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}}, + {0, 0, false, nil}, + {10, 11, false, []int{10}}, + } + for i, c := range cases { + var numbers []int + hashCh := iterateTransactions(chainDb, c.from, c.to, c.reverse, nil) + if hashCh != nil { + for h := range hashCh { + numbers = append(numbers, int(h.number)) + if len(h.hashes) > 0 { + if got, exp := h.hashes[0], txs[h.number-1].Hash(); got != exp { + t.Fatalf("block %d: hash wrong, got %x exp %x", h.number, got, exp) + } + } + } + } + if !c.reverse { + sort.Ints(numbers) + } else { + sort.Sort(sort.Reverse(sort.IntSlice(numbers))) + } + if !reflect.DeepEqual(numbers, c.expect) { + t.Fatalf("Case %d failed, visit element mismatch, want %v, got %v", i, c.expect, numbers) + } + } +} + +func TestIndexTransactions(t *testing.T) { + // Construct test chain db + chainDb := NewMemoryDatabase() + + var block *types.Block + var txs []*types.Transaction + to := common.BytesToAddress([]byte{0x11}) + + // Write empty genesis block + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newHasher()) + WriteBlock(chainDb, block) + WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) + + for i := uint64(1); i <= 10; i++ { + var tx *types.Transaction + if i%2 == 0 { + tx = types.NewTx(&types.LegacyTx{ + Nonce: i, + GasPrice: big.NewInt(11111), + Gas: 1111, + To: &to, + Value: big.NewInt(111), + Data: []byte{0x11, 0x11, 0x11}, + }) + } else { + tx = types.NewTx(&types.AccessListTx{ + ChainID: big.NewInt(1337), + Nonce: i, + GasPrice: big.NewInt(11111), + Gas: 1111, + To: &to, + Value: big.NewInt(111), + Data: []byte{0x11, 0x11, 0x11}, + }) + } + txs = append(txs, tx) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher()) + WriteBlock(chainDb, block) + WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) + } + // verify checks whether the tx indices in the range [from, to) + // is expected. + verify := func(from, to int, exist bool, tail uint64) { + for i := from; i < to; i++ { + if i == 0 { + continue + } + number := ReadTxLookupEntry(chainDb, txs[i-1].Hash()) + if exist && number == nil { + t.Fatalf("Transaction index %d missing", i) + } + if !exist && number != nil { + t.Fatalf("Transaction index %d is not deleted", i) + } + } + number := ReadTxIndexTail(chainDb) + if number == nil || *number != tail { + t.Fatalf("Transaction tail mismatch") + } + } + indexTransactionsForTesting(chainDb, 5, 11, nil, nil) + verify(5, 11, true, 5) + verify(0, 5, false, 5) + + indexTransactionsForTesting(chainDb, 0, 5, nil, nil) + verify(0, 11, true, 0) + + UnindexTransactions(chainDb, 0, 5, nil) + verify(5, 11, true, 5) + verify(0, 5, false, 5) + + UnindexTransactions(chainDb, 5, 11, nil) + verify(0, 11, false, 11) + + // Testing corner cases + signal := make(chan struct{}) + var once sync.Once + indexTransactionsForTesting(chainDb, 5, 11, signal, func(n uint64) bool { + if n <= 8 { + once.Do(func() { + close(signal) + }) + return false + } + return true + }) + verify(9, 11, true, 9) + verify(0, 9, false, 9) + indexTransactionsForTesting(chainDb, 0, 9, nil, nil) + + signal = make(chan struct{}) + var once2 sync.Once + unindexTransactionsForTesting(chainDb, 0, 11, signal, func(n uint64) bool { + if n >= 8 { + once2.Do(func() { + close(signal) + }) + return false + } + return true + }) + verify(8, 11, true, 8) + verify(0, 8, false, 8) +} diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 51c3da8065..dc4dd85688 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -208,7 +208,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { for _, meta := range [][]byte{ databaseVersionKey, headHeaderKey, headBlockKey, snapshotRootKey, snapshotBlockHashKey, snapshotGeneratorKey, - uncleanShutdownKey, syncRootKey, + uncleanShutdownKey, syncRootKey, txIndexTailKey, } { if bytes.Equal(key, meta) { metadata.Add(size) diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 4eb3382a84..bb0956abdf 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -56,6 +56,9 @@ var ( // snapshotGeneratorKey tracks the snapshot generation marker across restarts. snapshotGeneratorKey = []byte("SnapshotGenerator") + // txIndexTailKey tracks the oldest block whose transactions have been indexed. + txIndexTailKey = []byte("TransactionIndexTail") + // uncleanShutdownKey tracks the list of local crashes uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index aea3fed5ed..b6057a9211 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -599,6 +599,7 @@ func diffToDisk(bottom *diffLayer) (*diskLayer, bool, error) { // Mark the original base as stale as we're going to create a new wrapper base.lock.Lock() if base.stale { + base.lock.Unlock() return nil, false, ErrStaleParentLayer // we've committed into the same base from two children, boo } base.stale = true diff --git a/eth/backend.go b/eth/backend.go index 7e9aaebd84..958460a359 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -215,6 +215,7 @@ func New( SkipSnapshotRebuild: config.SkipSnapshotRebuild, Preimages: config.Preimages, AcceptedCacheSize: config.AcceptedCacheSize, + TxLookupLimit: config.TxLookupLimit, } ) diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index c1be8c0ead..98a16165a7 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -149,4 +149,10 @@ type Config struct { // their node before the network upgrade and their node accepts blocks that have // identical state with the pre-upgrade ruleset. SkipUpgradeCheck bool + + // TxLookupLimit is the maximum number of blocks from head whose tx indices + // are reserved: + // * 0: means no limit + // * N: means N block limit [HEAD-N+1, HEAD] and delete extra indexes + TxLookupLimit uint64 } diff --git a/eth/filters/bench_test.go b/eth/filters/bench_test.go index e9a55ae102..7ba3381f2f 100644 --- a/eth/filters/bench_test.go +++ b/eth/filters/bench_test.go @@ -73,7 +73,7 @@ const benchFilterCnt = 2000 func benchmarkBloomBits(b *testing.B, sectionSize uint64) { b.Skip("test disabled: this tests presume (and modify) an existing datadir.") - benchDataDir := b.TempDir() + "/coreth/chaindata" + benchDataDir := b.TempDir() + "/subnet-evm/chaindata" b.Log("Running bloombits benchmark section size:", sectionSize) db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false) @@ -173,7 +173,7 @@ func clearBloomBits(db ethdb.Database) { func BenchmarkNoBloomBits(b *testing.B) { b.Skip("test disabled: this tests presume (and modify) an existing datadir.") - benchDataDir := b.TempDir() + "/coreth/chaindata" + benchDataDir := b.TempDir() + "/subnet-evm/chaindata" b.Log("Running benchmark without bloombits") db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false) if err != nil { diff --git a/go.mod b/go.mod index 858efb956e..72b82932ec 100644 --- a/go.mod +++ b/go.mod @@ -4,19 +4,16 @@ go 1.18 require ( github.com/VictoriaMetrics/fastcache v1.10.0 - github.com/ava-labs/avalanche-network-runner-sdk v0.3.0 - github.com/ava-labs/avalanchego v1.9.5 + github.com/ava-labs/avalanchego v1.9.8 github.com/cespare/cp v0.1.0 - github.com/creack/pty v1.1.18 github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set v1.8.0 github.com/ethereum/go-ethereum v1.10.26 - github.com/fatih/color v1.13.0 github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 github.com/fsnotify/fsnotify v1.6.0 github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 github.com/go-cmd/cmd v1.4.1 - github.com/google/uuid v1.2.0 + github.com/google/uuid v1.3.0 github.com/gorilla/rpc v1.2.0 github.com/gorilla/websocket v1.4.2 github.com/hashicorp/go-bexpr v0.1.10 @@ -26,11 +23,11 @@ require ( github.com/mattn/go-colorable v0.1.12 github.com/mattn/go-isatty v0.0.14 github.com/olekukonko/tablewriter v0.0.5 - github.com/onsi/ginkgo/v2 v2.4.0 - github.com/onsi/gomega v1.24.0 - github.com/prometheus/client_golang v1.13.0 - github.com/prometheus/client_model v0.2.0 - github.com/rjeczalik/notify v0.9.2 + github.com/onsi/ginkgo/v2 v2.6.1 + github.com/onsi/gomega v1.24.2 + github.com/prometheus/client_golang v1.14.0 + github.com/prometheus/client_model v0.3.0 + github.com/rjeczalik/notify v0.9.3 github.com/shirou/gopsutil v3.21.11+incompatible github.com/spf13/cast v1.5.0 github.com/spf13/pflag v1.0.5 @@ -42,23 +39,25 @@ require ( github.com/urfave/cli/v2 v2.10.2 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d golang.org/x/sync v0.1.0 - golang.org/x/sys v0.1.0 - golang.org/x/text v0.4.0 + golang.org/x/sys v0.3.0 + golang.org/x/text v0.5.0 golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac gopkg.in/urfave/cli.v1 v1.20.0 - gopkg.in/yaml.v2 v2.4.0 - sigs.k8s.io/yaml v1.3.0 ) require ( + github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect + github.com/btcsuite/btcd/btcutil v1.1.3 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0-20200627015759-01fd2de07837 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/deepmap/oapi-codegen v1.8.2 // indirect github.com/edsrzf/mmap-go v1.0.0 // indirect + github.com/fatih/color v1.13.0 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect @@ -67,10 +66,12 @@ require ( github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.5.9 // indirect + github.com/gorilla/mux v1.8.0 // indirect github.com/graph-gophers/graphql-go v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 // indirect github.com/hashicorp/go-hclog v1.2.2 // indirect github.com/hashicorp/go-plugin v1.4.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -93,6 +94,7 @@ require ( github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.1 // indirect github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 // indirect + github.com/pires/go-proxyproto v0.6.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/common v0.37.0 // indirect @@ -100,6 +102,7 @@ require ( github.com/prometheus/tsdb v0.10.0 // indirect github.com/rs/cors v1.7.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/afero v1.8.2 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.3.0 // indirect @@ -118,16 +121,17 @@ require ( go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect - go.uber.org/zap v1.23.0 // indirect + go.uber.org/zap v1.24.0 // indirect golang.org/x/exp v0.0.0-20220426173459-3bcf042a4bf5 // indirect - golang.org/x/net v0.1.0 // indirect - golang.org/x/term v0.1.0 // indirect + golang.org/x/net v0.4.0 // indirect + golang.org/x/term v0.3.0 // indirect gonum.org/v1/gonum v0.11.0 // indirect - google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c // indirect - google.golang.org/grpc v1.51.0-dev // indirect + google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 // indirect + google.golang.org/grpc v1.53.0-dev // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 718a7e5494..0b4e002aee 100644 --- a/go.sum +++ b/go.sum @@ -44,9 +44,12 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/VictoriaMetrics/fastcache v1.10.0 h1:5hDJnLsKLpnUEToub7ETuRu8RCkb40woBZAUiKonXzY= github.com/VictoriaMetrics/fastcache v1.10.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJaNxq6132xHICNP77w8= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -58,10 +61,8 @@ github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= -github.com/ava-labs/avalanche-network-runner-sdk v0.3.0 h1:TVi9JEdKNU/RevYZ9PyW4pULbEdS+KQDA9Ki2DUvuAs= -github.com/ava-labs/avalanche-network-runner-sdk v0.3.0/go.mod h1:SgKJvtqvgo/Bl/c8fxEHCLaSxEbzimYfBopcfrajxQk= -github.com/ava-labs/avalanchego v1.9.5 h1:0uykbcKFocUL7U7SO/PGrXSMLX9RSt8xo5rj84bI3YY= -github.com/ava-labs/avalanchego v1.9.5/go.mod h1:1f/z4CBcz/VhNlOTj607dQj5ZQQaZQO/RO8sEa0EgvA= +github.com/ava-labs/avalanchego v1.9.8 h1:5SHKqkWpBn9Pqxg2qpzDZ7FQqYFapzaCZwArapBdqAA= +github.com/ava-labs/avalanchego v1.9.8/go.mod h1:t9+R55TgRJxYCekRf/EicIjHBeeEQT04TQxpaF98+yM= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -69,10 +70,30 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= -github.com/btcsuite/btcd v0.23.1 h1:IB8cVQcC2X5mHbnfirLG5IZnkWYNTPlLZVrxUYSotbE= -github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= -github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.0 h1:V2/ZgjfDFIygAX3ZapeigkVBoVUtOJKSwrhZdlpSvaA= +github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= +github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= @@ -97,19 +118,23 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= +github.com/decred/dcrd/chaincfg/chainhash v1.0.2 h1:rt5Vlq/jM3ZawwiacWjPa+smINyLRN07EO0cNBV6DGU= +github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0-20200627015759-01fd2de07837 h1:g2cyFTu5FKWhCo7L4hVJ797Q506B4EywA7L9I6OebgA= +github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0-20200627015759-01fd2de07837/go.mod h1:J70FGZSbzsjecRTiTzER+3f1KZLNaXkuv+yeFTKoxM8= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU= github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= @@ -224,6 +249,8 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -255,8 +282,8 @@ github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= @@ -273,8 +300,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92Bcuy github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0 h1:kr3j8iIMR4ywO/O0rvksXaJvauGGCMg2zAZIiNZ9uIQ= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.12.0/go.mod h1:ummNFgdgLhhX7aIiy35vVmQNS0rWXknfPE0qe6fmFXg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0 h1:1JYBfzqrWPcCclBwxFCPAou9n+q86mfnu7NAeHfte7A= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.0/go.mod h1:YDZoGHuwE+ov0c8smSH49WLF3F2LaWnYYuDVd+EWrc0= github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/go-hclog v1.2.2 h1:ihRI7YFwcZdiSD7SIenIhHfQH3OuDvWerAUBZbeQS3M= @@ -316,8 +343,11 @@ github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mq github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -333,6 +363,7 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= @@ -403,19 +434,23 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= -github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= +github.com/onsi/ginkgo/v2 v2.6.1 h1:1xQPCjcqYw/J5LchOcp4/2q/jzJFjiAOc25chhnDw+Q= +github.com/onsi/ginkgo/v2 v2.6.1/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= -github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= +github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE= +github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= @@ -430,6 +465,8 @@ github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQm github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8= +github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -443,13 +480,14 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU= -github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= @@ -467,8 +505,8 @@ github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0ua github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic= github.com/prometheus/tsdb v0.10.0/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4= github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= -github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= -github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= +github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY= +github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= @@ -487,6 +525,8 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -521,6 +561,7 @@ github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= github.com/supranational/blst v0.3.11-0.20220920110316-f72618070295 h1:rVKS9JjtqE4/PscoIsP46sRnJhfq8YFbjlk0fUJTRnY= github.com/supranational/blst v0.3.11-0.20220920110316-f72618070295/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= @@ -578,8 +619,9 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/ go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= -go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -632,6 +674,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -661,6 +704,7 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -674,8 +718,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -739,10 +783,12 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -769,13 +815,13 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -785,8 +831,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -927,8 +973,8 @@ google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c h1:QgY/XxIAIeccR+Ca/rDdKubLIU9rcJ3xfy1DC/Wd2Oo= -google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 h1:jmIfw8+gSvXcZSgaFAGyInDXeWzUhvYH57G/5GKMn70= +google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -949,8 +995,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.51.0-dev h1:JIZpGUpbGAukP4rGiKJ/AnpK9BqMYV6Rdx94XWZckHY= -google.golang.org/grpc v1.51.0-dev/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.53.0-dev h1:Bi96+XIrXJLXPJUff19tRXb7mIijir7agn12zNMaPAg= +google.golang.org/grpc v1.53.0-dev/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1006,5 +1052,3 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/params/precompile_config_test.go b/params/precompile_config_test.go index bb77d912e1..1ced4ee9a4 100644 --- a/params/precompile_config_test.go +++ b/params/precompile_config_test.go @@ -64,6 +64,26 @@ func TestVerifyWithChainConfig(t *testing.T) { assert.ErrorContains(t, err, "disable should be [true]") } +func TestVerifyWithChainConfigAtNilTimestamp(t *testing.T) { + admins := []common.Address{{1}} + baseConfig := *SubnetEVMDefaultChainConfig + config := &baseConfig + config.PrecompileUpgrades = []PrecompileUpgrade{ + // this does NOT enable the precompile, so it should be upgradeable. + {Config: txallowlist.NewConfig(nil, nil, nil)}, + } + require.False(t, config.IsPrecompileEnabled(txallowlist.ContractAddress, common.Big0)) // check the precompile is not enabled. + config.PrecompileUpgrades = []PrecompileUpgrade{ + { + // enable TxAllowList at timestamp 5 + Config: txallowlist.NewConfig(big.NewInt(5), admins, nil), + }, + } + + // check this config is valid + require.NoError(t, config.Verify()) +} + func TestVerifyPrecompileUpgrades(t *testing.T) { admins := []common.Address{{1}} tests := []struct { @@ -103,9 +123,11 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { upgrades: []PrecompileUpgrade{ { Config: feemanager.NewConfig(big.NewInt(3), admins, nil, - &commontype.FeeConfig{ - GasLimit: big.NewInt(-1), - }), + func() *commontype.FeeConfig { + feeConfig := DefaultFeeConfig + feeConfig.GasLimit = big.NewInt(-1) + return &feeConfig + }()), }, }, expectedError: "gasLimit = -1 cannot be less than or equal to 0", @@ -115,9 +137,11 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { upgrades: []PrecompileUpgrade{ { Config: feemanager.NewConfig(big.NewInt(3), admins, nil, - &commontype.FeeConfig{ - GasLimit: big.NewInt(0), - }), + func() *commontype.FeeConfig { + feeConfig := DefaultFeeConfig + feeConfig.GasLimit = common.Big0 + return &feeConfig + }()), }, }, expectedError: "gasLimit = 0 cannot be less than or equal to 0", @@ -194,9 +218,11 @@ func TestVerifyPrecompiles(t *testing.T) { name: "invalid initial fee manager config", precompiles: ChainConfigPrecompiles{ feemanager.ConfigKey: feemanager.NewConfig(big.NewInt(3), admins, nil, - &commontype.FeeConfig{ - GasLimit: big.NewInt(-1), - }), + func() *commontype.FeeConfig { + feeConfig := DefaultFeeConfig + feeConfig.GasLimit = big.NewInt(-1) + return &feeConfig + }()), }, expectedError: "gasLimit = -1 cannot be less than or equal to 0", }, diff --git a/peer/client.go b/peer/client.go index 31fe2175a9..6a002d0e38 100644 --- a/peer/client.go +++ b/peer/client.go @@ -19,15 +19,19 @@ var ( // NetworkClient defines ability to send request / response through the Network type NetworkClient interface { - // RequestAny synchronously sends request to a randomly chosen peer with a + // SendAppRequestAny synchronously sends request to an arbitrary peer with a // node version greater than or equal to minVersion. // Returns response bytes, the ID of the chosen peer, and ErrRequestFailed if // the request should be retried. - RequestAny(minVersion *version.Application, request []byte) ([]byte, ids.NodeID, error) + SendAppRequestAny(minVersion *version.Application, request []byte) ([]byte, ids.NodeID, error) - // Request synchronously sends request to the selected nodeID + // SendAppRequest synchronously sends request to the selected nodeID // Returns response bytes, and ErrRequestFailed if the request should be retried. - Request(nodeID ids.NodeID, request []byte) ([]byte, error) + SendAppRequest(nodeID ids.NodeID, request []byte) ([]byte, error) + + // SendCrossChainRequest sends a request to a specific blockchain running on this node. + // Returns response bytes, and ErrRequestFailed if the request failed. + SendCrossChainRequest(chainID ids.ID, request []byte) ([]byte, error) // Gossip sends given gossip message to peers Gossip(gossip []byte) error @@ -51,13 +55,13 @@ func NewNetworkClient(network Network) NetworkClient { } } -// RequestAny synchronously sends request to a randomly chosen peer with a +// SendAppRequestAny synchronously sends request to an arbitrary peer with a // node version greater than or equal to minVersion. // Returns response bytes, the ID of the chosen peer, and ErrRequestFailed if // the request should be retried. -func (c *client) RequestAny(minVersion *version.Application, request []byte) ([]byte, ids.NodeID, error) { +func (c *client) SendAppRequestAny(minVersion *version.Application, request []byte) ([]byte, ids.NodeID, error) { waitingHandler := newWaitingResponseHandler() - nodeID, err := c.network.RequestAny(minVersion, request, waitingHandler) + nodeID, err := c.network.SendAppRequestAny(minVersion, request, waitingHandler) if err != nil { return nil, nodeID, err } @@ -68,11 +72,25 @@ func (c *client) RequestAny(minVersion *version.Application, request []byte) ([] return response, nodeID, nil } -// Request synchronously sends request to the specified nodeID +// SendAppRequest synchronously sends request to the specified nodeID +// Returns response bytes and ErrRequestFailed if the request should be retried. +func (c *client) SendAppRequest(nodeID ids.NodeID, request []byte) ([]byte, error) { + waitingHandler := newWaitingResponseHandler() + if err := c.network.SendAppRequest(nodeID, request, waitingHandler); err != nil { + return nil, err + } + response := <-waitingHandler.responseChan + if waitingHandler.failed { + return nil, ErrRequestFailed + } + return response, nil +} + +// SendCrossChainRequest synchronously sends request to the specified chainID // Returns response bytes and ErrRequestFailed if the request should be retried. -func (c *client) Request(nodeID ids.NodeID, request []byte) ([]byte, error) { +func (c *client) SendCrossChainRequest(chainID ids.ID, request []byte) ([]byte, error) { waitingHandler := newWaitingResponseHandler() - if err := c.network.Request(nodeID, request, waitingHandler); err != nil { + if err := c.network.SendCrossChainRequest(chainID, request, waitingHandler); err != nil { return nil, err } response := <-waitingHandler.responseChan diff --git a/peer/network.go b/peer/network.go index 377235dad3..6d0ebe5990 100644 --- a/peer/network.go +++ b/peer/network.go @@ -30,6 +30,7 @@ const minRequestHandlingDuration = 100 * time.Millisecond var ( errAcquiringSemaphore = errors.New("error acquiring semaphore") + errExpiredRequest = errors.New("expired request") _ Network = &network{} _ validators.Connector = &network{} _ common.AppHandler = &network{} @@ -39,18 +40,21 @@ type Network interface { validators.Connector common.AppHandler - // RequestAny synchronously sends request to a randomly chosen peer with a + // SendAppRequestAny synchronously sends request to an arbitrary peer with a // node version greater than or equal to minVersion. // Returns the ID of the chosen peer, and an error if the request could not // be sent to a peer with the desired [minVersion]. - RequestAny(minVersion *version.Application, message []byte, handler message.ResponseHandler) (ids.NodeID, error) + SendAppRequestAny(minVersion *version.Application, message []byte, handler message.ResponseHandler) (ids.NodeID, error) - // Request sends message to given nodeID, notifying handler when there's a response or timeout - Request(nodeID ids.NodeID, message []byte, handler message.ResponseHandler) error + // SendAppRequest sends message to given nodeID, notifying handler when there's a response or timeout + SendAppRequest(nodeID ids.NodeID, message []byte, handler message.ResponseHandler) error // Gossip sends given gossip message to peers Gossip(gossip []byte) error + // SendCrossChainRequest sends a message to given chainID notifying handler when there's a response or timeout + SendCrossChainRequest(chainID ids.ID, message []byte, handler message.ResponseHandler) error + // Shutdown stops all peer channel listeners and marks the node to have stopped // n.Start() can be called again but the peers will have to be reconnected // by calling OnPeerConnected for each peer @@ -62,6 +66,9 @@ type Network interface { // SetRequestHandler sets the provided request handler as the request handler SetRequestHandler(handler message.RequestHandler) + // SetCrossChainHandler sets the provided cross chain request handler as the cross chain request handler + SetCrossChainRequestHandler(handler message.CrossChainRequestHandler) + // Size returns the size of the network in number of connected peers Size() uint32 @@ -77,74 +84,82 @@ type network struct { self ids.NodeID // NodeID of this node requestIDGen uint32 // requestID counter used to track outbound requests outstandingRequestHandlers map[uint32]message.ResponseHandler // maps avalanchego requestID => message.ResponseHandler - activeRequests *semaphore.Weighted // controls maximum number of active outbound requests + activeAppRequests *semaphore.Weighted // controls maximum number of active outbound requests + activeCrossChainRequests *semaphore.Weighted // controls maximum number of active outbound cross chain requests appSender common.AppSender // avalanchego AppSender for sending messages codec codec.Manager // Codec used for parsing messages - requestHandler message.RequestHandler // maps request type => handler + crossChainCodec codec.Manager // Codec used for parsing cross chain messages + appRequestHandler message.RequestHandler // maps request type => handler + crossChainRequestHandler message.CrossChainRequestHandler // maps cross chain request type => handler gossipHandler message.GossipHandler // maps gossip type => handler peers *peerTracker // tracking of peers & bandwidth - stats stats.RequestHandlerStats // Provide request handler metrics + appStats stats.RequestHandlerStats // Provide request handler metrics + crossChainStats stats.RequestHandlerStats // Provide cross chain request handler metrics } -func NewNetwork(appSender common.AppSender, codec codec.Manager, self ids.NodeID, maxActiveRequests int64) Network { +func NewNetwork(appSender common.AppSender, codec codec.Manager, crossChainCodec codec.Manager, self ids.NodeID, maxActiveAppRequests int64, maxActiveCrossChainRequests int64) Network { return &network{ appSender: appSender, codec: codec, + crossChainCodec: crossChainCodec, self: self, outstandingRequestHandlers: make(map[uint32]message.ResponseHandler), - activeRequests: semaphore.NewWeighted(maxActiveRequests), + activeAppRequests: semaphore.NewWeighted(maxActiveAppRequests), + activeCrossChainRequests: semaphore.NewWeighted(maxActiveCrossChainRequests), gossipHandler: message.NoopMempoolGossipHandler{}, - requestHandler: message.NoopRequestHandler{}, + appRequestHandler: message.NoopRequestHandler{}, + crossChainRequestHandler: message.NoopCrossChainRequestHandler{}, peers: NewPeerTracker(), - stats: stats.NewRequestHandlerStats(), + appStats: stats.NewRequestHandlerStats(), + crossChainStats: stats.NewCrossChainRequestHandlerStats(), } } -// RequestAny synchronously sends request to a randomly chosen peer with a +// SendAppRequestAny synchronously sends request to an arbitrary peer with a // node version greater than or equal to minVersion. If minVersion is nil, // the request will be sent to any peer regardless of their version. // Returns the ID of the chosen peer, and an error if the request could not // be sent to a peer with the desired [minVersion]. -func (n *network) RequestAny(minVersion *version.Application, request []byte, handler message.ResponseHandler) (ids.NodeID, error) { - // Take a slot from total [activeRequests] and block until a slot becomes available. - if err := n.activeRequests.Acquire(context.Background(), 1); err != nil { +func (n *network) SendAppRequestAny(minVersion *version.Application, request []byte, handler message.ResponseHandler) (ids.NodeID, error) { + // Take a slot from total [activeAppRequests] and block until a slot becomes available. + if err := n.activeAppRequests.Acquire(context.Background(), 1); err != nil { return ids.EmptyNodeID, errAcquiringSemaphore } n.lock.Lock() defer n.lock.Unlock() if nodeID, ok := n.peers.GetAnyPeer(minVersion); ok { - return nodeID, n.request(nodeID, request, handler) + return nodeID, n.sendAppRequest(nodeID, request, handler) } - n.activeRequests.Release(1) + n.activeAppRequests.Release(1) return ids.EmptyNodeID, fmt.Errorf("no peers found matching version %s out of %d peers", minVersion, n.peers.Size()) } -// Request sends request message bytes to specified nodeID, notifying the responseHandler on response or failure -func (n *network) Request(nodeID ids.NodeID, request []byte, responseHandler message.ResponseHandler) error { +// SendAppRequest sends request message bytes to specified nodeID, notifying the responseHandler on response or failure +func (n *network) SendAppRequest(nodeID ids.NodeID, request []byte, responseHandler message.ResponseHandler) error { if nodeID == ids.EmptyNodeID { return fmt.Errorf("cannot send request to empty nodeID, nodeID=%s, requestLen=%d", nodeID, len(request)) } - // Take a slot from total [activeRequests] and block until a slot becomes available. - if err := n.activeRequests.Acquire(context.Background(), 1); err != nil { + // Take a slot from total [activeAppRequests] and block until a slot becomes available. + if err := n.activeAppRequests.Acquire(context.Background(), 1); err != nil { return errAcquiringSemaphore } n.lock.Lock() defer n.lock.Unlock() - return n.request(nodeID, request, responseHandler) + return n.sendAppRequest(nodeID, request, responseHandler) } -// request sends request message bytes to specified nodeID and adds [responseHandler] to [outstandingRequestHandlers] +// sendAppRequest sends request message bytes to specified nodeID and adds [responseHandler] to [outstandingRequestHandlers] // so that it can be invoked when the network receives either a response or failure message. // Assumes [nodeID] is never [self] since we guarantee [self] will not be added to the [peers] map. // Releases active requests semaphore if there was an error in sending the request // Returns an error if [appSender] is unable to make the request. // Assumes write lock is held -func (n *network) request(nodeID ids.NodeID, request []byte, responseHandler message.ResponseHandler) error { +func (n *network) sendAppRequest(nodeID ids.NodeID, request []byte, responseHandler message.ResponseHandler) error { log.Debug("sending request to peer", "nodeID", nodeID, "requestLen", len(request)) n.peers.TrackPeer(nodeID) @@ -157,12 +172,10 @@ func (n *network) request(nodeID ids.NodeID, request []byte, responseHandler mes nodeIDs := set.NewSet[ids.NodeID](1) nodeIDs.Add(nodeID) - // send app request to the peer - // on failure: release the activeRequests slot, mark message as processed and return fatal error // Send app request to [nodeID]. - // On failure, release the slot from active requests and [outstandingRequestHandlers]. + // On failure, release the slot from [activeAppRequests] and delete request from [outstandingRequestHandlers] if err := n.appSender.SendAppRequest(context.TODO(), nodeIDs, requestID, request); err != nil { - n.activeRequests.Release(1) + n.activeAppRequests.Release(1) delete(n.outstandingRequestHandlers, requestID) return err } @@ -171,16 +184,116 @@ func (n *network) request(nodeID ids.NodeID, request []byte, responseHandler mes return nil } -func (n *network) CrossChainAppRequest(_ context.Context, requestingChainID ids.ID, requestID uint32, deadline time.Time, request []byte) error { +// SendCrossChainRequest sends request message bytes to specified chainID and adds [handler] to [outstandingRequestHandlers] +// so that it can be invoked when the network receives either a response or failure message. +// Returns an error if [appSender] is unable to make the request. +func (n *network) SendCrossChainRequest(chainID ids.ID, request []byte, handler message.ResponseHandler) error { + // Take a slot from total [activeCrossChainRequests] and block until a slot becomes available. + if err := n.activeCrossChainRequests.Acquire(context.Background(), 1); err != nil { + return errAcquiringSemaphore + } + + n.lock.Lock() + defer n.lock.Unlock() + + // generate requestID + requestID := n.requestIDGen + n.requestIDGen++ + + n.outstandingRequestHandlers[requestID] = handler + + // Send cross chain request to [chainID]. + // On failure, release the slot from [activeCrossChainRequests] and delete request from [outstandingRequestHandlers]. + if err := n.appSender.SendCrossChainAppRequest(context.TODO(), chainID, requestID, request); err != nil { + n.activeCrossChainRequests.Release(1) + delete(n.outstandingRequestHandlers, requestID) + return err + } + + log.Debug("sent request message to chain", "chainID", chainID, "crossChainRequestID", requestID) return nil } -func (n *network) CrossChainAppRequestFailed(_ context.Context, respondingChainID ids.ID, requestID uint32) error { - return nil +// CrossChainAppRequest notifies the VM when another chain in the network requests for data. +// Send a CrossChainAppResponse to [chainID] in response to a valid message using the same +// [requestID] before the deadline. +func (n *network) CrossChainAppRequest(ctx context.Context, requestingChainID ids.ID, requestID uint32, deadline time.Time, request []byte) error { + log.Debug("received CrossChainAppRequest from chain", "requestingChainID", requestingChainID, "requestID", requestID, "requestLen", len(request)) + + var req message.CrossChainRequest + if _, err := n.crossChainCodec.Unmarshal(request, &req); err != nil { + log.Debug("failed to unmarshal CrossChainAppRequest", "requestingChainID", requestingChainID, "requestID", requestID, "requestLen", len(request), "err", err) + return nil + } + + bufferedDeadline, err := calculateTimeUntilDeadline(deadline, n.crossChainStats) + if err != nil { + log.Debug("deadline to process CrossChainAppRequest has expired, skipping", "requestingChainID", requestingChainID, "requestID", requestID, "err", err) + return nil + } + + log.Debug("processing incoming CrossChainAppRequest", "requestingChainID", requestingChainID, "requestID", requestID, "req", req) + handleCtx, cancel := context.WithDeadline(context.Background(), bufferedDeadline) + defer cancel() + + responseBytes, err := req.Handle(handleCtx, requestingChainID, requestID, n.crossChainRequestHandler) + switch { + case err != nil && err != context.DeadlineExceeded: + return err // Return a fatal error + case responseBytes != nil: + return n.appSender.SendCrossChainAppResponse(ctx, requestingChainID, requestID, responseBytes) // Propagate fatal error + default: + return nil + } } -func (n *network) CrossChainAppResponse(_ context.Context, respondingChainID ids.ID, requestID uint32, response []byte) error { - return nil +// CrossChainAppRequestFailed can be called by the avalanchego -> VM in following cases: +// - respondingChain doesn't exist +// - invalid CrossChainAppResponse from respondingChain +// - invalid CrossChainRequest was sent to respondingChain +// - request times out before a response is provided +// If [requestID] is not known, this function will emit a log and return a nil error. +// If the response handler returns an error it is propagated as a fatal error. +func (n *network) CrossChainAppRequestFailed(ctx context.Context, respondingChainID ids.ID, requestID uint32) error { + n.lock.Lock() + defer n.lock.Unlock() + + log.Debug("received CrossChainAppRequestFailed from chain", "respondingChainID", respondingChainID, "requestID", requestID) + + handler, exists := n.markRequestFulfilled(requestID) + if !exists { + // Should never happen since the engine should be managing outstanding requests + log.Error("received CrossChainAppRequestFailed to unknown request", "respondingChainID", respondingChainID, "requestID", requestID) + return nil + } + + // We must release the slot + n.activeCrossChainRequests.Release(1) + + return handler.OnFailure() +} + +// CrossChainAppResponse is invoked when there is a +// response received from [respondingChainID] regarding a request the VM sent out +// If [requestID] is not known, this function will emit a log and return a nil error. +// If the response handler returns an error it is propagated as a fatal error. +func (n *network) CrossChainAppResponse(ctx context.Context, respondingChainID ids.ID, requestID uint32, response []byte) error { + n.lock.Lock() + defer n.lock.Unlock() + + log.Debug("received CrossChainAppResponse from responding chain", "respondingChainID", respondingChainID, "requestID", requestID) + + handler, exists := n.markRequestFulfilled(requestID) + if !exists { + // Should never happen since the engine should be managing outstanding requests + log.Error("received CrossChainAppResponse to unknown request", "respondingChainID", respondingChainID, "requestID", requestID, "responseLen", len(response)) + return nil + } + + // We must release the slot + n.activeCrossChainRequests.Release(1) + + return handler.OnResponse(response) } // AppRequest is called by avalanchego -> VM when there is an incoming AppRequest from a peer @@ -189,9 +302,6 @@ func (n *network) CrossChainAppResponse(_ context.Context, respondingChainID ids // sends a response back to the sender if length of response returned by the handler is >0 // expects the deadline to not have been passed func (n *network) AppRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, deadline time.Time, request []byte) error { - n.lock.RLock() - defer n.lock.RUnlock() - log.Debug("received AppRequest from node", "nodeID", nodeID, "requestID", requestID, "requestLen", len(request)) var req message.Request @@ -200,20 +310,9 @@ func (n *network) AppRequest(ctx context.Context, nodeID ids.NodeID, requestID u return nil } - // calculate how much time is left until the deadline - timeTillDeadline := time.Until(deadline) - n.stats.UpdateTimeUntilDeadline(timeTillDeadline) - - // bufferedDeadline is half the time till actual deadline so that the message has a reasonable chance - // of completing its processing and sending the response to the peer. - timeTillDeadline = time.Duration(timeTillDeadline.Nanoseconds() / 2) - bufferedDeadline := time.Now().Add(timeTillDeadline) - - // check if we have enough time to handle this request - if time.Until(bufferedDeadline) < minRequestHandlingDuration { - // Drop the request if we already missed the deadline to respond. - log.Debug("deadline to process AppRequest has expired, skipping", "nodeID", nodeID, "requestID", requestID, "req", req) - n.stats.IncDeadlineDroppedRequest() + bufferedDeadline, err := calculateTimeUntilDeadline(deadline, n.appStats) + if err != nil { + log.Debug("deadline to process AppRequest has expired, skipping", "nodeID", nodeID, "requestID", requestID, "err", err) return nil } @@ -223,7 +322,7 @@ func (n *network) AppRequest(ctx context.Context, nodeID ids.NodeID, requestID u handleCtx, cancel := context.WithDeadline(context.Background(), bufferedDeadline) defer cancel() - responseBytes, err := req.Handle(handleCtx, nodeID, requestID, n.requestHandler) + responseBytes, err := req.Handle(handleCtx, nodeID, requestID, n.appRequestHandler) switch { case err != nil && err != context.DeadlineExceeded: return err // Return a fatal error @@ -244,48 +343,77 @@ func (n *network) AppResponse(_ context.Context, nodeID ids.NodeID, requestID ui log.Debug("received AppResponse from peer", "nodeID", nodeID, "requestID", requestID) - handler, exists := n.getRequestHandler(requestID) + handler, exists := n.markRequestFulfilled(requestID) if !exists { // Should never happen since the engine should be managing outstanding requests - log.Error("received response to unknown request", "nodeID", nodeID, "requestID", requestID, "responseLen", len(response)) + log.Error("received AppResponse to unknown request", "nodeID", nodeID, "requestID", requestID, "responseLen", len(response)) return nil } - return handler.OnResponse(nodeID, requestID, response) + // We must release the slot + n.activeAppRequests.Release(1) + + return handler.OnResponse(response) } // AppRequestFailed can be called by the avalanchego -> VM in following cases: // - node is benched // - failed to send message to [nodeID] due to a network issue -// - timeout +// - request times out before a response is provided // error returned by this function is expected to be treated as fatal by the engine // returns error only when the response handler returns an error func (n *network) AppRequestFailed(_ context.Context, nodeID ids.NodeID, requestID uint32) error { n.lock.Lock() defer n.lock.Unlock() + log.Debug("received AppRequestFailed from peer", "nodeID", nodeID, "requestID", requestID) - handler, exists := n.getRequestHandler(requestID) + handler, exists := n.markRequestFulfilled(requestID) if !exists { // Should never happen since the engine should be managing outstanding requests - log.Error("received request failed to unknown request", "nodeID", nodeID, "requestID", requestID) + log.Error("received AppRequestFailed to unknown request", "nodeID", nodeID, "requestID", requestID) return nil } - return handler.OnFailure(nodeID, requestID) + // We must release the slot + n.activeAppRequests.Release(1) + + return handler.OnFailure() +} + +// calculateTimeUntilDeadline calculates the time until deadline and drops it if we missed he deadline to response. +// This function updates metrics for both app requests and cross chain requests. +// This is called by either [AppRequest] or [CrossChainAppRequest]. +func calculateTimeUntilDeadline(deadline time.Time, stats stats.RequestHandlerStats) (time.Time, error) { + // calculate how much time is left until the deadline + timeTillDeadline := time.Until(deadline) + stats.UpdateTimeUntilDeadline(timeTillDeadline) + + // bufferedDeadline is half the time till actual deadline so that the message has a reasonable chance + // of completing its processing and sending the response to the peer. + bufferedDeadline := time.Now().Add(timeTillDeadline / 2) + + // check if we have enough time to handle this request + if time.Until(bufferedDeadline) < minRequestHandlingDuration { + // Drop the request if we already missed the deadline to respond. + stats.IncDeadlineDroppedRequest() + return time.Time{}, errExpiredRequest + } + + return bufferedDeadline, nil } -// getRequestHandler fetches the handler for [requestID] and marks the request with [requestID] as having been fulfilled. +// markRequestFulfilled fetches the handler for [requestID] and marks the request with [requestID] as having been fulfilled. // This is called by either [AppResponse] or [AppRequestFailed]. -// assumes that the write lock is held. -func (n *network) getRequestHandler(requestID uint32) (message.ResponseHandler, bool) { +// Assumes that the write lock is held. +func (n *network) markRequestFulfilled(requestID uint32) (message.ResponseHandler, bool) { handler, exists := n.outstandingRequestHandlers[requestID] if !exists { return nil, false } - // mark message as processed, release activeRequests slot + // mark message as processed delete(n.outstandingRequestHandlers, requestID) - n.activeRequests.Release(1) + return handler, true } @@ -354,7 +482,14 @@ func (n *network) SetRequestHandler(handler message.RequestHandler) { n.lock.Lock() defer n.lock.Unlock() - n.requestHandler = handler + n.appRequestHandler = handler +} + +func (n *network) SetCrossChainRequestHandler(handler message.CrossChainRequestHandler) { + n.lock.Lock() + defer n.lock.Unlock() + + n.crossChainRequestHandler = handler } func (n *network) Size() uint32 { diff --git a/peer/network_test.go b/peer/network_test.go index c7ba5187f9..e589a48508 100644 --- a/peer/network_test.go +++ b/peer/network_test.go @@ -22,6 +22,8 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/version" "github.com/stretchr/testify/assert" + + ethcommon "github.com/ethereum/go-ethereum/common" ) var ( @@ -43,11 +45,14 @@ var ( _ common.AppSender = testAppSender{} _ message.GossipMessage = HelloGossip{} _ message.GossipHandler = &testGossipHandler{} + + _ message.CrossChainRequest = &ExampleCrossChainRequest{} + _ message.CrossChainRequestHandler = &testCrossChainHandler{} ) func TestNetworkDoesNotConnectToItself(t *testing.T) { selfNodeID := ids.GenerateTestNodeID() - n := NewNetwork(nil, nil, selfNodeID, 1) + n := NewNetwork(nil, nil, nil, selfNodeID, 1, 1) assert.NoError(t, n.Connected(context.Background(), selfNodeID, defaultPeerVersion)) assert.EqualValues(t, 0, n.Size()) } @@ -82,7 +87,8 @@ func TestRequestAnyRequestsRoutingAndResponse(t *testing.T) { } codecManager := buildCodec(t, HelloRequest{}, HelloResponse{}) - net = NewNetwork(sender, codecManager, ids.EmptyNodeID, 16) + crossChainCodecManager := buildCodec(t, ExampleCrossChainRequest{}, ExampleCrossChainResponse{}) + net = NewNetwork(sender, codecManager, crossChainCodecManager, ids.EmptyNodeID, 16, 16) net.SetRequestHandler(&HelloGreetingRequestHandler{codec: codecManager}) client := NewNetworkClient(net) nodeID := ids.GenerateTestNodeID() @@ -104,7 +110,7 @@ func TestRequestAnyRequestsRoutingAndResponse(t *testing.T) { defer wg.Done() requestBytes, err := message.RequestToBytes(codecManager, requestMessage) assert.NoError(t, err) - responseBytes, _, err := client.RequestAny(defaultPeerVersion, requestBytes) + responseBytes, _, err := client.SendAppRequestAny(defaultPeerVersion, requestBytes) assert.NoError(t, err) assert.NotNil(t, responseBytes) @@ -156,7 +162,8 @@ func TestRequestRequestsRoutingAndResponse(t *testing.T) { } codecManager := buildCodec(t, HelloRequest{}, HelloResponse{}) - net = NewNetwork(sender, codecManager, ids.EmptyNodeID, 16) + crossChainCodecManager := buildCodec(t, ExampleCrossChainRequest{}, ExampleCrossChainResponse{}) + net = NewNetwork(sender, codecManager, crossChainCodecManager, ids.EmptyNodeID, 16, 16) net.SetRequestHandler(&HelloGreetingRequestHandler{codec: codecManager}) client := NewNetworkClient(net) @@ -188,7 +195,7 @@ func TestRequestRequestsRoutingAndResponse(t *testing.T) { defer wg.Done() requestBytes, err := message.RequestToBytes(codecManager, requestMessage) assert.NoError(t, err) - responseBytes, err := client.Request(nodeID, requestBytes) + responseBytes, err := client.SendAppRequest(nodeID, requestBytes) assert.NoError(t, err) assert.NotNil(t, responseBytes) @@ -210,7 +217,7 @@ func TestRequestRequestsRoutingAndResponse(t *testing.T) { } // ensure empty nodeID is not allowed - _, err := client.Request(ids.EmptyNodeID, []byte("hello there")) + _, err := client.SendAppRequest(ids.EmptyNodeID, []byte("hello there")) assert.Error(t, err) assert.Contains(t, err.Error(), "cannot send request to empty nodeID") } @@ -242,7 +249,8 @@ func TestRequestMinVersion(t *testing.T) { } // passing nil as codec works because the net.AppRequest is never called - net = NewNetwork(sender, codecManager, ids.EmptyNodeID, 1) + crossChainCodecManager := buildCodec(t, ExampleCrossChainRequest{}, ExampleCrossChainResponse{}) + net = NewNetwork(sender, codecManager, crossChainCodecManager, ids.EmptyNodeID, 1, 16) client := NewNetworkClient(net) requestMessage := TestMessage{Message: "this is a request"} requestBytes, err := message.RequestToBytes(codecManager, requestMessage) @@ -260,7 +268,7 @@ func TestRequestMinVersion(t *testing.T) { ) // ensure version does not match - responseBytes, _, err := client.RequestAny( + responseBytes, _, err := client.SendAppRequestAny( &version.Application{ Major: 2, Minor: 0, @@ -272,7 +280,7 @@ func TestRequestMinVersion(t *testing.T) { assert.Nil(t, responseBytes) // ensure version matches and the request goes through - responseBytes, _, err = client.RequestAny(defaultPeerVersion, requestBytes) + responseBytes, _, err = client.SendAppRequestAny(defaultPeerVersion, requestBytes) assert.NoError(t, err) var response TestMessage @@ -296,6 +304,7 @@ func TestOnRequestHonoursDeadline(t *testing.T) { } codecManager := buildCodec(t, TestMessage{}) + crossChainCodecManager := buildCodec(t, ExampleCrossChainRequest{}, ExampleCrossChainResponse{}) requestBytes, err := marshalStruct(codecManager, TestMessage{Message: "hello there"}) assert.NoError(t, err) @@ -303,7 +312,8 @@ func TestOnRequestHonoursDeadline(t *testing.T) { requestHandler := &testRequestHandler{ processingDuration: 500 * time.Millisecond, } - net = NewNetwork(sender, codecManager, ids.EmptyNodeID, 1) + + net = NewNetwork(sender, codecManager, crossChainCodecManager, ids.EmptyNodeID, 1, 1) net.SetRequestHandler(requestHandler) nodeID := ids.GenerateTestNodeID() @@ -323,6 +333,7 @@ func TestOnRequestHonoursDeadline(t *testing.T) { func TestGossip(t *testing.T) { codecManager := buildCodec(t, HelloGossip{}) + crossChainCodecManager := buildCodec(t, ExampleCrossChainRequest{}, ExampleCrossChainResponse{}) nodeID := ids.GenerateTestNodeID() var clientNetwork Network @@ -342,7 +353,7 @@ func TestGossip(t *testing.T) { } gossipHandler := &testGossipHandler{} - clientNetwork = NewNetwork(sender, codecManager, ids.EmptyNodeID, 1) + clientNetwork = NewNetwork(sender, codecManager, crossChainCodecManager, ids.EmptyNodeID, 1, 1) clientNetwork.SetGossipHandler(gossipHandler) assert.NoError(t, clientNetwork.Connected(context.Background(), nodeID, defaultPeerVersion)) @@ -363,12 +374,13 @@ func TestGossip(t *testing.T) { func TestHandleInvalidMessages(t *testing.T) { codecManager := buildCodec(t, HelloGossip{}, TestMessage{}) + crossChainCodecManager := buildCodec(t, ExampleCrossChainRequest{}, ExampleCrossChainResponse{}) nodeID := ids.GenerateTestNodeID() requestID := uint32(1) sender := testAppSender{} - clientNetwork := NewNetwork(sender, codecManager, ids.EmptyNodeID, 1) + clientNetwork := NewNetwork(sender, codecManager, crossChainCodecManager, ids.EmptyNodeID, 1, 1) clientNetwork.SetGossipHandler(message.NoopMempoolGossipHandler{}) clientNetwork.SetRequestHandler(&testRequestHandler{}) @@ -412,12 +424,13 @@ func TestHandleInvalidMessages(t *testing.T) { func TestNetworkPropagatesRequestHandlerError(t *testing.T) { codecManager := buildCodec(t, TestMessage{}) + crossChainCodecManager := buildCodec(t, ExampleCrossChainRequest{}, ExampleCrossChainResponse{}) nodeID := ids.GenerateTestNodeID() requestID := uint32(1) sender := testAppSender{} - clientNetwork := NewNetwork(sender, codecManager, ids.EmptyNodeID, 1) + clientNetwork := NewNetwork(sender, codecManager, crossChainCodecManager, ids.EmptyNodeID, 1, 1) clientNetwork.SetGossipHandler(message.NoopMempoolGossipHandler{}) clientNetwork.SetRequestHandler(&testRequestHandler{err: errors.New("fail")}) // Return an error from the request handler @@ -433,6 +446,125 @@ func TestNetworkPropagatesRequestHandlerError(t *testing.T) { assert.Error(t, clientNetwork.AppRequest(context.Background(), nodeID, requestID, time.Now().Add(time.Second), requestMessage)) } +func TestCrossChainAppRequest(t *testing.T) { + var net Network + codecManager := buildCodec(t, TestMessage{}) + crossChainCodecManager := buildCodec(t, ExampleCrossChainRequest{}, ExampleCrossChainResponse{}) + + sender := testAppSender{ + sendCrossChainAppRequestFn: func(requestingChainID ids.ID, requestID uint32, requestBytes []byte) error { + go func() { + if err := net.CrossChainAppRequest(context.Background(), requestingChainID, requestID, time.Now().Add(5*time.Second), requestBytes); err != nil { + panic(err) + } + }() + return nil + }, + sendCrossChainAppResponseFn: func(respondingChainID ids.ID, requestID uint32, responseBytes []byte) error { + go func() { + if err := net.CrossChainAppResponse(context.Background(), respondingChainID, requestID, responseBytes); err != nil { + panic(err) + } + }() + return nil + }, + } + + net = NewNetwork(sender, codecManager, crossChainCodecManager, ids.EmptyNodeID, 1, 1) + net.SetCrossChainRequestHandler(&testCrossChainHandler{codec: crossChainCodecManager}) + client := NewNetworkClient(net) + + exampleCrossChainRequest := ExampleCrossChainRequest{ + Message: "hello this is an example request", + } + + crossChainRequest, err := buildCrossChainRequest(crossChainCodecManager, exampleCrossChainRequest) + assert.NoError(t, err) + + chainID := ids.ID(ethcommon.BytesToHash([]byte{1, 2, 3, 4, 5})) + responseBytes, err := client.SendCrossChainRequest(chainID, crossChainRequest) + assert.NoError(t, err) + + var response ExampleCrossChainResponse + if _, err = crossChainCodecManager.Unmarshal(responseBytes, &response); err != nil { + t.Fatal("unexpected error during unmarshal", err) + } + assert.Equal(t, "this is an example response", response.Response) +} + +func TestCrossChainRequestRequestsRoutingAndResponse(t *testing.T) { + var ( + callNum uint32 + senderWg sync.WaitGroup + net Network + ) + + sender := testAppSender{ + sendCrossChainAppRequestFn: func(requestingChainID ids.ID, requestID uint32, requestBytes []byte) error { + senderWg.Add(1) + go func() { + defer senderWg.Done() + if err := net.CrossChainAppRequest(context.Background(), requestingChainID, requestID, time.Now().Add(5*time.Second), requestBytes); err != nil { + panic(err) + } + }() + return nil + }, + sendCrossChainAppResponseFn: func(respondingChainID ids.ID, requestID uint32, responseBytes []byte) error { + senderWg.Add(1) + go func() { + defer senderWg.Done() + if err := net.CrossChainAppResponse(context.Background(), respondingChainID, requestID, responseBytes); err != nil { + panic(err) + } + atomic.AddUint32(&callNum, 1) + }() + return nil + }, + } + + codecManager := buildCodec(t, TestMessage{}) + crossChainCodecManager := buildCodec(t, ExampleCrossChainRequest{}, ExampleCrossChainResponse{}) + net = NewNetwork(sender, codecManager, crossChainCodecManager, ids.EmptyNodeID, 1, 1) + net.SetCrossChainRequestHandler(&testCrossChainHandler{codec: crossChainCodecManager}) + client := NewNetworkClient(net) + + exampleCrossChainRequest := ExampleCrossChainRequest{ + Message: "hello this is an example request", + } + + chainID := ids.ID(ethcommon.BytesToHash([]byte{1, 2, 3, 4, 5})) + defer net.Shutdown() + + totalRequests := 500 + numCallsPerRequest := 1 // on sending response + totalCalls := totalRequests * numCallsPerRequest + + var requestWg sync.WaitGroup + requestWg.Add(totalCalls) + + for i := 0; i < totalCalls; i++ { + go func() { + defer requestWg.Done() + crossChainRequest, err := buildCrossChainRequest(crossChainCodecManager, exampleCrossChainRequest) + assert.NoError(t, err) + responseBytes, err := client.SendCrossChainRequest(chainID, crossChainRequest) + assert.NoError(t, err) + assert.NotNil(t, responseBytes) + + var response ExampleCrossChainResponse + if _, err = crossChainCodecManager.Unmarshal(responseBytes, &response); err != nil { + panic(fmt.Errorf("unexpected error during unmarshal: %w", err)) + } + assert.Equal(t, "this is an example response", response.Response) + }() + } + + requestWg.Wait() + senderWg.Wait() + assert.Equal(t, totalCalls, int(atomic.LoadUint32(&callNum))) +} + func buildCodec(t *testing.T, types ...interface{}) codec.Manager { codecManager := codec.NewDefaultManager() c := linearcodec.NewDefault() @@ -453,6 +585,10 @@ func buildGossip(codec codec.Manager, msg message.GossipMessage) ([]byte, error) return codec.Marshal(message.Version, &msg) } +func buildCrossChainRequest(codec codec.Manager, msg message.CrossChainRequest) ([]byte, error) { + return codec.Marshal(message.Version, &msg) +} + type testAppSender struct { sendCrossChainAppRequestFn func(ids.ID, uint32, []byte) error sendCrossChainAppResponseFn func(ids.ID, uint32, []byte) error @@ -600,3 +736,32 @@ func (r *testRequestHandler) handleTestRequest(ctx context.Context, _ ids.NodeID } return r.response, r.err } + +type ExampleCrossChainRequest struct { + Message string `serialize:"true"` +} + +func (e ExampleCrossChainRequest) Handle(ctx context.Context, requestingChainID ids.ID, requestID uint32, handler message.CrossChainRequestHandler) ([]byte, error) { + return handler.(*testCrossChainHandler).HandleCrossChainRequest(ctx, requestingChainID, requestID, e) +} + +func (e ExampleCrossChainRequest) String() string { + return fmt.Sprintf("TestMessage(%s)", e.Message) +} + +type ExampleCrossChainResponse struct { + Response string `serialize:"true"` +} + +type TestCrossChainRequestHandler interface { + HandleCrossChainRequest(ctx context.Context, requestingchainID ids.ID, requestID uint32, exampleRequest message.CrossChainRequest) ([]byte, error) +} + +type testCrossChainHandler struct { + message.CrossChainRequestHandler + codec codec.Manager +} + +func (t *testCrossChainHandler) HandleCrossChainRequest(ctx context.Context, requestingChainID ids.ID, requestID uint32, exampleRequest message.CrossChainRequest) ([]byte, error) { + return t.codec.Marshal(message.Version, ExampleCrossChainResponse{Response: "this is an example response"}) +} diff --git a/peer/stats/stats.go b/peer/stats/stats.go index 2c1eea6713..e29a26e614 100644 --- a/peer/stats/stats.go +++ b/peer/stats/stats.go @@ -9,8 +9,7 @@ import ( "github.com/ava-labs/subnet-evm/metrics" ) -// RequestHandlerStats provides the interface for metrics on request handling. -// Since we drop +// RequestHandlerStats provides the interface for metrics for both app requests and cross chain requests. type RequestHandlerStats interface { UpdateTimeUntilDeadline(duration time.Duration) IncDeadlineDroppedRequest() @@ -35,3 +34,10 @@ func NewRequestHandlerStats() RequestHandlerStats { droppedRequests: metrics.GetOrRegisterCounter("net_req_deadline_dropped", nil), } } + +func NewCrossChainRequestHandlerStats() RequestHandlerStats { + return &requestHandlerStats{ + timeUntilDeadline: metrics.GetOrRegisterTimer("net_cross_chain_req_time_until_deadline", nil), + droppedRequests: metrics.GetOrRegisterCounter("net_cross_chain_req_deadline_dropped", nil), + } +} diff --git a/peer/waiting_handler.go b/peer/waiting_handler.go index 8162817f56..846166c121 100644 --- a/peer/waiting_handler.go +++ b/peer/waiting_handler.go @@ -4,7 +4,6 @@ package peer import ( - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/subnet-evm/plugin/evm/message" ) @@ -20,14 +19,14 @@ type waitingResponseHandler struct { } // OnResponse passes the response bytes to the responseChan and closes the channel -func (w *waitingResponseHandler) OnResponse(_ ids.NodeID, _ uint32, response []byte) error { +func (w *waitingResponseHandler) OnResponse(response []byte) error { w.responseChan <- response close(w.responseChan) return nil } // OnFailure sets the failed flag to true and closes the channel -func (w *waitingResponseHandler) OnFailure(ids.NodeID, uint32) error { +func (w *waitingResponseHandler) OnFailure() error { w.failed = true close(w.responseChan) return nil diff --git a/plugin/evm/config.go b/plugin/evm/config.go index 74f6a24be5..6abaed1093 100644 --- a/plugin/evm/config.go +++ b/plugin/evm/config.go @@ -15,37 +15,38 @@ import ( ) const ( - defaultAcceptorQueueLimit = 64 // Provides 2 minutes of buffer (2s block target) for a commit delay - defaultPruningEnabled = true - defaultCommitInterval = 4096 - defaultTrieCleanCache = 512 - defaultTrieDirtyCache = 256 - defaultTrieDirtyCommitTarget = 20 - defaultSnapshotCache = 256 - defaultSyncableCommitInterval = defaultCommitInterval * 4 - defaultSnapshotAsync = true - defaultRpcGasCap = 50_000_000 // Default to 50M Gas Limit - defaultRpcTxFeeCap = 100 // 100 AVAX - defaultMetricsExpensiveEnabled = true - defaultApiMaxDuration = 0 // Default to no maximum API call duration - defaultWsCpuRefillRate = 0 // Default to no maximum WS CPU usage - defaultWsCpuMaxStored = 0 // Default to no maximum WS CPU usage - defaultMaxBlocksPerRequest = 0 // Default to no maximum on the number of blocks per getLogs request - defaultContinuousProfilerFrequency = 15 * time.Minute - defaultContinuousProfilerMaxFiles = 5 - defaultRegossipFrequency = 1 * time.Minute - defaultRegossipMaxTxs = 16 - defaultRegossipTxsPerAddress = 1 - defaultPriorityRegossipFrequency = 1 * time.Second - defaultPriorityRegossipMaxTxs = 32 - defaultPriorityRegossipTxsPerAddress = 16 - defaultOfflinePruningBloomFilterSize uint64 = 512 // Default size (MB) for the offline pruner to use - defaultLogLevel = "info" - defaultLogJSONFormat = false - defaultMaxOutboundActiveRequests = 16 - defaultPopulateMissingTriesParallelism = 1024 - defaultStateSyncServerTrieCache = 64 // MB - defaultAcceptedCacheSize = 32 // blocks + defaultAcceptorQueueLimit = 64 // Provides 2 minutes of buffer (2s block target) for a commit delay + defaultPruningEnabled = true + defaultCommitInterval = 4096 + defaultTrieCleanCache = 512 + defaultTrieDirtyCache = 256 + defaultTrieDirtyCommitTarget = 20 + defaultSnapshotCache = 256 + defaultSyncableCommitInterval = defaultCommitInterval * 4 + defaultSnapshotAsync = true + defaultRpcGasCap = 50_000_000 // Default to 50M Gas Limit + defaultRpcTxFeeCap = 100 // 100 AVAX + defaultMetricsExpensiveEnabled = true + defaultApiMaxDuration = 0 // Default to no maximum API call duration + defaultWsCpuRefillRate = 0 // Default to no maximum WS CPU usage + defaultWsCpuMaxStored = 0 // Default to no maximum WS CPU usage + defaultMaxBlocksPerRequest = 0 // Default to no maximum on the number of blocks per getLogs request + defaultContinuousProfilerFrequency = 15 * time.Minute + defaultContinuousProfilerMaxFiles = 5 + defaultRegossipFrequency = 1 * time.Minute + defaultRegossipMaxTxs = 16 + defaultRegossipTxsPerAddress = 1 + defaultPriorityRegossipFrequency = 1 * time.Second + defaultPriorityRegossipMaxTxs = 32 + defaultPriorityRegossipTxsPerAddress = 16 + defaultOfflinePruningBloomFilterSize uint64 = 512 // Default size (MB) for the offline pruner to use + defaultLogLevel = "info" + defaultLogJSONFormat = false + defaultMaxOutboundActiveRequests = 16 + defaultMaxOutboundActiveCrossChainRequests = 64 + defaultPopulateMissingTriesParallelism = 1024 + defaultStateSyncServerTrieCache = 64 // MB + defaultAcceptedCacheSize = 32 // blocks // defaultStateSyncMinBlocks is the minimum number of blocks the blockchain // should be ahead of local last accepted to perform state sync. @@ -83,6 +84,7 @@ type Config struct { // Subnet EVM APIs SnowmanAPIEnabled bool `json:"snowman-api-enabled"` + WarpAPIEnabled bool `json:"warp-api-enabled"` AdminAPIEnabled bool `json:"admin-api-enabled"` AdminAPIDir string `json:"admin-api-dir"` @@ -171,7 +173,11 @@ type Config struct { OfflinePruningDataDirectory string `json:"offline-pruning-data-directory"` // VM2VM network - MaxOutboundActiveRequests int64 `json:"max-outbound-active-requests"` + MaxOutboundActiveRequests int64 `json:"max-outbound-active-requests"` + MaxOutboundActiveCrossChainRequests int64 `json:"max-outbound-active-cross-chain-requests"` + + // Database Settings + InspectDatabase bool `json:"inspect-database"` // Inspects the database on startup if enabled. // Sync settings StateSyncEnabled bool `json:"state-sync-enabled"` @@ -187,12 +193,21 @@ type Config struct { // identical state with the pre-upgrade ruleset. SkipUpgradeCheck bool `json:"skip-upgrade-check"` + // SkipSubnetEVMUpgradeCheck disables checking that SubnetEVM Upgrade is enabled at genesis + SkipSubnetEVMUpgradeCheck bool `json:"skip-subnet-evm-upgrade-check"` + // AcceptedCacheSize is the depth to keep in the accepted headers cache and the // accepted logs cache at the accepted tip. // // This is particularly useful for improving the performance of eth_getLogs // on RPC nodes. AcceptedCacheSize int `json:"accepted-cache-size"` + + // TxLookupLimit is the maximum number of blocks from head whose tx indices + // are reserved: + // * 0: means no limit + // * N: means N block limit [HEAD-N+1, HEAD] and delete extra indexes + TxLookupLimit uint64 `json:"tx-lookup-limit"` } // EthAPIs returns an array of strings representing the Eth APIs that should be enabled @@ -243,6 +258,7 @@ func (c *Config) SetDefaults() { c.LogLevel = defaultLogLevel c.LogJSONFormat = defaultLogJSONFormat c.MaxOutboundActiveRequests = defaultMaxOutboundActiveRequests + c.MaxOutboundActiveCrossChainRequests = defaultMaxOutboundActiveCrossChainRequests c.PopulateMissingTriesParallelism = defaultPopulateMissingTriesParallelism c.StateSyncServerTrieCache = defaultStateSyncServerTrieCache c.StateSyncCommitInterval = defaultSyncableCommitInterval diff --git a/plugin/evm/config_test.go b/plugin/evm/config_test.go index fd92065741..53a7777423 100644 --- a/plugin/evm/config_test.go +++ b/plugin/evm/config_test.go @@ -72,6 +72,34 @@ func TestUnmarshalConfig(t *testing.T) { Config{StateSyncIDs: "NodeID-CaBYJ9kzHvrQFiYWowMkJGAQKGMJqZoat"}, false, }, + { + "empty tx lookup limit", + []byte(`{}`), + Config{TxLookupLimit: 0}, + false, + }, + { + "zero tx lookup limit", + []byte(`{"tx-lookup-limit": 0}`), + func() Config { + return Config{TxLookupLimit: 0} + }(), + false, + }, + { + "1 tx lookup limit", + []byte(`{"tx-lookup-limit": 1}`), + func() Config { + return Config{TxLookupLimit: 1} + }(), + false, + }, + { + "-1 tx lookup limit", + []byte(`{"tx-lookup-limit": -1}`), + Config{}, + true, + }, { "allow unprotected tx hashes", []byte(`{"allow-unprotected-tx-hashes": ["0x803351deb6d745e91545a6a3e1c0ea3e9a6a02a1a4193b70edfcd2f40f71a01c"]}`), diff --git a/plugin/evm/gossiper.go b/plugin/evm/gossiper.go index 300958b78d..bc19021ebf 100644 --- a/plugin/evm/gossiper.go +++ b/plugin/evm/gossiper.go @@ -62,7 +62,7 @@ type pushGossiper struct { // [recentTxs] prevent us from over-gossiping the // same transaction in a short period of time. - recentTxs *cache.LRU + recentTxs *cache.LRU[common.Hash, interface{}] codec codec.Manager signer types.Signer @@ -86,7 +86,7 @@ func (vm *VM) createGossiper(stats GossipStats) Gossiper { txsToGossip: make(map[common.Hash]*types.Transaction), shutdownChan: vm.shutdownChan, shutdownWg: &vm.shutdownWg, - recentTxs: &cache.LRU{Size: recentCacheSize}, + recentTxs: &cache.LRU[common.Hash, interface{}]{Size: recentCacheSize}, codec: vm.networkCodec, signer: types.LatestSigner(vm.blockChain.Config()), stats: stats, diff --git a/plugin/evm/message/codec.go b/plugin/evm/message/codec.go index 8b0fabd550..8b9a84d43a 100644 --- a/plugin/evm/message/codec.go +++ b/plugin/evm/message/codec.go @@ -15,7 +15,10 @@ const ( maxMessageSize = 1 * units.MiB ) -var Codec codec.Manager +var ( + Codec codec.Manager + CrossChainCodec codec.Manager +) func init() { Codec = codec.NewManager(maxMessageSize) @@ -37,10 +40,30 @@ func init() { c.RegisterType(CodeRequest{}), c.RegisterType(CodeResponse{}), + // Warp request types + c.RegisterType(SignatureRequest{}), + c.RegisterType(SignatureResponse{}), + Codec.RegisterCodec(Version, c), ) if errs.Errored() { panic(errs.Err) } + + CrossChainCodec = codec.NewManager(maxMessageSize) + ccc := linearcodec.NewDefault() + + errs = wrappers.Errs{} + errs.Add( + // CrossChainRequest Types + ccc.RegisterType(EthCallRequest{}), + ccc.RegisterType(EthCallResponse{}), + + CrossChainCodec.RegisterCodec(Version, ccc), + ) + + if errs.Errored() { + panic(errs.Err) + } } diff --git a/plugin/evm/message/cross_chain_handler.go b/plugin/evm/message/cross_chain_handler.go new file mode 100644 index 0000000000..f356684ee3 --- /dev/null +++ b/plugin/evm/message/cross_chain_handler.go @@ -0,0 +1,73 @@ +// (c) 2021-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package message + +import ( + "context" + "encoding/json" + + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/ids" + + "github.com/ava-labs/subnet-evm/internal/ethapi" + "github.com/ava-labs/subnet-evm/rpc" + + "github.com/ethereum/go-ethereum/log" +) + +var _ CrossChainRequestHandler = &crossChainHandler{} + +// crossChainHandler implements the CrossChainRequestHandler interface +type crossChainHandler struct { + backend ethapi.Backend + crossChainCodec codec.Manager +} + +// NewCrossChainHandler creates and returns a new instance of CrossChainRequestHandler +func NewCrossChainHandler(b ethapi.Backend, codec codec.Manager) CrossChainRequestHandler { + return &crossChainHandler{ + backend: b, + crossChainCodec: codec, + } +} + +// HandleEthCallRequests returns an encoded EthCallResponse to the given [ethCallRequest] +// This function executes EVM Call against the state associated with [rpc.AcceptedBlockNumber] with the given +// transaction call object [ethCallRequest]. +// This function does not return an error as errors are treated as FATAL to the node. +func (c *crossChainHandler) HandleEthCallRequest(ctx context.Context, requestingChainID ids.ID, requestID uint32, ethCallRequest EthCallRequest) ([]byte, error) { + lastAcceptedBlockNumber := rpc.BlockNumber(c.backend.LastAcceptedBlock().NumberU64()) + lastAcceptedBlockNumberOrHash := rpc.BlockNumberOrHash{BlockNumber: &lastAcceptedBlockNumber} + + transactionArgs := ethapi.TransactionArgs{} + err := json.Unmarshal(ethCallRequest.RequestArgs, &transactionArgs) + if err != nil { + log.Debug("error occurred with JSON unmarshalling ethCallRequest.RequestArgs", "err", err) + return nil, nil + } + + result, err := ethapi.DoCall(ctx, c.backend, transactionArgs, lastAcceptedBlockNumberOrHash, nil, c.backend.RPCEVMTimeout(), c.backend.RPCGasCap()) + if err != nil { + log.Debug("error occurred with EthCall", "err", err, "transactionArgs", ethCallRequest.RequestArgs, "blockNumberOrHash", lastAcceptedBlockNumberOrHash) + return nil, nil + } + + executionResult, err := json.Marshal(&result) + if err != nil { + log.Debug("error occurred with JSON marshalling result", "err", err) + return nil, nil + } + + response := EthCallResponse{ + ExecutionResult: executionResult, + } + + responseBytes, err := c.crossChainCodec.Marshal(Version, response) + if err != nil { + log.Warn("error occurred with marshalling EthCallResponse", "err", err, "EthCallResponse", response) + return nil, nil + } + + return responseBytes, nil +} diff --git a/plugin/evm/message/eth_call_request.go b/plugin/evm/message/eth_call_request.go new file mode 100644 index 0000000000..69d1139a2b --- /dev/null +++ b/plugin/evm/message/eth_call_request.go @@ -0,0 +1,33 @@ +// (c) 2021-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package message + +import ( + "context" + "fmt" + + "github.com/ava-labs/avalanchego/ids" +) + +var _ CrossChainRequest = EthCallRequest{} + +// EthCallRequest has the JSON Data necessary to execute a new EVM call on the blockchain +type EthCallRequest struct { + RequestArgs []byte `serialize:"true"` +} + +// EthCallResponse represents the JSON return value of the executed EVM call +type EthCallResponse struct { + ExecutionResult []byte `serialize:"true"` +} + +// String converts EthCallRequest to a string +func (e EthCallRequest) String() string { + return fmt.Sprintf("%#v", e) +} + +// Handle returns the encoded EthCallResponse by executing EVM call with the given EthCallRequest +func (e EthCallRequest) Handle(ctx context.Context, requestingChainID ids.ID, requestID uint32, handler CrossChainRequestHandler) ([]byte, error) { + return handler.HandleEthCallRequest(ctx, requestingChainID, requestID, e) +} diff --git a/plugin/evm/message/handler.go b/plugin/evm/message/handler.go index 4b7bf7c4d7..659908aaee 100644 --- a/plugin/evm/message/handler.go +++ b/plugin/evm/message/handler.go @@ -12,8 +12,9 @@ import ( ) var ( - _ GossipHandler = NoopMempoolGossipHandler{} - _ RequestHandler = NoopRequestHandler{} + _ GossipHandler = NoopMempoolGossipHandler{} + _ RequestHandler = NoopRequestHandler{} + _ CrossChainRequestHandler = NoopCrossChainRequestHandler{} ) // GossipHandler handles incoming gossip messages @@ -35,17 +36,18 @@ func (NoopMempoolGossipHandler) HandleTxs(nodeID ids.NodeID, _ TxsGossip) error // Also see GossipHandler for implementation style. type RequestHandler interface { HandleTrieLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest LeafsRequest) ([]byte, error) - HandleBlockRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, request BlockRequest) ([]byte, error) + HandleBlockRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, blockRequest BlockRequest) ([]byte, error) HandleCodeRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, codeRequest CodeRequest) ([]byte, error) + HandleSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest SignatureRequest) ([]byte, error) } // ResponseHandler handles response for a sent request // Only one of OnResponse or OnFailure is called for a given requestID, not both type ResponseHandler interface { // OnResponse is invoked when the peer responded to a request - OnResponse(nodeID ids.NodeID, requestID uint32, response []byte) error + OnResponse(response []byte) error // OnFailure is invoked when there was a failure in processing a request - OnFailure(nodeID ids.NodeID, requestID uint32) error + OnFailure() error } type NoopRequestHandler struct{} @@ -61,3 +63,18 @@ func (NoopRequestHandler) HandleBlockRequest(ctx context.Context, nodeID ids.Nod func (NoopRequestHandler) HandleCodeRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, codeRequest CodeRequest) ([]byte, error) { return nil, nil } + +func (NoopRequestHandler) HandleSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest SignatureRequest) ([]byte, error) { + return nil, nil +} + +// CrossChainRequestHandler interface handles incoming requests from another chain +type CrossChainRequestHandler interface { + HandleEthCallRequest(ctx context.Context, requestingchainID ids.ID, requestID uint32, ethCallRequest EthCallRequest) ([]byte, error) +} + +type NoopCrossChainRequestHandler struct{} + +func (NoopCrossChainRequestHandler) HandleEthCallRequest(ctx context.Context, requestingchainID ids.ID, requestID uint32, ethCallRequest EthCallRequest) ([]byte, error) { + return nil, nil +} diff --git a/plugin/evm/message/request.go b/plugin/evm/message/request.go index 2aadf5a902..6b5831f9df 100644 --- a/plugin/evm/message/request.go +++ b/plugin/evm/message/request.go @@ -34,3 +34,13 @@ func BytesToRequest(codec codec.Manager, requestBytes []byte) (Request, error) { func RequestToBytes(codec codec.Manager, request Request) ([]byte, error) { return codec.Marshal(Version, &request) } + +// CrossChainRequest represents the interface a cross chain request should implement +type CrossChainRequest interface { + // CrossChainRequest should implement String() for logging. + fmt.Stringer + + // Handle allows [CrossChainRequest] to call respective methods on handler to handle + // this particular request type + Handle(ctx context.Context, requestingChainID ids.ID, requestID uint32, handler CrossChainRequestHandler) ([]byte, error) +} diff --git a/plugin/evm/message/signature_request.go b/plugin/evm/message/signature_request.go new file mode 100644 index 0000000000..3ed8b64829 --- /dev/null +++ b/plugin/evm/message/signature_request.go @@ -0,0 +1,33 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package message + +import ( + "context" + "fmt" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/bls" +) + +var _ Request = SignatureRequest{} + +// SignatureRequest is used to request a warp message's signature. +type SignatureRequest struct { + MessageID ids.ID `serialize:"true"` +} + +func (s SignatureRequest) String() string { + return fmt.Sprintf("SignatureRequest(MessageID=%s)", s.MessageID.String()) +} + +func (s SignatureRequest) Handle(ctx context.Context, nodeID ids.NodeID, requestID uint32, handler RequestHandler) ([]byte, error) { + return handler.HandleSignatureRequest(ctx, nodeID, requestID, s) +} + +// SignatureResponse is the response to a SignatureRequest. +// The response contains a BLS signature of the requested message, signed by the responding node's BLS private key. +type SignatureResponse struct { + Signature [bls.SignatureLen]byte `serialize:"true"` +} diff --git a/plugin/evm/message/signature_request_test.go b/plugin/evm/message/signature_request_test.go new file mode 100644 index 0000000000..9e4c2fd96e --- /dev/null +++ b/plugin/evm/message/signature_request_test.go @@ -0,0 +1,60 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package message + +import ( + "encoding/base64" + "encoding/hex" + "testing" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/stretchr/testify/require" +) + +// TestMarshalSignatureRequest asserts that the structure or serialization logic hasn't changed, primarily to +// ensure compatibility with the network. +func TestMarshalSignatureRequest(t *testing.T) { + messageIDBytes, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000000") + require.NoError(t, err) + messageID, err := ids.ToID(messageIDBytes) + require.NoError(t, err) + + signatureRequest := SignatureRequest{ + MessageID: messageID, + } + + base64SignatureRequest := "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + signatureRequestBytes, err := Codec.Marshal(Version, signatureRequest) + require.NoError(t, err) + require.Equal(t, base64SignatureRequest, base64.StdEncoding.EncodeToString(signatureRequestBytes)) + + var s SignatureRequest + _, err = Codec.Unmarshal(signatureRequestBytes, &s) + require.NoError(t, err) + require.Equal(t, signatureRequest.MessageID, s.MessageID) +} + +// TestMarshalSignatureResponse asserts that the structure or serialization logic hasn't changed, primarily to +// ensure compatibility with the network. +func TestMarshalSignatureResponse(t *testing.T) { + var signature [bls.SignatureLen]byte + sig, err := hex.DecodeString("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + require.NoError(t, err, "failed to decode string to hex") + + copy(signature[:], sig) + signatureResponse := SignatureResponse{ + Signature: signature, + } + + base64SignatureResponse := "AAABI0VniavN7wEjRWeJq83vASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8BI0VniavN7wEjRWeJq83vASNFZ4mrze8=" + signatureResponseBytes, err := Codec.Marshal(Version, signatureResponse) + require.NoError(t, err) + require.Equal(t, base64SignatureResponse, base64.StdEncoding.EncodeToString(signatureResponseBytes)) + + var s SignatureResponse + _, err = Codec.Unmarshal(signatureResponseBytes, &s) + require.NoError(t, err) + require.Equal(t, signatureResponse.Signature, s.Signature) +} diff --git a/plugin/evm/message/syncable.go b/plugin/evm/message/syncable.go index c9b73632ed..f79a9f2dfa 100644 --- a/plugin/evm/message/syncable.go +++ b/plugin/evm/message/syncable.go @@ -25,10 +25,10 @@ type SyncSummary struct { summaryID ids.ID bytes []byte - acceptImpl func(SyncSummary) (bool, error) + acceptImpl func(SyncSummary) (block.StateSyncMode, error) } -func NewSyncSummaryFromBytes(summaryBytes []byte, acceptImpl func(SyncSummary) (bool, error)) (SyncSummary, error) { +func NewSyncSummaryFromBytes(summaryBytes []byte, acceptImpl func(SyncSummary) (block.StateSyncMode, error)) (SyncSummary, error) { summary := SyncSummary{} if codecVersion, err := Codec.Unmarshal(summaryBytes, &summary); err != nil { return SyncSummary{}, err @@ -83,9 +83,9 @@ func (s SyncSummary) String() string { return fmt.Sprintf("SyncSummary(BlockHash=%s, BlockNumber=%d, BlockRoot=%s)", s.BlockHash, s.BlockNumber, s.BlockRoot) } -func (s SyncSummary) Accept(context.Context) (bool, error) { +func (s SyncSummary) Accept(context.Context) (block.StateSyncMode, error) { if s.acceptImpl == nil { - return false, fmt.Errorf("accept implementation not specified for summary: %s", s) + return block.StateSyncSkipped, fmt.Errorf("accept implementation not specified for summary: %s", s) } return s.acceptImpl(s) } diff --git a/plugin/evm/network_handler.go b/plugin/evm/network_handler.go new file mode 100644 index 0000000000..529249fbc1 --- /dev/null +++ b/plugin/evm/network_handler.go @@ -0,0 +1,60 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package evm + +import ( + "context" + + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/subnet-evm/metrics" + "github.com/ava-labs/subnet-evm/plugin/evm/message" + syncHandlers "github.com/ava-labs/subnet-evm/sync/handlers" + syncStats "github.com/ava-labs/subnet-evm/sync/handlers/stats" + "github.com/ava-labs/subnet-evm/trie" + warpHandlers "github.com/ava-labs/subnet-evm/warp/handlers" +) + +var _ message.RequestHandler = &networkHandler{} + +type networkHandler struct { + stateTrieLeafsRequestHandler *syncHandlers.LeafsRequestHandler + blockRequestHandler *syncHandlers.BlockRequestHandler + codeRequestHandler *syncHandlers.CodeRequestHandler + signatureRequestHandler warpHandlers.SignatureRequestHandler +} + +// newNetworkHandler constructs the handler for serving network requests. +func newNetworkHandler( + provider syncHandlers.SyncDataProvider, + evmTrieDB *trie.Database, + networkCodec codec.Manager, +) message.RequestHandler { + syncStats := syncStats.NewHandlerStats(metrics.Enabled) + return &networkHandler{ + // State sync handlers + stateTrieLeafsRequestHandler: syncHandlers.NewLeafsRequestHandler(evmTrieDB, provider, networkCodec, syncStats), + blockRequestHandler: syncHandlers.NewBlockRequestHandler(provider, networkCodec, syncStats), + codeRequestHandler: syncHandlers.NewCodeRequestHandler(evmTrieDB.DiskDB(), networkCodec, syncStats), + + // TODO: initialize actual signature request handler when warp is ready + signatureRequestHandler: &warpHandlers.NoopSignatureRequestHandler{}, + } +} + +func (n networkHandler) HandleTrieLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest message.LeafsRequest) ([]byte, error) { + return n.stateTrieLeafsRequestHandler.OnLeafsRequest(ctx, nodeID, requestID, leafsRequest) +} + +func (n networkHandler) HandleBlockRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, blockRequest message.BlockRequest) ([]byte, error) { + return n.blockRequestHandler.OnBlockRequest(ctx, nodeID, requestID, blockRequest) +} + +func (n networkHandler) HandleCodeRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, codeRequest message.CodeRequest) ([]byte, error) { + return n.codeRequestHandler.OnCodeRequest(ctx, nodeID, requestID, codeRequest) +} + +func (n networkHandler) HandleSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest message.SignatureRequest) ([]byte, error) { + return n.signatureRequestHandler.OnSignatureRequest(ctx, nodeID, requestID, signatureRequest) +} diff --git a/plugin/evm/syncervm_client.go b/plugin/evm/syncervm_client.go index b56f1a30c6..bf185900c0 100644 --- a/plugin/evm/syncervm_client.go +++ b/plugin/evm/syncervm_client.go @@ -154,7 +154,7 @@ func (client *stateSyncerClient) stateSync(ctx context.Context) error { // acceptSyncSummary returns true if sync will be performed and launches the state sync process // in a goroutine. -func (client *stateSyncerClient) acceptSyncSummary(proposedSummary message.SyncSummary) (bool, error) { +func (client *stateSyncerClient) acceptSyncSummary(proposedSummary message.SyncSummary) (block.StateSyncMode, error) { isResume := proposedSummary.BlockHash == client.resumableSummary.BlockHash if !isResume { // Skip syncing if the blockchain is not significantly ahead of local state, @@ -167,12 +167,12 @@ func (client *stateSyncerClient) acceptSyncSummary(proposedSummary message.SyncS "syncableHeight", proposedSummary.Height(), ) if err := client.StateSyncClearOngoingSummary(); err != nil { - return false, fmt.Errorf("failed to clear ongoing summary after skipping state sync: %w", err) + return block.StateSyncSkipped, fmt.Errorf("failed to clear ongoing summary after skipping state sync: %w", err) } // Initialize snapshots if we're skipping state sync, since it will not have been initialized on // startup. client.chain.BlockChain().InitializeSnapshots() - return false, nil + return block.StateSyncSkipped, nil } // Wipe the snapshot completely if we are not resuming from an existing sync, so that we do not @@ -193,10 +193,10 @@ func (client *stateSyncerClient) acceptSyncSummary(proposedSummary message.SyncS // Note: this must be performed after WipeSnapshot finishes so that we do not start a state sync // session from a partially wiped snapshot. if err := client.metadataDB.Put(stateSyncSummaryKey, proposedSummary.Bytes()); err != nil { - return false, fmt.Errorf("failed to write state sync summary key to disk: %w", err) + return block.StateSyncSkipped, fmt.Errorf("failed to write state sync summary key to disk: %w", err) } if err := client.db.Commit(); err != nil { - return false, fmt.Errorf("failed to commit db: %w", err) + return block.StateSyncSkipped, fmt.Errorf("failed to commit db: %w", err) } log.Info("Starting state sync", "summary", proposedSummary) @@ -220,7 +220,7 @@ func (client *stateSyncerClient) acceptSyncSummary(proposedSummary message.SyncS log.Info("stateSync completed, notifying engine", "err", client.stateSyncErr) client.toEngine <- commonEng.StateSyncDone }() - return true, nil + return block.StateSyncStatic, nil } // syncBlocks fetches (up to) [parentsToGet] blocks from peers diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index d67ab92df0..b5a37194cb 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -20,6 +20,7 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/choices" commonEng "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/subnet-evm/accounts/keystore" @@ -44,7 +45,7 @@ func TestSkipStateSync(t *testing.T) { test := syncTest{ syncableInterval: 256, stateSyncMinBlocks: 300, // must be greater than [syncableInterval] to skip sync - shouldSync: false, + syncMode: block.StateSyncSkipped, } vmSetup := createSyncServerAndClientVMs(t, test) defer vmSetup.Teardown(t) @@ -57,7 +58,7 @@ func TestStateSyncFromScratch(t *testing.T) { test := syncTest{ syncableInterval: 256, stateSyncMinBlocks: 50, // must be less than [syncableInterval] to perform sync - shouldSync: true, + syncMode: block.StateSyncStatic, } vmSetup := createSyncServerAndClientVMs(t, test) defer vmSetup.Teardown(t) @@ -78,7 +79,7 @@ func TestStateSyncToggleEnabledToDisabled(t *testing.T) { test := syncTest{ syncableInterval: 256, stateSyncMinBlocks: 50, // must be less than [syncableInterval] to perform sync - shouldSync: true, + syncMode: block.StateSyncStatic, responseIntercept: func(syncerVM *VM, nodeID ids.NodeID, requestID uint32, response []byte) { lock.Lock() defer lock.Unlock() @@ -107,7 +108,7 @@ func TestStateSyncToggleEnabledToDisabled(t *testing.T) { // Perform sync resulting in early termination. testSyncerVM(t, vmSetup, test) - test.shouldSync = true + test.syncMode = block.StateSyncStatic test.responseIntercept = nil test.expectedErr = nil @@ -360,7 +361,7 @@ type syncTest struct { responseIntercept func(vm *VM, nodeID ids.NodeID, requestID uint32, response []byte) stateSyncMinBlocks uint64 syncableInterval uint64 - shouldSync bool + syncMode block.StateSyncMode expectedErr error } @@ -388,14 +389,14 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { } assert.Equal(t, summary, retrievedSummary) - shouldSync, err := parsedSummary.Accept(context.Background()) + syncMode, err := parsedSummary.Accept(context.Background()) if err != nil { t.Fatal("unexpected error accepting state summary", "err", err) } - if shouldSync != test.shouldSync { - t.Fatal("unexpected value returned from accept", "expected", test.shouldSync, "got", shouldSync) + if syncMode != test.syncMode { + t.Fatal("unexpected value returned from accept", "expected", test.syncMode, "got", syncMode) } - if !shouldSync { + if syncMode == block.StateSyncSkipped { return } msg := <-syncerEngineChan diff --git a/plugin/evm/version.go b/plugin/evm/version.go index cfdcb2d22d..d889ba117f 100644 --- a/plugin/evm/version.go +++ b/plugin/evm/version.go @@ -11,7 +11,7 @@ var ( // GitCommit is set by the build script GitCommit string // Version is the version of Subnet EVM - Version string = "v0.4.7" + Version string = "v0.4.9" ) func init() { diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 1721ed1f25..d48b7744c4 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -8,7 +8,6 @@ import ( "encoding/json" "errors" "fmt" - "math/big" "os" "path/filepath" "strings" @@ -35,9 +34,8 @@ import ( "github.com/ava-labs/subnet-evm/rpc" statesyncclient "github.com/ava-labs/subnet-evm/sync/client" "github.com/ava-labs/subnet-evm/sync/client/stats" - "github.com/ava-labs/subnet-evm/sync/handlers" - handlerstats "github.com/ava-labs/subnet-evm/sync/handlers/stats" "github.com/ava-labs/subnet-evm/trie" + "github.com/ava-labs/subnet-evm/warp" // Force-load tracer engine to trigger registration // @@ -86,9 +84,10 @@ const ( // and fail verification maxFutureBlockTime = 10 * time.Second - decidedCacheSize = 100 - missingCacheSize = 50 - unverifiedCacheSize = 50 + decidedCacheSize = 100 + missingCacheSize = 50 + unverifiedCacheSize = 50 + warpSignatureCacheSize = 500 // Prefixes for metrics gatherers ethMetricsPrefix = "eth" @@ -107,17 +106,19 @@ var ( lastAcceptedKey = []byte("last_accepted_key") acceptedPrefix = []byte("snowman_accepted") metadataPrefix = []byte("metadata") + warpPrefix = []byte("warp") ethDBPrefix = []byte("ethdb") ) var ( - errEmptyBlock = errors.New("empty block") - errUnsupportedFXs = errors.New("unsupported feature extensions") - errInvalidBlock = errors.New("invalid block") - errInvalidNonce = errors.New("invalid nonce") - errUnclesUnsupported = errors.New("uncles unsupported") - errNilBaseFeeSubnetEVM = errors.New("nil base fee is invalid after subnetEVM") - errNilBlockGasCostSubnetEVM = errors.New("nil blockGasCost is invalid after subnetEVM") + errEmptyBlock = errors.New("empty block") + errUnsupportedFXs = errors.New("unsupported feature extensions") + errInvalidBlock = errors.New("invalid block") + errInvalidNonce = errors.New("invalid nonce") + errUnclesUnsupported = errors.New("uncles unsupported") + errNilBaseFeeSubnetEVM = errors.New("nil base fee is invalid after subnetEVM") + errNilBlockGasCostSubnetEVM = errors.New("nil blockGasCost is invalid after subnetEVM") + errSubnetEVMUpgradeNotEnabled = errors.New("SubnetEVM upgrade is not enabled in genesis") ) var originalStderr *os.File @@ -181,6 +182,10 @@ type VM struct { // block. acceptedBlockDB database.Database + // [warpDB] is used to store warp message signatures + // set to a prefixDB with the prefix [warpPrefix] + warpDB database.Database + toEngine chan<- commonEng.Message syntacticBlockValidator BlockValidator @@ -210,6 +215,10 @@ type VM struct { // State sync server and client StateSyncServer StateSyncClient + + // Avalanche Warp Messaging backend + // Used to serve BLS signatures of warp messages over RPC + warpBackend warp.WarpBackend } /* @@ -278,6 +287,17 @@ func (vm *VM) Initialize( vm.db = versiondb.New(baseDB) vm.acceptedBlockDB = prefixdb.New(acceptedPrefix, vm.db) vm.metadataDB = prefixdb.New(metadataPrefix, vm.db) + vm.warpDB = prefixdb.New(warpPrefix, vm.db) + + if vm.config.InspectDatabase { + start := time.Now() + log.Info("Starting database inspection") + if err := rawdb.InspectDatabase(vm.chaindb, nil, nil); err != nil { + return err + } + log.Info("Completed database inspection", "elapsed", time.Since(start)) + } + g := new(core.Genesis) if err := json.Unmarshal(genesisBytes, g); err != nil { return err @@ -350,6 +370,7 @@ func (vm *VM) Initialize( vm.ethConfig.CommitInterval = vm.config.CommitInterval vm.ethConfig.SkipUpgradeCheck = vm.config.SkipUpgradeCheck vm.ethConfig.AcceptedCacheSize = vm.config.AcceptedCacheSize + vm.ethConfig.TxLookupLimit = vm.config.TxLookupLimit // Create directory for offline pruning if len(vm.ethConfig.OfflinePruningDataDirectory) != 0 { @@ -372,6 +393,13 @@ func (vm *VM) Initialize( vm.chainConfig = g.Config vm.networkID = vm.ethConfig.NetworkId + if !vm.config.SkipSubnetEVMUpgradeCheck { + // check that subnetEVM upgrade is enabled from genesis before upgradeBytes + if !vm.chainConfig.IsSubnetEVM(common.Big0) { + return errSubnetEVMUpgradeNotEnabled + } + } + // Apply upgradeBytes (if any) by unmarshalling them into [chainConfig.UpgradeConfig]. // Initializing the chain will verify upgradeBytes are compatible with existing values. if len(upgradeBytes) > 0 { @@ -398,9 +426,12 @@ func (vm *VM) Initialize( // initialize peer network vm.networkCodec = message.Codec - vm.Network = peer.NewNetwork(appSender, vm.networkCodec, chainCtx.NodeID, vm.config.MaxOutboundActiveRequests) + vm.Network = peer.NewNetwork(appSender, vm.networkCodec, message.CrossChainCodec, chainCtx.NodeID, vm.config.MaxOutboundActiveRequests, vm.config.MaxOutboundActiveCrossChainRequests) vm.client = peer.NewNetworkClient(vm.Network) + // initialize warp backend + vm.warpBackend = warp.NewWarpBackend(vm.ctx, vm.warpDB, warpSignatureCacheSize) + if err := vm.initializeChain(lastAcceptedHash, vm.ethConfig); err != nil { return err } @@ -519,6 +550,7 @@ func (vm *VM) initializeStateSyncServer() { }) vm.setAppRequestHandlers() + vm.setCrossChainAppRequestHandler() } func (vm *VM) initChainState(lastAcceptedBlock *types.Block) error { @@ -590,13 +622,16 @@ func (vm *VM) setAppRequestHandlers() { Cache: vm.config.StateSyncServerTrieCache, }, ) - syncRequestHandler := handlers.NewSyncHandler( - vm.blockChain, - evmTrieDB, - vm.networkCodec, - handlerstats.NewHandlerStats(metrics.Enabled), - ) - vm.Network.SetRequestHandler(syncRequestHandler) + + networkHandler := newNetworkHandler(vm.blockChain, evmTrieDB, vm.networkCodec) + vm.Network.SetRequestHandler(networkHandler) +} + +// setCrossChainAppRequestHandler sets the request handlers for the VM to serve cross chain +// requests. +func (vm *VM) setCrossChainAppRequestHandler() { + crossChainRequestHandler := message.NewCrossChainHandler(vm.eth.APIBackend, message.CrossChainCodec) + vm.Network.SetCrossChainRequestHandler(crossChainRequestHandler) } // Shutdown implements the snowman.ChainVM interface @@ -777,6 +812,13 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]*commonEng.HTTPHandler enabledAPIs = append(enabledAPIs, "snowman") } + if vm.config.WarpAPIEnabled { + if err := handler.RegisterName("warp", &warp.WarpAPI{Backend: vm.warpBackend}); err != nil { + return nil, err + } + enabledAPIs = append(enabledAPIs, "warp") + } + log.Info(fmt.Sprintf("Enabled APIs: %s", strings.Join(enabledAPIs, ", "))) apis[ethRPCEndpoint] = &commonEng.HTTPHandler{ LockOptions: commonEng.NoLock, @@ -828,12 +870,6 @@ func (vm *VM) GetCurrentNonce(address common.Address) (uint64, error) { return state.GetNonce(address), nil } -// currentRules returns the chain rules for the current block. -func (vm *VM) currentRules() params.Rules { - header := vm.eth.APIBackend.CurrentHeader() - return vm.chainConfig.AvalancheRules(header.Number, big.NewInt(int64(header.Time))) -} - func (vm *VM) startContinuousProfiler() { // If the profiler directory is empty, return immediately // without creating or starting a continuous profiler. diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index d97e23811f..6b6e3a14a8 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -18,7 +18,9 @@ import ( "time" "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/internal/ethapi" "github.com/ava-labs/subnet-evm/metrics" + "github.com/ava-labs/subnet-evm/plugin/evm/message" "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" @@ -27,6 +29,7 @@ import ( "github.com/ava-labs/subnet-evm/trie" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" @@ -55,6 +58,7 @@ import ( "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/rpc" + "github.com/ava-labs/subnet-evm/accounts/abi" accountKeystore "github.com/ava-labs/subnet-evm/accounts/keystore" ) @@ -70,9 +74,10 @@ var ( password = "CjasdjhiPeirbSenfeI13" // #nosec G101 // Use chainId: 43111, so that it does not overlap with any Avalanche ChainIDs, which may have their // config overridden in vm.Initialize. - genesisJSONSubnetEVM = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"subnetEVMTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x7A1200\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0x71562b71999873DB5b286dF957af199Ec94617F7\": {\"balance\":\"0x4192927743b88000\"}, \"0x703c4b2bD70c169f5717101CaeE543299Fc946C7\": {\"balance\":\"0x4192927743b88000\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}" - genesisJSONPreSubnetEVM = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x7A1200\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0x71562b71999873DB5b286dF957af199Ec94617F7\": {\"balance\":\"0x4192927743b88000\"}, \"0x703c4b2bD70c169f5717101CaeE543299Fc946C7\": {\"balance\":\"0x4192927743b88000\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}" - genesisJSONLatest = genesisJSONSubnetEVM + genesisJSONSubnetEVMLateEnablement = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"subnetEVMTimestamp\":50},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x7A1200\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0x71562b71999873DB5b286dF957af199Ec94617F7\": {\"balance\":\"0x4192927743b88000\"}, \"0x703c4b2bD70c169f5717101CaeE543299Fc946C7\": {\"balance\":\"0x4192927743b88000\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}" + genesisJSONSubnetEVM = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"subnetEVMTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x7A1200\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0x71562b71999873DB5b286dF957af199Ec94617F7\": {\"balance\":\"0x4192927743b88000\"}, \"0x703c4b2bD70c169f5717101CaeE543299Fc946C7\": {\"balance\":\"0x4192927743b88000\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}" + genesisJSONPreSubnetEVM = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x7A1200\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0x71562b71999873DB5b286dF957af199Ec94617F7\": {\"balance\":\"0x4192927743b88000\"}, \"0x703c4b2bD70c169f5717101CaeE543299Fc946C7\": {\"balance\":\"0x4192927743b88000\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}" + genesisJSONLatest = genesisJSONSubnetEVM firstTxAmount *big.Int genesisBalance *big.Int @@ -380,6 +385,57 @@ func issueAndAccept(t *testing.T, issuer <-chan engCommon.Message, vm *VM) snowm return blk } +func TestSubnetEVMUpgradeRequiredAtGenesis(t *testing.T) { + genesisTests := []struct { + genesisJSON string + configJSON string + expectedErr error + }{ + { + // we expect an error when subnet evm upgrade is nil in chain config + genesisJSON: genesisJSONPreSubnetEVM, + configJSON: "", + expectedErr: errSubnetEVMUpgradeNotEnabled, + }, + { + // we expect an error when subnet evm upgrade is not enabled at genesis and at a later block instead + genesisJSON: genesisJSONSubnetEVMLateEnablement, + configJSON: "", + expectedErr: errSubnetEVMUpgradeNotEnabled, + }, + { + // we do not expect an err when skip-subnet-evm-upgrade-check is set to true + genesisJSON: genesisJSONPreSubnetEVM, + configJSON: "{\"skip-subnet-evm-upgrade-check\": true}", + expectedErr: nil, + }, + { + // we do not expect an err when skip-subnet-evm-upgrade-check is set to true + genesisJSON: genesisJSONSubnetEVMLateEnablement, + configJSON: "{\"skip-subnet-evm-upgrade-check\": true}", + expectedErr: nil, + }, + } + + for _, test := range genesisTests { + ctx, dbManager, genesisBytes, issuer := setupGenesis(t, test.genesisJSON) + vm := &VM{} + err := vm.Initialize( + context.Background(), + ctx, + dbManager, + genesisBytes, + []byte(""), + []byte(test.configJSON), + issuer, + []*engCommon.Fx{}, + nil, + ) + + require.ErrorIs(t, err, test.expectedErr) + } +} + func TestBuildEthTxBlock(t *testing.T) { // reduce block gas cost issuer, vm, dbManager, _ := GenesisVM(t, true, genesisJSONSubnetEVM, "{\"pruning-enabled\":true}", "") @@ -2951,3 +3007,192 @@ func TestSkipChainConfigCheckCompatible(t *testing.T) { require.NoError(t, err) require.NoError(t, reinitVM.Shutdown(context.Background())) } + +func TestCrossChainMessagestoVM(t *testing.T) { + crossChainCodec := message.CrossChainCodec + require := require.New(t) + + // the following is based on this contract: + // contract T { + // event received(address sender, uint amount, bytes memo); + // event receivedAddr(address sender); + // + // function receive(bytes calldata memo) external payable returns (string memory res) { + // emit received(msg.sender, msg.value, memo); + // emit receivedAddr(msg.sender); + // return "hello world"; + // } + // } + + const abiBin = `0x608060405234801561001057600080fd5b506102a0806100206000396000f3fe60806040526004361061003b576000357c010000000000000000000000000000000000000000000000000000000090048063a69b6ed014610040575b600080fd5b6100b76004803603602081101561005657600080fd5b810190808035906020019064010000000081111561007357600080fd5b82018360208201111561008557600080fd5b803590602001918460018302840111640100000000831117156100a757600080fd5b9091929391929390505050610132565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f75780820151818401526020810190506100dc565b50505050905090810190601f1680156101245780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60607f75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed33348585604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509550505050505060405180910390a17f46923992397eac56cf13058aced2a1871933622717e27b24eabc13bf9dd329c833604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a16040805190810160405280600b81526020017f68656c6c6f20776f726c6400000000000000000000000000000000000000000081525090509291505056fea165627a7a72305820ff0c57dad254cfeda48c9cfb47f1353a558bccb4d1bc31da1dae69315772d29e0029` + const abiJSON = `[ { "constant": false, "inputs": [ { "name": "memo", "type": "bytes" } ], "name": "receive", "outputs": [ { "name": "res", "type": "string" } ], "payable": true, "stateMutability": "payable", "type": "function" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "sender", "type": "address" }, { "indexed": false, "name": "amount", "type": "uint256" }, { "indexed": false, "name": "memo", "type": "bytes" } ], "name": "received", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "sender", "type": "address" } ], "name": "receivedAddr", "type": "event" } ]` + parsed, err := abi.JSON(strings.NewReader(abiJSON)) + require.NoErrorf(err, "could not parse abi: %v") + + calledSendCrossChainAppResponseFn := false + issuer, vm, _, appSender := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") + + defer func() { + err := vm.Shutdown(context.Background()) + require.NoError(err) + }() + + appSender.SendCrossChainAppResponseF = func(ctx context.Context, respondingChainID ids.ID, requestID uint32, responseBytes []byte) { + calledSendCrossChainAppResponseFn = true + + var response message.EthCallResponse + if _, err = crossChainCodec.Unmarshal(responseBytes, &response); err != nil { + require.NoErrorf(err, "unexpected error during unmarshal: %w") + } + + result := core.ExecutionResult{} + err = json.Unmarshal(response.ExecutionResult, &result) + require.NoError(err) + require.NotNil(result.ReturnData) + + finalResult, err := parsed.Unpack("receive", result.ReturnData) + require.NoError(err) + require.NotNil(finalResult) + require.Equal("hello world", finalResult[0]) + } + + newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) + vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) + + tx := types.NewTransaction(uint64(0), testEthAddrs[1], firstTxAmount, 21000, big.NewInt(testMinGasPrice), nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) + require.NoError(err) + + txErrors := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) + for _, err := range txErrors { + require.NoError(err) + } + + <-issuer + + blk1, err := vm.BuildBlock(context.Background()) + require.NoError(err) + + err = blk1.Verify(context.Background()) + require.NoError(err) + + if status := blk1.Status(); status != choices.Processing { + t.Fatalf("Expected status of built block to be %s, but found %s", choices.Processing, status) + } + + err = vm.SetPreference(context.Background(), blk1.ID()) + require.NoError(err) + + err = blk1.Accept(context.Background()) + require.NoError(err) + + newHead := <-newTxPoolHeadChan + if newHead.Head.Hash() != common.Hash(blk1.ID()) { + t.Fatalf("Expected new block to match") + } + + if status := blk1.Status(); status != choices.Accepted { + t.Fatalf("Expected status of accepted block to be %s, but found %s", choices.Accepted, status) + } + + lastAcceptedID, err := vm.LastAccepted(context.Background()) + require.NoError(err) + + if lastAcceptedID != blk1.ID() { + t.Fatalf("Expected last accepted blockID to be the accepted block: %s, but found %s", blk1.ID(), lastAcceptedID) + } + + contractTx := types.NewContractCreation(1, common.Big0, 200000, big.NewInt(testMinGasPrice), common.FromHex(abiBin)) + contractSignedTx, err := types.SignTx(contractTx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) + require.NoError(err) + + errs := vm.txPool.AddRemotesSync([]*types.Transaction{contractSignedTx}) + for _, err := range errs { + require.NoError(err) + } + testAddr := testEthAddrs[0] + contractAddress := crypto.CreateAddress(testAddr, 1) + + <-issuer + + blk2, err := vm.BuildBlock(context.Background()) + require.NoError(err) + + err = blk2.Verify(context.Background()) + require.NoError(err) + + if status := blk2.Status(); status != choices.Processing { + t.Fatalf("Expected status of built block to be %s, but found %s", choices.Processing, status) + } + + err = vm.SetPreference(context.Background(), blk2.ID()) + require.NoError(err) + + err = blk2.Accept(context.Background()) + require.NoError(err) + + newHead = <-newTxPoolHeadChan + if newHead.Head.Hash() != common.Hash(blk2.ID()) { + t.Fatalf("Expected new block to match") + } + + if status := blk2.Status(); status != choices.Accepted { + t.Fatalf("Expected status of accepted block to be %s, but found %s", choices.Accepted, status) + } + + lastAcceptedID, err = vm.LastAccepted(context.Background()) + require.NoError(err) + + if lastAcceptedID != blk2.ID() { + t.Fatalf("Expected last accepted blockID to be the accepted block: %s, but found %s", blk2.ID(), lastAcceptedID) + } + + input, err := parsed.Pack("receive", []byte("X")) + require.NoError(err) + + data := hexutil.Bytes(input) + + requestArgs, err := json.Marshal(ðapi.TransactionArgs{ + To: &contractAddress, + Data: &data, + }) + require.NoError(err) + + var ethCallRequest message.CrossChainRequest = message.EthCallRequest{ + RequestArgs: requestArgs, + } + + crossChainRequest, err := crossChainCodec.Marshal(message.Version, ðCallRequest) + require.NoError(err) + + requestingChainID := ids.ID(common.BytesToHash([]byte{1, 2, 3, 4, 5})) + + // we need all items in the acceptor queue to be processed before we process a cross chain request + vm.blockChain.DrainAcceptorQueue() + err = vm.Network.CrossChainAppRequest(context.Background(), requestingChainID, 1, time.Now().Add(60*time.Second), crossChainRequest) + require.NoError(err) + require.True(calledSendCrossChainAppResponseFn, "sendCrossChainAppResponseFn was not called") +} + +func TestSignatureRequestsToVM(t *testing.T) { + _, vm, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") + + defer func() { + err := vm.Shutdown(context.Background()) + require.NoError(t, err) + }() + + // Generate a SignatureRequest for an unknown message + var signatureRequest message.Request = message.SignatureRequest{ + MessageID: ids.GenerateTestID(), + } + + requestBytes, err := message.Codec.Marshal(message.Version, &signatureRequest) + require.NoError(t, err) + + // Currently with warp not being initialized we just need to make sure the NoopSignatureRequestHandler does not + // panic/crash when sent a SignatureRequest. + // TODO: We will need to update the test when warp is initialized to check for expected response. + err = vm.Network.AppRequest(context.Background(), ids.GenerateTestNodeID(), 1, time.Now().Add(60*time.Second), requestBytes) + require.NoError(t, err) +} diff --git a/plugin/evm/vm_upgrade_bytes_test.go b/plugin/evm/vm_upgrade_bytes_test.go index a4ac725a59..4880f0e348 100644 --- a/plugin/evm/vm_upgrade_bytes_test.go +++ b/plugin/evm/vm_upgrade_bytes_test.go @@ -165,9 +165,10 @@ func TestVMUpgradeBytesNetworkUpgrades(t *testing.T) { if err != nil { t.Fatalf("could not marshal upgradeConfig to json: %s", err) } + configJSON := "{\"skip-subnet-evm-upgrade-check\": true}" // initialize the VM with these upgrade bytes - issuer, vm, dbManager, appSender := GenesisVM(t, true, genesisJSONPreSubnetEVM, "", string(upgradeBytesJSON)) + issuer, vm, dbManager, appSender := GenesisVM(t, true, genesisJSONPreSubnetEVM, configJSON, string(upgradeBytesJSON)) vm.clock.Set(subnetEVMTimestamp) // verify upgrade is applied @@ -238,9 +239,10 @@ func TestVMUpgradeBytesNetworkUpgradesWithGenesis(t *testing.T) { if err != nil { t.Fatalf("could not marshal upgradeConfig to json: %s", err) } + configJSON := "{\"skip-subnet-evm-upgrade-check\": true}" // initialize the VM with these upgrade bytes - _, vm, _, _ := GenesisVM(t, true, string(genesisBytes), "", string(upgradeBytesJSON)) + _, vm, _, _ := GenesisVM(t, true, string(genesisBytes), configJSON, string(upgradeBytesJSON)) // verify upgrade is rescheduled assert.False(t, vm.chainConfig.IsSubnetEVM(genesisSubnetEVMTimestamp)) @@ -258,7 +260,7 @@ func TestVMUpgradeBytesNetworkUpgradesWithGenesis(t *testing.T) { } // initialize the VM with these upgrade bytes - _, vm, _, _ = GenesisVM(t, true, string(genesisBytes), "", string(upgradeBytesJSON)) + _, vm, _, _ = GenesisVM(t, true, string(genesisBytes), configJSON, string(upgradeBytesJSON)) // verify upgrade is aborted assert.False(t, vm.chainConfig.IsSubnetEVM(genesisSubnetEVMTimestamp)) diff --git a/plugin/main.go b/plugin/main.go index b37ebf3cfb..d8b4e772c2 100644 --- a/plugin/main.go +++ b/plugin/main.go @@ -9,19 +9,20 @@ import ( "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/ulimit" + "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/avalanchego/vms/rpcchainvm" "github.com/ava-labs/subnet-evm/plugin/evm" ) func main() { - version, err := PrintVersion() + printVersion, err := PrintVersion() if err != nil { fmt.Printf("couldn't get config: %s", err) os.Exit(1) } - if version { - fmt.Println(evm.Version) + if printVersion { + fmt.Printf("Subnet-EVM/%s [AvalancheGo=%s, rpcchainvm=%d]\n", evm.Version, version.Current, version.RPCChainVMProtocol) os.Exit(0) } if err := ulimit.Set(ulimit.DefaultFDLimit, logging.NoLog{}); err != nil { diff --git a/precompile/contracts/feemanager/config_test.go b/precompile/contracts/feemanager/config_test.go index 861c713023..7a21efdf46 100644 --- a/precompile/contracts/feemanager/config_test.go +++ b/precompile/contracts/feemanager/config_test.go @@ -39,12 +39,9 @@ func TestVerifyFeeManagerConfig(t *testing.T) { expectedError: "cannot set address", }, { - name: "invalid initial fee manager config", - config: NewConfig(big.NewInt(3), admins, nil, - &commontype.FeeConfig{ - GasLimit: big.NewInt(0), - }), - expectedError: "gasLimit = 0 cannot be less than or equal to 0", + name: "invalid initial fee manager config", + config: NewConfig(big.NewInt(3), admins, nil, &commontype.FeeConfig{}), + expectedError: "gasLimit cannot be nil", }, } for _, tt := range tests { diff --git a/scripts/build.sh b/scripts/build.sh index deb5713e0d..d6da71d6ed 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -40,14 +40,14 @@ source "$SUBNET_EVM_PATH"/scripts/versions.sh source "$SUBNET_EVM_PATH"/scripts/constants.sh if [[ $# -eq 1 ]]; then - binary_path=$1 + BINARY_PATH=$1 elif [[ $# -eq 0 ]]; then - binary_path="$GOPATH/src/github.com/ava-labs/avalanchego/build/plugins/srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy" + BINARY_PATH="$GOPATH/src/github.com/ava-labs/avalanchego/build/plugins/srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy" else echo "Invalid arguments to build subnet-evm. Requires zero (default location) or one argument to specify binary location." exit 1 fi # Build Subnet EVM, which is run as a subprocess -echo "Building Subnet EVM Version: $subnet_evm_version at $binary_path" -go build -ldflags "-X github.com/ava-labs/subnet-evm/plugin/evm.GitCommit=$subnet_evm_commit -X github.com/ava-labs/subnet-evm/plugin/evm.Version=$subnet_evm_version $static_ld_flags" -o "$binary_path" "plugin/"*.go +echo "Building Subnet EVM Version: $SUBNET_EVM_VERSION at $BINARY_PATH" +go build -ldflags "-X github.com/ava-labs/subnet-evm/plugin/evm.GitCommit=$SUBNET_EVM_COMMIT -X github.com/ava-labs/subnet-evm/plugin/evm.Version=$SUBNET_EVM_VERSION $STATIC_LD_FLAGS" -o "$BINARY_PATH" "plugin/"*.go diff --git a/scripts/build_image.sh b/scripts/build_image.sh index 06e2705b38..48c3b93117 100755 --- a/scripts/build_image.sh +++ b/scripts/build_image.sh @@ -13,8 +13,8 @@ source "$SUBNET_EVM_PATH"/scripts/versions.sh # Load the constants source "$SUBNET_EVM_PATH"/scripts/constants.sh -echo "Building Docker Image: $dockerhub_repo:$build_image_id based of $avalanche_version" -docker build -t "$dockerhub_repo:$build_image_id" "$SUBNET_EVM_PATH" -f "$SUBNET_EVM_PATH/Dockerfile" \ - --build-arg AVALANCHE_VERSION="$avalanche_version" \ - --build-arg SUBNET_EVM_COMMIT="$subnet_evm_commit" \ - --build-arg CURRENT_BRANCH="$current_branch" +echo "Building Docker Image: $DOCKERHUB_REPO:$BUILD_IMAGE_ID based of $AVALANCHEGO_VERSION" +docker build -t "$DOCKERHUB_REPO:$BUILD_IMAGE_ID" "$SUBNET_EVM_PATH" -f "$SUBNET_EVM_PATH/Dockerfile" \ + --build-arg AVALANCHE_VERSION="$AVALANCHEGO_VERSION" \ + --build-arg SUBNET_EVM_COMMIT="$SUBNET_EVM_COMMIT" \ + --build-arg CURRENT_BRANCH="$CURRENT_BRANCH" diff --git a/scripts/build_test.sh b/scripts/build_test.sh index bc8cd79fea..adb148a01e 100755 --- a/scripts/build_test.sh +++ b/scripts/build_test.sh @@ -20,5 +20,5 @@ source "$SUBNET_EVM_PATH"/scripts/constants.sh # We pass in the arguments to this script directly to enable easily passing parameters such as enabling race detection, # parallelism, and test coverage. -# DO NOT RUN "tests/e2e" since it's run by ginkgo -go test -coverprofile=coverage.out -covermode=atomic -timeout="30m" $@ $(go list ./... | grep -v tests/e2e) +# DO NOT RUN "tests/precompile" or "tests/load" since it's run by ginkgo +go test -coverprofile=coverage.out -covermode=atomic -timeout="30m" $@ $(go list ./... | grep -v tests/precompile | grep -v tests/load) diff --git a/scripts/constants.sh b/scripts/constants.sh index a58f910926..3a8aca35df 100644 --- a/scripts/constants.sh +++ b/scripts/constants.sh @@ -4,37 +4,37 @@ GOPATH="$(go env GOPATH)" # Avalabs docker hub -dockerhub_repo="avaplatform/avalanchego" +DOCKERHUB_REPO="avaplatform/avalanchego" # if this isn't a git repository (say building from a release), don't set our git constants. if [ ! -d .git ] then - current_branch="" - subnet_evm_commit="" - subnet_evm_commit_id="" + CURRENT_BRANCH="" + SUBNET_EVM_COMMIT="" + SUBNET_EVM_COMMIT_ID="" else # Current branch - current_branch=${CURRENT_BRANCH:-$(git describe --tags --exact-match 2> /dev/null || git symbolic-ref -q --short HEAD || git rev-parse --short HEAD || :)} + CURRENT_BRANCH=${CURRENT_BRANCH:-$(git describe --tags --exact-match 2> /dev/null || git symbolic-ref -q --short HEAD || git rev-parse --short HEAD || :)} # Image build id # # Use an abbreviated version of the full commit to tag the image. # WARNING: this will use the most recent commit even if there are un-committed changes present - subnet_evm_commit="$(git --git-dir="$SUBNET_EVM_PATH/.git" rev-parse HEAD || :)" - subnet_evm_commit_id="${subnet_evm_commit::8}" + SUBNET_EVM_COMMIT="$(git --git-dir="$SUBNET_EVM_PATH/.git" rev-parse HEAD || :)" + SUBNET_EVM_COMMIT_ID="${SUBNET_EVM_COMMIT::8}" fi -echo "Using branch: ${current_branch}" +echo "Using branch: ${CURRENT_BRANCH}" -build_image_id=${BUILD_IMAGE_ID:-"$avalanche_version-$subnet_evm_commit_id"} +BUILD_IMAGE_ID=${BUILD_IMAGE_ID:-"$AVALANCHEGO_VERSION-$SUBNET_EVM_COMMIT_ID"} # Static compilation -static_ld_flags='' +STATIC_LD_FLAGS='' if [ "${STATIC_COMPILATION:-}" = 1 ] then export CC=musl-gcc command -v $CC || ( echo $CC must be available for static compilation && exit 1 ) - static_ld_flags=' -extldflags "-static" -linkmode external ' + STATIC_LD_FLAGS=' -extldflags "-static" -linkmode external ' fi # Set the CGO flags to use the portable version of BLST diff --git a/scripts/generate_precompile.sh b/scripts/generate_precompile.sh new file mode 100755 index 0000000000..8876880659 --- /dev/null +++ b/scripts/generate_precompile.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -e + +# This script generates a Stateful Precompile stub based off of a Solidity ABI file. +# It first sets the necessary CGO_FLAGs for the BLST library used in AvalancheGo and +# then runs PrecompileGen. +if ! [[ "$0" =~ scripts/generate_precompile.sh ]]; then + echo "must be run from repository root, but got $0" + exit 255 +fi + +# Load the versions +SUBNET_EVM_PATH=$( + cd "$(dirname "${BASH_SOURCE[0]}")" + cd .. && pwd +) + +# Load the constants +source "$SUBNET_EVM_PATH"/scripts/constants.sh + +go run ./cmd/precompilegen/main.go $@ diff --git a/scripts/install_avalanchego_release.sh b/scripts/install_avalanchego_release.sh new file mode 100755 index 0000000000..ad0fcc66bc --- /dev/null +++ b/scripts/install_avalanchego_release.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +set -e + +# Load the versions +SUBNET_EVM_PATH=$( + cd "$(dirname "${BASH_SOURCE[0]}")" + cd .. && pwd +) +source "$SUBNET_EVM_PATH"/scripts/versions.sh + +# Load the constants +source "$SUBNET_EVM_PATH"/scripts/constants.sh + +VERSION=$AVALANCHEGO_VERSION + +############################ +# download avalanchego +# https://github.com/ava-labs/avalanchego/releases +GOARCH=$(go env GOARCH) +GOOS=$(go env GOOS) +BASEDIR=${BASE_DIR:-"/tmp/avalanchego-release"} +mkdir -p ${BASEDIR} +AVAGO_DOWNLOAD_URL=https://github.com/ava-labs/avalanchego/releases/download/${VERSION}/avalanchego-linux-${GOARCH}-${VERSION}.tar.gz +AVAGO_DOWNLOAD_PATH=${BASEDIR}/avalanchego-linux-${GOARCH}-${VERSION}.tar.gz +if [[ ${GOOS} == "darwin" ]]; then + AVAGO_DOWNLOAD_URL=https://github.com/ava-labs/avalanchego/releases/download/${VERSION}/avalanchego-macos-${VERSION}.zip + AVAGO_DOWNLOAD_PATH=${BASEDIR}/avalanchego-macos-${VERSION}.zip +fi + +AVALANCHEGO_BUILD_PATH=${AVALANCHEGO_BUILD_PATH:-${BASEDIR}/avalanchego-${VERSION}} +mkdir -p $AVALANCHEGO_BUILD_PATH + +if [[ ! -f ${AVAGO_DOWNLOAD_PATH} ]]; then + echo "downloading avalanchego ${VERSION} at ${AVAGO_DOWNLOAD_URL} to ${AVAGO_DOWNLOAD_PATH}" + curl -L ${AVAGO_DOWNLOAD_URL} -o ${AVAGO_DOWNLOAD_PATH} +fi +echo "extracting downloaded avalanchego to ${AVALANCHEGO_BUILD_PATH}" +if [[ ${GOOS} == "linux" ]]; then + mkdir -p ${AVALANCHEGO_BUILD_PATH} && tar xzvf ${AVAGO_DOWNLOAD_PATH} --directory ${AVALANCHEGO_BUILD_PATH} --strip-components 1 +elif [[ ${GOOS} == "darwin" ]]; then + unzip ${AVAGO_DOWNLOAD_PATH} -d ${AVALANCHEGO_BUILD_PATH} + mv ${AVALANCHEGO_BUILD_PATH}/build/* ${AVALANCHEGO_BUILD_PATH} + rm -rf ${AVALANCHEGO_BUILD_PATH}/build/ +fi + +AVALANCHEGO_PATH=${AVALANCHEGO_BUILD_PATH}/avalanchego +AVALANCHEGO_PLUGIN_DIR=${AVALANCHEGO_BUILD_PATH}/plugins + +echo "Installed AvalancheGo release ${VERSION}" +echo "AvalancheGo Path: ${AVALANCHEGO_PATH}" +echo "Plugin Dir: ${AVALANCHEGO_PLUGIN_DIR}" diff --git a/scripts/lint_allowed_geth_imports.sh b/scripts/lint_allowed_geth_imports.sh index cf1ba23e22..acd0f92003 100755 --- a/scripts/lint_allowed_geth_imports.sh +++ b/scripts/lint_allowed_geth_imports.sh @@ -8,7 +8,7 @@ set -o pipefail # 1. Recursively search through all go files for any lines that include a direct import from go-ethereum # 2. Sort the unique results # #. Print out the difference between the search results and the list of specified allowed package imports from geth. -extra_imports=$(grep -r --include='*.go' '"github.com/ethereum/go-ethereum/.*"' -o -h | sort -u | comm -23 - ./scripts/geth-allowed-packages.txt) +extra_imports=$(grep -r --include='*.go' --exclude-dir='simulator' '"github.com/ethereum/go-ethereum/.*"' -o -h | sort -u | comm -23 - ./scripts/geth-allowed-packages.txt) if [ ! -z "${extra_imports}" ]; then echo "new go-ethereum imports should be added to ./scripts/geth-allowed-packages.txt to prevent accidental imports:" echo "${extra_imports}" diff --git a/scripts/run.sh b/scripts/run.sh index 5e851297aa..699a7f4588 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -1,18 +1,10 @@ #!/usr/bin/env bash set -e -# e.g., -# -# run without e2e tests -# ./scripts/run.sh -# -# run without e2e tests, and with simulator -# RUN_SIMULATOR=true ./scripts/run.sh -# -# run without e2e tests with DEBUG log level -# AVALANCHE_LOG_LEVEL=DEBUG ./scripts/run.sh +# This script starts N nodes (TODO N instead of 5) and waits for ctrl-c to shutdown the process group of AvalancheGo processes +# Uses data directory to store all AvalancheGo data neatly in one location with minimal config overhead if ! [[ "$0" =~ scripts/run.sh ]]; then - echo "must be run from repository root" + echo "must be run from repository root, but got $0" exit 255 fi @@ -26,243 +18,38 @@ source "$SUBNET_EVM_PATH"/scripts/versions.sh # Load the constants source "$SUBNET_EVM_PATH"/scripts/constants.sh -VERSION=$avalanche_version - -# "ewoq" key -DEFAULT_ACCOUNT="0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" -GENESIS_ADDRESS=${GENESIS_ADDRESS-$DEFAULT_ACCOUNT} - -SKIP_NETWORK_RUNNER_START=${SKIP_NETWORK_RUNNER_START:-false} -SKIP_NETWORK_RUNNER_SHUTDOWN=${SKIP_NETWORK_RUNNER_SHUTDOWN:-true} -RUN_SIMULATOR=${RUN_SIMULATOR:-false} -ENABLE_SOLIDITY_TESTS=${ENABLE_SOLIDITY_TESTS:-false} -AVALANCHE_LOG_LEVEL=${AVALANCHE_LOG_LEVEL:-WARN} -ANR_VERSION=$network_runner_version -GINKGO_VERSION=$ginkgo_version - -# by default, "run.sh" should not run any tests... -# simulator tests are not implemented in ginkgo -# it instead runs external binary "simulator" -# so we exclude all e2e tests here -# ref. https://onsi.github.io/ginkgo/#spec-labels -GINKGO_LABEL_FILTER="!precompile-upgrade && !solidity-with-npx && !solidity-counter" -if [[ ${RUN_SIMULATOR} == true ]]; then - # only run "ping" tests, no other test - # because simulator itself will generate loads and run tests - GINKGO_LABEL_FILTER="ping" -fi -if [[ ${ENABLE_SOLIDITY_TESTS} == true ]]; then - GINKGO_LABEL_FILTER="solidity-with-npx" -fi - -echo "Running with:" -echo AVALANCHE_VERSION: ${VERSION} -echo ANR_VERSION: ${ANR_VERSION} -echo GINKGO_VERSION: ${GINKGO_VERSION} -echo GENESIS_ADDRESS: ${GENESIS_ADDRESS} -echo SKIP_NETWORK_RUNNER_START: ${SKIP_NETWORK_RUNNER_START} -echo SKIP_NETWORK_RUNNER_SHUTDOWN: ${SKIP_NETWORK_RUNNER_SHUTDOWN} -echo RUN_SIMULATOR: ${RUN_SIMULATOR} -echo ENABLE_SOLIDITY_TESTS: ${ENABLE_SOLIDITY_TESTS} -echo GINKGO_LABEL_FILTER: ${GINKGO_LABEL_FILTER} -echo AVALANCHE_LOG_LEVEL: ${AVALANCHE_LOG_LEVEL} - -############################ -# download avalanchego -# https://github.com/ava-labs/avalanchego/releases -GOARCH=$(go env GOARCH) -GOOS=$(go env GOOS) -BASEDIR=/tmp/subnet-evm-runner -mkdir -p ${BASEDIR} -AVAGO_DOWNLOAD_URL=https://github.com/ava-labs/avalanchego/releases/download/${VERSION}/avalanchego-linux-${GOARCH}-${VERSION}.tar.gz -AVAGO_DOWNLOAD_PATH=${BASEDIR}/avalanchego-linux-${GOARCH}-${VERSION}.tar.gz -if [[ ${GOOS} == "darwin" ]]; then - AVAGO_DOWNLOAD_URL=https://github.com/ava-labs/avalanchego/releases/download/${VERSION}/avalanchego-macos-${VERSION}.zip - AVAGO_DOWNLOAD_PATH=${BASEDIR}/avalanchego-macos-${VERSION}.zip -fi - -AVAGO_FILEPATH=${BASEDIR}/avalanchego-${VERSION} -if [[ ! -d ${AVAGO_FILEPATH} ]]; then - if [[ ! -f ${AVAGO_DOWNLOAD_PATH} ]]; then - echo "downloading avalanchego ${VERSION} at ${AVAGO_DOWNLOAD_URL} to ${AVAGO_DOWNLOAD_PATH}" - curl -L ${AVAGO_DOWNLOAD_URL} -o ${AVAGO_DOWNLOAD_PATH} - fi - echo "extracting downloaded avalanchego to ${AVAGO_FILEPATH}" - if [[ ${GOOS} == "linux" ]]; then - mkdir -p ${AVAGO_FILEPATH} && tar xzvf ${AVAGO_DOWNLOAD_PATH} --directory ${AVAGO_FILEPATH} --strip-components 1 - elif [[ ${GOOS} == "darwin" ]]; then - unzip ${AVAGO_DOWNLOAD_PATH} -d ${AVAGO_FILEPATH} - mv ${AVAGO_FILEPATH}/build/* ${AVAGO_FILEPATH} - rm -rf ${AVAGO_FILEPATH}/build/ - fi - find ${BASEDIR}/avalanchego-${VERSION} -fi - -AVALANCHEGO_PATH=${AVAGO_FILEPATH}/avalanchego -AVALANCHEGO_PLUGIN_DIR=${AVAGO_FILEPATH}/plugins - -################################# -# compile subnet-evm -# Check if SUBNET_EVM_COMMIT is set, if not retrieve the last commit from the repo. -# This is used in the Dockerfile to allow a commit hash to be passed in without -# including the .git/ directory within the Docker image. -subnet_evm_commit=${SUBNET_EVM_COMMIT:-$(git rev-list -1 HEAD)} - -# Build Subnet EVM, which is run as a subprocess -echo "Building Subnet EVM Version: $subnet_evm_version; GitCommit: $subnet_evm_commit" -go build \ - -ldflags "-X github.com/ava-labs/subnet_evm/plugin/evm.GitCommit=$subnet_evm_commit -X github.com/ava-labs/subnet_evm/plugin/evm.Version=$subnet_evm_version" \ - -o $AVALANCHEGO_PLUGIN_DIR/srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy \ - "plugin/"*.go - -################################# -# write subnet-evm genesis - -# Create genesis file to use in network (make sure to add your address to -# "alloc") -export CHAIN_ID=99999 -echo "creating genesis" - cat <$BASEDIR/genesis.json -{ - "config": { - "chainId": $CHAIN_ID, - "homesteadBlock": 0, - "eip150Block": 0, - "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0, - "muirGlacierBlock": 0, - "subnetEVMTimestamp": 0, - "feeConfig": { - "gasLimit": 20000000, - "minBaseFee": 1000000000, - "targetGas": 100000000, - "baseFeeChangeDenominator": 48, - "minBlockGasCost": 0, - "maxBlockGasCost": 10000000, - "targetBlockRate": 2, - "blockGasCostStep": 500000 - } - }, - "alloc": { - "${GENESIS_ADDRESS:2}": { - "balance": "0x52B7D2DCC80CD2E4000000" - } - }, - "nonce": "0x0", - "timestamp": "0x0", - "extraData": "0x00", - "gasLimit": "0x1312D00", - "difficulty": "0x0", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x0000000000000000000000000000000000000000", - "number": "0x0", - "gasUsed": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" -} +# Set up avalanche binary path and assume build directory is set +AVALANCHEGO_BUILD_PATH=${AVALANCHEGO_BUILD_PATH:-"$GOPATH/src/github.com/ava-labs/avalanchego/build"} +AVALANCHEGO_PATH=${AVALANCHEGO_PATH:-"$AVALANCHEGO_BUILD_PATH/avalanchego"} +AVALANCHEGO_PLUGIN_DIR=${AVALANCHEGO_PLUGIN_DIR:-"$AVALANCHEGO_BUILD_PATH/plugins"} +DATA_DIR=${DATA_DIR:-/tmp/subnet-evm-start-node/$(date "+%Y-%m-%d%:%H:%M:%S")} + +mkdir -p $DATA_DIR + +# Set the config file contents for the path passed in as the first argument +function _set_config(){ + cat <$1 + { + "network-id": "local", + "staking-enabled": false, + "health-check-frequency": "5s", + "plugin-dir": "$AVALANCHEGO_PLUGIN_DIR" + } EOF - -################################# -# download avalanche-network-runner -# https://github.com/ava-labs/avalanche-network-runner -ANR_REPO_PATH=github.com/ava-labs/avalanche-network-runner -# version set -go install -v ${ANR_REPO_PATH}@${ANR_VERSION} - -################################# -# run "avalanche-network-runner" server -GOPATH=$(go env GOPATH) -if [[ -z ${GOBIN+x} ]]; then - # no gobin set - BIN=${GOPATH}/bin/avalanche-network-runner -else - # gobin set - BIN=${GOBIN}/avalanche-network-runner -fi -echo "launch avalanche-network-runner in the background" -$BIN server \ - --log-level debug \ - --port=":12342" \ - --grpc-gateway-port=":12343" & -PID=${!} - -run_ginkgo() { - echo "building e2e.test" - # to install the ginkgo binary (required for test build and run) - go install -v github.com/onsi/ginkgo/v2/ginkgo@${GINKGO_VERSION} - ginkgo -h - - ACK_GINKGO_RC=true ginkgo build ./tests/e2e - - # By default, it runs all e2e test cases! - # Use "--ginkgo.skip" to skip tests. - # Use "--ginkgo.focus" to select tests. - echo "running e2e tests with SKIP_NETWORK_RUNNER_START ${SKIP_NETWORK_RUNNER_START}" - ./tests/e2e/e2e.test \ - --ginkgo.vv \ - --network-runner-log-level debug \ - --network-runner-grpc-endpoint="0.0.0.0:12342" \ - --avalanchego-path=${AVALANCHEGO_PATH} \ - --avalanchego-plugin-dir=${AVALANCHEGO_PLUGIN_DIR} \ - --avalanchego-log-level=${AVALANCHE_LOG_LEVEL} \ - --vm-genesis-path=$BASEDIR/genesis.json \ - --output-path=$BASEDIR/avalanchego-${VERSION}/output.yaml \ - --skip-network-runner-start=${SKIP_NETWORK_RUNNER_START} \ - --skip-network-runner-shutdown=${SKIP_NETWORK_RUNNER_SHUTDOWN} \ - --ginkgo.label-filter="${GINKGO_LABEL_FILTER}" } -run_simulator() { - ################################# - echo "building simulator" - pushd ./cmd/simulator - go install -v . - popd - - echo "running simulator" - simulator \ - --cluster-info-yaml=$BASEDIR/avalanchego-${VERSION}/output.yaml \ - --keys=./cmd/simulator/.simulator/keys \ - --timeout=30s \ - --concurrency=10 \ - --base-fee=25 \ - --priority-fee=1 +function execute_cmd() { + echo "Executing command: $@" + $@ } +NODE_NAME="node1" +NODE_DATA_DIR="$DATA_DIR/$NODE_NAME" +echo "Creating data directory: $NODE_DATA_DIR" +mkdir -p $NODE_DATA_DIR +NODE_CONFIG_FILE_PATH="$NODE_DATA_DIR/config.json" +_set_config $NODE_CONFIG_FILE_PATH -# whether start network via parser/main.go or e2e.test ginkgo -# run the tests with label filter -echo "running ginkgo" -run_ginkgo -# to fail the script if ginkgo failed -EXIT_CODE=$? - - -# e.g., "RUN_SIMULATOR=true scripts/run.sh" to launch network runner + simulator -if [[ ${RUN_SIMULATOR} == true ]]; then - run_simulator - # to fail the script if simulator failed - EXIT_CODE=$? -fi - -################################# -if [[ ${SKIP_NETWORK_RUNNER_SHUTDOWN} == false ]]; then - # just in case tests are aborted, manually terminate them again - echo "network-runner RPC server was running on PID ${PID} as test mode; terminating the process..." - pkill -P ${PID} || true - kill -2 ${PID} - pkill -9 -f srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy || true # in case pkill didn't work -else - echo "network-runner RPC server is running on PID ${PID}..." - echo "" - echo "use the following command to terminate:" - echo "" - echo "pkill -P ${PID} && kill -2 ${PID} && pkill -9 -f srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy" - echo "" -fi +CMD="$AVALANCHEGO_PATH --data-dir=$NODE_DATA_DIR --config-file=$NODE_CONFIG_FILE_PATH" -exit ${EXIT_CODE} +execute_cmd $CMD diff --git a/scripts/run_ginkgo.sh b/scripts/run_ginkgo.sh new file mode 100755 index 0000000000..db9bd61bf2 --- /dev/null +++ b/scripts/run_ginkgo.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -e + +# This script assumes that an AvalancheGo and Subnet-EVM binaries are available in the standard location +# within the $GOPATH +# The AvalancheGo and PluginDir paths can be specified via the environment variables used in ./scripts/run.sh. + +# Load the versions +SUBNET_EVM_PATH=$( + cd "$(dirname "${BASH_SOURCE[0]}")" + cd .. && pwd +) + +source "$SUBNET_EVM_PATH"/scripts/constants.sh + +source "$SUBNET_EVM_PATH"/scripts/versions.sh + +# Build ginkgo +echo "building precompile.test" +# to install the ginkgo binary (required for test build and run) +go install -v github.com/onsi/ginkgo/v2/ginkgo@${GINKGO_VERSION} + +ACK_GINKGO_RC=true ginkgo build ./tests/precompile ./tests/load + +# By default, it runs all e2e test cases! +# Use "--ginkgo.skip" to skip tests. +# Use "--ginkgo.focus" to select tests. +./tests/precompile/precompile.test \ + --ginkgo.vv \ + --ginkgo.label-filter=${GINKGO_LABEL_FILTER:-""} + +./tests/load/load.test \ + --ginkgo.vv \ + --ginkgo.label-filter=${GINKGO_LABEL_FILTER:-""} diff --git a/scripts/run_simulator.sh b/scripts/run_simulator.sh new file mode 100755 index 0000000000..8cd218df0b --- /dev/null +++ b/scripts/run_simulator.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -e + +echo "Beginning simulator script" + +if ! [[ "$0" =~ scripts/run_simulator.sh ]]; then + echo "must be run from repository root, but got $0" + exit 255 +fi + +# Load the versions +SUBNET_EVM_PATH=$( + cd "$(dirname "${BASH_SOURCE[0]}")" + cd .. && pwd +) +source "$SUBNET_EVM_PATH"/scripts/versions.sh + +# Load the constants +source "$SUBNET_EVM_PATH"/scripts/constants.sh + +run_simulator() { + ################################# + echo "building simulator" + pushd ./cmd/simulator + go install -v . + echo + + popd + echo "running simulator from $PWD" + simulator \ + --rpc-endpoints=$RPC_ENDPOINTS \ + --keys=./cmd/simulator/.simulator/keys \ + --timeout=30s \ + --concurrency=10 \ + --base-fee=300 \ + --priority-fee=100 +} + +run_simulator diff --git a/scripts/versions.sh b/scripts/versions.sh index d0b6c05a22..708a8c549a 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -1,11 +1,10 @@ #!/usr/bin/env bash -# Set up the versions to be used -subnet_evm_version=${SUBNET_EVM_VERSION:-'v0.4.7'} +# Set up the versions to be used - populate ENV variables only if they are not already populated +SUBNET_EVM_VERSION=${SUBNET_EVM_VERSION:-'v0.4.9'} # Don't export them as they're used in the context of other calls -avalanche_version=${AVALANCHE_VERSION:-'v1.9.5'} -network_runner_version=${NETWORK_RUNNER_VERSION:-'35be10cd3867a94fbe960a1c14a455f179de60d9'} -ginkgo_version=${GINKGO_VERSION:-'v2.2.0'} +AVALANCHEGO_VERSION=${AVALANCHE_VERSION:-'v1.9.8'} +GINKGO_VERSION=${GINKGO_VERSION:-'v2.2.0'} # This won't be used, but it's here to make code syncs easier -latest_coreth_version=0.11.3 +LATEST_CORETH_VERSION=0.11.6 diff --git a/sync/client/client.go b/sync/client/client.go index d4cde79bc4..773baf54c5 100644 --- a/sync/client/client.go +++ b/sync/client/client.go @@ -325,14 +325,14 @@ func (c *client) get(ctx context.Context, request message.Request, parseFn parse start time.Time = time.Now() ) if len(c.stateSyncNodes) == 0 { - response, nodeID, err = c.networkClient.RequestAny(StateSyncVersion, requestBytes) + response, nodeID, err = c.networkClient.SendAppRequestAny(StateSyncVersion, requestBytes) } else { // get the next nodeID using the nodeIdx offset. If we're out of nodes, loop back to 0 // we do this every attempt to ensure we get a different node each time if possible. nodeIdx := atomic.AddUint32(&c.stateSyncNodeIdx, 1) nodeID = c.stateSyncNodes[nodeIdx%uint32(len(c.stateSyncNodes))] - response, err = c.networkClient.Request(nodeID, requestBytes) + response, err = c.networkClient.SendAppRequest(nodeID, requestBytes) } metric.UpdateRequestLatency(time.Since(start)) diff --git a/sync/client/mock_network.go b/sync/client/mock_network.go index 16981e4471..b9729350fa 100644 --- a/sync/client/mock_network.go +++ b/sync/client/mock_network.go @@ -28,7 +28,7 @@ type mockNetwork struct { nodesRequested []ids.NodeID } -func (t *mockNetwork) RequestAny(minVersion *version.Application, request []byte) ([]byte, ids.NodeID, error) { +func (t *mockNetwork) SendAppRequestAny(minVersion *version.Application, request []byte) ([]byte, ids.NodeID, error) { if len(t.response) == 0 { return nil, ids.EmptyNodeID, errors.New("no mocked response to return in mockNetwork") } @@ -39,7 +39,7 @@ func (t *mockNetwork) RequestAny(minVersion *version.Application, request []byte return response, ids.EmptyNodeID, err } -func (t *mockNetwork) Request(nodeID ids.NodeID, request []byte) ([]byte, error) { +func (t *mockNetwork) SendAppRequest(nodeID ids.NodeID, request []byte) ([]byte, error) { if len(t.response) == 0 { return nil, errors.New("no mocked response to return in mockNetwork") } @@ -77,6 +77,10 @@ func (t *mockNetwork) Gossip([]byte) error { panic("not implemented") // we don't care about this function for this test } +func (t *mockNetwork) SendCrossChainRequest(chainID ids.ID, request []byte) ([]byte, error) { + panic("not implemented") // we don't care about this function for this test +} + func (t *mockNetwork) mockResponse(times uint8, callback func(), response []byte) { t.response = make([][]byte, times) for i := uint8(0); i < times; i++ { diff --git a/sync/handlers/code_request_test.go b/sync/handlers/code_request_test.go index cbf6b277bb..a7094742e1 100644 --- a/sync/handlers/code_request_test.go +++ b/sync/handlers/code_request_test.go @@ -94,7 +94,7 @@ func TestCodeRequestHandler(t *testing.T) { responseBytes, err := codeRequestHandler.OnCodeRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) assert.NoError(t, err) - // If the expected resposne is empty, assert that the handler returns an empty response and return early. + // If the expected response is empty, assert that the handler returns an empty response and return early. if len(expectedResponse) == 0 { assert.Len(t, responseBytes, 0, "expected response to be empty") return diff --git a/sync/handlers/handler.go b/sync/handlers/handler.go index 5c0389cf40..867941aa83 100644 --- a/sync/handlers/handler.go +++ b/sync/handlers/handler.go @@ -4,20 +4,11 @@ package handlers import ( - "context" - - "github.com/ava-labs/avalanchego/codec" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/subnet-evm/core/state/snapshot" "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/plugin/evm/message" - "github.com/ava-labs/subnet-evm/sync/handlers/stats" - "github.com/ava-labs/subnet-evm/trie" "github.com/ethereum/go-ethereum/common" ) -var _ message.RequestHandler = &syncHandler{} - type BlockProvider interface { GetBlock(common.Hash, uint64) *types.Block } @@ -30,35 +21,3 @@ type SyncDataProvider interface { BlockProvider SnapshotProvider } - -type syncHandler struct { - stateTrieLeafsRequestHandler *LeafsRequestHandler - blockRequestHandler *BlockRequestHandler - codeRequestHandler *CodeRequestHandler -} - -// NewSyncHandler constructs the handler for serving state sync. -func NewSyncHandler( - provider SyncDataProvider, - evmTrieDB *trie.Database, - networkCodec codec.Manager, - stats stats.HandlerStats, -) message.RequestHandler { - return &syncHandler{ - stateTrieLeafsRequestHandler: NewLeafsRequestHandler(evmTrieDB, provider, networkCodec, stats), - blockRequestHandler: NewBlockRequestHandler(provider, networkCodec, stats), - codeRequestHandler: NewCodeRequestHandler(evmTrieDB.DiskDB(), networkCodec, stats), - } -} - -func (s *syncHandler) HandleTrieLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest message.LeafsRequest) ([]byte, error) { - return s.stateTrieLeafsRequestHandler.OnLeafsRequest(ctx, nodeID, requestID, leafsRequest) -} - -func (s *syncHandler) HandleBlockRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, blockRequest message.BlockRequest) ([]byte, error) { - return s.blockRequestHandler.OnBlockRequest(ctx, nodeID, requestID, blockRequest) -} - -func (s *syncHandler) HandleCodeRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, codeRequest message.CodeRequest) ([]byte, error) { - return s.codeRequestHandler.OnCodeRequest(ctx, nodeID, requestID, codeRequest) -} diff --git a/tests/curl.go b/tests/curl.go deleted file mode 100644 index e51e074c1e..0000000000 --- a/tests/curl.go +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. -// -// This file is a derived work, based on github.com/etcd-io/etcd/pkg/expect. - -package tests - -import ( - "bufio" - "fmt" - "os" - "os/exec" - "strings" - "sync" - "syscall" - "time" - - "github.com/creack/pty" -) - -// Sends a POST request to the endpoint with JSON body "dataRaw", -// and returns an error if "expect" string is not found in the response. -func CURLPost(endpoint string, dataRaw string, expect string) (string, error) { - cmdArgs := []string{ - "curl", - "-L", endpoint, - "-m", "10", - "-H", "Content-Type: application/json", - "-X", "POST", - "-d", dataRaw, - } - - ex, err := newExpect(cmdArgs[0], cmdArgs[1:]...) - if err != nil { - return "", err - } - defer ex.Close() - - return ex.Expect(expect) -} - -const DEBUG_LINES_TAIL = 40 - -type expectProcess struct { - cmd *exec.Cmd - fpty *os.File - wg sync.WaitGroup - - mu sync.RWMutex // protects lines and err - lines []string - err error - - stopSignal os.Signal -} - -func newExpect(name string, args ...string) (ep *expectProcess, err error) { - cmd := exec.Command(name, args...) - ep = &expectProcess{ - cmd: cmd, - stopSignal: syscall.SIGKILL, - } - ep.cmd.Stderr = ep.cmd.Stdout - ep.cmd.Stdin = nil - - if ep.fpty, err = pty.Start(ep.cmd); err != nil { - return nil, err - } - - ep.wg.Add(1) - go ep.read() - return ep, nil -} - -func (ep *expectProcess) read() { - defer ep.wg.Done() - printDebugLines := os.Getenv("EXPECT_DEBUG") != "" - r := bufio.NewReader(ep.fpty) - for { - l, err := r.ReadString('\n') - ep.mu.Lock() - if l != "" { - if printDebugLines { - fmt.Printf("%s-%d: %s", ep.cmd.Path, ep.cmd.Process.Pid, l) - } - ep.lines = append(ep.lines, l) - } - if err != nil { - ep.err = err - ep.mu.Unlock() - break - } - ep.mu.Unlock() - } -} - -func (ep *expectProcess) ExpectFunc(f func(string) bool) (string, error) { - i := 0 - - for { - ep.mu.Lock() - for i < len(ep.lines) { - line := ep.lines[i] - i++ - if f(line) { - ep.mu.Unlock() - return line, nil - } - } - if ep.err != nil { - ep.mu.Unlock() - break - } - ep.mu.Unlock() - time.Sleep(time.Millisecond * 100) - } - ep.mu.Lock() - lastLinesIndex := len(ep.lines) - DEBUG_LINES_TAIL - if lastLinesIndex < 0 { - lastLinesIndex = 0 - } - lastLines := strings.Join(ep.lines[lastLinesIndex:], "") - ep.mu.Unlock() - return "", fmt.Errorf("match not found."+ - " Set EXPECT_DEBUG for more info Err: %v, last lines:\n%s", - ep.err, lastLines) -} - -// Expect returns the first line containing the given string. -func (ep *expectProcess) Expect(s string) (string, error) { - return ep.ExpectFunc(func(txt string) bool { return strings.Contains(txt, s) }) -} - -// Stop kills the expect process and waits for it to exit. -func (ep *expectProcess) Stop() error { return ep.close(true) } - -// Signal sends a signal to the expect process -func (ep *expectProcess) Signal(sig os.Signal) error { - return ep.cmd.Process.Signal(sig) -} - -func (ep *expectProcess) Close() error { return ep.close(false) } - -func (ep *expectProcess) close(kill bool) error { - if ep.cmd == nil { - return ep.err - } - if kill { - ep.Signal(ep.stopSignal) - } - - err := ep.cmd.Wait() - ep.fpty.Close() - ep.wg.Wait() - - if err != nil { - if !kill && strings.Contains(err.Error(), "exit status") { - // non-zero exit code - err = nil - } else if kill && strings.Contains(err.Error(), "signal:") { - err = nil - } - } - - ep.cmd = nil - return err -} - -func (ep *expectProcess) ProcessError() error { - if strings.Contains(ep.err.Error(), "input/output error") { - // TODO: The expect library should not return - // `/dev/ptmx: input/output error` when process just exits. - return nil - } - return ep.err -} - -func (ep *expectProcess) Lines() []string { - ep.mu.RLock() - defer ep.mu.RUnlock() - return ep.lines -} diff --git a/tests/curl_test.go b/tests/curl_test.go deleted file mode 100644 index 1324d3b985..0000000000 --- a/tests/curl_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package tests - -import "testing" - -func TestExpectFunc(t *testing.T) { - ep, err := newExpect("echo", "hello world") - if err != nil { - t.Fatal(err) - } - wstr := "hello world\r\n" - l, eerr := ep.ExpectFunc(func(a string) bool { return len(a) > 10 }) - if eerr != nil { - t.Fatal(eerr) - } - if l != wstr { - t.Fatalf(`got "%v", expected "%v"`, l, wstr) - } - if cerr := ep.Close(); cerr != nil { - t.Fatal(cerr) - } -} - -func TestEcho(t *testing.T) { - ep, err := newExpect("echo", "hello world") - if err != nil { - t.Fatal(err) - } - l, eerr := ep.Expect("world") - if eerr != nil { - t.Fatal(eerr) - } - wstr := "hello world" - if l[:len(wstr)] != wstr { - t.Fatalf(`got "%v", expected "%v"`, l, wstr) - } - if cerr := ep.Close(); cerr != nil { - t.Fatal(cerr) - } - if _, eerr = ep.Expect("..."); eerr == nil { - t.Fatalf("expected error on closed expect process") - } -} diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go deleted file mode 100644 index 7ebe67e567..0000000000 --- a/tests/e2e/e2e_test.go +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// e2e implements the e2e tests. -package e2e - -import ( - "context" - "flag" - "fmt" - "os" - "testing" - "time" - - runner_sdk "github.com/ava-labs/avalanche-network-runner-sdk" - runner_sdk_rpcpb "github.com/ava-labs/avalanche-network-runner-sdk/rpcpb" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/subnet-evm/tests/e2e/utils" - ginkgo "github.com/onsi/ginkgo/v2" - "github.com/onsi/gomega" - - _ "github.com/ava-labs/subnet-evm/tests/e2e/ping" - _ "github.com/ava-labs/subnet-evm/tests/e2e/solidity" -) - -func TestE2E(t *testing.T) { - gomega.RegisterFailHandler(ginkgo.Fail) - ginkgo.RunSpecs(t, "subnet-evm e2e test suites") -} - -type networkClient struct { - client runner_sdk.Client -} - -var ( - networkRunnerLogLevel string - gRPCEp string - gRPCGatewayEp string - - // sets the "avalanchego" exec path - avalanchegoExecPath string - avalanchegoPluginDir string - avalanchegoLogLevel string - vmGenesisPath string - - outputFile string - - skipNetworkRunnerStart bool - skipNetworkRunnerShutdown bool -) - -func init() { - flag.StringVar( - &networkRunnerLogLevel, - "network-runner-log-level", - "info", - "gRPC server endpoint", - ) - flag.StringVar( - &gRPCEp, - "network-runner-grpc-endpoint", - "0.0.0.0:8080", - "gRPC server endpoint", - ) - flag.StringVar( - &gRPCGatewayEp, - "network-runner-grpc-gateway-endpoint", - "0.0.0.0:8081", - "gRPC gateway endpoint", - ) - - flag.StringVar( - &avalanchegoExecPath, - "avalanchego-path", - "", - "avalanchego executable path", - ) - flag.StringVar( - &avalanchegoPluginDir, - "avalanchego-plugin-dir", - "", - "avalanchego plugin directory", - ) - flag.StringVar( - &avalanchegoLogLevel, - "avalanchego-log-level", - "info", - "avalanchego log level", - ) - flag.StringVar( - &vmGenesisPath, - "vm-genesis-path", - "", - "VM genesis file path", - ) - flag.StringVar( - &outputFile, - "output-path", - "", - "output YAML path to write local cluster information", - ) - - flag.BoolVar( - &skipNetworkRunnerStart, - "skip-network-runner-start", - false, - "'true' to skip network runner start", - ) - flag.BoolVar( - &skipNetworkRunnerShutdown, - "skip-network-runner-shutdown", - false, - "'true' to skip network runner shutdown", - ) -} - -const vmName = "subnetevm" - -var vmID ids.ID - -func init() { - // TODO: add "getVMID" util function in avalanchego and import from "avalanchego" - b := make([]byte, 32) - copy(b, []byte(vmName)) - var err error - vmID, err = ids.ToID(b) - if err != nil { - panic(err) - } -} - -var subnetEVMRPCEps []string - -var _ = ginkgo.BeforeSuite(func() { - networkClient := createNetworkClient() - - if skipNetworkRunnerStart { - utils.Outf("{{green}}skipped 'start'{{/}}\n") - return - } - - networkClient.startNetwork() - networkClient.checkHealth() -}) - -func createNetworkClient() *networkClient { - runnerCli, err := runner_sdk.New(runner_sdk.Config{ - LogLevel: networkRunnerLogLevel, - Endpoint: gRPCEp, - DialTimeout: 10 * time.Second, - }) - gomega.Expect(err).Should(gomega.BeNil()) - - utils.SetOutputFile(outputFile) - utils.SetExecPath(avalanchegoExecPath) - utils.SetPluginDir(avalanchegoPluginDir) - utils.SetVmGenesisPath(vmGenesisPath) - utils.SetSkipNetworkRunnerShutdown(skipNetworkRunnerShutdown) - utils.SetClient(runnerCli) - - // Set AVALANCHEGO_PATH for the solidity suite - os.Setenv("AVALANCHEGO_PATH", avalanchegoExecPath) - - return &networkClient{client: runnerCli} -} - -func (n *networkClient) startNetwork() { - utils.Outf("{{green}}sending 'start' with binary path:{{/}} %q\n", utils.GetExecPath()) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - resp, err := n.client.Start( - ctx, - utils.GetExecPath(), - runner_sdk.WithPluginDir(utils.GetPluginDir()), - runner_sdk.WithGlobalNodeConfig(fmt.Sprintf(`{"log-level":"%s"}`, avalanchegoLogLevel)), - runner_sdk.WithNumNodes(5), - runner_sdk.WithBlockchainSpecs( - []*runner_sdk_rpcpb.BlockchainSpec{ - { - VmName: vmName, - Genesis: utils.GetVmGenesisPath(), - }, - }, - )) - cancel() - gomega.Expect(err).Should(gomega.BeNil()) - utils.Outf("{{green}}successfully started:{{/}} %+v\n", resp.ClusterInfo.NodeNames) -} - -func (n *networkClient) checkHealth() { - // TODO: network runner health should imply custom VM healthiness - // or provide a separate API for custom VM healthiness - // "start" is async, so wait some time for cluster health - utils.Outf("\n{{magenta}}sleeping before checking custom VM status...{{/}}\n") - time.Sleep(2 * time.Minute) - - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) - _, err := n.client.Health(ctx) - cancel() - gomega.Expect(err).Should(gomega.BeNil()) - - subnetEVMRPCEps = make([]string, 0) - blockchainID, logsDir := "", "" - pid := 0 - - // wait up to 5-minute for custom VM installation - utils.Outf("\n{{magenta}}waiting for all custom VMs to report healthy...{{/}}\n") - ctx, cancel = context.WithTimeout(context.Background(), 5*time.Minute) -done: - for ctx.Err() == nil { - select { - case <-ctx.Done(): - break done - case <-time.After(5 * time.Second): - } - - utils.Outf("{{magenta}}checking custom VM status{{/}}\n") - cctx, ccancel := context.WithTimeout(context.Background(), 2*time.Minute) - resp, err := n.client.Status(cctx) - ccancel() - gomega.Expect(err).Should(gomega.BeNil()) - - // all logs are stored under root data dir - logsDir = resp.GetClusterInfo().GetRootDataDir() - - // ANR server pid - pid = int(resp.GetClusterInfo().GetPid()) - - for blkChainID, vmInfo := range resp.ClusterInfo.CustomChains { - if vmInfo.VmId == vmID.String() { - blockchainID = blkChainID - utils.Outf("{{blue}}subnet-evm is ready:{{/}} %+v\n", vmInfo) - break done - } - } - } - gomega.Expect(ctx.Err()).Should(gomega.BeNil()) - cancel() - - gomega.Expect(blockchainID).Should(gomega.Not(gomega.BeEmpty())) - gomega.Expect(logsDir).Should(gomega.Not(gomega.BeEmpty())) - - cctx, ccancel := context.WithTimeout(context.Background(), 2*time.Minute) - uris, err := n.client.URIs(cctx) - ccancel() - gomega.Expect(err).Should(gomega.BeNil()) - utils.Outf("{{blue}}avalanche HTTP RPCs URIs:{{/}} %q\n", uris) - - for _, u := range uris { - rpcEP := fmt.Sprintf("%s/ext/bc/%s/rpc", u, blockchainID) - subnetEVMRPCEps = append(subnetEVMRPCEps, rpcEP) - utils.Outf("{{blue}}avalanche subnet-evm RPC:{{/}} %q\n", rpcEP) - } - - utils.Outf("{{blue}}{{bold}}writing output %q with PID %d{{/}}\n", utils.GetOutputPath(), pid) - ci := utils.ClusterInfo{ - URIs: uris, - Endpoint: fmt.Sprintf("/ext/bc/%s", blockchainID), - PID: pid, - LogsDir: logsDir, - SubnetEVMRPCEndpoints: subnetEVMRPCEps, - } - utils.SetClusterInfo(ci) - gomega.Expect(ci.Save(utils.GetOutputPath())).Should(gomega.BeNil()) - - b, err := os.ReadFile(utils.GetOutputPath()) - gomega.Expect(err).Should(gomega.BeNil()) - utils.Outf("\n{{blue}}$ cat %s:{{/}}\n%s\n", utils.GetOutputPath(), string(b)) -} - -var _ = ginkgo.AfterSuite(func() { - if utils.GetSkipNetworkRunnerShutdown() { - return - } - - // if cluster is running, shut it down - if isRunnerUp() { - gomega.Expect(stopNetwork()).Should(gomega.BeNil()) - } - gomega.Expect(closeClient()).Should(gomega.BeNil()) -}) - -func isRunnerUp() bool { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - _, err := utils.GetClient().Health(ctx) - cancel() - return err == nil -} - -func stopNetwork() error { - utils.Outf("{{red}}shutting down network{{/}}\n") - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) - _, err := utils.GetClient().Stop(ctx) - cancel() - return err -} - -func closeClient() error { - utils.Outf("{{red}}shutting down client{{/}}\n") - return utils.GetClient().Close() -} diff --git a/tests/e2e/ping/suites.go b/tests/e2e/ping/suites.go deleted file mode 100644 index 62cd7a6e61..0000000000 --- a/tests/e2e/ping/suites.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Implements ping tests, requires network-runner cluster. -package ping - -import ( - "context" - "time" - - "github.com/ava-labs/subnet-evm/tests/e2e/utils" - - ginkgo "github.com/onsi/ginkgo/v2" - "github.com/onsi/gomega" -) - -var _ = utils.DescribeLocal("[Ping]", func() { - ginkgo.It("can ping network-runner RPC server", ginkgo.Label("ping"), func() { - runnerCli := utils.GetClient() - gomega.Expect(runnerCli).ShouldNot(gomega.BeNil()) - - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - _, err := runnerCli.Ping(ctx) - cancel() - gomega.Expect(err).Should(gomega.BeNil()) - }) -}) diff --git a/tests/e2e/runner/runner.go b/tests/e2e/runner/runner.go deleted file mode 100644 index 377814b71d..0000000000 --- a/tests/e2e/runner/runner.go +++ /dev/null @@ -1,226 +0,0 @@ -package runner - -import ( - "context" - "errors" - "fmt" - "os" - "time" - - client "github.com/ava-labs/avalanche-network-runner-sdk" - "github.com/ava-labs/avalanche-network-runner-sdk/rpcpb" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/subnet-evm/tests/e2e/utils" - - "sigs.k8s.io/yaml" -) - -type clusterInfo struct { - URIs []string `json:"uris"` - Endpoint string `json:"endpoint"` - PID int `json:"pid"` - LogsDir string `json:"logsDir"` -} - -const fsModeWrite = 0o600 - -func (ci clusterInfo) Save(p string) error { - ob, err := yaml.Marshal(ci) - if err != nil { - return err - } - return os.WriteFile(p, ob, fsModeWrite) -} - -func startRunner(grpcEp string, execPath string, vmName string, genesisPath string, pluginDir string) error { - cli, err := client.New(client.Config{ - LogLevel: "info", - Endpoint: grpcEp, - DialTimeout: 10 * time.Second, - }) - if err != nil { - return err - } - - utils.Outf("{{green}}tests/e2e/runner sending 'start' with binary path:{{/}} %q\n", execPath) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) - resp, err := cli.Start( - ctx, - execPath, - client.WithPluginDir(pluginDir), - client.WithBlockchainSpecs([]*rpcpb.BlockchainSpec{ - { - VmName: vmName, - Genesis: genesisPath, - }, - }), - ) - cancel() - if err != nil { - return err - } - utils.Outf("{{green}}successfully started:{{/}} %+v\n", resp.ClusterInfo.NodeNames) - return nil -} - -func WaitForCustomVm(grpcEp string, vmId ids.ID) (string, string, int, error) { - cli, err := client.New(client.Config{ - LogLevel: "info", - Endpoint: grpcEp, - DialTimeout: 10 * time.Second, - }) - if err != nil { - return "", "", 0, err - } - - blockchainID, logsDir := "", "" - pid := 0 - - // wait up to 5-minute for custom VM installation - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) -done: - for ctx.Err() == nil { - select { - case <-ctx.Done(): - break done - case <-time.After(5 * time.Second): - } - - utils.Outf("{{magenta}}checking custom VM status{{/}}\n") - cctx, ccancel := context.WithTimeout(context.Background(), 2*time.Minute) - resp, err := cli.Health(cctx) - ccancel() - if err != nil { - cancel() - return "", "", 0, err - } - - if !resp.ClusterInfo.Healthy { - continue - } - - if !resp.ClusterInfo.CustomChainsHealthy { - continue - } - - // all logs are stored under root data dir - logsDir = resp.GetClusterInfo().GetRootDataDir() - - // ANR server pid - pid = int(resp.GetClusterInfo().GetPid()) - - for chainID, chainInfo := range resp.ClusterInfo.CustomChains { - if chainInfo.VmId == vmId.String() { - blockchainID = chainID - utils.Outf("{{blue}}subnet-evm is ready:{{/}} %+v\n", chainInfo) - break done - } - } - } - err = ctx.Err() - if err != nil { - cancel() - return "", "", 0, err - } - cancel() - - if blockchainID == "" { - return "", "", 0, errors.New("BlockchainId not found") - } - if logsDir == "" { - return "", "", 0, errors.New("logsDir not found") - } - if pid == 0 { - return "", "", pid, errors.New("pid not found") - } - return blockchainID, logsDir, pid, nil -} - -func SaveClusterInfo(grpcEp string, blockchainId string, logsDir string, pid int) (clusterInfo, error) { - cli, err := client.New(client.Config{ - LogLevel: "info", - Endpoint: grpcEp, - DialTimeout: 10 * time.Second, - }) - if err != nil { - return clusterInfo{}, err - } - - cctx, ccancel := context.WithTimeout(context.Background(), 2*time.Minute) - uris, err := cli.URIs(cctx) - ccancel() - if err != nil { - return clusterInfo{}, err - } - utils.Outf("{{blue}}avalanche HTTP RPCs URIs:{{/}} %q\n", uris) - - subnetEVMRPCEps := make([]string, 0) - for _, u := range uris { - rpcEP := fmt.Sprintf("%s/ext/bc/%s/rpc", u, blockchainId) - subnetEVMRPCEps = append(subnetEVMRPCEps, rpcEP) - utils.Outf("{{blue}}avalanche subnet-evm RPC:{{/}} %q\n", rpcEP) - } - - ci := clusterInfo{ - URIs: uris, - Endpoint: fmt.Sprintf("/ext/bc/%s", blockchainId), - PID: pid, - LogsDir: logsDir, - } - err = ci.Save(utils.GetOutputPath()) - if err != nil { - return clusterInfo{}, err - } - return ci, nil -} - -func StartNetwork(grpcEp string, execPath string, vmId ids.ID, vmName string, genesisPath string, pluginDir string) (clusterInfo, error) { - fmt.Println("Starting network") - startRunner(grpcEp, execPath, vmName, genesisPath, pluginDir) - - blockchainId, logsDir, pid, err := WaitForCustomVm(grpcEp, vmId) - if err != nil { - return clusterInfo{}, err - } - fmt.Println("Got custom vm") - - return SaveClusterInfo(grpcEp, blockchainId, logsDir, pid) -} - -func StopNetwork(grpcEp string) error { - cli, err := client.New(client.Config{ - LogLevel: "info", - Endpoint: grpcEp, - DialTimeout: 10 * time.Second, - }) - if err != nil { - return err - } - - utils.Outf("{{red}}shutting down network{{/}}\n") - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) - _, err = cli.Stop(ctx) - cancel() - return err -} - -func ShutdownClient() error { - utils.Outf("{{red}}shutting down client{{/}}\n") - return nil -} - -func IsRunnerUp(grpcEp string) bool { - cli, err := client.New(client.Config{ - LogLevel: "info", - Endpoint: grpcEp, - DialTimeout: 10 * time.Second, - }) - if err != nil { - return false - } - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - _, err = cli.Health(ctx) - cancel() - return err == nil -} diff --git a/tests/e2e/solidity/suites.go b/tests/e2e/solidity/suites.go deleted file mode 100644 index 0d641112ff..0000000000 --- a/tests/e2e/solidity/suites.go +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Implements solidity tests. -package solidity - -import ( - "fmt" - "os" - "os/exec" - - "github.com/ava-labs/subnet-evm/plugin/evm" - "github.com/ava-labs/subnet-evm/tests/e2e/runner" - "github.com/ava-labs/subnet-evm/tests/e2e/utils" - - ginkgo "github.com/onsi/ginkgo/v2" - "github.com/onsi/gomega" -) - -const vmName = "subnetevm" - -// network-runner-grpc-endpoint from run script -const grpcEp = "0.0.0.0:12342" - -func runHardhatTests(test string) { - cmd := exec.Command("npx", "hardhat", "test", test, "--network", "e2e") - cmd.Dir = "./contract-examples" - out, err := cmd.Output() - if err != nil { - fmt.Println(string(out)) - fmt.Println(err) - } - gomega.Expect(err).Should(gomega.BeNil()) -} - -// startSubnet starts a test network and launches a subnetEVM instance with the genesis file at [genesisPath] -func startSubnet(genesisPath string) error { - fmt.Println("AVALANCHEGO_PATH:", os.Getenv("AVALANCHEGO_PATH")) - _, err := runner.StartNetwork(grpcEp, os.Getenv("AVALANCHEGO_PATH"), evm.ID, vmName, genesisPath, utils.GetPluginDir()) - gomega.Expect(err).Should(gomega.BeNil()) - return utils.UpdateHardhatConfig() -} - -// stopSubnet stops the test network. -func stopSubnet() { - err := runner.StopNetwork(grpcEp) - gomega.Expect(err).Should(gomega.BeNil()) -} - -var _ = utils.DescribePrecompile(func() { - ginkgo.It("tx allow list", ginkgo.Label("solidity-with-npx"), func() { - err := startSubnet("./tests/e2e/genesis/tx_allow_list.json") - gomega.Expect(err).Should(gomega.BeNil()) - running := runner.IsRunnerUp(grpcEp) - gomega.Expect(running).Should(gomega.BeTrue()) - runHardhatTests("./test/ExampleTxAllowList.ts") - stopSubnet() - running = runner.IsRunnerUp(grpcEp) - gomega.Expect(running).Should(gomega.BeFalse()) - }) - - ginkgo.It("deployer allow list", ginkgo.Label("solidity-with-npx"), func() { - err := startSubnet("./tests/e2e/genesis/deployer_allow_list.json") - gomega.Expect(err).Should(gomega.BeNil()) - running := runner.IsRunnerUp(grpcEp) - gomega.Expect(running).Should(gomega.BeTrue()) - runHardhatTests("./test/ExampleDeployerList.ts") - stopSubnet() - running = runner.IsRunnerUp(grpcEp) - gomega.Expect(running).Should(gomega.BeFalse()) - }) - - ginkgo.It("contract native minter", ginkgo.Label("solidity-with-npx"), func() { - err := startSubnet("./tests/e2e/genesis/contract_native_minter.json") - gomega.Expect(err).Should(gomega.BeNil()) - running := runner.IsRunnerUp(grpcEp) - gomega.Expect(running).Should(gomega.BeTrue()) - runHardhatTests("./test/ERC20NativeMinter.ts") - stopSubnet() - running = runner.IsRunnerUp(grpcEp) - gomega.Expect(running).Should(gomega.BeFalse()) - }) - - ginkgo.It("fee manager", ginkgo.Label("solidity-with-npx"), func() { - err := startSubnet("./tests/e2e/genesis/fee_manager.json") - gomega.Expect(err).Should(gomega.BeNil()) - running := runner.IsRunnerUp(grpcEp) - gomega.Expect(running).Should(gomega.BeTrue()) - runHardhatTests("./test/ExampleFeeManager.ts") - stopSubnet() - running = runner.IsRunnerUp(grpcEp) - gomega.Expect(running).Should(gomega.BeFalse()) - }) - - ginkgo.It("reward manager", ginkgo.Label("solidity-with-npx"), func() { - err := startSubnet("./tests/e2e/genesis/reward_manager.json") - gomega.Expect(err).Should(gomega.BeNil()) - running := runner.IsRunnerUp(grpcEp) - gomega.Expect(running).Should(gomega.BeTrue()) - runHardhatTests("./test/ExampleRewardManager.ts") - stopSubnet() - running = runner.IsRunnerUp(grpcEp) - gomega.Expect(running).Should(gomega.BeFalse()) - }) - - // ADD YOUR PRECOMPILE HERE - /* - ginkgo.It("your precompile", ginkgo.Label("solidity-with-npx"), func() { - err := startSubnet("./tests/e2e/genesis/{your_precompile}.json") - gomega.Expect(err).Should(gomega.BeNil()) - running := runner.IsRunnerUp(grpcEp) - gomega.Expect(running).Should(gomega.BeTrue()) - runHardhatTests("./test/{YourPrecompileTest}.ts") - stopSubnet() - running = runner.IsRunnerUp(grpcEp) - gomega.Expect(running).Should(gomega.BeFalse()) - }) - */ -}) diff --git a/tests/e2e/utils/describe.go b/tests/e2e/utils/describe.go deleted file mode 100644 index cde6ee371c..0000000000 --- a/tests/e2e/utils/describe.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package utils - -import ( - ginkgo "github.com/onsi/ginkgo/v2" -) - -// DescribeLocal annotates the tests that requires local network-runner. -// Can only run with local cluster. -func DescribeLocal(text string, body func()) bool { - return ginkgo.Describe("[Local] "+text, body) -} - -func DescribePrecompile(body func()) bool { - return ginkgo.Describe("[Precompiles]", ginkgo.Ordered, body) -} diff --git a/tests/e2e/utils/out.go b/tests/e2e/utils/out.go deleted file mode 100644 index df718c40bc..0000000000 --- a/tests/e2e/utils/out.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package utils - -import ( - "fmt" - - "github.com/onsi/ginkgo/v2/formatter" -) - -// Outputs to stdout. -// -// e.g., -// Outf("{{green}}{{bold}}hi there %q{{/}}", "aa") -// Outf("{{magenta}}{{bold}}hi therea{{/}} {{cyan}}{{underline}}b{{/}}") -// -// ref. -// https://github.com/onsi/ginkgo/blob/v2.0.0/formatter/formatter.go#L52-L73 -// -func Outf(format string, args ...interface{}) { - s := formatter.F(format, args...) - fmt.Fprint(formatter.ColorableStdOut, s) -} diff --git a/tests/e2e/utils/params.go b/tests/e2e/utils/params.go deleted file mode 100644 index 1a2b935c18..0000000000 --- a/tests/e2e/utils/params.go +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package utils - -import ( - "os" - "sync" - - runner_sdk "github.com/ava-labs/avalanche-network-runner-sdk" - - "gopkg.in/yaml.v2" -) - -// ClusterInfo represents the local cluster information. -type ClusterInfo struct { - URIs []string `json:"uris"` - Endpoint string `json:"endpoint"` - PID int `json:"pid"` - LogsDir string `json:"logsDir"` - SubnetEVMRPCEndpoints []string `json:"subnetEVMRPCEndpoints"` -} - -const fsModeWrite = 0o600 - -func (ci ClusterInfo) Save(p string) error { - ob, err := yaml.Marshal(ci) - if err != nil { - return err - } - return os.WriteFile(p, ob, fsModeWrite) -} - -var ( - mu sync.RWMutex - - cli runner_sdk.Client - - outputFile string - pluginDir string - - // executable path for "avalanchego" - execPath string - vmGenesisPath string - - skipNetworkRunnerShutdown bool - - clusterInfo ClusterInfo -) - -func SetClient(c runner_sdk.Client) { - mu.Lock() - cli = c - mu.Unlock() -} - -func GetClient() runner_sdk.Client { - mu.RLock() - c := cli - mu.RUnlock() - return c -} - -func SetOutputFile(filepath string) { - mu.Lock() - outputFile = filepath - mu.Unlock() -} - -func GetOutputPath() string { - mu.RLock() - e := outputFile - mu.RUnlock() - return e -} - -// Sets the executable path for "avalanchego". -func SetExecPath(p string) { - mu.Lock() - execPath = p - mu.Unlock() -} - -// Loads the executable path for "avalanchego". -func GetExecPath() string { - mu.RLock() - e := execPath - mu.RUnlock() - return e -} - -func SetPluginDir(dir string) { - mu.Lock() - pluginDir = dir - mu.Unlock() -} - -func GetPluginDir() string { - mu.RLock() - p := pluginDir - mu.RUnlock() - return p -} - -func SetVmGenesisPath(p string) { - mu.Lock() - vmGenesisPath = p - mu.Unlock() -} - -func GetVmGenesisPath() string { - mu.RLock() - p := vmGenesisPath - mu.RUnlock() - return p -} - -func SetSkipNetworkRunnerShutdown(b bool) { - mu.Lock() - skipNetworkRunnerShutdown = b - mu.Unlock() -} - -func GetSkipNetworkRunnerShutdown() bool { - mu.RLock() - b := skipNetworkRunnerShutdown - mu.RUnlock() - return b -} - -func SetClusterInfo(c ClusterInfo) { - mu.Lock() - clusterInfo = c - mu.Unlock() -} - -func GetClusterInfo() ClusterInfo { - mu.RLock() - c := clusterInfo - mu.RUnlock() - return c -} diff --git a/tests/e2e/utils/parser.go b/tests/e2e/utils/parser.go deleted file mode 100644 index 103114b8a8..0000000000 --- a/tests/e2e/utils/parser.go +++ /dev/null @@ -1,80 +0,0 @@ -// (c) 2022 Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package utils - -import ( - "encoding/json" - "fmt" - "io/ioutil" - - "github.com/fatih/color" - "gopkg.in/yaml.v2" -) - -/* -===Example File=== - -endpoint: /ext/bc/2Z36RnQuk1hvsnFeGWzfZUfXNr7w1SjzmDQ78YxfTVNAkDq3nZ -logsDir: /var/folders/mp/6jm81gc11dv3xtcwxmrd8mcr0000gn/T/runnerlogs2984620995 -pid: 55547 -uris: -- http://localhost:61278 -- http://localhost:61280 -- http://localhost:61282 -- http://localhost:61284 -- http://localhost:61286 -*/ - -type output struct { - Endpoint string `yaml:"endpoint"` - Logs string `yaml:"logsDir"` - PID int `yaml:"pid"` - URIs []string `yaml:"uris"` -} - -type rpcFile struct { - Rpc string `json:"rpc"` -} - -const ( - DYNAMIC_RPC_FILE = "contract-examples/dynamic_rpc.json" - LOCAL_RPC_FILE = "contract-examples/local_rpc.json" -) - -func writeRPC(rpcUrl string) error { - rpcFileData := rpcFile{ - Rpc: rpcUrl, - } - - file, err := json.MarshalIndent(rpcFileData, "", " ") - if err != nil { - return err - } - - err = ioutil.WriteFile(DYNAMIC_RPC_FILE, file, 0644) - if err != nil { - return err - } - err = ioutil.WriteFile(LOCAL_RPC_FILE, file, 0644) - return err -} - -func UpdateHardhatConfig() error { - yamlFile, err := ioutil.ReadFile(outputFile) - if err != nil { - return err - } - var o output - if err := yaml.Unmarshal(yamlFile, &o); err != nil { - return err - } - - color.Yellow("Updating hardhat config with RPC URL: %s%s/rpc", o.URIs[0], o.Endpoint) - - rpc := fmt.Sprintf("%s%s/rpc", o.URIs[0], o.Endpoint) - if err = writeRPC(rpc); err != nil { - return err - } - return nil -} diff --git a/tests/load/genesis/genesis.json b/tests/load/genesis/genesis.json new file mode 100644 index 0000000000..cac224d1a2 --- /dev/null +++ b/tests/load/genesis/genesis.json @@ -0,0 +1,45 @@ +{ + "config": { + "chainId": 99999, + "homesteadBlock": 0, + "eip150Block": 0, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "subnetEVMTimestamp": 0, + "feeConfig": { + "gasLimit": 20000000, + "minBaseFee": 1000000000, + "targetGas": 100000000, + "baseFeeChangeDenominator": 48, + "minBlockGasCost": 0, + "maxBlockGasCost": 10000000, + "targetBlockRate": 2, + "blockGasCostStep": 500000 + } + }, + "alloc": { + "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { + "balance": "0x52B7D2DCC80CD2E4000000" + }, + "0x0Fa8EA536Be85F32724D57A37758761B86416123": { + "balance": "0x52B7D2DCC80CD2E4000000" + } + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x00", + "gasLimit": "0x1312D00", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + \ No newline at end of file diff --git a/tests/load/load_test.go b/tests/load/load_test.go new file mode 100644 index 0000000000..07641a4ce6 --- /dev/null +++ b/tests/load/load_test.go @@ -0,0 +1,79 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package load + +import ( + "context" + "fmt" + "os" + "os/exec" + "strings" + "testing" + "time" + + "github.com/ava-labs/avalanchego/api/health" + "github.com/ava-labs/subnet-evm/tests/utils" + "github.com/ethereum/go-ethereum/log" + "github.com/go-cmd/cmd" + ginkgo "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" +) + +var startCmd *cmd.Cmd + +func TestE2E(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + ginkgo.RunSpecs(t, "subnet-evm small load simulator test suite") +} + +// BeforeSuite starts an AvalancheGo process to use for the e2e tests +var _ = ginkgo.BeforeSuite(func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + wd, err := os.Getwd() + gomega.Expect(err).Should(gomega.BeNil()) + log.Info("Starting AvalancheGo node", "wd", wd) + startCmd, err = utils.RunCommand("./scripts/run.sh") + gomega.Expect(err).Should(gomega.BeNil()) + + // Assumes that startCmd will launch a node with HTTP Port at [utils.DefaultLocalNodeURI] + healthClient := health.NewClient(utils.DefaultLocalNodeURI) + healthy, err := health.AwaitReady(ctx, healthClient, 5*time.Second) + gomega.Expect(err).Should(gomega.BeNil()) + gomega.Expect(healthy).Should(gomega.BeTrue()) + log.Info("AvalancheGo node is healthy") +}) + +var _ = ginkgo.Describe("[Load Simulator]", ginkgo.Ordered, func() { + ginkgo.It("basic subnet load test", ginkgo.Label("load"), func() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + blockchainID := utils.CreateNewSubnet(ctx, "./tests/load/genesis/genesis.json") + + rpcEndpoints := make([]string, 0, len(utils.NodeURIs)) + for _, uri := range []string{utils.DefaultLocalNodeURI} { // TODO: use NodeURIs instead, hack until fixing multi node in a network behavior + rpcEndpoints = append(rpcEndpoints, fmt.Sprintf("%s/ext/bc/%s/rpc", uri, blockchainID)) + } + commaSeparatedRPCEndpoints := strings.Join(rpcEndpoints, ",") + err := os.Setenv("RPC_ENDPOINTS", commaSeparatedRPCEndpoints) + gomega.Expect(err).Should(gomega.BeNil()) + + log.Info("Sleeping with network running", "rpcEndpoints", commaSeparatedRPCEndpoints) + cmd := exec.Command("./scripts/run_simulator.sh") + log.Info("Running load simulator script", "cmd", cmd.String()) + + out, err := cmd.CombinedOutput() + fmt.Printf("\nCombined output:\n\n%s\n", string(out)) + gomega.Expect(err).Should(gomega.BeNil()) + }) +}) + +var _ = ginkgo.AfterSuite(func() { + gomega.Expect(startCmd).ShouldNot(gomega.BeNil()) + gomega.Expect(startCmd.Stop()).Should(gomega.BeNil()) + // TODO add a new node to bootstrap off of the existing node and ensure it can bootstrap all subnets + // created during the test +}) diff --git a/tests/e2e/genesis/deployer_allow_list.json b/tests/precompile/genesis/contract_deployer_allow_list.json similarity index 100% rename from tests/e2e/genesis/deployer_allow_list.json rename to tests/precompile/genesis/contract_deployer_allow_list.json diff --git a/tests/e2e/genesis/contract_native_minter.json b/tests/precompile/genesis/contract_native_minter.json similarity index 100% rename from tests/e2e/genesis/contract_native_minter.json rename to tests/precompile/genesis/contract_native_minter.json diff --git a/tests/e2e/genesis/fee_manager.json b/tests/precompile/genesis/fee_manager.json similarity index 100% rename from tests/e2e/genesis/fee_manager.json rename to tests/precompile/genesis/fee_manager.json diff --git a/tests/e2e/genesis/reward_manager.json b/tests/precompile/genesis/reward_manager.json similarity index 100% rename from tests/e2e/genesis/reward_manager.json rename to tests/precompile/genesis/reward_manager.json diff --git a/tests/e2e/genesis/tx_allow_list.json b/tests/precompile/genesis/tx_allow_list.json similarity index 100% rename from tests/e2e/genesis/tx_allow_list.json rename to tests/precompile/genesis/tx_allow_list.json diff --git a/tests/precompile/precompile_test.go b/tests/precompile/precompile_test.go new file mode 100644 index 0000000000..46acc862df --- /dev/null +++ b/tests/precompile/precompile_test.go @@ -0,0 +1,54 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package precompile + +import ( + "context" + "os" + "testing" + "time" + + "github.com/ava-labs/avalanchego/api/health" + "github.com/ava-labs/subnet-evm/tests/utils" + "github.com/ethereum/go-ethereum/log" + "github.com/go-cmd/cmd" + ginkgo "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + + // Import the solidity package, so that ginkgo maps out the tests declared within the package + _ "github.com/ava-labs/subnet-evm/tests/precompile/solidity" +) + +var startCmd *cmd.Cmd + +func TestE2E(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + ginkgo.RunSpecs(t, "subnet-evm precompile ginkgo test suite") +} + +// BeforeSuite starts an AvalancheGo process to use for the e2e tests +var _ = ginkgo.BeforeSuite(func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + wd, err := os.Getwd() + gomega.Expect(err).Should(gomega.BeNil()) + log.Info("Starting AvalancheGo node", "wd", wd) + startCmd, err = utils.RunCommand("./scripts/run.sh") + gomega.Expect(err).Should(gomega.BeNil()) + + // Assumes that startCmd will launch a node with HTTP Port at [utils.DefaultLocalNodeURI] + healthClient := health.NewClient(utils.DefaultLocalNodeURI) + healthy, err := health.AwaitReady(ctx, healthClient, 5*time.Second) + gomega.Expect(err).Should(gomega.BeNil()) + gomega.Expect(healthy).Should(gomega.BeTrue()) + log.Info("AvalancheGo node is healthy") +}) + +var _ = ginkgo.AfterSuite(func() { + gomega.Expect(startCmd).ShouldNot(gomega.BeNil()) + gomega.Expect(startCmd.Stop()).Should(gomega.BeNil()) + // TODO add a new node to bootstrap off of the existing node and ensure it can bootstrap all subnets + // created during the test +}) diff --git a/tests/precompile/solidity/suites.go b/tests/precompile/solidity/suites.go new file mode 100644 index 0000000000..1404a01603 --- /dev/null +++ b/tests/precompile/solidity/suites.go @@ -0,0 +1,77 @@ +// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Implements solidity tests. +package solidity + +import ( + "context" + "time" + + "github.com/ava-labs/avalanchego/api/health" + "github.com/ava-labs/subnet-evm/tests/utils" + ginkgo "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" +) + +var _ = ginkgo.Describe("[Precompiles]", ginkgo.Ordered, func() { + ginkgo.It("ping the network", ginkgo.Label("setup"), func() { + client := health.NewClient(utils.DefaultLocalNodeURI) + healthy, err := client.Readiness(context.Background()) + gomega.Expect(err).Should(gomega.BeNil()) + gomega.Expect(healthy.Healthy).Should(gomega.BeTrue()) + }) +}) + +var _ = ginkgo.Describe("[Precompiles]", ginkgo.Ordered, func() { + // Each ginkgo It node specifies the name of the genesis file (in ./tests/precompile/genesis/) + // to use to launch the subnet and the name of the TS test file to run on the subnet (in ./contract-examples/tests/) + ginkgo.It("contract native minter", ginkgo.Label("Precompile"), ginkgo.Label("ContractNativeMinter"), func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + utils.ExecuteHardHatTestOnNewBlockchain(ctx, "contract_native_minter") + }) + + ginkgo.It("tx allow list", ginkgo.Label("Precompile"), ginkgo.Label("TxAllowList"), func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + utils.ExecuteHardHatTestOnNewBlockchain(ctx, "tx_allow_list") + }) + + ginkgo.It("contract deployer allow list", ginkgo.Label("Precompile"), ginkgo.Label("ContractDeployerAllowList"), func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + utils.ExecuteHardHatTestOnNewBlockchain(ctx, "contract_deployer_allow_list") + }) + + ginkgo.It("fee manager", ginkgo.Label("Precompile"), ginkgo.Label("FeeManager"), func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + utils.ExecuteHardHatTestOnNewBlockchain(ctx, "fee_manager") + }) + + ginkgo.It("reward manager", ginkgo.Label("Precompile"), ginkgo.Label("RewardManager"), func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + utils.ExecuteHardHatTestOnNewBlockchain(ctx, "reward_manager") + }) + + // TODO: can we refactor this so that it automagically checks to ensure each hardhat test file matches the name of a hardhat genesis file + // and then runs the hardhat tests for each one without forcing precompile developers to modify this file. + // ADD YOUR PRECOMPILE HERE + /* + ginkgo.It("your precompile", ginkgo.Label("Precompile"), ginkgo.Label("YourPrecompile"), func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // Specify the name shared by the genesis file in ./tests/precompile/genesis/{your_precompile}.json + // and the test file in ./contract-examples/tests/{your_precompile}.ts + utils.ExecuteHardHatTestOnNewBlockchain(ctx, "your_precompile") + }) + */ +}) diff --git a/tests/e2e/utils/command.go b/tests/utils/command.go similarity index 56% rename from tests/e2e/utils/command.go rename to tests/utils/command.go index 4f6cb6fcf5..367547be0b 100644 --- a/tests/e2e/utils/command.go +++ b/tests/utils/command.go @@ -4,19 +4,22 @@ package utils import ( - "context" "fmt" "strings" "time" + "github.com/ethereum/go-ethereum/log" "github.com/go-cmd/cmd" ) -func RunCommand(ctx context.Context, bin string, args ...string) (cmd.Status, error) { - Outf("{{green}}running '%s %s'{{/}}\n", bin, strings.Join(args, " ")) +// RunCommand starts the command [bin] with the given [args] and returns the command to the caller +// TODO cmd package mentions we can do this more efficiently with cmd.NewCmdOptions rather than looping +// and calling Status(). +func RunCommand(bin string, args ...string) (*cmd.Cmd, error) { + log.Info("Executing", "cmd", fmt.Sprintf("%s %s", bin, strings.Join(args, " "))) curCmd := cmd.NewCmd(bin, args...) - statusChan := curCmd.Start() + _ = curCmd.Start() // to stream outputs ticker := time.NewTicker(10 * time.Millisecond) @@ -38,11 +41,5 @@ func RunCommand(ctx context.Context, bin string, args ...string) (cmd.Status, er } }() - select { - case s := <-statusChan: - return s, nil - case <-ctx.Done(): - curCmd.Stop() - return cmd.Status{}, ctx.Err() - } + return curCmd, nil } diff --git a/tests/utils/constants.go b/tests/utils/constants.go new file mode 100644 index 0000000000..75cc779bd3 --- /dev/null +++ b/tests/utils/constants.go @@ -0,0 +1,9 @@ +// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package utils + +var ( + DefaultLocalNodeURI = "http://127.0.0.1:9650" + NodeURIs = []string{DefaultLocalNodeURI, "http://127.0.0.1:9652", "http://127.0.0.1:9654", "http://127.0.0.1:9656", "http://127.0.0.1:9658"} +) diff --git a/tests/e2e/utils/evm_client.go b/tests/utils/evm_client.go similarity index 85% rename from tests/e2e/utils/evm_client.go rename to tests/utils/evm_client.go index e1b9eecd12..8017aac1f5 100644 --- a/tests/e2e/utils/evm_client.go +++ b/tests/utils/evm_client.go @@ -9,13 +9,11 @@ import ( "fmt" "log" "math/big" - "strings" "time" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/ethclient" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" ) @@ -38,8 +36,9 @@ func NewEvmClient(ep string, baseFee uint64, priorityFee uint64) (*EvmClient, er } ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + chainID, err := ethCli.ChainID(ctx) - cancel() if err != nil { return nil, err } @@ -113,6 +112,7 @@ func (ec *EvmClient) ConfirmTx(ctx context.Context, txHash common.Hash) (*big.In time.Sleep(time.Second) continue } + // XXX: this uses gas instead of gas used, so it may be incorrect if the transaction does more than a simple transfer return result.Cost(), nil } return nil, ctx.Err() @@ -128,18 +128,14 @@ func (ec *EvmClient) TransferTx( for ctx.Err() == nil { senderBal, err := ec.FetchBalance(ctx, sender) if err != nil { - log.Printf("could not get balance: %s", err.Error()) - time.Sleep(time.Second) - continue + return nil, fmt.Errorf("failed to fetch balance: %w", err) } if senderBal.Cmp(transferAmount) < 0 { return nil, fmt.Errorf("not enough balance %s to transfer %s", senderBal, transferAmount) } - cctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - nonce, err := ec.FetchNonce(cctx, sender) - cancel() + nonce, err := ec.FetchNonce(ctx, sender) if err != nil { return nil, err } @@ -153,34 +149,23 @@ func (ec *EvmClient) TransferTx( GasFeeCap: ec.feeCap, GasTipCap: ec.priorityFee, Value: transferAmount, - Data: []byte{}, }), ec.signer, senderPriv, ) if err != nil { - log.Printf("failed to sign transaction: %v (key address %s)", err, sender) - time.Sleep(time.Second) - continue + return nil, fmt.Errorf("failed to sign transaction: %w", err) } if err := ec.ethClient.SendTransaction(ctx, signedTx); err != nil { log.Printf("failed to send transaction: %v (key address %s)", err, sender) - - if strings.Contains(err.Error(), vmerrs.ErrSenderAddressNotAllowListed.Error()) { - return nil, err - } - - time.Sleep(time.Second) - continue + return nil, err } txHash := signedTx.Hash() cost, err := ec.ConfirmTx(ctx, txHash) if err != nil { - log.Printf("failed to confirm %s: %v", txHash.Hex(), err) - time.Sleep(time.Second) - continue + return nil, err } senderBal = new(big.Int).Sub(senderBal, cost) diff --git a/tests/utils/subnet.go b/tests/utils/subnet.go new file mode 100644 index 0000000000..75fb42f585 --- /dev/null +++ b/tests/utils/subnet.go @@ -0,0 +1,110 @@ +// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package utils + +import ( + "context" + "encoding/json" + "fmt" + "os" + "os/exec" + "time" + + "github.com/ava-labs/avalanchego/api/info" + "github.com/ava-labs/avalanchego/genesis" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + wallet "github.com/ava-labs/avalanchego/wallet/subnet/primary" + "github.com/ava-labs/subnet-evm/core" + "github.com/ava-labs/subnet-evm/plugin/evm" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/onsi/gomega" +) + +func RunHardhatTests(test string, rpcURI string) { + log.Info("Sleeping to wait for test ping", "rpcURI", rpcURI) + client, err := NewEvmClient(rpcURI, 225, 2) + gomega.Expect(err).Should(gomega.BeNil()) + + bal, err := client.FetchBalance(context.Background(), common.HexToAddress("")) + gomega.Expect(err).Should(gomega.BeNil()) + gomega.Expect(bal.Cmp(common.Big0)).Should(gomega.Equal(0)) + + err = os.Setenv("RPC_URI", rpcURI) + gomega.Expect(err).Should(gomega.BeNil()) + cmd := exec.Command("npx", "hardhat", "test", fmt.Sprintf("./test/%s.ts", test), "--network", "local") + cmd.Dir = "./contract-examples" + log.Info("Running hardhat command", "cmd", cmd.String()) + + out, err := cmd.CombinedOutput() + fmt.Printf("\nCombined output:\n\n%s\n", string(out)) + if err != nil { + fmt.Printf("\nErr: %s\n", err.Error()) + } + gomega.Expect(err).Should(gomega.BeNil()) +} + +func CreateNewSubnet(ctx context.Context, genesisFilePath string) string { + kc := secp256k1fx.NewKeychain(genesis.EWOQKey) + + // NewWalletFromURI fetches the available UTXOs owned by [kc] on the network + // that [LocalAPIURI] is hosting. + wallet, err := wallet.NewWalletFromURI(ctx, DefaultLocalNodeURI, kc) + gomega.Expect(err).Should(gomega.BeNil()) + + pWallet := wallet.P() + + owner := &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + genesis.EWOQKey.PublicKey().Address(), + }, + } + + wd, err := os.Getwd() + gomega.Expect(err).Should(gomega.BeNil()) + log.Info("Reading genesis file", "filePath", genesisFilePath, "wd", wd) + genesisBytes, err := os.ReadFile(genesisFilePath) + gomega.Expect(err).Should(gomega.BeNil()) + + log.Info("Creating new subnet") + createSubnetTxID, err := pWallet.IssueCreateSubnetTx(owner) + gomega.Expect(err).Should(gomega.BeNil()) + + genesis := &core.Genesis{} + err = json.Unmarshal(genesisBytes, genesis) + gomega.Expect(err).Should(gomega.BeNil()) + + log.Info("Creating new Subnet-EVM blockchain", "genesis", genesis) + createChainTxID, err := pWallet.IssueCreateChainTx( + createSubnetTxID, + genesisBytes, + evm.ID, + nil, + "testChain", + ) + gomega.Expect(err).Should(gomega.BeNil()) + + // Confirm the new blockchain is ready by waiting for the readiness endpoint + infoClient := info.NewClient(DefaultLocalNodeURI) + bootstrapped, err := info.AwaitBootstrapped(ctx, infoClient, createChainTxID.String(), 2*time.Second) + gomega.Expect(err).Should(gomega.BeNil()) + gomega.Expect(bootstrapped).Should(gomega.BeTrue()) + + // Return the blockchainID of the newly created blockchain + return createChainTxID.String() +} + +func ExecuteHardHatTestOnNewBlockchain(ctx context.Context, test string) { + log.Info("Executing HardHat tests on a new blockchain", "test", test) + + genesisFilePath := fmt.Sprintf("./tests/precompile/genesis/%s.json", test) + + blockchainID := CreateNewSubnet(ctx, genesisFilePath) + chainURI := fmt.Sprintf("%s/ext/bc/%s/rpc", DefaultLocalNodeURI, blockchainID) + + log.Info("Created subnet successfully", "ChainURI", chainURI) + RunHardhatTests(test, chainURI) +} diff --git a/trie/database.go b/trie/database.go index 25c76e0825..1436a37f6e 100644 --- a/trie/database.go +++ b/trie/database.go @@ -702,6 +702,7 @@ func (db *Database) Commit(node common.Hash, report bool, callback func(common.H nodes, storage := len(db.dirties), db.dirtiesSize toFlush, err := db.commit(node, make([]*flushItem, 0, 128), callback) if err != nil { + db.lock.RUnlock() log.Error("Failed to commit trie from trie database", "err", err) return err } diff --git a/warp/backend.go b/warp/backend.go new file mode 100644 index 0000000000..8cfac0b54e --- /dev/null +++ b/warp/backend.go @@ -0,0 +1,92 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package warp + +import ( + "context" + "fmt" + + "github.com/ava-labs/avalanchego/cache" + "github.com/ava-labs/avalanchego/database" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/vms/platformvm/teleporter" +) + +var _ WarpBackend = &warpBackend{} + +// WarpBackend tracks signature eligible warp messages and provides an interface to fetch them. +// The backend is also used to query for warp message signatures by the signature request handler. +type WarpBackend interface { + // AddMessage signs [unsignedMessage] and adds it to the warp backend database + AddMessage(ctx context.Context, unsignedMessage *teleporter.UnsignedMessage) error + + // GetSignature returns the signature of the requested message hash. + GetSignature(ctx context.Context, messageHash ids.ID) ([bls.SignatureLen]byte, error) +} + +// warpBackend implements WarpBackend, keeps track of warp messages, and generates message signatures. +type warpBackend struct { + db database.Database + snowCtx *snow.Context + signatureCache *cache.LRU[ids.ID, [bls.SignatureLen]byte] +} + +// NewWarpBackend creates a new WarpBackend, and initializes the signature cache and message tracking database. +func NewWarpBackend(snowCtx *snow.Context, db database.Database, signatureCacheSize int) WarpBackend { + return &warpBackend{ + db: db, + snowCtx: snowCtx, + signatureCache: &cache.LRU[ids.ID, [bls.SignatureLen]byte]{Size: signatureCacheSize}, + } +} + +func (w *warpBackend) AddMessage(ctx context.Context, unsignedMessage *teleporter.UnsignedMessage) error { + messageID := hashing.ComputeHash256Array(unsignedMessage.Bytes()) + + // In the case when a node restarts, and possibly changes its bls key, the cache gets emptied but the database does not. + // So to avoid having incorrect signatures saved in the database after a bls key change, we save the full message in the database. + // Whereas for the cache, after the node restart, the cache would be emptied so we can directly save the signatures. + if err := w.db.Put(messageID[:], unsignedMessage.Bytes()); err != nil { + return fmt.Errorf("failed to put warp signature in db: %w", err) + } + + var signature [bls.SignatureLen]byte + sig, err := w.snowCtx.TeleporterSigner.Sign(unsignedMessage) + if err != nil { + return fmt.Errorf("failed to sign warp message: %w", err) + } + + copy(signature[:], sig) + w.signatureCache.Put(messageID, signature) + return nil +} + +func (w *warpBackend) GetSignature(ctx context.Context, messageID ids.ID) ([bls.SignatureLen]byte, error) { + if sig, ok := w.signatureCache.Get(messageID); ok { + return sig, nil + } + + unsignedMessageBytes, err := w.db.Get(messageID[:]) + if err != nil { + return [bls.SignatureLen]byte{}, fmt.Errorf("failed to get warp message %s from db: %w", messageID.String(), err) + } + + unsignedMessage, err := teleporter.ParseUnsignedMessage(unsignedMessageBytes) + if err != nil { + return [bls.SignatureLen]byte{}, fmt.Errorf("failed to parse unsigned message %s: %w", messageID.String(), err) + } + + var signature [bls.SignatureLen]byte + sig, err := w.snowCtx.TeleporterSigner.Sign(unsignedMessage) + if err != nil { + return [bls.SignatureLen]byte{}, fmt.Errorf("failed to sign warp message: %w", err) + } + + copy(signature[:], sig) + w.signatureCache.Put(messageID, signature) + return signature, nil +} diff --git a/warp/backend_test.go b/warp/backend_test.go new file mode 100644 index 0000000000..c4449245f5 --- /dev/null +++ b/warp/backend_test.go @@ -0,0 +1,88 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package warp + +import ( + "context" + "testing" + + "github.com/ava-labs/avalanchego/database/memdb" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/vms/platformvm/teleporter" + "github.com/stretchr/testify/require" +) + +var ( + sourceChainID = ids.GenerateTestID() + destinationChainID = ids.GenerateTestID() + payload = []byte("test") +) + +func TestAddAndGetValidMessage(t *testing.T) { + db := memdb.New() + + snowCtx := snow.DefaultContextTest() + sk, err := bls.NewSecretKey() + require.NoError(t, err) + snowCtx.TeleporterSigner = teleporter.NewSigner(sk, sourceChainID) + backend := NewWarpBackend(snowCtx, db, 500) + + // Create a new unsigned message and add it to the warp backend. + unsignedMsg, err := teleporter.NewUnsignedMessage(sourceChainID, destinationChainID, payload) + require.NoError(t, err) + err = backend.AddMessage(context.Background(), unsignedMsg) + require.NoError(t, err) + + // Verify that a signature is returned successfully, and compare to expected signature. + messageID := hashing.ComputeHash256Array(unsignedMsg.Bytes()) + signature, err := backend.GetSignature(context.Background(), messageID) + require.NoError(t, err) + + expectedSig, err := snowCtx.TeleporterSigner.Sign(unsignedMsg) + require.NoError(t, err) + require.Equal(t, expectedSig, signature[:]) +} + +func TestAddAndGetUnknownMessage(t *testing.T) { + db := memdb.New() + + backend := NewWarpBackend(snow.DefaultContextTest(), db, 500) + unsignedMsg, err := teleporter.NewUnsignedMessage(sourceChainID, destinationChainID, payload) + require.NoError(t, err) + + // Try getting a signature for a message that was not added. + messageID := hashing.ComputeHash256Array(unsignedMsg.Bytes()) + _, err = backend.GetSignature(context.Background(), messageID) + require.Error(t, err) +} + +func TestZeroSizedCache(t *testing.T) { + db := memdb.New() + + snowCtx := snow.DefaultContextTest() + sk, err := bls.NewSecretKey() + require.NoError(t, err) + snowCtx.TeleporterSigner = teleporter.NewSigner(sk, sourceChainID) + + // Verify zero sized cache works normally, because the lru cache will be initialized to size 1 for any size parameter <= 0. + backend := NewWarpBackend(snowCtx, db, 0) + + // Create a new unsigned message and add it to the warp backend. + unsignedMsg, err := teleporter.NewUnsignedMessage(sourceChainID, destinationChainID, payload) + require.NoError(t, err) + err = backend.AddMessage(context.Background(), unsignedMsg) + require.NoError(t, err) + + // Verify that a signature is returned successfully, and compare to expected signature. + messageID := hashing.ComputeHash256Array(unsignedMsg.Bytes()) + signature, err := backend.GetSignature(context.Background(), messageID) + require.NoError(t, err) + + expectedSig, err := snowCtx.TeleporterSigner.Sign(unsignedMsg) + require.NoError(t, err) + require.Equal(t, expectedSig, signature[:]) +} diff --git a/warp/handlers/signature_request.go b/warp/handlers/signature_request.go new file mode 100644 index 0000000000..6cf28efd74 --- /dev/null +++ b/warp/handlers/signature_request.go @@ -0,0 +1,75 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package handlers + +import ( + "context" + "time" + + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/subnet-evm/plugin/evm/message" + "github.com/ava-labs/subnet-evm/warp" + "github.com/ava-labs/subnet-evm/warp/handlers/stats" + "github.com/ethereum/go-ethereum/log" +) + +// SignatureRequestHandler is a peer.RequestHandler for message.SignatureRequest +// serving requested BLS signature data +type SignatureRequestHandler interface { + OnSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest message.SignatureRequest) ([]byte, error) +} + +// signatureRequestHandler implements the SignatureRequestHandler interface +type signatureRequestHandler struct { + backend warp.WarpBackend + codec codec.Manager + stats stats.SignatureRequestHandlerStats +} + +func NewSignatureRequestHandler(backend warp.WarpBackend, codec codec.Manager, stats stats.SignatureRequestHandlerStats) SignatureRequestHandler { + return &signatureRequestHandler{ + backend: backend, + codec: codec, + stats: stats, + } +} + +// OnSignatureRequest handles message.SignatureRequest, and retrieves a warp signature for the requested message ID. +// Never returns an error +// Expects returned errors to be treated as FATAL +// Returns empty response if signature is not found +// Assumes ctx is active +func (s *signatureRequestHandler) OnSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest message.SignatureRequest) ([]byte, error) { + startTime := time.Now() + s.stats.IncSignatureRequest() + + // Always report signature request time + defer func() { + s.stats.UpdateSignatureRequestTime(time.Since(startTime)) + }() + + signature, err := s.backend.GetSignature(ctx, signatureRequest.MessageID) + if err != nil { + log.Debug("Unknown warp signature requested", "messageID", signatureRequest.MessageID) + s.stats.IncSignatureMiss() + return nil, nil + } + + s.stats.IncSignatureHit() + response := message.SignatureResponse{Signature: signature} + responseBytes, err := s.codec.Marshal(message.Version, &response) + if err != nil { + log.Warn("could not marshal SignatureResponse, dropping request", "nodeID", nodeID, "requestID", requestID, "err", err) + return nil, nil + } + + return responseBytes, nil +} + +type NoopSignatureRequestHandler struct{} + +func (s *NoopSignatureRequestHandler) OnSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest message.SignatureRequest) ([]byte, error) { + return nil, nil +} diff --git a/warp/handlers/signature_request_test.go b/warp/handlers/signature_request_test.go new file mode 100644 index 0000000000..fa2d53664d --- /dev/null +++ b/warp/handlers/signature_request_test.go @@ -0,0 +1,99 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package handlers + +import ( + "context" + "testing" + "time" + + "github.com/ava-labs/avalanchego/database/memdb" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/hashing" + "github.com/ava-labs/avalanchego/vms/platformvm/teleporter" + "github.com/ava-labs/subnet-evm/plugin/evm/message" + "github.com/ava-labs/subnet-evm/warp" + "github.com/ava-labs/subnet-evm/warp/handlers/stats" + "github.com/stretchr/testify/require" +) + +func TestSignatureHandler(t *testing.T) { + database := memdb.New() + snowCtx := snow.DefaultContextTest() + blsSecretKey, err := bls.NewSecretKey() + require.NoError(t, err) + + snowCtx.TeleporterSigner = teleporter.NewSigner(blsSecretKey, snowCtx.ChainID) + warpBackend := warp.NewWarpBackend(snowCtx, database, 100) + + msg, err := teleporter.NewUnsignedMessage(snowCtx.ChainID, snowCtx.CChainID, []byte("test")) + require.NoError(t, err) + + messageID := hashing.ComputeHash256Array(msg.Bytes()) + require.NoError(t, warpBackend.AddMessage(context.Background(), msg)) + signature, err := warpBackend.GetSignature(context.Background(), messageID) + require.NoError(t, err) + unknownMessageID := ids.GenerateTestID() + + mockHandlerStats := &stats.MockSignatureRequestHandlerStats{} + signatureRequestHandler := NewSignatureRequestHandler(warpBackend, message.Codec, mockHandlerStats) + + tests := map[string]struct { + setup func() (request message.SignatureRequest, expectedResponse []byte) + verifyStats func(t *testing.T, stats *stats.MockSignatureRequestHandlerStats) + }{ + "normal": { + setup: func() (request message.SignatureRequest, expectedResponse []byte) { + return message.SignatureRequest{ + MessageID: messageID, + }, signature[:] + }, + verifyStats: func(t *testing.T, stats *stats.MockSignatureRequestHandlerStats) { + require.EqualValues(t, 1, mockHandlerStats.SignatureRequestCount) + require.EqualValues(t, 1, mockHandlerStats.SignatureRequestHit) + require.EqualValues(t, 0, mockHandlerStats.SignatureRequestMiss) + require.Greater(t, mockHandlerStats.SignatureRequestDuration, time.Duration(0)) + }, + }, + "unknown": { + setup: func() (request message.SignatureRequest, expectedResponse []byte) { + return message.SignatureRequest{ + MessageID: unknownMessageID, + }, nil + }, + verifyStats: func(t *testing.T, stats *stats.MockSignatureRequestHandlerStats) { + require.EqualValues(t, 1, mockHandlerStats.SignatureRequestCount) + require.EqualValues(t, 1, mockHandlerStats.SignatureRequestMiss) + require.EqualValues(t, 0, mockHandlerStats.SignatureRequestHit) + require.Greater(t, mockHandlerStats.SignatureRequestDuration, time.Duration(0)) + }, + }, + } + + for name, test := range tests { + // Reset stats before each test + mockHandlerStats.Reset() + + t.Run(name, func(t *testing.T) { + request, expectedResponse := test.setup() + responseBytes, err := signatureRequestHandler.OnSignatureRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) + require.NoError(t, err) + + // If the expected response is empty, assert that the handler returns an empty response and return early. + if len(expectedResponse) == 0 { + test.verifyStats(t, mockHandlerStats) + require.Len(t, responseBytes, 0, "expected response to be empty") + return + } + var response message.SignatureResponse + _, err = message.Codec.Unmarshal(responseBytes, &response) + require.NoError(t, err, "error unmarshalling SignatureResponse") + + require.Equal(t, expectedResponse, response.Signature[:]) + test.verifyStats(t, mockHandlerStats) + }) + } +} diff --git a/warp/handlers/stats/stats.go b/warp/handlers/stats/stats.go new file mode 100644 index 0000000000..2cc593667e --- /dev/null +++ b/warp/handlers/stats/stats.go @@ -0,0 +1,95 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package stats + +import ( + "sync" + "time" + + "github.com/ava-labs/subnet-evm/metrics" +) + +var ( + _ SignatureRequestHandlerStats = (*handlerStats)(nil) + _ SignatureRequestHandlerStats = (*MockSignatureRequestHandlerStats)(nil) +) + +type SignatureRequestHandlerStats interface { + IncSignatureRequest() + IncSignatureHit() + IncSignatureMiss() + UpdateSignatureRequestTime(duration time.Duration) +} + +type handlerStats struct { + // SignatureRequestHandler metrics + signatureRequest metrics.Counter + signatureHit metrics.Counter + signatureMiss metrics.Counter + signatureProcessingTime metrics.Timer +} + +func NewStats(enabled bool) SignatureRequestHandlerStats { + if !enabled { + return &MockSignatureRequestHandlerStats{} + } + + return &handlerStats{ + signatureRequest: metrics.GetOrRegisterCounter("signature_request_count", nil), + signatureHit: metrics.GetOrRegisterCounter("signature_request_hit", nil), + signatureMiss: metrics.GetOrRegisterCounter("signature_request_miss", nil), + signatureProcessingTime: metrics.GetOrRegisterTimer("signature_request_duration", nil), + } +} + +func (h *handlerStats) IncSignatureRequest() { h.signatureRequest.Inc(1) } +func (h *handlerStats) IncSignatureHit() { h.signatureHit.Inc(1) } +func (h *handlerStats) IncSignatureMiss() { h.signatureMiss.Inc(1) } +func (h *handlerStats) UpdateSignatureRequestTime(duration time.Duration) { + h.signatureProcessingTime.Update(duration) +} + +// MockSignatureRequestHandlerStats is mock for capturing and asserting on handler metrics in test +type MockSignatureRequestHandlerStats struct { + lock sync.Mutex + + SignatureRequestCount, + SignatureRequestHit, + SignatureRequestMiss uint32 + SignatureRequestDuration time.Duration +} + +func (m *MockSignatureRequestHandlerStats) Reset() { + m.lock.Lock() + defer m.lock.Unlock() + + m.SignatureRequestCount = 0 + m.SignatureRequestHit = 0 + m.SignatureRequestMiss = 0 + m.SignatureRequestDuration = 0 +} + +func (m *MockSignatureRequestHandlerStats) IncSignatureRequest() { + m.lock.Lock() + defer m.lock.Unlock() + m.SignatureRequestCount++ +} + +func (m *MockSignatureRequestHandlerStats) IncSignatureHit() { + m.lock.Lock() + defer m.lock.Unlock() + m.SignatureRequestHit++ +} + +func (m *MockSignatureRequestHandlerStats) IncSignatureMiss() { + m.lock.Lock() + defer m.lock.Unlock() + m.SignatureRequestMiss++ +} + +func (m *MockSignatureRequestHandlerStats) UpdateSignatureRequestTime(duration time.Duration) { + m.lock.Lock() + defer m.lock.Unlock() + m.SignatureRequestDuration += duration +} diff --git a/warp/warp_client.go b/warp/warp_client.go new file mode 100644 index 0000000000..bfa4638efb --- /dev/null +++ b/warp/warp_client.go @@ -0,0 +1,45 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package warp + +import ( + "context" + "fmt" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/subnet-evm/rpc" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var _ WarpClient = (*warpClient)(nil) + +type WarpClient interface { + GetSignature(ctx context.Context, messageID ids.ID) ([]byte, error) +} + +// warpClient implementation for interacting with EVM [chain] +type warpClient struct { + client *rpc.Client +} + +// NewWarpClient returns a WarpClient for interacting with EVM [chain] +func NewWarpClient(uri, chain string) (WarpClient, error) { + client, err := rpc.Dial(fmt.Sprintf("%s/ext/bc/%s/rpc", uri, chain)) + if err != nil { + return nil, fmt.Errorf("failed to dial client. err: %w", err) + } + return &warpClient{ + client: client, + }, nil +} + +// GetSignature requests the BLS signature associated with a messageID +func (c *warpClient) GetSignature(ctx context.Context, messageID ids.ID) ([]byte, error) { + var res hexutil.Bytes + err := c.client.CallContext(ctx, &res, "warp_getSignature", messageID) + if err != nil { + return nil, fmt.Errorf("call to warp_getSignature failed. err: %w", err) + } + return res, err +} diff --git a/warp/warp_service.go b/warp/warp_service.go new file mode 100644 index 0000000000..3e2a003207 --- /dev/null +++ b/warp/warp_service.go @@ -0,0 +1,26 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package warp + +import ( + "context" + "fmt" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// WarpAPI introduces snowman specific functionality to the evm +type WarpAPI struct { + Backend WarpBackend +} + +// GetSignature returns the BLS signature associated with a messageID. +func (api *WarpAPI) GetSignature(ctx context.Context, messageID ids.ID) (hexutil.Bytes, error) { + signature, err := api.Backend.GetSignature(ctx, messageID) + if err != nil { + return nil, fmt.Errorf("failed to get signature for with error %w", err) + } + return signature[:], nil +} From 7f4c66b9ad2e45a17ab98670cd3e2bf4ca17a5b1 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 16 Feb 2023 17:43:15 +0300 Subject: [PATCH 08/23] review fixes --- accounts/abi/bind/bind.go | 1 + accounts/abi/bind/precompile_bind.go | 8 +- core/genesis_test.go | 2 +- core/state_processor_test.go | 2 +- core/test_blockchain.go | 2 +- eth/gasprice/gasprice_test.go | 6 +- internal/ethapi/api.go | 9 +- params/config.go | 15 +- params/precompile_config_test.go | 10 +- params/precompile_upgrade.go | 4 +- params/precompile_upgrade_test.go | 4 +- ...n_config_precompiles.go => precompiles.go} | 6 +- plugin/evm/vm_test.go | 12 +- precompile/contract/utils.go | 28 ++ .../deployerallowlist/config_test.go | 8 +- .../deployerallowlist/contract_test.go | 227 +++++++------- .../contracts/deployerallowlist/module.go | 2 +- precompile/contracts/feemanager/config.go | 3 +- .../contracts/feemanager/config_test.go | 10 +- precompile/contracts/feemanager/contract.go | 2 +- .../contracts/feemanager/contract_test.go | 213 ++++++------- .../contracts/nativeminter/config_test.go | 16 +- .../contracts/nativeminter/contract_test.go | 166 +++++------ .../contracts/rewardmanager/config_test.go | 10 +- .../contracts/rewardmanager/contract_test.go | 282 +++++++++--------- precompile/contracts/rewardmanager/module.go | 2 +- .../contracts/txallowlist/config_test.go | 14 +- .../contracts/txallowlist/contract_test.go | 206 ++++++------- 28 files changed, 606 insertions(+), 664 deletions(-) rename params/{chain_config_precompiles.go => precompiles.go} (83%) diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index d15d452f89..6be7f0314a 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -51,6 +51,7 @@ const ( readAllowListFuncKey = "readAllowList" ) +// BindHook is a callback function that can be used to customize the binding. type BindHook func(lang Lang, pkg string, 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. diff --git a/accounts/abi/bind/precompile_bind.go b/accounts/abi/bind/precompile_bind.go index 20581b0336..f85fc8b8a6 100644 --- a/accounts/abi/bind/precompile_bind.go +++ b/accounts/abi/bind/precompile_bind.go @@ -52,8 +52,9 @@ func PrecompileBind(types []string, abis []string, bytecodes []string, fsigs []m return configBind, contractBind, nil } +// createPrecompileHook creates a bind hook for precompiled contracts. func createPrecompileHook(abifilename string, template string) BindHook { - var bindHook BindHook = func(lang Lang, pkg string, types []string, contracts map[string]*tmplContract, structs map[string]*tmplStruct) (interface{}, string, error) { + return func(lang Lang, pkg string, 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") @@ -81,7 +82,9 @@ func createPrecompileHook(abifilename string, template string) BindHook { } isAllowList := allowListEnabled(funcs) if isAllowList { - // remove these functions as we will directly inherit AllowList + // these functions are not needed for binded contract. + // AllowList struct can provide the same functionality, + // so we don't need to generate them. delete(funcs, readAllowListFuncKey) delete(funcs, setAdminFuncKey) delete(funcs, setEnabledFuncKey) @@ -102,7 +105,6 @@ func createPrecompileHook(abifilename string, template string) BindHook { } return data, template, nil } - return bindHook } func allowListEnabled(funcs map[string]*tmplMethod) bool { diff --git a/core/genesis_test.go b/core/genesis_test.go index 3c2b57d1c0..90ecc54984 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -191,7 +191,7 @@ func TestStatefulPrecompilesConfigure(t *testing.T) { "allow list enabled in genesis": { getConfig: func() *params.ChainConfig { config := *params.TestChainConfig - config.GenesisPrecompiles = params.ChainConfigPrecompiles{ + config.GenesisPrecompiles = params.Precompiles{ deployerallowlist.ConfigKey: deployerallowlist.NewConfig(big.NewInt(0), []common.Address{addr}, nil), } return &config diff --git a/core/state_processor_test.go b/core/state_processor_test.go index d8da0edda5..b48f612333 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -315,7 +315,7 @@ func TestBadTxAllowListBlock(t *testing.T) { NetworkUpgrades: params.NetworkUpgrades{ SubnetEVMTimestamp: big.NewInt(0), }, - GenesisPrecompiles: params.ChainConfigPrecompiles{ + GenesisPrecompiles: params.Precompiles{ txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(0), nil, nil), }, } diff --git a/core/test_blockchain.go b/core/test_blockchain.go index 2acbf65eae..de4650106b 100644 --- a/core/test_blockchain.go +++ b/core/test_blockchain.go @@ -1548,7 +1548,7 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, chainC genesisBalance := new(big.Int).Mul(big.NewInt(1000000), big.NewInt(params.Ether)) config := *params.TestChainConfig // Set all of the required config parameters - config.GenesisPrecompiles = params.ChainConfigPrecompiles{ + config.GenesisPrecompiles = params.Precompiles{ deployerallowlist.ConfigKey: deployerallowlist.NewConfig(big.NewInt(0), []common.Address{addr1}, nil), feemanager.ConfigKey: feemanager.NewConfig(big.NewInt(0), []common.Address{addr1}, nil, nil), } diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index 049c154127..8f80573b2b 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -96,7 +96,7 @@ func (b *testBackend) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig } func newTestBackendFakerEngine(t *testing.T, config *params.ChainConfig, numBlocks int, genBlocks func(i int, b *core.BlockGen)) *testBackend { - var gspec = &core.Genesis{ + gspec := &core.Genesis{ Config: config, Alloc: core.GenesisAlloc{addr: core.GenesisAccount{Balance: bal}}, } @@ -124,7 +124,7 @@ func newTestBackendFakerEngine(t *testing.T, config *params.ChainConfig, numBloc } func newTestBackend(t *testing.T, config *params.ChainConfig, numBlocks int, genBlocks func(i int, b *core.BlockGen)) *testBackend { - var gspec = &core.Genesis{ + gspec := &core.Genesis{ Config: config, Alloc: core.GenesisAlloc{addr: core.GenesisAccount{Balance: bal}}, } @@ -434,7 +434,7 @@ func TestSuggestGasPriceAfterFeeConfigUpdate(t *testing.T) { // create a chain config with fee manager enabled at genesis with [addr] as the admin chainConfig := *params.TestChainConfig - chainConfig.GenesisPrecompiles = params.ChainConfigPrecompiles{ + chainConfig.GenesisPrecompiles = params.Precompiles{ feemanager.ConfigKey: feemanager.NewConfig(big.NewInt(0), []common.Address{addr}, nil, nil), } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 9cdf0cf7f7..64d0803800 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -233,7 +233,7 @@ func (s *TxPoolAPI) Inspect() map[string]map[string]map[string]string { pending, queue := s.b.TxPoolContent() // Define a formatter to flatten a transaction into a string - var format = func(tx *types.Transaction) string { + format := func(tx *types.Transaction) string { if to := tx.To(); to != nil { return fmt.Sprintf("%s: %v wei + %v gas × %v wei", tx.To().Hex(), tx.Value(), tx.Gas(), tx.GasPrice()) } @@ -626,7 +626,7 @@ func (api *BlockChainAPI) ChainId() *hexutil.Big { } // GetActivePrecompilesAt returns the active precompile configs at the given block timestamp. -func (s *BlockChainAPI) GetActivePrecompilesAt(ctx context.Context, blockTimestamp *big.Int) params.ChainConfigPrecompiles { +func (s *BlockChainAPI) GetActivePrecompilesAt(ctx context.Context, blockTimestamp *big.Int) params.Precompiles { if blockTimestamp == nil { blockTimestamp = new(big.Int).SetUint64(s.b.CurrentHeader().Time) } @@ -1201,7 +1201,6 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr for lo+1 < hi { mid := (hi + lo) / 2 failed, _, err := executable(mid) - // If the error is not nil(consensus error), it means the provided message // call or transaction will never be accepted no matter how much gas it is // assigned. Return the error directly, don't struggle any more. @@ -1981,11 +1980,11 @@ func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, g matchTx := sendArgs.toTransaction() // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable. - var price = matchTx.GasPrice() + price := matchTx.GasPrice() if gasPrice != nil { price = gasPrice.ToInt() } - var gas = matchTx.Gas() + gas := matchTx.Gas() if gasLimit != nil { gas = uint64(*gasLimit) } diff --git a/params/config.go b/params/config.go index 2a925084dd..2ff5581565 100644 --- a/params/config.go +++ b/params/config.go @@ -84,7 +84,7 @@ var ( PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0), - GenesisPrecompiles: ChainConfigPrecompiles{}, + GenesisPrecompiles: Precompiles{}, NetworkUpgrades: NetworkUpgrades{ SubnetEVMTimestamp: big.NewInt(0), }, @@ -106,7 +106,7 @@ var ( IstanbulBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0), NetworkUpgrades: NetworkUpgrades{big.NewInt(0)}, - GenesisPrecompiles: ChainConfigPrecompiles{}, + GenesisPrecompiles: Precompiles{}, UpgradeConfig: UpgradeConfig{}, } @@ -126,7 +126,7 @@ var ( IstanbulBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0), NetworkUpgrades: NetworkUpgrades{}, - GenesisPrecompiles: ChainConfigPrecompiles{}, + GenesisPrecompiles: Precompiles{}, UpgradeConfig: UpgradeConfig{}, } ) @@ -158,9 +158,9 @@ type ChainConfig struct { IstanbulBlock *big.Int `json:"istanbulBlock,omitempty"` // Istanbul switch block (nil = no fork, 0 = already on istanbul) MuirGlacierBlock *big.Int `json:"muirGlacierBlock,omitempty"` // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated) - NetworkUpgrades // Config for timestamps that enable avalanche network upgrades - GenesisPrecompiles ChainConfigPrecompiles `json:"-"` // Config for enabling precompiles from genesis. JSON encode/decode will be handled by the custom marshaler/unmarshaler. - UpgradeConfig `json:"-"` // Config specified in upgradeBytes (avalanche network upgrades or enable/disabling precompiles). Skip encoding/decoding directly into ChainConfig. + NetworkUpgrades // Config for timestamps that enable avalanche network upgrades + GenesisPrecompiles Precompiles `json:"-"` // Config for enabling precompiles from genesis. JSON encode/decode will be handled by the custom marshaler/unmarshaler. + UpgradeConfig `json:"-"` // Config specified in upgradeBytes (avalanche network upgrades or enable/disabling precompiles). Skip encoding/decoding directly into ChainConfig. } // UnmarshalJSON parses the JSON-encoded data and stores the result in the @@ -561,6 +561,7 @@ type Rules struct { ActivePrecompiles map[common.Address]config.Config } +// IsPrecompileEnabled returns true if the precompile at [addr] is enabled for this rule set. func (r *Rules) IsPrecompileEnabled(addr common.Address) bool { _, ok := r.ActivePrecompiles[addr] return ok @@ -595,7 +596,7 @@ func (c *ChainConfig) AvalancheRules(blockNum, blockTimestamp *big.Int) Rules { // Initialize the stateful precompiles that should be enabled at [blockTimestamp]. rules.ActivePrecompiles = make(map[common.Address]config.Config) for _, module := range modules.RegisteredModules() { - if config := c.GetActivePrecompileConfig(module.Address, blockTimestamp); config != nil { + if config := c.GetActivePrecompileConfig(module.Address, blockTimestamp); config != nil && !config.IsDisabled() { rules.ActivePrecompiles[module.Address] = config } } diff --git a/params/precompile_config_test.go b/params/precompile_config_test.go index bb77d912e1..5f5342b46d 100644 --- a/params/precompile_config_test.go +++ b/params/precompile_config_test.go @@ -23,7 +23,7 @@ func TestVerifyWithChainConfig(t *testing.T) { admins := []common.Address{{1}} baseConfig := *SubnetEVMDefaultChainConfig config := &baseConfig - config.GenesisPrecompiles = ChainConfigPrecompiles{ + config.GenesisPrecompiles = Precompiles{ txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(2), nil, nil), } config.PrecompileUpgrades = []PrecompileUpgrade{ @@ -180,19 +180,19 @@ func TestVerifyPrecompiles(t *testing.T) { admins := []common.Address{{1}} tests := []struct { name string - precompiles ChainConfigPrecompiles + precompiles Precompiles expectedError string }{ { name: "invalid allow list config in tx allowlist", - precompiles: ChainConfigPrecompiles{ + precompiles: Precompiles{ txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(3), admins, admins), }, expectedError: "cannot set address", }, { name: "invalid initial fee manager config", - precompiles: ChainConfigPrecompiles{ + precompiles: Precompiles{ feemanager.ConfigKey: feemanager.NewConfig(big.NewInt(3), admins, nil, &commontype.FeeConfig{ GasLimit: big.NewInt(-1), @@ -240,7 +240,7 @@ func TestGetPrecompileConfig(t *testing.T) { assert := assert.New(t) baseConfig := *SubnetEVMDefaultChainConfig config := &baseConfig - config.GenesisPrecompiles = ChainConfigPrecompiles{ + config.GenesisPrecompiles = Precompiles{ deployerallowlist.ConfigKey: deployerallowlist.NewConfig(big.NewInt(10), nil, nil), } diff --git a/params/precompile_upgrade.go b/params/precompile_upgrade.go index 1bcff4e6b4..66dfee0202 100644 --- a/params/precompile_upgrade.go +++ b/params/precompile_upgrade.go @@ -248,8 +248,8 @@ func (c *ChainConfig) checkPrecompileCompatible(address common.Address, precompi } // EnabledStatefulPrecompiles returns current stateful precompile configs that are enabled at [blockTimestamp]. -func (c *ChainConfig) EnabledStatefulPrecompiles(blockTimestamp *big.Int) ChainConfigPrecompiles { - statefulPrecompileConfigs := make(ChainConfigPrecompiles) +func (c *ChainConfig) EnabledStatefulPrecompiles(blockTimestamp *big.Int) Precompiles { + statefulPrecompileConfigs := make(Precompiles) for _, module := range modules.RegisteredModules() { if config := c.GetActivePrecompileConfig(module.Address, blockTimestamp); config != nil && !config.IsDisabled() { statefulPrecompileConfigs[module.ConfigKey] = config diff --git a/params/precompile_upgrade_test.go b/params/precompile_upgrade_test.go index 3048693f65..1c85b04bcd 100644 --- a/params/precompile_upgrade_test.go +++ b/params/precompile_upgrade_test.go @@ -16,7 +16,7 @@ import ( func TestVerifyUpgradeConfig(t *testing.T) { admins := []common.Address{{1}} chainConfig := *TestChainConfig - chainConfig.GenesisPrecompiles = ChainConfigPrecompiles{ + chainConfig.GenesisPrecompiles = Precompiles{ txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(1), admins, nil), } @@ -73,7 +73,7 @@ func TestVerifyUpgradeConfig(t *testing.T) { func TestCheckCompatibleUpgradeConfigs(t *testing.T) { admins := []common.Address{{1}} chainConfig := *TestChainConfig - chainConfig.GenesisPrecompiles = ChainConfigPrecompiles{ + chainConfig.GenesisPrecompiles = Precompiles{ txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(1), admins, nil), deployerallowlist.ConfigKey: deployerallowlist.NewConfig(big.NewInt(10), admins, nil), } diff --git a/params/chain_config_precompiles.go b/params/precompiles.go similarity index 83% rename from params/chain_config_precompiles.go rename to params/precompiles.go index 5d60ab69b5..4ad0bf3a8f 100644 --- a/params/chain_config_precompiles.go +++ b/params/precompiles.go @@ -10,18 +10,18 @@ import ( "github.com/ava-labs/subnet-evm/precompile/modules" ) -type ChainConfigPrecompiles map[string]config.Config +type Precompiles map[string]config.Config // UnmarshalJSON parses the JSON-encoded data into the ChainConfigPrecompiles. // ChainConfigPrecompiles is a map of precompile module keys to their // configuration. -func (ccp *ChainConfigPrecompiles) UnmarshalJSON(data []byte) error { +func (ccp *Precompiles) UnmarshalJSON(data []byte) error { raw := make(map[string]json.RawMessage) if err := json.Unmarshal(data, &raw); err != nil { return err } - *ccp = make(ChainConfigPrecompiles) + *ccp = make(Precompiles) for _, module := range modules.RegisteredModules() { key := module.ConfigKey if value, ok := raw[key]; ok { diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index d97e23811f..8f07e6b7ef 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -2120,7 +2120,7 @@ func TestBuildAllowListActivationBlock(t *testing.T) { if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { t.Fatal(err) } - genesis.Config.GenesisPrecompiles = params.ChainConfigPrecompiles{ + genesis.Config.GenesisPrecompiles = params.Precompiles{ deployerallowlist.ConfigKey: deployerallowlist.NewConfig(big.NewInt(time.Now().Unix()), testEthAddrs, nil), } @@ -2186,7 +2186,7 @@ func TestTxAllowListSuccessfulTx(t *testing.T) { if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { t.Fatal(err) } - genesis.Config.GenesisPrecompiles = params.ChainConfigPrecompiles{ + genesis.Config.GenesisPrecompiles = params.Precompiles{ txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(0), testEthAddrs[0:1], nil), } genesisJSON, err := genesis.MarshalJSON() @@ -2264,7 +2264,7 @@ func TestTxAllowListDisablePrecompile(t *testing.T) { t.Fatal(err) } enableAllowListTimestamp := time.Unix(0, 0) // enable at genesis - genesis.Config.GenesisPrecompiles = params.ChainConfigPrecompiles{ + genesis.Config.GenesisPrecompiles = params.Precompiles{ txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(enableAllowListTimestamp.Unix()), testEthAddrs[0:1], nil), } genesisJSON, err := genesis.MarshalJSON() @@ -2378,7 +2378,7 @@ func TestFeeManagerChangeFee(t *testing.T) { if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { t.Fatal(err) } - genesis.Config.GenesisPrecompiles = params.ChainConfigPrecompiles{ + genesis.Config.GenesisPrecompiles = params.Precompiles{ feemanager.ConfigKey: feemanager.NewConfig(big.NewInt(0), testEthAddrs[0:1], nil, nil), } @@ -2620,7 +2620,7 @@ func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { genesis := &core.Genesis{} require.NoError(t, genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM))) - genesis.Config.GenesisPrecompiles = params.ChainConfigPrecompiles{ + genesis.Config.GenesisPrecompiles = params.Precompiles{ rewardmanager.ConfigKey: rewardmanager.NewConfig(common.Big0, testEthAddrs[0:1], nil, nil), } genesis.Config.AllowFeeRecipients = true // enable this in genesis to test if this is recognized by the reward manager @@ -2762,7 +2762,7 @@ func TestRewardManagerPrecompileAllowFeeRecipients(t *testing.T) { genesis := &core.Genesis{} require.NoError(t, genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM))) - genesis.Config.GenesisPrecompiles = params.ChainConfigPrecompiles{ + genesis.Config.GenesisPrecompiles = params.Precompiles{ rewardmanager.ConfigKey: rewardmanager.NewConfig(common.Big0, testEthAddrs[0:1], nil, nil), } genesis.Config.AllowFeeRecipients = false // disable this in genesis diff --git a/precompile/contract/utils.go b/precompile/contract/utils.go index 9cc50d3155..1718eb2dca 100644 --- a/precompile/contract/utils.go +++ b/precompile/contract/utils.go @@ -7,8 +7,10 @@ import ( "fmt" "regexp" "strings" + "testing" "github.com/ava-labs/subnet-evm/accounts/abi" + "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -22,6 +24,32 @@ const ( var functionSignatureRegex = regexp.MustCompile(`[\w]+\(((([\w]+)?)|((([\w]+),)+([\w]+)))\)`) +// PrecompileTest is a test case for a precompile +type PrecompileTest struct { + // Caller is the address of the precompile caller + Caller common.Address + // Input is the raw input bytes to the precompile + Input []byte + // SuppliedGas is the amount of gas supplied to the precompile + SuppliedGas uint64 + // ReadOnly is whether the precompile should be called in read only + // mode. If true, the precompile should not modify the state. + ReadOnly bool + // Config is the config to use for the precompile + // It should be the same precompile config that is used in the + // precompile's configurator. + // If nil, Configure on the Configurator will not be called. + Config config.Config + // BeforeHook is called before the precompile is called. + BeforeHook func(t *testing.T, state StateDB) + // AfterHook is called after the precompile is called. + AfterHook func(t *testing.T, state StateDB) + // ExpectedRes is the expected raw byte result returned by the precompile + ExpectedRes []byte + // ExpectedErr is the expected error returned by the precompile + ExpectedErr string +} + // CalculateFunctionSelector returns the 4 byte function selector that results from [functionSignature] // Ex. the function setBalance(addr address, balance uint256) should be passed in as the string: // "setBalance(address,uint256)" diff --git a/precompile/contracts/deployerallowlist/config_test.go b/precompile/contracts/deployerallowlist/config_test.go index c1245b0b09..46bba193d6 100644 --- a/precompile/contracts/deployerallowlist/config_test.go +++ b/precompile/contracts/deployerallowlist/config_test.go @@ -17,12 +17,12 @@ func TestVerifyContractDeployerConfig(t *testing.T) { tests := []struct { name string config config.Config - expectedError string + ExpectedError string }{ { name: "invalid allow list config in deployer allowlist", config: NewConfig(big.NewInt(3), admins, admins), - expectedError: "cannot set address", + ExpectedError: "cannot set address", }, } for _, tt := range tests { @@ -30,10 +30,10 @@ func TestVerifyContractDeployerConfig(t *testing.T) { require := require.New(t) err := tt.config.Verify() - if tt.expectedError == "" { + if tt.ExpectedError == "" { require.NoError(err) } else { - require.ErrorContains(err, tt.expectedError) + require.ErrorContains(err, tt.ExpectedError) } }) } diff --git a/precompile/contracts/deployerallowlist/contract_test.go b/precompile/contracts/deployerallowlist/contract_test.go index 245fea438a..f2b4a43138 100644 --- a/precompile/contracts/deployerallowlist/contract_test.go +++ b/precompile/contracts/deployerallowlist/contract_test.go @@ -4,185 +4,172 @@ package deployerallowlist import ( + "math/big" "testing" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) -type precompileTest struct { - caller common.Address - input func() []byte - suppliedGas uint64 - readOnly bool - - config config.Config - - preCondition func(t *testing.T, state *state.StateDB) - assertState func(t *testing.T, state *state.StateDB) - - expectedRes []byte - expectedErr string -} - func TestContractDeployerAllowListRun(t *testing.T) { - adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") - noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") + adminAddr := common.BigToAddress(big.NewInt(1)) + enabledAddr := common.BigToAddress(big.NewInt(2)) + noRoleAddr := common.BigToAddress(big.NewInt(3)) - for name, test := range map[string]precompileTest{ + for name, test := range map[string]contract.PrecompileTest{ "set admin": { - caller: adminAddr, - input: func() []byte { + Caller: adminAddr, + Input: func(tt *testing.T) []byte { input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AdminRole) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { + }(t), + SuppliedGas: allowlist.ModifyAllowListGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t *testing.T, state contract.StateDB) { res := GetContractDeployerAllowListStatus(state, noRoleAddr) require.Equal(t, allowlist.AdminRole, res) }, }, "set deployer": { - caller: adminAddr, - input: func() []byte { + Caller: adminAddr, + Input: func(tt *testing.T) []byte { input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.EnabledRole) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { + }(t), + SuppliedGas: allowlist.ModifyAllowListGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t *testing.T, state contract.StateDB) { res := GetContractDeployerAllowListStatus(state, noRoleAddr) require.Equal(t, allowlist.EnabledRole, res) }, }, "set no role": { - caller: adminAddr, - input: func() []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) - require.NoError(t, err) + Caller: adminAddr, + Input: func(tt *testing.T) []byte { + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.NoRole) + require.NoError(tt, err) return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { + }(t), + SuppliedGas: allowlist.ModifyAllowListGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t *testing.T, state contract.StateDB) { res := GetContractDeployerAllowListStatus(state, adminAddr) - require.Equal(t, allowlist.EnabledRole, res) + require.Equal(t, allowlist.NoRole, res) }, }, + "set no role from enabled": { + Caller: enabledAddr, + Input: func(tt *testing.T) []byte { + input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.NoRole) + require.NoError(tt, err) + + return input + }(t), + SuppliedGas: allowlist.ModifyAllowListGasCost, + ReadOnly: false, + ExpectedErr: allowlist.ErrCannotModifyAllowList.Error(), + }, "set no role from non-admin": { - caller: noRoleAddr, - input: func() []byte { + Caller: noRoleAddr, + Input: func(tt *testing.T) []byte { input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedErr: allowlist.ErrCannotModifyAllowList.Error(), + }(t), + SuppliedGas: allowlist.ModifyAllowListGasCost, + ReadOnly: false, + ExpectedErr: allowlist.ErrCannotModifyAllowList.Error(), }, "set deployer from non-admin": { - caller: noRoleAddr, - input: func() []byte { + Caller: noRoleAddr, + Input: func(tt *testing.T) []byte { input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedErr: allowlist.ErrCannotModifyAllowList.Error(), + }(t), + SuppliedGas: allowlist.ModifyAllowListGasCost, + ReadOnly: false, + ExpectedErr: allowlist.ErrCannotModifyAllowList.Error(), }, "set admin from non-admin": { - caller: noRoleAddr, - input: func() []byte { + Caller: noRoleAddr, + Input: func(tt *testing.T) []byte { input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AdminRole) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedErr: allowlist.ErrCannotModifyAllowList.Error(), + }(t), + SuppliedGas: allowlist.ModifyAllowListGasCost, + ReadOnly: false, + ExpectedErr: allowlist.ErrCannotModifyAllowList.Error(), }, "set no role with readOnly enabled": { - caller: adminAddr, - input: func() []byte { + Caller: adminAddr, + Input: func(tt *testing.T) []byte { input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), + }(t), + SuppliedGas: allowlist.ModifyAllowListGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "set no role insufficient gas": { - caller: adminAddr, - input: func() []byte { + Caller: adminAddr, + Input: func(tt *testing.T) []byte { input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), + }(t), + SuppliedGas: allowlist.ModifyAllowListGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, "read allow list no role": { - caller: noRoleAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost, - readOnly: false, - expectedRes: common.Hash(allowlist.EnabledRole).Bytes(), - assertState: nil, + Caller: noRoleAddr, + Input: allowlist.PackReadAllowList(enabledAddr), + SuppliedGas: allowlist.ReadAllowListGasCost, + ReadOnly: false, + ExpectedRes: common.Hash(allowlist.EnabledRole).Bytes(), }, "read allow list admin role": { - caller: adminAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost, - readOnly: false, - expectedRes: common.Hash(allowlist.EnabledRole).Bytes(), - assertState: nil, + Caller: adminAddr, + Input: allowlist.PackReadAllowList(enabledAddr), + SuppliedGas: allowlist.ReadAllowListGasCost, + ReadOnly: false, + ExpectedRes: common.Hash(allowlist.EnabledRole).Bytes(), }, "read allow list with readOnly enabled": { - caller: adminAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost, - readOnly: true, - expectedRes: common.Hash(allowlist.EnabledRole).Bytes(), - assertState: nil, + Caller: adminAddr, + Input: allowlist.PackReadAllowList(enabledAddr), + SuppliedGas: allowlist.ReadAllowListGasCost, + ReadOnly: true, + ExpectedRes: common.Hash(allowlist.EnabledRole).Bytes(), }, "read allow list out of gas": { - caller: adminAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost - 1, - readOnly: true, - expectedErr: vmerrs.ErrOutOfGas.Error(), + Caller: adminAddr, + Input: allowlist.PackReadAllowList(noRoleAddr), + SuppliedGas: allowlist.ReadAllowListGasCost - 1, + ReadOnly: true, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, } { t.Run(name, func(t *testing.T) { @@ -192,24 +179,26 @@ func TestContractDeployerAllowListRun(t *testing.T) { // Set up the state so that each address has the expected permissions at the start. SetContractDeployerAllowListStatus(state, adminAddr, allowlist.AdminRole) - SetContractDeployerAllowListStatus(state, noRoleAddr, allowlist.EnabledRole) + SetContractDeployerAllowListStatus(state, enabledAddr, allowlist.EnabledRole) + SetContractDeployerAllowListStatus(state, noRoleAddr, allowlist.NoRole) require.Equal(t, allowlist.AdminRole, GetContractDeployerAllowListStatus(state, adminAddr)) - require.Equal(t, allowlist.EnabledRole, GetContractDeployerAllowListStatus(state, noRoleAddr)) + require.Equal(t, allowlist.EnabledRole, GetContractDeployerAllowListStatus(state, enabledAddr)) + require.Equal(t, allowlist.NoRole, GetContractDeployerAllowListStatus(state, noRoleAddr)) blockContext := contract.NewMockBlockContext(common.Big0, 0) accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) - ret, remainingGas, err := ContractDeployerAllowListPrecompile.Run(accesibleState, test.caller, ContractAddress, test.input(), test.suppliedGas, test.readOnly) - if len(test.expectedErr) != 0 { - require.ErrorContains(t, err, test.expectedErr) + ret, remainingGas, err := ContractDeployerAllowListPrecompile.Run(accesibleState, test.Caller, ContractAddress, test.Input, test.SuppliedGas, test.ReadOnly) + if len(test.ExpectedErr) != 0 { + require.ErrorContains(t, err, test.ExpectedErr) } else { require.NoError(t, err) } require.Equal(t, uint64(0), remainingGas) - require.Equal(t, test.expectedRes, ret) + require.Equal(t, test.ExpectedRes, ret) - if test.assertState != nil { - test.assertState(t, state) + if test.AfterHook != nil { + test.AfterHook(t, state) } }) } diff --git a/precompile/contracts/deployerallowlist/module.go b/precompile/contracts/deployerallowlist/module.go index 47e4bc7269..73bd720bed 100644 --- a/precompile/contracts/deployerallowlist/module.go +++ b/precompile/contracts/deployerallowlist/module.go @@ -39,7 +39,7 @@ func (*configurator) NewConfig() config.Config { return &Config{} } -// Configure configures [state] with the initial state for the precompile. +// Configure configures [state] with the given [cfg] config. func (c *configurator) Configure(_ contract.ChainConfig, cfg config.Config, state contract.StateDB, _ contract.BlockContext) error { config, ok := cfg.(*Config) if !ok { diff --git a/precompile/contracts/feemanager/config.go b/precompile/contracts/feemanager/config.go index 9db36ffeee..05d04991f5 100644 --- a/precompile/contracts/feemanager/config.go +++ b/precompile/contracts/feemanager/config.go @@ -23,7 +23,8 @@ type Config struct { } // NewConfig returns a config for a network upgrade at [blockTimestamp] that enables -// FeeManager with the given [admins] and [enableds] as members of the allowlist with [initialConfig] as initial fee config if specified. +// FeeManager with the given [admins] and [enableds] as members of the +// allowlist with [initialConfig] as initial fee config if specified. func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialConfig *commontype.FeeConfig) *Config { return &Config{ Config: allowlist.Config{ diff --git a/precompile/contracts/feemanager/config_test.go b/precompile/contracts/feemanager/config_test.go index 861c713023..6deccb1ba5 100644 --- a/precompile/contracts/feemanager/config_test.go +++ b/precompile/contracts/feemanager/config_test.go @@ -31,12 +31,12 @@ func TestVerifyFeeManagerConfig(t *testing.T) { tests := []struct { name string config config.Config - expectedError string + ExpectedError string }{ { name: "invalid allow list config in fee manager allowlist", config: NewConfig(big.NewInt(3), admins, admins, nil), - expectedError: "cannot set address", + ExpectedError: "cannot set address", }, { name: "invalid initial fee manager config", @@ -44,7 +44,7 @@ func TestVerifyFeeManagerConfig(t *testing.T) { &commontype.FeeConfig{ GasLimit: big.NewInt(0), }), - expectedError: "gasLimit = 0 cannot be less than or equal to 0", + ExpectedError: "gasLimit = 0 cannot be less than or equal to 0", }, } for _, tt := range tests { @@ -52,10 +52,10 @@ func TestVerifyFeeManagerConfig(t *testing.T) { require := require.New(t) err := tt.config.Verify() - if tt.expectedError == "" { + if tt.ExpectedError == "" { require.NoError(err) } else { - require.ErrorContains(err, tt.expectedError) + require.ErrorContains(err, tt.ExpectedError) } }) } diff --git a/precompile/contracts/feemanager/contract.go b/precompile/contracts/feemanager/contract.go index 0d08338d36..a4ecd9a4e4 100644 --- a/precompile/contracts/feemanager/contract.go +++ b/precompile/contracts/feemanager/contract.go @@ -112,7 +112,7 @@ func packFeeConfigHelper(feeConfig commontype.FeeConfig, useSelector bool) ([]by // assumes that [input] does not include selector (omits first 4 bytes in PackSetFeeConfigInput) func UnpackFeeConfigInput(input []byte) (commontype.FeeConfig, error) { if len(input) != feeConfigInputLen { - return commontype.FeeConfig{}, fmt.Errorf("invalid input length for fee config input: %d", len(input)) + return commontype.FeeConfig{}, fmt.Errorf("invalid input length for fee config Input: %d", len(input)) } feeConfig := commontype.FeeConfig{} for i := minFeeConfigFieldKey; i <= numFeeConfigField; i++ { diff --git a/precompile/contracts/feemanager/contract_test.go b/precompile/contracts/feemanager/contract_test.go index 698fce802d..35e7fe89f3 100644 --- a/precompile/contracts/feemanager/contract_test.go +++ b/precompile/contracts/feemanager/contract_test.go @@ -12,28 +12,12 @@ import ( "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) -type precompileTest struct { - caller common.Address - input func() []byte - suppliedGas uint64 - readOnly bool - - config config.Config - - preCondition func(t *testing.T, state *state.StateDB) - assertState func(t *testing.T, state *state.StateDB) - - expectedRes []byte - expectedErr string -} - var testFeeConfig = commontype.FeeConfig{ GasLimit: big.NewInt(8_000_000), TargetBlockRate: 2, // in seconds @@ -50,73 +34,72 @@ var testFeeConfig = commontype.FeeConfig{ func TestFeeManagerRun(t *testing.T) { testBlockNumber := big.NewInt(7) - adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") - enabledAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") - noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") + adminAddr := common.BigToAddress(common.Big0) + enabledAddr := common.BigToAddress(common.Big1) + noRoleAddr := common.BigToAddress(common.Big2) - for name, test := range map[string]precompileTest{ + for name, test := range map[string]contract.PrecompileTest{ "set config from no role fails": { - caller: noRoleAddr, - input: func() []byte { + Caller: noRoleAddr, + Input: func(tt *testing.T) []byte { input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: SetFeeConfigGasCost, - readOnly: false, - expectedErr: ErrCannotChangeFee.Error(), + }(t), + SuppliedGas: SetFeeConfigGasCost, + ReadOnly: false, + ExpectedErr: ErrCannotChangeFee.Error(), }, "set config from enabled address": { - caller: enabledAddr, - input: func() []byte { + Caller: enabledAddr, + Input: func(tt *testing.T) []byte { input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: SetFeeConfigGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { + }(t), + SuppliedGas: SetFeeConfigGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t *testing.T, state contract.StateDB) { feeConfig := GetStoredFeeConfig(state) require.Equal(t, testFeeConfig, feeConfig) }, }, "set invalid config from enabled address": { - caller: enabledAddr, - input: func() []byte { + Caller: enabledAddr, + Input: func(tt *testing.T) []byte { feeConfig := testFeeConfig feeConfig.MinBlockGasCost = new(big.Int).Mul(feeConfig.MaxBlockGasCost, common.Big2) input, err := PackSetFeeConfig(feeConfig) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: SetFeeConfigGasCost, - readOnly: false, - expectedRes: nil, - config: &Config{ + }(t), + SuppliedGas: SetFeeConfigGasCost, + ReadOnly: false, + Config: &Config{ InitialFeeConfig: &testFeeConfig, }, - expectedErr: "cannot be greater than maxBlockGasCost", - assertState: func(t *testing.T, state *state.StateDB) { + ExpectedErr: "cannot be greater than maxBlockGasCost", + AfterHook: func(t *testing.T, state contract.StateDB) { feeConfig := GetStoredFeeConfig(state) require.Equal(t, testFeeConfig, feeConfig) }, }, "set config from admin address": { - caller: adminAddr, - input: func() []byte { + Caller: adminAddr, + Input: func(tt *testing.T) []byte { input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: SetFeeConfigGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { + }(t), + SuppliedGas: SetFeeConfigGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t *testing.T, state contract.StateDB) { feeConfig := GetStoredFeeConfig(state) require.Equal(t, testFeeConfig, feeConfig) lastChangedAt := GetFeeConfigLastChangedAt(state) @@ -124,22 +107,20 @@ func TestFeeManagerRun(t *testing.T) { }, }, "get fee config from non-enabled address": { - caller: noRoleAddr, - preCondition: func(t *testing.T, state *state.StateDB) { + Caller: noRoleAddr, + BeforeHook: func(t *testing.T, state contract.StateDB) { err := StoreFeeConfig(state, testFeeConfig, contract.NewMockBlockContext(big.NewInt(6), 0)) require.NoError(t, err) }, - input: func() []byte { - return PackGetFeeConfigInput() - }, - suppliedGas: GetFeeConfigGasCost, - readOnly: true, - expectedRes: func() []byte { + Input: PackGetFeeConfigInput(), + SuppliedGas: GetFeeConfigGasCost, + ReadOnly: true, + ExpectedRes: func() []byte { res, err := PackFeeConfig(testFeeConfig) require.NoError(t, err) return res }(), - assertState: func(t *testing.T, state *state.StateDB) { + AfterHook: func(t *testing.T, state contract.StateDB) { feeConfig := GetStoredFeeConfig(state) lastChangedAt := GetFeeConfigLastChangedAt(state) require.Equal(t, testFeeConfig, feeConfig) @@ -147,21 +128,19 @@ func TestFeeManagerRun(t *testing.T) { }, }, "get initial fee config": { - caller: noRoleAddr, - input: func() []byte { - return PackGetFeeConfigInput() - }, - suppliedGas: GetFeeConfigGasCost, - config: &Config{ + Caller: noRoleAddr, + Input: PackGetFeeConfigInput(), + SuppliedGas: GetFeeConfigGasCost, + Config: &Config{ InitialFeeConfig: &testFeeConfig, }, - readOnly: true, - expectedRes: func() []byte { + ReadOnly: true, + ExpectedRes: func() []byte { res, err := PackFeeConfig(testFeeConfig) require.NoError(t, err) return res }(), - assertState: func(t *testing.T, state *state.StateDB) { + AfterHook: func(t *testing.T, state contract.StateDB) { feeConfig := GetStoredFeeConfig(state) lastChangedAt := GetFeeConfigLastChangedAt(state) require.Equal(t, testFeeConfig, feeConfig) @@ -169,18 +148,16 @@ func TestFeeManagerRun(t *testing.T) { }, }, "get last changed at from non-enabled address": { - caller: noRoleAddr, - preCondition: func(t *testing.T, state *state.StateDB) { + Caller: noRoleAddr, + BeforeHook: func(t *testing.T, state contract.StateDB) { err := StoreFeeConfig(state, testFeeConfig, contract.NewMockBlockContext(testBlockNumber, 0)) require.NoError(t, err) }, - input: func() []byte { - return PackGetLastChangedAtInput() - }, - suppliedGas: GetLastChangedAtGasCost, - readOnly: true, - expectedRes: common.BigToHash(testBlockNumber).Bytes(), - assertState: func(t *testing.T, state *state.StateDB) { + Input: PackGetLastChangedAtInput(), + SuppliedGas: GetLastChangedAtGasCost, + ReadOnly: true, + ExpectedRes: common.BigToHash(testBlockNumber).Bytes(), + AfterHook: func(t *testing.T, state contract.StateDB) { feeConfig := GetStoredFeeConfig(state) lastChangedAt := GetFeeConfigLastChangedAt(state) require.Equal(t, testFeeConfig, feeConfig) @@ -188,52 +165,52 @@ func TestFeeManagerRun(t *testing.T) { }, }, "readOnly setFeeConfig with noRole fails": { - caller: noRoleAddr, - input: func() []byte { + Caller: noRoleAddr, + Input: func(tt *testing.T) []byte { input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: SetFeeConfigGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), + }(t), + SuppliedGas: SetFeeConfigGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly setFeeConfig with allow role fails": { - caller: enabledAddr, - input: func() []byte { + Caller: enabledAddr, + Input: func(tt *testing.T) []byte { input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: SetFeeConfigGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), + }(t), + SuppliedGas: SetFeeConfigGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly setFeeConfig with admin role fails": { - caller: adminAddr, - input: func() []byte { + Caller: adminAddr, + Input: func(tt *testing.T) []byte { input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: SetFeeConfigGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), + }(t), + SuppliedGas: SetFeeConfigGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "insufficient gas setFeeConfig from admin": { - caller: adminAddr, - input: func() []byte { + Caller: adminAddr, + Input: func(tt *testing.T) []byte { input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: SetFeeConfigGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), + }(t), + SuppliedGas: SetFeeConfigGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, } { t.Run(name, func(t *testing.T) { @@ -247,26 +224,26 @@ func TestFeeManagerRun(t *testing.T) { require.Equal(t, allowlist.AdminRole, GetFeeManagerStatus(state, adminAddr)) require.Equal(t, allowlist.EnabledRole, GetFeeManagerStatus(state, enabledAddr)) - if test.preCondition != nil { - test.preCondition(t, state) + if test.BeforeHook != nil { + test.BeforeHook(t, state) } blockContext := contract.NewMockBlockContext(testBlockNumber, 0) accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) - if test.config != nil { - Module.Configure(nil, test.config, state, blockContext) + if test.Config != nil { + Module.Configure(nil, test.Config, state, blockContext) } - ret, remainingGas, err := FeeManagerPrecompile.Run(accesibleState, test.caller, ContractAddress, test.input(), test.suppliedGas, test.readOnly) - if len(test.expectedErr) != 0 { - require.ErrorContains(t, err, test.expectedErr) + ret, remainingGas, err := FeeManagerPrecompile.Run(accesibleState, test.Caller, ContractAddress, test.Input, test.SuppliedGas, test.ReadOnly) + if len(test.ExpectedErr) != 0 { + require.ErrorContains(t, err, test.ExpectedErr) } else { require.NoError(t, err) } require.Equal(t, uint64(0), remainingGas) - require.Equal(t, test.expectedRes, ret) + require.Equal(t, test.ExpectedRes, ret) - if test.assertState != nil { - test.assertState(t, state) + if test.AfterHook != nil { + test.AfterHook(t, state) } }) } diff --git a/precompile/contracts/nativeminter/config_test.go b/precompile/contracts/nativeminter/config_test.go index d1232b9cc3..08227a9be1 100644 --- a/precompile/contracts/nativeminter/config_test.go +++ b/precompile/contracts/nativeminter/config_test.go @@ -19,22 +19,22 @@ func TestVerifyContractNativeMinterConfig(t *testing.T) { tests := []struct { name string config config.Config - expectedError string + ExpectedError string }{ { name: "invalid allow list config in native minter allowlist", config: NewConfig(big.NewInt(3), admins, admins, nil), - expectedError: "cannot set address", + ExpectedError: "cannot set address", }, { name: "duplicate admins in config in native minter allowlist", config: NewConfig(big.NewInt(3), append(admins, admins[0]), enableds, nil), - expectedError: "duplicate address", + ExpectedError: "duplicate address", }, { name: "duplicate enableds in config in native minter allowlist", config: NewConfig(big.NewInt(3), admins, append(enableds, enableds[0]), nil), - expectedError: "duplicate address", + ExpectedError: "duplicate address", }, { name: "nil amount in native minter config", @@ -43,7 +43,7 @@ func TestVerifyContractNativeMinterConfig(t *testing.T) { common.HexToAddress("0x01"): math.NewHexOrDecimal256(123), common.HexToAddress("0x02"): nil, }), - expectedError: "initial mint cannot contain nil", + ExpectedError: "initial mint cannot contain nil", }, { name: "negative amount in native minter config", @@ -52,7 +52,7 @@ func TestVerifyContractNativeMinterConfig(t *testing.T) { common.HexToAddress("0x01"): math.NewHexOrDecimal256(123), common.HexToAddress("0x02"): math.NewHexOrDecimal256(-1), }), - expectedError: "initial mint cannot contain invalid amount", + ExpectedError: "initial mint cannot contain invalid amount", }, } for _, tt := range tests { @@ -60,10 +60,10 @@ func TestVerifyContractNativeMinterConfig(t *testing.T) { require := require.New(t) err := tt.config.Verify() - if tt.expectedError == "" { + if tt.ExpectedError == "" { require.NoError(err) } else { - require.ErrorContains(err, tt.expectedError) + require.ErrorContains(err, tt.ExpectedError) } }) } diff --git a/precompile/contracts/nativeminter/contract_test.go b/precompile/contracts/nativeminter/contract_test.go index 4eb3202495..e00ccf759b 100644 --- a/precompile/contracts/nativeminter/contract_test.go +++ b/precompile/contracts/nativeminter/contract_test.go @@ -11,7 +11,6 @@ import ( "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" @@ -19,142 +18,127 @@ import ( "github.com/stretchr/testify/require" ) -type precompileTest struct { - caller common.Address - input func() []byte - suppliedGas uint64 - readOnly bool - - config config.Config - - preCondition func(t *testing.T, state *state.StateDB) - assertState func(t *testing.T, state *state.StateDB) - - expectedRes []byte - expectedErr string -} - func TestContractNativeMinterRun(t *testing.T) { - adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") - enabledAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") - noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") + adminAddr := common.BigToAddress(common.Big0) + enabledAddr := common.BigToAddress(common.Big1) + noRoleAddr := common.BigToAddress(common.Big2) - for name, test := range map[string]precompileTest{ + for name, test := range map[string]contract.PrecompileTest{ "mint funds from no role fails": { - caller: noRoleAddr, - input: func() []byte { + Caller: noRoleAddr, + Input: func(tt *testing.T) []byte { input, err := PackMintInput(noRoleAddr, common.Big1) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: MintGasCost, - readOnly: false, - expectedErr: ErrCannotMint.Error(), + }(t), + SuppliedGas: MintGasCost, + ReadOnly: false, + ExpectedErr: ErrCannotMint.Error(), }, "mint funds from enabled address": { - caller: enabledAddr, - input: func() []byte { + Caller: enabledAddr, + Input: func(tt *testing.T) []byte { input, err := PackMintInput(enabledAddr, common.Big1) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: MintGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { + }(t), + SuppliedGas: MintGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t *testing.T, state contract.StateDB) { require.Equal(t, common.Big1, state.GetBalance(enabledAddr), "expected minted funds") }, }, "initial mint funds": { - caller: enabledAddr, - config: &Config{ + Caller: enabledAddr, + Config: &Config{ InitialMint: map[common.Address]*math.HexOrDecimal256{ enabledAddr: math.NewHexOrDecimal256(2), }, }, - assertState: func(t *testing.T, state *state.StateDB) { + AfterHook: func(t *testing.T, state contract.StateDB) { require.Equal(t, common.Big2, state.GetBalance(enabledAddr), "expected minted funds") }, }, "mint funds from admin address": { - caller: adminAddr, - input: func() []byte { + Caller: adminAddr, + Input: func(tt *testing.T) []byte { input, err := PackMintInput(adminAddr, common.Big1) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: MintGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { + }(t), + SuppliedGas: MintGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t *testing.T, state contract.StateDB) { require.Equal(t, common.Big1, state.GetBalance(adminAddr), "expected minted funds") }, }, "mint max big funds": { - caller: adminAddr, - input: func() []byte { + Caller: adminAddr, + Input: func(tt *testing.T) []byte { input, err := PackMintInput(adminAddr, math.MaxBig256) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: MintGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { + }(t), + SuppliedGas: MintGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t *testing.T, state contract.StateDB) { require.Equal(t, math.MaxBig256, state.GetBalance(adminAddr), "expected minted funds") }, }, "readOnly mint with noRole fails": { - caller: noRoleAddr, - input: func() []byte { + Caller: noRoleAddr, + Input: func(tt *testing.T) []byte { input, err := PackMintInput(adminAddr, common.Big1) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: MintGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), + }(t), + SuppliedGas: MintGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly mint with allow role fails": { - caller: enabledAddr, - input: func() []byte { + Caller: enabledAddr, + Input: func(tt *testing.T) []byte { input, err := PackMintInput(enabledAddr, common.Big1) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: MintGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), + }(t), + SuppliedGas: MintGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly mint with admin role fails": { - caller: adminAddr, - input: func() []byte { + Caller: adminAddr, + Input: func(tt *testing.T) []byte { input, err := PackMintInput(adminAddr, common.Big1) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: MintGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), + }(t), + SuppliedGas: MintGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "insufficient gas mint from admin": { - caller: adminAddr, - input: func() []byte { + Caller: adminAddr, + Input: func(tt *testing.T) []byte { input, err := PackMintInput(enabledAddr, common.Big1) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: MintGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), + }(t), + SuppliedGas: MintGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, } { t.Run(name, func(t *testing.T) { @@ -170,23 +154,23 @@ func TestContractNativeMinterRun(t *testing.T) { blockContext := contract.NewMockBlockContext(common.Big0, 0) accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) - if test.config != nil { - Module.Configure(params.TestChainConfig, test.config, state, blockContext) + if test.Config != nil { + Module.Configure(params.TestChainConfig, test.Config, state, blockContext) } - if test.input != nil { - ret, remainingGas, err := ContractNativeMinterPrecompile.Run(accesibleState, test.caller, ContractAddress, test.input(), test.suppliedGas, test.readOnly) - if len(test.expectedErr) != 0 { - require.ErrorContains(t, err, test.expectedErr) + if test.Input != nil { + ret, remainingGas, err := ContractNativeMinterPrecompile.Run(accesibleState, test.Caller, ContractAddress, test.Input, test.SuppliedGas, test.ReadOnly) + if len(test.ExpectedErr) != 0 { + require.ErrorContains(t, err, test.ExpectedErr) } else { require.NoError(t, err) } require.Equal(t, uint64(0), remainingGas) - require.Equal(t, test.expectedRes, ret) + require.Equal(t, test.ExpectedRes, ret) } - if test.assertState != nil { - test.assertState(t, state) + if test.AfterHook != nil { + test.AfterHook(t, state) } }) } diff --git a/precompile/contracts/rewardmanager/config_test.go b/precompile/contracts/rewardmanager/config_test.go index 669ec1cb64..249fb85a16 100644 --- a/precompile/contracts/rewardmanager/config_test.go +++ b/precompile/contracts/rewardmanager/config_test.go @@ -18,12 +18,12 @@ func TestVerifyRewardManagerConfig(t *testing.T) { tests := []struct { name string config config.Config - expectedError string + ExpectedError string }{ { name: "duplicate enableds in config in reward manager allowlist", config: NewConfig(big.NewInt(3), admins, append(enableds, enableds[0]), nil), - expectedError: "duplicate address", + ExpectedError: "duplicate address", }, { name: "both reward mechanisms should not be activated at the same time in reward manager", @@ -31,7 +31,7 @@ func TestVerifyRewardManagerConfig(t *testing.T) { AllowFeeRecipients: true, RewardAddress: common.HexToAddress("0x01"), }), - expectedError: ErrCannotEnableBothRewards.Error(), + ExpectedError: ErrCannotEnableBothRewards.Error(), }, } for _, tt := range tests { @@ -39,10 +39,10 @@ func TestVerifyRewardManagerConfig(t *testing.T) { require := require.New(t) err := tt.config.Verify() - if tt.expectedError == "" { + if tt.ExpectedError == "" { require.NoError(err) } else { - require.ErrorContains(err, tt.expectedError) + require.ErrorContains(err, tt.ExpectedError) } }) } diff --git a/precompile/contracts/rewardmanager/contract_test.go b/precompile/contracts/rewardmanager/contract_test.go index e2db6b5d5e..bcd0c7474b 100644 --- a/precompile/contracts/rewardmanager/contract_test.go +++ b/precompile/contracts/rewardmanager/contract_test.go @@ -12,7 +12,6 @@ import ( "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" @@ -21,262 +20,247 @@ import ( var testBlockNumber = big.NewInt(7) -type precompileTest struct { - caller common.Address - input func() []byte - suppliedGas uint64 - readOnly bool - - config config.Config - - preCondition func(t *testing.T, state *state.StateDB) - assertState func(t *testing.T, state *state.StateDB) - - expectedRes []byte - expectedErr string -} - func TestRewardManagerRun(t *testing.T) { - adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") - enabledAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") - noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") + adminAddr := common.BigToAddress(common.Big0) + enabledAddr := common.BigToAddress(common.Big1) + noRoleAddr := common.BigToAddress(common.Big2) testAddr := common.HexToAddress("0x0123") - for name, test := range map[string]precompileTest{ + for name, test := range map[string]contract.PrecompileTest{ "set allow fee recipients from no role fails": { - caller: noRoleAddr, - input: func() []byte { + Caller: noRoleAddr, + Input: func(tt *testing.T) []byte { input, err := PackAllowFeeRecipients() - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: AllowFeeRecipientsGasCost, - readOnly: false, - expectedErr: ErrCannotAllowFeeRecipients.Error(), + }(t), + SuppliedGas: AllowFeeRecipientsGasCost, + ReadOnly: false, + ExpectedErr: ErrCannotAllowFeeRecipients.Error(), }, "set reward address from no role fails": { - caller: noRoleAddr, - input: func() []byte { + Caller: noRoleAddr, + Input: func(tt *testing.T) []byte { input, err := PackSetRewardAddress(testAddr) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: SetRewardAddressGasCost, - readOnly: false, - expectedErr: ErrCannotSetRewardAddress.Error(), + }(t), + SuppliedGas: SetRewardAddressGasCost, + ReadOnly: false, + ExpectedErr: ErrCannotSetRewardAddress.Error(), }, "disable rewards from no role fails": { - caller: noRoleAddr, - input: func() []byte { + Caller: noRoleAddr, + Input: func(tt *testing.T) []byte { input, err := PackDisableRewards() - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: DisableRewardsGasCost, - readOnly: false, - expectedErr: ErrCannotDisableRewards.Error(), + }(t), + SuppliedGas: DisableRewardsGasCost, + ReadOnly: false, + ExpectedErr: ErrCannotDisableRewards.Error(), }, "set allow fee recipients from enabled succeeds": { - caller: enabledAddr, - input: func() []byte { + Caller: enabledAddr, + Input: func(tt *testing.T) []byte { input, err := PackAllowFeeRecipients() - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: AllowFeeRecipientsGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { + }(t), + SuppliedGas: AllowFeeRecipientsGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t *testing.T, state contract.StateDB) { _, isFeeRecipients := GetStoredRewardAddress(state) require.True(t, isFeeRecipients) }, }, "set reward address from enabled succeeds": { - caller: enabledAddr, - input: func() []byte { + Caller: enabledAddr, + Input: func(tt *testing.T) []byte { input, err := PackSetRewardAddress(testAddr) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: SetRewardAddressGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { + }(t), + SuppliedGas: SetRewardAddressGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t *testing.T, state contract.StateDB) { address, isFeeRecipients := GetStoredRewardAddress(state) require.Equal(t, testAddr, address) require.False(t, isFeeRecipients) }, }, "disable rewards from enabled succeeds": { - caller: enabledAddr, - input: func() []byte { + Caller: enabledAddr, + Input: func(tt *testing.T) []byte { input, err := PackDisableRewards() - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: DisableRewardsGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { + }(t), + SuppliedGas: DisableRewardsGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t *testing.T, state contract.StateDB) { address, isFeeRecipients := GetStoredRewardAddress(state) require.False(t, isFeeRecipients) require.Equal(t, constants.BlackholeAddr, address) }, }, "get current reward address from no role succeeds": { - caller: noRoleAddr, - preCondition: func(t *testing.T, state *state.StateDB) { + Caller: noRoleAddr, + BeforeHook: func(t *testing.T, state contract.StateDB) { StoreRewardAddress(state, testAddr) }, - input: func() []byte { + Input: func(tt *testing.T) []byte { input, err := PackCurrentRewardAddress() - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: CurrentRewardAddressGasCost, - readOnly: false, - expectedRes: func() []byte { + }(t), + SuppliedGas: CurrentRewardAddressGasCost, + ReadOnly: false, + ExpectedRes: func() []byte { res, err := PackCurrentRewardAddressOutput(testAddr) require.NoError(t, err) return res }(), }, "get are fee recipients allowed from no role succeeds": { - caller: noRoleAddr, - preCondition: func(t *testing.T, state *state.StateDB) { + Caller: noRoleAddr, + BeforeHook: func(t *testing.T, state contract.StateDB) { EnableAllowFeeRecipients(state) }, - input: func() []byte { + Input: func(tt *testing.T) []byte { input, err := PackAreFeeRecipientsAllowed() - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: AreFeeRecipientsAllowedGasCost, - readOnly: false, - expectedRes: func() []byte { + }(t), + SuppliedGas: AreFeeRecipientsAllowedGasCost, + ReadOnly: false, + ExpectedRes: func() []byte { res, err := PackAreFeeRecipientsAllowedOutput(true) require.NoError(t, err) return res }(), }, "get initial config with address": { - caller: noRoleAddr, - input: func() []byte { + Caller: noRoleAddr, + Input: func(tt *testing.T) []byte { input, err := PackCurrentRewardAddress() - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: CurrentRewardAddressGasCost, - config: &Config{ + }(t), + SuppliedGas: CurrentRewardAddressGasCost, + Config: &Config{ InitialRewardConfig: &InitialRewardConfig{ RewardAddress: testAddr, }, }, - readOnly: false, - expectedRes: func() []byte { + ReadOnly: false, + ExpectedRes: func() []byte { res, err := PackCurrentRewardAddressOutput(testAddr) require.NoError(t, err) return res }(), }, "get initial config with allow fee recipients enabled": { - caller: noRoleAddr, - input: func() []byte { + Caller: noRoleAddr, + Input: func(tt *testing.T) []byte { input, err := PackAreFeeRecipientsAllowed() - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: AreFeeRecipientsAllowedGasCost, - config: &Config{ + }(t), + SuppliedGas: AreFeeRecipientsAllowedGasCost, + Config: &Config{ InitialRewardConfig: &InitialRewardConfig{ AllowFeeRecipients: true, }, }, - readOnly: false, - expectedRes: func() []byte { + ReadOnly: false, + ExpectedRes: func() []byte { res, err := PackAreFeeRecipientsAllowedOutput(true) require.NoError(t, err) return res }(), }, "readOnly allow fee recipients with allowed role fails": { - caller: enabledAddr, - input: func() []byte { + Caller: enabledAddr, + Input: func(tt *testing.T) []byte { input, err := PackAllowFeeRecipients() - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: AllowFeeRecipientsGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), + }(t), + SuppliedGas: AllowFeeRecipientsGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly set reward addresss with allowed role fails": { - caller: enabledAddr, - input: func() []byte { + Caller: enabledAddr, + Input: func(tt *testing.T) []byte { input, err := PackSetRewardAddress(testAddr) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: SetRewardAddressGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), + }(t), + SuppliedGas: SetRewardAddressGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "insufficient gas set reward address from allowed role": { - caller: enabledAddr, - input: func() []byte { + Caller: enabledAddr, + Input: func(tt *testing.T) []byte { input, err := PackSetRewardAddress(testAddr) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: SetRewardAddressGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), + }(t), + SuppliedGas: SetRewardAddressGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, "insufficient gas allow fee recipients from allowed role": { - caller: enabledAddr, - input: func() []byte { + Caller: enabledAddr, + Input: func(tt *testing.T) []byte { input, err := PackAllowFeeRecipients() - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: AllowFeeRecipientsGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), + }(t), + SuppliedGas: AllowFeeRecipientsGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, "insufficient gas read current reward address from allowed role": { - caller: enabledAddr, - input: func() []byte { + Caller: enabledAddr, + Input: func(tt *testing.T) []byte { input, err := PackCurrentRewardAddress() - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: CurrentRewardAddressGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), + }(t), + SuppliedGas: CurrentRewardAddressGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, "insufficient gas are fee recipients allowed from allowed role": { - caller: enabledAddr, - input: func() []byte { + Caller: enabledAddr, + Input: func(tt *testing.T) []byte { input, err := PackAreFeeRecipientsAllowed() - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: AreFeeRecipientsAllowedGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), + }(t), + SuppliedGas: AreFeeRecipientsAllowedGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, } { t.Run(name, func(t *testing.T) { @@ -290,28 +274,28 @@ func TestRewardManagerRun(t *testing.T) { require.Equal(t, allowlist.AdminRole, GetRewardManagerAllowListStatus(state, adminAddr)) require.Equal(t, allowlist.EnabledRole, GetRewardManagerAllowListStatus(state, enabledAddr)) - if test.preCondition != nil { - test.preCondition(t, state) + if test.BeforeHook != nil { + test.BeforeHook(t, state) } blockContext := contract.NewMockBlockContext(testBlockNumber, 0) accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) - if test.config != nil { - Module.Configure(nil, test.config, state, blockContext) + if test.Config != nil { + Module.Configure(nil, test.Config, state, blockContext) } - ret, remainingGas, err := RewardManagerPrecompile.Run(accesibleState, test.caller, ContractAddress, test.input(), test.suppliedGas, test.readOnly) - if len(test.expectedErr) != 0 { - require.ErrorContains(t, err, test.expectedErr) + ret, remainingGas, err := RewardManagerPrecompile.Run(accesibleState, test.Caller, ContractAddress, test.Input, test.SuppliedGas, test.ReadOnly) + if len(test.ExpectedErr) != 0 { + require.ErrorContains(t, err, test.ExpectedErr) } else { require.NoError(t, err) } require.Equal(t, uint64(0), remainingGas) - require.Equal(t, test.expectedRes, ret) + require.Equal(t, test.ExpectedRes, ret) - if test.assertState != nil { - test.assertState(t, state) + if test.AfterHook != nil { + test.AfterHook(t, state) } }) } diff --git a/precompile/contracts/rewardmanager/module.go b/precompile/contracts/rewardmanager/module.go index e5f18a7c2b..52f7ed62cb 100644 --- a/precompile/contracts/rewardmanager/module.go +++ b/precompile/contracts/rewardmanager/module.go @@ -57,5 +57,5 @@ func (*configurator) Configure(chainConfig contract.ChainConfig, cfg config.Conf // default to disabling rewards DisableFeeRewards(state) } - return config.Config.Configure(state, ContractAddress) + return config.Configure(state, ContractAddress) } diff --git a/precompile/contracts/txallowlist/config_test.go b/precompile/contracts/txallowlist/config_test.go index 51817d8959..e6cf062414 100644 --- a/precompile/contracts/txallowlist/config_test.go +++ b/precompile/contracts/txallowlist/config_test.go @@ -18,27 +18,27 @@ func TestVerifyTxAllowlistConfig(t *testing.T) { tests := []struct { name string config config.Config - expectedError string + ExpectedError string }{ { name: "invalid allow list config in tx allowlist", config: NewConfig(big.NewInt(3), admins, admins), - expectedError: "cannot set address", + ExpectedError: "cannot set address", }, { name: "nil member allow list config in tx allowlist", config: NewConfig(big.NewInt(3), nil, nil), - expectedError: "", + ExpectedError: "", }, { name: "empty member allow list config in tx allowlist", config: NewConfig(big.NewInt(3), []common.Address{}, []common.Address{}), - expectedError: "", + ExpectedError: "", }, { name: "valid allow list config in tx allowlist", config: NewConfig(big.NewInt(3), admins, enableds), - expectedError: "", + ExpectedError: "", }, } for _, tt := range tests { @@ -46,10 +46,10 @@ func TestVerifyTxAllowlistConfig(t *testing.T) { require := require.New(t) err := tt.config.Verify() - if tt.expectedError == "" { + if tt.ExpectedError == "" { require.NoError(err) } else { - require.ErrorContains(err, tt.expectedError) + require.ErrorContains(err, tt.ExpectedError) } }) } diff --git a/precompile/contracts/txallowlist/contract_test.go b/precompile/contracts/txallowlist/contract_test.go index 4fb3dfab74..6e7f678523 100644 --- a/precompile/contracts/txallowlist/contract_test.go +++ b/precompile/contracts/txallowlist/contract_test.go @@ -10,179 +10,155 @@ import ( "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) -type precompileTest struct { - caller common.Address - input func() []byte - suppliedGas uint64 - readOnly bool - - config config.Config - - preCondition func(t *testing.T, state *state.StateDB) - assertState func(t *testing.T, state *state.StateDB) - - expectedRes []byte - expectedErr string -} - func TestTxAllowListRun(t *testing.T) { - adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") - noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") + adminAddr := common.BigToAddress(common.Big0) + noRoleAddr := common.BigToAddress(common.Big2) - for name, test := range map[string]precompileTest{ + for name, test := range map[string]contract.PrecompileTest{ "set admin": { - caller: adminAddr, - input: func() []byte { + Caller: adminAddr, + Input: func(tt *testing.T) []byte { input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AdminRole) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { + }(t), + SuppliedGas: allowlist.ModifyAllowListGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t *testing.T, state contract.StateDB) { res := GetTxAllowListStatus(state, noRoleAddr) require.Equal(t, allowlist.AdminRole, res) }, }, "set allowed": { - caller: adminAddr, - input: func() []byte { + Caller: adminAddr, + Input: func(tt *testing.T) []byte { input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.EnabledRole) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { + }(t), + SuppliedGas: allowlist.ModifyAllowListGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t *testing.T, state contract.StateDB) { res := GetTxAllowListStatus(state, noRoleAddr) require.Equal(t, allowlist.EnabledRole, res) }, }, "set no role": { - caller: adminAddr, - input: func() []byte { + Caller: adminAddr, + Input: func(tt *testing.T) []byte { input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.NoRole) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { + }(t), + SuppliedGas: allowlist.ModifyAllowListGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t *testing.T, state contract.StateDB) { res := GetTxAllowListStatus(state, adminAddr) require.Equal(t, allowlist.NoRole, res) }, }, "set no role from non-admin": { - caller: noRoleAddr, - input: func() []byte { + Caller: noRoleAddr, + Input: func(tt *testing.T) []byte { input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.NoRole) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedErr: allowlist.ErrCannotModifyAllowList.Error(), + }(t), + SuppliedGas: allowlist.ModifyAllowListGasCost, + ReadOnly: false, + ExpectedErr: allowlist.ErrCannotModifyAllowList.Error(), }, "set allowed from non-admin": { - caller: noRoleAddr, - input: func() []byte { + Caller: noRoleAddr, + Input: func(tt *testing.T) []byte { input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedErr: allowlist.ErrCannotModifyAllowList.Error(), + }(t), + SuppliedGas: allowlist.ModifyAllowListGasCost, + ReadOnly: false, + ExpectedErr: allowlist.ErrCannotModifyAllowList.Error(), }, "set admin from non-admin": { - caller: noRoleAddr, - input: func() []byte { + Caller: noRoleAddr, + Input: func(tt *testing.T) []byte { input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AdminRole) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: false, - expectedErr: allowlist.ErrCannotModifyAllowList.Error(), + }(t), + SuppliedGas: allowlist.ModifyAllowListGasCost, + ReadOnly: false, + ExpectedErr: allowlist.ErrCannotModifyAllowList.Error(), }, "set no role with readOnly enabled": { - caller: adminAddr, - input: func() []byte { + Caller: adminAddr, + Input: func(tt *testing.T) []byte { input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), + }(t), + SuppliedGas: allowlist.ModifyAllowListGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "set no role insufficient gas": { - caller: adminAddr, - input: func() []byte { + Caller: adminAddr, + Input: func(tt *testing.T) []byte { input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) - require.NoError(t, err) + require.NoError(tt, err) return input - }, - suppliedGas: allowlist.ModifyAllowListGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), + }(t), + SuppliedGas: allowlist.ModifyAllowListGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, "read allow list with no role": { - caller: noRoleAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost, - readOnly: false, - expectedRes: common.Hash(allowlist.NoRole).Bytes(), - assertState: nil, + Caller: noRoleAddr, + Input: allowlist.PackReadAllowList(noRoleAddr), + SuppliedGas: allowlist.ReadAllowListGasCost, + ReadOnly: false, + ExpectedRes: common.Hash(allowlist.NoRole).Bytes(), + AfterHook: nil, }, "read allow list with admin role": { - caller: adminAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost, - readOnly: false, - expectedRes: common.Hash(allowlist.NoRole).Bytes(), - assertState: nil, + Caller: adminAddr, + Input: allowlist.PackReadAllowList(noRoleAddr), + SuppliedGas: allowlist.ReadAllowListGasCost, + ReadOnly: false, + ExpectedRes: common.Hash(allowlist.NoRole).Bytes(), + AfterHook: nil, }, "read allow list with readOnly enabled": { - caller: adminAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost, - readOnly: true, - expectedRes: common.Hash(allowlist.NoRole).Bytes(), - assertState: nil, + Caller: adminAddr, + Input: allowlist.PackReadAllowList(noRoleAddr), + SuppliedGas: allowlist.ReadAllowListGasCost, + ReadOnly: true, + ExpectedRes: common.Hash(allowlist.NoRole).Bytes(), + AfterHook: nil, }, "read allow list out of gas": { - caller: adminAddr, - input: func() []byte { - return allowlist.PackReadAllowList(noRoleAddr) - }, - suppliedGas: allowlist.ReadAllowListGasCost - 1, - readOnly: true, - expectedErr: vmerrs.ErrOutOfGas.Error(), + Caller: adminAddr, + Input: allowlist.PackReadAllowList(noRoleAddr), + SuppliedGas: allowlist.ReadAllowListGasCost - 1, + ReadOnly: true, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, } { t.Run(name, func(t *testing.T) { @@ -196,18 +172,18 @@ func TestTxAllowListRun(t *testing.T) { blockContext := contract.NewMockBlockContext(common.Big0, 0) accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) - ret, remainingGas, err := TxAllowListPrecompile.Run(accesibleState, test.caller, ContractAddress, test.input(), test.suppliedGas, test.readOnly) - if len(test.expectedErr) != 0 { - require.ErrorContains(t, err, test.expectedErr) + ret, remainingGas, err := TxAllowListPrecompile.Run(accesibleState, test.Caller, ContractAddress, test.Input, test.SuppliedGas, test.ReadOnly) + if len(test.ExpectedErr) != 0 { + require.ErrorContains(t, err, test.ExpectedErr) } else { require.NoError(t, err) } require.Equal(t, uint64(0), remainingGas) - require.Equal(t, test.expectedRes, ret) + require.Equal(t, test.ExpectedRes, ret) - if test.assertState != nil { - test.assertState(t, state) + if test.AfterHook != nil { + test.AfterHook(t, state) } }) } From 77751e477bd5d4e9543159faac2ce0baaf10fb1f Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Thu, 16 Feb 2023 06:50:09 -0800 Subject: [PATCH 09/23] minor nits --- precompile/allowlist/allowlist.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/precompile/allowlist/allowlist.go b/precompile/allowlist/allowlist.go index 357855b419..7822825d76 100644 --- a/precompile/allowlist/allowlist.go +++ b/precompile/allowlist/allowlist.go @@ -17,7 +17,7 @@ import ( // in the storage trie. Each account may have one of the following roles: // 1. NoRole - this is equivalent to common.Hash{} and deletes the key from the DB when set // 2. EnabledRole - allowed to call the precompile -// 3. Admin - allowed to modify both the allowlist and call the precompile +// 3. Admin - allowed to both modify the allowlist and call the precompile const ( SetAdminFuncKey = "setAdmin" @@ -27,14 +27,14 @@ const ( ModifyAllowListGasCost = contract.WriteGasCostPerSlot ReadAllowListGasCost = contract.ReadGasCostPerSlot -) -const allowListInputLen = common.HashLength + allowListInputLen = common.HashLength +) var ( NoRole = Role(common.BigToHash(common.Big0)) // NoRole - this is equivalent to common.Hash{} and deletes the key from the DB when set EnabledRole = Role(common.BigToHash(common.Big1)) // EnabledRole - allowed to call the precompile - AdminRole = Role(common.BigToHash(common.Big2)) // Admin - allowed to modify both the allowlist and call the precompile + AdminRole = Role(common.BigToHash(common.Big2)) // Admin - allowed to both modify the allowlist and call the precompile // AllowList function signatures setAdminSignature = contract.CalculateFunctionSelector("setAdmin(address)") From ce23fca18418ca90604544c63c81024ccbc57b59 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 16 Feb 2023 20:26:16 +0300 Subject: [PATCH 10/23] fix precompile generator --- abis/IAllowList.abi | 1 + abis/IRewardManager.abi | 1 + accounts/abi/bind/bind.go | 57 ++-- .../abi/bind/precompile_config_template.go | 168 ---------- .../{ => precompilebind}/precompile_bind.go | 42 ++- .../precompile_bind_test.go | 5 +- .../precompile_config_template.go | 136 ++++++++ .../precompile_contract_template.go | 91 +++--- .../precompile_module_template.go | 97 ++++++ accounts/abi/bind/template.go | 44 +-- cmd/precompilegen/main.go | 13 +- precompile/reward_managerx/config.go | 101 ++++++ precompile/reward_managerx/contract.abi | 1 + precompile/reward_managerx/contract.go | 305 ++++++++++++++++++ precompile/reward_managerx/module.go | 88 +++++ 15 files changed, 864 insertions(+), 286 deletions(-) create mode 100644 abis/IAllowList.abi create mode 100644 abis/IRewardManager.abi delete mode 100644 accounts/abi/bind/precompile_config_template.go rename accounts/abi/bind/{ => precompilebind}/precompile_bind.go (69%) rename accounts/abi/bind/{ => precompilebind}/precompile_bind_test.go (93%) create mode 100644 accounts/abi/bind/precompilebind/precompile_config_template.go rename accounts/abi/bind/{ => precompilebind}/precompile_contract_template.go (81%) create mode 100644 accounts/abi/bind/precompilebind/precompile_module_template.go create mode 100644 precompile/reward_managerx/config.go create mode 100644 precompile/reward_managerx/contract.abi create mode 100644 precompile/reward_managerx/contract.go create mode 100644 precompile/reward_managerx/module.go diff --git a/abis/IAllowList.abi b/abis/IAllowList.abi new file mode 100644 index 0000000000..ea89cd6408 --- /dev/null +++ b/abis/IAllowList.abi @@ -0,0 +1 @@ +[{"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"}] \ No newline at end of file diff --git a/abis/IRewardManager.abi b/abis/IRewardManager.abi new file mode 100644 index 0000000000..d21d5bdc6b --- /dev/null +++ b/abis/IRewardManager.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/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 6be7f0314a..37fd93a690 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -44,15 +44,8 @@ import ( "github.com/ethereum/go-ethereum/log" ) -const ( - setAdminFuncKey = "setAdmin" - setEnabledFuncKey = "setEnabled" - setNoneFuncKey = "setNone" - readAllowListFuncKey = "readAllowList" -) - // BindHook is a callback function that can be used to customize the binding. -type BindHook func(lang Lang, pkg string, types []string, contracts map[string]*tmplContract, structs map[string]*tmplStruct) (data interface{}, templateSource string, err error) +type BindHook func(lang Lang, pkg string, 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 @@ -105,16 +98,16 @@ func isKeyWord(arg string) bool { // 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) (string, error) { - return bindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, nil) + 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) { +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) + contracts = make(map[string]*TmplContract) // structs is the map of all redeclared structs shared by passed contracts. - structs = make(map[string]*tmplStruct) + structs = make(map[string]*TmplStruct) // isLib is the map used to flag each encountered library as such isLib = make(map[string]struct{}) @@ -135,11 +128,11 @@ func bindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s // Extract the call and transact methods; events, struct definitions; and sort them alphabetically var ( - calls = make(map[string]*tmplMethod) - transacts = make(map[string]*tmplMethod) + calls = make(map[string]*TmplMethod) + transacts = make(map[string]*TmplMethod) events = make(map[string]*tmplEvent) - fallback *tmplMethod - receive *tmplMethod + fallback *TmplMethod + receive *TmplMethod // identifiers are used to detect duplicated identifiers of functions // and events. For all calls, transacts and events, abigen will generate @@ -194,9 +187,9 @@ func bindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s } // Append the methods to the call or transact lists if original.IsConstant() { - calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} + calls[original.Name] = &TmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} } else { - transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} + transacts[original.Name] = &TmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} } } for _, original := range evmABI.Events { @@ -240,17 +233,17 @@ func bindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s } // Add two special fallback functions if they exist if evmABI.HasFallback() { - fallback = &tmplMethod{Original: evmABI.Fallback} + fallback = &TmplMethod{Original: evmABI.Fallback} } if evmABI.HasReceive() { - receive = &tmplMethod{Original: evmABI.Receive} + receive = &TmplMethod{Original: evmABI.Receive} } // There is no easy way to pass arbitrary java objects to the Go side. if len(structs) > 0 && lang == LangJava { return "", errors.New("java binding for tuple arguments is not supported yet") } - contracts[types[i]] = &tmplContract{ + contracts[types[i]] = &TmplContract{ Type: capitalise(types[i]), InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""), InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"), @@ -339,7 +332,7 @@ func bindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s // bindType is a set of type binders that convert Solidity types to some supported // programming language types. -var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ +var bindType = map[Lang]func(kind abi.Type, structs map[string]*TmplStruct) string{ LangGo: bindTypeGo, LangJava: bindTypeJava, } @@ -371,7 +364,7 @@ func bindBasicTypeGo(kind abi.Type) string { // bindTypeGo converts solidity types to Go ones. Since there is no clear mapping // from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly // mapped will use an upscaled type (e.g. BigDecimal). -func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { +func bindTypeGo(kind abi.Type, structs map[string]*TmplStruct) string { switch kind.T { case abi.TupleTy: return structs[kind.TupleRawName+kind.String()].Name @@ -448,7 +441,7 @@ func pluralizeJavaType(typ string) string { // bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping // from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly // mapped will use an upscaled type (e.g. BigDecimal). -func bindTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { +func bindTypeJava(kind abi.Type, structs map[string]*TmplStruct) string { switch kind.T { case abi.TupleTy: return structs[kind.TupleRawName+kind.String()].Name @@ -461,14 +454,14 @@ func bindTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { // bindTopicType is a set of type binders that convert Solidity types to some // supported programming language topic types. -var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ +var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*TmplStruct) string{ LangGo: bindTopicTypeGo, LangJava: bindTopicTypeJava, } // bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same // functionality as for simple types, but dynamic types get converted to hashes. -func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { +func bindTopicTypeGo(kind abi.Type, structs map[string]*TmplStruct) string { bound := bindTypeGo(kind, structs) // todo(rjl493456442) according solidity documentation, indexed event @@ -485,7 +478,7 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { // bindTopicTypeJava converts a Solidity topic type to a Java one. It is almost the same // functionality as for simple types, but dynamic types get converted to hashes. -func bindTopicTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { +func bindTopicTypeJava(kind abi.Type, structs map[string]*TmplStruct) string { bound := bindTypeJava(kind, structs) // todo(rjl493456442) according solidity documentation, indexed event @@ -502,7 +495,7 @@ func bindTopicTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { // bindStructType is a set of type binders that convert Solidity tuple types to some supported // programming language struct definition. -var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ +var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*TmplStruct) string{ LangGo: bindStructTypeGo, LangJava: bindStructTypeJava, } @@ -510,7 +503,7 @@ var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct // bindStructTypeGo converts a Solidity tuple type to a Go one and records the mapping // in the given map. // Notably, this function will resolve and record nested struct recursively. -func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { +func bindStructTypeGo(kind abi.Type, structs map[string]*TmplStruct) string { switch kind.T { case abi.TupleTy: // We compose a raw struct name and a canonical parameter expression @@ -539,7 +532,7 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { } name = capitalise(name) - structs[id] = &tmplStruct{ + structs[id] = &TmplStruct{ Name: name, Fields: fields, } @@ -556,7 +549,7 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { // bindStructTypeJava converts a Solidity tuple type to a Java one and records the mapping // in the given map. // Notably, this function will resolve and record nested struct recursively. -func bindStructTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { +func bindStructTypeJava(kind abi.Type, structs map[string]*TmplStruct) string { switch kind.T { case abi.TupleTy: // We compose a raw struct name and a canonical parameter expression @@ -578,7 +571,7 @@ func bindStructTypeJava(kind abi.Type, structs map[string]*tmplStruct) string { if name == "" { name = fmt.Sprintf("Class%d", len(structs)) } - structs[id] = &tmplStruct{ + structs[id] = &TmplStruct{ Name: name, Fields: fields, } diff --git a/accounts/abi/bind/precompile_config_template.go b/accounts/abi/bind/precompile_config_template.go deleted file mode 100644 index dabd5fed86..0000000000 --- a/accounts/abi/bind/precompile_config_template.go +++ /dev/null @@ -1,168 +0,0 @@ -// (c) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. -package bind - -// tmplSourcePrecompileConfigGo is the Go precompiled config source template. -const tmplSourcePrecompileConfigGo = ` -// Code generated -// This file is a generated precompile contract config with stubbed abstract functions. -// The file is generated by a template. Please inspect every code and comment in this file before use. - -// There are some must-be-done changes waiting in the file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. -// Additionally there are other files you need to edit to activate your precompile. -// These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". -// For testing take a look at other precompile tests in core/stateful_precompile_test.go and config_test.go in other precompile folders. - -/* General guidelines for precompile development: -1- Read the comment and set a suitable contract address in generated contract.go. E.g: - ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") -2- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. -Typically, custom codes are required in only those areas. -3- Set gas costs in generated contract.go -4- Register your precompile module in params/precompile_modules.go -5- Add your config unit tests under generated package config_test.go -6- Add your contract unit tests under tests/statefulprecompiles/{precompilename}_test.go -7- Add your solidity interface and test contract to contract-examples/contracts -8- Write solidity tests for your precompile in contract-examples/test -9- Create your genesis with your precompile enabled in tests/e2e/genesis/ -10- Create e2e test for your solidity test in tests/e2e/solidity/suites.go -11- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' - -*/ - -package {{.Package}} - -import ( - "math/big" - - "github.com/ava-labs/subnet-evm/precompile" - {{- if .Contract.AllowList}} - "github.com/ava-labs/subnet-evm/precompile/allowlist" - {{- end}} - - "github.com/ethereum/go-ethereum/common" -) - -var _ precompile.StatefulPrecompileConfig = &{{.Contract.Type}}Config{} - -// ConfigKey is the key used in json config files to specify this precompile config. -// Must be unique across all precompiles. -const ConfigKey = "{{decapitalise .Contract.Type}}Config" - -// {{.Contract.Type}}Config implements the StatefulPrecompileConfig -// interface while adding in the {{.Contract.Type}} specific precompile address. -type {{.Contract.Type}}Config struct { - {{- if .Contract.AllowList}} - allowlist.AllowListConfig - {{- end}} - precompile.UpgradeableConfig -} - -{{$structs := .Structs}} -{{range $structs}} - // {{.Name}} is an auto generated low-level Go binding around an user-defined struct. - type {{.Name}} struct { - {{range $field := .Fields}} - {{$field.Name}} {{$field.Type}}{{end}} - } -{{- end}} - -{{- range .Contract.Funcs}} -{{ if len .Normalized.Inputs | lt 1}} -type {{capitalise .Normalized.Name}}Input struct{ -{{range .Normalized.Inputs}} {{capitalise .Name}} {{bindtype .Type $structs}}; {{end}} -} -{{- end}} -{{ if len .Normalized.Outputs | lt 1}} -type {{capitalise .Normalized.Name}}Output struct{ -{{range .Normalized.Outputs}} {{capitalise .Name}} {{bindtype .Type $structs}}; {{end}} -} -{{- end}} -{{- end}} - -// New{{.Contract.Type}}Config returns a config for a network upgrade at [blockTimestamp] that enables -// {{.Contract.Type}} {{if .Contract.AllowList}} with the given [admins] as members of the allowlist {{end}}. -func New{{.Contract.Type}}Config(blockTimestamp *big.Int{{if .Contract.AllowList}}, admins []common.Address{{end}}) *{{.Contract.Type}}Config { - return &{{.Contract.Type}}Config{ - {{if .Contract.AllowList}}AllowListConfig: allowlist.AllowListConfig{AdminAddresses: admins},{{end}} - UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: blockTimestamp}, - } -} - -// NewDisable{{.Contract.Type}}Config returns config for a network upgrade at [blockTimestamp] -// that disables {{.Contract.Type}}. -func NewDisable{{.Contract.Type}}Config(blockTimestamp *big.Int) *{{.Contract.Type}}Config { - return &{{.Contract.Type}}Config{ - UpgradeableConfig: precompile.UpgradeableConfig{ - BlockTimestamp: blockTimestamp, - Disable: true, - }, - } -} - -// Verify tries to verify {{.Contract.Type}}Config and returns an error accordingly. -func (c *{{.Contract.Type}}Config) Verify() error { - {{if .Contract.AllowList}} - // Verify AllowList first - if err := c.AllowListConfig.Verify(); err != nil { - return err - } - {{end}} - // CUSTOM CODE STARTS HERE - // Add your own custom verify code for {{.Contract.Type}}Config here - // and return an error accordingly - return nil -} - -// Equal returns true if [s] is a [*{{.Contract.Type}}Config] and it has been configured identical to [c]. -func (c *{{.Contract.Type}}Config) Equal(s precompile.StatefulPrecompileConfig) bool { - // typecast before comparison - other, ok := (s).(*{{.Contract.Type}}Config) - if !ok { - return false - } - // CUSTOM CODE STARTS HERE - // modify this boolean accordingly with your custom {{.Contract.Type}}Config, to check if [other] and the current [c] are equal - // if {{.Contract.Type}}Config contains only UpgradeableConfig {{if .Contract.AllowList}} and AllowListConfig {{end}} you can skip modifying it. - equals := c.UpgradeableConfig.Equal(&other.UpgradeableConfig) {{if .Contract.AllowList}} && c.AllowListConfig.Equal(&other.AllowListConfig) {{end}} - return equals -} - -// Configure configures [state] with the initial configuration. -func (c *{{.Contract.Type}}Config) Configure(_ precompile.ChainConfig, state precompile.StateDB, _ precompile.BlockContext) error { - {{if .Contract.AllowList}}c.AllowListConfig.Configure(state, ContractAddress){{end}} - // CUSTOM CODE STARTS HERE - return nil -} - - -// Required module functions for {{.Contract.Type}}Config -// These functions mostly do not require any custom code. - -// NewModule returns a new module for {{.Contract.Type}}. -func NewModule() precompile.StatefulPrecompileModule { - return &{{.Contract.Type}}Config{} -} - -// Address returns the address of the {{.Contract.Type}}. -// Select a non-conflicting address and set it in generated contract.go -func ({{.Contract.Type}}Config) Address() common.Address { - return ContractAddress -} - -// Contract returns the singleton stateful precompiled contract to be used for {{.Contract.Type}}. -func ({{.Contract.Type}}Config) Contract() precompile.StatefulPrecompiledContract { - return {{.Contract.Type}}Precompile -} - -// Key returns the key used in json config files to specify this precompile config. -func ({{.Contract.Type}}Config) Key() string { - return ConfigKey -} - -// New returns a new {{.Contract.Type}}Config. -// This is used by the json parser to create a new instance of the {{.Contract.Type}}Config. -func ({{.Contract.Type}}Config) NewConfig() precompile.StatefulPrecompileConfig { - return new({{.Contract.Type}}Config) -} -` diff --git a/accounts/abi/bind/precompile_bind.go b/accounts/abi/bind/precompilebind/precompile_bind.go similarity index 69% rename from accounts/abi/bind/precompile_bind.go rename to accounts/abi/bind/precompilebind/precompile_bind.go index f85fc8b8a6..ae4b40b604 100644 --- a/accounts/abi/bind/precompile_bind.go +++ b/accounts/abi/bind/precompilebind/precompile_bind.go @@ -28,42 +28,56 @@ // // 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 +package precompilebind import ( "errors" "fmt" + + "github.com/ava-labs/subnet-evm/accounts/abi/bind" +) + +const ( + setAdminFuncKey = "setAdmin" + setEnabledFuncKey = "setEnabled" + setNoneFuncKey = "setNone" + readAllowListFuncKey = "readAllowList" ) // PrecompileBind generates a Go binding for a precompiled contract. It returns config binding and contract binding. -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, string, error) { +func PrecompileBind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang bind.Lang, libs map[string]string, aliases map[string]string, abifilename string) (string, string, string, error) { // create hooks configHook := createPrecompileHook(abifilename, tmplSourcePrecompileConfigGo) contractHook := createPrecompileHook(abifilename, tmplSourcePrecompileContractGo) + moduleHook := createPrecompileHook(abifilename, tmplSourcePrecompileModuleGo) - configBind, err := bindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, configHook) + configBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, configHook) + if err != nil { + return "", "", "", fmt.Errorf("failed to generate config binding: %w", err) + } + contractBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, contractHook) if err != nil { - return "", "", fmt.Errorf("failed to generate config binding: %w", err) + return "", "", "", fmt.Errorf("failed to generate contract binding: %w", err) } - contractBind, err := bindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, contractHook) + moduleBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, moduleHook) if err != nil { - return "", "", fmt.Errorf("failed to generate contract binding: %w", err) + return "", "", "", fmt.Errorf("failed to generate module binding: %w", err) } - return configBind, contractBind, nil + return configBind, contractBind, moduleBind, nil } // createPrecompileHook creates a bind hook for precompiled contracts. -func createPrecompileHook(abifilename string, template string) BindHook { - return func(lang Lang, pkg string, types []string, contracts map[string]*tmplContract, structs map[string]*tmplStruct) (interface{}, string, error) { +func createPrecompileHook(abifilename string, template string) bind.BindHook { + return func(lang bind.Lang, pkg string, types []string, contracts map[string]*bind.TmplContract, structs map[string]*bind.TmplStruct) (interface{}, string, error) { // verify first - if lang != LangGo { + if lang != bind.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) + funcs := make(map[string]*bind.TmplMethod) contract := contracts[types[0]] @@ -92,7 +106,7 @@ func createPrecompileHook(abifilename string, template string) BindHook { } precompileContract := &tmplPrecompileContract{ - tmplContract: contract, + TmplContract: contract, AllowList: isAllowList, Funcs: funcs, ABIFilename: abifilename, @@ -107,7 +121,7 @@ func createPrecompileHook(abifilename string, template string) BindHook { } } -func allowListEnabled(funcs map[string]*tmplMethod) bool { +func allowListEnabled(funcs map[string]*bind.TmplMethod) bool { keys := []string{readAllowListFuncKey, setAdminFuncKey, setEnabledFuncKey, setNoneFuncKey} for _, key := range keys { if _, ok := funcs[key]; !ok { @@ -117,7 +131,7 @@ func allowListEnabled(funcs map[string]*tmplMethod) bool { return true } -func checkOutputName(method tmplMethod) error { +func checkOutputName(method bind.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) diff --git a/accounts/abi/bind/precompile_bind_test.go b/accounts/abi/bind/precompilebind/precompile_bind_test.go similarity index 93% rename from accounts/abi/bind/precompile_bind_test.go rename to accounts/abi/bind/precompilebind/precompile_bind_test.go index 84e15b242a..f37d54e1ba 100644 --- a/accounts/abi/bind/precompile_bind_test.go +++ b/accounts/abi/bind/precompilebind/precompile_bind_test.go @@ -24,11 +24,12 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package bind +package precompilebind import ( "testing" + "github.com/ava-labs/subnet-evm/accounts/abi/bind" "github.com/stretchr/testify/require" ) @@ -98,7 +99,7 @@ func golangBindingsFailure(t *testing.T) { for i, tt := range bindFailedTests { t.Run(tt.name, func(t *testing.T) { // Generate the binding - _, _, err := PrecompileBind([]string{tt.name}, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases, "") + _, _, _, err := PrecompileBind([]string{tt.name}, tt.abi, tt.bytecode, tt.fsigs, "bindtest", bind.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/precompilebind/precompile_config_template.go b/accounts/abi/bind/precompilebind/precompile_config_template.go new file mode 100644 index 0000000000..f23f59d0b4 --- /dev/null +++ b/accounts/abi/bind/precompilebind/precompile_config_template.go @@ -0,0 +1,136 @@ +// (c) 2019-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package precompilebind + +// tmplSourcePrecompileConfigGo is the Go precompiled config source template. +const tmplSourcePrecompileConfigGo = ` +// Code generated +// This file is a generated precompile contract config with stubbed abstract functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +// There are some must-be-done changes waiting in the file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. +// Additionally there are other files you need to edit to activate your precompile. +// These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". +// For testing take a look at other precompile tests in core/stateful_precompile_test.go and config_test.go in other precompile folders. + +/* General guidelines for precompile development: +1- Read the comment and set a suitable contract address in generated contract.go. E.g: + ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") +2- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. +Typically, custom codes are required in only those areas. +3- Set gas costs in generated contract.go +4- Register your precompile module in params/precompile_modules.go +5- Add your config unit tests under generated package config_test.go +6- Add your contract unit tests under tests/statefulprecompiles/{precompilename}_test.go +7- Add your solidity interface and test contract to contract-examples/contracts +8- Write solidity tests for your precompile in contract-examples/test +9- Create your genesis with your precompile enabled in tests/e2e/genesis/ +10- Create e2e test for your solidity test in tests/e2e/solidity/suites.go +11- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' + +*/ + +package {{.Package}} + +import ( + "math/big" + + "github.com/ava-labs/subnet-evm/precompile/config" + {{- if .Contract.AllowList}} + "github.com/ava-labs/subnet-evm/precompile/allowlist" + {{- end}} + + "github.com/ethereum/go-ethereum/common" +) + +var _ config.Config = &Config{} + +// Config implements the StatefulPrecompileConfig +// interface while adding in the {{.Contract.Type}} specific precompile address. +type Config struct { + {{- if .Contract.AllowList}} + allowlist.Config + {{- end}} + config.Upgrade +} + +{{$structs := .Structs}} +{{range $structs}} + // {{.Name}} is an auto generated low-level Go binding around an user-defined struct. + type {{.Name}} struct { + {{range $field := .Fields}} + {{$field.Name}} {{$field.Type}}{{end}} + } +{{- end}} + +{{- range .Contract.Funcs}} +{{ if len .Normalized.Inputs | lt 1}} +type {{capitalise .Normalized.Name}}Input struct{ +{{range .Normalized.Inputs}} {{capitalise .Name}} {{bindtype .Type $structs}}; {{end}} +} +{{- end}} +{{ if len .Normalized.Outputs | lt 1}} +type {{capitalise .Normalized.Name}}Output struct{ +{{range .Normalized.Outputs}} {{capitalise .Name}} {{bindtype .Type $structs}}; {{end}} +} +{{- end}} +{{- end}} + +// NewConfig returns a config for a network upgrade at [blockTimestamp] that enables +// {{.Contract.Type}} with the given [admins] and [enableds] as members of the allowlist. +// {{.Contract.Type}} {{if .Contract.AllowList}} with the given [admins] as members of the allowlist {{end}}. +func NewConfig(blockTimestamp *big.Int{{if .Contract.AllowList}}, admins []common.Address, enableds []common.Address,{{end}}) *Config { + return &Config{ + {{- if .Contract.AllowList}} + Config: allowlist.Config{ + AdminAddresses: admins, + EnabledAddresses: enableds, + }, + {{- end}} + Upgrade: config.Upgrade{BlockTimestamp: blockTimestamp}, + } +} + +// NewDisableConfig returns config for a network upgrade at [blockTimestamp] +// that disables {{.Contract.Type}}. +func NewDisableConfig(blockTimestamp *big.Int) *Config { + return &Config{ + Upgrade: config.Upgrade{ + BlockTimestamp: blockTimestamp, + Disable: true, + }, + } +} + +// Key returns the key for the {{.Contract.Type}} config. +// This should be the same key as used in the precompile module. +func (*Config) Key() string { return ConfigKey } + +// Verify tries to verify Config and returns an error accordingly. +func (c *Config) Verify() error { + {{if .Contract.AllowList}} + // Verify AllowList first + if err := c.Config.Verify(); err != nil { + return err + } + {{end}} + // CUSTOM CODE STARTS HERE + // Add your own custom verify code for Config here + // and return an error accordingly + return nil +} + +// Equal returns true if [s] is a [*Config] and it has been configured identical to [c]. +func (c *Config) Equal(s config.Config) bool { + // typecast before comparison + other, ok := (s).(*Config) + if !ok { + return false + } + // CUSTOM CODE STARTS HERE + // modify this boolean accordingly with your custom Config, to check if [other] and the current [c] are equal + // if Config contains only Upgrade {{if .Contract.AllowList}} and AllowListConfig {{end}} you can skip modifying it. + equals := c.Upgrade.Equal(&other.Upgrade) {{if .Contract.AllowList}} && c.Config.Equal(&other.Config) {{end}} + return equals +} +` diff --git a/accounts/abi/bind/precompile_contract_template.go b/accounts/abi/bind/precompilebind/precompile_contract_template.go similarity index 81% rename from accounts/abi/bind/precompile_contract_template.go rename to accounts/abi/bind/precompilebind/precompile_contract_template.go index 47c3a04188..d9808d15e2 100644 --- a/accounts/abi/bind/precompile_contract_template.go +++ b/accounts/abi/bind/precompilebind/precompile_contract_template.go @@ -1,20 +1,22 @@ // (c) 2019-2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package bind +package precompilebind + +import "github.com/ava-labs/subnet-evm/accounts/abi/bind" // tmplPrecompileData is the data structure required to fill the binding template. type tmplPrecompileData struct { Package string - Contract *tmplPrecompileContract // The contract to generate into this file - Structs map[string]*tmplStruct // Contract struct type definitions + Contract *tmplPrecompileContract // The contract to generate into this file + Structs map[string]*bind.TmplStruct // Contract struct type definitions } // 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 - ABIFilename string // Path to the ABI file + *bind.TmplContract + AllowList bool // Indicator whether the contract uses AllowList precompile + Funcs map[string]*bind.TmplMethod // Contract functions that include both Calls + Transacts in tmplContract + ABIFilename string // Path to the ABI file } // tmplSourcePrecompileContractGo is the Go precompiled contract source template. @@ -48,26 +50,33 @@ Typically, custom codes are required in only those areas. package {{.Package}} import ( - "math/big" "errors" "fmt" - "strings" + "math/big" "github.com/ava-labs/subnet-evm/accounts/abi" - "github.com/ava-labs/subnet-evm/precompile" {{- if .Contract.AllowList}} "github.com/ava-labs/subnet-evm/precompile/allowlist" {{- end}} + "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/vmerrs" _ "embed" "github.com/ethereum/go-ethereum/common" ) - +{{$contract := .Contract}} const ( + // Gas costs for each function. These are set to 0 by default. + // You should set a gas cost for each function in your contract. + // Generally, you should not set gas costs very low as this may cause your network to be vulnerable to DoS attacks. + // There are some predefined gas costs in contract/utils.go that you can use. + {{- if .Contract.AllowList}} + // This contract also uses AllowList precompile. + // You should also increase gas costs of functions that read from AllowList storage. + {{- end}}} {{- range .Contract.Funcs}} - {{.Normalized.Name}}GasCost uint64 = 0 // SET A GAS COST HERE + {{.Normalized.Name}}GasCost uint64 = 0 {{if not .Original.IsConstant | and $contract.AllowList}} + allowlist.ReadAllowListGasCost {{end}} // SET A GAS COST HERE {{- end}} {{- if .Contract.Fallback}} {{.Contract.Type}}FallbackGasCost uint64 = 0 // SET A GAS COST LESS THAN 2300 HERE @@ -81,14 +90,8 @@ var ( _ = big.NewInt ) -{{$contract := .Contract}} // Singleton StatefulPrecompiledContract and signatures. var ( - // ContractAddress is the defined address of the precompile contract. - // This should be unique across all precompile contracts. - // See params/precompile_modules.go for registered precompile contracts and more information. - ContractAddress = common.HexToAddress("{ASUITABLEHEXADDRESS}") // SET A SUITABLE HEX ADDRESS HERE - {{- range .Contract.Funcs}} {{- if not .Original.IsConstant | and $contract.AllowList}} @@ -108,9 +111,10 @@ var ( //go:embed {{.Contract.ABIFilename}} {{.Contract.Type}}RawABI string {{- end}} - {{.Contract.Type}}ABI abi.ABI // will be initialized by init function - {{.Contract.Type}}Precompile precompile.StatefulPrecompiledContract // will be initialized by init function + {{.Contract.Type}}ABI = contract.ParseABI({{.Contract.Type}}RawABI) + + {{.Contract.Type}}Precompile = create{{.Contract.Type}}Precompile() ) {{$structs := .Structs}} @@ -135,22 +139,9 @@ type {{capitalise .Normalized.Name}}Output struct{ {{- end}} {{- end}} -func init() { - parsed, err := abi.JSON(strings.NewReader({{.Contract.Type}}RawABI)) - if err != nil { - panic(err) - } - {{.Contract.Type}}ABI = parsed - - {{.Contract.Type}}Precompile, err = create{{.Contract.Type}}Precompile() - if err != nil { - panic(err) - } -} - {{if .Contract.AllowList}} // Get{{.Contract.Type}}AllowListStatus returns the role of [address] for the {{.Contract.Type}} list. -func Get{{.Contract.Type}}AllowListStatus(stateDB precompile.StateDB, address common.Address) allowlist.AllowListRole { +func Get{{.Contract.Type}}AllowListStatus(stateDB contract.StateDB, address common.Address) allowlist.Role { return allowlist.GetAllowListStatus(stateDB, ContractAddress, address) } @@ -160,7 +151,7 @@ func Get{{.Contract.Type}}AllowListStatus(stateDB precompile.StateDB, address co // and [address] hash. It means that any reusage of the [address] key for different value // conflicts with the same slot [role] is stored. // Precompile implementations must use a different key than [address] for their storage. -func Set{{.Contract.Type}}AllowListStatus(stateDB precompile.StateDB, address common.Address, role allowlist.AllowListRole) { +func Set{{.Contract.Type}}AllowListStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) { allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) } {{end}} @@ -229,8 +220,8 @@ func Pack{{$method.Normalized.Name}}Output ({{decapitalise $output.Name}} {{bind } {{end}} -func {{decapitalise .Normalized.Name}}(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = precompile.DeductGas(suppliedGas, {{.Normalized.Name}}GasCost); err != nil { +func {{decapitalise .Normalized.Name}}(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, {{.Normalized.Name}}GasCost); err != nil { return nil, 0, err } @@ -294,8 +285,8 @@ func {{decapitalise .Normalized.Name}}(accessibleState precompile.PrecompileAcce {{- with .Contract.Fallback}} // {{decapitalise $contract.Type}}Fallback executed if a function identifier does not match any of the available functions in a smart contract. // This function cannot take an input or return an output. -func {{decapitalise $contract.Type}}Fallback (accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, _ []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = precompile.DeductGas(suppliedGas, {{$contract.Type}}FallbackGasCost); err != nil { +func {{decapitalise $contract.Type}}Fallback (accessibleState contract.AccessibleState, caller common.Address, addr common.Address, _ []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, {{$contract.Type}}FallbackGasCost); err != nil { return nil, 0, err } @@ -330,13 +321,13 @@ func {{decapitalise $contract.Type}}Fallback (accessibleState precompile.Precomp // create{{.Contract.Type}}Precompile returns a StatefulPrecompiledContract with getters and setters for the precompile. {{if .Contract.AllowList}} // Access to the getters/setters is controlled by an allow list for ContractAddress.{{end}} -func create{{.Contract.Type}}Precompile() (precompile.StatefulPrecompiledContract, error) { - var functions []*precompile.StatefulPrecompileFunction +func create{{.Contract.Type}}Precompile() contract.StatefulPrecompiledContract { + var functions []*contract.StatefulPrecompileFunction {{- if .Contract.AllowList}} functions = append(functions, allowlist.CreateAllowListFunctions(ContractAddress)...) {{- end}} - abiFunctionMap := map[string]precompile.RunStatefulPrecompileFunc{ + abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{ {{- range .Contract.Funcs}} "{{.Original.Name}}": {{decapitalise .Normalized.Name}}, {{- end}} @@ -345,17 +336,25 @@ func create{{.Contract.Type}}Precompile() (precompile.StatefulPrecompiledContrac for name, function := range abiFunctionMap { method, ok := {{$contract.Type}}ABI.Methods[name] if !ok { - return nil, fmt.Errorf("given method (%s) does not exist in the ABI", name) + panic(fmt.Errorf("given method (%s) does not exist in the ABI", name)) } - functions = append(functions, precompile.NewStatefulPrecompileFunction(method.ID, function)) + functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function)) } {{- if .Contract.Fallback}} // Construct the contract with the fallback function. - return precompile.NewStatefulPrecompileContract({{decapitalise $contract.Type}}Fallback, functions) + statefulContract, err := contract.NewStatefulPrecompileContract({{decapitalise $contract.Type}}Fallback, functions) + if err != nil { + panic(err) + } + return statefulContract {{- else}} // Construct the contract with no fallback function. - return precompile.NewStatefulPrecompileContract(nil, functions) + statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions) + if err != nil { + panic(err) + } + return statefulContract {{- end}} } ` diff --git a/accounts/abi/bind/precompilebind/precompile_module_template.go b/accounts/abi/bind/precompilebind/precompile_module_template.go new file mode 100644 index 0000000000..13fcfc0c3e --- /dev/null +++ b/accounts/abi/bind/precompilebind/precompile_module_template.go @@ -0,0 +1,97 @@ +// (c) 2019-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package precompilebind + +// tmplSourcePrecompileModuleGo is the Go precompiled module source template. +const tmplSourcePrecompileModuleGo = ` +// Code generated +// This file is a generated precompile contract config with stubbed abstract functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +// There are some must-be-done changes waiting in the file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. +// Additionally there are other files you need to edit to activate your precompile. +// These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". +// For testing take a look at other precompile tests in core/stateful_precompile_test.go and config_test.go in other precompile folders. + +/* General guidelines for precompile development: +1- Read the comment and set a suitable contract address in generated contract.go. E.g: + ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") +2- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. +Typically, custom codes are required in only those areas. +3- Set gas costs in generated contract.go +4- Register your precompile module in params/precompile_modules.go +5- Add your config unit tests under generated package config_test.go +6- Add your contract unit tests under tests/statefulprecompiles/{precompilename}_test.go +7- Add your solidity interface and test contract to contract-examples/contracts +8- Write solidity tests for your precompile in contract-examples/test +9- Create your genesis with your precompile enabled in tests/e2e/genesis/ +10- Create e2e test for your solidity test in tests/e2e/solidity/suites.go +11- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' + +*/ + +package {{.Package}} + +import ( + "fmt" + + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/modules" + + "github.com/ethereum/go-ethereum/common" +) + +var _ contract.Configurator = &configurator{} + +// ConfigKey is the key used in json config files to specify this precompile config. +// must be unique across all precompiles. +const ConfigKey = "{{decapitalise .Contract.Type}}Config" + +// ContractAddress is the defined address of the precompile contract. +// This should be unique across all precompile contracts. +// See params/precompile_modules.go for registered precompile contracts and more information. +var ContractAddress = common.HexToAddress("{ASUITABLEHEXADDRESS}") // SET A SUITABLE HEX ADDRESS HERE + +// Module is the precompile module. It is used to register the precompile contract. +var Module = modules.Module{ + ConfigKey: ConfigKey, + Address: ContractAddress, + Contract: {{.Contract.Type}}Precompile, + Configurator: &configurator{}, +} + +type configurator struct{} + +func init() { + // Register the precompile module. + // Each precompile contract registers itself through [RegisterModule] function. + if err := modules.RegisterModule(Module); err != nil { + panic(err) + } +} + +// NewConfig returns a new precompile config. +// This is required for Marshal/Unmarshal the precompile config. +func (*configurator) NewConfig() config.Config { + return &Config{} +} + +// Configure configures [state] with the given [cfg] config. +// This function is called by the EVM once per precompile contract activation. +// You can use this function to set up your precompile contract's initial state, +// by using the [cfg] config and [state] stateDB. +func (*configurator) Configure(chainConfig contract.ChainConfig, cfg config.Config, state contract.StateDB, _ contract.BlockContext) error { + config, ok := cfg.(*Config) + if !ok { + return fmt.Errorf("incorrect config %T: %v", config, config) + } + // CUSTOM CODE STARTS HERE + {{if .Contract.AllowList}} + // AllowList is activated for this precompile. Configuring allowlist addresses here. + return config.Configure(state, ContractAddress) + {{else}} + return nil + {{end}} +} +` diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index 7ea1635671..35e6e481f8 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -31,30 +31,30 @@ import "github.com/ava-labs/subnet-evm/accounts/abi" // tmplData is the data structure required to fill the binding template. type tmplData struct { Package string // Name of the package to place the generated file in - Contracts map[string]*tmplContract // List of contracts to generate into this file + Contracts map[string]*TmplContract // List of contracts to generate into this file Libraries map[string]string // Map the bytecode's link pattern to the library name - Structs map[string]*tmplStruct // Contract struct type definitions + Structs map[string]*TmplStruct // Contract struct type definitions } -// tmplContract contains the data needed to generate an individual contract binding. -type tmplContract struct { +// TmplContract contains the data needed to generate an individual contract binding. +type TmplContract struct { Type string // Type name of the main contract binding InputABI string // JSON ABI used as the input to generate the binding from InputBin string // Optional EVM bytecode used to generate deploy code from FuncSigs map[string]string // Optional map: string signature -> 4-byte signature Constructor abi.Method // Contract constructor for deploy parametrization - Calls map[string]*tmplMethod // Contract calls that only read state data - Transacts map[string]*tmplMethod // Contract calls that write state data - Fallback *tmplMethod // Additional special fallback function - Receive *tmplMethod // Additional special receive function + Calls map[string]*TmplMethod // Contract calls that only read state data + Transacts map[string]*TmplMethod // Contract calls that write state data + Fallback *TmplMethod // Additional special fallback function + Receive *TmplMethod // Additional special receive function Events map[string]*tmplEvent // Contract events accessors Libraries map[string]string // Same as tmplData, but filtered to only keep what the contract needs Library bool // Indicator whether the contract is a library } -// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed +// TmplMethod is a wrapper around an abi.Method that contains a few preprocessed // and cached data fields. -type tmplMethod struct { +type TmplMethod struct { Original abi.Method // Original method as parsed by the abi package Normalized abi.Method // Normalized version of the parsed method (capitalized names, non-anonymous args/returns) Structured bool // Whether the returns should be accumulated into a struct @@ -75,9 +75,9 @@ type tmplField struct { SolKind abi.Type // Raw abi type information } -// tmplStruct is a wrapper around an abi.tuple and contains an auto-generated +// TmplStruct is a wrapper around an abi.tuple and contains an auto-generated // struct name. -type tmplStruct struct { +type TmplStruct struct { Name string // Auto-generated struct name(before solidity v0.5.11) or raw name. Fields []*tmplField // Struct fields definition depends on the binding language. } @@ -335,7 +335,7 @@ var ( if err != nil { return *outstruct, err } - {{range $i, $t := .Normalized.Outputs}} + {{range $i, $t := .Normalized.Outputs}} outstruct.{{.Name}} = *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} return *outstruct, err @@ -345,7 +345,7 @@ var ( } {{range $i, $t := .Normalized.Outputs}} out{{$i}} := *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} - + return {{range $i, $t := .Normalized.Outputs}}out{{$i}}, {{end}} err {{end}} } @@ -388,7 +388,7 @@ var ( } {{end}} - {{if .Fallback}} + {{if .Fallback}} // Fallback is a paid mutator transaction binding the contract fallback function. // // Solidity: {{.Fallback.Original.String}} @@ -402,16 +402,16 @@ var ( func (_{{$contract.Type}} *{{$contract.Type}}Session) Fallback(calldata []byte) (*types.Transaction, error) { return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata) } - + // Fallback is a paid mutator transaction binding the contract fallback function. - // + // // Solidity: {{.Fallback.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata) } {{end}} - {{if .Receive}} + {{if .Receive}} // Receive is a paid mutator transaction binding the contract receive function. // // Solidity: {{.Receive.Original.String}} @@ -425,9 +425,9 @@ var ( func (_{{$contract.Type}} *{{$contract.Type}}Session) Receive() (*types.Transaction, error) { return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts) } - + // Receive is a paid mutator transaction binding the contract receive function. - // + // // Solidity: {{.Receive.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Receive() (*types.Transaction, error) { return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts) @@ -700,7 +700,7 @@ import java.util.*; // Fallback is a paid mutator transaction binding the contract fallback function. // // Solidity: {{.Fallback.Original.String}} - public Transaction Fallback(TransactOpts opts, byte[] calldata) throws Exception { + public Transaction Fallback(TransactOpts opts, byte[] calldata) throws Exception { return this.Contract.rawTransact(opts, calldata); } {{end}} @@ -709,7 +709,7 @@ import java.util.*; // Receive is a paid mutator transaction binding the contract receive function. // // Solidity: {{.Receive.Original.String}} - public Transaction Receive(TransactOpts opts) throws Exception { + public Transaction Receive(TransactOpts opts) throws Exception { return this.Contract.rawTransact(opts, null); } {{end}} diff --git a/cmd/precompilegen/main.go b/cmd/precompilegen/main.go index 7679d2c3e6..75606a9c87 100644 --- a/cmd/precompilegen/main.go +++ b/cmd/precompilegen/main.go @@ -34,6 +34,7 @@ import ( "strings" "github.com/ava-labs/subnet-evm/accounts/abi/bind" + "github.com/ava-labs/subnet-evm/accounts/abi/bind/precompilebind" "github.com/ava-labs/subnet-evm/internal/flags" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/log" @@ -145,7 +146,7 @@ func precompilegen(c *cli.Context) error { abipath = filepath.Join(outFlagStr, abifilename) } // Generate the contract precompile - configCode, contractCode, err := bind.PrecompileBind(types, abis, bins, sigs, pkg, lang, libs, aliases, abifilename) + configCode, contractCode, moduleCode, err := precompilebind.PrecompileBind(types, abis, bins, sigs, pkg, lang, libs, aliases, abifilename) if err != nil { utils.Fatalf("Failed to generate precompile: %v", err) } @@ -156,11 +157,13 @@ func precompilegen(c *cli.Context) error { fmt.Printf("%s\n", configCode) fmt.Print("-----Contract Code-----\n") fmt.Printf("%s\n", contractCode) + fmt.Print("-----Module Code-----\n") + fmt.Printf("%s\n", moduleCode) return nil } if _, err := os.Stat(outFlagStr); os.IsNotExist(err) { - os.MkdirAll(outFlagStr, 0700) // Create your file + os.MkdirAll(outFlagStr, 0o700) // Create your file } configCodeOut := filepath.Join(outFlagStr, "config.go") @@ -174,6 +177,12 @@ func precompilegen(c *cli.Context) error { utils.Fatalf("Failed to write generated contract code: %v", err) } + moduleCodeOut := filepath.Join(outFlagStr, "module.go") + + if err := os.WriteFile(moduleCodeOut, []byte(moduleCode), 0o600); err != nil { + utils.Fatalf("Failed to write generated module code: %v", err) + } + if err := os.WriteFile(abipath, []byte(abis[0]), 0o600); err != nil { utils.Fatalf("Failed to write ABI: %v", err) } diff --git a/precompile/reward_managerx/config.go b/precompile/reward_managerx/config.go new file mode 100644 index 0000000000..1b6643aa13 --- /dev/null +++ b/precompile/reward_managerx/config.go @@ -0,0 +1,101 @@ +// Code generated +// This file is a generated precompile contract config with stubbed abstract functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +// There are some must-be-done changes waiting in the file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. +// Additionally there are other files you need to edit to activate your precompile. +// These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". +// For testing take a look at other precompile tests in core/stateful_precompile_test.go and config_test.go in other precompile folders. + +/* General guidelines for precompile development: +1- Read the comment and set a suitable contract address in generated contract.go. E.g: + ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") +2- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. +Typically, custom codes are required in only those areas. +3- Set gas costs in generated contract.go +4- Register your precompile module in params/precompile_modules.go +5- Add your config unit tests under generated package config_test.go +6- Add your contract unit tests under tests/statefulprecompiles/{precompilename}_test.go +7- Add your solidity interface and test contract to contract-examples/contracts +8- Write solidity tests for your precompile in contract-examples/test +9- Create your genesis with your precompile enabled in tests/e2e/genesis/ +10- Create e2e test for your solidity test in tests/e2e/solidity/suites.go +11- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' + +*/ + +package rewardmanager + +import ( + "math/big" + + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/config" + + "github.com/ethereum/go-ethereum/common" +) + +var _ config.Config = &Config{} + +// Config implements the StatefulPrecompileConfig +// interface while adding in the RewardManager specific precompile address. +type Config struct { + allowlist.Config + config.Upgrade +} + +// NewConfig returns a config for a network upgrade at [blockTimestamp] that enables +// RewardManager with the given [admins] and [enableds] as members of the allowlist. +// RewardManager with the given [admins] as members of the allowlist . +func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address) *Config { + return &Config{ + Config: allowlist.Config{ + AdminAddresses: admins, + EnabledAddresses: enableds, + }, + Upgrade: config.Upgrade{BlockTimestamp: blockTimestamp}, + } +} + +// NewDisableConfig returns config for a network upgrade at [blockTimestamp] +// that disables RewardManager. +func NewDisableConfig(blockTimestamp *big.Int) *Config { + return &Config{ + Upgrade: config.Upgrade{ + BlockTimestamp: blockTimestamp, + Disable: true, + }, + } +} + +// Key returns the key for the RewardManager config. +// This should be the same key as used in the precompile module. +func (*Config) Key() string { return ConfigKey } + +// Verify tries to verify Config and returns an error accordingly. +func (c *Config) Verify() error { + + // Verify AllowList first + if err := c.Config.Verify(); err != nil { + return err + } + + // CUSTOM CODE STARTS HERE + // Add your own custom verify code for Config here + // and return an error accordingly + return nil +} + +// Equal returns true if [s] is a [*Config] and it has been configured identical to [c]. +func (c *Config) Equal(s config.Config) bool { + // typecast before comparison + other, ok := (s).(*Config) + if !ok { + return false + } + // CUSTOM CODE STARTS HERE + // modify this boolean accordingly with your custom Config, to check if [other] and the current [c] are equal + // if Config contains only Upgrade and AllowListConfig you can skip modifying it. + equals := c.Upgrade.Equal(&other.Upgrade) && c.Config.Equal(&other.Config) + return equals +} diff --git a/precompile/reward_managerx/contract.abi b/precompile/reward_managerx/contract.abi new file mode 100644 index 0000000000..d21d5bdc6b --- /dev/null +++ b/precompile/reward_managerx/contract.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_managerx/contract.go b/precompile/reward_managerx/contract.go new file mode 100644 index 0000000000..05b05271c0 --- /dev/null +++ b/precompile/reward_managerx/contract.go @@ -0,0 +1,305 @@ +// Code generated +// This file is a generated precompile contract config with stubbed abstract functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +// There are some must-be-done changes waiting in the file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. +// Additionally there are other files you need to edit to activate your precompile. +// These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". +// For testing take a look at other precompile tests in tests/statefulprecompiles/ and config_test.go in other precompile folders. + +/* General guidelines for precompile development: +1- Read the comment and set a suitable contract address in generated contract.go. E.g: + ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") +2- Set gas costs in generated contract.go +3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. +Typically, custom codes are required in only those areas. +4- Register your precompile module in params/precompile_modules.go +5- Add your config unit tests under generated package config_test.go +6- Add your contract unit tests under tests/statefulprecompiles/{precompilename}_test.go +7- Add your solidity interface and test contract to contract-examples/contracts +8- Write solidity tests for your precompile in contract-examples/test +9- Create your genesis with your precompile enabled in tests/e2e/genesis/ +10- Create e2e test for your solidity test in tests/e2e/solidity/suites.go +11- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' + +*/ + +package rewardmanager + +import ( + "errors" + "fmt" + "math/big" + + "github.com/ava-labs/subnet-evm/accounts/abi" + "github.com/ava-labs/subnet-evm/precompile/allowlist" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/vmerrs" + + _ "embed" + + "github.com/ethereum/go-ethereum/common" +) + +const ( + // Gas costs for each function. These are set to 0 by default. + // You should set a gas cost for each function in your contract. + // Generally, you should not set gas costs very low as this may cause your network to be vulnerable to DoS attacks. + // There are some predefined gas costs in contract/utils.go that you can use. + // This contract also uses AllowList precompile. + // You should also increase gas costs of functions that read from AllowList storage.} + AllowFeeRecipientsGasCost uint64 = 0 + allowlist.ReadAllowListGasCost // SET A GAS COST HERE + AreFeeRecipientsAllowedGasCost uint64 = 0 // SET A GAS COST HERE + CurrentRewardAddressGasCost uint64 = 0 // SET A GAS COST HERE + DisableRewardsGasCost uint64 = 0 + allowlist.ReadAllowListGasCost // SET A GAS COST HERE + SetRewardAddressGasCost uint64 = 0 + allowlist.ReadAllowListGasCost // SET A GAS COST HERE +) + +// CUSTOM CODE STARTS HERE +// Reference imports to suppress errors from unused imports. This code and any unnecessary imports can be removed. +var ( + _ = errors.New + _ = big.NewInt +) + +// Singleton StatefulPrecompiledContract and signatures. +var ( + ErrCannotAllowFeeRecipients = errors.New("non-enabled cannot call allowFeeRecipients") + + ErrCannotDisableRewards = errors.New("non-enabled cannot call disableRewards") + + ErrCannotSetRewardAddress = errors.New("non-enabled cannot call setRewardAddress") + + // RewardManagerRawABI contains the raw ABI of RewardManager contract. + //go:embed contract.abi + RewardManagerRawABI string + + RewardManagerABI = contract.ParseABI(RewardManagerRawABI) + + RewardManagerPrecompile = createRewardManagerPrecompile() +) + +// GetRewardManagerAllowListStatus returns the role of [address] for the RewardManager list. +func GetRewardManagerAllowListStatus(stateDB contract.StateDB, address common.Address) allowlist.Role { + return allowlist.GetAllowListStatus(stateDB, ContractAddress, address) +} + +// SetRewardManagerAllowListStatus sets the permissions of [address] to [role] for the +// RewardManager list. Assumes [role] has already been verified as valid. +// This stores the [role] in the contract storage with address [ContractAddress] +// and [address] hash. It means that any reusage of the [address] key for different value +// conflicts with the same slot [role] is stored. +// Precompile implementations must use a different key than [address] for their storage. +func SetRewardManagerAllowListStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) { + allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) +} + +// PackAllowFeeRecipients packs the include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackAllowFeeRecipients() ([]byte, error) { + return RewardManagerABI.Pack("allowFeeRecipients") +} + +func allowFeeRecipients(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, AllowFeeRecipientsGasCost); err != nil { + return nil, 0, err + } + if readOnly { + return nil, remainingGas, vmerrs.ErrWriteProtection + } + // no input provided for this function + + // Allow list is enabled and AllowFeeRecipients is a state-changer function. + // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. + // You can modify/delete this code if you don't want this function to be restricted by the allow list. + stateDB := accessibleState.GetStateDB() + // Verify that the caller is in the allow list and therefore has the right to call this function. + callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) + if !callerStatus.IsEnabled() { + return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotAllowFeeRecipients, caller) + } + // allow list code ends here. + + // CUSTOM CODE STARTS HERE + // this function does not return an output, leave this one as is + packedOutput := []byte{} + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// PackAreFeeRecipientsAllowed packs the include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackAreFeeRecipientsAllowed() ([]byte, error) { + return RewardManagerABI.Pack("areFeeRecipientsAllowed") +} + +// PackAreFeeRecipientsAllowedOutput attempts to pack given isAllowed of type bool +// to conform the ABI outputs. +func PackAreFeeRecipientsAllowedOutput(isAllowed bool) ([]byte, error) { + return RewardManagerABI.PackOutput("areFeeRecipientsAllowed", isAllowed) +} + +func areFeeRecipientsAllowed(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, AreFeeRecipientsAllowedGasCost); err != nil { + return nil, 0, err + } + // no input provided for this function + + // CUSTOM CODE STARTS HERE + + var output bool // CUSTOM CODE FOR AN OUTPUT + packedOutput, err := PackAreFeeRecipientsAllowedOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// PackCurrentRewardAddress packs the include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackCurrentRewardAddress() ([]byte, error) { + return RewardManagerABI.Pack("currentRewardAddress") +} + +// PackCurrentRewardAddressOutput attempts to pack given rewardAddress of type common.Address +// to conform the ABI outputs. +func PackCurrentRewardAddressOutput(rewardAddress common.Address) ([]byte, error) { + return RewardManagerABI.PackOutput("currentRewardAddress", rewardAddress) +} + +func currentRewardAddress(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, CurrentRewardAddressGasCost); err != nil { + return nil, 0, err + } + // no input provided for this function + + // CUSTOM CODE STARTS HERE + + var output common.Address // CUSTOM CODE FOR AN OUTPUT + packedOutput, err := PackCurrentRewardAddressOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// PackDisableRewards packs the include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackDisableRewards() ([]byte, error) { + return RewardManagerABI.Pack("disableRewards") +} + +func disableRewards(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, DisableRewardsGasCost); err != nil { + return nil, 0, err + } + if readOnly { + return nil, remainingGas, vmerrs.ErrWriteProtection + } + // no input provided for this function + + // Allow list is enabled and DisableRewards is a state-changer function. + // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. + // You can modify/delete this code if you don't want this function to be restricted by the allow list. + stateDB := accessibleState.GetStateDB() + // Verify that the caller is in the allow list and therefore has the right to call this function. + callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) + if !callerStatus.IsEnabled() { + return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotDisableRewards, caller) + } + // allow list code ends here. + + // CUSTOM CODE STARTS HERE + // this function does not return an output, leave this one as is + packedOutput := []byte{} + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// UnpackSetRewardAddressInput attempts to unpack [input] into the common.Address type argument +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackSetRewardAddressInput(input []byte) (common.Address, error) { + res, err := RewardManagerABI.UnpackInput("setRewardAddress", input) + if err != nil { + return common.Address{}, err + } + unpacked := *abi.ConvertType(res[0], new(common.Address)).(*common.Address) + return unpacked, nil +} + +// PackSetRewardAddress packs [addr] of type common.Address into the appropriate arguments for setRewardAddress. +// the packed bytes include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackSetRewardAddress(addr common.Address) ([]byte, error) { + return RewardManagerABI.Pack("setRewardAddress", addr) +} + +func setRewardAddress(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = contract.DeductGas(suppliedGas, SetRewardAddressGasCost); err != nil { + return nil, 0, err + } + if readOnly { + return nil, remainingGas, vmerrs.ErrWriteProtection + } + // attempts to unpack [input] into the arguments to the SetRewardAddressInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackSetRewardAddressInput(input) + if err != nil { + return nil, remainingGas, err + } + + // Allow list is enabled and SetRewardAddress is a state-changer function. + // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. + // You can modify/delete this code if you don't want this function to be restricted by the allow list. + stateDB := accessibleState.GetStateDB() + // Verify that the caller is in the allow list and therefore has the right to call this function. + callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) + if !callerStatus.IsEnabled() { + return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotSetRewardAddress, caller) + } + // allow list code ends here. + + // CUSTOM CODE STARTS HERE + _ = inputStruct // CUSTOM CODE OPERATES ON INPUT + // this function does not return an output, leave this one as is + packedOutput := []byte{} + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// createRewardManagerPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile. +// Access to the getters/setters is controlled by an allow list for ContractAddress. +func createRewardManagerPrecompile() contract.StatefulPrecompiledContract { + var functions []*contract.StatefulPrecompileFunction + functions = append(functions, allowlist.CreateAllowListFunctions(ContractAddress)...) + + abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{ + "allowFeeRecipients": allowFeeRecipients, + "areFeeRecipientsAllowed": areFeeRecipientsAllowed, + "currentRewardAddress": currentRewardAddress, + "disableRewards": disableRewards, + "setRewardAddress": setRewardAddress, + } + + for name, function := range abiFunctionMap { + method, ok := RewardManagerABI.Methods[name] + if !ok { + panic(fmt.Errorf("given method (%s) does not exist in the ABI", name)) + } + functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function)) + } + // Construct the contract with no fallback function. + statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions) + if err != nil { + panic(err) + } + return statefulContract +} diff --git a/precompile/reward_managerx/module.go b/precompile/reward_managerx/module.go new file mode 100644 index 0000000000..cad8a347cb --- /dev/null +++ b/precompile/reward_managerx/module.go @@ -0,0 +1,88 @@ +// Code generated +// This file is a generated precompile contract config with stubbed abstract functions. +// The file is generated by a template. Please inspect every code and comment in this file before use. + +// There are some must-be-done changes waiting in the file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. +// Additionally there are other files you need to edit to activate your precompile. +// These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". +// For testing take a look at other precompile tests in core/stateful_precompile_test.go and config_test.go in other precompile folders. + +/* General guidelines for precompile development: +1- Read the comment and set a suitable contract address in generated contract.go. E.g: + ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") +2- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. +Typically, custom codes are required in only those areas. +3- Set gas costs in generated contract.go +4- Register your precompile module in params/precompile_modules.go +5- Add your config unit tests under generated package config_test.go +6- Add your contract unit tests under tests/statefulprecompiles/{precompilename}_test.go +7- Add your solidity interface and test contract to contract-examples/contracts +8- Write solidity tests for your precompile in contract-examples/test +9- Create your genesis with your precompile enabled in tests/e2e/genesis/ +10- Create e2e test for your solidity test in tests/e2e/solidity/suites.go +11- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' + +*/ + +package rewardmanager + +import ( + "fmt" + + "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/modules" + + "github.com/ethereum/go-ethereum/common" +) + +var _ contract.Configurator = &configurator{} + +// ConfigKey is the key used in json config files to specify this precompile config. +// must be unique across all precompiles. +const ConfigKey = "rewardManagerConfig" + +// ContractAddress is the defined address of the precompile contract. +// This should be unique across all precompile contracts. +// See params/precompile_modules.go for registered precompile contracts and more information. +var ContractAddress = common.HexToAddress("{ASUITABLEHEXADDRESS}") // SET A SUITABLE HEX ADDRESS HERE + +// Module is the precompile module. It is used to register the precompile contract. +var Module = modules.Module{ + ConfigKey: ConfigKey, + Address: ContractAddress, + Contract: RewardManagerPrecompile, + Configurator: &configurator{}, +} + +type configurator struct{} + +func init() { + // Register the precompile module. + // Each precompile contract registers itself through [RegisterModule] function. + if err := modules.RegisterModule(Module); err != nil { + panic(err) + } +} + +// NewConfig returns a new precompile config. +// This is required for Marshal/Unmarshal the precompile config. +func (*configurator) NewConfig() config.Config { + return &Config{} +} + +// Configure configures [state] with the given [cfg] config. +// This function is called by the EVM once per precompile contract activation. +// You can use this function to set up your precompile contract's initial state, +// by using the [cfg] config and [state] stateDB. +func (*configurator) Configure(chainConfig contract.ChainConfig, cfg config.Config, state contract.StateDB, _ contract.BlockContext) error { + config, ok := cfg.(*Config) + if !ok { + return fmt.Errorf("incorrect config %T: %v", config, config) + } + // CUSTOM CODE STARTS HERE + + // AllowList is activated for this precompile. Configuring allowlist addresses here. + return config.Configure(state, ContractAddress) + +} From f806ad486c3b18cda3c4a7ddfa741818eca85f36 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 16 Feb 2023 21:21:27 +0300 Subject: [PATCH 11/23] fix fee manager config test --- precompile/contracts/feemanager/config_test.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/precompile/contracts/feemanager/config_test.go b/precompile/contracts/feemanager/config_test.go index 6deccb1ba5..41b1e1ff3d 100644 --- a/precompile/contracts/feemanager/config_test.go +++ b/precompile/contracts/feemanager/config_test.go @@ -28,6 +28,8 @@ var validFeeConfig = commontype.FeeConfig{ func TestVerifyFeeManagerConfig(t *testing.T) { admins := []common.Address{{1}} + invalidFeeConfig := validFeeConfig + invalidFeeConfig.GasLimit = big.NewInt(0) tests := []struct { name string config config.Config @@ -39,13 +41,15 @@ func TestVerifyFeeManagerConfig(t *testing.T) { ExpectedError: "cannot set address", }, { - name: "invalid initial fee manager config", - config: NewConfig(big.NewInt(3), admins, nil, - &commontype.FeeConfig{ - GasLimit: big.NewInt(0), - }), + name: "invalid initial fee manager config", + config: NewConfig(big.NewInt(3), admins, nil, &invalidFeeConfig), ExpectedError: "gasLimit = 0 cannot be less than or equal to 0", }, + { + name: "nil initial fee manager config", + config: NewConfig(big.NewInt(3), admins, nil, &commontype.FeeConfig{}), + ExpectedError: "gasLimit cannot be nil", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From ea3c13be126f7e968bab94e585ecd4ae0a272f58 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 17 Feb 2023 00:29:27 +0300 Subject: [PATCH 12/23] remove debug files --- abis/IAllowList.abi | 1 - abis/IRewardManager.abi | 1 - precompile/reward_managerx/config.go | 101 -------- precompile/reward_managerx/contract.abi | 1 - precompile/reward_managerx/contract.go | 305 ------------------------ precompile/reward_managerx/module.go | 88 ------- 6 files changed, 497 deletions(-) delete mode 100644 abis/IAllowList.abi delete mode 100644 abis/IRewardManager.abi delete mode 100644 precompile/reward_managerx/config.go delete mode 100644 precompile/reward_managerx/contract.abi delete mode 100644 precompile/reward_managerx/contract.go delete mode 100644 precompile/reward_managerx/module.go diff --git a/abis/IAllowList.abi b/abis/IAllowList.abi deleted file mode 100644 index ea89cd6408..0000000000 --- a/abis/IAllowList.abi +++ /dev/null @@ -1 +0,0 @@ -[{"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"}] \ No newline at end of file diff --git a/abis/IRewardManager.abi b/abis/IRewardManager.abi deleted file mode 100644 index d21d5bdc6b..0000000000 --- a/abis/IRewardManager.abi +++ /dev/null @@ -1 +0,0 @@ -[{"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_managerx/config.go b/precompile/reward_managerx/config.go deleted file mode 100644 index 1b6643aa13..0000000000 --- a/precompile/reward_managerx/config.go +++ /dev/null @@ -1,101 +0,0 @@ -// Code generated -// This file is a generated precompile contract config with stubbed abstract functions. -// The file is generated by a template. Please inspect every code and comment in this file before use. - -// There are some must-be-done changes waiting in the file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. -// Additionally there are other files you need to edit to activate your precompile. -// These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". -// For testing take a look at other precompile tests in core/stateful_precompile_test.go and config_test.go in other precompile folders. - -/* General guidelines for precompile development: -1- Read the comment and set a suitable contract address in generated contract.go. E.g: - ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") -2- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. -Typically, custom codes are required in only those areas. -3- Set gas costs in generated contract.go -4- Register your precompile module in params/precompile_modules.go -5- Add your config unit tests under generated package config_test.go -6- Add your contract unit tests under tests/statefulprecompiles/{precompilename}_test.go -7- Add your solidity interface and test contract to contract-examples/contracts -8- Write solidity tests for your precompile in contract-examples/test -9- Create your genesis with your precompile enabled in tests/e2e/genesis/ -10- Create e2e test for your solidity test in tests/e2e/solidity/suites.go -11- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' - -*/ - -package rewardmanager - -import ( - "math/big" - - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/config" - - "github.com/ethereum/go-ethereum/common" -) - -var _ config.Config = &Config{} - -// Config implements the StatefulPrecompileConfig -// interface while adding in the RewardManager specific precompile address. -type Config struct { - allowlist.Config - config.Upgrade -} - -// NewConfig returns a config for a network upgrade at [blockTimestamp] that enables -// RewardManager with the given [admins] and [enableds] as members of the allowlist. -// RewardManager with the given [admins] as members of the allowlist . -func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address) *Config { - return &Config{ - Config: allowlist.Config{ - AdminAddresses: admins, - EnabledAddresses: enableds, - }, - Upgrade: config.Upgrade{BlockTimestamp: blockTimestamp}, - } -} - -// NewDisableConfig returns config for a network upgrade at [blockTimestamp] -// that disables RewardManager. -func NewDisableConfig(blockTimestamp *big.Int) *Config { - return &Config{ - Upgrade: config.Upgrade{ - BlockTimestamp: blockTimestamp, - Disable: true, - }, - } -} - -// Key returns the key for the RewardManager config. -// This should be the same key as used in the precompile module. -func (*Config) Key() string { return ConfigKey } - -// Verify tries to verify Config and returns an error accordingly. -func (c *Config) Verify() error { - - // Verify AllowList first - if err := c.Config.Verify(); err != nil { - return err - } - - // CUSTOM CODE STARTS HERE - // Add your own custom verify code for Config here - // and return an error accordingly - return nil -} - -// Equal returns true if [s] is a [*Config] and it has been configured identical to [c]. -func (c *Config) Equal(s config.Config) bool { - // typecast before comparison - other, ok := (s).(*Config) - if !ok { - return false - } - // CUSTOM CODE STARTS HERE - // modify this boolean accordingly with your custom Config, to check if [other] and the current [c] are equal - // if Config contains only Upgrade and AllowListConfig you can skip modifying it. - equals := c.Upgrade.Equal(&other.Upgrade) && c.Config.Equal(&other.Config) - return equals -} diff --git a/precompile/reward_managerx/contract.abi b/precompile/reward_managerx/contract.abi deleted file mode 100644 index d21d5bdc6b..0000000000 --- a/precompile/reward_managerx/contract.abi +++ /dev/null @@ -1 +0,0 @@ -[{"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_managerx/contract.go b/precompile/reward_managerx/contract.go deleted file mode 100644 index 05b05271c0..0000000000 --- a/precompile/reward_managerx/contract.go +++ /dev/null @@ -1,305 +0,0 @@ -// Code generated -// This file is a generated precompile contract config with stubbed abstract functions. -// The file is generated by a template. Please inspect every code and comment in this file before use. - -// There are some must-be-done changes waiting in the file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. -// Additionally there are other files you need to edit to activate your precompile. -// These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". -// For testing take a look at other precompile tests in tests/statefulprecompiles/ and config_test.go in other precompile folders. - -/* General guidelines for precompile development: -1- Read the comment and set a suitable contract address in generated contract.go. E.g: - ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") -2- Set gas costs in generated contract.go -3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. -Typically, custom codes are required in only those areas. -4- Register your precompile module in params/precompile_modules.go -5- Add your config unit tests under generated package config_test.go -6- Add your contract unit tests under tests/statefulprecompiles/{precompilename}_test.go -7- Add your solidity interface and test contract to contract-examples/contracts -8- Write solidity tests for your precompile in contract-examples/test -9- Create your genesis with your precompile enabled in tests/e2e/genesis/ -10- Create e2e test for your solidity test in tests/e2e/solidity/suites.go -11- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' - -*/ - -package rewardmanager - -import ( - "errors" - "fmt" - "math/big" - - "github.com/ava-labs/subnet-evm/accounts/abi" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/vmerrs" - - _ "embed" - - "github.com/ethereum/go-ethereum/common" -) - -const ( - // Gas costs for each function. These are set to 0 by default. - // You should set a gas cost for each function in your contract. - // Generally, you should not set gas costs very low as this may cause your network to be vulnerable to DoS attacks. - // There are some predefined gas costs in contract/utils.go that you can use. - // This contract also uses AllowList precompile. - // You should also increase gas costs of functions that read from AllowList storage.} - AllowFeeRecipientsGasCost uint64 = 0 + allowlist.ReadAllowListGasCost // SET A GAS COST HERE - AreFeeRecipientsAllowedGasCost uint64 = 0 // SET A GAS COST HERE - CurrentRewardAddressGasCost uint64 = 0 // SET A GAS COST HERE - DisableRewardsGasCost uint64 = 0 + allowlist.ReadAllowListGasCost // SET A GAS COST HERE - SetRewardAddressGasCost uint64 = 0 + allowlist.ReadAllowListGasCost // SET A GAS COST HERE -) - -// CUSTOM CODE STARTS HERE -// Reference imports to suppress errors from unused imports. This code and any unnecessary imports can be removed. -var ( - _ = errors.New - _ = big.NewInt -) - -// Singleton StatefulPrecompiledContract and signatures. -var ( - ErrCannotAllowFeeRecipients = errors.New("non-enabled cannot call allowFeeRecipients") - - ErrCannotDisableRewards = errors.New("non-enabled cannot call disableRewards") - - ErrCannotSetRewardAddress = errors.New("non-enabled cannot call setRewardAddress") - - // RewardManagerRawABI contains the raw ABI of RewardManager contract. - //go:embed contract.abi - RewardManagerRawABI string - - RewardManagerABI = contract.ParseABI(RewardManagerRawABI) - - RewardManagerPrecompile = createRewardManagerPrecompile() -) - -// GetRewardManagerAllowListStatus returns the role of [address] for the RewardManager list. -func GetRewardManagerAllowListStatus(stateDB contract.StateDB, address common.Address) allowlist.Role { - return allowlist.GetAllowListStatus(stateDB, ContractAddress, address) -} - -// SetRewardManagerAllowListStatus sets the permissions of [address] to [role] for the -// RewardManager list. Assumes [role] has already been verified as valid. -// This stores the [role] in the contract storage with address [ContractAddress] -// and [address] hash. It means that any reusage of the [address] key for different value -// conflicts with the same slot [role] is stored. -// Precompile implementations must use a different key than [address] for their storage. -func SetRewardManagerAllowListStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) { - allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) -} - -// PackAllowFeeRecipients packs the include selector (first 4 func signature bytes). -// This function is mostly used for tests. -func PackAllowFeeRecipients() ([]byte, error) { - return RewardManagerABI.Pack("allowFeeRecipients") -} - -func allowFeeRecipients(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, AllowFeeRecipientsGasCost); err != nil { - return nil, 0, err - } - if readOnly { - return nil, remainingGas, vmerrs.ErrWriteProtection - } - // no input provided for this function - - // Allow list is enabled and AllowFeeRecipients is a state-changer function. - // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. - // You can modify/delete this code if you don't want this function to be restricted by the allow list. - stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to call this function. - callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) - if !callerStatus.IsEnabled() { - return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotAllowFeeRecipients, caller) - } - // allow list code ends here. - - // CUSTOM CODE STARTS HERE - // this function does not return an output, leave this one as is - packedOutput := []byte{} - - // Return the packed output and the remaining gas - return packedOutput, remainingGas, nil -} - -// PackAreFeeRecipientsAllowed packs the include selector (first 4 func signature bytes). -// This function is mostly used for tests. -func PackAreFeeRecipientsAllowed() ([]byte, error) { - return RewardManagerABI.Pack("areFeeRecipientsAllowed") -} - -// PackAreFeeRecipientsAllowedOutput attempts to pack given isAllowed of type bool -// to conform the ABI outputs. -func PackAreFeeRecipientsAllowedOutput(isAllowed bool) ([]byte, error) { - return RewardManagerABI.PackOutput("areFeeRecipientsAllowed", isAllowed) -} - -func areFeeRecipientsAllowed(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, AreFeeRecipientsAllowedGasCost); err != nil { - return nil, 0, err - } - // no input provided for this function - - // CUSTOM CODE STARTS HERE - - var output bool // CUSTOM CODE FOR AN OUTPUT - packedOutput, err := PackAreFeeRecipientsAllowedOutput(output) - if err != nil { - return nil, remainingGas, err - } - - // Return the packed output and the remaining gas - return packedOutput, remainingGas, nil -} - -// PackCurrentRewardAddress packs the include selector (first 4 func signature bytes). -// This function is mostly used for tests. -func PackCurrentRewardAddress() ([]byte, error) { - return RewardManagerABI.Pack("currentRewardAddress") -} - -// PackCurrentRewardAddressOutput attempts to pack given rewardAddress of type common.Address -// to conform the ABI outputs. -func PackCurrentRewardAddressOutput(rewardAddress common.Address) ([]byte, error) { - return RewardManagerABI.PackOutput("currentRewardAddress", rewardAddress) -} - -func currentRewardAddress(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, CurrentRewardAddressGasCost); err != nil { - return nil, 0, err - } - // no input provided for this function - - // CUSTOM CODE STARTS HERE - - var output common.Address // CUSTOM CODE FOR AN OUTPUT - packedOutput, err := PackCurrentRewardAddressOutput(output) - if err != nil { - return nil, remainingGas, err - } - - // Return the packed output and the remaining gas - return packedOutput, remainingGas, nil -} - -// PackDisableRewards packs the include selector (first 4 func signature bytes). -// This function is mostly used for tests. -func PackDisableRewards() ([]byte, error) { - return RewardManagerABI.Pack("disableRewards") -} - -func disableRewards(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, DisableRewardsGasCost); err != nil { - return nil, 0, err - } - if readOnly { - return nil, remainingGas, vmerrs.ErrWriteProtection - } - // no input provided for this function - - // Allow list is enabled and DisableRewards is a state-changer function. - // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. - // You can modify/delete this code if you don't want this function to be restricted by the allow list. - stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to call this function. - callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) - if !callerStatus.IsEnabled() { - return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotDisableRewards, caller) - } - // allow list code ends here. - - // CUSTOM CODE STARTS HERE - // this function does not return an output, leave this one as is - packedOutput := []byte{} - - // Return the packed output and the remaining gas - return packedOutput, remainingGas, nil -} - -// UnpackSetRewardAddressInput attempts to unpack [input] into the common.Address type argument -// assumes that [input] does not include selector (omits first 4 func signature bytes) -func UnpackSetRewardAddressInput(input []byte) (common.Address, error) { - res, err := RewardManagerABI.UnpackInput("setRewardAddress", input) - if err != nil { - return common.Address{}, err - } - unpacked := *abi.ConvertType(res[0], new(common.Address)).(*common.Address) - return unpacked, nil -} - -// PackSetRewardAddress packs [addr] of type common.Address into the appropriate arguments for setRewardAddress. -// the packed bytes include selector (first 4 func signature bytes). -// This function is mostly used for tests. -func PackSetRewardAddress(addr common.Address) ([]byte, error) { - return RewardManagerABI.Pack("setRewardAddress", addr) -} - -func setRewardAddress(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, SetRewardAddressGasCost); err != nil { - return nil, 0, err - } - if readOnly { - return nil, remainingGas, vmerrs.ErrWriteProtection - } - // attempts to unpack [input] into the arguments to the SetRewardAddressInput. - // Assumes that [input] does not include selector - // You can use unpacked [inputStruct] variable in your code - inputStruct, err := UnpackSetRewardAddressInput(input) - if err != nil { - return nil, remainingGas, err - } - - // Allow list is enabled and SetRewardAddress is a state-changer function. - // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. - // You can modify/delete this code if you don't want this function to be restricted by the allow list. - stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to call this function. - callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) - if !callerStatus.IsEnabled() { - return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotSetRewardAddress, caller) - } - // allow list code ends here. - - // CUSTOM CODE STARTS HERE - _ = inputStruct // CUSTOM CODE OPERATES ON INPUT - // this function does not return an output, leave this one as is - packedOutput := []byte{} - - // Return the packed output and the remaining gas - return packedOutput, remainingGas, nil -} - -// createRewardManagerPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile. -// Access to the getters/setters is controlled by an allow list for ContractAddress. -func createRewardManagerPrecompile() contract.StatefulPrecompiledContract { - var functions []*contract.StatefulPrecompileFunction - functions = append(functions, allowlist.CreateAllowListFunctions(ContractAddress)...) - - abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{ - "allowFeeRecipients": allowFeeRecipients, - "areFeeRecipientsAllowed": areFeeRecipientsAllowed, - "currentRewardAddress": currentRewardAddress, - "disableRewards": disableRewards, - "setRewardAddress": setRewardAddress, - } - - for name, function := range abiFunctionMap { - method, ok := RewardManagerABI.Methods[name] - if !ok { - panic(fmt.Errorf("given method (%s) does not exist in the ABI", name)) - } - functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function)) - } - // Construct the contract with no fallback function. - statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions) - if err != nil { - panic(err) - } - return statefulContract -} diff --git a/precompile/reward_managerx/module.go b/precompile/reward_managerx/module.go deleted file mode 100644 index cad8a347cb..0000000000 --- a/precompile/reward_managerx/module.go +++ /dev/null @@ -1,88 +0,0 @@ -// Code generated -// This file is a generated precompile contract config with stubbed abstract functions. -// The file is generated by a template. Please inspect every code and comment in this file before use. - -// There are some must-be-done changes waiting in the file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. -// Additionally there are other files you need to edit to activate your precompile. -// These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". -// For testing take a look at other precompile tests in core/stateful_precompile_test.go and config_test.go in other precompile folders. - -/* General guidelines for precompile development: -1- Read the comment and set a suitable contract address in generated contract.go. E.g: - ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") -2- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. -Typically, custom codes are required in only those areas. -3- Set gas costs in generated contract.go -4- Register your precompile module in params/precompile_modules.go -5- Add your config unit tests under generated package config_test.go -6- Add your contract unit tests under tests/statefulprecompiles/{precompilename}_test.go -7- Add your solidity interface and test contract to contract-examples/contracts -8- Write solidity tests for your precompile in contract-examples/test -9- Create your genesis with your precompile enabled in tests/e2e/genesis/ -10- Create e2e test for your solidity test in tests/e2e/solidity/suites.go -11- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' - -*/ - -package rewardmanager - -import ( - "fmt" - - "github.com/ava-labs/subnet-evm/precompile/config" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/precompile/modules" - - "github.com/ethereum/go-ethereum/common" -) - -var _ contract.Configurator = &configurator{} - -// ConfigKey is the key used in json config files to specify this precompile config. -// must be unique across all precompiles. -const ConfigKey = "rewardManagerConfig" - -// ContractAddress is the defined address of the precompile contract. -// This should be unique across all precompile contracts. -// See params/precompile_modules.go for registered precompile contracts and more information. -var ContractAddress = common.HexToAddress("{ASUITABLEHEXADDRESS}") // SET A SUITABLE HEX ADDRESS HERE - -// Module is the precompile module. It is used to register the precompile contract. -var Module = modules.Module{ - ConfigKey: ConfigKey, - Address: ContractAddress, - Contract: RewardManagerPrecompile, - Configurator: &configurator{}, -} - -type configurator struct{} - -func init() { - // Register the precompile module. - // Each precompile contract registers itself through [RegisterModule] function. - if err := modules.RegisterModule(Module); err != nil { - panic(err) - } -} - -// NewConfig returns a new precompile config. -// This is required for Marshal/Unmarshal the precompile config. -func (*configurator) NewConfig() config.Config { - return &Config{} -} - -// Configure configures [state] with the given [cfg] config. -// This function is called by the EVM once per precompile contract activation. -// You can use this function to set up your precompile contract's initial state, -// by using the [cfg] config and [state] stateDB. -func (*configurator) Configure(chainConfig contract.ChainConfig, cfg config.Config, state contract.StateDB, _ contract.BlockContext) error { - config, ok := cfg.(*Config) - if !ok { - return fmt.Errorf("incorrect config %T: %v", config, config) - } - // CUSTOM CODE STARTS HERE - - // AllowList is activated for this precompile. Configuring allowlist addresses here. - return config.Configure(state, ContractAddress) - -} From 39e16d8389515181ebbca6ae643bfcbfecacf9e1 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 17 Feb 2023 10:50:28 +0300 Subject: [PATCH 13/23] Update core/state_processor.go Co-authored-by: aaronbuchwald --- core/state_processor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state_processor.go b/core/state_processor.go index 99be0b20f6..2634f83640 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -183,7 +183,7 @@ func ApplyPrecompileActivations(c *params.ChainConfig, parentTimestamp *big.Int, // Note: RegisteredModules returns precompiles sorted by module addresses. // This ensures that the order we call Configure for each precompile is consistent. // This ensures even if precompiles read/write state other than their own they will observe - // an identical global state in a deterministic order when as they are configured. + // an identical global state in a deterministic order when they are configured. for _, module := range modules.RegisteredModules() { key := module.ConfigKey for _, activatingConfig := range c.GetActivatingPrecompileConfigs(module.Address, parentTimestamp, blockTimestamp, c.PrecompileUpgrades) { From 9a63a87806847eb8c3018eb97eb3056d5a5df2fe Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 17 Feb 2023 12:25:09 +0300 Subject: [PATCH 14/23] fix comments --- .../precompile_config_template.go | 27 ++++++++++--------- .../precompile_contract_template.go | 26 +++++++++--------- .../precompile_module_template.go | 27 ++++++++++--------- commontype/fee_config.go | 4 +-- 4 files changed, 44 insertions(+), 40 deletions(-) diff --git a/accounts/abi/bind/precompilebind/precompile_config_template.go b/accounts/abi/bind/precompilebind/precompile_config_template.go index f23f59d0b4..edab3ddb1b 100644 --- a/accounts/abi/bind/precompilebind/precompile_config_template.go +++ b/accounts/abi/bind/precompilebind/precompile_config_template.go @@ -11,23 +11,24 @@ const tmplSourcePrecompileConfigGo = ` // There are some must-be-done changes waiting in the file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. // Additionally there are other files you need to edit to activate your precompile. // These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". -// For testing take a look at other precompile tests in core/stateful_precompile_test.go and config_test.go in other precompile folders. +// For testing take a look at other precompile tests in contract_test.go and config_test.go in other precompile folders. +// See the tutorial in https://docs.avax.network/subnets/hello-world-precompile-tutorial for more information about precompile development. /* General guidelines for precompile development: -1- Read the comment and set a suitable contract address in generated contract.go. E.g: +1- Read the comment and set a suitable contract address in generated module.go. E.g: ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") -2- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. -Typically, custom codes are required in only those areas. -3- Set gas costs in generated contract.go -4- Register your precompile module in params/precompile_modules.go +2- Set a suitable config key in generated module.go. E.g: "yourPrecompileConfig" +3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Typically, custom codes are required in only those areas. +Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. +4- Set gas costs in generated contract.go 5- Add your config unit tests under generated package config_test.go -6- Add your contract unit tests under tests/statefulprecompiles/{precompilename}_test.go -7- Add your solidity interface and test contract to contract-examples/contracts -8- Write solidity tests for your precompile in contract-examples/test -9- Create your genesis with your precompile enabled in tests/e2e/genesis/ -10- Create e2e test for your solidity test in tests/e2e/solidity/suites.go -11- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' - +6- Add your contract unit tests undertgenerated package contract_test.go +7- Additionally you can add a full-fledged VM test for your precompile under plugin/vm/vm_test.go. See existing precompile tests for examples. +8- Add your solidity interface and test contract to contract-examples/contracts +9- Write solidity tests for your precompile in contract-examples/test +10- Create your genesis with your precompile enabled in tests/e2e/genesis/ +11- Create e2e test for your solidity test in tests/e2e/solidity/suites.go +12- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' */ package {{.Package}} diff --git a/accounts/abi/bind/precompilebind/precompile_contract_template.go b/accounts/abi/bind/precompilebind/precompile_contract_template.go index c233120fdd..d96b67e768 100644 --- a/accounts/abi/bind/precompilebind/precompile_contract_template.go +++ b/accounts/abi/bind/precompilebind/precompile_contract_template.go @@ -28,22 +28,24 @@ const tmplSourcePrecompileContractGo = ` // There are some must-be-done changes waiting in the file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. // Additionally there are other files you need to edit to activate your precompile. // These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". -// For testing take a look at other precompile tests in tests/statefulprecompiles/ and config_test.go in other precompile folders. +// For testing take a look at other precompile tests in contract_test.go and config_test.go in other precompile folders. +// See the tutorial in https://docs.avax.network/subnets/hello-world-precompile-tutorial for more information about precompile development. /* General guidelines for precompile development: -1- Read the comment and set a suitable contract address in generated contract.go. E.g: +1- Read the comment and set a suitable contract address in generated module.go. E.g: ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") -2- Set gas costs in generated contract.go -3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. -Typically, custom codes are required in only those areas. -4- Register your precompile module in params/precompile_modules.go +2- Set a suitable config key in generated module.go. E.g: "yourPrecompileConfig" +3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Typically, custom codes are required in only those areas. +Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. +4- Set gas costs in generated contract.go 5- Add your config unit tests under generated package config_test.go -6- Add your contract unit tests under tests/statefulprecompiles/{precompilename}_test.go -7- Add your solidity interface and test contract to contract-examples/contracts -8- Write solidity tests for your precompile in contract-examples/test -9- Create your genesis with your precompile enabled in tests/e2e/genesis/ -10- Create e2e test for your solidity test in tests/e2e/solidity/suites.go -11- Run your e2e precompile Solidity tests with './scripts/run_ginkgo.sh' +6- Add your contract unit tests undertgenerated package contract_test.go +7- Additionally you can add a full-fledged VM test for your precompile under plugin/vm/vm_test.go. See existing precompile tests for examples. +8- Add your solidity interface and test contract to contract-examples/contracts +9- Write solidity tests for your precompile in contract-examples/test +10- Create your genesis with your precompile enabled in tests/e2e/genesis/ +11- Create e2e test for your solidity test in tests/e2e/solidity/suites.go +12- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' */ package {{.Package}} diff --git a/accounts/abi/bind/precompilebind/precompile_module_template.go b/accounts/abi/bind/precompilebind/precompile_module_template.go index 13fcfc0c3e..b0b8c7f184 100644 --- a/accounts/abi/bind/precompilebind/precompile_module_template.go +++ b/accounts/abi/bind/precompilebind/precompile_module_template.go @@ -11,23 +11,24 @@ const tmplSourcePrecompileModuleGo = ` // There are some must-be-done changes waiting in the file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. // Additionally there are other files you need to edit to activate your precompile. // These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". -// For testing take a look at other precompile tests in core/stateful_precompile_test.go and config_test.go in other precompile folders. +// For testing take a look at other precompile tests in contract_test.go and config_test.go in other precompile folders. +// See the tutorial in https://docs.avax.network/subnets/hello-world-precompile-tutorial for more information about precompile development. /* General guidelines for precompile development: -1- Read the comment and set a suitable contract address in generated contract.go. E.g: +1- Read the comment and set a suitable contract address in generated module.go. E.g: ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") -2- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. -Typically, custom codes are required in only those areas. -3- Set gas costs in generated contract.go -4- Register your precompile module in params/precompile_modules.go +2- Set a suitable config key in generated module.go. E.g: "yourPrecompileConfig" +3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Typically, custom codes are required in only those areas. +Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. +4- Set gas costs in generated contract.go 5- Add your config unit tests under generated package config_test.go -6- Add your contract unit tests under tests/statefulprecompiles/{precompilename}_test.go -7- Add your solidity interface and test contract to contract-examples/contracts -8- Write solidity tests for your precompile in contract-examples/test -9- Create your genesis with your precompile enabled in tests/e2e/genesis/ -10- Create e2e test for your solidity test in tests/e2e/solidity/suites.go -11- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' - +6- Add your contract unit tests undertgenerated package contract_test.go +7- Additionally you can add a full-fledged VM test for your precompile under plugin/vm/vm_test.go. See existing precompile tests for examples. +8- Add your solidity interface and test contract to contract-examples/contracts +9- Write solidity tests for your precompile in contract-examples/test +10- Create your genesis with your precompile enabled in tests/e2e/genesis/ +11- Create e2e test for your solidity test in tests/e2e/solidity/suites.go +12- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' */ package {{.Package}} diff --git a/commontype/fee_config.go b/commontype/fee_config.go index d7d23f3f32..3089df5d9c 100644 --- a/commontype/fee_config.go +++ b/commontype/fee_config.go @@ -16,8 +16,8 @@ import ( // // The dynamic fee algorithm simply increases fees when the network is operating at a utilization level above the target and decreases fees // when the network is operating at a utilization level below the target. -// This struct is used by params.Config and precompile.FeeManager -// any modification of this struct has direct affect on the precompiled contract +// This struct is used by Genesis and Fee Manager precompile. +// Any modification of this struct has direct affect on the precompiled contract // and changes should be carefully handled in the precompiled contract code. type FeeConfig struct { // GasLimit sets the max amount of gas consumed per block. From c9a5811268b9698c0c3714e1bd70af72823a583e Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 17 Feb 2023 13:10:03 +0300 Subject: [PATCH 15/23] restore statedb ordering --- precompile/contracts/rewardmanager/module.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/precompile/contracts/rewardmanager/module.go b/precompile/contracts/rewardmanager/module.go index 52f7ed62cb..5cb703d45b 100644 --- a/precompile/contracts/rewardmanager/module.go +++ b/precompile/contracts/rewardmanager/module.go @@ -45,6 +45,10 @@ func (*configurator) Configure(chainConfig contract.ChainConfig, cfg config.Conf if !ok { return fmt.Errorf("incorrect config %T: %v", config, config) } + err := config.Configure(state, ContractAddress) + if err != nil { + return err + } // configure the RewardManager with the given initial configuration if config.InitialRewardConfig != nil { return config.InitialRewardConfig.Configure(state) @@ -57,5 +61,5 @@ func (*configurator) Configure(chainConfig contract.ChainConfig, cfg config.Conf // default to disabling rewards DisableFeeRewards(state) } - return config.Configure(state, ContractAddress) + return nil } From 81fb4a94d5f0b95c5128c90b086a88e4d6129f70 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 17 Feb 2023 19:22:59 +0300 Subject: [PATCH 16/23] fix configure in reward manager --- precompile/contracts/rewardmanager/module.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/precompile/contracts/rewardmanager/module.go b/precompile/contracts/rewardmanager/module.go index 5cb703d45b..83788648e5 100644 --- a/precompile/contracts/rewardmanager/module.go +++ b/precompile/contracts/rewardmanager/module.go @@ -45,13 +45,9 @@ func (*configurator) Configure(chainConfig contract.ChainConfig, cfg config.Conf if !ok { return fmt.Errorf("incorrect config %T: %v", config, config) } - err := config.Configure(state, ContractAddress) - if err != nil { - return err - } // configure the RewardManager with the given initial configuration if config.InitialRewardConfig != nil { - return config.InitialRewardConfig.Configure(state) + config.InitialRewardConfig.Configure(state) } else if chainConfig.AllowedFeeRecipients() { // configure the RewardManager according to chainConfig EnableAllowFeeRecipients(state) @@ -61,5 +57,5 @@ func (*configurator) Configure(chainConfig contract.ChainConfig, cfg config.Conf // default to disabling rewards DisableFeeRewards(state) } - return nil + return config.Configure(state, ContractAddress) } From d518651c8f6effa7e1ecf177b9dc346d6caee432 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 17 Feb 2023 08:59:16 -0800 Subject: [PATCH 17/23] precompiles: adds a regression test for the IsDisabled case in AvalancheRules (#515) --- params/config_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/params/config_test.go b/params/config_test.go index 8e1c3f7ada..a407c81bae 100644 --- a/params/config_test.go +++ b/params/config_test.go @@ -206,3 +206,24 @@ func TestConfigUnmarshalJSON(t *testing.T) { require.NoError(err) require.Equal(c, c2) } + +func TestActivePrecompiles(t *testing.T) { + config := ChainConfig{ + UpgradeConfig: UpgradeConfig{ + PrecompileUpgrades: []PrecompileUpgrade{ + { + nativeminter.NewConfig(common.Big0, nil, nil, nil), // enable at genesis + }, + { + nativeminter.NewDisableConfig(common.Big1), // disable at timestamp 1 + }, + }, + }, + } + + rules0 := config.AvalancheRules(common.Big0, common.Big0) + require.True(t, rules0.IsPrecompileEnabled(nativeminter.Module.Address)) + + rules1 := config.AvalancheRules(common.Big0, common.Big1) + require.False(t, rules1.IsPrecompileEnabled(nativeminter.Module.Address)) +} From c81b44b6c5e43a03fadc8787c15e6601b03c3d9f Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 17 Feb 2023 11:21:14 -0800 Subject: [PATCH 18/23] Rename configs: alternative (#520) * alternative renaming for precompile configs * fixes * update naming * rename to AllowListConfig * simplify --- params/config.go | 6 ++-- params/precompile_upgrade.go | 13 ++++---- params/precompiles.go | 4 +-- precompile/allowlist/allow_list_test.go | 6 ++-- precompile/allowlist/config.go | 10 +++--- precompile/allowlist/config_test.go | 32 +++++++++---------- precompile/contract/interfaces.go | 6 ++-- precompile/contract/utils.go | 4 +-- .../contracts/deployerallowlist/config.go | 20 ++++++------ .../deployerallowlist/config_test.go | 10 +++--- .../contracts/deployerallowlist/module.go | 8 ++--- precompile/contracts/feemanager/config.go | 20 ++++++------ .../contracts/feemanager/config_test.go | 10 +++--- precompile/contracts/feemanager/module.go | 8 ++--- precompile/contracts/nativeminter/config.go | 20 ++++++------ .../contracts/nativeminter/config_test.go | 10 +++--- precompile/contracts/nativeminter/module.go | 8 ++--- precompile/contracts/rewardmanager/config.go | 20 ++++++------ .../contracts/rewardmanager/config_test.go | 10 +++--- precompile/contracts/rewardmanager/module.go | 6 ++-- precompile/contracts/txallowlist/config.go | 18 +++++------ .../contracts/txallowlist/config_test.go | 8 ++--- precompile/contracts/txallowlist/module.go | 8 ++--- .../{config => precompileconfig}/config.go | 2 +- .../mock_config.go | 3 +- .../upgradeable.go | 2 +- 26 files changed, 135 insertions(+), 137 deletions(-) rename precompile/{config => precompileconfig}/config.go (97%) rename precompile/{config => precompileconfig}/mock_config.go (97%) rename precompile/{config => precompileconfig}/upgradeable.go (97%) diff --git a/params/config.go b/params/config.go index 2ff5581565..1f2289f2a9 100644 --- a/params/config.go +++ b/params/config.go @@ -34,8 +34,8 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" ) @@ -558,7 +558,7 @@ type Rules struct { // for this rule set. // Note: none of these addresses should conflict with the address space used by // any existing precompiles. - ActivePrecompiles map[common.Address]config.Config + ActivePrecompiles map[common.Address]precompileconfig.Config } // IsPrecompileEnabled returns true if the precompile at [addr] is enabled for this rule set. @@ -594,7 +594,7 @@ func (c *ChainConfig) AvalancheRules(blockNum, blockTimestamp *big.Int) Rules { rules.IsSubnetEVM = c.IsSubnetEVM(blockTimestamp) // Initialize the stateful precompiles that should be enabled at [blockTimestamp]. - rules.ActivePrecompiles = make(map[common.Address]config.Config) + rules.ActivePrecompiles = make(map[common.Address]precompileconfig.Config) for _, module := range modules.RegisteredModules() { if config := c.GetActivePrecompileConfig(module.Address, blockTimestamp); config != nil && !config.IsDisabled() { rules.ActivePrecompiles[module.Address] = config diff --git a/params/precompile_upgrade.go b/params/precompile_upgrade.go index 66dfee0202..16792c6781 100644 --- a/params/precompile_upgrade.go +++ b/params/precompile_upgrade.go @@ -9,9 +9,8 @@ import ( "fmt" "math/big" - "github.com/ava-labs/subnet-evm/precompile/config" - precompileConfig "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" ) @@ -23,7 +22,7 @@ var errNoKey = errors.New("PrecompileUpgrade cannot be empty") // based on the key. Keys are defined in each precompile module, and registered in // precompile/registry/registry.go. type PrecompileUpgrade struct { - config.Config + precompileconfig.Config } // UnmarshalJSON unmarshals the json into the correct precompile config type @@ -59,7 +58,7 @@ func (u *PrecompileUpgrade) UnmarshalJSON(data []byte) error { // MarshalJSON marshal the precompile config into json based on the precompile key. // Ex: {"feeManagerConfig": {...}} where "feeManagerConfig" is the key func (u *PrecompileUpgrade) MarshalJSON() ([]byte, error) { - res := make(map[string]precompileConfig.Config) + res := make(map[string]precompileconfig.Config) res[u.Key()] = u.Config return json.Marshal(res) } @@ -152,7 +151,7 @@ func (c *ChainConfig) verifyPrecompileUpgrades() error { // GetActivePrecompileConfig returns the most recent precompile config corresponding to [address]. // If none have occurred, returns nil. -func (c *ChainConfig) GetActivePrecompileConfig(address common.Address, blockTimestamp *big.Int) config.Config { +func (c *ChainConfig) GetActivePrecompileConfig(address common.Address, blockTimestamp *big.Int) precompileconfig.Config { configs := c.GetActivatingPrecompileConfigs(address, nil, blockTimestamp, c.PrecompileUpgrades) if len(configs) == 0 { return nil @@ -162,13 +161,13 @@ func (c *ChainConfig) GetActivePrecompileConfig(address common.Address, blockTim // GetActivatingPrecompileConfigs returns all upgrades configured to activate during the state transition from a block with timestamp [from] // to a block with timestamp [to]. -func (c *ChainConfig) GetActivatingPrecompileConfigs(address common.Address, from *big.Int, to *big.Int, upgrades []PrecompileUpgrade) []config.Config { +func (c *ChainConfig) GetActivatingPrecompileConfigs(address common.Address, from *big.Int, to *big.Int, upgrades []PrecompileUpgrade) []precompileconfig.Config { // Get key from address. module, ok := modules.GetPrecompileModuleByAddress(address) if !ok { return nil } - configs := make([]config.Config, 0) + configs := make([]precompileconfig.Config, 0) key := module.ConfigKey // First check the embedded [upgrade] for precompiles configured // in the genesis chain config. diff --git a/params/precompiles.go b/params/precompiles.go index 4ad0bf3a8f..fcb7604e5b 100644 --- a/params/precompiles.go +++ b/params/precompiles.go @@ -6,11 +6,11 @@ package params import ( "encoding/json" - "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" ) -type Precompiles map[string]config.Config +type Precompiles map[string]precompileconfig.Config // UnmarshalJSON parses the JSON-encoded data into the ChainConfigPrecompiles. // ChainConfigPrecompiles is a map of precompile module keys to their diff --git a/precompile/allowlist/allow_list_test.go b/precompile/allowlist/allow_list_test.go index 3a9f4f2dd3..6122a7c818 100644 --- a/precompile/allowlist/allow_list_test.go +++ b/precompile/allowlist/allow_list_test.go @@ -25,7 +25,7 @@ func TestAllowListRun(t *testing.T) { expectedRes []byte expectedErr string - config *Config + config *AllowListConfig assertState func(t *testing.T, state *state.StateDB) } @@ -221,7 +221,7 @@ func TestAllowListRun(t *testing.T) { expectedErr: vmerrs.ErrOutOfGas.Error(), }, "initial config sets admins": { - config: &Config{ + config: &AllowListConfig{ AdminAddresses: []common.Address{noRoleAddr, enabledAddr}, }, suppliedGas: 0, @@ -233,7 +233,7 @@ func TestAllowListRun(t *testing.T) { }, }, "initial config sets enabled": { - config: &Config{ + config: &AllowListConfig{ EnabledAddresses: []common.Address{noRoleAddr, adminAddr}, }, suppliedGas: 0, diff --git a/precompile/allowlist/config.go b/precompile/allowlist/config.go index c11f86afc2..3ab68d374f 100644 --- a/precompile/allowlist/config.go +++ b/precompile/allowlist/config.go @@ -10,15 +10,15 @@ import ( "github.com/ethereum/go-ethereum/common" ) -// Config specifies the initial set of addresses with Admin or Enabled roles. -type Config struct { +// AllowListConfig specifies the initial set of addresses with Admin or Enabled roles. +type AllowListConfig struct { AdminAddresses []common.Address `json:"adminAddresses,omitempty"` // initial admin addresses EnabledAddresses []common.Address `json:"enabledAddresses,omitempty"` // initial enabled addresses } // Configure initializes the address space of [precompileAddr] by initializing the role of each of // the addresses in [AllowListAdmins]. -func (c *Config) Configure(state contract.StateDB, precompileAddr common.Address) error { +func (c *AllowListConfig) Configure(state contract.StateDB, precompileAddr common.Address) error { for _, enabledAddr := range c.EnabledAddresses { SetAllowListRole(state, precompileAddr, enabledAddr, EnabledRole) } @@ -29,7 +29,7 @@ func (c *Config) Configure(state contract.StateDB, precompileAddr common.Address } // Equal returns true iff [other] has the same admins in the same order in its allow list. -func (c *Config) Equal(other *Config) bool { +func (c *AllowListConfig) Equal(other *AllowListConfig) bool { if other == nil { return false } @@ -52,7 +52,7 @@ func areEqualAddressLists(current []common.Address, other []common.Address) bool } // Verify returns an error if there is an overlapping address between admin and enabled roles -func (c *Config) Verify() error { +func (c *AllowListConfig) Verify() error { addressMap := make(map[common.Address]Role) // tracks which addresses we have seen and their role // check for duplicates in enabled list diff --git a/precompile/allowlist/config_test.go b/precompile/allowlist/config_test.go index b9fdb24053..8473115400 100644 --- a/precompile/allowlist/config_test.go +++ b/precompile/allowlist/config_test.go @@ -10,32 +10,32 @@ import ( "github.com/stretchr/testify/require" ) -func TestVerifyAllowlistConfig(t *testing.T) { +func TestVerifyAllowlistAllowList(t *testing.T) { admins := []common.Address{{1}} enableds := []common.Address{{2}} tests := []struct { name string - config Config + config AllowListConfig expectedError string }{ { name: "invalid allow list config in allowlist", - config: Config{admins, admins}, + config: AllowListConfig{admins, admins}, expectedError: "cannot set address", }, { name: "nil member allow list config in allowlist", - config: Config{nil, nil}, + config: AllowListConfig{nil, nil}, expectedError: "", }, { name: "empty member allow list config in allowlist", - config: Config{[]common.Address{}, []common.Address{}}, + config: AllowListConfig{[]common.Address{}, []common.Address{}}, expectedError: "", }, { name: "valid allow list config in allowlist", - config: Config{admins, enableds}, + config: AllowListConfig{admins, enableds}, expectedError: "", }, } @@ -53,37 +53,37 @@ func TestVerifyAllowlistConfig(t *testing.T) { } } -func TestEqualAllowListConfig(t *testing.T) { +func TestEqualAllowListAllowList(t *testing.T) { admins := []common.Address{{1}} enableds := []common.Address{{2}} tests := []struct { name string - config *Config - other *Config + config *AllowListConfig + other *AllowListConfig expected bool }{ { name: "non-nil config and nil other", - config: &Config{admins, enableds}, + config: &AllowListConfig{admins, enableds}, other: nil, expected: false, }, { name: "different admin", - config: &Config{admins, enableds}, - other: &Config{[]common.Address{{3}}, enableds}, + config: &AllowListConfig{admins, enableds}, + other: &AllowListConfig{[]common.Address{{3}}, enableds}, expected: false, }, { name: "different enabled", - config: &Config{admins, enableds}, - other: &Config{admins, []common.Address{{3}}}, + config: &AllowListConfig{admins, enableds}, + other: &AllowListConfig{admins, []common.Address{{3}}}, expected: false, }, { name: "same config", - config: &Config{admins, enableds}, - other: &Config{admins, enableds}, + config: &AllowListConfig{admins, enableds}, + other: &AllowListConfig{admins, enableds}, expected: true, }, } diff --git a/precompile/contract/interfaces.go b/precompile/contract/interfaces.go index 75f48980a1..66a449fe91 100644 --- a/precompile/contract/interfaces.go +++ b/precompile/contract/interfaces.go @@ -9,7 +9,7 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ethereum/go-ethereum/common" ) @@ -69,10 +69,10 @@ type BlockContext interface { } type Configurator interface { - NewConfig() config.Config + NewConfig() precompileconfig.Config Configure( chainConfig ChainConfig, - precompileConfig config.Config, + precompileconfig precompileconfig.Config, state StateDB, blockContext BlockContext, ) error diff --git a/precompile/contract/utils.go b/precompile/contract/utils.go index 1718eb2dca..f4b7c7db9c 100644 --- a/precompile/contract/utils.go +++ b/precompile/contract/utils.go @@ -10,7 +10,7 @@ import ( "testing" "github.com/ava-labs/subnet-evm/accounts/abi" - "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -39,7 +39,7 @@ type PrecompileTest struct { // It should be the same precompile config that is used in the // precompile's configurator. // If nil, Configure on the Configurator will not be called. - Config config.Config + Config precompileconfig.Config // BeforeHook is called before the precompile is called. BeforeHook func(t *testing.T, state StateDB) // AfterHook is called after the precompile is called. diff --git a/precompile/contracts/deployerallowlist/config.go b/precompile/contracts/deployerallowlist/config.go index 4f624de1f6..c2d5bf28dd 100644 --- a/precompile/contracts/deployerallowlist/config.go +++ b/precompile/contracts/deployerallowlist/config.go @@ -7,28 +7,28 @@ import ( "math/big" "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ethereum/go-ethereum/common" ) -var _ config.Config = &Config{} +var _ precompileconfig.Config = &Config{} // Config contains the configuration for the ContractDeployerAllowList precompile, // consisting of the initial allowlist and the timestamp for the network upgrade. type Config struct { - allowlist.Config - config.Upgrade + allowlist.AllowListConfig + precompileconfig.Upgrade } // NewConfig returns a config for a network upgrade at [blockTimestamp] that enables // ContractDeployerAllowList with [admins] and [enableds] as members of the allowlist. func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address) *Config { return &Config{ - Config: allowlist.Config{ + AllowListConfig: allowlist.AllowListConfig{ AdminAddresses: admins, EnabledAddresses: enableds, }, - Upgrade: config.Upgrade{BlockTimestamp: blockTimestamp}, + Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp}, } } @@ -36,7 +36,7 @@ func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []comm // that disables ContractDeployerAllowList. func NewDisableConfig(blockTimestamp *big.Int) *Config { return &Config{ - Upgrade: config.Upgrade{ + Upgrade: precompileconfig.Upgrade{ BlockTimestamp: blockTimestamp, Disable: true, }, @@ -46,13 +46,13 @@ func NewDisableConfig(blockTimestamp *big.Int) *Config { func (*Config) Key() string { return ConfigKey } // Equal returns true if [cfg] is a [*ContractDeployerAllowListConfig] and it has been configured identical to [c]. -func (c *Config) Equal(cfg config.Config) bool { +func (c *Config) Equal(cfg precompileconfig.Config) bool { // typecast before comparison other, ok := (cfg).(*Config) if !ok { return false } - return c.Upgrade.Equal(&other.Upgrade) && c.Config.Equal(&other.Config) + return c.Upgrade.Equal(&other.Upgrade) && c.AllowListConfig.Equal(&other.AllowListConfig) } -func (c *Config) Verify() error { return c.Config.Verify() } +func (c *Config) Verify() error { return c.AllowListConfig.Verify() } diff --git a/precompile/contracts/deployerallowlist/config_test.go b/precompile/contracts/deployerallowlist/config_test.go index 46bba193d6..caa3d8995a 100644 --- a/precompile/contracts/deployerallowlist/config_test.go +++ b/precompile/contracts/deployerallowlist/config_test.go @@ -7,7 +7,7 @@ import ( "math/big" "testing" - "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -16,7 +16,7 @@ func TestVerifyContractDeployerConfig(t *testing.T) { admins := []common.Address{{1}} tests := []struct { name string - config config.Config + config precompileconfig.Config ExpectedError string }{ { @@ -44,8 +44,8 @@ func TestEqualContractDeployerAllowListConfig(t *testing.T) { enableds := []common.Address{{2}} tests := []struct { name string - config config.Config - other config.Config + config precompileconfig.Config + other precompileconfig.Config expected bool }{ { @@ -57,7 +57,7 @@ func TestEqualContractDeployerAllowListConfig(t *testing.T) { { name: "different type", config: NewConfig(big.NewInt(3), admins, enableds), - other: config.NewNoopStatefulPrecompileConfig(), + other: precompileconfig.NewNoopStatefulPrecompileConfig(), expected: false, }, { diff --git a/precompile/contracts/deployerallowlist/module.go b/precompile/contracts/deployerallowlist/module.go index 73bd720bed..623eb27e0f 100644 --- a/precompile/contracts/deployerallowlist/module.go +++ b/precompile/contracts/deployerallowlist/module.go @@ -6,9 +6,9 @@ package deployerallowlist import ( "fmt" - "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ethereum/go-ethereum/common" ) @@ -35,15 +35,15 @@ func init() { } } -func (*configurator) NewConfig() config.Config { +func (*configurator) NewConfig() precompileconfig.Config { return &Config{} } // Configure configures [state] with the given [cfg] config. -func (c *configurator) Configure(_ contract.ChainConfig, cfg config.Config, state contract.StateDB, _ contract.BlockContext) error { +func (c *configurator) Configure(_ contract.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, _ contract.BlockContext) error { config, ok := cfg.(*Config) if !ok { return fmt.Errorf("incorrect config %T: %v", config, config) } - return config.Config.Configure(state, ContractAddress) + return config.AllowListConfig.Configure(state, ContractAddress) } diff --git a/precompile/contracts/feemanager/config.go b/precompile/contracts/feemanager/config.go index 05d04991f5..9debfa7b1f 100644 --- a/precompile/contracts/feemanager/config.go +++ b/precompile/contracts/feemanager/config.go @@ -8,17 +8,17 @@ import ( "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ethereum/go-ethereum/common" ) -var _ config.Config = &Config{} +var _ precompileconfig.Config = &Config{} // Config implements the StatefulPrecompileConfig interface while adding in the // FeeManager specific precompile config. type Config struct { - allowlist.Config // Config for the fee config manager allow list - config.Upgrade + allowlist.AllowListConfig // Config for the fee config manager allow list + precompileconfig.Upgrade InitialFeeConfig *commontype.FeeConfig `json:"initialFeeConfig,omitempty"` // initial fee config to be immediately activated } @@ -27,11 +27,11 @@ type Config struct { // allowlist with [initialConfig] as initial fee config if specified. func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialConfig *commontype.FeeConfig) *Config { return &Config{ - Config: allowlist.Config{ + AllowListConfig: allowlist.AllowListConfig{ AdminAddresses: admins, EnabledAddresses: enableds, }, - Upgrade: config.Upgrade{BlockTimestamp: blockTimestamp}, + Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp}, InitialFeeConfig: initialConfig, } } @@ -40,7 +40,7 @@ func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []comm // that disables FeeManager. func NewDisableConfig(blockTimestamp *big.Int) *Config { return &Config{ - Upgrade: config.Upgrade{ + Upgrade: precompileconfig.Upgrade{ BlockTimestamp: blockTimestamp, Disable: true, }, @@ -50,13 +50,13 @@ func NewDisableConfig(blockTimestamp *big.Int) *Config { func (*Config) Key() string { return ConfigKey } // Equal returns true if [cfg] is a [*FeeManagerConfig] and it has been configured identical to [c]. -func (c *Config) Equal(cfg config.Config) bool { +func (c *Config) Equal(cfg precompileconfig.Config) bool { // typecast before comparison other, ok := (cfg).(*Config) if !ok { return false } - eq := c.Upgrade.Equal(&other.Upgrade) && c.Config.Equal(&other.Config) + eq := c.Upgrade.Equal(&other.Upgrade) && c.AllowListConfig.Equal(&other.AllowListConfig) if !eq { return false } @@ -69,7 +69,7 @@ func (c *Config) Equal(cfg config.Config) bool { } func (c *Config) Verify() error { - if err := c.Config.Verify(); err != nil { + if err := c.AllowListConfig.Verify(); err != nil { return err } if c.InitialFeeConfig == nil { diff --git a/precompile/contracts/feemanager/config_test.go b/precompile/contracts/feemanager/config_test.go index 41b1e1ff3d..4b5654d5d9 100644 --- a/precompile/contracts/feemanager/config_test.go +++ b/precompile/contracts/feemanager/config_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -32,7 +32,7 @@ func TestVerifyFeeManagerConfig(t *testing.T) { invalidFeeConfig.GasLimit = big.NewInt(0) tests := []struct { name string - config config.Config + config precompileconfig.Config ExpectedError string }{ { @@ -70,8 +70,8 @@ func TestEqualFeeManagerConfig(t *testing.T) { enableds := []common.Address{{2}} tests := []struct { name string - config config.Config - other config.Config + config precompileconfig.Config + other precompileconfig.Config expected bool }{ { @@ -83,7 +83,7 @@ func TestEqualFeeManagerConfig(t *testing.T) { { name: "different type", config: NewConfig(big.NewInt(3), admins, enableds, nil), - other: config.NewNoopStatefulPrecompileConfig(), + other: precompileconfig.NewNoopStatefulPrecompileConfig(), expected: false, }, { diff --git a/precompile/contracts/feemanager/module.go b/precompile/contracts/feemanager/module.go index a725bb3f3d..fa5cf5ccad 100644 --- a/precompile/contracts/feemanager/module.go +++ b/precompile/contracts/feemanager/module.go @@ -6,9 +6,9 @@ package feemanager import ( "fmt" - "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ethereum/go-ethereum/common" ) @@ -35,12 +35,12 @@ func init() { } } -func (*configurator) NewConfig() config.Config { +func (*configurator) NewConfig() precompileconfig.Config { return &Config{} } // Configure configures [state] with the desired admins based on [configIface]. -func (*configurator) Configure(chainConfig contract.ChainConfig, cfg config.Config, state contract.StateDB, blockContext contract.BlockContext) error { +func (*configurator) Configure(chainConfig contract.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.BlockContext) error { config, ok := cfg.(*Config) if !ok { return fmt.Errorf("incorrect config %T: %v", config, config) @@ -57,5 +57,5 @@ func (*configurator) Configure(chainConfig contract.ChainConfig, cfg config.Conf return fmt.Errorf("cannot configure fee config in chain config: %w", err) } } - return config.Config.Configure(state, ContractAddress) + return config.AllowListConfig.Configure(state, ContractAddress) } diff --git a/precompile/contracts/nativeminter/config.go b/precompile/contracts/nativeminter/config.go index 21278e8de6..e2df6bbc82 100644 --- a/precompile/contracts/nativeminter/config.go +++ b/precompile/contracts/nativeminter/config.go @@ -8,19 +8,19 @@ import ( "math/big" "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" ) -var _ config.Config = &Config{} +var _ precompileconfig.Config = &Config{} // Config implements the StatefulPrecompileConfig interface while adding in the // ContractNativeMinter specific precompile config. type Config struct { - allowlist.Config - config.Upgrade + allowlist.AllowListConfig + precompileconfig.Upgrade InitialMint map[common.Address]*math.HexOrDecimal256 `json:"initialMint,omitempty"` // addresses to receive the initial mint mapped to the amount to mint } @@ -28,11 +28,11 @@ type Config struct { // ContractNativeMinter with the given [admins] and [enableds] as members of the allowlist. Also mints balances according to [initialMint] when the upgrade activates. func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialMint map[common.Address]*math.HexOrDecimal256) *Config { return &Config{ - Config: allowlist.Config{ + AllowListConfig: allowlist.AllowListConfig{ AdminAddresses: admins, EnabledAddresses: enableds, }, - Upgrade: config.Upgrade{BlockTimestamp: blockTimestamp}, + Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp}, InitialMint: initialMint, } } @@ -41,7 +41,7 @@ func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []comm // that disables ContractNativeMinter. func NewDisableConfig(blockTimestamp *big.Int) *Config { return &Config{ - Upgrade: config.Upgrade{ + Upgrade: precompileconfig.Upgrade{ BlockTimestamp: blockTimestamp, Disable: true, }, @@ -50,13 +50,13 @@ func NewDisableConfig(blockTimestamp *big.Int) *Config { func (*Config) Key() string { return ConfigKey } // Equal returns true if [cfg] is a [*ContractNativeMinterConfig] and it has been configured identical to [c]. -func (c *Config) Equal(cfg config.Config) bool { +func (c *Config) Equal(cfg precompileconfig.Config) bool { // typecast before comparison other, ok := (cfg).(*Config) if !ok { return false } - eq := c.Upgrade.Equal(&other.Upgrade) && c.Config.Equal(&other.Config) + eq := c.Upgrade.Equal(&other.Upgrade) && c.AllowListConfig.Equal(&other.AllowListConfig) if !eq { return false } @@ -91,5 +91,5 @@ func (c *Config) Verify() error { return fmt.Errorf("initial mint cannot contain invalid amount %v for address %s", bigIntAmount, addr) } } - return c.Config.Verify() + return c.AllowListConfig.Verify() } diff --git a/precompile/contracts/nativeminter/config_test.go b/precompile/contracts/nativeminter/config_test.go index 08227a9be1..1927a93b9b 100644 --- a/precompile/contracts/nativeminter/config_test.go +++ b/precompile/contracts/nativeminter/config_test.go @@ -7,7 +7,7 @@ import ( "math/big" "testing" - "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/stretchr/testify/require" @@ -18,7 +18,7 @@ func TestVerifyContractNativeMinterConfig(t *testing.T) { enableds := []common.Address{{2}} tests := []struct { name string - config config.Config + config precompileconfig.Config ExpectedError string }{ { @@ -74,8 +74,8 @@ func TestEqualContractNativeMinterConfig(t *testing.T) { enableds := []common.Address{{2}} tests := []struct { name string - config config.Config - other config.Config + config precompileconfig.Config + other precompileconfig.Config expected bool }{ { @@ -87,7 +87,7 @@ func TestEqualContractNativeMinterConfig(t *testing.T) { { name: "different type", config: NewConfig(big.NewInt(3), admins, enableds, nil), - other: config.NewNoopStatefulPrecompileConfig(), + other: precompileconfig.NewNoopStatefulPrecompileConfig(), expected: false, }, { diff --git a/precompile/contracts/nativeminter/module.go b/precompile/contracts/nativeminter/module.go index 76987071cd..8648c1f119 100644 --- a/precompile/contracts/nativeminter/module.go +++ b/precompile/contracts/nativeminter/module.go @@ -7,9 +7,9 @@ import ( "fmt" "math/big" - "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ethereum/go-ethereum/common" ) @@ -36,12 +36,12 @@ func init() { } } -func (*configurator) NewConfig() config.Config { +func (*configurator) NewConfig() precompileconfig.Config { return &Config{} } // Configure configures [state] with the desired admins based on [cfg]. -func (*configurator) Configure(_ contract.ChainConfig, cfg config.Config, state contract.StateDB, _ contract.BlockContext) error { +func (*configurator) Configure(_ contract.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, _ contract.BlockContext) error { config, ok := cfg.(*Config) if !ok { return fmt.Errorf("incorrect config %T: %v", config, config) @@ -53,5 +53,5 @@ func (*configurator) Configure(_ contract.ChainConfig, cfg config.Config, state } } - return config.Config.Configure(state, ContractAddress) + return config.AllowListConfig.Configure(state, ContractAddress) } diff --git a/precompile/contracts/rewardmanager/config.go b/precompile/contracts/rewardmanager/config.go index 80adc93429..9b7a8bb238 100644 --- a/precompile/contracts/rewardmanager/config.go +++ b/precompile/contracts/rewardmanager/config.go @@ -10,12 +10,12 @@ import ( "math/big" "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ethereum/go-ethereum/common" ) -var _ config.Config = &Config{} +var _ precompileconfig.Config = &Config{} type InitialRewardConfig struct { AllowFeeRecipients bool `json:"allowFeeRecipients"` @@ -57,8 +57,8 @@ func (i *InitialRewardConfig) Configure(state contract.StateDB) error { // Config implements the StatefulPrecompileConfig interface while adding in the // RewardManager specific precompile config. type Config struct { - allowlist.Config - config.Upgrade + allowlist.AllowListConfig + precompileconfig.Upgrade InitialRewardConfig *InitialRewardConfig `json:"initialRewardConfig,omitempty"` } @@ -66,11 +66,11 @@ type Config struct { // RewardManager with the given [admins] and [enableds] as members of the allowlist with [initialConfig] as initial rewards config if specified. func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialConfig *InitialRewardConfig) *Config { return &Config{ - Config: allowlist.Config{ + AllowListConfig: allowlist.AllowListConfig{ AdminAddresses: admins, EnabledAddresses: enableds, }, - Upgrade: config.Upgrade{BlockTimestamp: blockTimestamp}, + Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp}, InitialRewardConfig: initialConfig, } } @@ -79,7 +79,7 @@ func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []comm // that disables RewardManager. func NewDisableConfig(blockTimestamp *big.Int) *Config { return &Config{ - Upgrade: config.Upgrade{ + Upgrade: precompileconfig.Upgrade{ BlockTimestamp: blockTimestamp, Disable: true, }, @@ -94,11 +94,11 @@ func (c *Config) Verify() error { return err } } - return c.Config.Verify() + return c.AllowListConfig.Verify() } // Equal returns true if [cfg] is a [*RewardManagerConfig] and it has been configured identical to [c]. -func (c *Config) Equal(cfg config.Config) bool { +func (c *Config) Equal(cfg precompileconfig.Config) bool { // typecast before comparison other, ok := (cfg).(*Config) if !ok { @@ -114,5 +114,5 @@ func (c *Config) Equal(cfg config.Config) bool { } } - return c.Upgrade.Equal(&other.Upgrade) && c.Config.Equal(&other.Config) + return c.Upgrade.Equal(&other.Upgrade) && c.AllowListConfig.Equal(&other.AllowListConfig) } diff --git a/precompile/contracts/rewardmanager/config_test.go b/precompile/contracts/rewardmanager/config_test.go index 249fb85a16..c78dec4c6f 100644 --- a/precompile/contracts/rewardmanager/config_test.go +++ b/precompile/contracts/rewardmanager/config_test.go @@ -7,7 +7,7 @@ import ( "math/big" "testing" - "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -17,7 +17,7 @@ func TestVerifyRewardManagerConfig(t *testing.T) { enableds := []common.Address{{2}} tests := []struct { name string - config config.Config + config precompileconfig.Config ExpectedError string }{ { @@ -53,8 +53,8 @@ func TestEqualRewardManagerConfig(t *testing.T) { enableds := []common.Address{{2}} tests := []struct { name string - config config.Config - other config.Config + config precompileconfig.Config + other precompileconfig.Config expected bool }{ { @@ -66,7 +66,7 @@ func TestEqualRewardManagerConfig(t *testing.T) { { name: "different type", config: NewConfig(big.NewInt(3), admins, enableds, nil), - other: config.NewNoopStatefulPrecompileConfig(), + other: precompileconfig.NewNoopStatefulPrecompileConfig(), expected: false, }, { diff --git a/precompile/contracts/rewardmanager/module.go b/precompile/contracts/rewardmanager/module.go index 83788648e5..57739af6c8 100644 --- a/precompile/contracts/rewardmanager/module.go +++ b/precompile/contracts/rewardmanager/module.go @@ -6,9 +6,9 @@ package rewardmanager import ( "fmt" - "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ethereum/go-ethereum/common" ) @@ -35,12 +35,12 @@ func init() { } } -func (*configurator) NewConfig() config.Config { +func (*configurator) NewConfig() precompileconfig.Config { return &Config{} } // Configure configures [state] with the initial state for the precompile. -func (*configurator) Configure(chainConfig contract.ChainConfig, cfg config.Config, state contract.StateDB, _ contract.BlockContext) error { +func (*configurator) Configure(chainConfig contract.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, _ contract.BlockContext) error { config, ok := cfg.(*Config) if !ok { return fmt.Errorf("incorrect config %T: %v", config, config) diff --git a/precompile/contracts/txallowlist/config.go b/precompile/contracts/txallowlist/config.go index 93042c46da..c6204b41c5 100644 --- a/precompile/contracts/txallowlist/config.go +++ b/precompile/contracts/txallowlist/config.go @@ -7,28 +7,28 @@ import ( "math/big" "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ethereum/go-ethereum/common" ) -var _ config.Config = &Config{} +var _ precompileconfig.Config = &Config{} // Config implements the StatefulPrecompileConfig interface while adding in the // TxAllowList specific precompile config. type Config struct { - allowlist.Config - config.Upgrade + allowlist.AllowListConfig + precompileconfig.Upgrade } // NewConfig returns a config for a network upgrade at [blockTimestamp] that enables // TxAllowList with the given [admins] and [enableds] as members of the allowlist. func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address) *Config { return &Config{ - Config: allowlist.Config{ + AllowListConfig: allowlist.AllowListConfig{ AdminAddresses: admins, EnabledAddresses: enableds, }, - Upgrade: config.Upgrade{BlockTimestamp: blockTimestamp}, + Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp}, } } @@ -36,7 +36,7 @@ func NewConfig(blockTimestamp *big.Int, admins []common.Address, enableds []comm // that disables TxAllowList. func NewDisableConfig(blockTimestamp *big.Int) *Config { return &Config{ - Upgrade: config.Upgrade{ + Upgrade: precompileconfig.Upgrade{ BlockTimestamp: blockTimestamp, Disable: true, }, @@ -46,11 +46,11 @@ func NewDisableConfig(blockTimestamp *big.Int) *Config { func (c *Config) Key() string { return ConfigKey } // Equal returns true if [cfg] is a [*TxAllowListConfig] and it has been configured identical to [c]. -func (c *Config) Equal(cfg config.Config) bool { +func (c *Config) Equal(cfg precompileconfig.Config) bool { // typecast before comparison other, ok := (cfg).(*Config) if !ok { return false } - return c.Upgrade.Equal(&other.Upgrade) && c.Config.Equal(&other.Config) + return c.Upgrade.Equal(&other.Upgrade) && c.AllowListConfig.Equal(&other.AllowListConfig) } diff --git a/precompile/contracts/txallowlist/config_test.go b/precompile/contracts/txallowlist/config_test.go index e6cf062414..54ab46ac34 100644 --- a/precompile/contracts/txallowlist/config_test.go +++ b/precompile/contracts/txallowlist/config_test.go @@ -7,7 +7,7 @@ import ( "math/big" "testing" - "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) @@ -17,7 +17,7 @@ func TestVerifyTxAllowlistConfig(t *testing.T) { enableds := []common.Address{{2}} tests := []struct { name string - config config.Config + config precompileconfig.Config ExpectedError string }{ { @@ -60,8 +60,8 @@ func TestEqualTxAllowListConfig(t *testing.T) { enableds := []common.Address{{2}} tests := []struct { name string - config config.Config - other config.Config + config precompileconfig.Config + other precompileconfig.Config expected bool }{ { diff --git a/precompile/contracts/txallowlist/module.go b/precompile/contracts/txallowlist/module.go index 0da9721eb6..b1ae6a096e 100644 --- a/precompile/contracts/txallowlist/module.go +++ b/precompile/contracts/txallowlist/module.go @@ -6,9 +6,9 @@ package txallowlist import ( "fmt" - "github.com/ava-labs/subnet-evm/precompile/config" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ethereum/go-ethereum/common" ) @@ -35,15 +35,15 @@ func init() { } } -func (*configurator) NewConfig() config.Config { +func (*configurator) NewConfig() precompileconfig.Config { return &Config{} } // Configure configures [state] with the initial state for the precompile. -func (*configurator) Configure(chainConfig contract.ChainConfig, cfg config.Config, state contract.StateDB, _ contract.BlockContext) error { +func (*configurator) Configure(chainConfig contract.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, _ contract.BlockContext) error { config, ok := cfg.(*Config) if !ok { return fmt.Errorf("incorrect config %T: %v", config, config) } - return config.Config.Configure(state, ContractAddress) + return config.AllowListConfig.Configure(state, ContractAddress) } diff --git a/precompile/config/config.go b/precompile/precompileconfig/config.go similarity index 97% rename from precompile/config/config.go rename to precompile/precompileconfig/config.go index 63ccf43dad..fcee72fc95 100644 --- a/precompile/config/config.go +++ b/precompile/precompileconfig/config.go @@ -2,7 +2,7 @@ // See the file LICENSE for licensing terms. // Defines the stateless interface for unmarshalling an arbitrary config of a precompile -package config +package precompileconfig import ( "math/big" diff --git a/precompile/config/mock_config.go b/precompile/precompileconfig/mock_config.go similarity index 97% rename from precompile/config/mock_config.go rename to precompile/precompileconfig/mock_config.go index cb1f1f9a91..8abca06f88 100644 --- a/precompile/config/mock_config.go +++ b/precompile/precompileconfig/mock_config.go @@ -2,8 +2,7 @@ // See the file LICENSE for licensing terms. // TODO: replace with gomock - -package config +package precompileconfig import ( "math/big" diff --git a/precompile/config/upgradeable.go b/precompile/precompileconfig/upgradeable.go similarity index 97% rename from precompile/config/upgradeable.go rename to precompile/precompileconfig/upgradeable.go index 66a4381694..3ad8c0498b 100644 --- a/precompile/config/upgradeable.go +++ b/precompile/precompileconfig/upgradeable.go @@ -1,7 +1,7 @@ // (c) 2022 Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package config +package precompileconfig import ( "math/big" From aa6842de1ef23bb8da0ea9821d0562a8b5793a04 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Sat, 18 Feb 2023 01:14:13 +0300 Subject: [PATCH 19/23] move blackhole check to module registerer (#523) * move blackhole check to module registerer * check blackhole first * add unit test * Add test case for registering module outside of reserved range --------- Co-authored-by: Aaron Buchwald --- core/vm/contracts.go | 4 ---- precompile/modules/registerer.go | 5 +++++ precompile/modules/registerer_test.go | 17 ++++++++++++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index bdeb189fb5..2d8ec6b79d 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -33,7 +33,6 @@ import ( "fmt" "math/big" - "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/precompile/modules" @@ -164,9 +163,6 @@ func init() { if _, ok := PrecompileAllNativeAddresses[address]; ok { panic(fmt.Errorf("precompile address collides with existing native address: %s", address)) } - if address == constants.BlackholeAddr { - panic(fmt.Errorf("cannot use address %s for stateful precompile - overlaps with blackhole address", address)) - } } } diff --git a/precompile/modules/registerer.go b/precompile/modules/registerer.go index 5de73ec657..3ab469ed06 100644 --- a/precompile/modules/registerer.go +++ b/precompile/modules/registerer.go @@ -7,6 +7,7 @@ import ( "fmt" "sort" + "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" ) @@ -47,6 +48,10 @@ func ReservedAddress(addr common.Address) bool { func RegisterModule(stm Module) error { address := stm.Address key := stm.ConfigKey + + if address == constants.BlackholeAddr { + return fmt.Errorf("address %s overlaps with blackhole address", address) + } if !ReservedAddress(address) { return fmt.Errorf("address %s not in a reserved range", address) } diff --git a/precompile/modules/registerer_test.go b/precompile/modules/registerer_test.go index b91e0b01ed..c0e4feb711 100644 --- a/precompile/modules/registerer_test.go +++ b/precompile/modules/registerer_test.go @@ -7,11 +7,12 @@ import ( "math/big" "testing" + "github.com/ava-labs/subnet-evm/constants" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) -func TestRegisterModule(t *testing.T) { +func TestInsertSortedByAddress(t *testing.T) { data := make([]Module, 0) // test that the module is registered in sorted order module1 := Module{ @@ -42,3 +43,17 @@ func TestRegisterModule(t *testing.T) { data = insertSortedByAddress(data, module2) require.Equal(t, []Module{module0, module1, module2, module3}, data) } + +func TestRegisterModuleInvalidAddresses(t *testing.T) { + // Test the blockhole address cannot be registered + m := Module{ + Address: constants.BlackholeAddr, + } + err := RegisterModule(m) + require.ErrorContains(t, err, "overlaps with blackhole address") + + // Test an address outside of the reserved ranges cannot be registered + m.Address = common.BigToAddress(big.NewInt(1)) + err = RegisterModule(m) + require.ErrorContains(t, err, "not in a reserved range") +} From 8ca09a390bec637e72d097cfa4790e974a94403f Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Fri, 17 Feb 2023 16:17:11 -0800 Subject: [PATCH 20/23] precompile: improve test structure (#517) * refactor precompile tests * minor improvements * nit * fix merge * rename package * pr comments * rm file * merge AllowListTests * pr comments * explicit BeforeHook * wspace * Mark TestTransactionIndices flaky --------- Co-authored-by: Aaron Buchwald --- commontype/fee_config_test.go | 39 +-- commontype/test_fee_config.go | 19 ++ core/blockchain_test.go | 1 + core/state/test_statedb.go | 20 ++ precompile/allowlist/allow_list_test.go | 283 ----------------- precompile/allowlist/allowlist_test.go | 58 ++++ precompile/allowlist/test_allowlist.go | 290 ++++++++++++++++++ precompile/contract/mock_interfaces.go | 16 + precompile/contract/utils.go | 28 -- .../deployerallowlist/contract_test.go | 192 +----------- .../contracts/feemanager/contract_test.go | 131 ++++---- .../contracts/nativeminter/contract_test.go | 146 ++++----- .../contracts/rewardmanager/contract_test.go | 193 +++++------- .../contracts/txallowlist/contract_test.go | 177 +---------- precompile/testutils/test_precompile.go | 86 ++++++ 15 files changed, 701 insertions(+), 978 deletions(-) create mode 100644 commontype/test_fee_config.go create mode 100644 core/state/test_statedb.go delete mode 100644 precompile/allowlist/allow_list_test.go create mode 100644 precompile/allowlist/allowlist_test.go create mode 100644 precompile/allowlist/test_allowlist.go create mode 100644 precompile/testutils/test_precompile.go diff --git a/commontype/fee_config_test.go b/commontype/fee_config_test.go index 997cb708e3..c0e26c4ced 100644 --- a/commontype/fee_config_test.go +++ b/commontype/fee_config_test.go @@ -10,19 +10,6 @@ import ( "github.com/stretchr/testify/require" ) -var validFeeConfig = FeeConfig{ - GasLimit: big.NewInt(8_000_000), - TargetBlockRate: 2, // in seconds - - MinBaseFee: big.NewInt(25_000_000_000), - TargetGas: big.NewInt(15_000_000), - BaseFeeChangeDenominator: big.NewInt(36), - - MinBlockGasCost: big.NewInt(0), - MaxBlockGasCost: big.NewInt(1_000_000), - BlockGasCostStep: big.NewInt(200_000), -} - func TestVerify(t *testing.T) { tests := []struct { name string @@ -47,43 +34,43 @@ func TestVerify(t *testing.T) { }, { name: "invalid GasLimit in FeeConfig", - config: func() *FeeConfig { c := validFeeConfig; c.GasLimit = big.NewInt(0); return &c }(), + config: func() *FeeConfig { c := ValidTestFeeConfig; c.GasLimit = big.NewInt(0); return &c }(), expectedError: "gasLimit = 0 cannot be less than or equal to 0", }, { name: "invalid TargetBlockRate in FeeConfig", - config: func() *FeeConfig { c := validFeeConfig; c.TargetBlockRate = 0; return &c }(), + config: func() *FeeConfig { c := ValidTestFeeConfig; c.TargetBlockRate = 0; return &c }(), expectedError: "targetBlockRate = 0 cannot be less than or equal to 0", }, { name: "invalid MinBaseFee in FeeConfig", - config: func() *FeeConfig { c := validFeeConfig; c.MinBaseFee = big.NewInt(-1); return &c }(), + config: func() *FeeConfig { c := ValidTestFeeConfig; c.MinBaseFee = big.NewInt(-1); return &c }(), expectedError: "minBaseFee = -1 cannot be less than 0", }, { name: "invalid TargetGas in FeeConfig", - config: func() *FeeConfig { c := validFeeConfig; c.TargetGas = big.NewInt(0); return &c }(), + config: func() *FeeConfig { c := ValidTestFeeConfig; c.TargetGas = big.NewInt(0); return &c }(), expectedError: "targetGas = 0 cannot be less than or equal to 0", }, { name: "invalid BaseFeeChangeDenominator in FeeConfig", - config: func() *FeeConfig { c := validFeeConfig; c.BaseFeeChangeDenominator = big.NewInt(0); return &c }(), + config: func() *FeeConfig { c := ValidTestFeeConfig; c.BaseFeeChangeDenominator = big.NewInt(0); return &c }(), expectedError: "baseFeeChangeDenominator = 0 cannot be less than or equal to 0", }, { name: "invalid MinBlockGasCost in FeeConfig", - config: func() *FeeConfig { c := validFeeConfig; c.MinBlockGasCost = big.NewInt(-1); return &c }(), + config: func() *FeeConfig { c := ValidTestFeeConfig; c.MinBlockGasCost = big.NewInt(-1); return &c }(), expectedError: "minBlockGasCost = -1 cannot be less than 0", }, { name: "valid FeeConfig", - config: &validFeeConfig, + config: &ValidTestFeeConfig, expectedError: "", }, { name: "MinBlockGasCost bigger than MaxBlockGasCost in FeeConfig", config: func() *FeeConfig { - c := validFeeConfig + c := ValidTestFeeConfig c.MinBlockGasCost = big.NewInt(2) c.MaxBlockGasCost = big.NewInt(1) return &c @@ -92,7 +79,7 @@ func TestVerify(t *testing.T) { }, { name: "invalid BlockGasCostStep in FeeConfig", - config: func() *FeeConfig { c := validFeeConfig; c.BlockGasCostStep = big.NewInt(-1); return &c }(), + config: func() *FeeConfig { c := ValidTestFeeConfig; c.BlockGasCostStep = big.NewInt(-1); return &c }(), expectedError: "blockGasCostStep = -1 cannot be less than 0", }, } @@ -119,7 +106,7 @@ func TestEqual(t *testing.T) { }{ { name: "equal", - a: &validFeeConfig, + a: &ValidTestFeeConfig, b: &FeeConfig{ GasLimit: big.NewInt(8_000_000), TargetBlockRate: 2, // in seconds @@ -136,13 +123,13 @@ func TestEqual(t *testing.T) { }, { name: "not equal", - a: &validFeeConfig, - b: func() *FeeConfig { c := validFeeConfig; c.GasLimit = big.NewInt(1); return &c }(), + a: &ValidTestFeeConfig, + b: func() *FeeConfig { c := ValidTestFeeConfig; c.GasLimit = big.NewInt(1); return &c }(), expected: false, }, { name: "not equal nil", - a: &validFeeConfig, + a: &ValidTestFeeConfig, b: nil, expected: false, }, diff --git a/commontype/test_fee_config.go b/commontype/test_fee_config.go new file mode 100644 index 0000000000..646f21d1cb --- /dev/null +++ b/commontype/test_fee_config.go @@ -0,0 +1,19 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package commontype + +import "math/big" + +var ValidTestFeeConfig = FeeConfig{ + GasLimit: big.NewInt(8_000_000), + TargetBlockRate: 2, // in seconds + + MinBaseFee: big.NewInt(25_000_000_000), + TargetGas: big.NewInt(15_000_000), + BaseFeeChangeDenominator: big.NewInt(36), + + MinBlockGasCost: big.NewInt(0), + MaxBlockGasCost: big.NewInt(1_000_000), + BlockGasCostStep: big.NewInt(200_000), +} diff --git a/core/blockchain_test.go b/core/blockchain_test.go index e4eb86c3d0..678c0dbe91 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -747,6 +747,7 @@ func TestCanonicalHashMarker(t *testing.T) { } func TestTransactionIndices(t *testing.T) { + t.Skip("FLAKY") // Configure and generate a sample block chain require := require.New(t) var ( diff --git a/core/state/test_statedb.go b/core/state/test_statedb.go new file mode 100644 index 0000000000..6dc1aa1065 --- /dev/null +++ b/core/state/test_statedb.go @@ -0,0 +1,20 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package state + +import ( + "testing" + + "github.com/ava-labs/subnet-evm/ethdb/memorydb" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func NewTestStateDB(t *testing.T) contract.StateDB { + db := memorydb.New() + stateDB, err := New(common.Hash{}, NewDatabase(db), nil) + require.NoError(t, err) + return stateDB +} diff --git a/precompile/allowlist/allow_list_test.go b/precompile/allowlist/allow_list_test.go deleted file mode 100644 index 6122a7c818..0000000000 --- a/precompile/allowlist/allow_list_test.go +++ /dev/null @@ -1,283 +0,0 @@ -// (c) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package allowlist - -import ( - "testing" - - "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/subnet-evm/core/rawdb" - "github.com/ava-labs/subnet-evm/core/state" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/vmerrs" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" -) - -func TestAllowListRun(t *testing.T) { - type test struct { - caller common.Address - input func() []byte - suppliedGas uint64 - readOnly bool - - expectedRes []byte - expectedErr string - - config *AllowListConfig - - assertState func(t *testing.T, state *state.StateDB) - } - - adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") - enabledAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") - noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") - dummyContractAddr := common.HexToAddress("0x0000000000000000000000000000000000000000") - testAllowListPrecompile := CreateAllowListPrecompile(dummyContractAddr) - - for name, test := range map[string]test{ - "set admin": { - caller: adminAddr, - input: func() []byte { - input, err := PackModifyAllowList(noRoleAddr, AdminRole) - require.NoError(t, err) - - return input - }, - suppliedGas: ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - res := GetAllowListStatus(state, dummyContractAddr, noRoleAddr) - require.Equal(t, AdminRole, res) - }, - }, - "set enabled": { - caller: adminAddr, - input: func() []byte { - input, err := PackModifyAllowList(noRoleAddr, EnabledRole) - require.NoError(t, err) - - return input - }, - suppliedGas: ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - res := GetAllowListStatus(state, dummyContractAddr, noRoleAddr) - require.Equal(t, EnabledRole, res) - }, - }, - "set no role": { - caller: adminAddr, - input: func() []byte { - input, err := PackModifyAllowList(enabledAddr, NoRole) - require.NoError(t, err) - - return input - }, - suppliedGas: ModifyAllowListGasCost, - readOnly: false, - expectedRes: []byte{}, - assertState: func(t *testing.T, state *state.StateDB) { - res := GetAllowListStatus(state, dummyContractAddr, enabledAddr) - require.Equal(t, NoRole, res) - }, - }, - "set no role from no role": { - caller: noRoleAddr, - input: func() []byte { - input, err := PackModifyAllowList(enabledAddr, NoRole) - require.NoError(t, err) - - return input - }, - suppliedGas: ModifyAllowListGasCost, - readOnly: false, - expectedErr: ErrCannotModifyAllowList.Error(), - }, - "set enabled from no role": { - caller: noRoleAddr, - input: func() []byte { - input, err := PackModifyAllowList(noRoleAddr, EnabledRole) - require.NoError(t, err) - - return input - }, - suppliedGas: ModifyAllowListGasCost, - readOnly: false, - expectedErr: ErrCannotModifyAllowList.Error(), - }, - "set admin from no role": { - caller: noRoleAddr, - input: func() []byte { - input, err := PackModifyAllowList(enabledAddr, AdminRole) - require.NoError(t, err) - - return input - }, - suppliedGas: ModifyAllowListGasCost, - readOnly: false, - expectedErr: ErrCannotModifyAllowList.Error(), - }, - "set no role from enabled": { - caller: enabledAddr, - input: func() []byte { - input, err := PackModifyAllowList(adminAddr, NoRole) - require.NoError(t, err) - - return input - }, - suppliedGas: ModifyAllowListGasCost, - readOnly: false, - expectedErr: ErrCannotModifyAllowList.Error(), - }, - "set enabled from enabled": { - caller: enabledAddr, - input: func() []byte { - input, err := PackModifyAllowList(noRoleAddr, EnabledRole) - require.NoError(t, err) - - return input - }, - suppliedGas: ModifyAllowListGasCost, - readOnly: false, - expectedErr: ErrCannotModifyAllowList.Error(), - }, - "set admin from enabled": { - caller: enabledAddr, - input: func() []byte { - input, err := PackModifyAllowList(noRoleAddr, AdminRole) - require.NoError(t, err) - - return input - }, - suppliedGas: ModifyAllowListGasCost, - readOnly: false, - expectedErr: ErrCannotModifyAllowList.Error(), - }, - "set no role with readOnly enabled": { - caller: adminAddr, - input: func() []byte { - input, err := PackModifyAllowList(enabledAddr, NoRole) - require.NoError(t, err) - - return input - }, - suppliedGas: ModifyAllowListGasCost, - readOnly: true, - expectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "set no role insufficient gas": { - caller: adminAddr, - input: func() []byte { - input, err := PackModifyAllowList(enabledAddr, NoRole) - require.NoError(t, err) - - return input - }, - suppliedGas: ModifyAllowListGasCost - 1, - readOnly: false, - expectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "read allow list no role": { - caller: noRoleAddr, - input: func() []byte { - return PackReadAllowList(noRoleAddr) - }, - suppliedGas: ReadAllowListGasCost, - readOnly: false, - expectedRes: common.Hash(NoRole).Bytes(), - assertState: nil, - }, - "read allow list admin role": { - caller: adminAddr, - input: func() []byte { - return PackReadAllowList(adminAddr) - }, - suppliedGas: ReadAllowListGasCost, - readOnly: false, - expectedRes: common.Hash(AdminRole).Bytes(), - assertState: nil, - }, - "read allow list with readOnly enabled": { - caller: adminAddr, - input: func() []byte { - return PackReadAllowList(noRoleAddr) - }, - suppliedGas: ReadAllowListGasCost, - readOnly: true, - expectedRes: common.Hash(NoRole).Bytes(), - assertState: nil, - }, - "read allow list out of gas": { - caller: adminAddr, - input: func() []byte { - return PackReadAllowList(noRoleAddr) - }, - suppliedGas: ReadAllowListGasCost - 1, - readOnly: true, - expectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "initial config sets admins": { - config: &AllowListConfig{ - AdminAddresses: []common.Address{noRoleAddr, enabledAddr}, - }, - suppliedGas: 0, - readOnly: false, - expectedErr: "", - assertState: func(t *testing.T, state *state.StateDB) { - require.Equal(t, AdminRole, GetAllowListStatus(state, dummyContractAddr, noRoleAddr)) - require.Equal(t, AdminRole, GetAllowListStatus(state, dummyContractAddr, enabledAddr)) - }, - }, - "initial config sets enabled": { - config: &AllowListConfig{ - EnabledAddresses: []common.Address{noRoleAddr, adminAddr}, - }, - suppliedGas: 0, - readOnly: false, - expectedErr: "", - assertState: func(t *testing.T, state *state.StateDB) { - require.Equal(t, EnabledRole, GetAllowListStatus(state, dummyContractAddr, adminAddr)) - require.Equal(t, EnabledRole, GetAllowListStatus(state, dummyContractAddr, noRoleAddr)) - }, - }, - } { - t.Run(name, func(t *testing.T) { - db := rawdb.NewMemoryDatabase() - state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) - require.NoError(t, err) - - // Set up the state so that each address has the expected permissions at the start. - SetAllowListRole(state, dummyContractAddr, adminAddr, AdminRole) - SetAllowListRole(state, dummyContractAddr, enabledAddr, EnabledRole) - require.Equal(t, AdminRole, GetAllowListStatus(state, dummyContractAddr, adminAddr)) - require.Equal(t, EnabledRole, GetAllowListStatus(state, dummyContractAddr, enabledAddr)) - - if test.config != nil { - test.config.Configure(state, dummyContractAddr) - } - - blockContext := contract.NewMockBlockContext(common.Big0, 0) - accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) - if test.input != nil { - ret, remainingGas, err := testAllowListPrecompile.Run(accesibleState, test.caller, dummyContractAddr, test.input(), test.suppliedGas, test.readOnly) - - if len(test.expectedErr) != 0 { - require.ErrorContains(t, err, test.expectedErr) - } else { - require.NoError(t, err) - } - - require.Equal(t, uint64(0), remainingGas) - require.Equal(t, test.expectedRes, ret) - } - - if test.assertState != nil { - test.assertState(t, state) - } - }) - } -} diff --git a/precompile/allowlist/allowlist_test.go b/precompile/allowlist/allowlist_test.go new file mode 100644 index 0000000000..4eeaf7eb3f --- /dev/null +++ b/precompile/allowlist/allowlist_test.go @@ -0,0 +1,58 @@ +// (c) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package allowlist + +import ( + "math/big" + "testing" + + "github.com/ava-labs/subnet-evm/core/state" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" + "github.com/ethereum/go-ethereum/common" +) + +var ( + _ precompileconfig.Config = &dummyConfig{} + _ contract.Configurator = &dummyConfigurator{} + + dummyAddr = common.Address{1} +) + +type dummyConfig struct { + *AllowListConfig +} + +func (d *dummyConfig) Key() string { return "dummy" } +func (d *dummyConfig) Timestamp() *big.Int { return common.Big0 } +func (d *dummyConfig) IsDisabled() bool { return false } +func (d *dummyConfig) Equal(other precompileconfig.Config) bool { + return d.AllowListConfig.Equal(other.(*dummyConfig).AllowListConfig) +} + +type dummyConfigurator struct{} + +func (d *dummyConfigurator) NewConfig() precompileconfig.Config { + return &dummyConfig{} +} + +func (d *dummyConfigurator) Configure( + chainConfig contract.ChainConfig, + precompileConfig precompileconfig.Config, + state contract.StateDB, + blockContext contract.BlockContext, +) error { + cfg := precompileConfig.(*dummyConfig) + return cfg.Configure(state, dummyAddr) +} + +func TestAllowListRun(t *testing.T) { + dummyModule := modules.Module{ + Address: dummyAddr, + Contract: CreateAllowListPrecompile(dummyAddr), + Configurator: &dummyConfigurator{}, + } + RunPrecompileWithAllowListTests(t, dummyModule, state.NewTestStateDB, nil) +} diff --git a/precompile/allowlist/test_allowlist.go b/precompile/allowlist/test_allowlist.go new file mode 100644 index 0000000000..5abb39d5b9 --- /dev/null +++ b/precompile/allowlist/test_allowlist.go @@ -0,0 +1,290 @@ +// (c) 2019-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package allowlist + +import ( + "encoding/json" + "testing" + + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" + "github.com/ava-labs/subnet-evm/precompile/testutils" + "github.com/ava-labs/subnet-evm/vmerrs" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +var ( + TestAdminAddr = common.HexToAddress("0x0000000000000000000000000000000000000011") + TestEnabledAddr = common.HexToAddress("0x0000000000000000000000000000000000000022") + TestNoRoleAddr = common.HexToAddress("0x0000000000000000000000000000000000000033") +) + +// mkConfigWithAllowList creates a new config with the correct type for [module] +// by marshalling [cfg] to JSON and then unmarshalling it into the config. +func mkConfigWithAllowList(module modules.Module, cfg *AllowListConfig) precompileconfig.Config { + jsonBytes, err := json.Marshal(cfg) + if err != nil { + panic(err) + } + + moduleCfg := module.NewConfig() + err = json.Unmarshal(jsonBytes, moduleCfg) + if err != nil { + panic(err) + } + + return moduleCfg +} + +func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { + contractAddress := module.Address + return map[string]testutils.PrecompileTest{ + "set admin": { + Caller: TestAdminAddr, + BeforeHook: SetDefaultRoles(contractAddress), + InputFn: func(t *testing.T) []byte { + input, err := PackModifyAllowList(TestNoRoleAddr, AdminRole) + require.NoError(t, err) + + return input + }, + SuppliedGas: ModifyAllowListGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t *testing.T, state contract.StateDB) { + res := GetAllowListStatus(state, contractAddress, TestNoRoleAddr) + require.Equal(t, AdminRole, res) + }, + }, + "set enabled": { + Caller: TestAdminAddr, + BeforeHook: SetDefaultRoles(contractAddress), + InputFn: func(t *testing.T) []byte { + input, err := PackModifyAllowList(TestNoRoleAddr, EnabledRole) + require.NoError(t, err) + + return input + }, + SuppliedGas: ModifyAllowListGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t *testing.T, state contract.StateDB) { + res := GetAllowListStatus(state, contractAddress, TestNoRoleAddr) + require.Equal(t, EnabledRole, res) + }, + }, + "set no role": { + Caller: TestAdminAddr, + BeforeHook: SetDefaultRoles(contractAddress), + InputFn: func(t *testing.T) []byte { + input, err := PackModifyAllowList(TestEnabledAddr, NoRole) + require.NoError(t, err) + + return input + }, + SuppliedGas: ModifyAllowListGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t *testing.T, state contract.StateDB) { + res := GetAllowListStatus(state, contractAddress, TestEnabledAddr) + require.Equal(t, NoRole, res) + }, + }, + "set no role from no role": { + Caller: TestNoRoleAddr, + BeforeHook: SetDefaultRoles(contractAddress), + InputFn: func(t *testing.T) []byte { + input, err := PackModifyAllowList(TestEnabledAddr, NoRole) + require.NoError(t, err) + + return input + }, + SuppliedGas: ModifyAllowListGasCost, + ReadOnly: false, + ExpectedErr: ErrCannotModifyAllowList.Error(), + }, + "set enabled from no role": { + Caller: TestNoRoleAddr, + BeforeHook: SetDefaultRoles(contractAddress), + InputFn: func(t *testing.T) []byte { + input, err := PackModifyAllowList(TestNoRoleAddr, EnabledRole) + require.NoError(t, err) + + return input + }, + SuppliedGas: ModifyAllowListGasCost, + ReadOnly: false, + ExpectedErr: ErrCannotModifyAllowList.Error(), + }, + "set admin from no role": { + Caller: TestNoRoleAddr, + BeforeHook: SetDefaultRoles(contractAddress), + InputFn: func(t *testing.T) []byte { + input, err := PackModifyAllowList(TestEnabledAddr, AdminRole) + require.NoError(t, err) + + return input + }, + SuppliedGas: ModifyAllowListGasCost, + ReadOnly: false, + ExpectedErr: ErrCannotModifyAllowList.Error(), + }, + "set no role from enabled": { + Caller: TestEnabledAddr, + BeforeHook: SetDefaultRoles(contractAddress), + InputFn: func(t *testing.T) []byte { + input, err := PackModifyAllowList(TestAdminAddr, NoRole) + require.NoError(t, err) + + return input + }, + SuppliedGas: ModifyAllowListGasCost, + ReadOnly: false, + ExpectedErr: ErrCannotModifyAllowList.Error(), + }, + "set enabled from enabled": { + Caller: TestEnabledAddr, + BeforeHook: SetDefaultRoles(contractAddress), + InputFn: func(t *testing.T) []byte { + input, err := PackModifyAllowList(TestNoRoleAddr, EnabledRole) + require.NoError(t, err) + + return input + }, + SuppliedGas: ModifyAllowListGasCost, + ReadOnly: false, + ExpectedErr: ErrCannotModifyAllowList.Error(), + }, + "set admin from enabled": { + Caller: TestEnabledAddr, + BeforeHook: SetDefaultRoles(contractAddress), + InputFn: func(t *testing.T) []byte { + input, err := PackModifyAllowList(TestNoRoleAddr, AdminRole) + require.NoError(t, err) + + return input + }, + SuppliedGas: ModifyAllowListGasCost, + ReadOnly: false, + ExpectedErr: ErrCannotModifyAllowList.Error(), + }, + "set no role with readOnly enabled": { + Caller: TestAdminAddr, + BeforeHook: SetDefaultRoles(contractAddress), + InputFn: func(t *testing.T) []byte { + input, err := PackModifyAllowList(TestEnabledAddr, NoRole) + require.NoError(t, err) + + return input + }, + SuppliedGas: ModifyAllowListGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "set no role insufficient gas": { + Caller: TestAdminAddr, + BeforeHook: SetDefaultRoles(contractAddress), + InputFn: func(t *testing.T) []byte { + input, err := PackModifyAllowList(TestEnabledAddr, NoRole) + require.NoError(t, err) + + return input + }, + SuppliedGas: ModifyAllowListGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "read allow list no role": { + Caller: TestNoRoleAddr, + BeforeHook: SetDefaultRoles(contractAddress), + Input: PackReadAllowList(TestNoRoleAddr), + SuppliedGas: ReadAllowListGasCost, + ReadOnly: false, + ExpectedRes: common.Hash(NoRole).Bytes(), + }, + "read allow list admin role": { + Caller: TestAdminAddr, + BeforeHook: SetDefaultRoles(contractAddress), + Input: PackReadAllowList(TestAdminAddr), + SuppliedGas: ReadAllowListGasCost, + ReadOnly: false, + ExpectedRes: common.Hash(AdminRole).Bytes(), + }, + "read allow list with readOnly enabled": { + Caller: TestAdminAddr, + BeforeHook: SetDefaultRoles(contractAddress), + Input: PackReadAllowList(TestNoRoleAddr), + SuppliedGas: ReadAllowListGasCost, + ReadOnly: true, + ExpectedRes: common.Hash(NoRole).Bytes(), + }, + "read allow list out of gas": { + Caller: TestAdminAddr, + BeforeHook: SetDefaultRoles(contractAddress), + Input: PackReadAllowList(TestNoRoleAddr), + SuppliedGas: ReadAllowListGasCost - 1, + ReadOnly: true, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "initial config sets admins": { + Config: mkConfigWithAllowList( + module, + &AllowListConfig{ + AdminAddresses: []common.Address{TestNoRoleAddr, TestEnabledAddr}, + }, + ), + SuppliedGas: 0, + ReadOnly: false, + AfterHook: func(t *testing.T, state contract.StateDB) { + require.Equal(t, AdminRole, GetAllowListStatus(state, contractAddress, TestNoRoleAddr)) + require.Equal(t, AdminRole, GetAllowListStatus(state, contractAddress, TestEnabledAddr)) + }, + }, + "initial config sets enabled": { + Config: mkConfigWithAllowList( + module, + &AllowListConfig{ + EnabledAddresses: []common.Address{TestNoRoleAddr, TestAdminAddr}, + }, + ), + SuppliedGas: 0, + ReadOnly: false, + AfterHook: func(t *testing.T, state contract.StateDB) { + require.Equal(t, EnabledRole, GetAllowListStatus(state, contractAddress, TestAdminAddr)) + require.Equal(t, EnabledRole, GetAllowListStatus(state, contractAddress, TestNoRoleAddr)) + }, + }, + } +} + +// SetDefaultRoles returns a BeforeHook that sets roles TestAdminAddr and TestEnabledAddr +// to have the AdminRole and EnabledRole respectively. +func SetDefaultRoles(contractAddress common.Address) func(t *testing.T, state contract.StateDB) { + return func(t *testing.T, state contract.StateDB) { + SetAllowListRole(state, contractAddress, TestAdminAddr, AdminRole) + SetAllowListRole(state, contractAddress, TestEnabledAddr, EnabledRole) + require.Equal(t, AdminRole, GetAllowListStatus(state, contractAddress, TestAdminAddr)) + require.Equal(t, EnabledRole, GetAllowListStatus(state, contractAddress, TestEnabledAddr)) + require.Equal(t, NoRole, GetAllowListStatus(state, contractAddress, TestNoRoleAddr)) + } +} + +func RunPrecompileWithAllowListTests(t *testing.T, module modules.Module, newStateDB func(t *testing.T) contract.StateDB, contractTests map[string]testutils.PrecompileTest) { + tests := AllowListTests(module) + // Add the contract specific tests to the map of tests to run. + for name, test := range contractTests { + if _, exists := tests[name]; exists { + t.Fatalf("duplicate test name: %s", name) + } + tests[name] = test + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + test.Run(t, module, newStateDB(t)) + }) + } +} diff --git a/precompile/contract/mock_interfaces.go b/precompile/contract/mock_interfaces.go index fe8cf5bf87..aa4a9dcfaa 100644 --- a/precompile/contract/mock_interfaces.go +++ b/precompile/contract/mock_interfaces.go @@ -7,6 +7,7 @@ import ( "math/big" "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/subnet-evm/commontype" "github.com/ethereum/go-ethereum/common" ) @@ -55,3 +56,18 @@ func (m *mockAccessibleState) GetSnowContext() *snow.Context { return m.snowCont func (m *mockAccessibleState) CallFromPrecompile(caller common.Address, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { return nil, 0, nil } + +type mockChainState struct { + feeConfig commontype.FeeConfig + allowedFeeRecipients bool +} + +func (m *mockChainState) GetFeeConfig() commontype.FeeConfig { return m.feeConfig } +func (m *mockChainState) AllowedFeeRecipients() bool { return m.allowedFeeRecipients } + +func NewMockChainState(feeConfig commontype.FeeConfig, allowedFeeRecipients bool) *mockChainState { + return &mockChainState{ + feeConfig: feeConfig, + allowedFeeRecipients: allowedFeeRecipients, + } +} diff --git a/precompile/contract/utils.go b/precompile/contract/utils.go index f4b7c7db9c..9cc50d3155 100644 --- a/precompile/contract/utils.go +++ b/precompile/contract/utils.go @@ -7,10 +7,8 @@ import ( "fmt" "regexp" "strings" - "testing" "github.com/ava-labs/subnet-evm/accounts/abi" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -24,32 +22,6 @@ const ( var functionSignatureRegex = regexp.MustCompile(`[\w]+\(((([\w]+)?)|((([\w]+),)+([\w]+)))\)`) -// PrecompileTest is a test case for a precompile -type PrecompileTest struct { - // Caller is the address of the precompile caller - Caller common.Address - // Input is the raw input bytes to the precompile - Input []byte - // SuppliedGas is the amount of gas supplied to the precompile - SuppliedGas uint64 - // ReadOnly is whether the precompile should be called in read only - // mode. If true, the precompile should not modify the state. - ReadOnly bool - // Config is the config to use for the precompile - // It should be the same precompile config that is used in the - // precompile's configurator. - // If nil, Configure on the Configurator will not be called. - Config precompileconfig.Config - // BeforeHook is called before the precompile is called. - BeforeHook func(t *testing.T, state StateDB) - // AfterHook is called after the precompile is called. - AfterHook func(t *testing.T, state StateDB) - // ExpectedRes is the expected raw byte result returned by the precompile - ExpectedRes []byte - // ExpectedErr is the expected error returned by the precompile - ExpectedErr string -} - // CalculateFunctionSelector returns the 4 byte function selector that results from [functionSignature] // Ex. the function setBalance(addr address, balance uint256) should be passed in as the string: // "setBalance(address,uint256)" diff --git a/precompile/contracts/deployerallowlist/contract_test.go b/precompile/contracts/deployerallowlist/contract_test.go index f2b4a43138..ba144fd155 100644 --- a/precompile/contracts/deployerallowlist/contract_test.go +++ b/precompile/contracts/deployerallowlist/contract_test.go @@ -4,202 +4,12 @@ package deployerallowlist import ( - "math/big" "testing" - "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/vmerrs" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" ) func TestContractDeployerAllowListRun(t *testing.T) { - adminAddr := common.BigToAddress(big.NewInt(1)) - enabledAddr := common.BigToAddress(big.NewInt(2)) - noRoleAddr := common.BigToAddress(big.NewInt(3)) - - for name, test := range map[string]contract.PrecompileTest{ - "set admin": { - Caller: adminAddr, - Input: func(tt *testing.T) []byte { - input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AdminRole) - require.NoError(tt, err) - - return input - }(t), - SuppliedGas: allowlist.ModifyAllowListGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t *testing.T, state contract.StateDB) { - res := GetContractDeployerAllowListStatus(state, noRoleAddr) - require.Equal(t, allowlist.AdminRole, res) - }, - }, - "set deployer": { - Caller: adminAddr, - Input: func(tt *testing.T) []byte { - input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.EnabledRole) - require.NoError(tt, err) - - return input - }(t), - SuppliedGas: allowlist.ModifyAllowListGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t *testing.T, state contract.StateDB) { - res := GetContractDeployerAllowListStatus(state, noRoleAddr) - require.Equal(t, allowlist.EnabledRole, res) - }, - }, - "set no role": { - Caller: adminAddr, - Input: func(tt *testing.T) []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.NoRole) - require.NoError(tt, err) - - return input - }(t), - SuppliedGas: allowlist.ModifyAllowListGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t *testing.T, state contract.StateDB) { - res := GetContractDeployerAllowListStatus(state, adminAddr) - require.Equal(t, allowlist.NoRole, res) - }, - }, - "set no role from enabled": { - Caller: enabledAddr, - Input: func(tt *testing.T) []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.NoRole) - require.NoError(tt, err) - - return input - }(t), - SuppliedGas: allowlist.ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: allowlist.ErrCannotModifyAllowList.Error(), - }, - "set no role from non-admin": { - Caller: noRoleAddr, - Input: func(tt *testing.T) []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) - require.NoError(tt, err) - - return input - }(t), - SuppliedGas: allowlist.ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: allowlist.ErrCannotModifyAllowList.Error(), - }, - "set deployer from non-admin": { - Caller: noRoleAddr, - Input: func(tt *testing.T) []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) - require.NoError(tt, err) - - return input - }(t), - SuppliedGas: allowlist.ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: allowlist.ErrCannotModifyAllowList.Error(), - }, - "set admin from non-admin": { - Caller: noRoleAddr, - Input: func(tt *testing.T) []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AdminRole) - require.NoError(tt, err) - - return input - }(t), - SuppliedGas: allowlist.ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: allowlist.ErrCannotModifyAllowList.Error(), - }, - "set no role with readOnly enabled": { - Caller: adminAddr, - Input: func(tt *testing.T) []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) - require.NoError(tt, err) - - return input - }(t), - SuppliedGas: allowlist.ModifyAllowListGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "set no role insufficient gas": { - Caller: adminAddr, - Input: func(tt *testing.T) []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) - require.NoError(tt, err) - - return input - }(t), - SuppliedGas: allowlist.ModifyAllowListGasCost - 1, - ReadOnly: false, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "read allow list no role": { - Caller: noRoleAddr, - Input: allowlist.PackReadAllowList(enabledAddr), - SuppliedGas: allowlist.ReadAllowListGasCost, - ReadOnly: false, - ExpectedRes: common.Hash(allowlist.EnabledRole).Bytes(), - }, - "read allow list admin role": { - Caller: adminAddr, - Input: allowlist.PackReadAllowList(enabledAddr), - SuppliedGas: allowlist.ReadAllowListGasCost, - ReadOnly: false, - ExpectedRes: common.Hash(allowlist.EnabledRole).Bytes(), - }, - "read allow list with readOnly enabled": { - Caller: adminAddr, - Input: allowlist.PackReadAllowList(enabledAddr), - SuppliedGas: allowlist.ReadAllowListGasCost, - ReadOnly: true, - ExpectedRes: common.Hash(allowlist.EnabledRole).Bytes(), - }, - "read allow list out of gas": { - Caller: adminAddr, - Input: allowlist.PackReadAllowList(noRoleAddr), - SuppliedGas: allowlist.ReadAllowListGasCost - 1, - ReadOnly: true, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), - }, - } { - t.Run(name, func(t *testing.T) { - db := rawdb.NewMemoryDatabase() - state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) - require.NoError(t, err) - - // Set up the state so that each address has the expected permissions at the start. - SetContractDeployerAllowListStatus(state, adminAddr, allowlist.AdminRole) - SetContractDeployerAllowListStatus(state, enabledAddr, allowlist.EnabledRole) - SetContractDeployerAllowListStatus(state, noRoleAddr, allowlist.NoRole) - require.Equal(t, allowlist.AdminRole, GetContractDeployerAllowListStatus(state, adminAddr)) - require.Equal(t, allowlist.EnabledRole, GetContractDeployerAllowListStatus(state, enabledAddr)) - require.Equal(t, allowlist.NoRole, GetContractDeployerAllowListStatus(state, noRoleAddr)) - - blockContext := contract.NewMockBlockContext(common.Big0, 0) - accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) - ret, remainingGas, err := ContractDeployerAllowListPrecompile.Run(accesibleState, test.Caller, ContractAddress, test.Input, test.SuppliedGas, test.ReadOnly) - if len(test.ExpectedErr) != 0 { - require.ErrorContains(t, err, test.ExpectedErr) - } else { - require.NoError(t, err) - } - - require.Equal(t, uint64(0), remainingGas) - require.Equal(t, test.ExpectedRes, ret) - - if test.AfterHook != nil { - test.AfterHook(t, state) - } - }) - } + allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, nil) } diff --git a/precompile/contracts/feemanager/contract_test.go b/precompile/contracts/feemanager/contract_test.go index 35e7fe89f3..4911a2368e 100644 --- a/precompile/contracts/feemanager/contract_test.go +++ b/precompile/contracts/feemanager/contract_test.go @@ -7,12 +7,11 @@ import ( "math/big" "testing" - "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/testutils" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -31,34 +30,31 @@ var testFeeConfig = commontype.FeeConfig{ BlockGasCostStep: big.NewInt(200_000), } -func TestFeeManagerRun(t *testing.T) { +func TestFeeManager(t *testing.T) { testBlockNumber := big.NewInt(7) - - adminAddr := common.BigToAddress(common.Big0) - enabledAddr := common.BigToAddress(common.Big1) - noRoleAddr := common.BigToAddress(common.Big2) - - for name, test := range map[string]contract.PrecompileTest{ + tests := map[string]testutils.PrecompileTest{ "set config from no role fails": { - Caller: noRoleAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: SetFeeConfigGasCost, ReadOnly: false, ExpectedErr: ErrCannotChangeFee.Error(), }, "set config from enabled address": { - Caller: enabledAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: SetFeeConfigGasCost, ReadOnly: false, ExpectedRes: []byte{}, @@ -68,15 +64,16 @@ func TestFeeManagerRun(t *testing.T) { }, }, "set invalid config from enabled address": { - Caller: enabledAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { feeConfig := testFeeConfig feeConfig.MinBlockGasCost = new(big.Int).Mul(feeConfig.MaxBlockGasCost, common.Big2) input, err := PackSetFeeConfig(feeConfig) - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: SetFeeConfigGasCost, ReadOnly: false, Config: &Config{ @@ -89,16 +86,18 @@ func TestFeeManagerRun(t *testing.T) { }, }, "set config from admin address": { - Caller: adminAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestAdminAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: SetFeeConfigGasCost, ReadOnly: false, ExpectedRes: []byte{}, + BlockNumber: testBlockNumber.Int64(), AfterHook: func(t *testing.T, state contract.StateDB) { feeConfig := GetStoredFeeConfig(state) require.Equal(t, testFeeConfig, feeConfig) @@ -107,8 +106,9 @@ func TestFeeManagerRun(t *testing.T) { }, }, "get fee config from non-enabled address": { - Caller: noRoleAddr, + Caller: allowlist.TestNoRoleAddr, BeforeHook: func(t *testing.T, state contract.StateDB) { + allowlist.SetDefaultRoles(Module.Address)(t, state) err := StoreFeeConfig(state, testFeeConfig, contract.NewMockBlockContext(big.NewInt(6), 0)) require.NoError(t, err) }, @@ -128,7 +128,8 @@ func TestFeeManagerRun(t *testing.T) { }, }, "get initial fee config": { - Caller: noRoleAddr, + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), Input: PackGetFeeConfigInput(), SuppliedGas: GetFeeConfigGasCost, Config: &Config{ @@ -140,6 +141,7 @@ func TestFeeManagerRun(t *testing.T) { require.NoError(t, err) return res }(), + BlockNumber: testBlockNumber.Int64(), AfterHook: func(t *testing.T, state contract.StateDB) { feeConfig := GetStoredFeeConfig(state) lastChangedAt := GetFeeConfigLastChangedAt(state) @@ -148,8 +150,9 @@ func TestFeeManagerRun(t *testing.T) { }, }, "get last changed at from non-enabled address": { - Caller: noRoleAddr, + Caller: allowlist.TestNoRoleAddr, BeforeHook: func(t *testing.T, state contract.StateDB) { + allowlist.SetDefaultRoles(Module.Address)(t, state) err := StoreFeeConfig(state, testFeeConfig, contract.NewMockBlockContext(testBlockNumber, 0)) require.NoError(t, err) }, @@ -165,86 +168,58 @@ func TestFeeManagerRun(t *testing.T) { }, }, "readOnly setFeeConfig with noRole fails": { - Caller: noRoleAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: SetFeeConfigGasCost, ReadOnly: true, ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly setFeeConfig with allow role fails": { - Caller: enabledAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: SetFeeConfigGasCost, ReadOnly: true, ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly setFeeConfig with admin role fails": { - Caller: adminAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestAdminAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: SetFeeConfigGasCost, ReadOnly: true, ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "insufficient gas setFeeConfig from admin": { - Caller: adminAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestAdminAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: SetFeeConfigGasCost - 1, ReadOnly: false, ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, - } { - t.Run(name, func(t *testing.T) { - db := rawdb.NewMemoryDatabase() - state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) - require.NoError(t, err) - - // Set up the state so that each address has the expected permissions at the start. - SetFeeManagerStatus(state, adminAddr, allowlist.AdminRole) - SetFeeManagerStatus(state, enabledAddr, allowlist.EnabledRole) - require.Equal(t, allowlist.AdminRole, GetFeeManagerStatus(state, adminAddr)) - require.Equal(t, allowlist.EnabledRole, GetFeeManagerStatus(state, enabledAddr)) - - if test.BeforeHook != nil { - test.BeforeHook(t, state) - } - blockContext := contract.NewMockBlockContext(testBlockNumber, 0) - accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) - if test.Config != nil { - Module.Configure(nil, test.Config, state, blockContext) - } - ret, remainingGas, err := FeeManagerPrecompile.Run(accesibleState, test.Caller, ContractAddress, test.Input, test.SuppliedGas, test.ReadOnly) - if len(test.ExpectedErr) != 0 { - require.ErrorContains(t, err, test.ExpectedErr) - } else { - require.NoError(t, err) - } - - require.Equal(t, uint64(0), remainingGas) - require.Equal(t, test.ExpectedRes, ret) - - if test.AfterHook != nil { - test.AfterHook(t, state) - } - }) } + + allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests) } diff --git a/precompile/contracts/nativeminter/contract_test.go b/precompile/contracts/nativeminter/contract_test.go index e00ccf759b..a987dc9998 100644 --- a/precompile/contracts/nativeminter/contract_test.go +++ b/precompile/contracts/nativeminter/contract_test.go @@ -6,12 +6,10 @@ package nativeminter import ( "testing" - "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" - "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/testutils" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" @@ -19,159 +17,133 @@ import ( ) func TestContractNativeMinterRun(t *testing.T) { - adminAddr := common.BigToAddress(common.Big0) - enabledAddr := common.BigToAddress(common.Big1) - noRoleAddr := common.BigToAddress(common.Big2) - - for name, test := range map[string]contract.PrecompileTest{ + tests := map[string]testutils.PrecompileTest{ "mint funds from no role fails": { - Caller: noRoleAddr, - Input: func(tt *testing.T) []byte { - input, err := PackMintInput(noRoleAddr, common.Big1) - require.NoError(tt, err) + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { + input, err := PackMintInput(allowlist.TestNoRoleAddr, common.Big1) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: MintGasCost, ReadOnly: false, ExpectedErr: ErrCannotMint.Error(), }, "mint funds from enabled address": { - Caller: enabledAddr, - Input: func(tt *testing.T) []byte { - input, err := PackMintInput(enabledAddr, common.Big1) - require.NoError(tt, err) + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { + input, err := PackMintInput(allowlist.TestEnabledAddr, common.Big1) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: MintGasCost, ReadOnly: false, ExpectedRes: []byte{}, AfterHook: func(t *testing.T, state contract.StateDB) { - require.Equal(t, common.Big1, state.GetBalance(enabledAddr), "expected minted funds") + require.Equal(t, common.Big1, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") }, }, "initial mint funds": { - Caller: enabledAddr, + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), Config: &Config{ InitialMint: map[common.Address]*math.HexOrDecimal256{ - enabledAddr: math.NewHexOrDecimal256(2), + allowlist.TestEnabledAddr: math.NewHexOrDecimal256(2), }, }, AfterHook: func(t *testing.T, state contract.StateDB) { - require.Equal(t, common.Big2, state.GetBalance(enabledAddr), "expected minted funds") + require.Equal(t, common.Big2, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") }, }, "mint funds from admin address": { - Caller: adminAddr, - Input: func(tt *testing.T) []byte { - input, err := PackMintInput(adminAddr, common.Big1) - require.NoError(tt, err) + Caller: allowlist.TestAdminAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { + input, err := PackMintInput(allowlist.TestAdminAddr, common.Big1) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: MintGasCost, ReadOnly: false, ExpectedRes: []byte{}, AfterHook: func(t *testing.T, state contract.StateDB) { - require.Equal(t, common.Big1, state.GetBalance(adminAddr), "expected minted funds") + require.Equal(t, common.Big1, state.GetBalance(allowlist.TestAdminAddr), "expected minted funds") }, }, "mint max big funds": { - Caller: adminAddr, - Input: func(tt *testing.T) []byte { - input, err := PackMintInput(adminAddr, math.MaxBig256) - require.NoError(tt, err) + Caller: allowlist.TestAdminAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { + input, err := PackMintInput(allowlist.TestAdminAddr, math.MaxBig256) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: MintGasCost, ReadOnly: false, ExpectedRes: []byte{}, AfterHook: func(t *testing.T, state contract.StateDB) { - require.Equal(t, math.MaxBig256, state.GetBalance(adminAddr), "expected minted funds") + require.Equal(t, math.MaxBig256, state.GetBalance(allowlist.TestAdminAddr), "expected minted funds") }, }, "readOnly mint with noRole fails": { - Caller: noRoleAddr, - Input: func(tt *testing.T) []byte { - input, err := PackMintInput(adminAddr, common.Big1) - require.NoError(tt, err) + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { + input, err := PackMintInput(allowlist.TestAdminAddr, common.Big1) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: MintGasCost, ReadOnly: true, ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly mint with allow role fails": { - Caller: enabledAddr, - Input: func(tt *testing.T) []byte { - input, err := PackMintInput(enabledAddr, common.Big1) - require.NoError(tt, err) + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { + input, err := PackMintInput(allowlist.TestEnabledAddr, common.Big1) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: MintGasCost, ReadOnly: true, ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly mint with admin role fails": { - Caller: adminAddr, - Input: func(tt *testing.T) []byte { - input, err := PackMintInput(adminAddr, common.Big1) - require.NoError(tt, err) + Caller: allowlist.TestAdminAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { + input, err := PackMintInput(allowlist.TestAdminAddr, common.Big1) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: MintGasCost, ReadOnly: true, ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "insufficient gas mint from admin": { - Caller: adminAddr, - Input: func(tt *testing.T) []byte { - input, err := PackMintInput(enabledAddr, common.Big1) - require.NoError(tt, err) + Caller: allowlist.TestAdminAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { + input, err := PackMintInput(allowlist.TestEnabledAddr, common.Big1) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: MintGasCost - 1, ReadOnly: false, ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, - } { - t.Run(name, func(t *testing.T) { - db := rawdb.NewMemoryDatabase() - state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) - require.NoError(t, err) - - // Set up the state so that each address has the expected permissions at the start. - SetContractNativeMinterStatus(state, adminAddr, allowlist.AdminRole) - SetContractNativeMinterStatus(state, enabledAddr, allowlist.EnabledRole) - require.Equal(t, allowlist.AdminRole, GetContractNativeMinterStatus(state, adminAddr)) - require.Equal(t, allowlist.EnabledRole, GetContractNativeMinterStatus(state, enabledAddr)) - - blockContext := contract.NewMockBlockContext(common.Big0, 0) - accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) - if test.Config != nil { - Module.Configure(params.TestChainConfig, test.Config, state, blockContext) - } - if test.Input != nil { - ret, remainingGas, err := ContractNativeMinterPrecompile.Run(accesibleState, test.Caller, ContractAddress, test.Input, test.SuppliedGas, test.ReadOnly) - if len(test.ExpectedErr) != 0 { - require.ErrorContains(t, err, test.ExpectedErr) - } else { - require.NoError(t, err) - } - - require.Equal(t, uint64(0), remainingGas) - require.Equal(t, test.ExpectedRes, ret) - } - - if test.AfterHook != nil { - test.AfterHook(t, state) - } - }) } + + allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests) } diff --git a/precompile/contracts/rewardmanager/contract_test.go b/precompile/contracts/rewardmanager/contract_test.go index bcd0c7474b..db1d61d3ae 100644 --- a/precompile/contracts/rewardmanager/contract_test.go +++ b/precompile/contracts/rewardmanager/contract_test.go @@ -4,73 +4,70 @@ package rewardmanager import ( - "math/big" "testing" - "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/subnet-evm/constants" - "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/allowlist" "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/testutils" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) -var testBlockNumber = big.NewInt(7) - func TestRewardManagerRun(t *testing.T) { - adminAddr := common.BigToAddress(common.Big0) - enabledAddr := common.BigToAddress(common.Big1) - noRoleAddr := common.BigToAddress(common.Big2) testAddr := common.HexToAddress("0x0123") - for name, test := range map[string]contract.PrecompileTest{ + tests := map[string]testutils.PrecompileTest{ "set allow fee recipients from no role fails": { - Caller: noRoleAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackAllowFeeRecipients() - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: AllowFeeRecipientsGasCost, ReadOnly: false, ExpectedErr: ErrCannotAllowFeeRecipients.Error(), }, "set reward address from no role fails": { - Caller: noRoleAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackSetRewardAddress(testAddr) - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: SetRewardAddressGasCost, ReadOnly: false, ExpectedErr: ErrCannotSetRewardAddress.Error(), }, "disable rewards from no role fails": { - Caller: noRoleAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackDisableRewards() - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: DisableRewardsGasCost, ReadOnly: false, ExpectedErr: ErrCannotDisableRewards.Error(), }, "set allow fee recipients from enabled succeeds": { - Caller: enabledAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackAllowFeeRecipients() - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: AllowFeeRecipientsGasCost, ReadOnly: false, ExpectedRes: []byte{}, @@ -80,13 +77,14 @@ func TestRewardManagerRun(t *testing.T) { }, }, "set reward address from enabled succeeds": { - Caller: enabledAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackSetRewardAddress(testAddr) - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: SetRewardAddressGasCost, ReadOnly: false, ExpectedRes: []byte{}, @@ -97,13 +95,14 @@ func TestRewardManagerRun(t *testing.T) { }, }, "disable rewards from enabled succeeds": { - Caller: enabledAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackDisableRewards() - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: DisableRewardsGasCost, ReadOnly: false, ExpectedRes: []byte{}, @@ -114,16 +113,17 @@ func TestRewardManagerRun(t *testing.T) { }, }, "get current reward address from no role succeeds": { - Caller: noRoleAddr, + Caller: allowlist.TestNoRoleAddr, BeforeHook: func(t *testing.T, state contract.StateDB) { + allowlist.SetDefaultRoles(Module.Address)(t, state) StoreRewardAddress(state, testAddr) }, - Input: func(tt *testing.T) []byte { + InputFn: func(t *testing.T) []byte { input, err := PackCurrentRewardAddress() - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: CurrentRewardAddressGasCost, ReadOnly: false, ExpectedRes: func() []byte { @@ -133,15 +133,16 @@ func TestRewardManagerRun(t *testing.T) { }(), }, "get are fee recipients allowed from no role succeeds": { - Caller: noRoleAddr, + Caller: allowlist.TestNoRoleAddr, BeforeHook: func(t *testing.T, state contract.StateDB) { + allowlist.SetDefaultRoles(Module.Address)(t, state) EnableAllowFeeRecipients(state) }, - Input: func(tt *testing.T) []byte { + InputFn: func(t *testing.T) []byte { input, err := PackAreFeeRecipientsAllowed() - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: AreFeeRecipientsAllowedGasCost, ReadOnly: false, ExpectedRes: func() []byte { @@ -151,12 +152,13 @@ func TestRewardManagerRun(t *testing.T) { }(), }, "get initial config with address": { - Caller: noRoleAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackCurrentRewardAddress() - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: CurrentRewardAddressGasCost, Config: &Config{ InitialRewardConfig: &InitialRewardConfig{ @@ -171,12 +173,13 @@ func TestRewardManagerRun(t *testing.T) { }(), }, "get initial config with allow fee recipients enabled": { - Caller: noRoleAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackAreFeeRecipientsAllowed() - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: AreFeeRecipientsAllowedGasCost, Config: &Config{ InitialRewardConfig: &InitialRewardConfig{ @@ -191,112 +194,84 @@ func TestRewardManagerRun(t *testing.T) { }(), }, "readOnly allow fee recipients with allowed role fails": { - Caller: enabledAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackAllowFeeRecipients() - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: AllowFeeRecipientsGasCost, ReadOnly: true, ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly set reward addresss with allowed role fails": { - Caller: enabledAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackSetRewardAddress(testAddr) - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: SetRewardAddressGasCost, ReadOnly: true, ExpectedErr: vmerrs.ErrWriteProtection.Error(), }, "insufficient gas set reward address from allowed role": { - Caller: enabledAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackSetRewardAddress(testAddr) - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: SetRewardAddressGasCost - 1, ReadOnly: false, ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, "insufficient gas allow fee recipients from allowed role": { - Caller: enabledAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackAllowFeeRecipients() - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: AllowFeeRecipientsGasCost - 1, ReadOnly: false, ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, "insufficient gas read current reward address from allowed role": { - Caller: enabledAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackCurrentRewardAddress() - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: CurrentRewardAddressGasCost - 1, ReadOnly: false, ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, "insufficient gas are fee recipients allowed from allowed role": { - Caller: enabledAddr, - Input: func(tt *testing.T) []byte { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t *testing.T) []byte { input, err := PackAreFeeRecipientsAllowed() - require.NoError(tt, err) + require.NoError(t, err) return input - }(t), + }, SuppliedGas: AreFeeRecipientsAllowedGasCost - 1, ReadOnly: false, ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, - } { - t.Run(name, func(t *testing.T) { - db := rawdb.NewMemoryDatabase() - state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) - require.NoError(t, err) - - // Set up the state so that each address has the expected permissions at the start. - SetRewardManagerAllowListStatus(state, adminAddr, allowlist.AdminRole) - SetRewardManagerAllowListStatus(state, enabledAddr, allowlist.EnabledRole) - require.Equal(t, allowlist.AdminRole, GetRewardManagerAllowListStatus(state, adminAddr)) - require.Equal(t, allowlist.EnabledRole, GetRewardManagerAllowListStatus(state, enabledAddr)) - - if test.BeforeHook != nil { - test.BeforeHook(t, state) - } - - blockContext := contract.NewMockBlockContext(testBlockNumber, 0) - accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) - - if test.Config != nil { - Module.Configure(nil, test.Config, state, blockContext) - } - ret, remainingGas, err := RewardManagerPrecompile.Run(accesibleState, test.Caller, ContractAddress, test.Input, test.SuppliedGas, test.ReadOnly) - if len(test.ExpectedErr) != 0 { - require.ErrorContains(t, err, test.ExpectedErr) - } else { - require.NoError(t, err) - } - - require.Equal(t, uint64(0), remainingGas) - require.Equal(t, test.ExpectedRes, ret) - - if test.AfterHook != nil { - test.AfterHook(t, state) - } - }) } + + allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests) } diff --git a/precompile/contracts/txallowlist/contract_test.go b/precompile/contracts/txallowlist/contract_test.go index 6e7f678523..08104024c5 100644 --- a/precompile/contracts/txallowlist/contract_test.go +++ b/precompile/contracts/txallowlist/contract_test.go @@ -6,185 +6,10 @@ package txallowlist import ( "testing" - "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/vmerrs" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" ) func TestTxAllowListRun(t *testing.T) { - adminAddr := common.BigToAddress(common.Big0) - noRoleAddr := common.BigToAddress(common.Big2) - - for name, test := range map[string]contract.PrecompileTest{ - "set admin": { - Caller: adminAddr, - Input: func(tt *testing.T) []byte { - input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.AdminRole) - require.NoError(tt, err) - - return input - }(t), - SuppliedGas: allowlist.ModifyAllowListGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t *testing.T, state contract.StateDB) { - res := GetTxAllowListStatus(state, noRoleAddr) - require.Equal(t, allowlist.AdminRole, res) - }, - }, - "set allowed": { - Caller: adminAddr, - Input: func(tt *testing.T) []byte { - input, err := allowlist.PackModifyAllowList(noRoleAddr, allowlist.EnabledRole) - require.NoError(tt, err) - - return input - }(t), - SuppliedGas: allowlist.ModifyAllowListGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t *testing.T, state contract.StateDB) { - res := GetTxAllowListStatus(state, noRoleAddr) - require.Equal(t, allowlist.EnabledRole, res) - }, - }, - "set no role": { - Caller: adminAddr, - Input: func(tt *testing.T) []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.NoRole) - require.NoError(tt, err) - - return input - }(t), - SuppliedGas: allowlist.ModifyAllowListGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t *testing.T, state contract.StateDB) { - res := GetTxAllowListStatus(state, adminAddr) - require.Equal(t, allowlist.NoRole, res) - }, - }, - "set no role from non-admin": { - Caller: noRoleAddr, - Input: func(tt *testing.T) []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.NoRole) - require.NoError(tt, err) - - return input - }(t), - SuppliedGas: allowlist.ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: allowlist.ErrCannotModifyAllowList.Error(), - }, - "set allowed from non-admin": { - Caller: noRoleAddr, - Input: func(tt *testing.T) []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) - require.NoError(tt, err) - - return input - }(t), - SuppliedGas: allowlist.ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: allowlist.ErrCannotModifyAllowList.Error(), - }, - "set admin from non-admin": { - Caller: noRoleAddr, - Input: func(tt *testing.T) []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.AdminRole) - require.NoError(tt, err) - - return input - }(t), - SuppliedGas: allowlist.ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: allowlist.ErrCannotModifyAllowList.Error(), - }, - "set no role with readOnly enabled": { - Caller: adminAddr, - Input: func(tt *testing.T) []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) - require.NoError(tt, err) - - return input - }(t), - SuppliedGas: allowlist.ModifyAllowListGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "set no role insufficient gas": { - Caller: adminAddr, - Input: func(tt *testing.T) []byte { - input, err := allowlist.PackModifyAllowList(adminAddr, allowlist.EnabledRole) - require.NoError(tt, err) - - return input - }(t), - SuppliedGas: allowlist.ModifyAllowListGasCost - 1, - ReadOnly: false, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "read allow list with no role": { - Caller: noRoleAddr, - Input: allowlist.PackReadAllowList(noRoleAddr), - SuppliedGas: allowlist.ReadAllowListGasCost, - ReadOnly: false, - ExpectedRes: common.Hash(allowlist.NoRole).Bytes(), - AfterHook: nil, - }, - "read allow list with admin role": { - Caller: adminAddr, - Input: allowlist.PackReadAllowList(noRoleAddr), - SuppliedGas: allowlist.ReadAllowListGasCost, - ReadOnly: false, - ExpectedRes: common.Hash(allowlist.NoRole).Bytes(), - AfterHook: nil, - }, - "read allow list with readOnly enabled": { - Caller: adminAddr, - Input: allowlist.PackReadAllowList(noRoleAddr), - SuppliedGas: allowlist.ReadAllowListGasCost, - ReadOnly: true, - ExpectedRes: common.Hash(allowlist.NoRole).Bytes(), - AfterHook: nil, - }, - "read allow list out of gas": { - Caller: adminAddr, - Input: allowlist.PackReadAllowList(noRoleAddr), - SuppliedGas: allowlist.ReadAllowListGasCost - 1, - ReadOnly: true, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), - }, - } { - t.Run(name, func(t *testing.T) { - db := rawdb.NewMemoryDatabase() - state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) - require.NoError(t, err) - - // Set up the state so that each address has the expected permissions at the start. - SetTxAllowListStatus(state, adminAddr, allowlist.AdminRole) - require.Equal(t, allowlist.AdminRole, GetTxAllowListStatus(state, adminAddr)) - - blockContext := contract.NewMockBlockContext(common.Big0, 0) - accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) - ret, remainingGas, err := TxAllowListPrecompile.Run(accesibleState, test.Caller, ContractAddress, test.Input, test.SuppliedGas, test.ReadOnly) - if len(test.ExpectedErr) != 0 { - require.ErrorContains(t, err, test.ExpectedErr) - } else { - require.NoError(t, err) - } - - require.Equal(t, uint64(0), remainingGas) - require.Equal(t, test.ExpectedRes, ret) - - if test.AfterHook != nil { - test.AfterHook(t, state) - } - }) - } + allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, nil) } diff --git a/precompile/testutils/test_precompile.go b/precompile/testutils/test_precompile.go new file mode 100644 index 0000000000..6e7fe23c1c --- /dev/null +++ b/precompile/testutils/test_precompile.go @@ -0,0 +1,86 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package testutils + +import ( + "math/big" + "testing" + + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/precompile/contract" + "github.com/ava-labs/subnet-evm/precompile/modules" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +// PrecompileTest is a test case for a precompile +type PrecompileTest struct { + // Caller is the address of the precompile caller + Caller common.Address + // Input the raw input bytes to the precompile + Input []byte + // InputFn is a function that returns the raw input bytes to the precompile + // If specified, Input will be ignored. + InputFn func(t *testing.T) []byte + // SuppliedGas is the amount of gas supplied to the precompile + SuppliedGas uint64 + // ReadOnly is whether the precompile should be called in read only + // mode. If true, the precompile should not modify the state. + ReadOnly bool + // Config is the config to use for the precompile + // It should be the same precompile config that is used in the + // precompile's configurator. + // If nil, Configure will not be called. + Config precompileconfig.Config + // BeforeHook is called before the precompile is called. + BeforeHook func(t *testing.T, state contract.StateDB) + // AfterHook is called after the precompile is called. + AfterHook func(t *testing.T, state contract.StateDB) + // ExpectedRes is the expected raw byte result returned by the precompile + ExpectedRes []byte + // ExpectedErr is the expected error returned by the precompile + ExpectedErr string + // BlockNumber is the block number to use for the precompile's block context + BlockNumber int64 +} + +func (test PrecompileTest) Run(t *testing.T, module modules.Module, state contract.StateDB) { + t.Helper() + contractAddress := module.Address + + if test.BeforeHook != nil { + test.BeforeHook(t, state) + } + + blockContext := contract.NewMockBlockContext(big.NewInt(test.BlockNumber), 0) + accesibleState := contract.NewMockAccessibleState(state, blockContext, snow.DefaultContextTest()) + chainConfig := contract.NewMockChainState(commontype.ValidTestFeeConfig, false) + + if test.Config != nil { + err := module.Configure(chainConfig, test.Config, state, blockContext) + require.NoError(t, err) + } + + input := test.Input + if test.InputFn != nil { + input = test.InputFn(t) + } + + if input != nil { + ret, remainingGas, err := module.Contract.Run(accesibleState, test.Caller, contractAddress, input, test.SuppliedGas, test.ReadOnly) + if len(test.ExpectedErr) != 0 { + require.ErrorContains(t, err, test.ExpectedErr) + } else { + require.NoError(t, err) + } + require.Equal(t, uint64(0), remainingGas) + require.Equal(t, test.ExpectedRes, ret) + } + + if test.AfterHook != nil { + test.AfterHook(t, state) + } +} From c5c1017a932c2cca08d6a50a1b0cafb98a311f60 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 22 Feb 2023 01:42:38 +0300 Subject: [PATCH 21/23] nit improvements (#529) * nit improvements * move comments to README * Update cmd/precompilegen/template-readme.md --- .../precompile_config_template.go | 50 ++++++------------- .../precompile_contract_template.go | 23 --------- .../precompile_module_template.go | 39 +++------------ cmd/precompilegen/main.go | 21 ++++++-- cmd/precompilegen/template-readme.md | 22 ++++++++ precompile/allowlist/test_allowlist.go | 1 + 6 files changed, 62 insertions(+), 94 deletions(-) create mode 100644 cmd/precompilegen/template-readme.md diff --git a/accounts/abi/bind/precompilebind/precompile_config_template.go b/accounts/abi/bind/precompilebind/precompile_config_template.go index edab3ddb1b..4d8f9252de 100644 --- a/accounts/abi/bind/precompilebind/precompile_config_template.go +++ b/accounts/abi/bind/precompilebind/precompile_config_template.go @@ -8,51 +8,31 @@ const tmplSourcePrecompileConfigGo = ` // This file is a generated precompile contract config with stubbed abstract functions. // The file is generated by a template. Please inspect every code and comment in this file before use. -// There are some must-be-done changes waiting in the file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. -// Additionally there are other files you need to edit to activate your precompile. -// These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". -// For testing take a look at other precompile tests in contract_test.go and config_test.go in other precompile folders. -// See the tutorial in https://docs.avax.network/subnets/hello-world-precompile-tutorial for more information about precompile development. - -/* General guidelines for precompile development: -1- Read the comment and set a suitable contract address in generated module.go. E.g: - ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") -2- Set a suitable config key in generated module.go. E.g: "yourPrecompileConfig" -3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Typically, custom codes are required in only those areas. -Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. -4- Set gas costs in generated contract.go -5- Add your config unit tests under generated package config_test.go -6- Add your contract unit tests undertgenerated package contract_test.go -7- Additionally you can add a full-fledged VM test for your precompile under plugin/vm/vm_test.go. See existing precompile tests for examples. -8- Add your solidity interface and test contract to contract-examples/contracts -9- Write solidity tests for your precompile in contract-examples/test -10- Create your genesis with your precompile enabled in tests/e2e/genesis/ -11- Create e2e test for your solidity test in tests/e2e/solidity/suites.go -12- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' -*/ - package {{.Package}} import ( "math/big" - "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" {{- if .Contract.AllowList}} "github.com/ava-labs/subnet-evm/precompile/allowlist" - {{- end}} "github.com/ethereum/go-ethereum/common" + {{- end}} + ) -var _ config.Config = &Config{} +var _ precompileconfig.Config = &Config{} // Config implements the StatefulPrecompileConfig // interface while adding in the {{.Contract.Type}} specific precompile address. type Config struct { {{- if .Contract.AllowList}} - allowlist.Config + allowlist.AllowListConfig {{- end}} - config.Upgrade + precompileconfig.Upgrade + // CUSTOM CODE STARTS HERE + // Add your own custom fields for Config here } {{$structs := .Structs}} @@ -83,12 +63,12 @@ type {{capitalise .Normalized.Name}}Output struct{ func NewConfig(blockTimestamp *big.Int{{if .Contract.AllowList}}, admins []common.Address, enableds []common.Address,{{end}}) *Config { return &Config{ {{- if .Contract.AllowList}} - Config: allowlist.Config{ + AllowListConfig: allowlist.AllowListConfig{ AdminAddresses: admins, EnabledAddresses: enableds, }, {{- end}} - Upgrade: config.Upgrade{BlockTimestamp: blockTimestamp}, + Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp}, } } @@ -96,14 +76,14 @@ func NewConfig(blockTimestamp *big.Int{{if .Contract.AllowList}}, admins []commo // that disables {{.Contract.Type}}. func NewDisableConfig(blockTimestamp *big.Int) *Config { return &Config{ - Upgrade: config.Upgrade{ + Upgrade: precompileconfig.Upgrade{ BlockTimestamp: blockTimestamp, Disable: true, }, } } -// Key returns the key for the {{.Contract.Type}} config. +// Key returns the key for the {{.Contract.Type}} precompileconfig. // This should be the same key as used in the precompile module. func (*Config) Key() string { return ConfigKey } @@ -111,7 +91,7 @@ func (*Config) Key() string { return ConfigKey } func (c *Config) Verify() error { {{if .Contract.AllowList}} // Verify AllowList first - if err := c.Config.Verify(); err != nil { + if err := c.AllowListConfig.Verify(); err != nil { return err } {{end}} @@ -122,7 +102,7 @@ func (c *Config) Verify() error { } // Equal returns true if [s] is a [*Config] and it has been configured identical to [c]. -func (c *Config) Equal(s config.Config) bool { +func (c *Config) Equal(s precompileconfig.Config) bool { // typecast before comparison other, ok := (s).(*Config) if !ok { @@ -131,7 +111,7 @@ func (c *Config) Equal(s config.Config) bool { // CUSTOM CODE STARTS HERE // modify this boolean accordingly with your custom Config, to check if [other] and the current [c] are equal // if Config contains only Upgrade {{if .Contract.AllowList}} and AllowListConfig {{end}} you can skip modifying it. - equals := c.Upgrade.Equal(&other.Upgrade) {{if .Contract.AllowList}} && c.Config.Equal(&other.Config) {{end}} + equals := c.Upgrade.Equal(&other.Upgrade) {{if .Contract.AllowList}} && c.AllowListConfig.Equal(&other.AllowListConfig) {{end}} return equals } ` diff --git a/accounts/abi/bind/precompilebind/precompile_contract_template.go b/accounts/abi/bind/precompilebind/precompile_contract_template.go index d96b67e768..1c302d4b49 100644 --- a/accounts/abi/bind/precompilebind/precompile_contract_template.go +++ b/accounts/abi/bind/precompilebind/precompile_contract_template.go @@ -25,29 +25,6 @@ const tmplSourcePrecompileContractGo = ` // This file is a generated precompile contract config with stubbed abstract functions. // The file is generated by a template. Please inspect every code and comment in this file before use. -// There are some must-be-done changes waiting in the file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. -// Additionally there are other files you need to edit to activate your precompile. -// These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". -// For testing take a look at other precompile tests in contract_test.go and config_test.go in other precompile folders. -// See the tutorial in https://docs.avax.network/subnets/hello-world-precompile-tutorial for more information about precompile development. - -/* General guidelines for precompile development: -1- Read the comment and set a suitable contract address in generated module.go. E.g: - ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") -2- Set a suitable config key in generated module.go. E.g: "yourPrecompileConfig" -3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Typically, custom codes are required in only those areas. -Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. -4- Set gas costs in generated contract.go -5- Add your config unit tests under generated package config_test.go -6- Add your contract unit tests undertgenerated package contract_test.go -7- Additionally you can add a full-fledged VM test for your precompile under plugin/vm/vm_test.go. See existing precompile tests for examples. -8- Add your solidity interface and test contract to contract-examples/contracts -9- Write solidity tests for your precompile in contract-examples/test -10- Create your genesis with your precompile enabled in tests/e2e/genesis/ -11- Create e2e test for your solidity test in tests/e2e/solidity/suites.go -12- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' -*/ - package {{.Package}} import ( diff --git a/accounts/abi/bind/precompilebind/precompile_module_template.go b/accounts/abi/bind/precompilebind/precompile_module_template.go index b0b8c7f184..0fa4975e5d 100644 --- a/accounts/abi/bind/precompilebind/precompile_module_template.go +++ b/accounts/abi/bind/precompilebind/precompile_module_template.go @@ -8,35 +8,12 @@ const tmplSourcePrecompileModuleGo = ` // This file is a generated precompile contract config with stubbed abstract functions. // The file is generated by a template. Please inspect every code and comment in this file before use. -// There are some must-be-done changes waiting in the file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. -// Additionally there are other files you need to edit to activate your precompile. -// These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". -// For testing take a look at other precompile tests in contract_test.go and config_test.go in other precompile folders. -// See the tutorial in https://docs.avax.network/subnets/hello-world-precompile-tutorial for more information about precompile development. - -/* General guidelines for precompile development: -1- Read the comment and set a suitable contract address in generated module.go. E.g: - ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") -2- Set a suitable config key in generated module.go. E.g: "yourPrecompileConfig" -3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Typically, custom codes are required in only those areas. -Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. -4- Set gas costs in generated contract.go -5- Add your config unit tests under generated package config_test.go -6- Add your contract unit tests undertgenerated package contract_test.go -7- Additionally you can add a full-fledged VM test for your precompile under plugin/vm/vm_test.go. See existing precompile tests for examples. -8- Add your solidity interface and test contract to contract-examples/contracts -9- Write solidity tests for your precompile in contract-examples/test -10- Create your genesis with your precompile enabled in tests/e2e/genesis/ -11- Create e2e test for your solidity test in tests/e2e/solidity/suites.go -12- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh' -*/ - package {{.Package}} import ( "fmt" - "github.com/ava-labs/subnet-evm/precompile/config" + "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/precompile/modules" @@ -45,7 +22,7 @@ import ( var _ contract.Configurator = &configurator{} -// ConfigKey is the key used in json config files to specify this precompile config. +// ConfigKey is the key used in json config files to specify this precompile precompileconfig. // must be unique across all precompiles. const ConfigKey = "{{decapitalise .Contract.Type}}Config" @@ -72,17 +49,17 @@ func init() { } } -// NewConfig returns a new precompile config. -// This is required for Marshal/Unmarshal the precompile config. -func (*configurator) NewConfig() config.Config { +// NewConfig returns a new precompile precompileconfig. +// This is required for Marshal/Unmarshal the precompile precompileconfig. +func (*configurator) NewConfig() precompileconfig.Config { return &Config{} } -// Configure configures [state] with the given [cfg] config. +// Configure configures [state] with the given [cfg] precompileconfig. // This function is called by the EVM once per precompile contract activation. // You can use this function to set up your precompile contract's initial state, // by using the [cfg] config and [state] stateDB. -func (*configurator) Configure(chainConfig contract.ChainConfig, cfg config.Config, state contract.StateDB, _ contract.BlockContext) error { +func (*configurator) Configure(chainConfig contract.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, _ contract.BlockContext) error { config, ok := cfg.(*Config) if !ok { return fmt.Errorf("incorrect config %T: %v", config, config) @@ -90,7 +67,7 @@ func (*configurator) Configure(chainConfig contract.ChainConfig, cfg config.Conf // CUSTOM CODE STARTS HERE {{if .Contract.AllowList}} // AllowList is activated for this precompile. Configuring allowlist addresses here. - return config.Configure(state, ContractAddress) + return config.AllowListConfig.Configure(state, ContractAddress) {{else}} return nil {{end}} diff --git a/cmd/precompilegen/main.go b/cmd/precompilegen/main.go index 75606a9c87..a2545e3572 100644 --- a/cmd/precompilegen/main.go +++ b/cmd/precompilegen/main.go @@ -33,6 +33,8 @@ import ( "path/filepath" "strings" + _ "embed" + "github.com/ava-labs/subnet-evm/accounts/abi/bind" "github.com/ava-labs/subnet-evm/accounts/abi/bind/precompilebind" "github.com/ava-labs/subnet-evm/internal/flags" @@ -47,25 +49,28 @@ var ( gitDate = "" app *cli.App + + //go:embed template-readme.md + readme string ) var ( // Flags needed by abigen abiFlag = &cli.StringFlag{ Name: "abi", - Usage: "Path to the Ethereum contract ABI json to bind, - for STDIN", + Usage: "Path to the contract ABI json to generate, - for STDIN", } typeFlag = &cli.StringFlag{ Name: "type", - Usage: "Struct name for the precompile (default = {ABI name})", + Usage: "Struct name for the precompile (default = {abi file name})", } pkgFlag = &cli.StringFlag{ Name: "pkg", - Usage: "Package name to generate the precompile into (default = {type})", + Usage: "Go package name to generate the precompile into (default = {type})", } outFlag = &cli.StringFlag{ Name: "out", - Usage: "Output folder for the generated precompile files, - for STDOUT (default = ./{pkg})", + Usage: "Output folder for the generated precompile files, - for STDOUT (default = ./precompile/contracts/{pkg})", } ) @@ -134,7 +139,7 @@ func precompilegen(c *cli.Context) error { } if outFlagStr == "" { - outFlagStr = filepath.Join("./", pkg) + outFlagStr = filepath.Join("./precompile/contracts", pkg) } abifilename := "" @@ -187,6 +192,12 @@ func precompilegen(c *cli.Context) error { utils.Fatalf("Failed to write ABI: %v", err) } + readmeOut := filepath.Join(outFlagStr, "README.md") + + if err := os.WriteFile(readmeOut, []byte(readme), 0o600); err != nil { + utils.Fatalf("Failed to write README: %v", err) + } + fmt.Println("Precompile files generated successfully at: ", outFlagStr) return nil } diff --git a/cmd/precompilegen/template-readme.md b/cmd/precompilegen/template-readme.md new file mode 100644 index 0000000000..883f41f105 --- /dev/null +++ b/cmd/precompilegen/template-readme.md @@ -0,0 +1,22 @@ +There are some must-be-done changes waiting in the generated file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. +Additionally there are other files you need to edit to activate your precompile. +These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". +For testing take a look at other precompile tests in contract_test.go and config_test.go in other precompile folders. +See the tutorial in for more information about precompile development. + +General guidelines for precompile development: +1- Set a suitable config key in generated module.go. E.g: "yourPrecompileConfig" +2- Read the comment and set a suitable contract address in generated module.go. E.g: +ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") +3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Typically, custom codes are required in only those areas. +Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. +4- Set gas costs in generated contract.go +5- Force import your precompile package in precompile/registry/registry.go +6- Add your config unit tests under generated package config_test.go +7- Add your contract unit tests under generated package contract_test.go +8- Additionally you can add a full-fledged VM test for your precompile under plugin/vm/vm_test.go. See existing precompile tests for examples. +9- Add your solidity interface and test contract to contract-examples/contracts +10- Write solidity tests for your precompile in contract-examples/test +11- Create your genesis with your precompile enabled in tests/e2e/genesis/ +12- Create e2e test for your solidity test in tests/e2e/solidity/suites.go +13- Run your e2e precompile Solidity tests with 'E2E=true ./scripts/run.sh diff --git a/precompile/allowlist/test_allowlist.go b/precompile/allowlist/test_allowlist.go index 5abb39d5b9..70656dda3d 100644 --- a/precompile/allowlist/test_allowlist.go +++ b/precompile/allowlist/test_allowlist.go @@ -273,6 +273,7 @@ func SetDefaultRoles(contractAddress common.Address) func(t *testing.T, state co } func RunPrecompileWithAllowListTests(t *testing.T, module modules.Module, newStateDB func(t *testing.T) contract.StateDB, contractTests map[string]testutils.PrecompileTest) { + t.Helper() tests := AllowListTests(module) // Add the contract specific tests to the map of tests to run. for name, test := range contractTests { From 9059a092b8c3f92649df9c29a75882bce99ba4da Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 22 Feb 2023 01:45:15 +0300 Subject: [PATCH 22/23] Rename new config (#528) * rename configurator's new config to make config * use new built-in to create new config instance --- .../abi/bind/precompilebind/precompile_module_template.go | 8 ++++---- params/precompile_upgrade.go | 2 +- params/precompiles.go | 2 +- precompile/allowlist/allowlist_test.go | 2 +- precompile/allowlist/test_allowlist.go | 2 +- precompile/contract/interfaces.go | 2 +- precompile/contracts/deployerallowlist/module.go | 4 ++-- precompile/contracts/feemanager/module.go | 4 ++-- precompile/contracts/nativeminter/module.go | 4 ++-- precompile/contracts/rewardmanager/module.go | 4 ++-- precompile/contracts/txallowlist/module.go | 4 ++-- 11 files changed, 19 insertions(+), 19 deletions(-) diff --git a/accounts/abi/bind/precompilebind/precompile_module_template.go b/accounts/abi/bind/precompilebind/precompile_module_template.go index 0fa4975e5d..db3950e3f5 100644 --- a/accounts/abi/bind/precompilebind/precompile_module_template.go +++ b/accounts/abi/bind/precompilebind/precompile_module_template.go @@ -49,10 +49,10 @@ func init() { } } -// NewConfig returns a new precompile precompileconfig. -// This is required for Marshal/Unmarshal the precompile precompileconfig. -func (*configurator) NewConfig() precompileconfig.Config { - return &Config{} +// MakeConfig returns a new precompile config instance. +// This is required for Marshal/Unmarshal the precompile config. +func (*configurator) MakeConfig() precompileconfig.Config { + return new(Config) } // Configure configures [state] with the given [cfg] precompileconfig. diff --git a/params/precompile_upgrade.go b/params/precompile_upgrade.go index 16792c6781..9b4bcd81f6 100644 --- a/params/precompile_upgrade.go +++ b/params/precompile_upgrade.go @@ -46,7 +46,7 @@ func (u *PrecompileUpgrade) UnmarshalJSON(data []byte) error { if !ok { return fmt.Errorf("unknown precompile config: %s", key) } - config := module.NewConfig() + config := module.MakeConfig() if err := json.Unmarshal(value, config); err != nil { return err } diff --git a/params/precompiles.go b/params/precompiles.go index fcb7604e5b..5d8ed74bda 100644 --- a/params/precompiles.go +++ b/params/precompiles.go @@ -25,7 +25,7 @@ func (ccp *Precompiles) UnmarshalJSON(data []byte) error { for _, module := range modules.RegisteredModules() { key := module.ConfigKey if value, ok := raw[key]; ok { - conf := module.NewConfig() + conf := module.MakeConfig() if err := json.Unmarshal(value, conf); err != nil { return err } diff --git a/precompile/allowlist/allowlist_test.go b/precompile/allowlist/allowlist_test.go index 4eeaf7eb3f..f4c0f5fc21 100644 --- a/precompile/allowlist/allowlist_test.go +++ b/precompile/allowlist/allowlist_test.go @@ -34,7 +34,7 @@ func (d *dummyConfig) Equal(other precompileconfig.Config) bool { type dummyConfigurator struct{} -func (d *dummyConfigurator) NewConfig() precompileconfig.Config { +func (d *dummyConfigurator) MakeConfig() precompileconfig.Config { return &dummyConfig{} } diff --git a/precompile/allowlist/test_allowlist.go b/precompile/allowlist/test_allowlist.go index 70656dda3d..78158e55ee 100644 --- a/precompile/allowlist/test_allowlist.go +++ b/precompile/allowlist/test_allowlist.go @@ -30,7 +30,7 @@ func mkConfigWithAllowList(module modules.Module, cfg *AllowListConfig) precompi panic(err) } - moduleCfg := module.NewConfig() + moduleCfg := module.MakeConfig() err = json.Unmarshal(jsonBytes, moduleCfg) if err != nil { panic(err) diff --git a/precompile/contract/interfaces.go b/precompile/contract/interfaces.go index 66a449fe91..a8402bed75 100644 --- a/precompile/contract/interfaces.go +++ b/precompile/contract/interfaces.go @@ -69,7 +69,7 @@ type BlockContext interface { } type Configurator interface { - NewConfig() precompileconfig.Config + MakeConfig() precompileconfig.Config Configure( chainConfig ChainConfig, precompileconfig precompileconfig.Config, diff --git a/precompile/contracts/deployerallowlist/module.go b/precompile/contracts/deployerallowlist/module.go index 623eb27e0f..93dd59daef 100644 --- a/precompile/contracts/deployerallowlist/module.go +++ b/precompile/contracts/deployerallowlist/module.go @@ -35,8 +35,8 @@ func init() { } } -func (*configurator) NewConfig() precompileconfig.Config { - return &Config{} +func (*configurator) MakeConfig() precompileconfig.Config { + return new(Config) } // Configure configures [state] with the given [cfg] config. diff --git a/precompile/contracts/feemanager/module.go b/precompile/contracts/feemanager/module.go index fa5cf5ccad..ce96933bf1 100644 --- a/precompile/contracts/feemanager/module.go +++ b/precompile/contracts/feemanager/module.go @@ -35,8 +35,8 @@ func init() { } } -func (*configurator) NewConfig() precompileconfig.Config { - return &Config{} +func (*configurator) MakeConfig() precompileconfig.Config { + return new(Config) } // Configure configures [state] with the desired admins based on [configIface]. diff --git a/precompile/contracts/nativeminter/module.go b/precompile/contracts/nativeminter/module.go index 8648c1f119..6ebd23e63e 100644 --- a/precompile/contracts/nativeminter/module.go +++ b/precompile/contracts/nativeminter/module.go @@ -36,8 +36,8 @@ func init() { } } -func (*configurator) NewConfig() precompileconfig.Config { - return &Config{} +func (*configurator) MakeConfig() precompileconfig.Config { + return new(Config) } // Configure configures [state] with the desired admins based on [cfg]. diff --git a/precompile/contracts/rewardmanager/module.go b/precompile/contracts/rewardmanager/module.go index 57739af6c8..477d0d413c 100644 --- a/precompile/contracts/rewardmanager/module.go +++ b/precompile/contracts/rewardmanager/module.go @@ -35,8 +35,8 @@ func init() { } } -func (*configurator) NewConfig() precompileconfig.Config { - return &Config{} +func (*configurator) MakeConfig() precompileconfig.Config { + return new(Config) } // Configure configures [state] with the initial state for the precompile. diff --git a/precompile/contracts/txallowlist/module.go b/precompile/contracts/txallowlist/module.go index b1ae6a096e..3d58942dea 100644 --- a/precompile/contracts/txallowlist/module.go +++ b/precompile/contracts/txallowlist/module.go @@ -35,8 +35,8 @@ func init() { } } -func (*configurator) NewConfig() precompileconfig.Config { - return &Config{} +func (*configurator) MakeConfig() precompileconfig.Config { + return new(Config) } // Configure configures [state] with the initial state for the precompile. From 9a8a85dbc542de17ca458fbb31c586528441e8df Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Tue, 21 Feb 2023 16:03:27 -0800 Subject: [PATCH 23/23] precompile: just nits (#534) --- eth/gasprice/gasprice_test.go | 4 ++-- internal/ethapi/api.go | 7 ++++--- params/precompile_config_test.go | 19 +++++++++---------- params/precompile_upgrade.go | 2 +- params/precompile_upgrade_test.go | 12 +++++------- plugin/evm/vm_test.go | 2 +- precompile/allowlist/allowlist.go | 2 +- precompile/allowlist/config.go | 2 +- precompile/allowlist/config_test.go | 2 +- precompile/precompileconfig/mock_config.go | 2 +- utils/address_range.go | 2 +- 11 files changed, 27 insertions(+), 29 deletions(-) diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index 8f80573b2b..45672a53f5 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -96,7 +96,7 @@ func (b *testBackend) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig } func newTestBackendFakerEngine(t *testing.T, config *params.ChainConfig, numBlocks int, genBlocks func(i int, b *core.BlockGen)) *testBackend { - gspec := &core.Genesis{ + var gspec = &core.Genesis{ Config: config, Alloc: core.GenesisAlloc{addr: core.GenesisAccount{Balance: bal}}, } @@ -124,7 +124,7 @@ func newTestBackendFakerEngine(t *testing.T, config *params.ChainConfig, numBloc } func newTestBackend(t *testing.T, config *params.ChainConfig, numBlocks int, genBlocks func(i int, b *core.BlockGen)) *testBackend { - gspec := &core.Genesis{ + var gspec = &core.Genesis{ Config: config, Alloc: core.GenesisAlloc{addr: core.GenesisAccount{Balance: bal}}, } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 64d0803800..8ace36596f 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -233,7 +233,7 @@ func (s *TxPoolAPI) Inspect() map[string]map[string]map[string]string { pending, queue := s.b.TxPoolContent() // Define a formatter to flatten a transaction into a string - format := func(tx *types.Transaction) string { + var format = func(tx *types.Transaction) string { if to := tx.To(); to != nil { return fmt.Sprintf("%s: %v wei + %v gas × %v wei", tx.To().Hex(), tx.Value(), tx.Gas(), tx.GasPrice()) } @@ -1201,6 +1201,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr for lo+1 < hi { mid := (hi + lo) / 2 failed, _, err := executable(mid) + // If the error is not nil(consensus error), it means the provided message // call or transaction will never be accepted no matter how much gas it is // assigned. Return the error directly, don't struggle any more. @@ -1980,11 +1981,11 @@ func (s *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, g matchTx := sendArgs.toTransaction() // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable. - price := matchTx.GasPrice() + var price = matchTx.GasPrice() if gasPrice != nil { price = gasPrice.ToInt() } - gas := matchTx.Gas() + var gas = matchTx.Gas() if gasLimit != nil { gas = uint64(*gasLimit) } diff --git a/params/precompile_config_test.go b/params/precompile_config_test.go index 264f429750..ec81ecaf36 100644 --- a/params/precompile_config_test.go +++ b/params/precompile_config_test.go @@ -15,7 +15,6 @@ import ( "github.com/ava-labs/subnet-evm/precompile/contracts/rewardmanager" "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -39,7 +38,7 @@ func TestVerifyWithChainConfig(t *testing.T) { // check this config is valid err := config.Verify() - assert.NoError(t, err) + require.NoError(t, err) // same precompile cannot be configured twice for the same timestamp badConfig := *config @@ -50,7 +49,7 @@ func TestVerifyWithChainConfig(t *testing.T) { }, ) err = badConfig.Verify() - assert.ErrorContains(t, err, "config block timestamp (5) <= previous timestamp (5) of same key") + require.ErrorContains(t, err, "config block timestamp (5) <= previous timestamp (5) of same key") // cannot enable a precompile without disabling it first. badConfig = *config @@ -61,7 +60,7 @@ func TestVerifyWithChainConfig(t *testing.T) { }, ) err = badConfig.Verify() - assert.ErrorContains(t, err, "disable should be [true]") + require.ErrorContains(t, err, "disable should be [true]") } func TestVerifyWithChainConfigAtNilTimestamp(t *testing.T) { @@ -259,11 +258,11 @@ func TestVerifyRequiresSortedTimestamps(t *testing.T) { // block timestamps must be monotonically increasing, so this config is invalid err := config.Verify() - assert.ErrorContains(t, err, "config block timestamp (1) < previous timestamp (2)") + require.ErrorContains(t, err, "config block timestamp (1) < previous timestamp (2)") } func TestGetPrecompileConfig(t *testing.T) { - assert := assert.New(t) + require := require.New(t) baseConfig := *SubnetEVMDefaultChainConfig config := &baseConfig config.GenesisPrecompiles = Precompiles{ @@ -271,16 +270,16 @@ func TestGetPrecompileConfig(t *testing.T) { } deployerConfig := config.GetActivePrecompileConfig(deployerallowlist.ContractAddress, big.NewInt(0)) - assert.Nil(deployerConfig) + require.Nil(deployerConfig) deployerConfig = config.GetActivePrecompileConfig(deployerallowlist.ContractAddress, big.NewInt(10)) - assert.NotNil(deployerConfig) + require.NotNil(deployerConfig) deployerConfig = config.GetActivePrecompileConfig(deployerallowlist.ContractAddress, big.NewInt(11)) - assert.NotNil(deployerConfig) + require.NotNil(deployerConfig) txAllowListConfig := config.GetActivePrecompileConfig(txallowlist.ContractAddress, big.NewInt(0)) - assert.Nil(txAllowListConfig) + require.Nil(txAllowListConfig) } func TestPrecompileUpgradeUnmarshalJSON(t *testing.T) { diff --git a/params/precompile_upgrade.go b/params/precompile_upgrade.go index 9b4bcd81f6..98a32d4d42 100644 --- a/params/precompile_upgrade.go +++ b/params/precompile_upgrade.go @@ -1,4 +1,4 @@ -// (c) 2022 Ava Labs, Inc. All rights reserved. +// (c) 2023 Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package params diff --git a/params/precompile_upgrade_test.go b/params/precompile_upgrade_test.go index 1c85b04bcd..54078aad3b 100644 --- a/params/precompile_upgrade_test.go +++ b/params/precompile_upgrade_test.go @@ -10,7 +10,7 @@ import ( "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestVerifyUpgradeConfig(t *testing.T) { @@ -62,9 +62,9 @@ func TestVerifyUpgradeConfig(t *testing.T) { err := chainConfig.Verify() if tt.expectedErrorString != "" { - assert.ErrorContains(t, err, tt.expectedErrorString) + require.ErrorContains(t, err, tt.expectedErrorString) } else { - assert.NoError(t, err) + require.NoError(t, err) } }) } @@ -273,11 +273,9 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { } if tt.expectedErrorString != "" { - assert.ErrorContains(t, err, tt.expectedErrorString) + require.ErrorContains(t, err, tt.expectedErrorString) } else { - if err != nil { - t.Fatal(err) - } + require.Nil(t, err) } } }) diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index bd8fadba1d..8acca71c79 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -2480,7 +2480,7 @@ func TestFeeManagerChangeFee(t *testing.T) { } role = feemanager.GetFeeManagerStatus(genesisState, testEthAddrs[1]) if role != allowlist.NoRole { - t.Fatalf("Expected fee manager list status to be set to no role: %s, but found: %s", allowlist.AdminRole, role) + t.Fatalf("Expected fee manager list status to be set to no role: %s, but found: %s", allowlist.NoRole, role) } // Contract is initialized but no preconfig is given, reader should return genesis fee config feeConfig, lastChangedAt, err := vm.blockChain.GetFeeConfigAt(vm.blockChain.Genesis().Header()) diff --git a/precompile/allowlist/allowlist.go b/precompile/allowlist/allowlist.go index 7822825d76..cf8dcbdb4c 100644 --- a/precompile/allowlist/allowlist.go +++ b/precompile/allowlist/allowlist.go @@ -1,4 +1,4 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// (c) 2019-2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package allowlist diff --git a/precompile/allowlist/config.go b/precompile/allowlist/config.go index 3ab68d374f..90ef49a811 100644 --- a/precompile/allowlist/config.go +++ b/precompile/allowlist/config.go @@ -1,4 +1,4 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// (c) 2019-2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package allowlist diff --git a/precompile/allowlist/config_test.go b/precompile/allowlist/config_test.go index 8473115400..bc3a3d9d26 100644 --- a/precompile/allowlist/config_test.go +++ b/precompile/allowlist/config_test.go @@ -1,4 +1,4 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// (c) 2019-2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package allowlist diff --git a/precompile/precompileconfig/mock_config.go b/precompile/precompileconfig/mock_config.go index 8abca06f88..6f1a9debc6 100644 --- a/precompile/precompileconfig/mock_config.go +++ b/precompile/precompileconfig/mock_config.go @@ -1,4 +1,4 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// (c) 2019-2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. // TODO: replace with gomock diff --git a/utils/address_range.go b/utils/address_range.go index ee40d2bcca..940c39e8a1 100644 --- a/utils/address_range.go +++ b/utils/address_range.go @@ -1,4 +1,4 @@ -// (c) 2021-2022, Ava Labs, Inc. All rights reserved. +// (c) 2021-2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package utils