unwrap_lamports.rs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. mod setup;
  2. use {
  3. crate::setup::TOKEN_PROGRAM_ID,
  4. mollusk_svm::{result::Check, Mollusk},
  5. pinocchio_token_interface::{
  6. error::TokenError,
  7. instruction::TokenInstruction,
  8. native_mint,
  9. state::{
  10. account::Account as TokenAccount, account_state::AccountState, load_mut_unchecked,
  11. },
  12. },
  13. solana_account::Account,
  14. solana_instruction::{error::InstructionError, AccountMeta, Instruction},
  15. solana_program_error::ProgramError,
  16. solana_program_pack::Pack,
  17. solana_pubkey::Pubkey,
  18. solana_rent::Rent,
  19. solana_sdk_ids::bpf_loader_upgradeable,
  20. };
  21. fn create_token_account(
  22. mint: &Pubkey,
  23. owner: &Pubkey,
  24. is_native: bool,
  25. amount: u64,
  26. program_owner: &Pubkey,
  27. ) -> Account {
  28. let space = size_of::<TokenAccount>();
  29. let mut lamports = Rent::default().minimum_balance(space);
  30. let mut data: Vec<u8> = vec![0u8; space];
  31. let token = unsafe { load_mut_unchecked::<TokenAccount>(data.as_mut_slice()).unwrap() };
  32. token.set_account_state(AccountState::Initialized);
  33. token.mint = *mint.as_array();
  34. token.owner = *owner.as_array();
  35. token.set_amount(amount);
  36. token.set_native(is_native);
  37. if is_native {
  38. token.set_native_amount(lamports);
  39. lamports = lamports.saturating_add(amount);
  40. }
  41. Account {
  42. lamports,
  43. data,
  44. owner: *program_owner,
  45. executable: false,
  46. ..Default::default()
  47. }
  48. }
  49. /// Creates a Mollusk instance with the default feature set.
  50. fn mollusk() -> Mollusk {
  51. let mut mollusk = Mollusk::default();
  52. mollusk.add_program(
  53. &TOKEN_PROGRAM_ID,
  54. "pinocchio_token_program",
  55. &bpf_loader_upgradeable::id(),
  56. );
  57. mollusk
  58. }
  59. fn unwrap_lamports_instruction(
  60. source: &Pubkey,
  61. destination: &Pubkey,
  62. authority: &Pubkey,
  63. amount: Option<u64>,
  64. ) -> Result<Instruction, ProgramError> {
  65. let accounts = vec![
  66. AccountMeta::new(*source, false),
  67. AccountMeta::new(*destination, false),
  68. AccountMeta::new_readonly(*authority, true),
  69. ];
  70. // Start with the batch discriminator
  71. let mut data: Vec<u8> = vec![TokenInstruction::UnwrapLamports as u8];
  72. if let Some(amount) = amount {
  73. data.push(1);
  74. data.extend_from_slice(&amount.to_le_bytes());
  75. } else {
  76. data.push(0);
  77. }
  78. Ok(Instruction {
  79. program_id: spl_token_interface::ID,
  80. data,
  81. accounts,
  82. })
  83. }
  84. #[test]
  85. fn unwrap_lamports() {
  86. let native_mint = Pubkey::new_from_array(native_mint::ID);
  87. let authority_key = Pubkey::new_unique();
  88. let destination_account_key = Pubkey::new_unique();
  89. // native account:
  90. // - amount: 2_000_000_000
  91. let source_account_key = Pubkey::new_unique();
  92. let source_account = create_token_account(
  93. &native_mint,
  94. &authority_key,
  95. true,
  96. 2_000_000_000,
  97. &TOKEN_PROGRAM_ID,
  98. );
  99. let instruction = unwrap_lamports_instruction(
  100. &source_account_key,
  101. &destination_account_key,
  102. &authority_key,
  103. None,
  104. )
  105. .unwrap();
  106. // It should succeed to unwrap 2_000_000_000 lamports.
  107. let result = mollusk().process_and_validate_instruction(
  108. &instruction,
  109. &[
  110. (source_account_key, source_account),
  111. (destination_account_key, Account::default()),
  112. (authority_key, Account::default()),
  113. ],
  114. &[
  115. Check::success(),
  116. Check::account(&destination_account_key)
  117. .lamports(2_000_000_000)
  118. .build(),
  119. Check::account(&source_account_key)
  120. .lamports(Rent::default().minimum_balance(size_of::<TokenAccount>()))
  121. .build(),
  122. ],
  123. );
  124. // And the remaining amount must be 0.
  125. let account = result.get_account(&source_account_key);
  126. assert!(account.is_some());
  127. let account = account.unwrap();
  128. let token_account = spl_token_interface::state::Account::unpack(&account.data).unwrap();
  129. assert_eq!(token_account.amount, 0);
  130. }
  131. #[test]
  132. fn unwrap_lamports_with_amount() {
  133. let native_mint = Pubkey::new_from_array(native_mint::ID);
  134. let authority_key = Pubkey::new_unique();
  135. let destination_account_key = Pubkey::new_unique();
  136. // native account:
  137. // - amount: 2_000_000_000
  138. let source_account_key = Pubkey::new_unique();
  139. let source_account = create_token_account(
  140. &native_mint,
  141. &authority_key,
  142. true,
  143. 2_000_000_000,
  144. &TOKEN_PROGRAM_ID,
  145. );
  146. let instruction = unwrap_lamports_instruction(
  147. &source_account_key,
  148. &destination_account_key,
  149. &authority_key,
  150. Some(2_000_000_000),
  151. )
  152. .unwrap();
  153. // It should succeed to unwrap 2_000_000_000 lamports.
  154. let result = mollusk().process_and_validate_instruction(
  155. &instruction,
  156. &[
  157. (source_account_key, source_account),
  158. (destination_account_key, Account::default()),
  159. (authority_key, Account::default()),
  160. ],
  161. &[
  162. Check::success(),
  163. Check::account(&destination_account_key)
  164. .lamports(2_000_000_000)
  165. .build(),
  166. Check::account(&source_account_key)
  167. .lamports(Rent::default().minimum_balance(size_of::<TokenAccount>()))
  168. .build(),
  169. ],
  170. );
  171. // And the remaining amount must be 0.
  172. let account = result.get_account(&source_account_key);
  173. assert!(account.is_some());
  174. let account = account.unwrap();
  175. let token_account = spl_token_interface::state::Account::unpack(&account.data).unwrap();
  176. assert_eq!(token_account.amount, 0);
  177. }
  178. #[test]
  179. fn fail_unwrap_lamports_with_insufficient_funds() {
  180. let native_mint = Pubkey::new_from_array(native_mint::ID);
  181. let authority_key = Pubkey::new_unique();
  182. let destination_account_key = Pubkey::new_unique();
  183. // native account:
  184. // - amount: 1_000_000_000
  185. let source_account_key = Pubkey::new_unique();
  186. let source_account = create_token_account(
  187. &native_mint,
  188. &authority_key,
  189. true,
  190. 1_000_000_000,
  191. &TOKEN_PROGRAM_ID,
  192. );
  193. let instruction = unwrap_lamports_instruction(
  194. &source_account_key,
  195. &destination_account_key,
  196. &authority_key,
  197. Some(2_000_000_000),
  198. )
  199. .unwrap();
  200. // When we try to unwrap 2_000_000_000 lamports, we expect a
  201. // `TokenError::InsufficientFunds` error.
  202. mollusk().process_and_validate_instruction(
  203. &instruction,
  204. &[
  205. (source_account_key, source_account),
  206. (destination_account_key, Account::default()),
  207. (authority_key, Account::default()),
  208. ],
  209. &[Check::err(ProgramError::Custom(
  210. TokenError::InsufficientFunds as u32,
  211. ))],
  212. );
  213. }
  214. #[test]
  215. fn unwrap_lamports_with_parial_amount() {
  216. let native_mint = Pubkey::new_from_array(native_mint::ID);
  217. let authority_key = Pubkey::new_unique();
  218. let destination_account_key = Pubkey::new_unique();
  219. // native account:
  220. // - amount: 2_000_000_000
  221. let source_account_key = Pubkey::new_unique();
  222. let source_account = create_token_account(
  223. &native_mint,
  224. &authority_key,
  225. true,
  226. 2_000_000_000,
  227. &TOKEN_PROGRAM_ID,
  228. );
  229. let instruction = unwrap_lamports_instruction(
  230. &source_account_key,
  231. &destination_account_key,
  232. &authority_key,
  233. Some(1_000_000_000),
  234. )
  235. .unwrap();
  236. // It should succeed to unwrap 1_000_000_000 lamports.
  237. let result = mollusk().process_and_validate_instruction(
  238. &instruction,
  239. &[
  240. (source_account_key, source_account),
  241. (destination_account_key, Account::default()),
  242. (authority_key, Account::default()),
  243. ],
  244. &[
  245. Check::success(),
  246. Check::account(&destination_account_key)
  247. .lamports(1_000_000_000)
  248. .build(),
  249. Check::account(&source_account_key)
  250. .lamports(
  251. Rent::default().minimum_balance(size_of::<TokenAccount>()) + 1_000_000_000,
  252. )
  253. .build(),
  254. ],
  255. );
  256. // And the remaining amount must be 1_000_000_000.
  257. let account = result.get_account(&source_account_key);
  258. assert!(account.is_some());
  259. let account = account.unwrap();
  260. let token_account = spl_token_interface::state::Account::unpack(&account.data).unwrap();
  261. assert_eq!(token_account.amount, 1_000_000_000);
  262. }
  263. #[test]
  264. fn fail_unwrap_lamports_with_invalid_authority() {
  265. let native_mint = Pubkey::new_from_array(native_mint::ID);
  266. let authority_key = Pubkey::new_unique();
  267. let destination_account_key = Pubkey::new_unique();
  268. let fake_authority_key = Pubkey::new_unique();
  269. // native account:
  270. // - amount: 1_000_000_000
  271. let source_account_key = Pubkey::new_unique();
  272. let source_account = create_token_account(
  273. &native_mint,
  274. &authority_key,
  275. true,
  276. 1_000_000_000,
  277. &TOKEN_PROGRAM_ID,
  278. );
  279. let instruction = unwrap_lamports_instruction(
  280. &source_account_key,
  281. &destination_account_key,
  282. &fake_authority_key, // <-- wrong authority
  283. Some(2_000_000_000),
  284. )
  285. .unwrap();
  286. // When we try to unwrap lamports with an invalid authority, we expect a
  287. // `TokenError::OwnerMismatch` error.
  288. mollusk().process_and_validate_instruction(
  289. &instruction,
  290. &[
  291. (source_account_key, source_account),
  292. (destination_account_key, Account::default()),
  293. (fake_authority_key, Account::default()),
  294. ],
  295. &[Check::err(ProgramError::Custom(
  296. TokenError::OwnerMismatch as u32,
  297. ))],
  298. );
  299. }
  300. #[test]
  301. fn fail_unwrap_lamports_with_non_native_account() {
  302. let mint = Pubkey::new_unique();
  303. let authority_key = Pubkey::new_unique();
  304. let destination_account_key = Pubkey::new_unique();
  305. // non-native account:
  306. // - amount: 2_000_000_000
  307. let source_account_key = Pubkey::new_unique();
  308. let mut source_account = create_token_account(
  309. &mint,
  310. &authority_key,
  311. false, // <-- non-native account
  312. 2_000_000_000,
  313. &TOKEN_PROGRAM_ID,
  314. );
  315. source_account.lamports += 2_000_000_000;
  316. let instruction = unwrap_lamports_instruction(
  317. &source_account_key,
  318. &destination_account_key,
  319. &authority_key,
  320. Some(1_000_000_000),
  321. )
  322. .unwrap();
  323. // When we try to unwrap lamports from a non-native account, we expect a
  324. // `TokenError::NonNativeNotSupported` error.
  325. mollusk().process_and_validate_instruction(
  326. &instruction,
  327. &[
  328. (source_account_key, source_account),
  329. (destination_account_key, Account::default()),
  330. (authority_key, Account::default()),
  331. ],
  332. &[Check::err(ProgramError::Custom(
  333. TokenError::NonNativeNotSupported as u32,
  334. ))],
  335. );
  336. }
  337. #[test]
  338. fn unwrap_lamports_with_self_transfer() {
  339. let native_mint = Pubkey::new_from_array(native_mint::ID);
  340. let authority_key = Pubkey::new_unique();
  341. // native account:
  342. // - amount: 2_000_000_000
  343. let source_account_key = Pubkey::new_unique();
  344. let source_account = create_token_account(
  345. &native_mint,
  346. &authority_key,
  347. true,
  348. 2_000_000_000,
  349. &TOKEN_PROGRAM_ID,
  350. );
  351. let instruction = unwrap_lamports_instruction(
  352. &source_account_key,
  353. &source_account_key, // <-- destination same as source
  354. &authority_key,
  355. Some(1_000_000_000),
  356. )
  357. .unwrap();
  358. // It should succeed to unwrap lamports with the same source and destination
  359. // accounts.
  360. let result = mollusk().process_and_validate_instruction(
  361. &instruction,
  362. &[
  363. (source_account_key, source_account),
  364. (authority_key, Account::default()),
  365. ],
  366. &[
  367. Check::success(),
  368. Check::account(&source_account_key)
  369. .lamports(
  370. Rent::default().minimum_balance(size_of::<TokenAccount>()) + 2_000_000_000,
  371. )
  372. .build(),
  373. ],
  374. );
  375. let account = result.get_account(&source_account_key);
  376. assert!(account.is_some());
  377. let account = account.unwrap();
  378. let token_account = spl_token_interface::state::Account::unpack(&account.data).unwrap();
  379. assert_eq!(token_account.amount, 2_000_000_000);
  380. }
  381. #[test]
  382. fn fail_unwrap_lamports_with_invalid_native_account() {
  383. let native_mint = Pubkey::new_from_array(native_mint::ID);
  384. let authority_key = Pubkey::new_unique();
  385. let destination_account_key = Pubkey::new_unique();
  386. let invalid_program_owner = Pubkey::new_unique();
  387. // native account:
  388. // - amount: 2_000_000_000
  389. let source_account_key = Pubkey::new_unique();
  390. let mut source_account = create_token_account(
  391. &native_mint,
  392. &authority_key,
  393. true,
  394. 2_000_000_000,
  395. &invalid_program_owner, // <-- invalid program owner
  396. );
  397. source_account.lamports += 2_000_000_000;
  398. let instruction = unwrap_lamports_instruction(
  399. &source_account_key,
  400. &destination_account_key,
  401. &authority_key,
  402. Some(1_000_000_000),
  403. )
  404. .unwrap();
  405. // When we try to unwrap lamports with an invalid native account, we expect
  406. // a `InstructionError::ExternalAccountDataModified` error.
  407. mollusk().process_and_validate_instruction(
  408. &instruction,
  409. &[
  410. (source_account_key, source_account),
  411. (destination_account_key, Account::default()),
  412. (authority_key, Account::default()),
  413. ],
  414. &[Check::instruction_err(
  415. InstructionError::ExternalAccountDataModified,
  416. )],
  417. );
  418. }
  419. #[test]
  420. fn unwrap_lamports_to_native_account() {
  421. let native_mint = Pubkey::new_from_array(native_mint::ID);
  422. let authority_key = Pubkey::new_unique();
  423. // native account:
  424. // - amount: 2_000_000_000
  425. let source_account_key = Pubkey::new_unique();
  426. let source_account = create_token_account(
  427. &native_mint,
  428. &authority_key,
  429. true,
  430. 2_000_000_000,
  431. &TOKEN_PROGRAM_ID,
  432. );
  433. // destination native account:
  434. // - amount: 0
  435. let destination_account_key = Pubkey::new_unique();
  436. let destination_account =
  437. create_token_account(&native_mint, &authority_key, true, 0, &TOKEN_PROGRAM_ID);
  438. let instruction = unwrap_lamports_instruction(
  439. &source_account_key,
  440. &destination_account_key,
  441. &authority_key,
  442. None,
  443. )
  444. .unwrap();
  445. // It should succeed to unwrap 2_000_000_000 lamports.
  446. let result = mollusk().process_and_validate_instruction(
  447. &instruction,
  448. &[
  449. (source_account_key, source_account),
  450. (destination_account_key, destination_account),
  451. (authority_key, Account::default()),
  452. ],
  453. &[
  454. Check::success(),
  455. Check::account(&destination_account_key)
  456. .lamports(
  457. Rent::default().minimum_balance(size_of::<TokenAccount>()) + 2_000_000_000,
  458. )
  459. .build(),
  460. Check::account(&source_account_key)
  461. .lamports(Rent::default().minimum_balance(size_of::<TokenAccount>()))
  462. .build(),
  463. ],
  464. );
  465. // And the remaining amount on the source account must be 0.
  466. let account = result.get_account(&source_account_key);
  467. assert!(account.is_some());
  468. let account = account.unwrap();
  469. let token_account = spl_token_interface::state::Account::unpack(&account.data).unwrap();
  470. assert_eq!(token_account.amount, 0);
  471. // And the amount on the destination account must be 0 since we transferred
  472. // lamports directly to the account.
  473. let account = result.get_account(&destination_account_key);
  474. assert!(account.is_some());
  475. let account = account.unwrap();
  476. let token_account = spl_token_interface::state::Account::unpack(&account.data).unwrap();
  477. assert_eq!(token_account.amount, 0);
  478. }
  479. #[test]
  480. fn unwrap_lamports_to_token_account() {
  481. let native_mint = Pubkey::new_from_array(native_mint::ID);
  482. let authority_key = Pubkey::new_unique();
  483. let non_native_mint = Pubkey::new_unique();
  484. // native account:
  485. // - amount: 2_000_000_000
  486. let source_account_key = Pubkey::new_unique();
  487. let source_account = create_token_account(
  488. &native_mint,
  489. &authority_key,
  490. true,
  491. 2_000_000_000,
  492. &TOKEN_PROGRAM_ID,
  493. );
  494. // destination non-native account:
  495. // - amount: 0
  496. let destination_account_key = Pubkey::new_unique();
  497. let destination_account = create_token_account(
  498. &non_native_mint,
  499. &authority_key,
  500. false,
  501. 0,
  502. &TOKEN_PROGRAM_ID,
  503. );
  504. let instruction = unwrap_lamports_instruction(
  505. &source_account_key,
  506. &destination_account_key,
  507. &authority_key,
  508. None,
  509. )
  510. .unwrap();
  511. // It should succeed to unwrap 2_000_000_000 lamports.
  512. let result = mollusk().process_and_validate_instruction(
  513. &instruction,
  514. &[
  515. (source_account_key, source_account),
  516. (destination_account_key, destination_account),
  517. (authority_key, Account::default()),
  518. ],
  519. &[
  520. Check::success(),
  521. Check::account(&destination_account_key)
  522. .lamports(
  523. Rent::default().minimum_balance(size_of::<TokenAccount>()) + 2_000_000_000,
  524. )
  525. .build(),
  526. Check::account(&source_account_key)
  527. .lamports(Rent::default().minimum_balance(size_of::<TokenAccount>()))
  528. .build(),
  529. ],
  530. );
  531. // And the remaining amount on the source account must be 0.
  532. let account = result.get_account(&source_account_key);
  533. assert!(account.is_some());
  534. let account = account.unwrap();
  535. let token_account = spl_token_interface::state::Account::unpack(&account.data).unwrap();
  536. assert_eq!(token_account.amount, 0);
  537. // And the amount on the destination account must be 0 since we transferred
  538. // lamports directly to the account.
  539. let account = result.get_account(&destination_account_key);
  540. assert!(account.is_some());
  541. let account = account.unwrap();
  542. let token_account = spl_token_interface::state::Account::unpack(&account.data).unwrap();
  543. assert_eq!(token_account.amount, 0);
  544. }