Skip to content

Commit

Permalink
feat(viem): trace eth_sendTransaction
Browse files Browse the repository at this point in the history
  • Loading branch information
Rubilmax committed Dec 11, 2024
1 parent 8928ee2 commit 64bf903
Show file tree
Hide file tree
Showing 5 changed files with 491 additions and 292 deletions.
26 changes: 13 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,21 @@
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@commitlint/cli": "^19.5.0",
"@commitlint/config-conventional": "^19.5.0",
"@commitlint/cli": "^19.6.0",
"@commitlint/config-conventional": "^19.6.0",
"@types/lodash.kebabcase": "^4.1.9",
"@types/node": "^22.7.7",
"@vitest/coverage-v8": "^2.1.3",
"@types/node": "^22.10.2",
"@vitest/coverage-v8": "^2.1.8",
"conventional-changelog-conventionalcommits": "^8.0.0",
"dotenv-cli": "^7.4.2",
"husky": "^9.1.6",
"lint-staged": "^15.2.10",
"dotenv-cli": "^7.4.4",
"husky": "^9.1.7",
"lint-staged": "^15.2.11",
"lodash.kebabcase": "^4.1.1",
"semantic-release": "^24.1.3",
"typescript": "^5.6.3",
"viem": "^2.21.32",
"viem-deal": "^2.0.2",
"vitest": "^2.1.3"
"semantic-release": "^24.2.0",
"typescript": "^5.7.2",
"viem": "^2.21.54",
"viem-deal": "^2.0.4",
"vitest": "^2.1.8"
},
"lint-staged": {
"*.ts": "yarn biome check"
Expand Down Expand Up @@ -113,4 +113,4 @@
"@semantic-release/github"
]
}
}
}
153 changes: 101 additions & 52 deletions src/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { RawContractError, type Transport } from "viem";
import {
BaseError,
type Hash,
type RpcTransactionReceipt,
type RpcTransactionRequest,
type Transport,
WaitForTransactionReceiptTimeoutError,
} from "viem";
import type { TraceCallRpcSchema } from "./actions/traceCall";
import { type TraceFormatConfig, formatFullTrace } from "./format";

Expand All @@ -19,16 +26,30 @@ export type TracerConfig = TraceFormatConfig & {

export type TracedTransport<transport extends Transport = Transport> = transport extends Transport<
infer type,
infer rpcAttributes
infer rpcAttributes,
infer eip1193RequestFn
>
? Transport<
type,
rpcAttributes & {
tracer: TracerConfig;
}
},
eip1193RequestFn
>
: never;

export class ExecutionRevertedTraceError extends BaseError {
static code = 3;
static nodeMessage = /execution reverted/;

constructor(trace: string, reason?: string) {
super(`Execution reverted ${reason ? `with reason: ${reason}` : "for an unknown reason"}.`, {
name: "ExecutionRevertedError",
details: `\n${trace}`,
});
}
}

/**
* @description Overloads a transport intended to be used with a test client, to trace and debug transactions.
*/
Expand All @@ -48,62 +69,90 @@ export function traced<transport extends Transport>(
return {
...instance,
async request(args, options) {
switch (args.method) {
case "eth_estimateGas":
case "eth_sendTransaction": {
const { params } = args;
const { tracer } = instance.value!;

const traceCall = async () => {
const trace = await instance.request<TraceCallRpcSchema>(
const { method, params } = args;
if (method !== "eth_estimateGas" && method !== "eth_sendTransaction" && method !== "wallet_sendTransaction")
return instance.request(args, options);

const { tracer } = instance.value!;

// @ts-expect-error: params[0] is the rpc transaction request
const tx = params[0] as RpcTransactionRequest;

const traceCall = async () => {
const trace = await instance.request<TraceCallRpcSchema>(
{
method: "debug_traceCall",
params: [
tx,
// @ts-expect-error: params[1] is either undefined or the block identifier
params[1] || "latest",
{
method: "debug_traceCall",
params: [
// @ts-ignore: params[0] is the rpc transaction request
params[0],
// @ts-ignore: params[1] is either undefined or the block identifier
params[1] || "latest",
{
// @ts-ignore: params[2] may contain state and block overrides
...params[2],
tracer: "callTracer",
tracerConfig: {
onlyTopCall: false,
withLog: true,
},
},
],
// @ts-expect-error: params[2] may contain state and block overrides
...params[2],
tracer: "callTracer",
tracerConfig: {
onlyTopCall: false,
withLog: true,
},
},
{ retryCount: 0 },
);

return await formatFullTrace(trace, tracer);
};

if (tracer.next || (tracer.next == null && tracer.all)) {
try {
console.log(await traceCall());
} catch (error) {
console.warn(`Failed to trace transaction: ${error}`);
}
],
},
{ retryCount: 0 },
);

return new ExecutionRevertedTraceError(await formatFullTrace(trace, tracer), trace.revertReason);
};

if (tracer.next || (tracer.next == null && tracer.all)) {
try {
console.log((await traceCall()).details);
} catch (error) {
console.warn(`Failed to trace transaction: ${error}`);
}
}

const res = await instance
.request(args, options)
.catch(async (error) => {
if (tracer.next || (tracer.next == null && tracer.failed)) {
const trace = await traceCall();

trace.stack = error.stack;

throw trace;
}

return instance
.request(args, options)
.catch(async (error) => {
if (tracer.next || (tracer.next == null && tracer.failed)) {
throw new RawContractError({ message: `\n${await traceCall()}` });
}

throw error;
})
.finally(() => {
tracer.next = undefined;
throw error;
})
.finally(() => {
tracer.next = undefined;
});

if (method !== "eth_estimateGas") {
let receipt: RpcTransactionReceipt | null = null;

try {
for (let i = 0; i < 720; i++) {
receipt = await instance.request({
method: "eth_getTransactionReceipt",
params: [res],
});

if (receipt) break;

await new Promise((resolve) => setTimeout(resolve, 250));
}

if (receipt?.status === "0x0") throw await traceCall();
else throw new WaitForTransactionReceiptTimeoutError({ hash: res as Hash });
} catch (error) {
if (error instanceof ExecutionRevertedTraceError) throw error;

console.warn(`Failed to trace transaction: ${error}`);
}
default:
return instance.request(args, options);
}

return res;
},
};
};
Expand Down
14 changes: 12 additions & 2 deletions test/setup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { disable } from "colors";
import type { Client, HDAccount, HttpTransport, PublicActions, TestActions, TestRpcSchema, WalletActions } from "viem";
import type {
Client,
HDAccount,
HttpTransport,
PublicActions,
PublicRpcSchema,
TestActions,
TestRpcSchema,
WalletActions,
WalletRpcSchema,
} from "viem";
import { http, createTestClient, publicActions, walletActions } from "viem";
import { type DealActions, dealActions } from "viem-deal";
import { mainnet } from "viem/chains";
Expand Down Expand Up @@ -35,7 +45,7 @@ export const test = vitest.extend<{
TracedTransport<HttpTransport>,
typeof mainnet,
HDAccount,
TestRpcSchema<"anvil">,
TestRpcSchema<"anvil"> | PublicRpcSchema | WalletRpcSchema,
TestActions &
DealActions<HDAccount> &
TraceActions<typeof mainnet> &
Expand Down
Loading

0 comments on commit 64bf903

Please sign in to comment.