Exploring the Core Mechanisms of UniswapV4

AdvancedDec 24, 2023
This article interprets three innovative features of UniswapV4 - Flash Accounting, Singleton Contract, and Hooks Architecture - from a code and implementation perspective.
Exploring the Core Mechanisms of UniswapV4

Introduction:

Since the announcement of UniswapV4, this swap platform has undergone a significant transformation, evolving from a simple swap platform to an infrastructure service provider. In particular, the Hooks feature of V4 has gained widespread attention. After in-depth research, I have compiled some content to help everyone better understand this transformation and its implementation.

The focus of UniswapV4’s innovation is not just about improving AMM technology but also about expanding the ecosystem. Specifically, this innovation includes the following key features:

  • Flash Accounting
  • Singleton Contract
  • Hooks Architecture

In the following sections, I will explain in detail the significance of these features and their implementation principles.

source: https://twitter.com/jermywkh/status/1670779830621851650

Flash Accounting

Double Entry Bookkeeping

UniswapV4 adopts a record-keeping method similar to Double Entry Bookkeeping to track the balance changes of tokens corresponding to each operation. This Double Entry Bookkeeping method requires recording each transaction in multiple accounts simultaneously and ensuring the balance of assets between these accounts remains balanced. For example, suppose a user exchanges 100 TokenA for 50 TokenB from the pool. The record in the ledger would be as follows:

  • USER: TokenA decreased by 100 units (-100), while TokenB increased by 50 units (+50).
  • POOL: TokenA increased by 100 units (+100), while TokenB decreased by 50 units (-50).

Token Delta and Related Operations

In UniswapV4, this record-keeping method is primarily used for major operations, and a storage variable named lockState.currencyDelta[currency] is used in the code to record the amount of token balance changes. If the value of this delta is positive, it represents the expected increase in the token amount in the pool, while a negative value represents the expected decrease in the token amount. Alternatively, if the value is positive, it indicates the amount of token shortage in the pool (the expected amount to be received), while a negative value indicates the excess token in the pool (the expected amount for users to withdraw). The following list shows the effects of various operations on Token Delta:

  • modifyPosition: Represents the Add/Remove liquidity operation. For Add liquidity, TokenDelta is updated using addition (representing the expected amount of TokenA to be added to the pool). For Remove liquidity, TokenDelta is updated using subtraction (representing the expected amount of TokenB to be withdrawn from the pool).
  • swap: Represents the Swap operation. Taking the example of swapping TokenA for TokenB, TokenADelta is updated using addition, while TokenBDelta is updated using subtraction.
  • settle: Accompanies the transfer of tokens to the pool. The pool calculates the increase in token amount before and after and updates TokenDelta using subtraction. If the pool receives the expected amount of tokens, the subtraction will zero out TokenDelta.
  • take: Accompanies the withdrawal of tokens from the pool. TokenDelta is updated using addition, indicating that the tokens have been removed from the pool.
  • mint: The behavior of updating TokenDelta is similar to “take,” but instead of actually withdrawing tokens from the pool, ERC1155 tokens are issued as proof of withdrawal, while the tokens remain in the pool. Later, users can retrieve the tokens from the pool by burning the ERC1155 tokens. The purpose of this approach may be twofold: 1. Save gas costs for ERC20 token transfers (contract call + one fewer storage write), and use ERC1155 token burn in the future to update TokenDelta for transactions. 2. Preserve liquidity in the pool to maintain a deep liquidity pool for better user swapping experience.
  • donate: This operation declares the donation of tokens to the pool, but in actuality, tokens still need to be transferred to the pool using “settle.” Therefore, TokenDelta is updated using addition in this case.

Among these operations, only “settle” and “take” involve the actual transfer of tokens, while other operations are solely responsible for updating the TokenDelta value.

Token Delta Example

Here we use a simple example to illustrate how to update TokenDelta. Let’s assume that today we exchange 100 TokenA for 50 TokenB:

  1. Before the transaction starts, both TokenADelta and TokenBDelta are 0.
  2. swap: Calculate how much TokenA the Pool needs to receive and how much TokenB the user will receive. At this point, TokenADelta = 100, TokenBDelta = -50.
  3. settle: Send 100 TokenA to the Pool and update TokenADelta = 100 - 100 = 0.
  4. take: Transfer 50 TokenB from the Pool to the user’s account and update TokenBDelta = -50 + 50 = 0.
  5. After the transaction is completed, both TokenADelta and TokenBDelta are 0.

When the entire exchange operation is completed, both TokenADelta and TokenBDelta are reset to 0. This means that the operation has been completely balanced, thus ensuring the consistency of account balances.

EIP-1153: Transient storage opcodes

