11// SPDX-License-Identifier: AGPL-3.0-only
22pragma solidity 0.8.19 ;
33
4- import "solstat/Invariant .sol " ;
4+ import "solstat/Gaussian .sol " ;
55import {
66 PortfolioPool,
77 Iteration,
@@ -15,6 +15,7 @@ uint256 constant WAD = 1e18;
1515uint256 constant SQRT_WAD = 1e9 ;
1616uint256 constant YEAR = 31556953 seconds ;
1717uint256 constant BISECTION_EPSILON = 0 ;
18+ uint256 constant BISECTION_ITERATIONS = 256 ;
1819int256 constant BISECTION_ERROR = 2 ;
1920
2021/**
@@ -29,6 +30,8 @@ library RMM01Lib {
2930
3031 error UndefinedPrice ();
3132 error OverflowWad (int256 wad );
33+ error InvalidQuotient (uint256 quotient );
34+ error InvalidDifference (uint256 difference );
3235
3336 /**
3437 * @notice
@@ -80,11 +83,17 @@ library RMM01Lib {
8083 // σ√τ
8184 uint256 volSqrtYearsWad = volatilityWad.mulWadDown (sqrtTauWad);
8285 // y / K
83- uint256 quotientWad = reserveYPerWad.divWadUp (strikePriceWad); // todo: review rounding direction. Avoids scenarios division truncates to 0.
86+ uint256 quotientWad = reserveYPerWad.divWadUp (strikePriceWad);
87+ if (quotientWad == 0 || quotientWad == WAD) {
88+ revert InvalidQuotient (quotientWad);
89+ }
8490 // Φ⁻¹(y/K)
8591 int256 inverseCdfQuotient = Gaussian.ppf (int256 (quotientWad));
8692 // 1 - x
8793 uint256 differenceWad = WAD - reserveXPerWad;
94+ if (differenceWad == 0 || differenceWad == WAD) {
95+ revert InvalidDifference (differenceWad);
96+ }
8897 // Φ⁻¹(1-x)
8998 int256 inverseCdfDifference = Gaussian.ppf (int256 (differenceWad));
9099 // k = Φ⁻¹(y/K) - Φ⁻¹(1-x) + σ√τ
@@ -115,13 +124,18 @@ library RMM01Lib {
115124 uint256 timeRemainingSec ,
116125 int256 invariant
117126 ) internal pure returns (uint256 reserveXPerWad ) {
127+ // If y reserves has reached upper bound, x reserves is zero.
128+ if (reserveYPerWad >= strikePriceWad) return 0 ;
129+ // If y reserves has reached lower bound, x reserves is one.
130+ if (reserveYPerWad == 0 ) return WAD;
131+
118132 uint256 yearsWad = timeRemainingSec.divWadDown (uint256 (YEAR));
119133 // √τ, √τ is scaled to WAD by multiplying by 1E9.
120134 uint256 sqrtTauWad = yearsWad.sqrt () * SQRT_WAD;
121135 // σ√τ
122136 uint256 volSqrtYearsWad = volatilityWad.mulWadDown (sqrtTauWad);
123137 // y / K
124- uint256 quotientWad = reserveYPerWad.divWadDown (strikePriceWad);
138+ uint256 quotientWad = reserveYPerWad.divWadUp (strikePriceWad);
125139 // Φ⁻¹(y/K)
126140 int256 inverseCdfQuotient = Gaussian.ppf (int256 (quotientWad));
127141 // Φ⁻¹(y/K) + σ√τ - k
@@ -154,6 +168,11 @@ library RMM01Lib {
154168 uint256 timeRemainingSec ,
155169 int256 invariant
156170 ) internal pure returns (uint256 reserveYPerWad ) {
171+ // If x reserves has reached upper bound, y reserves is zero.
172+ if (reserveXPerWad >= WAD) return 0 ;
173+ // If x reserves has reached lower bound, y reserves is equal to the strike price.
174+ if (reserveXPerWad == 0 ) return strikePriceWad;
175+
157176 uint256 yearsWad = timeRemainingSec.divWadDown (uint256 (YEAR));
158177 // √τ, √τ is scaled to WAD by multiplying by 1E9.
159178 uint256 sqrtTauWad = yearsWad.sqrt () * SQRT_WAD;
@@ -172,14 +191,17 @@ library RMM01Lib {
172191 }
173192
174193 /**
175- * @dev Computes the invariant of the RMM-01 trading function.
194+ * @notice
195+ * Computes the invariant of the RMM-01 trading function.
196+ *
197+ * @dev
198+ * k = Φ⁻¹(y/K) - Φ⁻¹(1-x) + σ√τ
199+ *
176200 * @param self Pool instance.
177201 * @param R_x Quantity of `asset` reserves scaled to WAD units per WAD of liquidity.
178202 * @param R_y Quantity of `quote` reserves scaled to WAD units per WAD of liquidity.
179203 * @param timeRemainingSec Amount of time in seconds until the `self` PortfolioPool is matured.
180- * @return invariantWad Signed invariant denominated in `quote` tokens, scaled to WAD units.
181- * @custom:math k = y - KΦ(Φ⁻¹(1-x) - σ√τ)
182- * @custom:dependency https://github.com/primitivefinance/solstat
204+ * @return invariantWad Invariant of the pool. Does not have an explicit unit denomination.
183205 */
184206 function invariantOf (
185207 PortfolioPool memory self ,
@@ -198,11 +220,18 @@ library RMM01Lib {
198220 }
199221
200222 /**
201- * @dev Approximation of amount out of tokens given a swap `amountIn`.
202- * It is not exactly precise to the optimal amount.
223+ * @notice
224+ * Gets the total tokens out given an amount of tokens in for a swap.
225+ *
226+ * @dev
227+ * Approximation of amount out of tokens given a swap of `amountIn`.
228+ *
229+ * @param self PortfolioPool instance.
230+ * @param sellAsset True if `asset` tokens are being sold for `quote` tokens.
203231 * @param amountIn Quantity of tokens in, units are native token decimals.
204- * @param timestamp Timestamp to use to compute the remaining duration in the Portfolio.
205- * @custom:error Maximum absolute error of 1e-6.
232+ * @param timestamp Expected timestamp of the swap's execution.
233+ * @param swapper Address of the account that is swapping.
234+ * @return amountOut Quantity of tokens out, units are native token decimals.
206235 */
207236 function getAmountOut (
208237 PortfolioPool memory self ,
@@ -235,8 +264,14 @@ library RMM01Lib {
235264 }
236265
237266 /**
238- * @notice Fetches the data needed to simulate a swap to compute the output of tokens.
239- * @dev Does not consider protocol fees, therefore feeAmount could be overestimated since protocol fees are not subtracted.
267+ * @notice
268+ * Fetches the data needed to simulate a swap to compute the output of tokens.
269+ *
270+ * @dev
271+ * Does not consider protocol fees, therefore feeAmount could be overestimated since protocol fees are not subtracted.
272+ * Computes the invariant of the pool with rounded up virtual reserves for the output reserve of a trade.
273+ * This is on purpose so that the invariant is slightly overestimated, which will make sure any rounding errors
274+ * during swaps are advantageous to Portfolio rather than swappers.
240275 */
241276 function getSwapData (
242277 PortfolioPool memory self ,
@@ -356,18 +391,26 @@ library RMM01Lib {
356391 lower,
357392 upper,
358393 BISECTION_EPSILON, // Set to 0 to find the exact dependent reserve which sets the invariant to 0.
359- 256 , // Maximum amount of loops to run in bisection.
394+ BISECTION_ITERATIONS , // Maximum amount of loops to run in bisection.
360395 optimizeDependentReserve
361396 );
362397 // Increase dependent reserve per liquidity by 1 to account for precision loss.
363398 adjustedDependentReserve++ ;
364399 // Return the total adjusted dependent pool reserve for all the liquidity.
365- nextDep = adjustedDependentReserve.mulWadDown (data.liquidity);
400+ nextDep = adjustedDependentReserve.mulWadDown (data.liquidity); // Truncates product.
366401 }
367402
368403 /**
369- * @dev Optimized function used in the bisection method to compute the precise dependent reserve.
404+ * @notice
405+ * Function used in the bisection method to compute the precise dependent reserve.
406+ *
407+ * @dev
408+ * Optimizes for the case in which `optimized` is the dependent reserve
409+ * which produces an invariant equal to the previous invariant plus a small error.
410+ * Effectively, finds the dependent reserve which positively changes the invariant by `BISECTION_ERROR`.
411+ *
370412 * @param optimized Dependent reserve in WAD units per 1E18 liquidity.
413+ * @return error The difference between the invariant and the previous invariant plus a small error.
371414 */
372415 function optimizeDependentReserve (
373416 Bisection memory args ,
@@ -387,31 +430,37 @@ library RMM01Lib {
387430 }
388431
389432 /**
390- * @dev Computes the amount of `asset` and `quote` tokens scaled to WAD units to track per WAD units of liquidity.
433+ * @notice
434+ * Computes the x and y reserves given a price.
435+ *
436+ * @dev
437+ * Computes the amount of `asset` and `quote` tokens per one liquidity unit.
438+ *
439+ * @param self PortfolioPool instance.
391440 * @param priceWad Price of `asset` token scaled to WAD units.
392- * @param invariantWad Current invariant of the pool in its native WAD units.
441+ * @param invariantWad Current invariant of the pool.
442+ * @return R_x Quantity of `asset` reserves scaled to WAD units per WAD of liquidity.
443+ * @return R_y Quantity of `quote` reserves scaled to WAD units per WAD of liquidity.
393444 */
394445 function computeReservesWithPrice (
395446 PortfolioPool memory self ,
396447 uint256 priceWad ,
397448 int128 invariantWad
398449 ) internal pure returns (uint256 R_x , uint256 R_y ) {
399- uint256 terminalPriceWad = self.params.strikePrice;
400- uint256 volatilityFactorWad =
401- convertPercentageToWad (self.params.volatility);
402- uint256 timeRemainingSec = self.lastTau (); // uses self.lastTimestamp, is it set?
450+ uint256 volatilityWad = convertPercentageToWad (self.params.volatility);
451+ uint256 timeRemainingSec = self.lastTau (); // Uses self.lastTimestamp; must be set before calling this function.
403452 R_x = getXWithPrice ({
404453 prc: priceWad,
405- stk: terminalPriceWad ,
454+ stk: self.params.strikePrice ,
406455 vol: self.params.volatility,
407456 tau: timeRemainingSec
408457 });
409- R_y = Invariant. getY ({
410- R_x : R_x,
411- stk: terminalPriceWad ,
412- vol: volatilityFactorWad ,
413- tau : timeRemainingSec,
414- inv : invariantWad
458+ R_y = getReserveYPerWad ({
459+ reserveXPerWad : R_x,
460+ strikePriceWad: self.params.strikePrice ,
461+ volatilityWad: volatilityWad ,
462+ timeRemainingSec : timeRemainingSec,
463+ invariant : invariantWad
415464 });
416465 }
417466
0 commit comments