From fdd4eb84211fd95960e2c494257610f45679095d Mon Sep 17 00:00:00 2001 From: Sina M <1591639+s1na@users.noreply.github.com> Date: Mon, 19 May 2025 14:50:03 +0200 Subject: [PATCH] Updates to the ERC7562 tracer (#57) * refactor keccak preimages * simplify tests * rm depth field * more interrup handling * hex opcode in output * Respect ignored opcodes * minor readability change * common.Hash for storage access fields --- .../internal/tracetest/erc7562_tracer_test.go | 124 +--------- .../erc7562Tracer.test_deployer.json | 39 +--- .../erc7562Tracer.test_paymaster.json | 39 +--- .../erc7562Tracer.test_simple.json | 221 +----------------- eth/tracers/native/erc7562.go | 155 ++++++------ .../native/gen_callframewithopcodes_json.go | 30 ++- 6 files changed, 127 insertions(+), 481 deletions(-) diff --git a/eth/tracers/internal/tracetest/erc7562_tracer_test.go b/eth/tracers/internal/tracetest/erc7562_tracer_test.go index dafb7bdbe1..ce7bfa2731 100644 --- a/eth/tracers/internal/tracetest/erc7562_tracer_test.go +++ b/eth/tracers/internal/tracetest/erc7562_tracer_test.go @@ -1,4 +1,4 @@ -// Copyright 2021 The go-ethereum Authors +// Copyright 2025 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 @@ -18,12 +18,9 @@ package tracetest import ( "encoding/json" - "fmt" "math/big" "os" "path/filepath" - "reflect" - "sort" "strings" "testing" @@ -36,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/tests" + "github.com/stretchr/testify/require" ) type accessedSlots struct { @@ -64,7 +62,7 @@ type erc7562Trace struct { revertedSnapshot bool AccessedSlots accessedSlots `json:"accessedSlots"` ExtCodeAccessInfo []common.Address `json:"extCodeAccessInfo"` - UsedOpcodes map[vm.OpCode]uint64 `json:"usedOpcodes"` + UsedOpcodes map[hexutil.Uint64]uint64 `json:"usedOpcodes"` ContractSize map[common.Address]*contractSizeWithOpcode `json:"contractSize"` OutOfGas bool `json:"outOfGas"` Calls []erc7562Trace `json:"calls,omitempty" rlp:"optional"` @@ -144,121 +142,7 @@ func TestErc7562Tracer(t *testing.T) { if err != nil { t.Fatalf("failed to marshal test: %v", err) } - - // Compare JSON ignoring key order by unmarshalling both into interfaces. - var got, expected interface{} - - if err := json.Unmarshal(res, &got); err != nil { - t.Fatalf("failed to unmarshal result: %v", err) - } - if err := json.Unmarshal(want, &expected); err != nil { - t.Fatalf("failed to unmarshal expected result: %v", err) - } - got = sortKeccakArrays(got) - expected = sortKeccakArrays(expected) - if !reflect.DeepEqual(got, expected) { - diff(got, expected, "root") - t.Fatalf("trace mismatch\n have: %v\n want: %v\n", got, expected) - } - - // Sanity check: compare top call's gas used against vm result - type simpleResult struct { - GasUsed hexutil.Uint64 - } - var topCall simpleResult - if err := json.Unmarshal(res, &topCall); err != nil { - t.Fatalf("failed to unmarshal top calls gasUsed: %v", err) - } - if uint64(topCall.GasUsed) != vmRet.UsedGas { - t.Fatalf("top call has invalid gasUsed. have: %d want: %d", topCall.GasUsed, vmRet.UsedGas) - } + require.JSONEq(t, string(res), string(want)) }) } } - -func diff(a, b interface{}, path string) { - // If both values are deeply equal, nothing to report. - if reflect.DeepEqual(a, b) { - return - } - - // If the types differ, print the mismatch and return. - if reflect.TypeOf(a) != reflect.TypeOf(b) { - fmt.Printf("Type mismatch at %s: %T vs %T\n", path, a, b) - return - } - - switch aVal := a.(type) { - case map[string]interface{}: - bVal := b.(map[string]interface{}) - // Check keys present in aVal. - for k, va := range aVal { - newPath := fmt.Sprintf("%s.%s", path, k) - vb, exists := bVal[k] - if !exists { - fmt.Printf("Key %s present in a but missing in b: %v\n", newPath, va) - } else { - diff(va, vb, newPath) - } - } - // Check keys present in bVal but missing in aVal. - for k, vb := range bVal { - newPath := fmt.Sprintf("%s.%s", path, k) - if _, exists := aVal[k]; !exists { - fmt.Printf("Key %s present in b but missing in a: %v\n", newPath, vb) - } - } - case []interface{}: - bVal := b.([]interface{}) - if len(aVal) != len(bVal) { - fmt.Printf("Length mismatch at %s: %d vs %d\n", path, len(aVal), len(bVal)) - } - // Compare each element. - min := len(aVal) - if len(bVal) < min { - min = len(bVal) - } - for i := 0; i < min; i++ { - diff(aVal[i], bVal[i], fmt.Sprintf("%s[%d]", path, i)) - } - default: - // For other types, just print the difference. - fmt.Printf("Value mismatch at %s: %v vs %v\n", path, a, b) - } -} - -// sortKeccakArrays recursively traverses the JSON object and sorts any array found under the "keccak" key. -func sortKeccakArrays(v interface{}) interface{} { - switch val := v.(type) { - case map[string]interface{}: - for k, child := range val { - if k == "keccak" { - if arr, ok := child.([]interface{}); ok { - // Convert each element to a string. - strs := make([]string, len(arr)) - for i, elem := range arr { - strs[i] = fmt.Sprintf("%v", elem) - } - // Sort the strings. - sort.Strings(strs) - // Replace with sorted values. - sortedArr := make([]interface{}, len(strs)) - for i, s := range strs { - sortedArr[i] = s - } - val[k] = sortedArr - } - } else { - val[k] = sortKeccakArrays(child) - } - } - return val - case []interface{}: - for i, elem := range val { - val[i] = sortKeccakArrays(elem) - } - return val - default: - return v - } -} diff --git a/eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_deployer.json b/eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_deployer.json index 89a7b89d49..96db689d50 100644 --- a/eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_deployer.json +++ b/eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_deployer.json @@ -92,44 +92,7 @@ "outOfGas": false, "to": "0x0000000071727de22e5e9d8baf0edac6f37da032", "type": "CALL", - "usedOpcodes": { - "0": 1, - "1": 2, - "3": 1, - "16": 1, - "17": 1, - "18": 1, - "20": 14, - "21": 1, - "22": 3, - "28": 1, - "32": 1, - "52": 1, - "53": 2, - "54": 2, - "81": 1, - "82": 4, - "84": 1, - "85": 1, - "86": 8, - "87": 18, - "91": 10, - "96": 14, - "97": 26, - "98": 1, - "99": 13, - "115": 3, - "127": 2, - "128": 16, - "130": 4, - "133": 2, - "144": 3, - "145": 3, - "146": 2, - "147": 1, - "148": 1, - "162": 1 - }, + "usedOpcodes": {"0x0":1, "0x20":1, "0x34":1, "0x35":2, "0x36":2, "0x51":1, "0x52":4, "0x54":1, "0x55":1, "0x56":8, "0x57":18, "0x5b":10, "0xa2":1}, "value": "0xde0b6b3a7640000" } } diff --git a/eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_paymaster.json b/eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_paymaster.json index 550b7bec7d..bcad4052a4 100644 --- a/eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_paymaster.json +++ b/eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_paymaster.json @@ -92,44 +92,7 @@ "outOfGas": false, "to": "0x0000000071727de22e5e9d8baf0edac6f37da032", "type": "CALL", - "usedOpcodes": { - "0": 1, - "1": 2, - "3": 1, - "16": 1, - "17": 1, - "18": 1, - "20": 14, - "21": 1, - "22": 3, - "28": 1, - "32": 1, - "52": 1, - "53": 2, - "54": 2, - "81": 1, - "82": 4, - "84": 1, - "85": 1, - "86": 8, - "87": 18, - "91": 10, - "96": 14, - "97": 26, - "98": 1, - "99": 13, - "115": 3, - "127": 2, - "128": 16, - "130": 4, - "133": 2, - "144": 3, - "145": 3, - "146": 2, - "147": 1, - "148": 1, - "162": 1 - }, + "usedOpcodes": {"0x0":1, "0x20":1, "0x34":1, "0x35":2, "0x36":2, "0x51":1, "0x52":4, "0x54":1, "0x55":1, "0x56":8, "0x57":18, "0x5b":10, "0xa2":1}, "value": "0x4a9b6384487fff" } } diff --git a/eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_simple.json b/eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_simple.json index c87918f678..ad5c79004b 100644 --- a/eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_simple.json +++ b/eth/tracers/internal/tracetest/testdata/erc7562_tracer/erc7562Tracer.test_simple.json @@ -116,47 +116,7 @@ "output": "0x0000000000000000000000000000000000000000000000000000000000000000", "to": "0x8c9d927336adc963536122f8e0d269319e79ed7a", "type": "CALL", - "usedOpcodes": { - "1": 12, - "2": 2, - "3": 13, - "16": 2, - "17": 4, - "18": 4, - "19": 2, - "20": 5, - "21": 5, - "22": 7, - "27": 7, - "28": 1, - "52": 1, - "53": 9, - "54": 6, - "80": 8, - "81": 1, - "82": 2, - "86": 102, - "87": 19, - "91": 108, - "95": 7, - "96": 30, - "97": 130, - "99": 1, - "103": 3, - "128": 3, - "129": 12, - "130": 12, - "131": 4, - "132": 2, - "133": 2, - "134": 1, - "144": 66, - "145": 16, - "146": 8, - "147": 2, - "148": 1, - "243": 1 - }, + "usedOpcodes": {"0x34":1, "0x35":9, "0x36":6, "0x51":1, "0x52":2, "0x56":102, "0x57":19, "0x5b":108, "0xf3":1}, "value": "0x0" }, { @@ -195,41 +155,7 @@ "outOfGas": false, "to": "0x8c9d927336adc963536122f8e0d269319e79ed7a", "type": "CALL", - "usedOpcodes": { - "1": 2, - "3": 3, - "16": 1, - "18": 1, - "20": 5, - "21": 1, - "22": 2, - "23": 1, - "25": 2, - "27": 1, - "28": 1, - "52": 1, - "53": 2, - "54": 2, - "80": 1, - "81": 1, - "82": 1, - "84": 1, - "85": 1, - "86": 33, - "87": 9, - "91": 35, - "95": 5, - "96": 8, - "97": 42, - "99": 5, - "128": 6, - "129": 3, - "130": 4, - "144": 20, - "145": 4, - "146": 2, - "243": 1 - }, + "usedOpcodes": {"0x34":1, "0x35":2, "0x36":2, "0x51":1, "0x52":1, "0x54":1, "0x55":1, "0x56":33, "0x57":9, "0x5b":35, "0xf3":1}, "value": "0x0" } ], @@ -248,65 +174,7 @@ "output": "0x000000000000000000000000000000000000000000000000000421bab3f40fbc", "to": "0x0000000071727de22e5e9d8baf0edac6f37da032", "type": "CALL", - "usedOpcodes": { - "1": 66, - "2": 3, - "3": 8, - "4": 1, - "16": 8, - "17": 15, - "18": 5, - "20": 4, - "21": 8, - "22": 12, - "23": 4, - "28": 2, - "32": 1, - "48": 1, - "51": 1, - "52": 1, - "53": 19, - "54": 7, - "55": 2, - "72": 1, - "80": 11, - "81": 26, - "82": 31, - "84": 1, - "85": 1, - "86": 27, - "87": 33, - "90": 5, - "91": 35, - "96": 77, - "97": 77, - "98": 1, - "103": 8, - "115": 8, - "127": 7, - "128": 11, - "129": 34, - "130": 30, - "131": 16, - "132": 14, - "133": 6, - "134": 4, - "135": 3, - "136": 2, - "137": 3, - "140": 1, - "144": 27, - "145": 24, - "146": 15, - "147": 11, - "148": 6, - "149": 2, - "150": 4, - "151": 2, - "164": 1, - "241": 1, - "243": 1 - }, + "usedOpcodes": {"0x20":1, "0x30":1, "0x33":1, "0x34":1, "0x35":19, "0x36":7, "0x37":2, "0x48":1, "0x51":26, "0x52":31, "0x54":1, "0x55":1, "0x56":27, "0x57":33, "0x5a":5, "0x5b":35, "0xa4":1, "0xf1":1, "0xf3":1}, "value": "0x0" }, { @@ -349,87 +217,18 @@ "gasUsed": "0x19415", "input": "0x765e827f0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000008c9d927336adc963536122f8e0d269319e79ed7a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000f4240000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000493e0000000000000000000000000b2d05e00000000000000000000000000ee6b280000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024a9e966b7000000000000000000000000000000000000000000000000000000000010f4470000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002face000000000000000000000000000000000000000000000000000000000000", "keccak": [ - "0x0000000000000000000000008c9d927336adc963536122f8e0d269319e79ed7a0000000000000000000000000000000000000000000000000000000000000000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470ede3d138a3c0ac5537239d818f52c7c86b466472a500982b0e7dff43ab38975d000000000000000000000000000f4240000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000493e0000000000000000000000000b2d05e00000000000000000000000000ee6b2800c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "0xc72bc304a44b01f425579bd71219ccd5676c732ce2aed103da05dc145f00fe340000000000000000000000000000000071727de22e5e9d8baf0edac6f37da0320000000000000000000000000000000000000000000000000000000000000539", - "0x0000000000000000000000008c9d927336adc963536122f8e0d269319e79ed7a0000000000000000000000000000000000000000000000000000000000000000", - "0x0000000000000000000000008c9d927336adc963536122f8e0d269319e79ed7a0000000000000000000000000000000000000000000000000000000000000001", - "0x0000000000000000000000000000000000000000000000000000000000000000916f81e4e1b2122d13f0474f4c323777192f91bb579723004f6f3062b5fedc68", "0x", - "0xa9e966b7000000000000000000000000000000000000000000000000000000000010f447" + "0x0000000000000000000000000000000000000000000000000000000000000000916f81e4e1b2122d13f0474f4c323777192f91bb579723004f6f3062b5fedc68", + "0x0000000000000000000000008c9d927336adc963536122f8e0d269319e79ed7a0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000008c9d927336adc963536122f8e0d269319e79ed7a0000000000000000000000000000000000000000000000000000000000000000c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470ede3d138a3c0ac5537239d818f52c7c86b466472a500982b0e7dff43ab38975d000000000000000000000000000f4240000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000493e0000000000000000000000000b2d05e00000000000000000000000000ee6b2800c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "0x0000000000000000000000008c9d927336adc963536122f8e0d269319e79ed7a0000000000000000000000000000000000000000000000000000000000000001", + "0xa9e966b7000000000000000000000000000000000000000000000000000000000010f447", + "0xc72bc304a44b01f425579bd71219ccd5676c732ce2aed103da05dc145f00fe340000000000000000000000000000000071727de22e5e9d8baf0edac6f37da0320000000000000000000000000000000000000000000000000000000000000539" ], "outOfGas": false, "to": "0x0000000071727de22e5e9d8baf0edac6f37da032", "type": "CALL", - "usedOpcodes": { - "0": 1, - "1": 233, - "2": 1, - "3": 37, - "16": 19, - "17": 28, - "18": 15, - "19": 10, - "20": 14, - "21": 27, - "22": 26, - "23": 13, - "27": 7, - "28": 4, - "32": 9, - "48": 2, - "52": 1, - "53": 43, - "54": 24, - "55": 8, - "56": 2, - "61": 2, - "70": 1, - "80": 31, - "81": 56, - "82": 104, - "84": 4, - "85": 4, - "86": 92, - "87": 101, - "90": 4, - "91": 116, - "96": 217, - "97": 219, - "98": 1, - "99": 10, - "103": 20, - "110": 1, - "111": 1, - "115": 11, - "126": 1, - "127": 30, - "128": 44, - "129": 100, - "130": 83, - "131": 51, - "132": 35, - "133": 34, - "134": 48, - "135": 30, - "136": 17, - "137": 12, - "138": 3, - "139": 2, - "140": 1, - "144": 100, - "145": 67, - "146": 17, - "147": 26, - "148": 14, - "149": 10, - "150": 6, - "151": 5, - "152": 3, - "153": 1, - "154": 2, - "161": 1, - "241": 3 - }, + "usedOpcodes": {"0x0":1, "0x20":9, "0x30":2, "0x34":1, "0x35":43, "0x36":24, "0x37":8, "0x38":2, "0x3d":2, "0x46":1, "0x51":56, "0x52":104, "0x54":4, "0x55":4, "0x56":92, "0x57":101, "0x5a":4, "0x5b":116, "0xa1":1, "0xf1":3}, "value": "0x0" } } \ No newline at end of file diff --git a/eth/tracers/native/erc7562.go b/eth/tracers/native/erc7562.go index c5fe6cca1c..3ab98c7132 100644 --- a/eth/tracers/native/erc7562.go +++ b/eth/tracers/native/erc7562.go @@ -17,9 +17,11 @@ package native import ( + "bytes" "encoding/json" "errors" "math/big" + "slices" "sync/atomic" "github.com/ethereum/go-ethereum/accounts/abi" @@ -29,6 +31,8 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/eth/tracers/internal" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -63,7 +67,10 @@ type callFrameWithOpcodes struct { UsedOpcodes map[vm.OpCode]uint64 `json:"usedOpcodes"` ContractSize map[common.Address]*contractSizeWithOpcode `json:"contractSize"` OutOfGas bool `json:"outOfGas"` - Calls []callFrameWithOpcodes `json:"calls,omitempty" rlp:"optional"` + // Keccak preimages for the whole transaction are stored in the + // root call frame. + KeccakPreimages [][]byte `json:"keccak,omitempty"` + Calls []callFrameWithOpcodes `json:"calls,omitempty" rlp:"optional"` } func (f callFrameWithOpcodes) TypeString() string { @@ -103,19 +110,21 @@ func (f *callFrameWithOpcodes) processOutput(output []byte, err error, reverted } type callFrameWithOpcodesMarshaling struct { - TypeString string `json:"type"` - Gas hexutil.Uint64 - GasUsed hexutil.Uint64 - Value *hexutil.Big - Input hexutil.Bytes - Output hexutil.Bytes + TypeString string `json:"type"` + Gas hexutil.Uint64 + GasUsed hexutil.Uint64 + Value *hexutil.Big + Input hexutil.Bytes + Output hexutil.Bytes + UsedOpcodes map[hexutil.Uint64]uint64 + KeccakPreimages []hexutil.Bytes } type accessedSlots struct { - Reads map[string][]string `json:"reads"` - Writes map[string]uint64 `json:"writes"` - TransientReads map[string]uint64 `json:"transientReads"` - TransientWrites map[string]uint64 `json:"transientWrites"` + Reads map[common.Hash][]common.Hash `json:"reads"` + Writes map[common.Hash]uint64 `json:"writes"` + TransientReads map[common.Hash]uint64 `json:"transientReads"` + TransientWrites map[common.Hash]uint64 `json:"transientWrites"` } type opcodeWithPartialStack struct { @@ -126,7 +135,6 @@ type opcodeWithPartialStack struct { type erc7562Tracer struct { config erc7562TracerConfig gasLimit uint64 - depth int interrupt atomic.Bool // Atomic flag to signal execution interruption reason error // Textual reason for the interruption env *tracing.VMContext @@ -134,8 +142,7 @@ type erc7562Tracer struct { ignoredOpcodes map[vm.OpCode]struct{} callstackWithOpcodes []callFrameWithOpcodes lastOpWithStack *opcodeWithPartialStack - Keccak map[string]struct{} `json:"keccak"` - transactionType uint8 + keccakPreimages map[string]struct{} } // newErc7562Tracer returns a native go tracer which tracks @@ -160,9 +167,9 @@ func newErc7562Tracer(ctx *tracers.Context, cfg json.RawMessage, _ *params.Chain } type erc7562TracerConfig struct { - StackTopItemsSize int `json:"stackTopItemsSize"` - IgnoredOpcodes map[vm.OpCode]struct{} `json:"ignoredOpcodes"` // Opcodes to ignore during OnOpcode hook execution - WithLog bool `json:"withLog"` // If true, erc7562 tracer will collect event logs + StackTopItemsSize int `json:"stackTopItemsSize"` + IgnoredOpcodes []hexutil.Uint64 `json:"ignoredOpcodes"` // Opcodes to ignore during OnOpcode hook execution + WithLog bool `json:"withLog"` // If true, erc7562 tracer will collect event logs } func getFullConfiguration(partial erc7562TracerConfig) erc7562TracerConfig { @@ -185,24 +192,29 @@ func newErc7562TracerObject(cfg json.RawMessage) (*erc7562Tracer, error) { return nil, err } } + fullConfig := getFullConfiguration(config) + // Create a map of ignored opcodes for fast lookup + ignoredOpcodes := make(map[vm.OpCode]struct{}, len(fullConfig.IgnoredOpcodes)) + for _, op := range fullConfig.IgnoredOpcodes { + ignoredOpcodes[vm.OpCode(op)] = struct{}{} + } // First callframe contains tx context info // and is populated on start and end. return &erc7562Tracer{ callstackWithOpcodes: make([]callFrameWithOpcodes, 0, 1), - Keccak: make(map[string]struct{}), - config: getFullConfiguration(config), + config: fullConfig, + keccakPreimages: make(map[string]struct{}), + ignoredOpcodes: ignoredOpcodes, }, nil } func (t *erc7562Tracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { t.env = env t.gasLimit = tx.Gas() - t.transactionType = tx.Type() } // OnEnter is called when EVM enters a new scope (via call, create or selfdestruct). func (t *erc7562Tracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { - t.depth = depth // Skip if tracing was interrupted if t.interrupt.Load() { return @@ -217,10 +229,10 @@ func (t *erc7562Tracer) OnEnter(depth int, typ byte, from common.Address, to com Gas: gas, Value: value, AccessedSlots: accessedSlots{ - Reads: map[string][]string{}, - Writes: map[string]uint64{}, - TransientReads: map[string]uint64{}, - TransientWrites: map[string]uint64{}, + Reads: map[common.Hash][]common.Hash{}, + Writes: map[common.Hash]uint64{}, + TransientReads: map[common.Hash]uint64{}, + TransientWrites: map[common.Hash]uint64{}, }, UsedOpcodes: map[vm.OpCode]uint64{}, ExtCodeAccessInfo: make([]common.Address, 0), @@ -242,13 +254,14 @@ func (t *erc7562Tracer) captureEnd(output []byte, err error, reverted bool) { // OnExit is called when EVM exits a scope, even if the scope didn't // execute any code. func (t *erc7562Tracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if t.interrupt.Load() { + return + } if depth == 0 { t.captureEnd(output, err, reverted) return } - t.depth = depth - 1 - size := len(t.callstackWithOpcodes) if size <= 1 { return @@ -268,6 +281,9 @@ func (t *erc7562Tracer) OnExit(depth int, output []byte, gasUsed uint64, err err } func (t *erc7562Tracer) OnTxEnd(receipt *types.Receipt, err error) { + if t.interrupt.Load() { + return + } // Error happened during tx validation. if err != nil { return @@ -300,36 +316,28 @@ func (t *erc7562Tracer) OnLog(log1 *types.Log) { // GetResult returns the json-encoded nested list of call traces, and any // error arising from the encoding or forceful termination (via `Stop`). func (t *erc7562Tracer) GetResult() (json.RawMessage, error) { + if t.interrupt.Load() { + return nil, t.reason + } if len(t.callstackWithOpcodes) != 1 { return nil, errors.New("incorrect number of top-level calls") } - callFrameJSON, err := json.Marshal(t.callstackWithOpcodes[0]) + keccak := make([][]byte, 0, len(t.callstackWithOpcodes[0].KeccakPreimages)) + for k := range t.keccakPreimages { + keccak = append(keccak, []byte(k)) + } + t.callstackWithOpcodes[0].KeccakPreimages = keccak + slices.SortFunc(keccak, func(a, b []byte) int { + return bytes.Compare(a, b) + }) + + enc, err := json.Marshal(t.callstackWithOpcodes[0]) if err != nil { return nil, err } - // Unmarshal the generated JSON into a map - var resultMap map[string]interface{} - if err := json.Unmarshal(callFrameJSON, &resultMap); err != nil { - return nil, err - } - - // Converting keccak mapping to array - keccakArray := make([]hexutil.Bytes, len(t.Keccak)) - i := 0 - for k := range t.Keccak { - keccakArray[i] = hexutil.Bytes(k) - i++ - } - resultMap["keccak"] = keccakArray - - // Marshal the final map back to JSON - finalJSON, err := json.Marshal(resultMap) - if err != nil { - return nil, err - } - return finalJSON, t.reason + return enc, t.reason } // Stop terminates execution of the tracer at the first opportune moment. @@ -352,12 +360,18 @@ func (t *erc7562Tracer) clearFailedLogs(cf *callFrameWithOpcodes, parentFailed b } func (t *erc7562Tracer) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { - opcode := vm.OpCode(op) - var opcodeWithStack *opcodeWithPartialStack - stackSize := len(scope.StackData()) - var stackTopItems []uint256.Int - for i := 0; i < t.config.StackTopItemsSize && i < stackSize; i++ { - stackTopItems = append(stackTopItems, *peepStack(scope.StackData(), i)) + if t.interrupt.Load() { + return + } + var ( + opcode = vm.OpCode(op) + opcodeWithStack *opcodeWithPartialStack + stackSize = len(scope.StackData()) + stackLimit = min(stackSize, t.config.StackTopItemsSize) + stackTopItems = make([]uint256.Int, stackLimit) + ) + for i := 0; i < stackLimit; i++ { + stackTopItems[i] = *peepStack(scope.StackData(), i) } opcodeWithStack = &opcodeWithPartialStack{ Opcode: opcode, @@ -403,23 +417,22 @@ func (t *erc7562Tracer) storeUsedOpcode(opcode vm.OpCode, currentCallFrame *call func (t *erc7562Tracer) handleStorageAccess(opcode vm.OpCode, scope tracing.OpContext, currentCallFrame *callFrameWithOpcodes) { if opcode == vm.SLOAD || opcode == vm.SSTORE || opcode == vm.TLOAD || opcode == vm.TSTORE { slot := common.BytesToHash(peepStack(scope.StackData(), 0).Bytes()) - slotHex := slot.Hex() addr := scope.Address() if opcode == vm.SLOAD { // read slot values before this UserOp was created // (so saving it if it was written before the first read) - _, rOk := currentCallFrame.AccessedSlots.Reads[slotHex] - _, wOk := currentCallFrame.AccessedSlots.Writes[slotHex] + _, rOk := currentCallFrame.AccessedSlots.Reads[slot] + _, wOk := currentCallFrame.AccessedSlots.Writes[slot] if !rOk && !wOk { - currentCallFrame.AccessedSlots.Reads[slotHex] = append(currentCallFrame.AccessedSlots.Reads[slotHex], t.env.StateDB.GetState(addr, slot).Hex()) + currentCallFrame.AccessedSlots.Reads[slot] = append(currentCallFrame.AccessedSlots.Reads[slot], t.env.StateDB.GetState(addr, slot)) } } else if opcode == vm.SSTORE { - currentCallFrame.AccessedSlots.Writes[slotHex]++ + currentCallFrame.AccessedSlots.Writes[slot]++ } else if opcode == vm.TLOAD { - currentCallFrame.AccessedSlots.TransientReads[slotHex]++ + currentCallFrame.AccessedSlots.TransientReads[slot]++ } else { - currentCallFrame.AccessedSlots.TransientWrites[slotHex]++ + currentCallFrame.AccessedSlots.TransientWrites[slot]++ } } } @@ -428,10 +441,12 @@ func (t *erc7562Tracer) storeKeccak(opcode vm.OpCode, scope tracing.OpContext) { if opcode == vm.KECCAK256 { dataOffset := peepStack(scope.StackData(), 0).Uint64() dataLength := peepStack(scope.StackData(), 1).Uint64() - memory := scope.MemoryData() - keccak := make([]byte, dataLength) - copy(keccak, memory[dataOffset:dataOffset+dataLength]) - t.Keccak[string(keccak)] = struct{}{} + preimage, err := internal.GetMemoryCopyPadded(scope.MemoryData(), int64(dataOffset), int64(dataLength)) + if err != nil { + log.Warn("erc7562Tracer: failed to copy keccak preimage from memory", "err", err) + return + } + t.keccakPreimages[string(preimage)] = struct{}{} } } @@ -494,12 +509,12 @@ func (t *erc7562Tracer) isIgnoredOpcode(opcode vm.OpCode) bool { return false } -func defaultIgnoredOpcodes() map[vm.OpCode]struct{} { - ignored := make(map[vm.OpCode]struct{}) +func defaultIgnoredOpcodes() []hexutil.Uint64 { + ignored := make([]hexutil.Uint64, 0, 64) // Allow all PUSHx, DUPx and SWAPx opcodes as they have sequential codes for op := vm.PUSH0; op < vm.SWAP16; op++ { - ignored[op] = struct{}{} + ignored = append(ignored, hexutil.Uint64(op)) } for _, op := range []vm.OpCode{ @@ -508,7 +523,7 @@ func defaultIgnoredOpcodes() map[vm.OpCode]struct{} { vm.SLT, vm.SGT, vm.SHL, vm.SHR, vm.AND, vm.OR, vm.NOT, vm.ISZERO, } { - ignored[op] = struct{}{} + ignored = append(ignored, hexutil.Uint64(op)) } return ignored diff --git a/eth/tracers/native/gen_callframewithopcodes_json.go b/eth/tracers/native/gen_callframewithopcodes_json.go index 1602eb2a2e..f3d5dde8f1 100644 --- a/eth/tracers/native/gen_callframewithopcodes_json.go +++ b/eth/tracers/native/gen_callframewithopcodes_json.go @@ -29,9 +29,10 @@ func (c callFrameWithOpcodes) MarshalJSON() ([]byte, error) { Value *hexutil.Big `json:"value,omitempty" rlp:"optional"` AccessedSlots accessedSlots `json:"accessedSlots"` ExtCodeAccessInfo []common.Address `json:"extCodeAccessInfo"` - UsedOpcodes map[vm.OpCode]uint64 `json:"usedOpcodes"` + UsedOpcodes map[hexutil.Uint64]uint64 `json:"usedOpcodes"` ContractSize map[common.Address]*contractSizeWithOpcode `json:"contractSize"` OutOfGas bool `json:"outOfGas"` + KeccakPreimages []hexutil.Bytes `json:"keccak,omitempty"` Calls []callFrameWithOpcodes `json:"calls,omitempty" rlp:"optional"` TypeString string `json:"type"` } @@ -49,9 +50,20 @@ func (c callFrameWithOpcodes) MarshalJSON() ([]byte, error) { enc.Value = (*hexutil.Big)(c.Value) enc.AccessedSlots = c.AccessedSlots enc.ExtCodeAccessInfo = c.ExtCodeAccessInfo - enc.UsedOpcodes = c.UsedOpcodes + if c.UsedOpcodes != nil { + enc.UsedOpcodes = make(map[hexutil.Uint64]uint64, len(c.UsedOpcodes)) + for k, v := range c.UsedOpcodes { + enc.UsedOpcodes[hexutil.Uint64(k)] = v + } + } enc.ContractSize = c.ContractSize enc.OutOfGas = c.OutOfGas + if c.KeccakPreimages != nil { + enc.KeccakPreimages = make([]hexutil.Bytes, len(c.KeccakPreimages)) + for k, v := range c.KeccakPreimages { + enc.KeccakPreimages[k] = v + } + } enc.Calls = c.Calls enc.TypeString = c.TypeString() return json.Marshal(&enc) @@ -73,9 +85,10 @@ func (c *callFrameWithOpcodes) UnmarshalJSON(input []byte) error { Value *hexutil.Big `json:"value,omitempty" rlp:"optional"` AccessedSlots *accessedSlots `json:"accessedSlots"` ExtCodeAccessInfo []common.Address `json:"extCodeAccessInfo"` - UsedOpcodes map[vm.OpCode]uint64 `json:"usedOpcodes"` + UsedOpcodes map[hexutil.Uint64]uint64 `json:"usedOpcodes"` ContractSize map[common.Address]*contractSizeWithOpcode `json:"contractSize"` OutOfGas *bool `json:"outOfGas"` + KeccakPreimages []hexutil.Bytes `json:"keccak,omitempty"` Calls []callFrameWithOpcodes `json:"calls,omitempty" rlp:"optional"` } var dec callFrameWithOpcodes0 @@ -122,7 +135,10 @@ func (c *callFrameWithOpcodes) UnmarshalJSON(input []byte) error { c.ExtCodeAccessInfo = dec.ExtCodeAccessInfo } if dec.UsedOpcodes != nil { - c.UsedOpcodes = dec.UsedOpcodes + c.UsedOpcodes = make(map[vm.OpCode]uint64, len(dec.UsedOpcodes)) + for k, v := range dec.UsedOpcodes { + c.UsedOpcodes[vm.OpCode(k)] = v + } } if dec.ContractSize != nil { c.ContractSize = dec.ContractSize @@ -130,6 +146,12 @@ func (c *callFrameWithOpcodes) UnmarshalJSON(input []byte) error { if dec.OutOfGas != nil { c.OutOfGas = *dec.OutOfGas } + if dec.KeccakPreimages != nil { + c.KeccakPreimages = make([][]byte, len(dec.KeccakPreimages)) + for k, v := range dec.KeccakPreimages { + c.KeccakPreimages[k] = v + } + } if dec.Calls != nil { c.Calls = dec.Calls }