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