Previously, it was mentioned that UniswapV4 utilizes Storage Variables to record TokenDelta. However, within the contract, reading and writing to Storage Variables are quite expensive. This brings us to another EIP introduced by Uniswap: EIP1153 - Transient Storage Opcodes.

UniswapV4 plans to use the TSTORE and TLOAD opcodes provided by EIP1153 to update TokenDelta. Storage Variables that adopt Transient Storage Opcodes will be discarded after the end of the transaction (similar to Memory Variables), thus reducing gas fees.

EIP1153 has been confirmed to be included in the upcoming Cancun upgrade, and UniswapV4 has also stated that it will go live after the Cancun upgrade, as reported here.

source: https://etherworld.co/2022/12/13/transient-storage-for-beginners/

Flash Accounting — Lock

UniswapV4 introduces a lock mechanism, which means that before performing any Pool operation, you must first call PoolManager.lock() to acquire a lock. During the execution of lock(), it checks if the TokenDelta value is 0, otherwise it will revert. Once PoolManager.lock() is successfully acquired, it calls the lockAcquired() function of msg.sender. Inside the lockAcquired() function, the operations related to the Pool, such as swap and modifyPosition, are performed.

The process is illustrated below. When a user needs to perform a Token Swap operation, they must call a Smart Contract with the lockAcquired() function (referred to as the Callback Contract). The Callback Contract first calls PoolManager.lock(), and then PoolManager calls the lockAcquired() function of the Callback Contract. Inside the lockAcquired() function, the logic related to Pool operations, such as swap, settle, and take, is defined. Finally, when the lock() is about to end, PoolManager checks if the TokenDelta associated with this operation has been reset to 0, ensuring the balance of assets in the Pool remains intact.

Singleton Contract

Singleton Contract means that UniswapV4 has abandoned the previous Factory-Pool model. Each Pool is no longer an independent Smart Contract, but all Pools share a single singleton contract. This design, combined with the Flash Accounting mechanism, only requires updating the necessary Storage Variables, further reducing the complexity and cost of operations.

In the example below, using UniswapV3 as an example, exchanging ETH for DAI would require at least four Token transfers (Storage write operations). This includes multiple changes recorded for USDC, USDT, and DAI Tokens. However, with the improvements in UniswapV4, coupled with the Flash Accounting mechanism, only one Token transfer is needed (moving DAI from the Pool to the user), significantly reducing the number of operations and costs.

source: https://twitter.com/Uniswap/status/1671208668304486404

Hooks Architecture

In the latest update of UniswapV4, the most notable feature is the Hooks Architecture. This update brings great flexibility in terms of Pool availability. Hooks are additional actions that are triggered through the Hooks Contract when performing specific operations on the Pool. These actions are categorized into initialize (create pool), modifyPosition (add/remove liquidity), swap, and donate. Each category has pre-execution and post-execution actions.

  • beforeInitialize / afterInitialize
  • beforeModifyPosition / afterModifyPosition
  • beforeSwap / afterSwap
  • beforeDonate / afterDonate

This design allows users to execute custom logic before and after specific operations, making it more flexible and expanding the functionality of UniswapV4.

source: https://github.com/Uniswap/v4-core/blob/main/whitepaper-v4-draft.pdf

Hook Example — Limit Order Hook

Next, we will use an example of a Limit Order to explain the actual operation process of Hooks in UniswapV4. Before we begin, let’s briefly explain the principle of implementing Limit Orders in UniswapV4.

UniswapV4 Limit Order Mechanism

The UniswapV4 implementation of the limit order works by adding liquidity to a specific price range and then executing the remove liquidity operation if the liquidity in that range is swapped.

For example, let’s say we add liquidity in the price range of 1900-2000 for ETH, and then the price of ETH rises from 1800 to 2100. At this point, all the ETH liquidity we previously added in the 1900-2000 price range has been swapped for USDC (assuming in the ETH-USDC pool). By removing the liquidity at this moment, we can achieve a similar effect to executing an ETH market order at the current price range of 1900-2000.

Limit Order Hook Contract

This example is taken from GitHub of UniswapV4. In this example, the Limit Order Hook contract provides two hooks, namely afterInitialize and afterSwap. The afterInitialize hook is used to record the price range (tick) when creating a pool, in order to determine which limit orders have been matched after someone swaps.

Place Order

When the user needs to place an order, the Hook contract executes the liquidity addition operation based on the user-specified price range and quantity. In the Hook contract for limit orders, you can see the place() function. The main logic is to call the lockAcquiredPlace() function after acquiring the lock to execute the liquidity addition operation, which is equivalent to placing a limit order.

