Skip to content

Commit

Permalink
Bank: allow maint weights to change over time (#780)
Browse files Browse the repository at this point in the history
- token_edit can set it up to gradually scale to new target values
- security admin can abort an ongoing change via token_edit
- all health computations are now time dependent and get the weight
  based on it
- when the change is done, the keeper "cleans up" and moves the new
  values into the default fields
  • Loading branch information
ckamm authored Nov 14, 2023
1 parent 8e1f512 commit 93d85c3
Show file tree
Hide file tree
Showing 37 changed files with 750 additions and 225 deletions.
10 changes: 8 additions & 2 deletions lib/client/src/health_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use mango_v4::accounts_zerocopy::KeyedAccountSharedData;
use mango_v4::health::{FixedOrderAccountRetriever, HealthCache};
use mango_v4::state::MangoAccountValue;

use std::time::{SystemTime, UNIX_EPOCH};

pub async fn new(
context: &MangoGroupContext,
account_fetcher: &impl AccountFetcher,
Expand Down Expand Up @@ -33,7 +35,9 @@ pub async fn new(
begin_serum3: active_token_len * 2 + active_perp_len * 2,
staleness_slot: None,
};
mango_v4::health::new_health_cache(&account.borrow(), &retriever).context("make health cache")
let now_ts = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
mango_v4::health::new_health_cache(&account.borrow(), &retriever, now_ts)
.context("make health cache")
}

pub fn new_sync(
Expand Down Expand Up @@ -64,5 +68,7 @@ pub fn new_sync(
begin_serum3: active_token_len * 2 + active_perp_len * 2,
staleness_slot: None,
};
mango_v4::health::new_health_cache(&account.borrow(), &retriever).context("make health cache")
let now_ts = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
mango_v4::health::new_health_cache(&account.borrow(), &retriever, now_ts)
.context("make health cache")
}
56 changes: 55 additions & 1 deletion mango_v4.json
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,34 @@
"type": {
"option": "f32"
}
},
{
"name": "maintWeightShiftStartOpt",
"type": {
"option": "u64"
}
},
{
"name": "maintWeightShiftEndOpt",
"type": {
"option": "u64"
}
},
{
"name": "maintWeightShiftAssetTargetOpt",
"type": {
"option": "f32"
}
},
{
"name": "maintWeightShiftLiabTargetOpt",
"type": {
"option": "f32"
}
},
{
"name": "maintWeightShiftAbort",
"type": "bool"
}
]
},
Expand Down Expand Up @@ -7079,12 +7107,38 @@
"name": "depositsInSerum",
"type": "i64"
},
{
"name": "maintWeightShiftStart",
"type": "u64"
},
{
"name": "maintWeightShiftEnd",
"type": "u64"
},
{
"name": "maintWeightShiftDurationInv",
"type": {
"defined": "I80F48"
}
},
{
"name": "maintWeightShiftAssetTarget",
"type": {
"defined": "I80F48"
}
},
{
"name": "maintWeightShiftLiabTarget",
"type": {
"defined": "I80F48"
}
},
{
"name": "reserved",
"type": {
"array": [
"u8",
2072
2008
]
}
}
Expand Down
24 changes: 16 additions & 8 deletions programs/mango-v4/src/health/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,18 +94,20 @@ pub fn compute_health_from_fixed_accounts(
account: &MangoAccountRef,
health_type: HealthType,
ais: &[AccountInfo],
now_ts: u64,
) -> Result<I80F48> {
let retriever = new_fixed_order_account_retriever(ais, account)?;
Ok(new_health_cache(account, &retriever)?.health(health_type))
Ok(new_health_cache(account, &retriever, now_ts)?.health(health_type))
}

/// Compute health with an arbitrary AccountRetriever
pub fn compute_health(
account: &MangoAccountRef,
health_type: HealthType,
retriever: &impl AccountRetriever,
now_ts: u64,
) -> Result<I80F48> {
Ok(new_health_cache(account, retriever)?.health(health_type))
Ok(new_health_cache(account, retriever, now_ts)?.health(health_type))
}

