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