Skip to main content

tlb_ton/
address.rs

1use core::{
2    fmt::{self, Debug, Display},
3    str::FromStr,
4};
5
6use base64::{
7    Engine, engine::general_purpose::STANDARD_NO_PAD, engine::general_purpose::URL_SAFE_NO_PAD,
8};
9use crc::Crc;
10use digest::{Digest, Output};
11use strum::Display;
12use tlb::{
13    Context, Error, StringError,
14    bits::{
15        NBits, NoArgs, VarLen,
16        bitvec::{order::Msb0, vec::BitVec},
17        de::{BitReader, BitReaderExt, BitUnpack},
18        ser::{BitPack, BitWriter, BitWriterExt},
19    },
20    ser::{CellBuilderError, CellSerialize, CellSerializeExt},
21};
22
23use crate::state_init::StateInit;
24
25const CRC_16_XMODEM: Crc<u16> = Crc::<u16>::new(&crc::CRC_16_XMODEM);
26
27/// [MsgAddress](https://docs.ton.org/develop/data-formats/msg-tlb#msgaddressext-tl-b)
28/// ```tlb
29/// addr_none$00 = MsgAddressExt;
30/// addr_extern$01 len:(## 9) external_address:(bits len) = MsgAddressExt;
31///
32/// addr_std$10 anycast:(Maybe Anycast)
33/// workchain_id:int8 address:bits256  = MsgAddressInt;
34/// addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9)
35/// workchain_id:int32 address:(bits addr_len) = MsgAddressInt;
36///
37/// _ _:MsgAddressInt = MsgAddress;
38/// _ _:MsgAddressExt = MsgAddress;
39/// ```
40#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
41#[cfg_attr(
42    feature = "schemars_1",
43    derive(::schemars_1::JsonSchema),
44    schemars(crate = "::schemars_1", with = "String")
45)]
46#[cfg_attr(
47    feature = "serde",
48    derive(::serde_with::SerializeDisplay, ::serde_with::DeserializeFromStr)
49)]
50#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
51pub struct MsgAddress {
52    #[cfg_attr(
53        feature = "arbitrary",
54        arbitrary(with = |u: &mut ::arbitrary::Unstructured| u.int_in_range(i8::MIN as i32..=i8::MAX as i32))
55    )]
56    pub workchain_id: i32,
57    pub address: [u8; 32],
58}
59
60impl MsgAddress {
61    pub const NULL: Self = Self {
62        workchain_id: 0,
63        address: [0; 32],
64    };
65
66    /// [Derive](https://docs.ton.org/learn/overviews/addresses#address-of-smart-contract)
67    /// [`MsgAddress`] of a smart-contract by its workchain and [`StateInit`]
68    #[cfg(feature = "sha2")]
69    #[inline]
70    pub fn derive<C, D>(
71        workchain_id: i32,
72        state_init: StateInit<C, D>,
73    ) -> Result<Self, CellBuilderError>
74    where
75        C: CellSerialize<Args: NoArgs>,
76        D: CellSerialize<Args: NoArgs>,
77    {
78        Self::derive_digest::<C, D, sha2::Sha256>(workchain_id, state_init)
79    }
80
81    #[inline]
82    pub fn derive_digest<C, D, H>(
83        workchain_id: i32,
84        state_init: StateInit<C, D>,
85    ) -> Result<Self, CellBuilderError>
86    where
87        C: CellSerialize<Args: NoArgs>,
88        D: CellSerialize<Args: NoArgs>,
89        H: Digest,
90        Output<H>: Into<[u8; 32]>,
91    {
92        Ok(Self {
93            workchain_id,
94            address: state_init.to_cell(())?.hash_digest::<H>(),
95        })
96    }
97
98    pub fn from_hex(s: impl AsRef<str>) -> Result<Self, StringError> {
99        let s = s.as_ref();
100        let (workchain, addr) = s
101            .split_once(':')
102            .ok_or_else(|| Error::custom("wrong format"))?;
103        let workchain_id = workchain.parse::<i32>().map_err(Error::custom)?;
104        let mut address = [0; 32];
105        hex::decode_to_slice(addr, &mut address).map_err(Error::custom)?;
106        Ok(Self {
107            workchain_id,
108            address,
109        })
110    }
111
112    /// [Raw Address](https://docs.ton.org/learn/overviews/addresses#raw-address)
113    /// representation
114    #[inline]
115    pub fn to_hex(&self) -> String {
116        format!("{}:{}", self.workchain_id, hex::encode(self.address))
117    }
118
119    /// Shortcut for [`.from_base64_url_flags()?.0`](MsgAddress::from_base64_url_flags)
120    #[inline]
121    pub fn from_base64_url(s: impl AsRef<str>) -> Result<Self, StringError> {
122        Self::from_base64_url_flags(s).map(|(addr, _, _)| addr)
123    }
124
125    /// Parse address from URL-base64
126    /// [user-friendly](https://docs.ton.org/learn/overviews/addresses#user-friendly-address)
127    /// representation and its flags: `(address, non_bouncible, non_production)`
128    #[inline]
129    pub fn from_base64_url_flags(s: impl AsRef<str>) -> Result<(Self, bool, bool), StringError> {
130        Self::from_base64_repr(URL_SAFE_NO_PAD, s)
131    }
132
133    /// Shortcut for [`.from_base64_std_flags()?.0`](MsgAddress::from_base64_std_flags)
134    #[inline]
135    pub fn from_base64_std(s: impl AsRef<str>) -> Result<Self, StringError> {
136        Self::from_base64_std_flags(s).map(|(addr, _, _)| addr)
137    }
138
139    /// Parse address from standard base64
140    /// [user-friendly](https://docs.ton.org/learn/overviews/addresses#user-friendly-address)
141    /// representation and its flags: `(address, non_bouncible, non_production)`
142    #[inline]
143    pub fn from_base64_std_flags(s: impl AsRef<str>) -> Result<(Self, bool, bool), StringError> {
144        Self::from_base64_repr(STANDARD_NO_PAD, s)
145    }
146
147    /// Shortcut for [`.to_base64_url_flags(false, false)`](MsgAddress::to_base64_url_flags)
148    #[inline]
149    pub fn to_base64_url(self) -> String {
150        self.to_base64_url_flags(false, false)
151    }
152
153    /// Encode address as URL base64
154    #[inline]
155    pub fn to_base64_url_flags(self, non_bounceable: bool, non_production: bool) -> String {
156        self.to_base64_flags(non_bounceable, non_production, URL_SAFE_NO_PAD)
157    }
158
159    /// Shortcut for [`.to_base64_std_flags(false, false)`](MsgAddress::to_base64_std_flags)
160    #[inline]
161    pub fn to_base64_std(self) -> String {
162        self.to_base64_std_flags(false, false)
163    }
164
165    /// Encode address as standard base64
166    #[inline]
167    pub fn to_base64_std_flags(self, non_bounceable: bool, non_production: bool) -> String {
168        self.to_base64_flags(non_bounceable, non_production, STANDARD_NO_PAD)
169    }
170
171    /// Parses standard base64 representation of an address
172    ///
173    /// # Returns
174    /// the address, non-bounceable flag, non-production flag.
175    fn from_base64_repr(
176        engine: impl Engine,
177        s: impl AsRef<str>,
178    ) -> Result<(Self, bool, bool), StringError> {
179        let mut bytes = [0; 36];
180        if engine
181            .decode_slice(s.as_ref(), &mut bytes)
182            .map_err(Error::custom)
183            .context("base64")?
184            != bytes.len()
185        {
186            return Err(Error::custom("invalid length"));
187        };
188
189        let (non_production, non_bounceable) = match bytes[0] {
190            0x11 => (false, false),
191            0x51 => (false, true),
192            0x91 => (true, false),
193            0xD1 => (true, true),
194            flags => return Err(Error::custom(format!("unsupported flags: {flags:#x}"))),
195        };
196        let workchain_id = bytes[1] as i8 as i32;
197        let crc = ((bytes[34] as u16) << 8) | bytes[35] as u16;
198        if crc != CRC_16_XMODEM.checksum(&bytes[0..34]) {
199            return Err(Error::custom("CRC mismatch"));
200        }
201        let mut address = [0_u8; 32];
202        address.clone_from_slice(&bytes[2..34]);
203        Ok((
204            Self {
205                workchain_id,
206                address,
207            },
208            non_bounceable,
209            non_production,
210        ))
211    }
212
213    fn to_base64_flags(
214        self,
215        non_bounceable: bool,
216        non_production: bool,
217        engine: impl Engine,
218    ) -> String {
219        let mut bytes = [0; 36];
220        let tag: u8 = match (non_production, non_bounceable) {
221            (false, false) => 0x11,
222            (false, true) => 0x51,
223            (true, false) => 0x91,
224            (true, true) => 0xD1,
225        };
226        bytes[0] = tag;
227        bytes[1] = (self.workchain_id & 0xff) as u8;
228        bytes[2..34].clone_from_slice(&self.address);
229        let crc = CRC_16_XMODEM.checksum(&bytes[0..34]);
230        bytes[34] = ((crc >> 8) & 0xff) as u8;
231        bytes[35] = (crc & 0xff) as u8;
232        engine.encode(bytes)
233    }
234
235    /// Returns whether this address is [`NULL`](MsgAddress::NULL)
236    #[inline]
237    pub fn is_null(&self) -> bool {
238        *self == Self::NULL
239    }
240}
241
242impl Debug for MsgAddress {
243    #[inline]
244    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245        f.write_str(self.to_hex().as_str())
246    }
247}
248
249impl Display for MsgAddress {
250    #[inline]
251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252        f.write_str(self.to_base64_url().as_str())
253    }
254}
255
256impl FromStr for MsgAddress {
257    type Err = StringError;
258
259    fn from_str(s: &str) -> Result<Self, Self::Err> {
260        if s.len() == 48 {
261            if s.contains(['-', '_']) {
262                Self::from_base64_url(s)
263            } else {
264                Self::from_base64_std(s)
265            }
266        } else {
267            Self::from_hex(s)
268        }
269    }
270}
271
272impl BitPack for MsgAddress {
273    type Args = ();
274
275    #[inline]
276    fn pack<W>(&self, writer: &mut W, (): Self::Args) -> Result<(), W::Error>
277    where
278        W: BitWriter + ?Sized,
279    {
280        if self.is_null() {
281            writer.pack(MsgAddressTag::Null, ())?;
282        } else {
283            writer
284                .pack(MsgAddressTag::Std, ())?
285                // anycast:(Maybe Anycast)
286                .pack::<Option<Anycast>>(None, ())?
287                // workchain_id:int8
288                .pack(self.workchain_id as i8, ())?
289                // address:bits256
290                .pack(self.address, ())?;
291        }
292        Ok(())
293    }
294}
295
296impl<'de> BitUnpack<'de> for MsgAddress {
297    type Args = ();
298
299    #[inline]
300    fn unpack<R>(reader: &mut R, (): Self::Args) -> Result<Self, R::Error>
301    where
302        R: BitReader<'de> + ?Sized,
303    {
304        match reader.unpack(())? {
305            MsgAddressTag::Null => Ok(Self::NULL),
306            MsgAddressTag::Std => {
307                // anycast:(Maybe Anycast)
308                let _: Option<Anycast> = reader.unpack(())?;
309                Ok(Self {
310                    // workchain_id:int8
311                    workchain_id: reader.unpack::<i8>(())? as i32,
312                    // address:bits256
313                    address: reader.unpack(())?,
314                })
315            }
316            MsgAddressTag::Var => {
317                // anycast:(Maybe Anycast)
318                let _: Option<Anycast> = reader.unpack(())?;
319                // addr_len:(## 9)
320                let addr_len: u16 = reader.unpack_as::<_, NBits<9>>(())?;
321                if addr_len != 256 {
322                    // TODO
323                    return Err(Error::custom(format!(
324                        "only 256-bit addresses are supported for addr_var$11, got {addr_len} bits"
325                    )));
326                }
327                Ok(Self {
328                    // workchain_id:int32
329                    workchain_id: reader.unpack(())?,
330                    // address:(bits addr_len)
331                    address: reader.unpack(())?,
332                })
333            }
334            tag => Err(Error::custom(format!("unsupported address tag: {tag}"))),
335        }
336    }
337}
338
339#[derive(Clone, Copy, Display)]
340#[repr(u8)]
341enum MsgAddressTag {
342    #[strum(serialize = "addr_none$00")]
343    Null,
344    #[strum(serialize = "addr_extern$01")]
345    Extern,
346    #[strum(serialize = "addr_std$10")]
347    Std,
348    #[strum(serialize = "addr_var$11")]
349    Var,
350}
351
352impl BitPack for MsgAddressTag {
353    type Args = ();
354
355    #[inline]
356    fn pack<W>(&self, writer: &mut W, (): Self::Args) -> Result<(), W::Error>
357    where
358        W: BitWriter + ?Sized,
359    {
360        writer.pack_as::<_, NBits<2>>(*self as u8, ())?;
361        Ok(())
362    }
363}
364
365impl<'de> BitUnpack<'de> for MsgAddressTag {
366    type Args = ();
367
368    #[inline]
369    fn unpack<R>(reader: &mut R, (): Self::Args) -> Result<Self, R::Error>
370    where
371        R: BitReader<'de> + ?Sized,
372    {
373        Ok(match reader.unpack_as::<u8, NBits<2>>(())? {
374            0b00 => Self::Null,
375            0b01 => Self::Extern,
376            0b10 => Self::Std,
377            0b11 => Self::Var,
378            _ => unreachable!(),
379        })
380    }
381}
382
383/// ```tlb
384/// anycast_info$_ depth:(#<= 30) { depth >= 1 } rewrite_pfx:(bits depth) = Anycast;
385/// ```
386pub struct Anycast {
387    pub rewrite_pfx: BitVec<u8, Msb0>,
388}
389
390impl BitPack for Anycast {
391    type Args = ();
392
393    fn pack<W>(&self, writer: &mut W, (): Self::Args) -> Result<(), W::Error>
394    where
395        W: BitWriter + ?Sized,
396    {
397        if self.rewrite_pfx.is_empty() {
398            return Err(Error::custom("depth >= 1"));
399        }
400        writer.pack_as::<_, &VarLen<_, 5>>(&self.rewrite_pfx, ())?;
401        Ok(())
402    }
403}
404
405impl<'de> BitUnpack<'de> for Anycast {
406    type Args = ();
407
408    fn unpack<R>(reader: &mut R, (): Self::Args) -> Result<Self, R::Error>
409    where
410        R: BitReader<'de> + ?Sized,
411    {
412        let rewrite_pfx: BitVec<u8, Msb0> = reader.unpack_as::<_, VarLen<_, 5>>(())?;
413        if rewrite_pfx.is_empty() {
414            return Err(Error::custom("depth >= 1"));
415        }
416        Ok(Self { rewrite_pfx })
417    }
418}
419
420#[cfg(feature = "schemars_0_8")]
421// schemars 0.8 supports #[schemars(with = "...")] attribute only for fields
422const _: () = {
423    use schemars_0_8::{JsonSchema, r#gen::SchemaGenerator, schema::Schema};
424
425    impl JsonSchema for MsgAddress {
426        fn schema_name() -> String {
427            String::schema_name()
428        }
429
430        fn json_schema(generator: &mut SchemaGenerator) -> Schema {
431            String::json_schema(generator)
432        }
433    }
434};
435
436#[cfg(test)]
437mod tests {
438    use super::*;
439
440    #[test]
441    fn parse_address() {
442        let _: MsgAddress = "EQBGXZ9ddZeWypx8EkJieHJX75ct0bpkmu0Y4YoYr3NM0Z9e"
443            .parse()
444            .unwrap();
445    }
446
447    #[cfg(feature = "serde")]
448    #[test]
449    fn serde() {
450        use serde_json::json;
451
452        let _: MsgAddress =
453            serde_json::from_value(json!("EQBGXZ9ddZeWypx8EkJieHJX75ct0bpkmu0Y4YoYr3NM0Z9e"))
454                .unwrap();
455    }
456}