curvance 4.0.1 → 4.0.3
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/abis/ProtocolReader.json +64 -0
- package/dist/classes/CToken.d.ts +33 -6
- package/dist/classes/CToken.d.ts.map +1 -1
- package/dist/classes/CToken.js +280 -57
- package/dist/classes/CToken.js.map +1 -1
- package/dist/classes/DexAggregators/IDexAgg.d.ts +3 -1
- package/dist/classes/DexAggregators/IDexAgg.d.ts.map +1 -1
- package/dist/classes/DexAggregators/Kuru.d.ts +3 -3
- package/dist/classes/DexAggregators/Kuru.d.ts.map +1 -1
- package/dist/classes/DexAggregators/Kuru.js +19 -9
- package/dist/classes/DexAggregators/Kuru.js.map +1 -1
- package/dist/classes/DexAggregators/KyberSwap.d.ts +3 -3
- package/dist/classes/DexAggregators/KyberSwap.d.ts.map +1 -1
- package/dist/classes/DexAggregators/KyberSwap.js +16 -10
- package/dist/classes/DexAggregators/KyberSwap.js.map +1 -1
- package/dist/classes/DexAggregators/MultiDexAgg.d.ts +3 -3
- package/dist/classes/DexAggregators/MultiDexAgg.d.ts.map +1 -1
- package/dist/classes/DexAggregators/MultiDexAgg.js +13 -13
- package/dist/classes/DexAggregators/MultiDexAgg.js.map +1 -1
- package/dist/classes/OracleManager.js.map +1 -1
- package/dist/classes/ProtocolReader.d.ts +10 -0
- package/dist/classes/ProtocolReader.d.ts.map +1 -1
- package/dist/classes/ProtocolReader.js +4 -0
- package/dist/classes/ProtocolReader.js.map +1 -1
- package/dist/classes/Zapper.d.ts.map +1 -1
- package/dist/classes/Zapper.js +37 -1
- package/dist/classes/Zapper.js.map +1 -1
- package/dist/contracts/monad-mainnet.json +1 -1
- package/dist/feePolicy.d.ts +156 -0
- package/dist/feePolicy.d.ts.map +1 -0
- package/dist/feePolicy.js +102 -0
- package/dist/feePolicy.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/setup.d.ts +8 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +3 -1
- package/dist/setup.js.map +1 -1
- package/package.json +1 -1
package/dist/classes/CToken.js
CHANGED
|
@@ -18,6 +18,87 @@ const NativeToken_1 = require("./NativeToken");
|
|
|
18
18
|
const ERC4626_1 = require("./ERC4626");
|
|
19
19
|
const FormatConverter_1 = __importDefault(require("./FormatConverter"));
|
|
20
20
|
const chains_1 = require("../chains");
|
|
21
|
+
const EXCLUDED_ZAP_SYMBOLS = new Set([
|
|
22
|
+
'eBTC', 'earnAUSD', 'vUSD', 'syzUSD', 'ezETH', 'YZM', 'wsrUSD', 'sAUSD',
|
|
23
|
+
]);
|
|
24
|
+
/**
|
|
25
|
+
* Leverage operation buffers — centralized for tuning.
|
|
26
|
+
* Calibrated for fresh-state operation via getLeverageSnapshot under
|
|
27
|
+
* Curvance's permanent single-oracle architecture.
|
|
28
|
+
*
|
|
29
|
+
* Single-oracle architecture (permanent design)
|
|
30
|
+
* ---------------------------------------------
|
|
31
|
+
* Curvance uses single-adaptor oracle configs only (Redstone Core/Classic
|
|
32
|
+
* via BaseOracleAdaptor, which ignores the getLower flag — see line 78 of
|
|
33
|
+
* BaseOracleAdaptor.sol). Dual-feed mode was deprecated in favor of the
|
|
34
|
+
* price-guard system and orderflow MEV tech, and is not coming back.
|
|
35
|
+
* This means MarketManager._statusOf returns symmetric prices for
|
|
36
|
+
* collateral (queries with getLower=true) and debt (getLower=false), so
|
|
37
|
+
* there is no oracle bound asymmetry contributing to checkSlippage forced
|
|
38
|
+
* loss. Buffers below are sized accordingly — do not re-introduce
|
|
39
|
+
* (L-1)-scaled buffers to "future-proof" against dual-feed.
|
|
40
|
+
*
|
|
41
|
+
* MEV / slippage protection model
|
|
42
|
+
* -------------------------------
|
|
43
|
+
* The on-chain BasePositionManager.checkSlippage modifier is per its own
|
|
44
|
+
* docstring "primarily a sanity check rather than a security guarantee."
|
|
45
|
+
* Real MEV protection comes from SwapperLib._swapSafe, which oracle-prices
|
|
46
|
+
* the swap input and output and reverts if realized slippage exceeds the
|
|
47
|
+
* Swap.slippage parameter we pass (= the user's raw slippage in WAD).
|
|
48
|
+
*
|
|
49
|
+
* That swap-level check bounds any sandwich extraction to the user's
|
|
50
|
+
* tolerance regardless of how the buffers below are tuned. The buffers
|
|
51
|
+
* here only adjust the contract-level sanity check so it doesn't fire
|
|
52
|
+
* false-positives from intentional or unavoidable forced losses.
|
|
53
|
+
*
|
|
54
|
+
* Asymmetry between leverage up and deleverage
|
|
55
|
+
* --------------------------------------------
|
|
56
|
+
* Leverage UP: under single-oracle, the contract sees zero forced loss
|
|
57
|
+
* for a perfect swap. The only real sources of difference between
|
|
58
|
+
* snapshot-time prices and execution-time prices are: (a) wei-level share
|
|
59
|
+
* rounding, (b) Redstone update drift between the snapshot RPC and the
|
|
60
|
+
* tx broadcast block. Both are small constants in absolute terms, NOT
|
|
61
|
+
* leverage-scaled. A small flat buffer suffices.
|
|
62
|
+
*
|
|
63
|
+
* DELEVERAGE (full): forced loss comes from intentional swap overshoot
|
|
64
|
+
* (DELEVERAGE_OVERHEAD_BPS) which prevents dust debt by oversizing the
|
|
65
|
+
* collateral→debt swap. This is a real bps-level loss in absolute terms
|
|
66
|
+
* which becomes (L-1) × bps in equity-fraction terms — so the deleverage
|
|
67
|
+
* contract-slippage expansion DOES scale with leverage. Note: the contract
|
|
68
|
+
* returns excess debt token to the user's wallet (BasePositionManager
|
|
69
|
+
* onRedeem lines 482-493), so the economic loss from the overshoot is
|
|
70
|
+
* zero — only the contract's naive equity-loss check sees it as loss.
|
|
71
|
+
*/
|
|
72
|
+
const LEVERAGE = {
|
|
73
|
+
/** Max leverage cap: fraction of theoretical max the user can select.
|
|
74
|
+
* Prevents boundary singularity at exact max leverage. Independent of
|
|
75
|
+
* the slippage buffers below — protects post-op position health, not
|
|
76
|
+
* in-op slippage. */
|
|
77
|
+
MAX_LEVERAGE_FACTOR: (0, decimal_js_1.default)(0.995),
|
|
78
|
+
/** Flat BPS buffer added to leverage-up contract slippage tolerance.
|
|
79
|
+
* Under single-oracle, the only forced loss comes from wei-level share
|
|
80
|
+
* rounding plus possible Redstone price drift between the snapshot RPC
|
|
81
|
+
* and the tx broadcast block (typically same-block or 1-3 blocks
|
|
82
|
+
* later). Both are small constants in absolute terms; the equity-
|
|
83
|
+
* fraction amplification at high leverage happens automatically inside
|
|
84
|
+
* checkSlippage's denominator and does not require leverage-scaling
|
|
85
|
+
* the buffer itself. Conservative starting value — reduce after
|
|
86
|
+
* empirically observing successful leverage-up across the leverage
|
|
87
|
+
* range, especially at L > 5 with low (1%) user slippage. */
|
|
88
|
+
LEVERAGE_UP_BUFFER_BPS: 10n,
|
|
89
|
+
/** BPS overhead on full deleverage swap sizing — absolute terms.
|
|
90
|
+
* Oversizes the collateral→debt swap so DEX impact + drift doesn't
|
|
91
|
+
* underdeliver and leave dust debt. The contract returns any excess
|
|
92
|
+
* debt token to the user, so economic loss is zero — but the contract's
|
|
93
|
+
* checkSlippage modifier sees the overshoot as equity loss and amplifies
|
|
94
|
+
* it by (L-1)x. The deleverage contract slippage expansion compensates
|
|
95
|
+
* for that amplification (see leverageDown). Bump when aggregator fees
|
|
96
|
+
* are enabled to keep dust prevention reliable. */
|
|
97
|
+
DELEVERAGE_OVERHEAD_BPS: 20n,
|
|
98
|
+
/** BPS buffer on virtualConvertToShares for leverage + collateral cap.
|
|
99
|
+
* Covers exchange rate drift from interest accrual since cache load. */
|
|
100
|
+
SHARES_BUFFER_BPS: 2n,
|
|
101
|
+
};
|
|
21
102
|
class CToken extends Calldata_1.Calldata {
|
|
22
103
|
provider;
|
|
23
104
|
address;
|
|
@@ -45,16 +126,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
45
126
|
this.isNativeVault = chain_config.native_vaults.some(vault => vault.contract.toLowerCase() == assetAddr);
|
|
46
127
|
this.isVault = chain_config.vaults.some(vault => vault.contract.toLowerCase() == assetAddr);
|
|
47
128
|
this.isWrappedNative = chain_config.wrapped_native.toLowerCase() == assetAddr;
|
|
48
|
-
if (
|
|
49
|
-
'csAUSD',
|
|
50
|
-
'cwsrUSD',
|
|
51
|
-
'cezETH',
|
|
52
|
-
'csyzUSD',
|
|
53
|
-
'cearnAUSD',
|
|
54
|
-
'cYZM',
|
|
55
|
-
'cvUSD',
|
|
56
|
-
'ceBTC'
|
|
57
|
-
].includes(this.symbol)) {
|
|
129
|
+
if (EXCLUDED_ZAP_SYMBOLS.has(this.asset.symbol)) {
|
|
58
130
|
return;
|
|
59
131
|
}
|
|
60
132
|
if (this.isNativeVault)
|
|
@@ -88,7 +160,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
88
160
|
// to account for share rounding and fee losses that prevent reaching the exact max.
|
|
89
161
|
const theoretical = (0, decimal_js_1.default)(this.cache.maxLeverage).div(helpers_1.BPS);
|
|
90
162
|
const factor = theoretical.sub(1);
|
|
91
|
-
return (0, decimal_js_1.default)(1).add(factor.mul(
|
|
163
|
+
return (0, decimal_js_1.default)(1).add(factor.mul(LEVERAGE.MAX_LEVERAGE_FACTOR));
|
|
92
164
|
}
|
|
93
165
|
get canLeverage() { return this.leverageTypes.length > 0; }
|
|
94
166
|
get totalAssets() { return this.cache.totalAssets; }
|
|
@@ -105,8 +177,15 @@ class CToken extends Calldata_1.Calldata {
|
|
|
105
177
|
virtualConvertToAssets(shares) {
|
|
106
178
|
return (shares * this.totalAssets) / this.totalSupply;
|
|
107
179
|
}
|
|
108
|
-
|
|
109
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Convert assets to shares using cached totalSupply/totalAssets.
|
|
182
|
+
* @param bufferBps Optional downward buffer in BPS to account for
|
|
183
|
+
* exchange rate drift from interest accrual since cache load.
|
|
184
|
+
* Matches the buffer pattern in async convertToShares().
|
|
185
|
+
*/
|
|
186
|
+
virtualConvertToShares(assets, bufferBps = 0n) {
|
|
187
|
+
const shares = (assets * this.totalSupply) / this.totalAssets;
|
|
188
|
+
return bufferBps > 0n ? shares * (10000n - bufferBps) / 10000n : shares;
|
|
110
189
|
}
|
|
111
190
|
getLeverage() {
|
|
112
191
|
if (this.getUserCollateral(true).equals(0)) {
|
|
@@ -391,11 +470,17 @@ class CToken extends Calldata_1.Calldata {
|
|
|
391
470
|
async fetchPrice(asset = false, getLower = false, inUSD = true) {
|
|
392
471
|
const priceForAddress = asset ? this.asset.address : this.address;
|
|
393
472
|
const price = await this.market.oracle_manager.getPrice(priceForAddress, inUSD, getLower);
|
|
394
|
-
if (
|
|
395
|
-
|
|
473
|
+
if (asset) {
|
|
474
|
+
if (getLower)
|
|
475
|
+
this.cache.assetPriceLower = price;
|
|
476
|
+
else
|
|
477
|
+
this.cache.assetPrice = price;
|
|
396
478
|
}
|
|
397
479
|
else {
|
|
398
|
-
|
|
480
|
+
if (getLower)
|
|
481
|
+
this.cache.sharePriceLower = price;
|
|
482
|
+
else
|
|
483
|
+
this.cache.sharePrice = price;
|
|
399
484
|
}
|
|
400
485
|
return price;
|
|
401
486
|
}
|
|
@@ -611,6 +696,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
611
696
|
tokens_exclude.push(helpers_1.NATIVE_ADDRESS.toLowerCase());
|
|
612
697
|
}
|
|
613
698
|
}
|
|
699
|
+
tokens = tokens.filter(token => token.type === 'none' || !EXCLUDED_ZAP_SYMBOLS.has(token.interface.symbol ?? ''));
|
|
614
700
|
if (search) {
|
|
615
701
|
const lowerSearch = search.toLowerCase();
|
|
616
702
|
tokens = tokens.filter(token => (token.interface.name ?? '').toLowerCase().includes(lowerSearch) ||
|
|
@@ -624,18 +710,43 @@ class CToken extends Calldata_1.Calldata {
|
|
|
624
710
|
return this.market.reader.hypotheticalRedemptionOf(signer.address, this, shares);
|
|
625
711
|
}
|
|
626
712
|
/**
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
*
|
|
713
|
+
* Single-RPC snapshot of fresh position state for leverage operations.
|
|
714
|
+
* Calls ProtocolReader.getLeverageSnapshot which internally uses
|
|
715
|
+
* hypotheticalLiquidityOf for aggregate position + fresh oracle prices
|
|
716
|
+
* + projected debt balance. Updates the local cache so downstream
|
|
717
|
+
* preview computations (previewLeverageUp/Down) read fresh values.
|
|
718
|
+
*
|
|
719
|
+
* Returns the snapshot for direct use where needed (e.g. debtTokenBalance
|
|
720
|
+
* for full deleverage swap sizing).
|
|
721
|
+
*/
|
|
722
|
+
async _getLeverageSnapshot(borrow) {
|
|
723
|
+
const signer = (0, helpers_1.validateProviderAsSigner)(this.provider);
|
|
724
|
+
const snapshot = await this.market.reader.getLeverageSnapshot(signer.address, this.address, borrow.address, 120n);
|
|
725
|
+
if (snapshot.oracleError) {
|
|
726
|
+
throw new Error(`Oracle error fetching leverage snapshot for ${this.symbol}/${borrow.symbol}`);
|
|
727
|
+
}
|
|
728
|
+
// Update cache so preview functions read fresh values
|
|
729
|
+
this.cache.assetPrice = snapshot.collateralAssetPrice;
|
|
730
|
+
this.cache.sharePrice = snapshot.sharePrice;
|
|
731
|
+
borrow.cache.assetPrice = snapshot.debtAssetPrice;
|
|
732
|
+
this.market.cache.user.debt = snapshot.debtUsd;
|
|
733
|
+
return snapshot;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Compute slippage BPS for the contract's checkSlippage modifier when
|
|
737
|
+
* leveraging up. Under Curvance's permanent single-oracle architecture
|
|
738
|
+
* with fresh state from _getLeverageSnapshot, the only forced equity
|
|
739
|
+
* loss comes from wei-level share rounding plus possible Redstone price
|
|
740
|
+
* drift between snapshot RPC and tx broadcast — both small constants
|
|
741
|
+
* in absolute terms. We add a small flat buffer; the contract's
|
|
742
|
+
* equity-fraction denominator amplifies it by (L-1)x automatically.
|
|
743
|
+
* The user's swap-level slippage (passed separately to _swapSafe) is
|
|
744
|
+
* unaffected — that's the layer that bounds MEV extraction.
|
|
631
745
|
*/
|
|
632
746
|
_leverageUpSlippage(slippage, leverage) {
|
|
633
|
-
|
|
634
|
-
if (leverageFactor.lte(0))
|
|
747
|
+
if (leverage.lte(1))
|
|
635
748
|
return slippage;
|
|
636
|
-
|
|
637
|
-
const buffer = BigInt(leverageFactor.mul(20).ceil().toFixed(0));
|
|
638
|
-
return slippage + buffer;
|
|
749
|
+
return slippage + LEVERAGE.LEVERAGE_UP_BUFFER_BPS;
|
|
639
750
|
}
|
|
640
751
|
previewLeverageUp(newLeverage, borrow, depositAmount) {
|
|
641
752
|
const currentLeverage = this.getLeverage() ?? (0, decimal_js_1.default)(0);
|
|
@@ -649,29 +760,35 @@ class CToken extends Calldata_1.Calldata {
|
|
|
649
760
|
const collateralInUsd = this.convertTokensToUsd(collateralAvail, false);
|
|
650
761
|
const currentDebt = this.market.userDebt;
|
|
651
762
|
const notional = collateralInUsd.sub(currentDebt);
|
|
652
|
-
// Cap effective leverage slightly below target to account for protocol
|
|
653
|
-
// leverage fee and rounding losses. The fee reduces collateral gained
|
|
654
|
-
// relative to debt incurred, causing equity loss ≈ fee% × (leverage-1).
|
|
655
|
-
// Capping at 98% of the leverage factor ensures the on-chain slippage
|
|
656
|
-
// check passes even at max leverage.
|
|
657
763
|
const leverageFactor = newLeverage.sub(1);
|
|
658
764
|
const borrowPrice = borrow.getPrice(true);
|
|
659
|
-
// Raw borrow amount — what the user actually owes as debt
|
|
660
765
|
const rawDebtInUsd = notional.mul(newLeverage).sub(notional);
|
|
661
|
-
const
|
|
662
|
-
// Reduced borrow amount — what we send to the contract to avoid
|
|
663
|
-
// tripping the on-chain slippage check at max leverage
|
|
664
|
-
const effectiveLeverage = (0, decimal_js_1.default)(1).add(leverageFactor.mul((0, decimal_js_1.default)(0.99)));
|
|
665
|
-
const effectiveDebtInUsd = notional.mul(effectiveLeverage).sub(notional);
|
|
666
|
-
const borrowAmount = effectiveDebtInUsd.sub(currentDebt).div(borrowPrice);
|
|
766
|
+
const borrowAmount = rawDebtInUsd.sub(currentDebt).div(borrowPrice);
|
|
667
767
|
const newCollateralInUsd = notional.add(rawDebtInUsd);
|
|
768
|
+
// Fee preview: queried from the configured fee policy. Returned as
|
|
769
|
+
// ancillary fields so callers can display "you'll be charged $X in
|
|
770
|
+
// fees" without requiring the SDK's primary preview math (which
|
|
771
|
+
// preserves the equity-conservation invariant) to change.
|
|
772
|
+
const borrowAssets = FormatConverter_1.default.decimalToBigInt(borrowAmount, borrow.asset.decimals);
|
|
773
|
+
const feeBps = setup_1.setup_config.feePolicy.getFeeBps({
|
|
774
|
+
operation: 'leverage-up',
|
|
775
|
+
inputToken: borrow.asset.address,
|
|
776
|
+
outputToken: this.asset.address,
|
|
777
|
+
inputAmount: borrowAssets,
|
|
778
|
+
currentLeverage,
|
|
779
|
+
targetLeverage: newLeverage,
|
|
780
|
+
});
|
|
781
|
+
const feeAssets = borrowAmount.mul((0, decimal_js_1.default)(Number(feeBps))).div((0, decimal_js_1.default)(10000));
|
|
782
|
+
const feeUsd = feeAssets.mul(borrowPrice);
|
|
668
783
|
return {
|
|
669
784
|
borrowAmount,
|
|
670
|
-
rawBorrowAmount,
|
|
671
785
|
newDebt: rawDebtInUsd,
|
|
672
786
|
newDebtInAssets: borrow.convertUsdToTokens(rawDebtInUsd, true),
|
|
673
787
|
newCollateral: newCollateralInUsd,
|
|
674
|
-
newCollateralInAssets: this.convertUsdToTokens(newCollateralInUsd, true)
|
|
788
|
+
newCollateralInAssets: this.convertUsdToTokens(newCollateralInUsd, true),
|
|
789
|
+
feeBps,
|
|
790
|
+
feeAssets,
|
|
791
|
+
feeUsd,
|
|
675
792
|
};
|
|
676
793
|
}
|
|
677
794
|
previewLeverageDown(newLeverage, currentLeverage, borrow) {
|
|
@@ -690,6 +807,25 @@ class CToken extends Calldata_1.Calldata {
|
|
|
690
807
|
const collateralAssetReductionUsd = collateralInUsd.sub(targetCollateralUsd);
|
|
691
808
|
const collateralAssetReduction = FormatConverter_1.default.decimalToBigInt(collateralAssetReductionUsd.div(this.getPrice(true)), this.asset.decimals);
|
|
692
809
|
const leverageDiff = (0, decimal_js_1.default)(1).sub(newLeverage.div(currentLeverage));
|
|
810
|
+
// Fee preview: queried from the configured fee policy. The fee is
|
|
811
|
+
// taken on the collateral→debt swap; size of the swap depends on
|
|
812
|
+
// whether this is a partial or full deleverage. We use
|
|
813
|
+
// collateralAssetReductionUsd as the swap notional approximation
|
|
814
|
+
// (exact for partial; for full deleverage the actual swap is sized
|
|
815
|
+
// by leverageDown using the snapshot, but the preview is close enough
|
|
816
|
+
// for display purposes).
|
|
817
|
+
const feeBps = borrow ? setup_1.setup_config.feePolicy.getFeeBps({
|
|
818
|
+
operation: 'leverage-down',
|
|
819
|
+
inputToken: this.asset.address,
|
|
820
|
+
outputToken: borrow.asset.address,
|
|
821
|
+
inputAmount: collateralAssetReduction,
|
|
822
|
+
currentLeverage,
|
|
823
|
+
targetLeverage: newLeverage,
|
|
824
|
+
}) : 0n;
|
|
825
|
+
const feeUsd = collateralAssetReductionUsd.mul((0, decimal_js_1.default)(Number(feeBps))).div((0, decimal_js_1.default)(10000));
|
|
826
|
+
const feeAssets = this.getPrice(true).gt(0)
|
|
827
|
+
? feeUsd.div(this.getPrice(true))
|
|
828
|
+
: (0, decimal_js_1.default)(0);
|
|
693
829
|
return {
|
|
694
830
|
collateralAssetReduction,
|
|
695
831
|
collateralAssetReductionUsd,
|
|
@@ -697,7 +833,10 @@ class CToken extends Calldata_1.Calldata {
|
|
|
697
833
|
newDebt: newDebtUsd,
|
|
698
834
|
newDebtInAssets: borrow ? borrow.convertUsdToTokens(newDebtUsd, true) : undefined,
|
|
699
835
|
newCollateral: targetCollateralUsd,
|
|
700
|
-
newCollateralInAssets: this.convertUsdToTokens(targetCollateralUsd, true)
|
|
836
|
+
newCollateralInAssets: this.convertUsdToTokens(targetCollateralUsd, true),
|
|
837
|
+
feeBps,
|
|
838
|
+
feeAssets,
|
|
839
|
+
feeUsd,
|
|
701
840
|
};
|
|
702
841
|
}
|
|
703
842
|
async leverageUp(borrow, newLeverage, type, slippage_ = (0, decimal_js_1.default)(0.05), simulate = false) {
|
|
@@ -706,15 +845,26 @@ class CToken extends Calldata_1.Calldata {
|
|
|
706
845
|
const slippage = this._leverageUpSlippage(FormatConverter_1.default.percentageToBps(slippage_), newLeverage);
|
|
707
846
|
const manager = this.getPositionManager(type);
|
|
708
847
|
let calldata;
|
|
848
|
+
await this._getLeverageSnapshot(borrow);
|
|
709
849
|
const { borrowAmount } = this.previewLeverageUp(newLeverage, borrow);
|
|
710
850
|
switch (type) {
|
|
711
851
|
case 'simple': {
|
|
712
|
-
const
|
|
852
|
+
const borrowAssets = FormatConverter_1.default.decimalToBigInt(borrowAmount, borrow.asset.decimals);
|
|
853
|
+
const feeBps = setup_1.setup_config.feePolicy.getFeeBps({
|
|
854
|
+
operation: 'leverage-up',
|
|
855
|
+
inputToken: borrow.asset.address,
|
|
856
|
+
outputToken: this.asset.address,
|
|
857
|
+
inputAmount: borrowAssets,
|
|
858
|
+
currentLeverage: this.getLeverage() ?? (0, decimal_js_1.default)(1),
|
|
859
|
+
targetLeverage: newLeverage,
|
|
860
|
+
});
|
|
861
|
+
const feeReceiver = feeBps > 0n ? setup_1.setup_config.feePolicy.feeReceiver : undefined;
|
|
862
|
+
const { action, quote } = await chains_1.chain_config[setup_1.setup_config.chain].dexAgg.quoteAction(manager.address, borrow.asset.address, this.asset.address, borrowAssets, slippage, feeBps, feeReceiver);
|
|
713
863
|
calldata = manager.getLeverageCalldata({
|
|
714
864
|
borrowableCToken: borrow.address,
|
|
715
865
|
borrowAssets: FormatConverter_1.default.decimalToBigInt(borrowAmount, borrow.asset.decimals),
|
|
716
866
|
cToken: this.address,
|
|
717
|
-
expectedShares: this.virtualConvertToShares(BigInt(quote.min_out)),
|
|
867
|
+
expectedShares: this.virtualConvertToShares(BigInt(quote.min_out), LEVERAGE.SHARES_BUFFER_BPS),
|
|
718
868
|
swapAction: action,
|
|
719
869
|
auxData: "0x",
|
|
720
870
|
}, FormatConverter_1.default.bpsToBpsWad(slippage));
|
|
@@ -760,24 +910,77 @@ class CToken extends Calldata_1.Calldata {
|
|
|
760
910
|
const slippage = (0, helpers_1.toBps)(slippage_);
|
|
761
911
|
const manager = this.getPositionManager(type);
|
|
762
912
|
let calldata;
|
|
913
|
+
const snapshot = await this._getLeverageSnapshot(borrowToken);
|
|
763
914
|
const { collateralAssetReduction } = this.previewLeverageDown(newLeverage, currentLeverage);
|
|
764
915
|
const isFullDeleverage = newLeverage.equals(1);
|
|
765
|
-
const repay_balance = isFullDeleverage ? await borrowToken.fetchDebtBalanceAtTimestamp(100n, false) : null;
|
|
766
916
|
switch (type) {
|
|
767
917
|
case 'simple': {
|
|
768
918
|
let swapCollateral = collateralAssetReduction;
|
|
919
|
+
// Resolve fee policy once for this operation. The fee bps
|
|
920
|
+
// contributes to the deleverage overhead because KyberSwap
|
|
921
|
+
// deducts the fee from the swap input before swapping —
|
|
922
|
+
// effective swap input = swapCollateral × (1 - feeBps).
|
|
923
|
+
// We must oversize swapCollateral to compensate, otherwise
|
|
924
|
+
// the post-fee swap underdelivers and dust debt remains.
|
|
925
|
+
//
|
|
926
|
+
// Order-of-operations note: we pass collateralAssetReduction
|
|
927
|
+
// as the inputAmount estimate. For partial deleverage this
|
|
928
|
+
// is the actual swap size; for full deleverage the actual
|
|
929
|
+
// size is computed below from the snapshot and is slightly
|
|
930
|
+
// larger. flatFeePolicy ignores inputAmount, so this is
|
|
931
|
+
// exact for current callers. Future notional-tiered policies
|
|
932
|
+
// should be aware that for full deleverage the inputAmount
|
|
933
|
+
// passed here is an underestimate.
|
|
934
|
+
const feeBps = setup_1.setup_config.feePolicy.getFeeBps({
|
|
935
|
+
operation: 'leverage-down',
|
|
936
|
+
inputToken: this.asset.address,
|
|
937
|
+
outputToken: borrowToken.asset.address,
|
|
938
|
+
inputAmount: collateralAssetReduction,
|
|
939
|
+
currentLeverage: currentLeverage,
|
|
940
|
+
targetLeverage: newLeverage,
|
|
941
|
+
});
|
|
942
|
+
const feeReceiver = feeBps > 0n ? setup_1.setup_config.feePolicy.feeReceiver : undefined;
|
|
769
943
|
if (isFullDeleverage) {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
944
|
+
// Use exact projected debt from snapshot to size the swap.
|
|
945
|
+
// debtTokenBalance is in debt-token native decimals, projected
|
|
946
|
+
// forward by bufferTime. Convert to collateral-asset terms via
|
|
947
|
+
// snapshot prices (lower-bound collateral, standard debt — both
|
|
948
|
+
// conservative, overshooting slightly). Overhead covers DEX
|
|
949
|
+
// routing impact + oracle drift + fee deduction.
|
|
950
|
+
const debtDecimals = 10n ** borrowToken.asset.decimals;
|
|
951
|
+
const collDecimals = 10n ** this.asset.decimals;
|
|
952
|
+
const debtInCollateral = (snapshot.debtTokenBalance * snapshot.debtAssetPrice * collDecimals) / (snapshot.collateralAssetPrice * debtDecimals);
|
|
953
|
+
// Total overhead = base overhead (DEX impact + drift) + fee bps.
|
|
954
|
+
// Additive approximation is accurate to sub-bp at typical
|
|
955
|
+
// fee+overhead magnitudes (< 100 bps combined).
|
|
956
|
+
const overheadBps = LEVERAGE.DELEVERAGE_OVERHEAD_BPS + feeBps;
|
|
957
|
+
swapCollateral = debtInCollateral * (10000n + overheadBps) / 10000n;
|
|
958
|
+
const maxCollateral = this.virtualConvertToAssets(this.cache.userCollateral);
|
|
959
|
+
if (swapCollateral > maxCollateral) {
|
|
960
|
+
swapCollateral = maxCollateral;
|
|
773
961
|
}
|
|
774
962
|
}
|
|
775
|
-
const { action, quote } = await config.dexAgg.quoteAction(manager.address, this.asset.address, borrowToken.asset.address, swapCollateral, slippage);
|
|
776
|
-
const minRepay = isFullDeleverage ? 1n : quote.
|
|
777
|
-
//
|
|
778
|
-
//
|
|
963
|
+
const { action, quote } = await config.dexAgg.quoteAction(manager.address, this.asset.address, borrowToken.asset.address, swapCollateral, slippage, feeBps, feeReceiver);
|
|
964
|
+
const minRepay = isFullDeleverage ? 1n : quote.min_out;
|
|
965
|
+
// Full deleverage oversizes the swap by (DELEVERAGE_OVERHEAD_BPS +
|
|
966
|
+
// feeBps) in absolute terms to prevent dust debt. The contract's
|
|
967
|
+
// checkSlippage modifier compares equity-before vs equity-after
|
|
968
|
+
// as a fraction of starting equity, so the absolute overshoot
|
|
969
|
+
// becomes (L-1) × overhead in equity-fraction terms. We expand
|
|
970
|
+
// the contract slippage tolerance by exactly that forced amount,
|
|
971
|
+
// leaving the user's `slippage` budget available for variable
|
|
972
|
+
// DEX impact + oracle drift.
|
|
973
|
+
//
|
|
974
|
+
// This does NOT loosen MEV protection — that lives at the
|
|
975
|
+
// _swapSafe layer (which still receives raw user slippage).
|
|
976
|
+
// The contract checkSlippage is sanity-only per its docstring.
|
|
977
|
+
// Note: the contract returns excess debt token to the user's
|
|
978
|
+
// wallet, so the economic loss from the overshoot is zero.
|
|
779
979
|
const contractSlippage = isFullDeleverage
|
|
780
|
-
? slippage +
|
|
980
|
+
? slippage + BigInt(currentLeverage.sub(1)
|
|
981
|
+
.mul(Number(LEVERAGE.DELEVERAGE_OVERHEAD_BPS + feeBps))
|
|
982
|
+
.ceil()
|
|
983
|
+
.toFixed(0))
|
|
781
984
|
: slippage;
|
|
782
985
|
calldata = manager.getDeleverageCalldata({
|
|
783
986
|
cToken: this.address,
|
|
@@ -817,15 +1020,26 @@ class CToken extends Calldata_1.Calldata {
|
|
|
817
1020
|
const manager = this.getPositionManager(type);
|
|
818
1021
|
let calldata;
|
|
819
1022
|
const depositAssets = FormatConverter_1.default.decimalToBigInt(depositAmount, this.asset.decimals);
|
|
1023
|
+
await this._getLeverageSnapshot(borrow);
|
|
820
1024
|
const { borrowAmount } = this.previewLeverageUp(multiplier, borrow, depositAssets);
|
|
821
1025
|
switch (type) {
|
|
822
1026
|
case 'simple': {
|
|
823
|
-
const
|
|
1027
|
+
const borrowAssets = FormatConverter_1.default.decimalToBigInt(borrowAmount, borrow.asset.decimals);
|
|
1028
|
+
const feeBps = setup_1.setup_config.feePolicy.getFeeBps({
|
|
1029
|
+
operation: 'deposit-and-leverage',
|
|
1030
|
+
inputToken: borrow.asset.address,
|
|
1031
|
+
outputToken: this.asset.address,
|
|
1032
|
+
inputAmount: borrowAssets,
|
|
1033
|
+
currentLeverage: this.getLeverage() ?? (0, decimal_js_1.default)(1),
|
|
1034
|
+
targetLeverage: multiplier,
|
|
1035
|
+
});
|
|
1036
|
+
const feeReceiver = feeBps > 0n ? setup_1.setup_config.feePolicy.feeReceiver : undefined;
|
|
1037
|
+
const { action, quote } = await chains_1.chain_config[setup_1.setup_config.chain].dexAgg.quoteAction(manager.address, borrow.asset.address, this.asset.address, borrowAssets, slippage, feeBps, feeReceiver);
|
|
824
1038
|
calldata = manager.getDepositAndLeverageCalldata(FormatConverter_1.default.decimalToBigInt(depositAmount, this.asset.decimals), {
|
|
825
1039
|
borrowableCToken: borrow.address,
|
|
826
|
-
borrowAssets:
|
|
1040
|
+
borrowAssets: borrowAssets,
|
|
827
1041
|
cToken: this.address,
|
|
828
|
-
expectedShares: this.virtualConvertToShares(BigInt(quote.min_out)),
|
|
1042
|
+
expectedShares: this.virtualConvertToShares(BigInt(quote.min_out), LEVERAGE.SHARES_BUFFER_BPS),
|
|
829
1043
|
swapAction: action,
|
|
830
1044
|
auxData: "0x",
|
|
831
1045
|
}, FormatConverter_1.default.bpsToBpsWad(slippage));
|
|
@@ -850,6 +1064,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
850
1064
|
}
|
|
851
1065
|
if (simulate)
|
|
852
1066
|
return this.simulateOracleRoute(calldata, { to: manager.address });
|
|
1067
|
+
await this._checkPositionManagerApproval(manager);
|
|
853
1068
|
return this.oracleRoute(calldata, { to: manager.address });
|
|
854
1069
|
}
|
|
855
1070
|
catch (error) {
|
|
@@ -1013,7 +1228,7 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1013
1228
|
if (remainingCollateral == 0n)
|
|
1014
1229
|
throw new Error(collateralCapError);
|
|
1015
1230
|
if (remainingCollateral > 0n) {
|
|
1016
|
-
const shares = this.virtualConvertToShares(depositAssets);
|
|
1231
|
+
const shares = this.virtualConvertToShares(depositAssets, LEVERAGE.SHARES_BUFFER_BPS);
|
|
1017
1232
|
if (shares > remainingCollateral) {
|
|
1018
1233
|
throw new Error(collateralCapError);
|
|
1019
1234
|
}
|
|
@@ -1066,7 +1281,13 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1066
1281
|
}
|
|
1067
1282
|
convertTokensToUsd(tokenAmount, asset = true) {
|
|
1068
1283
|
const price = this.getPrice(asset, false, false);
|
|
1069
|
-
|
|
1284
|
+
// Pair the price with the matching decimals: asset price ↔ asset
|
|
1285
|
+
// decimals, share price ↔ share decimals. Falls back to share
|
|
1286
|
+
// decimals if asset.decimals is somehow unset (cToken share decimals
|
|
1287
|
+
// always equal asset decimals on current Curvance markets, so the
|
|
1288
|
+
// fallback is value-equivalent).
|
|
1289
|
+
const decimals = asset ? (this.asset.decimals ?? this.decimals) : this.decimals;
|
|
1290
|
+
return FormatConverter_1.default.bigIntTokensToUsd(tokenAmount, price, decimals);
|
|
1070
1291
|
}
|
|
1071
1292
|
async fetchConvertTokensToUsd(tokenAmount, asset = true) {
|
|
1072
1293
|
// Reload cache
|
|
@@ -1080,7 +1301,9 @@ class CToken extends Calldata_1.Calldata {
|
|
|
1080
1301
|
}
|
|
1081
1302
|
convertAssetsToUsd(tokenAmount) {
|
|
1082
1303
|
const price = this.getPrice(true, false, false);
|
|
1083
|
-
|
|
1304
|
+
// Asset price ↔ asset decimals (with fallback to share decimals,
|
|
1305
|
+
// which equal asset decimals on current Curvance markets).
|
|
1306
|
+
const decimals = this.asset.decimals ?? this.decimals;
|
|
1084
1307
|
return FormatConverter_1.default.bigIntTokensToUsd(tokenAmount, price, decimals);
|
|
1085
1308
|
}
|
|
1086
1309
|
async convertSharesToUsd(tokenAmount) {
|