使用UniswapV2 官方文件 在hardhat环境下进行部署和使用,并结合核心源码和公式分析。 涉及到的功能有添加流动性,包括首次和再次添加,还有swap交换代币功能。
1、新建文件
2、vscode打开文件,打开终端
3、终端输入 yarn
4、yarn add hardhat //下载hardhat
5、yarn hardhat //新建hardhat文件
6、选择Create a JavaScript project,全选y
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%;" />
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 < 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 <= amountBDesired) {
require(
amountBOptimal >= amountBMin,
"UniswapV2Router: INSUFFICIENT_B_AMOUNT"
);
(amountA, amountB) = (amountADesired, amountBOptimal);
} else {
uint amountAOptimal = quote(amountBDesired, reserveB, reserveA);
assert(amountAOptimal <= 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 < 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 < 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 < 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;
}
}
//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())));
}
}
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);
});
如果想用Uniswap官方的uniswap-v2-periphery代码可参考https://github.com/anirudhmakhana/UniswapV2?source=post_page-----f8f5dd436930--------------------------------
Uniswap v2 流动性addLiquidity源码分析,结合部署和使用进行分析 [addLiquidity] Uniswap v2 代币交换swap源码分析,同时结合部署和使用进行分析 [swap]
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!