利用Uniswap v2 简单实现和理解闪电贷SwapLoan,附源码分析 [ 1 ]
闪电贷源码分析链接
超详细闪电贷源码分析结合例子
https://blog.csdn.net/weixin_43458715/article/details/141994991
一、套利实现操作流程
1、yarn add @openzeppelin/contracts
2、将uniswap-v2-core文件复制到根目录的contracts文件下
文档结构如下:
3、将UniswapV2Library.sol文件复制到uniswap-v2-core
4、在uniswap-v2-core文件下创建UniswapRouter.sol
//SPDX-License-Identifier: MIT
pragma solidity =0.5.16;
import "./interfaces/IUniswapRouter.sol";
import "./libraries/SafeMath.sol";
import "./UniswapV2Library.sol";
import "./UniswapV2Pair.sol";
import "hardhat/console.sol";
contract UniswapRouter is IUniswapRouter {
using SafeMath for uint;
address public factory;
address public WETH;
address public getpair;
modifier ensure(uint deadline) {
require(deadline >= block.timestamp, "UniswapV2Router: EXPIRED");
_;
}
constructor(address _factory, address _WETH) public {
factory = _factory;
WETH = _WETH;
}
function getPair(
address tokenA,
address tokenB
) public view returns (address) {
(address token0, address token1) = UniswapV2Library.sortTokens(
tokenA,
tokenB
);
address pair = IUniswapV2Factory(factory).getPair(token0, token1);
return pair;
}
function getPairTotalsupply(address pair) external view returns (uint) {
uint256 totalSupply = IUniswapV2Pair(pair).totalSupply();
return totalSupply;
}
// **** ADD LIQUIDITY ****
function _addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin
) internal returns (uint amountA, uint amountB) {
// create the pair if it doesn't exist yet
if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
IUniswapV2Factory(factory).createPair(tokenA, tokenB);
}
(uint reserveA, uint reserveB) = UniswapV2Library.getReserves(
factory,
tokenA,
tokenB
);
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
uint amountBOptimal = UniswapV2Library.quote(
amountADesired,
reserveA,
reserveB
);
if (amountBOptimal <= amountBDesired) {
require(
amountBOptimal >= amountBMin,
"UniswapV2Router: INSUFFICIENT_B_AMOUNT"
);
(amountA, amountB) = (amountADesired, amountBOptimal);
} else {
uint amountAOptimal = UniswapV2Library.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 = IUniswapV2Factory(factory).getPair(tokenA, tokenB);
IERC20(tokenA).transferFrom(msg.sender, pair, amountA);
IERC20(tokenB).transferFrom(msg.sender, pair, amountB);
liquidity = IUniswapV2Pair(pair).mint(to);
}
// **** REMOVE LIQUIDITY ****
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public ensure(deadline) returns (uint amountA, uint amountB) {
address pair = IUniswapV2Factory(factory).getPair(tokenA, tokenB);
IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair
(uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
(address token0, ) = UniswapV2Library.sortTokens(tokenA, tokenB);
(amountA, amountB) = tokenA == token0
? (amount0, amount1)
: (amount1, amount0);
require(
amountA >= amountAMin,
"UniswapV2Router: INSUFFICIENT_A_AMOUNT"
);
require(
amountB >= amountBMin,
"UniswapV2Router: INSUFFICIENT_B_AMOUNT"
);
}
function flash_swap(
address uniswapAddress,
address token0,
address token1,
uint amount0Out,
uint amount1Out
) external {
IUniswapV2Pair(
IUniswapV2Factory(uniswapAddress).getPair(token0, token1)
).swap(
amount0Out,
amount1Out,
address(this),
abi.encodeWithSignature("flashLoan()")
);
}
// **** SWAP ****
// requires the initial amount to have already been sent to the first pair
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, ) = UniswapV2Library.sortTokens(input, output);
uint amountOut = amounts[i + 1];
console.log(
"Router swapExactTokensForTokens amountsIn:%d,amountsOut:%d",
amounts[i],
amounts[i + 1]
);
(uint amount0Out, uint amount1Out) = input == token0
? (uint(0), amountOut)
: (amountOut, uint(0));
address to = i < path.length - 2 // ? UniswapV2Library.pairFor(factory, output, path[i + 2])
? IUniswapV2Factory(factory).getPair(output, path[i + 2])
: _to;
IUniswapV2Pair(IUniswapV2Factory(factory).getPair(input, output))
.swap(amount0Out, amount1Out, to, new bytes(0));
}
}
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external ensure(deadline) returns (uint[] memory amounts) {
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(
amounts[amounts.length - 1] >= amountOutMin,
"UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT"
);
// console.log("Router swapExactTokensForTokens msg.sender:", msg.sender);
IERC20(path[0]).transferFrom(
msg.sender,
// UniswapV2Library.pairFor(factory, path[0], path[1]),
IUniswapV2Factory(factory).getPair(path[0], path[1]),
amounts[0]
); //address token, address from, address to, uint value)
_swap(amounts, path, to);
}
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external ensure(deadline) returns (uint[] memory amounts) {
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(
amounts[0] <= amountInMax,
"UniswapV2Router: EXCESSIVE_INPUT_AMOUNT"
);
IERC20(path[0]).transferFrom(
msg.sender,
// UniswapV2Library.pairFor(factory, path[0], path[1]),
IUniswapV2Factory(factory).getPair(path[0], path[1]),
amounts[0]
); //address token, address from, address to, uint value)
_swap(amounts, path, to);
}
// **** LIBRARY FUNCTIONS ****
function quote(
uint amountA,
uint reserveA,
uint reserveB
) public pure returns (uint amountB) {
return UniswapV2Library.quote(amountA, reserveA, reserveB);
}
function getAmountOut(
uint amountIn,
uint reserveIn,
uint reserveOut
) public pure returns (uint amountOut) {
return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
}
function getAmountIn(
uint amountOut,
uint reserveIn,
uint reserveOut
) public pure returns (uint amountIn) {
return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);
}
function getAmountsOut(
uint amountIn,
address[] memory path
) public view returns (uint[] memory amounts) {
return UniswapV2Library.getAmountsOut(factory, amountIn, path);
}
function getAmountsIn(
uint amountOut,
address[] memory path
) public view returns (uint[] memory amounts) {
return UniswapV2Library.getAmountsIn(factory, amountOut, path);
}
}
5、contracts下创建token文件,分别创建WETH、USDT、USDC合约
WETH合约
//SPDX-License-Identifier: MIT
pragma solidity =0.5.16;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";
contract WETH is ERC20, ERC20Detailed {
constructor() public ERC20Detailed("WETH", "WETH", 18) {}
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
}
USDT合约
//SPDX-License-Identifier: MIT
pragma solidity =0.5.16;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";
contract USDT is ERC20, ERC20Detailed {
constructor() public ERC20Detailed("USDT", "USDT", 18) {}
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
}
USDC合约
//SPDX-License-Identifier: MIT
pragma solidity =0.5.16;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";
contract USDC is ERC20, ERC20Detailed {
constructor() public ERC20Detailed("USDC", "USDC", 18) {}
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
}
6、contracts文件下创建 Bot.sol 套利合约
pragma solidity =0.5.16;
import "./uniswap-v2-core/interfaces/IUniswapV2Pair.sol";
import "./uniswap-v2-core/interfaces/IUniswapV2Factory.sol";
import "./uniswap-v2-core/interfaces/IUniswapRouter.sol";
import "./uniswap-v2-core/interfaces/IUniswapV2Callee.sol";
import "./uniswap-v2-core/UniswapV2Library.sol";
import "hardhat/console.sol"; // 用于调试输出
// 定义一个执行套利操作的合约,继承了Uniswap V2的IUniswapV2Callee接口
contract arbitrageBot is IUniswapV2Callee {
address public owner;
address public uniswapAddress; // Uniswap的工厂合约地址
address public router; // Uniswap Router合约地址
address private weth_Address; // WETH代币地址
address private usdt_Address; // USDT代币地址
address private usdc_Address; // USDC代币地址
address weth_usdt_Pair;
address[] weth_usdc_Arrary; // WETH和USDT的交易对路径
address[] usdt_usdc_weth_Arrary; // USDT、USDC和WETH的交易对路径
address[] paths;
// 构造函数,初始化Uniswap的地址和Router地址
constructor(address _uniswapAddress, address _router) public {
owner = msg.sender; // 将部署者设为合约的拥有者
uniswapAddress = _uniswapAddress; // 初始化Uniswap的工厂合约地址
router = _router; // 初始化Uniswap Router的合约地址
}
// 攻击函数,用于执行套利操作,参数为套利地址路径和打算换出多少USDT去进行套利
//例如我们是用w swap t swap c swap w ,因此路径为path =[weth_Address,usdt_Address,usdc_Address]
function attack(address[] calldata path, uint256 _amount) external {
// 初始化路径的代币地址
weth_Address = path[0]; // WETH地址
usdt_Address = path[1]; // USDT地址
usdc_Address = path[2]; // 其他代币地址
weth_usdc_Arrary = [weth_Address, usdt_Address];
console.log("将 WETH swap USDT ......");
// 计算需要的输入金额
uint[] memory amounts = UniswapV2Library.getAmountsIn(
uniswapAddress,
_amount,
weth_usdc_Arrary
);
console.log(
"WETH 换 USDT 的 amounts0In:%d,amounts0Out:%d",
amounts[0],
amounts[1]
);
// 通过UniswapV2Library对路径代币进行排序,确保交易顺序正确
(address token0, address token1) = UniswapV2Library.sortTokens(
path[0],
path[1]
);
//获取 WETH 和 USDT 的Pair交易对地址
weth_usdt_Pair = IUniswapV2Factory(uniswapAddress).getPair(
token0,
token1
);
// 计算在Pair合约中的交易输出
uint256 amount0Out = path[1] == token0 ? _amount : 0;
uint256 amount1Out = path[1] == token1 ? _amount : 0;
// 调用Pair合约的swap函数,执行套利交易,调用flashLoan函数
IUniswapV2Pair(weth_usdt_Pair).swap(
amount0Out,
amount1Out,
address(this),
abi.encodeWithSignature("flashLoan()") // 传递flashLoan函数的调用数据
);
}
// flashLoan函数,执行从 USDT 到 USDC 再换回 WETH 代币的交易
function flashLoan() public {
// 初始化内部交易路径,USDT -> USDC -> WETH
usdt_usdc_weth_Arrary = [usdt_Address, usdc_Address, weth_Address];
console.log("再用 USDT swap USDC 再swap WETH ......");
// 给router授权,允许操作bot地址的代币
IERC20(usdt_Address).approve(router, 99999999999999);
// 确定输入金额为20000wei的 USDT 代币,然后根据交易路径进行swap转换
IUniswapRouter(router).swapExactTokensForTokens(
20000, // 输入金额
0, // 最低输出金额
usdt_usdc_weth_Arrary, // 交易路径
address(this), // 接收代币的地址
block.timestamp + 10000 // 交易截止时间
);
IERC20(weth_Address).transfer(weth_usdt_Pair, 21);
}
// 当执行flashLoan操作时,Uniswap会回调这个函数
function uniswapV2Call(
address sender,
uint amount0,
uint amount1,
bytes calldata data
) external {
// 调用flashLoan函数完成套利交易
flashLoan();
// 避免编译器警告,将未使用的变量进行引用
sender;
amount0;
amount1;
data;
}
}
7、根目录下创建scripts文件,并创建flashLoan_attack.js文件
const { ethers } = require("hardhat");
// 主部署函数。
async function main() {
//从 ethers 提供的 signers 中获取签名者。
const [deployer, account01] = await ethers.getSigners();
const account1 = account01.getAddress();
const deployerAddress = deployer.address;
console.log(`使用账户部署合约: ${deployer.address}`);
//部署uniswapFactory和三种token
const UniswapFactory = await ethers.getContractFactory("UniswapV2Factory");
const uniswapFactory = await UniswapFactory.deploy(deployerAddress);
const uniswapAddress = await uniswapFactory.getAddress();
console.log(`uniswap Factory 部署在 ${uniswapAddress}`);
const WETHFactory = await ethers.getContractFactory("WETH");
const weth = await WETHFactory.deploy();
const USDTFactory = await ethers.getContractFactory("USDT");
const usdt = await USDTFactory.deploy();
const USDCFactory = await ethers.getContractFactory("USDC");
const usdc = await USDCFactory.deploy();
const usdcAddress = await usdc.getAddress();
const usdtAddress = await usdt.getAddress();
const wethAddress = await weth.getAddress();
console.log(`usdcAddress : ${usdcAddress}`);
console.log(`usdtAddress : ${usdtAddress}`);
console.log(`wethAddress : ${wethAddress}`);
//创建pair代币对
await uniswapFactory.createPair(wethAddress, usdcAddress);
await uniswapFactory.createPair(wethAddress, usdtAddress);
await uniswapFactory.createPair(usdcAddress, usdtAddress);
//部署 Router
const Router = await ethers.getContractFactory("UniswapRouter");
const router = await Router.deploy(uniswapAddress, wethAddress);
const routerAddress = await router.getAddress();
console.log(`router: ${routerAddress}`);
//铸币
await usdc.mint(deployerAddress, 100000100000);
await usdt.mint(deployerAddress, 100000100000);
await weth.mint(deployerAddress, 20000000);
console.log("\n---------添加流动性--------------------------");
// 授权给 Router 合约,允许其操作部署者的代币
await usdc.approve(routerAddress, 9999999999999);
await usdt.approve(routerAddress, 9999999999999);
await weth.approve(routerAddress, 9999999999999);
// 开始添加流动性
const deadline = Math.floor(Date.now() / 1000) + 10 * 60; // 当前时间加10分钟作为交易的最后期限
// USDC-USDT 交易对添加流动性
await router.addLiquidity(
usdcAddress,
usdtAddress,
100000, // 添加的USDC 数量
100000, // 添加的USDT 数量
0,
0,
deployer,
deadline
);
// WETH-USDT 交易对添加流动性
await router.addLiquidity(
wethAddress,
usdtAddress,
100000,
100000000,
0,
0,
deployerAddress,
deadline
);
// WETH-USDC 交易对添加流动性
await router.addLiquidity(
wethAddress,
usdcAddress,
100000,
100000000,
0,
0,
deployerAddress,
deadline
);
console.log(`add liquidity end `);
console.log(
"\n---------笨蛋用户 用 USDC swap USDT--------------------------"
);
// 假设 account1 拥有 5000000 wei 的 USDC,并且将其授权给 routerAddress
await usdc.mint(account1, 5000000);
await usdc.connect(account01).approve(routerAddress, 9999999999999);
// 用 5000000 wei 的 USDC 兑换 USDT
const path0 = [usdcAddress, usdtAddress]; // 交易路径 USDC -> USDT
await router
.connect(account01)
.swapExactTokensForTokens(500000, 0, path0, account1, deadline);
console.log(`笨蛋 完成换币 `); // 打印兑换完成的消息
// 攻击者开始套利
console.log("\n-------------攻击--------------------------");
const Bot = await ethers.getContractFactory("arbitrageBot");
const bot = await Bot.deploy(uniswapAddress, routerAddress);
const botAddress = await bot.getAddress();
console.log(`bot 部署在 : ${botAddress} `);
// 给 bot 合约账户铸造 WETH 代币
const bot_weth_balance = await weth.balanceOf(botAddress);
console.log("攻击前:bot_weth_balance:", bot_weth_balance);
// 开始套利,套利路径 WETH -> USDT -> USDC
const path = [wethAddress, usdtAddress, usdcAddress];
await bot.attack(path, 20000); // 使用 20000 WETH 进行套利
// 攻击后检查 bot 的 WETH 余额
const bot_weth_balance2 = await weth.balanceOf(botAddress);
console.log("攻击后:bot_weth_balance:", bot_weth_balance2);
// 判断是否套利成功
if (bot_weth_balance2 > bot_weth_balance) {
console.log("攻击成功!!~~~~");
}
}
// 执行主函数并处理可能的结果。
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
8、启动node
yarn hardhat node
9、运行flashLoan_attack.js脚本
yarn hardhat run scripts/flashLoan/flashLoan_attack.js
控制台结果输出: