@raac/rpc 2.0.0-beta.21 → 2.0.0-beta.22
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/RPCLibrary.js +8 -3
- package/dist/artifacts/core/pools/StabilityPool/LiquidationSwap.sol/LiquidationSwap.json +472 -0
- package/dist/contracts/governance/gauge/getUserGaugeState.js +4 -5
- package/dist/contracts/psm/getVaultInfo.js +1 -3
- package/dist/pools/stabilityPool/liquidation.js +211 -138
- package/dist/types/RPCLibrary.d.ts +9 -4
- package/dist/types/contracts/governance/gauge/getUserGaugeState.d.ts +3 -4
- package/dist/types/contracts/psm/getVaultInfo.d.ts +1 -1
- package/dist/types/pools/stabilityPool/liquidation.d.ts +166 -52
- package/dist/types/utils/artifacts.d.ts +113 -0
- package/dist/utils/artifacts.js +2 -0
- package/package.json +2 -1
|
@@ -3,41 +3,47 @@ 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.minSharesOut = exports.sameSplit = exports.splitTotal = exports.SPLIT_KEYS = void 0;
|
|
6
|
+
exports.minSharesOut = exports.sharesAtNav = exports.valueAtNav = exports.curveQuote = exports.DEFAULT_PLAN_PARAMS = exports.sameSplit = exports.splitTotal = exports.SPLIT_KEYS = void 0;
|
|
7
7
|
exports.readLiquidationContext = readLiquidationContext;
|
|
8
8
|
exports.readLiquidationTarget = readLiquidationTarget;
|
|
9
|
-
exports.
|
|
10
|
-
exports.
|
|
11
|
-
exports.
|
|
9
|
+
exports.toleranceNeeded = toleranceNeeded;
|
|
10
|
+
exports.priceImpact = priceImpact;
|
|
11
|
+
exports.largestClearingFloor = largestClearingFloor;
|
|
12
|
+
exports.planLiquidation = planLiquidation;
|
|
12
13
|
/**
|
|
13
|
-
*
|
|
14
|
+
* Planning StabilityPool.liquidateBorrower, mirroring LiquidationStrategyProxy:
|
|
14
15
|
* 1. the seized collateral is deposited into the RWAVault for vault-token shares, unless it already is the vault token;
|
|
15
16
|
* 2. the stability pool's whole vault-token balance (the proxy runs under delegatecall) is split by BPS into
|
|
16
|
-
* mint (treasury
|
|
17
|
-
* 3.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
* mint (treasury), burn, swap and liquidity (fee collector);
|
|
18
|
+
* 3. the swap slice is sold on Curve through LiquidationSwap, whose floor is the vault's NAV less a tolerance;
|
|
19
|
+
* 4. the reserve asset the pool then holds (swap proceeds plus whatever it held before) repays the debt; a shortfall
|
|
20
|
+
* is drawn from the pool's rTokens through the LendingPool, and the call reverts (InsufficientBalance) if too few.
|
|
21
|
+
*
|
|
22
|
+
* The plan sizes the swap on Curve's get_dy and values every other leg at NAV. Any gap between the swap and the debt
|
|
23
|
+
* is reserve asset the Treasury sends to the pool beforehand, and the unsold vault tokens go to the Treasury for it.
|
|
22
24
|
*/
|
|
23
25
|
const ethers_1 = require("ethers");
|
|
24
26
|
const contracts_1 = require("../../utils/contracts");
|
|
25
27
|
const artifacts_1 = require("../../utils/artifacts");
|
|
26
28
|
const getAdapters_1 = __importDefault(require("../rwaVault/getAdapters"));
|
|
27
29
|
const getParameters_1 = __importDefault(require("../lendingPool/getParameters"));
|
|
30
|
+
const getState_1 = __importDefault(require("../../contracts/crvUSDToUSDOracle/getState"));
|
|
28
31
|
const BPS = BigInt(10_000);
|
|
29
32
|
const WAD = BigInt(10) ** BigInt(18);
|
|
30
33
|
const ZERO = BigInt(0);
|
|
34
|
+
/** Curve quotes its fee on a 1e10 scale. */
|
|
35
|
+
const CURVE_FEE_PER_BPS = BigInt(10) ** BigInt(6);
|
|
36
|
+
const LIQUIDITY_FEE_TYPE = ethers_1.ethers.id("RWA_INDEX_TOKEN_LIQUIDITY_REWARDS");
|
|
31
37
|
const SWAP_ABI = [
|
|
32
38
|
"function liquidityPool() view returns (address)",
|
|
33
|
-
"function swapToken() view returns (address)",
|
|
34
39
|
"function iIn() view returns (uint256)",
|
|
35
40
|
"function iOut() view returns (uint256)",
|
|
36
41
|
"function enableSlippageProtection() view returns (bool)",
|
|
37
42
|
"function slippageProtectionBps() view returns (uint256)",
|
|
38
43
|
"function crvusdToUSDOracle() view returns (address)",
|
|
44
|
+
"function owner() view returns (address)",
|
|
39
45
|
];
|
|
40
|
-
const CURVE_ABI = ["function get_dy(uint256 i, uint256 j, uint256 dx) view returns (uint256)", "function balances(uint256 i) view returns (uint256)"];
|
|
46
|
+
const CURVE_ABI = ["function get_dy(uint256 i, uint256 j, uint256 dx) view returns (uint256)", "function balances(uint256 i) view returns (uint256)", "function fee() view returns (uint256)"];
|
|
41
47
|
const VAULT_ABI = ["function underlyingVaultToken() view returns (address)", "function previewDeposit(uint256 assetAmount) view returns (uint256)", "function pricePerShare() view returns (uint256)"];
|
|
42
48
|
const VAULT_ADAPTER_ABI = ["function getAssetToken() view returns (address)", "function getAssetValue(bytes data) view returns (uint256)"];
|
|
43
49
|
const ADAPTER_ABI = [
|
|
@@ -47,64 +53,103 @@ const ADAPTER_ABI = [
|
|
|
47
53
|
"function balanceOf(address user) view returns (uint256)",
|
|
48
54
|
"function getDepositedTokens(address user) view returns (uint256[])",
|
|
49
55
|
];
|
|
50
|
-
const ERC20_ABI = ["function balanceOf(address) view returns (uint256)"];
|
|
56
|
+
const ERC20_ABI = ["function balanceOf(address) view returns (uint256)", "function totalSupply() view returns (uint256)", "function symbol() view returns (string)"];
|
|
57
|
+
const YIELD_VAULT_ABI = ["function maxWithdraw(address owner, uint256 maxLoss, address[] strategies) view returns (uint256)"];
|
|
58
|
+
const TREASURY_ABI = ["function isTokenSupported(address) view returns (bool)"];
|
|
51
59
|
exports.SPLIT_KEYS = ["mintBps", "burnBps", "swapBps", "liquidityBps"];
|
|
52
60
|
const splitTotal = (s) => s.mintBps + s.burnBps + s.swapBps + s.liquidityBps;
|
|
53
61
|
exports.splitTotal = splitTotal;
|
|
54
62
|
const sameSplit = (a, b) => exports.SPLIT_KEYS.every((k) => a[k] === b[k]);
|
|
55
63
|
exports.sameSplit = sameSplit;
|
|
64
|
+
exports.DEFAULT_PLAN_PARAMS = { burnBps: BigInt(250), treasuryBps: BigInt(250), liquidityCapBps: BigInt(1500), impactCapBps: BigInt(100), toleranceCeilingBps: BigInt(500), toleranceCushionBps: BigInt(10), marginBps: BigInt(100) };
|
|
56
65
|
const contract = (address, abi, provider) => new ethers_1.ethers.Contract(address, abi, provider);
|
|
57
66
|
const decodeAmount = (data) => ethers_1.ethers.AbiCoder.defaultAbiCoder().decode(["uint256"], data)[0];
|
|
58
|
-
|
|
67
|
+
const min = (...values) => values.reduce((a, b) => (a < b ? a : b));
|
|
68
|
+
const ceilDiv = (a, b) => (a + b - BigInt(1)) / b;
|
|
69
|
+
/** Reads the stability pool's liquidation route, balances, Curve, NAV, floor and leg prerequisites at one block. */
|
|
59
70
|
async function readLiquidationContext(chainId, provider) {
|
|
60
71
|
const stabilityPool = (0, contracts_1.getContractAddress)(chainId, "stabilitypool");
|
|
72
|
+
const lendingPool = (0, contracts_1.getContractAddress)(chainId, "lendingpool");
|
|
61
73
|
const sp = contract(stabilityPool, (0, artifacts_1.getABI)("stabilitypool"), provider);
|
|
62
|
-
const [liquidationSwap, rwaVault, split, rToken, params] = await Promise.all([
|
|
74
|
+
const [liquidationSwap, rwaVault, split, rToken, deToken, reserveAsset, treasury, feeCollector, params, yieldVault, reserve] = await Promise.all([
|
|
63
75
|
sp.liquidationSwap(),
|
|
64
76
|
sp.rwaVault(),
|
|
65
77
|
sp.liquidationSplit(),
|
|
66
78
|
sp.rToken(),
|
|
79
|
+
sp.deToken(),
|
|
80
|
+
sp.crvUSDToken(),
|
|
81
|
+
sp.treasury(),
|
|
82
|
+
sp.feeCollector(),
|
|
67
83
|
(0, getParameters_1.default)(chainId, provider),
|
|
84
|
+
contract(lendingPool, (0, artifacts_1.getABI)("lendingpool"), provider).vault(),
|
|
85
|
+
contract(lendingPool, (0, artifacts_1.getABI)("lendingpool"), provider).reserve(),
|
|
68
86
|
]);
|
|
69
87
|
if (liquidationSwap === ethers_1.ethers.ZeroAddress)
|
|
70
88
|
throw new Error("The stability pool has no LiquidationSwap set, so the swap leg reverts.");
|
|
71
89
|
const swap = contract(liquidationSwap, SWAP_ABI, provider);
|
|
72
90
|
const vault = contract(rwaVault, VAULT_ABI, provider);
|
|
73
|
-
const [liquidityPool, iIn, iOut, vaultToken,
|
|
91
|
+
const [liquidityPool, iIn, iOut, vaultToken, enabled, bps, owner, oracle, pricePerShare] = await Promise.all([
|
|
74
92
|
swap.liquidityPool(),
|
|
75
93
|
swap.iIn(),
|
|
76
94
|
swap.iOut(),
|
|
77
95
|
vault.underlyingVaultToken(),
|
|
78
96
|
swap.enableSlippageProtection(),
|
|
97
|
+
swap.slippageProtectionBps(),
|
|
98
|
+
swap.owner(),
|
|
99
|
+
swap.crvusdToUSDOracle(),
|
|
100
|
+
vault.pricePerShare(),
|
|
79
101
|
]);
|
|
80
|
-
const slippage = protect
|
|
81
|
-
? await Promise.all([
|
|
82
|
-
swap.slippageProtectionBps(),
|
|
83
|
-
vault.pricePerShare(),
|
|
84
|
-
swap.crvusdToUSDOracle().then((o) => contract(o, ["function getPrice() view returns (uint256)"], provider).getPrice()),
|
|
85
|
-
]).then(([bps, pricePerShare, oraclePrice]) => ({ bps, pricePerShare, oraclePrice }))
|
|
86
|
-
: null;
|
|
87
102
|
if (liquidityPool === ethers_1.ethers.ZeroAddress)
|
|
88
103
|
throw new Error("LiquidationSwap has no Curve pool set, so the swap leg reverts.");
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
104
|
+
const curve = contract(liquidityPool, CURVE_ABI, provider);
|
|
105
|
+
const erc20 = (token) => contract(token, ERC20_ABI, provider);
|
|
106
|
+
const fc = feeCollector === ethers_1.ethers.ZeroAddress ? null : contract(feeCollector, (0, artifacts_1.getABI)("feecollector"), provider);
|
|
107
|
+
const [vaultSymbol, poolVaultTokens, vaultTokenSupply, rTokens, poolReserve, deTokenSupply, buffer, vaultWithdrawable, spot, depth, curveFee, oraclePrice, oracleState, fcPaused, feeType, treasurySupportsVaultToken] = await Promise.all([
|
|
108
|
+
erc20(vaultToken).symbol().catch(() => "vault token"),
|
|
109
|
+
erc20(vaultToken).balanceOf(stabilityPool),
|
|
110
|
+
erc20(vaultToken).totalSupply(),
|
|
111
|
+
erc20(rToken).balanceOf(stabilityPool),
|
|
112
|
+
erc20(reserveAsset).balanceOf(stabilityPool),
|
|
113
|
+
erc20(deToken).totalSupply(),
|
|
114
|
+
erc20(reserveAsset).balanceOf(rToken),
|
|
115
|
+
yieldVault === ethers_1.ethers.ZeroAddress ? ZERO : contract(yieldVault, YIELD_VAULT_ABI, provider).maxWithdraw(lendingPool, 0, []).catch(() => null),
|
|
116
|
+
curve.get_dy(iIn, iOut, WAD),
|
|
117
|
+
curve.balances(iOut),
|
|
118
|
+
curve.fee(),
|
|
119
|
+
contract(oracle, ["function getPrice() view returns (uint256)"], provider).getPrice(),
|
|
120
|
+
(0, getState_1.default)(chainId, provider, oracle).catch(() => null),
|
|
121
|
+
fc ? fc.paused() : false,
|
|
122
|
+
fc ? fc.getFeeType(LIQUIDITY_FEE_TYPE) : null,
|
|
123
|
+
treasury === ethers_1.ethers.ZeroAddress ? false : contract(treasury, TREASURY_ABI, provider).isTokenSupported(vaultToken),
|
|
93
124
|
]);
|
|
94
125
|
return {
|
|
95
126
|
stabilityPool,
|
|
127
|
+
lendingPool,
|
|
96
128
|
liquidationSwap,
|
|
97
129
|
liquidityPool,
|
|
98
130
|
rwaVault,
|
|
131
|
+
treasury,
|
|
132
|
+
feeCollector,
|
|
99
133
|
vaultToken,
|
|
100
134
|
vaultSymbol,
|
|
135
|
+
reserveAsset,
|
|
136
|
+
rToken,
|
|
101
137
|
iIn,
|
|
102
138
|
iOut,
|
|
103
139
|
split: { mintBps: split[0], burnBps: split[1], swapBps: split[2], liquidityBps: split[3] },
|
|
104
140
|
poolVaultTokens,
|
|
141
|
+
poolReserve,
|
|
105
142
|
rTokens,
|
|
143
|
+
deTokenSupply,
|
|
144
|
+
idleCash: { buffer, vaultWithdrawable },
|
|
145
|
+
lendingPoolLiquidity: { total: reserve.totalLiquidity, bufferRatioBps: params.liquidityBufferRatio, minRebalance: params.minVaultRebalanceAmount.value },
|
|
146
|
+
curve: { spot, depth, feeBps: curveFee / CURVE_FEE_PER_BPS },
|
|
147
|
+
nav: { pricePerShare, oraclePrice, priceTimestamp: oracleState?.priceTimestamp ?? null },
|
|
148
|
+
vaultTokenSupply,
|
|
149
|
+
floor: { enabled, bps, owner },
|
|
150
|
+
feeCollectorState: { paused: fcPaused, liquidityFeeTypeRegistered: feeType !== null && feeType.feeType !== ethers_1.ethers.ZeroHash },
|
|
151
|
+
treasurySupportsVaultToken,
|
|
106
152
|
gracePeriod: Number(params.liquidationGracePeriod),
|
|
107
|
-
slippage,
|
|
108
153
|
};
|
|
109
154
|
}
|
|
110
155
|
/**
|
|
@@ -147,13 +192,18 @@ async function findVaultAdapter(chainId, provider, assetToken, vaultToken) {
|
|
|
147
192
|
async function readLiquidationTarget(chainId, provider, ctx, positionId) {
|
|
148
193
|
const [user, poolAdapter, positionKey] = positionId.split(":");
|
|
149
194
|
const { data, assetType, assetToken } = await readLiquidationData(provider, poolAdapter, user, positionKey);
|
|
150
|
-
const pool = contract(
|
|
195
|
+
const pool = contract(ctx.lendingPool, (0, artifacts_1.getABI)("lendingpool"), provider);
|
|
196
|
+
const isVaultToken = assetToken.toLowerCase() === ctx.vaultToken.toLowerCase();
|
|
151
197
|
const [vaultAdapter, collateralValue, debt, position] = await Promise.all([
|
|
152
198
|
findVaultAdapter(chainId, provider, assetToken, ctx.vaultToken),
|
|
153
199
|
contract(poolAdapter, ADAPTER_ABI, provider).getAssetValue(user, data),
|
|
154
200
|
pool.getPositionScaledDebt(poolAdapter, user, data),
|
|
155
201
|
pool.getPosition(poolAdapter, user, data),
|
|
156
202
|
]);
|
|
203
|
+
// poolDepositAsset mints from the vault adapter's own valuation, which may use a different oracle than the pool's.
|
|
204
|
+
const sharesFromCollateral = isVaultToken
|
|
205
|
+
? decodeAmount(data)
|
|
206
|
+
: await contract(ctx.rwaVault, VAULT_ABI, provider).previewDeposit(await contract(vaultAdapter, VAULT_ADAPTER_ABI, provider).getAssetValue(data));
|
|
157
207
|
return {
|
|
158
208
|
poolAdapter: ethers_1.ethers.getAddress(poolAdapter),
|
|
159
209
|
vaultAdapter,
|
|
@@ -161,137 +211,160 @@ async function readLiquidationTarget(chainId, provider, ctx, positionId) {
|
|
|
161
211
|
data,
|
|
162
212
|
assetType,
|
|
163
213
|
assetToken,
|
|
164
|
-
isVaultToken
|
|
214
|
+
isVaultToken,
|
|
165
215
|
collateralValue,
|
|
216
|
+
sharesFromCollateral,
|
|
166
217
|
debt,
|
|
167
218
|
isUnderLiquidation: position.isUnderLiquidation,
|
|
168
219
|
isInsured: position.isInsured,
|
|
169
220
|
liquidationStartTime: Number(position.liquidationStartTime),
|
|
170
221
|
};
|
|
171
222
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
223
|
+
/** get_dy on the context's Curve pool. */
|
|
224
|
+
const curveQuote = (provider, ctx) => (amount) => amount <= ZERO ? Promise.resolve(ZERO) : contract(ctx.liquidityPool, CURVE_ABI, provider).get_dy(ctx.iIn, ctx.iOut, amount).catch(() => null);
|
|
225
|
+
exports.curveQuote = curveQuote;
|
|
226
|
+
/** Reserve asset that `shares` vault tokens are worth at NAV, as LiquidationSwap computes its expected output. */
|
|
227
|
+
const valueAtNav = (ctx, shares) => (ctx.nav.pricePerShare * shares) / ctx.nav.oraclePrice;
|
|
228
|
+
exports.valueAtNav = valueAtNav;
|
|
229
|
+
/** Vault tokens worth `value` of reserve asset at NAV, rounded up. */
|
|
230
|
+
const sharesAtNav = (ctx, value) => ceilDiv(value * ctx.nav.oraclePrice, ctx.nav.pricePerShare);
|
|
231
|
+
exports.sharesAtNav = sharesAtNav;
|
|
232
|
+
/**
|
|
233
|
+
* The floor tolerance a swap of `amount` returning `out` needs: how far `out` falls below NAV, in bps, rounded up.
|
|
234
|
+
* The proxy burns the burn leg before it swaps, which lifts the vault's price per share by the burned share of the
|
|
235
|
+
* supply, so the floor LiquidationSwap applies is computed on that higher NAV; `burned` accounts for it.
|
|
236
|
+
*/
|
|
237
|
+
function toleranceNeeded(ctx, amount, out, burned = ZERO) {
|
|
238
|
+
const supplyAfter = ctx.vaultTokenSupply - burned;
|
|
239
|
+
const expected = supplyAfter > ZERO && burned > ZERO ? ((0, exports.valueAtNav)(ctx, amount) * ctx.vaultTokenSupply) / supplyAfter : (0, exports.valueAtNav)(ctx, amount);
|
|
240
|
+
return expected <= out || expected === ZERO ? ZERO : ceilDiv((expected - out) * BPS, expected);
|
|
176
241
|
}
|
|
177
|
-
/**
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const totalVaultTokens = sharesFromCollateral + ctx.poolVaultTokens;
|
|
184
|
-
const swapAmount = (totalVaultTokens * split.swapBps) / BPS;
|
|
185
|
-
const [swapOut, maxSwapOut, spotOut, poolReserveOut] = await Promise.all([
|
|
186
|
-
quoteDy(provider, ctx, swapAmount),
|
|
187
|
-
quoteDy(provider, ctx, totalVaultTokens),
|
|
188
|
-
quoteDy(provider, ctx, WAD),
|
|
189
|
-
contract(ctx.liquidityPool, CURVE_ABI, provider).balances(ctx.iOut).catch(() => null),
|
|
190
|
-
]);
|
|
191
|
-
let priceImpactBps = null;
|
|
192
|
-
if (swapOut !== null && spotOut && swapAmount > ZERO) {
|
|
193
|
-
const realised = (swapOut * WAD) / swapAmount;
|
|
194
|
-
priceImpactBps = realised >= spotOut ? ZERO : ((spotOut - realised) * BPS) / spotOut;
|
|
195
|
-
}
|
|
196
|
-
return {
|
|
197
|
-
split,
|
|
198
|
-
sharesFromCollateral,
|
|
199
|
-
totalVaultTokens,
|
|
200
|
-
swapAmount,
|
|
201
|
-
swapOut,
|
|
202
|
-
minSwapOut: ctx.slippage && swapAmount > ZERO ? ((ctx.slippage.pricePerShare * swapAmount) / ctx.slippage.oraclePrice) * (BPS - ctx.slippage.bps) / BPS : null,
|
|
203
|
-
shortfall: swapOut === null ? target.debt : swapOut >= target.debt ? ZERO : target.debt - swapOut,
|
|
204
|
-
coversDebt: swapOut !== null && swapOut >= target.debt,
|
|
205
|
-
badDebt: target.collateralValue < target.debt ? target.debt - target.collateralValue : ZERO,
|
|
206
|
-
maxSwapOut,
|
|
207
|
-
priceImpactBps,
|
|
208
|
-
poolReserveOut,
|
|
209
|
-
};
|
|
242
|
+
/** How far the realised rate of a swap falls below Curve's spot rate, in bps. */
|
|
243
|
+
function priceImpact(ctx, amount, out) {
|
|
244
|
+
if (amount === ZERO || ctx.curve.spot === ZERO)
|
|
245
|
+
return ZERO;
|
|
246
|
+
const realised = (out * WAD) / amount;
|
|
247
|
+
return realised >= ctx.curve.spot ? ZERO : ((ctx.curve.spot - realised) * BPS) / ctx.curve.spot;
|
|
210
248
|
}
|
|
249
|
+
const shareOf = (total, bps) => (total * bps) / BPS;
|
|
211
250
|
/**
|
|
212
|
-
*
|
|
213
|
-
*
|
|
251
|
+
* Binary search over the swap share in bps of `total`, which is exact because the proxy computes the swap amount the
|
|
252
|
+
* same way. `holds` must be monotone in the share: true up to some share for "largest", true from some share on for
|
|
253
|
+
* "smallest". A reverted quote counts as not holding. Returns the share, or null when no share holds.
|
|
214
254
|
*/
|
|
215
|
-
async function
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const rest = BPS - swapBps;
|
|
221
|
-
const others = ctx.split.mintBps + ctx.split.burnBps + ctx.split.liquidityBps;
|
|
222
|
-
const mintBps = others === ZERO ? ZERO : (rest * ctx.split.mintBps) / others;
|
|
223
|
-
const burnBps = others === ZERO ? ZERO : (rest * ctx.split.burnBps) / others;
|
|
224
|
-
return { mintBps, burnBps, swapBps, liquidityBps: rest - mintBps - burnBps, projectedOut, bestEffort };
|
|
255
|
+
async function searchShare(total, quote, holds, want) {
|
|
256
|
+
const test = async (bps) => {
|
|
257
|
+
const amount = shareOf(total, bps);
|
|
258
|
+
const out = await quote(amount);
|
|
259
|
+
return out !== null && holds(amount, out);
|
|
225
260
|
};
|
|
226
|
-
|
|
227
|
-
|
|
261
|
+
if (want === "largest") {
|
|
262
|
+
if (!(await test(ZERO)))
|
|
263
|
+
return null;
|
|
264
|
+
let low = ZERO;
|
|
265
|
+
let high = BPS;
|
|
266
|
+
while (low < high) {
|
|
267
|
+
const mid = (low + high + BigInt(1)) / BigInt(2);
|
|
268
|
+
if (await test(mid))
|
|
269
|
+
low = mid;
|
|
270
|
+
else
|
|
271
|
+
high = mid - BigInt(1);
|
|
272
|
+
}
|
|
273
|
+
return low;
|
|
274
|
+
}
|
|
275
|
+
if (!(await test(BPS)))
|
|
228
276
|
return null;
|
|
229
|
-
if (full < required)
|
|
230
|
-
return spread(BPS, full, true);
|
|
231
277
|
let low = ZERO;
|
|
232
278
|
let high = BPS;
|
|
233
|
-
let best = full;
|
|
234
279
|
while (low < high) {
|
|
235
280
|
const mid = (low + high) / BigInt(2);
|
|
236
|
-
|
|
237
|
-
if (out !== null && out >= required) {
|
|
281
|
+
if (await test(mid))
|
|
238
282
|
high = mid;
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
283
|
+
else
|
|
242
284
|
low = mid + BigInt(1);
|
|
243
|
-
}
|
|
244
285
|
}
|
|
245
|
-
return
|
|
286
|
+
return low;
|
|
287
|
+
}
|
|
288
|
+
/** The largest swap share (bps) whose output clears the floor at `toleranceBps` once `burned` vault tokens are gone; the whole balance when the floor is off. */
|
|
289
|
+
async function largestClearingFloor(ctx, quote, total, toleranceBps, burned = ZERO) {
|
|
290
|
+
if (!ctx.floor.enabled)
|
|
291
|
+
return BPS;
|
|
292
|
+
return (await searchShare(total, quote, (amount, out) => toleranceNeeded(ctx, amount, out, burned) <= toleranceBps, "largest")) ?? ZERO;
|
|
246
293
|
}
|
|
247
|
-
/** minSharesOut for the RWAVault deposit: previewDeposit less `slippageBps`. Zero when nothing is deposited. */
|
|
248
|
-
const minSharesOut = (target, quote, slippageBps) => (target.isVaultToken ? ZERO : (quote.sharesFromCollateral * (BPS - slippageBps)) / BPS);
|
|
249
|
-
exports.minSharesOut = minSharesOut;
|
|
250
|
-
// StabilityPoolStorage is inherited first and the OpenZeppelin parents use namespaced storage, so liquidationSplit
|
|
251
|
-
// takes slots 10 to 13. Checked against the live split before it is relied on.
|
|
252
|
-
const SPLIT_SLOT = 10;
|
|
253
294
|
/**
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
* layout cannot be used; otherwise the revert reason, or "" when it succeeds.
|
|
295
|
+
* Sizes the swap, derives the split and says what the Treasury must send. `quote` is Curve's get_dy, so a test can
|
|
296
|
+
* pass a synthetic curve. In "otc" mode nothing is swapped and the Treasury funds the whole uncovered debt.
|
|
257
297
|
*/
|
|
258
|
-
async function
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
catch (error) {
|
|
277
|
-
if (!ethers_1.ethers.isError(error, "CALL_EXCEPTION"))
|
|
278
|
-
return null;
|
|
279
|
-
return (error.data && attemptParse(error.data)) || error.reason || error.shortMessage;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
function attemptParse(data) {
|
|
283
|
-
for (const key of ["stabilitypool", "lendingpool"]) {
|
|
284
|
-
try {
|
|
285
|
-
const e = new ethers_1.ethers.Interface((0, artifacts_1.getABI)(key)).parseError(data);
|
|
286
|
-
if (e)
|
|
287
|
-
return e.name === "Error" ? String(e.args[0]) : e.name;
|
|
288
|
-
}
|
|
289
|
-
catch { }
|
|
290
|
-
}
|
|
291
|
-
try {
|
|
292
|
-
return ethers_1.ethers.AbiCoder.defaultAbiCoder().decode(["string"], ethers_1.ethers.dataSlice(data, 4))[0];
|
|
298
|
+
async function planLiquidation(ctx, target, quote, params = exports.DEFAULT_PLAN_PARAMS, mode = "defi") {
|
|
299
|
+
const total = target.sharesFromCollateral + ctx.poolVaultTokens;
|
|
300
|
+
const required = (target.debt * (BPS + params.marginBps)) / BPS;
|
|
301
|
+
const toCover = required > ctx.poolReserve ? required - ctx.poolReserve : ZERO;
|
|
302
|
+
const floorCap = ctx.floor.enabled ? (params.toleranceCeilingBps > params.toleranceCushionBps ? params.toleranceCeilingBps - params.toleranceCushionBps : ZERO) : BPS;
|
|
303
|
+
// The burn runs before the swap; the policy burn is close enough to size the floor searches on.
|
|
304
|
+
const burnEstimate = shareOf(total, min(params.burnBps, BPS));
|
|
305
|
+
const [underImpact, atCurrent, atCeiling, needed] = total === ZERO ? [ZERO, ZERO, ZERO, ZERO] : await Promise.all([
|
|
306
|
+
searchShare(total, quote, (amount, out) => priceImpact(ctx, amount, out) <= params.impactCapBps, "largest").then((s) => s ?? ZERO),
|
|
307
|
+
largestClearingFloor(ctx, quote, total, ctx.floor.bps, burnEstimate),
|
|
308
|
+
largestClearingFloor(ctx, quote, total, floorCap, burnEstimate),
|
|
309
|
+
toCover === ZERO ? ZERO : searchShare(total, quote, (_, out) => out >= toCover, "smallest"),
|
|
310
|
+
]);
|
|
311
|
+
let swapBps;
|
|
312
|
+
let bound;
|
|
313
|
+
if (mode === "otc") {
|
|
314
|
+
swapBps = ZERO;
|
|
315
|
+
bound = "mode";
|
|
293
316
|
}
|
|
294
|
-
|
|
295
|
-
|
|
317
|
+
else {
|
|
318
|
+
const wanted = needed ?? BPS;
|
|
319
|
+
swapBps = min(wanted, underImpact, atCeiling);
|
|
320
|
+
bound = swapBps === wanted ? (needed === null ? "balance" : "debt") : swapBps === underImpact ? "impact" : "floor";
|
|
296
321
|
}
|
|
322
|
+
const swapAmount = shareOf(total, swapBps);
|
|
323
|
+
const swapOut = swapAmount === ZERO ? ZERO : ((await quote(swapAmount)) ?? ZERO);
|
|
324
|
+
const gap = toCover > swapOut ? toCover - swapOut : ZERO;
|
|
325
|
+
const liquidityDisabled = ctx.feeCollectorState.paused || !ctx.feeCollectorState.liquidityFeeTypeRegistered;
|
|
326
|
+
const burnBps = min(params.burnBps, BPS - swapBps);
|
|
327
|
+
const treasuryBaseBps = min(params.treasuryBps, BPS - swapBps - burnBps);
|
|
328
|
+
const remainderBps = BPS - swapBps - burnBps - treasuryBaseBps;
|
|
329
|
+
const gapBps = gap === ZERO || total === ZERO ? ZERO : ceilDiv((0, exports.sharesAtNav)(ctx, gap) * BPS, total);
|
|
330
|
+
const treasuryGapBps = min(gapBps, remainderBps);
|
|
331
|
+
const leftoverBps = remainderBps - treasuryGapBps;
|
|
332
|
+
const liquidityBps = liquidityDisabled ? ZERO : gap > ZERO ? min(leftoverBps, params.liquidityCapBps) : leftoverBps;
|
|
333
|
+
const split = { mintBps: treasuryBaseBps + treasuryGapBps + leftoverBps - liquidityBps, burnBps, swapBps, liquidityBps };
|
|
334
|
+
const legs = { swap: swapAmount, burn: shareOf(total, split.burnBps), treasury: shareOf(total, split.mintBps), liquidity: shareOf(total, split.liquidityBps) };
|
|
335
|
+
const needsBps = toleranceNeeded(ctx, swapAmount, swapOut, legs.burn);
|
|
336
|
+
const raise = ctx.floor.enabled && swapAmount > ZERO && needsBps > ctx.floor.bps;
|
|
337
|
+
const treasuryValue = (0, exports.valueAtNav)(ctx, legs.treasury);
|
|
338
|
+
const idle = ctx.idleCash.vaultWithdrawable === null ? null : ctx.idleCash.buffer + ctx.idleCash.vaultWithdrawable;
|
|
339
|
+
const wanted = ceilDiv((ctx.lendingPoolLiquidity.total + target.debt) * ctx.lendingPoolLiquidity.bufferRatioBps, BPS);
|
|
340
|
+
const shortage = wanted > ctx.idleCash.buffer ? wanted - ctx.idleCash.buffer : ZERO;
|
|
341
|
+
const rebalances = shortage >= ctx.lendingPoolLiquidity.minRebalance && shortage > ZERO;
|
|
342
|
+
const rebalanceFeasible = !rebalances ? true : ctx.idleCash.vaultWithdrawable === null ? null : shortage <= ctx.idleCash.vaultWithdrawable;
|
|
343
|
+
return {
|
|
344
|
+
mode,
|
|
345
|
+
params,
|
|
346
|
+
totalVaultTokens: total,
|
|
347
|
+
required,
|
|
348
|
+
toCover,
|
|
349
|
+
swapAmount,
|
|
350
|
+
swapOut,
|
|
351
|
+
priceImpactBps: priceImpact(ctx, swapAmount, swapOut),
|
|
352
|
+
toleranceNeededBps: needsBps,
|
|
353
|
+
proposedToleranceBps: raise ? min(needsBps + params.toleranceCushionBps, params.toleranceCeilingBps > needsBps ? params.toleranceCeilingBps : needsBps) : null,
|
|
354
|
+
bound,
|
|
355
|
+
ceilings: { atCurrent: shareOf(total, atCurrent), atCeiling: shareOf(total, atCeiling), underImpact: shareOf(total, underImpact) },
|
|
356
|
+
split,
|
|
357
|
+
legs,
|
|
358
|
+
gap,
|
|
359
|
+
treasuryValue,
|
|
360
|
+
treasuryNet: treasuryValue - gap,
|
|
361
|
+
liquidityCapped: !liquidityDisabled && gap > ZERO && leftoverBps > params.liquidityCapBps,
|
|
362
|
+
liquidityDisabled,
|
|
363
|
+
route: gap === ZERO ? "covered" : swapAmount > ZERO ? "partial" : "otc",
|
|
364
|
+
absorb: { feasible: idle === null ? null : ctx.rTokens >= gap && idle >= gap, rTokensAfter: ctx.rTokens - gap, inject: idle !== null && idle < gap ? gap - idle : ZERO },
|
|
365
|
+
rebalance: { shortage, feasible: rebalanceFeasible, inject: rebalanceFeasible === false ? shortage : ZERO },
|
|
366
|
+
};
|
|
297
367
|
}
|
|
368
|
+
/** minSharesOut for the RWAVault deposit: previewDeposit less `slippageBps`. Zero when nothing is deposited. */
|
|
369
|
+
const minSharesOut = (target, slippageBps) => (target.isVaultToken ? ZERO : (target.sharesFromCollateral * (BPS - slippageBps)) / BPS);
|
|
370
|
+
exports.minSharesOut = minSharesOut;
|
|
@@ -140,7 +140,7 @@ import getRoles from "./contracts/getRoles";
|
|
|
140
140
|
import getTreasuryState from "./contracts/treasury/getState";
|
|
141
141
|
import getComplianceStatuses from "./contracts/complianceRegistry/getStatuses";
|
|
142
142
|
import getStabilityPoolAdminState from "./pools/stabilityPool/getAdminState";
|
|
143
|
-
import { readLiquidationContext, readLiquidationTarget,
|
|
143
|
+
import { readLiquidationContext, readLiquidationTarget, planLiquidation, curveQuote, largestClearingFloor, toleranceNeeded, priceImpact, valueAtNav, sharesAtNav, minSharesOut, DEFAULT_PLAN_PARAMS } from "./pools/stabilityPool/liquidation";
|
|
144
144
|
import { getEmissionSchedule, getSchedulePosition, getEmissionPerSecondForWeek, getMintingWeekForTimestamp } from "./minter/emissionSchedule";
|
|
145
145
|
import getRWAVaultRegisteredAdapters from "./pools/rwaVault/getRegisteredAdapters";
|
|
146
146
|
import getRWAVaultSeedStatus from "./pools/rwaVault/getSeedStatus";
|
|
@@ -707,10 +707,15 @@ declare class RPCLibrary {
|
|
|
707
707
|
liquidation: {
|
|
708
708
|
getContext: typeof readLiquidationContext;
|
|
709
709
|
getTarget: typeof readLiquidationTarget;
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
710
|
+
plan: typeof planLiquidation;
|
|
711
|
+
curveQuote: typeof curveQuote;
|
|
712
|
+
largestClearingFloor: typeof largestClearingFloor;
|
|
713
|
+
toleranceNeeded: typeof toleranceNeeded;
|
|
714
|
+
priceImpact: typeof priceImpact;
|
|
715
|
+
valueAtNav: typeof valueAtNav;
|
|
716
|
+
sharesAtNav: typeof sharesAtNav;
|
|
713
717
|
minSharesOut: typeof minSharesOut;
|
|
718
|
+
DEFAULT_PLAN_PARAMS: typeof DEFAULT_PLAN_PARAMS;
|
|
714
719
|
};
|
|
715
720
|
};
|
|
716
721
|
lendingPool: {
|
|
@@ -7,8 +7,6 @@ export interface UserGaugeTokenState {
|
|
|
7
7
|
symbol?: string;
|
|
8
8
|
/** From the token registry, or read from the token when the registry does not list it. */
|
|
9
9
|
decimals: number;
|
|
10
|
-
/** Wallet balance, in this token's base units. */
|
|
11
|
-
balance: TokenAmount;
|
|
12
10
|
/** Allowance to `spender`, in this token's base units. */
|
|
13
11
|
allowance: TokenAmount;
|
|
14
12
|
/** The contract that pulls the token: the gauge for the staking token, the wrapper for the underlying. */
|
|
@@ -23,8 +21,9 @@ export interface UserGaugeState {
|
|
|
23
21
|
underlying: UserGaugeTokenState | null;
|
|
24
22
|
}
|
|
25
23
|
/**
|
|
26
|
-
* What a wallet needs to stake in, wrap for, or unstake from a gauge: its stake, its
|
|
27
|
-
*
|
|
24
|
+
* What a wallet needs to stake in, wrap for, or unstake from a gauge: its stake, its allowance of the staking token to the
|
|
25
|
+
* gauge, and for a wrapped gauge its allowance of the underlying to the wrapper. Read the wallet's token balances with
|
|
26
|
+
* `wallet.assets.getBalance`.
|
|
28
27
|
*
|
|
29
28
|
* @param chainId - The chain/network to use.
|
|
30
29
|
* @param gaugeAddressOrId - Address of the gauge contract or its config ID.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Provider } from "ethers";
|
|
2
2
|
import { ChainId } from "../../configs/chains";
|
|
3
3
|
import { TokenAmount, ScaledValue } from "../../utils/amount";
|
|
4
|
+
/** A wallet's open redeem request and allowances to the vault. Token balances, shares included, come from `wallet.assets.getBalance`. */
|
|
4
5
|
export interface PsmVaultUserInfo {
|
|
5
|
-
shareBalance: TokenAmount;
|
|
6
6
|
redeemRequest: {
|
|
7
7
|
shares: TokenAmount;
|
|
8
8
|
exitTime: number;
|