contract_analyze/
lib.rs

1// Copyright (C) Use Ink (UK) Ltd.
2// This file is part of cargo-contract.
3//
4// cargo-contract is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// cargo-contract is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with cargo-contract.  If not, see <http://www.gnu.org/licenses/>.
16#![deny(unused_crate_dependencies)]
17
18use anyhow::{
19    anyhow,
20    bail,
21    Result,
22};
23pub use contract_metadata::Language;
24use std::collections::HashMap;
25use wasmparser::{
26    BinaryReader,
27    FuncType,
28    Import,
29    Name,
30    NameSectionReader,
31    Operator,
32    Parser,
33    Payload,
34    TypeRef,
35    ValType,
36};
37
38/// WebAssembly module
39#[derive(Default)]
40pub struct Module<'a> {
41    /// Map the custom section name to its data.
42    pub custom_sections: HashMap<&'a str, &'a [u8]>,
43    /// Start section function.
44    pub start_section: Option<u32>,
45    /// Map the function index to the type index.
46    pub function_sections: Vec<u32>,
47    /// Type sections containing functions only.
48    pub type_sections: Vec<FuncType>,
49    /// Import sections.
50    pub import_sections: Vec<Import<'a>>,
51    /// Code sections containing instructions only.
52    pub code_sections: Vec<Vec<Operator<'a>>>,
53}
54
55impl<'a> Module<'a> {
56    /// Parse the Wasm module.
57    fn parse(code: &'a [u8]) -> Result<Self> {
58        let mut module: Module<'a> = Default::default();
59        for payload in Parser::new(0).parse_all(code) {
60            let payload = payload?;
61
62            match payload {
63                Payload::Version {
64                    num: _,
65                    encoding: wasmparser::Encoding::Component,
66                    range: _,
67                } => {
68                    anyhow::bail!("Unsupported component section.")
69                }
70                Payload::End(_) => break,
71                Payload::CustomSection(ref c) => {
72                    module.custom_sections.insert(c.name(), c.data());
73                }
74                Payload::StartSection { func, range: _ } => {
75                    module.start_section = Some(func);
76                }
77                Payload::CodeSectionStart {
78                    count: _,
79                    range,
80                    size: _,
81                } => {
82                    let binary_reader = BinaryReader::new(&code[range], 0);
83                    let reader = wasmparser::CodeSectionReader::new(binary_reader)?;
84                    for body in reader {
85                        let body = body?;
86                        let reader = body.get_operators_reader();
87                        let operators = reader?;
88                        let ops = operators
89                            .into_iter()
90                            .collect::<std::result::Result<Vec<_>, _>>()?;
91                        module.code_sections.push(ops);
92                    }
93                }
94                Payload::ImportSection(reader) => {
95                    for ty in reader {
96                        module.import_sections.push(ty?);
97                    }
98                }
99                Payload::TypeSection(reader) => {
100                    // Save function types
101                    for ty in reader.into_iter_err_on_gc_types() {
102                        module.type_sections.push(ty?);
103                    }
104                }
105                Payload::FunctionSection(reader) => {
106                    for ty in reader {
107                        module.function_sections.push(ty?);
108                    }
109                }
110                _ => {}
111            }
112        }
113        Ok(module)
114    }
115
116    /// Create a Module from the Wasm code.
117    pub fn new(code: &'a [u8]) -> Result<Self> {
118        Self::parse(code)
119    }
120
121    /// Check if the function name is present in the 'name' custom section.
122    pub fn has_function_name(&self, name: &str) -> Result<bool> {
123        // The contract compiled in debug mode includes function names in the name
124        // section.
125        let name_section = self
126            .custom_sections
127            .get("name")
128            .ok_or(anyhow!("Custom section 'name' not found."))?;
129        let binary_reader = BinaryReader::new(name_section, 0);
130        let reader = NameSectionReader::new(binary_reader);
131        for section in reader {
132            if let Name::Function(name_reader) = section? {
133                for naming in name_reader {
134                    let naming = naming?;
135                    if naming.name.contains(name) {
136                        return Ok(true)
137                    }
138                }
139            }
140        }
141        Ok(false)
142    }
143
144    /// Get the function's type index from the type section.
145    pub fn function_type_index(&self, function: &FuncType) -> Option<usize> {
146        self.type_sections.iter().enumerate().find_map(|(i, ty)| {
147            if ty == function {
148                return Some(i)
149            }
150            None
151        })
152    }
153
154    /// Get the function index from the import section.
155    pub fn function_import_index(&self, name: &str) -> Option<usize> {
156        self.import_sections
157            .iter()
158            .filter(|&entry| matches!(entry.ty, TypeRef::Func(_)))
159            .position(|e| e.name == name)
160    }
161
162    /// Search for a functions in a WebAssembly (Wasm) module that matches a given
163    /// function type.
164    ///
165    /// If one or more functions matching the specified type are found, this function
166    /// returns their bodies in a vector; otherwise, it returns an error.
167    pub fn functions_by_type(
168        &self,
169        function_type: &FuncType,
170    ) -> Result<Vec<Vec<Operator>>> {
171        self.function_sections
172            .iter()
173            .enumerate()
174            .filter(|(_, &elem)| {
175                Some(elem as usize) == self.function_type_index(function_type)
176            })
177            .map(|(index, _)| {
178                self.code_sections
179                    .get(index)
180                    .ok_or(anyhow!("Requested function not found in code section."))
181                    .cloned()
182            })
183            .collect::<Result<Vec<_>>>()
184    }
185}
186
187/// Checks if a ink! function is present.
188fn is_ink_function_present(module: &Module) -> bool {
189    // Signature for 'deny_payment' ink! function.
190    let ink_func_deny_payment_sig = FuncType::new(vec![], vec![ValType::I32]);
191    // Signature for 'transferred_value' ink! function.
192    let ink_func_transferred_value_sig = FuncType::new(vec![ValType::I32], vec![]);
193
194    // The deny_payment and transferred_value functions internally call the
195    // value_transferred function. Getting its index from import section.
196    let value_transferred_index =
197        // For ink! >=4
198        module.function_import_index("value_transferred").or(
199            // For ink! ^3
200            module.function_import_index("seal_value_transferred"),
201        );
202
203    let mut functions: Vec<Vec<Operator>> = Vec::new();
204    let function_signatures =
205        vec![&ink_func_deny_payment_sig, &ink_func_transferred_value_sig];
206
207    for signature in function_signatures {
208        if let Ok(mut func) = module.functions_by_type(signature) {
209            functions.append(&mut func);
210        }
211    }
212    if let Some(index) = value_transferred_index {
213        functions.iter().any(|body| {
214        body.iter().any(|instruction| {
215            // Matches the 'value_transferred' function.
216            matches!(instruction, &Operator::Call{function_index} if function_index as usize == index)
217        })
218    })
219    } else {
220        false
221    }
222}
223
224/// Detects the programming language of a smart contract from its WebAssembly (Wasm)
225/// binary code.
226///
227/// This function accepts a Wasm code as input and employs a set of heuristics to identify
228/// the contract's source language. It currently supports detection for Ink!, Solidity,
229/// and AssemblyScript languages.
230pub fn determine_language(code: &[u8]) -> Result<Language> {
231    let module = Module::new(code)?;
232    let start_section = module.start_section.is_some();
233
234    if !start_section && module.custom_sections.keys().any(|e| e == &"producers") {
235        return Ok(Language::Solidity)
236    } else if start_section
237        && module
238            .custom_sections
239            .keys()
240            .any(|e| e == &"sourceMappingURL")
241    {
242        return Ok(Language::AssemblyScript)
243    } else if !start_section
244        && (is_ink_function_present(&module)
245            || matches!(module.has_function_name("ink_env"), Ok(true)))
246    {
247        return Ok(Language::Ink)
248    }
249
250    bail!("Language unsupported or unrecognized.")
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    #[test]
258    fn failes_with_unsupported_language() {
259        let contract = r#"
260        (module
261            (type $none_=>_none (func))
262            (type (;0;) (func (param i32 i32 i32)))
263            (import "env" "memory" (func (;5;) (type 0)))
264            (start $~start)
265            (func $~start (type $none_=>_none))
266            (func (;5;) (type 0))
267        )
268        "#;
269        let code = &wat::parse_str(contract).expect("Invalid wat.");
270        let lang = determine_language(code);
271        assert!(lang.is_err());
272        assert_eq!(
273            lang.unwrap_err().to_string(),
274            "Language unsupported or unrecognized."
275        );
276    }
277
278    #[test]
279    fn determines_ink_language() {
280        let contract = r#"
281        (module
282            (type (;0;) (func (param i32 i32 i32)))
283            (type (;1;) (func (result i32)))
284            (type (;2;) (func (param i32 i32)))
285            (import "seal" "foo" (func (;0;) (type 0)))
286            (import "seal0" "value_transferred" (func (;1;) (type 2)))
287            (import "env" "memory" (memory (;0;) 2 16))
288            (func (;2;) (type 2))
289            (func (;3;) (type 1) (result i32)
290            (local i32 i64 i64)
291            global.get 0
292            i32.const 32
293            i32.sub
294            local.tee 0
295            global.set 0
296            local.get 0
297            i64.const 0
298            i64.store offset=8
299            local.get 0
300            i64.const 0
301            i64.store
302            local.get 0
303            i32.const 16
304            i32.store offset=28
305            local.get 0
306            local.get 0
307            i32.const 28
308            i32.add
309            call 1
310            local.get 0
311            i64.load offset=8
312            local.set 1
313            local.get 0
314            i64.load
315            local.set 2
316            local.get 0
317            i32.const 32
318            i32.add
319            global.set 0
320            i32.const 5
321            i32.const 4
322            local.get 1
323            local.get 2
324            i64.or
325            i64.eqz
326            select
327        )
328            (global (;0;) (mut i32) (i32.const 65536))
329        )"#;
330        let code = &wat::parse_str(contract).expect("Invalid wat.");
331        let lang = determine_language(code);
332        assert!(
333            matches!(lang, Ok(Language::Ink)),
334            "Failed to detect Ink! language."
335        );
336    }
337
338    #[test]
339    fn determines_solidity_language() {
340        let contract = r#"
341        (module
342            (type (;0;) (func (param i32 i32 i32)))
343            (import "env" "memory" (memory (;0;) 16 16))
344            (func (;0;) (type 0))
345            (@custom "producers" "data")
346        )
347        "#;
348        let code = &wat::parse_str(contract).expect("Invalid wat.");
349        let lang = determine_language(code);
350        assert!(
351            matches!(lang, Ok(Language::Solidity)),
352            "Failed to detect Solidity language."
353        );
354    }
355
356    #[test]
357    fn determines_assembly_script_language() {
358        let contract = r#"
359        (module
360            (type $none_=>_none (func))
361            (type (;0;) (func (param i32 i32 i32)))
362            (import "seal" "foo" (func (;0;) (type 0)))
363            (import "env" "memory" (memory $0 2 16))
364            (start $~start)
365            (func $~start (type $none_=>_none))
366            (func (;1;) (type 0))
367            (@custom "sourceMappingURL" "data")
368        )
369        "#;
370        let code = &wat::parse_str(contract).expect("Invalid wat.");
371        let lang = determine_language(code);
372        assert!(
373            matches!(lang, Ok(Language::AssemblyScript)),
374            "Failed to detect AssemblyScript language."
375        );
376    }
377}