1use crate::exchange_instruction::*;
4use crate::exchange_state::*;
5use crate::faucet;
6use log::*;
7use num_derive::{FromPrimitive, ToPrimitive};
8use serde_derive::Serialize;
9use solana_metrics::inc_new_counter_info;
10use solana_sdk::{
11 account::{ReadableAccount, WritableAccount},
12 decode_error::DecodeError,
13 instruction::InstructionError,
14 keyed_account::KeyedAccount,
15 process_instruction::InvokeContext,
16 program_utils::limited_deserialize,
17 pubkey::Pubkey,
18};
19use std::cmp;
20use thiserror::Error;
21
22#[derive(Error, Debug, Serialize, Clone, PartialEq, FromPrimitive, ToPrimitive)]
23pub enum ExchangeError {
24 #[error("Signer does not own account")]
25 SignerDoesNotOwnAccount,
26 #[error("Signer does not own order")]
27 SignerDoesNotOwnOrder,
28 #[error("The From account balance is too low")]
29 FromAccountBalanceTooLow,
30 #[error("Attmept operation on mismatched tokens")]
31 TokenMismatch,
32 #[error("From trade balance is too low")]
33 FromTradeBalanceTooLow,
34 #[error("Serialization failed")]
35 SerializeFailed,
36}
37impl<T> DecodeError<T> for ExchangeError {
38 fn type_of() -> &'static str {
39 "ExchangeError"
40 }
41}
42
43pub struct ExchangeProcessor {}
44
45impl ExchangeProcessor {
46 #[allow(clippy::needless_pass_by_value)]
47 fn map_to_invalid_arg(err: std::boxed::Box<bincode::ErrorKind>) -> InstructionError {
48 warn!("Deserialize failed, not a valid state: {:?}", err);
49 InstructionError::InvalidArgument
50 }
51
52 fn is_account_unallocated(data: &[u8]) -> Result<(), InstructionError> {
53 let state: ExchangeState = bincode::deserialize(data).map_err(Self::map_to_invalid_arg)?;
54 if let ExchangeState::Unallocated = state {
55 Ok(())
56 } else {
57 error!("New account is already in use");
58 Err(InstructionError::InvalidAccountData)
59 }
60 }
61
62 fn deserialize_account(data: &[u8]) -> Result<TokenAccountInfo, InstructionError> {
63 let state: ExchangeState = bincode::deserialize(data).map_err(Self::map_to_invalid_arg)?;
64 if let ExchangeState::Account(account) = state {
65 Ok(account)
66 } else {
67 error!("Not a valid account");
68 Err(InstructionError::InvalidAccountData)
69 }
70 }
71
72 fn deserialize_order(data: &[u8]) -> Result<OrderInfo, InstructionError> {
73 let state: ExchangeState = bincode::deserialize(data).map_err(Self::map_to_invalid_arg)?;
74 if let ExchangeState::Trade(info) = state {
75 Ok(info)
76 } else {
77 error!("Not a valid trade");
78 Err(InstructionError::InvalidAccountData)
79 }
80 }
81
82 fn serialize(state: &ExchangeState, data: &mut [u8]) -> Result<(), InstructionError> {
83 let writer = std::io::BufWriter::new(data);
84 match bincode::serialize_into(writer, state) {
85 Ok(_) => Ok(()),
86 Err(e) => {
87 error!("Serialize failed: {:?}", e);
88 Err(ExchangeError::SerializeFailed.into())
89 }
90 }
91 }
92
93 fn trade_to_token_account(trade: &OrderInfo) -> TokenAccountInfo {
94 let token = match trade.side {
97 OrderSide::Ask => trade.pair.Quote,
98 OrderSide::Bid => trade.pair.Base,
99 };
100
101 let mut account = TokenAccountInfo::default().owner(&trade.owner);
102 account.tokens[token] = trade.tokens_settled;
103 account
104 }
105
106 fn calculate_swap(
107 scaler: u64,
108 to_trade: &mut OrderInfo,
109 from_trade: &mut OrderInfo,
110 profit_account: &mut TokenAccountInfo,
111 ) -> Result<(), InstructionError> {
112 if to_trade.tokens == 0 || from_trade.tokens == 0 {
113 error!("Inactive Trade, balance is zero");
114 return Err(InstructionError::InvalidArgument);
115 }
116 if to_trade.price == 0 || from_trade.price == 0 {
117 error!("Inactive Trade, price is zero");
118 return Err(InstructionError::InvalidArgument);
119 }
120
121 trace!("tt {} ft {}", to_trade.tokens, from_trade.tokens);
124 trace!("tp {} fp {}", to_trade.price, from_trade.price);
125
126 let max_to_secondary = to_trade.tokens * to_trade.price / scaler;
127 let max_to_primary = from_trade.tokens * scaler / from_trade.price;
128
129 trace!("mtp {} mts {}", max_to_primary, max_to_secondary);
130
131 let max_primary = cmp::min(max_to_primary, to_trade.tokens);
132 let max_secondary = cmp::min(max_to_secondary, from_trade.tokens);
133
134 trace!("mp {} ms {}", max_primary, max_secondary);
135
136 let primary_tokens = if max_secondary < max_primary {
137 max_secondary * scaler / from_trade.price
138 } else {
139 max_primary
140 };
141 let secondary_tokens = if max_secondary < max_primary {
142 max_secondary
143 } else {
144 max_primary * to_trade.price / scaler
145 };
146
147 if primary_tokens == 0 || secondary_tokens == 0 {
148 error!("Trade quantities to low to be fulfilled");
149 return Err(InstructionError::InvalidArgument);
150 }
151
152 trace!("pt {} st {}", primary_tokens, secondary_tokens);
153
154 let primary_cost = cmp::max(primary_tokens, secondary_tokens * scaler / to_trade.price);
155 let secondary_cost = cmp::max(secondary_tokens, primary_tokens * from_trade.price / scaler);
156
157 trace!("pc {} sc {}", primary_cost, secondary_cost);
158
159 let primary_profit = primary_cost - primary_tokens;
160 let secondary_profit = secondary_cost - secondary_tokens;
161
162 trace!("pp {} sp {}", primary_profit, secondary_profit);
163
164 let primary_token = to_trade.pair.Base;
165 let secondary_token = from_trade.pair.Quote;
166
167 if to_trade.tokens < primary_cost {
170 error!("Not enough tokens in to account");
171 return Err(InstructionError::InvalidArgument);
172 }
173 if from_trade.tokens < secondary_cost {
174 error!("Not enough tokens in from account");
175 return Err(InstructionError::InvalidArgument);
176 }
177 to_trade.tokens -= primary_cost;
178 to_trade.tokens_settled += secondary_tokens;
179 from_trade.tokens -= secondary_cost;
180 from_trade.tokens_settled += primary_tokens;
181
182 profit_account.tokens[primary_token] += primary_profit;
183 profit_account.tokens[secondary_token] += secondary_profit;
184
185 Ok(())
186 }
187
188 fn do_account_request(keyed_accounts: &[KeyedAccount]) -> Result<(), InstructionError> {
189 const OWNER_INDEX: usize = 0;
190 const NEW_ACCOUNT_INDEX: usize = 1;
191
192 if keyed_accounts.len() < 2 {
193 error!("Not enough accounts");
194 return Err(InstructionError::InvalidArgument);
195 }
196 Self::is_account_unallocated(&keyed_accounts[NEW_ACCOUNT_INDEX].try_account_ref()?.data())?;
197 Self::serialize(
198 &ExchangeState::Account(
199 TokenAccountInfo::default()
200 .owner(&keyed_accounts[OWNER_INDEX].unsigned_key())
201 .tokens(100_000, 100_000, 100_000, 100_000),
202 ),
203 &mut keyed_accounts[NEW_ACCOUNT_INDEX]
204 .try_account_ref_mut()?
205 .data_as_mut_slice(),
206 )
207 }
208
209 fn do_transfer_request(
210 keyed_accounts: &[KeyedAccount],
211 token: Token,
212 tokens: u64,
213 ) -> Result<(), InstructionError> {
214 const OWNER_INDEX: usize = 0;
215 const TO_ACCOUNT_INDEX: usize = 1;
216 const FROM_ACCOUNT_INDEX: usize = 2;
217
218 if keyed_accounts.len() < 3 {
219 error!("Not enough accounts");
220 return Err(InstructionError::InvalidArgument);
221 }
222
223 let mut to_account =
224 Self::deserialize_account(&keyed_accounts[TO_ACCOUNT_INDEX].try_account_ref()?.data())?;
225
226 if &faucet::id() == keyed_accounts[FROM_ACCOUNT_INDEX].unsigned_key() {
227 to_account.tokens[token] += tokens;
228 } else {
229 let state: ExchangeState =
230 bincode::deserialize(&keyed_accounts[FROM_ACCOUNT_INDEX].try_account_ref()?.data())
231 .map_err(Self::map_to_invalid_arg)?;
232 match state {
233 ExchangeState::Account(mut from_account) => {
234 if &from_account.owner != keyed_accounts[OWNER_INDEX].unsigned_key() {
235 error!("Signer does not own from account");
236 return Err(ExchangeError::SignerDoesNotOwnAccount.into());
237 }
238
239 if from_account.tokens[token] < tokens {
240 error!("From account balance too low");
241 return Err(ExchangeError::FromAccountBalanceTooLow.into());
242 }
243
244 from_account.tokens[token] -= tokens;
245 to_account.tokens[token] += tokens;
246
247 Self::serialize(
248 &ExchangeState::Account(from_account),
249 &mut keyed_accounts[FROM_ACCOUNT_INDEX]
250 .try_account_ref_mut()?
251 .data_as_mut_slice(),
252 )?;
253 }
254 ExchangeState::Trade(mut from_trade) => {
255 if &from_trade.owner != keyed_accounts[OWNER_INDEX].unsigned_key() {
256 error!("Signer does not own from account");
257 return Err(ExchangeError::SignerDoesNotOwnAccount.into());
258 }
259
260 let from_token = match from_trade.side {
261 OrderSide::Ask => from_trade.pair.Quote,
262 OrderSide::Bid => from_trade.pair.Base,
263 };
264 if token != from_token {
265 error!("Trade to transfer from does not hold correct token");
266 return Err(ExchangeError::TokenMismatch.into());
267 }
268
269 if from_trade.tokens_settled < tokens {
270 error!("From trade balance too low");
271 return Err(ExchangeError::FromTradeBalanceTooLow.into());
272 }
273
274 from_trade.tokens_settled -= tokens;
275 to_account.tokens[token] += tokens;
276
277 Self::serialize(
278 &ExchangeState::Trade(from_trade),
279 &mut keyed_accounts[FROM_ACCOUNT_INDEX]
280 .try_account_ref_mut()?
281 .data_as_mut_slice(),
282 )?;
283 }
284 _ => {
285 error!("Not a valid from account for transfer");
286 return Err(InstructionError::InvalidArgument);
287 }
288 }
289 }
290
291 Self::serialize(
292 &ExchangeState::Account(to_account),
293 &mut keyed_accounts[TO_ACCOUNT_INDEX]
294 .try_account_ref_mut()?
295 .data_as_mut_slice(),
296 )
297 }
298
299 fn do_order_request(
300 keyed_accounts: &[KeyedAccount],
301 info: &OrderRequestInfo,
302 ) -> Result<(), InstructionError> {
303 const OWNER_INDEX: usize = 0;
304 const ORDER_INDEX: usize = 1;
305 const ACCOUNT_INDEX: usize = 2;
306
307 if keyed_accounts.len() < 3 {
308 error!("Not enough accounts");
309 return Err(InstructionError::InvalidArgument);
310 }
311
312 Self::is_account_unallocated(&keyed_accounts[ORDER_INDEX].try_account_ref()?.data())?;
313
314 let mut account = Self::deserialize_account(
315 &keyed_accounts[ACCOUNT_INDEX].try_account_ref_mut()?.data(),
316 )?;
317
318 if &account.owner != keyed_accounts[OWNER_INDEX].unsigned_key() {
319 error!("Signer does not own account");
320 return Err(ExchangeError::SignerDoesNotOwnAccount.into());
321 }
322 let from_token = match info.side {
323 OrderSide::Ask => info.pair.Base,
324 OrderSide::Bid => info.pair.Quote,
325 };
326 if account.tokens[from_token] < info.tokens {
327 error!("From token balance is too low");
328 return Err(ExchangeError::FromAccountBalanceTooLow.into());
329 }
330
331 if let Err(e) = check_trade(info.side, info.tokens, info.price) {
332 bincode::serialize(&e).unwrap();
333 }
334
335 account.tokens[from_token] -= info.tokens;
337
338 inc_new_counter_info!("exchange_processor-trades", 1);
339
340 Self::serialize(
341 &ExchangeState::Trade(OrderInfo {
342 owner: *keyed_accounts[OWNER_INDEX].unsigned_key(),
343 side: info.side,
344 pair: info.pair,
345 tokens: info.tokens,
346 price: info.price,
347 tokens_settled: 0,
348 }),
349 &mut keyed_accounts[ORDER_INDEX]
350 .try_account_ref_mut()?
351 .data_as_mut_slice(),
352 )?;
353 Self::serialize(
354 &ExchangeState::Account(account),
355 &mut keyed_accounts[ACCOUNT_INDEX]
356 .try_account_ref_mut()?
357 .data_as_mut_slice(),
358 )
359 }
360
361 fn do_order_cancellation(keyed_accounts: &[KeyedAccount]) -> Result<(), InstructionError> {
362 const OWNER_INDEX: usize = 0;
363 const ORDER_INDEX: usize = 1;
364
365 if keyed_accounts.len() < 2 {
366 error!("Not enough accounts");
367 return Err(InstructionError::InvalidArgument);
368 }
369
370 let order =
371 Self::deserialize_order(&keyed_accounts[ORDER_INDEX].try_account_ref()?.data())?;
372
373 if &order.owner != keyed_accounts[OWNER_INDEX].unsigned_key() {
374 error!("Signer does not own order");
375 return Err(ExchangeError::SignerDoesNotOwnOrder.into());
376 }
377
378 let token = match order.side {
379 OrderSide::Ask => order.pair.Base,
380 OrderSide::Bid => order.pair.Quote,
381 };
382
383 let mut account = TokenAccountInfo::default().owner(&order.owner);
384 account.tokens[token] = order.tokens;
385 account.tokens[token] += order.tokens_settled;
386
387 Self::serialize(
389 &ExchangeState::Account(account),
390 &mut keyed_accounts[ORDER_INDEX]
391 .try_account_ref_mut()?
392 .data_as_mut_slice(),
393 )
394 }
395
396 fn do_swap_request(keyed_accounts: &[KeyedAccount]) -> Result<(), InstructionError> {
397 const TO_ORDER_INDEX: usize = 1;
398 const FROM_ORDER_INDEX: usize = 2;
399 const PROFIT_ACCOUNT_INDEX: usize = 3;
400
401 if keyed_accounts.len() < 4 {
402 error!("Not enough accounts");
403 return Err(InstructionError::InvalidArgument);
404 }
405
406 let mut to_order =
407 Self::deserialize_order(&keyed_accounts[TO_ORDER_INDEX].try_account_ref()?.data())?;
408 let mut from_order =
409 Self::deserialize_order(&keyed_accounts[FROM_ORDER_INDEX].try_account_ref()?.data())?;
410 let mut profit_account = Self::deserialize_account(
411 &keyed_accounts[PROFIT_ACCOUNT_INDEX]
412 .try_account_ref()?
413 .data(),
414 )?;
415
416 if to_order.side != OrderSide::Ask {
417 error!("To trade is not a To");
418 return Err(InstructionError::InvalidArgument);
419 }
420 if from_order.side != OrderSide::Bid {
421 error!("From trade is not a From");
422 return Err(InstructionError::InvalidArgument);
423 }
424 if to_order.pair != from_order.pair {
425 error!("Mismatched token pairs");
426 return Err(InstructionError::InvalidArgument);
427 }
428 if to_order.side == from_order.side {
429 error!("Matching trade sides");
430 return Err(InstructionError::InvalidArgument);
431 }
432
433 if let Err(e) =
434 Self::calculate_swap(SCALER, &mut to_order, &mut from_order, &mut profit_account)
435 {
436 error!(
437 "Swap calculation failed from {} for {} to {} for {}",
438 from_order.tokens, from_order.price, to_order.tokens, to_order.price,
439 );
440 return Err(e);
441 }
442
443 inc_new_counter_info!("exchange_processor-swaps", 1);
444
445 if to_order.tokens == 0 {
446 Self::serialize(
448 &ExchangeState::Account(Self::trade_to_token_account(&from_order)),
449 &mut keyed_accounts[TO_ORDER_INDEX]
450 .try_account_ref_mut()?
451 .data_as_mut_slice(),
452 )?;
453 } else {
454 Self::serialize(
455 &ExchangeState::Trade(to_order),
456 &mut keyed_accounts[TO_ORDER_INDEX]
457 .try_account_ref_mut()?
458 .data_as_mut_slice(),
459 )?;
460 }
461
462 if from_order.tokens == 0 {
463 Self::serialize(
465 &ExchangeState::Account(Self::trade_to_token_account(&from_order)),
466 &mut keyed_accounts[FROM_ORDER_INDEX]
467 .try_account_ref_mut()?
468 .data_as_mut_slice(),
469 )?;
470 } else {
471 Self::serialize(
472 &ExchangeState::Trade(from_order),
473 &mut keyed_accounts[FROM_ORDER_INDEX]
474 .try_account_ref_mut()?
475 .data_as_mut_slice(),
476 )?;
477 }
478
479 Self::serialize(
480 &ExchangeState::Account(profit_account),
481 &mut keyed_accounts[PROFIT_ACCOUNT_INDEX]
482 .try_account_ref_mut()?
483 .data_as_mut_slice(),
484 )
485 }
486}
487
488pub fn process_instruction(
489 _program_id: &Pubkey,
490 keyed_accounts: &[KeyedAccount],
491 data: &[u8],
492 _invoke_context: &mut dyn InvokeContext,
493) -> Result<(), InstructionError> {
494 solana_logger::setup();
495
496 match limited_deserialize::<ExchangeInstruction>(data)? {
497 ExchangeInstruction::AccountRequest => {
498 ExchangeProcessor::do_account_request(keyed_accounts)
499 }
500 ExchangeInstruction::TransferRequest(token, tokens) => {
501 ExchangeProcessor::do_transfer_request(keyed_accounts, token, tokens)
502 }
503 ExchangeInstruction::OrderRequest(info) => {
504 ExchangeProcessor::do_order_request(keyed_accounts, &info)
505 }
506 ExchangeInstruction::OrderCancellation => {
507 ExchangeProcessor::do_order_cancellation(keyed_accounts)
508 }
509 ExchangeInstruction::SwapRequest => ExchangeProcessor::do_swap_request(keyed_accounts),
510 }
511}
512
513#[cfg(test)]
514mod test {
515 use super::*;
516 use crate::{exchange_instruction, id};
517 use solana_runtime::bank::Bank;
518 use solana_runtime::bank_client::BankClient;
519 use solana_sdk::client::SyncClient;
520 use solana_sdk::genesis_config::create_genesis_config;
521 use solana_sdk::message::Message;
522 use solana_sdk::signature::{Keypair, Signer};
523 use solana_sdk::system_instruction;
524 use std::mem;
525
526 #[allow(clippy::too_many_arguments)]
527 fn try_calc(
528 scaler: u64,
529 primary_tokens: u64,
530 primary_price: u64,
531 secondary_tokens: u64,
532 secondary_price: u64,
533 primary_tokens_expect: u64,
534 secondary_tokens_expect: u64,
535 primary_tokens_settled_expect: u64,
536 secondary_tokens_settled_expect: u64,
537 profit_account_tokens: Tokens,
538 ) -> Result<(), InstructionError> {
539 trace!(
540 "Swap {} for {} to {} for {}",
541 primary_tokens,
542 primary_price,
543 secondary_tokens,
544 secondary_price,
545 );
546 let mut to_trade = OrderInfo::default();
547 let mut from_trade = OrderInfo::default().side(OrderSide::Bid);
548 let mut profit_account = TokenAccountInfo::default();
549
550 to_trade.tokens = primary_tokens;
551 to_trade.price = primary_price;
552 from_trade.tokens = secondary_tokens;
553 from_trade.price = secondary_price;
554 ExchangeProcessor::calculate_swap(
555 scaler,
556 &mut to_trade,
557 &mut from_trade,
558 &mut profit_account,
559 )?;
560
561 trace!(
562 "{:?} {:?} {:?} {:?}\n{:?}\n{:?}\n{:?}\n{:?}",
563 to_trade.tokens,
564 primary_tokens_expect,
565 from_trade.tokens,
566 secondary_tokens_expect,
567 primary_tokens_settled_expect,
568 secondary_tokens_settled_expect,
569 profit_account.tokens,
570 profit_account_tokens
571 );
572
573 assert_eq!(to_trade.tokens, primary_tokens_expect);
574 assert_eq!(from_trade.tokens, secondary_tokens_expect);
575 assert_eq!(to_trade.tokens_settled, primary_tokens_settled_expect);
576 assert_eq!(from_trade.tokens_settled, secondary_tokens_settled_expect);
577 assert_eq!(profit_account.tokens, profit_account_tokens);
578 Ok(())
579 }
580
581 #[test]
582 #[rustfmt::skip]
583 fn test_calculate_swap() {
584 solana_logger::setup();
585
586 try_calc(1, 50, 2, 50, 1, 0, 0, 50, 50, Tokens::new( 0, 0, 0, 0)).unwrap_err();
587 try_calc(1, 50, 1, 0, 1, 0, 0, 50, 50, Tokens::new( 0, 0, 0, 0)).unwrap_err();
588 try_calc(1, 0, 1, 50, 1, 0, 0, 50, 50, Tokens::new( 0, 0, 0, 0)).unwrap_err();
589 try_calc(1, 50, 1, 50, 0, 0, 0, 50, 50, Tokens::new( 0, 0, 0, 0)).unwrap_err();
590 try_calc(1, 50, 0, 50, 1, 0, 0, 50, 50, Tokens::new( 0, 0, 0, 0)).unwrap_err();
591 try_calc(1, 1, 2, 2, 3, 1, 2, 0, 0, Tokens::new( 0, 0, 0, 0)).unwrap_err();
592
593 try_calc(1, 50, 1, 50, 1, 0, 0, 50, 50, Tokens::new( 0, 0, 0, 0)).unwrap();
594 try_calc(1, 1, 2, 3, 3, 0, 0, 2, 1, Tokens::new( 0, 1, 0, 0)).unwrap();
595 try_calc(1, 2, 2, 3, 3, 1, 0, 2, 1, Tokens::new( 0, 1, 0, 0)).unwrap();
596 try_calc(1, 3, 2, 3, 3, 2, 0, 2, 1, Tokens::new( 0, 1, 0, 0)).unwrap();
597 try_calc(1, 3, 2, 6, 3, 1, 0, 4, 2, Tokens::new( 0, 2, 0, 0)).unwrap();
598 try_calc(1000, 1, 2000, 3, 3000, 0, 0, 2, 1, Tokens::new( 0, 1, 0, 0)).unwrap();
599 try_calc(1, 3, 2, 7, 3, 1, 1, 4, 2, Tokens::new( 0, 2, 0, 0)).unwrap();
600 try_calc(1000, 3000, 333, 1000, 500, 0, 1,999, 1998, Tokens::new(1002, 0, 0, 0)).unwrap();
601 try_calc(1000, 50, 100, 50, 101, 0,45, 5, 49, Tokens::new( 1, 0, 0, 0)).unwrap();
602 }
603
604 fn create_bank(lamports: u64) -> (Bank, Keypair) {
605 let (genesis_config, mint_keypair) = create_genesis_config(lamports);
606 let mut bank = Bank::new(&genesis_config);
607 bank.add_builtin("exchange_program", id(), process_instruction);
608 (bank, mint_keypair)
609 }
610
611 fn create_client(bank: Bank, mint_keypair: Keypair) -> (BankClient, Keypair) {
612 let owner = Keypair::new();
613 let bank_client = BankClient::new(bank);
614 bank_client
615 .transfer_and_confirm(42, &mint_keypair, &owner.pubkey())
616 .unwrap();
617
618 (bank_client, owner)
619 }
620
621 fn create_account(client: &BankClient, owner: &Keypair) -> Pubkey {
622 let new = Keypair::new();
623
624 let instruction = system_instruction::create_account(
625 &owner.pubkey(),
626 &new.pubkey(),
627 1,
628 mem::size_of::<ExchangeState>() as u64,
629 &id(),
630 );
631
632 client
633 .send_and_confirm_message(
634 &[owner, &new],
635 Message::new(&[instruction], Some(&owner.pubkey())),
636 )
637 .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));
638 new.pubkey()
639 }
640
641 fn create_token_account(client: &BankClient, owner: &Keypair) -> Pubkey {
642 let new = create_account(&client, &owner);
643 let instruction = exchange_instruction::account_request(&owner.pubkey(), &new);
644 client
645 .send_and_confirm_instruction(owner, instruction)
646 .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));
647 new
648 }
649
650 fn transfer(client: &BankClient, owner: &Keypair, to: &Pubkey, token: Token, tokens: u64) {
651 let instruction = exchange_instruction::transfer_request(
652 &owner.pubkey(),
653 to,
654 &faucet::id(),
655 token,
656 tokens,
657 );
658 client
659 .send_and_confirm_instruction(owner, instruction)
660 .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));
661 }
662
663 fn trade(
664 client: &BankClient,
665 owner: &Keypair,
666 side: OrderSide,
667 pair: AssetPair,
668 from_token: Token,
669 src_tokens: u64,
670 trade_tokens: u64,
671 price: u64,
672 ) -> (Pubkey, Pubkey) {
673 let trade = create_account(&client, &owner);
674 let src = create_token_account(&client, &owner);
675 transfer(&client, &owner, &src, from_token, src_tokens);
676
677 let instruction = exchange_instruction::trade_request(
678 &owner.pubkey(),
679 &trade,
680 side,
681 pair,
682 trade_tokens,
683 price,
684 &src,
685 );
686 client
687 .send_and_confirm_instruction(owner, instruction)
688 .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));
689 (trade, src)
690 }
691
692 #[test]
693 fn test_exchange_new_account() {
694 solana_logger::setup();
695 let (bank, mint_keypair) = create_bank(10_000);
696 let (client, owner) = create_client(bank, mint_keypair);
697
698 let new = create_token_account(&client, &owner);
699 let new_account_data = client.get_account_data(&new).unwrap().unwrap();
700
701 assert_eq!(
704 TokenAccountInfo::default()
705 .owner(&owner.pubkey())
706 .tokens(100_000, 100_000, 100_000, 100_000),
707 ExchangeProcessor::deserialize_account(&new_account_data).unwrap()
708 );
709 }
710
711 #[test]
712 fn test_exchange_new_account_not_unallocated() {
713 solana_logger::setup();
714 let (bank, mint_keypair) = create_bank(10_000);
715 let (client, owner) = create_client(bank, mint_keypair);
716
717 let new = create_token_account(&client, &owner);
718 let instruction = exchange_instruction::account_request(&owner.pubkey(), &new);
719 client
720 .send_and_confirm_instruction(&owner, instruction)
721 .expect_err(&format!("{}:{}", line!(), file!()));
722 }
723
724 #[test]
725 fn test_exchange_new_transfer_request() {
726 solana_logger::setup();
727 let (bank, mint_keypair) = create_bank(10_000);
728 let (client, owner) = create_client(bank, mint_keypair);
729
730 let new = create_token_account(&client, &owner);
731
732 let instruction = exchange_instruction::transfer_request(
733 &owner.pubkey(),
734 &new,
735 &faucet::id(),
736 Token::A,
737 42,
738 );
739 client
740 .send_and_confirm_instruction(&owner, instruction)
741 .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));
742
743 let new_account_data = client.get_account_data(&new).unwrap().unwrap();
744
745 assert_eq!(
748 TokenAccountInfo::default()
749 .owner(&owner.pubkey())
750 .tokens(100_042, 100_000, 100_000, 100_000),
751 ExchangeProcessor::deserialize_account(&new_account_data).unwrap()
752 );
753 }
754
755 #[test]
756 fn test_exchange_new_trade_request() {
757 solana_logger::setup();
758 let (bank, mint_keypair) = create_bank(10_000);
759 let (client, owner) = create_client(bank, mint_keypair);
760
761 let (trade, src) = trade(
762 &client,
763 &owner,
764 OrderSide::Ask,
765 AssetPair::default(),
766 Token::A,
767 42,
768 2,
769 1000,
770 );
771
772 let trade_account_data = client.get_account_data(&trade).unwrap().unwrap();
773 let src_account_data = client.get_account_data(&src).unwrap().unwrap();
774
775 assert_eq!(
778 OrderInfo {
779 owner: owner.pubkey(),
780 side: OrderSide::Ask,
781 pair: AssetPair::default(),
782 tokens: 2,
783 price: 1000,
784 tokens_settled: 0
785 },
786 ExchangeProcessor::deserialize_order(&trade_account_data).unwrap()
787 );
788 assert_eq!(
789 TokenAccountInfo::default()
790 .owner(&owner.pubkey())
791 .tokens(100_040, 100_000, 100_000, 100_000),
792 ExchangeProcessor::deserialize_account(&src_account_data).unwrap()
793 );
794 }
795
796 #[test]
797 fn test_exchange_new_swap_request() {
798 solana_logger::setup();
799 let (bank, mint_keypair) = create_bank(10_000);
800 let (client, owner) = create_client(bank, mint_keypair);
801
802 let profit = create_token_account(&client, &owner);
803 let (to_trade, _) = trade(
804 &client,
805 &owner,
806 OrderSide::Ask,
807 AssetPair::default(),
808 Token::A,
809 2,
810 2,
811 2000,
812 );
813 let (from_trade, _) = trade(
814 &client,
815 &owner,
816 OrderSide::Bid,
817 AssetPair::default(),
818 Token::B,
819 3,
820 3,
821 3000,
822 );
823
824 let instruction =
825 exchange_instruction::swap_request(&owner.pubkey(), &to_trade, &from_trade, &profit);
826 client
827 .send_and_confirm_instruction(&owner, instruction)
828 .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));
829
830 let to_trade_account_data = client.get_account_data(&to_trade).unwrap().unwrap();
831 let from_trade_account_data = client.get_account_data(&from_trade).unwrap().unwrap();
832 let profit_account_data = client.get_account_data(&profit).unwrap().unwrap();
833
834 assert_eq!(
837 OrderInfo {
838 owner: owner.pubkey(),
839 side: OrderSide::Ask,
840 pair: AssetPair::default(),
841 tokens: 1,
842 price: 2000,
843 tokens_settled: 2,
844 },
845 ExchangeProcessor::deserialize_order(&to_trade_account_data).unwrap()
846 );
847
848 assert_eq!(
849 TokenAccountInfo::default()
850 .owner(&owner.pubkey())
851 .tokens(1, 0, 0, 0),
852 ExchangeProcessor::deserialize_account(&from_trade_account_data).unwrap()
853 );
854
855 assert_eq!(
856 TokenAccountInfo::default()
857 .owner(&owner.pubkey())
858 .tokens(100_000, 100_001, 100_000, 100_000),
859 ExchangeProcessor::deserialize_account(&profit_account_data).unwrap()
860 );
861 }
862
863 #[test]
864 fn test_exchange_trade_to_token_account() {
865 solana_logger::setup();
866 let (bank, mint_keypair) = create_bank(10_000);
867 let (client, owner) = create_client(bank, mint_keypair);
868
869 let profit = create_token_account(&client, &owner);
870 let (to_trade, _) = trade(
871 &client,
872 &owner,
873 OrderSide::Ask,
874 AssetPair::default(),
875 Token::A,
876 3,
877 3,
878 2000,
879 );
880 let (from_trade, _) = trade(
881 &client,
882 &owner,
883 OrderSide::Bid,
884 AssetPair::default(),
885 Token::B,
886 3,
887 3,
888 3000,
889 );
890
891 let instruction =
892 exchange_instruction::swap_request(&owner.pubkey(), &to_trade, &from_trade, &profit);
893 client
894 .send_and_confirm_instruction(&owner, instruction)
895 .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));
896
897 let new = create_token_account(&client, &owner);
898
899 let instruction =
900 exchange_instruction::transfer_request(&owner.pubkey(), &new, &to_trade, Token::B, 1);
901 client
902 .send_and_confirm_instruction(&owner, instruction)
903 .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));
904
905 let instruction =
906 exchange_instruction::transfer_request(&owner.pubkey(), &new, &from_trade, Token::A, 1);
907 client
908 .send_and_confirm_instruction(&owner, instruction)
909 .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));
910
911 let new_account_data = client.get_account_data(&new).unwrap().unwrap();
912
913 assert_eq!(
916 TokenAccountInfo::default()
917 .owner(&owner.pubkey())
918 .tokens(100_001, 100_001, 100_000, 100_000),
919 ExchangeProcessor::deserialize_account(&new_account_data).unwrap()
920 );
921 }
922}