From a638eb3ac2d5e46035b4d81e8a67b289db176625 Mon Sep 17 00:00:00 2001 From: Jon C Date: Tue, 10 Oct 2023 23:31:39 -0400 Subject: [PATCH] token-cli: Fix updating metadata with offline signer --- token/cli/src/main.rs | 43 +++++++++++++++++-- token/client/src/token.rs | 10 +++-- .../tests/token_metadata_remove_key.rs | 1 + .../tests/token_metadata_update_field.rs | 1 + 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/token/cli/src/main.rs b/token/cli/src/main.rs index f2c91a3cfb1..72b171518e0 100644 --- a/token/cli/src/main.rs +++ b/token/cli/src/main.rs @@ -110,6 +110,12 @@ pub const DELEGATE_ADDRESS_ARG: ArgConstant<'static> = ArgConstant { help: "Address of delegate currently assigned to token account. Required by --sign-only", }; +pub const TRANSFER_LAMPORTS_ARG: ArgConstant<'static> = ArgConstant { + name: "transfer_lamports", + long: "transfer-lamports", + help: "Additional lamports to transfer to make account rent-exempt after reallocation. Required by --sign-only", +}; + pub const MULTISIG_SIGNER_ARG: ArgConstant<'static> = ArgConstant { name: "multisig_signer", long: "multisig-signer", @@ -323,6 +329,15 @@ pub fn delegate_address_arg<'a, 'b>() -> Arg<'a, 'b> { .help(DELEGATE_ADDRESS_ARG.help) } +pub fn transfer_lamports_arg<'a, 'b>() -> Arg<'a, 'b> { + Arg::with_name(TRANSFER_LAMPORTS_ARG.name) + .long(TRANSFER_LAMPORTS_ARG.long) + .takes_value(true) + .value_name("LAMPORTS") + .validator(is_amount) + .help(TRANSFER_LAMPORTS_ARG.help) +} + pub fn multisig_signer_arg<'a, 'b>() -> Arg<'a, 'b> { Arg::with_name(MULTISIG_SIGNER_ARG.name) .long(MULTISIG_SIGNER_ARG.long) @@ -815,6 +830,7 @@ async fn command_update_metadata( authority: Pubkey, field: Field, value: Option, + transfer_lamports: Option, bulk_signers: Vec>, ) -> CommandResult { let token = token_client_from_config(config, &token_pubkey, None)?; @@ -826,6 +842,7 @@ async fn command_update_metadata( &authority, field, value, + transfer_lamports, &bulk_signers, ) .await? @@ -2902,6 +2919,16 @@ impl offline::ArgsConfig for SignOnlyNeedsDelegateAddress { } } +struct SignOnlyNeedsTransferLamports {} +impl offline::ArgsConfig for SignOnlyNeedsTransferLamports { + fn sign_only_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> { + arg.requires_all(&[TRANSFER_LAMPORTS_ARG.name]) + } + fn signer_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> { + arg.requires_all(&[TRANSFER_LAMPORTS_ARG.name]) + } +} + fn minimum_signers_help_string() -> String { format!( "The minimum number of signers required to allow the operation. [{} <= M <= N]", @@ -3299,7 +3326,8 @@ fn app<'a, 'b>( .help("Specify the metadata update authority keypair. Defaults to the client keypair.") ) .nonce_args(true) - .offline_args(), + .arg(transfer_lamports_arg()) + .offline_args_config(&SignOnlyNeedsTransferLamports{}), ) .subcommand( SubCommand::with_name(CommandName::CreateAccount.into()) @@ -4626,10 +4654,19 @@ async fn process_command<'a>( _ => Field::Key(field.to_string()), }; let value = arg_matches.value_of("value").map(|v| v.to_string()); + let transfer_lamports = value_of::(arg_matches, TRANSFER_LAMPORTS_ARG.name); let bulk_signers = vec![authority_signer]; - command_update_metadata(config, token_pubkey, authority, field, value, bulk_signers) - .await + command_update_metadata( + config, + token_pubkey, + authority, + field, + value, + transfer_lamports, + bulk_signers, + ) + .await } (CommandName::CreateAccount, arg_matches) => { let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager) diff --git a/token/client/src/token.rs b/token/client/src/token.rs index b8c7aa6dedd..b5a8122973d 100644 --- a/token/client/src/token.rs +++ b/token/client/src/token.rs @@ -3743,11 +3743,15 @@ where update_authority: &Pubkey, field: Field, value: String, + transfer_lamports: Option, signing_keypairs: &S, ) -> TokenResult { - let additional_lamports = self - .get_additional_rent_for_updated_metadata(field.clone(), value.clone()) - .await?; + let additional_lamports = if let Some(transfer_lamports) = transfer_lamports { + transfer_lamports + } else { + self.get_additional_rent_for_updated_metadata(field.clone(), value.clone()) + .await? + }; let mut instructions = vec![]; if additional_lamports > 0 { instructions.push(system_instruction::transfer( diff --git a/token/program-2022-test/tests/token_metadata_remove_key.rs b/token/program-2022-test/tests/token_metadata_remove_key.rs index 64fc57770d8..8640196df70 100644 --- a/token/program-2022-test/tests/token_metadata_remove_key.rs +++ b/token/program-2022-test/tests/token_metadata_remove_key.rs @@ -105,6 +105,7 @@ async fn success_remove() { &update_authority.pubkey(), field, value, + None, &[&update_authority], ) .await diff --git a/token/program-2022-test/tests/token_metadata_update_field.rs b/token/program-2022-test/tests/token_metadata_update_field.rs index 7b3dbd7f812..0555f1f8168 100644 --- a/token/program-2022-test/tests/token_metadata_update_field.rs +++ b/token/program-2022-test/tests/token_metadata_update_field.rs @@ -122,6 +122,7 @@ async fn success_update(field: Field, value: String) { &update_authority.pubkey(), field, value, + None, &[&update_authority], ) .await