From 3cee3db54ada8ee1622c625e51f1614c5feccb02 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Fri, 12 Jul 2024 10:18:40 +0200 Subject: [PATCH] test(indexer): add more unit tests for log methods (#1271) * test(indexer): add more unit tests for log methods * fix comment --- indexer/src/types/log.test.ts | 365 ++++++++++++++++++++++++++++++++++ indexer/src/types/log.ts | 35 ++-- 2 files changed, 382 insertions(+), 18 deletions(-) create mode 100644 indexer/src/types/log.test.ts diff --git a/indexer/src/types/log.test.ts b/indexer/src/types/log.test.ts new file mode 100644 index 000000000..401ac4387 --- /dev/null +++ b/indexer/src/types/log.test.ts @@ -0,0 +1,365 @@ +import { assertEquals } from "https://deno.land/std@0.213.0/assert/mod.ts"; +import { fromJsonRpcLog, IGNORED_KEYS, JsonRpcLog, toEthLog } from "./log.ts"; +import { bigIntToHex, Event, JsonRpcTx } from "../deps.ts"; + +// Mock for hexToBytes +const mockHexToBytes = (hex: string): Uint8Array => { + // Remove the '0x' prefix if present + const cleanedHex = hex.startsWith("0x") ? hex.slice(2) : hex; + + // Match pairs of hex characters + const hexPairs = cleanedHex.match(/.{1,2}/g) || []; + + // Convert pairs to integers and create a Uint8Array + return new Uint8Array(hexPairs.map((x) => parseInt(x, 16))); +}; + +Deno.test("fromJsonRpcLog with valid input", () => { + const log: JsonRpcLog = { + removed: false, + logIndex: "1", + transactionIndex: "0", + transactionHash: "0x1234567890abcdef", + blockHash: "0xabcdef1234567890", + blockNumber: "10", + address: "0x1234567890abcdef1234567890abcdef12345678", + data: "0xabcdef", + topics: ["0x1234", "0x5678"], + }; + + const expected: [Uint8Array, Uint8Array[], Uint8Array] = [ + mockHexToBytes("0x1234567890abcdef1234567890abcdef12345678"), + [mockHexToBytes("0x1234"), mockHexToBytes("0x5678")], + mockHexToBytes("0xabcdef"), + ]; + + assertEquals(fromJsonRpcLog(log), expected); +}); + +Deno.test("fromJsonRpcLog with empty data", () => { + const log: JsonRpcLog = { + removed: false, + logIndex: null, + transactionIndex: null, + transactionHash: null, + blockHash: null, + blockNumber: null, + address: "0x1234", + data: "0x", + topics: [], + }; + + const expected: [Uint8Array, Uint8Array[], Uint8Array] = [ + mockHexToBytes("0x1234"), + [], + mockHexToBytes("0x"), + ]; + + assertEquals(fromJsonRpcLog(log), expected); +}); + +Deno.test("fromJsonRpcLog with no topics", () => { + const log: JsonRpcLog = { + removed: false, + logIndex: null, + transactionIndex: null, + transactionHash: null, + blockHash: null, + blockNumber: null, + address: "0x1234", + data: "0x1234", + topics: [] as string[], + }; + + const expected: [Uint8Array, Uint8Array[], Uint8Array] = [ + mockHexToBytes("0x1234"), + [], + mockHexToBytes("0x1234"), + ]; + + assertEquals(fromJsonRpcLog(log), expected); +}); + +Deno.test("toEthLog with valid input", () => { + const transaction: JsonRpcTx = { + blockHash: "0xabcdef1234567890", + blockNumber: "0xa", + from: "0x1234567890abcdef", + gas: "0x5208", + gasPrice: "0x3b9aca00", + type: "0x2", + hash: "0xhash", + input: "0xinput", + nonce: "0x0", + to: "0xabcdef", + transactionIndex: "0x1", + value: "0xde0b6b3a7640000", + v: "0x1b", + r: "0x1c", + s: "0x1d", + }; + + const event: Event = { + index: 1, + fromAddress: "0x123456", + keys: ["0x1234", "0x5678", "0x9abc", "0xdef0", "0x1111"], + data: ["0x01", "0x02", "0x03"], + }; + + const expected = { + removed: false, + logIndex: null, + transactionIndex: "0x1", + transactionHash: "0xhash", + blockHash: "0xabcdef1234567890", + blockNumber: "0xa", + address: "0x0000000000000000000000000000000000001234", + data: "0x010203", + topics: [ + "0x00000000000000000000000000009abc00000000000000000000000000005678", + "0x000000000000000000000000000011110000000000000000000000000000def0", + ], + }; + + assertEquals( + toEthLog({ + transaction, + event, + blockNumber: "0xa", + blockHash: "0xabcdef1234567890", + isPendingBlock: false, + }), + expected, + ); +}); + +Deno.test("toEthLog with invalid event keys length", () => { + const transaction: JsonRpcTx = { + blockHash: "0xabcdef1234567890", + blockNumber: "0xa", + from: "0x1234567890abcdef", + gas: "0x5208", + gasPrice: "0x3b9aca00", + type: "0x2", + hash: "0xhash", + input: "0xinput", + nonce: "0x0", + to: "0xabcdef", + transactionIndex: "0x1", + value: "0xde0b6b3a7640000", + v: "0x1b", + r: "0x1c", + s: "0x1d", + }; + + // The event must have an odd number of keys. + const event: Event = { + index: 1, + fromAddress: "0x123456", + keys: ["0x1234", "0x5678"], + data: ["0x01", "0x02", "0x03"], + }; + + assertEquals( + toEthLog({ + transaction, + event, + blockNumber: "0xa", + blockHash: "0xabcdef1234567890", + isPendingBlock: false, + }), + null, + ); +}); + +Deno.test("toEthLog with no event key", () => { + const transaction: JsonRpcTx = { + blockHash: "0xabcdef1234567890", + blockNumber: "0xa", + from: "0x1234567890abcdef", + gas: "0x5208", + gasPrice: "0x3b9aca00", + type: "0x2", + hash: "0xhash", + input: "0xinput", + nonce: "0x0", + to: "0xabcdef", + transactionIndex: "0x1", + value: "0xde0b6b3a7640000", + v: "0x1b", + r: "0x1c", + s: "0x1d", + }; + + // The event must have at least one key. + const event: Event = { + index: 1, + fromAddress: "0x123456", + keys: [], + data: ["0x01", "0x02", "0x03"], + }; + + assertEquals( + toEthLog({ + transaction, + event, + blockNumber: "0xa", + blockHash: "0xabcdef1234567890", + isPendingBlock: false, + }), + null, + ); +}); + +Deno.test("toEthLog with empty event data", () => { + const transaction: JsonRpcTx = { + blockHash: "0xabcdef1234567890", + blockNumber: "0xa", + from: "0x1234567890abcdef", + gas: "0x5208", + gasPrice: "0x3b9aca00", + type: "0x2", + hash: "0xhash", + input: "0xinput", + nonce: "0x0", + to: "0xabcdef", + transactionIndex: "0x1", + value: "0xde0b6b3a7640000", + v: "0x1b", + r: "0x1c", + s: "0x1d", + }; + + // No data in the event. + const event: Event = { + index: 1, + fromAddress: "0x123456", + keys: ["0x1234", "0x5678", "0x9abc", "0xdef0", "0x1111"], + data: [], + }; + + const expected = { + removed: false, + logIndex: null, + transactionIndex: "0x1", + transactionHash: "0xhash", + blockHash: "0xabcdef1234567890", + blockNumber: "0xa", + address: "0x0000000000000000000000000000000000001234", + data: "0x", + topics: [ + "0x00000000000000000000000000009abc00000000000000000000000000005678", + "0x000000000000000000000000000011110000000000000000000000000000def0", + ], + }; + + assertEquals( + toEthLog({ + transaction, + event, + blockNumber: "0xa", + blockHash: "0xabcdef1234567890", + isPendingBlock: false, + }), + expected, + ); +}); + +Deno.test("toEthLog with pending block", () => { + const transaction: JsonRpcTx = { + blockHash: null, + blockNumber: null, + from: "0x1234567890abcdef", + gas: "0x5208", + gasPrice: "0x3b9aca00", + type: "0x2", + hash: "0xhash", + input: "0xinput", + nonce: "0x0", + to: "0xabcdef", + transactionIndex: "0x1", + value: "0xde0b6b3a7640000", + v: "0x1b", + r: "0x1c", + s: "0x1d", + }; + + const event: Event = { + index: 1, + fromAddress: "0x123456", + keys: ["0x1234", "0x5678", "0x9abc", "0xdef0", "0x1111"], + data: ["0x01", "0x02", "0x03"], + }; + + const expected = { + removed: false, + logIndex: null, + transactionIndex: "0x1", + transactionHash: "0xhash", + // Null block hash + blockHash: + "0x0000000000000000000000000000000000000000000000000000000000000000", + blockNumber: "0xa", + address: "0x0000000000000000000000000000000000001234", + data: "0x010203", + topics: [ + "0x00000000000000000000000000009abc00000000000000000000000000005678", + "0x000000000000000000000000000011110000000000000000000000000000def0", + ], + }; + + assertEquals( + toEthLog({ + transaction, + event, + blockNumber: "0xa", + blockHash: "0xabcdef1234567890", + // Pending block + isPendingBlock: true, + }), + expected, + ); +}); + +Deno.test("toEthLog with ignored keys", () => { + const transaction: JsonRpcTx = { + blockHash: "0xabcdef1234567890", + blockNumber: "0xa", + from: "0x1234567890abcdef", + gas: "0x5208", + gasPrice: "0x3b9aca00", + type: "0x2", + hash: "0xhash", + input: "0xinput", + nonce: "0x0", + to: "0xabcdef", + transactionIndex: "0x1", + value: "0xde0b6b3a7640000", + v: "0x1b", + r: "0x1c", + s: "0x1d", + }; + + // Loop through all ignored keys. + for ( + const ignoredKey of IGNORED_KEYS + ) { + const event: Event = { + index: 1, + fromAddress: "0x123456", + keys: [bigIntToHex(ignoredKey) as `0x${string}`], + data: ["0x01", "0x02", "0x03"], + }; + + assertEquals( + toEthLog({ + transaction, + event, + blockNumber: "0xa", + blockHash: "0xabcdef1234567890", + isPendingBlock: false, + }), + null, + ); + } +}); diff --git a/indexer/src/types/log.ts b/indexer/src/types/log.ts index 92021d9b8..d86d78655 100644 --- a/indexer/src/types/log.ts +++ b/indexer/src/types/log.ts @@ -18,7 +18,7 @@ import { // Events containing these keys are not // ETH logs and should be ignored. -const IGNORED_KEYS = [ +export const IGNORED_KEYS = [ BigInt(hash.getSelectorFromName("transaction_executed")), BigInt(hash.getSelectorFromName("evm_contract_deployed")), BigInt(hash.getSelectorFromName("Transfer")), @@ -47,30 +47,28 @@ export function toEthLog({ blockHash: PrefixedHexString; isPendingBlock: boolean; }): JsonRpcLog | null { - const keys = event.keys ?? []; + const { keys, data } = event; + const { transactionIndex, hash } = transaction; // The event must have at least one key (since the first key is the address) // and an odd number of keys (since each topic is split into two keys). // - if (keys.length < 1 || keys.length % 2 !== 1) { - console.error(`Invalid event ${event}`); + // + // We also want to filter out ignored events which aren't ETH logs. + if ( + keys?.length < 1 || keys.length % 2 !== 1 || + IGNORED_KEYS.includes(BigInt(keys[0])) + ) { return null; } - // Filter out ignored events which aren't ETH logs. - if (IGNORED_KEYS.includes(BigInt(keys[0]))) { - return null; - } - - // The address is the first key of the event. - const address = padBigint(BigInt(keys[0]), 20); // data field is FieldElement[] where each FieldElement represents a byte of data. // We convert it to a hex string and add leading zeros to make it a valid hex byte string. // Example: [1, 2, 3] -> "010203" - const data = event.data ?? []; - const paddedData = data - .map((d) => BigInt(d).toString(16).padStart(2, "0")) - .join(""); + const paddedData = + data?.map((d) => BigInt(d).toString(16).padStart(2, "0")).join("") || ""; + + // Construct topics array const topics: string[] = []; for (let i = 1; i < keys.length; i += 2) { // EVM Topics are u256, therefore are split into two felt keys, of at most @@ -84,11 +82,12 @@ export function toEthLog({ return { removed: false, logIndex: null, - transactionIndex: bigIntToHex(BigInt(transaction.transactionIndex ?? 0)), - transactionHash: transaction.hash, + transactionIndex: bigIntToHex(BigInt(transactionIndex ?? 0)), + transactionHash: hash, blockHash: isPendingBlock ? NULL_BLOCK_HASH : blockHash, blockNumber, - address, + // The address is the first key of the event. + address: padBigint(BigInt(keys[0]), 20), data: `0x${paddedData}`, topics, };