Back to all stories
Blogs
Ecosystem
Uniswap V4: Hooks Security Considerations
1/28/2025
Uniswap V4: Hooks Security Considerations

Uniswap V4 is scheduled to be deployed on the Ethereum Mainnet in January 2025, and marks a significant evolution in DeFi. One of the most notable innovations in Uniswap V4 is the introduction of hooks, which allow developers to build custom logic on top of Uniswap. This enables the introduction of a wide range of applications without needing to modify the Uniswap V4 core contracts. However, the flexibility of the hook design also introduces new surface area for potential security vulnerabilities.

This article discusses some of the new features of Uniswap V4, and explores the security considerations related to Uniswap V4 hooks.

Introduction to Uniswap V4 and Hook Integration

Uniswap V4 introduces several new features:

  1. Customizable hooks, through which developers can attach bespoke logic to the liquidity provision and swapping lifecycle
  2. Singleton design, where all pool state and operations are managed by a single PoolManager contract
  3. Flash accounting system, where all intermediary balance changes are recorded in transient storage and netted against each other before being settled at the end of the transaction
  4. Dynamic fees and native ETH support.

V4 hooks can be placed before/after initializing a liquidity pool, before/after modifying liquidity, before/after swapping, and before/after donating. Using swapping as an example, the following simplified diagram shows where hooks fit into the workflow.

Flow Chart

When a swapping transaction is initiated, the swap function in the PoolManager contract first checks if the beforeSwap flag is true, and if it is, the PoolManager calls the beforeSwap function of the corresponding hook contract. Then, the PoolManager executes the core swapping logic. Subsequently, the afterSwap flag is checked, and if it is true, the PoolManager calls the afterSwap function of the same hook contract.

The two hook calls have return values that potentially impact the logic of the swap. For example, the beforeSwap function call returns a lpFeeOverride that can affect swap fees, and the afterSwap function returns a hookDelta that can affect the distribution of currency deltas between the msg.sender and the hook address. If the beforeSwap and afterSwap flags are false, then the hook is never called and the lines of code in PoolManager with the hook logic are no-ops. The mechanism that Uniswap V4 employs to determine the permission of the hooks involves examining the least significant bits of the deployed hook address. Once a hook is deployed, the permissions cannot be changed.

Security Considerations of Uniswap V4 Hooks

Before the deployment of Uniswap V4, various ETH communities and the Uniswap Foundation funded incubators and organized hackathons specifically for the development of Uniswap V4 hooks. As a part of our security research process, our security engineers have reviewed multiple hooks submissions from the incubators and hackathons.

The following chart illustrates a simplified mental model for the security considerations of hooks.

Hooks Chart

Users can interact with Uniswap V4 PoolManager either via a Router (or Universal Router) for swapping, or a Position Manager for liquidity modification, and the PoolManager calls the corresponding hook contract. Users can also interact with a Hook contract directly which, in turn, calls the PoolManager contract. For the User-Hook interface, a user can be a victim (if the hook is malicious) or a potential exploiter of the hook (if the hook is vulnerable). For the Hook - V4 PoolManager interface, the security measures in PoolManager are designed to protect themselves against a malicious hook. However, the integration of a hook can still be incorrect which can cause unintended consequences.

Hook - PoolManager Interface

The V4-core PoolManager contract enforces several requirements on the integration of hooks. Hook developers should be aware of these requirements, to ensure that their hooks would function as intended. Potential issues of incorrectly configured hooks include the following:

Hooks Permission Mismatch

Each hook is designed to operate with specific permissions in Uniswap V4. To determine whether a hook has certain permissions, V4 compares the least significant bits of the hook’s deployed address with a flag that corresponds to the different permission.

Code 1 Source: Github

Code 2 Source: Github

An incorrect implementation may occur when a hook function is deployed at an address that lacks the necessary permissions, or conversely, when the deployed address has the permission, but the function itself is not implemented. If a hook does not have the permission, the hook call is essentially a no-op. If the hook has the permission but the relevant function is not implemented, transactions could revert. Either case can lead to the hooks not functioning as intended and should be addressed.

Fortunately, the BaseHook contract in V4-periphery already checks for potential permission mismatch in the constructor. If a hook developer does not find the right address when deploying the hook, the hook contract will not deploy at all. This is one of the many reasons that hook developers should always inherit the BaseHook contract in their custom hooks.

In order to pass the validateHookAddress() check, the getHookPermissions() function also needs to be implemented in the hook contract, and the specified permissions should have their corresponding hook callback functions implemented.

Code 3 Sources: Github, Github

Incorrect Return Values

Another potential integration issue with hooks is returning unexpected or incorrectly typed data. All hooks should return either bytes4 or (bytes4, 32-byte-delta) or (bytes4, 32-byte-delta, uint24). Hooks that return data in the wrong format or fail to handle return values properly can lead to failed transactions due to invalid hook response.

Code 4 Source: Github

Missing Access Control

One common mistake we have observed from reviewing hooks contracts from Hackathon submissions is missing access control. Specifically, certain functions in the hook contract should only be called by the singleton PoolManager contract. This includes 1) the unlockCallback() function, and 2) the other hook callback functions for which the hook has permissions.

Generally, if a hook inherits the BaseHook contract, unlockCallback() should already have the safeguard. The BaseHook contract inherits the SafeCallback contract, in which the unlockCallback() function already has the onlyPoolManager modifier. When hook developers implement their own internal _unlockCallback() function, access control is already incorporated.

Modifier Source: Github

