use crate::{
common::{
ActorExecutionErrorReplyReason, ExecutableActorData, JournalNote, ReservationsAndMemorySize,
},
configs::BlockConfig,
context::SystemReservationContext,
processing::{process_allowance_exceed, process_execution_error},
};
use alloc::vec::Vec;
use core::marker::PhantomData;
use gear_core::{
code::{CodeMetadata, InstantiatedSectionSizes, SectionName},
costs::{BytesAmount, ProcessCosts},
gas::{ChargeResult, GasAllowanceCounter, GasCounter},
ids::ActorId,
message::IncomingDispatch,
};
#[derive(Debug, PartialEq, Eq, derive_more::Display)]
pub enum PreChargeGasOperation {
#[display("handle memory static pages")]
StaticPages,
#[display("handle program data")]
ProgramData,
#[display("obtain code metadata")]
CodeMetadata,
#[display("obtain original code")]
OriginalCode,
#[display("obtain instrumented code")]
InstrumentedCode,
#[display("instantiate {_0} of Wasm module")]
ModuleInstantiation(SectionName),
#[display("instrument Wasm module")]
ModuleInstrumentation,
#[display("obtain program allocations")]
Allocations,
}
pub type PrechargeResult<T> = Result<T, Vec<JournalNote>>;
pub struct ForNothing;
pub struct ForProgram;
pub struct ForCodeMetadata;
pub struct ForOriginalCode;
pub struct ForInstrumentedCode;
pub struct ForAllocations;
pub struct ForModuleInstantiation;
pub struct ContextCharged<For = ForNothing> {
destination_id: ActorId,
dispatch: IncomingDispatch,
gas_counter: GasCounter,
gas_allowance_counter: GasAllowanceCounter,
actor_data: Option<ExecutableActorData>,
reservations_and_memory_size: Option<ReservationsAndMemorySize>,
_phantom: PhantomData<For>,
}
impl ContextCharged {
pub fn new(
destination_id: ActorId,
dispatch: IncomingDispatch,
gas_allowance: u64,
) -> ContextCharged<ForNothing> {
let gas_counter = GasCounter::new(dispatch.gas_limit());
let gas_allowance_counter = GasAllowanceCounter::new(gas_allowance);
Self {
destination_id,
dispatch,
gas_counter,
gas_allowance_counter,
actor_data: None,
reservations_and_memory_size: None,
_phantom: PhantomData,
}
}
}
impl<T> ContextCharged<T> {
pub fn into_parts(self) -> (ActorId, IncomingDispatch, GasCounter, GasAllowanceCounter) {
(
self.destination_id,
self.dispatch,
self.gas_counter,
self.gas_allowance_counter,
)
}
pub fn gas_burned(&self) -> u64 {
self.gas_counter.burned()
}
pub fn gas_left(&self) -> u64 {
self.gas_counter.left()
}
fn charge_gas<For>(
mut self,
operation: PreChargeGasOperation,
amount: u64,
) -> PrechargeResult<ContextCharged<For>> {
if self.gas_allowance_counter.charge_if_enough(amount) != ChargeResult::Enough {
let gas_burned = self.gas_counter.burned();
return Err(process_allowance_exceed(
self.dispatch,
self.destination_id,
gas_burned,
));
}
if self.gas_counter.charge_if_enough(amount) != ChargeResult::Enough {
let gas_burned = self.gas_counter.burned();
let system_reservation_ctx = SystemReservationContext::from_dispatch(&self.dispatch);
return Err(process_execution_error(
self.dispatch,
self.destination_id,
gas_burned,
system_reservation_ctx,
ActorExecutionErrorReplyReason::PreChargeGasLimitExceeded(operation),
));
}
Ok(ContextCharged {
destination_id: self.destination_id,
dispatch: self.dispatch,
gas_counter: self.gas_counter,
gas_allowance_counter: self.gas_allowance_counter,
actor_data: self.actor_data,
reservations_and_memory_size: self.reservations_and_memory_size,
_phantom: PhantomData,
})
}
}
impl ContextCharged<ForNothing> {
pub fn charge_for_program(
self,
block_config: &BlockConfig,
) -> PrechargeResult<ContextCharged<ForProgram>> {
self.charge_gas(
PreChargeGasOperation::ProgramData,
block_config.costs.db.read.cost_for_one(),
)
}
}
impl ContextCharged<ForProgram> {
pub fn charge_for_code_metadata(
self,
block_config: &BlockConfig,
) -> PrechargeResult<ContextCharged<ForCodeMetadata>> {
self.charge_gas(
PreChargeGasOperation::CodeMetadata,
block_config.costs.db.read.cost_for_one(),
)
}
}
impl ContextCharged<ForCodeMetadata> {
pub fn charge_for_original_code(
self,
block_config: &BlockConfig,
code_len_bytes: u32,
) -> PrechargeResult<ContextCharged<ForOriginalCode>> {
self.charge_gas(
PreChargeGasOperation::OriginalCode,
block_config
.costs
.db
.read
.with_bytes(block_config.costs.db.read_per_byte, code_len_bytes.into()),
)
}
pub fn charge_for_instrumented_code(
self,
block_config: &BlockConfig,
code_len_bytes: u32,
) -> PrechargeResult<ContextCharged<ForInstrumentedCode>> {
self.charge_gas(
PreChargeGasOperation::InstrumentedCode,
block_config
.costs
.db
.read
.with_bytes(block_config.costs.db.read_per_byte, code_len_bytes.into()),
)
}
}
impl ContextCharged<ForOriginalCode> {
pub fn charge_for_instrumentation(
self,
block_config: &BlockConfig,
original_code_len_bytes: u32,
) -> PrechargeResult<ContextCharged<ForInstrumentedCode>> {
self.charge_gas(
PreChargeGasOperation::ModuleInstrumentation,
block_config.costs.instrumentation.base.with_bytes(
block_config.costs.instrumentation.per_byte,
original_code_len_bytes.into(),
),
)
}
}
impl ContextCharged<ForInstrumentedCode> {
pub fn charge_for_allocations(
self,
block_config: &BlockConfig,
allocations_tree_len: u32,
) -> PrechargeResult<ContextCharged<ForAllocations>> {
if allocations_tree_len == 0 {
return Ok(ContextCharged {
destination_id: self.destination_id,
dispatch: self.dispatch,
gas_counter: self.gas_counter,
gas_allowance_counter: self.gas_allowance_counter,
actor_data: self.actor_data,
reservations_and_memory_size: self.reservations_and_memory_size,
_phantom: PhantomData,
});
}
let amount = block_config
.costs
.load_allocations_per_interval
.cost_for(allocations_tree_len)
.saturating_add(block_config.costs.db.read.cost_for_one());
self.charge_gas(PreChargeGasOperation::Allocations, amount)
}
}
impl ContextCharged<ForAllocations> {
pub fn charge_for_module_instantiation(
mut self,
block_config: &BlockConfig,
actor_data: ExecutableActorData,
section_sizes: &InstantiatedSectionSizes,
code_metadata: &CodeMetadata,
) -> PrechargeResult<ContextCharged<ForModuleInstantiation>> {
let memory_size = if let Some(page) = actor_data.allocations.end() {
page.inc()
} else {
code_metadata.static_pages()
};
let reservations_and_memory_size = ReservationsAndMemorySize {
max_reservations: block_config.max_reservations,
memory_size,
};
self.actor_data = Some(actor_data);
self.reservations_and_memory_size = Some(reservations_and_memory_size);
self = self.charge_gas_for_section_instantiation(
&block_config.costs,
SectionName::Function,
section_sizes.code_section().into(),
)?;
self = self.charge_gas_for_section_instantiation(
&block_config.costs,
SectionName::Data,
section_sizes.data_section().into(),
)?;
self = self.charge_gas_for_section_instantiation(
&block_config.costs,
SectionName::Global,
section_sizes.global_section().into(),
)?;
self = self.charge_gas_for_section_instantiation(
&block_config.costs,
SectionName::Table,
section_sizes.table_section().into(),
)?;
self = self.charge_gas_for_section_instantiation(
&block_config.costs,
SectionName::Element,
section_sizes.element_section().into(),
)?;
self = self.charge_gas_for_section_instantiation(
&block_config.costs,
SectionName::Type,
section_sizes.type_section().into(),
)?;
Ok(ContextCharged {
destination_id: self.destination_id,
dispatch: self.dispatch,
gas_counter: self.gas_counter,
gas_allowance_counter: self.gas_allowance_counter,
actor_data: self.actor_data,
reservations_and_memory_size: self.reservations_and_memory_size,
_phantom: PhantomData,
})
}
fn charge_gas_for_section_instantiation(
self,
costs: &ProcessCosts,
section_name: SectionName,
section_len: BytesAmount,
) -> PrechargeResult<ContextCharged<ForAllocations>> {
let instantiation_costs = &costs.instantiation;
let cost_per_byte = match section_name {
SectionName::Function => &instantiation_costs.code_section_per_byte,
SectionName::Data => &instantiation_costs.data_section_per_byte,
SectionName::Global => &instantiation_costs.global_section_per_byte,
SectionName::Table => &instantiation_costs.table_section_per_byte,
SectionName::Element => &instantiation_costs.element_section_per_byte,
SectionName::Type => &instantiation_costs.type_section_per_byte,
_ => {
unimplemented!("Wrong {section_name:?} for section instantiation")
}
};
self.charge_gas(
PreChargeGasOperation::ModuleInstantiation(section_name),
cost_per_byte.cost_for(section_len),
)
}
}
impl ContextCharged<ForModuleInstantiation> {
pub fn into_final_parts(
self,
) -> (
ActorId,
IncomingDispatch,
GasCounter,
GasAllowanceCounter,
ExecutableActorData,
ReservationsAndMemorySize,
) {
(
self.destination_id,
self.dispatch,
self.gas_counter,
self.gas_allowance_counter,
self.actor_data.unwrap(),
self.reservations_and_memory_size.unwrap(),
)
}
}