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
17#![deny(unused_crate_dependencies)]
18
19use anyhow::{
20    Result,
21    bail,
22};
23pub use contract_metadata::Language;
24
25/// Detects the programming language of a smart contract from its PolkaVM
26/// binary code.
27///
28/// This function accepts a binary contract as input and employs a set of heuristics
29/// to identify the contract's source language. It currently supports detection of the
30/// ink! and Solidity languages.
31///
32/// # Developer Note
33///
34/// Finding a heuristic to distinguish ink! bytecode vs Solidity bytecode is tricky.
35/// This is because the Rust compiler (ink!) compiles to LLVM IR, which is the
36/// compiled to RISC-V. The Parity `resolc` compiler compiles Yul to LLVM IR,
37/// which is then compiled to RISC-V. So in both cases the IR is already LLVM.
38///
39/// The heuristic that we have found to work is that _all_ Solidity binaries always
40/// have these two imports following each other: `seal_return`, `set_immutable_data`.
41/// This is true, even for read-only contracts that never store anything.
42///
43/// For ink!, we found that _all_ binaries have these two imports right after each
44/// other: `seal_return`, `set_storage`. This is also true for read-only contracts.
45///
46/// Note: It is unclear to us at this moment why both languages compile `set_*`
47/// imports into the binary, even if no mutation operations are in the syntax.
48pub fn determine_language(code: &[u8]) -> Result<Language> {
49    let blob = polkavm_linker::ProgramBlob::parse(code[..].into())
50        .expect("cannot parse code blob");
51    let mut found_seal_return: bool = false;
52
53    for import in blob.imports().iter().flatten() {
54        let import = String::from_utf8_lossy(import.as_bytes());
55        if found_seal_return && import == "set_storage" {
56            return Ok(Language::Ink)
57        } else if found_seal_return && import == "set_immutable_data" {
58            return Ok(Language::Solidity)
59        } else if found_seal_return {
60            bail!("Language unsupported or unrecognized.")
61        }
62        if import == "seal_return" {
63            found_seal_return = true;
64        }
65    }
66    bail!("Language unsupported or unrecognized.")
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn determines_solidity_language() {
75        for file in std::fs::read_dir("contract-binaries/resolc-0.3.0/").unwrap() {
76            let path = file.unwrap().path();
77            let code = std::fs::read(path).unwrap();
78            let lang = determine_language(&code[..]);
79            assert!(
80                matches!(lang, Ok(Language::Solidity)),
81                "Failed to detect Solidity language."
82            );
83        }
84    }
85
86    #[test]
87    fn determines_ink_language() {
88        for file in std::fs::read_dir("contract-binaries/ink-6.0.0-alpha.4/").unwrap() {
89            let path = file.unwrap().path();
90            let code = std::fs::read(path).unwrap();
91            let lang = determine_language(&code[..]);
92            assert!(
93                matches!(lang, Ok(Language::Ink)),
94                "Failed to detect ink! language."
95            );
96        }
97    }
98}