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