Skip to content

Commit d36ea01

Browse files
committed
fix: check quota limits when opening accounts
1 parent c618c5c commit d36ea01

File tree

9 files changed

+75
-53
lines changed

9 files changed

+75
-53
lines changed

src/dev/AccountOpener.ts

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import type {
77
CreditAccountData,
88
CreditAccountsService,
99
CreditSuite,
10-
GearboxSDK,
1110
ILogger,
1211
} from "../sdk";
1312
import {
@@ -19,6 +18,7 @@ import {
1918
ierc20Abi,
2019
MAX_UINT256,
2120
PERCENTAGE_FACTOR,
21+
SDKConstruct,
2222
sendRawTx,
2323
} from "../sdk";
2424
import { type AnvilClient, createAnvilClient } from "./createAnvilClient";
@@ -34,7 +34,7 @@ export interface TargetAccount {
3434
slippage?: number;
3535
}
3636

37-
export class AccountOpener {
37+
export class AccountOpener extends SDKConstruct {
3838
#service: CreditAccountsService;
3939
#anvil: AnvilClient;
4040
#logger?: ILogger;
@@ -45,6 +45,7 @@ export class AccountOpener {
4545
service: CreditAccountsService,
4646
options: AccountOpenerOptions = {},
4747
) {
48+
super(service.sdk);
4849
this.#service = service;
4950
this.#logger = childLogger("AccountOpener", service.sdk.logger);
5051
this.#anvil = createAnvilClient({
@@ -135,26 +136,23 @@ export class AccountOpener {
135136
});
136137
logger?.debug(strategy, "found open strategy");
137138
const debt = minDebt * BigInt(leverage - 1);
138-
const collateralLT = BigInt(cm.collateralTokens[collateral]);
139-
const inUnderlying = collateral.toLowerCase() === underlying.toLowerCase();
139+
const averageQuota = this.#getCollateralQuota(
140+
cm,
141+
collateral,
142+
strategy.amount,
143+
debt,
144+
);
145+
const minQuota = this.#getCollateralQuota(
146+
cm,
147+
collateral,
148+
strategy.minAmount,
149+
debt,
150+
);
151+
logger?.debug({ averageQuota, minQuota }, "calculated quotas");
140152
const { tx, calls } = await this.#service.openCA({
141153
creditManager: cm.creditManager.address,
142-
averageQuota: inUnderlying
143-
? []
144-
: [
145-
{
146-
token: collateral,
147-
balance: this.#calcQuota(strategy.amount, debt, collateralLT),
148-
},
149-
],
150-
minQuota: inUnderlying
151-
? []
152-
: [
153-
{
154-
token: collateral,
155-
balance: this.#calcQuota(strategy.minAmount, debt, collateralLT),
156-
},
157-
],
154+
averageQuota,
155+
minQuota,
158156
collateral: [{ token: underlying, balance: minDebt }],
159157
debt,
160158
calls: strategy.calls,
@@ -369,6 +367,38 @@ export class AccountOpener {
369367
return this.#borrower;
370368
}
371369

370+
#getCollateralQuota(
371+
cm: CreditSuite,
372+
collateral: Address,
373+
amount: bigint,
374+
debt: bigint,
375+
): Asset[] {
376+
const { underlying, collateralTokens } = cm;
377+
const inUnderlying = collateral.toLowerCase() === underlying.toLowerCase();
378+
if (inUnderlying) {
379+
return [];
380+
}
381+
const collateralLT = BigInt(collateralTokens[collateral]);
382+
const market = this.sdk.marketRegister.findByCreditManager(
383+
cm.creditManager.address,
384+
);
385+
const quotaInfo = market.pool.pqk.quotas.mustGet(collateral);
386+
const availableQuota = quotaInfo.limit - quotaInfo.totalQuoted;
387+
if (availableQuota <= 0n) {
388+
throw new Error(
389+
`quota exceeded for asset ${this.labelAddress(collateral)} in ${cm.name}`,
390+
);
391+
}
392+
const desiredQuota = this.#calcQuota(amount, debt, collateralLT);
393+
394+
return [
395+
{
396+
token: collateral,
397+
balance: desiredQuota < availableQuota ? desiredQuota : availableQuota,
398+
},
399+
];
400+
}
401+
372402
#calcQuota(amount: bigint, debt: bigint, lt: bigint): bigint {
373403
let quota = (amount * lt) / PERCENTAGE_FACTOR;
374404
quota = debt < quota ? debt : quota;
@@ -377,8 +407,4 @@ export class AccountOpener {
377407

378408
return (quota / PERCENTAGE_FACTOR) * PERCENTAGE_FACTOR;
379409
}
380-
381-
private get sdk(): GearboxSDK {
382-
return this.#service.sdk;
383-
}
384410
}

src/sdk/accounts/CreditAccountsService.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -632,8 +632,8 @@ export class CreditAccountsService extends SDKConstruct {
632632
calls: openPathCalls,
633633
} = props;
634634

