mirror of
https://github.com/lux-amm/v4-periphery.git
synced 2026-10-30 20:54:39 +00:00
1143 lines
48 KiB
Solidity
1143 lines
48 KiB
Solidity
// SPDX-License-Identifier: UNLICENSED
|
|
pragma solidity ^0.8.19;
|
|
|
|
import {Currency, CurrencyLibrary} from "@luxamm/v4-core/src/types/Currency.sol";
|
|
import {IV4Router} from "../../src/interfaces/IV4Router.sol";
|
|
import {RoutingTestHelpers} from "../shared/RoutingTestHelpers.sol";
|
|
import {Planner} from "../shared/Planner.sol";
|
|
import {Actions} from "../../src/libraries/Actions.sol";
|
|
import {ActionConstants} from "../../src/libraries/ActionConstants.sol";
|
|
import {TickMath} from "@luxamm/v4-core/src/libraries/TickMath.sol";
|
|
import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol";
|
|
import {IHooks} from "@luxamm/v4-core/src/interfaces/IHooks.sol";
|
|
import {PoolKey} from "@luxamm/v4-core/src/types/PoolKey.sol";
|
|
import {ModifyLiquidityParams} from "@luxamm/v4-core/src/types/PoolOperation.sol";
|
|
|
|
contract V4RouterTest is RoutingTestHelpers {
|
|
uint256 constant PRECISION = 1e36;
|
|
address alice = makeAddr("ALICE");
|
|
|
|
function setUp() public {
|
|
setupRouterCurrenciesAndPoolsWithLiquidity();
|
|
plan = Planner.init();
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////
|
|
ERC20 -> ERC20 EXACT INPUT
|
|
//////////////////////////////////////////////////////////////*/
|
|
|
|
function test_swapExactInputSingle_revertsForAmountOut() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
// min amount out of 1 higher than the actual amount out
|
|
IV4Router.ExactInputSingleParams memory params = IV4Router.ExactInputSingleParams(
|
|
key0, true, uint128(amountIn), uint128(expectedAmountOut + 1), 0, bytes("")
|
|
);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params));
|
|
bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER);
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(IV4Router.V4TooLittleReceived.selector, expectedAmountOut + 1, expectedAmountOut)
|
|
);
|
|
router.executeActions(data);
|
|
}
|
|
|
|
function test_swapExactInputSingle_revertsForPerHopSlippage() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
uint256 expectedPrice = expectedAmountOut * PRECISION / amountIn;
|
|
|
|
IV4Router.ExactInputSingleParams memory params =
|
|
IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, expectedPrice + 1, bytes(""));
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params));
|
|
bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER);
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(IV4Router.V4TooLittleReceivedPerHopSingle.selector, expectedPrice + 1, expectedPrice)
|
|
);
|
|
router.executeActions(data);
|
|
}
|
|
|
|
function test_swapExactInputSingle_succeedsForPerHopSlippage() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
uint256 expectedPrice = expectedAmountOut * PRECISION / amountIn;
|
|
|
|
IV4Router.ExactInputSingleParams memory params = IV4Router.ExactInputSingleParams(
|
|
key0, true, uint128(amountIn), uint128(expectedAmountOut), expectedPrice, bytes("")
|
|
);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params));
|
|
bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER);
|
|
|
|
router.executeActions(data);
|
|
}
|
|
|
|
function test_swapExactInputSingle_zeroForOne_takeToMsgSender() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
IV4Router.ExactInputSingleParams memory params =
|
|
IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes(""));
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params));
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(key0.currency0, key0.currency1, amountIn);
|
|
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, amountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut);
|
|
}
|
|
|
|
function test_swapExactInputSingle_zeroForOne_takeToRecipient() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
IV4Router.ExactInputSingleParams memory params =
|
|
IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes(""));
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params));
|
|
|
|
uint256 aliceOutputBalanceBefore = key0.currency1.balanceOf(alice);
|
|
|
|
// swap with alice as the take recipient
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(key0.currency0, key0.currency1, amountIn, alice);
|
|
|
|
uint256 aliceOutputBalanceAfter = key0.currency1.balanceOf(alice);
|
|
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, amountIn);
|
|
// this contract's output balance has not changed because funds went to alice
|
|
assertEq(outputBalanceAfter, outputBalanceBefore);
|
|
assertEq(aliceOutputBalanceAfter - aliceOutputBalanceBefore, expectedAmountOut);
|
|
}
|
|
|
|
// This is not a real use-case in isolation, but will be used in the UniversalRouter if a v4
|
|
// swap is before another swap on v2/v3
|
|
function test_swapExactInputSingle_zeroForOne_takeAllToRouter() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
IV4Router.ExactInputSingleParams memory params =
|
|
IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes(""));
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params));
|
|
|
|
// the router holds no funds before
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
|
|
// swap with the router as the take recipient
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(key0.currency0, key0.currency1, amountIn, ActionConstants.ADDRESS_THIS);
|
|
|
|
// the output tokens have been left in the router
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), expectedAmountOut);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, amountIn);
|
|
// this contract's output balance has not changed because funds went to the router
|
|
assertEq(outputBalanceAfter, outputBalanceBefore);
|
|
}
|
|
|
|
// This is not a real use-case in isolation, but will be used in the UniversalRouter if a v4
|
|
// swap is before another swap on v2/v3
|
|
function test_swapExactInputSingle_zeroForOne_takeToRouter() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
IV4Router.ExactInputSingleParams memory params =
|
|
IV4Router.ExactInputSingleParams(key0, true, uint128(amountIn), 0, 0, bytes(""));
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params));
|
|
plan = plan.add(Actions.SETTLE_ALL, abi.encode(key0.currency0, expectedAmountOut * 12 / 10));
|
|
// take the entire open delta to the router's address
|
|
plan = plan.add(
|
|
Actions.TAKE, abi.encode(key0.currency1, ActionConstants.ADDRESS_THIS, ActionConstants.OPEN_DELTA)
|
|
);
|
|
bytes memory data = plan.encode();
|
|
|
|
// the router holds no funds before
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
uint256 inputBalanceBefore = key0.currency0.balanceOfSelf();
|
|
uint256 outputBalanceBefore = key0.currency1.balanceOfSelf();
|
|
|
|
router.executeActions(data);
|
|
|
|
// the output tokens have been left in the router
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), expectedAmountOut);
|
|
uint256 inputBalanceAfter = key0.currency0.balanceOfSelf();
|
|
uint256 outputBalanceAfter = key0.currency1.balanceOfSelf();
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, amountIn);
|
|
// this contract's output balance has not changed because funds went to the router
|
|
assertEq(outputBalanceAfter, outputBalanceBefore);
|
|
}
|
|
|
|
function test_swapExactInputSingle_oneForZero() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
IV4Router.ExactInputSingleParams memory params =
|
|
IV4Router.ExactInputSingleParams(key0, false, uint128(amountIn), 0, 0, bytes(""));
|
|
plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(key0.currency1, key0.currency0, amountIn);
|
|
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, amountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut);
|
|
}
|
|
|
|
function test_swapExactInput_revertsForAmountOut() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
tokenPath.push(currency0);
|
|
tokenPath.push(currency1);
|
|
IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn);
|
|
params.amountOutMinimum = uint128(expectedAmountOut + 1);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params));
|
|
bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER);
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(IV4Router.V4TooLittleReceived.selector, 992054607780215625 + 1, 992054607780215625)
|
|
);
|
|
router.executeActions(data);
|
|
}
|
|
|
|
function test_swapExactInput_revertsForAmountOutPerHop() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
uint256 expectedPrice = expectedAmountOut * PRECISION / amountIn;
|
|
uint256[] memory minPricesX36 = new uint256[](1);
|
|
uint256 minPrice = expectedPrice + 1;
|
|
minPricesX36[0] = minPrice;
|
|
|
|
tokenPath.push(currency0);
|
|
tokenPath.push(currency1);
|
|
IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, minPricesX36, amountIn);
|
|
params.amountOutMinimum = uint128(0);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params));
|
|
bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER);
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(IV4Router.V4TooLittleReceivedPerHop.selector, 0, minPrice, expectedPrice)
|
|
);
|
|
router.executeActions(data);
|
|
}
|
|
|
|
function test_swapExactInput_succeedsForAmountOutPerHop() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
uint256 expectedPrice = expectedAmountOut * PRECISION / amountIn;
|
|
uint256[] memory minPricesX36 = new uint256[](1);
|
|
uint256 minPriceX36 = expectedPrice;
|
|
minPricesX36[0] = minPriceX36;
|
|
|
|
tokenPath.push(currency0);
|
|
tokenPath.push(currency1);
|
|
IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, minPricesX36, amountIn);
|
|
params.amountOutMinimum = uint128(expectedAmountOut);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params));
|
|
bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER);
|
|
|
|
router.executeActions(data);
|
|
}
|
|
|
|
// Regression test: with PRECISION = 1e18, the price truncates to 0 for extreme ratios, disabling the check.
|
|
function test_swapExactInput_extremeRatioPerHopSlippage() public {
|
|
// Create a pool at extreme price: ~1e22 token1 per token0 (simulates WBTC/PEPE-like ratio)
|
|
int24 extremeTick = 506520;
|
|
uint160 extremeSqrtPrice = TickMath.getSqrtPriceAtTick(extremeTick);
|
|
|
|
MockERC20 tokenA = new MockERC20("A", "A", 18);
|
|
MockERC20 tokenB = new MockERC20("B", "B", 18);
|
|
tokenA.mint(address(this), 2 ** 128);
|
|
tokenB.mint(address(this), 2 ** 128);
|
|
tokenA.approve(address(router), type(uint256).max);
|
|
tokenB.approve(address(router), type(uint256).max);
|
|
tokenA.approve(address(positionManager), type(uint256).max);
|
|
tokenB.approve(address(positionManager), type(uint256).max);
|
|
|
|
Currency currA = Currency.wrap(address(tokenA));
|
|
Currency currB = Currency.wrap(address(tokenB));
|
|
if (Currency.unwrap(currA) > Currency.unwrap(currB)) {
|
|
(currA, currB) = (currB, currA);
|
|
}
|
|
|
|
PoolKey memory extremeKey = PoolKey(currA, currB, 3000, 60, IHooks(address(0)));
|
|
manager.initialize(extremeKey, extremeSqrtPrice);
|
|
positionManager.modifyLiquidity(
|
|
extremeKey, ModifyLiquidityParams(extremeTick - 600, extremeTick + 600, 100 ether, 0), "0x"
|
|
);
|
|
|
|
// Swap currB -> currA (token1 -> token0): output is tiny relative to input at this extreme tick.
|
|
uint256 amountIn = 1e26;
|
|
Currency[] memory path = new Currency[](2);
|
|
path[0] = currB;
|
|
path[1] = currA;
|
|
|
|
// Snapshot state so the first swap doesn't drain liquidity for the second
|
|
uint256 snap = vm.snapshotState();
|
|
|
|
// First swap without per-hop slippage to discover actual amountOut
|
|
IV4Router.ExactInputParams memory params = _getExactInputParams(path, amountIn);
|
|
plan = Planner.init();
|
|
plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params));
|
|
bytes memory data = plan.finalizeSwap(currB, currA, ActionConstants.MSG_SENDER);
|
|
|
|
uint256 outputBefore = currA.balanceOfSelf();
|
|
router.executeActions(data);
|
|
uint256 actualAmountOut = currA.balanceOfSelf() - outputBefore;
|
|
|
|
// With 1e18 precision, the price truncates to 0 for this extreme ratio.
|
|
assertEq(actualAmountOut * 1e18 / amountIn, 0, "1e18 precision should truncate to zero");
|
|
// 1e36 precision preserves enough resolution.
|
|
uint256 expectedPrice = actualAmountOut * PRECISION / amountIn;
|
|
assertGt(expectedPrice, 0, "price should be non-zero");
|
|
|
|
// Restore state for the slippage test
|
|
vm.revertToState(snap);
|
|
|
|
// Verify the per-hop slippage check fires for extreme ratio pairs
|
|
uint256[] memory minPricesX36 = new uint256[](1);
|
|
minPricesX36[0] = expectedPrice + 1;
|
|
|
|
IV4Router.ExactInputParams memory params2 = _getExactInputParams(path, minPricesX36, amountIn);
|
|
params2.amountOutMinimum = 0;
|
|
plan = Planner.init();
|
|
plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params2));
|
|
data = plan.finalizeSwap(currB, currA, ActionConstants.MSG_SENDER);
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(IV4Router.V4TooLittleReceivedPerHop.selector, 0, expectedPrice + 1, expectedPrice)
|
|
);
|
|
router.executeActions(data);
|
|
}
|
|
|
|
function test_swapExactIn_1Hop_zeroForOne() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
tokenPath.push(currency0);
|
|
tokenPath.push(currency1);
|
|
IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn);
|
|
plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(currency0, currency1, amountIn);
|
|
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, amountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut);
|
|
}
|
|
|
|
function test_swapExactIn_1Hop_oneForZero() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
tokenPath.push(currency1);
|
|
tokenPath.push(currency0);
|
|
IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(currency1, currency0, amountIn);
|
|
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, amountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut);
|
|
}
|
|
|
|
function test_swapExactIn_2Hops() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 984211133872795298;
|
|
|
|
tokenPath.push(currency0);
|
|
tokenPath.push(currency1);
|
|
tokenPath.push(currency2);
|
|
IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params));
|
|
|
|
uint256 intermediateBalanceBefore = currency1.balanceOfSelf();
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(currency0, currency2, amountIn);
|
|
|
|
// check intermediate token balances
|
|
assertEq(intermediateBalanceBefore, currency1.balanceOfSelf());
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
assertEq(currency2.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, amountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut);
|
|
}
|
|
|
|
function test_swapExactIn_3Hops() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 976467664490096191;
|
|
|
|
tokenPath.push(currency0);
|
|
tokenPath.push(currency1);
|
|
tokenPath.push(currency2);
|
|
tokenPath.push(currency3);
|
|
IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(currency0, currency3, amountIn);
|
|
|
|
// check intermediate tokens werent left in the router
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
assertEq(currency2.balanceOf(address(router)), 0);
|
|
assertEq(currency3.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, amountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut);
|
|
}
|
|
|
|
function test_swap_settleRouterBalance_swapOpenDelta() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
key0.currency0.transfer(address(router), amountIn);
|
|
|
|
// amount in of 0 to show it should use the open delta
|
|
IV4Router.ExactInputSingleParams memory params =
|
|
IV4Router.ExactInputSingleParams(key0, true, ActionConstants.OPEN_DELTA, 0, 0, bytes(""));
|
|
|
|
plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, ActionConstants.CONTRACT_BALANCE, false));
|
|
plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params));
|
|
plan = plan.add(Actions.TAKE_ALL, abi.encode(key0.currency1, MIN_TAKE_AMOUNT));
|
|
|
|
bytes memory data = plan.encode();
|
|
|
|
uint256 callerInputBefore = key0.currency0.balanceOfSelf();
|
|
uint256 routerInputBefore = key0.currency0.balanceOf(address(router));
|
|
uint256 callerOutputBefore = key0.currency1.balanceOfSelf();
|
|
router.executeActions(data);
|
|
|
|
uint256 callerInputAfter = key0.currency0.balanceOfSelf();
|
|
uint256 routerInputAfter = key0.currency0.balanceOf(address(router));
|
|
uint256 callerOutputAfter = key0.currency1.balanceOfSelf();
|
|
|
|
// caller didnt pay, router paid, caller received the output
|
|
assertEq(callerInputBefore, callerInputAfter);
|
|
assertEq(routerInputBefore - amountIn, routerInputAfter);
|
|
assertEq(callerOutputBefore + expectedAmountOut, callerOutputAfter);
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////
|
|
ETH -> ERC20 and ERC20 -> ETH EXACT INPUT
|
|
//////////////////////////////////////////////////////////////*/
|
|
|
|
function test_nativeIn_swapExactInputSingle() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
IV4Router.ExactInputSingleParams memory params =
|
|
IV4Router.ExactInputSingleParams(nativeKey, true, uint128(amountIn), 0, 0, bytes(""));
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(nativeKey.currency0, nativeKey.currency1, amountIn);
|
|
|
|
assertEq(nativeKey.currency0.balanceOf(address(router)), 0);
|
|
assertEq(nativeKey.currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, amountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut);
|
|
}
|
|
|
|
function test_nativeOut_swapExactInputSingle() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
// native output means we need !zeroForOne
|
|
IV4Router.ExactInputSingleParams memory params =
|
|
IV4Router.ExactInputSingleParams(nativeKey, false, uint128(amountIn), 0, 0, bytes(""));
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(nativeKey.currency1, nativeKey.currency0, amountIn);
|
|
|
|
assertEq(nativeKey.currency0.balanceOf(address(router)), 0);
|
|
assertEq(nativeKey.currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, amountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut);
|
|
}
|
|
|
|
function test_nativeIn_swapExactIn_1Hop() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
tokenPath.push(CurrencyLibrary.ADDRESS_ZERO);
|
|
tokenPath.push(nativeKey.currency1);
|
|
IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn);
|
|
plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(CurrencyLibrary.ADDRESS_ZERO, nativeKey.currency1, amountIn);
|
|
|
|
assertEq(nativeKey.currency0.balanceOf(address(router)), 0);
|
|
assertEq(nativeKey.currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, amountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut);
|
|
}
|
|
|
|
function test_nativeOut_swapExactIn_1Hop() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
tokenPath.push(nativeKey.currency1);
|
|
tokenPath.push(CurrencyLibrary.ADDRESS_ZERO);
|
|
IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(nativeKey.currency1, CurrencyLibrary.ADDRESS_ZERO, amountIn);
|
|
|
|
assertEq(nativeKey.currency0.balanceOf(address(router)), 0);
|
|
assertEq(nativeKey.currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, amountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut);
|
|
}
|
|
|
|
function test_nativeIn_swapExactIn_2Hops() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 984211133872795298;
|
|
|
|
// the initialized nativeKey is (native, currency0)
|
|
tokenPath.push(CurrencyLibrary.ADDRESS_ZERO);
|
|
tokenPath.push(currency0);
|
|
tokenPath.push(currency1);
|
|
IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params));
|
|
|
|
uint256 intermediateBalanceBefore = currency0.balanceOfSelf();
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(CurrencyLibrary.ADDRESS_ZERO, currency1, amountIn);
|
|
|
|
// check intermediate token balances
|
|
assertEq(intermediateBalanceBefore, currency0.balanceOfSelf());
|
|
|
|
assertEq(nativeKey.currency0.balanceOf(address(router)), 0);
|
|
assertEq(nativeKey.currency1.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, amountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut);
|
|
}
|
|
|
|
function test_nativeOut_swapExactIn_2Hops() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 984211133872795298;
|
|
|
|
// the initialized nativeKey is (native, currency0)
|
|
tokenPath.push(currency1);
|
|
tokenPath.push(currency0);
|
|
tokenPath.push(CurrencyLibrary.ADDRESS_ZERO);
|
|
IV4Router.ExactInputParams memory params = _getExactInputParams(tokenPath, amountIn);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_IN, abi.encode(params));
|
|
|
|
uint256 intermediateBalanceBefore = currency0.balanceOfSelf();
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(currency1, CurrencyLibrary.ADDRESS_ZERO, amountIn);
|
|
|
|
// check intermediate token balances
|
|
assertEq(intermediateBalanceBefore, currency0.balanceOfSelf());
|
|
assertEq(nativeKey.currency0.balanceOf(address(router)), 0);
|
|
assertEq(nativeKey.currency1.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, amountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, expectedAmountOut);
|
|
}
|
|
|
|
function test_swap_nativeIn_settleRouterBalance_swapOpenDelta() public {
|
|
uint256 amountIn = 1 ether;
|
|
uint256 expectedAmountOut = 992054607780215625;
|
|
|
|
nativeKey.currency0.transfer(address(router), amountIn);
|
|
|
|
// amount in of 0 to show it should use the open delta
|
|
IV4Router.ExactInputSingleParams memory params =
|
|
IV4Router.ExactInputSingleParams(nativeKey, true, ActionConstants.OPEN_DELTA, 0, 0, bytes(""));
|
|
|
|
plan = plan.add(Actions.SETTLE, abi.encode(nativeKey.currency0, ActionConstants.CONTRACT_BALANCE, false));
|
|
plan = plan.add(Actions.SWAP_EXACT_IN_SINGLE, abi.encode(params));
|
|
plan = plan.add(Actions.TAKE_ALL, abi.encode(nativeKey.currency1, MIN_TAKE_AMOUNT));
|
|
|
|
bytes memory data = plan.encode();
|
|
|
|
uint256 callerInputBefore = nativeKey.currency0.balanceOfSelf();
|
|
uint256 routerInputBefore = nativeKey.currency0.balanceOf(address(router));
|
|
uint256 callerOutputBefore = nativeKey.currency1.balanceOfSelf();
|
|
router.executeActions(data);
|
|
|
|
uint256 callerInputAfter = nativeKey.currency0.balanceOfSelf();
|
|
uint256 routerInputAfter = nativeKey.currency0.balanceOf(address(router));
|
|
uint256 callerOutputAfter = nativeKey.currency1.balanceOfSelf();
|
|
|
|
// caller didnt pay, router paid, caller received the output
|
|
assertEq(callerInputBefore, callerInputAfter);
|
|
assertEq(routerInputBefore - amountIn, routerInputAfter);
|
|
assertEq(callerOutputBefore + expectedAmountOut, callerOutputAfter);
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////Ã¥
|
|
ERC20 -> ERC20 EXACT OUTPUT
|
|
//////////////////////////////////////////////////////////////*/
|
|
|
|
function test_swapExactOutputSingle_revertsForAmountIn() public {
|
|
uint256 amountOut = 1 ether;
|
|
uint256 expectedAmountIn = 1008049273448486163;
|
|
|
|
IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams(
|
|
key0, true, uint128(amountOut), uint128(expectedAmountIn - 1), 0, bytes("")
|
|
);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params));
|
|
bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER);
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(IV4Router.V4TooMuchRequested.selector, expectedAmountIn - 1, expectedAmountIn)
|
|
);
|
|
router.executeActions(data);
|
|
}
|
|
|
|
function test_swapExactOutputSingle_revertsForPerHopSlippage() public {
|
|
uint256 amountOut = 1 ether;
|
|
uint256 expectedAmountIn = 1008049273448486163;
|
|
|
|
uint256 expectedPrice = amountOut * PRECISION / expectedAmountIn;
|
|
|
|
IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams(
|
|
key0, true, uint128(amountOut), type(uint128).max, expectedPrice + 1, bytes("")
|
|
);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params));
|
|
bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER);
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(IV4Router.V4TooMuchRequestedPerHopSingle.selector, expectedPrice + 1, expectedPrice)
|
|
);
|
|
router.executeActions(data);
|
|
}
|
|
|
|
function test_swapExactOutputSingle_succeedsForPerHopSlippage() public {
|
|
uint256 amountOut = 1 ether;
|
|
uint256 expectedAmountIn = 1008049273448486163;
|
|
|
|
uint256 expectedPrice = amountOut * PRECISION / expectedAmountIn;
|
|
|
|
IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams(
|
|
key0, true, uint128(amountOut), type(uint128).max, expectedPrice, bytes("")
|
|
);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params));
|
|
bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER);
|
|
|
|
router.executeActions(data);
|
|
}
|
|
|
|
function test_swapExactOutputSingle_zeroForOne() public {
|
|
uint256 amountOut = 1 ether;
|
|
uint256 expectedAmountIn = 1008049273448486163;
|
|
|
|
IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams(
|
|
key0, true, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("")
|
|
);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(key0.currency0, key0.currency1, expectedAmountIn);
|
|
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, amountOut);
|
|
}
|
|
|
|
function test_swapExactOutputSingle_oneForZero() public {
|
|
uint256 amountOut = 1 ether;
|
|
uint256 expectedAmountIn = 1008049273448486163;
|
|
|
|
IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams(
|
|
key0, false, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("")
|
|
);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(key0.currency1, key0.currency0, expectedAmountIn);
|
|
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, amountOut);
|
|
}
|
|
|
|
function test_swapExactOutputSingle_swapOpenDelta() public {
|
|
uint256 expectedAmountIn = 1008049273448486163;
|
|
|
|
IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams(
|
|
key0, true, ActionConstants.OPEN_DELTA, uint128(expectedAmountIn + 1), 0, bytes("")
|
|
);
|
|
|
|
plan = plan.add(Actions.TAKE, abi.encode(key0.currency1, ActionConstants.ADDRESS_THIS, 1 ether));
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params));
|
|
plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, ActionConstants.OPEN_DELTA, true));
|
|
|
|
bytes memory data = plan.encode();
|
|
|
|
uint256 callerInputBefore = key0.currency0.balanceOfSelf();
|
|
uint256 routerInputBefore = key0.currency1.balanceOfSelf();
|
|
uint256 callerOutputBefore = key0.currency1.balanceOfSelf();
|
|
|
|
router.executeActions(data);
|
|
|
|
uint256 callerInputAfter = key0.currency0.balanceOfSelf();
|
|
uint256 routerInputAfter = key0.currency1.balanceOfSelf();
|
|
uint256 callerOutputAfter = key0.currency1.balanceOfSelf();
|
|
|
|
// caller paid
|
|
assertEq(callerInputBefore - expectedAmountIn, callerInputAfter);
|
|
assertEq(routerInputBefore, routerInputAfter);
|
|
assertEq(callerOutputBefore, callerOutputAfter);
|
|
}
|
|
|
|
function test_swapExactOut_revertsForAmountIn() public {
|
|
uint256 amountOut = 1 ether;
|
|
uint256 expectedAmountIn = 1008049273448486163;
|
|
|
|
tokenPath.push(currency0);
|
|
tokenPath.push(currency1);
|
|
IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut);
|
|
params.amountInMaximum = uint128(expectedAmountIn - 1);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params));
|
|
bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER);
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(IV4Router.V4TooMuchRequested.selector, expectedAmountIn - 1, expectedAmountIn)
|
|
);
|
|
router.executeActions(data);
|
|
}
|
|
|
|
function test_swapExactOut_revertsForAmountInPerHop() public {
|
|
uint256 amountOut = 1 ether;
|
|
uint256 expectedAmountIn = 1008049273448486163;
|
|
|
|
uint256 expectedPrice = amountOut * PRECISION / expectedAmountIn;
|
|
uint256[] memory minPricesX36 = new uint256[](1);
|
|
uint256 minPrice = expectedPrice + 1;
|
|
minPricesX36[0] = minPrice;
|
|
|
|
tokenPath.push(currency0);
|
|
tokenPath.push(currency1);
|
|
IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, minPricesX36, amountOut);
|
|
params.amountInMaximum = type(uint128).max;
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params));
|
|
bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER);
|
|
|
|
vm.expectRevert(abi.encodeWithSelector(IV4Router.V4TooMuchRequestedPerHop.selector, 0, minPrice, expectedPrice));
|
|
router.executeActions(data);
|
|
}
|
|
|
|
function test_swapExactOut_succeedsForAmountInPerHop() public {
|
|
uint256 amountOut = 1 ether;
|
|
uint256 expectedAmountIn = 1008049273448486163;
|
|
|
|
uint256 expectedPrice = amountOut * PRECISION / expectedAmountIn;
|
|
uint256[] memory minPricesX36 = new uint256[](1);
|
|
uint256 minPriceX36 = expectedPrice;
|
|
minPricesX36[0] = minPriceX36;
|
|
|
|
tokenPath.push(currency0);
|
|
tokenPath.push(currency1);
|
|
IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, minPricesX36, amountOut);
|
|
params.amountInMaximum = type(uint128).max;
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params));
|
|
bytes memory data = plan.finalizeSwap(key0.currency0, key0.currency1, ActionConstants.MSG_SENDER);
|
|
|
|
router.executeActions(data);
|
|
}
|
|
|
|
function test_swapExactOut_1Hop_zeroForOne() public {
|
|
uint256 amountOut = 1 ether;
|
|
uint256 expectedAmountIn = 1008049273448486163;
|
|
|
|
tokenPath.push(currency0);
|
|
tokenPath.push(currency1);
|
|
IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(key0.currency0, key0.currency1, expectedAmountIn);
|
|
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, amountOut);
|
|
}
|
|
|
|
function test_swapExactOut_1Hop_oneForZero() public {
|
|
uint256 amountOut = 1 ether;
|
|
uint256 expectedAmountIn = 1008049273448486163;
|
|
|
|
tokenPath.push(currency1);
|
|
tokenPath.push(currency0);
|
|
IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(currency1, currency0, expectedAmountIn);
|
|
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, amountOut);
|
|
}
|
|
|
|
function test_swapExactOut_2Hops() public {
|
|
uint256 amountOut = 1 ether;
|
|
uint256 expectedAmountIn = 1016204441757464409;
|
|
|
|
tokenPath.push(currency0);
|
|
tokenPath.push(currency1);
|
|
tokenPath.push(currency2);
|
|
IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut);
|
|
|
|
uint256 intermediateBalanceBefore = currency1.balanceOfSelf();
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(currency0, currency2, expectedAmountIn);
|
|
|
|
assertEq(intermediateBalanceBefore, currency1.balanceOfSelf());
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
assertEq(currency2.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, amountOut);
|
|
}
|
|
|
|
function test_swapExactOut_3Hops() public {
|
|
uint256 amountOut = 1 ether;
|
|
uint256 expectedAmountIn = 1024467570922834110;
|
|
|
|
tokenPath.push(currency0);
|
|
tokenPath.push(currency1);
|
|
tokenPath.push(currency2);
|
|
tokenPath.push(currency3);
|
|
IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(currency0, currency3, expectedAmountIn);
|
|
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
assertEq(currency2.balanceOf(address(router)), 0);
|
|
assertEq(currency3.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, amountOut);
|
|
}
|
|
|
|
function test_swapExactOut_swapOpenDelta() public {
|
|
uint256 expectedAmountIn = 1008049273448486163;
|
|
|
|
tokenPath.push(currency0);
|
|
tokenPath.push(currency1);
|
|
|
|
IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, ActionConstants.OPEN_DELTA);
|
|
|
|
plan = plan.add(Actions.TAKE, abi.encode(key0.currency1, ActionConstants.ADDRESS_THIS, 1 ether));
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params));
|
|
plan = plan.add(Actions.SETTLE, abi.encode(key0.currency0, ActionConstants.OPEN_DELTA, true));
|
|
|
|
bytes memory data = plan.encode();
|
|
|
|
uint256 callerInputBefore = key0.currency0.balanceOfSelf();
|
|
uint256 routerInputBefore = key0.currency1.balanceOfSelf();
|
|
uint256 callerOutputBefore = key0.currency1.balanceOfSelf();
|
|
|
|
router.executeActions(data);
|
|
|
|
uint256 callerInputAfter = key0.currency0.balanceOfSelf();
|
|
uint256 routerInputAfter = key0.currency1.balanceOfSelf();
|
|
uint256 callerOutputAfter = key0.currency1.balanceOfSelf();
|
|
|
|
// caller paid
|
|
assertEq(callerInputBefore - expectedAmountIn, callerInputAfter);
|
|
assertEq(routerInputBefore, routerInputAfter);
|
|
assertEq(callerOutputBefore, callerOutputAfter);
|
|
}
|
|
|
|
/*//////////////////////////////////////////////////////////////
|
|
ETH -> ERC20 and ERC20 -> ETH EXACT OUTPUT
|
|
//////////////////////////////////////////////////////////////*/
|
|
|
|
function test_nativeIn_swapExactOutputSingle_sweepExcessETH() public {
|
|
uint256 amountOut = 1 ether;
|
|
uint256 expectedAmountIn = 1008049273448486163;
|
|
|
|
IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams(
|
|
nativeKey, true, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("")
|
|
);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteNativeInputExactOutputSwap(nativeKey.currency0, nativeKey.currency1, expectedAmountIn);
|
|
|
|
assertEq(nativeKey.currency0.balanceOf(address(router)), 0);
|
|
assertEq(nativeKey.currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, amountOut);
|
|
}
|
|
|
|
function test_nativeOut_swapExactOutputSingle() public {
|
|
uint256 amountOut = 1 ether;
|
|
uint256 expectedAmountIn = 1008049273448486163;
|
|
|
|
IV4Router.ExactOutputSingleParams memory params = IV4Router.ExactOutputSingleParams(
|
|
nativeKey, false, uint128(amountOut), uint128(expectedAmountIn + 1), 0, bytes("")
|
|
);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT_SINGLE, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(nativeKey.currency1, nativeKey.currency0, expectedAmountIn);
|
|
|
|
assertEq(nativeKey.currency0.balanceOf(address(router)), 0);
|
|
assertEq(nativeKey.currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, amountOut);
|
|
}
|
|
|
|
function test_nativeIn_swapExactOut_1Hop_sweepExcessETH() public {
|
|
uint256 amountOut = 1 ether;
|
|
uint256 expectedAmountIn = 1008049273448486163;
|
|
|
|
tokenPath.push(CurrencyLibrary.ADDRESS_ZERO);
|
|
tokenPath.push(nativeKey.currency1);
|
|
IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteNativeInputExactOutputSwap(
|
|
CurrencyLibrary.ADDRESS_ZERO, nativeKey.currency1, expectedAmountIn
|
|
);
|
|
|
|
assertEq(nativeKey.currency0.balanceOf(address(router)), 0);
|
|
assertEq(nativeKey.currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, amountOut);
|
|
}
|
|
|
|
function test_nativeOut_swapExactOut_1Hop() public {
|
|
uint256 amountOut = 1 ether;
|
|
uint256 expectedAmountIn = 1008049273448486163;
|
|
|
|
tokenPath.push(nativeKey.currency1);
|
|
tokenPath.push(CurrencyLibrary.ADDRESS_ZERO);
|
|
IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut);
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteSwap(nativeKey.currency1, CurrencyLibrary.ADDRESS_ZERO, expectedAmountIn);
|
|
|
|
assertEq(nativeKey.currency0.balanceOf(address(router)), 0);
|
|
assertEq(nativeKey.currency1.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, amountOut);
|
|
}
|
|
|
|
function test_nativeIn_swapExactOut_2Hops_sweepExcessETH() public {
|
|
uint256 amountOut = 1 ether;
|
|
uint256 expectedAmountIn = 1016204441757464409;
|
|
|
|
// the initialized nativeKey is (native, currency0)
|
|
tokenPath.push(CurrencyLibrary.ADDRESS_ZERO);
|
|
tokenPath.push(currency0);
|
|
tokenPath.push(currency1);
|
|
IV4Router.ExactOutputParams memory params = _getExactOutputParams(tokenPath, amountOut);
|
|
|
|
uint256 intermediateBalanceBefore = currency0.balanceOfSelf();
|
|
|
|
plan = plan.add(Actions.SWAP_EXACT_OUT, abi.encode(params));
|
|
|
|
(
|
|
uint256 inputBalanceBefore,
|
|
uint256 outputBalanceBefore,
|
|
uint256 inputBalanceAfter,
|
|
uint256 outputBalanceAfter
|
|
) = _finalizeAndExecuteNativeInputExactOutputSwap(CurrencyLibrary.ADDRESS_ZERO, currency1, expectedAmountIn);
|
|
|
|
assertEq(intermediateBalanceBefore, currency0.balanceOfSelf());
|
|
assertEq(currency1.balanceOf(address(router)), 0);
|
|
assertEq(currency0.balanceOf(address(router)), 0);
|
|
assertEq(CurrencyLibrary.ADDRESS_ZERO.balanceOf(address(router)), 0);
|
|
|
|
assertEq(inputBalanceBefore - inputBalanceAfter, expectedAmountIn);
|
|
assertEq(outputBalanceAfter - outputBalanceBefore, amountOut);
|
|
}
|
|
}
|