use anchor_lang::prelude::*;
use gmsol_model::{BaseMarketExt, ClockKind, PnlFactorKind};
use crate::{constants, states::Oracle, CoreError, CoreResult, ModelError};
use super::{HasMarketMeta, Market};
pub trait ValidateMarketBalances:
gmsol_model::BaseMarket<{ constants::MARKET_DECIMALS }, Num = u128>
+ gmsol_model::Bank<Pubkey, Num = u64>
+ HasMarketMeta
{
fn validate_market_balance_for_the_given_token(
&self,
token: &Pubkey,
excluded: u64,
) -> gmsol_model::Result<()> {
let is_long_token = self.market_meta().to_token_side(token)?;
let is_pure = self.is_pure();
let balance = u128::from(self.balance_excluding(token, &excluded)?);
let mut min_token_balance = self
.expected_min_token_balance_excluding_collateral_amount_for_one_token_side(
is_long_token,
)?;
if is_pure {
min_token_balance = min_token_balance
.checked_add(
self.expected_min_token_balance_excluding_collateral_amount_for_one_token_side(
!is_long_token,
)?,
)
.ok_or(gmsol_model::Error::Computation(
"validate balance: overflow while adding the min token balance for the other side",
))?;
}
crate::debug_msg!(
"[Validation] validating min token balance: {} >= {}",
balance,
min_token_balance
);
if balance < min_token_balance {
return Err(gmsol_model::Error::InvalidTokenBalance(
"Less than expected min token balance excluding collateral amount",
min_token_balance.to_string(),
balance.to_string(),
));
}
let mut collateral_amount =
self.total_collateral_amount_for_one_token_side(is_long_token)?;
if is_pure {
collateral_amount = collateral_amount
.checked_add(self.total_collateral_amount_for_one_token_side(!is_long_token)?)
.ok_or(gmsol_model::Error::Computation(
"validate balance: overflow while adding the collateral amount for the other side",
))?;
}
crate::debug_msg!(
"[Validation] validating collateral amount: {} >= {}",
balance,
collateral_amount
);
if balance < collateral_amount {
return Err(gmsol_model::Error::InvalidTokenBalance(
"Less than total collateral amount",
collateral_amount.to_string(),
balance.to_string(),
));
}
Ok(())
}
fn validate_market_balances(
&self,
mut long_excluding_amount: u64,
mut short_excluding_amount: u64,
) -> Result<()> {
if self.is_pure() {
let total = long_excluding_amount
.checked_add(short_excluding_amount)
.ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
long_excluding_amount = total;
short_excluding_amount = 0;
}
let meta = self.market_meta();
self.validate_market_balance_for_the_given_token(
&meta.long_token_mint,
long_excluding_amount,
)
.map_err(ModelError::from)?;
if !self.is_pure() {
self.validate_market_balance_for_the_given_token(
&meta.short_token_mint,
short_excluding_amount,
)
.map_err(ModelError::from)?;
}
Ok(())
}
fn validate_market_balances_excluding_the_given_token_amounts(
&self,
first_token: &Pubkey,
second_token: &Pubkey,
first_excluding_amount: u64,
second_excluding_amount: u64,
) -> Result<()> {
let mut long_excluding_amount = 0u64;
let mut short_excluding_amount = 0u64;
for (token, amount) in [
(first_token, first_excluding_amount),
(second_token, second_excluding_amount),
] {
if amount == 0 {
continue;
}
let is_long = self
.market_meta()
.to_token_side(token)
.map_err(CoreError::from)?;
if is_long {
long_excluding_amount = long_excluding_amount
.checked_add(amount)
.ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
} else {
short_excluding_amount = short_excluding_amount
.checked_add(amount)
.ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
}
}
self.validate_market_balances(long_excluding_amount, short_excluding_amount)
}
}
impl<
M: gmsol_model::BaseMarket<{ constants::MARKET_DECIMALS }, Num = u128>
+ gmsol_model::Bank<Pubkey, Num = u64>
+ HasMarketMeta,
> ValidateMarketBalances for M
{
}
pub trait Adl {
fn validate_adl(&self, oracle: &Oracle, is_long: bool, max_staleness: u64) -> CoreResult<()>;
fn latest_adl_time(&self, is_long: bool) -> CoreResult<i64>;
fn update_adl_state(&mut self, oracle: &Oracle, is_long: bool) -> Result<()>;
}
impl Adl for Market {
fn latest_adl_time(&self, is_long: bool) -> CoreResult<i64> {
let clock = if is_long {
ClockKind::AdlForLong
} else {
ClockKind::AdlForShort
};
self.clock(clock).ok_or(CoreError::NotFound)
}
fn validate_adl(&self, oracle: &Oracle, is_long: bool, max_staleness: u64) -> CoreResult<()> {
if !self.is_adl_enabled(is_long) {
return Err(CoreError::AdlNotEnabled);
}
if oracle
.max_oracle_ts()
.saturating_add_unsigned(max_staleness)
< self.latest_adl_time(is_long)?
{
return Err(CoreError::OracleTimestampsAreSmallerThanRequired);
}
Ok(())
}
fn update_adl_state(&mut self, oracle: &Oracle, is_long: bool) -> Result<()> {
if oracle.max_oracle_ts() < self.latest_adl_time(is_long)? {
return err!(CoreError::OracleTimestampsAreSmallerThanRequired);
}
require!(self.is_enabled(), CoreError::DisabledMarket);
let prices = self.prices(oracle)?;
let is_exceeded = self
.pnl_factor_exceeded(&prices, PnlFactorKind::ForAdl, is_long)
.map_err(ModelError::from)?
.is_some();
self.set_adl_enabled(is_long, is_exceeded);
let kind = if is_long {
ClockKind::AdlForLong
} else {
ClockKind::AdlForShort
};
let clock = self
.state
.clocks
.get_mut(kind)
.ok_or_else(|| error!(CoreError::NotFound))?;
*clock = Clock::get()?.unix_timestamp;
Ok(())
}
}