Browse Source

token-2022: Allow anyone to burn/close an Account owned by the system program or the incinerator (#2890)

* Allow anyone to burn and close token Accounts owned by the system program and the incinerator

* Require rent from incinerator/system-owned token accounts be burnt when accounts closed

* Add support to OG program
Tyera Eulberg 3 years ago
parent
commit
e2d0291eeb
2 changed files with 298 additions and 28 deletions
  1. 293 28
      program/src/processor.rs
  2. 5 0
      program/src/state.rs

+ 293 - 28
program/src/processor.rs

@@ -612,32 +612,34 @@ impl Processor {
             }
             }
         }
         }
 
 
-        match source_account.delegate {
-            COption::Some(ref delegate) if Self::cmp_pubkeys(authority_info.key, delegate) => {
-                Self::validate_owner(
+        if !source_account.is_owned_by_system_program_or_incinerator() {
+            match source_account.delegate {
+                COption::Some(ref delegate) if Self::cmp_pubkeys(authority_info.key, delegate) => {
+                    Self::validate_owner(
+                        program_id,
+                        delegate,
+                        authority_info,
+                        account_info_iter.as_slice(),
+                    )?;
+
+                    if source_account.delegated_amount < amount {
+                        return Err(TokenError::InsufficientFunds.into());
+                    }
+                    source_account.delegated_amount = source_account
+                        .delegated_amount
+                        .checked_sub(amount)
+                        .ok_or(TokenError::Overflow)?;
+                    if source_account.delegated_amount == 0 {
+                        source_account.delegate = COption::None;
+                    }
+                }
+                _ => Self::validate_owner(
                     program_id,
                     program_id,
-                    delegate,
+                    &source_account.owner,
                     authority_info,
                     authority_info,
                     account_info_iter.as_slice(),
                     account_info_iter.as_slice(),
-                )?;
-
-                if source_account.delegated_amount < amount {
-                    return Err(TokenError::InsufficientFunds.into());
-                }
-                source_account.delegated_amount = source_account
-                    .delegated_amount
-                    .checked_sub(amount)
-                    .ok_or(TokenError::Overflow)?;
-                if source_account.delegated_amount == 0 {
-                    source_account.delegate = COption::None;
-                }
+                )?,
             }
             }
-            _ => Self::validate_owner(
-                program_id,
-                &source_account.owner,
-                authority_info,
-                account_info_iter.as_slice(),
-            )?,
         }
         }
 
 
         if amount == 0 {
         if amount == 0 {
@@ -679,12 +681,16 @@ impl Processor {
         let authority = source_account
         let authority = source_account
             .close_authority
             .close_authority
             .unwrap_or(source_account.owner);
             .unwrap_or(source_account.owner);
-        Self::validate_owner(
-            program_id,
-            &authority,
-            authority_info,
-            account_info_iter.as_slice(),
-        )?;
+        if !source_account.is_owned_by_system_program_or_incinerator() {
+            Self::validate_owner(
+                program_id,
+                &authority,
+                authority_info,
+                account_info_iter.as_slice(),
+            )?;
+        } else if !solana_program::incinerator::check_id(destination_account_info.key) {
+            return Err(ProgramError::InvalidAccountData);
+        }
 
 
         let destination_starting_lamports = destination_account_info.lamports();
         let destination_starting_lamports = destination_account_info.lamports();
         **destination_account_info.lamports.borrow_mut() = destination_starting_lamports
         **destination_account_info.lamports.borrow_mut() = destination_starting_lamports
@@ -4575,6 +4581,265 @@ mod tests {
         );
         );
     }
     }
 
 
