core_processor/
precharge.rs

1// Copyright (C) 2023-2025 Gear Technologies Inc.
2// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17use 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/// Operation related to gas charging.
37#[derive(Debug, PartialEq, Eq, derive_more::Display)]
38pub enum PreChargeGasOperation {
39    /// Handle memory static pages.
40    #[display(fmt = "handle memory static pages")]
41    StaticPages,
42    /// Handle program data.
43    #[display(fmt = "handle program data")]
44    ProgramData,
45    /// Obtain code length.
46    #[display(fmt = "obtain program code length")]
47    ProgramCodeLen,
48    /// Handle program code.
49    #[display(fmt = "handle program code")]
50    ProgramCode,
51    /// Instantiate the type section of the Wasm module.
52    #[display(fmt = "instantiate {_0} of Wasm module")]
53    ModuleInstantiation(SectionName),
54    /// Instrument Wasm module.
55    #[display(fmt = "instrument Wasm module")]
56    ModuleInstrumentation,
57    /// Obtain program allocations.
58    #[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                // TODO: change this to a system error in future
144                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/// Possible variants of the [`DispatchResult`] if the latter contains value.
168#[allow(missing_docs)]
169#[derive(Debug)]
170pub enum SuccessfulDispatchResultKind {
171    Exit(ProgramId),
172    Wait(Option<u32>, MessageWaitedType),
173    Success,
174}
175
176/// Defines result variants of the precharge functions.
177pub type PrechargeResult<T> = Result<T, Vec<JournalNote>>;
178
179/// Charge a message for program data beforehand.
180pub 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
223/// Precharge for allocations obtaining from storage.
224pub 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
269/// Charge a message for fetching the actual length of the binary code
270/// from a storage. The updated value of binary code length
271/// should be kept in standalone storage. The caller has to call this
272/// function to charge gas-counters accordingly before fetching the value.
273///
274/// The function also performs several additional checks:
275/// - if an actor is executable
276/// - if a required dispatch method is exported.
277pub 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
329/// Charge a message for the program binary code beforehand.
330pub 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
362/// Charge a message for instrumentation of the binary code beforehand.
363pub 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
395/// Charge a message for program memory and module instantiation beforehand.
396pub 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        // Calculates size of wasm memory buffer which must be created in execution environment
417        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}