Tick

Manages the discrete price points and liquidity distribution across the price curve.

Core Data Structures

TickManager

The main controller that organizes all ticks for a pool:

struct TickManager {
    tick_spacing: u32,           // Spacing between valid ticks
    ticks: SkipList<Tick>       // Ordered collection of ticks
}

Tick

Individual tick containing all relevant price point data:

struct Tick {
    index: I32,                          // Tick index (price level)
    sqrt_price: u128,                    // Square root price at this tick
    liquidity_net: I128,                 // Net liquidity change when crossing
    liquidity_gross: u128,               // Total liquidity at this tick
    fee_growth_outside_a: u128,          // Fee growth outside for token A
    fee_growth_outside_b: u128,          // Fee growth outside for token B
    points_growth_outside: u128,         // Points growth outside
    rewards_growth_outside: vector<u128> // Reward growth outside (per token)
}

Key Properties

Liquidity Net vs Gross

  • liquidity_net: Signed change in active liquidity when crossing this tick

    • Positive when tick is the lower bound of positions

    • Negative when tick is the upper bound of positions

  • liquidity_gross: Total amount of liquidity referencing this tick

    • Always positive

    • Used to determine if tick can be removed

Growth Outside Pattern

The "growth outside" pattern enables efficient fee and reward calculations:

  • Tracks accumulated growth outside the current active range

  • Allows O(1) calculation of growth within any range

  • Automatically updated when ticks are crossed during swaps

Core Functionality

Initialization

public(friend) fun new(tick_spacing: u32, seed: u64, ctx: &mut TxContext) : TickManager

Creates a new tick manager with:

  • Specified tick spacing

  • Empty SkipList with randomized structure

  • 16 levels maximum for optimal performance

Liquidity Management

Adding Liquidity

public(friend) fun increase_liquidity(
    tick_manager: &mut TickManager,
    current_tick: I32,
    tick_lower: I32,
    tick_upper: I32,
    amount: u128,
    fee_growth_global_a: u128,
    fee_growth_global_b: u128,
    points_growth_global: u128,
    rewards_growth_global: vector<u128>
)

Process:

  1. Tick Creation: Creates ticks if they don't exist

  2. Lower Tick Update: Increases liquidity_net (positive delta)

  3. Upper Tick Update: Decreases liquidity_net (negative delta)

  4. Growth Initialization: Sets appropriate "outside" growth values

Removing Liquidity

public(friend) fun decrease_liquidity(
    tick_manager: &mut TickManager,
    current_tick: I32,
    tick_lower: I32,
    tick_upper: I32,
    amount: u128,
    fee_growth_global_a: u128,
    fee_growth_global_b: u128,
    points_growth_global: u128,
    rewards_growth_global: vector<u128>
)

Process:

  1. Validation: Ensures ticks exist and have sufficient liquidity

  2. Liquidity Reduction: Decreases gross liquidity at both ticks

  3. Net Adjustment: Adjusts net liquidity appropriately

  4. Cleanup: Removes ticks if no liquidity remains

Swap Operations

Tick Crossing

public(friend) fun cross_by_swap(
    tick_manager: &mut TickManager,
    tick_idx: I32,
    a2b: bool,
    liquidity: u128,
    fee_growth_global_a: u128,
    fee_growth_global_b: u128,
    points_growth_global: u128,
    rewards_growth_global: vector<u128>
) : u128

When a swap crosses a tick:

  1. Liquidity Update: Applies liquidity_net to active liquidity

  2. Direction Handling: Negates delta for reverse direction (a2b)

  3. Growth Flipping: Updates "outside" growth values using wrapping subtraction

  4. Safety Checks: Validates liquidity doesn't overflow or underflow

Swap Navigation

public fun first_score_for_swap(
    tick_manager: &TickManager,
    tick_idx: I32,
    a2b: bool
) : OptionU64

Finds the next tick to process during swaps:

  • a2b (true): Finds previous tick (lower price)

  • b2a (false): Finds next tick (higher price)

  • Handles edge cases at minimum and maximum ticks

public fun borrow_tick_for_swap(
    tick_manager: &TickManager,
    node_id: u64,
    a2b: bool
) : (&Tick, OptionU64)

Returns current tick data and next tick ID for efficient swap iteration.

Range Calculations

Fee Growth in Range

public fun get_fee_in_range(
    current_tick: I32,
    fee_growth_global_a: u128,
    fee_growth_global_b: u128,
    tick_lower_opt: Option<Tick>,
    tick_upper_opt: Option<Tick>
) : (u128, u128)

Calculates accumulated fees within a position's range using the formula:

fee_inside = fee_global - fee_below - fee_above

Where:

  • fee_below: Fee growth below the lower tick

  • fee_above: Fee growth above the upper tick

Points Growth in Range

public fun get_points_in_range(
    current_tick: I32,
    points_growth_global: u128,
    tick_lower_opt: Option<Tick>,
    tick_upper_opt: Option<Tick>
) : u128

Similar calculation for points (used in reward calculations).

Rewards Growth in Range

public fun get_rewards_in_range(
    current_tick: I32,
    rewards_growth_global: vector<u128>,
    tick_lower_opt: Option<Tick>,
    tick_upper_opt: Option<Tick>
) : vector<u128>

