-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathYieldSpaceMath.sol
609 lines (568 loc) · 21.4 KB
/
YieldSpaceMath.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
/// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;
import { Errors } from "./Errors.sol";
import { FixedPointMath, ONE } from "./FixedPointMath.sol";
import { HyperdriveMath } from "./HyperdriveMath.sol";
/// @author DELV
/// @title YieldSpaceMath
/// @notice Math for the YieldSpace pricing model.
/// @custom:disclaimer The language used in this code is for coding convenience
/// only, and is not intended to, and does not, have any
/// particular legal or regulatory significance.
///
/// @dev It is advised for developers to attain the pre-requisite knowledge
/// of how this implementation works on the mathematical level. This
/// excerpt attempts to document this pre-requisite knowledge explaining
/// the underpinning mathematical concepts in an understandable manner and
/// relating it directly to the code implementation.
/// This implementation is based on a paper called "YieldSpace with Yield
/// Bearing Vaults" or more casually "Modified YieldSpace". It can be
/// found at the following link.
///
/// https://hackmd.io/lRZ4mgdrRgOpxZQXqKYlFw?view
///
/// That paper builds on the original YieldSpace paper, "YieldSpace:
/// An Automated Liquidity Provider for Fixed Yield Tokens". It can be
/// found at the following link:
///
/// https://yieldprotocol.com/YieldSpace.pdf
library YieldSpaceMath {
using FixedPointMath for uint256;
/// @dev Calculates the amount of bonds a user will receive from the pool by
/// providing a specified amount of shares. We underestimate the amount
/// of bonds out.
/// @param ze The effective share reserves.
/// @param y The bond reserves.
/// @param dz The amount of shares paid to the pool.
/// @param t The time elapsed since the term's start.
/// @param c The vault share price.
/// @param mu The initial vault share price.
/// @return result The amount of bonds the trader receives.
function calculateBondsOutGivenSharesInDown(
uint256 ze,
uint256 y,
uint256 dz,
uint256 t,
uint256 c,
uint256 mu
) internal pure returns (uint256 result) {
bool success;
(result, success) = calculateBondsOutGivenSharesInDownSafe(
ze,
y,
dz,
t,
c,
mu
);
if (!success) {
Errors.throwInsufficientLiquidityError();
}
}
/// @dev Calculates the amount of bonds a user will receive from the pool by
/// providing a specified amount of shares. This function returns a
/// success flag instead of reverting. We underestimate the amount
/// of bonds out.
/// @param ze The effective share reserves.
/// @param y The bond reserves.
/// @param dz The amount of shares paid to the pool.
/// @param t The time elapsed since the term's start.
/// @param c The vault share price.
/// @param mu The initial vault share price.
/// @return The amount of bonds the trader receives.
/// @return A flag indicating if the calculation succeeded.
function calculateBondsOutGivenSharesInDownSafe(
uint256 ze,
uint256 y,
uint256 dz,
uint256 t,
uint256 c,
uint256 mu
) internal pure returns (uint256, bool) {
// NOTE: We round k up to make the rhs of the equation larger.
//
// k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t)
uint256 k = kUp(ze, y, t, c, mu);
// NOTE: We round ze down to make the rhs of the equation larger.
//
// (µ * (ze + dz))^(1 - t)
ze = mu.mulDown(ze + dz).pow(t);
// (c / µ) * (µ * (ze + dz))^(1 - t)
ze = c.mulDivDown(ze, mu);
// If k < ze, we return a failure flag since the calculation would have
// underflowed.
if (k < ze) {
return (0, false);
}
// NOTE: We round _y up to make the rhs of the equation larger.
//
// (k - (c / µ) * (µ * (ze + dz))^(1 - t))^(1 / (1 - t))
uint256 _y;
unchecked {
_y = k - ze;
}
if (_y >= ONE) {
// Rounding up the exponent results in a larger result.
_y = _y.pow(ONE.divUp(t));
} else {
// Rounding down the exponent results in a larger result.
_y = _y.pow(ONE.divDown(t));
}
// If y < _y, we return a failure flag since the calculation would have
// underflowed.
if (y < _y) {
return (0, false);
}
// Δy = y - (k - (c / µ) * (µ * (ze + dz))^(1 - t))^(1 / (1 - t))
unchecked {
return (y - _y, true);
}
}
/// @dev Calculates the amount of shares a user must provide the pool to
/// receive a specified amount of bonds. We overestimate the amount of
/// shares in.
/// @param ze The effective share reserves.
/// @param y The bond reserves.
/// @param dy The amount of bonds paid to the trader.
/// @param t The time elapsed since the term's start.
/// @param c The vault share price.
/// @param mu The initial vault share price.
/// @return result The amount of shares the trader pays.
function calculateSharesInGivenBondsOutUp(
uint256 ze,
uint256 y,
uint256 dy,
uint256 t,
uint256 c,
uint256 mu
) internal pure returns (uint256 result) {
bool success;
(result, success) = calculateSharesInGivenBondsOutUpSafe(
ze,
y,
dy,
t,
c,
mu
);
if (!success) {
Errors.throwInsufficientLiquidityError();
}
}
/// @dev Calculates the amount of shares a user must provide the pool to
/// receive a specified amount of bonds. This function returns a
/// success flag instead of reverting. We overestimate the amount of
/// shares in.
/// @param ze The effective share reserves.
/// @param y The bond reserves.
/// @param dy The amount of bonds paid to the trader.
/// @param t The time elapsed since the term's start.
/// @param c The vault share price.
/// @param mu The initial vault share price.
/// @return The amount of shares the trader pays.
/// @return A flag indicating if the calculation succeeded.
function calculateSharesInGivenBondsOutUpSafe(
uint256 ze,
uint256 y,
uint256 dy,
uint256 t,
uint256 c,
uint256 mu
) internal pure returns (uint256, bool) {
// NOTE: We round k up to make the lhs of the equation larger.
//
// k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t)
uint256 k = kUp(ze, y, t, c, mu);
// If y < dy, we return a failure flag since the calculation would have
// underflowed.
if (y < dy) {
return (0, false);
}
// (y - dy)^(1 - t)
unchecked {
y -= dy;
}
y = y.pow(t);
// If k < y, we return a failure flag since the calculation would have
// underflowed.
if (k < y) {
return (0, false);
}
// NOTE: We round _z up to make the lhs of the equation larger.
//
// ((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))
uint256 _z;
unchecked {
_z = k - y;
}
_z = _z.mulDivUp(mu, c);
if (_z >= ONE) {
// Rounding up the exponent results in a larger result.
_z = _z.pow(ONE.divUp(t));
} else {
// Rounding down the exponent results in a larger result.
_z = _z.pow(ONE.divDown(t));
}
// ((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))) / µ
_z = _z.divUp(mu);
// If _z < ze, we return a failure flag since the calculation would have
// underflowed.
if (_z < ze) {
return (0, false);
}
// Δz = (((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))) / µ - ze
unchecked {
return (_z - ze, true);
}
}
/// @dev Calculates the amount of shares a user must provide the pool to
/// receive a specified amount of bonds. We underestimate the amount of
/// shares in.
/// @param ze The effective share reserves.
/// @param y The bond reserves.
/// @param dy The amount of bonds paid to the trader.
/// @param t The time elapsed since the term's start.
/// @param c The vault share price.
/// @param mu The initial vault share price.
/// @return The amount of shares the user pays.
function calculateSharesInGivenBondsOutDown(
uint256 ze,
uint256 y,
uint256 dy,
uint256 t,
uint256 c,
uint256 mu
) internal pure returns (uint256) {
// NOTE: We round k down to make the lhs of the equation smaller.
//
// k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t)
uint256 k = kDown(ze, y, t, c, mu);
// If y < dy, we have no choice but to revert.
if (y < dy) {
Errors.throwInsufficientLiquidityError();
}
// (y - dy)^(1 - t)
unchecked {
y -= dy;
}
y = y.pow(t);
// If k < y, we have no choice but to revert.
if (k < y) {
Errors.throwInsufficientLiquidityError();
}
// NOTE: We round _z down to make the lhs of the equation smaller.
//
// _z = ((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))
uint256 _z;
unchecked {
_z = k - y;
}
_z = _z.mulDivDown(mu, c);
if (_z >= ONE) {
// Rounding down the exponent results in a smaller result.
_z = _z.pow(ONE.divDown(t));
} else {
// Rounding up the exponent results in a smaller result.
_z = _z.pow(ONE.divUp(t));
}
// ((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))) / µ
_z = _z.divDown(mu);
// If _z < ze, we have no choice but to revert.
if (_z < ze) {
Errors.throwInsufficientLiquidityError();
}
// Δz = (((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))) / µ - ze
unchecked {
return _z - ze;
}
}
/// @dev Calculates the amount of shares a user will receive from the pool
/// by providing a specified amount of bonds. This function reverts if
/// an integer overflow or underflow occurs. We underestimate the
/// amount of shares out.
/// @param ze The effective share reserves.
/// @param y The bond reserves.
/// @param dy The amount of bonds paid to the pool.
/// @param t The time elapsed since the term's start.
/// @param c The vault share price.
/// @param mu The initial vault share price.
/// @return result The amount of shares the user receives.
function calculateSharesOutGivenBondsInDown(
uint256 ze,
uint256 y,
uint256 dy,
uint256 t,
uint256 c,
uint256 mu
) internal pure returns (uint256 result) {
bool success;
(result, success) = calculateSharesOutGivenBondsInDownSafe(
ze,
y,
dy,
t,
c,
mu
);
if (!success) {
Errors.throwInsufficientLiquidityError();
}
}
/// @dev Calculates the amount of shares a user will receive from the pool
/// by providing a specified amount of bonds. This function returns a
/// success flag instead of reverting. We underestimate the amount of
/// shares out.
/// @param ze The effective share reserves.
/// @param y The bond reserves.
/// @param dy The amount of bonds paid to the pool.
/// @param t The time elapsed since the term's start.
/// @param c The vault share price.
/// @param mu The initial vault share price.
/// @return The amount of shares the user receives
/// @return A flag indicating if the calculation succeeded.
function calculateSharesOutGivenBondsInDownSafe(
uint256 ze,
uint256 y,
uint256 dy,
uint256 t,
uint256 c,
uint256 mu
) internal pure returns (uint256, bool) {
// NOTE: We round k up to make the rhs of the equation larger.
//
// k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t)
uint256 k = kUp(ze, y, t, c, mu);
// (y + dy)^(1 - t)
y = (y + dy).pow(t);
// If k is less than y, we return with a failure flag.
if (k < y) {
return (0, false);
}
// NOTE: We round _z up to make the rhs of the equation larger.
//
// ((k - (y + dy)^(1 - t)) / (c / µ))^(1 / (1 - t)))
uint256 _z;
unchecked {
_z = k - y;
}
_z = _z.mulDivUp(mu, c);
if (_z >= ONE) {
// Rounding the exponent up results in a larger outcome.
_z = _z.pow(ONE.divUp(t));
} else {
// Rounding the exponent down results in a larger outcome.
_z = _z.pow(ONE.divDown(t));
}
// ((k - (y + dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))) / µ
_z = _z.divUp(mu);
// If ze is less than _z, we return a failure flag since the calculation
// underflowed.
if (ze < _z) {
return (0, false);
}
// Δz = ze - ((k - (y + dy)^(1 - t) ) / (c / µ))^(1 / (1 - t)) / µ
unchecked {
return (ze - _z, true);
}
}
/// @dev Calculates the share payment required to purchase the maximum
/// amount of bonds from the pool. This function returns a success flag
/// instead of reverting. We round so that the max buy amount is
/// underestimated.
/// @param ze The effective share reserves.
/// @param y The bond reserves.
/// @param t The time elapsed since the term's start.
/// @param c The vault share price.
/// @param mu The initial vault share price.
/// @return The share payment to purchase the maximum amount of bonds.
/// @return A flag indicating if the calculation succeeded.
function calculateMaxBuySharesInSafe(
uint256 ze,
uint256 y,
uint256 t,
uint256 c,
uint256 mu
) internal pure returns (uint256, bool) {
// We solve for the maximum buy using the constraint that the pool's
// spot price can never exceed 1. We do this by noting that a spot price
// of 1, ((mu * ze') / y') ** tau = 1, implies that mu * ze' = y'. This
// simplifies YieldSpace to:
//
// k = ((c / mu) + 1) * (mu * ze') ** (1 - tau),
//
// This gives us the maximum effective share reserves of:
//
// ze' = (1 / mu) * (k / ((c / mu) + 1)) ** (1 / (1 - tau)).
uint256 k = kDown(ze, y, t, c, mu);
uint256 optimalZe = k.divDown(c.divUp(mu) + ONE);
if (optimalZe >= ONE) {
// Rounding the exponent down results in a smaller outcome.
optimalZe = optimalZe.pow(ONE.divDown(t));
} else {
// Rounding the exponent up results in a smaller outcome.
optimalZe = optimalZe.pow(ONE.divUp(t));
}
optimalZe = optimalZe.divDown(mu);
// The optimal trade size is given by dz = ze' - ze. If the calculation
// underflows, we return a failure flag.
if (optimalZe < ze) {
return (0, false);
}
unchecked {
return (optimalZe - ze, true);
}
}
/// @dev Calculates the maximum amount of bonds that can be purchased with
/// the specified reserves. This function returns a success flag
/// instead of reverting. We round so that the max buy amount is
/// underestimated.
/// @param ze The effective share reserves.
/// @param y The bond reserves.
/// @param t The time elapsed since the term's start.
/// @param c The vault share price.
/// @param mu The initial vault share price.
/// @return The maximum amount of bonds that can be purchased.
/// @return A flag indicating if the calculation succeeded.
function calculateMaxBuyBondsOutSafe(
uint256 ze,
uint256 y,
uint256 t,
uint256 c,
uint256 mu
) internal pure returns (uint256, bool) {
// We can use the same derivation as in `calculateMaxBuySharesIn` to
// calculate the minimum bond reserves as:
//
// y' = (k / ((c / mu) + 1)) ** (1 / (1 - tau)).
uint256 k = kUp(ze, y, t, c, mu);
uint256 optimalY = k.divUp(c.divDown(mu) + ONE);
if (optimalY >= ONE) {
// Rounding the exponent up results in a larger outcome.
optimalY = optimalY.pow(ONE.divUp(t));
} else {
// Rounding the exponent down results in a larger outcome.
optimalY = optimalY.pow(ONE.divDown(t));
}
// The optimal trade size is given by dy = y - y'. If the calculation
// underflows, we return a failure flag.
if (y < optimalY) {
return (0, false);
}
unchecked {
return (y - optimalY, true);
}
}
/// @dev Calculates the maximum amount of bonds that can be sold with the
/// specified reserves. We round so that the max sell amount is
/// underestimated.
/// @param z The share reserves.
/// @param zeta The share adjustment.
/// @param y The bond reserves.
/// @param zMin The minimum share reserves.
/// @param t The time elapsed since the term's start.
/// @param c The vault share price.
/// @param mu The initial vault share price.
/// @return The maximum amount of bonds that can be sold.
/// @return A flag indicating whether or not the calculation was successful.
function calculateMaxSellBondsInSafe(
uint256 z,
int256 zeta,
uint256 y,
uint256 zMin,
uint256 t,
uint256 c,
uint256 mu
) internal pure returns (uint256, bool) {
// If the share adjustment is negative, the minimum share reserves is
// given by `zMin - zeta`, which ensures that the share reserves never
// fall below the minimum share reserves. Otherwise, the minimum share
// reserves is just zMin.
if (zeta < 0) {
zMin = zMin + uint256(-zeta);
}
// We solve for the maximum bond amount using the constraint that the
// pool's share reserves can never fall below the minimum share reserves
// `zMin`. Substituting `ze = zMin` simplifies YieldSpace to:
//
// k = (c / mu) * (mu * zMin) ** (1 - tau) + y' ** (1 - tau)
//
// This gives us the maximum bonds that can be sold to the pool as:
//
// y' = (k - (c / mu) * (mu * zMin) ** (1 - tau)) ** (1 / (1 - tau)).
(uint256 ze, bool success) = HyperdriveMath
.calculateEffectiveShareReservesSafe(z, zeta);
if (!success) {
return (0, false);
}
uint256 k = kDown(ze, y, t, c, mu);
uint256 rhs = c.mulDivUp(mu.mulUp(zMin).pow(t), mu);
if (k < rhs) {
return (0, false);
}
uint256 optimalY;
unchecked {
optimalY = k - rhs;
}
if (optimalY >= ONE) {
// Rounding the exponent down results in a smaller outcome.
optimalY = optimalY.pow(ONE.divDown(t));
} else {
// Rounding the exponent up results in a smaller outcome.
optimalY = optimalY.pow(ONE.divUp(t));
}
// The optimal trade size is given by dy = y' - y. If this subtraction
// will underflow, we return a failure flag.
if (optimalY < y) {
return (0, false);
}
unchecked {
return (optimalY - y, true);
}
}
/// @dev Calculates the YieldSpace invariant k. This invariant is given by:
///
/// k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t)
///
/// This variant of the calculation overestimates the result.
/// @param ze The effective share reserves.
/// @param y The bond reserves.
/// @param t The time elapsed since the term's start.
/// @param c The vault share price.
/// @param mu The initial vault share price.
/// @return The YieldSpace invariant, k.
function kUp(
uint256 ze,
uint256 y,
uint256 t,
uint256 c,
uint256 mu
) internal pure returns (uint256) {
// NOTE: Rounding up to overestimate the result.
//
/// k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t)
return c.mulDivUp(mu.mulUp(ze).pow(t), mu) + y.pow(t);
}
/// @dev Calculates the YieldSpace invariant k. This invariant is given by:
///
/// k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t)
///
/// This variant of the calculation underestimates the result.
/// @param ze The effective share reserves.
/// @param y The bond reserves.
/// @param t The time elapsed since the term's start.
/// @param c The vault share price.
/// @param mu The initial vault share price.
/// @return The modified YieldSpace Constant.
function kDown(
uint256 ze,
uint256 y,
uint256 t,
uint256 c,
uint256 mu
) internal pure returns (uint256) {
// NOTE: Rounding down to underestimate the result.
//
/// k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t)
return c.mulDivDown(mu.mulDown(ze).pow(t), mu) + y.pow(t);
}
}