Uniswap v4 Hooks Deep Dive: LA Tech Week Developer Workshop Insights

Just wrapped up an incredible Uniswap v4 hooks workshop at LA Tech Week 2025, and I need to share what we learned. Hooks are the most significant DeFi innovation since concentrated liquidity.

If you’re building AMMs or DeFi protocols, you need to understand what Uniswap v4 hooks enable.

What Are Uniswap v4 Hooks?

Uniswap v4 launched in Q2 2024 with a revolutionary feature: hooks. Hooks are smart contracts that execute at specific points in the swap lifecycle, allowing developers to customize AMM logic.

Think of hooks as middleware for AMMs.

Without Hooks (Uniswap v3)

User → Swap → Pool → Update State → Done

Static behavior: Constant product formula, fixed fee tiers (0.05%, 0.3%, 1%), no customization.

With Hooks (Uniswap v4)

User → beforeSwap hook → Swap → afterSwap hook → Pool → Done

Dynamic behavior: Custom pricing curves, dynamic fees, limit orders, TWAP oracles, MEV protection, geofencing, and more.

Hook Lifecycle Points

Uniswap v4 provides 10 hook points where custom logic can execute:

Pool Lifecycle Hooks

  1. beforeInitialize: Before pool creation
  2. afterInitialize: After pool creation

Swap Hooks

  1. beforeSwap: Before swap execution (modify fee, check conditions)
  2. afterSwap: After swap execution (update oracles, execute limit orders)

Liquidity Hooks

  1. beforeAddLiquidity: Before LP adds liquidity
  2. afterAddLiquidity: After LP adds liquidity
  3. beforeRemoveLiquidity: Before LP removes liquidity
  4. afterRemoveLiquidity: After LP removes liquidity

Donation Hooks

  1. beforeDonate: Before donation (directly adding to fee accumulator)
  2. afterDonate: After donation

Key insight: Hooks are optional. A pool can use 0, 1, or all 10 hooks.

Real Hook Examples from LA Tech Week

At the workshop, Uniswap Labs showcased several production-ready hooks:

1. Dynamic Fee Hook

Problem: Static fees (0.3%) are suboptimal during volatility.

  • High volatility → LPs need higher fees to compensate for IL
  • Low volatility → Lower fees attract more volume

Solution: Dynamic fee hook adjusts fees based on realized volatility.

contract DynamicFeeHook is BaseHook {
    function beforeSwap(
        address sender,
        PoolKey calldata key,
        IPoolManager.SwapParams calldata params,
        bytes calldata hookData
    ) external override returns (bytes4) {
        // Calculate volatility from TWAP oracle
        uint24 volatility = calculateVolatility(key.currency0, key.currency1);

        // Adjust fee: higher volatility = higher fee
        uint24 dynamicFee;
        if (volatility > HIGH_VOLATILITY_THRESHOLD) {
            dynamicFee = 10000; // 1%
        } else if (volatility > MEDIUM_VOLATILITY_THRESHOLD) {
            dynamicFee = 3000; // 0.3%
        } else {
            dynamicFee = 500; // 0.05%
        }

        // Update pool fee (Uniswap v4 allows dynamic fees)
        poolManager.updateDynamicSwapFee(key, dynamicFee);

        return BaseHook.beforeSwap.selector;
    }
}

Impact: 30% higher LP returns in backtesting, 15% more volume vs static fees.

2. TWAP Oracle Hook

Problem: AMMs need reliable price oracles, but TWAP requires storage.

Solution: Hook updates TWAP every swap.

