1、使用Ganache-cli 分叉出本地主网测试网 使用infura主网API地址

ganache-cli -f https://mainnet.infura.io/v3/节点地址 -e 10000 -p 8545

2、
https://v2.info.uniswap.org/pairs
查看uni配对 池子排行榜 点击ETH-USDT Pair 可以查看到配对合约地址 0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852

对配对合约的源代码 UniswapV2Pair.sol 进行编译 版本为 Compiler Version v0.5.16+commit.9c3226ce

使用Remix Web3 Provider网络粘贴配对合约地址At address读出函数变量并且可以调取配对合约

3、
实现应当参考UniswapV2Pair.sol中的函数实现 并且编写闪电贷代码
先部署合约 然后使用 swap 函数 里面填写借款的USDT 注意:1个USDT后面是6个0精度

pragma solidity ^0.5.0;

import "./IERC20.sol";

//调用外部配对合约 UniswapV2Router02
interface pair{
    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
}

interface uniRouter{
    // 用精确exact的token换尽量多的token函数
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    // 计算对价 - 算出来该还多少
    function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}

// 拷贝 UniswapV2Router02 中的 IWETH interface
interface IWETH {
    function deposit() external payable;
    function transfer(address to, uint value) external returns (bool);
    function withdraw(uint) external;
}

contract MyLoan {
    // V2路由合约从GAS搜索器获取
    address public router = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;

    // https://v2.info.uniswap.org 获取pair地址
    address public USDTETH = 0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852;
    address public WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address public USDT = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
    address public USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
    uint256 public loanAmount;
    uint256 public ETHAmount;

    bytes _data = bytes("MyLoanMessage");

    // 事件通知
    event Balance(uint256 amount);

    constructor() public {
        // 把所有的token进行批准
        safeApprove(WETH,router,uint(-1));
        safeApprove(USDT,router,uint(-1));
        safeApprove(USDC,router,uint(-1));
    }

    // 向合约内手动存钱用于手续费
    function deposit() public payable {
        ETHAmount = msg.value;
        // 存入ETH到合约
        IWETH(WETH).deposit.value(ETHAmount)();
        //通知
        emit Balance(IERC20(WETH).balanceOf(address(this)));
    }

    // 借款函数 必须还要在合约内还款
    function swap(uint256 _loanAmount) public {
        loanAmount = _loanAmount;
        // Part1、从USDTETH合约,直接借USDT
        // 参数1、ETH输出金额 2、借的USDT输出金额 3、to地址借给谁 4、data
        pair(USDTETH).swap(uint(0),_loanAmount,address(this),_data);
    }

    // 参照 UniswapV2Pair 中的代码 是利用Pair合约再次外部调用本合约中的 uniswapV2Call函数 本函数是用钱代码
    // if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
    function uniswapV2Call(address account, uint256 amount0, uint256 amount1, bytes memory data) public {
        // 查询所借到的USDT余额
        uint256 balance = IERC20(USDT).balanceOf(address(this));
        emit Balance(balance);

        // Part2、USDT换USDC
        address[] memory path1 = new address[](2);
        path1[0] = USDT; //输入
        path1[1] = USDC; //输出

        // 参数1、交换多少 2、最少交换多少 3、交易路径 4、to地址 5、deadline最后期限
        // 返回值amounts1显示换回来多少
        uint[] memory amounts1 = uniRouter(router).swapExactTokensForTokens(balance,uint(0),path1,address(this),block.timestamp+1800);
        emit Balance(amounts1[1]);

        // Part3、USDC再去换WETH
        address[] memory path2 = new address[](2);
        path2[0] = USDC; //输入
        path2[1] = WETH; //输出
        uint[] memory amounts2 = uniRouter(router).swapExactTokensForTokens(amounts1[1],uint(0),path2,address(this),block.timestamp+1800);
        emit Balance(amounts2[1]);

        // Part4、计算还款并还款
        address[] memory path3 = new address[](2);
        path3[0] = WETH; //计算这个
        path3[1] = USDT; 
        // 通过计算要还款的金额ETH
        uint[] memory amounts3 = uniRouter(router).getAmountsIn(loanAmount,path3);
        emit Balance(amounts3[0]);

        //还给USDTETH合约WETH
        IERC20(WETH).transfer(USDTETH,amounts3[0]);

        // 通知还款金额 WETH
        emit Balance(amounts3[0]);
    }

    function() external payable {
        assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
    }

    function withdraw() public {
        //查询余额 IERC20 库
        uint256 balance = IERC20(WETH).balanceOf(address(this));
        emit Balance(balance);
        
        //提款到本合约
        IWETH(WETH).withdraw(balance);
        emit Balance(address(this).balance);

        //提款到账户
        msg.sender.transfer(balance);

        ETHAmount = 0;
    }

    function checkBalance() external view returns(uint) {
        return IERC20(WETH).balanceOf(address(this));

    }

    //合约安全批准 这个函数从 UniswapV2Router02 提取的
    function safeApprove(address token, address to, uint value) internal {
        // bytes4(keccak256(bytes('approve(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: APPROVE_FAILED');
    }
}

IERC20.sol

pragma solidity ^0.5.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
 * the optional functions; to access them see {ERC20Detailed}.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}