@gearbox-protocol/sdk 3.0.0-vfour.21 → 3.0.0-vfour.211

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,385 @@
1
- import { PERCENTAGE_FACTOR, AP_ACL, WAD } from '../sdk/index.mjs';
2
- import { createTestClient, publicActions, walletActions, toHex, parseEther } from 'viem';
1
+ import { createTestClient, publicActions, walletActions, toHex, isAddress, parseEther, createWalletClient, http, createPublicClient, stringToHex } from 'viem';
2
+ import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts';
3
+ import { SDKConstruct, childLogger, AddressMap, sendRawTx, ADDRESS_0X0, formatBN, ierc20Abi, MAX_UINT256, iDegenNftv2Abi, PERCENTAGE_FACTOR, AP_PRICE_FEED_COMPRESSOR, iPriceFeedCompressorAbi, rawTxToMulticallPriceUpdate, GearboxSDK, json_stringify, iAddressProviderV3Abi, ADDRESS_PROVIDER, iAddressProviderV3_1Abi, WAD } from '../sdk/index.mjs';
4
+ import { writeFile, readFile } from 'node:fs/promises';
3
5
 
4
- // src/dev/calcLiquidatableLTs.ts
6
+ // src/dev/AccountOpener.ts
7
+ function createAnvilClient({
8
+ chain,
9
+ transport
10
+ }) {
11
+ return createTestClient(
12
+ {
13
+ chain,
14
+ mode: "anvil",
15
+ transport,
16
+ cacheTime: 0,
17
+ pollingInterval: 50
18
+ }
19
+ ).extend(publicActions).extend(walletActions).extend((client) => ({
20
+ anvilNodeInfo: () => anvilNodeInfo(client),
21
+ isAnvil: () => isAnvil(client),
22
+ evmMineDetailed: (timestamp) => evmMineDetailed(client, timestamp)
23
+ }));
24
+ }
25
+ async function isAnvil(client) {
26
+ try {
27
+ const resp = await client.request({
28
+ method: "anvil_nodeInfo",
29
+ params: []
30
+ });
31
+ return !!resp.currentBlockNumber;
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
36
+ async function anvilNodeInfo(client) {
37
+ return client.request({
38
+ method: "anvil_nodeInfo",
39
+ params: []
40
+ });
41
+ }
42
+ async function evmMineDetailed(client, timestamp) {
43
+ try {
44
+ const [block] = await client.request({
45
+ method: "evm_mine_detailed",
46
+ params: [toHex(timestamp)]
47
+ });
48
+ return block;
49
+ } catch {
50
+ return undefined;
51
+ }
52
+ }
53
+
54
+ // src/dev/AccountOpener.ts
55
+ var AccountOpener = class extends SDKConstruct {
56
+ #service;
57
+ #anvil;
58
+ #logger;
59
+ #borrower;
60
+ #faucet;
61
+ constructor(service, options = {}) {
62
+ super(service.sdk);
63
+ this.#service = service;
64
+ this.#logger = childLogger("AccountOpener", service.sdk.logger);
65
+ this.#anvil = createAnvilClient({
66
+ chain: service.sdk.provider.chain,
67
+ transport: service.sdk.provider.transport
68
+ });
69
+ this.#faucet = options.faucet ?? service.sdk.addressProvider.getAddress("FAUCET");
70
+ }
71
+ get borrower() {
72
+ if (!this.#borrower) {
73
+ throw new Error("borrower can be used only after openCreditAccounts");
74
+ }
75
+ return this.#borrower.address;
76
+ }
77
+ /**
78
+ * Tries to open account with underlying only in each CM
79
+ */
80
+ async openCreditAccounts(targets) {
81
+ await this.#prepareBorrower(targets);
82
+ const toApprove = new AddressMap();
83
+ for (const c of targets) {
84
+ const cm = this.sdk.marketRegister.findCreditManager(c.creditManager);
85
+ const toApproveOnCM = toApprove.get(c.creditManager) ?? /* @__PURE__ */ new Set();
86
+ toApproveOnCM.add(cm.underlying);
87
+ toApprove.upsert(c.creditManager, toApproveOnCM);
88
+ }
89
+ for (const [cmAddr, tokens] of toApprove.entries()) {
90
+ const cm = this.sdk.marketRegister.findCreditManager(cmAddr);
91
+ for (const token of tokens) {
92
+ await this.#approve(token, cm);
93
+ }
94
+ }
95
+ for (let i = 0; i < targets.length; i++) {
96
+ const target = targets[i];
97
+ try {
98
+ await this.#openAccount(target, i + 1, targets.length);
99
+ } catch (e) {
100
+ this.#logger?.error(e);
101
+ }
102
+ }
103
+ const accounts = await this.getOpenedAccounts();
104
+ this.#logger?.info(`opened ${accounts.length} accounts`);
105
+ return accounts;
106
+ }
107
+ async #openAccount({ creditManager, collateral, leverage = 4, slippage = 50 }, index, total) {
108
+ const borrower = await this.#getBorrower();
109
+ const cm = this.sdk.marketRegister.findCreditManager(creditManager);
110
+ const symbol = this.sdk.tokensMeta.symbol(collateral);
111
+ const logger = this.#logger?.child?.({
112
+ creditManager: cm.name,
113
+ collateral: symbol
114
+ });
115
+ logger?.debug(`opening account #${index}/${total}`);
116
+ const { minDebt, underlying } = cm.creditFacade;
117
+ const expectedBalances = [];
118
+ const leftoverBalances = [];
119
+ for (const t of Object.keys(cm.collateralTokens)) {
120
+ const token = t;
121
+ expectedBalances.push({
122
+ token,
123
+ balance: token === underlying ? BigInt(leverage) * minDebt : 1n
124
+ });
125
+ leftoverBalances.push({
126
+ token,
127
+ balance: 1n
128
+ });
129
+ }
130
+ logger?.debug("looking for open strategy");
131
+ const strategy = await this.sdk.router.findOpenStrategyPath({
132
+ creditManager: cm.creditManager,
133
+ expectedBalances,
134
+ leftoverBalances,
135
+ slippage,
136
+ target: collateral
137
+ });
138
+ logger?.debug(strategy, "found open strategy");
139
+ const debt = minDebt * BigInt(leverage - 1);
140
+ const averageQuota = this.#getCollateralQuota(
141
+ cm,
142
+ collateral,
143
+ strategy.amount,
144
+ debt
145
+ );
146
+ const minQuota = this.#getCollateralQuota(
147
+ cm,
148
+ collateral,
149
+ strategy.minAmount,
150
+ debt
151
+ );
152
+ logger?.debug({ averageQuota, minQuota }, "calculated quotas");
153
+ const { tx, calls } = await this.#service.openCA({
154
+ creditManager: cm.creditManager.address,
155
+ averageQuota,
156
+ minQuota,
157
+ collateral: [{ token: underlying, balance: minDebt }],
158
+ debt,
159
+ calls: strategy.calls,
160
+ ethAmount: 0n,
161
+ permits: {},
162
+ to: borrower.address,
163
+ referralCode: 0n
164
+ });
165
+ for (let i = 0; i < calls.length; i++) {
166
+ const call = calls[i];
167
+ logger?.debug(
168
+ `call #${i + 1}: ${this.sdk.parseFunctionData(call.target, call.callData)}`
169
+ );
170
+ }
171
+ logger?.debug("prepared open account transaction");
172
+ const hash = await sendRawTx(this.#anvil, {
173
+ tx,
174
+ account: borrower
175
+ });
176
+ logger?.debug(`send transaction ${hash}`);
177
+ const receipt = await this.#anvil.waitForTransactionReceipt({ hash });
178
+ if (receipt.status === "reverted") {
179
+ throw new Error(`open credit account tx ${hash} reverted`);
180
+ }
181
+ logger?.info(`opened credit account ${index}/${total}`);
182
+ }
183
+ async getOpenedAccounts() {
184
+ return await this.#service.getCreditAccounts({
185
+ owner: this.borrower
186
+ });
187
+ }
188
+ /**
189
+ * Creates borrower wallet,
190
+ * Sets ETH balance,
191
+ * Gets tokens from faucet,
192
+ * Approves collateral tokens to credit manager,
193
+ * Gets DEGEN_NFT,
194
+ */
195
+ async #prepareBorrower(targets) {
196
+ const borrower = await this.#getBorrower();
197
+ let claimUSD = 0n;
198
+ let degenNFTS = {};
199
+ for (const target of targets) {
200
+ const cm = this.sdk.marketRegister.findCreditManager(
201
+ target.creditManager
202
+ );
203
+ const market = this.sdk.marketRegister.findByCreditManager(
204
+ target.creditManager
205
+ );
206
+ const { minDebt, degenNFT } = cm.creditFacade;
207
+ claimUSD += market.priceOracle.convertToUSD(cm.underlying, minDebt);
208
+ if (isAddress(degenNFT) && degenNFT !== ADDRESS_0X0) {
209
+ degenNFTS[degenNFT] = (degenNFTS[degenNFT] ?? 0) + 1;
210
+ }
211
+ }
212
+ claimUSD = claimUSD * 11n / 10n;
213
+ this.#logger?.debug(`claiming ${formatBN(claimUSD, 8)} USD from faucet`);
214
+ let hash = await this.#anvil.writeContract({
215
+ account: borrower,
216
+ address: this.#faucet,
217
+ abi: [
218
+ {
219
+ type: "function",
220
+ inputs: [
221
+ { name: "amountUSD", internalType: "uint256", type: "uint256" }
222
+ ],
223
+ name: "claim",
224
+ outputs: [],
225
+ stateMutability: "nonpayable"
226
+ }
227
+ ],
228
+ functionName: "claim",
229
+ args: [claimUSD],
230
+ chain: this.#anvil.chain
231
+ });
232
+ let receipt = await this.#anvil.waitForTransactionReceipt({
233
+ hash
234
+ });
235
+ if (receipt.status === "reverted") {
236
+ throw new Error(
237
+ `borrower ${borrower.address} failed to claimed equivalent of ${formatBN(claimUSD, 8)} USD from faucet, tx: ${hash}`
238
+ );
239
+ }
240
+ this.#logger?.debug(
241
+ `borrower ${borrower.address} claimed equivalent of ${formatBN(claimUSD, 8)} USD from faucet, tx: ${hash}`
242
+ );
243
+ for (const [degenNFT, amount] of Object.entries(degenNFTS)) {
244
+ await this.#mintDegenNft(degenNFT, borrower.address, amount);
245
+ }
246
+ this.#logger?.debug("prepared borrower");
247
+ return borrower;
248
+ }
249
+ async #approve(token, cm) {
250
+ const borrower = await this.#getBorrower();
251
+ const symbol = this.#service.sdk.tokensMeta.symbol(token);
252
+ try {
253
+ if (symbol === "USDT") {
254
+ const hash2 = await this.#anvil.writeContract({
255
+ account: borrower,
256
+ address: token,
257
+ abi: ierc20Abi,
258
+ functionName: "approve",
259
+ args: [cm.creditManager.address, 0n],
260
+ chain: this.#anvil.chain
261
+ });
262
+ await this.#anvil.waitForTransactionReceipt({
263
+ hash: hash2
264
+ });
265
+ }
266
+ const hash = await this.#anvil.writeContract({
267
+ account: borrower,
268
+ address: token,
269
+ abi: ierc20Abi,
270
+ functionName: "approve",
271
+ args: [cm.creditManager.address, MAX_UINT256],
272
+ chain: this.#anvil.chain
273
+ });
274
+ const receipt = await this.#anvil.waitForTransactionReceipt({
275
+ hash
276
+ });
277
+ if (receipt.status === "reverted") {
278
+ this.#logger?.error(
279
+ `failed to allowed credit manager ${cm.creditManager.name} to spend ${symbol} (${token}), tx reverted: ${hash}`
280
+ );
281
+ } else {
282
+ this.#logger?.debug(
283
+ `allowed credit manager ${cm.creditManager.name} to spend ${symbol} (${token}), tx: ${hash}`
284
+ );
285
+ }
286
+ } catch (e) {
287
+ this.#logger?.error(
288
+ `failed to allowed credit manager ${cm.creditManager.name} to spend ${symbol} (${token}): ${e}`
289
+ );
290
+ }
291
+ }
292
+ async #mintDegenNft(degenNFT, to, amount) {
293
+ if (amount <= 0) {
294
+ return;
295
+ }
296
+ let minter;
297
+ try {
298
+ minter = await this.#anvil.readContract({
299
+ address: degenNFT,
300
+ abi: iDegenNftv2Abi,
301
+ functionName: "minter"
302
+ });
303
+ } catch (e) {
304
+ this.#logger?.error(`failed to get minter of degenNFT ${degenNFT}: ${e}`);
305
+ return;
306
+ }
307
+ try {
308
+ await this.#anvil.impersonateAccount({ address: minter });
309
+ await this.#anvil.setBalance({
310
+ address: minter,
311
+ value: parseEther("100")
312
+ });
313
+ const hash = await this.#anvil.writeContract({
314
+ account: minter,
315
+ address: degenNFT,
316
+ abi: iDegenNftv2Abi,
317
+ functionName: "mint",
318
+ args: [to, BigInt(amount)],
319
+ chain: this.#anvil.chain
320
+ });
321
+ const receipt = await this.#anvil.waitForTransactionReceipt({
322
+ hash
323
+ });
324
+ if (receipt.status === "reverted") {
325
+ this.#logger?.error(
326
+ `failed to mint ${amount} degenNFT ${degenNFT} to borrower ${to}, tx reverted: ${hash}`
327
+ );
328
+ }
329
+ this.#logger?.debug(
330
+ `minted ${amount} degenNFT ${degenNFT} to borrower ${to}, tx: ${hash}`
331
+ );
332
+ } catch (e) {
333
+ this.#logger?.error(
334
+ `failed to mint ${amount} degenNFT ${degenNFT} to borrower ${to}: ${e}`
335
+ );
336
+ } finally {
337
+ await this.#anvil.stopImpersonatingAccount({ address: minter });
338
+ }
339
+ }
340
+ async #getBorrower() {
341
+ if (!this.#borrower) {
342
+ this.#borrower = privateKeyToAccount(generatePrivateKey());
343
+ await this.#anvil.setBalance({
344
+ address: this.#borrower.address,
345
+ value: parseEther("100")
346
+ });
347
+ this.#logger?.info(`created borrower ${this.#borrower.address}`);
348
+ }
349
+ return this.#borrower;
350
+ }
351
+ #getCollateralQuota(cm, collateral, amount, debt) {
352
+ const { underlying, collateralTokens } = cm;
353
+ const inUnderlying = collateral.toLowerCase() === underlying.toLowerCase();
354
+ if (inUnderlying) {
355
+ return [];
356
+ }
357
+ const collateralLT = BigInt(collateralTokens[collateral]);
358
+ const market = this.sdk.marketRegister.findByCreditManager(
359
+ cm.creditManager.address
360
+ );
361
+ const quotaInfo = market.pool.pqk.quotas.mustGet(collateral);
362
+ const availableQuota = quotaInfo.limit - quotaInfo.totalQuoted;
363
+ if (availableQuota <= 0n) {
364
+ throw new Error(
365
+ `quota exceeded for asset ${this.labelAddress(collateral)} in ${cm.name}`
366
+ );
367
+ }
368
+ const desiredQuota = this.#calcQuota(amount, debt, collateralLT);
369
+ return [
370
+ {
371
+ token: collateral,
372
+ balance: desiredQuota < availableQuota ? desiredQuota : availableQuota
373
+ }
374
+ ];
375
+ }
376
+ #calcQuota(amount, debt, lt) {
377
+ let quota = amount * lt / PERCENTAGE_FACTOR;
378
+ quota = debt < quota ? debt : quota;
379
+ quota = quota * (PERCENTAGE_FACTOR + 500n) / PERCENTAGE_FACTOR;
380
+ return quota / PERCENTAGE_FACTOR * PERCENTAGE_FACTOR;
381
+ }
382
+ };
5
383
  async function calcLiquidatableLTs(sdk, ca, factor = 9990n, logger) {
6
384
  const cm = sdk.marketRegister.findCreditManager(ca.creditManager);
7
385
  const market = sdk.marketRegister.findByCreditManager(ca.creditManager);
@@ -40,51 +418,242 @@ async function calcLiquidatableLTs(sdk, ca, factor = 9990n, logger) {
40
418
  if (token !== ca.underlying) {
41
419
  const newLT = oldLT * k / WAD;
42
420
  logger?.debug(
43
- `proposed ${sdk.marketRegister.tokensMeta.mustGet(token).symbol} LT change: ${oldLT} => ${newLT} `
421
+ `proposed ${sdk.tokensMeta.symbol(token)} LT change: ${oldLT} => ${newLT} `
44
422
  );
45
423
  result[token] = Number(newLT);
46
424
  }
47
425
  }
48
426
  return result;
49
427
  }
