-
Notifications
You must be signed in to change notification settings - Fork 504
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
242 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
914551 | ||
184020 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
475485 | ||
143854 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
201607 | ||
151513 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
147725 | ||
124869 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity 0.8.26; | ||
|
||
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; | ||
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; | ||
import {SwapMath} from "@uniswap/v4-core/src/libraries/SwapMath.sol"; | ||
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; | ||
import "@uniswap/v4-core/src/libraries/SafeCast.sol"; | ||
import {LiquidityMath} from "@uniswap/v4-core/src/libraries/LiquidityMath.sol"; | ||
import {PoolTickBitmap} from "../libraries/PoolTickBitmap.sol"; | ||
import {Slot0, Slot0Library} from "@uniswap/v4-core/src/types/Slot0.sol"; | ||
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol"; | ||
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; | ||
import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; | ||
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; | ||
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; | ||
|
||
contract CheapQuoter { | ||
IPoolManager public immutable poolManager; | ||
|
||
using SafeCast for uint256; | ||
using SafeCast for int256; | ||
|
||
using Slot0Library for Slot0; | ||
using StateLibrary for IPoolManager; | ||
using PoolIdLibrary for PoolKey; | ||
|
||
struct Slot0Struct { | ||
// the current price | ||
uint160 sqrtPriceX96; | ||
// the current tick | ||
int24 tick; | ||
// tick spacing | ||
int24 tickSpacing; | ||
} | ||
|
||
// used for packing under the stack limit | ||
struct QuoteParams { | ||
bool zeroForOne; | ||
bool exactInput; | ||
uint24 fee; | ||
uint160 sqrtPriceLimitX96; | ||
} | ||
|
||
struct SwapCache { | ||
// the protocol fee for the input token | ||
uint8 feeProtocol; | ||
// liquidity at the beginning of the swap | ||
uint128 liquidityStart; | ||
// the timestamp of the current block | ||
uint32 blockTimestamp; | ||
// the current value of the tick accumulator, computed only if we cross an initialized tick | ||
int56 tickCumulative; | ||
// the current value of seconds per liquidity accumulator, computed only if we cross an initialized tick | ||
uint160 secondsPerLiquidityCumulativeX128; | ||
// whether we've computed and cached the above two accumulators | ||
bool computedLatestObservation; | ||
} | ||
|
||
// the top level state of the swap, the results of which are recorded in storage at the end | ||
struct SwapState { | ||
// the amount remaining to be swapped in/out of the input/output asset | ||
int256 amountSpecifiedRemaining; | ||
// the amount already swapped out/in of the output/input asset | ||
int256 amountCalculated; | ||
// current sqrt(price) | ||
uint160 sqrtPriceX96; | ||
// the tick associated with the current price | ||
int24 tick; | ||
// the global fee growth of the input token | ||
uint256 feeGrowthGlobalX128; | ||
// amount of input token paid as protocol fee | ||
uint128 protocolFee; | ||
// the current liquidity in range | ||
uint128 liquidity; | ||
} | ||
|
||
struct StepComputations { | ||
// the price at the beginning of the step | ||
uint160 sqrtPriceStartX96; | ||
// the next tick to swap to from the current tick in the swap direction | ||
int24 tickNext; | ||
// whether tickNext is initialized or not | ||
bool initialized; | ||
// sqrt(price) for the next tick (1/0) | ||
uint160 sqrtPriceNextX96; | ||
// how much is being swapped in in this step | ||
uint256 amountIn; | ||
// how much is being swapped out | ||
uint256 amountOut; | ||
// how much fee is being paid in | ||
uint256 feeAmount; | ||
} | ||
|
||
constructor(IPoolManager _poolManager) { | ||
poolManager = _poolManager; | ||
} | ||
|
||
function fillSlot0(PoolKey calldata poolKey) private view returns (Slot0Struct memory slot0) { | ||
(slot0.sqrtPriceX96, slot0.tick,,) = poolManager.getSlot0(poolKey.toId()); | ||
slot0.tickSpacing = poolKey.tickSpacing; | ||
return slot0; | ||
} | ||
|
||
function quote(PoolKey calldata poolKey, IPoolManager.SwapParams calldata swapParams) | ||
external | ||
view | ||
returns (int256 quote) | ||
{ | ||
QuoteParams memory quoteParams = QuoteParams( | ||
swapParams.zeroForOne, swapParams.amountSpecified < 0, poolKey.fee, swapParams.sqrtPriceLimitX96 | ||
); | ||
|
||
Slot0Struct memory slot0 = fillSlot0(poolKey); | ||
|
||
SwapState memory state = SwapState({ | ||
amountSpecifiedRemaining: -swapParams.amountSpecified, | ||
amountCalculated: 0, | ||
sqrtPriceX96: slot0.sqrtPriceX96, | ||
tick: slot0.tick, | ||
feeGrowthGlobalX128: 0, | ||
protocolFee: 0, | ||
liquidity: poolManager.getLiquidity(poolKey.toId()) | ||
}); | ||
|
||
while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != quoteParams.sqrtPriceLimitX96) { | ||
StepComputations memory step; | ||
|
||
step.sqrtPriceStartX96 = state.sqrtPriceX96; | ||
|
||
(step.tickNext, step.initialized) = PoolTickBitmap.nextInitializedTickWithinOneWord( | ||
poolManager, poolKey.toId(), slot0.tickSpacing, state.tick, quoteParams.zeroForOne | ||
); | ||
|
||
// ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds | ||
if (step.tickNext < TickMath.MIN_TICK) { | ||
step.tickNext = TickMath.MIN_TICK; | ||
} else if (step.tickNext > TickMath.MAX_TICK) { | ||
step.tickNext = TickMath.MAX_TICK; | ||
} | ||
|
||
// get the price for the next tick | ||
step.sqrtPriceNextX96 = TickMath.getSqrtPriceAtTick(step.tickNext); | ||
|
||
// compute values to swap to the target tick, price limit, or point where input/output amount is exhausted | ||
(state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = SwapMath.computeSwapStep( | ||
state.sqrtPriceX96, | ||
( | ||
quoteParams.zeroForOne | ||
? step.sqrtPriceNextX96 < quoteParams.sqrtPriceLimitX96 | ||
: step.sqrtPriceNextX96 > quoteParams.sqrtPriceLimitX96 | ||
) ? quoteParams.sqrtPriceLimitX96 : step.sqrtPriceNextX96, | ||
state.liquidity, | ||
-state.amountSpecifiedRemaining, | ||
quoteParams.fee | ||
); | ||
|
||
if (quoteParams.exactInput) { | ||
state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount).toInt256(); | ||
state.amountCalculated = state.amountCalculated + step.amountOut.toInt256(); | ||
} else { | ||
state.amountSpecifiedRemaining += step.amountOut.toInt256(); | ||
state.amountCalculated = state.amountCalculated - (step.amountIn + step.feeAmount).toInt256(); | ||
} | ||
|
||
// shift tick if we reached the next price | ||
if (state.sqrtPriceX96 == step.sqrtPriceNextX96) { | ||
// if the tick is initialized, run the tick transition | ||
if (step.initialized) { | ||
(, int128 liquidityNet,,) = poolManager.getTickInfo(poolKey.toId(), step.tickNext); | ||
|
||
// if we're moving leftward, we interpret liquidityNet as the opposite sign | ||
// safe because liquidityNet cannot be type(int128).min | ||
if (quoteParams.zeroForOne) liquidityNet = -liquidityNet; | ||
|
||
state.liquidity = LiquidityMath.addDelta(state.liquidity, liquidityNet); | ||
} | ||
|
||
state.tick = quoteParams.zeroForOne ? step.tickNext - 1 : step.tickNext; | ||
} else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) { | ||
// recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved | ||
state.tick = TickMath.getTickAtSqrtPrice(state.sqrtPriceX96); | ||
} | ||
|
||
quote = quoteParams.exactInput | ||
? state.amountCalculated | ||
: state.amountSpecifiedRemaining + swapParams.amountSpecified; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.