diff --git a/token/cli/src/main.rs b/token/cli/src/main.rs index c900741abd6..dafa09b1fd1 100644 --- a/token/cli/src/main.rs +++ b/token/cli/src/main.rs @@ -58,9 +58,10 @@ use spl_token_client::{ client::{ProgramRpcClientSendTransaction, RpcClientResponse}, token::{ExtensionInitializationParams, Token}, }; -use spl_token_metadata_interface::state::Field; +use spl_token_metadata_interface::state::{Field, TokenMetadata}; use std::{collections::HashMap, fmt, fmt::Display, process::exit, str::FromStr, sync::Arc}; -use strum_macros::{EnumString, IntoStaticStr}; +use strum::IntoEnumIterator; +use strum_macros::{EnumIter, EnumString, IntoStaticStr}; mod config; use config::{Config, MintInfo}; @@ -206,6 +207,52 @@ where } } +#[derive(Debug, Clone, PartialEq, EnumIter, EnumString, IntoStaticStr)] +#[strum(serialize_all = "kebab-case")] +pub enum CliAuthorityType { + Mint, + Freeze, + Owner, + Close, + CloseMint, + TransferFeeConfig, + WithheldWithdraw, + InterestRate, + PermanentDelegate, + ConfidentialTransferMint, + TransferHookProgramId, + ConfidentialTransferFee, + MetadataPointer, + Metadata, +} +impl TryFrom for AuthorityType { + type Error = Error; + fn try_from(authority_type: CliAuthorityType) -> Result { + match authority_type { + CliAuthorityType::Mint => Ok(AuthorityType::MintTokens), + CliAuthorityType::Freeze => Ok(AuthorityType::FreezeAccount), + CliAuthorityType::Owner => Ok(AuthorityType::AccountOwner), + CliAuthorityType::Close => Ok(AuthorityType::CloseAccount), + CliAuthorityType::CloseMint => Ok(AuthorityType::CloseMint), + CliAuthorityType::TransferFeeConfig => Ok(AuthorityType::TransferFeeConfig), + CliAuthorityType::WithheldWithdraw => Ok(AuthorityType::WithheldWithdraw), + CliAuthorityType::InterestRate => Ok(AuthorityType::InterestRate), + CliAuthorityType::PermanentDelegate => Ok(AuthorityType::PermanentDelegate), + CliAuthorityType::ConfidentialTransferMint => { + Ok(AuthorityType::ConfidentialTransferMint) + } + CliAuthorityType::TransferHookProgramId => Ok(AuthorityType::TransferHookProgramId), + CliAuthorityType::ConfidentialTransferFee => { + Ok(AuthorityType::ConfidentialTransferFeeConfig) + } + CliAuthorityType::MetadataPointer => Ok(AuthorityType::MetadataPointer), + CliAuthorityType::Metadata => { + Err("Metadata authority does not map to a token authority type".into()) + } + } + } +} + pub fn owner_address_arg<'a, 'b>() -> Arg<'a, 'b> { Arg::with_name(OWNER_ADDRESS_ARG.name) .long(OWNER_ADDRESS_ARG.long) @@ -996,29 +1043,13 @@ async fn command_create_multisig( async fn command_authorize( config: &Config<'_>, account: Pubkey, - authority_type: AuthorityType, + authority_type: CliAuthorityType, authority: Pubkey, new_authority: Option, force_authorize: bool, bulk_signers: BulkSigners, ) -> CommandResult { - let auth_str = match authority_type { - AuthorityType::MintTokens => "mint authority", - AuthorityType::FreezeAccount => "freeze authority", - AuthorityType::AccountOwner => "owner", - AuthorityType::CloseAccount => "close account authority", - AuthorityType::CloseMint => "close mint authority", - AuthorityType::TransferFeeConfig => "transfer fee authority", - AuthorityType::WithheldWithdraw => "withdraw withheld authority", - AuthorityType::InterestRate => "interest rate authority", - AuthorityType::PermanentDelegate => "permanent delegate", - AuthorityType::ConfidentialTransferMint => "confidential transfer mint authority", - AuthorityType::TransferHookProgramId => "transfer hook program id authority", - AuthorityType::ConfidentialTransferFeeConfig => { - "confidential transfer fee config authority" - } - AuthorityType::MetadataPointer => "metadata pointer authority", - }; + let auth_str: &'static str = (&authority_type).into(); let (mint_pubkey, previous_authority) = if !config.sign_only { let target_account = config.get_account_checked(&account).await?; @@ -1027,17 +1058,15 @@ async fn command_authorize( StateWithExtensionsOwned::::unpack(target_account.data.clone()) { let previous_authority = match authority_type { - AuthorityType::AccountOwner | AuthorityType::CloseAccount => Err(format!( + CliAuthorityType::Owner | CliAuthorityType::Close => Err(format!( "Authority type `{}` not supported for SPL Token mints", auth_str )), - AuthorityType::MintTokens => Ok(mint.base.mint_authority), - AuthorityType::FreezeAccount => Ok(mint.base.freeze_authority), - AuthorityType::CloseMint => { + CliAuthorityType::Mint => Ok(Option::::from(mint.base.mint_authority)), + CliAuthorityType::Freeze => Ok(Option::::from(mint.base.freeze_authority)), + CliAuthorityType::CloseMint => { if let Ok(mint_close_authority) = mint.get_extension::() { - Ok(COption::::from( - mint_close_authority.close_authority, - )) + Ok(Option::::from(mint_close_authority.close_authority)) } else { Err(format!( "Mint `{}` does not support close authority", @@ -1045,35 +1074,35 @@ async fn command_authorize( )) } } - AuthorityType::TransferFeeConfig => { + CliAuthorityType::TransferFeeConfig => { if let Ok(transfer_fee_config) = mint.get_extension::() { - Ok(COption::::from( + Ok(Option::::from( transfer_fee_config.transfer_fee_config_authority, )) } else { Err(format!("Mint `{}` does not support transfer fees", account)) } } - AuthorityType::WithheldWithdraw => { + CliAuthorityType::WithheldWithdraw => { if let Ok(transfer_fee_config) = mint.get_extension::() { - Ok(COption::::from( + Ok(Option::::from( transfer_fee_config.withdraw_withheld_authority, )) } else { Err(format!("Mint `{}` does not support transfer fees", account)) } } - AuthorityType::InterestRate => { + CliAuthorityType::InterestRate => { if let Ok(interest_rate_config) = mint.get_extension::() { - Ok(COption::::from(interest_rate_config.rate_authority)) + Ok(Option::::from(interest_rate_config.rate_authority)) } else { Err(format!("Mint `{}` is not interest-bearing", account)) } } - AuthorityType::PermanentDelegate => { + CliAuthorityType::PermanentDelegate => { if let Ok(permanent_delegate) = mint.get_extension::() { - Ok(COption::::from(permanent_delegate.delegate)) + Ok(Option::::from(permanent_delegate.delegate)) } else { Err(format!( "Mint `{}` does not support permanent delegate", @@ -1081,13 +1110,11 @@ async fn command_authorize( )) } } - AuthorityType::ConfidentialTransferMint => { + CliAuthorityType::ConfidentialTransferMint => { if let Ok(confidential_transfer_mint) = mint.get_extension::() { - Ok(COption::::from( - confidential_transfer_mint.authority, - )) + Ok(Option::::from(confidential_transfer_mint.authority)) } else { Err(format!( "Mint `{}` does not support confidential transfers", @@ -1095,9 +1122,9 @@ async fn command_authorize( )) } } - AuthorityType::TransferHookProgramId => { + CliAuthorityType::TransferHookProgramId => { if let Ok(extension) = mint.get_extension::() { - Ok(COption::::from(extension.authority)) + Ok(Option::::from(extension.authority)) } else { Err(format!( "Mint `{}` does not support a transfer hook program", @@ -1105,11 +1132,11 @@ async fn command_authorize( )) } } - AuthorityType::ConfidentialTransferFeeConfig => { + CliAuthorityType::ConfidentialTransferFee => { if let Ok(confidential_transfer_fee_config) = mint.get_extension::() { - Ok(COption::::from( + Ok(Option::::from( confidential_transfer_fee_config.authority, )) } else { @@ -1119,9 +1146,9 @@ async fn command_authorize( )) } } - AuthorityType::MetadataPointer => { + CliAuthorityType::MetadataPointer => { if let Ok(extension) = mint.get_extension::() { - Ok(COption::::from(extension.authority)) + Ok(Option::::from(extension.authority)) } else { Err(format!( "Mint `{}` does not support a metadata pointer", @@ -1129,6 +1156,13 @@ async fn command_authorize( )) } } + CliAuthorityType::Metadata => { + if let Ok(extension) = mint.get_variable_len_extension::() { + Ok(Option::::from(extension.update_authority)) + } else { + Err(format!("Mint `{account}` does not support metadata")) + } + } }?; Ok((account, previous_authority)) @@ -1156,27 +1190,27 @@ async fn command_authorize( }; let previous_authority = match authority_type { - AuthorityType::MintTokens - | AuthorityType::FreezeAccount - | AuthorityType::CloseMint - | AuthorityType::TransferFeeConfig - | AuthorityType::WithheldWithdraw - | AuthorityType::InterestRate - | AuthorityType::PermanentDelegate - | AuthorityType::ConfidentialTransferMint - | AuthorityType::TransferHookProgramId - | AuthorityType::ConfidentialTransferFeeConfig - | AuthorityType::MetadataPointer => Err(format!( - "Authority type `{}` not supported for SPL Token accounts", - auth_str + CliAuthorityType::Mint + | CliAuthorityType::Freeze + | CliAuthorityType::CloseMint + | CliAuthorityType::TransferFeeConfig + | CliAuthorityType::WithheldWithdraw + | CliAuthorityType::InterestRate + | CliAuthorityType::PermanentDelegate + | CliAuthorityType::ConfidentialTransferMint + | CliAuthorityType::TransferHookProgramId + | CliAuthorityType::ConfidentialTransferFee + | CliAuthorityType::MetadataPointer + | CliAuthorityType::Metadata => Err(format!( + "Authority type `{auth_str}` not supported for SPL Token accounts", )), - AuthorityType::AccountOwner => { + CliAuthorityType::Owner => { check_associated_token_account()?; - Ok(COption::Some(token_account.base.owner)) + Ok(Some(token_account.base.owner)) } - AuthorityType::CloseAccount => { + CliAuthorityType::Close => { check_associated_token_account()?; - Ok(COption::Some( + Ok(Some( token_account .base .close_authority @@ -1193,7 +1227,7 @@ async fn command_authorize( (mint_pubkey, previous_authority) } else { // default is safe here because authorize doesnt use it - (Pubkey::default(), COption::None) + (Pubkey::default(), None) }; let token = token_client_from_config(config, &mint_pubkey, None)?; @@ -1218,15 +1252,21 @@ async fn command_authorize( ), ); - let res = token - .set_authority( - &account, - &authority, - new_authority.as_ref(), - authority_type, - &bulk_signers, - ) - .await?; + let res = if let CliAuthorityType::Metadata = authority_type { + token + .token_metadata_update_authority(&authority, new_authority, &bulk_signers) + .await? + } else { + token + .set_authority( + &account, + &authority, + new_authority.as_ref(), + authority_type.try_into()?, + &bulk_signers, + ) + .await? + }; let tx_return = finish_tx(config, &res, false).await?; Ok(match tx_return { @@ -3220,12 +3260,7 @@ fn app<'a, 'b>( Arg::with_name("authority_type") .value_name("AUTHORITY_TYPE") .takes_value(true) - .possible_values(&[ - "mint", "freeze", "owner", "close", - "close-mint", "transfer-fee-config", "withheld-withdraw", - "interest-rate", "permanent-delegate", "confidential-transfer-mint", - "transfer-hook", - ]) + .possible_values(&CliAuthorityType::iter().map(Into::into).collect::>()) .index(2) .required(true) .help("The new authority type. \ @@ -4456,22 +4491,7 @@ async fn process_command<'a>( .unwrap() .unwrap(); let authority_type = arg_matches.value_of("authority_type").unwrap(); - let authority_type = match authority_type { - "mint" => AuthorityType::MintTokens, - "freeze" => AuthorityType::FreezeAccount, - "owner" => AuthorityType::AccountOwner, - "close" => AuthorityType::CloseAccount, - "close-mint" => AuthorityType::CloseMint, - "transfer-fee-config" => AuthorityType::TransferFeeConfig, - "withheld-withdraw" => AuthorityType::WithheldWithdraw, - "interest-rate" => AuthorityType::InterestRate, - "permanent-delegate" => AuthorityType::PermanentDelegate, - "confidential-transfer-mint" => AuthorityType::ConfidentialTransferMint, - "transfer-hook-program-id" => AuthorityType::TransferHookProgramId, - "confidential-transfer-fee" => AuthorityType::ConfidentialTransferFeeConfig, - "metadata-pointer" => AuthorityType::MetadataPointer, - _ => unreachable!(), - }; + let authority_type = CliAuthorityType::from_str(authority_type)?; let (authority_signer, authority) = config.signer_or_default(arg_matches, "authority", &mut wallet_manager); @@ -5113,7 +5133,6 @@ mod tests { spl_token_client::client::{ ProgramClient, ProgramOfflineClient, ProgramRpcClient, ProgramRpcClientSendTransaction, }, - spl_token_metadata_interface::{borsh::BorshDeserialize, state::TokenMetadata}, std::path::PathBuf, tempfile::NamedTempFile, }; @@ -7087,7 +7106,7 @@ mod tests { let result = command_authorize( &config, account, - AuthorityType::AccountOwner, + CliAuthorityType::Owner, payer.pubkey(), Some(new_owner), true, @@ -7119,7 +7138,7 @@ mod tests { let result = command_authorize( &config, aux_pubkey, - AuthorityType::AccountOwner, + CliAuthorityType::Owner, payer.pubkey(), Some(new_owner), true, @@ -7152,7 +7171,7 @@ mod tests { let result = command_authorize( &config, Pubkey::from_str(&accounts[0].pubkey).unwrap(), - AuthorityType::AccountOwner, + CliAuthorityType::Owner, payer.pubkey(), Some(new_owner), true, @@ -8313,8 +8332,9 @@ mod tests { let account = config.rpc_client.get_account(&mint).await.unwrap(); let mint_state = StateWithExtensionsOwned::::unpack(account.data).unwrap(); - let metadata_bytes = mint_state.get_extension_bytes::().unwrap(); - let fetched_metadata = TokenMetadata::try_from_slice(metadata_bytes).unwrap(); + let fetched_metadata = mint_state + .get_variable_len_extension::() + .unwrap(); assert_eq!(fetched_metadata.name, name); assert_eq!(fetched_metadata.symbol, symbol); assert_eq!(fetched_metadata.uri, uri); @@ -8342,8 +8362,9 @@ mod tests { .unwrap(); let account = config.rpc_client.get_account(&mint).await.unwrap(); let mint_state = StateWithExtensionsOwned::::unpack(account.data).unwrap(); - let metadata_bytes = mint_state.get_extension_bytes::().unwrap(); - let fetched_metadata = TokenMetadata::try_from_slice(metadata_bytes).unwrap(); + let fetched_metadata = mint_state + .get_variable_len_extension::() + .unwrap(); assert_eq!(fetched_metadata.name, new_value); // add new field @@ -8364,8 +8385,9 @@ mod tests { .unwrap(); let account = config.rpc_client.get_account(&mint).await.unwrap(); let mint_state = StateWithExtensionsOwned::::unpack(account.data).unwrap(); - let metadata_bytes = mint_state.get_extension_bytes::().unwrap(); - let fetched_metadata = TokenMetadata::try_from_slice(metadata_bytes).unwrap(); + let fetched_metadata = mint_state + .get_variable_len_extension::() + .unwrap(); assert_eq!( fetched_metadata.additional_metadata, [(field.to_string(), value.to_string())] @@ -8387,8 +8409,9 @@ mod tests { .unwrap(); let account = config.rpc_client.get_account(&mint).await.unwrap(); let mint_state = StateWithExtensionsOwned::::unpack(account.data).unwrap(); - let metadata_bytes = mint_state.get_extension_bytes::().unwrap(); - let fetched_metadata = TokenMetadata::try_from_slice(metadata_bytes).unwrap(); + let fetched_metadata = mint_state + .get_variable_len_extension::() + .unwrap(); assert_eq!(fetched_metadata.additional_metadata, []); // fail to remove name @@ -8405,5 +8428,30 @@ mod tests { ) .await .unwrap_err(); + + // update authority + process_test_command( + &config, + &payer, + &[ + "spl-token", + CommandName::Authorize.into(), + &mint.to_string(), + "metadata", + &mint.to_string(), + ], + ) + .await + .unwrap(); + + let account = config.rpc_client.get_account(&mint).await.unwrap(); + let mint_state = StateWithExtensionsOwned::::unpack(account.data).unwrap(); + let fetched_metadata = mint_state + .get_variable_len_extension::() + .unwrap(); + assert_eq!( + fetched_metadata.update_authority, + Some(mint).try_into().unwrap() + ); } }