From f7c22c13f980409a2757d2f332f8d08b9a543100 Mon Sep 17 00:00:00 2001 From: Daniel Bigos Date: Sun, 3 Nov 2024 12:02:48 +0100 Subject: [PATCH] feat: add IHooks trait for Uniswap V4 Refers #389 --- contracts/src/lib.rs | 1 + contracts/src/uniswap/mod.rs | 3 + contracts/src/uniswap/v4/hooks.rs | 289 ++++++++++++++++++++++++++++++ contracts/src/uniswap/v4/mod.rs | 9 + contracts/src/uniswap/v4/types.rs | 139 ++++++++++++++ 5 files changed, 441 insertions(+) create mode 100644 contracts/src/uniswap/mod.rs create mode 100644 contracts/src/uniswap/v4/hooks.rs create mode 100644 contracts/src/uniswap/v4/mod.rs create mode 100644 contracts/src/uniswap/v4/types.rs diff --git a/contracts/src/lib.rs b/contracts/src/lib.rs index 27b4eaa3d..53d108661 100644 --- a/contracts/src/lib.rs +++ b/contracts/src/lib.rs @@ -46,4 +46,5 @@ extern crate alloc; pub mod access; pub mod token; +pub mod uniswap; pub mod utils; diff --git a/contracts/src/uniswap/mod.rs b/contracts/src/uniswap/mod.rs new file mode 100644 index 000000000..59071de46 --- /dev/null +++ b/contracts/src/uniswap/mod.rs @@ -0,0 +1,3 @@ +//! Contracts implementing Uniswap mechanisms. +pub mod v4; +pub use v4::*; diff --git a/contracts/src/uniswap/v4/hooks.rs b/contracts/src/uniswap/v4/hooks.rs new file mode 100644 index 000000000..96cc520b8 --- /dev/null +++ b/contracts/src/uniswap/v4/hooks.rs @@ -0,0 +1,289 @@ +//! Uniswap V4 Hooks Interface. +//! +//! V4 decides whether to invoke specific hooks by inspecting the least +//! significant bits of the address that the hooks contract is deployed to. +//! For example, a hooks contract deployed to address: +//! 0x0000000000000000000000000000000000002400 +//! has the lowest bits '10 0100 0000 0000' which would cause +//! the 'before initialize' and 'after add liquidity' hooks to be used. +//! +//! See the Hooks library for the full spec. +//! +//! Should only be callable by the v4 PoolManager. + +use alloy_primitives::{Address, Bytes, FixedBytes, I128, U160, U256}; +use stylus_sdk::stylus_proc::SolidityError; + +use crate::uniswap::v4::{ + BalanceDelta, BeforeSwapDelta, ModifyLiquidityParams, PoolKey, SwapParams, + I24, U24, +}; + +/// An Uniswap V4 Hook error. +#[derive(SolidityError, Debug)] +pub enum Error {} + +/// Uniswap V4 Hooks Interface. +pub trait IHooks { + /// The hook called before the state of a pool is initialized. + /// + /// Returns the function selector for the hook. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `sender` -The initial msg::sender() for the initialize call. + /// * `key` - The key for the pool being initialized. + /// * `sqrt_price_x96` - The sqrt(price) of the pool as a Q64.96. + /// + /// # Errors + /// + /// May return an [`Error`]. + fn before_initialize( + &mut self, + sender: Address, + key: PoolKey, + sqrt_price_x96: U160, + ) -> Result, Error>; + + /// The hook called after the state of a pool is initialized. + /// + /// Returns the function selector for the hook. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `sender` - The initial msg::sender() for the initialize call. + /// * `key` - The key for the pool being initialized. + /// * `sqrt_price_x96` - The sqrt(price) of the pool as a Q64.96. + /// * `tick` - The current tick after the state of a pool is initialized. + /// + /// # Errors + /// + /// May return an [`Error`]. + fn after_initialize( + &mut self, + sender: Address, + key: PoolKey, + sqrt_price_x96: U160, + tick: I24, + ) -> Result, Error>; + + /// The hook called before liquidity is added. + /// + /// Returns the function selector for the hook. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `sender` - The initial msg::sender() for the add liquidity call. + /// * `key` - The key for the pool. + /// * `params` - The parameters for adding liquidity. + /// *` hook_data` - Arbitrary data handed into the Pool Manager by the + /// liquidity provider to be passed on to the hook. + /// + /// # Errors + /// + /// May return an [`Error`]. + fn before_add_liquidity( + &mut self, + sender: Address, + key: PoolKey, + params: ModifyLiquidityParams, + hook_data: Bytes, + ) -> Result, Error>; + + /// The hook called after liquidity is added + /// + /// Returns tuple containing the function selector for the hook, and + /// the hook's delta in token0 and token1. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `sender` - The initial msg::sender() for the add liquidity call. + /// * `key` - The key for the pool. + /// * `params` - The parameters for adding liquidity. + /// * `delta` - The caller's balance delta after adding liquidity; the sum + /// of principal delta, fees accrued, and hook delta. + /// * `fees_accrued` - The fees accrued since the last time fees were + /// collected from this position. + /// * `hook_data` - Arbitrary data handed into the Pool Manager by the + /// liquidity provider to be passed on to the hook. + /// + /// # Errors + /// + /// May return an [`Error`]. + fn after_add_liquidity( + &mut self, + sender: Address, + key: PoolKey, + params: ModifyLiquidityParams, + delta: BalanceDelta, + fees_accrued: BalanceDelta, + hook_data: Bytes, + ) -> Result<(FixedBytes<4>, BalanceDelta), Error>; + + /// The hook called before liquidity is removed. + /// + /// Returns the function selector for the hook. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `sender` - The initial msg::sender() for the remove liquidity call. + /// * `key` - The key for the pool. + /// * `params` - The parameters for removing liquidity. + /// * `hook_data` - Arbitrary data handed into the Pool Manager by the + /// liquidity provider to be be passed on to the hook. + /// + /// # Errors + /// + /// May return an [`Error`]. + fn before_remove_liquidity( + &mut self, + sender: Address, + key: PoolKey, + params: ModifyLiquidityParams, + hook_data: Bytes, + ) -> Result, Error>; + + /// The hook called after liquidity is removed. + /// + /// Returns tuple containing the function selector for the hook, and + /// the hook's delta in token0 and token1. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `sender` - The initial msg::sender() for the remove liquidity call. + /// * `key` - The key for the pool. + /// * `params` - The parameters for removing liquidity. + /// * `delta` - The caller's balance delta after removing liquidity; the sum + /// of principal delta, fees accrued, and hook delta. + /// * `fees_accrued` - The fees accrued since the last time fees were + /// collected from this position. + /// * `hook_data` - Arbitrary data handed into the Pool Manager by the + /// liquidity provider to be be passed on to the hook. + /// + /// # Errors + /// + /// May return an [`Error`]. + fn after_remove_liquidity( + &mut self, + sender: Address, + key: PoolKey, + params: ModifyLiquidityParams, + delta: BalanceDelta, + fees_accrued: BalanceDelta, + hook_data: Bytes, + ) -> Result<(FixedBytes<4>, BalanceDelta), Error>; + + /// The hook called before a swap. + /// + /// Returns tuple containing the function selector for the hook, and + /// the hook's delta in specified and unspecified currencies, and + /// optionally override the lp fee, only used if three conditions are met: + /// 1. the Pool has a dynamic fee, + /// 2. the value's 2nd highest bit is set (23rd bit, 0x400000), and + /// 3. the value is less than or equal to the maximum fee (1 million). + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `sender` - The initial msg::sender() for the swap call. + /// * `key` - The key for the pool. + /// * `params` - The parameters for the swap. + /// * `hook_data` - Arbitrary data handed into the Pool Manager by the + /// swapper to be be passed on to the hook. + /// + /// # Errors + /// + /// May return an [`Error`]. + fn before_swap( + sender: Address, + key: PoolKey, + params: SwapParams, + hook_data: Bytes, + ) -> Result<(FixedBytes<4>, BeforeSwapDelta, U24), Error>; + + /// The hook called after a swap. + /// + /// Returns the function selector for the hook, and + /// the hook's delta in unspecified currency. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `sender` - The initial msg::sender() for the swap call. + /// * `key` - The key for the pool. + /// * `params` - The parameters for the swap. + /// * `delta` - The amount owed to the caller (positive), or owed to the + /// pool (negative). + /// * `hook_data` - Arbitrary data handed into the Pool Manager by the + /// swapper to be be passed on to the hook. + /// + /// # Errors + /// + /// May return an [`Error`]. + fn after_swap( + &mut self, + sender: Address, + key: PoolKey, + params: SwapParams, + delta: BalanceDelta, + hook_data: Bytes, + ) -> Result<(FixedBytes<4>, I128), Error>; + + /// The hook called before donate. + /// + /// Returns the function selector for the hook. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `sender` - The initial msg::sender() for the donate call. + /// * `key` - The key for the pool. + /// * `amount0` - The amount of token0 being donated. + /// * `amount1` - The amount of token1 being donated. + /// * `hook_data` - Arbitrary data handed into the Pool Manager by the donor + /// to be be passed on to the hook + /// + /// # Errors + /// + /// May return an [`Error`]. + fn before_donate( + &mut self, + sender: Address, + key: PoolKey, + amount0: U256, + amount1: U256, + hook_data: Bytes, + ) -> Result, Error>; + + /// The hook called after donate + /// + /// Returns the function selector for the hook. + /// + /// # Arguments + /// + /// * `&mut self` - Write access to the contract's state. + /// * `sender` - The initial msg::sender() for the donate call. + /// * `key` - The key for the pool. + /// * `amount0` The amount of token0 being donated. + /// * `amount1` - The amount of token1 being donated. + /// * `hook_data` - Arbitrary data handed into the Pool Manager by the donor + /// to be be passed on to the hook + /// + /// # Errors + /// + /// May return an [`Error`]. + fn after_donate( + &mut self, + sender: Address, + key: PoolKey, + amount0: U256, + amount1: U256, + hook_data: Bytes, + ) -> Result, Error>; +} diff --git a/contracts/src/uniswap/v4/mod.rs b/contracts/src/uniswap/v4/mod.rs new file mode 100644 index 000000000..00067ffac --- /dev/null +++ b/contracts/src/uniswap/v4/mod.rs @@ -0,0 +1,9 @@ +//! Contracts implementing Uniswap V4 mechanisms. +pub mod hooks; +pub mod types; + +pub use hooks::IHooks; +pub use types::{ + BalanceDelta, BeforeSwapDelta, Currency, ModifyLiquidityParams, PoolKey, + SwapParams, I24, U24, +}; diff --git a/contracts/src/uniswap/v4/types.rs b/contracts/src/uniswap/v4/types.rs new file mode 100644 index 000000000..b473dd650 --- /dev/null +++ b/contracts/src/uniswap/v4/types.rs @@ -0,0 +1,139 @@ +//! Module with data types for Uniswap V4 Hooks. +use alloy_primitives::{Address, Signed, Uint, I256}; +use alloy_sol_types::sol; + +// TODO: newer alloy's versions have `I24`. +pub type I24 = Signed<24, 1>; +// TODO: newer alloy's versions have `U24`. +pub type U24 = Uint<24, 1>; + +pub type BalanceDelta = I256; +pub type BeforeSwapDelta = I256; + +pub type Currency = Address; + +sol! { + struct ModifyLiquidityParams { + /// the lower and upper tick of the position + int24 tickLower; + int24 tickUpper; + /// how to modify the liquidity + int256 liquidityDelta; + //// a value to set if you want unique liquidity positions at the same range + bytes32 salt; + } + +} + +sol! { + struct SwapParams { + /// Whether to swap token0 for token1 or vice versa + bool zeroForOne; + /// The desired input amount if negative (exactIn), or the desired output amount if positive (exactOut) + int256 amountSpecified; + /// The sqrt price at which, if reached, the swap will stop executing + uint160 sqrtPriceLimitX96; + } +} + +sol! { + /// Returns the key for identifying a pool. + struct PoolKey { + /// The lower currency of the pool, sorted numerically. + // TODO + address currency0; + /// The higher currency of the pool, sorted numerically. + // TODO + address currency1; + /// The pool LP fee, capped at 1_000_000. + /// If the highest bit is 1, the pool has a dynamic fee and + /// must be exactly equal to 0x800000. + uint24 fee; + /// Ticks that involve positions must be a multiple of tick spacing + int24 tickSpacing; + /* + /// @notice The hooks of the pool + IHooks hooks; + */ + } +} + +pub use interface::IHooks; +#[allow(missing_docs)] +mod interface { + stylus_sdk::stylus_proc::sol_interface! { + interface IHooks { + /* + function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96) external returns (bytes4); + + function afterInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, int24 tick) + external + returns (bytes4); + + function beforeAddLiquidity( + address sender, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external returns (bytes4); + + function afterAddLiquidity( + address sender, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata params, + BalanceDelta delta, + BalanceDelta feesAccrued, + bytes calldata hookData + ) external returns (bytes4, BalanceDelta); + + function beforeRemoveLiquidity( + address sender, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata params, + bytes calldata hookData + ) external returns (bytes4); + + function afterRemoveLiquidity( + address sender, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata params, + BalanceDelta delta, + BalanceDelta feesAccrued, + bytes calldata hookData + ) external returns (bytes4, BalanceDelta); + + function beforeSwap( + address sender, + PoolKey calldata key, + IPoolManager.SwapParams calldata params, + bytes calldata hookData + ) external returns (bytes4, BeforeSwapDelta, uint24); + + function afterSwap( + address sender, + PoolKey calldata key, + IPoolManager.SwapParams calldata params, + BalanceDelta delta, + bytes calldata hookData + ) external returns (bytes4, int128); + + function beforeDonate( + address sender, + PoolKey calldata key, + uint256 amount0, + uint256 amount1, + bytes calldata hookData + ) external returns (bytes4); + + function afterDonate( + address sender, + PoolKey calldata key, + uint256 amount0, + uint256 amount1, + bytes calldata hookData + ) external returns (bytes4); + */ + } + + } +}