Skip to content

Commit

Permalink
optimize quoter
Browse files Browse the repository at this point in the history
  • Loading branch information
Jun1on committed Aug 13, 2024
1 parent 81e9692 commit 3f69c05
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 39 deletions.
2 changes: 1 addition & 1 deletion .forge-snapshots/MIDDLEWARE_PROTECT-multi-protected.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
914551
184020
2 changes: 1 addition & 1 deletion .forge-snapshots/MIDDLEWARE_PROTECT-multi-vanilla.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
475485
143854
2 changes: 1 addition & 1 deletion .forge-snapshots/MIDDLEWARE_PROTECT-protected.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
201607
151513
2 changes: 1 addition & 1 deletion .forge-snapshots/MIDDLEWARE_PROTECT-vanilla.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
147725
124869
190 changes: 190 additions & 0 deletions src/middleware/CheapQuoter.sol
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;
}
}
}
18 changes: 8 additions & 10 deletions src/middleware/MiddlewareProtect.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {console} from "forge-std/console.sol";
import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {IViewQuoter} from "../interfaces/IViewQuoter.sol";
import {CheapQuoter} from "./CheapQuoter.sol";

contract MiddlewareProtect is BaseMiddleware {
using CustomRevert for bytes4;
Expand All @@ -44,25 +44,23 @@ contract MiddlewareProtect is BaseMiddleware {

bytes internal constant ZERO_BYTES = bytes("");

IViewQuoter public immutable viewQuoter;
CheapQuoter public immutable cheapQuoter;

// todo: use tstore
int256 private quote;

constructor(IPoolManager _manager, IViewQuoter _viewQuoter, address _impl) BaseMiddleware(_manager, _impl) {
viewQuoter = _viewQuoter;
constructor(IPoolManager _poolManager, CheapQuoter _cheapQuoter, address _impl)
BaseMiddleware(_poolManager, _impl)
{
cheapQuoter = _cheapQuoter;
_ensureValidFlags();
}

function beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata)
function beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata)
external
returns (bytes4, BeforeSwapDelta, uint24)
{
if (params.zeroForOne) {
(, quote,,) = viewQuoter.quoteSingle(key, params);
} else {
(quote,,,) = viewQuoter.quoteSingle(key, params);
}
quote = cheapQuoter.quote(key, params);
(bool success, bytes memory returnData) = address(implementation).delegatecall(msg.data);
if (!success) {
_handleRevert(returnData);
Expand Down
10 changes: 5 additions & 5 deletions src/middleware/MiddlewareProtectFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ pragma solidity ^0.8.19;

import {MiddlewareProtect} from "./MiddlewareProtect.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IViewQuoter} from "../interfaces/IViewQuoter.sol";
import {CheapQuoter} from "./CheapQuoter.sol";

contract MiddlewareProtectFactory {
event MiddlewareCreated(address implementation, address middleware);

mapping(address => address) private _implementations;

IPoolManager public immutable poolManager;
IViewQuoter public immutable viewQuoter;
CheapQuoter public immutable cheapQuoter;

constructor(IPoolManager _poolManager, IViewQuoter _viewQuoter) {
constructor(IPoolManager _poolManager, CheapQuoter _cheapQuoter) {
poolManager = _poolManager;
viewQuoter = _viewQuoter;
cheapQuoter = _cheapQuoter;
}

/**
Expand All @@ -34,7 +34,7 @@ contract MiddlewareProtectFactory {
* @return middleware The address of the newly created middlewareRemove contract.
*/
function createMiddleware(address implementation, bytes32 salt) external returns (address middleware) {
middleware = address(new MiddlewareProtect{salt: salt}(poolManager, viewQuoter, implementation));
middleware = address(new MiddlewareProtect{salt: salt}(poolManager, cheapQuoter, implementation));
_implementations[middleware] = implementation;
emit MiddlewareCreated(implementation, middleware);
}
Expand Down
Loading

0 comments on commit 3f69c05

Please sign in to comment.