stellar_base/
asset.rs

1//! Assets on the network.
2use std::io::{Read, Write};
3
4use crate::crypto::PublicKey;
5use crate::error::{Error, Result};
6use crate::liquidity_pool::LiquidityPoolId;
7use crate::xdr;
8
9/// Represent an asset, either the native asset (XLM) or an asset
10/// issued.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum Asset {
13    /// The native asset (XLM).
14    Native,
15    /// A non-native asset, identified by asset code/issuer id.
16    Credit(CreditAsset),
17}
18
19/// Represent an asset associated with a trustline, either a regular asset or a liquidity pool's
20/// shares
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum TrustLineAsset {
23    Native,
24    Credit(CreditAsset),
25    PoolShare(LiquidityPoolId),
26}
27
28/// The credit asset type, based on its code length.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub enum CreditAssetType {
31    CreditAlphaNum4(String),
32    CreditAlphaNum12(String),
33}
34
35/// A non-native asset, identified by asset code/issuer id.
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub enum CreditAsset {
38    AlphaNum4 { code: String, issuer: PublicKey },
39    AlphaNum12 { code: String, issuer: PublicKey },
40}
41
42impl Asset {
43    /// Create the native asset: Lumens.
44    pub fn new_native() -> Asset {
45        Asset::Native
46    }
47
48    /// Create the asset with `code` issued by `issuer`.
49    pub fn new_credit<S>(code: S, issuer: PublicKey) -> Result<Asset>
50    where
51        S: Into<String>,
52    {
53        let code = code.into();
54        let inner = CreditAsset::new(code, issuer)?;
55        Ok(Asset::Credit(inner))
56    }
57
58    /// Returns true if the asset is a Native. Returns false otherwise.
59    pub fn is_native(&self) -> bool {
60        matches!(*self, Asset::Native)
61    }
62
63    /// If the asset is a Credit, returns its value. Returns None otherwise
64    pub fn as_credit(&self) -> Option<&CreditAsset> {
65        match *self {
66            Asset::Credit(ref credit) => Some(credit),
67            _ => None,
68        }
69    }
70
71    /// If the asset is a Credit, returns its mutable value. Returns None otherwise
72    pub fn as_credit_mut(&mut self) -> Option<&mut CreditAsset> {
73        match *self {
74            Asset::Credit(ref mut credit) => Some(credit),
75            _ => None,
76        }
77    }
78
79    /// Returns true if the asset is a Credit. Returns false otherwise.
80    pub fn is_credit(&self) -> bool {
81        self.as_credit().is_some()
82    }
83
84    /// Returns the asset xdr object.
85    pub fn to_xdr(&self) -> Result<xdr::Asset> {
86        match self {
87            Asset::Native => Ok(xdr::Asset::Native),
88            Asset::Credit(credit) => match credit {
89                CreditAsset::AlphaNum4 { code, issuer } => {
90                    let code_len = code.len();
91                    let mut code_bytes = [0u8; 4];
92                    code_bytes[..code_len].copy_from_slice(code.as_bytes());
93                    let asset_code = xdr::AssetCode4(code_bytes);
94                    let issuer = issuer.to_xdr_account_id()?;
95                    let asset_alphanum4 = xdr::AlphaNum4 { asset_code, issuer };
96                    Ok(xdr::Asset::CreditAlphanum4(asset_alphanum4))
97                }
98                CreditAsset::AlphaNum12 { code, issuer } => {
99                    let code_len = code.len();
100                    let mut code_bytes = [0u8; 12];
101                    code_bytes[..code_len].copy_from_slice(code.as_bytes());
102                    let asset_code = xdr::AssetCode12(code_bytes);
103                    let issuer = issuer.to_xdr_account_id()?;
104                    let asset_alphanum12 = xdr::AlphaNum12 { asset_code, issuer };
105                    Ok(xdr::Asset::CreditAlphanum12(asset_alphanum12))
106                }
107            },
108        }
109    }
110
111    /// Creates an asset from the xdr object.
112    pub fn from_xdr(x: &xdr::Asset) -> Result<Asset> {
113        match x {
114            xdr::Asset::Native => Ok(Asset::new_native()),
115            xdr::Asset::CreditAlphanum4(credit) => {
116                let issuer = PublicKey::from_xdr_account_id(&credit.issuer)?;
117                let code = xdr_code_to_string(&credit.asset_code.0);
118                Asset::new_credit(code, issuer)
119            }
120            xdr::Asset::CreditAlphanum12(credit) => {
121                let issuer = PublicKey::from_xdr_account_id(&credit.issuer)?;
122                let code = xdr_code_to_string(&credit.asset_code.0);
123                Asset::new_credit(code, issuer)
124            }
125        }
126    }
127}
128
129impl CreditAsset {
130    /// Creates new credit asset with `code` and `issuer`.
131    ///
132    /// Code must be shorter than 12 characters.
133    pub fn new(code: String, issuer: PublicKey) -> Result<CreditAsset> {
134        let code_len = code.len();
135        if (1..=4).contains(&code_len) {
136            Ok(CreditAsset::AlphaNum4 { code, issuer })
137        } else if (5..=12).contains(&code_len) {
138            Ok(CreditAsset::AlphaNum12 { code, issuer })
139        } else {
140            Err(Error::InvalidAssetCode)
141        }
142    }
143
144    /// Returns the asset code.
145    pub fn code(&self) -> &str {
146        match self {
147            CreditAsset::AlphaNum4 { code, issuer: _ } => code,
148            CreditAsset::AlphaNum12 { code, issuer: _ } => code,
149        }
150    }
151
152    /// Returns the asset issuer.
153    pub fn issuer(&self) -> &PublicKey {
154        match self {
155            CreditAsset::AlphaNum4 { code: _, issuer } => issuer,
156            CreditAsset::AlphaNum12 { code: _, issuer } => issuer,
157        }
158    }
159
160    /// Returns the credit asset type.
161    pub fn asset_type(&self) -> CreditAssetType {
162        match self {
163            CreditAsset::AlphaNum4 { code, issuer: _ } => {
164                CreditAssetType::CreditAlphaNum4(code.clone())
165            }
166            CreditAsset::AlphaNum12 { code, issuer: _ } => {
167                CreditAssetType::CreditAlphaNum12(code.clone())
168            }
169        }
170    }
171}
172
173impl TrustLineAsset {
174    /// Create the native asset: Lumens.
175    pub fn new_native() -> Self {
176        Self::Native
177    }
178
179    /// Create the asset with `code` issued by `issuer`.
180    pub fn new_credit<S>(code: S, issuer: PublicKey) -> Result<Self>
181    where
182        S: Into<String>,
183    {
184        let code = code.into();
185        let inner = CreditAsset::new(code, issuer)?;
186        Ok(Self::Credit(inner))
187    }
188
189    /// Create a trustline asset for a liquidity pool shares.
190    pub fn new_pool_share(liquidity_pool_id: LiquidityPoolId) -> Result<Self> {
191        Ok(Self::PoolShare(liquidity_pool_id))
192    }
193
194    /// Returns true if the asset is a Native. Returns false otherwise.
195    pub fn is_native(&self) -> bool {
196        matches!(*self, Self::Native)
197    }
198
199    /// Returns true if the asset is a Credit. Returns false otherwise.
200    pub fn is_credit(&self) -> bool {
201        self.as_credit().is_some()
202    }
203
204    /// If the asset is a Credit, returns its value. Returns None otherwise
205    pub fn as_credit(&self) -> Option<&CreditAsset> {
206        match *self {
207            Self::Credit(ref credit) => Some(credit),
208            _ => None,
209        }
210    }
211
212    /// If the asset is a Credit, returns its mutable value. Returns None otherwise
213    pub fn as_credit_mut(&mut self) -> Option<&mut CreditAsset> {
214        match *self {
215            Self::Credit(ref mut credit) => Some(credit),
216            _ => None,
217        }
218    }
219
220    pub fn is_pool_share(&self) -> bool {
221        self.as_pool_share().is_some()
222    }
223
224    pub fn as_pool_share(&self) -> Option<&LiquidityPoolId> {
225        match *self {
226            Self::PoolShare(ref pool_id) => Some(pool_id),
227            _ => None,
228        }
229    }
230
231    pub fn as_pool_share_mut(&mut self) -> Option<&mut LiquidityPoolId> {
232        match *self {
233            Self::PoolShare(ref mut pool_id) => Some(pool_id),
234            _ => None,
235        }
236    }
237
238    /// Returns the trustline asset xdr object.
239    pub fn to_xdr(&self) -> Result<xdr::TrustLineAsset> {
240        match self {
241            Self::Native => Ok(xdr::TrustLineAsset::Native),
242            Self::Credit(credit) => match credit {
243                CreditAsset::AlphaNum4 { code, issuer } => {
244                    let code_len = code.len();
245                    let mut code_bytes = [0u8; 4];
246                    code_bytes[..code_len].copy_from_slice(code.as_bytes());
247                    let asset_code = xdr::AssetCode4(code_bytes);
248                    let issuer = issuer.to_xdr_account_id()?;
249                    let asset_alphanum4 = xdr::AlphaNum4 { asset_code, issuer };
250                    Ok(xdr::TrustLineAsset::CreditAlphanum4(asset_alphanum4))
251                }
252                CreditAsset::AlphaNum12 { code, issuer } => {
253                    let code_len = code.len();
254                    let mut code_bytes = [0u8; 12];
255                    code_bytes[..code_len].copy_from_slice(code.as_bytes());
256                    let asset_code = xdr::AssetCode12(code_bytes);
257                    let issuer = issuer.to_xdr_account_id()?;
258                    let asset_alphanum12 = xdr::AlphaNum12 { asset_code, issuer };
259                    Ok(xdr::TrustLineAsset::CreditAlphanum12(asset_alphanum12))
260                }
261            },
262            Self::PoolShare(pool_id) => Ok(xdr::TrustLineAsset::PoolShare(pool_id.to_xdr())),
263        }
264    }
265
266    /// Creates an asset from the xdr object.
267    pub fn from_xdr(x: &xdr::TrustLineAsset) -> Result<Self> {
268        match x {
269            xdr::TrustLineAsset::Native => Ok(Self::new_native()),
270            xdr::TrustLineAsset::CreditAlphanum4(credit) => {
271                let issuer = PublicKey::from_xdr_account_id(&credit.issuer)?;
272                let code = xdr_code_to_string(&credit.asset_code.0);
273                Ok(Self::new_credit(code, issuer)?)
274            }
275            xdr::TrustLineAsset::CreditAlphanum12(credit) => {
276                let issuer = PublicKey::from_xdr_account_id(&credit.issuer)?;
277                let code = xdr_code_to_string(&credit.asset_code.0);
278                Ok(Self::new_credit(code, issuer)?)
279            }
280            xdr::TrustLineAsset::PoolShare(pool_id) => {
281                let liquidity_pool_id = LiquidityPoolId::from_xdr(pool_id)?;
282                Ok(Self::new_pool_share(liquidity_pool_id)?)
283            }
284        }
285    }
286}
287
288impl From<Asset> for TrustLineAsset {
289    fn from(asset: Asset) -> Self {
290        match asset {
291            Asset::Native => TrustLineAsset::new_native(),
292            Asset::Credit(credit) => {
293                TrustLineAsset::new_credit(credit.code(), *credit.issuer()).unwrap()
294            }
295        }
296    }
297}
298
299impl xdr::WriteXdr for TrustLineAsset {
300    fn write_xdr<W: Write>(&self, w: &mut xdr::Limited<W>) -> xdr::Result<()> {
301        let xdr_asset = self.to_xdr().map_err(|_| xdr::Error::Invalid)?;
302        xdr_asset.write_xdr(w)
303    }
304}
305
306impl xdr::ReadXdr for TrustLineAsset {
307    fn read_xdr<R: Read>(r: &mut xdr::Limited<R>) -> xdr::Result<Self> {
308        let xdr_result = xdr::TrustLineAsset::read_xdr(r)?;
309        Self::from_xdr(&xdr_result).map_err(|_| xdr::Error::Invalid)
310    }
311}
312
313impl xdr::WriteXdr for Asset {
314    fn write_xdr<W: Write>(&self, w: &mut xdr::Limited<W>) -> xdr::Result<()> {
315        let xdr_asset = self.to_xdr().map_err(|_| xdr::Error::Invalid)?;
316        xdr_asset.write_xdr(w)
317    }
318}
319
320impl xdr::ReadXdr for Asset {
321    fn read_xdr<R: Read>(r: &mut xdr::Limited<R>) -> xdr::Result<Self> {
322        let xdr_result = xdr::Asset::read_xdr(r)?;
323        Self::from_xdr(&xdr_result).map_err(|_| xdr::Error::Invalid)
324    }
325}
326
327/// Create new String from asset code. Make sure not to copy zero bytes.
328pub(crate) fn xdr_code_to_string(x: &[u8]) -> String {
329    let mut pos = 0;
330    for b in x {
331        if *b == 0 {
332            break;
333        }
334        pos += 1;
335    }
336    String::from_utf8_lossy(&x[..pos]).into_owned()
337}
338
339#[cfg(test)]
340mod tests {
341    use super::{Asset, CreditAsset};
342    use crate::crypto::PublicKey;
343    use crate::xdr::{XDRDeserialize, XDRSerialize};
344
345    #[test]
346    fn test_error_code_too_long() {
347        let code = "1234567890123".to_string();
348        let pk =
349            PublicKey::from_account_id("GCZHXL5HXQX5ABDM26LHYRCQZ5OJFHLOPLZX47WEBP3V2PF5AVFK2A5D")
350                .unwrap();
351        let asset = CreditAsset::new(code, pk);
352        assert!(asset.is_err());
353    }
354
355    #[test]
356    fn test_asset_native_xdr_ser() {
357        let native = Asset::new_native();
358        let xdr = native.xdr_base64().unwrap();
359        assert_eq!("AAAAAA==", xdr);
360    }
361
362    #[test]
363    fn test_asset_alphanum4_xdr_ser() {
364        let code = "RUST".to_string();
365        let pk =
366            PublicKey::from_account_id("GCZHXL5HXQX5ABDM26LHYRCQZ5OJFHLOPLZX47WEBP3V2PF5AVFK2A5D")
367                .unwrap();
368        let asset = Asset::new_credit(code, pk).unwrap();
369        let xdr = asset.xdr_base64().unwrap();
370        assert_eq!(
371            "AAAAAVJVU1QAAAAAsnuvp7wv0ARs15Z8RFDPXJKdbnrzfn7EC/ddPL0FSq0=",
372            xdr
373        );
374    }
375
376    #[test]
377    fn test_asset_alphanum12_xdr_ser() {
378        let code = "RUSTRUSTRUST".to_string();
379        let pk =
380            PublicKey::from_account_id("GCZHXL5HXQX5ABDM26LHYRCQZ5OJFHLOPLZX47WEBP3V2PF5AVFK2A5D")
381                .unwrap();
382        let asset = Asset::new_credit(code, pk).unwrap();
383        let xdr = asset.xdr_base64().unwrap();
384        assert_eq!(
385            "AAAAAlJVU1RSVVNUUlVTVAAAAACye6+nvC/QBGzXlnxEUM9ckp1uevN+fsQL9108vQVKrQ==",
386            xdr
387        );
388    }
389
390    #[test]
391    fn test_asset_native_xdr_de() {
392        let expected = Asset::new_native();
393        let asset = Asset::from_xdr_base64("AAAAAA==").unwrap();
394        assert_eq!(expected, asset);
395    }
396
397    #[test]
398    fn test_asset_alphanum4_xdr_de() {
399        let code = "RUST".to_string();
400        let pk =
401            PublicKey::from_account_id("GCZHXL5HXQX5ABDM26LHYRCQZ5OJFHLOPLZX47WEBP3V2PF5AVFK2A5D")
402                .unwrap();
403        let expected = Asset::new_credit(code, pk).unwrap();
404        let asset =
405            Asset::from_xdr_base64("AAAAAVJVU1QAAAAAsnuvp7wv0ARs15Z8RFDPXJKdbnrzfn7EC/ddPL0FSq0=")
406                .unwrap();
407        assert_eq!(expected, asset);
408    }
409
410    #[test]
411    fn test_asset_alphanum12_xdr_de() {
412        let code = "RUSTRUSTRUST".to_string();
413        let pk =
414            PublicKey::from_account_id("GCZHXL5HXQX5ABDM26LHYRCQZ5OJFHLOPLZX47WEBP3V2PF5AVFK2A5D")
415                .unwrap();
416        let expected = Asset::new_credit(code, pk).unwrap();
417        let asset = Asset::from_xdr_base64(
418            "AAAAAlJVU1RSVVNUUlVTVAAAAACye6+nvC/QBGzXlnxEUM9ckp1uevN+fsQL9108vQVKrQ==",
419        )
420        .unwrap();
421        assert_eq!(expected, asset);
422    }
423}