Order book: evict the worst order when a side is full - #158
Merged
Merged
Conversation
A full side refused every new resting order, so anyone willing to lock the minimum order size and pay rent for each slot could fill a side with orders far from the spread and keep every new order on it out. The market authority had no remedy, and the market PDA's seeds are the two mints, so the pair could not move to a new market. place_order now evicts: when the order will rest on a full side and beats that side's worst price, the worst order is refunded through its owner's unsettled balance, exactly as cancel_order refunds it (the refund is now one shared function, credit_unfilled_lock), removed from the book and its owner's open orders, and stamped Cancelled. An order that does not beat the worst price still gets OrderBookFull. The caller passes the worst order and its owner's MarketUser after the maker pairs, or only the order when it is their own, because Anchor refuses a writable account that appears twice. New errors: MissingEvictedAccounts and EvictedAccountMismatch. Also corrects the stated capacity: a side holds 512 orders, not 1024, since every order after the first adds a leaf and an inner node to the side's 1024-node tree. MAX_ORDERS_PER_SIDE is now MAX_TREE_NODES / 2. Both the Anchor v2 and Quasar variants change, each with five tests that fill the bid side to 512 orders. Anchor v1 is a frozen snapshot per CONTRIBUTING.md and does not change. Claude-Session: https://claude.ai/code/session_01RBfNjrQd2J6muoi3thCUin
CONTRIBUTING.md now says an Anchor v1 copy tracks its v2 counterpart, so eviction goes into all three variants rather than only Anchor v2 and Quasar. The port matches the v2 copy: worst_leaf and OrderBook::worst, the 512-order capacity fix, credit_unfilled_lock shared with cancel_order, the MissingEvictedAccounts and EvictedAccountMismatch errors, the eviction step in place_order, the README and CHANGELOG, and the same five tests. When the worst order is the caller's own, only the order is passed and the caller's market_user is credited, as in v2. Anchor v1 would accept the caller's MarketUser a second time, but a second loaded copy would be overwritten when the instruction writes its accounts back, so the rule matters here too. This copy is not rustfmt-clean on main and is outside the workspace CI formats, so the change is left unformatted to keep the diff to the port. Claude-Session: https://claude.ai/code/session_01RBfNjrQd2J6muoi3thCUin
mikemaccana
force-pushed
the
claude/alex-book-feedback-4eciar
branch
from
September 23, 2026 14:22
baeed30 to
5dde8f6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A full side of the order book refused every new resting order. Anyone willing to lock the minimum order size and pay rent for each slot could fill a side with orders far from the spread and keep every new order off that side for as long as they liked. The market authority had no way to clear them, and the market PDA's seeds are the two mints, so the pair could not move to a new market.
What changes
place_order(). When the order will rest on a full side and beats that side's worst price, the worst order is evicted. Its owner is refunded through their unsettled balance, exactly ascancel_order()refunds, and the order is removed from the book and from its owner's open orders, then stampedCancelled. An order that does not beat the worst price still getsOrderBookFull. Equal is not better, because the resting order was there first.MarketUserafter the maker pairs. When the worst order is the caller's own, only the order is passed, and the caller'smarket_useris credited. Anchor v2 refuses a writable account that appears twice. In Anchor v1, a second loaded copy would be overwritten when the instruction writes its accounts back. The check that remaining accounts come in whole pairs is gone, and the per-fill length check still covers every maker pair.credit_unfilled_lock()now holds the refund arithmetic thatcancel_order()had inline, and eviction calls it too.MissingEvictedAccountsandEvictedAccountMismatch.MAX_ORDERS_PER_SIDEis nowMAX_TREE_NODES / 2, and the comments and READMEs say so.All three variants change: Anchor v2, Anchor v1, and Quasar. Anchor v1 follows the CONTRIBUTING.md rule that landed on main while this PR was open: an Anchor v1 copy tracks its Anchor v2 counterpart. The Anchor v1 copy is not rustfmt-clean on main and sits outside the workspace CI formats, so the port is left unformatted to keep its diff to the change itself. The Kani proofs only model
remaining_quantity(), so they are unaffected.The branch is rebased on main, as two commits: the Anchor v2 and Quasar change, then the Anchor v1 port.
Tests
Each variant gains five tests that fill the bid side with 512 bids from 27 traders:
full_side_refuses_an_order_no_better_than_its_worstbetter_order_evicts_the_worst_and_restsevicted_maker_settles_their_refundeviction_rejects_missing_or_wrong_evicted_accountstrader_can_evict_their_own_worst_orderRun locally with Solana 3.1.14 on the rebased branch. The Anchor counts include main's two critbit depth tests, which still pass with eviction in
place_order().cargo build-sbfcargo build-sbfquasar buildat the pinned revanchor buildcould not run here, because it tries to install the Solana version pinned inAnchor.tomlfrom a host this environment cannot reach. It wraps the samecargo build-sbfcall, and CI runs the real command.The book's Order Book Exchange chapter describes this eviction in quicknode/solana-book#170. That PR should merge after this one, so the book never names an error that has not shipped.
🤖 Generated with Claude Code
https://claude.ai/code/session_01RBfNjrQd2J6muoi3thCUin