1#![no_std]
22
23extern crate alloc;
24
25pub mod lazy_pages;
26
27mod utils;
28
29#[cfg(any(feature = "mock", test))]
30pub mod mock;
31
32pub mod funcs;
33pub mod memory;
34pub mod runtime;
35pub mod state;
36
37use crate::runtime::RunFallibleError;
38use actor_system_error::actor_system_error;
39use alloc::{
40 collections::{BTreeMap, BTreeSet},
41 string::String,
42 vec::Vec,
43};
44use core::{
45 convert::Infallible,
46 fmt::{Debug, Display},
47};
48use gear_core::{
49 env::Externalities,
50 gas::{ChargeError, CounterType, CountersOwner, GasAmount},
51 ids::{CodeId, MessageId, ProgramId, ReservationId},
52 memory::{Memory, MemoryError, MemoryInterval, PageBuf},
53 message::{
54 ContextStore, Dispatch, DispatchKind, IncomingDispatch, MessageWaitedType, WasmEntryPoint,
55 },
56 pages::{GearPage, WasmPage},
57 reservation::GasReserver,
58};
59use lazy_pages::GlobalsAccessConfig;
60use memory::ProcessAccessError;
61use scale_info::scale::{self, Decode, Encode};
62
63pub use crate::utils::LimitedStr;
64pub use log;
65
66pub const PTR_SPECIAL: u32 = u32::MAX;
67
68actor_system_error! {
69 pub type TerminationReason = ActorSystemError<ActorTerminationReason, SystemTerminationReason>;
70}
71
72#[derive(Debug, Clone, Eq, PartialEq, derive_more::From)]
73pub enum UndefinedTerminationReason {
74 Actor(ActorTerminationReason),
75 System(SystemTerminationReason),
76 ProcessAccessErrorResourcesExceed,
78}
79
80impl UndefinedTerminationReason {
81 pub fn define(self, current_counter: CounterType) -> TerminationReason {
82 match self {
83 Self::Actor(r) => r.into(),
84 Self::System(r) => r.into(),
85 Self::ProcessAccessErrorResourcesExceed => {
86 ActorTerminationReason::from(current_counter).into()
87 }
88 }
89 }
90}
91
92impl From<ChargeError> for UndefinedTerminationReason {
93 fn from(err: ChargeError) -> Self {
94 match err {
95 ChargeError::GasLimitExceeded => {
96 ActorTerminationReason::Trap(TrapExplanation::GasLimitExceeded).into()
97 }
98 ChargeError::GasAllowanceExceeded => {
99 ActorTerminationReason::GasAllowanceExceeded.into()
100 }
101 }
102 }
103}
104
105impl From<TrapExplanation> for UndefinedTerminationReason {
106 fn from(trap: TrapExplanation) -> Self {
107 ActorTerminationReason::Trap(trap).into()
108 }
109}
110
111impl<E: BackendSyscallError> From<E> for UndefinedTerminationReason {
112 fn from(err: E) -> Self {
113 err.into_termination_reason()
114 }
115}
116
117#[derive(Decode, Encode, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, derive_more::From)]
118#[codec(crate = scale)]
119pub enum ActorTerminationReason {
120 Exit(ProgramId),
121 Leave,
122 Success,
123 Wait(Option<u32>, MessageWaitedType),
124 GasAllowanceExceeded,
125 #[from]
126 Trap(TrapExplanation),
127}
128
129impl From<CounterType> for ActorTerminationReason {
130 fn from(counter_type: CounterType) -> Self {
131 match counter_type {
132 CounterType::GasLimit => Self::Trap(TrapExplanation::GasLimitExceeded),
133 CounterType::GasAllowance => Self::GasAllowanceExceeded,
134 }
135 }
136}
137
138#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display)]
144pub struct SystemTerminationReason;
145
146#[derive(
148 Decode,
149 Encode,
150 Debug,
151 Clone,
152 Eq,
153 PartialEq,
154 PartialOrd,
155 Ord,
156 derive_more::Display,
157 derive_more::From,
158)]
159pub enum UnrecoverableExecutionError {
160 #[display(fmt = "Invalid debug string passed in `gr_debug` sys-call")]
161 InvalidDebugString,
162 #[display(fmt = "Not enough gas for operation")]
163 NotEnoughGas,
164 #[display(fmt = "Length is overflowed to read payload")]
165 TooBigReadLen,
166 #[display(fmt = "Cannot take data in payload range from message with size")]
167 ReadWrongRange,
168}
169
170#[derive(
172 Decode,
173 Encode,
174 Debug,
175 Clone,
176 Eq,
177 PartialEq,
178 PartialOrd,
179 Ord,
180 derive_more::Display,
181 derive_more::From,
182)]
183pub enum UnrecoverableMemoryError {
184 #[display(fmt = "Trying to access memory outside wasm program memory")]
186 AccessOutOfBounds,
187 #[display(fmt = "Trying to allocate more memory in block-chain runtime than allowed")]
189 RuntimeAllocOutOfBounds,
190}
191
192#[derive(
194 Decode,
195 Encode,
196 Debug,
197 Clone,
198 Eq,
199 PartialEq,
200 PartialOrd,
201 Ord,
202 derive_more::Display,
203 derive_more::From,
204)]
205pub enum UnrecoverableWaitError {
206 #[display(fmt = "Waiting duration cannot be zero")]
208 ZeroDuration,
209 #[display(fmt = "`wait()` is not allowed after reply sent")]
211 WaitAfterReply,
212}
213
214#[derive(
215 Decode,
216 Encode,
217 Debug,
218 Clone,
219 Eq,
220 PartialEq,
221 PartialOrd,
222 Ord,
223 derive_more::Display,
224 derive_more::From,
225)]
226pub enum UnrecoverableExtError {
227 #[display(fmt = "Execution error: {_0}")]
228 Execution(UnrecoverableExecutionError),
229 #[display(fmt = "Memory error: {_0}")]
230 Memory(UnrecoverableMemoryError),
231 #[display(fmt = "Waiting error: {_0}")]
232 Wait(UnrecoverableWaitError),
233}
234
235#[derive(
236 Decode,
237 Encode,
238 Debug,
239 Clone,
240 PartialEq,
241 Eq,
242 PartialOrd,
243 Ord,
244 derive_more::Display,
245 derive_more::From,
246)]
247#[codec(crate = scale)]
248pub enum TrapExplanation {
249 #[display(fmt = "Not enough gas to continue execution")]
251 GasLimitExceeded,
252 #[display(fmt = "Unable to call a forbidden function")]
254 ForbiddenFunction,
255 #[display(fmt = "Trying to allocate more wasm program memory than allowed")]
258 ProgramAllocOutOfBounds,
259 #[display(fmt = "Sys-call unrecoverable error: {_0}")]
260 UnrecoverableExt(UnrecoverableExtError),
261 #[display(fmt = "{_0}")]
262 Panic(LimitedStr<'static>),
263 #[display(fmt = "Reason is unknown. Possibly `unreachable` instruction is occurred")]
264 Unknown,
265}
266
267#[derive(Debug, Default)]
268pub struct SystemReservationContext {
269 pub current_reservation: Option<u64>,
271 pub previous_reservation: Option<u64>,
273}
274
275impl SystemReservationContext {
276 pub fn from_dispatch(dispatch: &IncomingDispatch) -> Self {
277 Self {
278 current_reservation: None,
279 previous_reservation: dispatch
280 .context()
281 .as_ref()
282 .and_then(|ctx| ctx.system_reservation()),
283 }
284 }
285
286 pub fn has_any(&self) -> bool {
287 self.current_reservation.is_some() || self.previous_reservation.is_some()
288 }
289}
290
291#[derive(Debug)]
292pub struct ExtInfo {
293 pub gas_amount: GasAmount,
294 pub gas_reserver: GasReserver,
295 pub system_reservation_context: SystemReservationContext,
296 pub allocations: BTreeSet<WasmPage>,
297 pub pages_data: BTreeMap<GearPage, PageBuf>,
298 pub generated_dispatches: Vec<(Dispatch, u32, Option<ReservationId>)>,
299 pub awakening: Vec<(MessageId, u32)>,
300 pub reply_deposits: Vec<(MessageId, u64)>,
301 pub program_candidates_data: BTreeMap<CodeId, Vec<(MessageId, ProgramId)>>,
302 pub program_rents: BTreeMap<ProgramId, u32>,
303 pub context_store: ContextStore,
304}
305
306pub trait BackendExternalities: Externalities + CountersOwner {
308 fn into_ext_info(self, memory: &impl Memory) -> Result<ExtInfo, MemoryError>;
309
310 fn gas_amount(&self) -> GasAmount;
311
312 fn pre_process_memory_accesses(
314 reads: &[MemoryInterval],
315 writes: &[MemoryInterval],
316 gas_counter: &mut u64,
317 ) -> Result<(), ProcessAccessError>;
318}
319
320pub trait BackendSyscallError: Sized {
323 fn into_termination_reason(self) -> UndefinedTerminationReason;
324
325 fn into_run_fallible_error(self) -> RunFallibleError;
326}
327
328pub trait BackendAllocSyscallError: Sized {
333 type ExtError: BackendSyscallError;
334
335 fn into_backend_error(self) -> Result<Self::ExtError, Self>;
336}
337
338pub struct BackendReport<EnvMem, Ext>
339where
340 Ext: Externalities,
341{
342 pub termination_reason: TerminationReason,
343 pub memory_wrap: EnvMem,
344 pub ext: Ext,
345}
346
347#[derive(Debug, derive_more::Display)]
348pub enum EnvironmentError<EnvSystemError: Display, PrepareMemoryError: Display> {
349 #[display(fmt = "Actor backend error: {_1}")]
350 Actor(GasAmount, String),
351 #[display(fmt = "System backend error: {_0}")]
352 System(EnvSystemError),
353 #[display(fmt = "Prepare error: {_1}")]
354 PrepareMemory(GasAmount, PrepareMemoryError),
355}
356
357impl<EnvSystemError: Display, PrepareMemoryError: Display>
358 EnvironmentError<EnvSystemError, PrepareMemoryError>
359{
360 pub fn from_infallible(err: EnvironmentError<EnvSystemError, Infallible>) -> Self {
361 match err {
362 EnvironmentError::System(err) => Self::System(err),
363 EnvironmentError::PrepareMemory(_, err) => match err {},
364 EnvironmentError::Actor(gas_amount, s) => Self::Actor(gas_amount, s),
365 }
366 }
367}
368
369type EnvironmentBackendReport<Env, EntryPoint> =
370 BackendReport<<Env as Environment<EntryPoint>>::Memory, <Env as Environment<EntryPoint>>::Ext>;
371
372pub type EnvironmentExecutionResult<PrepareMemoryError, Env, EntryPoint> = Result<
373 EnvironmentBackendReport<Env, EntryPoint>,
374 EnvironmentError<<Env as Environment<EntryPoint>>::SystemError, PrepareMemoryError>,
375>;
376
377pub trait Environment<EntryPoint = DispatchKind>: Sized
378where
379 EntryPoint: WasmEntryPoint,
380{
381 type Ext: BackendExternalities + 'static;
382
383 type Memory: Memory;
385
386 type SystemError: Debug + Display;
391
392 fn new(
397 ext: Self::Ext,
398 binary: &[u8],
399 entry_point: EntryPoint,
400 entries: BTreeSet<DispatchKind>,
401 mem_size: WasmPage,
402 ) -> Result<Self, EnvironmentError<Self::SystemError, Infallible>>;
403
404 fn execute<PrepareMemory, PrepareMemoryError>(
406 self,
407 prepare_memory: PrepareMemory,
408 ) -> EnvironmentExecutionResult<PrepareMemoryError, Self, EntryPoint>
409 where
410 PrepareMemory: FnOnce(
411 &mut Self::Memory,
412 Option<u32>,
413 GlobalsAccessConfig,
414 ) -> Result<(), PrepareMemoryError>,
415 PrepareMemoryError: Display;
416}
417
418pub trait BackendState {
419 fn set_termination_reason(&mut self, reason: UndefinedTerminationReason);
421
422 fn process_fallible_func_result<T: Sized>(
424 &mut self,
425 res: Result<T, RunFallibleError>,
426 ) -> Result<Result<T, u32>, UndefinedTerminationReason> {
427 match res {
428 Err(RunFallibleError::FallibleExt(ext_err)) => {
429 let code = ext_err.to_u32();
430 log::trace!(target: "syscalls", "fallible syscall error: {ext_err}");
431 Ok(Err(code))
432 }
433 Err(RunFallibleError::UndefinedTerminationReason(reason)) => Err(reason),
434 Ok(res) => Ok(Ok(res)),
435 }
436 }
437
438 fn process_alloc_func_result<T: Sized, ExtAllocError: BackendAllocSyscallError>(
440 &mut self,
441 res: Result<T, ExtAllocError>,
442 ) -> Result<Result<T, ExtAllocError>, UndefinedTerminationReason> {
443 match res {
444 Ok(t) => Ok(Ok(t)),
445 Err(err) => match err.into_backend_error() {
446 Ok(ext_err) => Err(ext_err.into()),
447 Err(alloc_err) => Ok(Err(alloc_err)),
448 },
449 }
450 }
451}
452
453pub trait BackendTermination<Ext: BackendExternalities>: Sized {
459 fn into_parts(self) -> (Ext, UndefinedTerminationReason);
462
463 fn terminate<T: Debug, WasmCallErr: Debug>(
479 self,
480 res: Result<T, WasmCallErr>,
481 gas: u64,
482 ) -> (Ext, TerminationReason) {
483 log::trace!("Execution result = {res:?}");
484
485 let (mut ext, termination_reason) = self.into_parts();
486 let termination_reason = termination_reason.define(ext.current_counter_type());
487
488 ext.decrease_current_counter_to(gas);
489
490 let termination_reason = if res.is_err() {
491 if matches!(
492 termination_reason,
493 TerminationReason::Actor(ActorTerminationReason::Success)
494 ) {
495 ActorTerminationReason::Trap(TrapExplanation::Unknown).into()
496 } else {
497 termination_reason
498 }
499 } else if matches!(
500 termination_reason,
501 TerminationReason::Actor(ActorTerminationReason::Success)
502 ) {
503 termination_reason
504 } else {
505 unreachable!(
506 "Termination reason is not success, but executor successfully ends execution"
507 )
508 };
509
510 (ext, termination_reason)
511 }
512}
513
514#[macro_export]
515macro_rules! syscall_args_trace {
516 ($val:expr) => {
517 {
518 let s = stringify!($val);
519 if s.ends_with("_ptr") {
520 alloc::format!(", {} = {:#x?}", s, $val)
521 } else {
522 alloc::format!(", {} = {:?}", s, $val)
523 }
524 }
525 };
526 ($val:expr, $($rest:expr),+) => {
527 {
528 let mut s = $crate::syscall_args_trace!($val);
529 s.push_str(&$crate::syscall_args_trace!($($rest),+));
530 s
531 }
532 };
533}
534
535#[macro_export]
536macro_rules! syscall_trace {
537 ($name:expr, $($args:expr),+) => {
538 {
539 $crate::log::trace!(target: "syscalls", "{}{}", $name, $crate::syscall_args_trace!($($args),+));
540 }
541 };
542 ($name:expr) => {
543 {
544 $crate::log::trace!(target: "syscalls", "{}", $name);
545 }
546 }
547}
548
549#[cfg(test)]
550mod tests;