635-
const cmFactory = this.sdk.marketRegister.findCreditManager(creditManager);
636-
const cm = cmFactory.creditManager;
635+
const cmSuite = this.sdk.marketRegister.findCreditManager(creditManager);
636+
const cm = cmSuite.creditManager;
637637

638638
const priceUpdatesCalls = await this.getPriceUpdatesForFacade(
639639
cm.address,
@@ -652,14 +652,10 @@ export class CreditAccountsService extends SDKConstruct {
652652
: []),
653653
];
654654

655-
const tx = cmFactory.creditFacade.openCreditAccount(
656-
to,
657-
calls,
658-
referralCode,
659-
);
655+
const tx = cmSuite.creditFacade.openCreditAccount(to, calls, referralCode);
660656
tx.value = ethAmount.toString(10);
661657

662-
return { calls, tx, creditFacade: cmFactory.creditFacade };
658+
return { calls, tx, creditFacade: cmSuite.creditFacade };
663659
}
664660

665661
/**
@@ -727,7 +723,7 @@ export class CreditAccountsService extends SDKConstruct {
727723
const market = this.sdk.marketRegister.findByCreditManager(
728724
acc.creditManager,
729725
);
730-
const pool = market.poolFactory.pool.address;
726+
const pool = market.pool.pool.address;
731727
oracleByPool.set(pool, market.priceOracle);
732728

733729
for (const t of acc.tokens) {
@@ -740,9 +736,9 @@ export class CreditAccountsService extends SDKConstruct {
740736
}
741737
// priceFeeds can contain PriceFeeds from different markets
742738
const priceFeeds: Array<IPriceFeedContract> = [];
743-
for (const [pool, priceFeedFactory] of oracleByPool.entries()) {
739+
for (const [pool, oracle] of oracleByPool.entries()) {
744740
const tokens = Array.from(tokensByPool.get(pool) ?? []);
745-
priceFeeds.push(...priceFeedFactory.priceFeedsForTokens(tokens));
741+
priceFeeds.push(...oracle.priceFeedsForTokens(tokens));
746742
}
747743
return this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(priceFeeds);
748744
}
@@ -765,7 +761,7 @@ export class CreditAccountsService extends SDKConstruct {
765761
const market = this.sdk.marketRegister.findByCreditManager(creditManager);
766762
const cm =
767763
this.sdk.marketRegister.findCreditManager(creditManager).creditManager;
768-
const pool = market.poolFactory.pool.address;
764+
const pool = market.pool.pool.address;
769765

770766
oracleByPool.set(pool, market.priceOracle);
771767

src/sdk/market/MarketRegister.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { simulateMulticall } from "../utils/viem";
1111
import type { CreditSuite } from "./CreditSuite";
1212
import type { MarketConfiguratorContract } from "./MarketConfiguratorContract";
1313
import { MarketSuite } from "./MarketSuite";
14-
import type { PoolFactory } from "./PoolFactory";
14+
import type { PoolSuite } from "./PoolSuite";
1515
import { rawTxToMulticallPriceUpdate } from "./pricefeeds";
1616

1717
export class MarketRegister extends SDKConstruct {
@@ -48,7 +48,7 @@ export class MarketRegister extends SDKConstruct {
4848
public async syncState(): Promise<void> {
4949
const pools = this.markets
5050
.filter(m => m.dirty)
51-
.map(m => m.poolFactory.pool.address);
51+
.map(m => m.pool.pool.address);
5252
if (pools.length) {
5353
this.#logger?.debug(`need to reload ${pools.length} markets`);
5454
await this.#loadMarkets([], pools);
@@ -148,8 +148,8 @@ export class MarketRegister extends SDKConstruct {
148148
return this.markets.map(market => market.stateHuman(raw));
149149
}
150150

151-
public get pools(): PoolFactory[] {
152-
return this.markets.map(market => market.poolFactory);
151+
public get pools(): PoolSuite[] {
152+
return this.markets.map(market => market.pool);
153153
}
154154

155155
public get creditManagers(): CreditSuite[] {

src/sdk/market/MarketSuite.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import type { GearboxSDK } from "../GearboxSDK";
66
import type { MarketStateHuman } from "../types";
77
import { CreditSuite } from "./CreditSuite";
88
import { MarketConfiguratorContract } from "./MarketConfiguratorContract";
9-
import { PoolFactory } from "./PoolFactory";
9+
import { PoolSuite } from "./PoolSuite";
1010
import { PriceOracleV300Contract } from "./PriceOracleV300Contract";
1111
import { PriceOracleV310Contract } from "./PriceOracleV310Contract";
1212

1313
export class MarketSuite extends SDKConstruct {
1414
public readonly acl: Address;
1515
public readonly configurator: MarketConfiguratorContract;
16-
public readonly poolFactory: PoolFactory;
16+
public readonly pool: PoolSuite;
1717
public readonly priceOracle:
1818
| PriceOracleV300Contract
1919
| PriceOracleV310Contract;
@@ -50,7 +50,7 @@ export class MarketSuite extends SDKConstruct {
5050
sdk.provider.addressLabels.set(t.addr as Address, t.symbol);
5151
}
5252

53-
this.poolFactory = new PoolFactory(sdk, marketData);
53+
this.pool = new PoolSuite(sdk, marketData);
5454
this.zappers = marketData.zappers;
5555

5656
for (let i = 0; i < marketData.creditManagers.length; i++) {
@@ -75,15 +75,15 @@ export class MarketSuite extends SDKConstruct {
7575
override get dirty(): boolean {
7676
return (
7777
this.configurator.dirty ||
78-
this.poolFactory.dirty ||
78+
this.pool.dirty ||
7979
this.priceOracle.dirty ||
8080
this.creditManagers.some(cm => cm.dirty)
8181
);
8282
}
8383

8484
public stateHuman(raw = true): MarketStateHuman {
8585
return {
86-
pool: this.poolFactory.stateHuman(raw),
86+
pool: this.pool.stateHuman(raw),
8787
creditManagers: this.creditManagers.map(cm => cm.stateHuman(raw)),
8888
priceOracle: this.priceOracle.stateHuman(raw),
8989
pausableAdmins: this.state.pausableAdmins.map(a => this.labelAddress(a)),
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import type { Address } from "viem";
22

33
import { type MarketData, SDKConstruct } from "../base";
44
import type { GearboxSDK } from "../GearboxSDK";
5-
import type { PoolFactoryStateHuman } from "../types";
5+
import type { PoolSuiteStateHuman } from "../types";
66
import { GaugeContract } from "./GaugeContract";
77
import { LinearModelContract } from "./LinearModelContract";
88
import { PoolContract } from "./PoolContract";
99
import { PoolQuotaKeeperContract } from "./PoolQuotaKeeperContract";
1010

11-
export class PoolFactory extends SDKConstruct {
11+
export class PoolSuite extends SDKConstruct {
1212
public readonly pool: PoolContract;
1313
public readonly pqk: PoolQuotaKeeperContract;
1414
public readonly gauge: GaugeContract;
@@ -50,7 +50,7 @@ export class PoolFactory extends SDKConstruct {
5050
);
5151
}
5252

53-
public stateHuman(raw = true): PoolFactoryStateHuman {
53+
public stateHuman(raw = true): PoolSuiteStateHuman {
5454
return {
5555
pool: this.pool.stateHuman(raw),
5656
poolQuotaKeeper: this.pqk.stateHuman(raw),

src/sdk/market/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ export * from "./LinearModelContract";
1111
export * from "./MarketRegister";
1212
export * from "./MarketSuite";
1313
export * from "./PoolContract";
14-
export * from "./PoolFactory";
1514
export * from "./PoolQuotaKeeperContract";
15+
export * from "./PoolSuite";
1616
export * from "./pricefeeds";
1717
export {
1818
IPriceOracleContract,

src/sdk/market/pricefeeds/PriceFeedsRegister.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export type PriceFeedRegisterHooks = {
5252

5353
/**
5454
* PriceFeedRegister acts as a chain-level cache to avoid creating multiple contract instances.
55-
* It's reused by PriceFeedFactory belonging to different markets
55+
* It's reused by PriceOracles belonging to different markets
5656
*
5757
**/
5858
export class PriceFeedRegister

