Raydium CLMM

Overview

open_position

The open_position instruction facilitates the creation of a new liquidity position within a specified tick range in the AMM (Automated Market Maker) pool. This allows users to provide liquidity, earning fees and potentially rewards based on their contribution and the pool's activity.

Function Variants

  • instructions::open_position_v1: Acts as an entry point for the instruction, handling account validation and passing parameters to the core instructions::open_position function. It will mint NFT using token program, while instructions::open_position_v2 use token program 2022.
  • instructions::open_position: Contains the primary logic for opening a position, including liquidity calculations, state updates, and token transfers.

Instruction Context (OpenPosition Struct)

The OpenPosition struct defines the accounts required for the open_position instruction. Here's a breakdown of each field:
Account Name
Type
Description
payer
Signer<'info>
The user initiating the transaction, responsible for paying transaction fees and account creation costs.
position_nft_owner
UncheckedAccount<'info>
The owner of the position NFT that will be minted.
position_nft_mint
Box<Account<'info, Mint>>
The mint account for the position NFT, initialized with decimals set to 0.
position_nft_account
Box<Account<'info, TokenAccount>>
The token account where the position NFT will be minted, associated with the position_nft_owner.
metadata_account
UncheckedAccount<'info>
Account to store Metaplex metadata for the NFT.
pool_state
AccountLoader<'info, PoolState>
The AMM pool state account, loaded as a mutable reference.
protocol_position
Box<Account<'info, ProtocolPositionState>>
Account storing protocol-level position information, initialized if needed.
tick_array_lower
UncheckedAccount<'info>
Account representing the lower tick array state, loaded via PDA (Program Derived Address).
tick_array_upper
UncheckedAccount<'info>
Account representing the upper tick array state, loaded via PDA.
personal_position
Box<Account<'info, PersonalPositionState>>
User-specific position state account, initialized if needed.
token_account_0
Box<Account<'info, TokenAccount>>
User's token account for token_0, from which tokens will be deposited.
token_account_1
Box<Account<'info, TokenAccount>>
User's token account for token_1, from which tokens will be deposited.
token_vault_0
Box<Account<'info, TokenAccount>>
Pool's vault account for token_0, where user deposits will be transferred.
token_vault_1
Box<Account<'info, TokenAccount>>
Pool's vault account for token_1, where user deposits will be transferred.
rent
Sysvar<'info, Rent>
Sysvar account providing rent exemption information.
system_program
Program<'info, System>
Solana's System program for account creation and fund transfers.
token_program
Program<'info, Token>
SPL Token program for token operations.
associated_token_program
Program<'info, AssociatedToken>
Program for managing Associated Token Accounts (ATAs).
metadata_program
Program<'info, Metadata>
Metaplex Metadata program for NFT metadata management.

Function Parameters (open_position_v1 and open_position)

The open_position_v1 function receives additional parameters that are forwarded to the core open_position function:
Parameter Name
Type
Description
liquidity
u128
The amount of liquidity to add to the position.
amount_0_max
u64
Maximum amount of token_0 the user is willing to deposit.
amount_1_max
u64
Maximum amount of token_1 the user is willing to deposit.
tick_lower_index
i32
Lower bound tick index for the liquidity position.
tick_upper_index
i32
Upper bound tick index for the liquidity position.
tick_array_lower_start_index
i32
Start index for the lower tick array.
tick_array_upper_start_index
i32
Start index for the upper tick array.
with_metadata
bool
Flag indicating whether to create metadata for the NFT.
base_flag
Option<bool>
Determines whether to handle fees based on token_0 or token_1.
#[program] pub mod amm_v3 { /// ... pub fn open_position<'a, 'b, 'c: 'info, 'info>( ctx: Context<'a, 'b, 'c, 'info, OpenPosition<'info>>, tick_lower_index: i32, tick_upper_index: i32, tick_array_lower_start_index: i32, tick_array_upper_start_index: i32, liquidity: u128, amount_0_max: u64, amount_1_max: u64, ) -> Result<()> { instructions::open_position_v1( ctx, liquidity, amount_0_max, amount_1_max, tick_lower_index, tick_upper_index, tick_array_lower_start_index, tick_array_upper_start_index, true, None, ) } /// ... } /// --- programs/amm/src/instructions/open_position.rs --- pub fn open_position_v1<'a, 'b, 'c: 'info, 'info>( ctx: Context<'a, 'b, 'c, 'info, OpenPosition<'info>>, liquidity: u128, amount_0_max: u64, amount_1_max: u64, tick_lower_index: i32, tick_upper_index: i32, tick_array_lower_start_index: i32, tick_array_upper_start_index: i32, with_metadata: bool, base_flag: Option<bool>, ) -> Result<()> { open_position( &ctx.accounts.payer, &ctx.accounts.position_nft_owner, &ctx.accounts.position_nft_mint.to_account_info(), &ctx.accounts.position_nft_account.to_account_info(), Some(&ctx.accounts.metadata_account), &ctx.accounts.pool_state, &ctx.accounts.tick_array_lower, &ctx.accounts.tick_array_upper, &mut ctx.accounts.protocol_position, &mut ctx.accounts.personal_position, &ctx.accounts.token_account_0.to_account_info(), &ctx.accounts.token_account_1.to_account_info(), &ctx.accounts.token_vault_0.to_account_info(), &ctx.accounts.token_vault_1.to_account_info(), &ctx.accounts.rent, &ctx.accounts.system_program, &ctx.accounts.token_program, &ctx.accounts.associated_token_program, Some(&ctx.accounts.metadata_program), None, None, None, &ctx.remaining_accounts, ctx.bumps.protocol_position, ctx.bumps.personal_position, liquidity, amount_0_max, amount_1_max, tick_lower_index, tick_upper_index, tick_array_lower_start_index, tick_array_upper_start_index, with_metadata, base_flag, false, ) }

