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

铸造逻辑

  1. 增加总供应量

  2. 增加目标地址余额

  3. 发射Transfer事件(从零地址)

销毁代币

function _burn(address from, uint value) internal {
    balanceOf[from] = balanceOf[from] - value;
    totalSupply = totalSupply - value;
    emit Transfer(from, address(0), value);
}

销毁逻辑

  1. 减少源地址余额

  2. 减少总供应量

  3. 发射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流程

  1. 检查截止时间

  2. 构建EIP-712消息摘要

  3. 恢复签名者地址

  4. 验证签名者是否为owner

  5. 执行授权操作

  6. 增加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