1use std::io::{Read, Write};
3
4use crate::crypto::PublicKey;
5use crate::error::{Error, Result};
6use crate::liquidity_pool::LiquidityPoolId;
7use crate::xdr;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum Asset {
13 Native,
15 Credit(CreditAsset),
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum TrustLineAsset {
23 Native,
24 Credit(CreditAsset),
25 PoolShare(LiquidityPoolId),
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
30pub enum CreditAssetType {
31 CreditAlphaNum4(String),
32 CreditAlphaNum12(String),
33}
34
35#[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 pub fn new_native() -> Asset {
45 Asset::Native
46 }
47
48 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 pub fn is_native(&self) -> bool {
60 matches!(*self, Asset::Native)
61 }
62
63 pub fn as_credit(&self) -> Option<&CreditAsset> {
65 match *self {
66 Asset::Credit(ref credit) => Some(credit),
67 _ => None,
68 }
69 }
70
71 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 pub fn is_credit(&self) -> bool {
81 self.as_credit().is_some()
82 }
83
84 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 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 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 pub fn code(&self) -> &str {
146 match self {
147 CreditAsset::AlphaNum4 { code, issuer: _ } => code,
148 CreditAsset::AlphaNum12 { code, issuer: _ } => code,
149 }
150 }
151
152 pub fn issuer(&self) -> &PublicKey {
154 match self {
155 CreditAsset::AlphaNum4 { code: _, issuer } => issuer,
156 CreditAsset::AlphaNum12 { code: _, issuer } => issuer,
157 }
158 }
159
160 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 pub fn new_native() -> Self {
176 Self::Native
177 }
178
179 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 pub fn new_pool_share(liquidity_pool_id: LiquidityPoolId) -> Result<Self> {
191 Ok(Self::PoolShare(liquidity_pool_id))
192 }
193
194 pub fn is_native(&self) -> bool {
196 matches!(*self, Self::Native)
197 }
198
199 pub fn is_credit(&self) -> bool {
201 self.as_credit().is_some()
202 }
203
204 pub fn as_credit(&self) -> Option<&CreditAsset> {
206 match *self {
207 Self::Credit(ref credit) => Some(credit),
208 _ => None,
209 }
210 }
211
212 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 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 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
327pub(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}