Overall Analysis

  1. Entry Point: open_position_v1
    1. The open_position_v1 function serves as the public-facing instruction handler. It performs minimal processing and delegates the core logic to the open_position function which encapsulates the primary logic for opening a liquidity position.
  1. Pool State Loading and Validation:
      • Loads the mutable PoolState and ensures that the pool is active and allows opening positions.
      • Validates the order of the lower and upper tick indices.
      • Ensures that the tick array start indices align with the pool's tick spacing.
  1. Tick Array Management:
      • Lower Tick Array:
        • Uses TickArrayState::get_or_create_tick_array to load or initialize the lower tick array based on the provided start index.
      • Upper Tick Array:
        • If the lower and upper start indices are the same, it reuses the same tick array account.
        • Otherwise, it initializes the upper tick array using the same method as the lower tick array.
  1. Protocol Position Initialization:
      • Checks if the ProtocolPositionState is initialized.
      • If not, it sets up the initial protocol position with the provided tick indices and updates the tick states.
  1. Liquidity Addition:
    1. Calls the add_liquidity function to handle the core liquidity addition logic, which includes:
      • Calculating the required token amounts.
      • Updating tick states.
      • Managing transfer fees.
  1. Personal Position Setup:
      • Initializes the PersonalPositionState with the relevant details, including fee growth tracking.
      • Updates rewards for the personal position based on protocol growth metrics.
  1. Event Emission:
    1. Emits a CreatePersonalPositionEvent to log the creation of the personal position.
  1. NFT Minting and Metadata Handling:
    1. Calls mint_nft_and_remove_mint_authority to mint the position NFT, set up metadata if required, and disable further minting authority to ensure uniqueness.

check_tick_array_start_index

check_tick_array_start_index checks whether tick_array_start_index matches the tick_index according to the tick_spacing setting.
It calls get_array_start_index to get expected start_index and compare them.
Ticks is stored in a TickArrayState, and solana requires program to loads account first, so raydium checks whether it loads correct TickArrayStates which records ticks it touches.
/// --- programs/amm/src/states/tick_array.rs --- pub fn check_tick_array_start_index( tick_array_start_index: i32, tick_index: i32, tick_spacing: u16, ) -> Result<()> { require!( tick_index >= tick_math::MIN_TICK, ErrorCode::TickLowerOverflow ); require!( tick_index <= tick_math::MAX_TICK, ErrorCode::TickUpperOverflow ); require_eq!(0, tick_index % i32::from(tick_spacing)); let expect_start_index = TickArrayState::get_array_start_index(tick_index, tick_spacing); require_eq!(tick_array_start_index, expect_start_index); Ok(()) }

get_or_create_tick_array

Raydium divides ticks into arrays, each array contain TICK_ARRAY_SIZE_USIZE ticks. And each array records the index(start_tick_index) of the first tick in the array(from lower to upper)
 
