simulator-api 0.22.1

Wire-protocol types for the Solana simulator backtest WebSocket API
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
//! What flowed through a venue in a reroute population, on L1 and after the re-quote.
//!
//! Both sides come off the same record, so the comparison needs no control run. The L1 column
//! counts only legs among the re-quoted population, not the venue's whole L1 footprint.
//!
//! Nothing here does I/O.
//!
//! The two figures answer different questions. **Volume** is per-hop, valued by each hop's own
//! `inAmount`, so a leg crossing the venue twice counts at both. **Share** is per-leg
//! normalized, splitting each leg by `percent` across its route's venues, so the shares over a
//! direction sum to 100%.

use std::collections::BTreeMap;

use solana_address::Address;

use super::{LegRecord, Prices, Samples};
use crate::route_plan::{RouteHop, RoutePlan};

fn in_amount(hop: &RouteHop) -> Option<u128> {
    hop.swap_info.in_amount.as_ref()?.parse().ok()
}

/// The selector named a venue the stream never carried — a wrong address or a misspelling.
/// Reporting that as `$0` would be indistinguishable from a venue with no flow.
#[derive(Debug, thiserror::Error)]
pub enum RerouteReportError {
    #[error(
        "{asked} never appears in this stream.\nIt names {} venues; the busiest are {}.",
        .venues.len(),
        busiest(.venues),
    )]
    NothingMatched {
        /// The selector, as the caller gave it.
        asked: String,
        /// Every venue the stream's quotes did name, with its hop count.
        venues: Vec<(String, u64)>,
    },
}

const NAMED_IN_ERROR: usize = 8;

fn busiest(venues: &[(String, u64)]) -> String {
    let mut ranked: Vec<_> = venues.iter().collect();
    ranked.sort_unstable_by(|(a_label, a_hops), (b_label, b_hops)| {
        b_hops.cmp(a_hops).then_with(|| a_label.cmp(b_label))
    });
    ranked
        .iter()
        .take(NAMED_IN_ERROR)
        .map(|(label, hops)| format!("{label} ({hops})"))
        .collect::<Vec<_>>()
        .join(", ")
}

/// The venue under test, as the two sides can each name it.
///
/// Either key alone works; both is strictest. Resolving a program to its metis label is a
/// network lookup and not done here — pass the label you already hold.
#[derive(Clone, Default, Debug)]
pub struct Target {
    label: Option<String>,
    /// Paired with its base58, encoded once rather than per hop.
    program: Option<(Address, String)>,
}

impl Target {
    pub fn new(label: Option<String>, program: Option<Address>) -> Self {
        Self {
            label,
            program: program.map(|program| (program, program.to_string())),
        }
    }

    pub fn label(&self) -> Option<&str> {
        self.label.as_deref()
    }

    pub fn program(&self) -> Option<Address> {
        self.program.as_ref().map(|(program, _)| *program)
    }

    /// The quote plan names no program, so the label is the only key it can be matched on.
    pub fn claims_quoted(&self, hop: &RouteHop) -> bool {
        match &self.label {
            Some(label) => hop.label() == Some(label.as_str()),
            None => false,
        }
    }

    /// The program wins when given; the label fallback is what lets a label-only target measure
    /// this side instead of reporting zero.
    pub fn claims_fill(&self, hop: &RouteHop) -> bool {
        if let Some((_, key)) = &self.program {
            return hop.program.as_deref() == Some(key.as_str());
        }
        self.claims_quoted(hop)
    }

    fn describe(&self) -> String {
        if let Some(label) = &self.label {
            format!("label {label:?}")
        } else if let Some((program, _)) = &self.program {
            format!("program {program}")
        } else {
            "that venue".to_string()
        }
    }
}

/// What crossed the venue in one trade direction, on one side of the comparison.
#[derive(Default, Clone, Copy, Debug)]
pub struct Flow {
    /// A swap crossing the venue twice counts twice.
    pub legs: u64,
    pub usd: f64,
    pub without_amount: u64,
    /// Priced at nothing rather than zero, so the dollar figure never absorbs them silently.
    pub without_price: u64,
    /// The numerator of [`Direction::share`], which is the interface to it.
    share_usd: f64,
}

