diff --git a/src/swift_server.rs b/src/swift_server.rs index 766c80a..ad635de 100644 --- a/src/swift_server.rs +++ b/src/swift_server.rs @@ -18,7 +18,8 @@ use crate::{ PROCESS_ORDER_RESPONSE_ERROR_MSG_INVALID_ORDER_AMOUNT, PROCESS_ORDER_RESPONSE_ERROR_MSG_ORDER_SLOT_TOO_OLD, PROCESS_ORDER_RESPONSE_ERROR_MSG_VERIFY_SIGNATURE, - PROCESS_ORDER_RESPONSE_IGNORE_PUBKEY, PROCESS_ORDER_RESPONSE_MESSAGE_SUCCESS, + PROCESS_ORDER_RESPONSE_IGNORE_PUBKEY, PROCESS_ORDER_RESPONSE_INVALID_UUID_UTF8, + PROCESS_ORDER_RESPONSE_MESSAGE_SUCCESS, }, types::{unix_now_ms, RequestContext}, }, @@ -118,6 +119,16 @@ pub async fn process_order_wrapper( Json(incoming_message): Json, ) -> impl axum::response::IntoResponse { let context = RequestContext::from_incoming_message(&incoming_message); + if context.is_err() { + return ( + axum::http::StatusCode::BAD_REQUEST, + Json(ProcessOrderResponse { + message: PROCESS_ORDER_RESPONSE_INVALID_UUID_UTF8, + error: None, + }), + ); + } + let context = context.unwrap(); let (status, resp) = match process_order(server_params, incoming_message, false, &context).await { @@ -439,6 +450,16 @@ pub async fn deposit_trade( .order() .info(&req.swift_order.taker_authority); let context = RequestContext::from_incoming_message(&req.swift_order); + if context.is_err() { + return ( + axum::http::StatusCode::BAD_REQUEST, + Json(ProcessOrderResponse { + message: PROCESS_ORDER_RESPONSE_INVALID_UUID_UTF8, + error: None, + }), + ); + } + let context = context.unwrap(); let current_slot = server_params.slot_subscriber.current_slot(); let max_margin_ratio = match extract_signed_message_info( @@ -1705,6 +1726,7 @@ mod tests { accounts::User, SignedMsgOrderParamsDelegateMessage, SignedMsgOrderParamsMessage, SignedMsgTriggerOrderParams, }; + use ed25519_dalek::Signature as Ed25519Signature; use solana_sdk::native_token::LAMPORTS_PER_SOL; fn is_isolated_deposit(signed_msg: &SignedOrderType) -> bool { @@ -1882,6 +1904,78 @@ mod tests { ); } + #[test] + fn test_request_context_from_incoming_message_valid_utf8() { + let taker = Pubkey::new_unique(); + let uuid_valid: [u8; 8] = [b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h']; + let authority_msg = SignedOrderType::authority(SignedMsgOrderParamsMessage { + sub_account_id: 0, + signed_msg_order_params: OrderParams { + market_index: 2, + market_type: MarketType::Perp, + order_type: OrderType::Market, + base_asset_amount: LAMPORTS_PER_SOL, + price: 1000, + direction: PositionDirection::Long, + ..Default::default() + }, + uuid: uuid_valid, + slot: 1000, + stop_loss_order_params: None, + take_profit_order_params: None, + max_margin_ratio: None, + builder_fee_tenth_bps: None, + builder_idx: None, + isolated_position_deposit: None, + }); + let msg = IncomingSignedMessage { + taker_pubkey: taker, + signature: Ed25519Signature::from_bytes(&[0u8; 64]).unwrap(), + message: authority_msg, + signing_authority: Pubkey::default(), + taker_authority: Pubkey::default(), + }; + let ctx = RequestContext::from_incoming_message(&msg).expect("valid utf8 uuid"); + assert_eq!(ctx.order_uuid, "abcdefgh"); + assert_eq!(ctx.market_index, 2); + assert_eq!(ctx.market_type, "perp"); + assert_eq!(ctx.taker_authority, taker); + } + + #[test] + fn test_request_context_from_incoming_message_invalid_utf8() { + let taker = Pubkey::new_unique(); + let uuid_invalid: [u8; 8] = [0xFF; 8]; + let authority_msg = SignedOrderType::authority(SignedMsgOrderParamsMessage { + sub_account_id: 0, + signed_msg_order_params: OrderParams { + market_index: 0, + market_type: MarketType::Perp, + order_type: OrderType::Market, + base_asset_amount: LAMPORTS_PER_SOL, + price: 1000, + direction: PositionDirection::Long, + ..Default::default() + }, + uuid: uuid_invalid, + slot: 1000, + stop_loss_order_params: None, + take_profit_order_params: None, + max_margin_ratio: None, + builder_fee_tenth_bps: None, + builder_idx: None, + isolated_position_deposit: None, + }); + let msg = IncomingSignedMessage { + taker_pubkey: taker, + signature: Ed25519Signature::from_bytes(&[0u8; 64]).unwrap(), + message: authority_msg, + signing_authority: Pubkey::default(), + taker_authority: Pubkey::default(), + }; + assert!(RequestContext::from_incoming_message(&msg).is_err()); + } + #[test] fn test_extract_signed_message_info_delegated() { let taker_authority = Pubkey::new_unique(); diff --git a/src/types/messages.rs b/src/types/messages.rs index 7ff0c7c..decd226 100644 --- a/src/types/messages.rs +++ b/src/types/messages.rs @@ -154,6 +154,7 @@ pub const PROCESS_ORDER_RESPONSE_ERROR_MSG_INVALID_ORDER_AMOUNT: &str = "Invalid base_asset_amount in tp/sl"; pub const PROCESS_ORDER_RESPONSE_ERROR_MSG_DELIVERY_FAILED: &str = "Failed to deliver message"; pub const PROCESS_ORDER_RESPONSE_IGNORE_PUBKEY: &str = "Ignore pubkey"; +pub const PROCESS_ORDER_RESPONSE_INVALID_UUID_UTF8: &str = "Order uuid invalid utf8"; #[derive(serde::Deserialize, Clone, Debug)] #[serde(rename_all = "lowercase")] @@ -449,7 +450,7 @@ mod tests { if let SignedOrderType::Authority { inner: signed_msg, - raw, + raw: _, } = actual.order() { let expected = SignedMsgOrderParamsMessage { diff --git a/src/types/types.rs b/src/types/types.rs index 126c7a2..ae04a89 100644 --- a/src/types/types.rs +++ b/src/types/types.rs @@ -49,7 +49,7 @@ pub struct RequestContext { } impl RequestContext { - pub fn from_incoming_message(msg: &IncomingSignedMessage) -> Self { + pub fn from_incoming_message(msg: &IncomingSignedMessage) -> Result { let recv_ts = unix_now_ms(); let info = msg.order().info(&msg.taker_authority); let market_index = info.order_params.market_index; @@ -57,24 +57,60 @@ impl RequestContext { MarketType::Perp => "perp", MarketType::Spot => "spot", }; - let order_uuid = core::str::from_utf8(&info.uuid) - .unwrap_or("no uuid ????????") - .to_string(); - let taker_authority = if msg.taker_authority != Pubkey::default() { - msg.taker_authority - } else { - msg.taker_pubkey - }; + match core::str::from_utf8(&info.uuid) { + Ok(order_uuid) => { + let taker_authority = if msg.taker_authority != Pubkey::default() { + msg.taker_authority + } else { + msg.taker_pubkey + }; - Self { - market_index, - market_type, - recv_ts, - log_prefix: format!( - "[order uuid={order_uuid} market={market_type}:{market_index} taker={taker_authority}]" - ), - taker_authority, - order_uuid, + Ok(Self { + market_index, + market_type, + recv_ts, + log_prefix: format!( + "[order uuid={order_uuid} market={market_type}:{market_index} taker={taker_authority}]" + ), + taker_authority, + order_uuid: order_uuid.to_string(), + }) + } + Err(_) => { + let taker_authority = if msg.taker_authority != Pubkey::default() { + msg.taker_authority + } else { + msg.taker_pubkey + }; + log::info!( + target: "swift", + "[order market={market_type}:{market_index} taker={taker_authority}] uuid invalid utf-8" + ); + Err(()) + } } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn from_incoming_message_valid_utf8_uuid() { + // Payload from messages::tests with uuid [115, 56, 108, 117, 74, 76, 90, 101] = "s8luJLZe" + let json = r#"{ + "market_index": 2, + "market_type": "perp", + "message": "c8d5a65e2234f55d0001010080841e00000000000000000000000000020000000000000000013201bb60507d000000000117c0127c000000000000272108160000000073386c754a4c5a650000", + "signature": "H8HRloc2vBdhHyiNK5W/Shv3kVKmIYsHTBzlD2ecyxyOUh7EuysU/Y5AOXZ3IpsMxRyLn6OSAHKEgCIQX4OpDQ==", + "signing_authority": "4rmhwytmKH1XsgGAUyUUH7U64HS5FtT6gM8HGKAfwcFE", + "taker_pubkey": "4rmhwytmKH1XsgGAUyUUH7U64HS5FtT6gM8HGKAfwcFE" + }"#; + let msg: IncomingSignedMessage = serde_json::from_str(json).expect("deserialize"); + let ctx = RequestContext::from_incoming_message(&msg).expect("valid utf8 uuid"); + assert_eq!(ctx.order_uuid, "s8luJLZe"); + assert_eq!(ctx.market_index, 2); + assert_eq!(ctx.market_type, "perp"); + } +}