get_or_create_tick_array checks whether the tick_array_account has been initialized, if not, it initializes it.
tick_array_account's seed depends on the tick_array_start_index, which is checked to be valid first.
/// --- programs/amm/src/states/tick_array.rs --- pub const TICK_ARRAY_SEED: &str = "tick_array"; pub const TICK_ARRAY_SIZE_USIZE: usize = 60; pub const TICK_ARRAY_SIZE: i32 = 60; #[account(zero_copy(unsafe))] #[repr(C, packed)] pub struct TickArrayState { pub pool_id: Pubkey, pub start_tick_index: i32, pub ticks: [TickState; TICK_ARRAY_SIZE_USIZE], pub initialized_tick_count: u8, // account update recent epoch pub recent_epoch: u64, // Unused bytes for future upgrades. pub padding: [u8; 107], } #[zero_copy(unsafe)] #[repr(C, packed)] #[derive(Default, Debug)] pub struct TickState { pub tick: i32, /// Amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left) pub liquidity_net: i128, /// The total position liquidity that references this tick pub liquidity_gross: u128, /// Fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) /// only has relative meaning, not absolute — the value depends on when the tick is initialized pub fee_growth_outside_0_x64: u128, pub fee_growth_outside_1_x64: u128, // Reward growth per unit of liquidity like fee, array of Q64.64 pub reward_growths_outside_x64: [u128; REWARD_NUM], // Unused bytes for future upgrades. pub padding: [u32; 13], } impl TickArrayState { /// ... pub fn get_or_create_tick_array<'info>( payer: AccountInfo<'info>, tick_array_account_info: AccountInfo<'info>, system_program: AccountInfo<'info>, pool_state_loader: &AccountLoader<'info, PoolState>, tick_array_start_index: i32, tick_spacing: u16, ) -> Result<AccountLoad<'info, TickArrayState>> { /// check whether tick_array_start_index is valid /// according to the tick_spacing setting require!( TickArrayState::check_is_valid_start_index(tick_array_start_index, tick_spacing), ErrorCode::InvaildTickIndex ); /// if it doesn't exist, then creates and initializes it let tick_array_state = if tick_array_account_info.owner == &system_program::ID { let (expect_pda_address, bump) = Pubkey::find_program_address( &[ TICK_ARRAY_SEED.as_bytes(), pool_state_loader.key().as_ref(), &tick_array_start_index.to_be_bytes(), ], &crate::id(), ); require_keys_eq!(expect_pda_address, tick_array_account_info.key()); create_or_allocate_account( &crate::id(), payer, system_program, tick_array_account_info.clone(), &[ TICK_ARRAY_SEED.as_bytes(), pool_state_loader.key().as_ref(), &tick_array_start_index.to_be_bytes(), &[bump], ], TickArrayState::LEN, )?; let tick_array_state_loader = AccountLoad::<TickArrayState>::try_from_unchecked( &crate::id(), &tick_array_account_info, )?; { let mut tick_array_account = tick_array_state_loader.load_init()?; tick_array_account.initialize( tick_array_start_index, tick_spacing, pool_state_loader.key(), )?; } tick_array_state_loader } else { /// if it exists, just load it AccountLoad::<TickArrayState>::try_from(&tick_array_account_info)? }; Ok(tick_array_state) } /// ... } }

check_is_valid_start_index

check_is_valid_start_index checks whether a given tick_index is a valid start_tick_index according to the tick_spacing setting.
 
Steps:
  1. Check whether the tick_index is out of min and max tick indexes boundry
      • If it’s bigger than the max tick index, then it’s not valid.
      • If it’s smaller than the min tick index, it may be valid, because the minimal start_tick_index can be smaller than minimal tick_index. For example, if tick indexes are:
        • tick_spaing = 1 TICK_ARRAY_SIZE_USIZE = 3 tick_indexes = [-2,-1,0,1,2]
          then those tick arrays are:
          tick_array_states = [ [-3,-2,-1], [0,1,2] ]
          So the minimal start_tick_index will be -3 which is smaller than minimal tick_index -2.
  1. Otherwise, checks tick_index is multiple of tick_spacing.
/// --- programs/amm/src/states/tick_array.rs --- pub fn check_is_valid_start_index(tick_index: i32, tick_spacing: u16) -> bool { if TickState::check_is_out_of_boundary(tick_index) { if tick_index > tick_math::MAX_TICK { return false; } let min_start_index = TickArrayState::get_array_start_index(tick_math::MIN_TICK, tick_spacing); return tick_index == min_start_index; } tick_index % TickArrayState::tick_count(tick_spacing) == 0 } /// Common checks for a valid tick input. /// A tick is valid if it lies within tick boundaries pub fn check_is_out_of_boundary(tick: i32) -> bool { tick < tick_math::MIN_TICK || tick > tick_math::MAX_TICK } /// --- programs/amm/src/libraries/tick_math.rs --- /// The minimum tick pub const MIN_TICK: i32 = -443636; /// The minimum tick pub const MAX_TICK: i32 = -MIN_TICK;

get_array_start_index