50
- function createAnvilClient({
51
- chain,
52
- transport
53
- }) {
54
- return createTestClient(
55
- {
56
- chain,
57
- mode: "anvil",
58
- transport,
59
- cacheTime: 0
60
- }
61
- ).extend(publicActions).extend(walletActions).extend((client) => ({
62
- isAnvil: () => isAnvil(client),
63
- evmMineDetailed: (timestamp) => evmMineDetailed(client, timestamp)
64
- }));
65
- }
66
- async function isAnvil(client) {
67
- try {
68
- const resp = await client.request({
69
- method: "anvil_nodeInfo",
70
- params: []
71
- });
72
- return !!resp.currentBlockNumber;
73
- } catch {
74
- return false;
75
- }
76
- }
77
- async function evmMineDetailed(client, timestamp) {
78
- try {
79
- const [block] = await client.request({
80
- method: "evm_mine_detailed",
81
- params: [toHex(timestamp)]
82
- });
83
- return block;
84
- } catch {
85
- return void 0;
428
+
429
+ // src/dev/abi/iPriceFeedStore.ts
430
+ var iPriceFeedStoreAbi = [
431
+ {
432
+ type: "function",
433
+ name: "addPriceFeed",
434
+ inputs: [
435
+ { name: "priceFeed", type: "address", internalType: "address" },
436
+ { name: "stalenessPeriod", type: "uint32", internalType: "uint32" }
437
+ ],
438
+ outputs: [],
439
+ stateMutability: "nonpayable"
440
+ },
441
+ {
442
+ type: "function",
443
+ name: "allowPriceFeed",
444
+ inputs: [
445
+ { name: "token", type: "address", internalType: "address" },
446
+ { name: "priceFeed", type: "address", internalType: "address" }
447
+ ],
448
+ outputs: [],
449
+ stateMutability: "nonpayable"
450
+ },
451
+ {
452
+ type: "function",
453
+ name: "contractType",
454
+ inputs: [],
455
+ outputs: [{ name: "", type: "bytes32", internalType: "bytes32" }],
456
+ stateMutability: "view"
457
+ },
458
+ {
459
+ type: "function",
460
+ name: "forbidPriceFeed",
461
+ inputs: [
462
+ { name: "token", type: "address", internalType: "address" },
463
+ { name: "priceFeed", type: "address", internalType: "address" }
464
+ ],
465
+ outputs: [],
466
+ stateMutability: "nonpayable"
467
+ },
468
+ {
469
+ type: "function",
470
+ name: "getAllowanceTimestamp",
471
+ inputs: [
472
+ { name: "token", type: "address", internalType: "address" },
473
+ { name: "priceFeed", type: "address", internalType: "address" }
474
+ ],
475
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
476
+ stateMutability: "view"
477
+ },
478
+ {
479
+ type: "function",
480
+ name: "getKnownPriceFeeds",
481
+ inputs: [],
482
+ outputs: [{ name: "", type: "address[]", internalType: "address[]" }],
483
+ stateMutability: "view"
484
+ },
485
+ {
486
+ type: "function",
487
+ name: "getKnownTokens",
488
+ inputs: [],
489
+ outputs: [{ name: "", type: "address[]", internalType: "address[]" }],
490
+ stateMutability: "view"
491
+ },
492
+ {
493
+ type: "function",
494
+ name: "getPriceFeeds",
495
+ inputs: [{ name: "token", type: "address", internalType: "address" }],
496
+ outputs: [{ name: "", type: "address[]", internalType: "address[]" }],
497
+ stateMutability: "view"
498
+ },
499
+ {
500
+ type: "function",
501
+ name: "getStalenessPeriod",
502
+ inputs: [{ name: "priceFeed", type: "address", internalType: "address" }],
503
+ outputs: [{ name: "", type: "uint32", internalType: "uint32" }],
504
+ stateMutability: "view"
505
+ },
506
+ {
507
+ type: "function",
508
+ name: "getTokenPriceFeedsMap",
509
+ inputs: [],
510
+ outputs: [
511
+ {
512
+ name: "",
513
+ type: "tuple[]",
514
+ internalType: "struct ConnectedPriceFeed[]",
515
+ components: [
516
+ { name: "token", type: "address", internalType: "address" },
517
+ { name: "priceFeeds", type: "address[]", internalType: "address[]" }
518
+ ]
519
+ }
520
+ ],
521
+ stateMutability: "view"
522
+ },
523
+ {
524
+ type: "function",
525
+ name: "isAllowedPriceFeed",
526
+ inputs: [
527
+ { name: "token", type: "address", internalType: "address" },
528
+ { name: "priceFeed", type: "address", internalType: "address" }
529
+ ],
530
+ outputs: [{ name: "", type: "bool", internalType: "bool" }],
531
+ stateMutability: "view"
532
+ },
533
+ {
534
+ type: "function",
535
+ name: "owner",
536
+ inputs: [],
537
+ outputs: [{ name: "", type: "address", internalType: "address" }],
538
+ stateMutability: "view"
539
+ },
540
+ {
541
+ type: "function",
542
+ name: "setStalenessPeriod",
543
+ inputs: [
544
+ { name: "priceFeed", type: "address", internalType: "address" },
545
+ { name: "stalenessPeriod", type: "uint32", internalType: "uint32" }
546
+ ],
547
+ outputs: [],
548
+ stateMutability: "nonpayable"
549
+ },
550
+ {
551
+ type: "function",
552
+ name: "version",
553
+ inputs: [],
554
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
555
+ stateMutability: "view"
556
+ },
557
+ {
558
+ type: "event",
559
+ name: "AddPriceFeed",
560
+ inputs: [
561
+ {
562
+ name: "priceFeed",
563
+ type: "address",
564
+ indexed: false,
565
+ internalType: "address"
566
+ },
567
+ {
568
+ name: "stalenessPeriod",
569
+ type: "uint32",
570
+ indexed: false,
571
+ internalType: "uint32"
572
+ }
573
+ ],
574
+ anonymous: false
575
+ },
576
+ {
577
+ type: "event",
578
+ name: "AllowPriceFeed",
579
+ inputs: [
580
+ {
581
+ name: "token",
582
+ type: "address",
583
+ indexed: false,
584
+ internalType: "address"
585
+ },
586
+ {
587
+ name: "priceFeed",
588
+ type: "address",
589
+ indexed: false,
590
+ internalType: "address"
591
+ }
592
+ ],
593
+ anonymous: false
594
+ },
595
+ {
596
+ type: "event",
597
+ name: "ForbidPriceFeed",
598
+ inputs: [
599
+ {
600
+ name: "token",
601
+ type: "address",
602
+ indexed: false,
603
+ internalType: "address"
604
+ },
605
+ {
606
+ name: "priceFeed",
607
+ type: "address",
608
+ indexed: false,
609
+ internalType: "address"
610
+ }
611
+ ],
612
+ anonymous: false
613
+ },
614
+ {
615
+ type: "event",
616
+ name: "SetStalenessPeriod",
617
+ inputs: [
618
+ {
619
+ name: "priceFeed",
620
+ type: "address",
621
+ indexed: false,
622
+ internalType: "address"
623
+ },
624
+ {
625
+ name: "stalenessPeriod",
626
+ type: "uint32",
627
+ indexed: false,
628
+ internalType: "uint32"
629
+ }
630
+ ],
631
+ anonymous: false
632
+ },
633
+ {
634
+ type: "error",
635
+ name: "CallerIsNotOwnerException",
636
+ inputs: [{ name: "caller", type: "address", internalType: "address" }]
637
+ },
638
+ {
639
+ type: "error",
640
+ name: "PriceFeedAlreadyAddedException",
641
+ inputs: [{ name: "priceFeed", type: "address", internalType: "address" }]
642
+ },
643
+ {
644
+ type: "error",
645
+ name: "PriceFeedIsNotAllowedException",
646
+ inputs: [
647
+ { name: "token", type: "address", internalType: "address" },
648
+ { name: "priceFeed", type: "address", internalType: "address" }
649
+ ]
650
+ },
651
+ {
652
+ type: "error",
653
+ name: "PriceFeedNotKnownException",
654
+ inputs: [{ name: "priceFeed", type: "address", internalType: "address" }]
86
655
  }
87
- }
656
+ ];
88
657
 
89
658
  // src/dev/abi/v3.ts
90
659
  var iaclAbi = [
@@ -186,6 +755,21 @@ var iaclAbi = [
186
755
  name: "AddressNotUnpausableAdminException"
187
756
  }
188
757
  ];
758
+ var iaclTraitAbi = [
759
+ {
760
+ type: "function",
761
+ name: "acl",
762
+ inputs: [],
763
+ outputs: [
764
+ {
765
+ name: "",
766
+ type: "address",
767
+ internalType: "address"
768
+ }
769
+ ],
770
+ stateMutability: "view"
771
+ }
772
+ ];
189
773
  var iCreditConfiguratorV3Abi = [
190
774
  {
191
775
  type: "function",
@@ -1448,18 +2032,219 @@ var iCreditManagerV3Abi = [
1448
2032
  }
1449
2033
  ];
1450
2034
 
1451
- // src/dev/setLTs.ts
1452
- async function setLTs(sdk, cm, newLTs, logger) {
1453
- const aclAddr = sdk.addressProvider.getLatestVersion(AP_ACL);
1454
- const configuratorAddr = await sdk.provider.publicClient.readContract({
2035
+ // src/dev/PriceFeedStore.ts
2036
+ var PriceFeedStore = class extends SDKConstruct {
2037
+ #store;
2038
+ #compressor;
2039
+ #logger;
2040
+ constructor(sdk) {
2041
+ super(sdk);
2042
+ this.#store = this.sdk.addressProvider.getAddress("PRICE_FEED_STORE");
2043
+ this.#compressor = this.sdk.addressProvider.getLatestVersion(
2044
+ AP_PRICE_FEED_COMPRESSOR
2045
+ );
2046
+ this.#logger = sdk.logger?.child?.({
2047
+ module: "PriceFeedStore"
2048
+ });
2049
+ }
2050
+ async load(update = true) {
2051
+ const pfMap = await this.provider.publicClient.readContract({
2052
+ address: this.#store,
2053
+ abi: iPriceFeedStoreAbi,
2054
+ functionName: "getTokenPriceFeedsMap"
2055
+ });
2056
+ const addresses = pfMap.flatMap((f) => f.priceFeeds);
2057
+ const nodes = await this.#loadFromCompressor(addresses, update);
2058
+ const result = new AddressMap();
2059
+ for (const { token, priceFeeds } of pfMap) {
2060
+ result.upsert(
2061
+ token,
2062
+ priceFeeds.map((pf) => nodes.find((n) => n.baseParams.addr === pf)).filter(Boolean)
2063
+ );
2064
+ }
2065
+ return result;
2066
+ }
2067
+ async #loadFromCompressor(priceFeeds, update = true) {
2068
+ let result = await this.provider.publicClient.readContract({
2069
+ address: this.#compressor,
2070
+ abi: iPriceFeedCompressorAbi,
2071
+ functionName: "loadPriceFeedTree",
2072
+ args: [priceFeeds]
2073
+ });
2074
+ if (update) {
2075
+ const feeds = result.map((f) => this.sdk.priceFeeds.create(f));
2076
+ const { txs } = await this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(feeds);
2077
+ const resp = await this.provider.publicClient.multicall({
2078
+ contracts: [
2079
+ ...txs.map(rawTxToMulticallPriceUpdate),
2080
+ {
2081
+ address: this.#compressor,
2082
+ abi: iPriceFeedCompressorAbi,
2083
+ functionName: "loadPriceFeedTree",
2084
+ args: [priceFeeds]
2085
+ }
2086
+ ],
2087
+ allowFailure: false
2088
+ });
2089
+ result = resp.pop();
2090
+ }
2091
+ this.#logger?.debug(
2092
+ `loaded ${result.length} price feed nodes from compressor`
2093
+ );
2094
+ return [...result];
2095
+ }
2096
+ };
2097
+ var SDKExample = class {
2098
+ #sdk;
2099
+ #logger;
2100
+ constructor(logger) {
2101
+ this.#logger = logger;
2102
+ }
2103
+ async run(opts) {
2104
+ const {
2105
+ addressProvider: ap,
2106
+ addressProviderJson,
2107
+ marketConfigurators,
2108
+ anvilUrl = "http://127.0.0.1:8545",
2109
+ outFile
2110
+ } = opts;
2111
+ const addressProvider = await this.#readConfigAddress(
2112
+ "addressProvider",
2113
+ ap,
2114
+ addressProviderJson
2115
+ );
2116
+ this.#sdk = await GearboxSDK.attach({
2117
+ rpcURLs: [anvilUrl],
2118
+ timeout: 48e4,
2119
+ addressProvider,
2120
+ logger: this.#logger,
2121
+ ignoreUpdateablePrices: true,
2122
+ marketConfigurators
2123
+ });
2124
+ await this.#safeMigrateFaucet(addressProvider);
2125
+ const puTx = await this.#sdk.priceFeeds.getUpdatePriceFeedsTx(marketConfigurators);
2126
+ const updater = createWalletClient({
2127
+ account: privateKeyToAccount(
2128
+ "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
2129
+ // well-known anvil private key
2130
+ ),
2131
+ transport: http(anvilUrl)
2132
+ });
2133
+ const publicClient = createPublicClient({
2134
+ transport: http(anvilUrl)
2135
+ });
2136
+ const hash = await sendRawTx(updater, { tx: puTx });
2137
+ await publicClient.waitForTransactionReceipt({ hash });
2138
+ await this.#sdk.marketRegister.loadMarkets(marketConfigurators, true);
2139
+ try {
2140
+ await this.#sdk.marketRegister.loadZappers();
2141
+ } catch (e) {
2142
+ this.#logger?.error(`failed to load zappers: ${e}`);
2143
+ }
2144
+ this.#logger?.info("attached sdk");
2145
+ if (outFile) {
2146
+ try {
2147
+ await writeFile(
2148
+ outFile,
2149
+ json_stringify(this.#sdk.stateHuman()),
2150
+ "utf-8"
2151
+ );
2152
+ } catch (e) {
2153
+ this.#logger?.error(`failed to write to ${outFile}: ${e}`);
2154
+ }
2155
+ }
2156
+ }
2157
+ async #readConfigAddress(name, value, file) {
2158
+ let result = value;
2159
+ if (!result) {
2160
+ if (!file) {
2161
+ throw new Error(`${name} is not specified`);
2162
+ }
2163
+ this.#logger?.debug(`reading ${name} json ${file}`);
2164
+ const apFile = await readFile(file, "utf-8").then(JSON.parse);
2165
+ result = apFile[name];
2166
+ }
2167
+ if (!result) {
2168
+ throw new Error(`${name} is not specified`);
2169
+ }
2170
+ if (!isAddress(result)) {
2171
+ throw new Error(`${name} is not a valid address: ${result}`);
2172
+ }
2173
+ this.#logger?.info(`using ${name} ${result}`);
2174
+ return result;
2175
+ }
2176
+ async #safeMigrateFaucet(addressProvider) {
2177
+ try {
2178
+ await this.#migrateFaucet(addressProvider);
2179
+ this.#logger?.info("faucet migrated successfully");
2180
+ } catch (e) {
2181
+ this.#logger?.error(`faucet migration failed: ${e}`);
2182
+ }
2183
+ }
2184
+ /**
2185
+ * Migrates faucet from address provider v3 to v3.1
2186
+ * @param addressProvider 3.1 address provider
2187
+ */
2188
+ async #migrateFaucet(addressProvider) {
2189
+ const anvil = createAnvilClient({
2190
+ chain: this.sdk.provider.chain,
2191
+ transport: this.sdk.provider.transport
2192
+ });
2193
+ const [faucetAddr, owner] = await anvil.multicall({
2194
+ contracts: [
2195
+ {
2196
+ abi: iAddressProviderV3Abi,
2197
+ address: ADDRESS_PROVIDER[this.sdk.provider.networkType],
2198
+ functionName: "getAddressOrRevert",
2199
+ args: [stringToHex("FAUCET", { size: 32 }), 0n]
2200
+ },
2201
+ {
2202
+ abi: iAddressProviderV3_1Abi,
2203
+ address: addressProvider,
2204
+ functionName: "owner",
2205
+ args: []
2206
+ }
2207
+ ],
2208
+ allowFailure: false
2209
+ });
2210
+ this.#logger?.debug(`faucet address: ${faucetAddr}, owner: ${owner}`);
2211
+ await anvil.impersonateAccount({ address: owner });
2212
+ await anvil.setBalance({
2213
+ address: owner,
2214
+ value: parseEther("100")
2215
+ });
2216
+ const hash = await anvil.writeContract({
2217
+ chain: anvil.chain,
2218
+ account: owner,
2219
+ address: addressProvider,
2220
+ abi: iAddressProviderV3_1Abi,
2221
+ functionName: "setAddress",
2222
+ args: [stringToHex("FAUCET", { size: 32 }), faucetAddr, true]
2223
+ });
2224
+ const receipt = await anvil.waitForTransactionReceipt({ hash });
2225
+ await anvil.stopImpersonatingAccount({ address: owner });
2226
+ if (receipt.status === "reverted") {
2227
+ throw new Error("faucet migration reverted");
2228
+ }
2229
+ }
2230
+ get sdk() {
2231
+ if (!this.#sdk) {
2232
+ throw new Error("sdk is not attached");
2233
+ }
2234
+ return this.#sdk;
2235
+ }
2236
+ };
2237
+ async function setLTs(anvil, cm, newLTs, logger) {
2238
+ const aclAddr = await anvil.readContract({
2239
+ address: cm.creditConfigurator,
2240
+ abi: iaclTraitAbi,
2241
+ functionName: "acl"
2242
+ });
2243
+ const configuratorAddr = await anvil.readContract({
1455
2244
  address: aclAddr,
1456
2245
  abi: iaclAbi,
1457
2246
  functionName: "owner"
1458
2247
  });
1459
- const anvil = createAnvilClient({
1460
- transport: sdk.provider.transport,
1461
- chain: sdk.provider.chain
1462
- });
1463
2248
  await anvil.impersonateAccount({
1464
2249
  address: configuratorAddr
1465
2250
  });
@@ -1468,27 +2253,114 @@ async function setLTs(sdk, cm, newLTs, logger) {
1468
2253
  value: parseEther("100")
1469
2254
  });
