Skip to content

Commit

Permalink
feat: abstract router (#86)
Browse files Browse the repository at this point in the history
* add PoolTicksCounter library

* quoter exact input single

* quoter test

* return deltas instead

* safe casting to correct types

* QuoteExactInput skeleton

* multiple entries

* break handleRevert by type

* quoteExactInput and unit tests

* more QuoteExactInput tests

* remove lgos

* remove commented out struct

* via-ir in ci

* remove unused imports/functions

* store iteration params locally instead of editing function input

* pull out sqrtPriceLimit to its own function

* PathKey to its own library

* rename initializedTicksCrossed to initializedTicksLoaded

* remove manual abi encoding in yul :p

* fix linter warnings for Quoter

* natspec for IQuoter

* feat: update v4-core

This commit updates v4 core to latest and fixes integration issues

* fix: tests

* style fixes

* inheritdoc

* ExactInSingleBatch

* fix: update tests

* fix: test router was borked

* exact out

* fix: alice comments

* fix ExactOutput

* add ExactOput unit tests

* add quoteExactOutputBatch

* remove solhint config

* remove newline

* add QuoteExactOutput in interface

* refactor lockAcquired

* move magic numbers to constants + doc

* add more natspec

* natspec

* named imports

* self-call branching

* remove old code

* remove console2 import

* refactor PathKeyLib

* amountOutCached

* inherit ILockCallback

* add base contracts and interfaces (#75)

* remove unused errors

* test lockAcquired reverts

* remove ...Batch interface

* REASON -> RESPONSE when valid

* complete natspec

* remove SwapInfo imports

* rename to SwapParameters

* move quoter structs into IQuoter interface

* update to latest core

* use prev values

* change twamm to use pool getters

* changes after merging main

* use --via-ir in cli

* fix formatting

* fix FullRange/TWAMM hook

* update ticks counter

* update Quoter test

* typo

* typo

* simplify handleRevertSingle

* merge QuoteInput/OutputSingle structs

* combine IQuoter structs

* using ... ordering

* update snapshots

* start routing

* start routing contract

* naive swapExactIn impl

* lint + bytecode snapshot

* change concept of hops to token hops

* UniswapV4Routing --> Routing

* use PathKey

* exactInputSingle

* save DRY progress

* no sqrtPriceLimit for multipool hops

* exactOut implemented w awkward loops/int conversions

single hops passing on exactOut

* gas savings from not doing  double negative number

* gas savings from unchecked math

* add swapExactOuputSingle

* break out structs into interface

* PR comments

* pass hook data along

* gas and coherency optimization

* merge Quoter

* remove SwapIntention

* IV4Router structs

* remove SwapIntention

* remove logs

* natspec

* rebase onto latest core

* simplify function naming

* Routing diana (#104)

* feat: Revert style quoter (#73)

* add PoolTicksCounter library

* quoter exact input single

* quoter test

* return deltas instead

* safe casting to correct types

* QuoteExactInput skeleton

* multiple entries

* break handleRevert by type

* quoteExactInput and unit tests

* more QuoteExactInput tests

* remove lgos

* remove commented out struct

* via-ir in ci

* remove unused imports/functions

* store iteration params locally instead of editing function input

* pull out sqrtPriceLimit to its own function

* PathKey to its own library

* rename initializedTicksCrossed to initializedTicksLoaded

* remove manual abi encoding in yul :p

* fix linter warnings for Quoter

* natspec for IQuoter

* feat: update v4-core

This commit updates v4 core to latest and fixes integration issues

* fix: tests

* style fixes

* inheritdoc

* ExactInSingleBatch

* fix: update tests

* fix: test router was borked

* exact out

* fix: alice comments

* fix ExactOutput

* add ExactOput unit tests

* add quoteExactOutputBatch

* remove solhint config

* remove newline

* add QuoteExactOutput in interface

* refactor lockAcquired

* move magic numbers to constants + doc

* add more natspec

* natspec

* named imports

* self-call branching

* remove old code

* remove console2 import

* refactor PathKeyLib

* amountOutCached

* inherit ILockCallback

* add base contracts and interfaces (#75)

* remove unused errors

* test lockAcquired reverts

* remove ...Batch interface

* REASON -> RESPONSE when valid

* complete natspec

* remove SwapInfo imports

* rename to SwapParameters

* move quoter structs into IQuoter interface

* update to latest core

* use prev values

* change twamm to use pool getters

* changes after merging main

* use --via-ir in cli

* fix formatting

* fix FullRange/TWAMM hook

* update ticks counter

* update Quoter test

* typo

* typo

* simplify handleRevertSingle

* merge QuoteInput/OutputSingle structs

* combine IQuoter structs

* using ... ordering

* update snapshots

* move amountOutCached into inner call

* using PathKeyLib for PathKey

* fix amountOutCached

* remove console2 import

* resurface revert reason

* clean up validateRevert

* update natsppec

* remove unused

---------

Co-authored-by: Mark Toda <toda.mark@gmail.com>
Co-authored-by: Tina <59578595+tinaszheng@users.noreply.github.com>
Co-authored-by: Sara Reynolds <snreynolds2506@gmail.com>

* (Quoter) Avoid IR (#93)

* avoid stack too deep

* pack local variables into structs; remove need for IR

* reorg struct

* snapshots

* forge fmt

* restore settings

* remove IR

* ensure tokens are ordered properly by using salts

* gas snapshot

* remove console logs

* chore: update v4-core:latest (#89)

* update v4-core

* update to new liquidity hooks

* forge fmt; reuse v4-core justfile

* snapshots

* rename getHooksCalls --> getHookPermissions

* enforce permanent liquidity with beforeRemoveLiquidity

* snapshot

* update v4-core (again)

* snapshots with new v4-core

* v4-core:latest

* pin 0.8.24

* merge in remote; regenerate snapshots

* remove justfile

* repin cancun

* pin token addresses using vm.etch

* snapshots

* forge fmt

* remove via-ir and custom solc from CI

* test nit

* Update v4-core submodule to use https (#97)

Co-authored-by: saucepoint <98790946+saucepoint@users.noreply.github.com>

* chore: add semgrep (#94)

* [Chore] Update v4-core:latest (#100)

* Update v4-core

* Update various examples, BaseHook, Quoter and tests

* Remove nested locking for LimitOrder

* Fix Quoter

* update v4-core

* fix: remove getLocker as its a bool now

* update v4-core: flipped signs, push dynamic fees

* fix: flip delta signs

* flip delta signs

* flip delta signs

* flip delta signs

* fix getSlot0 calls

* snapshots

* remove deadcode

* remove unused param

* update core

* update for modifyLiquidity; misc doc updates

* correct min int256

* allow for manual fee updates

---------

Co-authored-by: saucepoint <saucepoint@protonmail.com>

* changes with core update

* fix casing

* switch versions back

* Updated lib/v4-core submodule to main branch

* switch to 0.8.19

---------

Co-authored-by: Zach Yang <zhiyuan.zach.yang@gmail.com>
Co-authored-by: Mark Toda <toda.mark@gmail.com>
Co-authored-by: Tina <59578595+tinaszheng@users.noreply.github.com>
Co-authored-by: Sara Reynolds <snreynolds2506@gmail.com>
Co-authored-by: saucepoint <98790946+saucepoint@users.noreply.github.com>
Co-authored-by: 0x57 <wqi@umich.edu>
Co-authored-by: mr-uniswap <144828035+mr-uniswap@users.noreply.github.com>
Co-authored-by: 0x57 <qi.wu@coinbase.com>
Co-authored-by: saucepoint <saucepoint@protonmail.com>
Co-authored-by: Alice Henshaw <henshawalice@gmail.com>

* update imports

---------

Co-authored-by: Mark Toda <toda.mark@gmail.com>
Co-authored-by: Tina <59578595+tinaszheng@users.noreply.github.com>
Co-authored-by: Sara Reynolds <snreynolds2506@gmail.com>
Co-authored-by: Emily Williams <emag3m@gmail.com>
Co-authored-by: Alice Henshaw <henshawalice@gmail.com>
Co-authored-by: diana <dlkocsis04@gmail.com>
Co-authored-by: saucepoint <98790946+saucepoint@users.noreply.github.com>
Co-authored-by: 0x57 <wqi@umich.edu>
Co-authored-by: mr-uniswap <144828035+mr-uniswap@users.noreply.github.com>
Co-authored-by: 0x57 <qi.wu@coinbase.com>
Co-authored-by: saucepoint <saucepoint@protonmail.com>
  • Loading branch information
12 people authored Jun 28, 2024
1 parent 7c6675b commit aa148b3
Show file tree
Hide file tree
Showing 17 changed files with 120 additions and 115 deletions.
2 changes: 1 addition & 1 deletion .forge-snapshots/RouterBytecode.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5027
5925
2 changes: 1 addition & 1 deletion .forge-snapshots/RouterExactIn1Hop.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
195446
101468
2 changes: 1 addition & 1 deletion .forge-snapshots/RouterExactIn2Hops.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
273998
160449
2 changes: 1 addition & 1 deletion .forge-snapshots/RouterExactIn3Hops.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
352561
212680
2 changes: 1 addition & 1 deletion .forge-snapshots/RouterExactInputSingle.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
193868
106771
2 changes: 1 addition & 1 deletion .forge-snapshots/RouterExactOut1Hop.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
194612
102242
2 changes: 1 addition & 1 deletion .forge-snapshots/RouterExactOut2Hops.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
274060
159812
2 changes: 1 addition & 1 deletion .forge-snapshots/RouterExactOut3Hops.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
353539
212732
2 changes: 1 addition & 1 deletion .forge-snapshots/RouterExactOutputSingle.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
193088
105380
78 changes: 39 additions & 39 deletions contracts/V4Router.sol
Original file line number Diff line number Diff line change
@@ -1,59 +1,58 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol";
import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol";
import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol";
import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol";
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol";
import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol";
import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {PathKey} from "./libraries/PathKey.sol";
import {IV4Router} from "./interfaces/IV4Router.sol";

/// @title UniswapV4Routing
/// @title UniswapV4Router
/// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools
abstract contract V4Router is IV4Router {
using CurrencyLibrary for Currency;

IPoolManager immutable poolManager;

/// @dev Only the pool manager may call this function
modifier poolManagerOnly() {
if (msg.sender != address(poolManager)) revert NotPoolManager();
_;
}

constructor(IPoolManager _poolManager) {
poolManager = _poolManager;
}

function _v4Swap(SwapType swapType, bytes memory params) internal {
poolManager.lock(abi.encode(SwapInfo(swapType, msg.sender, params)));
poolManager.unlock(abi.encode(SwapInfo(swapType, msg.sender, params)));
}

function lockAcquired(bytes calldata encodedSwapInfo) external poolManagerOnly returns (bytes memory) {
/// @inheritdoc IUnlockCallback
function unlockCallback(bytes calldata encodedSwapInfo) external override returns (bytes memory) {
if (msg.sender != address(poolManager)) revert NotPoolManager();

SwapInfo memory swapInfo = abi.decode(encodedSwapInfo, (SwapInfo));

if (swapInfo.swapType == SwapType.ExactInput) {
_swapExactInput(abi.decode(swapInfo.params, (ExactInputParams)), swapInfo.msgSender);
_swapExactInput(abi.decode(swapInfo.params, (IV4Router.ExactInputParams)), swapInfo.msgSender);
} else if (swapInfo.swapType == SwapType.ExactInputSingle) {
_swapExactInputSingle(abi.decode(swapInfo.params, (ExactInputSingleParams)), swapInfo.msgSender);
_swapExactInputSingle(abi.decode(swapInfo.params, (IV4Router.ExactInputSingleParams)), swapInfo.msgSender);
} else if (swapInfo.swapType == SwapType.ExactOutput) {
_swapExactOutput(abi.decode(swapInfo.params, (ExactOutputParams)), swapInfo.msgSender);
_swapExactOutput(abi.decode(swapInfo.params, (IV4Router.ExactOutputParams)), swapInfo.msgSender);
} else if (swapInfo.swapType == SwapType.ExactOutputSingle) {
_swapExactOutputSingle(abi.decode(swapInfo.params, (ExactOutputSingleParams)), swapInfo.msgSender);
_swapExactOutputSingle(abi.decode(swapInfo.params, (IV4Router.ExactOutputSingleParams)), swapInfo.msgSender);
} else {
revert InvalidSwapType();
}

return bytes("");
}

function _swapExactInputSingle(ExactInputSingleParams memory params, address msgSender) private {
_swapExactPrivate(
function _swapExactInputSingle(IV4Router.ExactInputSingleParams memory params, address msgSender) private {
_swap(
params.poolKey,
params.zeroForOne,
int256(int128(params.amountIn)),
int256(-int128(params.amountIn)),
params.sqrtPriceLimitX96,
msgSender,
true,
Expand All @@ -62,18 +61,18 @@ abstract contract V4Router is IV4Router {
);
}

function _swapExactInput(ExactInputParams memory params, address msgSender) private {
function _swapExactInput(IV4Router.ExactInputParams memory params, address msgSender) private {
unchecked {
uint256 pathLength = params.path.length;
uint128 amountOut;

for (uint256 i = 0; i < pathLength; i++) {
(PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(params.path[i], params.currencyIn);
amountOut = uint128(
-_swapExactPrivate(
_swap(
poolKey,
zeroForOne,
int256(int128(params.amountIn)),
int256(-int128(params.amountIn)),
0,
msgSender,
i == 0,
Expand All @@ -90,11 +89,11 @@ abstract contract V4Router is IV4Router {
}
}

function _swapExactOutputSingle(ExactOutputSingleParams memory params, address msgSender) private {
_swapExactPrivate(
function _swapExactOutputSingle(IV4Router.ExactOutputSingleParams memory params, address msgSender) private {
_swap(
params.poolKey,
params.zeroForOne,
-int256(int128(params.amountOut)),
int256(int128(params.amountOut)),
params.sqrtPriceLimitX96,
msgSender,
true,
Expand All @@ -103,7 +102,7 @@ abstract contract V4Router is IV4Router {
);
}

function _swapExactOutput(ExactOutputParams memory params, address msgSender) private {
function _swapExactOutput(IV4Router.ExactOutputParams memory params, address msgSender) private {
unchecked {
uint256 pathLength = params.path.length;
uint128 amountIn;
Expand All @@ -112,10 +111,10 @@ abstract contract V4Router is IV4Router {
(PoolKey memory poolKey, bool oneForZero) =
_getPoolAndSwapDirection(params.path[i - 1], params.currencyOut);
amountIn = uint128(
_swapExactPrivate(
-_swap(
poolKey,
!oneForZero,
-int256(int128(params.amountOut)),
int256(int128(params.amountOut)),
0,
msgSender,
i == 1,
Expand All @@ -131,7 +130,7 @@ abstract contract V4Router is IV4Router {
}
}

function _swapExactPrivate(
function _swap(
PoolKey memory poolKey,
bool zeroForOne,
int256 amountSpecified,
Expand All @@ -147,20 +146,20 @@ abstract contract V4Router is IV4Router {
zeroForOne,
amountSpecified,
sqrtPriceLimitX96 == 0
? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1)
? (zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1)
: sqrtPriceLimitX96
),
hookData
);

if (zeroForOne) {
reciprocalAmount = amountSpecified > 0 ? delta.amount1() : delta.amount0();
reciprocalAmount = amountSpecified < 0 ? delta.amount1() : delta.amount0();
if (settle) _payAndSettle(poolKey.currency0, msgSender, delta.amount0());
if (take) poolManager.take(poolKey.currency1, msgSender, uint128(-delta.amount1()));
if (take) poolManager.take(poolKey.currency1, msgSender, uint128(delta.amount1()));
} else {
reciprocalAmount = amountSpecified > 0 ? delta.amount0() : delta.amount1();
reciprocalAmount = amountSpecified < 0 ? delta.amount0() : delta.amount1();
if (settle) _payAndSettle(poolKey.currency1, msgSender, delta.amount1());
if (take) poolManager.take(poolKey.currency0, msgSender, uint128(-delta.amount0()));
if (take) poolManager.take(poolKey.currency0, msgSender, uint128(delta.amount0()));
}
}

Expand All @@ -178,7 +177,8 @@ abstract contract V4Router is IV4Router {
}

function _payAndSettle(Currency currency, address msgSender, int128 settleAmount) private {
_pay(Currency.unwrap(currency), msgSender, address(poolManager), uint256(uint128(settleAmount)));
poolManager.sync(currency);
_pay(Currency.unwrap(currency), msgSender, address(poolManager), uint256(uint128(-settleAmount)));
poolManager.settle(currency);
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/hooks/examples/LimitOrder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ contract LimitOrder is BaseHook {
) external selfOnly returns (uint128 amount0Fee, uint128 amount1Fee) {
int24 tickUpper = tickLower + key.tickSpacing;

// because `modifyPosition` includes not just principal value but also fees, we cannot allocate
// because `modifyLiquidity` includes not just principal value but also fees, we cannot allocate
// the proceeds pro-rata. if we were to do so, users who have been in a limit order that's partially filled
// could be unfairly diluted by a user sychronously placing then killing a limit order to skim off fees.
// to prevent this, we allocate all fee revenue to remaining limit order placers, unless this is the last order.
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IPeripheryPayments.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
pragma solidity ^0.8.20;

import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";

Expand Down
29 changes: 10 additions & 19 deletions contracts/interfaces/IV4Router.sol
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

import "forge-std/console.sol";
import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol";
import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol";
import {BalanceDelta} from "@uniswap/v4-core/contracts/types/BalanceDelta.sol";
import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol";
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol";
import {TickMath} from "@uniswap/v4-core/contracts/libraries/TickMath.sol";
import {IHooks} from "@uniswap/v4-core/contracts/interfaces/IHooks.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {PathKey} from "../libraries/PathKey.sol";

/// @title UniswapV4Routing
/// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools
interface IV4Router {
interface IV4Router is IUnlockCallback {
error NotPoolManager();
error InvalidSwapType();
error TooLittleReceived();
Expand All @@ -24,14 +25,6 @@ interface IV4Router {
bytes params;
}

struct PathKey {
Currency intermediateCurrency;
uint24 fee;
int24 tickSpacing;
IHooks hooks;
bytes hookData;
}

struct ExactInputSingleParams {
PoolKey poolKey;
bool zeroForOne;
Expand Down Expand Up @@ -74,6 +67,4 @@ interface IV4Router {
ExactOutput,
ExactOutputSingle
}

function lockAcquired(bytes calldata encodedSwapInfo) external returns (bytes memory);
}
2 changes: 1 addition & 1 deletion test/FullRange.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ contract TestFullRange is Test, Deployers, GasSnapshot {
int24 tickSpacing,
IHooks hooks
);
event ModifyPosition(
event ModifyLiquidity(
PoolId indexed poolId, address indexed sender, int24 tickLower, int24 tickUpper, int256 liquidityDelta
);
event Swap(
Expand Down
Loading

0 comments on commit aa148b3

Please sign in to comment.