流动性池
什么是流动性池
流动性池是JAMM DEX的核心组件,它是一个包含两种代币储备的智能合约。每个流动性池都是一个 JAMMPair
合约实例,负责管理特定代币对的交易和流动性。
池子的创建
通过Factory创建
所有流动性池都通过 JAMMFactory
合约创建:
// from JAMMFactory.sol
function createPair(
address tokenA,
address tokenB,
uint24 fee
) external override returns (address pair) {
require(tokenA != tokenB, "JAMMFactory: IDENTICAL_ADDRESSES");
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), "JAMMFactory: ZERO_ADDRESS");
require(
fee == FEE_0_5_PERCENT ||
fee == FEE_1_PERCENT ||
fee == FEE_2_PERCENT ||
fee == FEE_3_PERCENT,
"JAMMFactory: INVALID_FEE"
);
require(getPair[token0][token1][fee] == address(0), "JAMMFactory: PAIR_EXISTS");
// deploy the pair using CREATE2
bytes memory bytecode = type(JAMMPair).creationCode;
bytes32 salt = keccak256(abi.encodePacked(token0, token1, fee));
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
// initialize the pair
JAMMPair(pair).initialize(token0, token1, fee);
getPair[token0][token1][fee] = pair;
getPair[token1][token0][fee] = pair;
allPairs.push(pair);
emit PairCreated(token0, token1, fee, pair, allPairs.length);
}
确定性地址生成
JAMM DEX使用CREATE2来生成确定性的池子地址,这意味着给定相同的代币对和费率,池子地址总是相同的。这个计算在 JAMMLibrary.sol
中完成:
// from JAMMLibrary.sol
function pairFor(
address factory,
address tokenA,
address tokenB,
uint24 fee
) internal pure returns (address pair) {
(address token0, address token1) = sortTokens(tokenA, tokenB);
pair = address(uint160(uint256(keccak256(abi.encodePacked(
hex"ff",
factory,
keccak256(abi.encodePacked(token0, token1, fee)),
hex"2e599419feff8382bdfc47abf847537b06e96b0525db3802b2a9fb0bfe068ed8" // init code hash
)))));
}
池子的状态
储备量管理
每个池子维护两种代币的储备量:
// from JAMMPair.sol
uint112 private reserve0;
uint112 private reserve1;
uint32 private blockTimestampLast;
function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
_reserve0 = reserve0;
_reserve1 = reserve1;
_blockTimestampLast = blockTimestampLast;
}
储备量更新
储备量在每次交易或流动性变更后都会更新:
// from JAMMPair.sol
function _update(
uint balance0,
uint balance1,
uint112 _reserve0,
uint112 _reserve1
) private {
require(balance0 <= type(uint112).max && balance1 <= type(uint112).max, "JAMM: OVERFLOW");
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
uint32 timeElapsed = blockTimestamp - blockTimestampLast;
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
// update cumulative prices
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
}
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
blockTimestampLast = blockTimestamp;
emit Sync(reserve0, reserve1);
}
流动性代币 (LP代币)
LP代币的作用
当用户向池子提供流动性时,会收到LP代币作为凭证:
LP代币代表用户在池子中的份额。
可以随时用LP代币赎回相应的代币对。
LP代币本身也是ERC-20代币,可以转让。
LP代币的铸造
// from JAMMPair.sol
function mint(address to) external lock returns (uint liquidity) {
(uint112 _reserve0, uint112 _reserve1, ) = getReserves();
uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(token1).balanceOf(address(this));
uint amount0 = balance0 - _reserve0;
uint amount1 = balance1 - _reserve1;
bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply;
if (_totalSupply == 0) {
// first liquidity provider
liquidity = Math.sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY;
_mint(address(0), MINIMUM_LIQUIDITY);
} else {
// subsequent liquidity providers
liquidity = Math.min((amount0 * _totalSupply) / _reserve0, (amount1 * _totalSupply) / _reserve1);
}
require(liquidity > 0, "JAMM: INSUFFICIENT_LIQUIDITY_MINTED");
_mint(to, liquidity);
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0) * uint(reserve1);
emit Mint(msg.sender, amount0, amount1);
}
LP代币的销毁
// from JAMMPair.sol
function burn(address to) external lock returns (uint amount0, uint amount1) {
(uint112 _reserve0, uint112 _reserve1, ) = getReserves();
address _token0 = token0;
address _token1 = token1;
uint balance0 = IERC20(_token0).balanceOf(address(this));
uint balance1 = IERC20(_token1).balanceOf(address(this));
uint liquidity = balanceOf[address(this)];
bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply;
amount0 = (liquidity * balance0) / _totalSupply; // prorated distribution
amount1 = (liquidity * balance1) / _totalSupply; // prorated distribution
require(amount0 > 0 && amount1 > 0, "JAMM: INSUFFICIENT_LIQUIDITY_BURNED");
_burn(address(this), liquidity);
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0) * uint(reserve1);
emit Burn(msg.sender, amount0, amount1, to);
}
交易执行
Swap函数
池子的核心功能是执行代币交换:
// from JAMMPair.sol
function swap(
uint amount0Out,
uint amount1Out,
address to,
bytes calldata data,
address _referrer
) external lock {
require(amount0Out > 0 || amount1Out > 0, "JAMM: INSUFFICIENT_OUTPUT_AMOUNT");
(uint112 _reserve0, uint112 _reserve1, ) = getReserves();
require(amount0Out < _reserve0 && amount1Out < _reserve1, "JAMM: INSUFFICIENT_LIQUIDITY");
uint balance0;
uint balance1;
{
require(to != token0 && to != token1, "JAMM: INVALID_TO");
if (amount0Out > 0) _safeTransfer(token0, to, amount0Out);
if (amount1Out > 0) _safeTransfer(token1, to, amount1Out);
if (data.length > 0) IJAMMCallee(to).jammCall(msg.sender, amount0Out, amount1Out, data, _referrer);
balance0 = IERC20(token0).balanceOf(address(this));
balance1 = IERC20(token1).balanceOf(address(this));
}
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, "JAMM: INSUFFICIENT_INPUT_AMOUNT");
_collectFee(amount0In, amount1In, _referrer);
// K-value check
{
balance0 = IERC20(token0).balanceOf(address(this));
balance1 = IERC20(token1).balanceOf(address(this));
uint balance0Adjusted = (balance0 * 10000 - (amount0In * fee * 4) / 5);
uint balance1Adjusted = (balance1 * 10000 - (amount1In * fee * 4) / 5);
require(
balance0Adjusted * balance1Adjusted >= uint(_reserve0) * uint(_reserve1) * (10000 ** 2),
"JAMM: K"
);
}
_update(balance0, balance1, _reserve0, _reserve1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
安全机制
重入保护
所有关键函数都使用 lock
修饰符保护,防止重入攻击:
// from JAMMPair.sol
uint private unlocked = 1;
modifier lock() {
require(unlocked == 1, "JAMM: LOCKED");
unlocked = 0;
_;
unlocked = 1;
}
最小流动性锁定
为防止池子被完全清空,首次添加流动性时会永久锁定最小数量的LP代币:
// from JAMMPair.sol
uint public constant MINIMUM_LIQUIDITY = 10**3;
溢出保护
储备量使用 uint112
类型,并在更新时检查溢出,利用了 Solidity 0.8.x 的内置安全特性:
// from JAMMPair.sol
require(balance0 <= type(uint112).max && balance1 <= type(uint112).max, "JAMM: OVERFLOW");
实用函数
Skim函数
用于移除意外转入池子且未被计入储备量的多余代币:
// from JAMMPair.sol
function skim(address to) external lock {
address _token0 = token0;
address _token1 = token1;
_safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)) - reserve0);
_safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)) - reserve1);
}
Sync函数
用于强制同步储备量与合约的实际代币余额,在某些异常情况下可以用来恢复池子状态:
// from JAMMPair.sol
function sync() external lock {
_update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
}
Last updated