Skip to content

Commit f6e305f

Browse files
committed
fix: improve multicall spy
1 parent 8916726 commit f6e305f

File tree

5 files changed

+138
-64
lines changed

5 files changed

+138
-64
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@
2929
"@gearbox-protocol/cli-utils": "^5.45.17",
3030
"@gearbox-protocol/liquidator-contracts": "^1.36.0-experimental.41",
3131
"@gearbox-protocol/liquidator-v2-contracts": "^2.4.0",
32-
"@gearbox-protocol/sdk": "9.0.1",
32+
"@gearbox-protocol/sdk": "9.0.4",
3333
"@gearbox-protocol/types": "^1.14.8",
34-
"@types/node": "^24.4.0",
34+
"@types/node": "^24.5.0",
3535
"@uniswap/sdk-core": "^7.7.2",
3636
"@uniswap/v3-sdk": "^3.25.2",
3737
"@vlad-yakovlev/telegram-md": "^2.1.0",

src/MulticallSpy.ts

Lines changed: 78 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,110 @@
11
import { join } from "node:path";
22
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
3-
import { type EthCallRequest, EthCallSpy } from "@gearbox-protocol/sdk/dev";
3+
import {
4+
type DetectedCall,
5+
type EthCallRequest,
6+
EthCallSpy,
7+
} from "@gearbox-protocol/sdk/dev";
8+
import {
9+
decodeFunctionResult,
10+
multicall3Abi,
11+
parseAbi,
12+
type RequiredBy,
13+
} from "viem";
414
import type { Config } from "./config/index.js";
515
import { DI } from "./di.js";
6-
import { type ILogger, Logger } from "./log/index.js";
16+
import type { ILogger } from "./log/index.js";
17+
18+
const multicallTimestampAbi = parseAbi([
19+
"function getCurrentBlockTimestamp() public view returns (uint256 timestamp)",
20+
"function getBlockNumber() public view returns (uint256 blockNumber)",
21+
]);
22+
23+
interface SpiedCall extends DetectedCall {
24+
multicall: {
25+
blockNumber: string;
26+
timestamp: string;
27+
};
28+
}
729

