Skip to main content

solana_exchange_program/
exchange_processor.rs

1//! Config processor
2
3use 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        // Turn trade order into token account
95
96        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        // Calc swap
122
123        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        // Update tokens
168
169        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        // Trade holds the tokens in escrow
336        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        // Turn trade order into a token account
388        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            // Turn into token account
447            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            // Turn into token account
464            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        // Check results
702
703        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        // Check results
746
747        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        // check results
776
777        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        // check results
835
836        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        // Check results
914
915        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}