lib

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

order.rs (173315B)


      1 #![forbid(unsafe_code)]
      2 
      3 #[cfg(not(feature = "std"))]
      4 use alloc::{
      5     string::{String, ToString},
      6     vec::Vec,
      7 };
      8 
      9 #[cfg(feature = "event_store")]
     10 use radroots_event_store::{RadrootsEventStore, RadrootsEventStoreError, RadrootsStoredEvent};
     11 #[cfg(feature = "serde_json")]
     12 use radroots_events::RadrootsNostrEvent;
     13 use radroots_events::ids::{
     14     RadrootsEventId, RadrootsIdParseError, RadrootsInventoryBinId, RadrootsListingAddress,
     15     RadrootsOrderId, RadrootsPublicKey,
     16 };
     17 #[cfg(feature = "serde_json")]
     18 use radroots_events::order::RadrootsOrderEventType;
     19 use radroots_events::order::{
     20     RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderDecisionOutcome,
     21     RadrootsOrderEconomics, RadrootsOrderInventoryCommitment, RadrootsOrderItem,
     22     RadrootsOrderRequest, RadrootsOrderRevisionDecision, RadrootsOrderRevisionOutcome,
     23     RadrootsOrderRevisionProposal,
     24 };
     25 #[cfg(feature = "event_store")]
     26 use radroots_events::tags::TAG_D;
     27 #[cfg(feature = "serde_json")]
     28 use radroots_events_codec::order::{
     29     RadrootsOrderEnvelopeParseError, order_cancellation_from_event, order_decision_from_event,
     30     order_event_context_from_tags, order_request_from_event, order_revision_decision_from_event,
     31     order_revision_proposal_from_event,
     32 };
     33 #[cfg(feature = "serde_json")]
     34 use sha2::{Digest, Sha256};
     35 use thiserror::Error;
     36 
     37 use crate::listing::{
     38     RadrootsPublicListingAddress, RadrootsPublicListingAddressError, parse_public_listing_address,
     39 };
     40 
     41 #[derive(Debug, Error)]
     42 pub enum RadrootsOrderCanonicalizationError {
     43     #[error("{0} cannot be empty")]
     44     EmptyField(&'static str),
     45     #[error("invalid listing_addr: {0}")]
     46     InvalidListingAddress(String),
     47     #[error("listing_addr must reference a public NIP-99 listing")]
     48     InvalidListingKind,
     49     #[error("buyer_pubkey must match the requested signer identity")]
     50     InvalidBuyerSigner,
     51     #[error("seller_pubkey must match listing_addr seller")]
     52     InvalidSellerListing,
     53     #[error("items must contain at least one item")]
     54     MissingItems,
     55     #[error("items[{index}].bin_count must be greater than zero")]
     56     InvalidBinCount { index: usize },
     57     #[error("accepted decisions must contain at least one inventory commitment")]
     58     MissingInventoryCommitments,
     59     #[error("inventory_commitments[{index}].bin_count must be greater than zero")]
     60     InvalidInventoryCommitmentCount { index: usize },
     61 }
     62 
     63 pub const ORDER_EVENT_CONTRACT_IDS: [&str; 5] = [
     64     "radroots.order.request.v1",
     65     "radroots.order.decision.v1",
     66     "radroots.order.revision_proposal.v1",
     67     "radroots.order.revision_decision.v1",
     68     "radroots.order.cancellation.v1",
     69 ];
     70 
     71 #[derive(Clone, Debug, PartialEq, Eq)]
     72 pub struct RadrootsOrderRequestRecord {
     73     pub event_id: RadrootsEventId,
     74     pub author_pubkey: RadrootsPublicKey,
     75     pub payload: RadrootsOrderRequest,
     76 }
     77 
     78 #[derive(Clone, Debug, PartialEq, Eq)]
     79 pub struct RadrootsOrderDecisionRecord {
     80     pub event_id: RadrootsEventId,
     81     pub author_pubkey: RadrootsPublicKey,
     82     pub counterparty_pubkey: RadrootsPublicKey,
     83     pub root_event_id: RadrootsEventId,
     84     pub prev_event_id: RadrootsEventId,
     85     pub payload: RadrootsOrderDecision,
     86 }
     87 
     88 #[derive(Clone, Debug, PartialEq, Eq)]
     89 pub struct RadrootsOrderRevisionProposalRecord {
     90     pub event_id: RadrootsEventId,
     91     pub author_pubkey: RadrootsPublicKey,
     92     pub counterparty_pubkey: RadrootsPublicKey,
     93     pub root_event_id: RadrootsEventId,
     94     pub prev_event_id: RadrootsEventId,
     95     pub payload: RadrootsOrderRevisionProposal,
     96 }
     97 
     98 #[derive(Clone, Debug, PartialEq, Eq)]
     99 pub struct RadrootsOrderRevisionDecisionRecord {
    100     pub event_id: RadrootsEventId,
    101     pub author_pubkey: RadrootsPublicKey,
    102     pub counterparty_pubkey: RadrootsPublicKey,
    103     pub root_event_id: RadrootsEventId,
    104     pub prev_event_id: RadrootsEventId,
    105     pub payload: RadrootsOrderRevisionDecision,
    106 }
    107 
    108 #[derive(Clone, Debug, PartialEq, Eq)]
    109 pub struct RadrootsOrderCancellationRecord {
    110     pub event_id: RadrootsEventId,
    111     pub author_pubkey: RadrootsPublicKey,
    112     pub counterparty_pubkey: RadrootsPublicKey,
    113     pub root_event_id: RadrootsEventId,
    114     pub prev_event_id: RadrootsEventId,
    115     pub payload: RadrootsOrderCancellation,
    116 }
    117 
    118 #[derive(Clone, Debug, PartialEq, Eq)]
    119 pub enum RadrootsOrderEventRecord {
    120     Request(RadrootsOrderRequestRecord),
    121     Decision(RadrootsOrderDecisionRecord),
    122     RevisionProposal(RadrootsOrderRevisionProposalRecord),
    123     RevisionDecision(RadrootsOrderRevisionDecisionRecord),
    124     Cancellation(RadrootsOrderCancellationRecord),
    125 }
    126 
    127 impl RadrootsOrderEventRecord {
    128     pub fn event_id(&self) -> &RadrootsEventId {
    129         match self {
    130             Self::Request(record) => &record.event_id,
    131             Self::Decision(record) => &record.event_id,
    132             Self::RevisionProposal(record) => &record.event_id,
    133             Self::RevisionDecision(record) => &record.event_id,
    134             Self::Cancellation(record) => &record.event_id,
    135         }
    136     }
    137 
    138     pub fn order_id(&self) -> &RadrootsOrderId {
    139         match self {
    140             Self::Request(record) => &record.payload.order_id,
    141             Self::Decision(record) => &record.payload.order_id,
    142             Self::RevisionProposal(record) => &record.payload.order_id,
    143             Self::RevisionDecision(record) => &record.payload.order_id,
    144             Self::Cancellation(record) => &record.payload.order_id,
    145         }
    146     }
    147 }
    148 
    149 #[cfg(feature = "serde_json")]
    150 #[derive(Debug, Error)]
    151 pub enum RadrootsOrderEventDecodeError {
    152     #[error("unsupported order event kind: {kind}")]
    153     UnsupportedKind { kind: u32 },
    154     #[error("invalid order event id: {0}")]
    155     InvalidEventId(RadrootsIdParseError),
    156     #[error("invalid order event author: {0}")]
    157     InvalidAuthor(RadrootsIdParseError),
    158     #[error("order event context is missing root event id")]
    159     MissingRootEventId,
    160     #[error("order event context is missing previous event id")]
    161     MissingPreviousEventId,
    162     #[error("{0}")]
    163     Envelope(#[from] RadrootsOrderEnvelopeParseError),
    164 }
    165 
    166 #[cfg(feature = "serde_json")]
    167 pub fn order_event_record_from_event(
    168     event: &RadrootsNostrEvent,
    169 ) -> Result<RadrootsOrderEventRecord, RadrootsOrderEventDecodeError> {
    170     let message_type = RadrootsOrderEventType::from_kind(event.kind)
    171         .ok_or(RadrootsOrderEventDecodeError::UnsupportedKind { kind: event.kind })?;
    172     let context = order_event_context_from_tags(message_type, &event.tags)?;
    173     let event_id =
    174         RadrootsEventId::parse(&event.id).map_err(RadrootsOrderEventDecodeError::InvalidEventId)?;
    175     let author_pubkey = RadrootsPublicKey::parse(&event.author)
    176         .map_err(RadrootsOrderEventDecodeError::InvalidAuthor)?;
    177 
    178     match message_type {
    179         RadrootsOrderEventType::OrderRequested => {
    180             let envelope = order_request_from_event(event)?;
    181             Ok(RadrootsOrderEventRecord::Request(
    182                 RadrootsOrderRequestRecord {
    183                     event_id,
    184                     author_pubkey,
    185                     payload: envelope.payload,
    186                 },
    187             ))
    188         }
    189         RadrootsOrderEventType::OrderDecision => {
    190             let envelope = order_decision_from_event(event)?;
    191             Ok(RadrootsOrderEventRecord::Decision(
    192                 RadrootsOrderDecisionRecord {
    193                     event_id,
    194                     author_pubkey,
    195                     counterparty_pubkey: context.counterparty_pubkey.clone(),
    196                     root_event_id: require_context_root_event_id(&context)?,
    197                     prev_event_id: require_context_prev_event_id(&context)?,
    198                     payload: envelope.payload,
    199                 },
    200             ))
    201         }
    202         RadrootsOrderEventType::OrderRevisionProposed => {
    203             let envelope = order_revision_proposal_from_event(event)?;
    204             Ok(RadrootsOrderEventRecord::RevisionProposal(
    205                 RadrootsOrderRevisionProposalRecord {
    206                     event_id,
    207                     author_pubkey,
    208                     counterparty_pubkey: context.counterparty_pubkey.clone(),
    209                     root_event_id: require_context_root_event_id(&context)?,
    210                     prev_event_id: require_context_prev_event_id(&context)?,
    211                     payload: envelope.payload,
    212                 },
    213             ))
    214         }
    215         RadrootsOrderEventType::OrderRevisionDecision => {
    216             let envelope = order_revision_decision_from_event(event)?;
    217             Ok(RadrootsOrderEventRecord::RevisionDecision(
    218                 RadrootsOrderRevisionDecisionRecord {
    219                     event_id,
    220                     author_pubkey,
    221                     counterparty_pubkey: context.counterparty_pubkey.clone(),
    222                     root_event_id: require_context_root_event_id(&context)?,
    223                     prev_event_id: require_context_prev_event_id(&context)?,
    224                     payload: envelope.payload,
    225                 },
    226             ))
    227         }
    228         RadrootsOrderEventType::OrderCancelled => {
    229             let envelope = order_cancellation_from_event(event)?;
    230             Ok(RadrootsOrderEventRecord::Cancellation(
    231                 RadrootsOrderCancellationRecord {
    232                     event_id,
    233                     author_pubkey,
    234                     counterparty_pubkey: context.counterparty_pubkey.clone(),
    235                     root_event_id: require_context_root_event_id(&context)?,
    236                     prev_event_id: require_context_prev_event_id(&context)?,
    237                     payload: envelope.payload,
    238                 },
    239             ))
    240         }
    241     }
    242 }
    243 
    244 #[cfg(feature = "event_store")]
    245 #[derive(Debug, Error)]
    246 pub enum RadrootsOrderStoreQueryError {
    247     #[error("{0}")]
    248     Store(#[from] RadrootsEventStoreError),
    249     #[error("stored order event {event_id} contains invalid tags_json: {source}")]
    250     InvalidStoredTagsJson {
    251         event_id: String,
    252         source: serde_json::Error,
    253     },
    254     #[error("stored order event {event_id} could not decode as an order record: {source}")]
    255     Decode {
    256         event_id: String,
    257         source: RadrootsOrderEventDecodeError,
    258     },
    259 }
    260 
    261 #[cfg(feature = "event_store")]
    262 #[derive(Clone, Debug, PartialEq, Eq)]
    263 pub struct RadrootsOrderProjectionQueryResult {
    264     pub projection: RadrootsOrderProjection,
    265     pub event_count: usize,
    266     pub limit_applied: u32,
    267     pub event_ids: Vec<RadrootsEventId>,
    268 }
    269 
    270 #[cfg(feature = "event_store")]
    271 pub async fn order_events_for_order_id(
    272     store: &RadrootsEventStore,
    273     order_id: &RadrootsOrderId,
    274     limit: u32,
    275 ) -> Result<Vec<RadrootsOrderEventRecord>, RadrootsOrderStoreQueryError> {
    276     let stored_events = store
    277         .events_by_contract_and_tag(&ORDER_EVENT_CONTRACT_IDS, TAG_D, order_id.as_str(), limit)
    278         .await?;
    279     let mut records = Vec::with_capacity(stored_events.len());
    280     for stored_event in stored_events {
    281         let event = stored_order_event_to_nostr_event(&stored_event)?;
    282         let record = order_event_record_from_event(&event).map_err(|source| {
    283             RadrootsOrderStoreQueryError::Decode {
    284                 event_id: stored_event.event_id.clone(),
    285                 source,
    286             }
    287         })?;
    288         if record.order_id() == order_id {
    289             records.push(record);
    290         }
    291     }
    292     Ok(records)
    293 }
    294 
    295 #[cfg(feature = "event_store")]
    296 pub async fn order_projection_for_order_id(
    297     store: &RadrootsEventStore,
    298     order_id: &RadrootsOrderId,
    299     limit: u32,
    300 ) -> Result<RadrootsOrderProjection, RadrootsOrderStoreQueryError> {
    301     order_projection_query_for_order_id(store, order_id, limit)
    302         .await
    303         .map(|result| result.projection)
    304 }
    305 
    306 #[cfg(feature = "event_store")]
    307 pub async fn order_projection_query_for_order_id(
    308     store: &RadrootsEventStore,
    309     order_id: &RadrootsOrderId,
    310     limit: u32,
    311 ) -> Result<RadrootsOrderProjectionQueryResult, RadrootsOrderStoreQueryError> {
    312     let records = order_events_for_order_id(store, order_id, limit).await?;
    313     let event_ids = records
    314         .iter()
    315         .map(|record| record.event_id().clone())
    316         .collect::<Vec<_>>();
    317     let event_count = records.len();
    318     Ok(RadrootsOrderProjectionQueryResult {
    319         projection: reduce_order_event_records(order_id, records),
    320         event_count,
    321         limit_applied: limit,
    322         event_ids,
    323     })
    324 }
    325 
    326 #[cfg(feature = "event_store")]
    327 fn stored_order_event_to_nostr_event(
    328     stored_event: &RadrootsStoredEvent,
    329 ) -> Result<RadrootsNostrEvent, RadrootsOrderStoreQueryError> {
    330     let tags = serde_json::from_str(&stored_event.tags_json).map_err(|source| {
    331         RadrootsOrderStoreQueryError::InvalidStoredTagsJson {
    332             event_id: stored_event.event_id.clone(),
    333             source,
    334         }
    335     })?;
    336     Ok(RadrootsNostrEvent {
    337         id: stored_event.event_id.clone(),
    338         author: stored_event.pubkey.clone(),
    339         created_at: stored_event.created_at,
    340         kind: stored_event.kind,
    341         tags,
    342         content: stored_event.content.clone(),
    343         sig: stored_event.sig.clone(),
    344     })
    345 }
    346 
    347 #[cfg(feature = "serde_json")]
    348 fn require_context_root_event_id(
    349     context: &radroots_events_codec::order::RadrootsOrderEventContext,
    350 ) -> Result<RadrootsEventId, RadrootsOrderEventDecodeError> {
    351     context
    352         .root_event_id
    353         .clone()
    354         .ok_or(RadrootsOrderEventDecodeError::MissingRootEventId)
    355 }
    356 
    357 #[cfg(feature = "serde_json")]
    358 fn require_context_prev_event_id(
    359     context: &radroots_events_codec::order::RadrootsOrderEventContext,
    360 ) -> Result<RadrootsEventId, RadrootsOrderEventDecodeError> {
    361     context
    362         .prev_event_id
    363         .clone()
    364         .ok_or(RadrootsOrderEventDecodeError::MissingPreviousEventId)
    365 }
    366 
    367 #[derive(Clone, Debug, PartialEq, Eq)]
    368 pub enum RadrootsOrderStatus {
    369     Missing,
    370     Requested,
    371     Accepted,
    372     Declined,
    373     Cancelled,
    374     Invalid,
    375 }
    376 
    377 #[derive(Clone, Debug, PartialEq, Eq)]
    378 pub enum RadrootsOrderIssue {
    379     MissingRequest,
    380     MultipleRequests { event_ids: Vec<RadrootsEventId> },
    381     RequestPayloadInvalid { event_id: RadrootsEventId },
    382     RequestOrderIdMismatch { event_id: RadrootsEventId },
    383     RequestAuthorMismatch { event_id: RadrootsEventId },
    384     RequestListingAddressInvalid { event_id: RadrootsEventId },
    385     RequestSellerListingMismatch { event_id: RadrootsEventId },
    386     DecisionPayloadInvalid { event_id: RadrootsEventId },
    387     DecisionOrderIdMismatch { event_id: RadrootsEventId },
    388     DecisionAuthorMismatch { event_id: RadrootsEventId },
    389     DecisionCounterpartyMismatch { event_id: RadrootsEventId },
    390     DecisionBuyerMismatch { event_id: RadrootsEventId },
    391     DecisionSellerMismatch { event_id: RadrootsEventId },
    392     DecisionListingAddressInvalid { event_id: RadrootsEventId },
    393     DecisionListingMismatch { event_id: RadrootsEventId },
    394     DecisionRootMismatch { event_id: RadrootsEventId },
    395     DecisionPreviousMismatch { event_id: RadrootsEventId },
    396     DecisionMissingInventoryCommitments { event_id: RadrootsEventId },
    397     DecisionInventoryCommitmentMismatch { event_id: RadrootsEventId },
    398     DecisionMissingReason { event_id: RadrootsEventId },
    399     ConflictingDecisions { event_ids: Vec<RadrootsEventId> },
    400     RevisionProposalPayloadInvalid { event_id: RadrootsEventId },
    401     RevisionProposalOrderIdMismatch { event_id: RadrootsEventId },
    402     RevisionProposalAuthorMismatch { event_id: RadrootsEventId },
    403     RevisionProposalCounterpartyMismatch { event_id: RadrootsEventId },
    404     RevisionProposalBuyerMismatch { event_id: RadrootsEventId },
    405     RevisionProposalSellerMismatch { event_id: RadrootsEventId },
    406     RevisionProposalListingAddressInvalid { event_id: RadrootsEventId },
    407     RevisionProposalListingMismatch { event_id: RadrootsEventId },
    408     RevisionProposalRootMismatch { event_id: RadrootsEventId },
    409     RevisionProposalPreviousMismatch { event_id: RadrootsEventId },
    410     RevisionDecisionWithoutProposal { event_id: RadrootsEventId },
    411     RevisionDecisionPayloadInvalid { event_id: RadrootsEventId },
    412     RevisionDecisionOrderIdMismatch { event_id: RadrootsEventId },
    413     RevisionDecisionAuthorMismatch { event_id: RadrootsEventId },
    414     RevisionDecisionCounterpartyMismatch { event_id: RadrootsEventId },
    415     RevisionDecisionBuyerMismatch { event_id: RadrootsEventId },
    416     RevisionDecisionSellerMismatch { event_id: RadrootsEventId },
    417     RevisionDecisionListingAddressInvalid { event_id: RadrootsEventId },
    418     RevisionDecisionListingMismatch { event_id: RadrootsEventId },
    419     RevisionDecisionRootMismatch { event_id: RadrootsEventId },
    420     RevisionDecisionPreviousMismatch { event_id: RadrootsEventId },
    421     RevisionDecisionRevisionIdMismatch { event_id: RadrootsEventId },
    422     CancellationWithoutCancellableOrder { event_id: RadrootsEventId },
    423     CancellationPayloadInvalid { event_id: RadrootsEventId },
    424     CancellationOrderIdMismatch { event_id: RadrootsEventId },
    425     CancellationAuthorMismatch { event_id: RadrootsEventId },
    426     CancellationCounterpartyMismatch { event_id: RadrootsEventId },
    427     CancellationBuyerMismatch { event_id: RadrootsEventId },
    428     CancellationSellerMismatch { event_id: RadrootsEventId },
    429     CancellationListingAddressInvalid { event_id: RadrootsEventId },
    430     CancellationListingMismatch { event_id: RadrootsEventId },
    431     CancellationRootMismatch { event_id: RadrootsEventId },
    432     CancellationPreviousMismatch { event_id: RadrootsEventId },
    433     ForkedLifecycle { event_ids: Vec<RadrootsEventId> },
    434 }
    435 
    436 #[derive(Clone, Debug, PartialEq, Eq)]
    437 pub struct RadrootsOrderProjection {
    438     pub order_id: RadrootsOrderId,
    439     pub status: RadrootsOrderStatus,
    440     pub request_event_id: Option<RadrootsEventId>,
    441     pub decision_event_id: Option<RadrootsEventId>,
    442     pub cancellation_event_id: Option<RadrootsEventId>,
    443     pub lifecycle_terminal: bool,
    444     pub economics: Option<RadrootsOrderEconomics>,
    445     pub agreement_event_id: Option<RadrootsEventId>,
    446     pub pending_revision_event_id: Option<RadrootsEventId>,
    447     pub listing_addr: Option<RadrootsListingAddress>,
    448     pub buyer_pubkey: Option<RadrootsPublicKey>,
    449     pub seller_pubkey: Option<RadrootsPublicKey>,
    450     pub last_event_id: Option<RadrootsEventId>,
    451     pub issues: Vec<RadrootsOrderIssue>,
    452 }
    453 
    454 #[cfg(feature = "serde_json")]
    455 #[derive(Debug, Error)]
    456 pub enum RadrootsOrderEconomicsDigestError {
    457     #[error("failed to serialize order economics for digest: {0}")]
    458     Serialize(#[from] serde_json::Error),
    459 }
    460 
    461 #[derive(Clone, Debug, PartialEq, Eq)]
    462 pub struct RadrootsListingInventoryBinAvailability {
    463     pub bin_id: RadrootsInventoryBinId,
    464     pub available_count: u64,
    465 }
    466 
    467 #[derive(Clone, Debug, PartialEq, Eq)]
    468 pub struct RadrootsListingInventoryOrderReservation {
    469     pub order_id: RadrootsOrderId,
    470     pub agreement_event_id: RadrootsEventId,
    471     pub bin_count: u64,
    472 }
    473 
    474 #[derive(Clone, Debug, PartialEq, Eq)]
    475 pub struct RadrootsListingInventoryBinAccounting {
    476     pub bin_id: RadrootsInventoryBinId,
    477     pub available_count: u64,
    478     pub accepted_reserved_count: u64,
    479     pub remaining_count: u64,
    480     pub over_reserved: bool,
    481     pub accepted_orders: Vec<RadrootsListingInventoryOrderReservation>,
    482 }
    483 
    484 #[derive(Clone, Debug, PartialEq, Eq)]
    485 pub enum RadrootsListingInventoryAccountingIssue {
    486     InvalidOrder {
    487         order_id: RadrootsOrderId,
    488         event_ids: Vec<RadrootsEventId>,
    489     },
    490     ArithmeticOverflow {
    491         bin_id: RadrootsInventoryBinId,
    492         event_ids: Vec<RadrootsEventId>,
    493     },
    494     UnknownInventoryBin {
    495         bin_id: RadrootsInventoryBinId,
    496         event_ids: Vec<RadrootsEventId>,
    497     },
    498     OverReserved {
    499         bin_id: RadrootsInventoryBinId,
    500         available_count: u64,
    501         reserved_count: u64,
    502         event_ids: Vec<RadrootsEventId>,
    503     },
    504 }
    505 
    506 #[derive(Clone, Debug, PartialEq, Eq)]
    507 pub struct RadrootsListingInventoryAccountingProjection {
    508     pub listing_addr: RadrootsListingAddress,
    509     pub listing_event_id: RadrootsEventId,
    510     pub bins: Vec<RadrootsListingInventoryBinAccounting>,
    511     pub declined_order_ids: Vec<RadrootsOrderId>,
    512     pub cancelled_order_ids: Vec<RadrootsOrderId>,
    513     pub invalid_event_ids: Vec<RadrootsEventId>,
    514     pub issues: Vec<RadrootsListingInventoryAccountingIssue>,
    515 }
    516 
    517 #[derive(Clone, Debug, PartialEq, Eq)]
    518 pub struct RadrootsOrderReductionInputs<I, J, K, L, M> {
    519     pub requests: I,
    520     pub decisions: J,
    521     pub revision_proposals: K,
    522     pub revision_decisions: L,
    523     pub cancellations: M,
    524 }
    525 
    526 #[derive(Clone, Debug, Default, PartialEq, Eq)]
    527 pub struct RadrootsGroupedOrderEventRecords {
    528     pub requests: Vec<RadrootsOrderRequestRecord>,
    529     pub decisions: Vec<RadrootsOrderDecisionRecord>,
    530     pub revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>,
    531     pub revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>,
    532     pub cancellations: Vec<RadrootsOrderCancellationRecord>,
    533 }
    534 
    535 #[derive(Clone, Debug, PartialEq, Eq)]
    536 pub struct RadrootsListingInventoryAccountingInputs<I, J, K, L, M, N> {
    537     pub bins: I,
    538     pub requests: J,
    539     pub decisions: K,
    540     pub revision_proposals: L,
    541     pub revision_decisions: M,
    542     pub cancellations: N,
    543 }
    544 
    545 #[derive(Clone, Debug, Default, PartialEq, Eq)]
    546 struct RadrootsListingInventoryAccountingRecords {
    547     bins: Vec<RadrootsListingInventoryBinAvailability>,
    548     requests: Vec<RadrootsOrderRequestRecord>,
    549     decisions: Vec<RadrootsOrderDecisionRecord>,
    550     revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>,
    551     revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>,
    552     cancellations: Vec<RadrootsOrderCancellationRecord>,
    553 }
    554 
    555 pub fn reduce_order_events<I, J, K, L, M>(
    556     order_id: &RadrootsOrderId,
    557     inputs: RadrootsOrderReductionInputs<I, J, K, L, M>,
    558 ) -> RadrootsOrderProjection
    559 where
    560     I: IntoIterator<Item = RadrootsOrderRequestRecord>,
    561     J: IntoIterator<Item = RadrootsOrderDecisionRecord>,
    562     K: IntoIterator<Item = RadrootsOrderRevisionProposalRecord>,
    563     L: IntoIterator<Item = RadrootsOrderRevisionDecisionRecord>,
    564     M: IntoIterator<Item = RadrootsOrderCancellationRecord>,
    565 {
    566     reduce_grouped_order_event_records(
    567         order_id,
    568         RadrootsGroupedOrderEventRecords {
    569             requests: inputs.requests.into_iter().collect(),
    570             decisions: inputs.decisions.into_iter().collect(),
    571             revision_proposals: inputs.revision_proposals.into_iter().collect(),
    572             revision_decisions: inputs.revision_decisions.into_iter().collect(),
    573             cancellations: inputs.cancellations.into_iter().collect(),
    574         },
    575     )
    576 }
    577 
    578 pub fn reduce_order_event_records<I>(
    579     order_id: &RadrootsOrderId,
    580     records: I,
    581 ) -> RadrootsOrderProjection
    582 where
    583     I: IntoIterator<Item = RadrootsOrderEventRecord>,
    584 {
    585     let mut seen_event_ids = Vec::new();
    586     let mut grouped = RadrootsGroupedOrderEventRecords::default();
    587 
    588     for record in records {
    589         let event_id = record.event_id().clone();
    590         if seen_event_ids.iter().any(|seen| seen == &event_id) {
    591             continue;
    592         }
    593         seen_event_ids.push(event_id);
    594         match record {
    595             RadrootsOrderEventRecord::Request(record) => grouped.requests.push(record),
    596             RadrootsOrderEventRecord::Decision(record) => grouped.decisions.push(record),
    597             RadrootsOrderEventRecord::RevisionProposal(record) => {
    598                 grouped.revision_proposals.push(record);
    599             }
    600             RadrootsOrderEventRecord::RevisionDecision(record) => {
    601                 grouped.revision_decisions.push(record);
    602             }
    603             RadrootsOrderEventRecord::Cancellation(record) => grouped.cancellations.push(record),
    604         }
    605     }
    606 
    607     reduce_grouped_order_event_records(order_id, grouped)
    608 }
    609 
    610 fn reduce_grouped_order_event_records(
    611     order_id: &RadrootsOrderId,
    612     records: RadrootsGroupedOrderEventRecords,
    613 ) -> RadrootsOrderProjection {
    614     let requests = unique_request_records(records.requests);
    615     let decisions = unique_decision_records(records.decisions);
    616     let revision_proposals = unique_revision_proposal_records(records.revision_proposals);
    617     let revision_decisions = unique_revision_decision_records(records.revision_decisions);
    618     let cancellations = unique_cancellation_records(records.cancellations);
    619     if requests.is_empty()
    620         && decisions.is_empty()
    621         && revision_proposals.is_empty()
    622         && revision_decisions.is_empty()
    623         && cancellations.is_empty()
    624     {
    625         return empty_projection(order_id, RadrootsOrderStatus::Missing, false);
    626     }
    627 
    628     let mut issues = Vec::new();
    629     let mut valid_requests = Vec::new();
    630     for request in requests {
    631         if validate_order_request_record(order_id, &request, &mut issues) {
    632             valid_requests.push(request);
    633         }
    634     }
    635 
    636     if valid_requests.len() > 1 {
    637         let mut event_ids = valid_requests
    638             .iter()
    639             .map(|request| request.event_id.clone())
    640             .collect::<Vec<_>>();
    641         sort_and_dedup_values(&mut event_ids);
    642         issues.push(RadrootsOrderIssue::MultipleRequests { event_ids });
    643     }
    644 
    645     let Some(request) = valid_requests.first() else {
    646         if !decisions.is_empty()
    647             || !revision_proposals.is_empty()
    648             || !revision_decisions.is_empty()
    649             || !cancellations.is_empty()
    650         {
    651             issues.push(RadrootsOrderIssue::MissingRequest);
    652         }
    653         return invalid_projection(order_id, None, issues);
    654     };
    655 
    656     if valid_requests.len() > 1 {
    657         return invalid_projection(order_id, Some(request), issues);
    658     }
    659 
    660     let mut valid_decisions = Vec::new();
    661     for decision in decisions {
    662         if validate_order_decision_record(request, &decision, &mut issues) {
    663             valid_decisions.push(decision);
    664         }
    665     }
    666 
    667     let mut valid_revision_proposals = Vec::new();
    668     for proposal in revision_proposals {
    669         if validate_order_revision_proposal_record(request, &proposal, &mut issues) {
    670             valid_revision_proposals.push(proposal);
    671         }
    672     }
    673 
    674     let mut valid_revision_decisions = Vec::new();
    675     for decision in revision_decisions {
    676         if validate_order_revision_decision_record(request, &decision, &mut issues) {
    677             valid_revision_decisions.push(decision);
    678         }
    679     }
    680 
    681     let mut valid_cancellations = Vec::new();
    682     for cancellation in cancellations {
    683         if validate_order_cancellation_record(request, &cancellation, &mut issues) {
    684             valid_cancellations.push(cancellation);
    685         }
    686     }
    687 
    688     if !issues.is_empty() {
    689         return invalid_projection(order_id, Some(request), issues);
    690     }
    691 
    692     if valid_cancellations.len() > 1 {
    693         let mut event_ids = valid_cancellations
    694             .iter()
    695             .map(|cancellation| cancellation.event_id.clone())
    696             .collect::<Vec<_>>();
    697         sort_and_dedup_values(&mut event_ids);
    698         return invalid_projection(
    699             order_id,
    700             Some(request),
    701             vec![RadrootsOrderIssue::ForkedLifecycle { event_ids }],
    702         );
    703     }
    704 
    705     if let Some(cancellation) = valid_cancellations.first() {
    706         return cancelled_projection(
    707             order_id,
    708             request,
    709             cancellation,
    710             &valid_decisions,
    711             &valid_revision_proposals,
    712             &valid_revision_decisions,
    713         );
    714     }
    715 
    716     match valid_decisions.len() {
    717         0 => negotiation_projection(
    718             order_id,
    719             request,
    720             &valid_revision_proposals,
    721             &valid_revision_decisions,
    722         ),
    723         1 => decided_projection(
    724             order_id,
    725             request,
    726             &valid_decisions[0],
    727             &valid_revision_proposals,
    728             &valid_revision_decisions,
    729         ),
    730         _ => {
    731             let mut event_ids = valid_decisions
    732                 .iter()
    733                 .map(|decision| decision.event_id.clone())
    734                 .collect::<Vec<_>>();
    735             sort_and_dedup_values(&mut event_ids);
    736             invalid_projection(
    737                 order_id,
    738                 Some(request),
    739                 vec![RadrootsOrderIssue::ConflictingDecisions { event_ids }],
    740             )
    741         }
    742     }
    743 }
    744 
    745 pub fn reduce_listing_inventory_accounting<I, J, K, L, M, N>(
    746     listing_addr: &RadrootsListingAddress,
    747     listing_event_id: &RadrootsEventId,
    748     inputs: RadrootsListingInventoryAccountingInputs<I, J, K, L, M, N>,
    749 ) -> RadrootsListingInventoryAccountingProjection
    750 where
    751     I: IntoIterator<Item = RadrootsListingInventoryBinAvailability>,
    752     J: IntoIterator<Item = RadrootsOrderRequestRecord>,
    753     K: IntoIterator<Item = RadrootsOrderDecisionRecord>,
    754     L: IntoIterator<Item = RadrootsOrderRevisionProposalRecord>,
    755     M: IntoIterator<Item = RadrootsOrderRevisionDecisionRecord>,
    756     N: IntoIterator<Item = RadrootsOrderCancellationRecord>,
    757 {
    758     reduce_listing_inventory_accounting_records(
    759         listing_addr,
    760         listing_event_id,
    761         RadrootsListingInventoryAccountingRecords {
    762             bins: inputs.bins.into_iter().collect(),
    763             requests: inputs.requests.into_iter().collect(),
    764             decisions: inputs.decisions.into_iter().collect(),
    765             revision_proposals: inputs.revision_proposals.into_iter().collect(),
    766             revision_decisions: inputs.revision_decisions.into_iter().collect(),
    767             cancellations: inputs.cancellations.into_iter().collect(),
    768         },
    769     )
    770 }
    771 
    772 fn reduce_listing_inventory_accounting_records(
    773     listing_addr: &RadrootsListingAddress,
    774     listing_event_id: &RadrootsEventId,
    775     records: RadrootsListingInventoryAccountingRecords,
    776 ) -> RadrootsListingInventoryAccountingProjection {
    777     let (mut bins, mut issues) = normalized_listing_inventory_bins(records.bins);
    778     let requests = unique_request_records(records.requests)
    779         .into_iter()
    780         .filter(|request| request.payload.listing_addr.as_str() == listing_addr.as_str())
    781         .collect::<Vec<_>>();
    782     let decisions = unique_decision_records(records.decisions)
    783         .into_iter()
    784         .filter(|decision| decision.payload.listing_addr.as_str() == listing_addr.as_str())
    785         .collect::<Vec<_>>();
    786     let revision_proposals = unique_revision_proposal_records(records.revision_proposals)
    787         .into_iter()
    788         .filter(|proposal| proposal.payload.listing_addr.as_str() == listing_addr.as_str())
    789         .collect::<Vec<_>>();
    790     let revision_decisions = unique_revision_decision_records(records.revision_decisions)
    791         .into_iter()
    792         .filter(|decision| decision.payload.listing_addr.as_str() == listing_addr.as_str())
    793         .collect::<Vec<_>>();
    794     let cancellations = unique_cancellation_records(records.cancellations)
    795         .into_iter()
    796         .filter(|cancellation| cancellation.payload.listing_addr.as_str() == listing_addr.as_str())
    797         .collect::<Vec<_>>();
    798     let mut order_ids = listing_order_ids(
    799         &requests,
    800         &decisions,
    801         &revision_proposals,
    802         &revision_decisions,
    803         &cancellations,
    804     );
    805     let mut declined_order_ids = Vec::new();
    806     let mut cancelled_order_ids = Vec::new();
    807     let mut invalid_event_ids = Vec::new();
    808 
    809     for order_id in order_ids.drain(..) {
    810         let order_requests = requests
    811             .iter()
    812             .filter(|request| request.payload.order_id == order_id)
    813             .cloned()
    814             .collect::<Vec<_>>();
    815         let order_decisions = decisions
    816             .iter()
    817             .filter(|decision| decision.payload.order_id == order_id)
    818             .cloned()
    819             .collect::<Vec<_>>();
    820         let order_revision_proposals = revision_proposals
    821             .iter()
    822             .filter(|proposal| proposal.payload.order_id == order_id)
    823             .cloned()
    824             .collect::<Vec<_>>();
    825         let order_revision_decisions = revision_decisions
    826             .iter()
    827             .filter(|decision| decision.payload.order_id == order_id)
    828             .cloned()
    829             .collect::<Vec<_>>();
    830         let order_cancellations = cancellations
    831             .iter()
    832             .filter(|cancellation| cancellation.payload.order_id == order_id)
    833             .cloned()
    834             .collect::<Vec<_>>();
    835         let projection = reduce_order_events(
    836             &order_id,
    837             RadrootsOrderReductionInputs {
    838                 requests: order_requests.clone(),
    839                 decisions: order_decisions.clone(),
    840                 revision_proposals: order_revision_proposals.clone(),
    841                 revision_decisions: order_revision_decisions.clone(),
    842                 cancellations: order_cancellations.clone(),
    843             },
    844         );
    845         match projection.status {
    846             RadrootsOrderStatus::Accepted => {
    847                 for (agreement_event_id, economics) in projection
    848                     .agreement_event_id
    849                     .iter()
    850                     .zip(projection.economics.iter())
    851                 {
    852                     add_accepted_inventory_reservations_from_economics(
    853                         &mut bins,
    854                         &order_id,
    855                         agreement_event_id,
    856                         economics,
    857                         &mut issues,
    858                     );
    859                 }
    860             }
    861             RadrootsOrderStatus::Cancelled => cancelled_order_ids.push(order_id),
    862             RadrootsOrderStatus::Declined => declined_order_ids.push(order_id),
    863             RadrootsOrderStatus::Invalid => {
    864                 let mut event_ids = projection_issue_event_ids(&projection.issues);
    865                 if event_ids.is_empty() {
    866                     event_ids = fallback_order_event_ids(
    867                         &order_requests,
    868                         &order_decisions,
    869                         &order_revision_proposals,
    870                         &order_revision_decisions,
    871                         &order_cancellations,
    872                     );
    873                 }
    874                 invalid_event_ids.extend(event_ids.iter().cloned());
    875                 issues.push(RadrootsListingInventoryAccountingIssue::InvalidOrder {
    876                     order_id,
    877                     event_ids,
    878                 });
    879             }
    880             RadrootsOrderStatus::Missing | RadrootsOrderStatus::Requested => {}
    881         }
    882     }
    883 
    884     sort_and_dedup_values(&mut declined_order_ids);
    885     sort_and_dedup_values(&mut cancelled_order_ids);
    886     sort_and_dedup_values(&mut invalid_event_ids);
    887     finish_inventory_accounting_bins(&mut bins, &mut issues);
    888     issues.sort_by(inventory_issue_sort_key);
    889     RadrootsListingInventoryAccountingProjection {
    890         listing_addr: listing_addr.clone(),
    891         listing_event_id: listing_event_id.clone(),
    892         bins,
    893         declined_order_ids,
    894         cancelled_order_ids,
    895         invalid_event_ids,
    896         issues,
    897     }
    898 }
    899 
    900 fn fallback_order_event_ids(
    901     requests: &[RadrootsOrderRequestRecord],
    902     decisions: &[RadrootsOrderDecisionRecord],
    903     revision_proposals: &[RadrootsOrderRevisionProposalRecord],
    904     revision_decisions: &[RadrootsOrderRevisionDecisionRecord],
    905     cancellations: &[RadrootsOrderCancellationRecord],
    906 ) -> Vec<RadrootsEventId> {
    907     let mut event_ids = Vec::new();
    908     event_ids.extend(requests.iter().map(|request| request.event_id.clone()));
    909     event_ids.extend(decisions.iter().map(|decision| decision.event_id.clone()));
    910     event_ids.extend(
    911         revision_proposals
    912             .iter()
    913             .map(|proposal| proposal.event_id.clone()),
    914     );
    915     event_ids.extend(
    916         revision_decisions
    917             .iter()
    918             .map(|decision| decision.event_id.clone()),
    919     );
    920     event_ids.extend(
    921         cancellations
    922             .iter()
    923             .map(|cancellation| cancellation.event_id.clone()),
    924     );
    925     sort_and_dedup_values(&mut event_ids);
    926     event_ids
    927 }
    928 
    929 pub fn canonicalize_order_request_for_signer(
    930     mut request: RadrootsOrderRequest,
    931     signer_pubkey: &str,
    932 ) -> Result<RadrootsOrderRequest, RadrootsOrderCanonicalizationError> {
    933     let order_id = request.order_id.clone();
    934     let listing_addr_raw = request.listing_addr.to_string();
    935     let listing_addr = parse_public_listing_addr(&listing_addr_raw)?;
    936 
    937     let buyer_pubkey = request.buyer_pubkey.clone();
    938     if buyer_pubkey.as_str() != signer_pubkey {
    939         return Err(RadrootsOrderCanonicalizationError::InvalidBuyerSigner);
    940     }
    941 
    942     let seller_pubkey = request.seller_pubkey.clone();
    943     if seller_pubkey != listing_addr.seller_pubkey {
    944         return Err(RadrootsOrderCanonicalizationError::InvalidSellerListing);
    945     }
    946 
    947     canonicalize_items(&mut request.items)?;
    948     request.economics.canonicalize();
    949     request.order_id = order_id;
    950     request.listing_addr = listing_addr.address;
    951     request.buyer_pubkey = buyer_pubkey;
    952     request.seller_pubkey = seller_pubkey;
    953     Ok(request)
    954 }
    955 
    956 pub fn canonicalize_order_decision_for_signer(
    957     mut decision_event: RadrootsOrderDecision,
    958     signer_pubkey: &str,
    959 ) -> Result<RadrootsOrderDecision, RadrootsOrderCanonicalizationError> {
    960     let order_id = decision_event.order_id.clone();
    961     let listing_addr_raw = decision_event.listing_addr.to_string();
    962     let listing_addr = parse_public_listing_addr(&listing_addr_raw)?;
    963 
    964     let seller_pubkey = decision_event.seller_pubkey.clone();
    965     if seller_pubkey.as_str() != signer_pubkey || seller_pubkey != listing_addr.seller_pubkey {
    966         return Err(RadrootsOrderCanonicalizationError::InvalidSellerListing);
    967     }
    968 
    969     let buyer_pubkey = decision_event.buyer_pubkey.clone();
    970     canonicalize_decision(&mut decision_event.decision)?;
    971 
    972     decision_event.order_id = order_id;
    973     decision_event.listing_addr = listing_addr.address;
    974     decision_event.buyer_pubkey = buyer_pubkey;
    975     decision_event.seller_pubkey = seller_pubkey;
    976     Ok(decision_event)
    977 }
    978 
    979 #[cfg(feature = "serde_json")]
    980 pub fn radroots_order_economics_digest(
    981     economics: &RadrootsOrderEconomics,
    982 ) -> Result<String, RadrootsOrderEconomicsDigestError> {
    983     let encoded = serde_json::to_vec(economics)?;
    984     let digest = Sha256::digest(encoded);
    985     let mut value = String::from("sha256:");
    986     value.push_str(&hex::encode(digest));
    987     Ok(value)
    988 }
    989 
    990 fn cancelled_projection(
    991     order_id: &RadrootsOrderId,
    992     request: &RadrootsOrderRequestRecord,
    993     cancellation: &RadrootsOrderCancellationRecord,
    994     decisions: &[RadrootsOrderDecisionRecord],
    995     revision_proposals: &[RadrootsOrderRevisionProposalRecord],
    996     revision_decisions: &[RadrootsOrderRevisionDecisionRecord],
    997 ) -> RadrootsOrderProjection {
    998     if !decisions.is_empty() || !revision_decisions.is_empty() {
    999         let mut event_ids = Vec::new();
   1000         event_ids.extend(decisions.iter().map(|decision| decision.event_id.clone()));
   1001         event_ids.extend(
   1002             revision_decisions
   1003                 .iter()
   1004                 .map(|decision| decision.event_id.clone()),
   1005         );
   1006         event_ids.push(cancellation.event_id.clone());
   1007         sort_and_dedup_values(&mut event_ids);
   1008         return invalid_projection(
   1009             order_id,
   1010             Some(request),
   1011             vec![RadrootsOrderIssue::ForkedLifecycle { event_ids }],
   1012         );
   1013     }
   1014     if revision_proposals.len() > 1 {
   1015         let mut event_ids = revision_proposals
   1016             .iter()
   1017             .map(|proposal| proposal.event_id.clone())
   1018             .collect::<Vec<_>>();
   1019         event_ids.push(cancellation.event_id.clone());
   1020         sort_and_dedup_values(&mut event_ids);
   1021         return invalid_projection(
   1022             order_id,
   1023             Some(request),
   1024             vec![RadrootsOrderIssue::ForkedLifecycle { event_ids }],
   1025         );
   1026     }
   1027     let expected_prev_event_id = revision_proposals
   1028         .first()
   1029         .map(|proposal| &proposal.event_id)
   1030         .unwrap_or(&request.event_id);
   1031     if &cancellation.prev_event_id != expected_prev_event_id {
   1032         return invalid_projection(
   1033             order_id,
   1034             Some(request),
   1035             vec![RadrootsOrderIssue::CancellationPreviousMismatch {
   1036                 event_id: cancellation.event_id.clone(),
   1037             }],
   1038         );
   1039     }
   1040 
   1041     let mut projection = request_projection(order_id, request, RadrootsOrderStatus::Cancelled);
   1042     projection.cancellation_event_id = Some(cancellation.event_id.clone());
   1043     projection.lifecycle_terminal = true;
   1044     projection.last_event_id = Some(cancellation.event_id.clone());
   1045     projection
   1046 }
   1047 
   1048 fn negotiation_projection(
   1049     order_id: &RadrootsOrderId,
   1050     request: &RadrootsOrderRequestRecord,
   1051     revision_proposals: &[RadrootsOrderRevisionProposalRecord],
   1052     revision_decisions: &[RadrootsOrderRevisionDecisionRecord],
   1053 ) -> RadrootsOrderProjection {
   1054     match revision_proposals.len() {
   1055         0 => {
   1056             if revision_decisions.is_empty() {
   1057                 request_projection(order_id, request, RadrootsOrderStatus::Requested)
   1058             } else {
   1059                 invalid_projection(
   1060                     order_id,
   1061                     Some(request),
   1062                     revision_decisions
   1063                         .iter()
   1064                         .map(
   1065                             |decision| RadrootsOrderIssue::RevisionDecisionWithoutProposal {
   1066                                 event_id: decision.event_id.clone(),
   1067                             },
   1068                         )
   1069                         .collect(),
   1070                 )
   1071             }
   1072         }
   1073         1 => {
   1074             let proposal = &revision_proposals[0];
   1075             if proposal.prev_event_id != request.event_id {
   1076                 return invalid_projection(
   1077                     order_id,
   1078                     Some(request),
   1079                     vec![RadrootsOrderIssue::RevisionProposalPreviousMismatch {
   1080                         event_id: proposal.event_id.clone(),
   1081                     }],
   1082                 );
   1083             }
   1084             match revision_decisions.len() {
   1085                 0 => {
   1086                     let mut projection =
   1087                         request_projection(order_id, request, RadrootsOrderStatus::Requested);
   1088                     projection.pending_revision_event_id = Some(proposal.event_id.clone());
   1089                     projection.economics = Some(proposal.payload.economics.clone());
   1090                     projection.last_event_id = Some(proposal.event_id.clone());
   1091                     projection
   1092                 }
   1093                 1 => revision_decision_projection(
   1094                     order_id,
   1095                     request,
   1096                     proposal,
   1097                     &revision_decisions[0],
   1098                 ),
   1099                 _ => {
   1100                     let mut event_ids = revision_decisions
   1101                         .iter()
   1102                         .map(|decision| decision.event_id.clone())
   1103                         .collect::<Vec<_>>();
   1104                     sort_and_dedup_values(&mut event_ids);
   1105                     invalid_projection(
   1106                         order_id,
   1107                         Some(request),
   1108                         vec![RadrootsOrderIssue::ForkedLifecycle { event_ids }],
   1109                     )
   1110                 }
   1111             }
   1112         }
   1113         _ => {
   1114             let mut event_ids = revision_proposals
   1115                 .iter()
   1116                 .map(|proposal| proposal.event_id.clone())
   1117                 .collect::<Vec<_>>();
   1118             sort_and_dedup_values(&mut event_ids);
   1119             invalid_projection(
   1120                 order_id,
   1121                 Some(request),
   1122                 vec![RadrootsOrderIssue::ForkedLifecycle { event_ids }],
   1123             )
   1124         }
   1125     }
   1126 }
   1127 
   1128 fn decided_projection(
   1129     order_id: &RadrootsOrderId,
   1130     request: &RadrootsOrderRequestRecord,
   1131     decision: &RadrootsOrderDecisionRecord,
   1132     revision_proposals: &[RadrootsOrderRevisionProposalRecord],
   1133     revision_decisions: &[RadrootsOrderRevisionDecisionRecord],
   1134 ) -> RadrootsOrderProjection {
   1135     if !revision_proposals.is_empty() || !revision_decisions.is_empty() {
   1136         let mut event_ids = Vec::new();
   1137         event_ids.extend(
   1138             revision_proposals
   1139                 .iter()
   1140                 .map(|proposal| proposal.event_id.clone()),
   1141         );
   1142         event_ids.extend(
   1143             revision_decisions
   1144                 .iter()
   1145                 .map(|decision| decision.event_id.clone()),
   1146         );
   1147         event_ids.push(decision.event_id.clone());
   1148         sort_and_dedup_values(&mut event_ids);
   1149         return invalid_projection(
   1150             order_id,
   1151             Some(request),
   1152             vec![RadrootsOrderIssue::ForkedLifecycle { event_ids }],
   1153         );
   1154     }
   1155 
   1156     match &decision.payload.decision {
   1157         RadrootsOrderDecisionOutcome::Accepted { .. } => {
   1158             let mut projection =
   1159                 request_projection(order_id, request, RadrootsOrderStatus::Accepted);
   1160             projection.decision_event_id = Some(decision.event_id.clone());
   1161             projection.lifecycle_terminal = true;
   1162             projection.economics = Some(request.payload.economics.clone());
   1163             projection.agreement_event_id = Some(decision.event_id.clone());
   1164             projection.last_event_id = Some(decision.event_id.clone());
   1165             projection
   1166         }
   1167         RadrootsOrderDecisionOutcome::Declined { .. } => {
   1168             let mut projection =
   1169                 request_projection(order_id, request, RadrootsOrderStatus::Declined);
   1170             projection.decision_event_id = Some(decision.event_id.clone());
   1171             projection.lifecycle_terminal = true;
   1172             projection.last_event_id = Some(decision.event_id.clone());
   1173             projection
   1174         }
   1175     }
   1176 }
   1177 
   1178 fn revision_decision_projection(
   1179     order_id: &RadrootsOrderId,
   1180     request: &RadrootsOrderRequestRecord,
   1181     proposal: &RadrootsOrderRevisionProposalRecord,
   1182     decision: &RadrootsOrderRevisionDecisionRecord,
   1183 ) -> RadrootsOrderProjection {
   1184     if decision.prev_event_id != proposal.event_id {
   1185         return invalid_projection(
   1186             order_id,
   1187             Some(request),
   1188             vec![RadrootsOrderIssue::RevisionDecisionPreviousMismatch {
   1189                 event_id: decision.event_id.clone(),
   1190             }],
   1191         );
   1192     }
   1193     if decision.payload.revision_id != proposal.payload.revision_id {
   1194         return invalid_projection(
   1195             order_id,
   1196             Some(request),
   1197             vec![RadrootsOrderIssue::RevisionDecisionRevisionIdMismatch {
   1198                 event_id: decision.event_id.clone(),
   1199             }],
   1200         );
   1201     }
   1202 
   1203     match &decision.payload.decision {
   1204         RadrootsOrderRevisionOutcome::Accepted => {
   1205             let mut projection =
   1206                 request_projection(order_id, request, RadrootsOrderStatus::Accepted);
   1207             projection.economics = Some(proposal.payload.economics.clone());
   1208             projection.agreement_event_id = Some(decision.event_id.clone());
   1209             projection.lifecycle_terminal = true;
   1210             projection.last_event_id = Some(decision.event_id.clone());
   1211             projection
   1212         }
   1213         RadrootsOrderRevisionOutcome::Declined { .. } => {
   1214             let mut projection =
   1215                 request_projection(order_id, request, RadrootsOrderStatus::Declined);
   1216             projection.lifecycle_terminal = true;
   1217             projection.pending_revision_event_id = Some(proposal.event_id.clone());
   1218             projection.last_event_id = Some(decision.event_id.clone());
   1219             projection
   1220         }
   1221     }
   1222 }
   1223 
   1224 fn request_projection(
   1225     order_id: &RadrootsOrderId,
   1226     request: &RadrootsOrderRequestRecord,
   1227     status: RadrootsOrderStatus,
   1228 ) -> RadrootsOrderProjection {
   1229     RadrootsOrderProjection {
   1230         order_id: order_id.clone(),
   1231         status,
   1232         request_event_id: Some(request.event_id.clone()),
   1233         decision_event_id: None,
   1234         cancellation_event_id: None,
   1235         lifecycle_terminal: false,
   1236         economics: Some(request.payload.economics.clone()),
   1237         agreement_event_id: None,
   1238         pending_revision_event_id: None,
   1239         listing_addr: Some(request.payload.listing_addr.clone()),
   1240         buyer_pubkey: Some(request.payload.buyer_pubkey.clone()),
   1241         seller_pubkey: Some(request.payload.seller_pubkey.clone()),
   1242         last_event_id: Some(request.event_id.clone()),
   1243         issues: Vec::new(),
   1244     }
   1245 }
   1246 
   1247 fn invalid_projection(
   1248     order_id: &RadrootsOrderId,
   1249     request: Option<&RadrootsOrderRequestRecord>,
   1250     mut issues: Vec<RadrootsOrderIssue>,
   1251 ) -> RadrootsOrderProjection {
   1252     issues.sort_by(order_issue_sort_key);
   1253     let last_event_id = projection_issue_event_ids(&issues).into_iter().last();
   1254     match request {
   1255         Some(request) => {
   1256             let mut projection =
   1257                 request_projection(order_id, request, RadrootsOrderStatus::Invalid);
   1258             projection.lifecycle_terminal = true;
   1259             projection.last_event_id = last_event_id.or_else(|| Some(request.event_id.clone()));
   1260             projection.issues = issues;
   1261             projection
   1262         }
   1263         None => {
   1264             let mut projection = empty_projection(order_id, RadrootsOrderStatus::Invalid, true);
   1265             projection.last_event_id = last_event_id;
   1266             projection.issues = issues;
   1267             projection
   1268         }
   1269     }
   1270 }
   1271 
   1272 fn empty_projection(
   1273     order_id: &RadrootsOrderId,
   1274     status: RadrootsOrderStatus,
   1275     lifecycle_terminal: bool,
   1276 ) -> RadrootsOrderProjection {
   1277     RadrootsOrderProjection {
   1278         order_id: order_id.clone(),
   1279         status,
   1280         request_event_id: None,
   1281         decision_event_id: None,
   1282         cancellation_event_id: None,
   1283         lifecycle_terminal,
   1284         economics: None,
   1285         agreement_event_id: None,
   1286         pending_revision_event_id: None,
   1287         listing_addr: None,
   1288         buyer_pubkey: None,
   1289         seller_pubkey: None,
   1290         last_event_id: None,
   1291         issues: Vec::new(),
   1292     }
   1293 }
   1294 
   1295 fn validate_order_request_record(
   1296     order_id: &RadrootsOrderId,
   1297     request: &RadrootsOrderRequestRecord,
   1298     issues: &mut Vec<RadrootsOrderIssue>,
   1299 ) -> bool {
   1300     let mut valid = true;
   1301     if request.payload.validate().is_err() {
   1302         issues.push(RadrootsOrderIssue::RequestPayloadInvalid {
   1303             event_id: request.event_id.clone(),
   1304         });
   1305         valid = false;
   1306     }
   1307     if request.payload.order_id.as_str() != order_id.as_str() {
   1308         issues.push(RadrootsOrderIssue::RequestOrderIdMismatch {
   1309             event_id: request.event_id.clone(),
   1310         });
   1311         valid = false;
   1312     }
   1313     if request.author_pubkey != request.payload.buyer_pubkey {
   1314         issues.push(RadrootsOrderIssue::RequestAuthorMismatch {
   1315             event_id: request.event_id.clone(),
   1316         });
   1317         valid = false;
   1318     }
   1319     match parse_public_listing_addr(&request.payload.listing_addr) {
   1320         Ok(listing_addr) => {
   1321             if listing_addr.seller_pubkey != request.payload.seller_pubkey {
   1322                 issues.push(RadrootsOrderIssue::RequestSellerListingMismatch {
   1323                     event_id: request.event_id.clone(),
   1324                 });
   1325                 valid = false;
   1326             }
   1327         }
   1328         Err(_) => {
   1329             issues.push(RadrootsOrderIssue::RequestListingAddressInvalid {
   1330                 event_id: request.event_id.clone(),
   1331             });
   1332             valid = false;
   1333         }
   1334     }
   1335     valid
   1336 }
   1337 
   1338 fn validate_order_decision_record(
   1339     request: &RadrootsOrderRequestRecord,
   1340     decision: &RadrootsOrderDecisionRecord,
   1341     issues: &mut Vec<RadrootsOrderIssue>,
   1342 ) -> bool {
   1343     let mut valid = true;
   1344     if decision_payload_issue(&decision.payload.decision, &decision.event_id, issues) {
   1345         valid = false;
   1346     }
   1347     if decision.payload.validate().is_err() {
   1348         issues.push(RadrootsOrderIssue::DecisionPayloadInvalid {
   1349             event_id: decision.event_id.clone(),
   1350         });
   1351         valid = false;
   1352     }
   1353     if decision.payload.order_id != request.payload.order_id {
   1354         issues.push(RadrootsOrderIssue::DecisionOrderIdMismatch {
   1355             event_id: decision.event_id.clone(),
   1356         });
   1357         valid = false;
   1358     }
   1359     if decision.author_pubkey != decision.payload.seller_pubkey {
   1360         issues.push(RadrootsOrderIssue::DecisionAuthorMismatch {
   1361             event_id: decision.event_id.clone(),
   1362         });
   1363         valid = false;
   1364     }
   1365     if decision.counterparty_pubkey != request.payload.buyer_pubkey {
   1366         issues.push(RadrootsOrderIssue::DecisionCounterpartyMismatch {
   1367             event_id: decision.event_id.clone(),
   1368         });
   1369         valid = false;
   1370     }
   1371     if decision.payload.buyer_pubkey != request.payload.buyer_pubkey {
   1372         issues.push(RadrootsOrderIssue::DecisionBuyerMismatch {
   1373             event_id: decision.event_id.clone(),
   1374         });
   1375         valid = false;
   1376     }
   1377     if decision.payload.seller_pubkey != request.payload.seller_pubkey {
   1378         issues.push(RadrootsOrderIssue::DecisionSellerMismatch {
   1379             event_id: decision.event_id.clone(),
   1380         });
   1381         valid = false;
   1382     }
   1383     match parse_public_listing_addr(&decision.payload.listing_addr) {
   1384         Ok(listing_addr) => {
   1385             if decision.payload.listing_addr != request.payload.listing_addr
   1386                 || listing_addr.seller_pubkey != decision.payload.seller_pubkey
   1387             {
   1388                 issues.push(RadrootsOrderIssue::DecisionListingMismatch {
   1389                     event_id: decision.event_id.clone(),
   1390                 });
   1391                 valid = false;
   1392             }
   1393         }
   1394         Err(_) => {
   1395             issues.push(RadrootsOrderIssue::DecisionListingAddressInvalid {
   1396                 event_id: decision.event_id.clone(),
   1397             });
   1398             valid = false;
   1399         }
   1400     }
   1401     if decision.root_event_id != request.event_id {
   1402         issues.push(RadrootsOrderIssue::DecisionRootMismatch {
   1403             event_id: decision.event_id.clone(),
   1404         });
   1405         valid = false;
   1406     }
   1407     if decision.prev_event_id != request.event_id {
   1408         issues.push(RadrootsOrderIssue::DecisionPreviousMismatch {
   1409             event_id: decision.event_id.clone(),
   1410         });
   1411         valid = false;
   1412     }
   1413     if let RadrootsOrderDecisionOutcome::Accepted {
   1414         inventory_commitments,
   1415     } = &decision.payload.decision
   1416         && decision.payload.validate().is_ok()
   1417         && !inventory_commitments_match_request(&request.payload.items, inventory_commitments)
   1418     {
   1419         issues.push(RadrootsOrderIssue::DecisionInventoryCommitmentMismatch {
   1420             event_id: decision.event_id.clone(),
   1421         });
   1422         valid = false;
   1423     }
   1424     valid
   1425 }
   1426 
   1427 fn validate_order_revision_proposal_record(
   1428     request: &RadrootsOrderRequestRecord,
   1429     proposal: &RadrootsOrderRevisionProposalRecord,
   1430     issues: &mut Vec<RadrootsOrderIssue>,
   1431 ) -> bool {
   1432     let mut valid = true;
   1433     if proposal.payload.validate().is_err() {
   1434         issues.push(RadrootsOrderIssue::RevisionProposalPayloadInvalid {
   1435             event_id: proposal.event_id.clone(),
   1436         });
   1437         valid = false;
   1438     }
   1439     if proposal.payload.order_id != request.payload.order_id {
   1440         issues.push(RadrootsOrderIssue::RevisionProposalOrderIdMismatch {
   1441             event_id: proposal.event_id.clone(),
   1442         });
   1443         valid = false;
   1444     }
   1445     if proposal.author_pubkey != proposal.payload.seller_pubkey {
   1446         issues.push(RadrootsOrderIssue::RevisionProposalAuthorMismatch {
   1447             event_id: proposal.event_id.clone(),
   1448         });
   1449         valid = false;
   1450     }
   1451     if proposal.counterparty_pubkey != request.payload.buyer_pubkey {
   1452         issues.push(RadrootsOrderIssue::RevisionProposalCounterpartyMismatch {
   1453             event_id: proposal.event_id.clone(),
   1454         });
   1455         valid = false;
   1456     }
   1457     if proposal.payload.buyer_pubkey != request.payload.buyer_pubkey {
   1458         issues.push(RadrootsOrderIssue::RevisionProposalBuyerMismatch {
   1459             event_id: proposal.event_id.clone(),
   1460         });
   1461         valid = false;
   1462     }
   1463     if proposal.payload.seller_pubkey != request.payload.seller_pubkey {
   1464         issues.push(RadrootsOrderIssue::RevisionProposalSellerMismatch {
   1465             event_id: proposal.event_id.clone(),
   1466         });
   1467         valid = false;
   1468     }
   1469     match parse_public_listing_addr(&proposal.payload.listing_addr) {
   1470         Ok(listing_addr) => {
   1471             if proposal.payload.listing_addr != request.payload.listing_addr
   1472                 || listing_addr.seller_pubkey != proposal.payload.seller_pubkey
   1473             {
   1474                 issues.push(RadrootsOrderIssue::RevisionProposalListingMismatch {
   1475                     event_id: proposal.event_id.clone(),
   1476                 });
   1477                 valid = false;
   1478             }
   1479         }
   1480         Err(_) => {
   1481             issues.push(RadrootsOrderIssue::RevisionProposalListingAddressInvalid {
   1482                 event_id: proposal.event_id.clone(),
   1483             });
   1484             valid = false;
   1485         }
   1486     }
   1487     if proposal.root_event_id != request.event_id
   1488         || proposal.payload.root_event_id != request.event_id
   1489     {
   1490         issues.push(RadrootsOrderIssue::RevisionProposalRootMismatch {
   1491             event_id: proposal.event_id.clone(),
   1492         });
   1493         valid = false;
   1494     }
   1495     if proposal.prev_event_id == proposal.event_id
   1496         || proposal.payload.prev_event_id != proposal.prev_event_id
   1497     {
   1498         issues.push(RadrootsOrderIssue::RevisionProposalPreviousMismatch {
   1499             event_id: proposal.event_id.clone(),
   1500         });
   1501         valid = false;
   1502     }
   1503     valid
   1504 }
   1505 
   1506 fn validate_order_revision_decision_record(
   1507     request: &RadrootsOrderRequestRecord,
   1508     decision: &RadrootsOrderRevisionDecisionRecord,
   1509     issues: &mut Vec<RadrootsOrderIssue>,
   1510 ) -> bool {
   1511     let mut valid = true;
   1512     if decision.payload.validate().is_err() {
   1513         issues.push(RadrootsOrderIssue::RevisionDecisionPayloadInvalid {
   1514             event_id: decision.event_id.clone(),
   1515         });
   1516         valid = false;
   1517     }
   1518     if decision.payload.order_id != request.payload.order_id {
   1519         issues.push(RadrootsOrderIssue::RevisionDecisionOrderIdMismatch {
   1520             event_id: decision.event_id.clone(),
   1521         });
   1522         valid = false;
   1523     }
   1524     if decision.author_pubkey != decision.payload.buyer_pubkey {
   1525         issues.push(RadrootsOrderIssue::RevisionDecisionAuthorMismatch {
   1526             event_id: decision.event_id.clone(),
   1527         });
   1528         valid = false;
   1529     }
   1530     if decision.counterparty_pubkey != request.payload.seller_pubkey {
   1531         issues.push(RadrootsOrderIssue::RevisionDecisionCounterpartyMismatch {
   1532             event_id: decision.event_id.clone(),
   1533         });
   1534         valid = false;
   1535     }
   1536     if decision.payload.buyer_pubkey != request.payload.buyer_pubkey {
   1537         issues.push(RadrootsOrderIssue::RevisionDecisionBuyerMismatch {
   1538             event_id: decision.event_id.clone(),
   1539         });
   1540         valid = false;
   1541     }
   1542     if decision.payload.seller_pubkey != request.payload.seller_pubkey {
   1543         issues.push(RadrootsOrderIssue::RevisionDecisionSellerMismatch {
   1544             event_id: decision.event_id.clone(),
   1545         });
   1546         valid = false;
   1547     }
   1548     match parse_public_listing_addr(&decision.payload.listing_addr) {
   1549         Ok(listing_addr) => {
   1550             if decision.payload.listing_addr != request.payload.listing_addr
   1551                 || listing_addr.seller_pubkey != decision.payload.seller_pubkey
   1552             {
   1553                 issues.push(RadrootsOrderIssue::RevisionDecisionListingMismatch {
   1554                     event_id: decision.event_id.clone(),
   1555                 });
   1556                 valid = false;
   1557             }
   1558         }
   1559         Err(_) => {
   1560             issues.push(RadrootsOrderIssue::RevisionDecisionListingAddressInvalid {
   1561                 event_id: decision.event_id.clone(),
   1562             });
   1563             valid = false;
   1564         }
   1565     }
   1566     if decision.root_event_id != request.event_id
   1567         || decision.payload.root_event_id != request.event_id
   1568     {
   1569         issues.push(RadrootsOrderIssue::RevisionDecisionRootMismatch {
   1570             event_id: decision.event_id.clone(),
   1571         });
   1572         valid = false;
   1573     }
   1574     if decision.prev_event_id == decision.event_id
   1575         || decision.payload.prev_event_id != decision.prev_event_id
   1576     {
   1577         issues.push(RadrootsOrderIssue::RevisionDecisionPreviousMismatch {
   1578             event_id: decision.event_id.clone(),
   1579         });
   1580         valid = false;
   1581     }
   1582     valid
   1583 }
   1584 
   1585 fn validate_order_cancellation_record(
   1586     request: &RadrootsOrderRequestRecord,
   1587     cancellation: &RadrootsOrderCancellationRecord,
   1588     issues: &mut Vec<RadrootsOrderIssue>,
   1589 ) -> bool {
   1590     let mut valid = true;
   1591     if cancellation.payload.validate().is_err() {
   1592         issues.push(RadrootsOrderIssue::CancellationPayloadInvalid {
   1593             event_id: cancellation.event_id.clone(),
   1594         });
   1595         valid = false;
   1596     }
   1597     if cancellation.payload.order_id != request.payload.order_id {
   1598         issues.push(RadrootsOrderIssue::CancellationOrderIdMismatch {
   1599             event_id: cancellation.event_id.clone(),
   1600         });
   1601         valid = false;
   1602     }
   1603     if cancellation.author_pubkey != cancellation.payload.buyer_pubkey {
   1604         issues.push(RadrootsOrderIssue::CancellationAuthorMismatch {
   1605             event_id: cancellation.event_id.clone(),
   1606         });
   1607         valid = false;
   1608     }
   1609     if cancellation.counterparty_pubkey != request.payload.seller_pubkey {
   1610         issues.push(RadrootsOrderIssue::CancellationCounterpartyMismatch {
   1611             event_id: cancellation.event_id.clone(),
   1612         });
   1613         valid = false;
   1614     }
   1615     if cancellation.payload.buyer_pubkey != request.payload.buyer_pubkey {
   1616         issues.push(RadrootsOrderIssue::CancellationBuyerMismatch {
   1617             event_id: cancellation.event_id.clone(),
   1618         });
   1619         valid = false;
   1620     }
   1621     if cancellation.payload.seller_pubkey != request.payload.seller_pubkey {
   1622         issues.push(RadrootsOrderIssue::CancellationSellerMismatch {
   1623             event_id: cancellation.event_id.clone(),
   1624         });
   1625         valid = false;
   1626     }
   1627     match parse_public_listing_addr(&cancellation.payload.listing_addr) {
   1628         Ok(listing_addr) => {
   1629             if cancellation.payload.listing_addr != request.payload.listing_addr
   1630                 || listing_addr.seller_pubkey != cancellation.payload.seller_pubkey
   1631             {
   1632                 issues.push(RadrootsOrderIssue::CancellationListingMismatch {
   1633                     event_id: cancellation.event_id.clone(),
   1634                 });
   1635                 valid = false;
   1636             }
   1637         }
   1638         Err(_) => {
   1639             issues.push(RadrootsOrderIssue::CancellationListingAddressInvalid {
   1640                 event_id: cancellation.event_id.clone(),
   1641             });
   1642             valid = false;
   1643         }
   1644     }
   1645     if cancellation.root_event_id != request.event_id {
   1646         issues.push(RadrootsOrderIssue::CancellationRootMismatch {
   1647             event_id: cancellation.event_id.clone(),
   1648         });
   1649         valid = false;
   1650     }
   1651     if cancellation.prev_event_id == cancellation.event_id {
   1652         issues.push(RadrootsOrderIssue::CancellationPreviousMismatch {
   1653             event_id: cancellation.event_id.clone(),
   1654         });
   1655         valid = false;
   1656     }
   1657     valid
   1658 }
   1659 
   1660 fn decision_payload_issue(
   1661     decision: &RadrootsOrderDecisionOutcome,
   1662     event_id: &RadrootsEventId,
   1663     issues: &mut Vec<RadrootsOrderIssue>,
   1664 ) -> bool {
   1665     match decision {
   1666         RadrootsOrderDecisionOutcome::Accepted {
   1667             inventory_commitments,
   1668         } => {
   1669             if inventory_commitments.is_empty() {
   1670                 issues.push(RadrootsOrderIssue::DecisionMissingInventoryCommitments {
   1671                     event_id: event_id.clone(),
   1672                 });
   1673                 true
   1674             } else {
   1675                 false
   1676             }
   1677         }
   1678         RadrootsOrderDecisionOutcome::Declined { reason } => {
   1679             if reason.trim().is_empty() {
   1680                 issues.push(RadrootsOrderIssue::DecisionMissingReason {
   1681                     event_id: event_id.clone(),
   1682                 });
   1683                 true
   1684             } else {
   1685                 false
   1686             }
   1687         }
   1688     }
   1689 }
   1690 
   1691 fn unique_request_records(
   1692     requests: Vec<RadrootsOrderRequestRecord>,
   1693 ) -> Vec<RadrootsOrderRequestRecord> {
   1694     unique_records_by_event_id(requests, |record| &record.event_id)
   1695 }
   1696 
   1697 fn unique_decision_records(
   1698     decisions: Vec<RadrootsOrderDecisionRecord>,
   1699 ) -> Vec<RadrootsOrderDecisionRecord> {
   1700     unique_records_by_event_id(decisions, |record| &record.event_id)
   1701 }
   1702 
   1703 fn unique_revision_proposal_records(
   1704     revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>,
   1705 ) -> Vec<RadrootsOrderRevisionProposalRecord> {
   1706     unique_records_by_event_id(revision_proposals, |record| &record.event_id)
   1707 }
   1708 
   1709 fn unique_revision_decision_records(
   1710     revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>,
   1711 ) -> Vec<RadrootsOrderRevisionDecisionRecord> {
   1712     unique_records_by_event_id(revision_decisions, |record| &record.event_id)
   1713 }
   1714 
   1715 fn unique_cancellation_records(
   1716     cancellations: Vec<RadrootsOrderCancellationRecord>,
   1717 ) -> Vec<RadrootsOrderCancellationRecord> {
   1718     unique_records_by_event_id(cancellations, |record| &record.event_id)
   1719 }
   1720 
   1721 fn unique_records_by_event_id<T>(
   1722     mut records: Vec<T>,
   1723     event_id: impl Fn(&T) -> &RadrootsEventId,
   1724 ) -> Vec<T> {
   1725     let mut unique = Vec::new();
   1726     records.sort_by(|left, right| event_id(left).cmp(event_id(right)));
   1727     for record in records {
   1728         if unique
   1729             .iter()
   1730             .all(|existing: &T| event_id(existing) != event_id(&record))
   1731         {
   1732             unique.push(record);
   1733         }
   1734     }
   1735     unique
   1736 }
   1737 
   1738 fn normalized_listing_inventory_bins<I>(
   1739     bins: I,
   1740 ) -> (
   1741     Vec<RadrootsListingInventoryBinAccounting>,
   1742     Vec<RadrootsListingInventoryAccountingIssue>,
   1743 )
   1744 where
   1745     I: IntoIterator<Item = RadrootsListingInventoryBinAvailability>,
   1746 {
   1747     let mut normalized: Vec<RadrootsListingInventoryBinAccounting> = Vec::new();
   1748     let mut issues = Vec::new();
   1749     for bin in bins {
   1750         let bin_id = bin.bin_id;
   1751         if let Some(existing) = normalized
   1752             .iter_mut()
   1753             .find(|existing| existing.bin_id == bin_id)
   1754         {
   1755             if let Some(next_count) = existing.available_count.checked_add(bin.available_count) {
   1756                 existing.available_count = next_count;
   1757                 existing.remaining_count = next_count;
   1758             } else {
   1759                 existing.available_count = u64::MAX;
   1760                 existing.remaining_count = u64::MAX;
   1761                 issues.push(
   1762                     RadrootsListingInventoryAccountingIssue::ArithmeticOverflow {
   1763                         bin_id: existing.bin_id.clone(),
   1764                         event_ids: Vec::new(),
   1765                     },
   1766                 );
   1767             }
   1768         } else {
   1769             normalized.push(RadrootsListingInventoryBinAccounting {
   1770                 bin_id,
   1771                 available_count: bin.available_count,
   1772                 accepted_reserved_count: 0,
   1773                 remaining_count: bin.available_count,
   1774                 over_reserved: false,
   1775                 accepted_orders: Vec::new(),
   1776             });
   1777         }
   1778     }
   1779     normalized.sort_by(|left, right| left.bin_id.cmp(&right.bin_id));
   1780     (normalized, issues)
   1781 }
   1782 
   1783 fn listing_order_ids(
   1784     requests: &[RadrootsOrderRequestRecord],
   1785     decisions: &[RadrootsOrderDecisionRecord],
   1786     revision_proposals: &[RadrootsOrderRevisionProposalRecord],
   1787     revision_decisions: &[RadrootsOrderRevisionDecisionRecord],
   1788     cancellations: &[RadrootsOrderCancellationRecord],
   1789 ) -> Vec<RadrootsOrderId> {
   1790     let mut order_ids = Vec::new();
   1791     order_ids.extend(
   1792         requests
   1793             .iter()
   1794             .map(|request| request.payload.order_id.clone()),
   1795     );
   1796     order_ids.extend(
   1797         decisions
   1798             .iter()
   1799             .map(|decision| decision.payload.order_id.clone()),
   1800     );
   1801     order_ids.extend(
   1802         revision_proposals
   1803             .iter()
   1804             .map(|proposal| proposal.payload.order_id.clone()),
   1805     );
   1806     order_ids.extend(
   1807         revision_decisions
   1808             .iter()
   1809             .map(|decision| decision.payload.order_id.clone()),
   1810     );
   1811     order_ids.extend(
   1812         cancellations
   1813             .iter()
   1814             .map(|cancellation| cancellation.payload.order_id.clone()),
   1815     );
   1816     sort_and_dedup_values(&mut order_ids);
   1817     order_ids
   1818 }
   1819 
   1820 fn add_accepted_inventory_reservations_from_economics(
   1821     bins: &mut [RadrootsListingInventoryBinAccounting],
   1822     order_id: &RadrootsOrderId,
   1823     agreement_event_id: &RadrootsEventId,
   1824     economics: &RadrootsOrderEconomics,
   1825     issues: &mut Vec<RadrootsListingInventoryAccountingIssue>,
   1826 ) {
   1827     for item in &economics.items {
   1828         if let Some(bin) = bins.iter_mut().find(|bin| bin.bin_id == item.bin_id) {
   1829             add_inventory_reservation_event(
   1830                 bin,
   1831                 order_id,
   1832                 agreement_event_id,
   1833                 u64::from(item.bin_count),
   1834                 issues,
   1835             );
   1836         } else {
   1837             issues.push(
   1838                 RadrootsListingInventoryAccountingIssue::UnknownInventoryBin {
   1839                     bin_id: item.bin_id.clone(),
   1840                     event_ids: vec![agreement_event_id.clone()],
   1841                 },
   1842             );
   1843         }
   1844     }
   1845 }
   1846 
   1847 fn add_inventory_reservation_event(
   1848     bin: &mut RadrootsListingInventoryBinAccounting,
   1849     order_id: &RadrootsOrderId,
   1850     event_id: &RadrootsEventId,
   1851     bin_count: u64,
   1852     issues: &mut Vec<RadrootsListingInventoryAccountingIssue>,
   1853 ) {
   1854     if let Some(next_count) = bin.accepted_reserved_count.checked_add(bin_count) {
   1855         bin.accepted_reserved_count = next_count;
   1856         bin.accepted_orders
   1857             .push(RadrootsListingInventoryOrderReservation {
   1858                 order_id: order_id.clone(),
   1859                 agreement_event_id: event_id.clone(),
   1860                 bin_count,
   1861             });
   1862     } else {
   1863         issues.push(
   1864             RadrootsListingInventoryAccountingIssue::ArithmeticOverflow {
   1865                 bin_id: bin.bin_id.clone(),
   1866                 event_ids: vec![event_id.clone()],
   1867             },
   1868         );
   1869     }
   1870 }
   1871 
   1872 fn finish_inventory_accounting_bins(
   1873     bins: &mut [RadrootsListingInventoryBinAccounting],
   1874     issues: &mut Vec<RadrootsListingInventoryAccountingIssue>,
   1875 ) {
   1876     for bin in bins.iter_mut() {
   1877         bin.accepted_orders.sort_by(|left, right| {
   1878             left.order_id
   1879                 .cmp(&right.order_id)
   1880                 .then_with(|| left.agreement_event_id.cmp(&right.agreement_event_id))
   1881         });
   1882         bin.remaining_count = bin
   1883             .available_count
   1884             .saturating_sub(bin.accepted_reserved_count);
   1885         bin.over_reserved = bin.accepted_reserved_count > bin.available_count;
   1886         if bin.over_reserved {
   1887             let mut event_ids = bin
   1888                 .accepted_orders
   1889                 .iter()
   1890                 .map(|reservation| reservation.agreement_event_id.clone())
   1891                 .collect::<Vec<_>>();
   1892             sort_and_dedup_values(&mut event_ids);
   1893             issues.push(RadrootsListingInventoryAccountingIssue::OverReserved {
   1894                 bin_id: bin.bin_id.clone(),
   1895                 available_count: bin.available_count,
   1896                 reserved_count: bin.accepted_reserved_count,
   1897                 event_ids,
   1898             });
   1899         }
   1900     }
   1901     bins.sort_by(|left, right| left.bin_id.cmp(&right.bin_id));
   1902 }
   1903 
   1904 fn projection_issue_event_ids(issues: &[RadrootsOrderIssue]) -> Vec<RadrootsEventId> {
   1905     let mut event_ids = Vec::new();
   1906     for issue in issues {
   1907         match issue {
   1908             RadrootsOrderIssue::MissingRequest => {}
   1909             RadrootsOrderIssue::MultipleRequests { event_ids: ids }
   1910             | RadrootsOrderIssue::ConflictingDecisions { event_ids: ids }
   1911             | RadrootsOrderIssue::ForkedLifecycle { event_ids: ids } => {
   1912                 event_ids.extend(ids.iter().cloned());
   1913             }
   1914             RadrootsOrderIssue::RequestPayloadInvalid { event_id }
   1915             | RadrootsOrderIssue::RequestOrderIdMismatch { event_id }
   1916             | RadrootsOrderIssue::RequestAuthorMismatch { event_id }
   1917             | RadrootsOrderIssue::RequestListingAddressInvalid { event_id }
   1918             | RadrootsOrderIssue::RequestSellerListingMismatch { event_id }
   1919             | RadrootsOrderIssue::DecisionPayloadInvalid { event_id }
   1920             | RadrootsOrderIssue::DecisionOrderIdMismatch { event_id }
   1921             | RadrootsOrderIssue::DecisionAuthorMismatch { event_id }
   1922             | RadrootsOrderIssue::DecisionCounterpartyMismatch { event_id }
   1923             | RadrootsOrderIssue::DecisionBuyerMismatch { event_id }
   1924             | RadrootsOrderIssue::DecisionSellerMismatch { event_id }
   1925             | RadrootsOrderIssue::DecisionListingAddressInvalid { event_id }
   1926             | RadrootsOrderIssue::DecisionListingMismatch { event_id }
   1927             | RadrootsOrderIssue::DecisionRootMismatch { event_id }
   1928             | RadrootsOrderIssue::DecisionPreviousMismatch { event_id }
   1929             | RadrootsOrderIssue::DecisionMissingInventoryCommitments { event_id }
   1930             | RadrootsOrderIssue::DecisionInventoryCommitmentMismatch { event_id }
   1931             | RadrootsOrderIssue::DecisionMissingReason { event_id }
   1932             | RadrootsOrderIssue::RevisionProposalPayloadInvalid { event_id }
   1933             | RadrootsOrderIssue::RevisionProposalOrderIdMismatch { event_id }
   1934             | RadrootsOrderIssue::RevisionProposalAuthorMismatch { event_id }
   1935             | RadrootsOrderIssue::RevisionProposalCounterpartyMismatch { event_id }
   1936             | RadrootsOrderIssue::RevisionProposalBuyerMismatch { event_id }
   1937             | RadrootsOrderIssue::RevisionProposalSellerMismatch { event_id }
   1938             | RadrootsOrderIssue::RevisionProposalListingAddressInvalid { event_id }
   1939             | RadrootsOrderIssue::RevisionProposalListingMismatch { event_id }
   1940             | RadrootsOrderIssue::RevisionProposalRootMismatch { event_id }
   1941             | RadrootsOrderIssue::RevisionProposalPreviousMismatch { event_id }
   1942             | RadrootsOrderIssue::RevisionDecisionWithoutProposal { event_id }
   1943             | RadrootsOrderIssue::RevisionDecisionPayloadInvalid { event_id }
   1944             | RadrootsOrderIssue::RevisionDecisionOrderIdMismatch { event_id }
   1945             | RadrootsOrderIssue::RevisionDecisionAuthorMismatch { event_id }
   1946             | RadrootsOrderIssue::RevisionDecisionCounterpartyMismatch { event_id }
   1947             | RadrootsOrderIssue::RevisionDecisionBuyerMismatch { event_id }
   1948             | RadrootsOrderIssue::RevisionDecisionSellerMismatch { event_id }
   1949             | RadrootsOrderIssue::RevisionDecisionListingAddressInvalid { event_id }
   1950             | RadrootsOrderIssue::RevisionDecisionListingMismatch { event_id }
   1951             | RadrootsOrderIssue::RevisionDecisionRootMismatch { event_id }
   1952             | RadrootsOrderIssue::RevisionDecisionPreviousMismatch { event_id }
   1953             | RadrootsOrderIssue::RevisionDecisionRevisionIdMismatch { event_id }
   1954             | RadrootsOrderIssue::CancellationWithoutCancellableOrder { event_id }
   1955             | RadrootsOrderIssue::CancellationPayloadInvalid { event_id }
   1956             | RadrootsOrderIssue::CancellationOrderIdMismatch { event_id }
   1957             | RadrootsOrderIssue::CancellationAuthorMismatch { event_id }
   1958             | RadrootsOrderIssue::CancellationCounterpartyMismatch { event_id }
   1959             | RadrootsOrderIssue::CancellationBuyerMismatch { event_id }
   1960             | RadrootsOrderIssue::CancellationSellerMismatch { event_id }
   1961             | RadrootsOrderIssue::CancellationListingAddressInvalid { event_id }
   1962             | RadrootsOrderIssue::CancellationListingMismatch { event_id }
   1963             | RadrootsOrderIssue::CancellationRootMismatch { event_id }
   1964             | RadrootsOrderIssue::CancellationPreviousMismatch { event_id } => {
   1965                 event_ids.push(event_id.clone());
   1966             }
   1967         }
   1968     }
   1969     sort_and_dedup_values(&mut event_ids);
   1970     event_ids
   1971 }
   1972 
   1973 fn parse_public_listing_addr(
   1974     value: impl AsRef<str>,
   1975 ) -> Result<RadrootsPublicListingAddress, RadrootsOrderCanonicalizationError> {
   1976     parse_public_listing_address(value).map_err(|error| match error {
   1977         RadrootsPublicListingAddressError::InvalidAddress(error) => {
   1978             RadrootsOrderCanonicalizationError::InvalidListingAddress(error.to_string())
   1979         }
   1980         RadrootsPublicListingAddressError::InvalidListingKind { .. } => {
   1981             RadrootsOrderCanonicalizationError::InvalidListingKind
   1982         }
   1983         RadrootsPublicListingAddressError::InvalidKind { .. } => {
   1984             RadrootsOrderCanonicalizationError::InvalidListingKind
   1985         }
   1986     })
   1987 }
   1988 
   1989 fn canonicalize_items(
   1990     items: &mut [RadrootsOrderItem],
   1991 ) -> Result<(), RadrootsOrderCanonicalizationError> {
   1992     if items.is_empty() {
   1993         return Err(RadrootsOrderCanonicalizationError::MissingItems);
   1994     }
   1995     for (index, item) in items.iter().enumerate() {
   1996         if item.bin_count == 0 {
   1997             return Err(RadrootsOrderCanonicalizationError::InvalidBinCount { index });
   1998         }
   1999     }
   2000     items.sort_by(|left, right| left.bin_id.cmp(&right.bin_id));
   2001     Ok(())
   2002 }
   2003 
   2004 fn canonicalize_decision(
   2005     decision: &mut RadrootsOrderDecisionOutcome,
   2006 ) -> Result<(), RadrootsOrderCanonicalizationError> {
   2007     match decision {
   2008         RadrootsOrderDecisionOutcome::Accepted {
   2009             inventory_commitments,
   2010         } => {
   2011             if inventory_commitments.is_empty() {
   2012                 return Err(RadrootsOrderCanonicalizationError::MissingInventoryCommitments);
   2013             }
   2014             for (index, commitment) in inventory_commitments.iter().enumerate() {
   2015                 if commitment.bin_count == 0 {
   2016                     return Err(
   2017                         RadrootsOrderCanonicalizationError::InvalidInventoryCommitmentCount {
   2018                             index,
   2019                         },
   2020                     );
   2021                 }
   2022             }
   2023             inventory_commitments.sort_by(|left, right| left.bin_id.cmp(&right.bin_id));
   2024             Ok(())
   2025         }
   2026         RadrootsOrderDecisionOutcome::Declined { reason } => {
   2027             if reason.trim().is_empty() {
   2028                 return Err(RadrootsOrderCanonicalizationError::EmptyField("reason"));
   2029             }
   2030             *reason = reason.trim().to_string();
   2031             Ok(())
   2032         }
   2033     }
   2034 }
   2035 
   2036 fn inventory_commitments_match_request(
   2037     items: &[RadrootsOrderItem],
   2038     commitments: &[RadrootsOrderInventoryCommitment],
   2039 ) -> bool {
   2040     if items.len() != commitments.len() {
   2041         return false;
   2042     }
   2043     let mut expected = items.to_vec();
   2044     expected.sort_by(|left, right| left.bin_id.cmp(&right.bin_id));
   2045     let mut actual = commitments.to_vec();
   2046     actual.sort_by(|left, right| left.bin_id.cmp(&right.bin_id));
   2047     expected
   2048         .iter()
   2049         .zip(actual.iter())
   2050         .all(|(item, commitment)| {
   2051             item.bin_id == commitment.bin_id && item.bin_count == commitment.bin_count
   2052         })
   2053 }
   2054 
   2055 fn sort_and_dedup_values<T: Ord>(values: &mut Vec<T>) {
   2056     values.sort();
   2057     values.dedup();
   2058 }
   2059 
   2060 fn inventory_issue_sort_key(
   2061     left: &RadrootsListingInventoryAccountingIssue,
   2062     right: &RadrootsListingInventoryAccountingIssue,
   2063 ) -> core::cmp::Ordering {
   2064     inventory_issue_rank(left)
   2065         .cmp(&inventory_issue_rank(right))
   2066         .then_with(|| inventory_issue_id(left).cmp(inventory_issue_id(right)))
   2067         .then_with(|| inventory_issue_event_ids(left).cmp(inventory_issue_event_ids(right)))
   2068 }
   2069 
   2070 fn inventory_issue_rank(issue: &RadrootsListingInventoryAccountingIssue) -> u8 {
   2071     match issue {
   2072         RadrootsListingInventoryAccountingIssue::InvalidOrder { .. } => 0,
   2073         RadrootsListingInventoryAccountingIssue::ArithmeticOverflow { .. } => 1,
   2074         RadrootsListingInventoryAccountingIssue::UnknownInventoryBin { .. } => 2,
   2075         RadrootsListingInventoryAccountingIssue::OverReserved { .. } => 3,
   2076     }
   2077 }
   2078 
   2079 fn inventory_issue_id(issue: &RadrootsListingInventoryAccountingIssue) -> &str {
   2080     match issue {
   2081         RadrootsListingInventoryAccountingIssue::InvalidOrder { order_id, .. } => order_id,
   2082         RadrootsListingInventoryAccountingIssue::ArithmeticOverflow { bin_id, .. }
   2083         | RadrootsListingInventoryAccountingIssue::UnknownInventoryBin { bin_id, .. }
   2084         | RadrootsListingInventoryAccountingIssue::OverReserved { bin_id, .. } => bin_id,
   2085     }
   2086 }
   2087 
   2088 fn inventory_issue_event_ids(
   2089     issue: &RadrootsListingInventoryAccountingIssue,
   2090 ) -> &[RadrootsEventId] {
   2091     match issue {
   2092         RadrootsListingInventoryAccountingIssue::InvalidOrder { event_ids, .. }
   2093         | RadrootsListingInventoryAccountingIssue::ArithmeticOverflow { event_ids, .. }
   2094         | RadrootsListingInventoryAccountingIssue::UnknownInventoryBin { event_ids, .. }
   2095         | RadrootsListingInventoryAccountingIssue::OverReserved { event_ids, .. } => event_ids,
   2096     }
   2097 }
   2098 
   2099 fn order_issue_sort_key(
   2100     left: &RadrootsOrderIssue,
   2101     right: &RadrootsOrderIssue,
   2102 ) -> core::cmp::Ordering {
   2103     order_issue_rank(left)
   2104         .cmp(&order_issue_rank(right))
   2105         .then_with(|| {
   2106             projection_issue_event_ids(core::slice::from_ref(left))
   2107                 .cmp(&projection_issue_event_ids(core::slice::from_ref(right)))
   2108         })
   2109 }
   2110 
   2111 fn order_issue_rank(issue: &RadrootsOrderIssue) -> u8 {
   2112     match issue {
   2113         RadrootsOrderIssue::MissingRequest => 0,
   2114         RadrootsOrderIssue::MultipleRequests { .. } => 1,
   2115         RadrootsOrderIssue::RequestPayloadInvalid { .. } => 2,
   2116         RadrootsOrderIssue::RequestOrderIdMismatch { .. } => 3,
   2117         RadrootsOrderIssue::RequestAuthorMismatch { .. } => 4,
   2118         RadrootsOrderIssue::RequestListingAddressInvalid { .. } => 5,
   2119         RadrootsOrderIssue::RequestSellerListingMismatch { .. } => 6,
   2120         RadrootsOrderIssue::DecisionPayloadInvalid { .. } => 7,
   2121         RadrootsOrderIssue::DecisionOrderIdMismatch { .. } => 8,
   2122         RadrootsOrderIssue::DecisionAuthorMismatch { .. } => 9,
   2123         RadrootsOrderIssue::DecisionCounterpartyMismatch { .. } => 10,
   2124         RadrootsOrderIssue::DecisionBuyerMismatch { .. } => 11,
   2125         RadrootsOrderIssue::DecisionSellerMismatch { .. } => 12,
   2126         RadrootsOrderIssue::DecisionListingAddressInvalid { .. } => 13,
   2127         RadrootsOrderIssue::DecisionListingMismatch { .. } => 14,
   2128         RadrootsOrderIssue::DecisionRootMismatch { .. } => 15,
   2129         RadrootsOrderIssue::DecisionPreviousMismatch { .. } => 16,
   2130         RadrootsOrderIssue::DecisionMissingInventoryCommitments { .. } => 17,
   2131         RadrootsOrderIssue::DecisionInventoryCommitmentMismatch { .. } => 18,
   2132         RadrootsOrderIssue::DecisionMissingReason { .. } => 19,
   2133         RadrootsOrderIssue::ConflictingDecisions { .. } => 20,
   2134         RadrootsOrderIssue::RevisionProposalPayloadInvalid { .. } => 21,
   2135         RadrootsOrderIssue::RevisionProposalOrderIdMismatch { .. } => 22,
   2136         RadrootsOrderIssue::RevisionProposalAuthorMismatch { .. } => 23,
   2137         RadrootsOrderIssue::RevisionProposalCounterpartyMismatch { .. } => 24,
   2138         RadrootsOrderIssue::RevisionProposalBuyerMismatch { .. } => 25,
   2139         RadrootsOrderIssue::RevisionProposalSellerMismatch { .. } => 26,
   2140         RadrootsOrderIssue::RevisionProposalListingAddressInvalid { .. } => 27,
   2141         RadrootsOrderIssue::RevisionProposalListingMismatch { .. } => 28,
   2142         RadrootsOrderIssue::RevisionProposalRootMismatch { .. } => 29,
   2143         RadrootsOrderIssue::RevisionProposalPreviousMismatch { .. } => 30,
   2144         RadrootsOrderIssue::RevisionDecisionWithoutProposal { .. } => 31,
   2145         RadrootsOrderIssue::RevisionDecisionPayloadInvalid { .. } => 32,
   2146         RadrootsOrderIssue::RevisionDecisionOrderIdMismatch { .. } => 33,
   2147         RadrootsOrderIssue::RevisionDecisionAuthorMismatch { .. } => 34,
   2148         RadrootsOrderIssue::RevisionDecisionCounterpartyMismatch { .. } => 35,
   2149         RadrootsOrderIssue::RevisionDecisionBuyerMismatch { .. } => 36,
   2150         RadrootsOrderIssue::RevisionDecisionSellerMismatch { .. } => 37,
   2151         RadrootsOrderIssue::RevisionDecisionListingAddressInvalid { .. } => 38,
   2152         RadrootsOrderIssue::RevisionDecisionListingMismatch { .. } => 39,
   2153         RadrootsOrderIssue::RevisionDecisionRootMismatch { .. } => 40,
   2154         RadrootsOrderIssue::RevisionDecisionPreviousMismatch { .. } => 41,
   2155         RadrootsOrderIssue::RevisionDecisionRevisionIdMismatch { .. } => 42,
   2156         RadrootsOrderIssue::CancellationWithoutCancellableOrder { .. } => 43,
   2157         RadrootsOrderIssue::CancellationPayloadInvalid { .. } => 44,
   2158         RadrootsOrderIssue::CancellationOrderIdMismatch { .. } => 45,
   2159         RadrootsOrderIssue::CancellationAuthorMismatch { .. } => 46,
   2160         RadrootsOrderIssue::CancellationCounterpartyMismatch { .. } => 47,
   2161         RadrootsOrderIssue::CancellationBuyerMismatch { .. } => 48,
   2162         RadrootsOrderIssue::CancellationSellerMismatch { .. } => 49,
   2163         RadrootsOrderIssue::CancellationListingAddressInvalid { .. } => 50,
   2164         RadrootsOrderIssue::CancellationListingMismatch { .. } => 51,
   2165         RadrootsOrderIssue::CancellationRootMismatch { .. } => 52,
   2166         RadrootsOrderIssue::CancellationPreviousMismatch { .. } => 53,
   2167         RadrootsOrderIssue::ForkedLifecycle { .. } => 54,
   2168     }
   2169 }
   2170 
   2171 #[cfg(test)]
   2172 #[cfg_attr(coverage_nightly, coverage(off))]
   2173 mod tests {
   2174     use super::{
   2175         RadrootsListingInventoryAccountingInputs, RadrootsListingInventoryAccountingIssue,
   2176         RadrootsListingInventoryBinAvailability, RadrootsOrderCancellationRecord,
   2177         RadrootsOrderDecisionRecord, RadrootsOrderEventRecord, RadrootsOrderIssue,
   2178         RadrootsOrderReductionInputs, RadrootsOrderRequestRecord,
   2179         RadrootsOrderRevisionDecisionRecord, RadrootsOrderRevisionProposalRecord,
   2180         RadrootsOrderStatus, reduce_listing_inventory_accounting, reduce_order_event_records,
   2181         reduce_order_events,
   2182     };
   2183     use core::mem::discriminant;
   2184     use radroots_core::{
   2185         RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreUnit,
   2186     };
   2187     use radroots_events::{
   2188         RadrootsNostrEvent, RadrootsNostrEventPtr,
   2189         ids::{
   2190             RadrootsEventId, RadrootsInventoryBinId, RadrootsListingAddress, RadrootsOrderId,
   2191             RadrootsOrderQuoteId, RadrootsOrderRevisionId, RadrootsPublicKey,
   2192         },
   2193         kinds::{KIND_LISTING, KIND_LISTING_DRAFT},
   2194         order::{
   2195             RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderDecisionOutcome,
   2196             RadrootsOrderEconomicItem, RadrootsOrderEconomics, RadrootsOrderInventoryCommitment,
   2197             RadrootsOrderItem, RadrootsOrderPricingBasis, RadrootsOrderRequest,
   2198             RadrootsOrderRevisionDecision, RadrootsOrderRevisionOutcome,
   2199             RadrootsOrderRevisionProposal,
   2200         },
   2201     };
   2202     #[cfg(feature = "serde_json")]
   2203     use radroots_events_codec::{
   2204         order::{
   2205             order_cancellation_event_build, order_decision_event_build, order_request_event_build,
   2206             order_revision_decision_event_build, order_revision_proposal_event_build,
   2207         },
   2208         wire::WireEventParts,
   2209     };
   2210 
   2211     const BUYER: &str = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
   2212     const SELLER: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
   2213     const OTHER: &str = "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
   2214 
   2215     fn event_id(raw: u8) -> RadrootsEventId {
   2216         RadrootsEventId::parse(format!("{raw:064x}")).expect("event id")
   2217     }
   2218 
   2219     fn public_key(raw: &str) -> RadrootsPublicKey {
   2220         RadrootsPublicKey::parse(raw).expect("public key")
   2221     }
   2222 
   2223     fn order_id(raw: &str) -> RadrootsOrderId {
   2224         RadrootsOrderId::parse(raw).expect("order id")
   2225     }
   2226 
   2227     fn revision_id(raw: &str) -> RadrootsOrderRevisionId {
   2228         RadrootsOrderRevisionId::parse(raw).expect("revision id")
   2229     }
   2230 
   2231     fn quote_id(raw: &str) -> RadrootsOrderQuoteId {
   2232         RadrootsOrderQuoteId::parse(raw).expect("quote id")
   2233     }
   2234 
   2235     fn bin_id(raw: &str) -> RadrootsInventoryBinId {
   2236         RadrootsInventoryBinId::parse(raw).expect("bin id")
   2237     }
   2238 
   2239     fn listing_addr() -> RadrootsListingAddress {
   2240         RadrootsListingAddress::parse(format!("{KIND_LISTING}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg"))
   2241             .expect("listing address")
   2242     }
   2243 
   2244     fn draft_listing_addr() -> RadrootsListingAddress {
   2245         RadrootsListingAddress::parse(format!(
   2246             "{KIND_LISTING_DRAFT}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg"
   2247         ))
   2248         .expect("draft listing address")
   2249     }
   2250 
   2251     fn other_seller_listing_addr() -> RadrootsListingAddress {
   2252         RadrootsListingAddress::parse(format!("{KIND_LISTING}:{OTHER}:AAAAAAAAAAAAAAAAAAAAAg"))
   2253             .expect("other seller listing address")
   2254     }
   2255 
   2256     #[cfg(feature = "serde_json")]
   2257     fn listing_event_ptr() -> RadrootsNostrEventPtr {
   2258         RadrootsNostrEventPtr {
   2259             id: event_id(80).into_string(),
   2260             relays: Some("wss://relay.example.test".into()),
   2261         }
   2262     }
   2263 
   2264     #[cfg(feature = "serde_json")]
   2265     fn event_from_parts(raw_id: u8, author: &str, parts: WireEventParts) -> RadrootsNostrEvent {
   2266         RadrootsNostrEvent {
   2267             id: event_id(raw_id).into_string(),
   2268             author: author.into(),
   2269             created_at: 1,
   2270             kind: parts.kind,
   2271             tags: parts.tags,
   2272             content: parts.content,
   2273             sig: "sig".into(),
   2274         }
   2275     }
   2276 
   2277     fn economics(bin_count: u32) -> RadrootsOrderEconomics {
   2278         let currency = RadrootsCoreCurrency::USD;
   2279         let amount = RadrootsCoreDecimal::from(1200u32);
   2280         RadrootsOrderEconomics {
   2281             quote_id: quote_id("quote-1"),
   2282             quote_version: 1,
   2283             pricing_basis: RadrootsOrderPricingBasis::ListingEvent,
   2284             currency,
   2285             items: vec![RadrootsOrderEconomicItem {
   2286                 bin_id: bin_id("bin-1"),
   2287                 bin_count,
   2288                 quantity_amount: RadrootsCoreDecimal::ONE,
   2289                 quantity_unit: RadrootsCoreUnit::Each,
   2290                 unit_price_amount: amount,
   2291                 unit_price_currency: currency,
   2292                 line_subtotal: RadrootsCoreMoney::new(
   2293                     RadrootsCoreDecimal::from(u64::from(bin_count) * 1200),
   2294                     currency,
   2295                 ),
   2296             }],
   2297             discounts: Vec::new(),
   2298             adjustments: Vec::new(),
   2299             subtotal: RadrootsCoreMoney::new(
   2300                 RadrootsCoreDecimal::from(u64::from(bin_count) * 1200),
   2301                 currency,
   2302             ),
   2303             discount_total: RadrootsCoreMoney::zero(currency),
   2304             adjustment_total: RadrootsCoreMoney::zero(currency),
   2305             total: RadrootsCoreMoney::new(
   2306                 RadrootsCoreDecimal::from(u64::from(bin_count) * 1200),
   2307                 currency,
   2308             ),
   2309         }
   2310     }
   2311 
   2312     fn request_record() -> RadrootsOrderRequestRecord {
   2313         RadrootsOrderRequestRecord {
   2314             event_id: event_id(1),
   2315             author_pubkey: public_key(BUYER),
   2316             payload: RadrootsOrderRequest {
   2317                 order_id: order_id("order-1"),
   2318                 listing_addr: listing_addr(),
   2319                 buyer_pubkey: public_key(BUYER),
   2320                 seller_pubkey: public_key(SELLER),
   2321                 items: vec![RadrootsOrderItem {
   2322                     bin_id: bin_id("bin-1"),
   2323                     bin_count: 2,
   2324                 }],
   2325                 economics: economics(2),
   2326             },
   2327         }
   2328     }
   2329 
   2330     fn accepted_decision() -> RadrootsOrderDecisionRecord {
   2331         RadrootsOrderDecisionRecord {
   2332             event_id: event_id(2),
   2333             author_pubkey: public_key(SELLER),
   2334             counterparty_pubkey: public_key(BUYER),
   2335             root_event_id: event_id(1),
   2336             prev_event_id: event_id(1),
   2337             payload: RadrootsOrderDecision {
   2338                 order_id: order_id("order-1"),
   2339                 listing_addr: listing_addr(),
   2340                 buyer_pubkey: public_key(BUYER),
   2341                 seller_pubkey: public_key(SELLER),
   2342                 decision: RadrootsOrderDecisionOutcome::Accepted {
   2343                     inventory_commitments: vec![RadrootsOrderInventoryCommitment {
   2344                         bin_id: bin_id("bin-1"),
   2345                         bin_count: 2,
   2346                     }],
   2347                 },
   2348             },
   2349         }
   2350     }
   2351 
   2352     fn declined_decision() -> RadrootsOrderDecisionRecord {
   2353         RadrootsOrderDecisionRecord {
   2354             event_id: event_id(2),
   2355             author_pubkey: public_key(SELLER),
   2356             counterparty_pubkey: public_key(BUYER),
   2357             root_event_id: event_id(1),
   2358             prev_event_id: event_id(1),
   2359             payload: RadrootsOrderDecision {
   2360                 order_id: order_id("order-1"),
   2361                 listing_addr: listing_addr(),
   2362                 buyer_pubkey: public_key(BUYER),
   2363                 seller_pubkey: public_key(SELLER),
   2364                 decision: RadrootsOrderDecisionOutcome::Declined {
   2365                     reason: "not available".into(),
   2366                 },
   2367             },
   2368         }
   2369     }
   2370 
   2371     fn revision_proposal() -> RadrootsOrderRevisionProposalRecord {
   2372         RadrootsOrderRevisionProposalRecord {
   2373             event_id: event_id(3),
   2374             author_pubkey: public_key(SELLER),
   2375             counterparty_pubkey: public_key(BUYER),
   2376             root_event_id: event_id(1),
   2377             prev_event_id: event_id(1),
   2378             payload: RadrootsOrderRevisionProposal {
   2379                 revision_id: revision_id("revision-1"),
   2380                 order_id: order_id("order-1"),
   2381                 listing_addr: listing_addr(),
   2382                 buyer_pubkey: public_key(BUYER),
   2383                 seller_pubkey: public_key(SELLER),
   2384                 root_event_id: event_id(1),
   2385                 prev_event_id: event_id(1),
   2386                 items: vec![RadrootsOrderItem {
   2387                     bin_id: bin_id("bin-1"),
   2388                     bin_count: 1,
   2389                 }],
   2390                 economics: economics(1),
   2391                 reason: "one bin remains".into(),
   2392             },
   2393         }
   2394     }
   2395 
   2396     fn accepted_revision_decision() -> RadrootsOrderRevisionDecisionRecord {
   2397         RadrootsOrderRevisionDecisionRecord {
   2398             event_id: event_id(4),
   2399             author_pubkey: public_key(BUYER),
   2400             counterparty_pubkey: public_key(SELLER),
   2401             root_event_id: event_id(1),
   2402             prev_event_id: event_id(3),
   2403             payload: RadrootsOrderRevisionDecision {
   2404                 revision_id: revision_id("revision-1"),
   2405                 order_id: order_id("order-1"),
   2406                 listing_addr: listing_addr(),
   2407                 buyer_pubkey: public_key(BUYER),
   2408                 seller_pubkey: public_key(SELLER),
   2409                 root_event_id: event_id(1),
   2410                 prev_event_id: event_id(3),
   2411                 decision: RadrootsOrderRevisionOutcome::Accepted,
   2412             },
   2413         }
   2414     }
   2415 
   2416     fn cancellation(prev_event_id: RadrootsEventId) -> RadrootsOrderCancellationRecord {
   2417         RadrootsOrderCancellationRecord {
   2418             event_id: event_id(5),
   2419             author_pubkey: public_key(BUYER),
   2420             counterparty_pubkey: public_key(SELLER),
   2421             root_event_id: event_id(1),
   2422             prev_event_id,
   2423             payload: RadrootsOrderCancellation {
   2424                 order_id: order_id("order-1"),
   2425                 listing_addr: listing_addr(),
   2426                 buyer_pubkey: public_key(BUYER),
   2427                 seller_pubkey: public_key(SELLER),
   2428                 reason: "changed plans".into(),
   2429             },
   2430         }
   2431     }
   2432 
   2433     fn assert_order_issue_kind(issues: &[RadrootsOrderIssue], expected: RadrootsOrderIssue) {
   2434         let expected_kind = discriminant(&expected);
   2435         assert!(
   2436             issues
   2437                 .iter()
   2438                 .any(|issue| discriminant(issue) == expected_kind),
   2439             "missing issue kind {expected:?} in {issues:?}"
   2440         );
   2441     }
   2442 
   2443     fn assert_inventory_issue_kind(
   2444         issues: &[RadrootsListingInventoryAccountingIssue],
   2445         expected: RadrootsListingInventoryAccountingIssue,
   2446     ) {
   2447         let expected_kind = discriminant(&expected);
   2448         assert!(
   2449             issues
   2450                 .iter()
   2451                 .any(|issue| discriminant(issue) == expected_kind),
   2452             "missing inventory issue kind {expected:?} in {issues:?}"
   2453         );
   2454     }
   2455 
   2456     fn assert_request_issue(
   2457         mutate: impl FnOnce(&mut RadrootsOrderRequestRecord),
   2458         expected: RadrootsOrderIssue,
   2459     ) {
   2460         let mut request = request_record();
   2461         mutate(&mut request);
   2462         let mut issues = Vec::new();
   2463         assert!(!super::validate_order_request_record(
   2464             &order_id("order-1"),
   2465             &request,
   2466             &mut issues
   2467         ));
   2468         assert_order_issue_kind(&issues, expected);
   2469     }
   2470 
   2471     fn assert_decision_issue(
   2472         mutate: impl FnOnce(&mut RadrootsOrderDecisionRecord),
   2473         expected: RadrootsOrderIssue,
   2474     ) {
   2475         let request = request_record();
   2476         let mut decision = accepted_decision();
   2477         mutate(&mut decision);
   2478         let mut issues = Vec::new();
   2479         assert!(!super::validate_order_decision_record(
   2480             &request,
   2481             &decision,
   2482             &mut issues
   2483         ));
   2484         assert_order_issue_kind(&issues, expected);
   2485     }
   2486 
   2487     fn assert_revision_proposal_issue(
   2488         mutate: impl FnOnce(&mut RadrootsOrderRevisionProposalRecord),
   2489         expected: RadrootsOrderIssue,
   2490     ) {
   2491         let request = request_record();
   2492         let mut proposal = revision_proposal();
   2493         mutate(&mut proposal);
   2494         let mut issues = Vec::new();
   2495         assert!(!super::validate_order_revision_proposal_record(
   2496             &request,
   2497             &proposal,
   2498             &mut issues
   2499         ));
   2500         assert_order_issue_kind(&issues, expected);
   2501     }
   2502 
   2503     fn assert_revision_decision_issue(
   2504         mutate: impl FnOnce(&mut RadrootsOrderRevisionDecisionRecord),
   2505         expected: RadrootsOrderIssue,
   2506     ) {
   2507         let request = request_record();
   2508         let mut decision = accepted_revision_decision();
   2509         mutate(&mut decision);
   2510         let mut issues = Vec::new();
   2511         assert!(!super::validate_order_revision_decision_record(
   2512             &request,
   2513             &decision,
   2514             &mut issues
   2515         ));
   2516         assert_order_issue_kind(&issues, expected);
   2517     }
   2518 
   2519     fn assert_cancellation_issue(
   2520         mutate: impl FnOnce(&mut RadrootsOrderCancellationRecord),
   2521         expected: RadrootsOrderIssue,
   2522     ) {
   2523         let request = request_record();
   2524         let mut cancellation = cancellation(event_id(1));
   2525         mutate(&mut cancellation);
   2526         let mut issues = Vec::new();
   2527         assert!(!super::validate_order_cancellation_record(
   2528             &request,
   2529             &cancellation,
   2530             &mut issues
   2531         ));
   2532         assert_order_issue_kind(&issues, expected);
   2533     }
   2534 
   2535     fn reduce(
   2536         decisions: Vec<RadrootsOrderDecisionRecord>,
   2537         revision_proposals: Vec<RadrootsOrderRevisionProposalRecord>,
   2538         revision_decisions: Vec<RadrootsOrderRevisionDecisionRecord>,
   2539         cancellations: Vec<RadrootsOrderCancellationRecord>,
   2540     ) -> super::RadrootsOrderProjection {
   2541         reduce_order_events(
   2542             &order_id("order-1"),
   2543             RadrootsOrderReductionInputs {
   2544                 requests: vec![request_record()],
   2545                 decisions,
   2546                 revision_proposals,
   2547                 revision_decisions,
   2548                 cancellations,
   2549             },
   2550         )
   2551     }
   2552 
   2553     #[test]
   2554     fn order_event_record_accessors_cover_all_variants() {
   2555         let records = vec![
   2556             RadrootsOrderEventRecord::Request(request_record()),
   2557             RadrootsOrderEventRecord::Decision(accepted_decision()),
   2558             RadrootsOrderEventRecord::RevisionProposal(revision_proposal()),
   2559             RadrootsOrderEventRecord::RevisionDecision(accepted_revision_decision()),
   2560             RadrootsOrderEventRecord::Cancellation(cancellation(event_id(1))),
   2561         ];
   2562 
   2563         let event_ids = records
   2564             .iter()
   2565             .map(RadrootsOrderEventRecord::event_id)
   2566             .cloned()
   2567             .collect::<Vec<_>>();
   2568         let order_ids = records
   2569             .iter()
   2570             .map(RadrootsOrderEventRecord::order_id)
   2571             .cloned()
   2572             .collect::<Vec<_>>();
   2573 
   2574         assert_eq!(
   2575             event_ids,
   2576             vec![
   2577                 event_id(1),
   2578                 event_id(2),
   2579                 event_id(3),
   2580                 event_id(4),
   2581                 event_id(5)
   2582             ]
   2583         );
   2584         assert_eq!(order_ids, vec![order_id("order-1"); 5]);
   2585     }
   2586 
   2587     #[cfg(feature = "serde_json")]
   2588     #[test]
   2589     fn order_event_records_decode_wire_events_and_decode_errors() {
   2590         let request = request_record();
   2591         let request_parts =
   2592             order_request_event_build(&listing_event_ptr(), &request.payload).unwrap();
   2593         let request_record =
   2594             super::order_event_record_from_event(&event_from_parts(11, BUYER, request_parts))
   2595                 .unwrap();
   2596         assert!(matches!(
   2597             request_record,
   2598             RadrootsOrderEventRecord::Request(record)
   2599                 if record.event_id == event_id(11)
   2600                     && record.author_pubkey == public_key(BUYER)
   2601                     && record.payload.order_id == order_id("order-1")
   2602         ));
   2603 
   2604         let decision = accepted_decision();
   2605         let decision_parts = order_decision_event_build(
   2606             &decision.root_event_id,
   2607             &decision.prev_event_id,
   2608             &decision.payload,
   2609         )
   2610         .unwrap();
   2611         let decision_record =
   2612             super::order_event_record_from_event(&event_from_parts(12, SELLER, decision_parts))
   2613                 .unwrap();
   2614         assert!(matches!(
   2615             decision_record,
   2616             RadrootsOrderEventRecord::Decision(record)
   2617                 if record.event_id == event_id(12)
   2618                     && record.counterparty_pubkey == public_key(BUYER)
   2619                     && record.root_event_id == event_id(1)
   2620                     && record.prev_event_id == event_id(1)
   2621         ));
   2622 
   2623         let proposal = revision_proposal();
   2624         let proposal_parts = order_revision_proposal_event_build(
   2625             &proposal.root_event_id,
   2626             &proposal.prev_event_id,
   2627             &proposal.payload,
   2628         )
   2629         .unwrap();
   2630         let proposal_record =
   2631             super::order_event_record_from_event(&event_from_parts(13, SELLER, proposal_parts))
   2632                 .unwrap();
   2633         assert!(matches!(
   2634             proposal_record,
   2635             RadrootsOrderEventRecord::RevisionProposal(record)
   2636                 if record.event_id == event_id(13)
   2637                     && record.counterparty_pubkey == public_key(BUYER)
   2638                     && record.payload.revision_id == revision_id("revision-1")
   2639         ));
   2640 
   2641         let revision_decision = accepted_revision_decision();
   2642         let revision_decision_parts = order_revision_decision_event_build(
   2643             &revision_decision.root_event_id,
   2644             &revision_decision.prev_event_id,
   2645             &revision_decision.payload,
   2646         )
   2647         .unwrap();
   2648         let revision_decision_record = super::order_event_record_from_event(&event_from_parts(
   2649             14,
   2650             BUYER,
   2651             revision_decision_parts,
   2652         ))
   2653         .unwrap();
   2654         assert!(matches!(
   2655             revision_decision_record,
   2656             RadrootsOrderEventRecord::RevisionDecision(record)
   2657                 if record.event_id == event_id(14)
   2658                     && record.counterparty_pubkey == public_key(SELLER)
   2659                     && record.payload.revision_id == revision_id("revision-1")
   2660         ));
   2661 
   2662         let cancellation = cancellation(event_id(1));
   2663         let cancellation_parts = order_cancellation_event_build(
   2664             &cancellation.root_event_id,
   2665             &cancellation.prev_event_id,
   2666             &cancellation.payload,
   2667         )
   2668         .unwrap();
   2669         let cancellation_record =
   2670             super::order_event_record_from_event(&event_from_parts(15, BUYER, cancellation_parts))
   2671                 .unwrap();
   2672         assert!(matches!(
   2673             cancellation_record,
   2674             RadrootsOrderEventRecord::Cancellation(record)
   2675                 if record.event_id == event_id(15)
   2676                     && record.counterparty_pubkey == public_key(SELLER)
   2677                     && record.payload.reason == "changed plans"
   2678         ));
   2679 
   2680         let unsupported = RadrootsNostrEvent {
   2681             id: event_id(16).into_string(),
   2682             author: BUYER.into(),
   2683             created_at: 1,
   2684             kind: 1,
   2685             tags: Vec::new(),
   2686             content: "{}".into(),
   2687             sig: "sig".into(),
   2688         };
   2689         assert!(matches!(
   2690             super::order_event_record_from_event(&unsupported),
   2691             Err(super::RadrootsOrderEventDecodeError::UnsupportedKind { kind: 1 })
   2692         ));
   2693 
   2694         let request_parts =
   2695             order_request_event_build(&listing_event_ptr(), &request.payload).unwrap();
   2696         let mut invalid_id_event = event_from_parts(17, BUYER, request_parts);
   2697         invalid_id_event.id = "not-an-event-id".into();
   2698         assert!(matches!(
   2699             super::order_event_record_from_event(&invalid_id_event),
   2700             Err(super::RadrootsOrderEventDecodeError::InvalidEventId(_))
   2701         ));
   2702 
   2703         let request_parts =
   2704             order_request_event_build(&listing_event_ptr(), &request.payload).unwrap();
   2705         let mut invalid_author_event = event_from_parts(18, BUYER, request_parts);
   2706         invalid_author_event.author = "not-a-pubkey".into();
   2707         assert!(matches!(
   2708             super::order_event_record_from_event(&invalid_author_event),
   2709             Err(super::RadrootsOrderEventDecodeError::InvalidAuthor(_))
   2710         ));
   2711     }
   2712 
   2713     #[cfg(feature = "serde_json")]
   2714     #[test]
   2715     fn order_event_context_requirements_report_missing_chain_ids() {
   2716         let context = radroots_events_codec::order::RadrootsOrderEventContext {
   2717             counterparty_pubkey: public_key(BUYER),
   2718             listing_event: None,
   2719             root_event_id: None,
   2720             prev_event_id: None,
   2721         };
   2722 
   2723         assert!(matches!(
   2724             super::require_context_root_event_id(&context),
   2725             Err(super::RadrootsOrderEventDecodeError::MissingRootEventId)
   2726         ));
   2727         assert!(matches!(
   2728             super::require_context_prev_event_id(&context),
   2729             Err(super::RadrootsOrderEventDecodeError::MissingPreviousEventId)
   2730         ));
   2731     }
   2732 
   2733     #[test]
   2734     fn reducer_groups_all_record_variants_and_skips_duplicate_event_ids() {
   2735         let mut duplicate_decision = declined_decision();
   2736         duplicate_decision.event_id = event_id(2);
   2737         let projection = reduce_order_event_records(
   2738             &order_id("order-1"),
   2739             vec![
   2740                 RadrootsOrderEventRecord::Cancellation(cancellation(event_id(1))),
   2741                 RadrootsOrderEventRecord::RevisionDecision(accepted_revision_decision()),
   2742                 RadrootsOrderEventRecord::RevisionProposal(revision_proposal()),
   2743                 RadrootsOrderEventRecord::Decision(accepted_decision()),
   2744                 RadrootsOrderEventRecord::Decision(duplicate_decision),
   2745                 RadrootsOrderEventRecord::Request(request_record()),
   2746             ],
   2747         );
   2748 
   2749         assert_eq!(projection.status, RadrootsOrderStatus::Invalid);
   2750         assert_order_issue_kind(
   2751             &projection.issues,
   2752             RadrootsOrderIssue::ForkedLifecycle {
   2753                 event_ids: Vec::new(),
   2754             },
   2755         );
   2756         assert_eq!(
   2757             super::projection_issue_event_ids(&projection.issues),
   2758             vec![event_id(2), event_id(4), event_id(5)]
   2759         );
   2760 
   2761         let mut duplicate_request = request_record();
   2762         duplicate_request.payload.order_id = order_id("order-duplicate");
   2763         let mut duplicate_request_later = duplicate_request.clone();
   2764         duplicate_request_later.payload.buyer_pubkey = public_key(OTHER);
   2765         let deduped =
   2766             super::unique_request_records(vec![duplicate_request.clone(), duplicate_request_later]);
   2767         assert_eq!(deduped.len(), 1);
   2768         assert_eq!(
   2769             deduped[0].payload.order_id,
   2770             duplicate_request.payload.order_id
   2771         );
   2772         assert_eq!(
   2773             deduped[0].payload.buyer_pubkey,
   2774             duplicate_request.payload.buyer_pubkey
   2775         );
   2776     }
   2777 
   2778     #[test]
   2779     fn canonicalize_order_request_reports_signer_listing_and_item_errors() {
   2780         let canonical =
   2781             super::canonicalize_order_request_for_signer(request_record().payload, BUYER).unwrap();
   2782         assert_eq!(canonical.buyer_pubkey, public_key(BUYER));
   2783         assert_eq!(canonical.seller_pubkey, public_key(SELLER));
   2784 
   2785         let mut unsorted_items = request_record().payload;
   2786         unsorted_items.items.push(RadrootsOrderItem {
   2787             bin_id: bin_id("bin-0"),
   2788             bin_count: 1,
   2789         });
   2790         let canonical =
   2791             super::canonicalize_order_request_for_signer(unsorted_items, BUYER).unwrap();
   2792         assert_eq!(canonical.items[0].bin_id, bin_id("bin-0"));
   2793         assert_eq!(canonical.items[1].bin_id, bin_id("bin-1"));
   2794 
   2795         assert!(matches!(
   2796             super::parse_public_listing_addr("not-an-address"),
   2797             Err(super::RadrootsOrderCanonicalizationError::InvalidListingAddress(_))
   2798         ));
   2799         assert!(matches!(
   2800             super::parse_public_listing_addr(format!(
   2801                 "{KIND_LISTING_DRAFT}:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg"
   2802             )),
   2803             Err(super::RadrootsOrderCanonicalizationError::InvalidListingKind)
   2804         ));
   2805         assert!(matches!(
   2806             super::parse_public_listing_addr(format!("30023:{SELLER}:AAAAAAAAAAAAAAAAAAAAAg")),
   2807             Err(super::RadrootsOrderCanonicalizationError::InvalidListingKind)
   2808         ));
   2809 
   2810         assert!(matches!(
   2811             super::canonicalize_order_request_for_signer(request_record().payload, SELLER),
   2812             Err(super::RadrootsOrderCanonicalizationError::InvalidBuyerSigner)
   2813         ));
   2814 
   2815         let mut seller_mismatch = request_record().payload;
   2816         seller_mismatch.seller_pubkey = public_key(OTHER);
   2817         assert!(matches!(
   2818             super::canonicalize_order_request_for_signer(seller_mismatch, BUYER),
   2819             Err(super::RadrootsOrderCanonicalizationError::InvalidSellerListing)
   2820         ));
   2821 
   2822         let mut invalid_kind = request_record().payload;
   2823         invalid_kind.listing_addr = draft_listing_addr();
   2824         assert!(matches!(
   2825             super::canonicalize_order_request_for_signer(invalid_kind, BUYER),
   2826             Err(super::RadrootsOrderCanonicalizationError::InvalidListingKind)
   2827         ));
   2828 
   2829         let mut missing_items = request_record().payload;
   2830         missing_items.items.clear();
   2831         assert!(matches!(
   2832             super::canonicalize_order_request_for_signer(missing_items, BUYER),
   2833             Err(super::RadrootsOrderCanonicalizationError::MissingItems)
   2834         ));
   2835 
   2836         let mut zero_count = request_record().payload;
   2837         zero_count.items[0].bin_count = 0;
   2838         assert!(matches!(
   2839             super::canonicalize_order_request_for_signer(zero_count, BUYER),
   2840             Err(super::RadrootsOrderCanonicalizationError::InvalidBinCount { index: 0 })
   2841         ));
   2842     }
   2843 
   2844     #[test]
   2845     fn canonicalize_order_decision_reports_signer_and_decision_errors() {
   2846         let canonical =
   2847             super::canonicalize_order_decision_for_signer(accepted_decision().payload, SELLER)
   2848                 .unwrap();
   2849         assert_eq!(canonical.seller_pubkey, public_key(SELLER));
   2850 
   2851         let mut unsorted_commitments = accepted_decision().payload;
   2852         if let RadrootsOrderDecisionOutcome::Accepted {
   2853             inventory_commitments,
   2854         } = &mut unsorted_commitments.decision
   2855         {
   2856             inventory_commitments.push(RadrootsOrderInventoryCommitment {
   2857                 bin_id: bin_id("bin-0"),
   2858                 bin_count: 1,
   2859             });
   2860         }
   2861         let canonical =
   2862             super::canonicalize_order_decision_for_signer(unsorted_commitments, SELLER).unwrap();
   2863         if let RadrootsOrderDecisionOutcome::Accepted {
   2864             inventory_commitments,
   2865         } = canonical.decision
   2866         {
   2867             assert_eq!(inventory_commitments[0].bin_id, bin_id("bin-0"));
   2868             assert_eq!(inventory_commitments[1].bin_id, bin_id("bin-1"));
   2869         }
   2870 
   2871         let mut listing_seller_mismatch = accepted_decision().payload;
   2872         listing_seller_mismatch.listing_addr = other_seller_listing_addr();
   2873         assert!(matches!(
   2874             super::canonicalize_order_decision_for_signer(listing_seller_mismatch, SELLER),
   2875             Err(super::RadrootsOrderCanonicalizationError::InvalidSellerListing)
   2876         ));
   2877 
   2878         assert!(matches!(
   2879             super::canonicalize_order_decision_for_signer(accepted_decision().payload, BUYER),
   2880             Err(super::RadrootsOrderCanonicalizationError::InvalidSellerListing)
   2881         ));
   2882 
   2883         let mut missing_commitments = accepted_decision().payload;
   2884         missing_commitments.decision = RadrootsOrderDecisionOutcome::Accepted {
   2885             inventory_commitments: Vec::new(),
   2886         };
   2887         assert!(matches!(
   2888             super::canonicalize_order_decision_for_signer(missing_commitments, SELLER),
   2889             Err(super::RadrootsOrderCanonicalizationError::MissingInventoryCommitments)
   2890         ));
   2891 
   2892         let mut zero_commitment = accepted_decision().payload;
   2893         if let RadrootsOrderDecisionOutcome::Accepted {
   2894             inventory_commitments,
   2895         } = &mut zero_commitment.decision
   2896         {
   2897             inventory_commitments[0].bin_count = 0;
   2898         }
   2899         assert!(matches!(
   2900             super::canonicalize_order_decision_for_signer(zero_commitment, SELLER),
   2901             Err(
   2902                 super::RadrootsOrderCanonicalizationError::InvalidInventoryCommitmentCount {
   2903                     index: 0
   2904                 }
   2905             )
   2906         ));
   2907 
   2908         let mut declined = declined_decision().payload;
   2909         declined.decision = RadrootsOrderDecisionOutcome::Declined {
   2910             reason: "  already sold  ".into(),
   2911         };
   2912         let declined = super::canonicalize_order_decision_for_signer(declined, SELLER).unwrap();
   2913         assert_eq!(
   2914             declined.decision,
   2915             RadrootsOrderDecisionOutcome::Declined {
   2916                 reason: "already sold".into()
   2917             }
   2918         );
   2919 
   2920         let mut blank_reason = declined_decision().payload;
   2921         blank_reason.decision = RadrootsOrderDecisionOutcome::Declined { reason: " ".into() };
   2922         assert!(matches!(
   2923             super::canonicalize_order_decision_for_signer(blank_reason, SELLER),
   2924             Err(super::RadrootsOrderCanonicalizationError::EmptyField(
   2925                 "reason"
   2926             ))
   2927         ));
   2928     }
   2929 
   2930     #[cfg(feature = "serde_json")]
   2931     #[test]
   2932     fn order_economics_digest_is_stable_sha256_hex() {
   2933         let digest = super::radroots_order_economics_digest(&economics(2)).unwrap();
   2934         assert_eq!(
   2935             digest,
   2936             super::radroots_order_economics_digest(&economics(2)).unwrap()
   2937         );
   2938         assert!(digest.starts_with("sha256:"));
   2939         assert_eq!(digest.len(), "sha256:".len() + 64);
   2940     }
   2941 
   2942     #[test]
   2943     fn order_helper_sorting_and_matching_paths_are_deterministic() {
   2944         let request_items = vec![
   2945             RadrootsOrderItem {
   2946                 bin_id: bin_id("bin-2"),
   2947                 bin_count: 1,
   2948             },
   2949             RadrootsOrderItem {
   2950                 bin_id: bin_id("bin-1"),
   2951                 bin_count: 2,
   2952             },
   2953         ];
   2954         let matching_commitments = vec![
   2955             RadrootsOrderInventoryCommitment {
   2956                 bin_id: bin_id("bin-1"),
   2957                 bin_count: 2,
   2958             },
   2959             RadrootsOrderInventoryCommitment {
   2960                 bin_id: bin_id("bin-2"),
   2961                 bin_count: 1,
   2962             },
   2963         ];
   2964         assert!(super::inventory_commitments_match_request(
   2965             &request_items,
   2966             &matching_commitments
   2967         ));
   2968         assert!(!super::inventory_commitments_match_request(
   2969             &request_items,
   2970             &matching_commitments[..1]
   2971         ));
   2972         let mut count_mismatch = matching_commitments.clone();
   2973         count_mismatch[0].bin_count = 1;
   2974         assert!(!super::inventory_commitments_match_request(
   2975             &request_items,
   2976             &count_mismatch
   2977         ));
   2978         let mut bin_mismatch = matching_commitments.clone();
   2979         bin_mismatch[0].bin_id = bin_id("bin-3");
   2980         assert!(!super::inventory_commitments_match_request(
   2981             &request_items,
   2982             &bin_mismatch
   2983         ));
   2984 
   2985         let mut order_issues = vec![
   2986             RadrootsOrderIssue::ForkedLifecycle {
   2987                 event_ids: vec![event_id(9), event_id(3)],
   2988             },
   2989             RadrootsOrderIssue::CancellationWithoutCancellableOrder {
   2990                 event_id: event_id(5),
   2991             },
   2992             RadrootsOrderIssue::DecisionPayloadInvalid {
   2993                 event_id: event_id(2),
   2994             },
   2995             RadrootsOrderIssue::MissingRequest,
   2996         ];
   2997         assert_eq!(
   2998             super::projection_issue_event_ids(&order_issues),
   2999             vec![event_id(2), event_id(3), event_id(5), event_id(9)]
   3000         );
   3001         order_issues.sort_by(super::order_issue_sort_key);
   3002         assert!(matches!(
   3003             order_issues[0],
   3004             RadrootsOrderIssue::MissingRequest
   3005         ));
   3006         assert!(matches!(
   3007             order_issues[1],
   3008             RadrootsOrderIssue::DecisionPayloadInvalid { .. }
   3009         ));
   3010         assert!(matches!(
   3011             order_issues[2],
   3012             RadrootsOrderIssue::CancellationWithoutCancellableOrder { .. }
   3013         ));
   3014         assert!(matches!(
   3015             order_issues[3],
   3016             RadrootsOrderIssue::ForkedLifecycle { .. }
   3017         ));
   3018 
   3019         let mut tied_order_issues = vec![
   3020             RadrootsOrderIssue::DecisionPayloadInvalid {
   3021                 event_id: event_id(8),
   3022             },
   3023             RadrootsOrderIssue::DecisionPayloadInvalid {
   3024                 event_id: event_id(7),
   3025             },
   3026         ];
   3027         tied_order_issues.sort_by(super::order_issue_sort_key);
   3028         let RadrootsOrderIssue::DecisionPayloadInvalid {
   3029             event_id: issue_event_id,
   3030         } = &tied_order_issues[0]
   3031         else {
   3032             panic!("expected decision issue");
   3033         };
   3034         assert_eq!(issue_event_id, &event_id(7));
   3035 
   3036         let mut inventory_issues = vec![
   3037             RadrootsListingInventoryAccountingIssue::OverReserved {
   3038                 bin_id: bin_id("bin-2"),
   3039                 available_count: 1,
   3040                 reserved_count: 2,
   3041                 event_ids: vec![event_id(8)],
   3042             },
   3043             RadrootsListingInventoryAccountingIssue::UnknownInventoryBin {
   3044                 bin_id: bin_id("bin-1"),
   3045                 event_ids: vec![event_id(7)],
   3046             },
   3047             RadrootsListingInventoryAccountingIssue::ArithmeticOverflow {
   3048                 bin_id: bin_id("bin-3"),
   3049                 event_ids: vec![event_id(6)],
   3050             },
   3051             RadrootsListingInventoryAccountingIssue::InvalidOrder {
   3052                 order_id: order_id("order-1"),
   3053                 event_ids: vec![event_id(5)],
   3054             },
   3055         ];
   3056         inventory_issues.sort_by(super::inventory_issue_sort_key);
   3057         assert!(matches!(
   3058             inventory_issues[0],
   3059             RadrootsListingInventoryAccountingIssue::InvalidOrder { .. }
   3060         ));
   3061         assert!(matches!(
   3062             inventory_issues[1],
   3063             RadrootsListingInventoryAccountingIssue::ArithmeticOverflow { .. }
   3064         ));
   3065         assert!(matches!(
   3066             inventory_issues[2],
   3067             RadrootsListingInventoryAccountingIssue::UnknownInventoryBin { .. }
   3068         ));
   3069         assert!(matches!(
   3070             inventory_issues[3],
   3071             RadrootsListingInventoryAccountingIssue::OverReserved { .. }
   3072         ));
   3073 
   3074         let mut tied_inventory_issues = vec![
   3075             RadrootsListingInventoryAccountingIssue::UnknownInventoryBin {
   3076                 bin_id: bin_id("bin-2"),
   3077                 event_ids: vec![event_id(9)],
   3078             },
   3079             RadrootsListingInventoryAccountingIssue::UnknownInventoryBin {
   3080                 bin_id: bin_id("bin-1"),
   3081                 event_ids: vec![event_id(8)],
   3082             },
   3083             RadrootsListingInventoryAccountingIssue::UnknownInventoryBin {
   3084                 bin_id: bin_id("bin-1"),
   3085                 event_ids: vec![event_id(7)],
   3086             },
   3087         ];
   3088         tied_inventory_issues.sort_by(super::inventory_issue_sort_key);
   3089         assert_eq!(
   3090             super::inventory_issue_id(&tied_inventory_issues[0]),
   3091             "bin-1"
   3092         );
   3093         assert_eq!(
   3094             super::inventory_issue_event_ids(&tied_inventory_issues[0]),
   3095             &[event_id(7)]
   3096         );
   3097 
   3098         let invalid = super::invalid_projection(
   3099             &order_id("order-1"),
   3100             Some(&request_record()),
   3101             vec![RadrootsOrderIssue::MissingRequest],
   3102         );
   3103         assert_eq!(invalid.last_event_id, Some(event_id(1)));
   3104     }
   3105 
   3106     #[test]
   3107     fn order_issue_rank_and_event_id_helpers_cover_every_issue_variant() {
   3108         let id = event_id(42);
   3109         let event_ids = vec![id.clone()];
   3110         let issues = vec![
   3111             RadrootsOrderIssue::MissingRequest,
   3112             RadrootsOrderIssue::MultipleRequests {
   3113                 event_ids: event_ids.clone(),
   3114             },
   3115             RadrootsOrderIssue::RequestPayloadInvalid {
   3116                 event_id: id.clone(),
   3117             },
   3118             RadrootsOrderIssue::RequestOrderIdMismatch {
   3119                 event_id: id.clone(),
   3120             },
   3121             RadrootsOrderIssue::RequestAuthorMismatch {
   3122                 event_id: id.clone(),
   3123             },
   3124             RadrootsOrderIssue::RequestListingAddressInvalid {
   3125                 event_id: id.clone(),
   3126             },
   3127             RadrootsOrderIssue::RequestSellerListingMismatch {
   3128                 event_id: id.clone(),
   3129             },
   3130             RadrootsOrderIssue::DecisionPayloadInvalid {
   3131                 event_id: id.clone(),
   3132             },
   3133             RadrootsOrderIssue::DecisionOrderIdMismatch {
   3134                 event_id: id.clone(),
   3135             },
   3136             RadrootsOrderIssue::DecisionAuthorMismatch {
   3137                 event_id: id.clone(),
   3138             },
   3139             RadrootsOrderIssue::DecisionCounterpartyMismatch {
   3140                 event_id: id.clone(),
   3141             },
   3142             RadrootsOrderIssue::DecisionBuyerMismatch {
   3143                 event_id: id.clone(),
   3144             },
   3145             RadrootsOrderIssue::DecisionSellerMismatch {
   3146                 event_id: id.clone(),
   3147             },
   3148             RadrootsOrderIssue::DecisionListingAddressInvalid {
   3149                 event_id: id.clone(),
   3150             },
   3151             RadrootsOrderIssue::DecisionListingMismatch {
   3152                 event_id: id.clone(),
   3153             },
   3154             RadrootsOrderIssue::DecisionRootMismatch {
   3155                 event_id: id.clone(),
   3156             },
   3157             RadrootsOrderIssue::DecisionPreviousMismatch {
   3158                 event_id: id.clone(),
   3159             },
   3160             RadrootsOrderIssue::DecisionMissingInventoryCommitments {
   3161                 event_id: id.clone(),
   3162             },
   3163             RadrootsOrderIssue::DecisionInventoryCommitmentMismatch {
   3164                 event_id: id.clone(),
   3165             },
   3166             RadrootsOrderIssue::DecisionMissingReason {
   3167                 event_id: id.clone(),
   3168             },
   3169             RadrootsOrderIssue::ConflictingDecisions {
   3170                 event_ids: event_ids.clone(),
   3171             },
   3172             RadrootsOrderIssue::RevisionProposalPayloadInvalid {
   3173                 event_id: id.clone(),
   3174             },
   3175             RadrootsOrderIssue::RevisionProposalOrderIdMismatch {
   3176                 event_id: id.clone(),
   3177             },
   3178             RadrootsOrderIssue::RevisionProposalAuthorMismatch {
   3179                 event_id: id.clone(),
   3180             },
   3181             RadrootsOrderIssue::RevisionProposalCounterpartyMismatch {
   3182                 event_id: id.clone(),
   3183             },
   3184             RadrootsOrderIssue::RevisionProposalBuyerMismatch {
   3185                 event_id: id.clone(),
   3186             },
   3187             RadrootsOrderIssue::RevisionProposalSellerMismatch {
   3188                 event_id: id.clone(),
   3189             },
   3190             RadrootsOrderIssue::RevisionProposalListingAddressInvalid {
   3191                 event_id: id.clone(),
   3192             },
   3193             RadrootsOrderIssue::RevisionProposalListingMismatch {
   3194                 event_id: id.clone(),
   3195             },
   3196             RadrootsOrderIssue::RevisionProposalRootMismatch {
   3197                 event_id: id.clone(),
   3198             },
   3199             RadrootsOrderIssue::RevisionProposalPreviousMismatch {
   3200                 event_id: id.clone(),
   3201             },
   3202             RadrootsOrderIssue::RevisionDecisionWithoutProposal {
   3203                 event_id: id.clone(),
   3204             },
   3205             RadrootsOrderIssue::RevisionDecisionPayloadInvalid {
   3206                 event_id: id.clone(),
   3207             },
   3208             RadrootsOrderIssue::RevisionDecisionOrderIdMismatch {
   3209                 event_id: id.clone(),
   3210             },
   3211             RadrootsOrderIssue::RevisionDecisionAuthorMismatch {
   3212                 event_id: id.clone(),
   3213             },
   3214             RadrootsOrderIssue::RevisionDecisionCounterpartyMismatch {
   3215                 event_id: id.clone(),
   3216             },
   3217             RadrootsOrderIssue::RevisionDecisionBuyerMismatch {
   3218                 event_id: id.clone(),
   3219             },
   3220             RadrootsOrderIssue::RevisionDecisionSellerMismatch {
   3221                 event_id: id.clone(),
   3222             },
   3223             RadrootsOrderIssue::RevisionDecisionListingAddressInvalid {
   3224                 event_id: id.clone(),
   3225             },
   3226             RadrootsOrderIssue::RevisionDecisionListingMismatch {
   3227                 event_id: id.clone(),
   3228             },
   3229             RadrootsOrderIssue::RevisionDecisionRootMismatch {
   3230                 event_id: id.clone(),
   3231             },
   3232             RadrootsOrderIssue::RevisionDecisionPreviousMismatch {
   3233                 event_id: id.clone(),
   3234             },
   3235             RadrootsOrderIssue::RevisionDecisionRevisionIdMismatch {
   3236                 event_id: id.clone(),
   3237             },
   3238             RadrootsOrderIssue::CancellationWithoutCancellableOrder {
   3239                 event_id: id.clone(),
   3240             },
   3241             RadrootsOrderIssue::CancellationPayloadInvalid {
   3242                 event_id: id.clone(),
   3243             },
   3244             RadrootsOrderIssue::CancellationOrderIdMismatch {
   3245                 event_id: id.clone(),
   3246             },
   3247             RadrootsOrderIssue::CancellationAuthorMismatch {
   3248                 event_id: id.clone(),
   3249             },
   3250             RadrootsOrderIssue::CancellationCounterpartyMismatch {
   3251                 event_id: id.clone(),
   3252             },
   3253             RadrootsOrderIssue::CancellationBuyerMismatch {
   3254                 event_id: id.clone(),
   3255             },
   3256             RadrootsOrderIssue::CancellationSellerMismatch {
   3257                 event_id: id.clone(),
   3258             },
   3259             RadrootsOrderIssue::CancellationListingAddressInvalid {
   3260                 event_id: id.clone(),
   3261             },
   3262             RadrootsOrderIssue::CancellationListingMismatch {
   3263                 event_id: id.clone(),
   3264             },
   3265             RadrootsOrderIssue::CancellationRootMismatch {
   3266                 event_id: id.clone(),
   3267             },
   3268             RadrootsOrderIssue::CancellationPreviousMismatch {
   3269                 event_id: id.clone(),
   3270             },
   3271             RadrootsOrderIssue::ForkedLifecycle { event_ids },
   3272         ];
   3273 
   3274         for (rank, issue) in issues.iter().enumerate() {
   3275             assert_eq!(super::order_issue_rank(issue), rank as u8);
   3276         }
   3277         assert_eq!(super::projection_issue_event_ids(&issues), vec![id]);
   3278     }
   3279 
   3280     #[test]
   3281     fn inventory_issue_helpers_cover_all_issue_variants() {
   3282         let id = event_id(9);
   3283         let issues = vec![
   3284             RadrootsListingInventoryAccountingIssue::InvalidOrder {
   3285                 order_id: order_id("order-1"),
   3286                 event_ids: vec![id.clone()],
   3287             },
   3288             RadrootsListingInventoryAccountingIssue::ArithmeticOverflow {
   3289                 bin_id: bin_id("bin-1"),
   3290                 event_ids: vec![id.clone()],
   3291             },
   3292             RadrootsListingInventoryAccountingIssue::UnknownInventoryBin {
   3293                 bin_id: bin_id("bin-2"),
   3294                 event_ids: vec![id.clone()],
   3295             },
   3296             RadrootsListingInventoryAccountingIssue::OverReserved {
   3297                 bin_id: bin_id("bin-3"),
   3298                 available_count: 1,
   3299                 reserved_count: 2,
   3300                 event_ids: vec![id.clone()],
   3301             },
   3302         ];
   3303 
   3304         assert_eq!(super::inventory_issue_rank(&issues[0]), 0);
   3305         assert_eq!(super::inventory_issue_rank(&issues[1]), 1);
   3306         assert_eq!(super::inventory_issue_rank(&issues[2]), 2);
   3307         assert_eq!(super::inventory_issue_rank(&issues[3]), 3);
   3308         assert_eq!(super::inventory_issue_id(&issues[0]), "order-1");
   3309         assert_eq!(super::inventory_issue_id(&issues[1]), "bin-1");
   3310         assert_eq!(super::inventory_issue_id(&issues[2]), "bin-2");
   3311         assert_eq!(super::inventory_issue_id(&issues[3]), "bin-3");
   3312         for issue in &issues {
   3313             assert_eq!(super::inventory_issue_event_ids(issue), &[id.clone()]);
   3314         }
   3315     }
   3316 
   3317     #[test]
   3318     fn reducer_reports_missing_request_for_each_non_request_input_family() {
   3319         let decision_only = reduce_order_events(
   3320             &order_id("order-1"),
   3321             RadrootsOrderReductionInputs {
   3322                 requests: Vec::<RadrootsOrderRequestRecord>::new(),
   3323                 decisions: vec![accepted_decision()],
   3324                 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(),
   3325                 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(),
   3326                 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(),
   3327             },
   3328         );
   3329         assert_order_issue_kind(&decision_only.issues, RadrootsOrderIssue::MissingRequest);
   3330 
   3331         let proposal_only = reduce_order_events(
   3332             &order_id("order-1"),
   3333             RadrootsOrderReductionInputs {
   3334                 requests: Vec::<RadrootsOrderRequestRecord>::new(),
   3335                 decisions: Vec::<RadrootsOrderDecisionRecord>::new(),
   3336                 revision_proposals: vec![revision_proposal()],
   3337                 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(),
   3338                 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(),
   3339             },
   3340         );
   3341         assert_order_issue_kind(&proposal_only.issues, RadrootsOrderIssue::MissingRequest);
   3342 
   3343         let revision_decision_only = reduce_order_events(
   3344             &order_id("order-1"),
   3345             RadrootsOrderReductionInputs {
   3346                 requests: Vec::<RadrootsOrderRequestRecord>::new(),
   3347                 decisions: Vec::<RadrootsOrderDecisionRecord>::new(),
   3348                 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(),
   3349                 revision_decisions: vec![accepted_revision_decision()],
   3350                 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(),
   3351             },
   3352         );
   3353         assert_order_issue_kind(
   3354             &revision_decision_only.issues,
   3355             RadrootsOrderIssue::MissingRequest,
   3356         );
   3357 
   3358         let cancellation_only = reduce_order_events(
   3359             &order_id("order-1"),
   3360             RadrootsOrderReductionInputs {
   3361                 requests: Vec::<RadrootsOrderRequestRecord>::new(),
   3362                 decisions: Vec::<RadrootsOrderDecisionRecord>::new(),
   3363                 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(),
   3364                 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(),
   3365                 cancellations: vec![cancellation(event_id(1))],
   3366             },
   3367         );
   3368         assert_order_issue_kind(
   3369             &cancellation_only.issues,
   3370             RadrootsOrderIssue::MissingRequest,
   3371         );
   3372     }
   3373 
   3374     #[test]
   3375     fn reducer_reports_multiple_valid_cancellations_as_forked_lifecycle() {
   3376         let mut second_cancellation = cancellation(event_id(1));
   3377         second_cancellation.event_id = event_id(6);
   3378         let projection = reduce(
   3379             Vec::new(),
   3380             Vec::new(),
   3381             Vec::new(),
   3382             vec![cancellation(event_id(1)), second_cancellation],
   3383         );
   3384 
   3385         assert_order_issue_kind(
   3386             &projection.issues,
   3387             RadrootsOrderIssue::ForkedLifecycle {
   3388                 event_ids: Vec::new(),
   3389             },
   3390         );
   3391         assert_eq!(projection.last_event_id, Some(event_id(6)));
   3392     }
   3393 
   3394     #[test]
   3395     fn inventory_accounting_private_helpers_cover_merge_sort_and_overflow_paths() {
   3396         let (bins, issues) = super::normalized_listing_inventory_bins(vec![
   3397             RadrootsListingInventoryBinAvailability {
   3398                 bin_id: bin_id("bin-1"),
   3399                 available_count: 1,
   3400             },
   3401             RadrootsListingInventoryBinAvailability {
   3402                 bin_id: bin_id("bin-1"),
   3403                 available_count: 2,
   3404             },
   3405         ]);
   3406         assert_eq!(issues, Vec::new());
   3407         assert_eq!(bins[0].available_count, 3);
   3408         assert_eq!(bins[0].remaining_count, 3);
   3409 
   3410         let mut overflow_bin = super::RadrootsListingInventoryBinAccounting {
   3411             bin_id: bin_id("bin-overflow"),
   3412             available_count: u64::MAX,
   3413             accepted_reserved_count: u64::MAX,
   3414             remaining_count: u64::MAX,
   3415             over_reserved: false,
   3416             accepted_orders: Vec::new(),
   3417         };
   3418         let mut overflow_issues = Vec::new();
   3419         super::add_inventory_reservation_event(
   3420             &mut overflow_bin,
   3421             &order_id("order-overflow"),
   3422             &event_id(90),
   3423             1,
   3424             &mut overflow_issues,
   3425         );
   3426         assert_inventory_issue_kind(
   3427             &overflow_issues,
   3428             RadrootsListingInventoryAccountingIssue::ArithmeticOverflow {
   3429                 bin_id: bin_id("bin-overflow"),
   3430                 event_ids: Vec::new(),
   3431             },
   3432         );
   3433 
   3434         let mut sorting_bin = super::RadrootsListingInventoryBinAccounting {
   3435             bin_id: bin_id("bin-sort"),
   3436             available_count: 1,
   3437             accepted_reserved_count: 2,
   3438             remaining_count: 1,
   3439             over_reserved: false,
   3440             accepted_orders: vec![
   3441                 super::RadrootsListingInventoryOrderReservation {
   3442                     order_id: order_id("order-2"),
   3443                     agreement_event_id: event_id(92),
   3444                     bin_count: 1,
   3445                 },
   3446                 super::RadrootsListingInventoryOrderReservation {
   3447                     order_id: order_id("order-1"),
   3448                     agreement_event_id: event_id(91),
   3449                     bin_count: 1,
   3450                 },
   3451                 super::RadrootsListingInventoryOrderReservation {
   3452                     order_id: order_id("order-1"),
   3453                     agreement_event_id: event_id(90),
   3454                     bin_count: 1,
   3455                 },
   3456             ],
   3457         };
   3458         let mut finish_issues = Vec::new();
   3459         super::finish_inventory_accounting_bins(
   3460             core::slice::from_mut(&mut sorting_bin),
   3461             &mut finish_issues,
   3462         );
   3463         assert_eq!(sorting_bin.remaining_count, 0);
   3464         assert!(sorting_bin.over_reserved);
   3465         assert_eq!(sorting_bin.accepted_orders[0].order_id, order_id("order-1"));
   3466         assert_eq!(
   3467             sorting_bin.accepted_orders[0].agreement_event_id,
   3468             event_id(90)
   3469         );
   3470         assert_inventory_issue_kind(
   3471             &finish_issues,
   3472             RadrootsListingInventoryAccountingIssue::OverReserved {
   3473                 bin_id: bin_id("bin-sort"),
   3474                 available_count: 1,
   3475                 reserved_count: 2,
   3476                 event_ids: Vec::new(),
   3477             },
   3478         );
   3479 
   3480         let mut fallback_request = request_record();
   3481         fallback_request.event_id = event_id(95);
   3482         let mut fallback_decision = accepted_decision();
   3483         fallback_decision.event_id = event_id(93);
   3484         let mut fallback_proposal = revision_proposal();
   3485         fallback_proposal.event_id = event_id(94);
   3486         let mut fallback_revision_decision = accepted_revision_decision();
   3487         fallback_revision_decision.event_id = event_id(92);
   3488         let mut fallback_cancellation = cancellation(event_id(3));
   3489         fallback_cancellation.event_id = event_id(91);
   3490         let fallback_ids = super::fallback_order_event_ids(
   3491             &[fallback_request],
   3492             &[fallback_decision],
   3493             &[fallback_proposal],
   3494             &[fallback_revision_decision],
   3495             &[fallback_cancellation],
   3496         );
   3497         assert_eq!(
   3498             fallback_ids,
   3499             vec![
   3500                 event_id(91),
   3501                 event_id(92),
   3502                 event_id(93),
   3503                 event_id(94),
   3504                 event_id(95)
   3505             ]
   3506         );
   3507     }
   3508 
   3509     #[test]
   3510     fn reducer_reports_missing_duplicate_and_forked_lifecycles() {
   3511         let missing = reduce_order_events(
   3512             &order_id("order-1"),
   3513             RadrootsOrderReductionInputs {
   3514                 requests: Vec::<RadrootsOrderRequestRecord>::new(),
   3515                 decisions: Vec::<RadrootsOrderDecisionRecord>::new(),
   3516                 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(),
   3517                 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(),
   3518                 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(),
   3519             },
   3520         );
   3521         assert_eq!(missing.status, RadrootsOrderStatus::Missing);
   3522 
   3523         let missing_request = reduce_order_events(
   3524             &order_id("order-1"),
   3525             RadrootsOrderReductionInputs {
   3526                 requests: Vec::<RadrootsOrderRequestRecord>::new(),
   3527                 decisions: vec![accepted_decision()],
   3528                 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(),
   3529                 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(),
   3530                 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(),
   3531             },
   3532         );
   3533         assert_order_issue_kind(&missing_request.issues, RadrootsOrderIssue::MissingRequest);
   3534 
   3535         let mut duplicate_request = request_record();
   3536         duplicate_request.event_id = event_id(6);
   3537         let duplicate = reduce_order_events(
   3538             &order_id("order-1"),
   3539             RadrootsOrderReductionInputs {
   3540                 requests: vec![request_record(), duplicate_request],
   3541                 decisions: Vec::<RadrootsOrderDecisionRecord>::new(),
   3542                 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(),
   3543                 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(),
   3544                 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(),
   3545             },
   3546         );
   3547         assert_order_issue_kind(
   3548             &duplicate.issues,
   3549             RadrootsOrderIssue::MultipleRequests {
   3550                 event_ids: Vec::new(),
   3551             },
   3552         );
   3553 
   3554         let mut second_decision = declined_decision();
   3555         second_decision.event_id = event_id(6);
   3556         let conflicting = reduce(
   3557             vec![accepted_decision(), second_decision],
   3558             vec![],
   3559             vec![],
   3560             vec![],
   3561         );
   3562         assert_order_issue_kind(
   3563             &conflicting.issues,
   3564             RadrootsOrderIssue::ConflictingDecisions {
   3565                 event_ids: Vec::new(),
   3566             },
   3567         );
   3568 
   3569         let without_proposal = reduce(
   3570             Vec::new(),
   3571             Vec::new(),
   3572             vec![accepted_revision_decision()],
   3573             Vec::new(),
   3574         );
   3575         assert_order_issue_kind(
   3576             &without_proposal.issues,
   3577             RadrootsOrderIssue::RevisionDecisionWithoutProposal {
   3578                 event_id: event_id(4),
   3579             },
   3580         );
   3581 
   3582         let mut second_proposal = revision_proposal();
   3583         second_proposal.event_id = event_id(6);
   3584         let multiple_proposals = reduce(
   3585             Vec::new(),
   3586             vec![revision_proposal(), second_proposal],
   3587             vec![],
   3588             vec![],
   3589         );
   3590         assert_order_issue_kind(
   3591             &multiple_proposals.issues,
   3592             RadrootsOrderIssue::ForkedLifecycle {
   3593                 event_ids: Vec::new(),
   3594             },
   3595         );
   3596 
   3597         let mut second_revision_decision = accepted_revision_decision();
   3598         second_revision_decision.event_id = event_id(7);
   3599         let multiple_revision_decisions = reduce(
   3600             Vec::new(),
   3601             vec![revision_proposal()],
   3602             vec![accepted_revision_decision(), second_revision_decision],
   3603             Vec::new(),
   3604         );
   3605         assert_order_issue_kind(
   3606             &multiple_revision_decisions.issues,
   3607             RadrootsOrderIssue::ForkedLifecycle {
   3608                 event_ids: Vec::new(),
   3609             },
   3610         );
   3611 
   3612         let decided_then_revised = reduce(
   3613             vec![accepted_decision()],
   3614             vec![revision_proposal()],
   3615             Vec::new(),
   3616             Vec::new(),
   3617         );
   3618         assert_order_issue_kind(
   3619             &decided_then_revised.issues,
   3620             RadrootsOrderIssue::ForkedLifecycle {
   3621                 event_ids: Vec::new(),
   3622             },
   3623         );
   3624 
   3625         let decided_then_revision_decision = reduce(
   3626             vec![accepted_decision()],
   3627             Vec::new(),
   3628             vec![accepted_revision_decision()],
   3629             Vec::new(),
   3630         );
   3631         assert_order_issue_kind(
   3632             &decided_then_revision_decision.issues,
   3633             RadrootsOrderIssue::ForkedLifecycle {
   3634                 event_ids: Vec::new(),
   3635             },
   3636         );
   3637     }
   3638 
   3639     #[test]
   3640     fn reducer_covers_revision_and_cancellation_edge_paths() {
   3641         let pending_revision = reduce(
   3642             Vec::new(),
   3643             vec![revision_proposal()],
   3644             Vec::new(),
   3645             Vec::new(),
   3646         );
   3647         assert_eq!(pending_revision.status, RadrootsOrderStatus::Requested);
   3648         assert_eq!(
   3649             pending_revision.pending_revision_event_id,
   3650             Some(event_id(3))
   3651         );
   3652         assert_eq!(
   3653             pending_revision.economics.expect("pending economics").items[0].bin_count,
   3654             1
   3655         );
   3656 
   3657         let mut bad_proposal_previous = revision_proposal();
   3658         bad_proposal_previous.prev_event_id = event_id(8);
   3659         bad_proposal_previous.payload.prev_event_id = event_id(8);
   3660         let bad_proposal = reduce(
   3661             Vec::new(),
   3662             vec![bad_proposal_previous],
   3663             Vec::new(),
   3664             Vec::new(),
   3665         );
   3666         assert_order_issue_kind(
   3667             &bad_proposal.issues,
   3668             RadrootsOrderIssue::RevisionProposalPreviousMismatch {
   3669                 event_id: event_id(3),
   3670             },
   3671         );
   3672 
   3673         let mut bad_decision_previous = accepted_revision_decision();
   3674         bad_decision_previous.prev_event_id = event_id(8);
   3675         bad_decision_previous.payload.prev_event_id = event_id(8);
   3676         let bad_decision = reduce(
   3677             Vec::new(),
   3678             vec![revision_proposal()],
   3679             vec![bad_decision_previous],
   3680             Vec::new(),
   3681         );
   3682         assert_order_issue_kind(
   3683             &bad_decision.issues,
   3684             RadrootsOrderIssue::RevisionDecisionPreviousMismatch {
   3685                 event_id: event_id(4),
   3686             },
   3687         );
   3688 
   3689         let mut bad_revision_id = accepted_revision_decision();
   3690         bad_revision_id.payload.revision_id = revision_id("revision-2");
   3691         let bad_revision = reduce(
   3692             Vec::new(),
   3693             vec![revision_proposal()],
   3694             vec![bad_revision_id],
   3695             Vec::new(),
   3696         );
   3697         assert_order_issue_kind(
   3698             &bad_revision.issues,
   3699             RadrootsOrderIssue::RevisionDecisionRevisionIdMismatch {
   3700                 event_id: event_id(4),
   3701             },
   3702         );
   3703 
   3704         let mut declined_revision = accepted_revision_decision();
   3705         declined_revision.payload.decision = RadrootsOrderRevisionOutcome::Declined {
   3706             reason: "too late".into(),
   3707         };
   3708         let declined = reduce(
   3709             Vec::new(),
   3710             vec![revision_proposal()],
   3711             vec![declined_revision],
   3712             Vec::new(),
   3713         );
   3714         assert_eq!(declined.status, RadrootsOrderStatus::Declined);
   3715         assert_eq!(declined.pending_revision_event_id, Some(event_id(3)));
   3716 
   3717         let cancellation_after_decision = reduce(
   3718             vec![declined_decision()],
   3719             Vec::new(),
   3720             Vec::new(),
   3721             vec![cancellation(event_id(2))],
   3722         );
   3723         assert_order_issue_kind(
   3724             &cancellation_after_decision.issues,
   3725             RadrootsOrderIssue::ForkedLifecycle {
   3726                 event_ids: Vec::new(),
   3727             },
   3728         );
   3729 
   3730         let cancellation_after_revision_decision = reduce(
   3731             Vec::new(),
   3732             vec![revision_proposal()],
   3733             vec![accepted_revision_decision()],
   3734             vec![cancellation(event_id(4))],
   3735         );
   3736         assert_order_issue_kind(
   3737             &cancellation_after_revision_decision.issues,
   3738             RadrootsOrderIssue::ForkedLifecycle {
   3739                 event_ids: Vec::new(),
   3740             },
   3741         );
   3742 
   3743         let mut second_proposal = revision_proposal();
   3744         second_proposal.event_id = event_id(6);
   3745         let cancellation_after_multiple_proposals = reduce(
   3746             Vec::new(),
   3747             vec![revision_proposal(), second_proposal],
   3748             Vec::new(),
   3749             vec![cancellation(event_id(3))],
   3750         );
   3751         assert_order_issue_kind(
   3752             &cancellation_after_multiple_proposals.issues,
   3753             RadrootsOrderIssue::ForkedLifecycle {
   3754                 event_ids: Vec::new(),
   3755             },
   3756         );
   3757 
   3758         let cancellation_previous_mismatch = reduce(
   3759             Vec::new(),
   3760             vec![revision_proposal()],
   3761             Vec::new(),
   3762             vec![cancellation(event_id(1))],
   3763         );
   3764         assert_order_issue_kind(
   3765             &cancellation_previous_mismatch.issues,
   3766             RadrootsOrderIssue::CancellationPreviousMismatch {
   3767                 event_id: event_id(5),
   3768             },
   3769         );
   3770     }
   3771 
   3772     #[test]
   3773     fn reducer_validators_report_request_and_decision_issue_kinds() {
   3774         assert_request_issue(
   3775             |request| request.payload.items.clear(),
   3776             RadrootsOrderIssue::RequestPayloadInvalid {
   3777                 event_id: event_id(1),
   3778             },
   3779         );
   3780         assert_request_issue(
   3781             |request| request.payload.order_id = order_id("order-2"),
   3782             RadrootsOrderIssue::RequestOrderIdMismatch {
   3783                 event_id: event_id(1),
   3784             },
   3785         );
   3786         assert_request_issue(
   3787             |request| request.author_pubkey = public_key(SELLER),
   3788             RadrootsOrderIssue::RequestAuthorMismatch {
   3789                 event_id: event_id(1),
   3790             },
   3791         );
   3792         assert_request_issue(
   3793             |request| request.payload.listing_addr = draft_listing_addr(),
   3794             RadrootsOrderIssue::RequestListingAddressInvalid {
   3795                 event_id: event_id(1),
   3796             },
   3797         );
   3798         assert_request_issue(
   3799             |request| request.payload.seller_pubkey = public_key(OTHER),
   3800             RadrootsOrderIssue::RequestSellerListingMismatch {
   3801                 event_id: event_id(1),
   3802             },
   3803         );
   3804 
   3805         assert_decision_issue(
   3806             |decision| {
   3807                 decision.payload.decision = RadrootsOrderDecisionOutcome::Accepted {
   3808                     inventory_commitments: Vec::new(),
   3809                 };
   3810             },
   3811             RadrootsOrderIssue::DecisionMissingInventoryCommitments {
   3812                 event_id: event_id(2),
   3813             },
   3814         );
   3815         assert_decision_issue(
   3816             |decision| {
   3817                 decision.payload.decision =
   3818                     RadrootsOrderDecisionOutcome::Declined { reason: " ".into() };
   3819             },
   3820             RadrootsOrderIssue::DecisionMissingReason {
   3821                 event_id: event_id(2),
   3822             },
   3823         );
   3824         assert_decision_issue(
   3825             |decision| decision.payload.order_id = order_id("order-2"),
   3826             RadrootsOrderIssue::DecisionOrderIdMismatch {
   3827                 event_id: event_id(2),
   3828             },
   3829         );
   3830         assert_decision_issue(
   3831             |decision| decision.author_pubkey = public_key(BUYER),
   3832             RadrootsOrderIssue::DecisionAuthorMismatch {
   3833                 event_id: event_id(2),
   3834             },
   3835         );
   3836         assert_decision_issue(
   3837             |decision| decision.counterparty_pubkey = public_key(SELLER),
   3838             RadrootsOrderIssue::DecisionCounterpartyMismatch {
   3839                 event_id: event_id(2),
   3840             },
   3841         );
   3842         assert_decision_issue(
   3843             |decision| decision.payload.buyer_pubkey = public_key(SELLER),
   3844             RadrootsOrderIssue::DecisionBuyerMismatch {
   3845                 event_id: event_id(2),
   3846             },
   3847         );
   3848         assert_decision_issue(
   3849             |decision| decision.payload.seller_pubkey = public_key(BUYER),
   3850             RadrootsOrderIssue::DecisionSellerMismatch {
   3851                 event_id: event_id(2),
   3852             },
   3853         );
   3854         assert_decision_issue(
   3855             |decision| decision.payload.listing_addr = draft_listing_addr(),
   3856             RadrootsOrderIssue::DecisionListingAddressInvalid {
   3857                 event_id: event_id(2),
   3858             },
   3859         );
   3860         assert_decision_issue(
   3861             |decision| decision.payload.listing_addr = other_seller_listing_addr(),
   3862             RadrootsOrderIssue::DecisionListingMismatch {
   3863                 event_id: event_id(2),
   3864             },
   3865         );
   3866         assert_decision_issue(
   3867             |decision| decision.root_event_id = event_id(8),
   3868             RadrootsOrderIssue::DecisionRootMismatch {
   3869                 event_id: event_id(2),
   3870             },
   3871         );
   3872         assert_decision_issue(
   3873             |decision| decision.prev_event_id = event_id(8),
   3874             RadrootsOrderIssue::DecisionPreviousMismatch {
   3875                 event_id: event_id(2),
   3876             },
   3877         );
   3878         assert_decision_issue(
   3879             |decision| {
   3880                 if let RadrootsOrderDecisionOutcome::Accepted {
   3881                     inventory_commitments,
   3882                 } = &mut decision.payload.decision
   3883                 {
   3884                     inventory_commitments[0].bin_count = 1;
   3885                 }
   3886             },
   3887             RadrootsOrderIssue::DecisionInventoryCommitmentMismatch {
   3888                 event_id: event_id(2),
   3889             },
   3890         );
   3891     }
   3892 
   3893     #[test]
   3894     fn reducer_validators_report_revision_and_cancellation_issue_kinds() {
   3895         assert_revision_proposal_issue(
   3896             |proposal| proposal.payload.items.clear(),
   3897             RadrootsOrderIssue::RevisionProposalPayloadInvalid {
   3898                 event_id: event_id(3),
   3899             },
   3900         );
   3901         assert_revision_proposal_issue(
   3902             |proposal| proposal.payload.order_id = order_id("order-2"),
   3903             RadrootsOrderIssue::RevisionProposalOrderIdMismatch {
   3904                 event_id: event_id(3),
   3905             },
   3906         );
   3907         assert_revision_proposal_issue(
   3908             |proposal| proposal.author_pubkey = public_key(BUYER),
   3909             RadrootsOrderIssue::RevisionProposalAuthorMismatch {
   3910                 event_id: event_id(3),
   3911             },
   3912         );
   3913         assert_revision_proposal_issue(
   3914             |proposal| proposal.counterparty_pubkey = public_key(SELLER),
   3915             RadrootsOrderIssue::RevisionProposalCounterpartyMismatch {
   3916                 event_id: event_id(3),
   3917             },
   3918         );
   3919         assert_revision_proposal_issue(
   3920             |proposal| proposal.payload.buyer_pubkey = public_key(SELLER),
   3921             RadrootsOrderIssue::RevisionProposalBuyerMismatch {
   3922                 event_id: event_id(3),
   3923             },
   3924         );
   3925         assert_revision_proposal_issue(
   3926             |proposal| proposal.payload.seller_pubkey = public_key(BUYER),
   3927             RadrootsOrderIssue::RevisionProposalSellerMismatch {
   3928                 event_id: event_id(3),
   3929             },
   3930         );
   3931         assert_revision_proposal_issue(
   3932             |proposal| proposal.payload.listing_addr = draft_listing_addr(),
   3933             RadrootsOrderIssue::RevisionProposalListingAddressInvalid {
   3934                 event_id: event_id(3),
   3935             },
   3936         );
   3937         assert_revision_proposal_issue(
   3938             |proposal| proposal.payload.listing_addr = other_seller_listing_addr(),
   3939             RadrootsOrderIssue::RevisionProposalListingMismatch {
   3940                 event_id: event_id(3),
   3941             },
   3942         );
   3943         assert_revision_proposal_issue(
   3944             |proposal| proposal.root_event_id = event_id(8),
   3945             RadrootsOrderIssue::RevisionProposalRootMismatch {
   3946                 event_id: event_id(3),
   3947             },
   3948         );
   3949         assert_revision_proposal_issue(
   3950             |proposal| proposal.payload.root_event_id = event_id(8),
   3951             RadrootsOrderIssue::RevisionProposalRootMismatch {
   3952                 event_id: event_id(3),
   3953             },
   3954         );
   3955         assert_revision_proposal_issue(
   3956             |proposal| proposal.prev_event_id = event_id(8),
   3957             RadrootsOrderIssue::RevisionProposalPreviousMismatch {
   3958                 event_id: event_id(3),
   3959             },
   3960         );
   3961         assert_revision_proposal_issue(
   3962             |proposal| proposal.prev_event_id = event_id(3),
   3963             RadrootsOrderIssue::RevisionProposalPreviousMismatch {
   3964                 event_id: event_id(3),
   3965             },
   3966         );
   3967         assert_revision_proposal_issue(
   3968             |proposal| proposal.payload.prev_event_id = event_id(8),
   3969             RadrootsOrderIssue::RevisionProposalPreviousMismatch {
   3970                 event_id: event_id(3),
   3971             },
   3972         );
   3973 
   3974         assert_revision_decision_issue(
   3975             |decision| {
   3976                 decision.payload.decision =
   3977                     RadrootsOrderRevisionOutcome::Declined { reason: " ".into() };
   3978             },
   3979             RadrootsOrderIssue::RevisionDecisionPayloadInvalid {
   3980                 event_id: event_id(4),
   3981             },
   3982         );
   3983         assert_revision_decision_issue(
   3984             |decision| decision.payload.order_id = order_id("order-2"),
   3985             RadrootsOrderIssue::RevisionDecisionOrderIdMismatch {
   3986                 event_id: event_id(4),
   3987             },
   3988         );
   3989         assert_revision_decision_issue(
   3990             |decision| decision.author_pubkey = public_key(SELLER),
   3991             RadrootsOrderIssue::RevisionDecisionAuthorMismatch {
   3992                 event_id: event_id(4),
   3993             },
   3994         );
   3995         assert_revision_decision_issue(
   3996             |decision| decision.counterparty_pubkey = public_key(BUYER),
   3997             RadrootsOrderIssue::RevisionDecisionCounterpartyMismatch {
   3998                 event_id: event_id(4),
   3999             },
   4000         );
   4001         assert_revision_decision_issue(
   4002             |decision| decision.payload.buyer_pubkey = public_key(SELLER),
   4003             RadrootsOrderIssue::RevisionDecisionBuyerMismatch {
   4004                 event_id: event_id(4),
   4005             },
   4006         );
   4007         assert_revision_decision_issue(
   4008             |decision| decision.payload.seller_pubkey = public_key(BUYER),
   4009             RadrootsOrderIssue::RevisionDecisionSellerMismatch {
   4010                 event_id: event_id(4),
   4011             },
   4012         );
   4013         assert_revision_decision_issue(
   4014             |decision| decision.payload.listing_addr = draft_listing_addr(),
   4015             RadrootsOrderIssue::RevisionDecisionListingAddressInvalid {
   4016                 event_id: event_id(4),
   4017             },
   4018         );
   4019         assert_revision_decision_issue(
   4020             |decision| decision.payload.listing_addr = other_seller_listing_addr(),
   4021             RadrootsOrderIssue::RevisionDecisionListingMismatch {
   4022                 event_id: event_id(4),
   4023             },
   4024         );
   4025         assert_revision_decision_issue(
   4026             |decision| decision.root_event_id = event_id(8),
   4027             RadrootsOrderIssue::RevisionDecisionRootMismatch {
   4028                 event_id: event_id(4),
   4029             },
   4030         );
   4031         assert_revision_decision_issue(
   4032             |decision| decision.payload.root_event_id = event_id(8),
   4033             RadrootsOrderIssue::RevisionDecisionRootMismatch {
   4034                 event_id: event_id(4),
   4035             },
   4036         );
   4037         assert_revision_decision_issue(
   4038             |decision| decision.prev_event_id = event_id(8),
   4039             RadrootsOrderIssue::RevisionDecisionPreviousMismatch {
   4040                 event_id: event_id(4),
   4041             },
   4042         );
   4043         assert_revision_decision_issue(
   4044             |decision| decision.prev_event_id = event_id(4),
   4045             RadrootsOrderIssue::RevisionDecisionPreviousMismatch {
   4046                 event_id: event_id(4),
   4047             },
   4048         );
   4049         assert_revision_decision_issue(
   4050             |decision| decision.payload.prev_event_id = event_id(8),
   4051             RadrootsOrderIssue::RevisionDecisionPreviousMismatch {
   4052                 event_id: event_id(4),
   4053             },
   4054         );
   4055 
   4056         assert_cancellation_issue(
   4057             |cancellation| cancellation.payload.reason = " ".into(),
   4058             RadrootsOrderIssue::CancellationPayloadInvalid {
   4059                 event_id: event_id(5),
   4060             },
   4061         );
   4062         assert_cancellation_issue(
   4063             |cancellation| cancellation.payload.order_id = order_id("order-2"),
   4064             RadrootsOrderIssue::CancellationOrderIdMismatch {
   4065                 event_id: event_id(5),
   4066             },
   4067         );
   4068         assert_cancellation_issue(
   4069             |cancellation| cancellation.author_pubkey = public_key(SELLER),
   4070             RadrootsOrderIssue::CancellationAuthorMismatch {
   4071                 event_id: event_id(5),
   4072             },
   4073         );
   4074         assert_cancellation_issue(
   4075             |cancellation| cancellation.counterparty_pubkey = public_key(BUYER),
   4076             RadrootsOrderIssue::CancellationCounterpartyMismatch {
   4077                 event_id: event_id(5),
   4078             },
   4079         );
   4080         assert_cancellation_issue(
   4081             |cancellation| cancellation.payload.buyer_pubkey = public_key(SELLER),
   4082             RadrootsOrderIssue::CancellationBuyerMismatch {
   4083                 event_id: event_id(5),
   4084             },
   4085         );
   4086         assert_cancellation_issue(
   4087             |cancellation| cancellation.payload.seller_pubkey = public_key(BUYER),
   4088             RadrootsOrderIssue::CancellationSellerMismatch {
   4089                 event_id: event_id(5),
   4090             },
   4091         );
   4092         assert_cancellation_issue(
   4093             |cancellation| cancellation.payload.listing_addr = draft_listing_addr(),
   4094             RadrootsOrderIssue::CancellationListingAddressInvalid {
   4095                 event_id: event_id(5),
   4096             },
   4097         );
   4098         assert_cancellation_issue(
   4099             |cancellation| cancellation.payload.listing_addr = other_seller_listing_addr(),
   4100             RadrootsOrderIssue::CancellationListingMismatch {
   4101                 event_id: event_id(5),
   4102             },
   4103         );
   4104         assert_cancellation_issue(
   4105             |cancellation| cancellation.root_event_id = event_id(8),
   4106             RadrootsOrderIssue::CancellationRootMismatch {
   4107                 event_id: event_id(5),
   4108             },
   4109         );
   4110         assert_cancellation_issue(
   4111             |cancellation| cancellation.prev_event_id = event_id(5),
   4112             RadrootsOrderIssue::CancellationPreviousMismatch {
   4113                 event_id: event_id(5),
   4114             },
   4115         );
   4116     }
   4117 
   4118     #[test]
   4119     fn reducer_reports_invalid_records_from_all_non_request_families() {
   4120         let mut bad_request = request_record();
   4121         bad_request.payload.order_id = order_id("order-2");
   4122         let invalid_request = reduce_order_events(
   4123             &order_id("order-1"),
   4124             RadrootsOrderReductionInputs {
   4125                 requests: vec![bad_request],
   4126                 decisions: Vec::<RadrootsOrderDecisionRecord>::new(),
   4127                 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(),
   4128                 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(),
   4129                 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(),
   4130             },
   4131         );
   4132         assert_order_issue_kind(
   4133             &invalid_request.issues,
   4134             RadrootsOrderIssue::RequestOrderIdMismatch {
   4135                 event_id: event_id(1),
   4136             },
   4137         );
   4138 
   4139         let mut bad_decision = accepted_decision();
   4140         bad_decision.payload.order_id = order_id("order-2");
   4141         let mut bad_proposal = revision_proposal();
   4142         bad_proposal.payload.order_id = order_id("order-2");
   4143         let mut bad_revision_decision = accepted_revision_decision();
   4144         bad_revision_decision.payload.order_id = order_id("order-2");
   4145         let mut bad_cancellation = cancellation(event_id(1));
   4146         bad_cancellation.payload.order_id = order_id("order-2");
   4147         let invalid_non_requests = reduce_order_events(
   4148             &order_id("order-1"),
   4149             RadrootsOrderReductionInputs {
   4150                 requests: vec![request_record()],
   4151                 decisions: vec![bad_decision],
   4152                 revision_proposals: vec![bad_proposal],
   4153                 revision_decisions: vec![bad_revision_decision],
   4154                 cancellations: vec![bad_cancellation],
   4155             },
   4156         );
   4157 
   4158         assert_order_issue_kind(
   4159             &invalid_non_requests.issues,
   4160             RadrootsOrderIssue::DecisionOrderIdMismatch {
   4161                 event_id: event_id(2),
   4162             },
   4163         );
   4164         assert_order_issue_kind(
   4165             &invalid_non_requests.issues,
   4166             RadrootsOrderIssue::RevisionProposalOrderIdMismatch {
   4167                 event_id: event_id(3),
   4168             },
   4169         );
   4170         assert_order_issue_kind(
   4171             &invalid_non_requests.issues,
   4172             RadrootsOrderIssue::RevisionDecisionOrderIdMismatch {
   4173                 event_id: event_id(4),
   4174             },
   4175         );
   4176         assert_order_issue_kind(
   4177             &invalid_non_requests.issues,
   4178             RadrootsOrderIssue::CancellationOrderIdMismatch {
   4179                 event_id: event_id(5),
   4180             },
   4181         );
   4182     }
   4183 
   4184     #[test]
   4185     fn inventory_accounting_reports_invalid_unknown_overreserved_and_terminal_orders() {
   4186         let mut unknown_bin_request = request_record();
   4187         unknown_bin_request.event_id = event_id(40);
   4188         unknown_bin_request.payload.order_id = order_id("order-4");
   4189         unknown_bin_request.payload.items[0].bin_id = bin_id("bin-missing");
   4190         unknown_bin_request.payload.economics.items[0].bin_id = bin_id("bin-missing");
   4191 
   4192         let mut unknown_bin_decision = accepted_decision();
   4193         unknown_bin_decision.event_id = event_id(41);
   4194         unknown_bin_decision.root_event_id = event_id(40);
   4195         unknown_bin_decision.prev_event_id = event_id(40);
   4196         unknown_bin_decision.payload.order_id = order_id("order-4");
   4197         if let RadrootsOrderDecisionOutcome::Accepted {
   4198             inventory_commitments,
   4199         } = &mut unknown_bin_decision.payload.decision
   4200         {
   4201             inventory_commitments[0].bin_id = bin_id("bin-missing");
   4202         }
   4203 
   4204         let mut declined_request = request_record();
   4205         declined_request.event_id = event_id(20);
   4206         declined_request.payload.order_id = order_id("order-2");
   4207         let mut terminal_decline = declined_decision();
   4208         terminal_decline.event_id = event_id(21);
   4209         terminal_decline.root_event_id = event_id(20);
   4210         terminal_decline.prev_event_id = event_id(20);
   4211         terminal_decline.payload.order_id = order_id("order-2");
   4212 
   4213         let mut cancelled_request = request_record();
   4214         cancelled_request.event_id = event_id(30);
   4215         cancelled_request.payload.order_id = order_id("order-3");
   4216         let mut terminal_cancellation = cancellation(event_id(30));
   4217         terminal_cancellation.event_id = event_id(31);
   4218         terminal_cancellation.root_event_id = event_id(30);
   4219         terminal_cancellation.payload.order_id = order_id("order-3");
   4220 
   4221         let projection = reduce_listing_inventory_accounting(
   4222             &listing_addr(),
   4223             &event_id(9),
   4224             RadrootsListingInventoryAccountingInputs {
   4225                 bins: vec![
   4226                     RadrootsListingInventoryBinAvailability {
   4227                         bin_id: bin_id("bin-1"),
   4228                         available_count: 1,
   4229                     },
   4230                     RadrootsListingInventoryBinAvailability {
   4231                         bin_id: bin_id("bin-overflow"),
   4232                         available_count: u64::MAX,
   4233                     },
   4234                     RadrootsListingInventoryBinAvailability {
   4235                         bin_id: bin_id("bin-overflow"),
   4236                         available_count: 1,
   4237                     },
   4238                 ],
   4239                 requests: vec![
   4240                     request_record(),
   4241                     unknown_bin_request,
   4242                     declined_request,
   4243                     cancelled_request,
   4244                 ],
   4245                 decisions: vec![accepted_decision(), unknown_bin_decision, terminal_decline],
   4246                 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(),
   4247                 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(),
   4248                 cancellations: vec![terminal_cancellation],
   4249             },
   4250         );
   4251 
   4252         assert_eq!(projection.declined_order_ids, vec![order_id("order-2")]);
   4253         assert_eq!(projection.cancelled_order_ids, vec![order_id("order-3")]);
   4254         assert_inventory_issue_kind(
   4255             &projection.issues,
   4256             RadrootsListingInventoryAccountingIssue::ArithmeticOverflow {
   4257                 bin_id: bin_id("bin-overflow"),
   4258                 event_ids: Vec::new(),
   4259             },
   4260         );
   4261         assert_inventory_issue_kind(
   4262             &projection.issues,
   4263             RadrootsListingInventoryAccountingIssue::UnknownInventoryBin {
   4264                 bin_id: bin_id("bin-missing"),
   4265                 event_ids: Vec::new(),
   4266             },
   4267         );
   4268         assert_inventory_issue_kind(
   4269             &projection.issues,
   4270             RadrootsListingInventoryAccountingIssue::OverReserved {
   4271                 bin_id: bin_id("bin-1"),
   4272                 available_count: 0,
   4273                 reserved_count: 0,
   4274                 event_ids: Vec::new(),
   4275             },
   4276         );
   4277 
   4278         let invalid_without_request = reduce_listing_inventory_accounting(
   4279             &listing_addr(),
   4280             &event_id(9),
   4281             RadrootsListingInventoryAccountingInputs {
   4282                 bins: Vec::<RadrootsListingInventoryBinAvailability>::new(),
   4283                 requests: Vec::<RadrootsOrderRequestRecord>::new(),
   4284                 decisions: vec![accepted_decision()],
   4285                 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(),
   4286                 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(),
   4287                 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(),
   4288             },
   4289         );
   4290         assert_eq!(invalid_without_request.invalid_event_ids, vec![event_id(2)]);
   4291         assert_inventory_issue_kind(
   4292             &invalid_without_request.issues,
   4293             RadrootsListingInventoryAccountingIssue::InvalidOrder {
   4294                 order_id: order_id("order-1"),
   4295                 event_ids: Vec::new(),
   4296             },
   4297         );
   4298 
   4299         let mut duplicate_request = request_record();
   4300         duplicate_request.event_id = event_id(11);
   4301         let invalid_duplicate_requests = reduce_listing_inventory_accounting(
   4302             &listing_addr(),
   4303             &event_id(9),
   4304             RadrootsListingInventoryAccountingInputs {
   4305                 bins: Vec::<RadrootsListingInventoryBinAvailability>::new(),
   4306                 requests: vec![request_record(), duplicate_request],
   4307                 decisions: Vec::<RadrootsOrderDecisionRecord>::new(),
   4308                 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(),
   4309                 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(),
   4310                 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(),
   4311             },
   4312         );
   4313         assert_eq!(
   4314             invalid_duplicate_requests.invalid_event_ids,
   4315             vec![event_id(1), event_id(11)]
   4316         );
   4317         assert_inventory_issue_kind(
   4318             &invalid_duplicate_requests.issues,
   4319             RadrootsListingInventoryAccountingIssue::InvalidOrder {
   4320                 order_id: order_id("order-1"),
   4321                 event_ids: Vec::new(),
   4322             },
   4323         );
   4324 
   4325         let invalid_revision_streams = reduce_listing_inventory_accounting(
   4326             &listing_addr(),
   4327             &event_id(9),
   4328             RadrootsListingInventoryAccountingInputs {
   4329                 bins: Vec::<RadrootsListingInventoryBinAvailability>::new(),
   4330                 requests: Vec::<RadrootsOrderRequestRecord>::new(),
   4331                 decisions: vec![accepted_decision()],
   4332                 revision_proposals: vec![revision_proposal()],
   4333                 revision_decisions: vec![accepted_revision_decision()],
   4334                 cancellations: vec![cancellation(event_id(3))],
   4335             },
   4336         );
   4337         assert_eq!(
   4338             invalid_revision_streams.invalid_event_ids,
   4339             vec![event_id(2), event_id(3), event_id(4), event_id(5)]
   4340         );
   4341         assert_inventory_issue_kind(
   4342             &invalid_revision_streams.issues,
   4343             RadrootsListingInventoryAccountingIssue::InvalidOrder {
   4344                 order_id: order_id("order-1"),
   4345                 event_ids: Vec::new(),
   4346             },
   4347         );
   4348     }
   4349 
   4350     #[test]
   4351     fn reducer_projects_requested_order() {
   4352         let projection = reduce(Vec::new(), Vec::new(), Vec::new(), Vec::new());
   4353 
   4354         assert_eq!(projection.issues, Vec::new());
   4355         assert_eq!(projection.status, RadrootsOrderStatus::Requested);
   4356         assert_eq!(projection.request_event_id, Some(event_id(1)));
   4357         assert!(!projection.lifecycle_terminal);
   4358         assert!(projection.agreement_event_id.is_none());
   4359     }
   4360 
   4361     #[test]
   4362     fn reducer_projects_accepted_order_agreement() {
   4363         let projection = reduce(
   4364             vec![accepted_decision()],
   4365             Vec::new(),
   4366             Vec::new(),
   4367             Vec::new(),
   4368         );
   4369 
   4370         assert_eq!(projection.status, RadrootsOrderStatus::Accepted);
   4371         assert_eq!(projection.decision_event_id, Some(event_id(2)));
   4372         assert_eq!(projection.agreement_event_id, Some(event_id(2)));
   4373         assert!(projection.lifecycle_terminal);
   4374     }
   4375 
   4376     #[test]
   4377     fn reducer_projects_declined_order() {
   4378         let projection = reduce(
   4379             vec![declined_decision()],
   4380             Vec::new(),
   4381             Vec::new(),
   4382             Vec::new(),
   4383         );
   4384 
   4385         assert_eq!(projection.status, RadrootsOrderStatus::Declined);
   4386         assert_eq!(projection.decision_event_id, Some(event_id(2)));
   4387         assert!(projection.lifecycle_terminal);
   4388     }
   4389 
   4390     #[test]
   4391     fn reducer_projects_revision_acceptance_as_agreement() {
   4392         let projection = reduce(
   4393             Vec::new(),
   4394             vec![revision_proposal()],
   4395             vec![accepted_revision_decision()],
   4396             Vec::new(),
   4397         );
   4398 
   4399         assert_eq!(projection.status, RadrootsOrderStatus::Accepted);
   4400         assert_eq!(projection.agreement_event_id, Some(event_id(4)));
   4401         assert_eq!(
   4402             projection.economics.expect("economics").items[0].bin_count,
   4403             1
   4404         );
   4405         assert!(projection.lifecycle_terminal);
   4406     }
   4407 
   4408     #[test]
   4409     fn reducer_allows_pre_agreement_cancellation() {
   4410         let projection = reduce(
   4411             Vec::new(),
   4412             Vec::new(),
   4413             Vec::new(),
   4414             vec![cancellation(event_id(1))],
   4415         );
   4416 
   4417         assert_eq!(projection.status, RadrootsOrderStatus::Cancelled);
   4418         assert_eq!(projection.cancellation_event_id, Some(event_id(5)));
   4419         assert!(projection.lifecycle_terminal);
   4420     }
   4421 
   4422     #[test]
   4423     fn reducer_rejects_cancellation_after_agreement() {
   4424         let projection = reduce(
   4425             vec![accepted_decision()],
   4426             Vec::new(),
   4427             Vec::new(),
   4428             vec![cancellation(event_id(2))],
   4429         );
   4430 
   4431         assert_eq!(projection.status, RadrootsOrderStatus::Invalid);
   4432         assert!(projection.lifecycle_terminal);
   4433     }
   4434 
   4435     #[test]
   4436     fn reducer_groups_event_records() {
   4437         let projection = reduce_order_event_records(
   4438             &order_id("order-1"),
   4439             vec![
   4440                 RadrootsOrderEventRecord::Request(request_record()),
   4441                 RadrootsOrderEventRecord::Decision(accepted_decision()),
   4442             ],
   4443         );
   4444 
   4445         assert_eq!(projection.status, RadrootsOrderStatus::Accepted);
   4446         assert_eq!(projection.agreement_event_id, Some(event_id(2)));
   4447     }
   4448 
   4449     #[test]
   4450     fn inventory_accounting_reserves_only_accepted_agreements() {
   4451         let requested_projection = reduce_listing_inventory_accounting(
   4452             &listing_addr(),
   4453             &event_id(8),
   4454             RadrootsListingInventoryAccountingInputs {
   4455                 bins: vec![RadrootsListingInventoryBinAvailability {
   4456                     bin_id: bin_id("bin-1"),
   4457                     available_count: 3,
   4458                 }],
   4459                 requests: vec![request_record()],
   4460                 decisions: Vec::<RadrootsOrderDecisionRecord>::new(),
   4461                 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(),
   4462                 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(),
   4463                 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(),
   4464             },
   4465         );
   4466 
   4467         assert_eq!(requested_projection.bins[0].accepted_reserved_count, 0);
   4468         assert_eq!(requested_projection.bins[0].remaining_count, 3);
   4469 
   4470         let projection = reduce_listing_inventory_accounting(
   4471             &listing_addr(),
   4472             &event_id(9),
   4473             RadrootsListingInventoryAccountingInputs {
   4474                 bins: vec![RadrootsListingInventoryBinAvailability {
   4475                     bin_id: bin_id("bin-1"),
   4476                     available_count: 3,
   4477                 }],
   4478                 requests: vec![request_record()],
   4479                 decisions: vec![accepted_decision()],
   4480                 revision_proposals: Vec::<RadrootsOrderRevisionProposalRecord>::new(),
   4481                 revision_decisions: Vec::<RadrootsOrderRevisionDecisionRecord>::new(),
   4482                 cancellations: Vec::<RadrootsOrderCancellationRecord>::new(),
   4483             },
   4484         );
   4485 
   4486         assert_eq!(projection.bins[0].accepted_reserved_count, 2);
   4487         assert_eq!(projection.bins[0].remaining_count, 1);
   4488         assert_eq!(
   4489             projection.bins[0].accepted_orders[0].agreement_event_id,
   4490             event_id(2)
   4491         );
   4492     }
   4493 }