1use anchor_lang::prelude::*;
2use gmsol_model::{Bank, BorrowingFeeMarketMutExt, MarketAction, SwapMarketMutExt};
3use indexmap::{map::Entry, IndexMap};
4
5use crate::{
6 constants,
7 events::{BorrowingFeesUpdated, EventEmitter, SwapExecuted},
8 states::{
9 common::swap::SwapActionParams, market::utils::ValidateMarketBalances, HasMarketMeta,
10 Market, Oracle,
11 },
12 CoreError, ModelError,
13};
14
15use super::{market::RevertibleMarket, Revertible, Revision};
16
17pub struct SwapMarkets<'a, 'info> {
19 markets: IndexMap<Pubkey, RevertibleMarket<'a, 'info>>,
20 event_emitter: EventEmitter<'a, 'info>,
21}
22
23impl<'a, 'info> SwapMarkets<'a, 'info> {
24 pub(crate) fn new(
26 store: &Pubkey,
27 loaders: &'a [AccountLoader<'info, Market>],
28 current_market_token: Option<&Pubkey>,
29 event_emitter: EventEmitter<'a, 'info>,
30 ) -> Result<Self> {
31 let mut map = IndexMap::with_capacity(loaders.len());
32 for loader in loaders {
33 let key = loader.load()?.meta().market_token_mint;
34 if let Some(market_token) = current_market_token {
35 require!(key != *market_token, CoreError::InvalidSwapPath);
36 }
37 match map.entry(key) {
38 Entry::Occupied(_) => return err!(CoreError::InvalidSwapPath),
40 Entry::Vacant(e) => {
41 loader.load()?.validate(store)?;
42 let market = RevertibleMarket::new(loader, event_emitter)?;
43 e.insert(market);
44 }
45 }
46 }
47 Ok(Self {
48 markets: map,
49 event_emitter,
50 })
51 }
52
53 pub fn get_mut(&mut self, token: &Pubkey) -> Option<&mut RevertibleMarket<'a, 'info>> {
55 self.markets.get_mut(token)
56 }
57
58 pub fn get(&self, token: &Pubkey) -> Option<&RevertibleMarket<'a, 'info>> {
60 self.markets.get(token)
61 }
62
63 pub(crate) fn revertible_swap<M>(
65 &mut self,
66 mut direction: SwapDirection<M>,
67 oracle: &Oracle,
68 params: &SwapActionParams,
69 expected_token_outs: (Pubkey, Pubkey),
70 token_ins: (Option<Pubkey>, Option<Pubkey>),
71 token_in_amounts: (u64, u64),
72 ) -> Result<(u64, u64)>
73 where
74 M: Key
75 + Revision
76 + HasMarketMeta
77 + gmsol_model::Bank<Pubkey, Num = u64>
78 + gmsol_model::SwapMarketMut<{ constants::MARKET_DECIMALS }, Num = u128>
79 + gmsol_model::BorrowingFeeMarketMut<{ constants::MARKET_DECIMALS }>,
80 {
81 let long_path = params
82 .validated_primary_swap_path()
83 .map_err(CoreError::from)?;
84 let long_output_amount = token_ins
85 .0
86 .and_then(|token| (token_in_amounts.0 != 0).then_some(token))
87 .map(|token_in| {
88 self.revertible_swap_for_one_side(
89 &mut direction,
90 oracle,
91 long_path,
92 expected_token_outs.0,
93 token_in,
94 token_in_amounts.0,
95 )
96 })
97 .transpose()?
98 .unwrap_or_default();
99 let short_path = params
100 .validated_secondary_swap_path()
101 .map_err(CoreError::from)?;
102 let short_output_amount = token_ins
103 .1
104 .and_then(|token| (token_in_amounts.1 != 0).then_some(token))
105 .map(|token_in| {
106 self.revertible_swap_for_one_side(
107 &mut direction,
108 oracle,
109 short_path,
110 expected_token_outs.1,
111 token_in,
112 token_in_amounts.1,
113 )
114 })
115 .transpose()?
116 .unwrap_or_default();
117
118 let current = direction.current();
120 let mut long_output_market_token = long_path.last().unwrap_or(¤t);
121 let mut short_output_market_token = short_path.last().unwrap_or(¤t);
122
123 if direction.is_into() {
126 long_output_market_token = ¤t;
127 short_output_market_token = ¤t;
128 }
129
130 let mut current_validated = false;
131 if *long_output_market_token == *short_output_market_token {
132 if *long_output_market_token != current {
133 self.get(long_output_market_token)
134 .expect("must exist")
135 .validate_market_balances_excluding_the_given_token_amounts(
136 &expected_token_outs.0,
137 &expected_token_outs.1,
138 long_output_amount,
139 short_output_amount,
140 )?;
141 } else {
142 direction
143 .current_market()
144 .validate_market_balances_excluding_the_given_token_amounts(
145 &expected_token_outs.0,
146 &expected_token_outs.1,
147 long_output_amount,
148 short_output_amount,
149 )?;
150 current_validated = true;
151 }
152 } else {
153 for (market_token, amount, token) in [
154 (
155 long_output_market_token,
156 long_output_amount,
157 &expected_token_outs.0,
158 ),
159 (
160 short_output_market_token,
161 short_output_amount,
162 &expected_token_outs.1,
163 ),
164 ] {
165 if *market_token != current {
166 self.get(market_token)
167 .expect("must exist")
168 .validate_market_balances_excluding_the_given_token_amounts(
169 token, token, amount, 0,
170 )?;
171 } else {
172 direction
173 .current_market()
174 .validate_market_balances_excluding_the_given_token_amounts(
175 token, token, amount, 0,
176 )?;
177 current_validated = true;
178 }
179 }
180 }
181 if !current_validated {
182 direction.current_market().validate_market_balances(0, 0)?;
183 }
184 Ok((long_output_amount, short_output_amount))
185 }
186
187 fn swap_along_the_path(
196 &mut self,
197 oracle: &Oracle,
198 path: &[Pubkey],
199 token_in: &mut Pubkey,
200 token_in_amount: &mut u64,
201 ) -> Result<()> {
202 if path.is_empty() {
203 return Ok(());
204 }
205 let last_idx = path.len().saturating_sub(1);
206 for (idx, market_token) in path.iter().enumerate() {
207 let market = self.markets.get_mut(market_token).ok_or_else(|| {
208 msg!("Swap Error: missing market account for {}", market_token);
209 error!(CoreError::MarketAccountIsNotProvided)
210 })?;
211 if idx != 0 {
212 market
213 .record_transferred_in_by_token(token_in, token_in_amount)
214 .map_err(ModelError::from)?;
215 }
216 let side = market
217 .market_meta()
218 .to_token_side(token_in)
219 .map_err(CoreError::from)?;
220 let prices = oracle.market_prices(market)?;
221 {
223 let report = market
224 .update_borrowing(&prices)
225 .map_err(ModelError::from)?
226 .execute()
227 .map_err(ModelError::from)?;
228 market
229 .event_emitter()
230 .emit_cpi(&BorrowingFeesUpdated::from_report(
231 market.rev(),
232 *market_token,
233 report,
234 ))?;
235 }
236 let report = market
237 .swap(side, (*token_in_amount).into(), prices)
238 .map_err(ModelError::from)?
239 .execute()
240 .map_err(ModelError::from)?;
241 *token_in = *market
242 .market_meta()
243 .opposite_token(token_in)
244 .map_err(CoreError::from)?;
245 *token_in_amount = (*report.token_out_amount())
246 .try_into()
247 .map_err(|_| error!(CoreError::TokenAmountOverflow))?;
248 if idx != last_idx {
250 market
251 .record_transferred_out_by_token(token_in, token_in_amount)
252 .map_err(ModelError::from)?;
253 market.validate_market_balances(0, 0)?;
254 }
255 market.event_emitter().emit_cpi(&SwapExecuted::new(
256 market.rev(),
257 *market_token,
258 report,
259 None,
260 ))?;
261 }
262 msg!("[Swap] swapped along the path");
263 Ok(())
264 }
265
266 fn revertible_swap_for_one_side<M>(
271 &mut self,
272 direction: &mut SwapDirection<M>,
273 oracle: &Oracle,
274 mut path: &[Pubkey],
275 expected_token_out: Pubkey,
276 mut token_in: Pubkey,
277 mut token_in_amount: u64,
278 ) -> Result<u64>
279 where
280 M: Key
281 + Revision
282 + gmsol_model::Bank<Pubkey, Num = u64>
283 + gmsol_model::SwapMarketMut<{ constants::MARKET_DECIMALS }, Num = u128>
284 + HasMarketMeta,
285 {
286 require!(
287 self.get_mut(&direction.current()).is_none(),
288 CoreError::InvalidSwapPath
289 );
290 if !path.is_empty() {
291 let current = direction.current();
292
293 let first_market_token = path.first().unwrap();
294 if let SwapDirection::From(from_market) = direction {
295 if *first_market_token != current {
296 let first_market = self.get_mut(first_market_token).ok_or_else(|| {
297 msg!(
298 "Swap Error: missing market account for {}",
299 first_market_token
300 );
301 error!(CoreError::MarketAccountIsNotProvided)
302 })?;
303 from_market
305 .record_transferred_out_by_token(&token_in, &token_in_amount)
306 .map_err(ModelError::from)?;
307 from_market
308 .validate_market_balance_for_the_given_token(&token_in, 0)
309 .map_err(ModelError::from)?;
310 first_market
311 .record_transferred_in_by_token(&token_in, &token_in_amount)
312 .map_err(ModelError::from)?;
313 }
314 }
315
316 if *first_market_token == current {
317 direction.swap_with_current(
319 oracle,
320 &mut token_in,
321 &mut token_in_amount,
322 &self.event_emitter,
323 )?;
324 path = &path[1..];
325 if let Some(first_market_token_to_swap_at) = path.first() {
326 debug_assert!(*first_market_token_to_swap_at != current);
327 let first_market =
328 self.get_mut(first_market_token_to_swap_at).ok_or_else(|| {
329 msg!(
330 "Swap Error: missing market account for {}",
331 first_market_token
332 );
333 error!(CoreError::MarketAccountIsNotProvided)
334 })?;
335 direction
337 .current_market_mut()
338 .record_transferred_out_by_token(&token_in, &token_in_amount)
339 .map_err(ModelError::from)?;
340 first_market
342 .record_transferred_in_by_token(&token_in, &token_in_amount)
343 .map_err(ModelError::from)?;
344 }
345 }
346
347 if !path.is_empty() {
348 let mut should_swap_with_current = false;
349 let last_market_token = path.last().unwrap();
350
351 if *last_market_token == direction.current() {
352 should_swap_with_current = true;
353 path = path.split_last().unwrap().1;
354 }
355
356 self.swap_along_the_path(oracle, path, &mut token_in, &mut token_in_amount)?;
357
358 if should_swap_with_current {
359 if let Some(last_swapped_market_token) = path.last() {
360 debug_assert!(*last_swapped_market_token != current);
361 let last_market =
362 self.get_mut(last_swapped_market_token).expect("must exist");
363 last_market
365 .record_transferred_out_by_token(&token_in, &token_in_amount)
366 .map_err(ModelError::from)?;
367 last_market.validate_market_balances(0, 0)?;
368 direction
369 .current_market_mut()
370 .record_transferred_in_by_token(&token_in, &token_in_amount)
371 .map_err(ModelError::from)?;
372 }
373 direction.swap_with_current(
375 oracle,
376 &mut token_in,
377 &mut token_in_amount,
378 &self.event_emitter,
379 )?;
380 }
381
382 if let SwapDirection::Into(into_market) = direction {
383 if *last_market_token != current {
384 let last_market = self.get_mut(last_market_token).expect("must exist");
385 last_market
387 .record_transferred_out_by_token(&token_in, &token_in_amount)
388 .map_err(ModelError::from)?;
389 last_market.validate_market_balances(0, 0)?;
390 into_market
391 .record_transferred_in_by_token(&token_in, &token_in_amount)
392 .map_err(ModelError::from)?;
393 }
394 }
395 }
396 }
397 require_keys_eq!(token_in, expected_token_out, CoreError::InvalidSwapPath);
398 Ok(token_in_amount)
399 }
400}
401
402impl Revertible for SwapMarkets<'_, '_> {
403 fn commit(self) {
407 for market in self.markets.into_values() {
408 market.commit();
409 }
410 }
411}
412
413pub(crate) enum SwapDirection<'a, M> {
414 From(&'a mut M),
415 Into(&'a mut M),
416}
417
418impl<M> SwapDirection<'_, M>
419where
420 M: Key,
421{
422 fn current(&self) -> Pubkey {
423 match self {
424 Self::From(p) | Self::Into(p) => p.key(),
425 }
426 }
427}
428
429impl<M> Revision for SwapDirection<'_, M>
430where
431 M: Revision,
432{
433 fn rev(&self) -> u64 {
434 match self {
435 Self::From(p) | Self::Into(p) => p.rev(),
436 }
437 }
438}
439
440impl<M> SwapDirection<'_, M> {
441 fn current_market_mut(&mut self) -> &mut M {
442 match self {
443 Self::From(m) | Self::Into(m) => m,
444 }
445 }
446
447 fn current_market(&self) -> &M {
448 match self {
449 Self::From(m) | Self::Into(m) => m,
450 }
451 }
452
453 fn is_into(&self) -> bool {
454 matches!(self, Self::Into(_))
455 }
456}
457
458impl<M> SwapDirection<'_, M>
459where
460 M: Key
461 + Revision
462 + HasMarketMeta
463 + gmsol_model::SwapMarketMut<{ constants::MARKET_DECIMALS }, Num = u128>,
464{
465 fn swap_with_current(
466 &mut self,
467 oracle: &Oracle,
468 token_in: &mut Pubkey,
469 token_in_amount: &mut u64,
470 event_emitter: &EventEmitter,
471 ) -> Result<()> {
472 let current = match self {
473 Self::From(m) | Self::Into(m) => m,
474 };
475 let side = current
476 .market_meta()
477 .to_token_side(token_in)
478 .map_err(CoreError::from)?;
479 let prices = oracle.market_prices(*current)?;
480 let report = current
481 .swap(side, (*token_in_amount).into(), prices)
482 .map_err(ModelError::from)?
483 .execute()
484 .map_err(ModelError::from)?;
485 *token_in_amount = (*report.token_out_amount())
486 .try_into()
487 .map_err(|_| error!(CoreError::TokenAmountOverflow))?;
488 *token_in = *current
489 .market_meta()
490 .opposite_token(token_in)
491 .map_err(CoreError::from)?;
492 msg!("[Swap] swapped in current market");
493 event_emitter.emit_cpi(&SwapExecuted::new(self.rev(), self.current(), report, None))?;
494 Ok(())
495 }
496}