From 36a6d4e460b0563870b7bf1083d369c9b44250c8 Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Thu, 24 Sep 2026 19:06:52 +0000 Subject: [PATCH] finance/order-book: test the costliest cancel on a full side cancel_order finds an order's tree key by walking its side from the best price down, so its cost grows with the number of resting orders. Both Anchor copies gain full_side_cancel_of_the_last_scanned_order_fits_the_default_budget: it fills the bid side to 512 orders, cancels the worst bid (the last one the walk reaches), and checks the cancel fits the default 200,000-unit budget. Measured: 50,078 CU in Anchor v2, 57,383 CU in Anchor v1. The READMEs' expected test output, stale since eviction merged, is regenerated from the test files. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RQ5RZgoQjVK36qSjqUSq6X --- CHANGELOG.md | 10 +++++++ finance/order-book/anchor-v1/CHANGELOG.md | 9 +++++++ finance/order-book/anchor-v1/README.md | 9 ++++++- .../order-book/tests/test_order_book.rs | 26 +++++++++++++++++++ finance/order-book/anchor/CHANGELOG.md | 9 +++++++ finance/order-book/anchor/README.md | 9 ++++++- .../order-book/tests/test_order_book.rs | 26 +++++++++++++++++++ 7 files changed, 96 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a07ded68..b85d85ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this repository are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## [2026-10-03] - Order book tests cover the costliest cancel + +`cancel_order` finds an order's place in the tree by walking its side from the +best price down, so its cost grows with the number of resting orders. Both +Anchor copies gain `full_side_cancel_of_the_last_scanned_order_fits_the_default_budget`, +which fills the bid side to its 512 orders, cancels the bid the walk reaches +last, and checks it stays inside the default 200,000-unit instruction budget. +It measures 50,078 compute units in the Anchor v2 copy and 57,383 in the v1 +copy. No program behavior changes. + ## [2026-10-02] - Token Swap (Quasar): reserves live at PDAs of the pool ### Changed diff --git a/finance/order-book/anchor-v1/CHANGELOG.md b/finance/order-book/anchor-v1/CHANGELOG.md index 9744a0d9..7ca0a190 100644 --- a/finance/order-book/anchor-v1/CHANGELOG.md +++ b/finance/order-book/anchor-v1/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 2026-10-03 + +### Added + +- `full_side_cancel_of_the_last_scanned_order_fits_the_default_budget`: + `cancel_order` finds an order by walking its side from the best price, so + on a full side of 512 bids the worst bid is the last one it reads. Canceling + it stays inside the default 200,000-unit instruction budget. + ## 2026-09-23 ### Added diff --git a/finance/order-book/anchor-v1/README.md b/finance/order-book/anchor-v1/README.md index 687d1b2f..4bd40db2 100644 --- a/finance/order-book/anchor-v1/README.md +++ b/finance/order-book/anchor-v1/README.md @@ -1473,15 +1473,20 @@ anchor test --skip-local-validator Expected: ``` -running 29 tests +running 35 tests test authority_can_withdraw_fees_after_match ... ok +test better_order_evicts_the_worst_and_rests ... ok test cancel_and_settle_bid_refunds_full_quote ... ok test cancel_ask_credits_unsettled_base ... ok test cancel_order_rejects_non_owner ... ok test deepest_path_adds_little_compute_to_insert_fill_and_cancel ... ok test doubling_prices_build_the_deepest_path_prices_allow ... ok +test evicted_maker_settles_their_refund ... ok +test eviction_rejects_missing_or_wrong_evicted_accounts ... ok test fee_rounds_up_when_gross_is_not_a_bps_multiple ... ok test fee_vault_receives_exactly_bps_of_taker_gross ... ok +test full_side_cancel_of_the_last_scanned_order_fits_the_default_budget ... ok +test full_side_refuses_an_order_no_better_than_its_worst ... ok test initialize_market_rejects_oversized_fee ... ok test initialize_market_rejects_zero_base_lot_size ... ok test initialize_market_rejects_zero_quote_lot_size ... ok @@ -1503,6 +1508,7 @@ test taker_bid_gets_price_improvement_from_resting_ask ... ok test taker_crosses_multiple_resting_orders_best_price_first ... ok test taker_partially_filled_remainder_rests_on_book ... ok test taker_partially_fills_resting_order_rest_stays_on_book ... ok +test trader_can_evict_their_own_worst_order ... ok ``` ### What each test exercises @@ -1554,6 +1560,7 @@ test taker_partially_fills_resting_order_rest_stays_on_book ... ok - `evicted_maker_settles_their_refund`: The evicted owner's `settle_funds` pays out the refund - `eviction_rejects_missing_or_wrong_evicted_accounts`: No evicted order, a non-worst order, or the wrong owner's `MarketUser` - `trader_can_evict_their_own_worst_order`: Only the `Order` account is passed, and the caller's own `MarketUser` is credited +- `full_side_cancel_of_the_last_scanned_order_fits_the_default_budget`: Canceling the bid found last among 512 stays inside the default 200,000-unit budget ### CI note diff --git a/finance/order-book/anchor-v1/programs/order-book/tests/test_order_book.rs b/finance/order-book/anchor-v1/programs/order-book/tests/test_order_book.rs index b0824f21..cfcc992b 100644 --- a/finance/order-book/anchor-v1/programs/order-book/tests/test_order_book.rs +++ b/finance/order-book/anchor-v1/programs/order-book/tests/test_order_book.rs @@ -2775,3 +2775,29 @@ fn trader_can_evict_their_own_worst_order() { ORDERS_PER_FILLER as u32 ); } + +#[test] +fn full_side_cancel_of_the_last_scanned_order_fits_the_default_budget() { + // `cancel_order` finds the order's tree key by walking the side from the + // best price down, so on a full bid side the worst bid is the last of + // 512 leaves it reads. That is the most compute a cancel can cost. + let mut sc = full_setup(); + initialize_market_and_users(&mut sc); + let fillers = fill_bid_side(&mut sc); + + let instruction = build_cancel_order_ix( + &sc, + &fillers[0].keypair.pubkey(), + fillers[0].market_user, + WORST_BID_ORDER_ID, + ); + let units = send_and_measure(&mut sc.svm, instruction, &fillers[0].keypair); + println!("cancel of the last of {ORDERS_PER_SIDE} bids scanned: {units} CU"); + assert!(units < DEFAULT_INSTRUCTION_COMPUTE_UNITS, "{units} CU"); + + let (_, status) = read_order_fill_and_status( + &sc.svm, + &order_pda(&sc.program_id, &sc.market, WORST_BID_ORDER_ID), + ); + assert_eq!(status, ORDER_STATUS_CANCELLED); +} diff --git a/finance/order-book/anchor/CHANGELOG.md b/finance/order-book/anchor/CHANGELOG.md index 31aaa26a..ae7c562e 100644 --- a/finance/order-book/anchor/CHANGELOG.md +++ b/finance/order-book/anchor/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 2026-10-03 + +### Added + +- `full_side_cancel_of_the_last_scanned_order_fits_the_default_budget`: + `cancel_order` finds an order by walking its side from the best price, so + on a full side of 512 bids the worst bid is the last one it reads. Canceling + it stays inside the default 200,000-unit instruction budget. + ## 2026-09-23 ### Added diff --git a/finance/order-book/anchor/README.md b/finance/order-book/anchor/README.md index dbbf6de4..300ce557 100644 --- a/finance/order-book/anchor/README.md +++ b/finance/order-book/anchor/README.md @@ -1481,15 +1481,20 @@ anchor test --skip-local-validator Expected: ``` -running 29 tests +running 35 tests test authority_can_withdraw_fees_after_match ... ok +test better_order_evicts_the_worst_and_rests ... ok test cancel_and_settle_bid_refunds_full_quote ... ok test cancel_ask_credits_unsettled_base ... ok test cancel_order_rejects_non_owner ... ok test deepest_path_adds_little_compute_to_insert_fill_and_cancel ... ok test doubling_prices_build_the_deepest_path_prices_allow ... ok +test evicted_maker_settles_their_refund ... ok +test eviction_rejects_missing_or_wrong_evicted_accounts ... ok test fee_rounds_up_when_gross_is_not_a_bps_multiple ... ok test fee_vault_receives_exactly_bps_of_taker_gross ... ok +test full_side_cancel_of_the_last_scanned_order_fits_the_default_budget ... ok +test full_side_refuses_an_order_no_better_than_its_worst ... ok test initialize_market_rejects_oversized_fee ... ok test initialize_market_rejects_zero_base_lot_size ... ok test initialize_market_rejects_zero_quote_lot_size ... ok @@ -1511,6 +1516,7 @@ test taker_bid_gets_price_improvement_from_resting_ask ... ok test taker_crosses_multiple_resting_orders_best_price_first ... ok test taker_partially_filled_remainder_rests_on_book ... ok test taker_partially_fills_resting_order_rest_stays_on_book ... ok +test trader_can_evict_their_own_worst_order ... ok ``` ### What each test exercises @@ -1562,6 +1568,7 @@ test taker_partially_fills_resting_order_rest_stays_on_book ... ok - `evicted_maker_settles_their_refund`: The evicted owner's `settle_funds` pays out the refund - `eviction_rejects_missing_or_wrong_evicted_accounts`: No evicted order, a non-worst order, or the wrong owner's `MarketUser` - `trader_can_evict_their_own_worst_order`: Only the `Order` account is passed, and the caller's own `MarketUser` is credited +- `full_side_cancel_of_the_last_scanned_order_fits_the_default_budget`: Canceling the bid found last among 512 stays inside the default 200,000-unit budget ### CI note diff --git a/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs b/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs index ee8bf155..87feeb35 100644 --- a/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs +++ b/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs @@ -2783,3 +2783,29 @@ fn trader_can_evict_their_own_worst_order() { ORDERS_PER_FILLER as u32 ); } + +#[test] +fn full_side_cancel_of_the_last_scanned_order_fits_the_default_budget() { + // `cancel_order` finds the order's tree key by walking the side from the + // best price down, so on a full bid side the worst bid is the last of + // 512 leaves it reads. That is the most compute a cancel can cost. + let mut sc = full_setup(); + initialize_market_and_users(&mut sc); + let fillers = fill_bid_side(&mut sc); + + let instruction = build_cancel_order_ix( + &sc, + &fillers[0].keypair.pubkey(), + fillers[0].market_user, + WORST_BID_ORDER_ID, + ); + let units = send_and_measure(&mut sc.svm, instruction, &fillers[0].keypair); + println!("cancel of the last of {ORDERS_PER_SIDE} bids scanned: {units} CU"); + assert!(units < DEFAULT_INSTRUCTION_COMPUTE_UNITS, "{units} CU"); + + let (_, status) = read_order_fill_and_status( + &sc.svm, + &order_pda(&sc.program_id, &sc.market, WORST_BID_ORDER_ID), + ); + assert_eq!(status, ORDER_STATUS_CANCELLED); +}