在学习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

请先 登录 后评论
  • 0 关注
  • 0 收藏,98 浏览
  • 镜中影 提出于 1天前