stellar_base/operations/
change_trust.rs

1use crate::amount::Stroops;
2use crate::asset::{xdr_code_to_string, CreditAsset};
3use crate::crypto::MuxedAccount;
4use crate::error::{Error, Result};
5use crate::liquidity_pool::LiquidityPoolParameters;
6use crate::operations::Operation;
7use crate::{xdr, Asset, PublicKey};
8use std::convert::TryInto;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct ChangeTrustOperation {
12    source_account: Option<MuxedAccount>,
13    asset: ChangeTrustAsset,
14    limit: Option<Stroops>,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum ChangeTrustAsset {
19    Native,
20    Credit(CreditAsset),
21    PoolShare(LiquidityPoolParameters),
22}
23
24#[derive(Debug, Default)]
25pub struct ChangeTrustOperationBuilder {
26    source_account: Option<MuxedAccount>,
27    asset: Option<ChangeTrustAsset>,
28    limit: Option<Stroops>,
29}
30
31impl ChangeTrustOperation {
32    /// Retrieves the operation source account.
33    pub fn source_account(&self) -> &Option<MuxedAccount> {
34        &self.source_account
35    }
36
37    /// Retrieves a reference to the operation source account.
38    pub fn source_account_mut(&mut self) -> &mut Option<MuxedAccount> {
39        &mut self.source_account
40    }
41
42    /// Retrieves the operation asset.
43    pub fn asset(&self) -> &ChangeTrustAsset {
44        &self.asset
45    }
46
47    /// Retrieves a mutable reference the operation asset.
48    pub fn asset_mut(&mut self) -> &mut ChangeTrustAsset {
49        &mut self.asset
50    }
51
52    /// Retrieves the operation limit.
53    pub fn limit(&self) -> &Option<Stroops> {
54        &self.limit
55    }
56
57    /// Retrieves a mutable reference to the operation limit.
58    pub fn limit_mut(&mut self) -> &mut Option<Stroops> {
59        &mut self.limit
60    }
61
62    /// Returns the xdr operation body.
63    pub fn to_xdr_operation_body(&self) -> Result<xdr::OperationBody> {
64        let line = self.asset.to_xdr()?;
65        let limit = match &self.limit {
66            None => 0,
67            Some(limit) => limit.to_xdr_int64()?,
68        };
69        let inner = xdr::ChangeTrustOp { line, limit };
70        Ok(xdr::OperationBody::ChangeTrust(inner))
71    }
72
73    /// Creates from the xdr operation body.
74    pub fn from_xdr_operation_body(
75        source_account: Option<MuxedAccount>,
76        x: &xdr::ChangeTrustOp,
77    ) -> Result<ChangeTrustOperation> {
78        let asset = ChangeTrustAsset::from_xdr(&x.line)?;
79        // Don't check if limit is positive because the library sure
80        // has no control over the xdr.
81        let limit = match &x.limit {
82            0 => None,
83            n => Some(Stroops::new(*n)),
84        };
85        Ok(ChangeTrustOperation {
86            source_account,
87            asset,
88            limit,
89        })
90    }
91}
92
93impl ChangeTrustAsset {
94    pub fn new_native() -> Result<Self> {
95        Ok(Self::Native)
96    }
97
98    pub fn new_credit<S>(code: S, issuer: PublicKey) -> Result<Self>
99    where
100        S: Into<String>,
101    {
102        let code = code.into();
103        let inner = CreditAsset::new(code, issuer)?;
104        Ok(Self::Credit(inner))
105    }
106
107    pub fn new_pool_share(pool_params: LiquidityPoolParameters) -> Result<Self> {
108        Ok(Self::PoolShare(pool_params))
109    }
110
111    pub fn to_xdr(&self) -> Result<xdr::ChangeTrustAsset> {
112        match self {
113            Self::Native => Ok(xdr::ChangeTrustAsset::Native),
114            Self::Credit(ref credit) => match credit {
115                CreditAsset::AlphaNum4 { code, issuer } => {
116                    let code_len = code.len();
117                    let mut code_bytes = [0u8; 4];
118                    code_bytes[..code_len].copy_from_slice(code.as_bytes());
119                    let asset_code = xdr::AssetCode4(code_bytes);
120                    let issuer = issuer.to_xdr_account_id()?;
121                    let asset_alphanum4 = xdr::AlphaNum4 { asset_code, issuer };
122                    Ok(xdr::ChangeTrustAsset::CreditAlphanum4(asset_alphanum4))
123                }
124                CreditAsset::AlphaNum12 { code, issuer } => {
125                    let code_len = code.len();
126                    let mut code_bytes = [0u8; 12];
127                    code_bytes[..code_len].copy_from_slice(code.as_bytes());
128                    let asset_code = xdr::AssetCode12(code_bytes);
129                    let issuer = issuer.to_xdr_account_id()?;
130                    let asset_alphanum12 = xdr::AlphaNum12 { asset_code, issuer };
131                    Ok(xdr::ChangeTrustAsset::CreditAlphanum12(asset_alphanum12))
132                }
133            },
134            Self::PoolShare(ref pool_params) => {
135                let inner_xdr = pool_params.to_xdr()?;
136                Ok(xdr::ChangeTrustAsset::PoolShare(inner_xdr))
137            }
138        }
139    }
140
141    pub fn from_xdr(x: &xdr::ChangeTrustAsset) -> Result<Self> {
142        match *x {
143            xdr::ChangeTrustAsset::Native => Self::new_native(),
144            xdr::ChangeTrustAsset::CreditAlphanum4(ref credit) => {
145                let issuer = PublicKey::from_xdr_account_id(&credit.issuer)?;
146                let code = xdr_code_to_string(&credit.asset_code.0);
147                Self::new_credit(code, issuer)
148            }
149            xdr::ChangeTrustAsset::CreditAlphanum12(ref credit) => {
150                let issuer = PublicKey::from_xdr_account_id(&credit.issuer)?;
151                let code = xdr_code_to_string(&credit.asset_code.0);
152                Self::new_credit(code, issuer)
153            }
154            xdr::ChangeTrustAsset::PoolShare(ref pool_params_xdr) => {
155                let pool_params = LiquidityPoolParameters::from_xdr(pool_params_xdr)?;
156                Self::new_pool_share(pool_params)
157            }
158        }
159    }
160}
161
162impl From<Asset> for ChangeTrustAsset {
163    fn from(asset: Asset) -> Self {
164        match asset {
165            Asset::Native => ChangeTrustAsset::new_native().unwrap(),
166            Asset::Credit(credit) => {
167                ChangeTrustAsset::new_credit(credit.code(), *credit.issuer()).unwrap()
168            }
169        }
170    }
171}
172
173impl ChangeTrustOperationBuilder {
174    pub fn new() -> ChangeTrustOperationBuilder {
175        Default::default()
176    }
177
178    pub fn with_source_account<S>(mut self, source: S) -> ChangeTrustOperationBuilder
179    where
180        S: Into<MuxedAccount>,
181    {
182        self.source_account = Some(source.into());
183        self
184    }
185
186    pub fn with_asset(mut self, asset: ChangeTrustAsset) -> ChangeTrustOperationBuilder {
187        self.asset = Some(asset);
188        self
189    }
190
191    pub fn with_limit<A: TryInto<Stroops>>(
192        mut self,
193        limit: Option<A>,
194    ) -> Result<ChangeTrustOperationBuilder> {
195        self.limit = limit
196            .map(|l| l.try_into())
197            .transpose()
198            .map_err(|_| Error::InvalidStroopsAmount)?;
199        Ok(self)
200    }
201
202    pub fn build(self) -> Result<Operation> {
203        let asset = self
204            .asset
205            .ok_or_else(|| Error::InvalidOperation("missing change trust asset".to_string()))?;
206
207        if let Some(limit) = &self.limit {
208            if limit.to_i64() < 0 {
209                return Err(Error::InvalidOperation(
210                    "change trust limit must not be negative".to_string(),
211                ));
212            }
213        }
214
215        Ok(Operation::ChangeTrust(ChangeTrustOperation {
216            source_account: self.source_account,
217            asset,
218            limit: self.limit,
219        }))
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use crate::amount::Stroops;
226
227    use crate::network::Network;
228    use crate::operations::change_trust::ChangeTrustAsset;
229    use crate::operations::tests::*;
230    use crate::operations::Operation;
231    use crate::transaction::{Transaction, TransactionEnvelope, MIN_BASE_FEE};
232    use crate::xdr::{XDRDeserialize, XDRSerialize};
233
234    #[test]
235    fn test_change_trust() {
236        let kp = keypair0();
237        let kp1 = keypair1();
238
239        let asset = ChangeTrustAsset::new_credit("FOOBAR", kp1.public_key()).unwrap();
240
241        let op = Operation::new_change_trust()
242            .with_asset(asset)
243            .build()
244            .unwrap();
245        let mut tx = Transaction::builder(kp.public_key(), 3556091187167235, MIN_BASE_FEE)
246            .add_operation(op)
247            .into_transaction()
248            .unwrap();
249        tx.sign(kp.as_ref(), &Network::new_test()).unwrap();
250        let envelope = tx.to_envelope();
251        let xdr = envelope.xdr_base64().unwrap();
252        let expected = "AAAAAgAAAADg3G3hclysZlFitS+s5zWyiiJD5B0STWy5LXCj6i5yxQAAAGQADKI/AAAAAwAAAAAAAAAAAAAAAQAAAAAAAAAGAAAAAkZPT0JBUgAAAAAAAAAAAAAlyvHaD8duz+iEXkJUUbsHkklIlH46oMrMMYrt0odkfgAAAAAAAAAAAAAAAAAAAAHqLnLFAAAAQItMBQHM0xqmxrCWDEUHHLm8RZZAamXvBVCFovprlCnzAwGOCgo/HrZlKhZL5LMJtoUAgTtb9eQL+Ur7H8zKDAk=";
253        assert_eq!(expected, xdr);
254        let back = TransactionEnvelope::from_xdr_base64(&xdr).unwrap();
255        assert_eq!(envelope, back);
256    }
257
258    #[test]
259    fn test_change_trust_with_limit() {
260        let kp = keypair0();
261        let kp1 = keypair1();
262
263        let asset = ChangeTrustAsset::new_credit("FOOBAR", kp1.public_key()).unwrap();
264
265        let op = Operation::new_change_trust()
266            .with_asset(asset)
267            .with_limit(Some(Stroops::max()))
268            .unwrap()
269            .build()
270            .unwrap();
271        let mut tx = Transaction::builder(kp.public_key(), 3556091187167235, MIN_BASE_FEE)
272            .add_operation(op)
273            .into_transaction()
274            .unwrap();
275        tx.sign(kp.as_ref(), &Network::new_test()).unwrap();
276        let envelope = tx.to_envelope();
277        let xdr = envelope.xdr_base64().unwrap();
278        let expected = "AAAAAgAAAADg3G3hclysZlFitS+s5zWyiiJD5B0STWy5LXCj6i5yxQAAAGQADKI/AAAAAwAAAAAAAAAAAAAAAQAAAAAAAAAGAAAAAkZPT0JBUgAAAAAAAAAAAAAlyvHaD8duz+iEXkJUUbsHkklIlH46oMrMMYrt0odkfn//////////AAAAAAAAAAHqLnLFAAAAQBGXSIMx1RSjmS7XD9DluNCn6TolNnB9sdmvBSlWeaizwgfud6hD8BZSfqBHdTNm4DgmloojC9fIVRtVFEHhpAE=";
279        assert_eq!(expected, xdr);
280        let back = TransactionEnvelope::from_xdr_base64(&xdr).unwrap();
281        assert_eq!(envelope, back);
282    }
283
284    #[test]
285    fn test_change_trust_with_source_account() {
286        let kp = keypair0();
287        let kp1 = keypair1();
288        let kp2 = keypair2();
289
290        let asset = ChangeTrustAsset::new_credit("FOOBAR", kp1.public_key()).unwrap();
291
292        let op = Operation::new_change_trust()
293            .with_source_account(kp2.public_key())
294            .with_asset(asset)
295            .with_limit(Some(Stroops::max()))
296            .unwrap()
297            .build()
298            .unwrap();
299        let mut tx = Transaction::builder(kp.public_key(), 3556091187167235, MIN_BASE_FEE)
300            .add_operation(op)
301            .into_transaction()
302            .unwrap();
303        tx.sign(kp.as_ref(), &Network::new_test()).unwrap();
304        let envelope = tx.to_envelope();
305        let xdr = envelope.xdr_base64().unwrap();
306        let expected = "AAAAAgAAAADg3G3hclysZlFitS+s5zWyiiJD5B0STWy5LXCj6i5yxQAAAGQADKI/AAAAAwAAAAAAAAAAAAAAAQAAAAEAAAAAfhHLNNY19eGrAtSgLD3VpaRm2AjNjxIBWQg9zS4VWZgAAAAGAAAAAkZPT0JBUgAAAAAAAAAAAAAlyvHaD8duz+iEXkJUUbsHkklIlH46oMrMMYrt0odkfn//////////AAAAAAAAAAHqLnLFAAAAQOLqKZ6HQ5VeJvRyWk9FBA5z6UVoxKPXAODzj7c0sOr6tGYzbAtHW1ahOPdIOI03J4lGjM21ROvgi3ClSfZVUAc=";
307        assert_eq!(expected, xdr);
308        let back = TransactionEnvelope::from_xdr_base64(&xdr).unwrap();
309        assert_eq!(envelope, back);
310    }
311}