-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
QuoterV2.sol
273 lines (245 loc) · 10.1 KB
/
QuoterV2.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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;
import '@uniswap/v3-core/contracts/libraries/SafeCast.sol';
import '@uniswap/v3-core/contracts/libraries/TickMath.sol';
import '@uniswap/v3-core/contracts/libraries/TickBitmap.sol';
import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol';
import '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol';
import '../interfaces/IQuoterV2.sol';
import '../base/PeripheryImmutableState.sol';
import '../libraries/Path.sol';
import '../libraries/PoolAddress.sol';
import '../libraries/CallbackValidation.sol';
import '../libraries/PoolTicksCounter.sol';
/// @title Provides quotes for swaps
/// @notice Allows getting the expected amount out or amount in for a given swap without executing the swap
/// @dev These functions are not gas efficient and should _not_ be called on chain. Instead, optimistically execute
/// the swap and check the amounts in the callback.
contract QuoterV2 is IQuoterV2, IUniswapV3SwapCallback, PeripheryImmutableState {
using Path for bytes;
using SafeCast for uint256;
using PoolTicksCounter for IUniswapV3Pool;
/// @dev Transient storage variable used to check a safety condition in exact output swaps.
uint256 private amountOutCached;
constructor(address _factory, address _WETH9) PeripheryImmutableState(_factory, _WETH9) {}
function getPool(
address tokenA,
address tokenB,
uint24 fee
) private view returns (IUniswapV3Pool) {
return IUniswapV3Pool(PoolAddress.computeAddress(factory, PoolAddress.getPoolKey(tokenA, tokenB, fee)));
}
/// @inheritdoc IUniswapV3SwapCallback
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes memory path
) external view override {
require(amount0Delta > 0 || amount1Delta > 0); // swaps entirely within 0-liquidity regions are not supported
(address tokenIn, address tokenOut, uint24 fee) = path.decodeFirstPool();
CallbackValidation.verifyCallback(factory, tokenIn, tokenOut, fee);
(bool isExactInput, uint256 amountToPay, uint256 amountReceived) =
amount0Delta > 0
? (tokenIn < tokenOut, uint256(amount0Delta), uint256(-amount1Delta))
: (tokenOut < tokenIn, uint256(amount1Delta), uint256(-amount0Delta));
IUniswapV3Pool pool = getPool(tokenIn, tokenOut, fee);
(uint160 sqrtPriceX96After, int24 tickAfter, , , , , ) = pool.slot0();
if (isExactInput) {
assembly {
let ptr := mload(0x40)
mstore(ptr, amountReceived)
mstore(add(ptr, 0x20), sqrtPriceX96After)
mstore(add(ptr, 0x40), tickAfter)
revert(ptr, 96)
}
} else {
// if the cache has been populated, ensure that the full output amount has been received
if (amountOutCached != 0) require(amountReceived == amountOutCached);
assembly {
let ptr := mload(0x40)
mstore(ptr, amountToPay)
mstore(add(ptr, 0x20), sqrtPriceX96After)
mstore(add(ptr, 0x40), tickAfter)
revert(ptr, 96)
}
}
}
/// @dev Parses a revert reason that should contain the numeric quote
function parseRevertReason(bytes memory reason)
private
pure
returns (
uint256 amount,
uint160 sqrtPriceX96After,
int24 tickAfter
)
{
if (reason.length != 96) {
if (reason.length < 68) revert('Unexpected error');
assembly {
reason := add(reason, 0x04)
}
revert(abi.decode(reason, (string)));
}
return abi.decode(reason, (uint256, uint160, int24));
}
function handleRevert(
bytes memory reason,
IUniswapV3Pool pool,
uint256 gasEstimate
)
private
view
returns (
uint256 amount,
uint160 sqrtPriceX96After,
uint32 initializedTicksCrossed,
uint256
)
{
int24 tickBefore;
int24 tickAfter;
(, tickBefore, , , , , ) = pool.slot0();
(amount, sqrtPriceX96After, tickAfter) = parseRevertReason(reason);
initializedTicksCrossed = pool.countInitializedTicksCrossed(tickBefore, tickAfter);
return (amount, sqrtPriceX96After, initializedTicksCrossed, gasEstimate);
}
function quoteExactInputSingle(QuoteExactInputSingleParams memory params)
public
override
returns (
uint256 amountOut,
uint160 sqrtPriceX96After,
uint32 initializedTicksCrossed,
uint256 gasEstimate
)
{
bool zeroForOne = params.tokenIn < params.tokenOut;
IUniswapV3Pool pool = getPool(params.tokenIn, params.tokenOut, params.fee);
uint256 gasBefore = gasleft();
try
pool.swap(
address(this), // address(0) might cause issues with some tokens
zeroForOne,
params.amountIn.toInt256(),
params.sqrtPriceLimitX96 == 0
? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1)
: params.sqrtPriceLimitX96,
abi.encodePacked(params.tokenIn, params.fee, params.tokenOut)
)
{} catch (bytes memory reason) {
gasEstimate = gasBefore - gasleft();
return handleRevert(reason, pool, gasEstimate);
}
}
function quoteExactInput(bytes memory path, uint256 amountIn)
public
override
returns (
uint256 amountOut,
uint160[] memory sqrtPriceX96AfterList,
uint32[] memory initializedTicksCrossedList,
uint256 gasEstimate
)
{
sqrtPriceX96AfterList = new uint160[](path.numPools());
initializedTicksCrossedList = new uint32[](path.numPools());
uint256 i = 0;
while (true) {
(address tokenIn, address tokenOut, uint24 fee) = path.decodeFirstPool();
// the outputs of prior swaps become the inputs to subsequent ones
(uint256 _amountOut, uint160 _sqrtPriceX96After, uint32 _initializedTicksCrossed, uint256 _gasEstimate) =
quoteExactInputSingle(
QuoteExactInputSingleParams({
tokenIn: tokenIn,
tokenOut: tokenOut,
fee: fee,
amountIn: amountIn,
sqrtPriceLimitX96: 0
})
);
sqrtPriceX96AfterList[i] = _sqrtPriceX96After;
initializedTicksCrossedList[i] = _initializedTicksCrossed;
amountIn = _amountOut;
gasEstimate += _gasEstimate;
i++;
// decide whether to continue or terminate
if (path.hasMultiplePools()) {
path = path.skipToken();
} else {
return (amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList, gasEstimate);
}
}
}
function quoteExactOutputSingle(QuoteExactOutputSingleParams memory params)
public
override
returns (
uint256 amountIn,
uint160 sqrtPriceX96After,
uint32 initializedTicksCrossed,
uint256 gasEstimate
)
{
bool zeroForOne = params.tokenIn < params.tokenOut;
IUniswapV3Pool pool = getPool(params.tokenIn, params.tokenOut, params.fee);
// if no price limit has been specified, cache the output amount for comparison in the swap callback
if (params.sqrtPriceLimitX96 == 0) amountOutCached = params.amount;
uint256 gasBefore = gasleft();
try
pool.swap(
address(this), // address(0) might cause issues with some tokens
zeroForOne,
-params.amount.toInt256(),
params.sqrtPriceLimitX96 == 0
? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1)
: params.sqrtPriceLimitX96,
abi.encodePacked(params.tokenOut, params.fee, params.tokenIn)
)
{} catch (bytes memory reason) {
gasEstimate = gasBefore - gasleft();
if (params.sqrtPriceLimitX96 == 0) delete amountOutCached; // clear cache
return handleRevert(reason, pool, gasEstimate);
}
}
function quoteExactOutput(bytes memory path, uint256 amountOut)
public
override
returns (
uint256 amountIn,
uint160[] memory sqrtPriceX96AfterList,
uint32[] memory initializedTicksCrossedList,
uint256 gasEstimate
)
{
sqrtPriceX96AfterList = new uint160[](path.numPools());
initializedTicksCrossedList = new uint32[](path.numPools());
uint256 i = 0;
while (true) {
(address tokenOut, address tokenIn, uint24 fee) = path.decodeFirstPool();
// the inputs of prior swaps become the outputs of subsequent ones
(uint256 _amountIn, uint160 _sqrtPriceX96After, uint32 _initializedTicksCrossed, uint256 _gasEstimate) =
quoteExactOutputSingle(
QuoteExactOutputSingleParams({
tokenIn: tokenIn,
tokenOut: tokenOut,
amount: amountOut,
fee: fee,
sqrtPriceLimitX96: 0
})
);
sqrtPriceX96AfterList[i] = _sqrtPriceX96After;
initializedTicksCrossedList[i] = _initializedTicksCrossed;
amountOut = _amountIn;
gasEstimate += _gasEstimate;
i++;
// decide whether to continue or terminate
if (path.hasMultiplePools()) {
path = path.skipToken();
} else {
return (amountOut, sqrtPriceX96AfterList, initializedTicksCrossedList, gasEstimate);
}
}
}
}