// SPDX-License-Identifier: MIT pragma solidity 0.8.36; import {Test} from "forge-std/Test.sol"; import {FluxFactory} from "../src/FluxFactory.sol"; import {FluxRouter} from "../src/FluxRouter.sol"; import {FluxPair} from "../src/FluxPair.sol"; import {TestErc20} from "../src/TestErc20.sol"; contract FluxRouterTest is Test { address private constant receiver = address(0xBEEF); FluxFactory private factory; FluxRouter private router; TestErc20 private tokenA; TestErc20 private tokenB; TestErc20 private tokenC; function setUp() public { vm.warp(1000); factory = new FluxFactory(address(this)); router = new FluxRouter(address(factory)); tokenA = new TestErc20("Alpha", "ALPHA", 1_000_000e18); tokenB = new TestErc20("Beta", "BETA", 1_000_000e18); tokenC = new TestErc20("Gamma", "GAMMA", 1_000_000e18); tokenA.approve(address(router), type(uint256).max); tokenB.approve(address(router), type(uint256).max); tokenC.approve(address(router), type(uint256).max); } function addAb(uint256 amountA, uint256 amountB) private returns (uint256 liquidity) { (,, liquidity) = router.addLiquidity(address(tokenA), address(tokenB), amountA, amountB, 0, 0, address(this), 2000); } function testAddLiquidityInitial() public { (uint256 amountA, uint256 amountB, uint256 liquidity) = router.addLiquidity(address(tokenA), address(tokenB), 100e18, 400e18, 0, 0, address(this), 2000); assertEq(amountA, 100e18); assertEq(amountB, 400e18); assertEq(liquidity, 199999999999999999000); address pair = factory.getPair(address(tokenA), address(tokenB)); assertEq(FluxPair(pair).balanceOf(address(this)), 199999999999999999000); (uint112 reserveA, uint112 reserveB) = reservesOf(address(tokenA), address(tokenB)); assertEq(reserveA, 100e18); assertEq(reserveB, 400e18); } function testAddLiquidityUsesOptimalAmount() public { addAb(100e18, 400e18); (uint256 amountA, uint256 amountB,) = router.addLiquidity(address(tokenA), address(tokenB), 50e18, 1000e18, 0, 0, address(this), 2000); assertEq(amountA, 50e18); assertEq(amountB, 200e18); } function testAddLiquidityRevertsOnBSlippage() public { addAb(100e18, 400e18); vm.expectRevert(bytes("FluxRouter: INSUFFICIENT_B_AMOUNT")); router.addLiquidity(address(tokenA), address(tokenB), 50e18, 1000e18, 0, 300e18, address(this), 2000); } function testRemoveLiquidityReturnsProportional() public { uint256 liquidity = addAb(100e18, 400e18); address pair = factory.getPair(address(tokenA), address(tokenB)); FluxPair(pair).approve(address(router), liquidity); (uint256 amountA, uint256 amountB) = router.removeLiquidity(address(tokenA), address(tokenB), liquidity, 0, 0, receiver, 2000); assertEq(amountA, 99999999999999999500); assertEq(amountB, 399999999999999998000); assertEq(tokenA.balanceOf(receiver), 99999999999999999500); assertEq(tokenB.balanceOf(receiver), 399999999999999998000); } function testRemoveLiquidityRevertsOnSlippage() public { uint256 liquidity = addAb(100e18, 400e18); address pair = factory.getPair(address(tokenA), address(tokenB)); FluxPair(pair).approve(address(router), liquidity); vm.expectRevert(bytes("FluxRouter: INSUFFICIENT_A_AMOUNT")); router.removeLiquidity(address(tokenA), address(tokenB), liquidity, 100e18, 0, receiver, 2000); } function testSwapExactTokensForTokensSingleHop() public { addAb(100e18, 400e18); uint256 expectedOut = 36264435755205965263; address[] memory path = new address[](2); path[0] = address(tokenA); path[1] = address(tokenB); uint256[] memory amounts = router.swapExactTokensForTokens(10e18, 0, path, receiver, 2000); assertEq(amounts[0], 10e18); assertEq(amounts[1], expectedOut); assertEq(tokenB.balanceOf(receiver), expectedOut); (uint112 reserveA, uint112 reserveB) = reservesOf(address(tokenA), address(tokenB)); assertEq(reserveA, 110e18); assertEq(reserveB, 400e18 - expectedOut); } function testSwapTokensForExactTokensSingleHop() public { addAb(100e18, 400e18); uint256 expectedIn = 14328700386874910446; address[] memory path = new address[](2); path[0] = address(tokenA); path[1] = address(tokenB); uint256[] memory amounts = router.swapTokensForExactTokens(50e18, type(uint256).max, path, receiver, 2000); assertEq(amounts[0], expectedIn); assertEq(amounts[1], 50e18); assertEq(tokenB.balanceOf(receiver), 50e18); } function testMultiHopSwap() public { addAb(100e18, 200e18); router.addLiquidity(address(tokenB), address(tokenC), 300e18, 150e18, 0, 0, address(this), 2000); address[] memory path = new address[](3); path[0] = address(tokenA); path[1] = address(tokenB); path[2] = address(tokenC); uint256[] memory amounts = router.swapExactTokensForTokens(10e18, 0, path, receiver, 2000); assertEq(amounts[0], 10e18); assertEq(amounts[1], 18132217877602982631); assertEq(amounts[2], 8525187871197527614); assertEq(tokenC.balanceOf(receiver), 8525187871197527614); } function testSwapRevertsOnSlippage() public { addAb(100e18, 400e18); address[] memory path = new address[](2); path[0] = address(tokenA); path[1] = address(tokenB); vm.expectRevert(bytes("FluxRouter: INSUFFICIENT_OUTPUT_AMOUNT")); router.swapExactTokensForTokens(10e18, 40e18, path, receiver, 2000); } function testSwapRevertsOnExcessiveInput() public { addAb(100e18, 400e18); address[] memory path = new address[](2); path[0] = address(tokenA); path[1] = address(tokenB); vm.expectRevert(bytes("FluxRouter: EXCESSIVE_INPUT_AMOUNT")); router.swapTokensForExactTokens(50e18, 14e18, path, receiver, 2000); } function testSwapRevertsOnExpiredDeadline() public { addAb(100e18, 400e18); address[] memory path = new address[](2); path[0] = address(tokenA); path[1] = address(tokenB); vm.expectRevert(bytes("FluxRouter: EXPIRED")); router.swapExactTokensForTokens(10e18, 0, path, receiver, 999); } function reservesOf(address first, address second) private view returns (uint112 reserveFirst, uint112 reserveSecond) { address pair = factory.getPair(first, second); (uint112 reserve0, uint112 reserve1,) = FluxPair(pair).getReserves(); (address token0,) = first < second ? (first, second) : (second, first); (reserveFirst, reserveSecond) = first == token0 ? (reserve0, reserve1) : (reserve1, reserve0); } }