1use {
2 solana_instruction::error::InstructionError,
3 solana_nonce::{
4 self as nonce,
5 state::{DurableNonce, State},
6 versions::{AuthorizeNonceError, Versions},
7 },
8 solana_program_runtime::invoke_context::InvokeContext,
9 solana_pubkey::Pubkey,
10 solana_svm_log_collector::ic_msg,
11 solana_system_interface::error::SystemError,
12 solana_sysvar::rent::Rent,
13 solana_transaction_context::{
14 IndexOfAccount, instruction::InstructionContext,
15 instruction_accounts::BorrowedInstructionAccount,
16 },
17 std::collections::HashSet,
18};
19
20fn checked_add(a: u64, b: u64) -> Result<u64, InstructionError> {
22 a.checked_add(b).ok_or(InstructionError::InsufficientFunds)
23}
24
25pub fn advance_nonce_account(
26 account: &mut BorrowedInstructionAccount,
27 signers: &HashSet<Pubkey>,
28 invoke_context: &InvokeContext,
29) -> Result<(), InstructionError> {
30 if !account.is_writable() {
31 ic_msg!(
32 invoke_context,
33 "Advance nonce account: Account {} must be writeable",
34 account.get_key()
35 );
36 return Err(InstructionError::InvalidArgument);
37 }
38
39 let state: Versions = account.get_state()?;
40 match state.state() {
41 State::Initialized(data) => {
42 if !signers.contains(&data.authority) {
43 ic_msg!(
44 invoke_context,
45 "Advance nonce account: Account {} must be a signer",
46 data.authority
47 );
48 return Err(InstructionError::MissingRequiredSignature);
49 }
50 let next_durable_nonce =
51 DurableNonce::from_blockhash(&invoke_context.environment_config.blockhash);
52 if data.durable_nonce == next_durable_nonce {
53 ic_msg!(
54 invoke_context,
55 "Advance nonce account: nonce can only advance once per slot"
56 );
57 return Err(SystemError::NonceBlockhashNotExpired.into());
58 }
59
60 let new_data = nonce::state::Data::new(
61 data.authority,
62 next_durable_nonce,
63 invoke_context
64 .environment_config
65 .blockhash_lamports_per_signature,
66 );
67 account.set_state(&Versions::new(State::Initialized(new_data)))
68 }
69 State::Uninitialized => {
70 ic_msg!(
71 invoke_context,
72 "Advance nonce account: Account {} state is invalid",
73 account.get_key()
74 );
75 Err(InstructionError::InvalidAccountData)
76 }
77 }
78}
79
80pub(crate) fn withdraw_nonce_account(
81 from_account_index: IndexOfAccount,
82 lamports: u64,
83 to_account_index: IndexOfAccount,
84 rent: &Rent,
85 signers: &HashSet<Pubkey>,
86 invoke_context: &InvokeContext,
87 instruction_context: &InstructionContext,
88) -> Result<(), InstructionError> {
89 let mut from = instruction_context.try_borrow_instruction_account(from_account_index)?;
90 if !from.is_writable() {
91 ic_msg!(
92 invoke_context,
93 "Withdraw nonce account: Account {} must be writeable",
94 from.get_key()
95 );
96 return Err(InstructionError::InvalidArgument);
97 }
98
99 let check_signer = |signer: &Pubkey| {
100 if !signers.contains(signer) {
101 ic_msg!(
102 invoke_context,
103 "Withdraw nonce account: Account {} must sign",
104 signer
105 );
106 return Err(InstructionError::MissingRequiredSignature);
107 }
108 Ok(())
109 };
110
111 let state: Versions = from.get_state()?;
112 match state.state() {
113 State::Uninitialized => {
114 if lamports > from.get_lamports() {
115 ic_msg!(
116 invoke_context,
117 "Withdraw nonce account: insufficient lamports {}, need {}",
118 from.get_lamports(),
119 lamports,
120 );
121 return Err(InstructionError::InsufficientFunds);
122 }
123 check_signer(from.get_key())?;
124 }
125 State::Initialized(data) => {
126 if lamports == from.get_lamports() {
127 let durable_nonce =
128 DurableNonce::from_blockhash(&invoke_context.environment_config.blockhash);
129 if data.durable_nonce == durable_nonce {
130 ic_msg!(
131 invoke_context,
132 "Withdraw nonce account: nonce can only advance once per slot"
133 );
134 return Err(SystemError::NonceBlockhashNotExpired.into());
135 }
136 check_signer(&data.authority)?;
137 from.set_state(&Versions::new(State::Uninitialized))?;
138 } else {
139 let min_balance = rent.minimum_balance(from.get_data().len());
140 let amount = checked_add(lamports, min_balance)?;
141 if amount > from.get_lamports() {
142 ic_msg!(
143 invoke_context,
144 "Withdraw nonce account: insufficient lamports {}, need {}",
145 from.get_lamports(),
146 amount,
147 );
148 return Err(InstructionError::InsufficientFunds);
149 }
150 check_signer(&data.authority)?;
151 }
152 }
153 };
154
155 from.checked_sub_lamports(lamports)?;
156 drop(from);
157 let mut to = instruction_context.try_borrow_instruction_account(to_account_index)?;
158 to.checked_add_lamports(lamports)?;
159
160 Ok(())
161}
162
163pub(crate) fn initialize_nonce_account(
164 account: &mut BorrowedInstructionAccount,
165 nonce_authority: &Pubkey,
166 rent: &Rent,
167 invoke_context: &InvokeContext,
168) -> Result<(), InstructionError> {
169 if !account.is_writable() {
170 ic_msg!(
171 invoke_context,
172 "Initialize nonce account: Account {} must be writeable",
173 account.get_key()
174 );
175 return Err(InstructionError::InvalidArgument);
176 }
177
178 match account.get_state::<Versions>()?.state() {
179 State::Uninitialized => {
180 let min_balance = rent.minimum_balance(account.get_data().len());
181 if account.get_lamports() < min_balance {
182 ic_msg!(
183 invoke_context,
184 "Initialize nonce account: insufficient lamports {}, need {}",
185 account.get_lamports(),
186 min_balance
187 );
188 return Err(InstructionError::InsufficientFunds);
189 }
190 let durable_nonce =
191 DurableNonce::from_blockhash(&invoke_context.environment_config.blockhash);
192 let data = nonce::state::Data::new(
193 *nonce_authority,
194 durable_nonce,
195 invoke_context
196 .environment_config
197 .blockhash_lamports_per_signature,
198 );
199 let state = State::Initialized(data);
200 account.set_state(&Versions::new(state))
201 }
202 State::Initialized(_) => {
203 ic_msg!(
204 invoke_context,
205 "Initialize nonce account: Account {} state is invalid",
206 account.get_key()
207 );
208 Err(InstructionError::InvalidAccountData)
209 }
210 }
211}
212
213pub(crate) fn authorize_nonce_account(
214 account: &mut BorrowedInstructionAccount,
215 nonce_authority: &Pubkey,
216 signers: &HashSet<Pubkey>,
217 invoke_context: &InvokeContext,
218) -> Result<(), InstructionError> {
219 if !account.is_writable() {
220 ic_msg!(
221 invoke_context,
222 "Authorize nonce account: Account {} must be writeable",
223 account.get_key()
224 );
225 return Err(InstructionError::InvalidArgument);
226 }
227 match account
228 .get_state::<Versions>()?
229 .authorize(signers, *nonce_authority)
230 {
231 Ok(versions) => account.set_state(&versions),
232 Err(AuthorizeNonceError::Uninitialized) => {
233 ic_msg!(
234 invoke_context,
235 "Authorize nonce account: Account {} state is invalid",
236 account.get_key()
237 );
238 Err(InstructionError::InvalidAccountData)
239 }
240 Err(AuthorizeNonceError::MissingRequiredSignature(account_authority)) => {
241 ic_msg!(
242 invoke_context,
243 "Authorize nonce account: Account {} must sign",
244 account_authority
245 );
246 Err(InstructionError::MissingRequiredSignature)
247 }
248 }
249}
250
251#[cfg(test)]
252mod test {
253 use {
254 super::*,
255 assert_matches::assert_matches,
256 solana_account::AccountSharedData,
257 solana_nonce::{self as nonce, state::State},
258 solana_nonce_account::{create_account, verify_nonce_account},
259 solana_program_runtime::with_mock_invoke_context,
260 solana_sdk_ids::system_program,
261 solana_sha256_hasher::hash,
262 solana_transaction_context::instruction_accounts::InstructionAccount,
263 };
264
265 pub const NONCE_ACCOUNT_INDEX: IndexOfAccount = 0;
266 pub const WITHDRAW_TO_ACCOUNT_INDEX: IndexOfAccount = 1;
267
268 macro_rules! push_instruction_context {
269 ($invoke_context:expr, $instruction_context:ident, $instruction_accounts:ident) => {
270 $invoke_context
271 .transaction_context
272 .configure_top_level_instruction_for_tests(2, $instruction_accounts, vec![])
273 .unwrap();
274 $invoke_context.push().unwrap();
275 let transaction_context = &$invoke_context.transaction_context;
276 let $instruction_context = transaction_context
277 .get_current_instruction_context()
278 .unwrap();
279 };
280 }
281
282 macro_rules! prepare_mockup {
283 ($invoke_context:ident, $instruction_accounts:ident, $rent:ident, $transaction_context:ident) => {
284 let $rent = Rent {
285 lamports_per_byte: 42,
286 ..Rent::default()
287 };
288 let from_lamports = $rent.minimum_balance(State::size()) + 42;
289 let transaction_accounts = vec![
290 (
291 Pubkey::new_unique(),
292 create_account(from_lamports).into_inner(),
293 ),
294 (Pubkey::new_unique(), create_account(42).into_inner()),
295 (system_program::id(), AccountSharedData::default()),
296 ];
297 let $instruction_accounts = vec![
298 InstructionAccount::new(0, true, true),
299 InstructionAccount::new(1, false, true),
300 ];
301 with_mock_invoke_context!($invoke_context, $transaction_context, transaction_accounts);
302 };
303 }
304
305 macro_rules! set_invoke_context_blockhash {
306 ($invoke_context:expr, $seed:expr) => {
307 $invoke_context.environment_config.blockhash =
308 hash(&bincode::serialize(&$seed).unwrap());
309 $invoke_context
310 .environment_config
311 .blockhash_lamports_per_signature = ($seed as u64).saturating_mul(100);
312 };
313 }
314
315 #[test]
316 fn default_is_uninitialized() {
317 assert_eq!(State::default(), State::Uninitialized)
318 }
319
320 #[test]
321 fn expected_behavior() {
322 prepare_mockup!(
323 invoke_context,
324 instruction_accounts,
325 rent,
326 transaction_context
327 );
328 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
329 let mut nonce_account = instruction_context
330 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
331 .unwrap();
332 let data = nonce::state::Data {
333 authority: *nonce_account.get_key(),
334 ..nonce::state::Data::default()
335 };
336 let mut signers = HashSet::new();
337 signers.insert(*nonce_account.get_key());
338 let versions = nonce_account.get_state::<Versions>().unwrap();
339 assert_eq!(versions.state(), &State::Uninitialized);
341 set_invoke_context_blockhash!(invoke_context, 95);
342 let authorized = *nonce_account.get_key();
343 initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context).unwrap();
344 let versions = nonce_account.get_state::<Versions>().unwrap();
345 let data = nonce::state::Data::new(
346 data.authority,
347 DurableNonce::from_blockhash(&invoke_context.environment_config.blockhash),
348 invoke_context
349 .environment_config
350 .blockhash_lamports_per_signature,
351 );
352 assert_eq!(versions.state(), &State::Initialized(data.clone()));
354 set_invoke_context_blockhash!(invoke_context, 63);
355 advance_nonce_account(&mut nonce_account, &signers, &invoke_context).unwrap();
356 let versions = nonce_account.get_state::<Versions>().unwrap();
357 let data = nonce::state::Data::new(
358 data.authority,
359 DurableNonce::from_blockhash(&invoke_context.environment_config.blockhash),
360 invoke_context
361 .environment_config
362 .blockhash_lamports_per_signature,
363 );
364 assert_eq!(versions.state(), &State::Initialized(data.clone()));
366 set_invoke_context_blockhash!(invoke_context, 31);
367 advance_nonce_account(&mut nonce_account, &signers, &invoke_context).unwrap();
368 let versions = nonce_account.get_state::<Versions>().unwrap();
369 let data = nonce::state::Data::new(
370 data.authority,
371 DurableNonce::from_blockhash(&invoke_context.environment_config.blockhash),
372 invoke_context
373 .environment_config
374 .blockhash_lamports_per_signature,
375 );
376 assert_eq!(versions.state(), &State::Initialized(data));
378
379 set_invoke_context_blockhash!(invoke_context, 0);
380 let to_account = instruction_context
381 .try_borrow_instruction_account(WITHDRAW_TO_ACCOUNT_INDEX)
382 .unwrap();
383 let withdraw_lamports = nonce_account.get_lamports();
384 let expect_nonce_lamports = nonce_account.get_lamports() - withdraw_lamports;
385 let expect_to_lamports = to_account.get_lamports() + withdraw_lamports;
386 drop(nonce_account);
387 drop(to_account);
388 withdraw_nonce_account(
389 NONCE_ACCOUNT_INDEX,
390 withdraw_lamports,
391 WITHDRAW_TO_ACCOUNT_INDEX,
392 &rent,
393 &signers,
394 &invoke_context,
395 &instruction_context,
396 )
397 .unwrap();
398 let nonce_account = instruction_context
399 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
400 .unwrap();
401 let to_account = instruction_context
402 .try_borrow_instruction_account(WITHDRAW_TO_ACCOUNT_INDEX)
403 .unwrap();
404 assert_eq!(nonce_account.get_lamports(), expect_nonce_lamports);
406 assert_eq!(to_account.get_lamports(), expect_to_lamports);
408 let versions = nonce_account.get_state::<Versions>().unwrap();
409 assert_eq!(versions.state(), &State::Uninitialized);
411 }
412
413 #[test]
414 fn nonce_inx_initialized_account_not_signer_fail() {
415 prepare_mockup!(
416 invoke_context,
417 instruction_accounts,
418 rent,
419 transaction_context
420 );
421 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
422 let mut nonce_account = instruction_context
423 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
424 .unwrap();
425 set_invoke_context_blockhash!(invoke_context, 31);
426 let authority = *nonce_account.get_key();
427 initialize_nonce_account(&mut nonce_account, &authority, &rent, &invoke_context).unwrap();
428 let versions = nonce_account.get_state::<Versions>().unwrap();
429 let data = nonce::state::Data::new(
430 authority,
431 DurableNonce::from_blockhash(&invoke_context.environment_config.blockhash),
432 invoke_context
433 .environment_config
434 .blockhash_lamports_per_signature,
435 );
436 assert_eq!(versions.state(), &State::Initialized(data));
437 let signers = HashSet::new();
439 set_invoke_context_blockhash!(invoke_context, 0);
440 let result = advance_nonce_account(&mut nonce_account, &signers, &invoke_context);
441 assert_eq!(result, Err(InstructionError::MissingRequiredSignature));
442 }
443
444 #[test]
445 fn nonce_inx_too_early_fail() {
446 prepare_mockup!(
447 invoke_context,
448 instruction_accounts,
449 rent,
450 transaction_context
451 );
452 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
453 let mut nonce_account = instruction_context
454 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
455 .unwrap();
456 let mut signers = HashSet::new();
457 signers.insert(*nonce_account.get_key());
458 set_invoke_context_blockhash!(invoke_context, 63);
459 let authorized = *nonce_account.get_key();
460 initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context).unwrap();
461 let result = advance_nonce_account(&mut nonce_account, &signers, &invoke_context);
462 assert_eq!(result, Err(SystemError::NonceBlockhashNotExpired.into()));
463 }
464
465 #[test]
466 fn nonce_inx_uninitialized_account_fail() {
467 prepare_mockup!(
468 invoke_context,
469 instruction_accounts,
470 rent,
471 transaction_context
472 );
473 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
474 let mut nonce_account = instruction_context
475 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
476 .unwrap();
477 let mut signers = HashSet::new();
478 signers.insert(*nonce_account.get_key());
479 set_invoke_context_blockhash!(invoke_context, 63);
480 let result = advance_nonce_account(&mut nonce_account, &signers, &invoke_context);
481 assert_eq!(result, Err(InstructionError::InvalidAccountData));
482 }
483
484 #[test]
485 fn nonce_inx_independent_nonce_authority_ok() {
486 prepare_mockup!(
487 invoke_context,
488 instruction_accounts,
489 rent,
490 transaction_context
491 );
492 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
493 let mut nonce_account = instruction_context
494 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
495 .unwrap();
496 let nonce_authority = instruction_context
497 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX + 1)
498 .unwrap();
499 let mut signers = HashSet::new();
500 signers.insert(*nonce_account.get_key());
501 set_invoke_context_blockhash!(invoke_context, 63);
502 let authorized = *nonce_authority.get_key();
503 initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context).unwrap();
504 let mut signers = HashSet::new();
505 signers.insert(authorized);
506 set_invoke_context_blockhash!(invoke_context, 31);
507 let result = advance_nonce_account(&mut nonce_account, &signers, &invoke_context);
508 assert_eq!(result, Ok(()));
509 }
510
511 #[test]
512 fn nonce_inx_no_nonce_authority_sig_fail() {
513 prepare_mockup!(
514 invoke_context,
515 instruction_accounts,
516 rent,
517 transaction_context
518 );
519 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
520 let mut nonce_account = instruction_context
521 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
522 .unwrap();
523 let nonce_authority = instruction_context
524 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX + 1)
525 .unwrap();
526 let mut signers = HashSet::new();
527 signers.insert(*nonce_account.get_key());
528 set_invoke_context_blockhash!(invoke_context, 63);
529 let authorized = *nonce_authority.get_key();
530 initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context).unwrap();
531 let result = advance_nonce_account(&mut nonce_account, &signers, &invoke_context);
532 assert_eq!(result, Err(InstructionError::MissingRequiredSignature));
533 }
534
535 #[test]
536 fn withdraw_inx_unintialized_acc_ok() {
537 prepare_mockup!(
538 invoke_context,
539 instruction_accounts,
540 rent,
541 transaction_context
542 );
543 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
544 let nonce_account = instruction_context
545 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
546 .unwrap();
547 let to_account = instruction_context
548 .try_borrow_instruction_account(WITHDRAW_TO_ACCOUNT_INDEX)
549 .unwrap();
550 let versions = nonce_account.get_state::<Versions>().unwrap();
551 assert_eq!(versions.state(), &State::Uninitialized);
552 let mut signers = HashSet::new();
553 signers.insert(*nonce_account.get_key());
554 set_invoke_context_blockhash!(invoke_context, 0);
555 let withdraw_lamports = nonce_account.get_lamports();
556 let expect_from_lamports = nonce_account.get_lamports() - withdraw_lamports;
557 let expect_to_lamports = to_account.get_lamports() + withdraw_lamports;
558 drop(nonce_account);
559 drop(to_account);
560 withdraw_nonce_account(
561 NONCE_ACCOUNT_INDEX,
562 withdraw_lamports,
563 WITHDRAW_TO_ACCOUNT_INDEX,
564 &rent,
565 &signers,
566 &invoke_context,
567 &instruction_context,
568 )
569 .unwrap();
570 let nonce_account = instruction_context
571 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
572 .unwrap();
573 let to_account = instruction_context
574 .try_borrow_instruction_account(WITHDRAW_TO_ACCOUNT_INDEX)
575 .unwrap();
576 let versions = nonce_account.get_state::<Versions>().unwrap();
577 assert_eq!(versions.state(), &State::Uninitialized);
578 assert_eq!(nonce_account.get_lamports(), expect_from_lamports);
579 assert_eq!(to_account.get_lamports(), expect_to_lamports);
580 }
581
582 #[test]
583 fn withdraw_inx_unintialized_acc_unsigned_fail() {
584 prepare_mockup!(
585 invoke_context,
586 instruction_accounts,
587 rent,
588 transaction_context
589 );
590 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
591 let nonce_account = instruction_context
592 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
593 .unwrap();
594 let to_account = instruction_context
595 .try_borrow_instruction_account(WITHDRAW_TO_ACCOUNT_INDEX)
596 .unwrap();
597 let versions = nonce_account.get_state::<Versions>().unwrap();
598 assert_eq!(versions.state(), &State::Uninitialized);
599 let signers = HashSet::new();
600 set_invoke_context_blockhash!(invoke_context, 0);
601 let withdraw_lamports = nonce_account.get_lamports();
602 drop(nonce_account);
603 drop(to_account);
604 let result = withdraw_nonce_account(
605 NONCE_ACCOUNT_INDEX,
606 withdraw_lamports,
607 WITHDRAW_TO_ACCOUNT_INDEX,
608 &rent,
609 &signers,
610 &invoke_context,
611 &instruction_context,
612 );
613 assert_eq!(result, Err(InstructionError::MissingRequiredSignature));
614 }
615
616 #[test]
617 fn withdraw_inx_unintialized_acc_insuff_funds_fail() {
618 prepare_mockup!(
619 invoke_context,
620 instruction_accounts,
621 rent,
622 transaction_context
623 );
624 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
625 let nonce_account = instruction_context
626 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
627 .unwrap();
628 let versions = nonce_account.get_state::<Versions>().unwrap();
629 assert_eq!(versions.state(), &State::Uninitialized);
630 let mut signers = HashSet::new();
631 signers.insert(*nonce_account.get_key());
632 set_invoke_context_blockhash!(invoke_context, 0);
633 let withdraw_lamports = nonce_account.get_lamports() + 1;
634 drop(nonce_account);
635 let result = withdraw_nonce_account(
636 NONCE_ACCOUNT_INDEX,
637 withdraw_lamports,
638 WITHDRAW_TO_ACCOUNT_INDEX,
639 &rent,
640 &signers,
641 &invoke_context,
642 &instruction_context,
643 );
644 assert_eq!(result, Err(InstructionError::InsufficientFunds));
645 }
646
647 #[test]
648 fn withdraw_inx_uninitialized_acc_two_withdraws_ok() {
649 prepare_mockup!(
650 invoke_context,
651 instruction_accounts,
652 rent,
653 transaction_context
654 );
655 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
656 let nonce_account = instruction_context
657 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
658 .unwrap();
659 let to_account = instruction_context
660 .try_borrow_instruction_account(WITHDRAW_TO_ACCOUNT_INDEX)
661 .unwrap();
662 let mut signers = HashSet::new();
663 signers.insert(*nonce_account.get_key());
664 set_invoke_context_blockhash!(invoke_context, 0);
665 let withdraw_lamports = nonce_account.get_lamports() / 2;
666 let from_expect_lamports = nonce_account.get_lamports() - withdraw_lamports;
667 let to_expect_lamports = to_account.get_lamports() + withdraw_lamports;
668 drop(nonce_account);
669 drop(to_account);
670 withdraw_nonce_account(
671 NONCE_ACCOUNT_INDEX,
672 withdraw_lamports,
673 WITHDRAW_TO_ACCOUNT_INDEX,
674 &rent,
675 &signers,
676 &invoke_context,
677 &instruction_context,
678 )
679 .unwrap();
680 let nonce_account = instruction_context
681 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
682 .unwrap();
683 let to_account = instruction_context
684 .try_borrow_instruction_account(WITHDRAW_TO_ACCOUNT_INDEX)
685 .unwrap();
686 let versions = nonce_account.get_state::<Versions>().unwrap();
687 assert_eq!(versions.state(), &State::Uninitialized);
688 assert_eq!(nonce_account.get_lamports(), from_expect_lamports);
689 assert_eq!(to_account.get_lamports(), to_expect_lamports);
690 let withdraw_lamports = nonce_account.get_lamports();
691 let from_expect_lamports = nonce_account.get_lamports() - withdraw_lamports;
692 let to_expect_lamports = to_account.get_lamports() + withdraw_lamports;
693 drop(nonce_account);
694 drop(to_account);
695 withdraw_nonce_account(
696 NONCE_ACCOUNT_INDEX,
697 withdraw_lamports,
698 WITHDRAW_TO_ACCOUNT_INDEX,
699 &rent,
700 &signers,
701 &invoke_context,
702 &instruction_context,
703 )
704 .unwrap();
705 let nonce_account = instruction_context
706 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
707 .unwrap();
708 let to_account = instruction_context
709 .try_borrow_instruction_account(WITHDRAW_TO_ACCOUNT_INDEX)
710 .unwrap();
711 let versions = nonce_account.get_state::<Versions>().unwrap();
712 assert_eq!(versions.state(), &State::Uninitialized);
713 assert_eq!(nonce_account.get_lamports(), from_expect_lamports);
714 assert_eq!(to_account.get_lamports(), to_expect_lamports);
715 }
716
717 #[test]
718 fn withdraw_inx_initialized_acc_two_withdraws_ok() {
719 prepare_mockup!(
720 invoke_context,
721 instruction_accounts,
722 rent,
723 transaction_context
724 );
725 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
726 let mut nonce_account = instruction_context
727 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
728 .unwrap();
729 let to_account = instruction_context
730 .try_borrow_instruction_account(WITHDRAW_TO_ACCOUNT_INDEX)
731 .unwrap();
732 let mut signers = HashSet::new();
733 signers.insert(*nonce_account.get_key());
734 set_invoke_context_blockhash!(invoke_context, 31);
735 let authority = *nonce_account.get_key();
736 initialize_nonce_account(&mut nonce_account, &authority, &rent, &invoke_context).unwrap();
737 let versions = nonce_account.get_state::<Versions>().unwrap();
738 let data = nonce::state::Data::new(
739 authority,
740 DurableNonce::from_blockhash(&invoke_context.environment_config.blockhash),
741 invoke_context
742 .environment_config
743 .blockhash_lamports_per_signature,
744 );
745 assert_eq!(versions.state(), &State::Initialized(data.clone()));
746 let withdraw_lamports = 42;
747 let from_expect_lamports = nonce_account.get_lamports() - withdraw_lamports;
748 let to_expect_lamports = to_account.get_lamports() + withdraw_lamports;
749 drop(nonce_account);
750 drop(to_account);
751 withdraw_nonce_account(
752 NONCE_ACCOUNT_INDEX,
753 withdraw_lamports,
754 WITHDRAW_TO_ACCOUNT_INDEX,
755 &rent,
756 &signers,
757 &invoke_context,
758 &instruction_context,
759 )
760 .unwrap();
761 let nonce_account = instruction_context
762 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
763 .unwrap();
764 let to_account = instruction_context
765 .try_borrow_instruction_account(WITHDRAW_TO_ACCOUNT_INDEX)
766 .unwrap();
767 let versions = nonce_account.get_state::<Versions>().unwrap();
768 let data = nonce::state::Data::new(
769 data.authority,
770 DurableNonce::from_blockhash(&invoke_context.environment_config.blockhash),
771 invoke_context
772 .environment_config
773 .blockhash_lamports_per_signature,
774 );
775 assert_eq!(versions.state(), &State::Initialized(data));
776 assert_eq!(nonce_account.get_lamports(), from_expect_lamports);
777 assert_eq!(to_account.get_lamports(), to_expect_lamports);
778 set_invoke_context_blockhash!(invoke_context, 0);
779 let withdraw_lamports = nonce_account.get_lamports();
780 let from_expect_lamports = nonce_account.get_lamports() - withdraw_lamports;
781 let to_expect_lamports = to_account.get_lamports() + withdraw_lamports;
782 drop(nonce_account);
783 drop(to_account);
784 withdraw_nonce_account(
785 NONCE_ACCOUNT_INDEX,
786 withdraw_lamports,
787 WITHDRAW_TO_ACCOUNT_INDEX,
788 &rent,
789 &signers,
790 &invoke_context,
791 &instruction_context,
792 )
793 .unwrap();
794 let nonce_account = instruction_context
795 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
796 .unwrap();
797 let to_account = instruction_context
798 .try_borrow_instruction_account(WITHDRAW_TO_ACCOUNT_INDEX)
799 .unwrap();
800 let versions = nonce_account.get_state::<Versions>().unwrap();
801 assert_eq!(versions.state(), &State::Uninitialized);
802 assert_eq!(nonce_account.get_lamports(), from_expect_lamports);
803 assert_eq!(to_account.get_lamports(), to_expect_lamports);
804 }
805
806 #[test]
807 fn withdraw_inx_initialized_acc_nonce_too_early_fail() {
808 prepare_mockup!(
809 invoke_context,
810 instruction_accounts,
811 rent,
812 transaction_context
813 );
814 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
815 let mut nonce_account = instruction_context
816 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
817 .unwrap();
818 let to_account = instruction_context
819 .try_borrow_instruction_account(WITHDRAW_TO_ACCOUNT_INDEX)
820 .unwrap();
821 set_invoke_context_blockhash!(invoke_context, 0);
822 let authorized = *nonce_account.get_key();
823 initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context).unwrap();
824 let mut signers = HashSet::new();
825 signers.insert(*nonce_account.get_key());
826 let withdraw_lamports = nonce_account.get_lamports();
827 drop(nonce_account);
828 drop(to_account);
829 let result = withdraw_nonce_account(
830 NONCE_ACCOUNT_INDEX,
831 withdraw_lamports,
832 WITHDRAW_TO_ACCOUNT_INDEX,
833 &rent,
834 &signers,
835 &invoke_context,
836 &instruction_context,
837 );
838 assert_eq!(result, Err(SystemError::NonceBlockhashNotExpired.into()));
839 }
840
841 #[test]
842 fn withdraw_inx_initialized_acc_insuff_funds_fail() {
843 prepare_mockup!(
844 invoke_context,
845 instruction_accounts,
846 rent,
847 transaction_context
848 );
849 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
850 let mut nonce_account = instruction_context
851 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
852 .unwrap();
853 set_invoke_context_blockhash!(invoke_context, 95);
854 let authorized = *nonce_account.get_key();
855 initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context).unwrap();
856 set_invoke_context_blockhash!(invoke_context, 63);
857 let mut signers = HashSet::new();
858 signers.insert(*nonce_account.get_key());
859 let withdraw_lamports = nonce_account.get_lamports() + 1;
860 drop(nonce_account);
861 let result = withdraw_nonce_account(
862 NONCE_ACCOUNT_INDEX,
863 withdraw_lamports,
864 WITHDRAW_TO_ACCOUNT_INDEX,
865 &rent,
866 &signers,
867 &invoke_context,
868 &instruction_context,
869 );
870 assert_eq!(result, Err(InstructionError::InsufficientFunds));
871 }
872
873 #[test]
874 fn withdraw_inx_initialized_acc_insuff_rent_fail() {
875 prepare_mockup!(
876 invoke_context,
877 instruction_accounts,
878 rent,
879 transaction_context
880 );
881 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
882 let mut nonce_account = instruction_context
883 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
884 .unwrap();
885 set_invoke_context_blockhash!(invoke_context, 95);
886 let authorized = *nonce_account.get_key();
887 initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context).unwrap();
888 set_invoke_context_blockhash!(invoke_context, 63);
889 let mut signers = HashSet::new();
890 signers.insert(*nonce_account.get_key());
891 let withdraw_lamports = 42 + 1;
892 drop(nonce_account);
893 let result = withdraw_nonce_account(
894 NONCE_ACCOUNT_INDEX,
895 withdraw_lamports,
896 WITHDRAW_TO_ACCOUNT_INDEX,
897 &rent,
898 &signers,
899 &invoke_context,
900 &instruction_context,
901 );
902 assert_eq!(result, Err(InstructionError::InsufficientFunds));
903 }
904
905 #[test]
906 fn withdraw_inx_overflow() {
907 prepare_mockup!(
908 invoke_context,
909 instruction_accounts,
910 rent,
911 transaction_context
912 );
913 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
914 let mut nonce_account = instruction_context
915 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
916 .unwrap();
917 set_invoke_context_blockhash!(invoke_context, 95);
918 let authorized = *nonce_account.get_key();
919 initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context).unwrap();
920 set_invoke_context_blockhash!(invoke_context, 63);
921 let mut signers = HashSet::new();
922 signers.insert(*nonce_account.get_key());
923 let withdraw_lamports = u64::MAX - 54;
924 drop(nonce_account);
925 let result = withdraw_nonce_account(
926 NONCE_ACCOUNT_INDEX,
927 withdraw_lamports,
928 WITHDRAW_TO_ACCOUNT_INDEX,
929 &rent,
930 &signers,
931 &invoke_context,
932 &instruction_context,
933 );
934 assert_eq!(result, Err(InstructionError::InsufficientFunds));
935 }
936
937 #[test]
938 fn initialize_inx_ok() {
939 prepare_mockup!(
940 invoke_context,
941 instruction_accounts,
942 rent,
943 transaction_context
944 );
945 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
946 let mut nonce_account = instruction_context
947 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
948 .unwrap();
949 let versions = nonce_account.get_state::<Versions>().unwrap();
950 assert_eq!(versions.state(), &State::Uninitialized);
951 let mut signers = HashSet::new();
952 signers.insert(*nonce_account.get_key());
953 set_invoke_context_blockhash!(invoke_context, 0);
954 let authorized = *nonce_account.get_key();
955 let result =
956 initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context);
957 let data = nonce::state::Data::new(
958 authorized,
959 DurableNonce::from_blockhash(&invoke_context.environment_config.blockhash),
960 invoke_context
961 .environment_config
962 .blockhash_lamports_per_signature,
963 );
964 assert_eq!(result, Ok(()));
965 let versions = nonce_account.get_state::<Versions>().unwrap();
966 assert_eq!(versions.state(), &State::Initialized(data));
967 }
968
969 #[test]
970 fn initialize_inx_initialized_account_fail() {
971 prepare_mockup!(
972 invoke_context,
973 instruction_accounts,
974 rent,
975 transaction_context
976 );
977 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
978 let mut nonce_account = instruction_context
979 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
980 .unwrap();
981 set_invoke_context_blockhash!(invoke_context, 31);
982 let authorized = *nonce_account.get_key();
983 initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context).unwrap();
984 set_invoke_context_blockhash!(invoke_context, 0);
985 let result =
986 initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context);
987 assert_eq!(result, Err(InstructionError::InvalidAccountData));
988 }
989
990 #[test]
991 fn initialize_inx_uninitialized_acc_insuff_funds_fail() {
992 prepare_mockup!(
993 invoke_context,
994 instruction_accounts,
995 rent,
996 transaction_context
997 );
998 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
999 let mut nonce_account = instruction_context
1000 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
1001 .unwrap();
1002 nonce_account.checked_sub_lamports(42 * 2).unwrap();
1003 set_invoke_context_blockhash!(invoke_context, 63);
1004 let authorized = *nonce_account.get_key();
1005 let result =
1006 initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context);
1007 assert_eq!(result, Err(InstructionError::InsufficientFunds));
1008 }
1009
1010 #[test]
1011 fn authorize_inx_ok() {
1012 prepare_mockup!(
1013 invoke_context,
1014 instruction_accounts,
1015 rent,
1016 transaction_context
1017 );
1018 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
1019 let mut nonce_account = instruction_context
1020 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
1021 .unwrap();
1022 let mut signers = HashSet::new();
1023 signers.insert(*nonce_account.get_key());
1024 set_invoke_context_blockhash!(invoke_context, 31);
1025 let authorized = *nonce_account.get_key();
1026 initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context).unwrap();
1027 let authority = Pubkey::default();
1028 let data = nonce::state::Data::new(
1029 authority,
1030 DurableNonce::from_blockhash(&invoke_context.environment_config.blockhash),
1031 invoke_context
1032 .environment_config
1033 .blockhash_lamports_per_signature,
1034 );
1035 authorize_nonce_account(&mut nonce_account, &authority, &signers, &invoke_context).unwrap();
1036 let versions = nonce_account.get_state::<Versions>().unwrap();
1037 assert_eq!(versions.state(), &State::Initialized(data));
1038 }
1039
1040 #[test]
1041 fn authorize_inx_uninitialized_state_fail() {
1042 prepare_mockup!(
1043 invoke_context,
1044 instruction_accounts,
1045 rent,
1046 transaction_context
1047 );
1048 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
1049 let mut nonce_account = instruction_context
1050 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
1051 .unwrap();
1052 let mut signers = HashSet::new();
1053 signers.insert(*nonce_account.get_key());
1054 let result = authorize_nonce_account(
1055 &mut nonce_account,
1056 &Pubkey::default(),
1057 &signers,
1058 &invoke_context,
1059 );
1060 assert_eq!(result, Err(InstructionError::InvalidAccountData));
1061 }
1062
1063 #[test]
1064 fn authorize_inx_bad_authority_fail() {
1065 prepare_mockup!(
1066 invoke_context,
1067 instruction_accounts,
1068 rent,
1069 transaction_context
1070 );
1071 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
1072 let mut nonce_account = instruction_context
1073 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
1074 .unwrap();
1075 let mut signers = HashSet::new();
1076 signers.insert(*nonce_account.get_key());
1077 set_invoke_context_blockhash!(invoke_context, 31);
1078 let authorized = Pubkey::default();
1079 initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context).unwrap();
1080 let result =
1081 authorize_nonce_account(&mut nonce_account, &authorized, &signers, &invoke_context);
1082 assert_eq!(result, Err(InstructionError::MissingRequiredSignature));
1083 }
1084
1085 #[test]
1086 fn verify_nonce_ok() {
1087 prepare_mockup!(
1088 invoke_context,
1089 instruction_accounts,
1090 rent,
1091 transaction_context
1092 );
1093
1094 let blockhash = {
1095 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
1096 let mut nonce_account = instruction_context
1097 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
1098 .unwrap();
1099 let mut signers = HashSet::new();
1100 signers.insert(nonce_account.get_key());
1101 let versions: Versions = nonce_account.get_state().unwrap();
1102 assert_eq!(versions.state(), &State::Uninitialized);
1104 set_invoke_context_blockhash!(invoke_context, 0);
1105 let authorized = *nonce_account.get_key();
1106 initialize_nonce_account(&mut nonce_account, &authorized, &rent, &invoke_context)
1107 .unwrap();
1108 drop(nonce_account);
1109 invoke_context.environment_config.blockhash
1110 };
1111
1112 transaction_context.pop().unwrap();
1113 let post_accounts = transaction_context.deconstruct_without_keys().unwrap();
1114 assert_matches!(
1115 verify_nonce_account(
1116 post_accounts.get(NONCE_ACCOUNT_INDEX as usize).unwrap(),
1117 DurableNonce::from_blockhash(&blockhash).as_hash(),
1118 ),
1119 Some(_)
1120 );
1121 }
1122
1123 #[test]
1124 fn verify_nonce_bad_acc_state_fail() {
1125 prepare_mockup!(
1126 invoke_context,
1127 instruction_accounts,
1128 rent,
1129 transaction_context
1130 );
1131
1132 {
1133 push_instruction_context!(invoke_context, _instruction_context, instruction_accounts);
1134 }
1135
1136 transaction_context.pop().unwrap();
1137 let post_accounts = transaction_context.deconstruct_without_keys().unwrap();
1138 assert_eq!(
1139 verify_nonce_account(
1140 post_accounts.get(NONCE_ACCOUNT_INDEX as usize).unwrap(),
1141 &Hash::default(),
1142 ),
1143 None
1144 );
1145 }
1146
1147 #[test]
1148 fn verify_nonce_bad_query_hash_fail() {
1149 prepare_mockup!(
1150 invoke_context,
1151 instruction_accounts,
1152 rent,
1153 transaction_context
1154 );
1155
1156 let blockhash = {
1157 push_instruction_context!(invoke_context, instruction_context, instruction_accounts);
1158 let mut nonce_account = instruction_context
1159 .try_borrow_instruction_account(NONCE_ACCOUNT_INDEX)
1160 .unwrap();
1161 let mut signers = HashSet::new();
1162 signers.insert(nonce_account.get_key());
1163 let versions: Versions = nonce_account.get_state().unwrap();
1164 assert_eq!(versions.state(), &State::Uninitialized);
1166 set_invoke_context_blockhash!(invoke_context, 0);
1167 let authorized = *nonce_account.get_key();
1168 initialize_nonce_account(
1169 &mut nonce_account,
1170 &authorized,
1171 &Rent::free(),
1172 &invoke_context,
1173 )
1174 .unwrap();
1175 set_invoke_context_blockhash!(invoke_context, 1);
1176 drop(nonce_account);
1177 invoke_context.environment_config.blockhash
1178 };
1179
1180 transaction_context.pop().unwrap();
1181 let post_accounts = transaction_context.deconstruct_without_keys().unwrap();
1182
1183 assert_eq!(
1184 verify_nonce_account(
1185 post_accounts.get(NONCE_ACCOUNT_INDEX as usize).unwrap(),
1186 DurableNonce::from_blockhash(&blockhash).as_hash(),
1187 ),
1188 None
1189 );
1190 }
1191}