Skip to content

feat: match and execute orders#1270

Merged
MicBun merged 2 commits intomainfrom
matchingEnginee
Dec 2, 2025
Merged

feat: match and execute orders#1270
MicBun merged 2 commits intomainfrom
matchingEnginee

Conversation

@MicBun
Copy link
Member

@MicBun MicBun commented Dec 1, 2025

resolves: https://github.com/truflation/website/issues/2920

Summary by CodeRabbit

  • New Features

    • Full order matching engine implemented with three prioritized matching strategies (direct, mint, burn) and automatic continuation until no matches remain.
    • Improved handling of share movements between holdings and orders during fills.
  • Bug Fixes

    • Avoid leaving zero-amount holdings by deleting or adjusting holdings on full sells.
  • Tests

    • End-to-end test suite added covering direct, mint, burn, multi-round flows and edge cases.

✏️ Tip: You can customize this high-level summary in your review settings.

@MicBun MicBun self-assigned this Dec 1, 2025
@MicBun MicBun added the type: feat New feature or request label Dec 1, 2025
@holdex
Copy link

holdex bot commented Dec 1, 2025

Time Submission Status

Member Status Time Action Last Update
MicBun ✅ Submitted 5h Update time Dec 1, 2025, 2:59 PM

@coderabbitai
Copy link

coderabbitai bot commented Dec 1, 2025

Walkthrough

Replaces a stubbed matching routine with a complete order-book matching engine (private SQL actions): implements orchestrated match_orders with match_direct, match_mint, and match_burn, updates sell-order handling, and adds a comprehensive Go test exercising matching scenarios.

Changes

Cohort / File(s) Summary
Core matching engine implementation
internal/migrations/032-order-book-actions.sql
Implements full match_orders orchestration and three strategies: match_direct (FIFO same-price matching, partial fills, recursive continuation), match_mint (mint opposite-side share pairs and adjust collateral/holdings), and match_burn (burn opposite-side pairs, unlock collateral, adjust/delete orders). Updates place_sell_order to remove zero-amount holdings and handle partial/full sells; strengthens input validation and recursive processing.
Matching engine test suite
tests/streams/order_book/matching_engine_test.go
Adds TestMatchingEngine with scenario-driven helpers covering direct/mint/burn matches, multi-round sequences, partial fills, and edge cases; provisions ERC20 balances, creates markets, places orders, and asserts positions/holdings/balances.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant API as Order API
    participant SQL as SQL Actions
    participant Engine as match_orders
    participant Direct as match_direct
    participant Mint as match_mint
    participant Burn as match_burn
    participant Ledger as Holdings/Balances

    User->>API: place_buy_order(query_id, outcome, price, amount)
    API->>SQL: place_buy_order action (lock collateral, record order)
    SQL->>Engine: match_orders(query_id, outcome, price)

    Engine->>Direct: try match_direct(complement_price)
    Direct->>Ledger: fetch FIFO buy/sell at prices
    Direct->>Ledger: transfer shares / adjust orders
    Direct->>Engine: return (continue?) 

    Engine->>Mint: try match_mint(complement_price)
    Mint->>Ledger: mint YES/NO pairs, adjust collateral & holdings
    Mint->>Engine: return (continue?)

    Engine->>Burn: try match_burn(complement_price)
    Burn->>Ledger: burn opposite pairs, unlock collateral, adjust/delete orders
    Burn->>Engine: return (done)

    SQL->>API: return final order/position state
    API->>User: report matched/unmatched results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Review recursive termination and invariants in match_direct, match_mint, match_burn
  • Validate collateral lock/unlock and holdings deletion logic in place_sell_order and match_burn
  • Confirm atomicity / transactional assumptions in SQL actions and consistency across partial fills
  • Inspect new tests for coverage gaps (concurrency/rare edge-cases)

Possibly related PRs

Suggested reviewers

  • pr-time-tracker
  • outerlook

Poem

🐇 I hopped through ledgers late at night,

