solana-exchange-program 1.8.16

Solana Exchange program
Documentation
//! Config processor

use {
    crate::{exchange_instruction::*, exchange_state::*, faucet},
    log::*,
    num_derive::{FromPrimitive, ToPrimitive},
    serde_derive::Serialize,
    solana_metrics::inc_new_counter_info,
    solana_sdk::{
        account::{ReadableAccount, WritableAccount},
        decode_error::DecodeError,
        instruction::InstructionError,
        keyed_account::KeyedAccount,
        process_instruction::InvokeContext,
        program_utils::limited_deserialize,
        pubkey::Pubkey,
    },
    std::cmp,
    thiserror::Error,
};

#[derive(Error, Debug, Serialize, Clone, PartialEq, FromPrimitive, ToPrimitive)]
pub enum ExchangeError {
    #[error("Signer does not own account")]
    SignerDoesNotOwnAccount,
    #[error("Signer does not own order")]
    SignerDoesNotOwnOrder,
    #[error("The From account balance is too low")]
    FromAccountBalanceTooLow,
    #[error("Attmept operation on mismatched tokens")]
    TokenMismatch,
    #[error("From trade balance is too low")]
    FromTradeBalanceTooLow,
    #[error("Serialization failed")]
    SerializeFailed,
}
impl<T> DecodeError<T> for ExchangeError {
    fn type_of() -> &'static str {
        "ExchangeError"
    }
}

pub struct ExchangeProcessor {}

impl ExchangeProcessor {
    #[allow(clippy::needless_pass_by_value)]
    fn map_to_invalid_arg(err: std::boxed::Box<bincode::ErrorKind>) -> InstructionError {
        warn!("Deserialize failed, not a valid state: {:?}", err);
        InstructionError::InvalidArgument
    }

    fn is_account_unallocated(data: &[u8]) -> Result<(), InstructionError> {
        let state: ExchangeState = bincode::deserialize(data).map_err(Self::map_to_invalid_arg)?;
        if let ExchangeState::Unallocated = state {
            Ok(())
        } else {
            error!("New account is already in use");
            Err(InstructionError::InvalidAccountData)
        }
    }

    fn deserialize_account(data: &[u8]) -> Result<TokenAccountInfo, InstructionError> {
        let state: ExchangeState = bincode::deserialize(data).map_err(Self::map_to_invalid_arg)?;
        if let ExchangeState::Account(account) = state {
            Ok(account)
        } else {
            error!("Not a valid account");
            Err(InstructionError::InvalidAccountData)
        }
    }

    fn deserialize_order(data: &[u8]) -> Result<OrderInfo, InstructionError> {
        let state: ExchangeState = bincode::deserialize(data).map_err(Self::map_to_invalid_arg)?;
        if let ExchangeState::Trade(info) = state {
            Ok(info)
        } else {
            error!("Not a valid trade");
            Err(InstructionError::InvalidAccountData)
        }
    }

    fn serialize(state: &ExchangeState, data: &mut [u8]) -> Result<(), InstructionError> {
        let writer = std::io::BufWriter::new(data);
        match bincode::serialize_into(writer, state) {
            Ok(_) => Ok(()),
            Err(e) => {
                error!("Serialize failed: {:?}", e);
                Err(ExchangeError::SerializeFailed.into())
            }
        }
    }

    fn trade_to_token_account(trade: &OrderInfo) -> TokenAccountInfo {
        // Turn trade order into token account

        let token = match trade.side {
            OrderSide::Ask => trade.pair.Quote,
            OrderSide::Bid => trade.pair.Base,
        };

        let mut account = TokenAccountInfo::default().owner(&trade.owner);
        account.tokens[token] = trade.tokens_settled;
        account
    }