830
/**
931
* This is temporary solution to diagnose bug where compressor occasionally returns many accounts with HF = 0
1032
*/
1133
@DI.Injectable(DI.MulticallSpy)
12-
export default class MulticallSpy {
13-
@Logger("MulticallSpy")
14-
log!: ILogger;
15-
16-
@DI.Inject(DI.Config)
17-
config!: Config;
18-
19-
public readonly spy: EthCallSpy;
34+
export default class MulticallSpy extends EthCallSpy<SpiedCall> {
2035
#client = new S3Client({});
36+
#log: ILogger;
37+
#config: Config;
2138

2239
constructor() {
23-
this.spy = new EthCallSpy(
40+
const log = DI.create(DI.Logger, "MulticallSpy");
41+
const config = DI.get(DI.Config);
42+
super(
2443
isGetCreditAccountsMulticall,
25-
this.log,
26-
this.config.debugScanner && !this.config.optimistic,
44+
log,
45+
config.debugScanner && !config.optimistic,
2746
);
47+
this.#log = log;
48+
this.#config = config;
2849
}
2950

3051
public async dumpCalls(): Promise<void> {
31-
if (!this.config.outS3Bucket) {
32-
this.log.error("outS3Bucket is not set");
52+
if (!this.#config.outS3Bucket) {
53+
this.#log.error("outS3Bucket is not set");
3354
return;
3455
}
3556
const key = join(
36-
this.config.outS3Prefix,
37-
`getCreditAccounts_${this.spy.detectedBlock}.json`,
57+
this.#config.outS3Prefix,
58+
this.#config.network,
59+
`getCreditAccounts_${this.detectedBlock}.json`,
3860
);
39-
const s3Url = `s3://${this.config.outS3Bucket}/${key}`;
61+
const s3Url = `s3://${this.#config.outS3Bucket}/${key}`;
4062
try {
41-
this.log.debug(`uploading to ${s3Url}`);
63+
this.#log.debug(`uploading to ${s3Url}`);
4264
await this.#client.send(
4365
new PutObjectCommand({
44-
Bucket: this.config.outS3Bucket,
66+
Bucket: this.#config.outS3Bucket,
4567
Key: key,
4668
ContentType: "application/json",
47-
Body: JSON.stringify(this.spy.detectedCalls),
69+
Body: JSON.stringify(this.detectedCalls),
4870
}),
4971
);
50-
this.log.debug(`uploaded to ${s3Url}`);
72+
this.#log.debug(`uploaded to ${s3Url}`);
5173
} catch (e) {
52-
this.log.error(e, `failed to upload to ${s3Url}`);
74+
this.#log.error(e, `failed to upload to ${s3Url}`);
75+
}
76+
}
77+
78+
protected override storeResponse(
79+
call: RequiredBy<SpiedCall, "response" | "responseHeaders">,
80+
): void | Promise<void> {
81+
super.storeResponse(call);
82+
const result = call.response.result;
83+
if (result) {
84+
try {
85+
const res = decodeFunctionResult({
86+
abi: multicall3Abi,
87+
data: result,
88+
functionName: "aggregate3",
89+
});
90+
const [timestampEnc, blockNumberEnc] = res;
91+
const timestamp = decodeFunctionResult({
92+
abi: multicallTimestampAbi,
93+
data: timestampEnc.returnData,
94+
functionName: "getCurrentBlockTimestamp",
95+
});
96+
const blockNumber = decodeFunctionResult({
97+
abi: multicallTimestampAbi,
98+
data: blockNumberEnc.returnData,
99+
functionName: "getBlockNumber",
100+
});
101+
call.multicall = {
102+
blockNumber: blockNumber.toString(),
103+
timestamp: timestamp.toString(),
104+
};
105+
} catch (e) {
106+
this.#log.error(`failed to parse multicall response: ${e}`);
107+
}
53108
}
54109
}
55110
}

src/attachSDK.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ export default async function attachSDK(): Promise<ICreditAccountsService> {
6262

6363
const transport = createTransport(config, {
6464
timeout: 600_000,
65-
onFetchRequest: multicallSpy.spy.onFetchRequest,
66-
onFetchResponse: multicallSpy.spy.onFetchResponse,
65+
onFetchRequest: multicallSpy.onFetchRequest,
66+
onFetchResponse: multicallSpy.onFetchResponse,
6767
});
6868

6969
const sdk = await GearboxSDK.attach({

src/services/Scanner.ts

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,8 @@ class ScannerDiagnoster {
404404
this.#drpc = new WorkaroundCAS(this.caService.sdk, {
405405
rpcURL,
406406
rpcOptions: {
407-
onFetchRequest: this.multicallSpy.spy.onFetchRequest,
408-
onFetchResponse: this.multicallSpy.spy.onFetchResponse,
407+
onFetchRequest: this.multicallSpy.onFetchRequest,
408+
onFetchResponse: this.multicallSpy.onFetchResponse,
409409
},
410410
});
411411
}
@@ -420,8 +420,8 @@ class ScannerDiagnoster {
420420
this.#alchemy = new WorkaroundCAS(this.caService.sdk, {
421421
rpcURL,
422422
rpcOptions: {
423-
onFetchRequest: this.multicallSpy.spy.onFetchRequest,
424-
onFetchResponse: this.multicallSpy.spy.onFetchResponse,
423+
onFetchRequest: this.multicallSpy.onFetchRequest,
424+
onFetchResponse: this.multicallSpy.onFetchResponse,
425425
},
426426
});
427427
}
@@ -436,9 +436,10 @@ class ScannerDiagnoster {
436436
queue: GetCreditAccountsOptions,
437437
blockNumber?: bigint,
438438
): Promise<CreditAccountData[]> {
439+
let result = accounts;
439440
try {
440441
if (!accounts.length || !blockNumber || this.config.optimistic) {
441-
return accounts;
442+
return result;
442443
}
443444
const numZeroHF = accounts.filter(a => a.healthFactor === 0n).length;
444445
let [success, drpcSuccess, dprcZeroHF, alchemySuccess, alchemyZeroHF] = [
@@ -450,37 +451,39 @@ class ScannerDiagnoster {
450451
this.log.debug(
451452
`found ${accounts.length} liquidatable accounts (${success} successful, ${numZeroHF} zero HF) in block ${blockNumber}`,
452453
);
453-
if (!this.#drpc || !this.#alchemy || numZeroHF === 0) {
454-
return accounts;
454+
if (numZeroHF === 0) {
455+
return result;
455456
}
456-
const [drpcAccs, alchemyAccs] = await Promise.all([
457-
this.#drpc.getCreditAccounts(queue, blockNumber),
458-
this.#alchemy.getCreditAccounts(queue, blockNumber),
459-
]);
460-
for (const a of drpcAccs) {
461-
dprcZeroHF += a.healthFactor === 0n ? 1 : 0;
462-
drpcSuccess += a.success ? 1 : 0;
463-
}
464-
for (const a of alchemyAccs) {
465-
alchemyZeroHF += a.healthFactor === 0n ? 1 : 0;
466-
alchemySuccess += a.success ? 1 : 0;
457+
if (!!this.#drpc && !!this.#alchemy) {
458+
const [drpcAccs, alchemyAccs] = await Promise.all([
459+
this.#drpc.getCreditAccounts(queue, blockNumber),
460+
this.#alchemy.getCreditAccounts(queue, blockNumber),
461+
]);
462+
for (const a of drpcAccs) {
463+
dprcZeroHF += a.healthFactor === 0n ? 1 : 0;
464+
drpcSuccess += a.success ? 1 : 0;
465+
}
466+
for (const a of alchemyAccs) {
467+
alchemyZeroHF += a.healthFactor === 0n ? 1 : 0;
468+
alchemySuccess += a.success ? 1 : 0;
469+
}
470+
this.log.debug(
471+
`found ${drpcAccs.length} liquidatable accounts (${drpcSuccess} successful, ${dprcZeroHF} zero HF) in block ${blockNumber} with drpc`,
472+
);
473+
this.log.debug(
474+
`found ${alchemyAccs.length} liquidatable accounts (${alchemySuccess} successful, ${alchemyZeroHF} zero HF) in block ${blockNumber} with alchemy`,
475+
);
476+
this.notifier.alert({
477+
plain: `Found ${numZeroHF} zero HF accounts in block ${blockNumber}, second pass ${dprcZeroHF} drpc, ${alchemyZeroHF} alchemy`,
478+
markdown: `Found ${numZeroHF} zero HF accounts in block ${blockNumber}, second pass ${dprcZeroHF} drpc, ${alchemyZeroHF} alchemy`,
479+
});
480+
result = alchemyZeroHF < dprcZeroHF ? alchemyAccs : drpcAccs;
467481
}
468-
this.log.debug(
469-
`found ${drpcAccs.length} liquidatable accounts (${drpcSuccess} successful, ${dprcZeroHF} zero HF) in block ${blockNumber} with drpc`,
470-
);
471-
this.log.debug(
472-
`found ${alchemyAccs.length} liquidatable accounts (${alchemySuccess} successful, ${alchemyZeroHF} zero HF) in block ${blockNumber} with alchemy`,
473-
);
474-
this.notifier.alert({
475-
plain: `Found ${numZeroHF} zero HF accounts in block ${blockNumber}, second pass ${dprcZeroHF} drpc, ${alchemyZeroHF} alchemy`,
476-
markdown: `Found ${numZeroHF} zero HF accounts in block ${blockNumber}, second pass ${dprcZeroHF} drpc, ${alchemyZeroHF} alchemy`,
477-
});
478482
await this.multicallSpy.dumpCalls();
479-
return alchemyZeroHF < dprcZeroHF ? alchemyAccs : drpcAccs;
480483
} catch (e) {
481484
this.log.error(e);
482-
return accounts;
483485
}
486+
return result;
484487
}
485488
}
486489

yarn.lock

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1778,9 +1778,9 @@ __metadata:
17781778
"@gearbox-protocol/cli-utils": "npm:^5.45.17"
17791779
"@gearbox-protocol/liquidator-contracts": "npm:^1.36.0-experimental.41"
17801780
"@gearbox-protocol/liquidator-v2-contracts": "npm:^2.4.0"
1781-
"@gearbox-protocol/sdk": "npm:9.0.1"
1781+
"@gearbox-protocol/sdk": "npm:9.0.4"
17821782
"@gearbox-protocol/types": "npm:^1.14.8"
1783-
"@types/node": "npm:^24.4.0"
1783+
"@types/node": "npm:^24.5.0"
17841784
"@uniswap/sdk-core": "npm:^7.7.2"
17851785
"@uniswap/v3-sdk": "npm:^3.25.2"
17861786
"@vlad-yakovlev/telegram-md": "npm:^2.1.0"
@@ -1804,9 +1804,9 @@ __metadata:
18041804
languageName: unknown
18051805
linkType: soft
18061806

1807-
"@gearbox-protocol/sdk@npm:9.0.1":
1808-
version: 9.0.1
1809-
resolution: "@gearbox-protocol/sdk@npm:9.0.1"
1807+
"@gearbox-protocol/sdk@npm:9.0.4":
1808+
version: 9.0.4
1809+
resolution: "@gearbox-protocol/sdk@npm:9.0.4"
18101810
dependencies:
18111811
"@redstone-finance/evm-connector": "npm:^0.7.5"
18121812
"@redstone-finance/protocol": "npm:^0.7.5"
@@ -1820,7 +1820,7 @@ __metadata:
18201820
zod: "npm:^4.1.8"
18211821
peerDependencies:
18221822
axios: ^1.0.0
1823-
checksum: 10c0/17603590e88f7b6aeac77376233575eaa1721875be2b14d24c5551e71c06e4810450b4b8f13b6664d03a0c4d50063f9775a75afbcccc943a3f8785e6c37b4803
1823+
checksum: 10c0/cb5cc46cdabaeee379318746313106ed33645b02f75f3ca6fecc570bd2c3f10d4b46a62dc269a1500eeffed2f4931f78617e132e2bdf6995592017bec817e7d4
18241824
languageName: node
18251825
linkType: hard
18261826

@@ -2856,7 +2856,7 @@ __metadata:
28562856
languageName: node
28572857
linkType: hard
28582858

2859-
"@types/node@npm:*, @types/node@npm:^24.4.0":
2859+
"@types/node@npm:*":
28602860
version: 24.4.0
28612861
resolution: "@types/node@npm:24.4.0"
28622862
dependencies:
@@ -2865,6 +2865,15 @@ __metadata:
28652865
languageName: node
28662866
linkType: hard
28672867

2868+
"@types/node@npm:^24.5.0":
2869+
version: 24.5.0
2870+
resolution: "@types/node@npm:24.5.0"
2871+
dependencies:
2872+
undici-types: "npm:~7.12.0"
2873+
checksum: 10c0/c5beff68481e2cc667279a1478b34a1cfd048dbff914219cb5888967938d134907836b6c4d6d141dc862489cb09ef28f7d446c7a3b475181fd126c0fcd2916fa
2874+
languageName: node
2875+
linkType: hard
2876+
28682877
"@types/uuid@npm:^9.0.1":
28692878
version: 9.0.8
28702879
resolution: "@types/uuid@npm:9.0.8"
@@ -5988,6 +5997,13 @@ __metadata:
59885997
languageName: node
59895998
linkType: hard
59905999

6000+
"undici-types@npm:~7.12.0":
6001+
version: 7.12.0
6002+
resolution: "undici-types@npm:7.12.0"
6003+
checksum: 10c0/326e455bbc0026db1d6b81c76a1cf10c63f7e2f9821db2e24fdc258f482814e5bfa8481f8910d07c68e305937c5c049610fdc441c5e8b7bb0daca7154fb8a306
6004+
languageName: node
6005+
linkType: hard
6006+
59916007
"unicorn-magic@npm:^0.1.0":
59926008
version: 0.1.0
59936009
resolution: "unicorn-magic@npm:0.1.0"

0 commit comments

Comments
 (0)