Skip to content

Commit a0c5e55

Browse files
committed
feat(WIP): Uniswap V2 Adapter Hook
This commit is a proof of concept for a hook that adapts an existing liquidity source such as Uniswap V2. This allows routers to interact with various liquidity systems using the Uniswap V4 interface and route across Uniswap V4 and others without leaving the lock.
1 parent 60cd938 commit a0c5e55

File tree

8 files changed

+638
-0
lines changed

8 files changed

+638
-0
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44
[submodule "lib/permit2"]
55
path = lib/permit2
66
url = https://github.com/Uniswap/permit2
7+
[submodule "lib/briefcase"]
8+
path = lib/briefcase
9+
url = https://github.com/uniswap/briefcase

lib/briefcase

Submodule briefcase added at 71de140
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"UniswapV2Adapter_exactInput_usdc": "144072",
3+
"UniswapV2Adapter_exactInput_weth": "138426",
4+
"UniswapV2Adapter_exactOutput_usdc": "143907",
5+
"UniswapV2Adapter_exactOutput_weth": "138231"
6+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"UniswapV2Adapter_exactInput_weth": "138426"
3+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import {
5+
toBeforeSwapDelta, BeforeSwapDelta, BeforeSwapDeltaLibrary
6+
} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol";
7+
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
8+
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
9+
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
10+
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
11+
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
12+
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
13+
import {BaseHook} from "../../utils/BaseHook.sol";
14+
import {DeltaResolver} from "../DeltaResolver.sol";
15+
import {ModifyLiquidityParams, SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
16+
17+
/// @title BaseLiquidityAdapterHook
18+
/// @author Uniswap V4 Hooks
19+
/// @notice Abstract base contract for adapting external liquidity sources to Uniswap V4 pools
20+
/// @dev Provides core functionality for integrating alternative AMMs and liquidity protocols with V4
21+
/// @dev Liquidity operations (add/remove) are disabled - all liquidity is managed externally
22+
/// @dev Inheritors must implement: _swapExactInput, _swapExactOutput, and _liquidityExists
23+
abstract contract BaseLiquidityAdapterHook is BaseHook, DeltaResolver {
24+
using CurrencyLibrary for Currency;
25+
using SafeCast for int256;
26+
using SafeCast for uint256;
27+
28+
/// @notice Thrown when attempting to add or remove liquidity
29+
/// @dev Liquidity operations are disabled as liquidity is managed by the external source
30+
error LiquidityNotAllowed();
31+
32+
/// @notice Thrown when initializing a pool that doesn't have corresponding external liquidity
33+
/// @dev The external liquidity source must support the given token pair
34+
error InvalidPool();
35+
36+
/// @notice Initializes the base liquidity adapter hook
37+
/// @param _manager The Uniswap V4 pool manager contract
38+
constructor(IPoolManager _manager) BaseHook(_manager) {}
39+
40+
/// @inheritdoc BaseHook
41+
function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
42+
return Hooks.Permissions({
43+
beforeInitialize: true,
44+
beforeSwap: true,
45+
beforeSwapReturnDelta: true,
46+
beforeAddLiquidity: true,
47+
afterSwap: false,
48+
afterInitialize: false,
49+
beforeRemoveLiquidity: false,
50+
afterAddLiquidity: false,
51+
afterRemoveLiquidity: false,
52+
beforeDonate: false,
53+
afterDonate: false,
54+
afterSwapReturnDelta: false,
55+
afterAddLiquidityReturnDelta: false,
56+
afterRemoveLiquidityReturnDelta: false
57+
});
58+
}
59+
60+
/// @notice Hook called before pool initialization to validate compatibility
61+
/// @dev Ensures the external liquidity source supports the given token pair
62+
/// @param poolKey The pool configuration containing token pair and fee settings
63+
/// @return bytes4 The beforeInitialize selector on success
64+
function _beforeInitialize(address, PoolKey calldata poolKey, uint160) internal view override returns (bytes4) {
65+
// ensure the pool is supported by the underlying liquidity source
66+
if (!_liquidityExists(poolKey)) revert InvalidPool();
67+
68+
return IHooks.beforeInitialize.selector;
69+
}
70+
71+
/// @notice Hook called before adding liquidity - always reverts
72+
/// @dev Liquidity provision is disabled as all liquidity comes from external source
73+
function _beforeAddLiquidity(address, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata)
74+
internal
75+
pure
76+
override
77+
returns (bytes4)
78+
{
79+
revert LiquidityNotAllowed();
80+
}
81+
82+
/// @notice Hook called before swap execution to route through external liquidity
83+
/// @dev Handles both exact input (amountSpecified < 0) and exact output (amountSpecified > 0)
84+
/// @param poolKey The pool configuration
85+
/// @param params Swap parameters including direction, amount, and sqrtPriceLimit
86+
/// @return bytes4 The beforeSwap selector
87+
/// @return swapDelta The token deltas for pool accounting
88+
/// @return uint24 LP fee override (always 0 as fees are handled externally)
89+
function _beforeSwap(address, PoolKey calldata poolKey, SwapParams calldata params, bytes calldata)
90+
internal
91+
override
92+
returns (bytes4, BeforeSwapDelta swapDelta, uint24)
93+
{
94+
bool isExactInput = params.amountSpecified < 0;
95+
96+
if (isExactInput) {
97+
uint256 amountOut = _swapExactInput(poolKey, params);
98+
swapDelta = toBeforeSwapDelta(-params.amountSpecified.toInt128(), -int128(int256(amountOut)));
99+
} else {
100+
uint256 amountIn = _swapExactOutput(poolKey, params);
101+
swapDelta = toBeforeSwapDelta(-params.amountSpecified.toInt128(), int128(int256(amountIn)));
102+
}
103+
104+
return (IHooks.beforeSwap.selector, swapDelta, 0);
105+
}
106+
107+
/// @inheritdoc DeltaResolver
108+
/// @notice Settles positive deltas by transferring tokens to the pool manager
109+
/// @param token The currency to transfer
110+
/// @param amount The amount to transfer to the pool manager
111+
function _pay(Currency token, address, uint256 amount) internal override {
112+
token.transfer(address(poolManager), amount);
113+
}
114+
115+
/// @notice Executes a swap with exact input amount through external liquidity
116+
/// @param poolKey The pool configuration
117+
/// @param params Swap parameters with negative amountSpecified
118+
/// @return amountOut The amount of output tokens received
119+
function _swapExactInput(PoolKey calldata poolKey, SwapParams calldata params)
120+
internal
121+
virtual
122+
returns (uint256 amountOut);
123+
124+
/// @notice Executes a swap with exact output amount through external liquidity
125+
/// @param poolKey The pool configuration
126+
/// @param params Swap parameters with positive amountSpecified
127+
/// @return amountIn The amount of input tokens required
128+
function _swapExactOutput(PoolKey calldata poolKey, SwapParams calldata params)
129+
internal
130+
virtual
131+
returns (uint256 amountIn);
132+
133+
/// @notice Checks if the external liquidity source supports the given pool
134+
/// @param poolKey The pool configuration to validate
135+
/// @return exists True if external liquidity exists for this token pair
136+
function _liquidityExists(PoolKey calldata poolKey) internal view virtual returns (bool exists);
137+
}

