diff --git a/bpf-sdk-install.sh b/bpf-sdk-install.sh index 68c2bab6..412ed02b 100755 --- a/bpf-sdk-install.sh +++ b/bpf-sdk-install.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -e -channel=${1:-v1.3.9} +channel=${1:-v1.3.12} installDir="$(dirname "$0")"/bin cacheDir=~/.cache/solana-bpf-sdk/"$channel" diff --git a/token-swap/js/cli/token-swap-test.js b/token-swap/js/cli/token-swap-test.js index 1ee558ff..94743c5d 100644 --- a/token-swap/js/cli/token-swap-test.js +++ b/token-swap/js/cli/token-swap-test.js @@ -39,6 +39,10 @@ let tokenAccountB: PublicKey; const BASE_AMOUNT = 1000; // Amount passed to instructions const USER_AMOUNT = 100; +// Pool token amount minted on init +const DEFAULT_POOL_TOKEN_AMOUNT = 1000000000; +// Pool token amount to withdraw / deposit +const POOL_TOKEN_AMOUNT = 1000000; function assert(condition, message) { if (!condition) { @@ -219,16 +223,23 @@ export async function createTokenSwap(): Promise { } export async function deposit(): Promise { + const poolMintInfo = await tokenPool.getMintInfo(); + const supply = poolMintInfo.supply.toNumber(); + const swapTokenA = await mintA.getAccountInfo(tokenAccountA); + const tokenA = (swapTokenA.amount.toNumber() * POOL_TOKEN_AMOUNT) / supply; + const swapTokenB = await mintB.getAccountInfo(tokenAccountB); + const tokenB = (swapTokenB.amount.toNumber() * POOL_TOKEN_AMOUNT) / supply; + console.log('Creating depositor token a account'); - let userAccountA = await mintA.createAccount(owner.publicKey); - await mintA.mintTo(userAccountA, owner, [], USER_AMOUNT); - await mintA.approve(userAccountA, authority, owner, [], USER_AMOUNT); + const userAccountA = await mintA.createAccount(owner.publicKey); + await mintA.mintTo(userAccountA, owner, [], tokenA); + await mintA.approve(userAccountA, authority, owner, [], tokenA); console.log('Creating depositor token b account'); - let userAccountB = await mintB.createAccount(owner.publicKey); - await mintB.mintTo(userAccountB, owner, [], USER_AMOUNT); - await mintB.approve(userAccountB, authority, owner, [], USER_AMOUNT); + const userAccountB = await mintB.createAccount(owner.publicKey); + await mintB.mintTo(userAccountB, owner, [], tokenB); + await mintB.approve(userAccountB, authority, owner, [], tokenB); console.log('Creating depositor pool token account'); - let newAccountPool = await tokenPool.createAccount(owner.publicKey); + const newAccountPool = await tokenPool.createAccount(owner.publicKey); const [tokenProgramId] = await GetPrograms(connection); console.log('Depositing into swap'); @@ -241,7 +252,7 @@ export async function deposit(): Promise { tokenPool.publicKey, newAccountPool, tokenProgramId, - USER_AMOUNT, + POOL_TOKEN_AMOUNT, ); let info; @@ -250,21 +261,34 @@ export async function deposit(): Promise { info = await mintB.getAccountInfo(userAccountB); assert(info.amount.toNumber() == 0); info = await mintA.getAccountInfo(tokenAccountA); - assert(info.amount.toNumber() == BASE_AMOUNT + USER_AMOUNT); + assert(info.amount.toNumber() == BASE_AMOUNT + tokenA); info = await mintB.getAccountInfo(tokenAccountB); - assert(info.amount.toNumber() == BASE_AMOUNT + USER_AMOUNT); + assert(info.amount.toNumber() == BASE_AMOUNT + tokenB); info = await tokenPool.getAccountInfo(newAccountPool); - assert(info.amount.toNumber() == USER_AMOUNT); + assert(info.amount.toNumber() == POOL_TOKEN_AMOUNT); } export async function withdraw(): Promise { + const poolMintInfo = await tokenPool.getMintInfo(); + const supply = poolMintInfo.supply.toNumber(); + let swapTokenA = await mintA.getAccountInfo(tokenAccountA); + let swapTokenB = await mintB.getAccountInfo(tokenAccountB); + const tokenA = (swapTokenA.amount.toNumber() * POOL_TOKEN_AMOUNT) / supply; + const tokenB = (swapTokenB.amount.toNumber() * POOL_TOKEN_AMOUNT) / supply; + console.log('Creating withdraw token A account'); let userAccountA = await mintA.createAccount(owner.publicKey); console.log('Creating withdraw token B account'); let userAccountB = await mintB.createAccount(owner.publicKey); console.log('Approving withdrawal from pool account'); - await tokenPool.approve(tokenAccountPool, authority, owner, [], USER_AMOUNT); + await tokenPool.approve( + tokenAccountPool, + authority, + owner, + [], + POOL_TOKEN_AMOUNT, + ); const [tokenProgramId] = await GetPrograms(connection); console.log('Withdrawing pool tokens for A and B tokens'); @@ -277,19 +301,23 @@ export async function withdraw(): Promise { userAccountA, userAccountB, tokenProgramId, - USER_AMOUNT, + POOL_TOKEN_AMOUNT, ); + //const poolMintInfo = await tokenPool.getMintInfo(); + swapTokenA = await mintA.getAccountInfo(tokenAccountA); + swapTokenB = await mintB.getAccountInfo(tokenAccountB); + let info = await tokenPool.getAccountInfo(tokenAccountPool); - assert(info.amount.toNumber() == BASE_AMOUNT - USER_AMOUNT); - info = await mintA.getAccountInfo(tokenAccountA); - assert(info.amount.toNumber() == BASE_AMOUNT); - info = await mintB.getAccountInfo(tokenAccountB); - assert(info.amount.toNumber() == BASE_AMOUNT); + assert( + info.amount.toNumber() == DEFAULT_POOL_TOKEN_AMOUNT - POOL_TOKEN_AMOUNT, + ); + assert(swapTokenA.amount.toNumber() == BASE_AMOUNT); + assert(swapTokenB.amount.toNumber() == BASE_AMOUNT); info = await mintA.getAccountInfo(userAccountA); - assert(info.amount.toNumber() == USER_AMOUNT); + assert(info.amount.toNumber() == tokenA); info = await mintB.getAccountInfo(userAccountB); - assert(info.amount.toNumber() == USER_AMOUNT); + assert(info.amount.toNumber() == tokenB); } export async function swap(): Promise { @@ -322,5 +350,7 @@ export async function swap(): Promise { info = await mintB.getAccountInfo(userAccountB); assert(info.amount.toNumber() == 69); info = await tokenPool.getAccountInfo(tokenAccountPool); - assert(info.amount.toNumber() == BASE_AMOUNT - USER_AMOUNT); + assert( + info.amount.toNumber() == DEFAULT_POOL_TOKEN_AMOUNT - POOL_TOKEN_AMOUNT, + ); } diff --git a/token-swap/js/client/token-swap.js b/token-swap/js/client/token-swap.js index 9978bd8d..da569dab 100644 --- a/token-swap/js/client/token-swap.js +++ b/token-swap/js/client/token-swap.js @@ -419,7 +419,7 @@ export class TokenSwap { * @param poolToken Pool token * @param poolAccount Pool account to deposit the generated tokens * @param tokenProgramId Token program id - * @param amount Amount of token A to transfer, token B amount is set by the exchange rate + * @param amount Amount of pool token to deposit, token A and B amount are set by the exchange rate relative to the total pool token supply */ async deposit( authority: PublicKey, @@ -510,7 +510,7 @@ export class TokenSwap { * @param userAccountA Token A user account * @param userAccountB token B user account * @param tokenProgramId Token program id - * @param amount Amount of token A to transfer, token B amount is set by the exchange rate + * @param amount Amount of pool token to withdraw, token A and B amount are set by the exchange rate relative to the total pool token supply */ async withdraw( authority: PublicKey, diff --git a/token-swap/program/src/curve.rs b/token-swap/program/src/curve.rs new file mode 100644 index 00000000..6a672f28 --- /dev/null +++ b/token-swap/program/src/curve.rs @@ -0,0 +1,165 @@ +//! Swap calculations and curve implementations + +/// Initial amount of pool tokens for swap contract, hard-coded to something +/// "sensible" given a maximum of u64. +/// Note that on Ethereum, Uniswap uses the geometric mean of all provided +/// input amounts, and Balancer uses 100 * 10 ^ 18. +pub const INITIAL_SWAP_POOL_AMOUNT: u64 = 1_000_000_000; + +/// Encodes all results of swapping from a source token to a destination token +pub struct SwapResult { + /// New amount of source token + pub new_source_amount: u64, + /// New amount of destination token + pub new_destination_amount: u64, + /// Amount of destination token swapped + pub amount_swapped: u64, +} + +impl SwapResult { + /// SwapResult for swap from one currency into another, given pool information + /// and fee + pub fn swap_to( + source_amount: u64, + swap_source_amount: u64, + swap_destination_amount: u64, + fee_numerator: u64, + fee_denominator: u64, + ) -> Option { + let invariant = swap_source_amount.checked_mul(swap_destination_amount)?; + let new_source_amount = swap_source_amount.checked_add(source_amount)?; + let new_destination_amount = invariant.checked_div(new_source_amount)?; + let remove = swap_destination_amount.checked_sub(new_destination_amount)?; + let fee = remove + .checked_mul(fee_numerator)? + .checked_div(fee_denominator)?; + let new_destination_amount = new_destination_amount.checked_add(fee)?; + let amount_swapped = remove.checked_sub(fee)?; + Some(SwapResult { + new_source_amount, + new_destination_amount, + amount_swapped, + }) + } +} + +/// The Uniswap invariant calculator. +pub struct ConstantProduct { + /// Token A + pub token_a: u64, + /// Token B + pub token_b: u64, + /// Fee numerator + pub fee_numerator: u64, + /// Fee denominator + pub fee_denominator: u64, +} + +impl ConstantProduct { + /// Swap token a to b + pub fn swap_a_to_b(&mut self, token_a: u64) -> Option { + let result = SwapResult::swap_to( + token_a, + self.token_a, + self.token_b, + self.fee_numerator, + self.fee_denominator, + )?; + self.token_a = result.new_source_amount; + self.token_b = result.new_destination_amount; + Some(result.amount_swapped) + } + + /// Swap token b to a + pub fn swap_b_to_a(&mut self, token_b: u64) -> Option { + let result = SwapResult::swap_to( + token_b, + self.token_b, + self.token_a, + self.fee_numerator, + self.fee_denominator, + )?; + self.token_b = result.new_source_amount; + self.token_a = result.new_destination_amount; + Some(result.amount_swapped) + } +} + +/// Conversions for pool tokens, how much to deposit / withdraw, along with +/// proper initialization +pub struct PoolTokenConverter { + /// Total supply + pub supply: u64, + /// Token A amount + pub token_a: u64, + /// Token B amount + pub token_b: u64, +} + +impl PoolTokenConverter { + /// Create a converter based on existing market information + pub fn new_existing(supply: u64, token_a: u64, token_b: u64) -> Self { + Self { + supply, + token_a, + token_b, + } + } + + /// Create a converter for a new pool token, no supply present yet. + /// According to Uniswap, the geometric mean protects the pool creator + /// in case the initial ratio is off the market. + pub fn new_pool(token_a: u64, token_b: u64) -> Self { + let supply = INITIAL_SWAP_POOL_AMOUNT; + Self { + supply, + token_a, + token_b, + } + } + + /// A tokens for pool tokens + pub fn token_a_rate(&self, pool_tokens: u64) -> Option { + pool_tokens + .checked_mul(self.token_a)? + .checked_div(self.supply) + } + + /// B tokens for pool tokens + pub fn token_b_rate(&self, pool_tokens: u64) -> Option { + pool_tokens + .checked_mul(self.token_b)? + .checked_div(self.supply) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn initial_pool_amount() { + let token_converter = PoolTokenConverter::new_pool(1, 5); + assert_eq!(token_converter.supply, INITIAL_SWAP_POOL_AMOUNT); + } + + fn check_pool_token_a_rate( + token_a: u64, + token_b: u64, + deposit: u64, + supply: u64, + expected: Option, + ) { + let calculator = PoolTokenConverter::new_existing(supply, token_a, token_b); + assert_eq!(calculator.token_a_rate(deposit), expected); + } + + #[test] + fn issued_tokens() { + check_pool_token_a_rate(2, 50, 5, 10, Some(1)); + check_pool_token_a_rate(10, 10, 5, 10, Some(5)); + check_pool_token_a_rate(5, 100, 5, 10, Some(2)); + check_pool_token_a_rate(5, u64::MAX, 5, 10, Some(2)); + check_pool_token_a_rate(u64::MAX, u64::MAX, 5, 10, None); + } +} diff --git a/token-swap/program/src/lib.rs b/token-swap/program/src/lib.rs index 430d465f..d9552004 100644 --- a/token-swap/program/src/lib.rs +++ b/token-swap/program/src/lib.rs @@ -2,6 +2,7 @@ //! An Uniswap-like program for the Solana blockchain. +pub mod curve; pub mod entrypoint; pub mod error; pub mod instruction; diff --git a/token-swap/program/src/processor.rs b/token-swap/program/src/processor.rs index 673a94e5..531fecac 100644 --- a/token-swap/program/src/processor.rs +++ b/token-swap/program/src/processor.rs @@ -3,9 +3,10 @@ #![cfg(feature = "program")] use crate::{ + curve::{ConstantProduct, PoolTokenConverter}, error::SwapError, instruction::SwapInstruction, - state::{Invariant, SwapInfo}, + state::SwapInfo, }; use num_traits::FromPrimitive; #[cfg(not(target_arch = "bpf"))] @@ -150,7 +151,7 @@ impl Processor { let authority_info = next_account_info(account_info_iter)?; let token_a_info = next_account_info(account_info_iter)?; let token_b_info = next_account_info(account_info_iter)?; - let pool_info = next_account_info(account_info_iter)?; + let pool_mint_info = next_account_info(account_info_iter)?; let destination_info = next_account_info(account_info_iter)?; let token_program_info = next_account_info(account_info_iter)?; @@ -165,7 +166,7 @@ impl Processor { let token_a = Self::unpack_token_account(&token_a_info.data.borrow())?; let token_b = Self::unpack_token_account(&token_b_info.data.borrow())?; let destination = Self::unpack_token_account(&destination_info.data.borrow())?; - let pool_mint = Self::unpack_mint(&pool_info.data.borrow())?; + let pool_mint = Self::unpack_mint(&pool_mint_info.data.borrow())?; if *authority_info.key != token_a.owner { return Err(SwapError::InvalidOwner.into()); } @@ -197,17 +198,17 @@ impl Processor { return Err(SwapError::InvalidSupply.into()); } - // liquidity is measured in terms of token_a's value since both sides of - // the pool are equal - let amount = token_a.amount; + let converter = PoolTokenConverter::new_pool(token_a.amount, token_b.amount); + let initial_amount = converter.supply; + Self::token_mint_to( swap_info.key, token_program_info.clone(), - pool_info.clone(), + pool_mint_info.clone(), destination_info.clone(), authority_info.clone(), nonce, - amount, + initial_amount, )?; let obj = SwapInfo { @@ -215,7 +216,7 @@ impl Processor { nonce, token_a: *token_a_info.key, token_b: *token_b_info.key, - pool_mint: *pool_info.key, + pool_mint: *pool_mint_info.key, fee_numerator, fee_denominator, }; @@ -260,7 +261,7 @@ impl Processor { let dest_account = Self::unpack_token_account(&swap_destination_info.data.borrow())?; let output = if *swap_source_info.key == token_swap.token_a { - let mut invariant = Invariant { + let mut invariant = ConstantProduct { token_a: source_account.amount, token_b: dest_account.amount, fee_numerator: token_swap.fee_numerator, @@ -270,7 +271,7 @@ impl Processor { .swap_a_to_b(amount) .ok_or(SwapError::CalculationFailure)? } else { - let mut invariant = Invariant { + let mut invariant = ConstantProduct { token_a: dest_account.amount, token_b: source_account.amount, fee_numerator: token_swap.fee_numerator, @@ -304,7 +305,7 @@ impl Processor { /// Processes an [Deposit](enum.Instruction.html). pub fn process_deposit( program_id: &Pubkey, - a_amount: u64, + pool_amount: u64, accounts: &[AccountInfo], ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); @@ -314,7 +315,7 @@ impl Processor { let source_b_info = next_account_info(account_info_iter)?; let token_a_info = next_account_info(account_info_iter)?; let token_b_info = next_account_info(account_info_iter)?; - let pool_info = next_account_info(account_info_iter)?; + let pool_mint_info = next_account_info(account_info_iter)?; let dest_info = next_account_info(account_info_iter)?; let token_program_info = next_account_info(account_info_iter)?; @@ -328,26 +329,23 @@ impl Processor { if *token_b_info.key != token_swap.token_b { return Err(SwapError::IncorrectSwapAccount.into()); } - if *pool_info.key != token_swap.pool_mint { + if *pool_mint_info.key != token_swap.pool_mint { return Err(SwapError::IncorrectPoolMint.into()); } let token_a = Self::unpack_token_account(&token_a_info.data.borrow())?; let token_b = Self::unpack_token_account(&token_b_info.data.borrow())?; + let pool_mint = Self::unpack_mint(&pool_mint_info.data.borrow())?; - let invariant = Invariant { - token_a: token_a.amount, - token_b: token_b.amount, - fee_numerator: token_swap.fee_numerator, - fee_denominator: token_swap.fee_denominator, - }; - let b_amount = invariant - .exchange_rate(a_amount) + let converter = + PoolTokenConverter::new_existing(pool_mint.supply, token_a.amount, token_b.amount); + + let a_amount = converter + .token_a_rate(pool_amount) + .ok_or(SwapError::CalculationFailure)?; + let b_amount = converter + .token_b_rate(pool_amount) .ok_or(SwapError::CalculationFailure)?; - - // liquidity is measured in terms of token_a's value - // since both sides of the pool are equal - let output = a_amount; Self::token_transfer( swap_info.key, @@ -370,11 +368,11 @@ impl Processor { Self::token_mint_to( swap_info.key, token_program_info.clone(), - pool_info.clone(), + pool_mint_info.clone(), dest_info.clone(), authority_info.clone(), token_swap.nonce, - output, + pool_amount, )?; Ok(()) @@ -383,7 +381,7 @@ impl Processor { /// Processes an [Withdraw](enum.Instruction.html). pub fn process_withdraw( program_id: &Pubkey, - amount: u64, + pool_amount: u64, accounts: &[AccountInfo], ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); @@ -413,17 +411,16 @@ impl Processor { let token_a = Self::unpack_token_account(&token_a_info.data.borrow())?; let token_b = Self::unpack_token_account(&token_b_info.data.borrow())?; + let pool_mint = Self::unpack_mint(&pool_mint_info.data.borrow())?; - let invariant = Invariant { - token_a: token_a.amount, - token_b: token_b.amount, - fee_numerator: token_swap.fee_numerator, - fee_denominator: token_swap.fee_denominator, - }; + let converter = + PoolTokenConverter::new_existing(pool_mint.supply, token_a.amount, token_b.amount); - let a_amount = amount; - let b_amount = invariant - .exchange_rate(a_amount) + let a_amount = converter + .token_a_rate(pool_amount) + .ok_or(SwapError::CalculationFailure)?; + let b_amount = converter + .token_b_rate(pool_amount) .ok_or(SwapError::CalculationFailure)?; Self::token_transfer( @@ -451,7 +448,7 @@ impl Processor { pool_mint_info.clone(), authority_info.clone(), token_swap.nonce, - amount, + pool_amount, )?; Ok(()) } @@ -575,8 +572,8 @@ solana_sdk::program_stubs!(); mod tests { use super::*; use crate::{ + curve::{SwapResult, INITIAL_SWAP_POOL_AMOUNT}, instruction::{deposit, initialize, swap, withdraw}, - state::SwapResult, }; use solana_sdk::{ account::Account, account_info::create_is_signer_account_infos, instruction::Instruction, @@ -840,6 +837,7 @@ mod tests { mut depositor_token_b_account: &mut Account, depositor_pool_key: &Pubkey, mut depositor_pool_account: &mut Account, + pool_amount: u64, amount_a: u64, amount_b: u64, ) -> ProgramResult { @@ -891,7 +889,7 @@ mod tests { &self.token_b_key, &self.pool_mint_key, &depositor_pool_key, - amount_a, + pool_amount, ) .unwrap(), vec![ @@ -917,7 +915,7 @@ mod tests { mut token_a_account: &mut Account, token_b_key: &Pubkey, mut token_b_account: &mut Account, - amount: u64, + pool_amount: u64, ) -> ProgramResult { // approve swap program to take out pool tokens do_process_instruction( @@ -927,7 +925,7 @@ mod tests { &self.authority_key, &user_key, &[], - amount, + pool_amount, ) .unwrap(), vec![ @@ -951,7 +949,7 @@ mod tests { &self.token_b_key, &token_a_key, &token_b_key, - amount, + pool_amount, ) .unwrap(), vec![ @@ -1494,7 +1492,7 @@ mod tests { let fee_numerator = 1; let fee_denominator = 2; let token_a_amount = 1000; - let token_b_amount = 8000; + let token_b_amount = 9000; let mut accounts = SwapAccountInfo::new( &user_key, fee_numerator, @@ -1505,6 +1503,7 @@ mod tests { let deposit_a = token_a_amount / 10; let deposit_b = token_b_amount / 10; + let pool_amount = INITIAL_SWAP_POOL_AMOUNT / 10; // swap not initialized { @@ -1526,6 +1525,7 @@ mod tests { &mut token_b_account, &pool_key, &mut pool_account, + pool_amount, deposit_a, deposit_b, ) @@ -1560,6 +1560,7 @@ mod tests { &mut token_b_account, &pool_key, &mut pool_account, + pool_amount, deposit_a, deposit_b, ) @@ -1593,6 +1594,7 @@ mod tests { &mut token_b_account, &pool_key, &mut pool_account, + pool_amount, deposit_a, deposit_b, ) @@ -1625,6 +1627,7 @@ mod tests { &mut token_b_account, &pool_key, &mut pool_account, + pool_amount, deposit_a, deposit_b, ) @@ -1651,6 +1654,7 @@ mod tests { &mut token_a_account, &pool_key, &mut pool_account, + pool_amount, deposit_a, deposit_b, ) @@ -1685,6 +1689,7 @@ mod tests { &mut token_b_account, &wrong_token_key, &mut wrong_token_account, + pool_amount, deposit_a, deposit_b, ) @@ -1715,7 +1720,7 @@ mod tests { &accounts.token_b_key, &accounts.pool_mint_key, &pool_key, - deposit_a, + pool_amount, ) .unwrap(), vec![ @@ -1758,7 +1763,7 @@ mod tests { &accounts.token_b_key, &accounts.pool_mint_key, &pool_key, - deposit_a, + pool_amount, ) .unwrap(), vec![ @@ -1804,6 +1809,7 @@ mod tests { &mut token_b_account, &pool_key, &mut pool_account, + pool_amount, deposit_a, deposit_b, ) @@ -1829,6 +1835,7 @@ mod tests { &mut token_b_account, &pool_key, &mut pool_account, + pool_amount, deposit_a, deposit_b, ) @@ -1865,6 +1872,7 @@ mod tests { &mut token_b_account, &pool_key, &mut pool_account, + pool_amount, deposit_a, deposit_b, ) @@ -1893,6 +1901,7 @@ mod tests { &mut token_b_account, &pool_key, &mut pool_account, + pool_amount, deposit_a, deposit_b, ) @@ -1934,10 +1943,11 @@ mod tests { token_b_amount, ); let withdrawer_key = pubkey_rand(); + let pool_converter = PoolTokenConverter::new_pool(token_a_amount, token_b_amount); let initial_a = token_a_amount / 10; let initial_b = token_b_amount / 10; - let initial_pool = token_a_amount; - let withdraw_amount = token_a_amount / 4; + let initial_pool = pool_converter.supply / 10; + let withdraw_amount = initial_pool / 4; // swap not initialized { @@ -2335,14 +2345,23 @@ mod tests { let swap_token_a = Processor::unpack_token_account(&accounts.token_a_account.data).unwrap(); - assert_eq!(swap_token_a.amount, token_a_amount - withdraw_amount); let swap_token_b = Processor::unpack_token_account(&accounts.token_b_account.data).unwrap(); - assert_eq!(swap_token_b.amount, token_b_amount - (withdraw_amount * 2)); + let pool_mint = Processor::unpack_mint(&accounts.pool_mint_account.data).unwrap(); + let pool_converter = PoolTokenConverter::new_existing( + pool_mint.supply, + swap_token_a.amount, + swap_token_b.amount, + ); + + let withdrawn_a = pool_converter.token_a_rate(withdraw_amount).unwrap(); + assert_eq!(swap_token_a.amount, token_a_amount - withdrawn_a); + let withdrawn_b = pool_converter.token_b_rate(withdraw_amount).unwrap(); + assert_eq!(swap_token_b.amount, token_b_amount - withdrawn_b); let token_a = Processor::unpack_token_account(&token_a_account.data).unwrap(); - assert_eq!(token_a.amount, initial_a + withdraw_amount); + assert_eq!(token_a.amount, initial_a + withdrawn_a); let token_b = Processor::unpack_token_account(&token_b_account.data).unwrap(); - assert_eq!(token_b.amount, initial_b + (withdraw_amount * 2)); + assert_eq!(token_b.amount, initial_b + withdrawn_b); let pool_account = Processor::unpack_token_account(&pool_account.data).unwrap(); assert_eq!(pool_account.amount, initial_pool - withdraw_amount); } @@ -2655,14 +2674,14 @@ mod tests { let swap_token_a = Processor::unpack_token_account(&accounts.token_a_account.data).unwrap(); let token_a_amount = swap_token_a.amount; - assert_eq!(token_a_amount, results.new_source); + assert_eq!(token_a_amount, results.new_source_amount); let token_a = Processor::unpack_token_account(&token_a_account.data).unwrap(); assert_eq!(token_a.amount, initial_a - a_to_b_amount); let swap_token_b = Processor::unpack_token_account(&accounts.token_b_account.data).unwrap(); let token_b_amount = swap_token_b.amount; - assert_eq!(token_b_amount, results.new_destination); + assert_eq!(token_b_amount, results.new_destination_amount); let token_b = Processor::unpack_token_account(&token_b_account.data).unwrap(); assert_eq!(token_b.amount, initial_b + results.amount_swapped); @@ -2694,7 +2713,7 @@ mod tests { let swap_token_a = Processor::unpack_token_account(&accounts.token_a_account.data).unwrap(); - assert_eq!(swap_token_a.amount, results.new_destination); + assert_eq!(swap_token_a.amount, results.new_destination_amount); let token_a = Processor::unpack_token_account(&token_a_account.data).unwrap(); assert_eq!( token_a.amount, @@ -2703,7 +2722,7 @@ mod tests { let swap_token_b = Processor::unpack_token_account(&accounts.token_b_account.data).unwrap(); - assert_eq!(swap_token_b.amount, results.new_source); + assert_eq!(swap_token_b.amount, results.new_source_amount); let token_b = Processor::unpack_token_account(&token_b_account.data).unwrap(); assert_eq!( token_b.amount, diff --git a/token-swap/program/src/state.rs b/token-swap/program/src/state.rs index 3e6a6267..33caca24 100644 --- a/token-swap/program/src/state.rs +++ b/token-swap/program/src/state.rs @@ -83,90 +83,6 @@ impl SwapInfo { } } -/// Encodes all results of swapping from a source token to a destination token -pub struct SwapResult { - /// New amount of source token - pub new_source: u64, - /// New amount of destination token - pub new_destination: u64, - /// Amount of destination token swapped - pub amount_swapped: u64, -} - -impl SwapResult { - /// SwapResult for swap from one currency into another, given pool information - /// and fee - pub fn swap_to( - source: u64, - source_amount: u64, - dest_amount: u64, - fee_numerator: u64, - fee_denominator: u64, - ) -> Option { - let invariant = source_amount.checked_mul(dest_amount)?; - let new_source = source_amount.checked_add(source)?; - let new_destination = invariant.checked_div(new_source)?; - let remove = dest_amount.checked_sub(new_destination)?; - let fee = remove - .checked_mul(fee_numerator)? - .checked_div(fee_denominator)?; - let new_destination = new_destination.checked_add(fee)?; - let amount_swapped = remove.checked_sub(fee)?; - Some(SwapResult { - new_source, - new_destination, - amount_swapped, - }) - } -} - -/// The Uniswap invariant calculator. -pub struct Invariant { - /// Token A - pub token_a: u64, - /// Token B - pub token_b: u64, - /// Fee numerator - pub fee_numerator: u64, - /// Fee denominator - pub fee_denominator: u64, -} - -impl Invariant { - /// Swap token a to b - pub fn swap_a_to_b(&mut self, token_a: u64) -> Option { - let result = SwapResult::swap_to( - token_a, - self.token_a, - self.token_b, - self.fee_numerator, - self.fee_denominator, - )?; - self.token_a = result.new_source; - self.token_b = result.new_destination; - Some(result.amount_swapped) - } - - /// Swap token b to a - pub fn swap_b_to_a(&mut self, token_b: u64) -> Option { - let result = SwapResult::swap_to( - token_b, - self.token_b, - self.token_a, - self.fee_numerator, - self.fee_denominator, - )?; - self.token_b = result.new_source; - self.token_a = result.new_destination; - Some(result.amount_swapped) - } - - /// Exchange rate - pub fn exchange_rate(&self, token_a: u64) -> Option { - token_a.checked_mul(self.token_b)?.checked_div(self.token_a) - } -} - #[cfg(test)] mod tests { use super::*;