1470
2255
  for (const [t, lt] of Object.entries(newLTs)) {
1471
- await anvil.writeContract({
1472
- chain: anvil.chain,
1473
- address: cm.creditConfigurator.address,
1474
- account: configuratorAddr,
1475
- abi: iCreditConfiguratorV3Abi,
1476
- functionName: "setLiquidationThreshold",
1477
- args: [t, lt]
1478
- });
1479
- const newLT = await anvil.readContract({
1480
- address: cm.creditManager.address,
1481
- abi: iCreditManagerV3Abi,
1482
- functionName: "liquidationThresholds",
1483
- args: [t]
1484
- });
1485
- logger?.debug(
1486
- `set ${sdk.marketRegister.tokensMeta.mustGet(t).symbol} LT to ${newLT}`
1487
- );
2256
+ try {
2257
+ await anvil.writeContract({
2258
+ chain: anvil.chain,
2259
+ address: cm.creditConfigurator,
2260
+ account: configuratorAddr,
2261
+ abi: iCreditConfiguratorV3Abi,
2262
+ functionName: "setLiquidationThreshold",
2263
+ args: [t, lt]
2264
+ });
2265
+ const newLT = await anvil.readContract({
2266
+ address: cm.address,
2267
+ abi: iCreditManagerV3Abi,
2268
+ functionName: "liquidationThresholds",
2269
+ args: [t]
2270
+ });
2271
+ logger?.debug(`set ${t} LT to ${newLT}`);
2272
+ } catch {
2273
+ }
1488
2274
  }
