@morpho-org/blue-sdk 1.0.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 +98 -0
- package/package.json +54 -0
- package/src/addresses.ts +261 -0
- package/src/chain/chain.constants.ts +235 -0
- package/src/chain/chain.test.ts +51 -0
- package/src/chain/chain.types.ts +42 -0
- package/src/chain/chain.utils.ts +44 -0
- package/src/chain/index.ts +2 -0
- package/src/constants.ts +18 -0
- package/src/errors.ts +75 -0
- package/src/ethers/ethers.test.ts +17 -0
- package/src/ethers/index.ts +2 -0
- package/src/ethers/safeGetAddress.ts +4 -0
- package/src/ethers/safeParseUnits.ts +29 -0
- package/src/evm.ts +172 -0
- package/src/helpers/format/format.test.ts +340 -0
- package/src/helpers/format/format.ts +416 -0
- package/src/helpers/format/index.ts +1 -0
- package/src/helpers/getChecksumedAddress.ts +15 -0
- package/src/helpers/index.ts +4 -0
- package/src/helpers/isZeroAddressOrUnset.ts +13 -0
- package/src/helpers/locale.ts +108 -0
- package/src/holding/Holding.ts +109 -0
- package/src/holding/index.ts +1 -0
- package/src/index.ts +34 -0
- package/src/market/Market.ts +479 -0
- package/src/market/MarketConfig.ts +108 -0
- package/src/market/MarketUtils.test.ts +25 -0
- package/src/market/MarketUtils.ts +467 -0
- package/src/market/index.ts +3 -0
- package/src/maths/AdaptiveCurveIrmLib.ts +143 -0
- package/src/maths/MathLib.ts +208 -0
- package/src/maths/MathUtils.ts +31 -0
- package/src/maths/SharesMath.ts +40 -0
- package/src/maths/index.ts +4 -0
- package/src/notifications.ts +167 -0
- package/src/position/Position.ts +251 -0
- package/src/position/index.ts +1 -0
- package/src/signatures/index.ts +18 -0
- package/src/signatures/manager.ts +50 -0
- package/src/signatures/permit.ts +126 -0
- package/src/signatures/permit2.ts +120 -0
- package/src/signatures/types.ts +18 -0
- package/src/signatures/utils.ts +83 -0
- package/src/tests/mocks/markets.ts +110 -0
- package/src/token/ERC20Metadata.ts +124 -0
- package/src/token/Token.ts +83 -0
- package/src/token/TokenNamespace.ts +76 -0
- package/src/token/WrappedToken.ts +142 -0
- package/src/token/index.ts +2 -0
- package/src/types.ts +37 -0
- package/src/user/User.ts +32 -0
- package/src/user/index.ts +2 -0
- package/src/user/user.types.ts +23 -0
- package/src/vault/Vault.ts +370 -0
- package/src/vault/VaultAllocation.ts +58 -0
- package/src/vault/VaultConfig.ts +55 -0
- package/src/vault/VaultUtils.ts +47 -0
- package/src/vault/index.ts +4 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbiCoder,
|
|
3
|
+
BigNumberish,
|
|
4
|
+
MaxUint256,
|
|
5
|
+
keccak256,
|
|
6
|
+
toBigInt,
|
|
7
|
+
} from "ethers";
|
|
8
|
+
import { MarketParamsStruct } from "ethers-types/dist/protocols/morpho/blue/MorphoBlue";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
LIQUIDATION_CURSOR,
|
|
12
|
+
MAX_LIQUIDATION_INCENTIVE_FACTOR,
|
|
13
|
+
ORACLE_PRICE_SCALE,
|
|
14
|
+
SECONDS_PER_YEAR,
|
|
15
|
+
} from "../constants";
|
|
16
|
+
import { MathLib, MathUtils, RoundingDirection, SharesMath } from "../maths";
|
|
17
|
+
import { MarketId } from "../types";
|
|
18
|
+
|
|
19
|
+
export namespace MarketUtils {
|
|
20
|
+
export function getMarketId(market: MarketParamsStruct) {
|
|
21
|
+
const encodedMarket = AbiCoder.defaultAbiCoder().encode(
|
|
22
|
+
["address", "address", "address", "address", "uint256"],
|
|
23
|
+
[
|
|
24
|
+
market.loanToken,
|
|
25
|
+
market.collateralToken,
|
|
26
|
+
market.oracle,
|
|
27
|
+
market.irm,
|
|
28
|
+
market.lltv,
|
|
29
|
+
]
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return keccak256(encodedMarket) as MarketId;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getLiquidationIncentiveFactor({
|
|
36
|
+
lltv,
|
|
37
|
+
}: {
|
|
38
|
+
lltv: BigNumberish;
|
|
39
|
+
}) {
|
|
40
|
+
return MathLib.min(
|
|
41
|
+
MAX_LIQUIDATION_INCENTIVE_FACTOR,
|
|
42
|
+
MathLib.wDivDown(
|
|
43
|
+
MathLib.WAD,
|
|
44
|
+
MathLib.WAD -
|
|
45
|
+
MathLib.wMulDown(LIQUIDATION_CURSOR, MathLib.WAD - toBigInt(lltv))
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getUtilization({
|
|
51
|
+
totalSupplyAssets,
|
|
52
|
+
totalBorrowAssets,
|
|
53
|
+
}: {
|
|
54
|
+
totalSupplyAssets: BigNumberish;
|
|
55
|
+
totalBorrowAssets: BigNumberish;
|
|
56
|
+
}) {
|
|
57
|
+
totalSupplyAssets = toBigInt(totalSupplyAssets);
|
|
58
|
+
|
|
59
|
+
if (totalSupplyAssets === 0n) return 0n;
|
|
60
|
+
|
|
61
|
+
return MathLib.wDivDown(totalBorrowAssets, totalSupplyAssets);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getSupplyRate(
|
|
65
|
+
borrowRate: BigNumberish,
|
|
66
|
+
{ utilization, fee }: { utilization: BigNumberish; fee: BigNumberish }
|
|
67
|
+
) {
|
|
68
|
+
const borrowRateWithoutFees = MathLib.wMulUp(borrowRate, utilization);
|
|
69
|
+
|
|
70
|
+
return MathLib.wMulUp(borrowRateWithoutFees, MathLib.WAD - toBigInt(fee));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getApy(rate: BigNumberish) {
|
|
74
|
+
return MathLib.wTaylorCompounded(rate, SECONDS_PER_YEAR);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getAccruedInterest(
|
|
78
|
+
borrowRate: BigNumberish,
|
|
79
|
+
{
|
|
80
|
+
totalSupplyAssets,
|
|
81
|
+
totalBorrowAssets,
|
|
82
|
+
totalSupplyShares,
|
|
83
|
+
fee,
|
|
84
|
+
}: {
|
|
85
|
+
totalSupplyAssets: BigNumberish;
|
|
86
|
+
totalBorrowAssets: BigNumberish;
|
|
87
|
+
totalSupplyShares: BigNumberish;
|
|
88
|
+
fee: BigNumberish;
|
|
89
|
+
},
|
|
90
|
+
elapsed = 0n
|
|
91
|
+
) {
|
|
92
|
+
const interest = MathLib.wMulDown(
|
|
93
|
+
totalBorrowAssets,
|
|
94
|
+
MathLib.wTaylorCompounded(borrowRate, elapsed)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const feeAmount = MathLib.wMulDown(interest, fee);
|
|
98
|
+
const feeShares = toSupplyShares(
|
|
99
|
+
feeAmount,
|
|
100
|
+
{
|
|
101
|
+
totalSupplyAssets: toBigInt(totalSupplyAssets) - feeAmount,
|
|
102
|
+
totalSupplyShares,
|
|
103
|
+
},
|
|
104
|
+
"Down"
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
return { interest, feeShares };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function getLiquidityToUtilization(
|
|
111
|
+
{
|
|
112
|
+
totalSupplyAssets,
|
|
113
|
+
totalBorrowAssets,
|
|
114
|
+
}: {
|
|
115
|
+
totalSupplyAssets: BigNumberish;
|
|
116
|
+
totalBorrowAssets: BigNumberish;
|
|
117
|
+
},
|
|
118
|
+
utilization: BigNumberish
|
|
119
|
+
) {
|
|
120
|
+
utilization = toBigInt(utilization);
|
|
121
|
+
totalSupplyAssets = toBigInt(totalSupplyAssets);
|
|
122
|
+
totalBorrowAssets = toBigInt(totalBorrowAssets);
|
|
123
|
+
|
|
124
|
+
if (utilization === 0n) {
|
|
125
|
+
if (totalBorrowAssets === 0n) return totalSupplyAssets;
|
|
126
|
+
|
|
127
|
+
return 0n;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return MathUtils.zeroFloorSub(
|
|
131
|
+
totalSupplyAssets,
|
|
132
|
+
MathLib.wDivUp(totalBorrowAssets, utilization)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function getCollateralPower(
|
|
137
|
+
collateral: BigNumberish,
|
|
138
|
+
{ lltv }: { lltv: BigNumberish }
|
|
139
|
+
) {
|
|
140
|
+
return MathLib.wMulDown(collateral, lltv);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function getCollateralValue(
|
|
144
|
+
collateral: BigNumberish,
|
|
145
|
+
{ price }: { price: BigNumberish }
|
|
146
|
+
) {
|
|
147
|
+
return MathLib.mulDivDown(collateral, price, ORACLE_PRICE_SCALE);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function getMaxBorrowAssets(
|
|
151
|
+
collateral: BigNumberish,
|
|
152
|
+
market: { price: BigNumberish },
|
|
153
|
+
{ lltv }: { lltv: BigNumberish }
|
|
154
|
+
) {
|
|
155
|
+
return MathLib.wMulDown(getCollateralValue(collateral, market), lltv);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function getMaxBorrowableAssets(
|
|
159
|
+
{
|
|
160
|
+
collateral,
|
|
161
|
+
borrowShares,
|
|
162
|
+
}: { collateral: BigNumberish; borrowShares: BigNumberish },
|
|
163
|
+
market: {
|
|
164
|
+
totalBorrowAssets: BigNumberish;
|
|
165
|
+
totalBorrowShares: BigNumberish;
|
|
166
|
+
price: BigNumberish;
|
|
167
|
+
},
|
|
168
|
+
marketConfig: { lltv: BigNumberish }
|
|
169
|
+
) {
|
|
170
|
+
return MathUtils.zeroFloorSub(
|
|
171
|
+
getMaxBorrowAssets(collateral, market, marketConfig),
|
|
172
|
+
toBorrowAssets(borrowShares, market)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function getLiquidationSeizedAssets(
|
|
177
|
+
repaidShares: BigNumberish,
|
|
178
|
+
market: {
|
|
179
|
+
totalBorrowAssets: BigNumberish;
|
|
180
|
+
totalBorrowShares: BigNumberish;
|
|
181
|
+
price: BigNumberish;
|
|
182
|
+
},
|
|
183
|
+
config: { lltv: BigNumberish }
|
|
184
|
+
) {
|
|
185
|
+
market.price = toBigInt(market.price);
|
|
186
|
+
|
|
187
|
+
if (market.price === 0n) return 0n;
|
|
188
|
+
|
|
189
|
+
return MathLib.mulDivDown(
|
|
190
|
+
MathLib.wMulDown(
|
|
191
|
+
toBorrowAssets(repaidShares, market, "Down"),
|
|
192
|
+
getLiquidationIncentiveFactor(config)
|
|
193
|
+
),
|
|
194
|
+
ORACLE_PRICE_SCALE,
|
|
195
|
+
market.price
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function getLiquidationRepaidShares(
|
|
200
|
+
seizedAssets: BigNumberish,
|
|
201
|
+
market: {
|
|
202
|
+
totalBorrowAssets: BigNumberish;
|
|
203
|
+
totalBorrowShares: BigNumberish;
|
|
204
|
+
price: BigNumberish;
|
|
205
|
+
},
|
|
206
|
+
config: { lltv: BigNumberish }
|
|
207
|
+
) {
|
|
208
|
+
return toBorrowShares(
|
|
209
|
+
MathLib.wDivUp(
|
|
210
|
+
MathLib.mulDivUp(seizedAssets, market.price, ORACLE_PRICE_SCALE),
|
|
211
|
+
getLiquidationIncentiveFactor(config)
|
|
212
|
+
),
|
|
213
|
+
market,
|
|
214
|
+
"Up"
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function getSeizableCollateral(
|
|
219
|
+
position: { collateral: BigNumberish; borrowShares: BigNumberish },
|
|
220
|
+
market: {
|
|
221
|
+
totalBorrowAssets: BigNumberish;
|
|
222
|
+
totalBorrowShares: BigNumberish;
|
|
223
|
+
price: BigNumberish;
|
|
224
|
+
},
|
|
225
|
+
config: { lltv: BigNumberish }
|
|
226
|
+
) {
|
|
227
|
+
market.price = toBigInt(market.price);
|
|
228
|
+
|
|
229
|
+
if (market.price === 0n || isHealthy(position, market, config)) return 0n;
|
|
230
|
+
|
|
231
|
+
return MathLib.min(
|
|
232
|
+
position.collateral,
|
|
233
|
+
getLiquidationSeizedAssets(position.borrowShares, market, config)
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function getWithdrawableCollateral(
|
|
238
|
+
{
|
|
239
|
+
collateral,
|
|
240
|
+
borrowShares,
|
|
241
|
+
}: { collateral: BigNumberish; borrowShares: BigNumberish },
|
|
242
|
+
market: {
|
|
243
|
+
totalBorrowAssets: BigNumberish;
|
|
244
|
+
totalBorrowShares: BigNumberish;
|
|
245
|
+
price: BigNumberish;
|
|
246
|
+
},
|
|
247
|
+
{ lltv }: { lltv: BigNumberish }
|
|
248
|
+
) {
|
|
249
|
+
market.price = toBigInt(market.price);
|
|
250
|
+
|
|
251
|
+
if (market.price === 0n) return 0n;
|
|
252
|
+
|
|
253
|
+
return MathUtils.zeroFloorSub(
|
|
254
|
+
toBigInt(collateral),
|
|
255
|
+
MathLib.wDivUp(
|
|
256
|
+
MathLib.mulDivUp(
|
|
257
|
+
toBorrowAssets(borrowShares, market),
|
|
258
|
+
ORACLE_PRICE_SCALE,
|
|
259
|
+
market.price
|
|
260
|
+
),
|
|
261
|
+
lltv
|
|
262
|
+
)
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function isHealthy(
|
|
267
|
+
{
|
|
268
|
+
collateral,
|
|
269
|
+
borrowShares,
|
|
270
|
+
}: { collateral: BigNumberish; borrowShares: BigNumberish },
|
|
271
|
+
market: {
|
|
272
|
+
totalBorrowAssets: BigNumberish;
|
|
273
|
+
totalBorrowShares: BigNumberish;
|
|
274
|
+
price: BigNumberish;
|
|
275
|
+
},
|
|
276
|
+
marketConfig: { lltv: BigNumberish }
|
|
277
|
+
) {
|
|
278
|
+
return (
|
|
279
|
+
getMaxBorrowAssets(collateral, market, marketConfig) >=
|
|
280
|
+
toBorrowAssets(borrowShares, market)
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Returns the price of the collateral quoted in the loan token (e.g. ETH/DAI)
|
|
286
|
+
* that set the user's position to be liquidatable.
|
|
287
|
+
* Returns null if the user is not a borrower
|
|
288
|
+
*/
|
|
289
|
+
export function getLiquidationPrice(
|
|
290
|
+
{
|
|
291
|
+
collateral,
|
|
292
|
+
borrowShares,
|
|
293
|
+
}: { collateral: BigNumberish; borrowShares: BigNumberish },
|
|
294
|
+
market: {
|
|
295
|
+
totalBorrowAssets: BigNumberish;
|
|
296
|
+
totalBorrowShares: BigNumberish;
|
|
297
|
+
},
|
|
298
|
+
marketConfig: { lltv: BigNumberish }
|
|
299
|
+
) {
|
|
300
|
+
borrowShares = toBigInt(borrowShares);
|
|
301
|
+
market.totalBorrowShares = toBigInt(market.totalBorrowShares);
|
|
302
|
+
|
|
303
|
+
if (borrowShares === 0n || market.totalBorrowShares === 0n) return null;
|
|
304
|
+
|
|
305
|
+
const collateralPower = getCollateralPower(collateral, marketConfig);
|
|
306
|
+
if (collateralPower === 0n) return MaxUint256;
|
|
307
|
+
|
|
308
|
+
const borrowAssets = toBorrowAssets(borrowShares, market);
|
|
309
|
+
|
|
310
|
+
return MathLib.mulDivUp(borrowAssets, ORACLE_PRICE_SCALE, collateralPower);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function getPriceVariationToLiquidation(
|
|
314
|
+
position: { collateral: BigNumberish; borrowShares: BigNumberish },
|
|
315
|
+
market: {
|
|
316
|
+
totalBorrowAssets: BigNumberish;
|
|
317
|
+
totalBorrowShares: BigNumberish;
|
|
318
|
+
price: BigNumberish;
|
|
319
|
+
},
|
|
320
|
+
marketConfig: { lltv: BigNumberish }
|
|
321
|
+
) {
|
|
322
|
+
market.price = toBigInt(market.price);
|
|
323
|
+
|
|
324
|
+
const liquidationPrice = getLiquidationPrice(
|
|
325
|
+
position,
|
|
326
|
+
market,
|
|
327
|
+
marketConfig
|
|
328
|
+
);
|
|
329
|
+
if (market.price === 0n || liquidationPrice == null) return null;
|
|
330
|
+
|
|
331
|
+
return MathLib.WAD - MathLib.wDivUp(liquidationPrice, market.price);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function getHealthFactor(
|
|
335
|
+
{
|
|
336
|
+
collateral,
|
|
337
|
+
borrowShares,
|
|
338
|
+
}: { collateral: BigNumberish; borrowShares: BigNumberish },
|
|
339
|
+
market: {
|
|
340
|
+
totalBorrowAssets: BigNumberish;
|
|
341
|
+
totalBorrowShares: BigNumberish;
|
|
342
|
+
price: BigNumberish;
|
|
343
|
+
},
|
|
344
|
+
marketConfig: { lltv: BigNumberish }
|
|
345
|
+
) {
|
|
346
|
+
borrowShares = toBigInt(borrowShares);
|
|
347
|
+
market.totalBorrowShares = toBigInt(market.totalBorrowShares);
|
|
348
|
+
|
|
349
|
+
if (borrowShares === 0n || market.totalBorrowShares === 0n) return null;
|
|
350
|
+
|
|
351
|
+
const borrowAssets = toBorrowAssets(borrowShares, market);
|
|
352
|
+
if (borrowAssets === 0n) return MaxUint256;
|
|
353
|
+
|
|
354
|
+
const maxBorrowAssets = getMaxBorrowAssets(
|
|
355
|
+
collateral,
|
|
356
|
+
market,
|
|
357
|
+
marketConfig
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
return MathLib.wDivDown(maxBorrowAssets, borrowAssets);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function getLtv(
|
|
364
|
+
{
|
|
365
|
+
collateral,
|
|
366
|
+
borrowShares,
|
|
367
|
+
}: { collateral: BigNumberish; borrowShares: BigNumberish },
|
|
368
|
+
market: {
|
|
369
|
+
totalBorrowAssets: BigNumberish;
|
|
370
|
+
totalBorrowShares: BigNumberish;
|
|
371
|
+
price: BigNumberish;
|
|
372
|
+
}
|
|
373
|
+
) {
|
|
374
|
+
borrowShares = toBigInt(borrowShares);
|
|
375
|
+
market.totalBorrowShares = toBigInt(market.totalBorrowShares);
|
|
376
|
+
|
|
377
|
+
if (borrowShares === 0n || market.totalBorrowShares === 0n) return null;
|
|
378
|
+
|
|
379
|
+
const collateralValue = getCollateralValue(collateral, market);
|
|
380
|
+
if (collateralValue === 0n) return MaxUint256;
|
|
381
|
+
|
|
382
|
+
return MathLib.wDivUp(
|
|
383
|
+
toBorrowAssets(borrowShares, market),
|
|
384
|
+
collateralValue
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function getBorrowCapacityUsage(
|
|
389
|
+
position: { collateral: BigNumberish; borrowShares: BigNumberish },
|
|
390
|
+
market: {
|
|
391
|
+
totalBorrowAssets: BigNumberish;
|
|
392
|
+
totalBorrowShares: BigNumberish;
|
|
393
|
+
price: BigNumberish;
|
|
394
|
+
},
|
|
395
|
+
marketConfig: { lltv: BigNumberish }
|
|
396
|
+
) {
|
|
397
|
+
const hf = getHealthFactor(position, market, marketConfig);
|
|
398
|
+
if (hf === null) return null;
|
|
399
|
+
if (hf === 0n) return MaxUint256;
|
|
400
|
+
|
|
401
|
+
return MathLib.wDivUp(MathLib.WAD, hf);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export function toSupplyAssets(
|
|
405
|
+
shares: BigNumberish,
|
|
406
|
+
market: {
|
|
407
|
+
totalSupplyAssets: BigNumberish;
|
|
408
|
+
totalSupplyShares: BigNumberish;
|
|
409
|
+
},
|
|
410
|
+
rounding: RoundingDirection = "Down"
|
|
411
|
+
) {
|
|
412
|
+
return SharesMath.toAssets(
|
|
413
|
+
shares,
|
|
414
|
+
market.totalSupplyAssets,
|
|
415
|
+
market.totalSupplyShares,
|
|
416
|
+
rounding
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export function toSupplyShares(
|
|
421
|
+
assets: BigNumberish,
|
|
422
|
+
market: {
|
|
423
|
+
totalSupplyAssets: BigNumberish;
|
|
424
|
+
totalSupplyShares: BigNumberish;
|
|
425
|
+
},
|
|
426
|
+
rounding: RoundingDirection = "Up"
|
|
427
|
+
) {
|
|
428
|
+
return SharesMath.toShares(
|
|
429
|
+
assets,
|
|
430
|
+
market.totalSupplyAssets,
|
|
431
|
+
market.totalSupplyShares,
|
|
432
|
+
rounding
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export function toBorrowAssets(
|
|
437
|
+
shares: BigNumberish,
|
|
438
|
+
market: {
|
|
439
|
+
totalBorrowAssets: BigNumberish;
|
|
440
|
+
totalBorrowShares: BigNumberish;
|
|
441
|
+
},
|
|
442
|
+
rounding: RoundingDirection = "Up"
|
|
443
|
+
) {
|
|
444
|
+
return SharesMath.toAssets(
|
|
445
|
+
shares,
|
|
446
|
+
market.totalBorrowAssets,
|
|
447
|
+
market.totalBorrowShares,
|
|
448
|
+
rounding
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export function toBorrowShares(
|
|
453
|
+
assets: BigNumberish,
|
|
454
|
+
market: {
|
|
455
|
+
totalBorrowAssets: BigNumberish;
|
|
456
|
+
totalBorrowShares: BigNumberish;
|
|
457
|
+
},
|
|
458
|
+
rounding: RoundingDirection = "Down"
|
|
459
|
+
) {
|
|
460
|
+
return SharesMath.toShares(
|
|
461
|
+
assets,
|
|
462
|
+
market.totalBorrowAssets,
|
|
463
|
+
market.totalBorrowShares,
|
|
464
|
+
rounding
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { parseUnits } from "ethers";
|
|
2
|
+
|
|
3
|
+
import { SECONDS_PER_YEAR } from "../constants";
|
|
4
|
+
|
|
5
|
+
import { MathLib } from "./MathLib";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* JS implementation of {@link https://github.com/morpho-org/morpho-blue-irm/blob/main/src/libraries/adaptive-curve/ExpLib.sol ExpLib} used by the Adaptive Curve IRM.
|
|
9
|
+
*/
|
|
10
|
+
export namespace AdaptiveCurveIrmLib {
|
|
11
|
+
export const CURVE_STEEPNESS = parseUnits("4");
|
|
12
|
+
export const TARGET_UTILIZATION = parseUnits("0.9");
|
|
13
|
+
export const INITIAL_RATE_AT_TARGET = parseUnits("0.04") / SECONDS_PER_YEAR;
|
|
14
|
+
export const ADJUSTMENT_SPEED = parseUnits("50") / SECONDS_PER_YEAR;
|
|
15
|
+
export const MIN_RATE_AT_TARGET = parseUnits("0.001") / SECONDS_PER_YEAR;
|
|
16
|
+
export const MAX_RATE_AT_TARGET = parseUnits("2") / SECONDS_PER_YEAR;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* ln(2), scaled by WAD.
|
|
20
|
+
*/
|
|
21
|
+
export const LN_2_INT = 693147180559945309n;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* ln(1e-18), scaled by WAD.
|
|
25
|
+
*/
|
|
26
|
+
export const LN_WEI_INT = -41_446531673892822312n;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Above this bound, `wExp` is clipped to avoid overflowing when multiplied with 1 ether.
|
|
30
|
+
* This upper bound corresponds to: ln(type(int256).max / 1e36) (scaled by WAD, floored).
|
|
31
|
+
*/
|
|
32
|
+
export const WEXP_UPPER_BOUND = 93_859467695000404319n;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The value of wExp(`WEXP_UPPER_BOUND`).
|
|
36
|
+
*/
|
|
37
|
+
export const WEXP_UPPER_VALUE =
|
|
38
|
+
57716089161558943949701069502944508345128_422502756744429568n;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Returns an approximation of exp(x) used by the Adaptive Curve IRM.
|
|
42
|
+
* @param x
|
|
43
|
+
*/
|
|
44
|
+
export function wExp(x: bigint) {
|
|
45
|
+
// If x < ln(1e-18) then exp(x) < 1e-18 so it is rounded to zero.
|
|
46
|
+
if (x < LN_WEI_INT) return 0n;
|
|
47
|
+
// `wExp` is clipped to avoid overflowing when multiplied with 1 ether.
|
|
48
|
+
if (x >= WEXP_UPPER_BOUND) return WEXP_UPPER_VALUE;
|
|
49
|
+
|
|
50
|
+
// Decompose x as x = q * ln(2) + r with q an integer and -ln(2)/2 <= r <= ln(2)/2.
|
|
51
|
+
// q = x / ln(2) rounded half toward zero.
|
|
52
|
+
const roundingAdjustment = x < 0n ? -(LN_2_INT / 2n) : LN_2_INT / 2n;
|
|
53
|
+
const q = (x + roundingAdjustment) / LN_2_INT;
|
|
54
|
+
const r = x - q * LN_2_INT;
|
|
55
|
+
|
|
56
|
+
// Compute e^r with a 2nd-order Taylor polynomial.
|
|
57
|
+
const expR = MathLib.WAD + r + (r * r) / MathLib.WAD / 2n;
|
|
58
|
+
|
|
59
|
+
// Return e^x = 2^q * e^r.
|
|
60
|
+
if (q === 0n) return expR << q;
|
|
61
|
+
return expR >> -q;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getBorrowRate(
|
|
65
|
+
startUtilization: bigint,
|
|
66
|
+
startRateAtTarget: bigint,
|
|
67
|
+
elapsed: bigint
|
|
68
|
+
) {
|
|
69
|
+
const errNormFactor =
|
|
70
|
+
startUtilization > TARGET_UTILIZATION
|
|
71
|
+
? MathLib.WAD - TARGET_UTILIZATION
|
|
72
|
+
: TARGET_UTILIZATION;
|
|
73
|
+
const err = MathLib.wDivDown(
|
|
74
|
+
startUtilization - TARGET_UTILIZATION,
|
|
75
|
+
errNormFactor
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
let avgRateAtTarget;
|
|
79
|
+
let endRateAtTarget;
|
|
80
|
+
|
|
81
|
+
if (startRateAtTarget === 0n) {
|
|
82
|
+
// First interaction.
|
|
83
|
+
avgRateAtTarget = INITIAL_RATE_AT_TARGET;
|
|
84
|
+
endRateAtTarget = INITIAL_RATE_AT_TARGET;
|
|
85
|
+
} else {
|
|
86
|
+
// The speed is assumed constant between two updates, but it is in fact not constant because of interest.
|
|
87
|
+
// So the rate is always underestimated.
|
|
88
|
+
const speed = MathLib.wMulDown(ADJUSTMENT_SPEED, err);
|
|
89
|
+
const linearAdaptation = speed * elapsed;
|
|
90
|
+
|
|
91
|
+
if (linearAdaptation === 0n) {
|
|
92
|
+
// If linearAdaptation == 0, avgRateAtTarget = endRateAtTarget = startRateAtTarget;
|
|
93
|
+
avgRateAtTarget = startRateAtTarget;
|
|
94
|
+
endRateAtTarget = startRateAtTarget;
|
|
95
|
+
} else {
|
|
96
|
+
// Non negative because MIN_RATE_AT_TARGET > 0.
|
|
97
|
+
const _newRateAtTarget = (linearAdaptation: bigint) =>
|
|
98
|
+
MathLib.min(
|
|
99
|
+
MathLib.max(
|
|
100
|
+
MathLib.wMulDown(startRateAtTarget, wExp(linearAdaptation)),
|
|
101
|
+
MIN_RATE_AT_TARGET
|
|
102
|
+
),
|
|
103
|
+
MAX_RATE_AT_TARGET
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Formula of the average rate that should be returned to Morpho Blue:
|
|
107
|
+
// avg = 1/T * ∫_0^T curve(startRateAtTarget*exp(speed*x), err) dx
|
|
108
|
+
// The integral is approximated with the trapezoidal rule:
|
|
109
|
+
// avg ~= 1/T * Σ_i=1^N [curve(f((i-1) * T/N), err) + curve(f(i * T/N), err)] / 2 * T/N
|
|
110
|
+
// Where f(x) = startRateAtTarget*exp(speed*x)
|
|
111
|
+
// avg ~= Σ_i=1^N [curve(f((i-1) * T/N), err) + curve(f(i * T/N), err)] / (2 * N)
|
|
112
|
+
// As curve is linear in its first argument:
|
|
113
|
+
// avg ~= curve([Σ_i=1^N [f((i-1) * T/N) + f(i * T/N)] / (2 * N), err)
|
|
114
|
+
// avg ~= curve([(f(0) + f(T))/2 + Σ_i=1^(N-1) f(i * T/N)] / N, err)
|
|
115
|
+
// avg ~= curve([(startRateAtTarget + endRateAtTarget)/2 + Σ_i=1^(N-1) f(i * T/N)] / N, err)
|
|
116
|
+
// With N = 2:
|
|
117
|
+
// avg ~= curve([(startRateAtTarget + endRateAtTarget)/2 + startRateAtTarget*exp(speed*T/2)] / 2, err)
|
|
118
|
+
// avg ~= curve([startRateAtTarget + endRateAtTarget + 2*startRateAtTarget*exp(speed*T/2)] / 4, err)
|
|
119
|
+
endRateAtTarget = _newRateAtTarget(linearAdaptation);
|
|
120
|
+
avgRateAtTarget =
|
|
121
|
+
(startRateAtTarget +
|
|
122
|
+
endRateAtTarget +
|
|
123
|
+
2n * _newRateAtTarget(linearAdaptation / 2n)) /
|
|
124
|
+
4n;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Non negative because 1 - 1/C >= 0, C - 1 >= 0.
|
|
129
|
+
const coeff =
|
|
130
|
+
err < 0
|
|
131
|
+
? MathLib.WAD - MathLib.wDivDown(MathLib.WAD, CURVE_STEEPNESS)
|
|
132
|
+
: CURVE_STEEPNESS - MathLib.WAD;
|
|
133
|
+
|
|
134
|
+
// Non negative if avgRateAtTarget >= 0 because if err < 0, coeff <= 1.
|
|
135
|
+
return {
|
|
136
|
+
avgBorrowRate: MathLib.wMulDown(
|
|
137
|
+
MathLib.wMulDown(coeff, err) + MathLib.WAD,
|
|
138
|
+
avgRateAtTarget
|
|
139
|
+
),
|
|
140
|
+
endRateAtTarget,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|