trident_fuzz/
snapshot.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#![allow(dead_code)] // The Snapshot is constructed in the FuzzTestExecutor macro and is generated automatically

use anchor_lang::solana_program::account_info::Account as Acc;
use anchor_lang::solana_program::account_info::AccountInfo;
use solana_sdk::{account::Account, instruction::AccountMeta};

use crate::fuzz_client::FuzzClient;

use crate::error::*;
use crate::ix_ops::IxOps;
pub struct Snapshot<'info, T> {
    before: Vec<Option<Account>>,
    before_acc_inf: Vec<Option<AccountInfo<'info>>>,
    after: Vec<Option<Account>>,
    after_acc_inf: Vec<Option<AccountInfo<'info>>>,
    metas: Vec<AccountMeta>,
    ix: &'info T,
}

impl<'info, T> Snapshot<'info, T>
where
    T: IxOps<'info>,
{
    pub fn new_empty(ix: &'info T) -> Snapshot<'info, T> {
        let capacity = 0;
        Self {
            before: Vec::with_capacity(capacity),
            before_acc_inf: Vec::with_capacity(capacity),
            after: Vec::with_capacity(capacity),
            after_acc_inf: Vec::with_capacity(capacity),
            metas: vec![],
            ix,
        }
    }
    pub fn add_metas(&mut self, metas: &[AccountMeta]) {
        let capacity = metas.len();
        self.before = Vec::with_capacity(capacity);
        self.before_acc_inf = Vec::with_capacity(capacity);
        self.after = Vec::with_capacity(capacity);
        self.after_acc_inf = Vec::with_capacity(capacity);
        self.metas = metas.to_vec();
    }
    pub fn new(metas: &[AccountMeta], ix: &'info T) -> Snapshot<'info, T> {
        let capacity = metas.len();
        Self {
            before: Vec::with_capacity(capacity),
            before_acc_inf: Vec::with_capacity(capacity),
            after: Vec::with_capacity(capacity),
            after_acc_inf: Vec::with_capacity(capacity),
            metas: metas.to_vec(),
            ix,
        }
    }

    pub fn capture_before(
        &mut self,
        client: &mut impl FuzzClient,
    ) -> Result<(), FuzzClientErrorWithOrigin> {
        self.before = self
            .capture(client)
            .map_err(|e| e.with_context(Context::Pre))?;
        Ok(())
    }

    pub fn capture_after(
        &mut self,
        client: &mut impl FuzzClient,
    ) -> Result<(), FuzzClientErrorWithOrigin> {
        self.after = self
            .capture(client)
            .map_err(|e| e.with_context(Context::Post))?;
        Ok(())
    }

    fn capture(
        &mut self,
        client: &mut impl FuzzClient,
    ) -> Result<Vec<Option<Account>>, FuzzClientErrorWithOrigin> {
        client.get_accounts(&self.metas)
    }

    fn calculate_account_info(
        accounts: &'info mut [Option<Account>],
        metas: &'info [AccountMeta],
    ) -> Vec<Option<AccountInfo<'info>>> {
        accounts
            .iter_mut()
            .zip(metas)
            .map(|(account, meta)| {
                if let Some(account) = account {
                    let (lamports, data, owner, executable, rent_epoch) = account.get();
                    Some(AccountInfo::new(
                        &meta.pubkey,
                        meta.is_signer,
                        meta.is_writable,
                        lamports,
                        data,
                        owner,
                        executable,
                        rent_epoch,
                    ))
                } else {
                    None
                }
            })
            .collect()
    }

    fn set_missing_accounts_to_default(accounts: &mut [Option<Account>]) {
        for acc in accounts.iter_mut() {
            if acc.is_none() {
                *acc = Some(solana_sdk::account::Account::default());
            }
        }
    }

    pub fn get_raw_pre_ix_accounts(&'info mut self) -> &[Option<AccountInfo<'info>>] {
        Self::set_missing_accounts_to_default(&mut self.before);
        self.before_acc_inf = Self::calculate_account_info(&mut self.before, &self.metas);
        &self.before_acc_inf
    }

    pub fn get_snapshot(
        &'info mut self,
    ) -> Result<(T::IxSnapshot, T::IxSnapshot), FuzzingErrorWithOrigin> {
        // When user passes an account that is not initialized, the runtime will provide
        // a default empty account to the program. If the uninitialized account is of type
        // AccountInfo, Signer or UncheckedAccount, Anchor will not return an error. However
        // when we try to fetch "on-chain" accounts and an account is not initilized, this
        // account simply does not exist and the get_account() method returns None. To prevent
        // errors during deserialization due to missing accounts, we replace the missing accounts
        // with default values similar as the runtime does.
        Self::set_missing_accounts_to_default(&mut self.before);
        Self::set_missing_accounts_to_default(&mut self.after);

        self.before_acc_inf = Self::calculate_account_info(&mut self.before, &self.metas);
        self.after_acc_inf = Self::calculate_account_info(&mut self.after, &self.metas);

        let mut remaining_accounts_before: &[Option<AccountInfo<'info>>] = &self.before_acc_inf;
        let mut remaining_accounts_after: &[Option<AccountInfo<'info>>] = &self.after_acc_inf;

        let pre_ix = self
            .ix
            .deserialize_accounts(&mut remaining_accounts_before)
            .map_err(|e| e.with_context(Context::Pre))?;
        let post_ix = self
            .ix
            .deserialize_accounts(&mut remaining_accounts_after)
            .map_err(|e| e.with_context(Context::Post))?;
        Ok((pre_ix, post_ix))
    }
}