JAMMERC20 合约
JAMMERC20是JAMM DEX中LP代币的标准实现,它不仅完全符合ERC-20标准,还支持EIP-2612 Permit功能。每个JAMMPair合约都继承自JAMMERC20,使得流动性代币具备了完整的代币功能和现代化的授权机制。
合约基本信息
contract JAMMERC20 is IJAMMERC20 {
string public constant name = "JAMM LPs";
string public constant symbol = "JAMM-LP";
uint8 public constant decimals = 18;
uint public totalSupply;
mapping(address => uint) public balanceOf;
mapping(address => mapping(address => uint)) public allowance;
}
代币信息:
名称: "JAMM LPs" (所有交易对统一名称)
符号: "JAMM-LP" (所有交易对统一符号)
精度: 18位小数
供应量: 动态变化,根据流动性添加/移除
EIP-2612 Permit支持
Permit相关状态变量
bytes32 public DOMAIN_SEPARATOR;
bytes32 public constant PERMIT_TYPEHASH =
0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
mapping(address => uint) public nonces;
变量说明:
DOMAIN_SEPARATOR
: EIP-712域分隔符,用于签名验证PERMIT_TYPEHASH
: Permit消息类型的哈希值nonces
: 每个地址的随机数,防止重放攻击
域分隔符初始化
constructor() {
uint chainId;
assembly {
chainId := chainid()
}
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
),
keccak256(bytes(name)),
keccak256(bytes("1")),
chainId,
address(this)
)
);
}
域分隔符构成:
域类型哈希
代币名称哈希
版本号哈希 ("1")
链ID
合约地址
核心ERC-20功能
内部函数
铸造代币
function _mint(address to, uint value) internal {
totalSupply = totalSupply + value;
balanceOf[to] = balanceOf[to] + value;
emit Transfer(address(0), to, value);
}
铸造逻辑:
增加总供应量
增加目标地址余额
发射Transfer事件(从零地址)
销毁代币
function _burn(address from, uint value) internal {
balanceOf[from] = balanceOf[from] - value;
totalSupply = totalSupply - value;
emit Transfer(from, address(0), value);
}
销毁逻辑:
减少源地址余额
减少总供应量
发射Transfer事件(到零地址)
内部授权
function _approve(address owner, address spender, uint value) private {
allowance[owner][spender] = value;
emit Approval(owner, spender, value);
}
内部转账
function _transfer(address from, address to, uint value) private {
balanceOf[from] = balanceOf[from] - value;
balanceOf[to] = balanceOf[to] + value;
emit Transfer(from, to, value);
}
外部函数
授权
function approve(address spender, uint value) external returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
转账
function transfer(address to, uint value) external returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
授权转账
function transferFrom(
address from,
address to,
uint value
) external returns (bool) {
uint currentAllowance = allowance[from][msg.sender];
require(currentAllowance >= value, "JAMM: INSUFFICIENT_ALLOWANCE");
if (currentAllowance != type(uint).max) {
allowance[from][msg.sender] = currentAllowance - value;
}
_transfer(from, to, value);
return true;
}
授权转账特性:
检查授权额度是否足够
如果授权额度不是最大值,则减少授权额度
最大值授权不会减少(节省Gas)
Permit功能详解
Permit函数
function permit(
address owner,
address spender,
uint value,
uint deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
require(deadline >= block.timestamp, "JAMM: EXPIRED");
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
keccak256(
abi.encode(
PERMIT_TYPEHASH,
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(
recoveredAddress != address(0) && recoveredAddress == owner,
"JAMM: INVALID_SIGNATURE"
);
_approve(owner, spender, value);
}
Permit流程:
检查截止时间
构建EIP-712消息摘要
恢复签名者地址
验证签名者是否为owner
执行授权操作
增加nonce防止重放
消息结构
Permit消息的结构化数据:
const types = {
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' }
]
};
安全特性
溢出保护
使用Solidity 0.8.21的内置溢出保护:
加法溢出会自动回滚
减法下溢会自动回滚
无需额外的SafeMath库
重放攻击防护
mapping(address => uint) public nonces;
// 在permit函数中
nonces[owner]++
每次使用permit后,nonce会自动增加,防止签名被重复使用。
签名验证
address recoveredAddress = ecrecover(digest, v, r, s);
require(
recoveredAddress != address(0) && recoveredAddress == owner,
"JAMM: INVALID_SIGNATURE"
);
严格验证签名的有效性和签名者身份。
使用示例
基本ERC-20操作
const lpToken = new ethers.Contract(pairAddress, erc20ABI, signer);
// 查询余额
const balance = await lpToken.balanceOf(userAddress);
console.log("LP余额:", ethers.utils.formatEther(balance));
// 查询总供应量
const totalSupply = await lpToken.totalSupply();
console.log("总供应量:", ethers.utils.formatEther(totalSupply));
// 转账
const transferTx = await lpToken.transfer(recipientAddress, amount);
await transferTx.wait();
// 授权
const approveTx = await lpToken.approve(spenderAddress, amount);
await approveTx.wait();
// 授权转账
const transferFromTx = await lpToken.transferFrom(ownerAddress, recipientAddress, amount);
await transferFromTx.wait();
Permit签名生成
// 生成Permit签名
async function generatePermitSignature(
lpTokenAddress,
owner,
spender,
value,
deadline,
signer
) {
const lpToken = new ethers.Contract(lpTokenAddress, erc20ABI, provider);
// 获取当前nonce
const nonce = await lpToken.nonces(owner);
// 获取链ID
const chainId = await signer.getChainId();
// 构建域分隔符
const domain = {
name: 'JAMM LPs',
version: '1',
chainId: chainId,
verifyingContract: lpTokenAddress
};
// 构建消息类型
const types = {
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' }
]
};
// 构建消息值
const message = {
owner: owner,
spender: spender,
value: value,
nonce: nonce,
deadline: deadline
};
// 生成签名
const signature = await signer._signTypedData(domain, types, message);
const { v, r, s } = ethers.utils.splitSignature(signature);
return { v, r, s, nonce, deadline };
}
使用Permit授权
// 使用Permit进行授权
async function permitApprove(lpTokenAddress, spender, value, signer) {
const lpToken = new ethers.Contract(lpTokenAddress, erc20ABI, signer);
const owner = await signer.getAddress();
const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // 20分钟后过期
// 生成签名
const { v, r, s } = await generatePermitSignature(
lpTokenAddress,
owner,
spender,
value,
deadline,
signer
);
// 调用permit函数
const tx = await lpToken.permit(owner, spender, value, deadline, v, r, s);
await tx.wait();
console.log("Permit授权成功");
}
在Router中使用Permit
// 使用Permit移除流动性
async function removeLiquidityWithPermit(
tokenA,
tokenB,
fee,
liquidity,
amountAMin,
amountBMin,
to,
signer
) {
const router = new ethers.Contract(routerAddress, routerABI, signer);
const deadline = Math.floor(Date.now() / 1000) + 60 * 20;
// 计算交易对地址
const pairAddress = await factory.getPair(tokenA, tokenB, fee);
// 生成Permit签名
const { v, r, s } = await generatePermitSignature(
pairAddress,
await signer.getAddress(),
routerAddress,
liquidity,
deadline,
signer
);
// 使用Permit移除流动性
const tx = await router.removeLiquidityWithPermit(
tokenA,
tokenB,
fee,
liquidity,
amountAMin,
amountBMin,
to,
deadline,
false, // approveMax
v,
r,
s
);
await tx.wait();
console.log("流动性移除成功");
}
事件监听
监听Transfer事件
const lpToken = new ethers.Contract(pairAddress, erc20ABI, provider);
// 监听所有转账
lpToken.on("Transfer", (from, to, value) => {
if (from === ethers.constants.AddressZero) {
console.log("LP代币铸造:", ethers.utils.formatEther(value));
} else if (to === ethers.constants.AddressZero) {
console.log("LP代币销毁:", ethers.utils.formatEther(value));
} else {
console.log("LP代币转账:", {
from: from,
to: to,
value: ethers.utils.formatEther(value)
});
}
});
监听Approval事件
lpToken.on("Approval", (owner, spender, value) => {
console.log("授权事件:", {
owner: owner,
spender: spender,
value: ethers.utils.formatEther(value)
});
});
Last updated