JAMMFactory 合约

JAMMFactory是JAMM DEX的核心工厂合约,负责创建和管理所有的交易对。它采用CREATE2确定性部署,支持多级费率系统,并内置了推荐人管理功能。Factory合约是整个协议的入口点和控制中心。

合约基本信息

contract JAMMFactory is IJAMMFactory {
    // 合约地址: 0x6b5d54E6F73e96Ca960DBA71D778b98221939aa6 (主网)
    // 合约地址: 0xbddd716a9d6325700d1c29562c819987e5b1f6a8 (测试网)
}

费率常量

支持的费率等级

uint24 public constant FEE_0_5_PERCENT = 50;   // 0.5%
uint24 public constant FEE_1_PERCENT = 100;    // 1.0%
uint24 public constant FEE_2_PERCENT = 200;    // 2.0%
uint24 public constant FEE_3_PERCENT = 300;    // 3.0%

这四个常量定义了JAMM DEX支持的所有费率等级:

  • 基数: 10000 (即100%)

  • 0.5%: 50/10000 = 0.005

  • 1.0%: 100/10000 = 0.01

  • 2.0%: 200/10000 = 0.02

  • 3.0%: 300/10000 = 0.03

状态变量

管理地址

address public override mintTo;        // 协议费用接收地址
address public override feeTo;         // 交易费用接收地址
address public override feeToSetter;   // 费用设置管理员

地址说明

  • mintTo: 接收协议费用(LP代币形式)的地址

  • feeTo: 接收交易费用的地址

  • feeToSetter: 唯一有权限修改上述地址的管理员

交易对注册表

mapping(address => mapping(address => mapping(uint24 => address))) public override getPair;
address[] public override allPairs;

数据结构

  • getPair[tokenA][tokenB][fee]: 三维映射,通过代币地址和费率查找交易对

  • allPairs: 所有交易对地址的数组,用于遍历

推荐人系统

mapping(address => address) public referrer;

记录每个用户(tx.origin)对应的推荐人地址。

初始化代码哈希

bytes32 public constant override INIT_CODE_PAIR_HASH =
    keccak256(type(JAMMPair).creationCode);

这个哈希值用于CREATE2地址计算,确保交易对地址的确定性。实际值在合约部署时确定。

核心函数

构造函数

constructor(address _feeToSetter) {
    feeToSetter = _feeToSetter;
}

部署时设置费用管理员地址,这是唯一的初始化参数。

创建交易对

function createPair(
    address tokenA,
    address tokenB,
    uint24 fee
) external override returns (address pair) {
    require(tokenA != tokenB, "JAMMFactory: IDENTICAL_ADDRESSES");
    require(
        tokenA != address(0) && tokenB != address(0),
        "JAMMFactory: ZERO_ADDRESS"
    );
    (address token0, address token1) = tokenA < tokenB
        ? (tokenA, tokenB)
        : (tokenB, tokenA);
    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"
    );
    
    bytes memory bytecode = type(JAMMPair).creationCode;
    bytes32 salt = keccak256(abi.encodePacked(token0, token1, fee));
    assembly {
        pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
    }
    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);
}

创建流程

  1. 参数验证: 检查代币地址有效性和费率合法性

  2. 代币排序: 确保token0 < token1,保证唯一性

  3. 重复检查: 确保相同参数的交易对不存在

  4. CREATE2部署: 使用确定性地址部署新的JAMMPair合约

  5. 初始化: 调用新合约的initialize函数

  6. 注册: 更新映射表和数组

  7. 事件发射: 发射PairCreated事件

查询函数

function allPairsLength() external view override returns (uint) {
    return allPairs.length;
}

返回已创建的交易对总数。

管理函数

设置协议费用接收地址

function setMintTo(address _mintTo) external override {
    require(msg.sender == feeToSetter, "JAMMFactory: FORBIDDEN");
    mintTo = _mintTo;
}

设置交易费用接收地址

function setFeeTo(address _feeTo) external override {
    require(msg.sender == feeToSetter, "JAMMFactory: FORBIDDEN");
    feeTo = _feeTo;
}

设置费用管理员

function setFeeToSetter(address _feeToSetter) external override {
    require(msg.sender == feeToSetter, "JAMMFactory: FORBIDDEN");
    feeToSetter = _feeToSetter;
}

权限控制:所有管理函数都只能由当前的feeToSetter调用。

推荐人管理

