transfer now requires --allow-unfunded-recipient if the recipient doesn't exist

This commit is contained in:
Michael Vines 2021-03-22 11:10:44 -07:00 committed by mergify[bot]
parent d76ad33597
commit 3dff5c9dee
8 changed files with 108 additions and 7 deletions

View File

@ -360,6 +360,7 @@ pub enum CliCommand {
from: SignerIndex, from: SignerIndex,
sign_only: bool, sign_only: bool,
dump_transaction_message: bool, dump_transaction_message: bool,
allow_unfunded_recipient: bool,
no_wait: bool, no_wait: bool,
blockhash_query: BlockhashQuery, blockhash_query: BlockhashQuery,
nonce_account: Option<Pubkey>, nonce_account: Option<Pubkey>,
@ -865,6 +866,7 @@ pub fn parse_command(
let (fee_payer, fee_payer_pubkey) = let (fee_payer, fee_payer_pubkey) =
signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?; signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
let (from, from_pubkey) = signer_of(matches, "from", wallet_manager)?; let (from, from_pubkey) = signer_of(matches, "from", wallet_manager)?;
let allow_unfunded_recipient = matches.is_present("allow_unfunded_recipient");
let mut bulk_signers = vec![fee_payer, from]; let mut bulk_signers = vec![fee_payer, from];
if nonce_account.is_some() { if nonce_account.is_some() {
@ -886,6 +888,7 @@ pub fn parse_command(
to, to,
sign_only, sign_only,
dump_transaction_message, dump_transaction_message,
allow_unfunded_recipient,
no_wait, no_wait,
blockhash_query, blockhash_query,
nonce_account, nonce_account,
@ -1139,6 +1142,7 @@ fn process_transfer(
from: SignerIndex, from: SignerIndex,
sign_only: bool, sign_only: bool,
dump_transaction_message: bool, dump_transaction_message: bool,
allow_unfunded_recipient: bool,
no_wait: bool, no_wait: bool,
blockhash_query: &BlockhashQuery, blockhash_query: &BlockhashQuery,
nonce_account: Option<&Pubkey>, nonce_account: Option<&Pubkey>,
@ -1153,6 +1157,21 @@ fn process_transfer(
let (recent_blockhash, fee_calculator) = let (recent_blockhash, fee_calculator) =
blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?; blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?;
if !allow_unfunded_recipient {
let recipient_balance = rpc_client
.get_balance_with_commitment(to, config.commitment)?
.value;
if recipient_balance == 0 {
return Err(format!(
"The recipient address ({}) is not funded. \
Add `--allow-unfunded-recipient` to complete the transfer \
",
to
)
.into());
}
}
let nonce_authority = config.signers[nonce_authority]; let nonce_authority = config.signers[nonce_authority];
let fee_payer = config.signers[fee_payer]; let fee_payer = config.signers[fee_payer];
@ -1822,6 +1841,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
from, from,
sign_only, sign_only,
dump_transaction_message, dump_transaction_message,
allow_unfunded_recipient,
no_wait, no_wait,
ref blockhash_query, ref blockhash_query,
ref nonce_account, ref nonce_account,
@ -1837,6 +1857,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
*from, *from,
*sign_only, *sign_only,
*dump_transaction_message, *dump_transaction_message,
*allow_unfunded_recipient,
*no_wait, *no_wait,
blockhash_query, blockhash_query,
nonce_account.as_ref(), nonce_account.as_ref(),
@ -2205,6 +2226,12 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.requires("derived_address_seed") .requires("derived_address_seed")
.hidden(true) .hidden(true)
) )
.arg(
Arg::with_name("allow_unfunded_recipient")
.long("allow-unfunded-recipient")
.takes_value(false)
.help("Complete the transfer even if the recipient address is not funded")
)
.offline_args() .offline_args()
.nonce_args(false) .nonce_args(false)
.arg(fee_payer_arg()), .arg(fee_payer_arg()),
@ -2908,6 +2935,7 @@ mod tests {
from: 0, from: 0,
sign_only: false, sign_only: false,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: false,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None, nonce_account: None,
@ -2933,6 +2961,7 @@ mod tests {
from: 0, from: 0,
sign_only: false, sign_only: false,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: false,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None, nonce_account: None,
@ -2945,11 +2974,12 @@ mod tests {
} }
); );
// Test Transfer no-wait // Test Transfer no-wait and --allow-unfunded-recipient
let test_transfer = test_commands.clone().get_matches_from(vec![ let test_transfer = test_commands.clone().get_matches_from(vec![
"test", "test",
"transfer", "transfer",
"--no-wait", "--no-wait",
"--allow-unfunded-recipient",
&to_string, &to_string,
"42", "42",
]); ]);
@ -2962,6 +2992,7 @@ mod tests {
from: 0, from: 0,
sign_only: false, sign_only: false,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: true, no_wait: true,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None, nonce_account: None,
@ -2995,6 +3026,7 @@ mod tests {
from: 0, from: 0,
sign_only: true, sign_only: true,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: false,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::None(blockhash), blockhash_query: BlockhashQuery::None(blockhash),
nonce_account: None, nonce_account: None,
@ -3033,6 +3065,7 @@ mod tests {
from: 0, from: 0,
sign_only: false, sign_only: false,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: false,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator( blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::Cluster, blockhash_query::Source::Cluster,
@ -3075,6 +3108,7 @@ mod tests {
from: 0, from: 0,
sign_only: false, sign_only: false,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: false,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator( blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_address), blockhash_query::Source::NonceAccount(nonce_address),
@ -3115,6 +3149,7 @@ mod tests {
from: 0, from: 0,
sign_only: false, sign_only: false,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: false,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None, nonce_account: None,

View File

@ -294,6 +294,7 @@ fn test_create_account_with_seed() {
from: 0, from: 0,
sign_only: true, sign_only: true,
dump_transaction_message: true, dump_transaction_message: true,
allow_unfunded_recipient: true,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::None(nonce_hash), blockhash_query: BlockhashQuery::None(nonce_hash),
nonce_account: Some(nonce_address), nonce_account: Some(nonce_address),
@ -318,6 +319,7 @@ fn test_create_account_with_seed() {
from: 0, from: 0,
sign_only: false, sign_only: false,
dump_transaction_message: true, dump_transaction_message: true,
allow_unfunded_recipient: true,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator( blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_address), blockhash_query::Source::NonceAccount(nonce_address),

View File

@ -52,6 +52,7 @@ fn test_transfer() {
from: 0, from: 0,
sign_only: false, sign_only: false,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None, nonce_account: None,
@ -71,6 +72,7 @@ fn test_transfer() {
from: 0, from: 0,
sign_only: false, sign_only: false,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None, nonce_account: None,
@ -102,6 +104,7 @@ fn test_transfer() {
from: 0, from: 0,
sign_only: true, sign_only: true,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::None(blockhash), blockhash_query: BlockhashQuery::None(blockhash),
nonce_account: None, nonce_account: None,
@ -122,6 +125,7 @@ fn test_transfer() {
from: 0, from: 0,
sign_only: false, sign_only: false,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash), blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
nonce_account: None, nonce_account: None,
@ -167,6 +171,7 @@ fn test_transfer() {
from: 0, from: 0,
sign_only: false, sign_only: false,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator( blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_account.pubkey()), blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
@ -219,6 +224,7 @@ fn test_transfer() {
from: 0, from: 0,
sign_only: true, sign_only: true,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::None(nonce_hash), blockhash_query: BlockhashQuery::None(nonce_hash),
nonce_account: Some(nonce_account.pubkey()), nonce_account: Some(nonce_account.pubkey()),
@ -238,6 +244,7 @@ fn test_transfer() {
from: 0, from: 0,
sign_only: false, sign_only: false,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator( blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_account.pubkey()), blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
@ -307,6 +314,7 @@ fn test_transfer_multisession_signing() {
from: 1, from: 1,
sign_only: true, sign_only: true,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::None(blockhash), blockhash_query: BlockhashQuery::None(blockhash),
nonce_account: None, nonce_account: None,
@ -336,6 +344,7 @@ fn test_transfer_multisession_signing() {
from: 1, from: 1,
sign_only: true, sign_only: true,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::None(blockhash), blockhash_query: BlockhashQuery::None(blockhash),
nonce_account: None, nonce_account: None,
@ -362,6 +371,7 @@ fn test_transfer_multisession_signing() {
from: 1, from: 1,
sign_only: false, sign_only: false,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash), blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
nonce_account: None, nonce_account: None,
@ -410,6 +420,7 @@ fn test_transfer_all() {
from: 0, from: 0,
sign_only: false, sign_only: false,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None, nonce_account: None,
@ -423,6 +434,53 @@ fn test_transfer_all() {
check_recent_balance(49_999, &rpc_client, &recipient_pubkey); check_recent_balance(49_999, &rpc_client, &recipient_pubkey);
} }
#[test]
fn test_transfer_unfunded_recipient() {
solana_logger::setup();
let mint_keypair = Keypair::new();
let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
let faucet_addr = run_local_faucet(mint_keypair, None);
let rpc_client =
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
let default_signer = Keypair::new();
let mut config = CliConfig::recent_for_tests();
config.json_rpc_url = test_validator.rpc_url();
config.signers = vec![&default_signer];
let sender_pubkey = config.signers[0].pubkey();
let recipient_pubkey = Pubkey::new(&[1u8; 32]);
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &sender_pubkey, 50_000, &config)
.unwrap();
check_recent_balance(50_000, &rpc_client, &sender_pubkey);
check_recent_balance(0, &rpc_client, &recipient_pubkey);
check_ready(&rpc_client);
// Plain ole transfer
config.command = CliCommand::Transfer {
amount: SpendAmount::All,
to: recipient_pubkey,
from: 0,
sign_only: false,
dump_transaction_message: false,
allow_unfunded_recipient: false,
no_wait: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
nonce_authority: 0,
fee_payer: 0,
derived_address_seed: None,
derived_address_program_id: None,
};
// Expect failure due to unfunded recipient and the lack of the `allow_unfunded_recipient` flag
process_command(&config).unwrap_err();
}
#[test] #[test]
fn test_transfer_with_seed() { fn test_transfer_with_seed() {
solana_logger::setup(); solana_logger::setup();
@ -466,6 +524,7 @@ fn test_transfer_with_seed() {
from: 0, from: 0,
sign_only: false, sign_only: false,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None, nonce_account: None,

View File

@ -72,6 +72,7 @@ fn test_vote_authorize_and_withdraw() {
from: 0, from: 0,
sign_only: false, sign_only: false,
dump_transaction_message: false, dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false, no_wait: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster), blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None, nonce_account: None,

View File

@ -71,7 +71,7 @@ with the private keypair corresponding to the sender's public key in the
transaction. transaction.
```bash ```bash
solana transfer --from <KEYPAIR> <RECIPIENT_ACCOUNT_ADDRESS> 5 --url https://devnet.solana.com --fee-payer <KEYPAIR> solana transfer --from <KEYPAIR> <RECIPIENT_ACCOUNT_ADDRESS> 5 --allow-unfunded-recipient --url https://devnet.solana.com --fee-payer <KEYPAIR>
``` ```
where you replace `<KEYPAIR>` with the path to a keypair in your first wallet, where you replace `<KEYPAIR>` with the path to a keypair in your first wallet,
@ -118,7 +118,7 @@ Save this seed phrase to recover your new keypair:
clump panic cousin hurt coast charge engage fall eager urge win love # If this was a real wallet, never share these words on the internet like this! clump panic cousin hurt coast charge engage fall eager urge win love # If this was a real wallet, never share these words on the internet like this!
==================================================================== ====================================================================
$ solana transfer --from my_solana_wallet.json 7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv 5 --url https://devnet.solana.com --fee-payer my_solana_wallet.json # Transferring tokens to the public address of the paper wallet $ solana transfer --from my_solana_wallet.json 7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv 5 --allow-unfunded-recipient --url https://devnet.solana.com --fee-payer my_solana_wallet.json # Transferring tokens to the public address of the paper wallet
3gmXvykAd1nCQQ7MjosaHLf69Xyaqyq1qw2eu1mgPyYXd5G4v1rihhg1CiRw35b9fHzcftGKKEu4mbUeXY2pEX2z # This is the transaction signature 3gmXvykAd1nCQQ7MjosaHLf69Xyaqyq1qw2eu1mgPyYXd5G4v1rihhg1CiRw35b9fHzcftGKKEu4mbUeXY2pEX2z # This is the transaction signature
$ solana balance DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK --url https://devnet.solana.com $ solana balance DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK --url https://devnet.solana.com

View File

@ -388,7 +388,7 @@ will wait and track progress on stderr until the transaction has been finalized
by the cluster. If the transaction fails, it will report any transaction errors. by the cluster. If the transaction fails, it will report any transaction errors.
```bash ```bash
solana transfer <USER_ADDRESS> <AMOUNT> --keypair <KEYPAIR> --url http://localhost:8899 solana transfer <USER_ADDRESS> <AMOUNT> --allow-unfunded-recipient --keypair <KEYPAIR> --url http://localhost:8899
``` ```
The [Solana Javascript SDK](https://github.com/solana-labs/solana-web3.js) The [Solana Javascript SDK](https://github.com/solana-labs/solana-web3.js)
@ -420,7 +420,7 @@ In the command-line tool, pass the `--no-wait` argument to send a transfer
asynchronously, and include your recent blockhash with the `--blockhash` argument: asynchronously, and include your recent blockhash with the `--blockhash` argument:
```bash ```bash
solana transfer <USER_ADDRESS> <AMOUNT> --no-wait --blockhash <RECENT_BLOCKHASH> --keypair <KEYPAIR> --url http://localhost:8899 solana transfer <USER_ADDRESS> <AMOUNT> --no-wait --allow-unfunded-recipient --blockhash <RECENT_BLOCKHASH> --keypair <KEYPAIR> --url http://localhost:8899
``` ```
You can also build, sign, and serialize the transaction manually, and fire it off to You can also build, sign, and serialize the transaction manually, and fire it off to

View File

@ -102,7 +102,9 @@ if ((airdrops_enabled)); then
echo "--keypair argument must be provided" echo "--keypair argument must be provided"
exit 1 exit 1
fi fi
$solana_cli "${common_args[@]}" --keypair "$SOLANA_CONFIG_DIR/faucet.json" transfer "$keypair" "$stake_sol" $solana_cli \
"${common_args[@]}" --keypair "$SOLANA_CONFIG_DIR/faucet.json" \
transfer --allow-unfunded-recipient "$keypair" "$stake_sol"
fi fi
if [[ -n $keypair ]]; then if [[ -n $keypair ]]; then

View File

@ -274,7 +274,9 @@ setup_validator_accounts() {
echo "Adding $node_sol to validator identity account:" echo "Adding $node_sol to validator identity account:"
( (
set -x set -x
$solana_cli --keypair "$SOLANA_CONFIG_DIR/faucet.json" --url "$rpc_url" transfer "$identity" "$node_sol" $solana_cli \
--keypair "$SOLANA_CONFIG_DIR/faucet.json" --url "$rpc_url" \
transfer --allow-unfunded-recipient "$identity" "$node_sol"
) || return $? ) || return $?
fi fi