Skip to content

Commit

Permalink
feat: collect optimistic results from batch liquidator
Browse files Browse the repository at this point in the history
  • Loading branch information
doomsower committed Jul 15, 2024
1 parent 1035634 commit 8f576b5
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 44 deletions.
2 changes: 1 addition & 1 deletion src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const envConfigMapping: Record<keyof ConfigSchema, string | string[]> = {
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",
Expand Down
91 changes: 70 additions & 21 deletions src/services/liquidate/BatchLiquidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,25 @@ 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 {
BatchLiquidationErrorMessage,
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
{
Expand All @@ -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);
Expand All @@ -53,15 +57,19 @@ export default abstract class BatchLiquidator
): Promise<void> {
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<BatchLiquidationResult> {
): Promise<BatchLiquidationOutput> {
const input = accounts.map(ca =>
this.pathFinder.getEstimateBatchInput(ca, this.config.slippage),
);
Expand All @@ -72,20 +80,26 @@ export default abstract class BatchLiquidator
functionName: "estimateBatch",
args: [input] as any, // TODO: types
});
const batch: Record<Address, BatchLiquidationResult> = 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,
],
});
Expand All @@ -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,
};
}

Expand Down
8 changes: 1 addition & 7 deletions src/services/liquidate/types.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -79,9 +79,3 @@ export interface MerkleDistributorInfo {
}
>;
}

export interface BatchLiquidationResult {
receipt: TransactionReceipt;
liquidated: CreditAccountData[];
notLiquidated: CreditAccountData[];
}
10 changes: 10 additions & 0 deletions src/services/liquidate/viem-types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
iBatchLiquidatorAbi,
iLiquidatorAbi,
iPriceHelperAbi,
} from "@gearbox-protocol/liquidator-v2-contracts/abi";
Expand All @@ -22,3 +23,12 @@ export type TokenPriceInfo = ArrayElementType<
ExtractAbiFunction<typeof iPriceHelperAbi, "previewTokens">["outputs"]["0"]
>
>;

export type BatchLiquidationResult = ArrayElementType<
AbiParameterToPrimitiveType<
ExtractAbiFunction<
typeof iBatchLiquidatorAbi,
"estimateBatch"
>["outputs"]["0"]
>
>;
27 changes: 12 additions & 15 deletions src/services/notifier/messages.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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")}`;
}
Expand All @@ -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"))}`,
);
Expand Down

0 comments on commit 8f576b5

Please sign in to comment.