1use crate::{configs::BlockInfo, context::SystemReservationContext};
20use alloc::{
21 collections::{BTreeMap, BTreeSet},
22 format,
23 vec::Vec,
24};
25use core::marker::PhantomData;
26use gear_core::{
27 costs::{CostToken, ExtCosts, LazyPagesCosts},
28 env::{Externalities, PayloadSliceLock, UnlockPayloadBound},
29 env_vars::{EnvVars, EnvVarsV1},
30 gas::{
31 ChargeError, ChargeResult, CounterType, CountersOwner, GasAllowanceCounter, GasAmount,
32 GasCounter, GasLeft, ValueCounter,
33 },
34 ids::{prelude::*, CodeId, MessageId, ProgramId, ReservationId},
35 memory::{
36 AllocError, AllocationsContext, GrowHandler, Memory, MemoryError, MemoryInterval, PageBuf,
37 },
38 message::{
39 ContextOutcomeDrain, ContextStore, Dispatch, DispatchKind, GasLimit, HandlePacket,
40 InitPacket, MessageContext, Packet, ReplyPacket,
41 },
42 pages::{
43 numerated::{interval::Interval, tree::IntervalsTree},
44 GearPage, WasmPage, WasmPagesAmount,
45 },
46 program::MemoryInfix,
47 reservation::GasReserver,
48};
49use gear_core_backend::{
50 error::{
51 ActorTerminationReason, BackendAllocSyscallError, BackendSyscallError, RunFallibleError,
52 TrapExplanation, UndefinedTerminationReason, UnrecoverableExecutionError,
53 UnrecoverableExtError as UnrecoverableExtErrorCore, UnrecoverableWaitError,
54 },
55 BackendExternalities,
56};
57use gear_core_errors::{
58 ExecutionError as FallibleExecutionError, ExtError as FallibleExtErrorCore, MessageError,
59 ReplyCode, ReservationError, SignalCode,
60};
61use gear_lazy_pages_common::{GlobalsAccessConfig, LazyPagesInterface, ProcessAccessError, Status};
62use gear_wasm_instrument::syscalls::SyscallName;
63
64pub struct ProcessorContext {
66 pub gas_counter: GasCounter,
68 pub gas_allowance_counter: GasAllowanceCounter,
70 pub gas_reserver: GasReserver,
72 pub system_reservation: Option<u64>,
74 pub value_counter: ValueCounter,
76 pub allocations_context: AllocationsContext,
78 pub message_context: MessageContext,
80 pub block_info: BlockInfo,
82 pub performance_multiplier: gsys::Percent,
84 pub program_id: ProgramId,
86 pub program_candidates_data: BTreeMap<CodeId, Vec<(MessageId, ProgramId)>>,
89 pub forbidden_funcs: BTreeSet<SyscallName>,
91 pub reserve_for: u32,
93 pub random_data: (Vec<u8>, u32),
95 pub gas_multiplier: gsys::GasMultiplier,
97 pub existential_deposit: u128,
99 pub mailbox_threshold: u64,
101 pub costs: ExtCosts,
103}
104
105#[cfg(any(feature = "mock", test))]
106impl ProcessorContext {
107 pub fn new_mock() -> ProcessorContext {
109 const MAX_RESERVATIONS: u64 = 256;
110
111 ProcessorContext {
112 gas_counter: GasCounter::new(0),
113 gas_allowance_counter: GasAllowanceCounter::new(0),
114 gas_reserver: GasReserver::new(
115 &Default::default(),
116 Default::default(),
117 MAX_RESERVATIONS,
118 ),
119 system_reservation: None,
120 value_counter: ValueCounter::new(1_000_000),
121 allocations_context: AllocationsContext::try_new(
122 Default::default(),
123 Default::default(),
124 Default::default(),
125 Default::default(),
126 Default::default(),
127 )
128 .unwrap(),
129 message_context: MessageContext::new(
130 Default::default(),
131 Default::default(),
132 Default::default(),
133 ),
134 block_info: Default::default(),
135 performance_multiplier: gsys::Percent::new(100),
136 program_id: Default::default(),
137 program_candidates_data: Default::default(),
138 forbidden_funcs: Default::default(),
139 reserve_for: 0,
140 random_data: ([0u8; 32].to_vec(), 0),
141 gas_multiplier: gsys::GasMultiplier::from_value_per_gas(1),
142 existential_deposit: Default::default(),
143 mailbox_threshold: Default::default(),
144 costs: Default::default(),
145 }
146 }
147}
148
149#[derive(Debug)]
150pub struct ExtInfo {
151 pub gas_amount: GasAmount,
152 pub gas_reserver: GasReserver,
153 pub system_reservation_context: SystemReservationContext,
154 pub allocations: Option<IntervalsTree<WasmPage>>,
155 pub pages_data: BTreeMap<GearPage, PageBuf>,
156 pub generated_dispatches: Vec<(Dispatch, u32, Option<ReservationId>)>,
157 pub awakening: Vec<(MessageId, u32)>,
158 pub reply_deposits: Vec<(MessageId, u64)>,
159 pub program_candidates_data: BTreeMap<CodeId, Vec<(MessageId, ProgramId)>>,
160 pub context_store: ContextStore,
161 pub reply_sent: bool,
162}
163
164pub trait ProcessorExternalities {
167 fn new(context: ProcessorContext) -> Self;
169
170 fn into_ext_info<Context>(
172 self,
173 ctx: &mut Context,
174 memory: &impl Memory<Context>,
175 ) -> Result<ExtInfo, MemoryError>;
176
177 fn lazy_pages_init_for_program<Context>(
179 ctx: &mut Context,
180 mem: &mut impl Memory<Context>,
181 prog_id: ProgramId,
182 memory_infix: MemoryInfix,
183 stack_end: Option<WasmPage>,
184 globals_config: GlobalsAccessConfig,
185 lazy_pages_costs: LazyPagesCosts,
186 );
187
188 fn lazy_pages_post_execution_actions<Context>(
190 ctx: &mut Context,
191 mem: &mut impl Memory<Context>,
192 );
193
194 fn lazy_pages_status() -> Status;
196}
197
198#[derive(Debug, Clone, Eq, PartialEq, derive_more::From)]
200pub enum UnrecoverableExtError {
201 Core(UnrecoverableExtErrorCore),
203 Charge(ChargeError),
205}
206
207impl From<UnrecoverableExecutionError> for UnrecoverableExtError {
208 fn from(err: UnrecoverableExecutionError) -> UnrecoverableExtError {
209 Self::Core(UnrecoverableExtErrorCore::from(err))
210 }
211}
212
213impl From<UnrecoverableWaitError> for UnrecoverableExtError {
214 fn from(err: UnrecoverableWaitError) -> UnrecoverableExtError {
215 Self::Core(UnrecoverableExtErrorCore::from(err))
216 }
217}
218
219impl BackendSyscallError for UnrecoverableExtError {
220 fn into_termination_reason(self) -> UndefinedTerminationReason {
221 match self {
222 UnrecoverableExtError::Core(err) => {
223 ActorTerminationReason::Trap(TrapExplanation::UnrecoverableExt(err)).into()
224 }
225 UnrecoverableExtError::Charge(err) => err.into(),
226 }
227 }
228
229 fn into_run_fallible_error(self) -> RunFallibleError {
230 RunFallibleError::UndefinedTerminationReason(self.into_termination_reason())
231 }
232}
233
234#[derive(Debug, Clone, Eq, PartialEq, derive_more::From)]
236pub enum FallibleExtError {
237 Core(FallibleExtErrorCore),
239 ForbiddenFunction,
241 Charge(ChargeError),
243}
244
245impl From<MessageError> for FallibleExtError {
246 fn from(err: MessageError) -> Self {
247 Self::Core(FallibleExtErrorCore::Message(err))
248 }
249}
250
251impl From<FallibleExecutionError> for FallibleExtError {
252 fn from(err: FallibleExecutionError) -> Self {
253 Self::Core(FallibleExtErrorCore::Execution(err))
254 }
255}
256
257impl From<ReservationError> for FallibleExtError {
258 fn from(err: ReservationError) -> Self {
259 Self::Core(FallibleExtErrorCore::Reservation(err))
260 }
261}
262
263impl From<FallibleExtError> for RunFallibleError {
264 fn from(err: FallibleExtError) -> Self {
265 match err {
266 FallibleExtError::Core(err) => RunFallibleError::FallibleExt(err),
267 FallibleExtError::ForbiddenFunction => {
268 RunFallibleError::UndefinedTerminationReason(UndefinedTerminationReason::Actor(
269 ActorTerminationReason::Trap(TrapExplanation::ForbiddenFunction),
270 ))
271 }
272 FallibleExtError::Charge(err) => {
273 RunFallibleError::UndefinedTerminationReason(UndefinedTerminationReason::from(err))
274 }
275 }
276 }
277}
278
279#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display, derive_more::From)]
281pub enum AllocExtError {
282 #[display(fmt = "{_0}")]
284 Charge(ChargeError),
285 #[display(fmt = "{_0}")]
287 Alloc(AllocError),
288}
289
290impl BackendAllocSyscallError for AllocExtError {
291 type ExtError = UnrecoverableExtError;
292
293 fn into_backend_error(self) -> Result<Self::ExtError, Self> {
294 match self {
295 Self::Charge(err) => Ok(err.into()),
296 err => Err(err),
297 }
298 }
299}
300
301struct LazyGrowHandler<LP: LazyPagesInterface> {
302 old_mem_addr: Option<u64>,
303 old_mem_size: WasmPagesAmount,
304 _phantom: PhantomData<LP>,
305}
306
307impl<Context, LP: LazyPagesInterface> GrowHandler<Context> for LazyGrowHandler<LP> {
308 fn before_grow_action(ctx: &mut Context, mem: &mut impl Memory<Context>) -> Self {
309 let old_mem_addr = mem.get_buffer_host_addr(ctx);
313 LP::remove_lazy_pages_prot(ctx, mem);
314 Self {
315 old_mem_addr,
316 old_mem_size: mem.size(ctx),
317 _phantom: PhantomData,
318 }
319 }
320
321 fn after_grow_action(self, ctx: &mut Context, mem: &mut impl Memory<Context>) {
322 let new_mem_addr = mem.get_buffer_host_addr(ctx).unwrap_or_else(|| {
325 let err_msg = format!(
326 "LazyGrowHandler::after_grow_action: Memory size cannot be zero after grow is applied for memory. \
327 Old memory address - {:?}, old memory size - {:?}",
328 self.old_mem_addr, self.old_mem_size
329 );
330
331 log::error!("{err_msg}");
332 unreachable!("{err_msg}")
333 });
334 LP::update_lazy_pages_and_protect_again(
335 ctx,
336 mem,
337 self.old_mem_addr,
338 self.old_mem_size,
339 new_mem_addr,
340 );
341 }
342}
343
344#[must_use]
352struct ExtMutator<'a, LP: LazyPagesInterface> {
353 ext: &'a mut Ext<LP>,
354 gas_counter: GasCounter,
355 gas_allowance_counter: GasAllowanceCounter,
356 value_counter: ValueCounter,
357 outgoing_gasless: u64,
358 reservation_to_mark: Option<ReservationId>,
359}
360
361impl<LP: LazyPagesInterface> core::ops::Deref for ExtMutator<'_, LP> {
362 type Target = Ext<LP>;
363
364 fn deref(&self) -> &Self::Target {
365 self.ext
366 }
367}
368
369impl<'a, LP: LazyPagesInterface> ExtMutator<'a, LP> {
370 fn new(ext: &'a mut Ext<LP>) -> Self {
371 unsafe {
373 Self {
374 gas_counter: ext.context.gas_counter.clone(),
375 gas_allowance_counter: ext.context.gas_allowance_counter.clone(),
376 value_counter: ext.context.value_counter.clone(),
377 outgoing_gasless: ext.outgoing_gasless,
378 reservation_to_mark: None,
379 ext,
380 }
381 }
382 }
383
384 fn alloc<Context>(
385 &mut self,
386 ctx: &mut Context,
387 mem: &mut impl Memory<Context>,
388 pages: WasmPagesAmount,
389 ) -> Result<WasmPage, AllocError> {
390 let gas_for_call = self.context.costs.mem_grow.cost_for_one();
392 let gas_for_pages = self.context.costs.mem_grow_per_page;
393 self.ext
394 .context
395 .allocations_context
396 .alloc::<Context, LazyGrowHandler<LP>>(ctx, mem, pages, |pages| {
397 let cost = gas_for_call.saturating_add(gas_for_pages.cost_for(pages));
398 if self.gas_counter.charge_if_enough(cost) == ChargeResult::NotEnough {
400 return Err(ChargeError::GasLimitExceeded);
401 }
402
403 if self.gas_allowance_counter.charge_if_enough(cost) == ChargeResult::NotEnough {
404 return Err(ChargeError::GasAllowanceExceeded);
405 }
406 Ok(())
407 })
408 }
409
410 fn reduce_gas(&mut self, limit: GasLimit) -> Result<(), FallibleExtError> {
411 if self.gas_counter.reduce(limit) == ChargeResult::NotEnough {
412 return Err(FallibleExecutionError::NotEnoughGas.into());
413 }
414
415 Ok(())
416 }
417
418 fn charge_message_value(&mut self, value: u128) -> Result<(), FallibleExtError> {
419 if self.value_counter.reduce(value) == ChargeResult::NotEnough {
420 return Err(FallibleExecutionError::NotEnoughValue.into());
421 }
422
423 Ok(())
424 }
425
426 fn mark_reservation_used(
427 &mut self,
428 reservation_id: ReservationId,
429 ) -> Result<(), ReservationError> {
430 let _ = self
431 .ext
432 .context
433 .gas_reserver
434 .check_not_used(reservation_id)?;
435 self.reservation_to_mark = Some(reservation_id);
436 Ok(())
437 }
438
439 fn charge_gas_if_enough(&mut self, gas: u64) -> Result<(), ChargeError> {
440 if self.gas_counter.charge_if_enough(gas) == ChargeResult::NotEnough {
441 return Err(ChargeError::GasLimitExceeded);
442 }
443
444 if self.gas_allowance_counter.charge_if_enough(gas) == ChargeResult::NotEnough {
445 return Err(ChargeError::GasAllowanceExceeded);
446 }
447 Ok(())
448 }
449
450 fn charge_expiring_resources<T: Packet>(&mut self, packet: &T) -> Result<(), FallibleExtError> {
451 let reducing_gas_limit = self.get_reducing_gas_limit(packet)?;
452
453 self.reduce_gas(reducing_gas_limit)?;
454 self.charge_message_value(packet.value())
455 }
456
457 fn get_reducing_gas_limit<T: Packet>(&self, packet: &T) -> Result<u64, FallibleExtError> {
458 match T::kind() {
459 DispatchKind::Handle => {
460 let mailbox_threshold = self.context.mailbox_threshold;
466 let gas_limit = packet.gas_limit().unwrap_or(mailbox_threshold);
467
468 if gas_limit != 0 && gas_limit < mailbox_threshold {
470 return Err(MessageError::InsufficientGasLimit.into());
471 }
472
473 Ok(gas_limit)
474 }
475 DispatchKind::Init | DispatchKind::Reply => {
476 Ok(packet.gas_limit().unwrap_or(0))
486 }
487 DispatchKind::Signal => unreachable!("Signals can't be sent as a syscall"),
488 }
489 }
490
491 fn charge_sending_fee(&mut self, delay: u32) -> Result<(), ChargeError> {
492 if delay == 0 {
493 self.charge_gas_if_enough(self.context.message_context.settings().sending_fee)
494 } else {
495 self.charge_gas_if_enough(
496 self.context
497 .message_context
498 .settings()
499 .scheduled_sending_fee,
500 )
501 }
502 }
503
504 fn charge_for_dispatch_stash_hold(&mut self, delay: u32) -> Result<(), FallibleExtError> {
505 if delay != 0 {
506 let waiting_reserve = self
507 .context
508 .costs
509 .rent
510 .dispatch_stash
511 .cost_for(self.context.reserve_for.saturating_add(delay).into());
512
513 return self
515 .reduce_gas(waiting_reserve)
516 .map_err(|_| MessageError::InsufficientGasForDelayedSending.into());
517 }
518
519 Ok(())
520 }
521
522 fn apply(mut self) {
523 if let Some(reservation) = self.reservation_to_mark.take() {
524 let result = self.ext.context.gas_reserver.mark_used(reservation);
525 debug_assert!(result.is_ok());
526 }
527
528 self.ext.context.gas_counter = self.gas_counter;
529 self.ext.context.value_counter = self.value_counter;
530 self.ext.context.gas_allowance_counter = self.gas_allowance_counter;
531 self.ext.outgoing_gasless = self.outgoing_gasless;
532 }
533}
534
535pub struct Ext<LP: LazyPagesInterface> {
537 pub context: ProcessorContext,
539 pub current_counter: CounterType,
541 outgoing_gasless: u64,
545 _phantom: PhantomData<LP>,
546}
547
548impl<LP: LazyPagesInterface> ProcessorExternalities for Ext<LP> {
550 fn new(context: ProcessorContext) -> Self {
551 let current_counter = if context.gas_counter.left() <= context.gas_allowance_counter.left()
552 {
553 CounterType::GasLimit
554 } else {
555 CounterType::GasAllowance
556 };
557
558 Self {
559 context,
560 current_counter,
561 outgoing_gasless: 0,
562 _phantom: PhantomData,
563 }
564 }
565
566 fn into_ext_info<Context>(
567 self,
568 ctx: &mut Context,
569 memory: &impl Memory<Context>,
570 ) -> Result<ExtInfo, MemoryError> {
571 let ProcessorContext {
572 allocations_context,
573 message_context,
574 gas_counter,
575 gas_reserver,
576 system_reservation,
577 program_candidates_data,
578 ..
579 } = self.context;
580
581 let (static_pages, initial_allocations, allocations) = allocations_context.into_parts();
582
583 let mut accessed_pages = LP::get_write_accessed_pages();
585 accessed_pages.retain(|p| {
586 let wasm_page: WasmPage = p.to_page();
587 wasm_page < static_pages || allocations.contains(wasm_page)
588 });
589 log::trace!("accessed pages numbers = {:?}", accessed_pages);
590
591 let mut pages_data = BTreeMap::new();
592 for page in accessed_pages {
593 let mut buf = PageBuf::new_zeroed();
594 memory.read(ctx, page.offset(), &mut buf)?;
595 pages_data.insert(page, buf);
596 }
597
598 let (outcome, mut context_store) = message_context.drain();
599 let ContextOutcomeDrain {
600 outgoing_dispatches: generated_dispatches,
601 awakening,
602 reply_deposits,
603 reply_sent,
604 } = outcome.drain();
605
606 let system_reservation_context = SystemReservationContext {
607 current_reservation: system_reservation,
608 previous_reservation: context_store.system_reservation(),
609 };
610
611 context_store.set_reservation_nonce(&gas_reserver);
612 if let Some(reservation) = system_reservation {
613 context_store.add_system_reservation(reservation);
614 }
615
616 let info = ExtInfo {
617 gas_amount: gas_counter.to_amount(),
618 gas_reserver,
619 system_reservation_context,
620 allocations: (allocations != initial_allocations).then_some(allocations),
621 pages_data,
622 generated_dispatches,
623 awakening,
624 reply_deposits,
625 context_store,
626 program_candidates_data,
627 reply_sent,
628 };
629 Ok(info)
630 }
631
632 fn lazy_pages_init_for_program<Context>(
633 ctx: &mut Context,
634 mem: &mut impl Memory<Context>,
635 prog_id: ProgramId,
636 memory_infix: MemoryInfix,
637 stack_end: Option<WasmPage>,
638 globals_config: GlobalsAccessConfig,
639 lazy_pages_costs: LazyPagesCosts,
640 ) {
641 LP::init_for_program(
642 ctx,
643 mem,
644 prog_id,
645 memory_infix,
646 stack_end,
647 globals_config,
648 lazy_pages_costs,
649 );
650 }
651
652 fn lazy_pages_post_execution_actions<Context>(
653 ctx: &mut Context,
654 mem: &mut impl Memory<Context>,
655 ) {
656 LP::remove_lazy_pages_prot(ctx, mem);
657 }
658
659 fn lazy_pages_status() -> Status {
660 LP::get_status()
661 }
662}
663
664impl<LP: LazyPagesInterface> BackendExternalities for Ext<LP> {
665 fn gas_amount(&self) -> GasAmount {
666 self.context.gas_counter.to_amount()
667 }
668
669 fn pre_process_memory_accesses(
670 &mut self,
671 reads: &[MemoryInterval],
672 writes: &[MemoryInterval],
673 gas_counter: &mut u64,
674 ) -> Result<(), ProcessAccessError> {
675 LP::pre_process_memory_accesses(reads, writes, gas_counter)
676 }
677}
678
679impl<LP: LazyPagesInterface> Ext<LP> {
680 fn with_changes<F, R, E>(&mut self, callback: F) -> Result<R, E>
681 where
682 F: FnOnce(&mut ExtMutator<LP>) -> Result<R, E>,
683 {
684 let mut mutator = ExtMutator::new(self);
685 let result = callback(&mut mutator)?;
686 mutator.apply();
687 Ok(result)
688 }
689
690 fn check_reservation_gas_limit_for_delayed_sending(
693 &self,
694 reservation_id: &ReservationId,
695 delay: u32,
696 ) -> Result<(), FallibleExtError> {
697 if delay != 0 {
698 let limit = self
699 .context
700 .gas_reserver
701 .limit_of(reservation_id)
702 .ok_or(ReservationError::InvalidReservationId)?;
703
704 let waiting_reserve = self
705 .context
706 .costs
707 .rent
708 .dispatch_stash
709 .cost_for(self.context.reserve_for.saturating_add(delay).into());
710
711 if limit < waiting_reserve.saturating_add(self.context.mailbox_threshold) {
716 return Err(MessageError::InsufficientGasForDelayedSending.into());
717 }
718 }
719
720 Ok(())
721 }
722
723 fn check_forbidden_destination(&self, id: ProgramId) -> Result<(), FallibleExtError> {
724 if id == ProgramId::SYSTEM {
725 Err(FallibleExtError::ForbiddenFunction)
726 } else {
727 Ok(())
728 }
729 }
730
731 fn charge_gas_if_enough(
732 gas_counter: &mut GasCounter,
733 gas_allowance_counter: &mut GasAllowanceCounter,
734 amount: u64,
735 ) -> Result<(), ChargeError> {
736 if gas_counter.charge_if_enough(amount) != ChargeResult::Enough {
737 return Err(ChargeError::GasLimitExceeded);
738 }
739 if gas_allowance_counter.charge_if_enough(amount) != ChargeResult::Enough {
740 return Err(ChargeError::GasAllowanceExceeded);
744 }
745 Ok(())
746 }
747
748 fn cost_for_reservation(&self, amount: u64, duration: u32) -> u64 {
749 self.context
750 .costs
751 .rent
752 .reservation
753 .cost_for(self.context.reserve_for.saturating_add(duration).into())
754 .saturating_add(amount)
755 }
756}
757
758impl<LP: LazyPagesInterface> CountersOwner for Ext<LP> {
759 fn charge_gas_for_token(&mut self, token: CostToken) -> Result<(), ChargeError> {
760 let amount = self.context.costs.syscalls.cost_for_token(token);
761 let common_charge = self.context.gas_counter.charge(amount);
762 let allowance_charge = self.context.gas_allowance_counter.charge(amount);
763 match (common_charge, allowance_charge) {
764 (ChargeResult::NotEnough, _) => Err(ChargeError::GasLimitExceeded),
765 (ChargeResult::Enough, ChargeResult::NotEnough) => {
766 Err(ChargeError::GasAllowanceExceeded)
767 }
768 (ChargeResult::Enough, ChargeResult::Enough) => Ok(()),
769 }
770 }
771
772 fn charge_gas_if_enough(&mut self, amount: u64) -> Result<(), ChargeError> {
773 Self::charge_gas_if_enough(
774 &mut self.context.gas_counter,
775 &mut self.context.gas_allowance_counter,
776 amount,
777 )
778 }
779
780 fn gas_left(&self) -> GasLeft {
781 (
782 self.context.gas_counter.left(),
783 self.context.gas_allowance_counter.left(),
784 )
785 .into()
786 }
787
788 fn current_counter_type(&self) -> CounterType {
789 self.current_counter
790 }
791
792 fn decrease_current_counter_to(&mut self, amount: u64) {
793 if self.current_counter_value() <= amount {
802 log::trace!("Skipped decrease to global value");
803 return;
804 }
805
806 let GasLeft { gas, allowance } = self.gas_left();
807
808 let diff = match self.current_counter_type() {
809 CounterType::GasLimit => gas.checked_sub(amount),
810 CounterType::GasAllowance => allowance.checked_sub(amount),
811 }
812 .unwrap_or_else(|| {
813 let err_msg = format!(
814 "CounterOwner::decrease_current_counter_to: Checked sub operation overflowed. \
815 Message id - {message_id}, program id - {program_id}, current counter type - {current_counter_type:?}, \
816 gas - {gas}, allowance - {allowance}, amount - {amount}",
817 message_id = self.context.message_context.current().id(), program_id = self.context.program_id, current_counter_type = self.current_counter_type()
818 );
819
820 log::error!("{err_msg}");
821 unreachable!("{err_msg}")
822 });
823
824 if self.context.gas_counter.charge(diff) == ChargeResult::NotEnough {
825 let err_msg = format!(
826 "CounterOwner::decrease_current_counter_to: Tried to set gas limit left bigger than before. \
827 Message id - {message_id}, program id - {program_id}, gas counter - {gas_counter:?}, diff - {diff}",
828 message_id = self.context.message_context.current().id(),
829 program_id = self.context.program_id,
830 gas_counter = self.context.gas_counter
831 );
832
833 log::error!("{err_msg}");
834 unreachable!("{err_msg}")
835 }
836
837 if self.context.gas_allowance_counter.charge(diff) == ChargeResult::NotEnough {
838 let err_msg = format!(
839 "CounterOwner::decrease_current_counter_to: Tried to set gas allowance left bigger than before. \
840 Message id - {message_id}, program id - {program_id}, gas allowance counter - {gas_allowance_counter:?}, diff - {diff}",
841 message_id = self.context.message_context.current().id(),
842 program_id = self.context.program_id,
843 gas_allowance_counter = self.context.gas_allowance_counter,
844 );
845
846 log::error!("{err_msg}");
847 unreachable!("{err_msg}")
848 }
849 }
850
851 fn define_current_counter(&mut self) -> u64 {
852 let GasLeft { gas, allowance } = self.gas_left();
853
854 if gas <= allowance {
855 self.current_counter = CounterType::GasLimit;
856 gas
857 } else {
858 self.current_counter = CounterType::GasAllowance;
859 allowance
860 }
861 }
862}
863
864impl<LP: LazyPagesInterface> Externalities for Ext<LP> {
865 type UnrecoverableError = UnrecoverableExtError;
866 type FallibleError = FallibleExtError;
867 type AllocError = AllocExtError;
868
869 fn alloc<Context>(
870 &mut self,
871 ctx: &mut Context,
872 mem: &mut impl Memory<Context>,
873 pages_num: u32,
874 ) -> Result<WasmPage, Self::AllocError> {
875 let pages = WasmPagesAmount::try_from(pages_num)
876 .map_err(|_| AllocError::ProgramAllocOutOfBounds)?;
877
878 self.with_changes(|mutator| {
879 mutator
880 .alloc::<Context>(ctx, mem, pages)
881 .map_err(Into::into)
882 })
883 }
884
885 fn free(&mut self, page: WasmPage) -> Result<(), Self::AllocError> {
886 self.context
887 .allocations_context
888 .free(page)
889 .map_err(Into::into)
890 }
891
892 fn free_range(&mut self, start: WasmPage, end: WasmPage) -> Result<(), Self::AllocError> {
893 let interval = Interval::try_from(start..=end)
894 .map_err(|_| AllocExtError::Alloc(AllocError::InvalidFreeRange(start, end)))?;
895 self.with_changes(|mutator| {
896 mutator.charge_gas_if_enough(
897 mutator
898 .context
899 .costs
900 .syscalls
901 .free_range_per_page
902 .cost_for(interval.len()),
903 )?;
904 mutator
905 .ext
906 .context
907 .allocations_context
908 .free_range(interval)
909 .map_err(Into::into)
910 })
911 }
912
913 fn env_vars(&self, version: u32) -> Result<EnvVars, Self::UnrecoverableError> {
914 match version {
915 1 => Ok(EnvVars::V1(EnvVarsV1 {
916 performance_multiplier: self.context.performance_multiplier,
917 existential_deposit: self.context.existential_deposit,
918 mailbox_threshold: self.context.mailbox_threshold,
919 gas_multiplier: self.context.gas_multiplier,
920 })),
921 _ => Err(UnrecoverableExecutionError::UnsupportedEnvVarsVersion.into()),
922 }
923 }
924
925 fn block_height(&self) -> Result<u32, Self::UnrecoverableError> {
926 Ok(self.context.block_info.height)
927 }
928
929 fn block_timestamp(&self) -> Result<u64, Self::UnrecoverableError> {
930 Ok(self.context.block_info.timestamp)
931 }
932
933 fn send_init(&mut self) -> Result<u32, Self::FallibleError> {
934 let handle = self.context.message_context.send_init()?;
935 Ok(handle)
936 }
937
938 fn send_push(&mut self, handle: u32, buffer: &[u8]) -> Result<(), Self::FallibleError> {
939 self.context.message_context.send_push(handle, buffer)?;
940 Ok(())
941 }
942
943 fn send_push_input(
944 &mut self,
945 handle: u32,
946 offset: u32,
947 len: u32,
948 ) -> Result<(), Self::FallibleError> {
949 let range = self
950 .context
951 .message_context
952 .check_input_range(offset, len)?;
953
954 self.with_changes(|mutator| {
955 mutator.charge_gas_if_enough(
956 mutator
957 .context
958 .costs
959 .syscalls
960 .gr_send_push_input_per_byte
961 .cost_for(range.len().into()),
962 )?;
963 mutator
964 .ext
965 .context
966 .message_context
967 .send_push_input(handle, range)
968 .map_err(Into::into)
969 })
970 }
971
972 fn send_commit(
973 &mut self,
974 handle: u32,
975 msg: HandlePacket,
976 delay: u32,
977 ) -> Result<MessageId, Self::FallibleError> {
978 self.with_changes(|mutator| {
979 mutator.check_forbidden_destination(msg.destination())?;
980 mutator.charge_expiring_resources(&msg)?;
981 mutator.charge_sending_fee(delay)?;
982 mutator.charge_for_dispatch_stash_hold(delay)?;
983
984 mutator
985 .ext
986 .context
987 .message_context
988 .send_commit(handle, msg, delay, None)
989 .map_err(Into::into)
990 })
991 }
992
993 fn reservation_send_commit(
994 &mut self,
995 id: ReservationId,
996 handle: u32,
997 msg: HandlePacket,
998 delay: u32,
999 ) -> Result<MessageId, Self::FallibleError> {
1000 self.with_changes(|mutator| {
1001 mutator.check_forbidden_destination(msg.destination())?;
1002 mutator.check_reservation_gas_limit_for_delayed_sending(&id, delay)?;
1005 mutator.charge_message_value(msg.value())?;
1007 mutator.charge_sending_fee(delay)?;
1008
1009 mutator.mark_reservation_used(id)?;
1010
1011 mutator
1012 .ext
1013 .context
1014 .message_context
1015 .send_commit(handle, msg, delay, Some(id))
1016 .map_err(Into::into)
1017 })
1018 }
1019
1020 fn reply_push(&mut self, buffer: &[u8]) -> Result<(), Self::FallibleError> {
1021 self.context.message_context.reply_push(buffer)?;
1022 Ok(())
1023 }
1024
1025 fn reply_commit(&mut self, msg: ReplyPacket) -> Result<MessageId, Self::FallibleError> {
1027 self.with_changes(|mutator| {
1028 mutator
1029 .check_forbidden_destination(mutator.context.message_context.reply_destination())?;
1030 mutator.charge_expiring_resources(&msg)?;
1031 mutator.charge_sending_fee(0)?;
1032
1033 mutator
1034 .ext
1035 .context
1036 .message_context
1037 .reply_commit(msg, None)
1038 .map_err(Into::into)
1039 })
1040 }
1041
1042 fn reservation_reply_commit(
1043 &mut self,
1044 id: ReservationId,
1045 msg: ReplyPacket,
1046 ) -> Result<MessageId, Self::FallibleError> {
1047 self.with_changes(|mutator| {
1048 mutator
1049 .check_forbidden_destination(mutator.context.message_context.reply_destination())?;
1050 mutator.charge_message_value(msg.value())?;
1052 mutator.charge_sending_fee(0)?;
1053
1054 mutator.mark_reservation_used(id)?;
1055
1056 mutator
1057 .ext
1058 .context
1059 .message_context
1060 .reply_commit(msg, Some(id))
1061 .map_err(Into::into)
1062 })
1063 }
1064
1065 fn reply_to(&self) -> Result<MessageId, Self::FallibleError> {
1066 self.context
1067 .message_context
1068 .current()
1069 .details()
1070 .and_then(|d| d.to_reply_details().map(|d| d.to_message_id()))
1071 .ok_or_else(|| FallibleExecutionError::NoReplyContext.into())
1072 }
1073
1074 fn signal_from(&self) -> Result<MessageId, Self::FallibleError> {
1075 self.context
1076 .message_context
1077 .current()
1078 .details()
1079 .and_then(|d| d.to_signal_details().map(|d| d.to_message_id()))
1080 .ok_or_else(|| FallibleExecutionError::NoSignalContext.into())
1081 }
1082
1083 fn reply_push_input(&mut self, offset: u32, len: u32) -> Result<(), Self::FallibleError> {
1084 self.with_changes(|mutator| {
1085 let range = mutator
1086 .context
1087 .message_context
1088 .check_input_range(offset, len)?;
1089 mutator.charge_gas_if_enough(
1090 mutator
1091 .context
1092 .costs
1093 .syscalls
1094 .gr_reply_push_input_per_byte
1095 .cost_for(range.len().into()),
1096 )?;
1097 mutator
1098 .ext
1099 .context
1100 .message_context
1101 .reply_push_input(range)
1102 .map_err(Into::into)
1103 })
1104 }
1105
1106 fn source(&self) -> Result<ProgramId, Self::UnrecoverableError> {
1107 Ok(self.context.message_context.current().source())
1108 }
1109
1110 fn reply_code(&self) -> Result<ReplyCode, Self::FallibleError> {
1111 self.context
1112 .message_context
1113 .current()
1114 .details()
1115 .and_then(|d| d.to_reply_details().map(|d| d.to_reply_code()))
1116 .ok_or_else(|| FallibleExecutionError::NoReplyContext.into())
1117 }
1118
1119 fn signal_code(&self) -> Result<SignalCode, Self::FallibleError> {
1120 self.context
1121 .message_context
1122 .current()
1123 .details()
1124 .and_then(|d| d.to_signal_details().map(|d| d.to_signal_code()))
1125 .ok_or_else(|| FallibleExecutionError::NoSignalContext.into())
1126 }
1127
1128 fn message_id(&self) -> Result<MessageId, Self::UnrecoverableError> {
1129 Ok(self.context.message_context.current().id())
1130 }
1131
1132 fn program_id(&self) -> Result<ProgramId, Self::UnrecoverableError> {
1133 Ok(self.context.program_id)
1134 }
1135
1136 fn debug(&self, data: &str) -> Result<(), Self::UnrecoverableError> {
1137 let program_id = self.program_id()?;
1138 let message_id = self.message_id()?;
1139
1140 log::debug!(target: "gwasm", "DEBUG: [handle({message_id:.2?})] {program_id:.2?}: {data}");
1141
1142 Ok(())
1143 }
1144
1145 fn lock_payload(&mut self, at: u32, len: u32) -> Result<PayloadSliceLock, Self::FallibleError> {
1146 self.with_changes(|mutator| {
1147 let end = at
1148 .checked_add(len)
1149 .ok_or(FallibleExecutionError::TooBigReadLen)?;
1150 mutator.charge_gas_if_enough(
1151 mutator
1152 .context
1153 .costs
1154 .syscalls
1155 .gr_read_per_byte
1156 .cost_for(len.into()),
1157 )?;
1158 PayloadSliceLock::try_new((at, end), &mut mutator.ext.context.message_context)
1159 .ok_or_else(|| FallibleExecutionError::ReadWrongRange.into())
1160 })
1161 }
1162
1163 fn unlock_payload(&mut self, payload_holder: &mut PayloadSliceLock) -> UnlockPayloadBound {
1164 UnlockPayloadBound::from((&mut self.context.message_context, payload_holder))
1165 }
1166
1167 fn size(&self) -> Result<usize, Self::UnrecoverableError> {
1168 Ok(self.context.message_context.current().payload_bytes().len())
1169 }
1170
1171 fn reserve_gas(
1172 &mut self,
1173 amount: u64,
1174 duration: u32,
1175 ) -> Result<ReservationId, Self::FallibleError> {
1176 self.with_changes(|mutator| {
1177 mutator
1178 .charge_gas_if_enough(mutator.context.message_context.settings().reservation_fee)?;
1179
1180 if duration == 0 {
1181 return Err(ReservationError::ZeroReservationDuration.into());
1182 }
1183
1184 if amount < mutator.context.mailbox_threshold {
1185 return Err(ReservationError::ReservationBelowMailboxThreshold.into());
1186 }
1187
1188 let reduce_amount = mutator.cost_for_reservation(amount, duration);
1189
1190 mutator
1191 .reduce_gas(reduce_amount)
1192 .map_err(|_| FallibleExecutionError::NotEnoughGas)?;
1193
1194 mutator
1195 .ext
1196 .context
1197 .gas_reserver
1198 .reserve(amount, duration)
1199 .map_err(Into::into)
1200 })
1201 }
1202
1203 fn unreserve_gas(&mut self, id: ReservationId) -> Result<u64, Self::FallibleError> {
1204 let (amount, reimburse) = self.context.gas_reserver.unreserve(id)?;
1205
1206 if let Some(reimbursement) = reimburse {
1207 let current_gas_amount = self.gas_amount();
1208
1209 let reimbursement_amount = self.cost_for_reservation(amount, reimbursement.duration());
1211 self.context
1212 .gas_counter
1213 .increase(reimbursement_amount, reimbursement)
1214 .then_some(())
1215 .unwrap_or_else(|| {
1216 let err_msg = format!(
1217 "Ext::unreserve_gas: failed to reimburse unreserved gas to left counter. \
1218 Current gas amount - {}, reimburse amount - {}",
1219 current_gas_amount.left(),
1220 amount,
1221 );
1222
1223 log::error!("{err_msg}");
1224 unreachable!("{err_msg}");
1225 });
1226 }
1227
1228 Ok(amount)
1229 }
1230
1231 fn system_reserve_gas(&mut self, amount: u64) -> Result<(), Self::FallibleError> {
1232 if amount == 0 {
1234 return Err(ReservationError::ZeroReservationAmount.into());
1235 }
1236
1237 if self.context.gas_counter.reduce(amount) == ChargeResult::NotEnough {
1238 return Err(FallibleExecutionError::NotEnoughGas.into());
1239 }
1240
1241 let reservation = &mut self.context.system_reservation;
1242 *reservation = reservation
1243 .map(|reservation| reservation.saturating_add(amount))
1244 .or(Some(amount));
1245
1246 Ok(())
1247 }
1248
1249 fn gas_available(&self) -> Result<u64, Self::UnrecoverableError> {
1250 Ok(self.context.gas_counter.left())
1251 }
1252
1253 fn value(&self) -> Result<u128, Self::UnrecoverableError> {
1254 Ok(self.context.message_context.current().value())
1255 }
1256
1257 fn value_available(&self) -> Result<u128, Self::UnrecoverableError> {
1258 Ok(self.context.value_counter.left())
1259 }
1260
1261 fn wait(&mut self) -> Result<(), Self::UnrecoverableError> {
1262 self.with_changes(|mutator| {
1263 mutator.charge_gas_if_enough(mutator.context.message_context.settings().waiting_fee)?;
1264
1265 if mutator.context.message_context.reply_sent() {
1266 return Err(UnrecoverableWaitError::WaitAfterReply.into());
1267 }
1268
1269 let reserve = mutator
1270 .context
1271 .costs
1272 .rent
1273 .waitlist
1274 .cost_for(mutator.context.reserve_for.saturating_add(1).into());
1275
1276 mutator
1277 .reduce_gas(reserve)
1278 .map_err(|_| UnrecoverableExecutionError::NotEnoughGas)?;
1279
1280 Ok(())
1281 })
1282 }
1283
1284 fn wait_for(&mut self, duration: u32) -> Result<(), Self::UnrecoverableError> {
1285 self.with_changes(|mutator| {
1286 mutator.charge_gas_if_enough(mutator.context.message_context.settings().waiting_fee)?;
1287
1288 if mutator.context.message_context.reply_sent() {
1289 return Err(UnrecoverableWaitError::WaitAfterReply.into());
1290 }
1291
1292 if duration == 0 {
1293 return Err(UnrecoverableWaitError::ZeroDuration.into());
1294 }
1295
1296 let reserve = mutator
1297 .context
1298 .costs
1299 .rent
1300 .waitlist
1301 .cost_for(mutator.context.reserve_for.saturating_add(duration).into());
1302
1303 if mutator.gas_counter.reduce(reserve) != ChargeResult::Enough {
1304 return Err(UnrecoverableExecutionError::NotEnoughGas.into());
1305 }
1306
1307 Ok(())
1308 })
1309 }
1310
1311 fn wait_up_to(&mut self, duration: u32) -> Result<bool, Self::UnrecoverableError> {
1312 self.with_changes(|mutator| {
1313 mutator.charge_gas_if_enough(mutator.context.message_context.settings().waiting_fee)?;
1314
1315 if mutator.context.message_context.reply_sent() {
1316 return Err(UnrecoverableWaitError::WaitAfterReply.into());
1317 }
1318
1319 if duration == 0 {
1320 return Err(UnrecoverableWaitError::ZeroDuration.into());
1321 }
1322
1323 let reserve = mutator
1324 .context
1325 .costs
1326 .rent
1327 .waitlist
1328 .cost_for(mutator.context.reserve_for.saturating_add(1).into());
1329
1330 if mutator.gas_counter.reduce(reserve) != ChargeResult::Enough {
1331 return Err(UnrecoverableExecutionError::NotEnoughGas.into());
1332 }
1333
1334 let reserve_full = mutator
1335 .context
1336 .costs
1337 .rent
1338 .waitlist
1339 .cost_for(mutator.context.reserve_for.saturating_add(duration).into());
1340
1341 let reserve_diff = reserve_full - reserve;
1342
1343 Ok(mutator.gas_counter.reduce(reserve_diff) == ChargeResult::Enough)
1344 })
1345 }
1346
1347 fn wake(&mut self, waker_id: MessageId, delay: u32) -> Result<(), Self::FallibleError> {
1348 self.with_changes(|mutator| {
1349 mutator.charge_gas_if_enough(mutator.context.message_context.settings().waking_fee)?;
1350
1351 mutator
1352 .ext
1353 .context
1354 .message_context
1355 .wake(waker_id, delay)
1356 .map_err(Into::into)
1357 })
1358 }
1359
1360 fn create_program(
1361 &mut self,
1362 packet: InitPacket,
1363 delay: u32,
1364 ) -> Result<(MessageId, ProgramId), Self::FallibleError> {
1365 let ed = self.context.existential_deposit;
1366 self.with_changes(|mutator| {
1367 mutator.charge_expiring_resources(&packet)?;
1371 mutator.charge_sending_fee(delay)?;
1372 mutator.charge_for_dispatch_stash_hold(delay)?;
1373
1374 mutator.charge_message_value(ed)?;
1376
1377 let code_hash = packet.code_id();
1378
1379 mutator
1382 .ext
1383 .context
1384 .message_context
1385 .init_program(packet, delay)
1386 .map(|(init_msg_id, new_prog_id)| {
1387 let entry = mutator
1388 .ext
1389 .context
1390 .program_candidates_data
1391 .entry(code_hash)
1392 .or_default();
1393 entry.push((init_msg_id, new_prog_id));
1394 (init_msg_id, new_prog_id)
1395 })
1396 .map_err(Into::into)
1397 })
1398 }
1399
1400 fn reply_deposit(
1401 &mut self,
1402 message_id: MessageId,
1403 amount: u64,
1404 ) -> Result<(), Self::FallibleError> {
1405 self.with_changes(|mutator| {
1406 mutator.reduce_gas(amount)?;
1407 mutator
1408 .ext
1409 .context
1410 .message_context
1411 .reply_deposit(message_id, amount)
1412 .map_err(Into::into)
1413 })
1414 }
1415
1416 fn random(&self) -> Result<(&[u8], u32), Self::UnrecoverableError> {
1417 Ok((&self.context.random_data.0, self.context.random_data.1))
1418 }
1419
1420 fn forbidden_funcs(&self) -> &BTreeSet<SyscallName> {
1421 &self.context.forbidden_funcs
1422 }
1423}
1424
1425#[cfg(test)]
1426mod tests {
1427 use super::*;
1428 use alloc::vec;
1429 use gear_core::{
1430 costs::{CostOf, RentCosts, SyscallCosts},
1431 message::{ContextSettings, IncomingDispatch, Payload, MAX_PAYLOAD_SIZE},
1432 reservation::{GasReservationMap, GasReservationSlot, GasReservationState},
1433 };
1434
1435 struct MessageContextBuilder {
1436 incoming_dispatch: IncomingDispatch,
1437 program_id: ProgramId,
1438 context_settings: ContextSettings,
1439 }
1440
1441 type Ext = super::Ext<()>;
1442
1443 impl MessageContextBuilder {
1444 fn new() -> Self {
1445 Self {
1446 incoming_dispatch: Default::default(),
1447 program_id: Default::default(),
1448 context_settings: ContextSettings::with_outgoing_limits(u32::MAX, u32::MAX),
1449 }
1450 }
1451
1452 fn build(self) -> MessageContext {
1453 MessageContext::new(
1454 self.incoming_dispatch,
1455 self.program_id,
1456 self.context_settings,
1457 )
1458 }
1459
1460 fn with_outgoing_limit(mut self, outgoing_limit: u32) -> Self {
1461 self.context_settings.outgoing_limit = outgoing_limit;
1462
1463 self
1464 }
1465 }
1466
1467 struct ProcessorContextBuilder(ProcessorContext);
1468
1469 impl ProcessorContextBuilder {
1470 fn new() -> Self {
1471 Self(ProcessorContext::new_mock())
1472 }
1473
1474 fn build(self) -> ProcessorContext {
1475 self.0
1476 }
1477
1478 fn with_message_context(mut self, context: MessageContext) -> Self {
1479 self.0.message_context = context;
1480
1481 self
1482 }
1483
1484 fn with_gas(mut self, gas_counter: GasCounter) -> Self {
1485 self.0.gas_counter = gas_counter;
1486
1487 self
1488 }
1489
1490 fn with_allowance(mut self, gas_allowance_counter: GasAllowanceCounter) -> Self {
1491 self.0.gas_allowance_counter = gas_allowance_counter;
1492
1493 self
1494 }
1495
1496 fn with_costs(mut self, costs: ExtCosts) -> Self {
1497 self.0.costs = costs;
1498
1499 self
1500 }
1501
1502 fn with_allocation_context(mut self, ctx: AllocationsContext) -> Self {
1503 self.0.allocations_context = ctx;
1504
1505 self
1506 }
1507
1508 fn with_existential_deposit(mut self, ed: u128) -> Self {
1509 self.0.existential_deposit = ed;
1510
1511 self
1512 }
1513
1514 fn with_value(mut self, value: u128) -> Self {
1515 self.0.value_counter = ValueCounter::new(value);
1516
1517 self
1518 }
1519
1520 fn with_reservations_map(mut self, map: GasReservationMap) -> Self {
1521 self.0.gas_reserver = GasReserver::new(&Default::default(), map, 256);
1522
1523 self
1524 }
1525 }
1526
1527 #[test]
1529 fn free_no_refund() {
1530 let initial_gas = 100;
1532 let initial_allowance = 10000;
1533
1534 let gas_left = (initial_gas, initial_allowance).into();
1535
1536 let existing_page = 99.into();
1537 let non_existing_page = 100.into();
1538
1539 let allocations_context = AllocationsContext::try_new(
1540 512.into(),
1541 [existing_page].into_iter().collect(),
1542 1.into(),
1543 None,
1544 512.into(),
1545 )
1546 .unwrap();
1547
1548 let mut ext = Ext::new(
1549 ProcessorContextBuilder::new()
1550 .with_gas(GasCounter::new(initial_gas))
1551 .with_allowance(GasAllowanceCounter::new(initial_allowance))
1552 .with_allocation_context(allocations_context)
1553 .build(),
1554 );
1555
1556 assert!(ext.free(existing_page).is_ok());
1559 assert_eq!(ext.gas_left(), gas_left);
1560
1561 assert_eq!(
1564 ext.free(non_existing_page),
1565 Err(AllocExtError::Alloc(AllocError::InvalidFree(
1566 non_existing_page
1567 )))
1568 );
1569 assert_eq!(ext.gas_left(), gas_left);
1570 }
1571
1572 #[test]
1573 fn test_counter_zeroes() {
1574 let free_cost = 1000;
1576 let ext_costs = ExtCosts {
1577 syscalls: SyscallCosts {
1578 free: free_cost.into(),
1579 ..Default::default()
1580 },
1581 ..Default::default()
1582 };
1583
1584 let initial_gas = free_cost - 1;
1585 let initial_allowance = free_cost + 1;
1586
1587 let mut lack_gas_ext = Ext::new(
1588 ProcessorContextBuilder::new()
1589 .with_gas(GasCounter::new(initial_gas))
1590 .with_allowance(GasAllowanceCounter::new(initial_allowance))
1591 .with_costs(ext_costs.clone())
1592 .build(),
1593 );
1594
1595 assert_eq!(
1596 lack_gas_ext.charge_gas_for_token(CostToken::Free),
1597 Err(ChargeError::GasLimitExceeded),
1598 );
1599
1600 let gas_amount = lack_gas_ext.gas_amount();
1601 let allowance = lack_gas_ext.context.gas_allowance_counter.left();
1602 assert_eq!(0, gas_amount.left());
1604 assert_eq!(initial_gas, gas_amount.burned());
1605 assert_eq!(initial_allowance - free_cost, allowance);
1606
1607 let initial_gas = free_cost;
1608 let initial_allowance = free_cost - 1;
1609
1610 let mut lack_allowance_ext = Ext::new(
1611 ProcessorContextBuilder::new()
1612 .with_gas(GasCounter::new(initial_gas))
1613 .with_allowance(GasAllowanceCounter::new(initial_allowance))
1614 .with_costs(ext_costs)
1615 .build(),
1616 );
1617
1618 assert_eq!(
1619 lack_allowance_ext.charge_gas_for_token(CostToken::Free),
1620 Err(ChargeError::GasAllowanceExceeded),
1621 );
1622
1623 let gas_amount = lack_allowance_ext.gas_amount();
1624 let allowance = lack_allowance_ext.context.gas_allowance_counter.left();
1625 assert_eq!(initial_gas - free_cost, gas_amount.left());
1626 assert_eq!(initial_gas, gas_amount.burned());
1627 assert_eq!(0, allowance);
1629 }
1630
1631 #[test]
1632 fn test_send_commit() {
1639 let mut ext = Ext::new(
1640 ProcessorContextBuilder::new()
1641 .with_message_context(MessageContextBuilder::new().with_outgoing_limit(1).build())
1642 .build(),
1643 );
1644
1645 let data = HandlePacket::default();
1646
1647 let fake_handle = 0;
1648
1649 let msg = ext.send_commit(fake_handle, data.clone(), 0);
1650 assert_eq!(
1651 msg.unwrap_err(),
1652 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::OutOfBounds))
1653 );
1654
1655 let handle = ext.send_init().expect("Outgoing limit is 1");
1656
1657 let msg = ext.send_commit(handle, data.clone(), 0);
1658 assert!(msg.is_ok());
1659
1660 let msg = ext.send_commit(handle, data, 0);
1661 assert_eq!(
1662 msg.unwrap_err(),
1663 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1664 );
1665
1666 let handle = ext.send_init();
1667 assert_eq!(
1668 handle.unwrap_err(),
1669 FallibleExtError::Core(FallibleExtErrorCore::Message(
1670 MessageError::OutgoingMessagesAmountLimitExceeded
1671 ))
1672 );
1673 }
1674
1675 #[test]
1676 fn test_send_push() {
1684 let mut ext = Ext::new(
1685 ProcessorContextBuilder::new()
1686 .with_message_context(MessageContextBuilder::new().build())
1687 .build(),
1688 );
1689
1690 let data = HandlePacket::default();
1691
1692 let fake_handle = 0;
1693
1694 let res = ext.send_push(fake_handle, &[0, 0, 0]);
1695 assert_eq!(
1696 res.unwrap_err(),
1697 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::OutOfBounds))
1698 );
1699
1700 let handle = ext.send_init().expect("Outgoing limit is u32::MAX");
1701
1702 let res = ext.send_push(handle, &[1, 2, 3]);
1703 assert!(res.is_ok());
1704
1705 let res = ext.send_push(handle, &[4, 5, 6]);
1706 assert!(res.is_ok());
1707
1708 let large_payload = vec![0u8; MAX_PAYLOAD_SIZE + 1];
1709
1710 let res = ext.send_push(handle, &large_payload);
1711 assert_eq!(
1712 res.unwrap_err(),
1713 FallibleExtError::Core(FallibleExtErrorCore::Message(
1714 MessageError::MaxMessageSizeExceed
1715 ))
1716 );
1717
1718 let msg = ext.send_commit(handle, data, 0);
1719 assert!(msg.is_ok());
1720
1721 let res = ext.send_push(handle, &[7, 8, 9]);
1722 assert_eq!(
1723 res.unwrap_err(),
1724 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1725 );
1726
1727 let (outcome, _) = ext.context.message_context.drain();
1728 let ContextOutcomeDrain {
1729 mut outgoing_dispatches,
1730 ..
1731 } = outcome.drain();
1732 let dispatch = outgoing_dispatches
1733 .pop()
1734 .map(|(dispatch, _, _)| dispatch)
1735 .expect("Send commit was ok");
1736
1737 assert_eq!(dispatch.message().payload_bytes(), &[1, 2, 3, 4, 5, 6]);
1738 }
1739
1740 #[test]
1741 fn test_send_push_input() {
1748 let mut ext = Ext::new(
1749 ProcessorContextBuilder::new()
1750 .with_message_context(MessageContextBuilder::new().build())
1751 .build(),
1752 );
1753
1754 let res = ext
1755 .context
1756 .message_context
1757 .payload_mut()
1758 .try_extend_from_slice(&[1, 2, 3, 4, 5, 6]);
1759 assert!(res.is_ok());
1760
1761 let fake_handle = 0;
1762
1763 let res = ext.send_push_input(fake_handle, 0, 1);
1764 assert_eq!(
1765 res.unwrap_err(),
1766 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::OutOfBounds))
1767 );
1768
1769 let handle = ext.send_init().expect("Outgoing limit is u32::MAX");
1770
1771 let res = ext.send_push_input(handle, 2, 3);
1772 assert!(res.is_ok());
1773 let res = ext.send_push_input(handle, 5, 1);
1774 assert!(res.is_ok());
1775
1776 let res = ext.send_push_input(handle, 0, 7);
1778 assert_eq!(
1779 res.unwrap_err(),
1780 FallibleExtError::Core(FallibleExtErrorCore::Message(
1781 MessageError::OutOfBoundsInputSliceLength
1782 ))
1783 );
1784 let res = ext.send_push_input(handle, 5, 2);
1785 assert_eq!(
1786 res.unwrap_err(),
1787 FallibleExtError::Core(FallibleExtErrorCore::Message(
1788 MessageError::OutOfBoundsInputSliceLength
1789 ))
1790 );
1791
1792 let res = ext.send_push_input(handle, 6, 0);
1794 assert_eq!(
1795 res.unwrap_err(),
1796 FallibleExtError::Core(FallibleExtErrorCore::Message(
1797 MessageError::OutOfBoundsInputSliceOffset
1798 ))
1799 );
1800
1801 let msg = ext.send_commit(handle, HandlePacket::default(), 0);
1802 assert!(msg.is_ok());
1803
1804 let res = ext.send_push_input(handle, 0, 1);
1805 assert_eq!(
1806 res.unwrap_err(),
1807 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1808 );
1809
1810 let (outcome, _) = ext.context.message_context.drain();
1811 let ContextOutcomeDrain {
1812 mut outgoing_dispatches,
1813 ..
1814 } = outcome.drain();
1815 let dispatch = outgoing_dispatches
1816 .pop()
1817 .map(|(dispatch, _, _)| dispatch)
1818 .expect("Send commit was ok");
1819
1820 assert_eq!(dispatch.message().payload_bytes(), &[3, 4, 5, 6]);
1821 }
1822
1823 #[test]
1824 fn test_reply_commit() {
1831 let mut ext = Ext::new(
1832 ProcessorContextBuilder::new()
1833 .with_gas(GasCounter::new(u64::MAX))
1834 .with_message_context(MessageContextBuilder::new().build())
1835 .build(),
1836 );
1837
1838 let res = ext.reply_push(&[0]);
1839 assert!(res.is_ok());
1840
1841 let res = ext.reply_commit(ReplyPacket::new(Payload::filled_with(0), 0));
1842 assert_eq!(
1843 res.unwrap_err(),
1844 FallibleExtError::Core(FallibleExtErrorCore::Message(
1845 MessageError::MaxMessageSizeExceed
1846 ))
1847 );
1848
1849 let res = ext.reply_commit(ReplyPacket::auto());
1850 assert!(res.is_ok());
1851
1852 let res = ext.reply_commit(ReplyPacket::auto());
1853 assert_eq!(
1854 res.unwrap_err(),
1855 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::DuplicateReply))
1856 );
1857 }
1858
1859 #[test]
1860 fn test_reply_push() {
1868 let mut ext = Ext::new(
1869 ProcessorContextBuilder::new()
1870 .with_gas(GasCounter::new(u64::MAX))
1871 .with_message_context(MessageContextBuilder::new().build())
1872 .build(),
1873 );
1874
1875 let res = ext.reply_push(&[1, 2, 3]);
1876 assert!(res.is_ok());
1877
1878 let res = ext.reply_push(&[4, 5, 6]);
1879 assert!(res.is_ok());
1880
1881 let large_payload = vec![0u8; MAX_PAYLOAD_SIZE + 1];
1882
1883 let res = ext.reply_push(&large_payload);
1884 assert_eq!(
1885 res.unwrap_err(),
1886 FallibleExtError::Core(FallibleExtErrorCore::Message(
1887 MessageError::MaxMessageSizeExceed
1888 ))
1889 );
1890
1891 let res = ext.reply_commit(ReplyPacket::auto());
1892 assert!(res.is_ok());
1893
1894 let res = ext.reply_push(&[7, 8, 9]);
1895 assert_eq!(
1896 res.unwrap_err(),
1897 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1898 );
1899
1900 let (outcome, _) = ext.context.message_context.drain();
1901 let ContextOutcomeDrain {
1902 mut outgoing_dispatches,
1903 ..
1904 } = outcome.drain();
1905 let dispatch = outgoing_dispatches
1906 .pop()
1907 .map(|(dispatch, _, _)| dispatch)
1908 .expect("Send commit was ok");
1909
1910 assert_eq!(dispatch.message().payload_bytes(), &[1, 2, 3, 4, 5, 6]);
1911 }
1912
1913 #[test]
1914 fn test_reply_push_input() {
1920 let mut ext = Ext::new(
1921 ProcessorContextBuilder::new()
1922 .with_message_context(MessageContextBuilder::new().build())
1923 .build(),
1924 );
1925
1926 let res = ext
1927 .context
1928 .message_context
1929 .payload_mut()
1930 .try_extend_from_slice(&[1, 2, 3, 4, 5, 6]);
1931 assert!(res.is_ok());
1932
1933 let res = ext.reply_push_input(2, 3);
1934 assert!(res.is_ok());
1935 let res = ext.reply_push_input(5, 1);
1936 assert!(res.is_ok());
1937
1938 let res = ext.reply_push_input(0, 7);
1940 assert_eq!(
1941 res.unwrap_err(),
1942 FallibleExtError::Core(FallibleExtErrorCore::Message(
1943 MessageError::OutOfBoundsInputSliceLength
1944 ))
1945 );
1946 let res = ext.reply_push_input(5, 2);
1947 assert_eq!(
1948 res.unwrap_err(),
1949 FallibleExtError::Core(FallibleExtErrorCore::Message(
1950 MessageError::OutOfBoundsInputSliceLength
1951 ))
1952 );
1953
1954 let res = ext.reply_push_input(6, 0);
1956 assert_eq!(
1957 res.unwrap_err(),
1958 FallibleExtError::Core(FallibleExtErrorCore::Message(
1959 MessageError::OutOfBoundsInputSliceOffset
1960 ))
1961 );
1962
1963 let msg = ext.reply_commit(ReplyPacket::auto());
1964 assert!(msg.is_ok());
1965
1966 let res = ext.reply_push_input(0, 1);
1967 assert_eq!(
1968 res.unwrap_err(),
1969 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1970 );
1971
1972 let (outcome, _) = ext.context.message_context.drain();
1973 let ContextOutcomeDrain {
1974 mut outgoing_dispatches,
1975 ..
1976 } = outcome.drain();
1977 let dispatch = outgoing_dispatches
1978 .pop()
1979 .map(|(dispatch, _, _)| dispatch)
1980 .expect("Send commit was ok");
1981
1982 assert_eq!(dispatch.message().payload_bytes(), &[3, 4, 5, 6]);
1983 }
1984
1985 #[test]
1987 fn gas_has_gone_on_err() {
1988 const INIT_GAS: u64 = 1_000_000_000;
1989
1990 let mut ext = Ext::new(
1991 ProcessorContextBuilder::new()
1992 .with_message_context(
1993 MessageContextBuilder::new()
1994 .with_outgoing_limit(u32::MAX)
1995 .build(),
1996 )
1997 .with_gas(GasCounter::new(INIT_GAS))
1998 .build(),
1999 );
2000
2001 let i = ext.send_init().expect("Shouldn't fail");
2003
2004 assert_eq!(
2007 ext.send_commit(
2008 i,
2009 HandlePacket::new_with_gas(
2010 Default::default(),
2011 Default::default(),
2012 INIT_GAS,
2013 u128::MAX
2014 ),
2015 0
2016 )
2017 .unwrap_err(),
2018 FallibleExecutionError::NotEnoughValue.into()
2019 );
2020
2021 let res = ext.send_commit(
2022 i,
2023 HandlePacket::new_with_gas(Default::default(), Default::default(), INIT_GAS, 0),
2024 0,
2025 );
2026 assert!(res.is_ok());
2027 }
2028
2029 #[test]
2031 fn reservation_used_on_err() {
2032 let mut ext = Ext::new(
2033 ProcessorContextBuilder::new()
2034 .with_message_context(
2035 MessageContextBuilder::new()
2036 .with_outgoing_limit(u32::MAX)
2037 .build(),
2038 )
2039 .with_gas(GasCounter::new(1_000_000_000))
2040 .with_allowance(GasAllowanceCounter::new(1_000_000))
2041 .build(),
2042 );
2043
2044 let reservation_id = ext.reserve_gas(1_000_000, 1_000).expect("Shouldn't fail");
2046
2047 assert_eq!(
2050 ext.reservation_send_commit(reservation_id, u32::MAX, Default::default(), 0)
2051 .unwrap_err(),
2052 MessageError::OutOfBounds.into()
2053 );
2054
2055 let i = ext.send_init().expect("Shouldn't fail");
2057
2058 let res = ext.reservation_send_commit(reservation_id, i, Default::default(), 0);
2059 assert!(res.is_ok());
2060 }
2061
2062 #[test]
2063 fn rollback_works() {
2064 let mut ext = Ext::new(
2065 ProcessorContextBuilder::new()
2066 .with_message_context(
2067 MessageContextBuilder::new()
2068 .with_outgoing_limit(u32::MAX)
2069 .build(),
2070 )
2071 .with_gas(GasCounter::new(1_000_000_000))
2072 .build(),
2073 );
2074
2075 let reservation_id = ext.reserve_gas(1_000_000, 1_000).expect("Shouldn't fail");
2076 let remaining_gas = ext.context.gas_counter.to_amount();
2077 let remaining_gas_allowance = ext.context.gas_allowance_counter.left();
2078 let remaining_value_counter = ext.context.value_counter.left();
2079 let result = ext.with_changes::<_, (), _>(|mutator| {
2080 mutator.reduce_gas(42)?;
2081 mutator.charge_gas_if_enough(84)?; mutator.charge_message_value(128)?;
2083 mutator.outgoing_gasless = 1;
2084 mutator.mark_reservation_used(reservation_id)?;
2085 Err(FallibleExtError::Charge(ChargeError::GasLimitExceeded))
2086 });
2087
2088 assert!(result.is_err());
2089 assert_eq!(ext.context.gas_counter.left(), remaining_gas.left());
2090 assert_eq!(ext.context.gas_counter.burned(), remaining_gas.burned());
2091 assert_eq!(
2092 ext.context.gas_allowance_counter.left(),
2093 remaining_gas_allowance
2094 );
2095 assert_eq!(ext.outgoing_gasless, 0);
2096 assert_eq!(ext.context.value_counter.left(), remaining_value_counter);
2097 assert!(matches!(
2098 ext.context.gas_reserver.states().get(&reservation_id),
2099 Some(GasReservationState::Created {
2100 amount: 1_000_000,
2101 duration: 1_000,
2102 used: false
2103 })
2104 ));
2105 }
2106
2107 #[test]
2108 fn changes_do_apply() {
2109 let mut ext = Ext::new(
2110 ProcessorContextBuilder::new()
2111 .with_message_context(
2112 MessageContextBuilder::new()
2113 .with_outgoing_limit(u32::MAX)
2114 .build(),
2115 )
2116 .with_gas(GasCounter::new(1_000_000_000))
2117 .with_allowance(GasAllowanceCounter::new(1_000_000))
2118 .build(),
2119 );
2120
2121 let reservation_id = ext.reserve_gas(1_000_000, 1_000).expect("Shouldn't fail");
2122 let remaining_gas = ext.context.gas_counter.to_amount();
2123 let remaining_gas_allowance = ext.context.gas_allowance_counter.left();
2124 let remaining_value_counter = ext.context.value_counter.left();
2125 let result = ext.with_changes::<_, (), FallibleExtError>(|mutator| {
2126 mutator.reduce_gas(42)?;
2127 mutator.charge_gas_if_enough(84)?; mutator.charge_message_value(128)?;
2129 mutator.outgoing_gasless = 1;
2130 mutator.mark_reservation_used(reservation_id)?;
2131 Ok(())
2132 });
2133
2134 assert!(result.is_ok());
2135 assert_eq!(
2136 ext.context.gas_counter.left(),
2137 remaining_gas.left() - 42 - 84
2138 );
2139 assert_eq!(ext.outgoing_gasless, 1);
2140 assert_eq!(
2141 ext.context.gas_counter.burned(),
2142 remaining_gas.burned() + 84
2143 );
2144 assert_eq!(
2145 ext.context.gas_allowance_counter.left(),
2146 remaining_gas_allowance - 84
2147 );
2148 assert_eq!(
2149 ext.context.value_counter.left(),
2150 remaining_value_counter - 128
2151 );
2152 assert!(matches!(
2153 ext.context.gas_reserver.states().get(&reservation_id),
2154 Some(GasReservationState::Created {
2155 amount: 1_000_000,
2156 duration: 1_000,
2157 used: true
2158 })
2159 ));
2160 }
2161
2162 #[test]
2163 fn test_create_program() {
2168 let mut ext = Ext::new(
2169 ProcessorContextBuilder::new()
2170 .with_message_context(MessageContextBuilder::new().build())
2171 .with_existential_deposit(500)
2172 .with_value(0)
2173 .build(),
2174 );
2175
2176 let data = InitPacket::default();
2177
2178 let msg = ext.create_program(data.clone(), 0);
2179 assert_eq!(
2180 msg.unwrap_err(),
2181 FallibleExtError::Core(FallibleExtErrorCore::Execution(
2182 FallibleExecutionError::NotEnoughValue
2183 ))
2184 );
2185
2186 let mut ext = Ext::new(
2187 ProcessorContextBuilder::new()
2188 .with_gas(GasCounter::new(u64::MAX))
2189 .with_message_context(MessageContextBuilder::new().build())
2190 .with_existential_deposit(500)
2191 .with_value(1500)
2192 .build(),
2193 );
2194
2195 let msg = ext.create_program(data.clone(), 0);
2196 assert!(msg.is_ok());
2197 }
2198
2199 #[test]
2200 fn test_send_commit_with_value() {
2205 let mut ext = Ext::new(
2206 ProcessorContextBuilder::new()
2207 .with_message_context(
2208 MessageContextBuilder::new()
2209 .with_outgoing_limit(u32::MAX)
2210 .build(),
2211 )
2212 .with_existential_deposit(500)
2213 .with_value(0)
2214 .build(),
2215 );
2216
2217 let data = HandlePacket::new(ProgramId::default(), Payload::default(), 1000);
2218
2219 let handle = ext.send_init().expect("No outgoing limit");
2220
2221 let msg = ext.send_commit(handle, data.clone(), 0);
2222 assert_eq!(
2223 msg.unwrap_err(),
2224 FallibleExtError::Core(FallibleExtErrorCore::Execution(
2225 FallibleExecutionError::NotEnoughValue
2226 ))
2227 );
2228
2229 let mut ext = Ext::new(
2230 ProcessorContextBuilder::new()
2231 .with_message_context(
2232 MessageContextBuilder::new()
2233 .with_outgoing_limit(u32::MAX)
2234 .build(),
2235 )
2236 .with_existential_deposit(500)
2237 .with_value(5000)
2238 .build(),
2239 );
2240
2241 let handle = ext.send_init().expect("No outgoing limit");
2242 let msg = ext.send_commit(handle, data.clone(), 0);
2244 assert!(msg.is_ok());
2245
2246 let data = HandlePacket::new(ProgramId::default(), Payload::default(), 100);
2247 let handle = ext.send_init().expect("No outgoing limit");
2248 let msg = ext.send_commit(handle, data, 0);
2249 assert!(msg.is_ok());
2251 }
2252
2253 #[test]
2254 fn test_unreserve_no_reimbursement() {
2255 let costs = ExtCosts {
2256 rent: RentCosts {
2257 reservation: CostOf::new(10),
2258 ..Default::default()
2259 },
2260 ..Default::default()
2261 };
2262
2263 let (id, gas_reservation_map) = {
2265 let mut m = BTreeMap::new();
2266 let id = ReservationId::generate(MessageId::new([5; 32]), 10);
2267
2268 m.insert(
2269 id,
2270 GasReservationSlot {
2271 amount: 1_000_000,
2272 start: 0,
2273 finish: 10,
2274 },
2275 );
2276
2277 (id, m)
2278 };
2279 let mut ext = Ext::new(
2280 ProcessorContextBuilder::new()
2281 .with_gas(GasCounter::new(u64::MAX))
2282 .with_message_context(MessageContextBuilder::new().build())
2283 .with_existential_deposit(500)
2284 .with_reservations_map(gas_reservation_map)
2285 .with_costs(costs.clone())
2286 .build(),
2287 );
2288
2289 assert!(ext
2291 .context
2292 .gas_reserver
2293 .states()
2294 .iter()
2295 .all(|(_, state)| matches!(state, GasReservationState::Exists { .. })));
2296
2297 let gas_before = ext.gas_amount();
2299 assert!(ext.unreserve_gas(id).is_ok());
2300 let gas_after = ext.gas_amount();
2301
2302 assert_eq!(gas_after.left(), gas_before.left());
2303 }
2304
2305 #[test]
2306 fn test_unreserve_with_reimbursement() {
2307 let costs = ExtCosts {
2308 rent: RentCosts {
2309 reservation: CostOf::new(10),
2310 ..Default::default()
2311 },
2312 ..Default::default()
2313 };
2314 let mut ext = Ext::new(
2315 ProcessorContextBuilder::new()
2316 .with_gas(GasCounter::new(u64::MAX))
2317 .with_message_context(MessageContextBuilder::new().build())
2318 .with_existential_deposit(500)
2319 .with_costs(costs.clone())
2320 .build(),
2321 );
2322
2323 let reservation_amount = 1_000_000;
2325 let duration = 10;
2326 let duration_cost = costs
2327 .rent
2328 .reservation
2329 .cost_for(ext.context.reserve_for.saturating_add(duration).into());
2330 let reservation_total_cost = reservation_amount + duration_cost;
2331
2332 let gas_before_reservation = ext.gas_amount();
2333 assert_eq!(gas_before_reservation.left(), u64::MAX);
2334
2335 let id = ext
2336 .reserve_gas(reservation_amount, duration)
2337 .expect("internal error: failed reservation");
2338
2339 let gas_after_reservation = ext.gas_amount();
2340 assert_eq!(
2341 gas_before_reservation.left(),
2342 gas_after_reservation.left() + reservation_total_cost
2343 );
2344
2345 assert!(ext.unreserve_gas(id).is_ok());
2346
2347 let gas_after_unreserve = ext.gas_amount();
2348 assert_eq!(
2349 gas_after_unreserve.left(),
2350 gas_after_reservation.left() + reservation_total_cost
2351 );
2352 assert_eq!(gas_after_unreserve.left(), gas_before_reservation.left());
2353 }
2354}