diff --git a/eth/tracers/internal/tracetest/prestate_test.go b/eth/tracers/internal/tracetest/prestate_test.go index 23216fa78c..458d91d031 100644 --- a/eth/tracers/internal/tracetest/prestate_test.go +++ b/eth/tracers/internal/tracetest/prestate_test.go @@ -29,8 +29,11 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "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/eth/tracers" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/tests" + "github.com/holiman/uint256" ) // prestateTrace is the result of a prestateTrace run. @@ -49,6 +52,102 @@ type prestateTracerTest struct { Result interface{} `json:"result"` } +func TestPrestateWithDiffMode_7702Deauth(t *testing.T) { + chainConfig := params.AllDevChainProtocolChanges + authorityKey, _ := crypto.GenerateKey() + senderKey, _ := crypto.GenerateKey() + authorityAddr := crypto.PubkeyToAddress(authorityKey.PublicKey) + senderAddr := crypto.PubkeyToAddress(senderKey.PublicKey) + delegateTarget := common.HexToAddress("0x000000000000000000000000000000000000aaaa") + genesis := &core.Genesis{ + Config: chainConfig, + BaseFee: big.NewInt(params.InitialBaseFee), + GasLimit: 8000000, + Alloc: types.GenesisAlloc{ + authorityAddr: { + Balance: big.NewInt(1e18), + Code: types.AddressToDelegation(delegateTarget), + }, + senderAddr: { + Balance: big.NewInt(1e18), + }, + }, + } + signer := types.LatestSignerForChainID(chainConfig.ChainID) + auth, err := types.SignSetCode(authorityKey, types.SetCodeAuthorization{ + ChainID: *uint256.MustFromBig(chainConfig.ChainID), + Address: common.Address{}, + Nonce: 0, + }) + if err != nil { + t.Fatalf("failed to sign authorization: %v", err) + } + tx := types.MustSignNewTx(senderKey, signer, &types.SetCodeTx{ + ChainID: uint256.MustFromBig(chainConfig.ChainID), + Nonce: 0, + GasFeeCap: uint256.NewInt(params.InitialBaseFee * 2), + GasTipCap: uint256.NewInt(1), + Gas: 100000, + To: senderAddr, + AuthList: []types.SetCodeAuthorization{auth}, + }) + state := tests.MakePreState(rawdb.NewMemoryDatabase(), genesis.Alloc, false, rawdb.HashScheme) + defer state.Close() + tracerCfg := json.RawMessage(`{"diffMode":true}`) + tracer, err := tracers.DefaultDirectory.New("prestateTracer", new(tracers.Context), tracerCfg, chainConfig) + if err != nil { + t.Fatalf("failed to create tracer: %v", err) + } + blockCtx := vm.BlockContext{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + BlockNumber: big.NewInt(1), + Time: 0, + GasLimit: 8000000, + BaseFee: big.NewInt(params.InitialBaseFee), + } + msg, err := core.TransactionToMessage(tx, signer, blockCtx.BaseFee) + if err != nil { + t.Fatalf("failed to convert tx to message: %v", err) + } + evm := vm.NewEVM(blockCtx, state.StateDB, chainConfig, vm.Config{Tracer: tracer.Hooks}) + tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) + vmRet, err := core.ApplyMessage(evm, msg, nil) + if err != nil { + t.Fatalf("failed to apply message: %v", err) + } + tracer.OnTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, nil) + + res, err := tracer.GetResult() + if err != nil { + t.Fatalf("failed to get result: %v", err) + } + var result struct { + Post map[common.Address]json.RawMessage `json:"post"` + } + if err := json.Unmarshal(res, &result); err != nil { + t.Fatalf("failed to unmarshal result: %v", err) + } + postRaw, ok := result.Post[authorityAddr] + if !ok { + t.Fatalf("authority address not found in post state; full result: %s", res) + } + var postAccount struct { + Code string `json:"code"` + CodeHash string `json:"codeHash"` + } + if err := json.Unmarshal(postRaw, &postAccount); err != nil { + t.Fatalf("failed to unmarshal post account: %v", err) + } + if postAccount.Code != "0x" { + t.Errorf("post code: got %q, want %q", postAccount.Code, "0x") + } + wantCodeHash := types.EmptyCodeHash.Hex() + if postAccount.CodeHash != wantCodeHash { + t.Errorf("post codeHash: got %q, want %q", postAccount.CodeHash, wantCodeHash) + } +} + func TestPrestateTracerLegacy(t *testing.T) { testPrestateTracer("prestateTracerLegacy", "prestate_tracer_legacy", t) } diff --git a/eth/tracers/native/gen_account_json.go b/eth/tracers/native/gen_account_json.go index 5fec2648b7..f19cb28a82 100644 --- a/eth/tracers/native/gen_account_json.go +++ b/eth/tracers/native/gen_account_json.go @@ -16,7 +16,7 @@ var _ = (*accountMarshaling)(nil) func (a account) MarshalJSON() ([]byte, error) { type account struct { Balance *hexutil.Big `json:"balance,omitempty"` - Code hexutil.Bytes `json:"code,omitempty"` + Code *hexutil.Bytes `json:"code,omitempty"` CodeHash *common.Hash `json:"codeHash,omitempty"` Nonce uint64 `json:"nonce,omitempty"` Storage map[common.Hash]common.Hash `json:"storage,omitempty"` @@ -47,7 +47,7 @@ func (a *account) UnmarshalJSON(input []byte) error { a.Balance = (*big.Int)(dec.Balance) } if dec.Code != nil { - a.Code = *dec.Code + a.Code = dec.Code } if dec.CodeHash != nil { a.CodeHash = dec.CodeHash diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index 159a91b310..661eb91249 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -45,7 +45,7 @@ type stateMap = map[common.Address]*account type account struct { Balance *big.Int `json:"balance,omitempty"` - Code []byte `json:"code,omitempty"` + Code *hexutil.Bytes `json:"code,omitempty"` CodeHash *common.Hash `json:"codeHash,omitempty"` Nonce uint64 `json:"nonce,omitempty"` Storage map[common.Hash]common.Hash `json:"storage,omitempty"` @@ -53,12 +53,11 @@ type account struct { } func (a *account) exists() bool { - return a.Nonce > 0 || len(a.Code) > 0 || len(a.Storage) > 0 || (a.Balance != nil && a.Balance.Sign() != 0) + return a.Nonce > 0 || (a.Code != nil && len(*a.Code) > 0) || len(a.Storage) > 0 || (a.Balance != nil && a.Balance.Sign() != 0) } type accountMarshaling struct { Balance *hexutil.Big - Code hexutil.Bytes } type prestateTracer struct { @@ -270,20 +269,26 @@ func (t *prestateTracer) processDiffState() { if t.pre[addr].CodeHash != nil { prevCodeHash = *t.pre[addr].CodeHash } - // Empty code hashes are excluded from the prestate. Normalize - // the empty code hash to a zero hash to make it comparable. - if newCodeHash == types.EmptyCodeHash { - newCodeHash = common.Hash{} + // Empty code hashes are excluded from the prestate. Use a normalized + // copy only for comparison; keep the original value for post-state output. + normalizedNewCodeHash := newCodeHash + if normalizedNewCodeHash == types.EmptyCodeHash { + normalizedNewCodeHash = common.Hash{} } - if newCodeHash != prevCodeHash { + if normalizedNewCodeHash != prevCodeHash { modified = true postAccount.CodeHash = &newCodeHash } if !t.config.DisableCode { newCode := t.env.StateDB.GetCode(addr) - if !bytes.Equal(newCode, t.pre[addr].Code) { + var preCode []byte + if t.pre[addr].Code != nil { + preCode = []byte(*t.pre[addr].Code) + } + if !bytes.Equal(newCode, preCode) { modified = true - postAccount.Code = newCode + hcode := hexutil.Bytes(newCode) + postAccount.Code = &hcode // non-nil even if empty, so "0x" is output } } @@ -326,7 +331,10 @@ func (t *prestateTracer) lookupAccount(addr common.Address) { acc := &account{ Balance: t.env.StateDB.GetBalance(addr).ToBig(), Nonce: t.env.StateDB.GetNonce(addr), - Code: t.env.StateDB.GetCode(addr), + } + if code := t.env.StateDB.GetCode(addr); len(code) > 0 { + hcode := hexutil.Bytes(code) + acc.Code = &hcode } codeHash := t.env.StateDB.GetCodeHash(addr) // If the code is empty, we don't need to store it in the prestate.