Files

290 lines
12 KiB
Solidity

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {Vm} from "forge-std/Vm.sol";
import {SqrtPriceMath} from "../../src/libraries/SqrtPriceMath.sol";
import {SwapMath} from "../../src/libraries/SwapMath.sol";
import {Constants} from "../utils/Constants.sol";
contract SwapMathTest is Test {
function test_fuzz_getSqrtPriceTarget(bool zeroForOne, uint160 sqrtPriceNextX96, uint160 sqrtPriceLimitX96)
public
pure
{
assertEq(
SwapMath.getSqrtPriceTarget(zeroForOne, sqrtPriceNextX96, sqrtPriceLimitX96),
(zeroForOne ? sqrtPriceNextX96 < sqrtPriceLimitX96 : sqrtPriceNextX96 > sqrtPriceLimitX96)
? sqrtPriceLimitX96
: sqrtPriceNextX96
);
}
function test_computeSwapStep_exactAmountIn_oneForZero_thatGetsCappedAtPriceTargetIn() public pure {
uint160 priceTarget = Constants.SQRT_PRICE_101_100;
uint160 price = Constants.SQRT_PRICE_1_1;
uint128 liquidity = 2 ether;
int256 amount = (1 ether) * -1;
uint24 lpFee = 600;
bool zeroForOne = false;
(uint160 sqrtQ, uint256 amountIn, uint256 amountOut, uint256 feeAmount) =
SwapMath.computeSwapStep(price, priceTarget, liquidity, amount, lpFee);
assertEq(amountIn, 9975124224178055);
assertEq(amountOut, 9925619580021728);
assertEq(feeAmount, 5988667735148);
assert(amountIn + feeAmount < uint256(amount * -1));
uint256 priceAfterWholeInputAmount =
SqrtPriceMath.getNextSqrtPriceFromInput(price, liquidity, uint256(amount * -1), zeroForOne);
assertEq(sqrtQ, priceTarget);
assert(sqrtQ < priceAfterWholeInputAmount);
}
function test_computeSwapStep_exactAmountOut_oneForZero_thatGetsCappedAtPriceTargetIn() public pure {
uint160 priceTarget = Constants.SQRT_PRICE_101_100;
uint160 price = Constants.SQRT_PRICE_1_1;
uint128 liquidity = 2 ether;
int256 amount = 1 ether;
uint24 lpFee = 600;
bool zeroForOne = false;
(uint160 sqrtQ, uint256 amountIn, uint256 amountOut, uint256 feeAmount) =
SwapMath.computeSwapStep(price, priceTarget, liquidity, amount, lpFee);
assertEq(amountIn, 9975124224178055);
assertEq(amountOut, 9925619580021728);
assertEq(feeAmount, 5988667735148);
assert(amountOut < uint256(amount));
uint256 priceAfterWholeOutputAmount =
SqrtPriceMath.getNextSqrtPriceFromOutput(price, liquidity, uint256(amount), zeroForOne);
assertEq(sqrtQ, priceTarget);
assert(sqrtQ < priceAfterWholeOutputAmount);
}
function test_computeSwapStep_exactAmountIn_oneForZero_thatIsFullySpentIn() public pure {
uint160 priceTarget = Constants.SQRT_PRICE_1000_100;
uint160 price = Constants.SQRT_PRICE_1_1;
uint128 liquidity = 2 ether;
int256 amount = 1 ether * -1;
uint24 lpFee = 600;
bool zeroForOne = false;
(uint160 sqrtQ, uint256 amountIn, uint256 amountOut, uint256 feeAmount) =
SwapMath.computeSwapStep(price, priceTarget, liquidity, amount, lpFee);
assertEq(amountIn, 999400000000000000);
assertEq(amountOut, 666399946655997866);
assertEq(feeAmount, 600000000000000);
assertEq(amountIn + feeAmount, uint256(-amount));
uint256 priceAfterWholeInputAmountLessFee =
SqrtPriceMath.getNextSqrtPriceFromInput(price, liquidity, uint256(uint256(-amount) - feeAmount), zeroForOne);
assert(sqrtQ < priceTarget);
assertEq(sqrtQ, priceAfterWholeInputAmountLessFee);
}
function test_computeSwapStep_exactAmountOut_oneForZero_thatIsFullyReceivedIn() public pure {
uint160 priceTarget = Constants.SQRT_PRICE_10000_100;
uint160 price = Constants.SQRT_PRICE_1_1;
uint128 liquidity = 2 ether;
int256 amount = (1 ether);
uint24 lpFee = 600;
bool zeroForOne = false;
(uint160 sqrtQ, uint256 amountIn, uint256 amountOut, uint256 feeAmount) =
SwapMath.computeSwapStep(price, priceTarget, liquidity, amount, lpFee);
assertEq(amountIn, 2000000000000000000);
assertEq(feeAmount, 1200720432259356);
assertEq(amountOut, uint256(amount));
uint256 priceAfterWholeOutputAmount =
SqrtPriceMath.getNextSqrtPriceFromOutput(price, liquidity, uint256(amount), zeroForOne);
assert(sqrtQ < priceTarget);
assertEq(sqrtQ, priceAfterWholeOutputAmount);
}
function test_computeSwapStep_amountOut_isCappedAtTheDesiredAmountOut() public pure {
(uint160 sqrtQ, uint256 amountIn, uint256 amountOut, uint256 feeAmount) = SwapMath.computeSwapStep(
417332158212080721273783715441582, 1452870262520218020823638996, 159344665391607089467575320103, 1, 1
);
assertEq(amountIn, 1);
assertEq(feeAmount, 1);
assertEq(amountOut, 1); // would be 2 if not capped
assertEq(sqrtQ, 417332158212080721273783715441581);
}
function test_computeSwapStep_targetPriceOf1UsesPartialInputAmount() public pure {
(uint160 sqrtQ, uint256 amountIn, uint256 amountOut, uint256 feeAmount) =
SwapMath.computeSwapStep(2, 1, 1, -3915081100057732413702495386755767, 1);
assertEq(amountIn, Constants.SQRT_PRICE_1_4);
assertEq(feeAmount, 39614120871253040049813);
assert(amountIn + feeAmount <= 3915081100057732413702495386755767);
assertEq(amountOut, 0);
assertEq(sqrtQ, 1);
}
function test_computeSwapStep_notEntireInputAmountTakenAsFee() public pure {
(uint160 sqrtQ, uint256 amountIn, uint256 amountOut, uint256 feeAmount) =
SwapMath.computeSwapStep(2413, 79887613182836312, 1985041575832132834610021537970, -10, 1872);
assertEq(amountIn, 9);
assertEq(feeAmount, 1);
assertEq(amountOut, 0);
assertEq(sqrtQ, 2413);
}
function test_computeSwapStep_zeroForOne_handlesIntermediateInsufficientLiquidityInExactOutputCase() public pure {
uint160 sqrtP = 20282409603651670423947251286016;
uint160 sqrtPTarget = (sqrtP * 11) / 10;
uint128 liquidity = 1024;
// virtual reserves of one are only 4
// https://www.wolframalpha.com/input/?i=1024+%2F+%2820282409603651670423947251286016+%2F+2**96%29
int256 amountRemaining = 4;
uint24 feePips = 3000;
(uint160 sqrtQ, uint256 amountIn, uint256 amountOut, uint256 feeAmount) =
SwapMath.computeSwapStep(sqrtP, sqrtPTarget, liquidity, amountRemaining, feePips);
assertEq(amountOut, 0);
assertEq(sqrtQ, sqrtPTarget);
assertEq(amountIn, 26215);
assertEq(feeAmount, 79);
}
function test_computeSwapStep_oneForZero_handlesIntermediateInsufficientLiquidityInExactOutputCase() public pure {
uint160 sqrtP = 20282409603651670423947251286016;
uint160 sqrtPTarget = (sqrtP * 9) / 10;
uint128 liquidity = 1024;
// virtual reserves of zero are only 262144
// https://www.wolframalpha.com/input/?i=1024+*+%2820282409603651670423947251286016+%2F+2**96%29
int256 amountRemaining = 263000;
uint24 feePips = 3000;
(uint160 sqrtQ, uint256 amountIn, uint256 amountOut, uint256 feeAmount) =
SwapMath.computeSwapStep(sqrtP, sqrtPTarget, liquidity, amountRemaining, feePips);
assertEq(amountOut, 26214);
assertEq(sqrtQ, sqrtPTarget);
assertEq(amountIn, 1);
assertEq(feeAmount, 1);
}
function test_fuzz_computeSwapStep(
uint160 sqrtPriceRaw,
uint160 sqrtPriceTargetRaw,
uint128 liquidity,
int256 amountRemaining,
uint24 feePips
) public pure {
vm.assume(sqrtPriceRaw > 0);
vm.assume(sqrtPriceTargetRaw > 0);
vm.assume(feePips >= 0);
if (amountRemaining >= 0) {
vm.assume(feePips < 1e6);
} else {
vm.assume(feePips <= 1e6);
}
(uint160 sqrtQ, uint256 amountIn, uint256 amountOut, uint256 feeAmount) =
SwapMath.computeSwapStep(sqrtPriceRaw, sqrtPriceTargetRaw, liquidity, amountRemaining, feePips);
assertLe(amountIn, type(uint256).max - feeAmount);
unchecked {
if (amountRemaining >= 0) {
assertLe(amountOut, uint256(amountRemaining));
} else {
assertLe(amountIn + feeAmount, uint256(-amountRemaining));
}
}
if (sqrtPriceRaw == sqrtPriceTargetRaw) {
assertEq(amountIn, 0);
assertEq(amountOut, 0);
assertEq(feeAmount, 0);
assertEq(sqrtQ, sqrtPriceTargetRaw);
}
// didn't reach price target, entire amount must be consumed
if (sqrtQ != sqrtPriceTargetRaw) {
uint256 absAmtRemaining;
if (amountRemaining == type(int256).min) {
absAmtRemaining = uint256(type(int256).max) + 1;
} else if (amountRemaining < 0) {
absAmtRemaining = uint256(-amountRemaining);
} else {
absAmtRemaining = uint256(amountRemaining);
}
if (amountRemaining > 0) assertEq(amountOut, absAmtRemaining);
else assertEq(amountIn + feeAmount, absAmtRemaining);
}
// next price is between price and price target
if (sqrtPriceTargetRaw <= sqrtPriceRaw) {
assertLe(sqrtQ, sqrtPriceRaw);
assertGe(sqrtQ, sqrtPriceTargetRaw);
} else {
assertGe(sqrtQ, sqrtPriceRaw);
assertLe(sqrtQ, sqrtPriceTargetRaw);
}
}
function test_computeSwapStep_swapOneForZero_exactInCapped() public {
vm.startSnapshotGas("SwapMath_oneForZero_exactInCapped");
SwapMath.computeSwapStep(Constants.SQRT_PRICE_1_1, Constants.SQRT_PRICE_101_100, 2 ether, (1 ether) * -1, 600);
vm.stopSnapshotGas();
}
function test_computeSwapStep_swapZeroForOne_exactInCapped() public {
vm.startSnapshotGas("SwapMath_zeroForOne_exactInCapped");
SwapMath.computeSwapStep(Constants.SQRT_PRICE_1_1, Constants.SQRT_PRICE_99_100, 2 ether, (1 ether) * -1, 600);
vm.stopSnapshotGas();
}
function test_computeSwapStep_swapOneForZero_exactOutCapped() public {
vm.startSnapshotGas("SwapMath_oneForZero_exactOutCapped");
SwapMath.computeSwapStep(Constants.SQRT_PRICE_1_1, Constants.SQRT_PRICE_101_100, 2 ether, 1 ether, 600);
vm.stopSnapshotGas();
}
function test_computeSwapStep_swapZeroForOne_exactOutCapped() public {
vm.startSnapshotGas("SwapMath_zeroForOne_exactOutCapped");
SwapMath.computeSwapStep(Constants.SQRT_PRICE_1_1, Constants.SQRT_PRICE_99_100, 2 ether, 1 ether, 600);
vm.stopSnapshotGas();
}
function test_computeSwapStep_swapOneForZero_exactInPartial() public {
vm.startSnapshotGas("SwapMath_oneForZero_exactInPartial");
SwapMath.computeSwapStep(Constants.SQRT_PRICE_1_1, Constants.SQRT_PRICE_1010_100, 2 ether, 1_000 * -1, 600);
vm.stopSnapshotGas();
}
function test_computeSwapStep_swapZeroForOne_exactInPartial() public {
vm.startSnapshotGas("SwapMath_zeroForOne_exactInPartial");
SwapMath.computeSwapStep(Constants.SQRT_PRICE_1_1, Constants.SQRT_PRICE_99_1000, 2 ether, 1_000 * -1, 600);
vm.stopSnapshotGas();
}
function test_computeSwapStep_swapOneForZero_exactOutPartial() public {
vm.startSnapshotGas("SwapMath_oneForZero_exactOutPartial");
SwapMath.computeSwapStep(Constants.SQRT_PRICE_1_1, Constants.SQRT_PRICE_1010_100, 2 ether, 1_000, 600);
vm.stopSnapshotGas();
}
function test_computeSwapStep_swapZeroForOne_exactOutPartial() public {
vm.startSnapshotGas("SwapMath_zeroForOne_exactOutPartial");
SwapMath.computeSwapStep(Constants.SQRT_PRICE_1_1, Constants.SQRT_PRICE_99_1000, 2 ether, 1_000, 600);
vm.stopSnapshotGas();
}
}