contract TWAPOracle is BaseHook {
    struct TWAPObservation {
        uint32 blockTimestamp;
        uint256 tickCumulative;
    }

    mapping(PoolId => TWAPObservation[]) public observations;

    function afterSwap(
        address sender,
        PoolKey calldata key,
        IPoolManager.SwapParams calldata params,
        BalanceDelta delta,
        bytes calldata hookData
    ) external override returns (bytes4) {
        // Record price observation
        PoolId poolId = key.toId();
        (uint160 sqrtPriceX96, int24 tick, , ) = poolManager.getSlot0(poolId);

        observations[poolId].push(TWAPObservation({
            blockTimestamp: uint32(block.timestamp),
            tickCumulative: uint256(tick) * uint256(block.timestamp)
        }));

        // Keep only last 24 hours of observations (gas optimization)
        if (observations[poolId].length > 7200) { // ~12 sec per block
            delete observations[poolId][0];
        }

        return BaseHook.afterSwap.selector;
    }

    function getTWAP(PoolId poolId, uint32 secondsAgo) external view returns (uint256) {
        // Calculate TWAP from observations
        // ... implementation
    }
}

Impact: Free TWAP oracle for any Uniswap v4 pool. No external oracle fees.

3. Limit Order Hook

Problem: AMMs only support market orders. Limit orders require separate protocols (1inch, CoW).

Solution: Hook checks price after each swap and executes pending limit orders.

contract LimitOrderHook is BaseHook {
    struct LimitOrder {
        address trader;
        bool zeroForOne;
        uint256 amount;
        uint160 sqrtPriceLimitX96;
    }

    mapping(PoolId => LimitOrder[]) public limitOrders;

    function placeLimitOrder(
        PoolKey calldata key,
        bool zeroForOne,
        uint256 amount,
        uint160 sqrtPriceLimitX96
    ) external {
        PoolId poolId = key.toId();
        limitOrders[poolId].push(LimitOrder({
            trader: msg.sender,
            zeroForOne: zeroForOne,
            amount: amount,
            sqrtPriceLimitX96: sqrtPriceLimitX96
        }));
    }

    function afterSwap(
        address sender,
        PoolKey calldata key,
        IPoolManager.SwapParams calldata params,
        BalanceDelta delta,
        bytes calldata hookData
    ) external override returns (bytes4) {
        PoolId poolId = key.toId();
        (uint160 sqrtPriceX96, , , ) = poolManager.getSlot0(poolId);

        // Check if any limit orders can execute at current price
        LimitOrder[] storage orders = limitOrders[poolId];
        for (uint i = 0; i < orders.length; i++) {
            LimitOrder storage order = orders[i];

            bool shouldExecute = order.zeroForOne
                ? sqrtPriceX96 <= order.sqrtPriceLimitX96
                : sqrtPriceX96 >= order.sqrtPriceLimitX96;

            if (shouldExecute) {
                // Execute limit order via pool manager
                poolManager.swap(key, IPoolManager.SwapParams({
                    zeroForOne: order.zeroForOne,
                    amountSpecified: int256(order.amount),
                    sqrtPriceLimitX96: order.sqrtPriceLimitX96
                }), "");

                // Remove executed order
                delete orders[i];
            }
        }

        return BaseHook.afterSwap.selector;
    }
}

Impact: Native limit orders on Uniswap. No separate protocol needed.

4. MEV Protection Hook

Problem: Sandwich attacks extract value from traders.

Solution: Hook enforces maximum price impact per swap.

contract MEVProtectionHook is BaseHook {
    uint256 constant MAX_PRICE_IMPACT_BPS = 50; // 0.5%

    function beforeSwap(
        address sender,
        PoolKey calldata key,
        IPoolManager.SwapParams calldata params,
        bytes calldata hookData
    ) external override returns (bytes4) {
        // Get current price
        PoolId poolId = key.toId();
        (uint160 sqrtPriceX96Before, , , ) = poolManager.getSlot0(poolId);

        // Calculate maximum allowed price after swap
        uint160 maxSqrtPriceX96After = sqrtPriceX96Before
            + (sqrtPriceX96Before * MAX_PRICE_IMPACT_BPS / 10000);

        // Revert if price impact too high
        require(
            params.sqrtPriceLimitX96 <= maxSqrtPriceX96After,
            "Price impact too high - possible sandwich attack"
        );

        return BaseHook.beforeSwap.selector;
    }
}

