gear_core_processor/
ext.rs

1// This file is part of Gear.
2
3// Copyright (C) 2021-2023 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, PageCosts};
20use alloc::{
21    collections::{BTreeMap, BTreeSet},
22    vec::Vec,
23};
24use gear_backend_common::{
25    lazy_pages::{GlobalsAccessConfig, LazyPagesWeights, Status},
26    memory::ProcessAccessError,
27    runtime::RunFallibleError,
28    ActorTerminationReason, BackendAllocSyscallError, BackendExternalities, BackendSyscallError,
29    ExtInfo, SystemReservationContext, TrapExplanation, UndefinedTerminationReason,
30    UnrecoverableExecutionError, UnrecoverableExtError as UnrecoverableExtErrorCore,
31    UnrecoverableWaitError,
32};
33#[cfg(any(feature = "mock", test))]
34use gear_core::message::{ContextSettings, IncomingDispatch};
35use gear_core::{
36    costs::{HostFnWeights, RuntimeCosts},
37    env::{Externalities, PayloadSliceLock, UnlockPayloadBound},
38    gas::{
39        ChargeError, ChargeResult, CounterType, CountersOwner, GasAllowanceCounter, GasAmount,
40        GasCounter, GasLeft, Token, ValueCounter,
41    },
42    ids::{CodeId, MessageId, ProgramId, ReservationId},
43    memory::{
44        AllocError, AllocationsContext, GrowHandler, Memory, MemoryError, MemoryInterval,
45        NoopGrowHandler, PageBuf,
46    },
47    message::{
48        ContextOutcomeDrain, GasLimit, HandlePacket, InitPacket, MessageContext, Packet,
49        ReplyPacket,
50    },
51    pages::{GearPage, PageU32Size, WasmPage},
52    reservation::GasReserver,
53};
54use gear_core_errors::{
55    ExecutionError as FallibleExecutionError, ExtError as FallibleExtErrorCore, MessageError,
56    ProgramRentError, ReplyCode, ReservationError, SignalCode,
57};
58use gear_wasm_instrument::syscalls::SysCallName;
59
60/// Processor context.
61pub struct ProcessorContext {
62    /// Gas counter.
63    pub gas_counter: GasCounter,
64    /// Gas allowance counter.
65    pub gas_allowance_counter: GasAllowanceCounter,
66    /// Reserved gas counter.
67    pub gas_reserver: GasReserver,
68    /// System reservation.
69    pub system_reservation: Option<u64>,
70    /// Value counter.
71    pub value_counter: ValueCounter,
72    /// Allocations context.
73    pub allocations_context: AllocationsContext,
74    /// Message context.
75    pub message_context: MessageContext,
76    /// Block info.
77    pub block_info: BlockInfo,
78    /// Max allowed wasm memory pages.
79    pub max_pages: WasmPage,
80    /// Allocations config.
81    pub page_costs: PageCosts,
82    /// Account existential deposit
83    pub existential_deposit: u128,
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    /// Map of program ids to paid blocks.
90    pub program_rents: BTreeMap<ProgramId, u32>,
91    /// Weights of host functions.
92    pub host_fn_weights: HostFnWeights,
93    /// Functions forbidden to be called.
94    pub forbidden_funcs: BTreeSet<SysCallName>,
95    /// Mailbox threshold.
96    pub mailbox_threshold: u64,
97    /// Cost for single block waitlist holding.
98    pub waitlist_cost: u64,
99    /// Cost of holding a message in dispatch stash.
100    pub dispatch_hold_cost: u64,
101    /// Reserve for parameter of scheduling.
102    pub reserve_for: u32,
103    /// Cost for reservation holding.
104    pub reservation: u64,
105    /// Output from Randomness.
106    pub random_data: (Vec<u8>, u32),
107    /// Rent cost per block.
108    pub rent_cost: u128,
109}
110
111#[cfg(any(feature = "mock", test))]
112impl ProcessorContext {
113    /// Create new mock [`ProcessorContext`] for usage in tests.
114    pub fn new_mock() -> ProcessorContext {
115        ProcessorContext {
116            gas_counter: GasCounter::new(0),
117            gas_allowance_counter: GasAllowanceCounter::new(0),
118            gas_reserver: GasReserver::new(
119                &<IncomingDispatch as Default>::default(),
120                Default::default(),
121                Default::default(),
122            ),
123            system_reservation: None,
124            value_counter: ValueCounter::new(0),
125            allocations_context: AllocationsContext::new(
126                Default::default(),
127                Default::default(),
128                Default::default(),
129            ),
130            message_context: MessageContext::new(
131                Default::default(),
132                Default::default(),
133                ContextSettings::new(0, 0, 0, 0, 0, 0),
134            ),
135            block_info: Default::default(),
136            max_pages: 512.into(),
137            page_costs: Default::default(),
138            existential_deposit: 0,
139            program_id: Default::default(),
140            program_candidates_data: Default::default(),
141            program_rents: Default::default(),
142            host_fn_weights: Default::default(),
143            forbidden_funcs: Default::default(),
144            mailbox_threshold: 0,
145            waitlist_cost: 0,
146            dispatch_hold_cost: 0,
147            reserve_for: 0,
148            reservation: 0,
149            random_data: ([0u8; 32].to_vec(), 0),
150            rent_cost: 0,
151        }
152    }
153}
154
155/// Trait to which ext must have to work in processor wasm executor.
156/// Currently used only for lazy-pages support.
157pub trait ProcessorExternalities {
158    /// Whether this extension works with lazy pages.
159    const LAZY_PAGES_ENABLED: bool;
160
161    /// Create new
162    fn new(context: ProcessorContext) -> Self;
163
164    /// Protect and save storage keys for pages which has no data
165    fn lazy_pages_init_for_program(
166        mem: &mut impl Memory,
167        prog_id: ProgramId,
168        stack_end: Option<WasmPage>,
169        globals_config: GlobalsAccessConfig,
170        lazy_pages_weights: LazyPagesWeights,
171    );
172
173    /// Lazy pages contract post execution actions
174    fn lazy_pages_post_execution_actions(mem: &mut impl Memory);
175
176    /// Returns lazy pages status
177    fn lazy_pages_status() -> Status;
178}
179
180/// Infallible API error.
181#[derive(Debug, Clone, Eq, PartialEq, derive_more::From)]
182pub enum UnrecoverableExtError {
183    /// Basic error
184    Core(UnrecoverableExtErrorCore),
185    /// Charge error
186    Charge(ChargeError),
187}
188
189impl From<UnrecoverableExecutionError> for UnrecoverableExtError {
190    fn from(err: UnrecoverableExecutionError) -> UnrecoverableExtError {
191        Self::Core(UnrecoverableExtErrorCore::from(err))
192    }
193}
194
195impl From<UnrecoverableWaitError> for UnrecoverableExtError {
196    fn from(err: UnrecoverableWaitError) -> UnrecoverableExtError {
197        Self::Core(UnrecoverableExtErrorCore::from(err))
198    }
199}
200
201impl BackendSyscallError for UnrecoverableExtError {
202    fn into_termination_reason(self) -> UndefinedTerminationReason {
203        match self {
204            UnrecoverableExtError::Core(err) => {
205                ActorTerminationReason::Trap(TrapExplanation::UnrecoverableExt(err)).into()
206            }
207            UnrecoverableExtError::Charge(err) => err.into(),
208        }
209    }
210
211    fn into_run_fallible_error(self) -> RunFallibleError {
212        RunFallibleError::UndefinedTerminationReason(self.into_termination_reason())
213    }
214}
215
216/// Fallible API error.
217#[derive(Debug, Clone, Eq, PartialEq, derive_more::From)]
218pub enum FallibleExtError {
219    /// Basic error
220    Core(FallibleExtErrorCore),
221    /// An error occurs in attempt to call forbidden sys-call.
222    ForbiddenFunction,
223    /// Charge error
224    Charge(ChargeError),
225}
226
227impl From<MessageError> for FallibleExtError {
228    fn from(err: MessageError) -> Self {
229        Self::Core(FallibleExtErrorCore::Message(err))
230    }
231}
232
233impl From<FallibleExecutionError> for FallibleExtError {
234    fn from(err: FallibleExecutionError) -> Self {
235        Self::Core(FallibleExtErrorCore::Execution(err))
236    }
237}
238
239impl From<ProgramRentError> for FallibleExtError {
240    fn from(err: ProgramRentError) -> Self {
241        Self::Core(FallibleExtErrorCore::ProgramRent(err))
242    }
243}
244
245impl From<ReservationError> for FallibleExtError {
246    fn from(err: ReservationError) -> Self {
247        Self::Core(FallibleExtErrorCore::Reservation(err))
248    }
249}
250
251impl From<FallibleExtError> for RunFallibleError {
252    fn from(err: FallibleExtError) -> Self {
253        match err {
254            FallibleExtError::Core(err) => RunFallibleError::FallibleExt(err),
255            FallibleExtError::ForbiddenFunction => {
256                RunFallibleError::UndefinedTerminationReason(UndefinedTerminationReason::Actor(
257                    ActorTerminationReason::Trap(TrapExplanation::ForbiddenFunction),
258                ))
259            }
260            FallibleExtError::Charge(err) => {
261                RunFallibleError::UndefinedTerminationReason(UndefinedTerminationReason::from(err))
262            }
263        }
264    }
265}
266
267/// [`Ext`](Ext)'s memory management (calls to allocate and free) error.
268#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display, derive_more::From)]
269pub enum AllocExtError {
270    /// Charge error
271    #[display(fmt = "{_0}")]
272    Charge(ChargeError),
273    /// Allocation error
274    #[display(fmt = "{_0}")]
275    Alloc(AllocError),
276}
277
278impl BackendAllocSyscallError for AllocExtError {
279    type ExtError = UnrecoverableExtError;
280
281    fn into_backend_error(self) -> Result<Self::ExtError, Self> {
282        match self {
283            Self::Charge(err) => Ok(err.into()),
284            err => Err(err),
285        }
286    }
287}
288
289/// Structure providing externalities for running host functions.
290pub struct Ext {
291    /// Processor context.
292    pub context: ProcessorContext,
293    /// Actual gas counter type within wasm module's global.
294    pub current_counter: CounterType,
295    // Counter of outgoing gasless messages.
296    //
297    // It's temporary field, used to solve `core-audit/issue#22`.
298    outgoing_gasless: u64,
299}
300
301/// Empty implementation for non-substrate (and non-lazy-pages) using
302impl ProcessorExternalities for Ext {
303    const LAZY_PAGES_ENABLED: bool = false;
304
305    fn new(context: ProcessorContext) -> Self {
306        let current_counter = if context.gas_counter.left() <= context.gas_allowance_counter.left()
307        {
308            CounterType::GasLimit
309        } else {
310            CounterType::GasAllowance
311        };
312
313        Self {
314            context,
315            current_counter,
316            outgoing_gasless: 0,
317        }
318    }
319
320    fn lazy_pages_init_for_program(
321        _mem: &mut impl Memory,
322        _prog_id: ProgramId,
323        _stack_end: Option<WasmPage>,
324        _globals_config: GlobalsAccessConfig,
325        _lazy_pages_weights: LazyPagesWeights,
326    ) {
327        unreachable!("Must not be called: lazy-pages is unsupported by this ext")
328    }
329
330    fn lazy_pages_post_execution_actions(_mem: &mut impl Memory) {
331        unreachable!("Must not be called: lazy-pages is unsupported by this ext")
332    }
333
334    fn lazy_pages_status() -> Status {
335        unreachable!("Must not be called: lazy-pages is unsupported by this ext")
336    }
337}
338
339impl BackendExternalities for Ext {
340    fn into_ext_info(self, memory: &impl Memory) -> Result<ExtInfo, MemoryError> {
341        let pages_for_data =
342            |static_pages: WasmPage, allocations: &BTreeSet<WasmPage>| -> Vec<GearPage> {
343                static_pages
344                    .iter_from_zero()
345                    .chain(allocations.iter().copied())
346                    .flat_map(|p| p.to_pages_iter())
347                    .collect()
348            };
349
350        self.into_ext_info_inner(memory, pages_for_data)
351    }
352
353    fn gas_amount(&self) -> GasAmount {
354        self.context.gas_counter.to_amount()
355    }
356
357    fn pre_process_memory_accesses(
358        _reads: &[MemoryInterval],
359        _writes: &[MemoryInterval],
360        _gas_counter: &mut u64,
361    ) -> Result<(), ProcessAccessError> {
362        Ok(())
363    }
364}
365
366impl Ext {
367    fn check_message_value(&mut self, message_value: u128) -> Result<(), FallibleExtError> {
368        let existential_deposit = self.context.existential_deposit;
369        // Sending value should apply the range {0} ∪ [existential_deposit; +inf)
370        if message_value != 0 && message_value < existential_deposit {
371            Err(MessageError::InsufficientValue.into())
372        } else {
373            Ok(())
374        }
375    }
376
377    fn check_gas_limit(
378        &mut self,
379        gas_limit: Option<GasLimit>,
380    ) -> Result<GasLimit, FallibleExtError> {
381        let mailbox_threshold = self.context.mailbox_threshold;
382        let gas_limit = gas_limit.unwrap_or(0);
383
384        // Sending gas should apply the range {0} ∪ [mailbox_threshold; +inf)
385        if gas_limit < mailbox_threshold && gas_limit != 0 {
386            Err(MessageError::InsufficientGasLimit.into())
387        } else {
388            Ok(gas_limit)
389        }
390    }
391
392    fn reduce_gas(&mut self, gas_limit: GasLimit) -> Result<(), FallibleExtError> {
393        if self.context.gas_counter.reduce(gas_limit) != ChargeResult::Enough {
394            Err(FallibleExecutionError::NotEnoughGas.into())
395        } else {
396            Ok(())
397        }
398    }
399
400    fn charge_message_value(&mut self, message_value: u128) -> Result<(), FallibleExtError> {
401        if self.context.value_counter.reduce(message_value) != ChargeResult::Enough {
402            Err(FallibleExecutionError::NotEnoughValue.into())
403        } else {
404            Ok(())
405        }
406    }
407
408    // It's temporary fn, used to solve `core-audit/issue#22`.
409    fn safe_gasfull_sends<T: Packet>(&mut self, packet: &T) -> Result<(), FallibleExtError> {
410        let outgoing_gasless = self.outgoing_gasless;
411
412        match packet.gas_limit() {
413            Some(x) if x != 0 => {
414                self.outgoing_gasless = 0;
415
416                let prev_gasless_fee =
417                    outgoing_gasless.saturating_mul(self.context.mailbox_threshold);
418
419                self.reduce_gas(prev_gasless_fee)?;
420            }
421            None => self.outgoing_gasless = outgoing_gasless.saturating_add(1),
422            _ => {}
423        };
424
425        Ok(())
426    }
427
428    fn charge_expiring_resources<T: Packet>(
429        &mut self,
430        packet: &T,
431        check_gas_limit: bool,
432    ) -> Result<(), FallibleExtError> {
433        self.check_message_value(packet.value())?;
434        // Charge for using expiring resources. Charge for calling sys-call was done earlier.
435        let gas_limit = if check_gas_limit {
436            self.check_gas_limit(packet.gas_limit())?
437        } else {
438            packet.gas_limit().unwrap_or(0)
439        };
440        self.reduce_gas(gas_limit)?;
441        self.charge_message_value(packet.value())?;
442        Ok(())
443    }
444
445    fn check_forbidden_destination(&mut self, id: ProgramId) -> Result<(), FallibleExtError> {
446        if id == ProgramId::SYSTEM {
447            Err(FallibleExtError::ForbiddenFunction)
448        } else {
449            Ok(())
450        }
451    }
452
453    fn charge_sending_fee(&mut self, delay: u32) -> Result<(), ChargeError> {
454        if delay == 0 {
455            self.charge_gas_if_enough(self.context.message_context.settings().sending_fee())
456        } else {
457            self.charge_gas_if_enough(
458                self.context
459                    .message_context
460                    .settings()
461                    .scheduled_sending_fee(),
462            )
463        }
464    }
465
466    fn charge_for_dispatch_stash_hold(&mut self, delay: u32) -> Result<(), FallibleExtError> {
467        if delay != 0 {
468            // Take delay and get cost of block.
469            // reserve = wait_cost * (delay + reserve_for).
470            let cost_per_block = self.context.dispatch_hold_cost;
471            let waiting_reserve = (self.context.reserve_for as u64)
472                .saturating_add(delay as u64)
473                .saturating_mul(cost_per_block);
474
475            // Reduce gas for block waiting in dispatch stash.
476            if self.context.gas_counter.reduce(waiting_reserve) != ChargeResult::Enough {
477                return Err(MessageError::InsufficientGasForDelayedSending.into());
478            }
479        }
480        Ok(())
481    }
482
483    fn charge_gas_if_enough(
484        gas_counter: &mut GasCounter,
485        gas_allowance_counter: &mut GasAllowanceCounter,
486        amount: u64,
487    ) -> Result<(), ChargeError> {
488        if gas_counter.charge_if_enough(amount) != ChargeResult::Enough {
489            return Err(ChargeError::GasLimitExceeded);
490        }
491        if gas_allowance_counter.charge_if_enough(amount) != ChargeResult::Enough {
492            // Here might be refunds for gas counter, but it's meaningless since
493            // on gas allowance exceed we totally roll up the message and give
494            // it another try in next block with the same initial resources.
495            return Err(ChargeError::GasAllowanceExceeded);
496        }
497        Ok(())
498    }
499}
500
501impl CountersOwner for Ext {
502    fn charge_gas_runtime(&mut self, cost: RuntimeCosts) -> Result<(), ChargeError> {
503        let token = cost.token(&self.context.host_fn_weights);
504        let common_charge = self.context.gas_counter.charge(token);
505        let allowance_charge = self.context.gas_allowance_counter.charge(token);
506        match (common_charge, allowance_charge) {
507            (ChargeResult::NotEnough, _) => Err(ChargeError::GasLimitExceeded),
508            (ChargeResult::Enough, ChargeResult::NotEnough) => {
509                Err(ChargeError::GasAllowanceExceeded)
510            }
511            (ChargeResult::Enough, ChargeResult::Enough) => Ok(()),
512        }
513    }
514
515    fn charge_gas_runtime_if_enough(&mut self, cost: RuntimeCosts) -> Result<(), ChargeError> {
516        let amount = cost.token(&self.context.host_fn_weights).weight();
517        self.charge_gas_if_enough(amount)
518    }
519
520    fn charge_gas_if_enough(&mut self, amount: u64) -> Result<(), ChargeError> {
521        Ext::charge_gas_if_enough(
522            &mut self.context.gas_counter,
523            &mut self.context.gas_allowance_counter,
524            amount,
525        )
526    }
527
528    fn gas_left(&self) -> GasLeft {
529        (
530            self.context.gas_counter.left(),
531            self.context.gas_allowance_counter.left(),
532        )
533            .into()
534    }
535
536    fn current_counter_type(&self) -> CounterType {
537        self.current_counter
538    }
539
540    fn decrease_current_counter_to(&mut self, amount: u64) {
541        // For possible cases of non-atomic charges on backend side when global
542        // value is less than appropriate at the backend.
543        //
544        // Example:
545        // * While executing program calls some syscall.
546        // * Syscall ends up with unrecoverable error - gas limit exceeded.
547        // * We have to charge it so we leave backend and whole execution with 0 inner counter.
548        // * Meanwhile global is not zero, so for this case we have to skip decreasing.
549        if self.current_counter_value() <= amount {
550            log::trace!("Skipped decrease to global value");
551            return;
552        }
553
554        let GasLeft { gas, allowance } = self.gas_left();
555
556        let diff = match self.current_counter_type() {
557            CounterType::GasLimit => gas.checked_sub(amount),
558            CounterType::GasAllowance => allowance.checked_sub(amount),
559        }
560        .unwrap_or_else(|| unreachable!("Checked above"));
561
562        if self.context.gas_counter.charge(diff) == ChargeResult::NotEnough {
563            unreachable!("Tried to set gas limit left bigger than before")
564        }
565
566        if self.context.gas_allowance_counter.charge(diff) == ChargeResult::NotEnough {
567            unreachable!("Tried to set gas allowance left bigger than before")
568        }
569    }
570
571    fn define_current_counter(&mut self) -> u64 {
572        let GasLeft { gas, allowance } = self.gas_left();
573
574        if gas <= allowance {
575            self.current_counter = CounterType::GasLimit;
576            gas
577        } else {
578            self.current_counter = CounterType::GasAllowance;
579            allowance
580        }
581    }
582}
583
584impl Externalities for Ext {
585    type UnrecoverableError = UnrecoverableExtError;
586    type FallibleError = FallibleExtError;
587    type AllocError = AllocExtError;
588
589    fn alloc(
590        &mut self,
591        pages_num: u32,
592        mem: &mut impl Memory,
593    ) -> Result<WasmPage, Self::AllocError> {
594        self.alloc_inner::<NoopGrowHandler>(pages_num, mem)
595    }
596
597    fn free(&mut self, page: WasmPage) -> Result<(), Self::AllocError> {
598        self.context
599            .allocations_context
600            .free(page)
601            .map_err(Into::into)
602    }
603
604    fn block_height(&self) -> Result<u32, Self::UnrecoverableError> {
605        Ok(self.context.block_info.height)
606    }
607
608    fn block_timestamp(&self) -> Result<u64, Self::UnrecoverableError> {
609        Ok(self.context.block_info.timestamp)
610    }
611
612    fn send_init(&mut self) -> Result<u32, Self::FallibleError> {
613        let handle = self.context.message_context.send_init()?;
614        Ok(handle)
615    }
616
617    fn send_push(&mut self, handle: u32, buffer: &[u8]) -> Result<(), Self::FallibleError> {
618        self.context.message_context.send_push(handle, buffer)?;
619        Ok(())
620    }
621
622    fn send_push_input(
623        &mut self,
624        handle: u32,
625        offset: u32,
626        len: u32,
627    ) -> Result<(), Self::FallibleError> {
628        let range = self.context.message_context.check_input_range(offset, len);
629        self.charge_gas_runtime_if_enough(RuntimeCosts::SendPushInputPerByte(range.len()))?;
630
631        self.context
632            .message_context
633            .send_push_input(handle, range)?;
634
635        Ok(())
636    }
637
638    fn send_commit(
639        &mut self,
640        handle: u32,
641        msg: HandlePacket,
642        delay: u32,
643    ) -> Result<MessageId, Self::FallibleError> {
644        self.check_forbidden_destination(msg.destination())?;
645        self.safe_gasfull_sends(&msg)?;
646        self.charge_expiring_resources(&msg, true)?;
647        self.charge_sending_fee(delay)?;
648
649        self.charge_for_dispatch_stash_hold(delay)?;
650
651        let msg_id = self
652            .context
653            .message_context
654            .send_commit(handle, msg, delay, None)?;
655
656        Ok(msg_id)
657    }
658
659    fn reservation_send_commit(
660        &mut self,
661        id: ReservationId,
662        handle: u32,
663        msg: HandlePacket,
664        delay: u32,
665    ) -> Result<MessageId, Self::FallibleError> {
666        self.check_forbidden_destination(msg.destination())?;
667        self.check_message_value(msg.value())?;
668        self.check_gas_limit(msg.gas_limit())?;
669        // TODO: gasful sending (#1828)
670        self.charge_message_value(msg.value())?;
671        self.charge_sending_fee(delay)?;
672
673        self.charge_for_dispatch_stash_hold(delay)?;
674
675        self.context.gas_reserver.mark_used(id)?;
676
677        let msg_id = self
678            .context
679            .message_context
680            .send_commit(handle, msg, delay, Some(id))?;
681        Ok(msg_id)
682    }
683
684    fn reply_push(&mut self, buffer: &[u8]) -> Result<(), Self::FallibleError> {
685        self.context.message_context.reply_push(buffer)?;
686        Ok(())
687    }
688
689    // TODO: Consider per byte charge (issue #2255).
690    fn reply_commit(&mut self, msg: ReplyPacket) -> Result<MessageId, Self::FallibleError> {
691        self.check_forbidden_destination(self.context.message_context.reply_destination())?;
692        self.safe_gasfull_sends(&msg)?;
693        self.charge_expiring_resources(&msg, false)?;
694        self.charge_sending_fee(0)?;
695
696        let msg_id = self.context.message_context.reply_commit(msg, None)?;
697        Ok(msg_id)
698    }
699
700    fn reservation_reply_commit(
701        &mut self,
702        id: ReservationId,
703        msg: ReplyPacket,
704    ) -> Result<MessageId, Self::FallibleError> {
705        self.check_forbidden_destination(self.context.message_context.reply_destination())?;
706        self.check_message_value(msg.value())?;
707        // TODO: gasful sending (#1828)
708        self.charge_message_value(msg.value())?;
709        self.charge_sending_fee(0)?;
710
711        self.context.gas_reserver.mark_used(id)?;
712
713        let msg_id = self.context.message_context.reply_commit(msg, Some(id))?;
714        Ok(msg_id)
715    }
716
717    fn reply_to(&self) -> Result<MessageId, Self::FallibleError> {
718        self.context
719            .message_context
720            .current()
721            .details()
722            .and_then(|d| d.to_reply_details().map(|d| d.to_message_id()))
723            .ok_or_else(|| FallibleExecutionError::NoReplyContext.into())
724    }
725
726    fn signal_from(&self) -> Result<MessageId, Self::FallibleError> {
727        self.context
728            .message_context
729            .current()
730            .details()
731            .and_then(|d| d.to_signal_details().map(|d| d.to_message_id()))
732            .ok_or_else(|| FallibleExecutionError::NoSignalContext.into())
733    }
734
735    fn reply_push_input(&mut self, offset: u32, len: u32) -> Result<(), Self::FallibleError> {
736        let range = self.context.message_context.check_input_range(offset, len);
737        self.charge_gas_runtime_if_enough(RuntimeCosts::ReplyPushInputPerByte(range.len()))?;
738
739        self.context.message_context.reply_push_input(range)?;
740
741        Ok(())
742    }
743
744    fn source(&self) -> Result<ProgramId, Self::UnrecoverableError> {
745        Ok(self.context.message_context.current().source())
746    }
747
748    fn reply_code(&self) -> Result<ReplyCode, Self::FallibleError> {
749        self.context
750            .message_context
751            .current()
752            .details()
753            .and_then(|d| d.to_reply_details().map(|d| d.to_reply_code()))
754            .ok_or_else(|| FallibleExecutionError::NoReplyContext.into())
755    }
756
757    fn signal_code(&self) -> Result<SignalCode, Self::FallibleError> {
758        self.context
759            .message_context
760            .current()
761            .details()
762            .and_then(|d| d.to_signal_details().map(|d| d.to_signal_code()))
763            .ok_or_else(|| FallibleExecutionError::NoSignalContext.into())
764    }
765
766    fn message_id(&self) -> Result<MessageId, Self::UnrecoverableError> {
767        Ok(self.context.message_context.current().id())
768    }
769
770    fn pay_program_rent(
771        &mut self,
772        program_id: ProgramId,
773        rent: u128,
774    ) -> Result<(u128, u32), Self::FallibleError> {
775        if self.context.rent_cost == 0 {
776            return Ok((rent, 0));
777        }
778
779        let block_count = u32::try_from(rent / self.context.rent_cost).unwrap_or(u32::MAX);
780        let old_paid_blocks = self
781            .context
782            .program_rents
783            .get(&program_id)
784            .copied()
785            .unwrap_or(0);
786
787        let (paid_blocks, blocks_to_pay) = match old_paid_blocks.overflowing_add(block_count) {
788            (count, false) => (count, block_count),
789            (_, true) => return Err(ProgramRentError::MaximumBlockCountPaid.into()),
790        };
791
792        if blocks_to_pay == 0 {
793            return Ok((rent, 0));
794        }
795
796        let cost = self.context.rent_cost.saturating_mul(blocks_to_pay.into());
797        match self.context.value_counter.reduce(cost) {
798            ChargeResult::Enough => {
799                self.context.program_rents.insert(program_id, paid_blocks);
800            }
801            ChargeResult::NotEnough => return Err(FallibleExecutionError::NotEnoughValue.into()),
802        }
803
804        Ok((rent.saturating_sub(cost), blocks_to_pay))
805    }
806
807    fn program_id(&self) -> Result<ProgramId, Self::UnrecoverableError> {
808        Ok(self.context.program_id)
809    }
810
811    fn debug(&self, data: &str) -> Result<(), Self::UnrecoverableError> {
812        let program_id = self.program_id()?;
813        let message_id = self.message_id()?;
814
815        log::debug!(target: "gwasm", "DEBUG: [handle({message_id:.2?})] {program_id:.2?}: {data}");
816
817        Ok(())
818    }
819
820    fn lock_payload(&mut self, at: u32, len: u32) -> Result<PayloadSliceLock, Self::FallibleError> {
821        let end = at
822            .checked_add(len)
823            .ok_or(FallibleExecutionError::TooBigReadLen)?;
824        self.charge_gas_runtime_if_enough(RuntimeCosts::ReadPerByte(len))?;
825        PayloadSliceLock::try_new((at, end), &mut self.context.message_context)
826            .ok_or_else(|| FallibleExecutionError::ReadWrongRange.into())
827    }
828
829    fn unlock_payload(&mut self, payload_holder: &mut PayloadSliceLock) -> UnlockPayloadBound {
830        UnlockPayloadBound::from((&mut self.context.message_context, payload_holder))
831    }
832
833    fn size(&self) -> Result<usize, Self::UnrecoverableError> {
834        Ok(self.context.message_context.current().payload_bytes().len())
835    }
836
837    fn reserve_gas(
838        &mut self,
839        amount: u64,
840        duration: u32,
841    ) -> Result<ReservationId, Self::FallibleError> {
842        self.charge_gas_if_enough(self.context.message_context.settings().reservation_fee())?;
843
844        if duration == 0 {
845            return Err(ReservationError::ZeroReservationDuration.into());
846        }
847
848        if amount < self.context.mailbox_threshold {
849            return Err(ReservationError::ReservationBelowMailboxThreshold.into());
850        }
851
852        let reserve = u64::from(self.context.reserve_for.saturating_add(duration))
853            .saturating_mul(self.context.reservation);
854        let reduce_amount = amount.saturating_add(reserve);
855        if self.context.gas_counter.reduce(reduce_amount) == ChargeResult::NotEnough {
856            return Err(FallibleExecutionError::NotEnoughGas.into());
857        }
858
859        let id = self.context.gas_reserver.reserve(amount, duration)?;
860
861        Ok(id)
862    }
863
864    fn unreserve_gas(&mut self, id: ReservationId) -> Result<u64, Self::FallibleError> {
865        let amount = self.context.gas_reserver.unreserve(id)?;
866
867        // This statement is like an op that increases "left" counter, but do not affect "burned" counter,
868        // because we don't actually refund, we just rise "left" counter during unreserve
869        // and it won't affect gas allowance counter because we don't make any actual calculations
870        // TODO: uncomment when unreserving in current message features is discussed
871        /*if !self.context.gas_counter.increase(amount) {
872            return Err(some_charge_error.into());
873        }*/
874
875        Ok(amount)
876    }
877
878    fn system_reserve_gas(&mut self, amount: u64) -> Result<(), Self::FallibleError> {
879        // TODO: use `NonZeroU64` after issue #1838 is fixed
880        if amount == 0 {
881            return Err(ReservationError::ZeroReservationAmount.into());
882        }
883
884        if self.context.gas_counter.reduce(amount) == ChargeResult::NotEnough {
885            return Err(FallibleExecutionError::NotEnoughGas.into());
886        }
887
888        let reservation = &mut self.context.system_reservation;
889        *reservation = reservation
890            .map(|reservation| reservation.saturating_add(amount))
891            .or(Some(amount));
892
893        Ok(())
894    }
895
896    fn gas_available(&self) -> Result<u64, Self::UnrecoverableError> {
897        Ok(self.context.gas_counter.left())
898    }
899
900    fn value(&self) -> Result<u128, Self::UnrecoverableError> {
901        Ok(self.context.message_context.current().value())
902    }
903
904    fn value_available(&self) -> Result<u128, Self::UnrecoverableError> {
905        Ok(self.context.value_counter.left())
906    }
907
908    fn wait(&mut self) -> Result<(), Self::UnrecoverableError> {
909        self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee())?;
910
911        if self.context.message_context.reply_sent() {
912            return Err(UnrecoverableWaitError::WaitAfterReply.into());
913        }
914
915        let reserve = u64::from(self.context.reserve_for.saturating_add(1))
916            .saturating_mul(self.context.waitlist_cost);
917
918        if self.context.gas_counter.reduce(reserve) != ChargeResult::Enough {
919            return Err(UnrecoverableExecutionError::NotEnoughGas.into());
920        }
921
922        Ok(())
923    }
924
925    fn wait_for(&mut self, duration: u32) -> Result<(), Self::UnrecoverableError> {
926        self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee())?;
927
928        if self.context.message_context.reply_sent() {
929            return Err(UnrecoverableWaitError::WaitAfterReply.into());
930        }
931
932        if duration == 0 {
933            return Err(UnrecoverableWaitError::ZeroDuration.into());
934        }
935
936        let reserve = u64::from(self.context.reserve_for.saturating_add(duration))
937            .saturating_mul(self.context.waitlist_cost);
938
939        if self.context.gas_counter.reduce(reserve) != ChargeResult::Enough {
940            return Err(UnrecoverableExecutionError::NotEnoughGas.into());
941        }
942
943        Ok(())
944    }
945
946    fn wait_up_to(&mut self, duration: u32) -> Result<bool, Self::UnrecoverableError> {
947        self.charge_gas_if_enough(self.context.message_context.settings().waiting_fee())?;
948
949        if self.context.message_context.reply_sent() {
950            return Err(UnrecoverableWaitError::WaitAfterReply.into());
951        }
952
953        if duration == 0 {
954            return Err(UnrecoverableWaitError::ZeroDuration.into());
955        }
956
957        let reserve = u64::from(self.context.reserve_for.saturating_add(1))
958            .saturating_mul(self.context.waitlist_cost);
959
960        if self.context.gas_counter.reduce(reserve) != ChargeResult::Enough {
961            return Err(UnrecoverableExecutionError::NotEnoughGas.into());
962        }
963
964        let reserve_full = u64::from(self.context.reserve_for.saturating_add(duration))
965            .saturating_mul(self.context.waitlist_cost);
966        let reserve_diff = reserve_full - reserve;
967
968        Ok(self.context.gas_counter.reduce(reserve_diff) == ChargeResult::Enough)
969    }
970
971    fn wake(&mut self, waker_id: MessageId, delay: u32) -> Result<(), Self::FallibleError> {
972        self.charge_gas_if_enough(self.context.message_context.settings().waking_fee())?;
973
974        self.context.message_context.wake(waker_id, delay)?;
975        Ok(())
976    }
977
978    fn create_program(
979        &mut self,
980        packet: InitPacket,
981        delay: u32,
982    ) -> Result<(MessageId, ProgramId), Self::FallibleError> {
983        self.check_forbidden_destination(packet.destination())?;
984        self.safe_gasfull_sends(&packet)?;
985        self.charge_expiring_resources(&packet, true)?;
986        self.charge_sending_fee(delay)?;
987
988        self.charge_for_dispatch_stash_hold(delay)?;
989
990        let code_hash = packet.code_id();
991
992        // Send a message for program creation
993        let (mid, pid) = self
994            .context
995            .message_context
996            .init_program(packet, delay)
997            .map(|(init_msg_id, new_prog_id)| {
998                // Save a program candidate for this run
999                let entry = self
1000                    .context
1001                    .program_candidates_data
1002                    .entry(code_hash)
1003                    .or_default();
1004                entry.push((init_msg_id, new_prog_id));
1005
1006                (init_msg_id, new_prog_id)
1007            })?;
1008        Ok((mid, pid))
1009    }
1010
1011    fn reply_deposit(
1012        &mut self,
1013        message_id: MessageId,
1014        amount: u64,
1015    ) -> Result<(), Self::FallibleError> {
1016        self.reduce_gas(amount)?;
1017
1018        self.context
1019            .message_context
1020            .reply_deposit(message_id, amount)?;
1021
1022        Ok(())
1023    }
1024
1025    fn random(&self) -> Result<(&[u8], u32), Self::UnrecoverableError> {
1026        Ok((&self.context.random_data.0, self.context.random_data.1))
1027    }
1028
1029    fn forbidden_funcs(&self) -> &BTreeSet<SysCallName> {
1030        &self.context.forbidden_funcs
1031    }
1032}
1033
1034impl Ext {
1035    /// Inner alloc realization.
1036    pub fn alloc_inner<G: GrowHandler>(
1037        &mut self,
1038        pages_num: u32,
1039        mem: &mut impl Memory,
1040    ) -> Result<WasmPage, AllocExtError> {
1041        let pages = WasmPage::new(pages_num).map_err(|_| AllocError::ProgramAllocOutOfBounds)?;
1042
1043        self.context
1044            .allocations_context
1045            .alloc::<G>(pages, mem, |pages| {
1046                Ext::charge_gas_if_enough(
1047                    &mut self.context.gas_counter,
1048                    &mut self.context.gas_allowance_counter,
1049                    self.context.page_costs.mem_grow.calc(pages),
1050                )
1051            })
1052            .map_err(Into::into)
1053    }
1054
1055    /// Into ext info inner impl.
1056    /// `pages_for_data` returns vector of pages which data will be stored in info.
1057    pub fn into_ext_info_inner(
1058        self,
1059        memory: &impl Memory,
1060        pages_for_data: impl FnOnce(WasmPage, &BTreeSet<WasmPage>) -> Vec<GearPage>,
1061    ) -> Result<ExtInfo, MemoryError> {
1062        let ProcessorContext {
1063            allocations_context,
1064            message_context,
1065            gas_counter,
1066            gas_reserver,
1067            system_reservation,
1068            program_candidates_data,
1069            program_rents,
1070            ..
1071        } = self.context;
1072
1073        let (static_pages, initial_allocations, allocations) = allocations_context.into_parts();
1074        let mut pages_data = BTreeMap::new();
1075        for page in pages_for_data(static_pages, &allocations) {
1076            let mut buf = PageBuf::new_zeroed();
1077            memory.read(page.offset(), &mut buf)?;
1078            pages_data.insert(page, buf);
1079        }
1080
1081        let (outcome, mut context_store) = message_context.drain();
1082        let ContextOutcomeDrain {
1083            outgoing_dispatches: generated_dispatches,
1084            awakening,
1085            reply_deposits,
1086        } = outcome.drain();
1087
1088        let system_reservation_context = SystemReservationContext {
1089            current_reservation: system_reservation,
1090            previous_reservation: context_store.system_reservation(),
1091        };
1092
1093        context_store.set_reservation_nonce(&gas_reserver);
1094        if let Some(reservation) = system_reservation {
1095            context_store.add_system_reservation(reservation);
1096        }
1097
1098        let info = ExtInfo {
1099            gas_amount: gas_counter.to_amount(),
1100            gas_reserver,
1101            system_reservation_context,
1102            allocations: (allocations != initial_allocations)
1103                .then_some(allocations)
1104                .unwrap_or_default(),
1105            pages_data,
1106            generated_dispatches,
1107            awakening,
1108            reply_deposits,
1109            context_store,
1110            program_candidates_data,
1111            program_rents,
1112        };
1113        Ok(info)
1114    }
1115}
1116
1117#[cfg(test)]
1118mod tests {
1119    use super::*;
1120    use alloc::vec;
1121    use gear_core::{
1122        message::{ContextSettings, IncomingDispatch, Payload, MAX_PAYLOAD_SIZE},
1123        pages::PageNumber,
1124    };
1125
1126    struct MessageContextBuilder {
1127        incoming_dispatch: IncomingDispatch,
1128        program_id: ProgramId,
1129        sending_fee: u64,
1130        scheduled_sending_fee: u64,
1131        waiting_fee: u64,
1132        waking_fee: u64,
1133        reservation_fee: u64,
1134        outgoing_limit: u32,
1135    }
1136
1137    impl MessageContextBuilder {
1138        fn new() -> Self {
1139            Self {
1140                incoming_dispatch: Default::default(),
1141                program_id: Default::default(),
1142                sending_fee: 0,
1143                scheduled_sending_fee: 0,
1144                waiting_fee: 0,
1145                waking_fee: 0,
1146                reservation_fee: 0,
1147                outgoing_limit: 0,
1148            }
1149        }
1150
1151        fn build(self) -> MessageContext {
1152            MessageContext::new(
1153                self.incoming_dispatch,
1154                self.program_id,
1155                ContextSettings::new(
1156                    self.sending_fee,
1157                    self.scheduled_sending_fee,
1158                    self.waiting_fee,
1159                    self.waking_fee,
1160                    self.reservation_fee,
1161                    self.outgoing_limit,
1162                ),
1163            )
1164        }
1165
1166        fn with_outgoing_limit(mut self, outgoing_limit: u32) -> Self {
1167            self.outgoing_limit = outgoing_limit;
1168            self
1169        }
1170    }
1171
1172    struct ProcessorContextBuilder(ProcessorContext);
1173
1174    impl ProcessorContextBuilder {
1175        fn new() -> Self {
1176            Self(ProcessorContext {
1177                page_costs: PageCosts::new_for_tests(),
1178                ..ProcessorContext::new_mock()
1179            })
1180        }
1181
1182        fn build(self) -> ProcessorContext {
1183            self.0
1184        }
1185
1186        fn with_message_context(mut self, context: MessageContext) -> Self {
1187            self.0.message_context = context;
1188
1189            self
1190        }
1191
1192        fn with_gas(mut self, gas_counter: GasCounter) -> Self {
1193            self.0.gas_counter = gas_counter;
1194
1195            self
1196        }
1197
1198        fn with_allowance(mut self, gas_allowance_counter: GasAllowanceCounter) -> Self {
1199            self.0.gas_allowance_counter = gas_allowance_counter;
1200
1201            self
1202        }
1203
1204        fn with_weighs(mut self, weights: HostFnWeights) -> Self {
1205            self.0.host_fn_weights = weights;
1206
1207            self
1208        }
1209
1210        fn with_allocation_context(mut self, ctx: AllocationsContext) -> Self {
1211            self.0.allocations_context = ctx;
1212
1213            self
1214        }
1215    }
1216
1217    // Invariant: Refund never occurs in `free` call.
1218    #[test]
1219    fn free_no_refund() {
1220        // Set initial Ext state
1221        let initial_gas = 100;
1222        let initial_allowance = 10000;
1223
1224        let gas_left = (initial_gas, initial_allowance).into();
1225
1226        let existing_page = 99.into();
1227        let non_existing_page = 100.into();
1228
1229        let allocations_context =
1230            AllocationsContext::new(BTreeSet::from([existing_page]), 1.into(), 512.into());
1231
1232        let mut ext = Ext::new(
1233            ProcessorContextBuilder::new()
1234                .with_gas(GasCounter::new(initial_gas))
1235                .with_allowance(GasAllowanceCounter::new(initial_allowance))
1236                .with_allocation_context(allocations_context)
1237                .build(),
1238        );
1239
1240        // Freeing existing page.
1241        // Counters still shouldn't be changed.
1242        assert!(ext.free(existing_page).is_ok());
1243        assert_eq!(ext.gas_left(), gas_left);
1244
1245        // Freeing non existing page.
1246        // Counters shouldn't be changed.
1247        assert_eq!(
1248            ext.free(non_existing_page),
1249            Err(AllocExtError::Alloc(AllocError::InvalidFree(
1250                non_existing_page.raw()
1251            )))
1252        );
1253        assert_eq!(ext.gas_left(), gas_left);
1254    }
1255
1256    #[test]
1257    fn test_counter_zeroes() {
1258        // Set initial Ext state
1259        let free_weight = 1000;
1260        let host_fn_weights = HostFnWeights {
1261            free: free_weight,
1262            ..Default::default()
1263        };
1264
1265        let initial_gas = free_weight - 1;
1266        let initial_allowance = free_weight + 1;
1267
1268        let mut lack_gas_ext = Ext::new(
1269            ProcessorContextBuilder::new()
1270                .with_gas(GasCounter::new(initial_gas))
1271                .with_allowance(GasAllowanceCounter::new(initial_allowance))
1272                .with_weighs(host_fn_weights.clone())
1273                .build(),
1274        );
1275
1276        assert_eq!(
1277            lack_gas_ext.charge_gas_runtime(RuntimeCosts::Free),
1278            Err(ChargeError::GasLimitExceeded),
1279        );
1280
1281        let gas_amount = lack_gas_ext.gas_amount();
1282        let allowance = lack_gas_ext.context.gas_allowance_counter.left();
1283        // there was lack of gas
1284        assert_eq!(0, gas_amount.left());
1285        assert_eq!(initial_gas, gas_amount.burned());
1286        assert_eq!(initial_allowance - free_weight, allowance);
1287
1288        let initial_gas = free_weight;
1289        let initial_allowance = free_weight - 1;
1290
1291        let mut lack_allowance_ext = Ext::new(
1292            ProcessorContextBuilder::new()
1293                .with_gas(GasCounter::new(initial_gas))
1294                .with_allowance(GasAllowanceCounter::new(initial_allowance))
1295                .with_weighs(host_fn_weights)
1296                .build(),
1297        );
1298
1299        assert_eq!(
1300            lack_allowance_ext.charge_gas_runtime(RuntimeCosts::Free),
1301            Err(ChargeError::GasAllowanceExceeded),
1302        );
1303
1304        let gas_amount = lack_allowance_ext.gas_amount();
1305        let allowance = lack_allowance_ext.context.gas_allowance_counter.left();
1306        assert_eq!(initial_gas - free_weight, gas_amount.left());
1307        assert_eq!(initial_gas, gas_amount.burned());
1308        // there was lack of allowance
1309        assert_eq!(0, allowance);
1310    }
1311
1312    #[test]
1313    // This function tests:
1314    //
1315    // - `send_commit` on valid handle
1316    // - `send_commit` on invalid handle
1317    // - `send_commit` on used handle
1318    // - `send_init` after limit is exceeded
1319    fn test_send_commit() {
1320        let mut ext = Ext::new(
1321            ProcessorContextBuilder::new()
1322                .with_message_context(MessageContextBuilder::new().with_outgoing_limit(1).build())
1323                .build(),
1324        );
1325
1326        let data = HandlePacket::default();
1327
1328        let fake_handle = 0;
1329
1330        let msg = ext.send_commit(fake_handle, data.clone(), 0);
1331        assert_eq!(
1332            msg.unwrap_err(),
1333            FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::OutOfBounds))
1334        );
1335
1336        let handle = ext.send_init().expect("Outgoing limit is 1");
1337
1338        let msg = ext.send_commit(handle, data.clone(), 0);
1339        assert!(msg.is_ok());
1340
1341        let msg = ext.send_commit(handle, data, 0);
1342        assert_eq!(
1343            msg.unwrap_err(),
1344            FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1345        );
1346
1347        let handle = ext.send_init();
1348        assert_eq!(
1349            handle.unwrap_err(),
1350            FallibleExtError::Core(FallibleExtErrorCore::Message(
1351                MessageError::OutgoingMessagesAmountLimitExceeded
1352            ))
1353        );
1354    }
1355
1356    #[test]
1357    // This function tests:
1358    //
1359    // - `send_push` on non-existent handle
1360    // - `send_push` on valid handle
1361    // - `send_push` on used handle
1362    // - `send_push` with too large payload
1363    // - `send_push` data is added to buffer
1364    fn test_send_push() {
1365        let mut ext = Ext::new(
1366            ProcessorContextBuilder::new()
1367                .with_message_context(
1368                    MessageContextBuilder::new()
1369                        .with_outgoing_limit(u32::MAX)
1370                        .build(),
1371                )
1372                .build(),
1373        );
1374
1375        let data = HandlePacket::default();
1376
1377        let fake_handle = 0;
1378
1379        let res = ext.send_push(fake_handle, &[0, 0, 0]);
1380        assert_eq!(
1381            res.unwrap_err(),
1382            FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::OutOfBounds))
1383        );
1384
1385        let handle = ext.send_init().expect("Outgoing limit is u32::MAX");
1386
1387        let res = ext.send_push(handle, &[1, 2, 3]);
1388        assert!(res.is_ok());
1389
1390        let res = ext.send_push(handle, &[4, 5, 6]);
1391        assert!(res.is_ok());
1392
1393        let large_payload = vec![0u8; MAX_PAYLOAD_SIZE + 1];
1394
1395        let res = ext.send_push(handle, &large_payload);
1396        assert_eq!(
1397            res.unwrap_err(),
1398            FallibleExtError::Core(FallibleExtErrorCore::Message(
1399                MessageError::MaxMessageSizeExceed
1400            ))
1401        );
1402
1403        let msg = ext.send_commit(handle, data, 0);
1404        assert!(msg.is_ok());
1405
1406        let res = ext.send_push(handle, &[7, 8, 9]);
1407        assert_eq!(
1408            res.unwrap_err(),
1409            FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1410        );
1411
1412        let (outcome, _) = ext.context.message_context.drain();
1413        let ContextOutcomeDrain {
1414            mut outgoing_dispatches,
1415            ..
1416        } = outcome.drain();
1417        let dispatch = outgoing_dispatches
1418            .pop()
1419            .map(|(dispatch, _, _)| dispatch)
1420            .expect("Send commit was ok");
1421
1422        assert_eq!(dispatch.message().payload_bytes(), &[1, 2, 3, 4, 5, 6]);
1423    }
1424
1425    #[test]
1426    // This function tests:
1427    //
1428    // - `send_push_input` on non-existent handle
1429    // - `send_push_input` on valid handle
1430    // - `send_push_input` on used handle
1431    // - `send_push_input` data is added to buffer
1432    fn test_send_push_input() {
1433        let mut ext = Ext::new(
1434            ProcessorContextBuilder::new()
1435                .with_message_context(
1436                    MessageContextBuilder::new()
1437                        .with_outgoing_limit(u32::MAX)
1438                        .build(),
1439                )
1440                .build(),
1441        );
1442
1443        let data = HandlePacket::default();
1444
1445        let fake_handle = 0;
1446
1447        let res = ext.send_push_input(fake_handle, 0, 1);
1448        assert_eq!(
1449            res.unwrap_err(),
1450            FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::OutOfBounds))
1451        );
1452
1453        let handle = ext.send_init().expect("Outgoing limit is u32::MAX");
1454
1455        let res = ext
1456            .context
1457            .message_context
1458            .payload_mut()
1459            .try_extend_from_slice(&[1, 2, 3, 4, 5, 6]);
1460        assert!(res.is_ok());
1461
1462        let res = ext.send_push_input(handle, 2, 3);
1463        assert!(res.is_ok());
1464
1465        let res = ext.send_push_input(handle, 8, 10);
1466        assert!(res.is_ok());
1467
1468        let msg = ext.send_commit(handle, data, 0);
1469        assert!(msg.is_ok());
1470
1471        let res = ext.send_push_input(handle, 0, 1);
1472        assert_eq!(
1473            res.unwrap_err(),
1474            FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1475        );
1476
1477        let (outcome, _) = ext.context.message_context.drain();
1478        let ContextOutcomeDrain {
1479            mut outgoing_dispatches,
1480            ..
1481        } = outcome.drain();
1482        let dispatch = outgoing_dispatches
1483            .pop()
1484            .map(|(dispatch, _, _)| dispatch)
1485            .expect("Send commit was ok");
1486
1487        assert_eq!(dispatch.message().payload_bytes(), &[3, 4, 5]);
1488    }
1489
1490    #[test]
1491    // This function requires `reply_push` to work to add extra data.
1492    // This function tests:
1493    //
1494    // - `reply_commit` with too much data
1495    // - `reply_commit` with valid data
1496    // - `reply_commit` duplicate reply
1497    fn test_reply_commit() {
1498        let mut ext = Ext::new(
1499            ProcessorContextBuilder::new()
1500                .with_gas(GasCounter::new(u64::MAX))
1501                .with_message_context(
1502                    MessageContextBuilder::new()
1503                        .with_outgoing_limit(u32::MAX)
1504                        .build(),
1505                )
1506                .build(),
1507        );
1508
1509        let res = ext.reply_push(&[0]);
1510        assert!(res.is_ok());
1511
1512        let res = ext.reply_commit(ReplyPacket::new(Payload::filled_with(0), 0));
1513        assert_eq!(
1514            res.unwrap_err(),
1515            FallibleExtError::Core(FallibleExtErrorCore::Message(
1516                MessageError::MaxMessageSizeExceed
1517            ))
1518        );
1519
1520        let res = ext.reply_commit(ReplyPacket::auto());
1521        assert!(res.is_ok());
1522
1523        let res = ext.reply_commit(ReplyPacket::auto());
1524        assert_eq!(
1525            res.unwrap_err(),
1526            FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::DuplicateReply))
1527        );
1528    }
1529
1530    #[test]
1531    // This function requires `reply_push` to work to add extra data.
1532    // This function tests:
1533    //
1534    // - `reply_push` with valid data
1535    // - `reply_push` with too much data
1536    // - `reply_push` after `reply_commit`
1537    // - `reply_push` data is added to buffer
1538    fn test_reply_push() {
1539        let mut ext = Ext::new(
1540            ProcessorContextBuilder::new()
1541                .with_gas(GasCounter::new(u64::MAX))
1542                .with_message_context(
1543                    MessageContextBuilder::new()
1544                        .with_outgoing_limit(u32::MAX)
1545                        .build(),
1546                )
1547                .build(),
1548        );
1549
1550        let res = ext.reply_push(&[1, 2, 3]);
1551        assert!(res.is_ok());
1552
1553        let res = ext.reply_push(&[4, 5, 6]);
1554        assert!(res.is_ok());
1555
1556        let large_payload = vec![0u8; MAX_PAYLOAD_SIZE + 1];
1557
1558        let res = ext.reply_push(&large_payload);
1559        assert_eq!(
1560            res.unwrap_err(),
1561            FallibleExtError::Core(FallibleExtErrorCore::Message(
1562                MessageError::MaxMessageSizeExceed
1563            ))
1564        );
1565
1566        let res = ext.reply_commit(ReplyPacket::auto());
1567        assert!(res.is_ok());
1568
1569        let res = ext.reply_push(&[7, 8, 9]);
1570        assert_eq!(
1571            res.unwrap_err(),
1572            FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1573        );
1574
1575        let (outcome, _) = ext.context.message_context.drain();
1576        let ContextOutcomeDrain {
1577            mut outgoing_dispatches,
1578            ..
1579        } = outcome.drain();
1580        let dispatch = outgoing_dispatches
1581            .pop()
1582            .map(|(dispatch, _, _)| dispatch)
1583            .expect("Send commit was ok");
1584
1585        assert_eq!(dispatch.message().payload_bytes(), &[1, 2, 3, 4, 5, 6]);
1586    }
1587
1588    #[test]
1589    // This function tests:
1590    //
1591    // - `reply_push_input` with valid data
1592    // - `reply_push_input` after `reply_commit`
1593    // - `reply_push_input` data is added to buffer
1594    fn test_reply_push_input() {
1595        let mut ext = Ext::new(
1596            ProcessorContextBuilder::new()
1597                .with_message_context(
1598                    MessageContextBuilder::new()
1599                        .with_outgoing_limit(u32::MAX)
1600                        .build(),
1601                )
1602                .build(),
1603        );
1604
1605        let res = ext
1606            .context
1607            .message_context
1608            .payload_mut()
1609            .try_extend_from_slice(&[1, 2, 3, 4, 5, 6]);
1610        assert!(res.is_ok());
1611
1612        let res = ext.reply_push_input(2, 3);
1613        assert!(res.is_ok());
1614
1615        let res = ext.reply_push_input(8, 10);
1616        assert!(res.is_ok());
1617
1618        let msg = ext.reply_commit(ReplyPacket::auto());
1619        assert!(msg.is_ok());
1620
1621        let res = ext.reply_push_input(0, 1);
1622        assert_eq!(
1623            res.unwrap_err(),
1624            FallibleExtError::Core(FallibleExtErrorCore::Message(MessageError::LateAccess))
1625        );
1626
1627        let (outcome, _) = ext.context.message_context.drain();
1628        let ContextOutcomeDrain {
1629            mut outgoing_dispatches,
1630            ..
1631        } = outcome.drain();
1632        let dispatch = outgoing_dispatches
1633            .pop()
1634            .map(|(dispatch, _, _)| dispatch)
1635            .expect("Send commit was ok");
1636
1637        assert_eq!(dispatch.message().payload_bytes(), &[3, 4, 5]);
1638    }
1639
1640    mod property_tests {
1641        use super::*;
1642        use gear_core::{
1643            memory::HostPointer,
1644            pages::{PageError, PageNumber},
1645        };
1646        use proptest::{
1647            arbitrary::any,
1648            collection::size_range,
1649            prop_oneof, proptest,
1650            strategy::{Just, Strategy},
1651            test_runner::Config as ProptestConfig,
1652        };
1653
1654        struct TestMemory(WasmPage);
1655
1656        impl Memory for TestMemory {
1657            type GrowError = PageError;
1658
1659            fn grow(&mut self, pages: WasmPage) -> Result<(), Self::GrowError> {
1660                self.0 = self.0.add(pages)?;
1661                Ok(())
1662            }
1663
1664            fn size(&self) -> WasmPage {
1665                self.0
1666            }
1667
1668            fn write(&mut self, _offset: u32, _buffer: &[u8]) -> Result<(), MemoryError> {
1669                unimplemented!()
1670            }
1671
1672            fn read(&self, _offset: u32, _buffer: &mut [u8]) -> Result<(), MemoryError> {
1673                unimplemented!()
1674            }
1675
1676            unsafe fn get_buffer_host_addr_unsafe(&mut self) -> HostPointer {
1677                unimplemented!()
1678            }
1679        }
1680
1681        #[derive(Debug, Clone)]
1682        enum Action {
1683            Alloc { pages: WasmPage },
1684            Free { page: WasmPage },
1685        }
1686
1687        fn actions() -> impl Strategy<Value = Vec<Action>> {
1688            let action = wasm_page_number().prop_flat_map(|page| {
1689                prop_oneof![
1690                    Just(Action::Alloc { pages: page }),
1691                    Just(Action::Free { page })
1692                ]
1693            });
1694            proptest::collection::vec(action, 0..1024)
1695        }
1696
1697        fn allocations() -> impl Strategy<Value = BTreeSet<WasmPage>> {
1698            proptest::collection::btree_set(wasm_page_number(), size_range(0..1024))
1699        }
1700
1701        fn wasm_page_number() -> impl Strategy<Value = WasmPage> {
1702            any::<u16>().prop_map(WasmPage::from)
1703        }
1704
1705        fn proptest_config() -> ProptestConfig {
1706            ProptestConfig {
1707                cases: 1024,
1708                ..Default::default()
1709            }
1710        }
1711
1712        #[track_caller]
1713        fn assert_alloc_error(err: <Ext as Externalities>::AllocError) {
1714            match err {
1715                AllocExtError::Alloc(
1716                    AllocError::IncorrectAllocationData(_) | AllocError::ProgramAllocOutOfBounds,
1717                ) => {}
1718                err => Err(err).unwrap(),
1719            }
1720        }
1721
1722        #[track_caller]
1723        fn assert_free_error(err: <Ext as Externalities>::AllocError) {
1724            match err {
1725                AllocExtError::Alloc(AllocError::InvalidFree(_)) => {}
1726                err => Err(err).unwrap(),
1727            }
1728        }
1729
1730        proptest! {
1731            #![proptest_config(proptest_config())]
1732            #[test]
1733            fn alloc(
1734                static_pages in wasm_page_number(),
1735                allocations in allocations(),
1736                max_pages in wasm_page_number(),
1737                mem_size in wasm_page_number(),
1738                actions in actions(),
1739            ) {
1740                let _ = env_logger::try_init();
1741
1742                let ctx = AllocationsContext::new(allocations, static_pages, max_pages);
1743                let ctx = ProcessorContextBuilder::new()
1744                    .with_gas(GasCounter::new(u64::MAX))
1745                    .with_allowance(GasAllowanceCounter::new(u64::MAX))
1746                    .with_allocation_context(ctx)
1747                    .build();
1748                let mut ext = Ext::new(ctx);
1749                let mut mem = TestMemory(mem_size);
1750
1751                for action in actions {
1752                    match action {
1753                        Action::Alloc { pages } => {
1754                            if let Err(err) = ext.alloc(pages.raw(), &mut mem) {
1755                                assert_alloc_error(err);
1756                            }
1757                        }
1758                        Action::Free { page } => {
1759                            if let Err(err) = ext.free(page) {
1760                                assert_free_error(err);
1761                            }
1762                        },
1763                    }
1764                }
1765            }
1766        }
1767    }
1768}