use crate::core::events::*;
use crate::instr::program_ids::RAYDIUM_CLMM_PROGRAM_ID;
use solana_sdk::pubkey::Pubkey;
pub type AccountGetter<'a> = dyn Fn(usize) -> Pubkey + 'a;
const CLMM_MEMO_PROGRAM: Pubkey =
solana_sdk::pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr");
#[inline]
pub fn tick_array_bitmap_extension_pda(pool_state: &Pubkey) -> Pubkey {
Pubkey::find_program_address(
&[b"pool_tick_array_bitmap_extension", pool_state.as_ref()],
&RAYDIUM_CLMM_PROGRAM_ID,
)
.0
}
#[inline]
pub fn split_clmm_remaining_accounts(
pool_state: &Pubkey,
remaining: &[Pubkey],
) -> (Option<Pubkey>, Vec<Pubkey>) {
if remaining.is_empty() {
return (None, Vec::new());
}
let bitmap_pda = tick_array_bitmap_extension_pda(pool_state);
let mut bitmap = None;
let mut ticks = Vec::with_capacity(remaining.len());
for &key in remaining {
if key == bitmap_pda {
bitmap = Some(key);
} else {
ticks.push(key);
}
}
(bitmap, ticks)
}
pub fn fill_clmm_swap_accounts(e: &mut RaydiumClmmSwapEvent, get: &AccountGetter<'_>) {
fill_clmm_swap_accounts_impl(e, get, None);
}
pub fn fill_clmm_swap_accounts_with_count(
e: &mut RaydiumClmmSwapEvent,
get: &AccountGetter<'_>,
account_count: usize,
) {
fill_clmm_swap_accounts_impl(e, get, Some(account_count));
}
fn fill_clmm_swap_accounts_impl(
e: &mut RaydiumClmmSwapEvent,
get: &AccountGetter<'_>,
account_count: Option<usize>,
) {
if e.pool_state == Pubkey::default() {
e.pool_state = get(2);
}
if e.sender == Pubkey::default() {
e.sender = get(0);
}
if e.token_account_0 == Pubkey::default() {
e.token_account_0 = get(3);
}
if e.token_account_1 == Pubkey::default() {
e.token_account_1 = get(4);
}
if e.input_token_account == Pubkey::default() {
e.input_token_account = get(3);
}
if e.output_token_account == Pubkey::default() {
e.output_token_account = get(4);
}
if e.amm_config == Pubkey::default() {
e.amm_config = get(1);
}
if e.input_vault == Pubkey::default() {
e.input_vault = get(5);
}
if e.output_vault == Pubkey::default() {
e.output_vault = get(6);
}
if e.observation_state == Pubkey::default() {
e.observation_state = get(7);
}
let is_swap_v2 = match e.ix_name.as_str() {
"swap" => false,
"swap_v2" => true,
_ => get(10) == CLMM_MEMO_PROGRAM,
};
let remaining_start = if is_swap_v2 {
if e.input_mint == Pubkey::default() {
e.input_mint = get(11);
}
if e.output_mint == Pubkey::default() {
e.output_mint = get(12);
}
13usize
} else {
9usize
};
if e.tick_arrays.is_empty() {
let mut remaining = Vec::new();
for idx in remaining_start..account_count.unwrap_or(256) {
let key = get(idx);
if account_count.is_none() && key == Pubkey::default() {
break;
}
remaining.push(key);
}
if e.pool_state != Pubkey::default() && !remaining.is_empty() {
let bitmap_pda = tick_array_bitmap_extension_pda(&e.pool_state);
let mut bitmap = None;
remaining.retain(|key| {
if *key == bitmap_pda {
bitmap = Some(*key);
false
} else {
true
}
});
if e.tick_array_bitmap_extension.is_none() {
e.tick_array_bitmap_extension = bitmap;
}
}
e.tick_arrays = remaining;
}
}
pub fn fill_clmm_create_pool_accounts(e: &mut RaydiumClmmCreatePoolEvent, get: &AccountGetter<'_>) {
if e.creator == Pubkey::default() {
e.creator = get(0);
}
if e.pool == Pubkey::default() {
e.pool = get(2);
}
if e.token_0_mint == Pubkey::default() {
e.token_0_mint = get(3);
}
if e.token_1_mint == Pubkey::default() {
e.token_1_mint = get(4);
}
if e.token_vault_0 == Pubkey::default() {
e.token_vault_0 = get(5);
}
if e.token_vault_1 == Pubkey::default() {
e.token_vault_1 = get(6);
}
}
pub fn fill_clmm_open_position_accounts(
e: &mut RaydiumClmmOpenPositionEvent,
get: &AccountGetter<'_>,
) {
if e.user == Pubkey::default() {
e.user = get(1);
}
if e.position_nft_mint == Pubkey::default() {
e.position_nft_mint = get(2);
}
if e.pool == Pubkey::default() {
e.pool = get(5);
}
}
pub fn fill_clmm_close_position_accounts(
e: &mut RaydiumClmmClosePositionEvent,
get: &AccountGetter<'_>,
) {
if e.user == Pubkey::default() {
e.user = get(0);
}
if e.position_nft_mint == Pubkey::default() {
e.position_nft_mint = get(1);
}
}
pub fn fill_clmm_increase_liquidity_accounts(
e: &mut RaydiumClmmIncreaseLiquidityEvent,
get: &AccountGetter<'_>,
) {
if e.user == Pubkey::default() {
e.user = get(0);
}
if e.position_nft_mint == Pubkey::default() {
e.position_nft_mint = get(1);
}
if e.pool == Pubkey::default() {
e.pool = get(2);
}
}
pub fn fill_clmm_decrease_liquidity_accounts(
e: &mut RaydiumClmmDecreaseLiquidityEvent,
get: &AccountGetter<'_>,
) {
if e.user == Pubkey::default() {
e.user = get(0);
}
if e.position_nft_mint == Pubkey::default() {
e.position_nft_mint = get(1);
}
if e.pool == Pubkey::default() {
e.pool = get(3);
}
}
pub fn fill_cpmm_swap_accounts(e: &mut RaydiumCpmmSwapEvent, get: &AccountGetter<'_>) {
if e.payer == Pubkey::default() {
e.payer = get(0);
}
if e.authority == Pubkey::default() {
e.authority = get(1);
}
if e.input_token_account == Pubkey::default() {
e.input_token_account = get(4);
}
if e.output_token_account == Pubkey::default() {
e.output_token_account = get(5);
}
if e.pool_id == Pubkey::default() {
e.pool_id = get(3);
}
if e.amm_config == Pubkey::default() {
e.amm_config = get(2);
}
if e.input_vault == Pubkey::default() {
e.input_vault = get(6);
}
if e.output_vault == Pubkey::default() {
e.output_vault = get(7);
}
if e.input_token_program == Pubkey::default() {
e.input_token_program = get(8);
}
if e.output_token_program == Pubkey::default() {
e.output_token_program = get(9);
}
if e.input_token_mint == Pubkey::default() {
e.input_token_mint = get(10);
}
if e.output_token_mint == Pubkey::default() {
e.output_token_mint = get(11);
}
if e.observation_state == Pubkey::default() {
e.observation_state = get(12);
}
}
pub fn fill_cpmm_deposit_accounts(e: &mut RaydiumCpmmDepositEvent, get: &AccountGetter<'_>) {
if e.user == Pubkey::default() {
e.user = get(0); }
}
pub fn fill_cpmm_withdraw_accounts(e: &mut RaydiumCpmmWithdrawEvent, get: &AccountGetter<'_>) {
if e.user == Pubkey::default() {
e.user = get(0); }
}
pub fn fill_cpmm_initialize_accounts(e: &mut RaydiumCpmmInitializeEvent, get: &AccountGetter<'_>) {
if e.creator == Pubkey::default() {
e.creator = get(0);
}
if e.pool == Pubkey::default() {
e.pool = get(3);
}
}
pub fn fill_amm_v4_swap_accounts(e: &mut RaydiumAmmV4SwapEvent, get: &AccountGetter<'_>) {
if e.amm == Pubkey::default() {
e.amm = get(1);
}
}
pub fn fill_amm_v4_deposit_accounts(e: &mut RaydiumAmmV4DepositEvent, get: &AccountGetter<'_>) {
if e.token_program == Pubkey::default() {
e.token_program = get(0);
}
if e.amm_authority == Pubkey::default() {
e.amm_authority = get(2);
}
}
pub fn fill_amm_v4_withdraw_accounts(e: &mut RaydiumAmmV4WithdrawEvent, get: &AccountGetter<'_>) {
if e.token_program == Pubkey::default() {
e.token_program = get(0);
}
if e.amm_authority == Pubkey::default() {
e.amm_authority = get(2);
}
if e.amm_open_orders == Pubkey::default() {
e.amm_open_orders = get(3);
}
}
#[cfg(test)]
mod clmm_remaining_tests {
use super::*;
#[test]
fn counted_clmm_tail_keeps_long_lists_default_keys_and_existing_bitmap() {
for v2 in [false, true] {
let start = if v2 { 13 } else { 9 };
let mut accounts: Vec<_> = (0..start + 70).map(|_| Pubkey::new_unique()).collect();
let bitmap = tick_array_bitmap_extension_pda(&accounts[2]);
accounts[start + 65] = bitmap;
accounts[start + 25] = Pubkey::default();
let expected =
accounts[start..].iter().copied().filter(|key| *key != bitmap).collect::<Vec<_>>();
for bitmap_known in [false, true] {
let mut event = RaydiumClmmSwapEvent {
ix_name: if v2 { "swap_v2" } else { "swap" }.to_string(),
tick_array_bitmap_extension: bitmap_known.then_some(bitmap),
..Default::default()
};
fill_clmm_swap_accounts_with_count(
&mut event,
&|i| accounts.get(i).copied().unwrap_or_default(),
accounts.len(),
);
assert_eq!(event.tick_array_bitmap_extension, Some(bitmap));
assert_eq!(event.tick_arrays, expected);
assert_eq!(event.tick_arrays.len(), 69);
}
accounts[start + 25] = Pubkey::new_unique();
let mut event = RaydiumClmmSwapEvent {
ix_name: if v2 { "swap_v2" } else { "swap" }.to_string(),
..Default::default()
};
fill_clmm_swap_accounts(&mut event, &|i| accounts.get(i).copied().unwrap_or_default());
assert_eq!(event.tick_arrays.len(), 69);
}
}
#[test]
fn bitmap_pda_matches_idl_seeds() {
let pool = Pubkey::new_unique();
let (expected, _) = Pubkey::find_program_address(
&[b"pool_tick_array_bitmap_extension", pool.as_ref()],
&RAYDIUM_CLMM_PROGRAM_ID,
);
assert_eq!(tick_array_bitmap_extension_pda(&pool), expected);
}
#[test]
fn split_pulls_bitmap_from_any_remaining_slot() {
let pool = Pubkey::new_unique();
let bitmap = tick_array_bitmap_extension_pda(&pool);
let t0 = Pubkey::new_unique();
let t1 = Pubkey::new_unique();
let (ext, ticks) = split_clmm_remaining_accounts(&pool, &[t0, bitmap, t1]);
assert_eq!(ext, Some(bitmap));
assert_eq!(ticks, vec![t0, t1]);
}
#[test]
fn fill_swap_v2_separates_bitmap_and_ticks() {
let memo = CLMM_MEMO_PROGRAM;
let pool = Pubkey::new_unique();
let bitmap = tick_array_bitmap_extension_pda(&pool);
let t0 = Pubkey::new_unique();
let t1 = Pubkey::new_unique();
let mut accounts: Vec<_> = (0..16).map(|_| Pubkey::new_unique()).collect();
accounts[2] = pool;
accounts[10] = memo;
accounts[13] = bitmap;
accounts[14] = t0;
accounts[15] = t1;
let mut e = RaydiumClmmSwapEvent::default();
fill_clmm_swap_accounts(&mut e, &|i| accounts.get(i).copied().unwrap_or_default());
assert_eq!(e.tick_array_bitmap_extension, Some(bitmap));
assert_eq!(e.tick_arrays, vec![t0, t1]);
assert_eq!(e.input_mint, accounts[11]);
assert_eq!(e.output_mint, accounts[12]);
}
#[test]
fn fill_swap_v1_starts_remaining_at_tick_array_slot() {
let pool = Pubkey::new_unique();
let t0 = Pubkey::new_unique();
let t1 = Pubkey::new_unique();
let mut accounts: Vec<_> = (0..11).map(|_| Pubkey::new_unique()).collect();
accounts[2] = pool;
accounts[9] = t0;
accounts[10] = t1;
let mut e = RaydiumClmmSwapEvent::default();
fill_clmm_swap_accounts(&mut e, &|i| accounts.get(i).copied().unwrap_or_default());
assert!(e.tick_array_bitmap_extension.is_none());
assert_eq!(e.tick_arrays, vec![t0, t1]);
assert_eq!(e.input_mint, Pubkey::default());
}
}