Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased

### Features
- program: update stake + volume fee tier determination ([#1792](https://github.com/drift-labs/protocol-v2/pull/1792))

### Fixes

Expand Down
2 changes: 1 addition & 1 deletion programs/drift/src/controller/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2164,7 +2164,7 @@ pub fn fulfill_perp_order_with_amm(
limit_price,
override_fill_price,
existing_base_asset_amount,
fee_tier,
&fee_tier,
)?;

let fill_price = if user.orders[order_index].post_only {
Expand Down
117 changes: 78 additions & 39 deletions programs/drift/src/math/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::math::casting::Cast;
use crate::math::constants::{
FIFTY_MILLION_QUOTE, FIVE_MILLION_QUOTE, ONE_HUNDRED_MILLION_QUOTE, ONE_HUNDRED_THOUSAND_QUOTE,
ONE_MILLION_QUOTE, ONE_THOUSAND_QUOTE, TEN_BPS, TEN_MILLION_QUOTE, TEN_THOUSAND_QUOTE,
TWENTY_FIVE_THOUSAND_QUOTE,
TWENTY_FIVE_THOUSAND_QUOTE, TWO_HUNDRED_FIFTY_THOUSAND_QUOTE,
};
use crate::math::helpers::get_proportion_u128;
use crate::math::safe_math::SafeMath;
Expand Down Expand Up @@ -55,7 +55,7 @@ pub fn calculate_fee_for_fulfillment_with_amm(

// if there was a quote_asset_amount_surplus, the order was a maker order and fee_to_market comes from surplus
if is_post_only {
let maker_rebate = calculate_maker_rebate(quote_asset_amount, fee_tier, fee_adjustment)?;
let maker_rebate = calculate_maker_rebate(quote_asset_amount, &fee_tier, fee_adjustment)?;

let fee = quote_asset_amount_surplus
.cast::<u64>()?
Expand Down Expand Up @@ -94,7 +94,7 @@ pub fn calculate_fee_for_fulfillment_with_amm(
referee_discount: 0,
})
} else {
let mut fee = calculate_taker_fee(quote_asset_amount, fee_tier, fee_adjustment)?;
let mut fee = calculate_taker_fee(quote_asset_amount, &fee_tier, fee_adjustment)?;

if user_high_leverage_mode {
fee = fee.safe_mul(2)?;
Expand All @@ -103,7 +103,7 @@ pub fn calculate_fee_for_fulfillment_with_amm(
let (fee, referee_discount, referrer_reward) = if reward_referrer {
calculate_referee_fee_and_referrer_reward(
fee,
fee_tier,
&fee_tier,
fee_structure.referrer_reward_epoch_upper_bound,
referrer_stats,
)?
Expand Down Expand Up @@ -299,7 +299,7 @@ pub fn calculate_fee_for_fulfillment_with_match(
determine_user_fee_tier(taker_stats, fee_structure, market_type, false)?
};

let mut taker_fee = calculate_taker_fee(quote_asset_amount, taker_fee_tier, fee_adjustment)?;
let mut taker_fee = calculate_taker_fee(quote_asset_amount, &taker_fee_tier, fee_adjustment)?;

if user_high_leverage_mode {
taker_fee = taker_fee.safe_mul(2)?;
Expand All @@ -308,15 +308,15 @@ pub fn calculate_fee_for_fulfillment_with_match(
let (taker_fee, referee_discount, referrer_reward) = if reward_referrer {
calculate_referee_fee_and_referrer_reward(
taker_fee,
taker_fee_tier,
&taker_fee_tier,
fee_structure.referrer_reward_epoch_upper_bound,
referrer_stats,
)?
} else {
(taker_fee, 0, 0)
};

let maker_rebate = calculate_maker_rebate(quote_asset_amount, maker_fee_tier, fee_adjustment)?;
let maker_rebate = calculate_maker_rebate(quote_asset_amount, &maker_fee_tier, fee_adjustment)?;

let filler_reward = if filler_multiplier == 0 {
0_u64
Expand Down Expand Up @@ -370,7 +370,7 @@ pub fn calculate_fee_for_fulfillment_with_external_market(
let taker_fee_tier =
determine_user_fee_tier(user_stats, fee_structure, &MarketType::Spot, false)?;

let fee = calculate_taker_fee(quote_asset_amount, taker_fee_tier, fee_adjustment)?;
let fee = calculate_taker_fee(quote_asset_amount, &taker_fee_tier, fee_adjustment)?;

let fee_plus_referrer_rebate = external_market_fee.safe_add(unsettled_referrer_rebate)?;

Expand Down Expand Up @@ -420,52 +420,91 @@ pub fn determine_user_fee_tier<'a>(
fee_structure: &'a FeeStructure,
market_type: &MarketType,
user_high_leverage_mode: bool,
) -> DriftResult<&'a FeeTier> {
) -> DriftResult<FeeTier> {
match market_type {
MarketType::Perp if user_high_leverage_mode => Ok(&fee_structure.fee_tiers[0]),
MarketType::Perp if user_high_leverage_mode => Ok(fee_structure.fee_tiers[0]),
MarketType::Perp => determine_perp_fee_tier(user_stats, fee_structure),
MarketType::Spot => determine_spot_fee_tier(user_stats, fee_structure),
MarketType::Spot => Ok(*determine_spot_fee_tier(user_stats, fee_structure)?),
}
}

fn determine_perp_fee_tier<'a>(
fn determine_perp_fee_tier(
user_stats: &UserStats,
fee_structure: &'a FeeStructure,
) -> DriftResult<&'a FeeTier> {
fee_structure: &FeeStructure,
) -> DriftResult<FeeTier> {
let total_30d_volume = user_stats.get_total_30d_volume()?;
let staked_gov_token_amount = user_stats.if_staked_gov_token_amount;

if total_30d_volume >= ONE_HUNDRED_MILLION_QUOTE
|| staked_gov_token_amount >= ONE_HUNDRED_THOUSAND_QUOTE + 19_500 * QUOTE_PRECISION_U64
{
return Ok(&fee_structure.fee_tiers[5]);
}

if total_30d_volume >= FIFTY_MILLION_QUOTE
|| staked_gov_token_amount >= ONE_HUNDRED_THOUSAND_QUOTE - QUOTE_PRECISION_U64
{
return Ok(&fee_structure.fee_tiers[4]);
}

if total_30d_volume >= TEN_MILLION_QUOTE
|| staked_gov_token_amount >= TWENTY_FIVE_THOUSAND_QUOTE * 2 - QUOTE_PRECISION_U64
{
return Ok(&fee_structure.fee_tiers[3]);
const TIER_LENGTH: usize = 5;

const VOLUME_THRESHOLDS: [u64; TIER_LENGTH] = [
ONE_MILLION_QUOTE * 2,
FIVE_MILLION_QUOTE * 2,
TEN_MILLION_QUOTE * 2,
FIFTY_MILLION_QUOTE * 2,
ONE_HUNDRED_MILLION_QUOTE * 2,
];

const STAKE_THRESHOLDS: [u64; TIER_LENGTH] = [
ONE_THOUSAND_QUOTE - QUOTE_PRECISION_U64,
TEN_THOUSAND_QUOTE - QUOTE_PRECISION_U64,
(TWENTY_FIVE_THOUSAND_QUOTE * 2) - QUOTE_PRECISION_U64,
ONE_HUNDRED_THOUSAND_QUOTE - QUOTE_PRECISION_U64,
TWO_HUNDRED_FIFTY_THOUSAND_QUOTE - QUOTE_PRECISION_U64 * 5,
];

const STAKE_BENEFIT_FRAC: [u32; TIER_LENGTH + 1] = [0, 5, 10, 20, 30, 40];

let mut fee_tier_index = TIER_LENGTH;
for i in 0..TIER_LENGTH {
if total_30d_volume < VOLUME_THRESHOLDS[i] {
fee_tier_index = i;
break;
}
}

if total_30d_volume >= FIVE_MILLION_QUOTE
|| staked_gov_token_amount >= TEN_THOUSAND_QUOTE - QUOTE_PRECISION_U64
{
return Ok(&fee_structure.fee_tiers[2]);
let mut stake_benefit_index = TIER_LENGTH;
for i in 0..TIER_LENGTH {
if staked_gov_token_amount < STAKE_THRESHOLDS[i] {
stake_benefit_index = i;
break;
}
}

if total_30d_volume >= ONE_MILLION_QUOTE
|| staked_gov_token_amount >= ONE_THOUSAND_QUOTE - QUOTE_PRECISION_U64
{
return Ok(&fee_structure.fee_tiers[1]);
let stake_benefit = STAKE_BENEFIT_FRAC[stake_benefit_index];

let mut tier = fee_structure.fee_tiers[fee_tier_index];

if stake_benefit > 0 {
if let Some(div_scalar) = match stake_benefit {
5 => Some(20),
10 => Some(10),
20 => Some(5),
_ => None,
} {
// Fast path for 5%, 10%, 20% using no mul
tier.fee_numerator = tier
.fee_numerator
.saturating_sub(tier.fee_numerator.safe_div_ceil(div_scalar)?);

tier.maker_rebate_numerator = tier
.maker_rebate_numerator
.safe_add(tier.maker_rebate_numerator.safe_div(div_scalar)?)?;
} else {
// General path with mul/div
tier.fee_numerator = tier
.fee_numerator
.safe_mul(100_u32.saturating_sub(stake_benefit))?
.safe_div_ceil(100_u32)?;

tier.maker_rebate_numerator = tier
.maker_rebate_numerator
.safe_mul(100_u32.saturating_add(stake_benefit))?
.safe_div(100_u32)?;
}
}

Ok(&fee_structure.fee_tiers[0])
Ok(tier)
}

fn determine_spot_fee_tier<'a>(
Expand Down
161 changes: 161 additions & 0 deletions programs/drift/src/math/fees/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -917,3 +917,164 @@ mod calculate_fee_for_fulfillment_with_serum {
assert_eq!(filler_reward, 2000);
}
}

mod calcuate_fee_tiers {

use crate::math::constants::QUOTE_PRECISION_U64;
use crate::math::constants::{
FEE_DENOMINATOR, FEE_PERCENTAGE_DENOMINATOR, MAX_REFERRER_REWARD_EPOCH_UPPER_BOUND,
};
use crate::math::fees::{determine_user_fee_tier, OrderFillerRewardStructure};
use crate::state::state::{FeeStructure, FeeTier};
use crate::state::user::MarketType;
use crate::state::user::UserStats;

#[test]
fn test_calc_taker_tiers() {
let mut taker_stats = UserStats::default();
let mut fee_tiers = [FeeTier::default(); 10];

fee_tiers[0] = FeeTier {
fee_numerator: 35,
fee_denominator: FEE_DENOMINATOR, // 3.5 bps
maker_rebate_numerator: 25,
maker_rebate_denominator: FEE_DENOMINATOR * 10, // .25 bps
referrer_reward_numerator: 10,
referrer_reward_denominator: FEE_PERCENTAGE_DENOMINATOR, // 10% of taker fee
referee_fee_numerator: 5,
referee_fee_denominator: FEE_PERCENTAGE_DENOMINATOR, // 5%
};
fee_tiers[1] = FeeTier {
fee_numerator: 30,
fee_denominator: FEE_DENOMINATOR, // 3 bps
maker_rebate_numerator: 25,
maker_rebate_denominator: FEE_DENOMINATOR * 10, // .25 bps
referrer_reward_numerator: 10,
referrer_reward_denominator: FEE_PERCENTAGE_DENOMINATOR, // 10% of taker fee
referee_fee_numerator: 5,
referee_fee_denominator: FEE_PERCENTAGE_DENOMINATOR, // 5%
};
fee_tiers[2] = FeeTier {
fee_numerator: 275,
fee_denominator: FEE_DENOMINATOR * 10, // 2.75 bps
maker_rebate_numerator: 25,
maker_rebate_denominator: FEE_DENOMINATOR * 10, // .25 bps
referrer_reward_numerator: 10,
referrer_reward_denominator: FEE_PERCENTAGE_DENOMINATOR, // 10% of taker fee
referee_fee_numerator: 5,
referee_fee_denominator: FEE_PERCENTAGE_DENOMINATOR, // 5%
};
fee_tiers[3] = FeeTier {
fee_numerator: 25,
fee_denominator: FEE_DENOMINATOR, // 2.5 bps
maker_rebate_numerator: 25,
maker_rebate_denominator: FEE_DENOMINATOR * 10, // .25 bps
referrer_reward_numerator: 10,
referrer_reward_denominator: FEE_PERCENTAGE_DENOMINATOR, // 10% of taker fee
referee_fee_numerator: 5,
referee_fee_denominator: FEE_PERCENTAGE_DENOMINATOR, // 5%
};
fee_tiers[4] = FeeTier {
fee_numerator: 225,
fee_denominator: FEE_DENOMINATOR * 10, // 2.25 bps
maker_rebate_numerator: 25,
maker_rebate_denominator: FEE_DENOMINATOR * 10, // .25 bps
referrer_reward_numerator: 10,
referrer_reward_denominator: FEE_PERCENTAGE_DENOMINATOR, // 10% of taker fee
referee_fee_numerator: 5,
referee_fee_denominator: FEE_PERCENTAGE_DENOMINATOR, // 5%
};
fee_tiers[5] = FeeTier {
fee_numerator: 20,
fee_denominator: FEE_DENOMINATOR, // 2 bps
maker_rebate_numerator: 25,
maker_rebate_denominator: FEE_DENOMINATOR * 10, // .25 bps
referrer_reward_numerator: 10,
referrer_reward_denominator: FEE_PERCENTAGE_DENOMINATOR, // 10% of taker fee
referee_fee_numerator: 5,
referee_fee_denominator: FEE_PERCENTAGE_DENOMINATOR, // 5%
};
let fee_structure = FeeStructure {
fee_tiers,
filler_reward_structure: OrderFillerRewardStructure {
reward_numerator: 10,
reward_denominator: FEE_PERCENTAGE_DENOMINATOR,
time_based_reward_lower_bound: 10_000, // 1 cent
},
flat_filler_fee: 10_000,
referrer_reward_epoch_upper_bound: MAX_REFERRER_REWARD_EPOCH_UPPER_BOUND,
};

let res = determine_user_fee_tier(&taker_stats, &fee_structure, &MarketType::Perp, false)
.unwrap();
assert_eq!(res.fee_numerator, 35);
assert_eq!(res.fee_denominator, 100000);

assert_eq!(res.maker_rebate_numerator, 25);
assert_eq!(res.maker_rebate_denominator, 1000000);

taker_stats.taker_volume_30d = 80_000_000 * QUOTE_PRECISION_U64;

let res: FeeTier =
determine_user_fee_tier(&taker_stats, &fee_structure, &MarketType::Perp, false)
.unwrap();
assert_eq!(res.fee_numerator, 25);
assert_eq!(res.fee_denominator, 100000);

assert_eq!(res.maker_rebate_numerator, 25);
assert_eq!(res.maker_rebate_denominator, 1000000);

taker_stats.if_staked_gov_token_amount = 50_000 * QUOTE_PRECISION_U64 - 8970; // still counts for 50K tier
let res: FeeTier =
determine_user_fee_tier(&taker_stats, &fee_structure, &MarketType::Perp, false)
.unwrap();

assert_eq!(res.fee_numerator, 20);
assert_eq!(res.fee_denominator, 100000);

assert_eq!(res.maker_rebate_numerator, 30);
assert_eq!(res.maker_rebate_denominator, 1000000);

taker_stats.if_staked_gov_token_amount = 150_000 * QUOTE_PRECISION_U64 - 8970; // still counts for 100K tier
let res: FeeTier =
determine_user_fee_tier(&taker_stats, &fee_structure, &MarketType::Perp, false)
.unwrap();

assert_eq!(res.fee_numerator, 18);
assert_eq!(res.fee_denominator, 100000);

assert_eq!(res.maker_rebate_numerator, 32);
assert_eq!(res.maker_rebate_denominator, 1000000);

taker_stats.if_staked_gov_token_amount = 800_000 * QUOTE_PRECISION_U64;
let res: FeeTier =
determine_user_fee_tier(&taker_stats, &fee_structure, &MarketType::Perp, false)
.unwrap();

assert_eq!(res.fee_numerator, 15);
assert_eq!(res.fee_denominator, 100000);

assert_eq!(res.maker_rebate_numerator, 35);
assert_eq!(res.maker_rebate_denominator, 1000000);

taker_stats.taker_volume_30d = 280_000_000 * QUOTE_PRECISION_U64;
let res: FeeTier =
determine_user_fee_tier(&taker_stats, &fee_structure, &MarketType::Perp, false)
.unwrap();

assert_eq!(res.fee_numerator, 12);
assert_eq!(res.fee_denominator, 100000);

assert_eq!(res.maker_rebate_numerator, 35);
assert_eq!(res.maker_rebate_denominator, 1000000);

let res: FeeTier =
determine_user_fee_tier(&taker_stats, &fee_structure, &MarketType::Perp, true).unwrap();

assert_eq!(res.fee_numerator, 35);
assert_eq!(res.fee_denominator, 100000);

assert_eq!(res.maker_rebate_numerator, 25);
assert_eq!(res.maker_rebate_denominator, 1000000);
}
}
Loading
Loading