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}