Skip to content

Commit

Permalink
Add cancel bid api (#374)
Browse files Browse the repository at this point in the history
  • Loading branch information
danimhr authored Feb 3, 2025
1 parent 50b8f5f commit 310c614
Show file tree
Hide file tree
Showing 14 changed files with 338 additions and 65 deletions.
35 changes: 33 additions & 2 deletions auction-server/api-types/src/bid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,15 +306,13 @@ pub struct BidCreateSwapSvm {
pub _type: BidCreateSwapSvmTag, // this is mainly to distinguish next types of bids in the future
}


#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
#[serde(untagged)]
pub enum BidCreateSvm {
Swap(BidCreateSwapSvm),
OnChain(BidCreateOnChainSvm),
}


#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
#[serde(untagged)] // Remove tags to avoid key-value wrapping
pub enum BidCreate {
Expand Down Expand Up @@ -375,6 +373,8 @@ pub enum Route {
GetBidStatus,
#[strum(serialize = "submit")]
PostSubmitQuote,
#[strum(serialize = ":bid_id/cancel")]
PostCancelBid,
}

#[derive(Clone)]
Expand Down Expand Up @@ -424,6 +424,11 @@ impl Routable for Route {
access_level: AccessLevel::Public,
full_path: full_path_with_chain,
},
Route::PostCancelBid => crate::RouteProperties {
method: http::Method::POST,
access_level: AccessLevel::LoggedIn,
full_path: full_path_with_chain,
},
}
}
}
Expand Down Expand Up @@ -464,3 +469,29 @@ impl Routable for DeprecatedRoute {
}
}
}

#[derive(Serialize, Deserialize, IntoParams, Clone)]
pub struct BidCancelParams {
/// The chain id of the bid to cancel.
#[param(example="solana", value_type = String)]
pub chain_id: ChainId,
/// The id of the bid to cancel.
#[param(example="obo3ee3e-58cc-4372-a567-0e02b2c3d479", value_type = String)]
pub bid_id: BidId,
}

#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
pub struct BidCancelSvm {
/// The chain id of the bid to cancel.
#[schema(example = "solana", value_type = String)]
pub chain_id: ChainId,
/// The id of the bid to cancel.
#[schema(example="obo3ee3e-58cc-4372-a567-0e02b2c3d479", value_type = String)]
pub bid_id: BidId,
}

