JAMMPair 合约
JAMMPair是JAMM DEX的核心交易对合约,实现了具体的AMM流动性池功能。每个交易对都是一个独立的JAMMPair合约实例,负责管理两种代币的储备、执行交换逻辑、处理流动性操作,并维护价格预言机数据。
合约继承结构
contract JAMMPair is JAMMERC20 {
using UQ112x112 for uint224;
// ...
}
JAMMPair继承自JAMMERC20,这意味着每个交易对本身就是一个ERC-20代币(LP代币)。
核心常量
uint public constant MINIMUM_LIQUIDITY = 10 ** 3; // 1000
bytes4 private constant SELECTOR = bytes4(keccak256(bytes("transfer(address,uint256)")));
MINIMUM_LIQUIDITY
: 永久锁定的最小流动性数量SELECTOR
: ERC-20 transfer函数的选择器,用于安全转账
状态变量
基本信息
address public factory; // Factory合约地址
address public token0; // 第一个代币地址(较小地址)
address public token1; // 第二个代币地址(较大地址)
uint24 public fee; // 交易费率
储备量管理
uint112 private reserve0; // token0的储备量
uint112 private reserve1; // token1的储备量
uint32 private blockTimestampLast; // 最后更新的区块时间戳
存储优化:三个变量打包在一个存储槽中:
uint112 + uint112 + uint32 = 256 bits
价格预言机
uint public price0CumulativeLast; // token0的累积价格
uint public price1CumulativeLast; // token1的累积价格
uint public kLast; // 上次的k值 (reserve0 * reserve1)
重入保护
uint private unlocked = 1;
modifier lock() {
require(unlocked == 1, "JAMM: LOCKED");
unlocked = 0;
_;
unlocked = 1;
}
初始化
构造函数
constructor() {
factory = msg.sender;
}
构造函数只设置factory地址,实际初始化通过initialize
函数完成。
初始化函数
function initialize(
address _token0,
address _token1,
uint24 _fee
) external {
require(msg.sender == factory, "JAMM: FORBIDDEN");
token0 = _token0;
token1 = _token1;
fee = _fee;
}
安全性:只有Factory合约可以调用初始化函数。
核心功能
储备量查询
function getReserves()
public
view
returns (
uint112 _reserve0,
uint112 _reserve1,
uint32 _blockTimestampLast
)
{
_reserve0 = reserve0;
_reserve1 = reserve1;
_blockTimestampLast = blockTimestampLast;
}
这是最常用的查询函数,返回当前的储备量和最后更新时间。
储备量更新
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) {
// 更新累积价格
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);
}
更新逻辑:
检查余额不会溢出uint112
计算时间间隔
更新累积价格(如果有时间间隔且储备量非零)
更新储备量和时间戳
发射Sync事件
流动性管理
添加流动性(Mint)
function mint(address to) external lock returns (uint liquidity) {
(uint112 _reserve0, uint112 _reserve1, ) = getReserves();
uint balance0 = JAMMERC20(token0).balanceOf(address(this));
uint balance1 = JAMMERC20(token1).balanceOf(address(this));
uint amount0 = balance0 - _reserve0;
uint amount1 = balance1 - _reserve1;
bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply;
if (_totalSupply == 0) {
liquidity = Math.sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY;
_mint(address(0), MINIMUM_LIQUIDITY); // 永久锁定
} else {
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代币数量
铸造LP代币给指定地址
更新储备量
更新k值(如果启用协议费用)
发射Mint事件
移除流动性(Burn)
function burn(
address to
) external lock returns (uint amount0, uint amount1) {
(uint112 _reserve0, uint112 _reserve1, ) = getReserves();
address _token0 = token0;
address _token1 = token1;
uint balance0 = JAMMERC20(_token0).balanceOf(address(this));
uint balance1 = JAMMERC20(_token1).balanceOf(address(this));
uint liquidity = balanceOf[address(this)];
bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply;
amount0 = (liquidity * balance0) / _totalSupply;
amount1 = (liquidity * balance1) / _totalSupply;
require(
amount0 > 0 && amount1 > 0,
"JAMM: INSUFFICIENT_LIQUIDITY_BURNED"
);
_burn(address(this), liquidity);
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
balance0 = JAMMERC20(_token0).balanceOf(address(this));
balance1 = JAMMERC20(_token1).balanceOf(address(this));
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0) * uint(reserve1);
emit Burn(msg.sender, amount0, amount1, to);
}
销毁流程:
获取当前状态
处理协议费用
按比例计算应返还的代币数量
销毁LP代币
转账代币给指定地址
更新储备量
发射Burn事件
代币交换
主交换函数
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 = JAMMERC20(token0).balanceOf(address(this));
balance1 = JAMMERC20(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);
{
balance0 = JAMMERC20(token0).balanceOf(address(this));
balance1 = JAMMERC20(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);
}
交换流程:
验证输出数量和流动性充足性
乐观转账(先转出代币)
支持闪电贷回调
计算实际输入数量
收取交易费用
验证恒定乘积公式
更新储备量
发射Swap事件
费用收取
function _collectFee(
uint amount0In,
uint amount1In,
address _referrer
) private {
address referrer = IJAMMFactory(factory).referrer(tx.origin);
address feeTo = IJAMMFactory(factory).feeTo();
if (_referrer == address(0) && referrer == address(0)) {
// 无推荐人
_safeTransferFee(token0, feeTo, (amount0In * fee) / 50000);
_safeTransferFee(token1, feeTo, (amount1In * fee) / 50000);
} else {
// 有推荐人
_safeTransferFee(token0, feeTo, (amount0In * fee) / 100000);
_safeTransferFee(token1, feeTo, (amount1In * fee) / 100000);
if (referrer != address(0)) {
_safeTransferFee(token0, referrer, (amount0In * fee) / 100000);
_safeTransferFee(token1, referrer, (amount1In * fee) / 100000);
} else {
IJAMMFactory(factory).setReferrer(_referrer);
_safeTransferFee(token0, _referrer, (amount0In * fee) / 100000);
_safeTransferFee(token1, _referrer, (amount1In * fee) / 100000);
}
}
}
费用分配逻辑:
无推荐人: 协议收取
fee/50000
的费用有推荐人: 协议和推荐人各收取
fee/100000
的费用
协议费用铸造
function _mintFee(
uint112 _reserve0,
uint112 _reserve1
) private returns (bool feeOn) {
address mintTo = IJAMMFactory(factory).mintTo();
feeOn = mintTo != address(0);
uint _kLast = kLast;
if (feeOn) {
if (_kLast != 0) {
uint rootK = Math.sqrt(uint(_reserve0) * uint(_reserve1));
uint rootKLast = Math.sqrt(_kLast);
if (rootK > rootKLast) {
uint numerator = totalSupply * (rootK - rootKLast) * 8;
uint denominator = rootK * 17 + rootKLast * 8;
uint liquidity = numerator / denominator;
if (liquidity > 0) _mint(mintTo, liquidity);
}
}
} else if (_kLast != 0) {
kLast = 0;
}
}
协议费用计算:
基于k值的增长计算协议费用
公式:
liquidity = totalSupply * (√k - √kLast) * 8 / (√k * 17 + √kLast * 8)
安全转账
安全转账函数
function _safeTransfer(address token, address to, uint value) private {
if (value == 0) {
return;
}
(bool success, bytes memory data) = token.call(
abi.encodeWithSelector(SELECTOR, to, value)
);
require(
success && (data.length == 0 || abi.decode(data, (bool))),
"JAMM: TRANSFER_FAILED"
);
}
费用转账函数
function _safeTransferFee(address token, address to, uint value) private {
_safeTransfer(token, to, value);
if (value > 0) {
emit Fee(tx.origin, to, token, value);
}
}
特点:
处理不规范的ERC-20代币
零值转账直接返回
费用转账会发射Fee事件
实用函数
Skim函数
function skim(address to) external lock {
address _token0 = token0;
address _token1 = token1;
_safeTransfer(
_token0,
to,
JAMMERC20(_token0).balanceOf(address(this)) - reserve0
);
_safeTransfer(
_token1,
to,
JAMMERC20(_token1).balanceOf(address(this)) - reserve1
);
}
用途:移除超出储备量的多余代币余额。
Sync函数
function sync() external lock {
_update(
JAMMERC20(token0).balanceOf(address(this)),
JAMMERC20(token1).balanceOf(address(this)),
reserve0,
reserve1
);
}
用途:强制同步储备量与实际余额。
事件系统
核心事件
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(address indexed sender, uint amount0In, uint amount1In, uint amount0Out, uint amount1Out, address indexed to);
event Sync(uint112 reserve0, uint112 reserve1);
event Fee(address indexed sender, address indexed referrer, address token, uint amount);
使用示例
查询交易对信息
const pair = new ethers.Contract(pairAddress, pairABI, provider);
// 获取基本信息
const token0 = await pair.token0();
const token1 = await pair.token1();
const fee = await pair.fee();
console.log("Token0:", token0);
console.log("Token1:", token1);
console.log("费率:", fee);
// 获取储备量
const reserves = await pair.getReserves();
console.log("Reserve0:", ethers.utils.formatEther(reserves.reserve0));
console.log("Reserve1:", ethers.utils.formatEther(reserves.reserve1));
console.log("最后更新:", new Date(reserves.blockTimestampLast * 1000));
监听交换事件
pair.on("Swap", (sender, amount0In, amount1In, amount0Out, amount1Out, to) => {
console.log("交换事件:");
console.log("- 发送者:", sender);
console.log("- 输入0:", ethers.utils.formatEther(amount0In));
console.log("- 输入1:", ethers.utils.formatEther(amount1In));
console.log("- 输出0:", ethers.utils.formatEther(amount0Out));
console.log("- 输出1:", ethers.utils.formatEther(amount1Out));
console.log("- 接收者:", to);
});
监听费用事件
pair.on("Fee", (sender, referrer, token, amount) => {
console.log("费用事件:");
console.log("- 交易者:", sender);
console.log("- 推荐人:", referrer);
console.log("- 代币:", token);
console.log("- 数量:", ethers.utils.formatEther(amount));
});
Last updated