Skip to main content

mollusk_svm_fuzz_fixture/
lib.rs

1//! Mollusk SVM Fuzz: Mollusk-compatible fuzz fixture for SVM programs.
2//!
3//! Note: The fixtures defined in this library are compatible with Mollusk,
4//! which means developers can fuzz programs using the Mollusk harness.
5//! However, these fixtures (and this library) do not depend on the harness.
6//! They can be used to fuzz a custom entrypoint of the developer's choice.
7
8pub mod account;
9pub mod compute_budget;
10pub mod context;
11pub mod effects;
12pub mod feature_set;
13pub mod proto {
14    include!(concat!(env!("OUT_DIR"), "/org.mollusk.svm.rs"));
15}
16pub mod sysvars;
17
18use {
19    crate::{context::Context, effects::Effects, proto::InstrFixture as ProtoFixture},
20    mollusk_svm_fuzz_fs::{FsHandler, IntoSerializableFixture, SerializableFixture},
21    solana_keccak_hasher::{Hash, Hasher},
22};
23
24/// A fixture for invoking a single instruction against a simulated SVM
25/// program runtime environment, for a given program.
26#[derive(Clone, Debug, PartialEq)]
27pub struct Fixture {
28    /// The fixture inputs.
29    pub input: Context,
30    /// The fixture outputs.
31    pub output: Effects,
32}
33
34impl Fixture {
35    pub fn decode(blob: &[u8]) -> Self {
36        let proto_fixture = <ProtoFixture as SerializableFixture>::decode(blob);
37        proto_fixture.into()
38    }
39
40    pub fn load_from_blob_file(file_path: &str) -> Self {
41        let proto_fixture: ProtoFixture = FsHandler::load_from_blob_file(file_path);
42        proto_fixture.into()
43    }
44
45    pub fn load_from_json_file(file_path: &str) -> Self {
46        let proto_fixture: ProtoFixture = FsHandler::load_from_json_file(file_path);
47        proto_fixture.into()
48    }
49}
50
51impl From<ProtoFixture> for Fixture {
52    fn from(value: ProtoFixture) -> Self {
53        // All blobs should have an input and output.
54        Self {
55            input: value.input.unwrap().into(),
56            output: value.output.unwrap().into(),
57        }
58    }
59}
60
61impl From<Fixture> for ProtoFixture {
62    fn from(value: Fixture) -> Self {
63        Self {
64            input: Some(value.input.into()),
65            output: Some(value.output.into()),
66        }
67    }
68}
69
70impl SerializableFixture for ProtoFixture {
71    // Manually implemented for deterministic hashes.
72    fn hash(&self) -> Hash {
73        let mut hasher = Hasher::default();
74        if let Some(input) = &self.input {
75            crate::context::hash_proto_context(&mut hasher, input);
76        }
77        if let Some(output) = &self.output {
78            crate::effects::hash_proto_effects(&mut hasher, output);
79        }
80        hasher.result()
81    }
82}
83
84impl IntoSerializableFixture for Fixture {
85    type Fixture = ProtoFixture;
86
87    fn into(self) -> Self::Fixture {
88        Into::into(self)
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use {
95        super::{proto::InstrFixture, Fixture},
96        crate::{context::Context, effects::Effects, sysvars::Sysvars},
97        agave_feature_set::FeatureSet,
98        mollusk_svm_fuzz_fs::SerializableFixture,
99        solana_account::Account,
100        solana_compute_budget::compute_budget::ComputeBudget,
101        solana_instruction::AccountMeta,
102        solana_keccak_hasher::Hash,
103        solana_pubkey::Pubkey,
104    };
105
106    fn produce_hash(fixture: &Fixture) -> Hash {
107        let proto_fixture: InstrFixture = fixture.clone().into();
108        proto_fixture.hash()
109    }
110
111    #[test]
112    fn test_consistent_hashing() {
113        const ITERATIONS: usize = 1000;
114
115        let compute_budget = ComputeBudget::new_with_defaults(true, true);
116
117        let feature_set = FeatureSet::all_enabled();
118        let sysvars = Sysvars::default();
119        let program_id = Pubkey::default();
120        let instruction_accounts = vec![
121            AccountMeta::new(Pubkey::new_unique(), false),
122            AccountMeta::new(Pubkey::new_unique(), false),
123            AccountMeta::new(Pubkey::new_unique(), false),
124            AccountMeta::new(Pubkey::new_unique(), false),
125        ];
126        let instruction_data = vec![4; 24];
127        let accounts = instruction_accounts
128            .iter()
129            .map(|meta| (meta.pubkey, Account::new(42, 42, &Pubkey::default())))
130            .collect::<Vec<_>>();
131
132        let context = Context {
133            compute_budget,
134            feature_set,
135            sysvars,
136            program_id,
137            instruction_accounts,
138            instruction_data,
139            accounts,
140        };
141        let effects = Effects::default();
142
143        let fixture = Fixture {
144            input: context,
145            output: effects,
146        };
147
148        let mut last_hash = produce_hash(&fixture);
149        for _ in 0..ITERATIONS {
150            let new_hash = produce_hash(&fixture);
151            assert_eq!(last_hash, new_hash);
152            last_hash = new_hash;
153        }
154    }
155}