1#![recursion_limit = "4096"]
20#![cfg_attr(not(feature = "std"), no_std)]
21#![allow(clippy::items_after_test_module)]
22
23extern crate alloc;
24
25pub use crate::{gas_metering::Rules, syscalls::SyscallName};
26pub use module::{
27 BrTable, ConstExpr, Data, Element, ElementItems, Export, Function, Global, Import, Instruction,
28 MemArg, Module, ModuleBuilder, ModuleError, Name, Table, GEAR_SUPPORTED_FEATURES,
29};
30pub use wasmparser::{
31 BlockType, ExternalKind, FuncType, GlobalType, MemoryType, RefType, TableType, TypeRef, ValType,
32};
33
34use crate::stack_limiter::InjectionConfig;
35use alloc::{string::ToString, vec};
36
37mod module;
38#[cfg(test)]
39mod tests;
40
41pub mod gas_metering;
42pub mod stack_limiter;
43pub mod syscalls;
44
45pub const GLOBAL_NAME_GAS: &str = "gear_gas";
48
49pub const STACK_END_EXPORT_NAME: &str = "__gear_stack_end";
52pub const STACK_HEIGHT_EXPORT_NAME: &str = "__gear_stack_height";
56
57#[derive(Debug, Clone, Copy)]
59pub enum SystemBreakCode {
60 OutOfGas = 0,
61 StackLimitExceeded = 1,
62}
63
64#[derive(Clone, Debug, derive_more::Display)]
67#[display("Unsupported system break code")]
68pub struct SystemBreakCodeTryFromError;
69
70impl TryFrom<i32> for SystemBreakCode {
71 type Error = SystemBreakCodeTryFromError;
72
73 fn try_from(value: i32) -> Result<Self, Self::Error> {
74 match value {
75 0 => Ok(Self::OutOfGas),
76 1 => Ok(Self::StackLimitExceeded),
77 _ => Err(SystemBreakCodeTryFromError),
78 }
79 }
80}
81
82impl TryFrom<u32> for SystemBreakCode {
83 type Error = SystemBreakCodeTryFromError;
84
85 fn try_from(value: u32) -> Result<Self, Self::Error> {
86 SystemBreakCode::try_from(value as i32)
87 }
88}
89
90#[derive(Debug, PartialEq, Eq, derive_more::Display)]
92pub enum InstrumentationError {
93 #[display("The WASM module already has `gr_system_break` import")]
95 SystemBreakImportAlreadyExists,
96 #[display("Failed to inject stack height limits")]
98 StackLimitInjection,
99 #[display("The WASM module already has `gear_gas` global")]
101 GasGlobalAlreadyExists,
102 #[display("An overflow occurred while calculating the cost of the `gas_charge` function")]
104 CostCalculationOverflow,
105 #[display("Failed to get instruction cost")]
107 InstructionCostNotFound,
108 #[display(
112 "Failed to inject instructions for gas metrics: may be in case \
113 program contains unsupported instructions (memory grow, etc.)"
114 )]
115 GasInjection,
116}
117
118pub struct InstrumentationBuilder<'a, R, GetRulesFn>
120where
121 R: Rules,
122 GetRulesFn: FnMut(&Module) -> R,
123{
124 module_name: &'a str,
126 stack_limiter: Option<(u32, bool)>,
128 gas_limiter: Option<GetRulesFn>,
130}
131
132impl<'a, R, GetRulesFn> InstrumentationBuilder<'a, R, GetRulesFn>
133where
134 R: Rules,
135 GetRulesFn: FnMut(&Module) -> R,
136{
137 pub fn new(module_name: &'a str) -> Self {
140 Self {
141 module_name,
142 stack_limiter: None,
143 gas_limiter: None,
144 }
145 }
146
147 pub fn with_stack_limiter(&mut self, stack_limit: u32, export_stack_height: bool) -> &mut Self {
149 self.stack_limiter = Some((stack_limit, export_stack_height));
150 self
151 }
152
153 pub fn with_gas_limiter(&mut self, get_gas_rules: GetRulesFn) -> &mut Self {
155 self.gas_limiter = Some(get_gas_rules);
156 self
157 }
158
159 pub fn instrument(&mut self, module: Module) -> Result<Module, InstrumentationError> {
162 if let (None, None) = (self.stack_limiter, &self.gas_limiter) {
163 return Ok(module);
164 }
165
166 let (gr_system_break_index, mut module) =
167 inject_system_break_import(module, self.module_name)?;
168
169 if let Some((stack_limit, export_stack_height)) = self.stack_limiter {
170 let injection_config = InjectionConfig {
171 stack_limit,
172 injection_fn: |_| {
173 [
174 Instruction::I32Const(SystemBreakCode::StackLimitExceeded as i32),
175 Instruction::Call(gr_system_break_index),
176 ]
177 },
178 stack_height_export_name: export_stack_height.then_some(STACK_HEIGHT_EXPORT_NAME),
179 };
180
181 module = stack_limiter::inject_with_config(module, injection_config)
182 .map_err(|_| InstrumentationError::StackLimitInjection)?;
183 }
184
185 if let Some(ref mut get_gas_rules) = self.gas_limiter {
186 let gas_rules = get_gas_rules(&module);
187 module = inject_gas_limiter(module, &gas_rules, gr_system_break_index)?;
188 }
189
190 Ok(module)
191 }
192}
193
194fn inject_system_break_import(
195 module: Module,
196 break_module_name: &str,
197) -> Result<(u32, Module), InstrumentationError> {
198 if module
199 .import_section
200 .as_ref()
201 .map(|section| {
202 section.iter().any(|entry| {
203 entry.module == break_module_name && entry.name == SyscallName::SystemBreak.to_str()
204 })
205 })
206 .unwrap_or(false)
207 {
208 return Err(InstrumentationError::SystemBreakImportAlreadyExists);
209 }
210
211 let inserted_index = module.import_count(|ty| matches!(ty, TypeRef::Func(_))) as u32;
212
213 let mut mbuilder = ModuleBuilder::from_module(module);
214 let import_idx = mbuilder.push_type(FuncType::new([ValType::I32], []));
216
217 mbuilder.push_import(Import::func(
219 break_module_name.to_string(),
220 SyscallName::SystemBreak.to_str(),
221 import_idx,
222 ));
223
224 let module = mbuilder
225 .shift_func_index(inserted_index)
226 .shift_all()
227 .build();
228
229 Ok((inserted_index, module))
230}
231
232fn inject_gas_limiter<R: Rules>(
233 module: Module,
234 rules: &R,
235 gr_system_break_index: u32,
236) -> Result<Module, InstrumentationError> {
237 if module
238 .export_section
239 .as_ref()
240 .map(|section| section.iter().any(|entry| entry.name == GLOBAL_NAME_GAS))
241 .unwrap_or(false)
242 {
243 return Err(InstrumentationError::GasGlobalAlreadyExists);
244 }
245
246 let gas_charge_index = module.functions_space();
247 let gas_index = module.globals_space() as u32;
248
249 let mut mbuilder = ModuleBuilder::from_module(module);
250
251 mbuilder.push_global(Global::i64_value_mut(0));
252 mbuilder.push_export(Export::global(GLOBAL_NAME_GAS, gas_index));
253
254 const GAS_CHARGE_COST_PLACEHOLDER: i64 = 1248163264128;
257
258 let mut elements = vec![
259 Instruction::GlobalGet(gas_index),
261 Instruction::LocalGet(0),
267 Instruction::I64ExtendI32U,
268 Instruction::I64Const(GAS_CHARGE_COST_PLACEHOLDER),
269 Instruction::I64Add,
270 Instruction::LocalTee(1),
271 Instruction::I64LtU,
276 Instruction::If(BlockType::Empty),
277 Instruction::I32Const(SystemBreakCode::OutOfGas as i32),
278 Instruction::Call(gr_system_break_index),
279 Instruction::End,
280 Instruction::GlobalGet(gas_index),
284 Instruction::LocalGet(1),
285 Instruction::I64Sub,
286 Instruction::GlobalSet(gas_index),
287 Instruction::End,
289 ];
290
291 let mut block_of_code = false;
293
294 let cost_blocks = elements
295 .iter()
296 .filter(|instruction| match instruction {
297 Instruction::If { .. } => {
298 block_of_code = true;
299 true
300 }
301 Instruction::End => {
302 block_of_code = false;
303 false
304 }
305 _ => !block_of_code,
306 })
307 .try_fold(0u64, |cost, instruction| {
308 rules
309 .instruction_cost(instruction)
310 .and_then(|c| cost.checked_add(c.into()))
311 })
312 .ok_or(InstrumentationError::CostCalculationOverflow)?;
313
314 let cost_push_arg = rules
315 .instruction_cost(&Instruction::I32Const(0))
316 .map(|c| c as u64)
317 .ok_or(InstrumentationError::InstructionCostNotFound)?;
318
319 let cost_call = rules
320 .instruction_cost(&Instruction::Call(0))
321 .map(|c| c as u64)
322 .ok_or(InstrumentationError::InstructionCostNotFound)?;
323
324 let cost_local_var = rules.call_per_local_cost() as u64;
325
326 let cost = cost_push_arg + cost_call + cost_local_var + cost_blocks;
327
328 if cost > u64::MAX - u64::from(u32::MAX) {
332 return Err(InstrumentationError::CostCalculationOverflow);
333 }
334
335 let cost_instr = elements
337 .iter_mut()
338 .find(|i| **i == Instruction::I64Const(GAS_CHARGE_COST_PLACEHOLDER))
339 .expect("Const for cost of the fn not found");
340 *cost_instr = Instruction::I64Const(cost as i64);
341
342 mbuilder.add_func(
344 FuncType::new([ValType::I32], []),
345 Function {
346 locals: vec![(1, ValType::I64)],
347 instructions: elements,
348 },
349 );
350
351 let module = mbuilder.build();
353
354 gas_metering::post_injection_handler(module, rules, gas_charge_index)
355 .inspect_err(|err| {
356 log::debug!("post_injection_handler failed: {err:?}");
357 })
358 .map_err(|_| InstrumentationError::GasInjection)
359}