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