get_array_start_index gets start_tick_index of the tick_array it sits on.
It first calculates the index of the array the tick_array sits on(may be negative). If tick_index is negative and is not multiple of ticks_in_array, the array_index should minus 1 (Because rust performs integer division, the remainder is discarded). Like the example below, -1 tick’s array index is -1
tick_spacing = 1 TICK_ARRAY_SIZE_USIZE = 3 tick_indexes = [-2,-1,0,1,2] tick_array_states = [ [-3,-2,-1], [0,1,2] ]
/// --- programs/amm/src/states/tick_array.rs --- /// Input an arbitrary tick_index, output the start_index of the tick_array it sits on pub fn get_array_start_index(tick_index: i32, tick_spacing: u16) -> i32 { let ticks_in_array = TickArrayState::tick_count(tick_spacing); let mut start = tick_index / ticks_in_array; if tick_index < 0 && tick_index % ticks_in_array != 0 { start = start - 1 } start * ticks_in_array } pub fn tick_count(tick_spacing: u16) -> i32 { TICK_ARRAY_SIZE * i32::from(tick_spacing) }

TickArrayState.get_tick_state_mut

get_tick_state_mut gets the TickState of a tick specified by tick_index from TickArrayState.
get_tick_offset_in_array gets the offset of the tick where it also checks the tick is indeed stored in current tick array.
/// --- programs/amm/src/states/tick_array.rs --- impl TickArrayState { pub fn get_tick_state_mut( &mut self, tick_index: i32, tick_spacing: u16, ) -> Result<&mut TickState> { let offset_in_array = self.get_tick_offset_in_array(tick_index, tick_spacing)?; Ok(&mut self.ticks[offset_in_array]) } /// Get tick's offset in current tick array, tick must be include in tick array, otherwise throw an error fn get_tick_offset_in_array(self, tick_index: i32, tick_spacing: u16) -> Result<usize> { let start_tick_index = TickArrayState::get_array_start_index(tick_index, tick_spacing); require_eq!( start_tick_index, self.start_tick_index, ErrorCode::InvalidTickArray ); let offset_in_array = ((tick_index - self.start_tick_index) / i32::from(tick_spacing)) as usize; Ok(offset_in_array) } }

is_overflow_default_tickarray_bitmap

is_overflow_default_tickarray_bitmap checks whether the passed-in tick_array_lower_start_index and tick_array_upper_start_index crosses the default max start_indexs boundry.
 
tick_array_start_index_range calculates boundry based on default setting:
  • 512 tick arrays on each side(positive and negative ticks).
  • each tick array contains 60 ticks.
  • tick spacing