However, the same is not true for the other hook callback functions. In the example below, anyone can call the beforeSwap() function directly with arbitrary function arguments, including the swapper address which is encoded in the data field. Given that users need to approve the hook, anyone can force another user to transfer their tokens to the hook contract. The intended design should be to only let users trigger the transaction when they intend to do so, and only the PoolManager contract should have the ability to call the beforeSwap() function.

Swap Function Source: Github

Hook - User Interface

Uniswap V4 has robust security mechanisms to protect a malicious hook from tampering with the logic in the PoolManager contract; however, the hook-user interface can contain custom logic and thus have a broader attack surface. Depending on the design and intended use cases, the hook can act as a custodian of user funds, or contain execution logic only and never take possession of user funds.

When a hook acts as a custodian of user funds, vulnerabilities in the hook contract can cause direct loss of user funds, and thus requires close scrutiny. The following are a few categories of potential areas of concern in the hook-user interface.

Centralization Risks

Similar to any smart contract, there can be legitimate reasons for a hook contract to include privileged roles. However, if a privileged address is compromised, there could be significant risks. For example, if an upgradeable hook holds user funds, the address with the upgrade authority is capable of injecting a malicious withdrawal function via contract upgrade. As such, the usage of upgradeable contracts should be limited for hook contracts, and risk mitigation mechanisms such as multisig / DAO + timelock are also applicable to hook contracts.

The privileged role does not necessarily apply to the hook contract only. In the following example, the hook contract utilizes a custom ERC-20 contact to represent LP positions. If the custom ERC-20 contract allows a privileged address other than the hook contract to mint and burn tokens, then the privileged address could mint tokens and withdraw liquidity from the corresponding liquidity pool.

Balance Data Source: Github

Insufficient Input Validation

Uniswap V4 does not restrict who can create new liquidity pools, nor which hook address to use in a new liquidity pool. If a hook is not restricted to a specific pool or set of pools, an attacker could deploy a malicious pool with fake tokens using the hook. This can potentially compromise the hook contract.

In the BaseCustomAcounting abstract contract below, the _beforeInitialize() function allows poolKey to be set more than once. If a poolKey has already been set in the hook contract, a subsequent call to the _beforeInitialize() function would overwrite the previous poolKey. This abstract contract contains a note stating, “This base hook is designed to work with a single pool key. If you want to use the same custom accounting hook for multiple pools, you must have multiple storage instances of this contract and initialize them via the PoolManager with their respective pool keys.” However, there is no enforcement of such a mechanism in this base contract, and if a hook developer does not pay special attention to enforcing the single pool key requirement when extending this abstract contract, a malicious actor could still overwrite the pool key. Given that this base hook contract contains custom accounting and enables hook-owned liquidity, the impact of overwriting the pool key could be very serious, including the loss of user funds.

Function Code Source: Github

In the following example, the hook also does not restrict which pool can use the hook, and holds user funds. Anyone can register a pool with fake tokens and use the hook to perform swaps. Due to the external call to an untrusted address (fake token), a hacker can sandwich legit transactions (e.g. with a large swap or Just-In-Time liquidity), or reenter the handleSwap() function with arbitrary parameters.

Function Swap

Handle Swap Source: Github

To prevent situations like the above, hooks that are designed for specific pools should validate token pairs / PoolKey during initialization. This can be done in the beforeInitialize() or afterInitialize() functions. If a hook is designed to support any token pairs, developers should pay special attention to how a fake or malicious token pair could potentially compromise the hook contract. Robust access control and input validation could mitigate some of the risks associated with supporting arbitrary token pairs. Additionally, it is essential to conduct rigorous security reviews by working with trusted security partners.

Hooks that Modify swapDelta / callerDelta

The PoolManager in Uniswap V4 allows hooks to return a hookDelta in the modifyLiquidity() and swap() functions, provided that the hook has the necessary permissions when it is deployed. The hookDelta essentially modifies how delta is attributed between the msg.sender and the hook contract address. Given that all deltas need to be settled in order for a transaction to succeed, such attribution potentially dictates which party is responsible for settling which portions of the total delta.

This can introduce serious risks if the accounting is incorrect or intentionally manipulated to benefit the hook operator at the expense of users, or to benefit one group of users at the expense of another group.

Caller Data Sources: Github, Github

Potential Denial-of-Service Vulnerabilities

A hook could contain different revert conditions that are necessary, but it is important to ensure that key user flows, such as liquidity withdrawals, are not subject to unwarranted reverts. By deliberately reverting during the beforeRemoveLiquidity() or afterRemoveLiquidity() functions, a hook could permanently trap user funds in the protocol. There might be special situations where these revert conditions are the intended design, but these should be exceptions, and the behavior should be well-documented and disclosed to users.

In the following example, the beforeRemoveLiquidity() function always reverts, which could cause users’ liquidity positions to be permanently trapped.

Liquidity

Summary

Uniswap V4 sets the stage for more community-driven development, encouraging developers to create novel DeFi applications on top of its infrastructure. The introduction of hooks offers virtually limitless possibilities for customization, facilitating innovation in areas like capital-efficient LP management, custom trading strategies, and complex Oracle design. However, with great flexibility comes the potential of a broader attack surface.

In this article, we discussed how hooks integrate with Uniswap V4, as well as the security considerations of the Users - Hook and Hook - PoolManager interfaces. Like any smart contract development, performing rigorous security review should be an indispensable step in the development lifecycle.

CertiK has deep technical expertise in securing DeFi protocols. With ongoing research about Uniswap V4 hooks, CertiK stands ready to work with the Uniswap V4 hook developer community to help secure the V4 ecosystem and promote continuous innovation among developers.