Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(#439): updates trading function bound case v1.4.0-beta #441

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 55 additions & 66 deletions contracts/strategies/NormalStrategyLib.sol
Original file line number Diff line number Diff line change
@@ -62,6 +62,10 @@ error NormalStrategyLib_InvalidDuration();
error NormalStrategyLib_InvalidStrategyArgs();
error NormalStrategyLib_InvalidStrikePrice();
error NormalStrategyLib_InvalidVolatility();
error NormalStrategyLib_UpperReserveYBoundNotReached();
error NormalStrategyLib_LowerReserveYBoundNotReached();
error NormalStrategyLib_UpperReserveXBoundNotReached();
error NormalStrategyLib_LowerReserveXBoundNotReached();

/**
* @notice
@@ -164,12 +168,12 @@ function computeStdDevSqrtTau(NormalCurve memory self) pure returns (uint256) {
* As x approaches its lower bound of 0, Φ⁻¹(1-x) approaches +∞.
*
* Theoretically, if both x and y are at opposite bounds, they cancel eachother out.
* Pragmatically, it's a lot messier because infinity does not exist.
* Pragmatically, it's complicated because infinity does not exist.
*
* To handle this special property, the x and y reserves are checked for exceeding their bounds.
* If they do exceed a bound, they are overwritten to be at the bound minus 1.
* This prevents reverts that would happen if inputting values outside the theoretically and
* practial domain of the `Gaussian.ppf` function, i.e. inputs of 0 or 1.
* This prevents reverts that would happen if inputting values outside the theoretical
* domain of the `Gaussian.ppf` function, i.e. inputs of 0 or 1E18.
*
* By setting either or both the x and y to their bounds minus 1, each term will
* return the `ppf`'s effective minimum and maximum output.
@@ -180,54 +184,17 @@ function computeStdDevSqrtTau(NormalCurve memory self) pure returns (uint256) {
* It's likely that the x and y reserves will reach opposite bounds,
* in which case the invariant terms completely cancel out, returning `k = σ√τ`.
*
* In the case only one bound is reached, the invariant term for the bound will
* be either the min or max output of the ppf. This will require the other term
* to be very close to the min or max output of the ppf in order to cancel out.
*
* note Assumes maximum values are from valid pools created via this strategy in Portfolio.
*
* ## Example
* The maximum value of σ√τ, where σ_max = 25_000 and τ_max = 94670859, is 4330127017500000000 [4.33e18].
* The minimum value of σ√τ is 0.
*
* If all y tokens are removed, Φ⁻¹(0/K) -> Φ⁻¹(1e-18) = `invariantTermY` = -8710427241990476442.
* For the swap to pass, the invariant needs to grow by at least 1.
* Assuming we started at k = 0, we need to find the `invariantTermX` that solves this:
* -> 1 = -8710427241990476442 - invariantTermX + σ√τ.
* We need the σ√τ term to help us, so we can set it to its maximum value. Now we have:
* -> 1 = -8710427241990476442 - invariantTermX + 4330127017500000000.
* -> invariantTermX <= -8710427241990476442 + 4330127017500000000 - 1.
* -> invariantTermX <= -4380300224490476443 [~-4.38e18].
* -> Φ⁻¹(1-x) <= -4.380300224490476443. (not in wad anymore...)
* -> 1 - x <= Φ(-4.380300224490476443).
* -> x >= 1 - Φ(-4.380300224490476443).
* -> x >= ~0.999999218479338350565045237958.
* In conclusion, to take all y reserves, the x reserves must be __very close__ to its upper bound.
*
*
* If all x tokens are removed, `invariantTermX` = 8710427241990476442.
* For the swap to pass, the invariant needs to grow by at least 1.
* -> 1 = invariantTermY - 8710427241990476442 + σ√τ.
* We can use the σ√τ to help us, so we can set it to its maximum value. Now we have:
* -> 1 = invariantTermY - 8710427241990476442 + 4330127017500000000.
* -> invariantTermY >= 8710427241990476442 - 4330127017500000000 + 1.
* -> invariantTermY >= 4380300224490476443 [~4.38e18].
* -> Φ⁻¹(y/K) >= 4.380300224490476443. (not in wad anymore...)
* -> y/K >= Φ(4.380300224490476443).
* -> y >= KΦ(4.380300224490476443).
* -> y >= K * ~0.999994074204725119575460221025.
* However, since the `invariantTermX` will be subtracted from `invariantTermY` first, it will
* revert the transaction with an arithemtic underflow unless the `invariantTermX` is equal
* to it's maximum value of 8710427241990476442, which is when the y reserves are at their
* upper bound of y = k.
* In conclusion, to take all x reserves, the y reserves __must__ be at its upper bound.
* In the case only one bound is reached, the other reserve must be at the opposite bound or else the transaction will revert
* with the "ReserveX/YMustBeAtBounds" error.
*
* To take all y reserves (lower bound), the x reserves __must__ be at its upper bound.
* To take all x reserves (lower bound), the y reserves __must__ be at its upper bound.
*
* This overwrite of "infinity" effectively makes the trading function return an actual value
* at the bounds, instead of reverting. This is important for pools which might have accrued
* enough fees to push one of it's reserves over the bounds. Additionally, a maximum and minimum
* price are effectively enforced at these boundaries.
*
*
* note Adjusted trading function's invariant != original trading function's invariant.
*
* @return invariant k; Signed invariant of the pool.
@@ -243,27 +210,49 @@ function tradingFunction(NormalCurve memory self)
(uint256 upperBoundX, uint256 lowerBoundX) = self.getReserveXBounds();

// Overwrite the reserves to be as close to its bounds as possible, to avoid reverting.
uint256 invariantTermXInput; // x - 1
if (self.reserveXPerWad >= upperBoundX) {
// As x -> 1E18, 1E18 - x -> 0, Φ⁻¹(0) = -∞, therefore bound invariantTermXInput to 1E18 - 1E18 + 1.
invariantTermXInput = 1;
} else if (self.reserveXPerWad <= lowerBoundX) {
// As x -> 0, 1E18 - 0 -> 1E18, Φ⁻¹(1E18) = +∞, therefore bound invariantTermXInput to 1E18 - 0 - 1.
invariantTermXInput = WAD - 1;
uint256 invariantTermXInput; // 1E18 - x
uint256 invariantTermYInput =
self.reserveYPerWad.divWadDown(self.strikePriceWad); // y/K

if (self.reserveXPerWad >= upperBoundX || invariantTermYInput <= 0) {
// If x is at upper bound or y is at lower bound.
// Trading function is only valid y is at its lower bound when x is at its upper bound.
// If y is not at its lower bound, the invariant will be negative infinity, which will revert.
if (invariantTermYInput > 0) {
revert NormalStrategyLib_LowerReserveYBoundNotReached();
}
// If x is not at its upper bound, the invariant will be negative infinity, which will revert.
if (self.reserveXPerWad < upperBoundX) {
revert NormalStrategyLib_UpperReserveXBoundNotReached();
}

// As x -> 1E18, 1E18 - x -> 0, Φ⁻¹(0) = -∞
// As y -> 0, y/K -> 0, Φ⁻¹(0) = -∞
// These terms cancel eachother out, therefore leaving the invariant to be equal to `stdDevSqrtTau`.
return int256(stdDevSqrtTau);
} else if (self.reserveXPerWad <= lowerBoundX || invariantTermYInput >= WAD)
{
// If x is at lower bound or y is at upper bound.
// Trading function is only valid if y is at its upper bound when x is at its lower bound.
// If y is not at its upper bound, the invariant will be positive infinity, which will revert.
if (invariantTermYInput < WAD) {
revert NormalStrategyLib_UpperReserveYBoundNotReached();
}

// If x is not at its lower bound, the invariant will be positive infinity, which will revert.
if (self.reserveXPerWad > lowerBoundX) {
revert NormalStrategyLib_LowerReserveXBoundNotReached();
}

// As x -> 0, 1E18 - 0 -> 1E18, Φ⁻¹(1E18) = +∞
// As y -> K, y/K -> 1E18, Φ⁻¹(1E18) = +∞
// These terms cancel eachother out, therefore leaving the invariant to be equal to `stdDevSqrtTau`.
return int256(stdDevSqrtTau);
} else {
// Else no bounds have been reached and the invariant can be computed with both terms.
invariantTermXInput = WAD - self.reserveXPerWad;
}

uint256 invariantTermYInput =
self.reserveYPerWad.divWadDown(self.strikePriceWad); // y/K -> [0,1]
if (invariantTermYInput >= WAD) {
// As y -> K, y/K -> 1E18, Φ⁻¹(1E18) = +∞, therefore bound invariantTermYInput to 1E18 - 1.
invariantTermYInput = WAD - 1;
} else if (invariantTermYInput <= 0) {
// As y -> 0, y/K -> 0, Φ⁻¹(0) = -∞, therefore bound invariantTermYInput to 0 + 1.
invariantTermYInput = 1;
}

// Φ⁻¹(1-x)
int256 invariantTermX = Gaussian.ppf(int256(invariantTermXInput));
// Φ⁻¹(y/K)
@@ -290,9 +279,9 @@ function approximateXGivenY(NormalCurve memory self)
{
(uint256 upperBoundX, uint256 lowerBoundX) = self.getReserveXBounds();
(uint256 upperBoundY, uint256 lowerBoundY) = self.getReserveYBounds();
// If y reserves has reached upper bound, x reserves is zero.
// If y reserves has reached upper bound, x reserves must be zero (lower bound).
if (self.reserveYPerWad >= upperBoundY) return lowerBoundX;
// If y reserves has reached lower bound, x reserves is one.
// If y reserves has reached lower bound, x reserves must be one wad (upper bound).
if (self.reserveYPerWad <= lowerBoundY) return upperBoundX;
// σ√τ
uint256 stdDevSqrtTau = self.computeStdDevSqrtTau();
@@ -323,9 +312,9 @@ function approximateYGivenX(NormalCurve memory self)
{
(uint256 upperBoundX, uint256 lowerBoundX) = self.getReserveXBounds();
(uint256 upperBoundY, uint256 lowerBoundY) = self.getReserveYBounds();
// If x reserves has reached upper bound, y reserves is zero.
// If x reserves has reached upper bound, y reserves must be zero (lower bound).
if (self.reserveXPerWad >= upperBoundX) return lowerBoundY;
// If x reserves has reached lower bound, y reserves is equal to the strike price.
// If x reserves has reached lower bound, y reserves must be equal to the strike price (upper bound).
if (self.reserveXPerWad <= lowerBoundX) return upperBoundY;
// σ√τ
uint256 stdDevSqrtTau = self.computeStdDevSqrtTau();
15 changes: 11 additions & 4 deletions test/TestPortfolioInvariant.t.sol
Original file line number Diff line number Diff line change
@@ -51,6 +51,9 @@ contract TestPortfolioInvariant is Setup {
uint256 volSqrtYearsWad = volatilityWad.mulWadDown(sqrtTauWad);
// y / K
uint256 quotientWad = reserveYPerWad.divWadUp(strikePriceWad); // todo: should this round up??
if (quotientWad >= 1e18) {
quotientWad = reserveYPerWad.divWadDown(strikePriceWad);
}
console2.log(reserveYPerWad, strikePriceWad);
console2.log("quotientWad", quotientWad);
// Φ⁻¹(y/K)
@@ -258,6 +261,8 @@ contract TestPortfolioInvariant is Setup {
deltaX =
bound(deltaX, MINIMUM_DELTA, reserveXPerWad - MINIMUM_RESERVE_X);

console.log(reserveYPerWad);

int256 previousResult = tradingFunction(
NormalCurve(
reserveXPerWad,
@@ -418,9 +423,10 @@ contract TestPortfolioInvariant is Setup {
bound(deltaY, MINIMUM_DELTA, reserveYPerWad - MINIMUM_RESERVE_Y);

uint256 prevQuotient = reserveYPerWad.divWadUp(strikePriceWad);
uint256 quotient = (reserveYPerWad - deltaY).divWadUp(strikePriceWad);
uint256 quotient = (reserveYPerWad - deltaY).divWadDown(strikePriceWad);
console.log("prevQuotient: ", prevQuotient);
console.log("quotient: ", quotient);
vm.assume(quotient > 0 && quotient < 1 ether);

/// Make sure quotient is within bounds and it decreases by at least 2
vm.assume(prevQuotient >= MINIMUM_QUOTIENT_DELTA);
@@ -502,6 +508,7 @@ contract TestPortfolioInvariant is Setup {

// The Y reserve is bounded between the min delta and the strike price less min delta.
// min y <= y <= strike - min delta
// Y reserve must be large enough to be divided by the strike price and not be zero.
reserveYPerWad = bound(
reserveYPerWad,
MINIMUM_RESERVE_Y + MINIMUM_DELTA,
@@ -518,7 +525,7 @@ contract TestPortfolioInvariant is Setup {
timeRemainingSec = bound(timeRemainingSec, MIN_DURATION, MAX_DURATION);

// Need to make sure the computations in the function are valid.
uint256 quotient = reserveYPerWad.divWadUp(strikePriceWad);
uint256 quotient = reserveYPerWad.divWadDown(strikePriceWad);
uint256 difference = 1 ether - reserveXPerWad;
vm.assume(quotient > 0 && quotient < 1 ether);
vm.assume(difference > 0 && difference < 1 ether);
@@ -569,7 +576,7 @@ contract TestPortfolioInvariant is Setup {
: postReserve.divWadUp(strikePriceWad)
);

console.log("PPFs");
/* console.log("PPFs");
console.logInt(
Gaussian.ppf(
int256(
@@ -587,7 +594,7 @@ contract TestPortfolioInvariant is Setup {
: 1 ether - reserveXPerWad
)
)
);
); */
console.log(
"stdDevSqrtTau",
volatilityWad.mulWadDown(
17 changes: 4 additions & 13 deletions test/TestPortfolioSwap.t.sol
Original file line number Diff line number Diff line change
@@ -555,19 +555,10 @@ contract TestPortfolioSwap is Setup {
// This swap takes all of the quote token "y" out of the pool.
// Therefore, we will hit the "lower bound" case for the "y" reserves.
// This will make the invariant y term `Gaussian.ppf(1)`, which is -8710427241990476442.
// Then it will subtract the invariantTermX and add the stdDevSqrtTau term.
// The minimum value for the invariantTermX is the same, `Gaussian.ppf(1)`, which is -8710427241990476442.
// Therefore, the x reserve needs to be close to its upper bound, since the input to the ppf is 1 - x.
// If it is close or at the boundary, it will cancel out the invariant y term.
// If its not close, the stdDevSqrtTau term will need to be large enough to close the difference,
// which might be too large than that term could be.
vm.expectRevert(
abi.encodeWithSelector(
Portfolio_InvalidInvariant.selector,
int256(938),
int256(-8707810991428151127)
)
);
// This requires the asset token "x" reserves to be at the upper bound of 1 WAD.
// If both reserves are not at opposite bounds, it will revert.
// In this case, it will revert with the `NormalStrategyLib_UpperReserveXBoundNotReached` error.
vm.expectRevert(NormalStrategyLib_UpperReserveXBoundNotReached.selector);
subject_.swap(order);
}

400 changes: 400 additions & 0 deletions test/tradingFunction.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,400 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.4;

import "./Setup.sol";

/**
* @dev Please, solidity, add support for fixed sized arrays in storage!
*
* [NormalCurve({
* reserveXPerWad: 0,
* reserveYPerWad: 0,
* strikePriceWad: NormalConfiguration_DEFAULT_STRIKE_WAD,
* standardDeviationWad: NormalConfiguration_DEFAULT_VOLATILITY_BPS * 1e14, // 10%
* timeRemainingSeconds: NormalConfiguration_DEFAULT_DURATION_SEC,
* invariant: 0
* }),
* NormalCurve({
* reserveXPerWad: 0,
* reserveYPerWad: 0,
* strikePriceWad: NormalConfiguration_DEFAULT_STRIKE_WAD,
* standardDeviationWad: MIN_VOLATILITY * 1e14, // .01%
* timeRemainingSeconds: NormalConfiguration_DEFAULT_DURATION_SEC, // 1 days in seconds
* invariant: 0
* }),
* NormalCurve({
* reserveXPerWad: 0,
* reserveYPerWad: 0,
* strikePriceWad: NormalConfiguration_DEFAULT_STRIKE_WAD,
* standardDeviationWad: MAX_VOLATILITY * 1e14, // 250%,
* timeRemainingSeconds: NormalConfiguration_DEFAULT_DURATION_SEC,
* invariant: 0
* }),
* NormalCurve({
* reserveXPerWad: 0,
* reserveYPerWad: 0,
* strikePriceWad: NormalConfiguration_DEFAULT_STRIKE_WAD,
* standardDeviationWad: NormalConfiguration_DEFAULT_VOLATILITY_BPS * 1e14, // 10%,
* timeRemainingSeconds: MIN_DURATION,
* invariant: 0
* }),
* NormalCurve({
* reserveXPerWad: 0,
* reserveYPerWad: 0,
* strikePriceWad: NormalConfiguration_DEFAULT_STRIKE_WAD,
* standardDeviationWad: NormalConfiguration_DEFAULT_VOLATILITY_BPS * 1e14, // 10%
* timeRemainingSeconds: MAX_DURATION,
* invariant: 0
* })
* ];
* @author alexangelj
* @notice Uses the branching tree technique (BTT), defined in tradingFunction.tree.
*/
contract TestTradingFunction is Setup {
NormalCurve[] public profiles;

function setUp() public override {
super.setUp();

profiles.push(
NormalCurve({
reserveXPerWad: 0,
reserveYPerWad: 0,
strikePriceWad: NormalConfiguration_DEFAULT_STRIKE_WAD,
standardDeviationWad: NormalConfiguration_DEFAULT_VOLATILITY_BPS
* 1e14, // 10%
timeRemainingSeconds: NormalConfiguration_DEFAULT_DURATION_SEC,
invariant: 0
})
);

profiles.push(
NormalCurve({
reserveXPerWad: 0,
reserveYPerWad: 0,
strikePriceWad: NormalConfiguration_DEFAULT_STRIKE_WAD,
standardDeviationWad: MIN_VOLATILITY * 1e14, // .01%
timeRemainingSeconds: NormalConfiguration_DEFAULT_DURATION_SEC, // 1 days in seconds
invariant: 0
})
);

profiles.push(
NormalCurve({
reserveXPerWad: 0,
reserveYPerWad: 0,
strikePriceWad: NormalConfiguration_DEFAULT_STRIKE_WAD,
standardDeviationWad: MAX_VOLATILITY * 1e14, // 250%,
timeRemainingSeconds: NormalConfiguration_DEFAULT_DURATION_SEC,
invariant: 0
})
);

profiles.push(
NormalCurve({
reserveXPerWad: 0,
reserveYPerWad: 0,
strikePriceWad: NormalConfiguration_DEFAULT_STRIKE_WAD,
standardDeviationWad: NormalConfiguration_DEFAULT_VOLATILITY_BPS
* 1e14, // 10%,
timeRemainingSeconds: MIN_DURATION,
invariant: 0
})
);

profiles.push(
NormalCurve({
reserveXPerWad: 0,
reserveYPerWad: 0,
strikePriceWad: NormalConfiguration_DEFAULT_STRIKE_WAD,
standardDeviationWad: NormalConfiguration_DEFAULT_VOLATILITY_BPS
* 1e14, // 10%
timeRemainingSeconds: MAX_DURATION,
invariant: 0
})
);
}

uint256 public UPPER_BOUND = WAD;
uint256 public LOWER_BOUND = 0;

/// @dev Trading function is monotonic when reserves change by at least more than 1 wei.
/// This is because a 1 wei change is a rounding error for the trading function.
uint256 public MONOTONICITY_DELTA = 2;

NormalCurve defaultCurve = NormalCurve({
reserveXPerWad: 0,
reserveYPerWad: 0,
strikePriceWad: NormalConfiguration_DEFAULT_STRIKE_WAD, // $1
standardDeviationWad: NormalConfiguration_DEFAULT_VOLATILITY_BPS * 1e14, // 100%
timeRemainingSeconds: NormalConfiguration_DEFAULT_DURATION_SEC, // 1 days in seconds
invariant: 0
});

NormalCurve normalCurve = defaultCurve;

modifier whenReserveXPerWadIsAtTheUpperBound() {
normalCurve.reserveXPerWad = UPPER_BOUND;
_;
delete normalCurve;
}

modifier whenReserveYPerWadIsNotAtTheLowerBound() {
normalCurve.reserveYPerWad++;
_;
delete normalCurve;
}

function test_WhenReserveYPerWadIsNotAtTheLowerBound()
external
whenReserveXPerWadIsAtTheUpperBound
whenReserveYPerWadIsNotAtTheLowerBound
{
// It should revert with `NormalStrategyLib_LowerReserveYBoundNotReached`
vm.expectRevert(NormalStrategyLib_LowerReserveYBoundNotReached.selector);
normalCurve.tradingFunction();
}

modifier whenReserveYPerWadIsAtTheLowerBound() {
normalCurve.reserveYPerWad = LOWER_BOUND;
_;
}

function test_WhenReserveYPerWadIsAtTheLowerBound()
external
whenReserveXPerWadIsAtTheUpperBound
whenReserveYPerWadIsAtTheLowerBound
{
// It should return σ√τ
int256 expected = int256(normalCurve.computeStdDevSqrtTau());
int256 actual = normalCurve.tradingFunction();
assertEq(
actual, expected, "tradingFunction should return stdDevSqrtTau"
);
}

modifier whenReserveXPerWadIsNotAtTheUpperBound() {
normalCurve.reserveXPerWad++;
_;
}

function test_WhenReserveXPerWadIsNotAtTheUpperBound()
external
whenReserveYPerWadIsAtTheLowerBound
whenReserveXPerWadIsNotAtTheUpperBound
{
// It should revert with `NormalStrategyLib_UpperReserveXBoundNotReached`
vm.expectRevert(NormalStrategyLib_UpperReserveXBoundNotReached.selector);
normalCurve.tradingFunction();
}

function test_WhenReserveXPerWadIsAtTheUpperBound()
external
whenReserveYPerWadIsAtTheLowerBound
whenReserveXPerWadIsAtTheUpperBound
{
// It should return σ√τ
int256 expected = int256(normalCurve.computeStdDevSqrtTau());
int256 actual = normalCurve.tradingFunction();
assertEq(
actual, expected, "tradingFunction should return stdDevSqrtTau"
);
}

modifier whenReserveXPerWadIsAtTheLowerBound() {
normalCurve.reserveXPerWad = LOWER_BOUND;
_;
}

modifier whenReserveYPerWadIsNotAtTheUpperBound() {
if (normalCurve.reserveYPerWad >= UPPER_BOUND) {
normalCurve.reserveYPerWad--;
}

if (normalCurve.reserveYPerWad == LOWER_BOUND) {
normalCurve.reserveYPerWad++;
}
_;
}

function test_WhenReserveYPerWadIsNotAtTheUpperBound()
external
whenReserveXPerWadIsAtTheLowerBound
whenReserveYPerWadIsNotAtTheUpperBound
{
// It should revert with `NormalStrategyLib_UpperReserveYBoundNotReached`
vm.expectRevert(NormalStrategyLib_UpperReserveYBoundNotReached.selector);
normalCurve.tradingFunction();
}

modifier whenReserveYPerWadIsAtTheUpperBound() {
normalCurve.reserveYPerWad = UPPER_BOUND;
_;
}

function test_WhenReserveYPerWadIsAtTheUpperBound()
external
whenReserveXPerWadIsAtTheLowerBound
whenReserveYPerWadIsAtTheUpperBound
{
// It should return σ√τ if reserveYPerWad
int256 expected = int256(normalCurve.computeStdDevSqrtTau());
int256 actual = normalCurve.tradingFunction();
assertEq(
actual, expected, "tradingFunction should return stdDevSqrtTau"
);
}

modifier whenReserveXPerWadIsNotAtTheLowerBound() {
if (normalCurve.reserveXPerWad <= LOWER_BOUND) {
normalCurve.reserveXPerWad++;
}
_;
}

function test_WhenReserveXPerWadIsNotAtTheLowerBound()
external
whenReserveYPerWadIsAtTheUpperBound
whenReserveXPerWadIsNotAtTheLowerBound
{
// It should revert with `NormalStrategyLib_LowerReserveXBoundNotReached`
vm.expectRevert(NormalStrategyLib_LowerReserveXBoundNotReached.selector);
normalCurve.tradingFunction();
}

function test_WhenReserveXPerWadIsAtTheLowerBound()
external
whenReserveYPerWadIsAtTheUpperBound
whenReserveXPerWadIsAtTheLowerBound
{
// It should return σ√τ
int256 expected = int256(normalCurve.computeStdDevSqrtTau());
int256 actual = normalCurve.tradingFunction();
assertEq(
actual, expected, "tradingFunction should return stdDevSqrtTau"
);
}

modifier whenReserveXPerWadAndReserveYPerWadAreBothNotAtAnyBound() {
if (normalCurve.reserveXPerWad <= LOWER_BOUND) {
normalCurve.reserveXPerWad++;
}

if (normalCurve.reserveXPerWad >= UPPER_BOUND) {
normalCurve.reserveXPerWad--;
}

if (normalCurve.reserveYPerWad <= LOWER_BOUND) {
normalCurve.reserveYPerWad++;
}

if (normalCurve.reserveYPerWad >= UPPER_BOUND) {
normalCurve.reserveYPerWad--;
}
_;
}

function test_WhenReserveXPerWadAndReserveYPerWadAreBothNotAtAnyBound()
external
whenReserveXPerWadAndReserveYPerWadAreBothNotAtAnyBound
{
// It should not revert
// It should return `Φ⁻¹(y/K) - Φ⁻¹(1-x) + σ√τ`
uint256 stdDevSqrtTau = normalCurve.computeStdDevSqrtTau();
int256 invariant = normalCurve.tradingFunction();
assertTrue(
invariant != int256(stdDevSqrtTau),
"tradingFunction should not return only stdDevSqrtTau"
);
}

function test_fuzz_WhenReserveXPerWadAndReserveYPerWadAreBothNotAtAnyBound(
uint256 seed
) external whenReserveXPerWadAndReserveYPerWadAreBothNotAtAnyBound {
normalCurve.reserveXPerWad =
bound(seed, LOWER_BOUND + 1, UPPER_BOUND - 1);
normalCurve.reserveYPerWad =
bound(seed, LOWER_BOUND + 1, normalCurve.strikePriceWad - 1);

// It should not revert
// It should return `Φ⁻¹(y/K) - Φ⁻¹(1-x) + σ√τ`
uint256 stdDevSqrtTau = normalCurve.computeStdDevSqrtTau();
int256 invariant = normalCurve.tradingFunction();
assertTrue(
invariant != int256(stdDevSqrtTau),
"tradingFunction should not return only stdDevSqrtTau"
);
}

modifier whenUsingAllParameterProfiles(uint256 rand) {
uint256 index = rand % profiles.length;
normalCurve = profiles[index];
(normalCurve.reserveXPerWad, normalCurve.reserveYPerWad) = normalCurve
.approximateReservesGivenPrice(normalCurve.strikePriceWad);
_;
}

modifier whenIncreasingReserveXPerWad() {
int256 prev = normalCurve.tradingFunction();
normalCurve.reserveXPerWad += MONOTONICITY_DELTA;
_;
int256 post = normalCurve.tradingFunction();
assertTrue(post > prev, "Invariant should increase");
}

function test_fuzz_WhenIncreasingReserveXPerWad(uint256 rand)
external
whenUsingAllParameterProfiles(rand)
whenIncreasingReserveXPerWad
{
// It should increase the invariant
}

modifier whenIncreasingReserveYPerWad() {
int256 prev = normalCurve.tradingFunction();
normalCurve.reserveYPerWad += MONOTONICITY_DELTA;
_;
int256 post = normalCurve.tradingFunction();
assertTrue(post > prev, "Invariant should increase");
}

function test_fuzz_WhenIncreasingReserveYPerWad(uint256 rand)
external
whenUsingAllParameterProfiles(rand)
whenIncreasingReserveYPerWad
{
// It should increase the invariant
}

modifier whenDecreasingReserveXPerWad() {
int256 prev = normalCurve.tradingFunction();
normalCurve.reserveXPerWad -= MONOTONICITY_DELTA;
_;
int256 post = normalCurve.tradingFunction();
assertTrue(post < prev, "Invariant should decrease");
}

function test_fuzz_WhenDecreasingReserveXPerWad(uint256 rand)
external
whenUsingAllParameterProfiles(rand)
whenDecreasingReserveXPerWad
{
// It should decrease the invariant
}

modifier whenDecreasingReserveYPerWad() {
int256 prev = normalCurve.tradingFunction();
normalCurve.reserveYPerWad -= MONOTONICITY_DELTA;
_;
int256 post = normalCurve.tradingFunction();
assertTrue(post < prev, "Invariant should decrease");
}

function test_fuzz_WhenDecreasingReserveYPerWad(uint256 rand)
external
whenUsingAllParameterProfiles(rand)
whenDecreasingReserveYPerWad
{
// It should decrease the invariant
}
}
33 changes: 33 additions & 0 deletions test/tradingFunction.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
TestTradingFunction
├── When reserveXPerWad is at the upper bound
│ └── When reserveYPerWad is not at the lower bound
│ └── It should revert with `NormalStrategyLib_LowerReserveYBoundNotReached`
│ └── When reserveYPerWad is at the lower bound
│ └── It should return σ√τ
├── When reserveYPerWad is at the lower bound
│ └── When reserveXPerWad is not at the upper bound
│ └── It should revert with `NormalStrategyLib_UpperReserveXBoundNotReached`
│ └── When reserveXPerWad is at the upper bound
│ └── It should return σ√τ
├── When reserveXPerWad is at the lower bound
│ └── When reserveYPerWad is not at the upper bound
│ └── It should revert with `NormalStrategyLib_UpperReserveYBoundNotReached`
│ └── When reserveYPerWad is at the upper bound
│ └── It should return σ√τ if reserveYPerWad
├── When reserveYPerWad is at the upper bound
│ └── When reserveXPerWad is not at the lower bound
│ └── It should revert with `NormalStrategyLib_LowerReserveXBoundNotReached`
│ └── When reserveXPerWad is at the lower bound
│ └── It should return σ√τ
├── When reserveXPerWad and reserveYPerWad are both not at any bound
│ └── It should not revert
│ └── It should return `Φ⁻¹(y/K) - Φ⁻¹(1-x) + σ√τ`
├── When using all parameter profiles
│ └── When increasing reserveXPerWad
│ └── It should increase the invariant
│ └── When increasing reserveYPerWad
│ └── It should increase the invariant
│ └── When decreasing reserveXPerWad
│ └── It should decrease the invariant
│ └── When decreasing reserveYPerWad
│ └── It should decrease the invariant