#![no_std]
extern crate alloc;
pub mod lazy_pages;
mod utils;
#[cfg(any(feature = "mock", test))]
pub mod mock;
pub mod funcs;
pub mod memory;
pub mod runtime;
use alloc::{
collections::{BTreeMap, BTreeSet},
string::String,
vec::Vec,
};
use core::{
convert::Infallible,
fmt::{Debug, Display},
};
use gear_core::{
env::Externalities,
gas::{ChargeError, CountersOwner, GasAmount, GasLeft},
ids::{CodeId, MessageId, ProgramId, ReservationId},
memory::{Memory, MemoryInterval, PageBuf},
message::{
ContextStore, Dispatch, DispatchKind, IncomingDispatch, MessageWaitedType, WasmEntryPoint,
},
pages::{GearPage, WasmPage},
reservation::GasReserver,
};
use lazy_pages::GlobalsAccessConfig;
use memory::ProcessAccessError;
use scale_info::scale::{self, Decode, Encode};
use crate::runtime::RunFallibleError;
pub use crate::utils::{LimitedStr, TrimmedString};
use gear_core::memory::MemoryError;
pub use log;
pub const PTR_SPECIAL: u32 = u32::MAX;
#[derive(Debug, Clone, Eq, PartialEq, derive_more::From)]
pub enum TerminationReason {
Actor(ActorTerminationReason),
System(SystemTerminationReason),
}
impl From<ChargeError> for TerminationReason {
fn from(err: ChargeError) -> Self {
match err {
ChargeError::GasLimitExceeded => {
ActorTerminationReason::Trap(TrapExplanation::GasLimitExceeded).into()
}
ChargeError::GasAllowanceExceeded => {
ActorTerminationReason::GasAllowanceExceeded.into()
}
}
}
}
impl From<TrapExplanation> for TerminationReason {
fn from(trap: TrapExplanation) -> Self {
ActorTerminationReason::Trap(trap).into()
}
}
impl<E: BackendSyscallError> From<E> for TerminationReason {
fn from(err: E) -> Self {
err.into_termination_reason()
}
}
#[derive(Decode, Encode, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, derive_more::From)]
#[codec(crate = scale)]
pub enum ActorTerminationReason {
Exit(ProgramId),
Leave,
Success,
Wait(Option<u32>, MessageWaitedType),
GasAllowanceExceeded,
#[from]
Trap(TrapExplanation),
}
#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display)]
pub struct SystemTerminationReason;
#[derive(
Decode,
Encode,
Debug,
Clone,
Eq,
PartialEq,
PartialOrd,
Ord,
derive_more::Display,
derive_more::From,
)]
pub enum UnrecoverableExecutionError {
#[display(fmt = "Invalid debug string passed in `gr_debug` sys-call")]
InvalidDebugString,
#[display(fmt = "Not enough gas for operation")]
NotEnoughGas,
#[display(fmt = "Length is overflowed to read payload")]
TooBigReadLen,
#[display(fmt = "Cannot take data in payload range from message with size")]
ReadWrongRange,
}
#[derive(
Decode,
Encode,
Debug,
Clone,
Eq,
PartialEq,
PartialOrd,
Ord,
derive_more::Display,
derive_more::From,
)]
pub enum UnrecoverableMemoryError {
#[display(fmt = "Trying to access memory outside wasm program memory")]
AccessOutOfBounds,
#[display(fmt = "Trying to allocate more memory in block-chain runtime than allowed")]
RuntimeAllocOutOfBounds,
}
#[derive(
Decode,
Encode,
Debug,
Clone,
Eq,
PartialEq,
PartialOrd,
Ord,
derive_more::Display,
derive_more::From,
)]
pub enum UnrecoverableWaitError {
#[display(fmt = "Waiting duration cannot be zero")]
ZeroDuration,
#[display(fmt = "`wait()` is not allowed after reply sent")]
WaitAfterReply,
}
#[derive(
Decode,
Encode,
Debug,
Clone,
Eq,
PartialEq,
PartialOrd,
Ord,
derive_more::Display,
derive_more::From,
)]
pub enum UnrecoverableExtError {
#[display(fmt = "Execution error: {_0}")]
Execution(UnrecoverableExecutionError),
#[display(fmt = "Memory error: {_0}")]
Memory(UnrecoverableMemoryError),
#[display(fmt = "Waiting error: {_0}")]
Wait(UnrecoverableWaitError),
}
#[derive(
Decode,
Encode,
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
derive_more::Display,
derive_more::From,
)]
#[codec(crate = scale)]
pub enum TrapExplanation {
#[display(fmt = "Not enough gas to continue execution")]
GasLimitExceeded,
#[display(fmt = "Unable to call a forbidden function")]
ForbiddenFunction,
#[display(fmt = "Trying to allocate more wasm program memory than allowed")]
ProgramAllocOutOfBounds,
#[display(fmt = "Sys-call unrecoverable error: {_0}")]
UnrecoverableExt(UnrecoverableExtError),
#[display(fmt = "{_0}")]
Panic(TrimmedString),
#[display(fmt = "Reason is unknown. Possibly `unreachable` instruction is occurred")]
Unknown,
}
#[derive(Debug, Default)]
pub struct SystemReservationContext {
pub current_reservation: Option<u64>,
pub previous_reservation: Option<u64>,
}
impl SystemReservationContext {
pub fn from_dispatch(dispatch: &IncomingDispatch) -> Self {
Self {
current_reservation: None,
previous_reservation: dispatch
.context()
.as_ref()
.and_then(|ctx| ctx.system_reservation()),
}
}
pub fn has_any(&self) -> bool {
self.current_reservation.is_some() || self.previous_reservation.is_some()
}
}
#[derive(Debug)]
pub struct ExtInfo {
pub gas_amount: GasAmount,
pub gas_reserver: GasReserver,
pub system_reservation_context: SystemReservationContext,
pub allocations: BTreeSet<WasmPage>,
pub pages_data: BTreeMap<GearPage, PageBuf>,
pub generated_dispatches: Vec<(Dispatch, u32, Option<ReservationId>)>,
pub awakening: Vec<(MessageId, u32)>,
pub reply_deposits: Vec<(MessageId, u64)>,
pub program_candidates_data: BTreeMap<CodeId, Vec<(MessageId, ProgramId)>>,
pub program_rents: BTreeMap<ProgramId, u32>,
pub context_store: ContextStore,
}
pub trait BackendExternalities: Externalities + CountersOwner {
fn into_ext_info(self, memory: &impl Memory) -> Result<ExtInfo, MemoryError>;
fn gas_amount(&self) -> GasAmount;
fn pre_process_memory_accesses(
reads: &[MemoryInterval],
writes: &[MemoryInterval],
gas_left: &mut GasLeft,
) -> Result<(), ProcessAccessError>;
}
pub trait BackendSyscallError: Sized {
fn into_termination_reason(self) -> TerminationReason;
fn into_run_fallible_error(self) -> RunFallibleError;
}
pub trait BackendAllocSyscallError: Sized {
type ExtError: BackendSyscallError;
fn into_backend_error(self) -> Result<Self::ExtError, Self>;
}
pub struct BackendReport<EnvMem, Ext>
where
Ext: Externalities,
{
pub termination_reason: TerminationReason,
pub memory_wrap: EnvMem,
pub ext: Ext,
}
#[derive(Debug, derive_more::Display)]
pub enum EnvironmentError<EnvSystemError: Display, PrepareMemoryError: Display> {
#[display(fmt = "Actor backend error: {_1}")]
Actor(GasAmount, String),
#[display(fmt = "System backend error: {_0}")]
System(EnvSystemError),
#[display(fmt = "Prepare error: {_1}")]
PrepareMemory(GasAmount, PrepareMemoryError),
}
impl<EnvSystemError: Display, PrepareMemoryError: Display>
EnvironmentError<EnvSystemError, PrepareMemoryError>
{
pub fn from_infallible(err: EnvironmentError<EnvSystemError, Infallible>) -> Self {
match err {
EnvironmentError::System(err) => Self::System(err),
EnvironmentError::PrepareMemory(_, err) => match err {},
EnvironmentError::Actor(gas_amount, s) => Self::Actor(gas_amount, s),
}
}
}
type EnvironmentBackendReport<Env, EntryPoint> =
BackendReport<<Env as Environment<EntryPoint>>::Memory, <Env as Environment<EntryPoint>>::Ext>;
pub type EnvironmentExecutionResult<PrepareMemoryError, Env, EntryPoint> = Result<
EnvironmentBackendReport<Env, EntryPoint>,
EnvironmentError<<Env as Environment<EntryPoint>>::SystemError, PrepareMemoryError>,
>;
pub trait Environment<EntryPoint = DispatchKind>: Sized
where
EntryPoint: WasmEntryPoint,
{
type Ext: BackendExternalities + 'static;
type Memory: Memory;
type SystemError: Debug + Display;
fn new(
ext: Self::Ext,
binary: &[u8],
entry_point: EntryPoint,
entries: BTreeSet<DispatchKind>,
mem_size: WasmPage,
) -> Result<Self, EnvironmentError<Self::SystemError, Infallible>>;
fn execute<PrepareMemory, PrepareMemoryError>(
self,
prepare_memory: PrepareMemory,
) -> EnvironmentExecutionResult<PrepareMemoryError, Self, EntryPoint>
where
PrepareMemory: FnOnce(
&mut Self::Memory,
Option<u32>,
GlobalsAccessConfig,
) -> Result<(), PrepareMemoryError>,
PrepareMemoryError: Display;
}
pub trait BackendState {
fn set_termination_reason(&mut self, reason: TerminationReason);
fn process_fallible_func_result<T: Sized>(
&mut self,
res: Result<T, RunFallibleError>,
) -> Result<Result<T, u32>, TerminationReason> {
match res {
Err(RunFallibleError::FallibleExt(ext_err)) => {
let code = ext_err.to_u32();
log::trace!(target: "syscalls", "fallible syscall error: {ext_err}");
Ok(Err(code))
}
Err(RunFallibleError::TerminationReason(reason)) => Err(reason),
Ok(res) => Ok(Ok(res)),
}
}
fn process_alloc_func_result<T: Sized, ExtAllocError: BackendAllocSyscallError>(
&mut self,
res: Result<T, ExtAllocError>,
) -> Result<Result<T, ExtAllocError>, TerminationReason> {
match res {
Ok(t) => Ok(Ok(t)),
Err(err) => match err.into_backend_error() {
Ok(ext_err) => Err(ext_err.into()),
Err(alloc_err) => Ok(Err(alloc_err)),
},
}
}
}
pub trait BackendTermination<Ext: BackendExternalities, EnvMem: Sized>: Sized {
fn into_parts(self) -> (Ext, EnvMem, TerminationReason);
fn terminate<T: Debug, WasmCallErr: Debug>(
self,
res: Result<T, WasmCallErr>,
gas: i64,
allowance: i64,
) -> (Ext, EnvMem, TerminationReason) {
log::trace!("Execution result = {res:?}");
let (mut ext, memory, termination_reason) = self.into_parts();
ext.set_gas_left((gas, allowance).into());
let termination_reason = if res.is_err() {
if matches!(
termination_reason,
TerminationReason::Actor(ActorTerminationReason::Success)
) {
ActorTerminationReason::Trap(TrapExplanation::Unknown).into()
} else {
termination_reason
}
} else if matches!(
termination_reason,
TerminationReason::Actor(ActorTerminationReason::Success)
) {
termination_reason
} else {
unreachable!(
"Termination reason is not success, but executor successfully ends execution"
)
};
(ext, memory, termination_reason)
}
}
#[macro_export]
macro_rules! syscall_args_trace {
($val:expr) => {
{
let s = stringify!($val);
if s.ends_with("_ptr") {
alloc::format!(", {} = {:#x?}", s, $val)
} else {
alloc::format!(", {} = {:?}", s, $val)
}
}
};
($val:expr, $($rest:expr),+) => {
{
let mut s = $crate::syscall_args_trace!($val);
s.push_str(&$crate::syscall_args_trace!($($rest),+));
s
}
};
}
#[macro_export]
macro_rules! syscall_trace {
($name:expr, $($args:expr),+) => {
{
$crate::log::trace!(target: "syscalls", "{}{}", $name, $crate::syscall_args_trace!($($args),+));
}
};
($name:expr) => {
{
$crate::log::trace!(target: "syscalls", "{}", $name);
}
}
}
#[cfg(test)]
mod tests;