lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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 }