test(router): add multi-hop swap router tests

This commit is contained in:
dualfroz committed 2026-08-28 09:00:00 +02:00
1 parent 6444671cdd
commit 0bcc9d9455
1 file changed
+198
+198
View File
@@ -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);
}
}