    fn calculate_swap(
        scaler: u64,
        to_trade: &mut OrderInfo,
        from_trade: &mut OrderInfo,
        profit_account: &mut TokenAccountInfo,
    ) -> Result<(), InstructionError> {
        if to_trade.tokens == 0 || from_trade.tokens == 0 {
            error!("Inactive Trade, balance is zero");
            return Err(InstructionError::InvalidArgument);
        }
        if to_trade.price == 0 || from_trade.price == 0 {
            error!("Inactive Trade, price is zero");
            return Err(InstructionError::InvalidArgument);
        }

        // Calc swap

        trace!("tt {} ft {}", to_trade.tokens, from_trade.tokens);
        trace!("tp {} fp {}", to_trade.price, from_trade.price);

        let max_to_secondary = to_trade.tokens * to_trade.price / scaler;
        let max_to_primary = from_trade.tokens * scaler / from_trade.price;

        trace!("mtp {} mts {}", max_to_primary, max_to_secondary);

        let max_primary = cmp::min(max_to_primary, to_trade.tokens);
        let max_secondary = cmp::min(max_to_secondary, from_trade.tokens);

        trace!("mp {} ms {}", max_primary, max_secondary);

        let primary_tokens = if max_secondary < max_primary {
            max_secondary * scaler / from_trade.price
        } else {
            max_primary
        };
        let secondary_tokens = if max_secondary < max_primary {
            max_secondary
        } else {
            max_primary * to_trade.price / scaler
        };

        if primary_tokens == 0 || secondary_tokens == 0 {
            error!("Trade quantities to low to be fulfilled");
            return Err(InstructionError::InvalidArgument);
        }

        trace!("pt {} st {}", primary_tokens, secondary_tokens);

        let primary_cost = cmp::max(primary_tokens, secondary_tokens * scaler / to_trade.price);
        let secondary_cost = cmp::max(secondary_tokens, primary_tokens * from_trade.price / scaler);

        trace!("pc {} sc {}", primary_cost, secondary_cost);

        let primary_profit = primary_cost - primary_tokens;
        let secondary_profit = secondary_cost - secondary_tokens;

        trace!("pp {} sp {}", primary_profit, secondary_profit);

        let primary_token = to_trade.pair.Base;
        let secondary_token = from_trade.pair.Quote;

        // Update tokens

        if to_trade.tokens < primary_cost {
            error!("Not enough tokens in to account");
            return Err(InstructionError::InvalidArgument);
        }
        if from_trade.tokens < secondary_cost {
            error!("Not enough tokens in from account");
            return Err(InstructionError::InvalidArgument);
        }
        to_trade.tokens -= primary_cost;
        to_trade.tokens_settled += secondary_tokens;
        from_trade.tokens -= secondary_cost;
        from_trade.tokens_settled += primary_tokens;

        profit_account.tokens[primary_token] += primary_profit;
        profit_account.tokens[secondary_token] += secondary_profit;

        Ok(())
    }

    fn do_account_request(keyed_accounts: &[KeyedAccount]) -> Result<(), InstructionError> {
        const OWNER_INDEX: usize = 0;
        const NEW_ACCOUNT_INDEX: usize = 1;

        if keyed_accounts.len() < 2 {
            error!("Not enough accounts");
            return Err(InstructionError::InvalidArgument);
        }
        Self::is_account_unallocated(keyed_accounts[NEW_ACCOUNT_INDEX].try_account_ref()?.data())?;
        Self::serialize(
            &ExchangeState::Account(
                TokenAccountInfo::default()
                    .owner(keyed_accounts[OWNER_INDEX].unsigned_key())
                    .tokens(100_000, 100_000, 100_000, 100_000),
            ),
            &mut keyed_accounts[NEW_ACCOUNT_INDEX]
                .try_account_ref_mut()?
                .data_as_mut_slice(),
        )
    }

