1use crate::error::Error;
2use crate::lifecycle::adapters::{CorrelationOutcome, EventPayload, ProtocolAdapter};
3use crate::protocols::{AccountInfo, EventType, Protocol, ProtocolHelpers};
4use crate::types::{RawEvent, RawInstruction, ResolveContext};
5use strum::VariantNames;
6
7#[derive(serde::Deserialize, strum_macros::VariantNames)]
9pub enum LimitV2EventEnvelope {
10 CreateOrderEvent(OrderKeyHolder),
11 CancelOrderEvent(OrderKeyHolder),
12 TradeEvent(TradeEventFields),
13}
14
15#[derive(serde::Deserialize)]
17pub enum LimitV2InstructionKind {
18 InitializeOrder(serde_json::Value),
19 PreFlashFillOrder(serde_json::Value),
20 FlashFillOrder(serde_json::Value),
21 CancelOrder(serde_json::Value),
22 UpdateFee(serde_json::Value),
23 WithdrawFee(serde_json::Value),
24}
25
26#[cfg(feature = "wasm")]
27pub const INSTRUCTION_EVENT_TYPES: &[(&str, EventType)] = &[
28 ("InitializeOrder", EventType::Created),
29 ("PreFlashFillOrder", EventType::FillInitiated),
30 ("FlashFillOrder", EventType::FillCompleted),
31 ("CancelOrder", EventType::Cancelled),
32];
33
34#[cfg(feature = "wasm")]
35pub const EVENT_EVENT_TYPES: &[(&str, EventType)] = &[
36 ("CreateOrderEvent", EventType::Created),
37 ("CancelOrderEvent", EventType::Cancelled),
38 ("TradeEvent", EventType::FillCompleted),
39];
40
41#[cfg(feature = "wasm")]
42pub const CLOSED_VARIANTS: &[&str] = &[];
43
44#[derive(Debug)]
46pub struct LimitV2Adapter;
47
48#[derive(serde::Deserialize)]
50pub struct OrderKeyHolder {
51 order_key: String,
52}
53
54#[derive(serde::Deserialize)]
56pub struct TradeEventFields {
57 order_key: String,
58 #[serde(default = "LimitV2Adapter::default_unknown")]
59 taker: String,
60 making_amount: u64,
61 taking_amount: u64,
62 remaining_making_amount: u64,
63 #[expect(dead_code, reason = "consumed by serde for completeness")]
64 remaining_taking_amount: u64,
65}
66
67pub struct LimitV2TradeEvent {
69 pub order_pda: String,
70 pub taker: String,
71 pub in_amount: i64,
72 pub out_amount: i64,
73 pub remaining_in_amount: i64,
74 pub remaining_out_amount: i64,
75}
76
77pub struct LimitV2CreateArgs {
81 pub unique_id: Option<i64>,
82 pub making_amount: i64,
83 pub taking_amount: i64,
84 pub expired_at: Option<i64>,
85 pub fee_bps: Option<i16>,
86}
87
88pub struct LimitV2CreateMints {
90 pub input_mint: String,
91 pub output_mint: String,
92}
93
94#[derive(serde::Deserialize)]
95struct InitializeOrderParamsFields {
96 #[serde(default)]
97 unique_id: Option<u64>,
98 making_amount: u64,
99 taking_amount: u64,
100 expired_at: Option<i64>,
101 #[serde(default)]
102 fee_bps: Option<u16>,
103}
104
105#[derive(serde::Deserialize)]
106struct InitializeOrderWrapper {
107 params: InitializeOrderParamsFields,
108}
109
110impl ProtocolAdapter for LimitV2Adapter {
111 fn protocol(&self) -> Protocol {
112 Protocol::LimitV2
113 }
114
115 fn classify_instruction(&self, ix: &RawInstruction) -> Option<EventType> {
116 let wrapper = serde_json::json!({ &ix.instruction_name: ix.args });
117 let kind: LimitV2InstructionKind = serde_json::from_value(wrapper).ok()?;
118 match kind {
119 LimitV2InstructionKind::InitializeOrder(_) => Some(EventType::Created),
120 LimitV2InstructionKind::PreFlashFillOrder(_) => Some(EventType::FillInitiated),
121 LimitV2InstructionKind::FlashFillOrder(_) => Some(EventType::FillCompleted),
122 LimitV2InstructionKind::CancelOrder(_) => Some(EventType::Cancelled),
123 LimitV2InstructionKind::UpdateFee(_) | LimitV2InstructionKind::WithdrawFee(_) => None,
124 }
125 }
126
127 fn classify_and_resolve_event(
128 &self,
129 ev: &RawEvent,
130 _ctx: &ResolveContext,
131 ) -> Option<Result<(EventType, CorrelationOutcome, EventPayload), Error>> {
132 let fields = ev.fields.as_ref()?;
133 let envelope: LimitV2EventEnvelope = match serde_json::from_value(fields.clone()) {
134 Ok(e) => e,
135 Err(err) => {
136 if !ProtocolHelpers::contains_known_variant(fields, LimitV2EventEnvelope::VARIANTS)
137 {
138 return None;
139 }
140 return Some(Err(Error::Protocol {
141 reason: format!("failed to parse Limit v2 event payload: {err}"),
142 }));
143 }
144 };
145
146 Some(Self::resolve_event(envelope))
147 }
148}
149
150impl LimitV2Adapter {
151 fn default_unknown() -> String {
152 "unknown".to_string()
153 }
154
155 fn resolve_event(
156 envelope: LimitV2EventEnvelope,
157 ) -> Result<(EventType, CorrelationOutcome, EventPayload), Error> {
158 match envelope {
159 LimitV2EventEnvelope::CreateOrderEvent(OrderKeyHolder { order_key }) => Ok((
160 EventType::Created,
161 CorrelationOutcome::Correlated(vec![order_key]),
162 EventPayload::None,
163 )),
164 LimitV2EventEnvelope::CancelOrderEvent(OrderKeyHolder { order_key }) => Ok((
165 EventType::Cancelled,
166 CorrelationOutcome::Correlated(vec![order_key]),
167 EventPayload::None,
168 )),
169 LimitV2EventEnvelope::TradeEvent(TradeEventFields {
170 order_key,
171 taker,
172 making_amount,
173 taking_amount,
174 remaining_making_amount,
175 ..
176 }) => Ok((
177 EventType::FillCompleted,
178 CorrelationOutcome::Correlated(vec![order_key]),
179 EventPayload::LimitFill {
180 in_amount: ProtocolHelpers::checked_u64_to_i64(making_amount, "making_amount")?,
181 out_amount: ProtocolHelpers::checked_u64_to_i64(
182 taking_amount,
183 "taking_amount",
184 )?,
185 remaining_in_amount: ProtocolHelpers::checked_u64_to_i64(
186 remaining_making_amount,
187 "remaining_making_amount",
188 )?,
189 counterparty: taker,
190 },
191 )),
192 }
193 }
194
195 pub fn extract_order_pda(
199 accounts: &[AccountInfo],
200 instruction_name: &str,
201 ) -> Result<String, Error> {
202 if let Some(acc) = ProtocolHelpers::find_account_by_name(accounts, "order") {
203 return Ok(acc.pubkey.clone());
204 }
205
206 let wrapper = serde_json::json!({ instruction_name: serde_json::Value::Null });
207 let kind: LimitV2InstructionKind =
208 serde_json::from_value(wrapper).map_err(|_| Error::Protocol {
209 reason: format!("unknown Limit v2 instruction: {instruction_name}"),
210 })?;
211
212 let idx = match kind {
213 LimitV2InstructionKind::InitializeOrder(_) => 2,
214 LimitV2InstructionKind::FlashFillOrder(_) | LimitV2InstructionKind::CancelOrder(_) => 2,
215 LimitV2InstructionKind::PreFlashFillOrder(_) => 1,
216 LimitV2InstructionKind::UpdateFee(_) | LimitV2InstructionKind::WithdrawFee(_) => {
217 return Err(Error::Protocol {
218 reason: format!("Limit v2 instruction {instruction_name} has no order PDA"),
219 });
220 }
221 };
222
223 accounts
224 .get(idx)
225 .map(|a| a.pubkey.clone())
226 .ok_or_else(|| Error::Protocol {
227 reason: format!(
228 "Limit v2 account index {idx} out of bounds for {instruction_name}"
229 ),
230 })
231 }
232
233 pub fn extract_create_mints(accounts: &[AccountInfo]) -> Result<LimitV2CreateMints, Error> {
237 let by_name_input =
238 ProtocolHelpers::find_account_by_name(accounts, "input_mint").map(|a| a.pubkey.clone());
239 let by_name_output = ProtocolHelpers::find_account_by_name(accounts, "output_mint")
240 .map(|a| a.pubkey.clone());
241
242 if let (Some(input_mint), Some(output_mint)) = (by_name_input, by_name_output) {
243 return Ok(LimitV2CreateMints {
244 input_mint,
245 output_mint,
246 });
247 }
248
249 let input_mint =
250 accounts
251 .get(7)
252 .map(|a| a.pubkey.clone())
253 .ok_or_else(|| Error::Protocol {
254 reason: "Limit v2 input_mint index 7 out of bounds".into(),
255 })?;
256 let output_mint =
257 accounts
258 .get(8)
259 .map(|a| a.pubkey.clone())
260 .ok_or_else(|| Error::Protocol {
261 reason: "Limit v2 output_mint index 8 out of bounds".into(),
262 })?;
263
264 Ok(LimitV2CreateMints {
265 input_mint,
266 output_mint,
267 })
268 }
269
270 pub fn parse_create_args(args: &serde_json::Value) -> Result<LimitV2CreateArgs, Error> {
274 let params =
275 if let Ok(wrapper) = serde_json::from_value::<InitializeOrderWrapper>(args.clone()) {
276 wrapper.params
277 } else {
278 serde_json::from_value::<InitializeOrderParamsFields>(args.clone()).map_err(
279 |e| Error::Protocol {
280 reason: format!("failed to parse Limit v2 create args: {e}"),
281 },
282 )?
283 };
284
285 let InitializeOrderParamsFields {
286 unique_id,
287 making_amount,
288 taking_amount,
289 expired_at,
290 fee_bps,
291 } = params;
292
293 Ok(LimitV2CreateArgs {
294 unique_id: unique_id.and_then(ProtocolHelpers::optional_u64_to_i64),
295 making_amount: ProtocolHelpers::checked_u64_to_i64(making_amount, "making_amount")?,
296 taking_amount: ProtocolHelpers::checked_u64_to_i64(taking_amount, "taking_amount")?,
297 expired_at,
298 fee_bps: fee_bps
299 .map(|v| ProtocolHelpers::checked_u16_to_i16(v, "fee_bps"))
300 .transpose()?,
301 })
302 }
303
304 #[cfg(all(test, feature = "native"))]
305 pub fn classify_decoded(
306 decoded: &carbon_jupiter_limit_order_2_decoder::instructions::JupiterLimitOrder2Instruction,
307 ) -> Option<EventType> {
308 use carbon_jupiter_limit_order_2_decoder::instructions::JupiterLimitOrder2Instruction;
309 match decoded {
310 JupiterLimitOrder2Instruction::InitializeOrder(_) => Some(EventType::Created),
311 JupiterLimitOrder2Instruction::PreFlashFillOrder(_) => Some(EventType::FillInitiated),
312 JupiterLimitOrder2Instruction::FlashFillOrder(_) => Some(EventType::FillCompleted),
313 JupiterLimitOrder2Instruction::CancelOrder(_) => Some(EventType::Cancelled),
314 JupiterLimitOrder2Instruction::CreateOrderEvent(_) => Some(EventType::Created),
315 JupiterLimitOrder2Instruction::CancelOrderEvent(_) => Some(EventType::Cancelled),
316 JupiterLimitOrder2Instruction::TradeEvent(_) => Some(EventType::FillCompleted),
317 JupiterLimitOrder2Instruction::UpdateFee(_)
318 | JupiterLimitOrder2Instruction::WithdrawFee(_) => None,
319 }
320 }
321}
322
323#[cfg(test)]
324#[expect(clippy::unwrap_used, reason = "test assertions")]
325mod tests {
326 use super::*;
327
328 fn account(pubkey: &str, name: Option<&str>) -> AccountInfo {
329 AccountInfo {
330 pubkey: pubkey.to_string(),
331 is_signer: false,
332 is_writable: false,
333 name: name.map(str::to_string),
334 }
335 }
336
337 fn make_event(fields: serde_json::Value) -> RawEvent {
338 RawEvent {
339 id: 1,
340 signature: "sig".to_string(),
341 event_index: 0,
342 program_id: "p".to_string(),
343 inner_program_id: "p".to_string(),
344 event_name: "test".to_string(),
345 fields: Some(fields),
346 slot: 1,
347 }
348 }
349
350 fn resolve(
351 fields: serde_json::Value,
352 ) -> Option<Result<(EventType, CorrelationOutcome, EventPayload), crate::error::Error>> {
353 let ev = make_event(fields);
354 let ctx = ResolveContext {
355 pre_fetched_order_pdas: None,
356 };
357 LimitV2Adapter.classify_and_resolve_event(&ev, &ctx)
358 }
359
360 #[test]
361 fn classify_known_instructions_via_envelope() {
362 let cases = [
363 ("InitializeOrder", Some(EventType::Created)),
364 ("PreFlashFillOrder", Some(EventType::FillInitiated)),
365 ("FlashFillOrder", Some(EventType::FillCompleted)),
366 ("CancelOrder", Some(EventType::Cancelled)),
367 ("UpdateFee", None),
368 ("WithdrawFee", None),
369 ("Unknown", None),
370 ];
371 for (name, expected) in cases {
372 let ix = RawInstruction {
373 id: 1,
374 signature: "sig".to_string(),
375 instruction_index: 0,
376 program_id: "p".to_string(),
377 inner_program_id: "p".to_string(),
378 instruction_name: name.to_string(),
379 accounts: None,
380 args: None,
381 slot: 1,
382 };
383 assert_eq!(
384 LimitV2Adapter.classify_instruction(&ix),
385 expected,
386 "mismatch for {name}"
387 );
388 }
389 }
390
391 #[test]
392 fn resolve_trade_event_from_envelope() {
393 let fields = serde_json::json!({
394 "TradeEvent": {
395 "order_key": "HkLZgYy93cEi3Fn96SvdWeJk8DNeHeU5wiNV5SeRLiJC",
396 "taker": "j1oeQoPeuEDmjvyMwBmCWexzCQup77kbKKxV59CnYbd",
397 "making_amount": 724_773_829_u64,
398 "taking_amount": 51_821_329_u64,
399 "remaining_making_amount": 89_147_181_051_u64,
400 "remaining_taking_amount": 6_374_023_074_u64
401 }
402 });
403 let (event_type, correlation, payload) = resolve(fields).unwrap().unwrap();
404 assert_eq!(event_type, EventType::FillCompleted);
405 let CorrelationOutcome::Correlated(pdas) = correlation else {
406 panic!("expected Correlated");
407 };
408 assert_eq!(pdas, vec!["HkLZgYy93cEi3Fn96SvdWeJk8DNeHeU5wiNV5SeRLiJC"]);
409 let EventPayload::LimitFill {
410 in_amount,
411 out_amount,
412 remaining_in_amount,
413 counterparty,
414 } = payload
415 else {
416 panic!("expected LimitFill");
417 };
418 assert_eq!(in_amount, 724_773_829);
419 assert_eq!(out_amount, 51_821_329);
420 assert_eq!(remaining_in_amount, 89_147_181_051);
421 assert_eq!(counterparty, "j1oeQoPeuEDmjvyMwBmCWexzCQup77kbKKxV59CnYbd");
422 }
423
424 #[test]
425 fn malformed_known_event_returns_error() {
426 let fields = serde_json::json!({
427 "TradeEvent": {
428 "order_key": "order",
429 "making_amount": "bad",
430 "taking_amount": 1_u64,
431 "remaining_making_amount": 0_u64,
432 "remaining_taking_amount": 0_u64
433 }
434 });
435 let result = resolve(fields).unwrap();
436 assert!(result.is_err());
437 }
438
439 #[test]
440 fn resolve_trade_event_rejects_amount_overflow() {
441 let fields = serde_json::json!({
442 "TradeEvent": {
443 "order_key": "order",
444 "making_amount": (i64::MAX as u64) + 1,
445 "taking_amount": 1_u64,
446 "remaining_making_amount": 0_u64,
447 "remaining_taking_amount": 0_u64
448 }
449 });
450 let result = resolve(fields).unwrap();
451 assert!(result.is_err());
452 }
453
454 #[test]
455 fn resolve_trade_event_defaults_missing_taker() {
456 let fields = serde_json::json!({
457 "TradeEvent": {
458 "order_key": "order",
459 "making_amount": 10_u64,
460 "taking_amount": 5_u64,
461 "remaining_making_amount": 1_u64,
462 "remaining_taking_amount": 0_u64
463 }
464 });
465 let (_, _, payload) = resolve(fields).unwrap().unwrap();
466 let EventPayload::LimitFill { counterparty, .. } = payload else {
467 panic!("expected LimitFill");
468 };
469 assert_eq!(counterparty, "unknown");
470 }
471
472 #[test]
473 fn unknown_event_returns_none() {
474 let fields = serde_json::json!({"UnknownEvent": {"some_field": 1}});
475 assert!(resolve(fields).is_none());
476 }
477
478 #[test]
479 fn parse_create_args_with_params_wrapper() {
480 let args = serde_json::json!({
481 "params": {
482 "making_amount": 1000_u64,
483 "taking_amount": 500_u64,
484 "unique_id": 42_u64,
485 "expired_at": 1_700_000_000_i64,
486 "fee_bps": 25_u16
487 }
488 });
489 let parsed = LimitV2Adapter::parse_create_args(&args).unwrap();
490 assert_eq!(parsed.making_amount, 1000);
491 assert_eq!(parsed.taking_amount, 500);
492 assert_eq!(parsed.unique_id, Some(42));
493 assert_eq!(parsed.expired_at, Some(1_700_000_000));
494 assert_eq!(parsed.fee_bps, Some(25));
495 }
496
497 #[test]
498 fn parse_create_args_without_params_wrapper() {
499 let args = serde_json::json!({
500 "making_amount": 2000_u64,
501 "taking_amount": 1000_u64
502 });
503 let parsed = LimitV2Adapter::parse_create_args(&args).unwrap();
504 assert_eq!(parsed.making_amount, 2000);
505 assert_eq!(parsed.taking_amount, 1000);
506 assert_eq!(parsed.unique_id, None);
507 assert_eq!(parsed.expired_at, None);
508 assert_eq!(parsed.fee_bps, None);
509 }
510
511 #[test]
512 fn parse_create_args_rejects_overflow_values() {
513 let args = serde_json::json!({
514 "making_amount": (i64::MAX as u64) + 1,
515 "taking_amount": 1_u64,
516 "unique_id": (i64::MAX as u64) + 1
517 });
518 assert!(LimitV2Adapter::parse_create_args(&args).is_err());
519 }
520
521 #[test]
522 fn parse_create_args_rejects_fee_bps_out_of_range() {
523 let args = serde_json::json!({
524 "making_amount": 1_u64,
525 "taking_amount": 1_u64,
526 "fee_bps": 65_535_u16
527 });
528 assert!(LimitV2Adapter::parse_create_args(&args).is_err());
529 }
530
531 #[test]
532 fn parse_create_args_rejects_malformed_payload() {
533 let args = serde_json::json!({
534 "making_amount": "bad",
535 "taking_amount": 1_u64
536 });
537 assert!(LimitV2Adapter::parse_create_args(&args).is_err());
538 }
539
540 #[test]
541 fn extract_order_pda_prefers_named_account() {
542 let accounts = vec![
543 account("idx1", None),
544 account("idx2", None),
545 account("named_order", Some("order")),
546 ];
547 let extracted = LimitV2Adapter::extract_order_pda(&accounts, "CancelOrder").unwrap();
548 assert_eq!(extracted, "named_order");
549 }
550
551 #[test]
552 fn extract_order_pda_uses_fallback_indexes() {
553 let init_accounts = vec![
554 account("0", None),
555 account("1", None),
556 account("init_idx2", None),
557 ];
558 assert_eq!(
559 LimitV2Adapter::extract_order_pda(&init_accounts, "InitializeOrder").unwrap(),
560 "init_idx2"
561 );
562
563 let pre_flash_accounts = vec![account("0", None), account("pre_flash_idx1", None)];
564 assert_eq!(
565 LimitV2Adapter::extract_order_pda(&pre_flash_accounts, "PreFlashFillOrder").unwrap(),
566 "pre_flash_idx1"
567 );
568 }
569
570 #[test]
571 fn extract_order_pda_rejects_unknown_instruction() {
572 let err = LimitV2Adapter::extract_order_pda(&[], "Unknown").unwrap_err();
573 let Error::Protocol { reason } = err else {
574 panic!("expected protocol error");
575 };
576 assert_eq!(reason, "unknown Limit v2 instruction: Unknown");
577 }
578
579 #[test]
580 fn extract_order_pda_rejects_out_of_bounds_index() {
581 let err = LimitV2Adapter::extract_order_pda(&[], "CancelOrder").unwrap_err();
582 let Error::Protocol { reason } = err else {
583 panic!("expected protocol error");
584 };
585 assert_eq!(
586 reason,
587 "Limit v2 account index 2 out of bounds for CancelOrder"
588 );
589 }
590
591 #[test]
592 fn extract_create_mints_prefers_named_accounts() {
593 let accounts = vec![
594 account("idx7", None),
595 account("idx8", None),
596 account("named_input", Some("input_mint")),
597 account("named_output", Some("output_mint")),
598 ];
599 let extracted = LimitV2Adapter::extract_create_mints(&accounts).unwrap();
600 assert_eq!(extracted.input_mint, "named_input");
601 assert_eq!(extracted.output_mint, "named_output");
602 }
603
604 #[test]
605 fn extract_create_mints_uses_fallback_indexes() {
606 let accounts = vec![
607 account("0", None),
608 account("1", None),
609 account("2", None),
610 account("3", None),
611 account("4", None),
612 account("5", None),
613 account("6", None),
614 account("fallback_input", None),
615 account("fallback_output", None),
616 ];
617 let extracted = LimitV2Adapter::extract_create_mints(&accounts).unwrap();
618 assert_eq!(extracted.input_mint, "fallback_input");
619 assert_eq!(extracted.output_mint, "fallback_output");
620 }
621
622 #[test]
623 fn extract_create_mints_rejects_missing_input_fallback_index() {
624 let err = LimitV2Adapter::extract_create_mints(&[])
625 .err()
626 .expect("expected error");
627 let Error::Protocol { reason } = err else {
628 panic!("expected protocol error");
629 };
630 assert_eq!(reason, "Limit v2 input_mint index 7 out of bounds");
631 }
632
633 #[test]
634 fn extract_create_mints_rejects_missing_output_fallback_index() {
635 let accounts = vec![
636 account("0", None),
637 account("1", None),
638 account("2", None),
639 account("3", None),
640 account("4", None),
641 account("5", None),
642 account("6", None),
643 account("fallback_input", None),
644 ];
645 let err = LimitV2Adapter::extract_create_mints(&accounts)
646 .err()
647 .expect("expected error");
648 let Error::Protocol { reason } = err else {
649 panic!("expected protocol error");
650 };
651 assert_eq!(reason, "Limit v2 output_mint index 8 out of bounds");
652 }
653
654 #[cfg(feature = "wasm")]
655 #[test]
656 fn instruction_constants_match_classify() {
657 for (name, expected) in INSTRUCTION_EVENT_TYPES {
658 let ix = RawInstruction {
659 id: 1,
660 signature: "sig".to_string(),
661 instruction_index: 0,
662 program_id: "p".to_string(),
663 inner_program_id: "p".to_string(),
664 instruction_name: name.to_string(),
665 accounts: None,
666 args: None,
667 slot: 1,
668 };
669 assert_eq!(
670 LimitV2Adapter.classify_instruction(&ix).as_ref(),
671 Some(expected),
672 "INSTRUCTION_EVENT_TYPES mismatch for {name}"
673 );
674 }
675 }
676
677 #[cfg(feature = "wasm")]
678 #[test]
679 fn event_constants_match_resolve() {
680 for (name, expected) in EVENT_EVENT_TYPES {
681 let fields = match *name {
682 "TradeEvent" => {
683 serde_json::json!({(*name): {"order_key": "t", "making_amount": 1_u64, "taking_amount": 1_u64, "remaining_making_amount": 0_u64, "remaining_taking_amount": 0_u64}})
684 }
685 _ => serde_json::json!({(*name): {"order_key": "t"}}),
686 };
687 let result = resolve(fields);
688 let (event_type, _, _) = result.expect("should return Some").expect("should be Ok");
689 assert_eq!(
690 &event_type, expected,
691 "EVENT_EVENT_TYPES mismatch for {name}"
692 );
693 }
694 }
695
696 #[test]
697 fn mirror_enums_cover_all_carbon_variants() {
698 let instruction_variants = [
699 "InitializeOrder",
700 "PreFlashFillOrder",
701 "FlashFillOrder",
702 "CancelOrder",
703 "UpdateFee",
704 "WithdrawFee",
705 ];
706 for name in instruction_variants {
707 let json = serde_json::json!({ name: serde_json::Value::Null });
708 assert!(
709 serde_json::from_value::<LimitV2InstructionKind>(json).is_ok(),
710 "LimitV2InstructionKind missing variant: {name}"
711 );
712 }
713
714 for name in ["CreateOrderEvent", "CancelOrderEvent"] {
715 let json = serde_json::json!({ name: { "order_key": "test" } });
716 assert!(
717 serde_json::from_value::<LimitV2EventEnvelope>(json).is_ok(),
718 "LimitV2EventEnvelope missing variant: {name}"
719 );
720 }
721
722 let trade = serde_json::json!({
723 "TradeEvent": { "order_key": "t", "making_amount": 1_u64, "taking_amount": 1_u64,
724 "remaining_making_amount": 0_u64, "remaining_taking_amount": 0_u64 }
725 });
726 assert!(serde_json::from_value::<LimitV2EventEnvelope>(trade).is_ok());
727 }
728}