mango account freeze (#372)

* mango account freeze

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* format

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* Fixes from review

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* Fixes from review

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
microwavedcola1 2023-01-12 13:08:10 +01:00 committed by GitHub
parent 5ef04d6d08
commit 7c69197505
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 293 additions and 42 deletions

View File

@ -81,6 +81,8 @@ pub enum MangoError {
HasLiquidatablePerpBasePosition,
#[msg("has liquidatable trusted perp pnl")]
HasLiquidatableTrustedPerpPnl,
#[msg("account is frozen")]
AccountIsFrozen,
}
impl MangoError {

View File

@ -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>,

View File

@ -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>,

View File

@ -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>,

View File

@ -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<AccountToggleFreeze>, 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(())
}

View File

@ -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>,

View File

@ -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>,
}

View File

@ -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;

View File

@ -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>,

View File

@ -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>,

View File

@ -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>,

View File

@ -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>,

View File

@ -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>,

View File

@ -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>,

View File

@ -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>,
}

View File

@ -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(

View File

@ -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)]

View File

@ -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>,

View File

@ -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

View File

@ -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

View File

@ -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>,

View File

@ -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>,

View File

@ -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>,

View File

@ -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>,

View File

@ -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)]

View File

@ -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>,

View File

@ -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>,

View File

@ -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>,

View File

@ -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>,

View File

@ -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>,
}

View File

@ -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>,

View File

@ -222,6 +222,10 @@ pub mod mango_v4 {
instructions::account_edit(ctx, name_opt, delegate_opt)
}
pub fn aaccount_toggle_freeze(ctx: Context<AccountToggleFreeze>, freeze: bool) -> Result<()> {
instructions::account_toggle_freeze(ctx, freeze)
}
pub fn account_close(ctx: Context<AccountClose>, force_close: bool) -> Result<()> {
instructions::account_close(ctx, force_close)
}

View File

@ -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::<MangoAccountFixed>(), 32 * 4 + 8 + 3 * 8 + 240);
const_assert_eq!(size_of::<MangoAccountFixed>(), 32 * 4 + 8 + 3 * 8 + 8 + 232);
const_assert_eq!(size_of::<MangoAccountFixed>(), 400);
const_assert_eq!(size_of::<MangoAccountFixed>() % 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
}

View File

@ -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<string, Bank[]>,
@ -90,6 +93,10 @@ export class Group {
public vaultAmountsMap: Map<string, BN>,
) {}
public isOperational(): boolean {
return this.halted === 0;
}
public async reloadAll(client: MangoClient, ids?: Id): Promise<void> {
// console.time('group.reload');
await Promise.all([

View File

@ -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());
}

View File

@ -671,6 +671,21 @@ export class MangoClient {
);
}
public async toggleMangoAccountFreeze(
group: Group,
mangoAccount: MangoAccount,
freeze: boolean,
): Promise<TransactionSignature> {
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<MangoAccount> {

View File

@ -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"
}
]
};