diff --git a/programs/mango-v4/src/error.rs b/programs/mango-v4/src/error.rs index d162c7189..353d4d98e 100644 --- a/programs/mango-v4/src/error.rs +++ b/programs/mango-v4/src/error.rs @@ -83,6 +83,8 @@ pub enum MangoError { HasLiquidatableTrustedPerpPnl, #[msg("account is frozen")] AccountIsFrozen, + #[msg("Init Asset Weight can't be negative")] + InitAssetWeightCantBeNegative, #[msg("has open perp taker fills")] HasOpenPerpTakerFills, #[msg("deposit crosses the current group deposit limit")] diff --git a/programs/mango-v4/src/instructions/perp_edit_market.rs b/programs/mango-v4/src/instructions/perp_edit_market.rs index bd3fabd49..e5101954e 100644 --- a/programs/mango-v4/src/instructions/perp_edit_market.rs +++ b/programs/mango-v4/src/instructions/perp_edit_market.rs @@ -1,4 +1,4 @@ -use crate::{accounts_zerocopy::AccountInfoRef, state::*}; +use crate::{accounts_zerocopy::AccountInfoRef, error::MangoError, state::*}; use anchor_lang::prelude::*; use fixed::types::I80F48; @@ -6,10 +6,8 @@ use crate::logs::PerpMarketMetaDataLog; #[derive(Accounts)] pub struct PerpEditMarket<'info> { - #[account( - has_one = admin - )] pub group: AccountLoader<'info, Group>, + // group <-> admin relation is checked at #1 pub admin: Signer<'info>, #[account( @@ -55,17 +53,15 @@ pub fn perp_edit_market( reduce_only_opt: Option, reset_stable_price: bool, ) -> Result<()> { + let group = ctx.accounts.group.load()?; + let mut perp_market = ctx.accounts.perp_market.load_mut()?; - // note: unchanged fields are inline, and match exact definition in perp_register_market - // please maintain, and don't remove, makes it easy to reason about which support admin modification - - // unchanged - - // name - // group + let mut require_group_admin = false; if let Some(oracle_config) = oracle_config_opt { perp_market.oracle_config = oracle_config.to_oracle_config(); + require_group_admin = true; }; if let Some(oracle) = oracle_opt { perp_market.oracle = oracle; @@ -78,112 +74,144 @@ pub fn perp_edit_market( oracle_price.to_num(), Clock::get()?.unix_timestamp.try_into().unwrap(), ); + require_group_admin = true; } - // unchanged - - // bids - // asks - // event_queue - // quote_lot_size - // base_lot_size - if let Some(maint_base_asset_weight) = maint_base_asset_weight_opt { perp_market.maint_base_asset_weight = I80F48::from_num(maint_base_asset_weight); + require_group_admin = true; } if let Some(init_base_asset_weight) = init_base_asset_weight_opt { + require_gte!( + init_base_asset_weight, + 0.0, + MangoError::InitAssetWeightCantBeNegative + ); + + let old_init_base_asset_weight = perp_market.init_base_asset_weight; perp_market.init_base_asset_weight = I80F48::from_num(init_base_asset_weight); + + // security admin can only reduce init_base_asset_weight + if old_init_base_asset_weight < perp_market.init_base_asset_weight { + require_group_admin = true; + } } if let Some(maint_base_liab_weight) = maint_base_liab_weight_opt { perp_market.maint_base_liab_weight = I80F48::from_num(maint_base_liab_weight); + require_group_admin = true; } if let Some(init_base_liab_weight) = init_base_liab_weight_opt { perp_market.init_base_liab_weight = I80F48::from_num(init_base_liab_weight); + require_group_admin = true; } if let Some(maint_pnl_asset_weight) = maint_pnl_asset_weight_opt { perp_market.maint_pnl_asset_weight = I80F48::from_num(maint_pnl_asset_weight); + require_group_admin = true; } if let Some(init_pnl_asset_weight) = init_pnl_asset_weight_opt { perp_market.init_pnl_asset_weight = I80F48::from_num(init_pnl_asset_weight); + require_group_admin = true; } if let Some(liquidation_fee) = liquidation_fee_opt { perp_market.liquidation_fee = I80F48::from_num(liquidation_fee); + require_group_admin = true; } if let Some(maker_fee) = maker_fee_opt { perp_market.maker_fee = I80F48::from_num(maker_fee); + require_group_admin = true; } if let Some(taker_fee) = taker_fee_opt { perp_market.taker_fee = I80F48::from_num(taker_fee); + require_group_admin = true; } if let Some(min_funding) = min_funding_opt { perp_market.min_funding = I80F48::from_num(min_funding); + require_group_admin = true; } if let Some(max_funding) = max_funding_opt { perp_market.max_funding = I80F48::from_num(max_funding); + require_group_admin = true; } if let Some(impact_quantity) = impact_quantity_opt { perp_market.impact_quantity = impact_quantity; + require_group_admin = true; } if let Some(fee_penalty) = fee_penalty_opt { perp_market.fee_penalty = fee_penalty; + require_group_admin = true; } - // unchanged - - // long_funding - // short_funding - // funding_last_updated - // open_interest - // seq_num - // fees_accrued - // bump - if let Some(base_decimals) = base_decimals_opt { perp_market.base_decimals = base_decimals; + require_group_admin = true; } - // unchanged - - // perp_market_index - - // unchanged - - // registration_time - if let Some(group_insurance_fund) = group_insurance_fund_opt { perp_market.set_elligible_for_group_insurance_fund(group_insurance_fund); + require_group_admin = true; } if let Some(settle_fee_flat) = settle_fee_flat_opt { perp_market.settle_fee_flat = settle_fee_flat; + require_group_admin = true; } if let Some(settle_fee_amount_threshold) = settle_fee_amount_threshold_opt { perp_market.settle_fee_amount_threshold = settle_fee_amount_threshold; + require_group_admin = true; } if let Some(settle_fee_fraction_low_health) = settle_fee_fraction_low_health_opt { perp_market.settle_fee_fraction_low_health = settle_fee_fraction_low_health; + require_group_admin = true; } if let Some(stable_price_delay_interval_seconds) = stable_price_delay_interval_seconds_opt { // Updating this makes the old delay values slightly inconsistent perp_market.stable_price_model.delay_interval_seconds = stable_price_delay_interval_seconds; + require_group_admin = true; } if let Some(stable_price_delay_growth_limit) = stable_price_delay_growth_limit_opt { perp_market.stable_price_model.delay_growth_limit = stable_price_delay_growth_limit; + require_group_admin = true; } if let Some(stable_price_growth_limit) = stable_price_growth_limit_opt { perp_market.stable_price_model.stable_growth_limit = stable_price_growth_limit; + require_group_admin = true; } if let Some(settle_pnl_limit_factor_opt) = settle_pnl_limit_factor_opt { perp_market.settle_pnl_limit_factor = settle_pnl_limit_factor_opt; + require_group_admin = true; } if let Some(settle_pnl_limit_window_size_ts) = settle_pnl_limit_window_size_ts_opt { perp_market.settle_pnl_limit_window_size_ts = settle_pnl_limit_window_size_ts; + require_group_admin = true; } if let Some(reduce_only) = reduce_only_opt { perp_market.reduce_only = u8::from(reduce_only); + + // security admin can only enable reduce_only + if !reduce_only { + require_group_admin = true; + } }; + // account constraint #1 + if require_group_admin { + require!( + group.admin == ctx.accounts.admin.key(), + MangoError::SomeError + ); + } else { + require!( + group.admin == ctx.accounts.admin.key() + || group.security_admin == ctx.accounts.admin.key(), + MangoError::SomeError + ); + } + emit!(PerpMarketMetaDataLog { mango_group: ctx.accounts.group.key(), perp_market: ctx.accounts.perp_market.key(), diff --git a/programs/mango-v4/src/instructions/token_edit.rs b/programs/mango-v4/src/instructions/token_edit.rs index 549faf083..3b6fcc4ed 100644 --- a/programs/mango-v4/src/instructions/token_edit.rs +++ b/programs/mango-v4/src/instructions/token_edit.rs @@ -5,6 +5,7 @@ use fixed::types::I80F48; use super::InterestRateParams; use crate::accounts_zerocopy::{AccountInfoRef, LoadMutZeroCopyRef}; +use crate::error::MangoError; use crate::state::*; use crate::logs::TokenMetaDataLog; @@ -15,10 +16,8 @@ use crate::logs::TokenMetaDataLog; /// in MintInfo order. #[derive(Accounts)] pub struct TokenEdit<'info> { - #[account( - has_one = admin - )] pub group: AccountLoader<'info, Group>, + // group <-> admin relation is checked at #1 pub admin: Signer<'info>, #[account( @@ -60,27 +59,23 @@ pub fn token_edit( reset_net_borrow_limit: bool, reduce_only_opt: Option, ) -> Result<()> { + let group = ctx.accounts.group.load()?; + let mut mint_info = ctx.accounts.mint_info.load_mut()?; mint_info.verify_banks_ais(ctx.remaining_accounts)?; + let mut require_group_admin = false; for ai in ctx.remaining_accounts.iter() { let mut bank = ai.load_mut::()?; - // note: unchanged fields are inline, and match exact definition in register_token - // please maintain, and don't remove, makes it easy to reason about which support admin modification - - // unchanged - - // name - // group - // mint - // vault - if let Some(oracle_config) = oracle_config_opt.as_ref() { bank.oracle_config = oracle_config.to_oracle_config(); + require_group_admin = true; }; if let Some(oracle) = oracle_opt { bank.oracle = oracle; mint_info.oracle = oracle; + require_group_admin = true; } if reset_stable_price { require_keys_eq!(bank.oracle, ctx.accounts.oracle.key()); @@ -90,21 +85,14 @@ pub fn token_edit( oracle_price.to_num(), Clock::get()?.unix_timestamp.try_into().unwrap(), ); + require_group_admin = true; } if let Some(group_insurance_fund) = group_insurance_fund_opt { mint_info.group_insurance_fund = u8::from(group_insurance_fund); + require_group_admin = true; }; - // unchanged - - // deposit_index - // borrow_index - // cached_indexed_total_deposits - // cached_indexed_total_borrows - // indexed_deposits - // indexed_borrows - // last_updated - if let Some(ref interest_rate_params) = interest_rate_params_opt { // TODO: add a require! verifying relation between the parameters bank.adjustment_factor = I80F48::from_num(interest_rate_params.adjustment_factor); @@ -113,79 +101,113 @@ pub fn token_edit( bank.util1 = I80F48::from_num(interest_rate_params.util1); bank.rate1 = I80F48::from_num(interest_rate_params.rate1); bank.max_rate = I80F48::from_num(interest_rate_params.max_rate); + require_group_admin = true; } - // unchanged - - // collected_fees_native - if let Some(loan_origination_fee_rate) = loan_origination_fee_rate_opt { bank.loan_origination_fee_rate = I80F48::from_num(loan_origination_fee_rate); + require_group_admin = true; } if let Some(loan_fee_rate) = loan_fee_rate_opt { bank.loan_fee_rate = I80F48::from_num(loan_fee_rate); + require_group_admin = true; } if let Some(maint_asset_weight) = maint_asset_weight_opt { bank.maint_asset_weight = I80F48::from_num(maint_asset_weight); + require_group_admin = true; } if let Some(init_asset_weight) = init_asset_weight_opt { + require_gte!( + init_asset_weight, + 0.0, + MangoError::InitAssetWeightCantBeNegative + ); + + let old_init_asset_weight = bank.init_asset_weight; bank.init_asset_weight = I80F48::from_num(init_asset_weight); + + // security admin can only reduce init_base_asset_weight + if old_init_asset_weight < bank.init_asset_weight { + require_group_admin = true; + } } if let Some(maint_liab_weight) = maint_liab_weight_opt { bank.maint_liab_weight = I80F48::from_num(maint_liab_weight); + require_group_admin = true; } if let Some(init_liab_weight) = init_liab_weight_opt { bank.init_liab_weight = I80F48::from_num(init_liab_weight); + require_group_admin = true; } if let Some(liquidation_fee) = liquidation_fee_opt { bank.liquidation_fee = I80F48::from_num(liquidation_fee); + require_group_admin = true; } if let Some(stable_price_delay_interval_seconds) = stable_price_delay_interval_seconds_opt { // Updating this makes the old delay values slightly inconsistent bank.stable_price_model.delay_interval_seconds = stable_price_delay_interval_seconds; + require_group_admin = true; } if let Some(stable_price_delay_growth_limit) = stable_price_delay_growth_limit_opt { bank.stable_price_model.delay_growth_limit = stable_price_delay_growth_limit; + require_group_admin = true; } if let Some(stable_price_growth_limit) = stable_price_growth_limit_opt { bank.stable_price_model.stable_growth_limit = stable_price_growth_limit; + require_group_admin = true; } if let Some(min_vault_to_deposits_ratio) = min_vault_to_deposits_ratio_opt { bank.min_vault_to_deposits_ratio = min_vault_to_deposits_ratio; + require_group_admin = true; } if let Some(net_borrow_limit_per_window_quote) = net_borrow_limit_per_window_quote_opt { bank.net_borrow_limit_per_window_quote = net_borrow_limit_per_window_quote; + require_group_admin = true; } if let Some(net_borrow_limit_window_size_ts) = net_borrow_limit_window_size_ts_opt { bank.net_borrow_limit_window_size_ts = net_borrow_limit_window_size_ts; + require_group_admin = true; } if reset_net_borrow_limit { bank.net_borrows_in_window = 0; bank.last_net_borrows_window_start_ts = 0; + require_group_admin = true; } if let Some(borrow_weight_scale_start_quote) = borrow_weight_scale_start_quote_opt { bank.borrow_weight_scale_start_quote = borrow_weight_scale_start_quote; + require_group_admin = true; } if let Some(deposit_weight_scale_start_quote) = deposit_weight_scale_start_quote_opt { bank.deposit_weight_scale_start_quote = deposit_weight_scale_start_quote; + require_group_admin = true; } if let Some(reduce_only) = reduce_only_opt { bank.reduce_only = u8::from(reduce_only); - }; - // unchanged - - // dust - // flash_loan_token_account_initial - // flash_loan_approved_amount - // token_index - // bump - // mint_decimals - // bank_num - // reserved + // security admin can only enable reduce_only + if !reduce_only { + require_group_admin = true; + } + }; + } + + // account constraint #1 + if require_group_admin { + require!( + group.admin == ctx.accounts.admin.key(), + MangoError::SomeError + ); + } else { + require!( + group.admin == ctx.accounts.admin.key() + || group.security_admin == ctx.accounts.admin.key(), + MangoError::SomeError + ); } // Assumes that there is at least one bank diff --git a/ts/client/src/mango_v4.ts b/ts/client/src/mango_v4.ts index 4928755dd..fbf1ccd1a 100644 --- a/ts/client/src/mango_v4.ts +++ b/ts/client/src/mango_v4.ts @@ -7927,11 +7927,16 @@ export type MangoV4 = { }, { "code": 6039, + "name": "InitAssetWeightCantBeNegative", + "msg": "Init Asset Weight can't be negative" + }, + { + "code": 6040, "name": "HasOpenPerpTakerFills", "msg": "has open perp taker fills" }, { - "code": 6040, + "code": 6041, "name": "DepositLimit", "msg": "deposit crosses the current group deposit limit" } @@ -15867,11 +15872,16 @@ export const IDL: MangoV4 = { }, { "code": 6039, + "name": "InitAssetWeightCantBeNegative", + "msg": "Init Asset Weight can't be negative" + }, + { + "code": 6040, "name": "HasOpenPerpTakerFills", "msg": "has open perp taker fills" }, { - "code": 6040, + "code": 6041, "name": "DepositLimit", "msg": "deposit crosses the current group deposit limit" }