1use crate::token::interface::TokenMint;
2
3use super::interface::{TokenInterface, TokenState};
4use async_trait::async_trait;
5use solana_program::pubkey::Pubkey;
6use solana_sdk::{instruction::Instruction, program_pack::Pack};
7use spl_associated_token_account_interface::{
8 address::get_associated_token_address_with_program_id,
9 instruction::create_associated_token_account,
10};
11use spl_token_interface::{
12 self,
13 state::{Account as TokenAccountState, AccountState, Mint as MintState},
14};
15
16#[derive(Debug)]
17pub struct TokenAccount {
18 pub mint: Pubkey,
19 pub owner: Pubkey,
20 pub amount: u64,
21 pub delegate: Option<Pubkey>,
22 pub state: u8,
23 pub is_native: Option<u64>,
24 pub delegated_amount: u64,
25 pub close_authority: Option<Pubkey>,
26}
27
28impl TokenState for TokenAccount {
29 fn mint(&self) -> Pubkey {
30 self.mint
31 }
32 fn owner(&self) -> Pubkey {
33 self.owner
34 }
35 fn amount(&self) -> u64 {
36 self.amount
37 }
38 fn decimals(&self) -> u8 {
39 0
40 }
41 fn as_any(&self) -> &dyn std::any::Any {
42 self
43 }
44}
45
46#[derive(Debug)]
47pub struct SplMint {
48 pub mint: Pubkey,
49 pub mint_authority: Option<Pubkey>,
50 pub supply: u64,
51 pub decimals: u8,
52 pub is_initialized: bool,
53 pub freeze_authority: Option<Pubkey>,
54}
55
56impl TokenMint for SplMint {
57 fn address(&self) -> Pubkey {
58 self.mint
59 }
60
61 fn decimals(&self) -> u8 {
62 self.decimals
63 }
64
65 fn mint_authority(&self) -> Option<Pubkey> {
66 self.mint_authority
67 }
68
69 fn supply(&self) -> u64 {
70 self.supply
71 }
72
73 fn freeze_authority(&self) -> Option<Pubkey> {
74 self.freeze_authority
75 }
76
77 fn is_initialized(&self) -> bool {
78 self.is_initialized
79 }
80
81 fn get_token_program(&self) -> Box<dyn TokenInterface> {
82 Box::new(TokenProgram::new())
83 }
84
85 fn as_any(&self) -> &dyn std::any::Any {
86 self
87 }
88}
89
90pub struct TokenProgram;
91
92impl Default for TokenProgram {
93 fn default() -> Self {
94 Self::new()
95 }
96}
97
98impl TokenProgram {
99 pub fn new() -> Self {
100 Self
101 }
102}
103
104#[async_trait]
105impl TokenInterface for TokenProgram {
106 fn program_id(&self) -> Pubkey {
107 spl_token_interface::id()
108 }
109
110 fn unpack_token_account(
111 &self,
112 data: &[u8],
113 ) -> Result<Box<dyn TokenState + Send + Sync>, Box<dyn std::error::Error + Send + Sync>> {
114 let account = TokenAccountState::unpack(data)?;
115
116 Ok(Box::new(TokenAccount {
117 mint: account.mint,
118 owner: account.owner,
119 amount: account.amount,
120 delegate: account.delegate.into(),
121 state: match account.state {
122 AccountState::Uninitialized => 0,
123 AccountState::Initialized => 1,
124 AccountState::Frozen => 2,
125 },
126 is_native: account.is_native.into(),
127 delegated_amount: account.delegated_amount,
128 close_authority: account.close_authority.into(),
129 }))
130 }
131
132 fn create_initialize_account_instruction(
133 &self,
134 account: &Pubkey,
135 mint: &Pubkey,
136 owner: &Pubkey,
137 ) -> Result<Instruction, Box<dyn std::error::Error + Send + Sync>> {
138 Ok(spl_token_interface::instruction::initialize_account(
139 &self.program_id(),
140 account,
141 mint,
142 owner,
143 )?)
144 }
145
146 fn create_transfer_instruction(
147 &self,
148 source: &Pubkey,
149 destination: &Pubkey,
150 authority: &Pubkey,
151 amount: u64,
152 ) -> Result<Instruction, Box<dyn std::error::Error + Send + Sync>> {
153 Ok(spl_token_interface::instruction::transfer(
154 &self.program_id(),
155 source,
156 destination,
157 authority,
158 &[],
159 amount,
160 )?)
161 }
162
163 fn create_transfer_checked_instruction(
164 &self,
165 source: &Pubkey,
166 mint: &Pubkey,
167 destination: &Pubkey,
168 authority: &Pubkey,
169 amount: u64,
170 decimals: u8,
171 ) -> Result<Instruction, Box<dyn std::error::Error + Send + Sync>> {
172 Ok(spl_token_interface::instruction::transfer_checked(
173 &self.program_id(),
174 source,
175 mint,
176 destination,
177 authority,
178 &[],
179 amount,
180 decimals,
181 )?)
182 }
183
184 fn get_associated_token_address(&self, wallet: &Pubkey, mint: &Pubkey) -> Pubkey {
185 get_associated_token_address_with_program_id(wallet, mint, &self.program_id())
186 }
187
188 fn create_associated_token_account_instruction(
189 &self,
190 funding_account: &Pubkey,
191 wallet: &Pubkey,
192 mint: &Pubkey,
193 ) -> Instruction {
194 create_associated_token_account(funding_account, wallet, mint, &self.program_id())
195 }
196
197 fn unpack_mint(
198 &self,
199 mint: &Pubkey,
200 mint_data: &[u8],
201 ) -> Result<Box<dyn TokenMint + Send + Sync>, Box<dyn std::error::Error + Send + Sync>> {
202 let mint_state = MintState::unpack(mint_data)?;
203
204 Ok(Box::new(SplMint {
205 mint: *mint,
206 mint_authority: mint_state.mint_authority.into(),
207 supply: mint_state.supply,
208 decimals: mint_state.decimals,
209 is_initialized: mint_state.is_initialized,
210 freeze_authority: mint_state.freeze_authority.into(),
211 }))
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use crate::tests::common::{MintAccountMockBuilder, TokenAccountMockBuilder};
218
219 use super::*;
220 use solana_program::program_pack::Pack;
221 use solana_sdk::pubkey::Pubkey;
222 use spl_token_interface::state::{Account as SplTokenAccount, AccountState};
223
224 #[test]
225 fn test_token_program_creation_and_program_id() {
226 let program = TokenProgram::new();
227 assert_eq!(program.program_id(), spl_token_interface::id());
228 }
229
230 #[test]
231 fn test_unpack_token_account_success() {
232 let mint = Pubkey::new_unique();
233 let owner = Pubkey::new_unique();
234 let delegate = Pubkey::new_unique();
235 let close_authority = Pubkey::new_unique();
236 let amount = 1000000;
237 let delegated_amount = 500000;
238 let is_native = Some(2039280u64);
239
240 let account = TokenAccountMockBuilder::new()
241 .with_mint(&mint)
242 .with_owner(&owner)
243 .with_amount(amount)
244 .with_state(AccountState::Initialized)
245 .with_delegate(Some(delegate))
246 .with_native(is_native)
247 .with_delegated_amount(delegated_amount)
248 .with_close_authority(Some(close_authority))
249 .build();
250
251 let program = TokenProgram::new();
252 let result = program.unpack_token_account(&account.data);
253 assert!(result.is_ok());
254
255 let token_state = result.unwrap();
256 let token_account = token_state.as_any().downcast_ref::<TokenAccount>().unwrap();
257
258 assert_eq!(token_account.mint, mint);
259 assert_eq!(token_account.owner, owner);
260 assert_eq!(token_account.amount, amount);
261 assert_eq!(token_account.delegate, Some(delegate));
262 assert_eq!(token_account.state, 1); assert_eq!(token_account.is_native, is_native);
264 assert_eq!(token_account.delegated_amount, delegated_amount);
265 assert_eq!(token_account.close_authority, Some(close_authority));
266 }
267
268 #[test]
269 fn test_unpack_token_account_invalid_data() {
270 let program = TokenProgram::new();
271
272 let result = program.unpack_token_account(&[]);
274 assert!(result.is_err());
275
276 let short_data = vec![0u8; 10];
278 let result = program.unpack_token_account(&short_data);
279 assert!(result.is_err());
280
281 let mut corrupted_data = vec![0xFFu8; SplTokenAccount::LEN];
283 corrupted_data[0] = 0xFF; let result = program.unpack_token_account(&corrupted_data);
285 assert!(result.is_err());
286 }
287
288 #[test]
289 fn test_unpack_mint_success() {
290 let mint_pubkey = Pubkey::new_unique();
291 let mint_authority = Pubkey::new_unique();
292 let freeze_authority = Pubkey::new_unique();
293 let supply = 1000000000;
294 let decimals = 6;
295
296 let account = MintAccountMockBuilder::new()
297 .with_mint_authority(Some(mint_authority))
298 .with_supply(supply)
299 .with_decimals(decimals)
300 .with_initialized(true)
301 .with_freeze_authority(Some(freeze_authority))
302 .build();
303
304 let program = TokenProgram::new();
305 let result = program.unpack_mint(&mint_pubkey, &account.data);
306 assert!(result.is_ok());
307
308 let token_mint = result.unwrap();
309 let spl_mint = token_mint.as_any().downcast_ref::<SplMint>().unwrap();
310
311 assert_eq!(spl_mint.mint, mint_pubkey);
312 assert_eq!(spl_mint.mint_authority, Some(mint_authority));
313 assert_eq!(spl_mint.supply, supply);
314 assert_eq!(spl_mint.decimals, decimals);
315 assert!(spl_mint.is_initialized);
316 assert_eq!(spl_mint.freeze_authority, Some(freeze_authority));
317 }
318
319 #[test]
320 fn test_unpack_mint_with_none_authorities() {
321 let mint_pubkey = Pubkey::new_unique();
322 let account = MintAccountMockBuilder::new()
324 .with_mint_authority(None)
325 .with_supply(0)
326 .with_decimals(0)
327 .with_initialized(true)
328 .with_freeze_authority(None)
329 .build();
330
331 let program = TokenProgram::new();
332 let result = program.unpack_mint(&mint_pubkey, &account.data).unwrap();
333 let spl_mint = result.as_any().downcast_ref::<SplMint>().unwrap();
334
335 assert_eq!(spl_mint.mint_authority, None);
336 assert_eq!(spl_mint.freeze_authority, None);
337 assert!(spl_mint.is_initialized); }
339
340 #[test]
341 fn test_unpack_mint_invalid_data() {
342 let mint_pubkey = Pubkey::new_unique();
343 let program = TokenProgram::new();
344
345 let result = program.unpack_mint(&mint_pubkey, &[]);
347 assert!(result.is_err());
348
349 let short_data = vec![0u8; 10];
351 let result = program.unpack_mint(&mint_pubkey, &short_data);
352 assert!(result.is_err());
353 }
354
355 #[test]
356 fn test_create_initialize_account_instruction() {
357 let program = TokenProgram::new();
358 let account = Pubkey::new_unique();
359 let mint = Pubkey::new_unique();
360 let owner = Pubkey::new_unique();
361
362 let result = program.create_initialize_account_instruction(&account, &mint, &owner);
363 assert!(result.is_ok());
364
365 let instruction = result.unwrap();
366 assert_eq!(instruction.program_id, spl_token_interface::id());
367 assert_eq!(instruction.accounts.len(), 4); }
369
370 #[test]
371 fn test_create_transfer_instruction() {
372 let program = TokenProgram::new();
373 let source = Pubkey::new_unique();
374 let destination = Pubkey::new_unique();
375 let authority = Pubkey::new_unique();
376 let amount = 1000000;
377
378 let result = program.create_transfer_instruction(&source, &destination, &authority, amount);
379 assert!(result.is_ok());
380
381 let instruction = result.unwrap();
382 assert_eq!(instruction.program_id, spl_token_interface::id());
383 assert_eq!(instruction.accounts.len(), 3); }
385
386 #[test]
387 fn test_create_transfer_checked_instruction() {
388 let program = TokenProgram::new();
389 let source = Pubkey::new_unique();
390 let mint = Pubkey::new_unique();
391 let destination = Pubkey::new_unique();
392 let authority = Pubkey::new_unique();
393 let amount = 1000000;
394 let decimals = 6;
395
396 let result = program.create_transfer_checked_instruction(
397 &source,
398 &mint,
399 &destination,
400 &authority,
401 amount,
402 decimals,
403 );
404 assert!(result.is_ok());
405
406 let instruction = result.unwrap();
407 assert_eq!(instruction.program_id, spl_token_interface::id());
408 assert_eq!(instruction.accounts.len(), 4); }
410
411 #[test]
412 fn test_get_associated_token_address() {
413 let program = TokenProgram::new();
414 let wallet = Pubkey::new_unique();
415 let mint = Pubkey::new_unique();
416
417 let ata = program.get_associated_token_address(&wallet, &mint);
418
419 let ata2 = get_associated_token_address_with_program_id(
420 &wallet,
421 &mint,
422 &spl_token_interface::id(),
423 );
424
425 assert_eq!(ata, ata2);
426 }
427
428 #[test]
429 fn test_create_associated_token_account_instruction() {
430 let program = TokenProgram::new();
431 let funding_account = Pubkey::new_unique();
432 let wallet = Pubkey::new_unique();
433 let mint = Pubkey::new_unique();
434
435 let instruction =
436 program.create_associated_token_account_instruction(&funding_account, &wallet, &mint);
437
438 assert_eq!(instruction.program_id, spl_associated_token_account_interface::program::id());
439 assert_eq!(instruction.accounts.len(), 6); }
441
442 #[test]
443 fn test_spl_mint_get_token_program() {
444 let spl_mint = SplMint {
445 mint: Pubkey::new_unique(),
446 mint_authority: None,
447 supply: 0,
448 decimals: 0,
449 is_initialized: false,
450 freeze_authority: None,
451 };
452
453 let token_program = spl_mint.get_token_program();
454 assert_eq!(token_program.program_id(), spl_token_interface::id());
455 }
456
457 #[test]
458 fn test_spl_mint_as_any_downcasting() {
459 let spl_mint = SplMint {
460 mint: Pubkey::new_unique(),
461 mint_authority: None,
462 supply: 1000000,
463 decimals: 6,
464 is_initialized: true,
465 freeze_authority: None,
466 };
467
468 let any_ref = spl_mint.as_any();
469 assert!(any_ref.is::<SplMint>());
470
471 let downcast_result = any_ref.downcast_ref::<SplMint>();
472 assert!(downcast_result.is_some());
473 assert_eq!(downcast_result.unwrap().supply, 1000000);
474 }
475
476 #[test]
477 fn test_spl_mint_with_none_authorities() {
478 let spl_mint = SplMint {
479 mint: Pubkey::new_unique(),
480 mint_authority: None,
481 supply: 0,
482 decimals: 0,
483 is_initialized: false,
484 freeze_authority: None,
485 };
486
487 assert_eq!(spl_mint.mint_authority(), None);
488 assert_eq!(spl_mint.freeze_authority(), None);
489 assert!(!spl_mint.is_initialized());
490 }
491}