@@ -7,7 +7,7 @@ use solana_program::{
7
7
program_error:: ProgramError ,
8
8
program_option:: COption ,
9
9
program_pack:: { IsInitialized , Pack , Sealed } ,
10
- pubkey:: Pubkey ,
10
+ pubkey:: { Pubkey , PUBKEY_BYTES } ,
11
11
} ;
12
12
13
13
/// Mint data.
@@ -287,6 +287,56 @@ fn unpack_coption_u64(src: &[u8; 12]) -> Result<COption<u64>, ProgramError> {
287
287
}
288
288
}
289
289
290
+ const SPL_TOKEN_ACCOUNT_MINT_OFFSET : usize = 0 ;
291
+ const SPL_TOKEN_ACCOUNT_OWNER_OFFSET : usize = 32 ;
292
+
293
+ /// A trait for token Account structs to enable efficiently unpacking various fields
294
+ /// without unpacking the complete state.
295
+ pub trait GenericTokenAccount {
296
+ /// Check if the account data is a valid token account
297
+ fn valid_account_data ( account_data : & [ u8 ] ) -> bool ;
298
+
299
+ /// Call after account length has already been verified to unpack the account owner
300
+ fn unpack_account_owner_unchecked ( account_data : & [ u8 ] ) -> & Pubkey {
301
+ Self :: unpack_pubkey_unchecked ( account_data, SPL_TOKEN_ACCOUNT_OWNER_OFFSET )
302
+ }
303
+
304
+ /// Call after account length has already been verified to unpack the account mint
305
+ fn unpack_account_mint_unchecked ( account_data : & [ u8 ] ) -> & Pubkey {
306
+ Self :: unpack_pubkey_unchecked ( account_data, SPL_TOKEN_ACCOUNT_MINT_OFFSET )
307
+ }
308
+
309
+ /// Call after account length has already been verified to unpack a Pubkey at
310
+ /// the specified offset. Panics if `account_data.len()` is less than `PUBKEY_BYTES`
311
+ fn unpack_pubkey_unchecked ( account_data : & [ u8 ] , offset : usize ) -> & Pubkey {
312
+ bytemuck:: from_bytes ( & account_data[ offset..offset + PUBKEY_BYTES ] )
313
+ }
314
+
315
+ /// Unpacks an account's owner from opaque account data.
316
+ fn unpack_account_owner ( account_data : & [ u8 ] ) -> Option < & Pubkey > {
317
+ if Self :: valid_account_data ( account_data) {
318
+ Some ( Self :: unpack_account_owner_unchecked ( account_data) )
319
+ } else {
320
+ None
321
+ }
322
+ }
323
+
324
+ /// Unpacks an account's mint from opaque account data.
325
+ fn unpack_account_mint ( account_data : & [ u8 ] ) -> Option < & Pubkey > {
326
+ if Self :: valid_account_data ( account_data) {
327
+ Some ( Self :: unpack_account_mint_unchecked ( account_data) )
328
+ } else {
329
+ None
330
+ }
331
+ }
332
+ }
333
+
334
+ impl GenericTokenAccount for Account {
335
+ fn valid_account_data ( account_data : & [ u8 ] ) -> bool {
336
+ account_data. len ( ) == Account :: LEN
337
+ }
338
+ }
339
+
290
340
#[ cfg( test) ]
291
341
mod tests {
292
342
use super :: * ;
@@ -360,4 +410,40 @@ mod tests {
360
410
let result = unpack_coption_u64 ( & src) . unwrap_err ( ) ;
361
411
assert_eq ! ( result, ProgramError :: InvalidAccountData ) ;
362
412
}
413
+
414
+ #[ test]
415
+ fn test_unpack_token_owner ( ) {
416
+ // Account data length < Account::LEN, unpack will not return a key
417
+ let src: [ u8 ; 12 ] = [ 0 ; 12 ] ;
418
+ let result = Account :: unpack_account_owner ( & src) ;
419
+ assert_eq ! ( result, Option :: None ) ;
420
+
421
+ // The right account data size, unpack will return some key
422
+ let src: [ u8 ; Account :: LEN ] = [ 0 ; Account :: LEN ] ;
423
+ let result = Account :: unpack_account_owner ( & src) ;
424
+ assert ! ( result. is_some( ) ) ;
425
+
426
+ // Account data length > account data size, unpack will not return a key
427
+ let src: [ u8 ; Account :: LEN + 5 ] = [ 0 ; Account :: LEN + 5 ] ;
428
+ let result = Account :: unpack_account_owner ( & src) ;
429
+ assert_eq ! ( result, Option :: None ) ;
430
+ }
431
+
432
+ #[ test]
433
+ fn test_unpack_token_mint ( ) {
434
+ // Account data length < Account::LEN, unpack will not return a key
435
+ let src: [ u8 ; 12 ] = [ 0 ; 12 ] ;
436
+ let result = Account :: unpack_account_mint ( & src) ;
437
+ assert_eq ! ( result, Option :: None ) ;
438
+
439
+ // The right account data size, unpack will return some key
440
+ let src: [ u8 ; Account :: LEN ] = [ 0 ; Account :: LEN ] ;
441
+ let result = Account :: unpack_account_mint ( & src) ;
442
+ assert ! ( result. is_some( ) ) ;
443
+
444
+ // Account data length > account data size, unpack will not return a key
445
+ let src: [ u8 ; Account :: LEN + 5 ] = [ 0 ; Account :: LEN + 5 ] ;
446
+ let result = Account :: unpack_account_mint ( & src) ;
447
+ assert_eq ! ( result, Option :: None ) ;
448
+ }
363
449
}
0 commit comments