1489
2275
  await anvil.stopImpersonatingAccount({
1490
2276
  address: configuratorAddr
1491
2277
  });
1492
2278
  }
2279
+ async function setLTZero(anvil, cm, logger) {
2280
+ const aclAddr = await anvil.readContract({
2281
+ address: cm.creditConfigurator,
2282
+ abi: iaclTraitAbi,
2283
+ functionName: "acl"
2284
+ });
2285
+ const configuratorAddr = await anvil.readContract({
2286
+ address: aclAddr,
2287
+ abi: iaclAbi,
2288
+ functionName: "owner"
2289
+ });
2290
+ await anvil.impersonateAccount({
2291
+ address: configuratorAddr
2292
+ });
2293
+ await anvil.setBalance({
2294
+ address: configuratorAddr,
2295
+ value: parseEther("100")
2296
+ });
2297
+ let hash = await anvil.writeContract({
2298
+ chain: anvil.chain,
2299
+ address: cm.creditConfigurator,
2300
+ account: configuratorAddr,
2301
+ abi: iCreditConfiguratorV3Abi,
2302
+ functionName: "setFees",
2303
+ args: [
2304
+ cm.feeInterest,
2305
+ cm.liquidationDiscount - 1,
2306
+ Number(PERCENTAGE_FACTOR) - cm.liquidationDiscount,
2307
+ cm.feeLiquidationExpired,
2308
+ cm.liquidationDiscountExpired
2309
+ ]
2310
+ });
2311
+ await anvil.waitForTransactionReceipt({ hash });
2312
+ logger?.debug(`[${cm.name}] setFees part 2`);
2313
+ hash = await anvil.writeContract({
2314
+ chain: anvil.chain,
2315
+ address: cm.creditConfigurator,
2316
+ account: configuratorAddr,
2317
+ abi: iCreditConfiguratorV3Abi,
2318
+ functionName: "setFees",
2319
+ args: [
2320
+ cm.feeInterest,
2321
+ cm.feeLiquidation,
2322
+ Number(PERCENTAGE_FACTOR) - cm.liquidationDiscount,
2323
+ cm.feeLiquidationExpired,
2324
+ cm.liquidationDiscountExpired
2325
+ ]
2326
+ });
2327
+ await anvil.waitForTransactionReceipt({ hash });
2328
+ logger?.debug(`[${cm.name}] setFees done`);
2329
+ await anvil.impersonateAccount({
2330
+ address: cm.creditConfigurator
2331
+ });
2332
+ await anvil.setBalance({
2333
+ address: cm.creditConfigurator,
2334
+ value: parseEther("100")
2335
+ });
2336
+ logger?.debug(
2337
+ `[${cm.name}] impresonating creditConfigurator ${cm.creditConfigurator}`
2338
+ );
2339
+ logger?.debug(`[${cm.name}] setting liquidation threshold`);
2340
+ hash = await anvil.writeContract({
2341
+ chain: anvil.chain,
2342
+ address: cm.baseParams.addr,
2343
+ account: cm.creditConfigurator,
2344
+ abi: iCreditManagerV3Abi,
2345
+ functionName: "setCollateralTokenData",
2346
+ args: [cm.underlying, 1, 1, Number(2n ** 40n - 1n), 0]
2347
+ });
2348
+ await anvil.waitForTransactionReceipt({ hash });
2349
+ logger?.debug(`[${cm.name}] setting configurator ${cm.creditConfigurator}`);
2350
+ hash = await anvil.writeContract({
2351
+ chain: anvil.chain,
2352
+ address: cm.baseParams.addr,
2353
+ account: cm.creditConfigurator,
2354
+ abi: iCreditManagerV3Abi,
2355
+ functionName: "setCreditConfigurator",
2356
+ args: [cm.creditConfigurator]
2357
+ });
2358
+ await anvil.waitForTransactionReceipt({ hash });
2359
+ logger?.debug(`[${cm.name}] done`);
2360
+ await anvil.stopImpersonatingAccount({
2361
+ address: cm.creditConfigurator
2362
+ });
2363
+ await anvil.stopImpersonatingAccount({ address: configuratorAddr });
2364
+ }
1493
2365
 
1494
- export { calcLiquidatableLTs, createAnvilClient, evmMineDetailed, isAnvil, setLTs };
2366
+ export { AccountOpener, PriceFeedStore, SDKExample, anvilNodeInfo, calcLiquidatableLTs, createAnvilClient, evmMineDetailed, isAnvil, setLTZero, setLTs };