token-swap: Improve pool token supply on initialization, deposit, and withdrawal (#508)

* token-swap: Add token supply in invariant calculation

* Refactor state classes into curve components for future use
* Align pool initialization with Uniswap using geometric mean of token
  amounts
* Fix deposit and withdraw instruction to work as a proportion of pool
  tokens
* Add math utilities to calculate the geometric mean with u64

* Improve variable names

* Use a fixed starting pool size

* Run cargo fmt

* Update js tests with new pool numbers

* Run linting

* Remove math

* Fix BN type issues found by flow
This commit is contained in:
Jon Cinque 2020-09-23 23:08:44 +02:00 committed by GitHub
parent 51c4dc6951
commit 13de66817d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 296 additions and 165 deletions

View File

@ -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"

View File

@ -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<void> {
}
export async function deposit(): Promise<void> {
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<void> {
tokenPool.publicKey,
newAccountPool,
tokenProgramId,
USER_AMOUNT,
POOL_TOKEN_AMOUNT,
);
let info;
@ -250,21 +261,34 @@ export async function deposit(): Promise<void> {
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<void> {
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<void> {
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<void> {
@ -322,5 +350,7 @@ export async function swap(): Promise<void> {
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,
);
}

View File

@ -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,

View File

@ -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<SwapResult> {
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<u64> {
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<u64> {
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<u64> {
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<u64> {
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<u64>,
) {
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);
}
}

View File

@ -2,6 +2,7 @@
//! An Uniswap-like program for the Solana blockchain.
pub mod curve;
pub mod entrypoint;
pub mod error;
pub mod instruction;

View File

@ -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,

View File

@ -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<SwapResult> {
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<u64> {
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<u64> {
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<u64> {
token_a.checked_mul(self.token_b)?.checked_div(self.token_a)
}
}
#[cfg(test)]
mod tests {
use super::*;