gear_core_processor/
executor.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::{
20    common::{
21        ActorExecutionError, ActorExecutionErrorReplyReason, DispatchResult, DispatchResultKind,
22        ExecutionError, SystemExecutionError, WasmExecutionContext,
23    },
24    configs::{BlockInfo, ExecutionSettings},
25    ext::{ProcessorContext, ProcessorExternalities},
26};
27use actor_system_error::actor_system_error;
28use alloc::{
29    collections::{BTreeMap, BTreeSet},
30    format,
31    string::{String, ToString},
32    vec::Vec,
33};
34use gear_backend_common::{
35    lazy_pages::{GlobalsAccessConfig, LazyPagesWeights},
36    ActorTerminationReason, BackendExternalities, BackendReport, BackendSyscallError, Environment,
37    EnvironmentError, TerminationReason,
38};
39use gear_core::{
40    code::InstrumentedCode,
41    env::Externalities,
42    gas::{CountersOwner, GasAllowanceCounter, GasCounter, ValueCounter},
43    ids::ProgramId,
44    memory::{AllocationsContext, Memory, MemoryError, PageBuf},
45    message::{
46        ContextSettings, DispatchKind, IncomingDispatch, IncomingMessage, MessageContext,
47        WasmEntryPoint,
48    },
49    pages::{GearPage, PageU32Size, WasmPage},
50    program::Program,
51    reservation::GasReserver,
52};
53use scale_info::{
54    scale::{self, Decode, Encode},
55    TypeInfo,
56};
57
58actor_system_error! {
59    /// Prepare memory error.
60    pub type PrepareMemoryError = ActorSystemError<ActorPrepareMemoryError, SystemPrepareMemoryError>;
61}
62
63/// Prepare memory error
64#[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Eq, PartialOrd, Ord, derive_more::Display)]
65#[codec(crate = scale)]
66pub enum ActorPrepareMemoryError {
67    /// Stack end page, which value is specified in WASM code, cannot be bigger than static memory size.
68    #[display(fmt = "Stack end page {_0:?} is bigger then WASM static memory size {_1:?}")]
69    StackEndPageBiggerWasmMemSize(WasmPage, WasmPage),
70    /// It's not allowed to set initial data for stack memory pages, if they are specified in WASM code.
71    #[display(fmt = "Set initial data for stack pages is restricted")]
72    StackPagesHaveInitialData,
73    /// Stack is not aligned to WASM page size
74    #[display(fmt = "Stack end addr {_0:#x} must be aligned to WASM page size")]
75    StackIsNotAligned(u32),
76}
77
78#[derive(Debug, Eq, PartialEq, derive_more::Display)]
79pub enum SystemPrepareMemoryError {
80    /// Mem size less then static pages num
81    #[display(fmt = "Mem size less then static pages num")]
82    InsufficientMemorySize,
83    /// Page with data is not allocated for program
84    #[display(fmt = "{_0:?} is not allocated for program")]
85    PageIsNotAllocated(GearPage),
86    /// Cannot read initial memory data from wasm memory.
87    #[display(fmt = "Cannot read data for {_0:?}: {_1}")]
88    InitialMemoryReadFailed(GearPage, MemoryError),
89    /// Cannot write initial data to wasm memory.
90    #[display(fmt = "Cannot write initial data for {_0:?}: {_1}")]
91    InitialDataWriteFailed(GearPage, MemoryError),
92    /// Initial pages data must be empty in lazy pages mode
93    #[display(fmt = "Initial pages data must be empty when execute with lazy pages")]
94    InitialPagesContainsDataInLazyPagesMode,
95}
96
97/// Make checks that everything with memory goes well.
98fn check_memory<'a>(
99    allocations: &BTreeSet<WasmPage>,
100    pages_with_data: impl Iterator<Item = &'a GearPage>,
101    static_pages: WasmPage,
102    memory_size: WasmPage,
103) -> Result<(), SystemPrepareMemoryError> {
104    if memory_size < static_pages {
105        log::error!(
106            "Mem size less then static pages num: mem_size = {:?}, static_pages = {:?}",
107            memory_size,
108            static_pages
109        );
110        return Err(SystemPrepareMemoryError::InsufficientMemorySize);
111    }
112
113    // Checks that all pages with data are in allocations set.
114    for page in pages_with_data {
115        let wasm_page = page.to_page();
116        if wasm_page >= static_pages && !allocations.contains(&wasm_page) {
117            return Err(SystemPrepareMemoryError::PageIsNotAllocated(*page));
118        }
119    }
120
121    Ok(())
122}
123
124fn lazy_pages_check_initial_data(
125    initial_pages_data: &BTreeMap<GearPage, PageBuf>,
126) -> Result<(), SystemPrepareMemoryError> {
127    initial_pages_data
128        .is_empty()
129        .then_some(())
130        .ok_or(SystemPrepareMemoryError::InitialPagesContainsDataInLazyPagesMode)
131}
132
133/// Writes initial pages data to memory and prepare memory for execution.
134fn prepare_memory<ProcessorExt: ProcessorExternalities, EnvMem: Memory>(
135    mem: &mut EnvMem,
136    program_id: ProgramId,
137    pages_data: &mut BTreeMap<GearPage, PageBuf>,
138    static_pages: WasmPage,
139    stack_end: Option<u32>,
140    globals_config: GlobalsAccessConfig,
141    lazy_pages_weights: LazyPagesWeights,
142) -> Result<(), PrepareMemoryError> {
143    let stack_end = if let Some(stack_end) = stack_end {
144        let stack_end = (stack_end % WasmPage::size() == 0)
145            .then_some(WasmPage::from_offset(stack_end))
146            .ok_or(ActorPrepareMemoryError::StackIsNotAligned(stack_end))?;
147
148        if stack_end > static_pages {
149            return Err(ActorPrepareMemoryError::StackEndPageBiggerWasmMemSize(
150                stack_end,
151                static_pages,
152            )
153            .into());
154        }
155
156        Some(stack_end)
157    } else {
158        None
159    };
160
161    // Set initial data for pages
162    for (page, data) in pages_data.iter_mut() {
163        mem.write(page.offset(), data)
164            .map_err(|err| SystemPrepareMemoryError::InitialDataWriteFailed(*page, err))?;
165    }
166
167    if ProcessorExt::LAZY_PAGES_ENABLED {
168        lazy_pages_check_initial_data(pages_data)?;
169
170        ProcessorExt::lazy_pages_init_for_program(
171            mem,
172            program_id,
173            stack_end,
174            globals_config,
175            lazy_pages_weights,
176        );
177    } else {
178        // If we executes without lazy pages, then we have to save all initial data for static pages,
179        // in order to be able to identify pages, which has been changed during execution.
180        // Skip stack page if they are specified.
181        let begin = stack_end.unwrap_or_default();
182
183        if pages_data.keys().any(|&p| p < begin.to_page()) {
184            return Err(ActorPrepareMemoryError::StackPagesHaveInitialData.into());
185        }
186
187        let non_stack_pages = begin.iter_end(static_pages).unwrap_or_else(|err| {
188            unreachable!(
189                "We have already checked that `stack_end` is <= `static_pages`, but get: {}",
190                err
191            )
192        });
193        for page in non_stack_pages.flat_map(|p| p.to_pages_iter()) {
194            if pages_data.contains_key(&page) {
195                // This page already has initial data
196                continue;
197            }
198            let mut data = PageBuf::new_zeroed();
199            mem.read(page.offset(), &mut data)
200                .map_err(|err| SystemPrepareMemoryError::InitialMemoryReadFailed(page, err))?;
201            pages_data.insert(page, data);
202        }
203    }
204    Ok(())
205}
206
207/// Returns pages and their new data, which must be updated or uploaded to storage.
208fn get_pages_to_be_updated<ProcessorExt: ProcessorExternalities>(
209    old_pages_data: BTreeMap<GearPage, PageBuf>,
210    new_pages_data: BTreeMap<GearPage, PageBuf>,
211    static_pages: WasmPage,
212) -> BTreeMap<GearPage, PageBuf> {
213    if ProcessorExt::LAZY_PAGES_ENABLED {
214        // In lazy pages mode we update some page data in storage,
215        // when it has been write accessed, so no need to compare old and new page data.
216        new_pages_data.keys().for_each(|page| {
217            log::trace!("{:?} has been write accessed, update it in storage", page)
218        });
219        return new_pages_data;
220    }
221
222    let mut page_update = BTreeMap::new();
223    let mut old_pages_data = old_pages_data;
224    let static_gear_pages = static_pages.to_page();
225    for (page, new_data) in new_pages_data {
226        let initial_data = if let Some(initial_data) = old_pages_data.remove(&page) {
227            initial_data
228        } else {
229            // If it's static page without initial data,
230            // then it's stack page and we skip this page update.
231            if page < static_gear_pages {
232                continue;
233            }
234
235            // If page has no data in `pages_initial_data` then data is zeros.
236            // Because it's default data for wasm pages which is not static,
237            // and for all static pages we save data in `pages_initial_data` in E::new.
238            PageBuf::new_zeroed()
239        };
240
241        if new_data != initial_data {
242            page_update.insert(page, new_data);
243            log::trace!("{page:?} has been changed - will be updated in storage");
244        }
245    }
246    page_update
247}
248
249/// Execute wasm with dispatch and return dispatch result.
250pub fn execute_wasm<E>(
251    balance: u128,
252    dispatch: IncomingDispatch,
253    context: WasmExecutionContext,
254    settings: ExecutionSettings,
255    msg_ctx_settings: ContextSettings,
256) -> Result<DispatchResult, ExecutionError>
257where
258    E: Environment,
259    E::Ext: ProcessorExternalities + BackendExternalities + 'static,
260    <E::Ext as Externalities>::UnrecoverableError: BackendSyscallError,
261{
262    let WasmExecutionContext {
263        gas_counter,
264        gas_allowance_counter,
265        gas_reserver,
266        program,
267        mut pages_initial_data,
268        memory_size,
269    } = context;
270
271    let program_id = program.id();
272    let kind = dispatch.kind();
273
274    log::debug!("Executing program {}", program_id);
275    log::debug!("Executing dispatch {:?}", dispatch);
276
277    let static_pages = program.static_pages();
278    let allocations = program.allocations();
279
280    check_memory(
281        allocations,
282        pages_initial_data.keys(),
283        static_pages,
284        memory_size,
285    )
286    .map_err(SystemExecutionError::PrepareMemory)?;
287
288    // Creating allocations context.
289    let allocations_context =
290        AllocationsContext::new(allocations.clone(), static_pages, settings.max_pages);
291
292    // Creating message context.
293    let message_context = MessageContext::new(dispatch.clone(), program_id, msg_ctx_settings);
294
295    // Creating value counter.
296    //
297    // NOTE: Value available equals free balance with message value if value
298    // wasn't transferred to program yet.
299    //
300    // In case of second execution (between waits) - message value already
301    // included in free balance or wasted.
302    let value_available = balance.saturating_add(
303        dispatch
304            .context()
305            .is_none()
306            .then(|| dispatch.value())
307            .unwrap_or_default(),
308    );
309    let value_counter = ValueCounter::new(value_available);
310
311    let context = ProcessorContext {
312        gas_counter,
313        gas_allowance_counter,
314        gas_reserver,
315        system_reservation: None,
316        value_counter,
317        allocations_context,
318        message_context,
319        block_info: settings.block_info,
320        max_pages: settings.max_pages,
321        page_costs: settings.page_costs,
322        existential_deposit: settings.existential_deposit,
323        program_id,
324        program_candidates_data: Default::default(),
325        program_rents: Default::default(),
326        host_fn_weights: settings.host_fn_weights,
327        forbidden_funcs: settings.forbidden_funcs,
328        mailbox_threshold: settings.mailbox_threshold,
329        waitlist_cost: settings.waitlist_cost,
330        dispatch_hold_cost: settings.dispatch_hold_cost,
331        reserve_for: settings.reserve_for,
332        reservation: settings.reservation,
333        random_data: settings.random_data,
334        rent_cost: settings.rent_cost,
335    };
336
337    let lazy_pages_weights = context.page_costs.lazy_pages_weights();
338
339    // Creating externalities.
340    let ext = E::Ext::new(context);
341
342    // Execute program in backend env.
343    let execute = || {
344        let env = E::new(
345            ext,
346            program.raw_code(),
347            kind,
348            program.code().exports().clone(),
349            memory_size,
350        )
351        .map_err(EnvironmentError::from_infallible)?;
352        env.execute(|memory, stack_end, globals_config| {
353            prepare_memory::<E::Ext, E::Memory>(
354                memory,
355                program_id,
356                &mut pages_initial_data,
357                static_pages,
358                stack_end,
359                globals_config,
360                lazy_pages_weights,
361            )
362        })
363    };
364    let (termination, memory, ext) = match execute() {
365        Ok(report) => {
366            let BackendReport {
367                termination_reason,
368                memory_wrap: mut memory,
369                ext,
370            } = report;
371
372            let mut termination = match termination_reason {
373                TerminationReason::Actor(reason) => reason,
374                TerminationReason::System(reason) => {
375                    return Err(ExecutionError::System(reason.into()))
376                }
377            };
378
379            // released pages initial data will be added to `pages_initial_data` after execution.
380            if E::Ext::LAZY_PAGES_ENABLED {
381                E::Ext::lazy_pages_post_execution_actions(&mut memory);
382
383                if !E::Ext::lazy_pages_status().is_normal() {
384                    termination = ext.current_counter_type().into()
385                }
386            }
387
388            (termination, memory, ext)
389        }
390        Err(EnvironmentError::System(e)) => {
391            return Err(ExecutionError::System(SystemExecutionError::Environment(
392                e.to_string(),
393            )))
394        }
395        Err(EnvironmentError::PrepareMemory(gas_amount, PrepareMemoryError::Actor(e))) => {
396            return Err(ExecutionError::Actor(ActorExecutionError {
397                gas_amount,
398                reason: ActorExecutionErrorReplyReason::PrepareMemory(e),
399            }))
400        }
401        Err(EnvironmentError::PrepareMemory(_gas_amount, PrepareMemoryError::System(e))) => {
402            return Err(ExecutionError::System(e.into()));
403        }
404        Err(EnvironmentError::Actor(gas_amount, err)) => {
405            log::trace!("ActorExecutionErrorReplyReason::Environment({err}) occurred");
406            return Err(ExecutionError::Actor(ActorExecutionError {
407                gas_amount,
408                reason: ActorExecutionErrorReplyReason::Environment,
409            }));
410        }
411    };
412
413    log::debug!("Termination reason: {:?}", termination);
414
415    let info = ext
416        .into_ext_info(&memory)
417        .map_err(SystemExecutionError::IntoExtInfo)?;
418
419    if E::Ext::LAZY_PAGES_ENABLED {
420        lazy_pages_check_initial_data(&pages_initial_data)
421            .map_err(SystemExecutionError::PrepareMemory)?;
422    }
423
424    // Parsing outcome.
425    let kind = match termination {
426        ActorTerminationReason::Exit(value_dest) => DispatchResultKind::Exit(value_dest),
427        ActorTerminationReason::Leave | ActorTerminationReason::Success => {
428            DispatchResultKind::Success
429        }
430        ActorTerminationReason::Trap(explanation) => {
431            log::debug!("šŸ’„ Trap during execution of {program_id}\nšŸ“” Explanation: {explanation}");
432            DispatchResultKind::Trap(explanation)
433        }
434        ActorTerminationReason::Wait(duration, waited_type) => {
435            DispatchResultKind::Wait(duration, waited_type)
436        }
437        ActorTerminationReason::GasAllowanceExceeded => DispatchResultKind::GasAllowanceExceed,
438    };
439
440    let page_update =
441        get_pages_to_be_updated::<E::Ext>(pages_initial_data, info.pages_data, static_pages);
442
443    // Getting new programs that are scheduled to be initialized (respected messages are in `generated_dispatches` collection)
444    let program_candidates = info.program_candidates_data;
445
446    // Output
447    Ok(DispatchResult {
448        kind,
449        dispatch,
450        program_id,
451        context_store: info.context_store,
452        generated_dispatches: info.generated_dispatches,
453        awakening: info.awakening,
454        reply_deposits: info.reply_deposits,
455        program_candidates,
456        program_rents: info.program_rents,
457        gas_amount: info.gas_amount,
458        gas_reserver: Some(info.gas_reserver),
459        system_reservation_context: info.system_reservation_context,
460        page_update,
461        allocations: info.allocations,
462    })
463}
464
465/// !!! FOR TESTING / INFORMATIONAL USAGE ONLY
466#[allow(clippy::too_many_arguments)]
467pub fn execute_for_reply<E, EP>(
468    function: EP,
469    instrumented_code: InstrumentedCode,
470    pages_initial_data: Option<BTreeMap<GearPage, PageBuf>>,
471    allocations: Option<BTreeSet<WasmPage>>,
472    program_id: Option<ProgramId>,
473    payload: Vec<u8>,
474    gas_limit: u64,
475    block_info: BlockInfo,
476) -> Result<Vec<u8>, String>
477where
478    E: Environment<EP>,
479    E::Ext: ProcessorExternalities + BackendExternalities + 'static,
480    <E::Ext as Externalities>::UnrecoverableError: BackendSyscallError,
481    EP: WasmEntryPoint,
482{
483    let program = Program::new(program_id.unwrap_or_default(), instrumented_code);
484    let mut pages_initial_data: BTreeMap<GearPage, PageBuf> =
485        pages_initial_data.unwrap_or_default();
486    let static_pages = program.static_pages();
487    let allocations = allocations.unwrap_or_else(|| program.allocations().clone());
488
489    let memory_size = if let Some(page) = allocations.iter().next_back() {
490        page.inc()
491            .map_err(|err| err.to_string())
492            .expect("Memory size overflow, impossible")
493    } else if static_pages != WasmPage::from(0) {
494        static_pages
495    } else {
496        0.into()
497    };
498
499    let context = ProcessorContext {
500        gas_counter: GasCounter::new(gas_limit),
501        gas_allowance_counter: GasAllowanceCounter::new(gas_limit),
502        gas_reserver: GasReserver::new(&Default::default(), Default::default(), Default::default()),
503        value_counter: ValueCounter::new(Default::default()),
504        allocations_context: AllocationsContext::new(allocations, static_pages, 512.into()),
505        message_context: MessageContext::new(
506            IncomingDispatch::new(
507                DispatchKind::Handle,
508                IncomingMessage::new(
509                    Default::default(),
510                    Default::default(),
511                    payload
512                        .try_into()
513                        .map_err(|e| format!("Failed to create payload: {e:?}"))?,
514                    gas_limit,
515                    Default::default(),
516                    Default::default(),
517                ),
518                None,
519            ),
520            program.id(),
521            ContextSettings::new(0, 0, 0, 0, 0, 0),
522        ),
523        block_info,
524        max_pages: 512.into(),
525        page_costs: Default::default(),
526        existential_deposit: Default::default(),
527        program_id: program.id(),
528        program_candidates_data: Default::default(),
529        program_rents: Default::default(),
530        host_fn_weights: Default::default(),
531        forbidden_funcs: Default::default(),
532        mailbox_threshold: Default::default(),
533        waitlist_cost: Default::default(),
534        dispatch_hold_cost: Default::default(),
535        reserve_for: Default::default(),
536        reservation: Default::default(),
537        random_data: Default::default(),
538        system_reservation: Default::default(),
539        rent_cost: Default::default(),
540    };
541
542    let lazy_pages_weights = context.page_costs.lazy_pages_weights();
543
544    // Creating externalities.
545    let ext = E::Ext::new(context);
546
547    // Execute program in backend env.
548    let f = || {
549        let env = E::new(
550            ext,
551            program.raw_code(),
552            function,
553            program.code().exports().clone(),
554            memory_size,
555        )
556        .map_err(EnvironmentError::from_infallible)?;
557        env.execute(|memory, stack_end, globals_config| {
558            prepare_memory::<E::Ext, E::Memory>(
559                memory,
560                program.id(),
561                &mut pages_initial_data,
562                static_pages,
563                stack_end,
564                globals_config,
565                lazy_pages_weights,
566            )
567        })
568    };
569
570    let (termination, memory, ext) = match f() {
571        Ok(report) => {
572            let BackendReport {
573                termination_reason,
574                memory_wrap,
575                ext,
576            } = report;
577
578            let termination_reason = match termination_reason {
579                TerminationReason::Actor(reason) => reason,
580                TerminationReason::System(reason) => {
581                    return Err(format!("Backend error: {reason}"))
582                }
583            };
584
585            (termination_reason, memory_wrap, ext)
586        }
587        Err(e) => return Err(format!("Backend error: {e}")),
588    };
589
590    match termination {
591        ActorTerminationReason::Exit(_)
592        | ActorTerminationReason::Leave
593        | ActorTerminationReason::Wait(_, _) => {
594            return Err("Execution has incorrect termination reason".into())
595        }
596        ActorTerminationReason::Success => (),
597        ActorTerminationReason::Trap(explanation) => {
598            return Err(format!(
599                "Program execution failed with error: {explanation}"
600            ));
601        }
602        ActorTerminationReason::GasAllowanceExceeded => return Err("Unreachable".into()),
603    };
604
605    let info = ext
606        .into_ext_info(&memory)
607        .map_err(|e| format!("Backend postprocessing error: {e:?}"))?;
608
609    log::debug!(
610        "[execute_for_reply] Gas burned: {}",
611        info.gas_amount.burned()
612    );
613
614    for (dispatch, _, _) in info.generated_dispatches {
615        if matches!(dispatch.kind(), DispatchKind::Reply) {
616            return Ok(dispatch.payload_bytes().to_vec());
617        }
618    }
619
620    Err("Reply not found".into())
621}
622
623#[cfg(test)]
624mod tests {
625    use super::*;
626    use alloc::vec::Vec;
627    use gear_backend_common::lazy_pages::Status;
628    use gear_core::{
629        memory::PageBufInner,
630        pages::{PageNumber, WasmPage},
631    };
632
633    struct TestExt;
634    struct LazyTestExt;
635
636    impl ProcessorExternalities for TestExt {
637        const LAZY_PAGES_ENABLED: bool = false;
638        fn new(_context: ProcessorContext) -> Self {
639            Self
640        }
641
642        fn lazy_pages_init_for_program(
643            _mem: &mut impl Memory,
644            _prog_id: ProgramId,
645            _stack_end: Option<WasmPage>,
646            _globals_config: GlobalsAccessConfig,
647            _lazy_pages_weights: LazyPagesWeights,
648        ) {
649        }
650
651        fn lazy_pages_post_execution_actions(_mem: &mut impl Memory) {}
652        fn lazy_pages_status() -> Status {
653            Status::Normal
654        }
655    }
656
657    impl ProcessorExternalities for LazyTestExt {
658        const LAZY_PAGES_ENABLED: bool = true;
659
660        fn new(_context: ProcessorContext) -> Self {
661            Self
662        }
663
664        fn lazy_pages_init_for_program(
665            _mem: &mut impl Memory,
666            _prog_id: ProgramId,
667            _stack_end: Option<WasmPage>,
668            _globals_config: GlobalsAccessConfig,
669            _lazy_pages_weights: LazyPagesWeights,
670        ) {
671        }
672
673        fn lazy_pages_post_execution_actions(_mem: &mut impl Memory) {}
674        fn lazy_pages_status() -> Status {
675            Status::Normal
676        }
677    }
678
679    fn prepare_pages_and_allocs() -> (Vec<GearPage>, BTreeSet<WasmPage>) {
680        let data = [0u16, 1, 2, 8, 18, 25, 27, 28, 93, 146, 240, 518];
681        let pages = data.map(Into::into);
682        (pages.to_vec(), pages.map(|p| p.to_page()).into())
683    }
684
685    fn prepare_pages() -> BTreeMap<GearPage, PageBuf> {
686        let mut pages = BTreeMap::new();
687        for i in 0..=255 {
688            let buffer = PageBufInner::filled_with(i);
689            pages.insert((i as u16).into(), PageBuf::from_inner(buffer));
690        }
691        pages
692    }
693
694    #[test]
695    fn check_memory_insufficient() {
696        let res = check_memory(&[].into(), [].iter(), 8.into(), 4.into());
697        assert_eq!(res, Err(SystemPrepareMemoryError::InsufficientMemorySize));
698    }
699
700    #[test]
701    fn check_memory_not_allocated() {
702        let (pages, mut allocs) = prepare_pages_and_allocs();
703        let last = *allocs.iter().last().unwrap();
704        allocs.remove(&last);
705        let res = check_memory(&allocs, pages.iter(), 2.into(), 4.into());
706        assert_eq!(
707            res,
708            Err(SystemPrepareMemoryError::PageIsNotAllocated(
709                *pages.last().unwrap()
710            ))
711        );
712    }
713
714    #[test]
715    fn check_memory_ok() {
716        let (pages, allocs) = prepare_pages_and_allocs();
717        check_memory(&allocs, pages.iter(), 4.into(), 8.into()).unwrap();
718    }
719
720    #[test]
721    fn lazy_pages_to_update() {
722        let new_pages = prepare_pages();
723        let res =
724            get_pages_to_be_updated::<LazyTestExt>(Default::default(), new_pages.clone(), 0.into());
725        // All touched pages are to be updated in lazy mode
726        assert_eq!(res, new_pages);
727    }
728
729    #[test]
730    fn no_pages_to_update() {
731        let old_pages = prepare_pages();
732        let mut new_pages = old_pages.clone();
733        let static_pages = 4;
734        let res =
735            get_pages_to_be_updated::<TestExt>(old_pages, new_pages.clone(), static_pages.into());
736        assert_eq!(res, Default::default());
737
738        // Change static pages
739        for i in 0..static_pages {
740            let buffer = PageBufInner::filled_with(42);
741            new_pages.insert(i.into(), PageBuf::from_inner(buffer));
742        }
743        // Do not include non-static pages
744        let new_pages = new_pages
745            .into_iter()
746            .take(WasmPage::from(static_pages).to_page::<GearPage>().raw() as _)
747            .collect();
748        let res =
749            get_pages_to_be_updated::<TestExt>(Default::default(), new_pages, static_pages.into());
750        assert_eq!(res, Default::default());
751    }
752
753    #[test]
754    fn pages_to_update() {
755        let old_pages = prepare_pages();
756        let mut new_pages = old_pages.clone();
757
758        let page_with_zero_data = WasmPage::from(30).to_page();
759        let changes: BTreeMap<GearPage, PageBuf> = [
760            (
761                WasmPage::from(1).to_page(),
762                PageBuf::from_inner(PageBufInner::filled_with(42u8)),
763            ),
764            (
765                WasmPage::from(5).to_page(),
766                PageBuf::from_inner(PageBufInner::filled_with(84u8)),
767            ),
768            (page_with_zero_data, PageBuf::new_zeroed()),
769        ]
770        .into_iter()
771        .collect();
772        new_pages.extend(changes.clone().into_iter());
773
774        // Change pages
775        let static_pages = 4.into();
776        let res = get_pages_to_be_updated::<TestExt>(old_pages, new_pages.clone(), static_pages);
777        assert_eq!(res, changes);
778
779        // There was no any old page
780        let res =
781            get_pages_to_be_updated::<TestExt>(Default::default(), new_pages.clone(), static_pages);
782
783        // The result is all pages except the static ones
784        for page in static_pages.to_page::<GearPage>().iter_from_zero() {
785            new_pages.remove(&page);
786        }
787
788        // Remove page with zero data, because it must not be updated.
789        new_pages.remove(&page_with_zero_data);
790
791        assert_eq!(res, new_pages);
792    }
793}