Files
evm/core/predicate_check_test.go

462 lines
14 KiB
Go

// (c) 2023, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package core
import (
"errors"
"testing"
"github.com/luxfi/evm/commontype"
"github.com/luxfi/evm/core/types"
"github.com/luxfi/evm/params"
"github.com/luxfi/evm/precompile/precompileconfig"
"github.com/luxfi/geth/common"
"github.com/luxfi/node/v2/utils/set"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
)
type predicateCheckTest struct {
accessList types.AccessList
gas uint64
predicateContext *precompileconfig.PredicateContext
createPredicates func(t testing.TB) map[common.Address]precompileconfig.Predicater
expectedRes map[common.Address][]byte
expectedErr error
}
func TestCheckPredicate(t *testing.T) {
testErr := errors.New("test error")
addr1 := common.HexToAddress("0xaa")
addr2 := common.HexToAddress("0xbb")
addr3 := common.HexToAddress("0xcc")
addr4 := common.HexToAddress("0xdd")
predicateContext := &precompileconfig.PredicateContext{
ProposerVMBlockCtx: &commontype.BlockContext{
PChainHeight: 10,
},
}
for name, test := range map[string]predicateCheckTest{
"no predicates, no access list, no context passes": {
gas: 53000,
predicateContext: nil,
expectedRes: make(map[common.Address][]byte),
expectedErr: nil,
},
"no predicates, no access list, with context passes": {
gas: 53000,
predicateContext: predicateContext,
expectedRes: make(map[common.Address][]byte),
expectedErr: nil,
},
"no predicates, with access list, no context passes": {
gas: 57300,
predicateContext: nil,
accessList: types.AccessList([]types.AccessTuple{
{
Address: addr1,
StorageKeys: []common.Hash{
{1},
},
},
}),
expectedRes: make(map[common.Address][]byte),
expectedErr: nil,
},
"predicate, no access list, no context passes": {
gas: 53000,
predicateContext: nil,
createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater {
predicater := precompileconfig.NewMockPredicater(gomock.NewController(t))
return map[common.Address]precompileconfig.Predicater{
addr1: predicater,
}
},
expectedRes: make(map[common.Address][]byte),
expectedErr: nil,
},
"predicate, no access list, no block context passes": {
gas: 53000,
predicateContext: &precompileconfig.PredicateContext{
ProposerVMBlockCtx: nil,
},
createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater {
predicater := precompileconfig.NewMockPredicater(gomock.NewController(t))
return map[common.Address]precompileconfig.Predicater{
addr1: predicater,
}
},
expectedRes: make(map[common.Address][]byte),
expectedErr: nil,
},
"predicate named by access list, without context errors": {
gas: 53000,
predicateContext: nil,
createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater {
predicater := precompileconfig.NewMockPredicater(gomock.NewController(t))
arg := common.Hash{1}
predicater.EXPECT().PredicateGas(arg[:]).Return(uint64(0), nil).Times(1)
return map[common.Address]precompileconfig.Predicater{
addr1: predicater,
}
},
accessList: types.AccessList([]types.AccessTuple{
{
Address: addr1,
StorageKeys: []common.Hash{
{1},
},
},
}),
expectedErr: ErrMissingPredicateContext,
},
"predicate named by access list, without block context errors": {
gas: 53000,
predicateContext: &precompileconfig.PredicateContext{
ProposerVMBlockCtx: nil,
},
createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater {
predicater := precompileconfig.NewMockPredicater(gomock.NewController(t))
arg := common.Hash{1}
predicater.EXPECT().PredicateGas(arg[:]).Return(uint64(0), nil).Times(1)
return map[common.Address]precompileconfig.Predicater{
addr1: predicater,
}
},
accessList: types.AccessList([]types.AccessTuple{
{
Address: addr1,
StorageKeys: []common.Hash{
{1},
},
},
}),
expectedErr: ErrMissingPredicateContext,
},
"predicate named by access list returns non-empty": {
gas: 53000,
predicateContext: predicateContext,
createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater {
predicater := precompileconfig.NewMockPredicater(gomock.NewController(t))
arg := common.Hash{1}
predicater.EXPECT().PredicateGas(arg[:]).Return(uint64(0), nil).Times(2)
predicater.EXPECT().VerifyPredicate(gomock.Any(), arg[:]).Return(nil)
return map[common.Address]precompileconfig.Predicater{
addr1: predicater,
}
},
accessList: types.AccessList([]types.AccessTuple{
{
Address: addr1,
StorageKeys: []common.Hash{
{1},
},
},
}),
expectedRes: map[common.Address][]byte{
addr1: {}, // valid bytes
},
expectedErr: nil,
},
"predicate returns gas err": {
gas: 53000,
predicateContext: predicateContext,
createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater {
predicater := precompileconfig.NewMockPredicater(gomock.NewController(t))
arg := common.Hash{1}
predicater.EXPECT().PredicateGas(arg[:]).Return(uint64(0), testErr)
return map[common.Address]precompileconfig.Predicater{
addr1: predicater,
}
},
accessList: types.AccessList([]types.AccessTuple{
{
Address: addr1,
StorageKeys: []common.Hash{
{1},
},
},
}),
expectedErr: testErr,
},
"two predicates one named by access list returns non-empty": {
gas: 53000,
predicateContext: predicateContext,
createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater {
predicater := precompileconfig.NewMockPredicater(gomock.NewController(t))
arg := common.Hash{1}
predicater.EXPECT().PredicateGas(arg[:]).Return(uint64(0), nil).Times(2)
predicater.EXPECT().VerifyPredicate(gomock.Any(), arg[:]).Return(nil)
return map[common.Address]precompileconfig.Predicater{
addr1: predicater,
addr2: predicater,
}
},
accessList: types.AccessList([]types.AccessTuple{
{
Address: addr1,
StorageKeys: []common.Hash{
{1},
},
},
}),
expectedRes: map[common.Address][]byte{
addr1: {}, // valid bytes
},
expectedErr: nil,
},
"two predicates both named by access list returns non-empty": {
gas: 53000,
predicateContext: predicateContext,
createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater {
ctrl := gomock.NewController(t)
predicate1 := precompileconfig.NewMockPredicater(ctrl)
arg1 := common.Hash{1}
predicate1.EXPECT().PredicateGas(arg1[:]).Return(uint64(0), nil).Times(2)
predicate1.EXPECT().VerifyPredicate(gomock.Any(), arg1[:]).Return(nil)
predicate2 := precompileconfig.NewMockPredicater(ctrl)
arg2 := common.Hash{2}
predicate2.EXPECT().PredicateGas(arg2[:]).Return(uint64(0), nil).Times(2)
predicate2.EXPECT().VerifyPredicate(gomock.Any(), arg2[:]).Return(testErr)
return map[common.Address]precompileconfig.Predicater{
addr1: predicate1,
addr2: predicate2,
}
},
accessList: types.AccessList([]types.AccessTuple{
{
Address: addr1,
StorageKeys: []common.Hash{
{1},
},
},
{
Address: addr2,
StorageKeys: []common.Hash{
{2},
},
},
}),
expectedRes: map[common.Address][]byte{
addr1: {}, // valid bytes
addr2: {1}, // invalid bytes
},
expectedErr: nil,
},
"two predicates neither named by access list": {
gas: 61600,
predicateContext: predicateContext,
createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater {
predicater := precompileconfig.NewMockPredicater(gomock.NewController(t))
return map[common.Address]precompileconfig.Predicater{
addr1: predicater,
addr2: predicater,
}
},
accessList: types.AccessList([]types.AccessTuple{
{
Address: addr3,
StorageKeys: []common.Hash{
{1},
},
},
{
Address: addr4,
StorageKeys: []common.Hash{
{1},
},
},
}),
expectedRes: make(map[common.Address][]byte),
expectedErr: nil,
},
"insufficient gas": {
gas: 53000,
predicateContext: predicateContext,
createPredicates: func(t testing.TB) map[common.Address]precompileconfig.Predicater {
predicater := precompileconfig.NewMockPredicater(gomock.NewController(t))
arg := common.Hash{1}
predicater.EXPECT().PredicateGas(arg[:]).Return(uint64(1), nil)
return map[common.Address]precompileconfig.Predicater{
addr1: predicater,
}
},
accessList: types.AccessList([]types.AccessTuple{
{
Address: addr1,
StorageKeys: []common.Hash{
{1},
},
},
}),
expectedErr: ErrIntrinsicGas,
},
} {
t.Run(name, func(t *testing.T) {
require := require.New(t)
// Create the rules from TestChainConfig and update the predicates based on the test params
rules := params.TestChainConfig.Rules(common.Big0, 0)
if test.createPredicates != nil {
for address, predicater := range test.createPredicates(t) {
rules := params.GetRulesExtra(rules)
rules.Predicaters[address] = predicater
}
}
// Specify only the access list, since this test should not depend on any other values
tx := types.NewTx(&types.DynamicFeeTx{
AccessList: test.accessList,
Gas: test.gas,
})
predicateRes, err := CheckPredicates(rules, test.predicateContext, tx)
require.ErrorIs(err, test.expectedErr)
if test.expectedErr != nil {
return
}
require.Equal(test.expectedRes, predicateRes)
intrinsicGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), true, rules)
require.NoError(err)
require.Equal(tx.Gas(), intrinsicGas) // Require test specifies exact amount of gas consumed
})
}
}
func TestCheckPredicatesOutput(t *testing.T) {
testErr := errors.New("test error")
addr1 := common.HexToAddress("0xaa")
addr2 := common.HexToAddress("0xbb")
validHash := common.Hash{1}
invalidHash := common.Hash{2}
predicateContext := &precompileconfig.PredicateContext{
ProposerVMBlockCtx: &commontype.BlockContext{
PChainHeight: 10,
},
}
type testTuple struct {
address common.Address
isValidPredicate bool
}
type resultTest struct {
name string
expectedRes map[common.Address][]byte
testTuple []testTuple
}
tests := []resultTest{
{name: "no predicates", expectedRes: map[common.Address][]byte{}},
{
name: "one address one predicate",
testTuple: []testTuple{
{address: addr1, isValidPredicate: true},
},
expectedRes: map[common.Address][]byte{addr1: set.NewBits().Bytes()},
},
{
name: "one address one invalid predicate",
testTuple: []testTuple{
{address: addr1, isValidPredicate: false},
},
expectedRes: map[common.Address][]byte{addr1: set.NewBits(0).Bytes()},
},
{
name: "one address two invalid predicates",
testTuple: []testTuple{
{address: addr1, isValidPredicate: false},
{address: addr1, isValidPredicate: false},
},
expectedRes: map[common.Address][]byte{addr1: set.NewBits(0, 1).Bytes()},
},
{
name: "one address two mixed predicates",
testTuple: []testTuple{
{address: addr1, isValidPredicate: true},
{address: addr1, isValidPredicate: false},
},
expectedRes: map[common.Address][]byte{addr1: set.NewBits(1).Bytes()},
},
{
name: "one address mixed predicates",
testTuple: []testTuple{
{address: addr1, isValidPredicate: true},
{address: addr1, isValidPredicate: false},
{address: addr1, isValidPredicate: false},
{address: addr1, isValidPredicate: true},
},
expectedRes: map[common.Address][]byte{addr1: set.NewBits(1, 2).Bytes()},
},
{
name: "two addresses mixed predicates",
testTuple: []testTuple{
{address: addr1, isValidPredicate: true},
{address: addr2, isValidPredicate: false},
{address: addr1, isValidPredicate: false},
{address: addr1, isValidPredicate: false},
{address: addr2, isValidPredicate: true},
{address: addr2, isValidPredicate: true},
{address: addr2, isValidPredicate: false},
{address: addr2, isValidPredicate: true},
},
expectedRes: map[common.Address][]byte{addr1: set.NewBits(1, 2).Bytes(), addr2: set.NewBits(0, 3).Bytes()},
},
{
name: "two addresses all valid predicates",
testTuple: []testTuple{
{address: addr1, isValidPredicate: true},
{address: addr2, isValidPredicate: true},
{address: addr1, isValidPredicate: true},
{address: addr1, isValidPredicate: true},
},
expectedRes: map[common.Address][]byte{addr1: set.NewBits().Bytes(), addr2: set.NewBits().Bytes()},
},
{
name: "two addresses all invalid predicates",
testTuple: []testTuple{
{address: addr1, isValidPredicate: false},
{address: addr2, isValidPredicate: false},
{address: addr1, isValidPredicate: false},
{address: addr1, isValidPredicate: false},
},
expectedRes: map[common.Address][]byte{addr1: set.NewBits(0, 1, 2).Bytes(), addr2: set.NewBits(0).Bytes()},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
// Create the rules from TestChainConfig and update the predicates based on the test params
rules := params.TestChainConfig.Rules(common.Big0, 0)
predicater := precompileconfig.NewMockPredicater(gomock.NewController(t))
predicater.EXPECT().PredicateGas(gomock.Any()).Return(uint64(0), nil).Times(len(test.testTuple))
var txAccessList types.AccessList
for _, tuple := range test.testTuple {
var predicateHash common.Hash
if tuple.isValidPredicate {
predicateHash = validHash
predicater.EXPECT().VerifyPredicate(gomock.Any(), validHash[:]).Return(nil)
} else {
predicateHash = invalidHash
predicater.EXPECT().VerifyPredicate(gomock.Any(), invalidHash[:]).Return(testErr)
}
txAccessList = append(txAccessList, types.AccessTuple{
Address: tuple.address,
StorageKeys: []common.Hash{
predicateHash,
},
})
}
rulesExtra := params.GetRulesExtra(rules)
rulesExtra.Predicaters[addr1] = predicater
rulesExtra.Predicaters[addr2] = predicater
// Specify only the access list, since this test should not depend on any other values
tx := types.NewTx(&types.DynamicFeeTx{
AccessList: txAccessList,
Gas: 53000,
})
predicateRes, err := CheckPredicates(rules, predicateContext, tx)
require.NoError(err)
require.Equal(test.expectedRes, predicateRes)
})
}
}