使用UniswapV2官方文件在hardhat环境下进行部署和使用,并结合核心源码和公式分析

  • XUYU
  • 更新于 2024-09-01 19:12
  • 阅读 1273

使用UniswapV2 官方文件 在hardhat环境下进行部署和使用,并结合核心源码和公式分析。 涉及到的功能有添加流动性,包括首次和再次添加,还有swap交换代币功能。

使用UniswapV2官方文件在hardhat环境下进行部署和使用,并结合核心源码和公式分析

1、新建hardhat文件

1、新建文件

2、vscode打开文件,打开终端

3、终端输入 yarn

4、yarn add hardhat //下载hardhat

5、yarn hardhat //新建hardhat文件

6、选择Create a JavaScript project,全选y

2、加载相应的文件

yarn add @openzeppelin/contracts

uniswap-v2-core文件复制到contracts文件下 <img src="https://img.learnblockchain.cn/attachments/2024/09/lczcgN2n66d449f50a9a0.png" alt="image-20240829171423943" style="zoom:20%;" />

3、创建Router合约

pragma solidity =0.5.16;
import "./libraries/SafeMath.sol";
import "./UniswapV2Pair.sol";
import "hardhat/console.sol";

contract Router {
    using SafeMath for uint;
    address public _pair;
    modifier ensure(uint deadline) {
        require(deadline >= block.timestamp, "UniswapV2Router: EXPIRED");
        _;
    }

    constructor(address pair) public {
        _pair = pair;
    }

    function sortTokens(
        address tokenA,
        address tokenB
    ) internal pure returns (address token0, address token1) {
        require(tokenA != tokenB, "UniswapV2Library: IDENTICAL_ADDRESSES");
        (token0, token1) = tokenA &lt; tokenB
            ? (tokenA, tokenB)
            : (tokenB, tokenA);
        require(token0 != address(0), "UniswapV2Library: ZERO_ADDRESS");
    }

    function getReserves(
        address tokenA,
        address tokenB
    ) internal view returns (uint reserveA, uint reserveB) {
        (address token0, ) = sortTokens(tokenA, tokenB);
        (uint reserve0, uint reserve1, ) = IUniswapV2Pair(_pair).getReserves();
        (reserveA, reserveB) = tokenA == token0
            ? (reserve0, reserve1)
            : (reserve1, reserve0);
    }

    function quote(
        uint amountA,
        uint reserveA,
        uint reserveB
    ) internal pure returns (uint amountB) {
        require(amountA > 0, "UniswapV2Library: INSUFFICIENT_AMOUNT");
        require(
            reserveA > 0 && reserveB > 0,
            "UniswapV2Library: INSUFFICIENT_LIQUIDITY"
        );
        amountB = amountA.mul(reserveB) / reserveA;
    }

    function _addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin
    ) internal view returns (uint amountA, uint amountB) {
        (uint reserveA, uint reserveB) = getReserves(tokenA, tokenB);
        if (reserveA == 0 && reserveB == 0) {
            (amountA, amountB) = (amountADesired, amountBDesired);
        } else {
            uint amountBOptimal = quote(amountADesired, reserveA, reserveB);
            if (amountBOptimal &lt;= amountBDesired) {
                require(
                    amountBOptimal >= amountBMin,
                    "UniswapV2Router: INSUFFICIENT_B_AMOUNT"
                );
                (amountA, amountB) = (amountADesired, amountBOptimal);
            } else {
                uint amountAOptimal = quote(amountBDesired, reserveB, reserveA);
                assert(amountAOptimal &lt;= amountADesired);
                require(
                    amountAOptimal >= amountAMin,
                    "UniswapV2Router: INSUFFICIENT_A_AMOUNT"
                );
                (amountA, amountB) = (amountAOptimal, amountBDesired);
            }
        }
    }

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    )
        external
        ensure(deadline)
        returns (uint amountA, uint amountB, uint liquidity)
    {
        (amountA, amountB) = _addLiquidity(
            tokenA,
            tokenB,
            amountADesired,
            amountBDesired,
            amountAMin,
            amountBMin
        );
        // address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
        IERC20(tokenA).transferFrom(msg.sender, _pair, amountA);
        IERC20(tokenB).transferFrom(msg.sender, _pair, amountB);
        liquidity = IUniswapV2Pair(_pair).mint(to);
    }

    //----------------------------------------swap-------------------------------------------------------
    // 指定一个确切的 输入 金额,并希望获得至少达到某个最小值的输出金额。
    function swapExactTokensForTokens(
        uint amountIn, // 输入金额
        uint amountOutMin, // 用户期望的最少输出金额
        address[] calldata path, // 交易路径
        address to, // 接收代币的地址
        uint deadline // 交易截止时间
    ) external ensure(deadline) returns (uint[] memory amounts) {
        // 获取输出金额数组,已经扣除In的手续费了
        amounts = getAmountsOut(amountIn, path);
        // 检查实际的输出金额是否满足用户期望的最小值
        require(
            amounts[amounts.length - 1] >= amountOutMin,
            "UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT"
        );
        // 将amountIn金额从调用者转入pair0地址,理解为用户支付
        IERC20(path[0]).transferFrom(msg.sender, _pair, amounts[0]); //address token, address from, address to, uint value)
        // 调用内部的 _swap 函数执行交换
        _swap(amounts, path, to);
    }

    function _swap(
        uint[] memory amounts,
        address[] memory path,
        address _to
    ) internal {
        for (uint i; i &lt; path.length - 1; i++) {
            (address input, address output) = (path[i], path[i + 1]);
            (address token0, ) = sortTokens(input, output);
            uint amountOut = amounts[i + 1];
            (uint amount0Out, uint amount1Out) = input == token0
                ? (uint(0), amountOut)
                : (amountOut, uint(0));
            // 判断下一个接收地址,如果不是最后一步,则设置为下一对的地址;否则为终点地址
            address to = i &lt; path.length - 2 ? _pair : _to;
            // 执行交换
            IUniswapV2Pair(_pair).swap(
                amount0Out,
                amount1Out,
                to,
                new bytes(0)
            );
        }
    }

    function getAmountsOut(
        uint amountIn,
        address[] memory path
    ) internal view returns (uint[] memory amounts) {
        require(path.length >= 2, "UniswapV2Library: INVALID_PATH");
        amounts = new uint[](path.length);
        amounts[0] = amountIn;
        for (uint i; i &lt; path.length - 1; i++) {
            (uint reserveIn, uint reserveOut) = getReserves(
                path[i],
                path[i + 1]
            );
            amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
        }
    }

    function getAmountOut(
        uint amountIn,
        uint reserveIn,
        uint reserveOut
    ) internal pure returns (uint amountOut) {
        require(amountIn > 0, "UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT");
        require(
            reserveIn > 0 && reserveOut > 0,
            "UniswapV2Library: INSUFFICIENT_LIQUIDITY"
        );
        uint amountInWithFee = amountIn.mul(997);
        uint numerator = amountInWithFee.mul(reserveOut);
        uint denominator = reserveIn.mul(1000).add(amountInWithFee);
        amountOut = numerator / denominator;
    }
}

