-
Notifications
You must be signed in to change notification settings - Fork 16
/
Swapper.sol
174 lines (146 loc) · 6.83 KB
/
Swapper.sol
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "v3-core/interfaces/callback/IUniswapV3SwapCallback.sol";
import "v3-core/interfaces/IUniswapV3Pool.sol";
import "v3-core/libraries/TickMath.sol";
import "v3-periphery/interfaces/INonfungiblePositionManager.sol";
import "../../lib/IWETH9.sol";
import "../../lib/IUniversalRouter.sol";
import "../interfaces/IErrors.sol";
// base functionality to do swaps with different routing protocols
abstract contract Swapper is IUniswapV3SwapCallback, IErrors {
event Swap(address indexed tokenIn, address indexed tokenOut, uint256 amountIn, uint256 amountOut);
/// @notice Wrapped native token address
IWETH9 public immutable weth;
address public immutable factory;
/// @notice Uniswap v3 position manager
INonfungiblePositionManager public immutable nonfungiblePositionManager;
/// @notice 0x Exchange Proxy
address public immutable zeroxRouter;
/// @notice Uniswap Universal Router
address public immutable universalRouter;
/// @notice Constructor
/// @param _nonfungiblePositionManager Uniswap v3 position manager
/// @param _zeroxRouter 0x Exchange Proxy
constructor(
INonfungiblePositionManager _nonfungiblePositionManager,
address _zeroxRouter,
address _universalRouter
) {
weth = IWETH9(_nonfungiblePositionManager.WETH9());
factory = _nonfungiblePositionManager.factory();
nonfungiblePositionManager = _nonfungiblePositionManager;
zeroxRouter = _zeroxRouter;
universalRouter = _universalRouter;
}
// swap data for 0x
struct ZeroxRouterData {
address allowanceTarget;
bytes data;
}
// swap data for uni - must include sweep for input token
struct UniversalRouterData {
bytes commands;
bytes[] inputs;
uint256 deadline;
}
struct RouterSwapParams {
IERC20 tokenIn;
IERC20 tokenOut;
uint256 amountIn;
uint256 amountOutMin;
bytes swapData;
}
// general swap function which uses external router with off-chain calculated swap instructions
// does slippage check with amountOutMin param
// returns token amounts deltas after swap
function _routerSwap(RouterSwapParams memory params)
internal
returns (uint256 amountInDelta, uint256 amountOutDelta)
{
if (params.amountIn != 0 && params.swapData.length != 0 && address(params.tokenOut) != address(0)) {
uint256 balanceInBefore = params.tokenIn.balanceOf(address(this));
uint256 balanceOutBefore = params.tokenOut.balanceOf(address(this));
// get router specific swap data
(address router, bytes memory routerData) = abi.decode(params.swapData, (address, bytes));
if (router == zeroxRouter) {
ZeroxRouterData memory data = abi.decode(routerData, (ZeroxRouterData));
// approve needed amount
SafeERC20.safeApprove(params.tokenIn, data.allowanceTarget, params.amountIn);
// execute swap
(bool success,) = zeroxRouter.call(data.data);
if (!success) {
revert SwapFailed();
}
// reset approval
SafeERC20.safeApprove(params.tokenIn, data.allowanceTarget, 0);
} else if (router == universalRouter) {
UniversalRouterData memory data = abi.decode(routerData, (UniversalRouterData));
// tokens are transfered to Universalrouter directly (data.commands must include sweep action!)
SafeERC20.safeTransfer(params.tokenIn, universalRouter, params.amountIn);
IUniversalRouter(universalRouter).execute(data.commands, data.inputs, data.deadline);
} else {
revert WrongContract();
}
uint256 balanceInAfter = params.tokenIn.balanceOf(address(this));
uint256 balanceOutAfter = params.tokenOut.balanceOf(address(this));
amountInDelta = balanceInBefore - balanceInAfter;
amountOutDelta = balanceOutAfter - balanceOutBefore;
// amountMin slippage check
if (amountOutDelta < params.amountOutMin) {
revert SlippageError();
}
// event for any swap with exact swapped value
emit Swap(address(params.tokenIn), address(params.tokenOut), amountInDelta, amountOutDelta);
}
}
struct PoolSwapParams {
IUniswapV3Pool pool;
IERC20 token0;
IERC20 token1;
uint24 fee;
bool swap0For1;
uint256 amountIn;
uint256 amountOutMin;
}
// execute swap directly on specified pool
// amounts must be available on the contract for both tokens
function _poolSwap(PoolSwapParams memory params) internal returns (uint256 amountInDelta, uint256 amountOutDelta) {
if (params.amountIn > 0) {
(int256 amount0Delta, int256 amount1Delta) = params.pool.swap(
address(this),
params.swap0For1,
int256(params.amountIn),
(params.swap0For1 ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1),
abi.encode(
params.swap0For1 ? params.token0 : params.token1,
params.swap0For1 ? params.token1 : params.token0,
params.fee
)
);
amountInDelta = params.swap0For1 ? uint256(amount0Delta) : uint256(amount1Delta);
amountOutDelta = params.swap0For1 ? uint256(-amount1Delta) : uint256(-amount0Delta);
// amountMin slippage check
if (amountOutDelta < params.amountOutMin) {
revert SlippageError();
}
}
}
// swap callback function where amount for swap is payed
function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external override {
require(amount0Delta > 0 || amount1Delta > 0); // swaps entirely within 0-liquidity regions are not supported
// check if really called from pool
(address tokenIn, address tokenOut, uint24 fee) = abi.decode(data, (address, address, uint24));
if (address(_getPool(tokenIn, tokenOut, fee)) != msg.sender) {
revert Unauthorized();
}
// transfer needed amount of tokenIn
uint256 amountToPay = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta);
SafeERC20.safeTransfer(IERC20(tokenIn), msg.sender, amountToPay);
}
// get pool for token
function _getPool(address tokenA, address tokenB, uint24 fee) internal view returns (IUniswapV3Pool) {
return IUniswapV3Pool(PoolAddress.computeAddress(address(factory), PoolAddress.getPoolKey(tokenA, tokenB, fee)));
}
}