+    #[test]
+    fn test_burn_and_close_system_and_incinerator_tokens() {
+        let program_id = crate::id();
+        let account_key = Pubkey::new_unique();
+        let mut account_account = SolanaAccount::new(
+            account_minimum_balance(),
+            Account::get_packed_len(),
+            &program_id,
+        );
+        let incinerator_account_key = Pubkey::new_unique();
+        let mut incinerator_account = SolanaAccount::new(
+            account_minimum_balance(),
+            Account::get_packed_len(),
+            &program_id,
+        );
+        let system_account_key = Pubkey::new_unique();
+        let mut system_account = SolanaAccount::new(
+            account_minimum_balance(),
+            Account::get_packed_len(),
+            &program_id,
+        );
+        let owner_key = Pubkey::new_unique();
+        let mut owner_account = SolanaAccount::default();
+        let recipient_key = Pubkey::new_unique();
+        let mut recipient_account = SolanaAccount::default();
+        let mut mock_incinerator_account = SolanaAccount::default();
+        let mint_key = Pubkey::new_unique();
+        let mut mint_account =
+            SolanaAccount::new(mint_minimum_balance(), Mint::get_packed_len(), &program_id);
+
+        // create new mint
+        do_process_instruction(
+            initialize_mint2(&program_id, &mint_key, &owner_key, None, 2).unwrap(),
+            vec![&mut mint_account],
+        )
+        .unwrap();
+
+        // create account
+        do_process_instruction(
+            initialize_account3(&program_id, &account_key, &mint_key, &owner_key).unwrap(),
+            vec![&mut account_account, &mut mint_account],
+        )
+        .unwrap();
+
+        // create incinerator- and system-owned accounts
+        do_process_instruction(
+            initialize_account3(
+                &program_id,
+                &incinerator_account_key,
+                &mint_key,
+                &solana_program::incinerator::id(),
+            )
+            .unwrap(),
+            vec![&mut incinerator_account, &mut mint_account],
+        )
+        .unwrap();
+        do_process_instruction(
+            initialize_account3(
+                &program_id,
+                &system_account_key,
+                &mint_key,
+                &solana_program::system_program::id(),
+            )
+            .unwrap(),
+            vec![&mut system_account, &mut mint_account],
+        )
+        .unwrap();
+
+        // mint to account
+        do_process_instruction(
+            mint_to(&program_id, &mint_key, &account_key, &owner_key, &[], 1000).unwrap(),
+            vec![&mut mint_account, &mut account_account, &mut owner_account],
+        )
+        .unwrap();
+
+        // transfer half to incinerator, half to system program
+        do_process_instruction(
+            transfer(
+                &program_id,
+                &account_key,
+                &incinerator_account_key,
+                &owner_key,
+                &[],
+                500,
+            )
+            .unwrap(),
+            vec![
+                &mut account_account,
+                &mut incinerator_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+        do_process_instruction(
+            transfer(
+                &program_id,
+                &account_key,
+                &system_account_key,
+                &owner_key,
+                &[],
+                500,
+            )
+            .unwrap(),
+            vec![
+                &mut account_account,
+                &mut system_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+
+        // close with balance fails
+        assert_eq!(
+            Err(TokenError::NonNativeHasBalance.into()),
+            do_process_instruction(
+                close_account(
+                    &program_id,
+                    &incinerator_account_key,
+                    &solana_program::incinerator::id(),
+                    &owner_key,
+                    &[]
+                )
+                .unwrap(),
+                vec![
+                    &mut incinerator_account,
+                    &mut mock_incinerator_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+        assert_eq!(
+            Err(TokenError::NonNativeHasBalance.into()),
+            do_process_instruction(
+                close_account(
+                    &program_id,
+                    &system_account_key,
+                    &solana_program::incinerator::id(),
+                    &owner_key,
+                    &[]
+                )
+                .unwrap(),
+                vec![
+                    &mut system_account,
+                    &mut mock_incinerator_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+
+        // anyone can burn
+        do_process_instruction(
+            burn(
+                &program_id,
+                &incinerator_account_key,
+                &mint_key,
+                &recipient_key,
+                &[],
+                500,
+            )
+            .unwrap(),
+            vec![
+                &mut incinerator_account,
+                &mut mint_account,
+                &mut recipient_account,
+            ],
+        )
+        .unwrap();
+        do_process_instruction(
+            burn(
+                &program_id,
+                &system_account_key,
+                &mint_key,
+                &recipient_key,
+                &[],
+                500,
+            )
+            .unwrap(),
+            vec![
+                &mut system_account,
+                &mut mint_account,
+                &mut recipient_account,
+            ],
+        )
+        .unwrap();
+
+        // closing fails if destination is not the incinerator
+        assert_eq!(
+            Err(ProgramError::InvalidAccountData),
+            do_process_instruction(
+                close_account(
+                    &program_id,
+                    &incinerator_account_key,
+                    &recipient_key,
+                    &owner_key,
+                    &[]
+                )
+                .unwrap(),
+                vec![
+                    &mut incinerator_account,
+                    &mut recipient_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+        assert_eq!(
+            Err(ProgramError::InvalidAccountData),
+            do_process_instruction(
+                close_account(
+                    &program_id,
+                    &system_account_key,
+                    &recipient_key,
+                    &owner_key,
+                    &[]
+                )
+                .unwrap(),
+                vec![
+                    &mut system_account,
+                    &mut recipient_account,
+                    &mut owner_account,
+                ],
+            )
+        );
+
+        // closing succeeds with incinerator recipient
+        do_process_instruction(
+            close_account(
+                &program_id,
+                &incinerator_account_key,
+                &solana_program::incinerator::id(),
+                &owner_key,
+                &[],
+            )
+            .unwrap(),
+            vec![
+                &mut incinerator_account,
+                &mut mock_incinerator_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+
+        do_process_instruction(
+            close_account(
+                &program_id,
+                &system_account_key,
+                &solana_program::incinerator::id(),
+                &owner_key,
+                &[],
+            )
+            .unwrap(),
+            vec![
+                &mut system_account,
+                &mut mock_incinerator_account,
+                &mut owner_account,
+            ],
+        )
+        .unwrap();
+    }
+
     #[test]
     #[test]
     fn test_multisig() {
     fn test_multisig() {
         let program_id = crate::id();
         let program_id = crate::id();

+ 5 - 0
program/src/state.rs

@@ -113,6 +113,11 @@ impl Account {
     pub fn is_native(&self) -> bool {
     pub fn is_native(&self) -> bool {
         self.is_native.is_some()
         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 Sealed for Account {}
 impl Sealed for Account {}
 impl IsInitialized for Account {
 impl IsInitialized for Account {