    fn do_transfer_request(
        keyed_accounts: &[KeyedAccount],
        token: Token,
        tokens: u64,
    ) -> Result<(), InstructionError> {
        const OWNER_INDEX: usize = 0;
        const TO_ACCOUNT_INDEX: usize = 1;
        const FROM_ACCOUNT_INDEX: usize = 2;

        if keyed_accounts.len() < 3 {
            error!("Not enough accounts");
            return Err(InstructionError::InvalidArgument);
        }

        let mut to_account =
            Self::deserialize_account(keyed_accounts[TO_ACCOUNT_INDEX].try_account_ref()?.data())?;

        if &faucet::id() == keyed_accounts[FROM_ACCOUNT_INDEX].unsigned_key() {
            to_account.tokens[token] += tokens;
        } else {
            let state: ExchangeState =
                bincode::deserialize(keyed_accounts[FROM_ACCOUNT_INDEX].try_account_ref()?.data())
                    .map_err(Self::map_to_invalid_arg)?;
            match state {
                ExchangeState::Account(mut from_account) => {
                    if &from_account.owner != keyed_accounts[OWNER_INDEX].unsigned_key() {
                        error!("Signer does not own from account");
                        return Err(ExchangeError::SignerDoesNotOwnAccount.into());
                    }

                    if from_account.tokens[token] < tokens {
                        error!("From account balance too low");
                        return Err(ExchangeError::FromAccountBalanceTooLow.into());
                    }

                    from_account.tokens[token] -= tokens;
                    to_account.tokens[token] += tokens;

                    Self::serialize(
                        &ExchangeState::Account(from_account),
                        &mut keyed_accounts[FROM_ACCOUNT_INDEX]
                            .try_account_ref_mut()?
                            .data_as_mut_slice(),
                    )?;
                }
                ExchangeState::Trade(mut from_trade) => {
                    if &from_trade.owner != keyed_accounts[OWNER_INDEX].unsigned_key() {
                        error!("Signer does not own from account");
                        return Err(ExchangeError::SignerDoesNotOwnAccount.into());
                    }

                    let from_token = match from_trade.side {
                        OrderSide::Ask => from_trade.pair.Quote,
                        OrderSide::Bid => from_trade.pair.Base,
                    };
                    if token != from_token {
                        error!("Trade to transfer from does not hold correct token");
                        return Err(ExchangeError::TokenMismatch.into());
                    }

                    if from_trade.tokens_settled < tokens {
                        error!("From trade balance too low");
                        return Err(ExchangeError::FromTradeBalanceTooLow.into());
                    }

                    from_trade.tokens_settled -= tokens;
                    to_account.tokens[token] += tokens;

                    Self::serialize(
                        &ExchangeState::Trade(from_trade),
                        &mut keyed_accounts[FROM_ACCOUNT_INDEX]
                            .try_account_ref_mut()?
                            .data_as_mut_slice(),
                    )?;
                }
                _ => {
                    error!("Not a valid from account for transfer");
                    return Err(InstructionError::InvalidArgument);
                }
            }
        }

        Self::serialize(
            &ExchangeState::Account(to_account),
            &mut keyed_accounts[TO_ACCOUNT_INDEX]
                .try_account_ref_mut()?
                .data_as_mut_slice(),
        )
    }

    fn do_order_request(
        keyed_accounts: &[KeyedAccount],
        info: &OrderRequestInfo,
    ) -> Result<(), InstructionError> {
        const OWNER_INDEX: usize = 0;
        const ORDER_INDEX: usize = 1;
        const ACCOUNT_INDEX: usize = 2;

        if keyed_accounts.len() < 3 {
            error!("Not enough accounts");
            return Err(InstructionError::InvalidArgument);
        }

        Self::is_account_unallocated(keyed_accounts[ORDER_INDEX].try_account_ref()?.data())?;

        let mut account =
            Self::deserialize_account(keyed_accounts[ACCOUNT_INDEX].try_account_ref_mut()?.data())?;

        if &account.owner != keyed_accounts[OWNER_INDEX].unsigned_key() {
            error!("Signer does not own account");
            return Err(ExchangeError::SignerDoesNotOwnAccount.into());
        }
        let from_token = match info.side {
            OrderSide::Ask => info.pair.Base,
            OrderSide::Bid => info.pair.Quote,
        };
        if account.tokens[from_token] < info.tokens {
            error!("From token balance is too low");
            return Err(ExchangeError::FromAccountBalanceTooLow.into());
        }

        if let Err(e) = check_trade(info.side, info.tokens, info.price) {
            bincode::serialize(&e).unwrap();
        }

        // Trade holds the tokens in escrow
        account.tokens[from_token] -= info.tokens;

        inc_new_counter_info!("exchange_processor-trades", 1);

        Self::serialize(
            &ExchangeState::Trade(OrderInfo {
                owner: *keyed_accounts[OWNER_INDEX].unsigned_key(),
                side: info.side,
                pair: info.pair,
                tokens: info.tokens,
                price: info.price,
                tokens_settled: 0,
            }),
            &mut keyed_accounts[ORDER_INDEX]
                .try_account_ref_mut()?
                .data_as_mut_slice(),
        )?;
        Self::serialize(
            &ExchangeState::Account(account),
            &mut keyed_accounts[ACCOUNT_INDEX]
                .try_account_ref_mut()?
                .data_as_mut_slice(),
        )
    }

