{"author":{"address":"0xe409121c12E6d748d29c132BE68552Bdc8162a81","user":"https://learnblockchain.cn/people/22297"},"content":{"body":"# 使用UniswapV2官方文件在hardhat环境下进行部署和使用，并结合核心源码和公式分析\r\n## 1、新建hardhat文件\r\n\r\n1、新建文件\r\n\r\n2、vscode打开文件，打开终端\r\n\r\n3、终端输入 yarn\r\n\r\n4、yarn add hardhat\t//下载hardhat\r\n\r\n5、yarn hardhat\t//新建hardhat文件\r\n\r\n6、选择Create a JavaScript project，全选y\r\n\r\n## 2、加载相应的文件\r\n\r\nyarn add  @openzeppelin/contracts\r\n\r\n将[uniswap-v2-core](https://github.com/Uniswap/v2-core/tree/master/contracts)文件复制到contracts文件下\r\n\u003cimg src=\"https://img.learnblockchain.cn/attachments/2024/09/lczcgN2n66d449f50a9a0.png\" alt=\"image-20240829171423943\" style=\"zoom:20%;\" /\u003e\r\n## 3、创建Router合约\r\n```solidity\r\npragma solidity =0.5.16;\r\nimport \"./libraries/SafeMath.sol\";\r\nimport \"./UniswapV2Pair.sol\";\r\nimport \"hardhat/console.sol\";\r\n\r\ncontract Router {\r\n    using SafeMath for uint;\r\n    address public _pair;\r\n    modifier ensure(uint deadline) {\r\n        require(deadline \u003e= block.timestamp, \"UniswapV2Router: EXPIRED\");\r\n        _;\r\n    }\r\n\r\n    constructor(address pair) public {\r\n        _pair = pair;\r\n    }\r\n\r\n    function sortTokens(\r\n        address tokenA,\r\n        address tokenB\r\n    ) internal pure returns (address token0, address token1) {\r\n        require(tokenA != tokenB, \"UniswapV2Library: IDENTICAL_ADDRESSES\");\r\n        (token0, token1) = tokenA \u003c tokenB\r\n            ? (tokenA, tokenB)\r\n            : (tokenB, tokenA);\r\n        require(token0 != address(0), \"UniswapV2Library: ZERO_ADDRESS\");\r\n    }\r\n\r\n    function getReserves(\r\n        address tokenA,\r\n        address tokenB\r\n    ) internal view returns (uint reserveA, uint reserveB) {\r\n        (address token0, ) = sortTokens(tokenA, tokenB);\r\n        (uint reserve0, uint reserve1, ) = IUniswapV2Pair(_pair).getReserves();\r\n        (reserveA, reserveB) = tokenA == token0\r\n            ? (reserve0, reserve1)\r\n            : (reserve1, reserve0);\r\n    }\r\n\r\n    function quote(\r\n        uint amountA,\r\n        uint reserveA,\r\n        uint reserveB\r\n    ) internal pure returns (uint amountB) {\r\n        require(amountA \u003e 0, \"UniswapV2Library: INSUFFICIENT_AMOUNT\");\r\n        require(\r\n            reserveA \u003e 0 \u0026\u0026 reserveB \u003e 0,\r\n            \"UniswapV2Library: INSUFFICIENT_LIQUIDITY\"\r\n        );\r\n        amountB = amountA.mul(reserveB) / reserveA;\r\n    }\r\n\r\n    function _addLiquidity(\r\n        address tokenA,\r\n        address tokenB,\r\n        uint amountADesired,\r\n        uint amountBDesired,\r\n        uint amountAMin,\r\n        uint amountBMin\r\n    ) internal view returns (uint amountA, uint amountB) {\r\n        (uint reserveA, uint reserveB) = getReserves(tokenA, tokenB);\r\n        if (reserveA == 0 \u0026\u0026 reserveB == 0) {\r\n            (amountA, amountB) = (amountADesired, amountBDesired);\r\n        } else {\r\n            uint amountBOptimal = quote(amountADesired, reserveA, reserveB);\r\n            if (amountBOptimal \u003c= amountBDesired) {\r\n                require(\r\n                    amountBOptimal \u003e= amountBMin,\r\n                    \"UniswapV2Router: INSUFFICIENT_B_AMOUNT\"\r\n                );\r\n                (amountA, amountB) = (amountADesired, amountBOptimal);\r\n            } else {\r\n                uint amountAOptimal = quote(amountBDesired, reserveB, reserveA);\r\n                assert(amountAOptimal \u003c= amountADesired);\r\n                require(\r\n                    amountAOptimal \u003e= amountAMin,\r\n                    \"UniswapV2Router: INSUFFICIENT_A_AMOUNT\"\r\n                );\r\n                (amountA, amountB) = (amountAOptimal, amountBDesired);\r\n            }\r\n        }\r\n    }\r\n\r\n    function addLiquidity(\r\n        address tokenA,\r\n        address tokenB,\r\n        uint amountADesired,\r\n        uint amountBDesired,\r\n        uint amountAMin,\r\n        uint amountBMin,\r\n        address to,\r\n        uint deadline\r\n    )\r\n        external\r\n        ensure(deadline)\r\n        returns (uint amountA, uint amountB, uint liquidity)\r\n    {\r\n        (amountA, amountB) = _addLiquidity(\r\n            tokenA,\r\n            tokenB,\r\n            amountADesired,\r\n            amountBDesired,\r\n            amountAMin,\r\n            amountBMin\r\n        );\r\n        // address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);\r\n        IERC20(tokenA).transferFrom(msg.sender, _pair, amountA);\r\n        IERC20(tokenB).transferFrom(msg.sender, _pair, amountB);\r\n        liquidity = IUniswapV2Pair(_pair).mint(to);\r\n    }\r\n\r\n    //----------------------------------------swap-------------------------------------------------------\r\n    // 指定一个确切的 输入 金额，并希望获得至少达到某个最小值的输出金额。\r\n    function swapExactTokensForTokens(\r\n        uint amountIn, // 输入金额\r\n        uint amountOutMin, // 用户期望的最少输出金额\r\n        address[] calldata path, // 交易路径\r\n        address to, // 接收代币的地址\r\n        uint deadline // 交易截止时间\r\n    ) external ensure(deadline) returns (uint[] memory amounts) {\r\n        // 获取输出金额数组，已经扣除In的手续费了\r\n        amounts = getAmountsOut(amountIn, path);\r\n        // 检查实际的输出金额是否满足用户期望的最小值\r\n        require(\r\n            amounts[amounts.length - 1] \u003e= amountOutMin,\r\n            \"UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT\"\r\n        );\r\n        // 将amountIn金额从调用者转入pair0地址，理解为用户支付\r\n        IERC20(path[0]).transferFrom(msg.sender, _pair, amounts[0]); //address token, address from, address to, uint value)\r\n        // 调用内部的 _swap 函数执行交换\r\n        _swap(amounts, path, to);\r\n    }\r\n\r\n    function _swap(\r\n        uint[] memory amounts,\r\n        address[] memory path,\r\n        address _to\r\n    ) internal {\r\n        for (uint i; i \u003c path.length - 1; i++) {\r\n            (address input, address output) = (path[i], path[i + 1]);\r\n            (address token0, ) = sortTokens(input, output);\r\n            uint amountOut = amounts[i + 1];\r\n            (uint amount0Out, uint amount1Out) = input == token0\r\n                ? (uint(0), amountOut)\r\n                : (amountOut, uint(0));\r\n            // 判断下一个接收地址，如果不是最后一步，则设置为下一对的地址；否则为终点地址\r\n            address to = i \u003c path.length - 2 ? _pair : _to;\r\n            // 执行交换\r\n            IUniswapV2Pair(_pair).swap(\r\n                amount0Out,\r\n                amount1Out,\r\n                to,\r\n                new bytes(0)\r\n            );\r\n        }\r\n    }\r\n\r\n    function getAmountsOut(\r\n        uint amountIn,\r\n        address[] memory path\r\n    ) internal view returns (uint[] memory amounts) {\r\n        require(path.length \u003e= 2, \"UniswapV2Library: INVALID_PATH\");\r\n        amounts = new uint[](path.length);\r\n        amounts[0] = amountIn;\r\n        for (uint i; i \u003c path.length - 1; i++) {\r\n            (uint reserveIn, uint reserveOut) = getReserves(\r\n                path[i],\r\n                path[i + 1]\r\n            );\r\n            amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);\r\n        }\r\n    }\r\n\r\n    function getAmountOut(\r\n        uint amountIn,\r\n        uint reserveIn,\r\n        uint reserveOut\r\n    ) internal pure returns (uint amountOut) {\r\n        require(amountIn \u003e 0, \"UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT\");\r\n        require(\r\n            reserveIn \u003e 0 \u0026\u0026 reserveOut \u003e 0,\r\n            \"UniswapV2Library: INSUFFICIENT_LIQUIDITY\"\r\n        );\r\n        uint amountInWithFee = amountIn.mul(997);\r\n        uint numerator = amountInWithFee.mul(reserveOut);\r\n        uint denominator = reserveIn.mul(1000).add(amountInWithFee);\r\n        amountOut = numerator / denominator;\r\n    }\r\n}\r\n\r\n```\r\n\r\n## 4、创建ERC20token合约\r\n\r\n```solidity\r\n//SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.0;\r\n\r\nimport \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\r\nimport \"@openzeppelin/contracts/access/Ownable.sol\";\r\n\r\ncontract ERC20token is ERC20, Ownable {\r\n    constructor(\r\n        string memory name,\r\n        string memory symbol\r\n    ) ERC20(name, symbol) Ownable(msg.sender) {}\r\n\r\n    function mint(address to, uint256 amount) public onlyOwner {\r\n        // 这里示例铸造1000000个代币（根据ERC20的decimals，默认是18）\r\n        _mint(to, amount * (10 ** uint256(decimals())));\r\n    }\r\n}\r\n\r\n\r\n```\r\n\r\n## 5、创建部署js文件\r\n\r\n##### 新建script文件夹，然后在其下新建deploy.js文件\r\n\r\n```javascript\r\nconst { ethers } = require(\"hardhat\");\r\n\r\n// 主部署函数。\r\nasync function main() {\r\n  console.log(\r\n    \"------------部署、铸造并授权--------------------------------------\"\r\n  );\r\n  //从 ethers 提供的 signers 中获取签名者。\r\n  const [owner] = await ethers.getSigners();\r\n  console.log(`使用账户部署合约: ${owner.address}`);\r\n\r\n  //初始化uniswap工厂，并部署\r\n  const uniswapFactory = await ethers.getContractFactory(\"UniswapV2Factory\");\r\n  const uniswap = await uniswapFactory.deploy(owner.address);\r\n  const uniswapAddress = await uniswap.getAddress();\r\n  console.log(`Factory 部署在 ${uniswapAddress}`);\r\n\r\n  //初始化tokenFactory, 并部署koko、ac币\r\n  const tokenFactory = await ethers.getContractFactory(\"ERC20token\", owner);\r\n  const kokoToken = await tokenFactory.deploy(\"koko\", \"KO\");\r\n  const acToken = await tokenFactory.deploy(\"ac\", \"AC\");\r\n  const kokoTokenAddress = await kokoToken.getAddress();\r\n  const acTokenAddress = await acToken.getAddress();\r\n  console.log(`kokoToken 地址 ${kokoTokenAddress}`);\r\n  console.log(`acToken 地址 ${acTokenAddress}`);\r\n\r\n  //铸造koko币、ac币\r\n  await kokoToken.mint(owner.address, 2000);\r\n  await acToken.mint(owner.address, 3000);\r\n  let kokoBalance = await kokoToken.balanceOf(owner);\r\n  let acBalance = await acToken.balanceOf(owner);\r\n  console.log(\r\n    `铸造owner koko Balance: ${kokoBalance}, ac Balance: ${acBalance}`\r\n  );\r\n\r\n  //调用 uniswapfactory 的 createPair 函数来创造流动池\r\n  const tx1 = await uniswap.createPair(kokoTokenAddress, acTokenAddress);\r\n  await tx1.wait();\r\n  // 获取pair地址\r\n  const pairAddress = await uniswap.getPair(kokoTokenAddress, acTokenAddress);\r\n  console.log(`pair 部署在 ${pairAddress}`);\r\n  //从链上获取pair合约\r\n  const pair = await ethers.getContractAt(\"UniswapV2Pair\", pairAddress);\r\n\r\n  //初始化并部署Router合约\r\n  const routerFactory = await ethers.getContractFactory(\"Router\", owner);\r\n  const router = await routerFactory.deploy(pairAddress);\r\n  const routerAddress = await router.getAddress();\r\n\r\n  //将100个koko和200个ac代币授权给Router合约\r\n  const approveTx1 = await kokoToken.approve(\r\n    routerAddress,\r\n    ethers.parseEther(\"1000\")\r\n  );\r\n  await approveTx1.wait();\r\n  const approvalTx2 = await acToken.approve(\r\n    routerAddress,\r\n    ethers.parseEther(\"1000\")\r\n  );\r\n  await approvalTx2.wait();\r\n  console.log(\"授权 1000 个kokoToken和 1000 个acToken给router\");\r\n\r\n  console.log(\"\\n---------首次添加流动性--------------------------\");\r\n  console.log(`前`);\r\n  //查询 LP 流动币数量和 pair 交易对的储备量\r\n  let lpTokenBalance = await pair.balanceOf(owner.address);\r\n  let reserves = await pair.getReserves();\r\n  console.log(`owner LP 代币数量: ${lpTokenBalance.toString()};`);\r\n  console.log(\r\n    `pair 储备量: ${reserves[0].toString()}, ${reserves[1].toString()}`\r\n  );\r\n\r\n  //设置添加流动性的代币数量，分别添加 50 个koko币和 100 个ac币\r\n  const token0Amount = ethers.parseEther(\"100\");\r\n  const token1Amount = ethers.parseEther(\"50\");\r\n  console.log(`首次添加ing......  添加 100 个kokoToken 和 50 个acToken， `);\r\n  console.log(`后`);\r\n  //开始添加流动性\r\n  const deadline = Math.floor(Date.now() / 1000) + 10 * 60; //当前时间+10分钟的时间戳\r\n  const addLiquidityTx = await router\r\n    .connect(owner)\r\n    .addLiquidity(\r\n      kokoTokenAddress,\r\n      acTokenAddress,\r\n      token0Amount,\r\n      token1Amount,\r\n      0,\r\n      0,\r\n      owner,\r\n      deadline\r\n    );\r\n  await addLiquidityTx.wait();\r\n\r\n  //查询 LP 流动币数量和 pair 交易对的储备量\r\n  lpTokenBalance = await pair.balanceOf(owner.address);\r\n  reserves = await pair.getReserves();\r\n  console.log(`owner LP 代币数量: ${lpTokenBalance.toString()}`);\r\n  console.log(\r\n    `pair 储备量: ${reserves[0].toString()}, ${reserves[1].toString()}`\r\n  );\r\n  let totalSupply = await pair.totalSupply();\r\n  console.log(\"首次增加流动性后 totalSupply: \", totalSupply);\r\n\r\n  console.log(\r\n    \"\\n--------------------再次添加流动性-----------------------------------\"\r\n  );\r\n\r\n  const token0Amount2 = ethers.parseEther(\"80\");\r\n  const token1Amount2 = ethers.parseEther(\"60\");\r\n  console.log(` 添加ing...... 添加 80 个kokoToken 和 60 个acToken, `);\r\n  const deadline2 = Math.floor(Date.now() / 1000) + 10 * 60; //当前时间+10分钟的时间戳\r\n  const addLiquidityTx2 = await router\r\n    .connect(owner)\r\n    .addLiquidity(\r\n      kokoTokenAddress,\r\n      acTokenAddress,\r\n      token0Amount2,\r\n      token1Amount2,\r\n      0,\r\n      0,\r\n      owner,\r\n      deadline\r\n    );\r\n  await addLiquidityTx2.wait();\r\n\r\n  console.log(`后`);\r\n  //查询 LP 流动币数量和 pair 交易对的储备量\r\n  lpTokenBalance = await pair.balanceOf(owner.address);\r\n  reserves = await pair.getReserves();\r\n  console.log(`owner LP 代币数量: ${lpTokenBalance.toString()};`);\r\n  console.log(\r\n    `pair 储备量: ${reserves[0].toString()}, ${reserves[1].toString()}`\r\n  );\r\n  //查询totalSupply\r\n  totalSupply = await pair.totalSupply();\r\n  console.log(\"再次增加流动性后 totalSupply: \", totalSupply);\r\n\r\n  console.log(\r\n    \"\\n--------------------swap交换-----------------------------------\"\r\n  );\r\n  console.log(`前`);\r\n  //owner swap 前拥有kokoToken和acToken的数量\r\n  kokoBalance = await kokoToken.balanceOf(owner);\r\n  acBalance = await acToken.balanceOf(owner);\r\n  console.log(`owner koko Balance: ${kokoBalance}, ac Balance: ${acBalance}`);\r\n  //pair 对kokoToken和acToken的库存\r\n  reserves = await pair.getReserves();\r\n  console.log(\r\n    `swap前 pair的储备量: ${reserves[1].toString()}, ${reserves[0].toString()}`\r\n  );\r\n  totalSupply = await pair.totalSupply();\r\n  console.log(\"totalSupply: \", totalSupply);\r\n  const amountIn = ethers.parseEther(\"100\");\r\n  const amountOutMin = ethers.parseEther(\"20\");\r\n  const path = [kokoTokenAddress, acTokenAddress]; //前者是input地址，后者地址是output地址\r\n  console.log(\"swap ing......\");\r\n  let amounts = await router\r\n    .connect(owner)\r\n    .swapExactTokensForTokens(amountIn, amountOutMin, path, owner, deadline);\r\n  console.log(`后`);\r\n  //owner swap 后拥有kokoToken的数量\r\n  kokoBalance = await kokoToken.balanceOf(owner);\r\n  acBalance = await acToken.balanceOf(owner);\r\n  console.log(\r\n    `swap后, owner koko Balance: ${kokoBalance}, ac Balance: ${acBalance}`\r\n  );\r\n\r\n  reserves = await pair.getReserves();\r\n  console.log(\r\n    `swap后 pair的储备量: ${reserves[1].toString()}, ${reserves[0].toString()}`\r\n  );\r\n  totalSupply = await pair.totalSupply();\r\n  console.log(\"swap后 totalSupply: \", totalSupply);\r\n}\r\n\r\n// 执行主函数并处理可能的结果。\r\nmain()\r\n  .then(() =\u003e process.exit(0))\r\n  .catch((error) =\u003e {\r\n    console.error(error);\r\n    process.exit(1);\r\n  });\r\n\r\n\r\n```\r\n## 6、执行部署文件\r\n#### 终端输入：yarn hardhat node\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/09/JegSPld066d44b8349652.png)\r\n#### 另外新加终端，输入：yarn hardhat run script/deploy.js\r\n\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/09/5uz1Xlf466d44b907fa5e.png)\r\n### 终端输出结果\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/09/UFEAizQt66d44b9e6051d.png)\r\n\r\n如果想用Uniswap官方的uniswap-v2-periphery代码可参考[https://github.com/anirudhmakhana/UniswapV2?source=post_page-----f8f5dd436930--------------------------------](https://github.com/anirudhmakhana/UniswapV2?source=post_page-----f8f5dd436930--------------------------------)\r\n\r\n### uniswap部署和使用结束，后面是源码分析！！！\r\n[Uniswap v2 流动性addLiquidity源码分析，结合部署和使用进行分析 [addLiquidity]](https://blog.csdn.net/weixin_43458715/article/details/141784912?csdn_share_tail=%7B%22type%22:%22blog%22,%22rType%22:%22article%22,%22rId%22:%22141784912%22,%22source%22:%22weixin_43458715%22%7D)\r\n[Uniswap v2 代币交换swap源码分析，同时结合部署和使用进行分析 [swap]](https://blog.csdn.net/weixin_43458715/article/details/141787548?csdn_share_tail=%7B%22type%22:%22blog%22,%22rType%22:%22article%22,%22rId%22:%22141787548%22,%22source%22:%22weixin_43458715%22%7D)","title":"使用UniswapV2官方文件在hardhat环境下进行部署和使用，并结合核心源码和公式分析"},"history":null,"timestamp":1725189123,"version":1}