fix: validate bridged ABI and malformed logs
This commit is contained in:
@@ -135,6 +135,17 @@ describe("loadDashboardSnapshot", () => {
|
||||
expect(snapshot.diagnostics).toEqual([expect.objectContaining({ blockNumber: 4n, logIndex: 2, message: expect.any(String) })]);
|
||||
});
|
||||
|
||||
it("diagnoses proxy logs with malformed topics or payload data", async () => {
|
||||
// Catches payload-shape validation skipping malformed proxy logs before the diagnostic boundary.
|
||||
const reader = new ReaderDouble();
|
||||
const valid = depositedLog(4n, 1, owner, 7n);
|
||||
reader.logs = [valid, { ...valid, logIndex: 2, topics: "not-topics" }, { ...valid, logIndex: 3, data: "not-hex" }, { ...valid, logIndex: 4, data: undefined }];
|
||||
const snapshot = await loadDashboardSnapshot(reader, manifest);
|
||||
|
||||
expect(snapshot.activity).toHaveLength(1);
|
||||
expect(snapshot.diagnostics.map((diagnostic) => diagnostic.logIndex)).toEqual([2, 3, 4]);
|
||||
});
|
||||
|
||||
it("preserves a rejected contract read instead of substituting zero", async () => {
|
||||
// Catches disconnected RPC reads being silently rendered as zero balances.
|
||||
const reader = new ReaderDouble(); reader.error = new Error("node read failed");
|
||||
|
||||
@@ -106,9 +106,10 @@ function decodeActivity(logs: readonly unknown[], proxy: Address): { activity: r
|
||||
const activity: Activity[] = [];
|
||||
const diagnostics: DecodeDiagnostic[] = [];
|
||||
for (const log of logs) {
|
||||
if (!isLog(log) || !sameAddress(log.address, proxy)) continue;
|
||||
if (!isLogIdentity(log) || !sameAddress(log.address, proxy)) continue;
|
||||
const identity = { blockNumber: log.blockNumber, logIndex: log.logIndex, transactionHash: log.transactionHash };
|
||||
try {
|
||||
if (!isLogPayload(log)) throw new Error("proxy event payload is malformed");
|
||||
const decoded = decodeEventLog({ abi: bankV1Abi, data: log.data, topics: log.topics as [Hex, ...Hex[]] });
|
||||
const next = decodedActivity(decoded.eventName, decoded.args, identity);
|
||||
if (next) activity.push(next);
|
||||
@@ -121,14 +122,18 @@ function decodeActivity(logs: readonly unknown[], proxy: Address): { activity: r
|
||||
return { activity, diagnostics };
|
||||
}
|
||||
|
||||
function isLog(value: unknown): value is Readonly<{ address: Address; blockNumber: bigint; logIndex: number; transactionHash: Hex; data: Hex; topics: readonly Hex[] }> {
|
||||
function isLogIdentity(value: unknown): value is Readonly<{ address: Address; blockNumber: bigint; logIndex: number; transactionHash: Hex; data?: unknown; topics?: unknown }> {
|
||||
return typeof value === "object" && value !== null
|
||||
&& typeof (value as { address?: unknown }).address === "string" && isAddress((value as { address: string }).address)
|
||||
&& typeof (value as { blockNumber?: unknown }).blockNumber === "bigint"
|
||||
&& typeof (value as { logIndex?: unknown }).logIndex === "number"
|
||||
&& typeof (value as { transactionHash?: unknown }).transactionHash === "string"
|
||||
&& typeof (value as { data?: unknown }).data === "string"
|
||||
&& Array.isArray((value as { topics?: unknown }).topics);
|
||||
&& typeof (value as { transactionHash?: unknown }).transactionHash === "string";
|
||||
}
|
||||
|
||||
function isLogPayload(value: Readonly<{ data?: unknown; topics?: unknown }>): value is Readonly<{ data: Hex; topics: readonly Hex[] }> {
|
||||
return typeof value.data === "string" && /^0x[0-9a-fA-F]*$/.test(value.data)
|
||||
&& Array.isArray(value.topics) && value.topics.length > 0
|
||||
&& value.topics.every((topic) => typeof topic === "string" && /^0x[0-9a-fA-F]*$/.test(topic));
|
||||
}
|
||||
|
||||
function decodedActivity(eventName: string, args: unknown, identity: Readonly<{ blockNumber: bigint; logIndex: number; transactionHash: Hex }>): Activity | undefined {
|
||||
|
||||
Reference in New Issue
Block a user