Impact: Prevents sandwich attacks. Protects retail traders.

The Singleton Pattern: Gas Optimization

Uniswap v3: Each pool is a separate contract.

  • Deploying ETH/USDC pool: ~1.2M gas (~$30 at 50 gwei)
  • High deployment cost, inefficient cross-pool interactions

Uniswap v4: All pools in ONE singleton contract.

  • Deploying new pool: ~5000 gas (~$0.12)
  • 240x cheaper deployment
  • Flash accounting across pools (atomic multi-pool swaps)

Flash Accounting

Before (Uniswap v3): Transfer tokens at each step.

User → Pool A (transfer in) → Pool B (transfer out) → User (transfer)
3 ERC20 transfers = expensive

After (Uniswap v4): Net settle at end.

User → Singleton → Calculate net delta → Single transfer
1 ERC20 transfer = cheap

Result: Multi-hop swaps 40% cheaper.

Hook Security: The Critical Concern

At LA Tech Week, the security panel emphasized: Hooks introduce smart contract risk.

Hook Vulnerabilities

1. Reentrancy: Hook calls external contract, which re-enters pool.

2. Oracle manipulation: Hook relies on manipulable price feed.

3. Access control: Hook allows unauthorized users to modify state.

4. Gas griefing: Hook consumes excessive gas, DoS attack.

5. Front-running: Hook introduces new MEV vectors.

Best Practices (from Uniswap security team)

// ✅ GOOD: Use ReentrancyGuard
contract SafeHook is BaseHook, ReentrancyGuard {
    function beforeSwap(...) external override nonReentrant returns (bytes4) {
        // Hook logic
    }
}

// ✅ GOOD: Use Chainlink oracles (not pool price)
contract SafeOracleHook is BaseHook {
    AggregatorV3Interface priceFeed;

    function beforeSwap(...) external override returns (bytes4) {
        (, int price, , , ) = priceFeed.latestRoundData();
        // Use Chainlink price, not pool price
    }
}

// ✅ GOOD: Gas limit on hook execution
contract GasLimitedHook is BaseHook {
    function beforeSwap(...) external override returns (bytes4) {
        uint256 gasStart = gasleft();
        // Hook logic
        require(gasStart - gasleft() < 100000, "Hook gas limit exceeded");
    }
}

// ❌ BAD: Relying on pool price (manipulable)
contract UnsafeHook is BaseHook {
    function beforeSwap(...) external override returns (bytes4) {
        (uint160 sqrtPriceX96, , , ) = poolManager.getSlot0(poolId);
        // Using pool price for logic = DANGEROUS
    }
}

Hook Deployment: Deterministic Addresses

Hooks use CREATE2 for deterministic addresses. The hook address determines permissions.

Hook address encodes permissions:

Address: 0x1234567890abcdef...
         ↑↑↑↑↑↑↑↑↑↑
         Hook flags (beforeSwap, afterSwap, etc.)

Why? Gas optimization. No need to store hook permissions on-chain.

My Questions for the Community

  1. What hooks would YOU build? Dynamic fees? Limit orders? Something else?

  2. Security tradeoff: Are you comfortable with hook-enabled pools (custom logic) vs vanilla pools (battle-tested)?

  3. Gas costs: Would you pay 20% higher gas for hooks (MEV protection, dynamic fees)?

  4. Hook discovery: How should users find and trust hook-enabled pools?

Uniswap v4 hooks are a paradigm shift. The possibilities are endless.

Diana Martinez
DeFi Protocol Engineer @ Uniswap Labs


Resources:

Diana, incredible walkthrough of hooks! As someone who audits DeFi protocols, I need to emphasize the security considerations. Hooks introduce significant smart contract risk that developers must understand.

The Hook Attack Surface

Every hook is a potential vulnerability. Here’s what can go wrong:

Attack 1: Reentrancy via Hook

Scenario: Attacker creates malicious hook that re-enters the pool mid-swap.

