diff --git a/programs/mango-v4/src/instructions/serum3_place_order.rs b/programs/mango-v4/src/instructions/serum3_place_order.rs index c20a15e47..55af21d91 100644 --- a/programs/mango-v4/src/instructions/serum3_place_order.rs +++ b/programs/mango-v4/src/instructions/serum3_place_order.rs @@ -316,18 +316,13 @@ pub fn serum3_place_order( )? }; - // Deposit limit check: Placing an order can increase deposit limit use on both - // the payer and receiver bank. Imagine placing a bid for 500 base @ 0.5: it would - // use up 1000 quote and 500 base because either could be deposit on cancel/fill. - // This is why this must happen after update_bank_potential_tokens() and any withdraws. + // Deposit limit check, receiver side: + // Placing an order can always increase the receiver bank deposits on fill. { let receiver_bank = receiver_bank_ai.load::()?; receiver_bank .check_deposit_and_oo_limit() .with_context(|| std::format!("on {}", receiver_bank.name()))?; - payer_bank - .check_deposit_and_oo_limit() - .with_context(|| std::format!("on {}", payer_bank.name()))?; } // Payer bank safety checks like reduce-only, net borrows, vault-to-deposits ratio @@ -342,6 +337,18 @@ pub fn serum3_place_order( ); payer_bank.enforce_max_utilization_on_borrow()?; payer_bank.check_net_borrows(payer_bank_oracle)?; + + // Deposit limit check, payer side: + // The payer bank deposits could increase when cancelling the order later: + // Imagine the account borrowing payer tokens to place the order, repaying the borrows + // and then cancelling the order to create a deposit. + // + // However, if the account only decreases its deposits to place an order it can't + // worsen the situation and should always go through, even if payer deposit limits are + // already exceeded. + payer_bank + .check_deposit_and_oo_limit() + .with_context(|| std::format!("on {}", payer_bank.name()))?; } else { payer_bank.enforce_borrows_lte_deposits()?; } diff --git a/programs/mango-v4/tests/cases/test_serum.rs b/programs/mango-v4/tests/cases/test_serum.rs index 90713303b..123851241 100644 --- a/programs/mango-v4/tests/cases/test_serum.rs +++ b/programs/mango-v4/tests/cases/test_serum.rs @@ -1563,6 +1563,7 @@ async fn test_serum_deposit_limits() -> Result<(), TransportError> { // let deposit_amount = 5000; // for 10k tokens over both order_placers let CommonSetup { + serum_market_cookie, group_with_tokens, mut order_placer, quote_token, @@ -1677,11 +1678,15 @@ async fn test_serum_deposit_limits() -> Result<(), TransportError> { let remaining_quote = { || async { let b: Bank = solana2.get_account(quote_bank).await; - b.remaining_deposits_until_limit().round().to_num::() + b.remaining_deposits_until_limit().round().to_num::() } }; order_placer.cancel_all().await; + context + .serum + .consume_spot_events(&serum_market_cookie, &[order_placer.open_orders]) + .await; // // TEST: even when placing all quote tokens into a bid, they still count @@ -1705,6 +1710,43 @@ async fn test_serum_deposit_limits() -> Result<(), TransportError> { assert_mango_error(&r, MangoError::BankDepositLimit.into(), "dep limit".into()); order_placer.try_ask(5.0, 399).await.unwrap(); // not 400 due to rounding + // reset + order_placer.cancel_all().await; + context + .serum + .consume_spot_events(&serum_market_cookie, &[order_placer.open_orders]) + .await; + order_placer.settle().await; + + // + // TEST: can place a bid even if quote deposit limit is exhausted + // + send_tx( + solana, + TokenEdit { + group: group_with_tokens.group, + admin: group_with_tokens.admin, + mint: quote_token.mint.pubkey, + fallback_oracle: Pubkey::default(), + options: mango_v4::instruction::TokenEdit { + deposit_limit_opt: Some(1), + ..token_edit_instruction_default() + }, + }, + ) + .await + .unwrap(); + assert!(remaining_quote().await < 0); + assert_eq!( + account_position(solana, order_placer.account, quote_token.bank).await, + 5000 + ); + // borrowing might lead to a deposit increase later + let r = order_placer.try_bid(1.0, 5001, false).await; + assert_mango_error(&r, MangoError::BankDepositLimit.into(), "dep limit".into()); + // but just selling deposits is fine + order_placer.try_bid(1.0, 4999, false).await.unwrap(); + Ok(()) }