serum_swap/
lib.rs

1//! Program to perform instantly settled token swaps on the Serum DEX.
2//!
3//! Before using any instruction here, a user must first create an open orders
4//! account on all markets being used. This only needs to be done once, either
5//! via the system program create account instruction in the same transaction
6//! as the user's first trade or via the explicit `init_account` and
7//! `close_account` instructions provided here, which can be included in
8//! transactions.
9pub extern crate NT_anchor_lang as anchor_lang;
10pub extern crate NT_anchor_spl as anchor_spl;
11use anchor_lang::prelude::*;
12use anchor_spl::dex;
13use anchor_spl::dex::serum_dex::instruction::SelfTradeBehavior;
14use anchor_spl::dex::serum_dex::matching::{OrderType, Side as SerumSide};
15use anchor_spl::dex::serum_dex::state::MarketState;
16use anchor_spl::token;
17use solana_program::declare_id;
18use std::num::NonZeroU64;
19
20declare_id!("22Y43yTVxuUkoRKdm9thyRhQ3SdgQS7c7kB6UNCiaczD");//mainnet
21//declare_id!("CxXDXjGBJ6RwMKKLqkd9KCAR5yfswNd8iXcQPmFFeDvU");//localnet
22
23// Associated token account for Pubkey::default.
24mod empty {
25    use super::*;
26    declare_id!("HJt8Tjdsc9ms9i4WCZEzhzr4oyf3ANcdzXrNdLPFqm3M");
27}
28
29#[program]
30pub mod serum_swap {
31    use super::*;
32
33    /// Convenience API to initialize an open orders account on the Serum DEX.
34    pub fn init_account<'info>(ctx: Context<'_, '_, '_, 'info, InitAccount<'info>>) -> Result<()> {
35        let ctx = CpiContext::new(ctx.accounts.dex_program.clone(), ctx.accounts.into());
36        dex::init_open_orders(ctx)?;
37        Ok(())
38    }
39
40    /// Convenience API to close an open orders account on the Serum DEX.
41    pub fn close_account<'info>(
42        ctx: Context<'_, '_, '_, 'info, CloseAccount<'info>>,
43    ) -> Result<()> {
44        let ctx = CpiContext::new(ctx.accounts.dex_program.clone(), ctx.accounts.into());
45        dex::close_open_orders(ctx)?;
46        Ok(())
47    }
48
49    /// Swaps two tokens on a single A/B market, where A is the base currency
50    /// and B is the quote currency. This is just a direct IOC trade that
51    /// instantly settles.
52    ///
53    /// When side is "bid", then swaps B for A. When side is "ask", then swaps
54    /// A for B.
55    ///
56    /// Arguments:
57    ///
58    /// * `side`              - The direction to swap.
59    /// * `amount`            - The amount to swap *from*
60    /// * `min_exchange_rate` - The exchange rate to use when determining
61    ///    whether the transaction should abort.
62    #[access_control(is_valid_swap(&ctx))]
63    pub fn swap<'info>(
64        ctx: Context<'_, '_, '_, 'info, Swap<'info>>,
65        side: Side,
66        amount: u64,
67        min_exchange_rate: ExchangeRate,
68    ) -> Result<()> {
69        let mut min_exchange_rate = min_exchange_rate;
70
71        // Not used for direct swaps.
72        min_exchange_rate.quote_decimals = 0;
73        // Optional referral account (earns a referral fee).
74        let referral = ctx.remaining_accounts.iter().next().map(Clone::clone);
75        
76        // Side determines swap direction.
77        let (from_token, to_token) = match side {
78            Side::Bid => (&ctx.accounts.pc_wallet, &ctx.accounts.market.coin_wallet),
79            Side::Ask => (&ctx.accounts.market.coin_wallet, &ctx.accounts.pc_wallet),
80        };
81        
82        // Token balances before the trade.
83        let from_amount_before = token::accessor::amount(from_token)?;
84        let to_amount_before = token::accessor::amount(to_token)?;
85        // Execute trade.
86        let orderbook: OrderbookClient<'info> = (&*ctx.accounts).into();
87        match side {
88            Side::Bid => orderbook.buy(amount, None)?,
89            Side::Ask => orderbook.sell(amount, None)?,
90        };
91        orderbook.settle(referral)?;
92        // Token balances after the trade.
93        let from_amount_after = token::accessor::amount(from_token)?;
94        let to_amount_after = token::accessor::amount(to_token)?;
95        //  Calculate the delta, i.e. the amount swapped.
96        let from_amount = from_amount_before.checked_sub(from_amount_after).unwrap();
97        let to_amount = to_amount_after.checked_sub(to_amount_before).unwrap();
98        // Safety checks.
99        apply_risk_checks(DidSwap {
100            authority: *ctx.accounts.authority.key,
101            given_amount: amount,
102            min_exchange_rate,
103            from_amount,
104            to_amount,
105            quote_amount: 0,
106            spill_amount: 0,
107            from_mint: token::accessor::mint(from_token)?,
108            to_mint: token::accessor::mint(to_token)?,
109            quote_mint: match side {
110                Side::Bid => token::accessor::mint(from_token)?,
111                Side::Ask => token::accessor::mint(to_token)?,
112            },
113        })?;
114
115        Ok(())
116    }
117
118    /// Swaps two base currencies across two different markets.
119    ///
120    /// That is, suppose there are two markets, A/USD(x) and B/USD(x).
121    /// Then swaps token A for token B via
122    ///
123    /// * IOC (immediate or cancel) sell order on A/USD(x) market.
124    /// * Settle open orders to get USD(x).
125    /// * IOC buy order on B/USD(x) market to convert USD(x) to token B.
126    /// * Settle open orders to get token B.
127    ///
128    /// Arguments:
129    ///
130    /// * `amount`            - The amount to swap *from*.
131    /// * `min_exchange_rate` - The exchange rate to use when determining
132    ///    whether the transaction should abort.
133    #[access_control(is_valid_swap_transitive(&ctx))]
134    pub fn swap_transitive<'info>(
135        ctx: Context<'_, '_, '_, 'info, SwapTransitive<'info>>,
136        amount: u64,
137        min_exchange_rate: ExchangeRate,
138    ) -> Result<()> {
139        // Optional referral account (earns a referral fee).
140        let referral = ctx.remaining_accounts.iter().next().map(Clone::clone);
141
142        // Leg 1: Sell Token A for USD(x) (or whatever quote currency is used).
143        let (from_amount, sell_proceeds) = {
144            // Token balances before the trade.
145            let base_before = token::accessor::amount(&ctx.accounts.from.coin_wallet)?;
146            let quote_before = token::accessor::amount(&ctx.accounts.pc_wallet)?;
147
148            // Execute the trade.
149            let orderbook = ctx.accounts.orderbook_from();
150            orderbook.sell(amount, None)?;
151            orderbook.settle(referral.clone())?;
152
153            // Token balances after the trade.
154            let base_after = token::accessor::amount(&ctx.accounts.from.coin_wallet)?;
155            let quote_after = token::accessor::amount(&ctx.accounts.pc_wallet)?;
156
157            // Report the delta.
158            (
159                base_before.checked_sub(base_after).unwrap(),
160                quote_after.checked_sub(quote_before).unwrap(),
161            )
162        };
163
164        // Leg 2: Buy Token B with USD(x) (or whatever quote currency is used).
165        let (to_amount, buy_proceeds) = {
166            // Token balances before the trade.
167            let base_before = token::accessor::amount(&ctx.accounts.to.coin_wallet)?;
168            let quote_before = token::accessor::amount(&ctx.accounts.pc_wallet)?;
169
170            // Execute the trade.
171            let orderbook = ctx.accounts.orderbook_to();
172            orderbook.buy(sell_proceeds, None)?;
173            orderbook.settle(referral)?;
174
175            // Token balances after the trade.
176            let base_after = token::accessor::amount(&ctx.accounts.to.coin_wallet)?;
177            let quote_after = token::accessor::amount(&ctx.accounts.pc_wallet)?;
178
179            // Report the delta.
180            (
181                base_after.checked_sub(base_before).unwrap(),
182                quote_before.checked_sub(quote_after).unwrap(),
183            )
184        };
185
186        // The amount of surplus quote currency *not* fully consumed by the
187        // second half of the swap.
188        let spill_amount = sell_proceeds.checked_sub(buy_proceeds).unwrap();
189
190        // Safety checks.
191        apply_risk_checks(DidSwap {
192            given_amount: amount,
193            min_exchange_rate,
194            from_amount,
195            to_amount,
196            quote_amount: sell_proceeds,
197            spill_amount,
198            from_mint: token::accessor::mint(&ctx.accounts.from.coin_wallet)?,
199            to_mint: token::accessor::mint(&ctx.accounts.to.coin_wallet)?,
200            quote_mint: token::accessor::mint(&ctx.accounts.pc_wallet)?,
201            authority: *ctx.accounts.authority.key,
202        })?;
203
204        Ok(())
205    }
206}
207
208// Asserts the swap event executed at an exchange rate acceptable to the client.
209fn apply_risk_checks(event: DidSwap) -> Result<()> {
210    // Emit the event for client consumption.
211    emit!(event);
212    if event.to_amount == 0 {
213        return Err(ErrorCode::ZeroSwap.into());
214    }
215  
216    // Use the exchange rate to calculate the client's expectation.
217    //
218    // The exchange rate given must always have decimals equal to the
219    // `to_mint` decimals, guaranteeing the `min_expected_amount`
220    // always has decimals equal to
221    //
222    // `decimals(from_mint) + decimals(to_mint) + decimals(quote_mint)`.
223    //
224    // We avoid truncating by adding `decimals(quote_mint)`.
225    let min_expected_amount = u128::from(
226        // decimals(from).
227        event.from_amount,
228    )
229    .checked_mul(
230        // decimals(from) + decimals(to).
231        event.min_exchange_rate.rate.into(),
232    )
233    .unwrap()
234    .checked_mul(
235        // decimals(from) + decimals(to) + decimals(quote).
236        10u128
237            .checked_pow(event.min_exchange_rate.quote_decimals.into())
238            .unwrap(),
239    )
240    .unwrap();
241
242    // If there is spill (i.e. quote tokens *not* fully consumed for
243    // the buy side of a transitive swap), then credit those tokens marked
244    // at the executed exchange rate to create an "effective" to_amount.
245    let effective_to_amount = {
246        // Translates the leftover spill amount into "to" units via
247        //
248        // `(to_amount_received/quote_amount_given) * spill_amount`
249        //
250        let spill_surplus = match event.spill_amount == 0 || event.min_exchange_rate.strict {
251            true => 0,
252            false => u128::from(
253                // decimals(to).
254                event.to_amount,
255            )
256            .checked_mul(
257                // decimals(to) + decimals(quote).
258                event.spill_amount.into(),
259            )
260            .unwrap()
261            .checked_mul(
262                // decimals(to) + decimals(quote) + decimals(from).
263                10u128
264                    .checked_pow(event.min_exchange_rate.from_decimals.into())
265                    .unwrap(),
266            )
267            .unwrap()
268            .checked_mul(
269                // decimals(to) + decimals(quote)*2 + decimals(from).
270                10u128
271                    .checked_pow(event.min_exchange_rate.quote_decimals.into())
272                    .unwrap(),
273            )
274            .unwrap()
275            .checked_div(
276                // decimals(to) + decimals(quote) + decimals(from).
277                event
278                    .quote_amount
279                    .checked_sub(event.spill_amount)
280                    .unwrap()
281                    .into(),
282            )
283            .unwrap(),
284        };
285
286        // Translate the `to_amount` into a common number of decimals.
287        let to_amount = u128::from(
288            // decimals(to).
289            event.to_amount,
290        )
291        .checked_mul(
292            // decimals(to) + decimals(from).
293            10u128
294                .checked_pow(event.min_exchange_rate.from_decimals.into())
295                .unwrap(),
296        )
297        .unwrap()
298        .checked_mul(
299            // decimals(to) + decimals(from) + decimals(quote).
300            10u128
301                .checked_pow(event.min_exchange_rate.quote_decimals.into())
302                .unwrap(),
303        )
304        .unwrap();
305
306        to_amount.checked_add(spill_surplus).unwrap()
307    };
308
309    // Abort if the resulting amount is less than the client's expectation.
310    if effective_to_amount < min_expected_amount {
311        msg!(
312            "effective_to_amount, min_expected_amount: {:?}, {:?}",
313            effective_to_amount,
314            min_expected_amount,
315        );
316        return Err(ErrorCode::SlippageExceeded.into());
317    }
318
319    Ok(())
320}
321
322#[derive(Accounts)]
323pub struct InitAccount<'info> {
324    #[account(mut)]
325    open_orders: AccountInfo<'info>,
326    #[account(signer)]
327    authority: AccountInfo<'info>,
328    market: AccountInfo<'info>,
329    dex_program: AccountInfo<'info>,
330    rent: AccountInfo<'info>,
331}
332
333impl<'info> From<&mut InitAccount<'info>> for dex::InitOpenOrders<'info> {
334    fn from(accs: &mut InitAccount<'info>) -> dex::InitOpenOrders<'info> {
335        dex::InitOpenOrders {
336            open_orders: accs.open_orders.clone(),
337            authority: accs.authority.clone(),
338            market: accs.market.clone(),
339            rent: accs.rent.clone(),
340        }
341    }
342}
343
344#[derive(Accounts)]
345pub struct CloseAccount<'info> {
346    #[account(mut)]
347    open_orders: AccountInfo<'info>,
348    #[account(signer)]
349    authority: AccountInfo<'info>,
350    #[account(mut)]
351    destination: AccountInfo<'info>,
352    market: AccountInfo<'info>,
353    dex_program: AccountInfo<'info>,
354}
355
356impl<'info> From<&mut CloseAccount<'info>> for dex::CloseOpenOrders<'info> {
357    fn from(accs: &mut CloseAccount<'info>) -> dex::CloseOpenOrders<'info> {
358        dex::CloseOpenOrders {
359            open_orders: accs.open_orders.clone(),
360            authority: accs.authority.clone(),
361            destination: accs.destination.clone(),
362            market: accs.market.clone(),
363        }
364    }
365}
366
367// The only constraint imposed on these accounts is that the market's base
368// currency mint is not equal to the quote currency's. All other checks are
369// done by the DEX on CPI.
370#[derive(Accounts)]
371pub struct Swap<'info> {
372    pub market: MarketAccounts<'info>,
373    #[account(signer)]
374    pub authority: AccountInfo<'info>,
375    #[account(mut, constraint = pc_wallet.key != &empty::ID)]
376    pub pc_wallet: AccountInfo<'info>,
377    // Programs.
378    pub dex_program: AccountInfo<'info>,
379    pub token_program: AccountInfo<'info>,
380    // Sysvars.
381    pub rent: AccountInfo<'info>,
382}
383
384impl<'info> From<&Swap<'info>> for OrderbookClient<'info> {
385    fn from(accounts: &Swap<'info>) -> OrderbookClient<'info> {
386        OrderbookClient {
387            market: accounts.market.clone(),
388            authority: accounts.authority.clone(),
389            pc_wallet: accounts.pc_wallet.clone(),
390            dex_program: accounts.dex_program.clone(),
391            token_program: accounts.token_program.clone(),
392            rent: accounts.rent.clone(),
393        }
394    }
395}
396
397// The only constraint imposed on these accounts is that the from market's
398// base currency's is not equal to the to market's base currency. All other
399// checks are done by the DEX on CPI (and the quote currency is ensured to be
400// the same on both markets since there's only one account field for it).
401#[derive(Accounts)]
402pub struct SwapTransitive<'info> {
403    pub from: MarketAccounts<'info>,
404    pub to: MarketAccounts<'info>,
405    // Must be the authority over all open orders accounts used.
406    #[account(signer)]
407    pub authority: AccountInfo<'info>,
408    #[account(mut, constraint = pc_wallet.key != &empty::ID)]
409    pub pc_wallet: AccountInfo<'info>,
410    // Programs.
411    pub dex_program: AccountInfo<'info>,
412    pub token_program: AccountInfo<'info>,
413    // Sysvars.
414    pub rent: AccountInfo<'info>,
415}
416
417impl<'info> SwapTransitive<'info> {
418    fn orderbook_from(&self) -> OrderbookClient<'info> {
419        OrderbookClient {
420            market: self.from.clone(),
421            authority: self.authority.clone(),
422            pc_wallet: self.pc_wallet.clone(),
423            dex_program: self.dex_program.clone(),
424            token_program: self.token_program.clone(),
425            rent: self.rent.clone(),
426        }
427    }
428    fn orderbook_to(&self) -> OrderbookClient<'info> {
429        OrderbookClient {
430            market: self.to.clone(),
431            authority: self.authority.clone(),
432            pc_wallet: self.pc_wallet.clone(),
433            dex_program: self.dex_program.clone(),
434            token_program: self.token_program.clone(),
435            rent: self.rent.clone(),
436        }
437    }
438}
439
440// Client for sending orders to the Serum DEX.
441#[derive(Clone)]
442struct OrderbookClient<'info> {
443    market: MarketAccounts<'info>,
444    authority: AccountInfo<'info>,
445    pc_wallet: AccountInfo<'info>,
446    dex_program: AccountInfo<'info>,
447    token_program: AccountInfo<'info>,
448    rent: AccountInfo<'info>,
449}
450
451impl<'info> OrderbookClient<'info> {
452    // Executes the sell order portion of the swap, purchasing as much of the
453    // quote currency as possible for the given `base_amount`.
454    //
455    // `base_amount` is the "native" amount of the base currency, i.e., token
456    // amount including decimals.
457    fn sell(
458        &self,
459        base_amount: u64,
460        srm_msrm_discount: Option<AccountInfo<'info>>,
461    ) -> ProgramResult {
462        let limit_price = 1;
463        let max_coin_qty = {
464            // The loaded market must be dropped before CPI.
465            let market = MarketState::load(&self.market.market, &dex::ID)?;
466            coin_lots(&market, base_amount)
467        };
468        let max_native_pc_qty = u64::MAX;
469        self.order_cpi(
470            limit_price,
471            max_coin_qty,
472            max_native_pc_qty,
473            Side::Ask,
474            srm_msrm_discount,
475        )
476    }
477
478    // Executes the buy order portion of the swap, purchasing as much of the
479    // base currency as possible, for the given `quote_amount`.
480    //
481    // `quote_amount` is the "native" amount of the quote currency, i.e., token
482    // amount including decimals.
483    fn buy(
484        &self,
485        quote_amount: u64,
486        srm_msrm_discount: Option<AccountInfo<'info>>,
487    ) -> ProgramResult {
488        let limit_price = u64::MAX;
489        let max_coin_qty = u64::MAX;
490        let max_native_pc_qty = quote_amount;
491        self.order_cpi(
492            limit_price,
493            max_coin_qty,
494            max_native_pc_qty,
495            Side::Bid,
496            srm_msrm_discount,
497        )
498    }
499
500    // Executes a new order on the serum dex via CPI.
501    //
502    // * `limit_price` - the limit order price in lot units.
503    // * `max_coin_qty`- the max number of the base currency lot units.
504    // * `max_native_pc_qty` - the max number of quote currency in native token
505    //                         units (includes decimals).
506    // * `side` - bid or ask, i.e. the type of order.
507    // * `referral` - referral account, earning a fee.
508    fn order_cpi(
509        &self,
510        limit_price: u64,
511        max_coin_qty: u64,
512        max_native_pc_qty: u64,
513        side: Side,
514        srm_msrm_discount: Option<AccountInfo<'info>>,
515    ) -> ProgramResult {
516        // Client order id is only used for cancels. Not used here so hardcode.
517        let client_order_id = 0;
518        // Limit is the dex's custom compute budge parameter, setting an upper
519        // bound on the number of matching cycles the program can perform
520        // before giving up and posting the remaining unmatched order.
521        let limit = 65535;
522        let mut ctx = CpiContext::new(self.dex_program.clone(), self.clone().into());
523        if let Some(srm_msrm_discount) = srm_msrm_discount {
524            ctx = ctx.with_remaining_accounts(vec![srm_msrm_discount]);
525        }
526        dex::new_order_v3(
527            ctx,
528            side.into(),
529            NonZeroU64::new(limit_price).unwrap(),
530            NonZeroU64::new(max_coin_qty).unwrap(),
531            NonZeroU64::new(max_native_pc_qty).unwrap(),
532            SelfTradeBehavior::DecrementTake,
533            OrderType::ImmediateOrCancel,
534            client_order_id,
535            limit,
536        )
537    }
538
539    fn settle(&self, referral: Option<AccountInfo<'info>>) -> ProgramResult {
540        let settle_accs = dex::SettleFunds {
541            market: self.market.market.clone(),
542            open_orders: self.market.open_orders.clone(),
543            open_orders_authority: self.authority.clone(),
544            coin_vault: self.market.coin_vault.clone(),
545            pc_vault: self.market.pc_vault.clone(),
546            coin_wallet: self.market.coin_wallet.clone(),
547            pc_wallet: self.pc_wallet.clone(),
548            vault_signer: self.market.vault_signer.clone(),
549            token_program: self.token_program.clone(),
550        };
551        let mut ctx = CpiContext::new(self.dex_program.clone(), settle_accs);
552        if let Some(referral) = referral {
553            ctx = ctx.with_remaining_accounts(vec![referral]);
554        }
555        dex::settle_funds(ctx)
556    }
557}
558
559impl<'info> From<OrderbookClient<'info>> for dex::NewOrderV3<'info> {
560    fn from(c: OrderbookClient<'info>) -> dex::NewOrderV3<'info> {
561        dex::NewOrderV3 {
562            market: c.market.market.clone(),
563            open_orders: c.market.open_orders.clone(),
564            request_queue: c.market.request_queue.clone(),
565            event_queue: c.market.event_queue.clone(),
566            market_bids: c.market.bids.clone(),
567            market_asks: c.market.asks.clone(),
568            order_payer_token_account: c.market.order_payer_token_account.clone(),
569            open_orders_authority: c.authority.clone(),
570            coin_vault: c.market.coin_vault.clone(),
571            pc_vault: c.market.pc_vault.clone(),
572            token_program: c.token_program.clone(),
573            rent: c.rent.clone(),
574        }
575    }
576}
577
578// Returns the amount of lots for the base currency of a trade with `size`.
579fn coin_lots(market: &MarketState, size: u64) -> u64 {
580    size.checked_div(market.coin_lot_size).unwrap()
581}
582
583// Market accounts are the accounts used to place orders against the dex minus
584// common accounts, i.e., program ids, sysvars, and the `pc_wallet`.
585#[derive(Accounts, Clone)]
586pub struct MarketAccounts<'info> {
587    #[account(mut)]
588    pub market: AccountInfo<'info>,
589    #[account(mut)]
590    pub open_orders: AccountInfo<'info>,
591    #[account(mut)]
592    pub request_queue: AccountInfo<'info>,
593    #[account(mut)]
594    pub event_queue: AccountInfo<'info>,
595    #[account(mut)]
596    pub bids: AccountInfo<'info>,
597    #[account(mut)]
598    pub asks: AccountInfo<'info>,
599    // The `spl_token::Account` that funds will be taken from, i.e., transferred
600    // from the user into the market's vault.
601    //
602    // For bids, this is the base currency. For asks, the quote.
603    #[account(mut, constraint = order_payer_token_account.key != &empty::ID)]
604    pub order_payer_token_account: AccountInfo<'info>,
605    // Also known as the "base" currency. For a given A/B market,
606    // this is the vault for the A mint.
607    #[account(mut)]
608    pub coin_vault: AccountInfo<'info>,
609    // Also known as the "quote" currency. For a given A/B market,
610    // this is the vault for the B mint.
611    #[account(mut)]
612    pub pc_vault: AccountInfo<'info>,
613    // PDA owner of the DEX's token accounts for base + quote currencies.
614    pub vault_signer: AccountInfo<'info>,
615    // User wallets.
616    #[account(mut, constraint = coin_wallet.key != &empty::ID)]
617    pub coin_wallet: AccountInfo<'info>,
618}
619
620#[derive(AnchorSerialize, AnchorDeserialize)]
621pub enum Side {
622    Bid,
623    Ask,
624}
625
626impl From<Side> for SerumSide {
627    fn from(side: Side) -> SerumSide {
628        match side {
629            Side::Bid => SerumSide::Bid,
630            Side::Ask => SerumSide::Ask,
631        }
632    }
633}
634
635// Access control modifiers.
636
637fn is_valid_swap(ctx: &Context<Swap>) -> Result<()> {
638    _is_valid_swap(&ctx.accounts.market.coin_wallet, &ctx.accounts.pc_wallet)
639}
640
641fn is_valid_swap_transitive(ctx: &Context<SwapTransitive>) -> Result<()> {
642    _is_valid_swap(&ctx.accounts.from.coin_wallet, &ctx.accounts.to.coin_wallet)
643}
644
645// Validates the tokens being swapped are of different mints.
646fn _is_valid_swap<'info>(from: &AccountInfo<'info>, to: &AccountInfo<'info>) -> Result<()> {
647    let from_token_mint = token::accessor::mint(from)?;
648    let to_token_mint = token::accessor::mint(to)?;
649    if from_token_mint == to_token_mint {
650        return Err(ErrorCode::SwapTokensCannotMatch.into());
651    }
652    Ok(())
653}
654
655// Event emitted when a swap occurs for two base currencies on two different
656// markets (quoted in the same token).
657#[event]
658pub struct DidSwap {
659    // User given (max) amount  of the "from" token to swap.
660    pub given_amount: u64,
661    // The minimum exchange rate for swapping `from_amount` to `to_amount` in
662    // native units with decimals equal to the `to_amount`'s mint--specified
663    // by the client.
664    pub min_exchange_rate: ExchangeRate,
665    // Amount of the `from` token sold.
666    pub from_amount: u64,
667    // Amount of the `to` token purchased.
668    pub to_amount: u64,
669    // The amount of the quote currency used for a *transitive* swap. This is
670    // the amount *received* for selling on the first leg of the swap.
671    pub quote_amount: u64,
672    // Amount of the quote currency accumulated from a *transitive* swap, i.e.,
673    // the difference between the amount gained from the first leg of the swap
674    // (to sell) and the amount used in the second leg of the swap (to buy).
675    pub spill_amount: u64,
676    // Mint sold.
677    pub from_mint: Pubkey,
678    // Mint purchased.
679    pub to_mint: Pubkey,
680    // Mint of the token used as the quote currency in the two markets used
681    // for swapping.
682    pub quote_mint: Pubkey,
683    // User that signed the transaction.
684    pub authority: Pubkey,
685}
686
687// An exchange rate for swapping *from* one token *to* another.
688#[derive(AnchorSerialize, AnchorDeserialize)]
689pub struct ExchangeRate {
690    // The amount of *to* tokens one should receive for a single *from token.
691    // This number must be in native *to* units with the same amount of decimals
692    // as the *to* mint.
693    pub rate: u64,
694    // Number of decimals of the *from* token's mint.
695    pub from_decimals: u8,
696    // Number of decimals of the *to* token's mint.
697    // For a direct swap, this should be zero.
698    pub quote_decimals: u8,
699    // True if *all* of the *from* currency sold should be used when calculating
700    // the executed exchange rate.
701    //
702    // To perform a transitive swap, one sells on one market and buys on
703    // another, where both markets are quoted in the same currency. Now suppose
704    // one swaps A for B across A/USDC and B/USDC. Further suppose the first
705    // leg swaps the entire *from* amount A for USDC, and then only half of
706    // the USDC is used to swap for B on the second leg. How should we calculate
707    // the exchange rate?
708    //
709    // If strict is true, then the exchange rate will be calculated as a direct
710    // function of the A tokens lost and B tokens gained, ignoring the surplus
711    // in USDC received. If strict is false, an effective exchange rate will be
712    // used. I.e. the surplus in USDC will be marked at the exchange rate from
713    // the second leg of the swap and that amount will be added to the
714    // *to* mint received before calculating the swap's exchange rate.
715    //
716    // Transitive swaps only. For direct swaps, this field is ignored.
717    pub strict: bool,
718}
719
720#[error]
721pub enum ErrorCode {
722    #[msg("The tokens being swapped must have different mints")]
723    SwapTokensCannotMatch,
724    #[msg("Slippage tolerance exceeded")]
725    SlippageExceeded,
726    #[msg("No tokens received when swapping")]
727    ZeroSwap,
728}