Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Routing #58

Merged
merged 64 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
b6f8d3b
start routing
ewilz Aug 17, 2023
d5dc31a
start routing contract
ewilz Aug 23, 2023
2ca710a
naive swapExactIn impl
ewilz Aug 24, 2023
664d2c2
lint + bytecode snapshot
ewilz Aug 30, 2023
8b687b8
change concept of hops to token hops
ewilz Aug 31, 2023
1717039
UniswapV4Routing --> Routing
ewilz Sep 1, 2023
2549c3a
use PathKey
ewilz Sep 1, 2023
e1c46fd
exactInputSingle
ewilz Sep 1, 2023
8aae01b
save DRY progress
ewilz Sep 1, 2023
5823d2f
no sqrtPriceLimit for multipool hops
ewilz Sep 5, 2023
7e25390
exactOut implemented w awkward loops/int conversions
ewilz Sep 5, 2023
607ea3b
gas savings from not doing double negative number
ewilz Sep 5, 2023
cf82527
gas savings from unchecked math
ewilz Sep 5, 2023
9ebc9d5
add swapExactOuputSingle
ewilz Sep 5, 2023
54a48e5
break out structs into interface
ewilz Sep 5, 2023
01a5db0
PR comments
ewilz Sep 25, 2023
31cbfe5
pass hook data along
ewilz Sep 26, 2023
d50f18d
gas and coherency optimization
ewilz Oct 2, 2023
4b010ff
updated lib/v4-core submodule to main branch
dianakocsis Mar 19, 2024
7c6675b
Merge branch 'main' into routing
hensha256 Jun 28, 2024
aa148b3
feat: abstract router (#86)
ConjunctiveNormalForm Jun 28, 2024
1586c0d
allow payer and recipient to be different
hensha256 Jul 2, 2024
06961bf
try to fix ci
hensha256 Jul 5, 2024
528f15d
improve casting
hensha256 Jul 5, 2024
0ac638a
reverting 3 commits to make branch
hensha256 Jul 17, 2024
1844d70
remove unused recipient param
hensha256 Jul 17, 2024
86039e4
remove payment logic from swap logic
hensha256 Jul 17, 2024
90f3f35
factor out payment functions
hensha256 Jul 17, 2024
50372c7
gas opt removing struct
hensha256 Jul 17, 2024
64f4645
gas opt: decode in assembly
hensha256 Jul 17, 2024
e92b023
Merge branch 'main' into routing
hensha256 Jul 18, 2024
8283fb6
merge error
hensha256 Jul 18, 2024
7aacd94
merge main
hensha256 Jul 18, 2024
f2a3dd8
merge main
hensha256 Jul 23, 2024
4792d1c
merge routing
hensha256 Jul 23, 2024
903d5fa
use base actions router in v4router
hensha256 Jul 23, 2024
f1c4746
use isolate
hensha256 Jul 23, 2024
4b7d019
Merge pull request #155 from Uniswap/v4-routing
hensha256 Jul 23, 2024
f555329
Merge branch 'main' into routing
hensha256 Jul 23, 2024
9c11de4
merge conflicts
hensha256 Jul 23, 2024
b2fdb51
removing submodules
hensha256 Jul 23, 2024
963c360
renaming to stop clashes in UR
hensha256 Jul 24, 2024
eb801c7
Merge branch 'main' into routing
hensha256 Jul 24, 2024
86fd91a
Merge branch 'main' into routing
hensha256 Jul 25, 2024
22c0879
PR comments and gas optimisations
hensha256 Jul 25, 2024
3a4c36d
add _getLocker
hensha256 Jul 25, 2024
e86036b
Amount field on settle and take
hensha256 Jul 25, 2024
2236812
rename snaps
hensha256 Jul 25, 2024
456d0c8
correcy casting order
hensha256 Jul 25, 2024
9e5e418
Merge branch 'main' into routing
hensha256 Jul 25, 2024
5ceaadf
Add deltaresolver to router
hensha256 Jul 25, 2024
20224d7
take_all command, remove from delta resolver
hensha256 Jul 26, 2024
d02049b
Routing test helper
hensha256 Jul 26, 2024
ff206ec
Separate gas tests and regular tests
hensha256 Jul 26, 2024
09b2e23
remove duplicate snapshot name
hensha256 Jul 26, 2024
b51f0ea
another gas test
hensha256 Jul 26, 2024
b625d74
merge main
hensha256 Jul 26, 2024
d1d66ec
Merge branch 'main' into routing
hensha256 Jul 26, 2024
e2ae6d3
Merge branch 'main' into routing
hensha256 Jul 29, 2024
7094a75
PR comments
hensha256 Jul 29, 2024
9ee5768
remove unused function
hensha256 Jul 29, 2024
f14c6f0
Merge branch 'main' into routing
hensha256 Jul 29, 2024
7545fa3
PR cmments
hensha256 Jul 29, 2024
a92da59
handle hook edgecase
hensha256 Jul 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .forge-snapshots/RouterBytecode.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4838
1 change: 1 addition & 0 deletions .forge-snapshots/RouterExactIn1Hop.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
193914
1 change: 1 addition & 0 deletions .forge-snapshots/RouterExactIn2Hops.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
270990
1 change: 1 addition & 0 deletions .forge-snapshots/RouterExactIn3Hops.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
348071
1 change: 1 addition & 0 deletions .forge-snapshots/RouterExactInputSingle.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
192342
1 change: 1 addition & 0 deletions .forge-snapshots/RouterExactOut1Hop.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
193062
1 change: 1 addition & 0 deletions .forge-snapshots/RouterExactOut2Hops.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
271015
1 change: 1 addition & 0 deletions .forge-snapshots/RouterExactOut3Hops.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
348996
1 change: 1 addition & 0 deletions .forge-snapshots/RouterExactOutputSingle.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
191562
174 changes: 174 additions & 0 deletions contracts/V4Router.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// 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 {IV4Router} from "./interfaces/IV4Router.sol";

/// @title UniswapV4Routing
/// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools
hensha256 marked this conversation as resolved.
Show resolved Hide resolved
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 {
hensha256 marked this conversation as resolved.
Show resolved Hide resolved
poolManager.lock(abi.encode(SwapInfo(swapType, msg.sender, params)));
}

function lockAcquired(bytes calldata encodedSwapInfo) external poolManagerOnly returns (bytes memory) {
SwapInfo memory swapInfo = abi.decode(encodedSwapInfo, (SwapInfo));

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

return bytes("");
}

function _swapExactInputSingle(ExactInputSingleParams memory params, address msgSender) private {
_swapExactPrivate(
params.poolKey,
params.zeroForOne,
int256(int128(params.amountIn)),
params.sqrtPriceLimitX96,
msgSender,
true,
true
);
}

function _swapExactInput(ExactInputParams memory params, address msgSender) private {
unchecked {
for (uint256 i = 0; i < params.path.length; i++) {
hensha256 marked this conversation as resolved.
Show resolved Hide resolved
(PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(params.path[i], params.currencyIn);
uint128 amountOut = uint128(
-_swapExactPrivate(
poolKey,
zeroForOne,
int256(int128(params.amountIn)),
0,
msgSender,
i == 0,
i == params.path.length - 1
)
);

params.amountIn = amountOut;
params.currencyIn = params.path[i].tradeCurrency;
}

if (params.amountIn < params.amountOutMinimum) revert TooLittleReceived();
}
}

function _swapExactOutputSingle(ExactOutputSingleParams memory params, address msgSender) private {
_swapExactPrivate(
params.poolKey,
params.zeroForOne,
-int256(int128(params.amountOut)),
params.sqrtPriceLimitX96,
msgSender,
true,
true
);
}

function _swapExactOutput(ExactOutputParams memory params, address msgSender) private {
unchecked {
for (uint256 i = params.path.length; i > 0; i--) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ugh i dislike that this is from n to 1 not n-1 to 0... but I see the issue.
I guess it could be
for (uint256 i = params.path.length-1; i < params.path.length; i--) {
but thats also ugly so i guess theres no good way 😂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dude yah, I actually searched all Uniswap for i-- to see how ppl deal with this type of thing. And I found ANOTHER POC v4 router that Pote wrote ages ago and he did the same thing baha

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this is typical for for loops iterating backwards through the path though.. like doesn't seem too weird to me. Is there a reason we force it to be backwards though? I guess compatibility with how other routers work but there is no reason we couldn't just encode the path the other way also

(PoolKey memory poolKey, bool oneForZero) =
_getPoolAndSwapDirection(params.path[i - 1], params.currencyOut);
uint128 amountIn = uint128(
_swapExactPrivate(
poolKey,
!oneForZero,
-int256(int128(params.amountOut)),
0,
msgSender,
i == 1,
i == params.path.length
)
);

params.amountOut = amountIn;
params.currencyOut = params.path[i - 1].tradeCurrency;
}
if (params.amountOut > params.amountInMaximum) revert TooMuchRequested();
}
}

function _swapExactPrivate(
PoolKey memory poolKey,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
address msgSender,
bool settle,
bool take
) private returns (int128 reciprocalAmount) {
BalanceDelta delta = poolManager.swap(
poolKey,
IPoolManager.SwapParams(
zeroForOne,
amountSpecified,
sqrtPriceLimitX96 == 0
? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1)
: sqrtPriceLimitX96
),
bytes("")
);

if (zeroForOne) {
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()));
} else {
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()));
}
}

