contract_build/
post_process_wasm.rs1use std::{
18 fs,
19 path::{
20 Path,
21 PathBuf,
22 },
23};
24
25use colored::Colorize;
26use wasm_encoder::{
27 EntityType,
28 ExportSection,
29 ImportSection,
30 RawSection,
31 Section,
32};
33use wasmparser::{
34 ExportSectionReader,
35 ImportSectionReader,
36 Parser,
37 Payload,
38};
39
40use anyhow::{
41 anyhow,
42 Context,
43 Error,
44 Result,
45};
46
47use crate::{
48 validate_wasm,
49 verbose_eprintln,
50 Verbosity,
51};
52
53fn ensure_maximum_memory_pages(
58 imports_reader: &ImportSectionReader,
59 maximum_allowed_pages: u64,
60) -> Result<ImportSection> {
61 let mut memory_found = false;
62 let imports = imports_reader.clone().into_iter().try_fold(
63 ImportSection::new(), |mut imports, entry| {
64 let entry = entry?;
65 let mut entity = EntityType::try_from(
66 entry.ty).map_err(|_| anyhow!("Unsupported type in import section"))?;
67 if let EntityType::Memory(mut mem) = entity {
68 memory_found = true;
69 if let Some(requested_maximum) = mem.maximum {
70 if requested_maximum > maximum_allowed_pages {
72 anyhow::bail!(
73 "The wasm module requires {} pages. The maximum allowed number of pages is {}",
74 requested_maximum, maximum_allowed_pages,
75 );
76 }
77 }
78 else {
79 mem.maximum = Some(maximum_allowed_pages);
80 entity = EntityType::from(mem);
81 }
82 }
83 imports.import(entry.module, entry.name, entity);
84
85 Ok::<_, Error>(imports)
86 })?;
87
88 if !memory_found {
89 anyhow::bail!(
90 "Memory import is not found. Is --import-memory specified in the linker args",
91 );
92 }
93 Ok(imports)
94}
95
96fn strip_custom_sections(name: &str) -> bool {
101 !(name.starts_with("reloc.") || name == "name")
102}
103
104fn strip_export_section(exports_reader: &ExportSectionReader) -> Result<ExportSection> {
109 let filtered_exports = exports_reader.clone().into_iter().try_fold(
110 ExportSection::new(),
111 |mut exports, entry| {
112 let entry = entry.context("Parsing of wasm export section failed")?;
113 if matches!(entry.kind, wasmparser::ExternalKind::Func)
114 && (entry.name == "call" || entry.name == "deploy")
115 {
116 exports.export(entry.name, entry.kind.into(), entry.index);
117 }
118 Ok::<_, Error>(exports)
119 },
120 )?;
121
122 Ok(filtered_exports)
123}
124
125pub fn load_module<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
127 let path = path.as_ref();
128 fs::read(path).context(format!(
129 "Loading of wasm module at '{}' failed",
130 path.display(),
131 ))
132}
133
134pub fn post_process_wasm(
136 optimized_code: &PathBuf,
137 skip_wasm_validation: bool,
138 verbosity: &Verbosity,
139 max_memory_pages: u64,
140) -> Result<()> {
141 let module =
143 load_module(optimized_code).context("Loading of optimized wasm failed")?;
144 let output =
145 post_process_module(&module, skip_wasm_validation, verbosity, max_memory_pages)?;
146 fs::write(optimized_code, output)?;
147 Ok(())
148}
149
150pub fn post_process_module(
152 module: &[u8],
153 skip_wasm_validation: bool,
154 verbosity: &Verbosity,
155 max_memory_pages: u64,
156) -> Result<Vec<u8>> {
157 let mut output = Vec::new();
158 for payload in Parser::new(0).parse_all(module) {
159 let payload = payload?;
160
161 match payload {
162 Payload::Version { encoding, .. } => {
163 output.extend_from_slice(match encoding {
164 wasmparser::Encoding::Component => {
165 anyhow::bail!("Unsupported component section")
166 }
167 wasmparser::Encoding::Module => &wasm_encoder::Module::HEADER,
168 });
169 }
170 Payload::End(_) => break,
171 Payload::CustomSection(ref c) => {
172 if strip_custom_sections(c.name()) {
173 continue
175 }
176 }
177 Payload::ExportSection(ref e) => {
178 let exports = strip_export_section(e)?;
179 exports.append_to(&mut output);
180 continue
181 }
182 Payload::ImportSection(ref i) => {
183 let imports = ensure_maximum_memory_pages(i, max_memory_pages)?;
184 imports.append_to(&mut output);
185 continue
186 }
187 _ => {}
188 }
189 if let Some((id, range)) = payload.as_section() {
191 RawSection {
192 id,
193 data: &module[range],
194 }
195 .append_to(&mut output);
196 }
197 }
198
199 debug_assert!(
200 !output.is_empty(),
201 "resulting wasm size of post processing must be > 0"
202 );
203
204 if !skip_wasm_validation {
205 validate_wasm::validate_import_section(&output)?;
206 } else {
207 verbose_eprintln!(
208 verbosity,
209 " {}",
210 "Skipping wasm validation! Contract code may be invalid."
211 .bright_yellow()
212 .bold()
213 );
214 }
215
216 Ok(output)
217}
218
219#[cfg(test)]
220mod unit_tests {
221 use super::*;
222 use crate::Verbosity;
223 use wasmparser::TypeRef;
224
225 #[test]
226 fn post_process_wasm_exceeded_memory_limit() {
227 let contract = r#"
229 (module
230 (type (;0;) (func (param i32 i32 i32)))
231 (import "seal" "foo" (func (;5;) (type 0)))
232 (import "env" "memory" (memory (;0;) 2 32))
233 (func (;5;) (type 0))
234 )"#;
235 let module = wabt::wat2wasm(contract).expect("Invalid wabt");
236
237 let res = post_process_module(&module, true, &Verbosity::Verbose, 16);
239
240 assert!(res.is_err());
242 assert_eq!(
243 res.err().unwrap().to_string(),
244 "The wasm module requires 32 pages. The maximum allowed number of pages is 16");
245 }
246
247 #[test]
248 fn post_process_wasm_missing_memory_limit() {
249 let contract = r#"
251 (module
252 (type (;0;) (func (param i32 i32 i32)))
253 (import "seal" "foo" (func (;0;) (type 0)))
254 (import "env" "memory" (memory (;0;) 2))
255 (func (;1;) (type 0))
256 )"#;
257 let module = wabt::wat2wasm(contract).expect("Invalid wabt");
258
259 let output = post_process_module(&module, true, &Verbosity::Verbose, 16)
261 .expect("Invalid wasm module");
262
263 let maximum = Parser::new(0).parse_all(&output).find_map(|p| {
265 if let Payload::ImportSection(section) = p.unwrap() {
266 section.into_iter().find_map(|e| {
267 if let TypeRef::Memory(mem) = e.unwrap().ty {
268 mem.maximum
269 } else {
270 None
271 }
272 })
273 } else {
274 None
275 }
276 });
277 assert_eq!(maximum, Some(16));
278 }
279
280 #[test]
281 fn post_process_wasm_missing_memory_import() {
282 let contract = r#"
284 (module
285 (type (;0;) (func (param i32 i32 i32)))
286 (import "seal" "foo" (func (;0;) (type 0)))
287 (func (;1;) (type 0))
288 )"#;
289 let module = wabt::wat2wasm(contract).expect("Invalid wabt");
290
291 let res = post_process_module(&module, true, &Verbosity::Verbose, 16);
293
294 assert!(res.is_err());
296 assert_eq!(
297 res.err().unwrap().to_string(),
298 "Memory import is not found. Is --import-memory specified in the linker args"
299 );
300 }
301
302 #[test]
303 fn post_process_wasm_strip_export_section() {
304 let contract = r#"
306 (module
307 (type (;0;) (func (param i32 i32 i32)))
308 (import "seal" "foo" (func (;0;) (type 0)))
309 (import "env" "memory" (memory (;0;) 2))
310 (func (;1;) (type 0))
311 (export "call" (func 1))
312 (export "foo" (func 1))
313 (export "deploy" (func 1))
314 (export "goo" (func 1))
315 )"#;
316 let module = wabt::wat2wasm(contract).expect("Invalid wabt");
317
318 let output = post_process_module(&module, true, &Verbosity::Verbose, 1)
320 .expect("Invalid wasm module");
321
322 let exports_count = Parser::new(0).parse_all(&output).find_map(|p| {
324 if let Payload::ExportSection(section) = p.unwrap() {
325 Some(section.into_iter().count())
326 } else {
327 None
328 }
329 });
330 assert_eq!(exports_count, Some(2));
331 }
332
333 #[test]
334 fn post_process_wasm_untouched() {
335 let contract = r#"
337 (module
338 (type (;0;) (func (param i32 i32 i32)))
339 (type (;1;) (func (param i32 i32) (result i32)))
340 (type (;2;) (func (param i32 i32)))
341 (import "seal" "foo" (func (;0;) (type 0)))
342 (import "env" "memory" (memory (;0;) 2 16))
343 (func (;1;) (type 0))
344 (func (;2;) (type 2))
345 (func (;3;) (type 0))
346 (export "call" (func 1))
347 (export "deploy" (func 1))
348 (global (;0;) (mut i32) (i32.const 65536))
349 (global (;1;) i32 (i32.const 84291))
350 (global (;2;) i32 (i32.const 84304))
351 (data (;0;) (i32.const 65536) "test")
352 )"#;
353 let module = wabt::wat2wasm(contract).expect("Invalid wabt");
354
355 let output = post_process_module(&module, false, &Verbosity::Verbose, 16)
357 .expect("Invalid wasm module");
358
359 assert_eq!(module, output);
361 }
362}