diff --git a/bench-exchange/src/bench.rs b/bench-exchange/src/bench.rs index ca5cdff7c6..6d20f55d77 100644 --- a/bench-exchange/src/bench.rs +++ b/bench-exchange/src/bench.rs @@ -96,7 +96,7 @@ where let clients: Vec<_> = clients.into_iter().map(Arc::new).collect(); let client = clients[0].as_ref(); - let total_keys = accounts_in_groups as u64 * 5; + let total_keys = accounts_in_groups as u64 * 4; info!("Generating {:?} keys", total_keys); let mut keypairs = generate_keypairs(total_keys); let trader_signers: Vec<_> = keypairs @@ -111,10 +111,6 @@ where .drain(0..accounts_in_groups) .map(|keypair| keypair.pubkey()) .collect(); - let dst_pubkeys: Vec<_> = keypairs - .drain(0..accounts_in_groups) - .map(|keypair| keypair.pubkey()) - .collect(); let profit_pubkeys: Vec<_> = keypairs .drain(0..accounts_in_groups) .map(|keypair| keypair.pubkey()) @@ -127,8 +123,6 @@ where info!("Create {:?} source token accounts", src_pubkeys.len()); create_token_accounts(client, &trader_signers, &src_pubkeys); - info!("Create {:?} destination token accounts", dst_pubkeys.len()); - create_token_accounts(client, &trader_signers, &dst_pubkeys); info!("Create {:?} profit token accounts", profit_pubkeys.len()); create_token_accounts(client, &swapper_signers, &profit_pubkeys); @@ -216,7 +210,6 @@ where &shared_tx_active_thread_count, &trader_signers, &src_pubkeys, - &dst_pubkeys, trade_delay, batch_size, account_groups, @@ -432,18 +425,16 @@ fn swapper( stats.total += swaps_size as u64; let now = Instant::now(); - let swap_keys = generate_keypairs(swaps_size as u64); let mut to_swap = vec![]; let start = account_group * swaps_size as usize; let end = account_group * swaps_size as usize + batch_size as usize; - for (signer, swap, swap_key, profit) in izip!( + for (signer, swap, profit) in izip!( signers[start..end].iter(), swaps, - swap_keys, profit_pubkeys[start..end].iter(), ) { - to_swap.push((signer, swap_key, swap, profit)); + to_swap.push((signer, swap, profit)); } account_group = (account_group + 1) % account_groups as usize; let duration = now.elapsed(); @@ -471,30 +462,17 @@ fn swapper( let to_swap_txs: Vec<_> = to_swap .par_iter() - .map(|(signer, swap_key, swap, profit)| { + .map(|(signer, swap, profit)| { let s: &Keypair = &signer; let owner = &signer.pubkey(); - let space = mem::size_of::() as u64; Transaction::new_signed_instructions( &[s], - vec![ - system_instruction::create_account( - owner, - &swap_key.pubkey(), - 1, - space, - &id(), - ), - exchange_instruction::swap_request( - owner, - &swap_key.pubkey(), - &swap.0.pubkey, - &swap.1.pubkey, - &swap.0.info.dst_account, - &swap.1.info.dst_account, - &profit, - ), - ], + vec![exchange_instruction::swap_request( + owner, + &swap.0.pubkey, + &swap.1.pubkey, + &profit, + )], blockhash, ) }) @@ -562,7 +540,6 @@ fn trader( shared_tx_active_thread_count: &Arc, signers: &[Arc], srcs: &[Pubkey], - dsts: &[Pubkey], delay: u64, batch_size: usize, account_groups: usize, @@ -593,11 +570,10 @@ fn trader( let start = account_group * batch_size as usize; let end = account_group * batch_size as usize + batch_size as usize; let mut direction = Direction::To; - for (signer, trade, src, dst) in izip!( + for (signer, trade, src) in izip!( signers[start..end].iter(), trade_keys, srcs[start..end].iter(), - dsts[start..end].iter() ) { direction = if direction == Direction::To { Direction::From @@ -611,14 +587,13 @@ fn trader( pair, tokens, price, - src_account: Pubkey::default(), // don't care - dst_account: *dst, + tokens_settled: 0, }; trade_infos.push(TradeInfo { trade_account: trade.pubkey(), order_info, }); - trades.push((signer, trade.pubkey(), direction, src, dst)); + trades.push((signer, trade.pubkey(), direction, src)); } account_group = (account_group + 1) % account_groups as usize; let duration = now.elapsed(); @@ -647,7 +622,7 @@ fn trader( let trades_txs: Vec<_> = chunk .par_iter() - .map(|(signer, trade, direction, src, dst)| { + .map(|(signer, trade, direction, src)| { let s: &Keypair = &signer; let owner = &signer.pubkey(); let space = mem::size_of::() as u64; @@ -656,7 +631,7 @@ fn trader( vec![ system_instruction::create_account(owner, trade, 1, space, &id()), exchange_instruction::trade_request( - owner, trade, *direction, pair, tokens, price, src, dst, + owner, trade, *direction, pair, tokens, price, src, ), ], blockhash, diff --git a/programs/exchange_api/src/exchange_instruction.rs b/programs/exchange_api/src/exchange_instruction.rs index 5ba95b0241..390f1b8e96 100644 --- a/programs/exchange_api/src/exchange_instruction.rs +++ b/programs/exchange_api/src/exchange_instruction.rs @@ -20,9 +20,6 @@ pub struct TradeRequestInfo { /// The price ratio the primary price over the secondary price. The primary price is fixed /// and equal to the variable `SCALER`. pub price: u64, - - /// Token account to deposit tokens on successful swap - pub dst_account: Pubkey, } #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] @@ -40,8 +37,8 @@ pub enum ExchangeInstruction { /// Trade request /// key 0 - Signer - /// key 1 - Account in which to record the swap - /// key 2 - Token account associated with this trade + /// key 1 - Account in which to record the trade order + /// key 2 - Token account to source tokens from TradeRequest(TradeRequestInfo), /// Trade cancellation @@ -51,11 +48,8 @@ pub enum ExchangeInstruction { /// Trade swap request /// key 0 - Signer - /// key 1 - Account in which to record the swap /// key 2 - 'To' trade order /// key 3 - `From` trade order - /// key 4 - Token account associated with the To Trade - /// key 5 - Token account associated with From trade /// key 6 - Token account in which to deposit the brokers profit from the swap. SwapRequest, } @@ -95,7 +89,6 @@ pub fn trade_request( tokens: u64, price: u64, src_account: &Pubkey, - dst_account: &Pubkey, ) -> Instruction { let account_metas = vec![ AccountMeta::new(*owner, true), @@ -109,37 +102,29 @@ pub fn trade_request( pair, tokens, price, - dst_account: *dst_account, }), account_metas, ) } -pub fn trade_cancellation(owner: &Pubkey, trade: &Pubkey, account: &Pubkey) -> Instruction { +pub fn trade_cancellation(owner: &Pubkey, trade: &Pubkey) -> Instruction { let account_metas = vec![ AccountMeta::new(*owner, true), AccountMeta::new(*trade, false), - AccountMeta::new(*account, false), ]; Instruction::new(id(), &ExchangeInstruction::TradeCancellation, account_metas) } pub fn swap_request( owner: &Pubkey, - swap: &Pubkey, to_trade: &Pubkey, from_trade: &Pubkey, - to_trade_account: &Pubkey, - from_trade_account: &Pubkey, profit_account: &Pubkey, ) -> Instruction { let account_metas = vec![ AccountMeta::new(*owner, true), - AccountMeta::new(*swap, false), AccountMeta::new(*to_trade, false), AccountMeta::new(*from_trade, false), - AccountMeta::new(*to_trade_account, false), - AccountMeta::new(*from_trade_account, false), AccountMeta::new(*profit_account, false), ]; Instruction::new(id(), &ExchangeInstruction::SwapRequest, account_metas) diff --git a/programs/exchange_api/src/exchange_processor.rs b/programs/exchange_api/src/exchange_processor.rs index 28c91acc66..903be670ef 100644 --- a/programs/exchange_api/src/exchange_processor.rs +++ b/programs/exchange_api/src/exchange_processor.rs @@ -15,7 +15,7 @@ pub struct ExchangeProcessor {} impl ExchangeProcessor { #[allow(clippy::needless_pass_by_value)] fn map_to_invalid_arg(err: std::boxed::Box) -> InstructionError { - warn!("Deserialze failed: {:?}", err); + warn!("Deserialize failed, not a valid state: {:?}", err); InstructionError::InvalidArgument } @@ -60,13 +60,23 @@ impl ExchangeProcessor { } } + fn trade_to_token_account(trade: &TradeOrderInfo) -> TokenAccountInfo { + // Turn trade order into token account + + let token = match trade.direction { + Direction::To => trade.pair.secondary(), + Direction::From => trade.pair.primary(), + }; + + let mut account = TokenAccountInfo::default().owner(&trade.owner); + account.tokens[token] = trade.tokens_settled; + account + } + fn calculate_swap( scaler: u64, - swap: &mut TradeSwapInfo, to_trade: &mut TradeOrderInfo, from_trade: &mut TradeOrderInfo, - to_trade_account: &mut TokenAccountInfo, - from_trade_account: &mut TokenAccountInfo, profit_account: &mut TokenAccountInfo, ) -> Result<(), InstructionError> { if to_trade.tokens == 0 || from_trade.tokens == 0 { @@ -124,7 +134,7 @@ impl ExchangeProcessor { let primary_token = to_trade.pair.primary(); let secondary_token = from_trade.pair.secondary(); - // Update tokens/accounts + // Update tokens if to_trade.tokens < primary_cost { error!("Not enough tokens in to account"); @@ -135,20 +145,13 @@ impl ExchangeProcessor { Err(InstructionError::InvalidArgument)? } to_trade.tokens -= primary_cost; + to_trade.tokens_settled += secondary_tokens; from_trade.tokens -= secondary_cost; - - to_trade_account.tokens[secondary_token] += secondary_tokens; - from_trade_account.tokens[primary_token] += primary_tokens; + from_trade.tokens_settled += primary_tokens; profit_account.tokens[primary_token] += primary_profit; profit_account.tokens[secondary_token] += secondary_profit; - swap.pair = to_trade.pair; - swap.primary_tokens = primary_cost; - swap.primary_price = to_trade.price; - swap.secondary_tokens = secondary_cost; - swap.secondary_price = from_trade.price; - Ok(()) } @@ -192,26 +195,62 @@ impl ExchangeProcessor { if &id() == keyed_accounts[FROM_ACCOUNT_INDEX].unsigned_key() { to_account.tokens[token] += tokens; } else { - let mut from_account = - Self::deserialize_account(&keyed_accounts[FROM_ACCOUNT_INDEX].account.data)?; + let state: ExchangeState = + bincode::deserialize(&keyed_accounts[FROM_ACCOUNT_INDEX].account.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"); + Err(InstructionError::GenericError)? + } - if &from_account.owner != keyed_accounts[OWNER_INDEX].unsigned_key() { - error!("Signer does not own from account"); - Err(InstructionError::GenericError)? + if from_account.tokens[token] < tokens { + error!("From account balance too low"); + Err(InstructionError::GenericError)? + } + + from_account.tokens[token] -= tokens; + to_account.tokens[token] += tokens; + + Self::serialize( + &ExchangeState::Account(from_account), + &mut keyed_accounts[FROM_ACCOUNT_INDEX].account.data, + )?; + } + ExchangeState::Trade(mut from_trade) => { + if &from_trade.owner != keyed_accounts[OWNER_INDEX].unsigned_key() { + error!("Signer does not own from account"); + Err(InstructionError::GenericError)? + } + + let from_token = match from_trade.direction { + Direction::To => from_trade.pair.secondary(), + Direction::From => from_trade.pair.primary(), + }; + if token != from_token { + error!("Trade to transfer from does not hold correct token"); + Err(InstructionError::GenericError)? + } + + if from_trade.tokens_settled < tokens { + error!("From trade balance too low"); + Err(InstructionError::GenericError)? + } + + from_trade.tokens_settled -= tokens; + to_account.tokens[token] += tokens; + + Self::serialize( + &ExchangeState::Trade(from_trade), + &mut keyed_accounts[FROM_ACCOUNT_INDEX].account.data, + )?; + } + _ => { + error!("Not a valid from account for transfer"); + Err(InstructionError::InvalidArgument)? + } } - - if from_account.tokens[token] < tokens { - error!("From account balance too low"); - Err(InstructionError::GenericError)? - } - - from_account.tokens[token] -= tokens; - to_account.tokens[token] += tokens; - - Self::serialize( - &ExchangeState::Account(from_account), - &mut keyed_accounts[FROM_ACCOUNT_INDEX].account.data, - )?; } Self::serialize( @@ -266,8 +305,7 @@ impl ExchangeProcessor { pair: info.pair, tokens: info.tokens, price: info.price, - src_account: *keyed_accounts[ACCOUNT_INDEX].unsigned_key(), - dst_account: info.dst_account, + tokens_settled: 0, }), &mut keyed_accounts[TRADE_INDEX].account.data, )?; @@ -280,78 +318,51 @@ impl ExchangeProcessor { fn do_trade_cancellation(keyed_accounts: &mut [KeyedAccount]) -> Result<(), InstructionError> { const OWNER_INDEX: usize = 0; const TRADE_INDEX: usize = 1; - const ACCOUNT_INDEX: usize = 2; - if keyed_accounts.len() < 3 { + if keyed_accounts.len() < 2 { error!("Not enough accounts"); Err(InstructionError::InvalidArgument)? } - let mut trade = Self::deserialize_trade(&keyed_accounts[TRADE_INDEX].account.data)?; - let mut account = Self::deserialize_account(&keyed_accounts[ACCOUNT_INDEX].account.data)?; + let trade = Self::deserialize_trade(&keyed_accounts[TRADE_INDEX].account.data)?; if &trade.owner != keyed_accounts[OWNER_INDEX].unsigned_key() { error!("Signer does not own trade"); Err(InstructionError::GenericError)? } - if &account.owner != keyed_accounts[OWNER_INDEX].unsigned_key() { - error!("Signer does not own account"); - Err(InstructionError::GenericError)? - } - let token = match trade.direction { Direction::To => trade.pair.primary(), Direction::From => trade.pair.secondary(), }; - // Outstanding tokens transferred back to account - account.tokens[token] += trade.tokens; - // Trade becomes invalid - trade.tokens = 0; + let mut account = TokenAccountInfo::default().owner(&trade.owner); + account.tokens[token] = trade.tokens; + account.tokens[token] += trade.tokens_settled; - Self::serialize( - &ExchangeState::Trade(trade), - &mut keyed_accounts[TRADE_INDEX].account.data, - )?; + // Turn trade order into a token account Self::serialize( &ExchangeState::Account(account), - &mut keyed_accounts[ACCOUNT_INDEX].account.data, + &mut keyed_accounts[TRADE_INDEX].account.data, ) } fn do_swap_request(keyed_accounts: &mut [KeyedAccount]) -> Result<(), InstructionError> { - const SWAP_ACCOUNT_INDEX: usize = 1; - const TO_TRADE_INDEX: usize = 2; - const FROM_TRADE_INDEX: usize = 3; - const TO_ACCOUNT_INDEX: usize = 4; - const FROM_ACCOUNT_INDEX: usize = 5; - const PROFIT_ACCOUNT_INDEX: usize = 6; + const TO_TRADE_INDEX: usize = 1; + const FROM_TRADE_INDEX: usize = 2; + const PROFIT_ACCOUNT_INDEX: usize = 3; - if keyed_accounts.len() < 7 { + if keyed_accounts.len() < 4 { error!("Not enough accounts"); Err(InstructionError::InvalidArgument)? } - Self::is_account_unallocated(&keyed_accounts[SWAP_ACCOUNT_INDEX].account.data)?; let mut to_trade = Self::deserialize_trade(&keyed_accounts[TO_TRADE_INDEX].account.data)?; let mut from_trade = Self::deserialize_trade(&keyed_accounts[FROM_TRADE_INDEX].account.data)?; - let mut to_trade_account = - Self::deserialize_account(&keyed_accounts[TO_ACCOUNT_INDEX].account.data)?; - let mut from_trade_account = - Self::deserialize_account(&keyed_accounts[FROM_ACCOUNT_INDEX].account.data)?; let mut profit_account = Self::deserialize_account(&keyed_accounts[PROFIT_ACCOUNT_INDEX].account.data)?; - if &to_trade.dst_account != keyed_accounts[TO_ACCOUNT_INDEX].unsigned_key() { - error!("To trade account and to account differ"); - Err(InstructionError::InvalidArgument)? - } - if &from_trade.dst_account != keyed_accounts[FROM_ACCOUNT_INDEX].unsigned_key() { - error!("From trade account and from account differ"); - Err(InstructionError::InvalidArgument)? - } if to_trade.direction != Direction::To { error!("To trade is not a To"); Err(InstructionError::InvalidArgument)? @@ -369,19 +380,9 @@ impl ExchangeProcessor { Err(InstructionError::InvalidArgument)? } - let mut swap = TradeSwapInfo::default(); - swap.to_trade_order = *keyed_accounts[TO_TRADE_INDEX].unsigned_key(); - swap.from_trade_order = *keyed_accounts[FROM_TRADE_INDEX].unsigned_key(); - - if let Err(e) = Self::calculate_swap( - SCALER, - &mut swap, - &mut to_trade, - &mut from_trade, - &mut to_trade_account, - &mut from_trade_account, - &mut profit_account, - ) { + if let Err(e) = + Self::calculate_swap(SCALER, &mut to_trade, &mut from_trade, &mut profit_account) + { error!( "Swap calculation failed from {} for {} to {} for {}", from_trade.tokens, from_trade.price, to_trade.tokens, to_trade.price, @@ -391,26 +392,32 @@ impl ExchangeProcessor { inc_new_counter_info!("exchange_processor-swap", 1); - Self::serialize( - &ExchangeState::Swap(swap), - &mut keyed_accounts[SWAP_ACCOUNT_INDEX].account.data, - )?; - Self::serialize( - &ExchangeState::Trade(to_trade), - &mut keyed_accounts[TO_TRADE_INDEX].account.data, - )?; - Self::serialize( - &ExchangeState::Trade(from_trade), - &mut keyed_accounts[FROM_TRADE_INDEX].account.data, - )?; - Self::serialize( - &ExchangeState::Account(to_trade_account), - &mut keyed_accounts[TO_ACCOUNT_INDEX].account.data, - )?; - Self::serialize( - &ExchangeState::Account(from_trade_account), - &mut keyed_accounts[FROM_ACCOUNT_INDEX].account.data, - )?; + if to_trade.tokens == 0 { + // Turn into token account + Self::serialize( + &ExchangeState::Account(Self::trade_to_token_account(&from_trade)), + &mut keyed_accounts[TO_TRADE_INDEX].account.data, + )?; + } else { + Self::serialize( + &ExchangeState::Trade(to_trade), + &mut keyed_accounts[TO_TRADE_INDEX].account.data, + )?; + } + + if from_trade.tokens == 0 { + // Turn into token account + Self::serialize( + &ExchangeState::Account(Self::trade_to_token_account(&from_trade)), + &mut keyed_accounts[FROM_TRADE_INDEX].account.data, + )?; + } else { + Self::serialize( + &ExchangeState::Trade(from_trade), + &mut keyed_accounts[FROM_TRADE_INDEX].account.data, + )?; + } + Self::serialize( &ExchangeState::Account(profit_account), &mut keyed_accounts[PROFIT_ACCOUNT_INDEX].account.data, @@ -470,8 +477,8 @@ mod test { secondary_price: u64, primary_tokens_expect: u64, secondary_tokens_expect: u64, - primary_account_tokens: Tokens, - secondary_account_tokens: Tokens, + primary_tokens_settled_expect: u64, + secondary_tokens_settled_expect: u64, profit_account_tokens: Tokens, ) -> Result<(), InstructionError> { trace!( @@ -481,11 +488,8 @@ mod test { secondary_tokens, secondary_price, ); - let mut swap = TradeSwapInfo::default(); let mut to_trade = TradeOrderInfo::default(); let mut from_trade = TradeOrderInfo::default().direction(Direction::From); - let mut to_account = TokenAccountInfo::default(); - let mut from_account = TokenAccountInfo::default(); let mut profit_account = TokenAccountInfo::default(); to_trade.tokens = primary_tokens; @@ -494,37 +498,28 @@ mod test { from_trade.price = secondary_price; ExchangeProcessor::calculate_swap( scaler, - &mut swap, &mut to_trade, &mut from_trade, - &mut to_account, - &mut from_account, &mut profit_account, )?; trace!( - "{:?} {:?} {:?} {:?}\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}", + "{:?} {:?} {:?} {:?}\n{:?}\n{:?}\n{:?}\n{:?}", to_trade.tokens, primary_tokens_expect, from_trade.tokens, secondary_tokens_expect, - to_account.tokens, - primary_account_tokens, - from_account.tokens, - secondary_account_tokens, + 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_account.tokens, primary_account_tokens); - assert_eq!(from_account.tokens, secondary_account_tokens); + 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); - assert_eq!(swap.primary_tokens, primary_tokens - to_trade.tokens); - assert_eq!(swap.primary_price, to_trade.price); - assert_eq!(swap.secondary_tokens, secondary_tokens - from_trade.tokens); - assert_eq!(swap.secondary_price, from_trade.price); Ok(()) } @@ -533,22 +528,22 @@ mod test { fn test_calculate_swap() { solana_logger::setup(); - try_calc(1, 50, 2, 50, 1, 0, 0, Tokens::new(0, 50, 0, 0), Tokens::new( 50, 0, 0, 0), Tokens::new( 0, 0, 0, 0)).unwrap_err(); - try_calc(1, 50, 1, 0, 1, 0, 0, Tokens::new(0, 50, 0, 0), Tokens::new( 50, 0, 0, 0), Tokens::new( 0, 0, 0, 0)).unwrap_err(); - try_calc(1, 0, 1, 50, 1, 0, 0, Tokens::new(0, 50, 0, 0), Tokens::new( 50, 0, 0, 0), Tokens::new( 0, 0, 0, 0)).unwrap_err(); - try_calc(1, 50, 1, 50, 0, 0, 0, Tokens::new(0, 50, 0, 0), Tokens::new( 50, 0, 0, 0), Tokens::new( 0, 0, 0, 0)).unwrap_err(); - try_calc(1, 50, 0, 50, 1, 0, 0, Tokens::new(0, 50, 0, 0), Tokens::new( 50, 0, 0, 0), Tokens::new( 0, 0, 0, 0)).unwrap_err(); - try_calc(1, 1, 2, 2, 3, 1, 2, Tokens::new(0, 0, 0, 0), Tokens::new( 0, 0, 0, 0), Tokens::new( 0, 0, 0, 0)).unwrap_err(); + 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, Tokens::new(0, 50, 0, 0), Tokens::new( 50, 0, 0, 0), Tokens::new( 0, 0, 0, 0)).unwrap(); - try_calc(1, 1, 2, 3, 3, 0, 0, Tokens::new(0, 2, 0, 0), Tokens::new( 1, 0, 0, 0), Tokens::new( 0, 1, 0, 0)).unwrap(); - try_calc(1, 2, 2, 3, 3, 1, 0, Tokens::new(0, 2, 0, 0), Tokens::new( 1, 0, 0, 0), Tokens::new( 0, 1, 0, 0)).unwrap(); - try_calc(1, 3, 2, 3, 3, 2, 0, Tokens::new(0, 2, 0, 0), Tokens::new( 1, 0, 0, 0), Tokens::new( 0, 1, 0, 0)).unwrap(); - try_calc(1, 3, 2, 6, 3, 1, 0, Tokens::new(0, 4, 0, 0), Tokens::new( 2, 0, 0, 0), Tokens::new( 0, 2, 0, 0)).unwrap(); - try_calc(1000, 1, 2000, 3, 3000, 0, 0, Tokens::new(0, 2, 0, 0), Tokens::new( 1, 0, 0, 0), Tokens::new( 0, 1, 0, 0)).unwrap(); - try_calc(1, 3, 2, 7, 3, 1, 1, Tokens::new(0, 4, 0, 0), Tokens::new( 2, 0, 0, 0), Tokens::new( 0, 2, 0, 0)).unwrap(); - try_calc(1000, 3000, 333, 1000, 500, 0, 1, Tokens::new(0, 999, 0, 0), Tokens::new(1998, 0, 0, 0), Tokens::new(1002, 0, 0, 0)).unwrap(); - try_calc(1000, 50, 100, 50, 101, 0,45, Tokens::new(0, 5, 0, 0), Tokens::new( 49, 0, 0, 0), Tokens::new( 1, 0, 0, 0)).unwrap(); + 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) { @@ -619,10 +614,9 @@ mod test { src_tokens: u64, trade_tokens: u64, price: u64, - ) -> (Pubkey, Pubkey, Pubkey) { + ) -> (Pubkey, Pubkey) { let trade = create_account(&client, &owner); let src = create_token_account(&client, &owner); - let dst = create_token_account(&client, &owner); transfer(&client, &owner, &src, from_token, src_tokens); let instruction = exchange_instruction::trade_request( @@ -633,21 +627,11 @@ mod test { trade_tokens, price, &src, - &dst, ); client .send_instruction(owner, instruction) .expect(&format!("{}:{}", line!(), file!())); - (trade, src, dst) - } - - fn deserialize_swap(data: &[u8]) -> TradeSwapInfo { - let state: ExchangeState = - bincode::deserialize(data).expect(&format!("{}:{}", line!(), file!())); - match state { - ExchangeState::Swap(info) => info, - _ => panic!("Not a valid swap"), - } + (trade, src) } #[test] @@ -714,7 +698,7 @@ mod test { let (bank, mint_keypair) = create_bank(10_000); let (client, owner) = create_client(bank, mint_keypair); - let (trade, src, dst) = trade( + let (trade, src) = trade( &client, &owner, Direction::To, @@ -727,7 +711,6 @@ mod test { let trade_account_data = client.get_account_data(&trade).unwrap().unwrap(); let src_account_data = client.get_account_data(&src).unwrap().unwrap(); - let dst_account_data = client.get_account_data(&dst).unwrap().unwrap(); // check results @@ -738,8 +721,7 @@ mod test { pair: TokenPair::AB, tokens: 2, price: 1000, - src_account: src, - dst_account: dst + tokens_settled: 0 }, ExchangeProcessor::deserialize_trade(&trade_account_data).unwrap() ); @@ -749,12 +731,6 @@ mod test { .tokens(100_040, 100_000, 100_000, 100_000), ExchangeProcessor::deserialize_account(&src_account_data).unwrap() ); - assert_eq!( - TokenAccountInfo::default() - .owner(&owner.pubkey()) - .tokens(100_000, 100_000, 100_000, 100_000), - ExchangeProcessor::deserialize_account(&dst_account_data).unwrap() - ); } #[test] @@ -763,9 +739,8 @@ mod test { let (bank, mint_keypair) = create_bank(10_000); let (client, owner) = create_client(bank, mint_keypair); - let swap = create_account(&client, &owner); let profit = create_token_account(&client, &owner); - let (to_trade, to_src, to_dst) = trade( + let (to_trade, _) = trade( &client, &owner, Direction::To, @@ -775,7 +750,7 @@ mod test { 2, 2000, ); - let (from_trade, from_src, from_dst) = trade( + let (from_trade, _) = trade( &client, &owner, Direction::From, @@ -786,27 +761,15 @@ mod test { 3000, ); - let instruction = exchange_instruction::swap_request( - &owner.pubkey(), - &swap, - &to_trade, - &from_trade, - &to_dst, - &from_dst, - &profit, - ); + let instruction = + exchange_instruction::swap_request(&owner.pubkey(), &to_trade, &from_trade, &profit); client .send_instruction(&owner, instruction) .expect(&format!("{}:{}", line!(), file!())); let to_trade_account_data = client.get_account_data(&to_trade).unwrap().unwrap(); - let to_src_account_data = client.get_account_data(&to_src).unwrap().unwrap(); - let to_dst_account_data = client.get_account_data(&to_dst).unwrap().unwrap(); let from_trade_account_data = client.get_account_data(&from_trade).unwrap().unwrap(); - let from_src_account_data = client.get_account_data(&from_src).unwrap().unwrap(); - let from_dst_account_data = client.get_account_data(&from_dst).unwrap().unwrap(); let profit_account_data = client.get_account_data(&profit).unwrap().unwrap(); - let swap_account_data = client.get_account_data(&swap).unwrap().unwrap(); // check results @@ -817,64 +780,83 @@ mod test { pair: TokenPair::AB, tokens: 1, price: 2000, - src_account: to_src, - dst_account: to_dst + tokens_settled: 2, }, ExchangeProcessor::deserialize_trade(&to_trade_account_data).unwrap() ); + assert_eq!( TokenAccountInfo::default() .owner(&owner.pubkey()) - .tokens(100_000, 100_000, 100_000, 100_000), - ExchangeProcessor::deserialize_account(&to_src_account_data).unwrap() - ); - assert_eq!( - TokenAccountInfo::default() - .owner(&owner.pubkey()) - .tokens(100_000, 100_002, 100_000, 100_000), - ExchangeProcessor::deserialize_account(&to_dst_account_data).unwrap() - ); - assert_eq!( - TradeOrderInfo { - owner: owner.pubkey(), - direction: Direction::From, - pair: TokenPair::AB, - tokens: 0, - price: 3000, - src_account: from_src, - dst_account: from_dst - }, - ExchangeProcessor::deserialize_trade(&from_trade_account_data).unwrap() - ); - assert_eq!( - TokenAccountInfo::default() - .owner(&owner.pubkey()) - .tokens(100_000, 100_000, 100_000, 100_000), - ExchangeProcessor::deserialize_account(&from_src_account_data).unwrap() - ); - assert_eq!( - TokenAccountInfo::default() - .owner(&owner.pubkey()) - .tokens(100_001, 100_000, 100_000, 100_000), - ExchangeProcessor::deserialize_account(&from_dst_account_data).unwrap() + .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, + Direction::To, + TokenPair::AB, + Token::A, + 3, + 3, + 2000, + ); + let (from_trade, _) = trade( + &client, + &owner, + Direction::From, + TokenPair::AB, + Token::B, + 3, + 3, + 3000, + ); + + let instruction = + exchange_instruction::swap_request(&owner.pubkey(), &to_trade, &from_trade, &profit); + client + .send_instruction(&owner, instruction) + .expect(&format!("{}:{}", 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_instruction(&owner, instruction) + .expect(&format!("{}:{}", line!(), file!())); + + let instruction = + exchange_instruction::transfer_request(&owner.pubkey(), &new, &from_trade, Token::A, 1); + client + .send_instruction(&owner, instruction) + .expect(&format!("{}:{}", line!(), file!())); + + let new_account_data = client.get_account_data(&new).unwrap().unwrap(); + + // Check results + assert_eq!( - TradeSwapInfo { - pair: TokenPair::AB, - to_trade_order: to_trade, - from_trade_order: from_trade, - primary_tokens: 1, - primary_price: 2000, - secondary_tokens: 3, - secondary_price: 3000, - }, - deserialize_swap(&swap_account_data) + TokenAccountInfo::default() + .owner(&owner.pubkey()) + .tokens(100_001, 100_001, 100_000, 100_000), + ExchangeProcessor::deserialize_account(&new_account_data).unwrap() ); } } diff --git a/programs/exchange_api/src/exchange_state.rs b/programs/exchange_api/src/exchange_state.rs index cd5c55d375..911148afbc 100644 --- a/programs/exchange_api/src/exchange_state.rs +++ b/programs/exchange_api/src/exchange_state.rs @@ -157,16 +157,15 @@ pub struct TradeOrderInfo { pub direction: Direction, /// Token pair indicating two tokens to exchange, first is primary pub pair: TokenPair, - /// Number of tokens to exchange; primary or secondary depending on direction + /// Number of tokens to exchange; primary or secondary depending on direction. Once + /// this number goes to zero this trade order will be converted into a regular token account pub tokens: u64, /// Scaled price of the secondary token given the primary is equal to the scale value /// If scale is 1 and price is 2 then ratio is 1:2 or 1 primary token for 2 secondary tokens pub price: u64, - /// account which the tokens were source from. The trade account holds the tokens in escrow - /// until either one or more part of a swap or the trade is cancelled. - pub src_account: Pubkey, - /// account which the tokens the tokens will be deposited into on a successful trade - pub dst_account: Pubkey, + /// Number of tokens that have been settled so far. These nay be transferred to another + /// token account by the owner. + pub tokens_settled: u64, } impl Default for TradeOrderInfo { fn default() -> Self { @@ -176,8 +175,7 @@ impl Default for TradeOrderInfo { direction: Direction::To, tokens: 0, price: 0, - src_account: Pubkey::default(), - dst_account: Pubkey::default(), + tokens_settled: 0, } } } @@ -222,25 +220,6 @@ pub fn check_trade(direction: Direction, tokens: u64, price: u64) -> Result<(), Ok(()) } -/// Swap accounts are populated with this structure -#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -pub struct TradeSwapInfo { - /// Pair swapped - pub pair: TokenPair, - /// `To` trade order - pub to_trade_order: Pubkey, - /// `From` trade order - pub from_trade_order: Pubkey, - /// Number of primary tokens exchanged - pub primary_tokens: u64, - /// Price the primary tokens were exchanged for - pub primary_price: u64, - /// Number of secondary tokens exchanged - pub secondary_tokens: u64, - /// Price the secondary tokens were exchanged for - pub secondary_price: u64, -} - /// Type of exchange account, account's user data is populated with this enum #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] pub enum ExchangeState { @@ -250,8 +229,6 @@ pub enum ExchangeState { Account(TokenAccountInfo), // Trade order account Trade(TradeOrderInfo), - // Swap account - Swap(TradeSwapInfo), Invalid, } impl Default for ExchangeState {