Skip to main content

sol_parser_sdk/accounts/
token.rs

1//! SPL Token 和 Token-2022 账户解析
2//!
3//! 提供 Token Account 和 Mint 账户的解析功能,支持:
4//! - SPL Token (标准 Token Program)
5//! - Token-2022 (Token Extensions Program)
6//!
7//! ## 性能优化
8//! - 零拷贝解析:直接从字节切片读取,避免反序列化开销
9//! - 快速路径:优先使用零拷贝,失败时回退到完整解析
10//! - 智能检测:根据数据长度和 owner 自动识别账户类型
11
12use crate::core::events::{EventMetadata, TokenAccountEvent, TokenInfoEvent};
13use crate::DexEvent;
14use solana_sdk::pubkey::Pubkey;
15use spl_token::solana_program::program_pack::Pack;
16use spl_token::state::{Account, Mint};
17use spl_token_2022::{
18    extension::StateWithExtensions,
19    state::{Account as Account2022, Mint as Mint2022},
20};
21
22#[derive(Clone, Debug)]
23pub struct AccountData {
24    pub pubkey: Pubkey,
25    pub executable: bool,
26    pub lamports: u64,
27    pub owner: Pubkey,
28    pub rent_epoch: u64,
29    pub data: Vec<u8>,
30}
31
32/// 解析 Token 账户(支持 SPL Token 和 Token-2022)
33///
34/// # 解析策略
35/// 1. 优先尝试零拷贝快速解析(Mint 和 Token Account)
36/// 2. 如果快速解析失败,回退到完整的 Pack/StateWithExtensions 解析
37/// 3. 支持 Token-2022 扩展状态
38///
39/// # 性能
40/// - 快速路径:~50ns(零拷贝)
41/// - 完整解析:~200ns(Pack/StateWithExtensions)
42pub fn parse_token_account(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
43    // 快速路径:尝试零拷贝解析
44    if account.data.len() <= 100 {
45        if let Some(event) = parse_mint_fast(account, metadata.clone()) {
46            return Some(event);
47        }
48    }
49
50    if let Some(event) = parse_token_fast(account, metadata.clone()) {
51        return Some(event);
52    }
53
54    // 完整解析路径:支持 Token-2022 扩展
55    parse_token_with_extensions(account, metadata)
56}
57
58/// 快速解析 Mint 账户(零拷贝)
59///
60/// 直接从字节切片读取 supply 和 decimals,避免完整反序列化
61#[inline]
62fn parse_mint_fast(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
63    const MINT_SIZE: usize = 82;
64    const SUPPLY_OFFSET: usize = 36;
65    const DECIMALS_OFFSET: usize = 44;
66
67    if account.data.len() < MINT_SIZE {
68        return None;
69    }
70
71    let supply_bytes: [u8; 8] = account.data[SUPPLY_OFFSET..SUPPLY_OFFSET + 8].try_into().ok()?;
72    let supply = u64::from_le_bytes(supply_bytes);
73    let decimals = account.data[DECIMALS_OFFSET];
74
75    let event = TokenInfoEvent {
76        metadata,
77        pubkey: account.pubkey,
78        executable: account.executable,
79        lamports: account.lamports,
80        owner: account.owner,
81        rent_epoch: account.rent_epoch,
82        supply,
83        decimals,
84    };
85
86    Some(DexEvent::TokenInfo(event))
87}
88
89/// 快速解析 Token Account(零拷贝)
90///
91/// 直接从字节切片读取 amount,避免完整反序列化
92#[inline]
93fn parse_token_fast(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
94    const TOKEN_ACCOUNT_SIZE: usize = 165;
95    const AMOUNT_OFFSET: usize = 64;
96
97    if account.data.len() < AMOUNT_OFFSET + 8 {
98        return None;
99    }
100
101    // 只解析标准大小的 Token Account(不包含扩展)
102    if account.data.len() != TOKEN_ACCOUNT_SIZE {
103        return None;
104    }
105
106    let amount_bytes: [u8; 8] = account.data[AMOUNT_OFFSET..AMOUNT_OFFSET + 8].try_into().ok()?;
107    let amount = u64::from_le_bytes(amount_bytes);
108
109    let event = TokenAccountEvent {
110        metadata,
111        pubkey: account.pubkey,
112        executable: account.executable,
113        lamports: account.lamports,
114        owner: account.owner,
115        rent_epoch: account.rent_epoch,
116        amount: Some(amount),
117        token_owner: account.owner,
118    };
119
120    Some(DexEvent::TokenAccount(event))
121}
122
123/// 完整解析 Token 账户(支持 Token-2022 扩展)
124///
125/// 使用 Pack 和 StateWithExtensions 进行完整解析
126fn parse_token_with_extensions(account: &AccountData, metadata: EventMetadata) -> Option<DexEvent> {
127    // 尝试解析为 Token-2022 Mint(带扩展)
128    if account.data.len() >= Mint2022::LEN {
129        if let Ok(mint_state) = StateWithExtensions::<Mint2022>::unpack(&account.data) {
130            let event = TokenInfoEvent {
131                metadata,
132                pubkey: account.pubkey,
133                executable: account.executable,
134                lamports: account.lamports,
135                owner: account.owner,
136                rent_epoch: account.rent_epoch,
137                supply: mint_state.base.supply,
138                decimals: mint_state.base.decimals,
139            };
140            return Some(DexEvent::TokenInfo(event));
141        }
142    }
143
144    // 尝试解析为标准 SPL Token Mint
145    if account.data.len() >= Mint::LEN {
146        if let Ok(mint) = Mint::unpack_from_slice(&account.data) {
147            let event = TokenInfoEvent {
148                metadata,
149                pubkey: account.pubkey,
150                executable: account.executable,
151                lamports: account.lamports,
152                owner: account.owner,
153                rent_epoch: account.rent_epoch,
154                supply: mint.supply,
155                decimals: mint.decimals,
156            };
157            return Some(DexEvent::TokenInfo(event));
158        }
159    }
160
161    // 尝试解析为 Token-2022 Account(带扩展)
162    if account.owner.to_bytes() == spl_token_2022::ID.to_bytes() {
163        if let Ok(account_state) = StateWithExtensions::<Account2022>::unpack(&account.data) {
164            // 转换 spl_token_2022::Pubkey 到 solana_sdk::Pubkey
165            let token_owner = Pubkey::new_from_array(account_state.base.owner.to_bytes());
166            let event = TokenAccountEvent {
167                metadata,
168                pubkey: account.pubkey,
169                executable: account.executable,
170                lamports: account.lamports,
171                owner: account.owner,
172                rent_epoch: account.rent_epoch,
173                amount: Some(account_state.base.amount),
174                token_owner,
175            };
176            return Some(DexEvent::TokenAccount(event));
177        }
178    }
179
180    // 尝试解析为标准 SPL Token Account
181    if let Ok(token_account) = Account::unpack(&account.data) {
182        // 转换 spl_token::Pubkey 到 solana_sdk::Pubkey
183        let token_owner = Pubkey::new_from_array(token_account.owner.to_bytes());
184        let event = TokenAccountEvent {
185            metadata,
186            pubkey: account.pubkey,
187            executable: account.executable,
188            lamports: account.lamports,
189            owner: account.owner,
190            rent_epoch: account.rent_epoch,
191            amount: Some(token_account.amount),
192            token_owner,
193        };
194        return Some(DexEvent::TokenAccount(event));
195    }
196
197    None
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn test_parse_mint_fast() {
206        // 创建一个模拟的 Mint 账户数据
207        let mut data = vec![0u8; 82];
208        // 设置 supply (offset 36)
209        data[36..44].copy_from_slice(&1000000u64.to_le_bytes());
210        // 设置 decimals (offset 44)
211        data[44] = 6;
212
213        let account = AccountData {
214            pubkey: Pubkey::new_unique(),
215            executable: false,
216            lamports: 1000000,
217            owner: Pubkey::new_from_array(spl_token::ID.to_bytes()),
218            rent_epoch: 0,
219            data,
220        };
221
222        let metadata = EventMetadata::default();
223        let event = parse_mint_fast(&account, metadata);
224
225        assert!(event.is_some());
226        if let Some(DexEvent::TokenInfo(info)) = event {
227            assert_eq!(info.supply, 1000000);
228            assert_eq!(info.decimals, 6);
229        }
230    }
231
232    #[test]
233    fn test_parse_token_fast() {
234        // 创建一个模拟的 Token Account 数据
235        let mut data = vec![0u8; 165];
236        // 设置 amount (offset 64)
237        data[64..72].copy_from_slice(&5000u64.to_le_bytes());
238
239        let account = AccountData {
240            pubkey: Pubkey::new_unique(),
241            executable: false,
242            lamports: 2039280,
243            owner: Pubkey::new_from_array(spl_token::ID.to_bytes()),
244            rent_epoch: 0,
245            data,
246        };
247
248        let metadata = EventMetadata::default();
249        let event = parse_token_fast(&account, metadata);
250
251        assert!(event.is_some());
252        if let Some(DexEvent::TokenAccount(token_account)) = event {
253            assert_eq!(token_account.amount, Some(5000));
254        }
255    }
256}