test(router): add multi-hop swap router tests
This commit is contained in:
1 parent
6444671cdd
commit
0bcc9d9455
1 file changed
+198
@@ -0,0 +1,198 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity 0.8.36;
|
||||||
|
|
||||||
|
import {Test} from "forge-std/Test.sol";
|
||||||
|
import {FluxClFactory} from "../src/FluxClFactory.sol";
|
||||||
|
import {FluxClPool} from "../src/FluxClPool.sol";
|
||||||
|
import {FluxSwapRouter} from "../src/FluxSwapRouter.sol";
|
||||||
|
import {TestErc20} from "../src/TestErc20.sol";
|
||||||
|
import {IErc20} from "../src/interfaces/IErc20.sol";
|
||||||
|
import {IFluxMintCallback} from "../src/interfaces/IFluxMintCallback.sol";
|
||||||
|
|
||||||
|
contract FluxSwapRouterTest is Test, IFluxMintCallback {
|
||||||
|
uint160 private constant priceOne = 79228162514264337593543950336;
|
||||||
|
|
||||||
|
FluxClFactory private factory;
|
||||||
|
FluxSwapRouter private router;
|
||||||
|
TestErc20 private tokenA;
|
||||||
|
TestErc20 private tokenB;
|
||||||
|
TestErc20 private tokenC;
|
||||||
|
|
||||||
|
address private constant trader = address(0xF0);
|
||||||
|
|
||||||
|
function fluxMintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata data) external {
|
||||||
|
(address token0, address token1) = abi.decode(data, (address, address));
|
||||||
|
if (amount0Owed > 0) IErc20(token0).transfer(msg.sender, amount0Owed);
|
||||||
|
if (amount1Owed > 0) IErc20(token1).transfer(msg.sender, amount1Owed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function seed(FluxClPool pool) private {
|
||||||
|
pool.mint(address(this), -60000, 60000, 1e23, abi.encode(pool.token0(), pool.token1()));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setUp() public {
|
||||||
|
factory = new FluxClFactory();
|
||||||
|
router = new FluxSwapRouter(address(factory));
|
||||||
|
|
||||||
|
tokenA = new TestErc20("Aaa", "AAA", 1e30);
|
||||||
|
tokenB = new TestErc20("Bbb", "BBB", 1e30);
|
||||||
|
tokenC = new TestErc20("Ccc", "CCC", 1e30);
|
||||||
|
|
||||||
|
FluxClPool poolAB = FluxClPool(factory.createPool(address(tokenA), address(tokenB), 3000));
|
||||||
|
FluxClPool poolBC = FluxClPool(factory.createPool(address(tokenB), address(tokenC), 3000));
|
||||||
|
poolAB.initialize(priceOne);
|
||||||
|
poolBC.initialize(priceOne);
|
||||||
|
seed(poolAB);
|
||||||
|
seed(poolBC);
|
||||||
|
|
||||||
|
tokenA.mint(trader, 1e24);
|
||||||
|
tokenB.mint(trader, 1e24);
|
||||||
|
tokenC.mint(trader, 1e24);
|
||||||
|
vm.startPrank(trader);
|
||||||
|
tokenA.approve(address(router), type(uint256).max);
|
||||||
|
tokenB.approve(address(router), type(uint256).max);
|
||||||
|
tokenC.approve(address(router), type(uint256).max);
|
||||||
|
vm.stopPrank();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testExactInputSingle() public {
|
||||||
|
vm.prank(trader);
|
||||||
|
uint256 out = router.exactInputSingle(
|
||||||
|
FluxSwapRouter.ExactInputSingleParams({
|
||||||
|
tokenIn: address(tokenA),
|
||||||
|
tokenOut: address(tokenB),
|
||||||
|
fee: 3000,
|
||||||
|
recipient: trader,
|
||||||
|
deadline: block.timestamp + 1,
|
||||||
|
amountIn: 1e18,
|
||||||
|
amountOutMinimum: 0,
|
||||||
|
sqrtPriceLimitX96: 0
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assertGt(out, 0);
|
||||||
|
assertGt(out, 99e16);
|
||||||
|
assertLt(out, 1e18);
|
||||||
|
assertEq(tokenB.balanceOf(trader), 1e24 + out);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testExactInputMultiHop() public {
|
||||||
|
bytes memory path =
|
||||||
|
abi.encodePacked(address(tokenA), uint24(3000), address(tokenB), uint24(3000), address(tokenC));
|
||||||
|
uint256 cBefore = tokenC.balanceOf(trader);
|
||||||
|
vm.prank(trader);
|
||||||
|
uint256 out = router.exactInput(
|
||||||
|
FluxSwapRouter.ExactInputParams({
|
||||||
|
path: path, recipient: trader, deadline: block.timestamp + 1, amountIn: 1e18, amountOutMinimum: 0
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assertGt(out, 0);
|
||||||
|
assertGt(out, 98e16);
|
||||||
|
assertLt(out, 1e18);
|
||||||
|
assertEq(tokenC.balanceOf(trader), cBefore + out);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testExactOutputSingle() public {
|
||||||
|
uint256 aBefore = tokenA.balanceOf(trader);
|
||||||
|
uint256 bBefore = tokenB.balanceOf(trader);
|
||||||
|
vm.prank(trader);
|
||||||
|
uint256 amountIn = router.exactOutputSingle(
|
||||||
|
FluxSwapRouter.ExactOutputSingleParams({
|
||||||
|
tokenIn: address(tokenA),
|
||||||
|
tokenOut: address(tokenB),
|
||||||
|
fee: 3000,
|
||||||
|
recipient: trader,
|
||||||
|
deadline: block.timestamp + 1,
|
||||||
|
amountOut: 1e18,
|
||||||
|
amountInMaximum: 2e18,
|
||||||
|
sqrtPriceLimitX96: 0
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assertGt(amountIn, 1e18);
|
||||||
|
assertEq(tokenB.balanceOf(trader), bBefore + 1e18);
|
||||||
|
assertEq(tokenA.balanceOf(trader), aBefore - amountIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testExactOutputMultiHop() public {
|
||||||
|
bytes memory path =
|
||||||
|
abi.encodePacked(address(tokenC), uint24(3000), address(tokenB), uint24(3000), address(tokenA));
|
||||||
|
uint256 cBefore = tokenC.balanceOf(trader);
|
||||||
|
vm.prank(trader);
|
||||||
|
uint256 amountIn = router.exactOutput(
|
||||||
|
FluxSwapRouter.ExactOutputParams({
|
||||||
|
path: path, recipient: trader, deadline: block.timestamp + 1, amountOut: 1e18, amountInMaximum: 2e18
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assertGt(amountIn, 1e18);
|
||||||
|
assertEq(tokenC.balanceOf(trader), cBefore + 1e18);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testRevertsOnExpiredDeadline() public {
|
||||||
|
vm.warp(1000);
|
||||||
|
vm.prank(trader);
|
||||||
|
vm.expectRevert(bytes("FluxRouter: EXPIRED"));
|
||||||
|
router.exactInputSingle(
|
||||||
|
FluxSwapRouter.ExactInputSingleParams({
|
||||||
|
tokenIn: address(tokenA),
|
||||||
|
tokenOut: address(tokenB),
|
||||||
|
fee: 3000,
|
||||||
|
recipient: trader,
|
||||||
|
deadline: 999,
|
||||||
|
amountIn: 1e18,
|
||||||
|
amountOutMinimum: 0,
|
||||||
|
sqrtPriceLimitX96: 0
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testRevertsOnSlippageExactInput() public {
|
||||||
|
vm.prank(trader);
|
||||||
|
vm.expectRevert(bytes("FluxRouter: TOO_LITTLE_OUT"));
|
||||||
|
router.exactInputSingle(
|
||||||
|
FluxSwapRouter.ExactInputSingleParams({
|
||||||
|
tokenIn: address(tokenA),
|
||||||
|
tokenOut: address(tokenB),
|
||||||
|
fee: 3000,
|
||||||
|
recipient: trader,
|
||||||
|
deadline: block.timestamp + 1,
|
||||||
|
amountIn: 1e18,
|
||||||
|
amountOutMinimum: 1e18,
|
||||||
|
sqrtPriceLimitX96: 0
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testRevertsOnSlippageExactOutput() public {
|
||||||
|
vm.prank(trader);
|
||||||
|
vm.expectRevert(bytes("FluxRouter: TOO_MUCH_IN"));
|
||||||
|
router.exactOutputSingle(
|
||||||
|
FluxSwapRouter.ExactOutputSingleParams({
|
||||||
|
tokenIn: address(tokenA),
|
||||||
|
tokenOut: address(tokenB),
|
||||||
|
fee: 3000,
|
||||||
|
recipient: trader,
|
||||||
|
deadline: block.timestamp + 1,
|
||||||
|
amountOut: 1e18,
|
||||||
|
amountInMaximum: 1e18,
|
||||||
|
sqrtPriceLimitX96: 0
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testFuzzExactInputSingleMonotonic(uint128 amountIn) public {
|
||||||
|
amountIn = uint128(bound(amountIn, 1e9, 1e22));
|
||||||
|
vm.prank(trader);
|
||||||
|
uint256 out = router.exactInputSingle(
|
||||||
|
FluxSwapRouter.ExactInputSingleParams({
|
||||||
|
tokenIn: address(tokenA),
|
||||||
|
tokenOut: address(tokenB),
|
||||||
|
fee: 3000,
|
||||||
|
recipient: trader,
|
||||||
|
deadline: block.timestamp + 1,
|
||||||
|
amountIn: amountIn,
|
||||||
|
amountOutMinimum: 0,
|
||||||
|
sqrtPriceLimitX96: 0
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assertLt(out, amountIn);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user