stellar_base/operations/
change_trust.rs1use 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 pub fn source_account(&self) -> &Option<MuxedAccount> {
34 &self.source_account
35 }
36
37 pub fn source_account_mut(&mut self) -> &mut Option<MuxedAccount> {
39 &mut self.source_account
40 }
41
42 pub fn asset(&self) -> &ChangeTrustAsset {
44 &self.asset
45 }
46
47 pub fn asset_mut(&mut self) -> &mut ChangeTrustAsset {
49 &mut self.asset
50 }
51
52 pub fn limit(&self) -> &Option<Stroops> {
54 &self.limit
55 }
56
57 pub fn limit_mut(&mut self) -> &mut Option<Stroops> {
59 &mut self.limit
60 }
61
62 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 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 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}