// VULNERABLE HOOK
contract MaliciousHook is BaseHook {
    IPoolManager public manager;
    bool attacked = false;

    function beforeSwap(...) external override returns (bytes4) {
        if (!attacked) {
            attacked = true;
            // REENTRANCY: Call back into pool manager
            manager.swap(key, params, "");
        }
        return BaseHook.beforeSwap.selector;
    }
}

Impact: Double-spending, price manipulation, draining pool funds.

Defense: Uniswap v4 PoolManager has reentrancy guards, but hooks MUST also use them.

// SAFE HOOK
contract SafeHook is BaseHook, ReentrancyGuard {
    function beforeSwap(...) external override nonReentrant returns (bytes4) {
        // Safe: Cannot re-enter
    }
}

Attack 2: Oracle Manipulation

Scenario: Hook uses pool price for logic, attacker manipulates price.

Example: Dynamic fee hook using pool TWAP

// VULNERABLE
contract BadDynamicFeeHook is BaseHook {
    function beforeSwap(...) external override returns (bytes4) {
        // Get price from THIS pool's TWAP
        uint256 price = getPoolTWAP(poolId);
        uint24 fee = calculateFee(price); // BAD: Using manipulable price

        poolManager.updateDynamicSwapFee(key, fee);
        return BaseHook.beforeSwap.selector;
    }
}

Attack:

  1. Attacker flash loans large amount
  2. Swaps to manipulate pool price
  3. Hook calculates wrong fee based on manipulated price
  4. Attacker profits from mispriced swap

Defense: Use external oracle (Chainlink, UMA) or longer TWAP.

// SAFE
contract SafeDynamicFeeHook is BaseHook {
    AggregatorV3Interface public chainlinkFeed;

    function beforeSwap(...) external override returns (bytes4) {
        // Get price from Chainlink (manipulation-resistant)
        (, int256 price, , , ) = chainlinkFeed.latestRoundData();
        uint24 fee = calculateFee(uint256(price));

        poolManager.updateDynamicSwapFee(key, fee);
        return BaseHook.beforeSwap.selector;
    }
}

Attack 3: Front-Running Hook Logic

Scenario: Hook creates predictable state changes, bots front-run.

Example: Limit order hook

contract LimitOrderHook is BaseHook {
    mapping(PoolId => LimitOrder[]) public limitOrders; // PUBLIC

    function afterSwap(...) external override returns (bytes4) {
        // Execute limit orders at current price
        for (uint i = 0; i < limitOrders[poolId].length; i++) {
            if (priceReached(limitOrders[poolId][i])) {
                executeLimitOrder(limitOrders[poolId][i]);
            }
        }
    }
}

Attack:

  1. Bot monitors mempool for swap that will trigger limit order
  2. Bot front-runs with own swap at better price
  3. Limit order executes at worse price
  4. Bot back-runs for profit

Defense: Encrypted limit orders or commit-reveal scheme.

// SAFER: Use commit-reveal
contract SaferLimitOrderHook is BaseHook {
    mapping(bytes32 => LimitOrder) private limitOrders; // Hash-based

    function commitLimitOrder(bytes32 orderHash) external {
        // Store hash, not order details
        orderHashes[msg.sender].push(orderHash);
    }

    function revealAndExecute(LimitOrder calldata order, bytes32 salt) external {
        bytes32 orderHash = keccak256(abi.encode(order, salt));
        require(orderHashes[msg.sender].contains(orderHash), "Invalid order");

        // Execute if price reached
        if (priceReached(order)) {
            executeLimitOrder(order);
        }
    }
}

Attack 4: Gas Griefing

Scenario: Malicious hook consumes excessive gas, causing DoS.

// MALICIOUS HOOK
contract GasGriefHook is BaseHook {
    function beforeSwap(...) external override returns (bytes4) {
        // Infinite loop: consumes all gas
        for (uint i = 0; i < type(uint256).max; i++) {
            // Expensive operation
            keccak256(abi.encode(i));
        }
        return BaseHook.beforeSwap.selector;
    }
}

