phink_lib/fuzzer/
manager.rs

1#![allow(unused_imports, unused_variables)]
2use crate::{
3    cli::config::Configuration,
4    contract::{
5        remote::{
6            ContractSetup,
7            FullContractResponse,
8        },
9        selectors::{
10            database::SelectorDatabase,
11            selector::Selector,
12        },
13    },
14    cover::coverage::InputCoverage,
15    fuzzer::parser::{
16        Message,
17        OneInput,
18        Origin,
19    },
20    ResultOf,
21};
22use anyhow::{
23    bail,
24    Context,
25};
26use contract_transcode::ContractMessageTranscoder;
27use sp_runtime::{
28    DispatchError,
29    ModuleError,
30};
31use std::{
32    panic,
33    path::Path,
34    sync::{
35        Arc,
36        Mutex,
37    },
38};
39
40#[derive(Clone)]
41pub struct CampaignManager {
42    setup: ContractSetup,
43    database: SelectorDatabase,
44    configuration: Configuration,
45    transcoder: Arc<Mutex<ContractMessageTranscoder>>,
46}
47
48impl CampaignManager {
49    pub fn new(
50        database: SelectorDatabase,
51        setup: ContractSetup,
52        configuration: Configuration,
53    ) -> ResultOf<Self> {
54        let transcoder = Arc::new(Mutex::new(
55            ContractMessageTranscoder::load(Path::new(&setup.path_to_specs))
56                .context("Cannot instantiante the `ContractMessageTranscoder`")?,
57        ));
58
59        Ok(Self {
60            setup,
61            database,
62            configuration,
63            transcoder,
64        })
65    }
66
67    pub fn config(&self) -> Configuration {
68        self.configuration.clone()
69    }
70
71    pub fn database(&self) -> &SelectorDatabase {
72        &self.database
73    }
74
75    pub fn transcoder(&self) -> Arc<Mutex<ContractMessageTranscoder>> {
76        Arc::clone(&self.transcoder)
77    }
78
79    pub fn check_invariants(
80        &self,
81        responses: &[FullContractResponse],
82        decoded_msgs: &OneInput,
83        catch_trapped_contract: bool,
84    ) {
85        let trapped = responses.iter().filter(|response| response.is_trapped());
86
87        // If we are running the seeds or that we want the fuzzer to catch the trapped contract AND
88        // that we have a trapped contract, we panic artificially trigger a bug for AFL
89        if catch_trapped_contract && trapped.clone().next().is_some() {
90            trapped.for_each(|response| {
91                self.display_trap(decoded_msgs, response);
92            });
93            // Artificially trigger a bug for AFL
94            panic!("\n🫡  Job is done! Please, don't mind the backtrace below/above.\n\n");
95        }
96
97        // TODO: We only try to run the invariants as the first message's origin here
98        if let Ok(invariant_tested) = self.are_invariants_failing(decoded_msgs.messages[0].origin) {
99            self.display_invariant(responses.to_vec(), decoded_msgs, invariant_tested);
100            panic!("\n🫡   Job is done! Please, don't mind the backtrace below/above.\n\n"); // Artificially trigger a bug for AFL
101        }
102    }
103
104    pub fn display_trap(&self, message: &OneInput, response: &FullContractResponse) {
105        #[cfg(not(fuzzing))]
106        {
107            println!("\n🤯 A trapped contract got caught! Let's dive into it");
108            println!("\n🐛 IMPORTANT STACKTRACE : {response}\n");
109            println!("🎉 Find below the trace that caused that trapped contract");
110            message.pretty_print(vec![response.clone()]);
111        }
112    }
113
114    #[allow(unused_mut)]
115    pub fn display_invariant(
116        &self,
117        responses: Vec<FullContractResponse>,
118        decoded_msg: &OneInput,
119        mut invariant_tested: Selector,
120    ) {
121        #[cfg(not(fuzzing))]
122        {
123            let hex = self
124                .transcoder()
125                .lock()
126                .unwrap()
127                .decode_contract_message(&mut &*invariant_tested.as_mut())
128                .unwrap();
129
130            println!("\n🤯 An invariant got caught! Let's dive into it");
131            println!("\n🫵  This was caused by `{hex}`\n");
132            println!("🎉 Find below the trace that caused that invariant");
133            decoded_msg.pretty_print(responses);
134        }
135    }
136
137    /// This function aims to call every invariants via `invariant_selectors`.
138    pub fn are_invariants_failing(&self, origin: Origin) -> ResultOf<Selector> {
139        for invariant in &self.database.to_owned().invariants()? {
140            let invariant_call: FullContractResponse = self.to_owned().setup.call(
141                invariant.as_ref(),
142                origin.into(),
143                0,
144                self.configuration.clone(),
145            );
146            if invariant_call.failed() {
147                return Ok(*invariant);
148            }
149        }
150        bail!("All invariants passed")
151    }
152}