function setReferrer(address _referrer) external override {
    require(
        referrer[tx.origin] == address(0),
        "JAMMFactory: REFERER_EXISTS"
    );
    referrer[tx.origin] = _referrer;
    emit Referrer(tx.origin, _referrer);
}

特点

  • 使用tx.origin而非msg.sender

  • 每个用户只能设置一次推荐人

  • 设置后无法修改

事件系统

PairCreated事件

event PairCreated(
    address indexed token0,
    address indexed token1,
    uint24 fee,
    address pair,
    uint
);

记录新交易对的创建,包含:

  • token0: 排序后的第一个代币地址

  • token1: 排序后的第二个代币地址

  • fee: 费率

  • pair: 新创建的交易对地址

  • 最后一个参数:当前交易对总数

Referrer事件

event Referrer(address indexed sender, address indexed referrer);

记录推荐人关系的建立。

CREATE2地址计算

地址预计算

交易对地址可以通过以下方式预先计算:

function computePairAddress(
    address factory,
    address tokenA,
    address tokenB,
    uint24 fee
) pure returns (address pair) {
    (address token0, address token1) = tokenA < tokenB 
        ? (tokenA, tokenB) 
        : (tokenB, tokenA);
    
    pair = address(uint160(uint256(keccak256(abi.encodePacked(
        hex"ff",
        factory,
        keccak256(abi.encodePacked(token0, token1, fee)),
        INIT_CODE_PAIR_HASH // 使用Factory合约中的实际哈希值
    )))));
}

Salt计算

bytes32 salt = keccak256(abi.encodePacked(token0, token1, fee));

Salt由三个参数组成:

  • token0: 较小的代币地址

  • token1: 较大的代币地址

  • fee: 费率值

使用示例

创建新交易对

const factory = new ethers.Contract(factoryAddress, factoryABI, signer);

// 创建USDC/USDT交易对,使用0.5%费率
const tx = await factory.createPair(
    "0xUSDC_ADDRESS",
    "0xUSDT_ADDRESS", 
    50 // 0.5%
);

const receipt = await tx.wait();
const pairCreatedEvent = receipt.events.find(e => e.event === 'PairCreated');
const pairAddress = pairCreatedEvent.args.pair;

console.log("新交易对地址:", pairAddress);

查询交易对

// 查询特定交易对
const pairAddress = await factory.getPair(tokenA, tokenB, fee);
if (pairAddress === ethers.constants.AddressZero) {
    console.log("交易对不存在");
} else {
    console.log("交易对地址:", pairAddress);
}

// 查询所有交易对
const totalPairs = await factory.allPairsLength();
console.log("交易对总数:", totalPairs.toString());

for (let i = 0; i < totalPairs; i++) {
    const pairAddress = await factory.allPairs(i);
    console.log(`交易对 ${i}:`, pairAddress);
}

设置推荐人

// 检查当前推荐人
const currentReferrer = await factory.referrer(userAddress);
if (currentReferrer === ethers.constants.AddressZero) {
    // 设置推荐人
    const tx = await factory.setReferrer(referrerAddress);
    await tx.wait();
    console.log("推荐人设置成功");
} else {
    console.log("已有推荐人:", currentReferrer);
}

监听事件

// 监听新交易对创建
factory.on("PairCreated", (token0, token1, fee, pair, totalPairs) => {
    console.log("新交易对创建:");
    console.log("- Token0:", token0);
    console.log("- Token1:", token1);
    console.log("- 费率:", fee);
    console.log("- 地址:", pair);
    console.log("- 总数:", totalPairs.toString());
});

// 监听推荐人设置
factory.on("Referrer", (sender, referrer) => {
    console.log(`用户 ${sender} 设置推荐人为 ${referrer}`);
});

安全考虑

访问控制

  1. 管理员权限: 只有feeToSetter可以修改费用相关设置

  2. 推荐人唯一性: 每个用户只能设置一次推荐人

  3. 交易对唯一性: 相同参数的交易对只能创建一次

参数验证

  1. 地址验证: 确保代币地址非零且不相同

  2. 费率验证: 只允许预定义的四种费率

  3. 存在性检查: 防止重复创建相同的交易对

确定性部署

  1. CREATE2: 确保相同参数总是产生相同地址

  2. Salt唯一性: 通过代币地址和费率确保Salt唯一

  3. 初始化代码: 固定的合约字节码确保一致性

Last updated