transfer.rs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. #![allow(clippy::arithmetic_side_effects)]
  2. use {
  3. solana_cli::{
  4. check_balance,
  5. cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
  6. spend_utils::SpendAmount,
  7. test_utils::check_ready,
  8. },
  9. solana_cli_output::{parse_sign_only_reply_string, OutputFormat},
  10. solana_commitment_config::CommitmentConfig,
  11. solana_compute_budget_interface::ComputeBudgetInstruction,
  12. solana_faucet::faucet::run_local_faucet_with_unique_port_for_tests,
  13. solana_fee_structure::FeeStructure,
  14. solana_keypair::{keypair_from_seed, Keypair},
  15. solana_message::Message,
  16. solana_native_token::LAMPORTS_PER_SOL,
  17. solana_net_utils::SocketAddrSpace,
  18. solana_nonce::state::State as NonceState,
  19. solana_pubkey::Pubkey,
  20. solana_rpc_client::rpc_client::RpcClient,
  21. solana_rpc_client_nonce_utils::blockhash_query::{self, BlockhashQuery},
  22. solana_signer::{null_signer::NullSigner, Signer},
  23. solana_stake_interface as stake,
  24. solana_system_interface::instruction as system_instruction,
  25. solana_test_validator::TestValidator,
  26. test_case::test_case,
  27. };
  28. #[test_case(true; "Skip Preflight")]
  29. #[test_case(false; "Don`t skip Preflight")]
  30. fn test_transfer(skip_preflight: bool) {
  31. agave_logger::setup();
  32. let fee_one_sig = FeeStructure::default().get_max_fee(1, 0);
  33. let fee_two_sig = FeeStructure::default().get_max_fee(2, 0);
  34. let mint_keypair = Keypair::new();
  35. let mint_pubkey = mint_keypair.pubkey();
  36. let faucet_addr = run_local_faucet_with_unique_port_for_tests(mint_keypair);
  37. let test_validator = TestValidator::with_custom_fees(
  38. mint_pubkey,
  39. fee_one_sig,
  40. Some(faucet_addr),
  41. SocketAddrSpace::Unspecified,
  42. );
  43. let rpc_client =
  44. RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
  45. let default_signer = Keypair::new();
  46. let default_offline_signer = Keypair::new();
  47. let mut config = CliConfig::recent_for_tests();
  48. config.json_rpc_url = test_validator.rpc_url();
  49. config.signers = vec![&default_signer];
  50. config.send_transaction_config.skip_preflight = skip_preflight;
  51. let sender_pubkey = config.signers[0].pubkey();
  52. let recipient_pubkey = Pubkey::from([1u8; 32]);
  53. request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, 5 * LAMPORTS_PER_SOL)
  54. .unwrap();
  55. check_balance!(5 * LAMPORTS_PER_SOL, &rpc_client, &sender_pubkey);
  56. check_balance!(0, &rpc_client, &recipient_pubkey);
  57. check_ready(&rpc_client);
  58. // Plain ole transfer
  59. config.command = CliCommand::Transfer {
  60. amount: SpendAmount::Some(LAMPORTS_PER_SOL),
  61. to: recipient_pubkey,
  62. from: 0,
  63. sign_only: false,
  64. dump_transaction_message: false,
  65. allow_unfunded_recipient: true,
  66. no_wait: false,
  67. blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
  68. nonce_account: None,
  69. nonce_authority: 0,
  70. memo: None,
  71. fee_payer: 0,
  72. derived_address_seed: None,
  73. derived_address_program_id: None,
  74. compute_unit_price: None,
  75. };
  76. process_command(&config).unwrap();
  77. check_balance!(
  78. 4 * LAMPORTS_PER_SOL - fee_one_sig,
  79. &rpc_client,
  80. &sender_pubkey
  81. );
  82. check_balance!(LAMPORTS_PER_SOL, &rpc_client, &recipient_pubkey);
  83. // Plain ole transfer, failure due to InsufficientFundsForSpendAndFee
  84. config.command = CliCommand::Transfer {
  85. amount: SpendAmount::Some(4 * LAMPORTS_PER_SOL),
  86. to: recipient_pubkey,
  87. from: 0,
  88. sign_only: false,
  89. dump_transaction_message: false,
  90. allow_unfunded_recipient: true,
  91. no_wait: false,
  92. blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
  93. nonce_account: None,
  94. nonce_authority: 0,
  95. memo: None,
  96. fee_payer: 0,
  97. derived_address_seed: None,
  98. derived_address_program_id: None,
  99. compute_unit_price: None,
  100. };
  101. assert!(process_command(&config).is_err());
  102. check_balance!(
  103. 4 * LAMPORTS_PER_SOL - fee_one_sig,
  104. &rpc_client,
  105. &sender_pubkey
  106. );
  107. check_balance!(LAMPORTS_PER_SOL, &rpc_client, &recipient_pubkey);
  108. let mut offline = CliConfig::recent_for_tests();
  109. offline.json_rpc_url = String::default();
  110. offline.signers = vec![&default_offline_signer];
  111. // Verify we cannot contact the cluster
  112. offline.command = CliCommand::ClusterVersion;
  113. process_command(&offline).unwrap_err();
  114. let offline_pubkey = offline.signers[0].pubkey();
  115. request_and_confirm_airdrop(&rpc_client, &offline, &offline_pubkey, LAMPORTS_PER_SOL).unwrap();
  116. check_balance!(LAMPORTS_PER_SOL, &rpc_client, &offline_pubkey);
  117. // Offline transfer
  118. let blockhash = rpc_client.get_latest_blockhash().unwrap();
  119. offline.command = CliCommand::Transfer {
  120. amount: SpendAmount::Some(LAMPORTS_PER_SOL / 2),
  121. to: recipient_pubkey,
  122. from: 0,
  123. sign_only: true,
  124. dump_transaction_message: false,
  125. allow_unfunded_recipient: true,
  126. no_wait: false,
  127. blockhash_query: BlockhashQuery::None(blockhash),
  128. nonce_account: None,
  129. nonce_authority: 0,
  130. memo: None,
  131. fee_payer: 0,
  132. derived_address_seed: None,
  133. derived_address_program_id: None,
  134. compute_unit_price: None,
  135. };
  136. offline.output_format = OutputFormat::JsonCompact;
  137. let sign_only_reply = process_command(&offline).unwrap();
  138. let sign_only = parse_sign_only_reply_string(&sign_only_reply);
  139. assert!(sign_only.has_all_signers());
  140. let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
  141. config.signers = vec![&offline_presigner];
  142. config.command = CliCommand::Transfer {
  143. amount: SpendAmount::Some(LAMPORTS_PER_SOL / 2),
  144. to: recipient_pubkey,
  145. from: 0,
  146. sign_only: false,
  147. dump_transaction_message: false,
  148. allow_unfunded_recipient: true,
  149. no_wait: false,
  150. blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
  151. nonce_account: None,
  152. nonce_authority: 0,
  153. memo: None,
  154. fee_payer: 0,
  155. derived_address_seed: None,
  156. derived_address_program_id: None,
  157. compute_unit_price: None,
  158. };
  159. process_command(&config).unwrap();
  160. check_balance!(
  161. LAMPORTS_PER_SOL / 2 - fee_one_sig,
  162. &rpc_client,
  163. &offline_pubkey
  164. );
  165. check_balance!(1_500_000_000, &rpc_client, &recipient_pubkey);
  166. // Create nonce account
  167. let nonce_account = keypair_from_seed(&[3u8; 32]).unwrap();
  168. let minimum_nonce_balance = rpc_client
  169. .get_minimum_balance_for_rent_exemption(NonceState::size())
  170. .unwrap();
  171. config.signers = vec![&default_signer, &nonce_account];
  172. config.command = CliCommand::CreateNonceAccount {
  173. nonce_account: 1,
  174. seed: None,
  175. nonce_authority: None,
  176. memo: None,
  177. amount: SpendAmount::Some(minimum_nonce_balance),
  178. compute_unit_price: None,
  179. };
  180. process_command(&config).unwrap();
  181. check_balance!(
  182. 4 * LAMPORTS_PER_SOL - fee_one_sig - fee_two_sig - minimum_nonce_balance,
  183. &rpc_client,
  184. &sender_pubkey,
  185. );
  186. // Fetch nonce hash
  187. let nonce_hash = solana_rpc_client_nonce_utils::get_account_with_commitment(
  188. &rpc_client,
  189. &nonce_account.pubkey(),
  190. CommitmentConfig::processed(),
  191. )
  192. .and_then(|ref a| solana_rpc_client_nonce_utils::data_from_account(a))
  193. .unwrap()
  194. .blockhash();
  195. // Nonced transfer
  196. config.signers = vec![&default_signer];
  197. config.command = CliCommand::Transfer {
  198. amount: SpendAmount::Some(LAMPORTS_PER_SOL),
  199. to: recipient_pubkey,
  200. from: 0,
  201. sign_only: false,
  202. dump_transaction_message: false,
  203. allow_unfunded_recipient: true,
  204. no_wait: false,
  205. blockhash_query: BlockhashQuery::FeeCalculator(
  206. blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
  207. nonce_hash,
  208. ),
  209. nonce_account: Some(nonce_account.pubkey()),
  210. nonce_authority: 0,
  211. memo: None,
  212. fee_payer: 0,
  213. derived_address_seed: None,
  214. derived_address_program_id: None,
  215. compute_unit_price: None,
  216. };
  217. process_command(&config).unwrap();
  218. check_balance!(
  219. 3 * LAMPORTS_PER_SOL - 2 * fee_one_sig - fee_two_sig - minimum_nonce_balance,
  220. &rpc_client,
  221. &sender_pubkey,
  222. );
  223. check_balance!(2_500_000_000, &rpc_client, &recipient_pubkey);
  224. let new_nonce_hash = solana_rpc_client_nonce_utils::get_account_with_commitment(
  225. &rpc_client,
  226. &nonce_account.pubkey(),
  227. CommitmentConfig::processed(),
  228. )
  229. .and_then(|ref a| solana_rpc_client_nonce_utils::data_from_account(a))
  230. .unwrap()
  231. .blockhash();
  232. assert_ne!(nonce_hash, new_nonce_hash);
  233. // Assign nonce authority to offline
  234. config.signers = vec![&default_signer];
  235. config.command = CliCommand::AuthorizeNonceAccount {
  236. nonce_account: nonce_account.pubkey(),
  237. nonce_authority: 0,
  238. memo: None,
  239. new_authority: offline_pubkey,
  240. compute_unit_price: None,
  241. };
  242. process_command(&config).unwrap();
  243. check_balance!(
  244. 3 * LAMPORTS_PER_SOL - 3 * fee_one_sig - fee_two_sig - minimum_nonce_balance,
  245. &rpc_client,
  246. &sender_pubkey,
  247. );
  248. // Fetch nonce hash
  249. let nonce_hash = solana_rpc_client_nonce_utils::get_account_with_commitment(
  250. &rpc_client,
  251. &nonce_account.pubkey(),
  252. CommitmentConfig::processed(),
  253. )
  254. .and_then(|ref a| solana_rpc_client_nonce_utils::data_from_account(a))
  255. .unwrap()
  256. .blockhash();
  257. // Offline, nonced transfer
  258. offline.signers = vec![&default_offline_signer];
  259. offline.command = CliCommand::Transfer {
  260. amount: SpendAmount::Some(400_000_000),
  261. to: recipient_pubkey,
  262. from: 0,
  263. sign_only: true,
  264. dump_transaction_message: false,
  265. allow_unfunded_recipient: true,
  266. no_wait: false,
  267. blockhash_query: BlockhashQuery::None(nonce_hash),
  268. nonce_account: Some(nonce_account.pubkey()),
  269. nonce_authority: 0,
  270. memo: None,
  271. fee_payer: 0,
  272. derived_address_seed: None,
  273. derived_address_program_id: None,
  274. compute_unit_price: None,
  275. };
  276. let sign_only_reply = process_command(&offline).unwrap();
  277. let sign_only = parse_sign_only_reply_string(&sign_only_reply);
  278. assert!(sign_only.has_all_signers());
  279. let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
  280. config.signers = vec![&offline_presigner];
  281. config.command = CliCommand::Transfer {
  282. amount: SpendAmount::Some(400_000_000),
  283. to: recipient_pubkey,
  284. from: 0,
  285. sign_only: false,
  286. dump_transaction_message: false,
  287. allow_unfunded_recipient: true,
  288. no_wait: false,
  289. blockhash_query: BlockhashQuery::FeeCalculator(
  290. blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
  291. sign_only.blockhash,
  292. ),
  293. nonce_account: Some(nonce_account.pubkey()),
  294. nonce_authority: 0,
  295. memo: None,
  296. fee_payer: 0,
  297. derived_address_seed: None,
  298. derived_address_program_id: None,
  299. compute_unit_price: None,
  300. };
  301. process_command(&config).unwrap();
  302. check_balance!(
  303. LAMPORTS_PER_SOL / 10 - 2 * fee_one_sig,
  304. &rpc_client,
  305. &offline_pubkey
  306. );
  307. check_balance!(2_900_000_000, &rpc_client, &recipient_pubkey);
  308. }
  309. #[test]
  310. fn test_transfer_multisession_signing() {
  311. agave_logger::setup();
  312. let fee_one_sig = FeeStructure::default().get_max_fee(1, 0);
  313. let fee_two_sig = FeeStructure::default().get_max_fee(2, 0);
  314. let mint_keypair = Keypair::new();
  315. let mint_pubkey = mint_keypair.pubkey();
  316. let faucet_addr = run_local_faucet_with_unique_port_for_tests(mint_keypair);
  317. let test_validator = TestValidator::with_custom_fees(
  318. mint_pubkey,
  319. fee_one_sig,
  320. Some(faucet_addr),
  321. SocketAddrSpace::Unspecified,
  322. );
  323. let to_pubkey = Pubkey::from([1u8; 32]);
  324. let offline_from_signer = keypair_from_seed(&[2u8; 32]).unwrap();
  325. let offline_fee_payer_signer = keypair_from_seed(&[3u8; 32]).unwrap();
  326. let from_null_signer = NullSigner::new(&offline_from_signer.pubkey());
  327. // Setup accounts
  328. let rpc_client =
  329. RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
  330. request_and_confirm_airdrop(
  331. &rpc_client,
  332. &CliConfig::recent_for_tests(),
  333. &offline_from_signer.pubkey(),
  334. 43 * LAMPORTS_PER_SOL,
  335. )
  336. .unwrap();
  337. request_and_confirm_airdrop(
  338. &rpc_client,
  339. &CliConfig::recent_for_tests(),
  340. &offline_fee_payer_signer.pubkey(),
  341. LAMPORTS_PER_SOL + 2 * fee_two_sig,
  342. )
  343. .unwrap();
  344. check_balance!(
  345. 43 * LAMPORTS_PER_SOL,
  346. &rpc_client,
  347. &offline_from_signer.pubkey(),
  348. );
  349. check_balance!(
  350. LAMPORTS_PER_SOL + 2 * fee_two_sig,
  351. &rpc_client,
  352. &offline_fee_payer_signer.pubkey(),
  353. );
  354. check_balance!(0, &rpc_client, &to_pubkey);
  355. check_ready(&rpc_client);
  356. let blockhash = rpc_client.get_latest_blockhash().unwrap();
  357. // Offline fee-payer signs first
  358. let mut fee_payer_config = CliConfig::recent_for_tests();
  359. fee_payer_config.json_rpc_url = String::default();
  360. fee_payer_config.signers = vec![&offline_fee_payer_signer, &from_null_signer];
  361. // Verify we cannot contact the cluster
  362. fee_payer_config.command = CliCommand::ClusterVersion;
  363. process_command(&fee_payer_config).unwrap_err();
  364. fee_payer_config.command = CliCommand::Transfer {
  365. amount: SpendAmount::Some(42 * LAMPORTS_PER_SOL),
  366. to: to_pubkey,
  367. from: 1,
  368. sign_only: true,
  369. dump_transaction_message: false,
  370. allow_unfunded_recipient: true,
  371. no_wait: false,
  372. blockhash_query: BlockhashQuery::None(blockhash),
  373. nonce_account: None,
  374. nonce_authority: 0,
  375. memo: None,
  376. fee_payer: 0,
  377. derived_address_seed: None,
  378. derived_address_program_id: None,
  379. compute_unit_price: None,
  380. };
  381. fee_payer_config.output_format = OutputFormat::JsonCompact;
  382. let sign_only_reply = process_command(&fee_payer_config).unwrap();
  383. let sign_only = parse_sign_only_reply_string(&sign_only_reply);
  384. assert!(!sign_only.has_all_signers());
  385. let fee_payer_presigner = sign_only
  386. .presigner_of(&offline_fee_payer_signer.pubkey())
  387. .unwrap();
  388. // Now the offline fund source
  389. let mut from_config = CliConfig::recent_for_tests();
  390. from_config.json_rpc_url = String::default();
  391. from_config.signers = vec![&fee_payer_presigner, &offline_from_signer];
  392. // Verify we cannot contact the cluster
  393. from_config.command = CliCommand::ClusterVersion;
  394. process_command(&from_config).unwrap_err();
  395. from_config.command = CliCommand::Transfer {
  396. amount: SpendAmount::Some(42 * LAMPORTS_PER_SOL),
  397. to: to_pubkey,
  398. from: 1,
  399. sign_only: true,
  400. dump_transaction_message: false,
  401. allow_unfunded_recipient: true,
  402. no_wait: false,
  403. blockhash_query: BlockhashQuery::None(blockhash),
  404. nonce_account: None,
  405. nonce_authority: 0,
  406. memo: None,
  407. fee_payer: 0,
  408. derived_address_seed: None,
  409. derived_address_program_id: None,
  410. compute_unit_price: None,
  411. };
  412. from_config.output_format = OutputFormat::JsonCompact;
  413. let sign_only_reply = process_command(&from_config).unwrap();
  414. let sign_only = parse_sign_only_reply_string(&sign_only_reply);
  415. assert!(sign_only.has_all_signers());
  416. let from_presigner = sign_only
  417. .presigner_of(&offline_from_signer.pubkey())
  418. .unwrap();
  419. // Finally submit to the cluster
  420. let mut config = CliConfig::recent_for_tests();
  421. config.json_rpc_url = test_validator.rpc_url();
  422. config.signers = vec![&fee_payer_presigner, &from_presigner];
  423. config.command = CliCommand::Transfer {
  424. amount: SpendAmount::Some(42 * LAMPORTS_PER_SOL),
  425. to: to_pubkey,
  426. from: 1,
  427. sign_only: false,
  428. dump_transaction_message: false,
  429. allow_unfunded_recipient: true,
  430. no_wait: false,
  431. blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
  432. nonce_account: None,
  433. nonce_authority: 0,
  434. memo: None,
  435. fee_payer: 0,
  436. derived_address_seed: None,
  437. derived_address_program_id: None,
  438. compute_unit_price: None,
  439. };
  440. process_command(&config).unwrap();
  441. check_balance!(LAMPORTS_PER_SOL, &rpc_client, &offline_from_signer.pubkey(),);
  442. check_balance!(
  443. LAMPORTS_PER_SOL + fee_two_sig,
  444. &rpc_client,
  445. &offline_fee_payer_signer.pubkey(),
  446. );
  447. check_balance!(42 * LAMPORTS_PER_SOL, &rpc_client, &to_pubkey);
  448. }
  449. #[test_case(None; "default")]
  450. #[test_case(Some(100_000); "with_compute_unit_price")]
  451. fn test_transfer_all(compute_unit_price: Option<u64>) {
  452. agave_logger::setup();
  453. let lamports_per_signature = FeeStructure::default().get_max_fee(1, 0);
  454. let mint_keypair = Keypair::new();
  455. let mint_pubkey = mint_keypair.pubkey();
  456. let faucet_addr = run_local_faucet_with_unique_port_for_tests(mint_keypair);
  457. let test_validator = TestValidator::with_custom_fees(
  458. mint_pubkey,
  459. lamports_per_signature,
  460. Some(faucet_addr),
  461. SocketAddrSpace::Unspecified,
  462. );
  463. let rpc_client =
  464. RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
  465. let default_signer = Keypair::new();
  466. let recipient_pubkey = Pubkey::from([1u8; 32]);
  467. let fee = {
  468. let mut instructions = vec![system_instruction::transfer(
  469. &default_signer.pubkey(),
  470. &recipient_pubkey,
  471. 0,
  472. )];
  473. if let Some(compute_unit_price) = compute_unit_price {
  474. // This is brittle and will need to be updated if the compute unit
  475. // limit for the system program or compute budget program are changed,
  476. // or if they're converted to BPF.
  477. // See `solana_system_program::system_processor::DEFAULT_COMPUTE_UNITS`
  478. // and `solana_compute_budget_program::DEFAULT_COMPUTE_UNITS`
  479. instructions.push(ComputeBudgetInstruction::set_compute_unit_limit(450));
  480. instructions.push(ComputeBudgetInstruction::set_compute_unit_price(
  481. compute_unit_price,
  482. ));
  483. }
  484. let blockhash = rpc_client.get_latest_blockhash().unwrap();
  485. let sample_message =
  486. Message::new_with_blockhash(&instructions, Some(&default_signer.pubkey()), &blockhash);
  487. rpc_client.get_fee_for_message(&sample_message).unwrap()
  488. };
  489. let mut config = CliConfig::recent_for_tests();
  490. config.json_rpc_url = test_validator.rpc_url();
  491. config.signers = vec![&default_signer];
  492. let sender_pubkey = config.signers[0].pubkey();
  493. request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, 500_000).unwrap();
  494. check_balance!(500_000, &rpc_client, &sender_pubkey);
  495. check_balance!(0, &rpc_client, &recipient_pubkey);
  496. check_ready(&rpc_client);
  497. // Plain ole transfer
  498. config.command = CliCommand::Transfer {
  499. amount: SpendAmount::All,
  500. to: recipient_pubkey,
  501. from: 0,
  502. sign_only: false,
  503. dump_transaction_message: false,
  504. allow_unfunded_recipient: true,
  505. no_wait: false,
  506. blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
  507. nonce_account: None,
  508. nonce_authority: 0,
  509. memo: None,
  510. fee_payer: 0,
  511. derived_address_seed: None,
  512. derived_address_program_id: None,
  513. compute_unit_price,
  514. };
  515. process_command(&config).unwrap();
  516. check_balance!(0, &rpc_client, &sender_pubkey);
  517. check_balance!(500_000 - fee, &rpc_client, &recipient_pubkey);
  518. }
  519. #[test]
  520. fn test_transfer_unfunded_recipient() {
  521. agave_logger::setup();
  522. let mint_keypair = Keypair::new();
  523. let mint_pubkey = mint_keypair.pubkey();
  524. let faucet_addr = run_local_faucet_with_unique_port_for_tests(mint_keypair);
  525. let test_validator = TestValidator::with_custom_fees(
  526. mint_pubkey,
  527. 1,
  528. Some(faucet_addr),
  529. SocketAddrSpace::Unspecified,
  530. );
  531. let rpc_client =
  532. RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
  533. let default_signer = Keypair::new();
  534. let mut config = CliConfig::recent_for_tests();
  535. config.json_rpc_url = test_validator.rpc_url();
  536. config.signers = vec![&default_signer];
  537. config.send_transaction_config.skip_preflight = false;
  538. let sender_pubkey = config.signers[0].pubkey();
  539. let recipient_pubkey = Pubkey::from([1u8; 32]);
  540. request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, 50_000).unwrap();
  541. check_balance!(50_000, &rpc_client, &sender_pubkey);
  542. check_balance!(0, &rpc_client, &recipient_pubkey);
  543. check_ready(&rpc_client);
  544. // Plain ole transfer
  545. config.command = CliCommand::Transfer {
  546. amount: SpendAmount::All,
  547. to: recipient_pubkey,
  548. from: 0,
  549. sign_only: false,
  550. dump_transaction_message: false,
  551. allow_unfunded_recipient: false,
  552. no_wait: false,
  553. blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
  554. nonce_account: None,
  555. nonce_authority: 0,
  556. memo: None,
  557. fee_payer: 0,
  558. derived_address_seed: None,
  559. derived_address_program_id: None,
  560. compute_unit_price: None,
  561. };
  562. // Expect failure due to unfunded recipient and the lack of the `allow_unfunded_recipient` flag
  563. process_command(&config).unwrap_err();
  564. }
  565. #[test]
  566. fn test_transfer_with_seed() {
  567. agave_logger::setup();
  568. let fee = FeeStructure::default().get_max_fee(1, 0);
  569. let mint_keypair = Keypair::new();
  570. let mint_pubkey = mint_keypair.pubkey();
  571. let faucet_addr = run_local_faucet_with_unique_port_for_tests(mint_keypair);
  572. let test_validator = TestValidator::with_custom_fees(
  573. mint_pubkey,
  574. fee,
  575. Some(faucet_addr),
  576. SocketAddrSpace::Unspecified,
  577. );
  578. let rpc_client =
  579. RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
  580. let default_signer = Keypair::new();
  581. let mut config = CliConfig::recent_for_tests();
  582. config.json_rpc_url = test_validator.rpc_url();
  583. config.signers = vec![&default_signer];
  584. let sender_pubkey = config.signers[0].pubkey();
  585. let recipient_pubkey = Pubkey::from([1u8; 32]);
  586. let derived_address_seed = "seed".to_string();
  587. let derived_address_program_id = stake::program::id();
  588. let derived_address = Pubkey::create_with_seed(
  589. &sender_pubkey,
  590. &derived_address_seed,
  591. &derived_address_program_id,
  592. )
  593. .unwrap();
  594. request_and_confirm_airdrop(&rpc_client, &config, &sender_pubkey, LAMPORTS_PER_SOL).unwrap();
  595. request_and_confirm_airdrop(&rpc_client, &config, &derived_address, 5 * LAMPORTS_PER_SOL)
  596. .unwrap();
  597. check_balance!(LAMPORTS_PER_SOL, &rpc_client, &sender_pubkey);
  598. check_balance!(5 * LAMPORTS_PER_SOL, &rpc_client, &derived_address);
  599. check_balance!(0, &rpc_client, &recipient_pubkey);
  600. check_ready(&rpc_client);
  601. // Transfer with seed
  602. config.command = CliCommand::Transfer {
  603. amount: SpendAmount::Some(5 * LAMPORTS_PER_SOL),
  604. to: recipient_pubkey,
  605. from: 0,
  606. sign_only: false,
  607. dump_transaction_message: false,
  608. allow_unfunded_recipient: true,
  609. no_wait: false,
  610. blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
  611. nonce_account: None,
  612. nonce_authority: 0,
  613. memo: None,
  614. fee_payer: 0,
  615. derived_address_seed: Some(derived_address_seed),
  616. derived_address_program_id: Some(derived_address_program_id),
  617. compute_unit_price: None,
  618. };
  619. process_command(&config).unwrap();
  620. check_balance!(LAMPORTS_PER_SOL - fee, &rpc_client, &sender_pubkey);
  621. check_balance!(5 * LAMPORTS_PER_SOL, &rpc_client, &recipient_pubkey);
  622. check_balance!(0, &rpc_client, &derived_address);
  623. }