From 829c5c401d258682b7fe2699306463e146cb2697 Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Wed, 30 Sep 2026 23:37:44 +0000 Subject: [PATCH] Order book: take the PDA bump search out of the depth-cost test deepest_path_adds_little_compute_to_insert_fill_and_cancel compares an insert and a fill at depth 64 with the same instruction on a shallow book, and allows 15,000 extra units. Both instructions create an order account with a bare `bump`, so Anchor searches for the bump at runtime, paying for every rejected seed. The market's address comes from generated mints, and tests running in parallel draw from shared key generation, so the search length, and with it the measured difference, changed from run to run. Anchor v1 measured 15,228 in one CI run on a program identical to main, and passed on the re-run. The test now computes each order PDA's bump itself and subtracts the search: 1,500 units per rejected bump in Anchor v1 (sol_try_find_program_address) and 334 in Anchor v2 (sol_sha256 plus the curve check). With that out, the extra cost of depth 64 is about 10,650 units in v1 and 10,550 to 10,750 in v2, the same on every run. The 15,000 limit is unchanged. Claude-Session: https://claude.ai/code/session_01JGEoAUjMm7Evv69k46eNcn --- .../order-book/tests/test_order_book.rs | 24 ++++++++++++++++-- .../order-book/tests/test_order_book.rs | 25 +++++++++++++++++-- 2 files changed, 45 insertions(+), 4 deletions(-) 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 ae7303cb..b0824f21 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 @@ -2214,6 +2214,22 @@ fn send_and_measure(svm: &mut LiteSVM, instruction: Instruction, signer: &Keypai .compute_units_consumed } +// Creating an order account makes Anchor search for its PDA bump, starting at +// 255 and paying for every seed that lands on the curve. The market's address +// comes from freshly generated mints, so how long that search takes changes +// from run to run. It has nothing to do with the book's depth, so the +// comparison takes it out. Anchor v1 searches with the runtime's +// sol_try_find_program_address, which costs this many units per rejected bump. +const BUMP_ATTEMPT_UNITS: u64 = 1_500; + +fn order_bump_search_units(program_id: &Pubkey, market: &Pubkey, order_id: u64) -> u64 { + let (_, bump) = Pubkey::find_program_address( + &[ORDER_SEED, market.as_ref(), &order_id.to_le_bytes()], + program_id, + ); + (u8::MAX - bump) as u64 * BUMP_ATTEMPT_UNITS +} + // A fresh seller with base tokens and a market user account. fn add_funded_seller(sc: &mut Scenario) -> (Keypair, Pubkey, Pubkey, Pubkey) { let seller = create_wallet(&mut sc.svm, 10_000_000_000).unwrap(); @@ -2243,6 +2259,8 @@ fn add_funded_seller(sc: &mut Scenario) -> (Keypair, Pubkey, Pubkey, Pubkey) { (seller, base_ata, quote_ata, market_user) } +// Insert and fill exclude the order PDA's bump search; see +// order_bump_search_units. struct ProbeCosts { // Inner nodes above the second probe ask once it rests. depth: usize, @@ -2303,7 +2321,8 @@ fn run_probes_at_bottom_of_book(build_chain: bool) -> ProbeCosts { PROBE_PRICE, MIN_ORDER_SIZE, ); - let insert = send_and_measure(&mut sc.svm, instruction, &sc.seller); + let insert = send_and_measure(&mut sc.svm, instruction, &sc.seller) + - order_bump_search_units(&sc.program_id, &sc.market, first_probe_id); let second_probe_id = first_probe_id + 1; let instruction = build_place_order_ix( @@ -2333,7 +2352,8 @@ fn run_probes_at_bottom_of_book(build_chain: bool) -> ProbeCosts { MIN_ORDER_SIZE, &[(first_probe_id, sc.seller_market_user)], ); - let fill = send_and_measure(&mut sc.svm, instruction, &sc.buyer); + let fill = send_and_measure(&mut sc.svm, instruction, &sc.buyer) + - order_bump_search_units(&sc.program_id, &sc.market, taker_bid_id); let first_probe = order_pda(&sc.program_id, &sc.market, first_probe_id); assert_eq!( read_order_fill_and_status(&sc.svm, &first_probe).1, 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 e8b4fcfc..ee8bf155 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 @@ -2221,6 +2221,23 @@ fn send_and_measure(svm: &mut LiteSVM, instruction: Instruction, signer: &Keypai .compute_units_consumed } +// Creating an order account makes Anchor search for its PDA bump, starting at +// 255 and paying for every seed that lands on the curve. Tests running in +// parallel draw their mints from shared key generation, so the market's +// address, and with it how long that search takes, changes from run to run. +// It has nothing to do with the book's depth, so the comparison takes it out. +// Anchor v2 hashes each candidate with sol_sha256 and checks it against the +// curve, which together cost this many units per rejected bump. +const BUMP_ATTEMPT_UNITS: u64 = 334; + +fn order_bump_search_units(program_id: &Address, market: &Address, order_id: u64) -> u64 { + let (_, bump) = Address::find_program_address( + &[ORDER_SEED, market.as_ref(), &order_id.to_le_bytes()], + program_id, + ); + (u8::MAX - bump) as u64 * BUMP_ATTEMPT_UNITS +} + // A fresh seller with base tokens and a market user account. fn add_funded_seller(sc: &mut Scenario) -> (Keypair, Address, Address, Address) { let seller = create_wallet(&mut sc.svm, 10_000_000_000).unwrap(); @@ -2250,6 +2267,8 @@ fn add_funded_seller(sc: &mut Scenario) -> (Keypair, Address, Address, Address) (seller, base_ata, quote_ata, market_user) } +// Insert and fill exclude the order PDA's bump search; see +// order_bump_search_units. struct ProbeCosts { // Inner nodes above the second probe ask once it rests. depth: usize, @@ -2310,7 +2329,8 @@ fn run_probes_at_bottom_of_book(build_chain: bool) -> ProbeCosts { PROBE_PRICE, MIN_ORDER_SIZE, ); - let insert = send_and_measure(&mut sc.svm, instruction, &sc.seller); + let insert = send_and_measure(&mut sc.svm, instruction, &sc.seller) + - order_bump_search_units(&sc.program_id, &sc.market, first_probe_id); let second_probe_id = first_probe_id + 1; let instruction = build_place_order_ix( @@ -2340,7 +2360,8 @@ fn run_probes_at_bottom_of_book(build_chain: bool) -> ProbeCosts { MIN_ORDER_SIZE, &[(first_probe_id, sc.seller_market_user)], ); - let fill = send_and_measure(&mut sc.svm, instruction, &sc.buyer); + let fill = send_and_measure(&mut sc.svm, instruction, &sc.buyer) + - order_bump_search_units(&sc.program_id, &sc.market, taker_bid_id); let first_probe = order_pda(&sc.program_id, &sc.market, first_probe_id); assert_eq!( read_order_fill_and_status(&sc.svm, &first_probe).1,