diff --git a/core/state_processor_test.go b/core/state_processor_test.go index f2e5c2769c99..1756daaf8c66 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -37,6 +37,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie/utils" //"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" @@ -794,3 +795,135 @@ func TestProcessVerkleContractWithEmptyCode(t *testing.T) { } } } + +func TestProcessVerklExtCodeHashOpcode(t *testing.T) { + var ( + config = ¶ms.ChainConfig{ + ChainID: big.NewInt(69421), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + Ethash: new(params.EthashConfig), + ShanghaiTime: u64(0), + PragueTime: u64(0), + TerminalTotalDifficulty: common.Big0, + TerminalTotalDifficultyPassed: true, + ProofInBlocks: true, + } + signer = types.LatestSigner(config) + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain + gendb = rawdb.NewMemoryDatabase() // Database for the block-generation code, they must be separate as they are path-based. + coinbase = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7") + account1 = common.HexToAddress("0x687704DB07e902e9A8B3754031D168D46E3D586e") + account2 = common.HexToAddress("0x6177843db3138ae69679A54b95cf345ED759450d") + gspec = &Genesis{ + Config: config, + Alloc: GenesisAlloc{ + coinbase: GenesisAccount{ + Balance: big.NewInt(1000000000000000000), // 1 ether + Nonce: 0, + }, + account1: GenesisAccount{ + Balance: big.NewInt(1000000000000000000), // 1 ether + Nonce: 0, + }, + account2: GenesisAccount{ + Balance: big.NewInt(1000000000000000000), // 1 ether + Nonce: 3, + }, + }, + } + ) + // Verkle trees use the snapshot, which must be enabled before the + // data is saved into the tree+database. + genesis := gspec.MustCommit(bcdb) + + // Commit the genesis block to the block-generation database as it + // is now independent of the blockchain database. + gspec.MustCommit(gendb) + + dummyContract := []byte{ + 0x60, 2, // PUSH1 2 + 0x60, 12, // PUSH1 12 + 0x60, 0x00, // PUSH1 0 + 0x39, // CODECOPY + + 0x60, 2, // PUSH1 2 + 0x60, 0x00, // PUSH1 0 + 0xF3, // RETURN + + // Contract that auto-calls EXTCODEHASH + 0x60, 42, // PUSH1 42 + } + dummyContractAddr := common.HexToAddress("3a220f351252089d385b29beca14e27f204c296a") + extCodeHashContract := []byte{ + 0x60, 22, // PUSH1 22 + 0x60, 12, // PUSH1 12 + 0x60, 0x00, // PUSH1 0 + 0x39, // CODECOPY + + 0x60, 22, // PUSH1 22 + 0x60, 0x00, // PUSH1 0 + 0xF3, // RETURN + + // Contract that auto-calls EXTCODEHASH + 0x73, // PUSH20 + 0x3a, 0x22, 0x0f, 0x35, 0x12, 0x52, 0x08, 0x9d, 0x38, 0x5b, 0x29, 0xbe, 0xca, 0x14, 0xe2, 0x7f, 0x20, 0x4c, 0x29, 0x6a, + 0x3F, // EXTCODEHASH + } + extCodeHashContractAddr := common.HexToAddress("db7d6ab1f17c6b31909ae466702703daef9269cf") + _, _, _, statediff := GenerateVerkleChain(gspec.Config, genesis, beacon.New(ethash.NewFaker()), gendb, 2, func(i int, gen *BlockGen) { + gen.SetPoS() + + if i == 0 { + // Create dummy contract. + tx, _ := types.SignTx(types.NewContractCreation(0, big.NewInt(0), 100_000, big.NewInt(875000000), dummyContract), signer, testKey) + gen.AddTx(tx) + + // Create contract with EXTCODEHASH opcode. + tx, _ = types.SignTx(types.NewContractCreation(1, big.NewInt(0), 100_000, big.NewInt(875000000), extCodeHashContract), signer, testKey) + gen.AddTx(tx) + } else { + tx, _ := types.SignTx(types.NewTransaction(2, extCodeHashContractAddr, big.NewInt(0), 100_000, big.NewInt(875000000), nil), signer, testKey) + gen.AddTx(tx) + } + + }) + + contractKeccakTreeKey := utils.GetTreeKeyCodeKeccak(dummyContractAddr[:]) + + var stateDiffIdx = -1 + for i, stemStateDiff := range statediff[1] { + if bytes.Equal(stemStateDiff.Stem[:], contractKeccakTreeKey[:31]) { + stateDiffIdx = i + break + } + } + if stateDiffIdx == -1 { + t.Fatalf("no state diff found for stem") + } + + codeHashStateDiff := statediff[1][stateDiffIdx].SuffixDiffs[0] + if codeHashStateDiff.Suffix != utils.CodeKeccakLeafKey { + t.Fatalf("code hash invalid suffix") + } + if codeHashStateDiff.CurrentValue == nil { + t.Fatalf("codeHash.CurrentValue must not be empty") + } + expCodeHash := crypto.Keccak256Hash(dummyContract[12:]) + if *codeHashStateDiff.CurrentValue != expCodeHash { + t.Fatalf("codeHash.CurrentValue unexpected code hash") + } + if codeHashStateDiff.NewValue != nil { + t.Fatalf("codeHash.NewValue must be nil") + } +} diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 7fb340da7b24..35d9ccc62197 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -485,6 +485,13 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { slot := scope.Stack.peek() address := common.Address(slot.Bytes20()) + if interpreter.evm.chainRules.IsPrague { + statelessGas := interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(slot.Bytes(), uint256.Int{}, trieUtils.CodeKeccakLeafKey) + if !scope.Contract.UseGas(statelessGas) { + scope.Contract.Gas = 0 + return nil, ErrOutOfGas + } + } if interpreter.evm.StateDB.Empty(address) { slot.Clear() } else {