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 coreinstructions::open_position
function. It will mint NFT using token program, whileinstructions::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
- Entry Point:
open_position_v1
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.- 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.
- 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.
- 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.
- Liquidity Addition:
- Calculating the required token amounts.
- Updating tick states.
- Managing transfer fees.
Calls the
add_liquidity
function to handle the core liquidity addition logic, which includes:- 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.
- Event Emission:
Emits a
CreatePersonalPositionEvent
to log the creation of the personal position.- NFT Minting and Metadata Handling:
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 TickArrayState
s 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:
- 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 minimaltick_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
.- Otherwise, checks
tick_index
is multiple oftick_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_index
s 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 oftoken_0
transferred.amount_1
: Actual amount oftoken_1
transferred.amount_0_transfer_fee
: Transfer fee applied totoken_0
.amount_1_transfer_fee
: Transfer fee applied totoken_1
.
Steps:
- Initial Liquidity Calculation
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
/// --- 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, ) }