feat(pair): add constant-product amm pair with price accumulators

This commit is contained in:
dualfroz committed 2026-08-26 09:00:00 +02:00
1 parent 37278f2d17
commit 7b365bb5b8
1 file changed
+172
+172
View File
@@ -0,0 +1,172 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.36;
import {Erc20} from "./Erc20.sol";
import {IErc20} from "./interfaces/IErc20.sol";
import {IFluxCallee} from "./interfaces/IFluxCallee.sol";
import {Math} from "./libraries/Math.sol";
import {UQ112x112} from "./libraries/UQ112x112.sol";
/// @title Constant-product pair holding reserves of two tokens and the LP token.
contract FluxPair is Erc20 {
using UQ112x112 for uint224;
uint256 public constant minimumLiquidity = 10 ** 3;
address public factory;
address public token0;
address public token1;
uint112 private reserve0;
uint112 private reserve1;
uint32 private blockTimestampLast;
uint256 public price0CumulativeLast;
uint256 public price1CumulativeLast;
uint256 private unlocked = 1;
event Mint(address indexed sender, uint256 amount0, uint256 amount1);
event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to);
event Swap(
address indexed sender,
uint256 amount0In,
uint256 amount1In,
uint256 amount0Out,
uint256 amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
modifier lock() {
require(unlocked == 1, "Flux: LOCKED");
unlocked = 0;
_;
unlocked = 1;
}
constructor() Erc20("FluxSwap LP", "FLUX-LP") {
factory = msg.sender;
}
function initialize(address firstToken, address secondToken) external {
require(msg.sender == factory, "Flux: FORBIDDEN");
token0 = firstToken;
token1 = secondToken;
}
function getReserves()
public
view
returns (uint112 currentReserve0, uint112 currentReserve1, uint32 timestampLast)
{
currentReserve0 = reserve0;
currentReserve1 = reserve1;
timestampLast = blockTimestampLast;
}
function _safeTransfer(address token, address to, uint256 value) private {
(bool success, bytes memory returndata) =
token.call(abi.encodeWithSelector(IErc20.transfer.selector, to, value));
require(success && (returndata.length == 0 || abi.decode(returndata, (bool))), "Flux: TRANSFER_FAILED");
}
function _update(uint256 balance0, uint256 balance1, uint112 oldReserve0, uint112 oldReserve1) private {
require(balance0 <= type(uint112).max && balance1 <= type(uint112).max, "Flux: OVERFLOW");
uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32);
unchecked {
uint32 timeElapsed = blockTimestamp - blockTimestampLast;
if (timeElapsed > 0 && oldReserve0 != 0 && oldReserve1 != 0) {
price0CumulativeLast += uint256(UQ112x112.encode(oldReserve1).uqdiv(oldReserve0)) * timeElapsed;
price1CumulativeLast += uint256(UQ112x112.encode(oldReserve0).uqdiv(oldReserve1)) * timeElapsed;
}
}
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
blockTimestampLast = blockTimestamp;
emit Sync(reserve0, reserve1);
}
function mint(address to) external lock returns (uint256 liquidity) {
(uint112 oldReserve0, uint112 oldReserve1,) = getReserves();
uint256 balance0 = IErc20(token0).balanceOf(address(this));
uint256 balance1 = IErc20(token1).balanceOf(address(this));
uint256 amount0 = balance0 - oldReserve0;
uint256 amount1 = balance1 - oldReserve1;
uint256 supply = totalSupply;
if (supply == 0) {
liquidity = Math.sqrt(amount0 * amount1) - minimumLiquidity;
_mint(address(0), minimumLiquidity);
} else {
liquidity = Math.min(amount0 * supply / oldReserve0, amount1 * supply / oldReserve1);
}
require(liquidity > 0, "Flux: INSUFFICIENT_LIQUIDITY_MINTED");
_mint(to, liquidity);
_update(balance0, balance1, oldReserve0, oldReserve1);
emit Mint(msg.sender, amount0, amount1);
}
function burn(address to) external lock returns (uint256 amount0, uint256 amount1) {
address firstToken = token0;
address secondToken = token1;
uint256 balance0 = IErc20(firstToken).balanceOf(address(this));
uint256 balance1 = IErc20(secondToken).balanceOf(address(this));
uint256 liquidity = balanceOf[address(this)];
uint256 supply = totalSupply;
amount0 = liquidity * balance0 / supply;
amount1 = liquidity * balance1 / supply;
require(amount0 > 0 && amount1 > 0, "Flux: INSUFFICIENT_LIQUIDITY_BURNED");
_burn(address(this), liquidity);
_safeTransfer(firstToken, to, amount0);
_safeTransfer(secondToken, to, amount1);
balance0 = IErc20(firstToken).balanceOf(address(this));
balance1 = IErc20(secondToken).balanceOf(address(this));
(uint112 oldReserve0, uint112 oldReserve1,) = getReserves();
_update(balance0, balance1, oldReserve0, oldReserve1);
emit Burn(msg.sender, amount0, amount1, to);
}
function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external lock {
require(amount0Out > 0 || amount1Out > 0, "Flux: INSUFFICIENT_OUTPUT_AMOUNT");
(uint112 oldReserve0, uint112 oldReserve1,) = getReserves();
require(amount0Out < oldReserve0 && amount1Out < oldReserve1, "Flux: INSUFFICIENT_LIQUIDITY");
uint256 balance0;
uint256 balance1;
{
address firstToken = token0;
address secondToken = token1;
require(to != firstToken && to != secondToken, "Flux: INVALID_TO");
if (amount0Out > 0) _safeTransfer(firstToken, to, amount0Out);
if (amount1Out > 0) _safeTransfer(secondToken, to, amount1Out);
if (data.length > 0) IFluxCallee(to).fluxCall(msg.sender, amount0Out, amount1Out, data);
balance0 = IErc20(firstToken).balanceOf(address(this));
balance1 = IErc20(secondToken).balanceOf(address(this));
}
uint256 amount0In = balance0 > oldReserve0 - amount0Out ? balance0 - (oldReserve0 - amount0Out) : 0;
uint256 amount1In = balance1 > oldReserve1 - amount1Out ? balance1 - (oldReserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, "Flux: INSUFFICIENT_INPUT_AMOUNT");
{
uint256 balance0Adjusted = balance0 * 1000 - amount0In * 3;
uint256 balance1Adjusted = balance1 * 1000 - amount1In * 3;
require(balance0Adjusted * balance1Adjusted >= uint256(oldReserve0) * oldReserve1 * (1000 ** 2), "Flux: K");
}
_update(balance0, balance1, oldReserve0, oldReserve1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
function skim(address to) external lock {
address firstToken = token0;
address secondToken = token1;
_safeTransfer(firstToken, to, IErc20(firstToken).balanceOf(address(this)) - reserve0);
_safeTransfer(secondToken, to, IErc20(secondToken).balanceOf(address(this)) - reserve1);
}
function sync() external lock {
_update(IErc20(token0).balanceOf(address(this)), IErc20(token1).balanceOf(address(this)), reserve0, reserve1);
}
}