feat(pair): add constant-product amm pair with price accumulators
This commit is contained in:
1 parent
37278f2d17
commit
7b365bb5b8
1 file changed
+172
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user