@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.
Files changed (59) hide show
  1. package/README.md +98 -0
  2. package/package.json +54 -0
  3. package/src/addresses.ts +261 -0
  4. package/src/chain/chain.constants.ts +235 -0
  5. package/src/chain/chain.test.ts +51 -0
  6. package/src/chain/chain.types.ts +42 -0
  7. package/src/chain/chain.utils.ts +44 -0
  8. package/src/chain/index.ts +2 -0
  9. package/src/constants.ts +18 -0
  10. package/src/errors.ts +75 -0
  11. package/src/ethers/ethers.test.ts +17 -0
  12. package/src/ethers/index.ts +2 -0
  13. package/src/ethers/safeGetAddress.ts +4 -0
  14. package/src/ethers/safeParseUnits.ts +29 -0
  15. package/src/evm.ts +172 -0
  16. package/src/helpers/format/format.test.ts +340 -0
  17. package/src/helpers/format/format.ts +416 -0
  18. package/src/helpers/format/index.ts +1 -0
  19. package/src/helpers/getChecksumedAddress.ts +15 -0
  20. package/src/helpers/index.ts +4 -0
  21. package/src/helpers/isZeroAddressOrUnset.ts +13 -0
  22. package/src/helpers/locale.ts +108 -0
  23. package/src/holding/Holding.ts +109 -0
  24. package/src/holding/index.ts +1 -0
  25. package/src/index.ts +34 -0
  26. package/src/market/Market.ts +479 -0
  27. package/src/market/MarketConfig.ts +108 -0
  28. package/src/market/MarketUtils.test.ts +25 -0
  29. package/src/market/MarketUtils.ts +467 -0
  30. package/src/market/index.ts +3 -0
  31. package/src/maths/AdaptiveCurveIrmLib.ts +143 -0
  32. package/src/maths/MathLib.ts +208 -0
  33. package/src/maths/MathUtils.ts +31 -0
  34. package/src/maths/SharesMath.ts +40 -0
  35. package/src/maths/index.ts +4 -0
  36. package/src/notifications.ts +167 -0
  37. package/src/position/Position.ts +251 -0
  38. package/src/position/index.ts +1 -0
  39. package/src/signatures/index.ts +18 -0
  40. package/src/signatures/manager.ts +50 -0
  41. package/src/signatures/permit.ts +126 -0
  42. package/src/signatures/permit2.ts +120 -0
  43. package/src/signatures/types.ts +18 -0
  44. package/src/signatures/utils.ts +83 -0
  45. package/src/tests/mocks/markets.ts +110 -0
  46. package/src/token/ERC20Metadata.ts +124 -0
  47. package/src/token/Token.ts +83 -0
  48. package/src/token/TokenNamespace.ts +76 -0
  49. package/src/token/WrappedToken.ts +142 -0
  50. package/src/token/index.ts +2 -0
  51. package/src/types.ts +37 -0
  52. package/src/user/User.ts +32 -0
  53. package/src/user/index.ts +2 -0
  54. package/src/user/user.types.ts +23 -0
  55. package/src/vault/Vault.ts +370 -0
  56. package/src/vault/VaultAllocation.ts +58 -0
  57. package/src/vault/VaultConfig.ts +55 -0
  58. package/src/vault/VaultUtils.ts +47 -0
  59. 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,3 @@
1
+ export * from "./MarketUtils";
2
+ export * from "./MarketConfig";
3
+ export * from "./Market";
@@ -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
+ }