Skip to content

feat(ob): place sell orders on markets#1265

Merged
MicBun merged 1 commit intomainfrom
sellMarket
Nov 27, 2025
Merged

feat(ob): place sell orders on markets#1265
MicBun merged 1 commit intomainfrom
sellMarket

Conversation

@MicBun
Copy link
Member

@MicBun MicBun commented Nov 27, 2025

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

Summary by CodeRabbit

  • New Features

    • Users can now place sell orders in the order book with validated price and amount parameters.
    • Sell orders require sufficient holdings and are matched against existing buy orders.
    • Market settlement status is enforced to prevent trading on settled markets.
  • Tests

    • Added comprehensive test suite for sell order functionality covering success and error scenarios.

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

@MicBun MicBun self-assigned this Nov 27, 2025
@MicBun MicBun added the type: feat New feature or request label Nov 27, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 27, 2025

Walkthrough

A new place_sell_order public action is introduced to the order-book system along with its test suite. The action validates caller addresses and parameters, verifies market existence and settlement status, checks participant holdings, and transitions amounts from holdings to sell orders, triggering a matching engine.

Changes

Cohort / File(s) Summary
Order-book sell order action
internal/migrations/032-order-book-actions.sql
Introduces place_sell_order(query_id INT, outcome BOOL, price INT, amount INT8) public action that validates 0x-prefixed addresses, checks parameters (price 1–99, positive amount within limits), verifies market existence and unsettled state, ensures caller has sufficient holdings at price 0, moves amount from holdings to sell orders, upserts positions, and triggers matching engine via match_orders.
Sell order test suite
tests/streams/order_book/sell_order_test.go
Adds comprehensive test suite with TestPlaceSellOrder entry point covering successful sell flows, error conditions (no holdings, insufficient shares, non-existent/settled market, invalid price/amount), multiple orders at different prices, and UPSERT behavior. Includes helper functions callPlaceSellOrder for signed transaction context and giveShares for simulating holdings via admin privileges.

Sequence Diagram

sequenceDiagram
    participant User
    participant Engine
    participant place_sell_order as place_sell_order Action
    participant DB
    participant Matcher as Matching Engine

    User->>Engine: Call place_sell_order(query_id, outcome, price, amount)
    
    rect rgb(200, 220, 240)
    note over place_sell_order: Validation Phase
    Engine->>place_sell_order: Invoke with signed context
    place_sell_order->>place_sell_order: Validate 0x-prefixed caller
    place_sell_order->>place_sell_order: Check params (price ∈ [1,99], amount > 0)
    end
    
    rect rgb(220, 240, 200)
    note over place_sell_order: Market & Holdings Verification
    place_sell_order->>DB: Query market existence & settlement
    DB-->>place_sell_order: Market data
    place_sell_order->>DB: Query participant & holdings at price=0
    DB-->>place_sell_order: Holdings data
    place_sell_order->>place_sell_order: Verify sufficient holdings
    end
    
    rect rgb(240, 220, 200)
    note over place_sell_order: State Transition
    place_sell_order->>DB: Remove/update holdings (price=0)
    place_sell_order->>DB: Upsert sell order (positive price)
    DB-->>place_sell_order: Confirmed
    end
    
    place_sell_order->>Matcher: Trigger match_orders
    Matcher-->>place_sell_order: Matching initiated
    place_sell_order-->>Engine: Success
    Engine-->>User: Sell order placed
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • SQL migration logic: Review validation chains, parameter bounds, holdings deduction mechanics, and state transition ordering for correctness
  • Holdings-to-order transition: Verify deletion/update logic for zero-amount holdings rows and price field semantics
  • Test coverage comprehensiveness: Examine test cases for adequate error path coverage and fixture setup (ERC20 initialization, market creation, holdings simulation via giveShares)
  • Matching engine integration: Confirm that match_orders invocation is appropriate and side-effect-safe

Poem

🐰 A seller hops in, shares all in hand,
Sets a fair price on the marketplace stand,
Holdings shift down to orders so neat,
The matching engine springs its fleet,
Trades bloom! ✨

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 clearly and accurately summarizes the main change: introducing the ability to place sell orders on markets, which is the primary feature added in this pull request.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sellMarket

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.

@holdex
Copy link

holdex bot commented Nov 27, 2025

Time Submission Status

Member Status Time Action Last Update
MicBun ✅ Submitted 5h Update time Nov 27, 2025, 9:41 AM

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: 0

🧹 Nitpick comments (2)
internal/migrations/032-order-book-actions.sql (1)

657-663: Consider adding defensive verification that holdings were actually reduced.

While the sequential transaction model in Kwil ensures the earlier holdings check remains valid, adding a check that the UPDATE affected exactly 1 row would provide an extra layer of safety against unexpected data inconsistencies.

Example approach:

-- After the UPDATE, verify it succeeded
$rows_updated INT;
-- Note: Kuneiform may not support ROW_COUNT, check documentation

Based on learnings, Kwil processes transactions sequentially within a block, so race conditions are not possible. However, defensive programming is still valuable for detecting unexpected states.

tests/streams/order_book/sell_order_test.go (1)

573-636: Consider simplifying giveShares to avoid collateral locking issues.

The current implementation creates a buy order (which locks collateral) and then converts it to holdings. However, this locked collateral is never released, which could cause balance issues when multiple tests use giveShares.