source: https://github.com/Uniswap/v4-periphery/blob/main/contracts/hooks/examples/LimitOrder.sol#L246

afterSwap Hook

After the user completes a Swap Token within this Pool, the Pool will invoke the afterSwap() function of the Hook contract. The main logic of afterSwap is to remove the liquidity of previously placed orders that have been executed between the previous price range and the current price range. This behavior is equivalent to the order being filled.

source: https://github.com/Uniswap/v4-periphery/blob/main/contracts/hooks/examples/LimitOrder.sol#L192

Limit Order Flow

Here is a flowchart illustrating the process of executing a limit order:

  1. The order placer sends the order to the Hook contract.
  2. The Hook contract executes liquidity addition operations based on the order information.
  3. Regular users perform Token Swap operations in the Pool.
  4. After the completion of the Swap Token operation, the Pool calls the afterSwap() function of the Hook contract.
  5. The Hook contract executes liquidity removal operations for filled limit orders based on the price range variation of the swapped tokens.

The above is the entire process of implementing Limit-Order using the Hook mechanism.

Hook: Other features

Hooks have several interesting points that I find worth sharing.

Hooks Contract Address Bit

The decision to perform specific operations before/after is determined by the leftmost 1 byte of the Hook contract address. 1 byte is equal to 8 bits, which corresponds to 8 additional actions. The Pool will check if the bit of that action is 1 to determine whether to invoke the corresponding hook function of the Hook contract. This also means that the address of the Hook contract needs to be designed in a specific way and cannot be arbitrarily chosen as the Hook contract. This design is mainly aimed at reducing gas consumption and shifting the cost to contract deployment to achieve more efficient operations. (PS: In practice, different CREATE2 salts can be used to brute force calculate contract addresses that meet the conditions)

Dynamic Fee

In addition to being able to perform additional operations before and after each action, Hooks also support the implementation of dynamic fees. When creating a Pool, you can specify whether to enable dynamic fees. If dynamic fees are enabled, the getFee() function of the Hook contract is called when swapping tokens. The Hook contract can determine the amount of fees to be charged based on the current state of the Pool. This design allows for flexible fee calculation based on the actual circumstances, increasing the system’s flexibility.

Pool Creation

Each Pool needs to determine the Hook contract during its creation, and it cannot be changed afterward (although different Pools can share the same Hook contract). This is mainly because Hooks are considered part of the PoolKey, and the PoolManager uses the PoolKey to identify which Pool to operate on. Even if the assets are the same, if the Hook contract is different, it will be considered as a different Pool. This design ensures that the state and operations of different Pools can be managed independently, ensuring the consistency of Pools. However, it also increases the complexity of routing as the number of Pools increases (perhaps UniswapX is designed to solve this problem).

TL;DR

  • Flash Accounting is used to track the quantity changes of each token to ensure that all changes are zeroed out after completing a transaction. To save on gas fees, Flash Accounting uses a special storage method provided by EIP1153.
  • Singleton Contract design helps reduce gas consumption by avoiding updates to multiple storage variables.
  • Hooks architecture provides additional operations, divided into pre-execution and post-execution stages. This allows for more flexibility in each Pool operation but also makes the routing of Pools more complex.

UniswapV4 clearly emphasizes expanding the entire Uniswap ecosystem, turning it into infrastructure to enable more services to be built on the foundation of Uniswap Pools. This helps enhance Uniswap’s competitiveness and reduces the risk of alternative services. However, whether it will achieve the expected success remains to be seen. Some highlights include the combination of Flash Accounting and EIP1153, and we believe that more services will adopt these features in the future, leading to various application scenarios. This is the core concept of UniswapV4, and we hope it provides a deeper understanding of how UniswapV4 operates. If there are any errors in the article, please feel free to point them out. We also welcome discussions and feedback.

Finally, we would like to thank Anton Cheng and Ping Chen for reviewing the article and providing valuable feedback!

Disclaimer:

  1. This article is reprinted from [medium]. All copyrights belong to the original author [林瑋宸 Albert Lin]. If there are objections to this reprint, please contact the Gate Learn team([email protected]), and they will handle it promptly.
  2. Liability Disclaimer: The views and opinions expressed in this article are solely those of the author and do not constitute any investment advice.
  3. Translations of the article into other languages are done by the Gate Learn team. Unless mentioned, copying, distributing, or plagiarizing the translated articles is prohibited.
* The information is not intended to be and does not constitute financial advice or any other recommendation of any sort offered or endorsed by Gate.io.
* This article may not be reproduced, transmitted or copied without referencing Gate.io. Contravention is an infringement of Copyright Act and may be subject to legal action.
Start Now
Sign up and get a
$100
Voucher!
Create Account