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
17//! Precharge module.
18
19use crate::{
20    common::{
21        ActorExecutionErrorReplyReason, ExecutableActorData, JournalNote, ReservationsAndMemorySize,
22    },
23    configs::BlockConfig,
24    context::SystemReservationContext,
25    processing::{process_allowance_exceed, process_execution_error},
26};
27use alloc::vec::Vec;
28use core::marker::PhantomData;
29use gear_core::{
30    code::{CodeMetadata, InstantiatedSectionSizes, SectionName},
31    costs::{BytesAmount, ProcessCosts},
32    gas::{ChargeResult, GasAllowanceCounter, GasCounter},
33    ids::ActorId,
34    message::IncomingDispatch,
35};
36
37/// Operation related to gas charging.
38#[derive(Debug, PartialEq, Eq, derive_more::Display)]
39pub enum PreChargeGasOperation {
40    /// Handle memory static pages.
41    #[display("handle memory static pages")]
42    StaticPages,
43    /// Handle program data.
44    #[display("handle program data")]
45    ProgramData,
46    /// Obtain code metadata.
47    #[display("obtain code metadata")]
48    CodeMetadata,
49    /// Obtain original code
50    #[display("obtain original code")]
51    OriginalCode,
52    /// Obtain instrumented code
53    #[display("obtain instrumented code")]
54    InstrumentedCode,
55    /// Instantiate the type section of the Wasm module.
56    #[display("instantiate {_0} of Wasm module")]
57    ModuleInstantiation(SectionName),
58    /// Instrument Wasm module.
59    #[display("instrument Wasm module")]
60    ModuleInstrumentation,
61    /// Obtain program allocations.
62    #[display("obtain program allocations")]
63    Allocations,
64}
65
66/// Defines result variants of the precharge functions.
67pub type PrechargeResult<T> = Result<T, Vec<JournalNote>>;
68
69/// ZST for the context that charged nothing.
70pub struct ForNothing;
71
72/// ZST for the context that charged for program data.
73pub struct ForProgram;
74
75/// ZST for the context that charged for code metadata.
76pub struct ForCodeMetadata;
77
78/// ZST for the context that charged for original code.
79pub struct ForOriginalCode;
80
81/// ZST for the context that charged for instrumented code.
82pub struct ForInstrumentedCode;
83
84/// ZST for the context that charged for allocations.
85pub struct ForAllocations;
86
87/// ZST for the context that charged for module instantiation.
88pub struct ForModuleInstantiation;
89
90/// Context charged gas for the program execution.
91pub struct ContextCharged<For = ForNothing> {
92    destination_id: ActorId,
93    dispatch: IncomingDispatch,
94    gas_counter: GasCounter,
95    gas_allowance_counter: GasAllowanceCounter,
96    actor_data: Option<ExecutableActorData>,
97    reservations_and_memory_size: Option<ReservationsAndMemorySize>,
98
99    _phantom: PhantomData<For>,
100}
101
102impl ContextCharged {
103    /// Creates a new empty instance of the context charged for the program execution.
104    pub fn new(
105        destination_id: ActorId,
106        dispatch: IncomingDispatch,
107        gas_allowance: u64,
108    ) -> ContextCharged<ForNothing> {
109        let gas_counter = GasCounter::new(dispatch.gas_limit());
110        let gas_allowance_counter = GasAllowanceCounter::new(gas_allowance);
111
112        Self {
113            destination_id,
114            dispatch,
115            gas_counter,
116            gas_allowance_counter,
117            actor_data: None,
118            reservations_and_memory_size: None,
119            _phantom: PhantomData,
120        }
121    }
122}
123
124impl<T> ContextCharged<T> {
125    /// Splits the context into parts.
126    pub fn into_parts(self) -> (ActorId, IncomingDispatch, GasCounter, GasAllowanceCounter) {
127        (
128            self.destination_id,
129            self.dispatch,
130            self.gas_counter,
131            self.gas_allowance_counter,
132        )
133    }
134
135    /// Gas already burned
136    pub fn gas_burned(&self) -> u64 {
137        self.gas_counter.burned()
138    }
139
140    /// Gas left
141    pub fn gas_left(&self) -> u64 {
142        self.gas_counter.left()
143    }
144
145    /// Charges gas for the operation.
146    fn charge_gas<For>(
147        mut self,
148        operation: PreChargeGasOperation,
149        amount: u64,
150    ) -> PrechargeResult<ContextCharged<For>> {
151        if self.gas_allowance_counter.charge_if_enough(amount) != ChargeResult::Enough {
152            let gas_burned = self.gas_counter.burned();
153
154            return Err(process_allowance_exceed(
155                self.dispatch,
156                self.destination_id,
157                gas_burned,
158            ));
159        }
160
161        if self.gas_counter.charge_if_enough(amount) != ChargeResult::Enough {
162            let gas_burned = self.gas_counter.burned();
163            let system_reservation_ctx = SystemReservationContext::from_dispatch(&self.dispatch);
164
165            return Err(process_execution_error(
166                self.dispatch,
167                self.destination_id,
168                gas_burned,
169                system_reservation_ctx,
170                ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(operation),
171            ));
172        }
173
174        Ok(ContextCharged {
175            destination_id: self.destination_id,
176            dispatch: self.dispatch,
177            gas_counter: self.gas_counter,
178            gas_allowance_counter: self.gas_allowance_counter,
179            actor_data: self.actor_data,
180            reservations_and_memory_size: self.reservations_and_memory_size,
181            _phantom: PhantomData,
182        })
183    }
184}
185
186impl ContextCharged<ForNothing> {
187    /// Charges gas for getting the program data.
188    pub fn charge_for_program(
189        self,
190        block_config: &BlockConfig,
191    ) -> PrechargeResult<ContextCharged<ForProgram>> {
192        self.charge_gas(
193            PreChargeGasOperation::ProgramData,
194            block_config.costs.db.read.cost_for_one(),
195        )
196    }
197}
198
199impl ContextCharged<ForProgram> {
200    /// Charges gas for getting the code metadata.
201    pub fn charge_for_code_metadata(
202        self,
203        block_config: &BlockConfig,
204    ) -> PrechargeResult<ContextCharged<ForCodeMetadata>> {
205        self.charge_gas(
206            PreChargeGasOperation::CodeMetadata,
207            block_config.costs.db.read.cost_for_one(),
208        )
209    }
210}
211
212impl ContextCharged<ForCodeMetadata> {
213    /// Charges gas for getting the original code.
214    pub fn charge_for_original_code(
215        self,
216        block_config: &BlockConfig,
217        code_len_bytes: u32,
218    ) -> PrechargeResult<ContextCharged<ForOriginalCode>> {
219        self.charge_gas(
220            PreChargeGasOperation::OriginalCode,
221            block_config
222                .costs
223                .db
224                .read
225                .with_bytes(block_config.costs.db.read_per_byte, code_len_bytes.into()),
226        )
227    }
228
229    /// Charges gas for getting the instrumented code.
230    pub fn charge_for_instrumented_code(
231        self,
232        block_config: &BlockConfig,
233        code_len_bytes: u32,
234    ) -> PrechargeResult<ContextCharged<ForInstrumentedCode>> {
235        self.charge_gas(
236            PreChargeGasOperation::InstrumentedCode,
237            block_config
238                .costs
239                .db
240                .read
241                .with_bytes(block_config.costs.db.read_per_byte, code_len_bytes.into()),
242        )
243    }
244}
245
246impl ContextCharged<ForOriginalCode> {
247    /// Charges gas for code instrumentation.
248    pub fn charge_for_instrumentation(
249        self,
250        block_config: &BlockConfig,
251        original_code_len_bytes: u32,
252    ) -> PrechargeResult<ContextCharged<ForInstrumentedCode>> {
253        self.charge_gas(
254            PreChargeGasOperation::ModuleInstrumentation,
255            block_config.costs.instrumentation.base.with_bytes(
256                block_config.costs.instrumentation.per_byte,
257                original_code_len_bytes.into(),
258            ),
259        )
260    }
261}
262
263impl ContextCharged<ForInstrumentedCode> {
264    /// Charges gas for allocations.
265    pub fn charge_for_allocations(
266        self,
267        block_config: &BlockConfig,
268        allocations_tree_len: u32,
269    ) -> PrechargeResult<ContextCharged<ForAllocations>> {
270        if allocations_tree_len == 0 {
271            return Ok(ContextCharged {
272                destination_id: self.destination_id,
273                dispatch: self.dispatch,
274                gas_counter: self.gas_counter,
275                gas_allowance_counter: self.gas_allowance_counter,
276                actor_data: self.actor_data,
277                reservations_and_memory_size: self.reservations_and_memory_size,
278                _phantom: PhantomData,
279            });
280        }
281
282        let amount = block_config
283            .costs
284            .load_allocations_per_interval
285            .cost_for(allocations_tree_len)
286            .saturating_add(block_config.costs.db.read.cost_for_one());
287
288        self.charge_gas(PreChargeGasOperation::Allocations, amount)
289    }
290}
291
292impl ContextCharged<ForAllocations> {
293    /// Charges gas for module instantiation.
294    pub fn charge_for_module_instantiation(
295        mut self,
296        block_config: &BlockConfig,
297        actor_data: ExecutableActorData,
298        section_sizes: &InstantiatedSectionSizes,
299        code_metadata: &CodeMetadata,
300    ) -> PrechargeResult<ContextCharged<ForModuleInstantiation>> {
301        // Calculates size of wasm memory buffer which must be created in execution environment
302        let memory_size = if let Some(page) = actor_data.allocations.end() {
303            page.inc()
304        } else {
305            code_metadata.static_pages()
306        };
307
308        let reservations_and_memory_size = ReservationsAndMemorySize {
309            max_reservations: block_config.max_reservations,
310            memory_size,
311        };
312
313        self.actor_data = Some(actor_data);
314        self.reservations_and_memory_size = Some(reservations_and_memory_size);
315
316        self = self.charge_gas_for_section_instantiation(
317            &block_config.costs,
318            SectionName::Function,
319            section_sizes.code_section().into(),
320        )?;
321
322        self = self.charge_gas_for_section_instantiation(
323            &block_config.costs,
324            SectionName::Data,
325            section_sizes.data_section().into(),
326        )?;
327
328        self = self.charge_gas_for_section_instantiation(
329            &block_config.costs,
330            SectionName::Global,
331            section_sizes.global_section().into(),
332        )?;
333
334        self = self.charge_gas_for_section_instantiation(
335            &block_config.costs,
336            SectionName::Table,
337            section_sizes.table_section().into(),
338        )?;
339
340        self = self.charge_gas_for_section_instantiation(
341            &block_config.costs,
342            SectionName::Element,
343            section_sizes.element_section().into(),
344        )?;
345
346        self = self.charge_gas_for_section_instantiation(
347            &block_config.costs,
348            SectionName::Type,
349            section_sizes.type_section().into(),
350        )?;
351
352        Ok(ContextCharged {
353            destination_id: self.destination_id,
354            dispatch: self.dispatch,
355            gas_counter: self.gas_counter,
356            gas_allowance_counter: self.gas_allowance_counter,
357            actor_data: self.actor_data,
358            reservations_and_memory_size: self.reservations_and_memory_size,
359            _phantom: PhantomData,
360        })
361    }
362
363    /// Helper function to charge gas for section instantiation.
364    fn charge_gas_for_section_instantiation(
365        self,
366        costs: &ProcessCosts,
367        section_name: SectionName,
368        section_len: BytesAmount,
369    ) -> PrechargeResult<ContextCharged<ForAllocations>> {
370        let instantiation_costs = &costs.instantiation;
371
372        let cost_per_byte = match section_name {
373            SectionName::Function => &instantiation_costs.code_section_per_byte,
374            SectionName::Data => &instantiation_costs.data_section_per_byte,
375            SectionName::Global => &instantiation_costs.global_section_per_byte,
376            SectionName::Table => &instantiation_costs.table_section_per_byte,
377            SectionName::Element => &instantiation_costs.element_section_per_byte,
378            SectionName::Type => &instantiation_costs.type_section_per_byte,
379            _ => {
380                unimplemented!("Wrong {section_name:?} for section instantiation")
381            }
382        };
383
384        self.charge_gas(
385            PreChargeGasOperation::ModuleInstantiation(section_name),
386            cost_per_byte.cost_for(section_len),
387        )
388    }
389}
390
391impl ContextCharged<ForModuleInstantiation> {
392    /// Converts the context into the final parts.
393    pub fn into_final_parts(
394        self,
395    ) -> (
396        ActorId,
397        IncomingDispatch,
398        GasCounter,
399        GasAllowanceCounter,
400        ExecutableActorData,
401        ReservationsAndMemorySize,
402    ) {
403        (
404            self.destination_id,
405            self.dispatch,
406            self.gas_counter,
407            self.gas_allowance_counter,
408            self.actor_data.unwrap(),
409            self.reservations_and_memory_size.unwrap(),
410        )
411    }
412}