-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
Copy pathQuoter.sol
170 lines (149 loc) · 6.44 KB
/
Quoter.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
// 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/interfaces/IUniswapV3Pool.sol';
import '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol';
import '../interfaces/IQuoter.sol';
import '../base/PeripheryImmutableState.sol';
import '../libraries/Path.sol';
import '../libraries/PoolAddress.sol';
import '../libraries/CallbackValidation.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 Quoter is IQuoter, IUniswapV3SwapCallback, PeripheryImmutableState {
using Path for bytes;
using SafeCast for uint256;
/// @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));
if (isExactInput) {
assembly {
let ptr := mload(0x40)
mstore(ptr, amountReceived)
revert(ptr, 32)
}
} 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)
revert(ptr, 32)
}
}
}
/// @dev Parses a revert reason that should contain the numeric quote
function parseRevertReason(bytes memory reason) private pure returns (uint256) {
if (reason.length != 32) {
if (reason.length < 68) revert('Unexpected error');
assembly {
reason := add(reason, 0x04)
}
revert(abi.decode(reason, (string)));
}
return abi.decode(reason, (uint256));
}
/// @inheritdoc IQuoter
function quoteExactInputSingle(
address tokenIn,
address tokenOut,
uint24 fee,
uint256 amountIn,
uint160 sqrtPriceLimitX96
) public override returns (uint256 amountOut) {
bool zeroForOne = tokenIn < tokenOut;
try
getPool(tokenIn, tokenOut, fee).swap(
address(this), // address(0) might cause issues with some tokens
zeroForOne,
amountIn.toInt256(),
sqrtPriceLimitX96 == 0
? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1)
: sqrtPriceLimitX96,
abi.encodePacked(tokenIn, fee, tokenOut)
)
{} catch (bytes memory reason) {
return parseRevertReason(reason);
}
}
/// @inheritdoc IQuoter
function quoteExactInput(bytes memory path, uint256 amountIn) external override returns (uint256 amountOut) {
while (true) {
bool hasMultiplePools = path.hasMultiplePools();
(address tokenIn, address tokenOut, uint24 fee) = path.decodeFirstPool();
// the outputs of prior swaps become the inputs to subsequent ones
amountIn = quoteExactInputSingle(tokenIn, tokenOut, fee, amountIn, 0);
// decide whether to continue or terminate
if (hasMultiplePools) {
path = path.skipToken();
} else {
return amountIn;
}
}
}
/// @inheritdoc IQuoter
function quoteExactOutputSingle(
address tokenIn,
address tokenOut,
uint24 fee,
uint256 amountOut,
uint160 sqrtPriceLimitX96
) public override returns (uint256 amountIn) {
bool zeroForOne = tokenIn < tokenOut;
// if no price limit has been specified, cache the output amount for comparison in the swap callback
if (sqrtPriceLimitX96 == 0) amountOutCached = amountOut;
try
getPool(tokenIn, tokenOut, fee).swap(
address(this), // address(0) might cause issues with some tokens
zeroForOne,
-amountOut.toInt256(),
sqrtPriceLimitX96 == 0
? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1)
: sqrtPriceLimitX96,
abi.encodePacked(tokenOut, fee, tokenIn)
)
{} catch (bytes memory reason) {
if (sqrtPriceLimitX96 == 0) delete amountOutCached; // clear cache
return parseRevertReason(reason);
}
}
/// @inheritdoc IQuoter
function quoteExactOutput(bytes memory path, uint256 amountOut) external override returns (uint256 amountIn) {
while (true) {
bool hasMultiplePools = path.hasMultiplePools();
(address tokenOut, address tokenIn, uint24 fee) = path.decodeFirstPool();
// the inputs of prior swaps become the outputs of subsequent ones
amountOut = quoteExactOutputSingle(tokenIn, tokenOut, fee, amountOut, 0);
// decide whether to continue or terminate
if (hasMultiplePools) {
path = path.skipToken();
} else {
return amountOut;
}
}
}
}