A simpler and more efficient approach would be to directly INSERT holdings using admin privileges:

func giveShares(ctx context.Context, platform *kwilTesting.Platform, signer *util.EthereumAddress, marketID int, outcome bool, amount int64) error {
    callerBytes := signer.Bytes()
    
    // Step 1: Get or create participant
    tx := &common.TxContext{
        Ctx:          ctx,
        BlockContext: &common.BlockContext{Height: 1, Timestamp: time.Now().Unix()},
        Signer:       platform.Deployer,
        Caller:       "0x0000000000000000000000000000000000000000",
        TxID:         platform.Txid(),
    }
    engineCtx := &common.EngineContext{TxContext: tx, OverrideAuthz: true}
    
    var participantID int64
    err := platform.Engine.Execute(engineCtx, platform.DB,
        `INSERT INTO ob_participants (id, wallet_address)
         SELECT COALESCE(MAX(id), 0) + 1, $addr FROM ob_participants
         ON CONFLICT (wallet_address) DO NOTHING`,
        map[string]any{"$addr": callerBytes}, nil)
    if err != nil {
        return fmt.Errorf("failed to create participant: %w", err)
    }
    
    // Get participant ID
    err = platform.Engine.Execute(engineCtx, platform.DB,
        `SELECT id FROM ob_participants WHERE wallet_address = $addr`,
        map[string]any{"$addr": callerBytes},
        func(row *common.Row) error {
            participantID = row.Values[0].(int64)
            return nil
        })
    if err != nil {
        return fmt.Errorf("failed to get participant ID: %w", err)
    }
    
    // Step 2: Directly insert holdings (price = 0)
    return platform.Engine.Execute(engineCtx, platform.DB,
        `INSERT INTO ob_positions (query_id, participant_id, outcome, price, amount, last_updated)
         VALUES ($qid, $pid, $outcome, 0, $amount, $timestamp)
         ON CONFLICT (query_id, participant_id, outcome, price)
         DO UPDATE SET amount = ob_positions.amount + EXCLUDED.amount`,
        map[string]any{
            "$qid": marketID, "$pid": participantID,
            "$outcome": outcome, "$amount": amount,
            "$timestamp": time.Now().Unix(),
        }, nil)
}

This approach:

  • Avoids locking collateral unnecessarily
  • Is simpler and more direct
  • Prevents potential balance exhaustion in test suites
  • More clearly expresses intent (test setup, not real trading flow)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dfbdcba and e2dd082.

📒 Files selected for processing (2)
  • internal/migrations/032-order-book-actions.sql (1 hunks)
  • tests/streams/order_book/sell_order_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/streams/order_book/sell_order_test.go (1)
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). (2)
  • GitHub Check: lint
  • GitHub Check: acceptance-test
🔇 Additional comments (8)
internal/migrations/032-order-book-actions.sql (5)

557-608: LGTM! Validation logic is thorough and consistent.

The validation section correctly checks:

  • Caller address format (0x-prefixed Ethereum address)
  • Parameter constraints (price 1-99, amount positive and within limits)
  • Market existence and settlement status

The validation order is efficient (cheap checks before expensive DB lookups).


610-626: LGTM! Correctly prevents auto-creation of participants for sell orders.

Unlike buy orders, sell orders require an existing participant with holdings. The explicit check and clear error message ensure users understand they must own shares before selling.


628-651: LGTM! Holdings verification is correct and user-friendly.

The logic correctly:

  • Queries for holdings at price = 0 (per the price convention)
  • Distinguishes between no holdings and insufficient holdings
  • Provides informative error messages with actual vs. required amounts

674-686: LGTM! UPSERT logic correctly accumulates sell orders at the same price.

The implementation:

  • Uses positive price for sell orders (consistent with the price convention)
  • Accumulates amounts when multiple orders are placed at the same price
  • Updates timestamp to maintain FIFO ordering within price levels

688-696: LGTM! Matching engine stub is properly integrated.

The function correctly triggers the matching engine after placing the sell order. The stub implementation is documented and aligns with the overall order book architecture.

tests/streams/order_book/sell_order_test.go (3)

22-46: LGTM! Test suite structure is well-organized with clear documentation.

The test suite:

  • Properly uses the kwiltest build tag
  • Clearly marks tests blocked by Issue 6 (matching engine) with TODO comments
  • Focuses on validation paths that can be tested without the matching engine
  • Uses appropriate test utilities and schema setup

48-537: LGTM! Test functions provide comprehensive coverage of validation paths.

The test suite intelligently:

  • Separates tests that can run now (validation/error paths) from those blocked by Issue 6
  • Tests all validation constraints (price range, amount limits, market state)
  • Includes edge cases (no shares, wrong outcome, settled market)
  • Uses clear assertions with helpful error messages

The blocked tests (success paths, UPSERT behavior) are well-structured and ready to be enabled when the matching engine is implemented.


541-571: LGTM! Helper function follows standard Kwil testing patterns.

The callPlaceSellOrder helper:

  • Correctly constructs a signed transaction context
  • Uses appropriate authenticator (EthPersonalSignAuth)
  • Properly handles both engine errors and result errors
  • Follows the same pattern as buy order tests

@MicBun MicBun merged commit 54015f4 into main Nov 27, 2025
6 checks passed
@MicBun MicBun deleted the sellMarket branch November 27, 2025 09:41
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