Summary
Hooks contract is one of the main features of Uniswap V4. The execution of a pool can be split into multiple phases, such as: before and after the creation, or the swap of the pool, we can execute the predefined life cycle functions of the Hooks contract in the various phases of the execution of the pool, therefore, the developer can write a variety of Hooks contract, using its life cycle function to achieve a variety of new features, which is very conducive to the development of a variety of businesses based on the Hooks extension, such as: limit orders, dynamic tariffs, TWAMM, Yield interest generation and so on. This is the Hooks chapter of the Uniswap V4 series, which analyses Hooks contracts from the perspectives of their implementation principles and interaction flow.
Core Contract of the Hooks
The core code of the Hooks contract is in the repository v4-core, and the PoolManager contract manages the pools and stores the state of all the pools. Let's start by analyzing the initialize function of the PoolManager contract, which creates the pool.
The initialize function
The initialize function Initialises the specified pool:
- The first parameter of the initialize function is PoolKey, which determines the uniqueness of the Pool. PoolKey is defined as follows:
- Compared to Uniswap V3, a new field hooks is added to the pool's unique index, which indicates that even if the pair(currency0,currency1), fee, and tickSpacing are the same, once the hooks are different, it is still a different pool.
- The onlyByLocker modifier, which only allows calls from any address other than the current Locker or the most recently invoked pre-licensed hook.
- The main steps of the initialize function include:
- A series of parameter checks
- Whether static costs are out of bounds;
- Whether tickSpacing is out of bounds. The larger the tickSpacing is, the more gas is saved, and at the same time, the larger the slippage is, which is good for coins with high price fluctuations;
- The currency0 must be smaller than the currency1 to avoid creating duplicate pairs of coins;
- Check on the hooks contract address
- If NoOp is allowed, at least one of beforeModifyPosition, beforeSwap and beforeDonate should be allowed;
- If no Hooks contract is set, the fee cannot be set to dynamic;
- If a Hooks contract is set, at least 1 flag must be set, or a dynamic fee must be set.
- Call the beforeInitialize() and afterInitialize() functions of hooks successively, so far, we know that the reason why the Hooks contract can be called back before and after the initialization of the pool is that:
a. The PoolKey specifies the hooks contract to be used for the callback;
b. The PoolMananger contract calls back the beforeInitialize() and afterInitialize() functions of the hooks contract when it initialises the pool using the initialize function.
- Initialize a new pool, pools[id]. Inside Uniswap V3, a new UniswapV3Pool instance is created when the pool is created. In Uniswap V4, when creating a pool, do not create a new contract instance, and the state of all pools is stored in a mapping-type pool, which is the singleton pattern of Uniswap V4, replacing the factory pattern of Uniswap V3, which reduces the gas cost of creating pools, and all the pools are managed by the PoolManager.
The lock function
As mentioned above, only the Locker role can call the intialize function, and to become a Locker you need to call the lock function, as shown below:
- Add lockTarger as a Locker;
- Callback to the lockAcquired function of the Hooks contract(lockTarger);
- Check on and set the Lockers array. If the length of Lockers is 1, the nonzeroDeltaCount() of Lockers must be 0.
Therefore, we can get the main interaction flow between Hooks contract and PoolManager as follows:
The modifyPosition function
The modifyPosition function is used for liquidity changes.
Note: In the new commit, the modifyPosition is renamed to modifyLiquidity function, and beforeModifyPosition and afterModifyPosition are renamed to beforeModifyLiquidity and afterModifyLiquidity, but the main logic remains the same, and this article is based on commit 835571.
- Unlike the initialize function, there is an extra noDelegateCall modifier, which disables delegate calls;
- Verify whether the pool is initialised or not;
- The beforeModifyPosition function and afterModifyPosition function of Hooks are called back successively;
- Modify liquidity:
- The owner represents the owner of the position;
- The tickLower is the lower bound of the position interval, the tickUpper is the upper bound of the position interval (when the spot price is lower than the lower bound or higher than the upper bound of the position interval, there will be only one token left in the position);
- The liquidityDelta can be positive or negative, representing additions or removals to the liquidity pool;
- The tickSpacing is interpreted in the initialize function. (The modifyPosition function is similar to liquidity management in Uniswap V3, and will not be discussed further in this article)
- Update the token balance, and nonzeroDeltaCount`(), which is checked in the lock function mentioned above, and when hooks exit, nonzeroDeltaCount() is required to be 0, i.e., no tokens are outstanding between the user and the pool.
The swap function
The swap function handles the token swap for the user.
- The swap function has the same access rights as the modifyPosition function, controlled by noDelegateCall and onlyByLocker modifier.
- Verify that the pool is initialized or not;
- Call the beforeSwap and afterSwap callback functions of the Hooks contract successively;
- Swapping tokens:
- The tickSpacing is the tick spacing (interpreted in the initialize function before);
- The zeroForOne is the direction of the token swap, which determines the coin for which the fee is charged;
- The amountSpecified is the number of tokens swapped.
- The sqrtPriceLimitX96 is the limited price, used to prevent slippage. (The swap function is also similar to Uniswap V3's swap operation, which will not be discussed further in this article)
- Tokens balance update, note that here there is no transfer of tokens between the user and the pool, the final settlement of tokens by the settle function, take function to complete;
- Protocol fee update.
The take function
- Transfer tokens from the pool to the to address;
- Can be used for the flashloan.
The settle function
- Used to calculate the tokens that the user pays to the pool;
- Before calling the settle function, the user needs to transfer tokens to the PoolManager contract in advance, if the token is an ERC20 token.
The donate function
Donating tokens to the pool
- The main flow is the same as the previous initialise function: parameter checking, calling beforeDonate and afterDonate callback functions successively, token donation, token balance update;
Hooks Demos
BaseHook.sol and the official example Hooks are in the repository v4-periphery. There are five example Hooks in repository v4-periphery:
- FullRange.sol, add and remove liquidity across the entire price range, similar to Uniswap V2;
- GeomeanOracle.sol, uses the Uniswap pool to act as a Hook for the price oracle;
- LimitOrder.sol, Hook to support user limit orders;
- TWAMM.sol, TWAMM (Time Weighted Average Market Maker) is a type of market maker that makes a time-weighted average to calculate the price of an asset, and this Hook supports the TWAMM method of trading tokens. For more details, please refer to the blog post on the official website Uniswap v4 TWAMM Hook;
- VolatilityOracle.sol, support for a dynamic fee.
Note: At the time of writing, Uniswap V4 is not yet deployed on the mainnet and the code is still being updated. Uniswap mentioned that although some hooks have been audited and are in a production-ready state, it is not guaranteed that they are safe for all users, and it is recommended to conduct a security audit before the hooks go live.
The BaseHook contract
The BaseHook contract is an abstract contract, as the parent contract of Hooks contracts, contains the basic interfaces of the Hooks contract, which needs to be implemented by Hooks contracts.
The FullRange contract
Take the FullRange contract as an example to analyze. The liquidity range of the FullRange is the entire price range, rather than concentrated liquidity.
- The getHooksCalls function, it implies the Hook contract will implement 3 callback functions beforeInitialize, beforeModifyPosition, and beforeSwap.
- beforeInitialize, stores basic information of the new pool into poolInfo.
- The addLiquidity function, adds liquidity for users
- Query the pool liquidity and calculate the newly added liquidity;
- The actual liquidity management is done by the modifyPosition function, which calls the poolManager.lock function, which in turn calls back the LockAcquired function of the FullRange contract in the lock function;
- Mint LP tokens for the user and validate the slippage. (The first person to gain the LP tokens is debited with a small number of LP tokens equal to the share of MINIMUM_LIQUIDITY, which is actually how it was done in Uniswap V2.)
- The lockAcquired function, it is a callback function, e.g. the addLiquidity function triggers a call to this function and completes the settlement of the token transfer.
- The removeLiquidity function, it provides the ability to remove liquidity, which is equivalent to the inverse operation of addLiquidity.
The Community's Hooks Contract
- Multi-Sig, which requires multiple signatures for certain pool operations, such as adding or removing liquidity. This can be used to add an extra layer of security to the pool;
- Whitelist, which restricts participation in the pool to a whitelist of approved addresses. This can be used to prevent certain people from participating in the pool, such as those who have been banned from the platform or those who are considered high-risk traders.
- Stop Loss Order, which allows users to place a stop loss order on their positions. This means that the position will be automatically closed if the price reaches a target price;
- Ref Fee Hook, hooks that charge a referral fee for swaps and liquidity additions. This can be used to incentivize users to refer others to the pool;
- Dynamic Fee Hook, a hook that uses a volatility fee prediction machine to adjust the pool fee based on the actual volatility of the currency pair.
Note: The community contract does not guarantee its security, so it is recommended that a security audit be conducted before the Hooks go live.
Version
Uniswap V4 will go live after the Dencun upgrade, and its code may probably be updated in the meantime. This post is based on the following commits analysis:
V4-core: 83557113a0425eb3d81570c30e7a5ce550037149, Dec 11, 2023
V4-periphery: 63d64fcd82bff9ec0bad89730ce28d7ffa8e4225, Dec 20, 2023
The new commit renames the interface modifyposition and callback functions, but the basic structure remains the same.
Reference
https://uniswap.org/
https://github.com/Uniswap/v4-core
https://github.com/Uniswap/v4-periphery
https://github.com/hyperoracle/awesome-uniswap-hooks