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 tickPositive 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 tickAlways 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:
Tick Creation: Creates ticks if they don't exist
Lower Tick Update: Increases
liquidity_net
(positive delta)Upper Tick Update: Decreases
liquidity_net
(negative delta)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:
Validation: Ensures ticks exist and have sufficient liquidity
Liquidity Reduction: Decreases gross liquidity at both ticks
Net Adjustment: Adjusts net liquidity appropriately
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:
Liquidity Update: Applies
liquidity_net
to active liquidityDirection Handling: Negates delta for reverse direction (a2b)
Growth Flipping: Updates "outside" growth values using wrapping subtraction
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 tickfee_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:
Gross Liquidity: Add/subtract amount based on
is_increase
Net Liquidity: Adjust based on
is_upper
flagGrowth Initialization: Set "outside" values for new ticks based on current position
Overflow Protection: Check for arithmetic overflows
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
Tick Spacing: Choose spacing based on gas costs vs. precision needs
Batch Operations: Group multiple liquidity operations when possible
Error Handling: Always check for tick existence before operations
Memory Management: Clean up empty ticks to reduce storage costs
Precision: Use appropriate decimal precision for price calculations
Security Considerations
Overflow Protection: All arithmetic operations include overflow checks
Tick Bounds: Validate tick indices are within valid range
Liquidity Validation: Ensure sufficient liquidity before decreasing
Growth Calculation: Use wrapping arithmetic to handle edge cases
Access Control: Restrict dangerous operations to friend modules only
Last updated