decode.rs (59981B)
1 #[cfg(all(not(feature = "std"), feature = "serde_json"))] 2 use alloc::{borrow::ToOwned, format, string::String, vec::Vec}; 3 4 #[cfg(feature = "serde_json")] 5 use radroots_events::{ 6 RadrootsNostrEvent, RadrootsNostrEventPtr, 7 ids::{RadrootsEventId, RadrootsIdParseError, RadrootsListingAddress, RadrootsPublicKey}, 8 kinds::is_order_event_kind, 9 order::{ 10 RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderEnvelope, 11 RadrootsOrderEnvelopeError, RadrootsOrderEventType, RadrootsOrderPayloadError, 12 RadrootsOrderRequest, RadrootsOrderRevisionDecision, RadrootsOrderRevisionProposal, 13 }, 14 tags::{TAG_D, TAG_E_PREV, TAG_E_ROOT}, 15 }; 16 #[cfg(feature = "serde_json")] 17 use serde::de::DeserializeOwned; 18 19 #[cfg(feature = "serde_json")] 20 use crate::order::tags::{ 21 TAG_LISTING_EVENT, parse_order_counterparty_tag, parse_order_listing_event_tag, 22 parse_order_prev_tag, parse_order_root_tag, 23 }; 24 25 #[cfg(feature = "serde_json")] 26 #[derive(Clone, Debug, PartialEq, Eq)] 27 pub enum RadrootsOrderEnvelopeParseError { 28 InvalidKind(u32), 29 InvalidJson, 30 InvalidEnvelope(RadrootsOrderEnvelopeError), 31 InvalidPayload(RadrootsOrderPayloadError), 32 MessageTypeKindMismatch { 33 event_kind: u32, 34 message_type: RadrootsOrderEventType, 35 }, 36 MissingTag(&'static str), 37 InvalidTag(&'static str), 38 ListingAddrTagMismatch, 39 OrderIdTagMismatch, 40 PayloadBindingMismatch(&'static str), 41 AuthorMismatch, 42 CounterpartyTagMismatch, 43 InvalidListingAddr(RadrootsIdParseError), 44 } 45 46 #[cfg(feature = "serde_json")] 47 impl core::fmt::Display for RadrootsOrderEnvelopeParseError { 48 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 49 match self { 50 Self::InvalidKind(kind) => write!(f, "invalid order event kind: {kind}"), 51 Self::InvalidJson => write!(f, "invalid order envelope json"), 52 Self::InvalidEnvelope(error) => write!(f, "{error}"), 53 Self::InvalidPayload(error) => write!(f, "{error}"), 54 Self::MessageTypeKindMismatch { 55 event_kind, 56 message_type, 57 } => write!( 58 f, 59 "order envelope type {message_type:?} does not match event kind {event_kind}" 60 ), 61 Self::MissingTag(tag) => write!(f, "missing required order tag: {tag}"), 62 Self::InvalidTag(tag) => write!(f, "invalid order tag: {tag}"), 63 Self::ListingAddrTagMismatch => { 64 write!(f, "order listing address tag does not match envelope") 65 } 66 Self::OrderIdTagMismatch => { 67 write!(f, "order order id tag does not match envelope") 68 } 69 Self::PayloadBindingMismatch(field) => { 70 write!(f, "order payload {field} does not match envelope") 71 } 72 Self::AuthorMismatch => write!(f, "order event author does not match payload"), 73 Self::CounterpartyTagMismatch => { 74 write!(f, "order counterparty tag does not match payload") 75 } 76 Self::InvalidListingAddr(error) => write!(f, "{error}"), 77 } 78 } 79 } 80 81 #[cfg(all(feature = "std", feature = "serde_json"))] 82 impl std::error::Error for RadrootsOrderEnvelopeParseError { 83 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 84 match self { 85 Self::InvalidEnvelope(error) => Some(error), 86 Self::InvalidPayload(error) => Some(error), 87 Self::InvalidListingAddr(error) => Some(error), 88 _ => None, 89 } 90 } 91 } 92 93 #[cfg(feature = "serde_json")] 94 #[derive(Clone, Debug, PartialEq, Eq)] 95 pub struct RadrootsOrderEventContext { 96 pub counterparty_pubkey: RadrootsPublicKey, 97 pub listing_event: Option<RadrootsNostrEventPtr>, 98 pub root_event_id: Option<RadrootsEventId>, 99 pub prev_event_id: Option<RadrootsEventId>, 100 } 101 102 #[cfg(feature = "serde_json")] 103 pub fn order_envelope_from_event<T: DeserializeOwned>( 104 event: &RadrootsNostrEvent, 105 ) -> Result<RadrootsOrderEnvelope<T>, RadrootsOrderEnvelopeParseError> { 106 if !is_order_event_kind(event.kind) { 107 return Err(RadrootsOrderEnvelopeParseError::InvalidKind(event.kind)); 108 } 109 let envelope = serde_json::from_str::<RadrootsOrderEnvelope<T>>(&event.content) 110 .map_err(|_| RadrootsOrderEnvelopeParseError::InvalidJson)?; 111 envelope 112 .validate() 113 .map_err(RadrootsOrderEnvelopeParseError::InvalidEnvelope)?; 114 if envelope.message_type.kind() != event.kind { 115 return Err(RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { 116 event_kind: event.kind, 117 message_type: envelope.message_type, 118 }); 119 } 120 121 let listing_addr = required_order_tag_value(&event.tags, "a")?; 122 if envelope.listing_addr != listing_addr { 123 return Err(RadrootsOrderEnvelopeParseError::ListingAddrTagMismatch); 124 } 125 RadrootsListingAddress::parse(&envelope.listing_addr) 126 .map_err(RadrootsOrderEnvelopeParseError::InvalidListingAddr)?; 127 128 let tag_order_id = required_order_tag_value(&event.tags, TAG_D)?; 129 if tag_order_id != envelope.order_id { 130 return Err(RadrootsOrderEnvelopeParseError::OrderIdTagMismatch); 131 } 132 133 order_event_context_from_tags(envelope.message_type, &event.tags)?; 134 Ok(envelope) 135 } 136 137 #[cfg(feature = "serde_json")] 138 pub fn order_request_from_event( 139 event: &RadrootsNostrEvent, 140 ) -> Result<RadrootsOrderEnvelope<RadrootsOrderRequest>, RadrootsOrderEnvelopeParseError> { 141 let envelope = order_envelope_from_event::<RadrootsOrderRequest>(event)?; 142 if envelope.message_type != RadrootsOrderEventType::OrderRequested { 143 return Err(RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { 144 event_kind: event.kind, 145 message_type: envelope.message_type, 146 }); 147 } 148 envelope 149 .payload 150 .validate() 151 .map_err(RadrootsOrderEnvelopeParseError::InvalidPayload)?; 152 validate_order_binding( 153 event, 154 &envelope, 155 &envelope.payload.order_id, 156 &envelope.payload.listing_addr, 157 &envelope.payload.buyer_pubkey, 158 &envelope.payload.seller_pubkey, 159 )?; 160 Ok(envelope) 161 } 162 163 #[cfg(feature = "serde_json")] 164 pub fn order_decision_from_event( 165 event: &RadrootsNostrEvent, 166 ) -> Result<RadrootsOrderEnvelope<RadrootsOrderDecision>, RadrootsOrderEnvelopeParseError> { 167 let envelope = order_envelope_from_event::<RadrootsOrderDecision>(event)?; 168 if envelope.message_type != RadrootsOrderEventType::OrderDecision { 169 return Err(RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { 170 event_kind: event.kind, 171 message_type: envelope.message_type, 172 }); 173 } 174 envelope 175 .payload 176 .validate() 177 .map_err(RadrootsOrderEnvelopeParseError::InvalidPayload)?; 178 validate_order_binding( 179 event, 180 &envelope, 181 &envelope.payload.order_id, 182 &envelope.payload.listing_addr, 183 &envelope.payload.seller_pubkey, 184 &envelope.payload.buyer_pubkey, 185 )?; 186 Ok(envelope) 187 } 188 189 #[cfg(feature = "serde_json")] 190 pub fn order_revision_proposal_from_event( 191 event: &RadrootsNostrEvent, 192 ) -> Result<RadrootsOrderEnvelope<RadrootsOrderRevisionProposal>, RadrootsOrderEnvelopeParseError> { 193 let envelope = order_envelope_from_event::<RadrootsOrderRevisionProposal>(event)?; 194 if envelope.message_type != RadrootsOrderEventType::OrderRevisionProposed { 195 return Err(RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { 196 event_kind: event.kind, 197 message_type: envelope.message_type, 198 }); 199 } 200 envelope 201 .payload 202 .validate() 203 .map_err(RadrootsOrderEnvelopeParseError::InvalidPayload)?; 204 validate_order_binding( 205 event, 206 &envelope, 207 &envelope.payload.order_id, 208 &envelope.payload.listing_addr, 209 &envelope.payload.seller_pubkey, 210 &envelope.payload.buyer_pubkey, 211 )?; 212 let context = order_event_context_from_tags(envelope.message_type, &event.tags)?; 213 if context.root_event_id.as_deref() != Some(envelope.payload.root_event_id.as_str()) { 214 return Err(RadrootsOrderEnvelopeParseError::PayloadBindingMismatch( 215 "root_event_id", 216 )); 217 } 218 if context.prev_event_id.as_deref() != Some(envelope.payload.prev_event_id.as_str()) { 219 return Err(RadrootsOrderEnvelopeParseError::PayloadBindingMismatch( 220 "prev_event_id", 221 )); 222 } 223 Ok(envelope) 224 } 225 226 #[cfg(feature = "serde_json")] 227 pub fn order_revision_decision_from_event( 228 event: &RadrootsNostrEvent, 229 ) -> Result<RadrootsOrderEnvelope<RadrootsOrderRevisionDecision>, RadrootsOrderEnvelopeParseError> { 230 let envelope = order_envelope_from_event::<RadrootsOrderRevisionDecision>(event)?; 231 if envelope.message_type != RadrootsOrderEventType::OrderRevisionDecision { 232 return Err(RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { 233 event_kind: event.kind, 234 message_type: envelope.message_type, 235 }); 236 } 237 envelope 238 .payload 239 .validate() 240 .map_err(RadrootsOrderEnvelopeParseError::InvalidPayload)?; 241 validate_order_binding( 242 event, 243 &envelope, 244 &envelope.payload.order_id, 245 &envelope.payload.listing_addr, 246 &envelope.payload.buyer_pubkey, 247 &envelope.payload.seller_pubkey, 248 )?; 249 let context = order_event_context_from_tags(envelope.message_type, &event.tags)?; 250 if context.root_event_id.as_deref() != Some(envelope.payload.root_event_id.as_str()) { 251 return Err(RadrootsOrderEnvelopeParseError::PayloadBindingMismatch( 252 "root_event_id", 253 )); 254 } 255 if context.prev_event_id.as_deref() != Some(envelope.payload.prev_event_id.as_str()) { 256 return Err(RadrootsOrderEnvelopeParseError::PayloadBindingMismatch( 257 "prev_event_id", 258 )); 259 } 260 Ok(envelope) 261 } 262 263 #[cfg(feature = "serde_json")] 264 pub fn order_cancellation_from_event( 265 event: &RadrootsNostrEvent, 266 ) -> Result<RadrootsOrderEnvelope<RadrootsOrderCancellation>, RadrootsOrderEnvelopeParseError> { 267 let envelope = order_envelope_from_event::<RadrootsOrderCancellation>(event)?; 268 if envelope.message_type != RadrootsOrderEventType::OrderCancelled { 269 return Err(RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { 270 event_kind: event.kind, 271 message_type: envelope.message_type, 272 }); 273 } 274 envelope 275 .payload 276 .validate() 277 .map_err(RadrootsOrderEnvelopeParseError::InvalidPayload)?; 278 validate_order_binding( 279 event, 280 &envelope, 281 &envelope.payload.order_id, 282 &envelope.payload.listing_addr, 283 &envelope.payload.buyer_pubkey, 284 &envelope.payload.seller_pubkey, 285 )?; 286 Ok(envelope) 287 } 288 289 #[cfg(feature = "serde_json")] 290 pub fn order_event_context_from_tags( 291 message_type: RadrootsOrderEventType, 292 tags: &[Vec<String>], 293 ) -> Result<RadrootsOrderEventContext, RadrootsOrderEnvelopeParseError> { 294 let counterparty_pubkey = 295 parse_order_counterparty_tag(tags).map_err(map_tag_parse_error_for_order_envelope)?; 296 let counterparty_pubkey = RadrootsPublicKey::parse(&counterparty_pubkey) 297 .map_err(|_| RadrootsOrderEnvelopeParseError::InvalidTag("p"))?; 298 let listing_event = 299 parse_order_listing_event_tag(tags).map_err(map_tag_parse_error_for_order_envelope)?; 300 let root_event_id = 301 parse_order_root_tag(tags).map_err(map_tag_parse_error_for_order_envelope)?; 302 let root_event_id = root_event_id 303 .map(|id| { 304 RadrootsEventId::parse(id) 305 .map_err(|_| RadrootsOrderEnvelopeParseError::InvalidTag(TAG_E_ROOT)) 306 }) 307 .transpose()?; 308 let prev_event_id = 309 parse_order_prev_tag(tags).map_err(map_tag_parse_error_for_order_envelope)?; 310 let prev_event_id = prev_event_id 311 .map(|id| { 312 RadrootsEventId::parse(id) 313 .map_err(|_| RadrootsOrderEnvelopeParseError::InvalidTag(TAG_E_PREV)) 314 }) 315 .transpose()?; 316 317 if message_type.requires_listing_snapshot() && listing_event.is_none() { 318 return Err(RadrootsOrderEnvelopeParseError::MissingTag( 319 TAG_LISTING_EVENT, 320 )); 321 } 322 if message_type.requires_order_chain() { 323 if root_event_id.is_none() { 324 return Err(RadrootsOrderEnvelopeParseError::MissingTag(TAG_E_ROOT)); 325 } 326 if prev_event_id.is_none() { 327 return Err(RadrootsOrderEnvelopeParseError::MissingTag(TAG_E_PREV)); 328 } 329 } 330 331 Ok(RadrootsOrderEventContext { 332 counterparty_pubkey, 333 listing_event, 334 root_event_id, 335 prev_event_id, 336 }) 337 } 338 339 #[cfg(feature = "serde_json")] 340 fn required_order_tag_value<'a>( 341 tags: &'a [Vec<String>], 342 key: &'static str, 343 ) -> Result<&'a str, RadrootsOrderEnvelopeParseError> { 344 let tag = tags 345 .iter() 346 .find(|tag| tag.first().map(|value| value.as_str()) == Some(key)) 347 .ok_or(RadrootsOrderEnvelopeParseError::MissingTag(key))?; 348 let value = tag 349 .get(1) 350 .map(|value| value.as_str()) 351 .ok_or(RadrootsOrderEnvelopeParseError::InvalidTag(key))?; 352 if value.trim().is_empty() { 353 return Err(RadrootsOrderEnvelopeParseError::InvalidTag(key)); 354 } 355 Ok(value) 356 } 357 358 #[cfg(feature = "serde_json")] 359 fn map_tag_parse_error_for_order_envelope( 360 error: crate::error::EventParseError, 361 ) -> RadrootsOrderEnvelopeParseError { 362 match error { 363 crate::error::EventParseError::MissingTag(tag) => { 364 RadrootsOrderEnvelopeParseError::MissingTag(tag) 365 } 366 crate::error::EventParseError::InvalidTag(tag) => { 367 RadrootsOrderEnvelopeParseError::InvalidTag(tag) 368 } 369 crate::error::EventParseError::InvalidKind { expected: _, got } => { 370 RadrootsOrderEnvelopeParseError::InvalidKind(got) 371 } 372 crate::error::EventParseError::InvalidNumber(tag, _) 373 | crate::error::EventParseError::InvalidJson(tag) => { 374 RadrootsOrderEnvelopeParseError::InvalidTag(tag) 375 } 376 } 377 } 378 379 #[cfg(feature = "serde_json")] 380 fn validate_order_binding<T>( 381 event: &RadrootsNostrEvent, 382 envelope: &RadrootsOrderEnvelope<T>, 383 payload_order_id: &str, 384 payload_listing_addr: &str, 385 expected_author: &str, 386 expected_counterparty: &str, 387 ) -> Result<(), RadrootsOrderEnvelopeParseError> { 388 if envelope.order_id != payload_order_id { 389 return Err(RadrootsOrderEnvelopeParseError::PayloadBindingMismatch( 390 "order_id", 391 )); 392 } 393 if envelope.listing_addr != payload_listing_addr { 394 return Err(RadrootsOrderEnvelopeParseError::PayloadBindingMismatch( 395 "listing_addr", 396 )); 397 } 398 if event.author != expected_author { 399 return Err(RadrootsOrderEnvelopeParseError::AuthorMismatch); 400 } 401 let context = order_event_context_from_tags(envelope.message_type, &event.tags)?; 402 if context.counterparty_pubkey.as_str() != expected_counterparty { 403 return Err(RadrootsOrderEnvelopeParseError::CounterpartyTagMismatch); 404 } 405 Ok(()) 406 } 407 408 #[cfg(all(test, feature = "serde_json"))] 409 mod tests { 410 use super::{ 411 RadrootsOrderEnvelopeParseError, map_tag_parse_error_for_order_envelope, 412 order_cancellation_from_event, order_decision_from_event, order_envelope_from_event, 413 order_event_context_from_tags, order_request_from_event, 414 order_revision_decision_from_event, order_revision_proposal_from_event, 415 }; 416 use crate::order::tags::TAG_LISTING_EVENT; 417 use crate::{ 418 error::EventEncodeError, 419 order::encode::{ 420 order_cancellation_event_build, order_decision_event_build, order_request_event_build, 421 order_revision_decision_event_build, order_revision_proposal_event_build, 422 }, 423 }; 424 use radroots_core::{ 425 RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreUnit, 426 }; 427 use radroots_events::{ 428 RadrootsNostrEvent, RadrootsNostrEventPtr, 429 ids::{ 430 RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsOrderId, 431 RadrootsOrderQuoteId, RadrootsOrderRevisionId, RadrootsPublicKey, 432 }, 433 kinds::{ 434 KIND_ORDER_CANCELLATION, KIND_ORDER_DECISION, KIND_ORDER_REQUEST, 435 KIND_ORDER_REVISION_DECISION, KIND_ORDER_REVISION_PROPOSAL, 436 }, 437 order::{ 438 RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderDecisionOutcome, 439 RadrootsOrderEconomicItem, RadrootsOrderEconomicLine, RadrootsOrderEconomics, 440 RadrootsOrderEnvelope, RadrootsOrderEnvelopeError, RadrootsOrderEventType, 441 RadrootsOrderInventoryCommitment, RadrootsOrderItem, RadrootsOrderPayloadError, 442 RadrootsOrderPricingBasis, RadrootsOrderRequest, RadrootsOrderRevisionDecision, 443 RadrootsOrderRevisionOutcome, RadrootsOrderRevisionProposal, 444 }, 445 tags::{TAG_D, TAG_E_PREV, TAG_E_ROOT}, 446 }; 447 448 fn pubkey(character: char) -> RadrootsPublicKey { 449 core::iter::repeat_n(character, 64) 450 .collect::<String>() 451 .parse() 452 .unwrap() 453 } 454 455 fn buyer_pubkey() -> RadrootsPublicKey { 456 pubkey('b') 457 } 458 459 fn seller_pubkey() -> RadrootsPublicKey { 460 pubkey('a') 461 } 462 463 fn buyer_pubkey_wire() -> String { 464 buyer_pubkey().into_string() 465 } 466 467 fn seller_pubkey_wire() -> String { 468 seller_pubkey().into_string() 469 } 470 471 fn listing_addr() -> RadrootsListingAddress { 472 format!("30402:{}:AAAAAAAAAAAAAAAAAAAAAg", seller_pubkey_wire()) 473 .parse() 474 .unwrap() 475 } 476 477 fn listing_addr_wire() -> String { 478 listing_addr().into_string() 479 } 480 481 fn order_id(raw: &str) -> RadrootsOrderId { 482 raw.parse().unwrap() 483 } 484 485 fn revision_id(raw: &str) -> RadrootsOrderRevisionId { 486 raw.parse().unwrap() 487 } 488 489 fn quote_id(raw: &str) -> RadrootsOrderQuoteId { 490 raw.parse().unwrap() 491 } 492 493 fn bin_id(raw: &str) -> RadrootsInventoryBinId { 494 raw.parse().unwrap() 495 } 496 497 fn event_id(character: char) -> RadrootsEventId { 498 core::iter::repeat_n(character, 64) 499 .collect::<String>() 500 .parse() 501 .unwrap() 502 } 503 504 fn event_id_wire(character: char) -> String { 505 event_id(character).into_string() 506 } 507 508 fn order_request() -> RadrootsOrderRequest { 509 RadrootsOrderRequest { 510 order_id: order_id("order-1"), 511 listing_addr: listing_addr(), 512 buyer_pubkey: buyer_pubkey(), 513 seller_pubkey: seller_pubkey(), 514 items: vec![RadrootsOrderItem { 515 bin_id: bin_id("lb"), 516 bin_count: 3, 517 }], 518 economics: request_economics(), 519 } 520 } 521 522 fn decimal(raw: &str) -> RadrootsCoreDecimal { 523 raw.parse().unwrap() 524 } 525 526 fn usd(raw: &str) -> RadrootsCoreMoney { 527 RadrootsCoreMoney::new(decimal(raw), RadrootsCoreCurrency::USD) 528 } 529 530 fn request_economics() -> RadrootsOrderEconomics { 531 RadrootsOrderEconomics { 532 quote_id: quote_id("quote-1"), 533 quote_version: 1, 534 pricing_basis: RadrootsOrderPricingBasis::ListingEvent, 535 currency: RadrootsCoreCurrency::USD, 536 items: vec![RadrootsOrderEconomicItem { 537 bin_id: bin_id("lb"), 538 bin_count: 3, 539 quantity_amount: decimal("1"), 540 quantity_unit: RadrootsCoreUnit::Each, 541 unit_price_amount: decimal("5"), 542 unit_price_currency: RadrootsCoreCurrency::USD, 543 line_subtotal: usd("15"), 544 }], 545 discounts: Vec::<RadrootsOrderEconomicLine>::new(), 546 adjustments: Vec::<RadrootsOrderEconomicLine>::new(), 547 subtotal: usd("15"), 548 discount_total: usd("0"), 549 adjustment_total: usd("0"), 550 total: usd("15"), 551 } 552 } 553 554 fn order_decision() -> RadrootsOrderDecision { 555 RadrootsOrderDecision { 556 order_id: order_id("order-1"), 557 listing_addr: listing_addr(), 558 buyer_pubkey: buyer_pubkey(), 559 seller_pubkey: seller_pubkey(), 560 decision: RadrootsOrderDecisionOutcome::Accepted { 561 inventory_commitments: vec![RadrootsOrderInventoryCommitment { 562 bin_id: bin_id("lb"), 563 bin_count: 3, 564 }], 565 }, 566 } 567 } 568 569 fn order_revision_proposal() -> RadrootsOrderRevisionProposal { 570 let mut economics = request_economics(); 571 economics.quote_id = quote_id("revision-quote-1"); 572 economics.quote_version = 2; 573 economics.items[0].bin_count = 4; 574 economics.items[0].line_subtotal = usd("20"); 575 economics.subtotal = usd("20"); 576 economics.total = usd("20"); 577 economics.canonicalize(); 578 RadrootsOrderRevisionProposal { 579 revision_id: revision_id("rev-1"), 580 order_id: order_id("order-1"), 581 listing_addr: listing_addr(), 582 buyer_pubkey: buyer_pubkey(), 583 seller_pubkey: seller_pubkey(), 584 root_event_id: event_id('1'), 585 prev_event_id: event_id('2'), 586 items: vec![RadrootsOrderItem { 587 bin_id: bin_id("lb"), 588 bin_count: 4, 589 }], 590 economics, 591 reason: "update count".into(), 592 } 593 } 594 595 fn order_revision_decision( 596 decision: RadrootsOrderRevisionOutcome, 597 ) -> RadrootsOrderRevisionDecision { 598 RadrootsOrderRevisionDecision { 599 revision_id: revision_id("rev-1"), 600 order_id: order_id("order-1"), 601 listing_addr: listing_addr(), 602 buyer_pubkey: buyer_pubkey(), 603 seller_pubkey: seller_pubkey(), 604 root_event_id: event_id('1'), 605 prev_event_id: event_id('3'), 606 decision, 607 } 608 } 609 610 fn order_cancelled() -> RadrootsOrderCancellation { 611 RadrootsOrderCancellation { 612 order_id: order_id("order-1"), 613 listing_addr: listing_addr(), 614 buyer_pubkey: buyer_pubkey(), 615 seller_pubkey: seller_pubkey(), 616 reason: "changed plans".into(), 617 } 618 } 619 620 fn listing_event_ptr() -> RadrootsNostrEventPtr { 621 RadrootsNostrEventPtr { 622 id: event_id_wire('a'), 623 relays: Some("wss://relay.example.com".into()), 624 } 625 } 626 627 fn order_request_tags() -> Vec<Vec<String>> { 628 vec![ 629 vec!["p".into(), seller_pubkey_wire()], 630 vec!["a".into(), listing_addr_wire()], 631 vec![TAG_D.into(), "order-1".into()], 632 vec![TAG_LISTING_EVENT.into(), event_id_wire('a')], 633 ] 634 } 635 636 fn order_chain_tags(counterparty_pubkey: String) -> Vec<Vec<String>> { 637 vec![ 638 vec!["p".into(), counterparty_pubkey], 639 vec!["a".into(), listing_addr_wire()], 640 vec![TAG_D.into(), "order-1".into()], 641 vec![TAG_E_ROOT.into(), event_id_wire('1')], 642 vec![TAG_E_PREV.into(), event_id_wire('2')], 643 ] 644 } 645 646 fn order_event_with_envelope<T: serde::Serialize>( 647 kind: u32, 648 author: String, 649 message_type: RadrootsOrderEventType, 650 listing_addr: impl Into<String>, 651 order_id: impl Into<String>, 652 payload: &T, 653 tags: Vec<Vec<String>>, 654 ) -> RadrootsNostrEvent { 655 let envelope = RadrootsOrderEnvelope::new(message_type, listing_addr, order_id, payload); 656 RadrootsNostrEvent { 657 id: event_id_wire('e'), 658 author, 659 created_at: 1, 660 kind, 661 tags, 662 content: serde_json::to_string(&envelope).unwrap(), 663 sig: "sig".into(), 664 } 665 } 666 667 #[test] 668 fn listing_address_roundtrips() { 669 let raw = format!("30402:{}:listing-1", seller_pubkey_wire()); 670 let addr = RadrootsListingAddress::parse(&raw).expect("parse listing address"); 671 assert_eq!(addr.as_str(), raw); 672 } 673 674 #[test] 675 fn order_request_builder_emits_canonical_shape() { 676 let payload = order_request(); 677 let built = order_request_event_build(&listing_event_ptr(), &payload).unwrap(); 678 let envelope: RadrootsOrderEnvelope<RadrootsOrderRequest> = 679 serde_json::from_str(&built.content).unwrap(); 680 681 assert_eq!(built.kind, KIND_ORDER_REQUEST); 682 assert_eq!( 683 envelope.message_type, 684 RadrootsOrderEventType::OrderRequested 685 ); 686 assert_eq!(envelope.order_id, "order-1"); 687 assert_eq!(built.tags[0], vec!["p".to_string(), seller_pubkey_wire()]); 688 assert_eq!(built.tags[1], vec!["a".to_string(), listing_addr_wire()]); 689 assert_eq!( 690 built.tags[2], 691 vec![TAG_D.to_string(), "order-1".to_string()] 692 ); 693 assert_eq!(envelope.payload.economics.quote_id, "quote-1"); 694 assert_eq!(envelope.payload.economics.total, usd("15")); 695 assert!( 696 built 697 .tags 698 .iter() 699 .any(|tag| tag.first().map(String::as_str) == Some(TAG_LISTING_EVENT)) 700 ); 701 assert!( 702 !built 703 .tags 704 .iter() 705 .any(|tag| tag.first().map(String::as_str) == Some(TAG_E_ROOT)) 706 ); 707 } 708 709 #[test] 710 fn order_decision_builder_emits_canonical_chain_shape() { 711 let payload = order_decision(); 712 let root_event_id = event_id('1'); 713 let prev_event_id = event_id('9'); 714 let built = order_decision_event_build(&root_event_id, &prev_event_id, &payload).unwrap(); 715 let envelope: RadrootsOrderEnvelope<RadrootsOrderDecision> = 716 serde_json::from_str(&built.content).unwrap(); 717 718 assert_eq!(built.kind, KIND_ORDER_DECISION); 719 assert_eq!(envelope.message_type, RadrootsOrderEventType::OrderDecision); 720 assert_eq!(built.tags[0], vec!["p".to_string(), buyer_pubkey_wire()]); 721 assert_eq!( 722 built.tags[2], 723 vec![TAG_D.to_string(), "order-1".to_string()] 724 ); 725 assert!( 726 built 727 .tags 728 .iter() 729 .any(|tag| tag == &vec![TAG_E_ROOT.to_string(), event_id_wire('1')]) 730 ); 731 assert!( 732 built 733 .tags 734 .iter() 735 .any(|tag| tag == &vec![TAG_E_PREV.to_string(), event_id_wire('9')]) 736 ); 737 } 738 739 #[test] 740 fn order_revision_proposal_builder_emits_canonical_chain_shape() { 741 let payload = order_revision_proposal(); 742 let built = order_revision_proposal_event_build( 743 &payload.root_event_id, 744 &payload.prev_event_id, 745 &payload, 746 ) 747 .unwrap(); 748 let envelope: RadrootsOrderEnvelope<RadrootsOrderRevisionProposal> = 749 serde_json::from_str(&built.content).unwrap(); 750 751 assert_eq!(built.kind, KIND_ORDER_REVISION_PROPOSAL); 752 assert_eq!( 753 envelope.message_type, 754 RadrootsOrderEventType::OrderRevisionProposed 755 ); 756 assert_eq!(built.tags[0], vec!["p".to_string(), buyer_pubkey_wire()]); 757 assert_eq!( 758 built.tags[2], 759 vec![TAG_D.to_string(), "order-1".to_string()] 760 ); 761 assert_eq!(envelope.payload.revision_id, "rev-1"); 762 assert_eq!(envelope.payload.economics.quote_version, 2); 763 assert!( 764 built 765 .tags 766 .iter() 767 .any(|tag| tag == &vec![TAG_E_ROOT.to_string(), event_id_wire('1')]) 768 ); 769 assert!( 770 built 771 .tags 772 .iter() 773 .any(|tag| tag == &vec![TAG_E_PREV.to_string(), event_id_wire('2')]) 774 ); 775 } 776 777 #[test] 778 fn order_revision_decision_builder_emits_canonical_chain_shape() { 779 let payload = order_revision_decision(RadrootsOrderRevisionOutcome::Accepted); 780 let built = order_revision_decision_event_build( 781 &payload.root_event_id, 782 &payload.prev_event_id, 783 &payload, 784 ) 785 .unwrap(); 786 let envelope: RadrootsOrderEnvelope<RadrootsOrderRevisionDecision> = 787 serde_json::from_str(&built.content).unwrap(); 788 789 assert_eq!(built.kind, KIND_ORDER_REVISION_DECISION); 790 assert_eq!( 791 envelope.message_type, 792 RadrootsOrderEventType::OrderRevisionDecision 793 ); 794 assert_eq!(built.tags[0], vec!["p".to_string(), seller_pubkey_wire()]); 795 assert_eq!( 796 built.tags[2], 797 vec![TAG_D.to_string(), "order-1".to_string()] 798 ); 799 assert_eq!(envelope.payload.revision_id, "rev-1"); 800 assert!( 801 built 802 .tags 803 .iter() 804 .any(|tag| tag == &vec![TAG_E_ROOT.to_string(), event_id_wire('1')]) 805 ); 806 assert!( 807 built 808 .tags 809 .iter() 810 .any(|tag| tag == &vec![TAG_E_PREV.to_string(), event_id_wire('3')]) 811 ); 812 } 813 814 #[test] 815 fn order_revision_builders_reject_mismatched_chain_context() { 816 let proposal = order_revision_proposal(); 817 let wrong_root = event_id('9'); 818 let wrong_prev = event_id('8'); 819 820 let err = 821 order_revision_proposal_event_build(&wrong_root, &proposal.prev_event_id, &proposal) 822 .unwrap_err(); 823 assert!(matches!( 824 err, 825 EventEncodeError::InvalidField("root_event_id") 826 )); 827 828 let err = 829 order_revision_proposal_event_build(&proposal.root_event_id, &wrong_prev, &proposal) 830 .unwrap_err(); 831 assert!(matches!( 832 err, 833 EventEncodeError::InvalidField("prev_event_id") 834 )); 835 836 let decision = order_revision_decision(RadrootsOrderRevisionOutcome::Accepted); 837 let err = 838 order_revision_decision_event_build(&wrong_root, &decision.prev_event_id, &decision) 839 .unwrap_err(); 840 assert!(matches!( 841 err, 842 EventEncodeError::InvalidField("root_event_id") 843 )); 844 845 let err = 846 order_revision_decision_event_build(&decision.root_event_id, &wrong_prev, &decision) 847 .unwrap_err(); 848 assert!(matches!( 849 err, 850 EventEncodeError::InvalidField("prev_event_id") 851 )); 852 } 853 854 #[test] 855 fn order_cancellation_builder_emits_canonical_buyer_chain_shape() { 856 let payload = order_cancelled(); 857 let root_event_id = event_id('1'); 858 let prev_event_id = event_id('9'); 859 let built = 860 order_cancellation_event_build(&root_event_id, &prev_event_id, &payload).unwrap(); 861 let envelope: RadrootsOrderEnvelope<RadrootsOrderCancellation> = 862 serde_json::from_str(&built.content).unwrap(); 863 864 assert_eq!(built.kind, KIND_ORDER_CANCELLATION); 865 assert_eq!( 866 envelope.message_type, 867 RadrootsOrderEventType::OrderCancelled 868 ); 869 assert_eq!(envelope.payload.reason, payload.reason); 870 assert_eq!(built.tags[0], vec!["p".to_string(), seller_pubkey_wire()]); 871 assert_eq!( 872 built.tags[2], 873 vec![TAG_D.to_string(), "order-1".to_string()] 874 ); 875 assert!( 876 built 877 .tags 878 .iter() 879 .any(|tag| tag == &vec![TAG_E_ROOT.to_string(), event_id_wire('1')]) 880 ); 881 assert!( 882 built 883 .tags 884 .iter() 885 .any(|tag| tag == &vec![TAG_E_PREV.to_string(), event_id_wire('9')]) 886 ); 887 } 888 889 #[test] 890 fn order_request_parse_roundtrips_and_validates_tags() { 891 let payload = order_request(); 892 let built = order_request_event_build(&listing_event_ptr(), &payload).unwrap(); 893 let event = RadrootsNostrEvent { 894 id: event_id_wire('e'), 895 author: buyer_pubkey_wire(), 896 created_at: 1, 897 kind: built.kind, 898 tags: built.tags, 899 content: built.content, 900 sig: "sig".into(), 901 }; 902 let envelope = order_request_from_event(&event).unwrap(); 903 904 assert_eq!(envelope.payload, payload); 905 assert_eq!( 906 envelope.message_type, 907 RadrootsOrderEventType::OrderRequested 908 ); 909 } 910 911 #[test] 912 fn order_request_parse_rejects_mismatched_economics() { 913 let mut payload = order_request(); 914 let built = order_request_event_build(&listing_event_ptr(), &payload).unwrap(); 915 payload.economics.items[0].bin_id = bin_id("other-bin"); 916 let envelope = RadrootsOrderEnvelope::new( 917 RadrootsOrderEventType::OrderRequested, 918 payload.listing_addr.clone(), 919 payload.order_id.clone(), 920 payload, 921 ); 922 let event = RadrootsNostrEvent { 923 id: event_id_wire('e'), 924 author: buyer_pubkey_wire(), 925 created_at: 1, 926 kind: built.kind, 927 tags: built.tags, 928 content: serde_json::to_string(&envelope).unwrap(), 929 sig: "sig".into(), 930 }; 931 let err = order_request_from_event(&event).unwrap_err(); 932 assert_eq!( 933 err, 934 RadrootsOrderEnvelopeParseError::InvalidPayload( 935 RadrootsOrderPayloadError::InvalidOrderEconomicsBinding { 936 field: "items.bin_id" 937 } 938 ) 939 ); 940 } 941 942 #[test] 943 fn order_decision_parse_roundtrips_and_validates_chain_tags() { 944 let payload = order_decision(); 945 let root_event_id = event_id('1'); 946 let prev_event_id = event_id('9'); 947 let built = order_decision_event_build(&root_event_id, &prev_event_id, &payload).unwrap(); 948 let event = RadrootsNostrEvent { 949 id: event_id_wire('e'), 950 author: seller_pubkey_wire(), 951 created_at: 1, 952 kind: built.kind, 953 tags: built.tags, 954 content: built.content, 955 sig: "sig".into(), 956 }; 957 let envelope = order_decision_from_event(&event).unwrap(); 958 959 assert_eq!(envelope.payload, payload); 960 assert_eq!(envelope.message_type, RadrootsOrderEventType::OrderDecision); 961 } 962 963 #[test] 964 fn order_cancellation_parse_roundtrips_and_validates_buyer_actor() { 965 let payload = order_cancelled(); 966 let root_event_id = event_id('1'); 967 let prev_event_id = event_id('9'); 968 let built = 969 order_cancellation_event_build(&root_event_id, &prev_event_id, &payload).unwrap(); 970 let event = RadrootsNostrEvent { 971 id: event_id_wire('e'), 972 author: buyer_pubkey_wire(), 973 created_at: 1, 974 kind: built.kind, 975 tags: built.tags, 976 content: built.content, 977 sig: "sig".into(), 978 }; 979 let envelope = order_cancellation_from_event(&event).unwrap(); 980 981 assert_eq!(envelope.payload, payload); 982 assert_eq!( 983 envelope.message_type, 984 RadrootsOrderEventType::OrderCancelled 985 ); 986 } 987 988 #[test] 989 fn order_revision_proposal_parse_validates_actor_counterparty_and_chain_payload() { 990 let payload = order_revision_proposal(); 991 let built = order_revision_proposal_event_build( 992 &payload.root_event_id, 993 &payload.prev_event_id, 994 &payload, 995 ) 996 .unwrap(); 997 let mut event = RadrootsNostrEvent { 998 id: event_id_wire('e'), 999 author: seller_pubkey_wire(), 1000 created_at: 1, 1001 kind: built.kind, 1002 tags: built.tags, 1003 content: built.content, 1004 sig: "sig".into(), 1005 }; 1006 let envelope = order_revision_proposal_from_event(&event).unwrap(); 1007 assert_eq!(envelope.payload, payload); 1008 1009 event.author = buyer_pubkey_wire(); 1010 let err = order_revision_proposal_from_event(&event).unwrap_err(); 1011 assert_eq!(err, RadrootsOrderEnvelopeParseError::AuthorMismatch); 1012 } 1013 1014 #[test] 1015 fn order_revision_decision_parse_validates_actor_counterparty_and_chain_payload() { 1016 let payload = order_revision_decision(RadrootsOrderRevisionOutcome::Declined { 1017 reason: "no change".into(), 1018 }); 1019 let built = order_revision_decision_event_build( 1020 &payload.root_event_id, 1021 &payload.prev_event_id, 1022 &payload, 1023 ) 1024 .unwrap(); 1025 let mut event = RadrootsNostrEvent { 1026 id: event_id_wire('e'), 1027 author: buyer_pubkey_wire(), 1028 created_at: 1, 1029 kind: built.kind, 1030 tags: built.tags, 1031 content: built.content, 1032 sig: "sig".into(), 1033 }; 1034 let envelope = order_revision_decision_from_event(&event).unwrap(); 1035 assert_eq!(envelope.payload, payload); 1036 1037 event.author = seller_pubkey_wire(); 1038 let err = order_revision_decision_from_event(&event).unwrap_err(); 1039 assert_eq!(err, RadrootsOrderEnvelopeParseError::AuthorMismatch); 1040 } 1041 1042 #[cfg(feature = "std")] 1043 #[test] 1044 fn order_parse_error_display_and_source_cover_variants() { 1045 use std::error::Error as _; 1046 1047 let invalid_envelope = RadrootsOrderEnvelopeParseError::InvalidEnvelope( 1048 RadrootsOrderEnvelopeError::MissingOrderId, 1049 ); 1050 let invalid_payload = RadrootsOrderEnvelopeParseError::InvalidPayload( 1051 RadrootsOrderPayloadError::MissingItems, 1052 ); 1053 let invalid_listing_addr = RadrootsOrderEnvelopeParseError::InvalidListingAddr( 1054 RadrootsListingAddress::parse("not-a-listing-address").unwrap_err(), 1055 ); 1056 let errors = [ 1057 RadrootsOrderEnvelopeParseError::InvalidKind(3431), 1058 RadrootsOrderEnvelopeParseError::InvalidJson, 1059 invalid_envelope.clone(), 1060 invalid_payload.clone(), 1061 RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { 1062 event_kind: KIND_ORDER_REQUEST, 1063 message_type: RadrootsOrderEventType::OrderDecision, 1064 }, 1065 RadrootsOrderEnvelopeParseError::MissingTag("a"), 1066 RadrootsOrderEnvelopeParseError::InvalidTag("p"), 1067 RadrootsOrderEnvelopeParseError::ListingAddrTagMismatch, 1068 RadrootsOrderEnvelopeParseError::OrderIdTagMismatch, 1069 RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("order_id"), 1070 RadrootsOrderEnvelopeParseError::AuthorMismatch, 1071 RadrootsOrderEnvelopeParseError::CounterpartyTagMismatch, 1072 invalid_listing_addr.clone(), 1073 ]; 1074 1075 for error in errors { 1076 assert!(!error.to_string().is_empty()); 1077 } 1078 assert!(invalid_envelope.source().is_some()); 1079 assert!(invalid_payload.source().is_some()); 1080 assert!(invalid_listing_addr.source().is_some()); 1081 assert!( 1082 RadrootsOrderEnvelopeParseError::AuthorMismatch 1083 .source() 1084 .is_none() 1085 ); 1086 } 1087 1088 #[test] 1089 fn order_envelope_parse_rejects_content_tag_and_envelope_mismatches() { 1090 let payload = serde_json::json!({}); 1091 let invalid_json = RadrootsNostrEvent { 1092 id: event_id_wire('e'), 1093 author: buyer_pubkey_wire(), 1094 created_at: 1, 1095 kind: KIND_ORDER_REQUEST, 1096 tags: Vec::new(), 1097 content: "{".into(), 1098 sig: "sig".into(), 1099 }; 1100 assert_eq!( 1101 order_envelope_from_event::<serde_json::Value>(&invalid_json).unwrap_err(), 1102 RadrootsOrderEnvelopeParseError::InvalidJson 1103 ); 1104 1105 let mut invalid_version_envelope = RadrootsOrderEnvelope::new( 1106 RadrootsOrderEventType::OrderRequested, 1107 listing_addr_wire(), 1108 "order-1", 1109 &payload, 1110 ); 1111 invalid_version_envelope.version = 99; 1112 let invalid_version = RadrootsNostrEvent { 1113 id: event_id_wire('e'), 1114 author: buyer_pubkey_wire(), 1115 created_at: 1, 1116 kind: KIND_ORDER_REQUEST, 1117 tags: order_request_tags(), 1118 content: serde_json::to_string(&invalid_version_envelope).unwrap(), 1119 sig: "sig".into(), 1120 }; 1121 assert!(matches!( 1122 order_envelope_from_event::<serde_json::Value>(&invalid_version).unwrap_err(), 1123 RadrootsOrderEnvelopeParseError::InvalidEnvelope( 1124 RadrootsOrderEnvelopeError::InvalidVersion { .. } 1125 ) 1126 )); 1127 1128 let message_type_mismatch = order_event_with_envelope( 1129 KIND_ORDER_REQUEST, 1130 buyer_pubkey_wire(), 1131 RadrootsOrderEventType::OrderDecision, 1132 listing_addr_wire(), 1133 "order-1", 1134 &payload, 1135 Vec::new(), 1136 ); 1137 assert_eq!( 1138 order_envelope_from_event::<serde_json::Value>(&message_type_mismatch).unwrap_err(), 1139 RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { 1140 event_kind: KIND_ORDER_REQUEST, 1141 message_type: RadrootsOrderEventType::OrderDecision 1142 } 1143 ); 1144 1145 let listing_addr_mismatch = order_event_with_envelope( 1146 KIND_ORDER_REQUEST, 1147 buyer_pubkey_wire(), 1148 RadrootsOrderEventType::OrderRequested, 1149 listing_addr_wire(), 1150 "order-1", 1151 &payload, 1152 vec![ 1153 vec!["a".into(), "30402:pubkey:AAAAAAAAAAAAAAAAAAAAAg".into()], 1154 vec![TAG_D.into(), "order-1".into()], 1155 ], 1156 ); 1157 assert_eq!( 1158 order_envelope_from_event::<serde_json::Value>(&listing_addr_mismatch).unwrap_err(), 1159 RadrootsOrderEnvelopeParseError::ListingAddrTagMismatch 1160 ); 1161 1162 let order_id_mismatch = order_event_with_envelope( 1163 KIND_ORDER_REQUEST, 1164 buyer_pubkey_wire(), 1165 RadrootsOrderEventType::OrderRequested, 1166 listing_addr_wire(), 1167 "order-1", 1168 &payload, 1169 vec![ 1170 vec!["a".into(), listing_addr_wire()], 1171 vec![TAG_D.into(), "other-order".into()], 1172 ], 1173 ); 1174 assert_eq!( 1175 order_envelope_from_event::<serde_json::Value>(&order_id_mismatch).unwrap_err(), 1176 RadrootsOrderEnvelopeParseError::OrderIdTagMismatch 1177 ); 1178 1179 for tags in [ 1180 Vec::<Vec<String>>::new(), 1181 vec![vec!["a".into()]], 1182 vec![vec!["a".into(), " ".into()]], 1183 ] { 1184 let event = order_event_with_envelope( 1185 KIND_ORDER_REQUEST, 1186 buyer_pubkey_wire(), 1187 RadrootsOrderEventType::OrderRequested, 1188 listing_addr_wire(), 1189 "order-1", 1190 &payload, 1191 tags, 1192 ); 1193 let err = order_envelope_from_event::<serde_json::Value>(&event).unwrap_err(); 1194 assert!(matches!( 1195 err, 1196 RadrootsOrderEnvelopeParseError::MissingTag("a") 1197 | RadrootsOrderEnvelopeParseError::InvalidTag("a") 1198 )); 1199 } 1200 1201 let invalid_listing_addr = order_event_with_envelope( 1202 KIND_ORDER_REQUEST, 1203 buyer_pubkey_wire(), 1204 RadrootsOrderEventType::OrderRequested, 1205 "not-a-listing-address", 1206 "order-1", 1207 &payload, 1208 vec![ 1209 vec!["a".into(), "not-a-listing-address".into()], 1210 vec![TAG_D.into(), "order-1".into()], 1211 ], 1212 ); 1213 assert!(matches!( 1214 order_envelope_from_event::<serde_json::Value>(&invalid_listing_addr).unwrap_err(), 1215 RadrootsOrderEnvelopeParseError::InvalidListingAddr(_) 1216 )); 1217 } 1218 1219 #[test] 1220 fn order_typed_parsers_reject_message_type_mismatches() { 1221 let request_payload = order_request(); 1222 let decision_payload = order_decision(); 1223 let proposal_payload = order_revision_proposal(); 1224 let revision_decision_payload = 1225 order_revision_decision(RadrootsOrderRevisionOutcome::Accepted); 1226 let cancellation_payload = order_cancelled(); 1227 1228 let request_as_decision = order_event_with_envelope( 1229 KIND_ORDER_DECISION, 1230 buyer_pubkey_wire(), 1231 RadrootsOrderEventType::OrderDecision, 1232 listing_addr_wire(), 1233 "order-1", 1234 &request_payload, 1235 order_chain_tags(seller_pubkey_wire()), 1236 ); 1237 assert!(matches!( 1238 order_request_from_event(&request_as_decision).unwrap_err(), 1239 RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { .. } 1240 )); 1241 1242 let decision_as_request = order_event_with_envelope( 1243 KIND_ORDER_REQUEST, 1244 seller_pubkey_wire(), 1245 RadrootsOrderEventType::OrderRequested, 1246 listing_addr_wire(), 1247 "order-1", 1248 &decision_payload, 1249 order_request_tags(), 1250 ); 1251 assert!(matches!( 1252 order_decision_from_event(&decision_as_request).unwrap_err(), 1253 RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { .. } 1254 )); 1255 1256 let proposal_as_cancellation = order_event_with_envelope( 1257 KIND_ORDER_CANCELLATION, 1258 seller_pubkey_wire(), 1259 RadrootsOrderEventType::OrderCancelled, 1260 listing_addr_wire(), 1261 "order-1", 1262 &proposal_payload, 1263 order_chain_tags(buyer_pubkey_wire()), 1264 ); 1265 assert!(matches!( 1266 order_revision_proposal_from_event(&proposal_as_cancellation).unwrap_err(), 1267 RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { .. } 1268 )); 1269 1270 let revision_decision_as_cancellation = order_event_with_envelope( 1271 KIND_ORDER_CANCELLATION, 1272 buyer_pubkey_wire(), 1273 RadrootsOrderEventType::OrderCancelled, 1274 listing_addr_wire(), 1275 "order-1", 1276 &revision_decision_payload, 1277 order_chain_tags(seller_pubkey_wire()), 1278 ); 1279 assert!(matches!( 1280 order_revision_decision_from_event(&revision_decision_as_cancellation).unwrap_err(), 1281 RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { .. } 1282 )); 1283 1284 let cancellation_as_decision = order_event_with_envelope( 1285 KIND_ORDER_DECISION, 1286 buyer_pubkey_wire(), 1287 RadrootsOrderEventType::OrderDecision, 1288 listing_addr_wire(), 1289 "order-1", 1290 &cancellation_payload, 1291 order_chain_tags(seller_pubkey_wire()), 1292 ); 1293 assert!(matches!( 1294 order_cancellation_from_event(&cancellation_as_decision).unwrap_err(), 1295 RadrootsOrderEnvelopeParseError::MessageTypeKindMismatch { .. } 1296 )); 1297 } 1298 1299 #[test] 1300 fn order_parse_rejects_payload_and_chain_binding_mismatches() { 1301 let mut request_payload = order_request(); 1302 request_payload.order_id = order_id("other-order"); 1303 let request_built = 1304 order_request_event_build(&listing_event_ptr(), &order_request()).unwrap(); 1305 let mut request_event = RadrootsNostrEvent { 1306 id: event_id_wire('e'), 1307 author: buyer_pubkey_wire(), 1308 created_at: 1, 1309 kind: request_built.kind, 1310 tags: request_built.tags.clone(), 1311 content: serde_json::to_string(&RadrootsOrderEnvelope::new( 1312 RadrootsOrderEventType::OrderRequested, 1313 listing_addr_wire(), 1314 "order-1", 1315 &request_payload, 1316 )) 1317 .unwrap(), 1318 sig: "sig".into(), 1319 }; 1320 assert_eq!( 1321 order_request_from_event(&request_event).unwrap_err(), 1322 RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("order_id") 1323 ); 1324 1325 request_payload = order_request(); 1326 request_payload.listing_addr = 1327 format!("30402:{}:BBBBBBBBBBBBBBBBBBBBBA", seller_pubkey_wire()) 1328 .parse() 1329 .unwrap(); 1330 request_event.content = serde_json::to_string(&RadrootsOrderEnvelope::new( 1331 RadrootsOrderEventType::OrderRequested, 1332 listing_addr_wire(), 1333 "order-1", 1334 &request_payload, 1335 )) 1336 .unwrap(); 1337 assert_eq!( 1338 order_request_from_event(&request_event).unwrap_err(), 1339 RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("listing_addr") 1340 ); 1341 1342 let proposal_payload = order_revision_proposal(); 1343 let proposal_built = order_revision_proposal_event_build( 1344 &proposal_payload.root_event_id, 1345 &proposal_payload.prev_event_id, 1346 &proposal_payload, 1347 ) 1348 .unwrap(); 1349 let mut proposal_event = RadrootsNostrEvent { 1350 id: event_id_wire('e'), 1351 author: seller_pubkey_wire(), 1352 created_at: 1, 1353 kind: proposal_built.kind, 1354 tags: proposal_built.tags.clone(), 1355 content: proposal_built.content.clone(), 1356 sig: "sig".into(), 1357 }; 1358 proposal_event 1359 .tags 1360 .iter_mut() 1361 .find(|tag| tag.first().map(String::as_str) == Some(TAG_E_ROOT)) 1362 .unwrap()[1] = event_id_wire('4'); 1363 assert_eq!( 1364 order_revision_proposal_from_event(&proposal_event).unwrap_err(), 1365 RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("root_event_id") 1366 ); 1367 1368 proposal_event.tags = proposal_built.tags; 1369 proposal_event 1370 .tags 1371 .iter_mut() 1372 .find(|tag| tag.first().map(String::as_str) == Some(TAG_E_PREV)) 1373 .unwrap()[1] = event_id_wire('5'); 1374 assert_eq!( 1375 order_revision_proposal_from_event(&proposal_event).unwrap_err(), 1376 RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("prev_event_id") 1377 ); 1378 1379 let revision_decision_payload = 1380 order_revision_decision(RadrootsOrderRevisionOutcome::Accepted); 1381 let revision_decision_built = order_revision_decision_event_build( 1382 &revision_decision_payload.root_event_id, 1383 &revision_decision_payload.prev_event_id, 1384 &revision_decision_payload, 1385 ) 1386 .unwrap(); 1387 let mut revision_decision_event = RadrootsNostrEvent { 1388 id: event_id_wire('e'), 1389 author: buyer_pubkey_wire(), 1390 created_at: 1, 1391 kind: revision_decision_built.kind, 1392 tags: revision_decision_built.tags.clone(), 1393 content: revision_decision_built.content, 1394 sig: "sig".into(), 1395 }; 1396 revision_decision_event 1397 .tags 1398 .iter_mut() 1399 .find(|tag| tag.first().map(String::as_str) == Some(TAG_E_ROOT)) 1400 .unwrap()[1] = event_id_wire('6'); 1401 assert_eq!( 1402 order_revision_decision_from_event(&revision_decision_event).unwrap_err(), 1403 RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("root_event_id") 1404 ); 1405 1406 revision_decision_event.tags = revision_decision_built.tags; 1407 revision_decision_event 1408 .tags 1409 .iter_mut() 1410 .find(|tag| tag.first().map(String::as_str) == Some(TAG_E_PREV)) 1411 .unwrap()[1] = event_id_wire('7'); 1412 assert_eq!( 1413 order_revision_decision_from_event(&revision_decision_event).unwrap_err(), 1414 RadrootsOrderEnvelopeParseError::PayloadBindingMismatch("prev_event_id") 1415 ); 1416 } 1417 1418 #[test] 1419 fn order_event_context_and_parse_error_mapping_cover_missing_context() { 1420 let err = order_event_context_from_tags( 1421 RadrootsOrderEventType::OrderRequested, 1422 &[vec!["p".into(), seller_pubkey_wire()]], 1423 ) 1424 .unwrap_err(); 1425 assert_eq!( 1426 err, 1427 RadrootsOrderEnvelopeParseError::MissingTag(TAG_LISTING_EVENT) 1428 ); 1429 1430 let err = order_event_context_from_tags( 1431 RadrootsOrderEventType::OrderDecision, 1432 &[ 1433 vec!["p".into(), buyer_pubkey_wire()], 1434 vec![TAG_E_PREV.into(), event_id_wire('2')], 1435 ], 1436 ) 1437 .unwrap_err(); 1438 assert_eq!(err, RadrootsOrderEnvelopeParseError::MissingTag(TAG_E_ROOT)); 1439 1440 let err = order_event_context_from_tags( 1441 RadrootsOrderEventType::OrderDecision, 1442 &[ 1443 vec!["p".into(), buyer_pubkey_wire()], 1444 vec![TAG_E_ROOT.into(), event_id_wire('1')], 1445 vec![TAG_E_PREV.into(), "not-an-event-id".into()], 1446 ], 1447 ) 1448 .unwrap_err(); 1449 assert_eq!(err, RadrootsOrderEnvelopeParseError::InvalidTag(TAG_E_PREV)); 1450 1451 let invalid_number = "x".parse::<u32>().unwrap_err(); 1452 assert_eq!( 1453 map_tag_parse_error_for_order_envelope(crate::error::EventParseError::MissingTag("p")), 1454 RadrootsOrderEnvelopeParseError::MissingTag("p") 1455 ); 1456 assert_eq!( 1457 map_tag_parse_error_for_order_envelope(crate::error::EventParseError::InvalidTag("p")), 1458 RadrootsOrderEnvelopeParseError::InvalidTag("p") 1459 ); 1460 assert_eq!( 1461 map_tag_parse_error_for_order_envelope(crate::error::EventParseError::InvalidKind { 1462 expected: "1", 1463 got: 2, 1464 }), 1465 RadrootsOrderEnvelopeParseError::InvalidKind(2) 1466 ); 1467 assert_eq!( 1468 map_tag_parse_error_for_order_envelope(crate::error::EventParseError::InvalidNumber( 1469 "n", 1470 invalid_number, 1471 )), 1472 RadrootsOrderEnvelopeParseError::InvalidTag("n") 1473 ); 1474 assert_eq!( 1475 map_tag_parse_error_for_order_envelope(crate::error::EventParseError::InvalidJson( 1476 "json", 1477 )), 1478 RadrootsOrderEnvelopeParseError::InvalidTag("json") 1479 ); 1480 } 1481 1482 #[test] 1483 fn order_revision_kinds_parse_with_chain_tags() { 1484 for (kind, message_type) in [ 1485 ( 1486 KIND_ORDER_REVISION_PROPOSAL, 1487 RadrootsOrderEventType::OrderRevisionProposed, 1488 ), 1489 ( 1490 KIND_ORDER_REVISION_DECISION, 1491 RadrootsOrderEventType::OrderRevisionDecision, 1492 ), 1493 ] { 1494 let payload = serde_json::json!({}); 1495 let envelope = 1496 RadrootsOrderEnvelope::new(message_type, listing_addr_wire(), "order-1", &payload); 1497 let event = RadrootsNostrEvent { 1498 id: event_id_wire('e'), 1499 author: seller_pubkey_wire(), 1500 created_at: 1, 1501 kind, 1502 tags: vec![ 1503 vec!["p".into(), buyer_pubkey_wire()], 1504 vec!["a".into(), listing_addr_wire()], 1505 vec![TAG_D.into(), "order-1".into()], 1506 vec![TAG_E_ROOT.into(), event_id_wire('1')], 1507 vec![TAG_E_PREV.into(), event_id_wire('9')], 1508 ], 1509 content: serde_json::to_string(&envelope).unwrap(), 1510 sig: "sig".into(), 1511 }; 1512 let parsed = order_envelope_from_event::<serde_json::Value>(&event).unwrap(); 1513 1514 assert_eq!(parsed.message_type, message_type); 1515 assert_eq!(parsed.order_id, "order-1"); 1516 } 1517 } 1518 1519 #[test] 1520 fn order_parse_rejects_forbidden_kind() { 1521 let event = RadrootsNostrEvent { 1522 id: event_id_wire('e'), 1523 author: seller_pubkey_wire(), 1524 created_at: 1, 1525 kind: 3431, 1526 tags: Vec::new(), 1527 content: "{}".into(), 1528 sig: "sig".into(), 1529 }; 1530 let err = order_envelope_from_event::<serde_json::Value>(&event).unwrap_err(); 1531 assert_eq!(err, RadrootsOrderEnvelopeParseError::InvalidKind(3431)); 1532 } 1533 1534 #[test] 1535 fn order_parse_rejects_missing_required_refs() { 1536 let payload = order_decision(); 1537 let root_event_id = event_id('1'); 1538 let prev_event_id = event_id('9'); 1539 let built = order_decision_event_build(&root_event_id, &prev_event_id, &payload).unwrap(); 1540 let mut event = RadrootsNostrEvent { 1541 id: event_id_wire('e'), 1542 author: seller_pubkey_wire(), 1543 created_at: 1, 1544 kind: built.kind, 1545 tags: built.tags, 1546 content: built.content, 1547 sig: "sig".into(), 1548 }; 1549 event 1550 .tags 1551 .retain(|tag| tag.first().map(String::as_str) != Some(TAG_E_PREV)); 1552 1553 let err = order_decision_from_event(&event).unwrap_err(); 1554 assert_eq!(err, RadrootsOrderEnvelopeParseError::MissingTag(TAG_E_PREV)); 1555 } 1556 1557 #[test] 1558 fn order_parse_rejects_author_and_counterparty_mismatch() { 1559 let payload = order_request(); 1560 let built = order_request_event_build(&listing_event_ptr(), &payload).unwrap(); 1561 let mut event = RadrootsNostrEvent { 1562 id: event_id_wire('e'), 1563 author: seller_pubkey_wire(), 1564 created_at: 1, 1565 kind: built.kind, 1566 tags: built.tags.clone(), 1567 content: built.content.clone(), 1568 sig: "sig".into(), 1569 }; 1570 let err = order_request_from_event(&event).unwrap_err(); 1571 assert_eq!(err, RadrootsOrderEnvelopeParseError::AuthorMismatch); 1572 1573 event.author = buyer_pubkey_wire(); 1574 event.tags[0] = vec!["p".into(), pubkey('c').into_string()]; 1575 let err = order_request_from_event(&event).unwrap_err(); 1576 assert_eq!( 1577 err, 1578 RadrootsOrderEnvelopeParseError::CounterpartyTagMismatch 1579 ); 1580 } 1581 1582 #[test] 1583 fn order_cancellation_parse_rejects_wrong_actor() { 1584 let cancellation = order_cancelled(); 1585 let root_event_id = event_id('1'); 1586 let prev_event_id = event_id('9'); 1587 let cancellation_parts = 1588 order_cancellation_event_build(&root_event_id, &prev_event_id, &cancellation).unwrap(); 1589 let cancellation_event = RadrootsNostrEvent { 1590 id: event_id_wire('e'), 1591 author: seller_pubkey_wire(), 1592 created_at: 1, 1593 kind: cancellation_parts.kind, 1594 tags: cancellation_parts.tags, 1595 content: cancellation_parts.content, 1596 sig: "sig".into(), 1597 }; 1598 let err = order_cancellation_from_event(&cancellation_event).unwrap_err(); 1599 assert_eq!(err, RadrootsOrderEnvelopeParseError::AuthorMismatch); 1600 } 1601 1602 #[test] 1603 fn order_parse_rejects_invalid_protocol_tag_values() { 1604 let payload = order_decision(); 1605 let root_event_id = event_id('1'); 1606 let prev_event_id = event_id('9'); 1607 let built = order_decision_event_build(&root_event_id, &prev_event_id, &payload).unwrap(); 1608 let mut event = RadrootsNostrEvent { 1609 id: event_id_wire('e'), 1610 author: seller_pubkey_wire(), 1611 created_at: 1, 1612 kind: built.kind, 1613 tags: built.tags, 1614 content: built.content, 1615 sig: "sig".into(), 1616 }; 1617 1618 event.tags[0] = vec!["p".into(), "not-a-pubkey".into()]; 1619 let err = order_decision_from_event(&event).unwrap_err(); 1620 assert_eq!(err, RadrootsOrderEnvelopeParseError::InvalidTag("p")); 1621 1622 event.tags[0] = vec!["p".into(), buyer_pubkey_wire()]; 1623 let root_tag = event 1624 .tags 1625 .iter_mut() 1626 .find(|tag| tag.first().map(String::as_str) == Some(TAG_E_ROOT)) 1627 .unwrap(); 1628 root_tag[1] = "not-an-event-id".into(); 1629 let err = order_decision_from_event(&event).unwrap_err(); 1630 assert_eq!(err, RadrootsOrderEnvelopeParseError::InvalidTag(TAG_E_ROOT)); 1631 } 1632 }