Skip to content

fix(finance/order-book): decimal-agnostic pricing with base_lot_size + quote_lot_size#66

Merged
mikemaccana merged 5 commits into
mainfrom
claude/order-book-skill-audit-U8lX3
Jun 10, 2026
Merged

fix(finance/order-book): decimal-agnostic pricing with base_lot_size + quote_lot_size#66
mikemaccana merged 5 commits into
mainfrom
claude/order-book-skill-audit-U8lX3

Conversation

@mikemaccana

@mikemaccana mikemaccana commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to #41. Makes the order-book example price correctly for any pair of token decimals, surfaced when modelling its own NVDAx/USDC market with real on-chain decimals.

Note: main renamed defi/finance/ after this PR opened; the branch has been merged with main, so these files now live under finance/order-book/.

The problem

NVDAx has 8 decimals on-chain; USDC has 6. The program stored price as raw quote units per raw base unit, so the minimum representable tick was 10^(d_base − d_quote) = 10^2 = $100/share — unusable at realistic prices (~$130). Tests also used a single MINT_DECIMALS = 6 for both mints, hiding the mismatch.

The fix — two-lot model (Serum/Openbook)

Quantities are denominated in base lots and prices in quote lots, so neither side is tied to a mint's raw decimals:

raw_base  = quantity × base_lot_size
raw_quote = quantity × price × quote_lot_size

Choosing base_lot_size = 10^max(d_base − d_quote, 0) and quote_lot_size = 10^max(d_quote − d_base, 0) makes price the human-readable quote/base rate and tick_size = 1 one atomic increment — for any decimal combination, including d_quote > d_base.

  • NVDAx (8) / USDC (6): base_lot_size = 100, quote_lot_size = 1
  • WBTC (8) / HD-USDC (18): base_lot_size = 1, quote_lot_size = 10^10

Changes

  • state/market.rsbase_lot_size + quote_lot_size fields with decimal-math docs.
  • instructions/initialize_market.rs, lib.rs — both lot-size params, > 0 validation, storage.
  • errors.rsInvalidBaseLotSize, InvalidQuoteLotSize.
  • instructions/place_order.rs — base flows × base_lot_size; quote flows (bid lock, gross_quote, price-improvement rebate) × quote_lot_size. All via u128 intermediates to stay overflow-safe.
  • instructions/cancel_order.rs — ask refund × base_lot_size, bid refund × quote_lot_size.
  • tests/test_order_book.rsBASE_DECIMALS = 8 / QUOTE_DECIMALS = 6, BASE_LOT_SIZE = 100, QUOTE_LOT_SIZE = 1, all assertions updated, plus rejection tests for zero base/quote lot size.
  • README.md — reword the critbit section: it's a depth-bounded radix trie (depth ≤ key bit width), not a self-balancing BST. Same security conclusion (no adversarial-insert degeneration), stated accurately.

settle_funds is untouched — it transfers unsettled_*, already stored in raw tokens.

Verification

cargo check is clean at the new finance/order-book/ path. The full LiteSVM suite requires the Solana BPF toolchain (anchor build), which runs in CI.

https://claude.ai/code/session_01G6iaAjzg8aoFwe8ZWWG9VR

claude added 5 commits June 9, 2026 19:47
The base mint was using 6 decimals matching USDC, but the real NVDAx
token on-chain has 8 decimals. Split MINT_DECIMALS into BASE_DECIMALS=8
and QUOTE_DECIMALS=6 and add a comment explaining the price resolution
consequence: with these two mints, one tick = 10^(8-6) = $100/share.

https://claude.ai/code/session_01G6iaAjzg8aoFwe8ZWWG9VR
Without base_lot_size, raw-unit prices for NVDAx (8 dec) / USDC (6 dec)
produce a $100 minimum tick — unusable at real NVIDIA prices. Fix: quantities
are now in lots (1 lot = base_lot_size raw base tokens); setting
base_lot_size = 10^(d_base - d_quote) = 100 makes `price` equal the
human-readable USDC/share rate with a $1 minimum tick.

All base-token flows updated with u128 overflow-safe arithmetic per
Solana best practices: ask lock in place_order, bid fill receipt,
ask fill maker credit, and ask cancel refund in cancel_order. Tests
split MINT_DECIMALS into BASE_DECIMALS=8 / QUOTE_DECIMALS=6, add
BASE_LOT_SIZE=100, update all assertions, and add a rejection test
for zero base_lot_size.

https://claude.ai/code/session_01G6iaAjzg8aoFwe8ZWWG9VR
Adopts the Serum/Openbook two-lot model. Previously the program was only
correct when d_base >= d_quote (e.g. NVDAx/USDC), because quote amounts
were computed as price × quantity raw quote tokens with no scaling on the
quote side.

With quote_lot_size, all raw-quote amounts become:
  raw_quote = price × quantity × quote_lot_size

Choosing base_lot_size = 10^max(d_base - d_quote, 0) and
        quote_lot_size = 10^max(d_quote - d_base, 0)
makes price the human-readable quote/base rate and tick_size=1 one atomic
price increment regardless of which mint has more decimals.

NVDAx (8 dec) / USDC (6 dec): base_lot_size=100, quote_lot_size=1 (values unchanged)
WBTC  (8 dec) / HD-USDC (18 dec): base_lot_size=1, quote_lot_size=10^10

Changes: InvalidQuoteLotSize error; quote_lot_size field on Market; new
param in initialize_market; bid lock, gross_quote, and locked_for_this_fill
in place_order all gain x quote_lot_size; bid cancel refund in cancel_order
gains x quote_lot_size; settle_funds untouched (already operates on raw
amounts in unsettled_*). Tests add QUOTE_LOT_SIZE=1, update all call sites
and assertions, add rejection test for zero quote_lot_size.

https://claude.ai/code/session_01G6iaAjzg8aoFwe8ZWWG9VR
"Balanced-by-construction" lumped critbit in with red-black/AVL trees,
which it isn't — critbit never rotates or recolours. Reframe the §8
heading and prose around the accurate property: a radix trie whose depth
is bounded by the key's bit width (<=128), so it cannot degenerate under
adversarial insert order. The security conclusion is unchanged.

https://claude.ai/code/session_01G6iaAjzg8aoFwe8ZWWG9VR
Resolves the PR #66 conflict caused by main renaming defi/ -> finance/.
Git rename detection carried the order-book changes (base_lot_size,
quote_lot_size, README critbit wording) into finance/order-book/ cleanly;
verified cargo check passes at the new path.

https://claude.ai/code/session_01G6iaAjzg8aoFwe8ZWWG9VR
@mikemaccana mikemaccana changed the title fix(defi/order-book): correct NVDAx/USDC price resolution with base_lot_size fix(finance/order-book): decimal-agnostic pricing with base_lot_size + quote_lot_size Jun 9, 2026
@mikemaccana mikemaccana merged commit 5ac9d2d into main Jun 10, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants