diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go
index 82d5be5488..37fd93a690 100644
--- a/accounts/abi/bind/bind.go
+++ b/accounts/abi/bind/bind.go
@@ -44,12 +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)
// Lang is a target programming language selector to generate bindings for.
type Lang int
@@ -101,13 +97,17 @@ 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)
+ 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{})
@@ -128,11 +128,11 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
// 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
@@ -155,7 +155,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 +178,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)
}
@@ -192,9 +187,9 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
}
// 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 {
@@ -238,17 +233,17 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
}
// 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"),
@@ -291,19 +286,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, pkg, 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,
@@ -342,7 +332,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]
// 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,
}
@@ -374,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
@@ -451,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
@@ -464,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
@@ -488,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
@@ -505,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,
}
@@ -513,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
@@ -542,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,
}
@@ -559,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
@@ -581,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,
}
@@ -710,45 +700,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/precompilebind/precompile_bind.go b/accounts/abi/bind/precompilebind/precompile_bind.go
new file mode 100644
index 0000000000..ae4b40b604
--- /dev/null
+++ b/accounts/abi/bind/precompilebind/precompile_bind.go
@@ -0,0 +1,141 @@
+// (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 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 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 := 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 contract binding: %w", err)
+ }
+ moduleBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, moduleHook)
+ if err != nil {
+ return "", "", "", fmt.Errorf("failed to generate module binding: %w", err)
+ }
+ return configBind, contractBind, moduleBind, nil
+}
+
+// createPrecompileHook creates a bind hook for precompiled contracts.
+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 != 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]*bind.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 {
+ // 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)
+ delete(funcs, setNoneFuncKey)
+ }
+
+ precompileContract := &tmplPrecompileContract{
+ TmplContract: contract,
+ AllowList: isAllowList,
+ Funcs: funcs,
+ ABIFilename: abifilename,
+ }
+
+ data := &tmplPrecompileData{
+ Contract: precompileContract,
+ Structs: structs,
+ Package: pkg,
+ }
+ return data, template, nil
+ }
+}
+
+func allowListEnabled(funcs map[string]*bind.TmplMethod) bool {
+ keys := []string{readAllowListFuncKey, setAdminFuncKey, setEnabledFuncKey, setNoneFuncKey}
+ for _, key := range keys {
+ if _, ok := funcs[key]; !ok {
+ return false
+ }
+ }
+ return true
+}
+
+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)
+ }
+ }
+ return nil
+}
diff --git a/accounts/abi/bind/bind_precompile_test.go b/accounts/abi/bind/precompilebind/precompile_bind_test.go
similarity index 84%
rename from accounts/abi/bind/bind_precompile_test.go
rename to accounts/abi/bind/precompilebind/precompile_bind_test.go
index 03cbcebc09..f37d54e1ba 100644
--- a/accounts/abi/bind/bind_precompile_test.go
+++ b/accounts/abi/bind/precompilebind/precompile_bind_test.go
@@ -24,10 +24,13 @@
// 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"
)
var bindFailedTests = []struct {
@@ -49,7 +52,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 +67,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 +82,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,14 +99,11 @@ 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", bind.LangGo, tt.libs, tt.aliases, "")
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/precompilebind/precompile_config_template.go b/accounts/abi/bind/precompilebind/precompile_config_template.go
new file mode 100644
index 0000000000..4d8f9252de
--- /dev/null
+++ b/accounts/abi/bind/precompilebind/precompile_config_template.go
@@ -0,0 +1,117 @@
+// (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.
+
+package {{.Package}}
+
+import (
+ "math/big"
+
+ "github.com/ava-labs/subnet-evm/precompile/precompileconfig"
+ {{- if .Contract.AllowList}}
+ "github.com/ava-labs/subnet-evm/precompile/allowlist"
+
+ "github.com/ethereum/go-ethereum/common"
+ {{- end}}
+
+)
+
+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.AllowListConfig
+ {{- end}}
+ precompileconfig.Upgrade
+ // CUSTOM CODE STARTS HERE
+ // Add your own custom fields for Config here
+}
+
+{{$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}}
+ AllowListConfig: allowlist.AllowListConfig{
+ AdminAddresses: admins,
+ EnabledAddresses: enableds,
+ },
+ {{- end}}
+ Upgrade: precompileconfig.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: precompileconfig.Upgrade{
+ BlockTimestamp: blockTimestamp,
+ Disable: true,
+ },
+ }
+}
+
+// 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 }
+
+// Verify tries to verify Config and returns an error accordingly.
+func (c *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 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 precompileconfig.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.AllowListConfig.Equal(&other.AllowListConfig) {{end}}
+ return equals
+}
+`
diff --git a/accounts/abi/bind/precompile_template.go b/accounts/abi/bind/precompilebind/precompile_contract_template.go
similarity index 55%
rename from accounts/abi/bind/precompile_template.go
rename to accounts/abi/bind/precompilebind/precompile_contract_template.go
index 20bad6bdf5..1c302d4b49 100644
--- a/accounts/abi/bind/precompile_template.go
+++ b/accounts/abi/bind/precompilebind/precompile_contract_template.go
@@ -1,72 +1,64 @@
// (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 {
- Contract *tmplPrecompileContract // The contract to generate into this file
- Structs map[string]*tmplStruct // Contract struct type definitions
+ Package string
+ 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
+ *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
}
-// 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.
+// 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
-
-/* 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
-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 './scripts/run_ginkgo.sh'
-
-*/
-
-package precompile
+package {{.Package}}
import (
- "encoding/json"
"errors"
"fmt"
"math/big"
- "strings"
"github.com/ava-labs/subnet-evm/accounts/abi"
+ {{- 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
{{- end}}
-
- // {{.Contract.Type}}RawABI contains the raw ABI of {{.Contract.Type}} contract.
- {{.Contract.Type}}RawABI = "{{.Contract.InputABI}}"
)
// CUSTOM CODE STARTS HERE
@@ -74,15 +66,10 @@ const (
var (
_ = errors.New
_ = big.NewInt
- _ = strings.NewReader
- _ = fmt.Printf
)
-{{$contract := .Contract}}
// Singleton StatefulPrecompiledContract and signatures.
var (
- _ StatefulPrecompileConfig = &{{.Contract.Type}}Config{}
-
{{- range .Contract.Funcs}}
{{- if not .Original.IsConstant | and $contract.AllowList}}
@@ -95,24 +82,19 @@ var (
Err{{.Contract.Type}}CannotFallback = errors.New("non-enabled cannot call fallback function")
{{- end}}
- {{.Contract.Type}}ABI abi.ABI // will be initialized by init function
+ // {{.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}}Precompile StatefulPrecompiledContract // will be initialized by init function
+ {{.Contract.Type}}ABI = contract.ParseABI({{.Contract.Type}}RawABI)
- // 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}}Precompile = create{{.Contract.Type}}Precompile()
)
-// {{.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.
@@ -135,101 +117,20 @@ 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 = create{{.Contract.Type}}Precompile({{.Contract.Type}}Address)
-}
-
-// 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) {
- {{if .Contract.AllowList}}c.AllowListConfig.Configure(state, {{.Contract.Type}}Address){{end}}
- // CUSTOM CODE STARTS HERE
-}
-
-// 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 contract.StateDB, address common.Address) allowlist.Role {
+ 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 StateDB, address common.Address, role AllowListRole) {
- setAllowListRole(stateDB, {{.Contract.Type}}Address, address, role)
+func Set{{.Contract.Type}}AllowListStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) {
+ allowlist.SetAllowListRole(stateDB, ContractAddress, address, role)
}
{{end}}
@@ -297,8 +198,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 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
}
@@ -325,8 +226,8 @@ func {{decapitalise .Normalized.Name}}(accessibleState PrecompileAccessibleState
// 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 := 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)
}
@@ -362,8 +263,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 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
}
@@ -376,8 +277,8 @@ func {{decapitalise $contract.Type}}Fallback (accessibleState PrecompileAccessib
// 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 := 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)
}
@@ -397,28 +298,41 @@ func {{decapitalise $contract.Type}}Fallback (accessibleState PrecompileAccessib
{{- 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) StatefulPrecompiledContract {
- var functions []*statefulPrecompileFunction
+{{if .Contract.AllowList}} // Access to the getters/setters is controlled by an allow list for ContractAddress.{{end}}
+func create{{.Contract.Type}}Precompile() contract.StatefulPrecompiledContract {
+ var functions []*contract.StatefulPrecompileFunction
{{- if .Contract.AllowList}}
- functions = append(functions, createAllowListFunctions(precompileAddr)...)
+ functions = append(functions, allowlist.CreateAllowListFunctions(ContractAddress)...)
{{- 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]contract.RunStatefulPrecompileFunc{
+ {{- range .Contract.Funcs}}
+ "{{.Original.Name}}": {{decapitalise .Normalized.Name}},
+ {{- end}}
+ }
+
+ for name, function := range abiFunctionMap {
+ method, ok := {{$contract.Type}}ABI.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))
}
- 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)
+ statefulContract, err := contract.NewStatefulPrecompileContract({{decapitalise $contract.Type}}Fallback, functions)
+ if err != nil {
+ panic(err)
+ }
+ return statefulContract
{{- else}}
// Construct the contract with no fallback function.
- contract := newStatefulPrecompileWithFunctionSelectors(nil, functions)
+ statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions)
+ if err != nil {
+ panic(err)
+ }
+ return statefulContract
{{- end}}
- return contract
}
`
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..db3950e3f5
--- /dev/null
+++ b/accounts/abi/bind/precompilebind/precompile_module_template.go
@@ -0,0 +1,75 @@
+// (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.
+
+package {{.Package}}
+
+import (
+ "fmt"
+
+ "github.com/ava-labs/subnet-evm/precompile/precompileconfig"
+ "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 precompileconfig.
+// 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)
+ }
+}
+
+// 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.
+// 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 precompileconfig.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.AllowListConfig.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/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..a2545e3572 100644
--- a/cmd/precompilegen/main.go
+++ b/cmd/precompilegen/main.go
@@ -33,7 +33,10 @@ 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"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/log"
@@ -46,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 = precompile)",
+ Usage: "Go 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 = ./precompile/contracts/{pkg})",
}
)
@@ -81,13 +87,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 +111,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 +122,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)
@@ -125,23 +133,72 @@ func precompilegen(c *cli.Context) error {
}
types = append(types, kind)
+ pkg := c.String(pkgFlag.Name)
+ if pkg == "" {
+ pkg = strings.ToLower(kind)
+ }
+
+ if outFlagStr == "" {
+ outFlagStr = filepath.Join("./precompile/contracts", pkg)
+ }
+
+ abifilename := ""
+ abipath := ""
+ // we should not generate the abi file if output is set to stdout
+ if !isOutStdout {
+ // get file name from the output path
+ abifilename = "contract.abi"
+ abipath = filepath.Join(outFlagStr, abifilename)
+ }
// Generate the contract precompile
- code, err := bind.Bind(types, abis, bins, sigs, pkg, lang, libs, aliases, true)
+ configCode, contractCode, moduleCode, err := precompilebind.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
- if !c.IsSet(outFlag.Name) {
- 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)
+ fmt.Print("-----Module Code-----\n")
+ fmt.Printf("%s\n", moduleCode)
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.Stat(outFlagStr); os.IsNotExist(err) {
+ os.MkdirAll(outFlagStr, 0o700) // 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 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 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)
+ }
+
+ 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 Generation was a success!")
+ 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/commontype/fee_config.go b/commontype/fee_config.go
index e1dee3182a..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.FeeConfigManager
-// 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.
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/contract-examples/README.md b/contract-examples/README.md
index 6fbbc5f513..1e4b2c7cb0 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/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 f30a7fe553..6d1b56f77a 100644
--- a/core/blockchain_reader.go
+++ b/core/blockchain_reader.go
@@ -39,7 +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/contracts/feemanager"
+ "github.com/ava-labs/subnet-evm/precompile/contracts/rewardmanager"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
)
@@ -342,13 +343,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.IsFeeConfigManager(bigTime) {
+ if !config.IsPrecompileEnabled(feemanager.ContractAddress, bigTime) {
return config.FeeConfig, common.Big0, nil
}
@@ -366,7 +367,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 +375,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)
@@ -392,7 +393,7 @@ func (bc *BlockChain) GetCoinbaseAt(parent *types.Header) (common.Address, bool,
return constants.BlackholeAddr, false, nil
}
- if !config.IsRewardManager(bigTime) {
+ if !config.IsPrecompileEnabled(rewardmanager.ContractAddress, bigTime) {
if bc.chainConfig.AllowFeeRecipients {
return common.Address{}, true, nil
} else {
@@ -413,7 +414,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/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/genesis.go b/core/genesis.go
index eb88bc82f4..07a0ac1bc0 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 = ApplyPrecompileActivations(g.Config, 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/genesis_test.go b/core/genesis_test.go
index 920e5337b6..90ecc54984 100644
--- a/core/genesis_test.go
+++ b/core/genesis_test.go
@@ -38,7 +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/contracts/deployerallowlist"
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
@@ -190,12 +191,14 @@ 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.GenesisPrecompiles = params.Precompiles{
+ deployerallowlist.ConfigKey: deployerallowlist.NewConfig(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, 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))
},
},
} {
@@ -265,11 +268,10 @@ func TestPrecompileActivationAfterHeaderBlock(t *testing.T) {
require.Greater(block.Time(), bc.lastAccepted.Time())
activatedGenesis := customg
- contractDeployerConfig := precompile.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/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/core/state_processor.go b/core/state_processor.go
index 2b28f8b082..2634f83640 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -35,8 +35,11 @@ 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"
)
// StateProcessor is a basic Processor, which takes care of transitioning
@@ -78,7 +81,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 := 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
+ }
blockContext := NewEVMBlockContext(header, p.bc, nil)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
@@ -163,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 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 6bf05057d9..b48f612333 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/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: precompile.NewTxAllowListConfig(big.NewInt(0), nil, nil),
+ GenesisPrecompiles: params.Precompiles{
+ 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 d039d0694c..ccb72beeaa 100644
--- a/core/state_transition.go
+++ b/core/state_transition.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/contracts/txallowlist"
"github.com/ava-labs/subnet-evm/vmerrs"
"github.com/ethereum/go-ethereum/common"
)
@@ -249,10 +249,10 @@ 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) {
- txAllowListRole := precompile.GetTxAllowListStatus(st.state, st.msg.From())
+ 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", precompile.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 702c8715b4..0000000000
--- a/core/stateful_precompile_test.go
+++ /dev/null
@@ -1,1288 +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/vmerrs"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/math"
- "github.com/stretchr/testify/require"
-)
-
-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 := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListAdmin)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedRes: []byte{},
- assertState: func(t *testing.T, state *state.StateDB) {
- res := precompile.GetContractDeployerAllowListStatus(state, noRoleAddr)
- require.Equal(t, precompile.AllowListAdmin, res)
- },
- },
- "set deployer": {
- caller: adminAddr,
- input: func() []byte {
- input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedRes: []byte{},
- assertState: func(t *testing.T, state *state.StateDB) {
- res := precompile.GetContractDeployerAllowListStatus(state, noRoleAddr)
- require.Equal(t, precompile.AllowListEnabled, res)
- },
- },
- "set no role": {
- caller: adminAddr,
- input: func() []byte {
- input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedRes: []byte{},
- assertState: func(t *testing.T, state *state.StateDB) {
- res := precompile.GetContractDeployerAllowListStatus(state, adminAddr)
- require.Equal(t, precompile.AllowListNoRole, res)
- },
- },
- "set no role from non-admin": {
- caller: noRoleAddr,
- input: func() []byte {
- input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedErr: precompile.ErrCannotModifyAllowList.Error(),
- },
- "set deployer from non-admin": {
- caller: noRoleAddr,
- input: func() []byte {
- input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListEnabled)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedErr: precompile.ErrCannotModifyAllowList.Error(),
- },
- "set admin from non-admin": {
- caller: noRoleAddr,
- input: func() []byte {
- input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListAdmin)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedErr: precompile.ErrCannotModifyAllowList.Error(),
- },
- "set no role with readOnly enabled": {
- caller: adminAddr,
- input: func() []byte {
- input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: true,
- expectedErr: vmerrs.ErrWriteProtection.Error(),
- },
- "set no role insufficient gas": {
- caller: adminAddr,
- input: func() []byte {
- input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost - 1,
- readOnly: false,
- expectedErr: vmerrs.ErrOutOfGas.Error(),
- },
- "read allow list no role": {
- caller: noRoleAddr,
- input: func() []byte {
- return precompile.PackReadAllowList(noRoleAddr)
- },
- suppliedGas: precompile.ReadAllowListGasCost,
- readOnly: false,
- expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(),
- assertState: nil,
- },
- "read allow list admin role": {
- caller: adminAddr,
- input: func() []byte {
- return precompile.PackReadAllowList(noRoleAddr)
- },
- suppliedGas: precompile.ReadAllowListGasCost,
- readOnly: false,
- expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(),
- assertState: nil,
- },
- "read allow list with readOnly enabled": {
- caller: adminAddr,
- input: func() []byte {
- return precompile.PackReadAllowList(noRoleAddr)
- },
- suppliedGas: precompile.ReadAllowListGasCost,
- readOnly: true,
- expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(),
- assertState: nil,
- },
- "read allow list out of gas": {
- caller: adminAddr,
- input: func() []byte {
- return precompile.PackReadAllowList(noRoleAddr)
- },
- suppliedGas: precompile.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.
- 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))
-
- 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)
- 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 := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListAdmin)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedRes: []byte{},
- assertState: func(t *testing.T, state *state.StateDB) {
- res := precompile.GetTxAllowListStatus(state, noRoleAddr)
- require.Equal(t, precompile.AllowListAdmin, res)
- },
- },
- "set allowed": {
- caller: adminAddr,
- input: func() []byte {
- input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedRes: []byte{},
- assertState: func(t *testing.T, state *state.StateDB) {
- res := precompile.GetTxAllowListStatus(state, noRoleAddr)
- require.Equal(t, precompile.AllowListEnabled, res)
- },
- },
- "set no role": {
- caller: adminAddr,
- input: func() []byte {
- input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedRes: []byte{},
- assertState: func(t *testing.T, state *state.StateDB) {
- res := precompile.GetTxAllowListStatus(state, adminAddr)
- require.Equal(t, precompile.AllowListNoRole, res)
- },
- },
- "set no role from non-admin": {
- caller: noRoleAddr,
- input: func() []byte {
- input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedErr: precompile.ErrCannotModifyAllowList.Error(),
- },
- "set allowed from non-admin": {
- caller: noRoleAddr,
- input: func() []byte {
- input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListEnabled)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedErr: precompile.ErrCannotModifyAllowList.Error(),
- },
- "set admin from non-admin": {
- caller: noRoleAddr,
- input: func() []byte {
- input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListAdmin)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedErr: precompile.ErrCannotModifyAllowList.Error(),
- },
- "set no role with readOnly enabled": {
- caller: adminAddr,
- precompileAddr: precompile.TxAllowListAddress,
- input: func() []byte {
- input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: true,
- expectedErr: vmerrs.ErrWriteProtection.Error(),
- },
- "set no role insufficient gas": {
- caller: adminAddr,
- input: func() []byte {
- input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost - 1,
- readOnly: false,
- expectedErr: vmerrs.ErrOutOfGas.Error(),
- },
- "read allow list no role": {
- caller: noRoleAddr,
- input: func() []byte {
- return precompile.PackReadAllowList(noRoleAddr)
- },
- suppliedGas: precompile.ReadAllowListGasCost,
- readOnly: false,
- expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(),
- assertState: nil,
- },
- "read allow list admin role": {
- caller: adminAddr,
- input: func() []byte {
- return precompile.PackReadAllowList(noRoleAddr)
- },
- suppliedGas: precompile.ReadAllowListGasCost,
- readOnly: false,
- expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(),
- assertState: nil,
- },
- "read allow list with readOnly enabled": {
- caller: adminAddr,
- input: func() []byte {
- return precompile.PackReadAllowList(noRoleAddr)
- },
- suppliedGas: precompile.ReadAllowListGasCost,
- readOnly: true,
- expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(),
- assertState: nil,
- },
- "read allow list out of gas": {
- caller: adminAddr,
- input: func() []byte {
- return precompile.PackReadAllowList(noRoleAddr)
- },
- suppliedGas: precompile.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.
- precompile.SetTxAllowListStatus(state, adminAddr, precompile.AllowListAdmin)
- require.Equal(t, precompile.AllowListAdmin, precompile.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)
- 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 *precompile.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 := precompile.PackMintInput(noRoleAddr, common.Big1)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.MintGasCost,
- readOnly: false,
- expectedErr: precompile.ErrCannotMint.Error(),
- },
- "mint funds from enabled address": {
- caller: enabledAddr,
- input: func() []byte {
- input, err := precompile.PackMintInput(enabledAddr, common.Big1)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.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 precompile.PackReadAllowList(testAddr)
- },
- suppliedGas: precompile.ReadAllowListGasCost,
- readOnly: false,
- expectedRes: common.Hash(precompile.AllowListEnabled).Bytes(),
- assertState: func(t *testing.T, state *state.StateDB) {
- require.Equal(t, precompile.AllowListEnabled, precompile.GetContractNativeMinterStatus(state, testAddr))
- },
- config: &precompile.ContractNativeMinterConfig{
- AllowListConfig: precompile.AllowListConfig{EnabledAddresses: []common.Address{testAddr}},
- },
- },
- "initial mint funds": {
- caller: enabledAddr,
- config: &precompile.ContractNativeMinterConfig{
- InitialMint: map[common.Address]*math.HexOrDecimal256{
- enabledAddr: math.NewHexOrDecimal256(2),
- },
- },
- input: func() []byte {
- return precompile.PackReadAllowList(noRoleAddr)
- },
- suppliedGas: precompile.ReadAllowListGasCost,
- readOnly: false,
- expectedRes: common.Hash(precompile.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 := precompile.PackMintInput(adminAddr, common.Big1)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.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 := precompile.PackMintInput(adminAddr, math.MaxBig256)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.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 := precompile.PackMintInput(adminAddr, common.Big1)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.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)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.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)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.MintGasCost,
- readOnly: true,
- expectedErr: vmerrs.ErrWriteProtection.Error(),
- },
- "insufficient gas mint from admin": {
- caller: adminAddr,
- input: func() []byte {
- input, err := precompile.PackMintInput(enabledAddr, common.Big1)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.MintGasCost - 1,
- readOnly: false,
- expectedErr: vmerrs.ErrOutOfGas.Error(),
- },
- "read from noRole address": {
- caller: noRoleAddr,
- input: func() []byte {
- return precompile.PackReadAllowList(noRoleAddr)
- },
- suppliedGas: precompile.ReadAllowListGasCost,
- readOnly: false,
- expectedRes: common.Hash(precompile.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)
- },
- suppliedGas: precompile.ReadAllowListGasCost,
- readOnly: true,
- expectedRes: common.Hash(precompile.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)
- },
- suppliedGas: precompile.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)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedRes: []byte{},
- assertState: func(t *testing.T, state *state.StateDB) {
- res := precompile.GetContractNativeMinterStatus(state, noRoleAddr)
- require.Equal(t, precompile.AllowListEnabled, res)
- },
- },
- "set allow role from non-admin fails": {
- caller: enabledAddr,
- input: func() []byte {
- input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedErr: precompile.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.
- 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))
-
- 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)
- 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 TestFeeConfigManagerRun(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 *precompile.FeeConfigManagerConfig
-
- 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 := precompile.PackSetFeeConfig(testFeeConfig)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.SetFeeConfigGasCost,
- readOnly: false,
- expectedErr: precompile.ErrCannotChangeFee.Error(),
- },
- "set config from enabled address": {
- caller: enabledAddr,
- input: func() []byte {
- input, err := precompile.PackSetFeeConfig(testFeeConfig)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.SetFeeConfigGasCost,
- readOnly: false,
- expectedRes: []byte{},
- assertState: func(t *testing.T, state *state.StateDB) {
- feeConfig := precompile.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 := precompile.PackSetFeeConfig(feeConfig)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.SetFeeConfigGasCost,
- readOnly: false,
- expectedRes: nil,
- config: &precompile.FeeConfigManagerConfig{
- InitialFeeConfig: &testFeeConfig,
- },
- expectedErr: "cannot be greater than maxBlockGasCost",
- assertState: func(t *testing.T, state *state.StateDB) {
- feeConfig := precompile.GetStoredFeeConfig(state)
- require.Equal(t, testFeeConfig, feeConfig)
- },
- },
- "set config from admin address": {
- caller: adminAddr,
- input: func() []byte {
- input, err := precompile.PackSetFeeConfig(testFeeConfig)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.SetFeeConfigGasCost,
- readOnly: false,
- expectedRes: []byte{},
- assertState: func(t *testing.T, state *state.StateDB) {
- feeConfig := precompile.GetStoredFeeConfig(state)
- require.Equal(t, testFeeConfig, feeConfig)
- lastChangedAt := precompile.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)})
- require.NoError(t, err)
- },
- input: func() []byte {
- return precompile.PackGetFeeConfigInput()
- },
- suppliedGas: precompile.GetFeeConfigGasCost,
- readOnly: true,
- expectedRes: func() []byte {
- res, err := precompile.PackFeeConfig(testFeeConfig)
- require.NoError(t, err)
- return res
- }(),
- assertState: func(t *testing.T, state *state.StateDB) {
- feeConfig := precompile.GetStoredFeeConfig(state)
- lastChangedAt := precompile.GetFeeConfigLastChangedAt(state)
- require.Equal(t, testFeeConfig, feeConfig)
- require.EqualValues(t, big.NewInt(6), lastChangedAt)
- },
- },
- "get initial fee config": {
- caller: noRoleAddr,
- input: func() []byte {
- return precompile.PackGetFeeConfigInput()
- },
- suppliedGas: precompile.GetFeeConfigGasCost,
- config: &precompile.FeeConfigManagerConfig{
- InitialFeeConfig: &testFeeConfig,
- },
- readOnly: true,
- expectedRes: func() []byte {
- res, err := precompile.PackFeeConfig(testFeeConfig)
- require.NoError(t, err)
- return res
- }(),
- assertState: func(t *testing.T, state *state.StateDB) {
- feeConfig := precompile.GetStoredFeeConfig(state)
- lastChangedAt := precompile.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 := precompile.StoreFeeConfig(state, testFeeConfig, &mockBlockContext{blockNumber: testBlockNumber})
- require.NoError(t, err)
- },
- input: func() []byte {
- return precompile.PackGetLastChangedAtInput()
- },
- suppliedGas: precompile.GetLastChangedAtGasCost,
- readOnly: true,
- expectedRes: common.BigToHash(testBlockNumber).Bytes(),
- assertState: func(t *testing.T, state *state.StateDB) {
- feeConfig := precompile.GetStoredFeeConfig(state)
- lastChangedAt := precompile.GetFeeConfigLastChangedAt(state)
- require.Equal(t, testFeeConfig, feeConfig)
- require.Equal(t, testBlockNumber, lastChangedAt)
- },
- },
- "readOnly setFeeConfig with noRole fails": {
- caller: noRoleAddr,
- input: func() []byte {
- input, err := precompile.PackSetFeeConfig(testFeeConfig)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.SetFeeConfigGasCost,
- readOnly: true,
- expectedErr: vmerrs.ErrWriteProtection.Error(),
- },
- "readOnly setFeeConfig with allow role fails": {
- caller: enabledAddr,
- input: func() []byte {
- input, err := precompile.PackSetFeeConfig(testFeeConfig)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.SetFeeConfigGasCost,
- readOnly: true,
- expectedErr: vmerrs.ErrWriteProtection.Error(),
- },
- "readOnly setFeeConfig with admin role fails": {
- caller: adminAddr,
- input: func() []byte {
- input, err := precompile.PackSetFeeConfig(testFeeConfig)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.SetFeeConfigGasCost,
- readOnly: true,
- expectedErr: vmerrs.ErrWriteProtection.Error(),
- },
- "insufficient gas setFeeConfig from admin": {
- caller: adminAddr,
- input: func() []byte {
- input, err := precompile.PackSetFeeConfig(testFeeConfig)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.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)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedRes: []byte{},
- assertState: func(t *testing.T, state *state.StateDB) {
- res := precompile.GetFeeConfigManagerStatus(state, noRoleAddr)
- require.Equal(t, precompile.AllowListEnabled, res)
- },
- },
- "set allow role from non-admin fails": {
- caller: enabledAddr,
- input: func() []byte {
- input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedErr: precompile.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.
- precompile.SetFeeConfigManagerStatus(state, adminAddr, precompile.AllowListAdmin)
- precompile.SetFeeConfigManagerStatus(state, enabledAddr, precompile.AllowListEnabled)
- precompile.SetFeeConfigManagerStatus(state, noRoleAddr, precompile.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 := precompile.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 {
- 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 *precompile.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 := precompile.PackAllowFeeRecipients()
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.AllowFeeRecipientsGasCost,
- readOnly: false,
- expectedErr: precompile.ErrCannotAllowFeeRecipients.Error(),
- },
- "set reward address from no role fails": {
- caller: noRoleAddr,
- input: func() []byte {
- input, err := precompile.PackSetRewardAddress(testAddr)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.SetRewardAddressGasCost,
- readOnly: false,
- expectedErr: precompile.ErrCannotSetRewardAddress.Error(),
- },
- "disable rewards from no role fails": {
- caller: noRoleAddr,
- input: func() []byte {
- input, err := precompile.PackDisableRewards()
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.DisableRewardsGasCost,
- readOnly: false,
- expectedErr: precompile.ErrCannotDisableRewards.Error(),
- },
- "set allow fee recipients from enabled succeeds": {
- caller: enabledAddr,
- input: func() []byte {
- input, err := precompile.PackAllowFeeRecipients()
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.AllowFeeRecipientsGasCost,
- readOnly: false,
- expectedRes: []byte{},
- assertState: func(t *testing.T, state *state.StateDB) {
- _, isFeeRecipients := precompile.GetStoredRewardAddress(state)
- require.True(t, isFeeRecipients)
- },
- },
- "set reward address from enabled succeeds": {
- caller: enabledAddr,
- input: func() []byte {
- input, err := precompile.PackSetRewardAddress(testAddr)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.SetRewardAddressGasCost,
- readOnly: false,
- expectedRes: []byte{},
- assertState: func(t *testing.T, state *state.StateDB) {
- address, isFeeRecipients := precompile.GetStoredRewardAddress(state)
- require.Equal(t, testAddr, address)
- require.False(t, isFeeRecipients)
- },
- },
- "disable rewards from enabled succeeds": {
- caller: enabledAddr,
- input: func() []byte {
- input, err := precompile.PackDisableRewards()
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.DisableRewardsGasCost,
- readOnly: false,
- expectedRes: []byte{},
- assertState: func(t *testing.T, state *state.StateDB) {
- address, isFeeRecipients := precompile.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) {
- precompile.StoreRewardAddress(state, testAddr)
- },
- input: func() []byte {
- input, err := precompile.PackCurrentRewardAddress()
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.CurrentRewardAddressGasCost,
- readOnly: false,
- expectedRes: func() []byte {
- res, err := precompile.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) {
- precompile.EnableAllowFeeRecipients(state)
- },
- input: func() []byte {
- input, err := precompile.PackAreFeeRecipientsAllowed()
- require.NoError(t, err)
- return input
- },
- suppliedGas: precompile.AreFeeRecipientsAllowedGasCost,
- readOnly: false,
- expectedRes: func() []byte {
- res, err := precompile.PackAreFeeRecipientsAllowedOutput(true)
- require.NoError(t, err)
- return res
- }(),
- },
- "get initial config with address": {
- caller: noRoleAddr,
- input: func() []byte {
- input, err := precompile.PackCurrentRewardAddress()
- require.NoError(t, err)
- return input
- },
- suppliedGas: precompile.CurrentRewardAddressGasCost,
- config: &precompile.RewardManagerConfig{
- InitialRewardConfig: &precompile.InitialRewardConfig{
- RewardAddress: testAddr,
- },
- },
- readOnly: false,
- expectedRes: func() []byte {
- res, err := precompile.PackCurrentRewardAddressOutput(testAddr)
- require.NoError(t, err)
- return res
- }(),
- },
- "get initial config with allow fee recipients enabled": {
- caller: noRoleAddr,
- input: func() []byte {
- input, err := precompile.PackAreFeeRecipientsAllowed()
- require.NoError(t, err)
- return input
- },
- suppliedGas: precompile.AreFeeRecipientsAllowedGasCost,
- config: &precompile.RewardManagerConfig{
- InitialRewardConfig: &precompile.InitialRewardConfig{
- AllowFeeRecipients: true,
- },
- },
- readOnly: false,
- expectedRes: func() []byte {
- res, err := precompile.PackAreFeeRecipientsAllowedOutput(true)
- require.NoError(t, err)
- return res
- }(),
- },
- "readOnly allow fee recipients with allowed role fails": {
- caller: enabledAddr,
- input: func() []byte {
- input, err := precompile.PackAllowFeeRecipients()
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.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)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.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)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.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()
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.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()
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.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()
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.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)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedRes: []byte{},
- assertState: func(t *testing.T, state *state.StateDB) {
- res := precompile.GetRewardManagerAllowListStatus(state, noRoleAddr)
- require.Equal(t, precompile.AllowListEnabled, res)
- },
- },
- "set allow role from non-admin fails": {
- caller: enabledAddr,
- input: func() []byte {
- input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled)
- require.NoError(t, err)
-
- return input
- },
- suppliedGas: precompile.ModifyAllowListGasCost,
- readOnly: false,
- expectedErr: precompile.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.
- precompile.SetRewardManagerAllowListStatus(state, adminAddr, precompile.AllowListAdmin)
- precompile.SetRewardManagerAllowListStatus(state, enabledAddr, precompile.AllowListEnabled)
- precompile.SetRewardManagerAllowListStatus(state, noRoleAddr, precompile.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 := precompile.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 29b465a22f..de4650106b 100644
--- a/core/test_blockchain.go
+++ b/core/test_blockchain.go
@@ -16,7 +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/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"
@@ -1546,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 = precompile.NewContractDeployerAllowListConfig(big.NewInt(0), []common.Address{addr1}, nil)
- config.FeeManagerConfig = precompile.NewFeeManagerConfig(big.NewInt(0), []common.Address{addr1}, nil, nil)
+ 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),
+ }
gspec := &Genesis{
Config: &config,
Alloc: GenesisAlloc{addr1: {Balance: genesisBalance}},
@@ -1587,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 := precompile.PackModifyAllowList(addr2, precompile.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,
@@ -1609,38 +1613,38 @@ 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.AdminRole != res {
+ return fmt.Errorf("unexpected allow list status for addr1 %s, expected %s", res, allowlist.AdminRole)
}
- 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.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 := 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.AdminRole != res {
+ t.Fatalf("unexpected allow list status for addr1 %s, expected %s", res, allowlist.AdminRole)
}
- 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.NoRole != res {
+ t.Fatalf("unexpected allow list status for addr2 %s, expected %s", res, allowlist.NoRole)
}
},
},
"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)
}
tx := types.NewTx(&types.DynamicFeeTx{
ChainID: params.TestChainConfig.ChainID,
Nonce: gen.TxNonce(addr1),
- To: &precompile.FeeConfigManagerAddress,
+ To: &feemanager.ContractAddress,
Gas: 3_000_000,
Value: common.Big0,
GasFeeCap: feeCap,
@@ -1655,10 +1659,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.GetFeeManagerStatus(sdb, addr1)
+ assert.Equal(allowlist.AdminRole, res)
- storedConfig := precompile.GetStoredFeeConfig(sdb)
+ storedConfig := feemanager.GetStoredFeeConfig(sdb)
assert.EqualValues(testFeeConfig, storedConfig)
feeConfig, _, err := blockchain.GetFeeConfigAt(blockchain.CurrentHeader())
@@ -1667,8 +1671,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.GetFeeManagerStatus(sdb, addr1)
+ 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 ed5502d94b..dd3e4a4f39 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -42,7 +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/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"
@@ -693,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.IsTxAllowList(headTimestamp) {
- txAllowListRole := precompile.GetTxAllowListStatus(pool.currentState, from)
+ if pool.chainconfig.IsPrecompileEnabled(txallowlist.ContractAddress, headTimestamp) {
+ txAllowListRole := txallowlist.GetTxAllowListStatus(pool.currentState, from)
if !txAllowListRole.IsEnabled() {
- return fmt.Errorf("%w: %s", precompile.ErrSenderAddressNotAllowListed, from)
+ return fmt.Errorf("%w: %s", vmerrs.ErrSenderAddressNotAllowListed, from)
}
}
return nil
@@ -1441,7 +1444,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(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..2d8ec6b79d 100644
--- a/core/vm/contracts.go
+++ b/core/vm/contracts.go
@@ -33,9 +33,9 @@ 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"
+ "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 +57,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 +66,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 +79,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 +93,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 +107,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 +158,10 @@ 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))
- }
- if k == constants.BlackholeAddr {
- panic(fmt.Errorf("cannot use address %s for stateful precompile - overlaps with blackhole address", 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))
}
}
}
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 c13db0def9..764bce4e65 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -35,7 +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/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"
@@ -43,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
@@ -54,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
@@ -71,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
@@ -91,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
@@ -207,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
}
@@ -507,8 +513,8 @@ 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 {
- allowListRole := precompile.GetContractDeployerAllowListStatus(evm.StateDB, evm.TxContext.Origin)
+ 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 1554073693..253778c6af 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/contracts/feemanager"
"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(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 62e770564f..45672a53f5 100644
--- a/eth/gasprice/gasprice_test.go
+++ b/eth/gasprice/gasprice_test.go
@@ -40,7 +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/contracts/feemanager"
"github.com/ava-labs/subnet-evm/rpc"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
@@ -434,13 +434,15 @@ 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.GenesisPrecompiles = params.Precompiles{
+ 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)
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
@@ -462,7 +464,7 @@ func TestSuggestGasPriceAfterFeeConfigUpdate(t *testing.T) {
tx := types.NewTx(&types.DynamicFeeTx{
ChainID: chainConfig.ChainID,
Nonce: b.TxNonce(addr),
- To: &precompile.FeeConfigManagerAddress,
+ 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 ebf872015d..8ace36596f 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)
}
-func (s *BlockChainAPI) GetActivePrecompilesAt(ctx context.Context, blockTimestamp *big.Int) params.PrecompileUpgrade {
+// GetActivePrecompilesAt returns the active precompile configs at the given block timestamp.
+func (s *BlockChainAPI) GetActivePrecompilesAt(ctx context.Context, blockTimestamp *big.Int) params.Precompiles {
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().GetActivePrecompiles(blockTimestamp)
+
+ return s.b.ChainConfig().EnabledStatefulPrecompiles(blockTimestamp)
}
type FeeConfigResult struct {
diff --git a/miner/worker.go b/miner/worker.go
index d56c06cafd..26a9c31168 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 {
@@ -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 = 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
+ }
// Fill the block with all available pending transactions.
pending := w.eth.TxPool().Pending(true)
diff --git a/params/config.go b/params/config.go
index 7a3f013dcd..1f2289f2a9 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/modules"
+ "github.com/ava-labs/subnet-evm/precompile/precompileconfig"
"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: Precompiles{},
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: Precompiles{},
UpgradeConfig: UpgradeConfig{},
}
@@ -125,7 +126,7 @@ var (
IstanbulBlock: big.NewInt(0),
MuirGlacierBlock: big.NewInt(0),
NetworkUpgrades: NetworkUpgrades{},
- PrecompileUpgrade: PrecompileUpgrade{},
+ GenesisPrecompiles: Precompiles{},
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 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
+// 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")
}
@@ -272,46 +321,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.GetActivePrecompileConfig(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 {
@@ -339,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
@@ -539,20 +554,17 @@ 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
+ // 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]precompileconfig.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
}
// Rules ensures c's ChainID is not nil.
@@ -580,21 +592,13 @@ 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)
- for _, config := range c.EnabledStatefulPrecompiles(blockTimestamp) {
- if config.IsDisabled() {
- continue
+ 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
}
- rules.Precompiles[config.Address()] = config.Contract()
}
return rules
diff --git a/params/config_test.go b/params/config_test.go
index 50487f159a..a407c81bae 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,88 @@ 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)
+}
+
+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))
+}
diff --git a/params/precompile_config.go b/params/precompile_config.go
deleted file mode 100644
index 71ea6ee2dc..0000000000
--- a/params/precompile_config.go
+++ /dev/null
@@ -1,375 +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/utils"
- "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.
-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
- // ADD YOUR PRECOMPILE HERE
- // {YourPrecompile}Config *precompile.{YourPrecompile}Config `json:"{yourPrecompile}Config,omitempty"`
-}
-
-func (p *PrecompileUpgrade) getByKey(key precompileKey) (precompile.StatefulPrecompileConfig, bool) {
- switch key {
- case contractDeployerAllowListKey:
- return p.ContractDeployerAllowListConfig, p.ContractDeployerAllowListConfig != nil
- case contractNativeMinterKey:
- return p.ContractNativeMinterConfig, p.ContractNativeMinterConfig != nil
- case txAllowListKey:
- return p.TxAllowListConfig, p.TxAllowListConfig != nil
- case feeManagerKey:
- return p.FeeManagerConfig, p.FeeManagerConfig != nil
- case rewardManagerKey:
- return p.RewardManagerConfig, p.RewardManagerConfig != nil
- // ADD YOUR PRECOMPILE HERE
- /*
- case {yourPrecompile}Key:
- return p.{YourPrecompile}Config , p.{YourPrecompile}Config != nil
- */
- default:
- panic(fmt.Sprintf("unknown upgrade key: %v", key))
- }
-}
-
-// 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 _, key := range precompileKeys {
- config, ok := upgrade.getByKey(key)
- 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 _, key := range precompileKeys {
- var (
- lastUpgraded *big.Int
- disabled bool
- )
- // check the genesis chain config for any enabled upgrade
- if config, ok := c.PrecompileUpgrade.getByKey(key); ok && config.Timestamp() != nil {
- 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.getByKey(key)
- // Skip the upgrade if it's not relevant to [key].
- 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 [key].
-// 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)
- 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, key precompileKey, 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 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 {
- // Check if the precompile activates in the specified range.
- if utils.IsForkTransition(config.Timestamp(), from, to) {
- configs = append(configs, config)
- }
- }
- }
- 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)
- }
- 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 {
- pu := PrecompileUpgrade{}
- if config := c.GetContractDeployerAllowListConfig(blockTimestamp); config != nil && !config.Disable {
- pu.ContractDeployerAllowListConfig = config
- }
- if config := c.GetContractNativeMinterConfig(blockTimestamp); config != nil && !config.Disable {
- pu.ContractNativeMinterConfig = config
- }
- if config := c.GetTxAllowListConfig(blockTimestamp); config != nil && !config.Disable {
- pu.TxAllowListConfig = config
- }
- if config := c.GetFeeConfigManagerConfig(blockTimestamp); config != nil && !config.Disable {
- pu.FeeManagerConfig = config
- }
- if config := c.GetRewardManagerConfig(blockTimestamp); config != nil && !config.Disable {
- pu.RewardManagerConfig = config
- }
- // ADD YOUR PRECOMPILE HERE
- // if config := c.{YourPrecompile}Config(blockTimestamp); config != nil && !config.Disable {
- // pu.{YourPrecompile}Config = 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 _, key := range precompileKeys {
- if err := c.checkPrecompileCompatible(key, precompileUpgrades, lastTimestamp); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// checkPrecompileCompatible verifies that the precompile specified by [key] 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 {
- // all active upgrades must match
- activeUpgrades := c.getActivatingPrecompileConfigs(nil, lastTimestamp, key, c.PrecompileUpgrades)
- newUpgrades := c.getActivatingPrecompileConfigs(nil, lastTimestamp, key, 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 _, key := range precompileKeys {
- if config := c.getActivePrecompileConfig(blockTimestamp, key, c.PrecompileUpgrades); config != nil {
- statefulPrecompileConfigs = append(statefulPrecompileConfigs, config)
- }
- }
-
- return statefulPrecompileConfigs
-}
-
-// CheckConfigurePrecompiles 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) {
- blockTimestamp := blockContext.Timestamp()
- for _, key := range precompileKeys { // Note: configure precompiles in a deterministic order.
- for _, config := range c.getActivatingPrecompileConfigs(parentTimestamp, blockTimestamp, key, 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)
- 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", "name", key, "config", config)
- precompile.Configure(c, blockContext, config, statedb)
- }
- }
- }
-}
diff --git a/params/precompile_config_test.go b/params/precompile_config_test.go
index 42611899cc..ec81ecaf36 100644
--- a/params/precompile_config_test.go
+++ b/params/precompile_config_test.go
@@ -4,13 +4,17 @@
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/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"
)
@@ -18,60 +22,60 @@ func TestVerifyWithChainConfig(t *testing.T) {
admins := []common.Address{{1}}
baseConfig := *SubnetEVMDefaultChainConfig
config := &baseConfig
- config.PrecompileUpgrade = PrecompileUpgrade{
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(2), nil, nil),
+ config.GenesisPrecompiles = Precompiles{
+ txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(2), nil, nil),
}
config.PrecompileUpgrades = []PrecompileUpgrade{
{
// disable TxAllowList at timestamp 4
- TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(4)),
+ txallowlist.NewDisableConfig(big.NewInt(4)),
},
{
// re-enable TxAllowList at timestamp 5
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(5), admins, nil),
+ txallowlist.NewConfig(big.NewInt(5), admins, nil),
},
}
// 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
badConfig.PrecompileUpgrades = append(
badConfig.PrecompileUpgrades,
PrecompileUpgrade{
- TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(5)),
+ Config: txallowlist.NewDisableConfig(big.NewInt(5)),
},
)
err = badConfig.Verify()
- assert.ErrorContains(t, err, "config timestamp (5) <= previous timestamp (5)")
+ require.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: precompile.NewTxAllowListConfig(big.NewInt(5), admins, nil),
+ Config: txallowlist.NewConfig(big.NewInt(5), admins, nil),
},
)
err = badConfig.Verify()
- assert.ErrorContains(t, err, "disable should be [true]")
+ require.ErrorContains(t, err, "disable should be [true]")
}
func TestVerifyWithChainConfigAtNilTimestamp(t *testing.T) {
admins := []common.Address{{1}}
baseConfig := *SubnetEVMDefaultChainConfig
config := &baseConfig
- config.PrecompileUpgrade = PrecompileUpgrade{
+ config.PrecompileUpgrades = []PrecompileUpgrade{
// this does NOT enable the precompile, so it should be upgradeable.
- TxAllowListConfig: precompile.NewTxAllowListConfig(nil, nil, nil),
+ {Config: txallowlist.NewConfig(nil, nil, nil)},
}
- require.False(t, config.IsTxAllowList(common.Big0)) // check the precompile is not enabled.
+ require.False(t, config.IsPrecompileEnabled(txallowlist.ContractAddress, common.Big0)) // check the precompile is not enabled.
config.PrecompileUpgrades = []PrecompileUpgrade{
{
// enable TxAllowList at timestamp 5
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(5), admins, nil),
+ Config: txallowlist.NewConfig(big.NewInt(5), admins, nil),
},
}
@@ -90,10 +94,10 @@ func TestVerifyPrecompileUpgrades(t *testing.T) {
name: "enable and disable tx allow list",
upgrades: []PrecompileUpgrade{
{
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(1), admins, nil),
+ Config: txallowlist.NewConfig(big.NewInt(1), admins, nil),
},
{
- TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(2)),
+ Config: txallowlist.NewDisableConfig(big.NewInt(2)),
},
},
expectedError: "",
@@ -102,13 +106,13 @@ func TestVerifyPrecompileUpgrades(t *testing.T) {
name: "invalid allow list config in tx allowlist",
upgrades: []PrecompileUpgrade{
{
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(1), admins, nil),
+ Config: txallowlist.NewConfig(big.NewInt(1), admins, nil),
},
{
- TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(2)),
+ Config: txallowlist.NewDisableConfig(big.NewInt(2)),
},
{
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(3), admins, admins),
+ Config: txallowlist.NewConfig(big.NewInt(3), admins, admins),
},
},
expectedError: "cannot set address",
@@ -117,7 +121,7 @@ func TestVerifyPrecompileUpgrades(t *testing.T) {
name: "invalid initial fee manager config",
upgrades: []PrecompileUpgrade{
{
- FeeManagerConfig: precompile.NewFeeManagerConfig(big.NewInt(3), admins, nil,
+ Config: feemanager.NewConfig(big.NewInt(3), admins, nil,
func() *commontype.FeeConfig {
feeConfig := DefaultFeeConfig
feeConfig.GasLimit = big.NewInt(-1)
@@ -131,7 +135,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,
+ Config: feemanager.NewConfig(big.NewInt(3), admins, nil,
func() *commontype.FeeConfig {
feeConfig := DefaultFeeConfig
feeConfig.GasLimit = common.Big0
@@ -141,6 +145,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) {
@@ -163,20 +203,20 @@ func TestVerifyPrecompiles(t *testing.T) {
admins := []common.Address{{1}}
tests := []struct {
name string
- upgrade PrecompileUpgrade
+ precompiles Precompiles
expectedError string
}{
{
name: "invalid allow list config in tx allowlist",
- upgrade: PrecompileUpgrade{
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(3), admins, admins),
+ precompiles: Precompiles{
+ txallowlist.ConfigKey: txallowlist.NewConfig(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,
+ precompiles: Precompiles{
+ feemanager.ConfigKey: feemanager.NewConfig(big.NewInt(3), admins, nil,
func() *commontype.FeeConfig {
feeConfig := DefaultFeeConfig
feeConfig.GasLimit = big.NewInt(-1)
@@ -191,7 +231,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 == "" {
@@ -209,35 +249,93 @@ func TestVerifyRequiresSortedTimestamps(t *testing.T) {
config := &baseConfig
config.PrecompileUpgrades = []PrecompileUpgrade{
{
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(2), admins, nil),
+ Config: txallowlist.NewConfig(big.NewInt(2), admins, nil),
},
{
- ContractDeployerAllowListConfig: precompile.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)")
+ 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.PrecompileUpgrade = PrecompileUpgrade{
- ContractDeployerAllowListConfig: precompile.NewContractDeployerAllowListConfig(big.NewInt(10), nil, nil),
+ config.GenesisPrecompiles = Precompiles{
+ deployerallowlist.ConfigKey: deployerallowlist.NewConfig(big.NewInt(10), nil, nil),
}
- deployerConfig := config.GetContractDeployerAllowListConfig(big.NewInt(0))
- assert.Nil(deployerConfig)
+ deployerConfig := config.GetActivePrecompileConfig(deployerallowlist.ContractAddress, big.NewInt(0))
+ require.Nil(deployerConfig)
+
+ deployerConfig = config.GetActivePrecompileConfig(deployerallowlist.ContractAddress, big.NewInt(10))
+ require.NotNil(deployerConfig)
- deployerConfig = config.GetContractDeployerAllowListConfig(big.NewInt(10))
- assert.NotNil(deployerConfig)
+ deployerConfig = config.GetActivePrecompileConfig(deployerallowlist.ContractAddress, big.NewInt(11))
+ require.NotNil(deployerConfig)
+
+ txAllowListConfig := config.GetActivePrecompileConfig(txallowlist.ContractAddress, big.NewInt(0))
+ require.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))
- deployerConfig = config.GetContractDeployerAllowListConfig(big.NewInt(11))
- assert.NotNil(deployerConfig)
+ nativeMinterConfig := upgradeConfig.PrecompileUpgrades[1]
+ require.Equal(nativeMinterConfig.Key(), nativeminter.ConfigKey)
+ expectedNativeMinterConfig := nativeminter.NewConfig(big.NewInt(1671543172), nil, nil, nil)
+ require.True(nativeMinterConfig.Equal(expectedNativeMinterConfig))
- txAllowListConfig := config.GetTxAllowListConfig(big.NewInt(0))
- assert.Nil(txAllowListConfig)
+ // 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..98a32d4d42
--- /dev/null
+++ b/params/precompile_upgrade.go
@@ -0,0 +1,259 @@
+// (c) 2023 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/modules"
+ "github.com/ava-labs/subnet-evm/precompile/precompileconfig"
+ "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 {
+ precompileconfig.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.MakeConfig()
+ 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) precompileconfig.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) []precompileconfig.Config {
+ // Get key from address.
+ module, ok := modules.GetPrecompileModuleByAddress(address)
+ if !ok {
+ return nil
+ }
+ configs := make([]precompileconfig.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) Precompiles {
+ statefulPrecompileConfigs := make(Precompiles)
+ 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 62%
rename from params/upgrade_config_test.go
rename to params/precompile_upgrade_test.go
index 38c93f5386..54078aad3b 100644
--- a/params/upgrade_config_test.go
+++ b/params/precompile_upgrade_test.go
@@ -7,15 +7,18 @@ import (
"math/big"
"testing"
- "github.com/ava-labs/subnet-evm/precompile"
+ "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) {
admins := []common.Address{{1}}
chainConfig := *TestChainConfig
- chainConfig.TxAllowListConfig = precompile.NewTxAllowListConfig(big.NewInt(1), admins, nil)
+ chainConfig.GenesisPrecompiles = Precompiles{
+ txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(1), admins, nil),
+ }
type test struct {
upgrades []PrecompileUpgrade
@@ -27,23 +30,23 @@ func TestVerifyUpgradeConfig(t *testing.T) {
expectedErrorString: "disable should be [true]",
upgrades: []PrecompileUpgrade{
{
- TxAllowListConfig: precompile.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: precompile.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: precompile.NewDisableTxAllowListConfig(big.NewInt(1)),
+ Config: txallowlist.NewDisableConfig(big.NewInt(1)),
},
},
},
@@ -59,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)
}
})
}
@@ -70,8 +73,10 @@ 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.GenesisPrecompiles = Precompiles{
+ txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(1), admins, nil),
+ deployerallowlist.ConfigKey: deployerallowlist.NewConfig(big.NewInt(10), admins, nil),
+ }
type test struct {
configs []*UpgradeConfig
@@ -86,10 +91,10 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) {
{
PrecompileUpgrades: []PrecompileUpgrade{
{
- TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)),
+ Config: txallowlist.NewDisableConfig(big.NewInt(6)),
},
{
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil),
+ Config: txallowlist.NewConfig(big.NewInt(7), admins, nil),
},
},
},
@@ -101,20 +106,20 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) {
{
PrecompileUpgrades: []PrecompileUpgrade{
{
- TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)),
+ Config: txallowlist.NewDisableConfig(big.NewInt(6)),
},
{
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil),
+ Config: txallowlist.NewConfig(big.NewInt(7), admins, nil),
},
},
},
{
PrecompileUpgrades: []PrecompileUpgrade{
{
- TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)),
+ Config: txallowlist.NewDisableConfig(big.NewInt(6)),
},
{
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(8), admins, nil),
+ Config: txallowlist.NewConfig(big.NewInt(8), admins, nil),
},
},
},
@@ -127,20 +132,20 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) {
{
PrecompileUpgrades: []PrecompileUpgrade{
{
- TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)),
+ Config: txallowlist.NewDisableConfig(big.NewInt(6)),
},
{
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil),
+ Config: txallowlist.NewConfig(big.NewInt(7), admins, nil),
},
},
},
{
PrecompileUpgrades: []PrecompileUpgrade{
{
- TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)),
+ Config: txallowlist.NewDisableConfig(big.NewInt(6)),
},
{
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(8), admins, nil),
+ Config: txallowlist.NewConfig(big.NewInt(8), admins, nil),
},
},
},
@@ -152,17 +157,17 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) {
{
PrecompileUpgrades: []PrecompileUpgrade{
{
- TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)),
+ Config: txallowlist.NewDisableConfig(big.NewInt(6)),
},
{
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil),
+ Config: txallowlist.NewConfig(big.NewInt(7), admins, nil),
},
},
},
{
PrecompileUpgrades: []PrecompileUpgrade{
{
- TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)),
+ Config: txallowlist.NewDisableConfig(big.NewInt(6)),
},
},
},
@@ -175,17 +180,17 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) {
{
PrecompileUpgrades: []PrecompileUpgrade{
{
- TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)),
+ Config: txallowlist.NewDisableConfig(big.NewInt(6)),
},
{
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil),
+ Config: txallowlist.NewConfig(big.NewInt(7), admins, nil),
},
},
},
{
PrecompileUpgrades: []PrecompileUpgrade{
{
- TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)),
+ Config: txallowlist.NewDisableConfig(big.NewInt(6)),
},
},
},
@@ -198,21 +203,21 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) {
{
PrecompileUpgrades: []PrecompileUpgrade{
{
- TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)),
+ Config: txallowlist.NewDisableConfig(big.NewInt(6)),
},
{
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil),
+ Config: txallowlist.NewConfig(big.NewInt(7), admins, nil),
},
},
},
{
PrecompileUpgrades: []PrecompileUpgrade{
{
- TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)),
+ Config: txallowlist.NewDisableConfig(big.NewInt(6)),
},
{
// uses a different (empty) admin list, not allowed
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), []common.Address{}, nil),
+ Config: txallowlist.NewConfig(big.NewInt(7), []common.Address{}, nil),
},
},
},
@@ -224,20 +229,20 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) {
{
PrecompileUpgrades: []PrecompileUpgrade{
{
- TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)),
+ Config: txallowlist.NewDisableConfig(big.NewInt(6)),
},
{
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil),
+ Config: txallowlist.NewConfig(big.NewInt(7), admins, nil),
},
},
},
{
PrecompileUpgrades: []PrecompileUpgrade{
{
- TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)),
+ Config: txallowlist.NewDisableConfig(big.NewInt(6)),
},
{
- TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil),
+ Config: txallowlist.NewConfig(big.NewInt(7), admins, nil),
},
},
},
@@ -268,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/params/precompiles.go b/params/precompiles.go
new file mode 100644
index 0000000000..5d8ed74bda
--- /dev/null
+++ b/params/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/modules"
+ "github.com/ava-labs/subnet-evm/precompile/precompileconfig"
+)
+
+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
+// configuration.
+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(Precompiles)
+ for _, module := range modules.RegisteredModules() {
+ key := module.ConfigKey
+ if value, ok := raw[key]; ok {
+ conf := module.MakeConfig()
+ if err := json.Unmarshal(value, conf); err != nil {
+ return err
+ }
+ (*ccp)[key] = conf
+ }
+ }
+ return nil
+}
diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go
index ce6f80b3d8..bfff56904e 100644
--- a/plugin/evm/vm.go
+++ b/plugin/evm/vm.go
@@ -45,6 +45,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 2725596b48..8acca71c79 100644
--- a/plugin/evm/vm_test.go
+++ b/plugin/evm/vm_test.go
@@ -21,7 +21,11 @@ import (
"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"
+ "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"
+ "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"
@@ -2172,7 +2176,9 @@ 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.GenesisPrecompiles = params.Precompiles{
+ deployerallowlist.ConfigKey: deployerallowlist.NewConfig(big.NewInt(time.Now().Unix()), testEthAddrs, nil),
+ }
genesisJSON, err := genesis.MarshalJSON()
if err != nil {
@@ -2193,9 +2199,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.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.
@@ -2223,9 +2229,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.AdminRole {
+ t.Fatalf("Expected allow list status to be set role %s, but found: %s", allowlist.AdminRole, role)
}
}
@@ -2236,7 +2242,9 @@ 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.GenesisPrecompiles = params.Precompiles{
+ txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(0), testEthAddrs[0:1], nil),
+ }
genesisJSON, err := genesis.MarshalJSON()
if err != nil {
t.Fatal(err)
@@ -2258,13 +2266,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.AdminRole {
+ t.Fatalf("Expected allow list status to be set to admin: %s, but found: %s", allowlist.AdminRole, 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.NoRole {
+ t.Fatalf("Expected allow list status to be set to no role: %s, but found: %s", allowlist.NoRole, role)
}
// Submit a successful transaction
@@ -2285,7 +2293,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, vmerrs.ErrSenderAddressNotAllowListed) {
t.Fatalf("expected ErrSenderAddressNotAllowListed, got: %s", err)
}
@@ -2312,7 +2320,9 @@ 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.GenesisPrecompiles = params.Precompiles{
+ txallowlist.ConfigKey: txallowlist.NewConfig(big.NewInt(enableAllowListTimestamp.Unix()), testEthAddrs[0:1], nil),
+ }
genesisJSON, err := genesis.MarshalJSON()
if err != nil {
t.Fatal(err)
@@ -2353,13 +2363,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.AdminRole {
+ t.Fatalf("Expected allow list status to be set to admin: %s, but found: %s", allowlist.AdminRole, 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.NoRole {
+ t.Fatalf("Expected allow list status to be set to no role: %s, but found: %s", allowlist.NoRole, role)
}
// Submit a successful transaction
@@ -2380,7 +2390,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, vmerrs.ErrSenderAddressNotAllowListed) {
t.Fatalf("expected ErrSenderAddressNotAllowListed, got: %s", err)
}
@@ -2424,7 +2434,9 @@ 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.GenesisPrecompiles = params.Precompiles{
+ feemanager.ConfigKey: feemanager.NewConfig(big.NewInt(0), testEthAddrs[0:1], nil, nil),
+ }
// set a lower fee config now
testLowFeeConfig := commontype.FeeConfig{
@@ -2462,13 +2474,13 @@ 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 {
- t.Fatalf("Expected fee manager list status to be set to admin: %s, but found: %s", precompile.FeeConfigManagerAddress, role)
+ role := feemanager.GetFeeManagerStatus(genesisState, testEthAddrs[0])
+ if role != allowlist.AdminRole {
+ t.Fatalf("Expected fee manager list status to be set to admin: %s, but found: %s", allowlist.AdminRole, role)
}
- role = precompile.GetFeeConfigManagerStatus(genesisState, testEthAddrs[1])
- if role != precompile.AllowListNoRole {
- t.Fatalf("Expected fee manager list status to be set to no role: %s, but found: %s", precompile.FeeConfigManagerAddress, role)
+ 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.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())
@@ -2480,13 +2492,13 @@ 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{
ChainID: genesis.Config.ChainID,
Nonce: uint64(0),
- To: &precompile.FeeConfigManagerAddress,
+ 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
@@ -2522,7 +2534,7 @@ func TestFeeManagerChangeFee(t *testing.T) {
tx2 := types.NewTx(&types.DynamicFeeTx{
ChainID: genesis.Config.ChainID,
Nonce: uint64(1),
- To: &precompile.FeeConfigManagerAddress,
+ To: &feemanager.ContractAddress,
Gas: genesis.Config.FeeConfig.GasLimit.Uint64(),
Value: common.Big0,
GasFeeCap: testLowFeeConfig.MinBaseFee, // this is too low for applied config, should fail
@@ -2664,7 +2676,9 @@ 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.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
genesisJSON, err := genesis.MarshalJSON()
require.NoError(t, err)
@@ -2705,12 +2719,12 @@ 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)
+ 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)
@@ -2804,7 +2818,9 @@ 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.GenesisPrecompiles = params.Precompiles{
+ 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)
@@ -2841,12 +2857,12 @@ 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)
+ 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 7925f1388f..4880f0e348 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"
+ "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: precompile.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, precompile.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: precompile.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, precompile.ErrSenderAddressNotAllowListed) {
+ if err := errs[0]; !errors.Is(err, vmerrs.ErrSenderAddressNotAllowListed) {
t.Fatalf("expected ErrSenderAddressNotAllowListed, got: %s", err)
}
diff --git a/precompile/allow_list.go b/precompile/allow_list.go
deleted file mode 100644
index 5914718ae7..0000000000
--- a/precompile/allow_list.go
+++ /dev/null
@@ -1,235 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
-// See the file LICENSE for licensing terms.
-
-package precompile
-
-import (
- "errors"
- "fmt"
-
- "github.com/ava-labs/subnet-evm/vmerrs"
- "github.com/ethereum/go-ethereum/common"
-)
-
-const (
- SetAdminFuncKey = "setAdmin"
- SetEnabledFuncKey = "setEnabled"
- SetNoneFuncKey = "setNone"
- ReadAllowListFuncKey = "readAllowList"
-
- ModifyAllowListGasCost = writeGasCostPerSlot
- ReadAllowListGasCost = readGasCostPerSlot
-)
-
-var (
- // No role assigned - this is equivalent to common.Hash{} and deletes the key from the DB when set
- AllowListNoRole = AllowListRole(common.BigToHash(common.Big0))
- // Enabled - allowed to use state-changing precompile functions without modifying status of other admins or enableds
- AllowListEnabled = AllowListRole(common.BigToHash(common.Big1))
- // Admin - allowed to modify both the admin and enabled list, as well as to use state-changing precompile functions
- AllowListAdmin = AllowListRole(common.BigToHash(common.Big2))
-
- // AllowList function signatures
- setAdminSignature = CalculateFunctionSelector("setAdmin(address)")
- setEnabledSignature = CalculateFunctionSelector("setEnabled(address)")
- setNoneSignature = CalculateFunctionSelector("setNone(address)")
- readAllowListSignature = 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 StateDB, precompileAddr common.Address) {
- for _, enabledAddr := range c.EnabledAddresses {
- setAllowListRole(state, precompileAddr, enabledAddr, AllowListEnabled)
- }
- for _, adminAddr := range c.AllowListAdmins {
- setAllowListRole(state, precompileAddr, adminAddr, AllowListAdmin)
- }
-}
-
-// 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 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 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, 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, 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) 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 {
- 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) 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 {
- 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) StatefulPrecompiledContract {
- // Construct the contract with no fallback function.
- allowListFuncs := createAllowListFunctions(precompileAddr)
- contract := newStatefulPrecompileWithFunctionSelectors(nil, allowListFuncs)
- 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))
-
- return []*statefulPrecompileFunction{setAdmin, setEnabled, setNone, read}
-}
diff --git a/precompile/allowlist/allowlist.go b/precompile/allowlist/allowlist.go
new file mode 100644
index 0000000000..cf8dcbdb4c
--- /dev/null
+++ b/precompile/allowlist/allowlist.go
@@ -0,0 +1,172 @@
+// (c) 2019-2023, 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 both modify the allowlist and call the precompile
+
+const (
+ SetAdminFuncKey = "setAdmin"
+ SetEnabledFuncKey = "setEnabled"
+ SetNoneFuncKey = "setNone"
+ ReadAllowListFuncKey = "readAllowList"
+
+ ModifyAllowListGasCost = contract.WriteGasCostPerSlot
+ ReadAllowListGasCost = contract.ReadGasCostPerSlot
+
+ 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 both modify 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/allowlist_test.go b/precompile/allowlist/allowlist_test.go
new file mode 100644
index 0000000000..f4c0f5fc21
--- /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) MakeConfig() 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/config.go b/precompile/allowlist/config.go
new file mode 100644
index 0000000000..90ef49a811
--- /dev/null
+++ b/precompile/allowlist/config.go
@@ -0,0 +1,79 @@
+// (c) 2019-2023, 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"
+)
+
+// 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 *AllowListConfig) 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 *AllowListConfig) Equal(other *AllowListConfig) 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 *AllowListConfig) 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..bc3a3d9d26
--- /dev/null
+++ b/precompile/allowlist/config_test.go
@@ -0,0 +1,97 @@
+// (c) 2019-2023, 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 TestVerifyAllowlistAllowList(t *testing.T) {
+ admins := []common.Address{{1}}
+ enableds := []common.Address{{2}}
+ tests := []struct {
+ name string
+ config AllowListConfig
+ expectedError string
+ }{
+ {
+ name: "invalid allow list config in allowlist",
+ config: AllowListConfig{admins, admins},
+ expectedError: "cannot set address",
+ },
+ {
+ name: "nil member allow list config in allowlist",
+ config: AllowListConfig{nil, nil},
+ expectedError: "",
+ },
+ {
+ name: "empty member allow list config in allowlist",
+ config: AllowListConfig{[]common.Address{}, []common.Address{}},
+ expectedError: "",
+ },
+ {
+ name: "valid allow list config in allowlist",
+ config: AllowListConfig{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 TestEqualAllowListAllowList(t *testing.T) {
+ admins := []common.Address{{1}}
+ enableds := []common.Address{{2}}
+ tests := []struct {
+ name string
+ config *AllowListConfig
+ other *AllowListConfig
+ expected bool
+ }{
+ {
+ name: "non-nil config and nil other",
+ config: &AllowListConfig{admins, enableds},
+ other: nil,
+ expected: false,
+ },
+ {
+ name: "different admin",
+ config: &AllowListConfig{admins, enableds},
+ other: &AllowListConfig{[]common.Address{{3}}, enableds},
+ expected: false,
+ },
+ {
+ name: "different enabled",
+ config: &AllowListConfig{admins, enableds},
+ other: &AllowListConfig{admins, []common.Address{{3}}},
+ expected: false,
+ },
+ {
+ name: "same config",
+ config: &AllowListConfig{admins, enableds},
+ other: &AllowListConfig{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/allow_list_role.go b/precompile/allowlist/role.go
similarity index 55%
rename from precompile/allow_list_role.go
rename to precompile/allowlist/role.go
index 0c815d0819..aa55007662 100644
--- a/precompile/allow_list_role.go
+++ b/precompile/allowlist/role.go
@@ -1,17 +1,17 @@
// (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"
-// 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/allowlist/test_allowlist.go b/precompile/allowlist/test_allowlist.go
new file mode 100644
index 0000000000..78158e55ee
--- /dev/null
+++ b/precompile/allowlist/test_allowlist.go
@@ -0,0 +1,291 @@
+// (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.MakeConfig()
+ 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) {
+ t.Helper()
+ 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/config_test.go b/precompile/config_test.go
deleted file mode 100644
index 2772009390..0000000000
--- a/precompile/config_test.go
+++ /dev/null
@@ -1,465 +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,
- func() *commontype.FeeConfig {
- feeConfig := validFeeConfig
- feeConfig.GasLimit = big.NewInt(0)
- return &feeConfig
- }()),
-
- 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
deleted file mode 100644
index 0e2d720273..0000000000
--- a/precompile/contract.go
+++ /dev/null
@@ -1,142 +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/ava-labs/avalanchego/snow"
- "github.com/ava-labs/subnet-evm/commontype"
- "github.com/ethereum/go-ethereum/common"
-)
-
-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)
-
-// 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 {
- // selector is the 4 byte function selector for this function
- // This should be calculated from the function signature using CalculateFunctionSelector
- selector []byte
- // execute is performed when this function is selected
- execute RunStatefulPrecompileFunc
-}
-
-// newStatefulPrecompileFunction creates a stateful precompile function with the given arguments
-func newStatefulPrecompileFunction(selector []byte, execute RunStatefulPrecompileFunc) *statefulPrecompileFunction {
- return &statefulPrecompileFunction{
- selector: selector,
- execute: execute,
- }
-}
-
-// statefulPrecompileWithFunctionSelectors implements StatefulPrecompiledContract by using 4 byte function selectors to pass
-// off responsibilities to internal execution functions.
-// 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
-}
-
-// newStatefulPrecompileWithFunctionSelectors 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 {
- // Construct the contract and populate [functions].
- contract := &statefulPrecompileWithFunctionSelectors{
- fallback: fallback,
- functions: make(map[string]*statefulPrecompileFunction),
- }
- 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))
- }
- contract.functions[string(function.selector)] = function
- }
-
- return contract
-}
-
-// 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) {
- // 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)
- }
-
- // Otherwise, an unexpected input size will result in an error.
- 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:]
- function, ok := s.functions[string(selector)]
- if !ok {
- return nil, suppliedGas, fmt.Errorf("invalid function selector %#x", selector)
- }
-
- return function.execute(accessibleState, caller, addr, functionInput, suppliedGas, readOnly)
-}
diff --git a/precompile/contract/contract.go b/precompile/contract/contract.go
new file mode 100644
index 0000000000..82bb5fe21c
--- /dev/null
+++ b/precompile/contract/contract.go
@@ -0,0 +1,84 @@
+// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package contract
+
+import (
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+const (
+ SelectorLen = 4
+)
+
+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 {
+ // selector is the 4 byte function selector for this function
+ // This should be calculated from the function signature using CalculateFunctionSelector
+ selector []byte
+ // execute is performed when this function is selected
+ execute RunStatefulPrecompileFunc
+}
+
+// NewStatefulPrecompileFunction creates a stateful precompile function with the given arguments
+func NewStatefulPrecompileFunction(selector []byte, execute RunStatefulPrecompileFunc) *StatefulPrecompileFunction {
+ return &StatefulPrecompileFunction{
+ selector: selector,
+ execute: execute,
+ }
+}
+
+// statefulPrecompileWithFunctionSelectors implements StatefulPrecompiledContract by using 4 byte function selectors to pass
+// off responsibilities to internal execution functions.
+// 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
+}
+
+// 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) {
+ // Construct the contract and populate [functions].
+ contract := &statefulPrecompileWithFunctionSelectors{
+ fallback: fallback,
+ functions: make(map[string]*StatefulPrecompileFunction),
+ }
+ for _, function := range functions {
+ _, exists := contract.functions[string(function.selector)]
+ if exists {
+ return nil, fmt.Errorf("cannot create stateful precompile with duplicated function selector: %q", function.selector)
+ }
+ contract.functions[string(function.selector)] = function
+ }
+
+ 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
+// given arguments.
+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)
+ }
+
+ // Otherwise, an unexpected input size will result in an error.
+ 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:]
+ function, ok := s.functions[string(selector)]
+ if !ok {
+ return nil, suppliedGas, fmt.Errorf("invalid function selector %#x", selector)
+ }
+
+ return function.execute(accessibleState, caller, addr, functionInput, suppliedGas, readOnly)
+}
diff --git a/precompile/contract/interfaces.go b/precompile/contract/interfaces.go
new file mode 100644
index 0000000000..a8402bed75
--- /dev/null
+++ b/precompile/contract/interfaces.go
@@ -0,0 +1,79 @@
+// (c) 2023, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+// 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/precompileconfig"
+ "github.com/ethereum/go-ethereum/common"
+)
+
+// 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
+// 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)
+}
+
+// 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 {
+ MakeConfig() precompileconfig.Config
+ Configure(
+ chainConfig ChainConfig,
+ precompileconfig precompileconfig.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..aa4a9dcfaa
--- /dev/null
+++ b/precompile/contract/mock_interfaces.go
@@ -0,0 +1,73 @@
+// (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/ava-labs/subnet-evm/commontype"
+ "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
+}
+
+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/utils.go b/precompile/contract/utils.go
similarity index 58%
rename from precompile/utils.go
rename to precompile/contract/utils.go
index 2476a97e34..9cc50d3155 100644
--- a/precompile/utils.go
+++ b/precompile/contract/utils.go
@@ -1,22 +1,31 @@
// (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]
// 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,27 +34,27 @@ 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) {
+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.
+// 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,13 +66,25 @@ 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]
+// 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]
}
+
+// 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/contract_deployer_allow_list.go b/precompile/contract_deployer_allow_list.go
deleted file mode 100644
index 7d42a270b9..0000000000
--- a/precompile/contract_deployer_allow_list.go
+++ /dev/null
@@ -1,91 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
-// See the file LICENSE for licensing terms.
-
-package precompile
-
-import (
- "encoding/json"
- "math/big"
-
- "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)
-)
-
-// 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
-}
-
-// 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{
- AllowListAdmins: admins,
- EnabledAddresses: enableds,
- },
- UpgradeableConfig: UpgradeableConfig{BlockTimestamp: blockTimestamp},
- }
-}
-
-// NewDisableContractDeployerAllowListConfig returns config for a network upgrade at [blockTimestamp]
-// that disables ContractDeployerAllowList.
-func NewDisableContractDeployerAllowListConfig(blockTimestamp *big.Int) *ContractDeployerAllowListConfig {
- return &ContractDeployerAllowListConfig{
- UpgradeableConfig: UpgradeableConfig{
- BlockTimestamp: blockTimestamp,
- Disable: true,
- },
- }
-}
-
-// Address returns the address of the contract deployer allow list.
-func (c *ContractDeployerAllowListConfig) Address() common.Address {
- return ContractDeployerAllowListAddress
-}
-
-// Configure configures [state] with the desired admins based on [c].
-func (c *ContractDeployerAllowListConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) {
- c.AllowListConfig.Configure(state, ContractDeployerAllowListAddress)
-}
-
-// Contract returns the singleton stateful precompiled contract to be used for the allow list.
-func (c *ContractDeployerAllowListConfig) Contract() 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 {
- // 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)
-}
-
-// 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/contract_native_minter.go b/precompile/contract_native_minter.go
deleted file mode 100644
index 2da1b3bbaa..0000000000
--- a/precompile/contract_native_minter.go
+++ /dev/null
@@ -1,222 +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) {
- for to, amount := range c.InitialMint {
- if amount != nil {
- bigIntAmount := (*big.Int)(amount)
- state.AddBalance(to, bigIntAmount)
- }
- }
-
- 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)
- packOrderedHashesWithSelector(res, mintSignature, []common.Hash{
- address.Hash(),
- common.BigToHash(amount),
- })
-
- return res, nil
-}
-
-// 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 := newStatefulPrecompileWithFunctionSelectors(nil, enabledFuncs)
- return contract
-}
diff --git a/precompile/contracts/deployerallowlist/config.go b/precompile/contracts/deployerallowlist/config.go
new file mode 100644
index 0000000000..c2d5bf28dd
--- /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/precompileconfig"
+ "github.com/ethereum/go-ethereum/common"
+)
+
+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.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{
+ AllowListConfig: allowlist.AllowListConfig{
+ AdminAddresses: admins,
+ EnabledAddresses: enableds,
+ },
+ Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp},
+ }
+}
+
+// NewDisableConfig returns config for a network upgrade at [blockTimestamp]
+// that disables ContractDeployerAllowList.
+func NewDisableConfig(blockTimestamp *big.Int) *Config {
+ return &Config{
+ Upgrade: precompileconfig.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 precompileconfig.Config) bool {
+ // typecast before comparison
+ other, ok := (cfg).(*Config)
+ if !ok {
+ return false
+ }
+ return c.Upgrade.Equal(&other.Upgrade) && c.AllowListConfig.Equal(&other.AllowListConfig)
+}
+
+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
new file mode 100644
index 0000000000..caa3d8995a
--- /dev/null
+++ b/precompile/contracts/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/precompileconfig"
+ "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 precompileconfig.Config
+ ExpectedError string
+ }{
+ {
+ name: "invalid allow list config in deployer allowlist",
+ config: NewConfig(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 precompileconfig.Config
+ other precompileconfig.Config
+ expected bool
+ }{
+ {
+ name: "non-nil config and nil other",
+ config: NewConfig(big.NewInt(3), admins, enableds),
+ other: nil,
+ expected: false,
+ },
+ {
+ name: "different type",
+ config: NewConfig(big.NewInt(3), admins, enableds),
+ other: precompileconfig.NewNoopStatefulPrecompileConfig(),
+ expected: false,
+ },
+ {
+ name: "different admin",
+ config: NewConfig(big.NewInt(3), admins, enableds),
+ other: NewConfig(big.NewInt(3), []common.Address{{3}}, enableds),
+ expected: false,
+ },
+ {
+ name: "different enabled",
+ config: NewConfig(big.NewInt(3), admins, enableds),
+ other: NewConfig(big.NewInt(3), admins, []common.Address{{3}}),
+ expected: false,
+ },
+ {
+ name: "different timestamp",
+ config: NewConfig(big.NewInt(3), admins, enableds),
+ other: NewConfig(big.NewInt(4), admins, enableds),
+ expected: false,
+ },
+ {
+ name: "same config",
+ config: NewConfig(big.NewInt(3), admins, enableds),
+ other: NewConfig(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/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..ba144fd155
--- /dev/null
+++ b/precompile/contracts/deployerallowlist/contract_test.go
@@ -0,0 +1,15 @@
+// (c) 2019-2023, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package deployerallowlist
+
+import (
+ "testing"
+
+ "github.com/ava-labs/subnet-evm/core/state"
+ "github.com/ava-labs/subnet-evm/precompile/allowlist"
+)
+
+func TestContractDeployerAllowListRun(t *testing.T) {
+ allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, nil)
+}
diff --git a/precompile/contracts/deployerallowlist/module.go b/precompile/contracts/deployerallowlist/module.go
new file mode 100644
index 0000000000..93dd59daef
--- /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/contract"
+ "github.com/ava-labs/subnet-evm/precompile/modules"
+ "github.com/ava-labs/subnet-evm/precompile/precompileconfig"
+ "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) MakeConfig() precompileconfig.Config {
+ return new(Config)
+}
+
+// Configure configures [state] with the given [cfg] config.
+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.AllowListConfig.Configure(state, ContractAddress)
+}
diff --git a/precompile/contracts/feemanager/config.go b/precompile/contracts/feemanager/config.go
new file mode 100644
index 0000000000..9debfa7b1f
--- /dev/null
+++ b/precompile/contracts/feemanager/config.go
@@ -0,0 +1,80 @@
+// (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/precompileconfig"
+ "github.com/ethereum/go-ethereum/common"
+)
+
+var _ precompileconfig.Config = &Config{}
+
+// Config implements the StatefulPrecompileConfig interface while adding in the
+// FeeManager specific precompile config.
+type Config struct {
+ 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
+}
+
+// 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{
+ AllowListConfig: allowlist.AllowListConfig{
+ AdminAddresses: admins,
+ EnabledAddresses: enableds,
+ },
+ Upgrade: precompileconfig.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: precompileconfig.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 precompileconfig.Config) bool {
+ // typecast before comparison
+ other, ok := (cfg).(*Config)
+ if !ok {
+ return false
+ }
+ eq := c.Upgrade.Equal(&other.Upgrade) && c.AllowListConfig.Equal(&other.AllowListConfig)
+ 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.AllowListConfig.Verify(); err != nil {
+ return err
+ }
+ if c.InitialFeeConfig == nil {
+ return nil
+ }
+
+ return c.InitialFeeConfig.Verify()
+}
diff --git a/precompile/contracts/feemanager/config_test.go b/precompile/contracts/feemanager/config_test.go
new file mode 100644
index 0000000000..4b5654d5d9
--- /dev/null
+++ b/precompile/contracts/feemanager/config_test.go
@@ -0,0 +1,132 @@
+// (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/precompileconfig"
+ "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}}
+ invalidFeeConfig := validFeeConfig
+ invalidFeeConfig.GasLimit = big.NewInt(0)
+ tests := []struct {
+ name string
+ config precompileconfig.Config
+ ExpectedError string
+ }{
+ {
+ name: "invalid allow list config in fee manager allowlist",
+ config: NewConfig(big.NewInt(3), admins, admins, nil),
+ ExpectedError: "cannot set address",
+ },
+ {
+ 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) {
+ require := require.New(t)
+
+ err := tt.config.Verify()
+ if tt.ExpectedError == "" {
+ require.NoError(err)
+ } else {
+ require.ErrorContains(err, tt.ExpectedError)
+ }
+ })
+ }
+}
+
+func TestEqualFeeManagerConfig(t *testing.T) {
+ admins := []common.Address{{1}}
+ enableds := []common.Address{{2}}
+ tests := []struct {
+ name string
+ config precompileconfig.Config
+ other precompileconfig.Config
+ expected bool
+ }{
+ {
+ name: "non-nil config and nil other",
+ config: NewConfig(big.NewInt(3), admins, enableds, nil),
+ other: nil,
+ expected: false,
+ },
+ {
+ name: "different type",
+ config: NewConfig(big.NewInt(3), admins, enableds, nil),
+ other: precompileconfig.NewNoopStatefulPrecompileConfig(),
+ expected: false,
+ },
+ {
+ name: "different timestamp",
+ config: NewConfig(big.NewInt(3), admins, nil, nil),
+ other: NewConfig(big.NewInt(4), admins, nil, nil),
+ expected: false,
+ },
+ {
+ name: "different enabled",
+ 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: NewConfig(big.NewInt(3), admins, nil, &validFeeConfig),
+ other: NewConfig(big.NewInt(3), admins, nil, nil),
+ expected: false,
+ },
+ {
+ name: "different initial config",
+ 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)
+ return &c
+ }()),
+ expected: false,
+ },
+ {
+ name: "same config",
+ config: NewConfig(big.NewInt(3), admins, nil, &validFeeConfig),
+ other: NewConfig(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/contracts/feemanager/contract.go
similarity index 50%
rename from precompile/fee_config_manager.go
rename to precompile/contracts/feemanager/contract.go
index 3436ba360c..a4ecd9a4e4 100644
--- a/precompile/fee_config_manager.go
+++ b/precompile/contracts/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/allowlist"
+ "github.com/ava-labs/subnet-evm/precompile/contract"
"github.com/ava-labs/subnet-evm/vmerrs"
"github.com/ethereum/go-ethereum/common"
)
@@ -32,130 +33,34 @@ 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 = contract.WriteGasCostPerSlot * (numFeeConfigField + 1) // plus one for setting last changed at
+ GetFeeConfigGasCost = contract.ReadGasCostPerSlot * numFeeConfigField
+ GetLastChangedAtGasCost = contract.ReadGasCostPerSlot
)
var (
- _ StatefulPrecompileConfig = &FeeConfigManagerConfig{}
// Singleton StatefulPrecompiledContract for setting fee configs by permissioned callers.
- FeeConfigManagerPrecompile StatefulPrecompiledContract = createFeeConfigManagerPrecompile(FeeConfigManagerAddress)
+ FeeManagerPrecompile contract.StatefulPrecompiledContract = createFeeManagerPrecompile()
- setFeeConfigSignature = CalculateFunctionSelector("setFeeConfig(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)")
- getFeeConfigSignature = CalculateFunctionSelector("getFeeConfig()")
- getFeeConfigLastChangedAtSignature = 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")
)
-// 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
+// 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)
}
-// 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) {
- // 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))
- }
- } 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))
- }
- }
- 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)
-}
-
-// SetFeeConfigManagerStatus sets the permissions of [address] to [role] for the
+// SetFeeManagerStatus 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 SetFeeManagerStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) {
+ allowlist.SetAllowListRole(stateDB, ContractAddress, address, role)
}
// PackGetFeeConfigInput packs the getFeeConfig signature
@@ -171,16 +76,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,25 +99,25 @@ func packFeeConfigHelper(feeConfig commontype.FeeConfig, useSelector bool) []byt
if useSelector {
res := make([]byte, len(setFeeConfigSignature)+feeConfigInputLen)
- packOrderedHashesWithSelector(res, setFeeConfigSignature, hashes)
- return res
+ err := contract.PackOrderedHashesWithSelector(res, setFeeConfigSignature, hashes)
+ return res, err
}
res := make([]byte, len(hashes)*common.HashLength)
- packOrderedHashes(res, hashes)
- return res
+ err := contract.PackOrderedHashes(res, hashes)
+ return res, err
}
// UnpackFeeConfigInput attempts to unpack [input] into the arguments to the fee config precompile
// 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++ {
listIndex := i - 1
- packedElement := returnPackedHash(input, listIndex)
+ packedElement := contract.PackedHash(input, listIndex)
switch i {
case gasLimitKey:
feeConfig.GasLimit = new(big.Int).SetBytes(packedElement)
@@ -231,6 +136,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))
}
}
@@ -238,10 +144,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 contract.StateDB) commontype.FeeConfig {
feeConfig := commontype.FeeConfig{}
for i := minFeeConfigFieldKey; i <= numFeeConfigField; i++ {
- val := stateDB.GetState(FeeConfigManagerAddress, common.Hash{byte(i)})
+ val := stateDB.GetState(ContractAddress, common.Hash{byte(i)})
switch i {
case gasLimitKey:
feeConfig.GasLimit = new(big.Int).Set(val.Big())
@@ -260,22 +166,23 @@ 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))
}
}
return feeConfig
}
-func GetFeeConfigLastChangedAt(stateDB StateDB) *big.Int {
- val := stateDB.GetState(FeeConfigManagerAddress, 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 StateDB, feeConfig commontype.FeeConfig, blockContext BlockContext) error {
+func StoreFeeConfig(stateDB contract.StateDB, feeConfig commontype.FeeConfig, blockContext contract.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,24 +205,24 @@ 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)
+ stateDB.SetState(ContractAddress, 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(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 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 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
}
@@ -329,8 +236,8 @@ 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)
+ // 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)
}
@@ -345,8 +252,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 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
}
@@ -363,8 +270,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 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
}
@@ -374,18 +281,23 @@ func getFeeConfigLastChangedAt(accessibleState PrecompileAccessibleState, caller
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) StatefulPrecompiledContract {
- feeConfigManagerFunctions := createAllowListFunctions(precompileAddr)
+// is controlled by an allow list for ContractAddress.
+func createFeeManagerPrecompile() contract.StatefulPrecompiledContract {
+ feeManagerFunctions := allowlist.CreateAllowListFunctions(ContractAddress)
- setFeeConfigFunc := newStatefulPrecompileFunction(setFeeConfigSignature, setFeeConfig)
- getFeeConfigFunc := newStatefulPrecompileFunction(getFeeConfigSignature, getFeeConfig)
- getFeeConfigLastChangedAtFunc := newStatefulPrecompileFunction(getFeeConfigLastChangedAtSignature, getFeeConfigLastChangedAt)
+ setFeeConfigFunc := contract.NewStatefulPrecompileFunction(setFeeConfigSignature, setFeeConfig)
+ getFeeConfigFunc := contract.NewStatefulPrecompileFunction(getFeeConfigSignature, getFeeConfig)
+ getFeeConfigLastChangedAtFunc := contract.NewStatefulPrecompileFunction(getFeeConfigLastChangedAtSignature, getFeeConfigLastChangedAt)
- feeConfigManagerFunctions = append(feeConfigManagerFunctions, setFeeConfigFunc, getFeeConfigFunc, getFeeConfigLastChangedAtFunc)
+ feeManagerFunctions = append(feeManagerFunctions, setFeeConfigFunc, getFeeConfigFunc, getFeeConfigLastChangedAtFunc)
// Construct the contract with no fallback function.
- contract := newStatefulPrecompileWithFunctionSelectors(nil, feeConfigManagerFunctions)
+ 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 {
+ panic(err)
+ }
return contract
}
diff --git a/precompile/contracts/feemanager/contract_test.go b/precompile/contracts/feemanager/contract_test.go
new file mode 100644
index 0000000000..4911a2368e
--- /dev/null
+++ b/precompile/contracts/feemanager/contract_test.go
@@ -0,0 +1,225 @@
+// (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/subnet-evm/commontype"
+ "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 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 TestFeeManager(t *testing.T) {
+ testBlockNumber := big.NewInt(7)
+ tests := map[string]testutils.PrecompileTest{
+ "set config from no role fails": {
+ Caller: allowlist.TestNoRoleAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []byte {
+ input, err := PackSetFeeConfig(testFeeConfig)
+ require.NoError(t, err)
+
+ return input
+ },
+ SuppliedGas: SetFeeConfigGasCost,
+ ReadOnly: false,
+ ExpectedErr: ErrCannotChangeFee.Error(),
+ },
+ "set config from enabled address": {
+ Caller: allowlist.TestEnabledAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []byte {
+ input, err := PackSetFeeConfig(testFeeConfig)
+ require.NoError(t, err)
+
+ return input
+ },
+ 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: 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(t, err)
+
+ return input
+ },
+ SuppliedGas: SetFeeConfigGasCost,
+ ReadOnly: false,
+ Config: &Config{
+ InitialFeeConfig: &testFeeConfig,
+ },
+ 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: allowlist.TestAdminAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []byte {
+ input, err := PackSetFeeConfig(testFeeConfig)
+ require.NoError(t, err)
+
+ return input
+ },
+ 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)
+ lastChangedAt := GetFeeConfigLastChangedAt(state)
+ require.EqualValues(t, testBlockNumber, lastChangedAt)
+ },
+ },
+ "get fee config from non-enabled address": {
+ 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)
+ },
+ Input: PackGetFeeConfigInput(),
+ SuppliedGas: GetFeeConfigGasCost,
+ ReadOnly: true,
+ ExpectedRes: func() []byte {
+ res, err := PackFeeConfig(testFeeConfig)
+ require.NoError(t, err)
+ return res
+ }(),
+ AfterHook: func(t *testing.T, state contract.StateDB) {
+ feeConfig := GetStoredFeeConfig(state)
+ lastChangedAt := GetFeeConfigLastChangedAt(state)
+ require.Equal(t, testFeeConfig, feeConfig)
+ require.EqualValues(t, big.NewInt(6), lastChangedAt)
+ },
+ },
+ "get initial fee config": {
+ Caller: allowlist.TestNoRoleAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ Input: PackGetFeeConfigInput(),
+ SuppliedGas: GetFeeConfigGasCost,
+ Config: &Config{
+ InitialFeeConfig: &testFeeConfig,
+ },
+ ReadOnly: true,
+ ExpectedRes: func() []byte {
+ res, err := PackFeeConfig(testFeeConfig)
+ require.NoError(t, err)
+ return res
+ }(),
+ BlockNumber: testBlockNumber.Int64(),
+ AfterHook: func(t *testing.T, state contract.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: 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)
+ },
+ 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)
+ require.Equal(t, testBlockNumber, lastChangedAt)
+ },
+ },
+ "readOnly setFeeConfig with noRole fails": {
+ Caller: allowlist.TestNoRoleAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []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: allowlist.TestEnabledAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []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: allowlist.TestAdminAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []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: allowlist.TestAdminAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []byte {
+ input, err := PackSetFeeConfig(testFeeConfig)
+ require.NoError(t, err)
+
+ return input
+ },
+ SuppliedGas: SetFeeConfigGasCost - 1,
+ ReadOnly: false,
+ ExpectedErr: vmerrs.ErrOutOfGas.Error(),
+ },
+ }
+
+ allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests)
+}
diff --git a/precompile/contracts/feemanager/module.go b/precompile/contracts/feemanager/module.go
new file mode 100644
index 0000000000..ce96933bf1
--- /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/contract"
+ "github.com/ava-labs/subnet-evm/precompile/modules"
+ "github.com/ava-labs/subnet-evm/precompile/precompileconfig"
+ "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) MakeConfig() precompileconfig.Config {
+ return new(Config)
+}
+
+// Configure configures [state] with the desired admins based on [configIface].
+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)
+ }
+ // 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.AllowListConfig.Configure(state, ContractAddress)
+}
diff --git a/precompile/contracts/nativeminter/config.go b/precompile/contracts/nativeminter/config.go
new file mode 100644
index 0000000000..e2df6bbc82
--- /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/precompileconfig"
+ "github.com/ava-labs/subnet-evm/utils"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/math"
+)
+
+var _ precompileconfig.Config = &Config{}
+
+// Config implements the StatefulPrecompileConfig interface while adding in the
+// ContractNativeMinter specific precompile config.
+type Config struct {
+ 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
+}
+
+// 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{
+ AllowListConfig: allowlist.AllowListConfig{
+ AdminAddresses: admins,
+ EnabledAddresses: enableds,
+ },
+ Upgrade: precompileconfig.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: precompileconfig.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 precompileconfig.Config) bool {
+ // typecast before comparison
+ other, ok := (cfg).(*Config)
+ if !ok {
+ return false
+ }
+ eq := c.Upgrade.Equal(&other.Upgrade) && 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
+}
+
+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.AllowListConfig.Verify()
+}
diff --git a/precompile/contracts/nativeminter/config_test.go b/precompile/contracts/nativeminter/config_test.go
new file mode 100644
index 0000000000..1927a93b9b
--- /dev/null
+++ b/precompile/contracts/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/precompileconfig"
+ "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 precompileconfig.Config
+ ExpectedError string
+ }{
+ {
+ name: "invalid allow list config in native minter allowlist",
+ config: NewConfig(big.NewInt(3), admins, admins, nil),
+ 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",
+ },
+ {
+ name: "duplicate enableds in config in native minter allowlist",
+ config: NewConfig(big.NewInt(3), admins, append(enableds, enableds[0]), nil),
+ ExpectedError: "duplicate address",
+ },
+ {
+ name: "nil amount in native minter config",
+ config: NewConfig(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: NewConfig(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 precompileconfig.Config
+ other precompileconfig.Config
+ expected bool
+ }{
+ {
+ name: "non-nil config and nil other",
+ config: NewConfig(big.NewInt(3), admins, enableds, nil),
+ other: nil,
+ expected: false,
+ },
+ {
+ name: "different type",
+ config: NewConfig(big.NewInt(3), admins, enableds, nil),
+ other: precompileconfig.NewNoopStatefulPrecompileConfig(),
+ expected: false,
+ },
+ {
+ name: "different timestamps",
+ config: NewConfig(big.NewInt(3), admins, nil, nil),
+ other: NewConfig(big.NewInt(4), admins, nil, nil),
+ expected: false,
+ },
+ {
+ name: "different enabled",
+ config: NewConfig(big.NewInt(3), admins, nil, nil),
+ other: NewConfig(big.NewInt(3), admins, enableds, nil),
+ expected: false,
+ },
+ {
+ name: "different initial mint amounts",
+ config: NewConfig(big.NewInt(3), admins, nil,
+ map[common.Address]*math.HexOrDecimal256{
+ common.HexToAddress("0x01"): math.NewHexOrDecimal256(1),
+ }),
+ other: NewConfig(big.NewInt(3), admins, nil,
+ map[common.Address]*math.HexOrDecimal256{
+ common.HexToAddress("0x01"): math.NewHexOrDecimal256(2),
+ }),
+ expected: false,
+ },
+ {
+ name: "different initial mint addresses",
+ config: NewConfig(big.NewInt(3), admins, nil,
+ map[common.Address]*math.HexOrDecimal256{
+ common.HexToAddress("0x01"): math.NewHexOrDecimal256(1),
+ }),
+ other: NewConfig(big.NewInt(3), admins, nil,
+ map[common.Address]*math.HexOrDecimal256{
+ common.HexToAddress("0x02"): math.NewHexOrDecimal256(1),
+ }),
+ expected: false,
+ },
+ {
+ name: "same config",
+ config: NewConfig(big.NewInt(3), admins, nil,
+ map[common.Address]*math.HexOrDecimal256{
+ common.HexToAddress("0x01"): math.NewHexOrDecimal256(1),
+ }),
+ other: NewConfig(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/contracts/nativeminter/contract.go b/precompile/contracts/nativeminter/contract.go
new file mode 100644
index 0000000000..63d6a624da
--- /dev/null
+++ b/precompile/contracts/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/allowlist"
+ "github.com/ava-labs/subnet-evm/precompile/contract"
+ "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 contract.StatefulPrecompiledContract = createNativeMinterPrecompile()
+
+ 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 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 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, contract.SelectorLen+mintInputLen)
+ err := contract.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(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 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
+ }
+
+ 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 call this function.
+ callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, 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() contract.StatefulPrecompiledContract {
+ enabledFuncs := allowlist.CreateAllowListFunctions(ContractAddress)
+
+ mintFunc := contract.NewStatefulPrecompileFunction(mintSignature, mintNativeCoin)
+
+ enabledFuncs = append(enabledFuncs, mintFunc)
+ // Construct the contract with no fallback function.
+ 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 {
+ panic(err)
+ }
+ return contract
+}
diff --git a/precompile/contracts/nativeminter/contract_test.go b/precompile/contracts/nativeminter/contract_test.go
new file mode 100644
index 0000000000..a987dc9998
--- /dev/null
+++ b/precompile/contracts/nativeminter/contract_test.go
@@ -0,0 +1,149 @@
+// (c) 2019-2023, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package nativeminter
+
+import (
+ "testing"
+
+ "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/ethereum/go-ethereum/common/math"
+ "github.com/stretchr/testify/require"
+)
+
+func TestContractNativeMinterRun(t *testing.T) {
+ tests := map[string]testutils.PrecompileTest{
+ "mint funds from no role fails": {
+ 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
+ },
+ SuppliedGas: MintGasCost,
+ ReadOnly: false,
+ ExpectedErr: ErrCannotMint.Error(),
+ },
+ "mint funds from enabled address": {
+ 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
+ },
+ SuppliedGas: MintGasCost,
+ ReadOnly: false,
+ ExpectedRes: []byte{},
+ AfterHook: func(t *testing.T, state contract.StateDB) {
+ require.Equal(t, common.Big1, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds")
+ },
+ },
+ "initial mint funds": {
+ Caller: allowlist.TestEnabledAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ Config: &Config{
+ InitialMint: map[common.Address]*math.HexOrDecimal256{
+ allowlist.TestEnabledAddr: math.NewHexOrDecimal256(2),
+ },
+ },
+ AfterHook: func(t *testing.T, state contract.StateDB) {
+ require.Equal(t, common.Big2, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds")
+ },
+ },
+ "mint funds from admin address": {
+ 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
+ },
+ SuppliedGas: MintGasCost,
+ ReadOnly: false,
+ ExpectedRes: []byte{},
+ AfterHook: func(t *testing.T, state contract.StateDB) {
+ require.Equal(t, common.Big1, state.GetBalance(allowlist.TestAdminAddr), "expected minted funds")
+ },
+ },
+ "mint max big funds": {
+ 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
+ },
+ SuppliedGas: MintGasCost,
+ ReadOnly: false,
+ ExpectedRes: []byte{},
+ AfterHook: func(t *testing.T, state contract.StateDB) {
+ require.Equal(t, math.MaxBig256, state.GetBalance(allowlist.TestAdminAddr), "expected minted funds")
+ },
+ },
+ "readOnly mint with noRole fails": {
+ 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
+ },
+ SuppliedGas: MintGasCost,
+ ReadOnly: true,
+ ExpectedErr: vmerrs.ErrWriteProtection.Error(),
+ },
+ "readOnly mint with allow role fails": {
+ 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
+ },
+ SuppliedGas: MintGasCost,
+ ReadOnly: true,
+ ExpectedErr: vmerrs.ErrWriteProtection.Error(),
+ },
+ "readOnly mint with admin role fails": {
+ 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
+ },
+ SuppliedGas: MintGasCost,
+ ReadOnly: true,
+ ExpectedErr: vmerrs.ErrWriteProtection.Error(),
+ },
+ "insufficient gas mint from admin": {
+ 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
+ },
+ SuppliedGas: MintGasCost - 1,
+ ReadOnly: false,
+ ExpectedErr: vmerrs.ErrOutOfGas.Error(),
+ },
+ }
+
+ allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests)
+}
diff --git a/precompile/contracts/nativeminter/module.go b/precompile/contracts/nativeminter/module.go
new file mode 100644
index 0000000000..6ebd23e63e
--- /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/contract"
+ "github.com/ava-labs/subnet-evm/precompile/modules"
+ "github.com/ava-labs/subnet-evm/precompile/precompileconfig"
+ "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) MakeConfig() precompileconfig.Config {
+ return new(Config)
+}
+
+// Configure configures [state] with the desired admins based on [cfg].
+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)
+ }
+ for to, amount := range config.InitialMint {
+ if amount != nil {
+ bigIntAmount := (*big.Int)(amount)
+ state.AddBalance(to, bigIntAmount)
+ }
+ }
+
+ return config.AllowListConfig.Configure(state, ContractAddress)
+}
diff --git a/precompile/contracts/rewardmanager/config.go b/precompile/contracts/rewardmanager/config.go
new file mode 100644
index 0000000000..9b7a8bb238
--- /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/contract"
+ "github.com/ava-labs/subnet-evm/precompile/precompileconfig"
+ "github.com/ethereum/go-ethereum/common"
+)
+
+var _ precompileconfig.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.AllowListConfig
+ precompileconfig.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{
+ AllowListConfig: allowlist.AllowListConfig{
+ AdminAddresses: admins,
+ EnabledAddresses: enableds,
+ },
+ Upgrade: precompileconfig.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: precompileconfig.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.AllowListConfig.Verify()
+}
+
+// Equal returns true if [cfg] is a [*RewardManagerConfig] and it has been configured identical to [c].
+func (c *Config) Equal(cfg precompileconfig.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.AllowListConfig.Equal(&other.AllowListConfig)
+}
diff --git a/precompile/contracts/rewardmanager/config_test.go b/precompile/contracts/rewardmanager/config_test.go
new file mode 100644
index 0000000000..c78dec4c6f
--- /dev/null
+++ b/precompile/contracts/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/precompileconfig"
+ "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 precompileconfig.Config
+ 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",
+ },
+ {
+ name: "both reward mechanisms should not be activated at the same time in reward manager",
+ config: NewConfig(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 precompileconfig.Config
+ other precompileconfig.Config
+ expected bool
+ }{
+ {
+ name: "non-nil config and nil other",
+ config: NewConfig(big.NewInt(3), admins, enableds, nil),
+ other: nil,
+ expected: false,
+ },
+ {
+ name: "different type",
+ config: NewConfig(big.NewInt(3), admins, enableds, nil),
+ other: precompileconfig.NewNoopStatefulPrecompileConfig(),
+ expected: false,
+ },
+ {
+ name: "different timestamp",
+ config: NewConfig(big.NewInt(3), admins, nil, nil),
+ other: NewConfig(big.NewInt(4), admins, nil, nil),
+ expected: false,
+ },
+ {
+ name: "different enabled",
+ 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: NewConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{
+ AllowFeeRecipients: true,
+ }),
+ other: NewConfig(big.NewInt(3), admins, nil, nil),
+ expected: false,
+ },
+ {
+ name: "different initial config",
+ config: NewConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{
+ RewardAddress: common.HexToAddress("0x01"),
+ }),
+ other: NewConfig(big.NewInt(3), admins, nil,
+ &InitialRewardConfig{
+ RewardAddress: common.HexToAddress("0x02"),
+ }),
+ expected: false,
+ },
+ {
+ name: "same config",
+ config: NewConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{
+ RewardAddress: common.HexToAddress("0x01"),
+ }),
+ other: NewConfig(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/contracts/rewardmanager/contract.abi b/precompile/contracts/rewardmanager/contract.abi
new file mode 100644
index 0000000000..d21d5bdc6b
--- /dev/null
+++ b/precompile/contracts/rewardmanager/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/contracts/rewardmanager/contract.go b/precompile/contracts/rewardmanager/contract.go
new file mode 100644
index 0000000000..8d28e0815d
--- /dev/null
+++ b/precompile/contracts/rewardmanager/contract.go
@@ -0,0 +1,299 @@
+// (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 (
+ _ "embed"
+ "errors"
+ "fmt"
+
+ "github.com/ava-labs/subnet-evm/accounts/abi"
+ "github.com/ava-labs/subnet-evm/constants"
+ "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"
+)
+
+const (
+ AllowFeeRecipientsGasCost uint64 = (contract.WriteGasCostPerSlot) + allowlist.ReadAllowListGasCost // write 1 slot + read allow list
+ AreFeeRecipientsAllowedGasCost uint64 = allowlist.ReadAllowListGasCost
+ CurrentRewardAddressGasCost uint64 = allowlist.ReadAllowListGasCost
+ 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.
+var (
+ ErrCannotAllowFeeRecipients = errors.New("non-enabled cannot call allowFeeRecipients")
+ ErrCannotAreFeeRecipientsAllowed = errors.New("non-enabled cannot call areFeeRecipientsAllowed")
+ ErrCannotCurrentRewardAddress = errors.New("non-enabled cannot call currentRewardAddress")
+ ErrCannotDisableRewards = errors.New("non-enabled cannot call disableRewards")
+ ErrCannotSetRewardAddress = errors.New("non-enabled cannot call setRewardAddress")
+
+ 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 contract.abi
+ RewardManagerRawABI string
+
+ 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'}
+)
+
+// 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.
+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).
+// This function is mostly used for tests.
+func PackAllowFeeRecipients() ([]byte, error) {
+ return RewardManagerABI.Pack("allowFeeRecipients")
+}
+
+// EnableAllowFeeRecipients enables fee recipients.
+func EnableAllowFeeRecipients(stateDB contract.StateDB) {
+ stateDB.SetState(ContractAddress, rewardAddressStorageKey, allowFeeRecipientsAddressValue)
+}
+
+// DisableRewardAddress disables rewards and burns them by sending to Blackhole Address.
+func DisableFeeRewards(stateDB contract.StateDB) {
+ stateDB.SetState(ContractAddress, rewardAddressStorageKey, constants.BlackholeAddr.Hash())
+}
+
+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.
+
+ // this function does not return an output, leave this one as is
+ EnableAllowFeeRecipients(stateDB)
+ 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
+
+ stateDB := accessibleState.GetStateDB()
+ var output bool
+ _, output = GetStoredRewardAddress(stateDB)
+
+ 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)
+}
+
+// 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 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 contract.StateDB, val common.Address) error {
+ // if input is empty, return an error
+ if val == (common.Address{}) {
+ return ErrEmptyRewardAddress
+ }
+ stateDB.SetState(ContractAddress, rewardAddressStorageKey, val.Hash())
+ return 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)
+}
+
+// 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
+}
+
+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.
+
+ if err := StoreRewardAddress(stateDB, inputStruct); err != nil {
+ return nil, remainingGas, err
+ }
+ // 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
+}
+
+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
+ stateDB := accessibleState.GetStateDB()
+ output, _ := GetStoredRewardAddress(stateDB)
+ 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.
+ DisableFeeRewards(stateDB)
+ // 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 [precompileAddr].
+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/contracts/rewardmanager/contract_test.go b/precompile/contracts/rewardmanager/contract_test.go
new file mode 100644
index 0000000000..db1d61d3ae
--- /dev/null
+++ b/precompile/contracts/rewardmanager/contract_test.go
@@ -0,0 +1,277 @@
+// (c) 2019-2023, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package rewardmanager
+
+import (
+ "testing"
+
+ "github.com/ava-labs/subnet-evm/constants"
+ "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"
+)
+
+func TestRewardManagerRun(t *testing.T) {
+ testAddr := common.HexToAddress("0x0123")
+
+ tests := map[string]testutils.PrecompileTest{
+ "set allow fee recipients from no role fails": {
+ Caller: allowlist.TestNoRoleAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []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: allowlist.TestNoRoleAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []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: allowlist.TestNoRoleAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []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: allowlist.TestEnabledAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []byte {
+ input, err := PackAllowFeeRecipients()
+ require.NoError(t, err)
+
+ return input
+ },
+ 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: allowlist.TestEnabledAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []byte {
+ input, err := PackSetRewardAddress(testAddr)
+ require.NoError(t, err)
+
+ return input
+ },
+ 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: allowlist.TestEnabledAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []byte {
+ input, err := PackDisableRewards()
+ require.NoError(t, err)
+
+ return input
+ },
+ 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: allowlist.TestNoRoleAddr,
+ BeforeHook: func(t *testing.T, state contract.StateDB) {
+ allowlist.SetDefaultRoles(Module.Address)(t, state)
+ StoreRewardAddress(state, testAddr)
+ },
+ InputFn: func(t *testing.T) []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: allowlist.TestNoRoleAddr,
+ BeforeHook: func(t *testing.T, state contract.StateDB) {
+ allowlist.SetDefaultRoles(Module.Address)(t, state)
+ EnableAllowFeeRecipients(state)
+ },
+ InputFn: func(t *testing.T) []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: allowlist.TestNoRoleAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []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: allowlist.TestNoRoleAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []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: allowlist.TestEnabledAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []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: allowlist.TestEnabledAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []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: allowlist.TestEnabledAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []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: allowlist.TestEnabledAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []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: allowlist.TestEnabledAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []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: allowlist.TestEnabledAddr,
+ BeforeHook: allowlist.SetDefaultRoles(Module.Address),
+ InputFn: func(t *testing.T) []byte {
+ input, err := PackAreFeeRecipientsAllowed()
+ require.NoError(t, err)
+
+ return input
+ },
+ SuppliedGas: AreFeeRecipientsAllowedGasCost - 1,
+ ReadOnly: false,
+ ExpectedErr: vmerrs.ErrOutOfGas.Error(),
+ },
+ }
+
+ allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests)
+}
diff --git a/precompile/contracts/rewardmanager/module.go b/precompile/contracts/rewardmanager/module.go
new file mode 100644
index 0000000000..477d0d413c
--- /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/contract"
+ "github.com/ava-labs/subnet-evm/precompile/modules"
+ "github.com/ava-labs/subnet-evm/precompile/precompileconfig"
+ "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) MakeConfig() precompileconfig.Config {
+ return new(Config)
+}
+
+// Configure configures [state] with the initial state for the precompile.
+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)
+ }
+ // configure the RewardManager with the given initial configuration
+ if config.InitialRewardConfig != nil {
+ 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.Configure(state, ContractAddress)
+}
diff --git a/precompile/contracts/txallowlist/config.go b/precompile/contracts/txallowlist/config.go
new file mode 100644
index 0000000000..c6204b41c5
--- /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/precompileconfig"
+ "github.com/ethereum/go-ethereum/common"
+)
+
+var _ precompileconfig.Config = &Config{}
+
+// Config implements the StatefulPrecompileConfig interface while adding in the
+// TxAllowList specific precompile config.
+type Config struct {
+ 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{
+ AllowListConfig: allowlist.AllowListConfig{
+ AdminAddresses: admins,
+ EnabledAddresses: enableds,
+ },
+ Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp},
+ }
+}
+
+// NewDisableConfig returns config for a network upgrade at [blockTimestamp]
+// that disables TxAllowList.
+func NewDisableConfig(blockTimestamp *big.Int) *Config {
+ return &Config{
+ Upgrade: precompileconfig.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 precompileconfig.Config) bool {
+ // typecast before comparison
+ other, ok := (cfg).(*Config)
+ if !ok {
+ return false
+ }
+ 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
new file mode 100644
index 0000000000..54ab46ac34
--- /dev/null
+++ b/precompile/contracts/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/precompileconfig"
+ "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 precompileconfig.Config
+ ExpectedError string
+ }{
+ {
+ name: "invalid allow list config in tx allowlist",
+ config: NewConfig(big.NewInt(3), admins, admins),
+ ExpectedError: "cannot set address",
+ },
+ {
+ name: "nil member allow list config in tx allowlist",
+ config: NewConfig(big.NewInt(3), nil, nil),
+ ExpectedError: "",
+ },
+ {
+ name: "empty member allow list config in tx allowlist",
+ config: NewConfig(big.NewInt(3), []common.Address{}, []common.Address{}),
+ ExpectedError: "",
+ },
+ {
+ name: "valid allow list config in tx allowlist",
+ config: NewConfig(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 precompileconfig.Config
+ other precompileconfig.Config
+ expected bool
+ }{
+ {
+ name: "non-nil config and nil other",
+ config: NewConfig(big.NewInt(3), admins, enableds),
+ other: nil,
+ expected: false,
+ },
+ {
+ name: "different admin",
+ config: NewConfig(big.NewInt(3), admins, enableds),
+ other: NewConfig(big.NewInt(3), []common.Address{{3}}, enableds),
+ expected: false,
+ },
+ {
+ name: "different enabled",
+ config: NewConfig(big.NewInt(3), admins, enableds),
+ other: NewConfig(big.NewInt(3), admins, []common.Address{{3}}),
+ expected: false,
+ },
+ {
+ name: "different timestamp",
+ config: NewConfig(big.NewInt(3), admins, enableds),
+ other: NewConfig(big.NewInt(4), admins, enableds),
+ expected: false,
+ },
+ {
+ name: "same config",
+ config: NewConfig(big.NewInt(3), admins, enableds),
+ other: NewConfig(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/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..08104024c5
--- /dev/null
+++ b/precompile/contracts/txallowlist/contract_test.go
@@ -0,0 +1,15 @@
+// (c) 2019-2023, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package txallowlist
+
+import (
+ "testing"
+
+ "github.com/ava-labs/subnet-evm/core/state"
+ "github.com/ava-labs/subnet-evm/precompile/allowlist"
+)
+
+func TestTxAllowListRun(t *testing.T) {
+ allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, nil)
+}
diff --git a/precompile/contracts/txallowlist/module.go b/precompile/contracts/txallowlist/module.go
new file mode 100644
index 0000000000..3d58942dea
--- /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/contract"
+ "github.com/ava-labs/subnet-evm/precompile/modules"
+ "github.com/ava-labs/subnet-evm/precompile/precompileconfig"
+ "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) MakeConfig() precompileconfig.Config {
+ return new(Config)
+}
+
+// Configure configures [state] with the initial state for the precompile.
+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.AllowListConfig.Configure(state, ContractAddress)
+}
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..3ab469ed06
--- /dev/null
+++ b/precompile/modules/registerer.go
@@ -0,0 +1,98 @@
+// (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/constants"
+ "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 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)
+ }
+
+ 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..c0e4feb711
--- /dev/null
+++ b/precompile/modules/registerer_test.go
@@ -0,0 +1,59 @@
+// (c) 2023, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package modules
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/ava-labs/subnet-evm/constants"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/stretchr/testify/require"
+)
+
+func TestInsertSortedByAddress(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)
+}
+
+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")
+}
diff --git a/precompile/params.go b/precompile/params.go
deleted file mode 100644
index 965ab1df20..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")
- FeeConfigManagerAddress = common.HexToAddress("0x0200000000000000000000000000000000000003")
- RewardManagerAddress = common.HexToAddress("0x0200000000000000000000000000000000000004")
- // ADD YOUR PRECOMPILE HERE
- // {YourPrecompile}Address = common.HexToAddress("0x03000000000000000000000000000000000000??")
-
- UsedAddresses = []common.Address{
- ContractDeployerAllowListAddress,
- ContractNativeMinterAddress,
- TxAllowListAddress,
- FeeConfigManagerAddress,
- 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/precompileconfig/config.go b/precompile/precompileconfig/config.go
new file mode 100644
index 0000000000..fcee72fc95
--- /dev/null
+++ b/precompile/precompileconfig/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 precompileconfig
+
+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/precompileconfig/mock_config.go b/precompile/precompileconfig/mock_config.go
new file mode 100644
index 0000000000..6f1a9debc6
--- /dev/null
+++ b/precompile/precompileconfig/mock_config.go
@@ -0,0 +1,44 @@
+// (c) 2019-2023, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+// TODO: replace with gomock
+package precompileconfig
+
+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/precompileconfig/upgradeable.go
similarity index 60%
rename from precompile/upgradeable.go
rename to precompile/precompileconfig/upgradeable.go
index b835585b8b..3ad8c0498b 100644
--- a/precompile/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 precompile
+package precompileconfig
import (
"math/big"
@@ -9,29 +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.
-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/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/reward_manager.go b/precompile/reward_manager.go
deleted file mode 100644
index f887fa2f37..0000000000
--- a/precompile/reward_manager.go
+++ /dev/null
@@ -1,456 +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 precompile
-
-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/vmerrs"
-
- "github.com/ethereum/go-ethereum/common"
-)
-
-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
-
- // 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.
-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")
- ErrCannotDisableRewards = errors.New("non-enabled cannot call disableRewards")
- ErrCannotSetRewardAddress = errors.New("non-enabled cannot call setRewardAddress")
-
- ErrCannotEnableBothRewards = errors.New("cannot enable both fee recipients and reward address at the same time")
- ErrEmptyRewardAddress = errors.New("reward address cannot be empty")
-
- RewardManagerABI abi.ABI // will be initialized by init function
- RewardManagerPrecompile 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) {
- // 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
- if err := StoreRewardAddress(state, i.RewardAddress); err != nil {
- panic(err)
- }
- }
-}
-
-// 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 = createRewardManagerPrecompile(RewardManagerAddress)
-}
-
-// 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) {
- c.AllowListConfig.Configure(state, RewardManagerAddress)
- // configure the RewardManager with the given initial configuration
- if c.InitialRewardConfig != nil {
- 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)
- }
-}
-
-// 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)
-}
-
-// 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)
-}
-
-// PackAllowFeeRecipients packs the function selector (first 4 func signature bytes).
-// This function is mostly used for tests.
-func PackAllowFeeRecipients() ([]byte, error) {
- return RewardManagerABI.Pack("allowFeeRecipients")
-}
-
-// EnableAllowFeeRecipients enables fee recipients.
-func EnableAllowFeeRecipients(stateDB StateDB) {
- stateDB.SetState(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 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 {
- 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 modify it
- callerStatus := getAllowListStatus(stateDB, RewardManagerAddress, caller)
- if !callerStatus.IsEnabled() {
- return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotAllowFeeRecipients, caller)
- }
- // allow list code ends here.
-
- // this function does not return an output, leave this one as is
- EnableAllowFeeRecipients(stateDB)
- 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 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 {
- return nil, 0, err
- }
- // no input provided for this function
-
- stateDB := accessibleState.GetStateDB()
- var output bool
- _, output = GetStoredRewardAddress(stateDB)
-
- 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)
-}
-
-// 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)
- return common.BytesToAddress(val.Bytes()), val == allowFeeRecipientsAddressValue
-}
-
-// StoredRewardAddress stores the given [val] under rewardAddressStorageKey.
-func StoreRewardAddress(stateDB StateDB, val common.Address) error {
- // if input is empty, return an error
- if val == (common.Address{}) {
- return ErrEmptyRewardAddress
- }
- stateDB.SetState(RewardManagerAddress, rewardAddressStorageKey, val.Hash())
- return 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)
-}
-
-// 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
-}
-
-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 {
- 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 modify it
- callerStatus := getAllowListStatus(stateDB, RewardManagerAddress, caller)
- if !callerStatus.IsEnabled() {
- return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotSetRewardAddress, caller)
- }
- // allow list code ends here.
-
- if err := StoreRewardAddress(stateDB, inputStruct); err != nil {
- return nil, remainingGas, err
- }
- // 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
-}
-
-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 {
- return nil, 0, err
- }
-
- // no input provided for this function
- stateDB := accessibleState.GetStateDB()
- output, _ := GetStoredRewardAddress(stateDB)
- 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 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 {
- 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 modify it
- callerStatus := getAllowListStatus(stateDB, RewardManagerAddress, caller)
- if !callerStatus.IsEnabled() {
- return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotDisableRewards, caller)
- }
- // allow list code ends here.
- DisableFeeRewards(stateDB)
- // 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 [precompileAddr].
-func createRewardManagerPrecompile(precompileAddr common.Address) StatefulPrecompiledContract {
- 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")
- }
- functions = append(functions, newStatefulPrecompileFunction(methodSetRewardAddress.ID, setRewardAddress))
-
- // Construct the contract with no fallback function.
- contract := newStatefulPrecompileWithFunctionSelectors(nil, functions)
- return contract
-}
diff --git a/precompile/stateful_precompile_config.go b/precompile/stateful_precompile_config.go
deleted file mode 100644
index 7db30060e4..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
- // 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)
- // 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
-}
-
-// 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) {
- // 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})
- precompileConfig.Configure(chainConfig, state, blockContext)
-}
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)
+ }
+}
diff --git a/precompile/tx_allow_list.go b/precompile/tx_allow_list.go
deleted file mode 100644
index c67e07011f..0000000000
--- a/precompile/tx_allow_list.go
+++ /dev/null
@@ -1,94 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
-// See the file LICENSE for licensing terms.
-
-package precompile
-
-import (
- "encoding/json"
- "errors"
- "math/big"
-
- "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")
-)
-
-// TxAllowListConfig wraps [AllowListConfig] and uses it to implement the StatefulPrecompileConfig
-// interface while adding in the TxAllowList specific precompile address.
-type TxAllowListConfig struct {
- AllowListConfig
- 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{
- AllowListAdmins: admins,
- EnabledAddresses: enableds,
- },
- UpgradeableConfig: UpgradeableConfig{BlockTimestamp: blockTimestamp},
- }
-}
-
-// NewDisableTxAllowListConfig returns config for a network upgrade at [blockTimestamp]
-// that disables TxAllowList.
-func NewDisableTxAllowListConfig(blockTimestamp *big.Int) *TxAllowListConfig {
- return &TxAllowListConfig{
- UpgradeableConfig: UpgradeableConfig{
- BlockTimestamp: blockTimestamp,
- Disable: true,
- },
- }
-}
-
-// Address returns the address of the contract deployer allow list.
-func (c *TxAllowListConfig) Address() common.Address {
- return TxAllowListAddress
-}
-
-// Configure configures [state] with the desired admins based on [c].
-func (c *TxAllowListConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) {
- c.AllowListConfig.Configure(state, TxAllowListAddress)
-}
-
-// Contract returns the singleton stateful precompiled contract to be used for the allow list.
-func (c *TxAllowListConfig) Contract() 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 {
- // 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)
-}
-
-// 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/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..940c39e8a1 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-2023, 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")
)