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:
parent
51c4dc6951
commit
13de66817d
|
@ -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"
|
||||
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
//! An Uniswap-like program for the Solana blockchain.
|
||||
|
||||
pub mod curve;
|
||||
pub mod entrypoint;
|
||||
pub mod error;
|
||||
pub mod instruction;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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::*;
|
||||
|
|
Loading…
Reference in New Issue