Calculates reward growth for multiple reward tokens within a range.

Advanced Features

Tick Discovery and Querying

Pagination Support

public fun fetch_ticks(
    tick_manager: &TickManager,
    start: vector<u32>,
    limit: u64
) : vector<Tick>

Efficiently retrieves ticks with pagination:

  • start: Optional starting tick index

  • limit: Maximum number of ticks to return

  • Returns ordered list for UI display

Tick Lookup

public fun borrow_tick(tick_manager: &TickManager, tick_idx: I32) : &Tick
public fun try_borrow_tick(tick_manager: &TickManager, tick_idx: I32) : Option<Tick>
  • borrow_tick: Direct access (aborts if not found)

  • try_borrow_tick: Safe access returning Option

Data Access Functions

Tick Properties

public fun index(tick: &Tick) : I32
public fun sqrt_price(tick: &Tick) : u128
public fun liquidity_gross(tick: &Tick) : u128
public fun liquidity_net(tick: &Tick) : I128
public fun fee_growth_outside(tick: &Tick) : (u128, u128)
public fun points_growth_outside(tick: &Tick) : u128
public fun rewards_growth_outside(tick: &Tick) : &vector<u128>

Manager Properties

public fun tick_spacing(tick_manager: &TickManager) : u32
public fun tick_count(tick_manager: &TickManager) : u64

Reward Helpers

public fun get_reward_growth_outside(tick: &Tick, index: u64) : u128

Safely accesses reward growth for a specific reward token index.

Internal Implementation

Tick Scoring System

fun tick_score(tick_idx: I32) : u64

Converts tick indices to SkipList scores:

  • Handles negative tick indices

  • Ensures proper ordering in SkipList

  • Maps to range [0, tick_bound * 2]

Default Tick Creation

fun default(tick_idx: I32) : Tick

Creates new tick with:

  • Calculated sqrt_price from tick_math

  • Zero liquidity values

  • Zero growth tracking values

  • Empty rewards vector

Liquidity Update Logic

fun update_by_liquidity(
    tick: &mut Tick,
    current_tick: I32,
    amount: u128,
    is_new: bool,
    is_increase: bool,
    is_upper: bool,
    fee_growth_global_a: u128,
    fee_growth_global_b: u128,
    points_growth_global: u128,
    rewards_growth_global: vector<u128>
) : u128

Core logic for updating tick state:

  1. Gross Liquidity: Add/subtract amount based on is_increase

  2. Net Liquidity: Adjust based on is_upper flag

  3. Growth Initialization: Set "outside" values for new ticks based on current position

  4. Overflow Protection: Check for arithmetic overflows

  5. Return Remaining: Return remaining gross liquidity

Growth Initialization Logic

For new ticks:

  • Below current tick: Initialize outside growth to 0 (all growth is "above")

  • Above current tick: Initialize outside growth to global values (all growth is "below")

This ensures proper fee/reward calculations when the tick becomes active.

Error Codes

  • Error 0: Arithmetic overflow in liquidity calculations

  • Error 1: Insufficient liquidity for operation

  • Error 2: Invalid tick score (outside valid range)

  • Error 3: Tick not found when expected to exist

Mathematical Concepts

Price Calculation

Each tick represents a specific price:

price = 1.0001^tick_index
sqrt_price = sqrt(price) = 1.0001^(tick_index/2)

Liquidity Net Calculation

When crossing tick from left to right:

new_liquidity = current_liquidity + tick.liquidity_net

For reverse direction (right to left):

new_liquidity = current_liquidity - tick.liquidity_net

Growth Outside Pattern

Fee growth inside a range [tickLower, tickUpper]:

fee_inside = fee_global - fee_below - fee_above

where:
fee_below = (current_tick >= tickLower) ? tickLower.fee_outside : (fee_global - tickLower.fee_outside)
fee_above = (current_tick < tickUpper) ? tickUpper.fee_outside : (fee_global - tickUpper.fee_outside)

Performance Characteristics

Time Complexity

  • Tick lookup: O(log n)

  • Tick insertion/deletion: O(log n)

  • Range queries: O(log n + k) where k is result size

  • Crossing tick: O(1)

  • Fee/reward calculation: O(1)

Space Complexity

  • Memory per tick: ~200 bytes + rewards vector

  • SkipList overhead: ~16 pointers per tick

  • Total for n ticks: O(n)

Gas Efficiency

  • Efficient sparse storage (only initialized ticks consume gas)

  • O(1) tick crossing operations

  • Batch operations for multiple tick updates

  • Optimized wrapping arithmetic for growth calculations

Best Practices

  1. Tick Spacing: Choose spacing based on gas costs vs. precision needs

  2. Batch Operations: Group multiple liquidity operations when possible

  3. Error Handling: Always check for tick existence before operations

  4. Memory Management: Clean up empty ticks to reduce storage costs

  5. Precision: Use appropriate decimal precision for price calculations

Security Considerations

  1. Overflow Protection: All arithmetic operations include overflow checks

  2. Tick Bounds: Validate tick indices are within valid range

  3. Liquidity Validation: Ensure sufficient liquidity before decreasing

  4. Growth Calculation: Use wrapping arithmetic to handle edge cases

  5. Access Control: Restrict dangerous operations to friend modules only

Last updated