phink_lib/fuzzer/
parser.rs

1use crate::{
2    cli::config::OriginFuzzingOption,
3    contract::{
4        remote::{
5            BalanceOf,
6            ContractResponse,
7            FullContractResponse,
8        },
9        runtime::Runtime,
10        selectors::selector::Selector,
11    },
12    fuzzer::manager::CampaignManager,
13    ResultOf,
14};
15use contract_transcode::{
16    ContractMessageTranscoder,
17    Value,
18};
19use prettytable::{
20    Cell,
21    Row,
22    Table,
23};
24use serde_derive::Serialize;
25use sp_core::crypto::AccountId32;
26use std::fmt::{
27    Display,
28    Formatter,
29};
30use OriginFuzzingOption::{
31    DisableOriginFuzzing,
32    EnableOriginFuzzing,
33};
34
35pub const DELIMITER: [u8; 8] = [42; 8]; // call delimiter for each message
36pub const MIN_SEED_LEN: usize = 9;
37/// 0..4 covers indices 0, 1, 2, and 3. (value to be transfered)
38/// 4 covers index 4. (origin) (optionnal)
39/// 5.. starts from index 5 and goes to the end of the array.
40#[derive(Clone, Copy)]
41pub struct Data<'a> {
42    pub data: &'a [u8],
43    pub pointer: usize,
44    pub size: usize,
45    pub max_messages_per_exec: usize,
46}
47
48#[derive(Debug, Clone, Serialize)]
49pub struct Message {
50    pub is_payable: bool,
51    pub payload: Vec<u8>,
52    pub value_token: BalanceOf<Runtime>,
53    pub message_metadata: Value,
54    pub origin: Origin,
55}
56
57impl Message {
58    pub fn display_with_reply(&self, reply: &ContractResponse) -> String {
59        format!(
60            "ā›½ļø Gas required: {}\n\
61             šŸ”„ Gas consumed: {}\n\
62             šŸ§‘ Origin: {:?} ({})\n\
63             šŸ’¾ Storage deposit: {:?}{}",
64            reply.gas_required,
65            reply.gas_consumed,
66            self.origin,
67            AccountId32::new([self.origin.into(); 32]),
68            reply.storage_deposit,
69            if self.is_payable {
70                format!(
71                    "\nšŸ’ø Message was payable and {} units were transferred",
72                    self.value_token
73                )
74            } else {
75                String::new()
76            }
77        )
78    }
79    pub fn print(&self) -> String {
80        format!(
81            "Payload:\t0x{}\n\
82             Origin:\t{:?} (identifier: {})\n\
83             {}\
84             Message:\t{}\n\n",
85            hex::encode(&self.payload),
86            AccountId32::new([self.origin.into(); 32]),
87            self.origin.0,
88            if self.is_payable {
89                format!("Transfered: {}\n", self.value_token)
90            } else {
91                String::new()
92            },
93            self.message_metadata
94        )
95    }
96}
97
98impl Display for Message {
99    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
100        f.write_str(self.message_metadata.to_string().as_str())
101    }
102}
103
104#[derive(Debug, Clone, Serialize)]
105pub struct OneInput {
106    pub messages: Vec<Message>,
107    pub fuzz_option: OriginFuzzingOption,
108    pub raw_binary: Vec<u8>,
109}
110
111impl OneInput {
112    /// Pretty print the result of `OneInput`
113    #[allow(dead_code)]
114    pub fn pretty_print(&self, responses: Vec<FullContractResponse>) {
115        println!("\n🌱 Executing new seed");
116        let mut table = Table::new();
117        table.add_row(Row::new(vec![Cell::new("Message"), Cell::new("Details")]));
118
119        for (response, message) in responses.iter().zip(&self.messages) {
120            let call_description = message.message_metadata.to_string();
121            let debug = message.display_with_reply(response.get());
122
123            table.add_row(Row::new(vec![
124                Cell::new(&call_description),
125                Cell::new(&debug),
126            ]));
127        }
128
129        table.printstd();
130    }
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
134pub struct Origin(pub u8);
135impl Default for Origin {
136    fn default() -> Self {
137        Origin(1)
138    }
139}
140impl From<u8> for Origin {
141    fn from(value: u8) -> Self {
142        Origin(value)
143    }
144}
145impl From<Origin> for u8 {
146    fn from(origin: Origin) -> Self {
147        origin.0
148    }
149}
150
151impl Data<'_> {
152    fn size_limit_reached(&self) -> bool {
153        self.size >= self.max_messages_per_exec
154    }
155}
156
157impl<'a> Iterator for Data<'a> {
158    type Item = &'a [u8];
159
160    fn next(&mut self) -> Option<Self::Item> {
161        if self.size_limit_reached() {
162            return None;
163        }
164        // If `max_messages_per_exec` is 1, return the entire remaining data
165        if self.max_messages_per_exec == 1 {
166            let res = &self.data[self.pointer..];
167            self.pointer = self.data.len();
168            self.size += 1;
169            return if res.len() >= MIN_SEED_LEN {
170                Some(res)
171            } else {
172                None
173            };
174        }
175
176        loop {
177            if self.data.len() <= self.pointer {
178                return None;
179            }
180            let next_delimiter = self.data[self.pointer..]
181                .windows(DELIMITER.len())
182                .position(|window| window == DELIMITER);
183
184            let next_pointer = match next_delimiter {
185                Some(delimiter) => self.pointer + delimiter,
186                None => self.data.len(),
187            };
188
189            let res = &self.data[self.pointer..next_pointer];
190            self.pointer = next_pointer + DELIMITER.len();
191
192            if res.len() >= MIN_SEED_LEN {
193                self.size += 1;
194                return Some(res);
195            }
196        }
197    }
198}
199
200pub fn try_parse_input(bytes: &[u8], manager: CampaignManager) -> Option<OneInput> {
201    let config = manager.config();
202    let data = Data {
203        data: bytes,
204        pointer: 0,
205        size: 0,
206        max_messages_per_exec: config.max_messages_per_exec.unwrap_or_default(),
207    };
208
209    let mut input = OneInput {
210        messages: vec![],
211        fuzz_option: config.should_fuzz_origin(),
212        raw_binary: Vec::new(),
213    };
214
215    let arc = manager.transcoder();
216    let guard = arc.try_lock().expect("Failed on `try_lock`");
217
218    for payload in data {
219        let origin = match input.fuzz_option {
220            EnableOriginFuzzing => Origin(payload[4]),
221            DisableOriginFuzzing => Origin::default(),
222        };
223        let mut encoded_message = vec![0u8; payload.len() - 5];
224        encoded_message.copy_from_slice(&payload[5..]);
225
226        let selector: [u8; 4] = encoded_message[0..4].try_into().expect("[0..4] to u8 fail");
227        let slctr = Selector::from(selector);
228        let db = manager.database();
229
230        // If we see a message being an invariant or our selector isn't a proper message we stop
231        if db.contains_invariant(&slctr) || !db.contains_message(&slctr) {
232            return None;
233        }
234
235        let mut encoded_cloned = encoded_message.clone();
236
237        match decode_contract_message(&guard, &mut encoded_cloned) {
238            Ok(message_metadata) => {
239                if data.max_messages_per_exec != 0
240                    && input.messages.len() <= data.max_messages_per_exec
241                {
242                    let is_payable: bool = db.is_payable(&slctr);
243                    let mut value_token: u128 = 0;
244                    if is_payable {
245                        value_token = u32::from_ne_bytes(payload[0..4].try_into().unwrap()) as u128 // todo:16
246                    }
247                    input.raw_binary = Vec::from(bytes);
248                    input.messages.push(Message {
249                        is_payable,
250                        payload: encoded_message,
251                        value_token,
252                        message_metadata,
253                        origin,
254                    });
255                }
256            }
257            Err(_) => {
258                return None;
259            }
260        }
261    }
262
263    if !input.messages.is_empty() {
264        return Some(input);
265    }
266    None
267}
268
269pub fn decode_contract_message(
270    guard: &ContractMessageTranscoder,
271    data: &mut Vec<u8>,
272) -> ResultOf<Value> {
273    use contract_transcode::Map;
274    use std::io::Read;
275
276    let mut data_as_slice = data.as_slice();
277    let mut msg_selector: [u8; 4] = [0u8; 4];
278    data_as_slice.read_exact(&mut msg_selector)?;
279    let msg_spec = guard
280        .metadata()
281        .spec()
282        .messages()
283        .iter()
284        .find(|x| msg_selector == x.selector().to_bytes())
285        .ok_or_else(|| {
286            anyhow::anyhow!(
287                "Message with selector {} not found in contract metadata",
288                hex::encode_upper(msg_selector)
289            )
290        })?;
291
292    let mut args = Vec::new();
293    for arg in msg_spec.args() {
294        let name = arg.label().to_string();
295        let value = guard.decode(arg.ty().ty().id, &mut data_as_slice)?;
296        args.push((Value::String(name), value));
297    }
298
299    if !data_as_slice.is_empty() {
300        return Err(anyhow::anyhow!(
301            "input length was longer than expected by {} byte(s).\n `{}` bytes were left unread",
302            data_as_slice.len(),
303            hex::encode_upper(data)
304        ));
305    }
306    let name = msg_spec.label().to_string();
307    let map = Map::new(Some(&name), args.into_iter().collect());
308
309    Ok(Value::Map(map))
310}
311#[cfg(test)]
312mod tests {
313    use super::*;
314
315    #[test]
316    fn test_data_iterator() {
317        let input = [
318            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 42, 42, 42, 42, 42, 42, 42, 42, 5, 6, 7, 23, 123, 1, 8,
319            12, 13, 14,
320        ];
321        let data = Data {
322            data: &input,
323            pointer: 0,
324            size: 0,
325            max_messages_per_exec: 2,
326        };
327
328        let result: Vec<&[u8]> = data.collect();
329        assert_eq!(
330            result,
331            vec![
332                &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
333                &[5, 6, 7, 23, 123, 1, 8, 12, 13, 14]
334            ]
335        );
336    }
337
338    #[test]
339    fn test_data_size_limit() {
340        let input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
341        let mut data = Data {
342            data: &input,
343            pointer: 0,
344            size: 0,
345            max_messages_per_exec: 1,
346        };
347
348        assert_eq!(data.next(), Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..]));
349        assert_eq!(data.next(), None);
350    }
351
352    #[test]
353    fn test_origin_default() {
354        assert_eq!(Origin::default(), Origin(1));
355    }
356
357    #[test]
358    fn test_origin_from_u8() {
359        assert_eq!(Origin::from(5), Origin(5));
360    }
361
362    #[test]
363    fn test_u8_from_origin() {
364        assert_eq!(u8::from(Origin(3)), 3);
365    }
366}