diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index 2b2f03f1ea..bd8a19be6e 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -127,7 +127,7 @@ func (self *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, heade if err != nil { return fmt.Errorf("failed to calculate base fee: %w", err) } - if !bytes.Equal(expectedRollupWindowBytes, header.Extra) { + if len(header.Extra) < len(expectedRollupWindowBytes) || !bytes.Equal(expectedRollupWindowBytes, header.Extra[:len(expectedRollupWindowBytes)]) { return fmt.Errorf("expected rollup window bytes: %x, found %x", expectedRollupWindowBytes, header.Extra) } if header.BaseFee == nil { @@ -194,16 +194,21 @@ func (self *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header if uncle { return errUnclesUnsupported } - // Ensure that the header's extra-data section is of a reasonable size - if !config.IsApricotPhase3(header.Time) { + switch { + case config.IsDUpgrade(header.Time): + if len(header.Extra) < params.DynamicFeeExtraDataSize { + return fmt.Errorf("expected extra-data field length >= %d, found %d", params.DynamicFeeExtraDataSize, len(header.Extra)) + } + case config.IsApricotPhase3(header.Time): + if len(header.Extra) != params.DynamicFeeExtraDataSize { + return fmt.Errorf("expected extra-data field to be: %d, but found %d", params.DynamicFeeExtraDataSize, len(header.Extra)) + } + default: if uint64(len(header.Extra)) > params.MaximumExtraDataSize { return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize) } - } else { - if uint64(len(header.Extra)) != params.ApricotPhase3ExtraDataSize { - return fmt.Errorf("expected extra-data field to be: %d, but found %d", params.ApricotPhase3ExtraDataSize, len(header.Extra)) - } } + // Ensure gas-related header fields are correct if err := self.verifyHeaderGasFields(config, header, parent); err != nil { return err diff --git a/consensus/dummy/dynamic_fees.go b/consensus/dummy/dynamic_fees.go index 3b6ca38725..f92298095a 100644 --- a/consensus/dummy/dynamic_fees.go +++ b/consensus/dummy/dynamic_fees.go @@ -46,13 +46,15 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, timestamp uin isApricotPhase5 = config.IsApricotPhase5(parent.Time) ) if !isApricotPhase3 || parent.Number.Cmp(common.Big0) == 0 { - initialSlice := make([]byte, params.ApricotPhase3ExtraDataSize) + initialSlice := make([]byte, params.DynamicFeeExtraDataSize) initialBaseFee := big.NewInt(params.ApricotPhase3InitialBaseFee) return initialSlice, initialBaseFee, nil } - if uint64(len(parent.Extra)) != params.ApricotPhase3ExtraDataSize { - return nil, nil, fmt.Errorf("expected length of parent extra data to be %d, but found %d", params.ApricotPhase3ExtraDataSize, len(parent.Extra)) + + if uint64(len(parent.Extra)) < params.DynamicFeeExtraDataSize { + return nil, nil, fmt.Errorf("expected length of parent extra data to be %d, but found %d", params.DynamicFeeExtraDataSize, len(parent.Extra)) } + dynamicFeeWindow := parent.Extra[:params.DynamicFeeExtraDataSize] if timestamp < parent.Time { return nil, nil, fmt.Errorf("cannot calculate base fee for timestamp (%d) prior to parent timestamp (%d)", timestamp, parent.Time) @@ -61,7 +63,7 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, timestamp uin // roll the window over by the difference between the timestamps to generate // the new rollup window. - newRollupWindow, err := rollLongWindow(parent.Extra, int(roll)) + newRollupWindow, err := rollLongWindow(dynamicFeeWindow, int(roll)) if err != nil { return nil, nil, err } diff --git a/core/bench_test.go b/core/bench_test.go index eb9cafb0c9..a91b46b13b 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -88,7 +88,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) { return func(i int, gen *BlockGen) { toaddr := common.Address{} data := make([]byte, nbytes) - gas, _ := IntrinsicGas(data, nil, false, false, false, false) + gas, _ := IntrinsicGas(data, nil, false, params.Rules{}) signer := types.MakeSigner(gen.config, big.NewInt(int64(i)), gen.header.Time) tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(benchRootAddr), toaddr, big.NewInt(1), gas, big.NewInt(225000000000), data), signer, benchRootKey) gen.AddTx(tx) diff --git a/core/chain_makers.go b/core/chain_makers.go index 349c8a418f..6c72922f44 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -79,6 +79,11 @@ func (b *BlockGen) SetExtra(data []byte) { b.header.Extra = data } +// AppendExtra appends data to the extra data field of the generated block. +func (b *BlockGen) AppendExtra(data []byte) { + b.header.Extra = append(b.header.Extra, data...) +} + // SetNonce sets the nonce field of the generated block. func (b *BlockGen) SetNonce(nonce types.BlockNonce) { b.header.Nonce = nonce @@ -103,7 +108,8 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti b.SetCoinbase(common.Address{}) } b.statedb.SetTxContext(tx.Hash(), len(b.txs)) - receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vmConfig) + blockContext := NewEVMBlockContext(b.header, bc, &b.header.Coinbase) + receipt, err := ApplyTransaction(b.config, bc, blockContext, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vmConfig) if err != nil { panic(err) } @@ -246,6 +252,11 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse b := &BlockGen{i: i, chain: blocks, parent: parent, statedb: statedb, config: config, engine: engine} b.header = makeHeader(chainreader, config, parent, gap, statedb, b.engine) + err := ApplyUpgrades(config, &parent.Header().Time, b, statedb) + if err != nil { + return nil, nil, fmt.Errorf("failed to configure precompiles %v", err) + } + // Execute any user modifications to the block if gen != nil { gen(i, b) diff --git a/core/evm.go b/core/evm.go index 7cce46f9ad..e4607d94ce 100644 --- a/core/evm.go +++ b/core/evm.go @@ -32,7 +32,9 @@ import ( "github.com/ava-labs/coreth/consensus" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/core/vm" + "github.com/ava-labs/coreth/predicate" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" //"github.com/ethereum/go-ethereum/log" ) @@ -48,6 +50,33 @@ type ChainContext interface { // NewEVMBlockContext creates a new context for use in the EVM. func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext { + predicateBytes, ok := predicate.GetPredicateResultBytes(header.Extra) + if !ok { + return newEVMBlockContext(header, chain, author, nil) + } + // Prior to the DUpgrade, the VM enforces the extra data is smaller than or + // equal to this size. After the DUpgrade, the VM pre-verifies the extra + // data past the dynamic fee rollup window is valid. + predicateResults, err := predicate.ParseResults(predicateBytes) + if err != nil { + log.Error("failed to parse predicate results creating new block context", "err", err, "extra", header.Extra) + // As mentioned above, we pre-verify the extra data to ensure this never happens. + // If we hit an error, construct a new block context rather than use a potentially half initialized value + // as defense in depth. + return newEVMBlockContext(header, chain, author, nil) + } + return newEVMBlockContext(header, chain, author, predicateResults) +} + +// NewEVMBlockContextWithPredicateResults creates a new context for use in the EVM with an override for the predicate results that is not present +// in header.Extra. +// This function is used to create a BlockContext when the header Extra data is not fully formed yet and it's more efficient to pass in predicateResults +// directly rather than re-encode the latest results when executing each individaul transaction. +func NewEVMBlockContextWithPredicateResults(header *types.Header, chain ChainContext, author *common.Address, predicateResults *predicate.Results) vm.BlockContext { + return newEVMBlockContext(header, chain, author, predicateResults) +} + +func newEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, predicateResults *predicate.Results) vm.BlockContext { var ( beneficiary common.Address baseFee *big.Int @@ -68,6 +97,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common Transfer: Transfer, TransferMultiCoin: TransferMultiCoin, GetHash: GetHashFn(header, chain), + PredicateResults: predicateResults, Coinbase: beneficiary, BlockNumber: new(big.Int).Set(header.Number), Time: header.Time, diff --git a/core/genesis.go b/core/genesis.go index d5c2fe386d..dbc5252d91 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -266,7 +266,10 @@ func (g *Genesis) toBlock(db ethdb.Database, triedb *trie.Database) *types.Block } // Configure any stateful precompiles that should be enabled in the genesis. - g.Config.CheckConfigurePrecompiles(nil, types.NewBlockWithHeader(head), statedb) + err = ApplyPrecompileActivations(g.Config, nil, types.NewBlockWithHeader(head), statedb) + if err != nil { + panic(fmt.Sprintf("unable to configure precompiles in genesis block: %v", err)) + } for addr, account := range g.Alloc { statedb.AddBalance(addr, account.Balance) diff --git a/core/predicate_check.go b/core/predicate_check.go new file mode 100644 index 0000000000..ab6c9346d5 --- /dev/null +++ b/core/predicate_check.go @@ -0,0 +1,61 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package core + +import ( + "errors" + "fmt" + + "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/precompile/precompileconfig" + "github.com/ava-labs/coreth/predicate" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +var ErrMissingPredicateContext = errors.New("missing predicate context") + +// CheckPredicates verifies the predicates of [tx] and returns the result. Returning an error invalidates the block. +func CheckPredicates(rules params.Rules, predicateContext *precompileconfig.PredicateContext, tx *types.Transaction) (map[common.Address][]byte, error) { + // Check that the transaction can cover its IntrinsicGas (including the gas required by the predicate) before + // verifying the predicate. + intrinsicGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, rules) + if err != nil { + return nil, err + } + if tx.Gas() < intrinsicGas { + return nil, fmt.Errorf("%w for predicate verification (%d) < intrinsic gas (%d)", ErrIntrinsicGas, tx.Gas(), intrinsicGas) + } + + predicateResults := make(map[common.Address][]byte) + // Short circuit early if there are no precompile predicates to verify + if len(rules.Predicaters) == 0 { + return predicateResults, nil + } + + // Prepare the predicate storage slots from the transaction's access list + predicateArguments := predicate.PreparePredicateStorageSlots(rules, tx.AccessList()) + + // If there are no predicates to verify, return early and skip requiring the proposervm block + // context to be populated. + if len(predicateArguments) == 0 { + return predicateResults, nil + } + + if predicateContext == nil || predicateContext.ProposerVMBlockCtx == nil { + return nil, ErrMissingPredicateContext + } + + for address, predicates := range predicateArguments { + // Since [address] is only added to [predicateArguments] when there's a valid predicate in the ruleset + // there's no need to check if the predicate exists here. + predicaterContract := rules.Predicaters[address] + res := predicaterContract.VerifyPredicate(predicateContext, predicates) + log.Debug("predicate verify", "tx", tx.Hash(), "address", address, "res", res) + predicateResults[address] = res + } + + return predicateResults, nil +} diff --git a/core/predicate_check_test.go b/core/predicate_check_test.go new file mode 100644 index 0000000000..cbd069a5d6 --- /dev/null +++ b/core/predicate_check_test.go @@ -0,0 +1,324 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package core + +import ( + "errors" + "testing" + + "github.com/ava-labs/avalanchego/snow/engine/snowman/block" + "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/precompile/precompileconfig" + "github.com/ethereum/go-ethereum/common" + "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") + predicateResultBytes1 := []byte{1, 2, 3} + predicateResultBytes2 := []byte{3, 2, 1} + predicateContext := &precompileconfig.PredicateContext{ + ProposerVMBlockCtx: &block.Context{ + 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(), [][]byte{arg[:]}).Return(predicateResultBytes1) + 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: predicateResultBytes1, + }, + 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(), [][]byte{arg[:]}).Return(predicateResultBytes1) + 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: predicateResultBytes1, + }, + 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(), [][]byte{arg1[:]}).Return(predicateResultBytes1) + predicate2 := precompileconfig.NewMockPredicater(ctrl) + arg2 := common.Hash{2} + predicate2.EXPECT().PredicateGas(arg2[:]).Return(uint64(0), nil).Times(2) + predicate2.EXPECT().VerifyPredicate(gomock.Any(), [][]byte{arg2[:]}).Return(predicateResultBytes2) + 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: predicateResultBytes1, + addr2: predicateResultBytes2, + }, + expectedErr: nil, + }, + "two predicates niether 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, + }, + } { + test := test + 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.AvalancheRules(common.Big0, 0) + if test.createPredicates != nil { + for address, predicater := range test.createPredicates(t) { + 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 + }) + } +} diff --git a/core/state/statedb.go b/core/state/statedb.go index c64534b622..8af9a4acc0 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -39,6 +39,7 @@ import ( "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/metrics" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/predicate" "github.com/ava-labs/coreth/trie" "github.com/ava-labs/coreth/trie/trienode" "github.com/ethereum/go-ethereum/common" @@ -109,6 +110,9 @@ type StateDB struct { // Per-transaction access list accessList *accessList + // Ordered storage slots to be used in predicate verification as set in the tx access list. + // Only set in PrepareAccessList, and un-modified through execution. + predicateStorageSlots map[common.Address][][]byte // Transient storage transientStorage transientStorage @@ -158,19 +162,20 @@ func NewWithSnapshot(root common.Hash, db Database, snap snapshot.Snapshot) (*St return nil, err } sdb := &StateDB{ - db: db, - trie: tr, - originalRoot: root, - stateObjects: make(map[common.Address]*stateObject), - stateObjectsPending: make(map[common.Address]struct{}), - stateObjectsDirty: make(map[common.Address]struct{}), - stateObjectsDestruct: make(map[common.Address]struct{}), - logs: make(map[common.Hash][]*types.Log), - preimages: make(map[common.Hash][]byte), - journal: newJournal(), - accessList: newAccessList(), - transientStorage: newTransientStorage(), - hasher: crypto.NewKeccakState(), + db: db, + trie: tr, + originalRoot: root, + stateObjects: make(map[common.Address]*stateObject), + stateObjectsPending: make(map[common.Address]struct{}), + stateObjectsDirty: make(map[common.Address]struct{}), + stateObjectsDestruct: make(map[common.Address]struct{}), + logs: make(map[common.Hash][]*types.Log), + preimages: make(map[common.Hash][]byte), + journal: newJournal(), + predicateStorageSlots: make(map[common.Address][][]byte), + accessList: newAccessList(), + transientStorage: newTransientStorage(), + hasher: crypto.NewKeccakState(), } if snap != nil { if snap.Root() != root { @@ -217,7 +222,16 @@ func (s *StateDB) Error() error { return s.dbErr } -func (s *StateDB) AddLog(log *types.Log) { +// AddLog adds a log with the specified parameters to the statedb +// Note: blockNumber is a required argument because StateDB does not +// know the current block number. +func (s *StateDB) AddLog(addr common.Address, topics []common.Hash, data []byte, blockNumber uint64) { + log := &types.Log{ + Address: addr, + Topics: topics, + Data: data, + BlockNumber: blockNumber, + } s.journal.append(addLogChange{txhash: s.thash}) log.TxHash = s.thash @@ -246,6 +260,18 @@ func (s *StateDB) Logs() []*types.Log { return logs } +// GetLogData returns the underlying data from each log included in the StateDB +// Test helper function. +func (s *StateDB) GetLogData() [][]byte { + var logData [][]byte + for _, lgs := range s.logs { + for _, log := range lgs { + logData = append(logData, common.CopyBytes(log.Data)) + } + } + return logData +} + // AddPreimage records a SHA3 preimage seen by the VM. func (s *StateDB) AddPreimage(hash common.Hash, preimage []byte) { if _, ok := s.preimages[hash]; !ok { @@ -763,6 +789,19 @@ func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common return nil } +// copyPredicateStorageSlots creates a deep copy of the provided predicateStorageSlots map. +func copyPredicateStorageSlots(predicateStorageSlots map[common.Address][][]byte) map[common.Address][][]byte { + res := make(map[common.Address][][]byte, len(predicateStorageSlots)) + for address, predicates := range predicateStorageSlots { + copiedPredicates := make([][]byte, len(predicates)) + for i, predicateBytes := range predicates { + copiedPredicates[i] = common.CopyBytes(predicateBytes) + } + res[address] = copiedPredicates + } + return res +} + // Copy creates a deep, independent copy of the state. // Snapshots of the copied state cannot be applied to the copy. func (s *StateDB) Copy() *StateDB { @@ -837,6 +876,7 @@ func (s *StateDB) Copy() *StateDB { // in the middle of a transaction. state.accessList = s.accessList.Copy() state.transientStorage = s.transientStorage.Copy() + state.predicateStorageSlots = copyPredicateStorageSlots(s.predicateStorageSlots) // If there's a prefetcher running, make an inactive copy of it that can // only access data but does not actively preload (since the user will not @@ -1197,6 +1237,8 @@ func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, d if rules.IsDUpgrade { // EIP-3651: warm coinbase al.AddAddress(coinbase) } + + s.predicateStorageSlots = predicate.PreparePredicateStorageSlots(rules, list) } // Reset transient storage at the beginning of transaction execution s.transientStorage = newTransientStorage() @@ -1237,6 +1279,32 @@ func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addre return s.accessList.Contains(addr, slot) } +// GetTxHash returns the current tx hash on the StateDB set by SetTxContext. +func (s *StateDB) GetTxHash() common.Hash { + return s.thash +} + +// GetPredicateStorageSlots returns the storage slots associated with the address, index pair. +// A list of access tuples can be included within transaction types post EIP-2930. The address +// is declared directly on the access tuple and the index is the i'th occurrence of an access +// tuple with the specified address. +// +// Ex. AccessList[[AddrA, Predicate1], [AddrB, Predicate2], [AddrA, Predicate3]] +// In this case, the caller could retrieve predicates 1-3 with the following calls: +// GetPredicateStorageSlots(AddrA, 0) -> Predicate1 +// GetPredicateStorageSlots(AddrB, 0) -> Predicate2 +// GetPredicateStorageSlots(AddrA, 1) -> Predicate3 +func (s *StateDB) GetPredicateStorageSlots(address common.Address, index int) ([]byte, bool) { + predicates, exists := s.predicateStorageSlots[address] + if !exists { + return nil, false + } + if index >= len(predicates) { + return nil, false + } + return predicates[index], true +} + // convertAccountSet converts a provided account set from address keyed to hash keyed. func (s *StateDB) convertAccountSet(set map[common.Address]struct{}) map[common.Hash]struct{} { ret := make(map[common.Hash]struct{}, len(set)) @@ -1250,3 +1318,8 @@ func (s *StateDB) convertAccountSet(set map[common.Address]struct{}) map[common. } return ret } + +// SetPredicateStorageSlots sets the predicate storage slots for the given address +func (s *StateDB) SetPredicateStorageSlots(address common.Address, predicates [][]byte) { + s.predicateStorageSlots[address] = predicates +} diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 7d9928a413..130a7df734 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -327,7 +327,7 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction { fn: func(a testAction, s *StateDB) { data := make([]byte, 2) binary.BigEndian.PutUint16(data, uint16(a.args[0])) - s.AddLog(&types.Log{Address: addr, Data: data}) + s.AddLog(addr, nil, data, 0) }, args: make([]int64, 1), }, diff --git a/core/state/test_statedb.go b/core/state/test_statedb.go new file mode 100644 index 0000000000..c34fdec80d --- /dev/null +++ b/core/state/test_statedb.go @@ -0,0 +1,20 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package state + +import ( + "testing" + + "github.com/ava-labs/coreth/core/rawdb" + "github.com/ava-labs/coreth/precompile/contract" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func NewTestStateDB(t testing.TB) contract.StateDB { + db := rawdb.NewMemoryDatabase() + stateDB, err := New(common.Hash{}, NewDatabase(db), nil) + require.NoError(t, err) + return stateDB +} diff --git a/core/state_processor.go b/core/state_processor.go index 67c52c3af6..168bf9580c 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -35,8 +35,11 @@ import ( "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/core/vm" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/precompile/contract" + "github.com/ava-labs/coreth/precompile/modules" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" ) // StateProcessor is a basic Processor, which takes care of transitioning @@ -76,8 +79,12 @@ func (p *StateProcessor) Process(block *types.Block, parent *types.Header, state gp = new(GasPool).AddGas(block.GasLimit()) ) - // Configure any stateful precompiles that should go into effect during this block. - p.config.CheckConfigurePrecompiles(&parent.Time, block, statedb) + // Configure any upgrades that should go into effect during this block. + err := ApplyUpgrades(p.config, &parent.Time, block, statedb) + if err != nil { + log.Error("failed to configure precompiles processing block", "hash", block.Hash(), "number", block.NumberU64(), "timestamp", block.Time(), "err", err) + return nil, nil, 0, err + } var ( context = NewEVMBlockContext(header, p.bc, nil) @@ -155,13 +162,68 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta // and uses the input parameters for its environment. It returns the receipt // for the transaction, gas used and an error if the transaction failed, // indicating the block was invalid. -func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) { +func ApplyTransaction(config *params.ChainConfig, bc ChainContext, blockContext vm.BlockContext, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) { msg, err := TransactionToMessage(tx, types.MakeSigner(config, header.Number, header.Time), header.BaseFee) if err != nil { return nil, err } // Create a new context to be used in the EVM environment - blockContext := NewEVMBlockContext(header, bc, author) vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg) return applyTransaction(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) } + +// ApplyPrecompileActivations checks if any of the precompiles specified by the chain config are enabled or disabled by the block +// transition from [parentTimestamp] to the timestamp set in [blockContext]. If this is the case, it calls [Configure] +// to apply the necessary state transitions for the upgrade. +// This function is called within genesis setup to configure the starting state for precompiles enabled at genesis. +// In block processing and building, ApplyUpgrades is called instead which also applies state upgrades. +func ApplyPrecompileActivations(c *params.ChainConfig, parentTimestamp *uint64, blockContext contract.ConfigurationBlockContext, statedb *state.StateDB) error { + blockTimestamp := blockContext.Timestamp() + // Note: RegisteredModules returns precompiles sorted by module addresses. + // This ensures that the order we call Configure for each precompile is consistent. + // This ensures even if precompiles read/write state other than their own they will observe + // an identical global state in a deterministic order when they are configured. + for _, module := range modules.RegisteredModules() { + key := module.ConfigKey + for _, activatingConfig := range c.GetActivatingPrecompileConfigs(module.Address, parentTimestamp, blockTimestamp, c.PrecompileUpgrades) { + // If this transition activates the upgrade, configure the stateful precompile. + // (or deconfigure it if it is being disabled.) + if activatingConfig.IsDisabled() { + log.Info("Disabling precompile", "name", key) + statedb.Suicide(module.Address) + // Calling Finalise here effectively commits Suicide call and wipes the contract state. + // This enables re-configuration of the same contract state in the same block. + // Without an immediate Finalise call after the Suicide, a reconfigured precompiled state can be wiped out + // since Suicide will be committed after the reconfiguration. + statedb.Finalise(true) + } else { + module, ok := modules.GetPrecompileModule(key) + if !ok { + return fmt.Errorf("could not find module for activating precompile, name: %s", key) + } + log.Info("Activating new precompile", "name", key, "config", activatingConfig) + // Set the nonce of the precompile's address (as is done when a contract is created) to ensure + // that it is marked as non-empty and will not be cleaned up when the statedb is finalized. + statedb.SetNonce(module.Address, 1) + // Set the code of the precompile's address to a non-zero length byte slice to ensure that the precompile + // can be called from within Solidity contracts. Solidity adds a check before invoking a contract to ensure + // that it does not attempt to invoke a non-existent contract. + statedb.SetCode(module.Address, []byte{0x1}) + if err := module.Configure(c, activatingConfig, statedb, blockContext); err != nil { + return fmt.Errorf("could not configure precompile, name: %s, reason: %w", key, err) + } + } + } + } + return nil +} + +// ApplyUpgrades checks if any of the precompile or state upgrades specified by the chain config are activated by the block +// transition from [parentTimestamp] to the timestamp set in [header]. If this is the case, it calls [Configure] +// to apply the necessary state transitions for the upgrade. +// This function is called: +// - in block processing to update the state when processing a block. +// - in the miner to apply the state upgrades when producing a block. +func ApplyUpgrades(c *params.ChainConfig, parentTimestamp *uint64, blockContext contract.ConfigurationBlockContext, statedb *state.StateDB) error { + return ApplyPrecompileActivations(c, parentTimestamp, blockContext, statedb) +} diff --git a/core/state_transition.go b/core/state_transition.go index 527b7c383b..5be069529c 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -34,6 +34,7 @@ import ( "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/core/vm" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/utils" "github.com/ava-labs/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" cmath "github.com/ethereum/go-ethereum/common/math" @@ -75,10 +76,10 @@ func (result *ExecutionResult) Revert() []byte { } // IntrinsicGas computes the 'intrinsic gas' for a message with the given data. -func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028 bool, isEIP3860 bool) (uint64, error) { +func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, rules params.Rules) (uint64, error) { // Set the starting gas for the raw transaction var gas uint64 - if isContractCreation && isHomestead { + if isContractCreation && rules.IsHomestead { gas = params.TxGasContractCreation } else { gas = params.TxGas @@ -95,7 +96,7 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b } // Make sure we don't exceed uint64 for all data combinations nonZeroGas := params.TxDataNonZeroGasFrontier - if isEIP2028 { + if rules.IsIstanbul { nonZeroGas = params.TxDataNonZeroGasEIP2028 } if (math.MaxUint64-gas)/nonZeroGas < nz { @@ -109,7 +110,7 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b } gas += z * params.TxDataZeroGas - if isContractCreation && isEIP3860 { + if isContractCreation && rules.IsDUpgrade { lenWords := toWordSize(dataLen) if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords { return 0, ErrGasUintOverflow @@ -118,9 +119,55 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b } } if accessList != nil { + accessListGas, err := accessListGas(rules, accessList) + if err != nil { + return 0, err + } + totalGas, overflow := cmath.SafeAdd(gas, accessListGas) + if overflow { + return 0, ErrGasUintOverflow + } + gas = totalGas + } + + return gas, nil +} + +func accessListGas(rules params.Rules, accessList types.AccessList) (uint64, error) { + var gas uint64 + if !rules.PredicatersExist() { gas += uint64(len(accessList)) * params.TxAccessListAddressGas gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas + return gas, nil } + + for _, accessTuple := range accessList { + address := accessTuple.Address + predicaterContract, ok := rules.Predicaters[address] + if !ok { + // Previous access list gas calculation does not use safemath because an overflow would not be possible with + // the size of access lists that could be included in a block and standard access list gas costs. + // Therefore, we only check for overflow when adding to [totalGas], which could include the sum of values + // returned by a predicate. + accessTupleGas := params.TxAccessListAddressGas + uint64(len(accessTuple.StorageKeys))*params.TxAccessListStorageKeyGas + totalGas, overflow := cmath.SafeAdd(gas, accessTupleGas) + if overflow { + return 0, ErrGasUintOverflow + } + gas = totalGas + } else { + predicateGas, err := predicaterContract.PredicateGas(utils.HashSliceToBytes(accessTuple.StorageKeys)) + if err != nil { + return 0, err + } + totalGas, overflow := cmath.SafeAdd(gas, predicateGas) + if overflow { + return 0, ErrGasUintOverflow + } + gas = totalGas + } + } + return gas, nil } @@ -354,7 +401,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { ) // Check clauses 4-5, subtract intrinsic gas if everything is correct - gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsDUpgrade) + gas, err := IntrinsicGas(msg.Data, msg.AccessList, contractCreation, rules) if err != nil { return nil, err } diff --git a/core/stateful_precompile_test.go b/core/stateful_precompile_test.go deleted file mode 100644 index 0dbd757dfc..0000000000 --- a/core/stateful_precompile_test.go +++ /dev/null @@ -1,43 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package core - -import ( - "math/big" - - "github.com/ava-labs/coreth/core/state" - "github.com/ava-labs/coreth/precompile" - "github.com/ethereum/go-ethereum/common" -) - -var ( - _ precompile.BlockContext = &mockBlockContext{} - _ precompile.PrecompileAccessibleState = &mockAccessibleState{} -) - -type mockBlockContext struct { - blockNumber *big.Int - timestamp uint64 -} - -func (mb *mockBlockContext) Number() *big.Int { return mb.blockNumber } -func (mb *mockBlockContext) Timestamp() uint64 { return mb.timestamp } - -type mockAccessibleState struct { - state *state.StateDB - blockContext *mockBlockContext - - // NativeAssetCall return values - ret []byte - remainingGas uint64 - err error -} - -func (m *mockAccessibleState) GetStateDB() precompile.StateDB { return m.state } - -func (m *mockAccessibleState) GetBlockContext() precompile.BlockContext { return m.blockContext } - -func (m *mockAccessibleState) NativeAssetCall(common.Address, []byte, uint64, uint64, bool) ([]byte, uint64, error) { - return m.ret, m.remainingGas, m.err -} diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index bc3dd52eb6..4882a76778 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -269,10 +269,10 @@ type TxPool struct { signer types.Signer mu sync.RWMutex - istanbul atomic.Bool // Fork indicator whether we are in the istanbul stage. - eip2718 atomic.Bool // Fork indicator whether we are using EIP-2718 type transactions. - eip1559 atomic.Bool // Fork indicator whether we are using EIP-1559 type transactions. - eip3860 atomic.Bool // Fork indicator whether EIP-3860 is activated. (activated in Shanghai Upgrade in Ethereum) + rules atomic.Pointer[params.Rules] // Rules for the currentHead + eip2718 atomic.Bool // Fork indicator whether we are using EIP-2718 type transactions. + eip1559 atomic.Bool // Fork indicator whether we are using EIP-1559 type transactions. + eip3860 atomic.Bool // Fork indicator whether EIP-3860 is activated. (activated in Shanghai Upgrade in Ethereum) currentHead *types.Header // [currentState] is the state of the blockchain head. It is reset whenever @@ -764,7 +764,7 @@ func (pool *TxPool) validateTxBasics(tx *types.Transaction, local bool) error { return fmt.Errorf("%w: address %s have gas tip cap (%d) < pool gas tip cap (%d)", ErrUnderpriced, from.Hex(), tx.GasTipCap(), pool.gasPrice) } // Ensure the transaction has more gas than the basic tx fee. - intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul.Load(), pool.eip3860.Load()) + intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, *pool.rules.Load()) if err != nil { return err } @@ -1520,10 +1520,12 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { // Update all fork indicator by next pending block number. next := new(big.Int).Add(newHead.Number, big.NewInt(1)) - pool.istanbul.Store(pool.chainconfig.IsIstanbul(next)) - pool.eip2718.Store(pool.chainconfig.IsApricotPhase2(newHead.Time)) - pool.eip1559.Store(pool.chainconfig.IsApricotPhase3(newHead.Time)) - pool.eip3860.Store(pool.chainconfig.IsDUpgrade(newHead.Time)) + rules := pool.chainconfig.AvalancheRules(next, newHead.Time) + + pool.rules.Store(&rules) + pool.eip2718.Store(rules.IsApricotPhase2) + pool.eip1559.Store(rules.IsApricotPhase3) + pool.eip3860.Store(rules.IsDUpgrade) } // promoteExecutables moves transactions that have become processable from the diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 0f9b2c8b59..8bfdd72cf5 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -33,9 +33,9 @@ import ( "fmt" "math/big" - "github.com/ava-labs/coreth/constants" "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/precompile" + "github.com/ava-labs/coreth/precompile/contract" + "github.com/ava-labs/coreth/precompile/modules" "github.com/ava-labs/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" @@ -57,7 +57,7 @@ type PrecompiledContract interface { // PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum // contracts used in the Frontier and Homestead releases. -var PrecompiledContractsHomestead = map[common.Address]precompile.StatefulPrecompiledContract{ +var PrecompiledContractsHomestead = map[common.Address]contract.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), @@ -66,7 +66,7 @@ var PrecompiledContractsHomestead = map[common.Address]precompile.StatefulPrecom // PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum // contracts used in the Byzantium release. -var PrecompiledContractsByzantium = map[common.Address]precompile.StatefulPrecompiledContract{ +var PrecompiledContractsByzantium = map[common.Address]contract.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), @@ -79,7 +79,7 @@ var PrecompiledContractsByzantium = map[common.Address]precompile.StatefulPrecom // PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum // contracts used in the Istanbul release. -var PrecompiledContractsIstanbul = map[common.Address]precompile.StatefulPrecompiledContract{ +var PrecompiledContractsIstanbul = map[common.Address]contract.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), @@ -93,7 +93,7 @@ var PrecompiledContractsIstanbul = map[common.Address]precompile.StatefulPrecomp // PrecompiledContractsApricotPhase2 contains the default set of pre-compiled Ethereum // contracts used in the Apricot Phase 2 release. -var PrecompiledContractsApricotPhase2 = map[common.Address]precompile.StatefulPrecompiledContract{ +var PrecompiledContractsApricotPhase2 = map[common.Address]contract.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), @@ -110,7 +110,7 @@ var PrecompiledContractsApricotPhase2 = map[common.Address]precompile.StatefulPr // PrecompiledContractsApricotPhasePre6 contains the default set of pre-compiled Ethereum // contracts used in the PrecompiledContractsApricotPhasePre6 release. -var PrecompiledContractsApricotPhasePre6 = map[common.Address]precompile.StatefulPrecompiledContract{ +var PrecompiledContractsApricotPhasePre6 = map[common.Address]contract.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), @@ -127,7 +127,7 @@ var PrecompiledContractsApricotPhasePre6 = map[common.Address]precompile.Statefu // PrecompiledContractsApricotPhase6 contains the default set of pre-compiled Ethereum // contracts used in the Apricot Phase 6 release. -var PrecompiledContractsApricotPhase6 = map[common.Address]precompile.StatefulPrecompiledContract{ +var PrecompiledContractsApricotPhase6 = map[common.Address]contract.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), @@ -144,7 +144,7 @@ var PrecompiledContractsApricotPhase6 = map[common.Address]precompile.StatefulPr // PrecompiledContractsBanff contains the default set of pre-compiled Ethereum // contracts used in the Banff release. -var PrecompiledContractsBanff = map[common.Address]precompile.StatefulPrecompiledContract{ +var PrecompiledContractsBanff = map[common.Address]contract.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), @@ -208,24 +208,10 @@ func init() { // Ensure that this package will panic during init if there is a conflict present with the declared // precompile addresses. - for _, k := range precompile.UsedAddresses { - if _, ok := PrecompileAllNativeAddresses[k]; ok { - panic(fmt.Errorf("precompile address collides with existing native address: %s", k)) - } - if k == constants.BlackholeAddr { - panic(fmt.Errorf("cannot use address %s for stateful precompile - overlaps with blackhole address", k)) - } - - // check that [k] belongs to at least one ReservedRange - found := false - for _, reservedRange := range precompile.ReservedRanges { - if reservedRange.Contains(k) { - found = true - break - } - } - if !found { - panic(fmt.Errorf("address %s used for stateful precompile but not specified in any reserved range", k)) + for _, module := range modules.RegisteredModules() { + address := module.Address + if _, ok := PrecompileAllNativeAddresses[address]; ok { + panic(fmt.Errorf("precompile address collides with existing native address: %s", address)) } } } diff --git a/core/vm/contracts_stateful.go b/core/vm/contracts_stateful.go index 096026e537..5259f6e015 100644 --- a/core/vm/contracts_stateful.go +++ b/core/vm/contracts_stateful.go @@ -4,25 +4,8 @@ package vm import ( - "fmt" - "math/big" - - "github.com/ava-labs/coreth/precompile" - "github.com/ava-labs/coreth/vmerrs" + "github.com/ava-labs/coreth/precompile/contract" "github.com/ethereum/go-ethereum/common" - "github.com/holiman/uint256" -) - -// PrecompiledContractsApricot contains the default set of pre-compiled Ethereum -// contracts used in the Istanbul release and the stateful precompiled contracts -// added for the Avalanche Apricot release. -// Apricot is incompatible with the YoloV3 Release since it does not include the -// BLS12-381 Curve Operations added to the set of precompiled contracts - -var ( - genesisContractAddr = common.HexToAddress("0x0100000000000000000000000000000000000000") - NativeAssetBalanceAddr = common.HexToAddress("0x0100000000000000000000000000000000000001") - NativeAssetCallAddr = common.HexToAddress("0x0100000000000000000000000000000000000002") ) // wrappedPrecompiledContract implements StatefulPrecompiledContract by wrapping stateless native precompiled contracts @@ -31,103 +14,16 @@ type wrappedPrecompiledContract struct { p PrecompiledContract } -func newWrappedPrecompiledContract(p PrecompiledContract) precompile.StatefulPrecompiledContract { +func newWrappedPrecompiledContract(p PrecompiledContract) contract.StatefulPrecompiledContract { return &wrappedPrecompiledContract{p: p} } // Run implements the StatefulPrecompiledContract interface -func (w *wrappedPrecompiledContract) Run(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { +func (w *wrappedPrecompiledContract) Run(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { return RunPrecompiledContract(w.p, input, suppliedGas) } // RunStatefulPrecompiledContract confirms runs [precompile] with the specified parameters. -func RunStatefulPrecompiledContract(precompile precompile.StatefulPrecompiledContract, accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { +func RunStatefulPrecompiledContract(precompile contract.StatefulPrecompiledContract, accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { return precompile.Run(accessibleState, caller, addr, input, suppliedGas, readOnly) } - -// nativeAssetBalance is a precompiled contract used to retrieve the native asset balance -type nativeAssetBalance struct { - gasCost uint64 -} - -// PackNativeAssetBalanceInput packs the arguments into the required input data for a transaction to be passed into -// the native asset balance precompile. -func PackNativeAssetBalanceInput(address common.Address, assetID common.Hash) []byte { - input := make([]byte, 52) - copy(input, address.Bytes()) - copy(input[20:], assetID.Bytes()) - return input -} - -// UnpackNativeAssetBalanceInput attempts to unpack [input] into the arguments to the native asset balance precompile -func UnpackNativeAssetBalanceInput(input []byte) (common.Address, common.Hash, error) { - if len(input) != 52 { - return common.Address{}, common.Hash{}, fmt.Errorf("native asset balance input had unexpcted length %d", len(input)) - } - address := common.BytesToAddress(input[:20]) - assetID := common.Hash{} - assetID.SetBytes(input[20:52]) - return address, assetID, nil -} - -// Run implements StatefulPrecompiledContract -func (b *nativeAssetBalance) Run(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - // input: encodePacked(address 20 bytes, assetID 32 bytes) - if suppliedGas < b.gasCost { - return nil, 0, vmerrs.ErrOutOfGas - } - remainingGas = suppliedGas - b.gasCost - - address, assetID, err := UnpackNativeAssetBalanceInput(input) - if err != nil { - return nil, remainingGas, vmerrs.ErrExecutionReverted - } - - res, overflow := uint256.FromBig(accessibleState.GetStateDB().GetBalanceMultiCoin(address, assetID)) - if overflow { - return nil, remainingGas, vmerrs.ErrExecutionReverted - } - return common.LeftPadBytes(res.Bytes(), 32), remainingGas, nil -} - -// nativeAssetCall atomically transfers a native asset to a recipient address as well as calling that -// address -type nativeAssetCall struct { - gasCost uint64 -} - -// PackNativeAssetCallInput packs the arguments into the required input data for a transaction to be passed into -// the native asset precompile. -// Assumes that [assetAmount] is non-nil. -func PackNativeAssetCallInput(address common.Address, assetID common.Hash, assetAmount *big.Int, callData []byte) []byte { - input := make([]byte, 84+len(callData)) - copy(input[0:20], address.Bytes()) - copy(input[20:52], assetID.Bytes()) - assetAmount.FillBytes(input[52:84]) - copy(input[84:], callData) - return input -} - -// UnpackNativeAssetCallInput attempts to unpack [input] into the arguments to the native asset call precompile -func UnpackNativeAssetCallInput(input []byte) (common.Address, common.Hash, *big.Int, []byte, error) { - if len(input) < 84 { - return common.Address{}, common.Hash{}, nil, nil, fmt.Errorf("native asset call input had unexpected length %d", len(input)) - } - to := common.BytesToAddress(input[:20]) - assetID := common.BytesToHash(input[20:52]) - assetAmount := new(big.Int).SetBytes(input[52:84]) - callData := input[84:] - return to, assetID, assetAmount, callData, nil -} - -// Run implements StatefulPrecompiledContract -func (c *nativeAssetCall) Run(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - // input: encodePacked(address 20 bytes, assetID 32 bytes, assetAmount 32 bytes, callData variable length bytes) - return accessibleState.NativeAssetCall(caller, input, suppliedGas, c.gasCost, readOnly) -} - -type deprecatedContract struct{} - -func (*deprecatedContract) Run(accessibleState precompile.PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - return nil, suppliedGas, vmerrs.ErrExecutionReverted -} diff --git a/core/vm/contracts_stateful_native_asset.go b/core/vm/contracts_stateful_native_asset.go new file mode 100644 index 0000000000..6791efe2e7 --- /dev/null +++ b/core/vm/contracts_stateful_native_asset.go @@ -0,0 +1,113 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package vm + +import ( + "fmt" + "math/big" + + "github.com/ava-labs/coreth/precompile/contract" + "github.com/ava-labs/coreth/vmerrs" + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" +) + +// PrecompiledContractsApricot contains the default set of pre-compiled Ethereum +// contracts used in the Istanbul release and the stateful precompiled contracts +// added for the Avalanche Apricot release. +// Apricot is incompatible with the YoloV3 Release since it does not include the +// BLS12-381 Curve Operations added to the set of precompiled contracts + +var ( + genesisContractAddr = common.HexToAddress("0x0100000000000000000000000000000000000000") + NativeAssetBalanceAddr = common.HexToAddress("0x0100000000000000000000000000000000000001") + NativeAssetCallAddr = common.HexToAddress("0x0100000000000000000000000000000000000002") +) + +// nativeAssetBalance is a precompiled contract used to retrieve the native asset balance +type nativeAssetBalance struct { + gasCost uint64 +} + +// PackNativeAssetBalanceInput packs the arguments into the required input data for a transaction to be passed into +// the native asset balance contract. +func PackNativeAssetBalanceInput(address common.Address, assetID common.Hash) []byte { + input := make([]byte, 52) + copy(input, address.Bytes()) + copy(input[20:], assetID.Bytes()) + return input +} + +// UnpackNativeAssetBalanceInput attempts to unpack [input] into the arguments to the native asset balance precompile +func UnpackNativeAssetBalanceInput(input []byte) (common.Address, common.Hash, error) { + if len(input) != 52 { + return common.Address{}, common.Hash{}, fmt.Errorf("native asset balance input had unexpcted length %d", len(input)) + } + address := common.BytesToAddress(input[:20]) + assetID := common.Hash{} + assetID.SetBytes(input[20:52]) + return address, assetID, nil +} + +// Run implements StatefulPrecompiledContract +func (b *nativeAssetBalance) Run(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + // input: encodePacked(address 20 bytes, assetID 32 bytes) + if suppliedGas < b.gasCost { + return nil, 0, vmerrs.ErrOutOfGas + } + remainingGas = suppliedGas - b.gasCost + + address, assetID, err := UnpackNativeAssetBalanceInput(input) + if err != nil { + return nil, remainingGas, vmerrs.ErrExecutionReverted + } + + res, overflow := uint256.FromBig(accessibleState.GetStateDB().GetBalanceMultiCoin(address, assetID)) + if overflow { + return nil, remainingGas, vmerrs.ErrExecutionReverted + } + return common.LeftPadBytes(res.Bytes(), 32), remainingGas, nil +} + +// nativeAssetCall atomically transfers a native asset to a recipient address as well as calling that +// address +type nativeAssetCall struct { + gasCost uint64 +} + +// PackNativeAssetCallInput packs the arguments into the required input data for a transaction to be passed into +// the native asset contract. +// Assumes that [assetAmount] is non-nil. +func PackNativeAssetCallInput(address common.Address, assetID common.Hash, assetAmount *big.Int, callData []byte) []byte { + input := make([]byte, 84+len(callData)) + copy(input[0:20], address.Bytes()) + copy(input[20:52], assetID.Bytes()) + assetAmount.FillBytes(input[52:84]) + copy(input[84:], callData) + return input +} + +// UnpackNativeAssetCallInput attempts to unpack [input] into the arguments to the native asset call precompile +func UnpackNativeAssetCallInput(input []byte) (common.Address, common.Hash, *big.Int, []byte, error) { + if len(input) < 84 { + return common.Address{}, common.Hash{}, nil, nil, fmt.Errorf("native asset call input had unexpected length %d", len(input)) + } + to := common.BytesToAddress(input[:20]) + assetID := common.BytesToHash(input[20:52]) + assetAmount := new(big.Int).SetBytes(input[52:84]) + callData := input[84:] + return to, assetID, assetAmount, callData, nil +} + +// Run implements StatefulPrecompiledContract +func (c *nativeAssetCall) Run(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + // input: encodePacked(address 20 bytes, assetID 32 bytes, assetAmount 32 bytes, callData variable length bytes) + return accessibleState.NativeAssetCall(caller, input, suppliedGas, c.gasCost, readOnly) +} + +type deprecatedContract struct{} + +func (*deprecatedContract) Run(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + return nil, suppliedGas, vmerrs.ErrExecutionReverted +} diff --git a/core/vm/evm.go b/core/vm/evm.go index d0b861ad46..d0abe36189 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -31,9 +31,13 @@ import ( "sync/atomic" "time" + "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/coreth/constants" "github.com/ava-labs/coreth/params" - "github.com/ava-labs/coreth/precompile" + "github.com/ava-labs/coreth/precompile/contract" + "github.com/ava-labs/coreth/precompile/modules" + "github.com/ava-labs/coreth/precompile/precompileconfig" + "github.com/ava-labs/coreth/predicate" "github.com/ava-labs/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -41,8 +45,8 @@ import ( ) var ( - _ precompile.PrecompileAccessibleState = &EVM{} - _ precompile.BlockContext = &BlockContext{} + _ contract.AccessibleState = &EVM{} + _ contract.BlockContext = &BlockContext{} ) // IsProhibited returns true if [addr] is the blackhole address or is @@ -51,12 +55,8 @@ func IsProhibited(addr common.Address) bool { if addr == constants.BlackholeAddr { return true } - for _, reservedRange := range precompile.ReservedRanges { - if reservedRange.Contains(addr) { - return true - } - } - return false + + return modules.ReservedAddress(addr) } // emptyCodeHash is used by create to ensure deployment is disallowed to already @@ -75,8 +75,8 @@ type ( GetHashFunc func(uint64) common.Hash ) -func (evm *EVM) precompile(addr common.Address) (precompile.StatefulPrecompiledContract, bool) { - var precompiles map[common.Address]precompile.StatefulPrecompiledContract +func (evm *EVM) precompile(addr common.Address) (contract.StatefulPrecompiledContract, bool) { + var precompiles map[common.Address]contract.StatefulPrecompiledContract switch { case evm.chainRules.IsBanff: precompiles = PrecompiledContractsBanff @@ -101,8 +101,12 @@ func (evm *EVM) precompile(addr common.Address) (precompile.StatefulPrecompiledC } // Otherwise, check the chain rules for the additionally configured precompiles. - p, ok = evm.chainRules.Precompiles[addr] - return p, ok + if _, ok = evm.chainRules.ActivePrecompiles[addr]; ok { + module, ok := modules.GetPrecompileModuleByAddress(addr) + return module.Contract, ok + } + + return nil, false } // BlockContext provides the EVM with auxiliary information. Once provided @@ -120,6 +124,9 @@ type BlockContext struct { TransferMultiCoin TransferMCFunc // GetHash returns the hash corresponding to n GetHash GetHashFunc + // PredicateResults are the results of predicate verification available throughout the EVM's execution. + // PredicateResults may be nil if it is not encoded in the block's header. + PredicateResults *predicate.Results // Block information Coinbase common.Address // Provides information for COINBASE @@ -138,6 +145,13 @@ func (b *BlockContext) Timestamp() uint64 { return b.Time } +func (b *BlockContext) GetPredicateResults(txHash common.Hash, address common.Address) []byte { + if b.PredicateResults == nil { + return nil + } + return b.PredicateResults.GetResults(txHash, address) +} + // TxContext provides the EVM with information about a transaction. // All fields can change between transactions. type TxContext struct { @@ -215,13 +229,18 @@ func (evm *EVM) Cancelled() bool { return evm.abort.Load() } +// GetSnowContext returns the evm's snow.Context. +func (evm *EVM) GetSnowContext() *snow.Context { + return evm.chainConfig.AvalancheContext.SnowCtx +} + // GetStateDB returns the evm's StateDB -func (evm *EVM) GetStateDB() precompile.StateDB { +func (evm *EVM) GetStateDB() contract.StateDB { return evm.StateDB } // GetBlockContext returns the evm's BlockContext -func (evm *EVM) GetBlockContext() precompile.BlockContext { +func (evm *EVM) GetBlockContext() contract.BlockContext { return &evm.Context } @@ -685,6 +704,9 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment * // ChainConfig returns the environment's chain configuration func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } +// GetChainConfig implements AccessibleState +func (evm *EVM) GetChainConfig() precompileconfig.ChainConfig { return evm.chainConfig } + func (evm *EVM) NativeAssetCall(caller common.Address, input []byte, suppliedGas uint64, gasCost uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { if suppliedGas < gasCost { return nil, 0, vmerrs.ErrOutOfGas diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go index 021dc5700c..47da4a26d6 100644 --- a/core/vm/evm_test.go +++ b/core/vm/evm_test.go @@ -16,9 +16,15 @@ func TestIsProhibited(t *testing.T) { assert.True(t, IsProhibited(common.HexToAddress("0x0100000000000000000000000000000000000010"))) assert.True(t, IsProhibited(common.HexToAddress("0x01000000000000000000000000000000000000f0"))) assert.True(t, IsProhibited(common.HexToAddress("0x01000000000000000000000000000000000000ff"))) + assert.True(t, IsProhibited(common.HexToAddress("0x0200000000000000000000000000000000000000"))) + assert.True(t, IsProhibited(common.HexToAddress("0x02000000000000000000000000000000000000ff"))) + assert.True(t, IsProhibited(common.HexToAddress("0x0300000000000000000000000000000000000000"))) + assert.True(t, IsProhibited(common.HexToAddress("0x03000000000000000000000000000000000000ff"))) // allowed for use assert.False(t, IsProhibited(common.HexToAddress("0x00000000000000000000000000000000000000ff"))) + assert.False(t, IsProhibited(common.HexToAddress("0x00ffffffffffffffffffffffffffffffffffffff"))) assert.False(t, IsProhibited(common.HexToAddress("0x0100000000000000000000000000000000000100"))) - assert.False(t, IsProhibited(common.HexToAddress("0x0200000000000000000000000000000000000000"))) + assert.False(t, IsProhibited(common.HexToAddress("0x0200000000000000000000000000000000000100"))) + assert.False(t, IsProhibited(common.HexToAddress("0x0300000000000000000000000000000000000100"))) } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 951b7214c9..fb549dc32f 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -29,7 +29,6 @@ package vm import ( "errors" - "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/vmerrs" "github.com/ethereum/go-ethereum/common" @@ -914,14 +913,14 @@ func makeLog(size int) executionFunc { } d := scope.Memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64())) - interpreter.evm.StateDB.AddLog(&types.Log{ - Address: scope.Contract.Address(), - Topics: topics, - Data: d, + interpreter.evm.StateDB.AddLog( + scope.Contract.Address(), + topics, + d, // This is a non-consensus field, but assigned here because // core/state doesn't know the current block number. - BlockNumber: interpreter.evm.Context.BlockNumber.Uint64(), - }) + interpreter.evm.Context.BlockNumber.Uint64(), + ) return nil, nil } diff --git a/core/vm/interface.go b/core/vm/interface.go index 8546aea534..d2553b0ba7 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -68,6 +68,7 @@ type StateDB interface { Suicide(common.Address) bool HasSuicided(common.Address) bool + Finalise(deleteEmptyObjects bool) // Exist reports whether the given account exists in state. // Notably this should also return true for suicided accounts. @@ -89,7 +90,13 @@ type StateDB interface { RevertToSnapshot(int) Snapshot() int - AddLog(*types.Log) + AddLog(addr common.Address, topics []common.Hash, data []byte, blockNumber uint64) + GetLogData() [][]byte + GetPredicateStorageSlots(address common.Address, index int) ([]byte, bool) + SetPredicateStorageSlots(address common.Address, predicates [][]byte) + + GetTxHash() common.Hash + AddPreimage(common.Hash, []byte) } diff --git a/miner/miner.go b/miner/miner.go index 23fb222116..b6d02fedf4 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -34,6 +34,7 @@ import ( "github.com/ava-labs/coreth/core/txpool" "github.com/ava-labs/coreth/core/types" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/precompile/precompileconfig" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" ) @@ -63,8 +64,8 @@ func (miner *Miner) SetEtherbase(addr common.Address) { miner.worker.setEtherbase(addr) } -func (miner *Miner) GenerateBlock() (*types.Block, error) { - return miner.worker.commitNewWork() +func (miner *Miner) GenerateBlock(predicateContext *precompileconfig.PredicateContext) (*types.Block, error) { + return miner.worker.commitNewWork(predicateContext) } // SubscribePendingLogs starts delivering logs from pending transactions diff --git a/miner/worker.go b/miner/worker.go index 60a75345f9..e9617a44a4 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -43,7 +43,10 @@ import ( "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/core/state" "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/core/vm" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/precompile/precompileconfig" + "github.com/ava-labs/coreth/predicate" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" @@ -69,6 +72,14 @@ type environment struct { receipts []*types.Receipt size uint64 + rules params.Rules + predicateContext *precompileconfig.PredicateContext + // predicateResults contains the results of checking the predicates for each transaction in the miner. + // The results are accumulated as transactions are executed by the miner and set on the BlockContext. + // If a transaction is dropped, its results must explicitly be removed from predicateResults in the same + // way that the gas pool and state is reset. + predicateResults *predicate.Results + start time.Time // Time that block building began } @@ -115,7 +126,7 @@ func (w *worker) setEtherbase(addr common.Address) { } // commitNewWork generates several new sealing tasks based on the parent block. -func (w *worker) commitNewWork() (*types.Block, error) { +func (w *worker) commitNewWork(predicateContext *precompileconfig.PredicateContext) (*types.Block, error) { w.mu.RLock() defer w.mu.RUnlock() @@ -162,12 +173,10 @@ func (w *worker) commitNewWork() (*types.Block, error) { return nil, fmt.Errorf("failed to prepare header for mining: %w", err) } - // Instantiate stateDB and kick off prefetcher. - env, err := w.createCurrentEnvironment(parent, header, tstart) + env, err := w.createCurrentEnvironment(predicateContext, parent, header, tstart) if err != nil { return nil, fmt.Errorf("failed to create new current environment: %w", err) } - // Ensure we always stop prefetcher after block building is complete. defer func() { if env.state == nil { @@ -175,9 +184,12 @@ func (w *worker) commitNewWork() (*types.Block, error) { } env.state.StopPrefetcher() }() - - // Configure any stateful precompiles that should go into effect during this block. - w.chainConfig.CheckConfigurePrecompiles(&parent.Time, types.NewBlockWithHeader(header), env.state) + // Configure any upgrades that should go into effect during this block. + err = core.ApplyUpgrades(w.chainConfig, &parent.Time, types.NewBlockWithHeader(header), env.state) + if err != nil { + log.Error("failed to configure precompiles mining new block", "parent", parent.Hash(), "number", header.Number, "timestamp", header.Time, "err", err) + return nil, err + } // Fill the block with all available pending transactions. pending := w.eth.TxPool().Pending(true) @@ -203,35 +215,51 @@ func (w *worker) commitNewWork() (*types.Block, error) { return w.commit(env) } -func (w *worker) createCurrentEnvironment(parent *types.Header, header *types.Header, tstart time.Time) (*environment, error) { - // Retrieve the parent state to apply transactions to and kickoff prefetcher, - // which will retrieve intermediate nodes for all modified state. +func (w *worker) createCurrentEnvironment(predicateContext *precompileconfig.PredicateContext, parent *types.Header, header *types.Header, tstart time.Time) (*environment, error) { state, err := w.chain.StateAt(parent.Root) if err != nil { return nil, err } state.StartPrefetcher("miner", w.eth.BlockChain().CacheConfig().TriePrefetcherParallelism) return &environment{ - signer: types.MakeSigner(w.chainConfig, header.Number, header.Time), - state: state, - parent: parent, - header: header, - tcount: 0, - gasPool: new(core.GasPool).AddGas(header.GasLimit), - start: tstart, + signer: types.MakeSigner(w.chainConfig, header.Number, header.Time), + state: state, + parent: parent, + header: header, + tcount: 0, + gasPool: new(core.GasPool).AddGas(header.GasLimit), + rules: w.chainConfig.AvalancheRules(header.Number, header.Time), + predicateContext: predicateContext, + predicateResults: predicate.NewResults(), + start: tstart, }, nil } func (w *worker) commitTransaction(env *environment, tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) { var ( - snap = env.state.Snapshot() - gp = env.gasPool.Gas() + snap = env.state.Snapshot() + gp = env.gasPool.Gas() + blockContext vm.BlockContext ) - receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *w.chain.GetVMConfig()) + if env.rules.IsDUpgrade { + results, err := core.CheckPredicates(env.rules, env.predicateContext, tx) + if err != nil { + log.Debug("Transaction predicate failed verification in miner", "tx", tx.Hash(), "err", err) + return nil, err + } + env.predicateResults.SetTxResults(tx.Hash(), results) + + blockContext = core.NewEVMBlockContextWithPredicateResults(env.header, w.chain, &coinbase, env.predicateResults) + } else { + blockContext = core.NewEVMBlockContext(env.header, w.chain, &coinbase) + } + + receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, blockContext, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *w.chain.GetVMConfig()) if err != nil { env.state.RevertToSnapshot(snap) env.gasPool.SetGas(gp) + env.predicateResults.DeleteTxResults(tx.Hash()) return nil, err } env.txs = append(env.txs, tx) @@ -299,6 +327,13 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP // commit runs any post-transaction state modifications, assembles the final block // and commits new work if consensus engine is running. func (w *worker) commit(env *environment) (*types.Block, error) { + if env.rules.IsDUpgrade { + predicateResultsBytes, err := env.predicateResults.Bytes() + if err != nil { + return nil, fmt.Errorf("failed to marshal predicate results: %w", err) + } + env.header.Extra = append(env.header.Extra, predicateResultsBytes...) + } // Deep copy receipts here to avoid interaction between different tasks. receipts := copyReceipts(env.receipts) block, err := w.engine.FinalizeAndAssemble(w.chain, env.header, env.parent, env.state, env.txs, nil, receipts) diff --git a/params/avalanche_params.go b/params/avalanche_params.go index 3597e0c1c4..d0a51ef293 100644 --- a/params/avalanche_params.go +++ b/params/avalanche_params.go @@ -21,7 +21,6 @@ const ( ApricotPhase1GasLimit uint64 = 8_000_000 CortinaGasLimit uint64 = 15_000_000 - ApricotPhase3ExtraDataSize uint64 = 80 ApricotPhase3MinBaseFee int64 = 75_000_000_000 ApricotPhase3MaxBaseFee int64 = 225_000_000_000 ApricotPhase3InitialBaseFee int64 = 225_000_000_000 @@ -32,6 +31,9 @@ const ( ApricotPhase5TargetGas uint64 = 15_000_000 ApricotPhase5BaseFeeChangeDenominator uint64 = 36 + DynamicFeeExtraDataSize = 80 + RollupWindow uint64 = 10 + // The base cost to charge per atomic transaction. Added in Apricot Phase 5. AtomicTxBaseCost uint64 = 10_000 diff --git a/params/config.go b/params/config.go index 7edad0aacc..6b362c14de 100644 --- a/params/config.go +++ b/params/config.go @@ -32,7 +32,9 @@ import ( "math/big" "time" - "github.com/ava-labs/coreth/precompile" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/coreth/precompile/modules" + "github.com/ava-labs/coreth/precompile/precompileconfig" "github.com/ava-labs/coreth/utils" "github.com/ethereum/go-ethereum/common" ) @@ -132,7 +134,7 @@ var ( } TestChainConfig = &ChainConfig{ - AvalancheContext: AvalancheContext{common.Hash{1}}, + AvalancheContext: AvalancheContext{snow.DefaultContextTest()}, ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, @@ -159,7 +161,7 @@ var ( } TestLaunchConfig = &ChainConfig{ - AvalancheContext: AvalancheContext{common.Hash{1}}, + AvalancheContext: AvalancheContext{snow.DefaultContextTest()}, ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, @@ -186,7 +188,7 @@ var ( } TestApricotPhase1Config = &ChainConfig{ - AvalancheContext: AvalancheContext{common.Hash{1}}, + AvalancheContext: AvalancheContext{snow.DefaultContextTest()}, ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, @@ -213,7 +215,7 @@ var ( } TestApricotPhase2Config = &ChainConfig{ - AvalancheContext: AvalancheContext{common.Hash{1}}, + AvalancheContext: AvalancheContext{snow.DefaultContextTest()}, ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, @@ -240,7 +242,7 @@ var ( } TestApricotPhase3Config = &ChainConfig{ - AvalancheContext: AvalancheContext{common.Hash{1}}, + AvalancheContext: AvalancheContext{snow.DefaultContextTest()}, ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, @@ -267,7 +269,7 @@ var ( } TestApricotPhase4Config = &ChainConfig{ - AvalancheContext: AvalancheContext{common.Hash{1}}, + AvalancheContext: AvalancheContext{snow.DefaultContextTest()}, ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, @@ -294,7 +296,7 @@ var ( } TestApricotPhase5Config = &ChainConfig{ - AvalancheContext: AvalancheContext{common.Hash{1}}, + AvalancheContext: AvalancheContext{snow.DefaultContextTest()}, ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, @@ -321,7 +323,7 @@ var ( } TestApricotPhasePre6Config = &ChainConfig{ - AvalancheContext: AvalancheContext{common.Hash{1}}, + AvalancheContext: AvalancheContext{snow.DefaultContextTest()}, ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, @@ -348,7 +350,7 @@ var ( } TestApricotPhase6Config = &ChainConfig{ - AvalancheContext: AvalancheContext{common.Hash{1}}, + AvalancheContext: AvalancheContext{snow.DefaultContextTest()}, ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, @@ -375,7 +377,7 @@ var ( } TestApricotPhasePost6Config = &ChainConfig{ - AvalancheContext: AvalancheContext{common.Hash{1}}, + AvalancheContext: AvalancheContext{snow.DefaultContextTest()}, ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, @@ -402,7 +404,7 @@ var ( } TestBanffChainConfig = &ChainConfig{ - AvalancheContext: AvalancheContext{common.Hash{1}}, + AvalancheContext: AvalancheContext{snow.DefaultContextTest()}, ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, @@ -429,7 +431,7 @@ var ( } TestCortinaChainConfig = &ChainConfig{ - AvalancheContext: AvalancheContext{common.Hash{1}}, + AvalancheContext: AvalancheContext{snow.DefaultContextTest()}, ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, @@ -456,7 +458,7 @@ var ( } TestDUpgradeChainConfig = &ChainConfig{ - AvalancheContext: AvalancheContext{common.Hash{1}}, + AvalancheContext: AvalancheContext{snow.DefaultContextTest()}, ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, @@ -484,6 +486,19 @@ var ( TestRules = TestChainConfig.AvalancheRules(new(big.Int), 0) ) +// UpgradeConfig includes the following configs that may be specified in upgradeBytes: +// - Timestamps that enable avalanche network upgrades, +// - Enabling or disabling precompiles as network upgrades. +type UpgradeConfig struct { + // Config for enabling and disabling precompiles as network upgrades. + PrecompileUpgrades []PrecompileUpgrade `json:"precompileUpgrades,omitempty"` +} + +// AvalancheContext provides Avalanche specific context directly into the EVM. +type AvalancheContext struct { + SnowCtx *snow.Context +} + // ChainConfig is the core config which determines the blockchain settings. // // ChainConfig is stored in the database on a per block basis. This means @@ -537,11 +552,8 @@ type ChainConfig struct { DUpgradeBlockTimestamp *uint64 `json:"dUpgradeBlockTimestamp,omitempty"` // Cancun activates the Cancun upgrade from Ethereum. (nil = no fork, 0 = already activated) CancunTime *uint64 `json:"cancunTime,omitempty"` -} -// AvalancheContext provides Avalanche specific context directly into the EVM. -type AvalancheContext struct { - BlockchainID common.Hash + UpgradeConfig `json:"-"` // Config specified in upgradeBytes (avalanche network upgrades or enable/disabling precompiles). Skip encoding/decoding directly into ChainConfig. } // Description returns a human-readable description of ChainConfig. @@ -711,6 +723,21 @@ func (c *ChainConfig) IsCancun(time uint64) bool { return utils.IsTimestampForked(c.CancunTime, time) } +func (r *Rules) PredicatersExist() bool { + return len(r.Predicaters) > 0 +} + +func (r *Rules) PredicaterExists(addr common.Address) bool { + _, PredicaterExists := r.Predicaters[addr] + return PredicaterExists +} + +// IsPrecompileEnabled returns whether precompile with [address] is enabled at [timestamp]. +func (c *ChainConfig) IsPrecompileEnabled(address common.Address, timestamp uint64) bool { + config := c.getActivePrecompileConfig(address, timestamp) + return config != nil && !config.IsDisabled() +} + // CheckCompatible checks whether scheduled fork transitions have been imported // with a mismatching chain configuration. func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, time uint64) *ConfigCompatError { @@ -736,6 +763,16 @@ func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, time u return lasterr } +// Verify verifies chain config and returns error +func (c *ChainConfig) Verify() error { + // Verify the precompile upgrades are internally consistent given the existing chainConfig. + if err := c.verifyPrecompileUpgrades(); err != nil { + return fmt.Errorf("invalid precompile upgrades: %w", err) + } + + return nil +} + // CheckConfigForkOrder checks that we don't "skip" any forks, geth isn't pluggable enough // to guarantee that forks can be implemented in a different order than on official networks func (c *ChainConfig) CheckConfigForkOrder() error { @@ -1026,11 +1063,17 @@ type Rules struct { IsCortina bool IsDUpgrade bool - // Precompiles maps addresses to stateful precompiled contracts that are enabled + // ActivePrecompiles maps addresses to stateful precompiled contracts that are enabled // for this rule set. // Note: none of these addresses should conflict with the address space used by // any existing precompiles. - Precompiles map[common.Address]precompile.StatefulPrecompiledContract + ActivePrecompiles map[common.Address]precompileconfig.Config + // Predicaters maps addresses to stateful precompile Predicaters + // that are enabled for this rule set. + Predicaters map[common.Address]precompileconfig.Predicater + // AccepterPrecompiles map addresses to stateful precompile accepter functions + // that are enabled for this rule set. + AccepterPrecompiles map[common.Address]precompileconfig.Accepter } // Rules ensures c's ChainID is not nil. @@ -1071,35 +1114,26 @@ func (c *ChainConfig) AvalancheRules(blockNum *big.Int, timestamp uint64) Rules rules.IsDUpgrade = c.IsDUpgrade(timestamp) // Initialize the stateful precompiles that should be enabled at [blockTimestamp]. - rules.Precompiles = make(map[common.Address]precompile.StatefulPrecompiledContract) - for _, config := range c.enabledStatefulPrecompiles() { - if utils.IsTimestampForked(config.Timestamp(), timestamp) { - rules.Precompiles[config.Address()] = config.Contract() + rules.ActivePrecompiles = make(map[common.Address]precompileconfig.Config) + rules.Predicaters = make(map[common.Address]precompileconfig.Predicater) + rules.AccepterPrecompiles = make(map[common.Address]precompileconfig.Accepter) + for _, module := range modules.RegisteredModules() { + if config := c.getActivePrecompileConfig(module.Address, timestamp); config != nil && !config.IsDisabled() { + rules.ActivePrecompiles[module.Address] = config + if predicater, ok := config.(precompileconfig.Predicater); ok { + rules.Predicaters[module.Address] = predicater + } + if precompileAccepter, ok := config.(precompileconfig.Accepter); ok { + rules.AccepterPrecompiles[module.Address] = precompileAccepter + } } } return rules } -// enabledStatefulPrecompiles returns a list of stateful precompile configs in the order that they are enabled -// by block timestamp. -// Note: the return value does not include the native precompiles [nativeAssetCall] and [nativeAssetBalance]. -// These are handled in [evm.precompile] directly. -func (c *ChainConfig) enabledStatefulPrecompiles() []precompile.StatefulPrecompileConfig { - statefulPrecompileConfigs := make([]precompile.StatefulPrecompileConfig, 0) - - return statefulPrecompileConfigs -} - -// CheckConfigurePrecompiles checks if any of the precompiles specified in the chain config are enabled by the block -// transition from [parentTimestamp] to the timestamp set in [blockContext]. If this is the case, it calls [Configure] -// to apply the necessary state transitions for the upgrade. -// This function is called: -// - within genesis setup to configure the starting state for precompiles enabled at genesis, -// - during block processing to update the state before processing the given block. -func (c *ChainConfig) CheckConfigurePrecompiles(parentTimestamp *uint64, blockContext precompile.BlockContext, statedb precompile.StateDB) { - // Iterate the enabled stateful precompiles and configure them if needed - for _, config := range c.enabledStatefulPrecompiles() { - precompile.CheckConfigure(c, parentTimestamp, blockContext, config, statedb) - } +// IsPrecompileEnabled returns true if the precompile at [addr] is enabled for this rule set. +func (r *Rules) IsPrecompileEnabled(addr common.Address) bool { + _, ok := r.ActivePrecompiles[addr] + return ok } diff --git a/params/precompile_upgrade.go b/params/precompile_upgrade.go new file mode 100644 index 0000000000..8130db5d52 --- /dev/null +++ b/params/precompile_upgrade.go @@ -0,0 +1,234 @@ +// (c) 2023 Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package params + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/ava-labs/coreth/precompile/modules" + "github.com/ava-labs/coreth/precompile/precompileconfig" + "github.com/ava-labs/coreth/utils" + "github.com/ethereum/go-ethereum/common" +) + +var errNoKey = errors.New("PrecompileUpgrade cannot be empty") + +// PrecompileUpgrade is a helper struct embedded in UpgradeConfig. +// It is used to unmarshal the json into the correct precompile config type +// based on the key. Keys are defined in each precompile module, and registered in +// precompile/registry/registry.go. +type PrecompileUpgrade struct { + precompileconfig.Config +} + +// UnmarshalJSON unmarshals the json into the correct precompile config type +// based on the key. Keys are defined in each precompile module, and registered in +// precompile/registry/registry.go. +// Ex: {"feeManagerConfig": {...}} where "feeManagerConfig" is the key +func (u *PrecompileUpgrade) UnmarshalJSON(data []byte) error { + raw := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + if len(raw) == 0 { + return errNoKey + } + if len(raw) > 1 { + return fmt.Errorf("PrecompileUpgrade must have exactly one key, got %d", len(raw)) + } + for key, value := range raw { + module, ok := modules.GetPrecompileModule(key) + if !ok { + return fmt.Errorf("unknown precompile config: %s", key) + } + config := module.MakeConfig() + if err := json.Unmarshal(value, config); err != nil { + return err + } + u.Config = config + } + return nil +} + +// MarshalJSON marshal the precompile config into json based on the precompile key. +// Ex: {"feeManagerConfig": {...}} where "feeManagerConfig" is the key +func (u *PrecompileUpgrade) MarshalJSON() ([]byte, error) { + res := make(map[string]precompileconfig.Config) + res[u.Key()] = u.Config + return json.Marshal(res) +} + +// 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 +// specified in the chainConfig by genesis. +// - check a precompile is disabled before it is re-enabled +func (c *ChainConfig) verifyPrecompileUpgrades() error { + // Store this struct to keep track of the last upgrade for each precompile key. + // Required for timestamp and disabled checks. + type lastUpgradeData struct { + blockTimestamp uint64 + disabled bool + } + + lastPrecompileUpgrades := make(map[string]lastUpgradeData) + + // next range over upgrades to verify correct use of disabled and blockTimestamps. + // previousUpgradeTimestamp is used to verify monotonically increasing timestamps. + var previousUpgradeTimestamp *uint64 + for i, upgrade := range c.PrecompileUpgrades { + key := upgrade.Key() + + // lastUpgradeByKey is the previous processed upgrade for this precompile key. + lastUpgradeByKey, ok := lastPrecompileUpgrades[key] + var ( + disabled bool + lastTimestamp *uint64 + ) + if !ok { + disabled = true + lastTimestamp = nil + } else { + disabled = lastUpgradeByKey.disabled + lastTimestamp = utils.NewUint64(lastUpgradeByKey.blockTimestamp) + } + upgradeTimestamp := upgrade.Timestamp() + + if upgradeTimestamp == nil { + return fmt.Errorf("PrecompileUpgrade (%s) at [%d]: block timestamp cannot be nil ", key, i) + } + // Verify specified timestamps are monotonically increasing across all precompile keys. + // Note: It is OK for multiple configs of DIFFERENT keys to specify the same timestamp. + if previousUpgradeTimestamp != nil && *upgradeTimestamp < *previousUpgradeTimestamp { + return fmt.Errorf("PrecompileUpgrade (%s) at [%d]: config block timestamp (%v) < previous timestamp (%v)", key, i, *upgradeTimestamp, *previousUpgradeTimestamp) + } + + if disabled == upgrade.IsDisabled() { + return fmt.Errorf("PrecompileUpgrade (%s) at [%d]: disable should be [%v]", key, i, !disabled) + } + // Verify specified timestamps are monotonically increasing across same precompile keys. + // Note: It is NOT OK for multiple configs of the SAME key to specify the same timestamp. + if lastTimestamp != nil && *upgradeTimestamp <= *lastTimestamp { + return fmt.Errorf("PrecompileUpgrade (%s) at [%d]: config block timestamp (%v) <= previous timestamp (%v) of same key", key, i, *upgradeTimestamp, *lastTimestamp) + } + + if err := upgrade.Verify(c); err != nil { + return err + } + + lastPrecompileUpgrades[key] = lastUpgradeData{ + disabled: upgrade.IsDisabled(), + blockTimestamp: *upgradeTimestamp, + } + + previousUpgradeTimestamp = upgradeTimestamp + } + + return nil +} + +// getActivePrecompileConfig returns the most recent precompile config corresponding to [address]. +// If none have occurred, returns nil. +func (c *ChainConfig) getActivePrecompileConfig(address common.Address, timestamp uint64) precompileconfig.Config { + configs := c.GetActivatingPrecompileConfigs(address, nil, timestamp, c.PrecompileUpgrades) + if len(configs) == 0 { + return nil + } + return configs[len(configs)-1] // return the most recent config +} + +// GetActivatingPrecompileConfigs returns all precompile upgrades configured to activate during the +// state transition from a block with timestamp [from] to a block with timestamp [to]. +func (c *ChainConfig) GetActivatingPrecompileConfigs(address common.Address, from *uint64, to uint64, upgrades []PrecompileUpgrade) []precompileconfig.Config { + // Get key from address. + module, ok := modules.GetPrecompileModuleByAddress(address) + if !ok { + return nil + } + configs := make([]precompileconfig.Config, 0) + key := module.ConfigKey + // Loop over all upgrades checking for the requested precompile config. + for _, upgrade := range upgrades { + if upgrade.Key() == key { + // Check if the precompile activates in the specified range. + if utils.IsForkTransition(upgrade.Timestamp(), from, to) { + configs = append(configs, upgrade.Config) + } + } + } + return configs +} + +// CheckPrecompilesCompatible checks if [precompileUpgrades] are compatible with [c] at [headTimestamp]. +// Returns a ConfigCompatError if upgrades already activated at [headTimestamp] are missing from +// [precompileUpgrades]. Upgrades not already activated may be modified or absent from [precompileUpgrades]. +// Returns nil if [precompileUpgrades] is compatible with [c]. +// Assumes given timestamp is the last accepted block timestamp. +// This ensures that as long as the node has not accepted a block with a different rule set it will allow a +// new upgrade to be applied as long as it activates after the last accepted block. +func (c *ChainConfig) CheckPrecompilesCompatible(precompileUpgrades []PrecompileUpgrade, time uint64) *ConfigCompatError { + for _, module := range modules.RegisteredModules() { + if err := c.checkPrecompileCompatible(module.Address, precompileUpgrades, time); err != nil { + return err + } + } + + return nil +} + +// checkPrecompileCompatible verifies that the precompile specified by [address] is compatible between [c] +// and [precompileUpgrades] at [headTimestamp]. +// Returns an error if upgrades already activated at [headTimestamp] are missing from [precompileUpgrades]. +// Upgrades that have already gone into effect cannot be modified or absent from [precompileUpgrades]. +func (c *ChainConfig) checkPrecompileCompatible(address common.Address, precompileUpgrades []PrecompileUpgrade, time uint64) *ConfigCompatError { + // All active upgrades (from nil to [lastTimestamp]) must match. + activeUpgrades := c.GetActivatingPrecompileConfigs(address, nil, time, c.PrecompileUpgrades) + newUpgrades := c.GetActivatingPrecompileConfigs(address, nil, time, precompileUpgrades) + + // Check activated upgrades are still present. + for i, upgrade := range activeUpgrades { + if len(newUpgrades) <= i { + // missing upgrade + return newTimestampCompatError( + fmt.Sprintf("missing PrecompileUpgrade[%d]", i), + upgrade.Timestamp(), + nil, + ) + } + // All upgrades that have activated must be identical. + if !upgrade.Equal(newUpgrades[i]) { + return newTimestampCompatError( + fmt.Sprintf("PrecompileUpgrade[%d]", i), + upgrade.Timestamp(), + newUpgrades[i].Timestamp(), + ) + } + } + // then, make sure newUpgrades does not have additional upgrades + // that are already activated. (cannot perform retroactive upgrade) + if len(newUpgrades) > len(activeUpgrades) { + return newTimestampCompatError( + fmt.Sprintf("cannot retroactively enable PrecompileUpgrade[%d]", len(activeUpgrades)), + nil, + newUpgrades[len(activeUpgrades)].Timestamp(), // this indexes to the first element in newUpgrades after the end of activeUpgrades + ) + } + + return nil +} + +// EnabledStatefulPrecompiles returns current stateful precompile configs that are enabled at [blockTimestamp]. +func (c *ChainConfig) EnabledStatefulPrecompiles(blockTimestamp uint64) Precompiles { + statefulPrecompileConfigs := make(Precompiles) + for _, module := range modules.RegisteredModules() { + if config := c.getActivePrecompileConfig(module.Address, blockTimestamp); config != nil && !config.IsDisabled() { + statefulPrecompileConfigs[module.ConfigKey] = config + } + } + + return statefulPrecompileConfigs +} diff --git a/params/precompiles.go b/params/precompiles.go new file mode 100644 index 0000000000..9b47b219fd --- /dev/null +++ b/params/precompiles.go @@ -0,0 +1,36 @@ +// (c) 2023 Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package params + +import ( + "encoding/json" + + "github.com/ava-labs/coreth/precompile/modules" + "github.com/ava-labs/coreth/precompile/precompileconfig" +) + +type Precompiles map[string]precompileconfig.Config + +// UnmarshalJSON parses the JSON-encoded data into the ChainConfigPrecompiles. +// ChainConfigPrecompiles is a map of precompile module keys to their +// configuration. +func (ccp *Precompiles) UnmarshalJSON(data []byte) error { + raw := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + *ccp = make(Precompiles) + for _, module := range modules.RegisteredModules() { + key := module.ConfigKey + if value, ok := raw[key]; ok { + conf := module.MakeConfig() + if err := json.Unmarshal(value, conf); err != nil { + return err + } + (*ccp)[key] = conf + } + } + return nil +} diff --git a/plugin/evm/block.go b/plugin/evm/block.go index aa7ca8752d..08a2ef33c2 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -4,6 +4,7 @@ package evm import ( + "bytes" "context" "errors" "fmt" @@ -13,10 +14,14 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" + "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/precompile/precompileconfig" + "github.com/ava-labs/coreth/predicate" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/choices" + "github.com/ava-labs/avalanchego/snow/engine/snowman/block" ) var ( @@ -227,10 +232,49 @@ func (b *Block) syntacticVerify() error { // Verify implements the snowman.Block interface func (b *Block) Verify(context.Context) error { - return b.verify(true) + return b.verify(&precompileconfig.PredicateContext{ + SnowCtx: b.vm.ctx, + ProposerVMBlockCtx: nil, + }, true) } -func (b *Block) verify(writes bool) error { +// ShouldVerifyWithContext implements the block.WithVerifyContext interface +func (b *Block) ShouldVerifyWithContext(context.Context) (bool, error) { + predicates := b.vm.chainConfig.AvalancheRules(b.ethBlock.Number(), b.ethBlock.Timestamp()).Predicaters + // Short circuit early if there are no predicates to verify + if len(predicates) == 0 { + return false, nil + } + + // Check if any of the transactions in the block specify a precompile that enforces a predicate, which requires + // the ProposerVMBlockCtx. + for _, tx := range b.ethBlock.Transactions() { + for _, accessTuple := range tx.AccessList() { + if _, ok := predicates[accessTuple.Address]; ok { + log.Debug("Block verification requires proposerVM context", "block", b.ID(), "height", b.Height()) + return true, nil + } + } + } + + log.Debug("Block verification does not require proposerVM context", "block", b.ID(), "height", b.Height()) + return false, nil +} + +// VerifyWithContext implements the block.WithVerifyContext interface +func (b *Block) VerifyWithContext(ctx context.Context, proposerVMBlockCtx *block.Context) error { + return b.verify(&precompileconfig.PredicateContext{ + SnowCtx: b.vm.ctx, + ProposerVMBlockCtx: proposerVMBlockCtx, + }, true) +} + +func (b *Block) verify(predicateContext *precompileconfig.PredicateContext, writes bool) error { + if predicateContext.ProposerVMBlockCtx != nil { + log.Debug("Verifying block with context", "block", b.ID(), "height", b.Height()) + } else { + log.Debug("Verifying block without context", "block", b.ID(), "height", b.Height()) + } if err := b.syntacticVerify(); err != nil { return fmt.Errorf("syntactic block verification failed: %w", err) } @@ -240,6 +284,25 @@ func (b *Block) verify(writes bool) error { return err } + // Only enforce predicates if the chain has already bootstrapped. + // If the chain is still bootstrapping, we can assume that all blocks we are verifying have + // been accepted by the network (so the predicate was validated by the network when the + // block was originally verified). + if b.vm.bootstrapped { + if err := b.verifyPredicates(predicateContext); err != nil { + return fmt.Errorf("failed to verify predicates: %w", err) + } + } + + // The engine may call VerifyWithContext multiple times on the same block with different contexts. + // Since the engine will only call Accept/Reject once, we should only call InsertBlockManual once. + // Additionally, if a block is already in processing, then it has already passed verification and + // at this point we have checked the predicates are still valid in the different context so we + // can return nil. + if b.vm.State.IsProcessing(b.id) { + return nil + } + err := b.vm.blockChain.InsertBlockManual(b.ethBlock, writes) if err != nil || !writes { // if an error occurred inserting the block into the chain @@ -252,6 +315,41 @@ func (b *Block) verify(writes bool) error { return err } +// verifyPredicates verifies the predicates in the block are valid according to predicateContext. +func (b *Block) verifyPredicates(predicateContext *precompileconfig.PredicateContext) error { + rules := b.vm.chainConfig.AvalancheRules(b.ethBlock.Number(), b.ethBlock.Timestamp()) + + switch { + case !rules.IsDUpgrade && rules.PredicatersExist(): + return errors.New("cannot enable predicates before DUpgrade activation") + case !rules.IsDUpgrade: + return nil + } + + predicateResults := predicate.NewResults() + for _, tx := range b.ethBlock.Transactions() { + results, err := core.CheckPredicates(rules, predicateContext, tx) + if err != nil { + return err + } + predicateResults.SetTxResults(tx.Hash(), results) + } + // TODO: document required gas constraints to ensure marshalling predicate results does not error + predicateResultsBytes, err := predicateResults.Bytes() + if err != nil { + return fmt.Errorf("failed to marshal predicate results: %w", err) + } + extraData := b.ethBlock.Extra() + headerPredicateResultsBytes, ok := predicate.GetPredicateResultBytes(extraData) + if !ok { + return fmt.Errorf("failed to find predicate results in extra data: %x", extraData) + } + if !bytes.Equal(headerPredicateResultsBytes, predicateResultsBytes) { + return fmt.Errorf("%w (remote: %x local: %x)", errInvalidHeaderPredicateResults, headerPredicateResultsBytes, predicateResultsBytes) + } + return nil +} + // verifyUTXOsPresent returns an error if any of the atomic transactions name UTXOs that // are not present in shared memory. func (b *Block) verifyUTXOsPresent() error { diff --git a/plugin/evm/block_verification.go b/plugin/evm/block_verification.go index 392e76070b..5d02ffcdd0 100644 --- a/plugin/evm/block_verification.go +++ b/plugin/evm/block_verification.go @@ -118,11 +118,18 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { // Check that the size of the header's Extra data field is correct for [rules]. headerExtraDataSize := uint64(len(ethHeader.Extra)) switch { + case rules.IsDUpgrade: + if headerExtraDataSize < uint64(params.DynamicFeeExtraDataSize) { + return fmt.Errorf( + "expected header ExtraData to be len >= %d but got %d", + params.DynamicFeeExtraDataSize, len(ethHeader.Extra), + ) + } case rules.IsApricotPhase3: - if headerExtraDataSize != params.ApricotPhase3ExtraDataSize { + if headerExtraDataSize != params.DynamicFeeExtraDataSize { return fmt.Errorf( "expected header ExtraData to be %d but got %d", - params.ApricotPhase3ExtraDataSize, headerExtraDataSize, + params.DynamicFeeExtraDataSize, headerExtraDataSize, ) } case rules.IsApricotPhase1: diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index 0b27086c0d..66923153fb 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -40,6 +40,7 @@ import ( "github.com/ava-labs/coreth/ethdb" "github.com/ava-labs/coreth/metrics" "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/predicate" statesyncclient "github.com/ava-labs/coreth/sync/client" "github.com/ava-labs/coreth/sync/statesync" "github.com/ava-labs/coreth/trie" @@ -282,6 +283,11 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest) *syncVMSetup { err error ) generateAndAcceptBlocks(t, serverVM, parentsToGet, func(i int, gen *core.BlockGen) { + b, err := predicate.NewResults().Bytes() + if err != nil { + t.Fatal(err) + } + gen.AppendExtra(b) switch i { case 0: // spend the UTXOs from shared memory @@ -491,6 +497,11 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { txsPerBlock := 10 toAddress := testEthAddrs[2] // arbitrary choice generateAndAcceptBlocks(t, syncerVM, blocksToBuild, func(_ int, gen *core.BlockGen) { + b, err := predicate.NewResults().Bytes() + if err != nil { + t.Fatal(err) + } + gen.AppendExtra(b) i := 0 for k := range fundedAccounts { tx := types.NewTransaction(gen.TxNonce(k.Address), toAddress, big.NewInt(1), 21000, initialBaseFee, nil) @@ -517,6 +528,11 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { // Generate blocks after we have entered normal consensus as well generateAndAcceptBlocks(t, syncerVM, blocksToBuild, func(_ int, gen *core.BlockGen) { + b, err := predicate.NewResults().Bytes() + if err != nil { + t.Fatal(err) + } + gen.AppendExtra(b) i := 0 for k := range fundedAccounts { tx := types.NewTransaction(gen.TxNonce(k.Address), toAddress, big.NewInt(1), 21000, initialBaseFee, nil) diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index f60454787c..1e97132103 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -37,6 +37,7 @@ import ( "github.com/ava-labs/coreth/params" "github.com/ava-labs/coreth/peer" "github.com/ava-labs/coreth/plugin/evm/message" + "github.com/ava-labs/coreth/precompile/precompileconfig" "github.com/ava-labs/coreth/rpc" statesyncclient "github.com/ava-labs/coreth/sync/client" "github.com/ava-labs/coreth/sync/client/stats" @@ -215,6 +216,7 @@ var ( errConflictingAtomicTx = errors.New("conflicting atomic tx present") errTooManyAtomicTx = errors.New("too many atomic tx") errMissingAtomicTxs = errors.New("cannot build a block with non-empty extra data and zero atomic transactions") + errInvalidHeaderPredicateResults = errors.New("invalid header predicate results") ) var originalStderr *os.File @@ -462,7 +464,7 @@ func (vm *VM) Initialize( } // Set the Avalanche Context on the ChainConfig g.Config.AvalancheContext = params.AvalancheContext{ - BlockchainID: common.Hash(chainCtx.ChainID), + SnowCtx: chainCtx, } vm.syntacticBlockValidator = NewBlockValidator(extDataHashes) @@ -553,6 +555,10 @@ func (vm *VM) Initialize( }, } + if err := vm.chainConfig.Verify(); err != nil { + return fmt.Errorf("failed to verify chain config: %w", err) + } + vm.codec = Codec // TODO: read size from settings @@ -756,15 +762,16 @@ func (vm *VM) initChainState(lastAcceptedBlock *types.Block) error { block.status = choices.Accepted config := &chain.Config{ - DecidedCacheSize: decidedCacheSize, - MissingCacheSize: missingCacheSize, - UnverifiedCacheSize: unverifiedCacheSize, - BytesToIDCacheSize: bytesToIDCacheSize, - GetBlockIDAtHeight: vm.GetBlockIDAtHeight, - GetBlock: vm.getBlock, - UnmarshalBlock: vm.parseBlock, - BuildBlock: vm.buildBlock, - LastAcceptedBlock: block, + DecidedCacheSize: decidedCacheSize, + MissingCacheSize: missingCacheSize, + UnverifiedCacheSize: unverifiedCacheSize, + BytesToIDCacheSize: bytesToIDCacheSize, + GetBlockIDAtHeight: vm.GetBlockIDAtHeight, + GetBlock: vm.getBlock, + UnmarshalBlock: vm.parseBlock, + BuildBlock: vm.buildBlock, + BuildBlockWithContext: vm.buildBlockWithContext, + LastAcceptedBlock: block, } // Register chain state metrics @@ -1188,9 +1195,23 @@ func (vm *VM) Shutdown(context.Context) error { return nil } +func (vm *VM) buildBlock(ctx context.Context) (snowman.Block, error) { + return vm.buildBlockWithContext(ctx, nil) +} + // buildBlock builds a block to be wrapped by ChainState -func (vm *VM) buildBlock(_ context.Context) (snowman.Block, error) { - block, err := vm.miner.GenerateBlock() +func (vm *VM) buildBlockWithContext(ctx context.Context, proposerVMBlockCtx *block.Context) (snowman.Block, error) { + if proposerVMBlockCtx != nil { + log.Debug("Building block with context", "pChainBlockHeight", proposerVMBlockCtx.PChainHeight) + } else { + log.Debug("Building block without context") + } + predicateCtx := &precompileconfig.PredicateContext{ + SnowCtx: vm.ctx, + ProposerVMBlockCtx: proposerVMBlockCtx, + } + + block, err := vm.miner.GenerateBlock(predicateCtx) vm.builder.handleGenerateBlock() if err != nil { vm.mempool.CancelCurrentTxs() @@ -1217,7 +1238,7 @@ func (vm *VM) buildBlock(_ context.Context) (snowman.Block, error) { // We call verify without writes here to avoid generating a reference // to the blk state root in the triedb when we are going to call verify // again from the consensus engine with writes enabled. - if err := blk.verify(false /*=writes*/); err != nil { + if err := blk.verify(predicateCtx, false /*=writes*/); err != nil { vm.mempool.CancelCurrentTxs() return nil, fmt.Errorf("block failed verification due to: %w", err) } diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 42050e1162..9b5dc59599 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -92,9 +92,10 @@ var ( genesisJSONApricotPhase6 = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"apricotPhase1BlockTimestamp\":0,\"apricotPhase2BlockTimestamp\":0,\"apricotPhase3BlockTimestamp\":0,\"apricotPhase4BlockTimestamp\":0,\"apricotPhase5BlockTimestamp\":0,\"apricotPhasePre6BlockTimestamp\":0,\"apricotPhase6BlockTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}" genesisJSONApricotPhasePost6 = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"apricotPhase1BlockTimestamp\":0,\"apricotPhase2BlockTimestamp\":0,\"apricotPhase3BlockTimestamp\":0,\"apricotPhase4BlockTimestamp\":0,\"apricotPhase5BlockTimestamp\":0,\"apricotPhasePre6BlockTimestamp\":0,\"apricotPhase6BlockTimestamp\":0,\"apricotPhasePost6BlockTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}" - genesisJSONBanff = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"apricotPhase1BlockTimestamp\":0,\"apricotPhase2BlockTimestamp\":0,\"apricotPhase3BlockTimestamp\":0,\"apricotPhase4BlockTimestamp\":0,\"apricotPhase5BlockTimestamp\":0,\"apricotPhasePre6BlockTimestamp\":0,\"apricotPhase6BlockTimestamp\":0,\"apricotPhasePost6BlockTimestamp\":0,\"banffBlockTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}" - genesisJSONCortina = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"apricotPhase1BlockTimestamp\":0,\"apricotPhase2BlockTimestamp\":0,\"apricotPhase3BlockTimestamp\":0,\"apricotPhase4BlockTimestamp\":0,\"apricotPhase5BlockTimestamp\":0,\"apricotPhasePre6BlockTimestamp\":0,\"apricotPhase6BlockTimestamp\":0,\"apricotPhasePost6BlockTimestamp\":0,\"banffBlockTimestamp\":0,\"cortinaBlockTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}" - genesisJSONLatest = genesisJSONCortina + genesisJSONBanff = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"apricotPhase1BlockTimestamp\":0,\"apricotPhase2BlockTimestamp\":0,\"apricotPhase3BlockTimestamp\":0,\"apricotPhase4BlockTimestamp\":0,\"apricotPhase5BlockTimestamp\":0,\"apricotPhasePre6BlockTimestamp\":0,\"apricotPhase6BlockTimestamp\":0,\"apricotPhasePost6BlockTimestamp\":0,\"banffBlockTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}" + genesisJSONCortina = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"apricotPhase1BlockTimestamp\":0,\"apricotPhase2BlockTimestamp\":0,\"apricotPhase3BlockTimestamp\":0,\"apricotPhase4BlockTimestamp\":0,\"apricotPhase5BlockTimestamp\":0,\"apricotPhasePre6BlockTimestamp\":0,\"apricotPhase6BlockTimestamp\":0,\"apricotPhasePost6BlockTimestamp\":0,\"banffBlockTimestamp\":0,\"cortinaBlockTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}" + genesisJSONDUpgrade = "{\"config\":{\"chainId\":43111,\"homesteadBlock\":0,\"daoForkBlock\":0,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"apricotPhase1BlockTimestamp\":0,\"apricotPhase2BlockTimestamp\":0,\"apricotPhase3BlockTimestamp\":0,\"apricotPhase4BlockTimestamp\":0,\"apricotPhase5BlockTimestamp\":0,\"apricotPhasePre6BlockTimestamp\":0,\"apricotPhase6BlockTimestamp\":0,\"apricotPhasePost6BlockTimestamp\":0,\"banffBlockTimestamp\":0,\"cortinaBlockTimestamp\":0,\"dUpgradeBlockTimestamp\":0},\"nonce\":\"0x0\",\"timestamp\":\"0x0\",\"extraData\":\"0x00\",\"gasLimit\":\"0x5f5e100\",\"difficulty\":\"0x0\",\"mixHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"coinbase\":\"0x0000000000000000000000000000000000000000\",\"alloc\":{\"0100000000000000000000000000000000000000\":{\"code\":\"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033\",\"balance\":\"0x0\"}},\"number\":\"0x0\",\"gasUsed\":\"0x0\",\"parentHash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\"}" + genesisJSONLatest = genesisJSONDUpgrade apricotRulesPhase0 = params.Rules{} apricotRulesPhase1 = params.Rules{IsApricotPhase1: true} @@ -581,6 +582,11 @@ func TestVMUpgrades(t *testing.T) { genesis: genesisJSONCortina, expectedGasPrice: big.NewInt(0), }, + { + name: "DUpgrade", + genesis: genesisJSONDUpgrade, + expectedGasPrice: big.NewInt(0), + }, } for _, test := range genesisTests { t.Run(test.name, func(t *testing.T) { diff --git a/precompile/contract.go b/precompile/contract.go deleted file mode 100644 index 0980738f4d..0000000000 --- a/precompile/contract.go +++ /dev/null @@ -1,145 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package precompile - -import ( - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/common" -) - -const ( - selectorLen = 4 -) - -type RunStatefulPrecompileFunc func(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) - -// PrecompileAccessibleState defines the interface exposed to stateful precompile contracts -type PrecompileAccessibleState interface { - GetStateDB() StateDB - GetBlockContext() BlockContext - NativeAssetCall(caller common.Address, input []byte, suppliedGas uint64, gasGost uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) -} - -// BlockContext defines an interface that provides information to a stateful precompile -// about the block that activates the upgrade. The precompile can access this information -// to initialize its state. -type BlockContext interface { - Number() *big.Int - Timestamp() uint64 -} - -// ChainContext defines an interface that provides information to a stateful precompile -// about the chain configuration. The precompile can access this information to initialize -// its state. -type ChainConfig interface { - // Note: None of the existing stateful precompiles currently access chain config information - // in Configure so this interface is empty. -} - -// StateDB is the interface for accessing EVM state -type StateDB interface { - GetState(common.Address, common.Hash) common.Hash - SetState(common.Address, common.Hash, common.Hash) - - SetCode(common.Address, []byte) - - SetNonce(common.Address, uint64) - GetNonce(common.Address) uint64 - - GetBalance(common.Address) *big.Int - AddBalance(common.Address, *big.Int) - SubBalance(common.Address, *big.Int) - - SubBalanceMultiCoin(common.Address, common.Hash, *big.Int) - AddBalanceMultiCoin(common.Address, common.Hash, *big.Int) - GetBalanceMultiCoin(common.Address, common.Hash) *big.Int - - CreateAccount(common.Address) - Exist(common.Address) bool -} - -// StatefulPrecompiledContract is the interface for executing a precompiled contract -type StatefulPrecompiledContract interface { - // Run executes the precompiled contract. - Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) -} - -// statefulPrecompileFunction defines a function implemented by a stateful precompile -type statefulPrecompileFunction struct { - // selector is the 4 byte function selector for this function - // This should be calculated from the function signature using CalculateFunctionSelector - selector []byte - // execute is performed when this function is selected - execute RunStatefulPrecompileFunc -} - -// newStatefulPrecompileFunction creates a stateful precompile function with the given arguments -// -//nolint:unused,deadcode -func newStatefulPrecompileFunction(selector []byte, execute RunStatefulPrecompileFunc) *statefulPrecompileFunction { - return &statefulPrecompileFunction{ - selector: selector, - execute: execute, - } -} - -// statefulPrecompileWithFunctionSelectors implements StatefulPrecompiledContract by using 4 byte function selectors to pass -// off responsibilities to internal execution functions. -// Note: because we only ever read from [functions] there no lock is required to make it thread-safe. -type statefulPrecompileWithFunctionSelectors struct { - fallback *statefulPrecompileFunction - functions map[string]*statefulPrecompileFunction -} - -// newStatefulPrecompileWithFunctionSelectors generates new StatefulPrecompile using [functions] as the available functions and [fallback] -// as an optional fallback if there is no input data. Note: the selector of [fallback] will be ignored, so it is required to be left empty. -// -//nolint:unused,deadcode -func newStatefulPrecompileWithFunctionSelectors(fallback *statefulPrecompileFunction, functions []*statefulPrecompileFunction) StatefulPrecompiledContract { - // Ensure that if a fallback is present, it does not have a mistakenly populated function selector. - if fallback != nil && len(fallback.selector) != 0 { - panic(fmt.Errorf("fallback function cannot specify non-zero length function selector")) - } - - // Construct the contract and populate [functions]. - contract := &statefulPrecompileWithFunctionSelectors{ - fallback: fallback, - functions: make(map[string]*statefulPrecompileFunction), - } - for _, function := range functions { - _, exists := contract.functions[string(function.selector)] - if exists { - panic(fmt.Errorf("cannot create stateful precompile with duplicated function selector: %q", function.selector)) - } - contract.functions[string(function.selector)] = function - } - - return contract -} - -// Run selects the function using the 4 byte function selector at the start of the input and executes the underlying function on the -// given arguments. -func (s *statefulPrecompileWithFunctionSelectors) Run(accessibleState PrecompileAccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - // If there is no input data present, call the fallback function if present. - if len(input) == 0 && s.fallback != nil { - return s.fallback.execute(accessibleState, caller, addr, nil, suppliedGas, readOnly) - } - - // Otherwise, an unexpected input size will result in an error. - if len(input) < selectorLen { - return nil, suppliedGas, fmt.Errorf("missing function selector to precompile - input length (%d)", len(input)) - } - - // Use the function selector to grab the correct function - selector := input[:selectorLen] - functionInput := input[selectorLen:] - function, ok := s.functions[string(selector)] - if !ok { - return nil, suppliedGas, fmt.Errorf("invalid function selector %#x", selector) - } - - return function.execute(accessibleState, caller, addr, functionInput, suppliedGas, readOnly) -} diff --git a/precompile/contract/contract.go b/precompile/contract/contract.go new file mode 100644 index 0000000000..f843e2a15b --- /dev/null +++ b/precompile/contract/contract.go @@ -0,0 +1,110 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package contract + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" +) + +const ( + SelectorLen = 4 +) + +type RunStatefulPrecompileFunc func(accessibleState AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) + +// ActivationFunc defines a function that is used to determine if a function is active +// The return value is whether or not the function is active +type ActivationFunc func(AccessibleState) bool + +// StatefulPrecompileFunction defines a function implemented by a stateful precompile +type StatefulPrecompileFunction struct { + // selector is the 4 byte function selector for this function + // This should be calculated from the function signature using CalculateFunctionSelector + selector []byte + // execute is performed when this function is selected + execute RunStatefulPrecompileFunc + // activation is checked before this function is executed + activation ActivationFunc +} + +func (f *StatefulPrecompileFunction) IsActivated(accessibleState AccessibleState) bool { + if f.activation == nil { + return true + } + return f.activation(accessibleState) +} + +// NewStatefulPrecompileFunction creates a stateful precompile function with the given arguments +func NewStatefulPrecompileFunction(selector []byte, execute RunStatefulPrecompileFunc) *StatefulPrecompileFunction { + return &StatefulPrecompileFunction{ + selector: selector, + execute: execute, + } +} + +func NewStatefulPrecompileFunctionWithActivator(selector []byte, execute RunStatefulPrecompileFunc, activation ActivationFunc) *StatefulPrecompileFunction { + return &StatefulPrecompileFunction{ + selector: selector, + execute: execute, + activation: activation, + } +} + +// statefulPrecompileWithFunctionSelectors implements StatefulPrecompiledContract by using 4 byte function selectors to pass +// off responsibilities to internal execution functions. +// Note: because we only ever read from [functions] there no lock is required to make it thread-safe. +type statefulPrecompileWithFunctionSelectors struct { + fallback RunStatefulPrecompileFunc + functions map[string]*StatefulPrecompileFunction +} + +// NewStatefulPrecompileContract generates new StatefulPrecompile using [functions] as the available functions and [fallback] +// as an optional fallback if there is no input data. Note: the selector of [fallback] will be ignored, so it is required to be left empty. +func NewStatefulPrecompileContract(fallback RunStatefulPrecompileFunc, functions []*StatefulPrecompileFunction) (StatefulPrecompiledContract, error) { + // Construct the contract and populate [functions]. + contract := &statefulPrecompileWithFunctionSelectors{ + fallback: fallback, + functions: make(map[string]*StatefulPrecompileFunction), + } + for _, function := range functions { + _, exists := contract.functions[string(function.selector)] + if exists { + return nil, fmt.Errorf("cannot create stateful precompile with duplicated function selector: %q", function.selector) + } + contract.functions[string(function.selector)] = function + } + + return contract, nil +} + +// Run selects the function using the 4 byte function selector at the start of the input and executes the underlying function on the +// given arguments. +func (s *statefulPrecompileWithFunctionSelectors) Run(accessibleState AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + // If there is no input data present, call the fallback function if present. + if len(input) == 0 && s.fallback != nil { + return s.fallback(accessibleState, caller, addr, nil, suppliedGas, readOnly) + } + + // Otherwise, an unexpected input size will result in an error. + if len(input) < SelectorLen { + return nil, suppliedGas, fmt.Errorf("missing function selector to precompile - input length (%d)", len(input)) + } + + // Use the function selector to grab the correct function + selector := input[:SelectorLen] + functionInput := input[SelectorLen:] + function, ok := s.functions[string(selector)] + if !ok { + return nil, suppliedGas, fmt.Errorf("invalid function selector %#x", selector) + } + + // Check if the function is activated + if !function.IsActivated(accessibleState) { + return nil, suppliedGas, fmt.Errorf("invalid non-activated function selector %#x", selector) + } + + return function.execute(accessibleState, caller, addr, functionInput, suppliedGas, readOnly) +} diff --git a/precompile/contract/interfaces.go b/precompile/contract/interfaces.go new file mode 100644 index 0000000000..a3d7aecabe --- /dev/null +++ b/precompile/contract/interfaces.go @@ -0,0 +1,80 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Defines the interface for the configuration and execution of a precompile contract +package contract + +import ( + "math/big" + + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/coreth/precompile/precompileconfig" + "github.com/ethereum/go-ethereum/common" +) + +// StatefulPrecompiledContract is the interface for executing a precompiled contract +type StatefulPrecompiledContract interface { + // Run executes the precompiled contract. + Run(accessibleState AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) +} + +// StateDB is the interface for accessing EVM state +type StateDB interface { + GetState(common.Address, common.Hash) common.Hash + SetState(common.Address, common.Hash, common.Hash) + + SetNonce(common.Address, uint64) + GetNonce(common.Address) uint64 + + GetBalance(common.Address) *big.Int + AddBalance(common.Address, *big.Int) + GetBalanceMultiCoin(common.Address, common.Hash) *big.Int + + CreateAccount(common.Address) + Exist(common.Address) bool + + AddLog(addr common.Address, topics []common.Hash, data []byte, blockNumber uint64) + GetLogData() [][]byte + GetPredicateStorageSlots(address common.Address, index int) ([]byte, bool) + SetPredicateStorageSlots(address common.Address, predicates [][]byte) + + GetTxHash() common.Hash + + Suicide(common.Address) bool + Finalise(deleteEmptyObjects bool) + + Snapshot() int + RevertToSnapshot(int) +} + +// AccessibleState defines the interface exposed to stateful precompile contracts +type AccessibleState interface { + GetStateDB() StateDB + GetBlockContext() BlockContext + GetSnowContext() *snow.Context + GetChainConfig() precompileconfig.ChainConfig + NativeAssetCall(caller common.Address, input []byte, suppliedGas uint64, gasCost uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) +} + +// ConfigurationBlockContext defines the interface required to configure a precompile. +type ConfigurationBlockContext interface { + Number() *big.Int + Timestamp() uint64 +} + +type BlockContext interface { + ConfigurationBlockContext + // GetResults returns an arbitrary byte array result of verifying the predicates + // of the given transaction, precompile address pair. + GetPredicateResults(txHash common.Hash, precompileAddress common.Address) []byte +} + +type Configurator interface { + MakeConfig() precompileconfig.Config + Configure( + chainConfig precompileconfig.ChainConfig, + precompileconfig precompileconfig.Config, + state StateDB, + blockContext ConfigurationBlockContext, + ) error +} diff --git a/precompile/contract/mocks.go b/precompile/contract/mocks.go new file mode 100644 index 0000000000..3f4da49932 --- /dev/null +++ b/precompile/contract/mocks.go @@ -0,0 +1,576 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: interfaces.go + +// Package contract is a generated GoMock package. +package contract + +import ( + big "math/big" + reflect "reflect" + + snow "github.com/ava-labs/avalanchego/snow" + precompileconfig "github.com/ava-labs/coreth/precompile/precompileconfig" + common "github.com/ethereum/go-ethereum/common" + gomock "go.uber.org/mock/gomock" +) + +// MockStatefulPrecompiledContract is a mock of StatefulPrecompiledContract interface. +type MockStatefulPrecompiledContract struct { + ctrl *gomock.Controller + recorder *MockStatefulPrecompiledContractMockRecorder +} + +// MockStatefulPrecompiledContractMockRecorder is the mock recorder for MockStatefulPrecompiledContract. +type MockStatefulPrecompiledContractMockRecorder struct { + mock *MockStatefulPrecompiledContract +} + +// NewMockStatefulPrecompiledContract creates a new mock instance. +func NewMockStatefulPrecompiledContract(ctrl *gomock.Controller) *MockStatefulPrecompiledContract { + mock := &MockStatefulPrecompiledContract{ctrl: ctrl} + mock.recorder = &MockStatefulPrecompiledContractMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStatefulPrecompiledContract) EXPECT() *MockStatefulPrecompiledContractMockRecorder { + return m.recorder +} + +// Run mocks base method. +func (m *MockStatefulPrecompiledContract) Run(accessibleState AccessibleState, caller, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) ([]byte, uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Run", accessibleState, caller, addr, input, suppliedGas, readOnly) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(uint64) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Run indicates an expected call of Run. +func (mr *MockStatefulPrecompiledContractMockRecorder) Run(accessibleState, caller, addr, input, suppliedGas, readOnly interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockStatefulPrecompiledContract)(nil).Run), accessibleState, caller, addr, input, suppliedGas, readOnly) +} + +// MockStateDB is a mock of StateDB interface. +type MockStateDB struct { + ctrl *gomock.Controller + recorder *MockStateDBMockRecorder +} + +// MockStateDBMockRecorder is the mock recorder for MockStateDB. +type MockStateDBMockRecorder struct { + mock *MockStateDB +} + +// NewMockStateDB creates a new mock instance. +func NewMockStateDB(ctrl *gomock.Controller) *MockStateDB { + mock := &MockStateDB{ctrl: ctrl} + mock.recorder = &MockStateDBMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStateDB) EXPECT() *MockStateDBMockRecorder { + return m.recorder +} + +// AddBalance mocks base method. +func (m *MockStateDB) AddBalance(arg0 common.Address, arg1 *big.Int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AddBalance", arg0, arg1) +} + +// AddBalance indicates an expected call of AddBalance. +func (mr *MockStateDBMockRecorder) AddBalance(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddBalance", reflect.TypeOf((*MockStateDB)(nil).AddBalance), arg0, arg1) +} + +// AddLog mocks base method. +func (m *MockStateDB) AddLog(addr common.Address, topics []common.Hash, data []byte, blockNumber uint64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AddLog", addr, topics, data, blockNumber) +} + +// AddLog indicates an expected call of AddLog. +func (mr *MockStateDBMockRecorder) AddLog(addr, topics, data, blockNumber interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddLog", reflect.TypeOf((*MockStateDB)(nil).AddLog), addr, topics, data, blockNumber) +} + +// CreateAccount mocks base method. +func (m *MockStateDB) CreateAccount(arg0 common.Address) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "CreateAccount", arg0) +} + +// CreateAccount indicates an expected call of CreateAccount. +func (mr *MockStateDBMockRecorder) CreateAccount(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAccount", reflect.TypeOf((*MockStateDB)(nil).CreateAccount), arg0) +} + +// Exist mocks base method. +func (m *MockStateDB) Exist(arg0 common.Address) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Exist", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// Exist indicates an expected call of Exist. +func (mr *MockStateDBMockRecorder) Exist(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exist", reflect.TypeOf((*MockStateDB)(nil).Exist), arg0) +} + +// Finalise mocks base method. +func (m *MockStateDB) Finalise(deleteEmptyObjects bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Finalise", deleteEmptyObjects) +} + +// Finalise indicates an expected call of Finalise. +func (mr *MockStateDBMockRecorder) Finalise(deleteEmptyObjects interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Finalise", reflect.TypeOf((*MockStateDB)(nil).Finalise), deleteEmptyObjects) +} + +// GetBalance mocks base method. +func (m *MockStateDB) GetBalance(arg0 common.Address) *big.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBalance", arg0) + ret0, _ := ret[0].(*big.Int) + return ret0 +} + +// GetBalance indicates an expected call of GetBalance. +func (mr *MockStateDBMockRecorder) GetBalance(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBalance", reflect.TypeOf((*MockStateDB)(nil).GetBalance), arg0) +} + +// GetBalanceMultiCoin mocks base method. +func (m *MockStateDB) GetBalanceMultiCoin(arg0 common.Address, arg1 common.Hash) *big.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBalanceMultiCoin", arg0, arg1) + ret0, _ := ret[0].(*big.Int) + return ret0 +} + +// GetBalanceMultiCoin indicates an expected call of GetBalanceMultiCoin. +func (mr *MockStateDBMockRecorder) GetBalanceMultiCoin(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBalanceMultiCoin", reflect.TypeOf((*MockStateDB)(nil).GetBalanceMultiCoin), arg0, arg1) +} + +// GetLogData mocks base method. +func (m *MockStateDB) GetLogData() [][]byte { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLogData") + ret0, _ := ret[0].([][]byte) + return ret0 +} + +// GetLogData indicates an expected call of GetLogData. +func (mr *MockStateDBMockRecorder) GetLogData() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogData", reflect.TypeOf((*MockStateDB)(nil).GetLogData)) +} + +// GetNonce mocks base method. +func (m *MockStateDB) GetNonce(arg0 common.Address) uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNonce", arg0) + ret0, _ := ret[0].(uint64) + return ret0 +} + +// GetNonce indicates an expected call of GetNonce. +func (mr *MockStateDBMockRecorder) GetNonce(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNonce", reflect.TypeOf((*MockStateDB)(nil).GetNonce), arg0) +} + +// GetPredicateStorageSlots mocks base method. +func (m *MockStateDB) GetPredicateStorageSlots(address common.Address, index int) ([]byte, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPredicateStorageSlots", address, index) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(bool) + return ret0, ret1 +} + +// GetPredicateStorageSlots indicates an expected call of GetPredicateStorageSlots. +func (mr *MockStateDBMockRecorder) GetPredicateStorageSlots(address, index interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPredicateStorageSlots", reflect.TypeOf((*MockStateDB)(nil).GetPredicateStorageSlots), address, index) +} + +// GetState mocks base method. +func (m *MockStateDB) GetState(arg0 common.Address, arg1 common.Hash) common.Hash { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetState", arg0, arg1) + ret0, _ := ret[0].(common.Hash) + return ret0 +} + +// GetState indicates an expected call of GetState. +func (mr *MockStateDBMockRecorder) GetState(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetState", reflect.TypeOf((*MockStateDB)(nil).GetState), arg0, arg1) +} + +// GetTxHash mocks base method. +func (m *MockStateDB) GetTxHash() common.Hash { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTxHash") + ret0, _ := ret[0].(common.Hash) + return ret0 +} + +// GetTxHash indicates an expected call of GetTxHash. +func (mr *MockStateDBMockRecorder) GetTxHash() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTxHash", reflect.TypeOf((*MockStateDB)(nil).GetTxHash)) +} + +// RevertToSnapshot mocks base method. +func (m *MockStateDB) RevertToSnapshot(arg0 int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RevertToSnapshot", arg0) +} + +// RevertToSnapshot indicates an expected call of RevertToSnapshot. +func (mr *MockStateDBMockRecorder) RevertToSnapshot(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevertToSnapshot", reflect.TypeOf((*MockStateDB)(nil).RevertToSnapshot), arg0) +} + +// SetNonce mocks base method. +func (m *MockStateDB) SetNonce(arg0 common.Address, arg1 uint64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetNonce", arg0, arg1) +} + +// SetNonce indicates an expected call of SetNonce. +func (mr *MockStateDBMockRecorder) SetNonce(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetNonce", reflect.TypeOf((*MockStateDB)(nil).SetNonce), arg0, arg1) +} + +// SetPredicateStorageSlots mocks base method. +func (m *MockStateDB) SetPredicateStorageSlots(address common.Address, predicates [][]byte) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetPredicateStorageSlots", address, predicates) +} + +// SetPredicateStorageSlots indicates an expected call of SetPredicateStorageSlots. +func (mr *MockStateDBMockRecorder) SetPredicateStorageSlots(address, predicates interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPredicateStorageSlots", reflect.TypeOf((*MockStateDB)(nil).SetPredicateStorageSlots), address, predicates) +} + +// SetState mocks base method. +func (m *MockStateDB) SetState(arg0 common.Address, arg1, arg2 common.Hash) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetState", arg0, arg1, arg2) +} + +// SetState indicates an expected call of SetState. +func (mr *MockStateDBMockRecorder) SetState(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetState", reflect.TypeOf((*MockStateDB)(nil).SetState), arg0, arg1, arg2) +} + +// Snapshot mocks base method. +func (m *MockStateDB) Snapshot() int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Snapshot") + ret0, _ := ret[0].(int) + return ret0 +} + +// Snapshot indicates an expected call of Snapshot. +func (mr *MockStateDBMockRecorder) Snapshot() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Snapshot", reflect.TypeOf((*MockStateDB)(nil).Snapshot)) +} + +// Suicide mocks base method. +func (m *MockStateDB) Suicide(arg0 common.Address) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Suicide", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// Suicide indicates an expected call of Suicide. +func (mr *MockStateDBMockRecorder) Suicide(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Suicide", reflect.TypeOf((*MockStateDB)(nil).Suicide), arg0) +} + +// MockAccessibleState is a mock of AccessibleState interface. +type MockAccessibleState struct { + ctrl *gomock.Controller + recorder *MockAccessibleStateMockRecorder +} + +// MockAccessibleStateMockRecorder is the mock recorder for MockAccessibleState. +type MockAccessibleStateMockRecorder struct { + mock *MockAccessibleState +} + +// NewMockAccessibleState creates a new mock instance. +func NewMockAccessibleState(ctrl *gomock.Controller) *MockAccessibleState { + mock := &MockAccessibleState{ctrl: ctrl} + mock.recorder = &MockAccessibleStateMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAccessibleState) EXPECT() *MockAccessibleStateMockRecorder { + return m.recorder +} + +// GetBlockContext mocks base method. +func (m *MockAccessibleState) GetBlockContext() BlockContext { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBlockContext") + ret0, _ := ret[0].(BlockContext) + return ret0 +} + +// GetBlockContext indicates an expected call of GetBlockContext. +func (mr *MockAccessibleStateMockRecorder) GetBlockContext() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockContext", reflect.TypeOf((*MockAccessibleState)(nil).GetBlockContext)) +} + +// GetChainConfig mocks base method. +func (m *MockAccessibleState) GetChainConfig() precompileconfig.ChainConfig { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetChainConfig") + ret0, _ := ret[0].(precompileconfig.ChainConfig) + return ret0 +} + +// GetChainConfig indicates an expected call of GetChainConfig. +func (mr *MockAccessibleStateMockRecorder) GetChainConfig() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChainConfig", reflect.TypeOf((*MockAccessibleState)(nil).GetChainConfig)) +} + +// GetSnowContext mocks base method. +func (m *MockAccessibleState) GetSnowContext() *snow.Context { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSnowContext") + ret0, _ := ret[0].(*snow.Context) + return ret0 +} + +// GetSnowContext indicates an expected call of GetSnowContext. +func (mr *MockAccessibleStateMockRecorder) GetSnowContext() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSnowContext", reflect.TypeOf((*MockAccessibleState)(nil).GetSnowContext)) +} + +// GetStateDB mocks base method. +func (m *MockAccessibleState) GetStateDB() StateDB { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetStateDB") + ret0, _ := ret[0].(StateDB) + return ret0 +} + +// GetStateDB indicates an expected call of GetStateDB. +func (mr *MockAccessibleStateMockRecorder) GetStateDB() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStateDB", reflect.TypeOf((*MockAccessibleState)(nil).GetStateDB)) +} + +// NativeAssetCall mocks base method. +func (m *MockAccessibleState) NativeAssetCall(caller common.Address, input []byte, suppliedGas, gasCost uint64, readOnly bool) ([]byte, uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NativeAssetCall", caller, input, suppliedGas, gasCost, readOnly) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(uint64) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// NativeAssetCall indicates an expected call of NativeAssetCall. +func (mr *MockAccessibleStateMockRecorder) NativeAssetCall(caller, input, suppliedGas, gasCost, readOnly interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NativeAssetCall", reflect.TypeOf((*MockAccessibleState)(nil).NativeAssetCall), caller, input, suppliedGas, gasCost, readOnly) +} + +// MockConfigurationBlockContext is a mock of ConfigurationBlockContext interface. +type MockConfigurationBlockContext struct { + ctrl *gomock.Controller + recorder *MockConfigurationBlockContextMockRecorder +} + +// MockConfigurationBlockContextMockRecorder is the mock recorder for MockConfigurationBlockContext. +type MockConfigurationBlockContextMockRecorder struct { + mock *MockConfigurationBlockContext +} + +// NewMockConfigurationBlockContext creates a new mock instance. +func NewMockConfigurationBlockContext(ctrl *gomock.Controller) *MockConfigurationBlockContext { + mock := &MockConfigurationBlockContext{ctrl: ctrl} + mock.recorder = &MockConfigurationBlockContextMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockConfigurationBlockContext) EXPECT() *MockConfigurationBlockContextMockRecorder { + return m.recorder +} + +// Number mocks base method. +func (m *MockConfigurationBlockContext) Number() *big.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Number") + ret0, _ := ret[0].(*big.Int) + return ret0 +} + +// Number indicates an expected call of Number. +func (mr *MockConfigurationBlockContextMockRecorder) Number() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Number", reflect.TypeOf((*MockConfigurationBlockContext)(nil).Number)) +} + +// Timestamp mocks base method. +func (m *MockConfigurationBlockContext) Timestamp() uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Timestamp") + ret0, _ := ret[0].(uint64) + return ret0 +} + +// Timestamp indicates an expected call of Timestamp. +func (mr *MockConfigurationBlockContextMockRecorder) Timestamp() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Timestamp", reflect.TypeOf((*MockConfigurationBlockContext)(nil).Timestamp)) +} + +// MockBlockContext is a mock of BlockContext interface. +type MockBlockContext struct { + ctrl *gomock.Controller + recorder *MockBlockContextMockRecorder +} + +// MockBlockContextMockRecorder is the mock recorder for MockBlockContext. +type MockBlockContextMockRecorder struct { + mock *MockBlockContext +} + +// NewMockBlockContext creates a new mock instance. +func NewMockBlockContext(ctrl *gomock.Controller) *MockBlockContext { + mock := &MockBlockContext{ctrl: ctrl} + mock.recorder = &MockBlockContextMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBlockContext) EXPECT() *MockBlockContextMockRecorder { + return m.recorder +} + +// GetPredicateResults mocks base method. +func (m *MockBlockContext) GetPredicateResults(txHash common.Hash, precompileAddress common.Address) []byte { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPredicateResults", txHash, precompileAddress) + ret0, _ := ret[0].([]byte) + return ret0 +} + +// GetPredicateResults indicates an expected call of GetPredicateResults. +func (mr *MockBlockContextMockRecorder) GetPredicateResults(txHash, precompileAddress interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPredicateResults", reflect.TypeOf((*MockBlockContext)(nil).GetPredicateResults), txHash, precompileAddress) +} + +// Number mocks base method. +func (m *MockBlockContext) Number() *big.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Number") + ret0, _ := ret[0].(*big.Int) + return ret0 +} + +// Number indicates an expected call of Number. +func (mr *MockBlockContextMockRecorder) Number() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Number", reflect.TypeOf((*MockBlockContext)(nil).Number)) +} + +// Timestamp mocks base method. +func (m *MockBlockContext) Timestamp() uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Timestamp") + ret0, _ := ret[0].(uint64) + return ret0 +} + +// Timestamp indicates an expected call of Timestamp. +func (mr *MockBlockContextMockRecorder) Timestamp() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Timestamp", reflect.TypeOf((*MockBlockContext)(nil).Timestamp)) +} + +// MockConfigurator is a mock of Configurator interface. +type MockConfigurator struct { + ctrl *gomock.Controller + recorder *MockConfiguratorMockRecorder +} + +// MockConfiguratorMockRecorder is the mock recorder for MockConfigurator. +type MockConfiguratorMockRecorder struct { + mock *MockConfigurator +} + +// NewMockConfigurator creates a new mock instance. +func NewMockConfigurator(ctrl *gomock.Controller) *MockConfigurator { + mock := &MockConfigurator{ctrl: ctrl} + mock.recorder = &MockConfiguratorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockConfigurator) EXPECT() *MockConfiguratorMockRecorder { + return m.recorder +} + +// Configure mocks base method. +func (m *MockConfigurator) Configure(chainConfig precompileconfig.ChainConfig, precompileconfig precompileconfig.Config, state StateDB, blockContext ConfigurationBlockContext) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Configure", chainConfig, precompileconfig, state, blockContext) + ret0, _ := ret[0].(error) + return ret0 +} + +// Configure indicates an expected call of Configure. +func (mr *MockConfiguratorMockRecorder) Configure(chainConfig, precompileconfig, state, blockContext interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Configure", reflect.TypeOf((*MockConfigurator)(nil).Configure), chainConfig, precompileconfig, state, blockContext) +} + +// MakeConfig mocks base method. +func (m *MockConfigurator) MakeConfig() precompileconfig.Config { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MakeConfig") + ret0, _ := ret[0].(precompileconfig.Config) + return ret0 +} + +// MakeConfig indicates an expected call of MakeConfig. +func (mr *MockConfiguratorMockRecorder) MakeConfig() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MakeConfig", reflect.TypeOf((*MockConfigurator)(nil).MakeConfig)) +} diff --git a/precompile/contract/utils.go b/precompile/contract/utils.go new file mode 100644 index 0000000000..8954bbc551 --- /dev/null +++ b/precompile/contract/utils.go @@ -0,0 +1,90 @@ +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package contract + +import ( + "fmt" + "regexp" + "strings" + + "github.com/ava-labs/coreth/accounts/abi" + "github.com/ava-labs/coreth/vmerrs" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// Gas costs for stateful precompiles +const ( + WriteGasCostPerSlot = 20_000 + ReadGasCostPerSlot = 5_000 +) + +var functionSignatureRegex = regexp.MustCompile(`\w+\((\w*|(\w+,)+\w+)\)`) + +// 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)" +// TODO: remove this after moving to ABI based function selectors. +func CalculateFunctionSelector(functionSignature string) []byte { + if !functionSignatureRegex.MatchString(functionSignature) { + panic(fmt.Errorf("invalid function signature: %q", functionSignature)) + } + hash := crypto.Keccak256([]byte(functionSignature)) + return hash[:4] +} + +// DeductGas checks if [suppliedGas] is sufficient against [requiredGas] and deducts [requiredGas] from [suppliedGas]. +func DeductGas(suppliedGas uint64, requiredGas uint64) (uint64, error) { + if suppliedGas < requiredGas { + return 0, vmerrs.ErrOutOfGas + } + return suppliedGas - requiredGas, nil +} + +// PackOrderedHashesWithSelector packs the function selector and ordered list of hashes into [dst] +// byte slice. +// assumes that [dst] has sufficient room for [functionSelector] and [hashes]. +func PackOrderedHashesWithSelector(dst []byte, functionSelector []byte, hashes []common.Hash) error { + copy(dst[:len(functionSelector)], functionSelector) + return PackOrderedHashes(dst[len(functionSelector):], hashes) +} + +// PackOrderedHashes packs the ordered list of [hashes] into the [dst] byte buffer. +// assumes that [dst] has sufficient space to pack [hashes] or else this function will panic. +func PackOrderedHashes(dst []byte, hashes []common.Hash) error { + if len(dst) != len(hashes)*common.HashLength { + return fmt.Errorf("destination byte buffer has insufficient length (%d) for %d hashes", len(dst), len(hashes)) + } + + var ( + start = 0 + end = common.HashLength + ) + for _, hash := range hashes { + copy(dst[start:end], hash.Bytes()) + start += common.HashLength + end += common.HashLength + } + return nil +} + +// PackedHash returns packed the byte slice with common.HashLength from [packed] +// at the given [index]. +// Assumes that [packed] is composed entirely of packed 32 byte segments. +func PackedHash(packed []byte, index int) []byte { + start := common.HashLength * index + end := start + common.HashLength + return packed[start:end] +} + +// ParseABI parses the given ABI string and returns the parsed ABI. +// If the ABI is invalid, it panics. +func ParseABI(rawABI string) abi.ABI { + parsed, err := abi.JSON(strings.NewReader(rawABI)) + if err != nil { + panic(err) + } + + return parsed +} diff --git a/precompile/utils_test.go b/precompile/contract/utils_test.go similarity index 88% rename from precompile/utils_test.go rename to precompile/contract/utils_test.go index 8d9f6c9ef9..6220af95a8 100644 --- a/precompile/utils_test.go +++ b/precompile/contract/utils_test.go @@ -1,4 +1,7 @@ -package precompile +// (c) 2019-2020, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package contract import ( "testing" diff --git a/precompile/modules/module.go b/precompile/modules/module.go new file mode 100644 index 0000000000..fefa9fd2da --- /dev/null +++ b/precompile/modules/module.go @@ -0,0 +1,37 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package modules + +import ( + "bytes" + + "github.com/ava-labs/coreth/precompile/contract" + "github.com/ethereum/go-ethereum/common" +) + +type Module struct { + // ConfigKey is the key used in json config files to specify this precompile config. + ConfigKey string + // Address returns the address where the stateful precompile is accessible. + Address common.Address + // Contract returns a thread-safe singleton that can be used as the StatefulPrecompiledContract when + // this config is enabled. + Contract contract.StatefulPrecompiledContract + // Configurator is used to configure the stateful precompile when the config is enabled. + contract.Configurator +} + +type moduleArray []Module + +func (u moduleArray) Len() int { + return len(u) +} + +func (u moduleArray) Swap(i, j int) { + u[i], u[j] = u[j], u[i] +} + +func (m moduleArray) Less(i, j int) bool { + return bytes.Compare(m[i].Address.Bytes(), m[j].Address.Bytes()) < 0 +} diff --git a/precompile/modules/registerer.go b/precompile/modules/registerer.go new file mode 100644 index 0000000000..a7f3b92c60 --- /dev/null +++ b/precompile/modules/registerer.go @@ -0,0 +1,98 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package modules + +import ( + "fmt" + "sort" + + "github.com/ava-labs/coreth/constants" + "github.com/ava-labs/coreth/utils" + "github.com/ethereum/go-ethereum/common" +) + +var ( + // registeredModules is a list of Module to preserve order + // for deterministic iteration + registeredModules = make([]Module, 0) + + reservedRanges = []utils.AddressRange{ + { + Start: common.HexToAddress("0x0100000000000000000000000000000000000000"), + End: common.HexToAddress("0x01000000000000000000000000000000000000ff"), + }, + { + Start: common.HexToAddress("0x0200000000000000000000000000000000000000"), + End: common.HexToAddress("0x02000000000000000000000000000000000000ff"), + }, + { + Start: common.HexToAddress("0x0300000000000000000000000000000000000000"), + End: common.HexToAddress("0x03000000000000000000000000000000000000ff"), + }, + } +) + +// ReservedAddress returns true if [addr] is in a reserved range for custom precompiles +func ReservedAddress(addr common.Address) bool { + for _, reservedRange := range reservedRanges { + if reservedRange.Contains(addr) { + return true + } + } + + return false +} + +// RegisterModule registers a stateful precompile module +func RegisterModule(stm Module) error { + address := stm.Address + key := stm.ConfigKey + + if address == constants.BlackholeAddr { + return fmt.Errorf("address %s overlaps with blackhole address", address) + } + if !ReservedAddress(address) { + return fmt.Errorf("address %s not in a reserved range", address) + } + + for _, registeredModule := range registeredModules { + if registeredModule.ConfigKey == key { + return fmt.Errorf("name %s already used by a stateful precompile", key) + } + if registeredModule.Address == address { + return fmt.Errorf("address %s already used by a stateful precompile", address) + } + } + // sort by address to ensure deterministic iteration + registeredModules = insertSortedByAddress(registeredModules, stm) + return nil +} + +func GetPrecompileModuleByAddress(address common.Address) (Module, bool) { + for _, stm := range registeredModules { + if stm.Address == address { + return stm, true + } + } + return Module{}, false +} + +func GetPrecompileModule(key string) (Module, bool) { + for _, stm := range registeredModules { + if stm.ConfigKey == key { + return stm, true + } + } + return Module{}, false +} + +func RegisteredModules() []Module { + return registeredModules +} + +func insertSortedByAddress(data []Module, stm Module) []Module { + data = append(data, stm) + sort.Sort(moduleArray(data)) + return data +} diff --git a/precompile/modules/registerer_test.go b/precompile/modules/registerer_test.go new file mode 100644 index 0000000000..f2519046df --- /dev/null +++ b/precompile/modules/registerer_test.go @@ -0,0 +1,59 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package modules + +import ( + "math/big" + "testing" + + "github.com/ava-labs/coreth/constants" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestInsertSortedByAddress(t *testing.T) { + data := make([]Module, 0) + // test that the module is registered in sorted order + module1 := Module{ + Address: common.BigToAddress(big.NewInt(1)), + } + data = insertSortedByAddress(data, module1) + + require.Equal(t, []Module{module1}, data) + + module0 := Module{ + Address: common.BigToAddress(big.NewInt(0)), + } + + data = insertSortedByAddress(data, module0) + require.Equal(t, []Module{module0, module1}, data) + + module3 := Module{ + Address: common.BigToAddress(big.NewInt(3)), + } + + data = insertSortedByAddress(data, module3) + require.Equal(t, []Module{module0, module1, module3}, data) + + module2 := Module{ + Address: common.BigToAddress(big.NewInt(2)), + } + + data = insertSortedByAddress(data, module2) + require.Equal(t, []Module{module0, module1, module2, module3}, data) +} + +func TestRegisterModuleInvalidAddresses(t *testing.T) { + // Test the blockhole address cannot be registered + m := Module{ + Address: constants.BlackholeAddr, + } + err := RegisterModule(m) + require.ErrorContains(t, err, "overlaps with blackhole address") + + // Test an address outside of the reserved ranges cannot be registered + m.Address = common.BigToAddress(big.NewInt(1)) + err = RegisterModule(m) + require.ErrorContains(t, err, "not in a reserved range") +} diff --git a/precompile/params.go b/precompile/params.go deleted file mode 100644 index 97c62fc52e..0000000000 --- a/precompile/params.go +++ /dev/null @@ -1,47 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package precompile - -import ( - "bytes" - - "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 - End common.Address -} - -// Contains returns true iff [addr] is contained within the (inclusive) -func (a *AddressRange) Contains(addr common.Address) bool { - addrBytes := addr.Bytes() - return bytes.Compare(addrBytes, a.Start[:]) >= 0 && bytes.Compare(addrBytes, a.End[:]) <= 0 -} - -// Designated addresses of stateful precompiles -// Note: it is important that none of these addresses conflict with each other or any other precompiles -// in core/vm/contracts.go. -// We start at 0x0100000000000000000000000000000000000000 and will increment by 1 from here to reduce -// the risk of conflicts. -var ( - UsedAddresses = []common.Address{ - // precompile contract addresses can be added here - } - - // ReservedRanges contains addresses ranges that are reserved - // for precompiles and cannot be used as EOA or deployed contracts. - ReservedRanges = []AddressRange{ - { - // reserved for coreth precompiles - common.HexToAddress("0x0100000000000000000000000000000000000000"), - common.HexToAddress("0x01000000000000000000000000000000000000ff"), - }, - } -) diff --git a/precompile/precompileconfig/config.go b/precompile/precompileconfig/config.go new file mode 100644 index 0000000000..859393fb96 --- /dev/null +++ b/precompile/precompileconfig/config.go @@ -0,0 +1,86 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Defines the stateless interface for unmarshalling an arbitrary config of a precompile +package precompileconfig + +import ( + "github.com/ava-labs/avalanchego/chains/atomic" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/snow/engine/snowman/block" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ethereum/go-ethereum/common" +) + +// StatefulPrecompileConfig defines the interface for a stateful precompile to +// be enabled via a network upgrade. +type Config interface { + // Key returns the unique key for the stateful precompile. + Key() string + // Timestamp returns the timestamp at which this stateful precompile should be enabled. + // 1) 0 indicates that the precompile should be enabled from genesis. + // 2) n indicates that the precompile should be enabled in the first block with timestamp >= [n]. + // 3) nil indicates that the precompile is never enabled. + Timestamp() *uint64 + // IsDisabled returns true if this network upgrade should disable the precompile. + IsDisabled() bool + // Equal returns true if the provided argument configures the same precompile with the same parameters. + Equal(Config) bool + // Verify is called on startup and an error is treated as fatal. Configure can assume the Config has passed verification. + Verify(ChainConfig) error +} + +// PredicateContext is the context passed in to the Predicater interface to verify +// a precompile predicate within a specific ProposerVM wrapper. +type PredicateContext struct { + SnowCtx *snow.Context + // ProposerVMBlockCtx defines the ProposerVM context the predicate is verified within + ProposerVMBlockCtx *block.Context +} + +// Predicater is an optional interface for StatefulPrecompileContracts to implement. +// If implemented, the predicate will be enforced on every transaction in a block, prior to +// the block's execution. +// If VerifyPredicate returns an error, the block will fail verification with no further processing. +// WARNING: If you are implementing a custom precompile, beware that coreth +// will not maintain backwards compatibility of this interface and your code should not +// rely on this. Designed for use only by precompiles that ship with coreth. +type Predicater interface { + PredicateGas(storageSlots []byte) (uint64, error) + VerifyPredicate(predicateContext *PredicateContext, predicates [][]byte) []byte +} + +// SharedMemoryWriter defines an interface to allow a precompile's Accepter to write operations +// into shared memory to be committed atomically on block accept. +type SharedMemoryWriter interface { + AddSharedMemoryRequests(chainID ids.ID, requests *atomic.Requests) +} + +type WarpMessageWriter interface { + AddMessage(unsignedMessage *warp.UnsignedMessage) error +} + +// AcceptContext defines the context passed in to a precompileconfig's Accepter +type AcceptContext struct { + SnowCtx *snow.Context + SharedMemory SharedMemoryWriter + Warp WarpMessageWriter +} + +// Accepter is an optional interface for StatefulPrecompiledContracts to implement. +// If implemented, Accept will be called for every log with the address of the precompile when the block is accepted. +// WARNING: If you are implementing a custom precompile, beware that coreth +// will not maintain backwards compatibility of this interface and your code should not +// rely on this. Designed for use only by precompiles that ship with coreth. +type Accepter interface { + Accept(acceptCtx *AcceptContext, blockHash common.Hash, blockNumber uint64, txHash common.Hash, logIndex int, topics []common.Hash, logData []byte) error +} + +// ChainContext defines an interface that provides information to a stateful precompile +// about the chain configuration. The precompile can access this information to initialize +// its state. +type ChainConfig interface { + // IsDUpgrade returns true if the time is after the DUpgrade. + IsDUpgrade(time uint64) bool +} diff --git a/precompile/precompileconfig/mocks.go b/precompile/precompileconfig/mocks.go new file mode 100644 index 0000000000..a7fb5d0a9c --- /dev/null +++ b/precompile/precompileconfig/mocks.go @@ -0,0 +1,306 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: config.go + +// Package precompileconfig is a generated GoMock package. +package precompileconfig + +import ( + reflect "reflect" + + atomic "github.com/ava-labs/avalanchego/chains/atomic" + ids "github.com/ava-labs/avalanchego/ids" + warp "github.com/ava-labs/avalanchego/vms/platformvm/warp" + common "github.com/ethereum/go-ethereum/common" + gomock "go.uber.org/mock/gomock" +) + +// MockConfig is a mock of Config interface. +type MockConfig struct { + ctrl *gomock.Controller + recorder *MockConfigMockRecorder +} + +// MockConfigMockRecorder is the mock recorder for MockConfig. +type MockConfigMockRecorder struct { + mock *MockConfig +} + +// NewMockConfig creates a new mock instance. +func NewMockConfig(ctrl *gomock.Controller) *MockConfig { + mock := &MockConfig{ctrl: ctrl} + mock.recorder = &MockConfigMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockConfig) EXPECT() *MockConfigMockRecorder { + return m.recorder +} + +// Equal mocks base method. +func (m *MockConfig) Equal(arg0 Config) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Equal", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// Equal indicates an expected call of Equal. +func (mr *MockConfigMockRecorder) Equal(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Equal", reflect.TypeOf((*MockConfig)(nil).Equal), arg0) +} + +// IsDisabled mocks base method. +func (m *MockConfig) IsDisabled() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsDisabled") + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsDisabled indicates an expected call of IsDisabled. +func (mr *MockConfigMockRecorder) IsDisabled() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsDisabled", reflect.TypeOf((*MockConfig)(nil).IsDisabled)) +} + +// Key mocks base method. +func (m *MockConfig) Key() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Key") + ret0, _ := ret[0].(string) + return ret0 +} + +// Key indicates an expected call of Key. +func (mr *MockConfigMockRecorder) Key() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Key", reflect.TypeOf((*MockConfig)(nil).Key)) +} + +// Timestamp mocks base method. +func (m *MockConfig) Timestamp() *uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Timestamp") + ret0, _ := ret[0].(*uint64) + return ret0 +} + +// Timestamp indicates an expected call of Timestamp. +func (mr *MockConfigMockRecorder) Timestamp() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Timestamp", reflect.TypeOf((*MockConfig)(nil).Timestamp)) +} + +// Verify mocks base method. +func (m *MockConfig) Verify(arg0 ChainConfig) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Verify", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Verify indicates an expected call of Verify. +func (mr *MockConfigMockRecorder) Verify(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Verify", reflect.TypeOf((*MockConfig)(nil).Verify), arg0) +} + +// MockPredicater is a mock of Predicater interface. +type MockPredicater struct { + ctrl *gomock.Controller + recorder *MockPredicaterMockRecorder +} + +// MockPredicaterMockRecorder is the mock recorder for MockPredicater. +type MockPredicaterMockRecorder struct { + mock *MockPredicater +} + +// NewMockPredicater creates a new mock instance. +func NewMockPredicater(ctrl *gomock.Controller) *MockPredicater { + mock := &MockPredicater{ctrl: ctrl} + mock.recorder = &MockPredicaterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPredicater) EXPECT() *MockPredicaterMockRecorder { + return m.recorder +} + +// PredicateGas mocks base method. +func (m *MockPredicater) PredicateGas(storageSlots []byte) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PredicateGas", storageSlots) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PredicateGas indicates an expected call of PredicateGas. +func (mr *MockPredicaterMockRecorder) PredicateGas(storageSlots interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PredicateGas", reflect.TypeOf((*MockPredicater)(nil).PredicateGas), storageSlots) +} + +// VerifyPredicate mocks base method. +func (m *MockPredicater) VerifyPredicate(predicateContext *PredicateContext, predicates [][]byte) []byte { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyPredicate", predicateContext, predicates) + ret0, _ := ret[0].([]byte) + return ret0 +} + +// VerifyPredicate indicates an expected call of VerifyPredicate. +func (mr *MockPredicaterMockRecorder) VerifyPredicate(predicateContext, predicates interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyPredicate", reflect.TypeOf((*MockPredicater)(nil).VerifyPredicate), predicateContext, predicates) +} + +// MockSharedMemoryWriter is a mock of SharedMemoryWriter interface. +type MockSharedMemoryWriter struct { + ctrl *gomock.Controller + recorder *MockSharedMemoryWriterMockRecorder +} + +// MockSharedMemoryWriterMockRecorder is the mock recorder for MockSharedMemoryWriter. +type MockSharedMemoryWriterMockRecorder struct { + mock *MockSharedMemoryWriter +} + +// NewMockSharedMemoryWriter creates a new mock instance. +func NewMockSharedMemoryWriter(ctrl *gomock.Controller) *MockSharedMemoryWriter { + mock := &MockSharedMemoryWriter{ctrl: ctrl} + mock.recorder = &MockSharedMemoryWriterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSharedMemoryWriter) EXPECT() *MockSharedMemoryWriterMockRecorder { + return m.recorder +} + +// AddSharedMemoryRequests mocks base method. +func (m *MockSharedMemoryWriter) AddSharedMemoryRequests(chainID ids.ID, requests *atomic.Requests) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "AddSharedMemoryRequests", chainID, requests) +} + +// AddSharedMemoryRequests indicates an expected call of AddSharedMemoryRequests. +func (mr *MockSharedMemoryWriterMockRecorder) AddSharedMemoryRequests(chainID, requests interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddSharedMemoryRequests", reflect.TypeOf((*MockSharedMemoryWriter)(nil).AddSharedMemoryRequests), chainID, requests) +} + +// MockWarpMessageWriter is a mock of WarpMessageWriter interface. +type MockWarpMessageWriter struct { + ctrl *gomock.Controller + recorder *MockWarpMessageWriterMockRecorder +} + +// MockWarpMessageWriterMockRecorder is the mock recorder for MockWarpMessageWriter. +type MockWarpMessageWriterMockRecorder struct { + mock *MockWarpMessageWriter +} + +// NewMockWarpMessageWriter creates a new mock instance. +func NewMockWarpMessageWriter(ctrl *gomock.Controller) *MockWarpMessageWriter { + mock := &MockWarpMessageWriter{ctrl: ctrl} + mock.recorder = &MockWarpMessageWriterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockWarpMessageWriter) EXPECT() *MockWarpMessageWriterMockRecorder { + return m.recorder +} + +// AddMessage mocks base method. +func (m *MockWarpMessageWriter) AddMessage(unsignedMessage *warp.UnsignedMessage) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddMessage", unsignedMessage) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddMessage indicates an expected call of AddMessage. +func (mr *MockWarpMessageWriterMockRecorder) AddMessage(unsignedMessage interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMessage", reflect.TypeOf((*MockWarpMessageWriter)(nil).AddMessage), unsignedMessage) +} + +// MockAccepter is a mock of Accepter interface. +type MockAccepter struct { + ctrl *gomock.Controller + recorder *MockAccepterMockRecorder +} + +// MockAccepterMockRecorder is the mock recorder for MockAccepter. +type MockAccepterMockRecorder struct { + mock *MockAccepter +} + +// NewMockAccepter creates a new mock instance. +func NewMockAccepter(ctrl *gomock.Controller) *MockAccepter { + mock := &MockAccepter{ctrl: ctrl} + mock.recorder = &MockAccepterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAccepter) EXPECT() *MockAccepterMockRecorder { + return m.recorder +} + +// Accept mocks base method. +func (m *MockAccepter) Accept(acceptCtx *AcceptContext, blockHash common.Hash, blockNumber uint64, txHash common.Hash, logIndex int, topics []common.Hash, logData []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Accept", acceptCtx, blockHash, blockNumber, txHash, logIndex, topics, logData) + ret0, _ := ret[0].(error) + return ret0 +} + +// Accept indicates an expected call of Accept. +func (mr *MockAccepterMockRecorder) Accept(acceptCtx, blockHash, blockNumber, txHash, logIndex, topics, logData interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Accept", reflect.TypeOf((*MockAccepter)(nil).Accept), acceptCtx, blockHash, blockNumber, txHash, logIndex, topics, logData) +} + +// MockChainConfig is a mock of ChainConfig interface. +type MockChainConfig struct { + ctrl *gomock.Controller + recorder *MockChainConfigMockRecorder +} + +// MockChainConfigMockRecorder is the mock recorder for MockChainConfig. +type MockChainConfigMockRecorder struct { + mock *MockChainConfig +} + +// NewMockChainConfig creates a new mock instance. +func NewMockChainConfig(ctrl *gomock.Controller) *MockChainConfig { + mock := &MockChainConfig{ctrl: ctrl} + mock.recorder = &MockChainConfigMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockChainConfig) EXPECT() *MockChainConfigMockRecorder { + return m.recorder +} + +// IsDUpgrade mocks base method. +func (m *MockChainConfig) IsDUpgrade(time uint64) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsDUpgrade", time) + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsDUpgrade indicates an expected call of IsDUpgrade. +func (mr *MockChainConfigMockRecorder) IsDUpgrade(time interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsDUpgrade", reflect.TypeOf((*MockChainConfig)(nil).IsDUpgrade), time) +} diff --git a/precompile/precompileconfig/upgradeable.go b/precompile/precompileconfig/upgradeable.go new file mode 100644 index 0000000000..d63f310599 --- /dev/null +++ b/precompile/precompileconfig/upgradeable.go @@ -0,0 +1,33 @@ +// (c) 2022 Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package precompileconfig + +import "github.com/ava-labs/coreth/utils" + +// Upgrade contains the timestamp for the upgrade along with +// a boolean [Disable]. If [Disable] is set, the upgrade deactivates +// the precompile and clears its storage. +type Upgrade struct { + BlockTimestamp *uint64 `json:"blockTimestamp"` + Disable bool `json:"disable,omitempty"` +} + +// Timestamp returns the timestamp this network upgrade goes into effect. +func (u *Upgrade) Timestamp() *uint64 { + return u.BlockTimestamp +} + +// IsDisabled returns true if the network upgrade deactivates the precompile. +func (u *Upgrade) IsDisabled() bool { + return u.Disable +} + +// Equal returns true iff [other] has the same blockTimestamp and has the +// same on value for the Disable flag. +func (u *Upgrade) Equal(other *Upgrade) bool { + if other == nil { + return false + } + return u.Disable == other.Disable && utils.Uint64PtrEqual(u.BlockTimestamp, other.BlockTimestamp) +} diff --git a/precompile/registry/registry.go b/precompile/registry/registry.go new file mode 100644 index 0000000000..f08ec83950 --- /dev/null +++ b/precompile/registry/registry.go @@ -0,0 +1,10 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Module to facilitate the registration of precompiles and their configuration. +package registry + +// Force imports of each precompile to ensure each precompile's init function runs and registers itself +// with the registry. + +// import _ "github.com/ava-labs/coreth/x/warp" diff --git a/precompile/stateful_precompile_config.go b/precompile/stateful_precompile_config.go deleted file mode 100644 index 3728d94d78..0000000000 --- a/precompile/stateful_precompile_config.go +++ /dev/null @@ -1,56 +0,0 @@ -// (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" - - "github.com/ava-labs/coreth/utils" -) - -// StatefulPrecompileConfig defines the interface for a stateful precompile to -type StatefulPrecompileConfig interface { - // Address returns the address where the stateful precompile is accessible. - Address() common.Address - // Timestamp returns the timestamp at which this stateful precompile should be enabled. - // 1) 0 indicates that the precompile should be enabled from genesis. - // 2) n indicates that the precompile should be enabled in the first block with timestamp >= [n]. - // 3) nil indicates that the precompile is never enabled. - Timestamp() *uint64 - // Configure is called on the first block where the stateful precompile should be enabled. - // This allows the stateful precompile to configure its own state via [StateDB] as necessary. - // This function must be deterministic since it will impact the EVM state. If a change to the - // config causes a change to the state modifications made in Configure, then it cannot be safely - // made to the config after the network upgrade has gone into effect. - // - // Configure is called on the first block where the stateful precompile should be enabled. This - // provides the config the ability to set its initial state and should only modify the state within - // its own address space. - Configure(ChainConfig, StateDB, BlockContext) - // Contract returns a thread-safe singleton that can be used as the StatefulPrecompiledContract when - // this config is enabled. - Contract() StatefulPrecompiledContract -} - -// CheckConfigure checks if [config] is activated by the transition from block at [parentTimestamp] to the timestamp -// set in [blockContext]. -// If it does, then it calls Configure on [precompileConfig] to make the necessary state update to enable the StatefulPrecompile. -// Note: this function is called within genesis to configure the starting state if [precompileConfig] specifies that it should be -// configured at genesis, or happens during block processing to update the state before processing the given block. -// TODO: add ability to call Configure at different timestamps, so that developers can easily re-configure by updating the -// stateful precompile config. -// Assumes that [config] is non-nil. -func CheckConfigure(chainConfig ChainConfig, parentTimestamp *uint64, blockContext BlockContext, precompileConfig StatefulPrecompileConfig, state StateDB) { - // If the network upgrade goes into effect within this transition, configure the stateful precompile - if utils.IsForkTransition(precompileConfig.Timestamp(), parentTimestamp, blockContext.Timestamp()) { - // Set the nonce of the precompile's address (as is done when a contract is created) to ensure - // that it is marked as non-empty and will not be cleaned up when the statedb is finalized. - state.SetNonce(precompileConfig.Address(), 1) - // Set the code of the precompile's address to a non-zero length byte slice to ensure that the precompile - // can be called from within Solidity contracts. Solidity adds a check before invoking a contract to ensure - // that it does not attempt to invoke a non-existent contract. - state.SetCode(precompileConfig.Address(), []byte{0x1}) - precompileConfig.Configure(chainConfig, state, blockContext) - } -} diff --git a/precompile/testutils/test_config.go b/precompile/testutils/test_config.go new file mode 100644 index 0000000000..52316e8063 --- /dev/null +++ b/precompile/testutils/test_config.go @@ -0,0 +1,60 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package testutils + +import ( + "testing" + + "github.com/ava-labs/coreth/precompile/precompileconfig" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +// ConfigVerifyTest is a test case for verifying a config +type ConfigVerifyTest struct { + Config precompileconfig.Config + ChainConfig precompileconfig.ChainConfig + ExpectedError string +} + +// ConfigEqualTest is a test case for comparing two configs +type ConfigEqualTest struct { + Config precompileconfig.Config + Other precompileconfig.Config + Expected bool +} + +func RunVerifyTests(t *testing.T, tests map[string]ConfigVerifyTest) { + for name, test := range tests { + t.Run(name, func(t *testing.T) { + t.Helper() + require := require.New(t) + + chainConfig := test.ChainConfig + if chainConfig == nil { + ctrl := gomock.NewController(t) + mockChainConfig := precompileconfig.NewMockChainConfig(ctrl) + mockChainConfig.EXPECT().IsDUpgrade(gomock.Any()).AnyTimes().Return(true) + chainConfig = mockChainConfig + } + err := test.Config.Verify(chainConfig) + if test.ExpectedError == "" { + require.NoError(err) + } else { + require.ErrorContains(err, test.ExpectedError) + } + }) + } +} + +func RunEqualTests(t *testing.T, tests map[string]ConfigEqualTest) { + for name, test := range tests { + t.Run(name, func(t *testing.T) { + t.Helper() + require := require.New(t) + + require.Equal(test.Expected, test.Config.Equal(test.Other)) + }) + } +} diff --git a/precompile/testutils/test_precompile.go b/precompile/testutils/test_precompile.go new file mode 100644 index 0000000000..047ef7dae8 --- /dev/null +++ b/precompile/testutils/test_precompile.go @@ -0,0 +1,206 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package testutils + +import ( + "math/big" + "testing" + "time" + + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/coreth/precompile/contract" + "github.com/ava-labs/coreth/precompile/modules" + "github.com/ava-labs/coreth/precompile/precompileconfig" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +// PrecompileTest is a test case for a precompile +type PrecompileTest struct { + // Caller is the address of the precompile caller + Caller common.Address + // Input the raw input bytes to the precompile + 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.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 + // mode. If true, the precompile should not modify the state. + ReadOnly bool + // Config is the config to use for the precompile + // It should be the same precompile config that is used in the + // precompile's configurator. + // If nil, Configure will not be called. + Config precompileconfig.Config + // BeforeHook is called before the precompile is called. + BeforeHook func(t testing.TB, state contract.StateDB) + // SetupBlockContext sets the expected calls on MockBlockContext for the test execution. + SetupBlockContext func(*contract.MockBlockContext) + // AfterHook is called after the precompile is called. + 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 + ExpectedErr string + // ChainConfig is the chain config to use for the precompile's block context + // If nil, the default chain config will be used. + ChainConfig precompileconfig.ChainConfig +} + +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 + + ctrl := gomock.NewController(t) + + if test.BeforeHook != nil { + test.BeforeHook(t, state) + } + + chainConfig := test.ChainConfig + if chainConfig == nil { + mockChainConfig := precompileconfig.NewMockChainConfig(ctrl) + mockChainConfig.EXPECT().IsDUpgrade(gomock.Any()).AnyTimes().Return(true) + chainConfig = mockChainConfig + } + + blockContext := contract.NewMockBlockContext(ctrl) + if test.SetupBlockContext != nil { + test.SetupBlockContext(blockContext) + } else { + blockContext.EXPECT().Number().Return(big.NewInt(0)).AnyTimes() + blockContext.EXPECT().Timestamp().Return(uint64(time.Now().Unix())).AnyTimes() + } + snowContext := snow.DefaultContextTest() + + accessibleState := contract.NewMockAccessibleState(ctrl) + accessibleState.EXPECT().GetStateDB().Return(state).AnyTimes() + accessibleState.EXPECT().GetBlockContext().Return(blockContext).AnyTimes() + accessibleState.EXPECT().GetSnowContext().Return(snowContext).AnyTimes() + accessibleState.EXPECT().GetChainConfig().Return(chainConfig).AnyTimes() + + if test.Config != nil { + err := module.Configure(chainConfig, test.Config, state, blockContext) + require.NoError(t, err) + } + + input := test.Input + if test.InputFn != nil { + input = test.InputFn(t) + } + + return PrecompileRunparams{ + AccessibleState: accessibleState, + 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(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) + } +} + +func RunPrecompileTests(t *testing.T, module modules.Module, newStateDB func(t testing.TB) contract.StateDB, contractTests map[string]PrecompileTest) { + t.Helper() + + for name, test := range contractTests { + t.Run(name, func(t *testing.T) { + test.Run(t, module, newStateDB(t)) + }) + } +} diff --git a/precompile/testutils/test_predicate.go b/precompile/testutils/test_predicate.go new file mode 100644 index 0000000000..634bdf6257 --- /dev/null +++ b/precompile/testutils/test_predicate.go @@ -0,0 +1,103 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package testutils + +import ( + "testing" + "time" + + "github.com/ava-labs/coreth/precompile/precompileconfig" + cmath "github.com/ethereum/go-ethereum/common/math" + "github.com/stretchr/testify/require" +) + +// PredicateTest defines a unit test/benchmark for verifying a precompile predicate. +type PredicateTest struct { + Config precompileconfig.Config + + PredicateContext *precompileconfig.PredicateContext + + StorageSlots [][]byte + Gas uint64 + GasErr error + PredicateRes []byte +} + +func (test PredicateTest) Run(t testing.TB) { + t.Helper() + require := require.New(t) + + var ( + gas uint64 + gasErr error + predicateRes []byte + predicate = test.Config.(precompileconfig.Predicater) + ) + + for _, predicateBytes := range test.StorageSlots { + predicateGas, predicateGasErr := predicate.PredicateGas(predicateBytes) + if predicateGasErr != nil { + gasErr = predicateGasErr + break + } + updatedGas, overflow := cmath.SafeAdd(gas, predicateGas) + if overflow { + panic("predicate gas should not overflow") + } + gas = updatedGas + } + + if test.GasErr != nil { + // If PredicateGas returns an error, the predicate fails verification and we will + // never call VerifyPredicate. + require.ErrorIs(gasErr, test.GasErr) + return + } else { + require.NoError(gasErr) + } + require.Equal(test.Gas, gas) + + predicateRes = predicate.VerifyPredicate(test.PredicateContext, test.StorageSlots) + require.Equal(test.PredicateRes, predicateRes) +} + +func RunPredicateTests(t *testing.T, predicateTests map[string]PredicateTest) { + t.Helper() + + for name, test := range predicateTests { + t.Run(name, func(t *testing.T) { + test.Run(t) + }) + } +} + +func (test PredicateTest) RunBenchmark(b *testing.B) { + b.ReportAllocs() + start := time.Now() + b.ResetTimer() + for i := 0; i < b.N; i++ { + test.Run(b) + } + b.StopTimer() + elapsed := uint64(time.Since(start)) + if elapsed < 1 { + elapsed = 1 + } + + gasUsed := test.Gas * uint64(b.N) + b.ReportMetric(float64(test.Gas), "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") +} + +func RunPredicateBenchmarks(b *testing.B, predicateTests map[string]PredicateTest) { + b.Helper() + + for name, test := range predicateTests { + b.Run(name, func(b *testing.B) { + test.RunBenchmark(b) + }) + } +} diff --git a/precompile/utils.go b/precompile/utils.go deleted file mode 100644 index f185b2faa1..0000000000 --- a/precompile/utils.go +++ /dev/null @@ -1,34 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package precompile - -import ( - "fmt" - "regexp" - - "github.com/ava-labs/coreth/vmerrs" - "github.com/ethereum/go-ethereum/crypto" -) - -var functionSignatureRegex = regexp.MustCompile(`[\w]+\(((([\w]+)?)|((([\w]+),)+([\w]+)))\)`) - -// 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)" -func CalculateFunctionSelector(functionSignature string) []byte { - if !functionSignatureRegex.MatchString(functionSignature) { - panic(fmt.Errorf("invalid function signature: %q", functionSignature)) - } - hash := crypto.Keccak256([]byte(functionSignature)) - return hash[:4] -} - -// deductGas checks if [suppliedGas] is sufficient against [requiredGas] and deducts [requiredGas] from [suppliedGas]. -//nolint:unused,deadcode -func deductGas(suppliedGas uint64, requiredGas uint64) (uint64, error) { - if suppliedGas < requiredGas { - return 0, vmerrs.ErrOutOfGas - } - return suppliedGas - requiredGas, nil -} diff --git a/predicate/Predicate.md b/predicate/Predicate.md new file mode 100644 index 0000000000..b35cbfd145 --- /dev/null +++ b/predicate/Predicate.md @@ -0,0 +1,11 @@ +# Predicate + +This package contains the predicate data structure and its encoding and helper functions to unpack/pack the data structure. + +## Encoding + +A byte slice of size N is encoded as: + +1. Slice of N bytes +2. Delimiter byte `0xff` +3. Appended 0s to the nearest multiple of 32 bytes diff --git a/predicate/Results.md b/predicate/Results.md new file mode 100644 index 0000000000..67e6465030 --- /dev/null +++ b/predicate/Results.md @@ -0,0 +1,126 @@ +# Results + +The results package defines how to encode `PredicateResults` within the block header's `Extra` data field. + +For more information on the motivation for encoding the results of predicate verification within a block, see [here](../../x/warp/README.md#re-processing-historical-blocks). + +## Serialization + +Note: PredicateResults are encoded using the AvalancheGo codec, which serializes a map by serializing the length of the map as a uint32 and then serializes each key-value pair sequentially. + +PredicateResults: +``` ++---------------------+----------------------------------+-------------------+ +| codecID : uint16 | 2 bytes | ++---------------------+----------------------------------+-------------------+ +| results : map[[32]byte]TxPredicateResults | 4 + size(results) | ++---------------------+----------------------------------+-------------------+ + | 6 + size(results) | + +-------------------+ +``` + +- `codecID` is the codec version used to serialize the payload and is hardcoded to `0x0000` +- `results` is a map of transaction hashes to the corresponding `TxPredicateResults` + +TxPredicateResults +``` ++--------------------+---------------------+------------------------------------+ +| txPredicateResults : map[[20]byte][]byte | 4 + size(txPredicateResults) bytes | ++--------------------+---------------------+------------------------------------+ + | 4 + size(txPredicateResults) bytes | + +------------------------------------+ +``` + +- `txPredicateResults` is a map of precompile addresses to the corresponding byte array returned by the predicate + +### Examples + +#### Empty Predicate Results Map + +``` +// codecID +0x00, 0x00, +// results length +0x00, 0x00, 0x00, 0x00 +``` + +#### Predicate Map with a Single Transaction Result + +``` +// codecID +0x00, 0x00, +// Results length +0x00, 0x00, 0x00, 0x01, +// txHash (key in results map) +0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// TxPredicateResults (value in results map) +// TxPredicateResults length +0x00, 0x00, 0x00, 0x01, +// precompile address (key in TxPredicateResults map) +0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, +// Byte array results (value in TxPredicateResults map) +// Length of bytes result +0x00, 0x00, 0x00, 0x03, +// bytes +0x01, 0x02, 0x03 +``` + +#### Predicate Map with Two Transaction Results + +``` +// codecID +0x00, 0x00, +// Results length +0x00, 0x00, 0x00, 0x02, +// txHash (key in results map) +0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// TxPredicateResults (value in results map) +// TxPredicateResults length +0x00, 0x00, 0x00, 0x01, +// precompile address (key in TxPredicateResults map) +0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, +// Byte array results (value in TxPredicateResults map) +// Length of bytes result +0x00, 0x00, 0x00, 0x03, +// bytes +0x01, 0x02, 0x03 +// txHash2 (key in results map) +0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +// TxPredicateResults (value in results map) +// TxPredicateResults length +0x00, 0x00, 0x00, 0x01, +// precompile address (key in TxPredicateResults map) +0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, +// Byte array results (value in TxPredicateResults map) +// Length of bytes result +0x00, 0x00, 0x00, 0x03, +// bytes +0x01, 0x02, 0x03 +``` + +### Maximum Size + +Results has a maximum size of 1MB enforced by the codec. The actual size depends on how much data the Precompile predicates may put into the results, the gas cost they charge, and the block gas limit. + +The Results maximum size should comfortably exceed the maximum value that could happen in practice, so that a correct block builder will not attempt to build a block and fail to marshal the predicate results using the codec. + +We make this easy to reason about by assigning a minimum gas cost to the `PredicateGas` function of precompiles. In the case of Warp, the minimum gas cost is set to 200k gas, which can lead to at most 32 additional bytes being included in Results. + +The additional bytes come from the transaction hash (32 bytes), length of tx predicate results (4 bytes), the precompile address (20 bytes), length of the bytes result (4 bytes), and the additional byte in the results bitset (1 byte). This results in 200k gas contributing a maximum of 61 additional bytes to Result. + +For a block with a maximum gas limit of 100M, the block can include up to 500 validated predicates based contributing to the size of Result. At 61 bytes / validated predicate, this yields ~30KB, which is well short of the 1MB cap. diff --git a/predicate/predicate_bytes.go b/predicate/predicate_bytes.go new file mode 100644 index 0000000000..35bce3e1e4 --- /dev/null +++ b/predicate/predicate_bytes.go @@ -0,0 +1,64 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package predicate + +import ( + "fmt" + + "github.com/ava-labs/coreth/params" + "github.com/ethereum/go-ethereum/common" +) + +// EndByte is used as a delimiter for the bytes packed into a precompile predicate. +// Precompile predicates are encoded in the Access List of transactions in the access tuples +// which means that its length must be a multiple of 32 (common.HashLength). +// For messages with a length that does not comply to that, this delimiter is used to +// append/remove padding. +var EndByte = byte(0xff) + +var ( + ErrInvalidAllZeroBytes = fmt.Errorf("predicate specified invalid all zero bytes") + ErrInvalidPadding = fmt.Errorf("predicate specified invalid padding") + ErrInvalidEndDelimiter = fmt.Errorf("invalid end delimiter") + ErrorInvalidExtraData = fmt.Errorf("header extra data too short for predicate verification") +) + +// PackPredicate packs [predicate] by delimiting the actual message with [PredicateEndByte] +// and zero padding to reach a length that is a multiple of 32. +func PackPredicate(predicateBytes []byte) []byte { + predicateBytes = append(predicateBytes, EndByte) + return common.RightPadBytes(predicateBytes, (len(predicateBytes)+31)/32*32) +} + +// UnpackPredicate unpacks a predicate by stripping right padded zeroes, checking for the delimter, +// ensuring there is not excess padding, and returning the original message. +// Returns an error if it finds an incorrect encoding. +func UnpackPredicate(paddedPredicate []byte) ([]byte, error) { + trimmedPredicateBytes := common.TrimRightZeroes(paddedPredicate) + if len(trimmedPredicateBytes) == 0 { + return nil, fmt.Errorf("%w: 0x%x", ErrInvalidAllZeroBytes, paddedPredicate) + } + + if expectedPaddedLength := (len(trimmedPredicateBytes) + 31) / 32 * 32; expectedPaddedLength != len(paddedPredicate) { + return nil, fmt.Errorf("%w: got length (%d), expected length (%d)", ErrInvalidPadding, len(paddedPredicate), expectedPaddedLength) + } + + if trimmedPredicateBytes[len(trimmedPredicateBytes)-1] != EndByte { + return nil, ErrInvalidEndDelimiter + } + + return trimmedPredicateBytes[:len(trimmedPredicateBytes)-1], nil +} + +// GetPredicateResultBytes returns the predicate result bytes from the extra data and +// true iff the predicate results bytes have non-zero length. +func GetPredicateResultBytes(extraData []byte) ([]byte, bool) { + // Prior to the DUpgrade, the VM enforces the extra data is smaller than or equal to this size. + // After the DUpgrade, the VM pre-verifies the extra data past the dynamic fee rollup window is + // valid. + if len(extraData) <= params.DynamicFeeExtraDataSize { + return nil, false + } + return extraData[params.DynamicFeeExtraDataSize:], true +} diff --git a/predicate/predicate_bytes_test.go b/predicate/predicate_bytes_test.go new file mode 100644 index 0000000000..1bc4481786 --- /dev/null +++ b/predicate/predicate_bytes_test.go @@ -0,0 +1,66 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package predicate + +import ( + "bytes" + "testing" + + "github.com/ava-labs/avalanchego/utils" + "github.com/ava-labs/coreth/params" + "github.com/stretchr/testify/require" +) + +func testPackPredicate(t testing.TB, b []byte) { + packedPredicate := PackPredicate(b) + unpackedPredicated, err := UnpackPredicate(packedPredicate) + require.NoError(t, err) + require.Equal(t, b, unpackedPredicated) +} + +func FuzzPackPredicate(f *testing.F) { + for i := 0; i < 100; i++ { + f.Add(utils.RandomBytes(i)) + } + + f.Fuzz(func(t *testing.T, b []byte) { + testPackPredicate(t, b) + }) +} + +func TestUnpackInvalidPredicate(t *testing.T) { + require := require.New(t) + // Predicate encoding requires a 0xff delimiter byte followed by padding of all zeroes, so any other + // excess padding should invalidate the predicate. + paddingCases := make([][]byte, 0, 200) + for i := 1; i < 100; i++ { + paddingCases = append(paddingCases, bytes.Repeat([]byte{0xee}, i)) + paddingCases = append(paddingCases, make([]byte, i)) + } + + for _, l := range []int{0, 1, 31, 32, 33, 63, 64, 65} { + validPredicate := PackPredicate(utils.RandomBytes(l)) + + for _, padding := range paddingCases { + invalidPredicate := append(validPredicate, padding...) + _, err := UnpackPredicate(invalidPredicate) + require.Error(err, "Predicate length %d, Padding length %d (0x%x)", len(validPredicate), len(padding), invalidPredicate) + } + } +} + +func TestPredicateResultsBytes(t *testing.T) { + require := require.New(t) + dataTooShort := utils.RandomBytes(params.DynamicFeeExtraDataSize - 1) + _, ok := GetPredicateResultBytes(dataTooShort) + require.False(ok) + + preDUPgradeData := utils.RandomBytes(params.DynamicFeeExtraDataSize) + _, ok = GetPredicateResultBytes(preDUPgradeData) + require.False(ok) + postDUpgradeData := utils.RandomBytes(params.DynamicFeeExtraDataSize + 2) + resultBytes, ok := GetPredicateResultBytes(postDUpgradeData) + require.True(ok) + require.Equal(resultBytes, postDUpgradeData[params.DynamicFeeExtraDataSize:]) +} diff --git a/predicate/predicate_results.go b/predicate/predicate_results.go new file mode 100644 index 0000000000..f87b181f44 --- /dev/null +++ b/predicate/predicate_results.go @@ -0,0 +1,113 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package predicate + +import ( + "fmt" + "strings" + + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/codec/linearcodec" + "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/avalanchego/utils/wrappers" + "github.com/ethereum/go-ethereum/common" +) + +const ( + Version = uint16(0) + MaxResultsSize = units.MiB +) + +var Codec codec.Manager + +func init() { + Codec = codec.NewManager(MaxResultsSize) + + c := linearcodec.NewDefault() + errs := wrappers.Errs{} + errs.Add( + c.RegisterType(Results{}), + Codec.RegisterCodec(Version, c), + ) + if errs.Errored() { + panic(errs.Err) + } +} + +// TxResults is a map of results for each precompile address to the resulting byte array. +type TxResults map[common.Address][]byte + +// Results encodes the precompile predicate results included in a block on a per transaction basis. +// Results is not thread-safe. +type Results struct { + Results map[common.Hash]TxResults `serialize:"true"` +} + +// NewResults returns an empty predicate results. +func NewResults() *Results { + return &Results{ + Results: make(map[common.Hash]TxResults), + } +} + +func NewResultsFromMap(results map[common.Hash]TxResults) *Results { + return &Results{ + Results: results, + } +} + +// ParseResults parses [b] into predicate results. +func ParseResults(b []byte) (*Results, error) { + res := new(Results) + parsedVersion, err := Codec.Unmarshal(b, res) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal predicate results: %w", err) + } + if parsedVersion != Version { + return nil, fmt.Errorf("invalid version (found %d, expected %d)", parsedVersion, Version) + } + return res, nil +} + +// GetResults returns the byte array results for [txHash] from precompile [address] if available. +func (r *Results) GetResults(txHash common.Hash, address common.Address) []byte { + txResults, ok := r.Results[txHash] + if !ok { + return nil + } + return txResults[address] +} + +// SetTxResults sets the predicate results for the given [txHash]. Overrides results if present. +func (r *Results) SetTxResults(txHash common.Hash, txResults TxResults) { + // If there are no tx results, don't store an entry in the map + if len(txResults) == 0 { + delete(r.Results, txHash) + return + } + r.Results[txHash] = txResults +} + +// DeleteTxResults deletes the predicate results for the given [txHash]. +func (r *Results) DeleteTxResults(txHash common.Hash) { + delete(r.Results, txHash) +} + +// Bytes marshals the current state of predicate results +func (r *Results) Bytes() ([]byte, error) { + return Codec.Marshal(Version, r) +} + +func (r *Results) String() string { + sb := strings.Builder{} + + sb.WriteString(fmt.Sprintf("PredicateResults: (Size = %d)", len(r.Results))) + for txHash, results := range r.Results { + for address, result := range results { + sb.WriteString(fmt.Sprintf("\n%s %s: %x", txHash, address, result)) + } + } + + return sb.String() +} diff --git a/predicate/predicate_results_test.go b/predicate/predicate_results_test.go new file mode 100644 index 0000000000..e3daefa706 --- /dev/null +++ b/predicate/predicate_results_test.go @@ -0,0 +1,128 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package predicate + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestPredicateResultsParsing(t *testing.T) { + type test struct { + results map[common.Hash]TxResults + expectedHex string + } + for name, test := range map[string]test{ + "empty": { + results: make(map[common.Hash]TxResults), + expectedHex: "000000000000", + }, + "single tx no results": { + results: map[common.Hash]TxResults{ + {1}: map[common.Address][]byte{}, + }, + expectedHex: "000000000001010000000000000000000000000000000000000000000000000000000000000000000000", + }, + "single tx single result": { + results: map[common.Hash]TxResults{ + {1}: map[common.Address][]byte{ + {2}: {1, 2, 3}, + }, + }, + expectedHex: "000000000001010000000000000000000000000000000000000000000000000000000000000000000001020000000000000000000000000000000000000000000003010203", + }, + "single tx multiple results": { + results: map[common.Hash]TxResults{ + {1}: map[common.Address][]byte{ + {2}: {1, 2, 3}, + {3}: {1, 2, 3}, + }, + }, + expectedHex: "000000000001010000000000000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000003010203030000000000000000000000000000000000000000000003010203", + }, + "multiple txs no result": { + results: map[common.Hash]TxResults{ + {1}: map[common.Address][]byte{}, + {2}: map[common.Address][]byte{}, + }, + expectedHex: "000000000002010000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000", + }, + "multiple txs single result": { + results: map[common.Hash]TxResults{ + {1}: map[common.Address][]byte{ + {2}: {1, 2, 3}, + }, + {2}: map[common.Address][]byte{ + {3}: {3, 2, 1}, + }, + }, + expectedHex: "000000000002010000000000000000000000000000000000000000000000000000000000000000000001020000000000000000000000000000000000000000000003010203020000000000000000000000000000000000000000000000000000000000000000000001030000000000000000000000000000000000000000000003030201", + }, + "multiple txs multiple results": { + results: map[common.Hash]TxResults{ + {1}: map[common.Address][]byte{ + {2}: {1, 2, 3}, + {3}: {3, 2, 1}, + }, + {2}: map[common.Address][]byte{ + {2}: {1, 2, 3}, + {3}: {3, 2, 1}, + }, + }, + expectedHex: "000000000002010000000000000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000003010203030000000000000000000000000000000000000000000003030201020000000000000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000003010203030000000000000000000000000000000000000000000003030201", + }, + "multiple txs mixed results": { + results: map[common.Hash]TxResults{ + {1}: map[common.Address][]byte{ + {2}: {1, 2, 3}, + }, + {2}: map[common.Address][]byte{ + {2}: {1, 2, 3}, + {3}: {3, 2, 1}, + }, + {3}: map[common.Address][]byte{}, + }, + expectedHex: "000000000003010000000000000000000000000000000000000000000000000000000000000000000001020000000000000000000000000000000000000000000003010203020000000000000000000000000000000000000000000000000000000000000000000002020000000000000000000000000000000000000000000003010203030000000000000000000000000000000000000000000003030201030000000000000000000000000000000000000000000000000000000000000000000000", + }, + } { + t.Run(name, func(t *testing.T) { + require := require.New(t) + predicateResults := NewResultsFromMap(test.results) + b, err := predicateResults.Bytes() + require.NoError(err) + + parsedPredicateResults, err := ParseResults(b) + require.NoError(err) + require.Equal(predicateResults, parsedPredicateResults) + require.Equal(test.expectedHex, common.Bytes2Hex(b)) + }) + } +} + +func TestPredicateResultsAccessors(t *testing.T) { + require := require.New(t) + + predicateResults := NewResults() + + txHash := common.Hash{1} + addr := common.Address{2} + predicateResult := []byte{1, 2, 3} + txPredicateResults := map[common.Address][]byte{ + addr: predicateResult, + } + + require.Empty(predicateResults.GetResults(txHash, addr)) + predicateResults.SetTxResults(txHash, txPredicateResults) + require.Equal(predicateResult, predicateResults.GetResults(txHash, addr)) + predicateResults.DeleteTxResults(txHash) + require.Empty(predicateResults.GetResults(txHash, addr)) + + // Ensure setting empty tx predicate results removes the entry + predicateResults.SetTxResults(txHash, txPredicateResults) + require.Equal(predicateResult, predicateResults.GetResults(txHash, addr)) + predicateResults.SetTxResults(txHash, map[common.Address][]byte{}) + require.Empty(predicateResults.GetResults(txHash, addr)) +} diff --git a/predicate/predicate_slots.go b/predicate/predicate_slots.go new file mode 100644 index 0000000000..6eccc5bcae --- /dev/null +++ b/predicate/predicate_slots.go @@ -0,0 +1,27 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package predicate + +import ( + "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/params" + "github.com/ava-labs/coreth/utils" + "github.com/ethereum/go-ethereum/common" +) + +// PreparePredicateStorageSlots populates the the predicate storage slots of a transaction's access list +// Note: if an address is specified multiple times in the access list, each storage slot for that address is +// appended to a slice of byte slices. Each byte slice represents a predicate, making it a slice of predicates +// for each access list address, and every predicate in the slice goes through verification. +func PreparePredicateStorageSlots(rules params.Rules, list types.AccessList) map[common.Address][][]byte { + predicateStorageSlots := make(map[common.Address][][]byte) + for _, el := range list { + if !rules.PredicaterExists(el.Address) { + continue + } + predicateStorageSlots[el.Address] = append(predicateStorageSlots[el.Address], utils.HashSliceToBytes(el.StorageKeys)) + } + + return predicateStorageSlots +} diff --git a/predicate/predicate_tx.go b/predicate/predicate_tx.go new file mode 100644 index 0000000000..76bb3ce6bd --- /dev/null +++ b/predicate/predicate_tx.go @@ -0,0 +1,44 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package predicate + +import ( + "math/big" + + "github.com/ava-labs/coreth/core/types" + "github.com/ava-labs/coreth/utils" + "github.com/ethereum/go-ethereum/common" +) + +// NewPredicateTx returns a transaction with the predicateAddress/predicateBytes tuple +// packed and added to the access list of the transaction. +func NewPredicateTx( + chainID *big.Int, + nonce uint64, + to *common.Address, + gas uint64, + gasFeeCap *big.Int, + gasTipCap *big.Int, + value *big.Int, + data []byte, + accessList types.AccessList, + predicateAddress common.Address, + predicateBytes []byte, +) *types.Transaction { + accessList = append(accessList, types.AccessTuple{ + Address: predicateAddress, + StorageKeys: utils.BytesToHashSlice(PackPredicate(predicateBytes)), + }) + return types.NewTx(&types.DynamicFeeTx{ + ChainID: chainID, + Nonce: nonce, + To: to, + Gas: gas, + GasFeeCap: gasFeeCap, + GasTipCap: gasTipCap, + Value: value, + Data: data, + AccessList: accessList, + }) +} diff --git a/utils/address_range.go b/utils/address_range.go new file mode 100644 index 0000000000..940c39e8a1 --- /dev/null +++ b/utils/address_range.go @@ -0,0 +1,23 @@ +// (c) 2021-2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package utils + +import ( + "bytes" + + "github.com/ethereum/go-ethereum/common" +) + +// AddressRange represents a continuous range of addresses +type AddressRange struct { + Start common.Address + End common.Address +} + +// Contains returns true iff [addr] is contained within the (inclusive) +// range of addresses defined by [a]. +func (a *AddressRange) Contains(addr common.Address) bool { + addrBytes := addr.Bytes() + return bytes.Compare(addrBytes, a.Start[:]) >= 0 && bytes.Compare(addrBytes, a.End[:]) <= 0 +} diff --git a/utils/bytes.go b/utils/bytes.go index 186e3c41ef..54258b20f4 100644 --- a/utils/bytes.go +++ b/utils/bytes.go @@ -3,6 +3,8 @@ package utils +import "github.com/ethereum/go-ethereum/common" + // IncrOne increments bytes value by one func IncrOne(bytes []byte) { index := len(bytes) - 1 @@ -16,3 +18,27 @@ func IncrOne(bytes []byte) { } } } + +// HashSliceToBytes serializes a []common.Hash into a tightly packed byte array. +func HashSliceToBytes(hashes []common.Hash) []byte { + bytes := make([]byte, common.HashLength*len(hashes)) + for i, hash := range hashes { + copy(bytes[i*common.HashLength:], hash[:]) + } + return bytes +} + +// BytesToHashSlice packs [b] into a slice of hash values with zero padding +// to the right if the length of b is not a multiple of 32. +func BytesToHashSlice(b []byte) []common.Hash { + var ( + numHashes = (len(b) + 31) / 32 + hashes = make([]common.Hash, numHashes) + ) + + for i := range hashes { + start := i * common.HashLength + copy(hashes[i][:], b[start:]) + } + return hashes +} diff --git a/utils/bytes_test.go b/utils/bytes_test.go new file mode 100644 index 0000000000..b1bbc8fa6b --- /dev/null +++ b/utils/bytes_test.go @@ -0,0 +1,66 @@ +// (c) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package utils + +import ( + "bytes" + "testing" + + "github.com/ava-labs/avalanchego/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIncrOne(t *testing.T) { + type test struct { + input []byte + expected []byte + } + for name, test := range map[string]test{ + "increment no overflow no carry": { + input: []byte{0, 0}, + expected: []byte{0, 1}, + }, + "increment overflow": { + input: []byte{255, 255}, + expected: []byte{0, 0}, + }, + "increment carry": { + input: []byte{0, 255}, + expected: []byte{1, 0}, + }, + } { + t.Run(name, func(t *testing.T) { + output := common.CopyBytes(test.input) + IncrOne(output) + assert.Equal(t, output, test.expected) + }) + } +} + +func testBytesToHashSlice(t testing.TB, b []byte) { + hashSlice := BytesToHashSlice(b) + + copiedBytes := HashSliceToBytes(hashSlice) + + if len(b)%32 == 0 { + require.Equal(t, b, copiedBytes) + } else { + require.Equal(t, b, copiedBytes[:len(b)]) + // Require that any additional padding is all zeroes + padding := copiedBytes[len(b):] + require.Equal(t, bytes.Repeat([]byte{0x00}, len(padding)), padding) + } +} + +func FuzzHashSliceToBytes(f *testing.F) { + for i := 0; i < 100; i++ { + f.Add(utils.RandomBytes(i)) + } + + f.Fuzz(func(t *testing.T, b []byte) { + testBytesToHashSlice(t, b) + }) +} diff --git a/utils/fork.go b/utils/fork.go index fce1fba617..381e256165 100644 --- a/utils/fork.go +++ b/utils/fork.go @@ -52,3 +52,12 @@ func IsForkTransition(fork *uint64, parent *uint64, current uint64) bool { currentForked := IsTimestampForked(fork, current) return !parentForked && currentForked } + +// Uint64PtrEqual returns true if x and y pointers are equivalent ie. both nil or both +// contain the same value. +func Uint64PtrEqual(x, y *uint64) bool { + if x == nil || y == nil { + return x == y + } + return *x == *y +}