From 8c336a2d2fa3cebb9a3613937ac272a8d61a2075 Mon Sep 17 00:00:00 2001 From: kyscott18 <43524469+kyscott18@users.noreply.github.com> Date: Tue, 13 Oct 2026 18:41:20 +0000 Subject: [PATCH] perf(encoding): encode execute and enqueue calldata with a cached ox AbiFunction viem's encodeFunctionData looks up the ABI item and hashes the selector on every call: 14.6us for a one-mutation execute, against 3.5us for ox with the function resolved once. The runtime encodes execute calldata for each accepted mutation. Counter benchmark, open loop at 8,000/s: 4,200-4,320 -> 4,770-4,930 mutations/s. --- packages/typewriter/src/encoding.ts | 32 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/typewriter/src/encoding.ts b/packages/typewriter/src/encoding.ts index eea4188..4278f20 100644 --- a/packages/typewriter/src/encoding.ts +++ b/packages/typewriter/src/encoding.ts @@ -1,5 +1,5 @@ -import { AbiParameters, type Hex } from "ox"; -import { type Abi, encodeFunctionData } from "viem"; +import { AbiFunction, AbiParameters, type Hex } from "ox"; +import type { Abi } from "viem"; import type { Authorization, ExecutableMutation } from "./types"; export const AUTHORIZATION_ABI_PARAMS = [ @@ -70,6 +70,11 @@ export const TYPEWRITER_ABI = [ export type TypewriterAbi = typeof TYPEWRITER_ABI; +// Resolved once: looking up the ABI item and hashing its selector on each +// call costs more than the encoding itself. +const EXECUTE_FUNCTION = AbiFunction.fromAbi(TYPEWRITER_ABI, "execute"); +const ENQUEUE_FUNCTION = AbiFunction.fromAbi(TYPEWRITER_ABI, "enqueue"); + // Contract calldata and EIP-712 mutationData encode params as one top-level // Solidity struct whose components are `mutation.params`. function calldataStructParams( @@ -125,23 +130,18 @@ export function encodeExecuteCalldata( }[], forceExecuteIndexes: readonly bigint[], ): Hex.Hex { - return encodeFunctionData({ - abi: TYPEWRITER_ABI, - functionName: "execute", - args: [batches, forceExecuteIndexes], - }); + return AbiFunction.encodeData(EXECUTE_FUNCTION, [ + batches, + forceExecuteIndexes, + ]); } export function encodeEnqueueCalldata(mutation: ExecutableMutation): Hex.Hex { - return encodeFunctionData({ - abi: TYPEWRITER_ABI, - functionName: "enqueue", - args: [ - mutation.config.id, - encodeMutationCalldata(mutation), - encodeAuthorizationCalldata(mutation.authorization), - ], - }); + return AbiFunction.encodeData(ENQUEUE_FUNCTION, [ + mutation.config.id, + encodeMutationCalldata(mutation), + encodeAuthorizationCalldata(mutation.authorization), + ]); } // Build a structured Batch value from executable mutations.