decode.rs (42667B)
1 #[cfg(all(not(feature = "std"), feature = "json"))] 2 use alloc::{string::String, vec::Vec}; 3 4 #[cfg(feature = "json")] 5 use radroots_event::{ 6 envelope::EventEnvelope, 7 envelope::kind::is_order_event_kind, 8 id::{ClassifiedListingAddress, EventId, ParseError}, 9 tag::EventPtr, 10 tag::name::{TAG_D, TAG_E_PREV, TAG_E_ROOT}, 11 trade::order::{ 12 OrderCancellation, OrderDecision, OrderEnvelope, OrderEnvelopeError, OrderEventType, 13 OrderPayloadError, OrderRequest, 14 }, 15 }; 16 #[cfg(feature = "json")] 17 use radroots_identity::PublicKey; 18 #[cfg(feature = "json")] 19 use serde::de::DeserializeOwned; 20 21 #[cfg(feature = "json")] 22 use crate::order::tags::{ 23 TAG_LISTING_EVENT, parse_order_counterparty_tag, parse_order_listing_event_tag, 24 parse_order_prev_tag, parse_order_root_tag, 25 }; 26 27 #[cfg(feature = "json")] 28 #[derive(Clone, Debug, PartialEq, Eq)] 29 pub enum RadrootsOrderEnvelopeParseError { 30 InvalidKind(u32), 31 InvalidJson, 32 InvalidEnvelope(OrderEnvelopeError), 33 InvalidPayload(OrderPayloadError), 34 MessageTypeKindMismatch { 35 event_kind: u32, 36 message_type: OrderEventType, 37 }, 38 MissingTag(&'static str), 39 InvalidTag(&'static str), 40 ListingAddrTagMismatch, 41 OrderIdTagMismatch, 42 PayloadBindingMismatch(&'static str), 43 AuthorMismatch, 44 CounterpartyTagMismatch, 45 InvalidListingAddr(ParseError), 46 } 47 48 #[cfg(feature = "json")] 49 impl core::fmt::Display for RadrootsOrderEnvelopeParseError { 50 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 51 match self { 52 Self::InvalidKind(kind) => write!(f, "invalid order event kind: {kind}"), 53 Self::InvalidJson => write!(f, "invalid order envelope json"), 54 Self::InvalidEnvelope(error) => write!(f, "{error}"), 55 Self::InvalidPayload(error) => write!(f, "{error}"), 56 Self::MessageTypeKindMismatch { 57 event_kind, 58 message_type, 59 } => write!( 60 f, 61 "order envelope type {message_type:?} does not match event kind {event_kind}" 62 ), 63 Self::MissingTag(tag) => write!(f, "missing required order tag: {tag}"), 64 Self::InvalidTag(tag) => write!(f, "invalid order tag: {tag}"), 65 Self::ListingAddrTagMismatch => { 66 write!(f, "order listing address tag does not match envelope") 67 } 68 Self::OrderIdTagMismatch => { 69 write!(f, "order order id tag does not match envelope") 70 } 71 Self::PayloadBindingMismatch(field) => { 72 write!(f, "order payload {field} does not match envelope") 73 } 74 Self::AuthorMismatch => write!(f, "order event author does not match payload"), 75 Self::CounterpartyTagMismatch => { 76 write!(f, "order counterparty tag does not match payload") 77 } 78 Self::InvalidListingAddr(error) => write!(f, "{error}"), 79 } 80 } 81 } 82 83 #[cfg(all(feature = "std", feature = "json"))] 84 impl std::error::Error for RadrootsOrderEnvelopeParseError { 85 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 86 match self { 87 Self::InvalidEnvelope(error) => Some(error), 88 Self::InvalidPayload(error) => Some(error), 89 Self::InvalidListingAddr(error) => Some(error), 90 _ => None, 91 } 92 } 93 } 94 95 #[cfg(feature = "json")] 96 #[derive(Clone, Debug, PartialEq, Eq)] 97 pub struct RadrootsOrderEventContext { 98 pub counterparty_pubkey: PublicKey, 99 pub listing_event: Option<EventPtr>, 100 pub root_event_id: Option<EventId>, 101 pub prev_event_id: Option<EventId>, 102 } 103 104 #[cfg(feature = "json")] 105 pub fn order_envelope_from_event<T: DeserializeOwned>( 106 event: &EventEnvelope, 107 ) -> Result<OrderEnvelope<T>, RadrootsOrderEnvelopeParseError> { 108 let event_kind = event.kind_u32(); 109 let event_tags = event.tags_as_vec(); 110 if !is_order_event_kind(event_kind) { 111 return Err(RadrootsOrderEnvelopeParseError::InvalidKind(event_kind)); 112 } 113 let envelope = serde_json::from_str::<OrderEnvelope<T>>(event.content()) 114 .map_err(|_| RadrootsOrderEnvelopeParseError::InvalidJson)?; 115 envelope 116 .validate() 117 .map_err(RadrootsOrderEnvelopeParseError::InvalidEnvelope)?; 118 if envelope.message_type.kind() != event_kind { 119 return Err(RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { 120 event_kind, 121 message_type: envelope.message_type, 122 }); 123 } 124 125 let listing_addr = required_order_tag_value(&event_tags, "a")?; 126 if envelope.listing_addr != listing_addr { 127 return Err(RadrootsOrderEnvelopeParseError::ListingAddrTagMismatch); 128 } 129 ClassifiedListingAddress::parse(&envelope.listing_addr) 130 .map_err(RadrootsOrderEnvelopeParseError::InvalidListingAddr)?; 131 132 let tag_order_id = required_order_tag_value(&event_tags, TAG_D)?; 133 if tag_order_id != envelope.order_id { 134 return Err(RadrootsOrderEnvelopeParseError::OrderIdTagMismatch); 135 } 136 137 order_event_context_from_tags(envelope.message_type, &event_tags)?; 138 Ok(envelope) 139 } 140 141 #[cfg(feature = "json")] 142 pub fn order_request_from_event( 143 event: &EventEnvelope, 144 ) -> Result<OrderEnvelope<OrderRequest>, RadrootsOrderEnvelopeParseError> { 145 let envelope = order_envelope_from_event::<OrderRequest>(event)?; 146 if envelope.message_type != OrderEventType::OrderRequested { 147 return Err(RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { 148 event_kind: event.kind_u32(), 149 message_type: envelope.message_type, 150 }); 151 } 152 envelope 153 .payload 154 .validate() 155 .map_err(RadrootsOrderEnvelopeParseError::InvalidPayload)?; 156 validate_order_binding( 157 event, 158 &envelope, 159 envelope.payload.order_id.as_str(), 160 envelope.payload.listing_addr.as_str(), 161 &envelope.payload.buyer_pubkey, 162 &envelope.payload.seller_pubkey, 163 )?; 164 Ok(envelope) 165 } 166 167 #[cfg(feature = "json")] 168 pub fn order_decision_from_event( 169 event: &EventEnvelope, 170 ) -> Result<OrderEnvelope<OrderDecision>, RadrootsOrderEnvelopeParseError> { 171 let envelope = order_envelope_from_event::<OrderDecision>(event)?; 172 if envelope.message_type != OrderEventType::OrderDecision { 173 return Err(RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { 174 event_kind: event.kind_u32(), 175 message_type: envelope.message_type, 176 }); 177 } 178 envelope 179 .payload 180 .validate() 181 .map_err(RadrootsOrderEnvelopeParseError::InvalidPayload)?; 182 validate_order_binding( 183 event, 184 &envelope, 185 envelope.payload.order_id.as_str(), 186 envelope.payload.listing_addr.as_str(), 187 &envelope.payload.seller_pubkey, 188 &envelope.payload.buyer_pubkey, 189 )?; 190 Ok(envelope) 191 } 192 193 #[cfg(feature = "json")] 194 pub fn order_cancellation_from_event( 195 event: &EventEnvelope, 196 ) -> Result<OrderEnvelope<OrderCancellation>, RadrootsOrderEnvelopeParseError> { 197 let envelope = order_envelope_from_event::<OrderCancellation>(event)?; 198 if envelope.message_type != OrderEventType::OrderCancelled { 199 return Err(RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { 200 event_kind: event.kind_u32(), 201 message_type: envelope.message_type, 202 }); 203 } 204 envelope 205 .payload 206 .validate() 207 .map_err(RadrootsOrderEnvelopeParseError::InvalidPayload)?; 208 validate_order_binding( 209 event, 210 &envelope, 211 envelope.payload.order_id.as_str(), 212 envelope.payload.listing_addr.as_str(), 213 &envelope.payload.buyer_pubkey, 214 &envelope.payload.seller_pubkey, 215 )?; 216 Ok(envelope) 217 } 218 219 #[cfg(feature = "json")] 220 pub fn order_event_context_from_tags( 221 message_type: OrderEventType, 222 tags: &[Vec<String>], 223 ) -> Result<RadrootsOrderEventContext, RadrootsOrderEnvelopeParseError> { 224 let counterparty_pubkey = 225 parse_order_counterparty_tag(tags).map_err(map_tag_parse_error_for_order_envelope)?; 226 let counterparty_pubkey = PublicKey::from_hex(&counterparty_pubkey) 227 .map_err(|_| RadrootsOrderEnvelopeParseError::InvalidTag("p"))?; 228 let listing_event = 229 parse_order_listing_event_tag(tags).map_err(map_tag_parse_error_for_order_envelope)?; 230 let root_event_id = 231 parse_order_root_tag(tags).map_err(map_tag_parse_error_for_order_envelope)?; 232 let root_event_id = root_event_id 233 .map(|id| { 234 EventId::parse(id).map_err(|_| RadrootsOrderEnvelopeParseError::InvalidTag(TAG_E_ROOT)) 235 }) 236 .transpose()?; 237 let prev_event_id = 238 parse_order_prev_tag(tags).map_err(map_tag_parse_error_for_order_envelope)?; 239 let prev_event_id = prev_event_id 240 .map(|id| { 241 EventId::parse(id).map_err(|_| RadrootsOrderEnvelopeParseError::InvalidTag(TAG_E_PREV)) 242 }) 243 .transpose()?; 244 245 if message_type.requires_listing_snapshot() && listing_event.is_none() { 246 return Err(RadrootsOrderEnvelopeParseError::MissingTag( 247 TAG_LISTING_EVENT, 248 )); 249 } 250 if message_type.requires_order_chain() { 251 if root_event_id.is_none() { 252 return Err(RadrootsOrderEnvelopeParseError::MissingTag(TAG_E_ROOT)); 253 } 254 if prev_event_id.is_none() { 255 return Err(RadrootsOrderEnvelopeParseError::MissingTag(TAG_E_PREV)); 256 } 257 } 258 259 Ok(RadrootsOrderEventContext { 260 counterparty_pubkey, 261 listing_event, 262 root_event_id, 263 prev_event_id, 264 }) 265 } 266 267 #[cfg(feature = "json")] 268 fn required_order_tag_value<'a>( 269 tags: &'a [Vec<String>], 270 key: &'static str, 271 ) -> Result<&'a str, RadrootsOrderEnvelopeParseError> { 272 let tag = tags 273 .iter() 274 .find(|tag| tag.first().map(|value| value.as_str()) == Some(key)) 275 .ok_or(RadrootsOrderEnvelopeParseError::MissingTag(key))?; 276 let value = tag 277 .get(1) 278 .map(|value| value.as_str()) 279 .ok_or(RadrootsOrderEnvelopeParseError::InvalidTag(key))?; 280 if value.trim().is_empty() { 281 return Err(RadrootsOrderEnvelopeParseError::InvalidTag(key)); 282 } 283 Ok(value) 284 } 285 286 #[cfg(feature = "json")] 287 fn map_tag_parse_error_for_order_envelope( 288 error: crate::error::EventParseError, 289 ) -> RadrootsOrderEnvelopeParseError { 290 match error { 291 crate::error::EventParseError::MissingTag(tag) => { 292 RadrootsOrderEnvelopeParseError::MissingTag(tag) 293 } 294 crate::error::EventParseError::InvalidTag(tag) 295 | crate::error::EventParseError::DuplicateTag(tag) => { 296 RadrootsOrderEnvelopeParseError::InvalidTag(tag) 297 } 298 crate::error::EventParseError::InvalidKind { expected: _, got } => { 299 RadrootsOrderEnvelopeParseError::InvalidKind(got) 300 } 301 crate::error::EventParseError::InvalidNumber(tag, _) 302 | crate::error::EventParseError::InvalidJson(tag) => { 303 RadrootsOrderEnvelopeParseError::InvalidTag(tag) 304 } 305 crate::error::EventParseError::InvalidEnvelope => { 306 RadrootsOrderEnvelopeParseError::InvalidTag("event_envelope") 307 } 308 } 309 } 310 311 #[cfg(feature = "json")] 312 fn validate_order_binding<T>( 313 event: &EventEnvelope, 314 envelope: &OrderEnvelope<T>, 315 payload_order_id: &str, 316 payload_listing_addr: &str, 317 expected_author: &PublicKey, 318 expected_counterparty: &PublicKey, 319 ) -> Result<(), RadrootsOrderEnvelopeParseError> { 320 if envelope.order_id != payload_order_id { 321 return Err(RadrootsOrderEnvelopeParseError::PayloadBindingMismatch( 322 "order_id", 323 )); 324 } 325 if envelope.listing_addr != payload_listing_addr { 326 return Err(RadrootsOrderEnvelopeParseError::PayloadBindingMismatch( 327 "listing_addr", 328 )); 329 } 330 if event.author() != expected_author { 331 return Err(RadrootsOrderEnvelopeParseError::AuthorMismatch); 332 } 333 let context = order_event_context_from_tags(envelope.message_type, &event.tags_as_vec())?; 334 if &context.counterparty_pubkey != expected_counterparty { 335 return Err(RadrootsOrderEnvelopeParseError::CounterpartyTagMismatch); 336 } 337 Ok(()) 338 } 339 340 #[cfg(all(test, feature = "json"))] 341 mod tests { 342 use super::{ 343 RadrootsOrderEnvelopeParseError, map_tag_parse_error_for_order_envelope, 344 order_cancellation_from_event, order_decision_from_event, order_envelope_from_event, 345 order_event_context_from_tags, order_request_from_event, 346 }; 347 use crate::order::encode::{ 348 order_cancellation_event_build, order_decision_event_build, order_request_event_build, 349 }; 350 use crate::order::tags::TAG_LISTING_EVENT; 351 use radroots_core::{Currency, Decimal, Money, Unit}; 352 use radroots_event::{ 353 envelope::EventEnvelope, 354 envelope::EventEnvelopeParts, 355 envelope::kind::{KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION, KIND_ORDER_REQUEST}, 356 id::{ClassifiedListingAddress, EventId, InventoryBinId, OrderId, OrderQuoteId}, 357 tag::EventPtr, 358 tag::name::{TAG_D, TAG_E_PREV, TAG_E_ROOT}, 359 trade::order::{ 360 OrderCancellation, OrderDecision, OrderDecisionOutcome, OrderEconomicItem, 361 OrderEconomicLine, OrderEconomics, OrderEnvelope, OrderEnvelopeError, OrderEventType, 362 OrderInventoryCommitment, OrderItem, OrderPayloadError, OrderPricingBasis, 363 OrderRequest, 364 }, 365 }; 366 use radroots_identity::PublicKey; 367 368 fn pubkey(character: char) -> PublicKey { 369 crate::test_fixtures::fixture_public_key_hex(character) 370 .parse() 371 .unwrap() 372 } 373 374 fn buyer_pubkey() -> PublicKey { 375 pubkey('b') 376 } 377 378 fn seller_pubkey() -> PublicKey { 379 pubkey('a') 380 } 381 382 fn buyer_pubkey_wire() -> String { 383 buyer_pubkey().to_hex() 384 } 385 386 fn seller_pubkey_wire() -> String { 387 seller_pubkey().to_hex() 388 } 389 390 fn listing_addr() -> ClassifiedListingAddress { 391 format!("30402:{}:AAAAAAAAAAAAAAAAAAAAAg", seller_pubkey_wire()) 392 .parse() 393 .unwrap() 394 } 395 396 fn listing_addr_wire() -> String { 397 listing_addr().into_string() 398 } 399 400 fn order_id(raw: &str) -> OrderId { 401 raw.parse().unwrap() 402 } 403 404 fn quote_id(raw: &str) -> OrderQuoteId { 405 raw.parse().unwrap() 406 } 407 408 fn bin_id(raw: &str) -> InventoryBinId { 409 raw.parse().unwrap() 410 } 411 412 fn event_id(character: char) -> EventId { 413 core::iter::repeat_n(character, 64) 414 .collect::<String>() 415 .parse() 416 .unwrap() 417 } 418 419 fn event_id_wire(character: char) -> String { 420 event_id(character).into_string() 421 } 422 423 fn event_signature_wire() -> String { 424 core::iter::repeat_n('f', 128).collect() 425 } 426 427 fn event_envelope( 428 author: String, 429 kind: u32, 430 tags: Vec<Vec<String>>, 431 content: String, 432 ) -> EventEnvelope { 433 EventEnvelope::new(EventEnvelopeParts { 434 id: event_id_wire('e'), 435 author, 436 created_at: 1, 437 kind, 438 tags, 439 content, 440 sig: event_signature_wire(), 441 }) 442 .unwrap() 443 } 444 445 fn order_request() -> OrderRequest { 446 OrderRequest { 447 order_id: order_id("order-1"), 448 listing_addr: listing_addr(), 449 buyer_pubkey: buyer_pubkey(), 450 seller_pubkey: seller_pubkey(), 451 items: vec![OrderItem { 452 bin_id: bin_id("lb"), 453 bin_count: 3, 454 }], 455 economics: request_economics(), 456 } 457 } 458 459 fn decimal(raw: &str) -> Decimal { 460 raw.parse().unwrap() 461 } 462 463 fn usd(raw: &str) -> Money { 464 Money::try_new(decimal(raw), Currency::USD).unwrap() 465 } 466 467 fn request_economics() -> OrderEconomics { 468 OrderEconomics { 469 quote_id: quote_id("quote-1"), 470 quote_version: 1, 471 pricing_basis: OrderPricingBasis::ListingEvent, 472 currency: Currency::USD, 473 items: vec![OrderEconomicItem { 474 bin_id: bin_id("lb"), 475 bin_count: 3, 476 quantity_amount: decimal("1"), 477 quantity_unit: Unit::Each, 478 unit_price_amount: decimal("5"), 479 unit_price_currency: Currency::USD, 480 line_subtotal: usd("15"), 481 }], 482 discounts: Vec::<OrderEconomicLine>::new(), 483 adjustments: Vec::<OrderEconomicLine>::new(), 484 subtotal: usd("15"), 485 discount_total: usd("0"), 486 adjustment_total: usd("0"), 487 total: usd("15"), 488 } 489 } 490 491 fn order_decision() -> OrderDecision { 492 OrderDecision { 493 order_id: order_id("order-1"), 494 listing_addr: listing_addr(), 495 buyer_pubkey: buyer_pubkey(), 496 seller_pubkey: seller_pubkey(), 497 decision: OrderDecisionOutcome::Accepted { 498 inventory_commitments: vec![OrderInventoryCommitment { 499 bin_id: bin_id("lb"), 500 bin_count: 3, 501 }], 502 }, 503 } 504 } 505 506 fn order_cancelled() -> OrderCancellation { 507 OrderCancellation { 508 order_id: order_id("order-1"), 509 listing_addr: listing_addr(), 510 buyer_pubkey: buyer_pubkey(), 511 seller_pubkey: seller_pubkey(), 512 reason: "changed plans".into(), 513 } 514 } 515 516 fn listing_event_ptr() -> EventPtr { 517 EventPtr { 518 id: event_id_wire('a'), 519 relays: Some("wss://relay.example.com".into()), 520 } 521 } 522 523 fn order_request_tags() -> Vec<Vec<String>> { 524 vec![ 525 vec!["p".into(), seller_pubkey_wire()], 526 vec!["a".into(), listing_addr_wire()], 527 vec![TAG_D.into(), "order-1".into()], 528 vec![TAG_LISTING_EVENT.into(), event_id_wire('a')], 529 ] 530 } 531 532 fn order_chain_tags(counterparty_pubkey: String) -> Vec<Vec<String>> { 533 vec![ 534 vec!["p".into(), counterparty_pubkey], 535 vec!["a".into(), listing_addr_wire()], 536 vec![TAG_D.into(), "order-1".into()], 537 vec![TAG_E_ROOT.into(), event_id_wire('1')], 538 vec![TAG_E_PREV.into(), event_id_wire('2')], 539 ] 540 } 541 542 fn order_event_with_envelope<T: serde::Serialize>( 543 kind: u32, 544 author: String, 545 message_type: OrderEventType, 546 listing_addr: impl Into<String>, 547 order_id: impl Into<String>, 548 payload: &T, 549 tags: Vec<Vec<String>>, 550 ) -> EventEnvelope { 551 let envelope = OrderEnvelope::new(message_type, listing_addr, order_id, payload); 552 event_envelope( 553 author, 554 kind, 555 tags, 556 serde_json::to_string(&envelope).unwrap(), 557 ) 558 } 559 560 #[test] 561 fn listing_address_roundtrips() { 562 let raw = format!("30402:{}:listing-1", seller_pubkey_wire()); 563 let addr = ClassifiedListingAddress::parse(&raw).expect("parse listing address"); 564 assert_eq!(addr.as_str(), raw); 565 } 566 567 #[test] 568 fn order_request_builder_emits_canonical_shape() { 569 let payload = order_request(); 570 let built = order_request_event_build(&listing_event_ptr(), &payload).unwrap(); 571 let envelope: OrderEnvelope<OrderRequest> = serde_json::from_str(&built.content).unwrap(); 572 573 assert_eq!(built.kind, KIND_ORDER_REQUEST); 574 assert_eq!(envelope.message_type, OrderEventType::OrderRequested); 575 assert_eq!(envelope.order_id, "order-1"); 576 assert_eq!(built.tags[0], vec!["p".to_string(), seller_pubkey_wire()]); 577 assert_eq!(built.tags[1], vec!["a".to_string(), listing_addr_wire()]); 578 assert_eq!( 579 built.tags[2], 580 vec![TAG_D.to_string(), "order-1".to_string()] 581 ); 582 assert_eq!(envelope.payload.economics.quote_id.as_str(), "quote-1"); 583 assert_eq!(envelope.payload.economics.total, usd("15")); 584 assert!( 585 built 586 .tags 587 .iter() 588 .any(|tag| tag.first().map(String::as_str) == Some(TAG_LISTING_EVENT)) 589 ); 590 assert!( 591 !built 592 .tags 593 .iter() 594 .any(|tag| tag.first().map(String::as_str) == Some(TAG_E_ROOT)) 595 ); 596 } 597 598 #[test] 599 fn order_decision_builder_emits_canonical_chain_shape() { 600 let payload = order_decision(); 601 let root_event_id = event_id('1'); 602 let prev_event_id = event_id('9'); 603 let built = order_decision_event_build(&root_event_id, &prev_event_id, &payload).unwrap(); 604 let envelope: OrderEnvelope<OrderDecision> = serde_json::from_str(&built.content).unwrap(); 605 606 assert_eq!(built.kind, KIND_ORDER_DECISION); 607 assert_eq!(envelope.message_type, OrderEventType::OrderDecision); 608 assert_eq!(built.tags[0], vec!["p".to_string(), buyer_pubkey_wire()]); 609 assert_eq!( 610 built.tags[2], 611 vec![TAG_D.to_string(), "order-1".to_string()] 612 ); 613 assert!( 614 built 615 .tags 616 .iter() 617 .any(|tag| tag == &vec![TAG_E_ROOT.to_string(), event_id_wire('1')]) 618 ); 619 assert!( 620 built 621 .tags 622 .iter() 623 .any(|tag| tag == &vec![TAG_E_PREV.to_string(), event_id_wire('9')]) 624 ); 625 } 626 627 #[test] 628 fn order_cancellation_builder_emits_canonical_buyer_chain_shape() { 629 let payload = order_cancelled(); 630 let root_event_id = event_id('1'); 631 let prev_event_id = event_id('9'); 632 let built = 633 order_cancellation_event_build(&root_event_id, &prev_event_id, &payload).unwrap(); 634 let envelope: OrderEnvelope<OrderCancellation> = 635 serde_json::from_str(&built.content).unwrap(); 636 637 assert_eq!(built.kind, KIND_ORDER_CANCELLATION); 638 assert_eq!(envelope.message_type, OrderEventType::OrderCancelled); 639 assert_eq!(envelope.payload.reason, payload.reason); 640 assert_eq!(built.tags[0], vec!["p".to_string(), seller_pubkey_wire()]); 641 assert_eq!( 642 built.tags[2], 643 vec![TAG_D.to_string(), "order-1".to_string()] 644 ); 645 assert!( 646 built 647 .tags 648 .iter() 649 .any(|tag| tag == &vec![TAG_E_ROOT.to_string(), event_id_wire('1')]) 650 ); 651 assert!( 652 built 653 .tags 654 .iter() 655 .any(|tag| tag == &vec![TAG_E_PREV.to_string(), event_id_wire('9')]) 656 ); 657 } 658 659 #[test] 660 fn order_request_parse_roundtrips_and_validates_tags() { 661 let payload = order_request(); 662 let built = order_request_event_build(&listing_event_ptr(), &payload).unwrap(); 663 let event = event_envelope(buyer_pubkey_wire(), built.kind, built.tags, built.content); 664 let envelope = order_request_from_event(&event).unwrap(); 665 666 assert_eq!(envelope.payload, payload); 667 assert_eq!(envelope.message_type, OrderEventType::OrderRequested); 668 } 669 670 #[test] 671 fn order_request_parse_rejects_mismatched_economics() { 672 let mut payload = order_request(); 673 let built = order_request_event_build(&listing_event_ptr(), &payload).unwrap(); 674 payload.economics.items[0].bin_id = bin_id("other-bin"); 675 let envelope = OrderEnvelope::new( 676 OrderEventType::OrderRequested, 677 payload.listing_addr.clone(), 678 payload.order_id.clone(), 679 payload, 680 ); 681 let event = event_envelope( 682 buyer_pubkey_wire(), 683 built.kind, 684 built.tags, 685 serde_json::to_string(&envelope).unwrap(), 686 ); 687 let err = order_request_from_event(&event).unwrap_err(); 688 assert_eq!( 689 err, 690 RadrootsOrderEnvelopeParseError::InvalidPayload( 691 OrderPayloadError::InvalidOrderEconomicsBinding { 692 field: "items.bin_id" 693 } 694 ) 695 ); 696 } 697 698 #[test] 699 fn order_decision_parse_roundtrips_and_validates_chain_tags() { 700 let payload = order_decision(); 701 let root_event_id = event_id('1'); 702 let prev_event_id = event_id('9'); 703 let built = order_decision_event_build(&root_event_id, &prev_event_id, &payload).unwrap(); 704 let event = event_envelope(seller_pubkey_wire(), built.kind, built.tags, built.content); 705 let envelope = order_decision_from_event(&event).unwrap(); 706 707 assert_eq!(envelope.payload, payload); 708 assert_eq!(envelope.message_type, OrderEventType::OrderDecision); 709 } 710 711 #[test] 712 fn order_cancellation_parse_roundtrips_and_validates_buyer_actor() { 713 let payload = order_cancelled(); 714 let root_event_id = event_id('1'); 715 let prev_event_id = event_id('9'); 716 let built = 717 order_cancellation_event_build(&root_event_id, &prev_event_id, &payload).unwrap(); 718 let event = event_envelope(buyer_pubkey_wire(), built.kind, built.tags, built.content); 719 let envelope = order_cancellation_from_event(&event).unwrap(); 720 721 assert_eq!(envelope.payload, payload); 722 assert_eq!(envelope.message_type, OrderEventType::OrderCancelled); 723 } 724 725 #[cfg(feature = "std")] 726 #[test] 727 fn order_parse_error_display_and_source_cover_variants() { 728 use std::error::Error as _; 729 730 let invalid_envelope = 731 RadrootsOrderEnvelopeParseError::InvalidEnvelope(OrderEnvelopeError::MissingOrderId); 732 let invalid_payload = 733 RadrootsOrderEnvelopeParseError::InvalidPayload(OrderPayloadError::MissingItems); 734 let invalid_listing_addr = RadrootsOrderEnvelopeParseError::InvalidListingAddr( 735 ClassifiedListingAddress::parse("not-a-listing-address").unwrap_err(), 736 ); 737 let errors = [ 738 RadrootsOrderEnvelopeParseError::InvalidKind(3431), 739 RadrootsOrderEnvelopeParseError::InvalidJson, 740 invalid_envelope.clone(), 741 invalid_payload.clone(), 742 RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { 743 event_kind: KIND_ORDER_REQUEST, 744 message_type: OrderEventType::OrderDecision, 745 }, 746 RadrootsOrderEnvelopeParseError::MissingTag("a"), 747 RadrootsOrderEnvelopeParseError::InvalidTag("p"), 748 RadrootsOrderEnvelopeParseError::ListingAddrTagMismatch, 749 RadrootsOrderEnvelopeParseError::OrderIdTagMismatch, 750 RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("order_id"), 751 RadrootsOrderEnvelopeParseError::AuthorMismatch, 752 RadrootsOrderEnvelopeParseError::CounterpartyTagMismatch, 753 invalid_listing_addr.clone(), 754 ]; 755 756 for error in errors { 757 assert!(!error.to_string().is_empty()); 758 } 759 assert!(invalid_envelope.source().is_some()); 760 assert!(invalid_payload.source().is_some()); 761 assert!(invalid_listing_addr.source().is_some()); 762 assert!( 763 RadrootsOrderEnvelopeParseError::AuthorMismatch 764 .source() 765 .is_none() 766 ); 767 } 768 769 #[test] 770 fn order_envelope_parse_rejects_content_tag_and_envelope_mismatches() { 771 let payload = serde_json::json!({}); 772 let invalid_json = event_envelope( 773 buyer_pubkey_wire(), 774 KIND_ORDER_REQUEST, 775 Vec::new(), 776 "{".into(), 777 ); 778 assert_eq!( 779 order_envelope_from_event::<serde_json::Value>(&invalid_json).unwrap_err(), 780 RadrootsOrderEnvelopeParseError::InvalidJson 781 ); 782 783 let mut invalid_version_envelope = OrderEnvelope::new( 784 OrderEventType::OrderRequested, 785 listing_addr_wire(), 786 "order-1", 787 &payload, 788 ); 789 invalid_version_envelope.version = 99; 790 let invalid_version = event_envelope( 791 buyer_pubkey_wire(), 792 KIND_ORDER_REQUEST, 793 order_request_tags(), 794 serde_json::to_string(&invalid_version_envelope).unwrap(), 795 ); 796 assert!(matches!( 797 order_envelope_from_event::<serde_json::Value>(&invalid_version).unwrap_err(), 798 RadrootsOrderEnvelopeParseError::InvalidEnvelope( 799 OrderEnvelopeError::InvalidVersion { .. } 800 ) 801 )); 802 803 let message_type_mismatch = order_event_with_envelope( 804 KIND_ORDER_REQUEST, 805 buyer_pubkey_wire(), 806 OrderEventType::OrderDecision, 807 listing_addr_wire(), 808 "order-1", 809 &payload, 810 Vec::new(), 811 ); 812 assert_eq!( 813 order_envelope_from_event::<serde_json::Value>(&message_type_mismatch).unwrap_err(), 814 RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { 815 event_kind: KIND_ORDER_REQUEST, 816 message_type: OrderEventType::OrderDecision 817 } 818 ); 819 820 let listing_addr_mismatch = order_event_with_envelope( 821 KIND_ORDER_REQUEST, 822 buyer_pubkey_wire(), 823 OrderEventType::OrderRequested, 824 listing_addr_wire(), 825 "order-1", 826 &payload, 827 vec![ 828 vec!["a".into(), "30402:pubkey:AAAAAAAAAAAAAAAAAAAAAg".into()], 829 vec![TAG_D.into(), "order-1".into()], 830 ], 831 ); 832 assert_eq!( 833 order_envelope_from_event::<serde_json::Value>(&listing_addr_mismatch).unwrap_err(), 834 RadrootsOrderEnvelopeParseError::ListingAddrTagMismatch 835 ); 836 837 let order_id_mismatch = order_event_with_envelope( 838 KIND_ORDER_REQUEST, 839 buyer_pubkey_wire(), 840 OrderEventType::OrderRequested, 841 listing_addr_wire(), 842 "order-1", 843 &payload, 844 vec![ 845 vec!["a".into(), listing_addr_wire()], 846 vec![TAG_D.into(), "other-order".into()], 847 ], 848 ); 849 assert_eq!( 850 order_envelope_from_event::<serde_json::Value>(&order_id_mismatch).unwrap_err(), 851 RadrootsOrderEnvelopeParseError::OrderIdTagMismatch 852 ); 853 854 for tags in [ 855 Vec::<Vec<String>>::new(), 856 vec![vec!["a".into()]], 857 vec![vec!["a".into(), " ".into()]], 858 ] { 859 let event = order_event_with_envelope( 860 KIND_ORDER_REQUEST, 861 buyer_pubkey_wire(), 862 OrderEventType::OrderRequested, 863 listing_addr_wire(), 864 "order-1", 865 &payload, 866 tags, 867 ); 868 let err = order_envelope_from_event::<serde_json::Value>(&event).unwrap_err(); 869 assert!(matches!( 870 err, 871 RadrootsOrderEnvelopeParseError::MissingTag("a") 872 | RadrootsOrderEnvelopeParseError::InvalidTag("a") 873 )); 874 } 875 876 let invalid_listing_addr = order_event_with_envelope( 877 KIND_ORDER_REQUEST, 878 buyer_pubkey_wire(), 879 OrderEventType::OrderRequested, 880 "not-a-listing-address", 881 "order-1", 882 &payload, 883 vec![ 884 vec!["a".into(), "not-a-listing-address".into()], 885 vec![TAG_D.into(), "order-1".into()], 886 ], 887 ); 888 assert!(matches!( 889 order_envelope_from_event::<serde_json::Value>(&invalid_listing_addr).unwrap_err(), 890 RadrootsOrderEnvelopeParseError::InvalidListingAddr(_) 891 )); 892 } 893 894 #[test] 895 fn order_typed_parsers_reject_message_type_mismatches() { 896 let request_payload = order_request(); 897 let decision_payload = order_decision(); 898 let cancellation_payload = order_cancelled(); 899 900 let request_as_decision = order_event_with_envelope( 901 KIND_ORDER_DECISION, 902 buyer_pubkey_wire(), 903 OrderEventType::OrderDecision, 904 listing_addr_wire(), 905 "order-1", 906 &request_payload, 907 order_chain_tags(seller_pubkey_wire()), 908 ); 909 assert!(matches!( 910 order_request_from_event(&request_as_decision).unwrap_err(), 911 RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { .. } 912 )); 913 914 let decision_as_request = order_event_with_envelope( 915 KIND_ORDER_REQUEST, 916 seller_pubkey_wire(), 917 OrderEventType::OrderRequested, 918 listing_addr_wire(), 919 "order-1", 920 &decision_payload, 921 order_request_tags(), 922 ); 923 assert!(matches!( 924 order_decision_from_event(&decision_as_request).unwrap_err(), 925 RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { .. } 926 )); 927 928 let cancellation_as_decision = order_event_with_envelope( 929 KIND_ORDER_DECISION, 930 buyer_pubkey_wire(), 931 OrderEventType::OrderDecision, 932 listing_addr_wire(), 933 "order-1", 934 &cancellation_payload, 935 order_chain_tags(seller_pubkey_wire()), 936 ); 937 assert!(matches!( 938 order_cancellation_from_event(&cancellation_as_decision).unwrap_err(), 939 RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { .. } 940 )); 941 } 942 943 #[test] 944 #[cfg_attr(coverage_nightly, coverage(off))] 945 fn order_parse_rejects_payload_and_chain_binding_mismatches() { 946 let mut request_payload = order_request(); 947 request_payload.order_id = order_id("other-order"); 948 let request_built = 949 order_request_event_build(&listing_event_ptr(), &order_request()).unwrap(); 950 let request_event = event_envelope( 951 buyer_pubkey_wire(), 952 request_built.kind, 953 request_built.tags.clone(), 954 serde_json::to_string(&OrderEnvelope::new( 955 OrderEventType::OrderRequested, 956 listing_addr_wire(), 957 "order-1", 958 &request_payload, 959 )) 960 .unwrap(), 961 ); 962 assert_eq!( 963 order_request_from_event(&request_event).unwrap_err(), 964 RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("order_id") 965 ); 966 967 request_payload = order_request(); 968 request_payload.listing_addr = 969 format!("30402:{}:BBBBBBBBBBBBBBBBBBBBBA", seller_pubkey_wire()) 970 .parse() 971 .unwrap(); 972 let request_event = event_envelope( 973 buyer_pubkey_wire(), 974 request_built.kind, 975 request_built.tags, 976 serde_json::to_string(&OrderEnvelope::new( 977 OrderEventType::OrderRequested, 978 listing_addr_wire(), 979 "order-1", 980 &request_payload, 981 )) 982 .unwrap(), 983 ); 984 assert_eq!( 985 order_request_from_event(&request_event).unwrap_err(), 986 RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("listing_addr") 987 ); 988 989 let mut decision_payload = order_decision(); 990 decision_payload.order_id = order_id("other-order"); 991 let decision_built = 992 order_decision_event_build(&event_id('1'), &event_id('9'), &order_decision()).unwrap(); 993 let decision_event = event_envelope( 994 seller_pubkey_wire(), 995 decision_built.kind, 996 decision_built.tags, 997 serde_json::to_string(&OrderEnvelope::new( 998 OrderEventType::OrderDecision, 999 listing_addr_wire(), 1000 "order-1", 1001 &decision_payload, 1002 )) 1003 .unwrap(), 1004 ); 1005 assert_eq!( 1006 order_decision_from_event(&decision_event).unwrap_err(), 1007 RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("order_id") 1008 ); 1009 } 1010 1011 #[test] 1012 #[cfg_attr(coverage_nightly, coverage(off))] 1013 fn order_event_context_and_parse_error_mapping_cover_missing_context() { 1014 let err = order_event_context_from_tags( 1015 OrderEventType::OrderRequested, 1016 &[vec!["p".into(), seller_pubkey_wire()]], 1017 ) 1018 .unwrap_err(); 1019 assert_eq!( 1020 err, 1021 RadrootsOrderEnvelopeParseError::MissingTag(TAG_LISTING_EVENT) 1022 ); 1023 1024 let err = order_event_context_from_tags( 1025 OrderEventType::OrderDecision, 1026 &[ 1027 vec!["p".into(), buyer_pubkey_wire()], 1028 vec![TAG_E_PREV.into(), event_id_wire('2')], 1029 ], 1030 ) 1031 .unwrap_err(); 1032 assert_eq!(err, RadrootsOrderEnvelopeParseError::MissingTag(TAG_E_ROOT)); 1033 1034 let err = order_event_context_from_tags( 1035 OrderEventType::OrderDecision, 1036 &[ 1037 vec!["p".into(), buyer_pubkey_wire()], 1038 vec![TAG_E_ROOT.into(), event_id_wire('1')], 1039 vec![TAG_E_PREV.into(), "not-an-event-id".into()], 1040 ], 1041 ) 1042 .unwrap_err(); 1043 assert_eq!(err, RadrootsOrderEnvelopeParseError::InvalidTag(TAG_E_PREV)); 1044 1045 let invalid_number = "x".parse::<u32>().unwrap_err(); 1046 assert_eq!( 1047 map_tag_parse_error_for_order_envelope(crate::error::EventParseError::MissingTag("p")), 1048 RadrootsOrderEnvelopeParseError::MissingTag("p") 1049 ); 1050 assert_eq!( 1051 map_tag_parse_error_for_order_envelope(crate::error::EventParseError::InvalidTag("p")), 1052 RadrootsOrderEnvelopeParseError::InvalidTag("p") 1053 ); 1054 assert_eq!( 1055 map_tag_parse_error_for_order_envelope(crate::error::EventParseError::DuplicateTag( 1056 "p", 1057 )), 1058 RadrootsOrderEnvelopeParseError::InvalidTag("p") 1059 ); 1060 assert_eq!( 1061 map_tag_parse_error_for_order_envelope(crate::error::EventParseError::InvalidKind { 1062 expected: "1", 1063 got: 2, 1064 }), 1065 RadrootsOrderEnvelopeParseError::InvalidKind(2) 1066 ); 1067 assert_eq!( 1068 map_tag_parse_error_for_order_envelope(crate::error::EventParseError::InvalidNumber( 1069 "n", 1070 invalid_number, 1071 )), 1072 RadrootsOrderEnvelopeParseError::InvalidTag("n") 1073 ); 1074 assert_eq!( 1075 map_tag_parse_error_for_order_envelope(crate::error::EventParseError::InvalidJson( 1076 "json", 1077 )), 1078 RadrootsOrderEnvelopeParseError::InvalidTag("json") 1079 ); 1080 assert_eq!( 1081 map_tag_parse_error_for_order_envelope(crate::error::EventParseError::InvalidEnvelope), 1082 RadrootsOrderEnvelopeParseError::InvalidTag("event_envelope") 1083 ); 1084 } 1085 1086 #[test] 1087 fn order_parse_rejects_forbidden_kind() { 1088 let event = event_envelope(seller_pubkey_wire(), 3431, Vec::new(), "{}".into()); 1089 let err = order_envelope_from_event::<serde_json::Value>(&event).unwrap_err(); 1090 assert_eq!(err, RadrootsOrderEnvelopeParseError::InvalidKind(3431)); 1091 } 1092 1093 #[test] 1094 fn order_parse_rejects_missing_required_refs() { 1095 let payload = order_decision(); 1096 let root_event_id = event_id('1'); 1097 let prev_event_id = event_id('9'); 1098 let built = order_decision_event_build(&root_event_id, &prev_event_id, &payload).unwrap(); 1099 let mut tags = built.tags; 1100 tags.retain(|tag| tag.first().map(String::as_str) != Some(TAG_E_PREV)); 1101 let event = event_envelope(seller_pubkey_wire(), built.kind, tags, built.content); 1102 1103 let err = order_decision_from_event(&event).unwrap_err(); 1104 assert_eq!(err, RadrootsOrderEnvelopeParseError::MissingTag(TAG_E_PREV)); 1105 } 1106 1107 #[test] 1108 fn order_parse_rejects_author_and_counterparty_mismatch() { 1109 let payload = order_request(); 1110 let built = order_request_event_build(&listing_event_ptr(), &payload).unwrap(); 1111 let event = event_envelope( 1112 seller_pubkey_wire(), 1113 built.kind, 1114 built.tags.clone(), 1115 built.content.clone(), 1116 ); 1117 let err = order_request_from_event(&event).unwrap_err(); 1118 assert_eq!(err, RadrootsOrderEnvelopeParseError::AuthorMismatch); 1119 1120 let mut tags = built.tags; 1121 tags[0] = vec!["p".into(), pubkey('c').to_hex()]; 1122 let counterparty_mismatch = 1123 event_envelope(buyer_pubkey_wire(), built.kind, tags, built.content); 1124 let err = order_request_from_event(&counterparty_mismatch).unwrap_err(); 1125 assert_eq!( 1126 err, 1127 RadrootsOrderEnvelopeParseError::CounterpartyTagMismatch 1128 ); 1129 } 1130 1131 #[test] 1132 fn order_cancellation_parse_rejects_wrong_actor() { 1133 let cancellation = order_cancelled(); 1134 let root_event_id = event_id('1'); 1135 let prev_event_id = event_id('9'); 1136 let cancellation_parts = 1137 order_cancellation_event_build(&root_event_id, &prev_event_id, &cancellation).unwrap(); 1138 let cancellation_event = event_envelope( 1139 seller_pubkey_wire(), 1140 cancellation_parts.kind, 1141 cancellation_parts.tags, 1142 cancellation_parts.content, 1143 ); 1144 let err = order_cancellation_from_event(&cancellation_event).unwrap_err(); 1145 assert_eq!(err, RadrootsOrderEnvelopeParseError::AuthorMismatch); 1146 } 1147 1148 #[test] 1149 fn order_parse_rejects_invalid_protocol_tag_values() { 1150 let payload = order_decision(); 1151 let root_event_id = event_id('1'); 1152 let prev_event_id = event_id('9'); 1153 let built = order_decision_event_build(&root_event_id, &prev_event_id, &payload).unwrap(); 1154 let mut tags = built.tags.clone(); 1155 tags[0] = vec!["p".into(), "not-a-pubkey".into()]; 1156 let invalid_counterparty = event_envelope( 1157 seller_pubkey_wire(), 1158 built.kind, 1159 tags, 1160 built.content.clone(), 1161 ); 1162 let err = order_decision_from_event(&invalid_counterparty).unwrap_err(); 1163 assert_eq!(err, RadrootsOrderEnvelopeParseError::InvalidTag("p")); 1164 1165 let mut tags = built.tags; 1166 tags[0] = vec!["p".into(), buyer_pubkey_wire()]; 1167 let root_tag = tags 1168 .iter_mut() 1169 .find(|tag| tag.first().map(String::as_str) == Some(TAG_E_ROOT)) 1170 .unwrap(); 1171 root_tag[1] = "not-an-event-id".into(); 1172 let invalid_root = event_envelope(seller_pubkey_wire(), built.kind, tags, built.content); 1173 let err = order_decision_from_event(&invalid_root).unwrap_err(); 1174 assert_eq!(err, RadrootsOrderEnvelopeParseError::InvalidTag(TAG_E_ROOT)); 1175 } 1176 }