    fn do_order_cancellation(keyed_accounts: &[KeyedAccount]) -> Result<(), InstructionError> {
        const OWNER_INDEX: usize = 0;
        const ORDER_INDEX: usize = 1;

        if keyed_accounts.len() < 2 {
            error!("Not enough accounts");
            return Err(InstructionError::InvalidArgument);
        }

        let order = Self::deserialize_order(keyed_accounts[ORDER_INDEX].try_account_ref()?.data())?;

        if &order.owner != keyed_accounts[OWNER_INDEX].unsigned_key() {
            error!("Signer does not own order");
            return Err(ExchangeError::SignerDoesNotOwnOrder.into());
        }

        let token = match order.side {
            OrderSide::Ask => order.pair.Base,
            OrderSide::Bid => order.pair.Quote,
        };

        let mut account = TokenAccountInfo::default().owner(&order.owner);
        account.tokens[token] = order.tokens;
        account.tokens[token] += order.tokens_settled;

        // Turn trade order into a token account
        Self::serialize(
            &ExchangeState::Account(account),
            &mut keyed_accounts[ORDER_INDEX]
                .try_account_ref_mut()?
                .data_as_mut_slice(),
        )
    }

    fn do_swap_request(keyed_accounts: &[KeyedAccount]) -> Result<(), InstructionError> {
        const TO_ORDER_INDEX: usize = 1;
        const FROM_ORDER_INDEX: usize = 2;
        const PROFIT_ACCOUNT_INDEX: usize = 3;

        if keyed_accounts.len() < 4 {
            error!("Not enough accounts");
            return Err(InstructionError::InvalidArgument);
        }

        let mut to_order =
            Self::deserialize_order(keyed_accounts[TO_ORDER_INDEX].try_account_ref()?.data())?;
        let mut from_order =
            Self::deserialize_order(keyed_accounts[FROM_ORDER_INDEX].try_account_ref()?.data())?;
        let mut profit_account = Self::deserialize_account(
            keyed_accounts[PROFIT_ACCOUNT_INDEX]
                .try_account_ref()?
                .data(),
        )?;

        if to_order.side != OrderSide::Ask {
            error!("To trade is not a To");
            return Err(InstructionError::InvalidArgument);
        }
        if from_order.side != OrderSide::Bid {
            error!("From trade is not a From");
            return Err(InstructionError::InvalidArgument);
        }
        if to_order.pair != from_order.pair {
            error!("Mismatched token pairs");
            return Err(InstructionError::InvalidArgument);
        }
        if to_order.side == from_order.side {
            error!("Matching trade sides");
            return Err(InstructionError::InvalidArgument);
        }

        if let Err(e) =
            Self::calculate_swap(SCALER, &mut to_order, &mut from_order, &mut profit_account)
        {
            error!(
                "Swap calculation failed from {} for {} to {} for {}",
                from_order.tokens, from_order.price, to_order.tokens, to_order.price,
            );
            return Err(e);
        }

        inc_new_counter_info!("exchange_processor-swaps", 1);

        if to_order.tokens == 0 {
            // Turn into token account
            Self::serialize(
                &ExchangeState::Account(Self::trade_to_token_account(&from_order)),
                &mut keyed_accounts[TO_ORDER_INDEX]
                    .try_account_ref_mut()?
                    .data_as_mut_slice(),
            )?;
        } else {
            Self::serialize(
                &ExchangeState::Trade(to_order),
                &mut keyed_accounts[TO_ORDER_INDEX]
                    .try_account_ref_mut()?
                    .data_as_mut_slice(),
            )?;
        }

        if from_order.tokens == 0 {
            // Turn into token account
            Self::serialize(
                &ExchangeState::Account(Self::trade_to_token_account(&from_order)),
                &mut keyed_accounts[FROM_ORDER_INDEX]
                    .try_account_ref_mut()?
                    .data_as_mut_slice(),
            )?;
        } else {
            Self::serialize(
                &ExchangeState::Trade(from_order),
                &mut keyed_accounts[FROM_ORDER_INDEX]
                    .try_account_ref_mut()?
                    .data_as_mut_slice(),
            )?;
        }

        Self::serialize(
            &ExchangeState::Account(profit_account),
            &mut keyed_accounts[PROFIT_ACCOUNT_INDEX]
                .try_account_ref_mut()?
                .data_as_mut_slice(),
        )
    }
}

