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);
}