Skip to content

Commit

Permalink
token-cli: Allow burn command to accept ALL keyword for amount (#6057)
Browse files Browse the repository at this point in the history
Several of the token-spl command support using the ALL keyword as the
amount. spl-token burn does not, so add support for that command.
  • Loading branch information
steviez authored Mar 4, 2024
1 parent 8e5d820 commit 714e869
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 59 deletions.
4 changes: 2 additions & 2 deletions token/cli/src/clap_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1371,12 +1371,12 @@ pub fn app<'a, 'b>(
)
.arg(
Arg::with_name("amount")
.validator(is_amount)
.validator(is_amount_or_all)
.value_name("TOKEN_AMOUNT")
.takes_value(true)
.index(2)
.required(true)
.help("Amount to burn, in tokens"),
.help("Amount to burn, in tokens; accepts keyword ALL"),
)
.arg(owner_keypair_arg_with_value_name("TOKEN_OWNER_KEYPAIR")
.help(
Expand Down
49 changes: 33 additions & 16 deletions token/cli/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ fn get_signer(
(Arc::from(signer), signer_pubkey)
})
}

fn parse_amount_or_all(matches: &ArgMatches<'_>) -> Option<f64> {
match matches.value_of("amount").unwrap() {
"ALL" => None,
amount => Some(amount.parse::<f64>().unwrap()),
}
}

async fn check_wallet_balance(
config: &Config<'_>,
wallet: &Pubkey,
Expand Down Expand Up @@ -1654,28 +1662,43 @@ async fn command_burn(
config: &Config<'_>,
account: Pubkey,
owner: Pubkey,
ui_amount: f64,
ui_amount: Option<f64>,
mint_address: Option<Pubkey>,
mint_decimals: Option<u8>,
use_unchecked_instruction: bool,
memo: Option<String>,
bulk_signers: BulkSigners,
) -> CommandResult {
println_display(
config,
format!("Burn {} tokens\n Source: {}", ui_amount, account),
);

let mint_address = config.check_account(&account, mint_address).await?;
let mint_info = config.get_mint_info(&mint_address, mint_decimals).await?;
let amount = spl_token::ui_amount_to_amount(ui_amount, mint_info.decimals);
let decimals = if use_unchecked_instruction {
None
} else {
Some(mint_info.decimals)
};

let token = token_client_from_config(config, &mint_info.address, decimals)?;

let amount = if let Some(ui_amount) = ui_amount {
spl_token::ui_amount_to_amount(ui_amount, mint_info.decimals)
} else {
if config.sign_only {
return Err("Use of ALL keyword to burn tokens requires online signing"
.to_string()
.into());
}
token.get_account_info(&account).await?.base.amount
};

println_display(
config,
format!(
"Burn {} tokens\n Source: {}",
spl_token::amount_to_ui_amount(amount, mint_info.decimals),
account
),
);

if let Some(text) = memo {
token.with_memo(text, vec![config.default_signer()?.pubkey()]);
}
Expand Down Expand Up @@ -3701,10 +3724,7 @@ pub async fn process_command<'a>(
let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
.unwrap()
.unwrap();
let amount = match arg_matches.value_of("amount").unwrap() {
"ALL" => None,
amount => Some(amount.parse::<f64>().unwrap()),
};
let amount = parse_amount_or_all(arg_matches);
let recipient = pubkey_of_signer(arg_matches, "recipient", &mut wallet_manager)
.unwrap()
.unwrap();
Expand Down Expand Up @@ -3792,7 +3812,7 @@ pub async fn process_command<'a>(
push_signer_with_dedup(owner_signer, &mut bulk_signers);
}

let amount = value_t_or_exit!(arg_matches, "amount", f64);
let amount = parse_amount_or_all(arg_matches);
let mint_address =
pubkey_of_signer(arg_matches, MINT_ADDRESS_ARG.name, &mut wallet_manager).unwrap();
let mint_decimals = value_of::<u8>(arg_matches, MINT_DECIMALS_ARG.name);
Expand Down Expand Up @@ -4424,10 +4444,7 @@ pub async fn process_command<'a>(
let token = pubkey_of_signer(arg_matches, "token", &mut wallet_manager)
.unwrap()
.unwrap();
let amount = match arg_matches.value_of("amount").unwrap() {
"ALL" => None,
amount => Some(amount.parse::<f64>().unwrap()),
};
let amount = parse_amount_or_all(arg_matches);
let account = pubkey_of_signer(arg_matches, "address", &mut wallet_manager).unwrap();

let (owner_signer, owner) =
Expand Down
159 changes: 118 additions & 41 deletions token/cli/tests/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,11 @@ async fn main() {
async_trial!(disable_mint_authority, test_validator, payer),
async_trial!(set_owner, test_validator, payer),
async_trial!(transfer_with_account_delegate, test_validator, payer),
async_trial!(burn, test_validator, payer),
async_trial!(burn_with_account_delegate, test_validator, payer),
async_trial!(close_mint, test_validator, payer),
async_trial!(burn_with_permanent_delegate, test_validator, payer),
async_trial!(transfer_with_permanent_delegate, test_validator, payer),
async_trial!(close_mint, test_validator, payer),
async_trial!(required_transfer_memos, test_validator, payer),
async_trial!(cpi_guard, test_validator, payer),
async_trial!(immutable_accounts, test_validator, payer),
Expand Down Expand Up @@ -1508,6 +1509,82 @@ async fn transfer_with_account_delegate(test_validator: &TestValidator, payer: &
}
}

async fn burn(test_validator: &TestValidator, payer: &Keypair) {
for program_id in VALID_TOKEN_PROGRAM_IDS.iter() {
let mut config = test_config_with_default_signer(test_validator, payer, program_id);
let token = create_token(&config, payer).await;
let source = create_associated_account(&config, payer, &token, &payer.pubkey()).await;
let ui_amount = 100.0;
mint_tokens(&config, payer, token, ui_amount, source)
.await
.unwrap();

process_test_command(
&config,
payer,
&[
"spl-token",
CommandName::Burn.into(),
&source.to_string(),
"10",
],
)
.await
.unwrap();

let account = config.rpc_client.get_account(&source).await.unwrap();
let token_account = StateWithExtensionsOwned::<Account>::unpack(account.data).unwrap();
let amount = spl_token::ui_amount_to_amount(90.0, TEST_DECIMALS);
assert_eq!(token_account.base.amount, amount);

process_test_command(
&config,
payer,
&[
"spl-token",
CommandName::Burn.into(),
&source.to_string(),
"ALL",
],
)
.await
.unwrap();

let account = config.rpc_client.get_account(&source).await.unwrap();
let token_account = StateWithExtensionsOwned::<Account>::unpack(account.data).unwrap();
let amount = spl_token::ui_amount_to_amount(0.0, TEST_DECIMALS);
assert_eq!(token_account.base.amount, amount);

let result = process_test_command(
&config,
payer,
&[
"spl-token",
CommandName::Burn.into(),
&source.to_string(),
"10",
],
)
.await;
assert!(result.is_err());

// Use of the ALL keyword not supported with offline signing
config.sign_only = true;
let result = process_test_command(
&config,
payer,
&[
"spl-token",
CommandName::Burn.into(),
&source.to_string(),
"ALL",
],
)
.await;
assert!(result.is_err_and(|err| err.to_string().contains("ALL")));
}
}

async fn burn_with_account_delegate(test_validator: &TestValidator, payer: &Keypair) {
for program_id in VALID_TOKEN_PROGRAM_IDS.iter() {
let config = test_config_with_default_signer(test_validator, payer, program_id);
Expand Down Expand Up @@ -1600,46 +1677,6 @@ async fn burn_with_account_delegate(test_validator: &TestValidator, payer: &Keyp
}
}

async fn close_mint(test_validator: &TestValidator, payer: &Keypair) {
let config = test_config_with_default_signer(test_validator, payer, &spl_token_2022::id());

let token = Keypair::new();
let token_pubkey = token.pubkey();
let token_keypair_file = NamedTempFile::new().unwrap();
write_keypair_file(&token, &token_keypair_file).unwrap();
process_test_command(
&config,
payer,
&[
"spl-token",
CommandName::CreateToken.into(),
token_keypair_file.path().to_str().unwrap(),
"--enable-close",
],
)
.await
.unwrap();

let account = config.rpc_client.get_account(&token_pubkey).await.unwrap();
let test_mint = StateWithExtensionsOwned::<Mint>::unpack(account.data);
assert!(test_mint.is_ok());

process_test_command(
&config,
payer,
&[
"spl-token",
CommandName::CloseMint.into(),
&token_pubkey.to_string(),
],
)
.await
.unwrap();

let account = config.rpc_client.get_account(&token_pubkey).await;
assert!(account.is_err());
}

async fn burn_with_permanent_delegate(test_validator: &TestValidator, payer: &Keypair) {
let config = test_config_with_default_signer(test_validator, payer, &spl_token_2022::id());

Expand Down Expand Up @@ -1797,6 +1834,46 @@ async fn transfer_with_permanent_delegate(test_validator: &TestValidator, payer:
assert_eq!(ui_account.token_amount.amount, format!("{amount}"));
}

async fn close_mint(test_validator: &TestValidator, payer: &Keypair) {
let config = test_config_with_default_signer(test_validator, payer, &spl_token_2022::id());

let token = Keypair::new();
let token_pubkey = token.pubkey();
let token_keypair_file = NamedTempFile::new().unwrap();
write_keypair_file(&token, &token_keypair_file).unwrap();
process_test_command(
&config,
payer,
&[
"spl-token",
CommandName::CreateToken.into(),
token_keypair_file.path().to_str().unwrap(),
"--enable-close",
],
)
.await
.unwrap();

let account = config.rpc_client.get_account(&token_pubkey).await.unwrap();
let test_mint = StateWithExtensionsOwned::<Mint>::unpack(account.data);
assert!(test_mint.is_ok());

process_test_command(
&config,
payer,
&[
"spl-token",
CommandName::CloseMint.into(),
&token_pubkey.to_string(),
],
)
.await
.unwrap();

let account = config.rpc_client.get_account(&token_pubkey).await;
assert!(account.is_err());
}

async fn required_transfer_memos(test_validator: &TestValidator, payer: &Keypair) {
let program_id = spl_token_2022::id();
let config = test_config_with_default_signer(test_validator, payer, &program_id);
Expand Down

0 comments on commit 714e869

Please sign in to comment.