impl PoolState { /// ... pub fn is_overflow_default_tickarray_bitmap(&self, tick_indexs: Vec<i32>) -> bool { let (min_tick_array_start_index_boundary, max_tick_array_index_boundary) = self.tick_array_start_index_range(); for tick_index in tick_indexs { let tick_array_start_index = TickArrayState::get_array_start_index(tick_index, self.tick_spacing); if tick_array_start_index >= max_tick_array_index_boundary || tick_array_start_index < min_tick_array_start_index_boundary { return true; } } false } // the range of tick array start index that default tickarray bitmap can represent // if tick_spacing = 1, the result range is [-30720, 30720) pub fn tick_array_start_index_range(&self) -> (i32, i32) { // the range of ticks that default tickarrary can represent let mut max_tick_boundary = tick_array_bit_map::max_tick_in_tickarray_bitmap(self.tick_spacing); let mut min_tick_boundary = -max_tick_boundary; if max_tick_boundary > tick_math::MAX_TICK { max_tick_boundary = TickArrayState::get_array_start_index(tick_math::MAX_TICK, self.tick_spacing); // find the next tick array start index max_tick_boundary = max_tick_boundary + TickArrayState::tick_count(self.tick_spacing); } if min_tick_boundary < tick_math::MIN_TICK { min_tick_boundary = TickArrayState::get_array_start_index(tick_math::MIN_TICK, self.tick_spacing); } (min_tick_boundary, max_tick_boundary) } /// ... } /// --- programs/amm/src/libraries/tick_array_bit_map.rs --- pub const TICK_ARRAY_BITMAP_SIZE: i32 = 512; pub fn max_tick_in_tickarray_bitmap(tick_spacing: u16) -> i32 { i32::from(tick_spacing) * TICK_ARRAY_SIZE * TICK_ARRAY_BITMAP_SIZE } /// --- programs/amm/src/states/tick_array.rs --- pub const TICK_ARRAY_SIZE: i32 = 60;

add_liquidity

The add_liquidity function is a core component of the AMM's functionality, responsible for facilitating the addition of liquidity to the pool within specified tick ranges. It handles:
  • Calculating the appropriate liquidity based on user-provided token amounts.
  • Managing and updating the pool's state, including liquidity metrics and fee growth.
  • Interacting with tick arrays to represent the active liquidity ranges.
  • Handling token transfers from the user to the pool's vaults.
  • Emitting relevant events for off-chain tracking and transparency.
 
Parameters Breakdown
Parameter
Type
Description
payer
&Signer<'info>
The entity providing the funds to add liquidity, typically the user initiating the transaction.
token_account_0
&AccountInfo<'info>
User's token account for token_0 from which tokens will be deposited into the pool.
token_account_1
&AccountInfo<'info>
User's token account for token_1 from which tokens will be deposited into the pool.
token_vault_0
&AccountInfo<'info>
Pool's vault account for token_0, where user deposits will be transferred.
token_vault_1
&AccountInfo<'info>
Pool's vault account for token_1, where user deposits will be transferred.
tick_array_lower_loader
&AccountLoad<'info, TickArrayState>
Loader for the lower tick array state, facilitating read/write access.
tick_array_upper_loader
&AccountLoad<'info, TickArrayState>
Loader for the upper tick array state, facilitating read/write access.
protocol_position
&mut ProtocolPositionState
Mutable reference to the protocol-level position state, tracking overall pool metrics and reward distributions.
token_program_2022
Option<&Program<'info, Token2022>>
Optional reference to the Token 2022 program, enabling advanced token functionalities if present.
token_program
&Program<'info, Token>
Reference to the standard SPL Token program for handling token transfers and approvals.
vault_0_mint
Option<Box<InterfaceAccount<'info, token_interface::Mint>>>
Optional reference to the mint account of token_vault_0, providing mint-specific data.
vault_1_mint
Option<Box<InterfaceAccount<'info, token_interface::Mint>>>
Optional reference to the mint account of token_vault_1, providing mint-specific data.
tick_array_bitmap_extension
Option<&'c AccountInfo<'info>>
Optional reference to the Tick Array Bitmap Extension account, managing extended tick array ranges beyond the default bitmap.
pool_state
&mut RefMut<PoolState>
Mutable reference to the pool's state, allowing updates to liquidity, fee growths, and other pool-level metrics.
liquidity
&mut u128
Mutable reference to the liquidity value to be added, allowing for updates based on token amounts and pool state.
amount_0_max
u64
Maximum amount of token_0 the user is willing to deposit, serving as a cap to prevent excessive token transfers.
amount_1_max
u64
Maximum amount of token_1 the user is willing to deposit, serving as a cap to prevent excessive token transfers.
tick_lower_index
i32
The lower tick index defining the liquidity range for the position.
tick_upper_index
i32
The upper tick index defining the liquidity range for the position.
base_flag
Option<bool>
Optional flag determining specific fee handling or operational behaviors, potentially influencing fee calculations or transfer mechanisms.
Return Values
  • Tuple (u64, u64, u64, u64):
    • amount_0: Actual amount of token_0 transferred.
    • amount_1: Actual amount of token_1 transferred.
    • amount_0_transfer_fee: Transfer fee applied to token_0.
    • amount_1_transfer_fee: Transfer fee applied to token_1.
 
Steps:
  1. Initial Liquidity Calculation
    1. Determines the initial liquidity to be added based on the maximum token amounts (amount_0_max or amount_1_max) provided by the user. It calculates liquidity using fixed-point math functions (get_liquidity_from_amount0 or get_liquidity_from_amount1) depending on which token amount is non-zero.
  1. 1
  1. 1
  1. 1
  1. 1
  1. 1
  1. 1
  1. 1
  1. 1
  1. 1
  1. 1
/// --- programs/amm/src/instructions/open_position.rs --- /// Add liquidity to an initialized pool pub fn add_liquidity<'b, 'c: 'info, 'info>( payer: &'b Signer<'info>, token_account_0: &'b AccountInfo<'info>, token_account_1: &'b AccountInfo<'info>, token_vault_0: &'b AccountInfo<'info>, token_vault_1: &'b AccountInfo<'info>, tick_array_lower_loader: &'b AccountLoad<'info, TickArrayState>, tick_array_upper_loader: &'b AccountLoad<'info, TickArrayState>, protocol_position: &mut ProtocolPositionState, token_program_2022: Option<&Program<'info, Token2022>>, token_program: &'b Program<'info, Token>, vault_0_mint: Option<Box<InterfaceAccount<'info, token_interface::Mint>>>, vault_1_mint: Option<Box<InterfaceAccount<'info, token_interface::Mint>>>, tick_array_bitmap_extension: Option<&'c AccountInfo<'info>>, pool_state: &mut RefMut<PoolState>, liquidity: &mut u128, amount_0_max: u64, amount_1_max: u64, tick_lower_index: i32, tick_upper_index: i32, base_flag: Option<bool>, ) -> Result<(u64, u64, u64, u64)> { if *liquidity == 0 { if base_flag.is_none() { // when establishing a new position , liquidity allows for further additions return Ok((0, 0, 0, 0)); } if base_flag.unwrap() { // must deduct transfer fee before calculate liquidity // because only v2 instruction support token_2022, vault_0_mint must be exist let amount_0_transfer_fee = get_transfer_fee(vault_0_mint.clone().unwrap(), amount_0_max).unwrap(); *liquidity = liquidity_math::get_liquidity_from_single_amount_0( pool_state.sqrt_price_x64, tick_math::get_sqrt_price_at_tick(tick_lower_index)?, tick_math::get_sqrt_price_at_tick(tick_upper_index)?, amount_0_max.checked_sub(amount_0_transfer_fee).unwrap(), ); #[cfg(feature = "enable-log")] msg!( "liquidity: {}, amount_0_max:{}, amount_0_transfer_fee:{}", *liquidity, amount_0_max, amount_0_transfer_fee ); } else { // must deduct transfer fee before calculate liquidity // because only v2 instruction support token_2022, vault_1_mint must be exist let amount_1_transfer_fee = get_transfer_fee(vault_1_mint.clone().unwrap(), amount_1_max).unwrap(); *liquidity = liquidity_math::get_liquidity_from_single_amount_1( pool_state.sqrt_price_x64, tick_math::get_sqrt_price_at_tick(tick_lower_index)?, tick_math::get_sqrt_price_at_tick(tick_upper_index)?, amount_1_max.checked_sub(amount_1_transfer_fee).unwrap(), ); #[cfg(feature = "enable-log")] msg!( "liquidity: {}, amount_1_max:{}, amount_1_transfer_fee:{}", *liquidity, amount_1_max, amount_1_transfer_fee ); } } assert!(*liquidity > 0); let liquidity_before = pool_state.liquidity; require_keys_eq!(tick_array_lower_loader.load()?.pool_id, pool_state.key()); require_keys_eq!(tick_array_upper_loader.load()?.pool_id, pool_state.key()); // get tick_state let mut tick_lower_state = *tick_array_lower_loader .load_mut()? .get_tick_state_mut(tick_lower_index, pool_state.tick_spacing)?; let mut tick_upper_state = *tick_array_upper_loader .load_mut()? .get_tick_state_mut(tick_upper_index, pool_state.tick_spacing)?; if tick_lower_state.tick == 0 { tick_lower_state.tick = tick_lower_index; } if tick_upper_state.tick == 0 { tick_upper_state.tick = tick_upper_index; } let clock = Clock::get()?; let (amount_0, amount_1, flip_tick_lower, flip_tick_upper) = modify_position( i128::try_from(*liquidity).unwrap(), pool_state, protocol_position, &mut tick_lower_state, &mut tick_upper_state, clock.unix_timestamp as u64, )?; // update tick_state tick_array_lower_loader.load_mut()?.update_tick_state( tick_lower_index, pool_state.tick_spacing, tick_lower_state, )?; tick_array_upper_loader.load_mut()?.update_tick_state( tick_upper_index, pool_state.tick_spacing, tick_upper_state, )?; if flip_tick_lower { let mut tick_array_lower = tick_array_lower_loader.load_mut()?; let before_init_tick_count = tick_array_lower.initialized_tick_count; tick_array_lower.update_initialized_tick_count(true)?; if before_init_tick_count == 0 { pool_state.flip_tick_array_bit( tick_array_bitmap_extension, tick_array_lower.start_tick_index, )?; } } if flip_tick_upper { let mut tick_array_upper = tick_array_upper_loader.load_mut()?; let before_init_tick_count = tick_array_upper.initialized_tick_count; tick_array_upper.update_initialized_tick_count(true)?; if before_init_tick_count == 0 { pool_state.flip_tick_array_bit( tick_array_bitmap_extension, tick_array_upper.start_tick_index, )?; } } require!( amount_0 > 0 || amount_1 > 0, ErrorCode::ForbidBothZeroForSupplyLiquidity ); let mut amount_0_transfer_fee = 0; let mut amount_1_transfer_fee = 0; if vault_0_mint.is_some() { amount_0_transfer_fee = get_transfer_inverse_fee(vault_0_mint.clone().unwrap(), amount_0).unwrap(); }; if vault_1_mint.is_some() { amount_1_transfer_fee = get_transfer_inverse_fee(vault_1_mint.clone().unwrap(), amount_1).unwrap(); } emit!(LiquidityCalculateEvent { pool_liquidity: liquidity_before, pool_sqrt_price_x64: pool_state.sqrt_price_x64, pool_tick: pool_state.tick_current, calc_amount_0: amount_0, calc_amount_1: amount_1, trade_fee_owed_0: 0, trade_fee_owed_1: 0, transfer_fee_0: amount_0_transfer_fee, transfer_fee_1: amount_1_transfer_fee, }); #[cfg(feature = "enable-log")] msg!( "amount_0: {}, amount_0_transfer_fee: {}, amount_1: {}, amount_1_transfer_fee: {}", amount_0, amount_0_transfer_fee, amount_1, amount_1_transfer_fee ); require_gte!( amount_0_max, amount_0 + amount_0_transfer_fee, ErrorCode::PriceSlippageCheck ); require_gte!( amount_1_max, amount_1 + amount_1_transfer_fee, ErrorCode::PriceSlippageCheck ); let mut token_2022_program_opt: Option<AccountInfo> = None; if token_program_2022.is_some() { token_2022_program_opt = Some(token_program_2022.clone().unwrap().to_account_info()); } transfer_from_user_to_pool_vault( payer, token_account_0, token_vault_0, vault_0_mint, &token_program, token_2022_program_opt.clone(), amount_0 + amount_0_transfer_fee, )?; transfer_from_user_to_pool_vault( payer, token_account_1, token_vault_1, vault_1_mint, &token_program, token_2022_program_opt.clone(), amount_1 + amount_1_transfer_fee, )?; emit!(LiquidityChangeEvent { pool_state: pool_state.key(), tick: pool_state.tick_current, tick_lower: tick_lower_index, tick_upper: tick_upper_index, liquidity_before: liquidity_before, liquidity_after: pool_state.liquidity, }); Ok(( amount_0, amount_1, amount_0_transfer_fee, amount_1_transfer_fee, )) }

modify_position

/// --- programs/amm/src/instructions/open_position.rs --- pub fn modify_position( liquidity_delta: i128, pool_state: &mut RefMut<PoolState>, protocol_position_state: &mut ProtocolPositionState, tick_lower_state: &mut TickState, tick_upper_state: &mut TickState, timestamp: u64, ) -> Result<(u64, u64, bool, bool)> { let (flip_tick_lower, flip_tick_upper) = update_position( liquidity_delta, pool_state, protocol_position_state, tick_lower_state, tick_upper_state, timestamp, )?; let mut amount_0 = 0; let mut amount_1 = 0; if liquidity_delta != 0 { (amount_0, amount_1) = liquidity_math::get_delta_amounts_signed( pool_state.tick_current, pool_state.sqrt_price_x64, tick_lower_state.tick, tick_upper_state.tick, liquidity_delta, )?; if pool_state.tick_current >= tick_lower_state.tick && pool_state.tick_current < tick_upper_state.tick { pool_state.liquidity = liquidity_math::add_delta(pool_state.liquidity, liquidity_delta)?; } } Ok((amount_0, amount_1, flip_tick_lower, flip_tick_upper)) }
 

update_position

/// --- programs/amm/src/instructions/open_position.rs --- /// Updates a position with the given liquidity delta and tick pub fn update_position( liquidity_delta: i128, pool_state: &mut RefMut<PoolState>, protocol_position_state: &mut ProtocolPositionState, tick_lower_state: &mut TickState, tick_upper_state: &mut TickState, timestamp: u64, ) -> Result<(bool, bool)> { let updated_reward_infos = pool_state.update_reward_infos(timestamp)?; let mut flipped_lower = false; let mut flipped_upper = false; // update the ticks if liquidity delta is non-zero if liquidity_delta != 0 { // Update tick state and find if tick is flipped flipped_lower = tick_lower_state.update( pool_state.tick_current, liquidity_delta, pool_state.fee_growth_global_0_x64, pool_state.fee_growth_global_1_x64, false, &updated_reward_infos, )?; flipped_upper = tick_upper_state.update( pool_state.tick_current, liquidity_delta, pool_state.fee_growth_global_0_x64, pool_state.fee_growth_global_1_x64, true, &updated_reward_infos, )?; #[cfg(feature = "enable-log")] msg!( "tick_upper.reward_growths_outside_x64:{:?}, tick_lower.reward_growths_outside_x64:{:?}", identity(tick_upper_state.reward_growths_outside_x64), identity(tick_lower_state.reward_growths_outside_x64) ); } // Update fees let (fee_growth_inside_0_x64, fee_growth_inside_1_x64) = tick_array::get_fee_growth_inside( tick_lower_state.deref(), tick_upper_state.deref(), pool_state.tick_current, pool_state.fee_growth_global_0_x64, pool_state.fee_growth_global_1_x64, ); // Update reward outside if needed let reward_growths_inside = tick_array::get_reward_growths_inside( tick_lower_state.deref(), tick_upper_state.deref(), pool_state.tick_current, &updated_reward_infos, ); protocol_position_state.update( tick_lower_state.tick, tick_upper_state.tick, liquidity_delta, fee_growth_inside_0_x64, fee_growth_inside_1_x64, reward_growths_inside, )?; if liquidity_delta < 0 { if flipped_lower { tick_lower_state.clear(); } if flipped_upper { tick_upper_state.clear(); } } Ok((flipped_lower, flipped_upper)) }

get_delta_amounts_signed

/// --- programs/amm/src/libraries/liquidity_math.rs --- pub fn get_delta_amounts_signed( tick_current: i32, sqrt_price_x64_current: u128, tick_lower: i32, tick_upper: i32, liquidity_delta: i128, ) -> Result<(u64, u64)> { let mut amount_0 = 0; let mut amount_1 = 0; if tick_current < tick_lower { amount_0 = get_delta_amount_0_signed( tick_math::get_sqrt_price_at_tick(tick_lower)?, tick_math::get_sqrt_price_at_tick(tick_upper)?, liquidity_delta, ) .unwrap(); } else if tick_current < tick_upper { amount_0 = get_delta_amount_0_signed( sqrt_price_x64_current, tick_math::get_sqrt_price_at_tick(tick_upper)?, liquidity_delta, ) .unwrap(); amount_1 = get_delta_amount_1_signed( tick_math::get_sqrt_price_at_tick(tick_lower)?, sqrt_price_x64_current, liquidity_delta, ) .unwrap(); } else { amount_1 = get_delta_amount_1_signed( tick_math::get_sqrt_price_at_tick(tick_lower)?, tick_math::get_sqrt_price_at_tick(tick_upper)?, liquidity_delta, ) .unwrap(); } Ok((amount_0, amount_1)) }
 

mint_nft_and_remove_mint_authority

/// --- programs/amm/src/instructions/open_position.rs --- fn mint_nft_and_remove_mint_authority<'info>( payer: &Signer<'info>, pool_state_loader: &AccountLoader<'info, PoolState>, personal_position: &Account<'info, PersonalPositionState>, position_nft_mint: &AccountInfo<'info>, position_nft_account: &AccountInfo<'info>, metadata_account: Option<&UncheckedAccount<'info>>, metadata_program: Option<&Program<'info, Metadata>>, token_program: &Program<'info, Token>, token_program_2022: Option<&Program<'info, Token2022>>, system_program: &Program<'info, System>, rent: &Sysvar<'info, Rent>, with_metadata: bool, use_metadata_extension: bool, ) -> Result<()> { let pool_state_info = pool_state_loader.to_account_info(); let position_nft_mint_info = position_nft_mint.to_account_info(); let pool_state = pool_state_loader.load()?; let seeds = pool_state.seeds(); let token_program_info = if position_nft_mint_info.owner == token_program.key { token_program.to_account_info() } else { token_program_2022.unwrap().to_account_info() }; if with_metadata { let (name, symbol, uri) = get_metadata_data(personal_position.key()); if use_metadata_extension { initialize_token_metadata_extension( payer, &position_nft_mint_info, &pool_state_info, &personal_position.to_account_info(), token_program_2022.unwrap(), name, symbol, uri, &[&seeds], )?; } else { initialize_metadata_account( payer, &pool_state_info, &position_nft_mint_info, metadata_account.unwrap(), metadata_program.unwrap(), system_program, rent, name, symbol, uri, &[&seeds], )?; } } // Mint the NFT token_2022::mint_to( CpiContext::new_with_signer( token_program_info.to_account_info(), token_2022::MintTo { mint: position_nft_mint_info.clone(), to: position_nft_account.to_account_info(), authority: pool_state_info.clone(), }, &[&seeds], ), 1, )?; // Disable minting token_2022::set_authority( CpiContext::new_with_signer( token_program_info.to_account_info(), token_2022::SetAuthority { current_authority: pool_state_loader.to_account_info(), account_or_mint: position_nft_mint_info, }, &[&seeds], ), AuthorityType::MintTokens, None, ) }

Reference