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