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);
}
创建流程:
参数验证: 检查代币地址有效性和费率合法性
代币排序: 确保token0 < token1,保证唯一性
重复检查: 确保相同参数的交易对不存在
CREATE2部署: 使用确定性地址部署新的JAMMPair合约
初始化: 调用新合约的initialize函数
注册: 更新映射表和数组
事件发射: 发射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}`);
});
安全考虑
访问控制
管理员权限: 只有feeToSetter可以修改费用相关设置
推荐人唯一性: 每个用户只能设置一次推荐人
交易对唯一性: 相同参数的交易对只能创建一次
参数验证
地址验证: 确保代币地址非零且不相同
费率验证: 只允许预定义的四种费率
存在性检查: 防止重复创建相同的交易对
确定性部署
CREATE2: 确保相同参数总是产生相同地址
Salt唯一性: 通过代币地址和费率确保Salt唯一
初始化代码: 固定的合约字节码确保一致性
Last updated