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