miraland_system_program/
system_instruction.rs

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        // New is in Uninitialzed state
345        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        // First nonce instruction drives state from Uninitialized to Initialized
356        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        // Second nonce instruction consumes and replaces stored nonce
366        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        // Third nonce instruction for fun and profit
376        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        // Empties Account balance
405        assert_eq!(nonce_account.get_lamports(), expect_nonce_lamports);
406        // Account balance goes to `to`
407        assert_eq!(to_account.get_lamports(), expect_to_lamports);
408        let versions = nonce_account.get_state::<Versions>().unwrap();
409        // Empty balance deinitializes data
410        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        // Nonce account did not sign
436        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        // New is in Uninitialzed state
1104        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        // New is in Uninitialzed state
1158        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}