impl Flow {
    fn merge(&mut self, other: Self) {
        self.legs += other.legs;
        self.usd += other.usd;
        self.without_amount += other.without_amount;
        self.without_price += other.without_price;
        self.share_usd += other.share_usd;
    }
}

/// One trade direction's before and after.
#[derive(Default, Clone, Copy, Debug)]
pub struct Direction {
    pub l1: Flow,
    pub requoted: Flow,
    /// Every venue's normalized value here — the denominator of [`Direction::share`].
    l1_all: f64,
    requoted_all: f64,
}

impl Direction {
    fn merge(&mut self, other: Self) {
        self.l1.merge(other.l1);
        self.requoted.merge(other.requoted);
        self.l1_all += other.l1_all;
        self.requoted_all += other.requoted_all;
    }

    /// The venue's percentage of all flow here, on each side. `None` when nothing could be
    /// priced, which a `0.0%` would misreport.
    pub fn share(&self) -> Option<(f64, f64)> {
        (self.l1_all > 0.0 && self.requoted_all > 0.0).then(|| {
            (
                self.l1.share_usd / self.l1_all * 100.0,
                self.requoted.share_usd / self.requoted_all * 100.0,
            )
        })
    }

    /// Whether the venue was on either side, which is what makes the direction worth a row.
    pub const fn present(&self) -> bool {
        self.l1.legs > 0 || self.requoted.legs > 0
    }

    pub const fn unpriced(&self) -> bool {
        self.l1.without_price > 0 || self.requoted.without_price > 0
    }

    /// Nothing priceable, so the dollars are dropped: a `$0` row reads as "worth nothing".
    pub fn fully_unpriced(&self) -> bool {
        self.unpriced() && self.l1.usd == 0.0 && self.requoted.usd == 0.0
    }

    /// Dollars, falling back to legs so an unpriceable book still sorts above an empty one.
    pub(super) fn rank(&self) -> (u64, u64) {
        let usd = self.l1.usd.max(self.requoted.usd);
        (usd as u64, self.l1.legs.max(self.requoted.legs))
    }
}

pub(super) fn fold(directions: impl IntoIterator<Item = Direction>) -> Direction {
    directions
        .into_iter()
        .fold(Direction::default(), |mut total, direction| {
            total.merge(direction);
            total
        })
}

/// A trade direction, keyed by the mints it moves between as `(input, output)`.
pub type Pair = (String, String);

#[derive(Clone, Copy)]
enum Side {
    L1,
    Requoted,
}

/// What a stream said about one venue.
#[derive(Debug)]
pub struct Report {
    by_direction: BTreeMap<Pair, Direction>,
    target: Target,
    /// Absent when the stream carried no SOL<->USDC reroutes to anchor from.
    sol_usd: Option<f64>,
}

impl Report {
    /// Build from anything that yields records — a mapped notification stream, a DB read.
    pub fn from_records<'a>(
        target: Target,
        records: impl IntoIterator<Item = &'a LegRecord>,
    ) -> Result<Self, RerouteReportError> {
        let mut builder = ReportBuilder::new(target);
        for record in records {
            builder.push(record);
        }
        builder.finish()
    }

    pub const fn target(&self) -> &Target {
        &self.target
    }

    pub const fn sol_usd(&self) -> Option<f64> {
        self.sol_usd
    }

    /// Every direction the range priced, which includes those the venue never served — filter
    /// on [`Direction::present`] for the venue's own.
    pub fn by_direction(&self) -> impl Iterator<Item = (&Pair, &Direction)> {
        self.by_direction.iter()
    }

    /// The directions summed. Share stays meaningful because both numerator and denominator are
    /// per-leg normalized.
    pub fn total(&self) -> Direction {
        fold(self.by_direction.values().copied())
    }
}