Impact: Pool becomes unusable (all swaps revert due to gas).

Defense: Gas limit enforcement by PoolManager.

// PoolManager should limit hook gas
function _beforeSwap(PoolKey calldata key, ...) internal {
    if (key.hooks.hasBeforeSwap()) {
        uint256 gasBefore = gasleft();
        key.hooks.beforeSwap(...);
        uint256 gasUsed = gasBefore - gasleft();

        require(gasUsed < MAX_HOOK_GAS, "Hook exceeded gas limit");
    }
}

Attack 5: Access Control Bypass

Scenario: Hook allows unauthorized users to modify critical state.

// VULNERABLE
contract AccessControlHook is BaseHook {
    uint24 public currentFee = 3000; // 0.3%

    function setFee(uint24 newFee) external {
        currentFee = newFee; // NO ACCESS CONTROL
    }

    function beforeSwap(...) external override returns (bytes4) {
        poolManager.updateDynamicSwapFee(key, currentFee);
        return BaseHook.beforeSwap.selector;
    }
}

Attack: Anyone can set fee to 100% (10000 bps), stealing from swappers.

Defense: Use OpenZeppelin AccessControl.

// SAFE
contract SafeAccessControlHook is BaseHook, AccessControl {
    bytes32 public constant FEE_MANAGER_ROLE = keccak256("FEE_MANAGER");
    uint24 public currentFee = 3000;

    constructor() {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
    }

    function setFee(uint24 newFee) external onlyRole(FEE_MANAGER_ROLE) {
        require(newFee <= 10000, "Fee too high");
        currentFee = newFee;
    }
}

Hook Audit Checklist

Before deploying a hook-enabled pool, audit these:

1. Reentrancy

  • Hook uses ReentrancyGuard or nonReentrant modifier
  • No external calls to untrusted contracts
  • Checks-Effects-Interactions pattern followed

2. Oracle Security

  • No reliance on pool price for critical logic
  • External oracles (Chainlink) used where needed
  • TWAP window >= 30 minutes (if using TWAP)
  • Oracle failure modes handled gracefully

3. Access Control

  • All state-modifying functions have access control
  • Role-based permissions (OpenZeppelin AccessControl)
  • Emergency pause mechanism
  • Timelock for critical parameter changes

4. Gas Optimization

  • Hook gas usage < 100,000 gas (guideline)
  • No unbounded loops
  • Storage-efficient data structures

5. Front-Running Resistance

  • No predictable MEV opportunities in hook logic
  • Commit-reveal for sensitive operations
  • Slippage protection for automated actions

6. Upgrade Path

  • Hook is upgradeable (proxy pattern) OR immutable
  • If upgradeable: Timelock + multisig for upgrades
  • Migration plan if hook needs replacement

7. Edge Cases

  • Zero amount swaps handled
  • Maximum uint256 amounts handled
  • Pool initialization edge cases
  • Token decimals differences (USDC 6, WETH 18)

Audit Tools for Hooks

Static analysis:

# Slither (reentrancy, access control)
slither contracts/MyHook.sol --detect reentrancy,access-control

# Mythril (overflow, logic bugs)
myth analyze contracts/MyHook.sol

# Echidna (fuzzing)
echidna-test contracts/MyHook.sol --config echidna.yaml

Foundry fuzzing:

contract HookFuzzTest is Test {
    function testFuzz_SwapDoesNotRevert(
        uint256 amountIn,
        bool zeroForOne
    ) public {
        // Fuzz test: Random swap amounts and directions
        vm.assume(amountIn > 0 && amountIn < 1e30);

        // Should never revert
        try hook.beforeSwap(...) returns (bytes4 selector) {
            assertEq(selector, BaseHook.beforeSwap.selector);
        } catch {
            fail("Hook reverted unexpectedly");
        }
    }
}

Hook Risk Rating System

I propose a risk rating for hook-enabled pools:

Low Risk (Green):

  • Audited by top firm (Trail of Bits, OpenZeppelin)
  • No external calls in hook
  • No price oracle dependencies
  • Open source + verified on Etherscan
  • Example: Simple fee switch hook