4、创建ERC20token合约

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract ERC20token is ERC20, Ownable {
    constructor(
        string memory name,
        string memory symbol
    ) ERC20(name, symbol) Ownable(msg.sender) {}

    function mint(address to, uint256 amount) public onlyOwner {
        // 这里示例铸造1000000个代币(根据ERC20的decimals,默认是18)
        _mint(to, amount * (10 ** uint256(decimals())));
    }
}

5、创建部署js文件

新建script文件夹,然后在其下新建deploy.js文件
const { ethers } = require("hardhat");

// 主部署函数。
async function main() {
  console.log(
    "------------部署、铸造并授权--------------------------------------"
  );
  //从 ethers 提供的 signers 中获取签名者。
  const [owner] = await ethers.getSigners();
  console.log(`使用账户部署合约: ${owner.address}`);

  //初始化uniswap工厂,并部署
  const uniswapFactory = await ethers.getContractFactory("UniswapV2Factory");
  const uniswap = await uniswapFactory.deploy(owner.address);
  const uniswapAddress = await uniswap.getAddress();
  console.log(`Factory 部署在 ${uniswapAddress}`);

  //初始化tokenFactory, 并部署koko、ac币
  const tokenFactory = await ethers.getContractFactory("ERC20token", owner);
  const kokoToken = await tokenFactory.deploy("koko", "KO");
  const acToken = await tokenFactory.deploy("ac", "AC");
  const kokoTokenAddress = await kokoToken.getAddress();
  const acTokenAddress = await acToken.getAddress();
  console.log(`kokoToken 地址 ${kokoTokenAddress}`);
  console.log(`acToken 地址 ${acTokenAddress}`);

  //铸造koko币、ac币
  await kokoToken.mint(owner.address, 2000);
  await acToken.mint(owner.address, 3000);
  let kokoBalance = await kokoToken.balanceOf(owner);
  let acBalance = await acToken.balanceOf(owner);
  console.log(
    `铸造owner koko Balance: ${kokoBalance}, ac Balance: ${acBalance}`
  );

  //调用 uniswapfactory 的 createPair 函数来创造流动池
  const tx1 = await uniswap.createPair(kokoTokenAddress, acTokenAddress);
  await tx1.wait();
  // 获取pair地址
  const pairAddress = await uniswap.getPair(kokoTokenAddress, acTokenAddress);
  console.log(`pair 部署在 ${pairAddress}`);
  //从链上获取pair合约
  const pair = await ethers.getContractAt("UniswapV2Pair", pairAddress);

  //初始化并部署Router合约
  const routerFactory = await ethers.getContractFactory("Router", owner);
  const router = await routerFactory.deploy(pairAddress);
  const routerAddress = await router.getAddress();

  //将100个koko和200个ac代币授权给Router合约
  const approveTx1 = await kokoToken.approve(
    routerAddress,
    ethers.parseEther("1000")
  );
  await approveTx1.wait();
  const approvalTx2 = await acToken.approve(
    routerAddress,
    ethers.parseEther("1000")
  );
  await approvalTx2.wait();
  console.log("授权 1000 个kokoToken和 1000 个acToken给router");

  console.log("\n---------首次添加流动性--------------------------");
  console.log(`前`);
  //查询 LP 流动币数量和 pair 交易对的储备量
  let lpTokenBalance = await pair.balanceOf(owner.address);
  let reserves = await pair.getReserves();
  console.log(`owner LP 代币数量: ${lpTokenBalance.toString()};`);
  console.log(
    `pair 储备量: ${reserves[0].toString()}, ${reserves[1].toString()}`
  );

  //设置添加流动性的代币数量,分别添加 50 个koko币和 100 个ac币
  const token0Amount = ethers.parseEther("100");
  const token1Amount = ethers.parseEther("50");
  console.log(`首次添加ing......  添加 100 个kokoToken 和 50 个acToken, `);
  console.log(`后`);
  //开始添加流动性
  const deadline = Math.floor(Date.now() / 1000) + 10 * 60; //当前时间+10分钟的时间戳
  const addLiquidityTx = await router
    .connect(owner)
    .addLiquidity(
      kokoTokenAddress,
      acTokenAddress,
      token0Amount,
      token1Amount,
      0,
      0,
      owner,
      deadline
    );
  await addLiquidityTx.wait();

  //查询 LP 流动币数量和 pair 交易对的储备量
  lpTokenBalance = await pair.balanceOf(owner.address);
  reserves = await pair.getReserves();
  console.log(`owner LP 代币数量: ${lpTokenBalance.toString()}`);
  console.log(
    `pair 储备量: ${reserves[0].toString()}, ${reserves[1].toString()}`
  );
  let totalSupply = await pair.totalSupply();
  console.log("首次增加流动性后 totalSupply: ", totalSupply);

  console.log(
    "\n--------------------再次添加流动性-----------------------------------"
  );

  const token0Amount2 = ethers.parseEther("80");
  const token1Amount2 = ethers.parseEther("60");
  console.log(` 添加ing...... 添加 80 个kokoToken 和 60 个acToken, `);
  const deadline2 = Math.floor(Date.now() / 1000) + 10 * 60; //当前时间+10分钟的时间戳
  const addLiquidityTx2 = await router
    .connect(owner)
    .addLiquidity(
      kokoTokenAddress,
      acTokenAddress,
      token0Amount2,
      token1Amount2,
      0,
      0,
      owner,
      deadline
    );
  await addLiquidityTx2.wait();

  console.log(`后`);
  //查询 LP 流动币数量和 pair 交易对的储备量
  lpTokenBalance = await pair.balanceOf(owner.address);
  reserves = await pair.getReserves();
  console.log(`owner LP 代币数量: ${lpTokenBalance.toString()};`);
  console.log(
    `pair 储备量: ${reserves[0].toString()}, ${reserves[1].toString()}`
  );
  //查询totalSupply
  totalSupply = await pair.totalSupply();
  console.log("再次增加流动性后 totalSupply: ", totalSupply);

  console.log(
    "\n--------------------swap交换-----------------------------------"
  );
  console.log(`前`);
  //owner swap 前拥有kokoToken和acToken的数量
  kokoBalance = await kokoToken.balanceOf(owner);
  acBalance = await acToken.balanceOf(owner);
  console.log(`owner koko Balance: ${kokoBalance}, ac Balance: ${acBalance}`);
  //pair 对kokoToken和acToken的库存
  reserves = await pair.getReserves();
  console.log(
    `swap前 pair的储备量: ${reserves[1].toString()}, ${reserves[0].toString()}`
  );
  totalSupply = await pair.totalSupply();
  console.log("totalSupply: ", totalSupply);
  const amountIn = ethers.parseEther("100");
  const amountOutMin = ethers.parseEther("20");
  const path = [kokoTokenAddress, acTokenAddress]; //前者是input地址,后者地址是output地址
  console.log("swap ing......");
  let amounts = await router
    .connect(owner)
    .swapExactTokensForTokens(amountIn, amountOutMin, path, owner, deadline);
  console.log(`后`);
  //owner swap 后拥有kokoToken的数量
  kokoBalance = await kokoToken.balanceOf(owner);
  acBalance = await acToken.balanceOf(owner);
  console.log(
    `swap后, owner koko Balance: ${kokoBalance}, ac Balance: ${acBalance}`
  );

  reserves = await pair.getReserves();
  console.log(
    `swap后 pair的储备量: ${reserves[1].toString()}, ${reserves[0].toString()}`
  );
  totalSupply = await pair.totalSupply();
  console.log("swap后 totalSupply: ", totalSupply);
}

// 执行主函数并处理可能的结果。
main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

6、执行部署文件

终端输入:yarn hardhat node

image.png

另外新加终端,输入:yarn hardhat run script/deploy.js

image.png

终端输出结果

image.png

如果想用Uniswap官方的uniswap-v2-periphery代码可参考https://github.com/anirudhmakhana/UniswapV2?source=post_page-----f8f5dd436930--------------------------------

uniswap部署和使用结束,后面是源码分析!!!

Uniswap v2 流动性addLiquidity源码分析,结合部署和使用进行分析 [addLiquidity] Uniswap v2 代币交换swap源码分析,同时结合部署和使用进行分析 [swap]

点赞 1
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
XUYU
XUYU
0xe409...2a81
江湖只有他的大名,没有他的介绍。