/// Accumulates a [`Report`] one record at a time, holding pricing samples, one accumulator per
/// trade direction, and the venue labels seen — never the records.
///
/// Every price is a median over the whole stream, so amounts accumulate raw and convert once in
/// [`ReportBuilder::finish`]. [`Prices::usd`] is linear in the amount, which is what makes that
/// identical to converting each hop as it arrives.
pub struct ReportBuilder {
    target: Target,
    samples: Samples,
    by_direction: Directions,
    /// Venue labels the quotes named, for the error a selector that matches nothing raises.
    labels: BTreeMap<String, u64>,
}

impl ReportBuilder {
    pub fn new(target: Target) -> Self {
        Self {
            target,
            samples: Samples::default(),
            by_direction: Directions::default(),
            labels: BTreeMap::new(),
        }
    }

    /// Take one record's contribution. Cheap and order-independent.
    pub fn push(&mut self, record: &LegRecord) {
        self.samples.push(record);

        // Neither column counts flow that never landed, so the two are filtered alike.
        if record.failed || record.original_failed {
            return;
        }
        let Self {
            target,
            by_direction,
            labels,
            ..
        } = self;
        fn hops_of(plan: &Option<RoutePlan>) -> Option<&[RouteHop]> {
            plan.as_ref().map(RoutePlan::hops)
        }
        // Used twice: the census below, and the fallback when there is no fill plan.
        let quoted = hops_of(&record.route_plan);
        for label in quoted.iter().copied().flatten().filter_map(RouteHop::label) {
            match labels.get_mut(label) {
                Some(hops) => *hops += 1,
                None => {
                    labels.insert(label.to_string(), 1);
                }
            }
        }

        // The fill, so both columns compare a fill against a fill; the quote is the fallback.
        let (requoted, from_quote) = match hops_of(&record.realized_route_plan) {
            Some(filled) => (filled, false),
            None => (quoted.unwrap_or_default(), true),
        };
        let claimed = |hop: &RouteHop| match from_quote {
            true => target.claims_quoted(hop),
            false => target.claims_fill(hop),
        };
        for hop in requoted.iter().filter(|hop| claimed(hop)) {
            by_direction.record(Side::Requoted, hop);
        }

        let l1 = hops_of(&record.original_route_plan);
        for hop in l1
            .iter()
            .copied()
            .flatten()
            .filter(|hop| target.claims_fill(hop))
        {
            by_direction.record(Side::L1, hop);
        }

        // The leg's own value, counted once whichever routes it took, so the shares are over
        // the population rather than over the hops the venue served.
        let leg_amount = u128::from(record.amount) as f64;
        by_direction.accumulate_share(
            Side::Requoted,
            requoted,
            record.input_mint,
            leg_amount,
            claimed,
        );
        if let Some(l1) = l1 {
            by_direction.accumulate_share(Side::L1, l1, record.input_mint, leg_amount, |hop| {
                target.claims_fill(hop)
            });
        }
    }

    /// Nothing matched on either side means the selector named something the stream never saw.
    pub fn finish(self) -> Result<Report, RerouteReportError> {
        let prices = self.samples.prices();
        let report = Report {
            sol_usd: prices.as_ref().map(Prices::sol_usd),
            by_direction: self.by_direction.price(prices.as_ref()),
            target: self.target,
        };
        let total = report.total();
        if total.l1.legs == 0 && total.requoted.legs == 0 {
            return Err(RerouteReportError::NothingMatched {
                asked: report.target.describe(),
                venues: self.labels.into_iter().collect(),
            });
        }
        Ok(report)
    }
}

/// In its own type so `record` and `accumulate_share` borrow only the map, leaving the rest of
/// the builder free.
#[derive(Default)]
struct Directions(BTreeMap<Pair, DirectionAccum>);

impl Directions {
    /// The hop's notional. Its input mint is the direction's, so raw amounts sum unpriced.
    fn record(&mut self, side: Side, hop: &RouteHop) {
        let Some(key) = key_of(hop) else {
            return;
        };
        let flow = &mut self.0.entry(key).or_default().side_mut(side).flow;
        flow.legs += 1;
        match in_amount(hop) {
            Some(amount) => flow.raw += amount,
            None => flow.without_amount += 1,
        }
    }

