perf: add fixed prepared order path

This commit is contained in:
floor-licker
2026-06-22 21:54:21 -04:00
parent fd8a2ebdb3
commit 4115d0c4cb
2 changed files with 236 additions and 11 deletions
+82
View File
@@ -114,6 +114,20 @@ fn benchmark_create_order_eip712(c: &mut Criterion) {
black_box(signed_order)
})
});
c.bench_function("prepared_fixed_create_order_eip712_signature", |b| {
b.iter(|| {
let signed_order = prepared
.create_limit_order_fixed(
black_box(order_args.side),
black_box(7_537),
black_box(1_002_500),
black_box(order_args.expiration),
)
.unwrap();
black_box(signed_order)
})
});
}
// Benchmark: Serialize a signed order body and build L2 auth headers for POST /order.
@@ -134,6 +148,18 @@ fn benchmark_order_submit_payload_auth(c: &mut Criterion) {
passphrase: "benchmark-passphrase".to_string(),
};
let prepared_api_creds = PreparedApiCredentials::try_new(api_creds.clone()).unwrap();
let order_args = test_order_args();
let options = test_order_options();
let prepared = builder
.prepare_order_path(
CHAIN_ID,
order_args.token_id.clone(),
options.tick_size.unwrap(),
options.neg_risk.unwrap(),
order_args.builder_code.as_deref(),
order_args.metadata.as_deref(),
)
.unwrap();
c.bench_function("order_submit_body_and_l2_headers", |b| {
b.iter(|| {
@@ -154,6 +180,62 @@ fn benchmark_order_submit_payload_auth(c: &mut Criterion) {
black_box((body_bytes, headers))
})
});
c.bench_function("prepared_decimal_order_body_and_l2_headers", |b| {
b.iter(|| {
let signed_order = prepared
.create_limit_order(
black_box(order_args.side),
black_box(order_args.price),
black_box(order_args.size),
black_box(order_args.expiration),
)
.unwrap();
let body = PostOrder::new(
black_box(signed_order),
black_box(api_creds.api_key.clone()),
black_box(post_options),
);
let body_bytes = serde_json::to_vec(black_box(&body)).unwrap();
let headers = create_l2_headers_with_body_bytes(
&signer,
&prepared_api_creds,
"POST",
"/order",
Some(&body_bytes),
)
.unwrap();
black_box((body_bytes, headers))
})
});
c.bench_function("prepared_fixed_order_body_and_l2_headers", |b| {
b.iter(|| {
let signed_order = prepared
.create_limit_order_fixed(
black_box(order_args.side),
black_box(7_537),
black_box(1_002_500),
black_box(order_args.expiration),
)
.unwrap();
let body = PostOrder::new(
black_box(signed_order),
black_box(api_creds.api_key.clone()),
black_box(post_options),
);
let body_bytes = serde_json::to_vec(black_box(&body)).unwrap();
let headers = create_l2_headers_with_body_bytes(
&signer,
&prepared_api_creds,
"POST",
"/order",
Some(&body_bytes),
)
.unwrap();
black_box((body_bytes, headers))
})
});
}
// Benchmark: JSON parsing (simulate market data parsing)
+154 -11
View File
@@ -8,8 +8,8 @@ use crate::auth::{
};
use crate::errors::{PolyfillError, Result};
use crate::types::{
CreateOrderOptions, MarketOrderArgs, OrderArgs, OrderType, Side, SignedOrderRequest,
SCALE_FACTOR,
CreateOrderOptions, MarketOrderArgs, OrderArgs, OrderType, Price, Qty, Side,
SignedOrderRequest, MAX_PRICE_TICKS, MIN_PRICE_TICKS, SCALE_FACTOR,
};
use alloy_primitives::{keccak256, Address, B256, U256};
use alloy_signer_local::PrivateKeySigner;
@@ -66,6 +66,7 @@ pub struct PreparedOrderPath {
builder: OrderBuilder,
token_id: String,
token_id_u256: U256,
tick_size_ticks: Price,
round_config: RoundConfig,
domain: PreparedOrderDomain,
builder_bytes: B256,
@@ -230,6 +231,20 @@ fn decimal_to_token_units(amt: Decimal) -> Result<U256> {
}
fn parse_round_config(tick_size: Decimal) -> Result<&'static RoundConfig> {
let tick_size_ticks = parse_tick_size_ticks(tick_size)?;
match tick_size_ticks {
1000 => Ok(&ROUND_CONFIG_0_1),
100 => Ok(&ROUND_CONFIG_0_01),
10 => Ok(&ROUND_CONFIG_0_001),
1 => Ok(&ROUND_CONFIG_0_0001),
_ => Err(PolyfillError::validation(format!(
"Unsupported tick size {tick_size}"
))),
}
}
fn parse_tick_size_ticks(tick_size: Decimal) -> Result<Price> {
let scaled = tick_size * Decimal::from(SCALE_FACTOR);
if !scaled.is_integer() {
return Err(PolyfillError::validation(format!(
@@ -241,15 +256,7 @@ fn parse_round_config(tick_size: Decimal) -> Result<&'static RoundConfig> {
.try_into()
.map_err(|_| PolyfillError::validation(format!("Unsupported tick size {tick_size}")))?;
match tick_size_ticks {
1000 => Ok(&ROUND_CONFIG_0_1),
100 => Ok(&ROUND_CONFIG_0_01),
10 => Ok(&ROUND_CONFIG_0_001),
1 => Ok(&ROUND_CONFIG_0_0001),
_ => Err(PolyfillError::validation(format!(
"Unsupported tick size {tick_size}"
))),
}
Ok(tick_size_ticks)
}
pub(crate) fn validate_bytes32_hex(field: &str, value: &str) -> Result<()> {
@@ -382,6 +389,7 @@ impl OrderBuilder {
) -> Result<PreparedOrderPath> {
let token_id = token_id.into();
let token_id_u256 = parse_token_id(&token_id)?;
let tick_size_ticks = parse_tick_size_ticks(tick_size)?;
let round_config = *parse_round_config(tick_size)?;
let exchange = exchange_address_for(chain_id, neg_risk)?;
let domain = PreparedOrderDomain::new(chain_id, exchange);
@@ -392,6 +400,7 @@ impl OrderBuilder {
builder: self.clone(),
token_id,
token_id_u256,
tick_size_ticks,
round_config,
domain,
builder_bytes,
@@ -670,6 +679,24 @@ impl PreparedOrderPath {
self.build_signed_order(side, maker_amount, taker_amount, expiration.unwrap_or(0))
}
/// Create and sign a limit order from fixed-point book values.
///
/// `price_ticks` use the shared price scale where `7_537` means `0.7537`.
/// `size_units` use the shared quantity scale where `1_002_500` means `100.25`.
/// The size is truncated to Polymarket's two-decimal lot size, matching the Decimal path.
pub fn create_limit_order_fixed(
&self,
side: Side,
price_ticks: Price,
size_units: Qty,
expiration: Option<u64>,
) -> Result<SignedOrderRequest> {
let (maker_amount, taker_amount) =
self.get_fixed_order_amounts(side, price_ticks, size_units)?;
self.build_signed_order(side, maker_amount, taker_amount, expiration.unwrap_or(0))
}
/// Create and sign a market order using the cached market/token context.
pub fn create_market_order(
&self,
@@ -691,6 +718,52 @@ impl PreparedOrderPath {
self.build_signed_order(side, maker_amount, taker_amount, 0)
}
fn get_fixed_order_amounts(
&self,
side: Side,
price_ticks: Price,
size_units: Qty,
) -> Result<(U256, U256)> {
if !(MIN_PRICE_TICKS..=MAX_PRICE_TICKS).contains(&price_ticks) {
return Err(PolyfillError::validation("Price outside valid range"));
}
if self.tick_size_ticks > 0 && !price_ticks.is_multiple_of(self.tick_size_ticks) {
return Err(PolyfillError::validation("Price not aligned to tick size"));
}
if size_units <= 0 {
return Err(PolyfillError::validation("Size must be positive"));
}
// Polymarket order sizes are rounded toward zero to two decimal places.
// Qty uses 1e4 scale, so one lot is 0.01 == 100 units.
let lot_size_units = 100i64;
let lot_aligned_size = (size_units / lot_size_units) * lot_size_units;
if lot_aligned_size <= 0 {
return Err(PolyfillError::validation("Size below minimum lot"));
}
let size_token_units = u128::try_from(lot_aligned_size)
.map_err(|_| PolyfillError::validation("Invalid size"))?
.checked_mul(100)
.ok_or_else(|| PolyfillError::validation("Size overflow"))?;
let notional_token_units = u128::try_from(lot_aligned_size)
.map_err(|_| PolyfillError::validation("Invalid size"))?
.checked_mul(price_ticks as u128)
.ok_or_else(|| PolyfillError::validation("Order notional overflow"))?
/ 100;
match side {
Side::BUY => Ok((
U256::from(notional_token_units),
U256::from(size_token_units),
)),
Side::SELL => Ok((
U256::from(size_token_units),
U256::from(notional_token_units),
)),
}
}
fn build_signed_order(
&self,
side: Side,
@@ -964,6 +1037,76 @@ mod tests {
assert!(prepared_order.signature.starts_with("0x"));
}
#[test]
fn test_prepared_fixed_limit_order_matches_decimal_amounts() {
let builder = test_builder();
let args = OrderArgs {
token_id: "12345678901234567890".to_string(),
price: Decimal::from_str("0.7537").unwrap(),
size: Decimal::from_str("100.25").unwrap(),
side: Side::BUY,
expiration: Some(1_900_000_000),
builder_code: Some(BYTES32_ZERO.to_string()),
metadata: Some(BYTES32_ZERO.to_string()),
};
let options = CreateOrderOptions {
tick_size: Some(Decimal::from_str("0.0001").unwrap()),
neg_risk: Some(false),
};
let prepared = builder
.prepare_order_path(
137,
args.token_id.clone(),
options.tick_size.unwrap(),
options.neg_risk.unwrap(),
args.builder_code.as_deref(),
args.metadata.as_deref(),
)
.unwrap();
let decimal_buy = prepared
.create_limit_order(Side::BUY, args.price, args.size, args.expiration)
.unwrap();
let fixed_buy = prepared
.create_limit_order_fixed(Side::BUY, 7_537, 1_002_500, args.expiration)
.unwrap();
assert_eq!(fixed_buy.maker_amount, decimal_buy.maker_amount);
assert_eq!(fixed_buy.taker_amount, decimal_buy.taker_amount);
assert_eq!(fixed_buy.side, decimal_buy.side);
let decimal_sell = prepared
.create_limit_order(Side::SELL, args.price, args.size, args.expiration)
.unwrap();
let fixed_sell = prepared
.create_limit_order_fixed(Side::SELL, 7_537, 1_002_500, args.expiration)
.unwrap();
assert_eq!(fixed_sell.maker_amount, decimal_sell.maker_amount);
assert_eq!(fixed_sell.taker_amount, decimal_sell.taker_amount);
assert_eq!(fixed_sell.side, decimal_sell.side);
}
#[test]
fn test_prepared_fixed_limit_order_rejects_invalid_inputs() {
let builder = test_builder();
let prepared = builder
.prepare_order_path(
137,
"12345678901234567890",
Decimal::from_str("0.01").unwrap(),
false,
Some(BYTES32_ZERO),
Some(BYTES32_ZERO),
)
.unwrap();
assert!(prepared
.create_limit_order_fixed(Side::BUY, 7_537, 1_002_500, Some(1_900_000_000))
.is_err());
assert!(prepared
.create_limit_order_fixed(Side::BUY, 7_500, 0, Some(1_900_000_000))
.is_err());
}
#[test]
fn test_prepared_market_order_rejects_unsupported_order_type() {
let builder = test_builder();