Skip to content

Commit

Permalink
program: maker can be rewarded filler returns when amm gets fill (#1093)
Browse files Browse the repository at this point in the history
* program: maker can be rewarded filler returns when amm gets fill

* add test

* CHANGELOG
  • Loading branch information
crispheaney committed Jun 20, 2024
1 parent 5d87639 commit dab820d
Show file tree
Hide file tree
Showing 3 changed files with 270 additions and 17 deletions.
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

0 comments on commit dab820d

Please sign in to comment.