QuickLendX uses a deterministic, multi-level comparator (BidStorage::compare_bids) to rank
competing bids for an invoice. Determinism is a hard requirement in Soroban execution: every
validator node must produce the same ranking for the same ledger state, regardless of the order
in which bids were submitted or stored.
When two bids are compared, the following criteria are applied in order. The first criterion that differs between the two bids determines the winner.
| Priority | Criterion | Winner |
|---|---|---|
| 1 | Profit (expected_return − bid_amount) |
Higher profit |
| 2 | Expected return | Higher expected return |
| 3 | Bid amount | Higher bid amount |
| 4 | Timestamp | Newer (higher) timestamp |
| 5 | Bid ID (lexicographic byte order) | Higher bid ID |
The bid ID is a deterministic, monotonically-generated 32-byte value that encodes a timestamp
and a counter (see EscrowStorage::generate_unique_escrow_id for the analogous pattern). Because
no two bids can share the same ID, the comparator never returns Equal for two distinct bids.
Deterministically compares two bids using the five-level priority above.
/// @notice Deterministically compares two bids.
/// @dev Ordering priority: (1) profit, (2) expected_return, (3) bid_amount,
/// (4) timestamp with newer bids first, (5) bid_id as final stable tiebreaker.
pub fn compare_bids(bid1: &Bid, bid2: &Bid) -> OrderingSecurity properties:
- Reflexive:
compare_bids(a, a) == Equal - Antisymmetric:
compare_bids(a, b) == Greateriffcompare_bids(b, a) == Less - Transitive: if
a > bandb > cthena > c - Total: for any two distinct bids, exactly one of
GreaterorLessis returned
Returns all Placed bids for an invoice sorted from best to worst using compare_bids.
Non-Placed bids (Withdrawn, Accepted, Expired, Cancelled) are excluded.
Invariant: If the result is non-empty, result[0] equals the value returned by
get_best_bid for the same invoice and ledger state.
Returns the single highest-ranked Placed bid. Uses the same compare_bids comparator
as rank_bids so the two functions cannot drift on tie handling.
Invariant: get_best_bid(env, id) == rank_bids(env, id).get(0) always holds.
Only Placed bids participate in ranking:
Placed ──► included in rank_bids / get_best_bid
Withdrawn ──► excluded
Accepted ──► excluded
Expired ──► excluded
Cancelled ──► excluded
-
Determinism: The comparator is a pure function of bid fields. No randomness, no ledger-state reads, no node-dependent values. Every validator produces the same result.
-
Stability:
get_best_bidandrank_bidsshare the same comparator path (select_best_placed_bid/select_best_index). A change to one automatically applies to the other. -
Fairness: The bid-ID tiebreaker is lexicographic on a deterministic byte sequence. It does not favour any particular investor address or submission time beyond what is already captured by the timestamp tier.
-
No equal outcomes: Because bid IDs are unique,
compare_bidsnever returnsEqualfor two distinct bids. This prevents any ambiguity in best-bid selection. -
Insertion-order independence: The selection sort used in
rank_bidsand the linear scan inselect_best_placed_bidboth iterate over the full bid list and applycompare_bidsto every pair. The result is identical regardless of the order in which bids were stored.
All invariants above are codified in src/test_bid_ranking_tiebreaker.rs (issue #811).
| Test group | Tests | What is verified |
|---|---|---|
| Tier 1 – Profit | 3 | Higher profit wins; 3-bid descending order |
| Tier 2 – Expected return | 3 | Higher expected_return wins; insertion-order independence |
| Tier 3 – Bid amount | 3 | Higher bid_amount wins; insertion-order independence |
| Tier 4 – Timestamp | 4 | Newer timestamp wins; 3-bid descending; insertion-order independence |
| Tier 5 – Bid ID | 4 | Higher bid_id wins; 3-bid descending; insertion-order independence |
| Reflexivity / symmetry | 3 | Reflexive, antisymmetric, transitive |
| Non-Placed exclusion | 6 | Withdrawn, Accepted, Expired, Cancelled excluded; empty result |
| best == first-ranked | 5 | Invariant holds at every tie level |
| Large / stress | 2 | 10 bids distinct profits; 5 full-tie bids by bid_id |
| Cross-invoice isolation | 1 | Bids on different invoices do not interfere |
| Single bid | 1 | Single bid is its own best and sole ranked entry |
| Total | 35 | 35 passed, 0 failed |
cd quicklendx-contracts
cargo test --lib test_bid_ranking_tiebreakerExpected output:
running 35 tests
test result: ok. 35 passed; 0 failed; 0 ignored