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