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);
}

更新逻辑

  1. 检查余额不会溢出uint112

  2. 计算时间间隔

  3. 更新累积价格(如果有时间间隔且储备量非零)

  4. 更新储备量和时间戳

  5. 发射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);
}

铸造流程

  1. 获取当前储备量和余额

  2. 计算新增的代币数量

  3. 处理协议费用

  4. 计算应铸造的LP代币数量

  5. 铸造LP代币给指定地址

  6. 更新储备量

  7. 更新k值(如果启用协议费用)

  8. 发射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);
}

销毁流程

  1. 获取当前状态

  2. 处理协议费用

  3. 按比例计算应返还的代币数量

  4. 销毁LP代币

  5. 转账代币给指定地址

  6. 更新储备量

  7. 发射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);
}

交换流程

  1. 验证输出数量和流动性充足性

  2. 乐观转账(先转出代币)

  3. 支持闪电贷回调

  4. 计算实际输入数量

  5. 收取交易费用

  6. 验证恒定乘积公式

  7. 更新储备量

  8. 发射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