// Copyright 2024 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . package core import ( "bytes" "encoding/binary" "encoding/hex" "math/big" "slices" "testing" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "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/triedb" "github.com/ethereum/go-verkle" "github.com/holiman/uint256" ) var ( testVerkleChainConfig = ¶ms.ChainConfig{ ChainID: big.NewInt(1), 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), VerkleTime: u64(0), TerminalTotalDifficulty: common.Big0, // TODO uncomment when proof generation is merged // ProofInBlocks: true, } testKaustinenLikeChainConfig = ¶ms.ChainConfig{ ChainID: big.NewInt(69420), 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), VerkleTime: u64(0), TerminalTotalDifficulty: common.Big0, } ) func TestProcessVerkle(t *testing.T) { var ( code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`) intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, true, true, true, true) // A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness // will not contain that copied data. // Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985 codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`) intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, true, true, true, true) signer = types.LatestSigner(testVerkleChainConfig) testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain coinbase = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7") gspec = &Genesis{ Config: testVerkleChainConfig, Alloc: GenesisAlloc{ coinbase: { Balance: big.NewInt(1000000000000000000), // 1 ether Nonce: 0, }, params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0}, params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0}, params.WithdrawalQueueAddress: {Nonce: 1, Code: params.WithdrawalQueueCode, Balance: common.Big0}, params.ConsolidationQueueAddress: {Nonce: 1, Code: params.ConsolidationQueueCode, Balance: common.Big0}, }, } ) // Verkle trees use the snapshot, which must be enabled before the // data is saved into the tree+database. // genesis := gspec.MustCommit(bcdb, triedb) cacheConfig := DefaultCacheConfigWithScheme(rawdb.PathScheme) cacheConfig.SnapshotLimit = 0 blockchain, _ := NewBlockChain(bcdb, cacheConfig, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil) defer blockchain.Stop() txCost1 := params.TxGas txCost2 := params.TxGas contractCreationCost := intrinsicContractCreationGas + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* creation with value */ 739 /* execution costs */ codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (tx) */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at pc=0x20) */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #0 */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #1 */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #2 */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #3 */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #4 */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #5 */ params.WitnessChunkReadCost + /* SLOAD in constructor */ params.WitnessChunkWriteCost + /* SSTORE in constructor */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at PC=0x121) */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #0 */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #1 */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #2 */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #3 */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #4 */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* code chunk #5 */ params.WitnessChunkReadCost + /* SLOAD in constructor */ params.WitnessChunkWriteCost + /* SSTORE in constructor */ params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash for tx creation */ 15*(params.WitnessChunkReadCost+params.WitnessChunkWriteCost) + /* code chunks #0..#14 */ 4844 /* execution costs */ blockGasUsagesExpected := []uint64{ txCost1*2 + txCost2, txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas, } _, chain, _, proofs, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 2, func(i int, gen *BlockGen) { gen.SetPoS() // TODO need to check that the tx cost provided is the exact amount used (no remaining left-over) tx, _ := types.SignTx(types.NewTransaction(uint64(i)*3, common.Address{byte(i), 2, 3}, big.NewInt(999), txCost1, big.NewInt(875000000), nil), signer, testKey) gen.AddTx(tx) tx, _ = types.SignTx(types.NewTransaction(uint64(i)*3+1, common.Address{}, big.NewInt(999), txCost1, big.NewInt(875000000), nil), signer, testKey) gen.AddTx(tx) tx, _ = types.SignTx(types.NewTransaction(uint64(i)*3+2, common.Address{}, big.NewInt(0), txCost2, big.NewInt(875000000), nil), signer, testKey) gen.AddTx(tx) // Add two contract creations in block #2 if i == 1 { tx, _ = types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 6, Value: big.NewInt(16), Gas: 3000000, GasPrice: big.NewInt(875000000), Data: code, }) gen.AddTx(tx) tx, _ = types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 7, Value: big.NewInt(0), Gas: 3000000, GasPrice: big.NewInt(875000000), Data: codeWithExtCodeCopy, }) gen.AddTx(tx) } }) // Check proof for both blocks err := verkle.Verify(proofs[0], gspec.ToBlock().Root().Bytes(), chain[0].Root().Bytes(), statediffs[0]) if err != nil { t.Fatal(err) } err = verkle.Verify(proofs[1], chain[0].Root().Bytes(), chain[1].Root().Bytes(), statediffs[1]) if err != nil { t.Fatal(err) } t.Log("verified verkle proof, inserting blocks into the chain") endnum, err := blockchain.InsertChain(chain) if err != nil { t.Fatalf("block %d imported with error: %v", endnum, err) } for i := 0; i < 2; i++ { b := blockchain.GetBlockByNumber(uint64(i) + 1) if b == nil { t.Fatalf("expected block %d to be present in chain", i+1) } if b.Hash() != chain[i].Hash() { t.Fatalf("block #%d not found at expected height", b.NumberU64()) } if b.GasUsed() != blockGasUsagesExpected[i] { t.Fatalf("expected block #%d txs to use %d, got %d\n", b.NumberU64(), blockGasUsagesExpected[i], b.GasUsed()) } } } func TestProcessParentBlockHash(t *testing.T) { // This test uses blocks where, // block 1 parent hash is 0x0100.... // block 2 parent hash is 0x0200.... // etc checkBlockHashes := func(statedb *state.StateDB) { statedb.SetNonce(params.HistoryStorageAddress, 1) statedb.SetCode(params.HistoryStorageAddress, params.HistoryStorageCode) // Process n blocks, from 1 .. num var num = 2 for i := 1; i <= num; i++ { header := &types.Header{ParentHash: common.Hash{byte(i)}, Number: big.NewInt(int64(i)), Difficulty: new(big.Int)} vmContext := NewEVMBlockContext(header, nil, new(common.Address)) evm := vm.NewEVM(vmContext, statedb, params.MergedTestChainConfig, vm.Config{}) ProcessParentBlockHash(header.ParentHash, evm) } // Read block hashes for block 0 .. num-1 for i := 0; i < num; i++ { have, want := getContractStoredBlockHash(statedb, uint64(i)), common.Hash{byte(i + 1)} if have != want { t.Errorf("block %d, have parent hash %v, want %v", i, have, want) } } } t.Run("MPT", func(t *testing.T) { statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) checkBlockHashes(statedb) }) t.Run("Verkle", func(t *testing.T) { db := rawdb.NewMemoryDatabase() cacheConfig := DefaultCacheConfigWithScheme(rawdb.PathScheme) cacheConfig.SnapshotLimit = 0 triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(true)) statedb, _ := state.New(types.EmptyVerkleHash, state.NewDatabase(triedb, nil)) checkBlockHashes(statedb) }) } // getContractStoredBlockHash is a utility method which reads the stored parent blockhash for block 'number' func getContractStoredBlockHash(statedb *state.StateDB, number uint64) common.Hash { ringIndex := number % params.HistoryServeWindow var key common.Hash binary.BigEndian.PutUint64(key[24:], ringIndex) return statedb.GetState(params.HistoryStorageAddress, key) } // TestProcessVerkleInvalidContractCreation checks for several modes of contract creation failures func TestProcessVerkleInvalidContractCreation(t *testing.T) { var ( account1 = common.HexToAddress("0x687704DB07e902e9A8B3754031D168D46E3D586e") account2 = common.HexToAddress("0x6177843db3138ae69679A54b95cf345ED759450d") gspec = verkleTestGenesis(testKaustinenLikeChainConfig) ) // slightly modify it to suit the live txs from the testnet gspec.Alloc[account2] = types.Account{ Balance: big.NewInt(1000000000000000000), // 1 ether Nonce: 1, } // Create two blocks that reproduce what is happening on kaustinen. // - The first block contains two failing contract creation transactions, that // write to storage before they revert. // // - The second block contains a single failing contract creation transaction, // that fails right off the bat. _, chain, _, _, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 2, func(i int, gen *BlockGen) { gen.SetPoS() if i == 0 { for _, rlpData := range []string{ // SSTORE at slot 41 and reverts "f8d48084479c2c18830186a08080b8806000602955bda3f9600060ca55600060695523b360006039551983576000601255b0620c2fde2c592ac2600060bc55e0ac6000606455a63e22600060e655eb607e605c5360a2605d5360c7605e53601d605f5360eb606053606b606153608e60625360816063536079606453601e60655360fc60665360b7606753608b60685383021e7ca0cc20c65a97d2e526b8ec0f4266e8b01bdcde43b9aeb59d8bfb44e8eb8119c109a07a8e751813ae1b2ce734960dbc39a4f954917d7822a2c5d1dca18b06c584131f", // SSTORE at slot 133 and reverts "02f8db83010f2c01843b9aca0084479c2c18830186a08080b88060006085553fad6000600a55600060565555600060b55506600060cf557f1b8b38183e7bd1bdfaa7123c5a4976e54cce0e42049d841411978fd3595e25c66019527f0538943712953cf08900aae40222a40b2d5a4ac8075ad8cf0870e2be307edbb96039527f9f3174ff85024747041ae7a611acffb987c513c088d90ab288aec080a0cd6ac65ce2cb0a912371f6b5a551ba8caffc22ec55ad4d3cb53de41d05eb77b6a02e0dfe8513dfa6ec7bfd7eda6f5c0dac21b39b982436045e128cec46cfd3f960", // this one is a simple transfer that succeeds, necessary to get the correct nonce in the other block. "f8e80184479c2c18830186a094bbbbde4ca27f83fc18aa108170547ff57675936a80b8807ff71f7c15faadb969a76a5f54a81a0117e1e743cb7f24e378eda28442ea4c6eb6604a527fb5409e5718d44e23bfffac926e5ea726067f772772e7e19446acba0c853f62f5606a526020608a536088608b536039608c536004608d5360af608e537f7f7675d9f210e0a61564e6d11e7cd75f5bc9009ac9f6b94a0fc63035441a83021e7ba04a4a172d81ebb02847829b76a387ac09749c8b65668083699abe20c887fb9efca07c5b1a990702ec7b31a5e8e3935cd9a77649f8c25a84131229e24ab61aec6093", } { var tx = new(types.Transaction) if err := tx.UnmarshalBinary(common.Hex2Bytes(rlpData)); err != nil { t.Fatal(err) } gen.AddTx(tx) } } else { var tx = new(types.Transaction) // immediately reverts if err := tx.UnmarshalBinary(common.Hex2Bytes("01f8d683010f2c028443ad7d0e830186a08080b880b00e7fa3c849dce891cce5fae8a4c46cbb313d6aec0c0ffe7863e05fb7b22d4807674c6055527ffbfcb0938f3e18f7937aa8fa95d880afebd5c4cec0d85186095832d03c85cf8a60755260ab60955360cf6096536066609753606e60985360fa609953609e609a53608e609b536024609c5360f6609d536072609e5360a4609fc080a08fc6f7101f292ff1fb0de8ac69c2d320fbb23bfe61cf327173786ea5daee6e37a044c42d91838ef06646294bf4f9835588aee66243b16a66a2da37641fae4c045f")); err != nil { t.Fatal(err) } gen.AddTx(tx) } }) tx1ContractAddress := crypto.CreateAddress(account1, 0) tx1ContractStem := utils.GetTreeKey(tx1ContractAddress[:], uint256.NewInt(0), 105) tx1ContractStem = tx1ContractStem[:31] tx2ContractAddress := crypto.CreateAddress(account2, 1) tx2SlotKey := [32]byte{} tx2SlotKey[31] = 133 tx2ContractStem := utils.StorageSlotKey(tx2ContractAddress[:], tx2SlotKey[:]) tx2ContractStem = tx2ContractStem[:31] eip2935Stem := utils.GetTreeKey(params.HistoryStorageAddress[:], uint256.NewInt(0), 0) eip2935Stem = eip2935Stem[:31] // Check that the witness contains what we expect: a storage entry for each of the two contract // creations that failed: one at 133 for the 2nd tx, and one at 105 for the first tx. for _, stemStateDiff := range statediffs[0] { // Check that the slot number 133, which is overflowing the account header, // is present. Note that the offset of the 2nd group (first group after the // header) is skipping the first 64 values, hence we still have an offset // of 133, and not 133 - 64. if bytes.Equal(stemStateDiff.Stem[:], tx2ContractStem[:]) { for _, suffixDiff := range stemStateDiff.SuffixDiffs { if suffixDiff.Suffix != 133 { t.Fatalf("invalid suffix diff found for %x in block #1: %d\n", stemStateDiff.Stem, suffixDiff.Suffix) } if suffixDiff.CurrentValue != nil { t.Fatalf("invalid prestate value found for %x in block #1: %v != nil\n", stemStateDiff.Stem, suffixDiff.CurrentValue) } if suffixDiff.NewValue != nil { t.Fatalf("invalid poststate value found for %x in block #1: %v != nil\n", stemStateDiff.Stem, suffixDiff.NewValue) } } } else if bytes.Equal(stemStateDiff.Stem[:], tx1ContractStem) { // For this contract creation, check that only the account header and storage slot 41 // are found in the witness. for _, suffixDiff := range stemStateDiff.SuffixDiffs { if suffixDiff.Suffix != 105 && suffixDiff.Suffix != 0 && suffixDiff.Suffix != 1 { t.Fatalf("invalid suffix diff found for %x in block #1: %d\n", stemStateDiff.Stem, suffixDiff.Suffix) } } } else if bytes.Equal(stemStateDiff.Stem[:], eip2935Stem) { // Check the eip 2935 group of leaves. // Check that only one leaf was accessed, and is present in the witness. if len(stemStateDiff.SuffixDiffs) > 1 { t.Fatalf("invalid suffix diff count found for BLOCKHASH contract: %d != 1", len(stemStateDiff.SuffixDiffs)) } // Check that this leaf is the first storage slot if stemStateDiff.SuffixDiffs[0].Suffix != 64 { t.Fatalf("invalid suffix diff value found for BLOCKHASH contract: %d != 64", stemStateDiff.SuffixDiffs[0].Suffix) } // check that the prestate value is nil and that the poststate value isn't. if stemStateDiff.SuffixDiffs[0].CurrentValue != nil { t.Fatalf("non-nil current value in BLOCKHASH contract insert: %x", stemStateDiff.SuffixDiffs[0].CurrentValue) } if stemStateDiff.SuffixDiffs[0].NewValue == nil { t.Fatalf("nil new value in BLOCKHASH contract insert") } if *stemStateDiff.SuffixDiffs[0].NewValue != chain[0].Hash() { t.Fatalf("invalid BLOCKHASH value: %x != %x", *stemStateDiff.SuffixDiffs[0].NewValue, chain[0].Hash()) } } else { // For all other entries present in the witness, check that nothing beyond // the account header was accessed. for _, suffixDiff := range stemStateDiff.SuffixDiffs { if suffixDiff.Suffix > 2 { t.Fatalf("invalid suffix diff found for %x in block #1: %d\n", stemStateDiff.Stem, suffixDiff.Suffix) } } } } // Check that no account has a value above 4 in the 2nd block as no storage nor // code should make it to the witness. for _, stemStateDiff := range statediffs[1] { for _, suffixDiff := range stemStateDiff.SuffixDiffs { if bytes.Equal(stemStateDiff.Stem[:], eip2935Stem) { // BLOCKHASH contract stem if len(stemStateDiff.SuffixDiffs) > 1 { t.Fatalf("invalid suffix diff count found for BLOCKHASH contract at block #2: %d != 1", len(stemStateDiff.SuffixDiffs)) } if stemStateDiff.SuffixDiffs[0].Suffix != 65 { t.Fatalf("invalid suffix diff value found for BLOCKHASH contract at block #2: %d != 65", stemStateDiff.SuffixDiffs[0].Suffix) } if stemStateDiff.SuffixDiffs[0].NewValue == nil { t.Fatalf("missing post state value for BLOCKHASH contract at block #2") } if *stemStateDiff.SuffixDiffs[0].NewValue != common.HexToHash("0788c2c0f23aa07eb8bf76fe6c1ca9064a4821c1fd0af803913da488a58dba54") { t.Fatalf("invalid post state value for BLOCKHASH contract at block #2: 0788c2c0f23aa07eb8bf76fe6c1ca9064a4821c1fd0af803913da488a58dba54 != %x", (*stemStateDiff.SuffixDiffs[0].NewValue)[:]) } } else if suffixDiff.Suffix > 4 { t.Fatalf("invalid suffix diff found for %x in block #2: %d\n", stemStateDiff.Stem, suffixDiff.Suffix) } } } } func verkleTestGenesis(config *params.ChainConfig) *Genesis { var ( coinbase = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7") account1 = common.HexToAddress("0x687704DB07e902e9A8B3754031D168D46E3D586e") account2 = common.HexToAddress("0x6177843db3138ae69679A54b95cf345ED759450d") ) return &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, }, params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode, Balance: common.Big0}, params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0}, params.WithdrawalQueueAddress: {Nonce: 1, Code: params.WithdrawalQueueCode, Balance: common.Big0}, params.ConsolidationQueueAddress: {Nonce: 1, Code: params.ConsolidationQueueCode, Balance: common.Big0}, }, } } // TestProcessVerkleContractWithEmptyCode checks that the witness contains all valid // entries, if the initcode returns an empty code. func TestProcessVerkleContractWithEmptyCode(t *testing.T) { // The test txs were taken from a secondary testnet with chain id 69421 config := *testKaustinenLikeChainConfig config.ChainID.SetUint64(69421) gspec := verkleTestGenesis(&config) _, chain, _, _, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 1, func(i int, gen *BlockGen) { gen.SetPoS() var tx types.Transaction // a transaction that does some PUSH1n but returns a 0-sized contract txpayload := common.Hex2Bytes("02f8db83010f2d03843b9aca008444cf6a05830186a08080b8807fdfbbb59f2371a76485ce557fd0de00c298d3ede52a3eab56d35af674eb49ec5860335260826053536001605453604c60555360f3605653606060575360446058536096605953600c605a5360df605b5360f3605c5360fb605d53600c605e53609a605f53607f60605360fe606153603d60625360f4606353604b60645360cac001a0486b6dc55b8a311568b7239a2cae1d77e7446dba71df61eaafd53f73820a138fa010bd48a45e56133ac4c5645142c2ea48950d40eb35050e9510b6bad9e15c5865") if err := tx.UnmarshalBinary(txpayload); err != nil { t.Fatal(err) } gen.AddTx(&tx) }) eip2935Stem := utils.GetTreeKey(params.HistoryStorageAddress[:], uint256.NewInt(0), 0) eip2935Stem = eip2935Stem[:31] for _, stemStateDiff := range statediffs[0] { // Handle the case of the history contract: make sure only the correct // slots are added to the witness. if bytes.Equal(stemStateDiff.Stem[:], eip2935Stem) { // BLOCKHASH contract stem if len(stemStateDiff.SuffixDiffs) > 1 { t.Fatalf("invalid suffix diff count found for BLOCKHASH contract: %d != 1", len(stemStateDiff.SuffixDiffs)) } if stemStateDiff.SuffixDiffs[0].Suffix != 64 { t.Fatalf("invalid suffix diff value found for BLOCKHASH contract: %d != 64", stemStateDiff.SuffixDiffs[0].Suffix) } // check that the "current value" is nil and that the new value isn't. if stemStateDiff.SuffixDiffs[0].CurrentValue != nil { t.Fatalf("non-nil current value in BLOCKHASH contract insert: %x", stemStateDiff.SuffixDiffs[0].CurrentValue) } if stemStateDiff.SuffixDiffs[0].NewValue == nil { t.Fatalf("nil new value in BLOCKHASH contract insert") } if *stemStateDiff.SuffixDiffs[0].NewValue != chain[0].Hash() { t.Fatalf("invalid BLOCKHASH value: %x != %x", *stemStateDiff.SuffixDiffs[0].NewValue, chain[0].Hash()) } } else { for _, suffixDiff := range stemStateDiff.SuffixDiffs { if suffixDiff.Suffix > 2 { // if d8898012c484fb48610ecb7963886339207dab004bce968b007b616ffa18e0 shows up, it means that the PUSHn // in the transaction above added entries into the witness, when they should not have since they are // part of a contract deployment. t.Fatalf("invalid suffix diff found for %x in block #1: %d\n", stemStateDiff.Stem, suffixDiff.Suffix) } } } } } // TestProcessVerkleExtCodeHashOpcode verifies that calling EXTCODEHASH on another // deployed contract, creates all the right entries in the witness. func TestProcessVerkleExtCodeHashOpcode(t *testing.T) { // The test txs were taken from a secondary testnet with chain id 69421 config := *testKaustinenLikeChainConfig config.ChainID.SetUint64(69421) var ( signer = types.LatestSigner(&config) testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") gspec = verkleTestGenesis(&config) ) dummyContract := []byte{ byte(vm.PUSH1), 2, byte(vm.PUSH1), 12, byte(vm.PUSH1), 0x00, byte(vm.CODECOPY), byte(vm.PUSH1), 2, byte(vm.PUSH1), 0x00, byte(vm.RETURN), byte(vm.PUSH1), 42, } deployer := crypto.PubkeyToAddress(testKey.PublicKey) dummyContractAddr := crypto.CreateAddress(deployer, 0) // contract that calls EXTCODEHASH on the dummy contract extCodeHashContract := []byte{ byte(vm.PUSH1), 22, byte(vm.PUSH1), 12, byte(vm.PUSH1), 0x00, byte(vm.CODECOPY), byte(vm.PUSH1), 22, byte(vm.PUSH1), 0x00, byte(vm.RETURN), byte(vm.PUSH20), 0x3a, 0x22, 0x0f, 0x35, 0x12, 0x52, 0x08, 0x9d, 0x38, 0x5b, 0x29, 0xbe, 0xca, 0x14, 0xe2, 0x7f, 0x20, 0x4c, 0x29, 0x6a, byte(vm.EXTCODEHASH), } extCodeHashContractAddr := crypto.CreateAddress(deployer, 1) _, _, _, _, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 2, func(i int, gen *BlockGen) { gen.SetPoS() if i == 0 { // Create dummy contract. tx, _ := types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 0, Value: big.NewInt(0), Gas: 100_000, GasPrice: big.NewInt(875000000), Data: dummyContract, }) gen.AddTx(tx) // Create contract with EXTCODEHASH opcode. tx, _ = types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 1, Value: big.NewInt(0), Gas: 100_000, GasPrice: big.NewInt(875000000), Data: extCodeHashContract}) 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.CodeHashKey(dummyContractAddr[:]) var stateDiffIdx = -1 for i, stemStateDiff := range statediffs[1] { if bytes.Equal(stemStateDiff.Stem[:], contractKeccakTreeKey[:31]) { stateDiffIdx = i break } } if stateDiffIdx == -1 { t.Fatalf("no state diff found for stem") } codeHashStateDiff := statediffs[1][stateDiffIdx].SuffixDiffs[0] // Check location of code hash was accessed if codeHashStateDiff.Suffix != utils.CodeHashLeafKey { t.Fatalf("code hash invalid suffix") } // check the code hash wasn't present in the prestate, as // the contract was deployed in this block. if codeHashStateDiff.CurrentValue == nil { t.Fatalf("codeHash.CurrentValue must not be empty") } // check the poststate value corresponds to the code hash // of the deployed contract. 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") } } // TestProcessVerkleBalanceOpcode checks that calling balance // on another contract will add the correct entries to the witness. func TestProcessVerkleBalanceOpcode(t *testing.T) { // The test txs were taken from a secondary testnet with chain id 69421 config := *testKaustinenLikeChainConfig config.ChainID.SetUint64(69421) var ( signer = types.LatestSigner(&config) testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") account2 = common.HexToAddress("0x6177843db3138ae69679A54b95cf345ED759450d") gspec = verkleTestGenesis(&config) ) _, _, _, _, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 1, func(i int, gen *BlockGen) { gen.SetPoS() txData := slices.Concat( []byte{byte(vm.PUSH20)}, common.HexToAddress("0x6177843db3138ae69679A54b95cf345ED759450d").Bytes(), []byte{byte(vm.BALANCE)}) tx, _ := types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 0, Value: big.NewInt(0), Gas: 100_000, GasPrice: big.NewInt(875000000), Data: txData}) gen.AddTx(tx) }) account2BalanceTreeKey := utils.BasicDataKey(account2[:]) var stateDiffIdx = -1 for i, stemStateDiff := range statediffs[0] { if bytes.Equal(stemStateDiff.Stem[:], account2BalanceTreeKey[:31]) { stateDiffIdx = i break } } if stateDiffIdx == -1 { t.Fatalf("no state diff found for stem") } var zero [32]byte balanceStateDiff := statediffs[0][stateDiffIdx].SuffixDiffs[0] if balanceStateDiff.Suffix != utils.BasicDataLeafKey { t.Fatalf("invalid suffix diff") } // check the prestate balance wasn't 0 or missing if balanceStateDiff.CurrentValue == nil || *balanceStateDiff.CurrentValue == zero { t.Fatalf("invalid current value %v", *balanceStateDiff.CurrentValue) } // check that the poststate witness value for the balance is nil, // meaning that it didn't get updated. if balanceStateDiff.NewValue != nil { t.Fatalf("invalid new value") } } // TestProcessVerkleSelfDestructInSeparateTx controls the contents of the witness after // a non-eip6780-compliant selfdestruct occurs. func TestProcessVerkleSelfDestructInSeparateTx(t *testing.T) { // The test txs were taken from a secondary testnet with chain id 69421 config := *testKaustinenLikeChainConfig config.ChainID.SetUint64(69421) var ( signer = types.LatestSigner(&config) testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") account2 = common.HexToAddress("0x6177843db3138ae69679A54b95cf345ED759450d") gspec = verkleTestGenesis(&config) ) // runtime code: selfdestruct ( 0x6177843db3138ae69679A54b95cf345ED759450d ) runtimeCode := slices.Concat( []byte{byte(vm.PUSH20)}, account2.Bytes(), []byte{byte(vm.SELFDESTRUCT)}) //The goal of this test is to test SELFDESTRUCT that happens in a contract // execution which is created in a previous transaction. selfDestructContract := slices.Concat([]byte{ byte(vm.PUSH1), byte(len(runtimeCode)), byte(vm.PUSH1), 12, byte(vm.PUSH1), 0x00, byte(vm.CODECOPY), // Codecopy( to-offset: 0, code offset: 12, length: 22 ) byte(vm.PUSH1), byte(len(runtimeCode)), byte(vm.PUSH1), 0x00, byte(vm.RETURN), // Return ( 0 : len(runtimecode) }, runtimeCode) deployer := crypto.PubkeyToAddress(testKey.PublicKey) contract := crypto.CreateAddress(deployer, 0) _, _, _, _, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 2, func(i int, gen *BlockGen) { gen.SetPoS() if i == 0 { // Create selfdestruct contract, sending 42 wei. tx, _ := types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 0, Value: big.NewInt(42), Gas: 100_000, GasPrice: big.NewInt(875000000), Data: selfDestructContract, }) gen.AddTx(tx) } else { // Call it. tx, _ := types.SignTx(types.NewTransaction(1, contract, big.NewInt(0), 100_000, big.NewInt(875000000), nil), signer, testKey) gen.AddTx(tx) } }) var zero [32]byte { // Check self-destructed contract in the witness selfDestructContractTreeKey := utils.CodeHashKey(contract[:]) var stateDiffIdx = -1 for i, stemStateDiff := range statediffs[1] { if bytes.Equal(stemStateDiff.Stem[:], selfDestructContractTreeKey[:31]) { stateDiffIdx = i break } } if stateDiffIdx == -1 { t.Fatalf("no state diff found for stem") } balanceStateDiff := statediffs[1][stateDiffIdx].SuffixDiffs[0] if balanceStateDiff.Suffix != utils.BasicDataLeafKey { t.Fatalf("balance invalid suffix") } // The original balance was 42. var oldBalance [16]byte oldBalance[15] = 42 if !bytes.Equal((*balanceStateDiff.CurrentValue)[utils.BasicDataBalanceOffset:], oldBalance[:]) { t.Fatalf("the pre-state balance before self-destruct must be %x, got %x", oldBalance, *balanceStateDiff.CurrentValue) } // The new balance must be 0. if !bytes.Equal((*balanceStateDiff.NewValue)[utils.BasicDataBalanceOffset:], zero[utils.BasicDataBalanceOffset:]) { t.Fatalf("the post-state balance after self-destruct must be 0") } } { // Check self-destructed target in the witness. selfDestructTargetTreeKey := utils.CodeHashKey(account2[:]) var stateDiffIdx = -1 for i, stemStateDiff := range statediffs[1] { if bytes.Equal(stemStateDiff.Stem[:], selfDestructTargetTreeKey[:31]) { stateDiffIdx = i break } } if stateDiffIdx == -1 { t.Fatalf("no state diff found for stem") } balanceStateDiff := statediffs[1][stateDiffIdx].SuffixDiffs[0] if balanceStateDiff.Suffix != utils.BasicDataLeafKey { t.Fatalf("balance invalid suffix") } if balanceStateDiff.CurrentValue == nil { t.Fatalf("codeHash.CurrentValue must not be empty") } if balanceStateDiff.NewValue == nil { t.Fatalf("codeHash.NewValue must not be empty") } preStateBalance := binary.BigEndian.Uint64(balanceStateDiff.CurrentValue[utils.BasicDataBalanceOffset+8:]) postStateBalance := binary.BigEndian.Uint64(balanceStateDiff.NewValue[utils.BasicDataBalanceOffset+8:]) if postStateBalance-preStateBalance != 42 { t.Fatalf("the post-state balance after self-destruct must be 42, got %d-%d=%d", postStateBalance, preStateBalance, postStateBalance-preStateBalance) } } } // TestProcessVerkleSelfDestructInSeparateTx controls the contents of the witness after // a eip6780-compliant selfdestruct occurs. func TestProcessVerkleSelfDestructInSameTx(t *testing.T) { // The test txs were taken from a secondary testnet with chain id 69421 config := *testKaustinenLikeChainConfig config.ChainID.SetUint64(69421) var ( signer = types.LatestSigner(&config) testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") account2 = common.HexToAddress("0x6177843db3138ae69679A54b95cf345ED759450d") gspec = verkleTestGenesis(&config) ) // The goal of this test is to test SELFDESTRUCT that happens in a contract // execution which is created in **the same** transaction sending the remaining // balance to an external (i.e: not itself) account. selfDestructContract := slices.Concat( []byte{byte(vm.PUSH20)}, account2.Bytes(), []byte{byte(vm.SELFDESTRUCT)}) deployer := crypto.PubkeyToAddress(testKey.PublicKey) contract := crypto.CreateAddress(deployer, 0) _, _, _, _, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 1, func(i int, gen *BlockGen) { gen.SetPoS() tx, _ := types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 0, Value: big.NewInt(42), Gas: 100_000, GasPrice: big.NewInt(875000000), Data: selfDestructContract, }) gen.AddTx(tx) }) { // Check self-destructed contract in the witness selfDestructContractTreeKey := utils.CodeHashKey(contract[:]) var stateDiffIdx = -1 for i, stemStateDiff := range statediffs[0] { if bytes.Equal(stemStateDiff.Stem[:], selfDestructContractTreeKey[:31]) { stateDiffIdx = i break } } if stateDiffIdx == -1 { t.Fatalf("no state diff found for stem") } balanceStateDiff := statediffs[0][stateDiffIdx].SuffixDiffs[0] if balanceStateDiff.Suffix != utils.BasicDataLeafKey { t.Fatalf("balance invalid suffix") } if balanceStateDiff.CurrentValue != nil { t.Fatalf("the pre-state balance before must be nil, since the contract didn't exist") } if balanceStateDiff.NewValue != nil { t.Fatalf("the post-state balance after self-destruct must be nil since the contract shouldn't be created at all") } } { // Check self-destructed target in the witness. selfDestructTargetTreeKey := utils.CodeHashKey(account2[:]) var stateDiffIdx = -1 for i, stemStateDiff := range statediffs[0] { if bytes.Equal(stemStateDiff.Stem[:], selfDestructTargetTreeKey[:31]) { stateDiffIdx = i break } } if stateDiffIdx == -1 { t.Fatalf("no state diff found for stem") } balanceStateDiff := statediffs[0][stateDiffIdx].SuffixDiffs[0] if balanceStateDiff.Suffix != utils.BasicDataLeafKey { t.Fatalf("balance invalid suffix") } if balanceStateDiff.CurrentValue == nil { t.Fatalf("codeHash.CurrentValue must not be empty") } if balanceStateDiff.NewValue == nil { t.Fatalf("codeHash.NewValue must not be empty") } preStateBalance := binary.BigEndian.Uint64(balanceStateDiff.CurrentValue[utils.BasicDataBalanceOffset+8:]) postStateBalance := binary.BigEndian.Uint64(balanceStateDiff.NewValue[utils.BasicDataBalanceOffset+8:]) if postStateBalance-preStateBalance != 42 { t.Fatalf("the post-state balance after self-destruct must be 42. got %d", postStateBalance) } } } // TestProcessVerkleSelfDestructInSeparateTxWithSelfBeneficiary checks the content of the witness // if a selfdestruct occurs in a different tx than the one that created it, but the beneficiary // is the selfdestructed account. func TestProcessVerkleSelfDestructInSeparateTxWithSelfBeneficiary(t *testing.T) { // The test txs were taken from a secondary testnet with chain id 69421 config := *testKaustinenLikeChainConfig config.ChainID.SetUint64(69421) var ( signer = types.LatestSigner(&config) testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") gspec = verkleTestGenesis(&config) ) // The goal of this test is to test SELFDESTRUCT that happens in a contract // execution which is created in a *previous* transaction sending the remaining // balance to itself. selfDestructContract := []byte{ byte(vm.PUSH1), 2, // PUSH1 2 byte(vm.PUSH1), 10, // PUSH1 12 byte(vm.PUSH0), // PUSH0 byte(vm.CODECOPY), // Codecopy ( to offset 0, code@offset: 10, length: 2) byte(vm.PUSH1), 22, byte(vm.PUSH0), byte(vm.RETURN), // RETURN( memory[0:2] ) // Deployed code byte(vm.ADDRESS), byte(vm.SELFDESTRUCT), } deployer := crypto.PubkeyToAddress(testKey.PublicKey) contract := crypto.CreateAddress(deployer, 0) _, _, _, _, statediffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 2, func(i int, gen *BlockGen) { gen.SetPoS() if i == 0 { // Create self-destruct contract, sending 42 wei. tx, _ := types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 0, Value: big.NewInt(42), Gas: 100_000, GasPrice: big.NewInt(875000000), Data: selfDestructContract, }) gen.AddTx(tx) } else { // Call it. tx, _ := types.SignTx(types.NewTransaction(1, contract, big.NewInt(0), 100_000, big.NewInt(875000000), nil), signer, testKey) gen.AddTx(tx) } }) { // Check self-destructed contract in the witness. // The way 6780 is implemented today, it always SubBalance from the self-destructed contract, and AddBalance // to the beneficiary. In this case both addresses are the same, thus this might be optimizable from a gas // perspective. But until that happens, we need to honor this "balance reading" adding it to the witness. selfDestructContractTreeKey := utils.CodeHashKey(contract[:]) var stateDiffIdx = -1 for i, stemStateDiff := range statediffs[1] { if bytes.Equal(stemStateDiff.Stem[:], selfDestructContractTreeKey[:31]) { stateDiffIdx = i break } } if stateDiffIdx == -1 { t.Fatal("no state diff found for stem") } balanceStateDiff := statediffs[1][stateDiffIdx].SuffixDiffs[0] if balanceStateDiff.Suffix != utils.BasicDataLeafKey { t.Fatal("balance invalid suffix") } // The original balance was 42. var oldBalance [16]byte oldBalance[15] = 42 if !bytes.Equal((*balanceStateDiff.CurrentValue)[utils.BasicDataBalanceOffset:], oldBalance[:]) { t.Fatal("the pre-state balance before self-destruct must be 42") } // Note that the SubBalance+AddBalance net effect is a 0 change, so NewValue // must be nil. if balanceStateDiff.NewValue != nil { t.Fatal("the post-state balance after self-destruct must be empty") } } } // TestProcessVerkleSelfDestructInSameTxWithSelfBeneficiary checks the content of the witness // if a selfdestruct occurs in the same tx as the one that created it, but the beneficiary // is the selfdestructed account. func TestProcessVerkleSelfDestructInSameTxWithSelfBeneficiary(t *testing.T) { // The test txs were taken from a secondary testnet with chain id 69421 config := *testKaustinenLikeChainConfig config.ChainID.SetUint64(69421) var ( signer = types.LatestSigner(&config) testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") gspec = verkleTestGenesis(&config) deployer = crypto.PubkeyToAddress(testKey.PublicKey) contract = crypto.CreateAddress(deployer, 0) ) // The goal of this test is to test SELFDESTRUCT that happens while executing // the init code of a contract creation, that occurs in **the same** transaction. // The balance is sent to itself. t.Logf("Contract: %v", contract.String()) selfDestructContract := []byte{byte(vm.ADDRESS), byte(vm.SELFDESTRUCT)} _, _, _, _, stateDiffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 1, func(i int, gen *BlockGen) { gen.SetPoS() tx, _ := types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 0, Value: big.NewInt(42), Gas: 100_000, GasPrice: big.NewInt(875000000), Data: selfDestructContract, }) gen.AddTx(tx) }) stateDiff := stateDiffs[0] // state difference of block 1 { // Check self-destructed contract in the witness selfDestructContractTreeKey := utils.CodeHashKey(contract[:]) var stateDiffIdx = -1 for i, stemStateDiff := range stateDiff { if bytes.Equal(stemStateDiff.Stem[:], selfDestructContractTreeKey[:31]) { stateDiffIdx = i break } } if stateDiffIdx == -1 { t.Fatal("no state diff found for stem") } balanceStateDiff := stateDiff[stateDiffIdx].SuffixDiffs[0] if balanceStateDiff.Suffix != utils.BasicDataLeafKey { t.Fatal("balance invalid suffix") } if balanceStateDiff.CurrentValue != nil { t.Fatal("the pre-state balance before must be nil, since the contract didn't exist") } // Ensure that the value is burnt, and therefore that the balance of the self-destructed // contract isn't modified (it should remain missing from the state) if balanceStateDiff.NewValue != nil { t.Fatal("the post-state balance after self-destruct must be nil since the contract shouldn't be created at all") } } } // TestProcessVerkleSelfDestructInSameTxWithSelfBeneficiaryAndPrefundedAccount checks the // content of the witness if a selfdestruct occurs in the same tx as the one that created it, // it, but the beneficiary is the selfdestructed account. The difference with the test above, // is that the created account is prefunded and so the final value should be 0. func TestProcessVerkleSelfDestructInSameTxWithSelfBeneficiaryAndPrefundedAccount(t *testing.T) { // The test txs were taken from a secondary testnet with chain id 69421 config := *testKaustinenLikeChainConfig config.ChainID.SetUint64(69421) var ( signer = types.LatestSigner(&config) testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") gspec = verkleTestGenesis(&config) deployer = crypto.PubkeyToAddress(testKey.PublicKey) contract = crypto.CreateAddress(deployer, 0) ) // Prefund the account, at an address that the contract will be deployed at, // before it selfdestrucs. We can therefore check that the account itseld is // NOT destroyed, which is what the current version of the spec requires. // TODO(gballet) revisit after the spec has been modified. gspec.Alloc[contract] = types.Account{ Balance: big.NewInt(100), } selfDestructContract := []byte{byte(vm.ADDRESS), byte(vm.SELFDESTRUCT)} _, _, _, _, stateDiffs := GenerateVerkleChainWithGenesis(gspec, beacon.New(ethash.NewFaker()), 1, func(i int, gen *BlockGen) { gen.SetPoS() tx, _ := types.SignNewTx(testKey, signer, &types.LegacyTx{Nonce: 0, Value: big.NewInt(42), Gas: 100_000, GasPrice: big.NewInt(875000000), Data: selfDestructContract, }) gen.AddTx(tx) }) stateDiff := stateDiffs[0] // state difference of block 1 { // Check self-destructed contract in the witness selfDestructContractTreeKey := utils.CodeHashKey(contract[:]) var stateDiffIdx = -1 for i, stemStateDiff := range stateDiff { if bytes.Equal(stemStateDiff.Stem[:], selfDestructContractTreeKey[:31]) { stateDiffIdx = i break } } if stateDiffIdx == -1 { t.Fatal("no state diff found for stem") } balanceStateDiff := stateDiff[stateDiffIdx].SuffixDiffs[0] if balanceStateDiff.Suffix != utils.BasicDataLeafKey { t.Fatal("balance invalid suffix") } expected, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000064") if balanceStateDiff.CurrentValue == nil || !bytes.Equal(balanceStateDiff.CurrentValue[:], expected) { t.Fatalf("incorrect prestate balance: %x != %x", *balanceStateDiff.CurrentValue, expected) } // Ensure that the value is burnt, and therefore that the balance of the self-destructed // contract isn't modified (it should remain missing from the state) expected = make([]byte, 32) if balanceStateDiff.NewValue == nil { t.Fatal("incorrect nil poststate balance") } if !bytes.Equal(balanceStateDiff.NewValue[:], expected[:]) { t.Fatalf("incorrect poststate balance: %x != %x", *balanceStateDiff.NewValue, expected[:]) } } }