diff --git a/accounts/abi/bind/precompile_template.go b/accounts/abi/bind/precompile_template.go index f1ad1421f3..d7e70a3c18 100644 --- a/accounts/abi/bind/precompile_template.go +++ b/accounts/abi/bind/precompile_template.go @@ -195,6 +195,20 @@ 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 { @@ -203,6 +217,10 @@ func Get{{.Contract.Type}}AllowListStatus(stateDB StateDB, address common.Addres // 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] +// 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) } diff --git a/commontype/fee_config.go b/commontype/fee_config.go index 9843193ef9..f09eceeff9 100644 --- a/commontype/fee_config.go +++ b/commontype/fee_config.go @@ -7,6 +7,7 @@ import ( "fmt" "math/big" + "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" ) @@ -81,6 +82,22 @@ func (f *FeeConfig) Verify() error { return f.checkByteLens() } +// Equal checks if given [other] is same with this FeeConfig. +func (f *FeeConfig) Equal(other *FeeConfig) bool { + if other == nil { + return false + } + + return utils.BigNumEqual(f.GasLimit, other.GasLimit) && + f.TargetBlockRate == other.TargetBlockRate && + utils.BigNumEqual(f.MinBaseFee, other.MinBaseFee) && + utils.BigNumEqual(f.TargetGas, other.TargetGas) && + utils.BigNumEqual(f.BaseFeeChangeDenominator, other.BaseFeeChangeDenominator) && + utils.BigNumEqual(f.MinBlockGasCost, other.MinBlockGasCost) && + utils.BigNumEqual(f.MaxBlockGasCost, other.MaxBlockGasCost) && + utils.BigNumEqual(f.BlockGasCostStep, other.BlockGasCostStep) +} + // checkByteLens checks byte lengths against common.HashLen (32 bytes) and returns error func (f *FeeConfig) checkByteLens() error { if isBiggerThanHashLen(f.GasLimit) { diff --git a/commontype/fee_config_test.go b/commontype/fee_config_test.go new file mode 100644 index 0000000000..57c8a58b06 --- /dev/null +++ b/commontype/fee_config_test.go @@ -0,0 +1,140 @@ +// (c) 2019-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package commontype + +import ( + "math/big" + "testing" + + "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 + config *FeeConfig + expectedError string + }{ + { + name: "invalid GasLimit in FeeConfig", + config: func() *FeeConfig { c := validFeeConfig; 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 }(), + 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 }(), + 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 }(), + 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 }(), + 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 }(), + expectedError: "minBlockGasCost = -1 cannot be less than 0", + }, + { + name: "valid FeeConfig", + config: &validFeeConfig, + expectedError: "", + }, + { + name: "MinBlockGasCost bigger than MaxBlockGasCost in FeeConfig", + config: func() *FeeConfig { + c := validFeeConfig + c.MinBlockGasCost = big.NewInt(2) + c.MaxBlockGasCost = big.NewInt(1) + return &c + }(), + expectedError: "minBlockGasCost = 2 cannot be greater than maxBlockGasCost = 1", + }, + { + name: "invalid BlockGasCostStep in FeeConfig", + config: func() *FeeConfig { c := validFeeConfig; c.BlockGasCostStep = big.NewInt(-1); return &c }(), + expectedError: "blockGasCostStep = -1 cannot be less than 0", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.config.Verify() + if test.expectedError == "" { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), test.expectedError) + } + }) + } +} + +func TestEqual(t *testing.T) { + tests := []struct { + name string + a *FeeConfig + b *FeeConfig + expected bool + }{ + { + name: "equal", + a: &validFeeConfig, + b: &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), + }, + expected: true, + }, + { + name: "not equal", + a: &validFeeConfig, + b: func() *FeeConfig { c := validFeeConfig; c.GasLimit = big.NewInt(1); return &c }(), + expected: false, + }, + { + name: "not equal nil", + a: &validFeeConfig, + b: nil, + expected: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require.Equal(t, test.expected, test.a.Equal(test.b)) + }) + } +} diff --git a/core/genesis_test.go b/core/genesis_test.go index 5080d0baed..63762fd61d 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -183,7 +183,7 @@ 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}) + config.ContractDeployerAllowListConfig = precompile.NewContractDeployerAllowListConfig(big.NewInt(0), []common.Address{addr}, nil) return &config }, assertState: func(t *testing.T, sdb *state.StateDB) { diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 1c02a6072a..e7bee1692e 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -332,7 +332,7 @@ func TestBadTxAllowListBlock(t *testing.T) { SubnetEVMTimestamp: big.NewInt(0), }, PrecompileUpgrade: params.PrecompileUpgrade{ - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(0), []common.Address{}), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(0), nil, nil), }, } signer = types.LatestSigner(config) diff --git a/core/stateful_precompile_test.go b/core/stateful_precompile_test.go index d53017f647..23d5b0b0ed 100644 --- a/core/stateful_precompile_test.go +++ b/core/stateful_precompile_test.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" + "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" @@ -89,10 +90,7 @@ func TestContractDeployerAllowListRun(t *testing.T) { readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetContractDeployerAllowListStatus(state, adminAddr) - assert.Equal(t, precompile.AllowListAdmin, res) - - res = precompile.GetContractDeployerAllowListStatus(state, noRoleAddr) + res := precompile.GetContractDeployerAllowListStatus(state, noRoleAddr) assert.Equal(t, precompile.AllowListAdmin, res) }, }, @@ -110,10 +108,7 @@ func TestContractDeployerAllowListRun(t *testing.T) { readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetContractDeployerAllowListStatus(state, adminAddr) - assert.Equal(t, precompile.AllowListAdmin, res) - - res = precompile.GetContractDeployerAllowListStatus(state, noRoleAddr) + res := precompile.GetContractDeployerAllowListStatus(state, noRoleAddr) assert.Equal(t, precompile.AllowListEnabled, res) }, }, @@ -214,10 +209,7 @@ func TestContractDeployerAllowListRun(t *testing.T) { suppliedGas: precompile.ReadAllowListGasCost, readOnly: false, expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(), - assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetContractDeployerAllowListStatus(state, noRoleAddr) - assert.Equal(t, precompile.AllowListNoRole, res) - }, + assertState: nil, }, "read allow list admin role": { caller: adminAddr, @@ -228,10 +220,7 @@ func TestContractDeployerAllowListRun(t *testing.T) { suppliedGas: precompile.ReadAllowListGasCost, readOnly: false, expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(), - assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetContractDeployerAllowListStatus(state, adminAddr) - assert.Equal(t, precompile.AllowListAdmin, res) - }, + assertState: nil, }, "read allow list with readOnly enabled": { caller: adminAddr, @@ -242,10 +231,7 @@ func TestContractDeployerAllowListRun(t *testing.T) { suppliedGas: precompile.ReadAllowListGasCost, readOnly: true, expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(), - assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetContractDeployerAllowListStatus(state, adminAddr) - assert.Equal(t, precompile.AllowListAdmin, res) - }, + assertState: nil, }, "read allow list out of gas": { caller: adminAddr, @@ -268,6 +254,9 @@ func TestContractDeployerAllowListRun(t *testing.T) { // 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) + assert.Equal(t, precompile.AllowListAdmin, precompile.GetContractDeployerAllowListStatus(state, adminAddr)) + assert.Equal(t, precompile.AllowListNoRole, precompile.GetContractDeployerAllowListStatus(state, noRoleAddr)) + blockContext := &mockBlockContext{blockNumber: common.Big0} ret, remainingGas, err := precompile.ContractDeployerAllowListPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext}, test.caller, test.precompileAddr, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { @@ -325,10 +314,7 @@ func TestTxAllowListRun(t *testing.T) { readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetTxAllowListStatus(state, adminAddr) - assert.Equal(t, precompile.AllowListAdmin, res) - - res = precompile.GetTxAllowListStatus(state, noRoleAddr) + res := precompile.GetTxAllowListStatus(state, noRoleAddr) assert.Equal(t, precompile.AllowListAdmin, res) }, }, @@ -346,10 +332,7 @@ func TestTxAllowListRun(t *testing.T) { readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetTxAllowListStatus(state, adminAddr) - assert.Equal(t, precompile.AllowListAdmin, res) - - res = precompile.GetTxAllowListStatus(state, noRoleAddr) + res := precompile.GetTxAllowListStatus(state, noRoleAddr) assert.Equal(t, precompile.AllowListEnabled, res) }, }, @@ -450,10 +433,7 @@ func TestTxAllowListRun(t *testing.T) { suppliedGas: precompile.ReadAllowListGasCost, readOnly: false, expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(), - assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetTxAllowListStatus(state, noRoleAddr) - assert.Equal(t, precompile.AllowListNoRole, res) - }, + assertState: nil, }, "read allow list admin role": { caller: adminAddr, @@ -464,10 +444,7 @@ func TestTxAllowListRun(t *testing.T) { suppliedGas: precompile.ReadAllowListGasCost, readOnly: false, expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(), - assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetTxAllowListStatus(state, adminAddr) - assert.Equal(t, precompile.AllowListAdmin, res) - }, + assertState: nil, }, "read allow list with readOnly enabled": { caller: adminAddr, @@ -478,10 +455,7 @@ func TestTxAllowListRun(t *testing.T) { suppliedGas: precompile.ReadAllowListGasCost, readOnly: true, expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(), - assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetTxAllowListStatus(state, adminAddr) - assert.Equal(t, precompile.AllowListAdmin, res) - }, + assertState: nil, }, "read allow list out of gas": { caller: adminAddr, @@ -503,6 +477,8 @@ func TestTxAllowListRun(t *testing.T) { // Set up the state so that each address has the expected permissions at the start. precompile.SetTxAllowListStatus(state, adminAddr, precompile.AllowListAdmin) + assert.Equal(t, precompile.AllowListAdmin, precompile.GetTxAllowListStatus(state, adminAddr)) + blockContext := &mockBlockContext{blockNumber: common.Big0} ret, remainingGas, err := precompile.TxAllowListPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext}, test.caller, test.precompileAddr, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { @@ -535,6 +511,7 @@ func TestContractNativeMinterRun(t *testing.T) { input func() []byte suppliedGas uint64 readOnly bool + config *precompile.ContractNativeMinterConfig expectedRes []byte expectedErr string @@ -543,8 +520,9 @@ func TestContractNativeMinterRun(t *testing.T) { } adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") - allowAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") + 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": { @@ -561,11 +539,11 @@ func TestContractNativeMinterRun(t *testing.T) { readOnly: false, expectedErr: precompile.ErrCannotMint.Error(), }, - "mint funds from allow address": { - caller: allowAddr, + "mint funds from enabled address": { + caller: enabledAddr, precompileAddr: precompile.ContractNativeMinterAddress, input: func() []byte { - input, err := precompile.PackMintInput(allowAddr, common.Big1) + input, err := precompile.PackMintInput(enabledAddr, common.Big1) if err != nil { panic(err) } @@ -575,10 +553,41 @@ func TestContractNativeMinterRun(t *testing.T) { readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetContractNativeMinterStatus(state, allowAddr) - assert.Equal(t, precompile.AllowListEnabled, res) - - assert.Equal(t, common.Big1, state.GetBalance(allowAddr), "expected minted funds") + assert.Equal(t, common.Big1, state.GetBalance(enabledAddr), "expected minted funds") + }, + }, + "enabled role by config": { + caller: noRoleAddr, + precompileAddr: precompile.ContractNativeMinterAddress, + 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) { + assert.Equal(t, precompile.AllowListEnabled, precompile.GetContractNativeMinterStatus(state, testAddr)) + }, + config: &precompile.ContractNativeMinterConfig{ + AllowListConfig: precompile.AllowListConfig{EnabledAddresses: []common.Address{testAddr}}, + }, + }, + "initial mint funds": { + caller: enabledAddr, + precompileAddr: precompile.ContractNativeMinterAddress, + 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) { + assert.Equal(t, common.Big2, state.GetBalance(enabledAddr), "expected minted funds") }, }, "mint funds from admin address": { @@ -595,9 +604,6 @@ func TestContractNativeMinterRun(t *testing.T) { readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetContractNativeMinterStatus(state, adminAddr) - assert.Equal(t, precompile.AllowListAdmin, res) - assert.Equal(t, common.Big1, state.GetBalance(adminAddr), "expected minted funds") }, }, @@ -615,9 +621,6 @@ func TestContractNativeMinterRun(t *testing.T) { readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetContractNativeMinterStatus(state, adminAddr) - assert.Equal(t, precompile.AllowListAdmin, res) - assert.Equal(t, math.MaxBig256, state.GetBalance(adminAddr), "expected minted funds") }, }, @@ -636,10 +639,10 @@ func TestContractNativeMinterRun(t *testing.T) { expectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly mint with allow role fails": { - caller: allowAddr, + caller: enabledAddr, precompileAddr: precompile.ContractNativeMinterAddress, input: func() []byte { - input, err := precompile.PackMintInput(allowAddr, common.Big1) + input, err := precompile.PackMintInput(enabledAddr, common.Big1) if err != nil { panic(err) } @@ -667,7 +670,7 @@ func TestContractNativeMinterRun(t *testing.T) { caller: adminAddr, precompileAddr: precompile.ContractNativeMinterAddress, input: func() []byte { - input, err := precompile.PackMintInput(allowAddr, common.Big1) + input, err := precompile.PackMintInput(enabledAddr, common.Big1) if err != nil { panic(err) } @@ -723,15 +726,12 @@ func TestContractNativeMinterRun(t *testing.T) { readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetContractNativeMinterStatus(state, adminAddr) - assert.Equal(t, precompile.AllowListAdmin, res) - - res = precompile.GetContractNativeMinterStatus(state, noRoleAddr) + res := precompile.GetContractNativeMinterStatus(state, noRoleAddr) assert.Equal(t, precompile.AllowListEnabled, res) }, }, "set allow role from non-admin fails": { - caller: allowAddr, + caller: enabledAddr, precompileAddr: precompile.ContractNativeMinterAddress, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) @@ -753,9 +753,16 @@ func TestContractNativeMinterRun(t *testing.T) { } // Set up the state so that each address has the expected permissions at the start. precompile.SetContractNativeMinterStatus(state, adminAddr, precompile.AllowListAdmin) - precompile.SetContractNativeMinterStatus(state, allowAddr, precompile.AllowListEnabled) + precompile.SetContractNativeMinterStatus(state, enabledAddr, precompile.AllowListEnabled) precompile.SetContractNativeMinterStatus(state, noRoleAddr, precompile.AllowListNoRole) + assert.Equal(t, precompile.AllowListAdmin, precompile.GetContractNativeMinterStatus(state, adminAddr)) + assert.Equal(t, precompile.AllowListEnabled, precompile.GetContractNativeMinterStatus(state, enabledAddr)) + assert.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}, test.caller, test.precompileAddr, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { if err == nil { @@ -788,6 +795,7 @@ func TestFeeConfigManagerRun(t *testing.T) { input func() []byte suppliedGas uint64 readOnly bool + config *precompile.FeeConfigManagerConfig expectedRes []byte expectedErr string @@ -796,7 +804,7 @@ func TestFeeConfigManagerRun(t *testing.T) { } adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") - allowAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") + enabledAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") for name, test := range map[string]test{ @@ -814,8 +822,8 @@ func TestFeeConfigManagerRun(t *testing.T) { readOnly: false, expectedErr: precompile.ErrCannotChangeFee.Error(), }, - "set config from allow address": { - caller: allowAddr, + "set config from enabled address": { + caller: enabledAddr, precompileAddr: precompile.FeeConfigManagerAddress, input: func() []byte { input, err := precompile.PackSetFeeConfig(testFeeConfig) @@ -828,15 +836,12 @@ func TestFeeConfigManagerRun(t *testing.T) { readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetFeeConfigManagerStatus(state, allowAddr) - assert.Equal(t, precompile.AllowListEnabled, res) - feeConfig := precompile.GetStoredFeeConfig(state) assert.Equal(t, testFeeConfig, feeConfig) }, }, - "set invalid config from allow address": { - caller: allowAddr, + "set invalid config from enabled address": { + caller: enabledAddr, precompileAddr: precompile.FeeConfigManagerAddress, input: func() []byte { feeConfig := testFeeConfig @@ -852,9 +857,6 @@ func TestFeeConfigManagerRun(t *testing.T) { expectedRes: []byte{}, expectedErr: "cannot be greater than maxBlockGasCost", assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetFeeConfigManagerStatus(state, allowAddr) - assert.Equal(t, precompile.AllowListEnabled, res) - feeConfig := precompile.GetStoredFeeConfig(state) assert.Equal(t, testFeeConfig, feeConfig) }, @@ -873,9 +875,6 @@ func TestFeeConfigManagerRun(t *testing.T) { readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetFeeConfigManagerStatus(state, adminAddr) - assert.Equal(t, precompile.AllowListAdmin, res) - feeConfig := precompile.GetStoredFeeConfig(state) assert.Equal(t, testFeeConfig, feeConfig) lastChangedAt := precompile.GetFeeConfigLastChangedAt(state) @@ -908,6 +907,29 @@ func TestFeeConfigManagerRun(t *testing.T) { assert.EqualValues(t, big.NewInt(6), lastChangedAt) }, }, + "get initial fee config": { + caller: noRoleAddr, + precompileAddr: precompile.FeeConfigManagerAddress, + input: func() []byte { + return precompile.PackGetFeeConfigInput() + }, + suppliedGas: precompile.GetFeeConfigGasCost, + config: &precompile.FeeConfigManagerConfig{ + InitialFeeConfig: &testFeeConfig, + }, + readOnly: true, + expectedRes: func() []byte { + res, err := precompile.PackFeeConfig(testFeeConfig) + assert.NoError(t, err) + return res + }(), + assertState: func(t *testing.T, state *state.StateDB) { + feeConfig := precompile.GetStoredFeeConfig(state) + lastChangedAt := precompile.GetFeeConfigLastChangedAt(state) + assert.Equal(t, testFeeConfig, feeConfig) + assert.EqualValues(t, testBlockNumber, lastChangedAt) + }, + }, "get last changed at from non-enabled address": { caller: noRoleAddr, precompileAddr: precompile.FeeConfigManagerAddress, @@ -945,7 +967,7 @@ func TestFeeConfigManagerRun(t *testing.T) { expectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly setFeeConfig with allow role fails": { - caller: allowAddr, + caller: enabledAddr, precompileAddr: precompile.FeeConfigManagerAddress, input: func() []byte { input, err := precompile.PackSetFeeConfig(testFeeConfig) @@ -1000,15 +1022,12 @@ func TestFeeConfigManagerRun(t *testing.T) { readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetFeeConfigManagerStatus(state, adminAddr) - assert.Equal(t, precompile.AllowListAdmin, res) - - res = precompile.GetFeeConfigManagerStatus(state, noRoleAddr) + res := precompile.GetFeeConfigManagerStatus(state, noRoleAddr) assert.Equal(t, precompile.AllowListEnabled, res) }, }, "set allow role from non-admin fails": { - caller: allowAddr, + caller: enabledAddr, precompileAddr: precompile.FeeConfigManagerAddress, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) @@ -1030,7 +1049,7 @@ func TestFeeConfigManagerRun(t *testing.T) { } // Set up the state so that each address has the expected permissions at the start. precompile.SetFeeConfigManagerStatus(state, adminAddr, precompile.AllowListAdmin) - precompile.SetFeeConfigManagerStatus(state, allowAddr, precompile.AllowListEnabled) + precompile.SetFeeConfigManagerStatus(state, enabledAddr, precompile.AllowListEnabled) precompile.SetFeeConfigManagerStatus(state, noRoleAddr, precompile.AllowListNoRole) if test.preCondition != nil { @@ -1038,6 +1057,9 @@ func TestFeeConfigManagerRun(t *testing.T) { } 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}, test.caller, test.precompileAddr, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { if err == nil { diff --git a/core/test_blockchain.go b/core/test_blockchain.go index 59ee062ce6..50ef515d62 100644 --- a/core/test_blockchain.go +++ b/core/test_blockchain.go @@ -1546,8 +1546,8 @@ func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, chainC genesisBalance := new(big.Int).Mul(big.NewInt(1000000), big.NewInt(params.Ether)) config := *params.TestChainConfig // Set all of the required config parameters - config.ContractDeployerAllowListConfig = precompile.NewContractDeployerAllowListConfig(big.NewInt(0), []common.Address{addr1}) - config.FeeManagerConfig = precompile.NewFeeManagerConfig(big.NewInt(0), []common.Address{addr1}) + config.ContractDeployerAllowListConfig = precompile.NewContractDeployerAllowListConfig(big.NewInt(0), []common.Address{addr1}, nil) + config.FeeManagerConfig = precompile.NewFeeManagerConfig(big.NewInt(0), []common.Address{addr1}, nil, nil) gspec := &Genesis{ Config: &config, Alloc: GenesisAlloc{addr1: {Balance: genesisBalance}}, diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index 32ab8f092e..074f6769ca 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -483,7 +483,7 @@ func TestSuggestGasPriceAfterFeeConfigUpdate(t *testing.T) { // create a chain config with fee manager enabled at genesis with [addr] as the admin chainConfig := *params.TestChainConfig - chainConfig.FeeManagerConfig = precompile.NewFeeManagerConfig(big.NewInt(0), []common.Address{addr}) + chainConfig.FeeManagerConfig = precompile.NewFeeManagerConfig(big.NewInt(0), []common.Address{addr}, nil, nil) // create a fee config with higher MinBaseFee and prepare it for inclusion in a tx signer := types.LatestSigner(params.TestChainConfig) diff --git a/params/config.go b/params/config.go index ea798ed6c1..302227dadb 100644 --- a/params/config.go +++ b/params/config.go @@ -287,7 +287,7 @@ func (c *ChainConfig) Verify() error { } // Verify the precompile upgrades are internally consistent given the existing chainConfig. - if err := c.VerifyPrecompileUpgrades(); err != nil { + if err := c.verifyPrecompileUpgrades(); err != nil { return err } diff --git a/params/precompile_config.go b/params/precompile_config.go index 699ff594f1..933b88a693 100644 --- a/params/precompile_config.go +++ b/params/precompile_config.go @@ -60,13 +60,13 @@ func (p *PrecompileUpgrade) getByKey(key precompileKey) (precompile.StatefulPrec } } -// VerifyPrecompileUpgrades checks [c.PrecompileUpgrades] is well formed: +// 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 { +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 @@ -103,6 +103,9 @@ func (c *ChainConfig) VerifyPrecompileUpgrades() error { ) // check the genesis chain config for any enabled upgrade if config, ok := c.PrecompileUpgrade.getByKey(key); ok { + if err := config.Verify(); err != nil { + return err + } disabled = false lastUpgraded = config.Timestamp() } else { @@ -123,6 +126,10 @@ func (c *ChainConfig) VerifyPrecompileUpgrades() error { 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() } diff --git a/params/precompile_config_test.go b/params/precompile_config_test.go index 6c0fb521fc..55b138b65a 100644 --- a/params/precompile_config_test.go +++ b/params/precompile_config_test.go @@ -7,21 +7,19 @@ import ( "math/big" "testing" + "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/precompile" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestValidateWithChainConfig(t *testing.T) { +func TestVerifyWithChainConfig(t *testing.T) { admins := []common.Address{{1}} - config := &ChainConfig{ - PrecompileUpgrade: PrecompileUpgrade{ - TxAllowListConfig: &precompile.TxAllowListConfig{ - UpgradeableConfig: precompile.UpgradeableConfig{ - BlockTimestamp: big.NewInt(2), - }, - }, - }, + baseConfig := *SubnetEVMDefaultChainConfig + config := &baseConfig + config.PrecompileUpgrade = PrecompileUpgrade{ + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(2), nil, nil), } config.PrecompileUpgrades = []PrecompileUpgrade{ { @@ -30,12 +28,12 @@ func TestValidateWithChainConfig(t *testing.T) { }, { // re-enable TxAllowList at timestamp 5 - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(5), admins), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(5), admins, nil), }, } // check this config is valid - err := config.VerifyPrecompileUpgrades() + err := config.Verify() assert.NoError(t, err) // same precompile cannot be configured twice for the same timestamp @@ -46,7 +44,7 @@ func TestValidateWithChainConfig(t *testing.T) { TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(5)), }, ) - err = badConfig.VerifyPrecompileUpgrades() + err = badConfig.Verify() assert.ErrorContains(t, err, "config timestamp (5) <= previous timestamp (5)") // cannot enable a precompile without disabling it first. @@ -54,44 +52,146 @@ func TestValidateWithChainConfig(t *testing.T) { badConfig.PrecompileUpgrades = append( badConfig.PrecompileUpgrades, PrecompileUpgrade{ - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(5), admins), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(5), admins, nil), }, ) - err = badConfig.VerifyPrecompileUpgrades() + err = badConfig.Verify() assert.ErrorContains(t, err, "disable should be [true]") } -func TestValidate(t *testing.T) { +func TestVerifyPrecompileUpgrades(t *testing.T) { admins := []common.Address{{1}} - config := &ChainConfig{} - config.PrecompileUpgrades = []PrecompileUpgrade{ + tests := []struct { + name string + upgrades []PrecompileUpgrade + expectedError string + }{ { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(1), admins), + name: "enable and disable tx allow list", + upgrades: []PrecompileUpgrade{ + { + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(1), admins, nil), + }, + { + TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(2)), + }, + }, + expectedError: "", }, { - TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(2)), + name: "invalid allow list config in tx allowlist", + upgrades: []PrecompileUpgrade{ + { + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(1), admins, nil), + }, + { + TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(2)), + }, + { + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(3), admins, admins), + }, + }, + expectedError: "cannot set address", + }, + { + name: "invalid initial fee manager config", + upgrades: []PrecompileUpgrade{ + { + FeeManagerConfig: precompile.NewFeeManagerConfig(big.NewInt(3), admins, nil, + &commontype.FeeConfig{ + GasLimit: big.NewInt(-1), + }), + }, + }, + expectedError: "gasLimit = -1 cannot be less than or equal to 0", + }, + { + name: "invalid initial fee manager config gas limit 0", + upgrades: []PrecompileUpgrade{ + { + FeeManagerConfig: precompile.NewFeeManagerConfig(big.NewInt(3), admins, nil, + &commontype.FeeConfig{ + GasLimit: big.NewInt(0), + }), + }, + }, + expectedError: "gasLimit = 0 cannot be less than or equal to 0", }, } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + baseConfig := *SubnetEVMDefaultChainConfig + config := &baseConfig + config.PrecompileUpgrades = tt.upgrades + + err := config.Verify() + if tt.expectedError == "" { + require.NoError(err) + } else { + require.ErrorContains(err, tt.expectedError) + } + }) + } +} - // check this config is valid - err := config.VerifyPrecompileUpgrades() - assert.NoError(t, err) +func TestVerifyPrecompiles(t *testing.T) { + admins := []common.Address{{1}} + tests := []struct { + name string + upgrade PrecompileUpgrade + expectedError string + }{ + { + name: "invalid allow list config in tx allowlist", + upgrade: PrecompileUpgrade{ + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(3), admins, admins), + }, + expectedError: "cannot set address", + }, + { + name: "invalid initial fee manager config", + upgrade: PrecompileUpgrade{ + FeeManagerConfig: precompile.NewFeeManagerConfig(big.NewInt(3), admins, nil, + &commontype.FeeConfig{ + GasLimit: big.NewInt(-1), + }), + }, + expectedError: "gasLimit = -1 cannot be less than or equal to 0", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + baseConfig := *SubnetEVMDefaultChainConfig + config := &baseConfig + config.PrecompileUpgrade = tt.upgrade + + err := config.Verify() + if tt.expectedError == "" { + require.NoError(err) + } else { + require.ErrorContains(err, tt.expectedError) + } + }) + } } -func TestValidateRequiresSortedTimestamps(t *testing.T) { +func TestVerifyRequiresSortedTimestamps(t *testing.T) { admins := []common.Address{{1}} - config := &ChainConfig{} + baseConfig := *SubnetEVMDefaultChainConfig + config := &baseConfig config.PrecompileUpgrades = []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(2), admins), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(2), admins, nil), }, { - ContractDeployerAllowListConfig: precompile.NewContractDeployerAllowListConfig(big.NewInt(1), admins), + ContractDeployerAllowListConfig: precompile.NewContractDeployerAllowListConfig(big.NewInt(1), admins, nil), }, } // block timestamps must be monotonically increasing, so this config is invalid - err := config.VerifyPrecompileUpgrades() + err := config.Verify() assert.ErrorContains(t, err, "config timestamp (1) < previous timestamp (2)") } @@ -100,7 +200,7 @@ func TestGetPrecompileConfig(t *testing.T) { baseConfig := *SubnetEVMDefaultChainConfig config := &baseConfig config.PrecompileUpgrade = PrecompileUpgrade{ - ContractDeployerAllowListConfig: precompile.NewContractDeployerAllowListConfig(big.NewInt(10), []common.Address{}), + ContractDeployerAllowListConfig: precompile.NewContractDeployerAllowListConfig(big.NewInt(10), nil, nil), } deployerConfig := config.GetContractDeployerAllowListConfig(big.NewInt(0)) diff --git a/params/upgrade_config_test.go b/params/upgrade_config_test.go index f5db3bd296..38c93f5386 100644 --- a/params/upgrade_config_test.go +++ b/params/upgrade_config_test.go @@ -15,7 +15,7 @@ import ( func TestVerifyUpgradeConfig(t *testing.T) { admins := []common.Address{{1}} chainConfig := *TestChainConfig - chainConfig.TxAllowListConfig = precompile.NewTxAllowListConfig(big.NewInt(1), admins) + chainConfig.TxAllowListConfig = precompile.NewTxAllowListConfig(big.NewInt(1), admins, nil) type test struct { upgrades []PrecompileUpgrade @@ -27,7 +27,7 @@ func TestVerifyUpgradeConfig(t *testing.T) { expectedErrorString: "disable should be [true]", upgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(2), admins), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(2), admins, nil), }, }, }, @@ -70,8 +70,8 @@ func TestVerifyUpgradeConfig(t *testing.T) { func TestCheckCompatibleUpgradeConfigs(t *testing.T) { admins := []common.Address{{1}} chainConfig := *TestChainConfig - chainConfig.TxAllowListConfig = precompile.NewTxAllowListConfig(big.NewInt(1), admins) - chainConfig.ContractDeployerAllowListConfig = precompile.NewContractDeployerAllowListConfig(big.NewInt(10), admins) + chainConfig.TxAllowListConfig = precompile.NewTxAllowListConfig(big.NewInt(1), admins, nil) + chainConfig.ContractDeployerAllowListConfig = precompile.NewContractDeployerAllowListConfig(big.NewInt(10), admins, nil) type test struct { configs []*UpgradeConfig @@ -89,7 +89,7 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil), }, }, }, @@ -104,7 +104,7 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil), }, }, }, @@ -114,7 +114,7 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(8), admins), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(8), admins, nil), }, }, }, @@ -130,7 +130,7 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil), }, }, }, @@ -140,7 +140,7 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(8), admins), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(8), admins, nil), }, }, }, @@ -155,7 +155,7 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil), }, }, }, @@ -178,7 +178,7 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil), }, }, }, @@ -201,7 +201,7 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil), }, }, }, @@ -212,7 +212,7 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { }, { // uses a different (empty) admin list, not allowed - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), []common.Address{}), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), []common.Address{}, nil), }, }, }, @@ -227,7 +227,7 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil), }, }, }, @@ -237,7 +237,7 @@ func TestCheckCompatibleUpgradeConfigs(t *testing.T) { TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(6)), }, { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(7), admins, nil), }, }, }, diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 428ec25f65..5fa6bc4d30 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -2109,7 +2109,7 @@ func TestBuildAllowListActivationBlock(t *testing.T) { if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { t.Fatal(err) } - genesis.Config.ContractDeployerAllowListConfig = precompile.NewContractDeployerAllowListConfig(big.NewInt(time.Now().Unix()), testEthAddrs) + genesis.Config.ContractDeployerAllowListConfig = precompile.NewContractDeployerAllowListConfig(big.NewInt(time.Now().Unix()), testEthAddrs, nil) genesisJSON, err := genesis.MarshalJSON() if err != nil { @@ -2173,7 +2173,7 @@ func TestTxAllowListSuccessfulTx(t *testing.T) { if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { t.Fatal(err) } - genesis.Config.TxAllowListConfig = precompile.NewTxAllowListConfig(big.NewInt(0), testEthAddrs[0:1]) + genesis.Config.TxAllowListConfig = precompile.NewTxAllowListConfig(big.NewInt(0), testEthAddrs[0:1], nil) genesisJSON, err := genesis.MarshalJSON() if err != nil { t.Fatal(err) @@ -2249,7 +2249,7 @@ func TestTxAllowListDisablePrecompile(t *testing.T) { t.Fatal(err) } enableAllowListTimestamp := time.Unix(0, 0) // enable at genesis - genesis.Config.TxAllowListConfig = precompile.NewTxAllowListConfig(big.NewInt(enableAllowListTimestamp.Unix()), testEthAddrs[0:1]) + genesis.Config.TxAllowListConfig = precompile.NewTxAllowListConfig(big.NewInt(enableAllowListTimestamp.Unix()), testEthAddrs[0:1], nil) genesisJSON, err := genesis.MarshalJSON() if err != nil { t.Fatal(err) @@ -2356,7 +2356,7 @@ func TestFeeManagerChangeFee(t *testing.T) { if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { t.Fatal(err) } - genesis.Config.FeeManagerConfig = precompile.NewFeeManagerConfig(big.NewInt(0), testEthAddrs[0:1]) + genesis.Config.FeeManagerConfig = precompile.NewFeeManagerConfig(big.NewInt(0), testEthAddrs[0:1], nil, nil) // set a lower fee config now testLowFeeConfig := commontype.FeeConfig{ @@ -2484,7 +2484,7 @@ func TestAllowFeeRecipientDisabled(t *testing.T) { } issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), "", "") - vm.miner.SetEtherbase(common.BigToAddress(common.Big1)) // set non-blackhole address by force + vm.miner.SetEtherbase(common.HexToAddress("0x0123456789")) // set non-blackhole address by force defer func() { if err := vm.Shutdown(); err != nil { t.Fatal(err) @@ -2533,7 +2533,7 @@ func TestAllowFeeRecipientEnabled(t *testing.T) { t.Fatal(err) } - etherBase := common.BigToAddress(common.Big1) + etherBase := common.HexToAddress("0x0123456789") c := Config{} c.SetDefaults() c.FeeRecipient = etherBase.String() diff --git a/plugin/evm/vm_upgrade_bytes_test.go b/plugin/evm/vm_upgrade_bytes_test.go index 1b3d4673d7..a14bdeceb8 100644 --- a/plugin/evm/vm_upgrade_bytes_test.go +++ b/plugin/evm/vm_upgrade_bytes_test.go @@ -27,7 +27,7 @@ func TestVMUpgradeBytesPrecompile(t *testing.T) { upgradeConfig := ¶ms.UpgradeConfig{ PrecompileUpgrades: []params.PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(enableAllowListTimestamp.Unix()), testEthAddrs[0:1]), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(enableAllowListTimestamp.Unix()), testEthAddrs[0:1], nil), }, }, } diff --git a/precompile/allow_list.go b/precompile/allow_list.go index 66eab623bf..78e3d4b6c2 100644 --- a/precompile/allow_list.go +++ b/precompile/allow_list.go @@ -12,9 +12,6 @@ import ( "github.com/ethereum/go-ethereum/common" ) -// Enum constants for valid AllowListRole -type AllowListRole common.Hash - const ( SetAdminFuncKey = "setAdmin" SetEnabledFuncKey = "setEnabled" @@ -43,12 +40,16 @@ var ( // AllowListConfig specifies the initial set of allow list admins. type AllowListConfig struct { - AllowListAdmins []common.Address `json:"adminAddresses"` + 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) } @@ -59,55 +60,45 @@ func (c *AllowListConfig) Equal(other *AllowListConfig) bool { if other == nil { return false } - if len(c.AllowListAdmins) != len(other.AllowListAdmins) { + 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, admin := range c.AllowListAdmins { - if admin != other.AllowListAdmins[i] { + for i, address := range current { + if address != other[i] { return false } } return true } -// Valid returns true iff [s] represents a valid role. -func (s AllowListRole) Valid() bool { - switch s { - case AllowListNoRole, AllowListEnabled, AllowListAdmin: - return true - default: - return false +// 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 } -} - -// IsNoRole returns true if [s] indicates no specific role. -func (s AllowListRole) IsNoRole() bool { - switch s { - case AllowListNoRole: - return true - default: - return false + enabledMap := make(map[common.Address]struct{}) + for _, enabledAddr := range c.EnabledAddresses { + if _, ok := enabledMap[enabledAddr]; !ok { + enabledMap[enabledAddr] = struct{}{} + } } -} - -// IsAdmin returns true if [s] indicates the permission to modify the allow list. -func (s AllowListRole) IsAdmin() bool { - switch s { - case AllowListAdmin: - return true - default: - return false + for _, adminAddr := range c.AllowListAdmins { + if _, ok := enabledMap[adminAddr]; ok { + return fmt.Errorf("cannot set address %s as both admin and enabled", adminAddr) + } } -} -// IsEnabled returns true if [s] indicates that it has permission to access the resource. -func (s AllowListRole) IsEnabled() bool { - switch s { - case AllowListAdmin, AllowListEnabled: - return true - default: - return false - } + return nil } // getAllowListStatus returns the allow list role of [address] for the precompile @@ -125,6 +116,10 @@ func setAllowListRole(stateDB StateDB, precompileAddr, address common.Address, r // 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)) } diff --git a/precompile/allow_list_role.go b/precompile/allow_list_role.go new file mode 100644 index 0000000000..0c815d0819 --- /dev/null +++ b/precompile/allow_list_role.go @@ -0,0 +1,49 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package precompile + +import "github.com/ethereum/go-ethereum/common" + +// Enum constants for valid AllowListRole +type AllowListRole common.Hash + +// Valid returns true iff [s] represents a valid role. +func (s AllowListRole) Valid() bool { + switch s { + case AllowListNoRole, AllowListEnabled, AllowListAdmin: + return true + default: + return false + } +} + +// IsNoRole returns true if [s] indicates no specific role. +func (s AllowListRole) IsNoRole() bool { + switch s { + case AllowListNoRole: + return true + default: + return false + } +} + +// IsAdmin returns true if [s] indicates the permission to modify the allow list. +func (s AllowListRole) IsAdmin() bool { + switch s { + case AllowListAdmin: + return true + default: + return false + } +} + +// IsEnabled returns true if [s] indicates that it has permission to access the resource. +func (s AllowListRole) IsEnabled() bool { + switch s { + case AllowListAdmin, AllowListEnabled: + return true + default: + return false + } +} diff --git a/precompile/config_test.go b/precompile/config_test.go new file mode 100644 index 0000000000..56ebf979fb --- /dev/null +++ b/precompile/config_test.go @@ -0,0 +1,367 @@ +// (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: "invalid allow list config in fee manager allowlist", + config: NewFeeManagerConfig(big.NewInt(3), admins, admins, nil), + expectedError: "cannot set address", + }, + { + name: "invalid initial fee manager config", + config: NewFeeManagerConfig(big.NewInt(3), admins, nil, + &commontype.FeeConfig{ + GasLimit: big.NewInt(0), + }), + expectedError: "gasLimit = 0 cannot be less than or equal to 0", + }, + { + name: "nil amount in native minter config", + config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + map[common.Address]*math.HexOrDecimal256{ + common.HexToAddress("0x01"): math.NewHexOrDecimal256(123), + common.HexToAddress("0x02"): nil, + }), + expectedError: "initial mint cannot contain nil", + }, + { + name: "negative amount in native minter config", + config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, + map[common.Address]*math.HexOrDecimal256{ + common.HexToAddress("0x01"): math.NewHexOrDecimal256(123), + common.HexToAddress("0x02"): math.NewHexOrDecimal256(-1), + }), + expectedError: "initial mint cannot contain invalid amount", + }, + } + 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 version", + 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 version", + 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 version", + 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 version", + config: NewFeeManagerConfig(big.NewInt(3), admins, nil, nil), + other: NewFeeManagerConfig(big.NewInt(4), admins, nil, nil), + expected: false, + }, + { + name: "different enabled", + config: NewFeeManagerConfig(big.NewInt(3), admins, nil, nil), + other: NewFeeManagerConfig(big.NewInt(3), admins, enableds, nil), + expected: false, + }, + { + name: "non-nil initial config and nil initial config", + config: NewFeeManagerConfig(big.NewInt(3), admins, nil, &validFeeConfig), + other: NewFeeManagerConfig(big.NewInt(3), admins, nil, nil), + expected: false, + }, + { + name: "different initial config", + config: NewFeeManagerConfig(big.NewInt(3), admins, nil, &validFeeConfig), + other: NewFeeManagerConfig(big.NewInt(3), admins, nil, + func() *commontype.FeeConfig { + c := validFeeConfig + c.GasLimit = big.NewInt(123) + return &c + }()), + expected: false, + }, + { + name: "same config", + config: NewFeeManagerConfig(big.NewInt(3), admins, nil, &validFeeConfig), + other: NewFeeManagerConfig(big.NewInt(3), admins, nil, &validFeeConfig), + expected: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + require.Equal(tt.expected, tt.config.Equal(tt.other)) + }) + } +} diff --git a/precompile/contract_deployer_allow_list.go b/precompile/contract_deployer_allow_list.go index 97e1acd074..aa01d9b8cd 100644 --- a/precompile/contract_deployer_allow_list.go +++ b/precompile/contract_deployer_allow_list.go @@ -23,10 +23,13 @@ type ContractDeployerAllowListConfig struct { } // NewContractDeployerAllowListConfig returns a config for a network upgrade at [blockTimestamp] that enables -// ContractDeployerAllowList with the given [admins] as members of the allowlist. -func NewContractDeployerAllowListConfig(blockTimestamp *big.Int, admins []common.Address) *ContractDeployerAllowListConfig { +// 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}, + AllowListConfig: AllowListConfig{ + AllowListAdmins: admins, + EnabledAddresses: enableds, + }, UpgradeableConfig: UpgradeableConfig{BlockTimestamp: blockTimestamp}, } } diff --git a/precompile/contract_native_minter.go b/precompile/contract_native_minter.go index d26458b6f7..45301362c2 100644 --- a/precompile/contract_native_minter.go +++ b/precompile/contract_native_minter.go @@ -8,8 +8,10 @@ import ( "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 ( @@ -35,14 +37,19 @@ var ( 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] as members of the allowlist. -func NewContractNativeMinterConfig(blockTimestamp *big.Int, admins []common.Address) *ContractNativeMinterConfig { +// 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}, + AllowListConfig: AllowListConfig{ + AllowListAdmins: admins, + EnabledAddresses: enableds, + }, UpgradeableConfig: UpgradeableConfig{BlockTimestamp: blockTimestamp}, + InitialMint: initialMint, } } @@ -64,6 +71,13 @@ func (c *ContractNativeMinterConfig) Address() common.Address { // 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) } @@ -72,6 +86,23 @@ 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 @@ -79,7 +110,28 @@ func (c *ContractNativeMinterConfig) Equal(s StatefulPrecompileConfig) bool { if !ok { return false } - return c.UpgradeableConfig.Equal(&other.UpgradeableConfig) && c.AllowListConfig.Equal(&other.AllowListConfig) + 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 } // GetContractNativeMinterStatus returns the role of [address] for the minter list. diff --git a/precompile/fee_config_manager.go b/precompile/fee_config_manager.go index ae28e5b38b..b7bd0d44b2 100644 --- a/precompile/fee_config_manager.go +++ b/precompile/fee_config_manager.go @@ -56,14 +56,19 @@ var ( type FeeConfigManagerConfig struct { AllowListConfig // Config for the fee config manager allow list UpgradeableConfig + InitialFeeConfig *commontype.FeeConfig `json:"initialFeeConfig,omitempty"` // initial fee config to be immediately activated } // NewFeeManagerConfig returns a config for a network upgrade at [blockTimestamp] that enables -// FeeConfigManager with the given [admins] as members of the allowlist. -func NewFeeManagerConfig(blockTimestamp *big.Int, admins []common.Address) *FeeConfigManagerConfig { +// 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}, + AllowListConfig: AllowListConfig{ + AllowListAdmins: admins, + EnabledAddresses: enableds, + }, UpgradeableConfig: UpgradeableConfig{BlockTimestamp: blockTimestamp}, + InitialFeeConfig: initialConfig, } } @@ -90,14 +95,31 @@ func (c *FeeConfigManagerConfig) Equal(s StatefulPrecompileConfig) bool { if !ok { return false } - return c.UpgradeableConfig.Equal(&other.UpgradeableConfig) && c.AllowListConfig.Equal(&other.AllowListConfig) + 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 err := StoreFeeConfig(state, chainConfig.GetFeeConfig(), blockContext); err != nil { - panic(fmt.Sprintf("fee config should have been verified in genesis: %s", err)) + 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) } @@ -107,6 +129,17 @@ 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() +} + // 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) diff --git a/precompile/stateful_precompile_config.go b/precompile/stateful_precompile_config.go index 70ec2d0a56..4e4490cae7 100644 --- a/precompile/stateful_precompile_config.go +++ b/precompile/stateful_precompile_config.go @@ -35,6 +35,8 @@ type StatefulPrecompileConfig interface { // Contract returns a thread-safe singleton that can be used as the StatefulPrecompiledContract when // this config is enabled. Contract() StatefulPrecompiledContract + // Verify is called on startup and an error is treated as fatal. Configure can assume the Config has passed verification. + Verify() error } // Configure sets the nonce and code to non-empty values then calls Configure on [precompileConfig] to make the necessary diff --git a/precompile/tx_allow_list.go b/precompile/tx_allow_list.go index 668d4eac41..f969ac2546 100644 --- a/precompile/tx_allow_list.go +++ b/precompile/tx_allow_list.go @@ -26,10 +26,13 @@ type TxAllowListConfig struct { } // NewTxAllowListConfig returns a config for a network upgrade at [blockTimestamp] that enables -// TxAllowList with the given [admins] as members of the allowlist. -func NewTxAllowListConfig(blockTimestamp *big.Int, admins []common.Address) *TxAllowListConfig { +// 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}, + AllowListConfig: AllowListConfig{ + AllowListAdmins: admins, + EnabledAddresses: enableds, + }, UpgradeableConfig: UpgradeableConfig{BlockTimestamp: blockTimestamp}, } }