Skip to content

Commit a0f09b6

Browse files
committed
Merge remote-tracking branch 'upstream/master' into update/v2.85.0
2 parents 7d39234 + 0face12 commit a0f09b6

File tree

27 files changed

+6259
-171
lines changed

27 files changed

+6259
-171
lines changed

.github/workflows/release.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Release
2+
on:
3+
release:
4+
types: [published]
5+
jobs:
6+
release:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- name: Checkout
10+
uses: actions/[email protected]
11+
12+
- name: Set up Python
13+
uses: actions/[email protected]
14+
with:
15+
python-version: '3.10.10'
16+
17+
- name: Install and configure Poetry
18+
run: |
19+
cd python
20+
curl -sSL https://install.python-poetry.org | python3 -
21+
env:
22+
POETRY_VERSION: 1.4.2
23+
24+
- name: Build
25+
run: poetry build
26+
working-directory: python
27+
28+
- name: Publish
29+
run: poetry publish --username=__token__ --password=${{ secrets.PYPI_TOKEN }}
30+
working-directory: python

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ python/poetry.lock
1616
__pycache__/
1717
*.pyc
1818
*.pyo
19-
*.pyd
19+
*.pyd
20+
rust/.env

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
members = [
33
"programs/*"
44
]
5+
exclude = [
6+
"rust"
7+
]
58

69
[profile.release]
710
overflow-checks = true

programs/jit-proxy/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ pub enum ErrorCode {
2121
UnprofitableArb,
2222
#[msg("PositionLimitBreached")]
2323
PositionLimitBreached,
24+
#[msg("NoFill")]
25+
NoFill,
2426
}

programs/jit-proxy/src/instructions/jit.rs

Lines changed: 137 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,27 @@ pub fn jit<'info>(ctx: Context<'_, '_, 'info, 'info, Jit<'info>>, params: JitPar
3232
let market_index = taker_order.market_index;
3333
let taker_direction = taker_order.direction;
3434

35+
let slots_left = taker_order
36+
.slot
37+
.safe_add(taker_order.auction_duration.cast()?)?
38+
.cast::<i64>()?
39+
.safe_sub(slot.cast()?)?;
40+
msg!(
41+
"slot = {} auction duration = {} slots_left = {}",
42+
slot,
43+
taker_order.auction_duration,
44+
slots_left
45+
);
46+
47+
msg!(
48+
"taker order type {:?} auction start {} auction end {} limit price {} oracle price offset {}",
49+
taker_order.order_type,
50+
taker_order.auction_start_price,
51+
taker_order.auction_end_price,
52+
taker_order.price,
53+
taker_order.oracle_price_offset
54+
);
55+
3556
let remaining_accounts_iter = &mut ctx.remaining_accounts.iter().peekable();
3657
let AccountMaps {
3758
perp_market_map,
@@ -45,20 +66,45 @@ pub fn jit<'info>(ctx: Context<'_, '_, 'info, 'info, Jit<'info>>, params: JitPar
4566
None,
4667
)?;
4768