    /// A leg's value split across its route's venues, filed under each hop's own direction, so
    /// every leg counts once and the shares over a direction sum to 100%.
    ///
    /// Credited in the *leg's* input mint, which is not the direction's on a multi-hop route —
    /// hence a bucket per mint.
    fn accumulate_share(
        &mut self,
        side: Side,
        hops: &[RouteHop],
        leg_mint: Address,
        leg_amount: f64,
        claimed: impl Fn(&RouteHop) -> bool,
    ) {
        let total: u64 = hops.iter().map(|hop| hop.percent).sum();
        if total == 0 {
            return;
        }
        for hop in hops {
            let Some(key) = key_of(hop) else {
                continue;
            };
            let value = leg_amount * hop.percent as f64 / total as f64;
            let share = &mut self.0.entry(key).or_default().side_mut(side).share;
            credit(&mut share.all, leg_mint, value);
            if claimed(hop) {
                credit(&mut share.claimed, leg_mint, value);
            }
        }
    }

    /// A direction with nothing priceable and no venue legs carries no measurement — its bucket
    /// was opened before its mint was known to be unpriceable — so it is dropped, not zeroed.
    fn price(self, prices: Option<&Prices>) -> BTreeMap<Pair, Direction> {
        self.0
            .into_iter()
            .map(|(pair, accum)| {
                let direction = accum.price(&pair.0, prices);
                (pair, direction)
            })
            .filter(|(_, direction)| {
                direction.present() || direction.l1_all > 0.0 || direction.requoted_all > 0.0
            })
            .collect()
    }
}

/// `None` for a hop whose pair the plan never named: keying it on a default address would pool
/// unrelated unnamed trades into one priced bucket, so such a hop opens no direction at all.
fn key_of(hop: &RouteHop) -> Option<Pair> {
    Some((
        hop.input_mint()?.to_string(),
        hop.output_mint()?.to_string(),
    ))
}

/// Nearly every direction sees a single leg mint, so a scan beats a map; a hub pair terminating
/// routes out of many exotics is the exception.
fn credit(bucket: &mut Vec<(Address, f64)>, mint: Address, value: f64) {
    match bucket.iter_mut().find(|(held, _)| *held == mint) {
        Some((_, total)) => *total += value,
        None => bucket.push((mint, value)),
    }
}

/// Raw amounts for one side of one direction, awaiting a price.
#[derive(Default)]
struct FlowAccum {
    legs: u64,
    /// In the direction's input mint, shared by every hop filed here.
    raw: u128,
    without_amount: u64,
}

impl FlowAccum {
    fn price(self, mint: &str, prices: Option<&Prices>, share_usd: f64) -> Flow {
        let usd = prices.and_then(|prices| prices.usd_str(mint, self.raw as f64));
        Flow {
            legs: self.legs,
            usd: usd.unwrap_or_default(),
            without_amount: self.without_amount,
            // Every hop here shares one mint, so the range prices all of them or none.
            without_price: match usd {
                Some(_) => 0,
                None => self.legs - self.without_amount,
            },
            share_usd,
        }
    }
}

/// Weighted leg value for one side of one direction, per the leg's own input mint.
#[derive(Default)]
struct ShareAccum {
    all: Vec<(Address, f64)>,
    claimed: Vec<(Address, f64)>,
}

impl ShareAccum {
    /// `(all, claimed)`. An unpriceable mint contributes to neither, keeping the ratio honest.
    fn price(&self, prices: Option<&Prices>) -> (f64, f64) {
        let sum = |bucket: &[(Address, f64)]| {
            bucket
                .iter()
                .filter_map(|(mint, raw)| prices?.usd(mint, *raw))
                // Not `sum`: `Sum` for floats starts at `-0.0`, so an empty bucket would
                // render as `-0.0%`.
                .fold(0.0, |total, usd| total + usd)
        };
        (sum(&self.all), sum(&self.claimed))
    }
}

