diff --git a/src/errors/ErrorHandler.ts b/src/errors/ErrorHandler.ts index 5b5ab3c..37c5535 100644 --- a/src/errors/ErrorHandler.ts +++ b/src/errors/ErrorHandler.ts @@ -66,6 +66,15 @@ export class ErrorHandler { }; } + public async saveTransactionTrace(hash: string): Promise { + return this.#runCast([ + "run", + "--rpc-url", + this.config.ethProviderRpcs[0], + hash, + ]); + } + /** * Safely tries to save trace of failed transaction to configured output * @param error @@ -75,52 +84,65 @@ export class ErrorHandler { e: BaseError, context?: CreditAccountData, ): Promise { - const logger = this.#caLogger(context); - if (!this.config.castBin || !this.config.outDir) { - return undefined; - } - try { - let cast: string[] = []; - if (e instanceof TransactionRevertedError) { + let cast: string[] = []; + if (e instanceof TransactionRevertedError) { + cast = [ + "run", + "--rpc-url", + this.config.ethProviderRpcs[0], + e.receipt.transactionHash, + ]; + } else { + const exErr = e.walk( + err => err instanceof ContractFunctionExecutionError, + ); + if ( + exErr instanceof ContractFunctionExecutionError && + exErr.contractAddress + ) { + const data = encodeFunctionData({ + abi: exErr.abi, + args: exErr.args, + functionName: exErr.functionName, + }); cast = [ - "run", + "call", + "--trace", "--rpc-url", this.config.ethProviderRpcs[0], - e.receipt.transactionHash, + exErr.contractAddress, + data, ]; - } else { - const exErr = e.walk( - err => err instanceof ContractFunctionExecutionError, - ); - if ( - exErr instanceof ContractFunctionExecutionError && - exErr.contractAddress - ) { - const data = encodeFunctionData({ - abi: exErr.abi, - args: exErr.args, - functionName: exErr.functionName, - }); - cast = [ - "call", - "--trace", - "--rpc-url", - this.config.ethProviderRpcs[0], - exErr.contractAddress, - data, - ]; - } - } - if (!cast.length) { - return undefined; } + } + if (!cast.length) { + return undefined; + } + return this.#runCast(cast, context); + } + /** + * Runs cast cli command and saves output to a unique file + * @param args + * @param context + * @returns + */ + async #runCast( + args: string[], + context?: CreditAccountData, + ): Promise { + if (!this.config.castBin || !this.config.outDir) { + return undefined; + } + + const logger = this.#caLogger(context); + try { const traceId = `${nanoid()}.trace`; const traceFile = path.resolve(this.config.outDir, traceId); const out = createWriteStream(traceFile, "utf-8"); await events.once(out, "open"); // use node-pty instead of node:child_process to have colored output - const pty = spawn(this.config.castBin, cast, { cols: 1024 }); + const pty = spawn(this.config.castBin, args, { cols: 1024 }); pty.onData(data => out.write(data)); await new Promise(resolve => { pty.onExit(() => resolve(undefined)); diff --git a/src/services/liquidate/BatchLiquidator.ts b/src/services/liquidate/BatchLiquidator.ts index 9568fd0..4b0a47b 100644 --- a/src/services/liquidate/BatchLiquidator.ts +++ b/src/services/liquidate/BatchLiquidator.ts @@ -83,14 +83,21 @@ export default class BatchLiquidator this.logger.debug( `processing batch of ${batch.length} for ${batch[0]?.cmName}: ${batch.map(ca => ca.addr)}`, ); - const { results } = await this.#liquidateBatch( + const { results, receipt } = await this.#liquidateBatch( batch, cms, i, batches.length, ); + const hasErrors = results.some(r => !!r.isError); + let traceId: string | undefined; + if (hasErrors && !!receipt) { + traceId = await this.errorHandler.saveTransactionTrace( + receipt.transactionHash, + ); + } for (const r of results) { - this.optimistic.push(r); + this.optimistic.push({ ...r, traceFile: traceId }); } } const success = this.optimistic.get().filter(r => !r.isError).length;