Skip to main content

rialo_s_system_program/
system_instruction.rs

1// Copyright (c) Subzero Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3// This file is either (a) original to Subzero Labs, Inc. or (b) derived from the Anza codebase and modified by Subzero Labs, Inc.
4
5use 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
22/// Addition that returns [`InstructionError::InsufficientFunds`] on overflow.
23fn 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        // New is in Uninitialzed state
353        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        // First nonce instruction drives state from Uninitialized to Initialized
366        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        // Second nonce instruction consumes and replaces stored nonce
378        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        // Third nonce instruction for fun and profit
390        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        // Empties Account balance
419        assert_eq!(nonce_account.get_kelvins(), expect_nonce_kelvins);
420        // Account balance goes to `to`
421        assert_eq!(to_account.get_kelvins(), expect_to_kelvins);
422        let versions = nonce_account.get_state::<Versions>().unwrap();
423        // Empty balance deinitializes data
424        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        // Nonce account did not sign
452        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        // New is in Uninitialzed state
1126        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        // New is in Uninitialzed state
1181        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}