Repository: solana-labs/solana-program-library
Pull request #6406 (2024-03-12 12:10:45)

token-2022: Update burn to use Pod types
#### Problem

The Pod types exist in token-2022, but burn doesn't use them

#### Solution

Use Pod types in burn in token-2022


Patch:
diff --git a/libraries/tlv-account-resolution/src/account.rs b/libraries/tlv-account-resolution/src/account.rs
index 38b8c5605..83580abe5 100644
--- a/libraries/tlv-account-resolution/src/account.rs
+++ b/libraries/tlv-account-resolution/src/account.rs
@@ -215,8 +215,7 @@ impl TryFrom<&ExtraAccountMeta> for AccountMeta {
     fn try_from(pod: &ExtraAccountMeta) -> Result<Self, Self::Error> {
         if pod.discriminator == 0 {
             Ok(AccountMeta {
-                pubkey: Pubkey::try_from(pod.address_config)
-                    .map_err(|_| ProgramError::from(AccountResolutionError::InvalidPubkey))?,
+                pubkey: Pubkey::from(pod.address_config),
                 is_signer: pod.is_signer.into(),
                 is_writable: pod.is_writable.into(),
             })
diff --git a/token/program-2022/src/pod.rs b/token/program-2022/src/pod.rs
index 3920322bc..8b4d4ea14 100644
--- a/token/program-2022/src/pod.rs
+++ b/token/program-2022/src/pod.rs
@@ -88,6 +88,12 @@ impl PodAccount {
     pub fn is_native(&self) -> bool {
         self.is_native.is_some()
     }
+    /// Checks if a token Account's owner is the system_program or the
+    /// incinerator
+    pub fn is_owned_by_system_program_or_incinerator(&self) -> bool {
+        solana_program::system_program::check_id(&self.owner)
+            || solana_program::incinerator::check_id(&self.owner)
+    }
 }
 impl IsInitialized for PodAccount {
     fn is_initialized(&self) -> bool {
@@ -179,6 +185,16 @@ impl<T: Pod + Default> PodCOption<T> {
         }
     }
 
+    /// Get the underlying value or another provided value if it isn't set,
+    /// equivalent of `Option::unwrap_or`
+    pub fn unwrap_or(self, default: T) -> T {
+        if self.option == Self::NONE {
+            default
+        } else {
+            self.value
+        }
+    }
+
     /// Checks to see if a value is set, equivalent of `Option::is_some`
     pub fn is_some(&self) -> bool {
         self.option == Self::SOME
diff --git a/token/program-2022/src/processor.rs b/token/program-2022/src/processor.rs
index 027ed839d..f370894d0 100644
--- a/token/program-2022/src/processor.rs
+++ b/token/program-2022/src/processor.rs
@@ -45,7 +45,7 @@ use {
         system_instruction, system_program,
         sysvar::{rent::Rent, Sysvar},
     },
-    spl_pod::primitives::PodBool,
+    spl_pod::primitives::{PodBool, PodU64},
     spl_token_group_interface::instruction::TokenGroupInstruction,
     spl_token_metadata_interface::instruction::TokenMetadataInstruction,
     std::convert::{TryFrom, TryInto},
@@ -988,10 +988,10 @@ impl Processor {
         let authority_info_data_len = authority_info.data_len();
 
         let mut source_account_data = source_account_info.data.borrow_mut();
-        let mut source_account =
-            StateWithExtensionsMut::<Account>::unpack(&mut source_account_data)?;
+        let source_account =
+            PodStateWithExtensionsMut::<PodAccount>::unpack(&mut source_account_data)?;
         let mut mint_data = mint_info.data.borrow_mut();
-        let mut mint = StateWithExtensionsMut::<Mint>::unpack(&mut mint_data)?;
+        let mint = PodStateWithExtensionsMut::<PodMint>::unpack(&mut mint_data)?;
 
         if source_account.base.is_frozen() {
             return Err(TokenError::AccountFrozen.into());
@@ -999,7 +999,7 @@ impl Processor {
         if source_account.base.is_native() {
             return Err(TokenError::NativeNotSupported.into());
         }
-        if source_account.base.amount < amount {
+        if u64::from(source_account.base.amount) < amount {
             return Err(TokenError::InsufficientFunds.into());
         }
         if mint_info.key != &source_account.base.mint {
@@ -1017,7 +1017,7 @@ impl Processor {
             .base
             .is_owned_by_system_program_or_incinerator()
         {
-            match (source_account.base.delegate, maybe_permanent_delegate) {
+            match (&source_account.base.delegate, maybe_permanent_delegate) {
                 (_, Some(ref delegate)) if cmp_pubkeys(authority_info.key, delegate) => {
                     Self::validate_owner(
                         program_id,
@@ -1027,7 +1027,13 @@ impl Processor {
                         account_info_iter.as_slice(),
                     )?
                 }
-                (COption::Some(ref delegate), _) if cmp_pubkeys(authority_info.key, delegate) => {
+                (
+                    PodCOption {
+                        option: PodCOption::<Pubkey>::SOME,
+                        value: delegate,
+                    },
+                    _,
+                ) if cmp_pubkeys(authority_info.key, delegate) => {
                     Self::validate_owner(
                         program_id,
                         delegate,
@@ -1036,16 +1042,16 @@ impl Processor {
                         account_info_iter.as_slice(),
                     )?;
 
-                    if source_account.base.delegated_amount < amount {
+                    if u64::from(source_account.base.delegated_amount) < amount {
                         return Err(TokenError::InsufficientFunds.into());
                     }
-                    source_account.base.delegated_amount = source_account
-                        .base
-                        .delegated_amount
-                        .checked_sub(amount)
-                        .ok_or(TokenError::Overflow)?;
-                    if source_account.base.delegated_amount == 0 {
-                        source_account.base.delegate = COption::None;
+                    source_account.base.delegated_amount =
+                        u64::from(source_account.base.delegated_amount)
+                            .checked_sub(amount)
+                            .ok_or(TokenError::Overflow)?
+                            .into();
+                    if u64::from(source_account.base.delegated_amount) == 0 {
+                        source_account.base.delegate = PodCOption::none();
                     }
                 }
                 _ => {
@@ -1072,19 +1078,14 @@ impl Processor {
         check_program_account(source_account_info.owner)?;
         check_program_account(mint_info.owner)?;
 
-        source_account.base.amount = source_account
-            .base
-            .amount
+        source_account.base.amount = u64::from(source_account.base.amount)
             .checked_sub(amount)
-            .ok_or(TokenError::Overflow)?;
-        mint.base.supply = mint
-            .base
-            .supply
+            .ok_or(TokenError::Overflow)?
+            .into();
+        mint.base.supply = u64::from(mint.base.supply)
             .checked_sub(amount)
-            .ok_or(TokenError::Overflow)?;
-
-        source_account.pack_base();
-        mint.pack_base();
+            .ok_or(TokenError::Overflow)?
+            .into();
 
         Ok(())
     }
@@ -1102,8 +1103,10 @@ impl Processor {
         }
 
         let source_account_data = source_account_info.data.borrow();
-        if let Ok(source_account) = StateWithExtensions::<Account>::unpack(&source_account_data) {
-            if !source_account.base.is_native() && source_account.base.amount != 0 {
+        if let Ok(source_account) =
+            PodStateWithExtensions::<PodAccount>::unpack(&source_account_data)
+        {
+            if !source_account.base.is_native() && u64::from(source_account.base.amount) != 0 {
                 return Err(TokenError::NonNativeHasBalance.into());
             }
 
@@ -1151,7 +1154,7 @@ impl Processor {
             if let Ok(transfer_fee_state) = source_account.get_extension::<TransferFeeAmount>() {
                 transfer_fee_state.closable()?
             }
-        } else if let Ok(mint) = StateWithExtensions::<Mint>::unpack(&source_account_data) {
+        } else if let Ok(mint) = PodStateWithExtensions::<PodMint>::unpack(&source_account_data) {
             let extension = mint.get_extension::<MintCloseAuthority>()?;
             let maybe_authority: Option<Pubkey> = extension.close_authority.into();
             let authority = maybe_authority.ok_or(TokenError::AuthorityTypeNotSupported)?;
@@ -1163,7 +1166,7 @@ impl Processor {
                 account_info_iter.as_slice(),
             )?;
 
-            if mint.base.supply != 0 {
+            if u64::from(mint.base.supply) != 0 {
                 return Err(TokenError::MintHasSupply.into());
             }
         } else {
@@ -1196,8 +1199,8 @@ impl Processor {
         let authority_info_data_len = authority_info.data_len();
 
         let mut source_account_data = source_account_info.data.borrow_mut();
-        let mut source_account =
-            StateWithExtensionsMut::<Account>::unpack(&mut source_account_data)?;
+        let source_account =
+            PodStateWithExtensionsMut::<PodAccount>::unpack(&mut source_account_data)?;
         if freeze && source_account.base.is_frozen() || !freeze && !source_account.base.is_frozen()
         {
             return Err(TokenError::InvalidState.into());
@@ -1210,26 +1213,27 @@ impl Processor {
         }
 
         let mint_data = mint_info.data.borrow();
-        let mint = StateWithExtensions::<Mint>::unpack(&mint_data)?;
-        match mint.base.freeze_authority {
-            COption::Some(authority) => Self::validate_owner(
+        let mint = PodStateWithExtensions::<PodMint>::unpack(&mint_data)?;
+        match &mint.base.freeze_authority {
+            PodCOption {
+                option: PodCOption::<Pubkey>::SOME,
+                value: authority,
+            } => Self::validate_owner(
                 program_id,
-                &authority,
+                authority,
                 authority_info,
                 authority_info_data_len,
                 account_info_iter.as_slice(),
             ),
-            COption::None => Err(TokenError::MintCannotFreeze.into()),
+            _ => Err(TokenError::MintCannotFreeze.into()),
         }?;
 
         source_account.base.state = if freeze {
-            AccountState::Frozen
+            AccountState::Frozen.into()
         } else {
-            AccountState::Initialized
+            AccountState::Initialized.into()
         };
 
-        source_account.pack_base();
-
         Ok(())
     }
 
@@ -1240,23 +1244,26 @@ impl Processor {
 
         check_program_account(native_account_info.owner)?;
         let mut native_account_data = native_account_info.data.borrow_mut();
-        let mut native_account =
-            StateWithExtensionsMut::<Account>::unpack(&mut native_account_data)?;
+        let native_account =
+            PodStateWithExtensionsMut::<PodAccount>::unpack(&mut native_account_data)?;
 
-        if let COption::Some(rent_exempt_reserve) = native_account.base.is_native {
-            let new_amount = native_account_info
-                .lamports()
-                .checked_sub(rent_exempt_reserve)
-                .ok_or(TokenError::Overflow)?;
-            if new_amount < native_account.base.amount {
-                return Err(TokenError::InvalidState.into());
+        match native_account.base.is_native {
+            PodCOption {
+                option: PodCOption::<PodU64>::SOME,
+                value: amount,
+            } => {
+                let new_amount = native_account_info
+                    .lamports()
+                    .checked_sub(u64::from(amount))
+                    .ok_or(TokenError::Overflow)?;
+                if new_amount < u64::from(native_account.base.amount) {
+                    return Err(TokenError::InvalidState.into());
+                }
+                native_account.base.amount = new_amount.into();
             }
-            native_account.base.amount = new_amount;
-        } else {
-            return Err(TokenError::NonNativeNotSupported.into());
+            _ => return Err(TokenError::NonNativeNotSupported.into()),
         }
 
-        native_account.pack_base();
         Ok(())
     }
 
@@ -1310,7 +1317,7 @@ impl Processor {
         let token_account_info = next_account_info(account_info_iter)?;
         let token_account_data = &mut token_account_info.data.borrow_mut();
         let mut token_account =
-            StateWithExtensionsMut::<Account>::unpack_uninitialized(token_account_data)?;
+            PodStateWithExtensionsMut::<PodAccount>::unpack_uninitialized(token_account_data)?;
         token_account
             .init_extension::<ImmutableOwner>(true)
             .map(|_| ())
diff --git a/token/transfer-hook/cli/src/main.rs b/token/transfer-hook/cli/src/main.rs
index 237b47e2a..d611ba5cd 100644
--- a/token/transfer-hook/cli/src/main.rs
+++ b/token/transfer-hook/cli/src/main.rs
@@ -51,7 +51,7 @@ async fn build_transaction_with_rent_transfer(
     rpc_client: &RpcClient,
     payer: &dyn Signer,
     extra_account_metas_address: &Pubkey,
-    extra_account_metas: &Vec<ExtraAccountMeta>,
+    extra_account_metas: &[ExtraAccountMeta],
     instruction: Instruction,
 ) -> Result<Transaction, Box<dyn std::error::Error>> {
     let account_size = ExtraAccountMetaList::size_of(extra_account_metas.len())?;


Test patch:
diff --git a/token/program-2022/tests/assert_instruction_count.rs b/token/program-2022/tests/assert_instruction_count.rs
index 960aad4d6..7df8664eb 100644
--- a/token/program-2022/tests/assert_instruction_count.rs
+++ b/token/program-2022/tests/assert_instruction_count.rs
@@ -299,7 +299,7 @@ async fn transfer_checked() {
 #[tokio::test]
 async fn burn() {
     let mut pt = ProgramTest::new("spl_token_2022", id(), processor!(Processor::process));
-    pt.set_compute_max_units(8_000); // last known 2860
+    pt.set_compute_max_units(8_000); // last known 1395
     let (mut banks_client, payer, recent_blockhash) = pt.start().await;
 
     let owner = Keypair::new();
@@ -356,7 +356,7 @@ async fn burn() {
 #[tokio::test]
 async fn close_account() {
     let mut pt = ProgramTest::new("spl_token_2022", id(), processor!(Processor::process));
-    pt.set_compute_max_units(8_000); // last known 1990
+    pt.set_compute_max_units(8_000); // last known 1500
     let (mut banks_client, payer, recent_blockhash) = pt.start().await;
 
     let owner = Keypair::new();