利用Uniswap v2 简单实现闪电贷SwapLoan,附加超详细源码分析 [ 1 ]

利用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

控制台结果输出:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值