-
Notifications
You must be signed in to change notification settings - Fork 640
feat(WIP): Uniswap V2 Adapter Hook #495
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
Draft
marktoda
wants to merge
1
commit into
main
Choose a base branch
from
uniswap-v2-adapter-hook
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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,6 @@ | ||
| { | ||
| "UniswapV2Adapter_exactInput_usdc": "174728", | ||
| "UniswapV2Adapter_exactInput_weth": "169058", | ||
| "UniswapV2Adapter_exactOutput_usdc": "174263", | ||
| "UniswapV2Adapter_exactOutput_weth": "168539" | ||
| } |
This file contains hidden or 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,137 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| import { | ||
| toBeforeSwapDelta, BeforeSwapDelta, BeforeSwapDeltaLibrary | ||
| } from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol"; | ||
| import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; | ||
| import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; | ||
| import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; | ||
| import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; | ||
| import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; | ||
| import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; | ||
| import {BaseHook} from "../../utils/BaseHook.sol"; | ||
| import {DeltaResolver} from "../DeltaResolver.sol"; | ||
| import {ModifyLiquidityParams, SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol"; | ||
|
|
||
| /// @title BaseLiquidityAdapterHook | ||
| /// @author Uniswap V4 Hooks | ||
| /// @notice Abstract base contract for adapting external liquidity sources to Uniswap V4 pools | ||
| /// @dev Provides core functionality for integrating alternative AMMs and liquidity protocols with V4 | ||
| /// @dev Liquidity operations (add/remove) are disabled - all liquidity is managed externally | ||
| /// @dev Inheritors must implement: _swapExactInput, _swapExactOutput, and _liquidityExists | ||
| abstract contract BaseLiquidityAdapterHook is BaseHook, DeltaResolver { | ||
| using CurrencyLibrary for Currency; | ||
| using SafeCast for int256; | ||
| using SafeCast for uint256; | ||
|
|
||
| /// @notice Thrown when attempting to add or remove liquidity | ||
| /// @dev Liquidity operations are disabled as liquidity is managed by the external source | ||
| error LiquidityNotAllowed(); | ||
|
|
||
| /// @notice Thrown when initializing a pool that doesn't have corresponding external liquidity | ||
| /// @dev The external liquidity source must support the given token pair | ||
| error InvalidPool(); | ||
|
|
||
| /// @notice Initializes the base liquidity adapter hook | ||
| /// @param _manager The Uniswap V4 pool manager contract | ||
| constructor(IPoolManager _manager) BaseHook(_manager) {} | ||
|
|
||
| /// @inheritdoc BaseHook | ||
| function getHookPermissions() public pure override returns (Hooks.Permissions memory) { | ||
| return Hooks.Permissions({ | ||
| beforeInitialize: true, | ||
| beforeSwap: true, | ||
| beforeSwapReturnDelta: true, | ||
| beforeAddLiquidity: true, | ||
| afterSwap: false, | ||
| afterInitialize: false, | ||
| beforeRemoveLiquidity: false, | ||
| afterAddLiquidity: false, | ||
| afterRemoveLiquidity: false, | ||
| beforeDonate: false, | ||
| afterDonate: false, | ||
| afterSwapReturnDelta: false, | ||
| afterAddLiquidityReturnDelta: false, | ||
| afterRemoveLiquidityReturnDelta: false | ||
| }); | ||
| } | ||
|
|
||
| /// @notice Hook called before pool initialization to validate compatibility | ||
| /// @dev Ensures the external liquidity source supports the given token pair | ||
| /// @param poolKey The pool configuration containing token pair and fee settings | ||
| /// @return bytes4 The beforeInitialize selector on success | ||
| function _beforeInitialize(address, PoolKey calldata poolKey, uint160) internal view override returns (bytes4) { | ||
| // ensure the pool is supported by the underlying liquidity source | ||
| if (!_liquidityExists(poolKey)) revert InvalidPool(); | ||
|
|
||
| return IHooks.beforeInitialize.selector; | ||
| } | ||
|
|
||
| /// @notice Hook called before adding liquidity - always reverts | ||
| /// @dev Liquidity provision is disabled as all liquidity comes from external source | ||
| function _beforeAddLiquidity(address, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata) | ||
| internal | ||
| pure | ||
| override | ||
| returns (bytes4) | ||
| { | ||
| revert LiquidityNotAllowed(); | ||
| } | ||
|
|
||
| /// @notice Hook called before swap execution to route through external liquidity | ||
| /// @dev Handles both exact input (amountSpecified < 0) and exact output (amountSpecified > 0) | ||
| /// @param poolKey The pool configuration | ||
| /// @param params Swap parameters including direction, amount, and sqrtPriceLimit | ||
| /// @return bytes4 The beforeSwap selector | ||
| /// @return swapDelta The token deltas for pool accounting | ||
| /// @return uint24 LP fee override (always 0 as fees are handled externally) | ||
| function _beforeSwap(address, PoolKey calldata poolKey, SwapParams calldata params, bytes calldata) | ||
| internal | ||
| override | ||
| returns (bytes4, BeforeSwapDelta swapDelta, uint24) | ||
| { | ||
| bool isExactInput = params.amountSpecified < 0; | ||
|
|
||
| if (isExactInput) { | ||
| uint256 amountOut = _swapExactInput(poolKey, params); | ||
| swapDelta = toBeforeSwapDelta(-params.amountSpecified.toInt128(), -int128(int256(amountOut))); | ||
| } else { | ||
| uint256 amountIn = _swapExactOutput(poolKey, params); | ||
| swapDelta = toBeforeSwapDelta(-params.amountSpecified.toInt128(), int128(int256(amountIn))); | ||
| } | ||
|
|
||
| return (IHooks.beforeSwap.selector, swapDelta, 0); | ||
| } | ||
|
|
||
| /// @inheritdoc DeltaResolver | ||
| /// @notice Settles positive deltas by transferring tokens to the pool manager | ||
| /// @param token The currency to transfer | ||
| /// @param amount The amount to transfer to the pool manager | ||
| function _pay(Currency token, address, uint256 amount) internal override { | ||
| token.transfer(address(poolManager), amount); | ||
| } | ||
|
|
||
| /// @notice Executes a swap with exact input amount through external liquidity | ||
| /// @param poolKey The pool configuration | ||
| /// @param params Swap parameters with negative amountSpecified | ||
| /// @return amountOut The amount of output tokens received | ||
| function _swapExactInput(PoolKey calldata poolKey, SwapParams calldata params) | ||
| internal | ||
| virtual | ||
| returns (uint256 amountOut); | ||
|
|
||
| /// @notice Executes a swap with exact output amount through external liquidity | ||
| /// @param poolKey The pool configuration | ||
| /// @param params Swap parameters with positive amountSpecified | ||
| /// @return amountIn The amount of input tokens required | ||
| function _swapExactOutput(PoolKey calldata poolKey, SwapParams calldata params) | ||
| internal | ||
| virtual | ||
| returns (uint256 amountIn); | ||
|
|
||
| /// @notice Checks if the external liquidity source supports the given pool | ||
| /// @param poolKey The pool configuration to validate | ||
| /// @return exists True if external liquidity exists for this token pair | ||
| function _liquidityExists(PoolKey calldata poolKey) internal view virtual returns (bool exists); | ||
| } |
This file contains hidden or 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,128 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity 0.8.26; | ||
|
|
||
| import {IUniswapV2Factory} from "briefcase/protocols/v2-core/interfaces/IUniswapV2Factory.sol"; | ||
| import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; | ||
| import {IUniswapV2Pair} from "briefcase/protocols/v2-core/interfaces/IUniswapV2Pair.sol"; | ||
| import {BaseLiquidityAdapterHook} from "../base/hooks/BaseLiquidityAdapterHook.sol"; | ||
| import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol"; | ||
| import {UniswapV2Library} from "../libraries/UniswapV2Library.sol"; | ||
| import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; | ||
| import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; | ||
|
|
||
| /// @title UniswapV2AdapterHook | ||
| /// @author Uniswap V4 Hooks | ||
| /// @notice Adapter hook that routes V4 swaps through Uniswap V2 liquidity pools | ||
| /// @dev Enables V4 pools to leverage existing V2 liquidity without migration | ||
| contract UniswapV2AdapterHook is BaseLiquidityAdapterHook { | ||
| error InvalidFee(); | ||
| error InvalidTickSpacing(); | ||
|
|
||
| uint32 constant POOL_FEE = 3000; | ||
| int24 constant POOL_TICK_SPACING = 1; | ||
|
|
||
| /// @notice The Uniswap V2 factory contract for accessing V2 pairs | ||
| IUniswapV2Factory public immutable v2Factory; | ||
| bytes32 constant UNISWAP_V2_INIT_CODE_HASH = hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f"; | ||
|
|
||
| /// @notice Initializes the Uniswap V2 adapter hook | ||
| /// @param _manager The Uniswap V4 pool manager contract | ||
| /// @param _v2Factory The Uniswap V2 factory contract address | ||
| constructor(IPoolManager _manager, IUniswapV2Factory _v2Factory) BaseLiquidityAdapterHook(_manager) { | ||
| v2Factory = _v2Factory; | ||
| } | ||
|
|
||
| /// @inheritdoc BaseLiquidityAdapterHook | ||
| /// @dev Routes exact input swaps through the corresponding V2 pair | ||
| function _swapExactInput(PoolKey calldata poolKey, SwapParams calldata params) | ||
| internal | ||
| override | ||
| returns (uint256 amountOut) | ||
| { | ||
| ( | ||
| Currency tokenIn, | ||
| Currency tokenOut, | ||
| address pair, | ||
| uint256 reserveIn, | ||
| uint256 reserveOut, | ||
| uint256 amountSpecified | ||
| ) = _parseSwap(poolKey, params); | ||
| amountOut = UniswapV2Library.getAmountOut(amountSpecified, reserveIn, reserveOut); | ||
| (uint256 amount0Out, uint256 amount1Out) = params.zeroForOne ? (uint256(0), amountOut) : (amountOut, uint256(0)); | ||
|
|
||
| // Sync output token balance before V2 swap | ||
| poolManager.sync(tokenOut); | ||
| // Transfer input tokens from V4 pool manager directly to V2 pair | ||
| _take(tokenIn, pair, amountSpecified); | ||
| IUniswapV2Pair(pair).swap(amount0Out, amount1Out, address(poolManager), new bytes(0)); | ||
| // Settle with V4 pool manager to account for output tokens received | ||
| poolManager.settle(); | ||
| } | ||
|
|
||
| /// @inheritdoc BaseLiquidityAdapterHook | ||
| /// @dev Routes exact output swaps through the corresponding V2 pair | ||
| function _swapExactOutput(PoolKey calldata poolKey, SwapParams calldata params) | ||
| internal | ||
| override | ||
| returns (uint256 amountIn) | ||
| { | ||
| ( | ||
| Currency tokenIn, | ||
| Currency tokenOut, | ||
| address pair, | ||
| uint256 reserveIn, | ||
| uint256 reserveOut, | ||
| uint256 amountSpecified | ||
| ) = _parseSwap(poolKey, params); | ||
| amountIn = UniswapV2Library.getAmountIn(amountSpecified, reserveIn, reserveOut); | ||
| (uint256 amount0Out, uint256 amount1Out) = | ||
| params.zeroForOne ? (uint256(0), amountSpecified) : (amountSpecified, uint256(0)); | ||
|
|
||
| // Sync output token balance before V2 swap | ||
| poolManager.sync(tokenOut); | ||
| // Transfer input tokens from V4 pool manager directly to V2 pair | ||
| _take(tokenIn, pair, amountIn); | ||
| IUniswapV2Pair(pair).swap(amount0Out, amount1Out, address(poolManager), new bytes(0)); | ||
| // Settle with V4 pool manager to account for output tokens received | ||
| poolManager.settle(); | ||
| } | ||
|
|
||
| /// @inheritdoc BaseLiquidityAdapterHook | ||
| /// @dev Checks if a V2 pair exists for the given token pair | ||
| function _liquidityExists(PoolKey calldata poolKey) internal view override returns (bool exists) { | ||
| if (poolKey.fee != POOL_FEE) revert InvalidFee(); | ||
| if (poolKey.tickSpacing != POOL_TICK_SPACING) revert InvalidTickSpacing(); | ||
| return v2Factory.getPair(Currency.unwrap(poolKey.currency0), Currency.unwrap(poolKey.currency1)) != address(0); | ||
| } | ||
|
|
||
| /// @notice Parses swap parameters to determine tokens and amounts | ||
| /// @dev Extracts token direction, V2 pair address, and swap amount from V4 parameters | ||
| /// @param poolKey The V4 pool configuration | ||
| /// @param params The V4 swap parameters | ||
| /// @return tokenIn The input token currency | ||
| /// @return tokenOut The output token currency | ||
| /// @return pair The V2 pair contract for this token pair | ||
| /// @return reserveIn The reserve of the input token on the pair | ||
| /// @return reserveOut The reserve of the output token on the pair | ||
| /// @return amountSpecified The absolute swap amount | ||
| function _parseSwap(PoolKey calldata poolKey, SwapParams calldata params) | ||
| private | ||
| view | ||
| returns ( | ||
| Currency tokenIn, | ||
| Currency tokenOut, | ||
| address pair, | ||
| uint256 reserveIn, | ||
| uint256 reserveOut, | ||
| uint256 amountSpecified | ||
| ) | ||
| { | ||
| (tokenIn, tokenOut) = | ||
| params.zeroForOne ? (poolKey.currency0, poolKey.currency1) : (poolKey.currency1, poolKey.currency0); | ||
| (pair, reserveIn, reserveOut) = UniswapV2Library.pairAndReservesFor( | ||
| address(v2Factory), UNISWAP_V2_INIT_CODE_HASH, Currency.unwrap(tokenIn), Currency.unwrap(tokenOut) | ||
| ); | ||
| amountSpecified = | ||
| params.amountSpecified > 0 ? uint256(params.amountSpecified) : uint256(-params.amountSpecified); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.