#![deny(unused_crate_dependencies)]
use anyhow::{
anyhow,
bail,
Result,
};
pub use contract_metadata::Language;
use std::collections::HashMap;
use wasmparser::{
BinaryReader,
FuncType,
Import,
Name,
NameSectionReader,
Operator,
Parser,
Payload,
TypeRef,
ValType,
};
#[derive(Default)]
pub struct Module<'a> {
pub custom_sections: HashMap<&'a str, &'a [u8]>,
pub start_section: Option<u32>,
pub function_sections: Vec<u32>,
pub type_sections: Vec<FuncType>,
pub import_sections: Vec<Import<'a>>,
pub code_sections: Vec<Vec<Operator<'a>>>,
}
impl<'a> Module<'a> {
fn parse(code: &'a [u8]) -> Result<Self> {
let mut module: Module<'a> = Default::default();
for payload in Parser::new(0).parse_all(code) {
let payload = payload?;
match payload {
Payload::Version {
num: _,
encoding: wasmparser::Encoding::Component,
range: _,
} => {
anyhow::bail!("Unsupported component section.")
}
Payload::End(_) => break,
Payload::CustomSection(ref c) => {
module.custom_sections.insert(c.name(), c.data());
}
Payload::StartSection { func, range: _ } => {
module.start_section = Some(func);
}
Payload::CodeSectionStart {
count: _,
range,
size: _,
} => {
let binary_reader = BinaryReader::new(&code[range], 0);
let reader = wasmparser::CodeSectionReader::new(binary_reader)?;
for body in reader {
let body = body?;
let reader = body.get_operators_reader();
let operators = reader?;
let ops = operators
.into_iter()
.collect::<std::result::Result<Vec<_>, _>>()?;
module.code_sections.push(ops);
}
}
Payload::ImportSection(reader) => {
for ty in reader {
module.import_sections.push(ty?);
}
}
Payload::TypeSection(reader) => {
for ty in reader.into_iter_err_on_gc_types() {
module.type_sections.push(ty?);
}
}
Payload::FunctionSection(reader) => {
for ty in reader {
module.function_sections.push(ty?);
}
}
_ => {}
}
}
Ok(module)
}
pub fn new(code: &'a [u8]) -> Result<Self> {
Self::parse(code)
}
pub fn has_function_name(&self, name: &str) -> Result<bool> {
let name_section = self
.custom_sections
.get("name")
.ok_or(anyhow!("Custom section 'name' not found."))?;
let binary_reader = BinaryReader::new(name_section, 0);
let reader = NameSectionReader::new(binary_reader);
for section in reader {
if let Name::Function(name_reader) = section? {
for naming in name_reader {
let naming = naming?;
if naming.name.contains(name) {
return Ok(true)
}
}
}
}
Ok(false)
}
pub fn function_type_index(&self, function: &FuncType) -> Option<usize> {
self.type_sections.iter().enumerate().find_map(|(i, ty)| {
if ty == function {
return Some(i)
}
None
})
}
pub fn function_import_index(&self, name: &str) -> Option<usize> {
self.import_sections
.iter()
.filter(|&entry| matches!(entry.ty, TypeRef::Func(_)))
.position(|e| e.name == name)
}
pub fn functions_by_type(
&self,
function_type: &FuncType,
) -> Result<Vec<Vec<Operator>>> {
self.function_sections
.iter()
.enumerate()
.filter(|(_, &elem)| {
Some(elem as usize) == self.function_type_index(function_type)
})
.map(|(index, _)| {
self.code_sections
.get(index)
.ok_or(anyhow!("Requested function not found in code section."))
.cloned()
})
.collect::<Result<Vec<_>>>()
}
}
fn is_ink_function_present(module: &Module) -> bool {
let ink_func_deny_payment_sig = FuncType::new(vec![], vec![ValType::I32]);
let ink_func_transferred_value_sig = FuncType::new(vec![ValType::I32], vec![]);
let value_transferred_index =
module.function_import_index("value_transferred").or(
module.function_import_index("seal_value_transferred"),
);
let mut functions: Vec<Vec<Operator>> = Vec::new();
let function_signatures =
vec![&ink_func_deny_payment_sig, &ink_func_transferred_value_sig];
for signature in function_signatures {
if let Ok(mut func) = module.functions_by_type(signature) {
functions.append(&mut func);
}
}
if let Some(index) = value_transferred_index {
functions.iter().any(|body| {
body.iter().any(|instruction| {
matches!(instruction, &Operator::Call{function_index} if function_index as usize == index)
})
})
} else {
false
}
}
pub fn determine_language(code: &[u8]) -> Result<Language> {
let module = Module::new(code)?;
let start_section = module.start_section.is_some();
if !start_section && module.custom_sections.keys().any(|e| e == &"producers") {
return Ok(Language::Solidity)
} else if start_section
&& module
.custom_sections
.keys()
.any(|e| e == &"sourceMappingURL")
{
return Ok(Language::AssemblyScript)
} else if !start_section
&& (is_ink_function_present(&module)
|| matches!(module.has_function_name("ink_env"), Ok(true)))
{
return Ok(Language::Ink)
}
bail!("Language unsupported or unrecognized.")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn failes_with_unsupported_language() {
let contract = r#"
(module
(type $none_=>_none (func))
(type (;0;) (func (param i32 i32 i32)))
(import "env" "memory" (func (;5;) (type 0)))
(start $~start)
(func $~start (type $none_=>_none))
(func (;5;) (type 0))
)
"#;
let code = &wat::parse_str(contract).expect("Invalid wat.");
let lang = determine_language(code);
assert!(lang.is_err());
assert_eq!(
lang.unwrap_err().to_string(),
"Language unsupported or unrecognized."
);
}
#[test]
fn determines_ink_language() {
let contract = r#"
(module
(type (;0;) (func (param i32 i32 i32)))
(type (;1;) (func (result i32)))
(type (;2;) (func (param i32 i32)))
(import "seal" "foo" (func (;0;) (type 0)))
(import "seal0" "value_transferred" (func (;1;) (type 2)))
(import "env" "memory" (memory (;0;) 2 16))
(func (;2;) (type 2))
(func (;3;) (type 1) (result i32)
(local i32 i64 i64)
global.get 0
i32.const 32
i32.sub
local.tee 0
global.set 0
local.get 0
i64.const 0
i64.store offset=8
local.get 0
i64.const 0
i64.store
local.get 0
i32.const 16
i32.store offset=28
local.get 0
local.get 0
i32.const 28
i32.add
call 1
local.get 0
i64.load offset=8
local.set 1
local.get 0
i64.load
local.set 2
local.get 0
i32.const 32
i32.add
global.set 0
i32.const 5
i32.const 4
local.get 1
local.get 2
i64.or
i64.eqz
select
)
(global (;0;) (mut i32) (i32.const 65536))
)"#;
let code = &wat::parse_str(contract).expect("Invalid wat.");
let lang = determine_language(code);
assert!(
matches!(lang, Ok(Language::Ink)),
"Failed to detect Ink! language."
);
}
#[test]
fn determines_solidity_language() {
let contract = r#"
(module
(type (;0;) (func (param i32 i32 i32)))
(import "env" "memory" (memory (;0;) 16 16))
(func (;0;) (type 0))
(@custom "producers" "data")
)
"#;
let code = &wat::parse_str(contract).expect("Invalid wat.");
let lang = determine_language(code);
assert!(
matches!(lang, Ok(Language::Solidity)),
"Failed to detect Solidity language."
);
}
#[test]
fn determines_assembly_script_language() {
let contract = r#"
(module
(type $none_=>_none (func))
(type (;0;) (func (param i32 i32 i32)))
(import "seal" "foo" (func (;0;) (type 0)))
(import "env" "memory" (memory $0 2 16))
(start $~start)
(func $~start (type $none_=>_none))
(func (;1;) (type 0))
(@custom "sourceMappingURL" "data")
)
"#;
let code = &wat::parse_str(contract).expect("Invalid wat.");
let lang = determine_language(code);
assert!(
matches!(lang, Ok(Language::AssemblyScript)),
"Failed to detect AssemblyScript language."
);
}
}