在学习UniSwap V2源码的时候遇到一个bug:Pair合约继承了ERC20合约,但是在调用mint的时候,无法mint LP Token。

下面是简要的代码摘要:

Factory.sol

contract SwapFactory is ISwapFactory { address[] public allPairs; mapping(address tokenA => mapping(address tokenB => address pair)) public override getPair; constructor(){ } function allPairsLength() external view returns (uint) { return allPairs.length; } function createPair(address tokenA, address tokenB) external override returns (address pair) { require(tokenA != tokenB, "Identical addresses"); (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); // 对代币地址进行排序,以保证在映射中存储时的一致性 require(token0 != address(0), "Zero address"); require(getPair[token0][token1] == address(0), "Pair exists"); bytes32 salt = keccak256(abi.encodePacked(token0, token1)); assembly { pair := create2(0, add(bytecode, 32), mload(bytecode), salt) } // 调用新创建的流动性池合约的 initialize 函数,以设置代币对 // 如果是内联汇编的方式创建的pair对象,需要进行转换 SwapPair(pair).initialize(token0, token1); getPair[token0][token1] = pair; getPair[token1][token0] = pair; allPairs.push(pair); emit SwapPairCreated(token0, token1, pair); } }

Pair.sol

contract SwapPair is ISwapPair, SwapToken { uint256 public constant MINIMUM_LIQUIDITY = 10**3; address public factory; address public token0; address public token1; uint112 private reserve0; uint112 private reserve1; uint32 private blockTimestampLast; bool private unlock = true; modifier lock { require(unlock, "locked!"); unlock = false; _; unlock = true; } constructor() { factory = msg.sender; } function initialize(address _token0, address _token1) external { require(msg.sender == factory, "Unauthorized"); token0 = _token0; token1 = _token1; } // 返回的是最近一次更新(无论是 mint添加流动性、burn移除流动性,还是swap发生交易)之后的代币储备量 function getReserves() public view override returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) { _reserve0 = reserve0; _reserve1 = reserve1; _blockTimestampLast = blockTimestampLast; } // 更新流动性池中的代币储备(reserves) function _update(uint balance0, uint balance1) private { require(balance0 <= type(uint112).max && balance1 <= type(uint112).max, "Overflow"); // type(uint112) / 1e18 = 5192296858534827 // 更新池中的代币储备量 // balance0 和 balance1 是函数的输入参数,表示当前池中 token0 和 token1 的实际余额 reserve0 = uint112(balance0); reserve1 = uint112(balance1); blockTimestampLast = uint32(block.timestamp % 2**32); // // 将时间戳转换为 32 位整数,以避免溢出 } // 用户向流动性池中提供代币并获得流动性凭证(liquidity tokens) // to:表示流动性凭证的接收者,通常是添加流动性的一方 // lock 修饰符:防止重入攻击,这是一种确保在执行完当前函数之前,不允许再次调用该函数的机制 function mint(address to) external lock override returns (uint liquidity) { // 此时调用 getReserves() 获取的储备量对应于:在当前流动性添加操作mint之前的代币数量,尚未包含当前交易中的新代币注入。 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // 当前合约地址上持有的 token0 和 token1 的余额。等于 流动性提供者新注入的代币数量 加上 现有储备的总和 uint balance0 = IERC20(token0).balanceOf(address(this)); uint balance1 = IERC20(token1).balanceOf(address(this)); // 流动性提供者注入的代币量:通过当前合约代币余额减去储备余额,得到了新增的代币量 uint amount0 = balance0 - _reserve0; uint amount1 = balance1 - _reserve1; uint _totalSupply = totalSupply; // 第一次流动性注入,通过恒定乘积公式计算 sqrt(amount0 * amount1) 计算流动性代币数量 // 初始流动性代币中会永久锁定一部分 (MINIMUM_LIQUIDITY) 作为安全机制,这些代币被铸造给 address(0),确保流动池永远有一部分不会被提走 if (_totalSupply == 0) { liquidity = MathTools.sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY; _mint(address(1), MINIMUM_LIQUIDITY); } else { // 对于后续的流动性提供者,流动性代币按比例分配 // 最终取这两个值的最小值,以确保按比例添加的代币数量是平衡的 liquidity = MathTools.min((amount0 * _totalSupply) / _reserve0, (amount1 * _totalSupply) / _reserve1); } // 用户注入的代币数量过少,不足以铸造出流动性代币 require(liquidity > 0, "Insufficient liquidity"); // 使用 _mint 函数将计算好的流动性代币铸造给流动性提供者的地址 to _mint(to, liquidity); // balance0 和 balance1 是流动池的新代币余额 // _reserve0 和 _reserve1 是旧的储备。该函数会将新的储备值更新为当前的余额,确保储备数据一致性 _update(balance0, balance1); emit Mint(msg.sender, amount0, amount1); } 

SwapToken.sol

contract SwapToken is IERC20 { string public constant name = 'Uniswap V2'; string public constant symbol = 'UNI-V2'; uint8 public constant decimals = 18; uint public totalSupply; mapping(address => uint) public balanceOf; mapping(address => mapping(address => uint)) public allowance; constructor() {} function _mint(address to, uint value) internal { totalSupply = totalSupply + value; balanceOf[to] = balanceOf[to] + value; emit Transfer(address(0), to, value); } function _burn(address from, uint value) internal { balanceOf[from] = balanceOf[from] - value; totalSupply = totalSupply - value; emit Transfer(from, address(0), value); } 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) { if (allowance[from][msg.sender] > 0) { allowance[from][msg.sender] = allowance[from][msg.sender] - value; } _transfer(from, to, value); return true; } }

Router.sol

contract SwapRouter is ISwapRouter { address public immutable override factory; modifier ensure(uint deadline) { require(deadline >= block.timestamp, "Expired"); _; } modifier hasExpired(uint256 _deadline) { require(block.timestamp <= _deadline, "expired"); _; } constructor(address _factory) { factory = _factory; } // 基于 tokenA 和 tokenB 的储量 reserveA 和 reserveB 以及用户意向的 amountADesired 和 amountBDesired // 计算出满足 Δx / Δy = x / y,即满足比例关系的 tokenA 和 tokenB 的实际数量 amountA 和 amountB // 因为用户意向的 amountADesired 和 amountBDesired 并不一定满足 x / y 的比例关系 function _addLiquidity( address tokenA, address tokenB, uint amountADesired, // 用户希望添加的 tokenA 数量 uint amountBDesired, // 用户希望添加的 tokenB 数量 uint amountAMin, // 最小接受的 tokenA 数量(保护机制) uint amountBMin // 最小接受的 tokenB 数量(保护机制) ) private returns (uint amountA, uint amountB) { if (ISwapFactory(factory).getPair(tokenA, tokenB) == address(0)) { ISwapFactory(factory).createPair(tokenA, tokenB); } // (uint reserveA, uint reserveB,) = ISwapPair(ISwapFactory(factory).getPair(tokenA, tokenB)).getReserves(); (uint256 reserveA, uint256 reserveB) = SwapTools.getReserves(factory, tokenA, tokenB); if (reserveA == 0 && reserveB == 0) { (amountA, amountB) = (amountADesired, amountBDesired); } else { // 根据当前储备量,计算最优的 amountBOptimal 和 amountAOptimal,以确保添加流动性时保持比例 // 根据用户想要传入的 amountA,计算此时按照 x / y = Δx / Δy 比例算出的 amountB 的数量 // 在首次计算时,amountA 尚未确定,必须先用 amountADesired uint amountBOptimal = SwapTools.quote(amountADesired, reserveA, reserveB); if (amountBOptimal <= amountBDesired) { // 如果计算出的 amountBOptimal 小于或等于用户希望添加的 amountBDesired。 // 也就是说用户提供的 tokenB 的数量是足够的,能够完全消耗掉 tokenA require(amountBOptimal >= amountBMin, "Insufficient B amount"); // 检查是否大于等于 amountBMin,确保用户可以接受的最小数量 (amountA, amountB) = (amountADesired, amountBOptimal); // tokenA 完全被消耗,但是 tokenB 数量过多,只消耗了 amountBOptimal } else { // 如果 amountBOptimal 超过了用户的 amountBDesired // 也就是说此时提供的 tokenB 的数量不够,tokenA的数量过多 uint amountAOptimal = SwapTools.quote(amountBDesired, reserveB, reserveA); assert(amountAOptimal <= amountADesired); require(amountAOptimal >= amountAMin, "Insufficient A amount"); (amountA, amountB) = (amountAOptimal, amountBDesired); // tokenA 数量过多,只消耗了 amountAOptimal,tokenB 完全被消耗了 } } } // 下面这个函数是用户进行调用的,因此 msg.sender = 用户 function addLiquidity( address tokenA, address tokenB, uint amountADesired, uint amountBDesired, uint amountAMin, uint amountBMin, address to, uint deadline ) external override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) { // 计算出 满足 Δx / Δy = x / y 关系的 tokenA 和 tokenB 的数量 (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin); address pair = ISwapFactory(factory).getPair(tokenA, tokenB); // 把对应数量的 token 转到 pair 合约 IERC20(tokenA).transferFrom(msg.sender, pair, amountA); IERC20(tokenB).transferFrom(msg.sender, pair, amountB); // 获得 流动性LP Token liquidity = ISwapPair(pair).mint(to); } }

按照正常的调用逻辑:用户调用Router中的 addLiquidity ,然后Pair合约对象给to地址发送LP Token。

下面是在 remix 中调用 addLiquidity 后面的日志信息:

from 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 to SwapRouter.addLiquidity(address,address,uint256,uint256,uint256,uint256,address,uint256) 0x870f80823772b3Ef098844A852dDfBeec1061776 gas 3251666 gas transaction cost 2787735 gas execution cost 2784491 gas input 0xe8e...9d160 output 0x0000000000000000000000000000000000000000000000008ac7230489e800000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000002be2aac7077d57db decoded input { "address tokenA": "0x51A0dfea63768e7827e9AAA532314910648F3eD2", "address tokenB": "0xd20977056F58b3Fb3533b7C2b9028a19Fbcd2358", "uint256 amountADesired": "10000000000000000000", "uint256 amountBDesired": "1000000000000000000", "uint256 amountAMin": "9950000000000000000", "uint256 amountBMin": "995000000000000000", "address to": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "uint256 deadline": "1799999840" } decoded output { "0": "uint256: amountA 10000000000000000000", "1": "uint256: amountB 1000000000000000000", "2": "uint256: liquidity 3162277660168378331" } logs [ { "from": "0x7Dd7622649035B7460DF4c94D6dDE5c6Abc84e95", "topic": "0x43751f02e1e73fa4388fefe59462d9de97fd7b221544e34cc60fca29faf9d7df", "event": "SwapPairCreated", "args": { "0": "0x51A0dfea63768e7827e9AAA532314910648F3eD2", "1": "0xd20977056F58b3Fb3533b7C2b9028a19Fbcd2358", "2": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF", "token0": "0x51A0dfea63768e7827e9AAA532314910648F3eD2", "token1": "0xd20977056F58b3Fb3533b7C2b9028a19Fbcd2358", "pair": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF" } }, { "from": "0x51A0dfea63768e7827e9AAA532314910648F3eD2", "topic": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "event": "Transfer", "args": { "0": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "1": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF", "2": "10000000000000000000", "from": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "to": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF", // pair "value": "10000000000000000000" } }, { "from": "0xd20977056F58b3Fb3533b7C2b9028a19Fbcd2358", "topic": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "event": "Transfer", "args": { "0": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "1": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF", "2": "1000000000000000000", "from": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "to": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF", // pair "value": "1000000000000000000" } }, { "from": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF", "topic": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "event": "Transfer", "args": { "0": "0x0000000000000000000000000000000000000000", "1": "0x0000000000000000000000000000000000000001", "2": "1000", "from": "0x0000000000000000000000000000000000000000", "to": "0x0000000000000000000000000000000000000001", "value": "1000" } }, { "from": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF", "topic": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "event": "Transfer", "args": { "0": "0x0000000000000000000000000000000000000000", "1": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "2": "3162277660168378331", "from": "0x0000000000000000000000000000000000000000", "to": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "value": "3162277660168378331" } }, { "from": "0x3014449aAcE4A6F8c18B9c1c0B40D3a3Db2868dF", "topic": "0x4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f", "event": "Mint", "args": { "0": "0x870f80823772b3Ef098844A852dDfBeec1061776", "1": "10000000000000000000", "2": "1000000000000000000", "sender": "0x870f80823772b3Ef098844A852dDfBeec1061776", // router "amount0": "10000000000000000000", "amount1": "1000000000000000000" } } ]

很明显看到,是触发了Mint的事件的。 但是在remix控制面版中,查看相关信息如下: 感觉所有的状态都没有设置成功。 9a64a347f8d2933f7b07a8c5cdcb817.png

请先 登录 后评论

1 个回答

xyl2004
请先 登录 后评论
  • 1 关注
  • 0 收藏,2149 浏览
  • 镜中影 提出于 2025-04-22 15:51