glamsterdam-compat-lab 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +59 -0
- package/LICENSE +21 -0
- package/README.md +187 -0
- package/ROADMAP.md +76 -0
- package/SECURITY.md +19 -0
- package/data/client-compat/clients.example.json +42 -0
- package/data/detectors/thresholds.ci.json +33 -0
- package/data/detectors/thresholds.json +33 -0
- package/data/detectors/thresholds.research.json +33 -0
- package/data/eips/glamsterdam.json +85 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +163 -0
- package/dist/cli.js.map +1 -0
- package/dist/detectors/balDetectors.d.ts +3 -0
- package/dist/detectors/balDetectors.js +26 -0
- package/dist/detectors/balDetectors.js.map +1 -0
- package/dist/detectors/contractSizeDetectors.d.ts +3 -0
- package/dist/detectors/contractSizeDetectors.js +37 -0
- package/dist/detectors/contractSizeDetectors.js.map +1 -0
- package/dist/detectors/epbsDetectors.d.ts +7 -0
- package/dist/detectors/epbsDetectors.js +33 -0
- package/dist/detectors/epbsDetectors.js.map +1 -0
- package/dist/detectors/gasRepricingDetectors.d.ts +4 -0
- package/dist/detectors/gasRepricingDetectors.js +135 -0
- package/dist/detectors/gasRepricingDetectors.js.map +1 -0
- package/dist/detectors/nativeEthTransferLogDetectors.d.ts +8 -0
- package/dist/detectors/nativeEthTransferLogDetectors.js +54 -0
- package/dist/detectors/nativeEthTransferLogDetectors.js.map +1 -0
- package/dist/detectors/stateCreationDetectors.d.ts +4 -0
- package/dist/detectors/stateCreationDetectors.js +46 -0
- package/dist/detectors/stateCreationDetectors.js.map +1 -0
- package/dist/detectors/thresholds.d.ts +34 -0
- package/dist/detectors/thresholds.js +45 -0
- package/dist/detectors/thresholds.js.map +1 -0
- package/dist/detectors/types.d.ts +15 -0
- package/dist/detectors/types.js +23 -0
- package/dist/detectors/types.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/registry/eipRegistry.d.ts +6 -0
- package/dist/registry/eipRegistry.js +30 -0
- package/dist/registry/eipRegistry.js.map +1 -0
- package/dist/registry/schemas.d.ts +76 -0
- package/dist/registry/schemas.js +34 -0
- package/dist/registry/schemas.js.map +1 -0
- package/dist/reports/jsonReporter.d.ts +2 -0
- package/dist/reports/jsonReporter.js +5 -0
- package/dist/reports/jsonReporter.js.map +1 -0
- package/dist/reports/markdownReporter.d.ts +2 -0
- package/dist/reports/markdownReporter.js +75 -0
- package/dist/reports/markdownReporter.js.map +1 -0
- package/dist/reports/reportTypes.d.ts +150 -0
- package/dist/reports/reportTypes.js +111 -0
- package/dist/reports/reportTypes.js.map +1 -0
- package/dist/scanners/bytecodeScanner.d.ts +11 -0
- package/dist/scanners/bytecodeScanner.js +107 -0
- package/dist/scanners/bytecodeScanner.js.map +1 -0
- package/dist/scanners/indexerScanner.d.ts +13 -0
- package/dist/scanners/indexerScanner.js +129 -0
- package/dist/scanners/indexerScanner.js.map +1 -0
- package/dist/scanners/rpcTraceScanner.d.ts +40 -0
- package/dist/scanners/rpcTraceScanner.js +121 -0
- package/dist/scanners/rpcTraceScanner.js.map +1 -0
- package/dist/scanners/traceScanner.d.ts +24 -0
- package/dist/scanners/traceScanner.js +272 -0
- package/dist/scanners/traceScanner.js.map +1 -0
- package/dist/scanners/validatorScanner.d.ts +40 -0
- package/dist/scanners/validatorScanner.js +210 -0
- package/dist/scanners/validatorScanner.js.map +1 -0
- package/dist/utils/bytecode.d.ts +11 -0
- package/dist/utils/bytecode.js +140 -0
- package/dist/utils/bytecode.js.map +1 -0
- package/dist/utils/files.d.ts +10 -0
- package/dist/utils/files.js +51 -0
- package/dist/utils/files.js.map +1 -0
- package/dist/utils/severity.d.ts +2 -0
- package/dist/utils/severity.js +13 -0
- package/dist/utils/severity.js.map +1 -0
- package/docs/fixtures.md +60 -0
- package/docs/release.md +132 -0
- package/examples/storage-heavy-bytecode.md +57 -0
- package/fixtures/bytecode/storage-heavy.hex +1 -0
- package/fixtures/indexers/balance-diff-indexer.json +15 -0
- package/fixtures/indexers/subgraph.yaml +24 -0
- package/fixtures/traces/besu-debug-structlogs.json +18 -0
- package/fixtures/traces/call-tracer-tree.json +27 -0
- package/fixtures/traces/drpc-call-tracer-real.json +373 -0
- package/fixtures/traces/erigon-action-trace.json +29 -0
- package/fixtures/traces/foundry-json-trace.json +19 -0
- package/fixtures/traces/geth-json-rpc-structlogs.json +16 -0
- package/fixtures/traces/hardhat-debug-trace.json +13 -0
- package/fixtures/traces/nethermind-debug-structlogs.json +17 -0
- package/fixtures/traces/storage-heavy-trace.json +25 -0
- package/fixtures/validator/operator-config.yaml +15 -0
- package/package.json +80 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reportTypes.js","sourceRoot":"","sources":["../../src/reports/reportTypes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type DetectorThresholds } from "../detectors/thresholds.js";
|
|
2
|
+
import type { EipRegistry } from "../registry/schemas.js";
|
|
3
|
+
import { type CompatibilityReport } from "../reports/reportTypes.js";
|
|
4
|
+
export interface BytecodeScanOptions {
|
|
5
|
+
registry?: EipRegistry;
|
|
6
|
+
registryPath?: string;
|
|
7
|
+
thresholds?: DetectorThresholds;
|
|
8
|
+
thresholdsPath?: string;
|
|
9
|
+
targetName?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function scanBytecode(pathOrHex: string, options?: BytecodeScanOptions): CompatibilityReport;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { detectContractSize } from "../detectors/contractSizeDetectors.js";
|
|
2
|
+
import { detectBytecodeGasRepricingExposure } from "../detectors/gasRepricingDetectors.js";
|
|
3
|
+
import { detectBytecodeStateCreation } from "../detectors/stateCreationDetectors.js";
|
|
4
|
+
import { domains, makeFinding } from "../detectors/types.js";
|
|
5
|
+
import { loadDetectorThresholds } from "../detectors/thresholds.js";
|
|
6
|
+
import { loadEipRegistry } from "../registry/eipRegistry.js";
|
|
7
|
+
import { makeReport } from "../reports/reportTypes.js";
|
|
8
|
+
import { byteLength, countOpcodeNames, disassembleBytecode, normalizeBytecode, opcodeCount } from "../utils/bytecode.js";
|
|
9
|
+
import { readPathOrValue } from "../utils/files.js";
|
|
10
|
+
export function scanBytecode(pathOrHex, options = {}) {
|
|
11
|
+
const registry = options.registry ?? loadEipRegistry(options.registryPath);
|
|
12
|
+
const thresholds = options.thresholds ?? loadDetectorThresholds(options.thresholdsPath);
|
|
13
|
+
const source = readPathOrValue(pathOrHex);
|
|
14
|
+
const normalized = normalizeBytecode(source.text);
|
|
15
|
+
const opcodes = disassembleBytecode(normalized);
|
|
16
|
+
const opcodeCounts = countOpcodeNames(opcodes);
|
|
17
|
+
const sizeBytes = byteLength(normalized);
|
|
18
|
+
const context = {
|
|
19
|
+
registry,
|
|
20
|
+
thresholds,
|
|
21
|
+
targetName: options.targetName ?? source.name
|
|
22
|
+
};
|
|
23
|
+
const findings = [
|
|
24
|
+
...detectContractSize(sizeBytes, context),
|
|
25
|
+
...detectBytecodeGasRepricingExposure(opcodeCounts, context),
|
|
26
|
+
...detectBytecodeStateCreation(opcodeCounts, context),
|
|
27
|
+
...detectStoragePattern(opcodeCounts, thresholds),
|
|
28
|
+
...detectLogPattern(opcodeCounts),
|
|
29
|
+
makeManualReviewFinding(sizeBytes, opcodeCounts)
|
|
30
|
+
];
|
|
31
|
+
return makeReport({
|
|
32
|
+
fork: registry.fork,
|
|
33
|
+
target: {
|
|
34
|
+
kind: "bytecode",
|
|
35
|
+
name: options.targetName ?? source.name
|
|
36
|
+
},
|
|
37
|
+
findings,
|
|
38
|
+
assumptions: [
|
|
39
|
+
"Input was interpreted as EVM bytecode after removing whitespace and an optional 0x prefix.",
|
|
40
|
+
`The loaded registry is dated ${registry.lastUpdated}. Glamsterdam scope and gas parameters may change.`,
|
|
41
|
+
`Detector thresholds are dated ${thresholds.lastUpdated} and are MVP heuristics, not protocol gas parameters.`
|
|
42
|
+
],
|
|
43
|
+
limitations: [
|
|
44
|
+
"Static bytecode scanning cannot determine which branches are executed in production.",
|
|
45
|
+
"Opcode counts are not gas estimates and do not include dynamic call targets, storage keys, calldata sizes, or transaction context.",
|
|
46
|
+
"Findings are compatibility prompts, not predictions that a contract will fail."
|
|
47
|
+
]
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function detectStoragePattern(opcodeCounts, thresholds) {
|
|
51
|
+
const storageOps = opcodeCount(opcodeCounts, ["SLOAD", "SSTORE"]);
|
|
52
|
+
if (storageOps >= thresholds.bytecode.storagePattern.mediumStorageOpcodeCount) {
|
|
53
|
+
return [
|
|
54
|
+
makeFinding({
|
|
55
|
+
id: "bytecode.storage-heavy-pattern",
|
|
56
|
+
title: "Storage-related opcodes appear frequently",
|
|
57
|
+
severity: "medium",
|
|
58
|
+
confidence: "medium",
|
|
59
|
+
domain: domains("contracts", "execution"),
|
|
60
|
+
relatedEips: ["GAS-REPRICING", "EIP-8038"],
|
|
61
|
+
description: "This bytecode contains many storage-related opcodes. Glamsterdam gas repricing candidates may change the cost profile of storage-heavy execution.",
|
|
62
|
+
evidence: { SLOAD: opcodeCounts.SLOAD ?? 0, SSTORE: opcodeCounts.SSTORE ?? 0, storageOps },
|
|
63
|
+
recommendation: "Replay representative storage-heavy transactions against a Glamsterdam devnet or fork configuration when available."
|
|
64
|
+
})
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
function detectLogPattern(opcodeCounts) {
|
|
70
|
+
const logOps = opcodeCount(opcodeCounts, ["LOG0", "LOG1", "LOG2", "LOG3", "LOG4"]);
|
|
71
|
+
if (logOps === 0) {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
return [
|
|
75
|
+
makeFinding({
|
|
76
|
+
id: "bytecode.log-opcodes-present",
|
|
77
|
+
title: "Log opcodes are present",
|
|
78
|
+
severity: "low",
|
|
79
|
+
confidence: "medium",
|
|
80
|
+
domain: domains("contracts", "indexer", "monitoring"),
|
|
81
|
+
relatedEips: ["EIP-7708"],
|
|
82
|
+
description: "The bytecode can emit logs. Indexers and monitoring systems should include representative log-emitting transactions when testing Glamsterdam-era assumptions.",
|
|
83
|
+
evidence: {
|
|
84
|
+
LOG0: opcodeCounts.LOG0 ?? 0,
|
|
85
|
+
LOG1: opcodeCounts.LOG1 ?? 0,
|
|
86
|
+
LOG2: opcodeCounts.LOG2 ?? 0,
|
|
87
|
+
LOG3: opcodeCounts.LOG3 ?? 0,
|
|
88
|
+
LOG4: opcodeCounts.LOG4 ?? 0
|
|
89
|
+
},
|
|
90
|
+
recommendation: "Include log-emitting paths in trace replay and indexer regression tests, especially where native ETH transfer visibility matters."
|
|
91
|
+
})
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
function makeManualReviewFinding(sizeBytes, opcodeCounts) {
|
|
95
|
+
return makeFinding({
|
|
96
|
+
id: "bytecode.manual-review-required",
|
|
97
|
+
title: "Manual review is still required for runtime behavior",
|
|
98
|
+
severity: "unknown",
|
|
99
|
+
confidence: "low",
|
|
100
|
+
domain: domains("contracts", "execution"),
|
|
101
|
+
relatedEips: ["GAS-REPRICING"],
|
|
102
|
+
description: "The scanner found static opcode evidence, but exact fork impact depends on executed paths, calldata, storage warmness, account state, compiler output, and final Glamsterdam parameters.",
|
|
103
|
+
evidence: { byteLength: sizeBytes, opcodeKinds: Object.keys(opcodeCounts).length },
|
|
104
|
+
recommendation: "Use this report to choose transactions for trace replay and benchmark them under current and Glamsterdam configurations."
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=bytecodeScanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bytecodeScanner.js","sourceRoot":"","sources":["../../src/scanners/bytecodeScanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type IndexerHandlerSummary } from "../detectors/nativeEthTransferLogDetectors.js";
|
|
2
|
+
import { type DetectorThresholds } from "../detectors/thresholds.js";
|
|
3
|
+
import type { EipRegistry } from "../registry/schemas.js";
|
|
4
|
+
import { type CompatibilityReport } from "../reports/reportTypes.js";
|
|
5
|
+
export interface IndexerScanOptions {
|
|
6
|
+
registry?: EipRegistry;
|
|
7
|
+
registryPath?: string;
|
|
8
|
+
thresholds?: DetectorThresholds;
|
|
9
|
+
thresholdsPath?: string;
|
|
10
|
+
targetName?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function scanIndexer(indexerPath: string, options?: IndexerScanOptions): CompatibilityReport;
|
|
13
|
+
export declare function summarizeHandlers(config: unknown): IndexerHandlerSummary;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { detectMissingBalIndexerPlan } from "../detectors/balDetectors.js";
|
|
2
|
+
import { detectNativeEthTransferLogReadiness } from "../detectors/nativeEthTransferLogDetectors.js";
|
|
3
|
+
import { domains, makeFinding } from "../detectors/types.js";
|
|
4
|
+
import { loadDetectorThresholds } from "../detectors/thresholds.js";
|
|
5
|
+
import { loadEipRegistry } from "../registry/eipRegistry.js";
|
|
6
|
+
import { makeReport } from "../reports/reportTypes.js";
|
|
7
|
+
import { loadStructuredFile, readTextFile } from "../utils/files.js";
|
|
8
|
+
export function scanIndexer(indexerPath, options = {}) {
|
|
9
|
+
const registry = options.registry ?? loadEipRegistry(options.registryPath);
|
|
10
|
+
const thresholds = options.thresholds ?? loadDetectorThresholds(options.thresholdsPath);
|
|
11
|
+
const rawText = readTextFile(indexerPath);
|
|
12
|
+
const parsed = loadStructuredFile(indexerPath);
|
|
13
|
+
const handlerSummary = summarizeHandlers(parsed);
|
|
14
|
+
const context = {
|
|
15
|
+
registry,
|
|
16
|
+
thresholds,
|
|
17
|
+
targetName: options.targetName ?? indexerPath
|
|
18
|
+
};
|
|
19
|
+
const findings = [
|
|
20
|
+
...detectNativeEthTransferLogReadiness(handlerSummary, rawText, context),
|
|
21
|
+
...detectMissingBalIndexerPlan(rawText, context),
|
|
22
|
+
...detectMissingCompatibilityMetadata(rawText),
|
|
23
|
+
...detectMissingReplayPlan(rawText)
|
|
24
|
+
];
|
|
25
|
+
return makeReport({
|
|
26
|
+
fork: registry.fork,
|
|
27
|
+
target: {
|
|
28
|
+
kind: "indexer",
|
|
29
|
+
name: options.targetName ?? indexerPath
|
|
30
|
+
},
|
|
31
|
+
findings,
|
|
32
|
+
assumptions: [
|
|
33
|
+
"JSON and YAML configs were parsed statically; subgraph-style handler fields were summarized when present.",
|
|
34
|
+
`The loaded registry is dated ${registry.lastUpdated}. Glamsterdam scope and gas parameters may change.`
|
|
35
|
+
],
|
|
36
|
+
limitations: [
|
|
37
|
+
"Static indexer configuration cannot prove runtime indexing behavior.",
|
|
38
|
+
"Heuristics may miss compatibility logic implemented in code, environment variables, infrastructure manifests, or downstream pipelines.",
|
|
39
|
+
"Findings marked heuristic should be treated as review prompts."
|
|
40
|
+
]
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export function summarizeHandlers(config) {
|
|
44
|
+
const dataSources = findArraysByKey(config, "dataSources").flat();
|
|
45
|
+
const templates = findArraysByKey(config, "templates").flat();
|
|
46
|
+
const sources = [...dataSources, ...templates].filter(isRecord);
|
|
47
|
+
if (sources.length === 0) {
|
|
48
|
+
return {
|
|
49
|
+
eventHandlers: countArraysByKey(config, "eventHandlers"),
|
|
50
|
+
callHandlers: countArraysByKey(config, "callHandlers"),
|
|
51
|
+
blockHandlers: countArraysByKey(config, "blockHandlers")
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return sources.reduce((summary, source) => {
|
|
55
|
+
const mapping = isRecord(source.mapping) ? source.mapping : source;
|
|
56
|
+
summary.eventHandlers += arrayLength(mapping.eventHandlers);
|
|
57
|
+
summary.callHandlers += arrayLength(mapping.callHandlers);
|
|
58
|
+
summary.blockHandlers += arrayLength(mapping.blockHandlers);
|
|
59
|
+
return summary;
|
|
60
|
+
}, { eventHandlers: 0, callHandlers: 0, blockHandlers: 0 });
|
|
61
|
+
}
|
|
62
|
+
function detectMissingCompatibilityMetadata(rawText) {
|
|
63
|
+
const lower = rawText.toLowerCase();
|
|
64
|
+
const hasMetadata = lower.includes("glamsterdam")
|
|
65
|
+
|| lower.includes("eip")
|
|
66
|
+
|| lower.includes("fork")
|
|
67
|
+
|| lower.includes("compatibility");
|
|
68
|
+
if (hasMetadata) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
return [
|
|
72
|
+
makeFinding({
|
|
73
|
+
id: "indexer.missing-fork-compatibility-metadata",
|
|
74
|
+
title: "No explicit fork or EIP compatibility metadata found",
|
|
75
|
+
severity: "medium",
|
|
76
|
+
confidence: "medium",
|
|
77
|
+
domain: domains("indexer", "tooling"),
|
|
78
|
+
relatedEips: ["EIP-7928", "EIP-7708"],
|
|
79
|
+
description: "The config does not include explicit Glamsterdam, fork, or EIP compatibility metadata. That may be fine for older configs, but it makes readiness tracking harder.",
|
|
80
|
+
evidence: ["No Glamsterdam/fork/EIP/compatibility marker found in configuration text."],
|
|
81
|
+
recommendation: "Add a small compatibility section that records reviewed EIPs, replay status, testnet/devnet coverage, and owner notes."
|
|
82
|
+
})
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
function detectMissingReplayPlan(rawText) {
|
|
86
|
+
const lower = rawText.toLowerCase();
|
|
87
|
+
const hasReplayPlan = lower.includes("replay")
|
|
88
|
+
|| lower.includes("testnet")
|
|
89
|
+
|| lower.includes("devnet")
|
|
90
|
+
|| lower.includes("fork test")
|
|
91
|
+
|| lower.includes("fork-test");
|
|
92
|
+
if (hasReplayPlan) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
return [
|
|
96
|
+
makeFinding({
|
|
97
|
+
id: "indexer.missing-replay-plan",
|
|
98
|
+
title: "No replay, testnet, or devnet plan found",
|
|
99
|
+
severity: "medium",
|
|
100
|
+
confidence: "medium",
|
|
101
|
+
domain: domains("indexer", "monitoring"),
|
|
102
|
+
relatedEips: ["EIP-7928", "EIP-7708", "GAS-REPRICING"],
|
|
103
|
+
description: "The config does not mention a replay or testnet/devnet plan. Indexer compatibility is usually validated by replaying representative data rather than by static config alone.",
|
|
104
|
+
evidence: ["No replay/testnet/devnet marker found in configuration text."],
|
|
105
|
+
recommendation: "Add a replay plan that covers historical sync, recent blocks, expected reorg behavior, native ETH transfer handling, and BAL-related block data."
|
|
106
|
+
})
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
function countArraysByKey(value, key) {
|
|
110
|
+
return findArraysByKey(value, key).reduce((total, array) => total + array.length, 0);
|
|
111
|
+
}
|
|
112
|
+
function findArraysByKey(value, key) {
|
|
113
|
+
if (Array.isArray(value)) {
|
|
114
|
+
return value.flatMap((item) => findArraysByKey(item, key));
|
|
115
|
+
}
|
|
116
|
+
if (!isRecord(value)) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
const own = Array.isArray(value[key]) ? [value[key]] : [];
|
|
120
|
+
const nested = Object.values(value).flatMap((child) => findArraysByKey(child, key));
|
|
121
|
+
return [...own, ...nested];
|
|
122
|
+
}
|
|
123
|
+
function arrayLength(value) {
|
|
124
|
+
return Array.isArray(value) ? value.length : 0;
|
|
125
|
+
}
|
|
126
|
+
function isRecord(value) {
|
|
127
|
+
return typeof value === "object" && value !== null;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=indexerScanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexerScanner.js","sourceRoot":"","sources":["../../src/scanners/indexerScanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type TraceScanOptions } from "./traceScanner.js";
|
|
2
|
+
import type { CompatibilityReport } from "../reports/reportTypes.js";
|
|
3
|
+
export type DebugTraceMode = "structLogs" | "callTracer";
|
|
4
|
+
export type RpcFetch = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
|
5
|
+
export interface FetchDebugTraceOptions {
|
|
6
|
+
rpcUrl: string;
|
|
7
|
+
txHash: string;
|
|
8
|
+
tracer?: DebugTraceMode;
|
|
9
|
+
traceTimeout?: string;
|
|
10
|
+
rpcTimeoutMs?: number;
|
|
11
|
+
fetch?: RpcFetch;
|
|
12
|
+
}
|
|
13
|
+
export interface ScanTransactionTraceOptions extends TraceScanOptions, FetchDebugTraceOptions {
|
|
14
|
+
}
|
|
15
|
+
interface JsonRpcError {
|
|
16
|
+
code?: number;
|
|
17
|
+
message?: string;
|
|
18
|
+
data?: unknown;
|
|
19
|
+
}
|
|
20
|
+
interface JsonRpcEnvelope {
|
|
21
|
+
jsonrpc?: string;
|
|
22
|
+
id?: unknown;
|
|
23
|
+
result?: unknown;
|
|
24
|
+
error?: JsonRpcError;
|
|
25
|
+
}
|
|
26
|
+
export declare function scanTransactionTrace(options: ScanTransactionTraceOptions): Promise<CompatibilityReport>;
|
|
27
|
+
export interface ScannedTransactionTrace {
|
|
28
|
+
trace: JsonRpcEnvelope;
|
|
29
|
+
report: CompatibilityReport;
|
|
30
|
+
}
|
|
31
|
+
export declare function fetchAndScanTransactionTrace(options: ScanTransactionTraceOptions): Promise<ScannedTransactionTrace>;
|
|
32
|
+
export declare function scanFetchedTransactionTrace(traceEnvelope: unknown, options: TraceScanOptions & {
|
|
33
|
+
txHash: string;
|
|
34
|
+
tracer?: DebugTraceMode;
|
|
35
|
+
}): CompatibilityReport;
|
|
36
|
+
export declare function writeFetchedTrace(traceEnvelope: unknown, traceOutPath: string): string;
|
|
37
|
+
export declare function fetchDebugTraceTransaction(options: FetchDebugTraceOptions): Promise<JsonRpcEnvelope>;
|
|
38
|
+
export declare function normalizeTxHash(txHash: string): string;
|
|
39
|
+
export declare function parseDebugTraceMode(value: string): DebugTraceMode;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { scanTrace } from "./traceScanner.js";
|
|
4
|
+
const defaultTraceTimeout = "30s";
|
|
5
|
+
const defaultRpcTimeoutMs = 30_000;
|
|
6
|
+
export async function scanTransactionTrace(options) {
|
|
7
|
+
const { report } = await fetchAndScanTransactionTrace(options);
|
|
8
|
+
return report;
|
|
9
|
+
}
|
|
10
|
+
export async function fetchAndScanTransactionTrace(options) {
|
|
11
|
+
const traceEnvelope = await fetchDebugTraceTransaction(options);
|
|
12
|
+
return {
|
|
13
|
+
trace: traceEnvelope,
|
|
14
|
+
report: scanFetchedTransactionTrace(traceEnvelope, options)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function scanFetchedTransactionTrace(traceEnvelope, options) {
|
|
18
|
+
const tracer = options.tracer ?? "structLogs";
|
|
19
|
+
const report = scanTrace(traceEnvelope, {
|
|
20
|
+
...options,
|
|
21
|
+
targetName: options.targetName ?? `tx ${options.txHash}`
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
...report,
|
|
25
|
+
assumptions: [
|
|
26
|
+
`Trace was fetched with debug_traceTransaction using ${tracer} mode.`,
|
|
27
|
+
...report.assumptions
|
|
28
|
+
],
|
|
29
|
+
limitations: [
|
|
30
|
+
...report.limitations,
|
|
31
|
+
"RPC trace availability, tracer names, and returned fields vary by execution client and node configuration.",
|
|
32
|
+
"The RPC URL is intentionally not included in the report target or evidence."
|
|
33
|
+
]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function writeFetchedTrace(traceEnvelope, traceOutPath) {
|
|
37
|
+
const resolvedPath = resolve(process.cwd(), traceOutPath);
|
|
38
|
+
mkdirSync(dirname(resolvedPath), { recursive: true });
|
|
39
|
+
writeFileSync(resolvedPath, `${JSON.stringify(traceEnvelope, null, 2)}\n`, "utf8");
|
|
40
|
+
return resolvedPath;
|
|
41
|
+
}
|
|
42
|
+
export async function fetchDebugTraceTransaction(options) {
|
|
43
|
+
const txHash = normalizeTxHash(options.txHash);
|
|
44
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
45
|
+
if (typeof fetchImpl !== "function") {
|
|
46
|
+
throw new Error("No fetch implementation is available. Use Node.js 20+ or provide a fetch implementation.");
|
|
47
|
+
}
|
|
48
|
+
const controller = new AbortController();
|
|
49
|
+
const timeoutMs = options.rpcTimeoutMs ?? defaultRpcTimeoutMs;
|
|
50
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
51
|
+
try {
|
|
52
|
+
const response = await fetchImpl(options.rpcUrl, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: {
|
|
55
|
+
"content-type": "application/json"
|
|
56
|
+
},
|
|
57
|
+
body: JSON.stringify({
|
|
58
|
+
jsonrpc: "2.0",
|
|
59
|
+
id: 1,
|
|
60
|
+
method: "debug_traceTransaction",
|
|
61
|
+
params: [txHash, traceConfig(options.tracer ?? "structLogs", options.traceTimeout ?? defaultTraceTimeout)]
|
|
62
|
+
}),
|
|
63
|
+
signal: controller.signal
|
|
64
|
+
});
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
throw new Error(`RPC request failed with HTTP ${response.status}.`);
|
|
67
|
+
}
|
|
68
|
+
const payload = await response.json();
|
|
69
|
+
if (payload.error) {
|
|
70
|
+
const code = typeof payload.error.code === "number" ? ` ${payload.error.code}` : "";
|
|
71
|
+
const message = payload.error.message ?? "Unknown JSON-RPC error";
|
|
72
|
+
throw new Error(`RPC debug_traceTransaction failed${code}: ${message}`);
|
|
73
|
+
}
|
|
74
|
+
if (!("result" in payload) || payload.result === null || payload.result === undefined) {
|
|
75
|
+
throw new Error("RPC debug_traceTransaction returned no trace result.");
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
jsonrpc: payload.jsonrpc ?? "2.0",
|
|
79
|
+
id: payload.id ?? 1,
|
|
80
|
+
result: payload.result
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
85
|
+
throw new Error(`RPC request timed out after ${timeoutMs}ms.`);
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
clearTimeout(timeout);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function normalizeTxHash(txHash) {
|
|
94
|
+
const normalized = txHash.trim();
|
|
95
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(normalized)) {
|
|
96
|
+
throw new Error("Transaction hash must be a 0x-prefixed 32-byte hex string.");
|
|
97
|
+
}
|
|
98
|
+
return normalized;
|
|
99
|
+
}
|
|
100
|
+
export function parseDebugTraceMode(value) {
|
|
101
|
+
if (value === "structLogs" || value === "callTracer") {
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
throw new Error(`Unsupported trace mode "${value}". Use "structLogs" or "callTracer".`);
|
|
105
|
+
}
|
|
106
|
+
function traceConfig(tracer, timeout) {
|
|
107
|
+
if (tracer === "callTracer") {
|
|
108
|
+
return {
|
|
109
|
+
tracer: "callTracer",
|
|
110
|
+
timeout
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
disableStack: true,
|
|
115
|
+
disableStorage: true,
|
|
116
|
+
enableMemory: false,
|
|
117
|
+
enableReturnData: false,
|
|
118
|
+
timeout
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=rpcTraceScanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpcTraceScanner.js","sourceRoot":"","sources":["../../src/scanners/rpcTraceScanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type DetectorThresholds } from "../detectors/thresholds.js";
|
|
2
|
+
import type { EipRegistry } from "../registry/schemas.js";
|
|
3
|
+
import { type CompatibilityReport } from "../reports/reportTypes.js";
|
|
4
|
+
export interface NormalizedTraceStep {
|
|
5
|
+
op: string;
|
|
6
|
+
depth?: number;
|
|
7
|
+
gas?: number;
|
|
8
|
+
gasCost?: number;
|
|
9
|
+
calldataBytes?: number;
|
|
10
|
+
logs?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface TraceScanOptions {
|
|
13
|
+
registry?: EipRegistry;
|
|
14
|
+
registryPath?: string;
|
|
15
|
+
thresholds?: DetectorThresholds;
|
|
16
|
+
thresholdsPath?: string;
|
|
17
|
+
targetName?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function scanTraceFile(traceFile: string, options?: TraceScanOptions): CompatibilityReport;
|
|
20
|
+
export declare function scanTrace(input: unknown, options?: TraceScanOptions): CompatibilityReport;
|
|
21
|
+
export declare function normalizeTrace(input: unknown): {
|
|
22
|
+
steps: NormalizedTraceStep[];
|
|
23
|
+
warnings: string[];
|
|
24
|
+
};
|