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