From 8f576b5073aa17667e3966fe08f171f535b1d043 Mon Sep 17 00:00:00 2001 From: doomsower <12031673+doomsower@users.noreply.github.com> Date: Mon, 15 Jul 2024 10:19:13 -0500 Subject: [PATCH] feat: collect optimistic results from batch liquidator --- src/config/env.ts | 2 +- src/services/liquidate/BatchLiquidator.ts | 91 +++++++++++++++++------ src/services/liquidate/types.ts | 8 +- src/services/liquidate/viem-types.ts | 10 +++ src/services/notifier/messages.ts | 27 +++---- 5 files changed, 94 insertions(+), 44 deletions(-) diff --git a/src/config/env.ts b/src/config/env.ts index dab6de7..305eb57 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -8,7 +8,7 @@ const envConfigMapping: Record = { debugManagers: "DEBUG_MANAGERS", castBin: "CAST_BIN", deployPartialLiquidatorContracts: "DEPLOY_PARTIAL_LIQUIDATOR", - deployBatchLiquidatorContracts: "DEPLOY_BATCG_LIQUIDATOR", + deployBatchLiquidatorContracts: "DEPLOY_BATCH_LIQUIDATOR", ethProviderRpcs: ["JSON_RPC_PROVIDERS", "JSON_RPC_PROVIDER"], hfThreshold: "HF_TRESHOLD", restakingWorkaround: "RESTAKING_WORKAROUND", diff --git a/src/services/liquidate/BatchLiquidator.ts b/src/services/liquidate/BatchLiquidator.ts index 8556d9f..4e58d57 100644 --- a/src/services/liquidate/BatchLiquidator.ts +++ b/src/services/liquidate/BatchLiquidator.ts @@ -4,7 +4,9 @@ import { } from "@gearbox-protocol/liquidator-v2-contracts/abi"; import { BatchLiquidator_bytecode } from "@gearbox-protocol/liquidator-v2-contracts/bytecode"; import { iCreditFacadeV3Abi } from "@gearbox-protocol/types/abi"; -import { type Address, parseEventLogs } from "viem"; +import type { OptimisticResultV2 } from "@gearbox-protocol/types/optimist"; +import type { Address, TransactionReceipt } from "viem"; +import { parseEventLogs } from "viem"; import type { CreditAccountData } from "../../data/index.js"; import { @@ -12,9 +14,15 @@ import { BatchLiquidationFinishedMessage, } from "../notifier/messages.js"; import AbstractLiquidator from "./AbstractLiquidator.js"; -import type { BatchLiquidationResult, ILiquidatorService } from "./types.js"; +import type { ILiquidatorService } from "./types.js"; +import type { BatchLiquidationResult } from "./viem-types.js"; -export default abstract class BatchLiquidator +interface BatchLiquidationOutput { + readonly receipt: TransactionReceipt; + readonly results: OptimisticResultV2[]; +} + +export default class BatchLiquidator extends AbstractLiquidator implements ILiquidatorService { @@ -31,13 +39,9 @@ export default abstract class BatchLiquidator } this.logger.warn(`Need to liquidate ${accounts.length} accounts`); try { - const result = await this.#liquidateBatch(accounts); + const { receipt, results } = await this.#liquidateBatch(accounts); this.notifier.notify( - new BatchLiquidationFinishedMessage( - result.liquidated, - result.notLiquidated, - result.receipt, - ), + new BatchLiquidationFinishedMessage(receipt, results), ); } catch (e) { const decoded = await this.errorHandler.explain(e); @@ -53,15 +57,19 @@ export default abstract class BatchLiquidator ): Promise { const total = accounts.length; this.logger.info(`optimistic batch-liquidation for ${total} accounts`); - const result = await this.#liquidateBatch(accounts); + const { results } = await this.#liquidateBatch(accounts); + const success = results.filter(r => !r.isError).length; + for (const r of results) { + this.optimistic.push(r); + } this.logger.info( - `optimistic batch-liquidation finished: ${result.liquidated.length}/${total} accounts liquidated`, + `optimistic batch-liquidation finished: ${success}/${total} accounts liquidated`, ); } async #liquidateBatch( accounts: CreditAccountData[], - ): Promise { + ): Promise { const input = accounts.map(ca => this.pathFinder.getEstimateBatchInput(ca, this.config.slippage), ); @@ -72,20 +80,26 @@ export default abstract class BatchLiquidator functionName: "estimateBatch", args: [input] as any, // TODO: types }); + const batch: Record = Object.fromEntries( + result.map(r => [r.creditAccount.toLowerCase(), r]), + ); this.logger.debug(result, "estimated batch"); + const { request } = await this.client.pub.simulateContract({ account: this.client.account, address: this.batchLiquidator, abi: iBatchLiquidatorAbi, functionName: "liquidateBatch", args: [ - result.map(i => ({ - calls: i.calls, - creditAccount: i.creditAccount, - creditFacade: accounts.find( - ca => ca.addr === i.creditAccount.toLowerCase(), - )?.creditFacade!, // TODO: checks - })), + result + .filter(i => i.executed) + .map(i => ({ + calls: i.calls, + creditAccount: i.creditAccount, + creditFacade: accounts.find( + ca => ca.addr === i.creditAccount.toLowerCase(), + )?.creditFacade!, // TODO: checks + })), this.client.address, ], }); @@ -99,10 +113,45 @@ export default abstract class BatchLiquidator const liquidated = new Set( logs.map(l => l.args.creditAccount.toLowerCase() as Address), ); + const getError = (a: CreditAccountData): string | undefined => { + if (liquidated.has(a.addr)) { + return undefined; + } + const item = batch[a.addr]; + if (!item) { + return "not found in estimateBatch output"; + } + if (item.pathFound) { + return "batch path not found"; + } + if (item.executed) { + return "cannot execute in estimateBatch"; + } + return "cannot liquidate in batch"; + }; + const results = accounts.map( + (a): OptimisticResultV2 => ({ + version: "2", + callsHuman: [], + balancesBefore: a.filterDust(), + balancesAfter: {}, + hfBefore: a.healthFactor, + hfAfter: 0, + creditManager: a.creditManager, + borrower: a.borrower, + account: a.addr, + gasUsed: 0, // cannot know for single account + calls: [...(batch[a.addr]?.calls ?? [])], + pathAmount: "0", // TODO: ?? + liquidatorPremium: (batch[a.addr]?.profit ?? 0n).toString(10), + liquidatorProfit: "0", // cannot compute for single account + isError: !liquidated.has(a.addr), + error: getError(a), + }), + ); return { receipt, - liquidated: accounts.filter(a => liquidated.has(a.addr)), - notLiquidated: accounts.filter(a => !liquidated.has(a.addr)), + results, }; } diff --git a/src/services/liquidate/types.ts b/src/services/liquidate/types.ts index 0aae6a9..472f2b4 100644 --- a/src/services/liquidate/types.ts +++ b/src/services/liquidate/types.ts @@ -1,5 +1,5 @@ import type { PartialLiquidationCondition } from "@gearbox-protocol/types/optimist"; -import type { Address, Hash, Hex, TransactionReceipt } from "viem"; +import type { Address, Hash, Hex } from "viem"; import type { CreditAccountData, @@ -79,9 +79,3 @@ export interface MerkleDistributorInfo { } >; } - -export interface BatchLiquidationResult { - receipt: TransactionReceipt; - liquidated: CreditAccountData[]; - notLiquidated: CreditAccountData[]; -} diff --git a/src/services/liquidate/viem-types.ts b/src/services/liquidate/viem-types.ts index 0a788a3..8dd5cfd 100644 --- a/src/services/liquidate/viem-types.ts +++ b/src/services/liquidate/viem-types.ts @@ -1,4 +1,5 @@ import type { + iBatchLiquidatorAbi, iLiquidatorAbi, iPriceHelperAbi, } from "@gearbox-protocol/liquidator-v2-contracts/abi"; @@ -22,3 +23,12 @@ export type TokenPriceInfo = ArrayElementType< ExtractAbiFunction["outputs"]["0"] > >; + +export type BatchLiquidationResult = ArrayElementType< + AbiParameterToPrimitiveType< + ExtractAbiFunction< + typeof iBatchLiquidatorAbi, + "estimateBatch" + >["outputs"]["0"] + > +>; diff --git a/src/services/notifier/messages.ts b/src/services/notifier/messages.ts index 3ea912b..636f8b6 100644 --- a/src/services/notifier/messages.ts +++ b/src/services/notifier/messages.ts @@ -1,5 +1,6 @@ import type { NetworkType } from "@gearbox-protocol/sdk-gov"; import { formatBN } from "@gearbox-protocol/sdk-gov"; +import type { OptimisticResultV2 } from "@gearbox-protocol/types/optimist"; import type { Markdown } from "@vlad-yakovlev/telegram-md"; import { md } from "@vlad-yakovlev/telegram-md"; import type { Address, TransactionReceipt } from "viem"; @@ -200,27 +201,23 @@ export class BatchLiquidationFinishedMessage extends BaseMessage implements INotifierMessage { - #liquidated: CreditAccountData[]; - #notLiquidated: CreditAccountData[]; + #liquidated: number; + #notLiquidated: number; - constructor( - liquidated: CreditAccountData[], - notLiquidated: CreditAccountData[], - receipt: TransactionReceipt, - ) { + constructor(receipt: TransactionReceipt, results: OptimisticResultV2[]) { super({ receipt }); - this.#liquidated = liquidated; - this.#notLiquidated = notLiquidated; + this.#liquidated = results.filter(r => !r.isError).length; + this.#notLiquidated = results.filter(r => !!r.isError).length; } public get plain(): string { if (this.receipt?.status === "success") { - if (this.#notLiquidated.length === 0) { - return `✅ [${this.network}] batch-liquidated ${this.#liquidated.length} accounts: + if (this.#notLiquidated === 0) { + return `✅ [${this.network}] batch-liquidated ${this.#liquidated} accounts: Tx receipt: ${this.receiptPlain} Gas used: ${this.receipt?.gasUsed?.toLocaleString("en")}`; } else { - return `❌ [${this.network}] batch-liquidated ${this.#liquidated.length} accounts, but failed to liquidate ${this.#notLiquidated.length} more + return `❌ [${this.network}] batch-liquidated ${this.#liquidated} accounts, but failed to liquidate ${this.#notLiquidated} more Tx receipt: ${this.receiptPlain} Gas used: ${this.receipt?.gasUsed?.toLocaleString("en")}`; } @@ -232,15 +229,15 @@ Tx: ${this.receiptPlain}`; public get markdown(): string { if (this.receipt?.status === "success") { - if (this.#notLiquidated.length === 0) { + if (this.#notLiquidated === 0) { return md.build( - md`✅ [${this.network}] batch-liquidated ${this.#liquidated.length} accounts + md`✅ [${this.network}] batch-liquidated ${this.#liquidated} accounts Tx receipt: ${this.receiptMd} Gas used: ${md.bold(this.receipt?.gasUsed?.toLocaleString("en"))}`, ); } else { return md.build( - md`❌ [${this.network}] batch-liquidated ${this.#liquidated.length} accounts, but failed to liquidate ${this.#notLiquidated.length} more + md`❌ [${this.network}] batch-liquidated ${this.#liquidated} accounts, but failed to liquidate ${this.#notLiquidated} more Tx receipt: ${this.receiptMd} Gas used: ${md.bold(this.receipt?.gasUsed?.toLocaleString("en"))}`, );