Files

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)
}