gmsol_store/states/market/
utils.rs1use anchor_lang::prelude::*;
2
3use gmsol_model::{BaseMarketExt, ClockKind, PnlFactorKind};
4
5use crate::{constants, states::Oracle, CoreError, CoreResult, ModelError};
6
7use super::{HasMarketMeta, Market};
8
9pub trait ValidateMarketBalances:
11 gmsol_model::BaseMarket<{ constants::MARKET_DECIMALS }, Num = u128>
12 + gmsol_model::Bank<Pubkey, Num = u64>
13 + HasMarketMeta
14{
15 fn validate_market_balance_for_the_given_token(
27 &self,
28 token: &Pubkey,
29 excluded: u64,
30 ) -> gmsol_model::Result<()> {
31 let is_long_token = self.market_meta().to_token_side(token)?;
32 let is_pure = self.is_pure();
33
34 let balance = u128::from(self.balance_excluding(token, &excluded)?);
35
36 let mut min_token_balance = self
37 .expected_min_token_balance_excluding_collateral_amount_for_one_token_side(
38 is_long_token,
39 )?;
40
41 if is_pure {
44 min_token_balance = min_token_balance
45 .checked_add(
46 self.expected_min_token_balance_excluding_collateral_amount_for_one_token_side(
47 !is_long_token,
48 )?,
49 )
50 .ok_or(gmsol_model::Error::Computation(
51 "validate balance: overflow while adding the min token balance for the other side",
52 ))?;
53 }
54
55 crate::debug_msg!(
56 "[Validation] validating min token balance: {} >= {}",
57 balance,
58 min_token_balance
59 );
60 if balance < min_token_balance {
61 return Err(gmsol_model::Error::InvalidTokenBalance(
62 "Less than expected min token balance excluding collateral amount",
63 min_token_balance.to_string(),
64 balance.to_string(),
65 ));
66 }
67
68 let mut collateral_amount =
69 self.total_collateral_amount_for_one_token_side(is_long_token)?;
70
71 if is_pure {
74 collateral_amount = collateral_amount
75 .checked_add(self.total_collateral_amount_for_one_token_side(!is_long_token)?)
76 .ok_or(gmsol_model::Error::Computation(
77 "validate balance: overflow while adding the collateral amount for the other side",
78 ))?;
79 }
80
81 crate::debug_msg!(
82 "[Validation] validating collateral amount: {} >= {}",
83 balance,
84 collateral_amount
85 );
86 if balance < collateral_amount {
87 return Err(gmsol_model::Error::InvalidTokenBalance(
88 "Less than total collateral amount",
89 collateral_amount.to_string(),
90 balance.to_string(),
91 ));
92 }
93
94 Ok(())
97 }
98
99 fn validate_market_balances(
101 &self,
102 mut long_excluding_amount: u64,
103 mut short_excluding_amount: u64,
104 ) -> Result<()> {
105 if self.is_pure() {
106 let total = long_excluding_amount
107 .checked_add(short_excluding_amount)
108 .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
109 long_excluding_amount = total;
110 short_excluding_amount = 0;
111 }
112 let meta = self.market_meta();
113 self.validate_market_balance_for_the_given_token(
114 &meta.long_token_mint,
115 long_excluding_amount,
116 )
117 .map_err(ModelError::from)?;
118 if !self.is_pure() {
121 self.validate_market_balance_for_the_given_token(
122 &meta.short_token_mint,
123 short_excluding_amount,
124 )
125 .map_err(ModelError::from)?;
126 }
127 Ok(())
128 }
129
130 fn validate_market_balances_excluding_the_given_token_amounts(
132 &self,
133 first_token: &Pubkey,
134 second_token: &Pubkey,
135 first_excluding_amount: u64,
136 second_excluding_amount: u64,
137 ) -> Result<()> {
138 let mut long_excluding_amount = 0u64;
139 let mut short_excluding_amount = 0u64;
140
141 for (token, amount) in [
142 (first_token, first_excluding_amount),
143 (second_token, second_excluding_amount),
144 ] {
145 if amount == 0 {
146 continue;
147 }
148 let is_long = self
149 .market_meta()
150 .to_token_side(token)
151 .map_err(CoreError::from)?;
152 if is_long {
153 long_excluding_amount = long_excluding_amount
154 .checked_add(amount)
155 .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
156 } else {
157 short_excluding_amount = short_excluding_amount
158 .checked_add(amount)
159 .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
160 }
161 }
162
163 self.validate_market_balances(long_excluding_amount, short_excluding_amount)
164 }
165}
166
167impl<
168 M: gmsol_model::BaseMarket<{ constants::MARKET_DECIMALS }, Num = u128>
169 + gmsol_model::Bank<Pubkey, Num = u64>
170 + HasMarketMeta,
171 > ValidateMarketBalances for M
172{
173}
174
175pub trait Adl {
177 fn validate_adl(&self, oracle: &Oracle, is_long: bool, max_staleness: u64) -> CoreResult<()>;
179
180 fn latest_adl_time(&self, is_long: bool) -> CoreResult<i64>;
182
183 fn update_adl_state(&mut self, oracle: &Oracle, is_long: bool) -> Result<()>;
184}
185
186impl Adl for Market {
187 fn latest_adl_time(&self, is_long: bool) -> CoreResult<i64> {
188 let clock = if is_long {
189 ClockKind::AdlForLong
190 } else {
191 ClockKind::AdlForShort
192 };
193 self.clock(clock).ok_or(CoreError::NotFound)
194 }
195
196 fn validate_adl(&self, oracle: &Oracle, is_long: bool, max_staleness: u64) -> CoreResult<()> {
197 if !self.is_adl_enabled(is_long) {
198 return Err(CoreError::AdlNotEnabled);
199 }
200 if oracle
201 .max_oracle_ts()
202 .saturating_add_unsigned(max_staleness)
203 < self.latest_adl_time(is_long)?
204 {
205 return Err(CoreError::OracleTimestampsAreSmallerThanRequired);
206 }
207 Ok(())
208 }
209
210 fn update_adl_state(&mut self, oracle: &Oracle, is_long: bool) -> Result<()> {
211 if oracle.max_oracle_ts() < self.latest_adl_time(is_long)? {
212 return err!(CoreError::OracleTimestampsAreSmallerThanRequired);
213 }
214 require!(self.is_enabled(), CoreError::DisabledMarket);
215 let prices = self.prices(oracle)?;
216 let is_exceeded = self
217 .pnl_factor_exceeded(&prices, PnlFactorKind::ForAdl, is_long)
218 .map_err(ModelError::from)?
219 .is_some();
220 self.set_adl_enabled(is_long, is_exceeded);
221 let kind = if is_long {
222 ClockKind::AdlForLong
223 } else {
224 ClockKind::AdlForShort
225 };
226 let clock = self
227 .state
228 .clocks
229 .get_mut(kind)
230 .ok_or_else(|| error!(CoreError::NotFound))?;
231 *clock = Clock::get()?.unix_timestamp;
232 Ok(())
233 }
234}