@@ -1403,28 +1403,21 @@ async fn command_close(
1403
1403
recipient : Pubkey ,
1404
1404
bulk_signers : BulkSigners ,
1405
1405
) -> CommandResult {
1406
- let mint_pubkey = if !config. sign_only {
1407
- let source_account = config. get_account_checked ( & account) . await ?;
1406
+ let source_account = config. get_account_checked ( & account) . await ?;
1408
1407
1409
- let source_state = StateWithExtensionsOwned :: < Account > :: unpack ( source_account. data )
1410
- . map_err ( |_| format ! ( "Could not deserialize token account {}" , account) ) ?;
1411
- let source_amount = source_state. base . amount ;
1408
+ let source_state = StateWithExtensionsOwned :: < Account > :: unpack ( source_account. data )
1409
+ . map_err ( |_| format ! ( "Could not deserialize token account {}" , account) ) ?;
1410
+ let source_amount = source_state. base . amount ;
1412
1411
1413
- if !source_state. base . is_native ( ) && source_amount > 0 {
1414
- return Err ( format ! (
1415
- "Account {} still has {} tokens; empty the account in order to close it." ,
1416
- account, source_amount,
1417
- )
1418
- . into ( ) ) ;
1419
- }
1420
-
1421
- source_state. base . mint
1422
- } else {
1423
- // default is safe here because close doesnt use it
1424
- Pubkey :: default ( )
1425
- } ;
1412
+ if !source_state. base . is_native ( ) && source_amount > 0 {
1413
+ return Err ( format ! (
1414
+ "Account {} still has {} tokens; empty the account in order to close it." ,
1415
+ account, source_amount,
1416
+ )
1417
+ . into ( ) ) ;
1418
+ }
1426
1419
1427
- let token = token_client_from_config ( config, & mint_pubkey ) ;
1420
+ let token = token_client_from_config ( config, & source_state . base . mint ) ;
1428
1421
let res = token
1429
1422
. close_account ( & account, & recipient, & close_authority, & bulk_signers)
1430
1423
. await ?;
@@ -1649,15 +1642,6 @@ async fn command_gc(
1649
1642
return Ok ( "" . to_string ( ) ) ;
1650
1643
}
1651
1644
1652
- let minimum_balance_for_rent_exemption = if !config. sign_only {
1653
- config
1654
- . program_client
1655
- . get_minimum_balance_for_rent_exemption ( Account :: LEN )
1656
- . await ?
1657
- } else {
1658
- 0
1659
- } ;
1660
-
1661
1645
let mut accounts_by_token = HashMap :: new ( ) ;
1662
1646
1663
1647
for keyed_account in accounts {
@@ -1702,107 +1686,79 @@ async fn command_gc(
1702
1686
}
1703
1687
}
1704
1688
1705
- let mut instructions = vec ! [ ] ;
1706
- let mut lamports_needed = 0 ;
1689
+ let mut results = vec ! [ ] ;
1690
+ for ( token_pubkey, accounts) in accounts_by_token. into_iter ( ) {
1691
+ println_display ( config, format ! ( "Processing token: {}" , token_pubkey) ) ;
1707
1692
1708
- for ( token, accounts) in accounts_by_token. into_iter ( ) {
1709
- println_display ( config, format ! ( "Processing token: {}" , token) ) ;
1710
- let associated_token_account =
1711
- get_associated_token_address_with_program_id ( & owner, & token, & config. program_id ) ;
1693
+ let token = token_client_from_config ( config, & token_pubkey) ;
1694
+ let associated_token_account = token. get_associated_token_address ( & owner) ;
1712
1695
let total_balance: u64 = accounts. values ( ) . map ( |account| account. 0 ) . sum ( ) ;
1713
1696
1714
- if total_balance > 0 && !accounts. contains_key ( & associated_token_account) {
1715
- // Create the associated token account
1716
- instructions. push ( vec ! [ create_associated_token_account(
1717
- & config. fee_payer. pubkey( ) ,
1718
- & owner,
1719
- & token,
1720
- & config. program_id,
1721
- ) ] ) ;
1722
- lamports_needed += minimum_balance_for_rent_exemption;
1723
- }
1724
-
1725
1697
for ( address, ( amount, decimals, frozen, close_authority) ) in accounts {
1726
- match (
1727
- address == associated_token_account,
1728
- close_empty_associated_accounts,
1729
- total_balance > 0 ,
1730
- ) {
1731
- ( true , _, true ) => continue , // don't ever close associated token account with amount
1732
- ( true , false , _) => continue , // don't close associated token account if close_empty_associated_accounts isn't set
1733
- ( true , true , false ) => println_display (
1734
- config,
1735
- format ! ( "Closing Account {}" , associated_token_account) ,
1736
- ) ,
1737
- _ => { }
1698
+ let is_associated = address == associated_token_account;
1699
+
1700
+ // only close the associated account if --close-empty-associated-accounts is provided
1701
+ if is_associated && !close_empty_associated_accounts {
1702
+ continue ;
1738
1703
}
1739
1704
1740
- if frozen {
1741
- // leave frozen accounts alone
1705
+ // never close the associated account if *any* account carries a balance
1706
+ if is_associated && total_balance > 0 {
1742
1707
continue ;
1743
1708
}
1744
1709
1745
- let mut account_instructions = vec ! [ ] ;
1710
+ // dont attempt to close frozen accounts
1711
+ if frozen {
1712
+ continue ;
1713
+ }
1746
1714
1747
1715
// Sanity check!
1748
1716
// we shouldn't ever be here, but if we are here, abort!
1749
- assert ! ( amount == 0 || address != associated_token_account) ;
1750
-
1751
- if amount > 0 {
1752
- // Transfer the account balance into the associated token account
1753
- account_instructions. push ( transfer_checked (
1754
- & config. program_id ,
1755
- & address,
1756
- & token,
1757
- & associated_token_account,
1758
- & owner,
1759
- & config. multisigner_pubkeys ,
1760
- amount,
1761
- decimals,
1762
- ) ?) ;
1763
- }
1764
- // Close the account if config.owner is able to
1765
- if close_authority == owner {
1766
- account_instructions. push ( close_account (
1767
- & config. program_id ,
1768
- & address,
1769
- & owner,
1770
- & owner,
1771
- & config. multisigner_pubkeys ,
1772
- ) ?) ;
1717
+ if is_associated && amount > 0 {
1718
+ panic ! ( "gc should NEVER attempt to close a nonempty ata" ) ;
1773
1719
}
1774
1720
1775
- if !account_instructions. is_empty ( ) {
1776
- instructions. push ( account_instructions) ;
1721
+ if close_authority == owner {
1722
+ let res = if is_associated || amount == 0 {
1723
+ token
1724
+ . close_account ( & address, & owner, & owner, & bulk_signers)
1725
+ . await
1726
+ } else {
1727
+ token
1728
+ . empty_and_close_auxiliary_account (
1729
+ & address,
1730
+ & owner,
1731
+ & owner,
1732
+ decimals,
1733
+ & bulk_signers,
1734
+ )
1735
+ . await
1736
+ } ?;
1737
+
1738
+ let tx_return = finish_tx ( config, & res, false ) . await ?;
1739
+
1740
+ results. push ( match tx_return {
1741
+ TransactionReturnData :: CliSignature ( signature) => {
1742
+ config. output_format . formatted_string ( & signature)
1743
+ }
1744
+ TransactionReturnData :: CliSignOnlyData ( sign_only_data) => {
1745
+ config. output_format . formatted_string ( & sign_only_data)
1746
+ }
1747
+ } ) ;
1748
+ } else {
1749
+ println_display (
1750
+ config,
1751
+ format ! (
1752
+ "Note: skipping {} due to separate close authority {}; \
1753
+ revoke authority and rerun gc, or rerun gc with --owner",
1754
+ address, close_authority
1755
+ ) ,
1756
+ ) ;
1777
1757
}
1778
1758
}
1779
1759
}
1780
1760
1781
- let cli_signer_info = CliSignerInfo {
1782
- signers : bulk_signers,
1783
- } ;
1784
-
1785
- let mut result = String :: from ( "" ) ;
1786
- for tx_instructions in instructions {
1787
- let tx_return = handle_tx (
1788
- & cli_signer_info,
1789
- config,
1790
- false ,
1791
- lamports_needed,
1792
- tx_instructions,
1793
- )
1794
- . await ?;
1795
- result += & match tx_return {
1796
- TransactionReturnData :: CliSignature ( signature) => {
1797
- config. output_format . formatted_string ( & signature)
1798
- }
1799
- TransactionReturnData :: CliSignOnlyData ( sign_only_data) => {
1800
- config. output_format . formatted_string ( & sign_only_data)
1801
- }
1802
- } ;
1803
- result += "\n " ;
1804
- }
1805
- Ok ( result)
1761
+ Ok ( results. join ( "" ) )
1806
1762
}
1807
1763
1808
1764
async fn command_sync_native ( config : & Config < ' _ > , native_account_address : Pubkey ) -> CommandResult {
@@ -2630,7 +2586,8 @@ fn app<'a, 'b>(
2630
2586
. takes_value ( true )
2631
2587
. index ( 1 )
2632
2588
. required_unless ( "address" )
2633
- . help ( "Token to close. To close a specific account, use the `--address` parameter instead" ) ,
2589
+ . help ( "Token of the associated account to close. \
2590
+ To close a specific account, use the `--address` parameter instead") ,
2634
2591
)
2635
2592
. arg ( owner_address_arg ( ) )
2636
2593
. arg (
@@ -2667,7 +2624,6 @@ fn app<'a, 'b>(
2667
2624
)
2668
2625
. arg ( multisig_signer_arg ( ) )
2669
2626
. nonce_args ( true )
2670
- . offline_args ( ) ,
2671
2627
)
2672
2628
. subcommand (
2673
2629
SubCommand :: with_name ( CommandName :: CloseMint . into ( ) )
@@ -4419,6 +4375,78 @@ mod tests {
4419
4375
. unwrap ( ) ;
4420
4376
let value: serde_json:: Value = serde_json:: from_str ( & result) . unwrap ( ) ;
4421
4377
assert_eq ! ( value[ "accounts" ] . as_array( ) . unwrap( ) . len( ) , 1 ) ;
4378
+
4379
+ config. output_format = OutputFormat :: Display ;
4380
+
4381
+ // test implicit transfer
4382
+ let token = create_token ( & config, & payer) . await ;
4383
+ let ata = create_associated_account ( & config, & payer, token) . await ;
4384
+ let aux = create_auxiliary_account ( & config, & payer, token) . await ;
4385
+ mint_tokens ( & config, & payer, token, 1.0 , ata) . await ;
4386
+ mint_tokens ( & config, & payer, token, 1.0 , aux) . await ;
4387
+
4388
+ process_test_command ( & config, & payer, & [ "spl-token" , CommandName :: Gc . into ( ) ] )
4389
+ . await
4390
+ . unwrap ( ) ;
4391
+
4392
+ let ui_ata = config
4393
+ . rpc_client
4394
+ . get_token_account ( & ata)
4395
+ . await
4396
+ . unwrap ( )
4397
+ . unwrap ( ) ;
4398
+
4399
+ // aux is gone and its tokens are in ata
4400
+ assert_eq ! ( ui_ata. token_amount. amount, "2" ) ;
4401
+ config. rpc_client . get_account ( & aux) . await . unwrap_err ( ) ;
4402
+
4403
+ // test ata closure
4404
+ let token = create_token ( & config, & payer) . await ;
4405
+ let ata = create_associated_account ( & config, & payer, token) . await ;
4406
+
4407
+ process_test_command (
4408
+ & config,
4409
+ & payer,
4410
+ & [
4411
+ "spl-token" ,
4412
+ CommandName :: Gc . into ( ) ,
4413
+ "--close-empty-associated-accounts" ,
4414
+ ] ,
4415
+ )
4416
+ . await
4417
+ . unwrap ( ) ;
4418
+
4419
+ // ata is gone
4420
+ config. rpc_client . get_account ( & ata) . await . unwrap_err ( ) ;
4421
+
4422
+ // test a tricky corner case of both
4423
+ let token = create_token ( & config, & payer) . await ;
4424
+ let ata = create_associated_account ( & config, & payer, token) . await ;
4425
+ let aux = create_auxiliary_account ( & config, & payer, token) . await ;
4426
+ mint_tokens ( & config, & payer, token, 1.0 , aux) . await ;
4427
+
4428
+ process_test_command (
4429
+ & config,
4430
+ & payer,
4431
+ & [
4432
+ "spl-token" ,
4433
+ CommandName :: Gc . into ( ) ,
4434
+ "--close-empty-associated-accounts" ,
4435
+ ] ,
4436
+ )
4437
+ . await
4438
+ . unwrap ( ) ;
4439
+
4440
+ let ui_ata = config
4441
+ . rpc_client
4442
+ . get_token_account ( & ata)
4443
+ . await
4444
+ . unwrap ( )
4445
+ . unwrap ( ) ;
4446
+
4447
+ // aux is gone and its tokens are in ata, and ata has not been closed
4448
+ assert_eq ! ( ui_ata. token_amount. amount, "1" ) ;
4449
+ config. rpc_client . get_account ( & aux) . await . unwrap_err ( ) ;
4422
4450
}
4423
4451
}
4424
4452
0 commit comments