1use crate::models::ledger::objects::LedgerEntryType;
2use crate::models::{Currency, FlagCollection, Model};
3use alloc::borrow::Cow;
4use serde::{Deserialize, Serialize};
5use serde_repr::{Deserialize_repr, Serialize_repr};
6use serde_with::skip_serializing_none;
7use strum_macros::{AsRefStr, Display, EnumIter};
8
9use super::{CommonFields, LedgerObject};
10
11#[derive(
13 Debug, Eq, PartialEq, Clone, Serialize_repr, Deserialize_repr, Display, AsRefStr, EnumIter,
14)]
15#[repr(u32)]
16pub enum VaultFlag {
17 LsfVaultPrivate = 0x00010000,
20}
21
22#[skip_serializing_none]
37#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
38#[serde(rename_all = "PascalCase")]
39pub struct Vault<'a> {
40 #[serde(flatten)]
45 pub common_fields: CommonFields<'a, VaultFlag>,
46 pub owner: Cow<'a, str>,
48 pub account: Cow<'a, str>,
50 pub asset: Currency<'a>,
52 pub assets_total: Option<Cow<'a, str>>,
54 pub assets_available: Option<Cow<'a, str>>,
56 pub assets_maximum: Option<Cow<'a, str>>,
58 pub loss_unrealized: Option<Cow<'a, str>>,
60 #[serde(rename = "ShareMPTID")]
62 pub share_mpt_id: Cow<'a, str>,
63 pub withdrawal_policy: u8,
65 pub scale: Option<u8>,
68 pub sequence: u32,
70 pub data: Option<Cow<'a, str>>,
72 pub owner_node: Cow<'a, str>,
74 #[serde(rename = "PreviousTxnID")]
76 pub previous_txn_id: Cow<'a, str>,
77 pub previous_txn_lgr_seq: u32,
80}
81
82impl<'a> Model for Vault<'a> {}
83
84impl<'a> LedgerObject<VaultFlag> for Vault<'a> {
85 fn get_ledger_entry_type(&self) -> LedgerEntryType {
86 self.common_fields.get_ledger_entry_type()
87 }
88}
89
90impl<'a> Vault<'a> {
91 #[allow(clippy::too_many_arguments)]
93 pub fn new(
94 flags: FlagCollection<VaultFlag>,
95 index: Option<Cow<'a, str>>,
96 owner: Cow<'a, str>,
97 account: Cow<'a, str>,
98 asset: Currency<'a>,
99 share_mpt_id: Cow<'a, str>,
100 withdrawal_policy: u8,
101 sequence: u32,
102 owner_node: Cow<'a, str>,
103 previous_txn_id: Cow<'a, str>,
104 previous_txn_lgr_seq: u32,
105 ) -> Self {
106 Self {
107 common_fields: CommonFields {
108 flags,
109 ledger_entry_type: LedgerEntryType::Vault,
110 index,
111 ledger_index: None,
112 },
113 owner,
114 account,
115 asset,
116 assets_total: None,
117 assets_available: None,
118 assets_maximum: None,
119 loss_unrealized: None,
120 share_mpt_id,
121 withdrawal_policy,
122 scale: None,
123 sequence,
124 data: None,
125 owner_node,
126 previous_txn_id,
127 previous_txn_lgr_seq,
128 }
129 }
130}
131
132#[cfg(test)]
133mod test_serde {
134 use crate::models::currency::{Currency, IssuedCurrency};
135 use crate::models::ledger::objects::vault::{Vault, VaultFlag};
136 use crate::models::ledger::objects::CommonFields;
137 use crate::models::ledger::objects::LedgerEntryType;
138 use crate::models::FlagCollection;
139 use alloc::borrow::Cow;
140 use alloc::vec;
141
142 fn make_vault<'a>(
143 index: Option<Cow<'a, str>>,
144 owner: Cow<'a, str>,
145 account: Cow<'a, str>,
146 asset: Currency<'a>,
147 share_mpt_id: Cow<'a, str>,
148 withdrawal_policy: u8,
149 sequence: u32,
150 owner_node: Cow<'a, str>,
151 previous_txn_id: Cow<'a, str>,
152 previous_txn_lgr_seq: u32,
153 ) -> Vault<'a> {
154 Vault::new(
155 FlagCollection::<VaultFlag>::default(),
156 index,
157 owner,
158 account,
159 asset,
160 share_mpt_id,
161 withdrawal_policy,
162 sequence,
163 owner_node,
164 previous_txn_id,
165 previous_txn_lgr_seq,
166 )
167 }
168
169 #[test]
170 fn test_serialize() {
171 let vault = Vault {
172 common_fields: CommonFields {
173 flags: FlagCollection::<VaultFlag>::default(),
174 ledger_entry_type: LedgerEntryType::Vault,
175 index: Some(Cow::from("ForTest")),
176 ledger_index: None,
177 },
178 owner: "rVaultOwner123".into(),
179 account: "rPseudoAccount456".into(),
180 asset: Currency::IssuedCurrency(IssuedCurrency::new("USD".into(), "rIssuer456".into())),
181 assets_total: Some("1000000".into()),
182 assets_available: Some("800000".into()),
183 assets_maximum: Some("5000000".into()),
184 loss_unrealized: Some("0".into()),
185 share_mpt_id: "00000001C752C42A1EBD6BF2403134F7CFD2F1D835AFD26E".into(),
186 withdrawal_policy: 1,
187 scale: Some(6),
188 sequence: 5,
189 data: Some("48656C6C6F".into()),
190 owner_node: "0".into(),
191 previous_txn_id: "ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890"
192 .into(),
193 previous_txn_lgr_seq: 12345678,
194 };
195
196 let serialized = serde_json::to_string(&vault).unwrap();
197 let deserialized: Vault = serde_json::from_str(&serialized).unwrap();
198 assert_eq!(vault, deserialized);
199 }
200
201 #[test]
202 fn test_minimal_vault() {
203 let vault = make_vault(
204 Some(Cow::from("MinimalTest")),
205 "rMinimalOwner789".into(),
206 "rMinimalPseudo789".into(),
207 Currency::IssuedCurrency(IssuedCurrency::new("EUR".into(), "rEURIssuer012".into())),
208 "00000001C752C42A1EBD6BF2403134F7CFD2F1D835AFD26E".into(),
209 1,
210 1,
211 "0".into(),
212 "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF".into(),
213 1,
214 );
215
216 let serialized = serde_json::to_string(&vault).unwrap();
217 let deserialized: Vault = serde_json::from_str(&serialized).unwrap();
218 assert_eq!(vault, deserialized);
219 }
220
221 #[test]
222 fn test_vault_with_all_fields() {
223 let vault = Vault {
224 common_fields: CommonFields {
225 flags: FlagCollection::<VaultFlag>::default(),
226 ledger_entry_type: LedgerEntryType::Vault,
227 index: Some(Cow::from("FullVaultTest")),
228 ledger_index: Some(Cow::from("ledger_idx_123")),
229 },
230 owner: "rFullVaultOwner456".into(),
231 account: "rFullPseudoAccount".into(),
232 asset: Currency::IssuedCurrency(IssuedCurrency::new(
233 "BTC".into(),
234 "rBTCIssuer789".into(),
235 )),
236 assets_total: Some("50000000".into()),
237 assets_available: Some("45000000".into()),
238 assets_maximum: Some("100000000".into()),
239 loss_unrealized: Some("200000".into()),
240 share_mpt_id: "00000001C752C42A1EBD6BF2403134F7CFD2F1D835AFD26E".into(),
241 withdrawal_policy: 1,
242 scale: Some(6),
243 sequence: 1,
244 data: Some("44617461".into()),
245 owner_node: "42".into(),
246 previous_txn_id: "FEDCBA0987654321FEDCBA0987654321FEDCBA0987654321FEDCBA0987654321"
247 .into(),
248 previous_txn_lgr_seq: 99999999,
249 };
250
251 let serialized = serde_json::to_string(&vault).unwrap();
252 let deserialized: Vault = serde_json::from_str(&serialized).unwrap();
253 assert_eq!(vault, deserialized);
254 }
255
256 #[test]
257 fn test_new_constructor() {
258 let vault = Vault::new(
259 FlagCollection::<VaultFlag>::default(),
260 Some(Cow::from("NewConstructorTest")),
261 "rNewOwner".into(),
262 "rNewAccount".into(),
263 Currency::IssuedCurrency(IssuedCurrency::new("USD".into(), "rIssuer".into())),
264 "00000001C752C42A1EBD6BF2403134F7CFD2F1D835AFD26E".into(),
265 1,
266 42,
267 "0".into(),
268 "ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890".into(),
269 100,
270 );
271 let serialized = serde_json::to_string(&vault).unwrap();
272 let deserialized: Vault = serde_json::from_str(&serialized).unwrap();
273 assert_eq!(vault, deserialized);
274 assert!(vault.assets_total.is_none());
275 assert!(vault.data.is_none());
276 }
277
278 #[test]
279 fn test_vault_private_flag_serde() {
280 let vault = Vault {
281 common_fields: CommonFields {
282 flags: vec![VaultFlag::LsfVaultPrivate].into(),
283 ledger_entry_type: LedgerEntryType::Vault,
284 index: Some(Cow::from("PrivateFlagTest")),
285 ledger_index: None,
286 },
287 owner: "rFlagOwner".into(),
288 account: "rFlagAccount".into(),
289 asset: Currency::IssuedCurrency(IssuedCurrency::new("USD".into(), "rIssuer".into())),
290 assets_total: None,
291 assets_available: None,
292 assets_maximum: None,
293 loss_unrealized: None,
294 share_mpt_id: "00000001C752C42A1EBD6BF2403134F7CFD2F1D835AFD26E".into(),
295 withdrawal_policy: 1,
296 scale: None,
297 sequence: 1,
298 data: None,
299 owner_node: "0".into(),
300 previous_txn_id: "ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890"
301 .into(),
302 previous_txn_lgr_seq: 1,
303 };
304 let serialized = serde_json::to_string(&vault).unwrap();
305 let deserialized: Vault = serde_json::from_str(&serialized).unwrap();
306 assert_eq!(vault, deserialized);
307 assert!(
309 serialized.contains("65536"),
310 "expected LsfVaultPrivate flag value 65536 in JSON: {serialized}"
311 );
312 }
313
314 #[test]
315 fn test_serialized_keys_are_pascal_case() {
316 let vault = Vault {
317 common_fields: CommonFields {
318 flags: FlagCollection::<VaultFlag>::default(),
319 ledger_entry_type: LedgerEntryType::Vault,
320 index: Some(Cow::from("KeysTest")),
321 ledger_index: None,
322 },
323 owner: "rKeysOwner".into(),
324 account: "rKeysAccount".into(),
325 asset: Currency::IssuedCurrency(IssuedCurrency::new("USD".into(), "rIssuerX".into())),
326 assets_total: Some("100".into()),
327 assets_available: Some("90".into()),
328 assets_maximum: Some("200".into()),
329 loss_unrealized: Some("5".into()),
330 share_mpt_id: "00000001C752C42A1EBD6BF2403134F7CFD2F1D835AFD26E".into(),
331 withdrawal_policy: 1,
332 scale: Some(6),
333 sequence: 1,
334 data: Some("48656C6C6F".into()),
335 owner_node: "0".into(),
336 previous_txn_id: "ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890"
337 .into(),
338 previous_txn_lgr_seq: 100,
339 };
340
341 let json = serde_json::to_string(&vault).unwrap();
342 assert!(json.contains("\"Account\""), "missing Account key: {json}");
343 assert!(json.contains("\"Owner\""), "missing Owner key: {json}");
344 assert!(json.contains("\"Asset\""), "missing Asset key: {json}");
345 assert!(
346 json.contains("\"AssetsTotal\""),
347 "missing AssetsTotal key: {json}"
348 );
349 assert!(
350 json.contains("\"AssetsAvailable\""),
351 "missing AssetsAvailable key: {json}"
352 );
353 assert!(
354 json.contains("\"AssetsMaximum\""),
355 "missing AssetsMaximum key: {json}"
356 );
357 assert!(
358 json.contains("\"LossUnrealized\""),
359 "missing LossUnrealized key: {json}"
360 );
361 assert!(
362 json.contains("\"ShareMPTID\""),
363 "missing ShareMPTID key: {json}"
364 );
365 assert!(
366 json.contains("\"WithdrawalPolicy\""),
367 "missing WithdrawalPolicy key: {json}"
368 );
369 assert!(json.contains("\"Scale\""), "missing Scale key: {json}");
370 assert!(
371 json.contains("\"Sequence\""),
372 "missing Sequence key: {json}"
373 );
374 assert!(json.contains("\"Data\""), "missing Data key: {json}");
375 assert!(
376 json.contains("\"OwnerNode\""),
377 "missing OwnerNode key: {json}"
378 );
379 assert!(
380 json.contains("\"PreviousTxnID\""),
381 "missing PreviousTxnID key: {json}"
382 );
383 assert!(
384 json.contains("\"PreviousTxnLgrSeq\""),
385 "missing PreviousTxnLgrSeq key: {json}"
386 );
387 assert!(
388 json.contains("\"LedgerEntryType\":\"Vault\""),
389 "missing LedgerEntryType=Vault: {json}"
390 );
391 }
392}