• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

delvtech / hyperdrive / 8887270944

30 Apr 2024 12:22AM UTC coverage: 93.375% (-0.2%) from 93.53%
8887270944

Pull #963

github

mcclurejt
hh task definitions, native hh config for deployments, readme
Pull Request #963: contract deployment + verification tooling

1804 of 1932 relevant lines covered (93.37%)

396490.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.43
/contracts/src/libraries/YieldSpaceMath.sol
1
/// SPDX-License-Identifier: Apache-2.0
2
pragma solidity 0.8.20;
3

4
import { Errors } from "./Errors.sol";
5
import { FixedPointMath, ONE } from "./FixedPointMath.sol";
6
import { HyperdriveMath } from "./HyperdriveMath.sol";
7

8
/// @author DELV
9
/// @title YieldSpaceMath
10
/// @notice Math for the YieldSpace pricing model.
11
/// @custom:disclaimer The language used in this code is for coding convenience
12
///                    only, and is not intended to, and does not, have any
13
///                    particular legal or regulatory significance.
14
///
15
/// @dev It is advised for developers to attain the pre-requisite knowledge
16
///      of how this implementation works on the mathematical level. This
17
///      excerpt attempts to document this pre-requisite knowledge explaining
18
///      the underpinning mathematical concepts in an understandable manner and
19
///      relating it directly to the code implementation.
20
///      This implementation is based on a paper called "YieldSpace with Yield
21
///      Bearing Vaults" or more casually "Modified YieldSpace". It can be
22
///      found at the following link.
23
///
24
///      https://hackmd.io/lRZ4mgdrRgOpxZQXqKYlFw?view
25
///
26
///      That paper builds on the original YieldSpace paper, "YieldSpace:
27
///      An Automated Liquidity Provider for Fixed Yield Tokens". It can be
28
///      found at the following link:
29
///
30
///      https://yieldprotocol.com/YieldSpace.pdf
31
library YieldSpaceMath {
32
    using FixedPointMath for uint256;
33

34
    /// @dev Calculates the amount of bonds a user will receive from the pool by
35
    ///      providing a specified amount of shares. We underestimate the amount
36
    ///      of bonds out.
37
    /// @param ze The effective share reserves.
38
    /// @param y The bond reserves.
39
    /// @param dz The amount of shares paid to the pool.
40
    /// @param t The time elapsed since the term's start.
41
    /// @param c The vault share price.
42
    /// @param mu The initial vault share price.
43
    /// @return The amount of bonds the trader receives.
44
    function calculateBondsOutGivenSharesInDown(
45
        uint256 ze,
46
        uint256 y,
47
        uint256 dz,
48
        uint256 t,
49
        uint256 c,
50
        uint256 mu
51
    ) internal pure returns (uint256) {
52
        // NOTE: We round k up to make the rhs of the equation larger.
53
        //
54
        // k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t)
55
        uint256 k = kUp(ze, y, t, c, mu);
54,498✔
56

57
        // NOTE: We round ze down to make the rhs of the equation larger.
58
        //
59
        //  (µ * (ze + dz))^(1 - t)
60
        ze = mu.mulDown(ze + dz).pow(t);
36,332✔
61
        //  (c / µ) * (µ * (ze + dz))^(1 - t)
62
        ze = c.mulDivDown(ze, mu);
36,332✔
63

64
        // If k < ze, we have no choice but to revert.
65
        if (k < ze) {
36,332✔
66
            Errors.throwInsufficientLiquidityError();
258✔
67
        }
68

69
        // NOTE: We round _y up to make the rhs of the equation larger.
70
        //
71
        // (k - (c / µ) * (µ * (ze + dz))^(1 - t))^(1 / (1 - t))
72
        uint256 _y;
36,074✔
73
        unchecked {
74
            _y = k - ze;
36,074✔
75
        }
76
        if (_y >= ONE) {
36,074✔
77
            // Rounding up the exponent results in a larger result.
78
            _y = _y.pow(ONE.divUp(t));
33,016✔
79
        } else {
80
            // Rounding down the exponent results in a larger result.
81
            _y = _y.pow(ONE.divDown(t));
3,058✔
82
        }
83

84
        // If y < _y, we have no choice but to revert.
85
        if (y < _y) {
36,074✔
86
            Errors.throwInsufficientLiquidityError();
×
87
        }
88

89
        // Δy = y - (k - (c / µ) * (µ * (ze + dz))^(1 - t))^(1 / (1 - t))
90
        unchecked {
91
            return y - _y;
54,111✔
92
        }
93
    }
94

95
    /// @dev Calculates the amount of shares a user must provide the pool to
96
    ///      receive a specified amount of bonds. We overestimate the amount of
97
    ///      shares in.
98
    /// @param ze The effective share reserves.
99
    /// @param y The bond reserves.
100
    /// @param dy The amount of bonds paid to the trader.
101
    /// @param t The time elapsed since the term's start.
102
    /// @param c The vault share price.
103
    /// @param mu The initial vault share price.
104
    /// @return result The amount of shares the trader pays.
105
    function calculateSharesInGivenBondsOutUp(
106
        uint256 ze,
107
        uint256 y,
108
        uint256 dy,
109
        uint256 t,
110
        uint256 c,
111
        uint256 mu
112
    ) internal pure returns (uint256 result) {
113
        bool success;
13,842✔
114
        (result, success) = calculateSharesInGivenBondsOutUpSafe(
13,842✔
115
            ze,
116
            y,
117
            dy,
118
            t,
119
            c,
120
            mu
121
        );
122
        if (!success) {
13,842✔
123
            Errors.throwInsufficientLiquidityError();
×
124
        }
125
    }
126

127
    /// @dev Calculates the amount of shares a user must provide the pool to
128
    ///      receive a specified amount of bonds. This function returns a
129
    ///      success flag instead of reverting. We overestimate the amount of
130
    ///      shares in.
131
    /// @param ze The effective share reserves.
132
    /// @param y The bond reserves.
133
    /// @param dy The amount of bonds paid to the trader.
134
    /// @param t The time elapsed since the term's start.
135
    /// @param c The vault share price.
136
    /// @param mu The initial vault share price.
137
    /// @return The amount of shares the trader pays.
138
    /// @return A flag indicating if the calculation succeeded.
139
    function calculateSharesInGivenBondsOutUpSafe(
140
        uint256 ze,
141
        uint256 y,
142
        uint256 dy,
143
        uint256 t,
144
        uint256 c,
145
        uint256 mu
146
    ) internal pure returns (uint256, bool) {
147
        // NOTE: We round k up to make the lhs of the equation larger.
148
        //
149
        // k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t)
150
        uint256 k = kUp(ze, y, t, c, mu);
438,837✔
151

152
        // If y < dy, we return a failure flag since the calculation would have
153
        // underflowed.
154
        if (y < dy) {
292,558✔
155
            return (0, false);
×
156
        }
157

158
        // (y - dy)^(1 - t)
159
        unchecked {
160
            y -= dy;
292,558✔
161
        }
162
        y = y.pow(t);
292,558✔
163

164
        // If k < y, we return a failure flag since the calculation would have
165
        // underflowed.
166
        if (k < y) {
292,558✔
167
            return (0, false);
×
168
        }
169

170
        // NOTE: We round _z up to make the lhs of the equation larger.
171
        //
172
        // ((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))
173
        uint256 _z;
292,558✔
174
        unchecked {
175
            _z = k - y;
292,558✔
176
        }
177
        _z = _z.mulDivUp(mu, c);
292,558✔
178
        if (_z >= ONE) {
292,558✔
179
            // Rounding up the exponent results in a larger result.
180
            _z = _z.pow(ONE.divUp(t));
286,562✔
181
        } else {
182
            // Rounding down the exponent results in a larger result.
183
            _z = _z.pow(ONE.divDown(t));
5,996✔
184
        }
185
        // ((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))) / µ
186
        _z = _z.divUp(mu);
292,558✔
187

188
        // If _z < ze, we return a failure flag since the calculation would have
189
        // underflowed.
190
        if (_z < ze) {
292,558✔
191
            return (0, false);
2✔
192
        }
193

194
        // Δz = (((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))) / µ - ze
195
        unchecked {
196
            return (_z - ze, true);
292,556✔
197
        }
198
    }
199

200
    /// @dev Calculates the amount of shares a user must provide the pool to
201
    ///      receive a specified amount of bonds. We underestimate the amount of
202
    ///      shares in.
203
    /// @param ze The effective share reserves.
204
    /// @param y The bond reserves.
205
    /// @param dy The amount of bonds paid to the trader.
206
    /// @param t The time elapsed since the term's start.
207
    /// @param c The vault share price.
208
    /// @param mu The initial vault share price.
209
    /// @return The amount of shares the user pays.
210
    function calculateSharesInGivenBondsOutDown(
211
        uint256 ze,
212
        uint256 y,
213
        uint256 dy,
214
        uint256 t,
215
        uint256 c,
216
        uint256 mu
217
    ) internal pure returns (uint256) {
218
        // NOTE: We round k down to make the lhs of the equation smaller.
219
        //
220
        // k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t)
221
        uint256 k = kDown(ze, y, t, c, mu);
26,403✔
222

223
        // If y < dy, we have no choice but to revert.
224
        if (y < dy) {
17,602✔
225
            Errors.throwInsufficientLiquidityError();
2✔
226
        }
227

228
        // (y - dy)^(1 - t)
229
        unchecked {
230
            y -= dy;
17,600✔
231
        }
232
        y = y.pow(t);
17,600✔
233

234
        // If k < y, we have no choice but to revert.
235
        if (k < y) {
17,600✔
236
            Errors.throwInsufficientLiquidityError();
×
237
        }
238

239
        // NOTE: We round _z down to make the lhs of the equation smaller.
240
        //
241
        // _z = ((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))
242
        uint256 _z;
17,600✔
243
        unchecked {
244
            _z = k - y;
17,600✔
245
        }
246
        _z = _z.mulDivDown(mu, c);
17,600✔
247
        if (_z >= ONE) {
17,600✔
248
            // Rounding down the exponent results in a smaller result.
249
            _z = _z.pow(ONE.divDown(t));
2,828✔
250
        } else {
251
            // Rounding up the exponent results in a smaller result.
252
            _z = _z.pow(ONE.divUp(t));
14,772✔
253
        }
254
        // ((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))) / µ
255
        _z = _z.divDown(mu);
17,600✔
256

257
        // If _z < ze, we have no choice but to revert.
258
        if (_z < ze) {
17,600✔
259
            Errors.throwInsufficientLiquidityError();
×
260
        }
261

262
        // Δz = (((k - (y - dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))) / µ - ze
263
        unchecked {
264
            return _z - ze;
26,400✔
265
        }
266
    }
267

268
    /// @dev Calculates the amount of shares a user will receive from the pool
269
    ///      by providing a specified amount of bonds. This function reverts if
270
    ///      an integer overflow or underflow occurs. We underestimate the
271
    ///      amount of shares out.
272
    /// @param ze The effective share reserves.
273
    /// @param y The bond reserves.
274
    /// @param dy The amount of bonds paid to the pool.
275
    /// @param t The time elapsed since the term's start.
276
    /// @param c The vault share price.
277
    /// @param mu The initial vault share price.
278
    /// @return result The amount of shares the user receives.
279
    function calculateSharesOutGivenBondsInDown(
280
        uint256 ze,
281
        uint256 y,
282
        uint256 dy,
283
        uint256 t,
284
        uint256 c,
285
        uint256 mu
286
    ) internal pure returns (uint256 result) {
287
        bool success;
46,360✔
288
        (result, success) = calculateSharesOutGivenBondsInDownSafe(
46,360✔
289
            ze,
290
            y,
291
            dy,
292
            t,
293
            c,
294
            mu
295
        );
296
        if (!success) {
46,360✔
297
            Errors.throwInsufficientLiquidityError();
258✔
298
        }
299
    }
300

301
    /// @dev Calculates the amount of shares a user will receive from the pool
302
    ///      by providing a specified amount of bonds. This function returns a
303
    ///      success flag instead of reverting. We underestimate the amount of
304
    ///      shares out.
305
    /// @param ze The effective share reserves.
306
    /// @param y The bond reserves.
307
    /// @param dy The amount of bonds paid to the pool.
308
    /// @param t The time elapsed since the term's start.
309
    /// @param c The vault share price.
310
    /// @param mu The initial vault share price.
311
    /// @return The amount of shares the user receives
312
    /// @return A flag indicating if the calculation succeeded.
313
    function calculateSharesOutGivenBondsInDownSafe(
314
        uint256 ze,
315
        uint256 y,
316
        uint256 dy,
317
        uint256 t,
318
        uint256 c,
319
        uint256 mu
320
    ) internal pure returns (uint256, bool) {
321
        // NOTE: We round k up to make the rhs of the equation larger.
322
        //
323
        // k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t)
324
        uint256 k = kUp(ze, y, t, c, mu);
576,156✔
325

326
        // (y + dy)^(1 - t)
327
        y = (y + dy).pow(t);
384,104✔
328

329
        // If k is less than y, we return with a failure flag.
330
        if (k < y) {
384,104✔
331
            return (0, false);
258✔
332
        }
333

334
        // NOTE: We round _z up to make the rhs of the equation larger.
335
        //
336
        // ((k - (y + dy)^(1 - t)) / (c / µ))^(1 / (1 - t)))
337
        uint256 _z;
383,846✔
338
        unchecked {
339
            _z = k - y;
383,846✔
340
        }
341
        _z = _z.mulDivUp(mu, c);
383,846✔
342
        if (_z >= ONE) {
383,846✔
343
            // Rounding the exponent up results in a larger outcome.
344
            _z = _z.pow(ONE.divUp(t));
362,316✔
345
        } else {
346
            // Rounding the exponent down results in a larger outcome.
347
            _z = _z.pow(ONE.divDown(t));
21,530✔
348
        }
349
        // ((k - (y + dy)^(1 - t) ) / (c / µ))^(1 / (1 - t))) / µ
350
        _z = _z.divUp(mu);
383,846✔
351

352
        // If ze is less than _z, we return a failure flag since the calculation
353
        // underflowed.
354
        if (ze < _z) {
383,846✔
355
            return (0, false);
84,472✔
356
        }
357

358
        // Δz = ze - ((k - (y + dy)^(1 - t) ) / (c / µ))^(1 / (1 - t)) / µ
359
        unchecked {
360
            return (ze - _z, true);
299,374✔
361
        }
362
    }
363

364
    /// @dev Calculates the share payment required to purchase the maximum
365
    ///      amount of bonds from the pool. This function returns a success flag
366
    ///      instead of reverting. We round so that the max buy amount is
367
    ///      underestimated.
368
    /// @param ze The effective share reserves.
369
    /// @param y The bond reserves.
370
    /// @param t The time elapsed since the term's start.
371
    /// @param c The vault share price.
372
    /// @param mu The initial vault share price.
373
    /// @return The share payment to purchase the maximum amount of bonds.
374
    /// @return A flag indicating if the calculation succeeded.
375
    function calculateMaxBuySharesInSafe(
376
        uint256 ze,
377
        uint256 y,
378
        uint256 t,
379
        uint256 c,
380
        uint256 mu
381
    ) internal pure returns (uint256, bool) {
382
        // We solve for the maximum buy using the constraint that the pool's
383
        // spot price can never exceed 1. We do this by noting that a spot price
384
        // of 1, ((mu * ze) / y) ** tau = 1, implies that mu * ze = y. This
385
        // simplifies YieldSpace to:
386
        //
387
        // k = ((c / mu) + 1) * (mu * ze') ** (1 - tau),
388
        //
389
        // This gives us the maximum effective share reserves of:
390
        //
391
        // ze' = (1 / mu) * (k / ((c / mu) + 1)) ** (1 / (1 - tau)).
392
        uint256 k = kDown(ze, y, t, c, mu);
3,465✔
393
        uint256 optimalZe = k.divDown(c.divUp(mu) + ONE);
3,465✔
394
        if (optimalZe >= ONE) {
2,310✔
395
            // Rounding the exponent down results in a smaller outcome.
396
            optimalZe = optimalZe.pow(ONE.divDown(t));
2,198✔
397
        } else {
398
            // Rounding the exponent up results in a smaller outcome.
399
            optimalZe = optimalZe.pow(ONE.divUp(t));
112✔
400
        }
401
        optimalZe = optimalZe.divDown(mu);
2,310✔
402

403
        // The optimal trade size is given by dz = ze' - ze. If the calculation
404
        // underflows, we return a failure flag.
405
        if (optimalZe < ze) {
2,310✔
406
            return (0, false);
×
407
        }
408
        unchecked {
409
            return (optimalZe - ze, true);
2,310✔
410
        }
411
    }
412

413
    /// @dev Calculates the maximum amount of bonds that can be purchased with
414
    ///      the specified reserves. This function returns a success flag
415
    ///      instead of reverting. We round so that the max buy amount is
416
    ///      underestimated.
417
    /// @param ze The effective share reserves.
418
    /// @param y The bond reserves.
419
    /// @param t The time elapsed since the term's start.
420
    /// @param c The vault share price.
421
    /// @param mu The initial vault share price.
422
    /// @return The maximum amount of bonds that can be purchased.
423
    /// @return A flag indicating if the calculation succeeded.
424
    function calculateMaxBuyBondsOutSafe(
425
        uint256 ze,
426
        uint256 y,
427
        uint256 t,
428
        uint256 c,
429
        uint256 mu
430
    ) internal pure returns (uint256, bool) {
431
        // We can use the same derivation as in `calculateMaxBuySharesIn` to
432
        // calculate the minimum bond reserves as:
433
        //
434
        // y' = (k / ((c / mu) + 1)) ** (1 / (1 - tau)).
435
        uint256 k = kUp(ze, y, t, c, mu);
426,042✔
436
        uint256 optimalY = k.divUp(c.divDown(mu) + ONE);
426,042✔
437
        if (optimalY >= ONE) {
284,028✔
438
            // Rounding the exponent up results in a larger outcome.
439
            optimalY = optimalY.pow(ONE.divUp(t));
279,974✔
440
        } else {
441
            // Rounding the exponent down results in a larger outcome.
442
            optimalY = optimalY.pow(ONE.divDown(t));
4,054✔
443
        }
444

445
        // The optimal trade size is given by dy = y - y'. If the calculation
446
        // underflows, we return a failure flag.
447
        if (y < optimalY) {
284,028✔
448
            return (0, false);
×
449
        }
450
        unchecked {
451
            return (y - optimalY, true);
284,028✔
452
        }
453
    }
454

455
    /// @dev Calculates the maximum amount of bonds that can be sold with the
456
    ///      specified reserves. We round so that the max sell amount is
457
    ///      underestimated.
458
    /// @param z The share reserves.
459
    /// @param zeta The share adjustment.
460
    /// @param y The bond reserves.
461
    /// @param zMin The minimum share reserves.
462
    /// @param t The time elapsed since the term's start.
463
    /// @param c The vault share price.
464
    /// @param mu The initial vault share price.
465
    /// @return The maximum amount of bonds that can be sold.
466
    /// @return A flag indicating whether or not the calculation was successful.
467
    function calculateMaxSellBondsInSafe(
468
        uint256 z,
469
        int256 zeta,
470
        uint256 y,
471
        uint256 zMin,
472
        uint256 t,
473
        uint256 c,
474
        uint256 mu
475
    ) internal pure returns (uint256, bool) {
476
        // If the share adjustment is negative, the minimum share reserves is
477
        // given by `zMin - zeta`, which ensures that the share reserves never
478
        // fall below the minimum share reserves. Otherwise, the minimum share
479
        // reserves is just zMin.
480
        if (zeta < 0) {
326,958✔
481
            zMin = zMin + uint256(-zeta);
11,896✔
482
        }
483

484
        // We solve for the maximum bond amount using the constraint that the
485
        // pool's share reserves can never fall below the minimum share reserves
486
        // `zMin`. Substituting `ze = zMin` simplifies YieldSpace to:
487
        //
488
        // k = (c / mu) * (mu * zMin) ** (1 - tau) + y' ** (1 - tau)
489
        //
490
        // This gives us the maximum bonds that can be sold to the pool as:
491
        //
492
        // y' = (k - (c / mu) * (mu * zMin) ** (1 - tau)) ** (1 / (1 - tau)).
493
        (uint256 ze, bool success) = HyperdriveMath
490,437✔
494
            .calculateEffectiveShareReservesSafe(z, zeta);
495

496
        if (!success) {
326,958✔
497
            return (0, false);
×
498
        }
499
        uint256 k = kDown(ze, y, t, c, mu);
490,437✔
500
        uint256 rhs = c.mulDivUp(mu.mulUp(zMin).pow(t), mu);
490,437✔
501
        if (k < rhs) {
326,958✔
502
            return (0, false);
12✔
503
        }
504
        uint256 optimalY;
326,946✔
505
        unchecked {
506
            optimalY = k - rhs;
326,946✔
507
        }
508
        if (optimalY >= ONE) {
326,946✔
509
            // Rounding the exponent down results in a smaller outcome.
510
            optimalY = optimalY.pow(ONE.divDown(t));
317,826✔
511
        } else {
512
            // Rounding the exponent up results in a smaller outcome.
513
            optimalY = optimalY.pow(ONE.divUp(t));
9,120✔
514
        }
515

516
        // The optimal trade size is given by dy = y' - y. If this subtraction
517
        // will underflow, we return a failure flag.
518
        if (optimalY < y) {
326,946✔
519
            return (0, false);
198✔
520
        }
521
        unchecked {
522
            return (optimalY - y, true);
326,748✔
523
        }
524
    }
525

526
    /// @dev Calculates the YieldSpace invariant k. This invariant is given by:
527
    ///
528
    ///      k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t)
529
    ///
530
    ///      This variant of the calculation overestimates the result.
531
    /// @param ze The effective share reserves.
532
    /// @param y The bond reserves.
533
    /// @param t The time elapsed since the term's start.
534
    /// @param c The vault share price.
535
    /// @param mu The initial vault share price.
536
    /// @return The YieldSpace invariant, k.
537
    function kUp(
538
        uint256 ze,
539
        uint256 y,
540
        uint256 t,
541
        uint256 c,
542
        uint256 mu
543
    ) internal pure returns (uint256) {
544
        // NOTE: Rounding up to overestimate the result.
545
        //
546
        /// k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t)
547
        return c.mulDivUp(mu.mulUp(ze).pow(t), mu) + y.pow(t);
2,503,995✔
548
    }
549

550
    /// @dev Calculates the YieldSpace invariant k. This invariant is given by:
551
    ///
552
    ///      k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t)
553
    ///
554
    ///      This variant of the calculation underestimates the result.
555
    /// @param ze The effective share reserves.
556
    /// @param y The bond reserves.
557
    /// @param t The time elapsed since the term's start.
558
    /// @param c The vault share price.
559
    /// @param mu The initial vault share price.
560
    /// @return The modified YieldSpace Constant.
561
    function kDown(
562
        uint256 ze,
563
        uint256 y,
564
        uint256 t,
565
        uint256 c,
566
        uint256 mu
567
    ) internal pure returns (uint256) {
568
        // NOTE: Rounding down to underestimate the result.
569
        //
570
        /// k = (c / µ) * (µ * ze)^(1 - t) + y^(1 - t)
571
        return c.mulDivDown(mu.mulDown(ze).pow(t), mu) + y.pow(t);
1,046,385✔
572
    }
573
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc