Uniswap V4 Documentation

website·Indexed 31 days ago

https://blockchaincampus.net

Technical guides and core explanations for Uniswap V4, covering the Pool Manager, Flash Accounting via EIP-1153, and the execution of pool.swap(params). Includes deep dives into liquidity modification, pool creation, and hook examples such as Limit Orders.

Tokens
12.4K
Snippets
40
Records
57
Agent score
50%

What's inside Uniswap V4

  1. Support multiple users per limit order price

    The limit order contract is designed to allow multiple users to place orders at the same price (tick). This increases the liquidity depth at that specific price level, allowing multiple liquidity providers to participate in the same limit order tick.
  2. Understand Flash Accounting via EIP-1153 Transient Storage

    Uniswap V4 uses 'Flash Accounting' to replace frequent persistent storage writes and token transfers during multi-step operations (like cross-pool swaps). It leverages EIP-1153 transient storage (TLOAD and TSTORE opcodes), which is persistent only for the duration of a single transaction. This reduces gas costs significantly: transient storage operations cost 100 gas, compared to 2100 for cold reads and 2900 for dirty writes in traditional storage. Intermediate balance changes (deltas) are tracked in transient storage and settled in a single batch at the end of the transaction.
  3. Blockchain Campus co-reading workflow

    The process for participating in the co-reading program follows these five sequential steps:

    1. Connect Wallet
    2. Get SUI Tokens
    3. Stake SUI
    4. Submit Review
    5. Verify & Claim
  4. Understand the Epoch system in Uniswap V4 Limit Orders

    The Limit Order hook uses an 'Epoch' system to uniquely identify and manage conditional orders. An Epoch is a uint232 identifier. Multiple users can contribute liquidity to the same price level (tick) and direction (zeroForOne), effectively sharing a single Epoch. This allows the hook to track total liquidity for a specific order and distribute the resulting tokens proportionally to each contributor based on their share of the total liquidity.
    type Epoch is uint232;
    
    struct EpochInfo {
        bool filled;              // Whether the order is filled
        Currency currency0;       // First token in the pair
        Currency currency1;       // Second token in the pair
        uint256 token0Total;      // Total token0 amount accumulated
        uint256 token1Total;      // Total token1 amount accumulated
        uint128 liquidityTotal;   // Total liquidity provided by all users
        mapping(address => uint128) liquidity;  // Liquidity contribution per user
    }
  5. Understand Uniswap v4 Constant Product Formula

    Uniswap v4 utilizes the constant product formula, defined as x * y = k, where x and y represent the reserves of two assets in a liquidity pool and k is a constant. This formula ensures that the product of the reserves remains balanced, allowing for permissionless, non-custodial trading without the need for traditional order books.
  6. Optimize gas costs using Flash Accounting and EIP-1153

    Uniswap V4 utilizes a singleton pool design combined with EIP-1153 transient storage to implement 'Flash Accounting'. Instead of performing actual token transfers for every pool crossing during a transaction, the system stores temporary data (deltas) in transient storage. The final token state is written to the blockchain only once at the end of the transaction, replacing multiple individual transfers with a single final transfer to significantly reduce gas costs.
  7. Track and reconcile token deltas using NonzeroDeltaCount

    Uniswap V4 uses a transient storage counter to ensure all token deltas are reconciled by the end of a transaction. The count is incremented when a new delta is set (previous value was 0) and decremented when a delta is cleared (next value is 0). A final check ensures the count returns to 0, guaranteeing all obligations are settled.
    // src/libraries/NonzeroDeltaCount.sol
    function increment() internal {
        assembly ("memory-safe") {
            let count := tload(NONZERO_DELTA_COUNT_SLOT)
            count := add(count, 1)
            tstore(NONZERO_DELTA_COUNT_SLOT, count)
        }
    }
    function decrement() internal {
        assembly ("memory-safe") {
            let count := tload(NONZERO_DELTA_COUNT_SLOT)
            count := sub(count, 1)
            tstore(NONZERO_DELTA_COUNT_SLOT, count)
        }
    }
  8. Calculate final swap balance delta

    The final swapDelta is determined by comparing the swap direction (zeroForOne) against whether the user specified an exact input or output (params.amountSpecified).

    • If the swap is accurate for token1 (e.g., swapping token0 for token1 with exact output, or token1 for token0 with exact input), the balance delta is calculated using the calculated amount for token1.
    • Otherwise, the swap is accurate for token0, and the delta is calculated accordingly.
    if (zeroForOne != (params.amountSpecified < 0)) {
        swapDelta = toBalanceDelta(
            amountCalculated.toInt128(), (params.amountSpecified - amountSpecifiedRemaining).toInt128()
        );
    } else {
        swapDelta = toBalanceDelta(
            (params.amountSpecified - amountSpecifiedRemaining).toInt128(), amountCalculated.toInt128()
        );
    }
  9. Modify liquidity in Uniswap v4 pools

    To modify liquidity in a Uniswap v4 pool, developers use the modifyLiquidity function. This process requires a PoolKey to identify the pool and a ModifyLiquidityParams structure to define the liquidity change. Because Uniswap v4 uses a 'singleton' design, liquidity modifications must be performed within an unlock callback to ensure atomic settlement of token balances.
    struct ModifyLiquidityParams {
        int24 tickLower;    // The lower bound of the liquidity range
        int24 tickUpper;    // The upper bound of the liquidity range
        int256 liquidityDelta; // The amount of liquidity to add (positive) or remove (negative)
        bytes32 salt;       // Allows creating multiple independent positions in the same range
    };
  10. Handle Exact Input vs Exact Output swaps in UniswapV4

    UniswapV4 distinguishes between exact input and exact output swaps using the sign of params.amountSpecified:

    1. Exact Output (params.amountSpecified > 0): The user specifies the exact amount of tokens they wish to receive. The pool calculates the required input. amountCalculated tracks tokens the user must provide (represented as a negative value), and amountSpecifiedRemaining tracks the remaining output to be sent to the user.
    2. Exact Input (params.amountSpecified < 0): The user provides a fixed amount of tokens. The pool calculates the output. amountCalculated tracks tokens the user will receive (positive value), and amountSpecifiedRemaining tracks the remaining input to be provided (negative value).
  11. Update Pool state after a swap execution

    After a swap step is computed, the pool's state must be updated to reflect the new price, current tick, and liquidity. Additionally, the global fee growth tracker (feeGrowthGlobal1X128 or feeGrowthGlobal0X128) is updated based on the swap direction (zeroForOne).
    self.slot0 = slot0Start.setTick(result.tick).setSqrtPriceX96(result.sqrtPriceX96);
    // update liquidity if it changed
    if (self.liquidity != result.liquidity) self.liquidity = result.liquidity;
    
    // update fee growth global
    if (!zeroForOne) {
        self.feeGrowthGlobal1X128 = step.feeGrowthGlobalX128;
    } else {
        self.feeGrowthGlobal0X128 = step.feeGrowthGlobalX128;
    }
  12. Calculate Token Output for a Swap within a Price Range

    When swapping tokens within a price range where liquidity L is sufficient (and not crossing ticks), the amount of token x (Δx) received for a given input of token y is calculated by the change in the inverse square root of the price:

    Δx = Δ(1/sqrt(P)) * L where Δ(1/sqrt(P)) = (1/sqrt(P_target)) - (1/sqrt(P_current)).