1pub extern crate karima_anchor_lang as anchor_lang;
10pub extern crate karima_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");
21
22mod empty {
24 use super::*;
25 declare_id!("HJt8Tjdsc9ms9i4WCZEzhzr4oyf3ANcdzXrNdLPFqm3M");
26}
27
28#[program]
29pub mod serum_swap {
30 use super::*;
31
32 pub fn init_account<'info>(ctx: Context<'_, '_, '_, 'info, InitAccount<'info>>) -> Result<()> {
34 let ctx = CpiContext::new(ctx.accounts.dex_program.clone(), ctx.accounts.into());
35 dex::init_open_orders(ctx)?;
36 Ok(())
37 }
38
39 pub fn close_account<'info>(
41 ctx: Context<'_, '_, '_, 'info, CloseAccount<'info>>,
42 ) -> Result<()> {
43 let ctx = CpiContext::new(ctx.accounts.dex_program.clone(), ctx.accounts.into());
44 dex::close_open_orders(ctx)?;
45 Ok(())
46 }
47
48 #[access_control(is_valid_swap(&ctx))]
62 pub fn swap<'info>(
63 ctx: Context<'_, '_, '_, 'info, Swap<'info>>,
64 side: Side,
65 amount: u64,
66 min_exchange_rate: ExchangeRate,
67 ) -> Result<()> {
68 let mut min_exchange_rate = min_exchange_rate;
69
70 min_exchange_rate.quote_decimals = 0;
72 msg!("swap 0");
73 let referral = ctx.remaining_accounts.iter().next().map(Clone::clone);
75 msg!("swap 1");
76 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 msg!("swap 2");
82 let from_amount_before = token::accessor::amount(from_token)?;
84 let to_amount_before = token::accessor::amount(to_token)?;
85 msg!("swap 3 from_token {:?} to_token {:?} ",from_token,to_token);
86 let orderbook: OrderbookClient<'info> = (&*ctx.accounts).into();
88 msg!("swap 4 amount {:?}",amount);
89 match side {
90 Side::Bid => orderbook.buy(amount, None)?,
91 Side::Ask => orderbook.sell(amount, None)?,
92 };
93 msg!("swap 5");
94 orderbook.settle(referral)?;
95 msg!("swap 6");
96 let from_amount_after = token::accessor::amount(from_token)?;
98 let to_amount_after = token::accessor::amount(to_token)?;
99 msg!("swap 7 from_amount_after {:?} to_amount_after {:?}",from_amount_after,to_amount_after);
100 let from_amount = from_amount_before.checked_sub(from_amount_after).unwrap();
102 let to_amount = to_amount_after.checked_sub(to_amount_before).unwrap();
103 msg!("swap 8: before-after-from {:?} before-after-to {:?}",from_amount,to_amount);
104 apply_risk_checks(DidSwap {
106 authority: *ctx.accounts.authority.key,
107 given_amount: amount,
108 min_exchange_rate,
109 from_amount,
110 to_amount,
111 quote_amount: 0,
112 spill_amount: 0,
113 from_mint: token::accessor::mint(from_token)?,
114 to_mint: token::accessor::mint(to_token)?,
115 quote_mint: match side {
116 Side::Bid => token::accessor::mint(from_token)?,
117 Side::Ask => token::accessor::mint(to_token)?,
118 },
119 })?;
120
121 Ok(())
122 }
123
124 #[access_control(is_valid_swap_transitive(&ctx))]
140 pub fn swap_transitive<'info>(
141 ctx: Context<'_, '_, '_, 'info, SwapTransitive<'info>>,
142 amount: u64,
143 min_exchange_rate: ExchangeRate,
144 ) -> Result<()> {
145 let referral = ctx.remaining_accounts.iter().next().map(Clone::clone);
147
148 let (from_amount, sell_proceeds) = {
150 let base_before = token::accessor::amount(&ctx.accounts.from.coin_wallet)?;
152 let quote_before = token::accessor::amount(&ctx.accounts.pc_wallet)?;
153
154 let orderbook = ctx.accounts.orderbook_from();
156 orderbook.sell(amount, None)?;
157 orderbook.settle(referral.clone())?;
158
159 let base_after = token::accessor::amount(&ctx.accounts.from.coin_wallet)?;
161 let quote_after = token::accessor::amount(&ctx.accounts.pc_wallet)?;
162
163 (
165 base_before.checked_sub(base_after).unwrap(),
166 quote_after.checked_sub(quote_before).unwrap(),
167 )
168 };
169
170 let (to_amount, buy_proceeds) = {
172 let base_before = token::accessor::amount(&ctx.accounts.to.coin_wallet)?;
174 let quote_before = token::accessor::amount(&ctx.accounts.pc_wallet)?;
175
176 let orderbook = ctx.accounts.orderbook_to();
178 orderbook.buy(sell_proceeds, None)?;
179 orderbook.settle(referral)?;
180
181 let base_after = token::accessor::amount(&ctx.accounts.to.coin_wallet)?;
183 let quote_after = token::accessor::amount(&ctx.accounts.pc_wallet)?;
184
185 (
187 base_after.checked_sub(base_before).unwrap(),
188 quote_before.checked_sub(quote_after).unwrap(),
189 )
190 };
191
192 let spill_amount = sell_proceeds.checked_sub(buy_proceeds).unwrap();
195
196 apply_risk_checks(DidSwap {
198 given_amount: amount,
199 min_exchange_rate,
200 from_amount,
201 to_amount,
202 quote_amount: sell_proceeds,
203 spill_amount,
204 from_mint: token::accessor::mint(&ctx.accounts.from.coin_wallet)?,
205 to_mint: token::accessor::mint(&ctx.accounts.to.coin_wallet)?,
206 quote_mint: token::accessor::mint(&ctx.accounts.pc_wallet)?,
207 authority: *ctx.accounts.authority.key,
208 })?;
209
210 Ok(())
211 }
212}
213
214fn apply_risk_checks(event: DidSwap) -> Result<()> {
216 emit!(event);
218 msg!("apply_risk_checks ");
219 msg!("apply_risk_checks {:?}",event.to_amount);
220 if event.to_amount == 0 {
221 return Err(ErrorCode::ZeroSwap.into());
222 }
223
224 let min_expected_amount = u128::from(
234 event.from_amount,
236 )
237 .checked_mul(
238 event.min_exchange_rate.rate.into(),
240 )
241 .unwrap()
242 .checked_mul(
243 10u128
245 .checked_pow(event.min_exchange_rate.quote_decimals.into())
246 .unwrap(),
247 )
248 .unwrap();
249
250 let effective_to_amount = {
254 let spill_surplus = match event.spill_amount == 0 || event.min_exchange_rate.strict {
259 true => 0,
260 false => u128::from(
261 event.to_amount,
263 )
264 .checked_mul(
265 event.spill_amount.into(),
267 )
268 .unwrap()
269 .checked_mul(
270 10u128
272 .checked_pow(event.min_exchange_rate.from_decimals.into())
273 .unwrap(),
274 )
275 .unwrap()
276 .checked_mul(
277 10u128
279 .checked_pow(event.min_exchange_rate.quote_decimals.into())
280 .unwrap(),
281 )
282 .unwrap()
283 .checked_div(
284 event
286 .quote_amount
287 .checked_sub(event.spill_amount)
288 .unwrap()
289 .into(),
290 )
291 .unwrap(),
292 };
293
294 let to_amount = u128::from(
296 event.to_amount,
298 )
299 .checked_mul(
300 10u128
302 .checked_pow(event.min_exchange_rate.from_decimals.into())
303 .unwrap(),
304 )
305 .unwrap()
306 .checked_mul(
307 10u128
309 .checked_pow(event.min_exchange_rate.quote_decimals.into())
310 .unwrap(),
311 )
312 .unwrap();
313
314 to_amount.checked_add(spill_surplus).unwrap()
315 };
316
317 if effective_to_amount < min_expected_amount {
319 msg!(
320 "effective_to_amount, min_expected_amount: {:?}, {:?}",
321 effective_to_amount,
322 min_expected_amount,
323 );
324 return Err(ErrorCode::SlippageExceeded.into());
325 }
326
327 Ok(())
328}
329
330#[derive(Accounts)]
331pub struct InitAccount<'info> {
332 #[account(mut)]
333 open_orders: AccountInfo<'info>,
334 #[account(signer)]
335 authority: AccountInfo<'info>,
336 market: AccountInfo<'info>,
337 dex_program: AccountInfo<'info>,
338 rent: AccountInfo<'info>,
339}
340
341impl<'info> From<&mut InitAccount<'info>> for dex::InitOpenOrders<'info> {
342 fn from(accs: &mut InitAccount<'info>) -> dex::InitOpenOrders<'info> {
343 dex::InitOpenOrders {
344 open_orders: accs.open_orders.clone(),
345 authority: accs.authority.clone(),
346 market: accs.market.clone(),
347 rent: accs.rent.clone(),
348 }
349 }
350}
351
352#[derive(Accounts)]
353pub struct CloseAccount<'info> {
354 #[account(mut)]
355 open_orders: AccountInfo<'info>,
356 #[account(signer)]
357 authority: AccountInfo<'info>,
358 #[account(mut)]
359 destination: AccountInfo<'info>,
360 market: AccountInfo<'info>,
361 dex_program: AccountInfo<'info>,
362}
363
364impl<'info> From<&mut CloseAccount<'info>> for dex::CloseOpenOrders<'info> {
365 fn from(accs: &mut CloseAccount<'info>) -> dex::CloseOpenOrders<'info> {
366 dex::CloseOpenOrders {
367 open_orders: accs.open_orders.clone(),
368 authority: accs.authority.clone(),
369 destination: accs.destination.clone(),
370 market: accs.market.clone(),
371 }
372 }
373}
374
375#[derive(Accounts)]
379pub struct Swap<'info> {
380 pub market: MarketAccounts<'info>,
381 #[account(signer)]
382 pub authority: AccountInfo<'info>,
383 #[account(mut, constraint = pc_wallet.key != &empty::ID)]
384 pub pc_wallet: AccountInfo<'info>,
385 pub dex_program: AccountInfo<'info>,
387 pub token_program: AccountInfo<'info>,
388 pub rent: AccountInfo<'info>,
390}
391
392impl<'info> From<&Swap<'info>> for OrderbookClient<'info> {
393 fn from(accounts: &Swap<'info>) -> OrderbookClient<'info> {
394 OrderbookClient {
395 market: accounts.market.clone(),
396 authority: accounts.authority.clone(),
397 pc_wallet: accounts.pc_wallet.clone(),
398 dex_program: accounts.dex_program.clone(),
399 token_program: accounts.token_program.clone(),
400 rent: accounts.rent.clone(),
401 }
402 }
403}
404
405#[derive(Accounts)]
410pub struct SwapTransitive<'info> {
411 pub from: MarketAccounts<'info>,
412 pub to: MarketAccounts<'info>,
413 #[account(signer)]
415 pub authority: AccountInfo<'info>,
416 #[account(mut, constraint = pc_wallet.key != &empty::ID)]
417 pub pc_wallet: AccountInfo<'info>,
418 pub dex_program: AccountInfo<'info>,
420 pub token_program: AccountInfo<'info>,
421 pub rent: AccountInfo<'info>,
423}
424
425impl<'info> SwapTransitive<'info> {
426 fn orderbook_from(&self) -> OrderbookClient<'info> {
427 OrderbookClient {
428 market: self.from.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 fn orderbook_to(&self) -> OrderbookClient<'info> {
437 OrderbookClient {
438 market: self.to.clone(),
439 authority: self.authority.clone(),
440 pc_wallet: self.pc_wallet.clone(),
441 dex_program: self.dex_program.clone(),
442 token_program: self.token_program.clone(),
443 rent: self.rent.clone(),
444 }
445 }
446}
447
448#[derive(Clone)]
450struct OrderbookClient<'info> {
451 market: MarketAccounts<'info>,
452 authority: AccountInfo<'info>,
453 pc_wallet: AccountInfo<'info>,
454 dex_program: AccountInfo<'info>,
455 token_program: AccountInfo<'info>,
456 rent: AccountInfo<'info>,
457}
458
459impl<'info> OrderbookClient<'info> {
460 fn sell(
466 &self,
467 base_amount: u64,
468 srm_msrm_discount: Option<AccountInfo<'info>>,
469 ) -> ProgramResult {
470 let limit_price = 1;
471 msg!("sell 0");
472 let max_coin_qty = {
473 let market = MarketState::load(&self.market.market, &dex::ID)?;
475 coin_lots(&market, base_amount)
476 };
477 msg!("sell 1 max_coin_qty {:?} limit_price {:?}",max_coin_qty,limit_price);
478 let max_native_pc_qty = u64::MAX;
479 msg!("sell 111 max_native_pc_qty {:?} ",max_native_pc_qty);
480 self.order_cpi(
481 limit_price,
482 max_coin_qty,
483 max_native_pc_qty,
484 Side::Ask,
485 srm_msrm_discount,
486 )
487 }
488
489 fn buy(
495 &self,
496 quote_amount: u64,
497 srm_msrm_discount: Option<AccountInfo<'info>>,
498 ) -> ProgramResult {
499 let limit_price = u64::MAX;
500 let max_coin_qty = u64::MAX;
501 let max_native_pc_qty = quote_amount;
502 msg!("buy 1 quote_amount {:?}",quote_amount);
503 msg!("buy 2 limit_price {:?} max_coin_qty {:?} max_native_pc_qty {:?} ",limit_price,max_coin_qty,max_native_pc_qty);
504 self.order_cpi(
505 limit_price,
506 max_coin_qty,
507 max_native_pc_qty,
508 Side::Bid,
509 srm_msrm_discount,
510 )
511 }
512
513 fn order_cpi(
522 &self,
523 limit_price: u64,
524 max_coin_qty: u64,
525 max_native_pc_qty: u64,
526 side: Side,
527 srm_msrm_discount: Option<AccountInfo<'info>>,
528 ) -> ProgramResult {
529 let client_order_id = 0;
531 let limit = 65535;
535 msg!("order_cpi 1");
536 let mut ctx = CpiContext::new(self.dex_program.clone(), self.clone().into());
537 if let Some(srm_msrm_discount) = srm_msrm_discount {
538 ctx = ctx.with_remaining_accounts(vec![srm_msrm_discount]);
539 }
540 msg!("order_cpi 2");
541 dex::new_order_v3(
542 ctx,
543 side.into(),
544 NonZeroU64::new(limit_price).unwrap(),
545 NonZeroU64::new(max_coin_qty).unwrap(),
546 NonZeroU64::new(max_native_pc_qty).unwrap(),
547 SelfTradeBehavior::DecrementTake,
548 OrderType::ImmediateOrCancel,
549 client_order_id,
550 limit,
551 )
552 }
553
554 fn settle(&self, referral: Option<AccountInfo<'info>>) -> ProgramResult {
555 let settle_accs = dex::SettleFunds {
556 market: self.market.market.clone(),
557 open_orders: self.market.open_orders.clone(),
558 open_orders_authority: self.authority.clone(),
559 coin_vault: self.market.coin_vault.clone(),
560 pc_vault: self.market.pc_vault.clone(),
561 coin_wallet: self.market.coin_wallet.clone(),
562 pc_wallet: self.pc_wallet.clone(),
563 vault_signer: self.market.vault_signer.clone(),
564 token_program: self.token_program.clone(),
565 };
566 let mut ctx = CpiContext::new(self.dex_program.clone(), settle_accs);
567 if let Some(referral) = referral {
568 ctx = ctx.with_remaining_accounts(vec![referral]);
569 }
570 dex::settle_funds(ctx)
571 }
572}
573
574impl<'info> From<OrderbookClient<'info>> for dex::NewOrderV3<'info> {
575 fn from(c: OrderbookClient<'info>) -> dex::NewOrderV3<'info> {
576 dex::NewOrderV3 {
577 market: c.market.market.clone(),
578 open_orders: c.market.open_orders.clone(),
579 request_queue: c.market.request_queue.clone(),
580 event_queue: c.market.event_queue.clone(),
581 market_bids: c.market.bids.clone(),
582 market_asks: c.market.asks.clone(),
583 order_payer_token_account: c.market.order_payer_token_account.clone(),
584 open_orders_authority: c.authority.clone(),
585 coin_vault: c.market.coin_vault.clone(),
586 pc_vault: c.market.pc_vault.clone(),
587 token_program: c.token_program.clone(),
588 rent: c.rent.clone(),
589 }
590 }
591}
592
593fn coin_lots(market: &MarketState, size: u64) -> u64 {
595 size.checked_div(market.coin_lot_size).unwrap()
596}
597
598#[derive(Accounts, Clone)]
601pub struct MarketAccounts<'info> {
602 #[account(mut)]
603 pub market: AccountInfo<'info>,
604 #[account(mut)]
605 pub open_orders: AccountInfo<'info>,
606 #[account(mut)]
607 pub request_queue: AccountInfo<'info>,
608 #[account(mut)]
609 pub event_queue: AccountInfo<'info>,
610 #[account(mut)]
611 pub bids: AccountInfo<'info>,
612 #[account(mut)]
613 pub asks: AccountInfo<'info>,
614 #[account(mut, constraint = order_payer_token_account.key != &empty::ID)]
619 pub order_payer_token_account: AccountInfo<'info>,
620 #[account(mut)]
623 pub coin_vault: AccountInfo<'info>,
624 #[account(mut)]
627 pub pc_vault: AccountInfo<'info>,
628 pub vault_signer: AccountInfo<'info>,
630 #[account(mut, constraint = coin_wallet.key != &empty::ID)]
632 pub coin_wallet: AccountInfo<'info>,
633}
634
635#[derive(AnchorSerialize, AnchorDeserialize)]
636pub enum Side {
637 Bid,
638 Ask,
639}
640
641impl From<Side> for SerumSide {
642 fn from(side: Side) -> SerumSide {
643 match side {
644 Side::Bid => SerumSide::Bid,
645 Side::Ask => SerumSide::Ask,
646 }
647 }
648}
649
650fn is_valid_swap(ctx: &Context<Swap>) -> Result<()> {
653 _is_valid_swap(&ctx.accounts.market.coin_wallet, &ctx.accounts.pc_wallet)
654}
655
656fn is_valid_swap_transitive(ctx: &Context<SwapTransitive>) -> Result<()> {
657 _is_valid_swap(&ctx.accounts.from.coin_wallet, &ctx.accounts.to.coin_wallet)
658}
659
660fn _is_valid_swap<'info>(from: &AccountInfo<'info>, to: &AccountInfo<'info>) -> Result<()> {
662 let from_token_mint = token::accessor::mint(from)?;
663 let to_token_mint = token::accessor::mint(to)?;
664 if from_token_mint == to_token_mint {
665 return Err(ErrorCode::SwapTokensCannotMatch.into());
666 }
667 Ok(())
668}
669
670#[event]
673pub struct DidSwap {
674 pub given_amount: u64,
676 pub min_exchange_rate: ExchangeRate,
680 pub from_amount: u64,
682 pub to_amount: u64,
684 pub quote_amount: u64,
687 pub spill_amount: u64,
691 pub from_mint: Pubkey,
693 pub to_mint: Pubkey,
695 pub quote_mint: Pubkey,
698 pub authority: Pubkey,
700}
701
702#[derive(AnchorSerialize, AnchorDeserialize)]
704pub struct ExchangeRate {
705 pub rate: u64,
709 pub from_decimals: u8,
711 pub quote_decimals: u8,
714 pub strict: bool,
733}
734
735#[error]
736pub enum ErrorCode {
737 #[msg("The tokens being swapped must have different mints")]
738 SwapTokensCannotMatch,
739 #[msg("Slippage tolerance exceeded")]
740 SlippageExceeded,
741 #[msg("No tokens received when swapping")]
742 ZeroSwap,
743}