@@ -2,7 +2,7 @@ use std::{
22 collections:: HashSet ,
33 env,
44 net:: SocketAddr ,
5- sync:: { atomic:: AtomicBool , Arc , LazyLock } ,
5+ sync:: { atomic:: AtomicBool , Arc } ,
66 time:: { Duration , SystemTime } ,
77} ;
88
@@ -27,7 +27,7 @@ use crate::{
2727 metrics:: { metrics_handler, MetricsServerParams , SwiftServerMetrics } ,
2828 } ,
2929} ;
30- use anchor_lang:: { AnchorDeserialize , Discriminator } ;
30+ use anchor_lang:: AnchorDeserialize ;
3131use axum:: {
3232 extract:: State ,
3333 http:: { self , Method , StatusCode } ,
@@ -38,15 +38,14 @@ use base64::Engine;
3838use dotenv:: dotenv;
3939use drift_rs:: {
4040 constants:: high_leverage_mode_account,
41- drift_idl,
4241 event_subscriber:: PubsubClient ,
43- math:: { account_list_builder:: AccountsListBuilder , constants :: PRICE_PRECISION } ,
42+ math:: account_list_builder:: AccountsListBuilder ,
4443 swift_order_subscriber:: { SignedMessageInfo , SignedOrderType } ,
4544 types:: {
4645 accounts:: { HighLeverageModeConfig , User } ,
4746 errors:: ErrorCode ,
4847 CommitmentConfig , MarketId , MarketType , OrderParams , OrderType , PositionDirection ,
49- ProgramError , SdkError , SignedMsgTriggerOrderParams , VersionedMessage ,
48+ ProgramError , SdkError , SdkResult , SignedMsgTriggerOrderParams , VersionedMessage ,
5049 VersionedTransaction ,
5150 } ,
5251 Context , DriftClient , RpcClient , TransactionBuilder , Wallet ,
@@ -58,7 +57,12 @@ use rdkafka::{
5857 util:: Timeout ,
5958} ;
6059use redis:: { aio:: MultiplexedConnection , AsyncCommands } ;
61- use solana_rpc_client_api:: { client_error, config:: RpcSimulateTransactionConfig } ;
60+ use solana_account_decoder_client_types:: UiAccountEncoding ;
61+ use solana_rpc_client_api:: {
62+ client_error,
63+ config:: { RpcSimulateTransactionAccountsConfig , RpcSimulateTransactionConfig } ,
64+ response:: RpcSimulateTransactionResult ,
65+ } ;
6266use solana_sdk:: {
6367 clock:: Slot ,
6468 hash:: Hash ,
@@ -123,7 +127,8 @@ pub async fn process_order_wrapper(
123127 let uuid = core:: str:: from_utf8 ( & uuid_raw) . unwrap_or ( "00000000" ) ;
124128 let context = RequestContext :: from_incoming_message ( & incoming_message) ;
125129
126- let ( status, resp) = match process_order ( server_params, incoming_message, & context) . await {
130+ let ( status, resp) = match process_order ( server_params, incoming_message, false , & context) . await
131+ {
127132 Ok ( order_metadata) => {
128133 let metrics_labels = & [
129134 context. market_type ,
@@ -160,6 +165,7 @@ pub async fn process_order_wrapper(
160165pub async fn process_order (
161166 server_params : & ' static ServerParams ,
162167 incoming_message : IncomingSignedMessage ,
168+ skip_sim : bool ,
163169 context : & RequestContext ,
164170) -> Result < OrderMetadataAndMessage , ( http:: StatusCode , ProcessOrderResponse ) > {
165171 let IncomingSignedMessage {
@@ -258,44 +264,46 @@ pub async fn process_order(
258264 ) ) ;
259265 }
260266
261- match server_params
262- . simulate_taker_order_rpc ( & taker_pubkey, & order_params, delegate_signer, current_slot)
263- . await
264- {
265- Ok ( sim_res) => {
266- server_params
267- . metrics
268- . rpc_simulation_status
269- . with_label_values ( & [ sim_res. as_str ( ) ] )
270- . inc ( ) ;
271- }
272- Err ( ( status, sim_err_str, logs) ) => {
273- server_params
274- . metrics
275- . rpc_simulation_status
276- . with_label_values ( & [ "invalid" ] )
277- . inc ( ) ;
278- log:: warn!(
279- target: "server" ,
280- "{}: Order sim failed (taker: {taker_pubkey:?}, delegate: {delegate_signer:?}, market: {}-{}): {sim_err_str}. Logs: {logs:?}" ,
281- context. log_prefix,
282- order_params. market_type. as_str( ) ,
283- order_params. market_index,
284- ) ;
285- log:: warn!(
286- target: "server" ,
287- "{}: failed order params: {order_params:?}" ,
288- context. log_prefix,
289- ) ;
290- return Err ( (
291- status,
292- ProcessOrderResponse {
293- message : PROCESS_ORDER_RESPONSE_ERROR_MSG_INVALID_ORDER ,
294- error : Some ( sim_err_str) ,
295- } ,
296- ) ) ;
297- }
298- } ;
267+ if !skip_sim {
268+ match server_params
269+ . simulate_taker_order_rpc ( & taker_pubkey, & order_params, delegate_signer, current_slot)
270+ . await
271+ {
272+ Ok ( sim_res) => {
273+ server_params
274+ . metrics
275+ . rpc_simulation_status
276+ . with_label_values ( & [ sim_res. as_str ( ) ] )
277+ . inc ( ) ;
278+ }
279+ Err ( ( status, sim_err_str, logs) ) => {
280+ server_params
281+ . metrics
282+ . rpc_simulation_status
283+ . with_label_values ( & [ "invalid" ] )
284+ . inc ( ) ;
285+ log:: warn!(
286+ target: "server" ,
287+ "{}: Order sim failed (taker: {taker_pubkey:?}, delegate: {delegate_signer:?}, market: {}-{}): {sim_err_str}. Logs: {logs:?}" ,
288+ context. log_prefix,
289+ order_params. market_type. as_str( ) ,
290+ order_params. market_index,
291+ ) ;
292+ log:: warn!(
293+ target: "server" ,
294+ "{}: failed order params: {order_params:?}" ,
295+ context. log_prefix,
296+ ) ;
297+ return Err ( (
298+ status,
299+ ProcessOrderResponse {
300+ message : PROCESS_ORDER_RESPONSE_ERROR_MSG_INVALID_ORDER ,
301+ error : Some ( sim_err_str) ,
302+ } ,
303+ ) ) ;
304+ }
305+ } ;
306+ }
299307
300308 // If fat fingered order that requires sanitization, then just send the order
301309 let will_sanitize = server_params. simulate_will_auction_params_sanitize ( & order_params) ;
@@ -370,30 +378,27 @@ pub async fn send_heartbeat(server_params: &'static ServerParams) {
370378 }
371379}
372380
373- static DEPOSIT_IX_WHITELIST : LazyLock < HashSet < [ u8 ; 8 ] > > = LazyLock :: new ( || {
374- HashSet :: < [ u8 ; 8 ] > :: from_iter ( [
375- drift_idl:: instructions:: Deposit :: DISCRIMINATOR
376- . try_into ( )
377- . unwrap ( ) ,
378- drift_idl:: instructions:: EnableUserHighLeverageMode :: DISCRIMINATOR
379- . try_into ( )
380- . unwrap ( ) ,
381- drift_idl:: instructions:: InitializeSignedMsgUserOrders :: DISCRIMINATOR
382- . try_into ( )
383- . unwrap ( ) ,
384- ] )
385- } ) ;
386-
387381pub async fn deposit_trade (
388382 State ( server_params) : State < & ' static ServerParams > ,
389383 Json ( req) : Json < DepositAndPlaceRequest > ,
390384) -> impl axum:: response:: IntoResponse {
391- let min_deposit_value = 100 * PRICE_PRECISION as u64 ;
385+ let signed_order_info = req
386+ . swift_order
387+ . message
388+ . info ( & req. swift_order . taker_authority ) ;
389+
390+ let uuid = core:: str:: from_utf8 ( & signed_order_info. uuid ) . unwrap_or ( "<bad uuid>" ) ;
391+ log:: info!(
392+ target: "server" ,
393+ "[{uuid}] depositToTrade request | authority={:?},subaccount={:?}" ,
394+ req. swift_order. taker_authority,
395+ req. swift_order. taker_pubkey
396+ ) ;
392397
393398 if req. deposit_tx . signatures . is_empty ( )
394- || req. deposit_tx . message . instructions ( ) . len ( ) != 1
395- || req. deposit_tx . verify_with_results ( ) . iter ( ) . all ( |x| * x)
399+ || req. deposit_tx . verify_with_results ( ) . iter ( ) . any ( |x| !* x)
396400 {
401+ log:: info!( target: "server" , "[{uuid}] invalid deposit tx" ) ;
397402 return (
398403 StatusCode :: BAD_REQUEST ,
399404 Json ( ProcessOrderResponse {
@@ -403,69 +408,18 @@ pub async fn deposit_trade(
403408 ) ;
404409 }
405410
406- // verify deposit ix exists and amount
407- let mut has_deposit = false ;
408- for ix in req. deposit_tx . message . instructions ( ) {
409- let ix_disc = & ix. data [ ..8 ] ;
410- if !DEPOSIT_IX_WHITELIST . contains ( ix_disc) {
411- return (
412- StatusCode :: BAD_REQUEST ,
413- Json ( ProcessOrderResponse {
414- message : "" ,
415- error : Some ( "unsupported deposit ix" . into ( ) ) ,
416- } ) ,
417- ) ;
418- }
419-
420- if ix_disc == drift_idl:: instructions:: Deposit :: DISCRIMINATOR {
421- has_deposit = true ;
422- if let Ok ( deposit_ix) =
423- drift_idl:: instructions:: Deposit :: deserialize ( & mut & ix. data [ 8 ..] )
424- {
425- let spot_oracle = server_params
426- . drift
427- . try_get_oracle_price_data_and_slot ( MarketId :: spot ( deposit_ix. market_index ) )
428- . expect ( "got price" ) ;
429- let deposit_value = spot_oracle. data . price as u64 * deposit_ix. amount ;
430- if deposit_value < min_deposit_value {
431- return (
432- StatusCode :: BAD_REQUEST ,
433- Json ( ProcessOrderResponse {
434- message : "" ,
435- error : Some ( format ! ( "deposit value must be > ${min_deposit_value}" ) ) ,
436- } ) ,
437- ) ;
438- }
439- } else {
440- return (
441- StatusCode :: BAD_REQUEST ,
442- Json ( ProcessOrderResponse {
443- message : "" ,
444- error : Some ( "invalid deposit ix encoding" . into ( ) ) ,
445- } ) ,
446- ) ;
447- }
448- }
449- }
450-
451- if !has_deposit {
452- return (
453- StatusCode :: BAD_REQUEST ,
454- Json ( ProcessOrderResponse {
455- message : "" ,
456- error : Some ( "missing deposit ix" . into ( ) ) ,
457- } ) ,
458- ) ;
459- }
460-
461- match server_params
462- . drift
463- . simulate_tx ( req. deposit_tx . message . clone ( ) )
464- . await
411+ // ensure deposit tx is valid
412+ let mut user_after_deposit = None ;
413+ match simulate_tx (
414+ & server_params. drift ,
415+ req. deposit_tx . message . clone ( ) ,
416+ & [ req. swift_order . taker_pubkey ] ,
417+ )
418+ . await
465419 {
466420 Ok ( res) => {
467421 if let Some ( err) = res. err {
468- log:: info!( target: "server" , "deposit sim failed: {err:?}" ) ;
422+ log:: info!( target: "server" , "[{uuid}] deposit sim failed: {err:?}, logs: {:?}" , res . logs ) ;
469423 return (
470424 StatusCode :: BAD_REQUEST ,
471425 Json ( ProcessOrderResponse {
@@ -474,14 +428,32 @@ pub async fn deposit_trade(
474428 } ) ,
475429 ) ;
476430 }
431+ if let Some ( acc) = res. accounts {
432+ user_after_deposit =
433+ User :: try_from_slice ( & acc[ 0 ] . as_ref ( ) . unwrap ( ) . data . decode ( ) . unwrap ( ) ) . ok ( ) ;
434+ }
477435 }
478436 Err ( err) => {
479- log:: info!( target: "server" , "deposit sim network err: {err:?}" ) ;
437+ log:: info!( target: "server" , "[{uuid}] deposit sim network err: {uuid}: {err:?}" ) ;
438+ }
439+ }
440+
441+ if let Some ( user) = user_after_deposit {
442+ if !server_params. simulate_taker_order_local ( & signed_order_info. order_params , & user) {
443+ log:: info!( target: "server" , "[{uuid}] local order sim failed" ) ;
444+ return (
445+ StatusCode :: BAD_REQUEST ,
446+ Json ( ProcessOrderResponse {
447+ message : "" ,
448+ error : Some ( "invalid order" . into ( ) ) ,
449+ } ) ,
450+ ) ;
480451 }
481452 }
482453
483454 let context = RequestContext :: from_incoming_message ( & req. swift_order ) ;
484- let ( status, resp) = match process_order ( server_params, req. swift_order , & context) . await {
455+ // TODO: deposit tx should enable sim to pass, if it didn't before otherwise order is invalid
456+ let ( status, resp) = match process_order ( server_params, req. swift_order , true , & context) . await {
485457 Ok ( order_metadata) => {
486458 let metrics_labels = & [
487459 context. market_type ,
@@ -1422,6 +1394,34 @@ fn dump_account_state(
14221394 log:: debug!( target: "accountState" , "{}" , base64:: engine:: general_purpose:: STANDARD . encode( compressed) ) ;
14231395}
14241396
1397+ /// Simulate the tx on remote RPC node
1398+ pub async fn simulate_tx (
1399+ drift : & DriftClient ,
1400+ tx : VersionedMessage ,
1401+ accounts : & [ Pubkey ] ,
1402+ ) -> SdkResult < RpcSimulateTransactionResult > {
1403+ let response = drift
1404+ . rpc ( )
1405+ . simulate_transaction_with_config (
1406+ & VersionedTransaction {
1407+ message : tx,
1408+ // must provide a signature for the RPC call to work
1409+ signatures : vec ! [ Signature :: new_unique( ) ] ,
1410+ } ,
1411+ RpcSimulateTransactionConfig {
1412+ sig_verify : false ,
1413+ replace_recent_blockhash : true ,
1414+ accounts : Some ( RpcSimulateTransactionAccountsConfig {
1415+ encoding : Some ( UiAccountEncoding :: Base64Zstd ) ,
1416+ addresses : accounts. iter ( ) . map ( |x| x. to_string ( ) ) . collect ( ) ,
1417+ } ) ,
1418+ ..Default :: default ( )
1419+ } ,
1420+ )
1421+ . await ;
1422+ response. map ( |r| r. value ) . map_err ( Into :: into)
1423+ }
1424+
14251425#[ cfg( test) ]
14261426mod tests {
14271427 use std:: collections:: HashMap ;
0 commit comments