// SPDX-License-Identifier: MIT pragma solidity 0.8.36; import {Test} from "forge-std/Test.sol"; import {FluxFactory} from "../src/FluxFactory.sol"; import {FluxPair} from "../src/FluxPair.sol"; import {TestErc20} from "../src/TestErc20.sol"; import {IFluxCallee} from "../src/interfaces/IFluxCallee.sol"; contract ReentrantSwapper is IFluxCallee { FluxPair private pair; constructor(FluxPair pairAddress) { pair = pairAddress; } function fluxCall(address, uint256, uint256, bytes calldata) external { pair.swap(0, 1, address(this), ""); } } contract FluxPairTest is Test { address private constant receiver = address(0xBEEF); FluxFactory private factory; FluxPair private pair; TestErc20 private token0; TestErc20 private token1; uint256 private initialLp; function setUp() public { factory = new FluxFactory(address(this)); TestErc20 tokenA = new TestErc20("Alpha", "ALPHA", 1_000_000e18); TestErc20 tokenB = new TestErc20("Beta", "BETA", 1_000_000e18); address pairAddress = factory.createPair(address(tokenA), address(tokenB)); pair = FluxPair(pairAddress); token0 = TestErc20(pair.token0()); token1 = TestErc20(pair.token1()); token0.transfer(pairAddress, 100e18); token1.transfer(pairAddress, 400e18); initialLp = pair.mint(address(this)); } function testInitialMint() public view { assertEq(initialLp, 199999999999999999000); assertEq(pair.balanceOf(address(this)), 199999999999999999000); assertEq(pair.totalSupply(), 200e18); assertEq(pair.balanceOf(address(0)), 1000); (uint112 reserve0, uint112 reserve1,) = pair.getReserves(); assertEq(reserve0, 100e18); assertEq(reserve1, 400e18); } function testProportionalMint() public { token0.transfer(address(pair), 50e18); token1.transfer(address(pair), 200e18); uint256 minted = pair.mint(receiver); assertEq(minted, 100e18); assertEq(pair.balanceOf(receiver), 100e18); assertEq(pair.totalSupply(), 300e18); (uint112 reserve0, uint112 reserve1,) = pair.getReserves(); assertEq(reserve0, 150e18); assertEq(reserve1, 600e18); } function testSwapMovesReservesAndKeepsInvariant() public { uint256 expectedOut = 36264435755205965263; uint256 kBefore = uint256(100e18) * 400e18; token0.transfer(address(pair), 10e18); pair.swap(0, expectedOut, receiver, ""); assertEq(token1.balanceOf(receiver), expectedOut); (uint112 reserve0, uint112 reserve1,) = pair.getReserves(); assertEq(reserve0, 110e18); assertEq(reserve1, 363735564244794034737); assertGt(uint256(reserve0) * uint256(reserve1), kBefore); } function testSwapRevertsWhenInvariantBroken() public { token0.transfer(address(pair), 10e18); vm.expectRevert(bytes("Flux: K")); pair.swap(0, 36264435755205965263 + 1e15, receiver, ""); } function testSwapRevertsWithoutInput() public { vm.expectRevert(bytes("Flux: INSUFFICIENT_INPUT_AMOUNT")); pair.swap(0, 1e18, receiver, ""); } function testBurnReturnsProportional() public { uint256 lp = pair.balanceOf(address(this)); pair.transfer(address(pair), lp); (uint256 amount0, uint256 amount1) = pair.burn(receiver); assertEq(amount0, 99999999999999999500); assertEq(amount1, 399999999999999998000); assertEq(token0.balanceOf(receiver), 99999999999999999500); assertEq(token1.balanceOf(receiver), 399999999999999998000); assertEq(pair.totalSupply(), 1000); } function testSkimSendsExcessAndKeepsReserves() public { token0.transfer(address(pair), 5e18); pair.skim(receiver); assertEq(token0.balanceOf(receiver), 5e18); (uint112 reserve0,,) = pair.getReserves(); assertEq(reserve0, 100e18); } function testSyncAdoptsBalances() public { token0.transfer(address(pair), 5e18); pair.sync(); (uint112 reserve0, uint112 reserve1,) = pair.getReserves(); assertEq(reserve0, 105e18); assertEq(reserve1, 400e18); } function testPriceAccumulatorGrowsOverTime() public { assertEq(pair.price0CumulativeLast(), 0); vm.warp(block.timestamp + 3600); pair.sync(); assertGt(pair.price0CumulativeLast(), 0); assertGt(pair.price1CumulativeLast(), 0); } function testReentrancyLockBlocksSwap() public { ReentrantSwapper attacker = new ReentrantSwapper(pair); token0.transfer(address(pair), 10e18); vm.expectRevert(bytes("Flux: LOCKED")); pair.swap(0, 1e18, address(attacker), "0x01"); } }