From 64dda20cb51978d3893ce5df4c80b3f0267010e6 Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Mon, 23 Jan 2023 14:58:03 +0100 Subject: [PATCH] group level token deposit limit (#415) * group level token deposit limit Signed-off-by: microwavedcola1 * fix Signed-off-by: microwavedcola1 * fix Signed-off-by: microwavedcola1 * fix Signed-off-by: microwavedcola1 * fixes from review Signed-off-by: microwavedcola1 * fixes from review Signed-off-by: microwavedcola1 * fixes from review Signed-off-by: microwavedcola1 Signed-off-by: microwavedcola1 --- programs/mango-v4/src/error.rs | 2 + programs/mango-v4/src/health/cache.rs | 16 ++++++ programs/mango-v4/src/health/client.rs | 16 ------ .../mango-v4/src/instructions/group_edit.rs | 5 ++ .../src/instructions/token_deposit.rs | 28 ++++++++-- programs/mango-v4/src/lib.rs | 2 + programs/mango-v4/src/state/group.rs | 8 ++- ts/client/src/client.ts | 2 + ts/client/src/mango_v4.ts | 52 ++++++++++++++++++- 9 files changed, 106 insertions(+), 25 deletions(-) diff --git a/programs/mango-v4/src/error.rs b/programs/mango-v4/src/error.rs index 1df9789fb..d162c7189 100644 --- a/programs/mango-v4/src/error.rs +++ b/programs/mango-v4/src/error.rs @@ -85,6 +85,8 @@ pub enum MangoError { AccountIsFrozen, #[msg("has open perp taker fills")] HasOpenPerpTakerFills, + #[msg("deposit crosses the current group deposit limit")] + DepositLimit, } impl MangoError { diff --git a/programs/mango-v4/src/health/cache.rs b/programs/mango-v4/src/health/cache.rs index e712a478c..1fef1dc0c 100644 --- a/programs/mango-v4/src/health/cache.rs +++ b/programs/mango-v4/src/health/cache.rs @@ -346,6 +346,22 @@ impl HealthCache { health } + /// Sum of only the positive health components (assets) and + /// sum of absolute values of all negative health components (liabs, always >= 0) + pub fn health_assets_and_liabs(&self, health_type: HealthType) -> (I80F48, I80F48) { + let mut assets = I80F48::ZERO; + let mut liabs = I80F48::ZERO; + let sum = |contrib| { + if contrib > 0 { + cm!(assets += contrib); + } else { + cm!(liabs -= contrib); + } + }; + self.health_sum(health_type, sum); + (assets, liabs) + } + pub fn token_info(&self, token_index: TokenIndex) -> Result<&TokenInfo> { Ok(&self.token_infos[self.token_info_index(token_index)?]) } diff --git a/programs/mango-v4/src/health/client.rs b/programs/mango-v4/src/health/client.rs index 97aca881f..7c0985d6e 100644 --- a/programs/mango-v4/src/health/client.rs +++ b/programs/mango-v4/src/health/client.rs @@ -21,22 +21,6 @@ impl HealthCache { } } - /// Sum of only the positive health components (assets) and - /// sum of absolute values of all negative health components (liabs, always >= 0) - pub fn health_assets_and_liabs(&self, health_type: HealthType) -> (I80F48, I80F48) { - let mut assets = I80F48::ZERO; - let mut liabs = I80F48::ZERO; - let sum = |contrib| { - if contrib > 0 { - cm!(assets += contrib); - } else { - cm!(liabs -= contrib); - } - }; - self.health_sum(health_type, sum); - (assets, liabs) - } - /// The health ratio is /// - 0 if health is 0 - meaning assets = liabs /// - 100 if there's 2x as many assets as liabs diff --git a/programs/mango-v4/src/instructions/group_edit.rs b/programs/mango-v4/src/instructions/group_edit.rs index dd7bf4183..adc48bab3 100644 --- a/programs/mango-v4/src/instructions/group_edit.rs +++ b/programs/mango-v4/src/instructions/group_edit.rs @@ -21,6 +21,7 @@ pub fn group_edit( security_admin_opt: Option, testing_opt: Option, version_opt: Option, + deposit_limit_quote_opt: Option, ) -> Result<()> { let mut group = ctx.accounts.group.load_mut()?; @@ -44,5 +45,9 @@ pub fn group_edit( group.version = version; } + if let Some(deposit_limit_quote) = deposit_limit_quote_opt { + group.deposit_limit_quote = deposit_limit_quote; + } + Ok(()) } diff --git a/programs/mango-v4/src/instructions/token_deposit.rs b/programs/mango-v4/src/instructions/token_deposit.rs index 6afa21b0d..8796e6918 100644 --- a/programs/mango-v4/src/instructions/token_deposit.rs +++ b/programs/mango-v4/src/instructions/token_deposit.rs @@ -186,15 +186,15 @@ impl<'a, 'info> DepositCommon<'a, 'info> { // // Health computation // + let retriever = new_fixed_order_account_retriever(remaining_accounts, &account.borrow())?; + let cache = new_health_cache(&account.borrow(), &retriever)?; + let health = cache.health(HealthType::Init); + msg!("health: {}", health); + // Since depositing can only increase health, we can skip the usual pre-health computation. // Also, TokenDeposit is one of the rare instructions that is allowed even during being_liquidated. // if !account.fixed.is_in_health_region() { - let retriever = - new_fixed_order_account_retriever(remaining_accounts, &account.borrow())?; - let health = compute_health(&account.borrow(), HealthType::Init, &retriever) - .context("post-deposit init health")?; - msg!("health: {}", health); let was_being_liquidated = account.being_liquidated(); let recovered = account.fixed.maybe_recover_from_being_liquidated(health); require!( @@ -203,6 +203,24 @@ impl<'a, 'info> DepositCommon<'a, 'info> { ); } + // Group level deposit limit on account + let assets = cache + .health_assets_and_liabs(HealthType::Init) + .0 + .round_to_zero() + .checked_to_num::() + .unwrap(); + let group = self.group.load()?; + if group.deposit_limit_quote > 0 && assets > group.deposit_limit_quote { + require_msg_typed!( + assets <= group.deposit_limit_quote, + MangoError::DepositLimit, + "assets ({}) can't cross deposit limit on the group ({})", + assets, + group.deposit_limit_quote + ); + } + // // Deactivate the position only after the health check because the user passed in // remaining_accounts for all banks/oracles, including the account that will now be diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index 4065269a1..c513c5491 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -48,6 +48,7 @@ pub mod mango_v4 { security_admin_opt: Option, testing_opt: Option, version_opt: Option, + deposit_limit_quote_opt: Option, ) -> Result<()> { instructions::group_edit( ctx, @@ -56,6 +57,7 @@ pub mod mango_v4 { security_admin_opt, testing_opt, version_opt, + deposit_limit_quote_opt, ) } diff --git a/programs/mango-v4/src/state/group.rs b/programs/mango-v4/src/state/group.rs index 057b11494..4d8934f66 100644 --- a/programs/mango-v4/src/state/group.rs +++ b/programs/mango-v4/src/state/group.rs @@ -39,11 +39,15 @@ pub struct Group { pub security_admin: Pubkey, - pub reserved: [u8; 1888], + // Deposit limit for a mango account in quote native, enforced on quote value of account assets + // Set to 0 to disable, which also means by default there is no limit + pub deposit_limit_quote: u64, + + pub reserved: [u8; 1880], } const_assert_eq!( size_of::(), - 32 + 4 + 32 * 2 + 4 + 32 * 2 + 4 + 4 + 20 * 32 + 32 + 1888 + 32 + 4 + 32 * 2 + 4 + 32 * 2 + 4 + 4 + 20 * 32 + 32 + 8 + 1880 ); const_assert_eq!(size_of::(), 2736); const_assert_eq!(size_of::() % 8, 0); diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index ff389b268..f13c469b2 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -134,6 +134,7 @@ export class MangoClient { securityAdmin?: PublicKey, testing?: number, version?: number, + depositLimitQuote?: number, ): Promise { return await this.program.methods .groupEdit( @@ -142,6 +143,7 @@ export class MangoClient { securityAdmin ?? null, testing ?? null, version ?? null, + depositLimitQuote !== undefined ? new BN(depositLimitQuote) : null, ) .accounts({ group: group.publicKey, diff --git a/ts/client/src/mango_v4.ts b/ts/client/src/mango_v4.ts index 98f0c103e..4928755dd 100644 --- a/ts/client/src/mango_v4.ts +++ b/ts/client/src/mango_v4.ts @@ -138,6 +138,12 @@ export type MangoV4 = { "type": { "option": "u8" } + }, + { + "name": "depositLimitQuoteOpt", + "type": { + "option": "u64" + } } ] }, @@ -3982,12 +3988,16 @@ export type MangoV4 = { "name": "securityAdmin", "type": "publicKey" }, + { + "name": "depositLimitQuote", + "type": "u64" + }, { "name": "reserved", "type": { "array": [ "u8", - 1888 + 1880 ] } } @@ -5086,6 +5096,10 @@ export type MangoV4 = { { "name": "hasOpenOrders", "type": "bool" + }, + { + "name": "hasOpenFills", + "type": "bool" } ] } @@ -7910,6 +7924,16 @@ export type MangoV4 = { "code": 6038, "name": "AccountIsFrozen", "msg": "account is frozen" + }, + { + "code": 6039, + "name": "HasOpenPerpTakerFills", + "msg": "has open perp taker fills" + }, + { + "code": 6040, + "name": "DepositLimit", + "msg": "deposit crosses the current group deposit limit" } ] }; @@ -8054,6 +8078,12 @@ export const IDL: MangoV4 = { "type": { "option": "u8" } + }, + { + "name": "depositLimitQuoteOpt", + "type": { + "option": "u64" + } } ] }, @@ -11898,12 +11928,16 @@ export const IDL: MangoV4 = { "name": "securityAdmin", "type": "publicKey" }, + { + "name": "depositLimitQuote", + "type": "u64" + }, { "name": "reserved", "type": { "array": [ "u8", - 1888 + 1880 ] } } @@ -13002,6 +13036,10 @@ export const IDL: MangoV4 = { { "name": "hasOpenOrders", "type": "bool" + }, + { + "name": "hasOpenFills", + "type": "bool" } ] } @@ -15826,6 +15864,16 @@ export const IDL: MangoV4 = { "code": 6038, "name": "AccountIsFrozen", "msg": "account is frozen" + }, + { + "code": 6039, + "name": "HasOpenPerpTakerFills", + "msg": "has open perp taker fills" + }, + { + "code": 6040, + "name": "DepositLimit", + "msg": "deposit crosses the current group deposit limit" } ] };