Skip to main content

cow_composable/
multiplexer.rs

1//! [`Multiplexer`] — manages a set of conditional orders under a single Merkle root.
2
3use std::fmt;
4
5use alloy_primitives::{B256, keccak256};
6use serde::{Deserialize, Serialize};
7
8use cow_errors::CowError;
9
10use super::{
11    order_id,
12    types::{ConditionalOrderParams, ProofLocation},
13};
14
15/// Merkle inclusion proof for a single conditional order.
16#[derive(Debug, Clone)]
17pub struct OrderProof {
18    /// Unique identifier of the order.
19    pub order_id: B256,
20    /// Sibling hashes from leaf to root (`OpenZeppelin` `MerkleTree` format).
21    pub proof: Vec<B256>,
22    /// The params needed to reconstruct the leaf.
23    pub params: ConditionalOrderParams,
24}
25
26impl OrderProof {
27    /// Construct an [`OrderProof`] from its constituent fields.
28    ///
29    /// # Arguments
30    ///
31    /// * `order_id` — unique identifier (`keccak256` of ABI-encoded params) for the order.
32    /// * `proof` — sibling hashes from leaf to root in `OpenZeppelin` `MerkleTree` format.
33    /// * `params` — the [`ConditionalOrderParams`] that define the order.
34    ///
35    /// # Returns
36    ///
37    /// A new [`OrderProof`] bundling the id, proof, and params together.
38    #[must_use]
39    pub const fn new(order_id: B256, proof: Vec<B256>, params: ConditionalOrderParams) -> Self {
40        Self { order_id, proof, params }
41    }
42
43    /// Returns the number of Merkle proof siblings.
44    ///
45    /// # Returns
46    ///
47    /// The length of the `proof` vector, i.e. the number of sibling hashes
48    /// needed to verify membership against the Merkle root.
49    #[must_use]
50    pub const fn proof_len(&self) -> usize {
51        self.proof.len()
52    }
53}
54
55/// Proof and params bundled for watchtower export.
56#[derive(Debug, Clone)]
57pub struct ProofWithParams {
58    /// The order's inclusion proof.
59    pub proof: Vec<B256>,
60    /// The conditional order params.
61    pub params: ConditionalOrderParams,
62}
63
64impl ProofWithParams {
65    /// Construct a [`ProofWithParams`] bundle.
66    ///
67    /// # Arguments
68    ///
69    /// * `proof` — Merkle inclusion proof (sibling hashes from leaf to root).
70    /// * `params` — the [`ConditionalOrderParams`] for the order.
71    ///
72    /// # Returns
73    ///
74    /// A new [`ProofWithParams`] ready for watchtower export or on-chain verification.
75    #[must_use]
76    pub const fn new(proof: Vec<B256>, params: ConditionalOrderParams) -> Self {
77        Self { proof, params }
78    }
79
80    /// Returns the number of Merkle proof siblings.
81    ///
82    /// # Returns
83    ///
84    /// The length of the `proof` vector, i.e. the number of sibling hashes
85    /// needed to verify membership against the Merkle root.
86    #[must_use]
87    pub const fn proof_len(&self) -> usize {
88        self.proof.len()
89    }
90}
91
92impl fmt::Display for OrderProof {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        write!(f, "order-proof({:#x}, {} siblings)", self.order_id, self.proof.len())
95    }
96}
97
98impl fmt::Display for ProofWithParams {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        write!(
101            f,
102            "proof-with-params({} siblings, handler={:#x})",
103            self.proof.len(),
104            self.params.handler
105        )
106    }
107}
108
109// ── JSON serialisation helpers ────────────────────────────────────────────────
110
111#[derive(Serialize, Deserialize)]
112struct MultiplexerJson {
113    proof_location: u8,
114    orders: Vec<ParamsJson>,
115}
116
117#[derive(Serialize, Deserialize)]
118struct ParamsJson {
119    handler: String,
120    salt: String,
121    static_input: String,
122}
123
124/// Watchtower export format: array of `{ proof, params }` objects.
125#[derive(Deserialize)]
126struct WatchtowerEntry {
127    proof: Vec<String>,
128    params: WatchtowerParams,
129}
130
131/// Params in watchtower camelCase format.
132#[derive(Deserialize)]
133#[serde(rename_all = "camelCase")]
134struct WatchtowerParams {
135    handler: String,
136    salt: String,
137    static_input: String,
138}
139
140impl From<&ConditionalOrderParams> for ParamsJson {
141    fn from(p: &ConditionalOrderParams) -> Self {
142        Self {
143            handler: format!("{:?}", p.handler),
144            salt: format!("0x{}", alloy_primitives::hex::encode(p.salt.as_slice())),
145            static_input: format!("0x{}", alloy_primitives::hex::encode(&p.static_input)),
146        }
147    }
148}
149
150impl TryFrom<ParamsJson> for ConditionalOrderParams {
151    type Error = CowError;
152    fn try_from(j: ParamsJson) -> Result<Self, CowError> {
153        let handler = j
154            .handler
155            .parse()
156            .map_err(|e: alloy_primitives::hex::FromHexError| CowError::AppData(e.to_string()))?;
157        let salt_hex = j.salt.strip_prefix("0x").map_or(j.salt.as_str(), |s| s);
158        let salt_bytes = alloy_primitives::hex::decode(salt_hex)
159            .map_err(|e| CowError::AppData(format!("salt: {e}")))?;
160        let mut salt = [0u8; 32];
161        salt.copy_from_slice(&salt_bytes);
162        let input_hex = j.static_input.strip_prefix("0x").map_or(j.static_input.as_str(), |s| s);
163        let static_input = alloy_primitives::hex::decode(input_hex)
164            .map_err(|e| CowError::AppData(format!("static_input: {e}")))?;
165        Ok(Self { handler, salt: B256::new(salt), static_input })
166    }
167}
168
169// ── Multiplexer ───────────────────────────────────────────────────────────────
170
171/// Manages a set of conditional orders and computes their Merkle root.
172///
173/// The Merkle tree follows the `OpenZeppelin` `MerkleTree` standard used by
174/// the `ComposableCow` contract:
175///
176/// - Leaf   = `keccak256(keccak256(abi.encode(params)))`
177/// - Node   = `keccak256(min(left, right) ++ max(left, right))`
178/// - Root is verified on-chain by `ComposableCow::setRoot`.
179#[derive(Debug, Clone, Default)]
180pub struct Multiplexer {
181    orders: Vec<ConditionalOrderParams>,
182    proof_location: ProofLocation,
183}
184
185impl Multiplexer {
186    /// Create an empty multiplexer with the given proof location.
187    ///
188    /// # Arguments
189    ///
190    /// * `proof_location` — where the Merkle proofs will be stored or published (e.g.
191    ///   [`ProofLocation::Emitted`], [`ProofLocation::Ipfs`]).
192    ///
193    /// # Returns
194    ///
195    /// A new, empty [`Multiplexer`] configured with the specified proof location.
196    #[must_use]
197    pub const fn new(proof_location: ProofLocation) -> Self {
198        Self { orders: Vec::new(), proof_location }
199    }
200
201    /// Add a conditional order to the managed set.
202    ///
203    /// The order is appended to the end; its position index can be used with
204    /// [`proof`](Self::proof) or [`get_by_index`](Self::get_by_index).
205    ///
206    /// # Arguments
207    ///
208    /// * `params` — the [`ConditionalOrderParams`] describing the order to add.
209    pub fn add(&mut self, params: ConditionalOrderParams) {
210        self.orders.push(params);
211    }
212
213    /// Remove the first conditional order whose [`order_id`] matches `id`.
214    ///
215    /// If no order matches, this is a no-op.
216    ///
217    /// # Arguments
218    ///
219    /// * `id` — the `keccak256`-based order identifier to match against.
220    pub fn remove(&mut self, id: B256) {
221        self.orders.retain(|p| order_id(p) != id);
222    }
223
224    /// Update the order at `index` with new params.
225    ///
226    /// # Errors
227    ///
228    /// Returns [`CowError::AppData`] if `index` is out of range.
229    pub fn update(&mut self, index: usize, params: ConditionalOrderParams) -> Result<(), CowError> {
230        if index >= self.orders.len() {
231            return Err(CowError::AppData(format!(
232                "index {index} out of range (len {})",
233                self.orders.len()
234            )));
235        }
236        self.orders[index] = params;
237        Ok(())
238    }
239
240    /// Retrieve the order at `index`.
241    ///
242    /// # Arguments
243    ///
244    /// * `index` — zero-based position of the order in the managed set.
245    ///
246    /// # Returns
247    ///
248    /// `Some(&ConditionalOrderParams)` if the index is valid, or `None` if out of range.
249    #[must_use]
250    pub fn get_by_index(&self, index: usize) -> Option<&ConditionalOrderParams> {
251        self.orders.get(index)
252    }
253
254    /// Retrieve the first order matching `id`.
255    ///
256    /// # Arguments
257    ///
258    /// * `id` — the `keccak256`-based order identifier to search for.
259    ///
260    /// # Returns
261    ///
262    /// `Some(&ConditionalOrderParams)` for the first order whose computed
263    /// [`order_id`] equals `id`, or `None` if no order matches.
264    #[must_use]
265    pub fn get_by_id(&self, id: B256) -> Option<&ConditionalOrderParams> {
266        self.orders.iter().find(|p| order_id(p) == id)
267    }
268
269    /// Number of orders currently managed.
270    ///
271    /// # Returns
272    ///
273    /// The count of conditional orders in the managed set.
274    #[must_use]
275    pub const fn len(&self) -> usize {
276        self.orders.len()
277    }
278
279    /// True if no orders are managed.
280    ///
281    /// # Returns
282    ///
283    /// `true` when the multiplexer contains zero orders, `false` otherwise.
284    #[must_use]
285    pub const fn is_empty(&self) -> bool {
286        self.orders.is_empty()
287    }
288
289    /// Compute the Merkle root of all managed orders.
290    ///
291    /// The root is computed using the `OpenZeppelin` `MerkleTree` algorithm:
292    /// each leaf is `keccak256(keccak256(abi.encode(params)))` and internal
293    /// nodes are `keccak256(min(left, right) ++ max(left, right))`.
294    ///
295    /// Returns `None` if there are no orders.
296    ///
297    /// # Errors
298    ///
299    /// Returns [`CowError::AppData`] if ABI encoding of any order fails.
300    pub fn root(&self) -> Result<Option<B256>, CowError> {
301        if self.orders.is_empty() {
302            return Ok(None);
303        }
304        let leaves: Vec<B256> = self.orders.iter().map(leaf_hash).collect();
305        Ok(Some(merkle_root(&leaves)))
306    }
307
308    /// Generate a Merkle inclusion proof for the order at position `index`.
309    ///
310    /// Returns the sibling hashes needed to verify membership against the root
311    /// computed by [`root`](Self::root).
312    ///
313    /// # Errors
314    ///
315    /// Returns [`CowError::AppData`] if `index` is out of range.
316    pub fn proof(&self, index: usize) -> Result<OrderProof, CowError> {
317        if index >= self.orders.len() {
318            return Err(CowError::AppData(format!(
319                "index {index} out of range (len {})",
320                self.orders.len()
321            )));
322        }
323        let leaves: Vec<B256> = self.orders.iter().map(leaf_hash).collect();
324        Ok(OrderProof {
325            order_id: order_id(&self.orders[index]),
326            proof: generate_proof(&leaves, index),
327            params: self.orders[index].clone(),
328        })
329    }
330
331    /// Export all orders with their Merkle proofs — useful for watchtower services.
332    ///
333    /// # Errors
334    ///
335    /// Returns [`CowError::AppData`] if proof generation fails.
336    pub fn dump_proofs_and_params(&self) -> Result<Vec<ProofWithParams>, CowError> {
337        (0..self.orders.len())
338            .map(|i| {
339                let op = self.proof(i)?;
340                Ok(ProofWithParams { proof: op.proof, params: op.params })
341            })
342            .collect()
343    }
344
345    /// Iterate over the order IDs of all managed conditional orders.
346    ///
347    /// Each ID is the `keccak256` hash of the ABI-encoded [`ConditionalOrderParams`]
348    /// as computed by [`order_id`].
349    ///
350    /// # Returns
351    ///
352    /// An iterator yielding the [`B256`] identifier for each managed order.
353    pub fn order_ids(&self) -> impl Iterator<Item = alloy_primitives::B256> + '_ {
354        self.orders.iter().map(order_id)
355    }
356
357    /// Iterate over all managed conditional orders.
358    ///
359    /// # Returns
360    ///
361    /// An iterator yielding shared references to each [`ConditionalOrderParams`]
362    /// in insertion order.
363    pub fn iter(&self) -> impl Iterator<Item = &ConditionalOrderParams> {
364        self.orders.iter()
365    }
366
367    /// View all managed orders as a slice.
368    ///
369    /// # Returns
370    ///
371    /// A borrowed slice of all [`ConditionalOrderParams`] in insertion order.
372    #[must_use]
373    pub fn as_slice(&self) -> &[ConditionalOrderParams] {
374        &self.orders
375    }
376
377    /// Remove all managed orders.
378    pub fn clear(&mut self) {
379        self.orders.clear();
380    }
381
382    /// The configured proof location.
383    ///
384    /// # Returns
385    ///
386    /// The [`ProofLocation`] variant that was set at construction or via
387    /// [`with_proof_location`](Self::with_proof_location).
388    #[must_use]
389    pub const fn proof_location(&self) -> ProofLocation {
390        self.proof_location
391    }
392
393    /// Override the proof location and return `self` (builder style).
394    ///
395    /// # Arguments
396    ///
397    /// * `location` — the new [`ProofLocation`] to use.
398    ///
399    /// # Returns
400    ///
401    /// The same [`Multiplexer`] with its proof location updated, enabling
402    /// builder-style chaining.
403    #[must_use]
404    pub const fn with_proof_location(mut self, location: ProofLocation) -> Self {
405        self.proof_location = location;
406        self
407    }
408
409    /// Consume the multiplexer and return the managed orders as a `Vec`.
410    ///
411    /// # Returns
412    ///
413    /// A `Vec<ConditionalOrderParams>` containing all orders that were managed
414    /// by this multiplexer, in insertion order.
415    #[must_use]
416    pub fn into_vec(self) -> Vec<ConditionalOrderParams> {
417        self.orders
418    }
419
420    /// Serialise the multiplexer to a JSON string.
421    ///
422    /// The output is a JSON object with `proof_location` (integer) and `orders`
423    /// (array of `{ handler, salt, static_input }` hex strings).  Deserialise
424    /// with [`Multiplexer::from_json`].
425    ///
426    /// # Errors
427    ///
428    /// Returns [`CowError::AppData`] if serialisation fails.
429    pub fn to_json(&self) -> Result<String, CowError> {
430        let j = MultiplexerJson {
431            proof_location: self.proof_location as u8,
432            orders: self.orders.iter().map(ParamsJson::from).collect(),
433        };
434        serde_json::to_string(&j).map_err(|e| CowError::AppData(e.to_string()))
435    }
436
437    /// Decode a watchtower proof array from the JSON format used by the `CoW` Protocol
438    /// watchtower service.
439    ///
440    /// The input must be a JSON array of `{ "proof": ["0x...", ...], "params": { "handler":
441    /// "0x...", "salt": "0x...", "staticInput": "0x..." } }` objects.
442    ///
443    /// # Errors
444    ///
445    /// Returns [`CowError::AppData`] on parse or hex-decode failure.
446    pub fn decode_proofs_from_json(json: &str) -> Result<Vec<ProofWithParams>, CowError> {
447        let entries: Vec<WatchtowerEntry> =
448            serde_json::from_str(json).map_err(|e| CowError::AppData(e.to_string()))?;
449        entries
450            .into_iter()
451            .map(|entry| {
452                let proof = entry
453                    .proof
454                    .iter()
455                    .map(|s| {
456                        let hex = s.strip_prefix("0x").map_or(s.as_str(), |h| h);
457                        let bytes = alloy_primitives::hex::decode(hex)
458                            .map_err(|e| CowError::AppData(format!("proof hash: {e}")))?;
459                        let mut arr = [0u8; 32];
460                        arr.copy_from_slice(&bytes);
461                        Ok(B256::new(arr))
462                    })
463                    .collect::<Result<Vec<_>, CowError>>()?;
464                let p = entry.params;
465                let handler =
466                    p.handler.parse().map_err(|e: alloy_primitives::hex::FromHexError| {
467                        CowError::AppData(e.to_string())
468                    })?;
469                let salt_hex = p.salt.strip_prefix("0x").map_or(p.salt.as_str(), |s| s);
470                let salt_bytes = alloy_primitives::hex::decode(salt_hex)
471                    .map_err(|e| CowError::AppData(format!("salt: {e}")))?;
472                let mut salt = [0u8; 32];
473                salt.copy_from_slice(&salt_bytes);
474                let input_hex =
475                    p.static_input.strip_prefix("0x").map_or(p.static_input.as_str(), |s| s);
476                let static_input = alloy_primitives::hex::decode(input_hex)
477                    .map_err(|e| CowError::AppData(format!("staticInput: {e}")))?;
478                let params =
479                    ConditionalOrderParams { handler, salt: B256::new(salt), static_input };
480                Ok(ProofWithParams { proof, params })
481            })
482            .collect()
483    }
484
485    /// Deserialise a [`Multiplexer`] from a JSON string produced by [`Multiplexer::to_json`].
486    ///
487    /// # Errors
488    ///
489    /// Returns [`CowError::AppData`] on parse or decode failure.
490    pub fn from_json(json: &str) -> Result<Self, CowError> {
491        let j: MultiplexerJson =
492            serde_json::from_str(json).map_err(|e| CowError::AppData(e.to_string()))?;
493        let proof_location = match j.proof_location {
494            0 => ProofLocation::Private,
495            1 => ProofLocation::Emitted,
496            2 => ProofLocation::Swarm,
497            3 => ProofLocation::Waku,
498            4 => ProofLocation::Reserved,
499            5 => ProofLocation::Ipfs,
500            n => {
501                return Err(CowError::AppData(format!("unknown ProofLocation: {n}")));
502            }
503        };
504        let orders = j
505            .orders
506            .into_iter()
507            .map(ConditionalOrderParams::try_from)
508            .collect::<Result<Vec<_>, _>>()?;
509        Ok(Self { orders, proof_location })
510    }
511}
512impl fmt::Display for Multiplexer {
513    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
514        write!(f, "multiplexer({} orders, {})", self.orders.len(), self.proof_location)
515    }
516}
517
518// ── Merkle tree (OpenZeppelin standard) ───────────────────────────────────────
519
520/// Double-hash a leaf per the `OpenZeppelin` `MerkleTree` convention.
521///
522/// # Arguments
523///
524/// * `params` — the conditional order parameters whose ABI encoding is hashed.
525///
526/// # Returns
527///
528/// `keccak256(order_id(params))`, where `order_id` is itself
529/// `keccak256(abi.encode(params))`, producing the double-hash leaf expected
530/// by the `OpenZeppelin` Merkle tree.
531fn leaf_hash(params: &ConditionalOrderParams) -> B256 {
532    keccak256(order_id(params))
533}
534
535/// Compute the Merkle root from pre-hashed leaves using sorted pair hashing.
536///
537/// # Arguments
538///
539/// * `leaves` — non-empty slice of double-hashed leaf values.
540///
541/// # Returns
542///
543/// The single `B256` root hash produced by iteratively combining pairs of
544/// nodes with [`hash_pair`] until one node remains. An odd trailing node is
545/// promoted unchanged.
546fn merkle_root(leaves: &[B256]) -> B256 {
547    if leaves.len() == 1 {
548        return leaves[0];
549    }
550    let mut layer = leaves.to_vec();
551    while layer.len() > 1 {
552        let mut next = Vec::with_capacity(layer.len().div_ceil(2));
553        let mut i = 0;
554        while i < layer.len() {
555            if i + 1 < layer.len() {
556                next.push(hash_pair(layer[i], layer[i + 1]));
557            } else {
558                next.push(layer[i]);
559            }
560            i += 2;
561        }
562        layer = next;
563    }
564    layer[0]
565}
566
567/// Hash a sorted pair of nodes for the Merkle tree.
568///
569/// # Arguments
570///
571/// * `a` — first node hash.
572/// * `b` — second node hash.
573///
574/// # Returns
575///
576/// `keccak256(min(a, b) ++ max(a, b))` — the canonical sorted-pair hash
577/// used by the `OpenZeppelin` `MerkleTree` implementation.
578fn hash_pair(a: B256, b: B256) -> B256 {
579    let (lo, hi) = if a <= b { (a, b) } else { (b, a) };
580    let mut buf = [0u8; 64];
581    buf[..32].copy_from_slice(lo.as_slice());
582    buf[32..].copy_from_slice(hi.as_slice());
583    keccak256(buf)
584}
585
586/// Generate a Merkle inclusion proof for the leaf at `index`.
587///
588/// # Arguments
589///
590/// * `leaves` — the full set of double-hashed leaf values.
591/// * `index` — zero-based position of the target leaf within `leaves`.
592///
593/// # Returns
594///
595/// A `Vec<B256>` of sibling hashes ordered from leaf level to root level.
596/// Together with the leaf itself, these siblings are sufficient to
597/// reconstruct and verify the Merkle root.
598fn generate_proof(leaves: &[B256], mut index: usize) -> Vec<B256> {
599    let mut proof = Vec::new();
600    let mut layer = leaves.to_vec();
601    while layer.len() > 1 {
602        let sibling = if index.is_multiple_of(2) {
603            (index + 1 < layer.len()).then(|| layer[index + 1])
604        } else {
605            Some(layer[index - 1])
606        };
607        if let Some(s) = sibling {
608            proof.push(s);
609        }
610        let mut next = Vec::with_capacity(layer.len().div_ceil(2));
611        let mut i = 0;
612        while i < layer.len() {
613            if i + 1 < layer.len() {
614                next.push(hash_pair(layer[i], layer[i + 1]));
615            } else {
616                next.push(layer[i]);
617            }
618            i += 2;
619        }
620        layer = next;
621        index /= 2;
622    }
623    proof
624}
625
626// ── Tests ─────────────────────────────────────────────────────────────────────
627
628#[cfg(test)]
629mod tests {
630    use alloy_primitives::Address;
631
632    use super::*;
633
634    fn make_params(salt_byte: u8) -> ConditionalOrderParams {
635        ConditionalOrderParams {
636            handler: Address::ZERO,
637            salt: B256::new([salt_byte; 32]),
638            static_input: vec![salt_byte; 4],
639        }
640    }
641
642    #[test]
643    fn decode_proofs_from_json_roundtrip() {
644        // Build a multiplexer, export proofs, serialise to watchtower JSON, decode back.
645        let mut mux = Multiplexer::new(ProofLocation::Private);
646        mux.add(make_params(0xaa));
647        mux.add(make_params(0xbb));
648
649        let proofs = mux.dump_proofs_and_params().unwrap();
650
651        // Serialise to watchtower JSON format manually.
652        let json_entries: Vec<serde_json::Value> = proofs
653            .iter()
654            .map(|p| {
655                let proof_arr: Vec<String> = p
656                    .proof
657                    .iter()
658                    .map(|h| format!("0x{}", alloy_primitives::hex::encode(h.as_slice())))
659                    .collect();
660                serde_json::json!({
661                    "proof": proof_arr,
662                    "params": {
663                        "handler": format!("{:#x}", p.params.handler),
664                        "salt": format!("0x{}", alloy_primitives::hex::encode(p.params.salt.as_slice())),
665                        "staticInput": format!("0x{}", alloy_primitives::hex::encode(&p.params.static_input)),
666                    }
667                })
668            })
669            .collect();
670        let json = serde_json::to_string(&json_entries).unwrap();
671
672        let decoded = Multiplexer::decode_proofs_from_json(&json).unwrap();
673        assert_eq!(decoded.len(), 2);
674        assert_eq!(decoded[0].params.salt, proofs[0].params.salt);
675        assert_eq!(decoded[1].params.static_input, proofs[1].params.static_input);
676    }
677
678    #[test]
679    fn decode_proofs_from_json_invalid_returns_error() {
680        let result = Multiplexer::decode_proofs_from_json("not json");
681        assert!(result.is_err());
682    }
683
684    #[test]
685    fn decode_proofs_from_json_rejects_non_hex_handler() {
686        // A structurally valid watchtower entry whose `handler` is not a
687        // valid hex address — hits the handler parse-error branch.
688        let json = serde_json::json!([{
689            "proof": [],
690            "params": {
691                "handler": "not-a-hex-address",
692                "salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
693                "staticInput": "0x"
694            }
695        }])
696        .to_string();
697        let err = Multiplexer::decode_proofs_from_json(&json).unwrap_err();
698        assert!(matches!(err, CowError::AppData(_)));
699    }
700
701    #[test]
702    fn multiplexer_root_single_order() {
703        let mut mux = Multiplexer::new(ProofLocation::Private);
704        mux.add(make_params(1));
705        let root = mux.root().unwrap();
706        assert!(root.is_some());
707    }
708
709    #[test]
710    fn multiplexer_root_empty() {
711        let mux = Multiplexer::new(ProofLocation::Private);
712        assert!(mux.root().unwrap().is_none());
713    }
714
715    // ── add / remove / update ────────────────────────────────────────────
716
717    #[test]
718    fn add_increases_len() {
719        let mut mux = Multiplexer::new(ProofLocation::Private);
720        assert!(mux.is_empty());
721        mux.add(make_params(1));
722        assert_eq!(mux.len(), 1);
723        mux.add(make_params(2));
724        assert_eq!(mux.len(), 2);
725    }
726
727    #[test]
728    fn remove_by_id() {
729        let mut mux = Multiplexer::new(ProofLocation::Private);
730        let p = make_params(0xaa);
731        let id = order_id(&p);
732        mux.add(p);
733        mux.add(make_params(0xbb));
734        assert_eq!(mux.len(), 2);
735        mux.remove(id);
736        assert_eq!(mux.len(), 1);
737    }
738
739    #[test]
740    fn remove_nonexistent_is_noop() {
741        let mut mux = Multiplexer::new(ProofLocation::Private);
742        mux.add(make_params(1));
743        mux.remove(B256::ZERO);
744        assert_eq!(mux.len(), 1);
745    }
746
747    #[test]
748    fn update_in_range() {
749        let mut mux = Multiplexer::new(ProofLocation::Private);
750        mux.add(make_params(1));
751        mux.add(make_params(2));
752        let new_params = make_params(99);
753        mux.update(1, new_params.clone()).unwrap();
754        assert_eq!(mux.get_by_index(1).unwrap().salt, new_params.salt);
755    }
756
757    #[test]
758    fn update_out_of_range() {
759        let mut mux = Multiplexer::new(ProofLocation::Private);
760        mux.add(make_params(1));
761        assert!(mux.update(5, make_params(2)).is_err());
762    }
763
764    // ── get_by_index / get_by_id ─────────────────────────────────────────
765
766    #[test]
767    fn get_by_index_valid() {
768        let mut mux = Multiplexer::new(ProofLocation::Private);
769        let p = make_params(0xcc);
770        mux.add(p.clone());
771        let got = mux.get_by_index(0).unwrap();
772        assert_eq!(got.salt, p.salt);
773    }
774
775    #[test]
776    fn get_by_index_out_of_range() {
777        let mux = Multiplexer::new(ProofLocation::Private);
778        assert!(mux.get_by_index(0).is_none());
779    }
780
781    #[test]
782    fn get_by_id_found() {
783        let mut mux = Multiplexer::new(ProofLocation::Private);
784        let p = make_params(0xdd);
785        let id = order_id(&p);
786        mux.add(p.clone());
787        let got = mux.get_by_id(id).unwrap();
788        assert_eq!(got.salt, p.salt);
789    }
790
791    #[test]
792    fn get_by_id_not_found() {
793        let mut mux = Multiplexer::new(ProofLocation::Private);
794        mux.add(make_params(1));
795        assert!(mux.get_by_id(B256::ZERO).is_none());
796    }
797
798    // ── root / proof ─────────────────────────────────────────────────────
799
800    #[test]
801    fn root_changes_when_order_added() {
802        let mut mux = Multiplexer::new(ProofLocation::Private);
803        mux.add(make_params(1));
804        let root1 = mux.root().unwrap().unwrap();
805        mux.add(make_params(2));
806        let root2 = mux.root().unwrap().unwrap();
807        assert_ne!(root1, root2);
808    }
809
810    #[test]
811    fn root_two_orders() {
812        let mut mux = Multiplexer::new(ProofLocation::Private);
813        mux.add(make_params(0xaa));
814        mux.add(make_params(0xbb));
815        let root = mux.root().unwrap();
816        assert!(root.is_some());
817    }
818
819    #[test]
820    fn proof_valid_index() {
821        let mut mux = Multiplexer::new(ProofLocation::Private);
822        mux.add(make_params(0xaa));
823        mux.add(make_params(0xbb));
824        let proof = mux.proof(0).unwrap();
825        assert!(!proof.proof.is_empty());
826        assert_eq!(proof.params.salt, make_params(0xaa).salt);
827    }
828
829    #[test]
830    fn proof_out_of_range() {
831        let mut mux = Multiplexer::new(ProofLocation::Private);
832        mux.add(make_params(1));
833        assert!(mux.proof(5).is_err());
834    }
835
836    // ── dump_proofs_and_params ───────────────────────────────────────────
837
838    #[test]
839    fn dump_proofs_and_params_returns_all() {
840        let mut mux = Multiplexer::new(ProofLocation::Private);
841        mux.add(make_params(0xaa));
842        mux.add(make_params(0xbb));
843        mux.add(make_params(0xcc));
844        let proofs = mux.dump_proofs_and_params().unwrap();
845        assert_eq!(proofs.len(), 3);
846    }
847
848    // ── to_json / from_json roundtrip ────────────────────────────────────
849
850    #[test]
851    fn to_json_from_json_roundtrip() {
852        let mut mux = Multiplexer::new(ProofLocation::Ipfs);
853        mux.add(make_params(0x11));
854        mux.add(make_params(0x22));
855
856        let json = mux.to_json().unwrap();
857        let restored = Multiplexer::from_json(&json).unwrap();
858
859        assert_eq!(restored.len(), 2);
860        assert_eq!(restored.proof_location(), ProofLocation::Ipfs);
861        assert_eq!(restored.get_by_index(0).unwrap().salt, make_params(0x11).salt);
862        assert_eq!(restored.get_by_index(1).unwrap().salt, make_params(0x22).salt);
863    }
864
865    #[test]
866    fn from_json_invalid() {
867        assert!(Multiplexer::from_json("not json").is_err());
868    }
869
870    #[test]
871    fn from_json_unknown_proof_location() {
872        let json = r#"{"proof_location": 99, "orders": []}"#;
873        assert!(Multiplexer::from_json(json).is_err());
874    }
875
876    // ── miscellaneous ────────────────────────────────────────────────────
877
878    #[test]
879    fn clear_empties_multiplexer() {
880        let mut mux = Multiplexer::new(ProofLocation::Private);
881        mux.add(make_params(1));
882        mux.add(make_params(2));
883        mux.clear();
884        assert!(mux.is_empty());
885    }
886
887    #[test]
888    fn order_ids_iterator() {
889        let mut mux = Multiplexer::new(ProofLocation::Private);
890        mux.add(make_params(0xaa));
891        mux.add(make_params(0xbb));
892        let ids: Vec<_> = mux.order_ids().collect();
893        assert_eq!(ids.len(), 2);
894        assert_eq!(ids[0], order_id(&make_params(0xaa)));
895    }
896
897    #[test]
898    fn iter_and_as_slice() {
899        let mut mux = Multiplexer::new(ProofLocation::Private);
900        mux.add(make_params(1));
901        mux.add(make_params(2));
902        assert_eq!(mux.iter().count(), 2);
903        assert_eq!(mux.as_slice().len(), 2);
904    }
905
906    #[test]
907    fn into_vec_returns_orders() {
908        let mut mux = Multiplexer::new(ProofLocation::Private);
909        mux.add(make_params(1));
910        let v = mux.into_vec();
911        assert_eq!(v.len(), 1);
912    }
913
914    #[test]
915    fn with_proof_location_builder() {
916        let mux =
917            Multiplexer::new(ProofLocation::Private).with_proof_location(ProofLocation::Swarm);
918        assert_eq!(mux.proof_location(), ProofLocation::Swarm);
919    }
920
921    #[test]
922    fn display_multiplexer() {
923        let mut mux = Multiplexer::new(ProofLocation::Private);
924        mux.add(make_params(1));
925        let s = format!("{mux}");
926        assert!(s.contains("1 orders"));
927    }
928
929    #[test]
930    fn display_order_proof() {
931        let mut mux = Multiplexer::new(ProofLocation::Private);
932        mux.add(make_params(0xaa));
933        mux.add(make_params(0xbb));
934        let proof = mux.proof(0).unwrap();
935        let s = format!("{proof}");
936        assert!(s.contains("order-proof"));
937    }
938
939    #[test]
940    fn from_json_all_proof_locations() {
941        for (val, expected) in [
942            (0, ProofLocation::Private),
943            (1, ProofLocation::Emitted),
944            (2, ProofLocation::Swarm),
945            (3, ProofLocation::Waku),
946            (4, ProofLocation::Reserved),
947            (5, ProofLocation::Ipfs),
948        ] {
949            let json = format!(r#"{{"proof_location": {val}, "orders": []}}"#);
950            let mux = Multiplexer::from_json(&json).unwrap();
951            assert_eq!(mux.proof_location(), expected);
952        }
953    }
954
955    #[test]
956    fn multiplexer_root_three_orders() {
957        let mut mux = Multiplexer::new(ProofLocation::Private);
958        mux.add(make_params(0xaa));
959        mux.add(make_params(0xbb));
960        mux.add(make_params(0xcc));
961        let root = mux.root().unwrap();
962        assert!(root.is_some());
963        // Verify the proof for each order
964        for i in 0..3 {
965            let proof = mux.proof(i).unwrap();
966            assert!(!proof.proof.is_empty() || mux.len() == 1);
967        }
968    }
969
970    #[test]
971    fn order_proof_accessors() {
972        let params = make_params(0xaa);
973        let id = order_id(&params);
974        let proof = OrderProof::new(id, vec![B256::ZERO], params.clone());
975        assert_eq!(proof.order_id, id);
976        assert_eq!(proof.proof_len(), 1);
977        assert_eq!(proof.params.salt, params.salt);
978    }
979
980    #[test]
981    fn proof_with_params_accessors() {
982        let params = make_params(0xaa);
983        let pwp = ProofWithParams::new(vec![B256::ZERO, B256::ZERO], params);
984        assert_eq!(pwp.proof_len(), 2);
985    }
986
987    #[test]
988    fn display_proof_with_params() {
989        let mut mux = Multiplexer::new(ProofLocation::Private);
990        mux.add(make_params(0xaa));
991        mux.add(make_params(0xbb));
992        let proofs = mux.dump_proofs_and_params().unwrap();
993        let s = format!("{}", proofs[0]);
994        assert!(s.contains("proof-with-params"));
995    }
996
997    #[test]
998    fn order_proof_new_and_proof_len() {
999        let op = OrderProof::new(B256::ZERO, vec![B256::ZERO, B256::ZERO], make_params(1));
1000        assert_eq!(op.proof_len(), 2);
1001    }
1002
1003    #[test]
1004    fn proof_with_params_new_and_proof_len() {
1005        let pwp = ProofWithParams::new(vec![B256::ZERO], make_params(1));
1006        assert_eq!(pwp.proof_len(), 1);
1007    }
1008}