48-
let (oracle_price, tick_size) = if market_type == DriftMarketType::Perp {
69+
let (oracle_price, tick_size, min_order_size) = if market_type == DriftMarketType::Perp {
4970
let perp_market = perp_market_map.get_ref(&market_index)?;
5071
let oracle_price = oracle_map.get_price_data(&perp_market.amm.oracle)?.price;
5172

52-
(oracle_price, perp_market.amm.order_tick_size)
73+
(
74+
oracle_price,
75+
perp_market.amm.order_tick_size,
76+
perp_market.amm.min_order_size,
77+
)
5378
} else {
5479
let spot_market = spot_market_map.get_ref(&market_index)?;
5580
let oracle_price = oracle_map.get_price_data(&spot_market.oracle)?.price;
5681

57-
(oracle_price, spot_market.order_tick_size)
82+
(
83+
oracle_price,
84+
spot_market.order_tick_size,
85+
spot_market.min_order_size,
86+
)
5887
};
5988

6089
let taker_price =
61-
taker_order.force_get_limit_price(Some(oracle_price), None, slot, tick_size)?;
90+
match taker_order.get_limit_price(Some(oracle_price), None, slot, tick_size)? {
91+
Some(price) => price,
92+
None if market_type == DriftMarketType::Perp => {
93+
msg!("taker order didnt have price. deriving fallback");
94+
// if the order doesn't have a price, drift users amm price for taker price
95+
let perp_market = perp_market_map.get_ref(&market_index)?;
96+
let reserve_price = perp_market.amm.reserve_price()?;
97+
match taker_direction {
98+
PositionDirection::Long => perp_market.amm.ask_price(reserve_price)?,
99+
PositionDirection::Short => perp_market.amm.bid_price(reserve_price)?,
100+
}
101+
}
102+
None => {
103+
// Shouldnt be possible for spot
104+
msg!("taker order didnt have price");
105+
return Err(ErrorCode::TakerOrderNotFound.into());
106+
}
107+
};
62108

63109
let maker_direction = taker_direction.opposite();
64110
let maker_worst_price = params.get_worst_price(oracle_price, taker_direction)?;
@@ -84,9 +130,41 @@ pub fn jit<'info>(ctx: Context<'_, '_, 'info, 'info, Jit<'info>>, params: JitPar
84130
}
85131
}
86132
}
87-
let maker_price = taker_price;
88133

