Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit bbbf250

Browse files
Support unpacking token accounts fields partially (#2970)
* Support unpacking token accounts fields partially Co-authored-by: Tyera Eulberg <[email protected]>
1 parent 0c0168f commit bbbf250

File tree

4 files changed

+152
-2
lines changed

4 files changed

+152
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

token/program-2022/src/state.rs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! State transition types
22
33
use {
4-
crate::instruction::MAX_SIGNERS,
4+
crate::{extension::AccountType, instruction::MAX_SIGNERS},
55
arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs},
66
num_enum::{IntoPrimitive, TryFromPrimitive},
77
solana_program::{
@@ -10,6 +10,7 @@ use {
1010
program_pack::{IsInitialized, Pack, Sealed},
1111
pubkey::Pubkey,
1212
},
13+
spl_token::state::GenericTokenAccount,
1314
};
1415

1516
/// Mint data.
@@ -289,6 +290,15 @@ fn unpack_coption_u64(src: &[u8; 12]) -> Result<COption<u64>, ProgramError> {
289290
}
290291
}
291292

293+
// `spl_token_program_2022::extension::AccountType::Account` ordinal value
294+
const ACCOUNTTYPE_ACCOUNT: u8 = AccountType::Account as u8;
295+
impl GenericTokenAccount for Account {
296+
fn valid_account_data(account_data: &[u8]) -> bool {
297+
spl_token::state::Account::valid_account_data(account_data)
298+
|| ACCOUNTTYPE_ACCOUNT == *account_data.get(Account::LEN).unwrap_or(&0)
299+
}
300+
}
301+
292302
#[cfg(test)]
293303
pub(crate) mod test {
294304
use super::*;
@@ -402,4 +412,56 @@ pub(crate) mod test {
402412
let unpacked = Multisig::unpack(&packed).unwrap();
403413
assert_eq!(unpacked, check);
404414
}
415+
416+
#[test]
417+
fn test_unpack_token_owner() {
418+
// Account data length < Account::LEN, unpack will not return a key
419+
let src: [u8; 12] = [0; 12];
420+
let result = Account::unpack_account_owner(&src);
421+
assert_eq!(result, Option::None);
422+
423+
// The right account data size, unpack will return some key
424+
let src: [u8; Account::LEN] = [0; Account::LEN];
425+
let result = Account::unpack_account_owner(&src);
426+
assert!(result.is_some());
427+
428+
// Account data length > account data size, but not a valid extension,
429+
// unpack will not return a key
430+
let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
431+
let result = Account::unpack_account_owner(&src);
432+
assert_eq!(result, Option::None);
433+
434+
// Account data length > account data size with a valid extension,
435+
// expect some key returned
436+
let mut src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
437+
src[Account::LEN] = 2;
438+
let result = Account::unpack_account_owner(&src);
439+
assert!(result.is_some());
440+
}
441+
442+
#[test]
443+
fn test_unpack_token_mint() {
444+
// Account data length < Account::LEN, unpack will not return a key
445+
let src: [u8; 12] = [0; 12];
446+
let result = Account::unpack_account_mint(&src);
447+
assert_eq!(result, Option::None);
448+
449+
// The right account data size, unpack will return some key
450+
let src: [u8; Account::LEN] = [0; Account::LEN];
451+
let result = Account::unpack_account_mint(&src);
452+
assert!(result.is_some());
453+
454+
// Account data length > account data size, but not a valid extension,
455+
// unpack will not return a key
456+
let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
457+
let result = Account::unpack_account_mint(&src);
458+
assert_eq!(result, Option::None);
459+
460+
// Account data length > account data size with a valid extension,
461+
// expect some key returned
462+
let mut src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
463+
src[Account::LEN] = 2;
464+
let result = Account::unpack_account_mint(&src);
465+
assert!(result.is_some());
466+
}
405467
}

token/program/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ test-bpf = []
1414

1515
[dependencies]
1616
arrayref = "0.3.6"
17+
bytemuck = "1.7.2"
1718
num-derive = "0.3"
1819
num-traits = "0.2"
1920
num_enum = "0.5.4"

token/program/src/state.rs

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use solana_program::{
77
program_error::ProgramError,
88
program_option::COption,
99
program_pack::{IsInitialized, Pack, Sealed},
10-
pubkey::Pubkey,
10+
pubkey::{Pubkey, PUBKEY_BYTES},
1111
};
1212

1313
/// Mint data.
@@ -287,6 +287,56 @@ fn unpack_coption_u64(src: &[u8; 12]) -> Result<COption<u64>, ProgramError> {
287287
}
288288
}
289289

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+
290340
#[cfg(test)]
291341
mod tests {
292342
use super::*;
@@ -360,4 +410,40 @@ mod tests {
360410
let result = unpack_coption_u64(&src).unwrap_err();
361411
assert_eq!(result, ProgramError::InvalidAccountData);
362412
}
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+
}
363449
}

0 commit comments

Comments
 (0)