core_processor/
ext.rs

1// This file is part of Gear.
2
3// Copyright (C) 2021-2025 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use 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
65/// Processor context.
66pub struct ProcessorContext {
67    /// Gas counter.
68    pub gas_counter: GasCounter,
69    /// Gas allowance counter.
70    pub gas_allowance_counter: GasAllowanceCounter,
71    /// Reserved gas counter.
72    pub gas_reserver: GasReserver,
73    /// System reservation.
74    pub system_reservation: Option<u64>,
75    /// Value counter.
76    pub value_counter: ValueCounter,
77    /// Allocations context.
78    pub allocations_context: AllocationsContext,
79    /// Message context.
80    pub message_context: MessageContext,
81    /// Block info.
82    pub block_info: BlockInfo,
83    /// Performance multiplier.
84    pub performance_multiplier: gsys::Percent,
85    /// Current program id
86    pub program_id: ActorId,
87    /// Map of code hashes to program ids of future programs, which are planned to be
88    /// initialized with the corresponding code (with the same code hash).
89    pub program_candidates_data: BTreeMap<CodeId, Vec<(MessageId, ActorId)>>,
90    /// Functions forbidden to be called.
91    pub forbidden_funcs: BTreeSet<SyscallName>,
92    /// Reserve for parameter of scheduling.
93    pub reserve_for: u32,
94    /// Output from Randomness.
95    pub random_data: (Vec<u8>, u32),
96    /// Gas multiplier.
97    pub gas_multiplier: gsys::GasMultiplier,
98    /// Existential deposit.
99    pub existential_deposit: u128,
100    /// Mailbox threshold.
101    pub mailbox_threshold: u64,
102    /// Execution externalities costs.
103    pub costs: ExtCosts,
104}
105
106#[cfg(any(feature = "mock", test))]
107impl ProcessorContext {
108    /// Create new mock [`ProcessorContext`] for usage in tests.
109    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
169/// Trait to which ext must have to work in processor wasm executor.
170/// Currently used only for lazy-pages support.
171pub trait ProcessorExternalities {
172    /// Create new
173    fn new(context: ProcessorContext) -> Self;
174
175    /// Convert externalities into info.
176    fn into_ext_info<Context>(
177        self,
178        ctx: &mut Context,
179        memory: &impl Memory<Context>,
180    ) -> Result<ExtInfo, MemoryError>;
181
182    /// Protect and save storage keys for pages which has no data
183    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    /// Lazy pages program post execution actions
194    fn lazy_pages_post_execution_actions<Context>(
195        ctx: &mut Context,
196        mem: &mut impl Memory<Context>,
197    );
198
199    /// Returns lazy pages status
200    fn lazy_pages_status() -> Status;
201}
202
203/// Infallible API error.
204#[derive(Debug, Clone, Eq, PartialEq, derive_more::From)]
205pub enum UnrecoverableExtError {
206    /// Basic error
207    Core(UnrecoverableExtErrorCore),
208    /// Charge error
209    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/// Fallible API error.
240#[derive(Debug, Clone, Eq, PartialEq, derive_more::From)]
241pub enum FallibleExtError {
242    /// Basic error
243    Core(FallibleExtErrorCore),
244    /// An error occurs in attempt to call forbidden syscall.
245    ForbiddenFunction,
246    /// Charge error
247    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/// [`Ext`](Ext)'s memory management (calls to allocate and free) error.
285#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display, derive_more::From)]
286pub enum AllocExtError {
287    /// Charge error
288    Charge(ChargeError),
289    /// Allocation error
290    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        // New pages allocation may change wasm memory buffer location.
313        // So we remove protections from lazy-pages
314        // and then in `after_grow_action` we set protection back for new wasm memory buffer.
315        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        // Add new allocations to lazy pages.
326        // Protect all lazy pages including new allocations.
327        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/// Used to atomically update `Ext` which prevents some errors
348/// when data was updated but operation failed.
349///
350/// Copies some counters into itself and performs operations on them and
351/// incrementally builds list of changes.
352///
353/// Changes are applied after operation is completed
354#[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        // SAFETY: counters are cloned and modified *only* by mutator
375        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        // can't access context inside `alloc` so move here
394        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                // Inline charge_gas_if_enough because otherwise we have borrow error due to access to `allocations_context` mutable
402                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                // Any "handle" gasless and gasful *non zero* message must
464                // cover mailbox threshold. That's because destination
465                // of the message is unknown, so it could be a user,
466                // and if gasless message is sent, there must be a
467                // guaranteed gas to cover mailbox.
468                let mailbox_threshold = self.context.mailbox_threshold;
469                let gas_limit = packet.gas_limit().unwrap_or(mailbox_threshold);
470
471                // Zero gasful message is a special case.
472                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                // Init and reply messages never go to mailbox.
480                //
481                // For init case, even if there's no code with a provided
482                // code id, the init message still goes to queue and then is handled as non
483                // executable, as there is no code for the destination actor.
484                //
485                // Also no reply to user messages go to mailbox, they all are emitted
486                // within events.
487
488                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            // Reduce gas for block waiting in dispatch stash.
517            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
538/// Structure providing externalities for running host functions.
539pub struct Ext<LP: LazyPagesInterface> {
540    /// Processor context.
541    pub context: ProcessorContext,
542    /// Actual gas counter type within wasm module's global.
543    pub current_counter: CounterType,
544    // Counter of outgoing gasless messages.
545    //
546    // It's temporary field, used to solve `core-audit/issue#22`.
547    outgoing_gasless: u64,
548    _phantom: PhantomData<LP>,
549}
550
551/// Empty implementation for non-substrate (and non-lazy-pages) using
552impl<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        // Accessed pages are all pages, that had been released and are in allocations set or static.
587        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_changed` can be some times `true` event if final state of allocations is the same as before execution
624            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    /// Checking that reservation could be charged for
695    /// dispatch stash with given delay.
696    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            // Gas reservation is known for covering mailbox threshold, as reservation
716            // is created after passing a check for that.
717            // By this check we guarantee that reservation is enough both for delay
718            // and for mailbox threshold.
719            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            // Here might be refunds for gas counter, but it's meaningless since
745            // on gas allowance exceed we totally roll up the message and give
746            // it another try in next block with the same initial resources.
747            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        // For possible cases of non-atomic charges on backend side when global
798        // value is less than appropriate at the backend.
799        //
800        // Example:
801        // * While executing program calls some syscall.
802        // * Syscall ends up with unrecoverable error - gas limit exceeded.
803        // * We have to charge it so we leave backend and whole execution with 0 inner counter.
804        // * Meanwhile global is not zero, so for this case we have to skip decreasing.
805        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            // TODO: unify logic around different source of gas (may be origin msg,
1007            // or reservation) in order to implement #1828.
1008            mutator.check_reservation_gas_limit_for_delayed_sending(&id, delay)?;
1009            // TODO: gasful sending (#1828)
1010            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    // TODO: Consider per byte charge (issue #2255).
1030    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            // TODO: gasful sending (#1828)
1055            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            // Basically amount of the reseravtion and the cost for the hold duration.
1213            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        // TODO: use `NonZero<u64>` after issue #1838 is fixed
1236        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            // We don't check for forbidden destination here, since dest is always unique
1371            // and almost impossible to match SYSTEM_ID
1372
1373            mutator.charge_expiring_resources(&packet)?;
1374            mutator.charge_sending_fee(delay)?;
1375            mutator.charge_for_dispatch_stash_hold(delay)?;
1376
1377            // Charge ED to value_counter
1378            mutator.charge_message_value(ed)?;
1379
1380            let code_hash = packet.code_id();
1381
1382            // Send a message for program creation
1383
1384            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    // Invariant: Refund never occurs in `free` call.
1553    #[test]
1554    fn free_no_refund() {
1555        // Set initial Ext state
1556        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        // Freeing existing page.
1582        // Counters shouldn't be changed.
1583        assert!(ext.free(existing_page).is_ok());
1584        assert_eq!(ext.gas_left(), gas_left);
1585
1586        // Freeing non existing page.
1587        // Counters still shouldn't be changed.
1588        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        // Set initial Ext state
1600        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        // there was lack of gas
1628        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        // there was lack of allowance
1653        assert_eq!(0, allowance);
1654    }
1655
1656    #[test]
1657    // This function tests:
1658    //
1659    // - `send_commit` on valid handle
1660    // - `send_commit` on invalid handle
1661    // - `send_commit` on used handle
1662    // - `send_init` after limit is exceeded
1663    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    // This function tests:
1702    //
1703    // - `send_push` on non-existent handle
1704    // - `send_push` on valid handle
1705    // - `send_push` on used handle
1706    // - `send_push` with too large payload
1707    // - `send_push` data is added to buffer
1708    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    // This function tests:
1767    //
1768    // - `send_push_input` on non-existent handle
1769    // - `send_push_input` on valid handle
1770    // - `send_push_input` on used handle
1771    // - `send_push_input` data is added to buffer
1772    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        // Len too big
1799        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        // Too big offset
1815        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    // This function requires `reply_push` to work to add extra data.
1848    // This function tests:
1849    //
1850    // - `reply_commit` with too much data
1851    // - `reply_commit` with valid data
1852    // - `reply_commit` duplicate reply
1853    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    // This function requires `reply_push` to work to add extra data.
1884    // This function tests:
1885    //
1886    // - `reply_push` with valid data
1887    // - `reply_push` with too much data
1888    // - `reply_push` after `reply_commit`
1889    // - `reply_push` data is added to buffer
1890    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    // This function tests:
1938    //
1939    // - `reply_push_input` with valid data
1940    // - `reply_push_input` after `reply_commit`
1941    // - `reply_push_input` data is added to buffer
1942    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        // Len too big
1959        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        // Too big offset
1975        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    // TODO: fix me (issue #3881)
2006    #[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        // initializing send message
2022        let i = ext.send_init().expect("Shouldn't fail");
2023
2024        // this one fails due to lack of value, BUT [bug] gas for sending already
2025        // gone and no longer could be used within the execution.
2026        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    // TODO: fix me (issue #3881)
2050    #[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        // creating reservation to be used
2065        let reservation_id = ext.reserve_gas(1_000_000, 1_000).expect("Shouldn't fail");
2066
2067        let data = HandlePacket::default();
2068
2069        // this one fails due to absence of init nonce, BUT [bug] marks reservation used,
2070        // so another `reservation_send_commit` fails due to used reservation.
2071        assert_eq!(
2072            ext.reservation_send_commit(reservation_id, u32::MAX, data, 0)
2073                .unwrap_err(),
2074            MessageError::OutOfBounds.into()
2075        );
2076
2077        // initializing send message
2078        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)?; // changes gas_counter and gas_allowance_counter
2105            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)?; // changes gas_counter and gas_allowance_counter
2151            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    // This function tests:
2187    //
2188    // - `create_program` fails due to lack of value to pay for ED
2189    // - `create_program` is successful
2190    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    // This function tests:
2223    //
2224    // - `send_commit` with value greater than the ED
2225    // - `send_commit` with value below the ED
2226    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        // Sending value greater than ED is ok
2265        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        // Sending value below ED is also fine
2272        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        // Create "pre-reservation".
2286        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        // Check all the reseravations are in "existing" state.
2312        assert!(
2313            ext.context
2314                .gas_reserver
2315                .states()
2316                .iter()
2317                .all(|(_, state)| matches!(state, GasReservationState::Exists { .. }))
2318        );
2319
2320        // Unreserving existing and checking no gas reimbursed.
2321        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        // Define params
2347        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}