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, GEAR_SUPPORTED_FEATURES,
28 Global, Import, Instruction, MemArg, Module, ModuleBuilder, ModuleError, Name, Table,
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";
55
56#[derive(Debug, Clone, Copy)]
58pub enum SystemBreakCode {
59 OutOfGas = 0,
60 StackLimitExceeded = 1,
61}
62
63#[derive(Clone, Debug, derive_more::Display)]
66#[display("Unsupported system break code")]
67pub struct SystemBreakCodeTryFromError;
68
69impl TryFrom<i32> for SystemBreakCode {
70 type Error = SystemBreakCodeTryFromError;
71
72 fn try_from(value: i32) -> Result<Self, Self::Error> {
73 match value {
74 0 => Ok(Self::OutOfGas),
75 1 => Ok(Self::StackLimitExceeded),
76 _ => Err(SystemBreakCodeTryFromError),
77 }
78 }
79}
80
81impl TryFrom<u32> for SystemBreakCode {
82 type Error = SystemBreakCodeTryFromError;
83
84 fn try_from(value: u32) -> Result<Self, Self::Error> {
85 SystemBreakCode::try_from(value as i32)
86 }
87}
88
89#[derive(Debug, PartialEq, Eq, derive_more::Display)]
91pub enum InstrumentationError {
92 #[display("The WASM module already has `gr_system_break` import")]
94 SystemBreakImportAlreadyExists,
95 #[display("Failed to inject stack height limits")]
97 StackLimitInjection,
98 #[display("The WASM module already has `gear_gas` global")]
100 GasGlobalAlreadyExists,
101 #[display("An overflow occurred while calculating the cost of the `gas_charge` function")]
103 CostCalculationOverflow,
104 #[display("Failed to get instruction cost")]
106 InstructionCostNotFound,
107 #[display(
111 "Failed to inject instructions for gas metrics: may be in case \
112 program contains unsupported instructions (memory grow, etc.)"
113 )]
114 GasInjection,
115}
116
117pub struct InstrumentationBuilder<'a, R, GetRulesFn>
119where
120 R: Rules,
121 GetRulesFn: FnMut(&Module) -> R,
122{
123 module_name: &'a str,
125 stack_limiter: Option<(u32, bool)>,
127 gas_limiter: Option<GetRulesFn>,
129}
130
131impl<'a, R, GetRulesFn> InstrumentationBuilder<'a, R, GetRulesFn>
132where
133 R: Rules,
134 GetRulesFn: FnMut(&Module) -> R,
135{
136 pub fn new(module_name: &'a str) -> Self {
139 Self {
140 module_name,
141 stack_limiter: None,
142 gas_limiter: None,
143 }
144 }
145
146 pub fn with_stack_limiter(&mut self, stack_limit: u32, export_stack_height: bool) -> &mut Self {
148 self.stack_limiter = Some((stack_limit, export_stack_height));
149 self
150 }
151
152 pub fn with_gas_limiter(&mut self, get_gas_rules: GetRulesFn) -> &mut Self {
154 self.gas_limiter = Some(get_gas_rules);
155 self
156 }
157
158 pub fn instrument(&mut self, module: Module) -> Result<Module, InstrumentationError> {
161 if let (None, None) = (self.stack_limiter, &self.gas_limiter) {
162 return Ok(module);
163 }
164
165 let (gr_system_break_index, mut module) =
166 inject_system_break_import(module, self.module_name)?;
167
168 if let Some((stack_limit, export_stack_height)) = self.stack_limiter {
169 let injection_config = InjectionConfig {
170 stack_limit,
171 injection_fn: |_| {
172 [
173 Instruction::I32Const(SystemBreakCode::StackLimitExceeded as i32),
174 Instruction::Call(gr_system_break_index),
175 ]
176 },
177 stack_height_export_name: export_stack_height.then_some(STACK_HEIGHT_EXPORT_NAME),
178 };
179
180 module = stack_limiter::inject_with_config(module, injection_config)
181 .map_err(|_| InstrumentationError::StackLimitInjection)?;
182 }
183
184 if let Some(ref mut get_gas_rules) = self.gas_limiter {
185 let gas_rules = get_gas_rules(&module);
186 module = inject_gas_limiter(module, &gas_rules, gr_system_break_index)?;
187 }
188
189 Ok(module)
190 }
191}
192
193fn inject_system_break_import(
194 module: Module,
195 break_module_name: &str,
196) -> Result<(u32, Module), InstrumentationError> {
197 if module
198 .import_section
199 .as_ref()
200 .map(|section| {
201 section.iter().any(|entry| {
202 entry.module == break_module_name && entry.name == SyscallName::SystemBreak.to_str()
203 })
204 })
205 .unwrap_or(false)
206 {
207 return Err(InstrumentationError::SystemBreakImportAlreadyExists);
208 }
209
210 let inserted_index = module.import_count(|ty| matches!(ty, TypeRef::Func(_))) as u32;
211
212 let mut mbuilder = ModuleBuilder::from_module(module);
213 let import_idx = mbuilder.push_type(FuncType::new([ValType::I32], []));
215
216 mbuilder.push_import(Import::func(
218 break_module_name.to_string(),
219 SyscallName::SystemBreak.to_str(),
220 import_idx,
221 ));
222
223 let module = mbuilder
224 .shift_func_index(inserted_index)
225 .shift_all()
226 .build();
227
228 Ok((inserted_index, module))
229}
230
231fn inject_gas_limiter<R: Rules>(
232 module: Module,
233 rules: &R,
234 gr_system_break_index: u32,
235) -> Result<Module, InstrumentationError> {
236 if module
237 .export_section
238 .as_ref()
239 .map(|section| section.iter().any(|entry| entry.name == GLOBAL_NAME_GAS))
240 .unwrap_or(false)
241 {
242 return Err(InstrumentationError::GasGlobalAlreadyExists);
243 }
244
245 let gas_charge_index = module.functions_space();
246 let gas_index = module.globals_space() as u32;
247
248 let mut mbuilder = ModuleBuilder::from_module(module);
249
250 mbuilder.push_global(Global::i64_value_mut(0));
251 mbuilder.push_export(Export::global(GLOBAL_NAME_GAS, gas_index));
252
253 const GAS_CHARGE_COST_PLACEHOLDER: i64 = 1248163264128;
256
257 let mut elements = vec![
258 Instruction::GlobalGet(gas_index),
260 Instruction::LocalGet(0),
266 Instruction::I64ExtendI32U,
267 Instruction::I64Const(GAS_CHARGE_COST_PLACEHOLDER),
268 Instruction::I64Add,
269 Instruction::LocalTee(1),
270 Instruction::I64LtU,
275 Instruction::If(BlockType::Empty),
276 Instruction::I32Const(SystemBreakCode::OutOfGas as i32),
277 Instruction::Call(gr_system_break_index),
278 Instruction::End,
279 Instruction::GlobalGet(gas_index),
283 Instruction::LocalGet(1),
284 Instruction::I64Sub,
285 Instruction::GlobalSet(gas_index),
286 Instruction::End,
288 ];
289
290 let mut block_of_code = false;
292
293 let cost_blocks = elements
294 .iter()
295 .filter(|instruction| match instruction {
296 Instruction::If { .. } => {
297 block_of_code = true;
298 true
299 }
300 Instruction::End => {
301 block_of_code = false;
302 false
303 }
304 _ => !block_of_code,
305 })
306 .try_fold(0u64, |cost, instruction| {
307 rules
308 .instruction_cost(instruction)
309 .and_then(|c| cost.checked_add(c.into()))
310 })
311 .ok_or(InstrumentationError::CostCalculationOverflow)?;
312
313 let cost_push_arg = rules
314 .instruction_cost(&Instruction::I32Const(0))
315 .map(|c| c as u64)
316 .ok_or(InstrumentationError::InstructionCostNotFound)?;
317
318 let cost_call = rules
319 .instruction_cost(&Instruction::Call(0))
320 .map(|c| c as u64)
321 .ok_or(InstrumentationError::InstructionCostNotFound)?;
322
323 let cost_local_var = rules.call_per_local_cost() as u64;
324
325 let cost = cost_push_arg + cost_call + cost_local_var + cost_blocks;
326
327 if cost > u64::MAX - u64::from(u32::MAX) {
331 return Err(InstrumentationError::CostCalculationOverflow);
332 }
333
334 let cost_instr = elements
336 .iter_mut()
337 .find(|i| **i == Instruction::I64Const(GAS_CHARGE_COST_PLACEHOLDER))
338 .expect("Const for cost of the fn not found");
339 *cost_instr = Instruction::I64Const(cost as i64);
340
341 mbuilder.add_func(
343 FuncType::new([ValType::I32], []),
344 Function {
345 locals: vec![(1, ValType::I64)],
346 instructions: elements,
347 },
348 );
349
350 let module = mbuilder.build();
352
353 gas_metering::post_injection_handler(module, rules, gas_charge_index)
354 .inspect_err(|err| {
355 log::debug!("post_injection_handler failed: {err:?}");
356 })
357 .map_err(|_| InstrumentationError::GasInjection)
358}