matched buys and sells until they fit just right,
I minted pairs beneath the moon, then burned what none would keep,
collateral unlocked — a tidy heap,
Ledger neat, I thumped my foot and did a joyful leap!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: match and execute orders' directly aligns with the core changeset, which implements comprehensive order matching logic (direct_match, mint_match, burn_match) and order execution orchestration in the matching engine.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch matchingEnginee

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83d6e43 and 355e35b.

📒 Files selected for processing (1)
  • internal/migrations/032-order-book-actions.sql (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: acceptance-test
🔇 Additional comments (4)
internal/migrations/032-order-book-actions.sql (4)

434-448: ✓ Seller payment logic correctly implemented—resolves previous critical issue.

The buyer's locked collateral is now properly transferred to the seller after matching. Calculation is correct (match_amount × price × 10^16), and the transfer uses ob_unlock_collateral() with the seller's wallet address. This addresses the critical issue from the prior review.


368-486: Confirm recursion depth approach: documentation vs. explicit code guards.

The three match functions (match_direct, match_mint, match_burn) use tail recursion without explicit depth counters or guards (recursive calls at lines 485, 627, 788). The documentation (lines 345–361 and similar blocks) justifies safety via natural termination, FIFO ordering, economic constraints, and worst-case scenarios.

However, the prior review flagged this as a major concern and suggested explicit depth parameters (e.g., depth INT DEFAULT 0 with a cap like 100).

Please confirm whether the documentation-based approach is your intended design, or whether you'd prefer explicit code guards for additional safety. If documentation is sufficient, consider verifying this assumption holds under expected order book volumes.

Also applies to: 519-628, 663-789


1193-1209: ✓ Place_sell_order share handling correctly avoids amount=0 constraint.

Step 1 deletes holdings when selling all shares, and Step 2 updates when selling partial shares. This two-step DELETE/UPDATE pattern properly prevents zero-amount records and maintains data integrity. Logic is sound.


314-877: ✓ Matching engine orchestration and all three match types are well-implemented.

  • Validation & orchestration (match_orders, lines 808–877): Input validation is comprehensive; direct → mint → burn priority is logical.
  • Direct matching (lines 368–486): FIFO retrieval, correct sign convention (buy = negative price, sell = positive), full/partial fill logic, and collateral transfer all correct.
  • Mint matching (lines 519–628): Complementary price validation, share pair creation, and accumulation logic sound.
  • Burn matching (lines 663–789): Payout calculations correctly split collateral per complementary prices; both sellers receive refunds.
  • Data integrity: DELETE/UPDATE sequencing prevents amount=0 violations; UPSERT patterns correctly accumulate and preserve FIFO timestamps.
  • No security/injection risks: All inputs are parameterized; NUMERIC(78, 0) handles large values safely.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
tests/streams/order_book/matching_engine_test.go (2)

23-27: Consider using test-scoped state instead of package-level variables.

Package-level mutable state (balancePointCounter, lastBalancePoint) can cause issues if tests are ever run in parallel or if the test harness changes. While each test currently resets lastBalancePoint = nil, the balancePointCounter is never reset and will continuously increment across test runs.

Consider encapsulating this state in a struct passed to each test:

-var (
-	balancePointCounter int64 = 100
-	lastBalancePoint    *int64
-)
+type balanceTracker struct {
+	counter   int64
+	lastPoint *int64
+}
+
+func newBalanceTracker() *balanceTracker {
+	return &balanceTracker{counter: 100}
+}

74-79: Consider adding edge case tests for boundary conditions.

The current test suite is comprehensive for core matching logic. Consider adding tests for:

  1. Self-match scenario: Verify that a user cannot match with themselves (if this is prevented by the system).
  2. Price boundaries: Test behavior at price = 1 and price = 99.
  3. Same participant on both sides of mint/burn: Edge case where complementary orders belong to the same user.
internal/migrations/032-order-book-actions.sql (1)

361-407: Consider preventing self-matching in direct and mint matches.

Currently, match_direct and match_mint don't check if the buyer and seller are the same participant. While this doesn't cause economic harm (user trades with themselves), it wastes gas and could indicate a bug in client logic.

For match_burn, self-matching is a valid use case (user cashing out their complete position).

     -- No sell order, exit
     if NOT $sell_found {
         RETURN;
     }

+    -- Skip self-match (user cannot trade with themselves)
+    if $buy_participant_id = $sell_participant_id {
+        RETURN;
+    }
+
     -- Defensive: Skip if either order has zero or negative amount
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63f8e45 and 83d6e43.

📒 Files selected for processing (2)
  • internal/migrations/032-order-book-actions.sql (2 hunks)
  • tests/streams/order_book/matching_engine_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/streams/order_book/matching_engine_test.go (4)
tests/streams/utils/erc20/inject.go (1)
  • InjectERC20Transfer (24-85)
tests/streams/utils/runner.go (1)
  • RunSchemaTest (37-116)
tests/streams/utils/utils.go (1)
  • GetTestOptionsWithCache (75-88)
tests/streams/order_book/buy_order_test.go (1)
  • Position (399-406)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: acceptance-test
🔇 Additional comments (10)
tests/streams/order_book/matching_engine_test.go (5)

30-54: Helper function logic looks correct.

The chained deposit mechanism properly maintains the linked-list ordering for ordered-sync, updating lastBalancePoint only on success.


56-81: Good test organization with comprehensive scenario coverage.

The test suite covers all three match types (direct, mint, burn) with both full and partial fill scenarios, plus multi-round matching and edge cases. Clear category comments improve readability.


88-146: Direct match full fill test is well-structured.

The test correctly verifies that after a direct match of equal amounts, the buyer receives the expected holdings. The position-finding logic properly uses &positions[i] to avoid loop variable capture issues.


528-537: Partial burn payout calculations are correct.

  • User1 receives 36 TRUF (60 shares × $0.60)
  • User2 net change is -76 TRUF (locked 100, received 24 from burn at $0.40)

The assertions correctly verify the expected collateral movements for partial burn matches.


639-709: Good edge case test for non-matching orders.

The test correctly verifies that orders at different prices ($0.56 buy vs $0.65 sell) remain unmatched. The workaround to prevent self-burn (cancel NO sell after split limit) is appropriately documented.

internal/migrations/032-order-book-actions.sql (5)

478-587: Mint match implementation is correct.

The collateral flow for mint matching is sound:

  • Both buyers already have collateral locked (summing to $1.00 per pair)
  • New shares are created and backed by existing locked collateral
  • No additional collateral movement is needed

The FIFO ordering and partial fill logic are correctly implemented.


695-706: Burn match collateral unlocking is correct.

The payout calculations properly distribute collateral to both sellers based on their sell prices, with the total exactly matching the $1.00 per share pair that was originally locked.


759-823: Match orchestration logic is sound.

The orchestrator correctly:

  1. Validates all inputs
  2. Tries direct match first (most common case)
  3. Attempts mint match with complementary pricing
  4. Attempts burn match with complementary pricing

The order of operations is logical - direct matches are fastest and most common.


1139-1154: Holdings management logic is correct.

The two-step approach (DELETE for full sell, UPDATE for partial sell) correctly handles the constraint that prevents zero-amount positions. The mutually exclusive WHERE clauses ensure exactly one statement executes.


422-445: Trade execution does not record transaction events.

Unlike create_market which records events via record_transaction_event(8), the order matching functions (match_direct, match_mint, match_burn) have no corresponding transaction method_id defined. This means trades lack audit trail coverage for analytics, compliance, and trade history features.

Consider adding a transaction method_id for order matching to maintain consistent event recording across all user-facing actions.

@MicBun MicBun enabled auto-merge (squash) December 1, 2025 16:12
@MicBun MicBun disabled auto-merge December 2, 2025 04:05
@MicBun MicBun merged commit 70b1849 into main Dec 2, 2025
6 checks passed
@MicBun MicBun deleted the matchingEnginee branch December 2, 2025 04:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feat New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant