1#![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#[derive(Default)]
40pub struct Module<'a> {
41 pub custom_sections: HashMap<&'a str, &'a [u8]>,
43 pub start_section: Option<u32>,
45 pub function_sections: Vec<u32>,
47 pub type_sections: Vec<FuncType>,
49 pub import_sections: Vec<Import<'a>>,
51 pub code_sections: Vec<Vec<Operator<'a>>>,
53}
54
55impl<'a> Module<'a> {
56 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 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 pub fn new(code: &'a [u8]) -> Result<Self> {
118 Self::parse(code)
119 }
120
121 pub fn has_function_name(&self, name: &str) -> Result<bool> {
123 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 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 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 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
187fn is_ink_function_present(module: &Module) -> bool {
189 let ink_func_deny_payment_sig = FuncType::new(vec![], vec![ValType::I32]);
191 let ink_func_transferred_value_sig = FuncType::new(vec![ValType::I32], vec![]);
193
194 let value_transferred_index =
197 module.function_import_index("value_transferred").or(
199 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!(instruction, &Operator::Call{function_index} if function_index as usize == index)
217 })
218 })
219 } else {
220 false
221 }
222}
223
224pub 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}