pub fn process_instruction(
    _program_id: &Pubkey,
    data: &[u8],
    invoke_context: &mut dyn InvokeContext,
) -> Result<(), InstructionError> {
    let keyed_accounts = invoke_context.get_keyed_accounts()?;

    solana_logger::setup();
    match limited_deserialize::<ExchangeInstruction>(data)? {
        ExchangeInstruction::AccountRequest => {
            ExchangeProcessor::do_account_request(keyed_accounts)
        }
        ExchangeInstruction::TransferRequest(token, tokens) => {
            ExchangeProcessor::do_transfer_request(keyed_accounts, token, tokens)
        }
        ExchangeInstruction::OrderRequest(info) => {
            ExchangeProcessor::do_order_request(keyed_accounts, &info)
        }
        ExchangeInstruction::OrderCancellation => {
            ExchangeProcessor::do_order_cancellation(keyed_accounts)
        }
        ExchangeInstruction::SwapRequest => ExchangeProcessor::do_swap_request(keyed_accounts),
    }
}

#[cfg(test)]
mod test {
    use {
        super::*,
        crate::{exchange_instruction, id},
        solana_runtime::{bank::Bank, bank_client::BankClient},
        solana_sdk::{
            client::SyncClient,
            genesis_config::create_genesis_config,
            message::Message,
            signature::{Keypair, Signer},
            system_instruction,
        },
        std::mem,
    };

    #[allow(clippy::too_many_arguments)]
    fn try_calc(
        scaler: u64,
        primary_tokens: u64,
        primary_price: u64,
        secondary_tokens: u64,
        secondary_price: u64,
        primary_tokens_expect: u64,
        secondary_tokens_expect: u64,
        primary_tokens_settled_expect: u64,
        secondary_tokens_settled_expect: u64,
        profit_account_tokens: Tokens,
    ) -> Result<(), InstructionError> {
        trace!(
            "Swap {} for {} to {} for {}",
            primary_tokens,
            primary_price,
            secondary_tokens,
            secondary_price,
        );
        let mut to_trade = OrderInfo::default();
        let mut from_trade = OrderInfo::default().side(OrderSide::Bid);
        let mut profit_account = TokenAccountInfo::default();

        to_trade.tokens = primary_tokens;
        to_trade.price = primary_price;
        from_trade.tokens = secondary_tokens;
        from_trade.price = secondary_price;
        ExchangeProcessor::calculate_swap(
            scaler,
            &mut to_trade,
            &mut from_trade,
            &mut profit_account,
        )?;

        trace!(
            "{:?} {:?} {:?} {:?}\n{:?}\n{:?}\n{:?}\n{:?}",
            to_trade.tokens,
            primary_tokens_expect,
            from_trade.tokens,
            secondary_tokens_expect,
            primary_tokens_settled_expect,
            secondary_tokens_settled_expect,
            profit_account.tokens,
            profit_account_tokens
        );

        assert_eq!(to_trade.tokens, primary_tokens_expect);
        assert_eq!(from_trade.tokens, secondary_tokens_expect);
        assert_eq!(to_trade.tokens_settled, primary_tokens_settled_expect);
        assert_eq!(from_trade.tokens_settled, secondary_tokens_settled_expect);
        assert_eq!(profit_account.tokens, profit_account_tokens);
        Ok(())
    }