function _getPoolAndSwapDirection(PathKey memory params, Currency currencyIn)
private
pure
returns (PoolKey memory poolKey, bool zeroForOne)
{
(Currency currency0, Currency currency1) =
currencyIn < params.tradeCurrency ? (currencyIn, params.tradeCurrency) : (params.tradeCurrency, currencyIn);

zeroForOne = currencyIn == currency0;
poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks);
}

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

function _pay(address token, address payer, address recipient, uint256 amount) internal virtual;
hensha256 marked this conversation as resolved.
Show resolved Hide resolved
}
76 changes: 76 additions & 0 deletions contracts/interfaces/IV4Router.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// 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";

/// @title UniswapV4Routing
/// @notice Abstract contract that contains all internal logic needed for routing through Uniswap V4 pools
interface IV4Router {
error NotPoolManager();
error InvalidSwapType();
error TooLittleReceived();
error TooMuchRequested();

struct SwapInfo {
SwapType swapType;
address msgSender;
bytes params;
}

struct PathKey {
Currency tradeCurrency;
hensha256 marked this conversation as resolved.
Show resolved Hide resolved
uint24 fee;
int24 tickSpacing;
IHooks hooks;
}

struct ExactInputSingleParams {
PoolKey poolKey;
bool zeroForOne;
address recipient;
uint128 amountIn;
uint128 amountOutMinimum;
uint160 sqrtPriceLimitX96;
hensha256 marked this conversation as resolved.
Show resolved Hide resolved
}

struct ExactInputParams {
Currency currencyIn;
PathKey[] path;
address recipient;
uint128 amountIn;
uint128 amountOutMinimum;
}

struct ExactOutputSingleParams {
PoolKey poolKey;
bool zeroForOne;
address recipient;
uint128 amountOut;
uint128 amountInMaximum;
uint160 sqrtPriceLimitX96;
}

struct ExactOutputParams {
Currency currencyOut;
PathKey[] path;
address recipient;
uint128 amountOut;
uint128 amountInMaximum;
}

enum SwapType {
ExactInput,
ExactInputSingle,
ExactOutput,
ExactOutputSingle
}

function lockAcquired(bytes calldata encodedSwapInfo) external returns (bytes memory);
}
Loading