Skip to content

Commit 633f006

Browse files
committed
Merge branch 'beta'
2 parents 1e55a78 + 29c0a2d commit 633f006

14 files changed

+304
-69
lines changed

src/config/deleverage-liquidator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const DeleverageLiquidatorSchema = z.object({
1717
* Address of the partial liquidation bot (for deleverage)
1818
*/
1919
partialLiquidationBot: ZodAddress()
20-
.default("0xd9f080c7d9a7202a816d32075a9b50fa8c6c504a")
20+
// .default("0xc73a6741c77164dd06ff6edb09e8374abdf75c19")
2121
.register(zommandRegistry, {
2222
flags: "--partial-liquidation-bot <address>",
2323
description: "Address of the partial liquidation bot (for deleverage)",

src/services/HealthCheckerService.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ export default class HealthCheckerService {
9292
address: this.client.address,
9393
balance: this.client.balance,
9494
currentBlock: this.sdk.currentBlock,
95+
minHealthFactor: this.scanner.minHealthFactor,
96+
maxHealthFactor: this.scanner.maxHealthFactor,
9597
timestamp: {
9698
value: timestamp,
9799
status: (!!threshold && now - timestamp <= threshold
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { BaseContract, type GearboxSDK } from "@gearbox-protocol/sdk";
2+
import { iPartialLiquidationBotV310Abi } from "@gearbox-protocol/sdk/plugins/bots";
3+
import type { Address } from "viem";
4+
5+
const abi = iPartialLiquidationBotV310Abi;
6+
type abi = typeof abi;
7+
8+
export class PartialLiquidationBotV310Contract extends BaseContract<abi> {
9+
public static get(
10+
sdk: GearboxSDK,
11+
address: Address,
12+
): PartialLiquidationBotV310Contract {
13+
const existing = sdk.contracts.get(address) as
14+
| PartialLiquidationBotV310Contract
15+
| undefined;
16+
return existing ?? new PartialLiquidationBotV310Contract(sdk, address);
17+
}
18+
19+
#minHealthFactor?: bigint;
20+
#maxHealthFactor?: bigint;
21+
22+
constructor(sdk: GearboxSDK, address: Address) {
23+
super(sdk, {
24+
abi,
25+
addr: address,
26+
contractType: "partialLiquidationBot",
27+
version: 310,
28+
});
29+
}
30+
31+
public async loadHealthFactors(): Promise<[bigint, bigint]> {
32+
if (!this.#minHealthFactor || !this.#maxHealthFactor) {
33+
const [minHealthFactor, maxHealthFactor] = await this.client.multicall({
34+
contracts: [
35+
{
36+
address: this.address,
37+
abi: this.abi,
38+
functionName: "minHealthFactor",
39+
},
40+
{
41+
address: this.address,
42+
abi: this.abi,
43+
functionName: "maxHealthFactor",
44+
},
45+
],
46+
allowFailure: false,
47+
});
48+
this.#minHealthFactor = BigInt(minHealthFactor);
49+
this.#maxHealthFactor = BigInt(maxHealthFactor);
50+
}
51+
return [this.#minHealthFactor, this.#maxHealthFactor];
52+
}
53+
54+
public get minHealthFactor(): bigint {
55+
if (!this.#minHealthFactor) {
56+
throw new Error("minHealthFactor not loaded");
57+
}
58+
return this.#minHealthFactor;
59+
}
60+
61+
public get maxHealthFactor(): bigint {
62+
if (!this.#maxHealthFactor) {
63+
throw new Error("maxHealthFactor not loaded");
64+
}
65+
return this.#maxHealthFactor;
66+
}
67+
}

src/services/Scanner.ts

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,24 @@ import type {
77
import {
88
AddressSet,
99
hexEq,
10+
isVersionRange,
1011
MAX_UINT256,
1112
PERCENTAGE_FACTOR,
13+
VERSION_RANGE_310,
1214
WAD,
1315
} from "@gearbox-protocol/sdk";
1416
import { iBotListV310Abi } from "@gearbox-protocol/sdk/abi/310/generated";
1517
import { iCreditManagerV300Abi } from "@gearbox-protocol/sdk/abi/v300";
16-
import { iPartialLiquidationBotV310Abi } from "@gearbox-protocol/sdk/plugins/bots";
1718
import type { Address, Block } from "viem";
1819
import { getContract } from "viem";
1920
import type { Config } from "../config/index.js";
2021
import { DI } from "../di.js";
2122
import { type ILogger, Logger } from "../log/index.js";
23+
import { DELEVERAGE_PERMISSIONS } from "../utils/permissions.js";
2224
import type Client from "./Client.js";
2325
import type { ILiquidatorService } from "./liquidate/index.js";
2426
import { type INotifier, ZeroHFAccountsMessage } from "./notifier/index.js";
27+
import { PartialLiquidationBotV310Contract } from "./PartialLiquidationBotV310Contract.js";
2528

2629
const RESTAKING_CMS: Partial<Record<NetworkType, Address>> = {
2730
Mainnet:
@@ -80,20 +83,18 @@ export class Scanner {
8083
this.#maxHealthFactor = MAX_UINT256;
8184
}
8285
if (this.config.liquidationMode === "deleverage") {
86+
const [botMinHealthFactor] = await PartialLiquidationBotV310Contract.get(
87+
this.caService.sdk,
88+
this.config.partialLiquidationBot,
89+
).loadHealthFactors();
8390
if (this.config.optimistic) {
8491
this.#minHealthFactor = 0n;
8592
this.#maxHealthFactor = MAX_UINT256;
8693
} else {
8794
this.#minHealthFactor = WAD;
88-
const botMinHealthFactor = await this.client.pub.readContract({
89-
address: this.config.partialLiquidationBot,
90-
abi: iPartialLiquidationBotV310Abi,
91-
functionName: "minHealthFactor",
92-
});
93-
this.#maxHealthFactor =
94-
(BigInt(botMinHealthFactor) * WAD) / PERCENTAGE_FACTOR;
95+
this.#maxHealthFactor = (botMinHealthFactor * WAD) / PERCENTAGE_FACTOR;
9596
this.log.info(
96-
`deleverage bot max health factor is ${botMinHealthFactor / 100}% (${this.#maxHealthFactor})`,
97+
`deleverage bot max health factor is ${botMinHealthFactor / 100n}% (${this.#maxHealthFactor})`,
9798
);
9899
}
99100
}
@@ -411,27 +412,33 @@ export class Scanner {
411412
}
412413

413414
async #filterDeleverageAccounts(
414-
accounts: CreditAccountData[],
415+
accounts_: CreditAccountData[],
415416
partialLiquidationBot: Address,
416417
blockNumber?: bigint,
417418
): Promise<CreditAccountData[]> {
418-
const botList = this.caService.sdk.botListContract?.address;
419-
if (!botList) {
420-
this.log.warn(
421-
"bot list contract not found, skipping deleverage accounts filtering",
422-
);
423-
return accounts;
419+
if (this.config.optimistic) {
420+
return accounts_;
424421
}
422+
423+
const accounts = accounts_.filter(ca => {
424+
const cm = this.caService.sdk.marketRegister.findCreditManager(
425+
ca.creditManager,
426+
);
427+
return isVersionRange(cm.creditFacade.version, VERSION_RANGE_310);
428+
});
429+
425430
const res = await this.client.pub.multicall({
426-
contracts: accounts.map(
427-
ca =>
428-
({
429-
address: botList,
430-
abi: iBotListV310Abi,
431-
functionName: "getBotStatus",
432-
args: [partialLiquidationBot, ca.creditAccount],
433-
}) as const,
434-
),
431+
contracts: accounts.map(ca => {
432+
const cm = this.caService.sdk.marketRegister.findCreditManager(
433+
ca.creditManager,
434+
);
435+
return {
436+
address: cm.creditFacade.botList,
437+
abi: iBotListV310Abi,
438+
functionName: "getBotStatus",
439+
args: [partialLiquidationBot, ca.creditAccount],
440+
} as const;
441+
}),
435442
allowFailure: true,
436443
blockNumber,
437444
});
@@ -442,15 +449,19 @@ export class Scanner {
442449
const r = res[i];
443450
if (r.status === "success") {
444451
const [permissions, forbidden] = r.result;
445-
if (!!permissions && !forbidden) {
452+
if (
453+
!!permissions &&
454+
!forbidden &&
455+
(permissions & DELEVERAGE_PERMISSIONS) === DELEVERAGE_PERMISSIONS
456+
) {
446457
result.push(ca);
447458
}
448459
} else if (r.status === "failure") {
449460
errored++;
450461
}
451462
}
452463
this.log.debug(
453-
{ errored, before: accounts.length, after: result.length, botList },
464+
{ errored, before: accounts_.length, after: result.length },
454465
"filtered accounts for deleverage",
455466
);
456467
return result;
@@ -474,6 +485,14 @@ export class Scanner {
474485
return this.#liquidatableAccounts;
475486
}
476487

488+
public get minHealthFactor(): bigint {
489+
return this.#minHealthFactor;
490+
}
491+
492+
public get maxHealthFactor(): bigint {
493+
return this.#maxHealthFactor;
494+
}
495+
477496
public async stop(): Promise<void> {
478497
this.#unwatch?.();
479498
this.log.info("stopped");
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import {
2+
type CreditAccountData,
3+
type MultiCall,
4+
PERCENTAGE_FACTOR,
5+
VERSION_RANGE_310,
6+
} from "@gearbox-protocol/sdk";
7+
import { iCreditFacadeMulticallV310Abi } from "@gearbox-protocol/sdk/abi/310/generated";
8+
import { encodeFunctionData, parseEther } from "viem";
9+
import type {
10+
DeleverageLiquidatorSchema,
11+
LiqduiatorConfig,
12+
} from "../../config/index.js";
13+
import { DI } from "../../di.js";
14+
import { type ILogger, Logger } from "../../log/index.js";
15+
import { DELEVERAGE_PERMISSIONS } from "../../utils/permissions.js";
16+
import { PartialLiquidationBotV310Contract } from "../PartialLiquidationBotV310Contract.js";
17+
import LiquidationStrategyPartial from "./LiquidationStrategyPartial.js";
18+
import type {
19+
ILiquidationStrategy,
20+
MakeLiquidatableResult,
21+
PartialLiquidationPreview,
22+
} from "./types.js";
23+
24+
export default class LiquidationStrategyDeleverage
25+
extends LiquidationStrategyPartial
26+
implements ILiquidationStrategy<PartialLiquidationPreview>
27+
{
28+
@DI.Inject(DI.Config)
29+
// @ts-expect-error
30+
config!: LiqduiatorConfig<DeleverageLiquidatorSchema>;
31+
32+
@Logger("DeleverageStrategy")
33+
// @ts-expect-error
34+
logger!: ILogger;
35+
36+
public override isApplicable(ca: CreditAccountData): boolean {
37+
return this.checkAccountVersion(ca, VERSION_RANGE_310);
38+
}
39+
40+
public override async makeLiquidatable(
41+
ca: CreditAccountData,
42+
): Promise<MakeLiquidatableResult> {
43+
if (!this.isApplicable(ca)) {
44+
throw new Error("cannot deleverage: account is not applicable");
45+
}
46+
const result = await super.makeLiquidatable(ca);
47+
const { creditFacade } = this.sdk.marketRegister.findCreditManager(
48+
ca.creditManager,
49+
);
50+
51+
// await this.client.anvil.impersonateAccount({
52+
// address: creditFacade.botList,
53+
// });
54+
55+
// const hash = await this.client.anvil.writeContract({
56+
// account: creditFacade.address,
57+
// chain: this.client.anvil.chain,
58+
// address: creditFacade.botList,
59+
// abi: iBotListV310Abi,
60+
// functionName: "setBotPermissions",
61+
// args: [
62+
// ca.creditAccount,
63+
// this.config.partialLiquidationBot,
64+
// DELEVERAGE_PERMISSIONS,
65+
// ],
66+
// });
67+
// this.logger.debug(
68+
// `set bot permissions for account ${ca.creditAccount} in tx ${hash}`,
69+
// );
70+
// const receipt = await this.client.pub.waitForTransactionReceipt({
71+
// hash,
72+
// });
73+
// if (receipt.status === "reverted") {
74+
// throw new Error(
75+
// `failed to set bot permissions for account ${ca.creditAccount} in tx ${hash}`,
76+
// );
77+
// }
78+
// await this.client.anvil.stopImpersonatingAccount({
79+
// address: creditFacade.botList,
80+
// });
81+
await this.client.anvil.impersonateAccount({ address: ca.owner });
82+
83+
const addBotCall: MultiCall = {
84+
target: creditFacade.address,
85+
callData: encodeFunctionData({
86+
abi: iCreditFacadeMulticallV310Abi,
87+
functionName: "setBotPermissions",
88+
args: [this.config.partialLiquidationBot, DELEVERAGE_PERMISSIONS],
89+
}),
90+
};
91+
92+
const tx = creditFacade.multicall(ca.creditAccount, [addBotCall]);
93+
94+
await this.client.anvil.setBalance({
95+
address: ca.owner,
96+
value: parseEther("100"),
97+
});
98+
const hash = await this.client.anvil.sendTransaction({
99+
account: ca.owner,
100+
chain: this.client.anvil.chain,
101+
to: tx.to,
102+
data: tx.callData,
103+
});
104+
const receipt = await this.client.anvil.waitForTransactionReceipt({ hash });
105+
if (receipt.status === "reverted") {
106+
throw new Error(
107+
`failed to set bot permissions for account ${ca.creditAccount} in tx ${hash}: reverted`,
108+
);
109+
} else {
110+
this.logger.debug(
111+
`set bot permissions for account ${ca.creditAccount} in tx ${hash}`,
112+
);
113+
}
114+
await this.client.anvil.stopImpersonatingAccount({ address: ca.owner });
115+
116+
return result;
117+
}
118+
119+
protected override ignoreReservePrices(_ca: CreditAccountData): boolean {
120+
return false;
121+
}
122+
123+
protected override optimisticHF(_ca: CreditAccountData): bigint {
124+
const minHF = PartialLiquidationBotV310Contract.get(
125+
this.sdk,
126+
this.config.partialLiquidationBot,
127+
).minHealthFactor;
128+
return (minHF * 9990n) / PERCENTAGE_FACTOR;
129+
}
130+
}

0 commit comments

Comments
 (0)