Medium Risk (Yellow):

  • Audited by reputable firm
  • Uses Chainlink oracle (not pool price)
  • Upgradeable with timelock
  • Example: Dynamic fee hook with Chainlink

High Risk (Red):

  • Unaudited or self-audited
  • Uses pool price for logic
  • Complex external dependencies
  • Closed source or unverified
  • Example: Experimental hooks, new protocols

Critical Risk (Black):

  • Any of: Upgradeable without timelock, uses pool TWAP, unbounded loops, no access control
  • DO NOT USE

My Recommendations

  1. For users: Only use hook pools rated Green or Yellow. Check audit reports.

  2. For developers: Hire professional auditors. Trail of Bits, OpenZeppelin, Consensys Diligence.

  3. For protocols: Implement hook registry with risk ratings and audit links.

  4. For Uniswap: Add hook gas limits to PoolManager (prevent gas griefing).

Hooks are powerful, but power requires responsibility. Treat every hook as untrusted code.

Brian Zhang
Protocol Architect @ LayerZero


Resources:

Diana, Brian - excellent coverage of hooks and security! Let me add a practical implementation of dynamic fees based on volatility. I built this at LA Tech Week and it’s already running on testnet.

The Dynamic Fee Problem

Static fees don’t make economic sense:

During low volatility (bear market, stable periods):

  • LPs face minimal impermanent loss
  • Should charge LOWER fees to attract volume
  • Example: 0.05% fee tier

During high volatility (bull market, major events):

  • LPs face high impermanent loss risk
  • Need HIGHER fees to compensate
  • Example: 1% fee tier

Uniswap v3: LPs must choose ONE fee tier. Can’t adapt to market conditions.

Uniswap v4: Dynamic fee hook adjusts automatically.

Production Implementation

I deployed a working dynamic fee hook on Sepolia testnet with real data from LA Tech Week backtesting.

Results (7 days testnet):

  • Average fee: 0.18% (vs 0.3% static)
  • Volume: +22% vs comparable static pool
  • LP returns: +31% (estimated)

Contract: Address: 0x742d35Cc...595f0bEbE | Etherscan

The key insight: Volatility-based fees outperform static fees in ALL market conditions.

Questions

  1. Which volatility strategy would you prefer? On-chain, Chainlink, or hybrid?
  2. Update frequency: 15-minute cache (low gas) vs every-swap update?
  3. Fee range: Should hooks allow 0% fees during extreme low vol?

Dynamic fees are the future of AMMs. Static fees leave money on the table.

Chris Anderson
Full-Stack Crypto Developer

Diana, Brian, Chris - incredible depth! As a data engineer, let me add the monitoring perspective. If you deploy hooks to mainnet, you NEED comprehensive monitoring.

Essential Hook Metrics

1. Execution Success Rate

Track hook execution failures - if success rate < 99.5%, you have a critical issue.

2. Gas Usage

Monitor P95 gas usage. Alert if > 200k gas per swap.

3. Oracle Health

For Chainlink-dependent hooks, monitor staleness and validity.

4. Fee Accuracy

For dynamic fee hooks, validate calculations match expected values.

My Production Setup

I built a monitoring dashboard for LA Tech Week demonstrations:

Stack:

  • The Graph (indexing)
  • Grafana (visualization)
  • Prometheus (metrics)
  • AlertManager (PagerDuty)

Metrics tracked:

  • Success rate (99.97% uptime over 90 days)
  • P95 gas (avg 85k, never exceeded 150k)
  • Current dynamic fee
  • Oracle status
  • Volume processed

Open source repo: https://github.com/data-mike/hook-monitor

Questions

  1. What metrics should be on a hook dashboard?
  2. Alert frequency: Real-time vs hourly summaries?
  3. Should we standardize hook monitoring frameworks?

Monitoring is not optional for production hooks.

Mike Johnson
Data Engineer & DeFi Analytics