    #[test]
    #[rustfmt::skip]
    fn test_calculate_swap() {
        solana_logger::setup();

        try_calc(1,     50,     2,   50,    1,  0, 0, 50,   50, Tokens::new(   0, 0, 0, 0)).unwrap_err();
        try_calc(1,     50,     1,    0,    1,  0, 0, 50,   50, Tokens::new(   0, 0, 0, 0)).unwrap_err();
        try_calc(1,      0,     1,   50,    1,  0, 0, 50,   50, Tokens::new(   0, 0, 0, 0)).unwrap_err();
        try_calc(1,     50,     1,   50,    0,  0, 0, 50,   50, Tokens::new(   0, 0, 0, 0)).unwrap_err();
        try_calc(1,     50,     0,   50,    1,  0, 0, 50,   50, Tokens::new(   0, 0, 0, 0)).unwrap_err();
        try_calc(1,       1,    2,    2,    3,  1, 2,  0,    0, Tokens::new(   0, 0, 0, 0)).unwrap_err();

        try_calc(1,     50,     1,   50,    1,  0, 0, 50,   50, Tokens::new(   0, 0, 0, 0)).unwrap();
        try_calc(1,       1,    2,    3,    3,  0, 0,  2,    1, Tokens::new(   0, 1, 0, 0)).unwrap();
        try_calc(1,       2,    2,    3,    3,  1, 0,  2,    1, Tokens::new(   0, 1, 0, 0)).unwrap();
        try_calc(1,       3,    2,    3,    3,  2, 0,  2,    1, Tokens::new(   0, 1, 0, 0)).unwrap();
        try_calc(1,       3,    2,    6,    3,  1, 0,  4,    2, Tokens::new(   0, 2, 0, 0)).unwrap();
        try_calc(1000,    1, 2000,    3, 3000,  0, 0,  2,    1, Tokens::new(   0, 1, 0, 0)).unwrap();
        try_calc(1,       3,    2,    7,    3,  1, 1,  4,    2, Tokens::new(   0, 2, 0, 0)).unwrap();
        try_calc(1000, 3000,  333, 1000,  500,  0, 1,999, 1998, Tokens::new(1002, 0, 0, 0)).unwrap();
        try_calc(1000,   50,  100,   50,  101,  0,45,  5,   49, Tokens::new(   1, 0, 0, 0)).unwrap();
    }

    fn create_bank(lamports: u64) -> (Bank, Keypair) {
        let (genesis_config, mint_keypair) = create_genesis_config(lamports);
        let mut bank = Bank::new(&genesis_config);
        bank.add_builtin("exchange_program", id(), process_instruction);
        (bank, mint_keypair)
    }

    fn create_client(bank: Bank, mint_keypair: Keypair) -> (BankClient, Keypair) {
        let owner = Keypair::new();
        let bank_client = BankClient::new(bank);
        bank_client
            .transfer_and_confirm(42, &mint_keypair, &owner.pubkey())
            .unwrap();

        (bank_client, owner)
    }