src/hooks/UniswapV2AdapterHook.sol

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.26;
3+
4+
import {IUniswapV2Factory} from "briefcase/protocols/v2-core/interfaces/IUniswapV2Factory.sol";
5+
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
6+
import {IUniswapV2Pair} from "briefcase/protocols/v2-core/interfaces/IUniswapV2Pair.sol";
7+
import {BaseLiquidityAdapterHook} from "../base/hooks/BaseLiquidityAdapterHook.sol";
8+
import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
9+
import {UniswapV2Library} from "../libraries/UniswapV2Library.sol";
10+
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
11+
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
12+
13+
/// @title UniswapV2AdapterHook
14+
/// @author Uniswap V4 Hooks
15+
/// @notice Adapter hook that routes V4 swaps through Uniswap V2 liquidity pools
16+
/// @dev Enables V4 pools to leverage existing V2 liquidity without migration
17+
contract UniswapV2AdapterHook is BaseLiquidityAdapterHook {
18+
error InvalidFee();
19+
error InvalidTickSpacing();
20+
21+
uint32 constant POOL_FEE = 3000;
22+
int24 constant POOL_TICK_SPACING = 1;
23+
24+
/// @notice The Uniswap V2 factory contract for accessing V2 pairs
25+
IUniswapV2Factory public immutable v2Factory;
26+
bytes32 constant UNISWAP_V2_INIT_CODE_HASH = hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f";
27+
28+
/// @notice Initializes the Uniswap V2 adapter hook
29+
/// @param _manager The Uniswap V4 pool manager contract
30+
/// @param _v2Factory The Uniswap V2 factory contract address
31+
constructor(IPoolManager _manager, IUniswapV2Factory _v2Factory) BaseLiquidityAdapterHook(_manager) {
32+
v2Factory = _v2Factory;
33+
}
34+
35+
/// @inheritdoc BaseLiquidityAdapterHook
36+
/// @dev Routes exact input swaps through the corresponding V2 pair
37+
function _swapExactInput(PoolKey calldata poolKey, SwapParams calldata params)
38+
internal
39+
override
40+
returns (uint256 amountOut)
41+
{
42+
(
43+
Currency tokenIn,
44+
Currency tokenOut,
45+
address pair,
46+
uint256 reserveIn,
47+
uint256 reserveOut,
48+
uint256 amountSpecified
49+
) = _parseSwap(poolKey, params);
50+
amountOut = UniswapV2Library.getAmountOut(amountSpecified, reserveIn, reserveOut);
51+
(uint256 amount0Out, uint256 amount1Out) = params.zeroForOne ? (uint256(0), amountOut) : (amountOut, uint256(0));
52+
53+
// Sync output token balance before V2 swap
54+
poolManager.sync(tokenOut);
55+
// Transfer input tokens from V4 pool manager directly to V2 pair
56+
_take(tokenIn, pair, amountSpecified);
57+
IUniswapV2Pair(pair).swap(amount0Out, amount1Out, address(poolManager), new bytes(0));
58+
// Settle with V4 pool manager to account for output tokens received
59+
poolManager.settle();
60+
}
61+
62+
/// @inheritdoc BaseLiquidityAdapterHook
63+
/// @dev Routes exact output swaps through the corresponding V2 pair
64+
function _swapExactOutput(PoolKey calldata poolKey, SwapParams calldata params)
65+
internal
66+
override
67+
returns (uint256 amountIn)
68+
{
69+
(
70+
Currency tokenIn,
71+
Currency tokenOut,
72+
address pair,
73+
uint256 reserveIn,
74+
uint256 reserveOut,
75+
uint256 amountSpecified
76+
) = _parseSwap(poolKey, params);
77+
amountIn = UniswapV2Library.getAmountIn(amountSpecified, reserveIn, reserveOut);
78+
(uint256 amount0Out, uint256 amount1Out) =
79+
params.zeroForOne ? (uint256(0), amountSpecified) : (amountSpecified, uint256(0));
80+
81+
// Sync output token balance before V2 swap
82+
poolManager.sync(tokenOut);
83+
// Transfer input tokens from V4 pool manager directly to V2 pair
84+
_take(tokenIn, pair, amountIn);
85+
IUniswapV2Pair(pair).swap(amount0Out, amount1Out, address(poolManager), new bytes(0));
86+
// Settle with V4 pool manager to account for output tokens received
87+
poolManager.settle();
88+
}
89+
90+
/// @inheritdoc BaseLiquidityAdapterHook
91+
/// @dev Checks if a V2 pair exists for the given token pair
92+
function _liquidityExists(PoolKey calldata poolKey) internal view override returns (bool exists) {
93+
if (poolKey.fee != POOL_FEE) revert InvalidFee();
94+
if (poolKey.tickSpacing != POOL_TICK_SPACING) revert InvalidTickSpacing();
95+
return v2Factory.getPair(Currency.unwrap(poolKey.currency0), Currency.unwrap(poolKey.currency1)) != address(0);
96+
}
97+
98+
/// @notice Parses swap parameters to determine tokens and amounts
99+
/// @dev Extracts token direction, V2 pair address, and swap amount from V4 parameters
100+
/// @param poolKey The V4 pool configuration
101+
/// @param params The V4 swap parameters
102+
/// @return tokenIn The input token currency
103+
/// @return tokenOut The output token currency
104+
/// @return pair The V2 pair contract for this token pair
105+
/// @return reserveIn The reserve of the input token on the pair
106+
/// @return reserveOut The reserve of the output token on the pair
107+
/// @return amountSpecified The absolute swap amount
108+
function _parseSwap(PoolKey calldata poolKey, SwapParams calldata params)
109+
private
110+
view
111+
returns (
112+
Currency tokenIn,
113+
Currency tokenOut,
114+
address pair,
115+
uint256 reserveIn,
116+
uint256 reserveOut,
117+
uint256 amountSpecified
118+
)
119+
{
120+
(tokenIn, tokenOut) =
121+
params.zeroForOne ? (poolKey.currency0, poolKey.currency1) : (poolKey.currency1, poolKey.currency0);
122+
(pair, reserveIn, reserveOut) = UniswapV2Library.pairAndReservesFor(
123+
address(v2Factory), UNISWAP_V2_INIT_CODE_HASH, Currency.unwrap(tokenIn), Currency.unwrap(tokenOut)
124+
);
125+
amountSpecified =
126+
params.amountSpecified > 0 ? uint256(params.amountSpecified) : uint256(-params.amountSpecified);
127+
}
128+
}

0 commit comments

Comments
 (0)