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