Skip to main content

stellar_xdr/
str.rs

1//# Custom string representations of the following types, also used for JSON
2//# formatting.
3//#
4//# The types that have impls in this file are given to the generator's
5//# --custom-str cli option, so that the generator does not generate
6//# FromStr and Display impls for them.
7//#
8//# ## Strkey Types (SEP-23)
9//# - PublicKey
10//# - AccountId
11//# - MuxedAccount
12//# - MuxedAccountMed25519
13//# - SignerKey
14//# - SignerKeyEd25519SignedPayload
15//# - NodeId
16//#
17//# ## Asset Codes
18//# - AssetCode
19//# - AssetCode4
20//# - AssetCode12
21//#
22//# ## Integers
23//# - Int128Parts
24//# - UInt128Parts
25//# - Int256Parts
26//# - UInt256Parts
27//#
28//# ## Other
29//# - ClaimableBalanceId
30//# - PoolId
31#![cfg(feature = "alloc")]
32
33use super::{
34    AccountId, AssetCode, AssetCode12, AssetCode4, ClaimableBalanceId, ContractId, Error, Hash,
35    Int128Parts, Int256Parts, MuxedAccount, MuxedAccountMed25519, MuxedEd25519Account, NodeId,
36    PoolId, PublicKey, ScAddress, SignerKey, SignerKeyEd25519SignedPayload, UInt128Parts,
37    UInt256Parts, Uint256,
38};
39use crate::num128::{
40    i128_str_from_pieces, i128_str_into_pieces, u128_str_from_pieces, u128_str_into_pieces,
41};
42use crate::num256::{
43    i256_str_from_pieces, i256_str_into_pieces, u256_str_from_pieces, u256_str_into_pieces,
44};
45
46impl From<stellar_strkey::DecodeError> for Error {
47    fn from(_: stellar_strkey::DecodeError) -> Self {
48        Error::Invalid
49    }
50}
51
52impl core::fmt::Display for PublicKey {
53    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
54        match self {
55            PublicKey::PublicKeyTypeEd25519(Uint256(k)) => {
56                let k = stellar_strkey::ed25519::PublicKey::from_payload(k)
57                    .map_err(|_| core::fmt::Error)?;
58                let s = k.to_string();
59                f.write_str(&s)?;
60            }
61        }
62        Ok(())
63    }
64}
65
66impl core::str::FromStr for PublicKey {
67    type Err = Error;
68    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
69        let stellar_strkey::ed25519::PublicKey(k) =
70            stellar_strkey::ed25519::PublicKey::from_str(s)?;
71        Ok(PublicKey::PublicKeyTypeEd25519(Uint256(k)))
72    }
73}
74
75impl core::fmt::Display for AccountId {
76    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
77        self.0.fmt(f)
78    }
79}
80
81impl core::str::FromStr for AccountId {
82    type Err = Error;
83    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
84        Ok(AccountId(PublicKey::from_str(s)?))
85    }
86}
87
88impl core::fmt::Display for ContractId {
89    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
90        let k = stellar_strkey::Contract(self.0 .0);
91        let s = k.to_string();
92        f.write_str(&s)?;
93        Ok(())
94    }
95}
96
97impl core::str::FromStr for ContractId {
98    type Err = Error;
99    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
100        let stellar_strkey::Contract(h) = stellar_strkey::Contract::from_str(s)?;
101        Ok(ContractId(Hash(h)))
102    }
103}
104
105impl core::fmt::Display for PoolId {
106    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
107        let PoolId(Hash(p_id)) = self.clone();
108        let key = stellar_strkey::Strkey::LiquidityPool(stellar_strkey::LiquidityPool(p_id));
109        key.fmt(f)
110    }
111}
112
113impl core::str::FromStr for PoolId {
114    type Err = Error;
115    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
116        let pool_key = stellar_strkey::LiquidityPool::from_str(s)?;
117        Ok(PoolId(Hash(pool_key.0)))
118    }
119}
120
121impl core::fmt::Display for MuxedAccountMed25519 {
122    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
123        let MuxedAccountMed25519 {
124            ed25519: Uint256(ed25519),
125            id,
126        } = self;
127        let k = stellar_strkey::ed25519::MuxedAccount {
128            ed25519: *ed25519,
129            id: *id,
130        };
131        let s = k.to_string();
132        f.write_str(&s)?;
133        Ok(())
134    }
135}
136
137impl core::str::FromStr for MuxedAccountMed25519 {
138    type Err = Error;
139    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
140        let stellar_strkey::ed25519::MuxedAccount { ed25519, id } =
141            stellar_strkey::ed25519::MuxedAccount::from_str(s)?;
142        Ok(MuxedAccountMed25519 {
143            ed25519: Uint256(ed25519),
144            id,
145        })
146    }
147}
148
149impl core::str::FromStr for MuxedAccount {
150    type Err = Error;
151    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
152        let strkey = stellar_strkey::Strkey::from_str(s)?;
153        match strkey {
154            stellar_strkey::Strkey::PublicKeyEd25519(stellar_strkey::ed25519::PublicKey(k)) => {
155                Ok(MuxedAccount::Ed25519(Uint256(k)))
156            }
157            stellar_strkey::Strkey::MuxedAccountEd25519(
158                stellar_strkey::ed25519::MuxedAccount { ed25519, id },
159            ) => Ok(MuxedAccount::MuxedEd25519(MuxedAccountMed25519 {
160                ed25519: Uint256(ed25519),
161                id,
162            })),
163            stellar_strkey::Strkey::PreAuthTx(_)
164            | stellar_strkey::Strkey::HashX(_)
165            | stellar_strkey::Strkey::SignedPayloadEd25519(_)
166            | stellar_strkey::Strkey::Contract(_)
167            | stellar_strkey::Strkey::LiquidityPool(_)
168            | stellar_strkey::Strkey::ClaimableBalance(_)
169            | stellar_strkey::Strkey::MuxedContract(_) => Err(Error::Invalid),
170        }
171    }
172}
173
174impl core::fmt::Display for MuxedAccount {
175    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
176        match self {
177            MuxedAccount::Ed25519(Uint256(k)) => {
178                let k = stellar_strkey::ed25519::PublicKey(*k);
179                let s = k.to_string();
180                f.write_str(&s)?;
181            }
182            MuxedAccount::MuxedEd25519(m) => m.fmt(f)?,
183        }
184        Ok(())
185    }
186}
187
188impl core::fmt::Display for NodeId {
189    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
190        self.0.fmt(f)
191    }
192}
193
194impl core::str::FromStr for NodeId {
195    type Err = Error;
196    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
197        Ok(NodeId(PublicKey::from_str(s)?))
198    }
199}
200
201/// Formats the signed payload as a `P...` strkey.
202///
203/// An empty payload cannot be rendered as a strkey and is formatted as
204/// `<INVALID:G...:EMPTY_PAYLOAD>`, where `G...` is the ed25519 key as a strkey.
205/// The string is not accepted by [`FromStr`][core::str::FromStr]. See the
206/// [XDR-JSON exceptions](crate::_xdrjson).
207impl core::fmt::Display for SignerKeyEd25519SignedPayload {
208    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
209        let SignerKeyEd25519SignedPayload {
210            ed25519: Uint256(ed25519),
211            payload,
212        } = self;
213        // The XDR type caps the payload at 64 bytes, so the only payload that
214        // cannot be rendered as a strkey is an empty one.
215        let Ok(k) = stellar_strkey::ed25519::SignedPayload::new(*ed25519, payload.as_ref()) else {
216            let g = stellar_strkey::ed25519::PublicKey(*ed25519);
217            return write!(f, "<INVALID:{g}:EMPTY_PAYLOAD>");
218        };
219        let s = k.to_string();
220        f.write_str(&s)?;
221        Ok(())
222    }
223}
224
225impl core::str::FromStr for SignerKeyEd25519SignedPayload {
226    type Err = Error;
227    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
228        let sp = stellar_strkey::ed25519::SignedPayload::from_str(s)?;
229        Ok(SignerKeyEd25519SignedPayload {
230            ed25519: Uint256(*sp.ed25519()),
231            payload: sp.payload().try_into()?,
232        })
233    }
234}
235
236impl core::str::FromStr for SignerKey {
237    type Err = Error;
238    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
239        let strkey = stellar_strkey::Strkey::from_str(s)?;
240        match strkey {
241            stellar_strkey::Strkey::PublicKeyEd25519(stellar_strkey::ed25519::PublicKey(k)) => {
242                Ok(SignerKey::Ed25519(Uint256(k)))
243            }
244            stellar_strkey::Strkey::PreAuthTx(stellar_strkey::PreAuthTx(h)) => {
245                Ok(SignerKey::PreAuthTx(Uint256(h)))
246            }
247            stellar_strkey::Strkey::HashX(stellar_strkey::HashX(h)) => {
248                Ok(SignerKey::HashX(Uint256(h)))
249            }
250            stellar_strkey::Strkey::SignedPayloadEd25519(sp) => Ok(
251                SignerKey::Ed25519SignedPayload(SignerKeyEd25519SignedPayload {
252                    ed25519: Uint256(*sp.ed25519()),
253                    payload: sp.payload().try_into()?,
254                }),
255            ),
256            stellar_strkey::Strkey::Contract(_)
257            | stellar_strkey::Strkey::MuxedAccountEd25519(_)
258            | stellar_strkey::Strkey::LiquidityPool(_)
259            | stellar_strkey::Strkey::ClaimableBalance(_)
260            | stellar_strkey::Strkey::MuxedContract(_) => Err(Error::Invalid),
261        }
262    }
263}
264
265/// Formats the signer key as a strkey.
266///
267/// A signed payload with an empty payload cannot be rendered as a strkey and is
268/// formatted as `<INVALID:G...:EMPTY_PAYLOAD>`. See the
269/// [XDR-JSON exceptions](crate::_xdrjson).
270impl core::fmt::Display for SignerKey {
271    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
272        match self {
273            SignerKey::Ed25519(Uint256(k)) => {
274                let k = stellar_strkey::ed25519::PublicKey(*k);
275                let s = k.to_string();
276                f.write_str(&s)?;
277            }
278            SignerKey::PreAuthTx(Uint256(h)) => {
279                let k = stellar_strkey::PreAuthTx(*h);
280                let s = k.to_string();
281                f.write_str(&s)?;
282            }
283            SignerKey::HashX(Uint256(h)) => {
284                let k = stellar_strkey::HashX(*h);
285                let s = k.to_string();
286                f.write_str(&s)?;
287            }
288            SignerKey::Ed25519SignedPayload(p) => p.fmt(f)?,
289        }
290        Ok(())
291    }
292}
293
294impl core::str::FromStr for MuxedEd25519Account {
295    type Err = Error;
296    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
297        let strkey = stellar_strkey::Strkey::from_str(s)?;
298        match strkey {
299            stellar_strkey::Strkey::MuxedAccountEd25519(muxed_ed25519) => Ok(MuxedEd25519Account {
300                id: muxed_ed25519.id,
301                ed25519: Uint256(muxed_ed25519.ed25519),
302            }),
303            _ => Err(Error::Invalid),
304        }
305    }
306}
307
308impl core::fmt::Display for MuxedEd25519Account {
309    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
310        let k =
311            stellar_strkey::Strkey::MuxedAccountEd25519(stellar_strkey::ed25519::MuxedAccount {
312                ed25519: self.ed25519.0,
313                id: self.id,
314            });
315        let s = k.to_string();
316        f.write_str(&s)
317    }
318}
319
320impl core::str::FromStr for crate::MuxedContract {
321    type Err = Error;
322    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
323        let strkey = stellar_strkey::Strkey::from_str(s)?;
324        match strkey {
325            stellar_strkey::Strkey::MuxedContract(muxed_contract) => Ok(crate::MuxedContract {
326                id: muxed_contract.id,
327                contract_id: ContractId(Hash(muxed_contract.contract_id)),
328            }),
329            _ => Err(Error::Invalid),
330        }
331    }
332}
333
334impl core::fmt::Display for crate::MuxedContract {
335    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
336        let k = stellar_strkey::MuxedContract {
337            contract_id: self.contract_id.0 .0,
338            id: self.id,
339        };
340        let s = k.to_string();
341        f.write_str(&s)
342    }
343}
344
345impl core::str::FromStr for ScAddress {
346    type Err = Error;
347    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
348        let strkey = stellar_strkey::Strkey::from_str(s)?;
349        match strkey {
350            stellar_strkey::Strkey::PublicKeyEd25519(stellar_strkey::ed25519::PublicKey(k)) => Ok(
351                ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(k)))),
352            ),
353            stellar_strkey::Strkey::Contract(stellar_strkey::Contract(h)) => {
354                Ok(ScAddress::Contract(ContractId(Hash(h))))
355            }
356            stellar_strkey::Strkey::MuxedAccountEd25519(muxed_ed25519) => {
357                Ok(ScAddress::MuxedAccount(MuxedEd25519Account {
358                    id: muxed_ed25519.id,
359                    ed25519: Uint256(muxed_ed25519.ed25519),
360                }))
361            }
362            stellar_strkey::Strkey::LiquidityPool(liquidity_pool) => {
363                Ok(ScAddress::LiquidityPool(PoolId(Hash(liquidity_pool.0))))
364            }
365            stellar_strkey::Strkey::ClaimableBalance(stellar_strkey::ClaimableBalance::V0(
366                claimable_balance,
367            )) => Ok(ScAddress::ClaimableBalance(
368                ClaimableBalanceId::ClaimableBalanceIdTypeV0(Hash(claimable_balance)),
369            )),
370            stellar_strkey::Strkey::MuxedContract(stellar_strkey::MuxedContract {
371                contract_id,
372                id,
373            }) => Ok(ScAddress::MuxedContract(crate::MuxedContract {
374                id,
375                contract_id: ContractId(Hash(contract_id)),
376            })),
377            stellar_strkey::Strkey::PreAuthTx(_)
378            | stellar_strkey::Strkey::HashX(_)
379            | stellar_strkey::Strkey::SignedPayloadEd25519(_) => Err(Error::Invalid),
380        }
381    }
382}
383
384impl core::fmt::Display for ScAddress {
385    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
386        match self {
387            ScAddress::Account(a) => a.fmt(f),
388            ScAddress::Contract(ContractId(Hash(h))) => {
389                let k = stellar_strkey::Contract(*h);
390                let s = k.to_string();
391                f.write_str(&s)
392            }
393            ScAddress::MuxedAccount(muxed_ed25519_account) => {
394                let k = stellar_strkey::Strkey::MuxedAccountEd25519(
395                    stellar_strkey::ed25519::MuxedAccount {
396                        ed25519: muxed_ed25519_account.ed25519.0,
397                        id: muxed_ed25519_account.id,
398                    },
399                );
400                let s = k.to_string();
401                f.write_str(&s)
402            }
403            ScAddress::ClaimableBalance(claimable_balance_id) => claimable_balance_id.fmt(f),
404            ScAddress::LiquidityPool(pool_id) => pool_id.fmt(f),
405            ScAddress::MuxedContract(muxed_contract) => muxed_contract.fmt(f),
406        }
407    }
408}
409
410impl core::str::FromStr for AssetCode4 {
411    type Err = Error;
412    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
413        let mut code = AssetCode4([0u8; 4]);
414        escape_bytes::unescape_into(&mut code.0, s.as_bytes()).map_err(|_| Error::Invalid)?;
415        Ok(code)
416    }
417}
418
419impl core::fmt::Display for AssetCode4 {
420    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
421        if let Some(last_idx) = self.0.iter().rposition(|c| *c != 0) {
422            for b in escape_bytes::Escape::new(&self.0[..=last_idx]) {
423                write!(f, "{}", b as char)?;
424            }
425        }
426        Ok(())
427    }
428}
429
430impl core::str::FromStr for AssetCode12 {
431    type Err = Error;
432    /// Parses an `AssetCode12`. Requires at least 5 bytes: any code shorter
433    /// than 5 is an `AssetCode4`, and accepting it here would not round-trip (the
434    /// `Display` impl always renders at least 5 characters). For length-agnostic
435    /// parsing that selects the right variant, use `AssetCode`'s `FromStr`.
436    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
437        // An AssetCode12 is always at least 5 characters, because any asset
438        // code shorter than 5 characters is an AssetCode4. This mirrors the
439        // Display impl, which always renders at least 5 characters, and keeps
440        // the XDR<>JSON conversion round-trippable.
441        const MIN_LENGTH: usize = 5;
442        let mut code = AssetCode12([0u8; 12]);
443        let n =
444            escape_bytes::unescape_into(&mut code.0, s.as_bytes()).map_err(|_| Error::Invalid)?;
445        if n < MIN_LENGTH {
446            return Err(Error::Invalid);
447        }
448        Ok(code)
449    }
450}
451
452impl core::fmt::Display for AssetCode12 {
453    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
454        // AssetCode12's are always rendered as at least 5 characters, because
455        // any asset code shorter than 5 characters is an AssetCode4.
456        // AssetCode12 contains a fixed length 12-byte array, and the constant
457        // and slices in this function never operate out-of-bounds because of
458        // that.
459        const MIN_LENGTH: usize = 5;
460        let len = MIN_LENGTH
461            + self
462                .0
463                .iter()
464                .skip(MIN_LENGTH)
465                .rposition(|c| *c != 0)
466                .map_or(0, |last_idx| last_idx + 1);
467        for b in escape_bytes::Escape::new(&self.0[..len]) {
468            write!(f, "{}", b as char)?;
469        }
470        Ok(())
471    }
472}
473
474impl core::str::FromStr for AssetCode {
475    type Err = Error;
476    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
477        let mut code = [0u8; 12];
478        let n = escape_bytes::unescape_into(&mut code, s.as_bytes()).map_err(|_| Error::Invalid)?;
479        if n <= 4 {
480            Ok(AssetCode::CreditAlphanum4(AssetCode4([
481                code[0], code[1], code[2], code[3],
482            ])))
483        } else if n <= 12 {
484            Ok(AssetCode::CreditAlphanum12(AssetCode12(code)))
485        } else {
486            Err(Error::Invalid)
487        }
488    }
489}
490
491impl core::fmt::Display for AssetCode {
492    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
493        match self {
494            AssetCode::CreditAlphanum4(c) => c.fmt(f),
495            AssetCode::CreditAlphanum12(c) => c.fmt(f),
496        }
497    }
498}
499
500impl core::str::FromStr for ClaimableBalanceId {
501    type Err = Error;
502    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
503        let stellar_strkey::ClaimableBalance::V0(cb_id) =
504            stellar_strkey::ClaimableBalance::from_str(s)?;
505        Ok(ClaimableBalanceId::ClaimableBalanceIdTypeV0(Hash(cb_id)))
506    }
507}
508
509impl core::fmt::Display for ClaimableBalanceId {
510    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
511        let ClaimableBalanceId::ClaimableBalanceIdTypeV0(Hash(cb_id)) = self.clone();
512        let key =
513            stellar_strkey::Strkey::ClaimableBalance(stellar_strkey::ClaimableBalance::V0(cb_id));
514        key.fmt(f)
515    }
516}
517
518impl core::fmt::Display for UInt128Parts {
519    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
520        let v = u128_str_from_pieces(self.hi, self.lo);
521        write!(f, "{v}")
522    }
523}
524
525impl core::str::FromStr for UInt128Parts {
526    type Err = Error;
527    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
528        let (hi, lo) = u128_str_into_pieces(s).map_err(|_| Error::Invalid)?;
529        Ok(Self { hi, lo })
530    }
531}
532
533impl core::fmt::Display for Int128Parts {
534    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
535        let v = i128_str_from_pieces(self.hi, self.lo);
536        write!(f, "{v}")
537    }
538}
539
540impl core::str::FromStr for Int128Parts {
541    type Err = Error;
542    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
543        let (hi, lo) = i128_str_into_pieces(s).map_err(|_| Error::Invalid)?;
544        Ok(Self { hi, lo })
545    }
546}
547
548impl core::fmt::Display for UInt256Parts {
549    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
550        let u256 = u256_str_from_pieces(self.hi_hi, self.hi_lo, self.lo_hi, self.lo_lo);
551        write!(f, "{u256}")
552    }
553}
554
555impl core::str::FromStr for UInt256Parts {
556    type Err = Error;
557    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
558        let (hi_hi, hi_lo, lo_hi, lo_lo) = u256_str_into_pieces(s).map_err(|_| Error::Invalid)?;
559        Ok(Self {
560            hi_hi,
561            hi_lo,
562            lo_hi,
563            lo_lo,
564        })
565    }
566}
567
568impl core::fmt::Display for Int256Parts {
569    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
570        let i256 = i256_str_from_pieces(self.hi_hi, self.hi_lo, self.lo_hi, self.lo_lo);
571        write!(f, "{i256}")
572    }
573}
574
575impl core::str::FromStr for Int256Parts {
576    type Err = Error;
577    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
578        let (hi_hi, hi_lo, lo_hi, lo_lo) = i256_str_into_pieces(s).map_err(|_| Error::Invalid)?;
579        Ok(Self {
580            hi_hi,
581            hi_lo,
582            lo_hi,
583            lo_lo,
584        })
585    }
586}