/// How much of a token can be taken away before health decreases to zero?
Expand Down Expand Up @@ -1221,8 +1223,9 @@ pub(crate) fn find_token_info_index(infos: &[TokenInfo], token_index: TokenIndex
pub fn new_health_cache(
account: &MangoAccountRef,
retriever: &impl AccountRetriever,
now_ts: u64,
) -> Result<HealthCache> {
new_health_cache_impl(account, retriever, false)
new_health_cache_impl(account, retriever, now_ts, false)
}

/// Generate a special HealthCache for an account and its health accounts
Expand All @@ -1233,13 +1236,15 @@ pub fn new_health_cache(
pub fn new_health_cache_skipping_bad_oracles(
account: &MangoAccountRef,
retriever: &impl AccountRetriever,
now_ts: u64,
) -> Result<HealthCache> {
new_health_cache_impl(account, retriever, true)
new_health_cache_impl(account, retriever, now_ts, true)
}

fn new_health_cache_impl(
account: &MangoAccountRef,
retriever: &impl AccountRetriever,
now_ts: u64,
// If an oracle is stale or inconfident and the health contribution would
// not be negative, skip it. This decreases health, but maybe overall it's
// still positive?
Expand Down Expand Up @@ -1268,12 +1273,15 @@ fn new_health_cache_impl(
// Use the liab price for computing weight scaling, because it's pessimistic and
// causes the most unfavorable scaling.
let liab_price = prices.liab(HealthType::Init);

let (maint_asset_weight, maint_liab_weight) = bank.maint_weights(now_ts);

token_infos.push(TokenInfo {
token_index: bank.token_index,
maint_asset_weight: bank.maint_asset_weight,
maint_asset_weight,
init_asset_weight: bank.init_asset_weight,
init_scaled_asset_weight: bank.scaled_init_asset_weight(liab_price),
maint_liab_weight: bank.maint_liab_weight,
maint_liab_weight,
init_liab_weight: bank.init_liab_weight,
init_scaled_liab_weight: bank.scaled_init_liab_weight(liab_price),
prices,
Expand Down Expand Up @@ -1443,7 +1451,7 @@ mod tests {
// for bank2/oracle2
let health2 = (-10.0 + 3.0) * 5.0 * 1.5;
assert!(health_eq(
compute_health(&account.borrow(), HealthType::Init, &retriever).unwrap(),
compute_health(&account.borrow(), HealthType::Init, &retriever, 0).unwrap(),
health1 + health2
));
}
Expand Down Expand Up @@ -1568,7 +1576,7 @@ mod tests {
let retriever = ScanningAccountRetriever::new_with_staleness(&ais, &group, None).unwrap();

assert!(health_eq(
compute_health(&account.borrow(), HealthType::Init, &retriever).unwrap(),
compute_health(&account.borrow(), HealthType::Init, &retriever, 0).unwrap(),
testcase.expected_health
));
}
Expand Down
14 changes: 7 additions & 7 deletions programs/mango-v4/src/health/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1264,7 +1264,7 @@ mod tests {
let retriever = ScanningAccountRetriever::new_with_staleness(&ais, &group, None).unwrap();

assert!(health_eq(
compute_health(&account.borrow(), HealthType::Init, &retriever).unwrap(),
compute_health(&account.borrow(), HealthType::Init, &retriever, 0).unwrap(),
// token
0.8 * (100.0
// perp base
Expand Down Expand Up @@ -1353,27 +1353,27 @@ mod tests {
let retriever = ScanningAccountRetriever::new_with_staleness(&ais, &group, None).unwrap();

assert!(health_eq(
compute_health(&account.borrow(), HealthType::Init, &retriever).unwrap(),
compute_health(&account.borrow(), HealthType::Init, &retriever, 0).unwrap(),
0.8 * 0.5 * 100.0
));
assert!(health_eq(
compute_health(&account.borrow(), HealthType::Maint, &retriever).unwrap(),
compute_health(&account.borrow(), HealthType::Maint, &retriever, 0).unwrap(),
0.9 * 1.0 * 100.0
));
assert!(health_eq(
compute_health(&account2.borrow(), HealthType::Init, &retriever).unwrap(),
compute_health(&account2.borrow(), HealthType::Init, &retriever, 0).unwrap(),
-1.2 * 1.0 * 100.0
));
assert!(health_eq(
compute_health(&account2.borrow(), HealthType::Maint, &retriever).unwrap(),
compute_health(&account2.borrow(), HealthType::Maint, &retriever, 0).unwrap(),
-1.1 * 1.0 * 100.0
));
assert!(health_eq(
compute_health(&account3.borrow(), HealthType::Init, &retriever).unwrap(),
compute_health(&account3.borrow(), HealthType::Init, &retriever, 0).unwrap(),
1.2 * (0.8 * 0.5 * 10.0 * 10.0 - 100.0)
));
assert!(health_eq(
compute_health(&account3.borrow(), HealthType::Maint, &retriever).unwrap(),
compute_health(&account3.borrow(), HealthType::Maint, &retriever, 0).unwrap(),
1.1 * (0.9 * 1.0 * 10.0 * 10.0 - 100.0)
));
}
Expand Down
3 changes: 2 additions & 1 deletion programs/mango-v4/src/instructions/compute_account_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ pub fn compute_account_data(ctx: Context<ComputeAccountData>) -> Result<()> {

let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, &group_pk)?;

let health_cache = new_health_cache(&account.borrow(), &account_retriever)?;
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
let health_cache = new_health_cache(&account.borrow(), &account_retriever, now_ts)?;
let init_health = health_cache.health(HealthType::Init);
let maint_health = health_cache.health(HealthType::Maint);

Expand Down
5 changes: 3 additions & 2 deletions programs/mango-v4/src/instructions/flash_loan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,8 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(

// Check health before balance adjustments
let retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?;
let health_cache = new_health_cache(&account.borrow(), &retriever)?;
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
let health_cache = new_health_cache(&account.borrow(), &retriever, now_ts)?;
let pre_init_health = account.check_health_pre(&health_cache)?;

// Prices for logging and net borrow checks
Expand Down Expand Up @@ -500,7 +501,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(

// Check health after account position changes
let retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?;
let health_cache = new_health_cache(&account.borrow(), &retriever)?;
let health_cache = new_health_cache(&account.borrow(), &retriever, now_ts)?;
account.check_health_post(&health_cache, pre_init_health)?;

// Deactivate inactive token accounts after health check
Expand Down
6 changes: 4 additions & 2 deletions programs/mango-v4/src/instructions/health_region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ pub fn health_region_begin<'key, 'accounts, 'remaining, 'info>(
.context("create account retriever")?;

// Compute pre-health and store it on the account
let health_cache = new_health_cache(&account.borrow(), &account_retriever)?;
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
let health_cache = new_health_cache(&account.borrow(), &account_retriever, now_ts)?;
let pre_init_health = account.check_health_pre(&health_cache)?;
account.fixed.health_region_begin_init_health = pre_init_health.ceil().to_num();

Expand All @@ -107,7 +108,8 @@ pub fn health_region_end<'key, 'accounts, 'remaining, 'info>(
let group = account.fixed.group;
let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, &group)
.context("create account retriever")?;
let health_cache = new_health_cache(&account.borrow(), &account_retriever)?;
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
let health_cache = new_health_cache(&account.borrow(), &account_retriever, now_ts)?;

let pre_init_health = I80F48::from(account.fixed.health_region_begin_init_health);
account.check_health_post(&health_cache, pre_init_health)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub fn perp_liq_base_or_positive_pnl(
max_base_transfer = max_base_transfer.max(i64::MIN + 1);

let group_pk = &ctx.accounts.group.key();
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();

require_keys_neq!(ctx.accounts.liqor.key(), ctx.accounts.liqee.key());
let mut liqor = ctx.accounts.liqor.load_full_mut()?;
Expand All @@ -51,7 +52,7 @@ pub fn perp_liq_base_or_positive_pnl(
let mut liqee_health_cache = {
let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk)
.context("create account retriever")?;
new_health_cache(&liqee.borrow(), &account_retriever)
new_health_cache(&liqee.borrow(), &account_retriever, now_ts)
.context("create liqee health cache")?
};
let liqee_liq_end_health = liqee_health_cache.health(HealthType::LiquidationEnd);
Expand Down Expand Up @@ -87,7 +88,6 @@ pub fn perp_liq_base_or_positive_pnl(
// Settle funding, update limit
liqee_perp_position.settle_funding(&perp_market);
liqor_perp_position.settle_funding(&perp_market);
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
liqee_perp_position.update_settle_limit(&perp_market, now_ts);

//
Expand Down Expand Up @@ -183,8 +183,13 @@ pub fn perp_liq_base_or_positive_pnl(
if !liqor.fixed.is_in_health_region() {
let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk)
.context("create account retriever end")?;
let liqor_health = compute_health(&liqor.borrow(), HealthType::Init, &account_retriever)
.context("compute liqor health")?;
let liqor_health = compute_health(
&liqor.borrow(),
HealthType::Init,
&account_retriever,
now_ts,
)
.context("compute liqor health")?;
require!(liqor_health >= 0, MangoError::HealthMustBePositive);
}

Expand Down Expand Up @@ -675,7 +680,7 @@ mod tests {
let retriever =
ScanningAccountRetriever::new_with_staleness(&ais, &setup.group, None).unwrap();

health::new_health_cache(&setup.liqee.borrow(), &retriever).unwrap()
health::new_health_cache(&setup.liqee.borrow(), &retriever, 0).unwrap()
}

fn run(&self, max_base: i64, max_pnl: u64) -> Result<Self> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ pub fn perp_liq_force_cancel_orders(
) -> Result<()> {
let mut account = ctx.accounts.account.load_full_mut()?;

let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
let mut health_cache = {
let retriever =
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
new_health_cache(&account.borrow(), &retriever).context("create health cache")?
new_health_cache(&account.borrow(), &retriever, now_ts).context("create health cache")?
};

let mut perp_market = ctx.accounts.perp_market.load_mut()?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ pub fn perp_liq_negative_pnl_or_bankruptcy(

let retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, &mango_group)
.context("create account retriever")?;
let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &retriever)?;
let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &retriever, now_ts)?;
drop(retriever);
let liqee_liq_end_health = liqee_health_cache.health(HealthType::LiquidationEnd);

Expand Down Expand Up @@ -197,8 +197,13 @@ pub fn perp_liq_negative_pnl_or_bankruptcy(
if !liqor.fixed.is_in_health_region() {
let account_retriever =
ScanningAccountRetriever::new(ctx.remaining_accounts, &mango_group)?;
let liqor_health = compute_health(&liqor.borrow(), HealthType::Init, &account_retriever)
.context("compute liqor health")?;
let liqor_health = compute_health(
&liqor.borrow(),
HealthType::Init,
&account_retriever,
now_ts,
)
.context("compute liqor health")?;
require!(liqor_health >= 0, MangoError::HealthMustBePositive);
}

Expand Down Expand Up @@ -509,7 +514,7 @@ mod tests {
ScanningAccountRetriever::new_with_staleness(&ais, &setup.group, None).unwrap();

liqee_health_cache =
health::new_health_cache(&setup.liqee.borrow(), &retriever).unwrap();
health::new_health_cache(&setup.liqee.borrow(), &retriever, 0).unwrap();
liqee_liq_end_health = liqee_health_cache.health(HealthType::LiquidationEnd);
}

Expand Down
4 changes: 2 additions & 2 deletions programs/mango-v4/src/instructions/perp_place_order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ pub fn perp_place_order(
let pre_health_opt = if !account.fixed.is_in_health_region() {
let retriever =
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
let health_cache =
new_health_cache(&account.borrow(), &retriever).context("pre-withdraw init health")?;
let health_cache = new_health_cache(&account.borrow(), &retriever, now_ts)
.context("pre-withdraw init health")?;
let pre_init_health = account.check_health_pre(&health_cache)?;
Some((health_cache, pre_init_health))
} else {
Expand Down
Loading

0 comments on commit 93d85c3

Please sign in to comment.