use crate::{account::AccountView, address::Address, error::ProgramError, result::ProgramResult};
pub const EXT_UNINITIALIZED: u16 = 0;
pub const EXT_TRANSFER_FEE_CONFIG: u16 = 1;
pub const EXT_TRANSFER_FEE_AMOUNT: u16 = 2;
pub const EXT_MINT_CLOSE_AUTHORITY: u16 = 3;
pub const EXT_CONFIDENTIAL_TRANSFER_MINT: u16 = 4;
pub const EXT_CONFIDENTIAL_TRANSFER_ACCOUNT: u16 = 5;
pub const EXT_DEFAULT_ACCOUNT_STATE: u16 = 6;
pub const EXT_IMMUTABLE_OWNER: u16 = 7;
pub const EXT_MEMO_TRANSFER: u16 = 8;
pub const EXT_NON_TRANSFERABLE: u16 = 9;
pub const EXT_INTEREST_BEARING_CONFIG: u16 = 10;
pub const EXT_CPI_GUARD: u16 = 11;
pub const EXT_PERMANENT_DELEGATE: u16 = 12;
pub const EXT_NON_TRANSFERABLE_ACCOUNT: u16 = 13;
pub const EXT_TRANSFER_HOOK: u16 = 14;
pub const EXT_TRANSFER_HOOK_ACCOUNT: u16 = 15;
pub const EXT_CONFIDENTIAL_TRANSFER_FEE_CONFIG: u16 = 16;
pub const EXT_CONFIDENTIAL_TRANSFER_FEE_AMOUNT: u16 = 17;
pub const EXT_METADATA_POINTER: u16 = 18;
pub const EXT_TOKEN_METADATA: u16 = 19;
pub const EXT_GROUP_POINTER: u16 = 20;
pub const EXT_TOKEN_GROUP: u16 = 21;
pub const EXT_GROUP_MEMBER_POINTER: u16 = 22;
pub const EXT_TOKEN_GROUP_MEMBER: u16 = 23;
pub const EXT_CONFIDENTIAL_MINT_BURN: u16 = 24;
pub const EXT_SCALED_UI_AMOUNT_CONFIG: u16 = 25;
pub const EXT_PAUSABLE_CONFIG: u16 = 26;
pub const EXT_PAUSABLE_ACCOUNT: u16 = 27;
pub const EXT_PERMISSIONED_BURN: u16 = 28;
pub const MAX_KNOWN_EXTENSION_TYPE: u16 = EXT_PERMISSIONED_BURN;
pub const ACCOUNT_TYPE_MINT: u8 = 0x01;
pub const ACCOUNT_TYPE_TOKEN: u8 = 0x02;
pub const BASE_MINT_LEN: usize = 82;
pub const BASE_TOKEN_LEN: usize = 165;
pub const TOKEN_MULTISIG_LEN: usize = 355;
pub const ACCOUNT_TYPE_OFFSET: usize = BASE_TOKEN_LEN;
pub const TLV_OFFSET: usize = ACCOUNT_TYPE_OFFSET + 1;
pub const MINT_EXTENSION_PADDING_START: usize = BASE_MINT_LEN;
pub const MINT_EXTENSION_PADDING_END: usize = ACCOUNT_TYPE_OFFSET;
#[inline]
pub fn find_extension(tlv_bytes: &[u8], ext_type: u16) -> Option<&[u8]> {
let mut cursor = 0usize;
while cursor + 4 <= tlv_bytes.len() {
let t = u16::from_le_bytes([tlv_bytes[cursor], tlv_bytes[cursor + 1]]);
let len = u16::from_le_bytes([tlv_bytes[cursor + 2], tlv_bytes[cursor + 3]]) as usize;
let data_start = cursor + 4;
let data_end = data_start + len;
if data_end > tlv_bytes.len() {
return None;
}
if t == ext_type {
return Some(&tlv_bytes[data_start..data_end]);
}
if t == EXT_UNINITIALIZED {
return None;
}
cursor = data_end;
}
None
}
pub fn validate_tlv_structure(tlv_bytes: &[u8]) -> ProgramResult {
let mut cursor = 0usize;
while cursor < tlv_bytes.len() {
if tlv_bytes.len() - cursor < 2 {
return Ok(());
}
let ext_type = u16::from_le_bytes([tlv_bytes[cursor], tlv_bytes[cursor + 1]]);
if ext_type == EXT_UNINITIALIZED {
return Ok(());
}
if tlv_bytes.len() - cursor < 4 {
return Err(ProgramError::InvalidAccountData);
}
let len = u16::from_le_bytes([tlv_bytes[cursor + 2], tlv_bytes[cursor + 3]]) as usize;
cursor = cursor
.checked_add(4)
.and_then(|start| start.checked_add(len))
.ok_or(ProgramError::InvalidAccountData)?;
if cursor > tlv_bytes.len() {
return Err(ProgramError::InvalidAccountData);
}
}
Ok(())
}
pub fn validate_extension_allowlist(tlv_bytes: &[u8], allowed: &[u16]) -> ProgramResult {
let mut cursor = 0usize;
let mut seen = 0u32;
while cursor < tlv_bytes.len() {
if tlv_bytes.len() - cursor < 2 {
return Ok(());
}
let ext_type = u16::from_le_bytes([tlv_bytes[cursor], tlv_bytes[cursor + 1]]);
if ext_type == EXT_UNINITIALIZED {
return Ok(());
}
if ext_type > MAX_KNOWN_EXTENSION_TYPE || !allowed.contains(&ext_type) {
return Err(ProgramError::InvalidAccountData);
}
let bit = 1u32
.checked_shl(ext_type as u32)
.ok_or(ProgramError::InvalidAccountData)?;
if seen & bit != 0 {
return Err(ProgramError::InvalidAccountData);
}
seen |= bit;
if tlv_bytes.len() - cursor < 4 {
return Err(ProgramError::InvalidAccountData);
}
let len = u16::from_le_bytes([tlv_bytes[cursor + 2], tlv_bytes[cursor + 3]]) as usize;
cursor = cursor
.checked_add(4)
.and_then(|start| start.checked_add(len))
.ok_or(ProgramError::InvalidAccountData)?;
if cursor > tlv_bytes.len() {
return Err(ProgramError::InvalidAccountData);
}
}
Ok(())
}
#[inline]
pub fn mint_tlv_region(data: &[u8]) -> Option<&[u8]> {
if data.len() <= TLV_OFFSET || data.len() == TOKEN_MULTISIG_LEN {
return None;
}
let kind = data[ACCOUNT_TYPE_OFFSET];
if kind != ACCOUNT_TYPE_MINT && kind != 0 {
return None;
}
if data[MINT_EXTENSION_PADDING_START..MINT_EXTENSION_PADDING_END]
.iter()
.any(|byte| *byte != 0)
{
return None;
}
Some(&data[TLV_OFFSET..])
}
#[inline]
pub fn token_account_tlv_region(data: &[u8]) -> Option<&[u8]> {
if data.len() <= TLV_OFFSET || data.len() == TOKEN_MULTISIG_LEN {
return None;
}
let kind = data[ACCOUNT_TYPE_OFFSET];
if kind != ACCOUNT_TYPE_TOKEN && kind != 0 {
return None;
}
Some(&data[TLV_OFFSET..])
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ExtensionPolicy<'a> {
pub required: &'a [u16],
pub forbidden: &'a [u16],
}
impl<'a> ExtensionPolicy<'a> {
#[inline]
pub const fn new(required: &'a [u16], forbidden: &'a [u16]) -> Self {
Self {
required,
forbidden,
}
}
}
#[inline]
pub fn require_extension(tlv: &[u8], ext_type: u16) -> ProgramResult {
validate_tlv_structure(tlv)?;
if find_extension(tlv, ext_type).is_some() {
Ok(())
} else {
Err(ProgramError::InvalidAccountData)
}
}
#[inline]
pub fn forbid_extension(tlv: &[u8], ext_type: u16) -> ProgramResult {
validate_tlv_structure(tlv)?;
if find_extension(tlv, ext_type).is_none() {
Ok(())
} else {
Err(ProgramError::InvalidAccountData)
}
}
#[inline]
pub fn validate_extension_policy(tlv: &[u8], policy: &ExtensionPolicy<'_>) -> ProgramResult {
validate_tlv_structure(tlv)?;
for ext_type in policy.required {
if find_extension(tlv, *ext_type).is_none() {
return Err(ProgramError::InvalidAccountData);
}
}
for ext_type in policy.forbidden {
if find_extension(tlv, *ext_type).is_some() {
return Err(ProgramError::InvalidAccountData);
}
}
Ok(())
}
#[inline]
pub fn require_non_transferable(mint: &AccountView<'_>) -> ProgramResult {
let data = mint
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
let tlv = mint_tlv_region(&data).ok_or(ProgramError::InvalidAccountData)?;
if find_extension(tlv, EXT_NON_TRANSFERABLE).is_some() {
Ok(())
} else {
Err(ProgramError::InvalidAccountData)
}
}
#[inline]
pub fn require_mint_close_authority(mint: &AccountView<'_>, expected: &Address) -> ProgramResult {
let data = mint
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
let tlv = mint_tlv_region(&data).ok_or(ProgramError::InvalidAccountData)?;
let ext =
find_extension(tlv, EXT_MINT_CLOSE_AUTHORITY).ok_or(ProgramError::InvalidAccountData)?;
if ext.len() < 32 {
return Err(ProgramError::InvalidAccountData);
}
if crate::address::keys_eq_bytes(&ext[..32], expected.as_array()) {
Ok(())
} else {
Err(ProgramError::IncorrectAuthority)
}
}
#[inline]
pub fn require_permanent_delegate(mint: &AccountView<'_>, expected: &Address) -> ProgramResult {
let data = mint
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
let tlv = mint_tlv_region(&data).ok_or(ProgramError::InvalidAccountData)?;
let ext =
find_extension(tlv, EXT_PERMANENT_DELEGATE).ok_or(ProgramError::InvalidAccountData)?;
if ext.len() < 32 {
return Err(ProgramError::InvalidAccountData);
}
if crate::address::keys_eq_bytes(&ext[..32], expected.as_array()) {
Ok(())
} else {
Err(ProgramError::IncorrectAuthority)
}
}
#[inline]
pub fn require_transfer_hook_program(mint: &AccountView<'_>, expected: &Address) -> ProgramResult {
let data = mint
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
let tlv = mint_tlv_region(&data).ok_or(ProgramError::InvalidAccountData)?;
let ext = find_extension(tlv, EXT_TRANSFER_HOOK).ok_or(ProgramError::InvalidAccountData)?;
if ext.len() < 64 {
return Err(ProgramError::InvalidAccountData);
}
if crate::address::keys_eq_bytes(&ext[32..64], expected.as_array()) {
Ok(())
} else {
Err(ProgramError::IncorrectProgramId)
}
}
#[inline]
pub fn require_transfer_hook_authority(
mint: &AccountView<'_>,
expected: &Address,
) -> ProgramResult {
let data = mint
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
let tlv = mint_tlv_region(&data).ok_or(ProgramError::InvalidAccountData)?;
let ext = find_extension(tlv, EXT_TRANSFER_HOOK).ok_or(ProgramError::InvalidAccountData)?;
if ext.len() < 32 {
return Err(ProgramError::InvalidAccountData);
}
if crate::address::keys_eq_bytes(&ext[..32], expected.as_array()) {
Ok(())
} else {
Err(ProgramError::IncorrectAuthority)
}
}
#[inline]
pub fn require_metadata_pointer_address(
mint: &AccountView<'_>,
expected: &Address,
) -> ProgramResult {
let data = mint
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
let tlv = mint_tlv_region(&data).ok_or(ProgramError::InvalidAccountData)?;
let ext = find_extension(tlv, EXT_METADATA_POINTER).ok_or(ProgramError::InvalidAccountData)?;
if ext.len() < 64 {
return Err(ProgramError::InvalidAccountData);
}
if crate::address::keys_eq_bytes(&ext[32..64], expected.as_array()) {
Ok(())
} else {
Err(ProgramError::InvalidAccountData)
}
}
#[inline]
pub fn require_metadata_pointer_authority(
mint: &AccountView<'_>,
expected: &Address,
) -> ProgramResult {
let data = mint
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
let tlv = mint_tlv_region(&data).ok_or(ProgramError::InvalidAccountData)?;
let ext = find_extension(tlv, EXT_METADATA_POINTER).ok_or(ProgramError::InvalidAccountData)?;
if ext.len() < 32 {
return Err(ProgramError::InvalidAccountData);
}
if crate::address::keys_eq_bytes(&ext[..32], expected.as_array()) {
Ok(())
} else {
Err(ProgramError::IncorrectAuthority)
}
}
#[inline]
pub fn require_immutable_owner(token_account: &AccountView<'_>) -> ProgramResult {
let data = token_account
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
let tlv = token_account_tlv_region(&data).ok_or(ProgramError::InvalidAccountData)?;
require_extension(tlv, EXT_IMMUTABLE_OWNER)
}
#[inline]
pub fn require_cpi_guard(token_account: &AccountView<'_>) -> ProgramResult {
let data = token_account
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
let tlv = token_account_tlv_region(&data).ok_or(ProgramError::InvalidAccountData)?;
require_extension(tlv, EXT_CPI_GUARD)
}
#[inline]
pub fn require_confidential_transfer_mint(mint: &AccountView<'_>) -> ProgramResult {
let data = mint
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
let tlv = mint_tlv_region(&data).ok_or(ProgramError::InvalidAccountData)?;
require_extension(tlv, EXT_CONFIDENTIAL_TRANSFER_MINT)
}
#[inline]
pub fn require_confidential_transfer_account(token_account: &AccountView<'_>) -> ProgramResult {
let data = token_account
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
let tlv = token_account_tlv_region(&data).ok_or(ProgramError::InvalidAccountData)?;
require_extension(tlv, EXT_CONFIDENTIAL_TRANSFER_ACCOUNT)
}
#[inline]
pub fn require_scaled_ui_amount_config(mint: &AccountView<'_>) -> ProgramResult {
let data = mint
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
let tlv = mint_tlv_region(&data).ok_or(ProgramError::InvalidAccountData)?;
require_extension(tlv, EXT_SCALED_UI_AMOUNT_CONFIG)
}
#[inline]
pub fn require_default_account_state(mint: &AccountView<'_>, expected: u8) -> ProgramResult {
let data = mint
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
let tlv = mint_tlv_region(&data).ok_or(ProgramError::InvalidAccountData)?;
let ext =
find_extension(tlv, EXT_DEFAULT_ACCOUNT_STATE).ok_or(ProgramError::InvalidAccountData)?;
if ext.is_empty() {
return Err(ProgramError::InvalidAccountData);
}
if ext[0] == expected {
Ok(())
} else {
Err(ProgramError::InvalidAccountData)
}
}
#[inline]
pub fn require_interest_bearing_authority(
mint: &AccountView<'_>,
expected: &Address,
) -> ProgramResult {
let data = mint
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
let tlv = mint_tlv_region(&data).ok_or(ProgramError::InvalidAccountData)?;
let ext =
find_extension(tlv, EXT_INTEREST_BEARING_CONFIG).ok_or(ProgramError::InvalidAccountData)?;
if ext.len() < 32 {
return Err(ProgramError::InvalidAccountData);
}
if crate::address::keys_eq_bytes(&ext[..32], expected.as_array()) {
Ok(())
} else {
Err(ProgramError::IncorrectAuthority)
}
}
#[inline]
pub fn require_transfer_fee_config_authority(
mint: &AccountView<'_>,
expected: &Address,
) -> ProgramResult {
let data = mint
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
let tlv = mint_tlv_region(&data).ok_or(ProgramError::InvalidAccountData)?;
let ext =
find_extension(tlv, EXT_TRANSFER_FEE_CONFIG).ok_or(ProgramError::InvalidAccountData)?;
if ext.len() < 32 {
return Err(ProgramError::InvalidAccountData);
}
if crate::address::keys_eq_bytes(&ext[..32], expected.as_array()) {
Ok(())
} else {
Err(ProgramError::IncorrectAuthority)
}
}
#[inline]
pub fn require_transfer_fee_withdraw_authority(
mint: &AccountView<'_>,
expected: &Address,
) -> ProgramResult {
let data = mint
.try_borrow()
.map_err(|_| ProgramError::AccountBorrowFailed)?;
let tlv = mint_tlv_region(&data).ok_or(ProgramError::InvalidAccountData)?;
let ext =
find_extension(tlv, EXT_TRANSFER_FEE_CONFIG).ok_or(ProgramError::InvalidAccountData)?;
if ext.len() < 64 {
return Err(ProgramError::InvalidAccountData);
}
if crate::address::keys_eq_bytes(&ext[32..64], expected.as_array()) {
Ok(())
} else {
Err(ProgramError::IncorrectAuthority)
}
}
#[cfg(test)]
mod tests {
extern crate alloc;
use super::*;
fn mint_with_exts(exts: &[(u16, &[u8])]) -> alloc::vec::Vec<u8> {
let mut v = alloc::vec![0u8; ACCOUNT_TYPE_OFFSET];
v.push(ACCOUNT_TYPE_MINT);
for (ty, payload) in exts {
v.extend_from_slice(&ty.to_le_bytes());
v.extend_from_slice(&(payload.len() as u16).to_le_bytes());
v.extend_from_slice(payload);
}
debug_assert!(v.len() > TLV_OFFSET);
v
}
fn one_ext_mint(ext_type: u16, payload: &[u8]) -> alloc::vec::Vec<u8> {
mint_with_exts(&[(ext_type, payload)])
}
fn token_account_with_exts(exts: &[(u16, &[u8])]) -> alloc::vec::Vec<u8> {
let mut v = alloc::vec![0u8; BASE_TOKEN_LEN];
v.push(ACCOUNT_TYPE_TOKEN);
for (ty, payload) in exts {
v.extend_from_slice(&ty.to_le_bytes());
v.extend_from_slice(&(payload.len() as u16).to_le_bytes());
v.extend_from_slice(payload);
}
v
}
#[test]
fn offset_constants_match_authoritative_spec() {
assert_eq!(BASE_MINT_LEN, 82);
assert_eq!(BASE_TOKEN_LEN, 165);
assert_eq!(ACCOUNT_TYPE_OFFSET, 165);
assert_eq!(TLV_OFFSET, 166);
assert_eq!(MINT_EXTENSION_PADDING_START, 82);
assert_eq!(MINT_EXTENSION_PADDING_END, 165);
assert_eq!(TOKEN_MULTISIG_LEN, 355);
assert_eq!(ACCOUNT_TYPE_MINT, 0x01);
assert_eq!(ACCOUNT_TYPE_TOKEN, 0x02);
assert_eq!(EXT_CONFIDENTIAL_MINT_BURN, 24);
assert_eq!(EXT_SCALED_UI_AMOUNT_CONFIG, 25);
assert_eq!(EXT_PAUSABLE_CONFIG, 26);
assert_eq!(EXT_PAUSABLE_ACCOUNT, 27);
assert_eq!(EXT_PERMISSIONED_BURN, 28);
}
#[test]
fn real_layout_mint_tlv_region_starts_at_166() {
let data = one_ext_mint(EXT_NON_TRANSFERABLE, &[]);
let tlv = mint_tlv_region(&data).expect("extended mint must yield TLV region");
assert_eq!(tlv.as_ptr() as usize - data.as_ptr() as usize, TLV_OFFSET);
assert_eq!(u16::from_le_bytes([tlv[0], tlv[1]]), EXT_NON_TRANSFERABLE);
assert_eq!(u16::from_le_bytes([tlv[2], tlv[3]]), 0);
}
#[test]
fn real_layout_mint_padding_is_not_treated_as_tlv() {
let data = one_ext_mint(EXT_TRANSFER_HOOK, &[0u8; 64]);
let tlv = mint_tlv_region(&data).expect("tlv region");
assert!(find_extension(tlv, EXT_TRANSFER_HOOK).is_some());
}
#[test]
fn find_extension_returns_payload_slice() {
let data = one_ext_mint(EXT_NON_TRANSFERABLE, &[]);
let tlv = mint_tlv_region(&data).unwrap();
assert!(find_extension(tlv, EXT_NON_TRANSFERABLE).is_some());
}
#[test]
fn find_extension_returns_none_when_absent() {
let data = one_ext_mint(EXT_NON_TRANSFERABLE, &[]);
let tlv = mint_tlv_region(&data).unwrap();
assert!(find_extension(tlv, EXT_TRANSFER_HOOK).is_none());
}
#[test]
fn find_extension_bails_on_malformed_length() {
let mut data = alloc::vec![0u8; ACCOUNT_TYPE_OFFSET];
data.push(ACCOUNT_TYPE_MINT);
data.extend_from_slice(&EXT_TRANSFER_HOOK.to_le_bytes());
data.extend_from_slice(&999u16.to_le_bytes());
let tlv = mint_tlv_region(&data).unwrap();
assert!(find_extension(tlv, EXT_TRANSFER_HOOK).is_none());
assert_eq!(
forbid_extension(tlv, EXT_TRANSFER_HOOK),
Err(ProgramError::InvalidAccountData),
"a malformed forbidden extension must not look absent",
);
}
#[test]
fn allowlist_rejects_unknown_duplicate_and_truncated_entries() {
let allowed = [EXT_METADATA_POINTER];
let known = mint_with_exts(&[(EXT_METADATA_POINTER, &[0u8; 64])]);
validate_extension_allowlist(mint_tlv_region(&known).unwrap(), &allowed).unwrap();
let unknown = mint_with_exts(&[(MAX_KNOWN_EXTENSION_TYPE + 1, &[])]);
assert_eq!(
validate_extension_allowlist(mint_tlv_region(&unknown).unwrap(), &allowed),
Err(ProgramError::InvalidAccountData),
);
let duplicate = mint_with_exts(&[
(EXT_METADATA_POINTER, &[0u8; 64]),
(EXT_METADATA_POINTER, &[0u8; 64]),
]);
assert_eq!(
validate_extension_allowlist(mint_tlv_region(&duplicate).unwrap(), &allowed),
Err(ProgramError::InvalidAccountData),
);
let truncated = [EXT_METADATA_POINTER as u8, 0, 4, 0, 1];
assert_eq!(
validate_extension_allowlist(&truncated, &allowed),
Err(ProgramError::InvalidAccountData),
);
}
#[test]
fn find_extension_finds_second_entry() {
let data = mint_with_exts(&[
(EXT_METADATA_POINTER, &[1u8; 64]),
(EXT_PERMANENT_DELEGATE, &[2u8; 32]),
]);
let tlv = mint_tlv_region(&data).unwrap();
let perm = find_extension(tlv, EXT_PERMANENT_DELEGATE).unwrap();
assert_eq!(perm, &[2u8; 32]);
}
#[test]
fn extension_policy_requires_and_forbids_extensions() {
let data = mint_with_exts(&[
(EXT_CONFIDENTIAL_TRANSFER_MINT, &[0u8; 1]),
(EXT_SCALED_UI_AMOUNT_CONFIG, &[0u8; 1]),
]);
let tlv = mint_tlv_region(&data).unwrap();
let policy = ExtensionPolicy::new(
&[EXT_CONFIDENTIAL_TRANSFER_MINT, EXT_SCALED_UI_AMOUNT_CONFIG],
&[EXT_TRANSFER_HOOK],
);
validate_extension_policy(tlv, &policy).unwrap();
let rejected = ExtensionPolicy::new(
&[EXT_CONFIDENTIAL_TRANSFER_MINT],
&[EXT_SCALED_UI_AMOUNT_CONFIG],
);
assert_eq!(
validate_extension_policy(tlv, &rejected),
Err(ProgramError::InvalidAccountData)
);
}
#[test]
fn token_account_policy_sees_cpi_guard_and_confidential_account() {
let data = token_account_with_exts(&[
(EXT_CPI_GUARD, &[]),
(EXT_CONFIDENTIAL_TRANSFER_ACCOUNT, &[0u8; 1]),
]);
let tlv = token_account_tlv_region(&data).unwrap();
validate_extension_policy(
tlv,
&ExtensionPolicy::new(&[EXT_CPI_GUARD, EXT_CONFIDENTIAL_TRANSFER_ACCOUNT], &[]),
)
.unwrap();
}
#[test]
fn mint_tlv_region_rejects_short_account() {
let data = alloc::vec![0u8; 40];
assert!(mint_tlv_region(&data).is_none());
let data = alloc::vec![0u8; TLV_OFFSET];
assert!(mint_tlv_region(&data).is_none());
}
#[test]
fn mint_tlv_region_rejects_wrong_account_kind() {
let mut data = alloc::vec![0u8; ACCOUNT_TYPE_OFFSET];
data.push(ACCOUNT_TYPE_TOKEN);
data.push(0); assert!(mint_tlv_region(&data).is_none());
}
#[test]
fn tlv_regions_reject_multisig_collision_and_dirty_mint_padding() {
let mut multisig = alloc::vec![0u8; TOKEN_MULTISIG_LEN];
multisig[ACCOUNT_TYPE_OFFSET] = ACCOUNT_TYPE_MINT;
assert!(mint_tlv_region(&multisig).is_none());
multisig[ACCOUNT_TYPE_OFFSET] = ACCOUNT_TYPE_TOKEN;
assert!(token_account_tlv_region(&multisig).is_none());
let mut dirty_mint = one_ext_mint(EXT_NON_TRANSFERABLE, &[]);
dirty_mint[MINT_EXTENSION_PADDING_START] = 1;
assert!(mint_tlv_region(&dirty_mint).is_none());
}
#[test]
fn mint_tlv_region_accepts_zero_kind_byte() {
let mut data = alloc::vec![0u8; ACCOUNT_TYPE_OFFSET];
data.push(0u8);
data.push(0); assert!(mint_tlv_region(&data).is_some());
}
#[test]
fn token_account_tlv_region_accepts_zero_kind_byte() {
let mut data = alloc::vec![0u8; BASE_TOKEN_LEN];
data.push(0u8);
data.push(0); assert!(token_account_tlv_region(&data).is_some());
}
#[test]
fn token_account_tlv_region_rejects_mint_kind() {
let mut data = alloc::vec![0u8; BASE_TOKEN_LEN];
data.push(ACCOUNT_TYPE_MINT);
data.push(0);
assert!(token_account_tlv_region(&data).is_none());
}
#[test]
fn token_account_tlv_region_returns_real_tlv() {
let data = token_account_with_exts(&[(EXT_IMMUTABLE_OWNER, &[])]);
let tlv = token_account_tlv_region(&data).unwrap();
assert_eq!(tlv.as_ptr() as usize - data.as_ptr() as usize, TLV_OFFSET);
assert!(find_extension(tlv, EXT_IMMUTABLE_OWNER).is_some());
}
}