238 lines
9.1 KiB
Solidity
238 lines
9.1 KiB
Solidity
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
pragma solidity =0.7.6;
|
|
pragma abicoder v2;
|
|
|
|
import '@uniswap/v3-periphery/contracts/base/PeripheryImmutableState.sol';
|
|
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 '@uniswap/v3-periphery/contracts/libraries/Path.sol';
|
|
import '@uniswap/v3-periphery/contracts/libraries/PoolAddress.sol';
|
|
import '@uniswap/v3-periphery/contracts/libraries/CallbackValidation.sol';
|
|
import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';
|
|
|
|
import '../base/ImmutableState.sol';
|
|
import '../interfaces/IMixedRouteQuoterV1.sol';
|
|
import '../libraries/PoolTicksCounter.sol';
|
|
import '../libraries/UniswapV2Library.sol';
|
|
|
|
/// @title Provides on chain quotes for V3, V2, and MixedRoute exact input swaps
|
|
/// @notice Allows getting the expected amount out for a given swap without executing the swap
|
|
/// @notice Does not support exact output swaps since using the contract balance between exactOut swaps is not supported
|
|
/// @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 MixedRouteQuoterV1 is IMixedRouteQuoterV1, IUniswapV3SwapCallback, PeripheryImmutableState {
|
|
using Path for bytes;
|
|
using SafeCast for uint256;
|
|
using PoolTicksCounter for IUniswapV3Pool;
|
|
address public immutable factoryV2;
|
|
/// @dev Value to bit mask with path fee to determine if V2 or V3 route
|
|
// max V3 fee: 000011110100001001000000 (24 bits)
|
|
// mask: 1 << 23 = 100000000000000000000000 = decimal value 8388608
|
|
uint24 private constant flagBitmask = 8388608;
|
|
|
|
/// @dev Transient storage variable used to check a safety condition in exact output swaps.
|
|
uint256 private amountOutCached;
|
|
|
|
constructor(
|
|
address _factory,
|
|
address _factoryV2,
|
|
address _WETH9
|
|
) PeripheryImmutableState(_factory, _WETH9) {
|
|
factoryV2 = _factoryV2;
|
|
}
|
|
|
|
function getPool(
|
|
address tokenA,
|
|
address tokenB,
|
|
uint24 fee
|
|
) private view returns (IUniswapV3Pool) {
|
|
return IUniswapV3Pool(PoolAddress.computeAddress(factory, PoolAddress.getPoolKey(tokenA, tokenB, fee)));
|
|
}
|
|
|
|
/// @dev Given an amountIn, fetch the reserves of the V2 pair and get the amountOut
|
|
function getPairAmountOut(
|
|
uint256 amountIn,
|
|
address tokenIn,
|
|
address tokenOut
|
|
) private view returns (uint256) {
|
|
(uint256 reserveIn, uint256 reserveOut) = UniswapV2Library.getReserves(factoryV2, tokenIn, tokenOut);
|
|
return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
|
|
}
|
|
|
|
/// @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 amountReceived) =
|
|
amount0Delta > 0
|
|
? (tokenIn < tokenOut, uint256(-amount1Delta))
|
|
: (tokenOut < tokenIn, uint256(-amount0Delta));
|
|
|
|
IUniswapV3Pool pool = getPool(tokenIn, tokenOut, fee);
|
|
(uint160 v3SqrtPriceX96After, int24 tickAfter, , , , , ) = pool.slot0();
|
|
|
|
if (isExactInput) {
|
|
assembly {
|
|
let ptr := mload(0x40)
|
|
mstore(ptr, amountReceived)
|
|
mstore(add(ptr, 0x20), v3SqrtPriceX96After)
|
|
mstore(add(ptr, 0x40), tickAfter)
|
|
revert(ptr, 0x60)
|
|
}
|
|
} else {
|
|
/// since we don't support exactOutput, revert here
|
|
revert('Exact output quote not supported');
|
|
}
|
|
}
|
|
|
|
/// @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 != 0x60) {
|
|
if (reason.length < 0x44) revert('Unexpected error');
|
|
assembly {
|
|
reason := add(reason, 0x04)
|
|
}
|
|
revert(abi.decode(reason, (string)));
|
|
}
|
|
return abi.decode(reason, (uint256, uint160, int24));
|
|
}
|
|
|
|
function handleV3Revert(
|
|
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);
|
|
}
|
|
|
|
/// @dev Fetch an exactIn quote for a V3 Pool on chain
|
|
function quoteExactInputSingleV3(QuoteExactInputSingleV3Params 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 handleV3Revert(reason, pool, gasEstimate);
|
|
}
|
|
}
|
|
|
|
/// @dev Fetch an exactIn quote for a V2 pair on chain
|
|
function quoteExactInputSingleV2(QuoteExactInputSingleV2Params memory params)
|
|
public
|
|
view
|
|
override
|
|
returns (uint256 amountOut)
|
|
{
|
|
amountOut = getPairAmountOut(params.amountIn, params.tokenIn, params.tokenOut);
|
|
}
|
|
|
|
/// @dev Get the quote for an exactIn swap between an array of V2 and/or V3 pools
|
|
/// @notice To encode a V2 pair within the path, use 0x800000 (hex value of 8388608) for the fee between the two token addresses
|
|
function quoteExactInput(bytes memory path, uint256 amountIn)
|
|
public
|
|
override
|
|
returns (
|
|
uint256 amountOut,
|
|
uint160[] memory v3SqrtPriceX96AfterList,
|
|
uint32[] memory v3InitializedTicksCrossedList,
|
|
uint256 v3SwapGasEstimate
|
|
)
|
|
{
|
|
v3SqrtPriceX96AfterList = new uint160[](path.numPools());
|
|
v3InitializedTicksCrossedList = new uint32[](path.numPools());
|
|
|
|
uint256 i = 0;
|
|
while (true) {
|
|
(address tokenIn, address tokenOut, uint24 fee) = path.decodeFirstPool();
|
|
|
|
if (fee & flagBitmask != 0) {
|
|
amountIn = quoteExactInputSingleV2(
|
|
QuoteExactInputSingleV2Params({tokenIn: tokenIn, tokenOut: tokenOut, amountIn: amountIn})
|
|
);
|
|
} else {
|
|
/// the outputs of prior swaps become the inputs to subsequent ones
|
|
(
|
|
uint256 _amountOut,
|
|
uint160 _sqrtPriceX96After,
|
|
uint32 _initializedTicksCrossed,
|
|
uint256 _gasEstimate
|
|
) =
|
|
quoteExactInputSingleV3(
|
|
QuoteExactInputSingleV3Params({
|
|
tokenIn: tokenIn,
|
|
tokenOut: tokenOut,
|
|
fee: fee,
|
|
amountIn: amountIn,
|
|
sqrtPriceLimitX96: 0
|
|
})
|
|
);
|
|
v3SqrtPriceX96AfterList[i] = _sqrtPriceX96After;
|
|
v3InitializedTicksCrossedList[i] = _initializedTicksCrossed;
|
|
v3SwapGasEstimate += _gasEstimate;
|
|
amountIn = _amountOut;
|
|
}
|
|
i++;
|
|
|
|
/// decide whether to continue or terminate
|
|
if (path.hasMultiplePools()) {
|
|
path = path.skipToken();
|
|
} else {
|
|
return (amountIn, v3SqrtPriceX96AfterList, v3InitializedTicksCrossedList, v3SwapGasEstimate);
|
|
}
|
|
}
|
|
}
|
|
}
|