Ganache-cli 分叉出本地主网测试网 – UNI闪电贷示例
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); }