Skip to content

Commit

Permalink
transfer now requires --allow-unfunded-recipient if the recipient doe…
Browse files Browse the repository at this point in the history
…sn't exist
  • Loading branch information
mvines authored and mergify[bot] committed Mar 23, 2021
1 parent d76ad33 commit 3dff5c9
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 7 deletions.
37 changes: 36 additions & 1 deletion cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ pub enum CliCommand {
from: SignerIndex,
sign_only: bool,
dump_transaction_message: bool,
allow_unfunded_recipient: bool,
no_wait: bool,
blockhash_query: BlockhashQuery,
nonce_account: Option<Pubkey>,
Expand Down Expand Up @@ -865,6 +866,7 @@ pub fn parse_command(
let (fee_payer, fee_payer_pubkey) =
signer_of(matches, FEE_PAYER_ARG.name, 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];
if nonce_account.is_some() {
Expand All @@ -886,6 +888,7 @@ pub fn parse_command(
to,
sign_only,
dump_transaction_message,
allow_unfunded_recipient,
no_wait,
blockhash_query,
nonce_account,
Expand Down Expand Up @@ -1139,6 +1142,7 @@ fn process_transfer(
from: SignerIndex,
sign_only: bool,
dump_transaction_message: bool,
allow_unfunded_recipient: bool,
no_wait: bool,
blockhash_query: &BlockhashQuery,
nonce_account: Option<&Pubkey>,
Expand All @@ -1153,6 +1157,21 @@ fn process_transfer(
let (recent_blockhash, fee_calculator) =
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 fee_payer = config.signers[fee_payer];

Expand Down Expand Up @@ -1822,6 +1841,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
from,
sign_only,
dump_transaction_message,
allow_unfunded_recipient,
no_wait,
ref blockhash_query,
ref nonce_account,
Expand All @@ -1837,6 +1857,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
*from,
*sign_only,
*dump_transaction_message,
*allow_unfunded_recipient,
*no_wait,
blockhash_query,
nonce_account.as_ref(),
Expand Down Expand Up @@ -2205,6 +2226,12 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
.requires("derived_address_seed")
.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()
.nonce_args(false)
.arg(fee_payer_arg()),
Expand Down Expand Up @@ -2908,6 +2935,7 @@ mod tests {
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,
Expand All @@ -2933,6 +2961,7 @@ mod tests {
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,
Expand All @@ -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![
"test",
"transfer",
"--no-wait",
"--allow-unfunded-recipient",
&to_string,
"42",
]);
Expand All @@ -2962,6 +2992,7 @@ mod tests {
from: 0,
sign_only: false,
dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: true,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
Expand Down Expand Up @@ -2995,6 +3026,7 @@ mod tests {
from: 0,
sign_only: true,
dump_transaction_message: false,
allow_unfunded_recipient: false,
no_wait: false,
blockhash_query: BlockhashQuery::None(blockhash),
nonce_account: None,
Expand Down Expand Up @@ -3033,6 +3065,7 @@ mod tests {
from: 0,
sign_only: false,
dump_transaction_message: false,
allow_unfunded_recipient: false,
no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::Cluster,
Expand Down Expand Up @@ -3075,6 +3108,7 @@ mod tests {
from: 0,
sign_only: false,
dump_transaction_message: false,
allow_unfunded_recipient: false,
no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_address),
Expand Down Expand Up @@ -3115,6 +3149,7 @@ mod tests {
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,
Expand Down
2 changes: 2 additions & 0 deletions cli/tests/nonce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ fn test_create_account_with_seed() {
from: 0,
sign_only: true,
dump_transaction_message: true,
allow_unfunded_recipient: true,
no_wait: false,
blockhash_query: BlockhashQuery::None(nonce_hash),
nonce_account: Some(nonce_address),
Expand All @@ -318,6 +319,7 @@ fn test_create_account_with_seed() {
from: 0,
sign_only: false,
dump_transaction_message: true,
allow_unfunded_recipient: true,
no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_address),
Expand Down
59 changes: 59 additions & 0 deletions cli/tests/transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ fn test_transfer() {
from: 0,
sign_only: false,
dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
Expand All @@ -71,6 +72,7 @@ fn test_transfer() {
from: 0,
sign_only: false,
dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
Expand Down Expand Up @@ -102,6 +104,7 @@ fn test_transfer() {
from: 0,
sign_only: true,
dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false,
blockhash_query: BlockhashQuery::None(blockhash),
nonce_account: None,
Expand All @@ -122,6 +125,7 @@ fn test_transfer() {
from: 0,
sign_only: false,
dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
nonce_account: None,
Expand Down Expand Up @@ -167,6 +171,7 @@ fn test_transfer() {
from: 0,
sign_only: false,
dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
Expand Down Expand Up @@ -219,6 +224,7 @@ fn test_transfer() {
from: 0,
sign_only: true,
dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false,
blockhash_query: BlockhashQuery::None(nonce_hash),
nonce_account: Some(nonce_account.pubkey()),
Expand All @@ -238,6 +244,7 @@ fn test_transfer() {
from: 0,
sign_only: false,
dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator(
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
Expand Down Expand Up @@ -307,6 +314,7 @@ fn test_transfer_multisession_signing() {
from: 1,
sign_only: true,
dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false,
blockhash_query: BlockhashQuery::None(blockhash),
nonce_account: None,
Expand Down Expand Up @@ -336,6 +344,7 @@ fn test_transfer_multisession_signing() {
from: 1,
sign_only: true,
dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false,
blockhash_query: BlockhashQuery::None(blockhash),
nonce_account: None,
Expand All @@ -362,6 +371,7 @@ fn test_transfer_multisession_signing() {
from: 1,
sign_only: false,
dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false,
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
nonce_account: None,
Expand Down Expand Up @@ -410,6 +420,7 @@ fn test_transfer_all() {
from: 0,
sign_only: false,
dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
Expand All @@ -423,6 +434,53 @@ fn test_transfer_all() {
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]
fn test_transfer_with_seed() {
solana_logger::setup();
Expand Down Expand Up @@ -466,6 +524,7 @@ fn test_transfer_with_seed() {
from: 0,
sign_only: false,
dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
Expand Down
1 change: 1 addition & 0 deletions cli/tests/vote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ fn test_vote_authorize_and_withdraw() {
from: 0,
sign_only: false,
dump_transaction_message: false,
allow_unfunded_recipient: true,
no_wait: false,
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
nonce_account: None,
Expand Down
4 changes: 2 additions & 2 deletions docs/src/cli/transfer-tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ with the private keypair corresponding to the sender's public key in the
transaction.

```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,
Expand Down Expand Up @@ -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!
====================================================================

$ 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

$ solana balance DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK --url https://devnet.solana.com
Expand Down
4 changes: 2 additions & 2 deletions docs/src/integrations/exchange.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

```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)
Expand Down Expand Up @@ -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:

```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
Expand Down
4 changes: 3 additions & 1 deletion multinode-demo/delegate-stake.sh
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ if ((airdrops_enabled)); then
echo "--keypair argument must be provided"
exit 1
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

if [[ -n $keypair ]]; then
Expand Down
4 changes: 3 additions & 1 deletion multinode-demo/validator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,9 @@ setup_validator_accounts() {
echo "Adding $node_sol to validator identity account:"
(
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 $?
fi

Expand Down

0 comments on commit 3dff5c9

Please sign in to comment.