diff --git a/accounts/abi/bind/precompilebind/precompile_config_template.go b/accounts/abi/bind/precompilebind/precompile_config_template.go index 69c4fcbcc9..eaefe4de31 100644 --- a/accounts/abi/bind/precompilebind/precompile_config_template.go +++ b/accounts/abi/bind/precompilebind/precompile_config_template.go @@ -15,9 +15,9 @@ import ( {{- if .Contract.AllowList}} "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ethereum/go-ethereum/common" {{- end}} - + + "github.com/ethereum/go-ethereum/common" ) var _ precompileconfig.Config = &Config{} @@ -63,6 +63,10 @@ func NewDisableConfig(blockTimestamp *uint64) *Config { // This should be the same key as used in the precompile module. func (*Config) Key() string { return ConfigKey } +// Address returns the address for the {{.Contract.Type}} precompileconfig. +// This should be the same key as used in the precompile module. +func (*Config) Address() common.Address { return ContractAddress } + // Verify tries to verify Config and returns an error accordingly. func (c *Config) Verify(chainConfig precompileconfig.ChainConfig) error { {{- if .Contract.AllowList}} diff --git a/core/rawdb/accessors_metadata.go b/core/rawdb/accessors_metadata.go index eafc43d83e..f692dd93b4 100644 --- a/core/rawdb/accessors_metadata.go +++ b/core/rawdb/accessors_metadata.go @@ -35,6 +35,14 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" + + // This side-effect import goes against recommendations for not using blank + // imports in libraries: https://google.github.io/styleguide/go/decisions#import-blank-import-_ + // This is deliberate because the ReadChainConfig() method only logs errors + // and doesn't bubble them up; this caused hours of problems in development! + // The described cons of using a blank import here are _far_ outweighed by + // the benefits. + _ "github.com/ava-labs/subnet-evm/params/paramsjson" // registers JSON unmarshalers to avoid circular dependency ) // ReadDatabaseVersion retrieves the version number of the database. diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index 6c2aa1ab89..aac3434196 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -45,6 +45,8 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/rlp" + + _ "github.com/ava-labs/subnet-evm/params/paramsjson" // registers JSON unmarshalers to avoid circular dependency ) type callContext struct { diff --git a/params/config.go b/params/config.go index 59ae91a869..f17da4b18b 100644 --- a/params/config.go +++ b/params/config.go @@ -36,7 +36,6 @@ import ( "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/subnet-evm/commontype" - "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" @@ -751,27 +750,29 @@ func (c *ChainConfig) rules(num *big.Int, timestamp uint64) Rules { // Rules returns the Avalanche modified rules to support Avalanche // network upgrades func (c *ChainConfig) Rules(blockNum *big.Int, timestamp uint64) Rules { - rules := c.rules(blockNum, timestamp) - - rules.AvalancheRules = c.GetAvalancheRules(timestamp) - - // Initialize the stateful precompiles that should be enabled at [blockTimestamp]. - rules.ActivePrecompiles = make(map[common.Address]precompileconfig.Config) - rules.Predicaters = make(map[common.Address]precompileconfig.Predicater) - rules.AccepterPrecompiles = make(map[common.Address]precompileconfig.Accepter) - for _, module := range modules.RegisteredModules() { - if config := c.getActivePrecompileConfig(module.Address, timestamp); config != nil && !config.IsDisabled() { - rules.ActivePrecompiles[module.Address] = config - if predicater, ok := config.(precompileconfig.Predicater); ok { - rules.Predicaters[module.Address] = predicater - } - if precompileAccepter, ok := config.(precompileconfig.Accepter); ok { - rules.AccepterPrecompiles[module.Address] = precompileAccepter - } + r := c.rules(blockNum, timestamp) + r.AvalancheRules = c.GetAvalancheRules(timestamp) + + r.ActivePrecompiles = make(map[common.Address]precompileconfig.Config) + r.Predicaters = make(map[common.Address]precompileconfig.Predicater) + r.AccepterPrecompiles = make(map[common.Address]precompileconfig.Accepter) + + for _, addr := range c.allPrecompileAddresses() { + cfg := c.getActivePrecompileConfig(addr, timestamp) + if cfg == nil || cfg.IsDisabled() { + continue + } + r.ActivePrecompiles[addr] = cfg + + if p, ok := cfg.(precompileconfig.Predicater); ok { + r.Predicaters[addr] = p + } + if a, ok := cfg.(precompileconfig.Accepter); ok { + r.AccepterPrecompiles[addr] = a } } - return rules + return r } // GetFeeConfig returns the original FeeConfig contained in the genesis ChainConfig. diff --git a/params/config_extra.go b/params/config_extra.go index 8eafe9e8d4..71847bac7d 100644 --- a/params/config_extra.go +++ b/params/config_extra.go @@ -31,26 +31,6 @@ type AvalancheContext struct { SnowCtx *snow.Context } -// 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) { @@ -119,25 +99,6 @@ func (cu ChainConfigWithUpgradesJSON) MarshalJSON() ([]byte, error) { return mergedJSON, nil } -func (cu *ChainConfigWithUpgradesJSON) UnmarshalJSON(input []byte) error { - var cc ChainConfig - if err := json.Unmarshal(input, &cc); err != nil { - return err - } - - type upgrades struct { - UpgradeConfig UpgradeConfig `json:"upgrades"` - } - - var u upgrades - if err := json.Unmarshal(input, &u); err != nil { - return err - } - cu.ChainConfig = cc - cu.UpgradeConfig = u.UpgradeConfig - return nil -} - // ToWithUpgradesJSON converts the ChainConfig to ChainConfigWithUpgradesJSON with upgrades explicitly displayed. // ChainConfig does not include upgrades in its JSON output. // This is a workaround for showing upgrades in the JSON output. diff --git a/params/config_test.go b/params/config_test.go index 707cf89f9d..0e16435952 100644 --- a/params/config_test.go +++ b/params/config_test.go @@ -24,7 +24,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package params +package params_test import ( "encoding/json" @@ -39,7 +39,11 @@ import ( "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + . "github.com/ava-labs/subnet-evm/params" + _ "github.com/ava-labs/subnet-evm/params/paramsjson" // registers JSON unmarshalers to avoid circular dependency ) func TestCheckCompatible(t *testing.T) { @@ -211,8 +215,8 @@ func TestConfigUnmarshalJSON(t *testing.T) { } } `) - c := ChainConfig{} - err := json.Unmarshal(config, &c) + c := &ChainConfig{} + err := json.Unmarshal(config, c) require.NoError(err) require.Equal(c.ChainID, big.NewInt(43214)) @@ -230,14 +234,14 @@ func TestConfigUnmarshalJSON(t *testing.T) { // 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) + c2 := &ChainConfig{} + err = json.Unmarshal(marshaled, c2) require.NoError(err) require.Equal(c, c2) } func TestActivePrecompiles(t *testing.T) { - config := ChainConfig{ + config := &ChainConfig{ UpgradeConfig: UpgradeConfig{ PrecompileUpgrades: []PrecompileUpgrade{ { @@ -251,10 +255,10 @@ func TestActivePrecompiles(t *testing.T) { } rules0 := config.Rules(common.Big0, 0) - require.True(t, rules0.IsPrecompileEnabled(nativeminter.Module.Address)) + assert.True(t, rules0.IsPrecompileEnabled(nativeminter.Module.Address)) rules1 := config.Rules(common.Big0, 1) - require.False(t, rules1.IsPrecompileEnabled(nativeminter.Module.Address)) + assert.False(t, rules1.IsPrecompileEnabled(nativeminter.Module.Address)) } func TestChainConfigMarshalWithUpgrades(t *testing.T) { diff --git a/params/json.go b/params/json.go new file mode 100644 index 0000000000..2e32e263b3 --- /dev/null +++ b/params/json.go @@ -0,0 +1,82 @@ +package params + +import ( + "fmt" +) + +// A JSONUnmarshaler is a type-safe function for unmarshalling a JSON buffer +// into a specific type. +type JSONUnmarshaler[T any] func([]byte, T) error + +var jsonUmarshalers struct { + cc JSONUnmarshaler[*ChainConfig] + cu JSONUnmarshaler[*ChainConfigWithUpgradesJSON] + uc JSONUnmarshaler[*UpgradeConfig] + pu JSONUnmarshaler[*PrecompileUpgrade] +} + +// MustRegisterJSONUnmarshalers registers the JSON unmarshalling functions for +// various types. This allows their unmarshalling behaviour to be injected by +// the [params/paramsjson] package, which can't be directly imported as it would +// result in a circular dependency. +// +// This function SHOULD NOT be called directly. Instead, blank import the +// [params/paramsjson] package, which registers unmarshalers in its init() +// function. +// +// MustRegisterJSONUnmarshalers panics if any functions are nil or if called +// more than once. +func MustRegisterJSONUnmarshalers( + cc JSONUnmarshaler[*ChainConfig], + cu JSONUnmarshaler[*ChainConfigWithUpgradesJSON], + uc JSONUnmarshaler[*UpgradeConfig], + pu JSONUnmarshaler[*PrecompileUpgrade], +) { + if jsonUmarshalers.cc != nil { + panic("JSON unmarshalers already registered") + } + panicIfNil(cc) + panicIfNil(cu) + panicIfNil(uc) + panicIfNil(pu) + + jsonUmarshalers.cc = cc + jsonUmarshalers.cu = cu + jsonUmarshalers.uc = uc + jsonUmarshalers.pu = pu +} + +func panicIfNil[T any](fn JSONUnmarshaler[T]) { + if fn == nil { + panic(fmt.Sprintf("registering nil %T", fn)) + } +} + +func unmarshalJSON[T any](u JSONUnmarshaler[T], data []byte, v T) error { + if u == nil { + return fmt.Errorf(`%T is nil; did you remember to import _ "github.com/ava-labs/subnet-evm/params/paramsjson"`, u) + } + return u(data, v) +} + +// 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. +// TODO(arr4n) update this method comment DO NOT MERGE +func (c *ChainConfig) UnmarshalJSON(data []byte) error { + return unmarshalJSON(jsonUmarshalers.cc, data, c) +} + +func (cu *ChainConfigWithUpgradesJSON) UnmarshalJSON(data []byte) error { + return unmarshalJSON(jsonUmarshalers.cu, data, cu) +} + +func (u *UpgradeConfig) UnmarshalJSON(data []byte) error { + return unmarshalJSON(jsonUmarshalers.uc, data, u) +} + +func (u *PrecompileUpgrade) UnmarshalJSON(data []byte) error { + return unmarshalJSON(jsonUmarshalers.pu, data, u) +} diff --git a/params/paramsjson/paramsjson.go b/params/paramsjson/paramsjson.go new file mode 100644 index 0000000000..289a599c45 --- /dev/null +++ b/params/paramsjson/paramsjson.go @@ -0,0 +1,152 @@ +// Package paramsjson provides JSON unmarshalling for `params` types that depend +// on the `modules` package. This avoids `params` depending on `modules`, even +// transitively, which would result in a circular dependency. +// +// This package doesn't export any identifiers. It should instead be blank _ +// imported to register its unmarshallers, similarly to SQL drivers. +package paramsjson + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" + "unsafe" + + "github.com/ava-labs/subnet-evm/params" + "github.com/ava-labs/subnet-evm/precompile/modules" +) + +func init() { + params.MustRegisterJSONUnmarshalers( + unmarshalChainConfig, + unmarshalChainConfigWithUpgrades, + unmarshalUpgradeConfig, + func(data []byte, v *params.PrecompileUpgrade) error { + return json.Unmarshal(data, (*precompileUpgrade)(v)) + }, + ) +} + +func unmarshalChainConfig(data []byte, v *params.ChainConfig) error { + return unmarshalChainConfigAndUpgrades(data, v, nil, "") +} + +func unmarshalChainConfigWithUpgrades(data []byte, v *params.ChainConfigWithUpgradesJSON) error { + const fldName = "UpgradeConfig" + _ = v.UpgradeConfig // if changing this then change the line above too + + tStruct := reflect.TypeOf(v).Elem() + fld, ok := tStruct.FieldByName(fldName) + if !ok { + // If this happens then the constant `fldName` is of a different name to the actual struct field used below. + return fmt.Errorf("BUG: %T(%v).FieldByName(%q) returned false", tStruct, tStruct, fldName) + } + return unmarshalChainConfigAndUpgrades(data, &v.ChainConfig, &v.UpgradeConfig, strings.Split(fld.Tag.Get("json"), ",")[0]) +} + +func unmarshalChainConfigAndUpgrades(data []byte, cc *params.ChainConfig, upgrades *params.UpgradeConfig, upgradesJSONField string) error { + type withoutMethods *params.ChainConfig // circumvents UnmarshalJSON() method, which always returns an error + if err := json.Unmarshal(data, withoutMethods(cc)); err != nil { + return err + } + + byField := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &byField); err != nil { + return err + } + + if cc.GenesisPrecompiles == nil { + cc.GenesisPrecompiles = make(params.Precompiles) + } + for fld, buf := range byField { + switch mod, ok := modules.GetPrecompileModule(fld); { + case ok: + conf := mod.MakeConfig() + if err := json.Unmarshal(buf, conf); err != nil { + return err + } + cc.GenesisPrecompiles[mod.ConfigKey] = conf + + case fld == upgradesJSONField && upgrades != nil: + if err := unmarshalUpgradeConfig(buf, upgrades); err != nil { + return err + } + } + } + + return nil +} + +func unmarshalUpgradeConfig(data []byte, uc *params.UpgradeConfig) error { + byField := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &byField); err != nil { + return err + } + + precompileT := reflect.TypeOf([]params.PrecompileUpgrade{}) + + config := reflect.ValueOf(uc).Elem() + for i := 0; i < config.NumField(); i++ { + fld := config.Type().FieldByIndex([]int{i}) + jsonFld := strings.Split(fld.Tag.Get("json"), ",")[0] + if _, ok := byField[jsonFld]; !ok { + continue + } + + var jsonInto any + switch fldVal := config.Field(i); { + case fld.Type == precompileT: + var out []precompileUpgrade + jsonInto = &out + defer func() { + uc.PrecompileUpgrades = *(*[]params.PrecompileUpgrade)(unsafe.Pointer(&out)) + }() + + case fld.Type.Kind() == reflect.Slice: + jsonInto = fldVal.Addr().Interface() + + case fld.Type.Kind() == reflect.Pointer: + if fldVal.IsNil() { + fldVal.Set(reflect.New(fld.Type.Elem())) + } + jsonInto = fldVal.Interface() + + default: + return fmt.Errorf("unsupported field %T.%s", uc, fld.Name) + } + + if err := json.Unmarshal(byField[jsonFld], jsonInto); err != nil { + return fmt.Errorf("json.Unmarshal field %q: %v", jsonFld, err) + } + } + + return nil +} + +type precompileUpgrade params.PrecompileUpgrade + +var _ json.Unmarshaler = (*precompileUpgrade)(nil) + +func (u *precompileUpgrade) UnmarshalJSON(data []byte) error { + byField := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &byField); err != nil { + return err + } + if n := len(byField); n != 1 { + return fmt.Errorf("unmarshalling %T; got %d JSON fields; MUST be exactly one (name of precompile module)", ¶ms.PrecompileUpgrade{}, n) + } + + for key, value := range byField { + mod, ok := modules.GetPrecompileModule(key) + if !ok { + return fmt.Errorf("unknown precompile config: %s", key) + } + config := mod.MakeConfig() + if err := json.Unmarshal(value, config); err != nil { + return err + } + u.Config = config + } + return nil +} diff --git a/params/precompile_config_test.go b/params/precompile_config_test.go index 4e2c287241..e1b4441d87 100644 --- a/params/precompile_config_test.go +++ b/params/precompile_config_test.go @@ -1,10 +1,11 @@ // (c) 2022 Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package params +package params_test import ( "encoding/json" + "fmt" "math/big" "testing" @@ -17,6 +18,8 @@ import ( "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" + + . "github.com/ava-labs/subnet-evm/params" ) func TestVerifyWithChainConfig(t *testing.T) { @@ -270,30 +273,46 @@ func TestGetPrecompileConfig(t *testing.T) { deployerallowlist.ConfigKey: deployerallowlist.NewConfig(utils.NewUint64(10), nil, nil, nil), } - deployerConfig := config.getActivePrecompileConfig(deployerallowlist.ContractAddress, 0) - require.Nil(deployerConfig) + configs := config.GetActivatingPrecompileConfigs(deployerallowlist.ContractAddress, nil, 0, config.PrecompileUpgrades) + require.Len(configs, 0) - deployerConfig = config.getActivePrecompileConfig(deployerallowlist.ContractAddress, 10) - require.NotNil(deployerConfig) + configs = config.GetActivatingPrecompileConfigs(deployerallowlist.ContractAddress, nil, 10, config.PrecompileUpgrades) + require.GreaterOrEqual(len(configs), 1) + require.NotNil(configs[len(configs)-1]) - deployerConfig = config.getActivePrecompileConfig(deployerallowlist.ContractAddress, 11) - require.NotNil(deployerConfig) + configs = config.GetActivatingPrecompileConfigs(deployerallowlist.ContractAddress, nil, 11, config.PrecompileUpgrades) + require.GreaterOrEqual(len(configs), 1) + require.NotNil(configs[len(configs)-1]) - txAllowListConfig := config.getActivePrecompileConfig(txallowlist.ContractAddress, 0) - require.Nil(txAllowListConfig) + txAllowListConfig := config.GetActivatingPrecompileConfigs(txallowlist.ContractAddress, nil, 0, config.PrecompileUpgrades) + require.Len(txAllowListConfig, 0) } func TestPrecompileUpgradeUnmarshalJSON(t *testing.T) { require := require.New(t) - upgradeBytes := []byte(` + const ( + durangoTimestamp = 314159 + stateUpgradeTimestamp = 142857 + rewardManagerTimestamp = 1671542573 + rewardManagerAdmin = "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" + nativeMinterTimestamp = 1671543172 + ) + + upgradeBytes := []byte(fmt.Sprintf(` { + "networkUpgradeOverrides": { + "durangoTimestamp": %d + }, + "stateUpgrades": [ + {"blockTimestamp": %d} + ], "precompileUpgrades": [ { "rewardManagerConfig": { - "blockTimestamp": 1671542573, + "blockTimestamp": %d, "adminAddresses": [ - "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" + %q ], "initialRewardConfig": { "allowFeeRecipients": true @@ -302,36 +321,41 @@ func TestPrecompileUpgradeUnmarshalJSON(t *testing.T) { }, { "contractNativeMinterConfig": { - "blockTimestamp": 1671543172, + "blockTimestamp": %d, "disable": false } } ] } - `) + `, durangoTimestamp, stateUpgradeTimestamp, rewardManagerTimestamp, rewardManagerAdmin, nativeMinterTimestamp)) 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( - utils.NewUint64(1671542573), - []common.Address{common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC")}, - nil, - nil, - &rewardmanager.InitialRewardConfig{ - AllowFeeRecipients: true, - }) - require.True(rewardManagerConf.Equal(testRewardManagerConfig)) - - nativeMinterConfig := upgradeConfig.PrecompileUpgrades[1] - require.Equal(nativeMinterConfig.Key(), nativeminter.ConfigKey) - expectedNativeMinterConfig := nativeminter.NewConfig(utils.NewUint64(1671543172), nil, nil, nil, nil) - require.True(nativeMinterConfig.Equal(expectedNativeMinterConfig)) + want := UpgradeConfig{ + NetworkUpgradeOverrides: &NetworkUpgrades{ + DurangoTimestamp: utils.NewUint64(durangoTimestamp), + }, + StateUpgrades: []StateUpgrade{{ + BlockTimestamp: utils.NewUint64(stateUpgradeTimestamp), + }}, + PrecompileUpgrades: []PrecompileUpgrade{ + { + rewardmanager.NewConfig( + utils.NewUint64(rewardManagerTimestamp), + []common.Address{common.HexToAddress(rewardManagerAdmin)}, nil, nil, + &rewardmanager.InitialRewardConfig{ + AllowFeeRecipients: true, + }, + ), + }, + { + nativeminter.NewConfig(utils.NewUint64(nativeMinterTimestamp), nil, nil, nil, nil), + }, + }, + } + require.Equal(want, upgradeConfig) // Marshal and unmarshal again and check that the result is the same upgradeBytes2, err := json.Marshal(upgradeConfig) diff --git a/params/precompile_upgrade.go b/params/precompile_upgrade.go index 3f762f96c4..393bb65b31 100644 --- a/params/precompile_upgrade.go +++ b/params/precompile_upgrade.go @@ -5,17 +5,13 @@ package params import ( "encoding/json" - "errors" "fmt" - "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 @@ -24,35 +20,6 @@ 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 -// 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) { @@ -160,28 +127,17 @@ func (c *ChainConfig) getActivePrecompileConfig(address common.Address, timestam // GetActivatingPrecompileConfigs returns all precompile 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 *uint64, to uint64, 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) + var configs []precompileconfig.Config + maybeAppend := func(pc precompileconfig.Config) { + if pc.Address() == address && utils.IsForkTransition(pc.Timestamp(), from, to) { + configs = append(configs, pc) } } - // Loop over all upgrades checking for the requested precompile config. + for _, p := range c.GenesisPrecompiles { + maybeAppend(p) + } 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) - } - } + maybeAppend(upgrade.Config) } return configs } @@ -194,12 +150,11 @@ func (c *ChainConfig) GetActivatingPrecompileConfigs(address common.Address, fro // 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, time uint64) *ConfigCompatError { - for _, module := range modules.RegisteredModules() { - if err := c.checkPrecompileCompatible(module.Address, precompileUpgrades, time); err != nil { + for _, a := range c.allPrecompileAddressesPlus(precompileUpgrades...) { + if err := c.checkPrecompileCompatible(a, precompileUpgrades, time); err != nil { return err } } - return nil } @@ -247,11 +202,41 @@ func (c *ChainConfig) checkPrecompileCompatible(address common.Address, precompi // EnabledStatefulPrecompiles returns current stateful precompile configs that are enabled at [blockTimestamp]. func (c *ChainConfig) EnabledStatefulPrecompiles(blockTimestamp uint64) Precompiles { statefulPrecompileConfigs := make(Precompiles) - for _, module := range modules.RegisteredModules() { - if config := c.getActivePrecompileConfig(module.Address, blockTimestamp); config != nil && !config.IsDisabled() { - statefulPrecompileConfigs[module.ConfigKey] = config + for key, addr := range c.allPrecompileAddresses() { + if config := c.getActivePrecompileConfig(addr, blockTimestamp); config != nil && !config.IsDisabled() { + statefulPrecompileConfigs[key] = config } } - return statefulPrecompileConfigs } + +// allPrecompileAddresses is equivalent to allPrecompileAddressesPlus() without +// any arguments. The two methods are differentiated to improve readability at +// the call sites. +func (c *ChainConfig) allPrecompileAddresses() map[string]common.Address { + return c.allPrecompileAddressesPlus() +} + +// allPrecompileAddressesPlus returns a mapping from precompile config key to +// address for all precompiles defined in [ChainConfig.GenesisPrecompiles], +// [ChainConfig.UpgradeConfig.PrecompileUpgrades], plus the `extra` upgrades. +func (c *ChainConfig) allPrecompileAddressesPlus(extra ...PrecompileUpgrade) map[string]common.Address { + all := make(map[string]common.Address) + add := func(pc precompileconfig.Config) { + if a, ok := all[pc.Key()]; ok && a != pc.Address() { + panic("DO NOT MERGE") + } + all[pc.Key()] = pc.Address() + } + + for _, p := range c.GenesisPrecompiles { + add(p) + } + for _, p := range c.UpgradeConfig.PrecompileUpgrades { + add(p.Config) + } + for _, p := range extra { + add(p.Config) + } + return all +} diff --git a/params/precompile_upgrade_test.go b/params/precompile_upgrade_test.go index 8384ef4279..5c21b08534 100644 --- a/params/precompile_upgrade_test.go +++ b/params/precompile_upgrade_test.go @@ -1,7 +1,7 @@ // (c) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package params +package params_test import ( "testing" @@ -11,6 +11,8 @@ import ( "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" + + . "github.com/ava-labs/subnet-evm/params" ) func TestVerifyUpgradeConfig(t *testing.T) { @@ -279,7 +281,7 @@ func (tt *upgradeCompatibilityTest) run(t *testing.T, chainConfig ChainConfig) { newCfg := chainConfig newCfg.UpgradeConfig = *upgrade - err := chainConfig.checkCompatible(&newCfg, nil, tt.startTimestamps[i]) + err := chainConfig.CheckCompatible(&newCfg, 0, tt.startTimestamps[i]) // if this is not the final upgradeBytes, continue applying // the next upgradeBytes. (only check the result on the last apply) diff --git a/params/precompiles.go b/params/precompiles.go index 5d8ed74bda..4c05bc7e3c 100644 --- a/params/precompiles.go +++ b/params/precompiles.go @@ -4,33 +4,7 @@ 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/params/state_upgrade_test.go b/params/state_upgrade_test.go index 6ee4094fc0..da4361d3cb 100644 --- a/params/state_upgrade_test.go +++ b/params/state_upgrade_test.go @@ -1,7 +1,7 @@ // (c) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package params +package params_test import ( "encoding/json" @@ -12,6 +12,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/stretchr/testify/require" + + . "github.com/ava-labs/subnet-evm/params" ) func TestVerifyStateUpgrades(t *testing.T) { diff --git a/plugin/evm/static_service_test.go b/plugin/evm/static_service_test.go index 626f8f30d3..3529b4908d 100644 --- a/plugin/evm/static_service_test.go +++ b/plugin/evm/static_service_test.go @@ -12,6 +12,8 @@ import ( "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/params" "github.com/stretchr/testify/assert" + + _ "github.com/ava-labs/subnet-evm/params/paramsjson" // registers custom unmarshalers without circular dependencies ) var testGenesisJSON = `{"config":{"chainId":43111,"homesteadBlock":0,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"muirGlacierBlock":0,"subnetEVMTimestamp":0},"nonce":"0x0","timestamp":"0x0","extraData":"0x00","gasLimit":"0x5f5e100","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"0100000000000000000000000000000000000000":{"code":"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033","balance":"0x0"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}` diff --git a/plugin/main.go b/plugin/main.go index afdd416d2a..e5692d54e4 100644 --- a/plugin/main.go +++ b/plugin/main.go @@ -9,6 +9,8 @@ import ( "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/subnet-evm/plugin/evm" "github.com/ava-labs/subnet-evm/plugin/runner" + + _ "github.com/ava-labs/subnet-evm/params/paramsjson" // registers JSON unmarshalers to avoid circular dependency ) func main() { diff --git a/precompile/allowlist/allowlist.go b/precompile/allowlist/allowlist.go index c5e60458cc..75d2baea3d 100644 --- a/precompile/allowlist/allowlist.go +++ b/precompile/allowlist/allowlist.go @@ -1,4 +1,4 @@ -// (c) 2019-2023, Ava Labs, Inc. All rights reserved. +// (c) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package allowlist diff --git a/precompile/allowlist/allowlist_test.go b/precompile/allowlist/allowlist_test.go index ebbcf6b69e..5cc511228d 100644 --- a/precompile/allowlist/allowlist_test.go +++ b/precompile/allowlist/allowlist_test.go @@ -25,8 +25,10 @@ type dummyConfig struct { AllowListConfig } -func (d *dummyConfig) Key() string { return "dummy" } -func (d *dummyConfig) IsDisabled() bool { return false } +func (*dummyConfig) Key() string { return "dummy" } +func (*dummyConfig) Address() common.Address { return dummyAddr } +func (*dummyConfig) IsDisabled() bool { return false } + func (d *dummyConfig) Verify(chainConfig precompileconfig.ChainConfig) error { return d.AllowListConfig.Verify(chainConfig, d.Upgrade) } diff --git a/precompile/contract/interfaces.go b/precompile/contract/interfaces.go index 5ac6baa486..b7f800d26d 100644 --- a/precompile/contract/interfaces.go +++ b/precompile/contract/interfaces.go @@ -8,10 +8,14 @@ import ( "math/big" "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ethereum/go-ethereum/common" ) +// Guarantee that we don't have a circular dependency if importing types here. +var _ *types.Transaction = nil + // StatefulPrecompiledContract is the interface for executing a precompiled contract type StatefulPrecompiledContract interface { // Run executes the precompiled contract. diff --git a/precompile/contracts/deployerallowlist/config.go b/precompile/contracts/deployerallowlist/config.go index a588101dc3..521ecfd62b 100644 --- a/precompile/contracts/deployerallowlist/config.go +++ b/precompile/contracts/deployerallowlist/config.go @@ -1,4 +1,4 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// (c) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package deployerallowlist @@ -44,6 +44,8 @@ func NewDisableConfig(blockTimestamp *uint64) *Config { func (*Config) Key() string { return ConfigKey } +func (*Config) Address() common.Address { return ContractAddress } + // 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 diff --git a/precompile/contracts/feemanager/config.go b/precompile/contracts/feemanager/config.go index 9dcfc307d2..3183d7f226 100644 --- a/precompile/contracts/feemanager/config.go +++ b/precompile/contracts/feemanager/config.go @@ -1,4 +1,4 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// (c) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package feemanager @@ -50,6 +50,8 @@ func NewDisableConfig(blockTimestamp *uint64) *Config { // This should be the same key as used in the precompile module. func (*Config) Key() string { return ConfigKey } +func (*Config) Address() common.Address { return ContractAddress } + // 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 diff --git a/precompile/contracts/nativeminter/config.go b/precompile/contracts/nativeminter/config.go index 38a65ee6c8..a6c1c1464e 100644 --- a/precompile/contracts/nativeminter/config.go +++ b/precompile/contracts/nativeminter/config.go @@ -1,4 +1,4 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// (c) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nativeminter @@ -54,6 +54,8 @@ func NewDisableConfig(blockTimestamp *uint64) *Config { // This should be the same key as used in the precompile module. func (*Config) Key() string { return ConfigKey } +func (*Config) Address() common.Address { return ContractAddress } + // 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 diff --git a/precompile/contracts/rewardmanager/config.go b/precompile/contracts/rewardmanager/config.go index 49949cac1a..aa9fc6bcec 100644 --- a/precompile/contracts/rewardmanager/config.go +++ b/precompile/contracts/rewardmanager/config.go @@ -90,6 +90,8 @@ func NewDisableConfig(blockTimestamp *uint64) *Config { // This should be the same key as used in the precompile module. func (*Config) Key() string { return ConfigKey } +func (*Config) Address() common.Address { return ContractAddress } + // Verify tries to verify Config and returns an error accordingly. func (c *Config) Verify(chainConfig precompileconfig.ChainConfig) error { if c.InitialRewardConfig != nil { diff --git a/precompile/contracts/txallowlist/config.go b/precompile/contracts/txallowlist/config.go index f5656d9c78..e1449c1628 100644 --- a/precompile/contracts/txallowlist/config.go +++ b/precompile/contracts/txallowlist/config.go @@ -42,7 +42,9 @@ func NewDisableConfig(blockTimestamp *uint64) *Config { } } -func (c *Config) Key() string { return ConfigKey } +func (*Config) Key() string { return ConfigKey } + +func (*Config) Address() common.Address { return ContractAddress } // Equal returns true if [cfg] is a [*TxAllowListConfig] and it has been configured identical to [c]. func (c *Config) Equal(cfg precompileconfig.Config) bool { diff --git a/precompile/contracts/warp/config.go b/precompile/contracts/warp/config.go index dde04a8695..48d773b9ea 100644 --- a/precompile/contracts/warp/config.go +++ b/precompile/contracts/warp/config.go @@ -80,6 +80,8 @@ func NewDisableConfig(blockTimestamp *uint64) *Config { // This should be the same key as used in the precompile module. func (*Config) Key() string { return ConfigKey } +func (*Config) Address() common.Address { return ContractAddress } + // Verify tries to verify Config and returns an error accordingly. func (c *Config) Verify(chainConfig precompileconfig.ChainConfig) error { if c.Timestamp() != nil { diff --git a/precompile/precompileconfig/config.go b/precompile/precompileconfig/config.go index 05d204de45..58d4f73ab8 100644 --- a/precompile/precompileconfig/config.go +++ b/precompile/precompileconfig/config.go @@ -19,6 +19,8 @@ import ( type Config interface { // Key returns the unique key for the stateful precompile. Key() string + // Address returns the address at which the precompile should be made available. + 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]. diff --git a/precompile/precompileconfig/mocks.go b/precompile/precompileconfig/mocks.go index 614ec5a522..86642f99d8 100644 --- a/precompile/precompileconfig/mocks.go +++ b/precompile/precompileconfig/mocks.go @@ -92,6 +92,20 @@ func (m *MockConfig) EXPECT() *MockConfigMockRecorder { return m.recorder } +// Address mocks base method. +func (m *MockConfig) Address() common.Address { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Address") + ret0, _ := ret[0].(common.Address) + return ret0 +} + +// Address indicates an expected call of Address. +func (mr *MockConfigMockRecorder) Address() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Address", reflect.TypeOf((*MockConfig)(nil).Address)) +} + // Equal mocks base method. func (m *MockConfig) Equal(arg0 Config) bool { m.ctrl.T.Helper() diff --git a/tests/utils/subnet.go b/tests/utils/subnet.go index 6f7aea3500..e646f71f3b 100644 --- a/tests/utils/subnet.go +++ b/tests/utils/subnet.go @@ -25,6 +25,8 @@ import ( "github.com/go-cmd/cmd" "github.com/onsi/ginkgo/v2" "github.com/stretchr/testify/require" + + _ "github.com/ava-labs/subnet-evm/params/paramsjson" // registers JSON unmarshalers to avoid circular dependency ) type SubnetSuite struct {