@@ -37,12 +37,12 @@ use solana_sdk::{
37
37
use spl_associated_token_account:: get_associated_token_address_with_program_id;
38
38
use spl_token_2022:: {
39
39
extension:: {
40
- cpi_guard:: CpiGuard , interest_bearing_mint :: InterestBearingConfig ,
41
- memo_transfer :: MemoTransfer , mint_close_authority :: MintCloseAuthority , ExtensionType ,
42
- StateWithExtensionsOwned ,
40
+ cpi_guard:: CpiGuard , default_account_state :: DefaultAccountState ,
41
+ interest_bearing_mint :: InterestBearingConfig , memo_transfer :: MemoTransfer ,
42
+ mint_close_authority :: MintCloseAuthority , ExtensionType , StateWithExtensionsOwned ,
43
43
} ,
44
44
instruction:: * ,
45
- state:: { Account , Mint } ,
45
+ state:: { Account , AccountState , Mint } ,
46
46
} ;
47
47
use spl_token_client:: {
48
48
client:: { ProgramRpcClientSendTransaction , RpcClientResponse } ,
@@ -134,6 +134,7 @@ pub enum CommandName {
134
134
DisableRequiredTransferMemos ,
135
135
EnableCpiGuard ,
136
136
DisableCpiGuard ,
137
+ UpdateDefaultAccountState ,
137
138
}
138
139
impl fmt:: Display for CommandName {
139
140
fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
@@ -389,6 +390,7 @@ async fn command_create_token(
389
390
enable_non_transferable : bool ,
390
391
memo : Option < String > ,
391
392
rate_bps : Option < i16 > ,
393
+ default_account_state : Option < AccountState > ,
392
394
bulk_signers : Vec < Arc < dyn Signer > > ,
393
395
) -> CommandResult {
394
396
println_display (
@@ -422,6 +424,14 @@ async fn command_create_token(
422
424
extensions. push ( ExtensionInitializationParams :: NonTransferable ) ;
423
425
}
424
426
427
+ if let Some ( state) = default_account_state {
428
+ assert ! (
429
+ enable_freeze,
430
+ "Token requires a freeze authority to default to frozen accounts"
431
+ ) ;
432
+ extensions. push ( ExtensionInitializationParams :: DefaultAccountState { state } )
433
+ }
434
+
425
435
if let Some ( text) = memo {
426
436
token. with_memo ( text, vec ! [ config. default_signer( ) ?. pubkey( ) ] ) ;
427
437
}
@@ -1994,6 +2004,71 @@ async fn command_cpi_guard(
1994
2004
} )
1995
2005
}
1996
2006
2007
+ async fn command_update_default_account_state (
2008
+ config : & Config < ' _ > ,
2009
+ token_pubkey : Pubkey ,
2010
+ freeze_authority : Pubkey ,
2011
+ new_default_state : AccountState ,
2012
+ bulk_signers : BulkSigners ,
2013
+ ) -> CommandResult {
2014
+ if !config. sign_only {
2015
+ let mint_account = config. get_account_checked ( & token_pubkey) . await ?;
2016
+
2017
+ let mint_state = StateWithExtensionsOwned :: < Mint > :: unpack ( mint_account. data )
2018
+ . map_err ( |_| format ! ( "Could not deserialize token mint {}" , token_pubkey) ) ?;
2019
+ match mint_state. base . freeze_authority {
2020
+ COption :: None => {
2021
+ return Err ( format ! ( "Mint {} has no freeze authority." , token_pubkey) . into ( ) )
2022
+ }
2023
+ COption :: Some ( mint_freeze_authority) => {
2024
+ if mint_freeze_authority != freeze_authority {
2025
+ return Err ( format ! (
2026
+ "Mint {} has a freeze authority {}, {} provided" ,
2027
+ token_pubkey, mint_freeze_authority, freeze_authority
2028
+ )
2029
+ . into ( ) ) ;
2030
+ }
2031
+ }
2032
+ }
2033
+
2034
+ if let Ok ( default_account_state) = mint_state. get_extension :: < DefaultAccountState > ( ) {
2035
+ if default_account_state. state == u8:: from ( new_default_state) {
2036
+ let state_string = match new_default_state {
2037
+ AccountState :: Frozen => "frozen" ,
2038
+ AccountState :: Initialized => "initialized" ,
2039
+ _ => unreachable ! ( ) ,
2040
+ } ;
2041
+ return Err ( format ! (
2042
+ "Mint {} already has default account state {}" ,
2043
+ token_pubkey, state_string
2044
+ )
2045
+ . into ( ) ) ;
2046
+ }
2047
+ } else {
2048
+ return Err ( format ! (
2049
+ "Mint {} does not support default account states" ,
2050
+ token_pubkey
2051
+ )
2052
+ . into ( ) ) ;
2053
+ }
2054
+ }
2055
+
2056
+ let token = token_client_from_config ( config, & token_pubkey, None ) ?;
2057
+ let res = token
2058
+ . set_default_account_state ( & freeze_authority, & new_default_state, & bulk_signers)
2059
+ . await ?;
2060
+
2061
+ let tx_return = finish_tx ( config, & res, false ) . await ?;
2062
+ Ok ( match tx_return {
2063
+ TransactionReturnData :: CliSignature ( signature) => {
2064
+ config. output_format . formatted_string ( & signature)
2065
+ }
2066
+ TransactionReturnData :: CliSignOnlyData ( sign_only_data) => {
2067
+ config. output_format . formatted_string ( & sign_only_data)
2068
+ }
2069
+ } )
2070
+ }
2071
+
1997
2072
struct SignOnlyNeedsFullMintSpec { }
1998
2073
impl offline:: ArgsConfig for SignOnlyNeedsFullMintSpec {
1999
2074
fn sign_only_arg < ' a , ' b > ( & self , arg : Arg < ' a , ' b > ) -> Arg < ' a , ' b > {
@@ -2174,6 +2249,17 @@ fn app<'a, 'b>(
2174
2249
"Permanently force tokens to be non-transferable. Thay may still be burned."
2175
2250
) ,
2176
2251
)
2252
+ . arg (
2253
+ Arg :: with_name ( "default_account_state" )
2254
+ . long ( "default-account-state" )
2255
+ . requires ( "enable_freeze" )
2256
+ . possible_values ( & [ "initialized" , "frozen" ] )
2257
+ . help ( "Specify that accounts have a default state. \
2258
+ Note: specifying \" initialized\" adds an extension, which gives \
2259
+ the option of specifying default frozen accounts in the future. \
2260
+ This behavior is not the same as the default, which makes it \
2261
+ impossible to specify a default account state in the future.") ,
2262
+ )
2177
2263
. nonce_args ( true )
2178
2264
. arg ( memo_arg ( ) )
2179
2265
)
@@ -3053,6 +3139,44 @@ fn app<'a, 'b>(
3053
3139
. arg ( multisig_signer_arg ( ) )
3054
3140
. nonce_args ( true )
3055
3141
)
3142
+ . subcommand (
3143
+ SubCommand :: with_name ( CommandName :: UpdateDefaultAccountState . into ( ) )
3144
+ . about ( "Updates default account state for the mint. Requires the default account state extension." )
3145
+ . arg (
3146
+ Arg :: with_name ( "token" )
3147
+ . validator ( is_valid_pubkey)
3148
+ . value_name ( "TOKEN_MINT_ADDRESS" )
3149
+ . takes_value ( true )
3150
+ . index ( 1 )
3151
+ . required ( true )
3152
+ . help ( "The address of the token mint to update default account state" ) ,
3153
+ )
3154
+ . arg (
3155
+ Arg :: with_name ( "state" )
3156
+ . value_name ( "STATE" )
3157
+ . takes_value ( true )
3158
+ . possible_values ( & [ "initialized" , "frozen" ] )
3159
+ . index ( 2 )
3160
+ . required ( true )
3161
+ . help ( "The new default account state." ) ,
3162
+ )
3163
+ . arg (
3164
+ Arg :: with_name ( "freeze_authority" )
3165
+ . long ( "freeze-authority" )
3166
+ . value_name ( "KEYPAIR" )
3167
+ . validator ( is_valid_signer)
3168
+ . takes_value ( true )
3169
+ . help (
3170
+ "Specify the token's freeze authority. \
3171
+ This may be a keypair file or the ASK keyword. \
3172
+ Defaults to the client keypair.",
3173
+ ) ,
3174
+ )
3175
+ . arg ( owner_address_arg ( ) )
3176
+ . arg ( multisig_signer_arg ( ) )
3177
+ . nonce_args ( true )
3178
+ . offline_args ( ) ,
3179
+ )
3056
3180
}
3057
3181
3058
3182
#[ tokio:: main]
@@ -3121,6 +3245,15 @@ async fn process_command<'a>(
3121
3245
bulk_signers. push ( token_signer) ;
3122
3246
}
3123
3247
3248
+ let default_account_state =
3249
+ arg_matches
3250
+ . value_of ( "default_account_state" )
3251
+ . map ( |s| match s {
3252
+ "initialized" => AccountState :: Initialized ,
3253
+ "frozen" => AccountState :: Frozen ,
3254
+ _ => unreachable ! ( ) ,
3255
+ } ) ;
3256
+
3124
3257
command_create_token (
3125
3258
config,
3126
3259
decimals,
@@ -3131,6 +3264,7 @@ async fn process_command<'a>(
3131
3264
arg_matches. is_present ( "enable_non_transferable" ) ,
3132
3265
memo,
3133
3266
rate_bps,
3267
+ default_account_state,
3134
3268
bulk_signers,
3135
3269
)
3136
3270
. await
@@ -3657,6 +3791,31 @@ async fn process_command<'a>(
3657
3791
config. pubkey_or_default ( arg_matches, "account" , & mut wallet_manager) ?;
3658
3792
command_cpi_guard ( config, token_account, owner, bulk_signers, false ) . await
3659
3793
}
3794
+ ( CommandName :: UpdateDefaultAccountState , arg_matches) => {
3795
+ // Since account is required argument it will always be present
3796
+ let token = pubkey_of_signer ( arg_matches, "token" , & mut wallet_manager)
3797
+ . unwrap ( )
3798
+ . unwrap ( ) ;
3799
+ let ( freeze_authority_signer, freeze_authority) =
3800
+ config. signer_or_default ( arg_matches, "freeze_authority" , & mut wallet_manager) ;
3801
+ if !bulk_signers. contains ( & freeze_authority_signer) {
3802
+ bulk_signers. push ( freeze_authority_signer) ;
3803
+ }
3804
+ let new_default_state = arg_matches. value_of ( "state" ) . unwrap ( ) ;
3805
+ let new_default_state = match new_default_state {
3806
+ "initialized" => AccountState :: Initialized ,
3807
+ "frozen" => AccountState :: Frozen ,
3808
+ _ => unreachable ! ( ) ,
3809
+ } ;
3810
+ command_update_default_account_state (
3811
+ config,
3812
+ token,
3813
+ freeze_authority,
3814
+ new_default_state,
3815
+ bulk_signers,
3816
+ )
3817
+ . await
3818
+ }
3660
3819
}
3661
3820
}
3662
3821
@@ -3847,6 +4006,7 @@ mod tests {
3847
4006
false ,
3848
4007
None ,
3849
4008
None ,
4009
+ None ,
3850
4010
bulk_signers,
3851
4011
)
3852
4012
. await
@@ -3874,6 +4034,7 @@ mod tests {
3874
4034
false ,
3875
4035
None ,
3876
4036
Some ( rate_bps) ,
4037
+ None ,
3877
4038
bulk_signers,
3878
4039
)
3879
4040
. await
@@ -5121,6 +5282,7 @@ mod tests {
5121
5282
false ,
5122
5283
None ,
5123
5284
None ,
5285
+ None ,
5124
5286
bulk_signers,
5125
5287
)
5126
5288
. await
@@ -5425,6 +5587,7 @@ mod tests {
5425
5587
true ,
5426
5588
None ,
5427
5589
None ,
5590
+ None ,
5428
5591
bulk_signers,
5429
5592
)
5430
5593
. await
@@ -5455,4 +5618,67 @@ mod tests {
5455
5618
. await
5456
5619
. unwrap_err ( ) ;
5457
5620
}
5621
+
5622
+ #[ tokio:: test]
5623
+ #[ serial]
5624
+ async fn default_account_state ( ) {
5625
+ let ( test_validator, payer) = new_validator_for_test ( ) . await ;
5626
+ let program_id = spl_token_2022:: id ( ) ;
5627
+ let config = test_config_with_default_signer ( & test_validator, & payer, & program_id) ;
5628
+ let token_keypair = Keypair :: new ( ) ;
5629
+ let token_pubkey = token_keypair. pubkey ( ) ;
5630
+ let bulk_signers: Vec < Arc < dyn Signer > > =
5631
+ vec ! [ Arc :: new( clone_keypair( & payer) ) , Arc :: new( token_keypair) ] ;
5632
+
5633
+ command_create_token (
5634
+ & config,
5635
+ TEST_DECIMALS ,
5636
+ token_pubkey,
5637
+ payer. pubkey ( ) ,
5638
+ true ,
5639
+ false ,
5640
+ false ,
5641
+ None ,
5642
+ None ,
5643
+ Some ( AccountState :: Frozen ) ,
5644
+ bulk_signers,
5645
+ )
5646
+ . await
5647
+ . unwrap ( ) ;
5648
+
5649
+ let mint_account = config. rpc_client . get_account ( & token_pubkey) . await . unwrap ( ) ;
5650
+ let mint = StateWithExtensionsOwned :: < Mint > :: unpack ( mint_account. data ) . unwrap ( ) ;
5651
+ let extension = mint. get_extension :: < DefaultAccountState > ( ) . unwrap ( ) ;
5652
+ assert_eq ! ( extension. state, u8 :: from( AccountState :: Frozen ) ) ;
5653
+
5654
+ let frozen_account = create_associated_account ( & config, & payer, token_pubkey) . await ;
5655
+ let token_account = config
5656
+ . rpc_client
5657
+ . get_account ( & frozen_account)
5658
+ . await
5659
+ . unwrap ( ) ;
5660
+ let account = StateWithExtensionsOwned :: < Account > :: unpack ( token_account. data ) . unwrap ( ) ;
5661
+ assert_eq ! ( account. base. state, AccountState :: Frozen ) ;
5662
+
5663
+ process_test_command (
5664
+ & config,
5665
+ & payer,
5666
+ & [
5667
+ "spl-token" ,
5668
+ CommandName :: UpdateDefaultAccountState . into ( ) ,
5669
+ & token_pubkey. to_string ( ) ,
5670
+ "initialized" ,
5671
+ ] ,
5672
+ )
5673
+ . await
5674
+ . unwrap ( ) ;
5675
+ let unfrozen_account = create_auxiliary_account ( & config, & payer, token_pubkey) . await ;
5676
+ let token_account = config
5677
+ . rpc_client
5678
+ . get_account ( & unfrozen_account)
5679
+ . await
5680
+ . unwrap ( ) ;
5681
+ let account = StateWithExtensionsOwned :: < Account > :: unpack ( token_account. data ) . unwrap ( ) ;
5682
+ assert_eq ! ( account. base. state, AccountState :: Initialized ) ;
5683
+ }
5458
5684
}
0 commit comments