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)
2use chrono::{DateTime, Utc};
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, UnixTimestamp,
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    #[cfg_attr(
217        feature = "arbitrary",
218        arbitrary(with = UnixTimestamp::arbitrary_option)
219    )]
220    pub created_at: Option<DateTime<Utc>>,
221}
222
223impl InternalMsgInfo {
224    #[inline]
225    pub const fn transfer(dst: MsgAddress, grams: BigUint, bounce: bool) -> Self {
226        InternalMsgInfo {
227            ihr_disabled: true,
228            bounce,
229            bounced: false,
230            src: MsgAddress::NULL,
231            dst,
232            value: CurrencyCollection {
233                grams,
234                other: ExtraCurrencyCollection(HashmapE::Empty),
235            },
236            ihr_fee: BigUint::ZERO,
237            fwd_fee: BigUint::ZERO,
238            created_lt: 0,
239            created_at: None,
240        }
241    }
242}
243
244impl CellSerialize for InternalMsgInfo {
245    type Args = ();
246
247    fn store(&self, builder: &mut CellBuilder, _: Self::Args) -> Result<(), CellBuilderError> {
248        builder
249            .pack(self.ihr_disabled, ())?
250            .pack(self.bounce, ())?
251            .pack(self.bounced, ())?
252            .pack(self.src, ())?
253            .pack(self.dst, ())?
254            .store(&self.value, ())?
255            .pack_as::<_, &Grams>(&self.ihr_fee, ())?
256            .pack_as::<_, &Grams>(&self.fwd_fee, ())?
257            .pack(self.created_lt, ())?
258            .pack_as::<_, UnixTimestamp>(self.created_at.unwrap_or(DateTime::UNIX_EPOCH), ())?;
259        Ok(())
260    }
261}
262
263impl<'de> CellDeserialize<'de> for InternalMsgInfo {
264    type Args = ();
265
266    fn parse(parser: &mut CellParser<'de>, _: Self::Args) -> Result<Self, CellParserError<'de>> {
267        Ok(Self {
268            ihr_disabled: parser.unpack(())?,
269            bounce: parser.unpack(())?,
270            bounced: parser.unpack(())?,
271            src: parser.unpack(()).context("src")?,
272            dst: parser.unpack(()).context("dst")?,
273            value: parser.parse(()).context("value")?,
274            ihr_fee: parser.unpack_as::<_, Grams>(())?,
275            fwd_fee: parser.unpack_as::<_, Grams>(())?,
276            created_lt: parser.unpack(())?,
277            created_at: Some(parser.unpack_as::<_, UnixTimestamp>(())?)
278                .filter(|dt| *dt != DateTime::UNIX_EPOCH),
279        })
280    }
281}
282
283/// [`ext_in_msg_info$10`](https://docs.ton.org/develop/data-formats/msg-tlb#ext_in_msg_info10)
284/// ```tlb
285/// ext_in_msg_info$10 src:MsgAddressExt dest:MsgAddressInt
286/// import_fee:Grams = CommonMsgInfo;
287/// ```
288#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
289#[derive(Debug, Clone, PartialEq, Eq)]
290pub struct ExternalInMsgInfo {
291    pub src: MsgAddress,
292    pub dst: MsgAddress,
293    pub import_fee: BigUint,
294}
295
296impl BitPack for ExternalInMsgInfo {
297    type Args = ();
298
299    fn pack<W>(&self, writer: &mut W, _: Self::Args) -> Result<(), W::Error>
300    where
301        W: BitWriter + ?Sized,
302    {
303        writer
304            .pack(self.src, ())?
305            .pack(self.dst, ())?
306            .pack_as::<_, &Grams>(&self.import_fee, ())?;
307        Ok(())
308    }
309}
310
311impl<'de> BitUnpack<'de> for ExternalInMsgInfo {
312    type Args = ();
313
314    fn unpack<R>(reader: &mut R, _: Self::Args) -> Result<Self, R::Error>
315    where
316        R: BitReader<'de> + ?Sized,
317    {
318        Ok(Self {
319            src: reader.unpack(())?,
320            dst: reader.unpack(())?,
321            import_fee: reader.unpack_as::<_, Grams>(())?,
322        })
323    }
324}
325
326/// [`ext_out_msg_info$11`](https://docs.ton.org/develop/data-formats/msg-tlb#ext_out_msg_info11)
327/// ```tlb
328/// ext_out_msg_info$11 src:MsgAddressInt dest:MsgAddressExt
329/// created_lt:uint64 created_at:uint32 = CommonMsgInfo;
330/// ```
331#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
332#[derive(Debug, Clone, PartialEq, Eq)]
333pub struct ExternalOutMsgInfo {
334    pub src: MsgAddress,
335    pub dst: MsgAddress,
336    pub created_lt: u64,
337    pub created_at: DateTime<Utc>,
338}
339
340impl BitPack for ExternalOutMsgInfo {
341    type Args = ();
342
343    fn pack<W>(&self, writer: &mut W, _: Self::Args) -> Result<(), W::Error>
344    where
345        W: BitWriter + ?Sized,
346    {
347        writer
348            .pack(self.src, ())?
349            .pack(self.dst, ())?
350            .pack(self.created_lt, ())?
351            .pack_as::<_, UnixTimestamp>(self.created_at, ())?;
352        Ok(())
353    }
354}
355
356impl<'de> BitUnpack<'de> for ExternalOutMsgInfo {
357    type Args = ();
358
359    fn unpack<R>(reader: &mut R, _: Self::Args) -> Result<Self, R::Error>
360    where
361        R: BitReader<'de> + ?Sized,
362    {
363        Ok(Self {
364            src: reader.unpack(())?,
365            dst: reader.unpack(())?,
366            created_lt: reader.unpack(())?,
367            created_at: reader.unpack_as::<_, UnixTimestamp>(())?,
368        })
369    }
370}
371
372#[cfg(test)]
373mod tests {
374    use tlb::ser::CellSerializeExt;
375
376    use super::*;
377
378    #[test]
379    fn message_serde() {
380        let msg = Message::<(), (), ()> {
381            info: CommonMsgInfo::Internal(InternalMsgInfo {
382                ihr_disabled: true,
383                bounce: true,
384                bounced: false,
385                src: MsgAddress::NULL,
386                dst: MsgAddress::NULL,
387                value: Default::default(),
388                ihr_fee: BigUint::ZERO,
389                fwd_fee: BigUint::ZERO,
390                created_lt: 0,
391                created_at: None,
392            }),
393            init: None,
394            body: (),
395        };
396
397        let cell = msg.to_cell(()).unwrap();
398        let got: Message<(), (), ()> = cell.parse_fully(()).unwrap();
399
400        assert_eq!(got, msg);
401    }
402
403    #[test]
404    fn internal_msg_info_serde() {
405        let info = CommonMsgInfo::Internal(InternalMsgInfo {
406            ihr_disabled: true,
407            bounce: true,
408            bounced: false,
409            src: MsgAddress::NULL,
410            dst: MsgAddress::NULL,
411            value: Default::default(),
412            ihr_fee: BigUint::ZERO,
413            fwd_fee: BigUint::ZERO,
414            created_lt: 0,
415            created_at: None,
416        });
417
418        let cell = info.to_cell(()).unwrap();
419        let got: CommonMsgInfo = cell.parse_fully(()).unwrap();
420
421        assert_eq!(got, info);
422    }
423
424    #[test]
425    fn external_in_msg_info_serde() {
426        let info = CommonMsgInfo::ExternalIn(ExternalInMsgInfo {
427            src: MsgAddress::NULL,
428            dst: MsgAddress::NULL,
429            import_fee: BigUint::ZERO,
430        });
431
432        let cell = info.to_cell(()).unwrap();
433        let got: CommonMsgInfo = cell.parse_fully(()).unwrap();
434
435        assert_eq!(got, info);
436    }
437
438    #[test]
439    fn external_out_msg_info_serde() {
440        let info = CommonMsgInfo::ExternalOut(ExternalOutMsgInfo {
441            src: MsgAddress::NULL,
442            dst: MsgAddress::NULL,
443            created_lt: 0,
444            created_at: DateTime::UNIX_EPOCH,
445        });
446
447        let cell = info.to_cell(()).unwrap();
448        let got: CommonMsgInfo = cell.parse_fully(()).unwrap();
449
450        assert_eq!(got, info);
451    }
452}