dequanto 0.2.7 → 0.2.12
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/lib/cjs/clients/debug/ClientDebugMethods.js +3 -13
- package/lib/cjs/clients/debug/ClientDebugMethods.js.map +1 -1
- package/lib/cjs/hardhat/HardhatProvider.js +7 -2
- package/lib/cjs/hardhat/HardhatProvider.js.map +1 -1
- package/lib/cjs/hardhat/HardhatWeb3Client.js +4 -0
- package/lib/cjs/hardhat/HardhatWeb3Client.js.map +1 -1
- package/lib/cjs/txs/TxDataBuilder.js +20 -0
- package/lib/cjs/txs/TxDataBuilder.js.map +1 -1
- package/lib/cjs/utils/$color.js +2 -2
- package/lib/cjs/utils/$color.js.map +1 -1
- package/lib/cjs/utils/$contract.js +10 -0
- package/lib/cjs/utils/$contract.js.map +1 -1
- package/lib/cjs/utils/$hex.js +1 -1
- package/lib/cjs/utils/$hex.js.map +1 -1
- package/lib/cjs/utils/$require.js +7 -0
- package/lib/cjs/utils/$require.js.map +1 -1
- package/lib/cjs/utils/$sig.js +1 -1
- package/lib/cjs/utils/$sig.js.map +1 -1
- package/lib/cjs/utils/$traces.js +492 -0
- package/lib/cjs/utils/$traces.js.map +1 -0
- package/lib/esm/clients/debug/ClientDebugMethods.js.map +1 -1
- package/lib/esm/clients/debug/ClientDebugMethods.mjs +3 -13
- package/lib/esm/hardhat/HardhatProvider.js.map +1 -1
- package/lib/esm/hardhat/HardhatProvider.mjs +7 -2
- package/lib/esm/hardhat/HardhatWeb3Client.js.map +1 -1
- package/lib/esm/hardhat/HardhatWeb3Client.mjs +4 -0
- package/lib/esm/txs/TxDataBuilder.js.map +1 -1
- package/lib/esm/txs/TxDataBuilder.mjs +20 -0
- package/lib/esm/utils/$color.js.map +1 -1
- package/lib/esm/utils/$color.mjs +1 -1
- package/lib/esm/utils/$contract.js.map +1 -1
- package/lib/esm/utils/$contract.mjs +10 -0
- package/lib/esm/utils/$hex.js.map +1 -1
- package/lib/esm/utils/$hex.mjs +1 -1
- package/lib/esm/utils/$require.js.map +1 -1
- package/lib/esm/utils/$require.mjs +7 -0
- package/lib/esm/utils/$sig.js.map +1 -1
- package/lib/esm/utils/$sig.mjs +1 -1
- package/lib/esm/utils/$traces.js.map +1 -0
- package/lib/esm/utils/$traces.mjs +486 -0
- package/lib/types/clients/debug/ClientDebugMethods.d.ts +18 -0
- package/lib/types/clients/interfaces/IWeb3Client.d.ts +4 -0
- package/lib/types/models/TEth.d.ts +2 -2
- package/lib/types/utils/$color.d.ts +62 -0
- package/lib/types/utils/$contract.d.ts +8 -0
- package/lib/types/utils/$require.d.ts +2 -0
- package/lib/types/utils/$traces.d.ts +18 -0
- package/package.json +1 -1
- package/src/clients/debug/ClientDebugMethods.ts +23 -14
- package/src/clients/interfaces/IWeb3Client.ts +4 -0
- package/src/hardhat/HardhatProvider.ts +8 -3
- package/src/hardhat/HardhatWeb3Client.ts +4 -0
- package/src/models/TEth.ts +2 -2
- package/src/txs/TxDataBuilder.ts +20 -0
- package/src/types/TAbi.ts +1 -1
- package/src/utils/$color.ts +1 -1
- package/src/utils/$contract.ts +17 -1
- package/src/utils/$hex.ts +1 -1
- package/src/utils/$require.ts +9 -0
- package/src/utils/$sig.ts +1 -1
- package/src/utils/$traces.ts +600 -0
- package/lib/cjs/utils/$sign.js +0 -118
- package/lib/cjs/utils/$sign.js.map +0 -1
- package/lib/esm/utils/$sign.js.map +0 -1
- package/lib/esm/utils/$sign.mjs +0 -117
- package/lib/types/utils/$sign.d.ts +0 -1
- package/src/utils/$sign.ts +0 -136
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
import { $abiCoder } from '@dequanto/abi/$abiCoder';
|
|
2
|
+
import { TClientDebugTraces } from '@dequanto/clients/debug/ClientDebugMethods';
|
|
3
|
+
import { TEth } from '@dequanto/models/TEth';
|
|
4
|
+
import { $abiUtils } from './$abiUtils';
|
|
5
|
+
import { $bigint } from './$bigint';
|
|
6
|
+
import { $hex } from '@dequanto/utils/$hex';
|
|
7
|
+
import { $color, ColorData } from './$color';
|
|
8
|
+
import alot from 'alot';
|
|
9
|
+
|
|
10
|
+
export namespace $traces {
|
|
11
|
+
|
|
12
|
+
type TStructLog = TClientDebugTraces['structLogs'][0];
|
|
13
|
+
type TAbiMeta = {
|
|
14
|
+
name?: string
|
|
15
|
+
contractName?: string
|
|
16
|
+
abi: TEth.Abi.Item[]
|
|
17
|
+
};
|
|
18
|
+
type TTraceEvent = {
|
|
19
|
+
index: number
|
|
20
|
+
op: string
|
|
21
|
+
topics: TEth.Hex[]
|
|
22
|
+
data: TEth.Hex
|
|
23
|
+
label?: string
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type TTraceFrame = {
|
|
27
|
+
id: number
|
|
28
|
+
parentId: number | null
|
|
29
|
+
depth: number
|
|
30
|
+
start: number
|
|
31
|
+
end: number
|
|
32
|
+
callSite: number | null
|
|
33
|
+
callOp: string
|
|
34
|
+
to: TEth.Address | null
|
|
35
|
+
value: string | number | bigint | null
|
|
36
|
+
input: TEth.Hex | null
|
|
37
|
+
selector: TEth.Hex | null
|
|
38
|
+
output: TEth.Hex | null
|
|
39
|
+
children: TTraceFrame[]
|
|
40
|
+
events: TTraceEvent[]
|
|
41
|
+
failed?: boolean
|
|
42
|
+
exitOp?: string
|
|
43
|
+
label?: string
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const CALL_OPS = new Set([ 'CALL', 'STATICCALL', 'DELEGATECALL', 'CALLCODE' ]);
|
|
47
|
+
const FAILURE_OPS = new Set([ 'REVERT', 'INVALID' ]);
|
|
48
|
+
|
|
49
|
+
export async function format (params: {
|
|
50
|
+
tx: TEth.TxLike
|
|
51
|
+
traces: TClientDebugTraces
|
|
52
|
+
getABI (contract: TEth.Address): Promise<TAbiMeta>
|
|
53
|
+
color?: boolean
|
|
54
|
+
shortAddress?: boolean
|
|
55
|
+
addresses?: Record<TEth.Address, string>,
|
|
56
|
+
}): Promise<string> {
|
|
57
|
+
const logs = params.traces.structLogs ?? [];
|
|
58
|
+
const frames = logs.length > 0
|
|
59
|
+
? buildFrames(logs)
|
|
60
|
+
: [];
|
|
61
|
+
const abiCache = new Map<string, Promise<TAbiMeta | null>>();
|
|
62
|
+
|
|
63
|
+
const entryAbiMeta = params.tx.to != null
|
|
64
|
+
? await getAbiMeta(params.tx.to)
|
|
65
|
+
: null;
|
|
66
|
+
const entryAbiItem = getFunctionAbiItem(entryAbiMeta, getSelector(params.tx));
|
|
67
|
+
|
|
68
|
+
if (frames[0] != null) {
|
|
69
|
+
frames[0].to = params.tx.to ?? null;
|
|
70
|
+
frames[0].value = params.tx.value ?? null;
|
|
71
|
+
frames[0].input = getInput(params.tx);
|
|
72
|
+
frames[0].selector = getSelector(params.tx);
|
|
73
|
+
frames[0].output = params.traces.returnValue ?? frames[0].output;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await Promise.all(frames.map(async frame => {
|
|
77
|
+
let abiMeta = frame.to != null
|
|
78
|
+
? await getAbiMeta(frame.to)
|
|
79
|
+
: null;
|
|
80
|
+
let abiItem = getFunctionAbiItem(abiMeta, frame.selector);
|
|
81
|
+
|
|
82
|
+
if (frame.id !== 0) {
|
|
83
|
+
frame.label = formatFrameLabel(frame, abiMeta?.contractName ?? abiMeta?.name, abiItem, params);
|
|
84
|
+
}
|
|
85
|
+
for (let event of frame.events) {
|
|
86
|
+
event.label = formatEventLabel(event, abiMeta, params);
|
|
87
|
+
}
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
for (let frame of frames) {
|
|
91
|
+
if (frame.id !== 0) {
|
|
92
|
+
frame.label ??= formatFrameLabel(frame, null, null, params);
|
|
93
|
+
}
|
|
94
|
+
for (let event of frame.events) {
|
|
95
|
+
event.label ??= formatEventLabel(event, null, params);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let lines: string[] = [];
|
|
100
|
+
let entryLine = formatEntrypointLabel(params.tx, params.traces, entryAbiMeta?.contractName ?? entryAbiMeta?.name, entryAbiItem, params);
|
|
101
|
+
lines.push(`${entryLine}${params.traces.failed ? ' [FAILED]' : ''}`);
|
|
102
|
+
|
|
103
|
+
let root = frames[0];
|
|
104
|
+
if (root != null) {
|
|
105
|
+
appendEntries(root, 1, lines);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let addresses = alot.fromObject(params.addresses ?? {}).map(x => {
|
|
109
|
+
return `${x.key} <=> ${x.value}`;
|
|
110
|
+
}).toArray();
|
|
111
|
+
|
|
112
|
+
lines.push('\n', ...addresses);
|
|
113
|
+
|
|
114
|
+
return lines.join('\n');
|
|
115
|
+
|
|
116
|
+
async function getAbiMeta(contract: TEth.Address): Promise<TAbiMeta | null> {
|
|
117
|
+
let key = contract.toLowerCase();
|
|
118
|
+
if (abiCache.has(key) === false) {
|
|
119
|
+
abiCache.set(key, params.getABI(contract).catch(() => null));
|
|
120
|
+
}
|
|
121
|
+
return await abiCache.get(key);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function appendEntries(frame: TTraceFrame, level: number, out: string[]) {
|
|
125
|
+
let indent = ' '.repeat(level);
|
|
126
|
+
let entries = [
|
|
127
|
+
...frame.children.map(child => ({ kind: 'frame' as const, index: child.start, frame: child })),
|
|
128
|
+
...frame.events.map(event => ({ kind: 'event' as const, index: event.index, event })),
|
|
129
|
+
].sort((a, b) => a.index - b.index);
|
|
130
|
+
|
|
131
|
+
for (let entry of entries) {
|
|
132
|
+
if (entry.kind === 'frame') {
|
|
133
|
+
out.push(`${indent}${entry.frame.label}${entry.frame.failed ? ' [FAILED]' : ''}`);
|
|
134
|
+
appendEntries(entry.frame, level + 1, out);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
out.push(`${indent}${entry.event.label}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function buildFrames(logs: TStructLog[]): TTraceFrame[] {
|
|
143
|
+
let root: TTraceFrame = {
|
|
144
|
+
id: 0,
|
|
145
|
+
parentId: null,
|
|
146
|
+
depth: logs[0].depth,
|
|
147
|
+
start: 0,
|
|
148
|
+
end: logs.length - 1,
|
|
149
|
+
callSite: null,
|
|
150
|
+
callOp: 'ROOT',
|
|
151
|
+
to: null,
|
|
152
|
+
value: null,
|
|
153
|
+
input: null,
|
|
154
|
+
selector: null,
|
|
155
|
+
output: null,
|
|
156
|
+
children: [],
|
|
157
|
+
events: []
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
let frames = [ root ];
|
|
161
|
+
let open = [ root ];
|
|
162
|
+
|
|
163
|
+
for (let i = 1; i < logs.length; i++) {
|
|
164
|
+
let prev = logs[i - 1];
|
|
165
|
+
let current = logs[i];
|
|
166
|
+
|
|
167
|
+
while (open.length > 0 && open[open.length - 1].depth > current.depth) {
|
|
168
|
+
let closing = open.pop();
|
|
169
|
+
closing.end = i - 1;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let active = open[open.length - 1];
|
|
173
|
+
if (current.depth > active.depth) {
|
|
174
|
+
let meta = CALL_OPS.has(prev.op)
|
|
175
|
+
? getCallMeta(prev)
|
|
176
|
+
: { op: prev.op, to: null, value: null, input: null, selector: null };
|
|
177
|
+
|
|
178
|
+
let frame: TTraceFrame = {
|
|
179
|
+
id: frames.length,
|
|
180
|
+
parentId: active.id,
|
|
181
|
+
depth: current.depth,
|
|
182
|
+
start: i,
|
|
183
|
+
end: i,
|
|
184
|
+
callSite: i - 1,
|
|
185
|
+
callOp: meta.op,
|
|
186
|
+
to: meta.to,
|
|
187
|
+
value: meta.value,
|
|
188
|
+
input: meta.input,
|
|
189
|
+
selector: meta.selector,
|
|
190
|
+
output: null,
|
|
191
|
+
children: [],
|
|
192
|
+
events: []
|
|
193
|
+
};
|
|
194
|
+
active.children.push(frame);
|
|
195
|
+
frames.push(frame);
|
|
196
|
+
open.push(frame);
|
|
197
|
+
active = frame;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let event = getEventMeta(current, i);
|
|
201
|
+
if (event != null) {
|
|
202
|
+
active.events.push(event);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
active.end = i;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
while (open.length > 0) {
|
|
209
|
+
let frame = open.pop();
|
|
210
|
+
frame.end = logs.length - 1;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
for (let frame of frames) {
|
|
214
|
+
let endLog = logs[frame.end];
|
|
215
|
+
frame.exitOp = endLog?.op;
|
|
216
|
+
frame.failed = FAILURE_OPS.has(frame.exitOp);
|
|
217
|
+
frame.output = getOutputMeta(endLog);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return frames;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function getCallMeta(log: TStructLog): {
|
|
224
|
+
op: string
|
|
225
|
+
to: TEth.Address | null
|
|
226
|
+
value: bigint | null
|
|
227
|
+
input: TEth.Hex | null
|
|
228
|
+
selector: TEth.Hex | null
|
|
229
|
+
} {
|
|
230
|
+
let stack = log.stack ?? [];
|
|
231
|
+
let to: TEth.Address | null = null;
|
|
232
|
+
let value: bigint | null = null;
|
|
233
|
+
let inputOffset = 0;
|
|
234
|
+
let inputSize = 0;
|
|
235
|
+
|
|
236
|
+
if (log.op === 'CALL' || log.op === 'CALLCODE') {
|
|
237
|
+
if (stack.length < 7) {
|
|
238
|
+
return { op: log.op, to: null, value: null, input: null, selector: null };
|
|
239
|
+
}
|
|
240
|
+
to = toAddress(stack[stack.length - 2]);
|
|
241
|
+
value = toBigInt(stack[stack.length - 3]);
|
|
242
|
+
inputOffset = toNumber(stack[stack.length - 4]);
|
|
243
|
+
inputSize = toNumber(stack[stack.length - 5]);
|
|
244
|
+
} else if (log.op === 'STATICCALL' || log.op === 'DELEGATECALL') {
|
|
245
|
+
if (stack.length < 6) {
|
|
246
|
+
return { op: log.op, to: null, value: null, input: null, selector: null };
|
|
247
|
+
}
|
|
248
|
+
to = toAddress(stack[stack.length - 2]);
|
|
249
|
+
inputOffset = toNumber(stack[stack.length - 3]);
|
|
250
|
+
inputSize = toNumber(stack[stack.length - 4]);
|
|
251
|
+
} else {
|
|
252
|
+
return { op: log.op, to: null, value: null, input: null, selector: null };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let input = readMemory(log.memory ?? [], inputOffset, inputSize);
|
|
256
|
+
let selector = input != null && input.length >= 10
|
|
257
|
+
? input.slice(0, 10) as TEth.Hex
|
|
258
|
+
: null;
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
op: log.op,
|
|
262
|
+
to,
|
|
263
|
+
value,
|
|
264
|
+
input,
|
|
265
|
+
selector,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function getEventMeta(log: TStructLog, index: number): TTraceEvent | null {
|
|
270
|
+
let match = /^LOG([0-4])$/.exec(log.op);
|
|
271
|
+
if (match == null) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
let topicCount = Number(match[1]);
|
|
275
|
+
let stack = log.stack ?? [];
|
|
276
|
+
if (stack.length < topicCount + 2) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
let offset = toNumber(stack[stack.length - 1]);
|
|
280
|
+
let size = toNumber(stack[stack.length - 2]);
|
|
281
|
+
let topics = Array.from({ length: topicCount }, (_, i) => {
|
|
282
|
+
return $hex.ensure(stack[stack.length - 3 - i]) as TEth.Hex;
|
|
283
|
+
});
|
|
284
|
+
let data = readMemory(log.memory ?? [], offset, size) ?? '0x';
|
|
285
|
+
return {
|
|
286
|
+
index,
|
|
287
|
+
op: log.op,
|
|
288
|
+
topics,
|
|
289
|
+
data,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function readMemory(memory: string[], offset: number, size: number): TEth.Hex | null {
|
|
294
|
+
if (size <= 0 || memory.length === 0) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
let hex = memory.join('');
|
|
298
|
+
let start = offset * 2;
|
|
299
|
+
let end = start + size * 2;
|
|
300
|
+
return `0x${hex.slice(start, end)}` as TEth.Hex;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function formatEntrypointLabel(
|
|
304
|
+
tx: TEth.TxLike,
|
|
305
|
+
traces: TClientDebugTraces,
|
|
306
|
+
contractName: string | null,
|
|
307
|
+
abi: TEth.Abi.Item | null,
|
|
308
|
+
params: {
|
|
309
|
+
color?: boolean
|
|
310
|
+
}
|
|
311
|
+
): string {
|
|
312
|
+
let frame: TTraceFrame = {
|
|
313
|
+
id: -1,
|
|
314
|
+
parentId: null,
|
|
315
|
+
depth: 0,
|
|
316
|
+
start: 0,
|
|
317
|
+
end: 0,
|
|
318
|
+
callSite: null,
|
|
319
|
+
callOp: '',
|
|
320
|
+
to: tx.to ?? null,
|
|
321
|
+
value: tx.value ?? null,
|
|
322
|
+
input: getInput(tx),
|
|
323
|
+
selector: getSelector(tx),
|
|
324
|
+
output: traces.returnValue ?? null,
|
|
325
|
+
children: [],
|
|
326
|
+
events: [],
|
|
327
|
+
failed: traces.failed,
|
|
328
|
+
exitOp: null,
|
|
329
|
+
};
|
|
330
|
+
return formatFrameLabel(frame, contractName, abi, params);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function formatFrameLabel(frame: TTraceFrame, contractName: string | null, abi: TEth.Abi.Item | null, params: {
|
|
334
|
+
shortAddress?: boolean
|
|
335
|
+
color?: boolean
|
|
336
|
+
addresses?: Record<string, string>
|
|
337
|
+
}): string {
|
|
338
|
+
let head = color('gray', frame.callOp.toLowerCase(), params);
|
|
339
|
+
let suffix = '';
|
|
340
|
+
|
|
341
|
+
if (frame.to != null) {
|
|
342
|
+
let address = shortAddress(frame.to, params);
|
|
343
|
+
if (contractName != null && abi != null && 'name' in abi && abi.name != null) {
|
|
344
|
+
let args = frame.input != null
|
|
345
|
+
? decodeArgs(abi, frame.input, params)
|
|
346
|
+
: '';
|
|
347
|
+
if (params != null) {
|
|
348
|
+
params.addresses ??= {};
|
|
349
|
+
params.addresses[address] = contractName;
|
|
350
|
+
}
|
|
351
|
+
suffix = `${color('bold', contractName, params)}.${color('magenta', abi.name, params)}(${args})`;
|
|
352
|
+
} else {
|
|
353
|
+
suffix = `${address}${frame.selector ? ` ${frame.selector}` : ''}`;
|
|
354
|
+
if (frame.input != null) {
|
|
355
|
+
suffix += ` calldata=${shortHex(frame.input, 18, 10)}`;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (frame.value != null && frame.value !== 0n) {
|
|
361
|
+
suffix += `${suffix ? ' ' : ''}`;
|
|
362
|
+
if (frame.value && BigInt(frame.value) > 0n) {
|
|
363
|
+
suffix += `value=${frame.value.toString()}`
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (frame.exitOp != null && frame.exitOp !== 'STOP' && frame.exitOp !== 'RETURN') {
|
|
367
|
+
suffix += `${suffix ? ' ' : ''}exit=${frame.exitOp}`;
|
|
368
|
+
}
|
|
369
|
+
if (frame.output != null) {
|
|
370
|
+
let returnValue = formatReturnValue(frame, abi, params);
|
|
371
|
+
suffix += `: ${returnValue}`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return suffix ? `${head} ${suffix}` : head;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function formatEventLabel(event: TTraceEvent, abiMeta: TAbiMeta | null | undefined, params: {
|
|
378
|
+
color?: boolean
|
|
379
|
+
}): string {
|
|
380
|
+
let abi = getEventAbiItem(abiMeta, event.topics);
|
|
381
|
+
if (abi == null) {
|
|
382
|
+
let head = color('cyan', 'event', params);
|
|
383
|
+
let name = event.topics[0] != null
|
|
384
|
+
? shortHex(event.topics[0], 18, 10)
|
|
385
|
+
: event.op;
|
|
386
|
+
return `${head} ${name}`;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
let args = decodeEventArgs(abi, event, params);
|
|
390
|
+
return `${color('cyan', 'event', params)} ${color('magenta', abi.name, params)}(${args})`;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function getFunctionAbiItem(abiMeta: TAbiMeta | null | undefined, selector: TEth.Hex | null): TEth.Abi.Item | null {
|
|
394
|
+
if (abiMeta == null || selector == null) {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
return abiMeta.abi.find(item => {
|
|
398
|
+
if (item.type !== 'function' || !('name' in item)) {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
return $abiUtils.getMethodSignature(item) === selector;
|
|
402
|
+
}) ?? null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function getEventAbiItem(abiMeta: TAbiMeta | null | undefined, topics: TEth.Hex[]): TEth.Abi.Item | null {
|
|
406
|
+
if (abiMeta == null) {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
return abiMeta.abi.find(item => {
|
|
410
|
+
if (item.type !== 'event' || !('name' in item) || item.name == null) {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
if ('anonymous' in item && item.anonymous === true) {
|
|
414
|
+
return topics.length === (item.inputs?.filter(x => x.indexed).length ?? 0);
|
|
415
|
+
}
|
|
416
|
+
return topics[0] === $abiUtils.getTopicSignature(item);
|
|
417
|
+
}) ?? null;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function decodeArgs(abi: TEth.Abi.Item, input: TEth.Hex, params: {
|
|
421
|
+
color?: boolean
|
|
422
|
+
}): string {
|
|
423
|
+
try {
|
|
424
|
+
let inputs = 'inputs' in abi && Array.isArray(abi.inputs)
|
|
425
|
+
? abi.inputs
|
|
426
|
+
: [];
|
|
427
|
+
if (inputs.length === 0) {
|
|
428
|
+
return '';
|
|
429
|
+
}
|
|
430
|
+
let data = `0x${input.slice(10)}` as TEth.Hex;
|
|
431
|
+
let values = $abiCoder.decode(inputs as any, data);
|
|
432
|
+
return inputs.map((param, index) => {
|
|
433
|
+
let name = param.name?.trim() || `arg${index}`;
|
|
434
|
+
return `${name}: ${formatValue(values[index], params)}`;
|
|
435
|
+
}).join(', ');
|
|
436
|
+
} catch {
|
|
437
|
+
return shortHex(input, 18, 10);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function decodeEventArgs(abi: TEth.Abi.Item, event: TTraceEvent, params: {
|
|
442
|
+
color?: boolean
|
|
443
|
+
}): string {
|
|
444
|
+
try {
|
|
445
|
+
let inputs = 'inputs' in abi && Array.isArray(abi.inputs)
|
|
446
|
+
? abi.inputs
|
|
447
|
+
: [];
|
|
448
|
+
if (inputs.length === 0) {
|
|
449
|
+
return '';
|
|
450
|
+
}
|
|
451
|
+
let dataInputs = inputs.filter(x => x.indexed !== true);
|
|
452
|
+
let dataValues = dataInputs.length > 0
|
|
453
|
+
? $abiCoder.decode(dataInputs as any, event.data)
|
|
454
|
+
: [];
|
|
455
|
+
let topicIndex = 'anonymous' in abi && abi.anonymous === true ? 0 : 1;
|
|
456
|
+
let dataIndex = 0;
|
|
457
|
+
|
|
458
|
+
return inputs.map((param, index) => {
|
|
459
|
+
let name = param.name?.trim() || `arg${index}`;
|
|
460
|
+
let value;
|
|
461
|
+
if (param.indexed === true) {
|
|
462
|
+
let topic = event.topics[topicIndex++];
|
|
463
|
+
if (topic == null) {
|
|
464
|
+
value = 'undefined';
|
|
465
|
+
} else if ($abiUtils.isDynamicType(param.type)) {
|
|
466
|
+
value = topic;
|
|
467
|
+
} else {
|
|
468
|
+
value = $abiCoder.decode([ param ] as any, topic)[0];
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
value = dataValues[dataIndex++];
|
|
472
|
+
}
|
|
473
|
+
return `${name}: ${formatValue(value, params)}`;
|
|
474
|
+
}).join(', ');
|
|
475
|
+
} catch {
|
|
476
|
+
let rendered = [
|
|
477
|
+
...event.topics.map(topic => shortHex(topic, 18, 10)),
|
|
478
|
+
event.data !== '0x' ? shortHex(event.data, 18, 10) : null,
|
|
479
|
+
].filter(Boolean);
|
|
480
|
+
return rendered.join(', ');
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function formatReturnValue(frame: TTraceFrame, abi: TEth.Abi.Item | null, params: {
|
|
485
|
+
color?: boolean
|
|
486
|
+
}): string {
|
|
487
|
+
if (frame.output == null) {
|
|
488
|
+
return '0x';
|
|
489
|
+
}
|
|
490
|
+
if (frame.failed !== true && abi != null) {
|
|
491
|
+
try {
|
|
492
|
+
let outputs = 'outputs' in abi && Array.isArray(abi.outputs)
|
|
493
|
+
? abi.outputs
|
|
494
|
+
: [];
|
|
495
|
+
if (outputs.length > 0) {
|
|
496
|
+
let values = $abiCoder.decode(outputs as any, frame.output);
|
|
497
|
+
return outputs.map((param, index) => {
|
|
498
|
+
let name = param.name?.trim();
|
|
499
|
+
if (!name && outputs.length > 1) {
|
|
500
|
+
name = `out${index}`;
|
|
501
|
+
}
|
|
502
|
+
if (name) {
|
|
503
|
+
name += ': ';
|
|
504
|
+
}
|
|
505
|
+
return `${name ?? ''}${formatValue(values[index], params)}`;
|
|
506
|
+
}).join(', ');
|
|
507
|
+
}
|
|
508
|
+
} catch {
|
|
509
|
+
// Fall back to raw bytes if decoding fails.
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return shortHex(frame.output, 18, 10);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function getOutputMeta(log: TStructLog | null | undefined): TEth.Hex | null {
|
|
516
|
+
if (log == null || (log.op !== 'RETURN' && log.op !== 'REVERT')) {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
let stack = log.stack ?? [];
|
|
520
|
+
if (stack.length < 2) {
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
let offset = toNumber(stack[stack.length - 1]);
|
|
524
|
+
let size = toNumber(stack[stack.length - 2]);
|
|
525
|
+
return readMemory(log.memory ?? [], offset, size);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function getInput(tx: TEth.TxLike): TEth.Hex | null {
|
|
529
|
+
return tx.data ?? tx.input ?? null;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function getSelector(tx: TEth.TxLike): TEth.Hex | null {
|
|
533
|
+
let input = getInput(tx);
|
|
534
|
+
return input != null && input.length >= 10
|
|
535
|
+
? input.slice(0, 10) as TEth.Hex
|
|
536
|
+
: null;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function formatValue(value: any, params: { color?: boolean }): string {
|
|
540
|
+
if (typeof value === 'bigint') {
|
|
541
|
+
return color('cyan', value.toString(), params);
|
|
542
|
+
}
|
|
543
|
+
if (Array.isArray(value)) {
|
|
544
|
+
return `[${value.map(x => formatValue(x, params)).join(', ')}]`;
|
|
545
|
+
}
|
|
546
|
+
if (value != null && typeof value === 'object') {
|
|
547
|
+
let entries = Object.entries(value);
|
|
548
|
+
return `{ ${entries.map(([ key, val ]) => `${key}: ${formatValue(val, params)}`).join(', ')} }`;
|
|
549
|
+
}
|
|
550
|
+
if (typeof value === 'string') {
|
|
551
|
+
if (/^0x[a-fA-F0-9]{40}$/.test(value)) {
|
|
552
|
+
return value as TEth.Address;
|
|
553
|
+
}
|
|
554
|
+
if (/^0x[a-fA-F0-9]{18,}$/.test(value)) {
|
|
555
|
+
return shortHex(value as TEth.Hex, 18, 10);
|
|
556
|
+
}
|
|
557
|
+
return color('yellow', value, params);
|
|
558
|
+
}
|
|
559
|
+
return String(value);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function shortAddress(address: TEth.Address, params: {
|
|
563
|
+
shortAddress?: boolean
|
|
564
|
+
}): string {
|
|
565
|
+
if (params.shortAddress === false) {
|
|
566
|
+
return address;
|
|
567
|
+
}
|
|
568
|
+
return `${address.slice(0, 8)}...${address.slice(-4)}`;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function shortHex(hex: TEth.Hex, start: number, end: number): string {
|
|
572
|
+
if (hex.length <= start + end) {
|
|
573
|
+
return hex;
|
|
574
|
+
}
|
|
575
|
+
return `${hex.slice(0, start)}...${hex.slice(-end)}`;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function toAddress(word: string): TEth.Address {
|
|
579
|
+
let hex = word.replace(/^0x/, '').slice(-40).toLowerCase();
|
|
580
|
+
return `0x${hex}` as TEth.Address;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function toBigInt(word: string): bigint {
|
|
584
|
+
return $bigint.from($hex.ensure(word));
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function toNumber(word: string): number {
|
|
588
|
+
let value = toBigInt(word);
|
|
589
|
+
return value > BigInt(Number.MAX_SAFE_INTEGER)
|
|
590
|
+
? Number.MAX_SAFE_INTEGER
|
|
591
|
+
: Number(value);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function color (name: keyof (typeof ColorData['ColorAscii'])['value'], str: string, params: { color?: boolean }): string {
|
|
595
|
+
if (!params.color) {
|
|
596
|
+
return str;
|
|
597
|
+
}
|
|
598
|
+
return $color(`${name}<${str}>`);
|
|
599
|
+
}
|
|
600
|
+
}
|