curvance 4.1.2 → 5.1.0
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/README.md +65 -10
- package/dist/abis/OptimizerReader.json +43 -45
- package/dist/abis/ProtocolReader.json +279 -55
- package/dist/chains/arbitrum.js +3 -3
- package/dist/chains/arbitrum.js.map +1 -1
- package/dist/classes/Api.d.ts +2 -2
- package/dist/classes/Api.d.ts.map +1 -1
- package/dist/classes/Api.js +87 -14
- package/dist/classes/Api.js.map +1 -1
- package/dist/classes/BorrowableCToken.d.ts +9 -1
- package/dist/classes/BorrowableCToken.d.ts.map +1 -1
- package/dist/classes/BorrowableCToken.js +67 -12
- package/dist/classes/BorrowableCToken.js.map +1 -1
- package/dist/classes/CToken.d.ts +210 -21
- package/dist/classes/CToken.d.ts.map +1 -1
- package/dist/classes/CToken.js +692 -284
- package/dist/classes/CToken.js.map +1 -1
- package/dist/classes/Calldata.d.ts +2 -2
- package/dist/classes/Calldata.d.ts.map +1 -1
- package/dist/classes/Calldata.js +6 -2
- package/dist/classes/Calldata.js.map +1 -1
- package/dist/classes/DexAggregators/IDexAgg.d.ts +1 -1
- package/dist/classes/DexAggregators/Kuru.js +1 -1
- package/dist/classes/DexAggregators/Kuru.js.map +1 -1
- package/dist/classes/DexAggregators/KyberSwap.d.ts.map +1 -1
- package/dist/classes/DexAggregators/KyberSwap.js +112 -61
- package/dist/classes/DexAggregators/KyberSwap.js.map +1 -1
- package/dist/classes/DexAggregators/MultiDexAgg.d.ts +2 -2
- package/dist/classes/DexAggregators/MultiDexAgg.d.ts.map +1 -1
- package/dist/classes/DexAggregators/MultiDexAgg.js +30 -18
- package/dist/classes/DexAggregators/MultiDexAgg.js.map +1 -1
- package/dist/classes/DexAggregators/UnsupportedDexAgg.d.ts +19 -0
- package/dist/classes/DexAggregators/UnsupportedDexAgg.d.ts.map +1 -0
- package/dist/classes/DexAggregators/UnsupportedDexAgg.js +29 -0
- package/dist/classes/DexAggregators/UnsupportedDexAgg.js.map +1 -0
- package/dist/classes/DexAggregators/helpers.d.ts +30 -0
- package/dist/classes/DexAggregators/helpers.d.ts.map +1 -0
- package/dist/classes/DexAggregators/helpers.js +57 -0
- package/dist/classes/DexAggregators/helpers.js.map +1 -0
- package/dist/classes/DexAggregators/index.d.ts +2 -0
- package/dist/classes/DexAggregators/index.d.ts.map +1 -1
- package/dist/classes/DexAggregators/index.js +1 -0
- package/dist/classes/DexAggregators/index.js.map +1 -1
- package/dist/classes/ERC20.d.ts +3 -3
- package/dist/classes/ERC20.d.ts.map +1 -1
- package/dist/classes/ERC20.js +26 -10
- package/dist/classes/ERC20.js.map +1 -1
- package/dist/classes/FormatConverter.d.ts.map +1 -1
- package/dist/classes/FormatConverter.js +4 -1
- package/dist/classes/FormatConverter.js.map +1 -1
- package/dist/classes/Market.d.ts +46 -8
- package/dist/classes/Market.d.ts.map +1 -1
- package/dist/classes/Market.js +342 -111
- package/dist/classes/Market.js.map +1 -1
- package/dist/classes/NativeToken.d.ts +2 -2
- package/dist/classes/NativeToken.d.ts.map +1 -1
- package/dist/classes/NativeToken.js +21 -7
- package/dist/classes/NativeToken.js.map +1 -1
- package/dist/classes/OptimizerReader.d.ts +15 -7
- package/dist/classes/OptimizerReader.d.ts.map +1 -1
- package/dist/classes/OptimizerReader.js +108 -30
- package/dist/classes/OptimizerReader.js.map +1 -1
- package/dist/classes/OracleManager.d.ts.map +1 -1
- package/dist/classes/OracleManager.js +11 -4
- package/dist/classes/OracleManager.js.map +1 -1
- package/dist/classes/PositionManager.d.ts.map +1 -1
- package/dist/classes/PositionManager.js +1 -3
- package/dist/classes/PositionManager.js.map +1 -1
- package/dist/classes/ProtocolReader.d.ts +30 -9
- package/dist/classes/ProtocolReader.d.ts.map +1 -1
- package/dist/classes/ProtocolReader.js +158 -138
- package/dist/classes/ProtocolReader.js.map +1 -1
- package/dist/classes/Zapper.d.ts +5 -5
- package/dist/classes/Zapper.d.ts.map +1 -1
- package/dist/classes/Zapper.js +21 -18
- package/dist/classes/Zapper.js.map +1 -1
- package/dist/contracts/monad-mainnet.json +1 -1
- package/dist/feePolicy.d.ts +2 -0
- package/dist/feePolicy.d.ts.map +1 -1
- package/dist/feePolicy.js +19 -0
- package/dist/feePolicy.js.map +1 -1
- package/dist/format/borrow.d.ts.map +1 -1
- package/dist/format/borrow.js +7 -1
- package/dist/format/borrow.js.map +1 -1
- package/dist/format/health.d.ts +3 -2
- package/dist/format/health.d.ts.map +1 -1
- package/dist/format/health.js +10 -9
- package/dist/format/health.js.map +1 -1
- package/dist/format/leverage.js +5 -4
- package/dist/format/leverage.js.map +1 -1
- package/dist/helpers.d.ts +14 -11
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +171 -52
- package/dist/helpers.js.map +1 -1
- package/dist/integrations/merkl.d.ts +2 -1
- package/dist/integrations/merkl.d.ts.map +1 -1
- package/dist/integrations/merkl.js +199 -4
- package/dist/integrations/merkl.js.map +1 -1
- package/dist/integrations/snapshot.d.ts +28 -24
- package/dist/integrations/snapshot.d.ts.map +1 -1
- package/dist/integrations/snapshot.js +106 -42
- package/dist/integrations/snapshot.js.map +1 -1
- package/dist/retry-provider.d.ts +6 -0
- package/dist/retry-provider.d.ts.map +1 -1
- package/dist/retry-provider.js +103 -11
- package/dist/retry-provider.js.map +1 -1
- package/dist/setup.d.ts +8 -6
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +125 -45
- package/dist/setup.js.map +1 -1
- package/dist/validation.d.ts +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +13 -7
- package/dist/validation.js.map +1 -1
- package/package.json +6 -3
package/dist/classes/CToken.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.CToken = void 0;
|
|
6
|
+
exports.CToken = exports.LEVERAGE = void 0;
|
|
7
7
|
const helpers_1 = require("../helpers");
|
|
8
8
|
const ProtocolReader_1 = require("./ProtocolReader");
|
|
9
9
|
const ERC20_1 = require("./ERC20");
|
|
@@ -19,6 +19,7 @@ const FormatConverter_1 = __importDefault(require("./FormatConverter"));
|
|
|
19
19
|
const EXCLUDED_ZAP_SYMBOLS = new Set([
|
|
20
20
|
'eBTC', 'earnAUSD', 'vUSD', 'syzUSD', 'ezETH', 'YZM', 'wsrUSD', 'sAUSD',
|
|
21
21
|
]);
|
|
22
|
+
const EXECUTION_DEBT_BUFFER_TIME = 100n;
|
|
22
23
|
/**
|
|
23
24
|
* Leverage operation buffers — centralized for tuning.
|
|
24
25
|
* Calibrated for fresh-state operation via getLeverageSnapshot under
|
|
@@ -69,7 +70,7 @@ const EXCLUDED_ZAP_SYMBOLS = new Set([
|
|
|
69
70
|
* onRedeem lines 482-493), so the economic loss from the overshoot is
|
|
70
71
|
* zero — only the contract's naive equity-loss check sees it as loss.
|
|
71
72
|
*/
|
|
72
|
-
|
|
73
|
+
exports.LEVERAGE = {
|
|
73
74
|
/** Max leverage cap: fraction of theoretical max the user can select.
|
|
74
75
|
* Prevents boundary singularity at exact max leverage — the contract's
|
|
75
76
|
* post-op `canBorrow` check re-evaluates LTV against fresh on-chain
|
|
@@ -113,10 +114,44 @@ const LEVERAGE = {
|
|
|
113
114
|
* for that amplification (see leverageDown). Bump when aggregator fees
|
|
114
115
|
* are enabled to keep dust prevention reliable. */
|
|
115
116
|
DELEVERAGE_OVERHEAD_BPS: 20n,
|
|
116
|
-
/** BPS buffer on
|
|
117
|
+
/** BPS buffer on expected-share calculations for zap/leverage paths.
|
|
117
118
|
* Covers exchange rate drift from interest accrual since cache load. */
|
|
118
119
|
SHARES_BUFFER_BPS: 2n,
|
|
120
|
+
/** Per-leverage-unit BPS buffer for `checkSlippage` on vault + native-vault
|
|
121
|
+
* leverage-up paths. Absorbs the drift between the collateral vault's
|
|
122
|
+
* fundamental mint rate at tx time and the stored oracle price that
|
|
123
|
+
* `marketManager.statusOf` uses inside `checkSlippage`. The vault-token
|
|
124
|
+
* oracle publishes discretely; the vault's exchange rate accrues
|
|
125
|
+
* continuously — so new shares are minted at `r_current` but valued at
|
|
126
|
+
* `r_oracle`, leaving a (L-1)-amplified equity-fraction gap that the
|
|
127
|
+
* simple path doesn't see in practice (vault-token markets default to
|
|
128
|
+
* the vault/native-vault PM and `leverageDown` drift goes the other
|
|
129
|
+
* direction as a gain). Empirically calibrated against the ~3% user
|
|
130
|
+
* slippage failure threshold on shMON/WMON native-vault leverage-up;
|
|
131
|
+
* refine via fork testing if drift distribution turns out wider. The
|
|
132
|
+
* constant is NOT "feed divergence" — shMON oracle IS derived from
|
|
133
|
+
* p_MON × r_shMON off-chain; the gap is between publish-time snapshot
|
|
134
|
+
* and tx-time state, not between two independent feeds. */
|
|
135
|
+
LEVERAGE_UP_VAULT_DRIFT_BPS: 30n,
|
|
119
136
|
};
|
|
137
|
+
const USER_CACHE_FIELDS = [
|
|
138
|
+
"userAssetBalance",
|
|
139
|
+
"userShareBalance",
|
|
140
|
+
"userUnderlyingBalance",
|
|
141
|
+
"userCollateral",
|
|
142
|
+
"userDebt",
|
|
143
|
+
"liquidationPrice",
|
|
144
|
+
];
|
|
145
|
+
function createUserCacheFreshness(value) {
|
|
146
|
+
return {
|
|
147
|
+
userAssetBalance: value,
|
|
148
|
+
userShareBalance: value,
|
|
149
|
+
userUnderlyingBalance: value,
|
|
150
|
+
userCollateral: value,
|
|
151
|
+
userDebt: value,
|
|
152
|
+
liquidationPrice: value,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
120
155
|
class CToken extends Calldata_1.Calldata {
|
|
121
156
|
provider;
|
|
122
157
|
address;
|
|
@@ -132,13 +167,14 @@ class CToken extends Calldata_1.Calldata {
|
|
|
132
167
|
nativeApy = (0, decimal_js_1.default)(0);
|
|
133
168
|
incentiveSupplyApy = (0, decimal_js_1.default)(0);
|
|
134
169
|
incentiveBorrowApy = (0, decimal_js_1.default)(0);
|
|
170
|
+
userCacheFreshness;
|
|
135
171
|
get signer() { return this.market.signer; }
|
|
136
172
|
get account() { return this.market.account; }
|
|
137
173
|
constructor(provider, address, cache, market) {
|
|
138
174
|
super();
|
|
139
175
|
this.provider = provider;
|
|
140
176
|
this.address = address;
|
|
141
|
-
this.contract = (0, helpers_1.contractSetup)(provider, address, BaseCToken_json_1.default);
|
|
177
|
+
this.contract = (0, helpers_1.contractSetup)(this.provider, address, BaseCToken_json_1.default);
|
|
142
178
|
this.cache = cache;
|
|
143
179
|
this.market = market;
|
|
144
180
|
const chainSettings = this.currentChainConfig;
|
|
@@ -163,6 +199,31 @@ class CToken extends Calldata_1.Calldata {
|
|
|
163
199
|
this.leverageTypes.push('simple');
|
|
164
200
|
this.zapTypes.push('simple');
|
|
165
201
|
}
|
|
202
|
+
getUserCacheFreshness() {
|
|
203
|
+
if (this.userCacheFreshness == undefined) {
|
|
204
|
+
this.userCacheFreshness = createUserCacheFreshness(true);
|
|
205
|
+
}
|
|
206
|
+
return this.userCacheFreshness;
|
|
207
|
+
}
|
|
208
|
+
markUserCacheFresh(fields = USER_CACHE_FIELDS) {
|
|
209
|
+
const freshness = this.getUserCacheFreshness();
|
|
210
|
+
for (const field of fields) {
|
|
211
|
+
freshness[field] = true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
invalidateUserCache(fields = USER_CACHE_FIELDS) {
|
|
215
|
+
const freshness = this.getUserCacheFreshness();
|
|
216
|
+
for (const field of fields) {
|
|
217
|
+
freshness[field] = false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
readFreshUserCache(field, accessLabel) {
|
|
221
|
+
if (!this.getUserCacheFreshness()[field]) {
|
|
222
|
+
throw new Error(`Token-level user data is stale for ${this.address} after a summary-only refresh on market ${this.market.address}. ` +
|
|
223
|
+
`Call market.reloadUserData(account) or Market.reloadUserMarkets(...) before ${accessLabel}.`);
|
|
224
|
+
}
|
|
225
|
+
return this.cache[field];
|
|
226
|
+
}
|
|
166
227
|
get setup() { return this.market.setup; }
|
|
167
228
|
get currentChain() { return this.setup.chain; }
|
|
168
229
|
get currentChainConfig() { return (0, helpers_1.getChainConfig)(this.currentChain); }
|
|
@@ -173,6 +234,49 @@ class CToken extends Calldata_1.Calldata {
|
|
|
173
234
|
getWriteContract() {
|
|
174
235
|
return (0, helpers_1.contractSetup)(this.requireSigner(), this.address, BaseCToken_json_1.default);
|
|
175
236
|
}
|
|
237
|
+
getZapType(zap) {
|
|
238
|
+
return typeof zap === "object" ? zap.type : zap;
|
|
239
|
+
}
|
|
240
|
+
isZapInstruction(zap) {
|
|
241
|
+
return this.getZapType(zap) !== "none";
|
|
242
|
+
}
|
|
243
|
+
async getZapInputDecimals(zap) {
|
|
244
|
+
const zapType = this.getZapType(zap);
|
|
245
|
+
switch (zapType) {
|
|
246
|
+
case "none":
|
|
247
|
+
return this.asset.decimals;
|
|
248
|
+
case "native-vault":
|
|
249
|
+
case "native-simple":
|
|
250
|
+
return 18n;
|
|
251
|
+
case "vault": {
|
|
252
|
+
const vaultAsset = await this.getVaultAsset(true);
|
|
253
|
+
return vaultAsset.decimals ?? await vaultAsset.contract.decimals();
|
|
254
|
+
}
|
|
255
|
+
case "simple":
|
|
256
|
+
if (typeof zap !== "object") {
|
|
257
|
+
return this.asset.decimals;
|
|
258
|
+
}
|
|
259
|
+
if (zap.inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase()) {
|
|
260
|
+
return 18n;
|
|
261
|
+
}
|
|
262
|
+
const inputErc20 = new ERC20_1.ERC20(this.provider, zap.inputToken, undefined, undefined, this.signer);
|
|
263
|
+
return inputErc20.decimals ?? await inputErc20.contract.decimals();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
async getZapAssetAmount(amount, zap) {
|
|
267
|
+
return FormatConverter_1.default.decimalToBigInt(amount, await this.getZapInputDecimals(zap));
|
|
268
|
+
}
|
|
269
|
+
async assertVaultLeverageBorrowAssetSupported(borrow, type) {
|
|
270
|
+
const expectedAsset = type === "native-vault"
|
|
271
|
+
? this.currentChainConfig.wrapped_native
|
|
272
|
+
: await this.getVaultAsset(false);
|
|
273
|
+
const actualAsset = borrow.asset.address;
|
|
274
|
+
if (actualAsset.toLowerCase() === expectedAsset.toLowerCase()) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
throw new Error(`${type} leverage requires borrow asset ${expectedAsset}, received ${actualAsset}. ` +
|
|
278
|
+
`Use simple leverage for cross-asset borrow routes.`);
|
|
279
|
+
}
|
|
176
280
|
get adapters() { return this.cache.adapters; }
|
|
177
281
|
get borrowPaused() { return this.cache.borrowPaused; }
|
|
178
282
|
get collateralizationPaused() { return this.cache.collateralizationPaused; }
|
|
@@ -191,23 +295,36 @@ class CToken extends Calldata_1.Calldata {
|
|
|
191
295
|
// for the loss channels this buffer absorbs and the tuning history.
|
|
192
296
|
const theoretical = (0, decimal_js_1.default)(this.cache.maxLeverage).div(helpers_1.BPS);
|
|
193
297
|
const factor = theoretical.sub(1);
|
|
194
|
-
return (0, decimal_js_1.default)(1).add(factor.mul(LEVERAGE.MAX_LEVERAGE_FACTOR));
|
|
298
|
+
return (0, decimal_js_1.default)(1).add(factor.mul(exports.LEVERAGE.MAX_LEVERAGE_FACTOR));
|
|
195
299
|
}
|
|
196
300
|
get canLeverage() { return this.leverageTypes.length > 0; }
|
|
197
301
|
get totalAssets() { return this.cache.totalAssets; }
|
|
198
302
|
get totalSupply() { return this.cache.totalSupply; }
|
|
199
303
|
get liquidationPrice() {
|
|
200
|
-
|
|
304
|
+
const liquidationPrice = this.readFreshUserCache("liquidationPrice", "reading token liquidationPrice");
|
|
305
|
+
if (liquidationPrice == helpers_1.UINT256_MAX)
|
|
201
306
|
return null;
|
|
202
|
-
return (0, helpers_1.toDecimal)(
|
|
307
|
+
return (0, helpers_1.toDecimal)(liquidationPrice, 18n);
|
|
203
308
|
}
|
|
204
309
|
get irmTargetRate() { return (0, decimal_js_1.default)(this.cache.irmTargetRate).div(helpers_1.WAD); }
|
|
205
310
|
get irmMaxRate() { return (0, decimal_js_1.default)(this.cache.irmMaxRate).div(helpers_1.WAD); }
|
|
206
311
|
get irmTargetUtilization() { return (0, decimal_js_1.default)(this.cache.irmTargetUtilization).div(helpers_1.WAD); }
|
|
207
312
|
get interestFee() { return (0, decimal_js_1.default)(this.cache.interestFee).div(helpers_1.BPS); }
|
|
208
313
|
virtualConvertToAssets(shares) {
|
|
314
|
+
if (this.totalSupply === 0n || this.totalAssets === 0n) {
|
|
315
|
+
return shares;
|
|
316
|
+
}
|
|
209
317
|
return (shares * this.totalAssets) / this.totalSupply;
|
|
210
318
|
}
|
|
319
|
+
formatAssets(assets) {
|
|
320
|
+
return FormatConverter_1.default.bigIntToDecimal(assets, this.asset.decimals);
|
|
321
|
+
}
|
|
322
|
+
formatShares(shares) {
|
|
323
|
+
return FormatConverter_1.default.bigIntToDecimal(shares, this.decimals);
|
|
324
|
+
}
|
|
325
|
+
formatSharesAsAssets(shares) {
|
|
326
|
+
return this.formatAssets(this.virtualConvertToAssets(shares));
|
|
327
|
+
}
|
|
211
328
|
/**
|
|
212
329
|
* Convert assets to shares using cached totalSupply/totalAssets.
|
|
213
330
|
* @param bufferBps Optional downward buffer in BPS to account for
|
|
@@ -215,19 +332,35 @@ class CToken extends Calldata_1.Calldata {
|
|
|
215
332
|
* Matches the buffer pattern in async convertToShares().
|
|
216
333
|
*/
|
|
217
334
|
virtualConvertToShares(assets, bufferBps = 0n) {
|
|
218
|
-
const shares =
|
|
335
|
+
const shares = this.totalSupply === 0n || this.totalAssets === 0n
|
|
336
|
+
? assets
|
|
337
|
+
: (assets * this.totalSupply) / this.totalAssets;
|
|
219
338
|
return bufferBps > 0n ? shares * (10000n - bufferBps) / 10000n : shares;
|
|
220
339
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
340
|
+
getMarketLeverageState() {
|
|
341
|
+
const currentCollateralInUsd = this.market.userCollateral;
|
|
342
|
+
const currentDebt = this.market.userDebt;
|
|
343
|
+
const equity = currentCollateralInUsd.sub(currentDebt);
|
|
344
|
+
if (currentCollateralInUsd.lte(0) || equity.lte(0)) {
|
|
345
|
+
return {
|
|
346
|
+
currentCollateralInUsd,
|
|
347
|
+
currentDebt,
|
|
348
|
+
currentLeverage: null,
|
|
349
|
+
};
|
|
224
350
|
}
|
|
225
|
-
const
|
|
226
|
-
return
|
|
351
|
+
const currentLeverage = currentCollateralInUsd.div(equity);
|
|
352
|
+
return {
|
|
353
|
+
currentCollateralInUsd,
|
|
354
|
+
currentDebt,
|
|
355
|
+
currentLeverage: currentLeverage.eq(1) ? null : currentLeverage,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
getLeverage() {
|
|
359
|
+
return this.getMarketLeverageState().currentLeverage;
|
|
227
360
|
}
|
|
228
361
|
getRemainingCollateral(formatted = true) {
|
|
229
362
|
const diff = this.cache.collateralCap - this.cache.collateral;
|
|
230
|
-
return formatted ? this.
|
|
363
|
+
return formatted ? this.convertSharesToUsdSync(diff) : diff;
|
|
231
364
|
}
|
|
232
365
|
getRemainingDebt(formatted = true) {
|
|
233
366
|
const diff = this.cache.debtCap - this.cache.debt;
|
|
@@ -267,36 +400,52 @@ class CToken extends Calldata_1.Calldata {
|
|
|
267
400
|
return inBPS ? (0, decimal_js_1.default)(this.cache.closeFactorMax).div(helpers_1.BPS) : this.cache.closeFactorMax;
|
|
268
401
|
}
|
|
269
402
|
getUserShareBalance(inUSD) {
|
|
270
|
-
|
|
403
|
+
const userShareBalance = this.readFreshUserCache("userShareBalance", "reading token user share balance");
|
|
404
|
+
return inUSD ? this.convertTokensToUsd(userShareBalance, false) : this.formatShares(userShareBalance);
|
|
405
|
+
}
|
|
406
|
+
getUserShareBalanceAssets() {
|
|
407
|
+
const userShareBalance = this.readFreshUserCache("userShareBalance", "reading token user share balance as assets");
|
|
408
|
+
return this.formatSharesAsAssets(userShareBalance);
|
|
271
409
|
}
|
|
272
410
|
getUserAssetBalance(inUSD) {
|
|
273
|
-
|
|
411
|
+
const userAssetBalance = this.readFreshUserCache("userAssetBalance", "reading token user asset balance");
|
|
412
|
+
return inUSD ? this.convertTokensToUsd(userAssetBalance) : this.formatAssets(userAssetBalance);
|
|
274
413
|
}
|
|
275
414
|
getUserUnderlyingBalance(inUSD) {
|
|
276
|
-
|
|
415
|
+
const userUnderlyingBalance = this.readFreshUserCache("userUnderlyingBalance", "reading token user underlying balance");
|
|
416
|
+
return inUSD ? this.convertTokensToUsd(userUnderlyingBalance) : this.formatAssets(userUnderlyingBalance);
|
|
277
417
|
}
|
|
278
418
|
getCollateralCap(inUSD) {
|
|
279
|
-
return inUSD ? this.
|
|
419
|
+
return inUSD ? this.convertSharesToUsdSync(this.cache.collateralCap) : this.cache.collateralCap;
|
|
280
420
|
}
|
|
281
421
|
getDebtCap(inUSD) {
|
|
282
422
|
return inUSD ? this.convertTokensToUsd(this.cache.debtCap) : this.cache.debtCap;
|
|
283
423
|
}
|
|
284
424
|
getCollateral(inUSD) {
|
|
285
|
-
return inUSD ? this.
|
|
425
|
+
return inUSD ? this.convertSharesToUsdSync(this.cache.collateral) : this.cache.collateral;
|
|
286
426
|
}
|
|
287
427
|
getDebt(inUSD) {
|
|
288
428
|
return inUSD ? this.convertTokensToUsd(this.cache.debt) : this.cache.debt;
|
|
289
429
|
}
|
|
290
430
|
getUserCollateral(inUSD) {
|
|
291
|
-
|
|
431
|
+
const userCollateral = this.getUserCollateralShares();
|
|
432
|
+
return inUSD ? this.convertTokensToUsd(userCollateral, false) : this.formatShares(userCollateral);
|
|
433
|
+
}
|
|
434
|
+
getUserCollateralShares() {
|
|
435
|
+
return this.readFreshUserCache("userCollateral", "reading token user collateral shares");
|
|
436
|
+
}
|
|
437
|
+
getUserCollateralAssets() {
|
|
438
|
+
return this.formatSharesAsAssets(this.getUserCollateralShares());
|
|
292
439
|
}
|
|
293
440
|
async fetchUserCollateral(formatted = false) {
|
|
294
441
|
const collateral = await this.contract.collateralPosted(this.getAccountOrThrow());
|
|
295
442
|
this.cache.userCollateral = collateral;
|
|
443
|
+
this.markUserCacheFresh(["userCollateral"]);
|
|
296
444
|
return formatted ? (0, helpers_1.toDecimal)(collateral, this.decimals) : collateral;
|
|
297
445
|
}
|
|
298
446
|
getUserDebt(inUSD) {
|
|
299
|
-
|
|
447
|
+
const userDebt = this.readFreshUserCache("userDebt", "reading token user debt");
|
|
448
|
+
return inUSD ? this.convertTokensToUsd(userDebt) : FormatConverter_1.default.bigIntToDecimal(userDebt, this.asset.decimals);
|
|
300
449
|
}
|
|
301
450
|
earnChange(amount, rateType) {
|
|
302
451
|
const rate = this.getApy(false);
|
|
@@ -320,6 +469,16 @@ class CToken extends Calldata_1.Calldata {
|
|
|
320
469
|
async getVaultAsset(asErc20) {
|
|
321
470
|
return asErc20 ? await this.getUnderlyingVault().fetchAsset(true) : await this.getUnderlyingVault().fetchAsset(false);
|
|
322
471
|
}
|
|
472
|
+
async getExpectedVaultShares(assets) {
|
|
473
|
+
const vault = this.getUnderlyingVault();
|
|
474
|
+
const vaultSharesRaw = await vault.previewDeposit(assets);
|
|
475
|
+
// Vault/native-vault flows mint vault shares first, then convert those
|
|
476
|
+
// into Curvance shares. Buffer the inner preview so exchange-rate drift
|
|
477
|
+
// between quote time and inclusion cannot trip the outer expectedShares
|
|
478
|
+
// check on otherwise-valid deposits/leverage/zaps.
|
|
479
|
+
const vaultShares = vaultSharesRaw * (10000n - exports.LEVERAGE.SHARES_BUFFER_BPS) / 10000n;
|
|
480
|
+
return this.convertToShares(vaultShares);
|
|
481
|
+
}
|
|
323
482
|
getAsset(asErc20) {
|
|
324
483
|
return asErc20
|
|
325
484
|
? new ERC20_1.ERC20(this.provider, this.cache.asset.address, this.cache.asset, this.setup.contracts.OracleManager, this.signer)
|
|
@@ -349,22 +508,22 @@ class CToken extends Calldata_1.Calldata {
|
|
|
349
508
|
// TODO: add underlying yield rate
|
|
350
509
|
return asPercentage ? (0, decimal_js_1.default)(this.cache.supplyRate).div(helpers_1.WAD).mul(helpers_1.SECONDS_PER_YEAR) : this.cache.supplyRate;
|
|
351
510
|
}
|
|
352
|
-
|
|
353
|
-
const
|
|
354
|
-
return inUSD ? this.convertTokensToUsd(
|
|
511
|
+
getDeposits(inUSD = true) {
|
|
512
|
+
const deposits = this.cache.totalAssets;
|
|
513
|
+
return inUSD ? this.convertTokensToUsd(deposits) : deposits;
|
|
355
514
|
}
|
|
356
|
-
async
|
|
357
|
-
const
|
|
358
|
-
this.cache.
|
|
359
|
-
return inUSD ? this.
|
|
515
|
+
async fetchDeposits(inUSD = true) {
|
|
516
|
+
const deposits = await this.fetchTotalAssets();
|
|
517
|
+
this.cache.totalAssets = deposits;
|
|
518
|
+
return inUSD ? this.getDeposits(true) : this.getDeposits(false);
|
|
360
519
|
}
|
|
361
520
|
getTotalCollateral(inUSD = true) {
|
|
362
521
|
const totalCollateral = this.cache.collateral;
|
|
363
|
-
return inUSD ? this.
|
|
522
|
+
return inUSD ? this.convertSharesToUsdSync(totalCollateral) : totalCollateral;
|
|
364
523
|
}
|
|
365
524
|
async fetchTotalCollateral(inUSD = true) {
|
|
366
525
|
const totalCollateral = await this.contract.marketCollateralPosted();
|
|
367
|
-
return inUSD ? this.
|
|
526
|
+
return inUSD ? this.fetchConvertSharesToUsd(totalCollateral) : totalCollateral;
|
|
368
527
|
}
|
|
369
528
|
getPositionManager(type) {
|
|
370
529
|
const signer = this.requireSigner();
|
|
@@ -383,29 +542,24 @@ class CToken extends Calldata_1.Calldata {
|
|
|
383
542
|
return new Zapper_1.Zapper(zap_contract, signer, type, this.setup);
|
|
384
543
|
}
|
|
385
544
|
async isZapAssetApproved(instructions, amount) {
|
|
386
|
-
if (instructions == 'none'
|
|
545
|
+
if (instructions == 'none') {
|
|
387
546
|
return true;
|
|
388
547
|
}
|
|
389
|
-
|
|
548
|
+
const approvalTarget = await this.resolveZapApprovalTarget(instructions);
|
|
549
|
+
if (approvalTarget == null) {
|
|
390
550
|
return true;
|
|
391
551
|
}
|
|
392
|
-
|
|
393
|
-
const asset = new ERC20_1.ERC20(this.provider, instructions.inputToken, undefined, undefined, signer);
|
|
394
|
-
const plugin = this.getPluginAddress(instructions.type, 'zapper');
|
|
395
|
-
const allowance = await asset.allowance(signer.address, plugin);
|
|
396
|
-
return allowance >= amount;
|
|
552
|
+
return this.hasTokenApproval(approvalTarget, amount);
|
|
397
553
|
}
|
|
398
554
|
async approveZapAsset(instructions, amount) {
|
|
399
|
-
if (instructions == 'none'
|
|
555
|
+
if (instructions == 'none') {
|
|
400
556
|
throw new Error("Plugin does not have an associated contract");
|
|
401
557
|
}
|
|
402
|
-
|
|
558
|
+
const approvalTarget = await this.resolveZapApprovalTarget(instructions);
|
|
559
|
+
if (approvalTarget == null) {
|
|
403
560
|
return;
|
|
404
561
|
}
|
|
405
|
-
|
|
406
|
-
const asset = new ERC20_1.ERC20(this.provider, instructions.inputToken, undefined, undefined, signer);
|
|
407
|
-
const plugin = this.getPluginAddress(instructions.type, 'zapper');
|
|
408
|
-
return asset.approve(plugin, amount);
|
|
562
|
+
return approvalTarget.token.approve(approvalTarget.spender, amount);
|
|
409
563
|
}
|
|
410
564
|
async isPluginApproved(plugin, type) {
|
|
411
565
|
if (plugin == 'none') {
|
|
@@ -545,8 +699,22 @@ class CToken extends Calldata_1.Calldata {
|
|
|
545
699
|
receiver ??= signer.address;
|
|
546
700
|
owner ??= signer.address;
|
|
547
701
|
const shares = this.convertTokenInputToShares(amount);
|
|
548
|
-
const
|
|
549
|
-
|
|
702
|
+
const signerAddress = signer.address;
|
|
703
|
+
let method = "redeemCollateral";
|
|
704
|
+
if (owner.toLowerCase() !== signerAddress.toLowerCase()) {
|
|
705
|
+
const isDelegated = await this.contract.isDelegate(owner, signerAddress);
|
|
706
|
+
if (isDelegated) {
|
|
707
|
+
method = "redeemCollateralFor";
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
const allowance = await this.contract.allowance(owner, signerAddress);
|
|
711
|
+
if (allowance < shares) {
|
|
712
|
+
throw new Error(`Please approve ${this.symbol} shares for ${signerAddress} or delegate ${signerAddress} before redeeming collateral for ${owner}.`);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
const calldata = this.getCallData(method, [shares, receiver, owner]);
|
|
717
|
+
return this.oracleRoute(calldata, {}, owner);
|
|
550
718
|
}
|
|
551
719
|
async postCollateral(amount) {
|
|
552
720
|
const signer = this.requireSigner();
|
|
@@ -555,6 +723,9 @@ class CToken extends Calldata_1.Calldata {
|
|
|
555
723
|
const collateral = await this.fetchUserCollateral();
|
|
556
724
|
const available_shares = balance - collateral;
|
|
557
725
|
const max_shares = available_shares < shares ? available_shares : shares;
|
|
726
|
+
if (max_shares <= 0n) {
|
|
727
|
+
throw new Error("No cToken shares available to post as collateral.");
|
|
728
|
+
}
|
|
558
729
|
const calldata = this.getCallData("postCollateral", [max_shares]);
|
|
559
730
|
const tx = await this.oracleRoute(calldata);
|
|
560
731
|
// Reload collateral state after execution
|
|
@@ -591,56 +762,56 @@ class CToken extends Calldata_1.Calldata {
|
|
|
591
762
|
}
|
|
592
763
|
return asset.balanceOf(signer.address, false);
|
|
593
764
|
}
|
|
594
|
-
// TODO: Hack to remove
|
|
595
765
|
async ensureUnderlyingAmount(amount, zap) {
|
|
596
766
|
const balance = await this.getZapBalance(zap);
|
|
597
|
-
const
|
|
598
|
-
// Use the zap input token's decimals when zapping, otherwise the deposit token's decimals
|
|
599
|
-
let decimals = this.asset.decimals;
|
|
600
|
-
if (isZapping && zap.inputToken) {
|
|
601
|
-
if (zap.inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase()) {
|
|
602
|
-
decimals = 18n;
|
|
603
|
-
}
|
|
604
|
-
else {
|
|
605
|
-
const inputErc20 = new ERC20_1.ERC20(this.provider, zap.inputToken, undefined, undefined, this.signer);
|
|
606
|
-
decimals = inputErc20.decimals ?? await inputErc20.contract.decimals();
|
|
607
|
-
}
|
|
608
|
-
}
|
|
767
|
+
const decimals = await this.getZapInputDecimals(zap);
|
|
609
768
|
const assets = FormatConverter_1.default.decimalToBigInt(amount, decimals);
|
|
610
769
|
if (assets > balance) {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
formatted: FormatConverter_1.default.bigIntToDecimal(balance, decimals),
|
|
614
|
-
attempt: {
|
|
615
|
-
raw: assets,
|
|
616
|
-
formatted: amount
|
|
617
|
-
},
|
|
618
|
-
});
|
|
619
|
-
return FormatConverter_1.default.bigIntToDecimal(balance, decimals);
|
|
770
|
+
const formattedBalance = FormatConverter_1.default.bigIntToDecimal(balance, decimals);
|
|
771
|
+
throw new Error(`Insufficient balance: requested ${amount.toString()}, available ${formattedBalance.toString()}.`);
|
|
620
772
|
}
|
|
621
773
|
return amount;
|
|
622
774
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
775
|
+
getExecutionDebtBufferTime() {
|
|
776
|
+
return this.market.userDebt.greaterThan(0) ? EXECUTION_DEBT_BUFFER_TIME : 0n;
|
|
777
|
+
}
|
|
778
|
+
async resolveCollateralRemovalShares(amount) {
|
|
779
|
+
const max_removable_shares = await this.maxRemovableCollateral(true, this.getExecutionDebtBufferTime());
|
|
780
|
+
const requested_shares = this.convertTokenInputToShares(amount);
|
|
781
|
+
let shares = max_removable_shares < requested_shares ? max_removable_shares : requested_shares;
|
|
782
|
+
// If within 0.1% of the safe removable collateral, remove it all to avoid dust.
|
|
783
|
+
const threshold = max_removable_shares / 1000n || 10n;
|
|
784
|
+
if (max_removable_shares - shares <= threshold) {
|
|
785
|
+
shares = max_removable_shares;
|
|
628
786
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
if (current_shares - max_shares <= threshold) {
|
|
635
|
-
max_shares = current_shares;
|
|
636
|
-
}
|
|
787
|
+
return shares;
|
|
788
|
+
}
|
|
789
|
+
async executeCollateralRemoval(shares) {
|
|
790
|
+
if (shares === 0n) {
|
|
791
|
+
throw new Error("No removable collateral available.");
|
|
637
792
|
}
|
|
638
|
-
const calldata = this.getCallData("removeCollateral", [
|
|
793
|
+
const calldata = this.getCallData("removeCollateral", [shares]);
|
|
639
794
|
const tx = await this.oracleRoute(calldata);
|
|
640
795
|
// Reload collateral state after execution
|
|
641
796
|
await this.fetchUserCollateral();
|
|
642
797
|
return tx;
|
|
643
798
|
}
|
|
799
|
+
async maxRemovableCollateral(in_shares = false, bufferTime = 0n) {
|
|
800
|
+
if (in_shares) {
|
|
801
|
+
const breakdown = await this.maxRedemption(true, bufferTime, true);
|
|
802
|
+
return breakdown.max_collateral;
|
|
803
|
+
}
|
|
804
|
+
const breakdown = await this.maxRedemption(false, bufferTime, true);
|
|
805
|
+
return breakdown.max_collateral;
|
|
806
|
+
}
|
|
807
|
+
async removeCollateralExact(amount) {
|
|
808
|
+
const shares = await this.resolveCollateralRemovalShares(amount);
|
|
809
|
+
return this.executeCollateralRemoval(shares);
|
|
810
|
+
}
|
|
811
|
+
async removeMaxCollateral() {
|
|
812
|
+
const shares = await this.maxRemovableCollateral(true, this.getExecutionDebtBufferTime());
|
|
813
|
+
return this.executeCollateralRemoval(shares);
|
|
814
|
+
}
|
|
644
815
|
convertTokenInputToShares(amount) {
|
|
645
816
|
return this.virtualConvertToShares(FormatConverter_1.default.decimalToBigInt(amount, this.asset.decimals));
|
|
646
817
|
}
|
|
@@ -661,7 +832,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
661
832
|
async convertToAssets(shares) {
|
|
662
833
|
return this.contract.convertToAssets(shares);
|
|
663
834
|
}
|
|
664
|
-
async convertToShares(assets, bufferBps =
|
|
835
|
+
async convertToShares(assets, bufferBps = exports.LEVERAGE.SHARES_BUFFER_BPS) {
|
|
665
836
|
const shares = await this.contract.convertToShares(assets);
|
|
666
837
|
return bufferBps > 0n ? shares * (10000n - bufferBps) / 10000n : shares;
|
|
667
838
|
}
|
|
@@ -695,7 +866,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
695
866
|
interface: new NativeToken_1.NativeToken(this.currentChain, this.provider, this.setup.contracts.OracleManager, this.signer, this.account),
|
|
696
867
|
type: 'native-vault'
|
|
697
868
|
});
|
|
698
|
-
tokens_exclude.push(helpers_1.EMPTY_ADDRESS, helpers_1.NATIVE_ADDRESS);
|
|
869
|
+
tokens_exclude.push(helpers_1.EMPTY_ADDRESS.toLowerCase(), helpers_1.NATIVE_ADDRESS.toLowerCase());
|
|
699
870
|
}
|
|
700
871
|
if (this.zapTypes.includes('native-simple')) {
|
|
701
872
|
tokens.push({
|
|
@@ -703,7 +874,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
703
874
|
type: 'native-simple'
|
|
704
875
|
});
|
|
705
876
|
if (!this.zapTypes.includes('native-vault')) {
|
|
706
|
-
tokens_exclude.push(helpers_1.EMPTY_ADDRESS, helpers_1.NATIVE_ADDRESS);
|
|
877
|
+
tokens_exclude.push(helpers_1.EMPTY_ADDRESS.toLowerCase(), helpers_1.NATIVE_ADDRESS.toLowerCase());
|
|
707
878
|
}
|
|
708
879
|
}
|
|
709
880
|
if (this.zapTypes.includes('vault')) {
|
|
@@ -714,7 +885,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
714
885
|
});
|
|
715
886
|
tokens_exclude.push(vault_asset.address.toLocaleLowerCase());
|
|
716
887
|
}
|
|
717
|
-
if (this.zapTypes.includes('simple')) {
|
|
888
|
+
if (this.zapTypes.includes('simple') && this.currentChainConfig.dexAgg.router !== helpers_1.EMPTY_ADDRESS) {
|
|
718
889
|
let dexAggSearch = await this.currentChainConfig.dexAgg.getAvailableTokens(this.provider, search, this.account);
|
|
719
890
|
tokens = tokens.concat(dexAggSearch.filter(token => !tokens_exclude.includes(token.interface.address.toLocaleLowerCase())));
|
|
720
891
|
// Add native MON as a zap option for any token with a simple zapper
|
|
@@ -758,6 +929,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
758
929
|
this.cache.assetPrice = snapshot.collateralAssetPrice;
|
|
759
930
|
this.cache.sharePrice = snapshot.sharePrice;
|
|
760
931
|
borrow.cache.assetPrice = snapshot.debtAssetPrice;
|
|
932
|
+
this.market.cache.user.collateral = snapshot.collateralUsd;
|
|
761
933
|
this.market.cache.user.debt = snapshot.debtUsd;
|
|
762
934
|
return snapshot;
|
|
763
935
|
}
|
|
@@ -771,47 +943,133 @@ class CToken extends Calldata_1.Calldata {
|
|
|
771
943
|
* equity-fraction denominator amplifies it by (L-1)x automatically.
|
|
772
944
|
* The user's swap-level slippage (passed separately to _swapSafe) is
|
|
773
945
|
* unaffected — that's the layer that bounds MEV extraction.
|
|
946
|
+
*
|
|
947
|
+
* Applied uniformly to simple AND vault/native-vault leverage-up paths.
|
|
948
|
+
* Simple path uses the buffer for share-rounding + Redstone drift as
|
|
949
|
+
* described above. Vault paths inherit the flat 10 bps through the
|
|
950
|
+
* shared `slippage` variable before the per-branch
|
|
951
|
+
* `amplifyContractSlippage(..., LEVERAGE_UP_VAULT_DRIFT_BPS)` expansion;
|
|
952
|
+
* the flat addition is not amplified (base term stays flat) and covers
|
|
953
|
+
* the same residual class (share-rounding, oracle drift) on vault paths
|
|
954
|
+
* too. Removing the buffer for vault would save a trivial amount of
|
|
955
|
+
* user slippage budget at the cost of a false-negative risk on the
|
|
956
|
+
* residuals — we keep it for symmetry.
|
|
774
957
|
*/
|
|
775
958
|
_leverageUpSlippage(slippage, leverage) {
|
|
776
959
|
if (leverage.lte(1))
|
|
777
960
|
return slippage;
|
|
778
|
-
return slippage + LEVERAGE.LEVERAGE_UP_BUFFER_BPS;
|
|
961
|
+
return slippage + exports.LEVERAGE.LEVERAGE_UP_BUFFER_BPS;
|
|
962
|
+
}
|
|
963
|
+
assertSimpleLeverageSwapAssetsDiffer(borrow) {
|
|
964
|
+
if (borrow.asset.address.toLowerCase() === this.asset.address.toLowerCase()) {
|
|
965
|
+
throw new Error("Simple leverage requires distinct collateral and borrow assets.");
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
async assertLeverageBorrowCapacity(borrow, borrowAssets) {
|
|
969
|
+
if (borrowAssets === 0n) {
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
const [assetsHeld, outstandingDebt] = await Promise.all([
|
|
973
|
+
borrow.fetchLiquidity(),
|
|
974
|
+
borrow.marketOutstandingDebt(),
|
|
975
|
+
]);
|
|
976
|
+
const remainingDebtCap = borrow.cache.debtCap > outstandingDebt
|
|
977
|
+
? borrow.cache.debtCap - outstandingDebt
|
|
978
|
+
: 0n;
|
|
979
|
+
const capacity = assetsHeld < remainingDebtCap ? assetsHeld : remainingDebtCap;
|
|
980
|
+
if (borrowAssets > capacity) {
|
|
981
|
+
throw new Error("Selected borrow token does not have enough remaining debt capacity or liquidity for this leverage operation.");
|
|
982
|
+
}
|
|
779
983
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
984
|
+
assertSelectedBorrowDebtCanDeleverage(borrow, selectedDebtAssets, requiredDebtReductionUsd) {
|
|
985
|
+
if (requiredDebtReductionUsd.lte(0)) {
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
const price = borrow.getPrice(true);
|
|
989
|
+
if (!price.isFinite() || price.lte(0)) {
|
|
990
|
+
throw new Error("Selected borrow token has an invalid price for deleverage sizing.");
|
|
991
|
+
}
|
|
992
|
+
const requiredDebtAssets = FormatConverter_1.default.decimalToBigInt(requiredDebtReductionUsd.div(price), borrow.asset.decimals);
|
|
993
|
+
if (requiredDebtAssets > selectedDebtAssets) {
|
|
994
|
+
throw new Error("Selected borrow token debt is too small for the requested deleverage target.");
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
computePostDepositNaturalLeverage(currentCollateralInUsd, currentDebtInUsd, depositInUsd) {
|
|
998
|
+
if (currentDebtInUsd.lte(0))
|
|
999
|
+
return (0, decimal_js_1.default)(1);
|
|
1000
|
+
const collateralAfterDeposit = currentCollateralInUsd.add(depositInUsd);
|
|
1001
|
+
const equityAfterDeposit = collateralAfterDeposit.sub(currentDebtInUsd);
|
|
1002
|
+
if (equityAfterDeposit.lte(0))
|
|
1003
|
+
return (0, decimal_js_1.default)(1);
|
|
1004
|
+
return collateralAfterDeposit.div(equityAfterDeposit);
|
|
1005
|
+
}
|
|
1006
|
+
resolveLeverageUpPreview({ operation, targetLeverage, borrow, depositAssets = 0n, positionManagerType, }) {
|
|
1007
|
+
const leverageState = this.getMarketLeverageState();
|
|
1008
|
+
const currentLeverage = leverageState.currentLeverage ?? (0, decimal_js_1.default)(1);
|
|
1009
|
+
const currentCollateralInUsd = leverageState.currentCollateralInUsd;
|
|
1010
|
+
const depositInAssets = FormatConverter_1.default.bigIntToDecimal(depositAssets, this.asset.decimals);
|
|
1011
|
+
const depositInUsd = depositAssets > 0n
|
|
1012
|
+
? this.convertTokensToUsd(depositAssets, true)
|
|
1013
|
+
: (0, decimal_js_1.default)(0);
|
|
1014
|
+
const currentDebt = leverageState.currentDebt;
|
|
1015
|
+
const effectiveCurrentLeverage = depositAssets > 0n
|
|
1016
|
+
? this.computePostDepositNaturalLeverage(currentCollateralInUsd, currentDebt, depositInUsd)
|
|
1017
|
+
: currentLeverage;
|
|
1018
|
+
const cappedTargetLeverage = targetLeverage.gt(this.maxLeverage)
|
|
1019
|
+
? this.maxLeverage
|
|
1020
|
+
: targetLeverage;
|
|
1021
|
+
const resolvedTargetLeverage = operation === 'deposit-and-leverage'
|
|
1022
|
+
? decimal_js_1.default.max(cappedTargetLeverage, effectiveCurrentLeverage)
|
|
1023
|
+
: cappedTargetLeverage;
|
|
1024
|
+
if (operation === 'leverage-up' && resolvedTargetLeverage.lte(effectiveCurrentLeverage)) {
|
|
783
1025
|
throw new Error("New leverage must be more than current leverage");
|
|
784
1026
|
}
|
|
785
|
-
|
|
786
|
-
|
|
1027
|
+
const collateralAfterDepositInUsd = currentCollateralInUsd.add(depositInUsd);
|
|
1028
|
+
const notional = collateralAfterDepositInUsd.sub(currentDebt);
|
|
1029
|
+
if (notional.lte(0)) {
|
|
1030
|
+
throw new Error("Position has no positive equity to leverage.");
|
|
787
1031
|
}
|
|
788
|
-
const collateralAvail = this.cache.userCollateral + (depositAmount ? depositAmount : BigInt(0));
|
|
789
|
-
const collateralInUsd = this.convertTokensToUsd(collateralAvail, false);
|
|
790
|
-
const currentDebt = this.market.userDebt;
|
|
791
|
-
const notional = collateralInUsd.sub(currentDebt);
|
|
792
|
-
const leverageFactor = newLeverage.sub(1);
|
|
793
1032
|
const borrowPrice = borrow.getPrice(true);
|
|
794
|
-
const rawDebtInUsd = notional.mul(
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
1033
|
+
const rawDebtInUsd = notional.mul(resolvedTargetLeverage).sub(notional);
|
|
1034
|
+
const debtIncrease = decimal_js_1.default.max(rawDebtInUsd.sub(currentDebt), (0, decimal_js_1.default)(0));
|
|
1035
|
+
const borrowAmount = borrowPrice.gt(0)
|
|
1036
|
+
? debtIncrease.div(borrowPrice)
|
|
1037
|
+
: (0, decimal_js_1.default)(0);
|
|
1038
|
+
const borrowAssets = debtIncrease.gt(0)
|
|
1039
|
+
? FormatConverter_1.default.decimalToBigInt(borrowAmount, borrow.asset.decimals)
|
|
1040
|
+
: 0n;
|
|
1041
|
+
const feePolicyCurrentLeverage = operation === 'deposit-and-leverage'
|
|
1042
|
+
? effectiveCurrentLeverage
|
|
1043
|
+
: currentLeverage;
|
|
1044
|
+
const hasSwapFee = positionManagerType !== 'vault' && positionManagerType !== 'native-vault';
|
|
1045
|
+
const feeBps = borrowAssets > 0n && hasSwapFee
|
|
1046
|
+
? this.setup.feePolicy.getFeeBps({
|
|
1047
|
+
operation,
|
|
1048
|
+
inputToken: borrow.asset.address,
|
|
1049
|
+
outputToken: this.asset.address,
|
|
1050
|
+
inputAmount: borrowAssets,
|
|
1051
|
+
currentLeverage: feePolicyCurrentLeverage,
|
|
1052
|
+
targetLeverage: resolvedTargetLeverage,
|
|
1053
|
+
})
|
|
1054
|
+
: 0n;
|
|
806
1055
|
const feeAssets = borrowAmount.mul((0, decimal_js_1.default)(Number(feeBps))).div((0, decimal_js_1.default)(10000));
|
|
807
1056
|
const feeUsd = feeAssets.mul(borrowPrice);
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
const
|
|
1057
|
+
const collateralIncreaseFromBorrow = decimal_js_1.default.max(debtIncrease.sub(feeUsd), (0, decimal_js_1.default)(0));
|
|
1058
|
+
const collateralIncrease = depositInUsd.add(collateralIncreaseFromBorrow);
|
|
1059
|
+
const collateralIncreaseInAssets = depositInAssets.add(this.convertUsdToTokens(collateralIncreaseFromBorrow, true));
|
|
1060
|
+
const newCollateralInUsd = currentCollateralInUsd.add(collateralIncrease);
|
|
811
1061
|
return {
|
|
1062
|
+
currentLeverage,
|
|
1063
|
+
effectiveCurrentLeverage,
|
|
1064
|
+
targetLeverage: resolvedTargetLeverage,
|
|
812
1065
|
borrowAmount,
|
|
1066
|
+
borrowAssets,
|
|
1067
|
+
debtIncrease,
|
|
1068
|
+
debtIncreaseInAssets: borrowAmount,
|
|
813
1069
|
newDebt: rawDebtInUsd,
|
|
814
1070
|
newDebtInAssets: borrow.convertUsdToTokens(rawDebtInUsd, true),
|
|
1071
|
+
collateralIncrease,
|
|
1072
|
+
collateralIncreaseInAssets,
|
|
815
1073
|
newCollateral: newCollateralInUsd,
|
|
816
1074
|
newCollateralInAssets: this.convertUsdToTokens(newCollateralInUsd, true),
|
|
817
1075
|
feeBps,
|
|
@@ -819,6 +1077,26 @@ class CToken extends Calldata_1.Calldata {
|
|
|
819
1077
|
feeUsd,
|
|
820
1078
|
};
|
|
821
1079
|
}
|
|
1080
|
+
previewDepositAndLeverage(newLeverage, borrow, depositAmount, positionManagerType) {
|
|
1081
|
+
return this.resolveLeverageUpPreview({
|
|
1082
|
+
operation: 'deposit-and-leverage',
|
|
1083
|
+
targetLeverage: newLeverage,
|
|
1084
|
+
borrow,
|
|
1085
|
+
depositAssets: depositAmount,
|
|
1086
|
+
positionManagerType,
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
previewLeverageUp(newLeverage, borrow, depositAmount, positionManagerType) {
|
|
1090
|
+
if ((depositAmount ?? 0n) > 0n) {
|
|
1091
|
+
return this.previewDepositAndLeverage(newLeverage, borrow, depositAmount, positionManagerType);
|
|
1092
|
+
}
|
|
1093
|
+
return this.resolveLeverageUpPreview({
|
|
1094
|
+
operation: 'leverage-up',
|
|
1095
|
+
targetLeverage: newLeverage,
|
|
1096
|
+
borrow,
|
|
1097
|
+
positionManagerType,
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
822
1100
|
previewLeverageDown(newLeverage, currentLeverage, borrow) {
|
|
823
1101
|
if (newLeverage.gte(currentLeverage)) {
|
|
824
1102
|
throw new Error("New leverage must be less than current leverage");
|
|
@@ -826,10 +1104,13 @@ class CToken extends Calldata_1.Calldata {
|
|
|
826
1104
|
if (newLeverage.lt((0, decimal_js_1.default)(1))) {
|
|
827
1105
|
throw new Error("New leverage must be at least 1");
|
|
828
1106
|
}
|
|
829
|
-
const
|
|
830
|
-
const collateralInUsd =
|
|
831
|
-
const currentDebt =
|
|
1107
|
+
const leverageState = this.getMarketLeverageState();
|
|
1108
|
+
const collateralInUsd = leverageState.currentCollateralInUsd;
|
|
1109
|
+
const currentDebt = leverageState.currentDebt;
|
|
832
1110
|
const equity = collateralInUsd.sub(currentDebt);
|
|
1111
|
+
if (equity.lte(0)) {
|
|
1112
|
+
throw new Error("Position has no positive equity to deleverage.");
|
|
1113
|
+
}
|
|
833
1114
|
const targetCollateralUsd = equity.mul(newLeverage);
|
|
834
1115
|
const newDebtUsd = targetCollateralUsd.sub(equity);
|
|
835
1116
|
const collateralAssetReductionUsd = collateralInUsd.sub(targetCollateralUsd);
|
|
@@ -870,22 +1151,30 @@ class CToken extends Calldata_1.Calldata {
|
|
|
870
1151
|
async leverageUp(borrow, newLeverage, type, slippage_ = (0, decimal_js_1.default)(0.05), simulate = false) {
|
|
871
1152
|
try {
|
|
872
1153
|
this.requireSigner();
|
|
873
|
-
const slippage = this._leverageUpSlippage(FormatConverter_1.default.percentageToBps(slippage_), newLeverage);
|
|
874
1154
|
const manager = this.getPositionManager(type);
|
|
1155
|
+
if (type === 'vault' || type === 'native-vault') {
|
|
1156
|
+
await this.assertVaultLeverageBorrowAssetSupported(borrow, type);
|
|
1157
|
+
}
|
|
1158
|
+
else if (type === 'simple') {
|
|
1159
|
+
this.assertSimpleLeverageSwapAssetsDiffer(borrow);
|
|
1160
|
+
}
|
|
875
1161
|
let calldata;
|
|
876
1162
|
await this._getLeverageSnapshot(borrow);
|
|
877
|
-
const
|
|
1163
|
+
const preview = this.previewLeverageUp(newLeverage, borrow, undefined, type);
|
|
1164
|
+
const slippage = this._leverageUpSlippage(FormatConverter_1.default.percentageToBps(slippage_), preview.targetLeverage);
|
|
1165
|
+
const { borrowAmount, borrowAssets, feeBps, targetLeverage } = preview;
|
|
1166
|
+
if (borrowAssets === 0n) {
|
|
1167
|
+
if (simulate) {
|
|
1168
|
+
return {
|
|
1169
|
+
success: false,
|
|
1170
|
+
error: "Target leverage must exceed the current leverage enough to borrow more.",
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
throw new Error("Target leverage must exceed the current leverage enough to borrow more.");
|
|
1174
|
+
}
|
|
1175
|
+
await this.assertLeverageBorrowCapacity(borrow, borrowAssets);
|
|
878
1176
|
switch (type) {
|
|
879
1177
|
case 'simple': {
|
|
880
|
-
const borrowAssets = FormatConverter_1.default.decimalToBigInt(borrowAmount, borrow.asset.decimals);
|
|
881
|
-
const feeBps = this.setup.feePolicy.getFeeBps({
|
|
882
|
-
operation: 'leverage-up',
|
|
883
|
-
inputToken: borrow.asset.address,
|
|
884
|
-
outputToken: this.asset.address,
|
|
885
|
-
inputAmount: borrowAssets,
|
|
886
|
-
currentLeverage: this.getLeverage() ?? (0, decimal_js_1.default)(1),
|
|
887
|
-
targetLeverage: newLeverage,
|
|
888
|
-
});
|
|
889
1178
|
const feeReceiver = feeBps > 0n ? this.setup.feePolicy.feeReceiver : undefined;
|
|
890
1179
|
const { action, quote } = await this.currentChainConfig.dexAgg.quoteAction(manager.address, borrow.asset.address, this.asset.address, borrowAssets, slippage, feeBps, feeReceiver);
|
|
891
1180
|
// Fee-aware slippage expansion now lives inside KyberSwap.quoteAction
|
|
@@ -896,12 +1185,12 @@ class CToken extends Calldata_1.Calldata {
|
|
|
896
1185
|
// as equity loss amplified by (L-1) — same pattern as
|
|
897
1186
|
// deleverage. Expand the contract-level tolerance to absorb it.
|
|
898
1187
|
// See `amplifyContractSlippage` in helpers.ts for rationale.
|
|
899
|
-
const contractSlippage = (0, helpers_1.amplifyContractSlippage)(slippage,
|
|
1188
|
+
const contractSlippage = (0, helpers_1.amplifyContractSlippage)(slippage, targetLeverage.sub(1), feeBps);
|
|
900
1189
|
calldata = manager.getLeverageCalldata({
|
|
901
1190
|
borrowableCToken: borrow.address,
|
|
902
|
-
borrowAssets:
|
|
1191
|
+
borrowAssets: borrowAssets,
|
|
903
1192
|
cToken: this.address,
|
|
904
|
-
expectedShares: this.virtualConvertToShares(BigInt(quote.min_out), LEVERAGE.SHARES_BUFFER_BPS),
|
|
1193
|
+
expectedShares: this.virtualConvertToShares(BigInt(quote.min_out), exports.LEVERAGE.SHARES_BUFFER_BPS),
|
|
905
1194
|
swapAction: action,
|
|
906
1195
|
auxData: "0x",
|
|
907
1196
|
}, FormatConverter_1.default.bpsToBpsWad(contractSlippage));
|
|
@@ -909,14 +1198,20 @@ class CToken extends Calldata_1.Calldata {
|
|
|
909
1198
|
}
|
|
910
1199
|
case 'native-vault':
|
|
911
1200
|
case 'vault': {
|
|
1201
|
+
// No DEX leg, so no fee-driven forced loss to absorb.
|
|
1202
|
+
// The `(L-1)×K` expansion here covers the vault-token
|
|
1203
|
+
// collateral drift between the vault's fundamental mint
|
|
1204
|
+
// rate at tx time and the stored oracle price that
|
|
1205
|
+
// `checkSlippage` reads. See LEVERAGE_UP_VAULT_DRIFT_BPS.
|
|
1206
|
+
const contractSlippage = (0, helpers_1.amplifyContractSlippage)(slippage, targetLeverage.sub(1), exports.LEVERAGE.LEVERAGE_UP_VAULT_DRIFT_BPS);
|
|
912
1207
|
calldata = manager.getLeverageCalldata({
|
|
913
1208
|
borrowableCToken: borrow.address,
|
|
914
|
-
borrowAssets:
|
|
1209
|
+
borrowAssets: borrowAssets,
|
|
915
1210
|
cToken: this.address,
|
|
916
1211
|
expectedShares: await PositionManager_1.PositionManager.getVaultExpectedShares(this, borrow, borrowAmount),
|
|
917
1212
|
swapAction: PositionManager_1.PositionManager.emptySwapAction(),
|
|
918
1213
|
auxData: "0x",
|
|
919
|
-
}, FormatConverter_1.default.bpsToBpsWad(
|
|
1214
|
+
}, FormatConverter_1.default.bpsToBpsWad(contractSlippage));
|
|
920
1215
|
break;
|
|
921
1216
|
}
|
|
922
1217
|
default:
|
|
@@ -926,7 +1221,6 @@ class CToken extends Calldata_1.Calldata {
|
|
|
926
1221
|
}
|
|
927
1222
|
if (simulate)
|
|
928
1223
|
return this.simulateOracleRoute(calldata, { to: manager.address });
|
|
929
|
-
await this._checkPositionManagerApproval(manager);
|
|
930
1224
|
return this.oracleRoute(calldata, { to: manager.address });
|
|
931
1225
|
}
|
|
932
1226
|
catch (error) {
|
|
@@ -946,10 +1240,15 @@ class CToken extends Calldata_1.Calldata {
|
|
|
946
1240
|
const config = this.currentChainConfig;
|
|
947
1241
|
const slippage = (0, helpers_1.toBps)(slippage_);
|
|
948
1242
|
const manager = this.getPositionManager(type);
|
|
1243
|
+
if (type === 'simple') {
|
|
1244
|
+
this.assertSimpleLeverageSwapAssetsDiffer(borrowToken);
|
|
1245
|
+
}
|
|
949
1246
|
let calldata;
|
|
950
1247
|
const snapshot = await this._getLeverageSnapshot(borrowToken);
|
|
951
|
-
const
|
|
1248
|
+
const preview = this.previewLeverageDown(newLeverage, currentLeverage);
|
|
1249
|
+
const { collateralAssetReduction } = preview;
|
|
952
1250
|
const isFullDeleverage = newLeverage.equals(1);
|
|
1251
|
+
const maxTokenCollateral = this.virtualConvertToAssets(this.readFreshUserCache("userCollateral", "executing leverage down"));
|
|
953
1252
|
switch (type) {
|
|
954
1253
|
case 'simple': {
|
|
955
1254
|
let swapCollateral = collateralAssetReduction;
|
|
@@ -990,20 +1289,33 @@ class CToken extends Calldata_1.Calldata {
|
|
|
990
1289
|
// Total overhead = base overhead (DEX impact + drift) + fee bps.
|
|
991
1290
|
// Additive approximation is accurate to sub-bp at typical
|
|
992
1291
|
// fee+overhead magnitudes (< 100 bps combined).
|
|
993
|
-
const overheadBps = LEVERAGE.DELEVERAGE_OVERHEAD_BPS + feeBps;
|
|
1292
|
+
const overheadBps = exports.LEVERAGE.DELEVERAGE_OVERHEAD_BPS + feeBps;
|
|
994
1293
|
swapCollateral = debtInCollateral * (10000n + overheadBps) / 10000n;
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1294
|
+
if (swapCollateral > maxTokenCollateral) {
|
|
1295
|
+
const error = "Selected collateral token does not have enough posted collateral to fully deleverage.";
|
|
1296
|
+
if (simulate) {
|
|
1297
|
+
return { success: false, error };
|
|
1298
|
+
}
|
|
1299
|
+
throw new Error(error);
|
|
998
1300
|
}
|
|
999
1301
|
}
|
|
1000
|
-
else
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1302
|
+
else {
|
|
1303
|
+
this.assertSelectedBorrowDebtCanDeleverage(borrowToken, snapshot.debtTokenBalance, decimal_js_1.default.max(this.market.userDebt.sub(preview.newDebt), (0, decimal_js_1.default)(0)));
|
|
1304
|
+
if (feeBps > 0n) {
|
|
1305
|
+
// Partial deleverage: inflate swap size to compensate
|
|
1306
|
+
// for fee deduction on input. KyberSwap deducts feeBps
|
|
1307
|
+
// from input before swapping, so without compensation
|
|
1308
|
+
// the swap underdelivers and actual leverage is slightly
|
|
1309
|
+
// higher than target.
|
|
1310
|
+
swapCollateral = swapCollateral * 10000n / (10000n - feeBps);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
if (!isFullDeleverage && swapCollateral > maxTokenCollateral) {
|
|
1314
|
+
const error = "Selected collateral token does not have enough posted collateral to reach the requested leverage target.";
|
|
1315
|
+
if (simulate) {
|
|
1316
|
+
return { success: false, error };
|
|
1317
|
+
}
|
|
1318
|
+
throw new Error(error);
|
|
1007
1319
|
}
|
|
1008
1320
|
const { action, quote } = await config.dexAgg.quoteAction(manager.address, this.asset.address, borrowToken.asset.address, swapCollateral, slippage, feeBps, feeReceiver);
|
|
1009
1321
|
// Fee-aware slippage expansion for `_swapSafe` is handled by
|
|
@@ -1025,7 +1337,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1025
1337
|
? currentLeverage.sub(1)
|
|
1026
1338
|
: currentLeverage.sub(newLeverage);
|
|
1027
1339
|
const forcedBps = isFullDeleverage
|
|
1028
|
-
? LEVERAGE.DELEVERAGE_OVERHEAD_BPS + feeBps
|
|
1340
|
+
? exports.LEVERAGE.DELEVERAGE_OVERHEAD_BPS + feeBps
|
|
1029
1341
|
: feeBps;
|
|
1030
1342
|
const contractSlippage = (0, helpers_1.amplifyContractSlippage)(slippage, leverageDelta, forcedBps);
|
|
1031
1343
|
calldata = manager.getDeleverageCalldata({
|
|
@@ -1045,7 +1357,6 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1045
1357
|
}
|
|
1046
1358
|
if (simulate)
|
|
1047
1359
|
return this.simulateOracleRoute(calldata, { to: manager.address });
|
|
1048
|
-
await this._checkPositionManagerApproval(manager);
|
|
1049
1360
|
return this.oracleRoute(calldata, { to: manager.address });
|
|
1050
1361
|
}
|
|
1051
1362
|
catch (error) {
|
|
@@ -1062,35 +1373,44 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1062
1373
|
throw new Error("Multiplier must be greater than 1");
|
|
1063
1374
|
}
|
|
1064
1375
|
depositAmount = await this.ensureUnderlyingAmount(depositAmount, 'none');
|
|
1065
|
-
const slippage = this._leverageUpSlippage((0, helpers_1.toBps)(slippage_), multiplier);
|
|
1066
1376
|
const manager = this.getPositionManager(type);
|
|
1377
|
+
if (type === 'vault' || type === 'native-vault') {
|
|
1378
|
+
await this.assertVaultLeverageBorrowAssetSupported(borrow, type);
|
|
1379
|
+
}
|
|
1380
|
+
else if (type === 'simple') {
|
|
1381
|
+
this.assertSimpleLeverageSwapAssetsDiffer(borrow);
|
|
1382
|
+
}
|
|
1067
1383
|
let calldata;
|
|
1068
1384
|
const depositAssets = FormatConverter_1.default.decimalToBigInt(depositAmount, this.asset.decimals);
|
|
1385
|
+
await this._checkTokenApproval(this.getPositionManagerDepositApprovalTarget(manager), depositAssets);
|
|
1069
1386
|
await this._getLeverageSnapshot(borrow);
|
|
1070
|
-
const
|
|
1387
|
+
const preview = this.previewDepositAndLeverage(multiplier, borrow, depositAssets, type);
|
|
1388
|
+
if (preview.borrowAssets === 0n) {
|
|
1389
|
+
if (simulate) {
|
|
1390
|
+
return {
|
|
1391
|
+
success: false,
|
|
1392
|
+
error: "Target leverage must exceed the post-deposit leverage to borrow more.",
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
throw new Error("Target leverage must exceed the post-deposit leverage to borrow more.");
|
|
1396
|
+
}
|
|
1397
|
+
const slippage = this._leverageUpSlippage((0, helpers_1.toBps)(slippage_), preview.targetLeverage);
|
|
1398
|
+
const { borrowAmount, borrowAssets, feeBps, targetLeverage } = preview;
|
|
1399
|
+
await this.assertLeverageBorrowCapacity(borrow, borrowAssets);
|
|
1071
1400
|
switch (type) {
|
|
1072
1401
|
case 'simple': {
|
|
1073
|
-
const borrowAssets = FormatConverter_1.default.decimalToBigInt(borrowAmount, borrow.asset.decimals);
|
|
1074
|
-
const feeBps = this.setup.feePolicy.getFeeBps({
|
|
1075
|
-
operation: 'deposit-and-leverage',
|
|
1076
|
-
inputToken: borrow.asset.address,
|
|
1077
|
-
outputToken: this.asset.address,
|
|
1078
|
-
inputAmount: borrowAssets,
|
|
1079
|
-
currentLeverage: this.getLeverage() ?? (0, decimal_js_1.default)(1),
|
|
1080
|
-
targetLeverage: multiplier,
|
|
1081
|
-
});
|
|
1082
1402
|
const feeReceiver = feeBps > 0n ? this.setup.feePolicy.feeReceiver : undefined;
|
|
1083
1403
|
const { action, quote } = await this.currentChainConfig.dexAgg.quoteAction(manager.address, borrow.asset.address, this.asset.address, borrowAssets, slippage, feeBps, feeReceiver);
|
|
1084
1404
|
// Fee-aware slippage expansion for `_swapSafe` is handled by
|
|
1085
1405
|
// KyberSwap.quoteAction. See KyberSwap.ts for rationale.
|
|
1086
1406
|
// Fee amplification: same pattern as leverageUp. See
|
|
1087
1407
|
// `amplifyContractSlippage` in helpers.ts.
|
|
1088
|
-
const contractSlippage = (0, helpers_1.amplifyContractSlippage)(slippage,
|
|
1408
|
+
const contractSlippage = (0, helpers_1.amplifyContractSlippage)(slippage, targetLeverage.sub(1), feeBps);
|
|
1089
1409
|
calldata = manager.getDepositAndLeverageCalldata(FormatConverter_1.default.decimalToBigInt(depositAmount, this.asset.decimals), {
|
|
1090
1410
|
borrowableCToken: borrow.address,
|
|
1091
1411
|
borrowAssets: borrowAssets,
|
|
1092
1412
|
cToken: this.address,
|
|
1093
|
-
expectedShares: this.virtualConvertToShares(BigInt(quote.min_out), LEVERAGE.SHARES_BUFFER_BPS),
|
|
1413
|
+
expectedShares: this.virtualConvertToShares(BigInt(quote.min_out), exports.LEVERAGE.SHARES_BUFFER_BPS),
|
|
1094
1414
|
swapAction: action,
|
|
1095
1415
|
auxData: "0x",
|
|
1096
1416
|
}, FormatConverter_1.default.bpsToBpsWad(contractSlippage));
|
|
@@ -1098,14 +1418,20 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1098
1418
|
}
|
|
1099
1419
|
case 'native-vault':
|
|
1100
1420
|
case 'vault': {
|
|
1421
|
+
// Mirrors the leverageUp vault branch: absorb (L-1) ×
|
|
1422
|
+
// LEVERAGE_UP_VAULT_DRIFT_BPS for vault-token collateral
|
|
1423
|
+
// drift. Uses `multiplier.sub(1)` per the per-call-site
|
|
1424
|
+
// asymmetry documented in helpers.ts (depositAndLeverage
|
|
1425
|
+
// leverageDelta = multiplier - 1).
|
|
1426
|
+
const contractSlippage = (0, helpers_1.amplifyContractSlippage)(slippage, targetLeverage.sub(1), exports.LEVERAGE.LEVERAGE_UP_VAULT_DRIFT_BPS);
|
|
1101
1427
|
calldata = manager.getDepositAndLeverageCalldata(FormatConverter_1.default.decimalToBigInt(depositAmount, this.asset.decimals), {
|
|
1102
1428
|
borrowableCToken: borrow.address,
|
|
1103
|
-
borrowAssets:
|
|
1429
|
+
borrowAssets: borrowAssets,
|
|
1104
1430
|
cToken: this.address,
|
|
1105
1431
|
expectedShares: await PositionManager_1.PositionManager.getVaultExpectedShares(this, borrow, borrowAmount),
|
|
1106
1432
|
swapAction: PositionManager_1.PositionManager.emptySwapAction(),
|
|
1107
1433
|
auxData: "0x",
|
|
1108
|
-
}, FormatConverter_1.default.bpsToBpsWad(
|
|
1434
|
+
}, FormatConverter_1.default.bpsToBpsWad(contractSlippage));
|
|
1109
1435
|
break;
|
|
1110
1436
|
}
|
|
1111
1437
|
default:
|
|
@@ -1115,7 +1441,6 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1115
1441
|
}
|
|
1116
1442
|
if (simulate)
|
|
1117
1443
|
return this.simulateOracleRoute(calldata, { to: manager.address });
|
|
1118
|
-
await this._checkPositionManagerApproval(manager);
|
|
1119
1444
|
return this.oracleRoute(calldata, { to: manager.address });
|
|
1120
1445
|
}
|
|
1121
1446
|
catch (error) {
|
|
@@ -1129,19 +1454,10 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1129
1454
|
amount = await this.ensureUnderlyingAmount(amount, zap);
|
|
1130
1455
|
const signer = this.requireSigner();
|
|
1131
1456
|
receiver ??= signer.address;
|
|
1132
|
-
const isZapping = typeof zap === 'object' && zap.type !== 'none';
|
|
1133
1457
|
const depositAssets = FormatConverter_1.default.decimalToBigInt(amount, this.asset.decimals);
|
|
1134
|
-
|
|
1135
|
-
if (isZapping && zap.inputToken) {
|
|
1136
|
-
const isNative = zap.inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase();
|
|
1137
|
-
const zapDecimals = isNative ? 18n : (() => {
|
|
1138
|
-
const inputErc20 = new ERC20_1.ERC20(this.provider, zap.inputToken, undefined, undefined, this.signer);
|
|
1139
|
-
return inputErc20.decimals ?? inputErc20.contract.decimals();
|
|
1140
|
-
})();
|
|
1141
|
-
zapAssets = FormatConverter_1.default.decimalToBigInt(amount, await zapDecimals);
|
|
1142
|
-
}
|
|
1458
|
+
const zapAssets = await this.getZapAssetAmount(amount, zap);
|
|
1143
1459
|
const default_calldata = this.getCallData("deposit", [depositAssets, receiver]);
|
|
1144
|
-
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, false, default_calldata);
|
|
1460
|
+
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, false, default_calldata, receiver);
|
|
1145
1461
|
return this.simulateOracleRoute(calldata, calldata_overrides);
|
|
1146
1462
|
}
|
|
1147
1463
|
catch (error) {
|
|
@@ -1153,33 +1469,27 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1153
1469
|
amount = await this.ensureUnderlyingAmount(amount, zap);
|
|
1154
1470
|
const signer = this.requireSigner();
|
|
1155
1471
|
receiver ??= signer.address;
|
|
1156
|
-
const isZapping = typeof zap === 'object' && zap.type !== 'none';
|
|
1157
1472
|
const depositAssets = FormatConverter_1.default.decimalToBigInt(amount, this.asset.decimals);
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
})();
|
|
1165
|
-
zapAssets = FormatConverter_1.default.decimalToBigInt(amount, await zapDecimals);
|
|
1166
|
-
}
|
|
1167
|
-
const default_calldata = this.getCallData("depositAsCollateral", [depositAssets, receiver]);
|
|
1168
|
-
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, true, default_calldata);
|
|
1473
|
+
const zapAssets = await this.getZapAssetAmount(amount, zap);
|
|
1474
|
+
const collateralMethod = receiver.toLowerCase() === signer.address.toLowerCase()
|
|
1475
|
+
? "depositAsCollateral"
|
|
1476
|
+
: "depositAsCollateralFor";
|
|
1477
|
+
const default_calldata = this.getCallData(collateralMethod, [depositAssets, receiver]);
|
|
1478
|
+
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, true, default_calldata, receiver);
|
|
1169
1479
|
return this.simulateOracleRoute(calldata, calldata_overrides);
|
|
1170
1480
|
}
|
|
1171
1481
|
catch (error) {
|
|
1172
1482
|
return { success: false, error: error?.reason || error?.message || String(error) };
|
|
1173
1483
|
}
|
|
1174
1484
|
}
|
|
1175
|
-
async zap(assets, zap, collateralize = false, default_calldata) {
|
|
1485
|
+
async zap(assets, zap, collateralize = false, default_calldata, receiver = this.requireSigner().address) {
|
|
1176
1486
|
let calldata;
|
|
1177
1487
|
let calldata_overrides = {};
|
|
1178
1488
|
let slippage = 0n;
|
|
1179
1489
|
let inputToken = null;
|
|
1180
1490
|
let type_of_zap;
|
|
1181
1491
|
if (typeof zap == 'object') {
|
|
1182
|
-
slippage =
|
|
1492
|
+
slippage = FormatConverter_1.default.percentageToBps(zap.slippage);
|
|
1183
1493
|
inputToken = zap.inputToken;
|
|
1184
1494
|
type_of_zap = zap.type;
|
|
1185
1495
|
}
|
|
@@ -1197,20 +1507,20 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1197
1507
|
case 'simple':
|
|
1198
1508
|
if (inputToken == null)
|
|
1199
1509
|
throw new Error("Input token must be provided for simple zap");
|
|
1200
|
-
calldata = await zapper.getSimpleZapCalldata(this, inputToken, this.asset.address, assets, collateralize, slippage);
|
|
1510
|
+
calldata = await zapper.getSimpleZapCalldata(this, inputToken, this.asset.address, assets, collateralize, slippage, receiver);
|
|
1201
1511
|
const isNativeSimpleZap = inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase();
|
|
1202
1512
|
calldata_overrides = isNativeSimpleZap ? { value: assets, to: zapper.address } : { to: zapper.address };
|
|
1203
1513
|
break;
|
|
1204
1514
|
case 'vault':
|
|
1205
|
-
calldata = await zapper.getVaultZapCalldata(this, assets, collateralize);
|
|
1515
|
+
calldata = await zapper.getVaultZapCalldata(this, assets, collateralize, false, receiver);
|
|
1206
1516
|
calldata_overrides = { to: zapper.address };
|
|
1207
1517
|
break;
|
|
1208
1518
|
case 'native-vault':
|
|
1209
|
-
calldata = await zapper.getNativeZapCalldata(this, assets, collateralize);
|
|
1519
|
+
calldata = await zapper.getNativeZapCalldata(this, assets, collateralize, false, receiver);
|
|
1210
1520
|
calldata_overrides = { value: assets, to: zapper.address };
|
|
1211
1521
|
break;
|
|
1212
1522
|
case 'native-simple':
|
|
1213
|
-
calldata = await zapper.getNativeZapCalldata(this, assets, collateralize, true);
|
|
1523
|
+
calldata = await zapper.getNativeZapCalldata(this, assets, collateralize, true, receiver);
|
|
1214
1524
|
calldata_overrides = { value: assets, to: zapper.address };
|
|
1215
1525
|
break;
|
|
1216
1526
|
default:
|
|
@@ -1222,84 +1532,50 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1222
1532
|
amount = await this.ensureUnderlyingAmount(amount, zap);
|
|
1223
1533
|
const signer = this.requireSigner();
|
|
1224
1534
|
receiver ??= signer.address;
|
|
1225
|
-
// When zapping, the swap amount uses input token decimals, but the
|
|
1226
|
-
// default deposit calldata uses the deposit token decimals.
|
|
1227
|
-
const isZapping = typeof zap === 'object' && zap.type !== 'none';
|
|
1228
1535
|
const depositAssets = FormatConverter_1.default.decimalToBigInt(amount, this.asset.decimals);
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
if (zap.inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase()) {
|
|
1232
|
-
zapAssets = FormatConverter_1.default.decimalToBigInt(amount, 18n);
|
|
1233
|
-
}
|
|
1234
|
-
else {
|
|
1235
|
-
const inputErc20 = new ERC20_1.ERC20(this.provider, zap.inputToken, undefined, undefined, this.signer);
|
|
1236
|
-
const zapDecimals = inputErc20.decimals ?? await inputErc20.contract.decimals();
|
|
1237
|
-
zapAssets = FormatConverter_1.default.decimalToBigInt(amount, zapDecimals);
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
const zapType = typeof zap == 'object' ? zap.type : zap;
|
|
1241
|
-
const isNative = zapType == 'native-simple' || zapType == 'native-vault' || zapType == 'none'
|
|
1242
|
-
|| (typeof zap == 'object' && zap.inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase());
|
|
1536
|
+
const zapAssets = await this.getZapAssetAmount(amount, zap);
|
|
1537
|
+
await this._checkDepositApprovals(zap, depositAssets, zapAssets);
|
|
1243
1538
|
const default_calldata = this.getCallData("deposit", [depositAssets, receiver]);
|
|
1244
|
-
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, false, default_calldata);
|
|
1245
|
-
|
|
1246
|
-
await this._checkAssetApproval(depositAssets);
|
|
1247
|
-
}
|
|
1248
|
-
else {
|
|
1249
|
-
const zapper = this.getZapper(zapType);
|
|
1250
|
-
if (!zapper) {
|
|
1251
|
-
throw new Error(`No zapper contract found for type '${zapType}' on ${this.symbol}`);
|
|
1252
|
-
}
|
|
1253
|
-
await this._checkDepositApprovals(zapper, zapAssets);
|
|
1254
|
-
}
|
|
1255
|
-
return this.oracleRoute(calldata, calldata_overrides);
|
|
1539
|
+
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, false, default_calldata, receiver);
|
|
1540
|
+
return this.oracleRoute(calldata, calldata_overrides, receiver);
|
|
1256
1541
|
}
|
|
1257
1542
|
async depositAsCollateral(amount, zap = 'none', receiver = null) {
|
|
1258
1543
|
amount = await this.ensureUnderlyingAmount(amount, zap);
|
|
1259
1544
|
const signer = this.requireSigner();
|
|
1260
1545
|
receiver ??= signer.address;
|
|
1261
|
-
// When zapping, the swap amount uses input token decimals, but collateral
|
|
1262
|
-
// cap checks and the default deposit calldata use the deposit token decimals.
|
|
1263
|
-
const isZapping = typeof zap === 'object' && zap.type !== 'none';
|
|
1264
1546
|
const depositAssets = FormatConverter_1.default.decimalToBigInt(amount, this.asset.decimals);
|
|
1265
|
-
|
|
1266
|
-
if (
|
|
1267
|
-
if (zap.inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase()) {
|
|
1268
|
-
zapAssets = FormatConverter_1.default.decimalToBigInt(amount, 18n);
|
|
1269
|
-
}
|
|
1270
|
-
else {
|
|
1271
|
-
const inputErc20 = new ERC20_1.ERC20(this.provider, zap.inputToken, undefined, undefined, this.signer);
|
|
1272
|
-
const zapDecimals = inputErc20.decimals ?? await inputErc20.contract.decimals();
|
|
1273
|
-
zapAssets = FormatConverter_1.default.decimalToBigInt(amount, zapDecimals);
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
if (!isZapping) {
|
|
1547
|
+
const zapAssets = await this.getZapAssetAmount(amount, zap);
|
|
1548
|
+
if (!this.isZapInstruction(zap)) {
|
|
1277
1549
|
const collateralCapError = "There is not enough collateral left in this tokens collateral cap for this deposit.";
|
|
1278
1550
|
const remainingCollateral = this.getRemainingCollateral(false);
|
|
1279
|
-
if (remainingCollateral
|
|
1551
|
+
if (remainingCollateral <= 0n)
|
|
1280
1552
|
throw new Error(collateralCapError);
|
|
1281
1553
|
if (remainingCollateral > 0n) {
|
|
1282
|
-
const shares = this.virtualConvertToShares(depositAssets
|
|
1554
|
+
const shares = this.virtualConvertToShares(depositAssets);
|
|
1283
1555
|
if (shares > remainingCollateral) {
|
|
1284
1556
|
throw new Error(collateralCapError);
|
|
1285
1557
|
}
|
|
1286
1558
|
}
|
|
1287
1559
|
}
|
|
1288
|
-
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1291
|
-
|
|
1560
|
+
await this._checkDepositApprovals(zap, depositAssets, zapAssets, true, receiver);
|
|
1561
|
+
const collateralMethod = receiver.toLowerCase() === signer.address.toLowerCase()
|
|
1562
|
+
? "depositAsCollateral"
|
|
1563
|
+
: "depositAsCollateralFor";
|
|
1564
|
+
const default_calldata = this.getCallData(collateralMethod, [depositAssets, receiver]);
|
|
1565
|
+
const { calldata, calldata_overrides } = await this.zap(zapAssets, zap, true, default_calldata, receiver);
|
|
1566
|
+
return this.oracleRoute(calldata, calldata_overrides, receiver);
|
|
1292
1567
|
}
|
|
1293
1568
|
async redeem(amount) {
|
|
1294
1569
|
const signer = this.requireSigner();
|
|
1295
1570
|
const receiver = signer.address;
|
|
1296
1571
|
const owner = signer.address;
|
|
1297
|
-
const buffer = this.
|
|
1572
|
+
const buffer = this.getExecutionDebtBufferTime();
|
|
1298
1573
|
const balance_avail = await this.balanceOf(signer.address);
|
|
1299
1574
|
const max_shares = await this.maxRedemption(true, buffer);
|
|
1300
1575
|
const converted_shares = this.convertTokenInputToShares(amount);
|
|
1301
|
-
|
|
1302
|
-
|
|
1576
|
+
const maxExecutableShares = max_shares < balance_avail ? max_shares : balance_avail;
|
|
1577
|
+
let shares = maxExecutableShares < converted_shares ? maxExecutableShares : converted_shares;
|
|
1578
|
+
if (maxExecutableShares === balance_avail && balance_avail - shares <= 10n) {
|
|
1303
1579
|
shares = balance_avail;
|
|
1304
1580
|
}
|
|
1305
1581
|
const calldata = this.getCallData("redeem", [shares, receiver, owner]);
|
|
@@ -1322,6 +1598,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1322
1598
|
const snapshot = await this.contract.getSnapshot(account);
|
|
1323
1599
|
return {
|
|
1324
1600
|
asset: snapshot.asset,
|
|
1601
|
+
underlying: snapshot.underlying,
|
|
1325
1602
|
decimals: BigInt(snapshot.decimals),
|
|
1326
1603
|
isCollateral: snapshot.isCollateral,
|
|
1327
1604
|
collateralPosted: BigInt(snapshot.collateralPosted),
|
|
@@ -1355,19 +1632,49 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1355
1632
|
const decimals = this.asset.decimals ?? this.decimals;
|
|
1356
1633
|
return FormatConverter_1.default.bigIntTokensToUsd(tokenAmount, price, decimals);
|
|
1357
1634
|
}
|
|
1358
|
-
|
|
1359
|
-
tokenAmount = this.virtualConvertToShares(tokenAmount);
|
|
1635
|
+
convertSharesToUsdSync(tokenAmount) {
|
|
1360
1636
|
const price = this.getPrice(false, false, false);
|
|
1361
1637
|
const decimals = this.decimals;
|
|
1362
1638
|
return FormatConverter_1.default.bigIntTokensToUsd(tokenAmount, price, decimals);
|
|
1363
1639
|
}
|
|
1364
|
-
|
|
1640
|
+
async fetchConvertSharesToUsd(tokenAmount) {
|
|
1641
|
+
await this.fetchPrice(false);
|
|
1642
|
+
await this.fetchDecimals();
|
|
1643
|
+
return this.convertSharesToUsdSync(tokenAmount);
|
|
1644
|
+
}
|
|
1645
|
+
async convertSharesToUsd(tokenAmount) {
|
|
1646
|
+
return this.convertSharesToUsdSync(tokenAmount);
|
|
1647
|
+
}
|
|
1648
|
+
buildMultiCallAction(calldata, target = this.address) {
|
|
1365
1649
|
return {
|
|
1366
|
-
target
|
|
1650
|
+
target,
|
|
1367
1651
|
isPriceUpdate: false,
|
|
1368
1652
|
data: calldata
|
|
1369
1653
|
};
|
|
1370
1654
|
}
|
|
1655
|
+
hasNonZeroNativeValueOverride(override) {
|
|
1656
|
+
const value = override.value;
|
|
1657
|
+
if (value == null) {
|
|
1658
|
+
return false;
|
|
1659
|
+
}
|
|
1660
|
+
if (typeof value === "bigint") {
|
|
1661
|
+
return value > 0n;
|
|
1662
|
+
}
|
|
1663
|
+
if (typeof value === "number") {
|
|
1664
|
+
return value > 0;
|
|
1665
|
+
}
|
|
1666
|
+
if (typeof value === "string") {
|
|
1667
|
+
return BigInt(value) > 0n;
|
|
1668
|
+
}
|
|
1669
|
+
return true;
|
|
1670
|
+
}
|
|
1671
|
+
assertOracleMulticallSupportsValue(priceUpdates, override) {
|
|
1672
|
+
if (priceUpdates.length === 0 || !this.hasNonZeroNativeValueOverride(override)) {
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
throw new Error("Native gas-token zaps cannot be combined with oracle price-update multicalls. " +
|
|
1676
|
+
"Use the wrapped-native zap path or retry when no oracle update is required.");
|
|
1677
|
+
}
|
|
1371
1678
|
async _checkPositionManagerApproval(manager) {
|
|
1372
1679
|
const isApproved = await this.isPluginApproved(manager.type, 'positionManager');
|
|
1373
1680
|
if (!isApproved) {
|
|
@@ -1375,71 +1682,172 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1375
1682
|
}
|
|
1376
1683
|
}
|
|
1377
1684
|
async _checkZapperApproval(zapper) {
|
|
1378
|
-
|
|
1379
|
-
|
|
1685
|
+
const plugin_allowed = await this.isPluginApproved(zapper.type, 'zapper');
|
|
1686
|
+
if (!plugin_allowed) {
|
|
1687
|
+
throw new Error(`Please approve the ${zapper.type} Zapper to be able to move ${this.symbol} on your behalf.`);
|
|
1380
1688
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
}
|
|
1689
|
+
}
|
|
1690
|
+
async _checkDelegateApproval(owner, delegate, delegateLabel) {
|
|
1691
|
+
const pluginAllowed = await this.contract.isDelegate(owner, delegate);
|
|
1692
|
+
if (!pluginAllowed) {
|
|
1693
|
+
throw new Error(`Please approve ${delegateLabel} as a delegate for ${this.symbol} on behalf of ${owner}.`);
|
|
1386
1694
|
}
|
|
1387
1695
|
}
|
|
1388
|
-
|
|
1389
|
-
const
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1696
|
+
getDepositAssetApprovalTarget() {
|
|
1697
|
+
const asset = this.getAsset(true);
|
|
1698
|
+
return {
|
|
1699
|
+
token: asset,
|
|
1700
|
+
spender: this.address,
|
|
1701
|
+
spenderLabel: this.symbol,
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
getPositionManagerDepositApprovalTarget(manager) {
|
|
1705
|
+
return {
|
|
1706
|
+
token: this.getAsset(true),
|
|
1707
|
+
spender: manager.address,
|
|
1708
|
+
spenderLabel: `${manager.type} PositionManager`,
|
|
1709
|
+
};
|
|
1710
|
+
}
|
|
1711
|
+
async resolveZapApprovalTarget(instructions) {
|
|
1712
|
+
const zapType = typeof instructions == 'object' ? instructions.type : instructions;
|
|
1713
|
+
if (zapType == 'none') {
|
|
1714
|
+
return null;
|
|
1715
|
+
}
|
|
1716
|
+
const spender = this.getPluginAddress(zapType, 'zapper');
|
|
1717
|
+
if (spender == null) {
|
|
1718
|
+
throw new Error("Plugin does not have an associated contract");
|
|
1719
|
+
}
|
|
1720
|
+
switch (zapType) {
|
|
1721
|
+
case 'native-vault':
|
|
1722
|
+
case 'native-simple':
|
|
1723
|
+
return null;
|
|
1724
|
+
case 'vault':
|
|
1725
|
+
return {
|
|
1726
|
+
token: await this.getVaultAsset(true),
|
|
1727
|
+
spender,
|
|
1728
|
+
spenderLabel: `${zapType} Zapper`,
|
|
1729
|
+
};
|
|
1730
|
+
case 'simple':
|
|
1731
|
+
if (typeof instructions != 'object') {
|
|
1732
|
+
throw new Error("Input token must be provided for simple zap approval");
|
|
1733
|
+
}
|
|
1734
|
+
if (instructions.inputToken.toLowerCase() === helpers_1.NATIVE_ADDRESS.toLowerCase()) {
|
|
1735
|
+
return null;
|
|
1736
|
+
}
|
|
1737
|
+
return {
|
|
1738
|
+
token: new ERC20_1.ERC20(this.provider, instructions.inputToken, undefined, undefined, this.signer),
|
|
1739
|
+
spender,
|
|
1740
|
+
spenderLabel: `${zapType} Zapper`,
|
|
1741
|
+
};
|
|
1395
1742
|
}
|
|
1396
1743
|
}
|
|
1397
|
-
async
|
|
1398
|
-
|
|
1744
|
+
async hasTokenApproval(target, amount) {
|
|
1745
|
+
const owner = this.getAccountOrThrow();
|
|
1746
|
+
const allowance = await target.token.allowance(owner, target.spender);
|
|
1747
|
+
return allowance >= amount;
|
|
1748
|
+
}
|
|
1749
|
+
async _checkTokenApproval(target, amount) {
|
|
1750
|
+
const allowance = await this.hasTokenApproval(target, amount);
|
|
1751
|
+
if (allowance) {
|
|
1399
1752
|
return;
|
|
1400
1753
|
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1754
|
+
let tokenLabel = target.token.symbol ?? target.token.address;
|
|
1755
|
+
if (target.token.symbol == undefined) {
|
|
1756
|
+
try {
|
|
1757
|
+
tokenLabel = await target.token.fetchSymbol();
|
|
1758
|
+
}
|
|
1759
|
+
catch {
|
|
1760
|
+
tokenLabel = target.token.address;
|
|
1761
|
+
}
|
|
1406
1762
|
}
|
|
1763
|
+
throw new Error(`Please approve the ${tokenLabel} token for ${target.spenderLabel}`);
|
|
1407
1764
|
}
|
|
1408
|
-
async _checkDepositApprovals(
|
|
1409
|
-
|
|
1410
|
-
|
|
1765
|
+
async _checkDepositApprovals(zap, depositAssets, zapAssets, collateralize = false, receiver = null) {
|
|
1766
|
+
const zapType = typeof zap == 'object' ? zap.type : zap;
|
|
1767
|
+
const signer = this.requireSigner();
|
|
1768
|
+
if (zapType != 'none') {
|
|
1769
|
+
const zapper = this.getZapper(zapType);
|
|
1770
|
+
if (!zapper) {
|
|
1771
|
+
throw new Error(`No zapper contract found for type '${zapType}' on ${this.symbol}`);
|
|
1772
|
+
}
|
|
1773
|
+
if (collateralize) {
|
|
1774
|
+
const receiverAddress = receiver ?? signer.address;
|
|
1775
|
+
if (receiverAddress.toLowerCase() === signer.address.toLowerCase()) {
|
|
1776
|
+
await this._checkZapperApproval(zapper);
|
|
1777
|
+
}
|
|
1778
|
+
else {
|
|
1779
|
+
await this._checkDelegateApproval(receiverAddress, signer.address, "the connected signer");
|
|
1780
|
+
await this._checkDelegateApproval(receiverAddress, zapper.address, `${zapper.type} Zapper`);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1411
1783
|
}
|
|
1412
|
-
if (
|
|
1413
|
-
await this.
|
|
1784
|
+
else if (collateralize && receiver && receiver.toLowerCase() !== signer.address.toLowerCase()) {
|
|
1785
|
+
await this._checkDelegateApproval(receiver, signer.address, "the connected signer");
|
|
1414
1786
|
}
|
|
1415
|
-
|
|
1787
|
+
const approvalTarget = zapType == 'none'
|
|
1788
|
+
? this.getDepositAssetApprovalTarget()
|
|
1789
|
+
: await this.resolveZapApprovalTarget(zap);
|
|
1790
|
+
if (approvalTarget == null) {
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
const approvalAmount = zapType == 'none' ? depositAssets : zapAssets;
|
|
1794
|
+
await this._checkTokenApproval(approvalTarget, approvalAmount);
|
|
1416
1795
|
}
|
|
1417
|
-
async oracleRoute(calldata, override = {}) {
|
|
1796
|
+
async oracleRoute(calldata, override = {}, reloadAccount = null) {
|
|
1418
1797
|
const signer = this.requireSigner();
|
|
1419
1798
|
const price_updates = await this.getPriceUpdates();
|
|
1799
|
+
this.assertOracleMulticallSupportsValue(price_updates, override);
|
|
1420
1800
|
if (price_updates.length > 0) {
|
|
1421
|
-
const
|
|
1801
|
+
const actionTarget = (override.to ?? this.address);
|
|
1802
|
+
const token_action = this.buildMultiCallAction(calldata, actionTarget);
|
|
1422
1803
|
calldata = this.getCallData("multicall", [[...price_updates, token_action]]);
|
|
1423
1804
|
}
|
|
1424
1805
|
const tx = await this.executeCallData(calldata, override);
|
|
1425
|
-
|
|
1806
|
+
if (typeof tx.wait === "function") {
|
|
1807
|
+
await tx.wait();
|
|
1808
|
+
}
|
|
1809
|
+
const refreshAccount = reloadAccount ?? signer.address;
|
|
1810
|
+
await this.market.reloadUserData(refreshAccount, {
|
|
1811
|
+
allowSignerMismatch: refreshAccount.toLowerCase() !== signer.address.toLowerCase(),
|
|
1812
|
+
});
|
|
1426
1813
|
return tx;
|
|
1427
1814
|
}
|
|
1428
1815
|
async simulateOracleRoute(calldata, override = {}) {
|
|
1429
1816
|
const price_updates = await this.getPriceUpdates();
|
|
1817
|
+
this.assertOracleMulticallSupportsValue(price_updates, override);
|
|
1430
1818
|
if (price_updates.length > 0) {
|
|
1431
|
-
const
|
|
1819
|
+
const actionTarget = (override.to ?? this.address);
|
|
1820
|
+
const token_action = this.buildMultiCallAction(calldata, actionTarget);
|
|
1432
1821
|
calldata = this.getCallData("multicall", [[...price_updates, token_action]]);
|
|
1433
1822
|
}
|
|
1434
1823
|
return this.simulateCallData(calldata, override);
|
|
1435
1824
|
}
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1825
|
+
getRedstonePriceUpdateTokens() {
|
|
1826
|
+
const candidates = (this.market?.tokens?.length ? this.market.tokens : [this]);
|
|
1827
|
+
const seenAssets = new Set();
|
|
1828
|
+
const tokens = [];
|
|
1829
|
+
for (const token of candidates) {
|
|
1830
|
+
if (!token.adapters?.includes(ProtocolReader_1.AdaptorTypes.REDSTONE_CORE)) {
|
|
1831
|
+
continue;
|
|
1832
|
+
}
|
|
1833
|
+
let assetAddress;
|
|
1834
|
+
try {
|
|
1835
|
+
assetAddress = token.getAsset(false);
|
|
1836
|
+
}
|
|
1837
|
+
catch {
|
|
1838
|
+
assetAddress = token.cache?.asset?.address ?? token.address;
|
|
1839
|
+
}
|
|
1840
|
+
const key = assetAddress.toLowerCase();
|
|
1841
|
+
if (seenAssets.has(key)) {
|
|
1842
|
+
continue;
|
|
1843
|
+
}
|
|
1844
|
+
seenAssets.add(key);
|
|
1845
|
+
tokens.push(token);
|
|
1441
1846
|
}
|
|
1442
|
-
return
|
|
1847
|
+
return tokens;
|
|
1848
|
+
}
|
|
1849
|
+
async getPriceUpdates() {
|
|
1850
|
+
return Promise.all(this.getRedstonePriceUpdateTokens().map((token) => Redstone_1.Redstone.buildMultiCallAction(token)));
|
|
1443
1851
|
}
|
|
1444
1852
|
}
|
|
1445
1853
|
exports.CToken = CToken;
|