gear_core/
env.rs

1// This file is part of Gear.
2
3// Copyright (C) 2021-2025 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Environment for running a module.
20
21use crate::{
22    env_vars::EnvVars,
23    ids::{MessageId, ProgramId, ReservationId},
24    memory::Memory,
25    message::{HandlePacket, InitPacket, MessageContext, Payload, ReplyPacket},
26    pages::WasmPage,
27};
28use alloc::collections::BTreeSet;
29use core::{fmt::Display, mem};
30use gear_core_errors::{ReplyCode, SignalCode};
31use gear_wasm_instrument::syscalls::SyscallName;
32
33/// Lock for the payload of the incoming/currently executing message.
34///
35/// The type was mainly introduced to establish type safety mechanics
36/// for the read of the payload from externalities. To type's purposes
37/// see [`Externalities::lock_payload`] docs.
38///
39/// ### Usage
40/// This type gives access to some slice of the currently executing message
41/// payload, but doesn't do it directly. It gives to the caller the [`PayloadSliceAccess`]
42/// wrapper, which actually can return the slice of the payload. But this wrapper
43/// is instantiated only inside the [`Self::drop_with`] method.
44/// This is actually done to prevent a user of the type from locking payload of the
45/// message, which actually moves it, and forgetting to unlock it back, because
46/// if access to the slice buffer was granted directly from the holder, the type user
47/// could have written the data to memory and then have dropped the holder. As a result
48/// the executing message payload wouldn't have been returned. So [`PayloadSliceLock::drop_with`]
49/// is a kind of scope-guard for the data and the [`PayloadSliceAccess`] is a data access guard.
50///
51/// For more usage info read [`Self::drop_with`] docs.
52pub struct PayloadSliceLock {
53    /// Locked payload
54    payload: Payload,
55    /// Range values indicating slice bounds.
56    range: (usize, usize),
57}
58
59impl PayloadSliceLock {
60    /// Creates a new [`PayloadSliceLock`] from the currently executed message context.
61    ///
62    /// The method checks whether received range (slice) is correct, i.e., the end is lower
63    /// than payload's length. If the check goes well, the ownership over payload will be
64    /// taken from the message context by [`mem::take`].
65    pub fn try_new((start, end): (u32, u32), msg_ctx: &mut MessageContext) -> Option<Self> {
66        let payload_len = msg_ctx.payload_mut().inner().len();
67        if end as usize > payload_len {
68            return None;
69        }
70
71        Some(Self {
72            payload: mem::take(msg_ctx.payload_mut()),
73            range: (start as usize, end as usize),
74        })
75    }
76
77    /// Releases back ownership of the locked payload to the message context.
78    ///
79    /// The method actually performs [`mem::swap`] under the hood. It's supposed
80    /// to be called from [`Externalities::unlock_payload`], implementor of which
81    /// owns provided message context.
82    fn release(&mut self, msg_ctx: &mut MessageContext) {
83        mem::swap(msg_ctx.payload_mut(), &mut self.payload);
84    }
85
86    /// Uses the lock in the provided `job` and drops the lock after running it.
87    ///
88    /// [`PayloadSliceLock`]'s main purpose is to provide safe access to the payload's
89    /// slice and ensure it will be returned back to the message.
90    ///
91    /// Type docs explain how safe access is designed with [`PayloadSliceAccess`].
92    ///
93    /// We ensure that the payload is released back by returning the [`DropPayloadLockBound`]
94    /// from the `job`. This type can actually be instantiated only from tuple of two:
95    /// [`UnlockPayloadBound`] and some result with err variant type to be `JobErr`.
96    /// The first is returned from [`Externalities::unlock_payload`], so it means that
97    /// that payload was reclaimed by the original owner. The other result stores actual
98    /// error of the `Job` as it could have called fallible actions inside it. So,
99    /// [`DropPayloadLockBound`] gives an opportunity to store the actual result of the job,
100    /// but also gives guarantee that payload was reclaimed.
101    pub fn drop_with<JobErr, Job>(mut self, mut job: Job) -> DropPayloadLockBound<JobErr>
102    where
103        Job: FnMut(PayloadSliceAccess<'_>) -> DropPayloadLockBound<JobErr>,
104    {
105        let held_range = PayloadSliceAccess(&mut self);
106        job(held_range)
107    }
108
109    fn in_range(&self) -> &[u8] {
110        let (start, end) = self.range;
111        // Will not panic as range is checked.
112        &self.payload.inner()[start..end]
113    }
114}
115
116/// A wrapper over mutable reference to [`PayloadSliceLock`]
117/// which can give to the caller the slice of the held payload.
118///
119/// For more information read [`PayloadSliceLock`] docs.
120pub struct PayloadSliceAccess<'a>(&'a mut PayloadSliceLock);
121
122impl<'a> PayloadSliceAccess<'a> {
123    /// Returns slice of the held payload.
124    pub fn as_slice(&self) -> &[u8] {
125        self.0.in_range()
126    }
127
128    /// Converts the wrapper into [`PayloadSliceLock`].
129    pub fn into_lock(self) -> &'a mut PayloadSliceLock {
130        self.0
131    }
132}
133
134/// Result of calling a `job` within [`PayloadSliceLock::drop_with`].
135///
136/// This is a "bound" type which means it's main purpose is to give
137/// some type-level guarantees. More precisely, it gives guarantee
138/// that payload value was reclaimed/unlocked by the owner. Also it stores the error
139/// of the `job`, which gives opportunity to handle the actual job's runtime
140/// error, but not bound wrappers.
141pub struct DropPayloadLockBound<JobError> {
142    job_result: Result<(), JobError>,
143}
144
145impl<JobErr> DropPayloadLockBound<JobErr> {
146    /// Convert into inner job of the [`PayloadSliceLock::drop_with`] result.
147    pub fn into_inner(self) -> Result<(), JobErr> {
148        self.job_result
149    }
150}
151
152impl<JobErr> From<(UnlockPayloadBound, Result<(), JobErr>)> for DropPayloadLockBound<JobErr> {
153    fn from((_token, job_result): (UnlockPayloadBound, Result<(), JobErr>)) -> Self {
154        DropPayloadLockBound { job_result }
155    }
156}
157
158/// Result of calling [`Externalities::unlock_payload`].
159///
160/// This is a "bound" type which means it doesn't store
161/// anything, but gives type-level guarantees that [`PayloadSliceLock`]
162/// released the payload back to the message context.
163pub struct UnlockPayloadBound(());
164
165impl From<(&mut MessageContext, &mut PayloadSliceLock)> for UnlockPayloadBound {
166    fn from((msg_ctx, payload_holder): (&mut MessageContext, &mut PayloadSliceLock)) -> Self {
167        payload_holder.release(msg_ctx);
168
169        UnlockPayloadBound(())
170    }
171}
172
173/// External api and data for managing memory and messages,
174/// use by an executing program to trigger state transition
175/// in runtime.
176pub trait Externalities {
177    /// An error issued in infallible syscall.
178    type UnrecoverableError;
179
180    /// An error issued in fallible syscall.
181    type FallibleError;
182
183    /// An error issued during allocation.
184    type AllocError: Display;
185
186    /// Allocate number of pages.
187    ///
188    /// The resulting page number should point to `pages` consecutive memory pages.
189    fn alloc<Context>(
190        &mut self,
191        ctx: &mut Context,
192        mem: &mut impl Memory<Context>,
193        pages_num: u32,
194    ) -> Result<WasmPage, Self::AllocError>;
195
196    /// Free specific page.
197    fn free(&mut self, page: WasmPage) -> Result<(), Self::AllocError>;
198
199    /// Free specific memory range.
200    fn free_range(&mut self, start: WasmPage, end: WasmPage) -> Result<(), Self::AllocError>;
201
202    /// Get environment variables currently set in the system and in the form
203    /// corresponded to the requested version.
204    fn env_vars(&self, version: u32) -> Result<EnvVars, Self::UnrecoverableError>;
205
206    /// Get the current block height.
207    fn block_height(&self) -> Result<u32, Self::UnrecoverableError>;
208
209    /// Get the current block timestamp.
210    fn block_timestamp(&self) -> Result<u64, Self::UnrecoverableError>;
211
212    /// Initialize a new incomplete message for another program and return its handle.
213    fn send_init(&mut self) -> Result<u32, Self::FallibleError>;
214
215    /// Push an extra buffer into message payload by handle.
216    fn send_push(&mut self, handle: u32, buffer: &[u8]) -> Result<(), Self::FallibleError>;
217
218    /// Complete message and send it to another program.
219    fn send_commit(
220        &mut self,
221        handle: u32,
222        msg: HandlePacket,
223        delay: u32,
224    ) -> Result<MessageId, Self::FallibleError>;
225
226    /// Send message to another program.
227    fn send(&mut self, msg: HandlePacket, delay: u32) -> Result<MessageId, Self::FallibleError> {
228        let handle = self.send_init()?;
229        self.send_commit(handle, msg, delay)
230    }
231
232    /// Push the incoming message buffer into message payload by handle.
233    fn send_push_input(
234        &mut self,
235        handle: u32,
236        offset: u32,
237        len: u32,
238    ) -> Result<(), Self::FallibleError>;
239
240    /// Complete message and send it to another program using gas from reservation.
241    fn reservation_send_commit(
242        &mut self,
243        id: ReservationId,
244        handle: u32,
245        msg: HandlePacket,
246        delay: u32,
247    ) -> Result<MessageId, Self::FallibleError>;
248
249    /// Send message to another program using gas from reservation.
250    fn reservation_send(
251        &mut self,
252        id: ReservationId,
253        msg: HandlePacket,
254        delay: u32,
255    ) -> Result<MessageId, Self::FallibleError> {
256        let handle = self.send_init()?;
257        self.reservation_send_commit(id, handle, msg, delay)
258    }
259
260    /// Push an extra buffer into reply message.
261    fn reply_push(&mut self, buffer: &[u8]) -> Result<(), Self::FallibleError>;
262
263    /// Complete reply message and send it to source program.
264    fn reply_commit(&mut self, msg: ReplyPacket) -> Result<MessageId, Self::FallibleError>;
265
266    /// Complete reply message and send it to source program from reservation.
267    fn reservation_reply_commit(
268        &mut self,
269        id: ReservationId,
270        msg: ReplyPacket,
271    ) -> Result<MessageId, Self::FallibleError>;
272
273    /// Produce reply to the current message.
274    fn reply(&mut self, msg: ReplyPacket) -> Result<MessageId, Self::FallibleError> {
275        self.reply_commit(msg)
276    }
277
278    /// Produce reply to the current message from reservation.
279    fn reservation_reply(
280        &mut self,
281        id: ReservationId,
282        msg: ReplyPacket,
283    ) -> Result<MessageId, Self::FallibleError> {
284        self.reservation_reply_commit(id, msg)
285    }
286
287    /// Get the message id of the initial message.
288    fn reply_to(&self) -> Result<MessageId, Self::FallibleError>;
289
290    /// Get the message id which signal issues from.
291    fn signal_from(&self) -> Result<MessageId, Self::FallibleError>;
292
293    /// Push the incoming message buffer into reply message.
294    fn reply_push_input(&mut self, offset: u32, len: u32) -> Result<(), Self::FallibleError>;
295
296    /// Get the source of the message currently being handled.
297    fn source(&self) -> Result<ProgramId, Self::UnrecoverableError>;
298
299    /// Get the reply code if the message being processed.
300    fn reply_code(&self) -> Result<ReplyCode, Self::FallibleError>;
301
302    /// Get the signal code if the message being processed.
303    fn signal_code(&self) -> Result<SignalCode, Self::FallibleError>;
304
305    /// Get the id of the message currently being handled.
306    fn message_id(&self) -> Result<MessageId, Self::UnrecoverableError>;
307
308    /// Get the id of program itself
309    fn program_id(&self) -> Result<ProgramId, Self::UnrecoverableError>;
310
311    /// Send debug message.
312    ///
313    /// This should be no-op in release builds.
314    fn debug(&self, data: &str) -> Result<(), Self::UnrecoverableError>;
315
316    /// Takes ownership over payload of the executing message and
317    /// returns it in the wrapper [`PayloadSliceLock`], which acts
318    /// like lock.
319    ///
320    /// Due to details of implementation of the runtime which executes gear
321    /// syscalls inside wasm execution environment, to prevent additional memory
322    /// allocation on payload read op, we give ownership over payload to the caller.
323    /// Giving ownership over payload actually means, that the payload value in the
324    /// currently executed message will become empty.
325    /// To prevent from the risk of payload being not "returned" back to the
326    /// message a [`Externalities::unlock_payload`] is introduced. For more info,
327    /// read docs to [`PayloadSliceLock`], [`DropPayloadLockBound`],
328    /// [`UnlockPayloadBound`], [`PayloadSliceAccess`] types and their methods.
329    fn lock_payload(&mut self, at: u32, len: u32) -> Result<PayloadSliceLock, Self::FallibleError>;
330
331    /// Reclaims ownership from the payload lock over previously taken payload from the
332    /// currently executing message..
333    ///
334    /// It's supposed, that the implementation of the method calls `PayloadSliceLock::release`.
335    fn unlock_payload(&mut self, payload_holder: &mut PayloadSliceLock) -> UnlockPayloadBound;
336
337    /// Size of currently handled message payload.
338    fn size(&self) -> Result<usize, Self::UnrecoverableError>;
339
340    /// Returns a random seed for the current block with message id as a subject, along with the time in the past since when it was determinable by chain observers.
341    fn random(&self) -> Result<(&[u8], u32), Self::UnrecoverableError>;
342
343    /// Reserve some gas for a few blocks.
344    fn reserve_gas(
345        &mut self,
346        amount: u64,
347        duration: u32,
348    ) -> Result<ReservationId, Self::FallibleError>;
349
350    /// Unreserve gas using reservation ID.
351    fn unreserve_gas(&mut self, id: ReservationId) -> Result<u64, Self::FallibleError>;
352
353    /// Do system reservation.
354    fn system_reserve_gas(&mut self, amount: u64) -> Result<(), Self::FallibleError>;
355
356    /// Tell how much gas is left in running context.
357    fn gas_available(&self) -> Result<u64, Self::UnrecoverableError>;
358
359    /// Value associated with message.
360    fn value(&self) -> Result<u128, Self::UnrecoverableError>;
361
362    /// Tell how much value is left in running context.
363    fn value_available(&self) -> Result<u128, Self::UnrecoverableError>;
364
365    /// Interrupt the program and reschedule execution for maximum.
366    fn wait(&mut self) -> Result<(), Self::UnrecoverableError>;
367
368    /// Interrupt the program and reschedule execution in duration.
369    fn wait_for(&mut self, duration: u32) -> Result<(), Self::UnrecoverableError>;
370
371    /// Interrupt the program and reschedule execution for maximum,
372    /// but not more than duration.
373    fn wait_up_to(&mut self, duration: u32) -> Result<bool, Self::UnrecoverableError>;
374
375    /// Wake the waiting message and move it to the processing queue.
376    fn wake(&mut self, waker_id: MessageId, delay: u32) -> Result<(), Self::FallibleError>;
377
378    /// Send init message to create a new program.
379    fn create_program(
380        &mut self,
381        packet: InitPacket,
382        delay: u32,
383    ) -> Result<(MessageId, ProgramId), Self::FallibleError>;
384
385    /// Create deposit to handle reply on given message.
386    fn reply_deposit(
387        &mut self,
388        message_id: MessageId,
389        amount: u64,
390    ) -> Result<(), Self::FallibleError>;
391
392    /// Return the set of functions that are forbidden to be called.
393    fn forbidden_funcs(&self) -> &BTreeSet<SyscallName>;
394}