token-swap: Add fee account to receive withdraw / trading fees, trading token mints (#695)

* Add mints to swap info

* Add mints to JS

* Add fee account in SwapInfo / init

* Add test for 0 fee, init test fully

* Add withdraw command interface

* Add fee accounts to swap instruction

* Add calculations for swap and withdraw fees

* Run cargo fmt

* Add new fees to JS and test

* Review feedback: fixup instruction doc and clone

* Update order of accounts in instructions

* Run cargo fmt

* Fix owner fee pool token calculation to include trading fee

* Add owner fees to flat curve, per request

* Fix instruction comment numbering

* Add more errors types for clearer calculation errors

Add a check for withdrawing from fee account

* Cargo fmt
This commit is contained in:
Jon Cinque 2020-10-23 18:31:58 +02:00 committed by GitHub
parent 14d102b86a
commit b0867c7e28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1322 additions and 360 deletions

View File

@ -28,6 +28,7 @@ let owner: Account;
// Token pool
let tokenPool: Token;
let tokenAccountPool: PublicKey;
let feeAccount: PublicKey;
// Tokens swapped
let mintA: Token;
let mintB: Token;
@ -37,14 +38,25 @@ let tokenAccountB: PublicKey;
// curve type used to calculate swaps and deposits
const CURVE_TYPE = CurveType.ConstantProduct;
// Initial amount in each swap token
const BASE_AMOUNT = 1000;
let currentSwapTokenA = 1000;
let currentSwapTokenB = 1000;
let currentFeeAmount = 0;
// Amount passed to swap instruction
const SWAP_AMOUNT_IN = 100;
const SWAP_AMOUNT_OUT = 70;
const SWAP_AMOUNT_OUT = 53;
const SWAP_FEE = 6817150;
// Pool token amount minted on init
const DEFAULT_POOL_TOKEN_AMOUNT = 1000000000;
// Pool token amount to withdraw / deposit
const POOL_TOKEN_AMOUNT = 1000000;
const POOL_TOKEN_AMOUNT = 10000000;
// Pool fees
const TRADING_FEE_NUMERATOR = 1;
const TRADING_FEE_DENOMINATOR = 4;
const OWNER_TRADING_FEE_NUMERATOR = 1;
const OWNER_TRADING_FEE_DENOMINATOR = 5;
const OWNER_WITHDRAW_FEE_NUMERATOR = 1;
const OWNER_WITHDRAW_FEE_DENOMINATOR = 6;
function assert(condition, message) {
if (!condition) {
@ -131,11 +143,8 @@ export async function loadPrograms(): Promise<void> {
export async function createTokenSwap(): Promise<void> {
const connection = await getConnection();
const [tokenProgramId, tokenSwapProgramId] = await GetPrograms(connection);
const payer = await newAccountWithLamports(
connection,
100000000000 /* wag */,
);
owner = await newAccountWithLamports(connection, 100000000000 /* wag */);
const payer = await newAccountWithLamports(connection, 1000000000);
owner = await newAccountWithLamports(connection, 1000000000);
const tokenSwapAccount = new Account();
[authority, nonce] = await PublicKey.findProgramAddress(
@ -155,6 +164,7 @@ export async function createTokenSwap(): Promise<void> {
console.log('creating pool account');
tokenAccountPool = await tokenPool.createAccount(owner.publicKey);
feeAccount = await tokenPool.createAccount(owner.publicKey);
console.log('creating token A');
mintA = await Token.createMint(
@ -169,7 +179,7 @@ export async function createTokenSwap(): Promise<void> {
console.log('creating token A account');
tokenAccountA = await mintA.createAccount(authority);
console.log('minting token A to swap');
await mintA.mintTo(tokenAccountA, owner, [], BASE_AMOUNT);
await mintA.mintTo(tokenAccountA, owner, [], currentSwapTokenA);
console.log('creating token B');
mintB = await Token.createMint(
@ -184,13 +194,10 @@ export async function createTokenSwap(): Promise<void> {
console.log('creating token B account');
tokenAccountB = await mintB.createAccount(authority);
console.log('minting token B to swap');
await mintB.mintTo(tokenAccountB, owner, [], BASE_AMOUNT);
await mintB.mintTo(tokenAccountB, owner, [], currentSwapTokenB);
console.log('creating token swap');
const swapPayer = await newAccountWithLamports(
connection,
100000000000 /* wag */,
);
const swapPayer = await newAccountWithLamports(connection, 10000000000);
tokenSwap = await TokenSwap.createTokenSwap(
connection,
swapPayer,
@ -199,13 +206,20 @@ export async function createTokenSwap(): Promise<void> {
tokenAccountA,
tokenAccountB,
tokenPool.publicKey,
mintA.publicKey,
mintB.publicKey,
feeAccount,
tokenAccountPool,
tokenSwapProgramId,
tokenProgramId,
nonce,
CURVE_TYPE,
1,
4,
TRADING_FEE_NUMERATOR,
TRADING_FEE_DENOMINATOR,
OWNER_TRADING_FEE_NUMERATOR,
OWNER_TRADING_FEE_DENOMINATOR,
OWNER_WITHDRAW_FEE_NUMERATOR,
OWNER_WITHDRAW_FEE_DENOMINATOR,
);
console.log('loading token swap');
@ -219,10 +233,33 @@ export async function createTokenSwap(): Promise<void> {
assert(fetchedTokenSwap.tokenProgramId.equals(tokenProgramId));
assert(fetchedTokenSwap.tokenAccountA.equals(tokenAccountA));
assert(fetchedTokenSwap.tokenAccountB.equals(tokenAccountB));
assert(fetchedTokenSwap.mintA.equals(mintA.publicKey));
assert(fetchedTokenSwap.mintB.equals(mintB.publicKey));
assert(fetchedTokenSwap.poolToken.equals(tokenPool.publicKey));
assert(fetchedTokenSwap.feeAccount.equals(feeAccount));
assert(CURVE_TYPE == fetchedTokenSwap.curveType);
assert(1 == fetchedTokenSwap.feeNumerator.toNumber());
assert(4 == fetchedTokenSwap.feeDenominator.toNumber());
assert(
TRADING_FEE_NUMERATOR == fetchedTokenSwap.tradeFeeNumerator.toNumber(),
);
assert(
TRADING_FEE_DENOMINATOR == fetchedTokenSwap.tradeFeeDenominator.toNumber(),
);
assert(
OWNER_TRADING_FEE_NUMERATOR ==
fetchedTokenSwap.ownerTradeFeeNumerator.toNumber(),
);
assert(
OWNER_TRADING_FEE_DENOMINATOR ==
fetchedTokenSwap.ownerTradeFeeDenominator.toNumber(),
);
assert(
OWNER_WITHDRAW_FEE_NUMERATOR ==
fetchedTokenSwap.ownerWithdrawFeeNumerator.toNumber(),
);
assert(
OWNER_WITHDRAW_FEE_DENOMINATOR ==
fetchedTokenSwap.ownerWithdrawFeeDenominator.toNumber(),
);
}
export async function deposit(): Promise<void> {
@ -260,9 +297,11 @@ 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 + tokenA);
assert(info.amount.toNumber() == currentSwapTokenA + tokenA);
currentSwapTokenA += tokenA;
info = await mintB.getAccountInfo(tokenAccountB);
assert(info.amount.toNumber() == BASE_AMOUNT + tokenB);
assert(info.amount.toNumber() == currentSwapTokenB + tokenB);
currentSwapTokenB += tokenB;
info = await tokenPool.getAccountInfo(newAccountPool);
assert(info.amount.toNumber() == POOL_TOKEN_AMOUNT);
}
@ -272,8 +311,17 @@ export async function withdraw(): Promise<void> {
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;
const feeAmount = Math.floor(
(POOL_TOKEN_AMOUNT * OWNER_WITHDRAW_FEE_NUMERATOR) /
OWNER_WITHDRAW_FEE_DENOMINATOR,
);
const poolTokenAmount = POOL_TOKEN_AMOUNT - feeAmount;
const tokenA = Math.floor(
(swapTokenA.amount.toNumber() * poolTokenAmount) / supply,
);
const tokenB = Math.floor(
(swapTokenB.amount.toNumber() * poolTokenAmount) / supply,
);
console.log('Creating withdraw token A account');
let userAccountA = await mintA.createAccount(owner.publicKey);
@ -307,12 +355,17 @@ export async function withdraw(): Promise<void> {
assert(
info.amount.toNumber() == DEFAULT_POOL_TOKEN_AMOUNT - POOL_TOKEN_AMOUNT,
);
assert(swapTokenA.amount.toNumber() == BASE_AMOUNT);
assert(swapTokenB.amount.toNumber() == BASE_AMOUNT);
assert(swapTokenA.amount.toNumber() == currentSwapTokenA - tokenA);
currentSwapTokenA -= tokenA;
assert(swapTokenB.amount.toNumber() == currentSwapTokenB - tokenB);
currentSwapTokenB -= tokenB;
info = await mintA.getAccountInfo(userAccountA);
assert(info.amount.toNumber() == tokenA);
info = await mintB.getAccountInfo(userAccountB);
assert(info.amount.toNumber() == tokenB);
info = await tokenPool.getAccountInfo(feeAccount);
assert(info.amount.toNumber() == feeAmount);
currentFeeAmount = feeAmount;
}
export async function swap(): Promise<void> {
@ -337,13 +390,17 @@ export async function swap(): Promise<void> {
info = await mintA.getAccountInfo(userAccountA);
assert(info.amount.toNumber() == 0);
info = await mintA.getAccountInfo(tokenAccountA);
assert(info.amount.toNumber() == BASE_AMOUNT + SWAP_AMOUNT_IN);
assert(info.amount.toNumber() == currentSwapTokenA + SWAP_AMOUNT_IN);
currentSwapTokenA -= SWAP_AMOUNT_IN;
info = await mintB.getAccountInfo(tokenAccountB);
assert(info.amount.toNumber() == BASE_AMOUNT - SWAP_AMOUNT_OUT);
assert(info.amount.toNumber() == currentSwapTokenB - SWAP_AMOUNT_OUT);
currentSwapTokenB -= SWAP_AMOUNT_OUT;
info = await mintB.getAccountInfo(userAccountB);
assert(info.amount.toNumber() == SWAP_AMOUNT_OUT);
info = await tokenPool.getAccountInfo(tokenAccountPool);
assert(
info.amount.toNumber() == DEFAULT_POOL_TOKEN_AMOUNT - POOL_TOKEN_AMOUNT,
);
info = await tokenPool.getAccountInfo(feeAccount);
assert(info.amount.toNumber() == currentFeeAmount + SWAP_FEE);
}

View File

@ -64,10 +64,17 @@ export const TokenSwapLayout: typeof BufferLayout.Structure = BufferLayout.struc
Layout.publicKey('tokenAccountA'),
Layout.publicKey('tokenAccountB'),
Layout.publicKey('tokenPool'),
Layout.publicKey('mintA'),
Layout.publicKey('mintB'),
Layout.publicKey('feeAccount'),
BufferLayout.u8('curveType'),
Layout.uint64('feeNumerator'),
Layout.uint64('feeDenominator'),
BufferLayout.blob(48, 'padding'),
Layout.uint64('tradeFeeNumerator'),
Layout.uint64('tradeFeeDenominator'),
Layout.uint64('ownerTradeFeeNumerator'),
Layout.uint64('ownerTradeFeeDenominator'),
Layout.uint64('ownerWithdrawFeeNumerator'),
Layout.uint64('ownerWithdrawFeeDenominator'),
BufferLayout.blob(16, 'padding'),
],
);
@ -105,6 +112,11 @@ export class TokenSwap {
*/
poolToken: PublicKey;
/**
* The public key for the fee account receiving trade and/or withdrawal fees
*/
feeAccount: PublicKey;
/**
* Authority
*/
@ -121,14 +133,44 @@ export class TokenSwap {
tokenAccountB: PublicKey;
/**
* Fee numerator
* The public key for the mint of the first token account of the trading pair
*/
feeNumerator: Numberu64;
mintA: PublicKey;
/**
* Fee denominator
* The public key for the mint of the second token account of the trading pair
*/
feeDenominator: Numberu64;
mintB: PublicKey;
/**
* Trading fee numerator
*/
tradeFeeNumerator: Numberu64;
/**
* Trading fee denominator
*/
tradeFeeDenominator: Numberu64;
/**
* Owner trading fee numerator
*/
ownerTradeFeeNumerator: Numberu64;
/**
* Owner trading fee denominator
*/
ownerTradeFeeDenominator: Numberu64;
/**
* Owner withdraw fee numerator
*/
ownerWithdrawFeeNumerator: Numberu64;
/**
* Owner withdraw fee denominator
*/
ownerWithdrawFeeDenominator: Numberu64;
/**
* CurveType, current options are:
@ -159,12 +201,19 @@ export class TokenSwap {
swapProgramId: PublicKey,
tokenProgramId: PublicKey,
poolToken: PublicKey,
feeAccount: PublicKey,
authority: PublicKey,
tokenAccountA: PublicKey,
tokenAccountB: PublicKey,
mintA: PublicKey,
mintB: PublicKey,
curveType: number,
feeNumerator: Numberu64,
feeDenominator: Numberu64,
tradeFeeNumerator: Numberu64,
tradeFeeDenominator: Numberu64,
ownerTradeFeeNumerator: Numberu64,
ownerTradeFeeDenominator: Numberu64,
ownerWithdrawFeeNumerator: Numberu64,
ownerWithdrawFeeDenominator: Numberu64,
payer: Account,
) {
Object.assign(this, {
@ -173,12 +222,19 @@ export class TokenSwap {
swapProgramId,
tokenProgramId,
poolToken,
feeAccount,
authority,
tokenAccountA,
tokenAccountB,
mintA,
mintB,
curveType,
feeNumerator,
feeDenominator,
tradeFeeNumerator,
tradeFeeDenominator,
ownerTradeFeeNumerator,
ownerTradeFeeDenominator,
ownerWithdrawFeeNumerator,
ownerWithdrawFeeDenominator,
payer,
});
}
@ -202,13 +258,18 @@ export class TokenSwap {
tokenAccountA: PublicKey,
tokenAccountB: PublicKey,
tokenPool: PublicKey,
feeAccount: PublicKey,
tokenAccountPool: PublicKey,
tokenProgramId: PublicKey,
swapProgramId: PublicKey,
nonce: number,
curveType: number,
feeNumerator: number,
feeDenominator: number,
tradeFeeNumerator: number,
tradeFeeDenominator: number,
ownerTradeFeeNumerator: number,
ownerTradeFeeDenominator: number,
ownerWithdrawFeeNumerator: number,
ownerWithdrawFeeDenominator: number,
): TransactionInstruction {
const keys = [
{pubkey: tokenSwapAccount.publicKey, isSigner: false, isWritable: true},
@ -216,6 +277,7 @@ export class TokenSwap {
{pubkey: tokenAccountA, isSigner: false, isWritable: false},
{pubkey: tokenAccountB, isSigner: false, isWritable: false},
{pubkey: tokenPool, isSigner: false, isWritable: true},
{pubkey: feeAccount, isSigner: false, isWritable: false},
{pubkey: tokenAccountPool, isSigner: false, isWritable: true},
{pubkey: tokenProgramId, isSigner: false, isWritable: false},
];
@ -223,9 +285,13 @@ export class TokenSwap {
BufferLayout.u8('instruction'),
BufferLayout.u8('nonce'),
BufferLayout.u8('curveType'),
BufferLayout.nu64('feeNumerator'),
BufferLayout.nu64('feeDenominator'),
BufferLayout.blob(48, 'padding'),
BufferLayout.nu64('tradeFeeNumerator'),
BufferLayout.nu64('tradeFeeDenominator'),
BufferLayout.nu64('ownerTradeFeeNumerator'),
BufferLayout.nu64('ownerTradeFeeDenominator'),
BufferLayout.nu64('ownerWithdrawFeeNumerator'),
BufferLayout.nu64('ownerWithdrawFeeDenominator'),
BufferLayout.blob(16, 'padding'),
]);
let data = Buffer.alloc(1024);
{
@ -234,8 +300,12 @@ export class TokenSwap {
instruction: 0, // InitializeSwap instruction
nonce,
curveType,
feeNumerator,
feeDenominator,
tradeFeeNumerator,
tradeFeeDenominator,
ownerTradeFeeNumerator,
ownerTradeFeeDenominator,
ownerWithdrawFeeNumerator,
ownerWithdrawFeeDenominator,
},
data,
);
@ -266,12 +336,31 @@ export class TokenSwap {
);
const poolToken = new PublicKey(tokenSwapData.tokenPool);
const feeAccount = new PublicKey(tokenSwapData.feeAccount);
const tokenAccountA = new PublicKey(tokenSwapData.tokenAccountA);
const tokenAccountB = new PublicKey(tokenSwapData.tokenAccountB);
const mintA = new PublicKey(tokenSwapData.mintA);
const mintB = new PublicKey(tokenSwapData.mintB);
const tokenProgramId = new PublicKey(tokenSwapData.tokenProgramId);
const feeNumerator = Numberu64.fromBuffer(tokenSwapData.feeNumerator);
const feeDenominator = Numberu64.fromBuffer(tokenSwapData.feeDenominator);
const tradeFeeNumerator = Numberu64.fromBuffer(
tokenSwapData.tradeFeeNumerator,
);
const tradeFeeDenominator = Numberu64.fromBuffer(
tokenSwapData.tradeFeeDenominator,
);
const ownerTradeFeeNumerator = Numberu64.fromBuffer(
tokenSwapData.ownerTradeFeeNumerator,
);
const ownerTradeFeeDenominator = Numberu64.fromBuffer(
tokenSwapData.ownerTradeFeeDenominator,
);
const ownerWithdrawFeeNumerator = Numberu64.fromBuffer(
tokenSwapData.ownerWithdrawFeeNumerator,
);
const ownerWithdrawFeeDenominator = Numberu64.fromBuffer(
tokenSwapData.ownerWithdrawFeeDenominator,
);
const curveType = tokenSwapData.curveType;
return new TokenSwap(
@ -280,12 +369,19 @@ export class TokenSwap {
programId,
tokenProgramId,
poolToken,
feeAccount,
authority,
tokenAccountA,
tokenAccountB,
mintA,
mintB,
curveType,
feeNumerator,
feeDenominator,
tradeFeeNumerator,
tradeFeeDenominator,
ownerTradeFeeNumerator,
ownerTradeFeeDenominator,
ownerWithdrawFeeNumerator,
ownerWithdrawFeeDenominator,
payer,
);
}
@ -316,13 +412,20 @@ export class TokenSwap {
tokenAccountA: PublicKey,
tokenAccountB: PublicKey,
poolToken: PublicKey,
mintA: PublicKey,
mintB: PublicKey,
feeAccount: PublicKey,
tokenAccountPool: PublicKey,
swapProgramId: PublicKey,
tokenProgramId: PublicKey,
nonce: number,
curveType: number,
feeNumerator: number,
feeDenominator: number,
tradeFeeNumerator: number,
tradeFeeDenominator: number,
ownerTradeFeeNumerator: number,
ownerTradeFeeDenominator: number,
ownerWithdrawFeeNumerator: number,
ownerWithdrawFeeDenominator: number,
): Promise<TokenSwap> {
let transaction;
const tokenSwap = new TokenSwap(
@ -331,12 +434,19 @@ export class TokenSwap {
swapProgramId,
tokenProgramId,
poolToken,
feeAccount,
authority,
tokenAccountA,
tokenAccountB,
mintA,
mintB,
curveType,
new Numberu64(feeNumerator),
new Numberu64(feeDenominator),
new Numberu64(tradeFeeNumerator),
new Numberu64(tradeFeeDenominator),
new Numberu64(ownerTradeFeeNumerator),
new Numberu64(ownerTradeFeeDenominator),
new Numberu64(ownerWithdrawFeeNumerator),
new Numberu64(ownerWithdrawFeeDenominator),
payer,
);
@ -361,13 +471,18 @@ export class TokenSwap {
tokenAccountA,
tokenAccountB,
poolToken,
feeAccount,
tokenAccountPool,
tokenProgramId,
swapProgramId,
nonce,
curveType,
feeNumerator,
feeDenominator,
tradeFeeNumerator,
tradeFeeDenominator,
ownerTradeFeeNumerator,
ownerTradeFeeDenominator,
ownerWithdrawFeeNumerator,
ownerWithdrawFeeDenominator,
);
transaction.add(instruction);
@ -411,6 +526,8 @@ export class TokenSwap {
poolSource,
poolDestination,
userDestination,
this.poolToken,
this.feeAccount,
this.swapProgramId,
this.tokenProgramId,
amountIn,
@ -428,6 +545,8 @@ export class TokenSwap {
poolSource: PublicKey,
poolDestination: PublicKey,
userDestination: PublicKey,
poolMint: PublicKey,
feeAccount: PublicKey,
swapProgramId: PublicKey,
tokenProgramId: PublicKey,
amountIn: number | Numberu64,
@ -456,6 +575,8 @@ export class TokenSwap {
{pubkey: poolSource, isSigner: false, isWritable: true},
{pubkey: poolDestination, isSigner: false, isWritable: true},
{pubkey: userDestination, isSigner: false, isWritable: true},
{pubkey: poolMint, isSigner: false, isWritable: true},
{pubkey: feeAccount, isSigner: false, isWritable: true},
{pubkey: tokenProgramId, isSigner: false, isWritable: false},
];
return new TransactionInstruction({
@ -583,6 +704,7 @@ export class TokenSwap {
this.tokenSwap,
this.authority,
this.poolToken,
this.feeAccount,
poolAccount,
this.tokenAccountA,
this.tokenAccountB,
@ -603,6 +725,7 @@ export class TokenSwap {
tokenSwap: PublicKey,
authority: PublicKey,
poolMint: PublicKey,
feeAccount: PublicKey,
sourcePoolAccount: PublicKey,
fromA: PublicKey,
fromB: PublicKey,
@ -641,6 +764,7 @@ export class TokenSwap {
{pubkey: fromB, isSigner: false, isWritable: true},
{pubkey: userAccountA, isSigner: false, isWritable: true},
{pubkey: userAccountB, isSigner: false, isWritable: true},
{pubkey: feeAccount, isSigner: false, isWritable: true},
{pubkey: tokenProgramId, isSigner: false, isWritable: false},
];
return new TransactionInstruction({

View File

@ -26,12 +26,19 @@ declare module '@solana/spl-token-swap' {
tokenProgramId: PublicKey,
tokenSwap: PublicKey,
poolToken: PublicKey,
feeAccount: PublicKey,
authority: PublicKey,
tokenAccountA: PublicKey,
tokenAccountB: PublicKey,
mintA: PublicKey,
mintB: PublicKey,
curveType: number,
feeNumerator: Numberu64,
feeDenominator: Numberu64,
tradeFeeNumerator: Numberu64,
tradeFeeDenominator: Numberu64,
ownerTradeFeeNumerator: Numberu64,
ownerTradeFeeDenominator: Numberu64,
ownerWithdrawFeeNumerator: Numberu64,
ownerWithdrawFeeDenominator: Numberu64,
payer: Account,
);
@ -45,13 +52,18 @@ declare module '@solana/spl-token-swap' {
tokenAccountA: PublicKey,
tokenAccountB: PublicKey,
tokenPool: PublicKey,
feeAccount: PublicKey,
tokenAccountPool: PublicKey,
tokenProgramId: PublicKey,
swapProgramId: PublicKey,
nonce: number,
curveType: number,
feeNumerator: number,
feeDenominator: number,
tradeFeeNumerator: number,
tradeFeeDenominator: number,
ownerTradeFeeNumerator: number,
ownerTradeFeeDenominator: number,
ownerWithdrawFeeNumerator: number,
ownerWithdrawFeeDenominator: number,
): TransactionInstruction;
static loadTokenSwap(
@ -69,12 +81,19 @@ declare module '@solana/spl-token-swap' {
tokenAccountA: PublicKey,
tokenAccountB: PublicKey,
tokenPool: PublicKey,
mintA: PublicKey,
mintB: PublicKey,
feeAccount: PublicKey,
tokenAccountPool: PublicKey,
tokenProgramId: PublicKey,
nonce: number,
curveType: number,
feeNumerator: number,
feeDenominator: number,
tradeFeeNumerator: number,
tradeFeeDenominator: number,
ownerTradeFeeNumerator: number,
ownerTradeFeeDenominator: number,
ownerWithdrawFeeNumerator: number,
ownerWithdrawFeeDenominator: number,
swapProgramId: PublicKey,
): Promise<TokenSwap>;
@ -94,6 +113,8 @@ declare module '@solana/spl-token-swap' {
poolSource: PublicKey,
poolDestination: PublicKey,
userDestination: PublicKey,
poolMint: PublicKey,
feeAccount: PublicKey,
swapProgramId: PublicKey,
tokenProgramId: PublicKey,
amountIn: number | Numberu64,
@ -131,14 +152,8 @@ declare module '@solana/spl-token-swap' {
): TransactionInstruction;
withdraw(
authority: PublicKey,
poolMint: PublicKey,
sourcePoolAccount: PublicKey,
fromA: PublicKey,
fromB: PublicKey,
userAccountA: PublicKey,
userAccountB: PublicKey,
tokenProgramId: PublicKey,
poolTokenAmount: number | Numberu64,
minimumTokenA: number | Numberu64,
minimumTokenB: number | Numberu64,
@ -148,6 +163,7 @@ declare module '@solana/spl-token-swap' {
tokenSwap: PublicKey,
authority: PublicKey,
poolMint: PublicKey,
feeAccount: PublicKey,
sourcePoolAccount: PublicKey,
fromA: PublicKey,
fromB: PublicKey,

View File

@ -23,12 +23,19 @@ declare module '@solana/spl-token-swap' {
tokenProgramId: PublicKey,
tokenSwap: PublicKey,
poolToken: PublicKey,
feeAccount: PublicKey,
authority: PublicKey,
tokenAccountA: PublicKey,
tokenAccountB: PublicKey,
mintA: PublicKey,
mintB: PublicKey,
curveType: number,
feeNumerator: Numberu64,
feeDenominator: Numberu64,
tradeFeeNumerator: Numberu64,
tradeFeeDenominator: Numberu64,
ownerTradeFeeNumerator: Numberu64,
ownerTradeFeeDenominator: Numberu64,
ownerWithdrawFeeNumerator: Numberu64,
ownerWithdrawFeeDenominator: Numberu64,
payer: Account,
): TokenSwap;
@ -43,12 +50,17 @@ declare module '@solana/spl-token-swap' {
tokenAccountA: PublicKey,
tokenAccountB: PublicKey,
tokenPool: PublicKey,
feeAccount: PublicKey,
tokenAccountPool: PublicKey,
tokenProgramId: PublicKey,
nonce: number,
curveType: number,
feeNumerator: number,
feeDenominator: number,
tradeFeeNumerator: number,
tradeFeeDenominator: number,
ownerTradeFeeNumerator: number,
ownerTradeFeeDenominator: number,
ownerWithdrawFeeNumerator: number,
ownerWithdrawFeeDenominator: number,
): TransactionInstruction;
static loadTokenSwap(
@ -66,12 +78,19 @@ declare module '@solana/spl-token-swap' {
tokenAccountA: PublicKey,
tokenAccountB: PublicKey,
tokenPool: PublicKey,
mintA: PublicKey,
mintB: PublicKey,
feeAccount: PublicKey,
tokenAccountPool: PublicKey,
tokenProgramId: PublicKey,
nonce: number,
curveType: number,
feeNumerator: number,
feeDenominator: number,
tradeFeeNumerator: number,
tradeFeeDenominator: number,
ownerTradeFeeNumerator: number,
ownerTradeFeeDenominator: number,
ownerWithdrawFeeNumerator: number,
ownerWithdrawFeeDenominator: number,
programId: PublicKey,
): Promise<TokenSwap>;
@ -91,6 +110,8 @@ declare module '@solana/spl-token-swap' {
poolSource: PublicKey,
poolDestination: PublicKey,
userDestination: PublicKey,
poolMint: PublicKey,
feeAccount: PublicKey,
swapProgramId: PublicKey,
tokenProgramId: PublicKey,
amountIn: number | Numberu64,
@ -128,14 +149,9 @@ declare module '@solana/spl-token-swap' {
): TransactionInstruction;
withdraw(
authority: PublicKey,
poolMint: PublicKey,
sourcePoolAccount: PublicKey,
fromA: PublicKey,
fromB: PublicKey,
userAccountA: PublicKey,
userAccountB: PublicKey,
tokenProgramId: PublicKey,
poolAccount: PublicKey,
poolTokenAmount: number | Numberu64,
minimumTokenA: number | Numberu64,
minimumTokenB: number | Numberu64,
@ -145,6 +161,7 @@ declare module '@solana/spl-token-swap' {
tokenSwap: PublicKey,
authority: PublicKey,
poolMint: PublicKey,
feeAccount: PublicKey,
sourcePoolAccount: PublicKey,
fromA: PublicKey,
fromB: PublicKey,

View File

@ -50,6 +50,18 @@ impl Default for SwapCurve {
}
}
/// Clone takes advantage of pack / unpack to get around the difficulty of
/// cloning dynamic objects.
/// Note that this is only to be used for testing.
#[cfg(test)]
impl Clone for SwapCurve {
fn clone(&self) -> Self {
let mut packed_self = [0u8; Self::LEN];
Self::pack_into_slice(self, &mut packed_self);
Self::unpack_from_slice(&packed_self).unwrap()
}
}
/// Simple implementation for PartialEq which assumes that the output of
/// `Pack` is enough to guarantee equality
impl PartialEq for SwapCurve {
@ -134,18 +146,60 @@ pub trait CurveCalculator: Debug + DynPack {
swap_destination_amount: u64,
) -> Option<SwapResult>;
/// Get the supply of a new pool (can be a default amount or calculated
/// based on parameters)
fn new_pool_supply(&self) -> u64;
/// Calculate the withdraw fee in pool tokens
/// Default implementation assumes no fee
fn owner_withdraw_fee(&self, _pool_tokens: u64) -> Option<u64> {
Some(0)
}
/// Get the amount of liquidity tokens for pool tokens given the total amount
/// of liquidity tokens in the pool
fn liquidity_tokens(
/// Calculate the trading fee in trading tokens
/// Default implementation assumes no fee
fn trading_fee(&self, _trading_tokens: u64) -> Option<u64> {
Some(0)
}
/// Calculate the pool token equivalent of the owner fee on trade
/// See the math at: https://balancer.finance/whitepaper/#single-asset-deposit
/// For the moment, we do an approximation for the square root. For numbers
/// just above 1, simply dividing by 2 brings you very close to the correct
/// value.
fn owner_fee_to_pool_tokens(
&self,
owner_fee: u64,
trading_token_amount: u64,
pool_supply: u64,
tokens_in_pool: u64,
) -> Option<u64> {
// Get the trading fee incurred if the owner fee is swapped for the other side
let trade_fee = self.trading_fee(owner_fee)?;
let owner_fee = owner_fee.checked_sub(trade_fee)?;
pool_supply
.checked_mul(owner_fee)?
.checked_div(trading_token_amount)?
.checked_div(tokens_in_pool)
}
/// Get the supply for a new pool
/// The default implementation is a Balancer-style fixed initial supply
fn new_pool_supply(&self) -> u64 {
INITIAL_SWAP_POOL_AMOUNT
}
/// Get the amount of trading tokens for the given amount of pool tokens,
/// provided the total trading tokens and supply of pool tokens.
/// The default implementation is a simple ratio calculation for how many
/// trading tokens correspond to a certain number of pool tokens
fn pool_tokens_to_trading_tokens(
&self,
pool_tokens: u64,
pool_token_supply: u64,
total_liquidity_tokens: u64,
) -> Option<u64>;
total_trading_tokens: u64,
) -> Option<u64> {
pool_tokens
.checked_mul(total_trading_tokens)?
.checked_div(pool_token_supply)
.and_then(map_zero_to_none)
}
}
/// Encodes all results of swapping from a source token to a destination token
@ -156,6 +210,10 @@ pub struct SwapResult {
pub new_destination_amount: u64,
/// Amount of destination token swapped
pub amount_swapped: u64,
/// Amount of source tokens going to pool holders
pub trade_fee: u64,
/// Amount of source tokens going to owner
pub owner_fee: u64,
}
/// Helper function for mapping to SwapError::CalculationFailure
@ -171,9 +229,32 @@ fn map_zero_to_none(x: u64) -> Option<u64> {
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FlatCurve {
/// Fee numerator
pub fee_numerator: u64,
pub trade_fee_numerator: u64,
/// Fee denominator
pub fee_denominator: u64,
pub trade_fee_denominator: u64,
/// Owner trade fee numerator
pub owner_trade_fee_numerator: u64,
/// Owner trade fee denominator
pub owner_trade_fee_denominator: u64,
/// Owner withdraw fee numerator
pub owner_withdraw_fee_numerator: u64,
/// Owner withdraw fee denominator
pub owner_withdraw_fee_denominator: u64,
}
fn calculate_fee(token_amount: u64, fee_numerator: u64, fee_denominator: u64) -> Option<u64> {
if fee_numerator == 0 {
Some(0)
} else {
let fee = token_amount
.checked_mul(fee_numerator)?
.checked_div(fee_denominator)?;
if fee == 0 {
Some(1) // minimum fee of one token
} else {
Some(fee)
}
}
}
impl CurveCalculator for FlatCurve {
@ -185,14 +266,20 @@ impl CurveCalculator for FlatCurve {
swap_destination_amount: u64,
) -> Option<SwapResult> {
// debit the fee to calculate the amount swapped
let mut fee = source_amount
.checked_mul(self.fee_numerator)?
.checked_div(self.fee_denominator)?;
if fee == 0 {
fee = 1; // minimum fee of one token
}
let trade_fee = calculate_fee(
source_amount,
self.trade_fee_numerator,
self.trade_fee_denominator,
)?;
let owner_fee = calculate_fee(
source_amount,
self.owner_trade_fee_numerator,
self.owner_trade_fee_denominator,
)?;
let amount_swapped = source_amount.checked_sub(fee)?;
let amount_swapped = source_amount
.checked_sub(trade_fee)?
.checked_sub(owner_fee)?;
let new_destination_amount = swap_destination_amount.checked_sub(amount_swapped)?;
// actually add the whole amount coming in
@ -201,26 +288,18 @@ impl CurveCalculator for FlatCurve {
new_source_amount,
new_destination_amount,
amount_swapped,
trade_fee,
owner_fee,
})
}
/// Balancer-style fixed initial supply
fn new_pool_supply(&self) -> u64 {
INITIAL_SWAP_POOL_AMOUNT
}
/// Simple ratio calculation for how many liquidity tokens correspond to
/// a certain number of pool tokens
fn liquidity_tokens(
&self,
pool_tokens: u64,
pool_token_supply: u64,
total_liquidity_tokens: u64,
) -> Option<u64> {
pool_tokens
.checked_mul(total_liquidity_tokens)?
.checked_div(pool_token_supply)
.and_then(map_zero_to_none)
/// Calculate the withdraw fee in pool tokens
fn owner_withdraw_fee(&self, pool_tokens: u64) -> Option<u64> {
calculate_fee(
pool_tokens,
self.owner_withdraw_fee_numerator,
self.owner_withdraw_fee_denominator,
)
}
}
@ -232,15 +311,25 @@ impl IsInitialized for FlatCurve {
}
impl Sealed for FlatCurve {}
impl Pack for FlatCurve {
const LEN: usize = 16;
/// Unpacks a byte buffer into a SwapCurve
const LEN: usize = 48;
fn unpack_from_slice(input: &[u8]) -> Result<FlatCurve, ProgramError> {
let input = array_ref![input, 0, 16];
let input = array_ref![input, 0, 48];
#[allow(clippy::ptr_offset_with_cast)]
let (fee_numerator, fee_denominator) = array_refs![input, 8, 8];
let (
trade_fee_numerator,
trade_fee_denominator,
owner_trade_fee_numerator,
owner_trade_fee_denominator,
owner_withdraw_fee_numerator,
owner_withdraw_fee_denominator,
) = array_refs![input, 8, 8, 8, 8, 8, 8];
Ok(Self {
fee_numerator: u64::from_le_bytes(*fee_numerator),
fee_denominator: u64::from_le_bytes(*fee_denominator),
trade_fee_numerator: u64::from_le_bytes(*trade_fee_numerator),
trade_fee_denominator: u64::from_le_bytes(*trade_fee_denominator),
owner_trade_fee_numerator: u64::from_le_bytes(*owner_trade_fee_numerator),
owner_trade_fee_denominator: u64::from_le_bytes(*owner_trade_fee_denominator),
owner_withdraw_fee_numerator: u64::from_le_bytes(*owner_withdraw_fee_numerator),
owner_withdraw_fee_denominator: u64::from_le_bytes(*owner_withdraw_fee_denominator),
})
}
@ -251,20 +340,39 @@ impl Pack for FlatCurve {
impl DynPack for FlatCurve {
fn pack_into_slice(&self, output: &mut [u8]) {
let output = array_mut_ref![output, 0, 16];
let (fee_numerator, fee_denominator) = mut_array_refs![output, 8, 8];
*fee_numerator = self.fee_numerator.to_le_bytes();
*fee_denominator = self.fee_denominator.to_le_bytes();
let output = array_mut_ref![output, 0, 48];
let (
trade_fee_numerator,
trade_fee_denominator,
owner_trade_fee_numerator,
owner_trade_fee_denominator,
owner_withdraw_fee_numerator,
owner_withdraw_fee_denominator,
) = mut_array_refs![output, 8, 8, 8, 8, 8, 8];
*trade_fee_numerator = self.trade_fee_numerator.to_le_bytes();
*trade_fee_denominator = self.trade_fee_denominator.to_le_bytes();
*owner_trade_fee_numerator = self.owner_trade_fee_numerator.to_le_bytes();
*owner_trade_fee_denominator = self.owner_trade_fee_denominator.to_le_bytes();
*owner_withdraw_fee_numerator = self.owner_withdraw_fee_numerator.to_le_bytes();
*owner_withdraw_fee_denominator = self.owner_withdraw_fee_denominator.to_le_bytes();
}
}
/// The Uniswap invariant calculator.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ConstantProductCurve {
/// Fee numerator
pub fee_numerator: u64,
/// Fee denominator
pub fee_denominator: u64,
/// Trade fee numerator
pub trade_fee_numerator: u64,
/// Trade fee denominator
pub trade_fee_denominator: u64,
/// Owner trade fee numerator
pub owner_trade_fee_numerator: u64,
/// Owner trade fee denominator
pub owner_trade_fee_denominator: u64,
/// Owner withdraw fee numerator
pub owner_withdraw_fee_numerator: u64,
/// Owner withdraw fee denominator
pub owner_withdraw_fee_denominator: u64,
}
impl CurveCalculator for ConstantProductCurve {
@ -275,18 +383,19 @@ impl CurveCalculator for ConstantProductCurve {
swap_source_amount: u64,
swap_destination_amount: u64,
) -> Option<SwapResult> {
let invariant = swap_source_amount.checked_mul(swap_destination_amount)?;
// debit the fee to calculate the amount swapped
let mut fee = source_amount
.checked_mul(self.fee_numerator)?
.checked_div(self.fee_denominator)?;
if fee == 0 {
fee = 1; // minimum fee of one token
}
let trade_fee = self.trading_fee(source_amount)?;
let owner_fee = calculate_fee(
source_amount,
self.owner_trade_fee_numerator,
self.owner_trade_fee_denominator,
)?;
let invariant = swap_source_amount.checked_mul(swap_destination_amount)?;
let new_source_amount_less_fee = swap_source_amount
.checked_add(source_amount)?
.checked_sub(fee)?;
.checked_sub(trade_fee)?
.checked_sub(owner_fee)?;
let new_destination_amount = invariant.checked_div(new_source_amount_less_fee)?;
let amount_swapped =
map_zero_to_none(swap_destination_amount.checked_sub(new_destination_amount)?)?;
@ -297,27 +406,27 @@ impl CurveCalculator for ConstantProductCurve {
new_source_amount,
new_destination_amount,
amount_swapped,
trade_fee,
owner_fee,
})
}
/// Balancer-style supply starts at a constant. This could be modified to
/// follow the geometric mean, as done in Uniswap v2.
fn new_pool_supply(&self) -> u64 {
INITIAL_SWAP_POOL_AMOUNT
/// Calculate the withdraw fee in pool tokens
fn owner_withdraw_fee(&self, pool_tokens: u64) -> Option<u64> {
calculate_fee(
pool_tokens,
self.owner_withdraw_fee_numerator,
self.owner_withdraw_fee_denominator,
)
}
/// Simple ratio calculation to get the amount of liquidity tokens given
/// pool information
fn liquidity_tokens(
&self,
pool_tokens: u64,
pool_token_supply: u64,
total_liquidity_tokens: u64,
) -> Option<u64> {
pool_tokens
.checked_mul(total_liquidity_tokens)?
.checked_div(pool_token_supply)
.and_then(map_zero_to_none)
/// Calculate the trading fee in trading tokens
fn trading_fee(&self, trading_tokens: u64) -> Option<u64> {
calculate_fee(
trading_tokens,
self.trade_fee_numerator,
self.trade_fee_denominator,
)
}
}
@ -329,14 +438,25 @@ impl IsInitialized for ConstantProductCurve {
}
impl Sealed for ConstantProductCurve {}
impl Pack for ConstantProductCurve {
const LEN: usize = 16;
const LEN: usize = 48;
fn unpack_from_slice(input: &[u8]) -> Result<ConstantProductCurve, ProgramError> {
let input = array_ref![input, 0, 16];
let input = array_ref![input, 0, 48];
#[allow(clippy::ptr_offset_with_cast)]
let (fee_numerator, fee_denominator) = array_refs![input, 8, 8];
let (
trade_fee_numerator,
trade_fee_denominator,
owner_trade_fee_numerator,
owner_trade_fee_denominator,
owner_withdraw_fee_numerator,
owner_withdraw_fee_denominator,
) = array_refs![input, 8, 8, 8, 8, 8, 8];
Ok(Self {
fee_numerator: u64::from_le_bytes(*fee_numerator),
fee_denominator: u64::from_le_bytes(*fee_denominator),
trade_fee_numerator: u64::from_le_bytes(*trade_fee_numerator),
trade_fee_denominator: u64::from_le_bytes(*trade_fee_denominator),
owner_trade_fee_numerator: u64::from_le_bytes(*owner_trade_fee_numerator),
owner_trade_fee_denominator: u64::from_le_bytes(*owner_trade_fee_denominator),
owner_withdraw_fee_numerator: u64::from_le_bytes(*owner_withdraw_fee_numerator),
owner_withdraw_fee_denominator: u64::from_le_bytes(*owner_withdraw_fee_denominator),
})
}
@ -347,10 +467,21 @@ impl Pack for ConstantProductCurve {
impl DynPack for ConstantProductCurve {
fn pack_into_slice(&self, output: &mut [u8]) {
let output = array_mut_ref![output, 0, 16];
let (fee_numerator, fee_denominator) = mut_array_refs![output, 8, 8];
*fee_numerator = self.fee_numerator.to_le_bytes();
*fee_denominator = self.fee_denominator.to_le_bytes();
let output = array_mut_ref![output, 0, 48];
let (
trade_fee_numerator,
trade_fee_denominator,
owner_trade_fee_numerator,
owner_trade_fee_denominator,
owner_withdraw_fee_numerator,
owner_withdraw_fee_denominator,
) = mut_array_refs![output, 8, 8, 8, 8, 8, 8];
*trade_fee_numerator = self.trade_fee_numerator.to_le_bytes();
*trade_fee_denominator = self.trade_fee_denominator.to_le_bytes();
*owner_trade_fee_numerator = self.owner_trade_fee_numerator.to_le_bytes();
*owner_trade_fee_denominator = self.owner_trade_fee_denominator.to_le_bytes();
*owner_withdraw_fee_numerator = self.owner_withdraw_fee_numerator.to_le_bytes();
*owner_withdraw_fee_denominator = self.owner_withdraw_fee_denominator.to_le_bytes();
}
}
@ -360,53 +491,72 @@ mod tests {
#[test]
fn initial_pool_amount() {
let fee_numerator = 0;
let fee_denominator = 1;
let trade_fee_numerator = 0;
let trade_fee_denominator = 1;
let owner_trade_fee_numerator = 0;
let owner_trade_fee_denominator = 1;
let owner_withdraw_fee_numerator = 0;
let owner_withdraw_fee_denominator = 1;
let calculator = ConstantProductCurve {
fee_numerator,
fee_denominator,
trade_fee_numerator,
trade_fee_denominator,
owner_trade_fee_numerator,
owner_trade_fee_denominator,
owner_withdraw_fee_numerator,
owner_withdraw_fee_denominator,
};
assert_eq!(calculator.new_pool_supply(), INITIAL_SWAP_POOL_AMOUNT);
}
fn check_liquidity_pool_token_rate(
token_a: u64,
deposit: u64,
supply: u64,
expected: Option<u64>,
) {
let fee_numerator = 0;
let fee_denominator = 1;
fn check_pool_token_rate(token_a: u64, deposit: u64, supply: u64, expected: Option<u64>) {
let trade_fee_numerator = 0;
let trade_fee_denominator = 1;
let owner_trade_fee_numerator = 0;
let owner_trade_fee_denominator = 1;
let owner_withdraw_fee_numerator = 0;
let owner_withdraw_fee_denominator = 1;
let calculator = ConstantProductCurve {
fee_numerator,
fee_denominator,
trade_fee_numerator,
trade_fee_denominator,
owner_trade_fee_numerator,
owner_trade_fee_denominator,
owner_withdraw_fee_numerator,
owner_withdraw_fee_denominator,
};
assert_eq!(
calculator.liquidity_tokens(deposit, supply, token_a),
calculator.pool_tokens_to_trading_tokens(deposit, supply, token_a),
expected
);
}
#[test]
fn issued_tokens() {
check_liquidity_pool_token_rate(2, 5, 10, Some(1));
check_liquidity_pool_token_rate(10, 5, 10, Some(5));
check_liquidity_pool_token_rate(5, 5, 10, Some(2));
check_liquidity_pool_token_rate(5, 5, 10, Some(2));
check_liquidity_pool_token_rate(u64::MAX, 5, 10, None);
fn trading_token_conversion() {
check_pool_token_rate(2, 5, 10, Some(1));
check_pool_token_rate(10, 5, 10, Some(5));
check_pool_token_rate(5, 5, 10, Some(2));
check_pool_token_rate(5, 5, 10, Some(2));
check_pool_token_rate(u64::MAX, 5, 10, None);
}
#[test]
fn constant_product_swap_calculation() {
fn constant_product_swap_calculation_trade_fee() {
// calculation on https://github.com/solana-labs/solana-program-library/issues/341
let swap_source_amount: u64 = 1000;
let swap_destination_amount: u64 = 50000;
let fee_numerator: u64 = 1;
let fee_denominator: u64 = 100;
let trade_fee_numerator: u64 = 1;
let trade_fee_denominator: u64 = 100;
let owner_trade_fee_numerator: u64 = 0;
let owner_trade_fee_denominator: u64 = 0;
let owner_withdraw_fee_numerator: u64 = 0;
let owner_withdraw_fee_denominator: u64 = 0;
let source_amount: u64 = 100;
let curve = ConstantProductCurve {
fee_numerator,
fee_denominator,
trade_fee_numerator,
trade_fee_denominator,
owner_trade_fee_numerator,
owner_trade_fee_denominator,
owner_withdraw_fee_numerator,
owner_withdraw_fee_denominator,
};
let result = curve
.swap(source_amount, swap_source_amount, swap_destination_amount)
@ -414,25 +564,81 @@ mod tests {
assert_eq!(result.new_source_amount, 1100);
assert_eq!(result.amount_swapped, 4505);
assert_eq!(result.new_destination_amount, 45495);
assert_eq!(result.trade_fee, 1);
assert_eq!(result.owner_fee, 0);
}
#[test]
fn constant_product_swap_calculation_owner_fee() {
// calculation on https://github.com/solana-labs/solana-program-library/issues/341
let swap_source_amount: u64 = 1000;
let swap_destination_amount: u64 = 50000;
let trade_fee_numerator: u64 = 0;
let trade_fee_denominator: u64 = 0;
let owner_trade_fee_numerator: u64 = 1;
let owner_trade_fee_denominator: u64 = 100;
let owner_withdraw_fee_numerator: u64 = 0;
let owner_withdraw_fee_denominator: u64 = 0;
let source_amount: u64 = 100;
let curve = ConstantProductCurve {
trade_fee_numerator,
trade_fee_denominator,
owner_trade_fee_numerator,
owner_trade_fee_denominator,
owner_withdraw_fee_numerator,
owner_withdraw_fee_denominator,
};
let result = curve
.swap(source_amount, swap_source_amount, swap_destination_amount)
.unwrap();
assert_eq!(result.new_source_amount, 1100);
assert_eq!(result.amount_swapped, 4505);
assert_eq!(result.new_destination_amount, 45495);
assert_eq!(result.trade_fee, 0);
assert_eq!(result.owner_fee, 1);
}
#[test]
fn constant_product_swap_no_fee() {
let swap_source_amount: u64 = 1000;
let swap_destination_amount: u64 = 50000;
let source_amount: u64 = 100;
let curve = ConstantProductCurve::default();
let result = curve
.swap(source_amount, swap_source_amount, swap_destination_amount)
.unwrap();
assert_eq!(result.new_source_amount, 1100);
assert_eq!(result.amount_swapped, 4546);
assert_eq!(result.new_destination_amount, 45454);
}
#[test]
fn flat_swap_calculation() {
let swap_source_amount: u64 = 1000;
let swap_destination_amount: u64 = 50000;
let fee_numerator: u64 = 1;
let fee_denominator: u64 = 100;
let trade_fee_numerator: u64 = 1;
let trade_fee_denominator: u64 = 100;
let owner_trade_fee_numerator: u64 = 2;
let owner_trade_fee_denominator: u64 = 100;
let owner_withdraw_fee_numerator: u64 = 2;
let owner_withdraw_fee_denominator: u64 = 100;
let source_amount: u64 = 100;
let curve = FlatCurve {
fee_numerator,
fee_denominator,
trade_fee_numerator,
trade_fee_denominator,
owner_trade_fee_numerator,
owner_trade_fee_denominator,
owner_withdraw_fee_numerator,
owner_withdraw_fee_denominator,
};
let result = curve
.swap(source_amount, swap_source_amount, swap_destination_amount)
.unwrap();
let amount_swapped = 99;
let amount_swapped = 97;
assert_eq!(result.new_source_amount, 1100);
assert_eq!(result.amount_swapped, amount_swapped);
assert_eq!(result.trade_fee, 1);
assert_eq!(result.owner_fee, 2);
assert_eq!(
result.new_destination_amount,
swap_destination_amount - amount_swapped
@ -441,11 +647,19 @@ mod tests {
#[test]
fn pack_flat_curve() {
let fee_numerator = 1;
let fee_denominator = 4;
let trade_fee_numerator = 1;
let trade_fee_denominator = 4;
let owner_trade_fee_numerator = 2;
let owner_trade_fee_denominator = 5;
let owner_withdraw_fee_numerator = 4;
let owner_withdraw_fee_denominator = 10;
let curve = FlatCurve {
fee_numerator,
fee_denominator,
trade_fee_numerator,
trade_fee_denominator,
owner_trade_fee_numerator,
owner_trade_fee_denominator,
owner_withdraw_fee_numerator,
owner_withdraw_fee_denominator,
};
let mut packed = [0u8; FlatCurve::LEN];
@ -454,19 +668,31 @@ mod tests {
assert_eq!(curve, unpacked);
let mut packed = vec![];
packed.extend_from_slice(&fee_numerator.to_le_bytes());
packed.extend_from_slice(&fee_denominator.to_le_bytes());
packed.extend_from_slice(&trade_fee_numerator.to_le_bytes());
packed.extend_from_slice(&trade_fee_denominator.to_le_bytes());
packed.extend_from_slice(&owner_trade_fee_numerator.to_le_bytes());
packed.extend_from_slice(&owner_trade_fee_denominator.to_le_bytes());
packed.extend_from_slice(&owner_withdraw_fee_numerator.to_le_bytes());
packed.extend_from_slice(&owner_withdraw_fee_denominator.to_le_bytes());
let unpacked = FlatCurve::unpack(&packed).unwrap();
assert_eq!(curve, unpacked);
}
#[test]
fn pack_constant_product_curve() {
let fee_numerator = 1;
let fee_denominator = 4;
let trade_fee_numerator = 1;
let trade_fee_denominator = 4;
let owner_trade_fee_numerator = 2;
let owner_trade_fee_denominator = 5;
let owner_withdraw_fee_numerator = 4;
let owner_withdraw_fee_denominator = 10;
let curve = ConstantProductCurve {
fee_numerator,
fee_denominator,
trade_fee_numerator,
trade_fee_denominator,
owner_trade_fee_numerator,
owner_trade_fee_denominator,
owner_withdraw_fee_numerator,
owner_withdraw_fee_denominator,
};
let mut packed = [0u8; ConstantProductCurve::LEN];
@ -475,19 +701,31 @@ mod tests {
assert_eq!(curve, unpacked);
let mut packed = vec![];
packed.extend_from_slice(&fee_numerator.to_le_bytes());
packed.extend_from_slice(&fee_denominator.to_le_bytes());
packed.extend_from_slice(&trade_fee_numerator.to_le_bytes());
packed.extend_from_slice(&trade_fee_denominator.to_le_bytes());
packed.extend_from_slice(&owner_trade_fee_numerator.to_le_bytes());
packed.extend_from_slice(&owner_trade_fee_denominator.to_le_bytes());
packed.extend_from_slice(&owner_withdraw_fee_numerator.to_le_bytes());
packed.extend_from_slice(&owner_withdraw_fee_denominator.to_le_bytes());
let unpacked = ConstantProductCurve::unpack(&packed).unwrap();
assert_eq!(curve, unpacked);
}
#[test]
fn pack_swap_curve() {
let fee_numerator = 1;
let fee_denominator = 4;
let trade_fee_numerator = 1;
let trade_fee_denominator = 4;
let owner_trade_fee_numerator = 2;
let owner_trade_fee_denominator = 5;
let owner_withdraw_fee_numerator = 4;
let owner_withdraw_fee_denominator = 10;
let curve = ConstantProductCurve {
fee_numerator,
fee_denominator,
trade_fee_numerator,
trade_fee_denominator,
owner_trade_fee_numerator,
owner_trade_fee_denominator,
owner_withdraw_fee_numerator,
owner_withdraw_fee_denominator,
};
let curve_type = CurveType::ConstantProduct;
let swap_curve = SwapCurve {
@ -502,9 +740,13 @@ mod tests {
let mut packed = vec![];
packed.push(curve_type as u8);
packed.extend_from_slice(&fee_numerator.to_le_bytes());
packed.extend_from_slice(&fee_denominator.to_le_bytes());
packed.extend_from_slice(&[0u8; 48]); // padding
packed.extend_from_slice(&trade_fee_numerator.to_le_bytes());
packed.extend_from_slice(&trade_fee_denominator.to_le_bytes());
packed.extend_from_slice(&owner_trade_fee_numerator.to_le_bytes());
packed.extend_from_slice(&owner_trade_fee_denominator.to_le_bytes());
packed.extend_from_slice(&owner_withdraw_fee_numerator.to_le_bytes());
packed.extend_from_slice(&owner_withdraw_fee_denominator.to_le_bytes());
packed.extend_from_slice(&[0u8; 16]); // padding
let unpacked = SwapCurve::unpack_from_slice(&packed).unwrap();
assert_eq!(swap_curve, unpacked);
}

View File

@ -46,8 +46,8 @@ pub enum SwapError {
/// The output token is invalid for swap.
#[error("InvalidOutput")]
InvalidOutput,
/// The calculation failed.
#[error("CalculationFailure")]
/// General calculation failure due to overflow or underflow
#[error("General calculation failure due to overflow or underflow")]
CalculationFailure,
/// Invalid instruction number passed in.
#[error("Invalid instruction")]
@ -64,6 +64,15 @@ pub enum SwapError {
/// The pool token mint has a freeze authority.
#[error("Pool token mint has a freeze authority")]
InvalidFreezeAuthority,
/// The pool fee token account is incorrect
#[error("Pool fee token account incorrect")]
IncorrectFeeAccount,
/// Given pool token amount results in zero trading tokens
#[error("Given pool token amount results in zero trading tokens")]
ZeroTradingTokens,
/// The fee calculation failed due to overflow, underflow, or unexpected 0
#[error("Fee calculation failed due to overflow, underflow, or unexpected 0")]
FeeCalculationFailure,
}
impl From<SwapError> for ProgramError {
fn from(e: SwapError) -> Self {

View File

@ -2,7 +2,7 @@
#![allow(clippy::too_many_arguments)]
use crate::curve::{ConstantProductCurve, CurveType, FlatCurve, SwapCurve};
use crate::curve::SwapCurve;
use crate::error::SwapError;
use solana_sdk::{
instruction::{AccountMeta, Instruction},
@ -24,8 +24,11 @@ pub enum SwapInstruction {
/// 2. `[]` token_a Account. Must be non zero, owned by $authority.
/// 3. `[]` token_b Account. Must be non zero, owned by $authority.
/// 4. `[writable]` Pool Token Mint. Must be empty, owned by $authority.
/// 5. `[writable]` Pool Token Account to deposit the minted tokens. Must be empty, owned by user.
/// 6. '[]` Token program id
/// 5. `[]` Pool Token Account to deposit trading and withdraw fees.
/// Must be empty, not owned by $authority
/// 6. `[writable]` Pool Token Account to deposit the initial pool token
/// supply. Must be empty, not owned by $authority.
/// 7. '[]` Token program id
Initialize {
/// nonce used to create valid program address
nonce: u8,
@ -42,7 +45,9 @@ pub enum SwapInstruction {
/// 3. `[writable]` token_(A|B) Base Account to swap INTO. Must be the SOURCE token.
/// 4. `[writable]` token_(A|B) Base Account to swap FROM. Must be the DESTINATION token.
/// 5. `[writable]` token_(A|B) DESTINATION Account assigned to USER as the owner.
/// 6. '[]` Token program id
/// 6. `[writable]` Pool token mint, to generate trading fees
/// 7. `[writable]` Fee account, to receive trading fees
/// 8. '[]` Token program id
Swap {
/// SOURCE amount to transfer, output to DESTINATION is based on the exchange rate
amount_in: u64,
@ -82,7 +87,8 @@ pub enum SwapInstruction {
/// 5. `[writable]` token_b Swap Account to withdraw FROM.
/// 6. `[writable]` token_a user Account to credit.
/// 7. `[writable]` token_b user Account to credit.
/// 8. '[]` Token program id
/// 8. `[writable]` Fee account, to receive withdrawal fees
/// 9. '[]` Token program id
Withdraw {
/// Amount of pool tokens to burn. User receives an output of token a
/// and b based on the percentage of the pool tokens that are returned.
@ -203,25 +209,11 @@ pub fn initialize(
token_a_pubkey: &Pubkey,
token_b_pubkey: &Pubkey,
pool_pubkey: &Pubkey,
fee_pubkey: &Pubkey,
destination_pubkey: &Pubkey,
nonce: u8,
curve_type: CurveType,
fee_numerator: u64,
fee_denominator: u64,
swap_curve: SwapCurve,
) -> Result<Instruction, ProgramError> {
let swap_curve = SwapCurve {
curve_type,
calculator: match curve_type {
CurveType::ConstantProduct => Box::new(ConstantProductCurve {
fee_numerator,
fee_denominator,
}),
CurveType::Flat => Box::new(FlatCurve {
fee_numerator,
fee_denominator,
}),
},
};
let init_data = SwapInstruction::Initialize { nonce, swap_curve };
let data = init_data.pack();
@ -231,6 +223,7 @@ pub fn initialize(
AccountMeta::new_readonly(*token_a_pubkey, false),
AccountMeta::new_readonly(*token_b_pubkey, false),
AccountMeta::new(*pool_pubkey, false),
AccountMeta::new_readonly(*fee_pubkey, false),
AccountMeta::new(*destination_pubkey, false),
AccountMeta::new_readonly(*token_program_id, false),
];
@ -291,6 +284,7 @@ pub fn withdraw(
swap_pubkey: &Pubkey,
authority_pubkey: &Pubkey,
pool_mint_pubkey: &Pubkey,
fee_account_pubkey: &Pubkey,
source_pubkey: &Pubkey,
swap_token_a_pubkey: &Pubkey,
swap_token_b_pubkey: &Pubkey,
@ -316,6 +310,7 @@ pub fn withdraw(
AccountMeta::new(*swap_token_b_pubkey, false),
AccountMeta::new(*destination_token_a_pubkey, false),
AccountMeta::new(*destination_token_b_pubkey, false),
AccountMeta::new(*fee_account_pubkey, false),
AccountMeta::new_readonly(*token_program_id, false),
];
@ -336,6 +331,8 @@ pub fn swap(
swap_source_pubkey: &Pubkey,
swap_destination_pubkey: &Pubkey,
destination_pubkey: &Pubkey,
pool_mint_pubkey: &Pubkey,
pool_fee_pubkey: &Pubkey,
amount_in: u64,
minimum_amount_out: u64,
) -> Result<Instruction, ProgramError> {
@ -352,6 +349,8 @@ pub fn swap(
AccountMeta::new(*swap_source_pubkey, false),
AccountMeta::new(*swap_destination_pubkey, false),
AccountMeta::new(*destination_pubkey, false),
AccountMeta::new(*pool_mint_pubkey, false),
AccountMeta::new(*pool_fee_pubkey, false),
AccountMeta::new_readonly(*token_program_id, false),
];
@ -377,15 +376,25 @@ pub fn unpack<T>(input: &[u8]) -> Result<&T, ProgramError> {
mod tests {
use super::*;
use crate::curve::{CurveType, FlatCurve};
#[test]
fn test_instruction_packing() {
let fee_numerator: u64 = 1;
let fee_denominator: u64 = 4;
let trade_fee_numerator: u64 = 1;
let trade_fee_denominator: u64 = 4;
let owner_trade_fee_numerator: u64 = 2;
let owner_trade_fee_denominator: u64 = 5;
let owner_withdraw_fee_numerator: u64 = 1;
let owner_withdraw_fee_denominator: u64 = 3;
let nonce: u8 = 255;
let curve_type = CurveType::Flat;
let calculator = Box::new(FlatCurve {
fee_numerator,
fee_denominator,
trade_fee_numerator,
trade_fee_denominator,
owner_trade_fee_numerator,
owner_trade_fee_denominator,
owner_withdraw_fee_numerator,
owner_withdraw_fee_denominator,
});
let swap_curve = SwapCurve {
curve_type,
@ -397,9 +406,13 @@ mod tests {
expect.push(0 as u8);
expect.push(nonce);
expect.push(curve_type as u8);
expect.extend_from_slice(&fee_numerator.to_le_bytes());
expect.extend_from_slice(&fee_denominator.to_le_bytes());
expect.extend_from_slice(&[0u8; 48]); // padding
expect.extend_from_slice(&trade_fee_numerator.to_le_bytes());
expect.extend_from_slice(&trade_fee_denominator.to_le_bytes());
expect.extend_from_slice(&owner_trade_fee_numerator.to_le_bytes());
expect.extend_from_slice(&owner_trade_fee_denominator.to_le_bytes());
expect.extend_from_slice(&owner_withdraw_fee_numerator.to_le_bytes());
expect.extend_from_slice(&owner_withdraw_fee_denominator.to_le_bytes());
expect.extend_from_slice(&[0u8; 16]); // padding
assert_eq!(packed, expect);
let unpacked = SwapInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);

File diff suppressed because it is too large Load Diff

View File

@ -25,14 +25,22 @@ pub struct SwapInfo {
pub token_program_id: Pubkey,
/// Token A
/// The Liquidity token is issued against this value.
pub token_a: Pubkey,
/// Token B
pub token_b: Pubkey,
/// Pool tokens are issued when A or B tokens are deposited.
/// Pool tokens can be withdrawn back to the original A or B token.
pub pool_mint: Pubkey,
/// Mint information for token A
pub token_a_mint: Pubkey,
/// Mint information for token B
pub token_b_mint: Pubkey,
/// Pool token account to receive trading and / or withdrawal fees
pub pool_fee_account: Pubkey,
/// Swap curve parameters, to be unpacked and used by the SwapCurve, which
/// calculates swaps, deposits, and withdrawals
pub swap_curve: SwapCurve,
@ -46,14 +54,24 @@ impl IsInitialized for SwapInfo {
}
impl Pack for SwapInfo {
const LEN: usize = 195;
const LEN: usize = 291;
/// Unpacks a byte buffer into a [SwapInfo](struct.SwapInfo.html).
fn unpack_from_slice(input: &[u8]) -> Result<Self, ProgramError> {
let input = array_ref![input, 0, 195];
let input = array_ref![input, 0, 291];
#[allow(clippy::ptr_offset_with_cast)]
let (is_initialized, nonce, token_program_id, token_a, token_b, pool_mint, swap_curve) =
array_refs![input, 1, 1, 32, 32, 32, 32, 65];
let (
is_initialized,
nonce,
token_program_id,
token_a,
token_b,
pool_mint,
token_a_mint,
token_b_mint,
pool_fee_account,
swap_curve,
) = array_refs![input, 1, 1, 32, 32, 32, 32, 32, 32, 32, 65];
Ok(Self {
is_initialized: match is_initialized {
[0] => false,
@ -65,20 +83,36 @@ impl Pack for SwapInfo {
token_a: Pubkey::new_from_array(*token_a),
token_b: Pubkey::new_from_array(*token_b),
pool_mint: Pubkey::new_from_array(*pool_mint),
token_a_mint: Pubkey::new_from_array(*token_a_mint),
token_b_mint: Pubkey::new_from_array(*token_b_mint),
pool_fee_account: Pubkey::new_from_array(*pool_fee_account),
swap_curve: SwapCurve::unpack_from_slice(swap_curve)?,
})
}
fn pack_into_slice(&self, output: &mut [u8]) {
let output = array_mut_ref![output, 0, 195];
let (is_initialized, nonce, token_program_id, token_a, token_b, pool_mint, swap_curve) =
mut_array_refs![output, 1, 1, 32, 32, 32, 32, 65];
let output = array_mut_ref![output, 0, 291];
let (
is_initialized,
nonce,
token_program_id,
token_a,
token_b,
pool_mint,
token_a_mint,
token_b_mint,
pool_fee_account,
swap_curve,
) = mut_array_refs![output, 1, 1, 32, 32, 32, 32, 32, 32, 32, 65];
is_initialized[0] = self.is_initialized as u8;
nonce[0] = self.nonce;
token_program_id.copy_from_slice(self.token_program_id.as_ref());
token_a.copy_from_slice(self.token_a.as_ref());
token_b.copy_from_slice(self.token_b.as_ref());
pool_mint.copy_from_slice(self.pool_mint.as_ref());
token_a_mint.copy_from_slice(self.token_a_mint.as_ref());
token_b_mint.copy_from_slice(self.token_b_mint.as_ref());
pool_fee_account.copy_from_slice(self.pool_fee_account.as_ref());
self.swap_curve.pack_into_slice(&mut swap_curve[..]);
}
}
@ -99,15 +133,29 @@ mod tests {
let token_a_raw = [1u8; 32];
let token_b_raw = [2u8; 32];
let pool_mint_raw = [3u8; 32];
let token_a_mint_raw = [4u8; 32];
let token_b_mint_raw = [5u8; 32];
let pool_fee_account_raw = [6u8; 32];
let token_program_id = Pubkey::new_from_array(token_program_id_raw);
let token_a = Pubkey::new_from_array(token_a_raw);
let token_b = Pubkey::new_from_array(token_b_raw);
let pool_mint = Pubkey::new_from_array(pool_mint_raw);
let fee_numerator = 1;
let fee_denominator = 4;
let token_a_mint = Pubkey::new_from_array(token_a_mint_raw);
let token_b_mint = Pubkey::new_from_array(token_b_mint_raw);
let pool_fee_account = Pubkey::new_from_array(pool_fee_account_raw);
let trade_fee_numerator = 1;
let trade_fee_denominator = 4;
let owner_trade_fee_numerator = 3;
let owner_trade_fee_denominator = 10;
let owner_withdraw_fee_numerator = 2;
let owner_withdraw_fee_denominator = 7;
let calculator = Box::new(FlatCurve {
fee_numerator,
fee_denominator,
trade_fee_numerator,
trade_fee_denominator,
owner_trade_fee_numerator,
owner_trade_fee_denominator,
owner_withdraw_fee_numerator,
owner_withdraw_fee_denominator,
});
let swap_curve = SwapCurve {
curve_type,
@ -121,6 +169,9 @@ mod tests {
token_a,
token_b,
pool_mint,
token_a_mint,
token_b_mint,
pool_fee_account,
swap_curve,
};
@ -136,12 +187,17 @@ mod tests {
packed.extend_from_slice(&token_a_raw);
packed.extend_from_slice(&token_b_raw);
packed.extend_from_slice(&pool_mint_raw);
packed.extend_from_slice(&token_a_mint_raw);
packed.extend_from_slice(&token_b_mint_raw);
packed.extend_from_slice(&pool_fee_account_raw);
packed.push(curve_type_raw);
packed.push(fee_numerator as u8);
packed.extend_from_slice(&[0u8; 7]); // padding
packed.push(fee_denominator as u8);
packed.extend_from_slice(&[0u8; 7]); // padding
packed.extend_from_slice(&[0u8; 48]); // padding
packed.extend_from_slice(&trade_fee_numerator.to_le_bytes());
packed.extend_from_slice(&trade_fee_denominator.to_le_bytes());
packed.extend_from_slice(&owner_trade_fee_numerator.to_le_bytes());
packed.extend_from_slice(&owner_trade_fee_denominator.to_le_bytes());
packed.extend_from_slice(&owner_withdraw_fee_numerator.to_le_bytes());
packed.extend_from_slice(&owner_withdraw_fee_denominator.to_le_bytes());
packed.extend_from_slice(&[0u8; 16]); // padding
let unpacked = SwapInfo::unpack(&packed).unwrap();
assert_eq!(swap_info, unpacked);