Skip to content

feat(order): add realized quote and fee onto OrderRecord#171

Merged
gregorydemay merged 40 commits into
mainfrom
gdemay/DEFI-2901-order-level-scalars
Jun 29, 2026
Merged

feat(order): add realized quote and fee onto OrderRecord#171
gregorydemay merged 40 commits into
mainfrom
gdemay/DEFI-2901-order-level-scalars

Conversation

@gregorydemay

@gregorydemay gregorydemay commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Purpose

get_my_orders exposes the original limit price and cumulative base filled_quantity, but nothing about the price(s) actually traded — so the realized notional, the volume-weighted average price (VWAP), and the fees paid cannot be recovered from an order. This adds the first, order-level layer of that missing data: two cumulative scalars folded into the write that already updates filled_quantity.

Requirements coverage

  • R1filled_quote accumulates the realized quote notional per fill at the maker price.
  • R2filled_fee accumulates the realized fee in the order's receive token (the amount withheld).
  • R6 (order-level) — both scalars are exposed on OrderRecord through get_my_orders / get_my_order.
  • R7 — the scalars are folded into the same per-order write as filled_quantity and only under the write gate, so replay does not double-count.
  • R9 — every accumulation uses an always-on overflow trap.
  • R11 — per-fill notional, fees, and roles are computed once and feed both the balance operations and the per-order deltas, so the two can never diverge.

Performance impact

Measured with canbench (just bench-check); deltas are versus the pre-feature baseline.

  • matching scope: ~+10–14% from the inherent per-fill u256 notional and fee rollup.
  • per-fill qty rollup: ~+28–35%, dominated by the u256 Quantity arithmetic the realized notional/fee require (this is intrinsic to the feature, not allocation overhead).
  • order_history::apply_update: ~+3–6% for writing the two extra scalar fields.
  • The settlement path makes a single pass over the fills — no intermediate Vec<FillSettlement> is materialized — so the residual cost is the per-fill u256 rollup itself, not allocation.
  • The whole settlement (per-fill rollup, balance-op construction, per-order writes) now runs only under the write gate, so post-upgrade replay does none of this work; the committed canbench_results.yml is unchanged by that gating (just bench-check reports no change versus the committed baseline).
  • The former status bench scope is renamed apply_order_updates (it wraps the whole per-order apply-update), so its baseline key/value changed accordingly.

📚 PR stack

  1. feat(order): add realized quote and fee onto OrderRecord #171 — Order-level scalars (this PR) — base main.
  2. feat(order): persist per-fill records in stable memory (3/5) #179 — Fill store (stable-memory persistence) — base feat(order): add realized quote and fee onto OrderRecord #171.
  3. feat(order): expose get_my_trades ByOrder feed (4/5) #186 — Per-order get_my_trades { ByOrder } feed — base feat(order): persist per-fill records in stable memory (3/5) #179.
  4. feat(order): expose the account-wide get_my_trades ByAccount filter (5/5) #180 — Account-wide ByAccount filter — base feat(order): expose get_my_trades ByOrder feed (4/5) #186.

🤖 Generated with Claude Code

Add `filled_quote` and `filled_fee` to the order record so `get_my_orders`
exposes per-order realized notional and fee summaries (DEFI-2901, PR 1 of 3).

`filled_quote` is the cumulative realized quote notional
`Σ (maker_price × quantity / base_scale)`; a buy taker's released reservation
surplus is excluded, so VWAP is derivable as
`filled_quote × base_scale / filled_quantity`. `filled_fee` is the cumulative
realized fee in the order's receive token (base for a buy, quote for a sell),
the amount actually withheld.

`OrderUpdate` gains matching `quote_delta` / `fee_delta`, folded into the same
single read-modify-write as `filled_quantity` and `status`, each guarded by an
always-on overflow trap. Settlement computes the per-fill notional, fees, and
maker/taker roles once and feeds both the balance operations and the per-order
deltas from that single source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 23, 2026 10:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the order persistence and API surface so an order can report realized (executed) economics, not just limit price and filled base quantity. It adds cumulative per-order scalars for realized quote notional and realized fees, computed once per fill during settlement and folded into the existing per-order read/modify/write update path.

Changes:

  • Add filled_quote (realized quote notional) and filled_fee (realized fee in receive token) to OrderRecord across internal types, public types, and the hand-written .did.
  • Refactor matching settlement to compute per-fill realized values once (fill_settlements) and reuse them for both balance operations and per-order scalar accumulation.
  • Update fixtures and tests (including new worked-example tests) to validate rollups, overflow trapping, and replay behavior.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
libs/types/src/lib.rs Extends public OrderRecord with filled_quote/filled_fee fields and docs.
integration_tests/tests/tests.rs Updates integration expectations for canceled partially-filled orders to include realized scalars.
canister/src/tests.rs Updates canister unit tests’ expected OrderRecord values with new fields.
canister/src/test_fixtures/mod.rs Extends arbitrary OrderRecord generator with populated filled_quote/filled_fee.
canister/src/state/tests.rs Updates existing settlement tests and adds worked-example tests for realized scalars.
canister/src/state/mod.rs Introduces FillSettlement, computes settlements once, accrues per-order deltas, and builds balance ops from settlements.
canister/src/order/history/tests.rs Adds/updates tests to cover accumulation and overflow trapping for new scalars.
canister/src/order/history/mod.rs Adds filled_quote/filled_fee to the stable-minicbor OrderRecord and extends OrderUpdate to carry new deltas.
canister/oisy_trade.did Evolves the Candid OrderRecord type to include the new fields.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread canister/src/order/history/mod.rs
Comment thread canister/src/order/history/mod.rs
Comment thread canister/src/state/mod.rs Outdated
@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

