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(100),
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 Charge(ChargeError),
284 Alloc(AllocError),
286}
287
288impl BackendAllocSyscallError for AllocExtError {
289 type ExtError = UnrecoverableExtError;
290
291 fn into_backend_error(self) -> Result<Self::ExtError, Self> {
292 match self {
293 Self::Charge(err) => Ok(err.into()),
294 err => Err(err),
295 }
296 }
297}
298
299struct LazyGrowHandler<LP: LazyPagesInterface> {
300 old_mem_addr: Option<u64>,
301 old_mem_size: WasmPagesAmount,
302 _phantom: PhantomData<LP>,
303}
304
305impl<Context, LP: LazyPagesInterface> GrowHandler<Context> for LazyGrowHandler<LP> {
306 fn before_grow_action(ctx: &mut Context, mem: &mut impl Memory<Context>) -> Self {
307 let old_mem_addr = mem.get_buffer_host_addr(ctx);
311 LP::remove_lazy_pages_prot(ctx, mem);
312 Self {
313 old_mem_addr,
314 old_mem_size: mem.size(ctx),
315 _phantom: PhantomData,
316 }
317 }
318
319 fn after_grow_action(self, ctx: &mut Context, mem: &mut impl Memory<Context>) {
320 let new_mem_addr = mem.get_buffer_host_addr(ctx).unwrap_or_else(|| {
323 let err_msg = format!(
324 "LazyGrowHandler::after_grow_action: Memory size cannot be zero after grow is applied for memory. \
325 Old memory address - {:?}, old memory size - {:?}",
326 self.old_mem_addr, self.old_mem_size
327 );
328
329 log::error!("{err_msg}");
330 unreachable!("{err_msg}")
331 });
332 LP::update_lazy_pages_and_protect_again(
333 ctx,
334 mem,
335 self.old_mem_addr,
336 self.old_mem_size,
337 new_mem_addr,
338 );
339 }
340}
341
342#[must_use]
350struct ExtMutator<'a, LP: LazyPagesInterface> {
351 ext: &'a mut Ext<LP>,
352 gas_counter: GasCounter,
353 gas_allowance_counter: GasAllowanceCounter,
354 value_counter: ValueCounter,
355 outgoing_gasless: u64,
356 reservation_to_mark: Option<ReservationId>,
357}
358
359impl<LP: LazyPagesInterface> core::ops::Deref for ExtMutator<'_, LP> {
360 type Target = Ext<LP>;
361
362 fn deref(&self) -> &Self::Target {
363 self.ext
364 }
365}
366
367impl<'a, LP: LazyPagesInterface> ExtMutator<'a, LP> {
368 fn new(ext: &'a mut Ext<LP>) -> Self {
369 unsafe {
371 Self {
372 gas_counter: ext.context.gas_counter.clone(),
373 gas_allowance_counter: ext.context.gas_allowance_counter.clone(),
374 value_counter: ext.context.value_counter.clone(),
375 outgoing_gasless: ext.outgoing_gasless,
376 reservation_to_mark: None,
377 ext,
378 }
379 }
380 }
381
382 fn alloc<Context>(
383 &mut self,
384 ctx: &mut Context,
385 mem: &mut impl Memory<Context>,
386 pages: WasmPagesAmount,
387 ) -> Result<WasmPage, AllocError> {
388 let gas_for_call = self.context.costs.mem_grow.cost_for_one();
390 let gas_for_pages = self.context.costs.mem_grow_per_page;
391 self.ext
392 .context
393 .allocations_context
394 .alloc::<Context, LazyGrowHandler<LP>>(ctx, mem, pages, |pages| {
395 let cost = gas_for_call.saturating_add(gas_for_pages.cost_for(pages));
396 if self.gas_counter.charge_if_enough(cost) == ChargeResult::NotEnough {
398 return Err(ChargeError::GasLimitExceeded);
399 }
400
401 if self.gas_allowance_counter.charge_if_enough(cost) == ChargeResult::NotEnough {
402 return Err(ChargeError::GasAllowanceExceeded);
403 }
404 Ok(())
405 })
406 }
407
408 fn reduce_gas(&mut self, limit: GasLimit) -> Result<(), FallibleExtError> {
409 if self.gas_counter.reduce(limit) == ChargeResult::NotEnough {
410 return Err(FallibleExecutionError::NotEnoughGas.into());
411 }
412
413 Ok(())
414 }
415
416 fn charge_message_value(&mut self, value: u128) -> Result<(), FallibleExtError> {
417 if self.value_counter.reduce(value) == ChargeResult::NotEnough {
418 return Err(FallibleExecutionError::NotEnoughValue.into());
419 }
420
421 Ok(())
422 }
423
424 fn mark_reservation_used(
425 &mut self,
426 reservation_id: ReservationId,
427 ) -> Result<(), ReservationError> {
428 let _ = self
429 .ext
430 .context
431 .gas_reserver
432 .check_not_used(reservation_id)?;
433 self.reservation_to_mark = Some(reservation_id);
434 Ok(())
435 }
436
437 fn charge_gas_if_enough(&mut self, gas: u64) -> Result<(), ChargeError> {
438 if self.gas_counter.charge_if_enough(gas) == ChargeResult::NotEnough {
439 return Err(ChargeError::GasLimitExceeded);
440 }
441
442 if self.gas_allowance_counter.charge_if_enough(gas) == ChargeResult::NotEnough {
443 return Err(ChargeError::GasAllowanceExceeded);
444 }
445 Ok(())
446 }
447
448 fn charge_expiring_resources<T: Packet>(&mut self, packet: &T) -> Result<(), FallibleExtError> {
449 let reducing_gas_limit = self.get_reducing_gas_limit(packet)?;
450
451 self.reduce_gas(reducing_gas_limit)?;
452 self.charge_message_value(packet.value())
453 }
454
455 fn get_reducing_gas_limit<T: Packet>(&self, packet: &T) -> Result<u64, FallibleExtError> {
456 match T::kind() {
457 DispatchKind::Handle => {
458 let mailbox_threshold = self.context.mailbox_threshold;
464 let gas_limit = packet.gas_limit().unwrap_or(mailbox_threshold);
465
466 if gas_limit != 0 && gas_limit < mailbox_threshold {
468 return Err(MessageError::InsufficientGasLimit.into());
469 }
470
471 Ok(gas_limit)
472 }
473 DispatchKind::Init | DispatchKind::Reply => {
474 Ok(packet.gas_limit().unwrap_or(0))
484 }
485 DispatchKind::Signal => unreachable!("Signals can't be sent as a syscall"),
486 }
487 }
488
489 fn charge_sending_fee(&mut self, delay: u32) -> Result<(), ChargeError> {
490 if delay == 0 {
491 self.charge_gas_if_enough(self.context.message_context.settings().sending_fee)
492 } else {
493 self.charge_gas_if_enough(
494 self.context
495 .message_context
496 .settings()
497 .scheduled_sending_fee,
498 )
499 }
500 }
501
502 fn charge_for_dispatch_stash_hold(&mut self, delay: u32) -> Result<(), FallibleExtError> {
503 if delay != 0 {
504 let waiting_reserve = self
505 .context
506 .costs
507 .rent
508 .dispatch_stash
509 .cost_for(self.context.reserve_for.saturating_add(delay).into());
510
511 return self
513 .reduce_gas(waiting_reserve)
514 .map_err(|_| MessageError::InsufficientGasForDelayedSending.into());
515 }
516
517 Ok(())
518 }
519
520 fn apply(mut self) {
521 if let Some(reservation) = self.reservation_to_mark.take() {
522 let result = self.ext.context.gas_reserver.mark_used(reservation);
523 debug_assert!(result.is_ok());
524 }
525
526 self.ext.context.gas_counter = self.gas_counter;
527 self.ext.context.value_counter = self.value_counter;
528 self.ext.context.gas_allowance_counter = self.gas_allowance_counter;
529 self.ext.outgoing_gasless = self.outgoing_gasless;
530 }
531}
532
533pub struct Ext<LP: LazyPagesInterface> {
535 pub context: ProcessorContext,
537 pub current_counter: CounterType,
539 outgoing_gasless: u64,
543 _phantom: PhantomData<LP>,
544}
545
546impl<LP: LazyPagesInterface> ProcessorExternalities for Ext<LP> {
548 fn new(context: ProcessorContext) -> Self {
549 let current_counter = if context.gas_counter.left() <= context.gas_allowance_counter.left()
550 {
551 CounterType::GasLimit
552 } else {
553 CounterType::GasAllowance
554 };
555
556 Self {
557 context,
558 current_counter,
559 outgoing_gasless: 0,
560 _phantom: PhantomData,
561 }
562 }
563
564 fn into_ext_info<Context>(
565 self,
566 ctx: &mut Context,
567 memory: &impl Memory<Context>,
568 ) -> Result<ExtInfo, MemoryError> {
569 let ProcessorContext {
570 allocations_context,
571 message_context,
572 gas_counter,
573 gas_reserver,
574 system_reservation,
575 program_candidates_data,
576 ..
577 } = self.context;
578
579 let (static_pages, allocations, allocations_changed) = allocations_context.into_parts();
580
581 let mut accessed_pages = LP::get_write_accessed_pages();
583 accessed_pages.retain(|p| {
584 let wasm_page: WasmPage = p.to_page();
585 wasm_page < static_pages || allocations.contains(wasm_page)
586 });
587 log::trace!("accessed pages numbers = {:?}", accessed_pages);
588
589 let mut pages_data = BTreeMap::new();
590 for page in accessed_pages {
591 let mut buf = PageBuf::new_zeroed();
592 memory.read(ctx, page.offset(), &mut buf)?;
593 pages_data.insert(page, buf);
594 }
595
596 let (outcome, mut context_store) = message_context.drain();
597 let ContextOutcomeDrain {
598 outgoing_dispatches: generated_dispatches,
599 awakening,
600 reply_deposits,
601 reply_sent,
602 } = outcome.drain();
603
604 let system_reservation_context = SystemReservationContext {
605 current_reservation: system_reservation,
606 previous_reservation: context_store.system_reservation(),
607 };
608
609 context_store.set_reservation_nonce(&gas_reserver);
610 if let Some(reservation) = system_reservation {
611 context_store.add_system_reservation(reservation);
612 }
613
614 let info = ExtInfo {
615 gas_amount: gas_counter.to_amount(),
616 gas_reserver,
617 system_reservation_context,
618 allocations: allocations_changed.then_some(allocations),
620 pages_data,
621 generated_dispatches,
622 awakening,
623 reply_deposits,
624 context_store,
625 program_candidates_data,
626 reply_sent,
627 };
628 Ok(info)
629 }
630
631 fn lazy_pages_init_for_program<Context>(
632 ctx: &mut Context,
633 mem: &mut impl Memory<Context>,
634 prog_id: ProgramId,
635 memory_infix: MemoryInfix,
636 stack_end: Option<WasmPage>,
637 globals_config: GlobalsAccessConfig,
638 lazy_pages_costs: LazyPagesCosts,
639 ) {
640 LP::init_for_program(
641 ctx,
642 mem,
643 prog_id,
644 memory_infix,
645 stack_end,
646 globals_config,
647 lazy_pages_costs,
648 );
649 }
650
651 fn lazy_pages_post_execution_actions<Context>(
652 ctx: &mut Context,
653 mem: &mut impl Memory<Context>,
654 ) {
655 LP::remove_lazy_pages_prot(ctx, mem);
656 }
657
658 fn lazy_pages_status() -> Status {
659 LP::get_status()
660 }
661}
662
663impl<LP: LazyPagesInterface> BackendExternalities for Ext<LP> {
664 fn gas_amount(&self) -> GasAmount {
665 self.context.gas_counter.to_amount()
666 }
667
668 fn pre_process_memory_accesses(
669 &mut self,
670 reads: &[MemoryInterval],
671 writes: &[MemoryInterval],
672 gas_counter: &mut u64,
673 ) -> Result<(), ProcessAccessError> {
674 LP::pre_process_memory_accesses(reads, writes, gas_counter)
675 }
676}
677
678impl<LP: LazyPagesInterface> Ext<LP> {
679 fn with_changes<F, R, E>(&mut self, callback: F) -> Result<R, E>
680 where
681 F: FnOnce(&mut ExtMutator<LP>) -> Result<R, E>,
682 {
683 let mut mutator = ExtMutator::new(self);
684 let result = callback(&mut mutator)?;
685 mutator.apply();
686 Ok(result)
687 }
688
689 fn check_reservation_gas_limit_for_delayed_sending(
692 &self,
693 reservation_id: &ReservationId,
694 delay: u32,
695 ) -> Result<(), FallibleExtError> {
696 if delay != 0 {
697 let limit = self
698 .context
699 .gas_reserver
700 .limit_of(reservation_id)
701 .ok_or(ReservationError::InvalidReservationId)?;
702
703 let waiting_reserve = self
704 .context
705 .costs
706 .rent
707 .dispatch_stash
708 .cost_for(self.context.reserve_for.saturating_add(delay).into());
709
710 if limit < waiting_reserve.saturating_add(self.context.mailbox_threshold) {
715 return Err(MessageError::InsufficientGasForDelayedSending.into());
716 }
717 }
718
719 Ok(())
720 }
721
722 fn check_forbidden_destination(&self, id: ProgramId) -> Result<(), FallibleExtError> {
723 if id == ProgramId::SYSTEM {
724 Err(FallibleExtError::ForbiddenFunction)
725 } else {
726 Ok(())
727 }
728 }
729
730 fn charge_gas_if_enough(
731 gas_counter: &mut GasCounter,
732 gas_allowance_counter: &mut GasAllowanceCounter,
733 amount: u64,
734 ) -> Result<(), ChargeError> {
735 if gas_counter.charge_if_enough(amount) != ChargeResult::Enough {
736 return Err(ChargeError::GasLimitExceeded);
737 }
738 if gas_allowance_counter.charge_if_enough(amount) != ChargeResult::Enough {
739 return Err(ChargeError::GasAllowanceExceeded);
743 }
744 Ok(())
745 }
746
747 fn cost_for_reservation(&self, amount: u64, duration: u32) -> u64 {
748 self.context
749 .costs
750 .rent
751 .reservation
752 .cost_for(self.context.reserve_for.saturating_add(duration).into())
753 .saturating_add(amount)
754 }
755}
756
757impl<LP: LazyPagesInterface> CountersOwner for Ext<LP> {
758 fn charge_gas_for_token(&mut self, token: CostToken) -> Result<(), ChargeError> {
759 let amount = self.context.costs.syscalls.cost_for_token(token);
760 let common_charge = self.context.gas_counter.charge(amount);
761 let allowance_charge = self.context.gas_allowance_counter.charge(amount);
762 match (common_charge, allowance_charge) {
763 (ChargeResult::NotEnough, _) => Err(ChargeError::GasLimitExceeded),
764 (ChargeResult::Enough, ChargeResult::NotEnough) => {
765 Err(ChargeError::GasAllowanceExceeded)
766 }
767 (ChargeResult::Enough, ChargeResult::Enough) => Ok(()),
768 }
769 }
770
771 fn charge_gas_if_enough(&mut self, amount: u64) -> Result<(), ChargeError> {
772 Self::charge_gas_if_enough(
773 &mut self.context.gas_counter,
774 &mut self.context.gas_allowance_counter,
775 amount,
776 )
777 }
778
779 fn gas_left(&self) -> GasLeft {
780 (
781 self.context.gas_counter.left(),
782 self.context.gas_allowance_counter.left(),
783 )
784 .into()
785 }
786
787 fn current_counter_type(&self) -> CounterType {
788 self.current_counter
789 }
790
791 fn decrease_current_counter_to(&mut self, amount: u64) {
792 if self.current_counter_value() <= amount {
801 log::trace!("Skipped decrease to global value");
802 return;
803 }
804
805 let GasLeft { gas, allowance } = self.gas_left();
806
807 let diff = match self.current_counter_type() {
808 CounterType::GasLimit => gas.checked_sub(amount),
809 CounterType::GasAllowance => allowance.checked_sub(amount),
810 }
811 .unwrap_or_else(|| {
812 let err_msg = format!(
813 "CounterOwner::decrease_current_counter_to: Checked sub operation overflowed. \
814 Message id - {message_id}, program id - {program_id}, current counter type - {current_counter_type:?}, \
815 gas - {gas}, allowance - {allowance}, amount - {amount}",
816 message_id = self.context.message_context.current().id(), program_id = self.context.program_id, current_counter_type = self.current_counter_type()
817 );
818
819 log::error!("{err_msg}");
820 unreachable!("{err_msg}")
821 });
822
823 if self.context.gas_counter.charge(diff) == ChargeResult::NotEnough {
824 let err_msg = format!(
825 "CounterOwner::decrease_current_counter_to: Tried to set gas limit left bigger than before. \
826 Message id - {message_id}, program id - {program_id}, gas counter - {gas_counter:?}, diff - {diff}",
827 message_id = self.context.message_context.current().id(),
828 program_id = self.context.program_id,
829 gas_counter = self.context.gas_counter
830 );
831
832 log::error!("{err_msg}");
833 unreachable!("{err_msg}")
834 }
835
836 if self.context.gas_allowance_counter.charge(diff) == ChargeResult::NotEnough {
837 let err_msg = format!(
838 "CounterOwner::decrease_current_counter_to: Tried to set gas allowance left bigger than before. \
839 Message id - {message_id}, program id - {program_id}, gas allowance counter - {gas_allowance_counter:?}, diff - {diff}",
840 message_id = self.context.message_context.current().id(),
841 program_id = self.context.program_id,
842 gas_allowance_counter = self.context.gas_allowance_counter,
843 );
844
845 log::error!("{err_msg}");
846 unreachable!("{err_msg}")
847 }
848 }
849
850 fn define_current_counter(&mut self) -> u64 {
851 let GasLeft { gas, allowance } = self.gas_left();
852
853 if gas <= allowance {
854 self.current_counter = CounterType::GasLimit;
855 gas
856 } else {
857 self.current_counter = CounterType::GasAllowance;
858 allowance
859 }
860 }
861}
862
863impl<LP: LazyPagesInterface> Externalities for Ext<LP> {
864 type UnrecoverableError = UnrecoverableExtError;
865 type FallibleError = FallibleExtError;
866 type AllocError = AllocExtError;
867
868 fn alloc<Context>(
869 &mut self,
870 ctx: &mut Context,
871 mem: &mut impl Memory<Context>,
872 pages_num: u32,
873 ) -> Result<WasmPage, Self::AllocError> {
874 let pages = WasmPagesAmount::try_from(pages_num)
875 .map_err(|_| AllocError::ProgramAllocOutOfBounds)?;
876
877 self.with_changes(|mutator| {
878 mutator
879 .alloc::<Context>(ctx, mem, pages)
880 .map_err(Into::into)
881 })
882 }
883
884 fn free(&mut self, page: WasmPage) -> Result<(), Self::AllocError> {
885 self.context
886 .allocations_context
887 .free(page)
888 .map_err(Into::into)
889 }
890
891 fn free_range(&mut self, start: WasmPage, end: WasmPage) -> Result<(), Self::AllocError> {
892 let interval = Interval::try_from(start..=end)
893 .map_err(|_| AllocExtError::Alloc(AllocError::InvalidFreeRange(start, end)))?;
894 self.with_changes(|mutator| {
895 mutator.charge_gas_if_enough(
896 mutator
897 .context
898 .costs
899 .syscalls
900 .free_range_per_page
901 .cost_for(interval.len()),
902 )?;
903 mutator
904 .ext
905 .context
906 .allocations_context
907 .free_range(interval)
908 .map_err(Into::into)
909 })
910 }
911
912 fn env_vars(&self, version: u32) -> Result<EnvVars, Self::UnrecoverableError> {
913 match version {
914 1 => Ok(EnvVars::V1(EnvVarsV1 {
915 performance_multiplier: self.context.performance_multiplier,
916 existential_deposit: self.context.existential_deposit,
917 mailbox_threshold: self.context.mailbox_threshold,
918 gas_multiplier: self.context.gas_multiplier,
919 })),
920 _ => Err(UnrecoverableExecutionError::UnsupportedEnvVarsVersion.into()),
921 }
922 }
923
924 fn block_height(&self) -> Result<u32, Self::UnrecoverableError> {
925 Ok(self.context.block_info.height)
926 }
927
928 fn block_timestamp(&self) -> Result<u64, Self::UnrecoverableError> {
929 Ok(self.context.block_info.timestamp)
930 }
931
932 fn send_init(&mut self) -> Result<u32, Self::FallibleError> {
933 let handle = self.context.message_context.send_init()?;
934 Ok(handle)
935 }
936
937 fn send_push(&mut self, handle: u32, buffer: &[u8]) -> Result<(), Self::FallibleError> {
938 self.context.message_context.send_push(handle, buffer)?;
939 Ok(())
940 }
941
942 fn send_push_input(
943 &mut self,
944 handle: u32,
945 offset: u32,
946 len: u32,
947 ) -> Result<(), Self::FallibleError> {
948 let range = self
949 .context
950 .message_context
951 .check_input_range(offset, len)?;
952
953 self.with_changes(|mutator| {
954 mutator.charge_gas_if_enough(
955 mutator
956 .context
957 .costs
958 .syscalls
959 .gr_send_push_input_per_byte
960 .cost_for(range.len().into()),
961 )?;
962 mutator
963 .ext
964 .context
965 .message_context
966 .send_push_input(handle, range)
967 .map_err(Into::into)
968 })
969 }
970
971 fn send_commit(
972 &mut self,
973 handle: u32,
974 msg: HandlePacket,
975 delay: u32,
976 ) -> Result<MessageId, Self::FallibleError> {
977 self.with_changes(|mutator| {
978 mutator.check_forbidden_destination(msg.destination())?;
979 mutator.charge_expiring_resources(&msg)?;
980 mutator.charge_sending_fee(delay)?;
981 mutator.charge_for_dispatch_stash_hold(delay)?;
982
983 mutator
984 .ext
985 .context
986 .message_context
987 .send_commit(handle, msg, delay, None)
988 .map_err(Into::into)
989 })
990 }
991
992 fn reservation_send_commit(
993 &mut self,
994 id: ReservationId,
995 handle: u32,
996 msg: HandlePacket,
997 delay: u32,
998 ) -> Result<MessageId, Self::FallibleError> {
999 self.with_changes(|mutator| {
1000 mutator.check_forbidden_destination(msg.destination())?;
1001 mutator.check_reservation_gas_limit_for_delayed_sending(&id, delay)?;
1004 mutator.charge_message_value(msg.value())?;
1006 mutator.charge_sending_fee(delay)?;
1007
1008 mutator.mark_reservation_used(id)?;
1009
1010 mutator
1011 .ext
1012 .context
1013 .message_context
1014 .send_commit(handle, msg, delay, Some(id))
1015 .map_err(Into::into)
1016 })
1017 }
1018
1019 fn reply_push(&mut self, buffer: &[u8]) -> Result<(), Self::FallibleError> {
1020 self.context.message_context.reply_push(buffer)?;
1021 Ok(())
1022 }
1023
1024 fn reply_commit(&mut self, msg: ReplyPacket) -> Result<MessageId, Self::FallibleError> {
1026 self.with_changes(|mutator| {
1027 mutator
1028 .check_forbidden_destination(mutator.context.message_context.reply_destination())?;
1029 mutator.charge_expiring_resources(&msg)?;
1030 mutator.charge_sending_fee(0)?;
1031
1032 mutator
1033 .ext
1034 .context
1035 .message_context
1036 .reply_commit(msg, None)
1037 .map_err(Into::into)
1038 })
1039 }
1040
1041 fn reservation_reply_commit(
1042 &mut self,
1043 id: ReservationId,
1044 msg: ReplyPacket,
1045 ) -> Result<MessageId, Self::FallibleError> {
1046 self.with_changes(|mutator| {
1047 mutator
1048 .check_forbidden_destination(mutator.context.message_context.reply_destination())?;
1049 mutator.charge_message_value(msg.value())?;
1051 mutator.charge_sending_fee(0)?;
1052
1053 mutator.mark_reservation_used(id)?;
1054
1055 mutator
1056 .ext
1057 .context
1058 .message_context
1059 .reply_commit(msg, Some(id))
1060 .map_err(Into::into)
1061 })
1062 }
1063
1064 fn reply_to(&self) -> Result<MessageId, Self::FallibleError> {
1065 self.context
1066 .message_context
1067 .current()
1068 .details()
1069 .and_then(|d| d.to_reply_details().map(|d| d.to_message_id()))
1070 .ok_or_else(|| FallibleExecutionError::NoReplyContext.into())
1071 }
1072
1073 fn signal_from(&self) -> Result<MessageId, Self::FallibleError> {
1074 self.context
1075 .message_context
1076 .current()
1077 .details()
1078 .and_then(|d| d.to_signal_details().map(|d| d.to_message_id()))
1079 .ok_or_else(|| FallibleExecutionError::NoSignalContext.into())
1080 }
1081
1082 fn reply_push_input(&mut self, offset: u32, len: u32) -> Result<(), Self::FallibleError> {
1083 self.with_changes(|mutator| {
1084 let range = mutator
1085 .context
1086 .message_context
1087 .check_input_range(offset, len)?;
1088 mutator.charge_gas_if_enough(
1089 mutator
1090 .context
1091 .costs
1092 .syscalls
1093 .gr_reply_push_input_per_byte
1094 .cost_for(range.len().into()),
1095 )?;
1096 mutator
1097 .ext
1098 .context
1099 .message_context
1100 .reply_push_input(range)
1101 .map_err(Into::into)
1102 })
1103 }
1104
1105 fn source(&self) -> Result<ProgramId, Self::UnrecoverableError> {
1106 Ok(self.context.message_context.current().source())
1107 }
1108
1109 fn reply_code(&self) -> Result<ReplyCode, Self::FallibleError> {
1110 self.context
1111 .message_context
1112 .current()
1113 .details()
1114 .and_then(|d| d.to_reply_details().map(|d| d.to_reply_code()))
1115 .ok_or_else(|| FallibleExecutionError::NoReplyContext.into())
1116 }
1117
1118 fn signal_code(&self) -> Result<SignalCode, Self::FallibleError> {
1119 self.context
1120 .message_context
1121 .current()
1122 .details()
1123 .and_then(|d| d.to_signal_details().map(|d| d.to_signal_code()))
1124 .ok_or_else(|| FallibleExecutionError::NoSignalContext.into())
1125 }
1126
1127 fn message_id(&self) -> Result<MessageId, Self::UnrecoverableError> {
1128 Ok(self.context.message_context.current().id())
1129 }
1130
1131 fn program_id(&self) -> Result<ProgramId, Self::UnrecoverableError> {
1132 Ok(self.context.program_id)
1133 }
1134
1135 fn debug(&self, data: &str) -> Result<(), Self::UnrecoverableError> {
1136 let program_id = self.program_id()?;
1137 let message_id = self.message_id()?;
1138
1139 log::debug!(target: "gwasm", "[handle({message_id:.2?})] {program_id:.2?}: {data}");
1140
1141 Ok(())
1142 }
1143
1144 fn lock_payload(&mut self, at: u32, len: u32) -> Result<PayloadSliceLock, Self::FallibleError> {
1145 self.with_changes(|mutator| {
1146 let end = at
1147 .checked_add(len)
1148 .ok_or(FallibleExecutionError::TooBigReadLen)?;
1149 mutator.charge_gas_if_enough(
1150 mutator
1151 .context
1152 .costs
1153 .syscalls
1154 .gr_read_per_byte
1155 .cost_for(len.into()),
1156 )?;
1157 PayloadSliceLock::try_new((at, end), &mut mutator.ext.context.message_context)
1158 .ok_or_else(|| FallibleExecutionError::ReadWrongRange.into())
1159 })
1160 }
1161
1162 fn unlock_payload(&mut self, payload_holder: &mut PayloadSliceLock) -> UnlockPayloadBound {
1163 UnlockPayloadBound::from((&mut self.context.message_context, payload_holder))
1164 }
1165
1166 fn size(&self) -> Result<usize, Self::UnrecoverableError> {
1167 Ok(self.context.message_context.current().payload_bytes().len())
1168 }
1169
1170 fn reserve_gas(
1171 &mut self,
1172 amount: u64,
1173 duration: u32,
1174 ) -> Result<ReservationId, Self::FallibleError> {
1175 self.with_changes(|mutator| {
1176 mutator
1177 .charge_gas_if_enough(mutator.context.message_context.settings().reservation_fee)?;
1178
1179 if duration == 0 {
1180 return Err(ReservationError::ZeroReservationDuration.into());
1181 }
1182
1183 if amount < mutator.context.mailbox_threshold {
1184 return Err(ReservationError::ReservationBelowMailboxThreshold.into());
1185 }
1186
1187 let reduce_amount = mutator.cost_for_reservation(amount, duration);
1188
1189 mutator
1190 .reduce_gas(reduce_amount)
1191 .map_err(|_| FallibleExecutionError::NotEnoughGas)?;
1192
1193 mutator
1194 .ext
1195 .context
1196 .gas_reserver
1197 .reserve(amount, duration)
1198 .map_err(Into::into)
1199 })
1200 }
1201
1202 #[allow(clippy::obfuscated_if_else)]
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 fn msg_ctx(&self) -> &MessageContext {
1425 &self.context.message_context
1426 }
1427}
1428
1429#[cfg(test)]
1430mod tests {
1431 use super::*;
1432 use alloc::vec;
1433 use gear_core::{
1434 costs::{CostOf, RentCosts, SyscallCosts},
1435 message::{ContextSettings, IncomingDispatch, Payload, MAX_PAYLOAD_SIZE},
1436 reservation::{GasReservationMap, GasReservationSlot, GasReservationState},
1437 };
1438
1439 struct MessageContextBuilder {
1440 incoming_dispatch: IncomingDispatch,
1441 program_id: ProgramId,
1442 context_settings: ContextSettings,
1443 }
1444
1445 type Ext = super::Ext<()>;
1446
1447 impl MessageContextBuilder {
1448 fn new() -> Self {
1449 Self {
1450 incoming_dispatch: Default::default(),
1451 program_id: Default::default(),
1452 context_settings: ContextSettings::with_outgoing_limits(u32::MAX, u32::MAX),
1453 }
1454 }
1455
1456 fn build(self) -> MessageContext {
1457 MessageContext::new(
1458 self.incoming_dispatch,
1459 self.program_id,
1460 self.context_settings,
1461 )
1462 }
1463
1464 fn with_outgoing_limit(mut self, outgoing_limit: u32) -> Self {
1465 self.context_settings.outgoing_limit = outgoing_limit;
1466
1467 self
1468 }
1469 }
1470
1471 struct ProcessorContextBuilder(ProcessorContext);
1472
1473 impl ProcessorContextBuilder {
1474 fn new() -> Self {
1475 Self(ProcessorContext::new_mock())
1476 }
1477
1478 fn build(self) -> ProcessorContext {
1479 self.0
1480 }
1481
1482 fn with_message_context(mut self, context: MessageContext) -> Self {
1483 self.0.message_context = context;
1484
1485 self
1486 }
1487
1488 fn with_gas(mut self, gas_counter: GasCounter) -> Self {
1489 self.0.gas_counter = gas_counter;
1490
1491 self
1492 }
1493
1494 fn with_allowance(mut self, gas_allowance_counter: GasAllowanceCounter) -> Self {
1495 self.0.gas_allowance_counter = gas_allowance_counter;
1496
1497 self
1498 }
1499
1500 fn with_costs(mut self, costs: ExtCosts) -> Self {
1501 self.0.costs = costs;
1502
1503 self
1504 }
1505
1506 fn with_allocation_context(mut self, ctx: AllocationsContext) -> Self {
1507 self.0.allocations_context = ctx;
1508
1509 self
1510 }
1511
1512 fn with_existential_deposit(mut self, ed: u128) -> Self {
1513 self.0.existential_deposit = ed;
1514
1515 self
1516 }
1517
1518 fn with_value(mut self, value: u128) -> Self {
1519 self.0.value_counter = ValueCounter::new(value);
1520
1521 self
1522 }
1523
1524 fn with_reservations_map(mut self, map: GasReservationMap) -> Self {
1525 self.0.gas_reserver = GasReserver::new(&Default::default(), map, 256);
1526
1527 self
1528 }
1529 }
1530
1531 #[test]
1533 fn free_no_refund() {
1534 let initial_gas = 100;
1536 let initial_allowance = 10000;
1537
1538 let gas_left = (initial_gas, initial_allowance).into();
1539
1540 let existing_page = 99.into();
1541 let non_existing_page = 100.into();
1542
1543 let allocations_context = AllocationsContext::try_new(
1544 512.into(),
1545 [existing_page].into_iter().collect(),
1546 1.into(),
1547 None,
1548 512.into(),
1549 )
1550 .unwrap();
1551
1552 let mut ext = Ext::new(
1553 ProcessorContextBuilder::new()
1554 .with_gas(GasCounter::new(initial_gas))
1555 .with_allowance(GasAllowanceCounter::new(initial_allowance))
1556 .with_allocation_context(allocations_context)
1557 .build(),
1558 );
1559
1560 assert!(ext.free(existing_page).is_ok());
1563 assert_eq!(ext.gas_left(), gas_left);
1564
1565 assert_eq!(
1568 ext.free(non_existing_page),
1569 Err(AllocExtError::Alloc(AllocError::InvalidFree(
1570 non_existing_page
1571 )))
1572 );
1573 assert_eq!(ext.gas_left(), gas_left);
1574 }
1575
1576 #[test]
1577 fn test_counter_zeroes() {
1578 let free_cost = 1000;
1580 let ext_costs = ExtCosts {
1581 syscalls: SyscallCosts {
1582 free: free_cost.into(),
1583 ..Default::default()
1584 },
1585 ..Default::default()
1586 };
1587
1588 let initial_gas = free_cost - 1;
1589 let initial_allowance = free_cost + 1;
1590
1591 let mut lack_gas_ext = Ext::new(
1592 ProcessorContextBuilder::new()
1593 .with_gas(GasCounter::new(initial_gas))
1594 .with_allowance(GasAllowanceCounter::new(initial_allowance))
1595 .with_costs(ext_costs.clone())
1596 .build(),
1597 );
1598
1599 assert_eq!(
1600 lack_gas_ext.charge_gas_for_token(CostToken::Free),
1601 Err(ChargeError::GasLimitExceeded),
1602 );
1603
1604 let gas_amount = lack_gas_ext.gas_amount();
1605 let allowance = lack_gas_ext.context.gas_allowance_counter.left();
1606 assert_eq!(0, gas_amount.left());
1608 assert_eq!(initial_gas, gas_amount.burned());
1609 assert_eq!(initial_allowance - free_cost, allowance);
1610
1611 let initial_gas = free_cost;
1612 let initial_allowance = free_cost - 1;
1613
1614 let mut lack_allowance_ext = Ext::new(
1615 ProcessorContextBuilder::new()
1616 .with_gas(GasCounter::new(initial_gas))
1617 .with_allowance(GasAllowanceCounter::new(initial_allowance))
1618 .with_costs(ext_costs)
1619 .build(),
1620 );
1621
1622 assert_eq!(
1623 lack_allowance_ext.charge_gas_for_token(CostToken::Free),
1624 Err(ChargeError::GasAllowanceExceeded),
1625 );
1626
1627 let gas_amount = lack_allowance_ext.gas_amount();
1628 let allowance = lack_allowance_ext.context.gas_allowance_counter.left();
1629 assert_eq!(initial_gas - free_cost, gas_amount.left());
1630 assert_eq!(initial_gas, gas_amount.burned());
1631 assert_eq!(0, allowance);
1633 }
1634
1635 #[test]
1636 fn test_send_commit() {
1643 let mut ext = Ext::new(
1644 ProcessorContextBuilder::new()
1645 .with_message_context(MessageContextBuilder::new().with_outgoing_limit(1).build())
1646 .build(),
1647 );
1648
1649 let data = HandlePacket::default();
1650
1651 let fake_handle = 0;
1652
1653 let msg = ext.send_commit(fake_handle, data.clone(), 0);
1654 assert_eq!(
1655 msg.unwrap_err(),
1656 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::OutOfBounds))
1657 );
1658
1659 let handle = ext.send_init().expect("Outgoing limit is 1");
1660
1661 let msg = ext.send_commit(handle, data.clone(), 0);
1662 assert!(msg.is_ok());
1663
1664 let msg = ext.send_commit(handle, data, 0);
1665 assert_eq!(
1666 msg.unwrap_err(),
1667 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1668 );
1669
1670 let handle = ext.send_init();
1671 assert_eq!(
1672 handle.unwrap_err(),
1673 FallibleExtError::Core(FallibleExtErrorCore::Message(
1674 MessageError::OutgoingMessagesAmountLimitExceeded
1675 ))
1676 );
1677 }
1678
1679 #[test]
1680 fn test_send_push() {
1688 let mut ext = Ext::new(
1689 ProcessorContextBuilder::new()
1690 .with_message_context(MessageContextBuilder::new().build())
1691 .build(),
1692 );
1693
1694 let data = HandlePacket::default();
1695
1696 let fake_handle = 0;
1697
1698 let res = ext.send_push(fake_handle, &[0, 0, 0]);
1699 assert_eq!(
1700 res.unwrap_err(),
1701 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::OutOfBounds))
1702 );
1703
1704 let handle = ext.send_init().expect("Outgoing limit is u32::MAX");
1705
1706 let res = ext.send_push(handle, &[1, 2, 3]);
1707 assert!(res.is_ok());
1708
1709 let res = ext.send_push(handle, &[4, 5, 6]);
1710 assert!(res.is_ok());
1711
1712 let large_payload = vec![0u8; MAX_PAYLOAD_SIZE + 1];
1713
1714 let res = ext.send_push(handle, &large_payload);
1715 assert_eq!(
1716 res.unwrap_err(),
1717 FallibleExtError::Core(FallibleExtErrorCore::Message(
1718 MessageError::MaxMessageSizeExceed
1719 ))
1720 );
1721
1722 let msg = ext.send_commit(handle, data, 0);
1723 assert!(msg.is_ok());
1724
1725 let res = ext.send_push(handle, &[7, 8, 9]);
1726 assert_eq!(
1727 res.unwrap_err(),
1728 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1729 );
1730
1731 let (outcome, _) = ext.context.message_context.drain();
1732 let ContextOutcomeDrain {
1733 mut outgoing_dispatches,
1734 ..
1735 } = outcome.drain();
1736 let dispatch = outgoing_dispatches
1737 .pop()
1738 .map(|(dispatch, _, _)| dispatch)
1739 .expect("Send commit was ok");
1740
1741 assert_eq!(dispatch.message().payload_bytes(), &[1, 2, 3, 4, 5, 6]);
1742 }
1743
1744 #[test]
1745 fn test_send_push_input() {
1752 let mut ext = Ext::new(
1753 ProcessorContextBuilder::new()
1754 .with_message_context(MessageContextBuilder::new().build())
1755 .build(),
1756 );
1757
1758 let res = ext
1759 .context
1760 .message_context
1761 .payload_mut()
1762 .try_extend_from_slice(&[1, 2, 3, 4, 5, 6]);
1763 assert!(res.is_ok());
1764
1765 let fake_handle = 0;
1766
1767 let res = ext.send_push_input(fake_handle, 0, 1);
1768 assert_eq!(
1769 res.unwrap_err(),
1770 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::OutOfBounds))
1771 );
1772
1773 let handle = ext.send_init().expect("Outgoing limit is u32::MAX");
1774
1775 let res = ext.send_push_input(handle, 2, 3);
1776 assert!(res.is_ok());
1777 let res = ext.send_push_input(handle, 5, 1);
1778 assert!(res.is_ok());
1779
1780 let res = ext.send_push_input(handle, 0, 7);
1782 assert_eq!(
1783 res.unwrap_err(),
1784 FallibleExtError::Core(FallibleExtErrorCore::Message(
1785 MessageError::OutOfBoundsInputSliceLength
1786 ))
1787 );
1788 let res = ext.send_push_input(handle, 5, 2);
1789 assert_eq!(
1790 res.unwrap_err(),
1791 FallibleExtError::Core(FallibleExtErrorCore::Message(
1792 MessageError::OutOfBoundsInputSliceLength
1793 ))
1794 );
1795
1796 let res = ext.send_push_input(handle, 6, 0);
1798 assert_eq!(
1799 res.unwrap_err(),
1800 FallibleExtError::Core(FallibleExtErrorCore::Message(
1801 MessageError::OutOfBoundsInputSliceOffset
1802 ))
1803 );
1804
1805 let msg = ext.send_commit(handle, HandlePacket::default(), 0);
1806 assert!(msg.is_ok());
1807
1808 let res = ext.send_push_input(handle, 0, 1);
1809 assert_eq!(
1810 res.unwrap_err(),
1811 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1812 );
1813
1814 let (outcome, _) = ext.context.message_context.drain();
1815 let ContextOutcomeDrain {
1816 mut outgoing_dispatches,
1817 ..
1818 } = outcome.drain();
1819 let dispatch = outgoing_dispatches
1820 .pop()
1821 .map(|(dispatch, _, _)| dispatch)
1822 .expect("Send commit was ok");
1823
1824 assert_eq!(dispatch.message().payload_bytes(), &[3, 4, 5, 6]);
1825 }
1826
1827 #[test]
1828 fn test_reply_commit() {
1835 let mut ext = Ext::new(
1836 ProcessorContextBuilder::new()
1837 .with_gas(GasCounter::new(u64::MAX))
1838 .with_message_context(MessageContextBuilder::new().build())
1839 .build(),
1840 );
1841
1842 let res = ext.reply_push(&[0]);
1843 assert!(res.is_ok());
1844
1845 let res = ext.reply_commit(ReplyPacket::new(Payload::filled_with(0), 0));
1846 assert_eq!(
1847 res.unwrap_err(),
1848 FallibleExtError::Core(FallibleExtErrorCore::Message(
1849 MessageError::MaxMessageSizeExceed
1850 ))
1851 );
1852
1853 let res = ext.reply_commit(ReplyPacket::auto());
1854 assert!(res.is_ok());
1855
1856 let res = ext.reply_commit(ReplyPacket::auto());
1857 assert_eq!(
1858 res.unwrap_err(),
1859 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::DuplicateReply))
1860 );
1861 }
1862
1863 #[test]
1864 fn test_reply_push() {
1872 let mut ext = Ext::new(
1873 ProcessorContextBuilder::new()
1874 .with_gas(GasCounter::new(u64::MAX))
1875 .with_message_context(MessageContextBuilder::new().build())
1876 .build(),
1877 );
1878
1879 let res = ext.reply_push(&[1, 2, 3]);
1880 assert!(res.is_ok());
1881
1882 let res = ext.reply_push(&[4, 5, 6]);
1883 assert!(res.is_ok());
1884
1885 let large_payload = vec![0u8; MAX_PAYLOAD_SIZE + 1];
1886
1887 let res = ext.reply_push(&large_payload);
1888 assert_eq!(
1889 res.unwrap_err(),
1890 FallibleExtError::Core(FallibleExtErrorCore::Message(
1891 MessageError::MaxMessageSizeExceed
1892 ))
1893 );
1894
1895 let res = ext.reply_commit(ReplyPacket::auto());
1896 assert!(res.is_ok());
1897
1898 let res = ext.reply_push(&[7, 8, 9]);
1899 assert_eq!(
1900 res.unwrap_err(),
1901 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1902 );
1903
1904 let (outcome, _) = ext.context.message_context.drain();
1905 let ContextOutcomeDrain {
1906 mut outgoing_dispatches,
1907 ..
1908 } = outcome.drain();
1909 let dispatch = outgoing_dispatches
1910 .pop()
1911 .map(|(dispatch, _, _)| dispatch)
1912 .expect("Send commit was ok");
1913
1914 assert_eq!(dispatch.message().payload_bytes(), &[1, 2, 3, 4, 5, 6]);
1915 }
1916
1917 #[test]
1918 fn test_reply_push_input() {
1924 let mut ext = Ext::new(
1925 ProcessorContextBuilder::new()
1926 .with_message_context(MessageContextBuilder::new().build())
1927 .build(),
1928 );
1929
1930 let res = ext
1931 .context
1932 .message_context
1933 .payload_mut()
1934 .try_extend_from_slice(&[1, 2, 3, 4, 5, 6]);
1935 assert!(res.is_ok());
1936
1937 let res = ext.reply_push_input(2, 3);
1938 assert!(res.is_ok());
1939 let res = ext.reply_push_input(5, 1);
1940 assert!(res.is_ok());
1941
1942 let res = ext.reply_push_input(0, 7);
1944 assert_eq!(
1945 res.unwrap_err(),
1946 FallibleExtError::Core(FallibleExtErrorCore::Message(
1947 MessageError::OutOfBoundsInputSliceLength
1948 ))
1949 );
1950 let res = ext.reply_push_input(5, 2);
1951 assert_eq!(
1952 res.unwrap_err(),
1953 FallibleExtError::Core(FallibleExtErrorCore::Message(
1954 MessageError::OutOfBoundsInputSliceLength
1955 ))
1956 );
1957
1958 let res = ext.reply_push_input(6, 0);
1960 assert_eq!(
1961 res.unwrap_err(),
1962 FallibleExtError::Core(FallibleExtErrorCore::Message(
1963 MessageError::OutOfBoundsInputSliceOffset
1964 ))
1965 );
1966
1967 let msg = ext.reply_commit(ReplyPacket::auto());
1968 assert!(msg.is_ok());
1969
1970 let res = ext.reply_push_input(0, 1);
1971 assert_eq!(
1972 res.unwrap_err(),
1973 FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1974 );
1975
1976 let (outcome, _) = ext.context.message_context.drain();
1977 let ContextOutcomeDrain {
1978 mut outgoing_dispatches,
1979 ..
1980 } = outcome.drain();
1981 let dispatch = outgoing_dispatches
1982 .pop()
1983 .map(|(dispatch, _, _)| dispatch)
1984 .expect("Send commit was ok");
1985
1986 assert_eq!(dispatch.message().payload_bytes(), &[3, 4, 5, 6]);
1987 }
1988
1989 #[test]
1991 fn gas_has_gone_on_err() {
1992 const INIT_GAS: u64 = 1_000_000_000;
1993
1994 let mut ext = Ext::new(
1995 ProcessorContextBuilder::new()
1996 .with_message_context(
1997 MessageContextBuilder::new()
1998 .with_outgoing_limit(u32::MAX)
1999 .build(),
2000 )
2001 .with_gas(GasCounter::new(INIT_GAS))
2002 .build(),
2003 );
2004
2005 let i = ext.send_init().expect("Shouldn't fail");
2007
2008 assert_eq!(
2011 ext.send_commit(
2012 i,
2013 HandlePacket::new_with_gas(
2014 Default::default(),
2015 Default::default(),
2016 INIT_GAS,
2017 u128::MAX
2018 ),
2019 0
2020 )
2021 .unwrap_err(),
2022 FallibleExecutionError::NotEnoughValue.into()
2023 );
2024
2025 let res = ext.send_commit(
2026 i,
2027 HandlePacket::new_with_gas(Default::default(), Default::default(), INIT_GAS, 0),
2028 0,
2029 );
2030 assert!(res.is_ok());
2031 }
2032
2033 #[test]
2035 fn reservation_used_on_err() {
2036 let mut ext = Ext::new(
2037 ProcessorContextBuilder::new()
2038 .with_message_context(
2039 MessageContextBuilder::new()
2040 .with_outgoing_limit(u32::MAX)
2041 .build(),
2042 )
2043 .with_gas(GasCounter::new(1_000_000_000))
2044 .with_allowance(GasAllowanceCounter::new(1_000_000))
2045 .build(),
2046 );
2047
2048 let reservation_id = ext.reserve_gas(1_000_000, 1_000).expect("Shouldn't fail");
2050
2051 assert_eq!(
2054 ext.reservation_send_commit(reservation_id, u32::MAX, Default::default(), 0)
2055 .unwrap_err(),
2056 MessageError::OutOfBounds.into()
2057 );
2058
2059 let i = ext.send_init().expect("Shouldn't fail");
2061
2062 let res = ext.reservation_send_commit(reservation_id, i, Default::default(), 0);
2063 assert!(res.is_ok());
2064 }
2065
2066 #[test]
2067 fn rollback_works() {
2068 let mut ext = Ext::new(
2069 ProcessorContextBuilder::new()
2070 .with_message_context(
2071 MessageContextBuilder::new()
2072 .with_outgoing_limit(u32::MAX)
2073 .build(),
2074 )
2075 .with_gas(GasCounter::new(1_000_000_000))
2076 .build(),
2077 );
2078
2079 let reservation_id = ext.reserve_gas(1_000_000, 1_000).expect("Shouldn't fail");
2080 let remaining_gas = ext.context.gas_counter.to_amount();
2081 let remaining_gas_allowance = ext.context.gas_allowance_counter.left();
2082 let remaining_value_counter = ext.context.value_counter.left();
2083 let result = ext.with_changes::<_, (), _>(|mutator| {
2084 mutator.reduce_gas(42)?;
2085 mutator.charge_gas_if_enough(84)?; mutator.charge_message_value(128)?;
2087 mutator.outgoing_gasless = 1;
2088 mutator.mark_reservation_used(reservation_id)?;
2089 Err(FallibleExtError::Charge(ChargeError::GasLimitExceeded))
2090 });
2091
2092 assert!(result.is_err());
2093 assert_eq!(ext.context.gas_counter.left(), remaining_gas.left());
2094 assert_eq!(ext.context.gas_counter.burned(), remaining_gas.burned());
2095 assert_eq!(
2096 ext.context.gas_allowance_counter.left(),
2097 remaining_gas_allowance
2098 );
2099 assert_eq!(ext.outgoing_gasless, 0);
2100 assert_eq!(ext.context.value_counter.left(), remaining_value_counter);
2101 assert!(matches!(
2102 ext.context.gas_reserver.states().get(&reservation_id),
2103 Some(GasReservationState::Created {
2104 amount: 1_000_000,
2105 duration: 1_000,
2106 used: false
2107 })
2108 ));
2109 }
2110
2111 #[test]
2112 fn changes_do_apply() {
2113 let mut ext = Ext::new(
2114 ProcessorContextBuilder::new()
2115 .with_message_context(
2116 MessageContextBuilder::new()
2117 .with_outgoing_limit(u32::MAX)
2118 .build(),
2119 )
2120 .with_gas(GasCounter::new(1_000_000_000))
2121 .with_allowance(GasAllowanceCounter::new(1_000_000))
2122 .build(),
2123 );
2124
2125 let reservation_id = ext.reserve_gas(1_000_000, 1_000).expect("Shouldn't fail");
2126 let remaining_gas = ext.context.gas_counter.to_amount();
2127 let remaining_gas_allowance = ext.context.gas_allowance_counter.left();
2128 let remaining_value_counter = ext.context.value_counter.left();
2129 let result = ext.with_changes::<_, (), FallibleExtError>(|mutator| {
2130 mutator.reduce_gas(42)?;
2131 mutator.charge_gas_if_enough(84)?; mutator.charge_message_value(128)?;
2133 mutator.outgoing_gasless = 1;
2134 mutator.mark_reservation_used(reservation_id)?;
2135 Ok(())
2136 });
2137
2138 assert!(result.is_ok());
2139 assert_eq!(
2140 ext.context.gas_counter.left(),
2141 remaining_gas.left() - 42 - 84
2142 );
2143 assert_eq!(ext.outgoing_gasless, 1);
2144 assert_eq!(
2145 ext.context.gas_counter.burned(),
2146 remaining_gas.burned() + 84
2147 );
2148 assert_eq!(
2149 ext.context.gas_allowance_counter.left(),
2150 remaining_gas_allowance - 84
2151 );
2152 assert_eq!(
2153 ext.context.value_counter.left(),
2154 remaining_value_counter - 128
2155 );
2156 assert!(matches!(
2157 ext.context.gas_reserver.states().get(&reservation_id),
2158 Some(GasReservationState::Created {
2159 amount: 1_000_000,
2160 duration: 1_000,
2161 used: true
2162 })
2163 ));
2164 }
2165
2166 #[test]
2167 fn test_create_program() {
2172 let mut ext = Ext::new(
2173 ProcessorContextBuilder::new()
2174 .with_message_context(MessageContextBuilder::new().build())
2175 .with_existential_deposit(500)
2176 .with_value(0)
2177 .build(),
2178 );
2179
2180 let data = InitPacket::default();
2181
2182 let msg = ext.create_program(data.clone(), 0);
2183 assert_eq!(
2184 msg.unwrap_err(),
2185 FallibleExtError::Core(FallibleExtErrorCore::Execution(
2186 FallibleExecutionError::NotEnoughValue
2187 ))
2188 );
2189
2190 let mut ext = Ext::new(
2191 ProcessorContextBuilder::new()
2192 .with_gas(GasCounter::new(u64::MAX))
2193 .with_message_context(MessageContextBuilder::new().build())
2194 .with_existential_deposit(500)
2195 .with_value(1500)
2196 .build(),
2197 );
2198
2199 let msg = ext.create_program(data.clone(), 0);
2200 assert!(msg.is_ok());
2201 }
2202
2203 #[test]
2204 fn test_send_commit_with_value() {
2209 let mut ext = Ext::new(
2210 ProcessorContextBuilder::new()
2211 .with_message_context(
2212 MessageContextBuilder::new()
2213 .with_outgoing_limit(u32::MAX)
2214 .build(),
2215 )
2216 .with_existential_deposit(500)
2217 .with_value(0)
2218 .build(),
2219 );
2220
2221 let data = HandlePacket::new(ProgramId::default(), Payload::default(), 1000);
2222
2223 let handle = ext.send_init().expect("No outgoing limit");
2224
2225 let msg = ext.send_commit(handle, data.clone(), 0);
2226 assert_eq!(
2227 msg.unwrap_err(),
2228 FallibleExtError::Core(FallibleExtErrorCore::Execution(
2229 FallibleExecutionError::NotEnoughValue
2230 ))
2231 );
2232
2233 let mut ext = Ext::new(
2234 ProcessorContextBuilder::new()
2235 .with_message_context(
2236 MessageContextBuilder::new()
2237 .with_outgoing_limit(u32::MAX)
2238 .build(),
2239 )
2240 .with_existential_deposit(500)
2241 .with_value(5000)
2242 .build(),
2243 );
2244
2245 let handle = ext.send_init().expect("No outgoing limit");
2246 let msg = ext.send_commit(handle, data.clone(), 0);
2248 assert!(msg.is_ok());
2249
2250 let data = HandlePacket::new(ProgramId::default(), Payload::default(), 100);
2251 let handle = ext.send_init().expect("No outgoing limit");
2252 let msg = ext.send_commit(handle, data, 0);
2253 assert!(msg.is_ok());
2255 }
2256
2257 #[test]
2258 fn test_unreserve_no_reimbursement() {
2259 let costs = ExtCosts {
2260 rent: RentCosts {
2261 reservation: CostOf::new(10),
2262 ..Default::default()
2263 },
2264 ..Default::default()
2265 };
2266
2267 let (id, gas_reservation_map) = {
2269 let mut m = BTreeMap::new();
2270 let id = ReservationId::generate(MessageId::new([5; 32]), 10);
2271
2272 m.insert(
2273 id,
2274 GasReservationSlot {
2275 amount: 1_000_000,
2276 start: 0,
2277 finish: 10,
2278 },
2279 );
2280
2281 (id, m)
2282 };
2283 let mut ext = Ext::new(
2284 ProcessorContextBuilder::new()
2285 .with_gas(GasCounter::new(u64::MAX))
2286 .with_message_context(MessageContextBuilder::new().build())
2287 .with_existential_deposit(500)
2288 .with_reservations_map(gas_reservation_map)
2289 .with_costs(costs.clone())
2290 .build(),
2291 );
2292
2293 assert!(ext
2295 .context
2296 .gas_reserver
2297 .states()
2298 .iter()
2299 .all(|(_, state)| matches!(state, GasReservationState::Exists { .. })));
2300
2301 let gas_before = ext.gas_amount();
2303 assert!(ext.unreserve_gas(id).is_ok());
2304 let gas_after = ext.gas_amount();
2305
2306 assert_eq!(gas_after.left(), gas_before.left());
2307 }
2308
2309 #[test]
2310 fn test_unreserve_with_reimbursement() {
2311 let costs = ExtCosts {
2312 rent: RentCosts {
2313 reservation: CostOf::new(10),
2314 ..Default::default()
2315 },
2316 ..Default::default()
2317 };
2318 let mut ext = Ext::new(
2319 ProcessorContextBuilder::new()
2320 .with_gas(GasCounter::new(u64::MAX))
2321 .with_message_context(MessageContextBuilder::new().build())
2322 .with_existential_deposit(500)
2323 .with_costs(costs.clone())
2324 .build(),
2325 );
2326
2327 let reservation_amount = 1_000_000;
2329 let duration = 10;
2330 let duration_cost = costs
2331 .rent
2332 .reservation
2333 .cost_for(ext.context.reserve_for.saturating_add(duration).into());
2334 let reservation_total_cost = reservation_amount + duration_cost;
2335
2336 let gas_before_reservation = ext.gas_amount();
2337 assert_eq!(gas_before_reservation.left(), u64::MAX);
2338
2339 let id = ext
2340 .reserve_gas(reservation_amount, duration)
2341 .expect("internal error: failed reservation");
2342
2343 let gas_after_reservation = ext.gas_amount();
2344 assert_eq!(
2345 gas_before_reservation.left(),
2346 gas_after_reservation.left() + reservation_total_cost
2347 );
2348
2349 assert!(ext.unreserve_gas(id).is_ok());
2350
2351 let gas_after_unreserve = ext.gas_amount();
2352 assert_eq!(
2353 gas_after_unreserve.left(),
2354 gas_after_reservation.left() + reservation_total_cost
2355 );
2356 assert_eq!(gas_after_unreserve.left(), gas_before_reservation.left());
2357 }
2358}