Skip to main content

tlb_ton/
message.rs

1//! Collection of typs related to [Message](https://docs.ton.org/develop/data-formats/msg-tlb#message-tl-b)
2
3use num_bigint::BigUint;
4use tlb::{
5    Cell, Context, EitherInlineOrRef,
6    bits::{
7        NBits, NoArgs,
8        de::{BitReader, BitReaderExt, BitUnpack},
9        ser::{BitPack, BitWriter, BitWriterExt},
10    },
11    de::{CellDeserialize, CellParser, CellParserError},
12    hashmap::HashmapE,
13    ser::{CellBuilder, CellBuilderError, CellSerialize, CellSerializeExt},
14};
15
16use crate::{
17    MsgAddress,
18    currency::{CurrencyCollection, ExtraCurrencyCollection, Grams},
19    state_init::StateInit,
20};
21
22/// [Message](https://docs.ton.org/develop/data-formats/msg-tlb#message-tl-b)
23/// ```tlb
24/// message$_ {X:Type} info:CommonMsgInfo
25/// init:(Maybe (Either StateInit ^StateInit))
26/// body:(Either X ^X) = Message X;
27/// ```
28#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct Message<T = Cell, IC = Cell, ID = Cell> {
31    pub info: CommonMsgInfo,
32    pub init: Option<StateInit<IC, ID>>,
33    pub body: T,
34}
35
36impl<T, IC, ID> Message<T, IC, ID>
37where
38    T: CellSerialize<Args: NoArgs>,
39    IC: CellSerialize<Args: NoArgs>,
40    ID: CellSerialize<Args: NoArgs>,
41{
42    #[inline]
43    pub fn with_state_init(mut self, state_init: impl Into<Option<StateInit<IC, ID>>>) -> Self {
44        self.init = state_init.into();
45        self
46    }
47
48    #[inline]
49    pub fn normalize(&self) -> Result<Message, CellBuilderError> {
50        Ok(Message {
51            info: self.info.clone(),
52            init: self.init.as_ref().map(StateInit::normalize).transpose()?,
53            body: self.body.to_cell(NoArgs::EMPTY)?,
54        })
55    }
56}
57
58impl Message<()> {
59    /// Simple native transfer message
60    #[inline]
61    pub const fn transfer(dst: MsgAddress, grams: BigUint, bounce: bool) -> Self {
62        Self {
63            info: CommonMsgInfo::transfer(dst, grams, bounce),
64            init: None,
65            body: (),
66        }
67    }
68}
69
70impl<T, IC, ID> CellSerialize for Message<T, IC, ID>
71where
72    T: CellSerialize<Args: NoArgs>,
73    IC: CellSerialize<Args: NoArgs>,
74    ID: CellSerialize<Args: NoArgs>,
75{
76    type Args = ();
77
78    fn store(&self, builder: &mut CellBuilder, (): Self::Args) -> Result<(), CellBuilderError> {
79        builder
80            // info:CommonMsgInfo
81            .store(&self.info, ())?
82            // init:(Maybe (Either StateInit ^StateInit))
83            .store_as::<_, &Option<EitherInlineOrRef>>(&self.init, ())?
84            // body:(Either X ^X)
85            .store_as::<_, EitherInlineOrRef>(&self.body, NoArgs::EMPTY)?;
86        Ok(())
87    }
88}
89
90impl<'de, T, IC, ID> CellDeserialize<'de> for Message<T, IC, ID>
91where
92    T: CellDeserialize<'de, Args: NoArgs>,
93    IC: CellDeserialize<'de, Args: NoArgs>,
94    ID: CellDeserialize<'de, Args: NoArgs>,
95{
96    type Args = ();
97
98    fn parse(parser: &mut CellParser<'de>, (): Self::Args) -> Result<Self, CellParserError<'de>> {
99        Ok(Self {
100            // info:CommonMsgInfo
101            info: parser.parse(()).context("info")?,
102            // init:(Maybe (Either StateInit ^StateInit))
103            init: parser
104                .parse_as::<_, Option<EitherInlineOrRef>>(())
105                .context("init")?,
106            // body:(Either X ^X)
107            body: parser
108                .parse_as::<_, EitherInlineOrRef>(NoArgs::EMPTY)
109                .context("body")?,
110        })
111    }
112}
113
114/// `info` field for [`Message`]
115#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
116#[derive(Debug, Clone, PartialEq, Eq)]
117pub enum CommonMsgInfo {
118    /// ```tlb
119    /// int_msg_info$0
120    /// ```
121    Internal(InternalMsgInfo),
122
123    /// ```tlb
124    /// ext_in_msg_info$10
125    /// ```
126    ExternalIn(ExternalInMsgInfo),
127
128    /// ```tlb
129    /// ext_out_msg_info$11
130    /// ```
131    ExternalOut(ExternalOutMsgInfo),
132}
133
134impl CommonMsgInfo {
135    #[inline]
136    pub const fn transfer(dst: MsgAddress, grams: BigUint, bounce: bool) -> Self {
137        Self::Internal(InternalMsgInfo::transfer(dst, grams, bounce))
138    }
139}
140
141impl CellSerialize for CommonMsgInfo {
142    type Args = ();
143
144    #[inline]
145    fn store(&self, builder: &mut CellBuilder, (): Self::Args) -> Result<(), CellBuilderError> {
146        match self {
147            Self::Internal(msg) => builder
148                // int_msg_info$0
149                .pack(false, ())?
150                .store(msg, ())?,
151            Self::ExternalIn(msg) => builder
152                // ext_in_msg_info$10
153                .pack_as::<_, NBits<2>>(0b10, ())?
154                .pack(msg, ())?,
155            Self::ExternalOut(msg) => builder
156                // ext_out_msg_info$11
157                .pack_as::<_, NBits<2>>(0b11, ())?
158                .pack(msg, ())?,
159        };
160        Ok(())
161    }
162}
163
164impl<'de> CellDeserialize<'de> for CommonMsgInfo {
165    type Args = ();
166
167    #[inline]
168    fn parse(parser: &mut CellParser<'de>, (): Self::Args) -> Result<Self, CellParserError<'de>> {
169        match parser.unpack(())? {
170            // int_msg_info$0
171            false => Ok(Self::Internal(parser.parse(()).context("int_msg_info")?)),
172            true => match parser.unpack(())? {
173                // ext_in_msg_info$10
174                false => Ok(Self::ExternalIn(
175                    parser.unpack(()).context("ext_in_msg_info")?,
176                )),
177                // ext_out_msg_info$11
178                true => Ok(Self::ExternalOut(
179                    parser.unpack(()).context("ext_out_msg_info")?,
180                )),
181            },
182        }
183    }
184}
185
186/// [`int_msg_info$0`](https://docs.ton.org/develop/data-formats/msg-tlb#int_msg_info0)
187/// ```tlb
188/// int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
189/// src:MsgAddressInt dest:MsgAddressInt
190/// value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams
191/// created_lt:uint64 created_at:uint32 = CommonMsgInfo;
192/// ```
193#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
194#[derive(Debug, Clone, PartialEq, Eq)]
195pub struct InternalMsgInfo {
196    /// Hyper cube routing flag.
197    pub ihr_disabled: bool,
198    /// Message should be bounced if there are errors during processing.
199    /// If message's flat bounce = 1, it calls bounceable.
200    pub bounce: bool,
201    /// Flag that describes, that message itself is a result of bounce.
202    pub bounced: bool,
203    /// Address of smart contract sender of message.
204    pub src: MsgAddress,
205    /// Address of smart contract destination of message.
206    pub dst: MsgAddress,
207    /// Structure which describes currency information including total funds transferred in message.
208    pub value: CurrencyCollection,
209    /// Fees for hyper routing delivery
210    pub ihr_fee: BigUint,
211    /// Fees for forwarding messages assigned by validators
212    pub fwd_fee: BigUint,
213    /// Logic time of sending message assigned by validator. Using for odering actions in smart contract.
214    pub created_lt: u64,
215    /// Unix time
216    pub created_at: u32,
217}
218
219impl InternalMsgInfo {
220    #[inline]
221    pub const fn transfer(dst: MsgAddress, grams: BigUint, bounce: bool) -> Self {
222        Self {
223            ihr_disabled: true,
224            bounce,
225            bounced: false,
226            src: MsgAddress::NULL,
227            dst,
228            value: CurrencyCollection {
229                grams,
230                other: ExtraCurrencyCollection(HashmapE::Empty),
231            },
232            ihr_fee: BigUint::ZERO,
233            fwd_fee: BigUint::ZERO,
234            created_lt: 0,
235            created_at: 0,
236        }
237    }
238}
239
240impl CellSerialize for InternalMsgInfo {
241    type Args = ();
242
243    fn store(&self, builder: &mut CellBuilder, (): Self::Args) -> Result<(), CellBuilderError> {
244        builder
245            .pack(self.ihr_disabled, ())?
246            .pack(self.bounce, ())?
247            .pack(self.bounced, ())?
248            .pack(self.src, ())?
249            .pack(self.dst, ())?
250            .store(&self.value, ())?
251            .pack_as::<_, &Grams>(&self.ihr_fee, ())?
252            .pack_as::<_, &Grams>(&self.fwd_fee, ())?
253            .pack(self.created_lt, ())?
254            .pack(self.created_at, ())?;
255        Ok(())
256    }
257}
258
259impl<'de> CellDeserialize<'de> for InternalMsgInfo {
260    type Args = ();
261
262    fn parse(parser: &mut CellParser<'de>, (): Self::Args) -> Result<Self, CellParserError<'de>> {
263        Ok(Self {
264            ihr_disabled: parser.unpack(())?,
265            bounce: parser.unpack(())?,
266            bounced: parser.unpack(())?,
267            src: parser.unpack(()).context("src")?,
268            dst: parser.unpack(()).context("dst")?,
269            value: parser.parse(()).context("value")?,
270            ihr_fee: parser.unpack_as::<_, Grams>(())?,
271            fwd_fee: parser.unpack_as::<_, Grams>(())?,
272            created_lt: parser.unpack(())?,
273            created_at: parser.unpack(())?,
274        })
275    }
276}
277
278/// [`ext_in_msg_info$10`](https://docs.ton.org/develop/data-formats/msg-tlb#ext_in_msg_info10)
279/// ```tlb
280/// ext_in_msg_info$10 src:MsgAddressExt dest:MsgAddressInt
281/// import_fee:Grams = CommonMsgInfo;
282/// ```
283#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
284#[derive(Debug, Clone, PartialEq, Eq)]
285pub struct ExternalInMsgInfo {
286    pub src: MsgAddress,
287    pub dst: MsgAddress,
288    pub import_fee: BigUint,
289}
290
291impl BitPack for ExternalInMsgInfo {
292    type Args = ();
293
294    fn pack<W>(&self, writer: &mut W, (): Self::Args) -> Result<(), W::Error>
295    where
296        W: BitWriter + ?Sized,
297    {
298        writer
299            .pack(self.src, ())?
300            .pack(self.dst, ())?
301            .pack_as::<_, &Grams>(&self.import_fee, ())?;
302        Ok(())
303    }
304}
305
306impl<'de> BitUnpack<'de> for ExternalInMsgInfo {
307    type Args = ();
308
309    fn unpack<R>(reader: &mut R, (): Self::Args) -> Result<Self, R::Error>
310    where
311        R: BitReader<'de> + ?Sized,
312    {
313        Ok(Self {
314            src: reader.unpack(())?,
315            dst: reader.unpack(())?,
316            import_fee: reader.unpack_as::<_, Grams>(())?,
317        })
318    }
319}
320
321/// [`ext_out_msg_info$11`](https://docs.ton.org/develop/data-formats/msg-tlb#ext_out_msg_info11)
322/// ```tlb
323/// ext_out_msg_info$11 src:MsgAddressInt dest:MsgAddressExt
324/// created_lt:uint64 created_at:uint32 = CommonMsgInfo;
325/// ```
326#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
327#[derive(Debug, Clone, PartialEq, Eq)]
328pub struct ExternalOutMsgInfo {
329    pub src: MsgAddress,
330    pub dst: MsgAddress,
331    pub created_lt: u64,
332    pub created_at: u32,
333}
334
335impl BitPack for ExternalOutMsgInfo {
336    type Args = ();
337
338    fn pack<W>(&self, writer: &mut W, (): Self::Args) -> Result<(), W::Error>
339    where
340        W: BitWriter + ?Sized,
341    {
342        writer
343            .pack(self.src, ())?
344            .pack(self.dst, ())?
345            .pack(self.created_lt, ())?
346            .pack(self.created_at, ())?;
347        Ok(())
348    }
349}
350
351impl<'de> BitUnpack<'de> for ExternalOutMsgInfo {
352    type Args = ();
353
354    fn unpack<R>(reader: &mut R, (): Self::Args) -> Result<Self, R::Error>
355    where
356        R: BitReader<'de> + ?Sized,
357    {
358        Ok(Self {
359            src: reader.unpack(())?,
360            dst: reader.unpack(())?,
361            created_lt: reader.unpack(())?,
362            created_at: reader.unpack(())?,
363        })
364    }
365}
366
367#[cfg(test)]
368mod tests {
369    use tlb::ser::CellSerializeExt;
370
371    use super::*;
372
373    #[test]
374    fn message_serde() {
375        let msg = Message::<(), (), ()> {
376            info: CommonMsgInfo::Internal(InternalMsgInfo {
377                ihr_disabled: true,
378                bounce: true,
379                bounced: false,
380                src: MsgAddress::NULL,
381                dst: MsgAddress::NULL,
382                value: Default::default(),
383                ihr_fee: BigUint::ZERO,
384                fwd_fee: BigUint::ZERO,
385                created_lt: 0,
386                created_at: 0,
387            }),
388            init: None,
389            body: (),
390        };
391
392        let cell = msg.to_cell(()).unwrap();
393        let got: Message<(), (), ()> = cell.parse_fully(()).unwrap();
394
395        assert_eq!(got, msg);
396    }
397
398    #[test]
399    fn internal_msg_info_serde() {
400        let info = CommonMsgInfo::Internal(InternalMsgInfo {
401            ihr_disabled: true,
402            bounce: true,
403            bounced: false,
404            src: MsgAddress::NULL,
405            dst: MsgAddress::NULL,
406            value: Default::default(),
407            ihr_fee: BigUint::ZERO,
408            fwd_fee: BigUint::ZERO,
409            created_lt: 0,
410            created_at: 0,
411        });
412
413        let cell = info.to_cell(()).unwrap();
414        let got: CommonMsgInfo = cell.parse_fully(()).unwrap();
415
416        assert_eq!(got, info);
417    }
418
419    #[test]
420    fn external_in_msg_info_serde() {
421        let info = CommonMsgInfo::ExternalIn(ExternalInMsgInfo {
422            src: MsgAddress::NULL,
423            dst: MsgAddress::NULL,
424            import_fee: BigUint::ZERO,
425        });
426
427        let cell = info.to_cell(()).unwrap();
428        let got: CommonMsgInfo = cell.parse_fully(()).unwrap();
429
430        assert_eq!(got, info);
431    }
432
433    #[test]
434    fn external_out_msg_info_serde() {
435        let info = CommonMsgInfo::ExternalOut(ExternalOutMsgInfo {
436            src: MsgAddress::NULL,
437            dst: MsgAddress::NULL,
438            created_lt: 0,
439            created_at: 0,
440        });
441
442        let cell = info.to_cell(()).unwrap();
443        let got: CommonMsgInfo = cell.parse_fully(()).unwrap();
444
445        assert_eq!(got, info);
446    }
447}