gear_core/message/
reply.rs

1// This file is part of Gear.
2
3// Copyright (C) 2022-2025 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use super::common::ReplyDetails;
20use crate::{
21    buffer::Payload,
22    ids::{ActorId, MessageId, prelude::*},
23    message::{
24        Dispatch, DispatchKind, GasLimit, Message, Packet, StoredDispatch, StoredMessage, Value,
25    },
26};
27use gear_core_errors::{ErrorReplyReason, ReplyCode, SuccessReplyReason};
28
29/// Message for Reply entry point.
30/// [`ReplyMessage`] is unique because of storing [`MessageId`] from message on what it replies, and can be the only one per some message execution.
31#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct ReplyMessage {
33    /// Message id.
34    id: MessageId,
35    /// Message payload.
36    payload: Payload,
37    /// Message optional gas limit.
38    gas_limit: Option<GasLimit>,
39    /// Message value.
40    value: Value,
41    /// Reply status code.
42    code: ReplyCode,
43}
44
45impl ReplyMessage {
46    /// Create ReplyMessage from ReplyPacket.
47    pub fn from_packet(id: MessageId, packet: ReplyPacket) -> Self {
48        Self {
49            id,
50            payload: packet.payload,
51            gas_limit: packet.gas_limit,
52            value: packet.value,
53            code: packet.code,
54        }
55    }
56
57    /// Create new system generated ReplyMessage.
58    pub fn system(
59        origin_msg_id: MessageId,
60        payload: Payload,
61        value: Value,
62        err: impl Into<ErrorReplyReason>,
63    ) -> Self {
64        let id = MessageId::generate_reply(origin_msg_id);
65        let packet = ReplyPacket::system(payload, value, err);
66
67        Self::from_packet(id, packet)
68    }
69
70    /// Create new auto-generated ReplyMessage.
71    pub fn auto(origin_msg_id: MessageId) -> Self {
72        let id = MessageId::generate_reply(origin_msg_id);
73        let packet = ReplyPacket::auto();
74
75        Self::from_packet(id, packet)
76    }
77
78    /// Convert ReplyMessage into Message.
79    pub fn into_message(
80        self,
81        program_id: ActorId,
82        destination: ActorId,
83        origin_msg_id: MessageId,
84    ) -> Message {
85        Message::new(
86            self.id,
87            program_id,
88            destination,
89            self.payload,
90            self.gas_limit,
91            self.value,
92            Some(ReplyDetails::new(origin_msg_id, self.code).into()),
93        )
94    }
95
96    /// Convert ReplyMessage into StoredMessage.
97    pub fn into_stored(
98        self,
99        program_id: ActorId,
100        destination: ActorId,
101        origin_msg_id: MessageId,
102    ) -> StoredMessage {
103        self.into_message(program_id, destination, origin_msg_id)
104            .into()
105    }
106
107    /// Convert ReplyMessage into Dispatch.
108    pub fn into_dispatch(
109        self,
110        source: ActorId,
111        destination: ActorId,
112        origin_msg_id: MessageId,
113    ) -> Dispatch {
114        Dispatch::new(
115            DispatchKind::Reply,
116            self.into_message(source, destination, origin_msg_id),
117        )
118    }
119
120    /// Convert ReplyMessage into StoredDispatch.
121    pub fn into_stored_dispatch(
122        self,
123        source: ActorId,
124        destination: ActorId,
125        origin_msg_id: MessageId,
126    ) -> StoredDispatch {
127        self.into_dispatch(source, destination, origin_msg_id)
128            .into()
129    }
130
131    /// Message id.
132    pub fn id(&self) -> MessageId {
133        self.id
134    }
135
136    /// Message payload bytes.
137    pub fn payload_bytes(&self) -> &[u8] {
138        &self.payload
139    }
140
141    /// Message optional gas limit.
142    pub fn gas_limit(&self) -> Option<GasLimit> {
143        self.gas_limit
144    }
145
146    /// Message value.
147    pub fn value(&self) -> Value {
148        self.value
149    }
150
151    /// Reply code of the message.
152    pub fn code(&self) -> ReplyCode {
153        self.code
154    }
155}
156
157/// Reply message packet.
158///
159/// This structure is preparation for future reply message sending. Has no message id.
160#[derive(Clone, Debug, PartialEq, Eq)]
161pub struct ReplyPacket {
162    /// Message payload.
163    payload: Payload,
164    /// Message optional gas limit.
165    gas_limit: Option<GasLimit>,
166    /// Message value.
167    value: Value,
168    /// Reply status code.
169    code: ReplyCode,
170}
171
172#[cfg(test)]
173impl Default for ReplyPacket {
174    fn default() -> Self {
175        Self::auto()
176    }
177}
178
179impl ReplyPacket {
180    /// Create new manual ReplyPacket without gas.
181    pub fn new(payload: Payload, value: Value) -> Self {
182        Self {
183            payload,
184            gas_limit: None,
185            value,
186            code: ReplyCode::Success(SuccessReplyReason::Manual),
187        }
188    }
189
190    /// Create new manual ReplyPacket with gas.
191    pub fn new_with_gas(payload: Payload, gas_limit: GasLimit, value: Value) -> Self {
192        Self {
193            payload,
194            gas_limit: Some(gas_limit),
195            value,
196            code: ReplyCode::Success(SuccessReplyReason::Manual),
197        }
198    }
199
200    /// Create new manual ReplyPacket with optional gas.
201    pub fn maybe_with_gas(payload: Payload, gas_limit: Option<GasLimit>, value: Value) -> Self {
202        match gas_limit {
203            None => Self::new(payload, value),
204            Some(gas_limit) => Self::new_with_gas(payload, gas_limit, value),
205        }
206    }
207
208    // TODO: consider using here `impl CoreError` and/or provide `AsStatusCode`
209    // trait or append such functionality to `CoreError` (issue #1083).
210    /// Create new system generated ReplyPacket.
211    pub fn system(payload: Payload, value: Value, err: impl Into<ErrorReplyReason>) -> Self {
212        Self {
213            payload,
214            gas_limit: None,
215            value,
216            code: ReplyCode::error(err),
217        }
218    }
219
220    /// Auto-generated reply after success execution.
221    pub fn auto() -> Self {
222        Self {
223            payload: Default::default(),
224            gas_limit: Some(0),
225            value: 0,
226            code: ReplyCode::Success(SuccessReplyReason::Auto),
227        }
228    }
229
230    /// Prepend payload.
231    pub(super) fn try_prepend(&mut self, mut data: Payload) -> Result<(), Payload> {
232        if data.try_extend_from_slice(self.payload_bytes()).is_err() {
233            Err(data)
234        } else {
235            self.payload = data;
236            Ok(())
237        }
238    }
239
240    /// Packet status code.
241    pub fn code(&self) -> ReplyCode {
242        self.code
243    }
244}
245
246impl Packet for ReplyPacket {
247    fn payload_bytes(&self) -> &[u8] {
248        &self.payload
249    }
250
251    fn payload_len(&self) -> u32 {
252        self.payload.len_u32()
253    }
254
255    fn gas_limit(&self) -> Option<GasLimit> {
256        self.gas_limit
257    }
258
259    fn value(&self) -> Value {
260        self.value
261    }
262
263    fn kind() -> DispatchKind {
264        DispatchKind::Reply
265    }
266}