1use std::{collections::HashMap, fmt::Display};
17
18use nautilus_core::{Params, UUID4, UnixNanos};
19use serde::{Deserialize, Serialize, Serializer, ser::SerializeStruct};
20
21use crate::{
22 enums::AccountType,
23 identifiers::{AccountId, InstrumentId},
24 types::{AccountBalance, Currency, MarginBalance, balance::WalletAccountBalances},
25};
26
27#[repr(C)]
34#[derive(Debug, Clone, Deserialize)]
35#[cfg_attr(
36 feature = "python",
37 pyo3::pyclass(module = "nautilus_trader.model", from_py_object)
38)]
39#[cfg_attr(
40 feature = "python",
41 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")
42)]
43pub struct AccountState {
44 pub account_id: AccountId,
46 pub account_type: AccountType,
48 pub base_currency: Option<Currency>,
50 pub balances: Vec<AccountBalance>,
52 pub margins: Vec<MarginBalance>,
54 pub is_reported: bool,
57 pub event_id: UUID4,
59 pub ts_event: UnixNanos,
61 pub ts_init: UnixNanos,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub info: Option<Params>,
66}
67
68impl Serialize for AccountState {
69 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
70 where
71 S: Serializer,
72 {
73 let field_count = 9 + usize::from(self.info.is_some());
74 let mut state = serializer.serialize_struct("AccountState", field_count)?;
75 state.serialize_field("account_id", &self.account_id)?;
76 state.serialize_field("account_type", &self.account_type)?;
77 state.serialize_field("base_currency", &self.base_currency)?;
78 if self.account_type == AccountType::Wallet {
79 state.serialize_field("balances", &WalletAccountBalances::new(&self.balances))?;
80 } else {
81 state.serialize_field("balances", &self.balances)?;
82 }
83 state.serialize_field("margins", &self.margins)?;
84 state.serialize_field("is_reported", &self.is_reported)?;
85 state.serialize_field("event_id", &self.event_id)?;
86 state.serialize_field("ts_event", &self.ts_event)?;
87 state.serialize_field("ts_init", &self.ts_init)?;
88 if let Some(info) = &self.info {
89 state.serialize_field("info", info)?;
90 }
91 state.end()
92 }
93}
94
95impl AccountState {
96 #[expect(clippy::too_many_arguments)]
98 #[must_use]
99 pub fn new(
100 account_id: AccountId,
101 account_type: AccountType,
102 balances: Vec<AccountBalance>,
103 margins: Vec<MarginBalance>,
104 is_reported: bool,
105 event_id: UUID4,
106 ts_event: UnixNanos,
107 ts_init: UnixNanos,
108 base_currency: Option<Currency>,
109 ) -> Self {
110 Self {
111 account_id,
112 account_type,
113 base_currency,
114 balances,
115 margins,
116 is_reported,
117 event_id,
118 ts_event,
119 ts_init,
120 info: None,
121 }
122 }
123
124 #[must_use]
126 pub fn with_info(mut self, info: Option<Params>) -> Self {
127 self.info = info;
128 self
129 }
130
131 #[must_use]
142 pub fn has_same_balances_and_margins(&self, other: &Self) -> bool {
143 if self.balances.len() != other.balances.len() || self.margins.len() != other.margins.len()
145 {
146 return false;
147 }
148
149 let self_balances: HashMap<Currency, &AccountBalance> = self
151 .balances
152 .iter()
153 .map(|balance| (balance.currency, balance))
154 .collect();
155
156 let other_balances: HashMap<Currency, &AccountBalance> = other
157 .balances
158 .iter()
159 .map(|balance| (balance.currency, balance))
160 .collect();
161
162 for (currency, self_balance) in &self_balances {
164 match other_balances.get(currency) {
165 Some(other_balance) => {
166 if self_balance != other_balance {
167 return false;
168 }
169 }
170 None => return false, }
172 }
173
174 let self_margins: HashMap<(Option<InstrumentId>, Currency), &MarginBalance> = self
178 .margins
179 .iter()
180 .map(|margin| ((margin.instrument_id, margin.currency), margin))
181 .collect();
182
183 let other_margins: HashMap<(Option<InstrumentId>, Currency), &MarginBalance> = other
184 .margins
185 .iter()
186 .map(|margin| ((margin.instrument_id, margin.currency), margin))
187 .collect();
188
189 for (key, self_margin) in &self_margins {
191 match other_margins.get(key) {
192 Some(other_margin) => {
193 if self_margin != other_margin {
194 return false;
195 }
196 }
197 None => return false, }
199 }
200
201 true
202 }
203}
204
205impl Display for AccountState {
206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 write!(
208 f,
209 "{}(account_id={}, account_type={}, base_currency={}, is_reported={}, balances=[{}], margins=[{}], event_id={})",
210 stringify!(AccountState),
211 self.account_id,
212 self.account_type,
213 self.base_currency.map_or_else(
214 || "None".to_string(),
215 |base_currency| format!("{}", base_currency.code)
216 ),
217 self.is_reported,
218 self.balances
219 .iter()
220 .map(|b| format!("{b}"))
221 .collect::<Vec<String>>()
222 .join(", "),
223 self.margins
224 .iter()
225 .map(|m| format!("{m}"))
226 .collect::<Vec<String>>()
227 .join(", "),
228 self.event_id
229 )
230 }
231}
232
233impl PartialEq for AccountState {
234 fn eq(&self, other: &Self) -> bool {
235 self.account_id == other.account_id
236 && self.account_type == other.account_type
237 && self.event_id == other.event_id
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 #[cfg(feature = "defi")]
244 use std::process::Command;
245
246 use indexmap::IndexMap;
247 use nautilus_core::{Params, UUID4, UnixNanos};
248 use rstest::rstest;
249 use serde_json::json;
250
251 use crate::{
252 enums::{AccountType, CurrencyType},
253 events::{
254 AccountState,
255 account::stubs::{cash_account_state, margin_account_state},
256 },
257 identifiers::{AccountId, InstrumentId},
258 types::{AccountBalance, Currency, MarginBalance, Money},
259 };
260
261 #[rstest]
262 fn test_equality() {
263 let cash_account_state_1 = cash_account_state();
264 let cash_account_state_2 = cash_account_state();
265 assert_eq!(cash_account_state_1, cash_account_state_2);
266 }
267
268 #[rstest]
269 fn test_display_cash_account_state(cash_account_state: AccountState) {
270 let display = format!("{cash_account_state}");
271 assert_eq!(
272 display,
273 "AccountState(account_id=SIM-001, account_type=CASH, base_currency=USD, is_reported=true, \
274 balances=[AccountBalance(total=1525000.00 USD, locked=25000.00 USD, free=1500000.00 USD)], \
275 margins=[], event_id=16578139-a945-4b65-b46c-bc131a15d8e7)"
276 );
277 }
278
279 #[rstest]
280 fn test_display_margin_account_state(margin_account_state: AccountState) {
281 let display = format!("{margin_account_state}");
282 assert_eq!(
283 display,
284 "AccountState(account_id=SIM-001, account_type=MARGIN, base_currency=USD, is_reported=true, \
285 balances=[AccountBalance(total=1525000.00 USD, locked=25000.00 USD, free=1500000.00 USD)], \
286 margins=[MarginBalance(initial=5000.00 USD, maintenance=20000.00 USD, instrument_id=BTCUSDT.COINBASE)], \
287 event_id=16578139-a945-4b65-b46c-bc131a15d8e7)"
288 );
289 }
290
291 #[rstest]
292 fn test_has_same_balances_and_margins_when_identical() {
293 let state1 = cash_account_state();
294 let state2 = cash_account_state();
295 assert!(state1.has_same_balances_and_margins(&state2));
296 }
297
298 #[rstest]
299 fn test_has_same_balances_and_margins_when_different_balance_amounts() {
300 let state1 = cash_account_state();
301 let mut state2 = cash_account_state();
302 let usd = Currency::USD();
304 let different_balance = AccountBalance::new(
305 Money::new(2_000_000.0, usd),
306 Money::new(50000.0, usd),
307 Money::new(1_950_000.0, usd),
308 );
309 state2.balances = vec![different_balance];
310 assert!(!state1.has_same_balances_and_margins(&state2));
311 }
312
313 #[rstest]
314 fn test_has_same_balances_and_margins_when_different_balance_currencies() {
315 let state1 = cash_account_state();
316 let mut state2 = cash_account_state();
317 let eur = Currency::EUR();
319 let different_balance = AccountBalance::new(
320 Money::new(1_525_000.0, eur),
321 Money::new(25000.0, eur),
322 Money::new(1_500_000.0, eur),
323 );
324 state2.balances = vec![different_balance];
325 assert!(!state1.has_same_balances_and_margins(&state2));
326 }
327
328 #[rstest]
329 fn test_has_same_balances_and_margins_when_missing_balance() {
330 let state1 = cash_account_state();
331 let mut state2 = cash_account_state();
332 let eur = Currency::EUR();
334 let additional_balance = AccountBalance::new(
335 Money::new(1_000_000.0, eur),
336 Money::new(0.0, eur),
337 Money::new(1_000_000.0, eur),
338 );
339 state2.balances.push(additional_balance);
340 assert!(!state1.has_same_balances_and_margins(&state2));
341 }
342
343 #[rstest]
344 fn test_has_same_balances_and_margins_when_different_margin_amounts() {
345 let state1 = margin_account_state();
346 let mut state2 = margin_account_state();
347 let usd = Currency::USD();
349 let instrument_id = InstrumentId::from("BTCUSDT.COINBASE");
350 let different_margin = MarginBalance::new(
351 Money::new(10000.0, usd),
352 Money::new(40000.0, usd),
353 Some(instrument_id),
354 );
355 state2.margins = vec![different_margin];
356 assert!(!state1.has_same_balances_and_margins(&state2));
357 }
358
359 #[rstest]
360 fn test_has_same_balances_and_margins_when_different_margin_instruments() {
361 let state1 = margin_account_state();
362 let mut state2 = margin_account_state();
363 let usd = Currency::USD();
365 let different_instrument_id = InstrumentId::from("ETHUSDT.BINANCE");
366 let different_margin = MarginBalance::new(
367 Money::new(5000.0, usd),
368 Money::new(20000.0, usd),
369 Some(different_instrument_id),
370 );
371 state2.margins = vec![different_margin];
372 assert!(!state1.has_same_balances_and_margins(&state2));
373 }
374
375 #[rstest]
376 fn test_has_same_balances_and_margins_when_missing_margin() {
377 let state1 = margin_account_state();
378 let mut state2 = margin_account_state();
379 let usd = Currency::USD();
381 let additional_instrument_id = InstrumentId::from("ETHUSDT.BINANCE");
382 let additional_margin = MarginBalance::new(
383 Money::new(3000.0, usd),
384 Money::new(15000.0, usd),
385 Some(additional_instrument_id),
386 );
387 state2.margins.push(additional_margin);
388 assert!(!state1.has_same_balances_and_margins(&state2));
389 }
390
391 #[rstest]
392 fn test_has_same_balances_and_margins_with_empty_collections() {
393 let account_id = AccountId::new("TEST-001");
394 let event_id = UUID4::new();
395 let ts_event = UnixNanos::from(1);
396 let ts_init = UnixNanos::from(2);
397
398 let state1 = AccountState::new(
399 account_id,
400 AccountType::Cash,
401 vec![], vec![], true,
404 event_id,
405 ts_event,
406 ts_init,
407 Some(Currency::USD()),
408 );
409
410 let state2 = AccountState::new(
411 account_id,
412 AccountType::Cash,
413 vec![], vec![], true,
416 UUID4::new(), UnixNanos::from(3), UnixNanos::from(4),
419 Some(Currency::USD()),
420 );
421
422 assert!(state1.has_same_balances_and_margins(&state2));
423 }
424
425 #[rstest]
426 fn test_has_same_balances_and_margins_with_multiple_balances_and_margins() {
427 let account_id = AccountId::new("TEST-001");
428 let event_id = UUID4::new();
429 let ts_event = UnixNanos::from(1);
430 let ts_init = UnixNanos::from(2);
431
432 let usd = Currency::USD();
433 let eur = Currency::EUR();
434 let btc_instrument = InstrumentId::from("BTCUSDT.COINBASE");
435 let eth_instrument = InstrumentId::from("ETHUSDT.BINANCE");
436
437 let balances = vec![
438 AccountBalance::new(
439 Money::new(1_000_000.0, usd),
440 Money::new(0.0, usd),
441 Money::new(1_000_000.0, usd),
442 ),
443 AccountBalance::new(
444 Money::new(500_000.0, eur),
445 Money::new(10000.0, eur),
446 Money::new(490_000.0, eur),
447 ),
448 ];
449
450 let margins = vec![
451 MarginBalance::new(
452 Money::new(5000.0, usd),
453 Money::new(20000.0, usd),
454 Some(btc_instrument),
455 ),
456 MarginBalance::new(
457 Money::new(3000.0, usd),
458 Money::new(15000.0, usd),
459 Some(eth_instrument),
460 ),
461 ];
462
463 let state1 = AccountState::new(
464 account_id,
465 AccountType::Margin,
466 balances.clone(),
467 margins.clone(),
468 true,
469 event_id,
470 ts_event,
471 ts_init,
472 Some(usd),
473 );
474
475 let state2 = AccountState::new(
476 account_id,
477 AccountType::Margin,
478 balances,
479 margins,
480 true,
481 UUID4::new(), UnixNanos::from(3), UnixNanos::from(4),
484 Some(usd),
485 );
486
487 assert!(state1.has_same_balances_and_margins(&state2));
488 }
489
490 fn account_state_with_info() -> AccountState {
491 let mut info = IndexMap::new();
492 info.insert("total_wallet_balance".to_string(), json!(1525.0_f64));
493 info.insert("available_balance".to_string(), json!(1500.0_f64));
494 AccountState::new(
495 AccountId::new("SIM-001"),
496 AccountType::Cash,
497 vec![],
498 vec![],
499 true,
500 UUID4::new(),
501 UnixNanos::default(),
502 UnixNanos::default(),
503 Some(Currency::USD()),
504 )
505 .with_info(Some(Params::from_index_map(info)))
506 }
507
508 #[rstest]
509 fn test_new_defaults_info_to_none() {
510 let state = cash_account_state();
511 assert!(state.info.is_none());
512 }
513
514 #[rstest]
515 fn test_with_info_attaches_params() {
516 let state = account_state_with_info();
517 let info = state.info.expect("info should be set");
518 assert_eq!(info.get_f64("total_wallet_balance"), Some(1525.0));
519 assert_eq!(info.get_f64("available_balance"), Some(1500.0));
520 assert_eq!(info.get_f64("missing"), None);
521 }
522
523 #[rstest]
524 fn test_serde_round_trips_info() {
525 let state = account_state_with_info();
526 let serialized = serde_json::to_string(&state).expect("serialize");
527 let deserialized: AccountState = serde_json::from_str(&serialized).expect("deserialize");
528 let info = deserialized.info.expect("info should round-trip");
529 assert_eq!(info.get_f64("total_wallet_balance"), Some(1525.0));
530 assert_eq!(info.get_f64("available_balance"), Some(1500.0));
531 }
532
533 #[rstest]
534 fn test_serde_back_compatible_without_info() {
535 let state = cash_account_state();
539 let mut value = serde_json::to_value(&state)
540 .expect("serialize")
541 .as_object()
542 .cloned()
543 .unwrap();
544 value.remove("info");
545 let deserialized: AccountState =
546 serde_json::from_value(serde_json::Value::Object(value)).expect("deserialize legacy");
547 assert!(deserialized.info.is_none());
548 }
549
550 #[rstest]
551 #[case(AccountType::Cash, false)]
552 #[case(AccountType::Margin, false)]
553 #[case(AccountType::Betting, false)]
554 #[case(AccountType::Wallet, true)]
555 fn test_registered_account_balance_keeps_legacy_fields(
556 #[case] account_type: AccountType,
557 #[case] has_wallet_identity: bool,
558 ) {
559 let currency = Currency::USD();
560 let total = Money::from("10.25 USD");
561 let state = AccountState::new(
562 AccountId::new("SERDE-001"),
563 account_type,
564 vec![AccountBalance::new(total, Money::zero(currency), total)],
565 vec![],
566 true,
567 UUID4::new(),
568 UnixNanos::from(1),
569 UnixNanos::from(2),
570 None,
571 );
572
573 let value = serde_json::to_value(&state).expect("serialize account state");
574 let balance = &value["balances"][0];
575 let restored: AccountState =
576 serde_json::from_value(value.clone()).expect("deserialize account state");
577
578 assert_eq!(balance["currency"], "USD");
579 assert_eq!(balance["total"], "10.25 USD");
580 assert_eq!(balance["locked"], "0.00 USD");
581 assert_eq!(balance["free"], "10.25 USD");
582 assert_eq!(
583 balance.get("currency_identity").is_some(),
584 has_wallet_identity
585 );
586 assert_eq!(restored.balances[0].total.raw, total.raw);
587 assert_currency_identity(restored.balances[0].currency, currency);
588 }
589
590 #[rstest]
591 fn test_wallet_account_state_rejects_unaligned_raw_balance() {
592 let currency = Currency::USD();
593 let total = Money::from_raw(1, currency);
594 let state = AccountState::new(
595 AccountId::new("WALLET-SERDE-INVALID"),
596 AccountType::Wallet,
597 vec![AccountBalance::new(total, Money::zero(currency), total)],
598 vec![],
599 true,
600 UUID4::new(),
601 UnixNanos::from(1),
602 UnixNanos::from(2),
603 None,
604 );
605
606 let error = serde_json::to_string(&state).unwrap_err();
607
608 assert!(
609 error
610 .to_string()
611 .contains("is not aligned to currency precision 2"),
612 "was: {error}"
613 );
614 }
615
616 #[rstest]
617 fn test_wallet_account_state_rejects_inconsistent_exact_balance() {
618 let currency = Currency::USD();
619 let total = Money::from("10.25 USD");
620 let state = AccountState::new(
621 AccountId::new("WALLET-SERDE-CORRUPT"),
622 AccountType::Wallet,
623 vec![AccountBalance::new(total, Money::zero(currency), total)],
624 vec![],
625 true,
626 UUID4::new(),
627 UnixNanos::from(1),
628 UnixNanos::from(2),
629 None,
630 );
631 let mut value = serde_json::to_value(&state).expect("serialize account state");
632 value["balances"][0]["free_minor"] = json!("999");
633
634 let error = serde_json::from_value::<AccountState>(value).unwrap_err();
635
636 assert!(
637 error
638 .to_string()
639 .contains("`total` (10.25 USD) - `locked` (0.00 USD) != `free` (9.99 USD)"),
640 "was: {error}"
641 );
642 }
643
644 #[rstest]
645 fn test_wallet_account_state_rejects_same_code_currency_identity_mismatch() {
646 let balance_currency =
647 Currency::new("ENG729D", 6, 0, "Balance token", CurrencyType::Crypto);
648 let money_currency = Currency::new("ENG729D", 8, 0, "Money token", CurrencyType::Crypto);
649 let total = Money::from_mantissa_exponent(123_456_789, -8, money_currency);
650 let balance = AccountBalance {
651 currency: balance_currency,
652 total,
653 locked: Money::zero(money_currency),
654 free: total,
655 };
656 let state = AccountState::new(
657 AccountId::new("WALLET-SERDE-IDENTITY"),
658 AccountType::Wallet,
659 vec![balance],
660 vec![],
661 true,
662 UUID4::new(),
663 UnixNanos::from(1),
664 UnixNanos::from(2),
665 None,
666 );
667
668 let error = serde_json::to_string(&state).unwrap_err();
669
670 assert!(
671 error
672 .to_string()
673 .contains("Wallet account balance currency identity ENG729D does not match"),
674 "was: {error}"
675 );
676 }
677
678 #[cfg(feature = "defi")]
679 #[rstest]
680 fn test_wallet_account_state_fresh_process_round_trip() {
681 const PAYLOAD_ENV: &str = "NAUTILUS_WALLET_ACCOUNT_STATE_PAYLOAD";
682 const SUCCESS_SENTINEL: &str = "wallet account state fresh-process assertions passed";
683 const TEST_NAME: &str =
684 "events::account::state::tests::test_wallet_account_state_fresh_process_round_trip";
685
686 if let Ok(payload) = std::env::var(PAYLOAD_ENV) {
687 assert!(Currency::try_from_str("ENG729A").is_none());
688 assert!(Currency::try_from_str("ENG729B").is_none());
689
690 let restored: AccountState =
691 serde_json::from_str(&payload).expect("deserialize wallet account state");
692 let expected = wallet_serde_balances();
693
694 assert_eq!(restored.balances.len(), expected.len());
695 for (restored, expected) in restored.balances.iter().zip(&expected) {
696 assert_eq!(restored.total.raw, expected.total.raw);
697 assert_eq!(restored.locked.raw, expected.locked.raw);
698 assert_eq!(restored.free.raw, expected.free.raw);
699 assert_currency_identity(restored.currency, expected.currency);
700 assert_currency_identity(restored.total.currency, expected.total.currency);
701 assert_currency_identity(restored.locked.currency, expected.locked.currency);
702 assert_currency_identity(restored.free.currency, expected.free.currency);
703 }
704 assert!(Currency::try_from_str("ENG729A").is_none());
705 assert!(Currency::try_from_str("ENG729B").is_none());
706 println!("{SUCCESS_SENTINEL}");
707 return;
708 }
709
710 let producer_currency = Currency::new(
711 "ENG729A",
712 6,
713 0,
714 "Producer registered token",
715 CurrencyType::Crypto,
716 );
717 Currency::register(producer_currency, false).expect("register producer-only currency");
718 let state = AccountState::new(
719 AccountId::new("WALLET-SERDE-001"),
720 AccountType::Wallet,
721 wallet_serde_balances(),
722 vec![],
723 true,
724 UUID4::new(),
725 UnixNanos::from(1),
726 UnixNanos::from(2),
727 None,
728 );
729 let payload = serde_json::to_string(&state).expect("serialize wallet account state");
730
731 let output = Command::new(std::env::current_exe().expect("current test executable"))
732 .args(["--exact", TEST_NAME, "--nocapture"])
733 .env(PAYLOAD_ENV, payload)
734 .output()
735 .expect("run fresh-process wallet round-trip");
736
737 assert!(
738 output.status.success(),
739 "fresh-process wallet round-trip failed\nstdout:\n{}\nstderr:\n{}",
740 String::from_utf8_lossy(&output.stdout),
741 String::from_utf8_lossy(&output.stderr)
742 );
743 assert!(
744 String::from_utf8_lossy(&output.stdout).contains(SUCCESS_SENTINEL),
745 "fresh-process wallet round-trip did not run child assertions\nstdout:\n{}\nstderr:\n{}",
746 String::from_utf8_lossy(&output.stdout),
747 String::from_utf8_lossy(&output.stderr)
748 );
749 }
750
751 #[cfg(feature = "defi")]
752 fn wallet_serde_balances() -> Vec<AccountBalance> {
753 let eth = Currency::new("ETH", 18, 0, "Ethereum", CurrencyType::Crypto);
754 let registered = Currency::new(
755 "ENG729A",
756 6,
757 0,
758 "Producer registered token",
759 CurrencyType::Crypto,
760 );
761 let unregistered =
762 Currency::new("ENG729B", 8, 0, "Unregistered token", CurrencyType::Crypto);
763 let eth_total = Money::from_raw(1_234_567_890_123_456_789, eth);
764 let registered_total = Money::from_mantissa_exponent(123_456_789, -6, registered);
765 let unregistered_total = Money::from_mantissa_exponent(98_765_432, -8, unregistered);
766
767 [eth_total, registered_total, unregistered_total]
768 .into_iter()
769 .map(|total| AccountBalance::new(total, Money::zero(total.currency), total))
770 .collect()
771 }
772
773 fn assert_currency_identity(actual: Currency, expected: Currency) {
774 assert_eq!(actual.code, expected.code);
775 assert_eq!(actual.precision, expected.precision);
776 assert_eq!(actual.iso4217, expected.iso4217);
777 assert_eq!(actual.name, expected.name);
778 assert_eq!(actual.currency_type, expected.currency_type);
779 }
780
781 #[rstest]
782 fn test_info_excluded_from_equality() {
783 let base = account_state_with_info();
786 let mut other_info = IndexMap::new();
787 other_info.insert("different".to_string(), json!(1_u64));
788 let other = AccountState {
789 info: Some(Params::from_index_map(other_info)),
790 ..base.clone()
791 };
792 assert_eq!(base, other);
793 }
794}