test(pair): add constant-product pair tests
This commit is contained in:
1 parent
9b63845065
commit
0489caed13
1 file changed
+135
@@ -0,0 +1,135 @@
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user