89-
let taker_base_asset_amount_unfilled = taker_order.get_base_asset_amount_unfilled(None)?;
134+
let maker_price = if market_type == DriftMarketType::Perp {
135+
let perp_market = perp_market_map.get_ref(&market_index)?;
136+
let reserve_price = perp_market.amm.reserve_price()?;
137+
138+
let maker_price = match maker_direction {
139+
PositionDirection::Long => {
140+
let amm_bid_price = perp_market.amm.bid_price(reserve_price)?;
141+
142+
// if amm price is better than maker, use amm price to ensure fill
143+
if taker_price <= amm_bid_price {
144+
amm_bid_price.min(maker_worst_price)
145+
} else {
146+
taker_price
147+
}
148+
}
149+
PositionDirection::Short => {
150+
let amm_ask_price = perp_market.amm.ask_price(reserve_price)?;
151+
152+
if taker_price >= amm_ask_price {
153+
amm_ask_price.max(maker_worst_price)
154+
} else {
155+
taker_price
156+
}
157+
}
158+
};
159+
160+
maker_price
161+
} else {
162+
taker_price
163+
};
164+
165+
let taker_base_asset_amount_unfilled = taker_order
166+
.get_base_asset_amount_unfilled(None)?
167+
.max(min_order_size);
90168
let maker_existing_position = if market_type == DriftMarketType::Perp {
91169
let perp_market = perp_market_map.get_ref(&market_index)?;
92170
let perp_position = maker.get_perp_position(market_index);
@@ -111,6 +189,7 @@ pub fn jit<'info>(ctx: Context<'_, '_, 'info, 'info, Jit<'info>>, params: JitPar
111189
maker_direction,
112190
taker_base_asset_amount_unfilled,
113191
maker_existing_position,
192+
min_order_size,
114193
) {
115194
Ok(size) => size,
116195
Err(e) => {
@@ -144,7 +223,36 @@ pub fn jit<'info>(ctx: Context<'_, '_, 'info, 'info, Jit<'info>>, params: JitPar
144223
drop(taker);
145224
drop(maker);
146225

147-
place_and_make(ctx, params.taker_order_id, order_params)?;
226+
place_and_make(&ctx, params.taker_order_id, order_params)?;
227+
228+
let taker = ctx.accounts.taker.load()?;
229+
230+
let taker_base_asset_amount_unfilled_after = match taker.get_order(params.taker_order_id) {
231+
Some(order) => order.get_base_asset_amount_unfilled(None)?,
232+
None => 0,
233+
};
234+
235+
if taker_base_asset_amount_unfilled_after == taker_base_asset_amount_unfilled {
236+
// taker order failed to fill
237+
msg!(
238+
"taker price = {} maker price = {} oracle price = {}",
239+
taker_price,
240+
maker_price,
241+
oracle_price
242+
);
243+
msg!("jit params {:?}", params);
244+
if market_type == DriftMarketType::Perp {
245+
let perp_market = perp_market_map.get_ref(&market_index)?;
246+
let reserve_price = perp_market.amm.reserve_price()?;
247+
let (bid_price, ask_price) = perp_market.amm.bid_ask_price(reserve_price)?;
248+
msg!(
249+
"vamm bid price = {} vamm ask price = {}",
250+
bid_price,
251+
ask_price
252+
);
253+
}
254+
return Err(ErrorCode::NoFill.into());
255+
}
148256

149257
Ok(())
150258
}
@@ -213,15 +321,17 @@ fn check_position_limits(
213321
maker_direction: PositionDirection,
214322
taker_base_asset_amount_unfilled: u64,
215323
maker_existing_position: i64,
324+
min_order_size: u64,
216325
) -> Result<u64> {
217326
if maker_direction == PositionDirection::Long {
218327
let size = params.max_position.safe_sub(maker_existing_position)?;
219328

220-
if size <= 0 {
329+
if size <= min_order_size.cast()? {
221330
msg!(
222-
"maker existing position {} >= max position {}",
331+
"maker existing position {} >= max position {} + min order size {}",
223332
maker_existing_position,
224-
params.max_position
333+
params.max_position,
334+
min_order_size
225335
);
226336
return Err(ErrorCode::PositionLimitBreached.into());
227337
}
@@ -230,11 +340,12 @@ fn check_position_limits(
230340
} else {
231341
let size = maker_existing_position.safe_sub(params.min_position)?;
232342

233-
if size <= 0 {
343+
if size <= min_order_size.cast()? {
234344
msg!(
235-
"maker existing position {} <= min position {}",
345+
"maker existing position {} <= min position {} + min order size {}",
236346
maker_existing_position,
237-
params.min_position
347+
params.min_position,
348+
min_order_size
238349
);
239350
return Err(ErrorCode::PositionLimitBreached.into());
240351
}
@@ -256,55 +367,55 @@ mod tests {
256367
};
257368

258369
// same direction, doesn't breach
259-
let result = check_position_limits(params, PositionDirection::Long, 10, 40);
370+
let result = check_position_limits(params, PositionDirection::Long, 10, 40, 0);
260371
assert!(result.is_ok());
261372
assert_eq!(result.unwrap(), 10);
262-
let result = check_position_limits(params, PositionDirection::Short, 10, -40);
373+
let result = check_position_limits(params, PositionDirection::Short, 10, -40, 0);
263374
assert!(result.is_ok());
264375
assert_eq!(result.unwrap(), 10);
265376

266377
// same direction, whole order breaches, only takes enough to hit limit
267-
let result = check_position_limits(params, PositionDirection::Long, 100, 40);
378+
let result = check_position_limits(params, PositionDirection::Long, 100, 40, 0);
268379
assert!(result.is_ok());
269380
assert_eq!(result.unwrap(), 60);
270-
let result = check_position_limits(params, PositionDirection::Short, 100, -40);
381+
let result = check_position_limits(params, PositionDirection::Short, 100, -40, 0);
271382
assert!(result.is_ok());
272383
assert_eq!(result.unwrap(), 60);
273384

274385
// opposite direction, doesn't breach
275-
let result = check_position_limits(params, PositionDirection::Long, 10, -40);
386+
let result = check_position_limits(params, PositionDirection::Long, 10, -40, 0);
276387
assert!(result.is_ok());
277388
assert_eq!(result.unwrap(), 10);
278-
let result = check_position_limits(params, PositionDirection::Short, 10, 40);
389+
let result = check_position_limits(params, PositionDirection::Short, 10, 40, 0);
279390
assert!(result.is_ok());
280391
assert_eq!(result.unwrap(), 10);
281392

282393
// opposite direction, whole order breaches, only takes enough to take flipped limit
283-
let result = check_position_limits(params, PositionDirection::Long, 200, -40);
394+
let result = check_position_limits(params, PositionDirection::Long, 200, -40, 0);
284395
assert!(result.is_ok());
285396
assert_eq!(result.unwrap(), 140);
286-
let result = check_position_limits(params, PositionDirection::Short, 200, 40);
397+
let result = check_position_limits(params, PositionDirection::Short, 200, 40, 0);
287398
assert!(result.is_ok());
288399
assert_eq!(result.unwrap(), 140);
289400

290401
// opposite direction, maker already breached, allows reducing
291-
let result = check_position_limits(params, PositionDirection::Long, 200, -150);
402+
let result = check_position_limits(params, PositionDirection::Long, 200, -150, 0);
292403
assert!(result.is_ok());
293404
assert_eq!(result.unwrap(), 200);
294-
let result = check_position_limits(params, PositionDirection::Short, 200, 150);
405+
let result = check_position_limits(params, PositionDirection::Short, 200, 150, 0);
295406
assert!(result.is_ok());
296407
assert_eq!(result.unwrap(), 200);
297408

298409
// same direction, maker already breached, errors
299-
let result = check_position_limits(params, PositionDirection::Long, 200, 150);
410+
let result = check_position_limits(params, PositionDirection::Long, 200, 150, 0);
300411
assert!(result.is_err());
301-
let result = check_position_limits(params, PositionDirection::Short, 200, -150);
412+
let result = check_position_limits(params, PositionDirection::Short, 200, -150, 0);
302413
assert!(result.is_err());
303414
}
304415
}
305416

306417
fn place_and_make<'info>(
307-
ctx: Context<'_, '_, '_, 'info, Jit<'info>>,
418+
ctx: &Context<'_, '_, '_, 'info, Jit<'info>>,
308419
taker_order_id: u32,
309420
order_params: OrderParams,
310421
) -> Result<()> {

programs/jit-proxy/src/state.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub enum PostOnlyParam {
88
None,
99
MustPostOnly, // Tx fails if order can't be post only
1010
TryPostOnly, // Tx succeeds and order not placed if can't be post only
11+
Slide, // Modify price to be post only if can't be post only
1112
}
1213

1314
impl PostOnlyParam {
@@ -16,6 +17,7 @@ impl PostOnlyParam {
1617
PostOnlyParam::None => DriftPostOnlyParam::None,
1718
PostOnlyParam::MustPostOnly => DriftPostOnlyParam::MustPostOnly,
1819
PostOnlyParam::TryPostOnly => DriftPostOnlyParam::TryPostOnly,
20+
PostOnlyParam::Slide => DriftPostOnlyParam::Slide,
1921
}
2022
}
2123
}

python/pyproject.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
2-
name = "jit_proxy"
3-
version = "0.1.0"
2+
name = "drift_jit_proxy"
3+
version = "0.1.3"
44
description = "python sdk for drift protocol v2 jit proxy program"
55
authors = ["Frank <[email protected]>"]
66
readme = "README.md"
@@ -13,10 +13,10 @@ python = "^3.10"
1313
python-dotenv = "^1.0.0"
1414
solana = "^0.30.1"
1515
anchorpy = "^0.17.1"
16-
driftpy = "0.7.9"
16+
driftpy = "0.7.35"
1717

1818
[build-system]
1919
requires = ["poetry-core"]
2020
build-backend = "poetry.core.masonry.api"
2121

22-
#
22+
#

python/sdk/jit_proxy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.1.0"
1+
__version__ = "0.1.1"

0 commit comments

Comments
 (0)