663 lines
25 KiB
Rust
663 lines
25 KiB
Rust
// file: kb_lib/src/dex_pool_materialization.rs
|
|
|
|
//! Shared DEX pool materialization helpers.
|
|
//!
|
|
//! This module persists normalized pool, pair, pool token and pool listing
|
|
//! records from decoded DEX events.
|
|
//!
|
|
//! It intentionally does not decode instructions and does not record analysis
|
|
//! signals. Detection services remain responsible for deciding which signals
|
|
//! to emit.
|
|
|
|
/// Token ordering strategy used when materializing a decoded DEX pool.
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
pub(crate) enum DexPoolTokenOrder {
|
|
/// `token_a_mint` is already the base token and `token_b_mint` is already the quote token.
|
|
AlreadyBaseQuote,
|
|
/// Base/quote order should be chosen from token A/B using known quote-token hints.
|
|
ChooseBaseQuoteFromTokenAB,
|
|
}
|
|
|
|
/// Input required to materialize a normalized DEX pool.
|
|
#[derive(Debug, Clone)]
|
|
pub(crate) struct DexPoolMaterializationInput {
|
|
/// Parent decoded event id.
|
|
pub(crate) decoded_event_id: i64,
|
|
/// Internal DEX id.
|
|
pub(crate) dex_id: i64,
|
|
/// Pool account address.
|
|
pub(crate) pool_address: std::string::String,
|
|
/// Token A mint, or already-base mint depending on `token_order`.
|
|
pub(crate) token_a_mint: std::string::String,
|
|
/// Token B mint, or already-quote mint depending on `token_order`.
|
|
pub(crate) token_b_mint: std::string::String,
|
|
/// Optional LP mint.
|
|
pub(crate) lp_mint: std::option::Option<std::string::String>,
|
|
/// Optional token A vault address, or base vault when `token_order` is `AlreadyBaseQuote`.
|
|
pub(crate) token_a_vault_address: std::option::Option<std::string::String>,
|
|
/// Optional token B vault address, or quote vault when `token_order` is `AlreadyBaseQuote`.
|
|
pub(crate) token_b_vault_address: std::option::Option<std::string::String>,
|
|
/// Pool kind to persist.
|
|
pub(crate) pool_kind: crate::PoolKind,
|
|
/// Pool status to persist.
|
|
pub(crate) pool_status: crate::PoolStatus,
|
|
/// Token ordering strategy.
|
|
pub(crate) token_order: crate::dex_pool_materialization::DexPoolTokenOrder,
|
|
/// Listing source kind.
|
|
pub(crate) listing_source_kind: crate::ObservationSourceKind,
|
|
/// Optional source endpoint logical name.
|
|
pub(crate) source_endpoint_name: std::option::Option<std::string::String>,
|
|
}
|
|
|
|
impl DexPoolMaterializationInput {
|
|
/// Creates a materialization input from a decoded event requiring pool and two token mints.
|
|
pub(crate) fn from_decoded_event(
|
|
decoded_event: &crate::DexDecodedEventDto,
|
|
dex_id: i64,
|
|
pool_kind: crate::PoolKind,
|
|
pool_status: crate::PoolStatus,
|
|
token_order: crate::dex_pool_materialization::DexPoolTokenOrder,
|
|
token_a_vault_address: std::option::Option<std::string::String>,
|
|
token_b_vault_address: std::option::Option<std::string::String>,
|
|
source_endpoint_name: std::option::Option<std::string::String>,
|
|
) -> Result<Self, crate::Error> {
|
|
let decoded_event_id_result =
|
|
crate::dex_pool_materialization::required_decoded_event_id(decoded_event);
|
|
let decoded_event_id = match decoded_event_id_result {
|
|
Ok(decoded_event_id) => decoded_event_id,
|
|
Err(error) => return Err(error),
|
|
};
|
|
let pool_address_result =
|
|
crate::dex_pool_materialization::required_pool_account(decoded_event);
|
|
let pool_address = match pool_address_result {
|
|
Ok(pool_address) => pool_address,
|
|
Err(error) => return Err(error),
|
|
};
|
|
let token_a_mint_result =
|
|
crate::dex_pool_materialization::required_token_a_mint(decoded_event);
|
|
let token_a_mint = match token_a_mint_result {
|
|
Ok(token_a_mint) => token_a_mint,
|
|
Err(error) => return Err(error),
|
|
};
|
|
let token_b_mint_result =
|
|
crate::dex_pool_materialization::required_token_b_mint(decoded_event);
|
|
let token_b_mint = match token_b_mint_result {
|
|
Ok(token_b_mint) => token_b_mint,
|
|
Err(error) => return Err(error),
|
|
};
|
|
return Ok(Self {
|
|
decoded_event_id,
|
|
dex_id,
|
|
pool_address,
|
|
token_a_mint,
|
|
token_b_mint,
|
|
lp_mint: decoded_event.lp_mint.clone(),
|
|
token_a_vault_address,
|
|
token_b_vault_address,
|
|
pool_kind,
|
|
pool_status,
|
|
token_order,
|
|
listing_source_kind: crate::ObservationSourceKind::Dex,
|
|
source_endpoint_name,
|
|
});
|
|
}
|
|
|
|
/// Creates a materialization input from a decoded event and explicit token mints.
|
|
///
|
|
/// This is used by launch or bonding-curve events where the decoded event
|
|
/// may expose only the launched token mint and the quote mint is inferred
|
|
/// by the detector.
|
|
pub(crate) fn from_decoded_event_with_mints(
|
|
decoded_event: &crate::DexDecodedEventDto,
|
|
dex_id: i64,
|
|
token_a_mint: std::string::String,
|
|
token_b_mint: std::string::String,
|
|
lp_mint: std::option::Option<std::string::String>,
|
|
pool_kind: crate::PoolKind,
|
|
pool_status: crate::PoolStatus,
|
|
token_order: crate::dex_pool_materialization::DexPoolTokenOrder,
|
|
token_a_vault_address: std::option::Option<std::string::String>,
|
|
token_b_vault_address: std::option::Option<std::string::String>,
|
|
source_endpoint_name: std::option::Option<std::string::String>,
|
|
) -> Result<Self, crate::Error> {
|
|
let decoded_event_id_result =
|
|
crate::dex_pool_materialization::required_decoded_event_id(decoded_event);
|
|
let decoded_event_id = match decoded_event_id_result {
|
|
Ok(decoded_event_id) => decoded_event_id,
|
|
Err(error) => return Err(error),
|
|
};
|
|
let pool_address_result =
|
|
crate::dex_pool_materialization::required_pool_account(decoded_event);
|
|
let pool_address = match pool_address_result {
|
|
Ok(pool_address) => pool_address,
|
|
Err(error) => return Err(error),
|
|
};
|
|
return Ok(Self {
|
|
decoded_event_id,
|
|
dex_id,
|
|
pool_address,
|
|
token_a_mint,
|
|
token_b_mint,
|
|
lp_mint,
|
|
token_a_vault_address,
|
|
token_b_vault_address,
|
|
pool_kind,
|
|
pool_status,
|
|
token_order,
|
|
listing_source_kind: crate::ObservationSourceKind::Dex,
|
|
source_endpoint_name,
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Returns the decoded event id or fails with a stable diagnostic.
|
|
pub(crate) fn required_decoded_event_id(
|
|
decoded_event: &crate::DexDecodedEventDto,
|
|
) -> Result<i64, crate::Error> {
|
|
match decoded_event.id {
|
|
Some(decoded_event_id) => return Ok(decoded_event_id),
|
|
None => {
|
|
return Err(crate::Error::InvalidState(
|
|
"decoded dex event has no internal id".to_string(),
|
|
));
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Returns the pool account or fails with a stable diagnostic.
|
|
pub(crate) fn required_pool_account(
|
|
decoded_event: &crate::DexDecodedEventDto,
|
|
) -> Result<std::string::String, crate::Error> {
|
|
let decoded_event_id_result =
|
|
crate::dex_pool_materialization::required_decoded_event_id(decoded_event);
|
|
let decoded_event_id = match decoded_event_id_result {
|
|
Ok(decoded_event_id) => decoded_event_id,
|
|
Err(error) => return Err(error),
|
|
};
|
|
match decoded_event.pool_account.clone() {
|
|
Some(pool_account) => return Ok(pool_account),
|
|
None => {
|
|
return Err(crate::Error::InvalidState(format!(
|
|
"decoded event '{}' has no pool_account",
|
|
decoded_event_id
|
|
)));
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Returns token A mint or fails with a stable diagnostic.
|
|
pub(crate) fn required_token_a_mint(
|
|
decoded_event: &crate::DexDecodedEventDto,
|
|
) -> Result<std::string::String, crate::Error> {
|
|
let decoded_event_id_result =
|
|
crate::dex_pool_materialization::required_decoded_event_id(decoded_event);
|
|
let decoded_event_id = match decoded_event_id_result {
|
|
Ok(decoded_event_id) => decoded_event_id,
|
|
Err(error) => return Err(error),
|
|
};
|
|
match decoded_event.token_a_mint.clone() {
|
|
Some(token_a_mint) => return Ok(token_a_mint),
|
|
None => {
|
|
return Err(crate::Error::InvalidState(format!(
|
|
"decoded event '{}' has no token_a_mint",
|
|
decoded_event_id
|
|
)));
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Returns token B mint or fails with a stable diagnostic.
|
|
pub(crate) fn required_token_b_mint(
|
|
decoded_event: &crate::DexDecodedEventDto,
|
|
) -> Result<std::string::String, crate::Error> {
|
|
let decoded_event_id_result =
|
|
crate::dex_pool_materialization::required_decoded_event_id(decoded_event);
|
|
let decoded_event_id = match decoded_event_id_result {
|
|
Ok(decoded_event_id) => decoded_event_id,
|
|
Err(error) => return Err(error),
|
|
};
|
|
match decoded_event.token_b_mint.clone() {
|
|
Some(token_b_mint) => return Ok(token_b_mint),
|
|
None => {
|
|
return Err(crate::Error::InvalidState(format!(
|
|
"decoded event '{}' has no token_b_mint",
|
|
decoded_event_id
|
|
)));
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Persists pool, pair, pool tokens and listing, returning the materialized ids.
|
|
pub(crate) async fn materialize_dex_pool(
|
|
database: &crate::Database,
|
|
input: &crate::dex_pool_materialization::DexPoolMaterializationInput,
|
|
) -> Result<crate::DexPoolDetectionResult, crate::Error> {
|
|
let ordered_tokens = crate::dex_pool_materialization::ordered_pool_tokens_from_input(input);
|
|
let base_token_id_result =
|
|
crate::dex_pool_materialization::ensure_token(database, ordered_tokens.base_mint.as_str())
|
|
.await;
|
|
let base_token_id = match base_token_id_result {
|
|
Ok(base_token_id) => base_token_id,
|
|
Err(error) => return Err(error),
|
|
};
|
|
let quote_token_id_result =
|
|
crate::dex_pool_materialization::ensure_token(database, ordered_tokens.quote_mint.as_str())
|
|
.await;
|
|
let quote_token_id = match quote_token_id_result {
|
|
Ok(quote_token_id) => quote_token_id,
|
|
Err(error) => return Err(error),
|
|
};
|
|
let lp_token_id = match input.lp_mint.clone() {
|
|
Some(lp_mint) => {
|
|
let lp_token_id_result =
|
|
crate::dex_pool_materialization::ensure_token(database, lp_mint.as_str()).await;
|
|
match lp_token_id_result {
|
|
Ok(lp_token_id) => Some(lp_token_id),
|
|
Err(error) => return Err(error),
|
|
}
|
|
},
|
|
None => None,
|
|
};
|
|
let pool_result = crate::dex_pool_materialization::ensure_pool(database, input).await;
|
|
let pool_materialization = match pool_result {
|
|
Ok(pool_materialization) => pool_materialization,
|
|
Err(error) => return Err(error),
|
|
};
|
|
let pair_result = crate::dex_pool_materialization::ensure_pair(
|
|
database,
|
|
input.dex_id,
|
|
pool_materialization.pool_id,
|
|
base_token_id,
|
|
quote_token_id,
|
|
ordered_tokens.base_mint.as_str(),
|
|
ordered_tokens.quote_mint.as_str(),
|
|
)
|
|
.await;
|
|
let pair_materialization = match pair_result {
|
|
Ok(pair_materialization) => pair_materialization,
|
|
Err(error) => return Err(error),
|
|
};
|
|
let upsert_base_pool_token_result = crate::query_pool_tokens_upsert(
|
|
database,
|
|
&crate::PoolTokenDto::new(
|
|
pool_materialization.pool_id,
|
|
base_token_id,
|
|
crate::PoolTokenRole::Base,
|
|
ordered_tokens.base_vault_address,
|
|
Some(0),
|
|
),
|
|
)
|
|
.await;
|
|
if let Err(error) = upsert_base_pool_token_result {
|
|
return Err(error);
|
|
}
|
|
let upsert_quote_pool_token_result = crate::query_pool_tokens_upsert(
|
|
database,
|
|
&crate::PoolTokenDto::new(
|
|
pool_materialization.pool_id,
|
|
quote_token_id,
|
|
crate::PoolTokenRole::Quote,
|
|
ordered_tokens.quote_vault_address,
|
|
Some(1),
|
|
),
|
|
)
|
|
.await;
|
|
if let Err(error) = upsert_quote_pool_token_result {
|
|
return Err(error);
|
|
}
|
|
if let Some(lp_token_id) = lp_token_id {
|
|
let upsert_lp_pool_token_result = crate::query_pool_tokens_upsert(
|
|
database,
|
|
&crate::PoolTokenDto::new(
|
|
pool_materialization.pool_id,
|
|
lp_token_id,
|
|
crate::PoolTokenRole::LpMint,
|
|
None,
|
|
None,
|
|
),
|
|
)
|
|
.await;
|
|
if let Err(error) = upsert_lp_pool_token_result {
|
|
return Err(error);
|
|
}
|
|
}
|
|
let listing_result = crate::dex_pool_materialization::ensure_pool_listing(
|
|
database,
|
|
input,
|
|
pool_materialization.pool_id,
|
|
pair_materialization.pair_id,
|
|
)
|
|
.await;
|
|
let listing_materialization = match listing_result {
|
|
Ok(listing_materialization) => listing_materialization,
|
|
Err(error) => return Err(error),
|
|
};
|
|
return Ok(crate::DexPoolDetectionResult {
|
|
decoded_event_id: input.decoded_event_id,
|
|
dex_id: input.dex_id,
|
|
pool_id: pool_materialization.pool_id,
|
|
pair_id: pair_materialization.pair_id,
|
|
pool_listing_id: listing_materialization.pool_listing_id,
|
|
created_pool: pool_materialization.created_pool,
|
|
created_pair: pair_materialization.created_pair,
|
|
created_listing: listing_materialization.created_listing,
|
|
});
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct OrderedPoolTokens {
|
|
base_mint: std::string::String,
|
|
quote_mint: std::string::String,
|
|
base_vault_address: std::option::Option<std::string::String>,
|
|
quote_vault_address: std::option::Option<std::string::String>,
|
|
}
|
|
|
|
fn ordered_pool_tokens_from_input(
|
|
input: &crate::dex_pool_materialization::DexPoolMaterializationInput,
|
|
) -> OrderedPoolTokens {
|
|
match input.token_order {
|
|
crate::dex_pool_materialization::DexPoolTokenOrder::AlreadyBaseQuote => {
|
|
return OrderedPoolTokens {
|
|
base_mint: input.token_a_mint.clone(),
|
|
quote_mint: input.token_b_mint.clone(),
|
|
base_vault_address: input.token_a_vault_address.clone(),
|
|
quote_vault_address: input.token_b_vault_address.clone(),
|
|
};
|
|
},
|
|
crate::dex_pool_materialization::DexPoolTokenOrder::ChooseBaseQuoteFromTokenAB => {
|
|
let base_is_token_a = crate::dex_pool_materialization::choose_base_quote_order(
|
|
input.token_a_mint.as_str(),
|
|
input.token_b_mint.as_str(),
|
|
);
|
|
let base_mint = if base_is_token_a {
|
|
input.token_a_mint.clone()
|
|
} else {
|
|
input.token_b_mint.clone()
|
|
};
|
|
let quote_mint = if base_is_token_a {
|
|
input.token_b_mint.clone()
|
|
} else {
|
|
input.token_a_mint.clone()
|
|
};
|
|
let base_vault_address = if base_is_token_a {
|
|
input.token_a_vault_address.clone()
|
|
} else {
|
|
input.token_b_vault_address.clone()
|
|
};
|
|
let quote_vault_address = if base_is_token_a {
|
|
input.token_b_vault_address.clone()
|
|
} else {
|
|
input.token_a_vault_address.clone()
|
|
};
|
|
return OrderedPoolTokens {
|
|
base_mint,
|
|
quote_mint,
|
|
base_vault_address,
|
|
quote_vault_address,
|
|
};
|
|
},
|
|
}
|
|
}
|
|
|
|
async fn ensure_pool(
|
|
database: &crate::Database,
|
|
input: &crate::dex_pool_materialization::DexPoolMaterializationInput,
|
|
) -> Result<PoolMaterialization, crate::Error> {
|
|
let existing_pool_result =
|
|
crate::query_pools_get_by_address(database, input.pool_address.as_str()).await;
|
|
let existing_pool_option = match existing_pool_result {
|
|
Ok(existing_pool_option) => existing_pool_option,
|
|
Err(error) => return Err(error),
|
|
};
|
|
let created_pool = existing_pool_option.is_none();
|
|
let pool_id = match existing_pool_option {
|
|
Some(pool) => match pool.id {
|
|
Some(pool_id) => pool_id,
|
|
None => {
|
|
return Err(crate::Error::InvalidState(format!(
|
|
"pool '{}' has no internal id",
|
|
pool.address
|
|
)));
|
|
},
|
|
},
|
|
None => {
|
|
let pool_dto = crate::PoolDto::new(
|
|
input.dex_id,
|
|
input.pool_address.clone(),
|
|
input.pool_kind,
|
|
input.pool_status,
|
|
);
|
|
let upsert_result = crate::query_pools_upsert(database, &pool_dto).await;
|
|
match upsert_result {
|
|
Ok(pool_id) => pool_id,
|
|
Err(error) => return Err(error),
|
|
}
|
|
},
|
|
};
|
|
return Ok(PoolMaterialization { pool_id, created_pool });
|
|
}
|
|
|
|
async fn ensure_pair(
|
|
database: &crate::Database,
|
|
dex_id: i64,
|
|
pool_id: i64,
|
|
base_token_id: i64,
|
|
quote_token_id: i64,
|
|
base_mint: &str,
|
|
quote_mint: &str,
|
|
) -> Result<PairMaterialization, crate::Error> {
|
|
let existing_pair_result = crate::query_pairs_get_by_pool_id(database, pool_id).await;
|
|
let existing_pair_option = match existing_pair_result {
|
|
Ok(existing_pair_option) => existing_pair_option,
|
|
Err(error) => return Err(error),
|
|
};
|
|
let created_pair = existing_pair_option.is_none();
|
|
let pair_symbol = crate::dex_pool_materialization::build_pair_symbol(base_mint, quote_mint);
|
|
let pair_id = match existing_pair_option {
|
|
Some(pair) => match pair.id {
|
|
Some(pair_id) => pair_id,
|
|
None => {
|
|
return Err(crate::Error::InvalidState(format!(
|
|
"pair for pool '{}' has no internal id",
|
|
pool_id
|
|
)));
|
|
},
|
|
},
|
|
None => {
|
|
let pair_dto =
|
|
crate::PairDto::new(dex_id, pool_id, base_token_id, quote_token_id, pair_symbol);
|
|
let upsert_result = crate::query_pairs_upsert(database, &pair_dto).await;
|
|
match upsert_result {
|
|
Ok(pair_id) => pair_id,
|
|
Err(error) => return Err(error),
|
|
}
|
|
},
|
|
};
|
|
return Ok(PairMaterialization { pair_id, created_pair });
|
|
}
|
|
|
|
async fn ensure_pool_listing(
|
|
database: &crate::Database,
|
|
input: &crate::dex_pool_materialization::DexPoolMaterializationInput,
|
|
pool_id: i64,
|
|
pair_id: i64,
|
|
) -> Result<ListingMaterialization, crate::Error> {
|
|
let existing_listing_result =
|
|
crate::query_pool_listings_get_by_pool_id(database, pool_id).await;
|
|
let existing_listing_option = match existing_listing_result {
|
|
Ok(existing_listing_option) => existing_listing_option,
|
|
Err(error) => return Err(error),
|
|
};
|
|
let created_listing = existing_listing_option.is_none();
|
|
let pool_listing_id = match existing_listing_option {
|
|
Some(pool_listing) => pool_listing.id,
|
|
None => {
|
|
let listing_dto = crate::PoolListingDto::new(
|
|
input.dex_id,
|
|
pool_id,
|
|
Some(pair_id),
|
|
input.listing_source_kind,
|
|
input.source_endpoint_name.clone(),
|
|
None,
|
|
None,
|
|
None,
|
|
);
|
|
let upsert_result = crate::query_pool_listings_upsert(database, &listing_dto).await;
|
|
match upsert_result {
|
|
Ok(listing_id) => Some(listing_id),
|
|
Err(error) => return Err(error),
|
|
}
|
|
},
|
|
};
|
|
return Ok(ListingMaterialization { pool_listing_id, created_listing });
|
|
}
|
|
|
|
async fn ensure_token(database: &crate::Database, mint: &str) -> Result<i64, crate::Error> {
|
|
let token_result = crate::query_tokens_get_by_mint(database, mint).await;
|
|
let token_option = match token_result {
|
|
Ok(token_option) => token_option,
|
|
Err(error) => return Err(error),
|
|
};
|
|
match token_option {
|
|
Some(token) => match token.id {
|
|
Some(token_id) => return Ok(token_id),
|
|
None => {
|
|
return Err(crate::Error::InvalidState(format!(
|
|
"token '{}' has no internal id",
|
|
mint
|
|
)));
|
|
},
|
|
},
|
|
None => {
|
|
let token_dto = crate::TokenDto::new(
|
|
mint.to_string(),
|
|
None,
|
|
None,
|
|
None,
|
|
crate::SPL_TOKEN_PROGRAM_ID.to_string(),
|
|
crate::dex_pool_materialization::is_quote_mint(mint),
|
|
);
|
|
return crate::query_tokens_upsert(database, &token_dto).await;
|
|
},
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct PoolMaterialization {
|
|
pool_id: i64,
|
|
created_pool: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct PairMaterialization {
|
|
pair_id: i64,
|
|
created_pair: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct ListingMaterialization {
|
|
pool_listing_id: std::option::Option<i64>,
|
|
created_listing: bool,
|
|
}
|
|
|
|
fn is_quote_mint(mint: &str) -> bool {
|
|
return mint == crate::WSOL_MINT_ID;
|
|
}
|
|
|
|
fn choose_base_quote_order(token_a_mint: &str, token_b_mint: &str) -> bool {
|
|
let token_a_is_quote = crate::dex_pool_materialization::is_quote_mint(token_a_mint);
|
|
let token_b_is_quote = crate::dex_pool_materialization::is_quote_mint(token_b_mint);
|
|
if token_a_is_quote && !token_b_is_quote {
|
|
return false;
|
|
}
|
|
if token_b_is_quote && !token_a_is_quote {
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
fn build_pair_symbol(
|
|
base_mint: &str,
|
|
quote_mint: &str,
|
|
) -> std::option::Option<std::string::String> {
|
|
let base_symbol = crate::dex_pool_materialization::symbol_hint_from_mint(base_mint);
|
|
let quote_symbol = crate::dex_pool_materialization::symbol_hint_from_mint(quote_mint);
|
|
match (base_symbol, quote_symbol) {
|
|
(Some(base_symbol), Some(quote_symbol)) => {
|
|
return Some(format!("{base_symbol}/{quote_symbol}"));
|
|
},
|
|
_ => return None,
|
|
}
|
|
}
|
|
|
|
fn symbol_hint_from_mint(mint: &str) -> std::option::Option<std::string::String> {
|
|
if mint == crate::WSOL_MINT_ID {
|
|
return Some("WSOL".to_string());
|
|
}
|
|
return None;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
#[test]
|
|
fn quote_token_is_moved_to_quote_side_when_order_is_chosen() {
|
|
let input = crate::dex_pool_materialization::DexPoolMaterializationInput {
|
|
decoded_event_id: 1,
|
|
dex_id: 2,
|
|
pool_address: "Pool111".to_string(),
|
|
token_a_mint: crate::WSOL_MINT_ID.to_string(),
|
|
token_b_mint: "TokenB111".to_string(),
|
|
lp_mint: None,
|
|
token_a_vault_address: Some("VaultA111".to_string()),
|
|
token_b_vault_address: Some("VaultB111".to_string()),
|
|
pool_kind: crate::PoolKind::Amm,
|
|
pool_status: crate::PoolStatus::Active,
|
|
token_order:
|
|
crate::dex_pool_materialization::DexPoolTokenOrder::ChooseBaseQuoteFromTokenAB,
|
|
listing_source_kind: crate::ObservationSourceKind::Dex,
|
|
source_endpoint_name: Some("test".to_string()),
|
|
};
|
|
let ordered = super::ordered_pool_tokens_from_input(&input);
|
|
assert_eq!(ordered.base_mint, "TokenB111");
|
|
assert_eq!(ordered.quote_mint, crate::WSOL_MINT_ID);
|
|
assert_eq!(ordered.base_vault_address, Some("VaultB111".to_string()));
|
|
assert_eq!(ordered.quote_vault_address, Some("VaultA111".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn already_base_quote_order_is_preserved() {
|
|
let input = crate::dex_pool_materialization::DexPoolMaterializationInput {
|
|
decoded_event_id: 1,
|
|
dex_id: 2,
|
|
pool_address: "Pool111".to_string(),
|
|
token_a_mint: crate::WSOL_MINT_ID.to_string(),
|
|
token_b_mint: "TokenB111".to_string(),
|
|
lp_mint: None,
|
|
token_a_vault_address: Some("BaseVault111".to_string()),
|
|
token_b_vault_address: Some("QuoteVault111".to_string()),
|
|
pool_kind: crate::PoolKind::Amm,
|
|
pool_status: crate::PoolStatus::Active,
|
|
token_order: crate::dex_pool_materialization::DexPoolTokenOrder::AlreadyBaseQuote,
|
|
listing_source_kind: crate::ObservationSourceKind::Dex,
|
|
source_endpoint_name: Some("test".to_string()),
|
|
};
|
|
let ordered = super::ordered_pool_tokens_from_input(&input);
|
|
assert_eq!(ordered.base_mint, crate::WSOL_MINT_ID);
|
|
assert_eq!(ordered.quote_mint, "TokenB111");
|
|
assert_eq!(ordered.base_vault_address, Some("BaseVault111".to_string()));
|
|
assert_eq!(ordered.quote_vault_address, Some("QuoteVault111".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn pair_symbol_is_none_when_only_one_symbol_is_known() {
|
|
let pair_symbol = super::build_pair_symbol(crate::WSOL_MINT_ID, "TokenB111");
|
|
assert_eq!(pair_symbol, None);
|
|
}
|
|
|
|
#[test]
|
|
fn wsol_symbol_hint_is_known() {
|
|
let symbol = super::symbol_hint_from_mint(crate::WSOL_MINT_ID);
|
|
assert_eq!(symbol, Some("WSOL".to_string()));
|
|
}
|
|
}
|