LP 代币机制
概述
LP代币(Liquidity Provider Token)是JAMM DEX中代表流动性提供者在特定交易对中份额的凭证。每个交易对都有对应的LP代币,基于 JAMMERC20
合约实现,不仅符合ERC-20标准,还支持EIP-2612 Permit功能,为用户提供更好的交易体验。
JAMMERC20合约实现
基本信息
// from JAMMERC20.sol
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;
// ...
}
LP代币特性:
统一名称: 所有交易对的LP代币都叫"JAMM LPs"。
统一符号: "JAMM-LP"。
精度: 18位小数。
唯一性: 每个交易对都是一个独立的LP代币合约实例。
EIP-2612 Permit支持
JAMM DEX的LP代币支持EIP-2612标准,允许用户通过签名进行授权,无需发送额外的 approve
交易。
// from JAMMERC20.sol
bytes32 public DOMAIN_SEPARATOR;
bytes32 public constant PERMIT_TYPEHASH =
0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
mapping(address => uint) public 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)
));
}
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);
}
LP代币的生命周期
铸造(Mint)
当用户向流动性池添加流动性时,会铸造相应的LP代币。该逻辑在 JAMMPair.sol
中实现:
// from JAMMPair.sol
function mint(address to) external lock returns (uint liquidity) {
(uint112 _reserve0, uint112 _reserve1, ) = getReserves();
uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(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);
// ...
}
销毁(Burn)
当用户移除流动性时,会销毁相应的LP代币:
// from JAMMPair.sol
function burn(address to) external lock returns (uint amount0, uint amount1) {
(uint112 _reserve0, uint112 _reserve1, ) = getReserves();
uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(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);
// ...
}
最小流动性机制
永久锁定
// from JAMMPair.sol
uint public constant MINIMUM_LIQUIDITY = 10**3;
// inside mint()
if (_totalSupply == 0) {
liquidity = Math.sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY;
_mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first tokens
}
目的:
防止流动性池被完全清空,这会破坏价格计算。
防止某些类型的恶意操纵攻击。
Permit功能详解
签名授权的优势
传统的ERC-20授权需要两步交易:approve
和 transferFrom
。Permit功能允许用户通过一次离线签名和一次链上交易完成授权和操作,从而节省Gas并改善用户体验。
在Router中的应用
JAMMRouter
合约提供了使用Permit的流动性移除函数:
// from JAMMRouter.sol
function removeLiquidityWithPermit(
address tokenA, address tokenB, uint24 fee, uint liquidity,
uint amountAMin, uint amountBMin, address to, uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external returns (uint amountA, uint amountB) {
// Use permit to approve LP token spending
IJAMMPair(JAMMLibrary.pairFor(factory, tokenA, tokenB, fee)).permit(
msg.sender, address(this), approveMax ? type(uint).max : liquidity, deadline, v, r, s
);
// Then remove liquidity
(amountA, amountB) = removeLiquidity(
tokenA, tokenB, fee, liquidity, amountAMin, amountBMin, to, deadline
);
}
使用示例
查询LP代币信息
const pair = new ethers.Contract(pairAddress, pairABI, provider);
// Get LP token balance
const lpBalance = await pair.balanceOf(userAddress);
console.log("LP Balance:", ethers.utils.formatEther(lpBalance));
// Get total supply of LP tokens
const totalSupply = await pair.totalSupply();
console.log("Total Supply:", ethers.utils.formatEther(totalSupply));
// Calculate user's share of the pool
const userShare = lpBalance.mul(10000).div(totalSupply);
console.log("User Share:", userShare.toNumber() / 100, "%");
使用Permit移除流动性
// Generate permit signature
const nonce = await pair.nonces(signer.address);
const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // 20 minutes
const domain = {
name: 'JAMM LPs',
version: '1',
chainId: await signer.getChainId(),
verifyingContract: pairAddress
};
const types = {
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' }
]
};
const value = {
owner: signer.address,
spender: routerAddress,
value: liquidity,
nonce: nonce,
deadline: deadline
};
const signature = await signer._signTypedData(domain, types, value);
const { v, r, s } = ethers.utils.splitSignature(signature);
// Remove liquidity using the signature
const tx = await router.removeLiquidityWithPermit(
tokenA, tokenB, fee, liquidity,
amountAMin, amountBMin, to, deadline,
false, v, r, s
);
Last updated