diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fb4f86992..3a3009c487 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features - program: allow limit orders without auctions in swift ([#1661](https://github.com/drift-labs/protocol-v2/pull/1661)) +- program: add taker_speed_bump_override and amm_spread_adjustment ([#1665](https://github.com/drift-labs/protocol-v2/pull/1665)) ### Fixes diff --git a/programs/drift/src/controller/amm.rs b/programs/drift/src/controller/amm.rs index 96f8c3ce67..acdc854085 100644 --- a/programs/drift/src/controller/amm.rs +++ b/programs/drift/src/controller/amm.rs @@ -15,8 +15,8 @@ use crate::math::amm::calculate_quote_asset_amount_swapped; use crate::math::amm_spread::{calculate_spread_reserves, get_spread_reserves}; use crate::math::casting::Cast; use crate::math::constants::{ - CONCENTRATION_PRECISION, FEE_POOL_TO_REVENUE_POOL_THRESHOLD, K_BPS_UPDATE_SCALE, - MAX_CONCENTRATION_COEFFICIENT, MAX_K_BPS_INCREASE, MAX_SQRT_K, + CONCENTRATION_PRECISION, FEE_ADJUSTMENT_MAX, FEE_POOL_TO_REVENUE_POOL_THRESHOLD, + K_BPS_UPDATE_SCALE, MAX_CONCENTRATION_COEFFICIENT, MAX_K_BPS_INCREASE, MAX_SQRT_K, }; use crate::math::cp_curve::get_update_k_result; use crate::math::repeg::get_total_fee_lower_bound; @@ -209,7 +209,7 @@ pub fn update_spreads(market: &mut PerpMarket, reserve_price: u64) -> DriftResul 0 }; - let (long_spread, short_spread) = if market.amm.curve_update_intensity > 0 { + let (mut long_spread, mut short_spread) = if market.amm.curve_update_intensity > 0 { amm_spread::calculate_spread( market.amm.base_spread, market.amm.last_oracle_reserve_price_spread_pct, @@ -236,6 +236,42 @@ pub fn update_spreads(market: &mut PerpMarket, reserve_price: u64) -> DriftResul (half_base_spread, half_base_spread) }; + if market.amm.amm_spread_adjustment < 0 { + long_spread = long_spread + .saturating_sub( + long_spread + .safe_mul(market.amm.amm_spread_adjustment.unsigned_abs().cast()?) + .unwrap_or(u32::MAX) + .safe_div(100)?, + ) + .max(1); + short_spread = short_spread + .saturating_sub( + short_spread + .safe_mul(market.amm.amm_spread_adjustment.unsigned_abs().cast()?) + .unwrap_or(u32::MAX) + .safe_div(100)?, + ) + .max(1); + } else if market.amm.amm_spread_adjustment > 0 { + long_spread = long_spread + .saturating_add( + long_spread + .safe_mul(market.amm.amm_spread_adjustment.cast()?) + .unwrap_or(u32::MAX) + .safe_div_ceil(100)?, + ) + .max(1); + short_spread = short_spread + .saturating_add( + short_spread + .safe_mul(market.amm.amm_spread_adjustment.cast()?) + .unwrap_or(u32::MAX) + .safe_div_ceil(100)?, + ) + .max(1); + } + market.amm.long_spread = long_spread; market.amm.short_spread = short_spread; market.amm.reference_price_offset = reference_price_offset; diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index cb0d25196b..f3cfbfd0ea 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -1057,6 +1057,7 @@ pub fn fill_perp_order( let amm_lp_allowed_to_jit_make: bool; let oracle_valid_for_amm_fill: bool; let oracle_stale_for_margin: bool; + let min_auction_duration: u8; let mut amm_is_available = !state.amm_paused()?; { let market = &mut perp_market_map.get_ref_mut(&market_index)?; @@ -1113,6 +1114,9 @@ pub fn fill_perp_order( .last_oracle_price_twap_5min; oracle_validity = _oracle_validity; perp_market_index = market.market_index; + + min_auction_duration = + market.get_min_perp_auction_duration(state.min_perp_auction_duration); } // allow oracle price to be used to calculate limit price if it's valid or stale for amm @@ -1288,7 +1292,7 @@ pub fn fill_perp_order( valid_oracle_price, now, slot, - state.min_perp_auction_duration, + min_auction_duration, amm_availability, fill_mode, oracle_stale_for_margin, diff --git a/programs/drift/src/instructions/admin.rs b/programs/drift/src/instructions/admin.rs index 8f84c009f4..df2f3ed5cb 100644 --- a/programs/drift/src/instructions/admin.rs +++ b/programs/drift/src/instructions/admin.rs @@ -1041,7 +1041,8 @@ pub fn handle_initialize_perp_market( target_base_asset_amount_per_lp: 0, per_lp_base: 0, padding1: 0, - padding2: 0, + taker_speed_bump_override: 0, + amm_spread_adjustment: 0, total_fee_earned_per_lp: 0, net_unsettled_funding_pnl: 0, quote_asset_amount_with_unsettled_lp: 0, @@ -3910,7 +3911,7 @@ pub fn handle_update_perp_market_number_of_users( } pub fn handle_update_perp_market_fuel( - ctx: Context, + ctx: Context, fuel_boost_taker: Option, fuel_boost_maker: Option, fuel_boost_position: Option, @@ -3987,6 +3988,46 @@ pub fn handle_update_perp_market_protected_maker_params( Ok(()) } +#[access_control( + perp_market_valid(&ctx.accounts.perp_market) +)] +pub fn handle_update_perp_market_taker_speed_bump_override( + ctx: Context, + taker_speed_bump_override: i8, +) -> Result<()> { + let perp_market = &mut load_mut!(ctx.accounts.perp_market)?; + msg!("perp market {}", perp_market.market_index); + + msg!( + "perp_market.amm.taker_speed_bump_override: {:?} -> {:?}", + perp_market.amm.taker_speed_bump_override, + taker_speed_bump_override + ); + + perp_market.amm.taker_speed_bump_override = taker_speed_bump_override; + Ok(()) +} + +#[access_control( + perp_market_valid(&ctx.accounts.perp_market) +)] +pub fn handle_update_perp_market_amm_spread_adjustment( + ctx: Context, + amm_spread_adjustment: i8, +) -> Result<()> { + let perp_market = &mut load_mut!(ctx.accounts.perp_market)?; + msg!("perp market {}", perp_market.market_index); + + msg!( + "perp_market.amm.amm_spread_adjustment: {:?} -> {:?}", + perp_market.amm.amm_spread_adjustment, + amm_spread_adjustment + ); + + perp_market.amm.amm_spread_adjustment = amm_spread_adjustment; + Ok(()) +} + #[access_control( spot_market_valid(&ctx.accounts.spot_market) )] @@ -4865,7 +4906,7 @@ pub struct AdminUpdatePerpMarket<'info> { } #[derive(Accounts)] -pub struct AdminUpdatePerpMarketFuel<'info> { +pub struct HotAdminUpdatePerpMarket<'info> { #[account( constraint = admin.key() == admin_hot_wallet::id() || admin.key() == state.admin )] diff --git a/programs/drift/src/lib.rs b/programs/drift/src/lib.rs index 585c9d089a..cf6fa3bdef 100644 --- a/programs/drift/src/lib.rs +++ b/programs/drift/src/lib.rs @@ -1513,7 +1513,7 @@ pub mod drift { } pub fn update_perp_market_fuel( - ctx: Context, + ctx: Context, fuel_boost_taker: Option, fuel_boost_maker: Option, fuel_boost_position: Option, @@ -1533,6 +1533,20 @@ pub mod drift { ) } + pub fn update_perp_market_taker_speed_bump_override( + ctx: Context, + taker_speed_bump_override: i8, + ) -> Result<()> { + handle_update_perp_market_taker_speed_bump_override(ctx, taker_speed_bump_override) + } + + pub fn update_perp_market_amm_spread_adjustment( + ctx: Context, + amm_spread_adjustment: i8, + ) -> Result<()> { + handle_update_perp_market_amm_spread_adjustment(ctx, amm_spread_adjustment) + } + pub fn update_spot_market_fuel( ctx: Context, fuel_boost_deposits: Option, diff --git a/programs/drift/src/math/cp_curve/tests.rs b/programs/drift/src/math/cp_curve/tests.rs index 6f3e207afa..99039b67d0 100644 --- a/programs/drift/src/math/cp_curve/tests.rs +++ b/programs/drift/src/math/cp_curve/tests.rs @@ -312,6 +312,64 @@ fn calculate_k_tests_wrapper_fcn() { assert_eq!(pct_change_in_k, 10007); // k was increased .07% } +#[test] +fn amm_spread_adj_logic() { + let mut market = PerpMarket { + amm: AMM { + base_asset_reserve: 100 * AMM_RESERVE_PRECISION, + quote_asset_reserve: 100 * AMM_RESERVE_PRECISION, + terminal_quote_asset_reserve: 999900009999000 * AMM_RESERVE_PRECISION, + sqrt_k: 100 * AMM_RESERVE_PRECISION, + peg_multiplier: 50_000_000_000, + base_asset_amount_with_amm: (AMM_RESERVE_PRECISION / 10) as i128, + base_asset_amount_long: (AMM_RESERVE_PRECISION / 10) as i128, + order_step_size: 5, + base_spread: 100, + max_spread: 10000, + ..AMM::default_test() + }, + margin_ratio_initial: 1000, + ..PerpMarket::default() + }; + // let (t_price, _t_qar, _t_bar) = calculate_terminal_price_and_reserves(&market.amm).unwrap(); + // market.amm.terminal_quote_asset_reserve = _t_qar; + + let mut position = PerpPosition { + ..PerpPosition::default() + }; + + mint_lp_shares(&mut position, &mut market, BASE_PRECISION_U64).unwrap(); + + market.amm.base_asset_amount_per_lp = 1; + market.amm.quote_asset_amount_per_lp = -QUOTE_PRECISION_I64 as i128; + + let reserve_price = market.amm.reserve_price().unwrap(); + update_spreads(&mut market, reserve_price).unwrap(); + + assert_eq!(market.amm.long_spread, 50); + assert_eq!(market.amm.short_spread, 50); + + market.amm.amm_spread_adjustment = -100; + update_spreads(&mut market, reserve_price).unwrap(); + assert_eq!(market.amm.long_spread, 1); + assert_eq!(market.amm.short_spread, 1); + + market.amm.amm_spread_adjustment = 100; + update_spreads(&mut market, reserve_price).unwrap(); + assert_eq!(market.amm.long_spread, 100); + assert_eq!(market.amm.short_spread, 100); + + market.amm.amm_spread_adjustment = 20; + update_spreads(&mut market, reserve_price).unwrap(); + assert_eq!(market.amm.long_spread, 60); + assert_eq!(market.amm.short_spread, 60); + + market.amm.amm_spread_adjustment = 120; + update_spreads(&mut market, reserve_price).unwrap(); + assert_eq!(market.amm.long_spread, 110); + assert_eq!(market.amm.short_spread, 110); +} + #[test] fn calculate_k_with_lps_tests() { let mut market = PerpMarket { diff --git a/programs/drift/src/math/orders.rs b/programs/drift/src/math/orders.rs index 9b0a9c1dc5..29bec67816 100644 --- a/programs/drift/src/math/orders.rs +++ b/programs/drift/src/math/orders.rs @@ -12,7 +12,6 @@ use crate::math::casting::Cast; use crate::state::fill_mode::FillMode; use crate::state::protected_maker_mode_config::ProtectedMakerParams; use crate::state::user::OrderBitFlag; -use crate::BASE_PRECISION_U64; use crate::{ load, math, FeeTier, State, BASE_PRECISION_I128, FEE_ADJUSTMENT_MAX, MAX_PREDICTION_MARKET_PRICE, MAX_PREDICTION_MARKET_PRICE_I64, OPEN_ORDER_MARGIN_REQUIREMENT, diff --git a/programs/drift/src/state/perp_market.rs b/programs/drift/src/state/perp_market.rs index 869c22af13..2c873e5e16 100644 --- a/programs/drift/src/state/perp_market.rs +++ b/programs/drift/src/state/perp_market.rs @@ -735,6 +735,14 @@ impl PerpMarket { tick_size: self.amm.order_tick_size, } } + + pub fn get_min_perp_auction_duration(&self, default_min_auction_duration: u8) -> u8 { + if self.amm.taker_speed_bump_override != 0 { + self.amm.taker_speed_bump_override.max(0).unsigned_abs() + } else { + default_min_auction_duration + } + } } #[cfg(test)] @@ -1034,8 +1042,12 @@ pub struct AMM { pub target_base_asset_amount_per_lp: i32, /// expo for unit of per_lp, base 10 (if per_lp_base=X, then per_lp unit is 10^X) pub per_lp_base: i8, + /// the override for the state.min_perp_auction_duration + /// 0 is no override, -1 is disable speed bump, 1-100 is literal speed bump + pub taker_speed_bump_override: i8, + /// signed scale amm_spread similar to fee_adjustment logic (-100 = 0, 100 = double) + pub amm_spread_adjustment: i8, pub padding1: u8, - pub padding2: u16, pub total_fee_earned_per_lp: u64, pub net_unsettled_funding_pnl: i64, pub quote_asset_amount_with_unsettled_lp: i64, @@ -1123,8 +1135,9 @@ impl Default for AMM { last_oracle_valid: false, target_base_asset_amount_per_lp: 0, per_lp_base: 0, + taker_speed_bump_override: 0, + amm_spread_adjustment: 0, padding1: 0, - padding2: 0, total_fee_earned_per_lp: 0, net_unsettled_funding_pnl: 0, quote_asset_amount_with_unsettled_lp: 0, diff --git a/programs/drift/src/state/perp_market/tests.rs b/programs/drift/src/state/perp_market/tests.rs index ed90c98d65..3d3a9bb4cb 100644 --- a/programs/drift/src/state/perp_market/tests.rs +++ b/programs/drift/src/state/perp_market/tests.rs @@ -101,3 +101,58 @@ mod get_margin_ratio { assert_eq!(margin_ratio_maintenance, MARGIN_PRECISION / 100); } } + +mod get_min_perp_auction_duration { + use crate::state::perp_market::{PerpMarket, AMM}; + use crate::State; + + #[test] + fn test_get_speed_bump() { + let perp_market = PerpMarket { + amm: AMM { + taker_speed_bump_override: 0, + ..AMM::default() + }, + ..PerpMarket::default() + }; + + let state = State { + min_perp_auction_duration: 10, + ..State::default() + }; + + // no override uses state value + assert_eq!( + perp_market.get_min_perp_auction_duration(state.min_perp_auction_duration), + 10 + ); + + let perp_market = PerpMarket { + amm: AMM { + taker_speed_bump_override: -1, + ..AMM::default() + }, + ..PerpMarket::default() + }; + + // -1 override disables speed bump + assert_eq!( + perp_market.get_min_perp_auction_duration(state.min_perp_auction_duration), + 0 + ); + + let perp_market = PerpMarket { + amm: AMM { + taker_speed_bump_override: 20, + ..AMM::default() + }, + ..PerpMarket::default() + }; + + // positive override uses override value + assert_eq!( + perp_market.get_min_perp_auction_duration(state.min_perp_auction_duration), + 20 + ); + } +} diff --git a/sdk/src/adminClient.ts b/sdk/src/adminClient.ts index d33409784a..0315397c13 100644 --- a/sdk/src/adminClient.ts +++ b/sdk/src/adminClient.ts @@ -3859,6 +3859,86 @@ export class AdminClient extends DriftClient { ); } + public async updatePerpMarketTakerSpeedBumpOverride( + perpMarketIndex: number, + takerSpeedBumpOverride: number + ): Promise { + const updatePerpMarketTakerSpeedBumpOverrideIx = + await this.getUpdatePerpMarketTakerSpeedBumpOverrideIx( + perpMarketIndex, + takerSpeedBumpOverride + ); + const tx = await this.buildTransaction( + updatePerpMarketTakerSpeedBumpOverrideIx + ); + const { txSig } = await this.sendTransaction(tx, [], this.opts); + + return txSig; + } + + public async getUpdatePerpMarketTakerSpeedBumpOverrideIx( + perpMarketIndex: number, + takerSpeedBumpOverride: number + ): Promise { + const perpMarketPublicKey = await getPerpMarketPublicKey( + this.program.programId, + perpMarketIndex + ); + + return await this.program.instruction.updatePerpMarketTakerSpeedBumpOverride( + takerSpeedBumpOverride, + { + accounts: { + admin: this.isSubscribed + ? this.getStateAccount().admin + : this.wallet.publicKey, + state: await this.getStatePublicKey(), + perpMarket: perpMarketPublicKey, + }, + } + ); + } + + public async updatePerpMarketAmmSpreadAdjustment( + perpMarketIndex: number, + ammSpreadAdjustment: number + ): Promise { + const updatePerpMarketAmmSpreadAdjustmentIx = + await this.getUpdatePerpMarketAmmSpreadAdjustmentIx( + perpMarketIndex, + ammSpreadAdjustment + ); + const tx = await this.buildTransaction( + updatePerpMarketAmmSpreadAdjustmentIx + ); + const { txSig } = await this.sendTransaction(tx, [], this.opts); + + return txSig; + } + + public async getUpdatePerpMarketAmmSpreadAdjustmentIx( + perpMarketIndex: number, + ammSpreadAdjustment: number + ): Promise { + const perpMarketPublicKey = await getPerpMarketPublicKey( + this.program.programId, + perpMarketIndex + ); + + return await this.program.instruction.updatePerpMarketAmmSpreadAdjustment( + ammSpreadAdjustment, + { + accounts: { + admin: this.isSubscribed + ? this.getStateAccount().admin + : this.wallet.publicKey, + state: await this.getStatePublicKey(), + perpMarket: perpMarketPublicKey, + }, + } + ); + } + public async updatePerpMarketProtectedMakerParams( perpMarketIndex: number, protectedMakerLimitPriceDivisor?: number, diff --git a/sdk/src/types.ts b/sdk/src/types.ts index 2f808394b8..0a601a702c 100644 --- a/sdk/src/types.ts +++ b/sdk/src/types.ts @@ -971,6 +971,9 @@ export type AMM = { netUnsettledFundingPnl: BN; quoteAssetAmountWithUnsettledLp: BN; referencePriceOffset: number; + + takerSpeedBumpOverride: number; + ammSpreadAdjustment: number; }; // # User Account Types