src/sdk/market/pricefeeds/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { RawTx } from "../../types";
55

66
/**
77
* Helper method to convert our RawTx into viem's multicall format
8-
* Involves decoding what was previously encoded, but it's better than adding another method to PriceFeedFactory
8+
* Involves decoding what was previously encoded, but it's better than adding another method to PriceOracle
99
* @param tx
1010
* @returns
1111
*/

src/sdk/types/state-human.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ export interface LinearModelStateHuman extends BaseContractStateHuman {
180180
isBorrowingMoreU2Forbidden: boolean;
181181
}
182182

183-
export interface PoolFactoryStateHuman {
183+
export interface PoolSuiteStateHuman {
184184
pool: PoolStateHuman;
185185
poolQuotaKeeper: PoolQuotaKeeperStateHuman;
186186
linearModel?: LinearModelStateHuman;
@@ -193,7 +193,7 @@ export interface ZapperStateHuman extends BaseContractStateHuman {
193193
}
194194

195195
export interface MarketStateHuman {
196-
pool: PoolFactoryStateHuman;
196+
pool: PoolSuiteStateHuman;
197197
creditManagers: CreditSuiteStateHuman[];
198198
priceOracle: PriceOracleV3StateHuman;
199199
pausableAdmins: string[];

0 commit comments

Comments
 (0)