    fn create_account(client: &BankClient, owner: &Keypair) -> Pubkey {
        let new = Keypair::new();

        let instruction = system_instruction::create_account(
            &owner.pubkey(),
            &new.pubkey(),
            1,
            mem::size_of::<ExchangeState>() as u64,
            &id(),
        );

        client
            .send_and_confirm_message(
                &[owner, &new],
                Message::new(&[instruction], Some(&owner.pubkey())),
            )
            .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));
        new.pubkey()
    }

    fn create_token_account(client: &BankClient, owner: &Keypair) -> Pubkey {
        let new = create_account(client, owner);
        let instruction = exchange_instruction::account_request(&owner.pubkey(), &new);
        client
            .send_and_confirm_instruction(owner, instruction)
            .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));
        new
    }

    fn transfer(client: &BankClient, owner: &Keypair, to: &Pubkey, token: Token, tokens: u64) {
        let instruction = exchange_instruction::transfer_request(
            &owner.pubkey(),
            to,
            &faucet::id(),
            token,
            tokens,
        );
        client
            .send_and_confirm_instruction(owner, instruction)
            .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));
    }

    fn trade(
        client: &BankClient,
        owner: &Keypair,
        side: OrderSide,
        pair: AssetPair,
        from_token: Token,
        src_tokens: u64,
        trade_tokens: u64,
        price: u64,
    ) -> (Pubkey, Pubkey) {
        let trade = create_account(client, owner);
        let src = create_token_account(client, owner);
        transfer(client, owner, &src, from_token, src_tokens);

        let instruction = exchange_instruction::trade_request(
            &owner.pubkey(),
            &trade,
            side,
            pair,
            trade_tokens,
            price,
            &src,
        );
        client
            .send_and_confirm_instruction(owner, instruction)
            .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));
        (trade, src)
    }

    #[test]
    fn test_exchange_new_account() {
        solana_logger::setup();
        let (bank, mint_keypair) = create_bank(10_000);
        let (client, owner) = create_client(bank, mint_keypair);

        let new = create_token_account(&client, &owner);
        let new_account_data = client.get_account_data(&new).unwrap().unwrap();

        // Check results

        assert_eq!(
            TokenAccountInfo::default()
                .owner(&owner.pubkey())
                .tokens(100_000, 100_000, 100_000, 100_000),
            ExchangeProcessor::deserialize_account(&new_account_data).unwrap()
        );
    }

    #[test]
    fn test_exchange_new_account_not_unallocated() {
        solana_logger::setup();
        let (bank, mint_keypair) = create_bank(10_000);
        let (client, owner) = create_client(bank, mint_keypair);

        let new = create_token_account(&client, &owner);
        let instruction = exchange_instruction::account_request(&owner.pubkey(), &new);
        client
            .send_and_confirm_instruction(&owner, instruction)
            .expect_err(&format!("{}:{}", line!(), file!()));
    }

    #[test]
    fn test_exchange_new_transfer_request() {
        solana_logger::setup();
        let (bank, mint_keypair) = create_bank(10_000);
        let (client, owner) = create_client(bank, mint_keypair);

        let new = create_token_account(&client, &owner);

        let instruction = exchange_instruction::transfer_request(
            &owner.pubkey(),
            &new,
            &faucet::id(),
            Token::A,
            42,
        );
        client
            .send_and_confirm_instruction(&owner, instruction)
            .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));

        let new_account_data = client.get_account_data(&new).unwrap().unwrap();

        // Check results

        assert_eq!(
            TokenAccountInfo::default()
                .owner(&owner.pubkey())
                .tokens(100_042, 100_000, 100_000, 100_000),
            ExchangeProcessor::deserialize_account(&new_account_data).unwrap()
        );
    }

    #[test]
    fn test_exchange_new_trade_request() {
        solana_logger::setup();
        let (bank, mint_keypair) = create_bank(10_000);
        let (client, owner) = create_client(bank, mint_keypair);

        let (trade, src) = trade(
            &client,
            &owner,
            OrderSide::Ask,
            AssetPair::default(),
            Token::A,
            42,
            2,
            1000,
        );

        let trade_account_data = client.get_account_data(&trade).unwrap().unwrap();
        let src_account_data = client.get_account_data(&src).unwrap().unwrap();

        // check results

        assert_eq!(
            OrderInfo {
                owner: owner.pubkey(),
                side: OrderSide::Ask,
                pair: AssetPair::default(),
                tokens: 2,
                price: 1000,
                tokens_settled: 0
            },
            ExchangeProcessor::deserialize_order(&trade_account_data).unwrap()
        );
        assert_eq!(
            TokenAccountInfo::default()
                .owner(&owner.pubkey())
                .tokens(100_040, 100_000, 100_000, 100_000),
            ExchangeProcessor::deserialize_account(&src_account_data).unwrap()
        );
    }

    #[test]
    fn test_exchange_new_swap_request() {
        solana_logger::setup();
        let (bank, mint_keypair) = create_bank(10_000);
        let (client, owner) = create_client(bank, mint_keypair);

        let profit = create_token_account(&client, &owner);
        let (to_trade, _) = trade(
            &client,
            &owner,
            OrderSide::Ask,
            AssetPair::default(),
            Token::A,
            2,
            2,
            2000,
        );
        let (from_trade, _) = trade(
            &client,
            &owner,
            OrderSide::Bid,
            AssetPair::default(),
            Token::B,
            3,
            3,
            3000,
        );

        let instruction =
            exchange_instruction::swap_request(&owner.pubkey(), &to_trade, &from_trade, &profit);
        client
            .send_and_confirm_instruction(&owner, instruction)
            .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));

        let to_trade_account_data = client.get_account_data(&to_trade).unwrap().unwrap();
        let from_trade_account_data = client.get_account_data(&from_trade).unwrap().unwrap();
        let profit_account_data = client.get_account_data(&profit).unwrap().unwrap();

        // check results

        assert_eq!(
            OrderInfo {
                owner: owner.pubkey(),
                side: OrderSide::Ask,
                pair: AssetPair::default(),
                tokens: 1,
                price: 2000,
                tokens_settled: 2,
            },
            ExchangeProcessor::deserialize_order(&to_trade_account_data).unwrap()
        );

        assert_eq!(
            TokenAccountInfo::default()
                .owner(&owner.pubkey())
                .tokens(1, 0, 0, 0),
            ExchangeProcessor::deserialize_account(&from_trade_account_data).unwrap()
        );

        assert_eq!(
            TokenAccountInfo::default()
                .owner(&owner.pubkey())
                .tokens(100_000, 100_001, 100_000, 100_000),
            ExchangeProcessor::deserialize_account(&profit_account_data).unwrap()
        );
    }

    #[test]
    fn test_exchange_trade_to_token_account() {
        solana_logger::setup();
        let (bank, mint_keypair) = create_bank(10_000);
        let (client, owner) = create_client(bank, mint_keypair);

        let profit = create_token_account(&client, &owner);
        let (to_trade, _) = trade(
            &client,
            &owner,
            OrderSide::Ask,
            AssetPair::default(),
            Token::A,
            3,
            3,
            2000,
        );
        let (from_trade, _) = trade(
            &client,
            &owner,
            OrderSide::Bid,
            AssetPair::default(),
            Token::B,
            3,
            3,
            3000,
        );

        let instruction =
            exchange_instruction::swap_request(&owner.pubkey(), &to_trade, &from_trade, &profit);
        client
            .send_and_confirm_instruction(&owner, instruction)
            .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));

        let new = create_token_account(&client, &owner);

        let instruction =
            exchange_instruction::transfer_request(&owner.pubkey(), &new, &to_trade, Token::B, 1);
        client
            .send_and_confirm_instruction(&owner, instruction)
            .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));

        let instruction =
            exchange_instruction::transfer_request(&owner.pubkey(), &new, &from_trade, Token::A, 1);
        client
            .send_and_confirm_instruction(&owner, instruction)
            .unwrap_or_else(|_| panic!("{}:{}", line!(), file!()));

        let new_account_data = client.get_account_data(&new).unwrap().unwrap();

        // Check results

        assert_eq!(
            TokenAccountInfo::default()
                .owner(&owner.pubkey())
                .tokens(100_001, 100_001, 100_000, 100_000),
            ExchangeProcessor::deserialize_account(&new_account_data).unwrap()
        );
    }
}