/// Paired so a hop's notional and its share cannot be filed against different sides.
#[derive(Default)]
struct SideAccum {
    flow: FlowAccum,
    share: ShareAccum,
}

#[derive(Default)]
struct DirectionAccum {
    l1: SideAccum,
    requoted: SideAccum,
}

impl DirectionAccum {
    fn side_mut(&mut self, side: Side) -> &mut SideAccum {
        match side {
            Side::L1 => &mut self.l1,
            Side::Requoted => &mut self.requoted,
        }
    }

    fn price(self, mint: &str, prices: Option<&Prices>) -> Direction {
        let (l1_all, l1_claimed) = self.l1.share.price(prices);
        let (requoted_all, requoted_claimed) = self.requoted.share.price(prices);
        Direction {
            l1: self.l1.flow.price(mint, prices, l1_claimed),
            requoted: self.requoted.flow.price(mint, prices, requoted_claimed),
            l1_all,
            requoted_all,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{
        mints,
        reroute_report::testing::{hop, plan, record_of},
    };

    const SOL: &str = "So11111111111111111111111111111111111111112";
    const USDC: &str = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
    const VENUE_PROGRAM: &str = "BiSoNHVpsVZW2F7rx2eQ59yQwKxzU5NvBcmKshCSUypi";
    const OTHER_PROGRAM: &str = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";

    /// Fixes SOL at $100 from the stream's own swaps. Carries no route plan, so it prices the
    /// range without contributing a hop to anything.
    fn anchors() -> Vec<LegRecord> {
        (1..=3)
            .map(|n| LegRecord {
                original_realized_output_amount: Some(n * 100_000_000),
                ..record_of(mints::SOL, mints::USDC, n * 1_000_000_000, None, None, None)
            })
            .collect()
    }

    fn venue() -> Target {
        Target::new(
            Some("BisonFi".to_string()),
            Some(VENUE_PROGRAM.parse().expect("a venue program")),
        )
    }

    fn fill(
        program: &str,
        label: &str,
        input: &str,
        output: &str,
        amount: u64,
    ) -> Option<RoutePlan> {
        plan(vec![hop(Some(program), label, input, output, amount)])
    }

    fn report_of(records: &[LegRecord]) -> Report {
        Report::from_records(venue(), records).expect("the venue appears")
    }

    /// The anchors, plus whatever the test is actually about.
    fn priced(extra: impl IntoIterator<Item = LegRecord>) -> Vec<LegRecord> {
        let mut records = anchors();
        records.extend(extra);
        records
    }

    /// A leg the venue won: the re-quote routed it through the venue, L1 had it elsewhere.
    fn won_leg(amount: u64) -> LegRecord {
        let realized = fill(VENUE_PROGRAM, "BisonFi", SOL, USDC, amount);
        let l1 = fill(OTHER_PROGRAM, "Whirlpool", SOL, USDC, amount);
        record_of(mints::SOL, mints::USDC, amount, None, l1, realized)
    }

    /// A hop whose pair the plan never named opens no direction: keyed on a default address it
    /// would pool unrelated unnamed trades into one priced bucket. Its named sibling still
    /// counts alone.
    #[test]
    fn a_hop_with_an_unnamed_mint_opens_no_direction() {
        let mut unnamed = hop(Some(VENUE_PROGRAM), "BisonFi", USDC, SOL, 1_000_000);
        unnamed.swap_info.output_mint = None;
        let l1 = plan(vec![
            hop(Some(VENUE_PROGRAM), "BisonFi", SOL, USDC, 2_000_000_000),
            unnamed,
        ]);
        let report = report_of(&priced([record_of(
            mints::SOL,
            mints::USDC,
            2_000_000_000,
            None,
            l1,
            None,
        )]));

        let pairs: Vec<&Pair> = report.by_direction().map(|(pair, _)| pair).collect();
        assert_eq!(pairs, [&(SOL.to_string(), USDC.to_string())]);
        assert_eq!(report.total().l1.legs, 1, "only the named hop counts");
    }

    /// Prices are medians over the whole stream, so no dollar figure exists until it ends. The
    /// builder holds raw amounts and converts once — which only works because the conversion is
    /// linear, and this is the test of that.
    #[test]
    fn deferring_the_conversion_matches_pricing_each_hop_as_it_arrives() {
        let report = report_of(&priced([won_leg(2_000_000_000), won_leg(3_000_000_000)]));
        let total = report.total();

        assert_eq!(report.sol_usd(), Some(100.0));
        assert_eq!(total.requoted.legs, 2);
        // 2 SOL and 3 SOL at $100, whether summed raw and converted once or converted twice.
        assert_eq!(total.requoted.usd, 500.0);
        assert_eq!(total.l1.legs, 0, "L1 had these legs at another venue");
    }

    /// The order records arrive in is the order the session produced them, which is not a
    /// property of the venue. Every figure must be a fold, not a sequence.
    #[test]
    fn the_result_does_not_depend_on_arrival_order() {
        let forward = priced([won_leg(2_000_000_000), won_leg(3_000_000_000)]);
        let mut backward = forward.clone();
        backward.reverse();

        let (forward, backward) = (report_of(&forward), report_of(&backward));
        assert_eq!(forward.sol_usd(), backward.sol_usd());
        assert_eq!(forward.total().requoted.usd, backward.total().requoted.usd);
        assert_eq!(forward.render(None), backward.render(None));
    }

    /// Neither column counts flow that never landed, and the two must be filtered alike — a
    /// reverted simulation is not a leg the venue won, and a failed original is not one it held.
    #[test]
    fn neither_side_counts_flow_that_never_landed() {
        let records = priced([
            won_leg(2_000_000_000),
            LegRecord {
                failed: true,
                ..won_leg(9_000_000_000)
            },
            LegRecord {
                original_failed: true,
                ..won_leg(9_000_000_000)
            },
        ]);

        let total = report_of(&records).total();
        assert_eq!(total.requoted.legs, 1, "only the landed leg counts");
        assert_eq!(total.requoted.usd, 200.0);
    }

    /// Volume share needs every leg counted once, so a two-hop route gives each half — where
    /// per-hop notional credits both in full. The two answer different questions.
    #[test]
    fn a_share_splits_a_leg_where_notional_counts_it_twice() {
        let two_hops = plan(vec![
            hop(Some(VENUE_PROGRAM), "BisonFi", SOL, USDC, 1_000_000_000),
            hop(Some(OTHER_PROGRAM), "Whirlpool", USDC, "BONK", 100_000_000),
        ]);
        let l1 = fill(VENUE_PROGRAM, "BisonFi", SOL, USDC, 1_000_000_000);
        let record = record_of(mints::SOL, mints::USDC, 1_000_000_000, None, l1, two_hops);
        let report = report_of(&priced([record]));

        let total = report.total();
        // The leg is one SOL, $100. Per-hop notional credits the venue the whole of its own hop.
        assert_eq!(total.requoted.usd, 100.0);
        // Share splits that $100 across the two hops on the route, and the venue holds one.
        assert_eq!(total.requoted_all, 100.0, "the leg is counted once");
        assert_eq!(
            total.requoted.share_usd, 50.0,
            "half the route was the venue's"
        );
        // On L1 it held the leg outright.
        assert_eq!(total.share(), Some((100.0, 50.0)));

        // Each hop is filed under its own direction, so the rows decompose the same measure.
        let (_, first) = report
            .by_direction()
            .find(|((input, output), _)| input == SOL && output == USDC)
            .expect("the venue's direction");
        assert_eq!(first.requoted.share_usd, 50.0);
        assert_eq!(first.requoted_all, 50.0, "that direction held half the leg");
    }

    /// The program wins when given; the label is the fallback, so naming only a label still
    /// measures the fill columns rather than reporting a silent zero on them.
    #[test]
    fn a_fill_prefers_the_program_and_falls_back_to_the_label() {
        let filled = fill(VENUE_PROGRAM, "BisonFi", SOL, USDC, 1_000);
        let record = record_of(mints::SOL, mints::USDC, 1_000, None, filled.clone(), filled);
        let hop = &record.original_route_plan.as_ref().expect("a plan").hops()[0];

        let by_label = Target::new(Some("BisonFi".to_string()), None);
        assert!(by_label.claims_fill(hop));
        assert!(by_label.claims_quoted(hop));

        // A program that was given and does not match is not rescued by a matching label.
        let other = Target::new(Some("BisonFi".to_string()), Some(Address::new_unique()));
        assert!(!other.claims_fill(hop));

        let by_program = Target::new(None, Some(VENUE_PROGRAM.parse().expect("a program")));
        assert!(by_program.claims_fill(hop));
        // The quote names no program, so this key cannot match there.
        assert!(!by_program.claims_quoted(hop));
    }

    /// Dollars cross mints; a raw amount does not. Summing each direction's own input mint added
    /// lamports to stablecoin units and produced a figure that looked precise and meant nothing,
    /// so no amount is reported in mint units anywhere — only legs and dollars.
    #[test]
    fn the_report_is_swaps_and_dollars_only() {
        let sell = fill(VENUE_PROGRAM, "BisonFi", SOL, USDC, 1_000_000_000);
        let buy = fill(VENUE_PROGRAM, "BisonFi", USDC, SOL, 100_000_000);
        let report = report_of(&priced([
            record_of(mints::SOL, mints::USDC, 1_000_000_000, None, None, sell),
            record_of(mints::USDC, mints::SOL, 100_000_000, None, None, buy),
        ]));

        // Lamports in one direction, stablecoin units in the other; dollars survive the sum.
        let json = report.to_json();
        assert_eq!(json["total"]["l1"]["swaps"], 0);
        assert_eq!(json["total"]["requoted"]["swaps"], 2);
        assert_eq!(json["total"]["requoted"]["usd"], 200.0);

        // And the rendered table carries the two rows, and no third.
        let table = report.render(None);
        let totals = table.split('→').next().expect("a totals block");
        assert!(totals.contains("swaps") && totals.contains("volume"));
        assert!(!totals.contains("raw in"), "a raw amount reached the table");
        // "re-quoted" is a column heading; a *quoted fill* line is what must not appear.
        assert!(!table.contains("quoted fill"));
    }

    /// A range with nothing to anchor on prices nothing. Reporting those legs at $0 would read
    /// as "worth nothing" rather than "not valued".
    #[test]
    fn a_range_that_cannot_be_priced_reports_no_dollars() {
        let report = report_of(&[won_leg(2_000_000_000)]);
        let total = report.total();

        assert_eq!(report.sol_usd(), None);
        assert_eq!(total.requoted.legs, 1);
        assert_eq!(total.requoted.without_price, 1);
        assert!(total.fully_unpriced());

        let table = report.render(None);
        assert!(!table.contains("volume"), "a dollar row without a price");
        assert!(table.contains("could not price"));
    }

    /// A selector that matches nothing is a wrong address or a misspelling, not a venue with no
    /// flow. Reporting it as $0 was indistinguishable from the real thing.
    #[test]
    fn a_selector_that_matches_nothing_says_so() {
        let quote = plan(vec![hop(None, "Whirlpool", SOL, USDC, 1_000)]);
        let records = vec![record_of(mints::SOL, mints::USDC, 1_000, quote, None, None)];

        let target = Target::new(Some("Nope".to_string()), None);
        let error = Report::from_records(target, &records).expect_err("the venue never appears");

        let message = error.to_string();
        assert!(message.contains("Nope"), "names what was asked for");
        // And what the stream does name, so the misspelling is visible.
        assert!(message.contains("Whirlpool (1)"));
    }

    /// The subtitle is the caller's, since this module cannot know a slot range.
    #[test]
    fn the_subtitle_is_the_callers() {
        let report = report_of(&[won_leg(2_000_000_000)]);
        assert!(report.render(Some("slots 1–100")).contains("slots 1–100"));
        assert!(!report.render(None).contains("slots"));
    }
}