From 5359f70d11957ca19ebbc76fca8d27cd29a560e0 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 24 Aug 2022 17:30:25 +0300 Subject: [PATCH 01/64] add initial precompile configs and handle them --- precompile/allow_list.go | 11 ++++++++++- precompile/contract_native_minter.go | 7 +++++++ precompile/fee_config_manager.go | 6 ++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/precompile/allow_list.go b/precompile/allow_list.go index 66eab623bf..ce70595bd6 100644 --- a/precompile/allow_list.go +++ b/precompile/allow_list.go @@ -43,12 +43,17 @@ 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,omitempty"` // initial enabled addresses } // Configure initializes the address space of [precompileAddr] by initializing the role of each of // the addresses in [AllowListAdmins]. func (c *AllowListConfig) Configure(state StateDB, precompileAddr common.Address) { + // First set enabled roles so these can be upgraded to admin addresses below. + for _, enabledAddr := range c.EnabledAddresses { + setAllowListRole(state, precompileAddr, enabledAddr, AllowListEnabled) + } for _, adminAddr := range c.AllowListAdmins { setAllowListRole(state, precompileAddr, adminAddr, AllowListAdmin) } @@ -125,6 +130,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/contract_native_minter.go b/precompile/contract_native_minter.go index d26458b6f7..2fdda08481 100644 --- a/precompile/contract_native_minter.go +++ b/precompile/contract_native_minter.go @@ -35,6 +35,8 @@ var ( type ContractNativeMinterConfig struct { AllowListConfig UpgradeableConfig + // TODO: use hex amounts? + InitialMint map[common.Address]*big.Int `json:"initialMint,omitempty"` // initial mint config to be immediately minted } // NewContractNativeMinterConfig returns a config for a network upgrade at [blockTimestamp] that enables @@ -64,6 +66,11 @@ func (c *ContractNativeMinterConfig) Address() common.Address { // Configure configures [state] with the desired admins based on [c]. func (c *ContractNativeMinterConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) { + if len(c.InitialMint) != 0 { + for to, amount := range c.InitialMint { + state.AddBalance(to, amount) + } + } c.AllowListConfig.Configure(state, ContractNativeMinterAddress) } diff --git a/precompile/fee_config_manager.go b/precompile/fee_config_manager.go index ae28e5b38b..e1f08aeaac 100644 --- a/precompile/fee_config_manager.go +++ b/precompile/fee_config_manager.go @@ -56,6 +56,7 @@ 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 @@ -96,6 +97,11 @@ func (c *FeeConfigManagerConfig) Equal(s StatefulPrecompileConfig) bool { // Configure configures [state] with the desired admins based on [c]. func (c *FeeConfigManagerConfig) Configure(chainConfig ChainConfig, state StateDB, blockContext BlockContext) { // Store the initial fee config into the state when the fee config manager activates. + if c.InitialFeeConfig != nil { + if err := StoreFeeConfig(state, chainConfig.GetFeeConfig(), blockContext); err != nil { + panic(fmt.Sprintf("invalid feeConfig provided: %s", err)) + } + } if err := StoreFeeConfig(state, chainConfig.GetFeeConfig(), blockContext); err != nil { panic(fmt.Sprintf("fee config should have been verified in genesis: %s", err)) } From 8a488c5fcb7a71af56fa40d205d76ca27b531b63 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 25 Aug 2022 21:57:15 +0300 Subject: [PATCH 02/64] add verify to precompile configs --- params/config.go | 2 +- params/precompile_config.go | 9 ++++++++- precompile/allow_list.go | 22 +++++++++++++++++++--- precompile/contract_native_minter.go | 9 ++++++--- precompile/fee_config_manager.go | 16 ++++++++++++---- precompile/stateful_precompile_config.go | 3 +++ 6 files changed, 49 insertions(+), 12 deletions(-) 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 79561d3e52..986e04b7a6 100644 --- a/params/precompile_config.go +++ b/params/precompile_config.go @@ -66,7 +66,7 @@ func (p *PrecompileUpgrade) getByKey(key precompileKey) (precompile.StatefulPrec // - 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/precompile/allow_list.go b/precompile/allow_list.go index ce70595bd6..db16922356 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" @@ -75,6 +72,25 @@ func (c *AllowListConfig) Equal(other *AllowListConfig) bool { return true } +// Verify returns an error if there is an overlapping address between admins and enableds +func (c *AllowListConfig) Verify() error { + enabledMap := make(map[common.Address]struct{}) + for _, enabledAddr := range c.EnabledAddresses { + if _, ok := enabledMap[enabledAddr]; !ok { + enabledMap[enabledAddr] = struct{}{} + } + } + for _, adminAddr := range c.AllowListAdmins { + if _, ok := enabledMap[adminAddr]; ok { + return fmt.Errorf("cannot set address %s as both admin and enabled", adminAddr) + } + } + return nil +} + +// 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 { diff --git a/precompile/contract_native_minter.go b/precompile/contract_native_minter.go index 2fdda08481..d0e29ae4fe 100644 --- a/precompile/contract_native_minter.go +++ b/precompile/contract_native_minter.go @@ -10,6 +10,7 @@ import ( "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" ) const ( @@ -35,8 +36,7 @@ var ( type ContractNativeMinterConfig struct { AllowListConfig UpgradeableConfig - // TODO: use hex amounts? - InitialMint map[common.Address]*big.Int `json:"initialMint,omitempty"` // initial mint config to be immediately minted + 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 @@ -68,7 +68,10 @@ func (c *ContractNativeMinterConfig) Address() common.Address { func (c *ContractNativeMinterConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) { if len(c.InitialMint) != 0 { for to, amount := range c.InitialMint { - state.AddBalance(to, amount) + if amount != nil { + bigIntAmount := (*big.Int)(amount) + state.AddBalance(to, bigIntAmount) + } } } c.AllowListConfig.Configure(state, ContractNativeMinterAddress) diff --git a/precompile/fee_config_manager.go b/precompile/fee_config_manager.go index e1f08aeaac..c33079601b 100644 --- a/precompile/fee_config_manager.go +++ b/precompile/fee_config_manager.go @@ -98,12 +98,13 @@ func (c *FeeConfigManagerConfig) Equal(s StatefulPrecompileConfig) bool { func (c *FeeConfigManagerConfig) Configure(chainConfig ChainConfig, state StateDB, blockContext BlockContext) { // Store the initial fee config into the state when the fee config manager activates. if c.InitialFeeConfig != nil { - if err := StoreFeeConfig(state, chainConfig.GetFeeConfig(), blockContext); err != nil { + if err := StoreFeeConfig(state, *c.InitialFeeConfig, blockContext); err != nil { panic(fmt.Sprintf("invalid feeConfig provided: %s", err)) } - } - if err := StoreFeeConfig(state, chainConfig.GetFeeConfig(), blockContext); err != nil { - panic(fmt.Sprintf("fee config should have been verified in genesis: %s", err)) + } else { + if err := StoreFeeConfig(state, chainConfig.GetFeeConfig(), blockContext); err != nil { + panic(fmt.Sprintf("fee config should have been verified in genesis: %s", err)) + } } c.AllowListConfig.Configure(state, FeeConfigManagerAddress) } @@ -113,6 +114,13 @@ func (c *FeeConfigManagerConfig) Contract() StatefulPrecompiledContract { return FeeConfigManagerPrecompile } +func (c *FeeConfigManagerConfig) Verify() error { + if err := c.AllowListConfig.Verify(); err != nil { + return err + } + 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..e82493fb60 100644 --- a/precompile/stateful_precompile_config.go +++ b/precompile/stateful_precompile_config.go @@ -35,6 +35,9 @@ 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 before configuring stateful precompile config. It is important to capture any invalid configuration + // before activation. + Verify() error } // Configure sets the nonce and code to non-empty values then calls Configure on [precompileConfig] to make the necessary From 538f5987926fcbc293e5cabe61cfbc6b6393c364 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 25 Aug 2022 21:57:36 +0300 Subject: [PATCH 03/64] add unit tests for initial configs and verify --- accounts/abi/bind/precompile_template.go | 14 +++ core/stateful_precompile_test.go | 53 +++++++++ params/precompile_config_test.go | 142 ++++++++++++++++++++--- 3 files changed, 190 insertions(+), 19 deletions(-) diff --git a/accounts/abi/bind/precompile_template.go b/accounts/abi/bind/precompile_template.go index 261942fcfc..b7203f7bc8 100644 --- a/accounts/abi/bind/precompile_template.go +++ b/accounts/abi/bind/precompile_template.go @@ -192,6 +192,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 { diff --git a/core/stateful_precompile_test.go b/core/stateful_precompile_test.go index d53017f647..8887a33fc9 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" @@ -535,6 +536,7 @@ func TestContractNativeMinterRun(t *testing.T) { input func() []byte suppliedGas uint64 readOnly bool + config *precompile.ContractNativeMinterConfig expectedRes []byte expectedErr string @@ -581,6 +583,27 @@ func TestContractNativeMinterRun(t *testing.T) { assert.Equal(t, common.Big1, state.GetBalance(allowAddr), "expected minted funds") }, }, + "initial mint funds": { + caller: allowAddr, + precompileAddr: precompile.ContractNativeMinterAddress, + config: &precompile.ContractNativeMinterConfig{ + InitialMint: map[common.Address]*math.HexOrDecimal256{ + allowAddr: 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) { + res := precompile.GetContractNativeMinterStatus(state, allowAddr) + assert.Equal(t, precompile.AllowListEnabled, res) + + assert.Equal(t, common.Big2, state.GetBalance(allowAddr), "expected minted funds") + }, + }, "mint funds from admin address": { caller: adminAddr, precompileAddr: precompile.ContractNativeMinterAddress, @@ -756,6 +779,9 @@ func TestContractNativeMinterRun(t *testing.T) { precompile.SetContractNativeMinterStatus(state, allowAddr, precompile.AllowListEnabled) precompile.SetContractNativeMinterStatus(state, noRoleAddr, precompile.AllowListNoRole) 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 +814,7 @@ func TestFeeConfigManagerRun(t *testing.T) { input func() []byte suppliedGas uint64 readOnly bool + config *precompile.FeeConfigManagerConfig expectedRes []byte expectedErr string @@ -908,6 +935,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, @@ -1038,6 +1088,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/params/precompile_config_test.go b/params/precompile_config_test.go index 6c0fb521fc..cf29b3c044 100644 --- a/params/precompile_config_test.go +++ b/params/precompile_config_test.go @@ -7,19 +7,21 @@ 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) { 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.TxAllowListConfig{ + UpgradeableConfig: precompile.UpgradeableConfig{ + BlockTimestamp: big.NewInt(2), }, }, } @@ -35,7 +37,7 @@ func TestValidateWithChainConfig(t *testing.T) { } // 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 +48,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. @@ -57,30 +59,132 @@ func TestValidateWithChainConfig(t *testing.T) { TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(5), admins), }, ) - 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), + }, + { + 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), + }, + { + TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(2)), + }, + { + TxAllowListConfig: &precompile.TxAllowListConfig{ + AllowListConfig: precompile.AllowListConfig{AllowListAdmins: admins, EnabledAddresses: admins}, + UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, + }, + }, + }, + expectedError: "cannot set address", + }, + { + name: "invalid initial fee manager config", + upgrades: []PrecompileUpgrade{ + { + FeeManagerConfig: &precompile.FeeConfigManagerConfig{ + AllowListConfig: precompile.AllowListConfig{AllowListAdmins: admins}, + UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, + InitialFeeConfig: &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.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.TxAllowListConfig{ + AllowListConfig: precompile.AllowListConfig{AllowListAdmins: admins, EnabledAddresses: admins}, + UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, + }, + }, + expectedError: "cannot set address", + }, + { + name: "invalid initial fee manager config", + upgrade: PrecompileUpgrade{ + FeeManagerConfig: &precompile.FeeConfigManagerConfig{ + AllowListConfig: precompile.AllowListConfig{AllowListAdmins: admins}, + UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, + InitialFeeConfig: &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) { admins := []common.Address{{1}} - config := &ChainConfig{} + baseConfig := *SubnetEVMDefaultChainConfig + config := &baseConfig config.PrecompileUpgrades = []PrecompileUpgrade{ { TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(2), admins), @@ -91,7 +195,7 @@ func TestValidateRequiresSortedTimestamps(t *testing.T) { } // 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)") } From 27e207fd69271b59e507556f9019777c69db9a6f Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 26 Aug 2022 12:22:31 +0300 Subject: [PATCH 04/64] add a nil check for initial fee config verification --- precompile/fee_config_manager.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/precompile/fee_config_manager.go b/precompile/fee_config_manager.go index c33079601b..066774c412 100644 --- a/precompile/fee_config_manager.go +++ b/precompile/fee_config_manager.go @@ -118,7 +118,10 @@ func (c *FeeConfigManagerConfig) Verify() error { if err := c.AllowListConfig.Verify(); err != nil { return err } - return c.InitialFeeConfig.Verify() + if c.InitialFeeConfig != nil { + return c.InitialFeeConfig.Verify() + } + return nil } // GetFeeConfigManagerStatus returns the role of [address] for the fee config manager list. From 97d34722a7afe1efd2033e3018501e859f6bd5e1 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 26 Aug 2022 14:52:56 +0300 Subject: [PATCH 05/64] conditionally verify allowlist --- precompile/allow_list.go | 19 +++++++++++-------- precompile/contract_native_minter.go | 11 +++++------ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/precompile/allow_list.go b/precompile/allow_list.go index db16922356..b58328e06e 100644 --- a/precompile/allow_list.go +++ b/precompile/allow_list.go @@ -74,15 +74,18 @@ func (c *AllowListConfig) Equal(other *AllowListConfig) bool { // Verify returns an error if there is an overlapping address between admins and enableds func (c *AllowListConfig) Verify() error { - enabledMap := make(map[common.Address]struct{}) - for _, enabledAddr := range c.EnabledAddresses { - if _, ok := enabledMap[enabledAddr]; !ok { - enabledMap[enabledAddr] = struct{}{} + // check if both lists are empty + if len(c.EnabledAddresses) != 0 && len(c.AllowListAdmins) != 0 { + enabledMap := make(map[common.Address]struct{}) + for _, enabledAddr := range c.EnabledAddresses { + if _, ok := enabledMap[enabledAddr]; !ok { + enabledMap[enabledAddr] = struct{}{} + } } - } - for _, adminAddr := range c.AllowListAdmins { - if _, ok := enabledMap[adminAddr]; ok { - return fmt.Errorf("cannot set address %s as both admin and enabled", adminAddr) + for _, adminAddr := range c.AllowListAdmins { + if _, ok := enabledMap[adminAddr]; ok { + return fmt.Errorf("cannot set address %s as both admin and enabled", adminAddr) + } } } return nil diff --git a/precompile/contract_native_minter.go b/precompile/contract_native_minter.go index d0e29ae4fe..32d16544c7 100644 --- a/precompile/contract_native_minter.go +++ b/precompile/contract_native_minter.go @@ -66,14 +66,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) { - if len(c.InitialMint) != 0 { - for to, amount := range c.InitialMint { - if amount != nil { - bigIntAmount := (*big.Int)(amount) - state.AddBalance(to, bigIntAmount) - } + for to, amount := range c.InitialMint { + if amount != nil { + bigIntAmount := (*big.Int)(amount) + state.AddBalance(to, bigIntAmount) } } + c.AllowListConfig.Configure(state, ContractNativeMinterAddress) } From e70a80869f85f2a300688ec94402e35a0a74786a Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Mon, 29 Aug 2022 23:35:04 +0300 Subject: [PATCH 06/64] Update precompile/stateful_precompile_config.go Co-authored-by: aaronbuchwald --- precompile/stateful_precompile_config.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/precompile/stateful_precompile_config.go b/precompile/stateful_precompile_config.go index e82493fb60..4e4490cae7 100644 --- a/precompile/stateful_precompile_config.go +++ b/precompile/stateful_precompile_config.go @@ -35,8 +35,7 @@ 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 before configuring stateful precompile config. It is important to capture any invalid configuration - // before activation. + // Verify is called on startup and an error is treated as fatal. Configure can assume the Config has passed verification. Verify() error } From 38cc210f035f88f2fbb0dfb6996dadb292fe155b Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 30 Aug 2022 15:20:07 +0300 Subject: [PATCH 07/64] add config test to precompile package --- precompile/config_test.go | 78 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 precompile/config_test.go diff --git a/precompile/config_test.go b/precompile/config_test.go new file mode 100644 index 0000000000..2eea178f30 --- /dev/null +++ b/precompile/config_test.go @@ -0,0 +1,78 @@ +// (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/stretchr/testify/require" +) + +func TestVerifyPrecompileUpgrades(t *testing.T) { + admins := []common.Address{{1}} + tests := []struct { + name string + config StatefulPrecompileConfig + expectedError string + }{ + { + name: "invalid allow list config in tx allowlist", + config: &TxAllowListConfig{ + AllowListConfig: AllowListConfig{AllowListAdmins: admins, EnabledAddresses: admins}, + UpgradeableConfig: UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, + }, + expectedError: "cannot set address", + }, + { + name: "invalid allow list config in deployer allowlist", + config: &ContractDeployerAllowListConfig{ + AllowListConfig: AllowListConfig{AllowListAdmins: admins, EnabledAddresses: admins}, + UpgradeableConfig: UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, + }, + expectedError: "cannot set address", + }, + { + name: "invalid allow list config in native minter allowlist", + config: &ContractNativeMinterConfig{ + AllowListConfig: AllowListConfig{AllowListAdmins: admins, EnabledAddresses: admins}, + UpgradeableConfig: UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, + }, + expectedError: "cannot set address", + }, + { + name: "invalid allow list config in fee manager allowlist", + config: &FeeConfigManagerConfig{ + AllowListConfig: AllowListConfig{AllowListAdmins: admins, EnabledAddresses: admins}, + UpgradeableConfig: UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, + }, + expectedError: "cannot set address", + }, + { + name: "invalid initial fee manager config", + config: &FeeConfigManagerConfig{ + AllowListConfig: AllowListConfig{AllowListAdmins: admins}, + UpgradeableConfig: UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, + InitialFeeConfig: &commontype.FeeConfig{ + GasLimit: big.NewInt(0), + }, + }, + expectedError: "gasLimit = 0 cannot be less than or equal to 0", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + err := tt.config.Verify() + if tt.expectedError == "" { + require.NoError(err) + } else { + require.ErrorContains(err, tt.expectedError) + } + }) + } +} From 55d59e4070d940df45732ee8b8479d10110c92c0 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 30 Aug 2022 15:23:17 +0300 Subject: [PATCH 08/64] add 0 test case for gas limit --- params/precompile_config_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/params/precompile_config_test.go b/params/precompile_config_test.go index cf29b3c044..b7c6a082ee 100644 --- a/params/precompile_config_test.go +++ b/params/precompile_config_test.go @@ -115,6 +115,21 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { }, expectedError: "gasLimit = -1 cannot be less than or equal to 0", }, + { + name: "invalid initial fee manager config gas limit 0", + upgrades: []PrecompileUpgrade{ + { + FeeManagerConfig: &precompile.FeeConfigManagerConfig{ + AllowListConfig: precompile.AllowListConfig{AllowListAdmins: admins}, + UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, + InitialFeeConfig: &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) { From 258ae40b6b714e6a0b0739bc30f320d762399742 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 30 Aug 2022 15:38:44 +0300 Subject: [PATCH 09/64] add comments for allowlist storage keys --- accounts/abi/bind/precompile_template.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/accounts/abi/bind/precompile_template.go b/accounts/abi/bind/precompile_template.go index b7203f7bc8..9a10f64bf7 100644 --- a/accounts/abi/bind/precompile_template.go +++ b/accounts/abi/bind/precompile_template.go @@ -214,6 +214,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) } From c60ec84b2bfa449f013b95556f0e897d570f496f Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 31 Aug 2022 16:26:24 +0300 Subject: [PATCH 10/64] move allow list role to different file --- precompile/allow_list.go | 65 +++++++---------------------------- precompile/allow_list_role.go | 49 ++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 53 deletions(-) create mode 100644 precompile/allow_list_role.go diff --git a/precompile/allow_list.go b/precompile/allow_list.go index b58328e06e..46501af351 100644 --- a/precompile/allow_list.go +++ b/precompile/allow_list.go @@ -75,63 +75,22 @@ func (c *AllowListConfig) Equal(other *AllowListConfig) bool { // Verify returns an error if there is an overlapping address between admins and enableds func (c *AllowListConfig) Verify() error { // check if both lists are empty - if len(c.EnabledAddresses) != 0 && len(c.AllowListAdmins) != 0 { - enabledMap := make(map[common.Address]struct{}) - for _, enabledAddr := range c.EnabledAddresses { - if _, ok := enabledMap[enabledAddr]; !ok { - enabledMap[enabledAddr] = struct{}{} - } - } - for _, adminAddr := range c.AllowListAdmins { - if _, ok := enabledMap[adminAddr]; ok { - return fmt.Errorf("cannot set address %s as both admin and enabled", adminAddr) - } - } - } - return nil -} - -// 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 + 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 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 + } +} From bf00bb567e1e3e6868c9fe4ae0d0cf6c66c38172 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 31 Aug 2022 16:28:54 +0300 Subject: [PATCH 11/64] Update precompile/allow_list.go Co-authored-by: aaronbuchwald --- precompile/allow_list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompile/allow_list.go b/precompile/allow_list.go index b58328e06e..6717915266 100644 --- a/precompile/allow_list.go +++ b/precompile/allow_list.go @@ -41,7 +41,7 @@ var ( // AllowListConfig specifies the initial set of allow list admins. type AllowListConfig struct { AllowListAdmins []common.Address `json:"adminAddresses"` - EnabledAddresses []common.Address `json:"enabledAddresses,omitempty"` // initial enabled addresses + EnabledAddresses []common.Address `json:"enabledAddresses"` // initial enabled addresses } // Configure initializes the address space of [precompileAddr] by initializing the role of each of From 65bd9e8da49653987e99d18192dafb695afbca10 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 31 Aug 2022 16:29:53 +0300 Subject: [PATCH 12/64] rename test --- params/precompile_config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/params/precompile_config_test.go b/params/precompile_config_test.go index b7c6a082ee..55445fbf9e 100644 --- a/params/precompile_config_test.go +++ b/params/precompile_config_test.go @@ -14,7 +14,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestValidateWithChainConfig(t *testing.T) { +func TestVerifyWithChainConfig(t *testing.T) { admins := []common.Address{{1}} baseConfig := *SubnetEVMDefaultChainConfig config := &baseConfig From dfd41f57ade474a26a8d5a43925e1a7004d93511 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 31 Aug 2022 16:32:05 +0300 Subject: [PATCH 13/64] Update precompile/allow_list.go Co-authored-by: Darioush Jalali --- precompile/allow_list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompile/allow_list.go b/precompile/allow_list.go index 6717915266..59626ee653 100644 --- a/precompile/allow_list.go +++ b/precompile/allow_list.go @@ -72,7 +72,7 @@ func (c *AllowListConfig) Equal(other *AllowListConfig) bool { return true } -// Verify returns an error if there is an overlapping address between admins and enableds +// Verify returns an error if there is an overlapping address between admin and enabled roles func (c *AllowListConfig) Verify() error { // check if both lists are empty if len(c.EnabledAddresses) != 0 && len(c.AllowListAdmins) != 0 { From 8075ba12fbaa31ebd7cad5c9c7842dfaa4a0955e Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 31 Aug 2022 16:32:45 +0300 Subject: [PATCH 14/64] rename test & use func to create config --- params/precompile_config_test.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/params/precompile_config_test.go b/params/precompile_config_test.go index 55445fbf9e..dd3e77a986 100644 --- a/params/precompile_config_test.go +++ b/params/precompile_config_test.go @@ -19,11 +19,7 @@ func TestVerifyWithChainConfig(t *testing.T) { baseConfig := *SubnetEVMDefaultChainConfig config := &baseConfig config.PrecompileUpgrade = PrecompileUpgrade{ - TxAllowListConfig: &precompile.TxAllowListConfig{ - UpgradeableConfig: precompile.UpgradeableConfig{ - BlockTimestamp: big.NewInt(2), - }, - }, + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(2), []common.Address{}), } config.PrecompileUpgrades = []PrecompileUpgrade{ { @@ -196,7 +192,7 @@ func TestVerifyPrecompiles(t *testing.T) { } } -func TestValidateRequiresSortedTimestamps(t *testing.T) { +func TestVerifyRequiresSortedTimestamps(t *testing.T) { admins := []common.Address{{1}} baseConfig := *SubnetEVMDefaultChainConfig config := &baseConfig From 3ffa6dcbcf524790a983200b0d2a764c27cc42b6 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 31 Aug 2022 16:58:39 +0300 Subject: [PATCH 15/64] Remove unnecessary role checks --- core/stateful_precompile_test.go | 107 +++++++++++-------------------- 1 file changed, 38 insertions(+), 69 deletions(-) diff --git a/core/stateful_precompile_test.go b/core/stateful_precompile_test.go index 8887a33fc9..bec0c8fece 100644 --- a/core/stateful_precompile_test.go +++ b/core/stateful_precompile_test.go @@ -90,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) }, }, @@ -111,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) }, }, @@ -215,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, @@ -229,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, @@ -243,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, @@ -269,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 { @@ -326,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) }, }, @@ -347,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) }, }, @@ -451,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, @@ -465,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, @@ -479,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, @@ -504,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 { @@ -547,6 +522,7 @@ func TestContractNativeMinterRun(t *testing.T) { adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") allowAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") + testAddr := common.BigToAddress(common.Big2) for name, test := range map[string]test{ "mint funds from no role fails": { @@ -577,12 +553,25 @@ 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") }, }, + "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: allowAddr, precompileAddr: precompile.ContractNativeMinterAddress, @@ -598,9 +587,6 @@ func TestContractNativeMinterRun(t *testing.T) { readOnly: false, expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(), assertState: func(t *testing.T, state *state.StateDB) { - res := precompile.GetContractNativeMinterStatus(state, allowAddr) - assert.Equal(t, precompile.AllowListEnabled, res) - assert.Equal(t, common.Big2, state.GetBalance(allowAddr), "expected minted funds") }, }, @@ -618,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") }, }, @@ -638,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") }, }, @@ -746,10 +726,7 @@ 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) }, }, @@ -778,6 +755,10 @@ func TestContractNativeMinterRun(t *testing.T) { precompile.SetContractNativeMinterStatus(state, adminAddr, precompile.AllowListAdmin) precompile.SetContractNativeMinterStatus(state, allowAddr, 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, allowAddr)) + 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) @@ -855,9 +836,6 @@ 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) }, @@ -879,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) }, @@ -900,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) @@ -1050,10 +1022,7 @@ 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) }, }, From b844bbbabf3ea38b6511327dfb54090b5f86c4cb Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 1 Sep 2022 16:40:30 +0300 Subject: [PATCH 16/64] use config constructor for precompile tests --- core/genesis_test.go | 2 +- core/state_processor_test.go | 2 +- core/test_blockchain.go | 4 +- eth/gasprice/gasprice_test.go | 2 +- params/precompile_config_test.go | 53 ++++++++-------------- params/upgrade_config_test.go | 30 ++++++------ plugin/evm/vm_test.go | 8 ++-- plugin/evm/vm_upgrade_bytes_test.go | 2 +- precompile/config_test.go | 37 +++++---------- precompile/contract_deployer_allow_list.go | 9 ++-- precompile/contract_native_minter.go | 10 ++-- precompile/fee_config_manager.go | 10 ++-- precompile/tx_allow_list.go | 9 ++-- 13 files changed, 81 insertions(+), 97 deletions(-) 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 42307dc0c1..3878641712 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/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/precompile_config_test.go b/params/precompile_config_test.go index dd3e77a986..55b138b65a 100644 --- a/params/precompile_config_test.go +++ b/params/precompile_config_test.go @@ -19,7 +19,7 @@ func TestVerifyWithChainConfig(t *testing.T) { baseConfig := *SubnetEVMDefaultChainConfig config := &baseConfig config.PrecompileUpgrade = PrecompileUpgrade{ - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(2), []common.Address{}), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(2), nil, nil), } config.PrecompileUpgrades = []PrecompileUpgrade{ { @@ -28,7 +28,7 @@ func TestVerifyWithChainConfig(t *testing.T) { }, { // re-enable TxAllowList at timestamp 5 - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(5), admins), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(5), admins, nil), }, } @@ -52,7 +52,7 @@ func TestVerifyWithChainConfig(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.Verify() @@ -70,7 +70,7 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { name: "enable and disable tx allow list", upgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(1), admins), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(1), admins, nil), }, { TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(2)), @@ -82,16 +82,13 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { name: "invalid allow list config in tx allowlist", upgrades: []PrecompileUpgrade{ { - TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(1), admins), + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(1), admins, nil), }, { TxAllowListConfig: precompile.NewDisableTxAllowListConfig(big.NewInt(2)), }, { - TxAllowListConfig: &precompile.TxAllowListConfig{ - AllowListConfig: precompile.AllowListConfig{AllowListAdmins: admins, EnabledAddresses: admins}, - UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, - }, + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(3), admins, admins), }, }, expectedError: "cannot set address", @@ -100,13 +97,10 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { name: "invalid initial fee manager config", upgrades: []PrecompileUpgrade{ { - FeeManagerConfig: &precompile.FeeConfigManagerConfig{ - AllowListConfig: precompile.AllowListConfig{AllowListAdmins: admins}, - UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, - InitialFeeConfig: &commontype.FeeConfig{ + 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", @@ -115,13 +109,10 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { name: "invalid initial fee manager config gas limit 0", upgrades: []PrecompileUpgrade{ { - FeeManagerConfig: &precompile.FeeConfigManagerConfig{ - AllowListConfig: precompile.AllowListConfig{AllowListAdmins: admins}, - UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, - InitialFeeConfig: &commontype.FeeConfig{ + 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", @@ -154,23 +145,17 @@ func TestVerifyPrecompiles(t *testing.T) { { name: "invalid allow list config in tx allowlist", upgrade: PrecompileUpgrade{ - TxAllowListConfig: &precompile.TxAllowListConfig{ - AllowListConfig: precompile.AllowListConfig{AllowListAdmins: admins, EnabledAddresses: admins}, - UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, - }, + TxAllowListConfig: precompile.NewTxAllowListConfig(big.NewInt(3), admins, admins), }, expectedError: "cannot set address", }, { name: "invalid initial fee manager config", upgrade: PrecompileUpgrade{ - FeeManagerConfig: &precompile.FeeConfigManagerConfig{ - AllowListConfig: precompile.AllowListConfig{AllowListAdmins: admins}, - UpgradeableConfig: precompile.UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, - InitialFeeConfig: &commontype.FeeConfig{ + 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", }, @@ -198,10 +183,10 @@ func TestVerifyRequiresSortedTimestamps(t *testing.T) { 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), }, } @@ -215,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..7229acc251 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{ 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/config_test.go b/precompile/config_test.go index 2eea178f30..36af6fbf57 100644 --- a/precompile/config_test.go +++ b/precompile/config_test.go @@ -20,46 +20,31 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { expectedError string }{ { - name: "invalid allow list config in tx allowlist", - config: &TxAllowListConfig{ - AllowListConfig: AllowListConfig{AllowListAdmins: admins, EnabledAddresses: admins}, - UpgradeableConfig: UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, - }, + name: "invalid allow list config in tx allowlist", + config: NewTxAllowListConfig(big.NewInt(3), admins, admins), expectedError: "cannot set address", }, { - name: "invalid allow list config in deployer allowlist", - config: &ContractDeployerAllowListConfig{ - AllowListConfig: AllowListConfig{AllowListAdmins: admins, EnabledAddresses: admins}, - UpgradeableConfig: UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, - }, + name: "invalid allow list config in deployer allowlist", + config: NewTxAllowListConfig(big.NewInt(3), admins, admins), expectedError: "cannot set address", }, { - name: "invalid allow list config in native minter allowlist", - config: &ContractNativeMinterConfig{ - AllowListConfig: AllowListConfig{AllowListAdmins: admins, EnabledAddresses: admins}, - UpgradeableConfig: UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, - }, + name: "invalid allow list config in native minter allowlist", + config: NewTxAllowListConfig(big.NewInt(3), admins, admins), expectedError: "cannot set address", }, { - name: "invalid allow list config in fee manager allowlist", - config: &FeeConfigManagerConfig{ - AllowListConfig: AllowListConfig{AllowListAdmins: admins, EnabledAddresses: admins}, - UpgradeableConfig: UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, - }, + name: "invalid allow list config in fee manager allowlist", + config: NewTxAllowListConfig(big.NewInt(3), admins, admins), expectedError: "cannot set address", }, { name: "invalid initial fee manager config", - config: &FeeConfigManagerConfig{ - AllowListConfig: AllowListConfig{AllowListAdmins: admins}, - UpgradeableConfig: UpgradeableConfig{BlockTimestamp: big.NewInt(3)}, - InitialFeeConfig: &commontype.FeeConfig{ + config: NewFeeManagerConfig(big.NewInt(3), admins, nil, + &commontype.FeeConfig{ GasLimit: big.NewInt(0), - }, - }, + }), expectedError: "gasLimit = 0 cannot be less than or equal to 0", }, } diff --git a/precompile/contract_deployer_allow_list.go b/precompile/contract_deployer_allow_list.go index 97e1acd074..3b82efb01d 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 the given [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 32d16544c7..d0c5c097e9 100644 --- a/precompile/contract_native_minter.go +++ b/precompile/contract_native_minter.go @@ -40,11 +40,15 @@ type ContractNativeMinterConfig struct { } // 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 with [initialMint] as initial mint operation. +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, } } diff --git a/precompile/fee_config_manager.go b/precompile/fee_config_manager.go index 066774c412..65ae35b262 100644 --- a/precompile/fee_config_manager.go +++ b/precompile/fee_config_manager.go @@ -60,11 +60,15 @@ type FeeConfigManagerConfig struct { } // 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, } } 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}, } } From 2777e0ec90e54eb6868a8488fccc4c793c36a985 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 1 Sep 2022 18:54:59 +0300 Subject: [PATCH 17/64] Update accounts/abi/bind/precompile_template.go Co-authored-by: Darioush Jalali --- accounts/abi/bind/precompile_template.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/abi/bind/precompile_template.go b/accounts/abi/bind/precompile_template.go index 9a10f64bf7..71157f59ec 100644 --- a/accounts/abi/bind/precompile_template.go +++ b/accounts/abi/bind/precompile_template.go @@ -196,7 +196,7 @@ func (c *{{.Contract.Type}}Config) Contract() StatefulPrecompiledContract { func (c *{{.Contract.Type}}Config) Verify() error { {{if .Contract.AllowList}} // Verify AllowList first - if err := c.AllowListConfig.Verify(); err != nil{ + if err := c.AllowListConfig.Verify(); err != nil { return err } {{end}} From 83a9db64ea12500273da3d736d021488ca4215a3 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 1 Sep 2022 18:57:35 +0300 Subject: [PATCH 18/64] Update precompile/contract_native_minter.go Co-authored-by: Darioush Jalali --- precompile/contract_native_minter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompile/contract_native_minter.go b/precompile/contract_native_minter.go index d0c5c097e9..1c5deffb5a 100644 --- a/precompile/contract_native_minter.go +++ b/precompile/contract_native_minter.go @@ -40,7 +40,7 @@ type ContractNativeMinterConfig struct { } // NewContractNativeMinterConfig returns a config for a network upgrade at [blockTimestamp] that enables -// ContractNativeMinter with the given [admins] and [enableds] as members of the allowlist with [initialMint] as initial mint operation. +// 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{ From 415a32bfe27d27ef231fed0c8abcfe2ee2bcfa14 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 1 Sep 2022 18:58:27 +0300 Subject: [PATCH 19/64] Update precompile/contract_deployer_allow_list.go Co-authored-by: Darioush Jalali --- precompile/contract_deployer_allow_list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompile/contract_deployer_allow_list.go b/precompile/contract_deployer_allow_list.go index 3b82efb01d..aa01d9b8cd 100644 --- a/precompile/contract_deployer_allow_list.go +++ b/precompile/contract_deployer_allow_list.go @@ -23,7 +23,7 @@ type ContractDeployerAllowListConfig struct { } // NewContractDeployerAllowListConfig returns a config for a network upgrade at [blockTimestamp] that enables -// ContractDeployerAllowList with the given [admins] and [enableds] as members of the allowlist. +// 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{ From 333aa8b97d5933f95c440c79a432523d0f466641 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 1 Sep 2022 18:58:35 +0300 Subject: [PATCH 20/64] Update precompile/allow_list.go Co-authored-by: Darioush Jalali --- precompile/allow_list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompile/allow_list.go b/precompile/allow_list.go index 418aabb0b4..dc8ba05a7e 100644 --- a/precompile/allow_list.go +++ b/precompile/allow_list.go @@ -74,7 +74,7 @@ func (c *AllowListConfig) Equal(other *AllowListConfig) bool { // Verify returns an error if there is an overlapping address between admin and enabled roles func (c *AllowListConfig) Verify() error { - // check if both lists are empty + // return early if either list is empty if len(c.EnabledAddresses) == 0 || len(c.AllowListAdmins) == 0 { return nil } From abfa39e6a9219f024423e57065432a349896b8e1 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 1 Sep 2022 19:52:44 +0300 Subject: [PATCH 21/64] add equals methods --- commontype/fee_config.go | 17 +++++++++++ core/stateful_precompile_test.go | 44 ++++++++++++++-------------- params/precompile_config.go | 2 +- precompile/allow_list.go | 14 +++++++-- precompile/contract_native_minter.go | 24 ++++++++++++++- precompile/fee_config_manager.go | 13 +++++++- 6 files changed, 86 insertions(+), 28 deletions(-) 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/core/stateful_precompile_test.go b/core/stateful_precompile_test.go index bec0c8fece..782cb38a79 100644 --- a/core/stateful_precompile_test.go +++ b/core/stateful_precompile_test.go @@ -520,7 +520,7 @@ func TestContractNativeMinterRun(t *testing.T) { } adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") - allowAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") + enabledAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") testAddr := common.BigToAddress(common.Big2) @@ -539,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) } @@ -553,7 +553,7 @@ func TestContractNativeMinterRun(t *testing.T) { readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - 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": { @@ -573,11 +573,11 @@ func TestContractNativeMinterRun(t *testing.T) { }, }, "initial mint funds": { - caller: allowAddr, + caller: enabledAddr, precompileAddr: precompile.ContractNativeMinterAddress, config: &precompile.ContractNativeMinterConfig{ InitialMint: map[common.Address]*math.HexOrDecimal256{ - allowAddr: math.NewHexOrDecimal256(2), + enabledAddr: math.NewHexOrDecimal256(2), }, }, input: func() []byte { @@ -587,7 +587,7 @@ func TestContractNativeMinterRun(t *testing.T) { readOnly: false, expectedRes: common.Hash(precompile.AllowListNoRole).Bytes(), assertState: func(t *testing.T, state *state.StateDB) { - assert.Equal(t, common.Big2, state.GetBalance(allowAddr), "expected minted funds") + assert.Equal(t, common.Big2, state.GetBalance(enabledAddr), "expected minted funds") }, }, "mint funds from admin address": { @@ -639,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) } @@ -670,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) } @@ -731,7 +731,7 @@ func TestContractNativeMinterRun(t *testing.T) { }, }, "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,10 +753,10 @@ 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, allowAddr)) + assert.Equal(t, precompile.AllowListEnabled, precompile.GetContractNativeMinterStatus(state, enabledAddr)) assert.Equal(t, precompile.AllowListNoRole, precompile.GetContractNativeMinterStatus(state, noRoleAddr)) blockContext := &mockBlockContext{blockNumber: common.Big0} @@ -804,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{ @@ -822,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) @@ -840,8 +840,8 @@ func TestFeeConfigManagerRun(t *testing.T) { 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 @@ -967,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) @@ -1027,7 +1027,7 @@ func TestFeeConfigManagerRun(t *testing.T) { }, }, "set allow role from non-admin fails": { - caller: allowAddr, + caller: enabledAddr, precompileAddr: precompile.FeeConfigManagerAddress, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) @@ -1049,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 { diff --git a/params/precompile_config.go b/params/precompile_config.go index 0c609e6b94..933b88a693 100644 --- a/params/precompile_config.go +++ b/params/precompile_config.go @@ -60,7 +60,7 @@ 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 diff --git a/precompile/allow_list.go b/precompile/allow_list.go index 418aabb0b4..8823e984f0 100644 --- a/precompile/allow_list.go +++ b/precompile/allow_list.go @@ -61,11 +61,19 @@ func (c *AllowListConfig) Equal(other *AllowListConfig) bool { if other == nil { return false } - if len(c.AllowListAdmins) != len(other.AllowListAdmins) { + if !checkList(c.AllowListAdmins, other.AllowListAdmins) { return false } - for i, admin := range c.AllowListAdmins { - if admin != other.AllowListAdmins[i] { + + return checkList(c.EnabledAddresses, other.EnabledAddresses) +} + +func checkList(current []common.Address, other []common.Address) bool { + if len(current) != len(other) { + return false + } + for i, address := range current { + if address != other[i] { return false } } diff --git a/precompile/contract_native_minter.go b/precompile/contract_native_minter.go index d0c5c097e9..ccc87eecbc 100644 --- a/precompile/contract_native_minter.go +++ b/precompile/contract_native_minter.go @@ -8,6 +8,7 @@ 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" @@ -92,7 +93,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 65ae35b262..0df86b0977 100644 --- a/precompile/fee_config_manager.go +++ b/precompile/fee_config_manager.go @@ -95,7 +95,16 @@ 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]. @@ -103,10 +112,12 @@ func (c *FeeConfigManagerConfig) Configure(chainConfig ChainConfig, state StateD // Store the initial fee config into the state when the fee config manager activates. if c.InitialFeeConfig != nil { if err := StoreFeeConfig(state, *c.InitialFeeConfig, blockContext); err != nil { + // This should not happen since we already checked this config with Verify() panic(fmt.Sprintf("invalid feeConfig provided: %s", err)) } } else { if err := StoreFeeConfig(state, chainConfig.GetFeeConfig(), blockContext); err != nil { + // This should not happen since we already checked the chain config in the genesis creation. panic(fmt.Sprintf("fee config should have been verified in genesis: %s", err)) } } From 409c736402c245aa26fcff3986662e0345e9c3ed Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 16 Sep 2022 17:32:52 +0300 Subject: [PATCH 22/64] initialize reward manager precompile by precompilegen --- .../contracts/IRewardManager.sol | 15 + params/config.go | 8 + params/precompile_config.go | 18 +- precompile/params.go | 2 + precompile/reward_manager.go | 484 ++++++++++++++++++ precompile/utils.go | 13 + precompile/utils_test.go | 10 + 7 files changed, 549 insertions(+), 1 deletion(-) create mode 100644 contract-examples/contracts/IRewardManager.sol create mode 100644 precompile/reward_manager.go diff --git a/contract-examples/contracts/IRewardManager.sol b/contract-examples/contracts/IRewardManager.sol new file mode 100644 index 0000000000..4387dd0d08 --- /dev/null +++ b/contract-examples/contracts/IRewardManager.sol @@ -0,0 +1,15 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +import "./IAllowList.sol"; + +interface IRewardManager is IAllowList { + function setRewardAddress(address addr) external; + + function allowFeeRecipients() external; + + function disableRewards() external; + + function currentRewardAddress() external returns (address rewardAddress); + + function areFeeRecipientsAllowed() external returns (bool isAllowed); +} diff --git a/params/config.go b/params/config.go index 302227dadb..4e1c78fa60 100644 --- a/params/config.go +++ b/params/config.go @@ -253,6 +253,12 @@ func (c *ChainConfig) IsFeeConfigManager(blockTimestamp *big.Int) bool { return config != nil && !config.Disable } +// IsRewardManager returns whether [blockTimestamp] is either equal to the RewardManager fork block timestamp or greater. +func (c *ChainConfig) IsRewardManager(blockTimestamp *big.Int) bool { + config := c.GetRewardManagerConfig(blockTimestamp) + return config != nil && !config.Disable +} + // ADD YOUR PRECOMPILE HERE /* func (c *ChainConfig) Is{YourPrecompile}(blockTimestamp *big.Int) bool { @@ -493,6 +499,7 @@ type Rules struct { IsContractNativeMinterEnabled bool IsTxAllowListEnabled bool IsFeeConfigManagerEnabled bool + IsRewardConfigManagerEnabled bool // ADD YOUR PRECOMPILE HERE // Is{YourPrecompile}Enabled bool @@ -532,6 +539,7 @@ func (c *ChainConfig) AvalancheRules(blockNum, blockTimestamp *big.Int) Rules { rules.IsContractNativeMinterEnabled = c.IsContractNativeMinter(blockTimestamp) rules.IsTxAllowListEnabled = c.IsTxAllowList(blockTimestamp) rules.IsFeeConfigManagerEnabled = c.IsFeeConfigManager(blockTimestamp) + rules.IsRewardConfigManagerEnabled = c.IsRewardConfigManager(blockTimestamp) // ADD YOUR PRECOMPILE HERE // rules.Is{YourPrecompile}Enabled = c.{IsYourPrecompile}(blockTimestamp) diff --git a/params/precompile_config.go b/params/precompile_config.go index 933b88a693..f29a83a8f6 100644 --- a/params/precompile_config.go +++ b/params/precompile_config.go @@ -21,12 +21,13 @@ const ( contractNativeMinterKey txAllowListKey feeManagerKey + rewardManagerKey // ADD YOUR PRECOMPILE HERE // {yourPrecompile}Key ) // ADD YOUR PRECOMPILE HERE -var precompileKeys = []precompileKey{contractDeployerAllowListKey, contractNativeMinterKey, txAllowListKey, feeManagerKey /* {yourPrecompile}Key */} +var precompileKeys = []precompileKey{contractDeployerAllowListKey, contractNativeMinterKey, txAllowListKey, feeManagerKey, rewardManagerKey /* {yourPrecompile}Key */} // PrecompileUpgrade is a helper struct embedded in UpgradeConfig, representing // each of the possible stateful precompile types that can be activated @@ -36,6 +37,7 @@ type PrecompileUpgrade struct { ContractNativeMinterConfig *precompile.ContractNativeMinterConfig `json:"contractNativeMinterConfig,omitempty"` // Config for the native minter precompile TxAllowListConfig *precompile.TxAllowListConfig `json:"txAllowListConfig,omitempty"` // Config for the tx allow list precompile FeeManagerConfig *precompile.FeeConfigManagerConfig `json:"feeManagerConfig,omitempty"` // Config for the fee manager precompile + RewardManagerConfig *precompile.RewardManagerConfig `json:"rewardManagerConfig,omitempty"` // Config for the reward manager precompile // ADD YOUR PRECOMPILE HERE // {YourPrecompile}Config *precompile.{YourPrecompile}Config `json:"{yourPrecompile}Config,omitempty"` } @@ -50,6 +52,8 @@ func (p *PrecompileUpgrade) getByKey(key precompileKey) (precompile.StatefulPrec return p.TxAllowListConfig, p.TxAllowListConfig != nil case feeManagerKey: return p.FeeManagerConfig, p.FeeManagerConfig != nil + case rewardManagerKey: + return p.RewardManagerConfig, p.RewardManagerConfig != nil // ADD YOUR PRECOMPILE HERE /* case {yourPrecompile}Key: @@ -207,6 +211,15 @@ func (c *ChainConfig) GetFeeConfigManagerConfig(blockTimestamp *big.Int) *precom return nil } +// GetRewardManagerConfig returns the latest forked RewardManagerConfig +// specified by [c] or nil if it was never enabled. +func (c *ChainConfig) GetRewardManagerConfig(blockTimestamp *big.Int) *precompile.RewardManagerConfig { + if val := c.getActivePrecompileConfig(blockTimestamp, rewardManagerKey, c.PrecompileUpgrades); val != nil { + return val.(*precompile.RewardManagerConfig) + } + return nil +} + /* ADD YOUR PRECOMPILE HERE func (c *ChainConfig) Get{YourPrecompile}Config(blockTimestamp *big.Int) *precompile.{YourPrecompile}Config { if val := c.getActivePrecompileConfig(blockTimestamp, {yourPrecompile}Key, c.PrecompileUpgrades); val != nil { @@ -230,6 +243,9 @@ func (c *ChainConfig) GetActivePrecompiles(blockTimestamp *big.Int) PrecompileUp if config := c.GetFeeConfigManagerConfig(blockTimestamp); config != nil && !config.Disable { pu.FeeManagerConfig = config } + if config := c.GetRewardManagerConfig(blockTimestamp); config != nil && !config.Disable { + pu.RewardManagerConfig = config + } // ADD YOUR PRECOMPILE HERE // if config := c.{YourPrecompile}Config(blockTimestamp); config != nil && !config.Disable { // pu.{YourPrecompile}Config = config diff --git a/precompile/params.go b/precompile/params.go index b5829cf1db..5776bb842e 100644 --- a/precompile/params.go +++ b/precompile/params.go @@ -31,6 +31,7 @@ var ( ContractNativeMinterAddress = common.HexToAddress("0x0200000000000000000000000000000000000001") TxAllowListAddress = common.HexToAddress("0x0200000000000000000000000000000000000002") FeeConfigManagerAddress = common.HexToAddress("0x0200000000000000000000000000000000000003") + RewardManagerAddress = common.HexToAddress("0x0200000000000000000000000000000000000004") // ADD YOUR PRECOMPILE HERE // {YourPrecompile}Address = common.HexToAddress("0x03000000000000000000000000000000000000??") @@ -39,6 +40,7 @@ var ( ContractNativeMinterAddress, TxAllowListAddress, FeeConfigManagerAddress, + RewardManagerAddress, // ADD YOUR PRECOMPILE HERE // YourPrecompileAddress } diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go new file mode 100644 index 0000000000..36f90541ce --- /dev/null +++ b/precompile/reward_manager.go @@ -0,0 +1,484 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Code generated +// This file is a generated precompile contract with stubbed abstract functions. + +package precompile + +import ( + "errors" + "fmt" + "math/big" + "strings" + + "github.com/ava-labs/subnet-evm/accounts/abi" + "github.com/ava-labs/subnet-evm/vmerrs" + + "github.com/ethereum/go-ethereum/common" +) + +const ( + AllowFeeRecipientsGasCost uint64 = (writeGasCostPerSlot * 2) + ReadAllowListGasCost // 2 slots + read allow list + AreFeeRecipientsAllowedGasCost uint64 = readGasCostPerSlot + CurrentRewardAddressGasCost uint64 = readGasCostPerSlot + DisableRewardsGasCost uint64 = (writeGasCostPerSlot * 2) + ReadAllowListGasCost // 2 slots + read allow list + SetRewardAddressGasCost uint64 = (writeGasCostPerSlot * 2) + ReadAllowListGasCost // 2 slots + read allow list + + // RewardManagerRawABI contains the raw ABI of RewardManager contract. + RewardManagerRawABI = "[{\"inputs\":[],\"name\":\"allowFeeRecipients\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"areFeeRecipientsAllowed\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isAllowed\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"currentRewardAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"rewardAddress\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"disableRewards\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"readAllowList\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setEnabled\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setNone\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setRewardAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader +) + +// Singleton StatefulPrecompiledContract and signatures. +var ( + _ StatefulPrecompileConfig = &RewardManagerConfig{} + + ErrCannotAllowFeeRecipients = errors.New("non-enabled cannot allowFeeRecipients") + ErrCannotAreFeeRecipientsAllowed = errors.New("non-enabled cannot areFeeRecipientsAllowed") + ErrCannotCurrentRewardAddress = errors.New("non-enabled cannot currentRewardAddress") + ErrCannotDisableRewards = errors.New("non-enabled cannot disableRewards") + ErrCannotSetRewardAddress = errors.New("non-enabled cannot setRewardAddress") + + ErrCannotEnableBothRewards = errors.New("cannot enable both fee recipients and reward address at the same time") + ErrEmptyRewardAddress = errors.New("reward address cannot be empty") + + RewardManagerABI abi.ABI // will be initialized by init function + RewardManagerPrecompile StatefulPrecompiledContract // will be initialized by init function + + allowFeeRecipientsStorageKey = common.Hash{'a', 'f', 'r', 's', 'k'} + rewardAddressStorageKey = common.Hash{'r', 'a', 's', 'k'} +) + +type InitialRewardConfig struct { + AllowFeeRecipients bool `json:"allowFeeRecipients"` + RewardAddress common.Address `json:"rewardAddress,omitempty"` +} + +func (i *InitialRewardConfig) Verify() error { + switch { + case i.AllowFeeRecipients && i.RewardAddress != (common.Address{}): + return ErrCannotEnableBothRewards + // shall we also check blackhole address here? + default: + return nil + } +} + +func (c *InitialRewardConfig) Equal(other *InitialRewardConfig) bool { + if other == nil { + return false + } + + return c.AllowFeeRecipients == other.AllowFeeRecipients && c.RewardAddress == other.RewardAddress +} + +// RewardManagerConfig implements the StatefulPrecompileConfig +// interface while adding in the RewardManager specific precompile address. +type RewardManagerConfig struct { + AllowListConfig + UpgradeableConfig + InitialRewardConfig *InitialRewardConfig `json:"initialRewardConfig,omitempty"` +} + +func init() { + parsed, err := abi.JSON(strings.NewReader(RewardManagerRawABI)) + if err != nil { + panic(err) + } + RewardManagerABI = parsed + RewardManagerPrecompile = createRewardManagerPrecompile(RewardManagerAddress) +} + +// NewRewardManagerConfig returns a config for a network upgrade at [blockTimestamp] that enables +// RewardManager with the given [admins] and [enableds] as members of the allowlist with [initialConfig] as initial rewards config if specified. +func NewRewardManagerConfig(blockTimestamp *big.Int, admins []common.Address, enableds []common.Address, initialConfig *InitialRewardConfig) *RewardManagerConfig { + return &RewardManagerConfig{ + AllowListConfig: AllowListConfig{ + AllowListAdmins: admins, + EnabledAddresses: enableds, + }, + UpgradeableConfig: UpgradeableConfig{BlockTimestamp: blockTimestamp}, + InitialRewardConfig: initialConfig, + } +} + +// NewDisableRewardManagerConfig returns config for a network upgrade at [blockTimestamp] +// that disables RewardManager. +func NewDisableRewardManagerConfig(blockTimestamp *big.Int) *RewardManagerConfig { + return &RewardManagerConfig{ + UpgradeableConfig: UpgradeableConfig{ + BlockTimestamp: blockTimestamp, + Disable: true, + }, + } +} + +// Equal returns true if [s] is a [*RewardManagerConfig] and it has been configured identical to [c]. +func (c *RewardManagerConfig) Equal(s StatefulPrecompileConfig) bool { + // typecast before comparison + other, ok := (s).(*RewardManagerConfig) + if !ok { + return false + } + // CUSTOM CODE STARTS HERE + // modify this boolean accordingly with your custom RewardManagerConfig, to check if [other] and the current [c] are equal + // if RewardManagerConfig contains only UpgradeableConfig and AllowListConfig you can skip modifying it. + equals := c.UpgradeableConfig.Equal(&other.UpgradeableConfig) && c.AllowListConfig.Equal(&other.AllowListConfig) + if !equals { + return false + } + + if c.InitialRewardConfig == nil { + return other.InitialRewardConfig == nil + } + + return c.InitialRewardConfig.Equal(other.InitialRewardConfig) +} + +// Address returns the address of the RewardManager. Addresses reside under the precompile/params.go +// Select a non-conflicting address and set it in the params.go. +func (c *RewardManagerConfig) Address() common.Address { + return RewardManagerAddress +} + +// Configure configures [state] with the initial configuration. +func (c *RewardManagerConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) { + c.AllowListConfig.Configure(state, RewardManagerAddress) + // CUSTOM CODE STARTS HERE + // configure the RewardManager with the initial configuration + if c.InitialRewardConfig != nil { + // set the initial reward config + if c.InitialRewardConfig.AllowFeeRecipients { + StoreAllowFeeRecipients(state, true) + } else if c.InitialRewardConfig.RewardAddress != (common.Address{}) { + StoreRewardAddress(state, c.InitialRewardConfig.RewardAddress) + } + } +} + +// Contract returns the singleton stateful precompiled contract to be used for RewardManager. +func (c *RewardManagerConfig) Contract() StatefulPrecompiledContract { + return RewardManagerPrecompile +} + +func (c *RewardManagerConfig) Verify() error { + if err := c.AllowListConfig.Verify(); err != nil { + return err + } + if c.InitialRewardConfig != nil { + return c.InitialRewardConfig.Verify() + } + return nil +} + +// GetRewardManagerAllowListStatus returns the role of [address] for the RewardManager list. +func GetRewardManagerAllowListStatus(stateDB StateDB, address common.Address) AllowListRole { + return getAllowListStatus(stateDB, RewardManagerAddress, address) +} + +// SetRewardManagerAllowListStatus sets the permissions of [address] to [role] for the +// RewardManager list. Assumes [role] has already been verified as valid. +func SetRewardManagerAllowListStatus(stateDB StateDB, address common.Address, role AllowListRole) { + setAllowListRole(stateDB, RewardManagerAddress, address, role) +} + +// PackAllowFeeRecipients packs the include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackAllowFeeRecipients() ([]byte, error) { + return RewardManagerABI.Pack("allowFeeRecipients") +} + +// GetStoredAllowFeeRecipients returns the current value of the stored allowFeeRecipients flag. +func GetStoredAllowFeeRecipients(stateDB StateDB) bool { + val := stateDB.GetState(RewardManagerAddress, allowFeeRecipientsStorageKey) + return hashToBool(val) +} + +// StoreAllowFeeRecipients stores the given [val] under allowFeeRecipientsStoragekey. +func StoreAllowFeeRecipients(stateDB StateDB, val bool) { + stateDB.SetState(RewardManagerAddress, allowFeeRecipientsStorageKey, boolToHash(val)) +} + +func allowFeeRecipients(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = deductGas(suppliedGas, AllowFeeRecipientsGasCost); err != nil { + return nil, 0, err + } + if readOnly { + return nil, remainingGas, vmerrs.ErrWriteProtection + } + // no input provided for this function + + // Allow list is enabled and AllowFeeRecipients is a state-changer function. + // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. + // You can modify/delete this code if you don't want this function to be restricted by the allow list. + stateDB := accessibleState.GetStateDB() + // Verify that the caller is in the allow list and therefore has the right to modify it + callerStatus := getAllowListStatus(stateDB, RewardManagerAddress, caller) + if !callerStatus.IsEnabled() { + return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotAllowFeeRecipients, caller) + } + // allow list code ends here. + + // CUSTOM CODE STARTS HERE + // this function does not return an output, leave this one as is + if GetStoredRewardAddress(stateDB) != (common.Address{}) { + // reset stored reward address first + StoreRewardAddress(stateDB, common.Address{}) + } + StoreAllowFeeRecipients(stateDB, true) + packedOutput := []byte{} + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// PackAreFeeRecipientsAllowed packs the include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackAreFeeRecipientsAllowed() ([]byte, error) { + return RewardManagerABI.Pack("areFeeRecipientsAllowed") +} + +// PackAreFeeRecipientsAllowedOutput attempts to pack given isAllowed of type bool +// to conform the ABI outputs. +func PackAreFeeRecipientsAllowedOutput(isAllowed bool) ([]byte, error) { + return RewardManagerABI.PackOutput("areFeeRecipientsAllowed", isAllowed) +} + +func areFeeRecipientsAllowed(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = deductGas(suppliedGas, AreFeeRecipientsAllowedGasCost); err != nil { + return nil, 0, err + } + if readOnly { + return nil, remainingGas, vmerrs.ErrWriteProtection + } + // no input provided for this function + + // Allow list is enabled and AreFeeRecipientsAllowed is a state-changer function. + // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. + // You can modify/delete this code if you don't want this function to be restricted by the allow list. + stateDB := accessibleState.GetStateDB() + // Verify that the caller is in the allow list and therefore has the right to modify it + callerStatus := getAllowListStatus(stateDB, RewardManagerAddress, caller) + if !callerStatus.IsEnabled() { + return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotAreFeeRecipientsAllowed, caller) + } + // allow list code ends here. + + // CUSTOM CODE STARTS HERE + var output bool // CUSTOM CODE FOR AN OUTPUT + output = GetStoredAllowFeeRecipients(stateDB) + packedOutput, err := PackAreFeeRecipientsAllowedOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// PackCurrentRewardAddress packs the include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackCurrentRewardAddress() ([]byte, error) { + return RewardManagerABI.Pack("currentRewardAddress") +} + +// PackCurrentRewardAddressOutput attempts to pack given rewardAddress of type common.Address +// to conform the ABI outputs. +func PackCurrentRewardAddressOutput(rewardAddress common.Address) ([]byte, error) { + return RewardManagerABI.PackOutput("currentRewardAddress", rewardAddress) +} + +// GetStoredRewardAddress returns the current value of the address stored under rewardAddressStorageKey. +func GetStoredRewardAddress(stateDB StateDB) common.Address { + val := stateDB.GetState(RewardManagerAddress, rewardAddressStorageKey) + return common.BytesToAddress(val.Bytes()) +} + +// StoredRewardAddress stores the given [val] under rewardAddressStorageKey. +func StoreRewardAddress(stateDB StateDB, val common.Address) { + stateDB.SetState(RewardManagerAddress, rewardAddressStorageKey, val.Hash()) +} + +// PackSetRewardAddress packs [addr] of type common.Address into the appropriate arguments for setRewardAddress. +// the packed bytes include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackSetRewardAddress(addr common.Address) ([]byte, error) { + return RewardManagerABI.Pack("setRewardAddress", addr) +} + +// UnpackSetRewardAddressInput attempts to unpack [input] into the common.Address type argument +// assumes that [input] does not include selector (omits first 4 func signature bytes) +func UnpackSetRewardAddressInput(input []byte) (common.Address, error) { + res, err := RewardManagerABI.UnpackInput("setRewardAddress", input) + if err != nil { + return common.Address{}, err + } + unpacked := *abi.ConvertType(res[0], new(common.Address)).(*common.Address) + return unpacked, nil +} + +func setRewardAddress(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = deductGas(suppliedGas, SetRewardAddressGasCost); err != nil { + return nil, 0, err + } + if readOnly { + return nil, remainingGas, vmerrs.ErrWriteProtection + } + // attempts to unpack [input] into the arguments to the SetRewardAddressInput. + // Assumes that [input] does not include selector + // You can use unpacked [inputStruct] variable in your code + inputStruct, err := UnpackSetRewardAddressInput(input) + if err != nil { + return nil, remainingGas, err + } + + // Allow list is enabled and SetRewardAddress is a state-changer function. + // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. + // You can modify/delete this code if you don't want this function to be restricted by the allow list. + stateDB := accessibleState.GetStateDB() + // Verify that the caller is in the allow list and therefore has the right to modify it + callerStatus := getAllowListStatus(stateDB, RewardManagerAddress, caller) + if !callerStatus.IsEnabled() { + return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotSetRewardAddress, caller) + } + // allow list code ends here. + + // CUSTOM CODE STARTS HERE + // if input is empty, return an error + if inputStruct == (common.Address{}) { + return nil, remainingGas, ErrEmptyRewardAddress + } + // reset stored allow fee recipients flag only if it's already set to true + if GetStoredAllowFeeRecipients(stateDB) { + StoreAllowFeeRecipients(stateDB, false) + } + + StoreRewardAddress(stateDB, inputStruct) + // this function does not return an output, leave this one as is + packedOutput := []byte{} + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +func currentRewardAddress(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = deductGas(suppliedGas, CurrentRewardAddressGasCost); err != nil { + return nil, 0, err + } + if readOnly { + return nil, remainingGas, vmerrs.ErrWriteProtection + } + // no input provided for this function + + // Allow list is enabled and CurrentRewardAddress is a state-changer function. + // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. + // You can modify/delete this code if you don't want this function to be restricted by the allow list. + stateDB := accessibleState.GetStateDB() + // Verify that the caller is in the allow list and therefore has the right to modify it + callerStatus := getAllowListStatus(stateDB, RewardManagerAddress, caller) + if !callerStatus.IsEnabled() { + return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotCurrentRewardAddress, caller) + } + // allow list code ends here. + + // CUSTOM CODE STARTS HERE + output := GetStoredRewardAddress(stateDB) + packedOutput, err := PackCurrentRewardAddressOutput(output) + if err != nil { + return nil, remainingGas, err + } + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// PackDisableRewards packs the include selector (first 4 func signature bytes). +// This function is mostly used for tests. +func PackDisableRewards() ([]byte, error) { + return RewardManagerABI.Pack("disableRewards") +} + +func disableRewards(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if remainingGas, err = deductGas(suppliedGas, DisableRewardsGasCost); err != nil { + return nil, 0, err + } + if readOnly { + return nil, remainingGas, vmerrs.ErrWriteProtection + } + // no input provided for this function + + // Allow list is enabled and DisableRewards is a state-changer function. + // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. + // You can modify/delete this code if you don't want this function to be restricted by the allow list. + stateDB := accessibleState.GetStateDB() + // Verify that the caller is in the allow list and therefore has the right to modify it + callerStatus := getAllowListStatus(stateDB, RewardManagerAddress, caller) + if !callerStatus.IsEnabled() { + return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotDisableRewards, caller) + } + // allow list code ends here. + + // CUSTOM CODE STARTS HERE + // reset stored allow fee recipients flag only if it's already set to true + if GetStoredAllowFeeRecipients(stateDB) { + StoreAllowFeeRecipients(stateDB, false) + } + + // reset stored reward address only if it's already set to non empty address + if GetStoredRewardAddress(stateDB) != (common.Address{}) { + StoreRewardAddress(stateDB, common.Address{}) + } + // this function does not return an output, leave this one as is + packedOutput := []byte{} + + // Return the packed output and the remaining gas + return packedOutput, remainingGas, nil +} + +// createRewardManagerPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile. +// Access to the getters/setters is controlled by an allow list for [precompileAddr]. +func createRewardManagerPrecompile(precompileAddr common.Address) StatefulPrecompiledContract { + var functions []*statefulPrecompileFunction + functions = append(functions, createAllowListFunctions(precompileAddr)...) + + methodAllowFeeRecipients, ok := RewardManagerABI.Methods["allowFeeRecipients"] + if !ok { + panic("given method does not exist in the ABI") + } + functions = append(functions, newStatefulPrecompileFunction(methodAllowFeeRecipients.ID, allowFeeRecipients)) + + methodAreFeeRecipientsAllowed, ok := RewardManagerABI.Methods["areFeeRecipientsAllowed"] + if !ok { + panic("given method does not exist in the ABI") + } + functions = append(functions, newStatefulPrecompileFunction(methodAreFeeRecipientsAllowed.ID, areFeeRecipientsAllowed)) + + methodCurrentRewardAddress, ok := RewardManagerABI.Methods["currentRewardAddress"] + if !ok { + panic("given method does not exist in the ABI") + } + functions = append(functions, newStatefulPrecompileFunction(methodCurrentRewardAddress.ID, currentRewardAddress)) + + methodDisableRewards, ok := RewardManagerABI.Methods["disableRewards"] + if !ok { + panic("given method does not exist in the ABI") + } + functions = append(functions, newStatefulPrecompileFunction(methodDisableRewards.ID, disableRewards)) + + methodSetRewardAddress, ok := RewardManagerABI.Methods["setRewardAddress"] + if !ok { + panic("given method does not exist in the ABI") + } + functions = append(functions, newStatefulPrecompileFunction(methodSetRewardAddress.ID, setRewardAddress)) + + // Construct the contract with no fallback function. + contract := newStatefulPrecompileWithFunctionSelectors(nil, functions) + return contract +} diff --git a/precompile/utils.go b/precompile/utils.go index 2476a97e34..0f6f726381 100644 --- a/precompile/utils.go +++ b/precompile/utils.go @@ -14,6 +14,8 @@ import ( var functionSignatureRegex = regexp.MustCompile(`[\w]+\(((([\w]+)?)|((([\w]+),)+([\w]+)))\)`) +var hashTrue = common.HexToHash("1") + // CalculateFunctionSelector returns the 4 byte function selector that results from [functionSignature] // Ex. the function setBalance(addr address, balance uint256) should be passed in as the string: // "setBalance(address,uint256)" @@ -67,3 +69,14 @@ func returnPackedHash(packed []byte, index int) []byte { end := start + common.HashLength return packed[start:end] } + +func hashToBool(hash common.Hash) bool { + return hash == hashTrue +} + +func boolToHash(bl bool) common.Hash { + if bl { + return hashTrue + } + return common.Hash{} +} diff --git a/precompile/utils_test.go b/precompile/utils_test.go index ff6d43486b..9dbcb67f51 100644 --- a/precompile/utils_test.go +++ b/precompile/utils_test.go @@ -6,6 +6,8 @@ package precompile import ( "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" "gotest.tools/assert" ) @@ -64,3 +66,11 @@ func TestFunctionSignatureRegex(t *testing.T) { assert.Equal(t, test.pass, functionSignatureRegex.MatchString(test.str), "unexpected result for %q", test.str) } } + +func TestHashBool(t *testing.T) { + require.Equal(t, hashTrue, boolToHash(true)) + require.Equal(t, common.Hash{}, boolToHash(false)) + require.Equal(t, true, hashToBool(hashTrue)) + require.Equal(t, false, hashToBool(common.Hash{})) + require.Equal(t, false, hashToBool(common.HexToHash("0x012"))) +} From 718440d050fc326bf6ae0f0d31749d61455984a9 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 20 Sep 2022 19:46:59 +0300 Subject: [PATCH 23/64] add reward upgrade to block verification --- params/config.go | 4 ++-- plugin/evm/block_verification.go | 10 +++++++--- plugin/evm/vm.go | 5 ++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/params/config.go b/params/config.go index 4e1c78fa60..18d3cf6fde 100644 --- a/params/config.go +++ b/params/config.go @@ -499,7 +499,7 @@ type Rules struct { IsContractNativeMinterEnabled bool IsTxAllowListEnabled bool IsFeeConfigManagerEnabled bool - IsRewardConfigManagerEnabled bool + IsRewardManagerEnabled bool // ADD YOUR PRECOMPILE HERE // Is{YourPrecompile}Enabled bool @@ -539,7 +539,7 @@ func (c *ChainConfig) AvalancheRules(blockNum, blockTimestamp *big.Int) Rules { rules.IsContractNativeMinterEnabled = c.IsContractNativeMinter(blockTimestamp) rules.IsTxAllowListEnabled = c.IsTxAllowList(blockTimestamp) rules.IsFeeConfigManagerEnabled = c.IsFeeConfigManager(blockTimestamp) - rules.IsRewardConfigManagerEnabled = c.IsRewardConfigManager(blockTimestamp) + rules.IsRewardManagerEnabled = c.IsRewardManager(blockTimestamp) // ADD YOUR PRECOMPILE HERE // rules.Is{YourPrecompile}Enabled = c.{IsYourPrecompile}(blockTimestamp) diff --git a/plugin/evm/block_verification.go b/plugin/evm/block_verification.go index 7002f12e0c..5bd53dd662 100644 --- a/plugin/evm/block_verification.go +++ b/plugin/evm/block_verification.go @@ -112,6 +112,7 @@ func (v blockValidatorLegacy) SyntacticVerify(b *Block) error { type blockValidatorSubnetEVM struct { feeConfigManagerEnabled bool + rewardManagerEnabled bool } func (v blockValidatorSubnetEVM) SyntacticVerify(b *Block) error { @@ -186,9 +187,12 @@ func (v blockValidatorSubnetEVM) SyntacticVerify(b *Block) error { if uncleHash != ethHeader.UncleHash { return errUncleHashMismatch } - // Coinbase must be zero, if AllowFeeRecipients is not enabled - if !b.vm.chainConfig.AllowFeeRecipients && b.ethBlock.Coinbase() != constants.BlackholeAddr { - return errInvalidBlock + // reward manager is enabled, Coinbase depends on state. State is not available here, so skip checking the coinbase here. + if !v.rewardManagerEnabled { + // Coinbase must be zero, if AllowFeeRecipients is not enabled + if !b.vm.chainConfig.AllowFeeRecipients && b.ethBlock.Coinbase() != constants.BlackholeAddr { + return errInvalidBlock + } } // Block must not have any uncles if len(b.ethBlock.Uncles()) > 0 { diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 7277e66607..8f375c0707 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -799,7 +799,10 @@ func (vm *VM) currentRules() params.Rules { // follows the ruleset defined by [rules] func (vm *VM) getBlockValidator(rules params.Rules) BlockValidator { if rules.IsSubnetEVM { - return blockValidatorSubnetEVM{feeConfigManagerEnabled: rules.IsFeeConfigManagerEnabled} + return blockValidatorSubnetEVM{ + feeConfigManagerEnabled: rules.IsFeeConfigManagerEnabled, + rewardManagerEnabled: rules.IsRewardManagerEnabled, + } } return legacyBlockValidator From 9d74ff44553c74dde2eeec44aa49451460c9df6b Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 20 Sep 2022 19:47:24 +0300 Subject: [PATCH 24/64] handle empty case in configure --- precompile/reward_manager.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go index 36f90541ce..37bc913fdb 100644 --- a/precompile/reward_manager.go +++ b/precompile/reward_manager.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/ava-labs/subnet-evm/accounts/abi" + "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" @@ -47,6 +48,7 @@ var ( ErrCannotSetRewardAddress = errors.New("non-enabled cannot setRewardAddress") ErrCannotEnableBothRewards = errors.New("cannot enable both fee recipients and reward address at the same time") + ErrCannotEmptyRewardAddr = errors.New("cannot set empty reward address") ErrEmptyRewardAddress = errors.New("reward address cannot be empty") RewardManagerABI abi.ABI // will be initialized by init function @@ -157,8 +159,12 @@ func (c *RewardManagerConfig) Configure(_ ChainConfig, state StateDB, _ BlockCon // set the initial reward config if c.InitialRewardConfig.AllowFeeRecipients { StoreAllowFeeRecipients(state, true) - } else if c.InitialRewardConfig.RewardAddress != (common.Address{}) { - StoreRewardAddress(state, c.InitialRewardConfig.RewardAddress) + } else { + if c.InitialRewardConfig.RewardAddress == (common.Address{}) { + StoreRewardAddress(state, constants.BlackholeAddr) + } else { + StoreRewardAddress(state, c.InitialRewardConfig.RewardAddress) + } } } } @@ -433,7 +439,7 @@ func disableRewards(accessibleState PrecompileAccessibleState, caller common.Add // reset stored reward address only if it's already set to non empty address if GetStoredRewardAddress(stateDB) != (common.Address{}) { - StoreRewardAddress(stateDB, common.Address{}) + StoreRewardAddress(stateDB, constants.BlackholeAddr) } // this function does not return an output, leave this one as is packedOutput := []byte{} From dd7ef824fc548f4cc92ff355e456119cf2361518 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Mon, 26 Sep 2022 17:32:38 +0300 Subject: [PATCH 25/64] use single field to decide allow fee recipieints --- params/config.go | 7 ++- precompile/contract.go | 2 + precompile/reward_manager.go | 91 ++++++++++++++++++------------------ 3 files changed, 53 insertions(+), 47 deletions(-) diff --git a/params/config.go b/params/config.go index 18d3cf6fde..1bd24dc1ca 100644 --- a/params/config.go +++ b/params/config.go @@ -555,7 +555,12 @@ func (c *ChainConfig) AvalancheRules(blockNum, blockTimestamp *big.Int) Rules { return rules } -// GetFeeConfig returns the FeeConfig +// GetFeeConfig implements precompile.ChainConfig interface. func (c *ChainConfig) GetFeeConfig() commontype.FeeConfig { return c.FeeConfig } + +// AllowedFeeRecipients implements precompile.ChainConfig interface. +func (c *ChainConfig) AllowedFeeRecipients() bool { + return c.AllowFeeRecipients +} diff --git a/precompile/contract.go b/precompile/contract.go index d7b81a8873..5d7e10a6bb 100644 --- a/precompile/contract.go +++ b/precompile/contract.go @@ -37,6 +37,8 @@ type BlockContext interface { type ChainConfig interface { // GetFeeConfig returns the original FeeConfig that was set in the genesis. GetFeeConfig() commontype.FeeConfig + // AllowedFeeRecipients returns true if fee recipients are allowed in the genesis. + AllowedFeeRecipients() bool } // StateDB is the interface for accessing EVM state diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go index 37bc913fdb..a6a24be29b 100644 --- a/precompile/reward_manager.go +++ b/precompile/reward_manager.go @@ -54,8 +54,8 @@ var ( RewardManagerABI abi.ABI // will be initialized by init function RewardManagerPrecompile StatefulPrecompiledContract // will be initialized by init function - allowFeeRecipientsStorageKey = common.Hash{'a', 'f', 'r', 's', 'k'} - rewardAddressStorageKey = common.Hash{'r', 'a', 's', 'k'} + rewardAddressStorageKey = common.Hash{'r', 'a', 's', 'k'} + allowFeeRecipientsAddressValue = common.Address{'a', 'f', 'r', 'a', 'v'} ) type InitialRewardConfig struct { @@ -151,21 +151,30 @@ func (c *RewardManagerConfig) Address() common.Address { } // Configure configures [state] with the initial configuration. -func (c *RewardManagerConfig) Configure(_ ChainConfig, state StateDB, _ BlockContext) { +func (c *RewardManagerConfig) Configure(chainConfig ChainConfig, state StateDB, _ BlockContext) { c.AllowListConfig.Configure(state, RewardManagerAddress) // CUSTOM CODE STARTS HERE - // configure the RewardManager with the initial configuration + // configure the RewardManager with the given initial configuration if c.InitialRewardConfig != nil { - // set the initial reward config + // enable allow fee recipients if c.InitialRewardConfig.AllowFeeRecipients { - StoreAllowFeeRecipients(state, true) - } else { - if c.InitialRewardConfig.RewardAddress == (common.Address{}) { - StoreRewardAddress(state, constants.BlackholeAddr) - } else { - StoreRewardAddress(state, c.InitialRewardConfig.RewardAddress) + EnableAllowFeeRecipients(state) + return + } + // set the initial reward address if specified + if c.InitialRewardConfig.RewardAddress != (common.Address{}) { + if err := StoreRewardAddress(state, c.InitialRewardConfig.RewardAddress); err != nil { + panic(err) } + return + } + DisableFeeRewards(state) + } else { // configure the RewardManager according to chainConfig + if chainConfig.AllowedFeeRecipients() { + EnableAllowFeeRecipients(state) + return } + DisableFeeRewards(state) } } @@ -201,15 +210,14 @@ func PackAllowFeeRecipients() ([]byte, error) { return RewardManagerABI.Pack("allowFeeRecipients") } -// GetStoredAllowFeeRecipients returns the current value of the stored allowFeeRecipients flag. -func GetStoredAllowFeeRecipients(stateDB StateDB) bool { - val := stateDB.GetState(RewardManagerAddress, allowFeeRecipientsStorageKey) - return hashToBool(val) +// EnableAllowFeeRecipients enables fee recipients. +func EnableAllowFeeRecipients(stateDB StateDB) { + stateDB.SetState(RewardManagerAddress, rewardAddressStorageKey, allowFeeRecipientsAddressValue.Hash()) } -// StoreAllowFeeRecipients stores the given [val] under allowFeeRecipientsStoragekey. -func StoreAllowFeeRecipients(stateDB StateDB, val bool) { - stateDB.SetState(RewardManagerAddress, allowFeeRecipientsStorageKey, boolToHash(val)) +// DisableRewardAddress disables rewards and burns them by sending to Blackhole Address. +func DisableFeeRewards(stateDB StateDB) { + stateDB.SetState(RewardManagerAddress, rewardAddressStorageKey, constants.BlackholeAddr.Hash()) } func allowFeeRecipients(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { @@ -234,11 +242,7 @@ func allowFeeRecipients(accessibleState PrecompileAccessibleState, caller common // CUSTOM CODE STARTS HERE // this function does not return an output, leave this one as is - if GetStoredRewardAddress(stateDB) != (common.Address{}) { - // reset stored reward address first - StoreRewardAddress(stateDB, common.Address{}) - } - StoreAllowFeeRecipients(stateDB, true) + EnableAllowFeeRecipients(stateDB) packedOutput := []byte{} // Return the packed output and the remaining gas @@ -279,7 +283,8 @@ func areFeeRecipientsAllowed(accessibleState PrecompileAccessibleState, caller c // CUSTOM CODE STARTS HERE var output bool // CUSTOM CODE FOR AN OUTPUT - output = GetStoredAllowFeeRecipients(stateDB) + _, output = GetStoredRewardAddress(stateDB) + packedOutput, err := PackAreFeeRecipientsAllowedOutput(output) if err != nil { return nil, remainingGas, err @@ -302,14 +307,23 @@ func PackCurrentRewardAddressOutput(rewardAddress common.Address) ([]byte, error } // GetStoredRewardAddress returns the current value of the address stored under rewardAddressStorageKey. -func GetStoredRewardAddress(stateDB StateDB) common.Address { +// Returns true if allow fee recipients is enabled, otherwise returns current reward address. +func GetStoredRewardAddress(stateDB StateDB) (common.Address, bool) { val := stateDB.GetState(RewardManagerAddress, rewardAddressStorageKey) - return common.BytesToAddress(val.Bytes()) + if val == allowFeeRecipientsAddressValue.Hash() { + return common.Address{}, true + } + return common.BytesToAddress(val.Bytes()), false } // StoredRewardAddress stores the given [val] under rewardAddressStorageKey. -func StoreRewardAddress(stateDB StateDB, val common.Address) { +func StoreRewardAddress(stateDB StateDB, val common.Address) error { + // if input is empty, return an error + if val == (common.Address{}) { + return ErrEmptyRewardAddress + } stateDB.SetState(RewardManagerAddress, rewardAddressStorageKey, val.Hash()) + return nil } // PackSetRewardAddress packs [addr] of type common.Address into the appropriate arguments for setRewardAddress. @@ -357,16 +371,9 @@ func setRewardAddress(accessibleState PrecompileAccessibleState, caller common.A // allow list code ends here. // CUSTOM CODE STARTS HERE - // if input is empty, return an error - if inputStruct == (common.Address{}) { - return nil, remainingGas, ErrEmptyRewardAddress - } - // reset stored allow fee recipients flag only if it's already set to true - if GetStoredAllowFeeRecipients(stateDB) { - StoreAllowFeeRecipients(stateDB, false) + if err := StoreRewardAddress(stateDB, inputStruct); err != nil { + return nil, remainingGas, err } - - StoreRewardAddress(stateDB, inputStruct) // this function does not return an output, leave this one as is packedOutput := []byte{} @@ -395,7 +402,7 @@ func currentRewardAddress(accessibleState PrecompileAccessibleState, caller comm // allow list code ends here. // CUSTOM CODE STARTS HERE - output := GetStoredRewardAddress(stateDB) + output, _ := GetStoredRewardAddress(stateDB) packedOutput, err := PackCurrentRewardAddressOutput(output) if err != nil { return nil, remainingGas, err @@ -432,15 +439,7 @@ func disableRewards(accessibleState PrecompileAccessibleState, caller common.Add // allow list code ends here. // CUSTOM CODE STARTS HERE - // reset stored allow fee recipients flag only if it's already set to true - if GetStoredAllowFeeRecipients(stateDB) { - StoreAllowFeeRecipients(stateDB, false) - } - - // reset stored reward address only if it's already set to non empty address - if GetStoredRewardAddress(stateDB) != (common.Address{}) { - StoreRewardAddress(stateDB, constants.BlackholeAddr) - } + DisableFeeRewards(stateDB) // this function does not return an output, leave this one as is packedOutput := []byte{} From 5ed8a4e7e39df342b3fc312e806d3fcf76da2704 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 6 Oct 2022 17:04:00 +0300 Subject: [PATCH 26/64] add coinbase verification --- consensus/consensus.go | 3 +++ consensus/dummy/consensus.go | 24 ++++++++++++++++++++++++ core/blockchain_reader.go | 26 ++++++++++++++++++++++++++ core/chain_makers.go | 5 +++++ plugin/evm/block_verification.go | 7 ++++--- precompile/reward_manager.go | 2 +- vmerrs/vmerrs.go | 1 + 7 files changed, 64 insertions(+), 4 deletions(-) diff --git a/consensus/consensus.go b/consensus/consensus.go index f1b03a7f3c..5bbf7f4108 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -57,6 +57,9 @@ type ChainHeaderReader interface { // GetFeeConfigAt retrieves the fee config and last changed block number at block header. GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) + + // GetCoinbaseAt retrieves the configured coinbase address at [header]. If fee recipients are allowed, returns true in the second return value. + GetCoinbaseAt(header *types.Header) (common.Address, bool, error) } // ChainReader defines a small collection of methods needed to access the local diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index 9f32da98ac..d28757a180 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -16,6 +16,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/trie" + "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" ) @@ -69,6 +70,25 @@ func NewFullFaker() *DummyEngine { } } +// verifyCoinbase checks that the coinbase is valid for the given [parent]. +func (self *DummyEngine) verifyCoinbase(config *params.ChainConfig, parent *types.Header, chain consensus.ChainHeaderReader) error { + // get the coinbase configured at parent + address, isAllowFeeRecipients, err := chain.GetCoinbaseAt(parent) + if err != nil { + return err + } + + if isAllowFeeRecipients { + // if fee recipients are allowed we don't need to check the coinbase + return nil + } + + if address != parent.Coinbase { + return fmt.Errorf("%w: %v does not match configured coinbase address %v", vmerrs.ErrInvalidCoinbase, parent.Coinbase, address) + } + return nil +} + func (self *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, header *types.Header, parent *types.Header, chain consensus.ChainHeaderReader) error { timestamp := new(big.Int).SetUint64(header.Time) @@ -170,6 +190,10 @@ func (self *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header if len(header.Extra) != expectedExtraDataSize { return fmt.Errorf("expected extra-data field to be: %d, but found %d", expectedExtraDataSize, len(header.Extra)) } + // Ensure that coinbase is valid + if err := self.verifyCoinbase(config, parent, chain); err != nil { + return err + } } // Ensure gas-related header fields are correct if err := self.verifyHeaderGasFields(config, header, parent, chain); err != nil { diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index b5fb3acd36..e9a9ee35e9 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -32,6 +32,7 @@ import ( "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus" + "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/core/state/snapshot" @@ -388,3 +389,28 @@ func (bc *BlockChain) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig bc.feeConfigCache.Add(parent.Root, cacheable) return storedFeeConfig, lastChangedAt, nil } + +// GetCoinbaseAt returns the configured coinbase address at [parent]. If fee recipients are allowed, returns true in the second return value. +func (bc *BlockChain) GetCoinbaseAt(parent *types.Header) (common.Address, bool, error) { + config := bc.Config() + bigTime := new(big.Int).SetUint64(parent.Time) + + if !config.IsSubnetEVM(bigTime) { + return constants.BlackholeAddr, false, nil + } + + if !config.IsRewardManager(bigTime) { + if bc.chainConfig.AllowFeeRecipients { + return common.Address{}, true, nil + } else { + return constants.BlackholeAddr, false, nil + } + } + + stateDB, err := bc.StateAt(parent.Root) + if err != nil { + return common.Address{}, false, err + } + rewardAddress, feeRecipients := precompile.GetStoredRewardAddress(stateDB) + return rewardAddress, feeRecipients, nil +} diff --git a/core/chain_makers.go b/core/chain_makers.go index c213e2d47c..2fda72e92a 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -33,6 +33,7 @@ import ( "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus" "github.com/ava-labs/subnet-evm/consensus/dummy" + "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" @@ -321,3 +322,7 @@ func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Bloc func (cr *fakeChainReader) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) { return cr.config.FeeConfig, nil, nil } + +func (cr *fakeChainReader) GetCoinbaseAt(header *types.Header) (common.Address, bool, error) { + return constants.BlackholeAddr, cr.config.AllowFeeRecipients, nil +} diff --git a/plugin/evm/block_verification.go b/plugin/evm/block_verification.go index 5bd53dd662..d2fc2421a4 100644 --- a/plugin/evm/block_verification.go +++ b/plugin/evm/block_verification.go @@ -13,6 +13,7 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/trie" + "github.com/ava-labs/subnet-evm/vmerrs" ) var ( @@ -187,11 +188,11 @@ func (v blockValidatorSubnetEVM) SyntacticVerify(b *Block) error { if uncleHash != ethHeader.UncleHash { return errUncleHashMismatch } - // reward manager is enabled, Coinbase depends on state. State is not available here, so skip checking the coinbase here. + // if reward manager is enabled, Coinbase depends on state. State is not available here, so skip checking the coinbase here. if !v.rewardManagerEnabled { - // Coinbase must be zero, if AllowFeeRecipients is not enabled + // If AllowFeeRecipients is not enabled, Coinbase must be BlackholeAddr. if !b.vm.chainConfig.AllowFeeRecipients && b.ethBlock.Coinbase() != constants.BlackholeAddr { - return errInvalidBlock + return fmt.Errorf("%w: %v does not match required blackhole address %v", vmerrs.ErrInvalidCoinbase, ethHeader.Coinbase, constants.BlackholeAddr) } } // Block must not have any uncles diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go index a6a24be29b..06ae1fdf74 100644 --- a/precompile/reward_manager.go +++ b/precompile/reward_manager.go @@ -307,7 +307,7 @@ func PackCurrentRewardAddressOutput(rewardAddress common.Address) ([]byte, error } // GetStoredRewardAddress returns the current value of the address stored under rewardAddressStorageKey. -// Returns true if allow fee recipients is enabled, otherwise returns current reward address. +// Returns an empty address and true if allow fee recipients is enabled, otherwise returns current reward address and false. func GetStoredRewardAddress(stateDB StateDB) (common.Address, bool) { val := stateDB.GetState(RewardManagerAddress, rewardAddressStorageKey) if val == allowFeeRecipientsAddressValue.Hash() { diff --git a/vmerrs/vmerrs.go b/vmerrs/vmerrs.go index 6103aca03c..5ab30248ff 100644 --- a/vmerrs/vmerrs.go +++ b/vmerrs/vmerrs.go @@ -46,4 +46,5 @@ var ( ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") ErrAddrProhibited = errors.New("prohibited address cannot be sender or created contract address") + ErrInvalidCoinbase = errors.New("invalid coinbase") ) From c1428283c770819bb6abf481fee2cb120687bdcf Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 6 Oct 2022 17:04:23 +0300 Subject: [PATCH 27/64] configure miner with coinbase in state accordingly --- miner/worker.go | 19 +++++++++++++++++-- plugin/evm/vm.go | 16 ++++++---------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index a7cb9161b4..3508208f42 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -162,7 +162,22 @@ func (w *worker) commitNewWork() (*types.Block, error) { if w.coinbase == (common.Address{}) { return nil, errors.New("cannot mine without etherbase") } + header.Coinbase = w.coinbase + + configuredCoinbase, isAllowFeeRecipient, err := w.chain.GetCoinbaseAt(parent.Header()) + if err != nil { + return nil, fmt.Errorf("failed to get configured coinbase: %w", err) + } + + // if fee recipients are not allowed, then the coinbase is the configured coinbase + // don't set w.coinbase directly to the configured coinbase because that would override the + // coinbase set by the user + if !isAllowFeeRecipient && w.coinbase != configuredCoinbase { + log.Info("fee recipients are not allowed, using required coinbase for the mining", "currentminer", w.coinbase, "required", configuredCoinbase) + header.Coinbase = configuredCoinbase + } + if err := w.engine.Prepare(w.chain, header); err != nil { return nil, fmt.Errorf("failed to prepare header for mining: %w", err) } @@ -188,11 +203,11 @@ func (w *worker) commitNewWork() (*types.Block, error) { } if len(localTxs) > 0 { txs := types.NewTransactionsByPriceAndNonce(env.signer, localTxs, header.BaseFee) - w.commitTransactions(env, txs, w.coinbase) + w.commitTransactions(env, txs, header.Coinbase) } if len(remoteTxs) > 0 { txs := types.NewTransactionsByPriceAndNonce(env.signer, remoteTxs, header.BaseFee) - w.commitTransactions(env, txs, w.coinbase) + w.commitTransactions(env, txs, header.Coinbase) } return w.commit(env) diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 8f375c0707..3617fb7d43 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -308,17 +308,13 @@ func (vm *VM) Initialize( } // Handle custom fee recipient - vm.ethConfig.Miner.Etherbase = constants.BlackholeAddr if common.IsHexAddress(vm.config.FeeRecipient) { - if g.Config.AllowFeeRecipients { - address := common.HexToAddress(vm.config.FeeRecipient) - log.Info("Setting fee recipient", "address", address) - vm.ethConfig.Miner.Etherbase = address - } else { - return errors.New("cannot specify a custom fee recipient on this blockchain") - } - } else if g.Config.AllowFeeRecipients { - log.Warn("Chain enabled `AllowFeeRecipients`, but chain config has not specified any coinbase address. Defaulting to the blackhole address.") + address := common.HexToAddress(vm.config.FeeRecipient) + log.Info("Setting fee recipient", "address", address) + vm.ethConfig.Miner.Etherbase = address + } else { + log.Warn("Config has not specified any coinbase address. Defaulting to the blackhole address.") + vm.ethConfig.Miner.Etherbase = constants.BlackholeAddr } vm.chainConfig = g.Config From 89baa42f4166a674e66a7eb310b9e533b9048b72 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 6 Oct 2022 18:34:50 +0300 Subject: [PATCH 28/64] Remove abis --- abis/IAllowList.abi | 1 - abis/IRewardManager.abi | 1 - 2 files changed, 2 deletions(-) delete mode 100644 abis/IAllowList.abi delete mode 100644 abis/IRewardManager.abi diff --git a/abis/IAllowList.abi b/abis/IAllowList.abi deleted file mode 100644 index 7f1f8e56b3..0000000000 --- a/abis/IAllowList.abi +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/abis/IRewardManager.abi b/abis/IRewardManager.abi deleted file mode 100644 index add3bbc1d9..0000000000 --- a/abis/IRewardManager.abi +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[],"name":"allowFeeRecipients","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"areFeeRecipientsAllowed","outputs":[{"internalType":"bool","name":"isAllowed","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"currentRewardAddress","outputs":[{"internalType":"address","name":"rewardAddress","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"disableRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setRewardAddress","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file From 034cd0d594c62c37a6ee0d1c2febdcecda730958 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 6 Oct 2022 18:43:22 +0300 Subject: [PATCH 29/64] use header coinbase for verification --- consensus/dummy/consensus.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index aa8867b4c5..b8275449a2 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -70,10 +70,10 @@ func NewFullFaker() *DummyEngine { } } -// verifyCoinbase checks that the coinbase is valid for the given [parent]. -func (self *DummyEngine) verifyCoinbase(config *params.ChainConfig, parent *types.Header, chain consensus.ChainHeaderReader) error { +// verifyCoinbase checks that the coinbase is valid for the given [header] and [parent]. +func (self *DummyEngine) verifyCoinbase(config *params.ChainConfig, header *types.Header, parent *types.Header, chain consensus.ChainHeaderReader) error { // get the coinbase configured at parent - address, isAllowFeeRecipients, err := chain.GetCoinbaseAt(parent) + configuredAddressAtParent, isAllowFeeRecipients, err := chain.GetCoinbaseAt(parent) if err != nil { return err } @@ -82,9 +82,10 @@ func (self *DummyEngine) verifyCoinbase(config *params.ChainConfig, parent *type // if fee recipients are allowed we don't need to check the coinbase return nil } - - if address != parent.Coinbase { - return fmt.Errorf("%w: %v does not match configured coinbase address %v", vmerrs.ErrInvalidCoinbase, parent.Coinbase, address) + // we fetch the configured coinbase at the parent's state + // but we check the header coinbase against this coinfigured coinbase + if configuredAddressAtParent != header.Coinbase { + return fmt.Errorf("%w: %v does not match configured coinbase address %v", vmerrs.ErrInvalidCoinbase, header.Coinbase, configuredAddressAtParent) } return nil } @@ -191,7 +192,7 @@ func (self *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header return fmt.Errorf("expected extra-data field to be: %d, but found %d", expectedExtraDataSize, len(header.Extra)) } // Ensure that coinbase is valid - if err := self.verifyCoinbase(config, parent, chain); err != nil { + if err := self.verifyCoinbase(config, header, parent, chain); err != nil { return err } } From c33b269fd7e95865bcd762cf6d8cd521371ff1f1 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 6 Oct 2022 18:44:31 +0300 Subject: [PATCH 30/64] remove hashToBool util methods --- precompile/utils.go | 13 ------------- precompile/utils_test.go | 10 ---------- 2 files changed, 23 deletions(-) diff --git a/precompile/utils.go b/precompile/utils.go index 0f6f726381..2476a97e34 100644 --- a/precompile/utils.go +++ b/precompile/utils.go @@ -14,8 +14,6 @@ import ( var functionSignatureRegex = regexp.MustCompile(`[\w]+\(((([\w]+)?)|((([\w]+),)+([\w]+)))\)`) -var hashTrue = common.HexToHash("1") - // CalculateFunctionSelector returns the 4 byte function selector that results from [functionSignature] // Ex. the function setBalance(addr address, balance uint256) should be passed in as the string: // "setBalance(address,uint256)" @@ -69,14 +67,3 @@ func returnPackedHash(packed []byte, index int) []byte { end := start + common.HashLength return packed[start:end] } - -func hashToBool(hash common.Hash) bool { - return hash == hashTrue -} - -func boolToHash(bl bool) common.Hash { - if bl { - return hashTrue - } - return common.Hash{} -} diff --git a/precompile/utils_test.go b/precompile/utils_test.go index 9dbcb67f51..ff6d43486b 100644 --- a/precompile/utils_test.go +++ b/precompile/utils_test.go @@ -6,8 +6,6 @@ package precompile import ( "testing" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" "gotest.tools/assert" ) @@ -66,11 +64,3 @@ func TestFunctionSignatureRegex(t *testing.T) { assert.Equal(t, test.pass, functionSignatureRegex.MatchString(test.str), "unexpected result for %q", test.str) } } - -func TestHashBool(t *testing.T) { - require.Equal(t, hashTrue, boolToHash(true)) - require.Equal(t, common.Hash{}, boolToHash(false)) - require.Equal(t, true, hashToBool(hashTrue)) - require.Equal(t, false, hashToBool(common.Hash{})) - require.Equal(t, false, hashToBool(common.HexToHash("0x012"))) -} From 263ad503d09c1fae237739bd64ae527d99403b7e Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 11 Oct 2022 13:19:56 +0300 Subject: [PATCH 31/64] use require and remove unneccessary address field from test --- core/stateful_precompile_test.go | 290 +++++++++++++------------------ 1 file changed, 118 insertions(+), 172 deletions(-) diff --git a/core/stateful_precompile_test.go b/core/stateful_precompile_test.go index 23d5b0b0ed..b343808136 100644 --- a/core/stateful_precompile_test.go +++ b/core/stateful_precompile_test.go @@ -16,7 +16,7 @@ import ( "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var ( @@ -60,11 +60,10 @@ func (m *mockAccessibleState) GetBlockContext() precompile.BlockContext { return // without creating any import cycles func TestContractDeployerAllowListRun(t *testing.T) { type test struct { - caller common.Address - precompileAddr common.Address - input func() []byte - suppliedGas uint64 - readOnly bool + caller common.Address + input func() []byte + suppliedGas uint64 + readOnly bool expectedRes []byte expectedErr string @@ -77,8 +76,7 @@ func TestContractDeployerAllowListRun(t *testing.T) { for name, test := range map[string]test{ "set admin": { - caller: adminAddr, - precompileAddr: precompile.ContractDeployerAllowListAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListAdmin) if err != nil { @@ -91,12 +89,11 @@ func TestContractDeployerAllowListRun(t *testing.T) { expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { res := precompile.GetContractDeployerAllowListStatus(state, noRoleAddr) - assert.Equal(t, precompile.AllowListAdmin, res) + require.Equal(t, precompile.AllowListAdmin, res) }, }, "set deployer": { - caller: adminAddr, - precompileAddr: precompile.ContractDeployerAllowListAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) if err != nil { @@ -109,12 +106,11 @@ func TestContractDeployerAllowListRun(t *testing.T) { expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { res := precompile.GetContractDeployerAllowListStatus(state, noRoleAddr) - assert.Equal(t, precompile.AllowListEnabled, res) + require.Equal(t, precompile.AllowListEnabled, res) }, }, "set no role": { - caller: adminAddr, - precompileAddr: precompile.ContractDeployerAllowListAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) if err != nil { @@ -127,12 +123,11 @@ func TestContractDeployerAllowListRun(t *testing.T) { expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { res := precompile.GetContractDeployerAllowListStatus(state, adminAddr) - assert.Equal(t, precompile.AllowListNoRole, res) + require.Equal(t, precompile.AllowListNoRole, res) }, }, "set no role from non-admin": { - caller: noRoleAddr, - precompileAddr: precompile.ContractDeployerAllowListAddress, + caller: noRoleAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) if err != nil { @@ -145,8 +140,7 @@ func TestContractDeployerAllowListRun(t *testing.T) { expectedErr: precompile.ErrCannotModifyAllowList.Error(), }, "set deployer from non-admin": { - caller: noRoleAddr, - precompileAddr: precompile.ContractDeployerAllowListAddress, + caller: noRoleAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListEnabled) if err != nil { @@ -159,8 +153,7 @@ func TestContractDeployerAllowListRun(t *testing.T) { expectedErr: precompile.ErrCannotModifyAllowList.Error(), }, "set admin from non-admin": { - caller: noRoleAddr, - precompileAddr: precompile.ContractDeployerAllowListAddress, + caller: noRoleAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListAdmin) if err != nil { @@ -173,8 +166,7 @@ func TestContractDeployerAllowListRun(t *testing.T) { expectedErr: precompile.ErrCannotModifyAllowList.Error(), }, "set no role with readOnly enabled": { - caller: adminAddr, - precompileAddr: precompile.ContractDeployerAllowListAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) if err != nil { @@ -187,8 +179,7 @@ func TestContractDeployerAllowListRun(t *testing.T) { expectedErr: vmerrs.ErrWriteProtection.Error(), }, "set no role insufficient gas": { - caller: adminAddr, - precompileAddr: precompile.ContractDeployerAllowListAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) if err != nil { @@ -201,8 +192,7 @@ func TestContractDeployerAllowListRun(t *testing.T) { expectedErr: vmerrs.ErrOutOfGas.Error(), }, "read allow list no role": { - caller: noRoleAddr, - precompileAddr: precompile.ContractDeployerAllowListAddress, + caller: noRoleAddr, input: func() []byte { return precompile.PackReadAllowList(noRoleAddr) }, @@ -212,8 +202,7 @@ func TestContractDeployerAllowListRun(t *testing.T) { assertState: nil, }, "read allow list admin role": { - caller: adminAddr, - precompileAddr: precompile.ContractDeployerAllowListAddress, + caller: adminAddr, input: func() []byte { return precompile.PackReadAllowList(noRoleAddr) }, @@ -223,8 +212,7 @@ func TestContractDeployerAllowListRun(t *testing.T) { assertState: nil, }, "read allow list with readOnly enabled": { - caller: adminAddr, - precompileAddr: precompile.ContractDeployerAllowListAddress, + caller: adminAddr, input: func() []byte { return precompile.PackReadAllowList(noRoleAddr) }, @@ -234,8 +222,7 @@ func TestContractDeployerAllowListRun(t *testing.T) { assertState: nil, }, "read allow list out of gas": { - caller: adminAddr, - precompileAddr: precompile.ContractDeployerAllowListAddress, + caller: adminAddr, input: func() []byte { return precompile.PackReadAllowList(noRoleAddr) }, @@ -254,16 +241,16 @@ 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)) + require.Equal(t, precompile.AllowListAdmin, precompile.GetContractDeployerAllowListStatus(state, adminAddr)) + require.Equal(t, precompile.AllowListNoRole, precompile.GetContractDeployerAllowListStatus(state, noRoleAddr)) blockContext := &mockBlockContext{blockNumber: common.Big0} - ret, remainingGas, err := precompile.ContractDeployerAllowListPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext}, test.caller, test.precompileAddr, test.input(), test.suppliedGas, test.readOnly) + ret, remainingGas, err := precompile.ContractDeployerAllowListPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext}, test.caller, precompile.ContractDeployerAllowListAddress, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { if err == nil { - assert.Failf(t, "run expectedly passed without error", "expected error %q", test.expectedErr) + require.Failf(t, "run expectedly passed without error", "expected error %q", test.expectedErr) } else { - assert.True(t, strings.Contains(err.Error(), test.expectedErr), "expected error (%s) to contain substring (%s)", err, test.expectedErr) + require.True(t, strings.Contains(err.Error(), test.expectedErr), "expected error (%s) to contain substring (%s)", err, test.expectedErr) } return } @@ -272,8 +259,8 @@ func TestContractDeployerAllowListRun(t *testing.T) { t.Fatal(err) } - assert.Equal(t, uint64(0), remainingGas) - assert.Equal(t, test.expectedRes, ret) + require.Equal(t, uint64(0), remainingGas) + require.Equal(t, test.expectedRes, ret) if test.assertState != nil { test.assertState(t, state) @@ -301,8 +288,7 @@ func TestTxAllowListRun(t *testing.T) { for name, test := range map[string]test{ "set admin": { - caller: adminAddr, - precompileAddr: precompile.TxAllowListAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListAdmin) if err != nil { @@ -315,12 +301,11 @@ func TestTxAllowListRun(t *testing.T) { expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { res := precompile.GetTxAllowListStatus(state, noRoleAddr) - assert.Equal(t, precompile.AllowListAdmin, res) + require.Equal(t, precompile.AllowListAdmin, res) }, }, "set allowed": { - caller: adminAddr, - precompileAddr: precompile.TxAllowListAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) if err != nil { @@ -333,12 +318,11 @@ func TestTxAllowListRun(t *testing.T) { expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { res := precompile.GetTxAllowListStatus(state, noRoleAddr) - assert.Equal(t, precompile.AllowListEnabled, res) + require.Equal(t, precompile.AllowListEnabled, res) }, }, "set no role": { - caller: adminAddr, - precompileAddr: precompile.TxAllowListAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) if err != nil { @@ -351,12 +335,11 @@ func TestTxAllowListRun(t *testing.T) { expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { res := precompile.GetTxAllowListStatus(state, adminAddr) - assert.Equal(t, precompile.AllowListNoRole, res) + require.Equal(t, precompile.AllowListNoRole, res) }, }, "set no role from non-admin": { - caller: noRoleAddr, - precompileAddr: precompile.TxAllowListAddress, + caller: noRoleAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) if err != nil { @@ -369,8 +352,7 @@ func TestTxAllowListRun(t *testing.T) { expectedErr: precompile.ErrCannotModifyAllowList.Error(), }, "set allowed from non-admin": { - caller: noRoleAddr, - precompileAddr: precompile.TxAllowListAddress, + caller: noRoleAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListEnabled) if err != nil { @@ -383,8 +365,7 @@ func TestTxAllowListRun(t *testing.T) { expectedErr: precompile.ErrCannotModifyAllowList.Error(), }, "set admin from non-admin": { - caller: noRoleAddr, - precompileAddr: precompile.TxAllowListAddress, + caller: noRoleAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListAdmin) if err != nil { @@ -411,8 +392,7 @@ func TestTxAllowListRun(t *testing.T) { expectedErr: vmerrs.ErrWriteProtection.Error(), }, "set no role insufficient gas": { - caller: adminAddr, - precompileAddr: precompile.TxAllowListAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) if err != nil { @@ -425,8 +405,7 @@ func TestTxAllowListRun(t *testing.T) { expectedErr: vmerrs.ErrOutOfGas.Error(), }, "read allow list no role": { - caller: noRoleAddr, - precompileAddr: precompile.TxAllowListAddress, + caller: noRoleAddr, input: func() []byte { return precompile.PackReadAllowList(noRoleAddr) }, @@ -436,8 +415,7 @@ func TestTxAllowListRun(t *testing.T) { assertState: nil, }, "read allow list admin role": { - caller: adminAddr, - precompileAddr: precompile.TxAllowListAddress, + caller: adminAddr, input: func() []byte { return precompile.PackReadAllowList(noRoleAddr) }, @@ -447,8 +425,7 @@ func TestTxAllowListRun(t *testing.T) { assertState: nil, }, "read allow list with readOnly enabled": { - caller: adminAddr, - precompileAddr: precompile.TxAllowListAddress, + caller: adminAddr, input: func() []byte { return precompile.PackReadAllowList(noRoleAddr) }, @@ -458,8 +435,7 @@ func TestTxAllowListRun(t *testing.T) { assertState: nil, }, "read allow list out of gas": { - caller: adminAddr, - precompileAddr: precompile.TxAllowListAddress, + caller: adminAddr, input: func() []byte { return precompile.PackReadAllowList(noRoleAddr) }, @@ -477,15 +453,15 @@ 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)) + require.Equal(t, precompile.AllowListAdmin, precompile.GetTxAllowListStatus(state, adminAddr)) blockContext := &mockBlockContext{blockNumber: common.Big0} - ret, remainingGas, err := precompile.TxAllowListPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext}, test.caller, test.precompileAddr, test.input(), test.suppliedGas, test.readOnly) + ret, remainingGas, err := precompile.TxAllowListPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext}, test.caller, precompile.TxAllowListAddress, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { if err == nil { - assert.Failf(t, "run expectedly passed without error", "expected error %q", test.expectedErr) + require.Failf(t, "run expectedly passed without error", "expected error %q", test.expectedErr) } else { - assert.True(t, strings.Contains(err.Error(), test.expectedErr), "expected error (%s) to contain substring (%s)", err, test.expectedErr) + require.True(t, strings.Contains(err.Error(), test.expectedErr), "expected error (%s) to contain substring (%s)", err, test.expectedErr) } return } @@ -494,8 +470,8 @@ func TestTxAllowListRun(t *testing.T) { t.Fatal(err) } - assert.Equal(t, uint64(0), remainingGas) - assert.Equal(t, test.expectedRes, ret) + require.Equal(t, uint64(0), remainingGas) + require.Equal(t, test.expectedRes, ret) if test.assertState != nil { test.assertState(t, state) @@ -506,12 +482,11 @@ func TestTxAllowListRun(t *testing.T) { func TestContractNativeMinterRun(t *testing.T) { type test struct { - caller common.Address - precompileAddr common.Address - input func() []byte - suppliedGas uint64 - readOnly bool - config *precompile.ContractNativeMinterConfig + caller common.Address + input func() []byte + suppliedGas uint64 + readOnly bool + config *precompile.ContractNativeMinterConfig expectedRes []byte expectedErr string @@ -526,8 +501,7 @@ func TestContractNativeMinterRun(t *testing.T) { for name, test := range map[string]test{ "mint funds from no role fails": { - caller: noRoleAddr, - precompileAddr: precompile.ContractNativeMinterAddress, + caller: noRoleAddr, input: func() []byte { input, err := precompile.PackMintInput(noRoleAddr, common.Big1) if err != nil { @@ -540,8 +514,7 @@ func TestContractNativeMinterRun(t *testing.T) { expectedErr: precompile.ErrCannotMint.Error(), }, "mint funds from enabled address": { - caller: enabledAddr, - precompileAddr: precompile.ContractNativeMinterAddress, + caller: enabledAddr, input: func() []byte { input, err := precompile.PackMintInput(enabledAddr, common.Big1) if err != nil { @@ -553,12 +526,11 @@ func TestContractNativeMinterRun(t *testing.T) { readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - assert.Equal(t, common.Big1, state.GetBalance(enabledAddr), "expected minted funds") + require.Equal(t, common.Big1, state.GetBalance(enabledAddr), "expected minted funds") }, }, "enabled role by config": { - caller: noRoleAddr, - precompileAddr: precompile.ContractNativeMinterAddress, + caller: noRoleAddr, input: func() []byte { return precompile.PackReadAllowList(testAddr) }, @@ -566,15 +538,14 @@ func TestContractNativeMinterRun(t *testing.T) { 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)) + require.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, + caller: enabledAddr, config: &precompile.ContractNativeMinterConfig{ InitialMint: map[common.Address]*math.HexOrDecimal256{ enabledAddr: math.NewHexOrDecimal256(2), @@ -587,12 +558,11 @@ func TestContractNativeMinterRun(t *testing.T) { 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") + require.Equal(t, common.Big2, state.GetBalance(enabledAddr), "expected minted funds") }, }, "mint funds from admin address": { - caller: adminAddr, - precompileAddr: precompile.ContractNativeMinterAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackMintInput(adminAddr, common.Big1) if err != nil { @@ -604,12 +574,11 @@ func TestContractNativeMinterRun(t *testing.T) { readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - assert.Equal(t, common.Big1, state.GetBalance(adminAddr), "expected minted funds") + require.Equal(t, common.Big1, state.GetBalance(adminAddr), "expected minted funds") }, }, "mint max big funds": { - caller: adminAddr, - precompileAddr: precompile.ContractNativeMinterAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackMintInput(adminAddr, math.MaxBig256) if err != nil { @@ -621,12 +590,11 @@ func TestContractNativeMinterRun(t *testing.T) { readOnly: false, expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { - assert.Equal(t, math.MaxBig256, state.GetBalance(adminAddr), "expected minted funds") + require.Equal(t, math.MaxBig256, state.GetBalance(adminAddr), "expected minted funds") }, }, "readOnly mint with noRole fails": { - caller: noRoleAddr, - precompileAddr: precompile.ContractNativeMinterAddress, + caller: noRoleAddr, input: func() []byte { input, err := precompile.PackMintInput(adminAddr, common.Big1) if err != nil { @@ -639,8 +607,7 @@ func TestContractNativeMinterRun(t *testing.T) { expectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly mint with allow role fails": { - caller: enabledAddr, - precompileAddr: precompile.ContractNativeMinterAddress, + caller: enabledAddr, input: func() []byte { input, err := precompile.PackMintInput(enabledAddr, common.Big1) if err != nil { @@ -653,8 +620,7 @@ func TestContractNativeMinterRun(t *testing.T) { expectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly mint with admin role fails": { - caller: adminAddr, - precompileAddr: precompile.ContractNativeMinterAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackMintInput(adminAddr, common.Big1) if err != nil { @@ -667,8 +633,7 @@ func TestContractNativeMinterRun(t *testing.T) { expectedErr: vmerrs.ErrWriteProtection.Error(), }, "insufficient gas mint from admin": { - caller: adminAddr, - precompileAddr: precompile.ContractNativeMinterAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackMintInput(enabledAddr, common.Big1) if err != nil { @@ -681,8 +646,7 @@ func TestContractNativeMinterRun(t *testing.T) { expectedErr: vmerrs.ErrOutOfGas.Error(), }, "read from noRole address": { - caller: noRoleAddr, - precompileAddr: precompile.ContractNativeMinterAddress, + caller: noRoleAddr, input: func() []byte { return precompile.PackReadAllowList(noRoleAddr) }, @@ -692,8 +656,7 @@ func TestContractNativeMinterRun(t *testing.T) { assertState: func(t *testing.T, state *state.StateDB) {}, }, "read from noRole address readOnly enabled": { - caller: noRoleAddr, - precompileAddr: precompile.ContractNativeMinterAddress, + caller: noRoleAddr, input: func() []byte { return precompile.PackReadAllowList(noRoleAddr) }, @@ -703,8 +666,7 @@ func TestContractNativeMinterRun(t *testing.T) { assertState: func(t *testing.T, state *state.StateDB) {}, }, "read from noRole address with insufficient gas": { - caller: noRoleAddr, - precompileAddr: precompile.ContractNativeMinterAddress, + caller: noRoleAddr, input: func() []byte { return precompile.PackReadAllowList(noRoleAddr) }, @@ -713,8 +675,7 @@ func TestContractNativeMinterRun(t *testing.T) { expectedErr: vmerrs.ErrOutOfGas.Error(), }, "set allow role from admin": { - caller: adminAddr, - precompileAddr: precompile.ContractNativeMinterAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) if err != nil { @@ -727,12 +688,11 @@ func TestContractNativeMinterRun(t *testing.T) { expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { res := precompile.GetContractNativeMinterStatus(state, noRoleAddr) - assert.Equal(t, precompile.AllowListEnabled, res) + require.Equal(t, precompile.AllowListEnabled, res) }, }, "set allow role from non-admin fails": { - caller: enabledAddr, - precompileAddr: precompile.ContractNativeMinterAddress, + caller: enabledAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) if err != nil { @@ -755,20 +715,20 @@ func TestContractNativeMinterRun(t *testing.T) { precompile.SetContractNativeMinterStatus(state, adminAddr, precompile.AllowListAdmin) 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)) + require.Equal(t, precompile.AllowListAdmin, precompile.GetContractNativeMinterStatus(state, adminAddr)) + require.Equal(t, precompile.AllowListEnabled, precompile.GetContractNativeMinterStatus(state, enabledAddr)) + require.Equal(t, precompile.AllowListNoRole, precompile.GetContractNativeMinterStatus(state, noRoleAddr)) blockContext := &mockBlockContext{blockNumber: common.Big0} if test.config != nil { test.config.Configure(params.TestChainConfig, state, blockContext) } - ret, remainingGas, err := precompile.ContractNativeMinterPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext}, test.caller, test.precompileAddr, test.input(), test.suppliedGas, test.readOnly) + ret, remainingGas, err := precompile.ContractNativeMinterPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext}, test.caller, precompile.ContractNativeMinterAddress, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { if err == nil { - assert.Failf(t, "run expectedly passed without error", "expected error %q", test.expectedErr) + require.Failf(t, "run expectedly passed without error", "expected error %q", test.expectedErr) } else { - assert.True(t, strings.Contains(err.Error(), test.expectedErr), "expected error (%s) to contain substring (%s)", err, test.expectedErr) + require.True(t, strings.Contains(err.Error(), test.expectedErr), "expected error (%s) to contain substring (%s)", err, test.expectedErr) } return } @@ -777,8 +737,8 @@ func TestContractNativeMinterRun(t *testing.T) { t.Fatal(err) } - assert.Equal(t, uint64(0), remainingGas) - assert.Equal(t, test.expectedRes, ret) + require.Equal(t, uint64(0), remainingGas) + require.Equal(t, test.expectedRes, ret) if test.assertState != nil { test.assertState(t, state) @@ -789,13 +749,12 @@ func TestContractNativeMinterRun(t *testing.T) { func TestFeeConfigManagerRun(t *testing.T) { type test struct { - caller common.Address - precompileAddr common.Address - preCondition func(t *testing.T, state *state.StateDB) - input func() []byte - suppliedGas uint64 - readOnly bool - config *precompile.FeeConfigManagerConfig + caller common.Address + preCondition func(t *testing.T, state *state.StateDB) + input func() []byte + suppliedGas uint64 + readOnly bool + config *precompile.FeeConfigManagerConfig expectedRes []byte expectedErr string @@ -809,8 +768,7 @@ func TestFeeConfigManagerRun(t *testing.T) { for name, test := range map[string]test{ "set config from no role fails": { - caller: noRoleAddr, - precompileAddr: precompile.FeeConfigManagerAddress, + caller: noRoleAddr, input: func() []byte { input, err := precompile.PackSetFeeConfig(testFeeConfig) if err != nil { @@ -823,8 +781,7 @@ func TestFeeConfigManagerRun(t *testing.T) { expectedErr: precompile.ErrCannotChangeFee.Error(), }, "set config from enabled address": { - caller: enabledAddr, - precompileAddr: precompile.FeeConfigManagerAddress, + caller: enabledAddr, input: func() []byte { input, err := precompile.PackSetFeeConfig(testFeeConfig) if err != nil { @@ -837,12 +794,11 @@ func TestFeeConfigManagerRun(t *testing.T) { expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { feeConfig := precompile.GetStoredFeeConfig(state) - assert.Equal(t, testFeeConfig, feeConfig) + require.Equal(t, testFeeConfig, feeConfig) }, }, "set invalid config from enabled address": { - caller: enabledAddr, - precompileAddr: precompile.FeeConfigManagerAddress, + caller: enabledAddr, input: func() []byte { feeConfig := testFeeConfig feeConfig.MinBlockGasCost = new(big.Int).Mul(feeConfig.MaxBlockGasCost, common.Big2) @@ -858,12 +814,11 @@ func TestFeeConfigManagerRun(t *testing.T) { expectedErr: "cannot be greater than maxBlockGasCost", assertState: func(t *testing.T, state *state.StateDB) { feeConfig := precompile.GetStoredFeeConfig(state) - assert.Equal(t, testFeeConfig, feeConfig) + require.Equal(t, testFeeConfig, feeConfig) }, }, "set config from admin address": { - caller: adminAddr, - precompileAddr: precompile.FeeConfigManagerAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackSetFeeConfig(testFeeConfig) if err != nil { @@ -876,14 +831,13 @@ func TestFeeConfigManagerRun(t *testing.T) { expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { feeConfig := precompile.GetStoredFeeConfig(state) - assert.Equal(t, testFeeConfig, feeConfig) + require.Equal(t, testFeeConfig, feeConfig) lastChangedAt := precompile.GetFeeConfigLastChangedAt(state) - assert.EqualValues(t, testBlockNumber, lastChangedAt) + require.EqualValues(t, testBlockNumber, lastChangedAt) }, }, "get fee config from non-enabled address": { - caller: noRoleAddr, - precompileAddr: precompile.FeeConfigManagerAddress, + caller: noRoleAddr, preCondition: func(t *testing.T, state *state.StateDB) { err := precompile.StoreFeeConfig(state, testFeeConfig, &mockBlockContext{blockNumber: big.NewInt(6)}) if err != nil { @@ -897,19 +851,18 @@ func TestFeeConfigManagerRun(t *testing.T) { readOnly: true, expectedRes: func() []byte { res, err := precompile.PackFeeConfig(testFeeConfig) - assert.NoError(t, err) + require.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, big.NewInt(6), lastChangedAt) + require.Equal(t, testFeeConfig, feeConfig) + require.EqualValues(t, big.NewInt(6), lastChangedAt) }, }, "get initial fee config": { - caller: noRoleAddr, - precompileAddr: precompile.FeeConfigManagerAddress, + caller: noRoleAddr, input: func() []byte { return precompile.PackGetFeeConfigInput() }, @@ -920,19 +873,18 @@ func TestFeeConfigManagerRun(t *testing.T) { readOnly: true, expectedRes: func() []byte { res, err := precompile.PackFeeConfig(testFeeConfig) - assert.NoError(t, err) + require.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) + require.Equal(t, testFeeConfig, feeConfig) + require.EqualValues(t, testBlockNumber, lastChangedAt) }, }, "get last changed at from non-enabled address": { - caller: noRoleAddr, - precompileAddr: precompile.FeeConfigManagerAddress, + caller: noRoleAddr, preCondition: func(t *testing.T, state *state.StateDB) { err := precompile.StoreFeeConfig(state, testFeeConfig, &mockBlockContext{blockNumber: testBlockNumber}) if err != nil { @@ -948,13 +900,12 @@ func TestFeeConfigManagerRun(t *testing.T) { assertState: func(t *testing.T, state *state.StateDB) { feeConfig := precompile.GetStoredFeeConfig(state) lastChangedAt := precompile.GetFeeConfigLastChangedAt(state) - assert.Equal(t, testFeeConfig, feeConfig) - assert.Equal(t, testBlockNumber, lastChangedAt) + require.Equal(t, testFeeConfig, feeConfig) + require.Equal(t, testBlockNumber, lastChangedAt) }, }, "readOnly setFeeConfig with noRole fails": { - caller: noRoleAddr, - precompileAddr: precompile.FeeConfigManagerAddress, + caller: noRoleAddr, input: func() []byte { input, err := precompile.PackSetFeeConfig(testFeeConfig) if err != nil { @@ -967,8 +918,7 @@ func TestFeeConfigManagerRun(t *testing.T) { expectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly setFeeConfig with allow role fails": { - caller: enabledAddr, - precompileAddr: precompile.FeeConfigManagerAddress, + caller: enabledAddr, input: func() []byte { input, err := precompile.PackSetFeeConfig(testFeeConfig) if err != nil { @@ -981,8 +931,7 @@ func TestFeeConfigManagerRun(t *testing.T) { expectedErr: vmerrs.ErrWriteProtection.Error(), }, "readOnly setFeeConfig with admin role fails": { - caller: adminAddr, - precompileAddr: precompile.FeeConfigManagerAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackSetFeeConfig(testFeeConfig) if err != nil { @@ -995,8 +944,7 @@ func TestFeeConfigManagerRun(t *testing.T) { expectedErr: vmerrs.ErrWriteProtection.Error(), }, "insufficient gas setFeeConfig from admin": { - caller: adminAddr, - precompileAddr: precompile.FeeConfigManagerAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackSetFeeConfig(testFeeConfig) if err != nil { @@ -1009,8 +957,7 @@ func TestFeeConfigManagerRun(t *testing.T) { expectedErr: vmerrs.ErrOutOfGas.Error(), }, "set allow role from admin": { - caller: adminAddr, - precompileAddr: precompile.FeeConfigManagerAddress, + caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) if err != nil { @@ -1023,12 +970,11 @@ func TestFeeConfigManagerRun(t *testing.T) { expectedRes: []byte{}, assertState: func(t *testing.T, state *state.StateDB) { res := precompile.GetFeeConfigManagerStatus(state, noRoleAddr) - assert.Equal(t, precompile.AllowListEnabled, res) + require.Equal(t, precompile.AllowListEnabled, res) }, }, "set allow role from non-admin fails": { - caller: enabledAddr, - precompileAddr: precompile.FeeConfigManagerAddress, + caller: enabledAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) if err != nil { @@ -1060,12 +1006,12 @@ func TestFeeConfigManagerRun(t *testing.T) { if test.config != nil { test.config.Configure(params.TestChainConfig, state, blockContext) } - ret, remainingGas, err := precompile.FeeConfigManagerPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext}, test.caller, test.precompileAddr, test.input(), test.suppliedGas, test.readOnly) + ret, remainingGas, err := precompile.FeeConfigManagerPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext}, test.caller, precompile.FeeConfigManagerAddress, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { if err == nil { - assert.Failf(t, "run expectedly passed without error", "expected error %q", test.expectedErr) + require.Failf(t, "run expectedly passed without error", "expected error %q", test.expectedErr) } else { - assert.True(t, strings.Contains(err.Error(), test.expectedErr), "expected error (%s) to contain substring (%s)", err, test.expectedErr) + require.True(t, strings.Contains(err.Error(), test.expectedErr), "expected error (%s) to contain substring (%s)", err, test.expectedErr) } return } @@ -1074,8 +1020,8 @@ func TestFeeConfigManagerRun(t *testing.T) { t.Fatal(err) } - assert.Equal(t, uint64(0), remainingGas) - assert.Equal(t, test.expectedRes, ret) + require.Equal(t, uint64(0), remainingGas) + require.Equal(t, test.expectedRes, ret) if test.assertState != nil { test.assertState(t, state) From 4ce6fbde4b4039f3e887e7d86327dc98e0378a68 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 11 Oct 2022 14:59:39 +0300 Subject: [PATCH 32/64] add unit tests --- core/stateful_precompile_test.go | 329 +++++++++++++++++++++++++++++++ plugin/evm/vm_test.go | 140 +++++++++++++ precompile/config_test.go | 85 ++++++++ 3 files changed, 554 insertions(+) diff --git a/core/stateful_precompile_test.go b/core/stateful_precompile_test.go index b343808136..9f46cfdb18 100644 --- a/core/stateful_precompile_test.go +++ b/core/stateful_precompile_test.go @@ -1029,3 +1029,332 @@ func TestFeeConfigManagerRun(t *testing.T) { }) } } + +func TestRewardManagerRun(t *testing.T) { + type test struct { + caller common.Address + preCondition func(t *testing.T, state *state.StateDB) + input func() []byte + suppliedGas uint64 + readOnly bool + config *precompile.RewardManagerConfig + + expectedRes []byte + expectedErr string + + assertState func(t *testing.T, state *state.StateDB) + } + + adminAddr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") + enabledAddr := common.HexToAddress("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B") + noRoleAddr := common.HexToAddress("0xF60C45c607D0f41687c94C314d300f483661E13a") + testAddr := common.HexToAddress("0x0123") + + for name, test := range map[string]test{ + "set allow fee recipients from no role fails": { + caller: noRoleAddr, + input: func() []byte { + input, err := precompile.PackAllowFeeRecipients() + if err != nil { + panic(err) + } + return input + }, + suppliedGas: precompile.AllowFeeRecipientsGasCost, + readOnly: false, + expectedErr: precompile.ErrCannotAllowFeeRecipients.Error(), + }, + "set reward address from no role fails": { + caller: noRoleAddr, + input: func() []byte { + input, err := precompile.PackSetRewardAddress(testAddr) + if err != nil { + panic(err) + } + return input + }, + suppliedGas: precompile.SetRewardAddressGasCost, + readOnly: false, + expectedErr: precompile.ErrCannotSetRewardAddress.Error(), + }, + "disable rewards from no role fails": { + caller: noRoleAddr, + input: func() []byte { + input, err := precompile.PackDisableRewards() + if err != nil { + panic(err) + } + return input + }, + suppliedGas: precompile.DisableRewardsGasCost, + readOnly: false, + expectedErr: precompile.ErrCannotDisableRewards.Error(), + }, + "set allow fee recipients from enabled succeeds": { + caller: enabledAddr, + input: func() []byte { + input, err := precompile.PackAllowFeeRecipients() + if err != nil { + panic(err) + } + return input + }, + suppliedGas: precompile.AllowFeeRecipientsGasCost, + readOnly: false, + expectedRes: []byte{}, + }, + "set reward address from enabled succeeds": { + caller: enabledAddr, + input: func() []byte { + input, err := precompile.PackSetRewardAddress(testAddr) + if err != nil { + panic(err) + } + return input + }, + suppliedGas: precompile.SetRewardAddressGasCost, + readOnly: false, + expectedRes: []byte{}, + }, + "disable rewards from enabled succeeds": { + caller: enabledAddr, + input: func() []byte { + input, err := precompile.PackDisableRewards() + if err != nil { + panic(err) + } + return input + }, + suppliedGas: precompile.DisableRewardsGasCost, + readOnly: false, + expectedRes: []byte{}, + }, + "get current reward address from no role succeeds": { + caller: noRoleAddr, + preCondition: func(t *testing.T, state *state.StateDB) { + precompile.StoreRewardAddress(state, testAddr) + }, + input: func() []byte { + input, err := precompile.PackCurrentRewardAddress() + if err != nil { + panic(err) + } + return input + }, + suppliedGas: precompile.CurrentRewardAddressGasCost, + readOnly: false, + expectedRes: func() []byte { + res, err := precompile.PackCurrentRewardAddressOutput(testAddr) + require.NoError(t, err) + return res + }(), + }, + "get are fee recipients allowed from no role succeeds": { + caller: noRoleAddr, + preCondition: func(t *testing.T, state *state.StateDB) { + precompile.EnableAllowFeeRecipients(state) + }, + input: func() []byte { + input, err := precompile.PackAreFeeRecipientsAllowed() + require.NoError(t, err) + return input + }, + suppliedGas: precompile.AreFeeRecipientsAllowedGasCost, + readOnly: false, + expectedRes: func() []byte { + res, err := precompile.PackAreFeeRecipientsAllowedOutput(true) + require.NoError(t, err) + return res + }(), + }, + "get initial config with address": { + caller: noRoleAddr, + input: func() []byte { + input, err := precompile.PackCurrentRewardAddress() + require.NoError(t, err) + return input + }, + suppliedGas: precompile.CurrentRewardAddressGasCost, + config: &precompile.RewardManagerConfig{ + InitialRewardConfig: &precompile.InitialRewardConfig{ + RewardAddress: testAddr, + }, + }, + readOnly: false, + expectedRes: func() []byte { + res, err := precompile.PackCurrentRewardAddressOutput(testAddr) + require.NoError(t, err) + return res + }(), + }, + "get initial config with allow fee recipients enabled": { + caller: noRoleAddr, + input: func() []byte { + input, err := precompile.PackAreFeeRecipientsAllowed() + require.NoError(t, err) + return input + }, + suppliedGas: precompile.AreFeeRecipientsAllowedGasCost, + config: &precompile.RewardManagerConfig{ + InitialRewardConfig: &precompile.InitialRewardConfig{ + AllowFeeRecipients: true, + }, + }, + readOnly: false, + expectedRes: func() []byte { + res, err := precompile.PackAreFeeRecipientsAllowedOutput(true) + require.NoError(t, err) + return res + }(), + }, + "readOnly allow fee recipients with allowed role fails": { + caller: enabledAddr, + input: func() []byte { + input, err := precompile.PackAllowFeeRecipients() + if err != nil { + panic(err) + } + return input + }, + suppliedGas: precompile.AllowFeeRecipientsGasCost, + readOnly: true, + expectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "readOnly set reward addresss with allowed role fails": { + caller: enabledAddr, + input: func() []byte { + input, err := precompile.PackSetRewardAddress(testAddr) + if err != nil { + panic(err) + } + return input + }, + suppliedGas: precompile.SetRewardAddressGasCost, + readOnly: true, + expectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "insufficient gas set reward address from allowed role": { + caller: enabledAddr, + input: func() []byte { + input, err := precompile.PackSetRewardAddress(testAddr) + if err != nil { + panic(err) + } + return input + }, + suppliedGas: precompile.SetRewardAddressGasCost - 1, + readOnly: false, + expectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "insufficient gas allow fee recipieints from allowed role": { + caller: enabledAddr, + input: func() []byte { + input, err := precompile.PackAllowFeeRecipients() + if err != nil { + panic(err) + } + return input + }, + suppliedGas: precompile.AllowFeeRecipientsGasCost - 1, + readOnly: false, + expectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "insufficient read current reward address from allowed role": { + caller: enabledAddr, + input: func() []byte { + input, err := precompile.PackCurrentRewardAddress() + if err != nil { + panic(err) + } + return input + }, + suppliedGas: precompile.CurrentRewardAddressGasCost - 1, + readOnly: false, + expectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "insufficient are fee recipients allowed from allowed role": { + caller: enabledAddr, + input: func() []byte { + input, err := precompile.PackAreFeeRecipientsAllowed() + if err != nil { + panic(err) + } + return input + }, + suppliedGas: precompile.AreFeeRecipientsAllowedGasCost - 1, + readOnly: false, + expectedErr: vmerrs.ErrOutOfGas.Error(), + }, + "set allow role from admin": { + caller: adminAddr, + input: func() []byte { + input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) + if err != nil { + panic(err) + } + return input + }, + suppliedGas: precompile.ModifyAllowListGasCost, + readOnly: false, + expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + res := precompile.GetRewardManagerAllowListStatus(state, noRoleAddr) + require.Equal(t, precompile.AllowListEnabled, res) + }, + }, + "set allow role from non-admin fails": { + caller: enabledAddr, + input: func() []byte { + input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) + if err != nil { + panic(err) + } + return input + }, + suppliedGas: precompile.ModifyAllowListGasCost, + readOnly: false, + expectedErr: precompile.ErrCannotModifyAllowList.Error(), + }, + } { + t.Run(name, func(t *testing.T) { + db := rawdb.NewMemoryDatabase() + state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) + if err != nil { + t.Fatal(err) + } + // Set up the state so that each address has the expected permissions at the start. + precompile.SetRewardManagerAllowListStatus(state, adminAddr, precompile.AllowListAdmin) + precompile.SetRewardManagerAllowListStatus(state, enabledAddr, precompile.AllowListEnabled) + precompile.SetRewardManagerAllowListStatus(state, noRoleAddr, precompile.AllowListNoRole) + + if test.preCondition != nil { + test.preCondition(t, state) + } + + blockContext := &mockBlockContext{blockNumber: testBlockNumber} + if test.config != nil { + test.config.Configure(params.TestChainConfig, state, blockContext) + } + ret, remainingGas, err := precompile.RewardManagerPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext}, test.caller, precompile.RewardManagerAddress, test.input(), test.suppliedGas, test.readOnly) + if len(test.expectedErr) != 0 { + if err == nil { + require.Failf(t, "run expectedly passed without error", "expected error %q", test.expectedErr) + } else { + require.True(t, strings.Contains(err.Error(), test.expectedErr), "expected error (%s) to contain substring (%s)", err, test.expectedErr) + } + return + } + + if err != nil { + t.Fatal(err) + } + + require.Equal(t, uint64(0), remainingGas) + require.Equal(t, test.expectedRes, ret) + + if test.assertState != nil { + test.assertState(t, state) + } + }) + } +} diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index e226bccd0f..2ad0043bda 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/api/keystore" "github.com/ava-labs/avalanchego/database/manager" @@ -2576,3 +2577,142 @@ func TestAllowFeeRecipientEnabled(t *testing.T) { balance := blkState.GetBalance(etherBase) assert.Equal(t, 1, balance.Cmp(common.Big0)) } + +func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { + genesis := &core.Genesis{} + if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { + t.Fatal(err) + } + genesis.Config.RewardManagerConfig = precompile.NewRewardManagerConfig(common.Big0, testEthAddrs[0:1], nil, nil) + genesis.Config.AllowFeeRecipients = true // enable this in genesis to test if this is recognized by the reward manager + genesisJSON, err := genesis.MarshalJSON() + if err != nil { + t.Fatal(err) + } + etherBase := common.HexToAddress("0x0123456789") // give custom ether base + c := Config{} + c.SetDefaults() + c.FeeRecipient = etherBase.String() + configJSON, err := json.Marshal(c) + require.NoError(t, err) + issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), string(configJSON), "") + + defer func() { + if err := vm.Shutdown(); err != nil { + t.Fatal(err) + } + }() + + newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) + vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) + + testAddr := common.HexToAddress("0x9999991111") + data, err := precompile.PackSetRewardAddress(testAddr) + require.NoError(t, err) + + tx := types.NewTransaction(uint64(0), precompile.RewardManagerAddress, big.NewInt(1), 21240+precompile.SetRewardAddressGasCost, big.NewInt(testMinGasPrice), data) + + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) + require.NoError(t, err) + + txErrors := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) + for _, err := range txErrors { + require.NoError(t, err) + } + + blk := issueAndAccept(t, issuer, vm) + newHead := <-newTxPoolHeadChan + require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) + ethBlock := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock + require.Equal(t, ethBlock.Coinbase(), etherBase) // reward address is activated at this block so this is fine + + tx1 := types.NewTransaction(uint64(0), testEthAddrs[0], big.NewInt(2), 21000, big.NewInt(testMinGasPrice*3), nil) + signedTx1, err := types.SignTx(tx1, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[1]) + require.NoError(t, err) + + txErrors = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) + for _, err := range txErrors { + require.NoError(t, err) + } + + blk = issueAndAccept(t, issuer, vm) + newHead = <-newTxPoolHeadChan + require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) + ethBlock = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock + require.Equal(t, ethBlock.Coinbase(), testAddr) // reward address was activated at previous block + // Verify that etherBase has received fees + blkState, err := vm.blockChain.StateAt(ethBlock.Root()) + require.NoError(t, err) + + balance := blkState.GetBalance(testAddr) + assert.Equal(t, 1, balance.Cmp(common.Big0)) +} + +func TestRewardManagerPrecompileAllowFeeRecipieints(t *testing.T) { + genesis := &core.Genesis{} + if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { + t.Fatal(err) + } + genesis.Config.RewardManagerConfig = precompile.NewRewardManagerConfig(common.Big0, testEthAddrs[0:1], nil, nil) + genesis.Config.AllowFeeRecipients = false // disable this in genesis + genesisJSON, err := genesis.MarshalJSON() + if err != nil { + t.Fatal(err) + } + etherBase := common.HexToAddress("0x0123456789") // give custom ether base + c := Config{} + c.SetDefaults() + c.FeeRecipient = etherBase.String() + configJSON, err := json.Marshal(c) + require.NoError(t, err) + issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), string(configJSON), "") + + defer func() { + if err := vm.Shutdown(); err != nil { + t.Fatal(err) + } + }() + + newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) + vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) + + data, err := precompile.PackAllowFeeRecipients() + require.NoError(t, err) + + tx := types.NewTransaction(uint64(0), precompile.RewardManagerAddress, big.NewInt(1), 21240+precompile.SetRewardAddressGasCost, big.NewInt(testMinGasPrice), data) + + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) + require.NoError(t, err) + + txErrors := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) + for _, err := range txErrors { + require.NoError(t, err) + } + + blk := issueAndAccept(t, issuer, vm) + newHead := <-newTxPoolHeadChan + require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) + ethBlock := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock + require.Equal(t, ethBlock.Coinbase(), constants.BlackholeAddr) // reward address is activated at this block so this is fine + + tx1 := types.NewTransaction(uint64(0), testEthAddrs[0], big.NewInt(2), 21000, big.NewInt(testMinGasPrice*3), nil) + signedTx1, err := types.SignTx(tx1, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[1]) + require.NoError(t, err) + + txErrors = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) + for _, err := range txErrors { + require.NoError(t, err) + } + + blk = issueAndAccept(t, issuer, vm) + newHead = <-newTxPoolHeadChan + require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) + ethBlock = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock + require.Equal(t, ethBlock.Coinbase(), etherBase) // reward address was activated at previous block + // Verify that etherBase has received fees + blkState, err := vm.blockChain.StateAt(ethBlock.Root()) + require.NoError(t, err) + + balance := blkState.GetBalance(etherBase) + assert.Equal(t, 1, balance.Cmp(common.Big0)) +} diff --git a/precompile/config_test.go b/precompile/config_test.go index f5b21d629b..6d7bfc442c 100644 --- a/precompile/config_test.go +++ b/precompile/config_test.go @@ -105,6 +105,19 @@ func TestVerifyPrecompileUpgrades(t *testing.T) { }), expectedError: "initial mint cannot contain invalid amount", }, + { + name: "duplicate enableds in config in reward manager allowlist", + config: NewRewardManagerConfig(big.NewInt(3), admins, append(enableds, enableds[0]), nil), + expectedError: "duplicate address", + }, + { + name: "both reward mechanisms should not be activated at the same time in reward manager", + config: NewRewardManagerConfig(big.NewInt(3), admins, enableds, &InitialRewardConfig{ + AllowFeeRecipients: true, + RewardAddress: common.HexToAddress("0x01"), + }), + expectedError: ErrCannotEnableBothRewards.Error(), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -375,3 +388,75 @@ func TestEqualFeeConfigManagerConfig(t *testing.T) { }) } } + +func TestEqualRewardManagerConfig(t *testing.T) { + admins := []common.Address{{1}} + enableds := []common.Address{{2}} + tests := []struct { + name string + config StatefulPrecompileConfig + other StatefulPrecompileConfig + expected bool + }{ + { + name: "non-nil config and nil other", + config: NewRewardManagerConfig(big.NewInt(3), admins, enableds, nil), + other: nil, + expected: false, + }, + { + name: "different type", + config: NewRewardManagerConfig(big.NewInt(3), admins, enableds, nil), + other: NewTxAllowListConfig(big.NewInt(3), []common.Address{{1}}, []common.Address{{2}}), + expected: false, + }, + { + name: "different version", + config: NewRewardManagerConfig(big.NewInt(3), admins, nil, nil), + other: NewRewardManagerConfig(big.NewInt(4), admins, nil, nil), + expected: false, + }, + { + name: "different enabled", + config: NewRewardManagerConfig(big.NewInt(3), admins, nil, nil), + other: NewRewardManagerConfig(big.NewInt(3), admins, enableds, nil), + expected: false, + }, + { + name: "non-nil initial config and nil initial config", + config: NewRewardManagerConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ + AllowFeeRecipients: true, + }), + other: NewRewardManagerConfig(big.NewInt(3), admins, nil, nil), + expected: false, + }, + { + name: "different initial config", + config: NewRewardManagerConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ + RewardAddress: common.HexToAddress("0x01"), + }), + other: NewRewardManagerConfig(big.NewInt(3), admins, nil, + &InitialRewardConfig{ + RewardAddress: common.HexToAddress("0x02"), + }), + expected: false, + }, + { + name: "same config", + config: NewRewardManagerConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ + RewardAddress: common.HexToAddress("0x01"), + }), + other: NewRewardManagerConfig(big.NewInt(3), admins, nil, &InitialRewardConfig{ + RewardAddress: common.HexToAddress("0x01"), + }), + expected: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + require.Equal(tt.expected, tt.config.Equal(tt.other)) + }) + } +} From 152f179b8dcd24e765eb51436fc8721630eedb0c Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 11 Oct 2022 15:01:01 +0300 Subject: [PATCH 33/64] cleanups --- accounts/abi/bind/precompile_template.go | 2 +- consensus/dummy/consensus.go | 12 ++-- contract-examples/contracts/IAllowList.sol | 10 ++-- .../contracts/IRewardManager.sol | 4 +- contract-examples/tasks.ts | 57 +++++++++++++++++-- precompile/reward_manager.go | 36 ++++-------- 6 files changed, 76 insertions(+), 45 deletions(-) diff --git a/accounts/abi/bind/precompile_template.go b/accounts/abi/bind/precompile_template.go index d7e70a3c18..9d9ab1bedb 100644 --- a/accounts/abi/bind/precompile_template.go +++ b/accounts/abi/bind/precompile_template.go @@ -69,7 +69,7 @@ const ( ) // CUSTOM CODE STARTS HERE -// Reference imports to suppress errors from unused imports. This code and any unnecessary imports can be removed. +// Reference imports to suppress errors from unused imports. This code and any unnecessary imports can be removed. var ( _ = errors.New _ = big.NewInt diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index b8275449a2..3ad5ced47d 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -85,7 +85,7 @@ func (self *DummyEngine) verifyCoinbase(config *params.ChainConfig, header *type // we fetch the configured coinbase at the parent's state // but we check the header coinbase against this coinfigured coinbase if configuredAddressAtParent != header.Coinbase { - return fmt.Errorf("%w: %v does not match configured coinbase address %v", vmerrs.ErrInvalidCoinbase, header.Coinbase, configuredAddressAtParent) + return fmt.Errorf("%w: %v does not match required coinbase address %v", vmerrs.ErrInvalidCoinbase, header.Coinbase, configuredAddressAtParent) } return nil } @@ -191,15 +191,17 @@ func (self *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header if len(header.Extra) != expectedExtraDataSize { return fmt.Errorf("expected extra-data field to be: %d, but found %d", expectedExtraDataSize, len(header.Extra)) } - // Ensure that coinbase is valid - if err := self.verifyCoinbase(config, header, parent, chain); err != nil { - return err - } } // Ensure gas-related header fields are correct if err := self.verifyHeaderGasFields(config, header, parent, chain); err != nil { return err } + // Ensure that coinbase is valid if reward manager is enabled + if config.IsRewardManager(timestamp) { + if err := self.verifyCoinbase(config, header, parent, chain); err != nil { + return err + } + } // Verify the header's timestamp if header.Time > uint64(self.clock.Time().Add(allowedFutureBlockTime).Unix()) { return consensus.ErrFutureBlock diff --git a/contract-examples/contracts/IAllowList.sol b/contract-examples/contracts/IAllowList.sol index a70423efaa..a45bd4095b 100644 --- a/contract-examples/contracts/IAllowList.sol +++ b/contract-examples/contracts/IAllowList.sol @@ -2,15 +2,15 @@ pragma solidity ^0.8.0; interface IAllowList { - // Set [addr] to have the admin role over the minter list + // Set [addr] to have the admin role over the precompile contract. function setAdmin(address addr) external; - // Set [addr] to be enabled on the minter list + // Set [addr] to be enabled on the precompile contract. function setEnabled(address addr) external; - // Set [addr] to have no role over the minter list + // Set [addr] to have no role the precompile contract. function setNone(address addr) external; - // Read the status of [addr] - function readAllowList(address addr) external view returns (uint256); + // Read the status of [addr]. + function readAllowList(address addr) external view returns (uint256 role); } diff --git a/contract-examples/contracts/IRewardManager.sol b/contract-examples/contracts/IRewardManager.sol index 4387dd0d08..8ceb27b46b 100644 --- a/contract-examples/contracts/IRewardManager.sol +++ b/contract-examples/contracts/IRewardManager.sol @@ -9,7 +9,7 @@ interface IRewardManager is IAllowList { function disableRewards() external; - function currentRewardAddress() external returns (address rewardAddress); + function currentRewardAddress() external view returns (address rewardAddress); - function areFeeRecipientsAllowed() external returns (bool isAllowed); + function areFeeRecipientsAllowed() external view returns (bool isAllowed); } diff --git a/contract-examples/tasks.ts b/contract-examples/tasks.ts index f9dcd73563..cb2393ed16 100644 --- a/contract-examples/tasks.ts +++ b/contract-examples/tasks.ts @@ -7,6 +7,7 @@ const CONTRACT_ALLOW_LIST_ADDRESS = "0x0200000000000000000000000000000000000000" const MINT_ADDRESS = "0x0200000000000000000000000000000000000001" const TX_ALLOW_LIST_ADDRESS = "0x0200000000000000000000000000000000000002" const FEE_MANAGER_ADDRESS = "0x0200000000000000000000000000000000000003" +const REWARD_MANAGER_ADDDRESS = "0x0200000000000000000000000000000000000004" const ROLES = { @@ -42,8 +43,8 @@ task("balance", "a task to get the balance") .addParam("address", "the address you want to know balance of") .setAction(async (args, hre) => { const balance = await hre.ethers.provider.getBalance(args.address) - const balanceInEth = hre.ethers.utils.formatEther(balance) - console.log(`balance: ${balanceInEth} ETH`) + const balanceInCoin = hre.ethers.utils.formatEther(balance) + console.log(`balance: ${balanceInCoin} Coin`) }) // npx hardhat allowList:readRole --network local --address [address] @@ -160,12 +161,12 @@ task("minter:mint", "mint native token") .addParam("amount", "the amount you want to mint") .setAction(async (args, hre) => { const minter = await hre.ethers.getContractAt("INativeMinter", MINT_ADDRESS) - await minter.mintNativeToken(args.address, args.amount) + await minter.mintNativeCoin(args.address, args.amount) }) // npx hardhat minter:burn --network local --address [address] task("minter:burn", "burn") - .addParam("amount", "the amount you want to burn (in ETH unit)") + .addParam("amount", "the amount you want to burn (in AVAX unit)") .setAction(async (args, hre) => { const [owner] = await hre.ethers.getSigners() const transactionHash = await owner.sendTransaction({ @@ -188,7 +189,7 @@ task("feeManager:set", "sets fee config") .setAction(async (args, hre) => { const feeManager = await hre.ethers.getContractAt("IFeeManager", FEE_MANAGER_ADDRESS) - const feeConfig = await feeManager.setFeeConfig( + await feeManager.setFeeConfig( args.gaslimit, args.targetblockrate, args.minbasefee, @@ -215,7 +216,7 @@ task("feeManager:get", "gets fee config") }) -// npx hardhat minter:readRole --network local --address [address] +// npx hardhat feeManager:readRole --network local --address [address] task("feeManager:readRole", "a task to get the network deployer minter list") .addParam("address", "the address you want to know the minter role for") .setAction(async (args, hre) => { @@ -223,3 +224,47 @@ task("feeManager:readRole", "a task to get the network deployer minter list") await getRole(allowList, args.address) }) + +// npx hardhat rewardManager:readRole --network local --address [address] +task("rewardManager:currentRewardAddress", "a task to get the current configured rewarding address") + .setAction(async (_, hre) => { + const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) + const result = await rewardManager.currentRewardAddress() + if (result == "0x0000000000000000000000000000000000000000") { + console.log("Custom Fee Recipients are allowed.") + } else { + console.log(`Current reward address is ${result}`) + } + }) + +// npx hardhat rewardManager:areFeeRecipientsAllowed --network local --address [address] +task("rewardManager:areFeeRecipientsAllowed", "a task to get wheter the fee recipients are allowed") + .setAction(async (_, hre) => { + const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) + const result = await rewardManager.areFeeRecipientsAllowed() + console.log(result) + }) + +// npx hardhat rewardManager:setRewardAddress --network local --address [address] +task("rewardManager:setRewardAddress", "sets a new reward address") + .addParam("address", "the address that will receive rewards") + .setAction(async (args, hre) => { + const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) + const result = await rewardManager.setRewardAddress(args.address) + console.log(result) + }) + +// npx hardhat rewardManager:allowFeeRecipients --network local +task("rewardManager:allowFeeRecipients", "allows custom fee recipients to receive rewards") + .setAction(async (_, hre) => { + const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) + const result = await rewardManager.allowFeeRecipients() + console.log(result) + }) + +task("rewardManager:disableRewards", "disables all rewards, and starts burning fees.") + .setAction(async (_, hre) => { + const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) + const result = await rewardManager.disableRewards() + console.log(result) + }) diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go index 06ae1fdf74..b50db35bd9 100644 --- a/precompile/reward_manager.go +++ b/precompile/reward_manager.go @@ -20,14 +20,14 @@ import ( ) const ( - AllowFeeRecipientsGasCost uint64 = (writeGasCostPerSlot * 2) + ReadAllowListGasCost // 2 slots + read allow list + AllowFeeRecipientsGasCost uint64 = (writeGasCostPerSlot) + ReadAllowListGasCost // write 1 slot + read allow list AreFeeRecipientsAllowedGasCost uint64 = readGasCostPerSlot CurrentRewardAddressGasCost uint64 = readGasCostPerSlot - DisableRewardsGasCost uint64 = (writeGasCostPerSlot * 2) + ReadAllowListGasCost // 2 slots + read allow list - SetRewardAddressGasCost uint64 = (writeGasCostPerSlot * 2) + ReadAllowListGasCost // 2 slots + read allow list + DisableRewardsGasCost uint64 = (writeGasCostPerSlot) + ReadAllowListGasCost // write 1 slot + read allow list + SetRewardAddressGasCost uint64 = (writeGasCostPerSlot) + ReadAllowListGasCost // write 1 slot + read allow list // RewardManagerRawABI contains the raw ABI of RewardManager contract. - RewardManagerRawABI = "[{\"inputs\":[],\"name\":\"allowFeeRecipients\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"areFeeRecipientsAllowed\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isAllowed\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"currentRewardAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"rewardAddress\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"disableRewards\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"readAllowList\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setEnabled\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setNone\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setRewardAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" + RewardManagerRawABI = "[{\"inputs\":[],\"name\":\"allowFeeRecipients\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"areFeeRecipientsAllowed\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isAllowed\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"currentRewardAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"rewardAddress\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"disableRewards\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"readAllowList\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"role\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setEnabled\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setNone\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setRewardAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" ) // Reference imports to suppress errors if they are not otherwise used. @@ -168,12 +168,16 @@ func (c *RewardManagerConfig) Configure(chainConfig ChainConfig, state StateDB, } return } + // if we did not enable any reward until now, disable rewards + // since given initial config is not nil DisableFeeRewards(state) } else { // configure the RewardManager according to chainConfig if chainConfig.AllowedFeeRecipients() { EnableAllowFeeRecipients(state) return } + // chainConfig does not have any reward address + // if it does not enable fee recipients, disable rewards DisableFeeRewards(state) } } @@ -270,18 +274,8 @@ func areFeeRecipientsAllowed(accessibleState PrecompileAccessibleState, caller c } // no input provided for this function - // Allow list is enabled and AreFeeRecipientsAllowed is a state-changer function. - // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. - // You can modify/delete this code if you don't want this function to be restricted by the allow list. - stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := getAllowListStatus(stateDB, RewardManagerAddress, caller) - if !callerStatus.IsEnabled() { - return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotAreFeeRecipientsAllowed, caller) - } - // allow list code ends here. - // CUSTOM CODE STARTS HERE + stateDB := accessibleState.GetStateDB() var output bool // CUSTOM CODE FOR AN OUTPUT _, output = GetStoredRewardAddress(stateDB) @@ -390,18 +384,8 @@ func currentRewardAddress(accessibleState PrecompileAccessibleState, caller comm } // no input provided for this function - // Allow list is enabled and CurrentRewardAddress is a state-changer function. - // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. - // You can modify/delete this code if you don't want this function to be restricted by the allow list. - stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to modify it - callerStatus := getAllowListStatus(stateDB, RewardManagerAddress, caller) - if !callerStatus.IsEnabled() { - return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotCurrentRewardAddress, caller) - } - // allow list code ends here. - // CUSTOM CODE STARTS HERE + stateDB := accessibleState.GetStateDB() output, _ := GetStoredRewardAddress(stateDB) packedOutput, err := PackCurrentRewardAddressOutput(output) if err != nil { From 21ac1c2bb9f0c4f8a2349641b6b12a35457fa688 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 11 Oct 2022 22:44:05 +0300 Subject: [PATCH 34/64] Update consensus/dummy/consensus.go Co-authored-by: Darioush Jalali --- consensus/dummy/consensus.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index 3ad5ced47d..d1449e1cbf 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -83,7 +83,7 @@ func (self *DummyEngine) verifyCoinbase(config *params.ChainConfig, header *type return nil } // we fetch the configured coinbase at the parent's state - // but we check the header coinbase against this coinfigured coinbase + // to check against the coinbase in [header]. if configuredAddressAtParent != header.Coinbase { return fmt.Errorf("%w: %v does not match required coinbase address %v", vmerrs.ErrInvalidCoinbase, header.Coinbase, configuredAddressAtParent) } From 7dae0c2079c22150d3ae7150cca2942a40e5ecdc Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 11 Oct 2022 22:44:11 +0300 Subject: [PATCH 35/64] Update contract-examples/contracts/IAllowList.sol Co-authored-by: Darioush Jalali --- contract-examples/contracts/IAllowList.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contract-examples/contracts/IAllowList.sol b/contract-examples/contracts/IAllowList.sol index a45bd4095b..e76148bb34 100644 --- a/contract-examples/contracts/IAllowList.sol +++ b/contract-examples/contracts/IAllowList.sol @@ -8,7 +8,7 @@ interface IAllowList { // Set [addr] to be enabled on the precompile contract. function setEnabled(address addr) external; - // Set [addr] to have no role the precompile contract. + // Set [addr] to have no role for the precompile contract. function setNone(address addr) external; // Read the status of [addr]. From 382f49289bb470d4df165225e4f8dce2ea3ddf7f Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 12 Oct 2022 13:25:55 +0300 Subject: [PATCH 36/64] Update core/stateful_precompile_test.go Co-authored-by: Darioush Jalali --- core/stateful_precompile_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/stateful_precompile_test.go b/core/stateful_precompile_test.go index 9f46cfdb18..c9d6aaa17d 100644 --- a/core/stateful_precompile_test.go +++ b/core/stateful_precompile_test.go @@ -1246,7 +1246,7 @@ func TestRewardManagerRun(t *testing.T) { readOnly: false, expectedErr: vmerrs.ErrOutOfGas.Error(), }, - "insufficient gas allow fee recipieints from allowed role": { + "insufficient gas allow fee recipients from allowed role": { caller: enabledAddr, input: func() []byte { input, err := precompile.PackAllowFeeRecipients() From d428cfa48cc0c3f3788e24a251545ee6bdc3df9f Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 12 Oct 2022 13:26:10 +0300 Subject: [PATCH 37/64] Update core/stateful_precompile_test.go Co-authored-by: Darioush Jalali --- core/stateful_precompile_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/stateful_precompile_test.go b/core/stateful_precompile_test.go index c9d6aaa17d..a640e9a105 100644 --- a/core/stateful_precompile_test.go +++ b/core/stateful_precompile_test.go @@ -1259,7 +1259,7 @@ func TestRewardManagerRun(t *testing.T) { readOnly: false, expectedErr: vmerrs.ErrOutOfGas.Error(), }, - "insufficient read current reward address from allowed role": { + "insufficient gas read current reward address from allowed role": { caller: enabledAddr, input: func() []byte { input, err := precompile.PackCurrentRewardAddress() From ee7fac5fbb7039d16520ae1641742bc2d758af24 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 12 Oct 2022 13:26:29 +0300 Subject: [PATCH 38/64] Update core/stateful_precompile_test.go Co-authored-by: Darioush Jalali --- core/stateful_precompile_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/stateful_precompile_test.go b/core/stateful_precompile_test.go index a640e9a105..324db7f086 100644 --- a/core/stateful_precompile_test.go +++ b/core/stateful_precompile_test.go @@ -1272,7 +1272,7 @@ func TestRewardManagerRun(t *testing.T) { readOnly: false, expectedErr: vmerrs.ErrOutOfGas.Error(), }, - "insufficient are fee recipients allowed from allowed role": { + "insufficient gas are fee recipients allowed from allowed role": { caller: enabledAddr, input: func() []byte { input, err := precompile.PackAreFeeRecipientsAllowed() From bdf3ecb858bb89448c617fb95471bcd6a49d9680 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 12 Oct 2022 14:48:38 +0300 Subject: [PATCH 39/64] Update precompile/reward_manager.go Co-authored-by: Darioush Jalali --- precompile/reward_manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go index b50db35bd9..497eabd756 100644 --- a/precompile/reward_manager.go +++ b/precompile/reward_manager.go @@ -82,7 +82,7 @@ func (c *InitialRewardConfig) Equal(other *InitialRewardConfig) bool { } // RewardManagerConfig implements the StatefulPrecompileConfig -// interface while adding in the RewardManager specific precompile address. +// interface while adding in the RewardManager specific precompile config. type RewardManagerConfig struct { AllowListConfig UpgradeableConfig From ebd3849376710c23837aa75ab96d48e4da909428 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 12 Oct 2022 14:48:45 +0300 Subject: [PATCH 40/64] Update precompile/reward_manager.go Co-authored-by: Darioush Jalali --- precompile/reward_manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go index 497eabd756..978da13ff2 100644 --- a/precompile/reward_manager.go +++ b/precompile/reward_manager.go @@ -131,7 +131,7 @@ func (c *RewardManagerConfig) Equal(s StatefulPrecompileConfig) bool { } // CUSTOM CODE STARTS HERE // modify this boolean accordingly with your custom RewardManagerConfig, to check if [other] and the current [c] are equal - // if RewardManagerConfig contains only UpgradeableConfig and AllowListConfig you can skip modifying it. + // if RewardManagerConfig contains only UpgradeableConfig and AllowListConfig you can skip modifying it. equals := c.UpgradeableConfig.Equal(&other.UpgradeableConfig) && c.AllowListConfig.Equal(&other.AllowListConfig) if !equals { return false From e489d4f43d9d12ac8479b74ad26f7daefdbd7f3d Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 12 Oct 2022 14:48:52 +0300 Subject: [PATCH 41/64] Update precompile/reward_manager.go Co-authored-by: Darioush Jalali --- precompile/reward_manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go index 978da13ff2..6e5c218185 100644 --- a/precompile/reward_manager.go +++ b/precompile/reward_manager.go @@ -208,7 +208,7 @@ func SetRewardManagerAllowListStatus(stateDB StateDB, address common.Address, ro setAllowListRole(stateDB, RewardManagerAddress, address, role) } -// PackAllowFeeRecipients packs the include selector (first 4 func signature bytes). +// PackAllowFeeRecipients packs the function selector (first 4 func signature bytes). // This function is mostly used for tests. func PackAllowFeeRecipients() ([]byte, error) { return RewardManagerABI.Pack("allowFeeRecipients") From 6caf6df9085564d8c0ca7758ae4315c5042ae729 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 12 Oct 2022 14:49:34 +0300 Subject: [PATCH 42/64] review fixes --- abis/IAllowList.abi | 1 + abis/IRewardManager.abi | 1 + .../contracts/ExampleFeeDistributor.sol | 30 ++ core/stateful_precompile_test.go | 352 +++++++----------- plugin/evm/vm_test.go | 25 +- precompile/reward_manager.go | 1 - 6 files changed, 167 insertions(+), 243 deletions(-) create mode 100644 abis/IAllowList.abi create mode 100644 abis/IRewardManager.abi create mode 100644 contract-examples/contracts/ExampleFeeDistributor.sol diff --git a/abis/IAllowList.abi b/abis/IAllowList.abi new file mode 100644 index 0000000000..ea89cd6408 --- /dev/null +++ b/abis/IAllowList.abi @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/abis/IRewardManager.abi b/abis/IRewardManager.abi new file mode 100644 index 0000000000..d21d5bdc6b --- /dev/null +++ b/abis/IRewardManager.abi @@ -0,0 +1 @@ +[{"inputs":[],"name":"allowFeeRecipients","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"areFeeRecipientsAllowed","outputs":[{"internalType":"bool","name":"isAllowed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentRewardAddress","outputs":[{"internalType":"address","name":"rewardAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setRewardAddress","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/contract-examples/contracts/ExampleFeeDistributor.sol b/contract-examples/contracts/ExampleFeeDistributor.sol new file mode 100644 index 0000000000..68b71baf39 --- /dev/null +++ b/contract-examples/contracts/ExampleFeeDistributor.sol @@ -0,0 +1,30 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IAllowList.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract ExampleFeeDistributor is Ownable { + mapping(address => bool) rewardAddresses; + uint256 rewardAddressCount; + + constructor() Ownable() {} + + function addRewardAddress(address addr) public onlyOwner { + require(!rewardAddresses[addr], "Already a reward address"); + + rewardAddresses[addr] = true; + rewardAddressCount++; + } + + function revoke(address addr) public onlyOwner { + require(rewardAddresses[addr], "Not a reward address"); + + rewardAddresses[addr] = false; + rewardAddressCount--; + } + + function claim() public { + require(rewardAddresses[msg.sender], "Not a reward address"); + } +} diff --git a/core/stateful_precompile_test.go b/core/stateful_precompile_test.go index 9f46cfdb18..21c33d84b9 100644 --- a/core/stateful_precompile_test.go +++ b/core/stateful_precompile_test.go @@ -5,7 +5,6 @@ package core import ( "math/big" - "strings" "testing" "github.com/ava-labs/subnet-evm/commontype" @@ -79,9 +78,8 @@ func TestContractDeployerAllowListRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListAdmin) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -96,9 +94,8 @@ func TestContractDeployerAllowListRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -113,9 +110,8 @@ func TestContractDeployerAllowListRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -130,9 +126,8 @@ func TestContractDeployerAllowListRun(t *testing.T) { caller: noRoleAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -143,9 +138,8 @@ func TestContractDeployerAllowListRun(t *testing.T) { caller: noRoleAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListEnabled) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -156,9 +150,8 @@ func TestContractDeployerAllowListRun(t *testing.T) { caller: noRoleAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListAdmin) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -169,9 +162,8 @@ func TestContractDeployerAllowListRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -182,9 +174,8 @@ func TestContractDeployerAllowListRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost - 1, @@ -234,9 +225,7 @@ func TestContractDeployerAllowListRun(t *testing.T) { t.Run(name, func(t *testing.T) { db := rawdb.NewMemoryDatabase() state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // Set up the state so that each address has the expected permissions at the start. precompile.SetContractDeployerAllowListStatus(state, adminAddr, precompile.AllowListAdmin) @@ -247,16 +236,9 @@ func TestContractDeployerAllowListRun(t *testing.T) { blockContext := &mockBlockContext{blockNumber: common.Big0} ret, remainingGas, err := precompile.ContractDeployerAllowListPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext}, test.caller, precompile.ContractDeployerAllowListAddress, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { - if err == nil { - require.Failf(t, "run expectedly passed without error", "expected error %q", test.expectedErr) - } else { - require.True(t, strings.Contains(err.Error(), test.expectedErr), "expected error (%s) to contain substring (%s)", err, test.expectedErr) - } - return - } - - if err != nil { - t.Fatal(err) + require.ErrorContains(t, err, test.expectedErr) + } else { + require.NoError(t, err) } require.Equal(t, uint64(0), remainingGas) @@ -291,9 +273,8 @@ func TestTxAllowListRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListAdmin) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -308,9 +289,8 @@ func TestTxAllowListRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -325,9 +305,8 @@ func TestTxAllowListRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -342,9 +321,8 @@ func TestTxAllowListRun(t *testing.T) { caller: noRoleAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -355,9 +333,8 @@ func TestTxAllowListRun(t *testing.T) { caller: noRoleAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListEnabled) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -368,9 +345,8 @@ func TestTxAllowListRun(t *testing.T) { caller: noRoleAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListAdmin) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -382,9 +358,8 @@ func TestTxAllowListRun(t *testing.T) { precompileAddr: precompile.TxAllowListAddress, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -395,9 +370,8 @@ func TestTxAllowListRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(adminAddr, precompile.AllowListNoRole) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost - 1, @@ -447,9 +421,7 @@ func TestTxAllowListRun(t *testing.T) { t.Run(name, func(t *testing.T) { db := rawdb.NewMemoryDatabase() state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) // Set up the state so that each address has the expected permissions at the start. precompile.SetTxAllowListStatus(state, adminAddr, precompile.AllowListAdmin) @@ -458,16 +430,9 @@ func TestTxAllowListRun(t *testing.T) { blockContext := &mockBlockContext{blockNumber: common.Big0} ret, remainingGas, err := precompile.TxAllowListPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext}, test.caller, precompile.TxAllowListAddress, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { - if err == nil { - require.Failf(t, "run expectedly passed without error", "expected error %q", test.expectedErr) - } else { - require.True(t, strings.Contains(err.Error(), test.expectedErr), "expected error (%s) to contain substring (%s)", err, test.expectedErr) - } - return - } - - if err != nil { - t.Fatal(err) + require.ErrorContains(t, err, test.expectedErr) + } else { + require.NoError(t, err) } require.Equal(t, uint64(0), remainingGas) @@ -504,9 +469,8 @@ func TestContractNativeMinterRun(t *testing.T) { caller: noRoleAddr, input: func() []byte { input, err := precompile.PackMintInput(noRoleAddr, common.Big1) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.MintGasCost, @@ -517,9 +481,8 @@ func TestContractNativeMinterRun(t *testing.T) { caller: enabledAddr, input: func() []byte { input, err := precompile.PackMintInput(enabledAddr, common.Big1) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.MintGasCost, @@ -565,9 +528,8 @@ func TestContractNativeMinterRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackMintInput(adminAddr, common.Big1) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.MintGasCost, @@ -581,9 +543,8 @@ func TestContractNativeMinterRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackMintInput(adminAddr, math.MaxBig256) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.MintGasCost, @@ -597,9 +558,8 @@ func TestContractNativeMinterRun(t *testing.T) { caller: noRoleAddr, input: func() []byte { input, err := precompile.PackMintInput(adminAddr, common.Big1) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.MintGasCost, @@ -610,9 +570,8 @@ func TestContractNativeMinterRun(t *testing.T) { caller: enabledAddr, input: func() []byte { input, err := precompile.PackMintInput(enabledAddr, common.Big1) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.MintGasCost, @@ -623,9 +582,8 @@ func TestContractNativeMinterRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackMintInput(adminAddr, common.Big1) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.MintGasCost, @@ -636,9 +594,8 @@ func TestContractNativeMinterRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackMintInput(enabledAddr, common.Big1) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.MintGasCost - 1, @@ -678,9 +635,8 @@ func TestContractNativeMinterRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -695,9 +651,8 @@ func TestContractNativeMinterRun(t *testing.T) { caller: enabledAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -708,9 +663,8 @@ func TestContractNativeMinterRun(t *testing.T) { t.Run(name, func(t *testing.T) { db := rawdb.NewMemoryDatabase() state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + // Set up the state so that each address has the expected permissions at the start. precompile.SetContractNativeMinterStatus(state, adminAddr, precompile.AllowListAdmin) precompile.SetContractNativeMinterStatus(state, enabledAddr, precompile.AllowListEnabled) @@ -725,16 +679,9 @@ func TestContractNativeMinterRun(t *testing.T) { } ret, remainingGas, err := precompile.ContractNativeMinterPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext}, test.caller, precompile.ContractNativeMinterAddress, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { - if err == nil { - require.Failf(t, "run expectedly passed without error", "expected error %q", test.expectedErr) - } else { - require.True(t, strings.Contains(err.Error(), test.expectedErr), "expected error (%s) to contain substring (%s)", err, test.expectedErr) - } - return - } - - if err != nil { - t.Fatal(err) + require.ErrorContains(t, err, test.expectedErr) + } else { + require.NoError(t, err) } require.Equal(t, uint64(0), remainingGas) @@ -771,9 +718,8 @@ func TestFeeConfigManagerRun(t *testing.T) { caller: noRoleAddr, input: func() []byte { input, err := precompile.PackSetFeeConfig(testFeeConfig) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.SetFeeConfigGasCost, @@ -784,9 +730,8 @@ func TestFeeConfigManagerRun(t *testing.T) { caller: enabledAddr, input: func() []byte { input, err := precompile.PackSetFeeConfig(testFeeConfig) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.SetFeeConfigGasCost, @@ -803,9 +748,8 @@ func TestFeeConfigManagerRun(t *testing.T) { feeConfig := testFeeConfig feeConfig.MinBlockGasCost = new(big.Int).Mul(feeConfig.MaxBlockGasCost, common.Big2) input, err := precompile.PackSetFeeConfig(feeConfig) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.SetFeeConfigGasCost, @@ -821,9 +765,8 @@ func TestFeeConfigManagerRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackSetFeeConfig(testFeeConfig) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.SetFeeConfigGasCost, @@ -840,9 +783,7 @@ func TestFeeConfigManagerRun(t *testing.T) { caller: noRoleAddr, preCondition: func(t *testing.T, state *state.StateDB) { err := precompile.StoreFeeConfig(state, testFeeConfig, &mockBlockContext{blockNumber: big.NewInt(6)}) - if err != nil { - panic(err) - } + require.NoError(t, err) }, input: func() []byte { return precompile.PackGetFeeConfigInput() @@ -887,9 +828,7 @@ func TestFeeConfigManagerRun(t *testing.T) { caller: noRoleAddr, preCondition: func(t *testing.T, state *state.StateDB) { err := precompile.StoreFeeConfig(state, testFeeConfig, &mockBlockContext{blockNumber: testBlockNumber}) - if err != nil { - panic(err) - } + require.NoError(t, err) }, input: func() []byte { return precompile.PackGetLastChangedAtInput() @@ -908,9 +847,8 @@ func TestFeeConfigManagerRun(t *testing.T) { caller: noRoleAddr, input: func() []byte { input, err := precompile.PackSetFeeConfig(testFeeConfig) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.SetFeeConfigGasCost, @@ -921,9 +859,8 @@ func TestFeeConfigManagerRun(t *testing.T) { caller: enabledAddr, input: func() []byte { input, err := precompile.PackSetFeeConfig(testFeeConfig) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.SetFeeConfigGasCost, @@ -934,9 +871,8 @@ func TestFeeConfigManagerRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackSetFeeConfig(testFeeConfig) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.SetFeeConfigGasCost, @@ -947,9 +883,8 @@ func TestFeeConfigManagerRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackSetFeeConfig(testFeeConfig) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.SetFeeConfigGasCost - 1, @@ -960,9 +895,8 @@ func TestFeeConfigManagerRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -977,9 +911,8 @@ func TestFeeConfigManagerRun(t *testing.T) { caller: enabledAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -990,9 +923,8 @@ func TestFeeConfigManagerRun(t *testing.T) { t.Run(name, func(t *testing.T) { db := rawdb.NewMemoryDatabase() state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + // Set up the state so that each address has the expected permissions at the start. precompile.SetFeeConfigManagerStatus(state, adminAddr, precompile.AllowListAdmin) precompile.SetFeeConfigManagerStatus(state, enabledAddr, precompile.AllowListEnabled) @@ -1008,16 +940,9 @@ func TestFeeConfigManagerRun(t *testing.T) { } ret, remainingGas, err := precompile.FeeConfigManagerPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext}, test.caller, precompile.FeeConfigManagerAddress, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { - if err == nil { - require.Failf(t, "run expectedly passed without error", "expected error %q", test.expectedErr) - } else { - require.True(t, strings.Contains(err.Error(), test.expectedErr), "expected error (%s) to contain substring (%s)", err, test.expectedErr) - } - return - } - - if err != nil { - t.Fatal(err) + require.ErrorContains(t, err, test.expectedErr) + } else { + require.NoError(t, err) } require.Equal(t, uint64(0), remainingGas) @@ -1055,9 +980,8 @@ func TestRewardManagerRun(t *testing.T) { caller: noRoleAddr, input: func() []byte { input, err := precompile.PackAllowFeeRecipients() - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.AllowFeeRecipientsGasCost, @@ -1068,9 +992,8 @@ func TestRewardManagerRun(t *testing.T) { caller: noRoleAddr, input: func() []byte { input, err := precompile.PackSetRewardAddress(testAddr) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.SetRewardAddressGasCost, @@ -1081,9 +1004,8 @@ func TestRewardManagerRun(t *testing.T) { caller: noRoleAddr, input: func() []byte { input, err := precompile.PackDisableRewards() - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.DisableRewardsGasCost, @@ -1094,9 +1016,8 @@ func TestRewardManagerRun(t *testing.T) { caller: enabledAddr, input: func() []byte { input, err := precompile.PackAllowFeeRecipients() - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.AllowFeeRecipientsGasCost, @@ -1107,9 +1028,8 @@ func TestRewardManagerRun(t *testing.T) { caller: enabledAddr, input: func() []byte { input, err := precompile.PackSetRewardAddress(testAddr) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.SetRewardAddressGasCost, @@ -1120,9 +1040,8 @@ func TestRewardManagerRun(t *testing.T) { caller: enabledAddr, input: func() []byte { input, err := precompile.PackDisableRewards() - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.DisableRewardsGasCost, @@ -1136,9 +1055,8 @@ func TestRewardManagerRun(t *testing.T) { }, input: func() []byte { input, err := precompile.PackCurrentRewardAddress() - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.CurrentRewardAddressGasCost, @@ -1211,9 +1129,8 @@ func TestRewardManagerRun(t *testing.T) { caller: enabledAddr, input: func() []byte { input, err := precompile.PackAllowFeeRecipients() - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.AllowFeeRecipientsGasCost, @@ -1224,9 +1141,8 @@ func TestRewardManagerRun(t *testing.T) { caller: enabledAddr, input: func() []byte { input, err := precompile.PackSetRewardAddress(testAddr) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.SetRewardAddressGasCost, @@ -1237,9 +1153,8 @@ func TestRewardManagerRun(t *testing.T) { caller: enabledAddr, input: func() []byte { input, err := precompile.PackSetRewardAddress(testAddr) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.SetRewardAddressGasCost - 1, @@ -1250,9 +1165,8 @@ func TestRewardManagerRun(t *testing.T) { caller: enabledAddr, input: func() []byte { input, err := precompile.PackAllowFeeRecipients() - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.AllowFeeRecipientsGasCost - 1, @@ -1263,9 +1177,8 @@ func TestRewardManagerRun(t *testing.T) { caller: enabledAddr, input: func() []byte { input, err := precompile.PackCurrentRewardAddress() - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.CurrentRewardAddressGasCost - 1, @@ -1276,9 +1189,8 @@ func TestRewardManagerRun(t *testing.T) { caller: enabledAddr, input: func() []byte { input, err := precompile.PackAreFeeRecipientsAllowed() - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.AreFeeRecipientsAllowedGasCost - 1, @@ -1289,9 +1201,8 @@ func TestRewardManagerRun(t *testing.T) { caller: adminAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -1306,9 +1217,8 @@ func TestRewardManagerRun(t *testing.T) { caller: enabledAddr, input: func() []byte { input, err := precompile.PackModifyAllowList(noRoleAddr, precompile.AllowListEnabled) - if err != nil { - panic(err) - } + require.NoError(t, err) + return input }, suppliedGas: precompile.ModifyAllowListGasCost, @@ -1319,9 +1229,8 @@ func TestRewardManagerRun(t *testing.T) { t.Run(name, func(t *testing.T) { db := rawdb.NewMemoryDatabase() state, err := state.New(common.Hash{}, state.NewDatabase(db), nil) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + // Set up the state so that each address has the expected permissions at the start. precompile.SetRewardManagerAllowListStatus(state, adminAddr, precompile.AllowListAdmin) precompile.SetRewardManagerAllowListStatus(state, enabledAddr, precompile.AllowListEnabled) @@ -1337,16 +1246,9 @@ func TestRewardManagerRun(t *testing.T) { } ret, remainingGas, err := precompile.RewardManagerPrecompile.Run(&mockAccessibleState{state: state, blockContext: blockContext}, test.caller, precompile.RewardManagerAddress, test.input(), test.suppliedGas, test.readOnly) if len(test.expectedErr) != 0 { - if err == nil { - require.Failf(t, "run expectedly passed without error", "expected error %q", test.expectedErr) - } else { - require.True(t, strings.Contains(err.Error(), test.expectedErr), "expected error (%s) to contain substring (%s)", err, test.expectedErr) - } - return - } - - if err != nil { - t.Fatal(err) + require.ErrorContains(t, err, test.expectedErr) + } else { + require.NoError(t, err) } require.Equal(t, uint64(0), remainingGas) diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 2ad0043bda..8f938bcdbc 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -2507,15 +2507,11 @@ func TestAllowFeeRecipientDisabled(t *testing.T) { <-issuer - _, err = vm.BuildBlock() - assert.ErrorContains(t, err, "block failed verification") - - vm.miner.SetEtherbase(constants.BlackholeAddr) // set blackhole address + blk, err := vm.BuildBlock() + require.NoError(t, err) // this won't return an error since miner will set the etherbase to blackhole address - _, err = vm.BuildBlock() - if err != nil { - t.Fatal(err) - } + ethBlock := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock + assert.Equal(t, ethBlock.Coinbase(), constants.BlackholeAddr) } func TestAllowFeeRecipientEnabled(t *testing.T) { @@ -2586,9 +2582,7 @@ func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { genesis.Config.RewardManagerConfig = precompile.NewRewardManagerConfig(common.Big0, testEthAddrs[0:1], nil, nil) genesis.Config.AllowFeeRecipients = true // enable this in genesis to test if this is recognized by the reward manager genesisJSON, err := genesis.MarshalJSON() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) etherBase := common.HexToAddress("0x0123456789") // give custom ether base c := Config{} c.SetDefaults() @@ -2598,9 +2592,8 @@ func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), string(configJSON), "") defer func() { - if err := vm.Shutdown(); err != nil { - t.Fatal(err) - } + err := vm.Shutdown() + require.NoError(t, err) }() newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) @@ -2656,9 +2649,7 @@ func TestRewardManagerPrecompileAllowFeeRecipieints(t *testing.T) { genesis.Config.RewardManagerConfig = precompile.NewRewardManagerConfig(common.Big0, testEthAddrs[0:1], nil, nil) genesis.Config.AllowFeeRecipients = false // disable this in genesis genesisJSON, err := genesis.MarshalJSON() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) etherBase := common.HexToAddress("0x0123456789") // give custom ether base c := Config{} c.SetDefaults() diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go index b50db35bd9..c8e3c85dfb 100644 --- a/precompile/reward_manager.go +++ b/precompile/reward_manager.go @@ -48,7 +48,6 @@ var ( ErrCannotSetRewardAddress = errors.New("non-enabled cannot setRewardAddress") ErrCannotEnableBothRewards = errors.New("cannot enable both fee recipients and reward address at the same time") - ErrCannotEmptyRewardAddr = errors.New("cannot set empty reward address") ErrEmptyRewardAddress = errors.New("reward address cannot be empty") RewardManagerABI abi.ABI // will be initialized by init function From be399828d44cd6cc76fb06ae6488444d9a3c04b9 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 12 Oct 2022 20:34:02 +0300 Subject: [PATCH 43/64] add e2e tests and example contract --- abis/IAllowList.abi | 1 - abis/IRewardManager.abi | 1 - contract-examples/README.md | 2 +- .../contracts/ExampleFeeDistributor.sol | 30 --- .../contracts/ExampleRewardDistributor.sol | 72 +++++++ .../scripts/deployExampleRewardDistributor.ts | 22 ++ .../test/ExampleRewardDistributor.ts | 203 ++++++++++++++++++ tests/e2e/genesis/reward_manager.json | 50 +++++ tests/e2e/solidity/suites.go | 13 +- 9 files changed, 360 insertions(+), 34 deletions(-) delete mode 100644 abis/IAllowList.abi delete mode 100644 abis/IRewardManager.abi delete mode 100644 contract-examples/contracts/ExampleFeeDistributor.sol create mode 100644 contract-examples/contracts/ExampleRewardDistributor.sol create mode 100644 contract-examples/scripts/deployExampleRewardDistributor.ts create mode 100644 contract-examples/test/ExampleRewardDistributor.ts create mode 100644 tests/e2e/genesis/reward_manager.json diff --git a/abis/IAllowList.abi b/abis/IAllowList.abi deleted file mode 100644 index ea89cd6408..0000000000 --- a/abis/IAllowList.abi +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/abis/IRewardManager.abi b/abis/IRewardManager.abi deleted file mode 100644 index d21d5bdc6b..0000000000 --- a/abis/IRewardManager.abi +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[],"name":"allowFeeRecipients","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"areFeeRecipientsAllowed","outputs":[{"internalType":"bool","name":"isAllowed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentRewardAddress","outputs":[{"internalType":"address","name":"rewardAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setRewardAddress","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/contract-examples/README.md b/contract-examples/README.md index d3848c1991..04b6f22c40 100644 --- a/contract-examples/README.md +++ b/contract-examples/README.md @@ -1,6 +1,6 @@ # Subnet EVM Contracts -CONTRACTS HERE ARE [ALPHA SOFTWARE](https://en.wikipedia.org/wiki/Software_release_life_cycle#Alpha) AND ARE NOT YET AUDITED. USE AT YOUR OWN RISK! +CONTRACTS HERE ARE [ALPHA SOFTWARE](https://en.wikipedia.org/wiki/Software_release_life_cycle#Alpha) AND ARE NOT AUDITED. USE AT YOUR OWN RISK! ## Introduction diff --git a/contract-examples/contracts/ExampleFeeDistributor.sol b/contract-examples/contracts/ExampleFeeDistributor.sol deleted file mode 100644 index 68b71baf39..0000000000 --- a/contract-examples/contracts/ExampleFeeDistributor.sol +++ /dev/null @@ -1,30 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "./IAllowList.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -contract ExampleFeeDistributor is Ownable { - mapping(address => bool) rewardAddresses; - uint256 rewardAddressCount; - - constructor() Ownable() {} - - function addRewardAddress(address addr) public onlyOwner { - require(!rewardAddresses[addr], "Already a reward address"); - - rewardAddresses[addr] = true; - rewardAddressCount++; - } - - function revoke(address addr) public onlyOwner { - require(rewardAddresses[addr], "Not a reward address"); - - rewardAddresses[addr] = false; - rewardAddressCount--; - } - - function claim() public { - require(rewardAddresses[msg.sender], "Not a reward address"); - } -} diff --git a/contract-examples/contracts/ExampleRewardDistributor.sol b/contract-examples/contracts/ExampleRewardDistributor.sol new file mode 100644 index 0000000000..e7514a113c --- /dev/null +++ b/contract-examples/contracts/ExampleRewardDistributor.sol @@ -0,0 +1,72 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IAllowList.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract ExampleRewardDistributor is Ownable { + mapping(address => uint256) rewardAddresses; // address to last claimed reward block number + + uint256 rewardRatePerBlk; // reward rate per block + + constructor(uint256 rewardRate) Ownable() { + rewardRatePerBlk = rewardRate; + } + + modifier _isRewardAddress(address addr) { + require(rewardAddresses[addr] > 0, "Not a reward address"); + _; + } + + function isRewardAddress(address addr) public view returns (bool) { + return rewardAddresses[addr] != 0; + } + + function getRewardRate() public view returns (uint256) { + return rewardRatePerBlk; + } + + function setRewardRate(uint256 rewardRate) public onlyOwner { + rewardRatePerBlk = rewardRate; + } + + function addRewardAddress(address addr) public onlyOwner { + require(rewardAddresses[addr] == 0, "Already a reward address"); + + rewardAddresses[addr] = block.number; + } + + function revoke(address addr) public onlyOwner _isRewardAddress(addr) { + uint256 reward = _estimateReward(addr); + delete rewardAddresses[addr]; + + payable(addr).transfer(reward); + } + + function claim() public _isRewardAddress(msg.sender) { + _reward(msg.sender); + } + + function estimateReward(address addr) public view returns (uint256) { + return _estimateReward(addr); + } + + function _estimateReward(address addr) private view returns (uint256) { + uint256 lastClaimedBlk = rewardAddresses[addr]; + if (lastClaimedBlk == 0) { + return 0; + } + uint256 currentBlk = block.number; + uint256 reward = (currentBlk - lastClaimedBlk) * rewardRatePerBlk; + return reward; + } + + function _reward(address addr) private { + uint256 reward = _estimateReward(addr); + require(reward > 0, "Nothing to claim"); + require(reward <= address(this).balance, "Not enough collected balance"); + + rewardAddresses[addr] = block.number; + payable(addr).transfer(reward); + } +} diff --git a/contract-examples/scripts/deployExampleRewardDistributor.ts b/contract-examples/scripts/deployExampleRewardDistributor.ts new file mode 100644 index 0000000000..63f40d10c2 --- /dev/null +++ b/contract-examples/scripts/deployExampleRewardDistributor.ts @@ -0,0 +1,22 @@ +import { + Contract, + ContractFactory +} from "ethers" +import { ethers } from "hardhat" + +const rewardRate = ethers.utils.parseEther("0.00001") + +const main = async (): Promise => { + const Contract: ContractFactory = await ethers.getContractFactory("ExampleRewardDistributor") + const contract: Contract = await Contract.deploy(rewardRate) + + await contract.deployed() + console.log(`Contract deployed to: ${contract.address}`) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/contract-examples/test/ExampleRewardDistributor.ts b/contract-examples/test/ExampleRewardDistributor.ts new file mode 100644 index 0000000000..b981008b76 --- /dev/null +++ b/contract-examples/test/ExampleRewardDistributor.ts @@ -0,0 +1,203 @@ +// (c) 2019-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { expect } from "chai"; +import { + BigNumber, + Contract, + ContractFactory, +} from "ethers" +import { ethers } from "hardhat" +import ts = require("typescript"); + +// make sure this is always an admin for minter precompile +const adminAddress: string = "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" +const REWARD_MANAGER_ADDRESS = "0x0200000000000000000000000000000000000004"; +const BLACKHOLE_ADDRESS = "0x0100000000000000000000000000000000000000"; +const rewardRate = ethers.utils.parseEther("0.00001") + +const ROLES = { + NONE: 0, + MINTER: 1, + ADMIN: 2 +}; + +describe("ExampleRewardDistributor", function () { + this.timeout("30s") + let owner: SignerWithAddress + let contract: Contract + let signer1: SignerWithAddress + let signer2: SignerWithAddress + before(async function () { + owner = await ethers.getSigner(adminAddress); + signer1 = (await ethers.getSigners())[1] + signer2 = (await ethers.getSigners())[2] + const Contract: ContractFactory = await ethers.getContractFactory("ExampleRewardDistributor", { signer: owner }) + contract = await Contract.deploy(rewardRate) + await contract.deployed() + const contractAddress: string = contract.address + console.log(`Contract deployed to: ${contractAddress}`) + + // Send a transaction to mine a new block + const tx = await owner.sendTransaction({ + to: signer1.address, + value: ethers.utils.parseEther("10") + }) + await tx.wait() + }); + + it("should add contract deployer as owner", async function () { + const contractOwnerAddr: string = await contract.owner() + expect(owner.address).to.equal(contractOwnerAddr) + }); + + // this contract is not selected as the reward address yet, so should not be able to receive fees + it("contract should not be able to receive fees", async function () { + const rewardManager = await ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDRESS, owner); + let rewardAddress = await rewardManager.currentRewardAddress(); + expect(rewardAddress).to.be.equal(BLACKHOLE_ADDRESS) + + let balance = await ethers.provider.getBalance(contract.address) + expect(balance).to.be.equal(0) + + // Send a transaction to mine a new block + const tx = await owner.sendTransaction({ + to: signer1.address, + value: ethers.utils.parseEther("0.0001") + }) + await tx.wait() + + balance = await ethers.provider.getBalance(contract.address) + expect(balance).to.be.equal(0) + }) + + it("should be appointed as reward address", async function () { + const rewardManager = await ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDRESS, owner); + let adminRole = await rewardManager.readAllowList(adminAddress); + expect(adminRole).to.be.equal(ROLES.ADMIN) + + let tx = await rewardManager.setRewardAddress(contract.address); + await tx.wait() + let rewardAddress = await rewardManager.currentRewardAddress(); + expect(rewardAddress).to.be.equal(contract.address) + }); + + it("contract should be able to receive fees", async function () { + let previousBalance = await ethers.provider.getBalance(contract.address) + + // Send a transaction to mine a new block + const tx = await owner.sendTransaction({ + to: signer1.address, + value: ethers.utils.parseEther("0.0001") + }) + await tx.wait() + + let balance = await ethers.provider.getBalance(contract.address) + expect(balance.gt(previousBalance)).to.be.true + }) + + it("should not let non-added addres to claim rewards", async function () { + const nonRewardAddress = signer1 + try { + await contract.connect(nonRewardAddress).claim(); + } + catch (err) { + expect(err.message).to.contains('Not a reward address') + return + } + expect.fail("should have errored") + }) + + it("should not revoke non-added address", async function () { + const nonRewardAddress = signer1 + try { + await contract.revoke(nonRewardAddress.address); + } + catch (err) { + expect(err.message).to.contains('Not a reward address') + return + } + expect.fail("should have errored") + }) + + it("should return 0 estimate reward for non-added address", async function () { + const nonRewardAddress = signer1 + const estimatedReward = await contract.estimateReward(nonRewardAddress.address) + expect(estimatedReward).to.be.equal(0) + }) + + it("should return false for isRewardAddress for non-added address", async function () { + const nonRewardAddress = signer1 + const isRewardAddress = await contract.isRewardAddress(nonRewardAddress.address) + expect(isRewardAddress).to.be.equal(false) + }) + + it("should able to add a reward address", async function () { + const rewardAddress = signer1 + let tx = await contract.addRewardAddress(rewardAddress.address); + await tx.wait() + const isRewardAddress = await contract.isRewardAddress(rewardAddress.address) + expect(isRewardAddress).to.be.equal(true) + }) + + it("should not be able to add a reward address twice", async function () { + const rewardAddress = signer1 + try { + await contract.addRewardAddress(rewardAddress.address); + } + catch (err) { + expect(err.message).to.contains('Already a reward address') + return + } + expect.fail("should have errored") + }) + + it("should distribute according to reward rate", async function () { + const rewardAddress = signer1 + let previousBalance = await ethers.provider.getBalance(rewardAddress.address) + let previousBlockNum = await ethers.provider.getBlockNumber() + + // send a transaction to mine a new block + let transfer = await owner.sendTransaction({ + to: signer2.address, + value: ethers.utils.parseEther("0.0001") + }) + + await transfer.wait() + + let tx = await contract.connect(rewardAddress).claim(); + await tx.wait() + + let balance = await ethers.provider.getBalance(rewardAddress.address) + + let txRec = await tx.wait() + let gasUsed: BigNumber = txRec.cumulativeGasUsed + let gasPrice: BigNumber = txRec.effectiveGasPrice + let txFee = gasUsed.mul(gasPrice) + let blockNum = await ethers.provider.getBlockNumber() + + let blockDiff = blockNum - previousBlockNum + expect(balance).to.be.equal(previousBalance.add(rewardRate.mul(blockDiff)).sub(txFee)) + }) + + it("should be able to revoke a reward address", async function () { + const rewardAddress = signer1 + let tx = await contract.revoke(rewardAddress.address); + await tx.wait() + const isRewardAddress = await contract.isRewardAddress(rewardAddress.address) + expect(isRewardAddress).to.be.equal(false) + }) + + it("should be able to receive rewards after revoke", async function () { + const nonRewardAddress = signer1 + try { + await contract.revoke(nonRewardAddress.address); + } + catch (err) { + expect(err.message).to.contains('Not a reward address') + return + } + expect.fail("should have errored") + }) +}); diff --git a/tests/e2e/genesis/reward_manager.json b/tests/e2e/genesis/reward_manager.json new file mode 100644 index 0000000000..6ebb2027f5 --- /dev/null +++ b/tests/e2e/genesis/reward_manager.json @@ -0,0 +1,50 @@ +{ + "config": { + "chainId": 99999, + "homesteadBlock": 0, + "eip150Block": 0, + "eip150Hash": "0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "subnetEVMTimestamp": 0, + "feeConfig": { + "gasLimit": 20000000, + "minBaseFee": 1000000000, + "targetGas": 100000000, + "baseFeeChangeDenominator": 48, + "minBlockGasCost": 0, + "maxBlockGasCost": 10000000, + "targetBlockRate": 2, + "blockGasCostStep": 500000 + }, + "rewardManagerConfig": { + "blockTimestamp": 0, + "adminAddresses": [ + "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" + ] + } + }, + "alloc": { + "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { + "balance": "0x52B7D2DCC80CD2E4000000" + }, + "0x0Fa8EA536Be85F32724D57A37758761B86416123": { + "balance": "0x52B7D2DCC80CD2E4000000" + } + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x00", + "gasLimit": "0x1312D00", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} diff --git a/tests/e2e/solidity/suites.go b/tests/e2e/solidity/suites.go index ef07428e7e..216f98dae6 100644 --- a/tests/e2e/solidity/suites.go +++ b/tests/e2e/solidity/suites.go @@ -84,6 +84,17 @@ var _ = utils.DescribePrecompile(func() { gomega.Expect(running).Should(gomega.BeFalse()) }) + ginkgo.It("reward manager", func() { + err := startSubnet("./tests/e2e/genesis/reward_manager.json") + gomega.Expect(err).Should(gomega.BeNil()) + running := runner.IsRunnerUp() + gomega.Expect(running).Should(gomega.BeTrue()) + runHardhatTests("./test/ExampleRewardManager.ts") + stopSubnet() + running = runner.IsRunnerUp() + gomega.Expect(running).Should(gomega.BeFalse()) + }) + // ADD YOUR PRECOMPILE HERE /* ginkgo.It("your precompile", func() { @@ -91,7 +102,7 @@ var _ = utils.DescribePrecompile(func() { gomega.Expect(err).Should(gomega.BeNil()) running := runner.IsRunnerUp() gomega.Expect(running).Should(gomega.BeTrue()) - runHardhatTests("./test/Example{YourPrecompile}Test.ts") + runHardhatTests("./test/{YourPrecompileTest}.ts") stopSubnet() running = runner.IsRunnerUp() gomega.Expect(running).Should(gomega.BeFalse()) From 1e1abc6e45594c8e3736353c83937d2f70b1ff4f Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Thu, 13 Oct 2022 14:28:52 +0300 Subject: [PATCH 44/64] add initial config to test --- core/stateful_precompile_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/stateful_precompile_test.go b/core/stateful_precompile_test.go index 058c7f1c96..5a9cf5ee79 100644 --- a/core/stateful_precompile_test.go +++ b/core/stateful_precompile_test.go @@ -754,7 +754,10 @@ func TestFeeConfigManagerRun(t *testing.T) { }, suppliedGas: precompile.SetFeeConfigGasCost, readOnly: false, - expectedRes: []byte{}, + expectedRes: nil, + config: &precompile.FeeConfigManagerConfig{ + InitialFeeConfig: &testFeeConfig, + }, expectedErr: "cannot be greater than maxBlockGasCost", assertState: func(t *testing.T, state *state.StateDB) { feeConfig := precompile.GetStoredFeeConfig(state) From 25e8ab486feaa2e627e8443f2be3323763e1f8bd Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 8 Nov 2022 18:17:36 +0300 Subject: [PATCH 45/64] Update contract-examples/tasks.ts Co-authored-by: Darioush Jalali --- contract-examples/tasks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contract-examples/tasks.ts b/contract-examples/tasks.ts index cb2393ed16..3566a14739 100644 --- a/contract-examples/tasks.ts +++ b/contract-examples/tasks.ts @@ -238,7 +238,7 @@ task("rewardManager:currentRewardAddress", "a task to get the current configured }) // npx hardhat rewardManager:areFeeRecipientsAllowed --network local --address [address] -task("rewardManager:areFeeRecipientsAllowed", "a task to get wheter the fee recipients are allowed") +task("rewardManager:areFeeRecipientsAllowed", "a task to get whether the fee recipients are allowed") .setAction(async (_, hre) => { const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) const result = await rewardManager.areFeeRecipientsAllowed() From 55a983096d5d4f582103596d8bd7911fa7ed03fa Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 8 Nov 2022 18:18:06 +0300 Subject: [PATCH 46/64] Update contract-examples/test/ExampleRewardDistributor.ts Co-authored-by: Darioush Jalali --- contract-examples/test/ExampleRewardDistributor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contract-examples/test/ExampleRewardDistributor.ts b/contract-examples/test/ExampleRewardDistributor.ts index b981008b76..bebfbf1e67 100644 --- a/contract-examples/test/ExampleRewardDistributor.ts +++ b/contract-examples/test/ExampleRewardDistributor.ts @@ -97,7 +97,7 @@ describe("ExampleRewardDistributor", function () { expect(balance.gt(previousBalance)).to.be.true }) - it("should not let non-added addres to claim rewards", async function () { + it("should not let non-added address to claim rewards", async function () { const nonRewardAddress = signer1 try { await contract.connect(nonRewardAddress).claim(); From c264ce8594a40e0ffd99cb40943972f805a8cd05 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 8 Nov 2022 18:18:16 +0300 Subject: [PATCH 47/64] Update contract-examples/test/ExampleRewardDistributor.ts Co-authored-by: Darioush Jalali --- contract-examples/test/ExampleRewardDistributor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contract-examples/test/ExampleRewardDistributor.ts b/contract-examples/test/ExampleRewardDistributor.ts index bebfbf1e67..601eb730c6 100644 --- a/contract-examples/test/ExampleRewardDistributor.ts +++ b/contract-examples/test/ExampleRewardDistributor.ts @@ -133,7 +133,7 @@ describe("ExampleRewardDistributor", function () { expect(isRewardAddress).to.be.equal(false) }) - it("should able to add a reward address", async function () { + it("should be able to add a reward address", async function () { const rewardAddress = signer1 let tx = await contract.addRewardAddress(rewardAddress.address); await tx.wait() From e8b03a16074d635d94c72ed672923f191dbd34ad Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 8 Nov 2022 20:14:04 +0300 Subject: [PATCH 48/64] review fixes --- consensus/consensus.go | 4 +- .../contracts/ExampleRewardDistributor.sol | 36 +++++++------- contract-examples/tasks.ts | 5 +- .../test/ExampleRewardDistributor.ts | 7 ++- core/chain_makers.go | 2 +- plugin/evm/vm_test.go | 48 +++++++++---------- precompile/config_test.go | 10 ++-- precompile/reward_manager.go | 7 --- 8 files changed, 58 insertions(+), 61 deletions(-) diff --git a/consensus/consensus.go b/consensus/consensus.go index 5bbf7f4108..89503bd996 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -58,8 +58,8 @@ type ChainHeaderReader interface { // GetFeeConfigAt retrieves the fee config and last changed block number at block header. GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) - // GetCoinbaseAt retrieves the configured coinbase address at [header]. If fee recipients are allowed, returns true in the second return value. - GetCoinbaseAt(header *types.Header) (common.Address, bool, error) + // GetCoinbaseAt retrieves the configured coinbase address at [parent]. If fee recipients are allowed, returns true in the second return value. + GetCoinbaseAt(parent *types.Header) (common.Address, bool, error) } // ChainReader defines a small collection of methods needed to access the local diff --git a/contract-examples/contracts/ExampleRewardDistributor.sol b/contract-examples/contracts/ExampleRewardDistributor.sol index e7514a113c..f584e96bf1 100644 --- a/contract-examples/contracts/ExampleRewardDistributor.sol +++ b/contract-examples/contracts/ExampleRewardDistributor.sol @@ -4,6 +4,10 @@ pragma solidity ^0.8.0; import "./IAllowList.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; +// ExampleRewardDistributor is a sample contract to be used in conjunction +// with the RewardManager precompile. This contract allows its owner to +// add reward addresses, each of which can claim fees that accumulate to the +// contract (up to rewardRate per block). The owner can also adjust rewardRate. contract ExampleRewardDistributor is Ownable { mapping(address => uint256) rewardAddresses; // address to last claimed reward block number @@ -26,32 +30,41 @@ contract ExampleRewardDistributor is Ownable { return rewardRatePerBlk; } + // setRewardRate sets the reward rate per block function setRewardRate(uint256 rewardRate) public onlyOwner { rewardRatePerBlk = rewardRate; } + // addRewardAddress adds an address to the reward list + // and sets the last claimed block to the current block function addRewardAddress(address addr) public onlyOwner { require(rewardAddresses[addr] == 0, "Already a reward address"); rewardAddresses[addr] = block.number; } + // revoke removes address from reward addresses and transfers any remaining rewards to the address function revoke(address addr) public onlyOwner _isRewardAddress(addr) { - uint256 reward = _estimateReward(addr); + uint256 reward = estimateReward(addr); delete rewardAddresses[addr]; - payable(addr).transfer(reward); + // reward only if there is any to reward + if (reward > 0 && reward <= address(this).balance) { + payable(addr).transfer(reward); + } } + // claim transfers any rewards to the address function claim() public _isRewardAddress(msg.sender) { - _reward(msg.sender); - } + uint256 reward = estimateReward(addr); + require(reward <= address(this).balance, "Not enough collected balance"); - function estimateReward(address addr) public view returns (uint256) { - return _estimateReward(addr); + rewardAddresses[addr] = block.number; + payable(addr).transfer(reward); } - function _estimateReward(address addr) private view returns (uint256) { + // estimateReward returns the estimated reward for the address + function estimateReward(address addr) public view returns (uint256) { uint256 lastClaimedBlk = rewardAddresses[addr]; if (lastClaimedBlk == 0) { return 0; @@ -60,13 +73,4 @@ contract ExampleRewardDistributor is Ownable { uint256 reward = (currentBlk - lastClaimedBlk) * rewardRatePerBlk; return reward; } - - function _reward(address addr) private { - uint256 reward = _estimateReward(addr); - require(reward > 0, "Nothing to claim"); - require(reward <= address(this).balance, "Not enough collected balance"); - - rewardAddresses[addr] = block.number; - payable(addr).transfer(reward); - } } diff --git a/contract-examples/tasks.ts b/contract-examples/tasks.ts index cb2393ed16..09ab0f813d 100644 --- a/contract-examples/tasks.ts +++ b/contract-examples/tasks.ts @@ -225,7 +225,7 @@ task("feeManager:readRole", "a task to get the network deployer minter list") }) -// npx hardhat rewardManager:readRole --network local --address [address] +// npx hardhat rewardManager:currentRewardAddress --network local task("rewardManager:currentRewardAddress", "a task to get the current configured rewarding address") .setAction(async (_, hre) => { const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) @@ -237,7 +237,7 @@ task("rewardManager:currentRewardAddress", "a task to get the current configured } }) -// npx hardhat rewardManager:areFeeRecipientsAllowed --network local --address [address] +// npx hardhat rewardManager:areFeeRecipientsAllowed --network local task("rewardManager:areFeeRecipientsAllowed", "a task to get wheter the fee recipients are allowed") .setAction(async (_, hre) => { const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) @@ -262,6 +262,7 @@ task("rewardManager:allowFeeRecipients", "allows custom fee recipients to receiv console.log(result) }) +// npx hardhat rewardManager:disableRewards --network local task("rewardManager:disableRewards", "disables all rewards, and starts burning fees.") .setAction(async (_, hre) => { const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) diff --git a/contract-examples/test/ExampleRewardDistributor.ts b/contract-examples/test/ExampleRewardDistributor.ts index b981008b76..1f237db45e 100644 --- a/contract-examples/test/ExampleRewardDistributor.ts +++ b/contract-examples/test/ExampleRewardDistributor.ts @@ -189,10 +189,13 @@ describe("ExampleRewardDistributor", function () { expect(isRewardAddress).to.be.equal(false) }) - it("should be able to receive rewards after revoke", async function () { + it("should be able to claim after revoke", async function () { const nonRewardAddress = signer1 + const isRewardAddress = await contract.isRewardAddress(nonRewardAddress.address) + expect(isRewardAddress).to.be.equal(false) + try { - await contract.revoke(nonRewardAddress.address); + await contract.connect(nonRewardAddress).claim(); } catch (err) { expect(err.message).to.contains('Not a reward address') diff --git a/core/chain_makers.go b/core/chain_makers.go index 2fda72e92a..a441183ac3 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -323,6 +323,6 @@ func (cr *fakeChainReader) GetFeeConfigAt(parent *types.Header) (commontype.FeeC return cr.config.FeeConfig, nil, nil } -func (cr *fakeChainReader) GetCoinbaseAt(header *types.Header) (common.Address, bool, error) { +func (cr *fakeChainReader) GetCoinbaseAt(parent *types.Header) (common.Address, bool, error) { return constants.BlackholeAddr, cr.config.AllowFeeRecipients, nil } diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 8f938bcdbc..d9ef87a30e 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -224,8 +224,8 @@ func TestVMConfig(t *testing.T) { enabledEthAPIs := []string{"debug"} configJSON := fmt.Sprintf("{\"rpc-tx-fee-cap\": %g,\"eth-apis\": %s}", txFeeCap, fmt.Sprintf("[%q]", enabledEthAPIs[0])) _, vm, _, _ := GenesisVM(t, false, "", configJSON, "") - assert.Equal(t, vm.config.RPCTxFeeCap, txFeeCap, "Tx Fee Cap should be set") - assert.Equal(t, vm.config.EthAPIs(), enabledEthAPIs, "EnabledEthAPIs should be set") + require.Equal(t, vm.config.RPCTxFeeCap, txFeeCap, "Tx Fee Cap should be set") + require.Equal(t, vm.config.EthAPIs(), enabledEthAPIs, "EnabledEthAPIs should be set") assert.NoError(t, vm.Shutdown()) } @@ -239,7 +239,7 @@ func TestVMConfigDefaults(t *testing.T) { vmConfig.SetDefaults() vmConfig.RPCTxFeeCap = txFeeCap vmConfig.EnabledEthAPIs = enabledEthAPIs - assert.Equal(t, vmConfig, vm.config, "VM Config should match default with overrides") + require.Equal(t, vmConfig, vm.config, "VM Config should match default with overrides") assert.NoError(t, vm.Shutdown()) } @@ -249,7 +249,7 @@ func TestVMNilConfig(t *testing.T) { // VM Config should match defaults if no config is passed in var vmConfig Config vmConfig.SetDefaults() - assert.Equal(t, vmConfig, vm.config, "VM Config should match default config") + require.Equal(t, vmConfig, vm.config, "VM Config should match default config") assert.NoError(t, vm.Shutdown()) } @@ -258,8 +258,8 @@ func TestVMContinuousProfiler(t *testing.T) { profilerFrequency := 500 * time.Millisecond configJSON := fmt.Sprintf("{\"continuous-profiler-dir\": %q,\"continuous-profiler-frequency\": \"500ms\"}", profilerDir) _, vm, _, _ := GenesisVM(t, false, "", configJSON, "") - assert.Equal(t, vm.config.ContinuousProfilerDir, profilerDir, "profiler dir should be set") - assert.Equal(t, vm.config.ContinuousProfilerFrequency.Duration, profilerFrequency, "profiler frequency should be set") + require.Equal(t, vm.config.ContinuousProfilerDir, profilerDir, "profiler dir should be set") + require.Equal(t, vm.config.ContinuousProfilerFrequency.Duration, profilerFrequency, "profiler frequency should be set") // Sleep for twice the frequency of the profiler to give it time // to generate the first profile. @@ -2235,7 +2235,7 @@ func TestTxAllowListSuccessfulTx(t *testing.T) { t.Fatalf("Expected number of txs to be %d, but found %d", 1, txs.Len()) } - assert.Equal(t, signedTx0.Hash(), txs[0].Hash()) + require.Equal(t, signedTx0.Hash(), txs[0].Hash()) } // Test that the tx allow list allows whitelisted transactions and blocks non-whitelisted addresses @@ -2322,7 +2322,7 @@ func TestTxAllowListDisablePrecompile(t *testing.T) { if txs.Len() != 1 { t.Fatalf("Expected number of txs to be %d, but found %d", 1, txs.Len()) } - assert.Equal(t, signedTx0.Hash(), txs[0].Hash()) + require.Equal(t, signedTx0.Hash(), txs[0].Hash()) // verify the issued block is after the network upgrade assert.True(t, block.Timestamp().Cmp(big.NewInt(disableAllowListTimestamp.Unix())) >= 0) @@ -2344,7 +2344,7 @@ func TestTxAllowListDisablePrecompile(t *testing.T) { if txs.Len() != 1 { t.Fatalf("Expected number of txs to be %d, but found %d", 1, txs.Len()) } - assert.Equal(t, signedTx1.Hash(), txs[0].Hash()) + require.Equal(t, signedTx1.Hash(), txs[0].Hash()) } // Test that the fee manager changes fee configuration @@ -2403,7 +2403,7 @@ func TestFeeManagerChangeFee(t *testing.T) { // Contract is initialized but no preconfig is given, reader should return genesis fee config feeConfig, lastChangedAt, err := vm.blockChain.GetFeeConfigAt(vm.blockChain.Genesis().Header()) assert.NoError(t, err) - assert.EqualValues(t, feeConfig, testLowFeeConfig) + require.EqualValues(t, feeConfig, testLowFeeConfig) assert.Zero(t, vm.blockChain.CurrentBlock().Number().Cmp(lastChangedAt)) // set a different fee config now @@ -2445,8 +2445,8 @@ func TestFeeManagerChangeFee(t *testing.T) { // Contract is initialized but no state is given, reader should return genesis fee config feeConfig, lastChangedAt, err = vm.blockChain.GetFeeConfigAt(block.Header()) assert.NoError(t, err) - assert.EqualValues(t, testHighFeeConfig, feeConfig) - assert.EqualValues(t, vm.blockChain.CurrentBlock().Number(), lastChangedAt) + require.EqualValues(t, testHighFeeConfig, feeConfig) + require.EqualValues(t, vm.blockChain.CurrentBlock().Number(), lastChangedAt) // should fail, with same params since fee is higher now tx2 := types.NewTx(&types.DynamicFeeTx{ @@ -2511,7 +2511,7 @@ func TestAllowFeeRecipientDisabled(t *testing.T) { require.NoError(t, err) // this won't return an error since miner will set the etherbase to blackhole address ethBlock := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - assert.Equal(t, ethBlock.Coinbase(), constants.BlackholeAddr) + require.Equal(t, ethBlock.Coinbase(), constants.BlackholeAddr) } func TestAllowFeeRecipientEnabled(t *testing.T) { @@ -2563,7 +2563,7 @@ func TestAllowFeeRecipientEnabled(t *testing.T) { t.Fatalf("Expected new block to match") } ethBlock := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - assert.Equal(t, ethBlock.Coinbase(), etherBase) + require.Equal(t, ethBlock.Coinbase(), etherBase) // Verify that etherBase has received fees blkState, err := vm.blockChain.StateAt(ethBlock.Root()) if err != nil { @@ -2571,14 +2571,13 @@ func TestAllowFeeRecipientEnabled(t *testing.T) { } balance := blkState.GetBalance(etherBase) - assert.Equal(t, 1, balance.Cmp(common.Big0)) + require.Equal(t, 1, balance.Cmp(common.Big0)) } func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { genesis := &core.Genesis{} - if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { - t.Fatal(err) - } + require.NoError(t, genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM))) + genesis.Config.RewardManagerConfig = precompile.NewRewardManagerConfig(common.Big0, testEthAddrs[0:1], nil, nil) genesis.Config.AllowFeeRecipients = true // enable this in genesis to test if this is recognized by the reward manager genesisJSON, err := genesis.MarshalJSON() @@ -2638,14 +2637,13 @@ func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { require.NoError(t, err) balance := blkState.GetBalance(testAddr) - assert.Equal(t, 1, balance.Cmp(common.Big0)) + require.Equal(t, 1, balance.Cmp(common.Big0)) } func TestRewardManagerPrecompileAllowFeeRecipieints(t *testing.T) { genesis := &core.Genesis{} - if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { - t.Fatal(err) - } + require.NoError(t, genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM))) + genesis.Config.RewardManagerConfig = precompile.NewRewardManagerConfig(common.Big0, testEthAddrs[0:1], nil, nil) genesis.Config.AllowFeeRecipients = false // disable this in genesis genesisJSON, err := genesis.MarshalJSON() @@ -2659,9 +2657,7 @@ func TestRewardManagerPrecompileAllowFeeRecipieints(t *testing.T) { issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), string(configJSON), "") defer func() { - if err := vm.Shutdown(); err != nil { - t.Fatal(err) - } + require.NoError(t, vm.Shutdown()) }() newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) @@ -2705,5 +2701,5 @@ func TestRewardManagerPrecompileAllowFeeRecipieints(t *testing.T) { require.NoError(t, err) balance := blkState.GetBalance(etherBase) - assert.Equal(t, 1, balance.Cmp(common.Big0)) + require.Equal(t, 1, balance.Cmp(common.Big0)) } diff --git a/precompile/config_test.go b/precompile/config_test.go index 6d7bfc442c..a396cec543 100644 --- a/precompile/config_test.go +++ b/precompile/config_test.go @@ -167,7 +167,7 @@ func TestEqualTxAllowListConfig(t *testing.T) { expected: false, }, { - name: "different version", + name: "different timestamp", config: NewTxAllowListConfig(big.NewInt(3), admins, enableds), other: NewTxAllowListConfig(big.NewInt(4), admins, enableds), expected: false, @@ -222,7 +222,7 @@ func TestEqualContractDeployerAllowListConfig(t *testing.T) { expected: false, }, { - name: "different version", + name: "different timestamp", config: NewContractDeployerAllowListConfig(big.NewInt(3), admins, enableds), other: NewContractDeployerAllowListConfig(big.NewInt(4), admins, enableds), expected: false, @@ -265,7 +265,7 @@ func TestEqualContractNativeMinterConfig(t *testing.T) { expected: false, }, { - name: "different version", + name: "different timestamps", config: NewContractNativeMinterConfig(big.NewInt(3), admins, nil, nil), other: NewContractNativeMinterConfig(big.NewInt(4), admins, nil, nil), expected: false, @@ -345,7 +345,7 @@ func TestEqualFeeConfigManagerConfig(t *testing.T) { expected: false, }, { - name: "different version", + name: "different timestamp", config: NewFeeManagerConfig(big.NewInt(3), admins, nil, nil), other: NewFeeManagerConfig(big.NewInt(4), admins, nil, nil), expected: false, @@ -411,7 +411,7 @@ func TestEqualRewardManagerConfig(t *testing.T) { expected: false, }, { - name: "different version", + name: "different timestamp", config: NewRewardManagerConfig(big.NewInt(3), admins, nil, nil), other: NewRewardManagerConfig(big.NewInt(4), admins, nil, nil), expected: false, diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go index 1ee0c62217..1c12bffbdd 100644 --- a/precompile/reward_manager.go +++ b/precompile/reward_manager.go @@ -30,13 +30,6 @@ const ( RewardManagerRawABI = "[{\"inputs\":[],\"name\":\"allowFeeRecipients\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"areFeeRecipientsAllowed\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isAllowed\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"currentRewardAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"rewardAddress\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"disableRewards\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"readAllowList\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"role\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setEnabled\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setNone\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setRewardAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" ) -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ = errors.New - _ = big.NewInt - _ = strings.NewReader -) - // Singleton StatefulPrecompiledContract and signatures. var ( _ StatefulPrecompileConfig = &RewardManagerConfig{} From 58753ab4c46f7e665773933f461a151714848881 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 9 Nov 2022 13:37:03 +0300 Subject: [PATCH 49/64] Update consensus/dummy/consensus.go Co-authored-by: aaronbuchwald --- consensus/dummy/consensus.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index d1449e1cbf..36225601a0 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -75,7 +75,7 @@ func (self *DummyEngine) verifyCoinbase(config *params.ChainConfig, header *type // get the coinbase configured at parent configuredAddressAtParent, isAllowFeeRecipients, err := chain.GetCoinbaseAt(parent) if err != nil { - return err + return fmt.Errorf("failed to get coinbase at %v: %w", header.Hash(), err) } if isAllowFeeRecipients { From 0d479354ec19050e94cc3367a93b464399efe82b Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 9 Nov 2022 13:45:53 +0300 Subject: [PATCH 50/64] Update params/config.go Co-authored-by: aaronbuchwald --- params/config.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/params/config.go b/params/config.go index a2cf69ecf9..d097aedad2 100644 --- a/params/config.go +++ b/params/config.go @@ -555,7 +555,8 @@ func (c *ChainConfig) AvalancheRules(blockNum, blockTimestamp *big.Int) Rules { return rules } -// GetFeeConfig implements precompile.ChainConfig interface. +// GetFeeConfig returns the original FeeConfig contained in the ChainConfig (will not be modified by FeeConfigManager. +// Implements precompile.ChainConfig interface. func (c *ChainConfig) GetFeeConfig() commontype.FeeConfig { return c.FeeConfig } From 6e4fd0313d6c890d86b36a6e39d6bb28458c3770 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 9 Nov 2022 13:46:12 +0300 Subject: [PATCH 51/64] Update params/config.go Co-authored-by: aaronbuchwald --- params/config.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/params/config.go b/params/config.go index d097aedad2..56f89e44dd 100644 --- a/params/config.go +++ b/params/config.go @@ -561,7 +561,8 @@ func (c *ChainConfig) GetFeeConfig() commontype.FeeConfig { return c.FeeConfig } -// AllowedFeeRecipients implements precompile.ChainConfig interface. +// AllowedFeeRecipients returns whether the original AllowedFeeRecipients parameter (will not be modified by the RewardManager. +// Implements precompile.ChainConfig interface. func (c *ChainConfig) AllowedFeeRecipients() bool { return c.AllowFeeRecipients } From 22553b1e0877437d409f3f250a7bf6b3fcd4355e Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 9 Nov 2022 14:23:47 +0300 Subject: [PATCH 52/64] fixs after review --- consensus/dummy/consensus.go | 9 ++++----- .../test/ExampleRewardDistributor.ts | 5 +++++ plugin/evm/block_verification.go | 9 --------- plugin/evm/vm_test.go | 19 +++++++++++++++++++ 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index 36225601a0..40eb1e7cb4 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -196,12 +196,11 @@ func (self *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header if err := self.verifyHeaderGasFields(config, header, parent, chain); err != nil { return err } - // Ensure that coinbase is valid if reward manager is enabled - if config.IsRewardManager(timestamp) { - if err := self.verifyCoinbase(config, header, parent, chain); err != nil { - return err - } + // Veriy the header's coinbase address + if err := self.verifyCoinbase(config, header, parent, chain); err != nil { + return err } + // Verify the header's timestamp if header.Time > uint64(self.clock.Time().Add(allowedFutureBlockTime).Unix()) { return consensus.ErrFutureBlock diff --git a/contract-examples/test/ExampleRewardDistributor.ts b/contract-examples/test/ExampleRewardDistributor.ts index 58b4be2239..e498b80d2c 100644 --- a/contract-examples/test/ExampleRewardDistributor.ts +++ b/contract-examples/test/ExampleRewardDistributor.ts @@ -61,6 +61,8 @@ describe("ExampleRewardDistributor", function () { let balance = await ethers.provider.getBalance(contract.address) expect(balance).to.be.equal(0) + let firstBHBalance = await ethers.provider.getBalance(BLACKHOLE_ADDRESS) + // Send a transaction to mine a new block const tx = await owner.sendTransaction({ to: signer1.address, @@ -70,6 +72,9 @@ describe("ExampleRewardDistributor", function () { balance = await ethers.provider.getBalance(contract.address) expect(balance).to.be.equal(0) + + let secondBHBalance = await ethers.provider.getBalance(BLACKHOLE_ADDRESS) + expect(secondBHBalance).to.be.greaterThan(firstBHBalance) }) it("should be appointed as reward address", async function () { diff --git a/plugin/evm/block_verification.go b/plugin/evm/block_verification.go index d46cf22855..a020110d0c 100644 --- a/plugin/evm/block_verification.go +++ b/plugin/evm/block_verification.go @@ -9,11 +9,9 @@ import ( "github.com/ethereum/go-ethereum/common" - "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/trie" - "github.com/ava-labs/subnet-evm/vmerrs" ) var legacyMinGasPrice = big.NewInt(params.MinGasPrice) @@ -96,13 +94,6 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { if uncleHash != ethHeader.UncleHash { return fmt.Errorf("invalid uncle hash %v does not match calculated uncle hash %v", ethHeader.UncleHash, uncleHash) } - // if reward manager is enabled, Coinbase depends on state. State is not available here, so skip checking the coinbase here. - if !rules.IsRewardManagerEnabled { - // If Subnet EVM or AllowFeeRecipients is not enabled, Coinbase must be BlackholeAddr. - if (!rules.IsSubnetEVM || !b.vm.chainConfig.AllowFeeRecipients) && b.ethBlock.Coinbase() != constants.BlackholeAddr { - return fmt.Errorf("%w: %v does not match required blackhole address %v", vmerrs.ErrInvalidCoinbase, ethHeader.Coinbase, constants.BlackholeAddr) - } - } // Block must not have any uncles if len(b.ethBlock.Uncles()) > 0 { return errUnclesUnsupported diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index d9ef87a30e..bf869d2fae 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -20,6 +20,7 @@ import ( "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/precompile" "github.com/ava-labs/subnet-evm/trie" + "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" @@ -2512,6 +2513,22 @@ func TestAllowFeeRecipientDisabled(t *testing.T) { ethBlock := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock require.Equal(t, ethBlock.Coinbase(), constants.BlackholeAddr) + + // Create empty block from blk + internalBlk := blk.(*chain.BlockWrapper).Block.(*Block) + modifiedHeader := types.CopyHeader(internalBlk.ethBlock.Header()) + modifiedHeader.Coinbase = common.HexToAddress("0x0123456789") // set non-blackhole address by force + modifiedBlock := types.NewBlock( + modifiedHeader, + internalBlk.ethBlock.Transactions(), + nil, + nil, + new(trie.Trie), + ) + + modifiedBlk := vm.newBlock(modifiedBlock) + + require.ErrorIs(t, modifiedBlk.Verify(), vmerrs.ErrInvalidCoinbase) } func TestAllowFeeRecipientEnabled(t *testing.T) { @@ -2582,12 +2599,14 @@ func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { genesis.Config.AllowFeeRecipients = true // enable this in genesis to test if this is recognized by the reward manager genesisJSON, err := genesis.MarshalJSON() require.NoError(t, err) + etherBase := common.HexToAddress("0x0123456789") // give custom ether base c := Config{} c.SetDefaults() c.FeeRecipient = etherBase.String() configJSON, err := json.Marshal(c) require.NoError(t, err) + issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), string(configJSON), "") defer func() { From 8d42bf71edb227ac0bd72938e2a681982745b050 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 9 Nov 2022 14:41:55 +0300 Subject: [PATCH 53/64] add string method to reward manager --- precompile/reward_manager.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go index 1c12bffbdd..91a7b09867 100644 --- a/precompile/reward_manager.go +++ b/precompile/reward_manager.go @@ -7,6 +7,7 @@ package precompile import ( + "encoding/json" "errors" "fmt" "math/big" @@ -189,6 +190,12 @@ func (c *RewardManagerConfig) Verify() error { return nil } +// String returns a string representation of the RewardManagerConfig. +func (c *RewardManagerConfig) String() string { + bytes, _ := json.Marshal(c) + return string(bytes) +} + // GetRewardManagerAllowListStatus returns the role of [address] for the RewardManager list. func GetRewardManagerAllowListStatus(stateDB StateDB, address common.Address) AllowListRole { return getAllowListStatus(stateDB, RewardManagerAddress, address) From f1110af5e9589b792da424efdb069d5b3bb88d85 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 9 Nov 2022 20:05:21 +0300 Subject: [PATCH 54/64] revert coinbase syntactic verification --- consensus/dummy/consensus.go | 9 ++++++--- plugin/evm/block_verification.go | 14 ++++++++++++++ precompile/allow_list.go | 1 - 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index 40eb1e7cb4..eb1fdd4930 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -196,9 +196,12 @@ func (self *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header if err := self.verifyHeaderGasFields(config, header, parent, chain); err != nil { return err } - // Veriy the header's coinbase address - if err := self.verifyCoinbase(config, header, parent, chain); err != nil { - return err + // Ensure that coinbase is valid if reward manager is enabled + // If reward manager is disabled, this will be handled in syntactic verification + if config.IsRewardManager(timestamp) { + if err := self.verifyCoinbase(config, header, parent, chain); err != nil { + return err + } } // Verify the header's timestamp diff --git a/plugin/evm/block_verification.go b/plugin/evm/block_verification.go index a020110d0c..e6d1fc4638 100644 --- a/plugin/evm/block_verification.go +++ b/plugin/evm/block_verification.go @@ -9,9 +9,11 @@ import ( "github.com/ethereum/go-ethereum/common" + "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/trie" + "github.com/ava-labs/subnet-evm/vmerrs" ) var legacyMinGasPrice = big.NewInt(params.MinGasPrice) @@ -94,6 +96,18 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { if uncleHash != ethHeader.UncleHash { return fmt.Errorf("invalid uncle hash %v does not match calculated uncle hash %v", ethHeader.UncleHash, uncleHash) } + + switch { + case rules.IsRewardManagerEnabled: + // if reward manager is enabled, Coinbase depends on state. + // State is not available here, so skip checking the coinbase here. + case rules.IsSubnetEVM && b.vm.chainConfig.AllowFeeRecipients: + // If Subnet EVM and AllowFeeRecipients is enabled we don't check the coinbase. + case b.ethBlock.Coinbase() != constants.BlackholeAddr: + // If Subnet EVM or AllowFeeRecipients is not enabled, Coinbase must be BlackholeAddr. + return fmt.Errorf("%w: %v does not match required blackhole address %v", vmerrs.ErrInvalidCoinbase, ethHeader.Coinbase, constants.BlackholeAddr) + } + // Block must not have any uncles if len(b.ethBlock.Uncles()) > 0 { return errUnclesUnsupported diff --git a/precompile/allow_list.go b/precompile/allow_list.go index 7515253954..e411b5eba4 100644 --- a/precompile/allow_list.go +++ b/precompile/allow_list.go @@ -47,7 +47,6 @@ type AllowListConfig struct { // Configure initializes the address space of [precompileAddr] by initializing the role of each of // the addresses in [AllowListAdmins]. func (c *AllowListConfig) Configure(state StateDB, precompileAddr common.Address) { - // First set enabled roles so these can be upgraded to admin addresses below. for _, enabledAddr := range c.EnabledAddresses { setAllowListRole(state, precompileAddr, enabledAddr, AllowListEnabled) } From 4d626e36cff54b62f4f91f2d1ff83d9aa9dfb8d3 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 15 Nov 2022 17:53:04 +0300 Subject: [PATCH 55/64] review fixes --- .../contracts/ExampleFeeManager.sol | 2 - .../contracts/ExampleRewardDistributor.sol | 76 ------- .../contracts/ExampleRewardManager.sol | 33 +++ .../contracts/IRewardManager.sol | 5 + ...butor.ts => deployExampleRewardManager.ts} | 3 +- contract-examples/tasks.ts | 5 +- .../test/ExampleRewardDistributor.ts | 211 ------------------ .../test/ExampleRewardManager.ts | 154 +++++++++++++ 8 files changed, 196 insertions(+), 293 deletions(-) delete mode 100644 contract-examples/contracts/ExampleRewardDistributor.sol create mode 100644 contract-examples/contracts/ExampleRewardManager.sol rename contract-examples/scripts/{deployExampleRewardDistributor.ts => deployExampleRewardManager.ts} (78%) delete mode 100644 contract-examples/test/ExampleRewardDistributor.ts create mode 100644 contract-examples/test/ExampleRewardManager.ts diff --git a/contract-examples/contracts/ExampleFeeManager.sol b/contract-examples/contracts/ExampleFeeManager.sol index 31dff1cd3b..dcc5828689 100644 --- a/contract-examples/contracts/ExampleFeeManager.sol +++ b/contract-examples/contracts/ExampleFeeManager.sol @@ -13,8 +13,6 @@ contract ExampleFeeManager is AllowList { address constant FEE_MANAGER_ADDRESS = 0x0200000000000000000000000000000000000003; IFeeManager feeManager = IFeeManager(FEE_MANAGER_ADDRESS); - bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); - struct FeeConfig { uint256 gasLimit; uint256 targetBlockRate; diff --git a/contract-examples/contracts/ExampleRewardDistributor.sol b/contract-examples/contracts/ExampleRewardDistributor.sol deleted file mode 100644 index f584e96bf1..0000000000 --- a/contract-examples/contracts/ExampleRewardDistributor.sol +++ /dev/null @@ -1,76 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "./IAllowList.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -// ExampleRewardDistributor is a sample contract to be used in conjunction -// with the RewardManager precompile. This contract allows its owner to -// add reward addresses, each of which can claim fees that accumulate to the -// contract (up to rewardRate per block). The owner can also adjust rewardRate. -contract ExampleRewardDistributor is Ownable { - mapping(address => uint256) rewardAddresses; // address to last claimed reward block number - - uint256 rewardRatePerBlk; // reward rate per block - - constructor(uint256 rewardRate) Ownable() { - rewardRatePerBlk = rewardRate; - } - - modifier _isRewardAddress(address addr) { - require(rewardAddresses[addr] > 0, "Not a reward address"); - _; - } - - function isRewardAddress(address addr) public view returns (bool) { - return rewardAddresses[addr] != 0; - } - - function getRewardRate() public view returns (uint256) { - return rewardRatePerBlk; - } - - // setRewardRate sets the reward rate per block - function setRewardRate(uint256 rewardRate) public onlyOwner { - rewardRatePerBlk = rewardRate; - } - - // addRewardAddress adds an address to the reward list - // and sets the last claimed block to the current block - function addRewardAddress(address addr) public onlyOwner { - require(rewardAddresses[addr] == 0, "Already a reward address"); - - rewardAddresses[addr] = block.number; - } - - // revoke removes address from reward addresses and transfers any remaining rewards to the address - function revoke(address addr) public onlyOwner _isRewardAddress(addr) { - uint256 reward = estimateReward(addr); - delete rewardAddresses[addr]; - - // reward only if there is any to reward - if (reward > 0 && reward <= address(this).balance) { - payable(addr).transfer(reward); - } - } - - // claim transfers any rewards to the address - function claim() public _isRewardAddress(msg.sender) { - uint256 reward = estimateReward(addr); - require(reward <= address(this).balance, "Not enough collected balance"); - - rewardAddresses[addr] = block.number; - payable(addr).transfer(reward); - } - - // estimateReward returns the estimated reward for the address - function estimateReward(address addr) public view returns (uint256) { - uint256 lastClaimedBlk = rewardAddresses[addr]; - if (lastClaimedBlk == 0) { - return 0; - } - uint256 currentBlk = block.number; - uint256 reward = (currentBlk - lastClaimedBlk) * rewardRatePerBlk; - return reward; - } -} diff --git a/contract-examples/contracts/ExampleRewardManager.sol b/contract-examples/contracts/ExampleRewardManager.sol new file mode 100644 index 0000000000..6c6635bdeb --- /dev/null +++ b/contract-examples/contracts/ExampleRewardManager.sol @@ -0,0 +1,33 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IRewardManager.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +// ExampleRewardManager is a sample wrapper contract for RewardManager precompile. +contract ExampleRewardManager is Ownable { + address constant REWARD_MANAGER_ADDRESS = 0x0200000000000000000000000000000000000004; + IRewardManager rewardManager = IRewardManager(REWARD_MANAGER_ADDRESS); + + constructor() Ownable() {} + + function currentRewardAddress() public view returns (address) { + return rewardManager.currentRewardAddress(); + } + + function setRewardAddress(address addr) public onlyOwner { + rewardManager.setRewardAddress(addr); + } + + function allowFeeRecipients() public onlyOwner { + rewardManager.allowFeeRecipients(); + } + + function disableRewards() public onlyOwner { + rewardManager.disableRewards(); + } + + function areFeeRecipientsAllowed() public view returns (bool) { + return rewardManager.areFeeRecipientsAllowed(); + } +} diff --git a/contract-examples/contracts/IRewardManager.sol b/contract-examples/contracts/IRewardManager.sol index 8ceb27b46b..031fadbd6b 100644 --- a/contract-examples/contracts/IRewardManager.sol +++ b/contract-examples/contracts/IRewardManager.sol @@ -3,13 +3,18 @@ pragma solidity ^0.8.0; import "./IAllowList.sol"; interface IRewardManager is IAllowList { + // setRewardAddress sets the reward address to the given address function setRewardAddress(address addr) external; + // allowFeeRecipients allows block builders to claim fees function allowFeeRecipients() external; + // disableRewards disables block rewards and starts burning fees function disableRewards() external; + // currentRewardAddress returns the current reward address function currentRewardAddress() external view returns (address rewardAddress); + // areFeeRecipientsAllowed returns true if fee recipients are allowed function areFeeRecipientsAllowed() external view returns (bool isAllowed); } diff --git a/contract-examples/scripts/deployExampleRewardDistributor.ts b/contract-examples/scripts/deployExampleRewardManager.ts similarity index 78% rename from contract-examples/scripts/deployExampleRewardDistributor.ts rename to contract-examples/scripts/deployExampleRewardManager.ts index 63f40d10c2..866c7ec7d0 100644 --- a/contract-examples/scripts/deployExampleRewardDistributor.ts +++ b/contract-examples/scripts/deployExampleRewardManager.ts @@ -4,11 +4,10 @@ import { } from "ethers" import { ethers } from "hardhat" -const rewardRate = ethers.utils.parseEther("0.00001") const main = async (): Promise => { const Contract: ContractFactory = await ethers.getContractFactory("ExampleRewardDistributor") - const contract: Contract = await Contract.deploy(rewardRate) + const contract: Contract = await Contract.deploy() await contract.deployed() console.log(`Contract deployed to: ${contract.address}`) diff --git a/contract-examples/tasks.ts b/contract-examples/tasks.ts index 856ef51f60..09cffd0eea 100644 --- a/contract-examples/tasks.ts +++ b/contract-examples/tasks.ts @@ -229,9 +229,10 @@ task("feeManager:readRole", "a task to get the network deployer minter list") task("rewardManager:currentRewardAddress", "a task to get the current configured rewarding address") .setAction(async (_, hre) => { const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) + const areFeeRecipientsAllowed = await rewardManager.areFeeRecipientsAllowed() const result = await rewardManager.currentRewardAddress() - if (result == "0x0000000000000000000000000000000000000000") { - console.log("Custom Fee Recipients are allowed.") + if (areFeeRecipientsAllowed) { + console.log("Custom Fee Recipients are allowed. (%s)", result) } else { console.log(`Current reward address is ${result}`) } diff --git a/contract-examples/test/ExampleRewardDistributor.ts b/contract-examples/test/ExampleRewardDistributor.ts deleted file mode 100644 index e498b80d2c..0000000000 --- a/contract-examples/test/ExampleRewardDistributor.ts +++ /dev/null @@ -1,211 +0,0 @@ -// (c) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { - BigNumber, - Contract, - ContractFactory, -} from "ethers" -import { ethers } from "hardhat" -import ts = require("typescript"); - -// make sure this is always an admin for minter precompile -const adminAddress: string = "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" -const REWARD_MANAGER_ADDRESS = "0x0200000000000000000000000000000000000004"; -const BLACKHOLE_ADDRESS = "0x0100000000000000000000000000000000000000"; -const rewardRate = ethers.utils.parseEther("0.00001") - -const ROLES = { - NONE: 0, - MINTER: 1, - ADMIN: 2 -}; - -describe("ExampleRewardDistributor", function () { - this.timeout("30s") - let owner: SignerWithAddress - let contract: Contract - let signer1: SignerWithAddress - let signer2: SignerWithAddress - before(async function () { - owner = await ethers.getSigner(adminAddress); - signer1 = (await ethers.getSigners())[1] - signer2 = (await ethers.getSigners())[2] - const Contract: ContractFactory = await ethers.getContractFactory("ExampleRewardDistributor", { signer: owner }) - contract = await Contract.deploy(rewardRate) - await contract.deployed() - const contractAddress: string = contract.address - console.log(`Contract deployed to: ${contractAddress}`) - - // Send a transaction to mine a new block - const tx = await owner.sendTransaction({ - to: signer1.address, - value: ethers.utils.parseEther("10") - }) - await tx.wait() - }); - - it("should add contract deployer as owner", async function () { - const contractOwnerAddr: string = await contract.owner() - expect(owner.address).to.equal(contractOwnerAddr) - }); - - // this contract is not selected as the reward address yet, so should not be able to receive fees - it("contract should not be able to receive fees", async function () { - const rewardManager = await ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDRESS, owner); - let rewardAddress = await rewardManager.currentRewardAddress(); - expect(rewardAddress).to.be.equal(BLACKHOLE_ADDRESS) - - let balance = await ethers.provider.getBalance(contract.address) - expect(balance).to.be.equal(0) - - let firstBHBalance = await ethers.provider.getBalance(BLACKHOLE_ADDRESS) - - // Send a transaction to mine a new block - const tx = await owner.sendTransaction({ - to: signer1.address, - value: ethers.utils.parseEther("0.0001") - }) - await tx.wait() - - balance = await ethers.provider.getBalance(contract.address) - expect(balance).to.be.equal(0) - - let secondBHBalance = await ethers.provider.getBalance(BLACKHOLE_ADDRESS) - expect(secondBHBalance).to.be.greaterThan(firstBHBalance) - }) - - it("should be appointed as reward address", async function () { - const rewardManager = await ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDRESS, owner); - let adminRole = await rewardManager.readAllowList(adminAddress); - expect(adminRole).to.be.equal(ROLES.ADMIN) - - let tx = await rewardManager.setRewardAddress(contract.address); - await tx.wait() - let rewardAddress = await rewardManager.currentRewardAddress(); - expect(rewardAddress).to.be.equal(contract.address) - }); - - it("contract should be able to receive fees", async function () { - let previousBalance = await ethers.provider.getBalance(contract.address) - - // Send a transaction to mine a new block - const tx = await owner.sendTransaction({ - to: signer1.address, - value: ethers.utils.parseEther("0.0001") - }) - await tx.wait() - - let balance = await ethers.provider.getBalance(contract.address) - expect(balance.gt(previousBalance)).to.be.true - }) - - it("should not let non-added address to claim rewards", async function () { - const nonRewardAddress = signer1 - try { - await contract.connect(nonRewardAddress).claim(); - } - catch (err) { - expect(err.message).to.contains('Not a reward address') - return - } - expect.fail("should have errored") - }) - - it("should not revoke non-added address", async function () { - const nonRewardAddress = signer1 - try { - await contract.revoke(nonRewardAddress.address); - } - catch (err) { - expect(err.message).to.contains('Not a reward address') - return - } - expect.fail("should have errored") - }) - - it("should return 0 estimate reward for non-added address", async function () { - const nonRewardAddress = signer1 - const estimatedReward = await contract.estimateReward(nonRewardAddress.address) - expect(estimatedReward).to.be.equal(0) - }) - - it("should return false for isRewardAddress for non-added address", async function () { - const nonRewardAddress = signer1 - const isRewardAddress = await contract.isRewardAddress(nonRewardAddress.address) - expect(isRewardAddress).to.be.equal(false) - }) - - it("should be able to add a reward address", async function () { - const rewardAddress = signer1 - let tx = await contract.addRewardAddress(rewardAddress.address); - await tx.wait() - const isRewardAddress = await contract.isRewardAddress(rewardAddress.address) - expect(isRewardAddress).to.be.equal(true) - }) - - it("should not be able to add a reward address twice", async function () { - const rewardAddress = signer1 - try { - await contract.addRewardAddress(rewardAddress.address); - } - catch (err) { - expect(err.message).to.contains('Already a reward address') - return - } - expect.fail("should have errored") - }) - - it("should distribute according to reward rate", async function () { - const rewardAddress = signer1 - let previousBalance = await ethers.provider.getBalance(rewardAddress.address) - let previousBlockNum = await ethers.provider.getBlockNumber() - - // send a transaction to mine a new block - let transfer = await owner.sendTransaction({ - to: signer2.address, - value: ethers.utils.parseEther("0.0001") - }) - - await transfer.wait() - - let tx = await contract.connect(rewardAddress).claim(); - await tx.wait() - - let balance = await ethers.provider.getBalance(rewardAddress.address) - - let txRec = await tx.wait() - let gasUsed: BigNumber = txRec.cumulativeGasUsed - let gasPrice: BigNumber = txRec.effectiveGasPrice - let txFee = gasUsed.mul(gasPrice) - let blockNum = await ethers.provider.getBlockNumber() - - let blockDiff = blockNum - previousBlockNum - expect(balance).to.be.equal(previousBalance.add(rewardRate.mul(blockDiff)).sub(txFee)) - }) - - it("should be able to revoke a reward address", async function () { - const rewardAddress = signer1 - let tx = await contract.revoke(rewardAddress.address); - await tx.wait() - const isRewardAddress = await contract.isRewardAddress(rewardAddress.address) - expect(isRewardAddress).to.be.equal(false) - }) - - it("should be able to claim after revoke", async function () { - const nonRewardAddress = signer1 - const isRewardAddress = await contract.isRewardAddress(nonRewardAddress.address) - expect(isRewardAddress).to.be.equal(false) - - try { - await contract.connect(nonRewardAddress).claim(); - } - catch (err) { - expect(err.message).to.contains('Not a reward address') - return - } - expect.fail("should have errored") - }) -}); diff --git a/contract-examples/test/ExampleRewardManager.ts b/contract-examples/test/ExampleRewardManager.ts new file mode 100644 index 0000000000..901995b196 --- /dev/null +++ b/contract-examples/test/ExampleRewardManager.ts @@ -0,0 +1,154 @@ +// (c) 2019-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { expect } from "chai"; +import { + BigNumber, + Contract, + ContractFactory, +} from "ethers" +import { ethers } from "hardhat" +import ts = require("typescript"); + +// make sure this is always an admin for minter precompile +const adminAddress: string = "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" +const REWARD_MANAGER_ADDRESS = "0x0200000000000000000000000000000000000004"; +const BLACKHOLE_ADDRESS = "0x0100000000000000000000000000000000000000"; + +const ROLES = { + NONE: 0, + ENABLED: 1, + ADMIN: 2 +}; + +describe("ExampleRewardManager", function () { + this.timeout("30s") + let owner: SignerWithAddress + let contract: Contract + let signer1: SignerWithAddress + let signer2: SignerWithAddress + let precompile: Contract + + before(async function () { + owner = await ethers.getSigner(adminAddress); + signer1 = (await ethers.getSigners())[1] + signer2 = (await ethers.getSigners())[2] + const Contract: ContractFactory = await ethers.getContractFactory("ExampleRewardManager", { signer: owner }) + contract = await Contract.deploy() + await contract.deployed() + const contractAddress: string = contract.address + console.log(`Contract deployed to: ${contractAddress}`) + + precompile = await ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDRESS, owner); + + // Send a transaction to mine a new block + const tx = await owner.sendTransaction({ + to: signer1.address, + value: ethers.utils.parseEther("10") + }) + await tx.wait() + }); + + it("should add contract deployer as owner", async function () { + const contractOwnerAddr: string = await contract.owner() + expect(owner.address).to.equal(contractOwnerAddr) + }); + + // this contract is not selected as the reward address yet, so should not be able to receive fees + it("should send fees to blackhole", async function () { + let rewardAddress = await contract.currentRewardAddress(); + expect(rewardAddress).to.be.equal(BLACKHOLE_ADDRESS) + + let firstBHBalance = await ethers.provider.getBalance(BLACKHOLE_ADDRESS) + + // Send a transaction to mine a new block + const tx = await owner.sendTransaction({ + to: signer1.address, + value: ethers.utils.parseEther("0.0001") + }) + await tx.wait() + + let secondBHBalance = await ethers.provider.getBalance(BLACKHOLE_ADDRESS) + expect(secondBHBalance.gt(firstBHBalance)).to.be.true + }) + + it("should not appoint reward address before enabled", async function () { + let contractRole = await precompile.readAllowList(contract.address); + expect(contractRole).to.be.equal(ROLES.NONE) + try { + let tx = await contract.setRewardAddress(signer1.address); + await tx.wait() + } + catch (err) { + return + } + expect.fail("should have errored") + }); + + + it("contract should be added to enabled list", async function () { + let contractRole = await precompile.readAllowList(contract.address); + expect(contractRole).to.be.equal(ROLES.NONE) + + let enableTx = await precompile.setEnabled(contract.address); + await enableTx.wait() + contractRole = await precompile.readAllowList(contract.address); + expect(contractRole).to.be.equal(ROLES.ENABLED) + }); + + + it("should be appointed as reward address", async function () { + let tx = await contract.setRewardAddress(contract.address); + await tx.wait() + let rewardAddress = await contract.currentRewardAddress(); + expect(rewardAddress).to.be.equal(contract.address) + }); + + it("should be able to receive fees", async function () { + let previousBalance = await ethers.provider.getBalance(contract.address) + + // Send a transaction to mine a new block + const tx = await owner.sendTransaction({ + to: signer1.address, + value: ethers.utils.parseEther("0.0001") + }) + await tx.wait() + + let balance = await ethers.provider.getBalance(contract.address) + expect(balance.gt(previousBalance)).to.be.true + }) + + it("signer1 should be appointed as reward address", async function () { + let tx = await contract.setRewardAddress(signer1.address); + await tx.wait() + let rewardAddress = await contract.currentRewardAddress(); + expect(rewardAddress).to.be.equal(signer1.address) + }); + + it("signer1 should be able to receive fees", async function () { + let previousBalance = await ethers.provider.getBalance(signer1.address) + + // Send a transaction to mine a new block + const tx = await owner.sendTransaction({ + to: signer2.address, + value: ethers.utils.parseEther("0.0001") + }) + await tx.wait() + + let balance = await ethers.provider.getBalance(signer1.address) + expect(balance.gt(previousBalance)).to.be.true + }) + + it("should return false for allowFeeRecipients check", async function () { + let res = await contract.areFeeRecipientsAllowed(); + expect(res).to.be.false + }) + + it("should enable allowFeeRecipients", async function () { + let tx = await contract.allowFeeRecipients(); + await tx.wait() + let res = await contract.areFeeRecipientsAllowed(); + expect(res).to.be.true + }) +}); From 7cf7bc96f7408b466aaf8cbd734ba835c2b33dbb Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 15 Nov 2022 17:55:29 +0300 Subject: [PATCH 56/64] review fixes --- accounts/abi/bind/precompile_template.go | 4 +-- consensus/consensus.go | 3 +- core/stateful_precompile_test.go | 15 ++++++++++ plugin/evm/vm_test.go | 22 ++++++++------ precompile/params.go | 10 ++++++- precompile/reserved_range.go | 4 --- precompile/reward_manager.go | 38 +++++++----------------- 7 files changed, 51 insertions(+), 45 deletions(-) diff --git a/accounts/abi/bind/precompile_template.go b/accounts/abi/bind/precompile_template.go index 23a8459757..58c96f35ab 100644 --- a/accounts/abi/bind/precompile_template.go +++ b/accounts/abi/bind/precompile_template.go @@ -86,12 +86,12 @@ var ( {{- if not .Original.IsConstant | and $contract.AllowList}} - ErrCannot{{.Normalized.Name}} = errors.New("non-enabled cannot {{.Original.Name}}") + ErrCannot{{.Normalized.Name}} = errors.New("non-enabled cannot call {{.Original.Name}}") {{- end}} {{- end}} {{- if .Contract.Fallback | and $contract.AllowList}} - Err{{.Contract.Type}}CannotFallback = errors.New("non-enabled cannot use fallback function") + Err{{.Contract.Type}}CannotFallback = errors.New("non-enabled cannot call fallback function") {{- end}} {{.Contract.Type}}ABI abi.ABI // will be initialized by init function diff --git a/consensus/consensus.go b/consensus/consensus.go index 89503bd996..b96297c4ad 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -58,7 +58,8 @@ type ChainHeaderReader interface { // GetFeeConfigAt retrieves the fee config and last changed block number at block header. GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) - // GetCoinbaseAt retrieves the configured coinbase address at [parent]. If fee recipients are allowed, returns true in the second return value. + // GetCoinbaseAt retrieves the configured coinbase address at [parent]. + // If fee recipients are allowed, returns true in the second return value and a predefined address in the first value. GetCoinbaseAt(parent *types.Header) (common.Address, bool, error) } diff --git a/core/stateful_precompile_test.go b/core/stateful_precompile_test.go index 5a9cf5ee79..c66338724e 100644 --- a/core/stateful_precompile_test.go +++ b/core/stateful_precompile_test.go @@ -7,6 +7,7 @@ import ( "math/big" "testing" + "github.com/ava-labs/coreth/constants" "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" @@ -1026,6 +1027,10 @@ func TestRewardManagerRun(t *testing.T) { suppliedGas: precompile.AllowFeeRecipientsGasCost, readOnly: false, expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + _, isFeeRecipients := precompile.GetStoredRewardAddress(state) + require.True(t, isFeeRecipients) + }, }, "set reward address from enabled succeeds": { caller: enabledAddr, @@ -1038,6 +1043,11 @@ func TestRewardManagerRun(t *testing.T) { suppliedGas: precompile.SetRewardAddressGasCost, readOnly: false, expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + address, isFeeRecipients := precompile.GetStoredRewardAddress(state) + require.Equal(t, testAddr, address) + require.False(t, isFeeRecipients) + }, }, "disable rewards from enabled succeeds": { caller: enabledAddr, @@ -1050,6 +1060,11 @@ func TestRewardManagerRun(t *testing.T) { suppliedGas: precompile.DisableRewardsGasCost, readOnly: false, expectedRes: []byte{}, + assertState: func(t *testing.T, state *state.StateDB) { + address, isFeeRecipients := precompile.GetStoredRewardAddress(state) + require.False(t, isFeeRecipients) + require.Equal(t, constants.BlackholeAddr, address) + }, }, "get current reward address from no role succeeds": { caller: noRoleAddr, diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index b924a9964b..1faa6ffb3d 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -2512,7 +2512,7 @@ func TestAllowFeeRecipientDisabled(t *testing.T) { require.NoError(t, err) // this won't return an error since miner will set the etherbase to blackhole address ethBlock := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - require.Equal(t, ethBlock.Coinbase(), constants.BlackholeAddr) + require.Equal(t, constants.BlackholeAddr, ethBlock.Coinbase()) // Create empty block from blk internalBlk := blk.(*chain.BlockWrapper).Block.(*Block) @@ -2580,7 +2580,7 @@ func TestAllowFeeRecipientEnabled(t *testing.T) { t.Fatalf("Expected new block to match") } ethBlock := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - require.Equal(t, ethBlock.Coinbase(), etherBase) + require.Equal(t, etherBase, ethBlock.Coinbase()) // Verify that etherBase has received fees blkState, err := vm.blockChain.StateAt(ethBlock.Root()) if err != nil { @@ -2621,7 +2621,9 @@ func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { data, err := precompile.PackSetRewardAddress(testAddr) require.NoError(t, err) - tx := types.NewTransaction(uint64(0), precompile.RewardManagerAddress, big.NewInt(1), 21240+precompile.SetRewardAddressGasCost, big.NewInt(testMinGasPrice), data) + gas := 21000 + 240 + precompile.SetRewardAddressGasCost // 21000 for tx, 240 for tx data + + tx := types.NewTransaction(uint64(0), precompile.RewardManagerAddress, big.NewInt(1), gas, big.NewInt(testMinGasPrice), data) signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) require.NoError(t, err) @@ -2635,7 +2637,7 @@ func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { newHead := <-newTxPoolHeadChan require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) ethBlock := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - require.Equal(t, ethBlock.Coinbase(), etherBase) // reward address is activated at this block so this is fine + require.Equal(t, etherBase, ethBlock.Coinbase()) // reward address is activated at this block so this is fine tx1 := types.NewTransaction(uint64(0), testEthAddrs[0], big.NewInt(2), 21000, big.NewInt(testMinGasPrice*3), nil) signedTx1, err := types.SignTx(tx1, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[1]) @@ -2650,7 +2652,7 @@ func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { newHead = <-newTxPoolHeadChan require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) ethBlock = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - require.Equal(t, ethBlock.Coinbase(), testAddr) // reward address was activated at previous block + require.Equal(t, testAddr, ethBlock.Coinbase()) // reward address was activated at previous block // Verify that etherBase has received fees blkState, err := vm.blockChain.StateAt(ethBlock.Root()) require.NoError(t, err) @@ -2659,7 +2661,7 @@ func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { require.Equal(t, 1, balance.Cmp(common.Big0)) } -func TestRewardManagerPrecompileAllowFeeRecipieints(t *testing.T) { +func TestRewardManagerPrecompileAllowFeeRecipients(t *testing.T) { genesis := &core.Genesis{} require.NoError(t, genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM))) @@ -2685,7 +2687,9 @@ func TestRewardManagerPrecompileAllowFeeRecipieints(t *testing.T) { data, err := precompile.PackAllowFeeRecipients() require.NoError(t, err) - tx := types.NewTransaction(uint64(0), precompile.RewardManagerAddress, big.NewInt(1), 21240+precompile.SetRewardAddressGasCost, big.NewInt(testMinGasPrice), data) + gas := 21000 + 240 + precompile.AllowFeeRecipientsGasCost // 21000 for tx, 240 for tx data + + tx := types.NewTransaction(uint64(0), precompile.RewardManagerAddress, big.NewInt(1), gas, big.NewInt(testMinGasPrice), data) signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) require.NoError(t, err) @@ -2699,7 +2703,7 @@ func TestRewardManagerPrecompileAllowFeeRecipieints(t *testing.T) { newHead := <-newTxPoolHeadChan require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) ethBlock := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - require.Equal(t, ethBlock.Coinbase(), constants.BlackholeAddr) // reward address is activated at this block so this is fine + require.Equal(t, constants.BlackholeAddr, ethBlock.Coinbase()) // reward address is activated at this block so this is fine tx1 := types.NewTransaction(uint64(0), testEthAddrs[0], big.NewInt(2), 21000, big.NewInt(testMinGasPrice*3), nil) signedTx1, err := types.SignTx(tx1, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[1]) @@ -2714,7 +2718,7 @@ func TestRewardManagerPrecompileAllowFeeRecipieints(t *testing.T) { newHead = <-newTxPoolHeadChan require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) ethBlock = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - require.Equal(t, ethBlock.Coinbase(), etherBase) // reward address was activated at previous block + require.Equal(t, etherBase, ethBlock.Coinbase()) // reward address was activated at previous block // Verify that etherBase has received fees blkState, err := vm.blockChain.StateAt(ethBlock.Root()) require.NoError(t, err) diff --git a/precompile/params.go b/precompile/params.go index 5776bb842e..3a18011a61 100644 --- a/precompile/params.go +++ b/precompile/params.go @@ -20,12 +20,14 @@ const ( // in core/vm/contracts.go. // The first stateful precompiles were added in coreth to support nativeAssetCall and nativeAssetBalance. New stateful precompiles // originating in coreth will continue at this prefix, so we reserve this range in subnet-evm so that they can be migrated into -// subnet-evm without issue. These start at the address: 0x0100000000000000000000000000000000000000 and will increment by 1. +// subnet-evm without issue. +// These start at the address: 0x0100000000000000000000000000000000000000 and will increment by 1. // Optional precompiles implemented in subnet-evm start at 0x0200000000000000000000000000000000000000 and will increment by 1 // from here to reduce the risk of conflicts. // For forks of subnet-evm, users should start at 0x0300000000000000000000000000000000000000 to ensure // that their own modifications do not conflict with stateful precompiles that may be added to subnet-evm // in the future. +// Addresses starting at 0x0400000000000000000000000000000000000000 are reserved for special use cases in precompiles. var ( ContractDeployerAllowListAddress = common.HexToAddress("0x0200000000000000000000000000000000000000") ContractNativeMinterAddress = common.HexToAddress("0x0200000000000000000000000000000000000001") @@ -35,6 +37,8 @@ var ( // ADD YOUR PRECOMPILE HERE // {YourPrecompile}Address = common.HexToAddress("0x03000000000000000000000000000000000000??") + AllowFeeRecipientsAddressValue = common.HexToAddress("0x0400000000000000000000000000000000000000") + UsedAddresses = []common.Address{ ContractDeployerAllowListAddress, ContractNativeMinterAddress, @@ -57,6 +61,10 @@ var ( common.HexToAddress("0x0300000000000000000000000000000000000000"), common.HexToAddress("0x03000000000000000000000000000000000000ff"), }, + { + common.HexToAddress("0x0400000000000000000000000000000000000000"), + common.HexToAddress("0x04000000000000000000000000000000000000ff"), + }, } ) diff --git a/precompile/reserved_range.go b/precompile/reserved_range.go index d2627bffa4..4bbc953b54 100644 --- a/precompile/reserved_range.go +++ b/precompile/reserved_range.go @@ -9,10 +9,6 @@ import ( "github.com/ethereum/go-ethereum/common" ) -// Gas costs for stateful precompiles -// can be added here eg. -// const MintGasCost = 30_000 - // AddressRange represents a continuous range of addresses type AddressRange struct { Start common.Address diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go index 91a7b09867..aa76603fb0 100644 --- a/precompile/reward_manager.go +++ b/precompile/reward_manager.go @@ -35,11 +35,11 @@ const ( var ( _ StatefulPrecompileConfig = &RewardManagerConfig{} - ErrCannotAllowFeeRecipients = errors.New("non-enabled cannot allowFeeRecipients") - ErrCannotAreFeeRecipientsAllowed = errors.New("non-enabled cannot areFeeRecipientsAllowed") - ErrCannotCurrentRewardAddress = errors.New("non-enabled cannot currentRewardAddress") - ErrCannotDisableRewards = errors.New("non-enabled cannot disableRewards") - ErrCannotSetRewardAddress = errors.New("non-enabled cannot setRewardAddress") + ErrCannotAllowFeeRecipients = errors.New("non-enabled cannot call allowFeeRecipients") + ErrCannotAreFeeRecipientsAllowed = errors.New("non-enabled cannot call areFeeRecipientsAllowed") + ErrCannotCurrentRewardAddress = errors.New("non-enabled cannot call currentRewardAddress") + ErrCannotDisableRewards = errors.New("non-enabled cannot call disableRewards") + ErrCannotSetRewardAddress = errors.New("non-enabled cannot call setRewardAddress") ErrCannotEnableBothRewards = errors.New("cannot enable both fee recipients and reward address at the same time") ErrEmptyRewardAddress = errors.New("reward address cannot be empty") @@ -47,8 +47,7 @@ var ( RewardManagerABI abi.ABI // will be initialized by init function RewardManagerPrecompile StatefulPrecompiledContract // will be initialized by init function - rewardAddressStorageKey = common.Hash{'r', 'a', 's', 'k'} - allowFeeRecipientsAddressValue = common.Address{'a', 'f', 'r', 'a', 'v'} + rewardAddressStorageKey = common.Hash{'r', 'a', 's', 'k'} ) type InitialRewardConfig struct { @@ -122,7 +121,6 @@ func (c *RewardManagerConfig) Equal(s StatefulPrecompileConfig) bool { if !ok { return false } - // CUSTOM CODE STARTS HERE // modify this boolean accordingly with your custom RewardManagerConfig, to check if [other] and the current [c] are equal // if RewardManagerConfig contains only UpgradeableConfig and AllowListConfig you can skip modifying it. equals := c.UpgradeableConfig.Equal(&other.UpgradeableConfig) && c.AllowListConfig.Equal(&other.AllowListConfig) @@ -146,7 +144,6 @@ func (c *RewardManagerConfig) Address() common.Address { // Configure configures [state] with the initial configuration. func (c *RewardManagerConfig) Configure(chainConfig ChainConfig, state StateDB, _ BlockContext) { c.AllowListConfig.Configure(state, RewardManagerAddress) - // CUSTOM CODE STARTS HERE // configure the RewardManager with the given initial configuration if c.InitialRewardConfig != nil { // enable allow fee recipients @@ -215,7 +212,7 @@ func PackAllowFeeRecipients() ([]byte, error) { // EnableAllowFeeRecipients enables fee recipients. func EnableAllowFeeRecipients(stateDB StateDB) { - stateDB.SetState(RewardManagerAddress, rewardAddressStorageKey, allowFeeRecipientsAddressValue.Hash()) + stateDB.SetState(RewardManagerAddress, rewardAddressStorageKey, AllowFeeRecipientsAddressValue.Hash()) } // DisableRewardAddress disables rewards and burns them by sending to Blackhole Address. @@ -243,7 +240,6 @@ func allowFeeRecipients(accessibleState PrecompileAccessibleState, caller common } // allow list code ends here. - // CUSTOM CODE STARTS HERE // this function does not return an output, leave this one as is EnableAllowFeeRecipients(stateDB) packedOutput := []byte{} @@ -268,14 +264,10 @@ func areFeeRecipientsAllowed(accessibleState PrecompileAccessibleState, caller c if remainingGas, err = deductGas(suppliedGas, AreFeeRecipientsAllowedGasCost); err != nil { return nil, 0, err } - if readOnly { - return nil, remainingGas, vmerrs.ErrWriteProtection - } // no input provided for this function - // CUSTOM CODE STARTS HERE stateDB := accessibleState.GetStateDB() - var output bool // CUSTOM CODE FOR AN OUTPUT + var output bool _, output = GetStoredRewardAddress(stateDB) packedOutput, err := PackAreFeeRecipientsAllowedOutput(output) @@ -303,10 +295,7 @@ func PackCurrentRewardAddressOutput(rewardAddress common.Address) ([]byte, error // Returns an empty address and true if allow fee recipients is enabled, otherwise returns current reward address and false. func GetStoredRewardAddress(stateDB StateDB) (common.Address, bool) { val := stateDB.GetState(RewardManagerAddress, rewardAddressStorageKey) - if val == allowFeeRecipientsAddressValue.Hash() { - return common.Address{}, true - } - return common.BytesToAddress(val.Bytes()), false + return common.BytesToAddress(val.Bytes()), val == AllowFeeRecipientsAddressValue.Hash() } // StoredRewardAddress stores the given [val] under rewardAddressStorageKey. @@ -363,7 +352,6 @@ func setRewardAddress(accessibleState PrecompileAccessibleState, caller common.A } // allow list code ends here. - // CUSTOM CODE STARTS HERE if err := StoreRewardAddress(stateDB, inputStruct); err != nil { return nil, remainingGas, err } @@ -378,12 +366,8 @@ func currentRewardAddress(accessibleState PrecompileAccessibleState, caller comm if remainingGas, err = deductGas(suppliedGas, CurrentRewardAddressGasCost); err != nil { return nil, 0, err } - if readOnly { - return nil, remainingGas, vmerrs.ErrWriteProtection - } - // no input provided for this function - // CUSTOM CODE STARTS HERE + // no input provided for this function stateDB := accessibleState.GetStateDB() output, _ := GetStoredRewardAddress(stateDB) packedOutput, err := PackCurrentRewardAddressOutput(output) @@ -420,8 +404,6 @@ func disableRewards(accessibleState PrecompileAccessibleState, caller common.Add return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotDisableRewards, caller) } // allow list code ends here. - - // CUSTOM CODE STARTS HERE DisableFeeRewards(stateDB) // this function does not return an output, leave this one as is packedOutput := []byte{} From 344cfdac9509c6abcbed00fe18e24ae2376007b2 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 15 Nov 2022 19:40:35 +0300 Subject: [PATCH 57/64] add disabled precompile test cases --- core/stateful_precompile_test.go | 2 +- plugin/evm/vm_test.go | 79 +++++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/core/stateful_precompile_test.go b/core/stateful_precompile_test.go index c66338724e..7d1aa2390a 100644 --- a/core/stateful_precompile_test.go +++ b/core/stateful_precompile_test.go @@ -7,8 +7,8 @@ import ( "math/big" "testing" - "github.com/ava-labs/coreth/constants" "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/params" diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 1faa6ffb3d..dc857ed4ea 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -2659,13 +2659,52 @@ func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { balance := blkState.GetBalance(testAddr) require.Equal(t, 1, balance.Cmp(common.Big0)) + + // Test Case: Disable reward manager + // This should revert back to enabling fee recipients + previousBalance := blkState.GetBalance(etherBase) + + // configure a network upgrade to remove the reward manager + disableTime := vm.clock.Time() + precompileConfigs := &vm.blockChain.Config().UpgradeConfig + precompileConfigs.PrecompileUpgrades = append( + precompileConfigs.PrecompileUpgrades, + params.PrecompileUpgrade{ + RewardManagerConfig: precompile.NewDisableRewardManagerConfig(big.NewInt(disableTime.Unix())), + }, + ) + + vm.clock.Set(vm.clock.Time().Add(2 * time.Hour)) // upgrade takes effect after a block is issued, so we can set vm's clock here. + tx2 := types.NewTransaction(uint64(1), testEthAddrs[0], big.NewInt(2), 21000, big.NewInt(testMinGasPrice), nil) + signedTx2, err := types.SignTx(tx2, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[1]) + require.NoError(t, err) + + txErrors = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx2}) + for _, err := range txErrors { + require.NoError(t, err) + } + + blk = issueAndAccept(t, issuer, vm) + newHead = <-newTxPoolHeadChan + require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) + ethBlock = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock + require.Equal(t, etherBase, ethBlock.Coinbase()) // reward address was activated at previous block + assert.True(t, ethBlock.Timestamp().Cmp(big.NewInt(disableTime.Unix())) >= 0) + + // Verify that Blackhole has received fees + blkState, err = vm.blockChain.StateAt(ethBlock.Root()) + require.NoError(t, err) + + balance = blkState.GetBalance(etherBase) + require.Equal(t, 1, balance.Cmp(previousBalance)) } func TestRewardManagerPrecompileAllowFeeRecipients(t *testing.T) { genesis := &core.Genesis{} require.NoError(t, genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM))) - genesis.Config.RewardManagerConfig = precompile.NewRewardManagerConfig(common.Big0, testEthAddrs[0:1], nil, nil) + enableRewardManagerTimestamp := time.Unix(0, 0) // enable at genesis + genesis.Config.RewardManagerConfig = precompile.NewRewardManagerConfig(big.NewInt(enableRewardManagerTimestamp.Unix()), testEthAddrs[0:1], nil, nil) genesis.Config.AllowFeeRecipients = false // disable this in genesis genesisJSON, err := genesis.MarshalJSON() require.NoError(t, err) @@ -2725,4 +2764,42 @@ func TestRewardManagerPrecompileAllowFeeRecipients(t *testing.T) { balance := blkState.GetBalance(etherBase) require.Equal(t, 1, balance.Cmp(common.Big0)) + + // Test Case: Disable reward manager + // This should revert back to burning fees + previousBalance := blkState.GetBalance(constants.BlackholeAddr) + + // configure a network upgrade to remove the reward manager + disableTime := vm.clock.Time() + precompileConfigs := &vm.blockChain.Config().UpgradeConfig + precompileConfigs.PrecompileUpgrades = append( + precompileConfigs.PrecompileUpgrades, + params.PrecompileUpgrade{ + RewardManagerConfig: precompile.NewDisableRewardManagerConfig(big.NewInt(disableTime.Unix())), + }, + ) + + vm.clock.Set(vm.clock.Time().Add(2 * time.Hour)) // upgrade takes effect after a block is issued, so we can set vm's clock here. + tx2 := types.NewTransaction(uint64(1), testEthAddrs[0], big.NewInt(2), 21000, big.NewInt(testMinGasPrice), nil) + signedTx2, err := types.SignTx(tx2, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[1]) + require.NoError(t, err) + + txErrors = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx2}) + for _, err := range txErrors { + require.NoError(t, err) + } + + blk = issueAndAccept(t, issuer, vm) + newHead = <-newTxPoolHeadChan + require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) + ethBlock = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock + require.Equal(t, constants.BlackholeAddr, ethBlock.Coinbase()) // reward address was activated at previous block + assert.True(t, ethBlock.Timestamp().Cmp(big.NewInt(disableTime.Unix())) >= 0) + + // Verify that Blackhole has received fees + blkState, err = vm.blockChain.StateAt(ethBlock.Root()) + require.NoError(t, err) + + balance = blkState.GetBalance(constants.BlackholeAddr) + require.Equal(t, 1, balance.Cmp(previousBalance)) } From 79f136f7b246188a45cc044a9775b290e580c3ba Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Tue, 15 Nov 2022 20:44:31 +0300 Subject: [PATCH 58/64] use hash value instead of address --- precompile/params.go | 7 ------- precompile/reward_manager.go | 7 ++++--- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/precompile/params.go b/precompile/params.go index 3a18011a61..965ab1df20 100644 --- a/precompile/params.go +++ b/precompile/params.go @@ -27,7 +27,6 @@ const ( // For forks of subnet-evm, users should start at 0x0300000000000000000000000000000000000000 to ensure // that their own modifications do not conflict with stateful precompiles that may be added to subnet-evm // in the future. -// Addresses starting at 0x0400000000000000000000000000000000000000 are reserved for special use cases in precompiles. var ( ContractDeployerAllowListAddress = common.HexToAddress("0x0200000000000000000000000000000000000000") ContractNativeMinterAddress = common.HexToAddress("0x0200000000000000000000000000000000000001") @@ -37,8 +36,6 @@ var ( // ADD YOUR PRECOMPILE HERE // {YourPrecompile}Address = common.HexToAddress("0x03000000000000000000000000000000000000??") - AllowFeeRecipientsAddressValue = common.HexToAddress("0x0400000000000000000000000000000000000000") - UsedAddresses = []common.Address{ ContractDeployerAllowListAddress, ContractNativeMinterAddress, @@ -61,10 +58,6 @@ var ( common.HexToAddress("0x0300000000000000000000000000000000000000"), common.HexToAddress("0x03000000000000000000000000000000000000ff"), }, - { - common.HexToAddress("0x0400000000000000000000000000000000000000"), - common.HexToAddress("0x04000000000000000000000000000000000000ff"), - }, } ) diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go index aa76603fb0..523295022d 100644 --- a/precompile/reward_manager.go +++ b/precompile/reward_manager.go @@ -47,7 +47,8 @@ var ( RewardManagerABI abi.ABI // will be initialized by init function RewardManagerPrecompile StatefulPrecompiledContract // will be initialized by init function - rewardAddressStorageKey = common.Hash{'r', 'a', 's', 'k'} + rewardAddressStorageKey = common.Hash{'r', 'a', 's', 'k'} + allowFeeRecipientsAddressValue = common.Hash{'a', 'f', 'r', 'a', 'v'} ) type InitialRewardConfig struct { @@ -212,7 +213,7 @@ func PackAllowFeeRecipients() ([]byte, error) { // EnableAllowFeeRecipients enables fee recipients. func EnableAllowFeeRecipients(stateDB StateDB) { - stateDB.SetState(RewardManagerAddress, rewardAddressStorageKey, AllowFeeRecipientsAddressValue.Hash()) + stateDB.SetState(RewardManagerAddress, rewardAddressStorageKey, allowFeeRecipientsAddressValue) } // DisableRewardAddress disables rewards and burns them by sending to Blackhole Address. @@ -295,7 +296,7 @@ func PackCurrentRewardAddressOutput(rewardAddress common.Address) ([]byte, error // Returns an empty address and true if allow fee recipients is enabled, otherwise returns current reward address and false. func GetStoredRewardAddress(stateDB StateDB) (common.Address, bool) { val := stateDB.GetState(RewardManagerAddress, rewardAddressStorageKey) - return common.BytesToAddress(val.Bytes()), val == AllowFeeRecipientsAddressValue.Hash() + return common.BytesToAddress(val.Bytes()), val == allowFeeRecipientsAddressValue } // StoredRewardAddress stores the given [val] under rewardAddressStorageKey. From 8b40ad213055abbdee77f57aebea5dc1b85e98a9 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 16 Nov 2022 22:45:52 +0300 Subject: [PATCH 59/64] config cleanup --- precompile/reward_manager.go | 44 +++++++++++++++++++----------------- tests/e2e/solidity/suites.go | 6 ++--- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go index 523295022d..e9f55b568a 100644 --- a/precompile/reward_manager.go +++ b/precompile/reward_manager.go @@ -74,6 +74,22 @@ func (c *InitialRewardConfig) Equal(other *InitialRewardConfig) bool { return c.AllowFeeRecipients == other.AllowFeeRecipients && c.RewardAddress == other.RewardAddress } +func (i *InitialRewardConfig) Configure(state StateDB) { + // enable allow fee recipients + if i.AllowFeeRecipients { + EnableAllowFeeRecipients(state) + } else if i.RewardAddress == (common.Address{}) { + // if reward address is empty and allow fee recipients is false + // then disable rewards + DisableFeeRewards(state) + } else { + // set reward address + if err := StoreRewardAddress(state, i.RewardAddress); err != nil { + panic(err) + } + } +} + // RewardManagerConfig implements the StatefulPrecompileConfig // interface while adding in the RewardManager specific precompile config. type RewardManagerConfig struct { @@ -147,28 +163,14 @@ func (c *RewardManagerConfig) Configure(chainConfig ChainConfig, state StateDB, c.AllowListConfig.Configure(state, RewardManagerAddress) // configure the RewardManager with the given initial configuration if c.InitialRewardConfig != nil { - // enable allow fee recipients - if c.InitialRewardConfig.AllowFeeRecipients { - EnableAllowFeeRecipients(state) - return - } - // set the initial reward address if specified - if c.InitialRewardConfig.RewardAddress != (common.Address{}) { - if err := StoreRewardAddress(state, c.InitialRewardConfig.RewardAddress); err != nil { - panic(err) - } - return - } - // if we did not enable any reward until now, disable rewards - // since given initial config is not nil - DisableFeeRewards(state) - } else { // configure the RewardManager according to chainConfig - if chainConfig.AllowedFeeRecipients() { - EnableAllowFeeRecipients(state) - return - } + c.InitialRewardConfig.Configure(state) + } else if chainConfig.AllowedFeeRecipients() { + // configure the RewardManager according to chainConfig + EnableAllowFeeRecipients(state) + } else { // chainConfig does not have any reward address - // if it does not enable fee recipients, disable rewards + // if chainConfig does not enable fee recipients + // default to disabling rewards DisableFeeRewards(state) } } diff --git a/tests/e2e/solidity/suites.go b/tests/e2e/solidity/suites.go index 80b140600b..0d641112ff 100644 --- a/tests/e2e/solidity/suites.go +++ b/tests/e2e/solidity/suites.go @@ -92,14 +92,14 @@ var _ = utils.DescribePrecompile(func() { gomega.Expect(running).Should(gomega.BeFalse()) }) - ginkgo.It("reward manager", func() { + ginkgo.It("reward manager", ginkgo.Label("solidity-with-npx"), func() { err := startSubnet("./tests/e2e/genesis/reward_manager.json") gomega.Expect(err).Should(gomega.BeNil()) - running := runner.IsRunnerUp() + running := runner.IsRunnerUp(grpcEp) gomega.Expect(running).Should(gomega.BeTrue()) runHardhatTests("./test/ExampleRewardManager.ts") stopSubnet() - running = runner.IsRunnerUp() + running = runner.IsRunnerUp(grpcEp) gomega.Expect(running).Should(gomega.BeFalse()) }) From d65327aa2cddd22c45a9c93345945248e07ed4cc Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 18 Nov 2022 13:13:00 +0300 Subject: [PATCH 60/64] Update contract-examples/tasks.ts Co-authored-by: Darioush Jalali --- contract-examples/tasks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contract-examples/tasks.ts b/contract-examples/tasks.ts index 09cffd0eea..7adc64e828 100644 --- a/contract-examples/tasks.ts +++ b/contract-examples/tasks.ts @@ -226,7 +226,7 @@ task("feeManager:readRole", "a task to get the network deployer minter list") // npx hardhat rewardManager:currentRewardAddress --network local -task("rewardManager:currentRewardAddress", "a task to get the current configured rewarding address") +task("rewardManager:currentRewardAddress", "a task to get the current configured reward address") .setAction(async (_, hre) => { const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) const areFeeRecipientsAllowed = await rewardManager.areFeeRecipientsAllowed() From 71b978221d120a715b5d9be35d8fd1edd8a6ab2f Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 18 Nov 2022 13:16:34 +0300 Subject: [PATCH 61/64] Update contract-examples/tasks.ts Co-authored-by: Darioush Jalali --- contract-examples/tasks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contract-examples/tasks.ts b/contract-examples/tasks.ts index 7adc64e828..4c6e19230a 100644 --- a/contract-examples/tasks.ts +++ b/contract-examples/tasks.ts @@ -239,7 +239,7 @@ task("rewardManager:currentRewardAddress", "a task to get the current configured }) // npx hardhat rewardManager:areFeeRecipientsAllowed --network local -task("rewardManager:areFeeRecipientsAllowed", "a task to get whether the fee recipients are allowed") +task("rewardManager:areFeeRecipientsAllowed", "a task to get whether custom fee recipients are allowed to receive rewards") .setAction(async (_, hre) => { const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) const result = await rewardManager.areFeeRecipientsAllowed() From 35c167b735ef80915652c03d63142ec47680c26e Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 18 Nov 2022 13:18:49 +0300 Subject: [PATCH 62/64] more nit fixes --- contract-examples/tasks.ts | 46 +++++++++---------- .../test/ExampleRewardManager.ts | 2 +- params/config.go | 4 +- precompile/reward_manager.go | 1 - 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/contract-examples/tasks.ts b/contract-examples/tasks.ts index 09cffd0eea..dea597dc8e 100644 --- a/contract-examples/tasks.ts +++ b/contract-examples/tasks.ts @@ -39,7 +39,7 @@ task("balances", "Prints the list of account balances", async (args, hre): Promi }) -task("balance", "a task to get the balance") +task("balance", "get the balance") .addParam("address", "the address you want to know balance of") .setAction(async (args, hre) => { const balance = await hre.ethers.provider.getBalance(args.address) @@ -48,7 +48,7 @@ task("balance", "a task to get the balance") }) // npx hardhat allowList:readRole --network local --address [address] -task("deployerAllowList:readRole", "a task to get the network deployer allow list") +task("deployerAllowList:readRole", "Gets the network deployer allow list") .addParam("address", "the address you want to know the allowlist role for") .setAction(async (args, hre) => { const allowList = await hre.ethers.getContractAt("IAllowList", CONTRACT_ALLOW_LIST_ADDRESS) @@ -56,7 +56,7 @@ task("deployerAllowList:readRole", "a task to get the network deployer allow lis }) // npx hardhat allowList:addDeployer --network local --address [address] -task("deployerAllowList:addDeployer", "a task to add the deployer on the allow list") +task("deployerAllowList:addDeployer", "Adds the deployer on the allow list") .addParam("address", "the address you want to add as a deployer") .setAction(async (args, hre) => { const allowList = await hre.ethers.getContractAt("IAllowList", CONTRACT_ALLOW_LIST_ADDRESS) @@ -66,7 +66,7 @@ task("deployerAllowList:addDeployer", "a task to add the deployer on the allow l }) // npx hardhat allowList:addAdmin --network local --address [address] -task("deployerAllowList:addAdmin", "a task to add a admin on the allowlist") +task("deployerAllowList:addAdmin", "Adds an admin on the allowlist") .addParam("address", "the address you want to add as a admin") .setAction(async (args, hre) => { const allowList = await hre.ethers.getContractAt("IAllowList", CONTRACT_ALLOW_LIST_ADDRESS) @@ -75,7 +75,7 @@ task("deployerAllowList:addAdmin", "a task to add a admin on the allowlist") }) // npx hardhat allowList:revoke --network local --address [address] -task("deployerAllowList:revoke", "remove the address from the list") +task("deployerAllowList:revoke", "Removes the address from the list") .addParam("address", "the address you want to revoke all permission") .setAction(async (args, hre) => { const allowList = await hre.ethers.getContractAt("IAllowList", CONTRACT_ALLOW_LIST_ADDRESS) @@ -84,7 +84,7 @@ task("deployerAllowList:revoke", "remove the address from the list") }) // npx hardhat allowList:readRole --network local --address [address] -task("txAllowList:readRole", "a task to get the network transaction allow list") +task("txAllowList:readRole", "Gets the network transaction allow list") .addParam("address", "the address you want to know the allowlist role for") .setAction(async (args, hre) => { const allowList = await hre.ethers.getContractAt("IAllowList", TX_ALLOW_LIST_ADDRESS) @@ -92,7 +92,7 @@ task("txAllowList:readRole", "a task to get the network transaction allow list") }) // npx hardhat allowList:addDeployer --network local --address [address] -task("txAllowList:addDeployer", "a task to add an address to the transaction allow list") +task("txAllowList:addDeployer", "Adds an address to the transaction allow list") .addParam("address", "the address you want to add as a deployer") .setAction(async (args, hre) => { const allowList = await hre.ethers.getContractAt("IAllowList", TX_ALLOW_LIST_ADDRESS) @@ -102,7 +102,7 @@ task("txAllowList:addDeployer", "a task to add an address to the transaction all }) // npx hardhat allowList:addAdmin --network local --address [address] -task("txAllowList:addAdmin", "a task to add a admin on the transaction allow list") +task("txAllowList:addAdmin", "Adds an admin on the transaction allow list") .addParam("address", "the address you want to add as a admin") .setAction(async (args, hre) => { const allowList = await hre.ethers.getContractAt("IAllowList", TX_ALLOW_LIST_ADDRESS) @@ -111,7 +111,7 @@ task("txAllowList:addAdmin", "a task to add a admin on the transaction allow lis }) // npx hardhat allowList:revoke --network local --address [address] -task("txAllowList:revoke", "remove the address from the transaction allow list") +task("txAllowList:revoke", "Removes the address from the transaction allow list") .addParam("address", "the address you want to revoke all permission") .setAction(async (args, hre) => { const allowList = await hre.ethers.getContractAt("IAllowList", TX_ALLOW_LIST_ADDRESS) @@ -120,7 +120,7 @@ task("txAllowList:revoke", "remove the address from the transaction allow list") }) // npx hardhat minter:readRole --network local --address [address] -task("minter:readRole", "a task to get the network deployer minter list") +task("minter:readRole", "Gets the network deployer minter list") .addParam("address", "the address you want to know the minter role for") .setAction(async (args, hre) => { const allowList = await hre.ethers.getContractAt("INativeMinter", MINT_ADDRESS) @@ -129,7 +129,7 @@ task("minter:readRole", "a task to get the network deployer minter list") // npx hardhat minter:addMinter --network local --address [address] -task("minter:addMinter", "a task to add the address on the minter list") +task("minter:addMinter", "Adds the address on the minter list") .addParam("address", "the address you want to add as a minter") .setAction(async (args, hre) => { const allowList = await hre.ethers.getContractAt("INativeMinter", MINT_ADDRESS) @@ -138,7 +138,7 @@ task("minter:addMinter", "a task to add the address on the minter list") }) // npx hardhat minter:addAdmin --network local --address [address] -task("minter:addAdmin", "a task to add a admin on the minter list") +task("minter:addAdmin", "Adds an admin on the minter list") .addParam("address", "the address you want to add as a admin") .setAction(async (args, hre) => { const allowList = await hre.ethers.getContractAt("INativeMinter", MINT_ADDRESS) @@ -147,7 +147,7 @@ task("minter:addAdmin", "a task to add a admin on the minter list") }) // npx hardhat minter:revoke --network local --address [address] -task("minter:revoke", "remove the address from the list") +task("minter:revoke", "Removes the address from the list") .addParam("address", "the address you want to revoke all permission") .setAction(async (args, hre) => { const allowList = await hre.ethers.getContractAt("INativeMinter", MINT_ADDRESS) @@ -156,7 +156,7 @@ task("minter:revoke", "remove the address from the list") }) // npx hardhat minter:mint --network local --address [address] -task("minter:mint", "mint native token") +task("minter:mint", "Mints native tokens") .addParam("address", "the address you want to mint for") .addParam("amount", "the amount you want to mint") .setAction(async (args, hre) => { @@ -165,7 +165,7 @@ task("minter:mint", "mint native token") }) // npx hardhat minter:burn --network local --address [address] -task("minter:burn", "burn") +task("minter:burn", "Burns native tokens") .addParam("amount", "the amount you want to burn (in AVAX unit)") .setAction(async (args, hre) => { const [owner] = await hre.ethers.getSigners() @@ -177,7 +177,7 @@ task("minter:burn", "burn") }) // npx hardhat feeManager:set --network local --address [address] -task("feeManager:set", "sets fee config") +task("feeManager:set", "Sets the provided fee config") .addParam("gaslimit", "", undefined, undefined, false) .addParam("targetblockrate", "", undefined, undefined, false) .addParam("minbasefee", "", undefined, undefined, false) @@ -200,7 +200,7 @@ task("feeManager:set", "sets fee config") args.blockgascoststep) }) -task("feeManager:get", "gets fee config") +task("feeManager:get", "Gets the fee config") .setAction(async (_, hre) => { const feeManager = await hre.ethers.getContractAt("IFeeManager", FEE_MANAGER_ADDRESS) const result = await feeManager.getFeeConfig() @@ -217,7 +217,7 @@ task("feeManager:get", "gets fee config") // npx hardhat feeManager:readRole --network local --address [address] -task("feeManager:readRole", "a task to get the network deployer minter list") +task("feeManager:readRole", "Gets the network deployer minter list") .addParam("address", "the address you want to know the minter role for") .setAction(async (args, hre) => { const allowList = await hre.ethers.getContractAt("IFeeManager", FEE_MANAGER_ADDRESS) @@ -226,7 +226,7 @@ task("feeManager:readRole", "a task to get the network deployer minter list") // npx hardhat rewardManager:currentRewardAddress --network local -task("rewardManager:currentRewardAddress", "a task to get the current configured rewarding address") +task("rewardManager:currentRewardAddress", "Gets the current configured rewarding address") .setAction(async (_, hre) => { const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) const areFeeRecipientsAllowed = await rewardManager.areFeeRecipientsAllowed() @@ -239,7 +239,7 @@ task("rewardManager:currentRewardAddress", "a task to get the current configured }) // npx hardhat rewardManager:areFeeRecipientsAllowed --network local -task("rewardManager:areFeeRecipientsAllowed", "a task to get whether the fee recipients are allowed") +task("rewardManager:areFeeRecipientsAllowed", "Gets whether the fee recipients are allowed") .setAction(async (_, hre) => { const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) const result = await rewardManager.areFeeRecipientsAllowed() @@ -247,7 +247,7 @@ task("rewardManager:areFeeRecipientsAllowed", "a task to get whether the fee rec }) // npx hardhat rewardManager:setRewardAddress --network local --address [address] -task("rewardManager:setRewardAddress", "sets a new reward address") +task("rewardManager:setRewardAddress", "Sets a new reward address") .addParam("address", "the address that will receive rewards") .setAction(async (args, hre) => { const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) @@ -256,7 +256,7 @@ task("rewardManager:setRewardAddress", "sets a new reward address") }) // npx hardhat rewardManager:allowFeeRecipients --network local -task("rewardManager:allowFeeRecipients", "allows custom fee recipients to receive rewards") +task("rewardManager:allowFeeRecipients", "Allows custom fee recipients to receive rewards") .setAction(async (_, hre) => { const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) const result = await rewardManager.allowFeeRecipients() @@ -264,7 +264,7 @@ task("rewardManager:allowFeeRecipients", "allows custom fee recipients to receiv }) // npx hardhat rewardManager:disableRewards --network local -task("rewardManager:disableRewards", "disables all rewards, and starts burning fees.") +task("rewardManager:disableRewards", "Disables all rewards, and starts burning fees.") .setAction(async (_, hre) => { const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) const result = await rewardManager.disableRewards() diff --git a/contract-examples/test/ExampleRewardManager.ts b/contract-examples/test/ExampleRewardManager.ts index 901995b196..ebb0d78e23 100644 --- a/contract-examples/test/ExampleRewardManager.ts +++ b/contract-examples/test/ExampleRewardManager.ts @@ -11,7 +11,7 @@ import { import { ethers } from "hardhat" import ts = require("typescript"); -// make sure this is always an admin for minter precompile +// make sure this is always an admin for reward manager precompile const adminAddress: string = "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" const REWARD_MANAGER_ADDRESS = "0x0200000000000000000000000000000000000004"; const BLACKHOLE_ADDRESS = "0x0100000000000000000000000000000000000000"; diff --git a/params/config.go b/params/config.go index 56f89e44dd..cdf9314ead 100644 --- a/params/config.go +++ b/params/config.go @@ -555,13 +555,13 @@ func (c *ChainConfig) AvalancheRules(blockNum, blockTimestamp *big.Int) Rules { return rules } -// GetFeeConfig returns the original FeeConfig contained in the ChainConfig (will not be modified by FeeConfigManager. +// GetFeeConfig returns the original FeeConfig contained in the genesis ChainConfig. // Implements precompile.ChainConfig interface. func (c *ChainConfig) GetFeeConfig() commontype.FeeConfig { return c.FeeConfig } -// AllowedFeeRecipients returns whether the original AllowedFeeRecipients parameter (will not be modified by the RewardManager. +// AllowedFeeRecipients returns the original AllowedFeeRecipients parameter contained in the genesis ChainConfig. // Implements precompile.ChainConfig interface. func (c *ChainConfig) AllowedFeeRecipients() bool { return c.AllowFeeRecipients diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go index e9f55b568a..f887fa2f37 100644 --- a/precompile/reward_manager.go +++ b/precompile/reward_manager.go @@ -60,7 +60,6 @@ func (i *InitialRewardConfig) Verify() error { switch { case i.AllowFeeRecipients && i.RewardAddress != (common.Address{}): return ErrCannotEnableBothRewards - // shall we also check blackhole address here? default: return nil } From 86030493050f59594a4ef80bae0b582c90105e96 Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Fri, 18 Nov 2022 14:05:24 +0300 Subject: [PATCH 63/64] fix conflict --- contract-examples/tasks.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/contract-examples/tasks.ts b/contract-examples/tasks.ts index 229a3117b1..110072d93b 100644 --- a/contract-examples/tasks.ts +++ b/contract-examples/tasks.ts @@ -226,11 +226,7 @@ task("feeManager:readRole", "Gets the network deployer minter list") // npx hardhat rewardManager:currentRewardAddress --network local -<<<<<<< HEAD task("rewardManager:currentRewardAddress", "Gets the current configured rewarding address") -======= -task("rewardManager:currentRewardAddress", "a task to get the current configured reward address") ->>>>>>> 71b978221d120a715b5d9be35d8fd1edd8a6ab2f .setAction(async (_, hre) => { const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) const areFeeRecipientsAllowed = await rewardManager.areFeeRecipientsAllowed() From d6e2a0c692943963f2a0c425ff186f01524e3e6a Mon Sep 17 00:00:00 2001 From: Ceyhun Onur Date: Wed, 30 Nov 2022 18:47:01 +0300 Subject: [PATCH 64/64] move inline string ABIs to separate files and embed them --- accounts/abi/bind/bind.go | 74 +++--------- accounts/abi/bind/bind_test.go | 4 +- accounts/abi/bind/precompile_bind.go | 109 ++++++++++++++++++ ...ompile_test.go => precompile_bind_test.go} | 2 +- accounts/abi/bind/precompile_template.go | 17 ++- cmd/abigen/main.go | 2 +- cmd/precompilegen/main.go | 24 +++- precompile/reward_manager.abi | 1 + precompile/reward_manager.go | 9 +- 9 files changed, 165 insertions(+), 77 deletions(-) create mode 100644 accounts/abi/bind/precompile_bind.go rename accounts/abi/bind/{bind_precompile_test.go => precompile_bind_test.go} (96%) create mode 100644 precompile/reward_manager.abi diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index a126817308..be5a61a3b6 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -51,6 +51,8 @@ const ( readAllowListFuncKey = "readAllowList" ) +type BindHook func(lang Lang, types []string, contracts map[string]*tmplContract, structs map[string]*tmplStruct) (data interface{}, templateSource string, err error) + // Lang is a target programming language selector to generate bindings for. type Lang int @@ -101,7 +103,11 @@ func isKeyWord(arg string) bool { // to be used as is in client code, but rather as an intermediate struct which // enforces compile time type safety and naming convention opposed to having to // manually maintain hard coded strings that break on runtime. -func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string, isPrecompile bool) (string, error) { +func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) { + return BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, nil) +} + +func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string, bindHook BindHook) (string, error) { var ( // contracts is the map of each individual contract requested binding contracts = make(map[string]*tmplContract) @@ -177,11 +183,6 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] normalized.Outputs = make([]abi.Argument, len(original.Outputs)) copy(normalized.Outputs, original.Outputs) for j, output := range normalized.Outputs { - if isPrecompile { - if output.Name == "" { - return "", fmt.Errorf("ABI outputs for %s require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs", normalized.Name) - } - } if output.Name != "" { normalized.Outputs[j].Name = capitalise(output.Name) } @@ -290,19 +291,14 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] templateSource string ) - // Generate the contract template data according to contract type (precompile/non) - if isPrecompile { - if lang != LangGo { - return "", errors.New("only GoLang binding for precompiled contracts is supported yet") - } - - if len(contracts) != 1 { - return "", errors.New("cannot generate more than 1 contract") + // Generate the contract template data according to hook + if bindHook != nil { + var err error + data, templateSource, err = bindHook(lang, types, contracts, structs) + if err != nil { + return "", err } - precompileType := types[0] - firstContract := contracts[precompileType] - data, templateSource = createPrecompileDataAndTemplate(firstContract, structs) - } else { + } else { // default to generate contract binding templateSource = tmplSource[lang] data = &tmplData{ Package: pkg, @@ -709,45 +705,3 @@ func hasStruct(t abi.Type) bool { return false } } - -func createPrecompileDataAndTemplate(contract *tmplContract, structs map[string]*tmplStruct) (interface{}, string) { - funcs := make(map[string]*tmplMethod) - - for k, v := range contract.Transacts { - funcs[k] = v - } - - for k, v := range contract.Calls { - funcs[k] = v - } - isAllowList := allowListEnabled(funcs) - if isAllowList { - // remove these functions as we will directly inherit AllowList - delete(funcs, readAllowListFuncKey) - delete(funcs, setAdminFuncKey) - delete(funcs, setEnabledFuncKey) - delete(funcs, setNoneFuncKey) - } - - precompileContract := &tmplPrecompileContract{ - tmplContract: contract, - AllowList: isAllowList, - Funcs: funcs, - } - - data := &tmplPrecompileData{ - Contract: precompileContract, - Structs: structs, - } - return data, tmplSourcePrecompileGo -} - -func allowListEnabled(funcs map[string]*tmplMethod) bool { - keys := []string{readAllowListFuncKey, setAdminFuncKey, setEnabledFuncKey, setNoneFuncKey} - for _, key := range keys { - if _, ok := funcs[key]; !ok { - return false - } - } - return true -} diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index b61fadf9ce..f6c021eca3 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -2106,7 +2106,7 @@ func golangBindings(t *testing.T, overload bool) { types = []string{tt.name} } // Generate the binding and create a Go source file in the workspace - bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases, false) + bind, err := Bind(types, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases) if err != nil { t.Fatalf("test %d: failed to generate binding: %v", i, err) } @@ -2536,7 +2536,7 @@ public class Test { }, } for i, c := range cases { - binding, err := Bind([]string{c.name}, []string{c.abi}, []string{c.bytecode}, nil, "bindtest", LangJava, nil, nil, false) + binding, err := Bind([]string{c.name}, []string{c.abi}, []string{c.bytecode}, nil, "bindtest", LangJava, nil, nil) if err != nil { t.Fatalf("test %d: failed to generate binding: %v", i, err) } diff --git a/accounts/abi/bind/precompile_bind.go b/accounts/abi/bind/precompile_bind.go new file mode 100644 index 0000000000..ddc940eedc --- /dev/null +++ b/accounts/abi/bind/precompile_bind.go @@ -0,0 +1,109 @@ +// (c) 2019-2020, Ava Labs, Inc. +// +// This file is a derived work, based on the go-ethereum library whose original +// notices appear below. +// +// It is distributed under a license compatible with the licensing terms of the +// original code from which it is derived. +// +// Much love to the original authors for their work. +// ********** +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package bind generates Ethereum contract Go bindings. +// +// Detailed usage document and tutorial available on the go-ethereum Wiki page: +// https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts +package bind + +import ( + "errors" + "fmt" +) + +func PrecompileBind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string, abifilename string) (string, error) { + // create hook + var createPrecompileFunc BindHook = func(lang Lang, types []string, contracts map[string]*tmplContract, structs map[string]*tmplStruct) (interface{}, string, error) { + // verify first + if lang != LangGo { + return nil, "", errors.New("only GoLang binding for precompiled contracts is supported yet") + } + + if len(types) != 1 { + return nil, "", errors.New("cannot generate more than 1 contract") + } + funcs := make(map[string]*tmplMethod) + + contract := contracts[types[0]] + + for k, v := range contract.Transacts { + if err := checkOutputName(*v); err != nil { + return nil, "", err + } + funcs[k] = v + } + + for k, v := range contract.Calls { + if err := checkOutputName(*v); err != nil { + return nil, "", err + } + funcs[k] = v + } + isAllowList := allowListEnabled(funcs) + if isAllowList { + // remove these functions as we will directly inherit AllowList + delete(funcs, readAllowListFuncKey) + delete(funcs, setAdminFuncKey) + delete(funcs, setEnabledFuncKey) + delete(funcs, setNoneFuncKey) + } + + precompileContract := &tmplPrecompileContract{ + tmplContract: contract, + AllowList: isAllowList, + Funcs: funcs, + ABIFilename: abifilename, + } + + data := &tmplPrecompileData{ + Contract: precompileContract, + Structs: structs, + } + return data, tmplSourcePrecompileGo, nil + } + + return BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, createPrecompileFunc) +} + +func allowListEnabled(funcs map[string]*tmplMethod) bool { + keys := []string{readAllowListFuncKey, setAdminFuncKey, setEnabledFuncKey, setNoneFuncKey} + for _, key := range keys { + if _, ok := funcs[key]; !ok { + return false + } + } + return true +} + +func checkOutputName(method tmplMethod) error { + for _, output := range method.Original.Outputs { + if output.Name == "" { + return fmt.Errorf("ABI outputs for %s require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs", method.Original.Name) + } + } + return nil +} diff --git a/accounts/abi/bind/bind_precompile_test.go b/accounts/abi/bind/precompile_bind_test.go similarity index 96% rename from accounts/abi/bind/bind_precompile_test.go rename to accounts/abi/bind/precompile_bind_test.go index 03cbcebc09..b5cab4b257 100644 --- a/accounts/abi/bind/bind_precompile_test.go +++ b/accounts/abi/bind/precompile_bind_test.go @@ -96,7 +96,7 @@ func golangBindingsFailure(t *testing.T) { for i, tt := range bindFailedTests { t.Run(tt.name, func(t *testing.T) { // Generate the binding - _, err := Bind([]string{tt.name}, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases, true) + _, err := PrecompileBind([]string{tt.name}, tt.abi, tt.bytecode, tt.fsigs, "bindtest", LangGo, tt.libs, tt.aliases, "") if err == nil { t.Fatalf("test %d: no error occurred but was expected", i) } diff --git a/accounts/abi/bind/precompile_template.go b/accounts/abi/bind/precompile_template.go index 58c96f35ab..2f4cd39ad1 100644 --- a/accounts/abi/bind/precompile_template.go +++ b/accounts/abi/bind/precompile_template.go @@ -11,8 +11,9 @@ type tmplPrecompileData struct { // tmplPrecompileContract contains the data needed to generate an individual contract binding. type tmplPrecompileContract struct { *tmplContract - AllowList bool // Indicator whether the contract uses AllowList precompile - Funcs map[string]*tmplMethod // Contract functions that include both Calls + Transacts in tmplContract + AllowList bool // Indicator whether the contract uses AllowList precompile + Funcs map[string]*tmplMethod // Contract functions that include both Calls + Transacts in tmplContract + ABIFilename string // Path to the ABI file } // tmplSourcePrecompileGo is the Go precompiled source template. @@ -53,6 +54,8 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/vmerrs" + _ "embed" + "github.com/ethereum/go-ethereum/common" ) @@ -63,9 +66,6 @@ const ( {{- if .Contract.Fallback}} {{.Contract.Type}}FallbackGasCost uint64 = 0 // SET A GAS COST LESS THAN 2300 HERE {{- end}} - - // {{.Contract.Type}}RawABI contains the raw ABI of {{.Contract.Type}} contract. - {{.Contract.Type}}RawABI = "{{.Contract.InputABI}}" ) // CUSTOM CODE STARTS HERE @@ -94,6 +94,13 @@ var ( Err{{.Contract.Type}}CannotFallback = errors.New("non-enabled cannot call fallback function") {{- end}} + // {{.Contract.Type}}RawABI contains the raw ABI of {{.Contract.Type}} contract. + {{- if .Contract.ABIFilename | eq ""}} + {{.Contract.Type}}RawABI = "{{.Contract.InputABI}}" + {{- else}} + //go:embed {{.Contract.ABIFilename}} + {{.Contract.Type}}RawABI string + {{- end}} {{.Contract.Type}}ABI abi.ABI // will be initialized by init function {{.Contract.Type}}Precompile StatefulPrecompiledContract // will be initialized by init function diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go index 2cb10e587d..015410ce62 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -232,7 +232,7 @@ func abigen(c *cli.Context) error { } } // Generate the contract binding - code, err := bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs, aliases, false) + code, err := bind.Bind(types, abis, bins, sigs, c.String(pkgFlag.Name), lang, libs, aliases) if err != nil { utils.Fatalf("Failed to generate ABI binding: %v", err) } diff --git a/cmd/precompilegen/main.go b/cmd/precompilegen/main.go index 99aed93c52..454a560bc5 100644 --- a/cmd/precompilegen/main.go +++ b/cmd/precompilegen/main.go @@ -124,21 +124,35 @@ func precompilegen(c *cli.Context) error { kind = strings.TrimSpace(kind) } types = append(types, kind) - + outFlagSet := c.IsSet(outFlag.Name) + outFlag := c.String(outFlag.Name) + abifilename := "" + abipath := "" + // we should not generate the abi file if output is set to stdout + if outFlagSet { + // get file name from the output path + pathNoExt := strings.TrimSuffix(outFlag, filepath.Ext(outFlag)) + abipath = pathNoExt + ".abi" + abifilename = filepath.Base(abipath) + } // Generate the contract precompile - code, err := bind.Bind(types, abis, bins, sigs, pkg, lang, libs, aliases, true) + code, err := bind.PrecompileBind(types, abis, bins, sigs, pkg, lang, libs, aliases, abifilename) if err != nil { utils.Fatalf("Failed to generate ABI precompile: %v", err) } // Either flush it out to a file or display on the standard output - if !c.IsSet(outFlag.Name) { + if !outFlagSet { fmt.Printf("%s\n", code) return nil } - if err := os.WriteFile(c.String(outFlag.Name), []byte(code), 0o600); err != nil { - utils.Fatalf("Failed to write ABI precompile: %v", err) + if err := os.WriteFile(outFlag, []byte(code), 0o600); err != nil { + utils.Fatalf("Failed to write generated precompile: %v", err) + } + + if err := os.WriteFile(abipath, []byte(abis[0]), 0o600); err != nil { + utils.Fatalf("Failed to write ABI: %v", err) } fmt.Println("Precompile Generation was a success!") diff --git a/precompile/reward_manager.abi b/precompile/reward_manager.abi new file mode 100644 index 0000000000..d21d5bdc6b --- /dev/null +++ b/precompile/reward_manager.abi @@ -0,0 +1 @@ +[{"inputs":[],"name":"allowFeeRecipients","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"areFeeRecipientsAllowed","outputs":[{"internalType":"bool","name":"isAllowed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentRewardAddress","outputs":[{"internalType":"address","name":"rewardAddress","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setRewardAddress","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompile/reward_manager.go b/precompile/reward_manager.go index f887fa2f37..f57b0f72a8 100644 --- a/precompile/reward_manager.go +++ b/precompile/reward_manager.go @@ -17,6 +17,8 @@ import ( "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/vmerrs" + _ "embed" + "github.com/ethereum/go-ethereum/common" ) @@ -26,9 +28,6 @@ const ( CurrentRewardAddressGasCost uint64 = readGasCostPerSlot DisableRewardsGasCost uint64 = (writeGasCostPerSlot) + ReadAllowListGasCost // write 1 slot + read allow list SetRewardAddressGasCost uint64 = (writeGasCostPerSlot) + ReadAllowListGasCost // write 1 slot + read allow list - - // RewardManagerRawABI contains the raw ABI of RewardManager contract. - RewardManagerRawABI = "[{\"inputs\":[],\"name\":\"allowFeeRecipients\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"areFeeRecipientsAllowed\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isAllowed\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"currentRewardAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"rewardAddress\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"disableRewards\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"readAllowList\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"role\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setEnabled\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setNone\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"setRewardAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" ) // Singleton StatefulPrecompiledContract and signatures. @@ -44,6 +43,10 @@ var ( ErrCannotEnableBothRewards = errors.New("cannot enable both fee recipients and reward address at the same time") ErrEmptyRewardAddress = errors.New("reward address cannot be empty") + // RewardManagerRawABI contains the raw ABI of RewardManager contract. + //go:embed reward_manager.abi + RewardManagerRawABI string + RewardManagerABI abi.ABI // will be initialized by init function RewardManagerPrecompile StatefulPrecompiledContract // will be initialized by init function