Skip to content

Commit 408c054

Browse files
authored
depositToTrade: allow all deposit txs (#87)
* depositToTrade: allow all deposit txs * improve logs, fix sim
1 parent 559b075 commit 408c054

File tree

3 files changed

+126
-124
lines changed

3 files changed

+126
-124
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ uuid = { version = "1.10.0", features = ["v4", "fast-rng", "macro-diagnostics",
4949
tower-http = { version = "0.6.2", features= ["cors"] }
5050
strum_macros = "0.27.0"
5151
zstd = "0.13.3"
52+
solana-account-decoder-client-types = "2.3.0"
5253

5354
[features]
5455
default = ["rdkafka/cmake-build"]

src/swift_server.rs

Lines changed: 124 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -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;
3131
use axum::{
3232
extract::State,
3333
http::{self, Method, StatusCode},
@@ -38,15 +38,14 @@ use base64::Engine;
3838
use dotenv::dotenv;
3939
use 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
};
6059
use 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+
};
6266
use 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(
160165
pub 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-
387381
pub 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)]
14261426
mod tests {
14271427
use std::collections::HashMap;

0 commit comments

Comments
 (0)