Repository: ponder-sh/ponder (ts)

Issue:
Ponder Implicitly Removes Null Bytes from Event Arguments
### Version

0.8.28

### Current behavior

In indexing ENS, some labels with null bytes are emitted, but these labels cannot be stored in postgres (which is likely the reason ponder trims them to begin with). Unfortunately this removes information from the application-layer who might handle these cases differently. In ENS's case, it's important that `'vitalik\x00'.eth` and `vitalik.eth` are differentiable. 

In this specific case, we can use `const hadNullBytes = labelhash(name) !== label` as a proxy for null-byte detection, since the labelhash will be different for the true argument value that includes the null byte.

More information is available here, where I debugged this behavior 👇

https://github.com/namehash/ensnode/issues/36

### Expected behavior

I believe the current behavior of ponder is extremely reasonable, but I worry that the developer integrating ponder loses this information and may accidentally index the wrong data. In our case we were treating these two names as identical, which messed up the resulting data (and would have perhaps enabled someone resolving `vitalik.eth` to receive the imposter's information. We assumed we were getting the raw argument data from ponder, intending to handle the null-byte ourselves, but were actually receiving strings with null bytes already removed.

It seems that the default behavior of postgres is to throw an error if a null byte is inserted, though I'm not sure why this didn't cause subgraph indexers from failing to index these names in the original postmortem.

https://ens.mirror.xyz/9GN77d-MqGvRypm72FcwgxlUnPSuKWhG3rWxddHhRwM

In any case, I think that the default behavior, where postgres throws an unrecoverable error, forcing the user to handle this case, is likely the correct path. Ponder can/should provide null-byte helper functions to make this easier on users, for example a `containsNullCharacters` or `removeNullCharacters` helper fn.

### Steps to reproduce

You don't really need to reproduce this, but here's an offending tx: `0x535bf909ec258054cb03ab03498b4de0b365e1037a43fcdfbdcb28202382a9cd` which you can access via ponder with the following config:

```ts
EthRegistrarControllerOld: {
      network: "mainnet",
      abi: EthRegistrarControllerOld,
      address: "0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5",
      startBlock: 16369364,
      endBlock: 16369364 + 1,
    },
```

and you can log the arguments provided to your event handler to see that the null byte is removed.

the relevant line in ponder is https://github.com/ponder-sh/ponder/blob/178bcbfd36b5eb418ee118c0e6b02a46c9c7ed6d/packages/core/src/sync/events.ts#L505C25-L505C45

### Link to repository

https://github.com/namehash/ensnode

### Anything else?

_No response_

Pull request description:
Fix null bytes behavior
Closes #1456 

Patch:
diff --git a/.changeset/gold-shirts-tickle.md b/.changeset/gold-shirts-tickle.md
new file mode 100644
index 00000000..7abf606d
--- /dev/null
+++ b/.changeset/gold-shirts-tickle.md
@@ -0,0 +1,5 @@
+---
+"ponder": patch
+---
+
+Fixed a bug with factories incorrectly filtering events.
diff --git a/.changeset/new-seals-buy.md b/.changeset/new-seals-buy.md
new file mode 100644
index 00000000..aa848113
--- /dev/null
+++ b/.changeset/new-seals-buy.md
@@ -0,0 +1,5 @@
+---
+"ponder": minor
+---
+
+Released `0.10`. Visit the [migration guide](https://ponder.sh/docs/migration-guide#010) for details.
diff --git a/.changeset/stupid-coats-promise.md b/.changeset/stupid-coats-promise.md
new file mode 100644
index 00000000..a89680fc
--- /dev/null
+++ b/.changeset/stupid-coats-promise.md
@@ -0,0 +1,5 @@
+---
+"ponder": patch
+---
+
+Fixed an issue where `eth_call` responses containing `0x` were being cached. Now, only non-empty responses are cached.
diff --git a/.changeset/witty-bears-appear.md b/.changeset/witty-bears-appear.md
new file mode 100644
index 00000000..6b11505a
--- /dev/null
+++ b/.changeset/witty-bears-appear.md
@@ -0,0 +1,5 @@
+---
+"ponder": patch
+---
+
+Changed event decoding behavior to allow users to handle null bytes.
diff --git a/.changeset/yellow-feet-drop.md b/.changeset/yellow-feet-drop.md
new file mode 100644
index 00000000..f7f59f1d
--- /dev/null
+++ b/.changeset/yellow-feet-drop.md
@@ -0,0 +1,5 @@
+---
+"ponder": patch
+---
+
+Fixed an issue where setting a network `maxRequestsPerSecond` value greater than ~256 could freeze the indexer and cause contention with other chains.
diff --git a/examples/project-uniswap-v3-flash/ponder.config.ts b/examples/project-uniswap-v3-flash/ponder.config.ts
index 761a98e2..8a958916 100644
--- a/examples/project-uniswap-v3-flash/ponder.config.ts
+++ b/examples/project-uniswap-v3-flash/ponder.config.ts
@@ -1,11 +1,15 @@
 import { createConfig, factory } from "ponder";
 import { http, getAbiItem } from "viem";
+
 import { UniswapV3FactoryAbi } from "./abis/UniswapV3FactoryAbi";
 import { UniswapV3PoolAbi } from "./abis/UniswapV3PoolAbi";
 
 export default createConfig({
   networks: {
-    mainnet: { chainId: 1, transport: http(process.env.PONDER_RPC_URL_1) },
+    mainnet: {
+      chainId: 1,
+      transport: http(process.env.PONDER_RPC_URL_1),
+    },
   },
   contracts: {
     UniswapV3Pool: {
@@ -17,6 +21,9 @@ export default createConfig({
         parameter: "pool",
       }),
       startBlock: 12369621,
+      filter: {
+        event: "Flash",
+      },
     },
   },
 });
diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md
index b10b7c17..dd5afc1c 100644
--- a/packages/client/CHANGELOG.md
+++ b/packages/client/CHANGELOG.md
@@ -1,7 +1,5 @@
 # @ponder/client
 
-## 1.0.0
-
 ## 0.9.28
 
 ## 0.9.27
diff --git a/packages/client/package.json b/packages/client/package.json
index 581c0616..55b9553e 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@ponder/client",
-  "version": "1.0.0",
+  "version": "0.9.28",
   "description": "",
   "license": "MIT",
   "type": "module",
diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md
index 5dde8dd5..8ff14d85 100644
--- a/packages/core/CHANGELOG.md
+++ b/packages/core/CHANGELOG.md
@@ -1,19 +1,5 @@
 # ponder
 
-## 1.0.0
-
-### Minor Changes
-
-- [#1603](https://github.com/ponder-sh/ponder/pull/1603) [`c4452f6798e44b62704ad38d8217cfcc9cabb496`](https://github.com/ponder-sh/ponder/commit/c4452f6798e44b62704ad38d8217cfcc9cabb496) Thanks [@kyscott18](https://github.com/kyscott18)! - Released `0.10`. Visit the [migration guide](https://ponder.sh/docs/migration-guide#010) for details.
-
-### Patch Changes
-
-- [#1603](https://github.com/ponder-sh/ponder/pull/1603) [`c4452f6798e44b62704ad38d8217cfcc9cabb496`](https://github.com/ponder-sh/ponder/commit/c4452f6798e44b62704ad38d8217cfcc9cabb496) Thanks [@kyscott18](https://github.com/kyscott18)! - Fixed a bug with factories incorrectly filtering events.
-
-- [#1603](https://github.com/ponder-sh/ponder/pull/1603) [`c4452f6798e44b62704ad38d8217cfcc9cabb496`](https://github.com/ponder-sh/ponder/commit/c4452f6798e44b62704ad38d8217cfcc9cabb496) Thanks [@kyscott18](https://github.com/kyscott18)! - Fixed an issue where `eth_call` responses containing `0x` were being cached. Now, only non-empty responses are cached.
-
-- [#1603](https://github.com/ponder-sh/ponder/pull/1603) [`c4452f6798e44b62704ad38d8217cfcc9cabb496`](https://github.com/ponder-sh/ponder/commit/c4452f6798e44b62704ad38d8217cfcc9cabb496) Thanks [@kyscott18](https://github.com/kyscott18)! - Fixed an issue where setting a network `maxRequestsPerSecond` value greater than ~256 could freeze the indexer and cause contention with other chains.
-
 ## 0.9.28
 
 ### Patch Changes
diff --git a/packages/core/package.json b/packages/core/package.json
index 5bd28350..3d02b4ad 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
 {
   "name": "ponder",
-  "version": "1.0.0",
+  "version": "0.9.28",
   "description": "An open-source framework for crypto application backends",
   "license": "MIT",
   "type": "module",
diff --git a/packages/core/src/build/index.ts b/packages/core/src/build/index.ts
index d147037f..6c979a35 100644
--- a/packages/core/src/build/index.ts
+++ b/packages/core/src/build/index.ts
@@ -33,6 +33,7 @@ import { safeBuildSchema } from "./schema.js";
 import { parseViteNodeError } from "./stacktrace.js";
 
 declare global {
+  var PONDER_COMMON: Common;
   var PONDER_NAMESPACE_BUILD: NamespaceBuild;
   var PONDER_INDEXING_BUILD: IndexingBuild;
   var PONDER_DATABASE: Database;
@@ -83,6 +84,8 @@ export const createBuild = async ({
 }): Promise<Build> => {
   const escapeRegex = /[.*+?^${}()|[\]\\]/g;
 
+  globalThis.PONDER_COMMON = common;
+
   const escapedIndexingDir = common.options.indexingDir
     // If on Windows, use a POSIX path for this regex.
     .replace(/\\/g, "/")
diff --git a/packages/core/src/drizzle/onchain.ts b/packages/core/src/drizzle/onchain.ts
index f0c69bbd..c5b9b023 100644
--- a/packages/core/src/drizzle/onchain.ts
+++ b/packages/core/src/drizzle/onchain.ts
@@ -15,6 +15,7 @@ import {
   PgTable,
   type PgTableExtraConfig,
   type PgTableWithColumns,
+  type PgTextConfig,
   type TableConfig,
   primaryKey as drizzlePrimaryKey,
 } from "drizzle-orm/pg-core";
@@ -25,6 +26,17 @@ import {
 import { PgBigintBuilder, type PgBigintBuilderInitial } from "./bigint.js";
 import { PgBytesBuilder, type PgBytesBuilderInitial } from "./bytes.js";
 import { PgHexBuilder, type PgHexBuilderInitial } from "./hex.js";
+import { PgTextBuilder, type PgTextBuilderInitial } from "./text.js";
+
+/** @internal */
+function getColumnNameAndConfig<
+  TConfig extends Record<string, any> | undefined,
+>(a: string | TConfig | undefined, b: TConfig | undefined) {
+  return {
+    name: typeof a === "string" && a.length > 0 ? a : ("" as string),
+    config: typeof a === "object" ? a : (b as TConfig),
+  };
+}
 
 // @ts-ignore
 export function hex(): PgHexBuilderInitial<"">;
@@ -53,6 +65,23 @@ export function bytes(columnName?: string) {
   return new PgBytesBuilder(columnName ?? "");
 }
 
+export function text(): PgTextBuilderInitial<"", [string, ...string[]]>;
+export function text<U extends string, T extends Readonly<[U, ...U[]]>>(
+  config?: PgTextConfig<T | Writable<T>>,
+): PgTextBuilderInitial<"", Writable<T>>;
+export function text<
+  TName extends string,
+  U extends string,
+  T extends Readonly<[U, ...U[]]>,
+>(
+  name: TName,
+  config?: PgTextConfig<T | Writable<T>>,
+): PgTextBuilderInitial<TName, Writable<T>>;
+export function text(a?: string | PgTextConfig, b: PgTextConfig = {}): any {
+  const { name, config } = getColumnNameAndConfig<PgTextConfig>(a, b);
+  return new PgTextBuilder(name, config as any);
+}
+
 export const onchain = Symbol.for("ponder:onchain");
 
 export type PrimaryKeyBuilder<columnNames extends string = string> =
@@ -240,11 +269,11 @@ function pgTableWithSchema<
     dialect: "pg";
   }>(name, schema, baseName);
 
-  const { bigint: int8, ...restColumns } = getPgColumnBuilders();
+  const { bigint: int8, text: _text, ...restColumns } = getPgColumnBuilders();
 
   const parsedColumns: columns =
     typeof columns === "function"
-      ? columns({ ...restColumns, int8, hex, bigint, bytes })
+      ? columns({ ...restColumns, int8, hex, bigint, bytes, text })
       : columns;
 
   const builtColumns = Object.fromEntries(
diff --git a/packages/core/src/drizzle/text.ts b/packages/core/src/drizzle/text.ts
new file mode 100644
index 00000000..ee419434
--- /dev/null
+++ b/packages/core/src/drizzle/text.ts
@@ -0,0 +1,72 @@
+import {
+  type ColumnBaseConfig,
+  type ColumnBuilderBaseConfig,
+  type ColumnBuilderRuntimeConfig,
+  type MakeColumnConfig,
+  entityKind,
+  getTableName,
+} from "drizzle-orm";
+import {
+  type AnyPgTable,
+  PgColumn,
+  PgColumnBuilder,
+  type PgTextConfig,
+} from "drizzle-orm/pg-core";
+
+export type PgTextBuilderInitial<
+  TName extends string,
+  TEnum extends [string, ...string[]],
+> = PgTextBuilder<{
+  name: TName;
+  dataType: "string";
+  columnType: "PgText";
+  data: TEnum[number];
+  enumValues: TEnum;
+  driverParam: string;
+}>;
+
+export class PgTextBuilder<
+  T extends ColumnBuilderBaseConfig<"string", "PgText">,
+> extends PgColumnBuilder<T, { enumValues: T["enumValues"] }> {
+  static override readonly [entityKind]: string = "PgTextBuilder";
+
+  constructor(name: T["name"], config: PgTextConfig<T["enumValues"]>) {
+    super(name, "string", "PgText");
+    this.config.enumValues = config.enum;
+  }
+
+  /** @internal */
+  // @ts-ignore
+  override build<TTableName extends string>(
+    table: AnyPgTable<{ name: TTableName }>,
+  ): PgText<MakeColumnConfig<T, TTableName>> {
+    return new PgText<MakeColumnConfig<T, TTableName>>(
+      table,
+      this.config as ColumnBuilderRuntimeConfig<any, any>,
+    );
+  }
+}
+
+export class PgText<
+  T extends ColumnBaseConfig<"string", "PgText">,
+> extends PgColumn<T, { enumValues: T["enumValues"] }> {
+  static override readonly [entityKind]: string = "PgText";
+
+  override readonly enumValues = this.config.enumValues;
+
+  getSQLType(): string {
+    return "text";
+  }
+
+  override mapToDriverValue(value: string) {
+    if (value.match(/\0/g)) {
+      globalThis.PONDER_COMMON?.logger.warn({
+        service: "indexing",
+        msg: `Detected and removed null byte characters from the string '${value}' inserted into the ${getTableName(this.table)}.${this.name} column. Postgres "text" columns do not support null byte characters. Please consider handling this case in your indexing logic.`,
+      });
+
+      return value.replace(/\0/g, "");
+    }
+    return value;
+  }
+}
diff --git a/packages/core/src/sync-store/encoding.ts b/packages/core/src/sync-store/encoding.ts
index 3827bad2..ea876302 100644
--- a/packages/core/src/sync-store/encoding.ts
+++ b/packages/core/src/sync-store/encoding.ts
@@ -372,10 +372,8 @@ export const encodeTrace = ({
   type: trace.trace.type,
   gas: hexToBigInt(trace.trace.gas),
   gas_used: hexToBigInt(trace.trace.gasUsed),
-  error: trace.trace.error ? trace.trace.error.replace(/\0/g, "") : null,
-  revert_reason: trace.trace.revertReason
-    ? trace.trace.revertReason.replace(/\0/g, "")
-    : null,
+  error: trace.trace.error ?? null,
+  revert_reason: trace.trace.revertReason ?? null,
   subcalls: trace.trace.subcalls,
 });
 
@@ -391,8 +389,10 @@ export const decodeTrace = ({ trace }: { trace: DbTrace }): InternalTrace => ({
   type: trace.type as Trace["type"],
   gas: BigInt(trace.gas),
   gasUsed: BigInt(trace.gas_used),
-  error: trace.error ?? undefined,
-  revertReason: trace.revert_reason ?? undefined,
+  error: trace.error ? trace.error.replace(/\0/g, "") : undefined,
+  revertReason: trace.revert_reason
+    ? trace.revert_reason.replace(/\0/g, "")
+    : undefined,
   subcalls: trace.subcalls,
 });
 
diff --git a/packages/core/src/sync/events.ts b/packages/core/src/sync/events.ts
index 597f17b6..597a5ba4 100644
--- a/packages/core/src/sync/events.ts
+++ b/packages/core/src/sync/events.ts
@@ -369,7 +369,7 @@ export const decodeEvents = (
 
                 event: {
                   id: event.checkpoint,
-                  args: removeNullCharacters(args),
+                  args,
                   log: event.log!,
                   block: event.block,
                   transaction: event.transaction!,
@@ -426,8 +426,8 @@ export const decodeEvents = (
 
                 event: {
                   id: event.checkpoint,
-                  args: removeNullCharacters(args),
-                  result: removeNullCharacters(result),
+                  args,
+                  result,
                   trace: event.trace!,
                   block: event.block,
                   transaction: event.transaction!,
@@ -592,38 +592,20 @@ export function decodeEventLog({
   return Object.values(args).length > 0 ? args : undefined;
 }
 
+const ARRAY_REGEX = /^(.*)\[(\d+)?\]$/;
+
 function decodeTopic({ param, value }: { param: AbiParameter; value: Hex }) {
   if (
     param.type === "string" ||
     param.type === "bytes" ||
     param.type === "tuple" ||
-    param.type.match(/^(.*)\[(\d+)?\]$/)
+    param.type.match(ARRAY_REGEX)
   )
     return value;
   const decodedArg = decodeAbiParameters([param], value) || [];
   return decodedArg[0];
 }
 
-export function removeNullCharacters(obj: unknown): unknown {
-  if (typeof obj === "string") {
-    return obj.replace(/\0/g, "");
-  }
-  if (Array.isArray(obj)) {
-    // Recursively handle array elements
-    return obj.map(removeNullCharacters);
-  }
-  if (obj && typeof obj === "object") {
-    // Recursively handle object properties
-    const newObj: { [key: string]: unknown } = {};
-    for (const [key, val] of Object.entries(obj)) {
-      newObj[key] = removeNullCharacters(val);
-    }
-    return newObj;
-  }
-  // For other types (number, boolean, null, undefined, etc.), return as-is
-  return obj;
-}
-
 export const syncBlockToInternal = ({
   block,
 }: { block: SyncBlock }): InternalBlock => ({
diff --git a/packages/create-ponder/CHANGELOG.md b/packages/create-ponder/CHANGELOG.md
index 99366fb6..77a5ff0f 100644
--- a/packages/create-ponder/CHANGELOG.md
+++ b/packages/create-ponder/CHANGELOG.md
@@ -1,7 +1,5 @@
 # create-ponder
 
-## 1.0.0
-
 ## 0.9.28
 
 ## 0.9.27
diff --git a/packages/create-ponder/package.json b/packages/create-ponder/package.json
index c7c24058..f7ec2b35 100644
--- a/packages/create-ponder/package.json
+++ b/packages/create-ponder/package.json
@@ -1,6 +1,6 @@
 {
   "name": "create-ponder",
-  "version": "1.0.0",
+  "version": "0.9.28",
   "type": "module",
   "description": "A CLI tool to create Ponder apps",
   "license": "MIT",
diff --git a/packages/eslint-config-ponder/CHANGELOG.md b/packages/eslint-config-ponder/CHANGELOG.md
index 266630de..ba01c7ea 100644
--- a/packages/eslint-config-ponder/CHANGELOG.md
+++ b/packages/eslint-config-ponder/CHANGELOG.md
@@ -1,7 +1,5 @@
 # eslint-config-ponder
 
-## 1.0.0
-
 ## 0.9.28
 
 ## 0.9.27
diff --git a/packages/eslint-config-ponder/package.json b/packages/eslint-config-ponder/package.json
index 0fdfb1e3..d541fae9 100644
--- a/packages/eslint-config-ponder/package.json
+++ b/packages/eslint-config-ponder/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint-config-ponder",
-  "version": "1.0.0",
+  "version": "0.9.28",
   "description": "ESLint config for Ponder apps",
   "license": "MIT",
   "main": "./index.js",
diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md
index f9e205dd..8d5b96f2 100644
--- a/packages/react/CHANGELOG.md
+++ b/packages/react/CHANGELOG.md
@@ -1,12 +1,5 @@
 # @ponder/react
 
-## 1.0.0
-
-### Patch Changes
-
-- Updated dependencies []:
-  - @ponder/client@1.0.0
-
 ## 0.9.28
 
 ### Patch Changes
diff --git a/packages/react/package.json b/packages/react/package.json
index 4facd78b..2ab1a4ec 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@ponder/react",
-  "version": "1.0.0",
+  "version": "0.9.28",
   "description": "React hooks for Ponder",
   "license": "MIT",
   "repository": {
@@ -37,7 +37,7 @@
     "superjson": "^2.2.2"
   },
   "peerDependencies": {
-    "@ponder/client": ">=1.0.0",
+    "@ponder/client": ">=0.9.28",
     "@tanstack/react-query": ">=5.0.0",
     "react": ">=18",
     "typescript": ">=5.0.4"


Test patch:
diff --git a/packages/core/src/indexing-store/historical.test.ts b/packages/core/src/indexing-store/historical.test.ts
index e94c0d02..4e5c7fbc 100644
--- a/packages/core/src/indexing-store/historical.test.ts
+++ b/packages/core/src/indexing-store/historical.test.ts
@@ -994,3 +994,44 @@ test("bytes", async (context) => {
     });
   });
 });
+
+test("text with null bytes", async (context) => {
+  const { database } = await setupDatabaseServices(context);
+
+  const schema = {
+    account: onchainTable("account", (t) => ({
+      address: t.hex().primaryKey(),
+      name: t.text().notNull(),
+    })),
+  };
+
+  const indexingCache = createIndexingCache({
+    common: context.common,
+    schemaBuild: { schema },
+    checkpoint: ZERO_CHECKPOINT_STRING,
+  });
+
+  await database.transaction(async (client, tx) => {
+    const indexingStore = createHistoricalIndexingStore({
+      common: context.common,
+      schemaBuild: { schema },
+      indexingCache,
+      db: tx,
+      client,
+    });
+
+    await indexingStore.insert(schema.account).values({
+      address: zeroAddress,
+      name: "tencentclub\x00\x00\x00\x00\x00\x00",
+    });
+
+    const result = await indexingStore.find(schema.account, {
+      address: zeroAddress,
+    });
+
+    expect(result).toStrictEqual({
+      address: zeroAddress,
+      name: "tencentclub",
+    });
+  });
+});
diff --git a/packages/core/src/indexing-store/realtime.test.ts b/packages/core/src/indexing-store/realtime.test.ts
index d5000ca8..43b4e3c3 100644
--- a/packages/core/src/indexing-store/realtime.test.ts
+++ b/packages/core/src/indexing-store/realtime.test.ts
@@ -758,3 +758,36 @@ test("bytes", async (context) => {
     calldata: toBytes(zeroAddress),
   });
 });
+
+test("text with null bytes", async (context) => {
+  const schema = {
+    account: onchainTable("account", (t) => ({
+      address: t.hex().primaryKey(),
+      name: t.text().notNull(),
+    })),
+  };
+
+  const { database } = await setupDatabaseServices(context, {
+    schemaBuild: { schema },
+  });
+
+  const indexingStore = createRealtimeIndexingStore({
+    common: context.common,
+    database,
+    schemaBuild: { schema },
+  });
+
+  await indexingStore.insert(schema.account).values({
+    address: zeroAddress,
+    name: "tencentclub\x00\x00\x00\x00\x00\x00",
+  });
+
+  const result = await indexingStore.find(schema.account, {
+    address: zeroAddress,
+  });
+
+  expect(result).toStrictEqual({
+    address: zeroAddress,
+    name: "tencentclub",
+  });
+});
diff --git a/packages/core/src/sync/events.test.ts b/packages/core/src/sync/events.test.ts
index 8978435b..82ed989f 100644
--- a/packages/core/src/sync/events.test.ts
+++ b/packages/core/src/sync/events.test.ts
@@ -25,11 +25,7 @@ import {
 } from "viem";
 import { encodeFunctionData, encodeFunctionResult } from "viem/utils";
 import { beforeEach, expect, test } from "vitest";
-import {
-  decodeEventLog,
-  decodeEvents,
-  removeNullCharacters,
-} from "./events.js";
+import { decodeEvents } from "./events.js";
 
 beforeEach(setupCommon);
 
@@ -313,66 +309,3 @@ test("decodeEvents() trace error", async (context) => {
 
   expect(events).toHaveLength(0);
 });
-
-test("removeNullCharacters removes null characters", () => {
-  // NameRegistered event from this transaction contains null characters:
-  // https://etherscan.io/tx/0x2e67be22d5e700e61e102b926f28ba451c53a6cd6438c53b43dbb783c2081a12#eventlog
-  const log = {
-    topics: [
-      "0xca6abbe9d7f11422cb6ca7629fbf6fe9efb1c621f71ce8f02b9f2a230097404f",
-      "0x56e1003dc29ff83445ba93c493f4a76570eb667494e78c6974a745593131ae2a",
-      "0x0000000000000000000000008504a09352555ff1acf9c8a8d9fb5fdcc4161cbc",
-    ],
-    data: "0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000697a5dd2a81dc000000000000000000000000000000000000000000000000000000006457430e000000000000000000000000000000000000000000000000000000000000001174656e63656e74636c7562000000000000000000000000000000000000000000",
-  } as const;
-
-  const abiItem = {
-    anonymous: false,
-    inputs: [
-      {
-        indexed: false,
-        internalType: "string",
-        name: "name",
-        type: "string",
-      },
-      {
-        indexed: true,
-        internalType: "bytes32",
-        name: "label",
-        type: "bytes32",
-      },
-      {
-        indexed: true,
-        internalType: "address",
-        name: "owner",
-        type: "address",
-      },
-      {
-        indexed: false,
-        internalType: "uint256",
-        name: "cost",
-        type: "uint256",
-      },
-      {
-        indexed: false,
-        internalType: "uint256",
-        name: "expires",
-        type: "uint256",
-      },
-    ],
-    name: "NameRegistered",
-    type: "event",
-  } as const;
-
-  const args = decodeEventLog({
-    abiItem,
-    topics: log.topics as unknown as [signature: Hex, ...args: Hex[]],
-    data: log.data,
-  });
-
-  expect(args.name).toBe("tencentclub\x00\x00\x00\x00\x00\x00");
-
-  const cleanedArgs = removeNullCharacters(args);
-
-  expect((cleanedArgs as any).name).toBe("tencentclub");
-});