|
| 1 | +use anchor_lang::prelude::*; |
| 2 | + |
| 3 | +use crate::errors::ErrorCode; |
| 4 | +use crate::state::{ |
| 5 | + remaining_quantity, remove_open_order, Market, Order, OrderBook, OrderSide, OrderStatus, |
| 6 | + MarketUser, ORDER_SEED, MARKET_USER_SEED, |
| 7 | +}; |
| 8 | + |
| 9 | +pub fn handle_cancel_order(context: Context<CancelOrder>) -> Result<()> { |
| 10 | + let order = &mut context.accounts.order; |
| 11 | + |
| 12 | + require!( |
| 13 | + order.owner == context.accounts.owner.key(), |
| 14 | + ErrorCode::Unauthorized |
| 15 | + ); |
| 16 | + |
| 17 | + require!( |
| 18 | + order.status == OrderStatus::Open || order.status == OrderStatus::PartiallyFilled, |
| 19 | + ErrorCode::OrderNotCancellable |
| 20 | + ); |
| 21 | + |
| 22 | + // Funds the order had locked in the vault are now owed back to the |
| 23 | + // owner. Credit the appropriate unsettled balance; settle_funds moves |
| 24 | + // those funds from the vault to the owner's token account. |
| 25 | + let remaining = remaining_quantity(order); |
| 26 | + if remaining > 0 { |
| 27 | + let market_user = &mut context.accounts.market_user; |
| 28 | + match order.side { |
| 29 | + OrderSide::Bid => { |
| 30 | + // u128 intermediate: the lock was originally taken on a |
| 31 | + // u64 quote balance, so price * remaining must fit u64 |
| 32 | + // — but the multiplication itself can transiently exceed |
| 33 | + // u64. Mirror the same pattern as place_order: widen, |
| 34 | + // multiply, narrow. |
| 35 | + let quote_amount: u64 = (order.price as u128) |
| 36 | + .checked_mul(remaining as u128) |
| 37 | + .ok_or(ErrorCode::NumericalOverflow)? |
| 38 | + .try_into() |
| 39 | + .map_err(|_| error!(ErrorCode::NumericalOverflow))?; |
| 40 | + market_user.unsettled_quote = market_user |
| 41 | + .unsettled_quote |
| 42 | + .checked_add(quote_amount) |
| 43 | + .ok_or(ErrorCode::NumericalOverflow)?; |
| 44 | + } |
| 45 | + OrderSide::Ask => { |
| 46 | + market_user.unsettled_base = market_user |
| 47 | + .unsettled_base |
| 48 | + .checked_add(remaining) |
| 49 | + .ok_or(ErrorCode::NumericalOverflow)?; |
| 50 | + } |
| 51 | + } |
| 52 | + } |
| 53 | + |
| 54 | + // Remove the leaf from the slab. The current cancel API doesn't tell us |
| 55 | + // which side the order is on without reading the Order PDA — which we |
| 56 | + // already have, so use it. |
| 57 | + let mut order_book = context.accounts.order_book.load_mut()?; |
| 58 | + let removed = order_book.remove_from(order.side, order.order_id).is_some(); |
| 59 | + require!(removed, ErrorCode::OrderNotFound); |
| 60 | + drop(order_book); |
| 61 | + |
| 62 | + let market_user = &mut context.accounts.market_user; |
| 63 | + remove_open_order(market_user, order.order_id); |
| 64 | + |
| 65 | + order.status = OrderStatus::Cancelled; |
| 66 | + |
| 67 | + Ok(()) |
| 68 | +} |
| 69 | + |
| 70 | +#[derive(Accounts)] |
| 71 | +pub struct CancelOrder<'info> { |
| 72 | + #[account(has_one = order_book @ ErrorCode::InvalidOrderBook)] |
| 73 | + pub market: Account<'info, Market>, |
| 74 | + |
| 75 | + // Not a PDA (see initialize_market.rs); bound to `market` via has_one. |
| 76 | + #[account(mut)] |
| 77 | + pub order_book: AccountLoader<'info, OrderBook>, |
| 78 | + |
| 79 | + #[account( |
| 80 | + mut, |
| 81 | + seeds = [ORDER_SEED, market.key().as_ref(), order.order_id.to_le_bytes().as_ref()], |
| 82 | + bump = order.bump |
| 83 | + )] |
| 84 | + pub order: Account<'info, Order>, |
| 85 | + |
| 86 | + #[account( |
| 87 | + mut, |
| 88 | + seeds = [MARKET_USER_SEED, market.key().as_ref(), owner.key().as_ref()], |
| 89 | + bump = market_user.bump |
| 90 | + )] |
| 91 | + pub market_user: Account<'info, MarketUser>, |
| 92 | + |
| 93 | + pub owner: Signer<'info>, |
| 94 | +} |
0 commit comments