diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml new file mode 100644 index 0000000000..a2c5614b50 --- /dev/null +++ b/.github/workflows/bench.yml @@ -0,0 +1,19 @@ +name: Bench + +on: + workflow_dispatch: + pull_request: + +jobs: + bench: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: "1.19" + - run: go mod download + shell: bash + - run: ./scripts/build_bench_precompiles.sh + shell: bash + diff --git a/core/state/test_statedb.go b/core/state/test_statedb.go index 6dc1aa1065..9aa5bb092e 100644 --- a/core/state/test_statedb.go +++ b/core/state/test_statedb.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/require" ) -func NewTestStateDB(t *testing.T) contract.StateDB { +func NewTestStateDB(t testing.TB) contract.StateDB { db := memorydb.New() stateDB, err := New(common.Hash{}, NewDatabase(db), nil) require.NoError(t, err) diff --git a/precompile/allowlist/allowlist_test.go b/precompile/allowlist/allowlist_test.go index 4f0eeb3b51..5ae172a06a 100644 --- a/precompile/allowlist/allowlist_test.go +++ b/precompile/allowlist/allowlist_test.go @@ -60,3 +60,12 @@ func TestAllowListRun(t *testing.T) { } RunPrecompileWithAllowListTests(t, dummyModule, state.NewTestStateDB, nil) } + +func BenchmarkAllowList(b *testing.B) { + dummyModule := modules.Module{ + Address: dummyAddr, + Contract: CreateAllowListPrecompile(dummyAddr), + Configurator: &dummyConfigurator{}, + } + BenchPrecompileWithAllowList(b, dummyModule, state.NewTestStateDB, nil) +} diff --git a/precompile/allowlist/test_allowlist.go b/precompile/allowlist/test_allowlist.go index 233ade3f65..8012d2e95c 100644 --- a/precompile/allowlist/test_allowlist.go +++ b/precompile/allowlist/test_allowlist.go @@ -26,7 +26,7 @@ func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { "set admin": { Caller: TestAdminAddr, BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackModifyAllowList(TestNoRoleAddr, AdminRole) require.NoError(t, err) @@ -35,7 +35,7 @@ func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { SuppliedGas: ModifyAllowListGasCost, ReadOnly: false, ExpectedRes: []byte{}, - AfterHook: func(t *testing.T, state contract.StateDB) { + AfterHook: func(t testing.TB, state contract.StateDB) { res := GetAllowListStatus(state, contractAddress, TestNoRoleAddr) require.Equal(t, AdminRole, res) }, @@ -43,7 +43,7 @@ func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { "set enabled": { Caller: TestAdminAddr, BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackModifyAllowList(TestNoRoleAddr, EnabledRole) require.NoError(t, err) @@ -52,7 +52,7 @@ func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { SuppliedGas: ModifyAllowListGasCost, ReadOnly: false, ExpectedRes: []byte{}, - AfterHook: func(t *testing.T, state contract.StateDB) { + AfterHook: func(t testing.TB, state contract.StateDB) { res := GetAllowListStatus(state, contractAddress, TestNoRoleAddr) require.Equal(t, EnabledRole, res) }, @@ -60,7 +60,7 @@ func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { "set no role": { Caller: TestAdminAddr, BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackModifyAllowList(TestEnabledAddr, NoRole) require.NoError(t, err) @@ -69,7 +69,7 @@ func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { SuppliedGas: ModifyAllowListGasCost, ReadOnly: false, ExpectedRes: []byte{}, - AfterHook: func(t *testing.T, state contract.StateDB) { + AfterHook: func(t testing.TB, state contract.StateDB) { res := GetAllowListStatus(state, contractAddress, TestEnabledAddr) require.Equal(t, NoRole, res) }, @@ -77,7 +77,7 @@ func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { "set no role from no role": { Caller: TestNoRoleAddr, BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackModifyAllowList(TestEnabledAddr, NoRole) require.NoError(t, err) @@ -90,7 +90,7 @@ func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { "set enabled from no role": { Caller: TestNoRoleAddr, BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackModifyAllowList(TestNoRoleAddr, EnabledRole) require.NoError(t, err) @@ -103,7 +103,7 @@ func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { "set admin from no role": { Caller: TestNoRoleAddr, BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackModifyAllowList(TestEnabledAddr, AdminRole) require.NoError(t, err) @@ -116,7 +116,7 @@ func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { "set no role from enabled": { Caller: TestEnabledAddr, BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackModifyAllowList(TestAdminAddr, NoRole) require.NoError(t, err) @@ -129,7 +129,7 @@ func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { "set enabled from enabled": { Caller: TestEnabledAddr, BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackModifyAllowList(TestNoRoleAddr, EnabledRole) require.NoError(t, err) @@ -142,7 +142,7 @@ func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { "set admin from enabled": { Caller: TestEnabledAddr, BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackModifyAllowList(TestNoRoleAddr, AdminRole) require.NoError(t, err) @@ -155,7 +155,7 @@ func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { "set no role with readOnly enabled": { Caller: TestAdminAddr, BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackModifyAllowList(TestEnabledAddr, NoRole) require.NoError(t, err) @@ -168,7 +168,7 @@ func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { "set no role insufficient gas": { Caller: TestAdminAddr, BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackModifyAllowList(TestEnabledAddr, NoRole) require.NoError(t, err) @@ -219,7 +219,7 @@ func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { ), SuppliedGas: 0, ReadOnly: false, - AfterHook: func(t *testing.T, state contract.StateDB) { + AfterHook: func(t testing.TB, state contract.StateDB) { require.Equal(t, AdminRole, GetAllowListStatus(state, contractAddress, TestNoRoleAddr)) require.Equal(t, AdminRole, GetAllowListStatus(state, contractAddress, TestEnabledAddr)) }, @@ -233,7 +233,7 @@ func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { ), SuppliedGas: 0, ReadOnly: false, - AfterHook: func(t *testing.T, state contract.StateDB) { + AfterHook: func(t testing.TB, state contract.StateDB) { require.Equal(t, EnabledRole, GetAllowListStatus(state, contractAddress, TestAdminAddr)) require.Equal(t, EnabledRole, GetAllowListStatus(state, contractAddress, TestNoRoleAddr)) }, @@ -243,8 +243,8 @@ func AllowListTests(module modules.Module) map[string]testutils.PrecompileTest { // SetDefaultRoles returns a BeforeHook that sets roles TestAdminAddr and TestEnabledAddr // to have the AdminRole and EnabledRole respectively. -func SetDefaultRoles(contractAddress common.Address) func(t *testing.T, state contract.StateDB) { - return func(t *testing.T, state contract.StateDB) { +func SetDefaultRoles(contractAddress common.Address) func(t testing.TB, state contract.StateDB) { + return func(t testing.TB, state contract.StateDB) { SetAllowListRole(state, contractAddress, TestAdminAddr, AdminRole) SetAllowListRole(state, contractAddress, TestEnabledAddr, EnabledRole) require.Equal(t, AdminRole, GetAllowListStatus(state, contractAddress, TestAdminAddr)) @@ -253,7 +253,7 @@ func SetDefaultRoles(contractAddress common.Address) func(t *testing.T, state co } } -func RunPrecompileWithAllowListTests(t *testing.T, module modules.Module, newStateDB func(t *testing.T) contract.StateDB, contractTests map[string]testutils.PrecompileTest) { +func RunPrecompileWithAllowListTests(t *testing.T, module modules.Module, newStateDB func(t testing.TB) contract.StateDB, contractTests map[string]testutils.PrecompileTest) { t.Helper() tests := AllowListTests(module) // Add the contract specific tests to the map of tests to run. @@ -270,3 +270,22 @@ func RunPrecompileWithAllowListTests(t *testing.T, module modules.Module, newSta }) } } + +func BenchPrecompileWithAllowList(b *testing.B, module modules.Module, newStateDB func(t testing.TB) contract.StateDB, contractTests map[string]testutils.PrecompileTest) { + b.Helper() + + tests := AllowListTests(module) + // Add the contract specific tests to the map of tests to run. + for name, test := range contractTests { + if _, exists := tests[name]; exists { + b.Fatalf("duplicate bench name: %s", name) + } + tests[name] = test + } + + for name, test := range tests { + b.Run(name, func(b *testing.B) { + test.Bench(b, module, newStateDB(b)) + }) + } +} diff --git a/precompile/contract/interfaces.go b/precompile/contract/interfaces.go index e36df7aefa..0432c939b6 100644 --- a/precompile/contract/interfaces.go +++ b/precompile/contract/interfaces.go @@ -48,6 +48,9 @@ type StateDB interface { Suicide(common.Address) bool Finalise(deleteEmptyObjects bool) + + Snapshot() int + RevertToSnapshot(int) } // AccessibleState defines the interface exposed to stateful precompile contracts diff --git a/precompile/contracts/deployerallowlist/contract_test.go b/precompile/contracts/deployerallowlist/contract_test.go index ba144fd155..d5037444a5 100644 --- a/precompile/contracts/deployerallowlist/contract_test.go +++ b/precompile/contracts/deployerallowlist/contract_test.go @@ -13,3 +13,7 @@ import ( func TestContractDeployerAllowListRun(t *testing.T) { allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, nil) } + +func BenchmarkContractDeployerAllowList(b *testing.B) { + allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, nil) +} diff --git a/precompile/contracts/feemanager/contract_test.go b/precompile/contracts/feemanager/contract_test.go index 4911a2368e..6daa21935d 100644 --- a/precompile/contracts/feemanager/contract_test.go +++ b/precompile/contracts/feemanager/contract_test.go @@ -17,26 +17,25 @@ import ( "github.com/stretchr/testify/require" ) -var 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), -} - -func TestFeeManager(t *testing.T) { - testBlockNumber := big.NewInt(7) - tests := map[string]testutils.PrecompileTest{ +var ( + 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), + } + 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.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackSetFeeConfig(testFeeConfig) require.NoError(t, err) @@ -49,7 +48,7 @@ func TestFeeManager(t *testing.T) { "set config from enabled address": { Caller: allowlist.TestEnabledAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackSetFeeConfig(testFeeConfig) require.NoError(t, err) @@ -58,7 +57,7 @@ func TestFeeManager(t *testing.T) { SuppliedGas: SetFeeConfigGasCost, ReadOnly: false, ExpectedRes: []byte{}, - AfterHook: func(t *testing.T, state contract.StateDB) { + AfterHook: func(t testing.TB, state contract.StateDB) { feeConfig := GetStoredFeeConfig(state) require.Equal(t, testFeeConfig, feeConfig) }, @@ -66,7 +65,7 @@ func TestFeeManager(t *testing.T) { "set invalid config from enabled address": { Caller: allowlist.TestEnabledAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { feeConfig := testFeeConfig feeConfig.MinBlockGasCost = new(big.Int).Mul(feeConfig.MaxBlockGasCost, common.Big2) input, err := PackSetFeeConfig(feeConfig) @@ -80,7 +79,7 @@ func TestFeeManager(t *testing.T) { InitialFeeConfig: &testFeeConfig, }, ExpectedErr: "cannot be greater than maxBlockGasCost", - AfterHook: func(t *testing.T, state contract.StateDB) { + AfterHook: func(t testing.TB, state contract.StateDB) { feeConfig := GetStoredFeeConfig(state) require.Equal(t, testFeeConfig, feeConfig) }, @@ -88,7 +87,7 @@ func TestFeeManager(t *testing.T) { "set config from admin address": { Caller: allowlist.TestAdminAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackSetFeeConfig(testFeeConfig) require.NoError(t, err) @@ -98,7 +97,7 @@ func TestFeeManager(t *testing.T) { ReadOnly: false, ExpectedRes: []byte{}, BlockNumber: testBlockNumber.Int64(), - AfterHook: func(t *testing.T, state contract.StateDB) { + AfterHook: func(t testing.TB, state contract.StateDB) { feeConfig := GetStoredFeeConfig(state) require.Equal(t, testFeeConfig, feeConfig) lastChangedAt := GetFeeConfigLastChangedAt(state) @@ -107,7 +106,7 @@ func TestFeeManager(t *testing.T) { }, "get fee config from non-enabled address": { Caller: allowlist.TestNoRoleAddr, - BeforeHook: func(t *testing.T, state contract.StateDB) { + BeforeHook: func(t testing.TB, state contract.StateDB) { allowlist.SetDefaultRoles(Module.Address)(t, state) err := StoreFeeConfig(state, testFeeConfig, contract.NewMockBlockContext(big.NewInt(6), 0)) require.NoError(t, err) @@ -117,10 +116,12 @@ func TestFeeManager(t *testing.T) { ReadOnly: true, ExpectedRes: func() []byte { res, err := PackFeeConfig(testFeeConfig) - require.NoError(t, err) + if err != nil { + panic(err) + } return res }(), - AfterHook: func(t *testing.T, state contract.StateDB) { + AfterHook: func(t testing.TB, state contract.StateDB) { feeConfig := GetStoredFeeConfig(state) lastChangedAt := GetFeeConfigLastChangedAt(state) require.Equal(t, testFeeConfig, feeConfig) @@ -138,11 +139,13 @@ func TestFeeManager(t *testing.T) { ReadOnly: true, ExpectedRes: func() []byte { res, err := PackFeeConfig(testFeeConfig) - require.NoError(t, err) + if err != nil { + panic(err) + } return res }(), BlockNumber: testBlockNumber.Int64(), - AfterHook: func(t *testing.T, state contract.StateDB) { + AfterHook: func(t testing.TB, state contract.StateDB) { feeConfig := GetStoredFeeConfig(state) lastChangedAt := GetFeeConfigLastChangedAt(state) require.Equal(t, testFeeConfig, feeConfig) @@ -151,7 +154,7 @@ func TestFeeManager(t *testing.T) { }, "get last changed at from non-enabled address": { Caller: allowlist.TestNoRoleAddr, - BeforeHook: func(t *testing.T, state contract.StateDB) { + BeforeHook: func(t testing.TB, state contract.StateDB) { allowlist.SetDefaultRoles(Module.Address)(t, state) err := StoreFeeConfig(state, testFeeConfig, contract.NewMockBlockContext(testBlockNumber, 0)) require.NoError(t, err) @@ -160,7 +163,7 @@ func TestFeeManager(t *testing.T) { SuppliedGas: GetLastChangedAtGasCost, ReadOnly: true, ExpectedRes: common.BigToHash(testBlockNumber).Bytes(), - AfterHook: func(t *testing.T, state contract.StateDB) { + AfterHook: func(t testing.TB, state contract.StateDB) { feeConfig := GetStoredFeeConfig(state) lastChangedAt := GetFeeConfigLastChangedAt(state) require.Equal(t, testFeeConfig, feeConfig) @@ -170,7 +173,7 @@ func TestFeeManager(t *testing.T) { "readOnly setFeeConfig with noRole fails": { Caller: allowlist.TestNoRoleAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackSetFeeConfig(testFeeConfig) require.NoError(t, err) @@ -183,7 +186,7 @@ func TestFeeManager(t *testing.T) { "readOnly setFeeConfig with allow role fails": { Caller: allowlist.TestEnabledAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackSetFeeConfig(testFeeConfig) require.NoError(t, err) @@ -196,7 +199,7 @@ func TestFeeManager(t *testing.T) { "readOnly setFeeConfig with admin role fails": { Caller: allowlist.TestAdminAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackSetFeeConfig(testFeeConfig) require.NoError(t, err) @@ -209,7 +212,7 @@ func TestFeeManager(t *testing.T) { "insufficient gas setFeeConfig from admin": { Caller: allowlist.TestAdminAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackSetFeeConfig(testFeeConfig) require.NoError(t, err) @@ -220,6 +223,12 @@ func TestFeeManager(t *testing.T) { ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, } +) +func TestFeeManager(t *testing.T) { allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests) } + +func BenchmarkFeeManager(b *testing.B) { + allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) +} diff --git a/precompile/contracts/nativeminter/contract_test.go b/precompile/contracts/nativeminter/contract_test.go index a987dc9998..a45f26dc2c 100644 --- a/precompile/contracts/nativeminter/contract_test.go +++ b/precompile/contracts/nativeminter/contract_test.go @@ -16,134 +16,138 @@ import ( "github.com/stretchr/testify/require" ) -func TestContractNativeMinterRun(t *testing.T) { - tests := map[string]testutils.PrecompileTest{ - "mint funds from no role fails": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { - input, err := PackMintInput(allowlist.TestNoRoleAddr, common.Big1) - require.NoError(t, err) +var tests = map[string]testutils.PrecompileTest{ + "mint funds from no role fails": { + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackMintInput(allowlist.TestNoRoleAddr, common.Big1) + require.NoError(t, err) - return input - }, - SuppliedGas: MintGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotMint.Error(), + return input }, - "mint funds from enabled address": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { - input, err := PackMintInput(allowlist.TestEnabledAddr, common.Big1) - require.NoError(t, err) + SuppliedGas: MintGasCost, + ReadOnly: false, + ExpectedErr: ErrCannotMint.Error(), + }, + "mint funds from enabled address": { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackMintInput(allowlist.TestEnabledAddr, common.Big1) + require.NoError(t, err) - return input - }, - SuppliedGas: MintGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t *testing.T, state contract.StateDB) { - require.Equal(t, common.Big1, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") - }, + return input }, - "initial mint funds": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - Config: &Config{ - InitialMint: map[common.Address]*math.HexOrDecimal256{ - allowlist.TestEnabledAddr: math.NewHexOrDecimal256(2), - }, - }, - AfterHook: func(t *testing.T, state contract.StateDB) { - require.Equal(t, common.Big2, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") + SuppliedGas: MintGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t testing.TB, state contract.StateDB) { + require.Equal(t, common.Big1, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") + }, + }, + "initial mint funds": { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + Config: &Config{ + InitialMint: map[common.Address]*math.HexOrDecimal256{ + allowlist.TestEnabledAddr: math.NewHexOrDecimal256(2), }, }, - "mint funds from admin address": { - Caller: allowlist.TestAdminAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { - input, err := PackMintInput(allowlist.TestAdminAddr, common.Big1) - require.NoError(t, err) + AfterHook: func(t testing.TB, state contract.StateDB) { + require.Equal(t, common.Big2, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") + }, + }, + "mint funds from admin address": { + Caller: allowlist.TestAdminAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackMintInput(allowlist.TestAdminAddr, common.Big1) + require.NoError(t, err) - return input - }, - SuppliedGas: MintGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t *testing.T, state contract.StateDB) { - require.Equal(t, common.Big1, state.GetBalance(allowlist.TestAdminAddr), "expected minted funds") - }, + return input }, - "mint max big funds": { - Caller: allowlist.TestAdminAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { - input, err := PackMintInput(allowlist.TestAdminAddr, math.MaxBig256) - require.NoError(t, err) + SuppliedGas: MintGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t testing.TB, state contract.StateDB) { + require.Equal(t, common.Big1, state.GetBalance(allowlist.TestAdminAddr), "expected minted funds") + }, + }, + "mint max big funds": { + Caller: allowlist.TestAdminAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackMintInput(allowlist.TestAdminAddr, math.MaxBig256) + require.NoError(t, err) - return input - }, - SuppliedGas: MintGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t *testing.T, state contract.StateDB) { - require.Equal(t, math.MaxBig256, state.GetBalance(allowlist.TestAdminAddr), "expected minted funds") - }, + return input + }, + SuppliedGas: MintGasCost, + ReadOnly: false, + ExpectedRes: []byte{}, + AfterHook: func(t testing.TB, state contract.StateDB) { + require.Equal(t, math.MaxBig256, state.GetBalance(allowlist.TestAdminAddr), "expected minted funds") }, - "readOnly mint with noRole fails": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { - input, err := PackMintInput(allowlist.TestAdminAddr, common.Big1) - require.NoError(t, err) + }, + "readOnly mint with noRole fails": { + Caller: allowlist.TestNoRoleAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackMintInput(allowlist.TestAdminAddr, common.Big1) + require.NoError(t, err) - return input - }, - SuppliedGas: MintGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), + return input }, - "readOnly mint with allow role fails": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { - input, err := PackMintInput(allowlist.TestEnabledAddr, common.Big1) - require.NoError(t, err) + SuppliedGas: MintGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "readOnly mint with allow role fails": { + Caller: allowlist.TestEnabledAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackMintInput(allowlist.TestEnabledAddr, common.Big1) + require.NoError(t, err) - return input - }, - SuppliedGas: MintGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), + return input }, - "readOnly mint with admin role fails": { - Caller: allowlist.TestAdminAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { - input, err := PackMintInput(allowlist.TestAdminAddr, common.Big1) - require.NoError(t, err) + SuppliedGas: MintGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "readOnly mint with admin role fails": { + Caller: allowlist.TestAdminAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackMintInput(allowlist.TestAdminAddr, common.Big1) + require.NoError(t, err) - return input - }, - SuppliedGas: MintGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), + return input }, - "insufficient gas mint from admin": { - Caller: allowlist.TestAdminAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { - input, err := PackMintInput(allowlist.TestEnabledAddr, common.Big1) - require.NoError(t, err) + SuppliedGas: MintGasCost, + ReadOnly: true, + ExpectedErr: vmerrs.ErrWriteProtection.Error(), + }, + "insufficient gas mint from admin": { + Caller: allowlist.TestAdminAddr, + BeforeHook: allowlist.SetDefaultRoles(Module.Address), + InputFn: func(t testing.TB) []byte { + input, err := PackMintInput(allowlist.TestEnabledAddr, common.Big1) + require.NoError(t, err) - return input - }, - SuppliedGas: MintGasCost - 1, - ReadOnly: false, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), + return input }, - } + SuppliedGas: MintGasCost - 1, + ReadOnly: false, + ExpectedErr: vmerrs.ErrOutOfGas.Error(), + }, +} +func TestContractNativeMinterRun(t *testing.T) { allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests) } + +func BenchmarkContractNativeMinter(b *testing.B) { + allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) +} diff --git a/precompile/contracts/rewardmanager/contract_test.go b/precompile/contracts/rewardmanager/contract_test.go index db1d61d3ae..034856c0c9 100644 --- a/precompile/contracts/rewardmanager/contract_test.go +++ b/precompile/contracts/rewardmanager/contract_test.go @@ -16,14 +16,13 @@ import ( "github.com/stretchr/testify/require" ) -func TestRewardManagerRun(t *testing.T) { - testAddr := common.HexToAddress("0x0123") - - tests := map[string]testutils.PrecompileTest{ +var ( + testAddr = common.HexToAddress("0x0123") + tests = map[string]testutils.PrecompileTest{ "set allow fee recipients from no role fails": { Caller: allowlist.TestNoRoleAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackAllowFeeRecipients() require.NoError(t, err) @@ -36,7 +35,7 @@ func TestRewardManagerRun(t *testing.T) { "set reward address from no role fails": { Caller: allowlist.TestNoRoleAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackSetRewardAddress(testAddr) require.NoError(t, err) @@ -49,7 +48,7 @@ func TestRewardManagerRun(t *testing.T) { "disable rewards from no role fails": { Caller: allowlist.TestNoRoleAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackDisableRewards() require.NoError(t, err) @@ -62,7 +61,7 @@ func TestRewardManagerRun(t *testing.T) { "set allow fee recipients from enabled succeeds": { Caller: allowlist.TestEnabledAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackAllowFeeRecipients() require.NoError(t, err) @@ -71,7 +70,7 @@ func TestRewardManagerRun(t *testing.T) { SuppliedGas: AllowFeeRecipientsGasCost, ReadOnly: false, ExpectedRes: []byte{}, - AfterHook: func(t *testing.T, state contract.StateDB) { + AfterHook: func(t testing.TB, state contract.StateDB) { _, isFeeRecipients := GetStoredRewardAddress(state) require.True(t, isFeeRecipients) }, @@ -79,7 +78,7 @@ func TestRewardManagerRun(t *testing.T) { "set reward address from enabled succeeds": { Caller: allowlist.TestEnabledAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackSetRewardAddress(testAddr) require.NoError(t, err) @@ -88,7 +87,7 @@ func TestRewardManagerRun(t *testing.T) { SuppliedGas: SetRewardAddressGasCost, ReadOnly: false, ExpectedRes: []byte{}, - AfterHook: func(t *testing.T, state contract.StateDB) { + AfterHook: func(t testing.TB, state contract.StateDB) { address, isFeeRecipients := GetStoredRewardAddress(state) require.Equal(t, testAddr, address) require.False(t, isFeeRecipients) @@ -97,7 +96,7 @@ func TestRewardManagerRun(t *testing.T) { "disable rewards from enabled succeeds": { Caller: allowlist.TestEnabledAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackDisableRewards() require.NoError(t, err) @@ -106,7 +105,7 @@ func TestRewardManagerRun(t *testing.T) { SuppliedGas: DisableRewardsGasCost, ReadOnly: false, ExpectedRes: []byte{}, - AfterHook: func(t *testing.T, state contract.StateDB) { + AfterHook: func(t testing.TB, state contract.StateDB) { address, isFeeRecipients := GetStoredRewardAddress(state) require.False(t, isFeeRecipients) require.Equal(t, constants.BlackholeAddr, address) @@ -114,11 +113,11 @@ func TestRewardManagerRun(t *testing.T) { }, "get current reward address from no role succeeds": { Caller: allowlist.TestNoRoleAddr, - BeforeHook: func(t *testing.T, state contract.StateDB) { + BeforeHook: func(t testing.TB, state contract.StateDB) { allowlist.SetDefaultRoles(Module.Address)(t, state) StoreRewardAddress(state, testAddr) }, - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackCurrentRewardAddress() require.NoError(t, err) @@ -128,17 +127,19 @@ func TestRewardManagerRun(t *testing.T) { ReadOnly: false, ExpectedRes: func() []byte { res, err := PackCurrentRewardAddressOutput(testAddr) - require.NoError(t, err) + if err != nil { + panic(err) + } return res }(), }, "get are fee recipients allowed from no role succeeds": { Caller: allowlist.TestNoRoleAddr, - BeforeHook: func(t *testing.T, state contract.StateDB) { + BeforeHook: func(t testing.TB, state contract.StateDB) { allowlist.SetDefaultRoles(Module.Address)(t, state) EnableAllowFeeRecipients(state) }, - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackAreFeeRecipientsAllowed() require.NoError(t, err) return input @@ -147,14 +148,16 @@ func TestRewardManagerRun(t *testing.T) { ReadOnly: false, ExpectedRes: func() []byte { res, err := PackAreFeeRecipientsAllowedOutput(true) - require.NoError(t, err) + if err != nil { + panic(err) + } return res }(), }, "get initial config with address": { Caller: allowlist.TestNoRoleAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackCurrentRewardAddress() require.NoError(t, err) return input @@ -168,14 +171,16 @@ func TestRewardManagerRun(t *testing.T) { ReadOnly: false, ExpectedRes: func() []byte { res, err := PackCurrentRewardAddressOutput(testAddr) - require.NoError(t, err) + if err != nil { + panic(err) + } return res }(), }, "get initial config with allow fee recipients enabled": { Caller: allowlist.TestNoRoleAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackAreFeeRecipientsAllowed() require.NoError(t, err) return input @@ -189,14 +194,16 @@ func TestRewardManagerRun(t *testing.T) { ReadOnly: false, ExpectedRes: func() []byte { res, err := PackAreFeeRecipientsAllowedOutput(true) - require.NoError(t, err) + if err != nil { + panic(err) + } return res }(), }, "readOnly allow fee recipients with allowed role fails": { Caller: allowlist.TestEnabledAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackAllowFeeRecipients() require.NoError(t, err) @@ -209,7 +216,7 @@ func TestRewardManagerRun(t *testing.T) { "readOnly set reward addresss with allowed role fails": { Caller: allowlist.TestEnabledAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackSetRewardAddress(testAddr) require.NoError(t, err) @@ -222,7 +229,7 @@ func TestRewardManagerRun(t *testing.T) { "insufficient gas set reward address from allowed role": { Caller: allowlist.TestEnabledAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackSetRewardAddress(testAddr) require.NoError(t, err) @@ -235,7 +242,7 @@ func TestRewardManagerRun(t *testing.T) { "insufficient gas allow fee recipients from allowed role": { Caller: allowlist.TestEnabledAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackAllowFeeRecipients() require.NoError(t, err) @@ -248,7 +255,7 @@ func TestRewardManagerRun(t *testing.T) { "insufficient gas read current reward address from allowed role": { Caller: allowlist.TestEnabledAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackCurrentRewardAddress() require.NoError(t, err) @@ -261,7 +268,7 @@ func TestRewardManagerRun(t *testing.T) { "insufficient gas are fee recipients allowed from allowed role": { Caller: allowlist.TestEnabledAddr, BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t *testing.T) []byte { + InputFn: func(t testing.TB) []byte { input, err := PackAreFeeRecipientsAllowed() require.NoError(t, err) @@ -272,6 +279,12 @@ func TestRewardManagerRun(t *testing.T) { ExpectedErr: vmerrs.ErrOutOfGas.Error(), }, } +) +func TestRewardManagerRun(t *testing.T) { allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests) } + +func BenchmarkRewardManager(b *testing.B) { + allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) +} diff --git a/precompile/contracts/txallowlist/contract_test.go b/precompile/contracts/txallowlist/contract_test.go index 08104024c5..119fec3817 100644 --- a/precompile/contracts/txallowlist/contract_test.go +++ b/precompile/contracts/txallowlist/contract_test.go @@ -13,3 +13,7 @@ import ( func TestTxAllowListRun(t *testing.T) { allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, nil) } + +func BenchmarkTxAllowList(b *testing.B) { + allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, nil) +} diff --git a/precompile/testutils/test_precompile.go b/precompile/testutils/test_precompile.go index 6e7fe23c1c..eb06ec3325 100644 --- a/precompile/testutils/test_precompile.go +++ b/precompile/testutils/test_precompile.go @@ -6,6 +6,7 @@ package testutils import ( "math/big" "testing" + "time" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/subnet-evm/commontype" @@ -24,7 +25,7 @@ type PrecompileTest struct { Input []byte // InputFn is a function that returns the raw input bytes to the precompile // If specified, Input will be ignored. - InputFn func(t *testing.T) []byte + InputFn func(t testing.TB) []byte // SuppliedGas is the amount of gas supplied to the precompile SuppliedGas uint64 // ReadOnly is whether the precompile should be called in read only @@ -36,9 +37,9 @@ type PrecompileTest struct { // If nil, Configure will not be called. Config precompileconfig.Config // BeforeHook is called before the precompile is called. - BeforeHook func(t *testing.T, state contract.StateDB) + BeforeHook func(t testing.TB, state contract.StateDB) // AfterHook is called after the precompile is called. - AfterHook func(t *testing.T, state contract.StateDB) + AfterHook func(t testing.TB, state contract.StateDB) // ExpectedRes is the expected raw byte result returned by the precompile ExpectedRes []byte // ExpectedErr is the expected error returned by the precompile @@ -47,7 +48,35 @@ type PrecompileTest struct { BlockNumber int64 } +type PrecompileRunparams struct { + AccessibleState contract.AccessibleState + Caller common.Address + ContractAddress common.Address + Input []byte + SuppliedGas uint64 + ReadOnly bool +} + func (test PrecompileTest) Run(t *testing.T, module modules.Module, state contract.StateDB) { + runParams := test.setup(t, module, state) + + if runParams.Input != nil { + ret, remainingGas, err := module.Contract.Run(runParams.AccessibleState, runParams.Caller, runParams.ContractAddress, runParams.Input, runParams.SuppliedGas, runParams.ReadOnly) + if len(test.ExpectedErr) != 0 { + require.ErrorContains(t, err, test.ExpectedErr) + } else { + require.NoError(t, err) + } + require.Equal(t, uint64(0), remainingGas) + require.Equal(t, test.ExpectedRes, ret) + } + + if test.AfterHook != nil { + test.AfterHook(t, state) + } +} + +func (test PrecompileTest) setup(t testing.TB, module modules.Module, state contract.StateDB) PrecompileRunparams { t.Helper() contractAddress := module.Address @@ -69,18 +98,76 @@ func (test PrecompileTest) Run(t *testing.T, module modules.Module, state contra input = test.InputFn(t) } - if input != nil { - ret, remainingGas, err := module.Contract.Run(accesibleState, test.Caller, contractAddress, input, test.SuppliedGas, test.ReadOnly) - if len(test.ExpectedErr) != 0 { - require.ErrorContains(t, err, test.ExpectedErr) - } else { - require.NoError(t, err) - } - require.Equal(t, uint64(0), remainingGas) - require.Equal(t, test.ExpectedRes, ret) + return PrecompileRunparams{ + AccessibleState: accesibleState, + Caller: test.Caller, + ContractAddress: contractAddress, + Input: input, + SuppliedGas: test.SuppliedGas, + ReadOnly: test.ReadOnly, + } +} + +func (test PrecompileTest) Bench(b *testing.B, module modules.Module, state contract.StateDB) { + runParams := test.setup(b, module, state) + + if runParams.Input == nil { + b.Skip("Skipping precompile benchmark due to nil input (used for configuration tests)") } + stateDB := runParams.AccessibleState.GetStateDB() + snapshot := stateDB.Snapshot() + + ret, remainingGas, err := module.Contract.Run(runParams.AccessibleState, runParams.Caller, runParams.ContractAddress, runParams.Input, runParams.SuppliedGas, runParams.ReadOnly) + if len(test.ExpectedErr) != 0 { + require.ErrorContains(b, err, test.ExpectedErr) + } else { + require.NoError(b, err) + } + require.Equal(b, uint64(0), remainingGas) + require.Equal(b, test.ExpectedRes, ret) + if test.AfterHook != nil { - test.AfterHook(t, state) + test.AfterHook(b, state) + } + + b.ReportAllocs() + start := time.Now() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + // Revert to the previous snapshot and take a new snapshot, so we can reset the state after execution + stateDB.RevertToSnapshot(snapshot) + snapshot = stateDB.Snapshot() + + // Ignore return values for benchmark + _, _, _ = module.Contract.Run(runParams.AccessibleState, runParams.Caller, runParams.ContractAddress, runParams.Input, runParams.SuppliedGas, runParams.ReadOnly) + } + b.StopTimer() + + elapsed := uint64(time.Since(start)) + if elapsed < 1 { + elapsed = 1 + } + gasUsed := runParams.SuppliedGas * uint64(b.N) + b.ReportMetric(float64(runParams.SuppliedGas), "gas/op") + // Keep it as uint64, multiply 100 to get two digit float later + mgasps := (100 * 1000 * gasUsed) / elapsed + b.ReportMetric(float64(mgasps)/100, "mgas/s") + + // Execute the test one final time to ensure that if our RevertToSnapshot logic breaks such that each run is actually failing or resulting in unexpected behavior + // the benchmark should catch the error here. + stateDB.RevertToSnapshot(snapshot) + ret, remainingGas, err = module.Contract.Run(runParams.AccessibleState, runParams.Caller, runParams.ContractAddress, runParams.Input, runParams.SuppliedGas, runParams.ReadOnly) + if len(test.ExpectedErr) != 0 { + require.ErrorContains(b, err, test.ExpectedErr) + } else { + require.NoError(b, err) + } + require.Equal(b, uint64(0), remainingGas) + require.Equal(b, test.ExpectedRes, ret) + + if test.AfterHook != nil { + test.AfterHook(b, state) } } diff --git a/scripts/build_bench_precompiles.sh b/scripts/build_bench_precompiles.sh new file mode 100755 index 0000000000..5cd0be44b0 --- /dev/null +++ b/scripts/build_bench_precompiles.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +# Root directory +SUBNET_EVM_PATH=$( + cd "$(dirname "${BASH_SOURCE[0]}")" + cd .. && pwd +) + +# Load the versions +source "$SUBNET_EVM_PATH"/scripts/versions.sh + +# Load the constants +source "$SUBNET_EVM_PATH"/scripts/constants.sh + +go test ./precompile/contracts/... -bench=./... -timeout="10m" $@