#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
#[serde(untagged)] // Remove tags to avoid key-value wrapping
pub enum BidCancel {
Svm(BidCancelSvm),
}
4 changes: 4 additions & 0 deletions auction-server/api-types/src/ws.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use {
crate::{
bid::{
BidCancel,
BidCreate,
BidResult,
BidStatusWithId,
Expand Down Expand Up @@ -47,6 +48,9 @@ pub enum ClientMessage {
opportunity_id: OpportunityId,
opportunity_bid: OpportunityBidEvm,
},

#[serde(rename = "cancel_bid")]
CancelBid { data: BidCancel },
}

#[derive(Deserialize, Clone, ToSchema, Serialize)]
Expand Down
3 changes: 3 additions & 0 deletions auction-server/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ pub async fn start_api(run_options: RunOptions, store: Arc<StoreNew>) -> Result<
bid::get_bids_by_time,
bid::get_bids_by_time_deprecated,
bid::get_bid_status_deprecated,
bid::post_cancel_bid,
opportunity::post_opportunity,
opportunity::opportunity_bid,
Expand Down Expand Up @@ -334,6 +335,8 @@ pub async fn start_api(run_options: RunOptions, store: Arc<StoreNew>) -> Result<
api_types::bid::BidSvm,
api_types::bid::Bids,
api_types::SvmChainUpdate,
api_types::bid::BidCancel,
api_types::bid::BidCancelSvm,
api_types::opportunity::SpecifiedTokenAmount,
api_types::opportunity::OpportunityBidEvm,
Expand Down
50 changes: 34 additions & 16 deletions auction-server/src/api/ws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use {
},
crate::{
auction::{
api::process_bid,
api::{
cancel_bid,
process_bid,
},
entities::BidId,
},
config::ChainId,
Expand Down Expand Up @@ -34,6 +37,7 @@ use {
},
express_relay_api_types::{
bid::{
BidCancel,
BidCreate,
BidResult,
BidStatusWithId,
Expand Down Expand Up @@ -292,10 +296,9 @@ impl Subscriber {

async fn handle_subscribe(
&mut self,
id: String,
message_id: String,
chain_ids: Vec<String>,
) -> Result<ServerResultResponse, ServerResultResponse> {
tracing::Span::current().record("name", "handle_subscribe");
let available_chain_ids: Vec<&ChainId> = self
.store
.store
Expand All @@ -311,47 +314,59 @@ impl Subscriber {
// asked correct chain ids and return an error to be more explicit and clear.
if !not_found_chain_ids.is_empty() {
Err(ServerResultResponse {
id: Some(id),
id: Some(message_id),
result: ServerResultMessage::Err(format!(
"Chain id(s) with id(s) {:?} not found",
not_found_chain_ids
)),
})
} else {
self.chain_ids.extend(chain_ids);
Ok(ok_response(id))
Ok(ok_response(message_id))
}
}

async fn handle_unsubscribe(
&mut self,
id: String,
message_id: String,
chain_ids: Vec<String>,
) -> Result<ServerResultResponse, ServerResultResponse> {
tracing::Span::current().record("name", "unsubscribe");
self.chain_ids
.retain(|chain_id| !chain_ids.contains(chain_id));
Ok(ok_response(id))
Ok(ok_response(message_id))
}

async fn handle_post_bid(
&mut self,
id: String,
message_id: String,
bid: BidCreate,
) -> Result<ServerResultResponse, ServerResultResponse> {
tracing::Span::current().record("name", "post_bid");
match process_bid(self.auth.clone(), self.store.clone(), bid).await {
Ok(bid_result) => {
self.bid_ids.insert(bid_result.id);
Ok(ServerResultResponse {
id: Some(id.clone()),
id: Some(message_id.clone()),
result: ServerResultMessage::Success(Some(APIResponse::BidResult(
bid_result.0,
))),
})
}
Err(e) => Err(ServerResultResponse {
id: Some(id),
id: Some(message_id),
result: ServerResultMessage::Err(e.to_status_and_message().1),
}),
}
}

async fn handle_cancel_bid(
&mut self,
message_id: String,
bid_cancel: BidCancel,
) -> Result<ServerResultResponse, ServerResultResponse> {
match cancel_bid(self.auth.clone(), self.store.clone(), bid_cancel).await {
Ok(_) => Ok(ok_response(message_id)),
Err(e) => Err(ServerResultResponse {
id: Some(message_id),
result: ServerResultMessage::Err(e.to_status_and_message().1),
}),
}
Expand All @@ -360,11 +375,10 @@ impl Subscriber {
#[instrument(skip_all)]
async fn handle_post_opportunity_bid(
&mut self,
id: String,
message_id: String,
opportunity_bid: OpportunityBidEvm,
opportunity_id: OpportunityId,
) -> Result<ServerResultResponse, ServerResultResponse> {
tracing::Span::current().record("name", "post_opportunity_bid");
match self
.store
.opportunity_service_evm
Expand All @@ -379,15 +393,15 @@ impl Subscriber {
Ok(bid_result) => {
self.bid_ids.insert(bid_result);
Ok(ServerResultResponse {
id: Some(id.clone()),
id: Some(message_id.clone()),
result: ServerResultMessage::Success(Some(APIResponse::BidResult(BidResult {
status: "OK".to_string(),
id: bid_result,
}))),
})
}
Err(e) => Err(ServerResultResponse {
id: Some(id),
id: Some(message_id),
result: ServerResultMessage::Err(e.to_status_and_message().1),
}),
}
Expand Down Expand Up @@ -457,6 +471,10 @@ impl Subscriber {
.in_current_span()
.await
}
ClientMessage::CancelBid { data } => {
tracing::Span::current().record("name", "cancel_bid");
self.handle_cancel_bid(id, data).await
}
},
};

Expand Down
59 changes: 59 additions & 0 deletions auction-server/src/auction/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use {
BidChainData,
},
service::{
cancel_bid::CancelBidInput,
get_bid::GetBidInput,
get_bids::GetBidsInput,
handle_bid::HandleBidInput,
Expand Down Expand Up @@ -42,6 +43,9 @@ use {
express_relay_api_types::{
bid::{
Bid,
BidCancel,
BidCancelParams,
BidCancelSvm,
BidCoreFields,
BidCreate,
BidCreateEvm,
Expand Down Expand Up @@ -103,6 +107,60 @@ pub async fn process_bid(
}
}

/// Cancel a specific bid.
///
/// Bids can only be cancelled if they are in the awaiting signature state.
/// Only the user who created the bid can cancel it.
#[utoipa::path(post, path = "/v1/{chain_id}/bids/{bid_id}/cancel", responses(
(status = 200, description = "Bid was cancelled successfully"),
(status = 400, response = ErrorBodyResponse),
(status = 404, description = "Chain id was not found", body = ErrorBodyResponse),
),)]
pub async fn post_cancel_bid(
auth: Auth,
State(store): State<Arc<StoreNew>>,
Path(params): Path<BidCancelParams>,
) -> Result<Json<()>, RestError> {
cancel_bid(
auth,
store,
BidCancel::Svm(BidCancelSvm {
chain_id: params.chain_id,
bid_id: params.bid_id,
}),
)
.await
}

// We cannot be sure that the user is authorized here because this can be called by the ws as well.
pub async fn cancel_bid(
auth: Auth,
store: Arc<StoreNew>,
bid_cancel: BidCancel,
) -> Result<Json<()>, RestError> {
match auth {
Auth::Authorized(_, profile) => {
let BidCancel::Svm(bid_cancel) = bid_cancel;
let service = store.get_auction_service(&bid_cancel.chain_id)?;
match service {
ServiceEnum::Evm(_) => Err(RestError::BadParameters(
"EVM chain_id is not supported for cancel_bid".to_string(),
)),
ServiceEnum::Svm(service) => {
service
.cancel_bid(CancelBidInput {
bid_id: bid_cancel.bid_id,
profile,
})
.await?;
Ok(Json(()))
}
}
}
_ => Err(RestError::Unauthorized),
}
}

/// Query the status of a specific bid.
#[utoipa::path(get, path = "/v1/{chain_id}/bids/{bid_id}",
responses(
Expand Down Expand Up @@ -280,6 +338,7 @@ pub fn get_routes(store: Arc<StoreNew>) -> Router<Arc<StoreNew>> {
get_bid_status_deprecated,
)
.route(Route::PostSubmitQuote, post_submit_quote)
.route(Route::PostCancelBid, post_cancel_bid)
.router
}

Expand Down
3 changes: 3 additions & 0 deletions auction-server/src/auction/entities/bid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,16 @@ use {
Formatter,
},
hash::Hash,
sync::Arc,
},
strum::FromRepr,
time::OffsetDateTime,
tokio::sync::Mutex,
uuid::Uuid,
};

pub type BidId = Uuid;
pub type BidLock = Arc<Mutex<()>>;

pub trait BidStatus:
Clone
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use {
super::Repository,
crate::auction::{
entities,
service::ChainTrait,
},
};

impl<T: ChainTrait> Repository<T> {
pub async fn get_in_memory_auction_bid_by_bid_id(
&self,
bid_id: entities::BidId,
) -> Option<(entities::Bid<T>, entities::Auction<T>)> {
self.get_in_memory_auctions()
.await
.into_iter()
.find_map(|auction| {
auction
.bids
.iter()
.find(|bid| bid.id == bid_id)
.map(|bid| (bid.clone(), auction.clone()))
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use {
super::Repository,
crate::auction::{
entities,
service::ChainTrait,
},
};

impl<T: ChainTrait> Repository<T> {
pub async fn get_or_create_in_memory_bid_lock(
&self,
key: entities::BidId,
) -> entities::BidLock {
self.in_memory_store
.bid_lock
.lock()
.await
.entry(key)
.or_default()
.clone()
}
}
Loading

0 comments on commit 310c614

Please sign in to comment.