1use crate::{
18 common::{
19 ActorExecutionErrorReplyReason, DispatchResult, ExecutableActorData, JournalNote,
20 PrechargedDispatch,
21 },
22 configs::{BlockConfig, PageCosts},
23 context::{ContextChargedForCodeLength, ContextChargedForMemory, ContextData},
24 processing::{
25 process_allowance_exceed, process_error, process_non_executable, process_success,
26 },
27 ContextChargedForCode, ContextChargedForInstrumentation,
28};
29use alloc::{collections::BTreeSet, vec::Vec};
30use gear_backend_common::SystemReservationContext;
31use gear_core::{
32 gas::{ChargeResult, GasAllowanceCounter, GasCounter},
33 ids::ProgramId,
34 message::{DispatchKind, IncomingDispatch, MessageWaitedType},
35 pages::{PageU32Size, WasmPage},
36};
37use scale_info::{
38 scale::{self, Decode, Encode},
39 TypeInfo,
40};
41
42#[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Eq, PartialOrd, Ord, derive_more::Display)]
44#[codec(crate = scale)]
45pub enum PreChargeGasOperation {
46 #[display(fmt = "handle memory static pages")]
48 StaticPages,
49 #[display(fmt = "handle program data")]
51 ProgramData,
52 #[display(fmt = "obtain program code length")]
54 ProgramCodeLen,
55 #[display(fmt = "handle program code")]
57 ProgramCode,
58 #[display(fmt = "instantiate Wasm module")]
60 ModuleInstantiation,
61 #[display(fmt = "instrument Wasm module")]
63 ModuleInstrumentation,
64}
65
66#[derive(Debug, Eq, PartialEq)]
67enum PrechargeError {
68 BlockGasExceeded,
69 GasExceeded(PreChargeGasOperation),
70}
71
72struct GasPrecharger<'a> {
73 counter: &'a mut GasCounter,
74 allowance_counter: &'a mut GasAllowanceCounter,
75}
76
77impl<'a> GasPrecharger<'a> {
78 pub fn new(
79 counter: &'a mut GasCounter,
80 allowance_counter: &'a mut GasAllowanceCounter,
81 ) -> Self {
82 Self {
83 counter,
84 allowance_counter,
85 }
86 }
87
88 fn charge_gas(
89 &mut self,
90 operation: PreChargeGasOperation,
91 amount: u64,
92 ) -> Result<(), PrechargeError> {
93 if self.allowance_counter.charge_if_enough(amount) != ChargeResult::Enough {
94 return Err(PrechargeError::BlockGasExceeded);
95 }
96 if self.counter.charge_if_enough(amount) != ChargeResult::Enough {
97 return Err(PrechargeError::GasExceeded(operation));
98 }
99
100 Ok(())
101 }
102
103 pub fn charge_gas_for_program_data(
104 &mut self,
105 read_cost: u64,
106 per_byte_cost: u64,
107 ) -> Result<(), PrechargeError> {
108 self.charge_gas(
109 PreChargeGasOperation::ProgramData,
110 calculate_gas_for_program(read_cost, per_byte_cost),
111 )
112 }
113
114 pub fn charge_gas_for_program_code_len(
115 &mut self,
116 read_cost: u64,
117 ) -> Result<(), PrechargeError> {
118 self.charge_gas(PreChargeGasOperation::ProgramCodeLen, read_cost)
119 }
120
121 pub fn charge_gas_for_program_code(
122 &mut self,
123 read_cost: u64,
124 per_byte_cost: u64,
125 code_len_bytes: u32,
126 ) -> Result<(), PrechargeError> {
127 self.charge_gas(
128 PreChargeGasOperation::ProgramCode,
129 calculate_gas_for_code(read_cost, per_byte_cost, code_len_bytes.into()),
130 )
131 }
132
133 pub fn charge_gas_for_instantiation(
134 &mut self,
135 gas_per_byte: u64,
136 code_length: u32,
137 ) -> Result<(), PrechargeError> {
138 let amount = gas_per_byte.saturating_mul(code_length as u64);
139 self.charge_gas(PreChargeGasOperation::ModuleInstantiation, amount)
140 }
141
142 pub fn charge_gas_for_instrumentation(
143 &mut self,
144 instrumentation_cost: u64,
145 instrumentation_byte_cost: u64,
146 original_code_len_bytes: u32,
147 ) -> Result<(), PrechargeError> {
148 let amount = instrumentation_cost.saturating_add(
149 instrumentation_byte_cost.saturating_mul(original_code_len_bytes.into()),
150 );
151 self.charge_gas(PreChargeGasOperation::ModuleInstrumentation, amount)
152 }
153
154 pub fn charge_gas_for_pages(
157 &mut self,
158 costs: &PageCosts,
159 allocations: &BTreeSet<WasmPage>,
160 static_pages: WasmPage,
161 ) -> Result<WasmPage, PrechargeError> {
162 let amount = costs.static_page.calc(static_pages);
164 self.charge_gas(PreChargeGasOperation::StaticPages, amount)?;
165
166 if let Some(page) = allocations.iter().next_back() {
167 Ok(page
171 .inc()
172 .unwrap_or_else(|_| unreachable!("WASM memory size is too big")))
173 } else {
174 Ok(static_pages)
175 }
176 }
177}
178
179pub fn calculate_gas_for_program(read_cost: u64, _per_byte_cost: u64) -> u64 {
181 read_cost
182}
183
184pub fn calculate_gas_for_code(read_cost: u64, per_byte_cost: u64, code_len_bytes: u64) -> u64 {
186 read_cost.saturating_add(code_len_bytes.saturating_mul(per_byte_cost))
187}
188
189#[derive(Debug)]
190pub enum SuccessfulDispatchResultKind {
191 Exit(ProgramId),
192 Wait(Option<u32>, MessageWaitedType),
193 Success,
194}
195
196pub type PrechargeResult<T> = Result<T, Vec<JournalNote>>;
198
199pub fn precharge_for_program(
201 block_config: &BlockConfig,
202 gas_allowance: u64,
203 dispatch: IncomingDispatch,
204 destination_id: ProgramId,
205) -> PrechargeResult<PrechargedDispatch> {
206 let read_per_byte_cost = block_config.read_per_byte_cost;
207 let read_cost = block_config.read_cost;
208
209 let mut gas_counter = GasCounter::new(dispatch.gas_limit());
210 let mut gas_allowance_counter = GasAllowanceCounter::new(gas_allowance);
211 let mut charger = GasPrecharger::new(&mut gas_counter, &mut gas_allowance_counter);
212
213 match charger.charge_gas_for_program_data(read_cost, read_per_byte_cost) {
214 Ok(()) => Ok((dispatch, gas_counter, gas_allowance_counter).into()),
215 Err(PrechargeError::BlockGasExceeded) => {
216 let gas_burned = gas_counter.burned();
217 Err(process_allowance_exceed(
218 dispatch,
219 destination_id,
220 gas_burned,
221 ))
222 }
223 Err(PrechargeError::GasExceeded(op)) => {
224 let gas_burned = gas_counter.burned();
225 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
226 Err(process_error(
227 dispatch,
228 destination_id,
229 gas_burned,
230 system_reservation_ctx,
231 ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(op),
232 false,
233 ))
234 }
235 }
236}
237
238fn check_is_executable(
239 executable_data: Option<ExecutableActorData>,
240 dispatch: &IncomingDispatch,
241) -> Option<ExecutableActorData> {
242 executable_data
243 .filter(|data| !(data.initialized && matches!(dispatch.kind(), DispatchKind::Init)))
244}
245
246pub fn precharge_for_code_length(
255 block_config: &BlockConfig,
256 dispatch: PrechargedDispatch,
257 destination_id: ProgramId,
258 executable_data: Option<ExecutableActorData>,
259) -> PrechargeResult<ContextChargedForCodeLength> {
260 let read_cost = block_config.read_cost;
261
262 let (dispatch, mut gas_counter, mut gas_allowance_counter) = dispatch.into_parts();
263
264 let Some(actor_data) = check_is_executable(executable_data, &dispatch) else {
265 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
266 return Err(process_non_executable(
267 dispatch,
268 destination_id,
269 system_reservation_ctx,
270 ));
271 };
272
273 if !actor_data.code_exports.contains(&dispatch.kind()) {
274 return Err(process_success(
275 SuccessfulDispatchResultKind::Success,
276 DispatchResult::success(dispatch, destination_id, gas_counter.to_amount()),
277 ));
278 }
279
280 let mut charger = GasPrecharger::new(&mut gas_counter, &mut gas_allowance_counter);
281 match charger.charge_gas_for_program_code_len(read_cost) {
282 Ok(()) => Ok(ContextChargedForCodeLength {
283 data: ContextData {
284 gas_counter,
285 gas_allowance_counter,
286 dispatch,
287 destination_id,
288 actor_data,
289 },
290 }),
291 Err(PrechargeError::BlockGasExceeded) => Err(process_allowance_exceed(
292 dispatch,
293 destination_id,
294 gas_counter.burned(),
295 )),
296 Err(PrechargeError::GasExceeded(op)) => {
297 let system_reservation_ctx = SystemReservationContext::from_dispatch(&dispatch);
298 Err(process_error(
299 dispatch,
300 destination_id,
301 gas_counter.burned(),
302 system_reservation_ctx,
303 ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(op),
304 false,
305 ))
306 }
307 }
308}
309
310pub fn precharge_for_code(
312 block_config: &BlockConfig,
313 mut context: ContextChargedForCodeLength,
314 code_len_bytes: u32,
315) -> PrechargeResult<ContextChargedForCode> {
316 let read_per_byte_cost = block_config.read_per_byte_cost;
317 let read_cost = block_config.read_cost;
318
319 let mut charger = GasPrecharger::new(
320 &mut context.data.gas_counter,
321 &mut context.data.gas_allowance_counter,
322 );
323
324 match charger.charge_gas_for_program_code(read_cost, read_per_byte_cost, code_len_bytes) {
325 Ok(()) => Ok((context, code_len_bytes).into()),
326 Err(PrechargeError::BlockGasExceeded) => Err(process_allowance_exceed(
327 context.data.dispatch,
328 context.data.destination_id,
329 context.data.gas_counter.burned(),
330 )),
331 Err(PrechargeError::GasExceeded(op)) => {
332 let system_reservation_ctx =
333 SystemReservationContext::from_dispatch(&context.data.dispatch);
334 Err(process_error(
335 context.data.dispatch,
336 context.data.destination_id,
337 context.data.gas_counter.burned(),
338 system_reservation_ctx,
339 ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(op),
340 false,
341 ))
342 }
343 }
344}
345
346pub fn precharge_for_instrumentation(
348 block_config: &BlockConfig,
349 mut context: ContextChargedForCode,
350 original_code_len_bytes: u32,
351) -> PrechargeResult<ContextChargedForInstrumentation> {
352 let cost_base = block_config.code_instrumentation_cost;
353 let cost_per_byte = block_config.code_instrumentation_byte_cost;
354
355 let mut charger = GasPrecharger::new(
356 &mut context.data.gas_counter,
357 &mut context.data.gas_allowance_counter,
358 );
359
360 match charger.charge_gas_for_instrumentation(cost_base, cost_per_byte, original_code_len_bytes)
361 {
362 Ok(()) => Ok(context.into()),
363 Err(PrechargeError::BlockGasExceeded) => Err(process_allowance_exceed(
364 context.data.dispatch,
365 context.data.destination_id,
366 context.data.gas_counter.burned(),
367 )),
368 Err(PrechargeError::GasExceeded(op)) => {
369 let system_reservation_ctx =
370 SystemReservationContext::from_dispatch(&context.data.dispatch);
371 Err(process_error(
372 context.data.dispatch,
373 context.data.destination_id,
374 context.data.gas_counter.burned(),
375 system_reservation_ctx,
376 ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(op),
377 false,
378 ))
379 }
380 }
381}
382
383pub fn precharge_for_memory(
385 block_config: &BlockConfig,
386 mut context: ContextChargedForInstrumentation,
387) -> PrechargeResult<ContextChargedForMemory> {
388 let ContextChargedForInstrumentation {
389 data:
390 ContextData {
391 gas_counter,
392 gas_allowance_counter,
393 actor_data,
394 ..
395 },
396 code_len_bytes,
397 } = &mut context;
398
399 let mut f = || {
400 let mut charger = GasPrecharger::new(gas_counter, gas_allowance_counter);
401
402 let memory_size = charger.charge_gas_for_pages(
403 &block_config.page_costs,
404 &actor_data.allocations,
405 actor_data.static_pages,
406 )?;
407
408 charger.charge_gas_for_instantiation(
409 block_config.module_instantiation_byte_cost,
410 *code_len_bytes,
411 )?;
412
413 Ok(memory_size)
414 };
415
416 let memory_size = match f() {
417 Ok(size) => {
418 log::debug!("Charged for module instantiation and memory pages. Size: {size:?}");
419 size
420 }
421 Err(err) => {
422 log::debug!("Failed to charge for module instantiation or memory pages: {err:?}");
423 let reason = match err {
424 PrechargeError::BlockGasExceeded => {
425 return Err(process_allowance_exceed(
426 context.data.dispatch,
427 context.data.destination_id,
428 context.data.gas_counter.burned(),
429 ));
430 }
431 PrechargeError::GasExceeded(op) => {
432 ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(op)
433 }
434 };
435
436 let system_reservation_ctx =
437 SystemReservationContext::from_dispatch(&context.data.dispatch);
438 return Err(process_error(
439 context.data.dispatch,
440 context.data.destination_id,
441 context.data.gas_counter.burned(),
442 system_reservation_ctx,
443 reason,
444 false,
445 ));
446 }
447 };
448
449 Ok(ContextChargedForMemory {
450 data: context.data,
451 max_reservations: block_config.max_reservations,
452 memory_size,
453 })
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459 use gear_backend_common::assert_ok;
460
461 fn prepare_gas_counters() -> (GasCounter, GasAllowanceCounter) {
462 (
463 GasCounter::new(1_000_000),
464 GasAllowanceCounter::new(4_000_000),
465 )
466 }
467
468 #[test]
469 fn gas_for_static_pages() {
470 let costs = PageCosts::new_for_tests();
471 let (mut gas_counter, mut gas_allowance_counter) = prepare_gas_counters();
472 let mut charger = GasPrecharger::new(&mut gas_counter, &mut gas_allowance_counter);
473 let static_pages = 4.into();
474 let res = charger.charge_gas_for_pages(&costs, &Default::default(), static_pages);
475 assert_ok!(res, static_pages);
477 let charge = costs.static_page.calc(static_pages);
479 assert_eq!(charger.counter.left(), 1_000_000 - charge);
480 assert_eq!(charger.allowance_counter.left(), 4_000_000 - charge);
481 }
482}