1use 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#[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 #[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 .store(&self.info)?
80 .store_as::<_, &Option<EitherInlineOrRef>>(&self.init)?
82 .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: parser.parse().context("info")?,
98 init: parser
100 .parse_as::<_, Option<EitherInlineOrRef>>()
101 .context("init")?,
102 body: parser.parse_as::<_, EitherInlineOrRef>().context("body")?,
104 })
105 }
106}
107
108#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
110#[derive(Debug, Clone, PartialEq, Eq)]
111pub enum CommonMsgInfo {
112 Internal(InternalMsgInfo),
116
117 ExternalIn(ExternalInMsgInfo),
121
122 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 .pack(false)?
142 .store(msg)?,
143 Self::ExternalIn(msg) => builder
144 .pack_as::<_, NBits<2>>(0b10)?
146 .pack(msg)?,
147 Self::ExternalOut(msg) => builder
148 .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 false => Ok(Self::Internal(parser.parse().context("int_msg_info")?)),
162 true => match parser.unpack()? {
163 false => Ok(Self::ExternalIn(
165 parser.unpack().context("ext_in_msg_info")?,
166 )),
167 true => Ok(Self::ExternalOut(
169 parser.unpack().context("ext_out_msg_info")?,
170 )),
171 },
172 }
173 }
174}
175
176#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
184#[derive(Debug, Clone, PartialEq, Eq)]
185pub struct InternalMsgInfo {
186 pub ihr_disabled: bool,
188 pub bounce: bool,
191 pub bounced: bool,
193 pub src: MsgAddress,
195 pub dst: MsgAddress,
197 pub value: CurrencyCollection,
199 pub ihr_fee: BigUint,
201 pub fwd_fee: BigUint,
203 pub created_lt: u64,
205 #[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#[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#[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}