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