Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

program: maker can be rewarded filler returns when amm gets fill #1093

Merged
merged 4 commits into from
Jun 20, 2024
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 @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Features

- program: maker can be rewarded filler returns when amm gets fill ([#1093](https://github.com/drift-labs/protocol-v2/pull/1093))
- program: avoid overwriting 0 duration auction (https://github.com/drift-labs/protocol-v2/pull/1097)
- program: add pyth pull oracles (https://github.com/drift-labs/protocol-v2/pull/1067)
- ts-sdk: add pyth pull oracle clients
Expand Down
90 changes: 73 additions & 17 deletions programs/drift/src/controller/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,22 @@ fn fulfill_perp_order(
None,
)?;

// maker may try to fill their own order (e.g. via jit)
// if amm takes fill, give maker filler reward
let (mut maker, mut maker_stats) =
if makers_and_referrer.0.contains_key(filler_key) && filler.is_none() {
let maker = makers_and_referrer.get_ref_mut(filler_key)?;
if maker.authority == user.authority {
(None, None)
} else {
let maker_stats =
makers_and_referrer_stats.get_ref_mut(&maker.authority)?;
(Some(maker), Some(maker_stats))
}
} else {
(None, None)
};

let (fill_base_asset_amount, fill_quote_asset_amount) =
fulfill_perp_order_with_amm(
user,
Expand All @@ -1584,6 +1600,8 @@ fn fulfill_perp_order(
filler_key,
filler,
filler_stats,
&mut maker.as_deref_mut(),
&mut maker_stats.as_deref_mut(),
&mut referrer.as_deref_mut(),
&mut referrer_stats.as_deref_mut(),
fee_structure,
Expand Down Expand Up @@ -1801,6 +1819,8 @@ pub fn fulfill_perp_order_with_amm(
filler_key: &Pubkey,
filler: &mut Option<&mut User>,
filler_stats: &mut Option<&mut UserStats>,
maker: &mut Option<&mut User>,
maker_stats: &mut Option<&mut UserStats>,
referrer: &mut Option<&mut User>,
referrer_stats: &mut Option<&mut UserStats>,
fee_structure: &FeeStructure,
Expand Down Expand Up @@ -1900,7 +1920,8 @@ pub fn fulfill_perp_order_with_amm(
}

let reward_referrer = can_reward_user_with_perp_pnl(referrer, market.market_index);
let reward_filler = can_reward_user_with_perp_pnl(filler, market.market_index);
let reward_filler = can_reward_user_with_perp_pnl(filler, market.market_index)
|| can_reward_user_with_perp_pnl(maker, market.market_index);

let FillFees {
user_fee,
Expand Down Expand Up @@ -2005,22 +2026,25 @@ pub fn fulfill_perp_order_with_amm(
}

if let Some(filler) = filler.as_mut() {
if filler_reward > 0 {
let position_index = get_position_index(&filler.perp_positions, market.market_index)
.or_else(|_| add_new_position(&mut filler.perp_positions, market.market_index))?;

controller::position::update_quote_asset_amount(
&mut filler.perp_positions[position_index],
market,
filler_reward.cast()?,
)?;

filler_stats
.as_mut()
.safe_unwrap()?
.update_filler_volume(quote_asset_amount, now)?;
}
filler.update_last_active_slot(slot);
credit_filler_perp_pnl(
filler,
filler_stats,
market,
filler_reward,
quote_asset_amount,
now,
slot,
)?;
} else if let Some(maker) = maker.as_mut() {
credit_filler_perp_pnl(
maker,
maker_stats,
market,
filler_reward,
quote_asset_amount,
now,
slot,
)?;
}

update_order_after_fill(
Expand Down Expand Up @@ -2081,6 +2105,36 @@ pub fn fulfill_perp_order_with_amm(
Ok((base_asset_amount, quote_asset_amount))
}

pub fn credit_filler_perp_pnl(
filler: &mut User,
filler_stats: &mut Option<&mut UserStats>,
market: &mut PerpMarket,
filler_reward: u64,
quote_asset_amount: u64,
now: i64,
slot: u64,
) -> DriftResult {
if filler_reward > 0 {
let position_index = get_position_index(&filler.perp_positions, market.market_index)
.or_else(|_| add_new_position(&mut filler.perp_positions, market.market_index))?;

controller::position::update_quote_asset_amount(
&mut filler.perp_positions[position_index],
market,
filler_reward.cast()?,
)?;

filler_stats
.as_mut()
.safe_unwrap()?
.update_filler_volume(quote_asset_amount, now)?;
}

filler.update_last_active_slot(slot);

Ok(())
}

pub fn fulfill_perp_order_with_match(
market: &mut PerpMarket,
taker: &mut User,
Expand Down Expand Up @@ -2209,6 +2263,8 @@ pub fn fulfill_perp_order_with_match(
filler_stats,
&mut None,
&mut None,
&mut None,
&mut None,
fee_structure,
taker_limit_price,
Some(jit_base_asset_amount),
Expand Down
196 changes: 196 additions & 0 deletions programs/drift/src/controller/orders/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4734,6 +4734,202 @@ pub mod fulfill_order {
0
);
}

#[test]
fn fulfill_with_amm_when_maker_is_filler() {
let now = 0_i64;
let slot = 0_u64;

let mut oracle_price = get_pyth_price(100, 6);
let oracle_price_key =
Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap();
let pyth_program = crate::ids::pyth_program::id();
create_account_info!(
oracle_price,
&oracle_price_key,
&pyth_program,
oracle_account_info
);
let mut oracle_map = OracleMap::load_one(&oracle_account_info, slot, None).unwrap();

let mut market = PerpMarket {
amm: AMM {
base_asset_reserve: 100 * AMM_RESERVE_PRECISION,
quote_asset_reserve: 100 * AMM_RESERVE_PRECISION,
bid_base_asset_reserve: 100 * AMM_RESERVE_PRECISION,
bid_quote_asset_reserve: 100 * AMM_RESERVE_PRECISION,
ask_base_asset_reserve: 100 * AMM_RESERVE_PRECISION,
ask_quote_asset_reserve: 100 * AMM_RESERVE_PRECISION,
sqrt_k: 100 * AMM_RESERVE_PRECISION,
peg_multiplier: 100 * PEG_PRECISION,
max_slippage_ratio: 50,
max_fill_reserve_fraction: 100,
order_step_size: 1000,
order_tick_size: 1,
oracle: oracle_price_key,
base_spread: 0, // 1 basis point
historical_oracle_data: HistoricalOracleData {
last_oracle_price: (100 * PRICE_PRECISION) as i64,
last_oracle_price_twap: (100 * PRICE_PRECISION) as i64,
last_oracle_price_twap_5min: (100 * PRICE_PRECISION) as i64,

..HistoricalOracleData::default()
},
..AMM::default()
},
margin_ratio_initial: 1000,
margin_ratio_maintenance: 500,
status: MarketStatus::Initialized,
..PerpMarket::default_test()
};
market.amm.max_base_asset_reserve = u128::MAX;
market.amm.min_base_asset_reserve = 0;

create_anchor_account_info!(market, PerpMarket, market_account_info);
let market_map = PerpMarketMap::load_one(&market_account_info, true).unwrap();

let mut spot_market = SpotMarket {
market_index: 0,
oracle_source: OracleSource::QuoteAsset,
cumulative_deposit_interest: SPOT_CUMULATIVE_INTEREST_PRECISION,
decimals: 6,
initial_asset_weight: SPOT_WEIGHT_PRECISION,
maintenance_asset_weight: SPOT_WEIGHT_PRECISION,
historical_oracle_data: HistoricalOracleData::default_price(QUOTE_PRECISION_I64),
..SpotMarket::default()
};
create_anchor_account_info!(spot_market, SpotMarket, spot_market_account_info);
let spot_market_map = SpotMarketMap::load_one(&spot_market_account_info, true).unwrap();

let mut taker = User {
orders: get_orders(Order {
market_index: 0,
status: OrderStatus::Open,
order_type: OrderType::Market,
direction: PositionDirection::Long,
base_asset_amount: BASE_PRECISION_U64,
slot: 0,
auction_start_price: 0,
auction_end_price: 100 * PRICE_PRECISION_I64,
auction_duration: 0,
price: 150 * PRICE_PRECISION_U64,
..Order::default()
}),
perp_positions: get_positions(PerpPosition {
market_index: 0,
open_orders: 1,
open_bids: BASE_PRECISION_I64,
..PerpPosition::default()
}),
spot_positions: get_spot_positions(SpotPosition {
market_index: 0,
balance_type: SpotBalanceType::Deposit,
scaled_balance: 100 * SPOT_BALANCE_PRECISION_U64,
..SpotPosition::default()
}),
..User::default()
};

let maker_key = Pubkey::new_unique();
let maker_authority =
Pubkey::from_str("J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix").unwrap();
let mut maker = User {
authority: maker_authority,
orders: get_orders(Order {
market_index: 0,
post_only: true,
order_type: OrderType::Limit,
direction: PositionDirection::Short,
base_asset_amount: BASE_PRECISION_U64 / 2,
price: 100_010_000 * PRICE_PRECISION_U64 / 1_000_000, // .01 worse than amm
..Order::default()
}),
perp_positions: get_positions(PerpPosition {
market_index: 0,
open_orders: 1,
open_asks: -BASE_PRECISION_I64 / 2,
..PerpPosition::default()
}),
spot_positions: get_spot_positions(SpotPosition {
market_index: 0,
balance_type: SpotBalanceType::Deposit,
scaled_balance: 100 * SPOT_BALANCE_PRECISION_U64,
..SpotPosition::default()
}),
..User::default()
};
create_anchor_account_info!(maker, &maker_key, User, maker_account_info);
let makers_and_referrers = UserMap::load_one(&maker_account_info).unwrap();

let fee_structure = get_fee_structure();

let (taker_key, _, _) = get_user_keys();

let mut taker_stats = UserStats::default();
let mut maker_stats = UserStats {
authority: maker_authority,
..UserStats::default()
};
create_anchor_account_info!(maker_stats, UserStats, maker_stats_account_info);
let maker_and_referrer_stats = UserStatsMap::load_one(&maker_stats_account_info).unwrap();

let (base_asset_amount, _) = fulfill_perp_order(
&mut taker,
0,
&taker_key,
&mut taker_stats,
&makers_and_referrers,
&maker_and_referrer_stats,
&[(maker_key, 0, 100_010_000 * PRICE_PRECISION_U64 / 1_000_000)],
&mut None,
&maker_key,
&mut None,
None,
&spot_market_map,
&market_map,
&mut oracle_map,
&fee_structure,
0,
Some(market.amm.historical_oracle_data.last_oracle_price),
now,
slot,
0,
true,
FillMode::Fill,
)
.unwrap();

assert_eq!(base_asset_amount, BASE_PRECISION_U64);

let taker_position = &taker.perp_positions[0];
assert_eq!(taker_position.base_asset_amount, BASE_PRECISION_I64);
assert_eq!(taker_position.quote_asset_amount, -100306387);
assert_eq!(taker_position.quote_entry_amount, -100256258);
assert_eq!(taker_position.quote_break_even_amount, -100306387);
assert_eq!(taker_position.open_bids, 0);
assert_eq!(taker_position.open_orders, 0);
assert_eq!(taker_stats.fees.total_fee_paid, 50129);
assert_eq!(taker_stats.fees.total_referee_discount, 0);
assert_eq!(taker_stats.fees.total_token_discount, 0);
assert_eq!(taker_stats.taker_volume_30d, 100256237);
assert_eq!(taker.orders[0], Order::default());

let maker = makers_and_referrers.get_ref_mut(&maker_key).unwrap();
let maker_stats = maker_and_referrer_stats
.get_ref_mut(&maker_authority)
.unwrap();
let maker_position = &maker.perp_positions[0];
assert_eq!(maker_position.base_asset_amount, -BASE_PRECISION_I64 / 2);
assert_eq!(maker_position.quote_break_even_amount, 50_020_001);
assert_eq!(maker_position.quote_entry_amount, 50_005_000);
assert_eq!(maker_position.quote_asset_amount, 50022513); // 50_005_000 + 50_005_000 * .0003
assert_eq!(maker_position.open_orders, 0);
assert_eq!(maker_position.open_asks, 0);
assert_eq!(maker_stats.fees.total_fee_rebate, 15001);
assert_eq!(maker_stats.maker_volume_30d, 50_005_000);
assert_eq!(maker_stats.filler_volume_30d, 50251257); // gets filler volume
assert_eq!(maker.orders[0], Order::default());
}
}

pub mod fill_order {
Expand Down
Loading