light_token_interface/
pool_derivation.rs

1//! SPL interface PDA derivation utilities for Light Protocol.
2//!
3//! This module provides functions to derive SPL interface PDAs (token pools) for both regular
4//! and restricted mints. Restricted mints (those with Pausable, PermanentDelegate,
5//! TransferFeeConfig, TransferHook, or DefaultAccountState extensions) use a different derivation path
6//! to prevent accidental compression via legacy anchor instructions.
7
8use solana_pubkey::Pubkey;
9use spl_token_2022::{
10    extension::{BaseStateWithExtensions, PodStateWithExtensions},
11    pod::PodMint,
12};
13
14use crate::{
15    constants::{LIGHT_TOKEN_PROGRAM_ID, POOL_SEED, RESTRICTED_POOL_SEED},
16    is_restricted_extension,
17};
18
19/// Maximum number of pool accounts per mint.
20pub const NUM_MAX_POOL_ACCOUNTS: u8 = 5;
21
22// ============================================================================
23// SPL interface PDA derivation (uses LIGHT_TOKEN_PROGRAM_ID)
24// ============================================================================
25
26/// Find the SPL interface PDA for a given mint (index 0).
27///
28/// # Arguments
29/// * `mint` - The mint public key
30/// * `restricted` - Whether to use restricted derivation (for mints with restricted extensions)
31///
32/// # Seed format
33/// - Regular: `["pool", mint]`
34/// - Restricted: `["pool", mint, "restricted"]`
35pub fn find_spl_interface_pda(mint: &Pubkey, restricted: bool) -> (Pubkey, u8) {
36    find_spl_interface_pda_with_index(mint, 0, restricted)
37}
38
39/// Find the SPL interface PDA for a given mint and index.
40///
41/// # Arguments
42/// * `mint` - The mint public key
43/// * `index` - The pool index (0-4)
44/// * `restricted` - Whether to use restricted derivation (for mints with restricted extensions)
45///
46/// # Seed format
47/// - Regular index 0: `["pool", mint]`
48/// - Regular index 1-4: `["pool", mint, index]`
49/// - Restricted index 0: `["pool", mint, "restricted"]`
50/// - Restricted index 1-4: `["pool", mint, "restricted", index]`
51pub fn find_spl_interface_pda_with_index(
52    mint: &Pubkey,
53    index: u8,
54    restricted: bool,
55) -> (Pubkey, u8) {
56    let program_id = Pubkey::from(LIGHT_TOKEN_PROGRAM_ID);
57    let index_bytes = [index];
58
59    let seeds: &[&[u8]] = if restricted {
60        if index == 0 {
61            &[POOL_SEED, mint.as_ref(), RESTRICTED_POOL_SEED]
62        } else {
63            &[POOL_SEED, mint.as_ref(), RESTRICTED_POOL_SEED, &index_bytes]
64        }
65    } else if index == 0 {
66        &[POOL_SEED, mint.as_ref()]
67    } else {
68        &[POOL_SEED, mint.as_ref(), &index_bytes]
69    };
70
71    Pubkey::find_program_address(seeds, &program_id)
72}
73
74/// Get the SPL interface PDA address for a given mint (index 0).
75pub fn get_spl_interface_pda(mint: &Pubkey, restricted: bool) -> Pubkey {
76    find_spl_interface_pda(mint, restricted).0
77}
78
79// ============================================================================
80// Validation
81// ============================================================================
82
83/// Validate that an SPL interface PDA is correctly derived.
84///
85/// # Arguments
86/// * `mint_bytes` - The mint public key as bytes
87/// * `spl_interface_pubkey` - The SPL interface PDA to validate
88/// * `pool_index` - The pool index (0-4)
89/// * `bump` - Optional bump seed for faster validation
90/// * `restricted` - Whether to validate against restricted derivation
91///
92/// # Returns
93/// `true` if the PDA is valid, `false` otherwise
94#[inline(always)]
95pub fn is_valid_spl_interface_pda(
96    mint_bytes: &[u8],
97    spl_interface_pubkey: &Pubkey,
98    pool_index: u8,
99    bump: Option<u8>,
100    restricted: bool,
101) -> bool {
102    let program_id = Pubkey::from(LIGHT_TOKEN_PROGRAM_ID);
103    let index_bytes = [pool_index];
104
105    let pda = if let Some(bump) = bump {
106        // Fast path: use provided bump to derive address directly
107        let bump_bytes = [bump];
108        let seeds: &[&[u8]] = if restricted {
109            if pool_index == 0 {
110                &[POOL_SEED, mint_bytes, RESTRICTED_POOL_SEED, &bump_bytes]
111            } else {
112                &[
113                    POOL_SEED,
114                    mint_bytes,
115                    RESTRICTED_POOL_SEED,
116                    &index_bytes,
117                    &bump_bytes,
118                ]
119            }
120        } else if pool_index == 0 {
121            &[POOL_SEED, mint_bytes, &bump_bytes]
122        } else {
123            &[POOL_SEED, mint_bytes, &index_bytes, &bump_bytes]
124        };
125
126        match Pubkey::create_program_address(seeds, &program_id) {
127            Ok(pda) => pda,
128            Err(_) => return false,
129        }
130    } else {
131        // Slow path: find program address
132        let seeds: &[&[u8]] = if restricted {
133            if pool_index == 0 {
134                &[POOL_SEED, mint_bytes, RESTRICTED_POOL_SEED]
135            } else {
136                &[POOL_SEED, mint_bytes, RESTRICTED_POOL_SEED, &index_bytes]
137            }
138        } else if pool_index == 0 {
139            &[POOL_SEED, mint_bytes]
140        } else {
141            &[POOL_SEED, mint_bytes, &index_bytes]
142        };
143
144        Pubkey::find_program_address(seeds, &program_id).0
145    };
146
147    pda == *spl_interface_pubkey
148}
149
150// ============================================================================
151// Mint extension helpers
152// ============================================================================
153
154/// Check if a mint has any restricted extensions.
155///
156/// Restricted extensions (Pausable, PermanentDelegate, TransferFeeConfig, TransferHook, DefaultAccountState)
157/// require using the restricted pool derivation path.
158///
159/// # Arguments
160/// * `mint_data` - The raw mint account data
161///
162/// # Returns
163/// `true` if the mint has any restricted extensions, `false` otherwise
164pub fn has_restricted_extensions(mint_data: &[u8]) -> bool {
165    let mint = match PodStateWithExtensions::<PodMint>::unpack(mint_data) {
166        Ok(mint) => mint,
167        Err(_) => return false,
168    };
169
170    let extensions = match mint.get_extension_types() {
171        Ok(exts) => exts,
172        Err(_) => return false,
173    };
174
175    extensions.iter().any(is_restricted_extension)
176}