mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
457 lines
16 KiB
Go
457 lines
16 KiB
Go
// (c) 2020-2023, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package feemanager
|
|
|
|
import (
|
|
"math/big"
|
|
"testing"
|
|
"github.com/luxfi/evm/commontype"
|
|
"github.com/luxfi/evm/core/extstate/testhelpers"
|
|
"github.com/luxfi/evm/precompile/allowlist"
|
|
"github.com/luxfi/evm/precompile/contract"
|
|
"github.com/luxfi/evm/precompile/precompileconfig"
|
|
"github.com/luxfi/evm/precompile/testutils"
|
|
"github.com/luxfi/evm/vmerrs"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/mock/gomock"
|
|
)
|
|
|
|
var (
|
|
regressionBytes = "8f10b58600000000000000000000000000000000000000000000000000000000017d78400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000012a05f20000000000000000000000000000000000000000000000000000000000047868c0000000000000000000000000000000000000000000000000000000000000005400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001bc16d674ec800000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000"
|
|
regressionFeeConfig = commontype.FeeConfig{
|
|
GasLimit: big.NewInt(25000000),
|
|
TargetBlockRate: 2,
|
|
MinBaseFee: big.NewInt(5000000000),
|
|
TargetGas: big.NewInt(75000000),
|
|
BaseFeeChangeDenominator: big.NewInt(84),
|
|
MinBlockGasCost: big.NewInt(0),
|
|
MaxBlockGasCost: big.NewInt(2000000000000000000),
|
|
BlockGasCostStep: big.NewInt(1000000000000000000),
|
|
}
|
|
testFeeConfig = commontype.FeeConfig{
|
|
GasLimit: big.NewInt(8_000_000),
|
|
TargetBlockRate: 2, // in seconds
|
|
|
|
MinBaseFee: big.NewInt(25_000_000_000),
|
|
TargetGas: big.NewInt(15_000_000),
|
|
BaseFeeChangeDenominator: big.NewInt(36),
|
|
|
|
MinBlockGasCost: big.NewInt(0),
|
|
MaxBlockGasCost: big.NewInt(1_000_000),
|
|
BlockGasCostStep: big.NewInt(200_000),
|
|
}
|
|
zeroFeeConfig = commontype.FeeConfig{
|
|
GasLimit: new(big.Int),
|
|
MinBaseFee: new(big.Int),
|
|
TargetGas: new(big.Int),
|
|
BaseFeeChangeDenominator: new(big.Int),
|
|
|
|
MinBlockGasCost: new(big.Int),
|
|
MaxBlockGasCost: new(big.Int),
|
|
BlockGasCostStep: new(big.Int),
|
|
}
|
|
testBlockNumber = big.NewInt(7)
|
|
tests = map[string]testutils.PrecompileTest{
|
|
"set config from no role fails": {
|
|
Caller: allowlist.TestNoRoleAddr,
|
|
BeforeHook: allowlist.SetDefaultRoles(Module.Address()),
|
|
InputFn: func(t testing.TB) []byte {
|
|
input, err := PackSetFeeConfig(testFeeConfig)
|
|
require.NoError(t, err)
|
|
|
|
return input
|
|
},
|
|
SuppliedGas: SetFeeConfigGasCost,
|
|
ReadOnly: false,
|
|
ExpectedErr: ErrCannotChangeFee.Error(),
|
|
},
|
|
"set config from enabled address succeeds and emits logs": {
|
|
Caller: allowlist.TestEnabledAddr,
|
|
BeforeHook: allowlist.SetDefaultRoles(Module.Address()),
|
|
InputFn: func(t testing.TB) []byte {
|
|
input, err := PackSetFeeConfig(testFeeConfig)
|
|
require.NoError(t, err)
|
|
|
|
return input
|
|
},
|
|
SuppliedGas: SetFeeConfigGasCost + FeeConfigChangedEventGasCost,
|
|
ReadOnly: false,
|
|
ExpectedRes: []byte{},
|
|
AfterHook: func(t testing.TB, state contract.StateDB) {
|
|
feeConfig := GetStoredFeeConfig(state)
|
|
require.Equal(t, testFeeConfig, feeConfig)
|
|
|
|
logsTopics, logsData := state.GetLogData()
|
|
assertFeeEvent(t, logsTopics, logsData, allowlist.TestEnabledAddr, zeroFeeConfig, testFeeConfig)
|
|
},
|
|
},
|
|
"set config from manager succeeds": {
|
|
Caller: allowlist.TestManagerAddr,
|
|
BeforeHook: allowlist.SetDefaultRoles(Module.Address()),
|
|
InputFn: func(t testing.TB) []byte {
|
|
input, err := PackSetFeeConfig(testFeeConfig)
|
|
require.NoError(t, err)
|
|
|
|
return input
|
|
},
|
|
SuppliedGas: SetFeeConfigGasCost + FeeConfigChangedEventGasCost,
|
|
ReadOnly: false,
|
|
ExpectedRes: []byte{},
|
|
AfterHook: func(t testing.TB, state contract.StateDB) {
|
|
feeConfig := GetStoredFeeConfig(state)
|
|
require.Equal(t, testFeeConfig, feeConfig)
|
|
|
|
logsTopics, logsData := state.GetLogData()
|
|
assertFeeEvent(t, logsTopics, logsData, allowlist.TestManagerAddr, zeroFeeConfig, testFeeConfig)
|
|
},
|
|
},
|
|
"set invalid config from enabled address": {
|
|
Caller: allowlist.TestEnabledAddr,
|
|
BeforeHook: allowlist.SetDefaultRoles(Module.Address()),
|
|
InputFn: func(t testing.TB) []byte {
|
|
feeConfig := testFeeConfig
|
|
feeConfig.MinBlockGasCost = new(big.Int).Mul(feeConfig.MaxBlockGasCost, common.Big2)
|
|
input, err := PackSetFeeConfig(feeConfig)
|
|
require.NoError(t, err)
|
|
|
|
return input
|
|
},
|
|
SuppliedGas: SetFeeConfigGasCost + FeeConfigChangedEventGasCost,
|
|
ReadOnly: false,
|
|
Config: &Config{
|
|
InitialFeeConfig: &testFeeConfig,
|
|
},
|
|
ExpectedErr: "cannot be greater than maxBlockGasCost",
|
|
AfterHook: func(t testing.TB, state contract.StateDB) {
|
|
feeConfig := GetStoredFeeConfig(state)
|
|
require.Equal(t, testFeeConfig, feeConfig)
|
|
},
|
|
},
|
|
"set config from admin address": {
|
|
Caller: allowlist.TestAdminAddr,
|
|
BeforeHook: allowlist.SetDefaultRoles(Module.Address()),
|
|
InputFn: func(t testing.TB) []byte {
|
|
input, err := PackSetFeeConfig(testFeeConfig)
|
|
require.NoError(t, err)
|
|
|
|
return input
|
|
},
|
|
SuppliedGas: SetFeeConfigGasCost + FeeConfigChangedEventGasCost,
|
|
ReadOnly: false,
|
|
ExpectedRes: []byte{},
|
|
SetupBlockContext: func(mbc *contract.MockBlockContext) {
|
|
mbc.EXPECT().Number().Return(testBlockNumber).AnyTimes()
|
|
mbc.EXPECT().Timestamp().Return(uint64(0)).AnyTimes()
|
|
},
|
|
AfterHook: func(t testing.TB, state contract.StateDB) {
|
|
feeConfig := GetStoredFeeConfig(state)
|
|
require.Equal(t, testFeeConfig, feeConfig)
|
|
lastChangedAt := GetFeeConfigLastChangedAt(state)
|
|
require.EqualValues(t, testBlockNumber, lastChangedAt)
|
|
|
|
logsTopics, logsData := state.GetLogData()
|
|
assertFeeEvent(t, logsTopics, logsData, allowlist.TestAdminAddr, zeroFeeConfig, testFeeConfig)
|
|
},
|
|
},
|
|
"get fee config from non-enabled address": {
|
|
Caller: allowlist.TestNoRoleAddr,
|
|
BeforeHook: func(t testing.TB, state contract.StateDB) {
|
|
blockContext := contract.NewMockBlockContext(gomock.NewController(t))
|
|
blockContext.EXPECT().Number().Return(big.NewInt(6)).Times(1)
|
|
allowlist.SetDefaultRoles(Module.Address())(t, state)
|
|
err := StoreFeeConfig(state, testFeeConfig, blockContext)
|
|
require.NoError(t, err)
|
|
},
|
|
InputFn: func(t testing.TB) []byte {
|
|
input, err := PackGetFeeConfig()
|
|
require.NoError(t, err)
|
|
|
|
return input
|
|
},
|
|
SuppliedGas: GetFeeConfigGasCost,
|
|
ReadOnly: true,
|
|
ExpectedRes: func() []byte {
|
|
res, err := PackGetFeeConfigOutput(testFeeConfig)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return res
|
|
}(),
|
|
AfterHook: func(t testing.TB, state contract.StateDB) {
|
|
feeConfig := GetStoredFeeConfig(state)
|
|
lastChangedAt := GetFeeConfigLastChangedAt(state)
|
|
require.Equal(t, testFeeConfig, feeConfig)
|
|
require.EqualValues(t, big.NewInt(6), lastChangedAt)
|
|
},
|
|
},
|
|
"get initial fee config": {
|
|
Caller: allowlist.TestNoRoleAddr,
|
|
BeforeHook: allowlist.SetDefaultRoles(Module.Address()),
|
|
InputFn: func(t testing.TB) []byte {
|
|
input, err := PackGetFeeConfig()
|
|
require.NoError(t, err)
|
|
|
|
return input
|
|
},
|
|
SuppliedGas: GetFeeConfigGasCost,
|
|
Config: &Config{
|
|
InitialFeeConfig: &testFeeConfig,
|
|
},
|
|
ReadOnly: true,
|
|
ExpectedRes: func() []byte {
|
|
res, err := PackGetFeeConfigOutput(testFeeConfig)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return res
|
|
}(),
|
|
SetupBlockContext: func(mbc *contract.MockBlockContext) {
|
|
mbc.EXPECT().Number().Return(testBlockNumber)
|
|
},
|
|
AfterHook: func(t testing.TB, state contract.StateDB) {
|
|
feeConfig := GetStoredFeeConfig(state)
|
|
lastChangedAt := GetFeeConfigLastChangedAt(state)
|
|
require.Equal(t, testFeeConfig, feeConfig)
|
|
require.EqualValues(t, testBlockNumber, lastChangedAt)
|
|
},
|
|
},
|
|
"get last changed at from non-enabled address": {
|
|
Caller: allowlist.TestNoRoleAddr,
|
|
BeforeHook: func(t testing.TB, state contract.StateDB) {
|
|
blockContext := contract.NewMockBlockContext(gomock.NewController(t))
|
|
blockContext.EXPECT().Number().Return(testBlockNumber).Times(1)
|
|
allowlist.SetDefaultRoles(Module.Address())(t, state)
|
|
err := StoreFeeConfig(state, testFeeConfig, blockContext)
|
|
require.NoError(t, err)
|
|
},
|
|
InputFn: func(t testing.TB) []byte {
|
|
input, err := PackGetFeeConfigLastChangedAt()
|
|
require.NoError(t, err)
|
|
|
|
return input
|
|
},
|
|
SuppliedGas: GetLastChangedAtGasCost,
|
|
ReadOnly: true,
|
|
ExpectedRes: func() []byte {
|
|
res, err := PackGetFeeConfigLastChangedAtOutput(testBlockNumber)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return res
|
|
}(),
|
|
AfterHook: func(t testing.TB, state contract.StateDB) {
|
|
feeConfig := GetStoredFeeConfig(state)
|
|
lastChangedAt := GetFeeConfigLastChangedAt(state)
|
|
require.Equal(t, testFeeConfig, feeConfig)
|
|
require.Equal(t, testBlockNumber, lastChangedAt)
|
|
},
|
|
},
|
|
"readOnly setFeeConfig with noRole fails": {
|
|
Caller: allowlist.TestNoRoleAddr,
|
|
BeforeHook: allowlist.SetDefaultRoles(Module.Address()),
|
|
InputFn: func(t testing.TB) []byte {
|
|
input, err := PackSetFeeConfig(testFeeConfig)
|
|
require.NoError(t, err)
|
|
|
|
return input
|
|
},
|
|
SuppliedGas: SetFeeConfigGasCost,
|
|
ReadOnly: true,
|
|
ExpectedErr: vmerrs.ErrWriteProtection.Error(),
|
|
},
|
|
"readOnly setFeeConfig with allow role fails": {
|
|
Caller: allowlist.TestEnabledAddr,
|
|
BeforeHook: allowlist.SetDefaultRoles(Module.Address()),
|
|
InputFn: func(t testing.TB) []byte {
|
|
input, err := PackSetFeeConfig(testFeeConfig)
|
|
require.NoError(t, err)
|
|
|
|
return input
|
|
},
|
|
SuppliedGas: SetFeeConfigGasCost,
|
|
ReadOnly: true,
|
|
ExpectedErr: vmerrs.ErrWriteProtection.Error(),
|
|
},
|
|
"readOnly setFeeConfig with admin role fails": {
|
|
Caller: allowlist.TestAdminAddr,
|
|
BeforeHook: allowlist.SetDefaultRoles(Module.Address()),
|
|
InputFn: func(t testing.TB) []byte {
|
|
input, err := PackSetFeeConfig(testFeeConfig)
|
|
require.NoError(t, err)
|
|
|
|
return input
|
|
},
|
|
SuppliedGas: SetFeeConfigGasCost,
|
|
ReadOnly: true,
|
|
ExpectedErr: vmerrs.ErrWriteProtection.Error(),
|
|
},
|
|
"insufficient gas setFeeConfig from admin": {
|
|
Caller: allowlist.TestAdminAddr,
|
|
BeforeHook: allowlist.SetDefaultRoles(Module.Address()),
|
|
InputFn: func(t testing.TB) []byte {
|
|
input, err := PackSetFeeConfig(testFeeConfig)
|
|
require.NoError(t, err)
|
|
|
|
return input
|
|
},
|
|
SuppliedGas: SetFeeConfigGasCost - 1,
|
|
ReadOnly: false,
|
|
ExpectedErr: vmerrs.ErrOutOfGas.Error(),
|
|
},
|
|
"set config with extra padded bytes should fail before Durango": {
|
|
Caller: allowlist.TestEnabledAddr,
|
|
BeforeHook: allowlist.SetDefaultRoles(Module.Address()),
|
|
InputFn: func(t testing.TB) []byte {
|
|
input, err := PackSetFeeConfig(testFeeConfig)
|
|
require.NoError(t, err)
|
|
|
|
input = append(input, make([]byte, 32)...)
|
|
return input
|
|
},
|
|
ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig {
|
|
config := precompileconfig.NewMockChainConfig(ctrl)
|
|
config.EXPECT().IsDurango(gomock.Any()).Return(false).AnyTimes()
|
|
return config
|
|
},
|
|
SuppliedGas: SetFeeConfigGasCost,
|
|
ReadOnly: false,
|
|
ExpectedErr: ErrInvalidLen.Error(),
|
|
SetupBlockContext: func(mbc *contract.MockBlockContext) {
|
|
mbc.EXPECT().Number().Return(testBlockNumber).AnyTimes()
|
|
mbc.EXPECT().Timestamp().Return(uint64(0)).AnyTimes()
|
|
},
|
|
},
|
|
"set config with extra padded bytes should succeed with Durango": {
|
|
Caller: allowlist.TestEnabledAddr,
|
|
BeforeHook: allowlist.SetDefaultRoles(Module.Address()),
|
|
InputFn: func(t testing.TB) []byte {
|
|
input, err := PackSetFeeConfig(testFeeConfig)
|
|
require.NoError(t, err)
|
|
|
|
input = append(input, make([]byte, 32)...)
|
|
return input
|
|
},
|
|
ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig {
|
|
config := precompileconfig.NewMockChainConfig(ctrl)
|
|
config.EXPECT().IsDurango(gomock.Any()).Return(true).AnyTimes()
|
|
return config
|
|
},
|
|
SuppliedGas: SetFeeConfigGasCost + FeeConfigChangedEventGasCost,
|
|
ReadOnly: false,
|
|
ExpectedRes: []byte{},
|
|
SetupBlockContext: func(mbc *contract.MockBlockContext) {
|
|
mbc.EXPECT().Number().Return(testBlockNumber).AnyTimes()
|
|
mbc.EXPECT().Timestamp().Return(uint64(0)).AnyTimes()
|
|
},
|
|
AfterHook: func(t testing.TB, state contract.StateDB) {
|
|
feeConfig := GetStoredFeeConfig(state)
|
|
require.Equal(t, testFeeConfig, feeConfig)
|
|
lastChangedAt := GetFeeConfigLastChangedAt(state)
|
|
require.EqualValues(t, testBlockNumber, lastChangedAt)
|
|
|
|
logsTopics, logsData := state.GetLogData()
|
|
assertFeeEvent(t, logsTopics, logsData, allowlist.TestEnabledAddr, zeroFeeConfig, testFeeConfig)
|
|
},
|
|
},
|
|
// from https://github.com/luxfi/evm/issues/487
|
|
"setFeeConfig regression test should fail before DUpgrade": {
|
|
Caller: allowlist.TestEnabledAddr,
|
|
BeforeHook: allowlist.SetDefaultRoles(Module.Address()),
|
|
Input: common.Hex2Bytes(regressionBytes),
|
|
ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig {
|
|
config := precompileconfig.NewMockChainConfig(ctrl)
|
|
config.EXPECT().IsDurango(gomock.Any()).Return(false).AnyTimes()
|
|
return config
|
|
},
|
|
SuppliedGas: SetFeeConfigGasCost,
|
|
ExpectedErr: ErrInvalidLen.Error(),
|
|
ReadOnly: false,
|
|
SetupBlockContext: func(mbc *contract.MockBlockContext) {
|
|
mbc.EXPECT().Number().Return(testBlockNumber).AnyTimes()
|
|
mbc.EXPECT().Timestamp().Return(uint64(0)).AnyTimes()
|
|
},
|
|
},
|
|
"setFeeConfig regression test should succeed after Durango": {
|
|
Caller: allowlist.TestEnabledAddr,
|
|
BeforeHook: allowlist.SetDefaultRoles(Module.Address()),
|
|
Input: common.Hex2Bytes(regressionBytes),
|
|
ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig {
|
|
config := precompileconfig.NewMockChainConfig(ctrl)
|
|
config.EXPECT().IsDurango(gomock.Any()).Return(true).AnyTimes()
|
|
return config
|
|
},
|
|
SuppliedGas: SetFeeConfigGasCost + FeeConfigChangedEventGasCost,
|
|
ReadOnly: false,
|
|
ExpectedRes: []byte{},
|
|
SetupBlockContext: func(mbc *contract.MockBlockContext) {
|
|
mbc.EXPECT().Number().Return(testBlockNumber).AnyTimes()
|
|
mbc.EXPECT().Timestamp().Return(uint64(0)).AnyTimes()
|
|
},
|
|
AfterHook: func(t testing.TB, state contract.StateDB) {
|
|
feeConfig := GetStoredFeeConfig(state)
|
|
require.Equal(t, regressionFeeConfig, feeConfig)
|
|
lastChangedAt := GetFeeConfigLastChangedAt(state)
|
|
require.EqualValues(t, testBlockNumber, lastChangedAt)
|
|
|
|
logsTopics, logsData := state.GetLogData()
|
|
assertFeeEvent(t, logsTopics, logsData, allowlist.TestEnabledAddr, zeroFeeConfig, regressionFeeConfig)
|
|
},
|
|
},
|
|
"set config should not emit event before Durango": {
|
|
Caller: allowlist.TestEnabledAddr,
|
|
BeforeHook: allowlist.SetDefaultRoles(Module.Address()),
|
|
ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig {
|
|
config := precompileconfig.NewMockChainConfig(ctrl)
|
|
config.EXPECT().IsDurango(gomock.Any()).Return(false).AnyTimes()
|
|
return config
|
|
},
|
|
InputFn: func(t testing.TB) []byte {
|
|
input, err := PackSetFeeConfig(testFeeConfig)
|
|
require.NoError(t, err)
|
|
return input
|
|
},
|
|
SuppliedGas: SetFeeConfigGasCost,
|
|
ReadOnly: false,
|
|
ExpectedRes: []byte{},
|
|
AfterHook: func(t testing.TB, state contract.StateDB) {
|
|
logsTopics, logsData := state.GetLogData()
|
|
require.Len(t, logsTopics, 0)
|
|
require.Len(t, logsData, 0)
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
func TestFeeManager(t *testing.T) {
|
|
allowlist.RunPrecompileWithAllowListTests(t, Module, testhelpers.NewTestStateDB, tests)
|
|
}
|
|
|
|
func BenchmarkFeeManager(b *testing.B) {
|
|
allowlist.BenchPrecompileWithAllowList(b, Module, testhelpers.NewTestStateDB, tests)
|
|
}
|
|
|
|
func assertFeeEvent(
|
|
t testing.TB,
|
|
logsTopics [][]common.Hash,
|
|
logsData [][]byte,
|
|
sender common.Address,
|
|
expectedOldFeeConfig commontype.FeeConfig,
|
|
expectedNewFeeConfig commontype.FeeConfig,
|
|
) {
|
|
require.Len(t, logsTopics, 1)
|
|
require.Len(t, logsData, 1)
|
|
|
|
topics := logsTopics[0]
|
|
require.Len(t, topics, 2)
|
|
require.Equal(t, FeeManagerABI.Events["FeeConfigChanged"].ID, topics[0])
|
|
require.Equal(t, common.BytesToHash(sender[:]), topics[1])
|
|
|
|
logData := logsData[0]
|
|
oldFeeConfig, resFeeConfig, err := UnpackFeeConfigChangedEventData(logData)
|
|
require.NoError(t, err)
|
|
require.True(t, expectedOldFeeConfig.Equal(&oldFeeConfig), "expected %v, got %v", expectedOldFeeConfig, oldFeeConfig)
|
|
require.True(t, expectedNewFeeConfig.Equal(&resFeeConfig), "expected %v, got %v", expectedNewFeeConfig, resFeeConfig)
|
|
}
|