136 lines
4.7 KiB
Solidity
136 lines
4.7 KiB
Solidity
// 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");
|
|
}
|
|
}
|