group level token deposit limit (#415)

* group level token deposit limit

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

* fix

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

* fix

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

* fix

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>

* 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-23 14:58:03 +01:00 committed by GitHub
parent f0c797a2e4
commit 64dda20cb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 106 additions and 25 deletions

View File

@ -85,6 +85,8 @@ pub enum MangoError {
AccountIsFrozen, AccountIsFrozen,
#[msg("has open perp taker fills")] #[msg("has open perp taker fills")]
HasOpenPerpTakerFills, HasOpenPerpTakerFills,
#[msg("deposit crosses the current group deposit limit")]
DepositLimit,
} }
impl MangoError { impl MangoError {

View File

@ -346,6 +346,22 @@ impl HealthCache {
health 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> { pub fn token_info(&self, token_index: TokenIndex) -> Result<&TokenInfo> {
Ok(&self.token_infos[self.token_info_index(token_index)?]) Ok(&self.token_infos[self.token_info_index(token_index)?])
} }

View File

@ -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 /// The health ratio is
/// - 0 if health is 0 - meaning assets = liabs /// - 0 if health is 0 - meaning assets = liabs
/// - 100 if there's 2x as many assets as liabs /// - 100 if there's 2x as many assets as liabs

View File

@ -21,6 +21,7 @@ pub fn group_edit(
security_admin_opt: Option<Pubkey>, security_admin_opt: Option<Pubkey>,
testing_opt: Option<u8>, testing_opt: Option<u8>,
version_opt: Option<u8>, version_opt: Option<u8>,
deposit_limit_quote_opt: Option<u64>,
) -> Result<()> { ) -> Result<()> {
let mut group = ctx.accounts.group.load_mut()?; let mut group = ctx.accounts.group.load_mut()?;
@ -44,5 +45,9 @@ pub fn group_edit(
group.version = version; group.version = version;
} }
if let Some(deposit_limit_quote) = deposit_limit_quote_opt {
group.deposit_limit_quote = deposit_limit_quote;
}
Ok(()) Ok(())
} }

View File

@ -186,15 +186,15 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
// //
// Health computation // 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. // 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. // Also, TokenDeposit is one of the rare instructions that is allowed even during being_liquidated.
// //
if !account.fixed.is_in_health_region() { 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 was_being_liquidated = account.being_liquidated();
let recovered = account.fixed.maybe_recover_from_being_liquidated(health); let recovered = account.fixed.maybe_recover_from_being_liquidated(health);
require!( 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::<u64>()
.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 // 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 // remaining_accounts for all banks/oracles, including the account that will now be

View File

@ -48,6 +48,7 @@ pub mod mango_v4 {
security_admin_opt: Option<Pubkey>, security_admin_opt: Option<Pubkey>,
testing_opt: Option<u8>, testing_opt: Option<u8>,
version_opt: Option<u8>, version_opt: Option<u8>,
deposit_limit_quote_opt: Option<u64>,
) -> Result<()> { ) -> Result<()> {
instructions::group_edit( instructions::group_edit(
ctx, ctx,
@ -56,6 +57,7 @@ pub mod mango_v4 {
security_admin_opt, security_admin_opt,
testing_opt, testing_opt,
version_opt, version_opt,
deposit_limit_quote_opt,
) )
} }

View File

@ -39,11 +39,15 @@ pub struct Group {
pub security_admin: Pubkey, 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!( const_assert_eq!(
size_of::<Group>(), size_of::<Group>(),
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::<Group>(), 2736); const_assert_eq!(size_of::<Group>(), 2736);
const_assert_eq!(size_of::<Group>() % 8, 0); const_assert_eq!(size_of::<Group>() % 8, 0);

View File

@ -134,6 +134,7 @@ export class MangoClient {
securityAdmin?: PublicKey, securityAdmin?: PublicKey,
testing?: number, testing?: number,
version?: number, version?: number,
depositLimitQuote?: number,
): Promise<TransactionSignature> { ): Promise<TransactionSignature> {
return await this.program.methods return await this.program.methods
.groupEdit( .groupEdit(
@ -142,6 +143,7 @@ export class MangoClient {
securityAdmin ?? null, securityAdmin ?? null,
testing ?? null, testing ?? null,
version ?? null, version ?? null,
depositLimitQuote !== undefined ? new BN(depositLimitQuote) : null,
) )
.accounts({ .accounts({
group: group.publicKey, group: group.publicKey,

View File

@ -138,6 +138,12 @@ export type MangoV4 = {
"type": { "type": {
"option": "u8" "option": "u8"
} }
},
{
"name": "depositLimitQuoteOpt",
"type": {
"option": "u64"
}
} }
] ]
}, },
@ -3982,12 +3988,16 @@ export type MangoV4 = {
"name": "securityAdmin", "name": "securityAdmin",
"type": "publicKey" "type": "publicKey"
}, },
{
"name": "depositLimitQuote",
"type": "u64"
},
{ {
"name": "reserved", "name": "reserved",
"type": { "type": {
"array": [ "array": [
"u8", "u8",
1888 1880
] ]
} }
} }
@ -5086,6 +5096,10 @@ export type MangoV4 = {
{ {
"name": "hasOpenOrders", "name": "hasOpenOrders",
"type": "bool" "type": "bool"
},
{
"name": "hasOpenFills",
"type": "bool"
} }
] ]
} }
@ -7910,6 +7924,16 @@ export type MangoV4 = {
"code": 6038, "code": 6038,
"name": "AccountIsFrozen", "name": "AccountIsFrozen",
"msg": "account is frozen" "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": { "type": {
"option": "u8" "option": "u8"
} }
},
{
"name": "depositLimitQuoteOpt",
"type": {
"option": "u64"
}
} }
] ]
}, },
@ -11898,12 +11928,16 @@ export const IDL: MangoV4 = {
"name": "securityAdmin", "name": "securityAdmin",
"type": "publicKey" "type": "publicKey"
}, },
{
"name": "depositLimitQuote",
"type": "u64"
},
{ {
"name": "reserved", "name": "reserved",
"type": { "type": {
"array": [ "array": [
"u8", "u8",
1888 1880
] ]
} }
} }
@ -13002,6 +13036,10 @@ export const IDL: MangoV4 = {
{ {
"name": "hasOpenOrders", "name": "hasOpenOrders",
"type": "bool" "type": "bool"
},
{
"name": "hasOpenFills",
"type": "bool"
} }
] ]
} }
@ -15826,6 +15864,16 @@ export const IDL: MangoV4 = {
"code": 6038, "code": 6038,
"name": "AccountIsFrozen", "name": "AccountIsFrozen",
"msg": "account is frozen" "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"
} }
] ]
}; };