gmsol_store/states/market/revertible/
swap_market.rs

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
17/// A map of markets used for swaps where the key is the market token mint address.
18pub 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    /// Create a new [`SwapMarkets`] from loders.
25    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                // Cannot have duplicated markets.
39                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    /// Get market mutably.
54    pub fn get_mut(&mut self, token: &Pubkey) -> Option<&mut RevertibleMarket<'a, 'info>> {
55        self.markets.get_mut(token)
56    }
57
58    /// Get market.
59    pub fn get(&self, token: &Pubkey) -> Option<&RevertibleMarket<'a, 'info>> {
60        self.markets.get(token)
61    }
62
63    /// Revertible Swap.
64    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        // Validate token balances of output markets and current market.
119        let current = direction.current();
120        let mut long_output_market_token = long_path.last().unwrap_or(&current);
121        let mut short_output_market_token = short_path.last().unwrap_or(&current);
122
123        // When the swap direction is swapping into current market,
124        // the balances should have been transferred into current market.
125        if direction.is_into() {
126            long_output_market_token = &current;
127            short_output_market_token = &current;
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    /// Swap along the path.
188    ///
189    /// ## Assumptions
190    /// - The input amount is already deposited in the first market.
191    /// - The path consists of the mint addresses of unique market tokens.
192    ///
193    /// ## Notes
194    /// - The output amount will also remain deposited in the last market.
195    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            // Update borrowing state.
222            {
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            // Only validate the market without extra balances.
249            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    /// Swap for one side.
267    ///
268    /// ## Assumption
269    /// - The market tokens in the path must be unique.
270    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                    // We are assuming that they are sharing the same vault of `token_in`.
304                    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                // The validation of current market is delayed.
318                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                    // We are assuming that they are sharing the same vault of `token_in`.
336                    direction
337                        .current_market_mut()
338                        .record_transferred_out_by_token(&token_in, &token_in_amount)
339                        .map_err(ModelError::from)?;
340                    // The validation of current market is delayed.
341                    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                        // We are assuming that they are sharing the same vault of `token_in`.
364                        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                    // The validation of current market is delayed.
374                    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                        // We are assuming that they are sharing the same vault of `token_in`.
386                        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    /// Commit the swap.
404    /// ## Panic
405    /// Panic if one of the commitments panics.
406    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}