#![deny(unused_crate_dependencies)]
use anyhow::{
anyhow,
bail,
Result,
};
pub use contract_metadata::Language;
use parity_wasm::elements::{
External,
FuncBody,
FunctionType,
ImportSection,
Instruction,
Module,
Type,
TypeSection,
ValueType,
};
pub fn determine_language(code: &[u8]) -> Result<Language> {
let wasm_module: Module = parity_wasm::deserialize_buffer(code)?;
let module = wasm_module.clone().parse_names().unwrap_or(wasm_module);
let start_section = module.start_section();
if start_section.is_none() && has_custom_section(&module, "producers") {
return Ok(Language::Solidity)
} else if start_section.is_some() && has_custom_section(&module, "sourceMappingURL") {
return Ok(Language::AssemblyScript)
} else if start_section.is_none()
&& (is_ink_function_present(&module) || has_function_name(&module, "ink_env"))
{
return Ok(Language::Ink)
}
bail!("Language unsupported or unrecognized")
}
fn is_ink_function_present(module: &Module) -> bool {
let import_section = module
.import_section()
.expect("Import setction shall be present");
let ink_func_deny_payment_sig =
Type::Function(FunctionType::new(vec![], vec![ValueType::I32]));
let ink_func_transferred_value_sig =
Type::Function(FunctionType::new(vec![ValueType::I32], vec![]));
let value_transferred_index =
get_function_import_index(import_section, "value_transferred").or(
get_function_import_index(import_section, "seal_value_transferred"),
);
let mut functions: Vec<&FuncBody> = 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) = filter_function_by_type(module, signature) {
functions.append(&mut func);
}
}
if let Ok(index) = value_transferred_index {
functions.iter().any(|&body| {
body.code().elements().iter().any(|instruction| {
matches!(instruction, &Instruction::Call(i) if i as usize == index)
})
})
} else {
false
}
}
fn has_function_name(module: &Module, name: &str) -> bool {
module
.names_section()
.map(|section| {
if let Some(functions) = section.functions() {
functions
.names()
.iter()
.any(|(_, func)| func.contains(name))
} else {
false
}
})
.unwrap_or(false)
}
fn has_custom_section(module: &Module, section_name: &str) -> bool {
module
.custom_sections()
.any(|section| section.name() == section_name)
}
fn get_function_import_index(
import_section: &ImportSection,
field: &str,
) -> Result<usize> {
import_section
.entries()
.iter()
.filter(|&entry| matches!(entry.external(), External::Function(_)))
.position(|e| e.field() == field)
.ok_or(anyhow!("Missing required import for: {}", field))
}
fn get_function_type_index(
type_section: &TypeSection,
function_type: &Type,
) -> Result<usize> {
type_section
.types()
.iter()
.position(|e| e == function_type)
.ok_or(anyhow!("Requested function type not found"))
}
fn filter_function_by_type<'a>(
module: &'a Module,
function_type: &Type,
) -> Result<Vec<&'a FuncBody>> {
let type_section = module
.type_section()
.ok_or(anyhow!("Missing required type section"))?;
let func_type_index = get_function_type_index(type_section, function_type)?;
module
.function_section()
.ok_or(anyhow!("Missing required function section"))?
.entries()
.iter()
.enumerate()
.filter(|(_, elem)| elem.type_ref() == func_type_index as u32)
.map(|(index, _)| {
module
.code_section()
.ok_or(anyhow!("Missing required code section"))?
.bodies()
.get(index)
.ok_or(anyhow!("Requested function not found code section"))
})
.collect::<Result<Vec<_>>>()
}
#[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 = wabt::wat2wasm(contract).expect("invalid wabt");
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 = wabt::wat2wasm(contract).expect("invalid wabt");
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))
)
"#;
let code = wabt::wat2wasm(contract).expect("invalid wabt");
let mut module: Module = parity_wasm::deserialize_buffer(&code).unwrap();
module.set_custom_section("producers".to_string(), Vec::new());
let code = module.into_bytes().unwrap();
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))
)
"#;
let code = wabt::wat2wasm(contract).expect("invalid wabt");
let mut module: Module = parity_wasm::deserialize_buffer(&code).unwrap();
module.set_custom_section("sourceMappingURL".to_string(), Vec::new());
let code = module.into_bytes().unwrap();
let lang = determine_language(&code);
assert!(
matches!(lang, Ok(Language::AssemblyScript)),
"Failed to detect AssemblyScript language"
);
}
}