Skip to content

Commit

Permalink
bigz/fix-amm-revenue-pool-transfer-last-settle-ts (#447)
Browse files Browse the repository at this point in the history
* bigz/fix-amm-revenue-pool-transfer-last-settle-ts

* test pass

* add exhaustive tests

* add test_update_last_revenue_withdraw_ts

* more test for withdraw and timestamp, more validates

* rm dlog!

* add validates in settle pnl and description comments (feedback)
  • Loading branch information
0xbigz committed Apr 28, 2023
1 parent 6912f62 commit eee140e
Show file tree
Hide file tree
Showing 6 changed files with 661 additions and 99 deletions.
235 changes: 136 additions & 99 deletions programs/drift/src/controller/amm.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::cmp::{max, min};
use std::cmp::{max, min, Ordering};

use anchor_lang::prelude::*;
use solana_program::msg;
Expand Down Expand Up @@ -382,6 +382,103 @@ pub fn get_fee_pool_tokens(
)?
.cast()
}
fn calculate_revenue_pool_transfer(
market: &PerpMarket,
spot_market: &SpotMarket,
amm_fee_pool_token_amount_after: u128,
terminal_state_surplus: i128,
) -> DriftResult<i128> {
// Calculates the revenue pool transfer amount for a given market state (positive = send to revenue pool, negative = pull from revenue pool)
// If the AMM budget is above `FEE_POOL_TO_REVENUE_POOL_THRESHOLD` (in surplus), settle fees collected to the revenue pool depending on the health of the AMM state
// Otherwise, spull from the revenue pool (up to a constraint amount)

let amm_budget_surplus =
terminal_state_surplus.saturating_sub(FEE_POOL_TO_REVENUE_POOL_THRESHOLD.cast()?);

if amm_budget_surplus > 0 {
let fee_pool_threshold = amm_fee_pool_token_amount_after
.saturating_sub(
FEE_POOL_TO_REVENUE_POOL_THRESHOLD
.safe_add(market.amm.total_social_loss)?
.cast()?,
)
.cast()?;

let total_liq_fees_for_revenue_pool = market
.amm
.total_liquidation_fee
.min(
market
.insurance_claim
.quote_settled_insurance
.safe_add(market.insurance_claim.quote_max_insurance)?
.cast()?,
)
.cast::<i128>()?;

let max_revenue_to_settle = market
.insurance_claim
.revenue_withdraw_since_last_settle
.safe_add(
market
.insurance_claim
.max_revenue_withdraw_per_period
.cast()?,
)?
.min(market.amm.net_revenue_since_last_funding)
.max(0);

let total_fee_for_if = get_total_fee_lower_bound(market)?.cast::<i128>()?;

let revenue_pool_transfer = total_fee_for_if
.safe_add(total_liq_fees_for_revenue_pool)?
.saturating_sub(market.amm.total_fee_withdrawn.cast()?)
.max(0)
.min(fee_pool_threshold)
.min(max_revenue_to_settle.cast()?);

validate!(
revenue_pool_transfer >= 0,
ErrorCode::InsufficientPerpPnlPool,
"revenue_pool_transfer negative ({})",
revenue_pool_transfer
)?;

Ok(revenue_pool_transfer)
} else if amm_budget_surplus < 0 {
let max_revenue_withdraw_allowed = market
.insurance_claim
.max_revenue_withdraw_per_period
.cast::<i64>()?
.saturating_sub(market.insurance_claim.revenue_withdraw_since_last_settle)
.cast::<u128>()?
.min(
get_token_amount(
spot_market.revenue_pool.scaled_balance,
spot_market,
&SpotBalanceType::Deposit,
)?
.cast()?,
)
.min(
market
.insurance_claim
.max_revenue_withdraw_per_period
.cast()?,
);

if max_revenue_withdraw_allowed > 0 {
let revenue_pool_transfer = -(amm_budget_surplus
.abs()
.min(max_revenue_withdraw_allowed.cast()?));
Ok(revenue_pool_transfer)
} else {
Ok(0)
}
} else {
Ok(0)
}
}

