Skip to main content

xrpl/models/transactions/
vault_deposit.rs

1use alloc::borrow::Cow;
2use alloc::vec::Vec;
3use serde::{Deserialize, Serialize};
4use serde_with::skip_serializing_none;
5
6use crate::models::amount::XRPAmount;
7use crate::models::{Amount, FlagCollection, Model, NoFlags, ValidateCurrencies, XRPLModelResult};
8
9use super::vault_common::{validate_positive_amount, validate_vault_id};
10use super::{CommonFields, CommonTransactionBuilder, Memo, Signer, Transaction, TransactionType};
11
12/// Deposit assets into a vault on the XRP Ledger (XLS-65).
13///
14/// The depositor receives share tokens (MPTokens) proportional to their
15/// deposit relative to the vault's total assets.
16///
17/// See VaultDeposit transaction:
18/// `<https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0065d-single-asset-vault>`
19#[skip_serializing_none]
20#[derive(
21    Debug,
22    Default,
23    Serialize,
24    Deserialize,
25    PartialEq,
26    Eq,
27    Clone,
28    xrpl_rust_macros::ValidateCurrencies,
29)]
30#[serde(rename_all = "PascalCase")]
31pub struct VaultDeposit<'a> {
32    /// The base fields for all transaction models.
33    ///
34    /// See Transaction Common Fields:
35    /// `<https://xrpl.org/transaction-common-fields.html>`
36    #[serde(flatten)]
37    pub common_fields: CommonFields<'a, NoFlags>,
38    /// The ID of the vault to deposit into (256-bit hex string).
39    #[serde(rename = "VaultID")]
40    pub vault_id: Cow<'a, str>,
41    /// The amount of the asset to deposit into the vault.
42    pub amount: Amount<'a>,
43}
44
45impl Model for VaultDeposit<'_> {
46    fn get_errors(&self) -> XRPLModelResult<()> {
47        self.validate_currencies()?;
48        validate_vault_id(&self.vault_id)?;
49        validate_positive_amount("amount", &self.amount)
50    }
51}
52
53impl<'a> Transaction<'a, NoFlags> for VaultDeposit<'a> {
54    fn get_common_fields(&self) -> &CommonFields<'_, NoFlags> {
55        &self.common_fields
56    }
57
58    fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> {
59        &mut self.common_fields
60    }
61
62    fn get_transaction_type(&self) -> &TransactionType {
63        self.common_fields.get_transaction_type()
64    }
65}
66
67impl<'a> CommonTransactionBuilder<'a, NoFlags> for VaultDeposit<'a> {
68    fn get_mut_common_fields(&mut self) -> &mut CommonFields<'a, NoFlags> {
69        &mut self.common_fields
70    }
71
72    fn into_self(self) -> Self {
73        self
74    }
75}
76
77impl<'a> VaultDeposit<'a> {
78    pub fn new(
79        account: Cow<'a, str>,
80        account_txn_id: Option<Cow<'a, str>>,
81        fee: Option<XRPAmount<'a>>,
82        last_ledger_sequence: Option<u32>,
83        memos: Option<Vec<Memo>>,
84        sequence: Option<u32>,
85        signers: Option<Vec<Signer>>,
86        source_tag: Option<u32>,
87        ticket_sequence: Option<u32>,
88        vault_id: Cow<'a, str>,
89        amount: Amount<'a>,
90    ) -> VaultDeposit<'a> {
91        VaultDeposit {
92            common_fields: CommonFields::new(
93                account,
94                TransactionType::VaultDeposit,
95                account_txn_id,
96                fee,
97                Some(FlagCollection::default()),
98                last_ledger_sequence,
99                memos,
100                None,
101                sequence,
102                signers,
103                None,
104                source_tag,
105                ticket_sequence,
106                None,
107            ),
108            vault_id,
109            amount,
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use crate::models::{IssuedCurrencyAmount, XRPAmount};
118
119    const VAULT_ID: &str = "A0000000000000000000000000000000000000000000000000000000DEADBEEF";
120
121    #[test]
122    fn test_serde() {
123        let vault_deposit = VaultDeposit {
124            common_fields: CommonFields {
125                account: "rDepositor123".into(),
126                transaction_type: TransactionType::VaultDeposit,
127                signing_pub_key: Some("".into()),
128                ..Default::default()
129            },
130            vault_id: VAULT_ID.into(),
131            amount: Amount::XRPAmount(XRPAmount::from("1000000")),
132        };
133
134        let json_str = r#"{"Account":"rDepositor123","TransactionType":"VaultDeposit","Flags":0,"SigningPubKey":"","VaultID":"A0000000000000000000000000000000000000000000000000000000DEADBEEF","Amount":"1000000"}"#;
135
136        // Serialize
137        let serialized = serde_json::to_string(&vault_deposit).unwrap();
138        assert_eq!(
139            serde_json::to_value(&serialized).unwrap(),
140            serde_json::to_value(json_str).unwrap()
141        );
142
143        // Deserialize
144        let deserialized: VaultDeposit = serde_json::from_str(json_str).unwrap();
145        assert_eq!(vault_deposit, deserialized);
146    }
147
148    #[test]
149    fn test_serde_issued_currency() {
150        let vault_deposit = VaultDeposit {
151            common_fields: CommonFields {
152                account: "rDepositorICA456".into(),
153                transaction_type: TransactionType::VaultDeposit,
154                signing_pub_key: Some("".into()),
155                ..Default::default()
156            },
157            vault_id: VAULT_ID.into(),
158            amount: Amount::IssuedCurrencyAmount(IssuedCurrencyAmount::new(
159                "USD".into(),
160                "rIssuer789".into(),
161                "1000".into(),
162            )),
163        };
164
165        let serialized = serde_json::to_string(&vault_deposit).unwrap();
166        let deserialized: VaultDeposit = serde_json::from_str(&serialized).unwrap();
167        assert_eq!(vault_deposit, deserialized);
168    }
169
170    #[test]
171    fn test_builder_pattern() {
172        let vault_deposit = VaultDeposit {
173            common_fields: CommonFields {
174                account: "rDepositor123".into(),
175                transaction_type: TransactionType::VaultDeposit,
176                ..Default::default()
177            },
178            vault_id: VAULT_ID.into(),
179            amount: Amount::XRPAmount(XRPAmount::from("1000000")),
180        }
181        .with_fee("12".into())
182        .with_sequence(100)
183        .with_last_ledger_sequence(7108682)
184        .with_source_tag(12345)
185        .with_memo(Memo {
186            memo_data: Some("depositing into vault".into()),
187            memo_format: None,
188            memo_type: Some("text".into()),
189        });
190
191        assert_eq!(vault_deposit.vault_id, VAULT_ID);
192        assert_eq!(vault_deposit.common_fields.fee.as_ref().unwrap().0, "12");
193        assert_eq!(vault_deposit.common_fields.sequence, Some(100));
194        assert_eq!(
195            vault_deposit.common_fields.last_ledger_sequence,
196            Some(7108682)
197        );
198        assert_eq!(vault_deposit.common_fields.source_tag, Some(12345));
199        assert_eq!(vault_deposit.common_fields.memos.as_ref().unwrap().len(), 1);
200    }
201
202    #[test]
203    fn test_default() {
204        let vault_deposit = VaultDeposit {
205            common_fields: CommonFields {
206                account: "rDepositor789".into(),
207                transaction_type: TransactionType::VaultDeposit,
208                ..Default::default()
209            },
210            vault_id: VAULT_ID.into(),
211            amount: Amount::XRPAmount(XRPAmount::from("5000000")),
212        };
213
214        assert_eq!(vault_deposit.common_fields.account, "rDepositor789");
215        assert_eq!(
216            vault_deposit.common_fields.transaction_type,
217            TransactionType::VaultDeposit
218        );
219        assert_eq!(vault_deposit.vault_id, VAULT_ID);
220        assert!(vault_deposit.common_fields.fee.is_none());
221        assert!(vault_deposit.common_fields.sequence.is_none());
222    }
223
224    #[test]
225    fn test_ticket_sequence() {
226        let ticket_deposit = VaultDeposit {
227            common_fields: CommonFields {
228                account: "rTicketDepositor111".into(),
229                transaction_type: TransactionType::VaultDeposit,
230                ..Default::default()
231            },
232            vault_id: VAULT_ID.into(),
233            amount: Amount::XRPAmount(XRPAmount::from("2000000")),
234        }
235        .with_ticket_sequence(54321)
236        .with_fee("12".into());
237
238        assert_eq!(ticket_deposit.common_fields.ticket_sequence, Some(54321));
239        assert!(ticket_deposit.common_fields.sequence.is_none());
240    }
241
242    #[test]
243    fn test_multiple_memos() {
244        let multi_memo_deposit = VaultDeposit {
245            common_fields: CommonFields {
246                account: "rMultiMemoDepositor222".into(),
247                transaction_type: TransactionType::VaultDeposit,
248                ..Default::default()
249            },
250            vault_id: VAULT_ID.into(),
251            amount: Amount::IssuedCurrencyAmount(IssuedCurrencyAmount::new(
252                "USD".into(),
253                "rUSDIssuer333".into(),
254                "500".into(),
255            )),
256        }
257        .with_memo(Memo {
258            memo_data: Some("first deposit".into()),
259            memo_format: None,
260            memo_type: Some("text".into()),
261        })
262        .with_memo(Memo {
263            memo_data: Some("additional note".into()),
264            memo_format: None,
265            memo_type: Some("text".into()),
266        })
267        .with_fee("18".into())
268        .with_sequence(400);
269
270        assert_eq!(
271            multi_memo_deposit
272                .common_fields
273                .memos
274                .as_ref()
275                .unwrap()
276                .len(),
277            2
278        );
279        assert_eq!(multi_memo_deposit.common_fields.sequence, Some(400));
280    }
281
282    #[test]
283    fn test_new_constructor() {
284        let vault_deposit = VaultDeposit::new(
285            "rNewDepositor444".into(),
286            None,
287            Some("12".into()),
288            Some(7108682),
289            None,
290            Some(100),
291            None,
292            None,
293            None,
294            VAULT_ID.into(),
295            Amount::XRPAmount(XRPAmount::from("10000000")),
296        );
297
298        assert_eq!(vault_deposit.common_fields.account, "rNewDepositor444");
299        assert_eq!(
300            vault_deposit.common_fields.transaction_type,
301            TransactionType::VaultDeposit
302        );
303        assert_eq!(vault_deposit.common_fields.fee.as_ref().unwrap().0, "12");
304        assert_eq!(vault_deposit.vault_id, VAULT_ID);
305    }
306
307    #[test]
308    fn test_validate() {
309        let vault_deposit = VaultDeposit {
310            common_fields: CommonFields {
311                account: "rValidateDepositor555".into(),
312                transaction_type: TransactionType::VaultDeposit,
313                ..Default::default()
314            },
315            vault_id: VAULT_ID.into(),
316            amount: Amount::XRPAmount(XRPAmount::from("1000000")),
317        }
318        .with_fee("12".into())
319        .with_sequence(300);
320
321        assert!(vault_deposit.validate().is_ok());
322    }
323
324    #[test]
325    fn test_amount_zero_rejected() {
326        let vault_deposit = VaultDeposit {
327            common_fields: CommonFields {
328                account: "rDepositor".into(),
329                transaction_type: TransactionType::VaultDeposit,
330                ..Default::default()
331            },
332            vault_id: VAULT_ID.into(),
333            amount: Amount::XRPAmount(XRPAmount::from("0")),
334        };
335        assert!(vault_deposit.validate().is_err());
336    }
337
338    #[test]
339    fn test_amount_negative_rejected() {
340        let vault_deposit = VaultDeposit {
341            common_fields: CommonFields {
342                account: "rDepositor".into(),
343                transaction_type: TransactionType::VaultDeposit,
344                ..Default::default()
345            },
346            vault_id: VAULT_ID.into(),
347            amount: Amount::IssuedCurrencyAmount(crate::models::amount::IssuedCurrencyAmount::new(
348                "USD".into(),
349                "rIssuer".into(),
350                "-10".into(),
351            )),
352        };
353        assert!(vault_deposit.validate().is_err());
354    }
355
356    #[test]
357    fn test_amount_non_numeric_rejected() {
358        let vault_deposit = VaultDeposit {
359            common_fields: CommonFields {
360                account: "rDepositor".into(),
361                transaction_type: TransactionType::VaultDeposit,
362                ..Default::default()
363            },
364            vault_id: VAULT_ID.into(),
365            amount: Amount::XRPAmount(XRPAmount::from("not-a-number")),
366        };
367        assert!(vault_deposit.validate().is_err());
368    }
369}