@@ -44,7 +44,8 @@ use spl_associated_token_account::{
44
44
use spl_token_2022:: {
45
45
extension:: {
46
46
interest_bearing_mint, interest_bearing_mint:: InterestBearingConfig ,
47
- mint_close_authority:: MintCloseAuthority , StateWithExtensionsOwned ,
47
+ memo_transfer:: MemoTransfer , mint_close_authority:: MintCloseAuthority , ExtensionType ,
48
+ StateWithExtensionsOwned ,
48
49
} ,
49
50
instruction:: * ,
50
51
state:: { Account , Mint , Multisig } ,
@@ -153,6 +154,8 @@ pub enum CommandName {
153
154
Display ,
154
155
Gc ,
155
156
SyncNative ,
157
+ EnableRequiredTransferMemos ,
158
+ DisableRequiredTransferMemos ,
156
159
}
157
160
impl fmt:: Display for CommandName {
158
161
fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
@@ -1887,6 +1890,103 @@ async fn command_sync_native(
1887
1890
} )
1888
1891
}
1889
1892
1893
+ // Both enable_required_transfer_mesos and disable_required_transfer_mesos
1894
+ // Switches with enable_memos bool
1895
+ async fn command_required_transfer_memos (
1896
+ config : & Config < ' _ > ,
1897
+ token_account_address : Pubkey ,
1898
+ owner : Pubkey ,
1899
+ bulk_signers : BulkSigners ,
1900
+ enable_memos : bool ,
1901
+ ) -> CommandResult {
1902
+ if config. sign_only {
1903
+ panic ! ( "Config can not be sign only for enabling/disabling required transfer memos." ) ;
1904
+ }
1905
+ let account_fetch = config
1906
+ . rpc_client
1907
+ . get_account ( & token_account_address)
1908
+ . await
1909
+ . map_err ( |err| {
1910
+ format ! (
1911
+ "Token account {} does not exist: {}" ,
1912
+ token_account_address, err
1913
+ )
1914
+ } ) ?;
1915
+ let program_id = config. program_id ;
1916
+ config. get_account_checked ( & token_account_address) . await ?;
1917
+ let mut instructions: Vec < Instruction > = Vec :: new ( ) ;
1918
+ // Reallocation (if needed)
1919
+ let current_account_len = account_fetch. data . len ( ) ;
1920
+ let state_with_extension = StateWithExtensionsOwned :: < Account > :: unpack ( account_fetch. data ) ?;
1921
+ let mut existing_extensions: Vec < ExtensionType > = state_with_extension. get_extension_types ( ) ?;
1922
+ if existing_extensions. contains ( & ExtensionType :: MemoTransfer ) {
1923
+ let extension_data: bool = state_with_extension
1924
+ . get_extension :: < MemoTransfer > ( ) ?
1925
+ . require_incoming_transfer_memos
1926
+ . into ( ) ;
1927
+ if extension_data == enable_memos {
1928
+ return Ok ( format ! (
1929
+ "Required memo transfer was already {}" ,
1930
+ if extension_data {
1931
+ "enabled"
1932
+ } else {
1933
+ "disabled"
1934
+ }
1935
+ ) ) ;
1936
+ }
1937
+ } else {
1938
+ existing_extensions. push ( ExtensionType :: MemoTransfer ) ;
1939
+ let needed_account_len = ExtensionType :: get_account_len :: < Account > ( & existing_extensions) ;
1940
+ if needed_account_len > current_account_len {
1941
+ instructions. push ( reallocate (
1942
+ & program_id,
1943
+ & token_account_address,
1944
+ & config. fee_payer . pubkey ( ) ,
1945
+ & owner,
1946
+ & config. multisigner_pubkeys ,
1947
+ & existing_extensions,
1948
+ ) ?) ;
1949
+ }
1950
+ }
1951
+ if enable_memos {
1952
+ instructions. push (
1953
+ spl_token_2022:: extension:: memo_transfer:: instruction:: enable_required_transfer_memos (
1954
+ & program_id,
1955
+ & token_account_address,
1956
+ & owner,
1957
+ & config. multisigner_pubkeys ,
1958
+ ) ?,
1959
+ ) ;
1960
+ } else {
1961
+ instructions. push (
1962
+ spl_token_2022:: extension:: memo_transfer:: instruction:: disable_required_transfer_memos (
1963
+ & program_id,
1964
+ & token_account_address,
1965
+ & owner,
1966
+ & config. multisigner_pubkeys ,
1967
+ ) ?,
1968
+ ) ;
1969
+ }
1970
+ let tx_return = handle_tx (
1971
+ & CliSignerInfo {
1972
+ signers : bulk_signers,
1973
+ } ,
1974
+ config,
1975
+ false ,
1976
+ 0 ,
1977
+ instructions,
1978
+ )
1979
+ . await ?;
1980
+ Ok ( match tx_return {
1981
+ TransactionReturnData :: CliSignature ( signature) => {
1982
+ config. output_format . formatted_string ( & signature)
1983
+ }
1984
+ TransactionReturnData :: CliSignOnlyData ( sign_only_data) => {
1985
+ config. output_format . formatted_string ( & sign_only_data)
1986
+ }
1987
+ } )
1988
+ }
1989
+
1890
1990
struct SignOnlyNeedsFullMintSpec { }
1891
1991
impl offline:: ArgsConfig for SignOnlyNeedsFullMintSpec {
1892
1992
fn sign_only_arg < ' a , ' b > ( & self , arg : Arg < ' a , ' b > ) -> Arg < ' a , ' b > {
@@ -2821,6 +2921,44 @@ fn app<'a, 'b>(
2821
2921
. help ( "Specify the specific token account address to sync" ) ,
2822
2922
) ,
2823
2923
)
2924
+ . subcommand (
2925
+ SubCommand :: with_name ( CommandName :: EnableRequiredTransferMemos . into ( ) )
2926
+ . about ( "Enable required transfer memos for token account" )
2927
+ . arg (
2928
+ Arg :: with_name ( "account" )
2929
+ . validator ( is_valid_pubkey)
2930
+ . value_name ( "TOKEN_ACCOUNT_ADDRESS" )
2931
+ . takes_value ( true )
2932
+ . index ( 1 )
2933
+ . required ( true )
2934
+ . help ( "The address of the token account to enable required transfer memos" )
2935
+ )
2936
+ . arg (
2937
+ owner_address_arg ( )
2938
+ )
2939
+ . arg ( multisig_signer_arg ( ) )
2940
+ . nonce_args ( true )
2941
+ . offline_args ( )
2942
+ )
2943
+ . subcommand (
2944
+ SubCommand :: with_name ( CommandName :: DisableRequiredTransferMemos . into ( ) )
2945
+ . about ( "Disable required transfer memos for token account" )
2946
+ . arg (
2947
+ Arg :: with_name ( "account" )
2948
+ . validator ( is_valid_pubkey)
2949
+ . value_name ( "TOKEN_ACCOUNT_ADDRESS" )
2950
+ . takes_value ( true )
2951
+ . index ( 1 )
2952
+ . required ( true )
2953
+ . help ( "The address of the token account to disable required transfer memos" ) ,
2954
+ )
2955
+ . arg (
2956
+ owner_address_arg ( )
2957
+ )
2958
+ . arg ( multisig_signer_arg ( ) )
2959
+ . nonce_args ( true )
2960
+ . offline_args ( )
2961
+ )
2824
2962
}
2825
2963
2826
2964
#[ tokio:: main]
@@ -3356,6 +3494,28 @@ async fn process_command<'a>(
3356
3494
. await ;
3357
3495
command_sync_native ( address, bulk_signers, config) . await
3358
3496
}
3497
+ ( CommandName :: EnableRequiredTransferMemos , arg_matches) => {
3498
+ let ( owner_signer, owner) =
3499
+ config. signer_or_default ( arg_matches, "owner" , & mut wallet_manager) ;
3500
+ if !bulk_signers. contains ( & owner_signer) {
3501
+ bulk_signers. push ( owner_signer) ;
3502
+ }
3503
+ // Since account is required argument it will always be present
3504
+ let token_account =
3505
+ config. pubkey_or_default ( arg_matches, "account" , & mut wallet_manager) ;
3506
+ command_required_transfer_memos ( config, token_account, owner, bulk_signers, true ) . await
3507
+ }
3508
+ ( CommandName :: DisableRequiredTransferMemos , arg_matches) => {
3509
+ let ( owner_signer, owner) =
3510
+ config. signer_or_default ( arg_matches, "owner" , & mut wallet_manager) ;
3511
+ if !bulk_signers. contains ( & owner_signer) {
3512
+ bulk_signers. push ( owner_signer) ;
3513
+ }
3514
+ // Since account is required argument it will always be present
3515
+ let token_account =
3516
+ config. pubkey_or_default ( arg_matches, "account" , & mut wallet_manager) ;
3517
+ command_required_transfer_memos ( config, token_account, owner, bulk_signers, false ) . await
3518
+ }
3359
3519
}
3360
3520
}
3361
3521
@@ -4546,4 +4706,60 @@ mod tests {
4546
4706
let account = config. rpc_client . get_account ( & token_pubkey) . await ;
4547
4707
assert ! ( account. is_err( ) ) ;
4548
4708
}
4709
+
4710
+ #[ tokio:: test]
4711
+ #[ serial]
4712
+ async fn required_transfer_memos ( ) {
4713
+ let ( test_validator, payer) = new_validator_for_test ( ) . await ;
4714
+ let program_id = spl_token_2022:: id ( ) ;
4715
+ let config = test_config ( & test_validator, & payer, & program_id) ;
4716
+ let token = create_token ( & config, & payer) . await ;
4717
+ let token_account = create_associated_account ( & config, & payer, token) . await ;
4718
+ let result = process_test_command (
4719
+ & config,
4720
+ & payer,
4721
+ & [
4722
+ "spl-token" ,
4723
+ CommandName :: EnableRequiredTransferMemos . into ( ) ,
4724
+ & token_account. to_string ( ) ,
4725
+ ] ,
4726
+ )
4727
+ . await ;
4728
+ result. unwrap ( ) ;
4729
+ let extensions = StateWithExtensionsOwned :: < Account > :: unpack (
4730
+ config
4731
+ . rpc_client
4732
+ . get_account ( & token_account)
4733
+ . await
4734
+ . unwrap ( )
4735
+ . data ,
4736
+ )
4737
+ . unwrap ( ) ;
4738
+ let memo_transfer = extensions. get_extension :: < MemoTransfer > ( ) . unwrap ( ) ;
4739
+ let enabled: bool = memo_transfer. require_incoming_transfer_memos . into ( ) ;
4740
+ assert ! ( enabled) ;
4741
+ let result = process_test_command (
4742
+ & config,
4743
+ & payer,
4744
+ & [
4745
+ "spl-token" ,
4746
+ CommandName :: DisableRequiredTransferMemos . into ( ) ,
4747
+ & token_account. to_string ( ) ,
4748
+ ] ,
4749
+ )
4750
+ . await ;
4751
+ result. unwrap ( ) ;
4752
+ let extensions = StateWithExtensionsOwned :: < Account > :: unpack (
4753
+ config
4754
+ . rpc_client
4755
+ . get_account ( & token_account)
4756
+ . await
4757
+ . unwrap ( )
4758
+ . data ,
4759
+ )
4760
+ . unwrap ( ) ;
4761
+ let memo_transfer = extensions. get_extension :: < MemoTransfer > ( ) . unwrap ( ) ;
4762
+ let enabled: bool = memo_transfer. require_incoming_transfer_memos . into ( ) ;
4763
+ assert ! ( !enabled) ;
4764
+ }
4549
4765
}
0 commit comments