use chrono::{DateTime, Utc};
use num_bigint::BigUint;
use tlb::{
Cell, Context,
r#as::{DefaultOnNone, EitherInlineOrRef, hashmap::HashmapE},
bits::{
r#as::NBits,
de::{BitReader, BitReaderExt, BitUnpack},
ser::{BitPack, BitWriter, BitWriterExt},
},
de::{CellDeserialize, CellParser, CellParserError},
ser::{CellBuilder, CellBuilderError, CellSerialize, CellSerializeExt},
};
use crate::{
MsgAddress, UnixTimestamp,
currency::{CurrencyCollection, ExtraCurrencyCollection, Grams},
state_init::StateInit,
};
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Message<T = Cell, IC = Cell, ID = Cell> {
pub info: CommonMsgInfo,
pub init: Option<StateInit<IC, ID>>,
pub body: T,
}
impl<T, IC, ID> Message<T, IC, ID>
where
T: CellSerialize,
IC: CellSerialize,
ID: CellSerialize,
{
#[inline]
pub fn with_state_init(mut self, state_init: impl Into<Option<StateInit<IC, ID>>>) -> Self {
self.init = state_init.into();
self
}
#[inline]
pub fn normalize(&self) -> Result<Message, CellBuilderError> {
Ok(Message {
info: self.info.clone(),
init: self.init.as_ref().map(StateInit::normalize).transpose()?,
body: self.body.to_cell()?,
})
}
}
impl Message<()> {
#[inline]
pub const fn transfer(dst: MsgAddress, grams: BigUint, bounce: bool) -> Self {
Self {
info: CommonMsgInfo::transfer(dst, grams, bounce),
init: None,
body: (),
}
}
}
impl<T, IC, ID> CellSerialize for Message<T, IC, ID>
where
T: CellSerialize,
IC: CellSerialize,
ID: CellSerialize,
{
fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> {
builder
.store(&self.info)?
.store_as::<_, &Option<EitherInlineOrRef>>(&self.init)?
.store_as::<_, EitherInlineOrRef>(&self.body)?;
Ok(())
}
}
impl<'de, T, IC, ID> CellDeserialize<'de> for Message<T, IC, ID>
where
T: CellDeserialize<'de>,
IC: CellDeserialize<'de>,
ID: CellDeserialize<'de>,
{
fn parse(parser: &mut CellParser<'de>) -> Result<Self, CellParserError<'de>> {
Ok(Self {
info: parser.parse().context("info")?,
init: parser
.parse_as::<_, Option<EitherInlineOrRef>>()
.context("init")?,
body: parser.parse_as::<_, EitherInlineOrRef>().context("body")?,
})
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CommonMsgInfo {
Internal(InternalMsgInfo),
ExternalIn(ExternalInMsgInfo),
ExternalOut(ExternalOutMsgInfo),
}
impl CommonMsgInfo {
#[inline]
pub const fn transfer(dst: MsgAddress, grams: BigUint, bounce: bool) -> Self {
Self::Internal(InternalMsgInfo::transfer(dst, grams, bounce))
}
}
impl CellSerialize for CommonMsgInfo {
#[inline]
fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> {
match self {
Self::Internal(msg) => builder
.pack(false)?
.store(msg)?,
Self::ExternalIn(msg) => builder
.pack_as::<_, NBits<2>>(0b10)?
.pack(msg)?,
Self::ExternalOut(msg) => builder
.pack_as::<_, NBits<2>>(0b11)?
.pack(msg)?,
};
Ok(())
}
}
impl<'de> CellDeserialize<'de> for CommonMsgInfo {
#[inline]
fn parse(parser: &mut CellParser<'de>) -> Result<Self, CellParserError<'de>> {
match parser.unpack()? {
false => Ok(Self::Internal(parser.parse().context("int_msg_info")?)),
true => match parser.unpack()? {
false => Ok(Self::ExternalIn(
parser.unpack().context("ext_in_msg_info")?,
)),
true => Ok(Self::ExternalOut(
parser.unpack().context("ext_out_msg_info")?,
)),
},
}
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InternalMsgInfo {
pub ihr_disabled: bool,
pub bounce: bool,
pub bounced: bool,
pub src: MsgAddress,
pub dst: MsgAddress,
pub value: CurrencyCollection,
pub ihr_fee: BigUint,
pub fwd_fee: BigUint,
pub created_lt: u64,
pub created_at: Option<DateTime<Utc>>,
}
impl InternalMsgInfo {
#[inline]
pub const fn transfer(dst: MsgAddress, grams: BigUint, bounce: bool) -> Self {
InternalMsgInfo {
ihr_disabled: true,
bounce,
bounced: false,
src: MsgAddress::NULL,
dst,
value: CurrencyCollection {
grams,
other: ExtraCurrencyCollection(HashmapE::Empty),
},
ihr_fee: BigUint::ZERO,
fwd_fee: BigUint::ZERO,
created_lt: 0,
created_at: None,
}
}
}
impl CellSerialize for InternalMsgInfo {
fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> {
builder
.pack(self.ihr_disabled)?
.pack(self.bounce)?
.pack(self.bounced)?
.pack(self.src)?
.pack(self.dst)?
.store(&self.value)?
.pack_as::<_, &Grams>(&self.ihr_fee)?
.pack_as::<_, &Grams>(&self.fwd_fee)?
.pack(self.created_lt)?
.pack_as::<_, DefaultOnNone<UnixTimestamp>>(self.created_at)?;
Ok(())
}
}
impl<'de> CellDeserialize<'de> for InternalMsgInfo {
fn parse(parser: &mut CellParser<'de>) -> Result<Self, CellParserError<'de>> {
Ok(Self {
ihr_disabled: parser.unpack()?,
bounce: parser.unpack()?,
bounced: parser.unpack()?,
src: parser.unpack().context("src")?,
dst: parser.unpack().context("dst")?,
value: parser.parse().context("value")?,
ihr_fee: parser.unpack_as::<_, Grams>()?,
fwd_fee: parser.unpack_as::<_, Grams>()?,
created_lt: parser.unpack()?,
created_at: Some(parser.unpack_as::<_, UnixTimestamp>()?)
.filter(|dt| *dt != DateTime::UNIX_EPOCH),
})
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExternalInMsgInfo {
pub src: MsgAddress,
pub dst: MsgAddress,
pub import_fee: BigUint,
}
impl BitPack for ExternalInMsgInfo {
fn pack<W>(&self, mut writer: W) -> Result<(), W::Error>
where
W: BitWriter,
{
writer
.pack(self.src)?
.pack(self.dst)?
.pack_as::<_, &Grams>(&self.import_fee)?;
Ok(())
}
}
impl BitUnpack for ExternalInMsgInfo {
fn unpack<R>(mut reader: R) -> Result<Self, R::Error>
where
R: BitReader,
{
Ok(Self {
src: reader.unpack()?,
dst: reader.unpack()?,
import_fee: reader.unpack_as::<_, Grams>()?,
})
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExternalOutMsgInfo {
pub src: MsgAddress,
pub dst: MsgAddress,
pub created_lt: u64,
pub created_at: DateTime<Utc>,
}
impl BitPack for ExternalOutMsgInfo {
fn pack<W>(&self, mut writer: W) -> Result<(), W::Error>
where
W: BitWriter,
{
writer
.pack(self.src)?
.pack(self.dst)?
.pack(self.created_lt)?
.pack_as::<_, UnixTimestamp>(self.created_at)?;
Ok(())
}
}
impl BitUnpack for ExternalOutMsgInfo {
fn unpack<R>(mut reader: R) -> Result<Self, R::Error>
where
R: BitReader,
{
Ok(Self {
src: reader.unpack()?,
dst: reader.unpack()?,
created_lt: reader.unpack()?,
created_at: reader.unpack_as::<_, UnixTimestamp>()?,
})
}
}
#[cfg(test)]
mod tests {
use tlb::ser::CellSerializeExt;
use super::*;
#[test]
fn message_serde() {
let msg = Message::<(), (), ()> {
info: CommonMsgInfo::Internal(InternalMsgInfo {
ihr_disabled: true,
bounce: true,
bounced: false,
src: MsgAddress::NULL,
dst: MsgAddress::NULL,
value: Default::default(),
ihr_fee: BigUint::ZERO,
fwd_fee: BigUint::ZERO,
created_lt: 0,
created_at: None,
}),
init: None,
body: (),
};
let cell = msg.to_cell().unwrap();
let got: Message<(), (), ()> = cell.parse_fully().unwrap();
assert_eq!(got, msg);
}
#[test]
fn internal_msg_info_serde() {
let info = CommonMsgInfo::Internal(InternalMsgInfo {
ihr_disabled: true,
bounce: true,
bounced: false,
src: MsgAddress::NULL,
dst: MsgAddress::NULL,
value: Default::default(),
ihr_fee: BigUint::ZERO,
fwd_fee: BigUint::ZERO,
created_lt: 0,
created_at: None,
});
let cell = info.to_cell().unwrap();
let got: CommonMsgInfo = cell.parse_fully().unwrap();
assert_eq!(got, info);
}
#[test]
fn external_in_msg_info_serde() {
let info = CommonMsgInfo::ExternalIn(ExternalInMsgInfo {
src: MsgAddress::NULL,
dst: MsgAddress::NULL,
import_fee: BigUint::ZERO,
});
let cell = info.to_cell().unwrap();
let got: CommonMsgInfo = cell.parse_fully().unwrap();
assert_eq!(got, info);
}
#[test]
fn external_out_msg_info_serde() {
let info = CommonMsgInfo::ExternalOut(ExternalOutMsgInfo {
src: MsgAddress::NULL,
dst: MsgAddress::NULL,
created_lt: 0,
created_at: DateTime::UNIX_EPOCH,
});
let cell = info.to_cell().unwrap();
let got: CommonMsgInfo = cell.parse_fully().unwrap();
assert_eq!(got, info);
}
}