1use alloy::primitives::FixedBytes;
11pub use hypercall_engine::command::{
12 AccruePmSettlementInterestCommand, ApplyPmSettlementRepaymentCommand, EngineCommand,
13 JournalPmRecoveryPlanCommand, MarkPmRecoveryActionSubmittedCommand, MarketActionCommand,
14 PmRecoveryActionCommand, PmRecoveryActionKind, PmRecoveryActionResult, PmRecoveryExternalKind,
15 PmRecoveryPlanCommand, PmRecoveryReason, PmRecoveryTrigger, RecordPmVaultDepositCommand,
16 RequestPmVaultWithdrawalCommand, ResolvePmRecoveryActionCommand, RfqExecuteCommand,
17 RfqExecuteLeg, RfqExecuteResult, SetPmSettlementPoolConfigCommand, TickExpiryContext,
18 TickExpiryDueGroup, TickExpiryPendingGroup, TickExpiryPmSettlement, TickExpirySettlementPrice,
19 TickExpiryWalletMarginMode,
20};
21use hypercall_types::EngineMessage;
22use hypercall_types::{OrderUpdateMessage, WalletAddress};
23use rust_decimal::Decimal;
24use serde::{Deserialize, Serialize};
25
26#[derive(Debug, Clone, PartialEq)]
28pub enum ExpiryEffect {
29 UpdateInstrumentStatus {
30 symbols: Vec<String>,
31 status: String,
32 },
33 BatchCancelOrdersForSettlement {
34 order_ids: Vec<i64>,
35 now_ms: u64,
36 },
37 CancelOrphanedOrdersBySymbols {
38 symbols: Vec<String>,
39 },
40 ApplySettlement(ExpirySettlementIntent),
41}
42
43#[derive(Debug, Clone)]
45pub enum MarketEffect {
46 SaveMarketAndInstrument {
47 underlying: String,
48 expiry: i64,
49 instrument: hypercall_db::InstrumentRecord,
50 },
51 DeleteMarketAndInstrument {
52 symbol: String,
53 },
54 RegisterSettlement {
55 underlying: String,
56 symbol: String,
57 expiry_ts: i64,
58 twap_window_seconds: u32,
59 },
60}
61
62#[derive(Debug, Clone, PartialEq)]
64pub struct ExpirySettlementIntent {
65 pub wallet: WalletAddress,
66 pub symbol: String,
67 pub position_size: Decimal,
68 pub settlement_price: Decimal,
69 pub settlement_value: Decimal,
70 pub margin_mode: crate::rsm::margin_mode::MarginMode,
71 pub event_ts_ms: i64,
72 pub settlement_entry_price: Option<Decimal>,
73 pub cost_basis: Option<Decimal>,
74 pub net_pnl: Option<Decimal>,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct PriceUpdatePayload {
80 pub underlying: String,
81 pub spot_price: Decimal,
82 pub timestamp_ms: u64,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct IvUpdatePayload {
88 pub underlying: String,
89 pub strike_points: Vec<crate::vol_oracle::vol_surface_cache::VolPoint>,
90 pub timestamp_ms: u64,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct TierUpdatePayload {
96 pub wallet: hypercall_types::WalletAddress,
97 pub margin_mode: crate::rsm::margin_mode::MarginMode,
98 pub tier: String,
99 pub trading_limits: hypercall_types::api_models::TradingLimits,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct TierMarginModeUpdatePayload {
105 pub wallet: hypercall_types::WalletAddress,
106 pub margin_mode: crate::rsm::margin_mode::MarginMode,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct HypercorePositionUpdatePayload {
112 pub account: String,
113 pub coin: String,
114 pub size: f64,
115 pub entry_price: f64,
116 pub unrealized_pnl: f64,
117 pub timestamp_ms: u64,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct MmpConfigUpdatePayload {
123 pub wallet: hypercall_types::WalletAddress,
124 pub currency: String,
125 pub enabled: bool,
126 pub interval_ms: i64,
127 pub frozen_time_ms: i64,
128 #[serde(default)]
129 pub qty_limit: Option<f64>,
130 #[serde(default)]
131 pub delta_limit: Option<f64>,
132 #[serde(default)]
133 pub vega_limit: Option<f64>,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct TradingModeUpdatePayload {
139 pub modes: std::collections::HashMap<String, hypercall_types::TradingModes>,
140 pub timestamp_ms: u64,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct DepositUpdatePayload {
146 pub wallet: hypercall_types::WalletAddress,
147 pub amount: Decimal,
148 pub timestamp_ms: u64,
149 pub sequence: u64,
150 pub source_event_hash: FixedBytes<32>,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct BalanceCommandPayload {
156 pub wallet: hypercall_types::WalletAddress,
157 pub amount: Decimal,
158 pub balance_after: Decimal,
159 pub timestamp_ms: u64,
160 #[serde(default)]
161 pub sequence: Option<u64>,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct ApproveAgentPayload {
167 pub wallet: hypercall_types::WalletAddress,
168 pub agent: hypercall_types::WalletAddress,
169 #[serde(default)]
170 pub expires_at_ms: Option<u64>,
171 #[serde(default)]
172 pub nonce: Option<u64>,
173 pub timestamp_ms: u64,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct RevokeAgentPayload {
179 pub wallet: hypercall_types::WalletAddress,
180 pub agent: hypercall_types::WalletAddress,
181 #[serde(default)]
182 pub nonce: Option<u64>,
183 pub timestamp_ms: u64,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct NonceAdvancePayload {
189 pub wallet: hypercall_types::WalletAddress,
190 pub nonce: u64,
191 pub timestamp_ms: u64,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct HypercoreEquityUpdatePayload {
197 pub wallet: hypercall_types::WalletAddress,
198 pub account_value: Decimal,
199 pub timestamp_ms: u64,
200}
201
202#[derive(Debug, Clone)]
204pub struct CommandEnvelope {
205 pub received_ts_ms: u64,
207 pub command: EngineCommand,
209}
210
211impl CommandEnvelope {
212 pub fn new(received_ts_ms: u64, command: EngineCommand) -> Self {
214 Self {
215 received_ts_ms,
216 command,
217 }
218 }
219
220 pub fn request_id(&self) -> Option<String> {
222 self.command.request_id()
223 }
224}
225
226#[derive(Debug, Clone, Default)]
233pub struct ApplyOutput {
234 pub events: Vec<EngineMessage>,
237 pub balance_updates: Vec<hypercall_types::BalanceUpdate>,
239 pub order_responses: Vec<OrderUpdateMessage>,
244 pub market_response: Option<hypercall_types::MarketUpdateMessage>,
246 pub expiry_effects: Vec<ExpiryEffect>,
248 pub market_effects: Vec<MarketEffect>,
250 pub rfq_plan: Option<Result<RfqPlanOutput, hypercall_runtime_api::RfqExecuteResult>>,
254 pub outbox_appends: Vec<crate::directive_outbox::DirectiveOutboxAppend>,
256 pub pm_settlement_effects:
258 Vec<crate::rsm::portfolio_margin::settlement_state::PmSettlementProjectionEffect>,
259 #[cfg(feature = "rsm-state")]
261 pub command_identity_hash: [u8; 32],
262}
263
264#[derive(Debug, Clone)]
266pub struct RfqPlanOutput {
267 pub fill_id: String,
268 pub mmp_updates: Vec<MmpFillUpdate>,
269}
270
271#[derive(Debug, Clone)]
274pub struct MmpFillUpdate {
275 pub qp_wallet: hypercall_types::WalletAddress,
276 pub underlying: String,
277 pub fill: hypercall_types::Fill,
278 pub timestamp_ms: u64,
279}
280
281impl ApplyOutput {
282 pub fn new() -> Self {
284 Self::with_capacity(0)
285 }
286
287 pub fn with_capacity(capacity: usize) -> Self {
289 Self {
290 events: Vec::with_capacity(capacity),
291 balance_updates: Vec::new(),
292 order_responses: Vec::new(),
293 market_response: None,
294 expiry_effects: Vec::new(),
295 market_effects: Vec::new(),
296 rfq_plan: None,
297 outbox_appends: Vec::new(),
298 pm_settlement_effects: Vec::new(),
299 #[cfg(feature = "rsm-state")]
300 command_identity_hash: [0u8; 32],
301 }
302 }
303
304 pub fn push(&mut self, event: EngineMessage) {
306 self.events.push(event);
307 }
308
309 pub fn is_empty(&self) -> bool {
311 self.events.is_empty()
312 && self.balance_updates.is_empty()
313 && self.order_responses.is_empty()
314 && self.market_response.is_none()
315 && self.expiry_effects.is_empty()
316 && self.market_effects.is_empty()
317 && self.rfq_plan.is_none()
318 && self.outbox_appends.is_empty()
319 && self.pm_settlement_effects.is_empty()
320 }
321
322 pub fn len(&self) -> usize {
324 self.events.len()
325 + self.balance_updates.len()
326 + self.order_responses.len()
327 + usize::from(self.market_response.is_some())
328 + self.expiry_effects.len()
329 + self.market_effects.len()
330 + usize::from(self.rfq_plan.is_some())
331 + self.outbox_appends.len()
332 + self.pm_settlement_effects.len()
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339 use hypercall_types::{OrderAction, OrderActionMessage, OrderInfo, TimeInForce};
340 use hypercall_types::{Side, WalletAddress};
341 use rust_decimal_macros::dec;
342 use std::str::FromStr;
343
344 #[test]
345 fn test_engine_command_type() {
346 let wallet = WalletAddress::from_str("0x1234567890123456789012345678901234567890").unwrap();
347 let order_info = OrderInfo {
348 symbol: "BTC-20250131-100000-C".to_string(),
349 price: dec!(0.05),
350 size: dec!(1),
351 side: Side::Buy,
352 tif: TimeInForce::GTC,
353 client_id: None,
354 order_id: None,
355 is_perp: false,
356 underlying: None,
357 reduce_only: None,
358 nonce: None,
359 signature: None,
360 mmp_enabled: false,
361 builder_code_address: None,
362 };
363
364 let msg = OrderActionMessage {
365 timestamp: 1000,
366 info: order_info,
367 action: OrderAction::CreateOrder,
368 wallet,
369 api_wallet_address: None,
370 mmp_triggered: false,
371 request_id: Some("test-request-id".to_string()),
372 };
373
374 let cmd = EngineCommand::OrderAction(msg);
375 assert_eq!(cmd.command_type(), "CreateOrder");
376 assert_eq!(cmd.request_id().as_deref(), Some("test-request-id"));
377 }
378
379 #[test]
380 fn test_command_envelope() {
381 let wallet = WalletAddress::from_str("0x1234567890123456789012345678901234567890").unwrap();
382 let order_info = OrderInfo {
383 symbol: "BTC-20250131-100000-C".to_string(),
384 price: dec!(0.05),
385 size: dec!(1),
386 side: Side::Buy,
387 tif: TimeInForce::GTC,
388 client_id: None,
389 order_id: None,
390 is_perp: false,
391 underlying: None,
392 reduce_only: None,
393 nonce: None,
394 signature: None,
395 mmp_enabled: false,
396 builder_code_address: None,
397 };
398
399 let msg = OrderActionMessage {
400 timestamp: 1000,
401 info: order_info,
402 action: OrderAction::CreateOrder,
403 wallet,
404 api_wallet_address: None,
405 mmp_triggered: false,
406 request_id: Some("envelope-test".to_string()),
407 };
408
409 let envelope = CommandEnvelope::new(1234567890, EngineCommand::OrderAction(msg));
410 assert_eq!(envelope.received_ts_ms, 1234567890);
411 assert_eq!(envelope.request_id().as_deref(), Some("envelope-test"));
412 }
413
414 #[test]
415 fn test_price_update_command_type() {
416 let cmd = EngineCommand::PriceUpdate {
417 underlying: "BTC".to_string(),
418 spot_price: dec!(95000),
419 timestamp_ms: 1700000000000,
420 };
421 assert_eq!(cmd.command_type(), "PriceUpdate");
422 assert_eq!(cmd.request_id(), None);
423 assert_eq!(cmd.wallet(), None);
424 assert_eq!(cmd.symbol(), None);
425 assert_eq!(cmd.order_id(), None);
426 }
427
428 #[test]
429 fn test_price_update_payload_serialization_roundtrip() {
430 let payload = PriceUpdatePayload {
431 underlying: "ETH".to_string(),
432 spot_price: dec!(3200.50),
433 timestamp_ms: 1700000001000,
434 };
435
436 let wire = hypercall_types::serialize_to_wire_bytes(&payload);
437 assert!(!wire.is_empty());
438 assert_eq!(wire[0], hypercall_types::WIRE_FORMAT_VERSION);
439
440 let deserialized: PriceUpdatePayload =
441 rmp_serde::from_slice(&wire[1..]).expect("deserialize PriceUpdatePayload");
442 assert_eq!(deserialized.underlying, "ETH");
443 assert_eq!(deserialized.spot_price, dec!(3200.50));
444 assert_eq!(deserialized.timestamp_ms, 1700000001000);
445 }
446
447 #[test]
448 fn test_iv_update_command_type() {
449 let cmd = EngineCommand::IvUpdate {
450 underlying: "BTC".to_string(),
451 surface: crate::vol_oracle::vol_surface_cache::VolatilitySurface::new(),
452 journal_data: None,
453 timestamp_ms: 1700000000000,
454 };
455 assert_eq!(cmd.command_type(), "IvUpdate");
456 assert_eq!(cmd.request_id(), None);
457 }
458
459 #[test]
460 fn test_iv_update_payload_serialization_roundtrip() {
461 use crate::vol_oracle::vol_surface_cache::VolPoint;
462
463 let payload = IvUpdatePayload {
464 underlying: "BTC".to_string(),
465 strike_points: vec![
466 VolPoint {
467 strike: 100000.0,
468 expiry: 1700000000,
469 iv: 0.65,
470 timestamp: 1700000000000,
471 },
472 VolPoint {
473 strike: 110000.0,
474 expiry: 1700000000,
475 iv: 0.70,
476 timestamp: 1700000000000,
477 },
478 ],
479 timestamp_ms: 1700000001000,
480 };
481
482 let wire = hypercall_types::serialize_to_wire_bytes(&payload);
483 assert!(!wire.is_empty());
484
485 let deserialized: IvUpdatePayload =
486 rmp_serde::from_slice(&wire[1..]).expect("deserialize IvUpdatePayload");
487 assert_eq!(deserialized.underlying, "BTC");
488 assert_eq!(deserialized.strike_points.len(), 2);
489 assert_eq!(deserialized.strike_points[0].iv, 0.65);
490 assert_eq!(deserialized.strike_points[1].strike, 110000.0);
491 }
492
493 #[cfg(feature = "rsm-state")]
498 use sha3::{Digest, Keccak256};
499
500 #[cfg(feature = "rsm-state")]
501 fn chain_update(prev: &[u8; 32], cmd: &EngineCommand) -> [u8; 32] {
502 let mut h = Keccak256::new();
503 h.update(prev);
504 h.update(cmd.identity_hash());
505 h.finalize().into()
506 }
507
508 #[cfg(feature = "rsm-state")]
509 #[test]
510 fn test_identity_hash_determinism() {
511 let cmd = EngineCommand::PriceUpdate {
512 underlying: "BTC".to_string(),
513 spot_price: dec!(95000),
514 timestamp_ms: 1000,
515 };
516 assert_eq!(cmd.identity_hash(), cmd.identity_hash());
517 }
518
519 #[cfg(feature = "rsm-state")]
520 #[test]
521 fn test_identity_hash_different_prices_diverge() {
522 let a = EngineCommand::PriceUpdate {
523 underlying: "BTC".to_string(),
524 spot_price: dec!(95000),
525 timestamp_ms: 1000,
526 };
527 let b = EngineCommand::PriceUpdate {
528 underlying: "BTC".to_string(),
529 spot_price: dec!(96000),
530 timestamp_ms: 1000,
531 };
532 assert_ne!(a.identity_hash(), b.identity_hash());
533 }
534
535 #[cfg(feature = "rsm-state")]
536 #[test]
537 fn test_identity_hash_different_iv_surfaces_diverge() {
538 let mut surface_a = crate::vol_oracle::vol_surface_cache::VolatilitySurface::new();
539 surface_a.set_atm_vol(1_700_000_000, 0.65);
540
541 let mut surface_b = crate::vol_oracle::vol_surface_cache::VolatilitySurface::new();
542 surface_b.set_atm_vol(1_700_000_000, 0.70);
543
544 let a = EngineCommand::IvUpdate {
545 underlying: "BTC".to_string(),
546 surface: surface_a,
547 journal_data: None,
548 timestamp_ms: 1000,
549 };
550 let b = EngineCommand::IvUpdate {
551 underlying: "BTC".to_string(),
552 surface: surface_b,
553 journal_data: None,
554 timestamp_ms: 1000,
555 };
556
557 assert_ne!(a.identity_hash(), b.identity_hash());
558 }
559
560 #[cfg(feature = "rsm-state")]
561 #[test]
562 fn test_identity_hash_different_positions_diverge() {
563 let a = EngineCommand::HypercorePositionUpdate {
564 account: "0x1234".to_string(),
565 coin: "BTC".to_string(),
566 size: 1.0,
567 entry_price: 95000.0,
568 unrealized_pnl: 100.0,
569 timestamp_ms: 1000,
570 };
571 let b = EngineCommand::HypercorePositionUpdate {
572 account: "0x1234".to_string(),
573 coin: "BTC".to_string(),
574 size: 2.0,
575 entry_price: 95000.0,
576 unrealized_pnl: 100.0,
577 timestamp_ms: 1000,
578 };
579 assert_ne!(a.identity_hash(), b.identity_hash());
580 }
581
582 #[cfg(feature = "rsm-state")]
583 #[test]
584 fn test_identity_hash_different_rfq_taker_submit_signers_diverge() {
585 let wallet = WalletAddress::from_str("0x1234567890123456789012345678901234567890").unwrap();
586 let signer_a =
587 WalletAddress::from_str("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap();
588 let signer_b =
589 WalletAddress::from_str("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb").unwrap();
590
591 let base = hypercall_runtime_api::RfqExecuteCommand {
592 request_id: "test-req".into(),
593 fill_id: "test-fill".into(),
594 rfq_id: "test-rfq".into(),
595 quote_id: "test-quote".into(),
596 taker_wallet: wallet,
597 qp_wallet: wallet,
598 builder_code_address: None,
599 timestamp_ms: 1,
600 legs: vec![],
601 net_premium: dec!(1),
602 taker_signature: String::new(),
603 qp_signature: String::new(),
604 taker_nonce: Some(1),
605 taker_accept_nonce: None,
606 taker_submit_signer: Some(signer_a),
607 taker_accept_signer: None,
608 };
609
610 let mut changed_signer = base.clone();
611 changed_signer.taker_submit_signer = Some(signer_b);
612
613 assert_ne!(
614 EngineCommand::RfqExecute(base).identity_hash(),
615 EngineCommand::RfqExecute(changed_signer).identity_hash()
616 );
617 }
618
619 #[cfg(feature = "rsm-state")]
620 #[test]
621 fn test_identity_hash_different_rfq_accept_nonces_diverge() {
622 let wallet = WalletAddress::from_str("0x1234567890123456789012345678901234567890").unwrap();
623 let accept_signer_b =
624 WalletAddress::from_str("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb").unwrap();
625 let base = hypercall_runtime_api::RfqExecuteCommand {
626 request_id: "test-req".into(),
627 fill_id: "test-fill".into(),
628 rfq_id: "test-rfq".into(),
629 quote_id: "test-quote".into(),
630 taker_wallet: wallet,
631 qp_wallet: wallet,
632 builder_code_address: None,
633 timestamp_ms: 1,
634 legs: vec![],
635 net_premium: dec!(1),
636 taker_signature: String::new(),
637 qp_signature: String::new(),
638 taker_nonce: Some(1),
639 taker_accept_nonce: Some(2),
640 taker_submit_signer: Some(wallet),
641 taker_accept_signer: Some(wallet),
642 };
643
644 let mut changed_accept_nonce = base.clone();
645 changed_accept_nonce.taker_accept_nonce = Some(3);
646 let mut changed_accept_signer = base.clone();
647 changed_accept_signer.taker_accept_signer = Some(accept_signer_b);
648
649 assert_ne!(
650 EngineCommand::RfqExecute(base.clone()).identity_hash(),
651 EngineCommand::RfqExecute(changed_accept_nonce).identity_hash()
652 );
653 assert_ne!(
654 EngineCommand::RfqExecute(base).identity_hash(),
655 EngineCommand::RfqExecute(changed_accept_signer).identity_hash()
656 );
657 }
658
659 #[cfg(feature = "rsm-state")]
660 #[test]
661 fn test_identity_hash_different_types_diverge() {
662 let price = EngineCommand::PriceUpdate {
663 underlying: "BTC".to_string(),
664 spot_price: dec!(95000),
665 timestamp_ms: 1000,
666 };
667 let tick = EngineCommand::TickExpiry {
668 now_ms: 1000,
669 context: TickExpiryContext::empty(),
670 };
671 assert_ne!(price.identity_hash(), tick.identity_hash());
672 }
673
674 #[cfg(feature = "rsm-state")]
675 #[test]
676 fn test_hash_chain_ordering_matters() {
677 let a = EngineCommand::PriceUpdate {
678 underlying: "BTC".to_string(),
679 spot_price: dec!(95000),
680 timestamp_ms: 1000,
681 };
682 let b = EngineCommand::TickExpiry {
683 now_ms: 2000,
684 context: TickExpiryContext::empty(),
685 };
686 let root_ab = chain_update(&chain_update(&[0u8; 32], &a), &b);
687 let root_ba = chain_update(&chain_update(&[0u8; 32], &b), &a);
688 assert_ne!(root_ab, root_ba);
689 }
690
691 #[cfg(feature = "rsm-state")]
692 #[test]
693 fn test_hash_chain_long_sequence_determinism() {
694 let mut root_a = [0u8; 32];
695 let mut root_b = [0u8; 32];
696 for i in 0..100u64 {
697 let cmd = EngineCommand::PriceUpdate {
698 underlying: "BTC".to_string(),
699 spot_price: Decimal::from(95000 + i),
700 timestamp_ms: i * 1000,
701 };
702 root_a = chain_update(&root_a, &cmd);
703 root_b = chain_update(&root_b, &cmd);
704 }
705 assert_eq!(root_a, root_b);
706 assert_ne!(root_a, [0u8; 32]);
707 }
708
709 #[cfg(feature = "rsm-state")]
710 #[test]
711 fn test_hash_chain_prev_root_matters() {
712 let cmd = EngineCommand::TickExpiry {
713 now_ms: 1000,
714 context: TickExpiryContext::empty(),
715 };
716 assert_ne!(
717 chain_update(&[0u8; 32], &cmd),
718 chain_update(&[1u8; 32], &cmd)
719 );
720 }
721
722 #[cfg(feature = "rsm-state")]
723 #[test]
724 fn test_identity_hash_all_types_nonzero() {
725 let wallet = WalletAddress::from_str("0x1234567890123456789012345678901234567890").unwrap();
726 let cmds: Vec<EngineCommand> = vec![
727 EngineCommand::TickExpiry {
728 now_ms: 1,
729 context: TickExpiryContext::empty(),
730 },
731 EngineCommand::TickSnapshot { now_ms: 1 },
732 EngineCommand::PriceUpdate {
733 underlying: "BTC".into(),
734 spot_price: dec!(1),
735 timestamp_ms: 1,
736 },
737 EngineCommand::IvUpdate {
738 underlying: "BTC".into(),
739 surface: crate::vol_oracle::vol_surface_cache::VolatilitySurface::new(),
740 journal_data: None,
741 timestamp_ms: 1,
742 },
743 EngineCommand::TierUpdate {
744 wallet,
745 margin_mode: crate::rsm::margin_mode::MarginMode::Portfolio,
746 tier: "tier2".to_string(),
747 trading_limits: hypercall_types::api_models::TradingLimits::default(),
748 },
749 EngineCommand::LegacyTierMarginModeUpdate {
750 wallet,
751 margin_mode: crate::rsm::margin_mode::MarginMode::Standard,
752 },
753 EngineCommand::HypercorePositionUpdate {
754 account: "0x1".into(),
755 coin: "BTC".into(),
756 size: 1.0,
757 entry_price: 1.0,
758 unrealized_pnl: 0.0,
759 timestamp_ms: 1,
760 },
761 EngineCommand::MmpConfigUpdate {
762 wallet,
763 currency: "BTC".into(),
764 enabled: true,
765 interval_ms: 1000,
766 frozen_time_ms: 5000,
767 qty_limit: None,
768 delta_limit: None,
769 vega_limit: None,
770 },
771 EngineCommand::RfqExecute(hypercall_runtime_api::RfqExecuteCommand {
772 request_id: "test-req".into(),
773 fill_id: "test-fill".into(),
774 rfq_id: "test-rfq".into(),
775 quote_id: "test-quote".into(),
776 taker_wallet: wallet,
777 qp_wallet: wallet,
778 builder_code_address: None,
779 timestamp_ms: 1,
780 legs: vec![],
781 net_premium: dec!(1),
782 taker_signature: String::new(),
783 qp_signature: String::new(),
784 taker_nonce: None,
785 taker_accept_nonce: None,
786 taker_submit_signer: None,
787 taker_accept_signer: None,
788 }),
789 EngineCommand::DepositUpdate {
790 wallet,
791 amount: dec!(1),
792 timestamp_ms: 1,
793 sequence: Some(1),
794 source_event_hash: FixedBytes::from([1; 32]),
795 },
796 EngineCommand::LiquidationBonusUpdate {
797 wallet,
798 amount: dec!(1),
799 balance_after: dec!(3),
800 timestamp_ms: 1,
801 sequence: Some(2),
802 },
803 EngineCommand::ApproveAgent {
804 wallet,
805 agent: wallet,
806 expires_at_ms: None,
807 nonce: None,
808 timestamp_ms: 1,
809 },
810 EngineCommand::RevokeAgent {
811 wallet,
812 agent: wallet,
813 nonce: None,
814 timestamp_ms: 1,
815 },
816 EngineCommand::HypercoreEquityUpdate {
817 wallet,
818 account_value: dec!(5000),
819 timestamp_ms: 1,
820 },
821 ];
822 for cmd in &cmds {
823 assert_ne!(
824 cmd.identity_hash(),
825 [0u8; 32],
826 "{} hash should be non-zero",
827 cmd.command_type()
828 );
829 }
830 }
831}