canbench 🏋 (dir: canister) 9b11b45 2026-06-29 08:19:17 UTC

canister/canbench_results.yml is up to date
📦 canbench_results_benchmark.csv available in artifacts

---------------------------------------------------

Summary:
  instructions:
    status:   Regressions detected 🔴
    counts:   [total 14 | regressed 2 | improved 0 | new 0 | unchanged 12]
    change:   [max +19.49M | p75 +6.56M | median +199.94K | p25 +3.65K | min -2.34K]
    change %: [max +2.07% | p75 +1.50% | median +0.70% | p25 +0.25% | min -0.05%]

  heap_increase:
    status:   No significant changes 👍
    counts:   [total 14 | regressed 0 | improved 0 | new 0 | unchanged 14]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

  stable_memory_increase:
    status:   No significant changes 👍
    counts:   [total 14 | regressed 0 | improved 0 | new 0 | unchanged 14]
    change:   [max 0 | p75 0 | median 0 | p25 0 | min 0]
    change %: [max 0.00% | p75 0.00% | median 0.00% | p25 0.00% | min 0.00%]

---------------------------------------------------

Only significant changes:
| status | name                                                                     |  calls |     ins |   ins Δ% | HI |  HI Δ% | SMI |  SMI Δ% |
|--------|--------------------------------------------------------------------------|--------|---------|----------|----|--------|-----|---------|
|   +    | bench_fok_fill_full_bid_side::qty::add                                   |  6.97K |   2.46M | +100.00% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_gtc_fill_full_bid_side::qty::add                                   |  6.97K |   2.46M | +100.00% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000::qty::add                              |  8.11K |   2.86M |  +90.48% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000_with_fees::qty::add                    | 11.96K |   4.22M |  +63.09% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_fok_fill_full_bid_side::qty                                        | 13.24K |  25.14M |  +39.06% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_gtc_fill_full_bid_side::qty                                        | 13.24K |  25.14M |  +39.06% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000::qty                                   | 17.18K |  34.84M |  +30.72% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000_with_fees::qty                         | 24.10K |  49.52M |  +27.72% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000::bal::deposit                          |  1.54K |   4.83M |  +26.25% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000::qty::mul_u128                         |  1.17K |   5.13M |  +19.98% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000_with_fees::qty::mul_u128               |  1.17K |   4.88M |  +15.74% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000_with_fees::bal::unreserve              |    405 |   2.44M |  +12.47% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000::bal::unreserve                        |    405 |   2.44M |  +12.47% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000_with_fees::matching                    |      1 | 166.73M |  +12.20% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_gtc_fill_full_bid_side::matching                                   |      1 |  98.15M |  +12.18% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_fok_fill_full_bid_side::matching                                   |      1 |  98.15M |  +12.18% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000::bal                                   |  3.48K |  17.86M |  +11.95% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000_with_fees::bal::debit_reserved         |  1.54K |   4.88M |  +11.76% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000::matching                              |      1 | 149.32M |  +10.18% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000_with_fees::bal                         |  3.48K |  17.17M |   +5.38% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000_with_fees::order_history::apply_update |  1.01K | 101.99M |   +5.28% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_fok_fill_full_bid_side::order_history::apply_update                |    698 |  68.18M |   +5.20% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_gtc_fill_full_bid_side::order_history::apply_update                |    698 |  68.18M |   +5.20% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_fok_killed_full_bid_side::bal::unreserve                           |      1 |   7.08K |   +4.10% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_fok_killed_full_bid_side::bal                                      |      1 |   8.64K |   +4.01% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000::bal::debit_reserved                   |  1.54K |   4.88M |   +3.94% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_fok_fill_full_bid_side::order_history                              |  1.40K |  95.55M |   +3.50% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_gtc_fill_full_bid_side::order_history                              |  1.40K |  95.55M |   +3.50% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_fok_killed_full_bid_side::order_history::get                       |      1 |  43.45K |   +3.47% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000_with_fees::order_history               |  1.78K | 133.93M |   +3.30% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000::order_history::apply_update           |  1.01K |  99.78M |   +3.00% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_get_my_orders                                                      |        |  53.74M |   +2.07% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000_no_fills::matching                     |      1 |  96.30M |   +2.04% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_process_pending_orders_1000_no_fills                               |        |  96.85M |   +2.03% |  0 |  0.00% |   0 |   0.00% |
|   +    | bench_fok_killed_full_bid_side::order_history                            |      2 | 165.28K |   +2.01% |  0 |  0.00% |   0 |   0.00% |
|   -    | bench_fok_fill_full_bid_side::bal                                        |  2.79K |  12.30M |   -6.16% |  0 |  0.00% |   0 |   0.00% |
|   -    | bench_gtc_fill_full_bid_side::bal                                        |  2.79K |  12.30M |   -6.16% |  0 |  0.00% |   0 |   0.00% |
|   -    | bench_fok_fill_full_bid_side::bal::debit_reserved                        |  1.39K |   4.46M |   -6.49% |  0 |  0.00% |   0 |   0.00% |
|   -    | bench_gtc_fill_full_bid_side::bal::debit_reserved                        |  1.39K |   4.46M |   -6.49% |  0 |  0.00% |   0 |   0.00% |
|   -    | bench_process_pending_orders_1000_with_fees::bal::deposit                |  1.54K |   4.18M |   -6.85% |  0 |  0.00% |   0 |   0.00% |
|   -    | bench_fok_fill_full_bid_side::bal::deposit                               |  1.39K |   3.58M |  -14.18% |  0 |  0.00% |   0 |   0.00% |
|   -    | bench_gtc_fill_full_bid_side::bal::deposit                               |  1.39K |   3.58M |  -14.18% |  0 |  0.00% |   0 |   0.00% |
|  new   | bench_fok_fill_full_bid_side::apply_order_updates                        |      1 |  71.24M |          |  0 |        |   0 |         |
|  new   | bench_fok_killed_full_bid_side::apply_order_updates                      |      1 | 123.48K |          |  0 |        |   0 |         |
|  new   | bench_gtc_fill_full_bid_side::apply_order_updates                        |      1 |  71.24M |          |  0 |        |   0 |         |
|  new   | bench_process_pending_orders_1000::apply_order_updates                   |      1 | 104.42M |          |  0 |        |   0 |         |
|  new   | bench_process_pending_orders_1000_no_fills::apply_order_updates          |      1 |  85.79M |          |  0 |        |   0 |         |
|  new   | bench_process_pending_orders_1000_with_fees::apply_order_updates         |      1 | 106.66M |          |  0 |        |   0 |         |

ins = instructions, HI = heap_increase, SMI = stable_memory_increase, Δ% = percent change

---------------------------------------------------
CSV results saved to canbench_results.csv

Comment thread canister/src/state/tests.rs Outdated
@gregorydemay

Copy link
Copy Markdown
Contributor Author

🤖 Automated review — PR 1 of the DEFI-2901 delivery (order-level scalars), against the spec and the team review rules.

(Posted as a comment because GitHub won't let me file a formal request-changes review on a PR owned by the same account.)

Verdict

The code is substantively sound and scoped correctly. The only thing gating READY is CI: lint, unit-tests, benchmark, and reproducible-build are all still pending. Per our rules, pending CI is a hard block on approval. Holding until CI goes green — absent new commits this flips to a pass (only a 🔵 nit remains).

Severity tally: 🔴 0 · 🟠 0 · 🔵 1 (inline, comment/fixture naming).

What I verified locally (all green)

  • cargo fmt --all --check — clean.
  • cargo clippy --locked --tests --workspace -D warnings — clean.
  • cargo test -p oisy_trade_canister — 387 passed.
  • Candid backward-compat: check_candid_interface_compatibility (service_equal vs. the committed oisy_trade.did) passes; the wasm builds and embeds the .did cleanly. Adding filled_quote/filled_fee to a returned record is a backward-compatible evolution. ✅
  • cargo test -p oisy_trade_int_tests could not run here (needs the downloaded ledger wasm fixture, an env artifact, not a code issue). PR-1 acceptance does not require new integration tests — the single cancel_limit_order edit just keeps the existing assertion compiling. Fine.
  • Coverage by mutation (reverted): zeroing quote_delta application, mapping notional→quantity, and swapping taker/maker fees each make the worked-example state tests and the apply history tests fail. The assertions can catch a regression. ✅

Acceptance criteria

  • R1/R6 (order-level)filled_quote accumulates notional = maker_price × quantity / base_scale; buy-taker surplus is the Unreserve op and is excluded. Worked-example test reproduces 53 ckUSDT / VWAP 10.6 with the 7-ckUSDT surplus released. ✅
  • R2filled_fee is the realized withheld amount in the receive token (base for buy, quote for sell), summed, never re-derived from a rate. Maker/taker per-fill rates pinned by the cross-on-entry-then-rest test. ✅
  • R7quote_delta/fee_delta fold into the same single OrderUpdate read-modify-write as filled_delta/status; no-op still writes nothing. ✅
  • R9 — both scalars monotonic via checked_add + always-on .expect("BUG: …") (not debug_assert!); dedicated trap tests. ✅
  • R11fill_settlements computes notional/fee/role once into FillSettlement, feeding both compute_balance_operations and the OrderUpdate deltas. The taker_fee/maker_fee ↔ quote_fee/base_fee round-trip in both fns is consistent for Buy and Sell, so the balance ops are preserved (the existing fuzz proptest confirms the op shape). ✅

Maintainability sweep

  • Duplication: none found — the refactor de-duplicates fee/notional computation into fill_settlements rather than adding any.
  • Unused derives: none — OrderUpdate's existing derives (Default, PartialEq/Eq) are all exercised; the new fields add no capability.
  • Primitive-obsession / ambiguous params: accrue_fill(update, quantity, notional, fee) takes three same-typed Quantity positional args. Mildly swap-prone, but Quantity is already the domain newtype for all three and the helper is internal with a clear doc; not worth a struct. Cleared (below nit threshold).
  • Divergent invariant handling: consistent — filled_delta/quote_delta/fee_delta all use checked_add + BUG: trap identically.
  • Silent fallbacks: none — no unwrap_or_default/ok()/NaN/let _ = introduced; the only debug_assert mention is a test comment explaining why the trap is always-on.
  • arb_ fuzzing*: arb_order_record derives filled_quote/filled_fee from the fuzzed filled_lots/price_ticks (not hard-coded sentinels), and the storable roundtrip proptest exercises the new minicbor indices #[n(9)]/#[n(10)]. ✅

Scope

Correctly limited to order-level scalars — no FillStore/Trade/get_my_trades/new MemoryIds/canbench pulled in from PRs 2–3.

VERDICT: CHANGES_REQUESTED

gregorydemay and others added 3 commits June 23, 2026 10:34
…USDT

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`record_matching_event` materialized every fill's settlement into a
`Vec<FillSettlement>` and then iterated it twice — once to accrue the
per-order scalar deltas and once to build the balance operations. The
struct carries several u256-backed `Quantity` fields, so the allocation,
store, and reload were paid for on the matching hot path for every fill.

Fold the compute and both consumers into one pass over `output.fills`:
each fill's notional, fees, and roles are computed exactly once and feed
both the balance operations (always) and the per-order deltas (only under
`StableMemoryOptions::Write`), preserving the R11 single-source guarantee
without the intermediate `Vec`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Regenerate `canbench_results.yml` to capture the expected cost of the
`filled_quote`/`filled_fee` order-level rollup. The remaining increase on
`process_pending_orders` (matching ~+10-14%, the per-fill `qty` rollup
+28-35%, `order_history::apply_update` +3-6%) is the inherent per-fill
u256 notional/fee arithmetic over up to 1000 fills, which the single-pass
settlement already keeps to one computation per fill.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 23, 2026 10:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Comment thread canister/src/test_fixtures/mod.rs Outdated
Comment thread canister/src/state/tests.rs Outdated
gregorydemay and others added 2 commits June 23, 2026 11:02
Compute the proptest generator's `filled_quote` the way the engine does
(`maker_price × filled_quantity / base_scale`, cf. `Fill::quote_amount`),
so generated `OrderRecord`s are semantically consistent with realized
quote notional instead of inflated by `base_scale`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Register the new ICP (8-dec) / ckUSDT (6-dec) fixture in the
`realized_scalars` worked-example test and restore the ckUSDT labels, so
the smallest-unit prices/fees literally mean ckUSDT amounts as in the
DEFI-2901 spec. Rewrite the module doc accordingly; base stays ICP so
`base_scale = 10^8` and every stored integer is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@gregorydemay gregorydemay left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🤖 Automated re-review — PR 1 of the DEFI-2901 delivery (order-level scalars). Follow-up to my prior CHANGES_REQUESTED (gated on pending CI + one nit).

Verdict

All prior gates cleared. CI is green and every open thread is resolved with new commits. Ready for your review.

Severity tally: 🔴 0 · 🟠 0 · 🔵 0.

Re-verification of what changed since last pass

  • Single-pass settlement (R11). record_matching_event now iterates output.fills once: each fill's realized values are computed exactly once via fill_settlement(...), feeding push_balance_operations(...) (always) and the per-order accrue_fill(...) deltas (only under StableMemoryOptions::Write). The write gate is intact — both the accrue_fill calls and the updates-map application sit inside if write. The single-source guarantee holds: there is one FillSettlement per fill and both consumers read it. The compute_balance_operations shape proptest was updated to drive the same fill_settlement + push_balance_operations path, so the op-shape coverage is preserved.
  • arb_order_record (Copilot #2). filled_quote is now derived as the engine does — price.checked_mul_quantity_scaled(&filled_quantity, base_scale=10^8) (cf. Fill::quote_amount) — no longer inflated by base_scale. Both new minicbor fields (#[n(9)]/#[n(10)]) are fuzzed (not hard-coded sentinels) and exercised by seq_order_record_roundtrips_through_storable.
  • Worked-example test (my nit + Copilot #3). realized_scalars is now a literal ICP/ckUSDT (8/6) instance via icp_ckusdt_trading_pair() / ckusdt_metadata(); the misleading "decimals irrelevant" doc is gone and the ckUSDT labels are correct. Stored smallest-unit assertions are unchanged (base stays ICP, base_scale = 10^8).
  • CBOR #[cbor(default)] (Copilot #1). Correctly declined: consistent with the module's documented pre-launch schema policy and the sibling non-Option fields filled_quantity (n(6)) / time_in_force (n(8)); backfilling pre-existing orders is a spec non-goal.

Local verification (all green)

  • cargo test -p oisy_trade_canister — 353 passed; check_candid_interface_compatibility passes (two trailing nat fields on a returned record is a backward-compatible Candid evolution).
  • gh pr checks 171 — all green; canbench reports canbench_results.yml up to date.
  • Coverage by mutation (reverted): mapping quote_delta's notional → quantity, and swapping taker_fee ↔ maker_fee in accrue_fill, each fail the worked-example state tests. The assertions pin the realized notional and which fee accrues to which leg.

Canbench cost assessment

The baseline refresh is appropriate for PR-1 and the implementer's characterization is accurate. fill_settlement computes the same notional/quote_fee/base_fee the old compute_balance_operations did — moved, not duplicated. The residual qty-scope growth (+28-35%) is the genuinely-new per-fill u256 quote_delta/fee_delta rollup that R1/R2 require — inherent to the feature, not avoidable without dropping it. R12's dedicated with/without-persistence benchmark lands in PR-2.

Acceptance criteria

  • R1/R6 (order-level)filled_quote = Σ maker_price × quantity / base_scale; buy-taker surplus stays the Unreserve op, excluded. Worked example: 53 ckUSDT, VWAP 10.6, 7-ckUSDT surplus released. ✅
  • R2filled_fee is the realized withheld amount in the receive token (base buy / quote sell), summed, never rate-derived; per-leg rates pinned by the cross-then-rest test. ✅
  • R7quote_delta/fee_delta fold into the same single OrderUpdate read-modify-write; no-op still writes nothing. ✅
  • R9 — both scalars monotonic via checked_add + always-on .expect("BUG: …"); dedicated #[should_panic] trap tests (not debug_assert!). ✅
  • R11 — one FillSettlement per fill feeds both balance ops and order deltas. ✅

Maintainability sweep

  • Duplication: none — the refactor de-duplicates fee/notional into fill_settlement.
  • Unused derives: none — OrderUpdate's Default/PartialEq/Eq all exercised; new fields add no capability.
  • Primitive-obsession: accrue_fill(update, quantity, notional, fee) takes three Quantity positional args; all are the domain newtype, internal helper with a clear doc — below the nit threshold. Cleared.
  • Divergent invariant handling: consistent — filled_delta/quote_delta/fee_delta all use checked_add + BUG: trap identically.
  • Silent fallbacks: none introduced — no unwrap_or_default/ok()/NaN/let _ =; the surplus path traps with a BUG: message.

Scope

Correctly limited to order-level scalars — no FillStore/Trade/get_my_trades/new MemoryIds/canbench micro-bench pulled in from PRs 2-3.

VERDICT: READY

@gregorydemay

Copy link
Copy Markdown
Contributor Author

🤖 This PR is ready for your review.

The spec-driven build loop has completed for DEFI-2901 PR 1 (order-level scalars):

  • Reviewer verdict: READY (0 blocking/medium/nit findings).
  • CI: green across lint, unit-tests, integration-tests, benchmark, reproducible-build, and candid checks.
  • All review comments (reviewer + Copilot) addressed with new commits and replies.

Acceptance criteria R1, R2, R6 (order-level), R7, R9, R11 are met. Scope is limited to PR 1 — the fill store, Trade/get_my_trades, and the R12 canbench benchmark remain for PRs 2 and 3.

Note for your review: the order-level rollup adds ~+10–14% on the matching scope and ~+28–35% on the per-fill qty scope in canbench. The intermediate Vec was removed (single-pass settlement); the residual is the inherent per-fill u256 Quantity rollup that filled_quote/filled_fee require. The baseline was refreshed to reflect it. This is larger than the spec's "≈ two u128 adds" framing suggested, so worth a look before merge.

Left as a draft — marking ready, approval, and merge are yours.

@gregorydemay gregorydemay left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Preliminary Review

Comment thread canister/oisy_trade.did Outdated
Comment thread integration_tests/tests/tests.rs Outdated
Comment thread canister/canbench_results.yml Outdated
Comment thread canister/src/state/mod.rs Outdated
Comment thread canister/src/state/mod.rs Outdated
Comment thread canister/src/state/mod.rs Outdated
Comment thread canister/src/state/tests.rs Outdated
gregorydemay and others added 5 commits June 23, 2026 15:22
Address review comment 3460414419: drop the base_scale formula from the
filled_quote doc (it is plainly cumulative quote in quote-smallest units),
spell out VWAP as volume-weighted average price, and remove the confusing
reservation-surplus sentence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…me scope

Address review comments 3460598457, 3460594366, and 3460487183 on the
settlement path in record_matching_event:

- FillSettlement now borrows the Fill it derives from and stores only the
  extra computed values (notional, taker/maker fee, surplus) instead of
  duplicating the Fill's seqs, side, and quantity.
- Consolidate the per-order apply-update under a single write gate; the cheap
  delta accumulation runs in the single fills pass and only the stable-memory
  writes are gated, so replay still does not double-count.
- Rename the stale bench scope status to apply_order_updates, reflecting the
  whole per-order apply-update it wraps (apply_update keeps its own inner
  scope).
- Drop the R# spec-requirement tags from code comments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address review comment 3460709715: remove the standalone realized_scalars
module name and assert filled_quote/filled_fee from existing tests where they
extend naturally — fill_deducts_fees_on_both_sides now checks each side's
roll-up (catching a notional/quantity confusion and a taker/maker fee swap),
and should_write_once_for_taker_spanning_multiple_fills asserts the summed
filled_quote across distinct maker prices. The multi-level-sweep worked
example (with surplus exclusion and VWAP) and the both-ways single-batch case
move into the fees module as the only dedicated tests, since no existing test
covers them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address review comment 3460438909: register the trading pair with non-zero
maker/taker fee rates so the partially filled buy accrues a non-trivial
filled_fee (the maker-rate base fee) alongside a consistent filled_quote, and
assert the withheld fee on the buyer's settled base balance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The status bench scope is renamed to apply_order_updates and the
FillSettlement rework changes the settlement path, so the persisted baseline
keys/values shift accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 23, 2026 15:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

@gregorydemay gregorydemay left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🤖 Automated re-review — PR 1 of the DEFI-2901 delivery (order-level scalars). Follow-up to the prior READY verdict, re-verifying the seven addressed comments and the origin/main merge.

Verdict

All prior gates remain cleared, CI is green, and every one of the changes since the last pass holds up under independent verification. Ready for your review.

Severity tally: 🔴 0 · 🟠 0 · 🔵 0.

Re-verification of the changes since last pass

  1. Candid docs simplifiedoisy_trade.did filled_quote/filled_fee now describe smallest-unit semantics + VWAP in prose, no base_scale formula, no reservation-surplus sentence. (The libs/types Rust doc keeps the R1 formula — that is the public Rust surface, not the candid, so the simplification request is satisfied.)
  2. Integration test exercises feesshould_cancel_partially_filled_buy_and_refund_residual now registers 10/23 bps maker/taker, asserts non-zero filled_fee = 1_000, consistent filled_quote, and the withheld base fee reflected in the post-cancel free balance (999_000).
  3. PR description — Performance-impact and Requirements-coverage sections present.
  4. R# tags removed — code comments carry no requirement tags; expect("BUG: …") strings retained.
  5. Single if write + scope rename — confirmed sound. The per-fill fill_settlement + accrue_fill loop runs once over output.fills; the updates map and balance_operations are built unconditionally, but apply_update (the only stable-memory write) stays inside if write. The Skip-path cost is just three checked_adds per fill into a discarded map — balance_operations was already built unconditionally before this PR (old compute_balance_operations sat outside the gate), so the only new replay-path work is negligible and cannot double-count. Acceptable as-is; re-gating would buy nothing measurable. The statusapply_order_updates bench scope rename is reflected in canbench_results.yml.
  6. FillSettlement borrows Fillfill: &'a Fill plus the four derived fields (notional, taker_fee, maker_fee, surplus); no field duplication. All four are read by push_balance_operations/accrue_fill. R11 single-source holds: one FillSettlement per fill feeds both the balance ops and the per-order deltas.
  7. realized_scalars module removed — coverage folded into fees::*: the parameterized fill_deducts_fees_on_both_sides now also asserts each side's filled_quote/filled_fee (buyer base-fee, seller quote-fee, never swapped), plus the two kept dedicated tests (multi-level sweep worked example; both-ways single batch).

The origin/main merge is clean; the effective diff vs main is exactly the PR-1 work (10 files) — no FillStore/Trade/get_my_trades/new MemoryIds pulled in.

Coverage by evidence (mutation, reverted)

  • accrue_fill notional → quantity: caught by 4 fees::* tests (worked-example filled_quote 53M vs the quantity sum).
  • accrue_fill taker_fee ↔ maker_fee swap: caught by the same 4 tests.
    Both mutation guarantees the task called out are real.

Local verification

  • cargo test -p oisy_trade_canister — 386 passed, 0 failed; check_candid_interface_compatibility passes (two trailing nat on a returned record is a backward-compatible candid evolution).
  • gh pr checks 171 — all green (incl. candid-backward-compat, benchmark, integration-tests).

Acceptance criteria

  • R1 / R6 (order-level)filled_quote = Σ maker_price × quantity / base_scale; buy-taker surplus stays the Unreserve op, excluded. Worked example: 53 ckUSDT, VWAP 10.6, 7-ckUSDT surplus released. ✅
  • R2filled_fee realized withheld amount in receive token (base buy / quote sell), summed, never rate-derived. ✅
  • R7quote_delta/fee_delta fold into the same single OrderUpdate::apply read-modify-write; no-op writes nothing. ✅
  • R9 — both scalars monotonic via checked_add + always-on BUG: trap; dedicated #[should_panic] tests, not debug_assert!. ✅
  • R11 — one FillSettlement per fill feeds balance ops and order deltas. ✅

Maintainability sweep

  • Duplication: none — fill_settlement de-duplicates the fee/notional computation; the proptest drives the same path.
  • Unused derives: none — OrderUpdate's Default/Copy/PartialEq/Eq all exercised; FillSettlement has no derives, every field read.
  • Primitive obsession: accrue_fill(update, quantity, notional, fee) takes three Quantity newtypes (the domain primitive), internal helper with a clear doc — below the nit threshold. Cleared.
  • Divergent invariant handling: consistent — filled_delta/quote_delta/fee_delta all use checked_add + BUG: trap identically.
  • Silent fallbacks: none introduced — no unwrap_or_default/ok()/NaN/let _ =; the surplus path traps with a BUG: message.

Note (non-blocking)

The 11 review threads (Copilot + author) each carry a substantive reply tied to a commit, but several are not yet marked "Resolved" in the GitHub UI. The content is fully addressed; flagging only so the threads can be closed out before merge.

VERDICT: READY

@gregorydemay

Copy link
Copy Markdown
Contributor Author

🤖 Ready for your review again — your 7 review comments are all addressed and CI is green.

  • Changes: simplified .did field docs; integration test now exercises non-zero fees; R# tags removed from code (requirement mapping moved to the PR description, which also gained a Performance-impact section); single if write + statusapply_order_updates bench scope; FillSettlement now borrows Fill; the realized_scalars module was dissolved into the existing fee tests.
  • Merged origin/main to clear the new candid-backward-compat gate (the breaking get_balances change came from feat(types)!: disposition-tagged errors for query endpoints (2/3) #168, not this PR; the gate's baseline is the merge-base, so pulling main in resolves it). Merge is clean.
  • Reviewer verdict: READY (0 blocking/medium/nit). Mutation-checked that the notional and maker/taker-fee guarantees are still caught after the test reorg.

One small thing for your judgment (flagged on the relevant thread): to satisfy "single if write", the cheap per-fill delta accumulation now also runs on the Skip replay path (the updates map is built then discarded; the actual stable-memory write stays gated, so no double-count).

Left as a draft — marking ready, approval, and merge are yours.

…r-level-scalars

# Conflicts:
#	canister/src/state/mod.rs

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Comment thread canister/src/state/mod.rs Outdated
…play

Move the refund settlement of a canceled limit order -- the
`RemovedOrderSettlement` balance-operation production and the
`pending_settling_events` push -- inside the `StableMemoryOptions::Write`
branch of `record_cancel_limit_order`, alongside the already write-gated
`order_history` status apply. Only the in-memory `book.remove_order(seq)`
mutation stays outside the gate, since the state rebuild on replay needs it.

This mirrors the matching-side fix in c2e8354. Behavior is identical: under
Skip (post-upgrade replay) the SettlingEvent was either no-op'd
(`record_settling_event` returns early under Skip) or discarded
(`replay_events` clears the whole `pending_settling_events` queue at the end);
under Write nothing changes. The net effect is skipping wasted refund
computation and queue churn on replay.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@gregorydemay gregorydemay left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🧐 Re-review at head 772edb2 — Review passed, no blockers/mediums, CI green. Ready for human approval.

Re-verified the five commits landed since the prior READY; acceptance (R1, R2, R6 order-level, R7, R9, R11) still holds.

  • f676083 (test-only): completed the second worked-example table row ("taker on entry then maker within one batch") so all three orders now carry Expects. Arithmetic checks out per the per-fill role: SELLER maker fee 10_000, BUYER 200_000 (taker leg) + 150_000 (maker leg), MAKER_B taker-leg quote fee 30_000. Strengthens coverage.
  • d1d23b4 (test-only): three identical local record_of copies collapsed into one generic test_fixtures::record_of; 15 call sites routed to it; one definition remains. Same lookup, pure dedup, no coverage change.
  • f0e21a0 (test-only): three fill-result sites now use assert_eq_ignoring_timestamp(&record, &OrderRecord{..}) — a whole-record compare that pins MORE fields than the prior per-field asserts (owner/side/price/quantity/time_in_force) and normalizes only the two timestamp fields, so the created_at/last_updated_at values pulled from actual are not tautological. The replay-under-Skip site keeps its separate last_updated_at == None assertion (the one field the helper ignores).
  • 933e131 (test-only): worked-example loop swapped to mock_runtime_for_timer(); correct, since timer-driven matching reads time() but no msg_caller() — a stronger guard (mockall would panic on an unexpected caller read).
  • 772edb2 (only production change): record_cancel_limit_order now gates the RemovedOrderSettlement refund + pending_settling_events push under Write, mirroring the matching-side fix in c2e8354; only book.remove_order(seq) stays unconditional. base_scale_for_book is a pure read computed before the gate in both old and new code, so behavior on the Write path is identical and replay just skips wasted work. Verified by mutation: dropping the moved push fails 9 tests on BOTH the Write path (balance-refund assert_balance) and the replay path (assert_replay_matches / should_replay_cancel_*); reverted the mutation.

Maintainability sweep on the changed lines — duplication: none introduced (commits net-remove it); unused derives: none (no new types); primitive-obsession params: none (no new signatures, record_of is typed); divergent invariant handling: none — the cancel gate now CONVERGES with the matching-side gate rather than diverging; silent fallbacks: none — the moved code keeps only always-on expect("BUG: ...") traps.

Evidence: cargo test -p oisy_trade_canister --lib 395/395 pass (incl. cancel_limit_order::*, should_replay_cancel_*, replay_under_skip_does_not_write_history, order::history R7/R9 apply+overflow traps, the worked-example table). No .did / libs/types / canbench_results.yml changes (Write path identical) — consistent with green candid-backward-compat and benchmark. gh pr checks 171: all green, none pending.

VERDICT: READY

@gregorydemay

Copy link
Copy Markdown
Contributor Author

🤖 Ready for your review again — reviewer READY, CI 12/12 green, MERGEABLE, all threads answered.

Since the last pass: addressed your 5 test nits (record_oftest_fixtures, assert_eq_ignoring_timestamp ×3, mock_runtime_for_timer) and the Copilot nit on the cancel path — record_cancel_limit_order now gates its refund/SettlingEvent push under the write gate, mirroring the matching-side fix. All behavior-preserving (Write-path canbench unchanged; replay/cancel tests pass).

Left as a draft — approval/merge are yours.

Comment thread canister/src/test_fixtures/mod.rs
Comment thread integration_tests/tests/tests.rs Outdated
@gregorydemay gregorydemay marked this pull request as ready for review June 26, 2026 07:22
@gregorydemay gregorydemay requested a review from a team as a code owner June 26, 2026 07:22
Copilot AI review requested due to automatic review settings June 26, 2026 07:22
@zeropath-ai

zeropath-ai Bot commented Jun 26, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to 0b2fb0b.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► canbench_results.yml
    Update benchmark results with new instruction counts
► oisy_trade.did
    Add filled_quote and filled_fee fields to OrderRecord
► src/order/book.rs
    Remove redundant Fill struct definition
► src/order/fill.rs
    Introduce Fill struct for fill settlement logic
    Implement FillSettlement for computing realized values from fills
    Implement RemovedOrderSettlement for releasing reservations
► src/order/history/mod.rs
    Add filled_quote and filled_fee to OrderRecord
    Update OrderUpdate to include quote and fee deltas
    Modify OrderUpdate::apply to handle quote and fee updates
► src/order/history/tests.rs
    Add tests for accumulating quote and fee in apply_update
    Add tests for overflow panics in apply_update
► src/order/mod.rs
    Export new fill-related structs
► src/state/mod.rs
    Initialize filled_quote and filled_fee for new orders
    Integrate RemovedOrderSettlement for unreserving canceled orders
    Update order processing to account for new fill settlement logic

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

gregorydemay and others added 2 commits June 26, 2026 07:28
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@gregorydemay gregorydemay left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🧐 Re-review at head e28d073 — prior READY verdict holds.

Two commits landed since the last READY, both strictly TEST/FIXTURE-only (no production code):

  • 3fc7aedckbtc_token_id()/ckusdt_token_id()/icp_token_id() in canister/src/test_fixtures/mod.rs now delegate to SupportedTokens::{CKBTC,CKUSDT,ICP}.token_id().into(). Behavior-identical: From<oisy_trade_types::TokenId> is a pure field copy (Self(value.ledger_id)), and SupportedTokens carries the same principals (mxzaz-…, cngnf-…, ryjl3-…). No coverage lost; removes the duplicated principal literals (one source of truth).
  • e28d073 — drops a 3-line redundant comment atop should_cancel_partially_filled_buy_and_refund_residual; code unchanged.

These two commits touch only 4 lines in test_fixtures/mod.rs and delete 3 comment lines in integration_tests/tests/tests.rs. No production code changed since the previously-reviewed head; in-scope criteria (R1, R2, R6 order-level, R7, R9, R11) untouched. PRs 2–3 (fill store / get_my_trades) remain out of scope.

Maintainability sweep on the delta: duplication — improved (principal literals deduplicated), none introduced; unused derives — none (no new types); primitive-obsession params — none (no signatures changed); divergent invariant handling — none; silent fallbacks — none; test-only code in production modules — none (changes are in test_fixtures / tests).

CI: all green at e28d073 (12/12 reported). Unit tests pass locally (353/353). All review threads answered with a reply + commit; the few not-yet-resolved threads await only the human resolve action, not implementer work.

No blockers, no mediums, no new nits. Ready for human approval.

VERDICT: READY

@gregorydemay

Copy link
Copy Markdown
Contributor Author

🤖 Ready for your review again — reviewer READY, CI 12/12 green, MERGEABLE, all threads answered. This round was test/fixture-only: the token-id fixtures now delegate to SupportedTokens::token_id() (3fc7aed, verified behavior-identical — same principals), and a redundant integration-test comment was removed (e28d073). No production code changed. Left a draft — approval/merge are yours.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 26, 2026 10:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

@mbjorkqvist mbjorkqvist left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, thanks @gregorydemay! I couldn't find anything that wasn't already covered in one of the earlier review rounds.

@gregorydemay gregorydemay enabled auto-merge June 29, 2026 08:14
@gregorydemay gregorydemay added this pull request to the merge queue Jun 29, 2026
Merged via the queue into main with commit a8f9593 Jun 29, 2026
16 checks passed
@gregorydemay gregorydemay deleted the gdemay/DEFI-2901-order-level-scalars branch June 29, 2026 08:26
gregorydemay added a commit that referenced this pull request Jun 29, 2026
Bring the fill-store branch current after #171 squash-merged to main:
integrate the realized quote + fee OrderRecord scalars (now in their final
reviewed form on main) with the fill-store additions, keep the redesigned
TradeHistory / FillId spec, and regenerate the canbench baseline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

3 participants