revive_solc_json_interface/standard_json/output/
source.rs

1//! The `solc --standard-json` output source.
2
3use serde::Deserialize;
4use serde::Serialize;
5
6use crate::standard_json::output::error::Error as SolcStandardJsonOutputError;
7#[cfg(feature = "resolc")]
8use crate::warning::Warning;
9
10/// The `solc --standard-json` output source.
11#[derive(Debug, Serialize, Deserialize, Clone)]
12#[serde(rename_all = "camelCase")]
13pub struct Source {
14    /// The source code ID.
15    pub id: usize,
16    /// The source code AST.
17    pub ast: Option<serde_json::Value>,
18}
19
20impl Source {
21    /// Checks the AST node for the `ecrecover` function usage.
22    pub fn check_ecrecover(ast: &serde_json::Value) -> Option<SolcStandardJsonOutputError> {
23        let ast = ast.as_object()?;
24
25        if ast.get("nodeType")?.as_str()? != "FunctionCall" {
26            return None;
27        }
28
29        let expression = ast.get("expression")?.as_object()?;
30        if expression.get("nodeType")?.as_str()? != "Identifier" {
31            return None;
32        }
33        if expression.get("name")?.as_str()? != "ecrecover" {
34            return None;
35        }
36
37        Some(SolcStandardJsonOutputError::message_ecrecover(
38            ast.get("src")?.as_str(),
39        ))
40    }
41
42    /// Checks the AST node for the `<address payable>`'s `send` and `transfer` methods usage.
43    pub fn check_send_and_transfer(ast: &serde_json::Value) -> Option<SolcStandardJsonOutputError> {
44        let ast = ast.as_object()?;
45
46        if ast.get("nodeType")?.as_str()? != "FunctionCall" {
47            return None;
48        }
49
50        let expression = ast.get("expression")?.as_object()?;
51        if expression.get("nodeType")?.as_str()? != "MemberAccess" {
52            return None;
53        }
54        let member_name = expression.get("memberName")?.as_str()?;
55        if member_name != "send" && member_name != "transfer" {
56            return None;
57        }
58
59        Some(SolcStandardJsonOutputError::message_send_and_transfer(
60            ast.get("src")?.as_str(),
61        ))
62    }
63
64    /// Checks the AST node for the `extcodesize` assembly instruction usage.
65    pub fn check_assembly_extcodesize(
66        ast: &serde_json::Value,
67    ) -> Option<SolcStandardJsonOutputError> {
68        let ast = ast.as_object()?;
69
70        if ast.get("nodeType")?.as_str()? != "YulFunctionCall" {
71            return None;
72        }
73        if ast
74            .get("functionName")?
75            .as_object()?
76            .get("name")?
77            .as_str()?
78            != "extcodesize"
79        {
80            return None;
81        }
82
83        Some(SolcStandardJsonOutputError::message_extcodesize(
84            ast.get("src")?.as_str(),
85        ))
86    }
87
88    /// Checks the AST node for the `origin` assembly instruction usage.
89    pub fn check_assembly_origin(ast: &serde_json::Value) -> Option<SolcStandardJsonOutputError> {
90        let ast = ast.as_object()?;
91
92        if ast.get("nodeType")?.as_str()? != "YulFunctionCall" {
93            return None;
94        }
95        if ast
96            .get("functionName")?
97            .as_object()?
98            .get("name")?
99            .as_str()?
100            != "origin"
101        {
102            return None;
103        }
104
105        Some(SolcStandardJsonOutputError::message_tx_origin(
106            ast.get("src")?.as_str(),
107        ))
108    }
109
110    /// Checks the AST node for the `tx.origin` value usage.
111    pub fn check_tx_origin(ast: &serde_json::Value) -> Option<SolcStandardJsonOutputError> {
112        let ast = ast.as_object()?;
113
114        if ast.get("nodeType")?.as_str()? != "MemberAccess" {
115            return None;
116        }
117        if ast.get("memberName")?.as_str()? != "origin" {
118            return None;
119        }
120
121        let expression = ast.get("expression")?.as_object()?;
122        if expression.get("nodeType")?.as_str()? != "Identifier" {
123            return None;
124        }
125        if expression.get("name")?.as_str()? != "tx" {
126            return None;
127        }
128
129        Some(SolcStandardJsonOutputError::message_tx_origin(
130            ast.get("src")?.as_str(),
131        ))
132    }
133
134    /// Returns the list of messages for some specific parts of the AST.
135    #[cfg(feature = "resolc")]
136    pub fn get_messages(
137        ast: &serde_json::Value,
138        suppressed_warnings: &[Warning],
139    ) -> Vec<SolcStandardJsonOutputError> {
140        let mut messages = Vec::new();
141        if !suppressed_warnings.contains(&Warning::EcRecover) {
142            if let Some(message) = Self::check_ecrecover(ast) {
143                messages.push(message);
144            }
145        }
146        if !suppressed_warnings.contains(&Warning::SendTransfer) {
147            if let Some(message) = Self::check_send_and_transfer(ast) {
148                messages.push(message);
149            }
150        }
151        if !suppressed_warnings.contains(&Warning::ExtCodeSize) {
152            if let Some(message) = Self::check_assembly_extcodesize(ast) {
153                messages.push(message);
154            }
155        }
156        if !suppressed_warnings.contains(&Warning::TxOrigin) {
157            if let Some(message) = Self::check_assembly_origin(ast) {
158                messages.push(message);
159            }
160            if let Some(message) = Self::check_tx_origin(ast) {
161                messages.push(message);
162            }
163        }
164
165        match ast {
166            serde_json::Value::Array(array) => {
167                for element in array.iter() {
168                    messages.extend(Self::get_messages(element, suppressed_warnings));
169                }
170            }
171            serde_json::Value::Object(object) => {
172                for (_key, value) in object.iter() {
173                    messages.extend(Self::get_messages(value, suppressed_warnings));
174                }
175            }
176            _ => {}
177        }
178
179        messages
180    }
181
182    /// Returns the name of the last contract.
183    pub fn last_contract_name(&self) -> anyhow::Result<String> {
184        self.ast
185            .as_ref()
186            .ok_or_else(|| anyhow::anyhow!("The AST is empty"))?
187            .get("nodes")
188            .and_then(|value| value.as_array())
189            .ok_or_else(|| {
190                anyhow::anyhow!("The last contract cannot be found in an empty list of nodes")
191            })?
192            .iter()
193            .filter_map(
194                |node| match node.get("nodeType").and_then(|node| node.as_str()) {
195                    Some("ContractDefinition") => Some(node.get("name")?.as_str()?.to_owned()),
196                    _ => None,
197                },
198            )
199            .next_back()
200            .ok_or_else(|| anyhow::anyhow!("The last contract not found in the AST"))
201    }
202}