1#![cfg_attr(not(feature = "std"), no_std)]
20#![allow(clippy::items_after_test_module)]
21
22extern crate alloc;
23
24use alloc::vec;
25use gwasm_instrument::{
26 parity_wasm::{
27 builder,
28 elements::{
29 self, BlockType, ImportCountType, Instruction, Instructions, Local, Module, ValueType,
30 },
31 },
32 InjectionConfig,
33};
34
35pub use crate::{gas_metering::Rules, syscalls::SyscallName};
36pub use gwasm_instrument::{self as wasm_instrument, gas_metering, parity_wasm, utils};
37
38#[cfg(test)]
39mod tests;
40
41pub mod syscalls;
42
43pub const GLOBAL_NAME_GAS: &str = "gear_gas";
46
47pub const STACK_END_EXPORT_NAME: &str = "__gear_stack_end";
50pub const STACK_HEIGHT_EXPORT_NAME: &str = "__gear_stack_height";
54
55#[derive(Debug, Clone, Copy)]
57pub enum SystemBreakCode {
58 OutOfGas = 0,
59 StackLimitExceeded = 1,
60}
61
62#[derive(Clone, Debug, derive_more::Display)]
65#[display(fmt = "Unsupported system break code")]
66pub struct SystemBreakCodeTryFromError;
67
68impl TryFrom<i32> for SystemBreakCode {
69 type Error = SystemBreakCodeTryFromError;
70
71 fn try_from(value: i32) -> Result<Self, Self::Error> {
72 match value {
73 0 => Ok(Self::OutOfGas),
74 1 => Ok(Self::StackLimitExceeded),
75 _ => Err(SystemBreakCodeTryFromError),
76 }
77 }
78}
79
80impl TryFrom<u32> for SystemBreakCode {
81 type Error = SystemBreakCodeTryFromError;
82
83 fn try_from(value: u32) -> Result<Self, Self::Error> {
84 SystemBreakCode::try_from(value as i32)
85 }
86}
87
88#[derive(Debug, PartialEq, Eq, derive_more::Display)]
90pub enum InstrumentationError {
91 #[display(fmt = "The WASM module already has `gr_system_break` import")]
93 SystemBreakImportAlreadyExists,
94 #[display(fmt = "Failed to inject stack height limits")]
96 StackLimitInjection,
97 #[display(fmt = "The WASM module already has `gear_gas` global")]
99 GasGlobalAlreadyExists,
100 #[display(
102 fmt = "An overflow occurred while calculating the cost of the `gas_charge` function"
103 )]
104 CostCalculationOverflow,
105 #[display(fmt = "Failed to get instruction cost")]
107 InstructionCostNotFound,
108 #[display(fmt = "Failed to inject instructions for gas metrics: may be in case \
113 program contains unsupported instructions (floats, memory grow, etc.)")]
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 = wasm_instrument::inject_stack_limiter_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: elements::Module,
195 break_module_name: &str,
196) -> Result<(u32, elements::Module), InstrumentationError> {
197 if module
198 .import_section()
199 .map(|section| {
200 section.entries().iter().any(|entry| {
201 entry.module() == break_module_name
202 && entry.field() == SyscallName::SystemBreak.to_str()
203 })
204 })
205 .unwrap_or(false)
206 {
207 return Err(InstrumentationError::SystemBreakImportAlreadyExists);
208 }
209
210 let mut mbuilder = builder::from_module(module);
211
212 let import_sig =
214 mbuilder.push_signature(builder::signature().with_param(ValueType::I32).build_sig());
215
216 let module = mbuilder
218 .import()
219 .module(break_module_name)
220 .field(SyscallName::SystemBreak.to_str())
221 .external()
222 .func(import_sig)
223 .build()
224 .build();
225
226 let import_count = module.import_count(ImportCountType::Function);
227 let inserted_index = import_count as u32 - 1;
228
229 let module = utils::rewrite_sections_after_insertion(module, inserted_index, 1)
230 .expect("Failed to rewrite sections");
231
232 Ok((inserted_index, module))
233}
234
235fn inject_gas_limiter<R: Rules>(
236 module: Module,
237 rules: &R,
238 gr_system_break_index: u32,
239) -> Result<Module, InstrumentationError> {
240 if module
241 .export_section()
242 .map(|section| {
243 section
244 .entries()
245 .iter()
246 .any(|entry| entry.field() == GLOBAL_NAME_GAS)
247 })
248 .unwrap_or(false)
249 {
250 return Err(InstrumentationError::GasGlobalAlreadyExists);
251 }
252
253 let gas_charge_index = module.functions_space();
254 let gas_index = module.globals_space() as u32;
255
256 let mut mbuilder = builder::from_module(module);
257
258 mbuilder.push_global(
259 builder::global()
260 .value_type()
261 .i64()
262 .init_expr(Instruction::I64Const(0))
263 .mutable()
264 .build(),
265 );
266
267 mbuilder.push_export(
268 builder::export()
269 .field(GLOBAL_NAME_GAS)
270 .internal()
271 .global(gas_index)
272 .build(),
273 );
274
275 const GAS_CHARGE_COST_PLACEHOLDER: i64 = 1248163264128;
278
279 let mut elements = vec![
280 Instruction::GetGlobal(gas_index),
282 Instruction::GetLocal(0),
288 Instruction::I64ExtendUI32,
289 Instruction::I64Const(GAS_CHARGE_COST_PLACEHOLDER),
290 Instruction::I64Add,
291 Instruction::TeeLocal(1),
292 Instruction::I64LtU,
297 Instruction::If(BlockType::NoResult),
298 Instruction::I32Const(SystemBreakCode::OutOfGas as i32),
299 Instruction::Call(gr_system_break_index),
300 Instruction::End,
301 Instruction::GetGlobal(gas_index),
305 Instruction::GetLocal(1),
306 Instruction::I64Sub,
307 Instruction::SetGlobal(gas_index),
308 Instruction::End,
310 ];
311
312 let mut block_of_code = false;
314
315 let cost_blocks = elements
316 .iter()
317 .filter(|instruction| match instruction {
318 Instruction::If(_) => {
319 block_of_code = true;
320 true
321 }
322 Instruction::End => {
323 block_of_code = false;
324 false
325 }
326 _ => !block_of_code,
327 })
328 .try_fold(0u64, |cost, instruction| {
329 rules
330 .instruction_cost(instruction)
331 .and_then(|c| cost.checked_add(c.into()))
332 })
333 .ok_or(InstrumentationError::CostCalculationOverflow)?;
334
335 let cost_push_arg = rules
336 .instruction_cost(&Instruction::I32Const(0))
337 .map(|c| c as u64)
338 .ok_or(InstrumentationError::InstructionCostNotFound)?;
339
340 let cost_call = rules
341 .instruction_cost(&Instruction::Call(0))
342 .map(|c| c as u64)
343 .ok_or(InstrumentationError::InstructionCostNotFound)?;
344
345 let cost_local_var = rules.call_per_local_cost() as u64;
346
347 let cost = cost_push_arg + cost_call + cost_local_var + cost_blocks;
348
349 if cost > u64::MAX - u64::from(u32::MAX) {
353 return Err(InstrumentationError::CostCalculationOverflow);
354 }
355
356 let cost_instr = elements
358 .iter_mut()
359 .find(|i| **i == Instruction::I64Const(GAS_CHARGE_COST_PLACEHOLDER))
360 .expect("Const for cost of the fn not found");
361 *cost_instr = Instruction::I64Const(cost as i64);
362
363 mbuilder.push_function(
365 builder::function()
366 .signature()
367 .with_param(ValueType::I32)
368 .build()
369 .body()
370 .with_locals(vec![Local::new(1, ValueType::I64)])
371 .with_instructions(Instructions::new(elements))
372 .build()
373 .build(),
374 );
375
376 let module = mbuilder.build();
378
379 gas_metering::post_injection_handler(module, rules, gas_charge_index)
380 .map_err(|_| InstrumentationError::GasInjection)
381}