0%

SmartContract-合约里调用Uniswap

Pre:

在学习闪电贷的过程中,先熟悉一下合约里调用其他swap合约的用法先试一下如何在自己的合约里调用uniswap

完整代码:

kovan测试网里

  • uniswapV2: 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D

  • dai: 0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
pragma solidity  0.8.0;

import "https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/interfaces/IUniswapV2Router02.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol";

contract UseSwap {
IUniswapV2Router02 private constant ROUTER = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); //
address internal owner;

constructor() {
owner = msg.sender;
}

modifier isOwner(){
require(msg.sender == owner, "Caller is not owner");
_;
}

function swapTokenToEth(address token, uint amountOut, uint amountInMax ) public{
IERC20(token).transferFrom(msg.sender, address(this), amountInMax); // 往合约里转账
_approveTokenIfNeeded(token); // 合约地址授权给router地址
uint deadline = block.timestamp + 100;
ROUTER.swapTokensForExactETH(amountOut, amountInMax, getPathForTokenToEth(token), address(this), deadline);
}

function swapEthToToken(address token, uint TokenOutAmountMin) public payable{
uint deadline = block.timestamp + 100;
ROUTER.swapExactETHForTokens{ value: msg.value }(TokenOutAmountMin, getPathForEthToToken(token), address(this), deadline);
}

// 授权
function _approveTokenIfNeeded(address token) private {
if (IERC20(token).allowance(address(this), address(ROUTER)) == 0) {
IERC20(token).approve(address(ROUTER), 1000000 ether);
}
}

function approveToken(address token) public {
IERC20(token).approve(address(ROUTER), 1000000 ether);
}

function getPathForTokenToEth(address token) private view returns (address[] memory){
address[] memory path = new address[](2);
path[0] = token;
path[1] = ROUTER.WETH();
return path;
}

function getPathForEthToToken(address token) private view returns (address[] memory){
address[] memory path = new address[](2);
path[0] = ROUTER.WETH();
path[1] = token;
return path;
}

function withdraw(address payable _address, uint withdrawAmount) public payable isOwner{
_address.transfer(withdrawAmount);
}

// important to receive ETH
receive() external payable {}
}

实例化合约:

知道合约的interface和合约地址后,就可以实例化合约了。

Eth -> Token:

定义receive函数:

1
2
3
4
receive 接收以太函数
一个合约最多有一个 receive 函数, 声明函数为: receive() external payable { ... }

不需要 function 关键字,也没有参数和返回值并且必须是 external 可见性和 payable 修饰. 它可以是 virtual 的,可以被重载也可以有 修改器modifier 。

合约里要定义receive函数才能接受eth

swapExactETHForTokens:

要用到uniswapRouterswapExactETHForTokens函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path); // 获得可兑换出来的数量
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'); // 可兑换的数量要大于我们的期望值
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to); // swap
}

该函数有payable修饰,需要输入eth的数量通过msg.value传入

通过在自己合约里调用swapExactETHForTokens函数的流程是:

1
2
3
4
function swapEthToToken(address token, uint TokenOutAmountMin) public payable{
uint deadline = block.timestamp + 100;
ROUTER.swapExactETHForTokens{ value: msg.value }(TokenOutAmountMin, getPathForEthToToken(token), address(this), deadline);
}
  1. 函数为payable

  2. 调用函数的同时转账msg.value数量的eth

  3. eth继续通过msg.value传入,调用swapExactETHForTokens

Token -> Eth:

这里要用到uniswapRouterswapTokensForExactETH函数,一开始遇到Fail with error 'TransferHelper: ETH_TRANSFER_FAILED'报错看了一下函数的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
); // Here
_swap(amounts, path, address(this));
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}

没有payable修饰,在兑换成ETH之前,需要把token转进去

通过在自己合约里调用swapTokensForExactETH函数的流程是:

1
2
3
4
5
6
function swapTokenToEth(address token, uint amountOut, uint amountInMax ) public{
IERC20(token).transferFrom(msg.sender, address(this), amountInMax); // 往合约里转账
_approveTokenIfNeeded(token);
uint deadline = block.timestamp + 100;
ROUTER.swapTokensForExactETH(amountOut, amountInMax, getPathForTokenToEth(token), address(this), deadline);
}
  1. 自己的钱包地址授权Dai额度给自己的合约地址

  2. 合约从钱包里提取一定额度Dai

  3. 合约授权Dai给uniswap router

  4. uniswap router从合约里提取Dai,兑换成eth,再发回来

Refs: