feat(order): expose the account-wide get_my_trades ByAccount filter (5/5)#180
feat(order): expose the account-wide get_my_trades ByAccount filter (5/5)#180gregorydemay wants to merge 167 commits into
Conversation
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>
…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>
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>
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>
…r-level-scalars # Conflicts: # canister/src/state/mod.rs
The merge of origin/main shifted instruction counts within noise (max +0.02%). Persist the refreshed canbench_results.yml so the regression gate stays green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e_operations Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses review comment 3465971111. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses review comment 3465986823: `FillSettlement` now owns the `Fill` (no lifetime parameter), built by consuming `output.fills` in the single settlement pass, with all downstream access routed through `settlement.fill`. Refreshes the canbench baseline for the resulting sub-0.03% instruction drift. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…se_scale) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move `Fill` and `FillSettlement` into a new `canister/src/order/fill.rs`, rewrite `fill_settlement` as the `FillSettlement::new` constructor, and turn `push_balance_operations` and `accrue_fill` into methods on `FillSettlement`. Condense the single-write-per-order comment at the settlement call site. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
🧐 Review — PR 3/3 (Account-wide Substance: clean. No blockers, no mediums, no nits. The incremental work faithfully mirrors the established Verified against the in-scope acceptance criterion (R4) and its sub-points:
Tests: thorough at every layer (unit Blocking reason — CI not green. Tally: 0 🔴 / 0 🟠 / 0 🔵 — CI pending. (Note: GitHub blocks VERDICT: CHANGES_REQUESTED |
|
gregorydemay
left a comment
There was a problem hiding this comment.
🧐 Review passed — no blockers/mediums/nits, CI green. Ready for human approval.
Substance was clean in the prior pass (PR 3/3, account-wide ByAccount filter, R4). CI is now fully green at unchanged head 83ecec1 — all checks pass (lint, unit-tests, integration-tests, reproducible-build, reproducibility-verify, benchmark, candid-backward-compat, and PR-meta checks). This completes the DEFI-2901 three-PR stack.
VERDICT: READY
|
🤖 Ready for your review — reviewer verdict is READY, CI 11/11 green, MERGEABLE. PR 3 of 3 (final) for DEFI-2901 — account-wide Adds the With this, the spec's full requirements set (R1–R12) is delivered across #171 / #179 / #180. Left as a draft — marking ready, approval, and merge are yours. |
…r-level-scalars Integrate the DEFI-2853 Fill-or-Kill matching/settlement work from main with this PR's order-level realized scalars: the settlement loop builds one FillSettlement per fill (feeding both balance operations and per-order scalar deltas) and also unreserves the placement reservation of each killed FOK order. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
🧐 Re-review after the stacked cascade merge ( Merge integration — correct. I diffed the merge head against both parents (
The refund loop sits outside the FOK × fills — no fill written for a killed order. A killed FOK takes the R4 acceptance — holds. Account-wide newest-first across orders via Tests / CI. 431 canister unit tests pass on the merge head; the state-level Maintainability sweep.
No blockers, no mediums, no nits. Merge is behavior-preserving for PR-3 and CI is green. VERDICT: READY |
|
🤖 Updated after the stack cascade (merge 7a0ef75): merged the updated #179 down. |
… tests Add the generic page_by_user, range_primary, and seq-envelope tests (table- driven cases plus paging proptests) and remove the now-redundant account-wide trades_after tests from the trade store. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebuild OrderHistory as a wrapper over the generic History store, retiring the bespoke SeqOrderRecord and UserOrderKey (now a CompositeId via the core) and their hand-written Storable impls. Drops the UserOrderKey/seq-record proptests, covered by the CompositeId and seq-envelope tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…te test Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove three tests now covered elsewhere: the trade per-order length-clamp (covered by the generic range_primary/page_by_user tests), the trade unknown-order empty page (covered by the state get_user_order_trades test), and the order-history insert/get roundtrip (covered by the generic insert/get test and the public-record conversion test). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ry docs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…canister-global Addresses Copilot review comments 3501830909 and 3501830958. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ename insert to insert_once Addresses review comments 3503500443 (reuse the named HistoryGlobalSeq type for the stored insertion sequence instead of a raw u64) and 3503508148 (rename the insert method to insert_once), updating all call sites in the order-history and trade-history wrappers and tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… store Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…to gdemay/DEFI-2901-fill-store # Conflicts: # canister/src/order/mod.rs # canister/src/state/mod.rs
…o gdemay/DEFI-2901-fills-endpoint # Conflicts: # canister/src/order/trades/mod.rs
… into gdemay/DEFI-2901-account-fills # Conflicts: # canister/src/state/tests.rs
…s-endpoint # Conflicts: # canister/canbench_results.yml # canister/src/history/tests.rs # canister/src/main.rs # canister/src/order/fill.rs # canister/src/order/mod.rs # canister/src/order/tests.rs # canister/src/order/trades/mod.rs # canister/src/order/trades/tests.rs # canister/src/state/event/mod.rs # canister/src/state/mod.rs # canister/src/state/snapshot/tests.rs # canister/src/state/tests.rs # canister/src/test_fixtures/event.rs # canister/src/test_fixtures/mod.rs
… into gdemay/DEFI-2901-account-fills # Conflicts: # canister/src/state/mod.rs
A ByOrder `after` cursor is a TradeId (OrderId, FillSeq). Parsing previously dropped the OrderId and kept only the FillSeq, so a cursor whose OrderId names the counterparty leg — which shares the match's FillSeq — was wrongly accepted. The per-order reader now takes the full TradeId cursor and rejects one whose embedded OrderId differs from the queried order as an unknown cursor. Addresses review thread 3508319771. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wrap the get_my_trades filter in a GetMyTradesArgs struct mirroring GetMyOrdersArgs, so future fields can be added without breaking the endpoint signature. Add an OrderNotFound variant to GetMyTradesError and return it — as get_my_orders does — when a ByOrder caller does not own the queried order, replacing the prior empty-page behavior. Tighten the Trade candid docs and correct the InvalidCursor wording to describe a trade-id cursor. Addresses review threads 3510525808, 3510518734, 3510530340, 3510537441, 3510541153, 3508319809. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…_orders_trades Removes the "cursor carrying the maker leg's order id" row that duplicated case 1 of should_reject_a_cursor_from_the_other_leg_of_the_same_fill, and aligns the internal GetMyTradesError::InvalidCursor doc with the public "trade-id cursor" wording. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… into gdemay/DEFI-2901-account-fills # Conflicts: # canister/src/tests.rs
…unt-fills # Conflicts: # canister/oisy_trade.did # canister/src/lib.rs # canister/src/main.rs # canister/src/state/mod.rs # canister/src/state/tests.rs # canister/src/tests.rs # integration_tests/tests/tests.rs # libs/types/src/error/mod.rs # libs/types/src/lib.rs
There was a problem hiding this comment.
Pull request overview
Wires the previously-stubbed get_my_trades { ByAccount { after, length } } query arm to the fill store’s account-wide (per-user) trade reader, enabling an owner-scoped, newest-first trade feed across all of a caller’s orders with cursor pagination and request-error handling for malformed cursors.
Changes:
- Implement
TradesFilter::ByAccountinget_my_trades, includingTradeIdcursor parsing, max-length clamping, andTradeId-carrying results. - Add a state-level reader
State::get_user_tradesthat resolves caller → user id and reads account-wide trades. - Add unit + integration coverage for ordering, pagination, owner scoping, unknown/malformed cursor behavior, clamping, and unregistered callers.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| integration_tests/tests/tests.rs | Adds PocketIC E2E test validating the ByAccount feed across multiple orders and paging semantics. |
| canister/src/tests.rs | Adds unit tests and helpers covering ByAccount behavior (ordering, paging, scoping, cursor handling, clamping, unregistered caller). |
| canister/src/state/tests.rs | Adds state-level test ensuring unregistered callers get empty pages (with/without cursor). |
| canister/src/state/mod.rs | Introduces State::get_user_trades to read account-wide trades via the trade history’s per-user index. |
| canister/src/lib.rs | Implements the get_my_trades ByAccount arm: parse TradeId cursor, clamp length, read, and return public trades with ids. |
| canister/canbench_results.yml | Updates bench numbers reflecting the small instruction-count delta from the new code paths. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ByAccount Addresses Copilot comment 3512105076: replace .unwrap_or_default() with an explicit match on Ok/Err(CursorNotFound), mirroring the ByOrder arm so the cursor-not-found to empty-page mapping is explicit. Behavior unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Final PR of the persist-fills stack: the thin account-wide trades feed.
The per-order feed PR (#186) left the
get_my_trades { ByAccount }arm as an empty-page stub, and the account-wide storage (the by-user index, the account-wide reader, and the settlement wiring that populates it) now lives in the fill-store PR (#179). This PR is the thin API layer that wires theByAccountarm to that storage.What this delivers:
get_my_trades { ByAccount { after, length } }arm: owner-scoped, malformedTradeIdcursor ->Err(RequestError(...)), unknown cursor ->Ok([]),lengthclamped to the max, each returnedTradecarrying its ownid.ByAccountarm (cross-order newest-first, paging without overlap or gap, owner scoping, unknown/empty cursor, malformed cursor, clamping, unregistered caller) and an end-to-end PocketIC test over three orders.The Candid surface is unchanged: the
ByAccountvariant andTradeIdcursor already existed in the type from #186; this PR only wires behavior. Thecheck_candid_interface_compatibilitytest passes. No storage, index, settlement, or canbench changes — those land in #179.Spec:
docs/src/development/specs/DEFI-2901-persist-fills.md.📚 PR stack
main.main.main.main.main.main(sibling test-only PR).🤖 Generated with Claude Code