价差套利所谓的价差套利,本质上就是低买高卖。举个例子,假设sushiswap里的ETH价值1500USDT,uniswap里的ETH价值1600USDT,那么我们可以以1500USDT在sushiswap里买入1ETH,然后在uniswap里卖出获得1600USDT
所谓的价差套利,本质上就是低买高卖。举个例子,假设sushiswap里的ETH价值1500USDT,uniswap里的ETH价值1600USDT,那么我们可以以1500USDT在sushiswap里买入1ETH,然后在uniswap里卖出获得1600USDT,假设手续费是5U,那么最终获利95USDT。这里有个问题,那就是我们必须先用自己的1500USDT去买ETH,假设我们没有这么多钱怎么办呢?uniswap给我们提供了类似flash loan的flash swap功能,可以让我们无成本进行套利。
在UniswapV2里,真正执行swap的过程是发生在UniswapV2Pair里的,下面让我们看看UniswapV2Pair的swap函数:
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
uint balance0;
uint balance1;
{ // scope for _token{0,1}, avoids stack too deep errors
address _token0 = token0;
address _token1 = token1;
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
}
_update(balance0, balance1, _reserve0, _reserve1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
我们知道,一次swap,其实是用一种token去换另一种token,你把你要卖掉的token转给pair,pair给你转你想要的token。swap函数有四个参数,其中amount0Out
和 amount1Out
是指你想要获得的那个token的数量。比如你想卖token1获得100e18个token0,你就需要传入amount0Out = 100e18
以及 amount1Out = 0
,这时候pair会先把100e18的token0转给你,也就是下面这行代码:
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
然后判断data是否有值,如果有值,就说明是一次flash swap,它会把调用者指定的to地址当成一个实现了IUniswapV2Callee接口的合约,然后调用这个合约的uniswapV2Call方法:
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
因为是先把token0转给to地址,然后再调用uniswapV2Call,这样就相当于to地址的那个合约先问pair借了一笔钱,接下来只要在uniswapV2Call方法把一定的token1转到pair,并且满足K值公式,那么整个swap就是合法的。
回到第一节的那个例子,我们如何进行套利呢?这里我们不需要第一步用1500USDT去买1ETH,我们可以直接找到sushiswap的ETH-USDT的pair,然后amount0Out传入1e18,to传入一个你的合约地址,这时候pair就会把1个ETH转到你的合约地址,然后你需要在uniswapV2Call方法里使用uniswap把1ETH换成1600USDT,然后把1500USDT转回sushiswap的ETH-USDT的pair里就可以了,剩下的USDT就会留在你的合约中,成为你的利润。
function startArbitrageFromV2(
address pool,
uint256 amountBorrowed)external onlyOwner{
IUniswapV3Pool v3Pool = IUniswapV3Pool(pool);
address token0 = v3Pool.token0();
address token1 = v3Pool.token1();
address pairAddress =
IUniswapV2Factory(v2Factory).getPair(token0, token1);
require(pairAddress != address(0), "This pool does not exist");
bytes memory data = abi.encode(
pool,
amountBorrowed
);
// borrow token0 from V2
IUniswapV2Pair(pairAddress).swap(
amountBorrowed,
0,
address(this),
data
);
}
首先我们写一个startArbitrageFromV2函数,这个函数的入参是一个pool地址和amountBorrowed。pool地址是uniswapV3中的ETH-USDT交易对地址,是用来卖出ETH的,amountBorrowed就是我们要从sushiswap里ETH-USDT的pair借出的ETH的数量1e18。然后我们拿到pool里的token0和token1,也就是ETH和USDT,然后通过IUniswapV2Factory获得sushiswap里的ETH-USDT交易对,判断pair不为0地址,然后将pool和amountBorrowed编码进data,最后调用sushiswap里ETH-USDT的pair的swap方法,amount0Out传入的是1e18,amount1Out传入的是0,to传的是address(this)。
接下来我们实现uniswapV2Call方法:
function uniswapV2Call(
address _sender,
uint256 _amount0,
uint256 _amount1,
bytes calldata _data
) external {
address token0 = IUniswapV2Pair(msg.sender).token0();
address token1 = IUniswapV2Pair(msg.sender).token1();
require(
msg.sender == UniswapV2Library.pairFor(v2Factory, token0, token1),
"not from swap pair"
);
(address pool,,)=abi.decode(_data,(address,uint256,uint256));
IUniswapV3Pool v3Pool = IUniswapV3Pool(pool);
uint24 fee = v3Pool.fee();
require(_amount0 > 0,"amount invalid");
//V3 sell token0 get token1
address[] memory path = new address[](2);
path[0] = token0;
path[1] = token1;
IERC20(token0).approve(address(v3Router), _amount0);
uint256 amountShouldReturned =
UniswapV2Library.getAmountsIn(v2Factory, _amount0, path)[0];
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
.ExactInputSingleParams({
tokenIn: path[0],
tokenOut: path[1],
fee: fee,
recipient: address(this),
deadline: block.timestamp+deadline,
amountIn: _amount0,
amountOutMinimum: amountShouldReturned,
sqrtPriceLimitX96: 0
});
uint256 amountReceived = v3Router.exactInputSingle(params);
//return back token1 to V2
IERC20(token1).transfer(msg.sender, amountShouldReturned);
emit Logs(amountReceived - amountShouldReturned);
}
由于这个方法是pair调用的,因此msg.sender是个UniswapV2Pair,通过拿到token0和token1,还原出pair,和msg.sender进行比较。然后解析出pool,拿到pool的fee,为下面swap做准备。这时候这个合约已经有了ETH,数量就是_amount0,这时候就可以使用UniswapV2Library计算出应该还回去多少USDT,amountShouldReturned计算的值就是这个数据。接下来就是调用UniswapV3的exactInputSingle方法,将ETH全部转成成USDT,amountReceived就是得到的USDT的数量,然后把amountShouldReturned数量的USDT转给msg.sender,也就是转给pair。如果一切顺利,这个合约就留下了(amountReceived - amountShouldReturned)数量的USDT作为利润。
本文实现了一个从sushiswap到uniswapV3的价差套利合约,理论上来说价差套利可以是V2->V2、V2->V3、V3->V2、V3->V3,具体步骤都差不多,都是先找到有价差的两个pair,然后调用价格低的那个pair的swap,然后callback到你的合约,你的合约把你得到的token0去价格高的那个pair卖掉换成token1,然后一部分token1转回价格低的pair,留下的token1就是利润。当然,这只是理论情况,实际情况下这种套利空间几乎不存在,一般来说都是CEX的币价先变,然后产生套利空间驱动套利者将DEX的价格修正,正常来说套利者不可能只修正uniswap而不去修正sushiswap,因此DEX之间的套利空间就变得很小。
代码中除了实现了V2->V3的套利,还实现了V3->V2的套利,具体代码见startArbitrageFromV3和uniswapV3SwapCallback。 注意:这部分代码没有严格测试过,如果想使用,请自担风险!
//SPDX-License-Identifier: MIT
pragma solidity >=0.7.5;
pragma abicoder v2;
import "./UniswapV2Library.sol";
import "./interfaces/IUniswapV2Router02.sol";
import "./interfaces/IUniswapV2Pair.sol";
import "./interfaces/IUniswapV2Factory.sol";
import "./interfaces/IERC20.sol";
import "./uniswap/v3-core/interfaces/IUniswapV3Pool.sol";
import "./uniswap/v3-periphery/interfaces/ISwapRouter.sol";
import "./uniswap/v3-periphery/libraries/PoolAddress.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract ArbitrageSwapWithFlashLoaner is Ownable{
uint256 constant deadline = 100;
uint160 internal constant MIN_SQRT_RATIO = 4295128739;
uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;
IUniswapV2Router02 public v2Router;
address public v2Factory;
ISwapRouter public v3Router;
address public v3Factory;
event Logs(uint256 amount);
constructor(
address _v2Factory,
address _v3Factory,
address _v2Router,
address _v3Router
) public {
v2Factory = _v2Factory;
v3Factory = _v3Factory;
v2Router = IUniswapV2Router02(_v2Router);
v3Router = ISwapRouter(_v3Router);
}
function startArbitrageFromV2(
address pool,
uint256 amountBorrowed)external onlyOwner{
IUniswapV3Pool v3Pool = IUniswapV3Pool(pool);
address token0 = v3Pool.token0();
address token1 = v3Pool.token1();
address pairAddress =
IUniswapV2Factory(v2Factory).getPair(token0, token1);
require(pairAddress != address(0), "This pool does not exist");
bytes memory data = abi.encode(
pool,
amountBorrowed
);
// borrow token0 from V2
IUniswapV2Pair(pairAddress).swap(
amountBorrowed,
0,
address(this),
data
);
}
function uniswapV2Call(
address _sender,
uint256 _amount0,
uint256 _amount1,
bytes calldata _data
) external {
address token0 = IUniswapV2Pair(msg.sender).token0();
address token1 = IUniswapV2Pair(msg.sender).token1();
require(
msg.sender == UniswapV2Library.pairFor(v2Factory, token0, token1),
"not from swap pair"
);
(address pool,,)=abi.decode(_data,(address,uint256,uint256));
IUniswapV3Pool v3Pool = IUniswapV3Pool(pool);
uint24 fee = v3Pool.fee();
require(_amount0 > 0,"amount invalid");
//V3 sell token0 get token1
address[] memory path = new address[](2);
path[0] = token0;
path[1] = token1;
IERC20(token0).approve(address(v3Router), _amount0);
uint256 amountShouldReturned =
UniswapV2Library.getAmountsIn(v2Factory, _amount0, path)[0];
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
.ExactInputSingleParams({
tokenIn: path[0],
tokenOut: path[1],
fee: fee,
recipient: address(this),
deadline: block.timestamp+deadline,
amountIn: _amount0,
amountOutMinimum: amountShouldReturned,
sqrtPriceLimitX96: 0
});
uint256 amountReceived = v3Router.exactInputSingle(params);
//return back token1 to V2
IERC20(token1).transfer(msg.sender, amountShouldReturned);
emit Logs(amountReceived - amountShouldReturned);
}
function startArbitrageFromV3(
address pool,
uint256 amountBorrowed) external onlyOwner{
IUniswapV3Pool v3Pool = IUniswapV3Pool(pool);
bytes memory data = abi.encode(
pool,
amountBorrowed
);
//borrow token0 from v3
IUniswapV3Pool(v3Pool).swap(
address(this),
false,
int(amountBorrowed),
MAX_SQRT_RATIO-1,
data
);
}
function uniswapV3SwapCallback(
int amount0,
int amount1,
bytes calldata data
) external{
IUniswapV3Pool v3Pool = IUniswapV3Pool(msg.sender);
address token0 = v3Pool.token0();
address token1 = v3Pool.token1();
uint24 fee = v3Pool.fee();
require(msg.sender ==PoolAddress.computeAddress(v3Factory,PoolAddress.getPoolKey(token0, token1, fee)), "not authorized");
uint256 amountRequired = uint256(amount1);
uint256 amountToken0 = uint256(-amount0);
//v2 sell token0 and get token1
address[] memory path = new address[](2);
path[0] = token0;
path[1] = token1;
IERC20(token0).approve(address(v2Router), amountToken0);
uint256 amountReceived = v2Router.swapExactTokensForTokens(
amountToken0,
amountRequired,
path,
address(this),
block.timestamp+deadline
)[1];
// return token1 back to pool
IERC20(token1).transfer(msg.sender, amountRequired);
// (amountReceived - amountRequired) token0 will be profit
emit Logs(amountReceived - amountRequired);
}
function withdrawBalance(address token,uint256 amount) external onlyOwner {
require(amount > 0, "amount==0");
if(token==address(0x0)){
msg.sender.transfer(amount);
}else{
IERC20(token).transfer(msg.sender, amount);
}
}
function setV2Router(address _v2Router,address _v2Factory) external onlyOwner {
v2Router = IUniswapV2Router02(_v2Router);
v2Factory = _v2Factory;
}
function setV3Router(address _v3Router,address _v3Factory) external onlyOwner {
v3Router = ISwapRouter(_v3Router);
v3Factory = _v3Factory;
}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!