diff --git a/programs/mango-v4/src/error.rs b/programs/mango-v4/src/error.rs index 7ac7ccacd..c1f564104 100644 --- a/programs/mango-v4/src/error.rs +++ b/programs/mango-v4/src/error.rs @@ -81,6 +81,8 @@ pub enum MangoError { HasLiquidatablePerpBasePosition, #[msg("has liquidatable trusted perp pnl")] HasLiquidatableTrustedPerpPnl, + #[msg("account is frozen")] + AccountIsFrozen, } impl MangoError { diff --git a/programs/mango-v4/src/instructions/account_close.rs b/programs/mango-v4/src/instructions/account_close.rs index 0d59565bb..2f823d9e0 100644 --- a/programs/mango-v4/src/instructions/account_close.rs +++ b/programs/mango-v4/src/instructions/account_close.rs @@ -15,6 +15,7 @@ pub struct AccountClose<'info> { mut, has_one = group, has_one = owner, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen, close = sol_destination )] pub account: AccountLoader<'info, MangoAccountFixed>, diff --git a/programs/mango-v4/src/instructions/account_edit.rs b/programs/mango-v4/src/instructions/account_edit.rs index 70530b7e0..08c74ae6e 100644 --- a/programs/mango-v4/src/instructions/account_edit.rs +++ b/programs/mango-v4/src/instructions/account_edit.rs @@ -14,7 +14,8 @@ pub struct AccountEdit<'info> { #[account( mut, has_one = group, - has_one = owner + has_one = owner, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen )] pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, diff --git a/programs/mango-v4/src/instructions/account_expand.rs b/programs/mango-v4/src/instructions/account_expand.rs index bff654074..f151a5737 100644 --- a/programs/mango-v4/src/instructions/account_expand.rs +++ b/programs/mango-v4/src/instructions/account_expand.rs @@ -14,7 +14,8 @@ pub struct AccountExpand<'info> { #[account( mut, has_one = group, - has_one = owner + has_one = owner, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen )] pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, diff --git a/programs/mango-v4/src/instructions/account_toggle_freeze.rs b/programs/mango-v4/src/instructions/account_toggle_freeze.rs new file mode 100644 index 000000000..a7e44218b --- /dev/null +++ b/programs/mango-v4/src/instructions/account_toggle_freeze.rs @@ -0,0 +1,35 @@ +use anchor_lang::prelude::*; + +use crate::error::MangoError; +use crate::state::*; + +#[derive(Accounts)] +pub struct AccountToggleFreeze<'info> { + #[account( + constraint = group.load()?.is_operational() @ MangoError::GroupIsHalted, + constraint = group.load()?.admin == admin.key() || group.load()?.security_admin == admin.key(), + )] + pub group: AccountLoader<'info, Group>, + + #[account( + mut, + has_one = group, + )] + pub account: AccountLoader<'info, MangoAccountFixed>, + + pub admin: Signer<'info>, +} + +// Freezing an account, prevents all instructions involving account (also settling and liquidation), except +// perp consume events and force cancellation of orders +pub fn account_toggle_freeze(ctx: Context, freeze: bool) -> Result<()> { + let mut account = ctx.accounts.account.load_full_mut()?; + if freeze { + let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap(); + account.fixed.frozen_until = now_ts + 7 * 24 * 60 * 60; + } else { + account.fixed.frozen_until = 0; + } + + Ok(()) +} diff --git a/programs/mango-v4/src/instructions/flash_loan.rs b/programs/mango-v4/src/instructions/flash_loan.rs index 9701cf40f..445be2097 100644 --- a/programs/mango-v4/src/instructions/flash_loan.rs +++ b/programs/mango-v4/src/instructions/flash_loan.rs @@ -31,6 +31,9 @@ pub mod jupiter_mainnet_3 { /// 4. the mango group #[derive(Accounts)] pub struct FlashLoanBegin<'info> { + #[account( + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen + )] pub account: AccountLoader<'info, MangoAccountFixed>, // owner is checked at #1 pub owner: Signer<'info>, @@ -53,7 +56,10 @@ pub struct FlashLoanBegin<'info> { /// 4. the mango group #[derive(Accounts)] pub struct FlashLoanEnd<'info> { - #[account(mut)] + #[account( + mut, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen + )] pub account: AccountLoader<'info, MangoAccountFixed>, // owner is checked at #1 pub owner: Signer<'info>, diff --git a/programs/mango-v4/src/instructions/health_region.rs b/programs/mango-v4/src/instructions/health_region.rs index 312b75d15..65318ae5e 100644 --- a/programs/mango-v4/src/instructions/health_region.rs +++ b/programs/mango-v4/src/instructions/health_region.rs @@ -18,7 +18,10 @@ pub struct HealthRegionBegin<'info> { #[account(address = tx_instructions::ID)] pub instructions: UncheckedAccount<'info>, - #[account(mut)] + #[account( + mut, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen + )] pub account: AccountLoader<'info, MangoAccountFixed>, } @@ -27,7 +30,10 @@ pub struct HealthRegionBegin<'info> { /// remaining_accounts: health accounts for account #[derive(Accounts)] pub struct HealthRegionEnd<'info> { - #[account(mut)] + #[account( + mut, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen + )] pub account: AccountLoader<'info, MangoAccountFixed>, } diff --git a/programs/mango-v4/src/instructions/mod.rs b/programs/mango-v4/src/instructions/mod.rs index 521e64aed..43bc8420e 100644 --- a/programs/mango-v4/src/instructions/mod.rs +++ b/programs/mango-v4/src/instructions/mod.rs @@ -2,6 +2,7 @@ pub use account_close::*; pub use account_create::*; pub use account_edit::*; pub use account_expand::*; +pub use account_toggle_freeze::*; pub use alt_extend::*; pub use alt_set::*; pub use benchmark::*; @@ -56,6 +57,7 @@ mod account_close; mod account_create; mod account_edit; mod account_expand; +mod account_toggle_freeze; mod alt_extend; mod alt_set; mod benchmark; diff --git a/programs/mango-v4/src/instructions/perp_cancel_all_orders.rs b/programs/mango-v4/src/instructions/perp_cancel_all_orders.rs index 94dddb705..269fa534b 100644 --- a/programs/mango-v4/src/instructions/perp_cancel_all_orders.rs +++ b/programs/mango-v4/src/instructions/perp_cancel_all_orders.rs @@ -10,7 +10,11 @@ pub struct PerpCancelAllOrders<'info> { )] pub group: AccountLoader<'info, Group>, - #[account(mut, has_one = group)] + #[account( + mut, + has_one = group, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen + )] pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, diff --git a/programs/mango-v4/src/instructions/perp_cancel_all_orders_by_side.rs b/programs/mango-v4/src/instructions/perp_cancel_all_orders_by_side.rs index a3f702f5a..9cca64546 100644 --- a/programs/mango-v4/src/instructions/perp_cancel_all_orders_by_side.rs +++ b/programs/mango-v4/src/instructions/perp_cancel_all_orders_by_side.rs @@ -12,7 +12,11 @@ pub struct PerpCancelAllOrdersBySide<'info> { )] pub group: AccountLoader<'info, Group>, - #[account(mut, has_one = group)] + #[account( + mut, + has_one = group, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen + )] pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, diff --git a/programs/mango-v4/src/instructions/perp_cancel_order.rs b/programs/mango-v4/src/instructions/perp_cancel_order.rs index ab4f99b59..192792250 100644 --- a/programs/mango-v4/src/instructions/perp_cancel_order.rs +++ b/programs/mango-v4/src/instructions/perp_cancel_order.rs @@ -10,7 +10,11 @@ pub struct PerpCancelOrder<'info> { )] pub group: AccountLoader<'info, Group>, - #[account(mut, has_one = group)] + #[account( + mut, + has_one = group, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen + )] pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, diff --git a/programs/mango-v4/src/instructions/perp_cancel_order_by_client_order_id.rs b/programs/mango-v4/src/instructions/perp_cancel_order_by_client_order_id.rs index 27e55eb22..f2f66d11f 100644 --- a/programs/mango-v4/src/instructions/perp_cancel_order_by_client_order_id.rs +++ b/programs/mango-v4/src/instructions/perp_cancel_order_by_client_order_id.rs @@ -10,7 +10,11 @@ pub struct PerpCancelOrderByClientOrderId<'info> { )] pub group: AccountLoader<'info, Group>, - #[account(mut, has_one = group)] + #[account( + mut, + has_one = group, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen + )] pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, diff --git a/programs/mango-v4/src/instructions/perp_deactivate_position.rs b/programs/mango-v4/src/instructions/perp_deactivate_position.rs index d30d0f66c..89f282b9d 100644 --- a/programs/mango-v4/src/instructions/perp_deactivate_position.rs +++ b/programs/mango-v4/src/instructions/perp_deactivate_position.rs @@ -12,7 +12,8 @@ pub struct PerpDeactivatePosition<'info> { #[account( mut, - has_one = group + has_one = group, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen // owner is checked at #1 )] pub account: AccountLoader<'info, MangoAccountFixed>, diff --git a/programs/mango-v4/src/instructions/perp_liq_bankruptcy.rs b/programs/mango-v4/src/instructions/perp_liq_bankruptcy.rs index f1524f42e..99fa6bb23 100644 --- a/programs/mango-v4/src/instructions/perp_liq_bankruptcy.rs +++ b/programs/mango-v4/src/instructions/perp_liq_bankruptcy.rs @@ -26,7 +26,8 @@ pub struct PerpLiqBankruptcy<'info> { #[account( mut, - has_one = group + has_one = group, + constraint = liqor.load()?.is_operational() @ MangoError::AccountIsFrozen // liqor_owner is checked at #1 )] pub liqor: AccountLoader<'info, MangoAccountFixed>, @@ -34,7 +35,8 @@ pub struct PerpLiqBankruptcy<'info> { #[account( mut, - has_one = group + has_one = group, + constraint = liqee.load()?.is_operational() @ MangoError::AccountIsFrozen )] pub liqee: AccountLoader<'info, MangoAccountFixed>, diff --git a/programs/mango-v4/src/instructions/perp_liq_base_position.rs b/programs/mango-v4/src/instructions/perp_liq_base_position.rs index aa5bc66ba..7ecc5997b 100644 --- a/programs/mango-v4/src/instructions/perp_liq_base_position.rs +++ b/programs/mango-v4/src/instructions/perp_liq_base_position.rs @@ -24,13 +24,18 @@ pub struct PerpLiqBasePosition<'info> { #[account( mut, - has_one = group + has_one = group, + constraint = liqor.load()?.is_operational() @ MangoError::AccountIsFrozen // liqor_owner is checked at #1 )] pub liqor: AccountLoader<'info, MangoAccountFixed>, pub liqor_owner: Signer<'info>, - #[account(mut, has_one = group)] + #[account( + mut, + has_one = group, + constraint = liqee.load()?.is_operational() @ MangoError::AccountIsFrozen + )] pub liqee: AccountLoader<'info, MangoAccountFixed>, } diff --git a/programs/mango-v4/src/instructions/perp_liq_force_cancel_orders.rs b/programs/mango-v4/src/instructions/perp_liq_force_cancel_orders.rs index d284ee67c..6d9785304 100644 --- a/programs/mango-v4/src/instructions/perp_liq_force_cancel_orders.rs +++ b/programs/mango-v4/src/instructions/perp_liq_force_cancel_orders.rs @@ -12,7 +12,11 @@ pub struct PerpLiqForceCancelOrders<'info> { )] pub group: AccountLoader<'info, Group>, - #[account(mut, has_one = group)] + // Allow force cancel even if account is frozen + #[account( + mut, + has_one = group + )] pub account: AccountLoader<'info, MangoAccountFixed>, #[account( diff --git a/programs/mango-v4/src/instructions/perp_liq_quote_and_bankruptcy.rs b/programs/mango-v4/src/instructions/perp_liq_quote_and_bankruptcy.rs index 546c269dd..1b8ac546c 100644 --- a/programs/mango-v4/src/instructions/perp_liq_quote_and_bankruptcy.rs +++ b/programs/mango-v4/src/instructions/perp_liq_quote_and_bankruptcy.rs @@ -20,13 +20,18 @@ pub struct PerpLiqQuoteAndBankruptcy<'info> { #[account( mut, has_one = group, + constraint = liqor.load()?.is_operational() @ MangoError::AccountIsFrozen, // liqor_owner is checked at #1 )] pub liqor: AccountLoader<'info, MangoAccountFixed>, pub liqor_owner: Signer<'info>, // This account MUST have a loss - #[account(mut, has_one = group)] + #[account( + mut, + has_one = group, + constraint = liqee.load()?.is_operational() @ MangoError::AccountIsFrozen + )] pub liqee: AccountLoader<'info, MangoAccountFixed>, #[account(mut, has_one = group, has_one = oracle)] diff --git a/programs/mango-v4/src/instructions/perp_place_order.rs b/programs/mango-v4/src/instructions/perp_place_order.rs index 9af01690b..a96ba98d6 100644 --- a/programs/mango-v4/src/instructions/perp_place_order.rs +++ b/programs/mango-v4/src/instructions/perp_place_order.rs @@ -16,7 +16,11 @@ pub struct PerpPlaceOrder<'info> { )] pub group: AccountLoader<'info, Group>, - #[account(mut, has_one = group)] + #[account( + mut, + has_one = group, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen + )] pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, diff --git a/programs/mango-v4/src/instructions/perp_settle_fees.rs b/programs/mango-v4/src/instructions/perp_settle_fees.rs index 576582876..b5e267ef4 100644 --- a/programs/mango-v4/src/instructions/perp_settle_fees.rs +++ b/programs/mango-v4/src/instructions/perp_settle_fees.rs @@ -21,7 +21,11 @@ pub struct PerpSettleFees<'info> { pub perp_market: AccountLoader<'info, PerpMarket>, // This account MUST have a loss - #[account(mut, has_one = group)] + #[account( + mut, + has_one = group, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen + )] pub account: AccountLoader<'info, MangoAccountFixed>, /// CHECK: Oracle can have different account types, constrained by address in perp_market diff --git a/programs/mango-v4/src/instructions/perp_settle_pnl.rs b/programs/mango-v4/src/instructions/perp_settle_pnl.rs index 2d1381afe..270729216 100644 --- a/programs/mango-v4/src/instructions/perp_settle_pnl.rs +++ b/programs/mango-v4/src/instructions/perp_settle_pnl.rs @@ -19,6 +19,7 @@ pub struct PerpSettlePnl<'info> { #[account( mut, has_one = group, + constraint = settler.load()?.is_operational() @ MangoError::AccountIsFrozen // settler_owner is checked at #1 )] pub settler: AccountLoader<'info, MangoAccountFixed>, @@ -28,10 +29,17 @@ pub struct PerpSettlePnl<'info> { pub perp_market: AccountLoader<'info, PerpMarket>, // This account MUST be profitable - #[account(mut, has_one = group)] + #[account(mut, + has_one = group, + constraint = account_a.load()?.is_operational() @ MangoError::AccountIsFrozen + )] pub account_a: AccountLoader<'info, MangoAccountFixed>, // This account MUST have a loss - #[account(mut, has_one = group)] + #[account( + mut, + has_one = group, + constraint = account_b.load()?.is_operational() @ MangoError::AccountIsFrozen + )] pub account_b: AccountLoader<'info, MangoAccountFixed>, /// CHECK: Oracle can have different account types, constrained by address in perp_market diff --git a/programs/mango-v4/src/instructions/serum3_cancel_all_orders.rs b/programs/mango-v4/src/instructions/serum3_cancel_all_orders.rs index 58546b256..17e66bd1b 100644 --- a/programs/mango-v4/src/instructions/serum3_cancel_all_orders.rs +++ b/programs/mango-v4/src/instructions/serum3_cancel_all_orders.rs @@ -14,7 +14,8 @@ pub struct Serum3CancelAllOrders<'info> { pub group: AccountLoader<'info, Group>, #[account( - has_one = group + has_one = group, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen // owner is checked at #1 )] pub account: AccountLoader<'info, MangoAccountFixed>, diff --git a/programs/mango-v4/src/instructions/serum3_cancel_order.rs b/programs/mango-v4/src/instructions/serum3_cancel_order.rs index 53529cd40..6ec1d41d7 100644 --- a/programs/mango-v4/src/instructions/serum3_cancel_order.rs +++ b/programs/mango-v4/src/instructions/serum3_cancel_order.rs @@ -20,7 +20,8 @@ pub struct Serum3CancelOrder<'info> { #[account( mut, - has_one = group + has_one = group, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen // owner is checked at #1 )] pub account: AccountLoader<'info, MangoAccountFixed>, diff --git a/programs/mango-v4/src/instructions/serum3_close_open_orders.rs b/programs/mango-v4/src/instructions/serum3_close_open_orders.rs index 4c7190658..e01f462ba 100644 --- a/programs/mango-v4/src/instructions/serum3_close_open_orders.rs +++ b/programs/mango-v4/src/instructions/serum3_close_open_orders.rs @@ -12,7 +12,8 @@ pub struct Serum3CloseOpenOrders<'info> { #[account( mut, - has_one = group + has_one = group, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen // owner is checked at #1 )] pub account: AccountLoader<'info, MangoAccountFixed>, diff --git a/programs/mango-v4/src/instructions/serum3_create_open_orders.rs b/programs/mango-v4/src/instructions/serum3_create_open_orders.rs index be4a49b43..74def64b2 100644 --- a/programs/mango-v4/src/instructions/serum3_create_open_orders.rs +++ b/programs/mango-v4/src/instructions/serum3_create_open_orders.rs @@ -12,7 +12,8 @@ pub struct Serum3CreateOpenOrders<'info> { #[account( mut, - has_one = group + has_one = group, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen // owner is checked at #1 )] pub account: AccountLoader<'info, MangoAccountFixed>, diff --git a/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs b/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs index ff8f8cca2..744ad64c6 100644 --- a/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs +++ b/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs @@ -19,7 +19,11 @@ pub struct Serum3LiqForceCancelOrders<'info> { )] pub group: AccountLoader<'info, Group>, - #[account(mut, has_one = group)] + // Allow force cancel even if account is frozen + #[account( + mut, + has_one = group + )] pub account: AccountLoader<'info, MangoAccountFixed>, #[account(mut)] diff --git a/programs/mango-v4/src/instructions/serum3_place_order.rs b/programs/mango-v4/src/instructions/serum3_place_order.rs index 2f279d102..33106c5a7 100644 --- a/programs/mango-v4/src/instructions/serum3_place_order.rs +++ b/programs/mango-v4/src/instructions/serum3_place_order.rs @@ -142,7 +142,8 @@ pub struct Serum3PlaceOrder<'info> { #[account( mut, - has_one = group + has_one = group, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen // owner is checked at #1 )] pub account: AccountLoader<'info, MangoAccountFixed>, diff --git a/programs/mango-v4/src/instructions/serum3_settle_funds.rs b/programs/mango-v4/src/instructions/serum3_settle_funds.rs index 81fcfc9a0..cdffcd6e8 100644 --- a/programs/mango-v4/src/instructions/serum3_settle_funds.rs +++ b/programs/mango-v4/src/instructions/serum3_settle_funds.rs @@ -20,7 +20,8 @@ pub struct Serum3SettleFunds<'info> { #[account( mut, - has_one = group + has_one = group, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen // owner is checked at #1 )] pub account: AccountLoader<'info, MangoAccountFixed>, diff --git a/programs/mango-v4/src/instructions/token_deposit.rs b/programs/mango-v4/src/instructions/token_deposit.rs index 5382bc027..6afa21b0d 100644 --- a/programs/mango-v4/src/instructions/token_deposit.rs +++ b/programs/mango-v4/src/instructions/token_deposit.rs @@ -20,7 +20,11 @@ pub struct TokenDepositIntoExisting<'info> { )] pub group: AccountLoader<'info, Group>, - #[account(mut, has_one = group)] + #[account( + mut, + has_one = group, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen + )] pub account: AccountLoader<'info, MangoAccountFixed>, #[account( @@ -53,7 +57,12 @@ pub struct TokenDeposit<'info> { )] pub group: AccountLoader<'info, Group>, - #[account(mut, has_one = group, has_one = owner)] + #[account( + mut, + has_one = group, + has_one = owner, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen + )] pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, diff --git a/programs/mango-v4/src/instructions/token_liq_bankruptcy.rs b/programs/mango-v4/src/instructions/token_liq_bankruptcy.rs index 3f62e07c8..a60b61b36 100644 --- a/programs/mango-v4/src/instructions/token_liq_bankruptcy.rs +++ b/programs/mango-v4/src/instructions/token_liq_bankruptcy.rs @@ -28,7 +28,8 @@ pub struct TokenLiqBankruptcy<'info> { #[account( mut, - has_one = group + has_one = group, + constraint = liqor.load()?.is_operational() @ MangoError::AccountIsFrozen // liqor_owner is checked at #1 )] pub liqor: AccountLoader<'info, MangoAccountFixed>, @@ -36,7 +37,8 @@ pub struct TokenLiqBankruptcy<'info> { #[account( mut, - has_one = group + has_one = group, + constraint = liqee.load()?.is_operational() @ MangoError::AccountIsFrozen )] pub liqee: AccountLoader<'info, MangoAccountFixed>, diff --git a/programs/mango-v4/src/instructions/token_liq_with_token.rs b/programs/mango-v4/src/instructions/token_liq_with_token.rs index 9af24dbf1..4230bcf34 100644 --- a/programs/mango-v4/src/instructions/token_liq_with_token.rs +++ b/programs/mango-v4/src/instructions/token_liq_with_token.rs @@ -20,7 +20,8 @@ pub struct TokenLiqWithToken<'info> { #[account( mut, - has_one = group + has_one = group, + constraint = liqor.load()?.is_operational() @ MangoError::AccountIsFrozen // liqor_owner is checked at #1 )] pub liqor: AccountLoader<'info, MangoAccountFixed>, @@ -28,7 +29,8 @@ pub struct TokenLiqWithToken<'info> { #[account( mut, - has_one = group + has_one = group, + constraint = liqee.load()?.is_operational() @ MangoError::AccountIsFrozen )] pub liqee: AccountLoader<'info, MangoAccountFixed>, } diff --git a/programs/mango-v4/src/instructions/token_withdraw.rs b/programs/mango-v4/src/instructions/token_withdraw.rs index b86b98dfd..41c421dec 100644 --- a/programs/mango-v4/src/instructions/token_withdraw.rs +++ b/programs/mango-v4/src/instructions/token_withdraw.rs @@ -20,7 +20,12 @@ pub struct TokenWithdraw<'info> { )] pub group: AccountLoader<'info, Group>, - #[account(mut, has_one = group, has_one = owner)] + #[account( + mut, + has_one = group, + has_one = owner, + constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen + )] pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index 89f116ccb..2bf503156 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -222,6 +222,10 @@ pub mod mango_v4 { instructions::account_edit(ctx, name_opt, delegate_opt) } + pub fn aaccount_toggle_freeze(ctx: Context, freeze: bool) -> Result<()> { + instructions::account_toggle_freeze(ctx, freeze) + } + pub fn account_close(ctx: Context, force_close: bool) -> Result<()> { instructions::account_close(ctx, force_close) } diff --git a/programs/mango-v4/src/state/mango_account.rs b/programs/mango-v4/src/state/mango_account.rs index 1381382f6..f660d91e8 100644 --- a/programs/mango-v4/src/state/mango_account.rs +++ b/programs/mango-v4/src/state/mango_account.rs @@ -86,7 +86,9 @@ pub struct MangoAccount { /// Init health as calculated during HealthReginBegin, rounded up. pub health_region_begin_init_health: i64, - pub reserved: [u8; 240], + pub frozen_until: i64, + + pub reserved: [u8; 232], // dynamic pub header_version: u8, @@ -120,7 +122,8 @@ impl MangoAccount { padding: Default::default(), net_deposits: 0, health_region_begin_init_health: 0, - reserved: [0; 240], + frozen_until: 0, + reserved: [0; 232], header_version: DEFAULT_MANGO_ACCOUNT_VERSION, padding3: Default::default(), padding4: Default::default(), @@ -201,9 +204,10 @@ pub struct MangoAccountFixed { pub net_deposits: i64, pub perp_spot_transfers: i64, pub health_region_begin_init_health: i64, - pub reserved: [u8; 240], + pub frozen_until: u64, + pub reserved: [u8; 232], } -const_assert_eq!(size_of::(), 32 * 4 + 8 + 3 * 8 + 240); +const_assert_eq!(size_of::(), 32 * 4 + 8 + 3 * 8 + 8 + 232); const_assert_eq!(size_of::(), 400); const_assert_eq!(size_of::() % 8, 0); @@ -214,6 +218,11 @@ impl MangoAccountFixed { .trim_matches(char::from(0)) } + pub fn is_operational(&self) -> bool { + let now_ts: u64 = Clock::get().unwrap().unix_timestamp.try_into().unwrap(); + self.frozen_until < now_ts + } + pub fn is_owner_or_delegate(&self, ix_signer: Pubkey) -> bool { self.owner == ix_signer || self.delegate == ix_signer } diff --git a/ts/client/src/accounts/group.ts b/ts/client/src/accounts/group.ts index e5f2883fb..e138c977b 100644 --- a/ts/client/src/accounts/group.ts +++ b/ts/client/src/accounts/group.ts @@ -34,6 +34,7 @@ export class Group { insuranceVault: PublicKey; testing: number; version: number; + halted: number; addressLookupTables: PublicKey[]; }, ): Group { @@ -47,6 +48,7 @@ export class Group { obj.insuranceVault, obj.testing, obj.version, + obj.halted, obj.addressLookupTables, [], // addressLookupTablesList new Map(), // banksMapByName @@ -74,6 +76,7 @@ export class Group { public insuranceVault: PublicKey, public testing: number, public version: number, + public halted: number, public addressLookupTables: PublicKey[], public addressLookupTablesList: AddressLookupTableAccount[], public banksMapByName: Map, @@ -90,6 +93,10 @@ export class Group { public vaultAmountsMap: Map, ) {} + public isOperational(): boolean { + return this.halted === 0; + } + public async reloadAll(client: MangoClient, ids?: Id): Promise { // console.time('group.reload'); await Promise.all([ diff --git a/ts/client/src/accounts/mangoAccount.ts b/ts/client/src/accounts/mangoAccount.ts index ac87c3bec..6a2e2b3af 100644 --- a/ts/client/src/accounts/mangoAccount.ts +++ b/ts/client/src/accounts/mangoAccount.ts @@ -31,6 +31,7 @@ export class MangoAccount { netDeposits: BN; perpSpotTransfers: BN; healthRegionBeginInitHealth: BN; + frozenUntil: BN; headerVersion: number; tokens: unknown; serum3: unknown; @@ -50,6 +51,7 @@ export class MangoAccount { obj.netDeposits, obj.perpSpotTransfers, obj.healthRegionBeginInitHealth, + obj.frozenUntil, obj.headerVersion, obj.tokens as TokenPositionDto[], obj.serum3 as Serum3PositionDto[], @@ -71,6 +73,7 @@ export class MangoAccount { public netDeposits: BN, public perpSpotTransfers: BN, public healthRegionBeginInitHealth: BN, + public frozenUntil: BN, public headerVersion: number, tokens: TokenPositionDto[], serum3: Serum3PositionDto[], @@ -134,6 +137,10 @@ export class MangoAccount { ); } + public isOperational(): boolean { + return this.frozenUntil.lt(new BN(Date.now() / 1000)); + } + public tokensActive(): TokenPosition[] { return this.tokens.filter((token) => token.isActive()); } diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index e7564c10e..303f8a5ef 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -671,6 +671,21 @@ export class MangoClient { ); } + public async toggleMangoAccountFreeze( + group: Group, + mangoAccount: MangoAccount, + freeze: boolean, + ): Promise { + return await this.program.methods + .aaccountToggleFreeze(freeze) + .accounts({ + group: group.publicKey, + account: mangoAccount.publicKey, + admin: (this.program.provider as AnchorProvider).wallet.publicKey, + }) + .rpc(); + } + public async getMangoAccount( mangoAccount: MangoAccount | PublicKey, ): Promise { diff --git a/ts/client/src/mango_v4.ts b/ts/client/src/mango_v4.ts index 14803afbe..c7eb0425d 100644 --- a/ts/client/src/mango_v4.ts +++ b/ts/client/src/mango_v4.ts @@ -1022,6 +1022,32 @@ export type MangoV4 = { } ] }, + { + "name": "aaccountToggleFreeze", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false + }, + { + "name": "account", + "isMut": true, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + } + ], + "args": [ + { + "name": "freeze", + "type": "bool" + } + ] + }, { "name": "accountClose", "accounts": [ @@ -4002,12 +4028,16 @@ export type MangoV4 = { ], "type": "i64" }, + { + "name": "frozenUntil", + "type": "i64" + }, { "name": "reserved", "type": { "array": [ "u8", - 240 + 232 ] } }, @@ -5425,12 +5455,16 @@ export type MangoV4 = { "name": "healthRegionBeginInitHealth", "type": "i64" }, + { + "name": "frozenUntil", + "type": "u64" + }, { "name": "reserved", "type": { "array": [ "u8", - 240 + 232 ] } } @@ -7673,6 +7707,11 @@ export type MangoV4 = { "code": 6037, "name": "HasLiquidatableTrustedPerpPnl", "msg": "has liquidatable trusted perp pnl" + }, + { + "code": 6038, + "name": "AccountIsFrozen", + "msg": "account is frozen" } ] }; @@ -8701,6 +8740,32 @@ export const IDL: MangoV4 = { } ] }, + { + "name": "aaccountToggleFreeze", + "accounts": [ + { + "name": "group", + "isMut": false, + "isSigner": false + }, + { + "name": "account", + "isMut": true, + "isSigner": false + }, + { + "name": "admin", + "isMut": false, + "isSigner": true + } + ], + "args": [ + { + "name": "freeze", + "type": "bool" + } + ] + }, { "name": "accountClose", "accounts": [ @@ -11681,12 +11746,16 @@ export const IDL: MangoV4 = { ], "type": "i64" }, + { + "name": "frozenUntil", + "type": "i64" + }, { "name": "reserved", "type": { "array": [ "u8", - 240 + 232 ] } }, @@ -13104,12 +13173,16 @@ export const IDL: MangoV4 = { "name": "healthRegionBeginInitHealth", "type": "i64" }, + { + "name": "frozenUntil", + "type": "u64" + }, { "name": "reserved", "type": { "array": [ "u8", - 240 + 232 ] } } @@ -15352,6 +15425,11 @@ export const IDL: MangoV4 = { "code": 6037, "name": "HasLiquidatableTrustedPerpPnl", "msg": "has liquidatable trusted perp pnl" + }, + { + "code": 6038, + "name": "AccountIsFrozen", + "msg": "account is frozen" } ] };