Skip to main content

mollusk_svm_fuzz_fs/
lib.rs

1use {
2    prost::Message,
3    serde::{de::DeserializeOwned, Serialize},
4    std::{
5        fs::{self, File},
6        io::{Read, Write},
7        path::Path,
8    },
9};
10
11/// Represents a serializable fuzz fixture.
12pub trait SerializableFixture: Default + DeserializeOwned + Message + Serialize + Sized {
13    /// Decode a `Protobuf` blob into a fixture.
14    fn decode(blob: &[u8]) -> Self {
15        <Self as Message>::decode(blob)
16            .unwrap_or_else(|err| panic!("Failed to decode fixture: {}", err))
17    }
18
19    /// Encode the fixture into a `Protobuf` blob.
20    fn encode(&self) -> Vec<u8> {
21        let mut buf = Vec::new();
22        Message::encode(self, &mut buf).expect("Failed to encode fixture");
23        buf
24    }
25
26    /// Hash the fixture's contents into a Keccak hash.
27    fn hash(&self) -> solana_keccak_hasher::Hash;
28}
29
30/// Represents a fixture that can be converted into a serializable fixture.
31pub trait IntoSerializableFixture {
32    type Fixture: SerializableFixture;
33
34    fn into(self) -> Self::Fixture;
35}
36
37pub struct FsHandler<SF>
38where
39    SF: SerializableFixture,
40{
41    serializable_fixture: SF,
42}
43
44impl<SF> FsHandler<SF>
45where
46    SF: SerializableFixture,
47{
48    pub fn new<T: IntoSerializableFixture<Fixture = SF>>(fix: T) -> Self {
49        let serializable_fixture = fix.into();
50        Self {
51            serializable_fixture,
52        }
53    }
54
55    /// Dumps the fixture to a protobuf binary blob file.
56    /// The file name is a hash of the fixture with the `.fix` extension.
57    pub fn dump_to_blob_file(&self, dir: &str) {
58        let blob = SerializableFixture::encode(&self.serializable_fixture);
59
60        let hash = self.serializable_fixture.hash();
61        let file_name = format!("instr-{}.fix", bs58::encode(hash).into_string());
62
63        write_file(Path::new(dir), &file_name, &blob);
64    }
65
66    /// Dumps the fixture to a JSON file.
67    /// The file name is a hash of the fixture with the `.json` extension.
68    pub fn dump_to_json_file(self, dir_path: &str) {
69        let json = serde_json::to_string_pretty(&self.serializable_fixture)
70            .expect("Failed to serialize fixture to JSON");
71
72        let hash = self.serializable_fixture.hash();
73        let file_name = format!("instr-{}.json", bs58::encode(hash).into_string());
74
75        write_file(Path::new(dir_path), &file_name, json.as_bytes());
76    }
77
78    /// Loads a fixture from a protobuf binary blob file.
79    pub fn load_from_blob_file(file_path: &str) -> SF {
80        if !file_path.ends_with(".fix") {
81            panic!("Invalid fixture file extension: {}", file_path);
82        }
83        let mut file = File::open(file_path).expect("Failed to open fixture file");
84        let mut blob = Vec::new();
85        file.read_to_end(&mut blob)
86            .expect("Failed to read fixture file");
87        <SF as SerializableFixture>::decode(&blob)
88    }
89
90    /// Loads a fixture from a JSON file.
91    pub fn load_from_json_file(file_path: &str) -> SF {
92        if !file_path.ends_with(".json") {
93            panic!("Invalid fixture file extension: {}", file_path);
94        }
95        let mut file = File::open(file_path).expect("Failed to open fixture file");
96        let mut json = String::new();
97        file.read_to_string(&mut json)
98            .expect("Failed to read fixture file");
99        serde_json::from_str(&json).expect("Failed to deserialize fixture from JSON")
100    }
101}
102
103fn write_file(dir: &Path, file_name: &str, data: &[u8]) {
104    fs::create_dir_all(dir).expect("Failed to create directory");
105    let file_path = dir.join(file_name);
106    let mut file = File::create(file_path).unwrap();
107    file.write_all(data)
108        .expect("Failed to write fixture to file");
109}