From 5827fec6a97ee1e010a61b31c7abff078e65a044 Mon Sep 17 00:00:00 2001 From: Nour Alharithi <14929853+moosecat2@users.noreply.github.com> Date: Wed, 24 Sep 2025 11:33:40 -0700 Subject: [PATCH 1/3] fix building and deserialization tests pass --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- src/swift_server.rs | 3 ++- src/types/messages.rs | 48 ++++++++++++++++++++++++++----------------- 4 files changed, 35 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17a4882..d3c392a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1974,7 +1974,7 @@ checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" [[package]] name = "drift-idl-gen" version = "0.2.0" -source = "git+https://github.com/drift-labs/drift-rs.git?rev=cd60371#cd60371cbff430140c2577796f00bb6bc13be857" +source = "git+https://github.com/drift-labs/drift-rs.git?rev=bd276394fcf1ff3d076607cd8dde922ebd7038c7#bd276394fcf1ff3d076607cd8dde922ebd7038c7" dependencies = [ "proc-macro2", "quote", @@ -1987,7 +1987,7 @@ dependencies = [ [[package]] name = "drift-pubsub-client" version = "0.1.1" -source = "git+https://github.com/drift-labs/drift-rs.git?rev=cd60371#cd60371cbff430140c2577796f00bb6bc13be857" +source = "git+https://github.com/drift-labs/drift-rs.git?rev=bd276394fcf1ff3d076607cd8dde922ebd7038c7#bd276394fcf1ff3d076607cd8dde922ebd7038c7" dependencies = [ "futures-util", "gjson", @@ -2007,7 +2007,7 @@ dependencies = [ [[package]] name = "drift-rs" version = "1.0.0-alpha.16" -source = "git+https://github.com/drift-labs/drift-rs.git?rev=cd60371#cd60371cbff430140c2577796f00bb6bc13be857" +source = "git+https://github.com/drift-labs/drift-rs.git?rev=bd276394fcf1ff3d076607cd8dde922ebd7038c7#bd276394fcf1ff3d076607cd8dde922ebd7038c7" dependencies = [ "abi_stable", "ahash 0.8.12", diff --git a/Cargo.toml b/Cargo.toml index 749c591..c71a2e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ bincode = "1" clap = { version = "4.0", features = ["derive"] } dashmap = "6.1.0" dotenv = "0.15.0" -drift-rs = { git = "https://github.com/drift-labs/drift-rs.git" , rev = "cd60371" } +drift-rs = { git = "https://github.com/drift-labs/drift-rs.git" , rev = "bd276394fcf1ff3d076607cd8dde922ebd7038c7" } ed25519-dalek = "1.0.1" env_logger = "0.11" faster-hex = "0.10.0" diff --git a/src/swift_server.rs b/src/swift_server.rs index 73533a7..2e0ff68 100644 --- a/src/swift_server.rs +++ b/src/swift_server.rs @@ -320,7 +320,8 @@ pub async fn process_order( let order_metadata = OrderMetadataAndMessage { signing_authority: signing_pubkey, taker_authority, - order_message: *signed_msg, + order_message: incoming_message.message.original_message.clone(), + deserialized_order_message: *signed_msg, order_signature: taker_signature.into(), ts: context.recv_ts, uuid, diff --git a/src/types/messages.rs b/src/types/messages.rs index 6ea655a..f423547 100644 --- a/src/types/messages.rs +++ b/src/types/messages.rs @@ -21,6 +21,8 @@ pub struct SignedOrderTypeWithLen { pub signed_len: usize, /// signed order message pub order: SignedOrderType, + /// Original message bytes for ed25519 + pub original_message: Vec, } /// Deserialize hex-ified, borsh bytes as a `SignedOrderType` @@ -46,6 +48,8 @@ where faster_hex::hex_decode(payload.as_bytes(), &mut borsh_buf[..borsh_len]) .map_err(serde::de::Error::custom)?; + let original_message = payload.as_bytes().to_vec(); + // this is basically the same as if we derived AnchorDeserialize on `SignedOrderType` _expect_ it does not // add a u8 to distinguish the enum if borsh_buf[..8] == SWIFT_DELEGATE_MSG_PREFIX { @@ -53,6 +57,7 @@ where .map(|o| SignedOrderTypeWithLen { signed_len: borsh_len, order: SignedOrderType::Delegated(o), + original_message, }) .map_err(serde::de::Error::custom) } else { @@ -60,6 +65,7 @@ where .map(|o| SignedOrderTypeWithLen { signed_len: borsh_len, order: SignedOrderType::Authority(o), + original_message, }) .map_err(serde::de::Error::custom) } @@ -101,15 +107,8 @@ impl IncomingSignedMessage { PublicKey::from_bytes(self.taker_pubkey.as_array()) }?; - // client hex encodes msg before signing so use that as comparison - // since we allow older clients with potentially shorter messages than the current IDL - // need to truncate the message to the size the client signed (exclude default padded bytes) - let msg_data = &self.order().to_borsh()[..self.message.signed_len]; - let mut hex_bytes = vec![0; msg_data.len() * 2]; // 2 hex bytes per msg byte - let _ = faster_hex::hex_encode(msg_data, &mut hex_bytes).expect("hexified"); - pubkey - .verify(&hex_bytes, &self.signature) + .verify(&self.message.original_message, &self.signature) .context("Signature did not verify") } pub fn verify_and_get_signed_message(&self) -> Result<&SignedOrderType> { @@ -122,7 +121,9 @@ impl IncomingSignedMessage { pub struct OrderMetadataAndMessage { pub signing_authority: Pubkey, pub taker_authority: Pubkey, - pub order_message: SignedOrderType, + #[max_len(2*(SignedMsgOrderParamsDelegateMessage::INIT_SPACE + 8))] + pub order_message: Vec, + pub deserialized_order_message: SignedOrderType, pub order_signature: [u8; 64], pub uuid: [u8; 8], pub ts: u64, @@ -156,8 +157,17 @@ impl OrderMetadataAndMessage { } pub fn jsonify(&self) -> serde_json::Value { - let taker_order_params = self.order_message.info(&self.taker_authority).order_params; - let signed_msg_borsh = self.order_message.to_borsh(); + let taker_order_params = self + .deserialized_order_message + .info(&self.taker_authority) + .order_params; + + let order_message_str = match core::str::from_utf8(&self.order_message) { + Ok(s) if s.as_bytes().iter().all(u8::is_ascii_hexdigit) && (s.len() % 2 == 0) => { + s.to_string() + } + _ => faster_hex::hex_string(self.order_message.as_slice()), + }; json!({ "market_type": match taker_order_params.market_type { @@ -165,7 +175,7 @@ impl OrderMetadataAndMessage { MarketType::Spot => "spot", }, "market_index": taker_order_params.market_index, - "order_message": faster_hex::hex_string(signed_msg_borsh.as_slice()), + "order_message": order_message_str, "order_signature": base64::prelude::BASE64_STANDARD.encode(self.order_signature), "taker_authority": self.taker_authority.to_string(), "signing_authority": self.signing_authority.to_string(), @@ -521,7 +531,8 @@ mod tests { let encoded = OrderMetadataAndMessage { signing_authority: Pubkey::new_unique(), taker_authority: Pubkey::new_unique(), - order_message: SignedOrderType::Authority(Default::default()), + order_message: vec![0u8; 2 * (SignedMsgOrderParamsDelegateMessage::INIT_SPACE + 8)], + deserialized_order_message: SignedOrderType::Authority(Default::default()), order_signature: [1u8; 64], ts: 55555, uuid: nanoid!(8).as_bytes().try_into().unwrap(), @@ -531,7 +542,7 @@ mod tests { let order_metadata = OrderMetadataAndMessage::decode(&encoded).unwrap(); dbg!(base64::prelude::BASE64_STANDARD.encode( &order_metadata - .order_message + .deserialized_order_message .try_to_vec() .unwrap() .as_slice() @@ -560,13 +571,15 @@ mod tests { }; let signed_order_message = SignedOrderType::Authority(order_params); + let hex_msg = faster_hex::hex_string(signed_order_message.to_borsh().as_slice()); let order_metadata_json = OrderMetadataAndMessage { signing_authority, taker_authority, + order_message: hex_msg.as_bytes().to_vec(), order_signature, uuid: order_params.uuid, - order_message: signed_order_message.clone(), + deserialized_order_message: signed_order_message.clone(), ts: 55555, will_sanitize: false, } @@ -585,10 +598,7 @@ mod tests { base64::prelude::BASE64_STANDARD.encode(order_signature), ); - assert_eq!( - order_metadata_json["order_message"], - faster_hex::hex_string(signed_order_message.to_borsh().as_slice()), - ); + assert_eq!(order_metadata_json["order_message"], hex_msg,); assert_eq!(order_metadata_json["ts"], 55555); From f7bba8da066017a7842047091b8082650ec27d8c Mon Sep 17 00:00:00 2001 From: Nour Alharithi <14929853+moosecat2@users.noreply.github.com> Date: Wed, 24 Sep 2025 12:49:04 -0700 Subject: [PATCH 2/3] use const for max length --- src/types/messages.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/types/messages.rs b/src/types/messages.rs index f423547..fe819ea 100644 --- a/src/types/messages.rs +++ b/src/types/messages.rs @@ -15,6 +15,9 @@ use ed25519_dalek::{PublicKey, Signature, Verifier}; use serde_json::json; use solana_sdk::{pubkey::Pubkey, transaction::VersionedTransaction}; +pub const MAX_SIGNED_MSG_BORSH_LEN: usize = SignedMsgOrderParamsDelegateMessage::INIT_SPACE + 8; +pub const MAX_SIGNED_MSG_HEX_LEN: usize = MAX_SIGNED_MSG_BORSH_LEN * 2; + #[derive(Clone, Debug, PartialEq)] pub struct SignedOrderTypeWithLen { /// length of the signed order when borsh encoded @@ -121,7 +124,7 @@ impl IncomingSignedMessage { pub struct OrderMetadataAndMessage { pub signing_authority: Pubkey, pub taker_authority: Pubkey, - #[max_len(2*(SignedMsgOrderParamsDelegateMessage::INIT_SPACE + 8))] + #[max_len(MAX_SIGNED_MSG_HEX_LEN)] pub order_message: Vec, pub deserialized_order_message: SignedOrderType, pub order_signature: [u8; 64], From a68de06f32eb43671a7b738678b179ec77e12825 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Thu, 25 Sep 2025 10:10:19 +0800 Subject: [PATCH 3/3] backport hot fix for reduce-only order closing --- src/swift_server.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/swift_server.rs b/src/swift_server.rs index 2e0ff68..2167a51 100644 --- a/src/swift_server.rs +++ b/src/swift_server.rs @@ -830,8 +830,11 @@ fn validate_signed_order_params( } if taker_order_params.base_asset_amount < min_order_size { - log::info!(target: "server", "{} < {min_order_size}", taker_order_params.base_asset_amount); - return Err(ErrorCode::InvalidOrderSizeTooSmall); + // can always close reduce_only + if !taker_order_params.reduce_only { + log::info!(target: "server", "{} < {min_order_size}", taker_order_params.base_asset_amount); + return Err(ErrorCode::InvalidOrderSizeTooSmall); + } } // has_valid_auction_params