1use crate::{
18 common::{ActorExecutionErrorReplyReason, DispatchResult, ExecutableActorData, JournalNote},
19 configs::BlockConfig,
20 context::{
21 ContextChargedForAllocations, ContextChargedForCodeLength, ContextChargedForMemory,
22 ContextChargedForProgram, ContextData, SystemReservationContext,
23 },
24 processing::{process_allowance_exceed, process_execution_error, process_success},
25 ContextChargedForCode, ContextChargedForInstrumentation,
26};
27use alloc::vec::Vec;
28use gear_core::{
29 code::{InstantiatedSectionSizes, SectionName},
30 costs::{BytesAmount, ProcessCosts},
31 gas::{ChargeResult, GasAllowanceCounter, GasCounter},
32 ids::ProgramId,
33 message::{IncomingDispatch, MessageWaitedType},
34};
35
36#[derive(Debug, PartialEq, Eq, derive_more::Display)]
38pub enum PreChargeGasOperation {
39 #[display(fmt = "handle memory static pages")]
41 StaticPages,
42 #[display(fmt = "handle program data")]
44 ProgramData,
45 #[display(fmt = "obtain program code length")]
47 ProgramCodeLen,
48 #[display(fmt = "handle program code")]
50 ProgramCode,
51 #[display(fmt = "instantiate {_0} of Wasm module")]
53 ModuleInstantiation(SectionName),
54 #[display(fmt = "instrument Wasm module")]
56 ModuleInstrumentation,
57 #[display(fmt = "obtain program allocations")]
59 Allocations,
60}
61
62#[derive(Debug, Eq, PartialEq)]
63enum PrechargeError {
64 BlockGasExceeded,
65 GasExceeded(PreChargeGasOperation),
66}
67
68struct GasPrecharger<'a> {
69 counter: &'a mut GasCounter,
70 allowance_counter: &'a mut GasAllowanceCounter,
71 costs: &'a ProcessCosts,
72}
73
74impl<'a> GasPrecharger<'a> {
75 pub fn new(
76 counter: &'a mut GasCounter,
77 allowance_counter: &'a mut GasAllowanceCounter,
78 costs: &'a ProcessCosts,
79 ) -> Self {
80 Self {
81 counter,
82 allowance_counter,
83 costs,
84 }
85 }
86
87 fn charge_gas(
88 &mut self,
89 operation: PreChargeGasOperation,
90 amount: u64,
91 ) -> Result<(), PrechargeError> {
92 if self.allowance_counter.charge_if_enough(amount) != ChargeResult::Enough {
93 return Err(PrechargeError::BlockGasExceeded);
94 }
95 if self.counter.charge_if_enough(amount) != ChargeResult::Enough {
96 return Err(PrechargeError::GasExceeded(operation));
97 }
98
99 Ok(())
100 }
101
102 pub fn charge_gas_for_program_data(&mut self) -> Result<(), PrechargeError> {
103 self.charge_gas(
104 PreChargeGasOperation::ProgramData,
105 self.costs.read.cost_for_one(),
106 )
107 }
108
109 pub fn charge_gas_for_program_code_len(&mut self) -> Result<(), PrechargeError> {
110 self.charge_gas(
111 PreChargeGasOperation::ProgramCodeLen,
112 self.costs.read.cost_for_one(),
113 )
114 }
115
116 pub fn charge_gas_for_program_code(
117 &mut self,
118 code_len: BytesAmount,
119 ) -> Result<(), PrechargeError> {
120 self.charge_gas(
121 PreChargeGasOperation::ProgramCode,
122 self.costs
123 .read
124 .cost_for_with_bytes(self.costs.read_per_byte, code_len),
125 )
126 }
127
128 pub fn charge_gas_for_section_instantiation(
129 &mut self,
130 section_name: SectionName,
131 section_len: BytesAmount,
132 ) -> Result<(), PrechargeError> {
133 let instantiation_costs = &self.costs.instantiation_costs;
134
135 let cost_per_byte = match section_name {
136 SectionName::Function => &instantiation_costs.code_section_per_byte,
137 SectionName::Data => &instantiation_costs.data_section_per_byte,
138 SectionName::Global => &instantiation_costs.global_section_per_byte,
139 SectionName::Table => &instantiation_costs.table_section_per_byte,
140 SectionName::Element => &instantiation_costs.element_section_per_byte,
141 SectionName::Type => &instantiation_costs.type_section_per_byte,
142 _ => {
143 unimplemented!("Wrong {section_name:?} for section instantiation")
145 }
146 };
147
148 self.charge_gas(
149 PreChargeGasOperation::ModuleInstantiation(section_name),
150 cost_per_byte.cost_for(section_len),
151 )
152 }
153
154 pub fn charge_gas_for_instrumentation(
155 &mut self,
156 original_code_len_bytes: BytesAmount,
157 ) -> Result<(), PrechargeError> {
158 self.charge_gas(
159 PreChargeGasOperation::ModuleInstrumentation,
160 self.costs
161 .instrumentation
162 .cost_for_with_bytes(self.costs.instrumentation_per_byte, original_code_len_bytes),
163 )
164 }
165}
166
167#[allow(missing_docs)]
169#[derive(Debug)]
170pub enum SuccessfulDispatchResultKind {
171 Exit(ProgramId),
172 Wait(Option<u32>, MessageWaitedType),
173 Success,
174}
175
176pub type PrechargeResult<T> = Result<T, Vec<JournalNote>>;
178
179pub fn precharge_for_program(
181 block_config: &BlockConfig,
182 gas_allowance: u64,
183 dispatch: IncomingDispatch,
184 destination_id: ProgramId,
185) -> PrechargeResult<ContextChargedForProgram> {
186 let mut gas_counter = GasCounter::new(dispatch.gas_limit());
187 let mut gas_allowance_counter = GasAllowanceCounter::new(gas_allowance);
188 let mut charger = GasPrecharger::new(
189 &mut gas_counter,
190 &mut gas_allowance_counter,
191 &block_config.costs,
192 );
193
194 match charger.charge_gas_for_program_data() {
195 Ok(()) => Ok(ContextChargedForProgram {
196 dispatch,
197 destination_id,
198 gas_counter,
199 gas_allowance_counter,
200 }),
201 Err(PrechargeError::BlockGasExceeded) => {
202 let gas_burned = gas_counter.burned();
203 Err(process_allowance_exceed(
204 dispatch,
205 destination_id,
206 gas_burned,
207 ))
208 }
209 Err(PrechargeError::GasExceeded(op)) => {
210 let gas_burned = gas_counter.burned();
211 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
212 Err(process_execution_error(
213 dispatch,
214 destination_id,
215 gas_burned,
216 system_reservation_ctx,
217 ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(op),
218 ))
219 }
220 }
221}
222
223pub fn precharge_for_allocations(
225 block_config: &BlockConfig,
226 mut context: ContextChargedForProgram,
227 allocations_tree_len: u32,
228) -> PrechargeResult<ContextChargedForAllocations> {
229 let mut charger = GasPrecharger::new(
230 &mut context.gas_counter,
231 &mut context.gas_allowance_counter,
232 &block_config.costs,
233 );
234
235 if allocations_tree_len == 0 {
236 return Ok(ContextChargedForAllocations(context));
237 }
238
239 let amount = block_config
240 .costs
241 .load_allocations_per_interval
242 .cost_for(allocations_tree_len)
243 .saturating_add(block_config.costs.read.cost_for_one());
244
245 match charger.charge_gas(PreChargeGasOperation::Allocations, amount) {
246 Ok(()) => Ok(ContextChargedForAllocations(context)),
247 Err(PrechargeError::BlockGasExceeded) => {
248 let gas_burned = context.gas_counter.burned();
249 Err(process_allowance_exceed(
250 context.dispatch,
251 context.destination_id,
252 gas_burned,
253 ))
254 }
255 Err(PrechargeError::GasExceeded(op)) => {
256 let gas_burned = context.gas_counter.burned();
257 let system_reservation_ctx = SystemReservationContext::from_dispatch(&context.dispatch);
258 Err(process_execution_error(
259 context.dispatch,
260 context.destination_id,
261 gas_burned,
262 system_reservation_ctx,
263 ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(op),
264 ))
265 }
266 }
267}
268
269pub fn precharge_for_code_length(
278 block_config: &BlockConfig,
279 context: ContextChargedForAllocations,
280 actor_data: ExecutableActorData,
281) -> PrechargeResult<ContextChargedForCodeLength> {
282 let ContextChargedForProgram {
283 dispatch,
284 destination_id,
285 mut gas_counter,
286 mut gas_allowance_counter,
287 } = context.0;
288
289 if !actor_data.code_exports.contains(&dispatch.kind()) {
290 return Err(process_success(
291 SuccessfulDispatchResultKind::Success,
292 DispatchResult::success(dispatch, destination_id, gas_counter.to_amount()),
293 ));
294 }
295
296 let mut charger = GasPrecharger::new(
297 &mut gas_counter,
298 &mut gas_allowance_counter,
299 &block_config.costs,
300 );
301 match charger.charge_gas_for_program_code_len() {
302 Ok(()) => Ok(ContextChargedForCodeLength {
303 data: ContextData {
304 gas_counter,
305 gas_allowance_counter,
306 dispatch,
307 destination_id,
308 actor_data,
309 },
310 }),
311 Err(PrechargeError::BlockGasExceeded) => Err(process_allowance_exceed(
312 dispatch,
313 destination_id,
314 gas_counter.burned(),
315 )),
316 Err(PrechargeError::GasExceeded(op)) => {
317 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
318 Err(process_execution_error(
319 dispatch,
320 destination_id,
321 gas_counter.burned(),
322 system_reservation_ctx,
323 ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(op),
324 ))
325 }
326 }
327}
328
329pub fn precharge_for_code(
331 block_config: &BlockConfig,
332 mut context: ContextChargedForCodeLength,
333 code_len_bytes: u32,
334) -> PrechargeResult<ContextChargedForCode> {
335 let mut charger = GasPrecharger::new(
336 &mut context.data.gas_counter,
337 &mut context.data.gas_allowance_counter,
338 &block_config.costs,
339 );
340
341 match charger.charge_gas_for_program_code(code_len_bytes.into()) {
342 Ok(()) => Ok(context.into()),
343 Err(PrechargeError::BlockGasExceeded) => Err(process_allowance_exceed(
344 context.data.dispatch,
345 context.data.destination_id,
346 context.data.gas_counter.burned(),
347 )),
348 Err(PrechargeError::GasExceeded(op)) => {
349 let system_reservation_ctx =
350 SystemReservationContext::from_dispatch(&context.data.dispatch);
351 Err(process_execution_error(
352 context.data.dispatch,
353 context.data.destination_id,
354 context.data.gas_counter.burned(),
355 system_reservation_ctx,
356 ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(op),
357 ))
358 }
359 }
360}
361
362pub fn precharge_for_instrumentation(
364 block_config: &BlockConfig,
365 mut context: ContextChargedForCode,
366 original_code_len_bytes: u32,
367) -> PrechargeResult<ContextChargedForInstrumentation> {
368 let mut charger = GasPrecharger::new(
369 &mut context.data.gas_counter,
370 &mut context.data.gas_allowance_counter,
371 &block_config.costs,
372 );
373
374 match charger.charge_gas_for_instrumentation(original_code_len_bytes.into()) {
375 Ok(()) => Ok(context.into()),
376 Err(PrechargeError::BlockGasExceeded) => Err(process_allowance_exceed(
377 context.data.dispatch,
378 context.data.destination_id,
379 context.data.gas_counter.burned(),
380 )),
381 Err(PrechargeError::GasExceeded(op)) => {
382 let system_reservation_ctx =
383 SystemReservationContext::from_dispatch(&context.data.dispatch);
384 Err(process_execution_error(
385 context.data.dispatch,
386 context.data.destination_id,
387 context.data.gas_counter.burned(),
388 system_reservation_ctx,
389 ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(op),
390 ))
391 }
392 }
393}
394
395pub fn precharge_for_module_instantiation(
397 block_config: &BlockConfig,
398 mut context: ContextChargedForInstrumentation,
399 section_sizes: &InstantiatedSectionSizes,
400) -> PrechargeResult<ContextChargedForMemory> {
401 let ContextChargedForInstrumentation {
402 data:
403 ContextData {
404 gas_counter,
405 gas_allowance_counter,
406 actor_data,
407 ..
408 },
409 ..
410 } = &mut context;
411
412 let mut f = || {
413 let mut charger =
414 GasPrecharger::new(gas_counter, gas_allowance_counter, &block_config.costs);
415
416 let memory_size = if let Some(page) = actor_data.allocations.end() {
418 page.inc()
419 } else {
420 actor_data.static_pages
421 };
422
423 charger.charge_gas_for_section_instantiation(
424 SectionName::Function,
425 section_sizes.code_section.into(),
426 )?;
427 charger.charge_gas_for_section_instantiation(
428 SectionName::Data,
429 section_sizes.data_section.into(),
430 )?;
431 charger.charge_gas_for_section_instantiation(
432 SectionName::Global,
433 section_sizes.global_section.into(),
434 )?;
435 charger.charge_gas_for_section_instantiation(
436 SectionName::Table,
437 section_sizes.table_section.into(),
438 )?;
439 charger.charge_gas_for_section_instantiation(
440 SectionName::Element,
441 section_sizes.element_section.into(),
442 )?;
443 charger.charge_gas_for_section_instantiation(
444 SectionName::Type,
445 section_sizes.type_section.into(),
446 )?;
447
448 Ok(memory_size)
449 };
450
451 match f() {
452 Ok(memory_size) => {
453 log::trace!("Charged for module instantiation and memory pages. Size: {memory_size:?}");
454 Ok(ContextChargedForMemory {
455 data: context.data,
456 max_reservations: block_config.max_reservations,
457 memory_size,
458 })
459 }
460 Err(err) => {
461 log::trace!("Failed to charge for module instantiation or memory pages: {err:?}");
462 match err {
463 PrechargeError::BlockGasExceeded => Err(process_allowance_exceed(
464 context.data.dispatch,
465 context.data.destination_id,
466 context.data.gas_counter.burned(),
467 )),
468 PrechargeError::GasExceeded(op) => {
469 let system_reservation_ctx =
470 SystemReservationContext::from_dispatch(&context.data.dispatch);
471 Err(process_execution_error(
472 context.data.dispatch,
473 context.data.destination_id,
474 context.data.gas_counter.burned(),
475 system_reservation_ctx,
476 ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(op),
477 ))
478 }
479 }
480 }
481 }
482}