pub fn update_pool_balances(
market: &mut PerpMarket,
Expand Down Expand Up @@ -466,121 +563,61 @@ pub fn update_pool_balances(
.total_fee_minus_distributions
.safe_sub(market.amm.total_fee_withdrawn.cast()?)?;

if terminal_state_surplus < FEE_POOL_TO_REVENUE_POOL_THRESHOLD.cast()? {
// market can perform withdraw from revenue pool
if spot_market.insurance_fund.last_revenue_settle_ts
> market.insurance_claim.last_revenue_withdraw_ts
{
validate!(now >= market.insurance_claim.last_revenue_withdraw_ts && now >= spot_market.insurance_fund.last_revenue_settle_ts,
ErrorCode::BlockchainClockInconsistency,
"issue with clock unix timestamp {} < market.insurance_claim.last_revenue_withdraw_ts={}/spot_market.last_revenue_settle_ts={}",
now,
market.insurance_claim.last_revenue_withdraw_ts,
spot_market.insurance_fund.last_revenue_settle_ts,
)?;
market.insurance_claim.revenue_withdraw_since_last_settle = 0;
}
// market can perform withdraw from revenue pool
if spot_market.insurance_fund.last_revenue_settle_ts
> market.insurance_claim.last_revenue_withdraw_ts
{
validate!(now >= market.insurance_claim.last_revenue_withdraw_ts && now >= spot_market.insurance_fund.last_revenue_settle_ts,
ErrorCode::BlockchainClockInconsistency,
"issue with clock unix timestamp {} < market.insurance_claim.last_revenue_withdraw_ts={}/spot_market.last_revenue_settle_ts={}",
now,
market.insurance_claim.last_revenue_withdraw_ts,
spot_market.insurance_fund.last_revenue_settle_ts,
)?;
market.insurance_claim.revenue_withdraw_since_last_settle = 0;
}

let max_revenue_withdraw_allowed = market
.insurance_claim
.max_revenue_withdraw_per_period
.cast::<i64>()?
.safe_sub(market.insurance_claim.revenue_withdraw_since_last_settle)?
.cast::<u128>()?;
let revenue_pool_transfer = calculate_revenue_pool_transfer(
&market,
&spot_market,
amm_fee_pool_token_amount_after,
terminal_state_surplus,
)?;

if max_revenue_withdraw_allowed > 0 {
let spot_market_revenue_pool_amount = get_token_amount(
spot_market.revenue_pool.scaled_balance,
match revenue_pool_transfer.cmp(&0) {
Ordering::Greater => {
transfer_spot_balance_to_revenue_pool(
revenue_pool_transfer.unsigned_abs(),
spot_market,
&SpotBalanceType::Deposit,
&mut market.amm.fee_pool,
)?;

let revenue_pool_transfer: u128 = terminal_state_surplus
.safe_sub(FEE_POOL_TO_REVENUE_POOL_THRESHOLD.cast()?)?
.unsigned_abs()
.min(spot_market_revenue_pool_amount)
.min(max_revenue_withdraw_allowed);

market.amm.total_fee_withdrawn = market
.amm
.total_fee_withdrawn
.safe_add(revenue_pool_transfer.unsigned_abs())?;
}
Ordering::Less => {
transfer_revenue_pool_to_spot_balance(
revenue_pool_transfer,
revenue_pool_transfer.unsigned_abs(),
spot_market,
&mut market.amm.fee_pool,
)?;

market.amm.total_fee_minus_distributions = market
.amm
.total_fee_minus_distributions
.safe_add(revenue_pool_transfer.cast::<i128>()?)?;

market.insurance_claim.revenue_withdraw_since_last_settle = market
.insurance_claim
.revenue_withdraw_since_last_settle
.safe_add(revenue_pool_transfer.cast()?)?;

market.insurance_claim.last_revenue_withdraw_ts = now;
}
} else {
let fee_pool_threshold = amm_fee_pool_token_amount_after
.saturating_sub(
FEE_POOL_TO_REVENUE_POOL_THRESHOLD.safe_add(market.amm.total_social_loss)?,
)
.cast()?;
Ordering::Equal => (),
}

let total_liq_fees_for_revenue_pool: i128 = market
if revenue_pool_transfer != 0 {
market.amm.total_fee_minus_distributions = market
.amm
.total_liquidation_fee
.min(
market
.insurance_claim
.quote_settled_insurance
.safe_add(market.insurance_claim.quote_max_insurance)?
.cast()?,
)
.cast()?;

let max_revenue_to_settle = market
.insurance_claim
.revenue_withdraw_since_last_settle
.safe_add(
market
.insurance_claim
.max_revenue_withdraw_per_period
.cast()?,
)?
.min(market.amm.net_revenue_since_last_funding)
.max(0);

let total_fee_for_if = get_total_fee_lower_bound(market)?.cast::<i128>()?;

let revenue_pool_transfer = total_fee_for_if
.safe_add(total_liq_fees_for_revenue_pool)?
.safe_sub(market.amm.total_fee_withdrawn.cast()?)?
.max(0)
.min(fee_pool_threshold)
.min(max_revenue_to_settle.cast()?);

validate!(
revenue_pool_transfer >= 0,
ErrorCode::InsufficientPerpPnlPool,
"revenue_pool_transfer negative ({})",
revenue_pool_transfer,
)?;

transfer_spot_balance_to_revenue_pool(
revenue_pool_transfer.unsigned_abs(),
spot_market,
&mut market.amm.fee_pool,
)?;
.total_fee_minus_distributions
.safe_sub(revenue_pool_transfer)?;

market.insurance_claim.revenue_withdraw_since_last_settle = market
.insurance_claim
.revenue_withdraw_since_last_settle
.safe_sub(revenue_pool_transfer.cast()?)?;

market.amm.total_fee_withdrawn = market
.amm
.total_fee_withdrawn
.safe_add(revenue_pool_transfer.unsigned_abs())?;
market.insurance_claim.last_revenue_withdraw_ts = now;
}
}

Expand Down
Loading

0 comments on commit eee140e

Please sign in to comment.