1use 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#[derive(Debug, PartialEq, Eq, derive_more::Display)]
39pub enum PreChargeGasOperation {
40 #[display("handle memory static pages")]
42 StaticPages,
43 #[display("handle program data")]
45 ProgramData,
46 #[display("obtain code metadata")]
48 CodeMetadata,
49 #[display("obtain original code")]
51 OriginalCode,
52 #[display("obtain instrumented code")]
54 InstrumentedCode,
55 #[display("instantiate {_0} of Wasm module")]
57 ModuleInstantiation(SectionName),
58 #[display("instrument Wasm module")]
60 ModuleInstrumentation,
61 #[display("obtain program allocations")]
63 Allocations,
64}
65
66pub type PrechargeResult<T> = Result<T, Vec<JournalNote>>;
68
69pub struct ForNothing;
71
72pub struct ForProgram;
74
75pub struct ForCodeMetadata;
77
78pub struct ForOriginalCode;
80
81pub struct ForInstrumentedCode;
83
84pub struct ForAllocations;
86
87pub struct ForModuleInstantiation;
89
90pub 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 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 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 pub fn gas_burned(&self) -> u64 {
137 self.gas_counter.burned()
138 }
139
140 pub fn gas_left(&self) -> u64 {
142 self.gas_counter.left()
143 }
144
145 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 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 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 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 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 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 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 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 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 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 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}