1use crate::configs::{BlockInfo, PageCosts};
20use alloc::{
21 collections::{BTreeMap, BTreeSet},
22 vec::Vec,
23};
24use gear_backend_common::{
25 lazy_pages::{GlobalsAccessConfig, LazyPagesWeights, Status},
26 memory::ProcessAccessError,
27 runtime::RunFallibleError,
28 ActorTerminationReason, BackendAllocSyscallError, BackendExternalities, BackendSyscallError,
29 ExtInfo, SystemReservationContext, TrapExplanation, UndefinedTerminationReason,
30 UnrecoverableExecutionError, UnrecoverableExtError as UnrecoverableExtErrorCore,
31 UnrecoverableWaitError,
32};
33#[cfg(any(feature = "mock", test))]
34use gear_core::message::{ContextSettings, IncomingDispatch};
35use gear_core::{
36 costs::{HostFnWeights, RuntimeCosts},
37 env::{Externalities, PayloadSliceLock, UnlockPayloadBound},
38 gas::{
39 ChargeError, ChargeResult, CounterType, CountersOwner, GasAllowanceCounter, GasAmount,
40 GasCounter, GasLeft, Token, ValueCounter,
41 },
42 ids::{CodeId, MessageId, ProgramId, ReservationId},
43 memory::{
44 AllocError, AllocationsContext, GrowHandler, Memory, MemoryError, MemoryInterval,
45 NoopGrowHandler, PageBuf,
46 },
47 message::{
48 ContextOutcomeDrain, GasLimit, HandlePacket, InitPacket, MessageContext, Packet,
49 ReplyPacket,
50 },
51 pages::{GearPage, PageU32Size, WasmPage},
52 reservation::GasReserver,
53};
54use gear_core_errors::{
55 ExecutionError as FallibleExecutionError, ExtError as FallibleExtErrorCore, MessageError,
56 ProgramRentError, ReplyCode, ReservationError, SignalCode,
57};
58use gear_wasm_instrument::syscalls::SysCallName;
59
60pub struct ProcessorContext {
62 pub gas_counter: GasCounter,
64 pub gas_allowance_counter: GasAllowanceCounter,
66 pub gas_reserver: GasReserver,
68 pub system_reservation: Option<u64>,
70 pub value_counter: ValueCounter,
72 pub allocations_context: AllocationsContext,
74 pub message_context: MessageContext,
76 pub block_info: BlockInfo,
78 pub max_pages: WasmPage,
80 pub page_costs: PageCosts,
82 pub existential_deposit: u128,
84 pub program_id: ProgramId,
86 pub program_candidates_data: BTreeMap<CodeId, Vec<(MessageId, ProgramId)>>,
89 pub program_rents: BTreeMap<ProgramId, u32>,
91 pub host_fn_weights: HostFnWeights,
93 pub forbidden_funcs: BTreeSet<SysCallName>,
95 pub mailbox_threshold: u64,
97 pub waitlist_cost: u64,
99 pub dispatch_hold_cost: u64,
101 pub reserve_for: u32,
103 pub reservation: u64,
105 pub random_data: (Vec<u8>, u32),
107 pub rent_cost: u128,
109}
110
111#[cfg(any(feature = "mock", test))]
112impl ProcessorContext {
113 pub fn new_mock() -> ProcessorContext {
115 ProcessorContext {
116 gas_counter: GasCounter::new(0),
117 gas_allowance_counter: GasAllowanceCounter::new(0),
118 gas_reserver: GasReserver::new(
119 &<IncomingDispatch as Default>::default(),
120 Default::default(),
121 Default::default(),
122 ),
123 system_reservation: None,
124 value_counter: ValueCounter::new(0),
125 allocations_context: AllocationsContext::new(
126 Default::default(),
127 Default::default(),
128 Default::default(),
129 ),
130 message_context: MessageContext::new(
131 Default::default(),
132 Default::default(),
133 ContextSettings::new(0, 0, 0, 0, 0, 0),
134 ),
135 block_info: Default::default(),
136 max_pages: 512.into(),
137 page_costs: Default::default(),
138 existential_deposit: 0,
139 program_id: Default::default(),
140 program_candidates_data: Default::default(),
141 program_rents: Default::default(),
142 host_fn_weights: Default::default(),
143 forbidden_funcs: Default::default(),
144 mailbox_threshold: 0,
145 waitlist_cost: 0,
146 dispatch_hold_cost: 0,
147 reserve_for: 0,
148 reservation: 0,
149 random_data: ([0u8; 32].to_vec(), 0),
150 rent_cost: 0,
151 }
152 }
153}
154
155pub trait ProcessorExternalities {
158 const LAZY_PAGES_ENABLED: bool;
160
161 fn new(context: ProcessorContext) -> Self;
163
164 fn lazy_pages_init_for_program(
166 mem: &mut impl Memory,
167 prog_id: ProgramId,
168 stack_end: Option<WasmPage>,
169 globals_config: GlobalsAccessConfig,
170 lazy_pages_weights: LazyPagesWeights,
171 );
172
173 fn lazy_pages_post_execution_actions(mem: &mut impl Memory);
175
176 fn lazy_pages_status() -> Status;
178}
179
180#[derive(Debug, Clone, Eq, PartialEq, derive_more::From)]
182pub enum UnrecoverableExtError {
183 Core(UnrecoverableExtErrorCore),
185 Charge(ChargeError),
187}
188
189impl From<UnrecoverableExecutionError> for UnrecoverableExtError {
190 fn from(err: UnrecoverableExecutionError) -> UnrecoverableExtError {
191 Self::Core(UnrecoverableExtErrorCore::from(err))
192 }
193}
194
195impl From<UnrecoverableWaitError> for UnrecoverableExtError {
196 fn from(err: UnrecoverableWaitError) -> UnrecoverableExtError {
197 Self::Core(UnrecoverableExtErrorCore::from(err))
198 }
199}
200
201impl BackendSyscallError for UnrecoverableExtError {
202 fn into_termination_reason(self) -> UndefinedTerminationReason {
203 match self {
204 UnrecoverableExtError::Core(err) => {
205 ActorTerminationReason::Trap(TrapExplanation::UnrecoverableExt(err)).into()
206 }
207 UnrecoverableExtError::Charge(err) => err.into(),
208 }
209 }
210
211 fn into_run_fallible_error(self) -> RunFallibleError {
212 RunFallibleError::UndefinedTerminationReason(self.into_termination_reason())
213 }
214}
215
216#[derive(Debug, Clone, Eq, PartialEq, derive_more::From)]
218pub enum FallibleExtError {
219 Core(FallibleExtErrorCore),
221 ForbiddenFunction,
223 Charge(ChargeError),
225}
226
227impl From<MessageError> for FallibleExtError {
228 fn from(err: MessageError) -> Self {
229 Self::Core(FallibleExtErrorCore::Message(err))
230 }
231}
232
233impl From<FallibleExecutionError> for FallibleExtError {
234 fn from(err: FallibleExecutionError) -> Self {
235 Self::Core(FallibleExtErrorCore::Execution(err))
236 }
237}
238
239impl From<ProgramRentError> for FallibleExtError {
240 fn from(err: ProgramRentError) -> Self {
241 Self::Core(FallibleExtErrorCore::ProgramRent(err))
242 }
243}
244
245impl From<ReservationError> for FallibleExtError {
246 fn from(err: ReservationError) -> Self {
247 Self::Core(FallibleExtErrorCore::Reservation(err))
248 }
249}
250
251impl From<FallibleExtError> for RunFallibleError {
252 fn from(err: FallibleExtError) -> Self {
253 match err {
254 FallibleExtError::Core(err) => RunFallibleError::FallibleExt(err),
255 FallibleExtError::ForbiddenFunction => {
256 RunFallibleError::UndefinedTerminationReason(UndefinedTerminationReason::Actor(
257 ActorTerminationReason::Trap(TrapExplanation::ForbiddenFunction),
258 ))
259 }
260 FallibleExtError::Charge(err) => {
261 RunFallibleError::UndefinedTerminationReason(UndefinedTerminationReason::from(err))
262 }
263 }
264 }
265}
266
267#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display, derive_more::From)]
269pub enum AllocExtError {
270 #[display(fmt = "{_0}")]
272 Charge(ChargeError),
273 #[display(fmt = "{_0}")]
275 Alloc(AllocError),
276}
277
278impl BackendAllocSyscallError for AllocExtError {
279 type ExtError = UnrecoverableExtError;
280
281 fn into_backend_error(self) -> Result<Self::ExtError, Self> {
282 match self {
283 Self::Charge(err) => Ok(err.into()),
284 err => Err(err),
285 }
286 }
287}
288
289pub struct Ext {
291 pub context: ProcessorContext,
293 pub current_counter: CounterType,
295 outgoing_gasless: u64,
299}
300
301impl ProcessorExternalities for Ext {
303 const LAZY_PAGES_ENABLED: bool = false;
304
305 fn new(context: ProcessorContext) -> Self {
306 let current_counter = if context.gas_counter.left() <= context.gas_allowance_counter.left()
307 {
308 CounterType::GasLimit
309 } else {
310 CounterType::GasAllowance
311 };
312
313 Self {
314 context,
315 current_counter,
316 outgoing_gasless: 0,
317 }
318 }
319
320 fn lazy_pages_init_for_program(
321 _mem: &mut impl Memory,
322 _prog_id: ProgramId,
323 _stack_end: Option<WasmPage>,
324 _globals_config: GlobalsAccessConfig,
325 _lazy_pages_weights: LazyPagesWeights,
326 ) {
327 unreachable!("Must not be called: lazy-pages is unsupported by this ext")
328 }
329
330 fn lazy_pages_post_execution_actions(_mem: &mut impl Memory) {
331 unreachable!("Must not be called: lazy-pages is unsupported by this ext")
332 }
333
334 fn lazy_pages_status() -> Status {
335 unreachable!("Must not be called: lazy-pages is unsupported by this ext")
336 }
337}
338
339impl BackendExternalities for Ext {
340 fn into_ext_info(self, memory: &impl Memory) -> Result<ExtInfo, MemoryError> {
341 let pages_for_data =
342 |static_pages: WasmPage, allocations: &BTreeSet<WasmPage>| -> Vec<GearPage> {
343 static_pages
344 .iter_from_zero()
345 .chain(allocations.iter().copied())
346 .flat_map(|p| p.to_pages_iter())
347 .collect()
348 };
349
350 self.into_ext_info_inner(memory, pages_for_data)
351 }
352
353 fn gas_amount(&self) -> GasAmount {
354 self.context.gas_counter.to_amount()
355 }
356
357 fn pre_process_memory_accesses(
358 _reads: &[MemoryInterval],
359 _writes: &[MemoryInterval],
360 _gas_counter: &mut u64,
361 ) -> Result<(), ProcessAccessError> {
362 Ok(())
363 }
364}
365
366impl Ext {
367 fn check_message_value(&mut self, message_value: u128) -> Result<(), FallibleExtError> {
368 let existential_deposit = self.context.existential_deposit;
369 if message_value != 0 && message_value < existential_deposit {
371 Err(MessageError::InsufficientValue.into())
372 } else {
373 Ok(())
374 }
375 }
376
377 fn check_gas_limit(
378 &mut self,
379 gas_limit: Option<GasLimit>,
380 ) -> Result<GasLimit, FallibleExtError> {
381 let mailbox_threshold = self.context.mailbox_threshold;
382 let gas_limit = gas_limit.unwrap_or(0);
383
384 if gas_limit < mailbox_threshold && gas_limit != 0 {
386 Err(MessageError::InsufficientGasLimit.into())
387 } else {
388 Ok(gas_limit)
389 }
390 }
391
392 fn reduce_gas(&mut self, gas_limit: GasLimit) -> Result<(), FallibleExtError> {
393 if self.context.gas_counter.reduce(gas_limit) != ChargeResult::Enough {
394 Err(FallibleExecutionError::NotEnoughGas.into())
395 } else {
396 Ok(())
397 }
398 }
399
400 fn charge_message_value(&mut self, message_value: u128) -> Result<(), FallibleExtError> {
401 if self.context.value_counter.reduce(message_value) != ChargeResult::Enough {
402 Err(FallibleExecutionError::NotEnoughValue.into())
403 } else {
404 Ok(())
405 }
406 }
407
408 fn safe_gasfull_sends<T: Packet>(&mut self, packet: &T) -> Result<(), FallibleExtError> {
410 let outgoing_gasless = self.outgoing_gasless;
411
412 match packet.gas_limit() {
413 Some(x) if x != 0 => {
414 self.outgoing_gasless = 0;
415
416 let prev_gasless_fee =
417 outgoing_gasless.saturating_mul(self.context.mailbox_threshold);
418
419 self.reduce_gas(prev_gasless_fee)?;
420 }
421 None => self.outgoing_gasless = outgoing_gasless.saturating_add(1),
422 _ => {}
423 };
424
425 Ok(())
426 }
427
428 fn charge_expiring_resources<T: Packet>(
429 &mut self,
430 packet: &T,
431 check_gas_limit: bool,
432 ) -> Result<(), FallibleExtError> {
433 self.check_message_value(packet.value())?;
434 let gas_limit = if check_gas_limit {
436 self.check_gas_limit(packet.gas_limit())?
437 } else {
438 packet.gas_limit().unwrap_or(0)
439 };
440 self.reduce_gas(gas_limit)?;
441 self.charge_message_value(packet.value())?;
442 Ok(())
443 }
444
445 fn check_forbidden_destination(&mut self, id: ProgramId) -> Result<(), FallibleExtError> {
446 if id == ProgramId::SYSTEM {
447 Err(FallibleExtError::ForbiddenFunction)
448 } else {
449 Ok(())
450 }
451 }
452
453 fn charge_sending_fee(&mut self, delay: u32) -> Result<(), ChargeError> {
454 if delay == 0 {
455 self.charge_gas_if_enough(self.context.message_context.settings().sending_fee())
456 } else {
457 self.charge_gas_if_enough(
458 self.context
459 .message_context
460 .settings()
461 .scheduled_sending_fee(),
462 )
463 }
464 }
465
466 fn charge_for_dispatch_stash_hold(&mut self, delay: u32) -> Result<(), FallibleExtError> {
467 if delay != 0 {
468 let cost_per_block = self.context.dispatch_hold_cost;
471 let waiting_reserve = (self.context.reserve_for as u64)
472 .saturating_add(delay as u64)
473 .saturating_mul(cost_per_block);
474
475 if self.context.gas_counter.reduce(waiting_reserve) != ChargeResult::Enough {
477 return Err(MessageError::InsufficientGasForDelayedSending.into());
478 }
479 }
480 Ok(())
481 }
482
483 fn charge_gas_if_enough(
484 gas_counter: &mut GasCounter,
485 gas_allowance_counter: &mut GasAllowanceCounter,
486 amount: u64,
487 ) -> Result<(), ChargeError> {
488 if gas_counter.charge_if_enough(amount) != ChargeResult::Enough {
489 return Err(ChargeError::GasLimitExceeded);
490 }
491 if gas_allowance_counter.charge_if_enough(amount) != ChargeResult::Enough {
492 return Err(ChargeError::GasAllowanceExceeded);
496 }
497 Ok(())
498 }
499}
500
501impl CountersOwner for Ext {
502 fn charge_gas_runtime(&mut self, cost: RuntimeCosts) -> Result<(), ChargeError> {
503 let token = cost.token(&self.context.host_fn_weights);
504 let common_charge = self.context.gas_counter.charge(token);
505 let allowance_charge = self.context.gas_allowance_counter.charge(token);
506 match (common_charge, allowance_charge) {
507 (ChargeResult::NotEnough, _) => Err(ChargeError::GasLimitExceeded),
508 (ChargeResult::Enough, ChargeResult::NotEnough) => {
509 Err(ChargeError::GasAllowanceExceeded)
510 }
511 (ChargeResult::Enough, ChargeResult::Enough) => Ok(()),
512 }
513 }
514
515 fn charge_gas_runtime_if_enough(&mut self, cost: RuntimeCosts) -> Result<(), ChargeError> {
516 let amount = cost.token(&self.context.host_fn_weights).weight();
517 self.charge_gas_if_enough(amount)
518 }
519
520 fn charge_gas_if_enough(&mut self, amount: u64) -> Result<(), ChargeError> {
521 Ext::charge_gas_if_enough(
522 &mut self.context.gas_counter,
523 &mut self.context.gas_allowance_counter,
524 amount,
525 )
526 }
527
528 fn gas_left(&self) -> GasLeft {
529 (
530 self.context.gas_counter.left(),
531 self.context.gas_allowance_counter.left(),
532 )
533 .into()
534 }
535
536 fn current_counter_type(&self) -> CounterType {
537 self.current_counter
538 }
539
540 fn decrease_current_counter_to(&mut self, amount: u64) {
541 if self.current_counter_value() <= amount {
550 log::trace!("Skipped decrease to global value");
551 return;
552 }
553
554 let GasLeft { gas, allowance } = self.gas_left();
555
556 let diff = match self.current_counter_type() {
557 CounterType::GasLimit => gas.checked_sub(amount),
558 CounterType::GasAllowance => allowance.checked_sub(amount),
559 }
560 .unwrap_or_else(|| unreachable!("Checked above"));
561
562 if self.context.gas_counter.charge(diff) == ChargeResult::NotEnough {
563 unreachable!("Tried to set gas limit left bigger than before")
564 }
565
566 if self.context.gas_allowance_counter.charge(diff) == ChargeResult::NotEnough {
567 unreachable!("Tried to set gas allowance left bigger than before")
568 }
569 }
570
571 fn define_current_counter(&mut self) -> u64 {
572 let GasLeft { gas, allowance } = self.gas_left();
573
574 if gas <= allowance {
575 self.current_counter = CounterType::GasLimit;
576 gas
577 } else {
578 self.current_counter = CounterType::GasAllowance;
579 allowance
580 }
581 }
582}
583
584impl Externalities for Ext {
585 type UnrecoverableError = UnrecoverableExtError;
586 type FallibleError = FallibleExtError;
587 type AllocError = AllocExtError;
588
589 fn alloc(
590 &mut self,
591 pages_num: u32,
592 mem: &mut impl Memory,
593 ) -> Result<WasmPage, Self::AllocError> {
594 self.alloc_inner::<NoopGrowHandler>(pages_num, mem)
595 }
596
597 fn free(&mut self, page: WasmPage) -> Result<(), Self::AllocError> {
598 self.context
599 .allocations_context
600 .free(page)
601 .map_err(Into::into)
602 }
603
604 fn block_height(&self) -> Result<u32, Self::UnrecoverableError> {
605 Ok(self.context.block_info.height)
606 }
607
608 fn block_timestamp(&self) -> Result<u64, Self::UnrecoverableError> {
609 Ok(self.context.block_info.timestamp)
610 }
611
612 fn send_init(&mut self) -> Result<u32, Self::FallibleError> {
613 let handle = self.context.message_context.send_init()?;
614 Ok(handle)
615 }
616
617 fn send_push(&mut self, handle: u32, buffer: &[u8]) -> Result<(), Self::FallibleError> {
618 self.context.message_context.send_push(handle, buffer)?;
619 Ok(())
620 }
621
622 fn send_push_input(
623 &mut self,
624 handle: u32,
625 offset: u32,
626 len: u32,
627 ) -> Result<(), Self::FallibleError> {
628 let range = self.context.message_context.check_input_range(offset, len);
629 self.charge_gas_runtime_if_enough(RuntimeCosts::SendPushInputPerByte(range.len()))?;
630
631 self.context
632 .message_context
633 .send_push_input(handle, range)?;
634
635 Ok(())
636 }
637
638 fn send_commit(
639 &mut self,
640 handle: u32,
641 msg: HandlePacket,
642 delay: u32,
643 ) -> Result<MessageId, Self::FallibleError> {
644 self.check_forbidden_destination(msg.destination())?;
645 self.safe_gasfull_sends(&msg)?;
646 self.charge_expiring_resources(&msg, true)?;
647 self.charge_sending_fee(delay)?;
648
649 self.charge_for_dispatch_stash_hold(delay)?;
650
651 let msg_id = self
652 .context
653 .message_context
654 .send_commit(handle, msg, delay, None)?;
655
656 Ok(msg_id)
657 }
658
659 fn reservation_send_commit(
660 &mut self,
661 id: ReservationId,
662 handle: u32,
663 msg: HandlePacket,
664 delay: u32,
665 ) -> Result<MessageId, Self::FallibleError> {
666 self.check_forbidden_destination(msg.destination())?;
667 self.check_message_value(msg.value())?;
668 self.check_gas_limit(msg.gas_limit())?;
669 self.charge_message_value(msg.value())?;
671 self.charge_sending_fee(delay)?;
672
673 self.charge_for_dispatch_stash_hold(delay)?;
674
675 self.context.gas_reserver.mark_used(id)?;
676
677 let msg_id = self
678 .context
679 .message_context
680 .send_commit(handle, msg, delay, Some(id))?;
681 Ok(msg_id)
682 }
683
684 fn reply_push(&mut self, buffer: &[u8]) -> Result<(), Self::FallibleError> {
685 self.context.message_context.reply_push(buffer)?;
686 Ok(())
687 }
688
689 fn reply_commit(&mut self, msg: ReplyPacket) -> Result<MessageId, Self::FallibleError> {
691 self.check_forbidden_destination(self.context.message_context.reply_destination())?;
692 self.safe_gasfull_sends(&msg)?;
693 self.charge_expiring_resources(&msg, false)?;
694 self.charge_sending_fee(0)?;
695
696 let msg_id = self.context.message_context.reply_commit(msg, None)?;
697 Ok(msg_id)
698 }
699
700 fn reservation_reply_commit(
701 &mut self,
702 id: ReservationId,
703 msg: ReplyPacket,
704 ) -> Result<MessageId, Self::FallibleError> {
705 self.check_forbidden_destination(self.context.message_context.reply_destination())?;
706 self.check_message_value(msg.value())?;
707 self.charge_message_value(msg.value())?;
709 self.charge_sending_fee(0)?;
710
711 self.context.gas_reserver.mark_used(id)?;
712
713 let msg_id = self.context.message_context.reply_commit(msg, Some(id))?;
714 Ok(msg_id)
715 }
716
717 fn reply_to(&self) -> Result<MessageId, Self::FallibleError> {
718 self.context
719 .message_context
720 .current()
721 .details()
722 .and_then(|d| d.to_reply_details().map(|d| d.to_message_id()))
723 .ok_or_else(|| FallibleExecutionError::NoReplyContext.into())
724 }
725
726 fn signal_from(&self) -> Result<MessageId, Self::FallibleError> {
727 self.context
728 .message_context
729 .current()
730 .details()
731 .and_then(|d| d.to_signal_details().map(|d| d.to_message_id()))
732 .ok_or_else(|| FallibleExecutionError::NoSignalContext.into())
733 }
734
735 fn reply_push_input(&mut self, offset: u32, len: u32) -> Result<(), Self::FallibleError> {
736 let range = self.context.message_context.check_input_range(offset, len);
737 self.charge_gas_runtime_if_enough(RuntimeCosts::ReplyPushInputPerByte(range.len()))?;
738
739 self.context.message_context.reply_push_input(range)?;
740
741 Ok(())
742 }
743
744 fn source(&self) -> Result<ProgramId, Self::UnrecoverableError> {
745 Ok(self.context.message_context.current().source())
746 }
747
748 fn reply_code(&self) -> Result<ReplyCode, Self::FallibleError> {
749 self.context
750 .message_context
751 .current()
752 .details()
753 .and_then(|d| d.to_reply_details().map(|d| d.to_reply_code()))
754 .ok_or_else(|| FallibleExecutionError::NoReplyContext.into())
755 }
756
757 fn signal_code(&self) -> Result<SignalCode, Self::FallibleError> {
758 self.context
759 .message_context
760 .current()
761 .details()
762 .and_then(|d| d.to_signal_details().map(|d| d.to_signal_code()))
763 .ok_or_else(|| FallibleExecutionError::NoSignalContext.into())
764 }
765
766 fn message_id(&self) -> Result<MessageId, Self::UnrecoverableError> {
767 Ok(self.context.message_context.current().id())
768 }
769
770 fn pay_program_rent(
771 &mut self,
772 program_id: ProgramId,
773 rent: u128,
774 ) -> Result<(u128, u32), Self::FallibleError> {
775 if self.context.rent_cost == 0 {
776 return Ok((rent, 0));
777 }
778
779 let block_count = u32::try_from(rent / self.context.rent_cost).unwrap_or(u32::MAX);
780 let old_paid_blocks = self
781 .context
782 .program_rents
783 .get(&program_id)
784 .copied()
785 .unwrap_or(0);
786
787 let (paid_blocks, blocks_to_pay) = match old_paid_blocks.overflowing_add(block_count) {
788 (count, false) => (count, block_count),
789 (_, true) => return Err(ProgramRentError::MaximumBlockCountPaid.into()),
790 };
791
792 if blocks_to_pay == 0 {
793 return Ok((rent, 0));
794 }
795
796 let cost = self.context.rent_cost.saturating_mul(blocks_to_pay.into());
797 match self.context.value_counter.reduce(cost) {
798 ChargeResult::Enough => {
799 self.context.program_rents.insert(program_id, paid_blocks);
800 }
801 ChargeResult::NotEnough => return Err(FallibleExecutionError::NotEnoughValue.into()),
802 }
803
804 Ok((rent.saturating_sub(cost), blocks_to_pay))
805 }
806
807 fn program_id(&self) -> Result<ProgramId, Self::UnrecoverableError> {
808 Ok(self.context.program_id)
809 }
810
811 fn debug(&self, data: &str) -> Result<(), Self::UnrecoverableError> {
812 let program_id = self.program_id()?;
813 let message_id = self.message_id()?;
814
815 log::debug!(target: "gwasm", "DEBUG: [handle({message_id:.2?})] {program_id:.2?}: {data}");
816
817 Ok(())
818 }
819
820 fn lock_payload(&mut self, at: u32, len: u32) -> Result<PayloadSliceLock, Self::FallibleError> {
821 let end = at
822 .checked_add(len)
823 .ok_or(FallibleExecutionError::TooBigReadLen)?;
824 self.charge_gas_runtime_if_enough(RuntimeCosts::ReadPerByte(len))?;
825 PayloadSliceLock::try_new((at, end), &mut self.context.message_context)
826 .ok_or_else(|| FallibleExecutionError::ReadWrongRange.into())
827 }
828
829 fn unlock_payload(&mut self, payload_holder: &mut PayloadSliceLock) -> UnlockPayloadBound {
830 UnlockPayloadBound::from((&mut self.context.message_context, payload_holder))
831 }
832
833 fn size(&self) -> Result<usize, Self::UnrecoverableError> {
834 Ok(self.context.message_context.current().payload_bytes().len())
835 }
836
837 fn reserve_gas(
838 &mut self,
839 amount: u64,
840 duration: u32,
841 ) -> Result<ReservationId, Self::FallibleError> {
842 self.charge_gas_if_enough(self.context.message_context.settings().reservation_fee())?;
843
844 if duration == 0 {
845 return Err(ReservationError::ZeroReservationDuration.into());
846 }
847
848 if amount < self.context.mailbox_threshold {
849 return Err(ReservationError::ReservationBelowMailboxThreshold.into());
850 }
851
852 let reserve = u64::from(self.context.reserve_for.saturating_add(duration))
853 .saturating_mul(self.context.reservation);
854 let reduce_amount = amount.saturating_add(reserve);
855 if self.context.gas_counter.reduce(reduce_amount) == ChargeResult::NotEnough {
856 return Err(FallibleExecutionError::NotEnoughGas.into());
857 }
858
859 let id = self.context.gas_reserver.reserve(amount, duration)?;
860
861 Ok(id)
862 }
863
864 fn unreserve_gas(&mut self, id: ReservationId) -> Result<u64, Self::FallibleError> {
865 let amount = self.context.gas_reserver.unreserve(id)?;
866
867 Ok(amount)
876 }
877
878 fn system_reserve_gas(&mut self, amount: u64) -> Result<(), Self::FallibleError> {
879 if amount == 0 {
881 return Err(ReservationError::ZeroReservationAmount.into());
882 }
883
884 if self.context.gas_counter.reduce(amount) == ChargeResult::NotEnough {
885 return Err(FallibleExecutionError::NotEnoughGas.into());
886 }
887
888 let reservation = &mut self.context.system_reservation;
889 *reservation = reservation
890 .map(|reservation| reservation.saturating_add(amount))
891 .or(Some(amount));
892
893 Ok(())
894 }
895
896 fn gas_available(&self) -> Result<u64, Self::UnrecoverableError> {
897 Ok(self.context.gas_counter.left())
898 }
899
900 fn value(&self) -> Result<u128, Self::UnrecoverableError> {
901 Ok(self.context.message_context.current().value())
902 }
903
904 fn value_available(&self) -> Result<u128, Self::UnrecoverableError> {
905 Ok(self.context.value_counter.left())
906 }
907
908 fn wait(&mut self) -> Result<(), Self::UnrecoverableError> {
909 self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee())?;
910
911 if self.context.message_context.reply_sent() {
912 return Err(UnrecoverableWaitError::WaitAfterReply.into());
913 }
914
915 let reserve = u64::from(self.context.reserve_for.saturating_add(1))
916 .saturating_mul(self.context.waitlist_cost);
917
918 if self.context.gas_counter.reduce(reserve) != ChargeResult::Enough {
919 return Err(UnrecoverableExecutionError::NotEnoughGas.into());
920 }
921
922 Ok(())
923 }
924
925 fn wait_for(&mut self, duration: u32) -> Result<(), Self::UnrecoverableError> {
926 self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee())?;
927
928 if self.context.message_context.reply_sent() {
929 return Err(UnrecoverableWaitError::WaitAfterReply.into());
930 }
931
932 if duration == 0 {
933 return Err(UnrecoverableWaitError::ZeroDuration.into());
934 }
935
936 let reserve = u64::from(self.context.reserve_for.saturating_add(duration))
937 .saturating_mul(self.context.waitlist_cost);
938
939 if self.context.gas_counter.reduce(reserve) != ChargeResult::Enough {
940 return Err(UnrecoverableExecutionError::NotEnoughGas.into());
941 }
942
943 Ok(())
944 }
945
946 fn wait_up_to(&mut self, duration: u32) -> Result<bool, Self::UnrecoverableError> {
947 self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee())?;
948
949 if self.context.message_context.reply_sent() {
950 return Err(UnrecoverableWaitError::WaitAfterReply.into());
951 }
952
953 if duration == 0 {
954 return Err(UnrecoverableWaitError::ZeroDuration.into());
955 }
956
957 let reserve = u64::from(self.context.reserve_for.saturating_add(1))
958 .saturating_mul(self.context.waitlist_cost);
959
960 if self.context.gas_counter.reduce(reserve) != ChargeResult::Enough {
961 return Err(UnrecoverableExecutionError::NotEnoughGas.into());
962 }
963
964 let reserve_full = u64::from(self.context.reserve_for.saturating_add(duration))
965 .saturating_mul(self.context.waitlist_cost);
966 let reserve_diff = reserve_full - reserve;
967
968 Ok(self.context.gas_counter.reduce(reserve_diff) == ChargeResult::Enough)
969 }
970
971 fn wake(&mut self, waker_id: MessageId, delay: u32) -> Result<(), Self::FallibleError> {
972 self.charge_gas_if_enough(self.context.message_context.settings().waking_fee())?;
973
974 self.context.message_context.wake(waker_id, delay)?;
975 Ok(())
976 }
977
978 fn create_program(
979 &mut self,
980 packet: InitPacket,
981 delay: u32,
982 ) -> Result<(MessageId, ProgramId), Self::FallibleError> {
983 self.check_forbidden_destination(packet.destination())?;
984 self.safe_gasfull_sends(&packet)?;
985 self.charge_expiring_resources(&packet, true)?;
986 self.charge_sending_fee(delay)?;
987
988 self.charge_for_dispatch_stash_hold(delay)?;
989
990 let code_hash = packet.code_id();
991
992 let (mid, pid) = self
994 .context
995 .message_context
996 .init_program(packet, delay)
997 .map(|(init_msg_id, new_prog_id)| {
998 let entry = self
1000 .context
1001 .program_candidates_data
1002 .entry(code_hash)
1003 .or_default();
1004 entry.push((init_msg_id, new_prog_id));
1005
1006 (init_msg_id, new_prog_id)
1007 })?;
1008 Ok((mid, pid))
1009 }
1010
1011 fn reply_deposit(
1012 &mut self,
1013 message_id: MessageId,
1014 amount: u64,
1015 ) -> Result<(), Self::FallibleError> {
1016 self.reduce_gas(amount)?;
1017
1018 self.context
1019 .message_context
1020 .reply_deposit(message_id, amount)?;
1021
1022 Ok(())
1023 }
1024
1025 fn random(&self) -> Result<(&[u8], u32), Self::UnrecoverableError> {
1026 Ok((&self.context.random_data.0, self.context.random_data.1))
1027 }
1028
1029 fn forbidden_funcs(&self) -> &BTreeSet<SysCallName> {
1030 &self.context.forbidden_funcs
1031 }
1032}
1033
1034impl Ext {
1035 pub fn alloc_inner<G: GrowHandler>(
1037 &mut self,
1038 pages_num: u32,
1039 mem: &mut impl Memory,
1040 ) -> Result<WasmPage, AllocExtError> {
1041 let pages = WasmPage::new(pages_num).map_err(|_| AllocError::ProgramAllocOutOfBounds)?;
1042
1043 self.context
1044 .allocations_context
1045 .alloc::<G>(pages, mem, |pages| {
1046 Ext::charge_gas_if_enough(
1047 &mut self.context.gas_counter,
1048 &mut self.context.gas_allowance_counter,
1049 self.context.page_costs.mem_grow.calc(pages),
1050 )
1051 })
1052 .map_err(Into::into)
1053 }
1054
1055 pub fn into_ext_info_inner(
1058 self,
1059 memory: &impl Memory,
1060 pages_for_data: impl FnOnce(WasmPage, &BTreeSet<WasmPage>) -> Vec<GearPage>,
1061 ) -> Result<ExtInfo, MemoryError> {
1062 let ProcessorContext {
1063 allocations_context,
1064 message_context,
1065 gas_counter,
1066 gas_reserver,
1067 system_reservation,
1068 program_candidates_data,
1069 program_rents,
1070 ..
1071 } = self.context;
1072
1073 let (static_pages, initial_allocations, allocations) = allocations_context.into_parts();
1074 let mut pages_data = BTreeMap::new();
1075 for page in pages_for_data(static_pages, &allocations) {
1076 let mut buf = PageBuf::new_zeroed();
1077 memory.read(page.offset(), &mut buf)?;
1078 pages_data.insert(page, buf);
1079 }
1080
1081 let (outcome, mut context_store) = message_context.drain();
1082 let ContextOutcomeDrain {
1083 outgoing_dispatches: generated_dispatches,
1084 awakening,
1085 reply_deposits,
1086 } = outcome.drain();
1087
1088 let system_reservation_context = SystemReservationContext {
1089 current_reservation: system_reservation,
1090 previous_reservation: context_store.system_reservation(),
1091 };
1092
1093 context_store.set_reservation_nonce(&gas_reserver);
1094 if let Some(reservation) = system_reservation {
1095 context_store.add_system_reservation(reservation);
1096 }
1097
1098 let info = ExtInfo {
1099 gas_amount: gas_counter.to_amount(),
1100 gas_reserver,
1101 system_reservation_context,
1102 allocations: (allocations != initial_allocations)
1103 .then_some(allocations)
1104 .unwrap_or_default(),
1105 pages_data,
1106 generated_dispatches,
1107 awakening,
1108 reply_deposits,
1109 context_store,
1110 program_candidates_data,
1111 program_rents,
1112 };
1113 Ok(info)
1114 }
1115}
1116
1117#[cfg(test)]
1118mod tests {
1119 use super::*;
1120 use alloc::vec;
1121 use gear_core::{
1122 message::{ContextSettings, IncomingDispatch, Payload, MAX_PAYLOAD_SIZE},
1123 pages::PageNumber,
1124 };
1125
1126 struct MessageContextBuilder {
1127 incoming_dispatch: IncomingDispatch,
1128 program_id: ProgramId,
1129 sending_fee: u64,
1130 scheduled_sending_fee: u64,
1131 waiting_fee: u64,
1132 waking_fee: u64,
1133 reservation_fee: u64,
1134 outgoing_limit: u32,
1135 }
1136
1137 impl MessageContextBuilder {
1138 fn new() -> Self {
1139 Self {
1140 incoming_dispatch: Default::default(),
1141 program_id: Default::default(),
1142 sending_fee: 0,
1143 scheduled_sending_fee: 0,
1144 waiting_fee: 0,
1145 waking_fee: 0,
1146 reservation_fee: 0,
1147 outgoing_limit: 0,
1148 }
1149 }
1150
1151 fn build(self) -> MessageContext {
1152 MessageContext::new(
1153 self.incoming_dispatch,
1154 self.program_id,
1155 ContextSettings::new(
1156 self.sending_fee,
1157 self.scheduled_sending_fee,
1158 self.waiting_fee,
1159 self.waking_fee,
1160 self.reservation_fee,
1161 self.outgoing_limit,
1162 ),
1163 )
1164 }
1165
1166 fn with_outgoing_limit(mut self, outgoing_limit: u32) -> Self {
1167 self.outgoing_limit = outgoing_limit;
1168 self
1169 }
1170 }
1171
1172 struct ProcessorContextBuilder(ProcessorContext);
1173
1174 impl ProcessorContextBuilder {
1175 fn new() -> Self {
1176 Self(ProcessorContext {
1177 page_costs: PageCosts::new_for_tests(),
1178 ..ProcessorContext::new_mock()
1179 })
1180 }
1181
1182 fn build(self) -> ProcessorContext {
1183 self.0
1184 }
1185
1186 fn with_message_context(mut self, context: MessageContext) -> Self {
1187 self.0.message_context = context;
1188
1189 self
1190 }
1191
1192 fn with_gas(mut self, gas_counter: GasCounter) -> Self {
1193 self.0.gas_counter = gas_counter;
1194
1195 self
1196 }
1197
1198 fn with_allowance(mut self, gas_allowance_counter: GasAllowanceCounter) -> Self {
1199 self.0.gas_allowance_counter = gas_allowance_counter;
1200
1201 self
1202 }
1203
1204 fn with_weighs(mut self, weights: HostFnWeights) -> Self {
1205 self.0.host_fn_weights = weights;
1206
1207 self
1208 }
1209
1210 fn with_allocation_context(mut self, ctx: AllocationsContext) -> Self {
1211 self.0.allocations_context = ctx;
1212
1213 self
1214 }
1215 }
1216
1217 #[test]
1219 fn free_no_refund() {
1220 let initial_gas = 100;
1222 let initial_allowance = 10000;
1223
1224 let gas_left = (initial_gas, initial_allowance).into();
1225
1226 let existing_page = 99.into();
1227 let non_existing_page = 100.into();
1228
1229 let allocations_context =
1230 AllocationsContext::new(BTreeSet::from([existing_page]), 1.into(), 512.into());
1231
1232 let mut ext = Ext::new(
1233 ProcessorContextBuilder::new()
1234 .with_gas(GasCounter::new(initial_gas))
1235 .with_allowance(GasAllowanceCounter::new(initial_allowance))
1236 .with_allocation_context(allocations_context)
1237 .build(),
1238 );
1239
1240 assert!(ext.free(existing_page).is_ok());
1243 assert_eq!(ext.gas_left(), gas_left);
1244
1245 assert_eq!(
1248 ext.free(non_existing_page),
1249 Err(AllocExtError::Alloc(AllocError::InvalidFree(
1250 non_existing_page.raw()
1251 )))
1252 );
1253 assert_eq!(ext.gas_left(), gas_left);
1254 }
1255
1256 #[test]
1257 fn test_counter_zeroes() {
1258 let free_weight = 1000;
1260 let host_fn_weights = HostFnWeights {
1261 free: free_weight,
1262 ..Default::default()
1263 };
1264
1265 let initial_gas = free_weight - 1;
1266 let initial_allowance = free_weight + 1;
1267
1268 let mut lack_gas_ext = Ext::new(
1269 ProcessorContextBuilder::new()
1270 .with_gas(GasCounter::new(initial_gas))
1271 .with_allowance(GasAllowanceCounter::new(initial_allowance))
1272 .with_weighs(host_fn_weights.clone())
1273 .build(),
1274 );
1275
1276 assert_eq!(
1277 lack_gas_ext.charge_gas_runtime(RuntimeCosts::Free),
1278 Err(ChargeError::GasLimitExceeded),
1279 );
1280
1281 let gas_amount = lack_gas_ext.gas_amount();
1282 let allowance = lack_gas_ext.context.gas_allowance_counter.left();
1283 assert_eq!(0, gas_amount.left());
1285 assert_eq!(initial_gas, gas_amount.burned());
1286 assert_eq!(initial_allowance - free_weight, allowance);
1287
1288 let initial_gas = free_weight;
1289 let initial_allowance = free_weight - 1;
1290
1291 let mut lack_allowance_ext = Ext::new(
1292 ProcessorContextBuilder::new()
1293 .with_gas(GasCounter::new(initial_gas))
1294 .with_allowance(GasAllowanceCounter::new(initial_allowance))
1295 .with_weighs(host_fn_weights)
1296 .build(),
1297 );
1298
1299 assert_eq!(
1300 lack_allowance_ext.charge_gas_runtime(RuntimeCosts::Free),
1301 Err(ChargeError::GasAllowanceExceeded),
1302 );
1303
1304 let gas_amount = lack_allowance_ext.gas_amount();
1305 let allowance = lack_allowance_ext.context.gas_allowance_counter.left();
1306 assert_eq!(initial_gas - free_weight, gas_amount.left());
1307 assert_eq!(initial_gas, gas_amount.burned());
1308 assert_eq!(0, allowance);
1310 }
1311
1312 #[test]
1313 fn test_send_commit() {
1320 let mut ext = Ext::new(
1321 ProcessorContextBuilder::new()
1322 .with_message_context(MessageContextBuilder::new().with_outgoing_limit(1).build())
1323 .build(),
1324 );
1325
1326 let data = HandlePacket::default();
1327
1328 let fake_handle = 0;
1329
1330 let msg = ext.send_commit(fake_handle, data.clone(), 0);
1331 assert_eq!(
1332 msg.unwrap_err(),
1333 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::OutOfBounds))
1334 );
1335
1336 let handle = ext.send_init().expect("Outgoing limit is 1");
1337
1338 let msg = ext.send_commit(handle, data.clone(), 0);
1339 assert!(msg.is_ok());
1340
1341 let msg = ext.send_commit(handle, data, 0);
1342 assert_eq!(
1343 msg.unwrap_err(),
1344 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1345 );
1346
1347 let handle = ext.send_init();
1348 assert_eq!(
1349 handle.unwrap_err(),
1350 FallibleExtError::Core(FallibleExtErrorCore::Message(
1351 MessageError::OutgoingMessagesAmountLimitExceeded
1352 ))
1353 );
1354 }
1355
1356 #[test]
1357 fn test_send_push() {
1365 let mut ext = Ext::new(
1366 ProcessorContextBuilder::new()
1367 .with_message_context(
1368 MessageContextBuilder::new()
1369 .with_outgoing_limit(u32::MAX)
1370 .build(),
1371 )
1372 .build(),
1373 );
1374
1375 let data = HandlePacket::default();
1376
1377 let fake_handle = 0;
1378
1379 let res = ext.send_push(fake_handle, &[0, 0, 0]);
1380 assert_eq!(
1381 res.unwrap_err(),
1382 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::OutOfBounds))
1383 );
1384
1385 let handle = ext.send_init().expect("Outgoing limit is u32::MAX");
1386
1387 let res = ext.send_push(handle, &[1, 2, 3]);
1388 assert!(res.is_ok());
1389
1390 let res = ext.send_push(handle, &[4, 5, 6]);
1391 assert!(res.is_ok());
1392
1393 let large_payload = vec![0u8; MAX_PAYLOAD_SIZE + 1];
1394
1395 let res = ext.send_push(handle, &large_payload);
1396 assert_eq!(
1397 res.unwrap_err(),
1398 FallibleExtError::Core(FallibleExtErrorCore::Message(
1399 MessageError::MaxMessageSizeExceed
1400 ))
1401 );
1402
1403 let msg = ext.send_commit(handle, data, 0);
1404 assert!(msg.is_ok());
1405
1406 let res = ext.send_push(handle, &[7, 8, 9]);
1407 assert_eq!(
1408 res.unwrap_err(),
1409 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1410 );
1411
1412 let (outcome, _) = ext.context.message_context.drain();
1413 let ContextOutcomeDrain {
1414 mut outgoing_dispatches,
1415 ..
1416 } = outcome.drain();
1417 let dispatch = outgoing_dispatches
1418 .pop()
1419 .map(|(dispatch, _, _)| dispatch)
1420 .expect("Send commit was ok");
1421
1422 assert_eq!(dispatch.message().payload_bytes(), &[1, 2, 3, 4, 5, 6]);
1423 }
1424
1425 #[test]
1426 fn test_send_push_input() {
1433 let mut ext = Ext::new(
1434 ProcessorContextBuilder::new()
1435 .with_message_context(
1436 MessageContextBuilder::new()
1437 .with_outgoing_limit(u32::MAX)
1438 .build(),
1439 )
1440 .build(),
1441 );
1442
1443 let data = HandlePacket::default();
1444
1445 let fake_handle = 0;
1446
1447 let res = ext.send_push_input(fake_handle, 0, 1);
1448 assert_eq!(
1449 res.unwrap_err(),
1450 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::OutOfBounds))
1451 );
1452
1453 let handle = ext.send_init().expect("Outgoing limit is u32::MAX");
1454
1455 let res = ext
1456 .context
1457 .message_context
1458 .payload_mut()
1459 .try_extend_from_slice(&[1, 2, 3, 4, 5, 6]);
1460 assert!(res.is_ok());
1461
1462 let res = ext.send_push_input(handle, 2, 3);
1463 assert!(res.is_ok());
1464
1465 let res = ext.send_push_input(handle, 8, 10);
1466 assert!(res.is_ok());
1467
1468 let msg = ext.send_commit(handle, data, 0);
1469 assert!(msg.is_ok());
1470
1471 let res = ext.send_push_input(handle, 0, 1);
1472 assert_eq!(
1473 res.unwrap_err(),
1474 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1475 );
1476
1477 let (outcome, _) = ext.context.message_context.drain();
1478 let ContextOutcomeDrain {
1479 mut outgoing_dispatches,
1480 ..
1481 } = outcome.drain();
1482 let dispatch = outgoing_dispatches
1483 .pop()
1484 .map(|(dispatch, _, _)| dispatch)
1485 .expect("Send commit was ok");
1486
1487 assert_eq!(dispatch.message().payload_bytes(), &[3, 4, 5]);
1488 }
1489
1490 #[test]
1491 fn test_reply_commit() {
1498 let mut ext = Ext::new(
1499 ProcessorContextBuilder::new()
1500 .with_gas(GasCounter::new(u64::MAX))
1501 .with_message_context(
1502 MessageContextBuilder::new()
1503 .with_outgoing_limit(u32::MAX)
1504 .build(),
1505 )
1506 .build(),
1507 );
1508
1509 let res = ext.reply_push(&[0]);
1510 assert!(res.is_ok());
1511
1512 let res = ext.reply_commit(ReplyPacket::new(Payload::filled_with(0), 0));
1513 assert_eq!(
1514 res.unwrap_err(),
1515 FallibleExtError::Core(FallibleExtErrorCore::Message(
1516 MessageError::MaxMessageSizeExceed
1517 ))
1518 );
1519
1520 let res = ext.reply_commit(ReplyPacket::auto());
1521 assert!(res.is_ok());
1522
1523 let res = ext.reply_commit(ReplyPacket::auto());
1524 assert_eq!(
1525 res.unwrap_err(),
1526 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::DuplicateReply))
1527 );
1528 }
1529
1530 #[test]
1531 fn test_reply_push() {
1539 let mut ext = Ext::new(
1540 ProcessorContextBuilder::new()
1541 .with_gas(GasCounter::new(u64::MAX))
1542 .with_message_context(
1543 MessageContextBuilder::new()
1544 .with_outgoing_limit(u32::MAX)
1545 .build(),
1546 )
1547 .build(),
1548 );
1549
1550 let res = ext.reply_push(&[1, 2, 3]);
1551 assert!(res.is_ok());
1552
1553 let res = ext.reply_push(&[4, 5, 6]);
1554 assert!(res.is_ok());
1555
1556 let large_payload = vec![0u8; MAX_PAYLOAD_SIZE + 1];
1557
1558 let res = ext.reply_push(&large_payload);
1559 assert_eq!(
1560 res.unwrap_err(),
1561 FallibleExtError::Core(FallibleExtErrorCore::Message(
1562 MessageError::MaxMessageSizeExceed
1563 ))
1564 );
1565
1566 let res = ext.reply_commit(ReplyPacket::auto());
1567 assert!(res.is_ok());
1568
1569 let res = ext.reply_push(&[7, 8, 9]);
1570 assert_eq!(
1571 res.unwrap_err(),
1572 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1573 );
1574
1575 let (outcome, _) = ext.context.message_context.drain();
1576 let ContextOutcomeDrain {
1577 mut outgoing_dispatches,
1578 ..
1579 } = outcome.drain();
1580 let dispatch = outgoing_dispatches
1581 .pop()
1582 .map(|(dispatch, _, _)| dispatch)
1583 .expect("Send commit was ok");
1584
1585 assert_eq!(dispatch.message().payload_bytes(), &[1, 2, 3, 4, 5, 6]);
1586 }
1587
1588 #[test]
1589 fn test_reply_push_input() {
1595 let mut ext = Ext::new(
1596 ProcessorContextBuilder::new()
1597 .with_message_context(
1598 MessageContextBuilder::new()
1599 .with_outgoing_limit(u32::MAX)
1600 .build(),
1601 )
1602 .build(),
1603 );
1604
1605 let res = ext
1606 .context
1607 .message_context
1608 .payload_mut()
1609 .try_extend_from_slice(&[1, 2, 3, 4, 5, 6]);
1610 assert!(res.is_ok());
1611
1612 let res = ext.reply_push_input(2, 3);
1613 assert!(res.is_ok());
1614
1615 let res = ext.reply_push_input(8, 10);
1616 assert!(res.is_ok());
1617
1618 let msg = ext.reply_commit(ReplyPacket::auto());
1619 assert!(msg.is_ok());
1620
1621 let res = ext.reply_push_input(0, 1);
1622 assert_eq!(
1623 res.unwrap_err(),
1624 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1625 );
1626
1627 let (outcome, _) = ext.context.message_context.drain();
1628 let ContextOutcomeDrain {
1629 mut outgoing_dispatches,
1630 ..
1631 } = outcome.drain();
1632 let dispatch = outgoing_dispatches
1633 .pop()
1634 .map(|(dispatch, _, _)| dispatch)
1635 .expect("Send commit was ok");
1636
1637 assert_eq!(dispatch.message().payload_bytes(), &[3, 4, 5]);
1638 }
1639
1640 mod property_tests {
1641 use super::*;
1642 use gear_core::{
1643 memory::HostPointer,
1644 pages::{PageError, PageNumber},
1645 };
1646 use proptest::{
1647 arbitrary::any,
1648 collection::size_range,
1649 prop_oneof, proptest,
1650 strategy::{Just, Strategy},
1651 test_runner::Config as ProptestConfig,
1652 };
1653
1654 struct TestMemory(WasmPage);
1655
1656 impl Memory for TestMemory {
1657 type GrowError = PageError;
1658
1659 fn grow(&mut self, pages: WasmPage) -> Result<(), Self::GrowError> {
1660 self.0 = self.0.add(pages)?;
1661 Ok(())
1662 }
1663
1664 fn size(&self) -> WasmPage {
1665 self.0
1666 }
1667
1668 fn write(&mut self, _offset: u32, _buffer: &[u8]) -> Result<(), MemoryError> {
1669 unimplemented!()
1670 }
1671
1672 fn read(&self, _offset: u32, _buffer: &mut [u8]) -> Result<(), MemoryError> {
1673 unimplemented!()
1674 }
1675
1676 unsafe fn get_buffer_host_addr_unsafe(&mut self) -> HostPointer {
1677 unimplemented!()
1678 }
1679 }
1680
1681 #[derive(Debug, Clone)]
1682 enum Action {
1683 Alloc { pages: WasmPage },
1684 Free { page: WasmPage },
1685 }
1686
1687 fn actions() -> impl Strategy<Value = Vec<Action>> {
1688 let action = wasm_page_number().prop_flat_map(|page| {
1689 prop_oneof![
1690 Just(Action::Alloc { pages: page }),
1691 Just(Action::Free { page })
1692 ]
1693 });
1694 proptest::collection::vec(action, 0..1024)
1695 }
1696
1697 fn allocations() -> impl Strategy<Value = BTreeSet<WasmPage>> {
1698 proptest::collection::btree_set(wasm_page_number(), size_range(0..1024))
1699 }
1700
1701 fn wasm_page_number() -> impl Strategy<Value = WasmPage> {
1702 any::<u16>().prop_map(WasmPage::from)
1703 }
1704
1705 fn proptest_config() -> ProptestConfig {
1706 ProptestConfig {
1707 cases: 1024,
1708 ..Default::default()
1709 }
1710 }
1711
1712 #[track_caller]
1713 fn assert_alloc_error(err: <Ext as Externalities>::AllocError) {
1714 match err {
1715 AllocExtError::Alloc(
1716 AllocError::IncorrectAllocationData(_) | AllocError::ProgramAllocOutOfBounds,
1717 ) => {}
1718 err => Err(err).unwrap(),
1719 }
1720 }
1721
1722 #[track_caller]
1723 fn assert_free_error(err: <Ext as Externalities>::AllocError) {
1724 match err {
1725 AllocExtError::Alloc(AllocError::InvalidFree(_)) => {}
1726 err => Err(err).unwrap(),
1727 }
1728 }
1729
1730 proptest! {
1731 #![proptest_config(proptest_config())]
1732 #[test]
1733 fn alloc(
1734 static_pages in wasm_page_number(),
1735 allocations in allocations(),
1736 max_pages in wasm_page_number(),
1737 mem_size in wasm_page_number(),
1738 actions in actions(),
1739 ) {
1740 let _ = env_logger::try_init();
1741
1742 let ctx = AllocationsContext::new(allocations, static_pages, max_pages);
1743 let ctx = ProcessorContextBuilder::new()
1744 .with_gas(GasCounter::new(u64::MAX))
1745 .with_allowance(GasAllowanceCounter::new(u64::MAX))
1746 .with_allocation_context(ctx)
1747 .build();
1748 let mut ext = Ext::new(ctx);
1749 let mut mem = TestMemory(mem_size);
1750
1751 for action in actions {
1752 match action {
1753 Action::Alloc { pages } => {
1754 if let Err(err) = ext.alloc(pages.raw(), &mut mem) {
1755 assert_alloc_error(err);
1756 }
1757 }
1758 Action::Free { page } => {
1759 if let Err(err) = ext.free(page) {
1760 assert_free_error(err);
1761 }
1762 },
1763 }
1764 }
1765 }
1766 }
1767 }
1768}