@strkfarm/sdk 1.0.56 → 1.0.58
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.
- package/dist/index.browser.global.js +265 -84
- package/dist/index.browser.mjs +260 -67
- package/dist/index.d.ts +56 -9
- package/dist/index.js +261 -71
- package/dist/index.mjs +261 -71
- package/package.json +1 -1
- package/src/node/deployer.ts +1 -1
- package/src/notifs/telegram.ts +0 -2
- package/src/strategies/base-strategy.ts +3 -23
- package/src/strategies/sensei.ts +13 -6
- package/src/strategies/universal-adapters/baseAdapter.ts +2 -1
- package/src/strategies/universal-adapters/vesu-adapter.ts +80 -10
- package/src/strategies/universal-strategy.ts +190 -31
- package/src/utils/cacheClass.ts +29 -0
- package/src/utils/oz-merkle.ts +0 -9
|
@@ -55,7 +55,6 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
55
55
|
config: VesuAdapterConfig;
|
|
56
56
|
networkConfig: IConfig | undefined;
|
|
57
57
|
pricer: PricerBase | undefined;
|
|
58
|
-
cache: Record<string, any> = {};
|
|
59
58
|
|
|
60
59
|
constructor(config: VesuAdapterConfig) {
|
|
61
60
|
super();
|
|
@@ -121,17 +120,19 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
121
120
|
denomination: this.formatAmountDenominationEnum(params.collateralAmount.denomination),
|
|
122
121
|
value: {
|
|
123
122
|
abs: uint256.bnToUint256(params.collateralAmount.value.abs.toWei()),
|
|
124
|
-
is_negative: params.collateralAmount.value.is_negative
|
|
123
|
+
is_negative: params.collateralAmount.value.abs.isZero() ? false: params.collateralAmount.value.is_negative
|
|
125
124
|
}
|
|
126
125
|
};
|
|
126
|
+
logger.verbose(`VesuAdapter::ConstructingModify::Collateral::${JSON.stringify(_collateral)}`)
|
|
127
127
|
const _debt = {
|
|
128
128
|
amount_type: this.formatAmountTypeEnum(params.debtAmount.amount_type),
|
|
129
129
|
denomination: this.formatAmountDenominationEnum(params.debtAmount.denomination),
|
|
130
130
|
value: {
|
|
131
131
|
abs: uint256.bnToUint256(params.debtAmount.value.abs.toWei()),
|
|
132
|
-
is_negative: params.debtAmount.value.is_negative
|
|
132
|
+
is_negative: params.debtAmount.value.abs.isZero() ? false: params.debtAmount.value.is_negative
|
|
133
133
|
}
|
|
134
134
|
};
|
|
135
|
+
logger.verbose(`VesuAdapter::ConstructingModify::Debt::${JSON.stringify(_debt)}`)
|
|
135
136
|
const singletonContract = new Contract(VesuSingletonAbi, this.VESU_SINGLETON.toString(), new RpcProvider({nodeUrl: ''}));
|
|
136
137
|
const call = singletonContract.populate('modify_position', {
|
|
137
138
|
params: {
|
|
@@ -182,13 +183,14 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
182
183
|
|
|
183
184
|
async getLTVConfig(config: IConfig) {
|
|
184
185
|
const CACHE_KEY = 'ltv_config';
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
const cacheData = this.getCache<number>(CACHE_KEY);
|
|
187
|
+
if (cacheData) {
|
|
188
|
+
return cacheData as number;
|
|
187
189
|
}
|
|
188
190
|
const output: any = await this.getVesuSingletonContract(config)
|
|
189
191
|
.call('ltv_config', [this.config.poolId.address, this.config.collateral.address.address, this.config.debt.address.address])
|
|
190
|
-
this.
|
|
191
|
-
return this.
|
|
192
|
+
this.setCache(CACHE_KEY, Number(output.max_ltv) / 1e18, 300000); // ttl: 5min
|
|
193
|
+
return this.getCache<number>(CACHE_KEY) as number;
|
|
192
194
|
}
|
|
193
195
|
|
|
194
196
|
async getPositions(config: IConfig): Promise<VaultPosition[]> {
|
|
@@ -197,8 +199,9 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
197
199
|
}
|
|
198
200
|
// { '0': { collateral_shares: 0n, nominal_debt: 0n }, '1': 0n, '2': 0n }
|
|
199
201
|
const CACHE_KEY = 'positions';
|
|
200
|
-
|
|
201
|
-
|
|
202
|
+
const cacheData = this.getCache<VaultPosition[]>(CACHE_KEY);
|
|
203
|
+
if (cacheData) {
|
|
204
|
+
return cacheData;
|
|
202
205
|
}
|
|
203
206
|
const output: any = await this.getVesuSingletonContract(config)
|
|
204
207
|
.call('position_unsafe', [
|
|
@@ -224,10 +227,77 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
224
227
|
usdValue: debtAmount.multipliedBy(token2Price.price).toNumber(),
|
|
225
228
|
remarks: "Debt"
|
|
226
229
|
}];
|
|
227
|
-
this.
|
|
230
|
+
this.setCache(CACHE_KEY, value, 60000); // ttl: 1min
|
|
228
231
|
return value;
|
|
229
232
|
}
|
|
230
233
|
|
|
234
|
+
async getCollateralization(config: IConfig): Promise<Omit<VaultPosition, 'amount'>[]> {
|
|
235
|
+
if (!this.pricer) {
|
|
236
|
+
throw new Error('Pricer is not initialized');
|
|
237
|
+
}
|
|
238
|
+
// { '0': bool, '1': 0n, '2': 0n }
|
|
239
|
+
const CACHE_KEY = 'collateralization';
|
|
240
|
+
const cacheData = this.getCache<Omit<VaultPosition, 'amount'>[]>(CACHE_KEY);
|
|
241
|
+
if (cacheData) {
|
|
242
|
+
return cacheData;
|
|
243
|
+
}
|
|
244
|
+
const output: any = await this.getVesuSingletonContract(config)
|
|
245
|
+
.call('check_collateralization_unsafe', [
|
|
246
|
+
this.config.poolId.address,
|
|
247
|
+
this.config.collateral.address.address,
|
|
248
|
+
this.config.debt.address.address,
|
|
249
|
+
this.config.vaultAllocator.address
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
// usd values
|
|
253
|
+
const collateralAmount = Web3Number.fromWei(output['1'].toString(), 18);
|
|
254
|
+
const debtAmount = Web3Number.fromWei(output['2'].toString(), 18);
|
|
255
|
+
const value = [{
|
|
256
|
+
token: this.config.collateral,
|
|
257
|
+
usdValue: collateralAmount.toNumber(),
|
|
258
|
+
remarks: "Collateral"
|
|
259
|
+
}, {
|
|
260
|
+
token: this.config.debt,
|
|
261
|
+
usdValue: debtAmount.toNumber(),
|
|
262
|
+
remarks: "Debt"
|
|
263
|
+
}];
|
|
264
|
+
this.setCache(CACHE_KEY, value, 60000); // ttl: 1min
|
|
265
|
+
return value;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async getAssetPrices() {
|
|
269
|
+
const collateralizationProm = this.getCollateralization(this.networkConfig!);
|
|
270
|
+
const positionsProm = this.getPositions(this.networkConfig!);
|
|
271
|
+
const ltvProm = this.getLTVConfig(this.networkConfig!);
|
|
272
|
+
|
|
273
|
+
const output = await Promise.all([collateralizationProm, positionsProm, ltvProm]);
|
|
274
|
+
const [collateralization, positions, ltv] = output;
|
|
275
|
+
|
|
276
|
+
const collateralTokenAmount = positions[0].amount;
|
|
277
|
+
const collateralUSDAmount = collateralization[0].usdValue;
|
|
278
|
+
const collateralPrice = collateralUSDAmount / collateralTokenAmount.toNumber();
|
|
279
|
+
|
|
280
|
+
const debtTokenAmount = positions[1].amount;
|
|
281
|
+
const debtUSDAmount = collateralization[1].usdValue;
|
|
282
|
+
const debtPrice = debtUSDAmount / debtTokenAmount.toNumber();
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
collateralTokenAmount,
|
|
286
|
+
collateralUSDAmount,
|
|
287
|
+
collateralPrice,
|
|
288
|
+
debtTokenAmount,
|
|
289
|
+
debtUSDAmount,
|
|
290
|
+
debtPrice,
|
|
291
|
+
ltv
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async getHealthFactor() {
|
|
296
|
+
const ltv = await this.getLTVConfig(this.networkConfig!);
|
|
297
|
+
const collateralisation = await this.getCollateralization(this.networkConfig!);
|
|
298
|
+
return collateralisation[0].usdValue * ltv / collateralisation[1].usdValue;
|
|
299
|
+
}
|
|
300
|
+
|
|
231
301
|
static async getVesuPools(
|
|
232
302
|
retry = 0
|
|
233
303
|
): Promise<VesuPoolsInfo> {
|
|
@@ -9,13 +9,17 @@ import UniversalVaultAbi from '../data/universal-vault.abi.json';
|
|
|
9
9
|
import ManagerAbi from '../data/vault-manager.abi.json';
|
|
10
10
|
import { ApproveCallParams, BaseAdapter, CommonAdapter, FlashloanCallParams, GenerateCallFn, LeafAdapterFn, ManageCall, VesuAdapter, VesuModifyPositionCallParams, VesuPools } from "./universal-adapters";
|
|
11
11
|
import { Global } from "@/global";
|
|
12
|
+
import { ERC20 } from "@/modules";
|
|
12
13
|
|
|
13
14
|
export interface UniversalStrategySettings {
|
|
14
15
|
manager: ContractAddr,
|
|
15
16
|
vaultAllocator: ContractAddr,
|
|
16
17
|
redeemRequestNFT: ContractAddr,
|
|
18
|
+
aumOracle: ContractAddr,
|
|
17
19
|
leafAdapters: LeafAdapterFn<any>[],
|
|
18
|
-
adapters: {id: string, adapter: BaseAdapter}[]
|
|
20
|
+
adapters: {id: string, adapter: BaseAdapter}[],
|
|
21
|
+
targetHealthFactor: number,
|
|
22
|
+
minHealthFactor: number
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
|
|
@@ -166,7 +170,7 @@ export class UniversalStrategy<
|
|
|
166
170
|
* Calculates the weighted average APY across all pools based on USD value.
|
|
167
171
|
* @returns {Promise<number>} The weighted average APY across all pools
|
|
168
172
|
*/
|
|
169
|
-
async netAPY(): Promise<number> {
|
|
173
|
+
async netAPY(): Promise<{ net: number, splits: { apy: number, id: string }[] }> {
|
|
170
174
|
const [vesuAdapter1, vesuAdapter2] = this.getVesuAdapters();
|
|
171
175
|
const pools = await VesuAdapter.getVesuPools();
|
|
172
176
|
const pool1 = pools.pools.find(p => vesuAdapter1.config.poolId.eqString(num.getHexString(p.id)));
|
|
@@ -182,20 +186,32 @@ export class UniversalStrategy<
|
|
|
182
186
|
// supplyApy: { value: '8057256029163289', decimals: 18 },
|
|
183
187
|
// defiSpringSupplyApr: { value: '46856062629264560', decimals: 18 },
|
|
184
188
|
// borrowApr: { value: '12167825982336000', decimals: 18 },
|
|
185
|
-
const collateral1APY = Number(collateralAsset1.supplyApy.value) / 1e18
|
|
189
|
+
const collateral1APY = Number(collateralAsset1.supplyApy.value) / 1e18;
|
|
186
190
|
const debt1APY = Number(debtAsset1.borrowApr.value) / 1e18;
|
|
187
|
-
const collateral2APY = Number(collateralAsset2.supplyApy.value) / 1e18
|
|
191
|
+
const collateral2APY = Number(collateralAsset2.supplyApy.value) / 1e18;
|
|
188
192
|
const debt2APY = Number(debtAsset2.borrowApr.value) / 1e18;
|
|
189
193
|
|
|
190
|
-
const apys = [collateral1APY, debt1APY, collateral2APY, debt2APY];
|
|
191
194
|
const positions = await this.getVaultPositions();
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
const
|
|
198
|
-
|
|
195
|
+
const weights = positions.map((p, index) => p.usdValue * (index % 2 == 0 ? 1 : -1));
|
|
196
|
+
const baseAPYs = [collateral1APY, debt1APY, collateral2APY, debt2APY];
|
|
197
|
+
assert(positions.length == baseAPYs.length, "Positions and APYs length mismatch");
|
|
198
|
+
const rewardAPYs = [Number(collateralAsset1.defiSpringSupplyApr.value) / 1e18, 0, Number(collateralAsset2.defiSpringSupplyApr.value) / 1e18, 0];
|
|
199
|
+
const baseAPY = this.computeAPY(baseAPYs, weights);
|
|
200
|
+
const rewardAPY = this.computeAPY(rewardAPYs, weights);
|
|
201
|
+
const apys = [...baseAPYs, ...rewardAPYs];
|
|
202
|
+
const netAPY = baseAPY + rewardAPY;
|
|
203
|
+
return { net: netAPY, splits: [{
|
|
204
|
+
apy: baseAPY, id: 'base'
|
|
205
|
+
}, {
|
|
206
|
+
apy: rewardAPY, id: 'defispring'
|
|
207
|
+
}] };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private computeAPY(apys: number[], weights: number[]) {
|
|
211
|
+
assert(apys.length === weights.length, "APYs and weights length mismatch");
|
|
212
|
+
const weightedSum = apys.reduce((acc, apy, i) => acc + apy * weights[i], 0);
|
|
213
|
+
const totalWeight = weights.reduce((acc, weight) => acc + weight, 0);
|
|
214
|
+
return weightedSum / totalWeight;
|
|
199
215
|
}
|
|
200
216
|
|
|
201
217
|
/**
|
|
@@ -219,23 +235,56 @@ export class UniversalStrategy<
|
|
|
219
235
|
};
|
|
220
236
|
}
|
|
221
237
|
|
|
222
|
-
async getAUM(): Promise<SingleTokenInfo> {
|
|
238
|
+
async getAUM(): Promise<{net: SingleTokenInfo, prevAum: Web3Number, splits: {id: string, aum: Web3Number}[]}> {
|
|
239
|
+
const currentAUM: bigint = await this.contract.call('aum', []) as bigint;
|
|
240
|
+
const lastReportTime = await this.contract.call('last_report_timestamp', []);
|
|
241
|
+
|
|
242
|
+
const token1Price = await this.pricer.getPrice(this.metadata.depositTokens[0].symbol);
|
|
243
|
+
|
|
244
|
+
// calculate actual aum
|
|
223
245
|
const [vesuAdapter1, vesuAdapter2] = this.getVesuAdapters();
|
|
224
246
|
const leg1AUM = await vesuAdapter1.getPositions(this.config);
|
|
225
247
|
const leg2AUM = await vesuAdapter2.getPositions(this.config);
|
|
226
248
|
|
|
227
|
-
const
|
|
249
|
+
const balance = await (new ERC20(this.config)).balanceOf(this.asset().address, this.metadata.additionalInfo.vaultAllocator, this.asset().decimals);
|
|
250
|
+
logger.verbose(`${this.getTag()} unused balance: ${balance}`);
|
|
228
251
|
|
|
229
252
|
const aumToken = leg1AUM[0].amount
|
|
230
253
|
.plus(leg2AUM[0].usdValue / token1Price.price)
|
|
231
254
|
.minus(leg1AUM[1].usdValue / token1Price.price)
|
|
232
|
-
.minus(leg2AUM[1].amount);
|
|
233
|
-
|
|
234
|
-
|
|
255
|
+
.minus(leg2AUM[1].amount).plus(balance);
|
|
256
|
+
logger.verbose(`${this.getTag()} Actual AUM: ${aumToken}`);
|
|
257
|
+
|
|
258
|
+
// calculate estimated growth from strk rewards
|
|
259
|
+
const netAPY = await this.netAPY();
|
|
260
|
+
const defispringAPY = netAPY.splits.find(s => s.id === 'defispring')?.apy || 0;
|
|
261
|
+
if (!defispringAPY) throw new Error('DefiSpring APY not found');
|
|
262
|
+
|
|
263
|
+
const timeDiff = (Math.round(Date.now() / 1000) - Number(lastReportTime));
|
|
264
|
+
const growthRate = timeDiff * defispringAPY / (365 * 24 * 60 * 60);
|
|
265
|
+
const prevAum = Web3Number.fromWei(currentAUM.toString(), this.asset().decimals);
|
|
266
|
+
const rewardAssets = prevAum.multipliedBy(growthRate);
|
|
267
|
+
logger.verbose(`${this.getTag()} DefiSpring AUM time difference: ${timeDiff}`);
|
|
268
|
+
logger.verbose(`${this.getTag()} Current AUM: ${currentAUM}`);
|
|
269
|
+
logger.verbose(`${this.getTag()} Net APY: ${JSON.stringify(netAPY)}`);
|
|
270
|
+
logger.verbose(`${this.getTag()} rewards AUM: ${rewardAssets}`);
|
|
271
|
+
|
|
272
|
+
const newAUM = aumToken.plus(rewardAssets);
|
|
273
|
+
logger.verbose(`${this.getTag()} New AUM: ${newAUM}`);
|
|
274
|
+
|
|
275
|
+
const net = {
|
|
235
276
|
tokenInfo: this.asset(),
|
|
236
|
-
amount:
|
|
237
|
-
usdValue:
|
|
238
|
-
}
|
|
277
|
+
amount: newAUM,
|
|
278
|
+
usdValue: newAUM.multipliedBy(token1Price.price).toNumber()
|
|
279
|
+
};
|
|
280
|
+
const splits = [{
|
|
281
|
+
id: 'finalised',
|
|
282
|
+
aum: aumToken
|
|
283
|
+
}, {
|
|
284
|
+
id: 'defispring',
|
|
285
|
+
aum: rewardAssets
|
|
286
|
+
}];
|
|
287
|
+
return { net, splits, prevAum };
|
|
239
288
|
}
|
|
240
289
|
|
|
241
290
|
getVesuAdapters() {
|
|
@@ -243,6 +292,8 @@ export class UniversalStrategy<
|
|
|
243
292
|
const vesuAdapter2 = this.getAdapter(UNIVERSAL_ADAPTERS.VESU_LEG2) as VesuAdapter;
|
|
244
293
|
vesuAdapter1.pricer = this.pricer;
|
|
245
294
|
vesuAdapter2.pricer = this.pricer;
|
|
295
|
+
vesuAdapter1.networkConfig = this.config;
|
|
296
|
+
vesuAdapter2.networkConfig = this.config;
|
|
246
297
|
|
|
247
298
|
return [vesuAdapter1, vesuAdapter2];
|
|
248
299
|
}
|
|
@@ -297,12 +348,14 @@ export class UniversalStrategy<
|
|
|
297
348
|
|
|
298
349
|
const output = [{
|
|
299
350
|
proofs: manage5Info.proofs,
|
|
300
|
-
manageCall: manageCall5
|
|
351
|
+
manageCall: manageCall5,
|
|
352
|
+
step: STEP2_ID
|
|
301
353
|
}];
|
|
302
354
|
if (approveAmount.gt(0)) {
|
|
303
355
|
output.unshift({
|
|
304
356
|
proofs: manage4Info.proofs,
|
|
305
|
-
manageCall: manageCall4
|
|
357
|
+
manageCall: manageCall4,
|
|
358
|
+
step: STEP1_ID
|
|
306
359
|
})
|
|
307
360
|
}
|
|
308
361
|
return output;
|
|
@@ -312,12 +365,114 @@ export class UniversalStrategy<
|
|
|
312
365
|
return `${UniversalStrategy.name}:${this.metadata.name}`;
|
|
313
366
|
}
|
|
314
367
|
|
|
368
|
+
async getVesuHealthFactors() {
|
|
369
|
+
return await Promise.all(this.getVesuAdapters().map(v => v.getHealthFactor()));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async computeRebalanceConditionAndReturnCalls(): Promise<Call[]> {
|
|
373
|
+
const vesuAdapters = this.getVesuAdapters();
|
|
374
|
+
const healthFactors = await this.getVesuHealthFactors();
|
|
375
|
+
const leg1HealthFactor = healthFactors[0];
|
|
376
|
+
const leg2HealthFactor = healthFactors[1];
|
|
377
|
+
logger.verbose(`${this.getTag()}: HealthFactorLeg1: ${leg1HealthFactor}`);
|
|
378
|
+
logger.verbose(`${this.getTag()}: HealthFactorLeg2: ${leg2HealthFactor}`);
|
|
379
|
+
|
|
380
|
+
const minHf = this.metadata.additionalInfo.minHealthFactor;
|
|
381
|
+
const isRebalanceNeeded1 = leg1HealthFactor < minHf;
|
|
382
|
+
const isRebalanceNeeded2 = leg2HealthFactor < minHf;
|
|
383
|
+
if (!isRebalanceNeeded1 && !isRebalanceNeeded2) {
|
|
384
|
+
return [];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (isRebalanceNeeded1) {
|
|
388
|
+
const amount = await this.getLegRebalanceAmount(vesuAdapters[0], leg1HealthFactor, false);
|
|
389
|
+
const leg2HF = await this.getNewHealthFactor(vesuAdapters[1], amount, true);
|
|
390
|
+
assert(leg2HF > minHf, `Rebalance Leg1 failed: Leg2 HF after rebalance would be too low: ${leg2HF}`);
|
|
391
|
+
return [await this.getRebalanceCall({
|
|
392
|
+
isLeg1toLeg2: false,
|
|
393
|
+
amount: amount
|
|
394
|
+
})];
|
|
395
|
+
} else {
|
|
396
|
+
const amount = await this.getLegRebalanceAmount(vesuAdapters[1], leg2HealthFactor, true);
|
|
397
|
+
const leg1HF = await this.getNewHealthFactor(vesuAdapters[0], amount, false);
|
|
398
|
+
assert(leg1HF > minHf, `Rebalance Leg2 failed: Leg1 HF after rebalance would be too low: ${leg1HF}`);
|
|
399
|
+
return [await this.getRebalanceCall({
|
|
400
|
+
isLeg1toLeg2: true,
|
|
401
|
+
amount: amount
|
|
402
|
+
})];
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private async getNewHealthFactor(vesuAdapter: VesuAdapter, newAmount: Web3Number, isWithdraw: boolean) {
|
|
407
|
+
const {
|
|
408
|
+
collateralTokenAmount,
|
|
409
|
+
collateralUSDAmount,
|
|
410
|
+
collateralPrice,
|
|
411
|
+
debtTokenAmount,
|
|
412
|
+
debtUSDAmount,
|
|
413
|
+
debtPrice,
|
|
414
|
+
ltv
|
|
415
|
+
} = await vesuAdapter.getAssetPrices();
|
|
416
|
+
|
|
417
|
+
if (isWithdraw) {
|
|
418
|
+
const newHF = ((collateralTokenAmount.toNumber() - newAmount.toNumber()) * collateralPrice * ltv) / debtUSDAmount;
|
|
419
|
+
logger.verbose(`getNewHealthFactor:: HF: ${newHF}, amoutn: ${newAmount.toNumber()}, isDeposit`);
|
|
420
|
+
return newHF;
|
|
421
|
+
} else { // is borrow
|
|
422
|
+
const newHF = (collateralUSDAmount * ltv) / ((debtTokenAmount.toNumber() + newAmount.toNumber()) * debtPrice);
|
|
423
|
+
logger.verbose(`getNewHealthFactor:: HF: ${newHF}, amoutn: ${newAmount.toNumber()}, isRepay`);
|
|
424
|
+
return newHF;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
*
|
|
430
|
+
* @param vesuAdapter
|
|
431
|
+
* @param currentHf
|
|
432
|
+
* @param isDeposit if true, attempt by adding collateral, else by repaying
|
|
433
|
+
* @returns
|
|
434
|
+
*/
|
|
435
|
+
private async getLegRebalanceAmount(vesuAdapter: VesuAdapter, currentHf: number, isDeposit: boolean) {
|
|
436
|
+
const {
|
|
437
|
+
collateralTokenAmount,
|
|
438
|
+
collateralUSDAmount,
|
|
439
|
+
collateralPrice,
|
|
440
|
+
debtTokenAmount,
|
|
441
|
+
debtUSDAmount,
|
|
442
|
+
debtPrice,
|
|
443
|
+
ltv
|
|
444
|
+
} = await vesuAdapter.getAssetPrices();
|
|
445
|
+
|
|
446
|
+
// debt is zero, nothing to rebalance
|
|
447
|
+
if(debtTokenAmount.isZero()) {
|
|
448
|
+
return Web3Number.fromWei(0, 0);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
assert(collateralPrice > 0 && debtPrice > 0, "getRebalanceAmount: Invalid price");
|
|
452
|
+
|
|
453
|
+
// avoid calculating for too close
|
|
454
|
+
const targetHF = this.metadata.additionalInfo.targetHealthFactor;
|
|
455
|
+
if (currentHf > targetHF - 0.01)
|
|
456
|
+
throw new Error("getLegRebalanceAmount: Current health factor is healthy");
|
|
457
|
+
|
|
458
|
+
if (isDeposit) {
|
|
459
|
+
// TargetHF = (collAmount + newAmount) * price * ltv / debtUSD
|
|
460
|
+
const newAmount = targetHF * debtUSDAmount / (collateralPrice * ltv) - collateralTokenAmount.toNumber();
|
|
461
|
+
logger.verbose(`${this.getTag()}:: getLegRebalanceAmount: addCollateral, currentHf: ${currentHf}, targetHF: ${targetHF}, collAmount: ${collateralTokenAmount.toString()}, collUSD: ${collateralUSDAmount}, collPrice: ${collateralPrice}, debtAmount: ${debtTokenAmount.toString()}, debtUSD: ${debtUSDAmount}, debtPrice: ${debtPrice}, ltv: ${ltv}, newAmount: ${newAmount}`);
|
|
462
|
+
return new Web3Number(newAmount.toFixed(8), collateralTokenAmount.decimals);
|
|
463
|
+
} else {
|
|
464
|
+
// TargetHF = collUSD * ltv / (debtAmount - newAmount) * debtPrice
|
|
465
|
+
const newAmount = debtTokenAmount.toNumber() - collateralUSDAmount * ltv / (targetHF * debtPrice);
|
|
466
|
+
logger.verbose(`${this.getTag()}:: getLegRebalanceAmount: repayDebt, currentHf: ${currentHf}, targetHF: ${targetHF}, collAmount: ${collateralTokenAmount.toString()}, collUSD: ${collateralUSDAmount}, collPrice: ${collateralPrice}, debtAmount: ${debtTokenAmount.toString()}, debtUSD: ${debtUSDAmount}, debtPrice: ${debtPrice}, ltv: ${ltv}, newAmount: ${newAmount}`);
|
|
467
|
+
return new Web3Number(newAmount.toFixed(8), debtTokenAmount.decimals);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
315
471
|
async getVesuMultiplyCall(params: {
|
|
316
472
|
isDeposit: boolean,
|
|
317
473
|
leg1DepositAmount: Web3Number
|
|
318
474
|
}) {
|
|
319
|
-
const vesuAdapter1 = this.
|
|
320
|
-
const vesuAdapter2 = this.getAdapter(UNIVERSAL_ADAPTERS.VESU_LEG2) as VesuAdapter;
|
|
475
|
+
const [vesuAdapter1, vesuAdapter2] = this.getVesuAdapters();
|
|
321
476
|
const leg1LTV = await vesuAdapter1.getLTVConfig(this.config);
|
|
322
477
|
const leg2LTV = await vesuAdapter2.getLTVConfig(this.config);
|
|
323
478
|
logger.verbose(`${this.getTag()}: LTVLeg1: ${leg1LTV}`);
|
|
@@ -328,7 +483,7 @@ export class UniversalStrategy<
|
|
|
328
483
|
logger.verbose(`${this.getTag()}: Price${vesuAdapter1.config.collateral.symbol}: ${token1Price.price}`);
|
|
329
484
|
logger.verbose(`${this.getTag()}: Price${vesuAdapter2.config.collateral.symbol}: ${token2Price.price}`);
|
|
330
485
|
|
|
331
|
-
const TARGET_HF =
|
|
486
|
+
const TARGET_HF = this.metadata.additionalInfo.targetHealthFactor;
|
|
332
487
|
|
|
333
488
|
const k1 = token1Price.price * leg1LTV / token2Price.price / TARGET_HF;
|
|
334
489
|
const k2 = token1Price.price * TARGET_HF / token2Price.price / leg2LTV;
|
|
@@ -399,14 +554,12 @@ export class UniversalStrategy<
|
|
|
399
554
|
|
|
400
555
|
if (params.isLeg1toLeg2) {
|
|
401
556
|
const manageCall = this.getManageCall([
|
|
402
|
-
|
|
403
|
-
UNIVERSAL_MANAGE_IDS.VESU_LEG2
|
|
557
|
+
...callSet1.map(i => i.step), ...callSet2.map(i => i.step)
|
|
404
558
|
], [...callSet1.map(i => i.manageCall), ...callSet2.map(i => i.manageCall)]);
|
|
405
559
|
return manageCall;
|
|
406
560
|
} else {
|
|
407
561
|
const manageCall = this.getManageCall([
|
|
408
|
-
|
|
409
|
-
UNIVERSAL_MANAGE_IDS.VESU_LEG1
|
|
562
|
+
...callSet2.map(i => i.step), ...callSet1.map(i => i.step)
|
|
410
563
|
], [...callSet2.map(i => i.manageCall), ...callSet1.map(i => i.manageCall)]);
|
|
411
564
|
return manageCall;
|
|
412
565
|
}
|
|
@@ -484,16 +637,22 @@ const usdcVaultSettings: UniversalStrategySettings = {
|
|
|
484
637
|
manager: ContractAddr.from('0xf41a2b1f498a7f9629db0b8519259e66e964260a23d20003f3e42bb1997a07'),
|
|
485
638
|
vaultAllocator: ContractAddr.from('0x228cca1005d3f2b55cbaba27cb291dacf1b9a92d1d6b1638195fbd3d0c1e3ba'),
|
|
486
639
|
redeemRequestNFT: ContractAddr.from('0x906d03590010868cbf7590ad47043959d7af8e782089a605d9b22567b64fda'),
|
|
640
|
+
aumOracle: ContractAddr.from("0x6faf45ed185dec13ef723c9ead4266cab98d06f2cb237e331b1fa5c2aa79afe"),
|
|
487
641
|
leafAdapters: [],
|
|
488
|
-
adapters: []
|
|
642
|
+
adapters: [],
|
|
643
|
+
targetHealthFactor: 1.3,
|
|
644
|
+
minHealthFactor: 1.25
|
|
489
645
|
}
|
|
490
646
|
|
|
491
647
|
const wbtcVaultSettings: UniversalStrategySettings = {
|
|
492
648
|
manager: ContractAddr.from('0xef8a664ffcfe46a6af550766d27c28937bf1b77fb4ab54d8553e92bca5ba34'),
|
|
493
649
|
vaultAllocator: ContractAddr.from('0x1e01c25f0d9494570226ad28a7fa856c0640505e809c366a9fab4903320e735'),
|
|
494
650
|
redeemRequestNFT: ContractAddr.from('0x4fec59a12f8424281c1e65a80b5de51b4e754625c60cddfcd00d46941ec37b2'),
|
|
651
|
+
aumOracle: ContractAddr.from("0x2edf4edbed3f839e7f07dcd913e92299898ff4cf0ba532f8c572c66c5b331b2"),
|
|
495
652
|
leafAdapters: [],
|
|
496
|
-
adapters: []
|
|
653
|
+
adapters: [],
|
|
654
|
+
targetHealthFactor: 1.3,
|
|
655
|
+
minHealthFactor: 1.25
|
|
497
656
|
}
|
|
498
657
|
|
|
499
658
|
export const UniversalStrategies: IStrategyMetadata<UniversalStrategySettings>[] =
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
interface CacheData {
|
|
2
|
+
timestamp: number;
|
|
3
|
+
ttl: number;
|
|
4
|
+
data: any;
|
|
5
|
+
}
|
|
6
|
+
export class CacheClass {
|
|
7
|
+
readonly cache: Map<string, CacheData> = new Map();
|
|
8
|
+
|
|
9
|
+
setCache(key: string, data: any, ttl: number = 60000): void {
|
|
10
|
+
const timestamp = Date.now();
|
|
11
|
+
this.cache.set(key, { timestamp, ttl, data });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getCache<T>(key: string): T | null {
|
|
15
|
+
const cachedData = this.cache.get(key);
|
|
16
|
+
if (!cachedData || !this.isCacheValid(key)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return cachedData.data;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
isCacheValid(key: string): boolean {
|
|
23
|
+
const cachedData = this.cache.get(key);
|
|
24
|
+
if (!cachedData) return false;
|
|
25
|
+
|
|
26
|
+
const { timestamp, ttl } = cachedData;
|
|
27
|
+
return Date.now() - timestamp <= ttl;
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/utils/oz-merkle.ts
CHANGED
|
@@ -59,15 +59,6 @@ export class StandardMerkleTree extends MerkleTreeImpl<LeafData> {
|
|
|
59
59
|
return new StandardMerkleTree(tree, indexedValues, leafEncoding);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
static load(data: StandardMerkleTreeData<LeafData>): StandardMerkleTree {
|
|
63
|
-
validateArgument(data.format === 'standard-v1', `Unknown format '${data.format}'`);
|
|
64
|
-
validateArgument(data.leafEncoding !== undefined, 'Expected leaf encoding');
|
|
65
|
-
|
|
66
|
-
const tree = new StandardMerkleTree(data.tree, data.values, data.leafEncoding);
|
|
67
|
-
tree.validate();
|
|
68
|
-
return tree;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
62
|
static verify<T extends any[]>(root: BytesLike, leafEncoding: ValueType[], leaf: T, proof: BytesLike[]): boolean {
|
|
72
63
|
// use default nodeHash (standardNodeHash) for processProof
|
|
73
64
|
return toHex(root) === processProof(standardLeafHash(leafEncoding, leaf), proof);
|