Skip to content

Commit 9cd1cb0

Browse files
authored
Token-swap: Add "production mode" hard-coded fee constraints (solana-labs#731)
* Add statically configured program constraints * Allow missing host fee account * Run cargo fmt + clippy * Update JS for new host fee account param * Fix merge issues * Add host fee to curve, constrain on curves * Cleanup static variable * Add production test to CI Integrate review feedback * Run cargo fmt, clippy, prettier * Re-run cargo fmt * Fix test for eslint / prettier clash
1 parent 3774681 commit 9cd1cb0

File tree

13 files changed

+977
-43
lines changed

13 files changed

+977
-43
lines changed

ci/script.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,20 @@ js_token_swap() {
9494
}
9595
_ js_token_swap
9696

97+
# Test token-swap js bindings with "production" feature
98+
production_token_swap() {
99+
address="SwaPpA9LAaLfeLi3a68M4DjnLqgtticKg6CnyNwgAC8"
100+
SWAP_PROGRAM_OWNER_FEE_ADDRESS="$address" cargo build-bpf --manifest-path=token-swap/program/Cargo.toml --dump --features production
101+
cd token-swap/js
102+
npm run cluster:localnet || exit $?
103+
npm run localnet:down
104+
npm run localnet:update || exit $?
105+
npm run localnet:up || exit $?
106+
SWAP_PROGRAM_OWNER_FEE_ADDRESS="$address" npm run start || exit $?
107+
npm run localnet:down
108+
}
109+
_ production_token_swap
110+
97111
# Test token-lending js bindings
98112
js_token_lending() {
99113
cd token-lending/js

token-swap/js/cli/token-swap-test.js

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,29 +35,44 @@ let mintB: Token;
3535
let tokenAccountA: PublicKey;
3636
let tokenAccountB: PublicKey;
3737

38+
// Hard-coded fee address, for testing production mode
39+
const SWAP_PROGRAM_OWNER_FEE_ADDRESS =
40+
process.env.SWAP_PROGRAM_OWNER_FEE_ADDRESS;
41+
42+
// Pool fees
43+
const TRADING_FEE_NUMERATOR = 25;
44+
const TRADING_FEE_DENOMINATOR = 10000;
45+
const OWNER_TRADING_FEE_NUMERATOR = 5;
46+
const OWNER_TRADING_FEE_DENOMINATOR = 10000;
47+
const OWNER_WITHDRAW_FEE_NUMERATOR = SWAP_PROGRAM_OWNER_FEE_ADDRESS ? 0 : 1;
48+
const OWNER_WITHDRAW_FEE_DENOMINATOR = SWAP_PROGRAM_OWNER_FEE_ADDRESS ? 0 : 6;
49+
const HOST_FEE_NUMERATOR = 20;
50+
const HOST_FEE_DENOMINATOR = 100;
51+
3852
// curve type used to calculate swaps and deposits
3953
const CURVE_TYPE = CurveType.ConstantProduct;
54+
4055
// Initial amount in each swap token
41-
let currentSwapTokenA = 1000;
42-
let currentSwapTokenB = 1000;
56+
let currentSwapTokenA = 1000000;
57+
let currentSwapTokenB = 1000000;
4358
let currentFeeAmount = 0;
44-
// Amount passed to swap instruction
45-
const SWAP_AMOUNT_IN = 100;
46-
const SWAP_AMOUNT_OUT = 53;
47-
const SWAP_FEE = 6817150;
59+
60+
// Swap instruction constants
61+
// Because there is no withdraw fee in the production version, these numbers
62+
// need to get slightly tweaked in the two cases.
63+
const SWAP_AMOUNT_IN = 100000;
64+
const SWAP_AMOUNT_OUT = SWAP_PROGRAM_OWNER_FEE_ADDRESS ? 90662 : 90675;
65+
const SWAP_FEE = SWAP_PROGRAM_OWNER_FEE_ADDRESS ? 22272 : 22276;
66+
const HOST_SWAP_FEE = SWAP_PROGRAM_OWNER_FEE_ADDRESS
67+
? Math.floor((SWAP_FEE * HOST_FEE_NUMERATOR) / HOST_FEE_DENOMINATOR)
68+
: 0;
69+
const OWNER_SWAP_FEE = SWAP_FEE - HOST_SWAP_FEE;
70+
4871
// Pool token amount minted on init
4972
const DEFAULT_POOL_TOKEN_AMOUNT = 1000000000;
5073
// Pool token amount to withdraw / deposit
5174
const POOL_TOKEN_AMOUNT = 10000000;
5275

53-
// Pool fees
54-
const TRADING_FEE_NUMERATOR = 1;
55-
const TRADING_FEE_DENOMINATOR = 4;
56-
const OWNER_TRADING_FEE_NUMERATOR = 1;
57-
const OWNER_TRADING_FEE_DENOMINATOR = 5;
58-
const OWNER_WITHDRAW_FEE_NUMERATOR = 1;
59-
const OWNER_WITHDRAW_FEE_DENOMINATOR = 6;
60-
6176
function assert(condition, message) {
6277
if (!condition) {
6378
console.log(Error().stack + ':token-test.js');
@@ -164,7 +179,8 @@ export async function createTokenSwap(): Promise<void> {
164179

165180
console.log('creating pool account');
166181
tokenAccountPool = await tokenPool.createAccount(owner.publicKey);
167-
feeAccount = await tokenPool.createAccount(owner.publicKey);
182+
const ownerKey = SWAP_PROGRAM_OWNER_FEE_ADDRESS || owner.publicKey.toString();
183+
feeAccount = await tokenPool.createAccount(new PublicKey(ownerKey));
168184

169185
console.log('creating token A');
170186
mintA = await Token.createMint(
@@ -220,6 +236,8 @@ export async function createTokenSwap(): Promise<void> {
220236
OWNER_TRADING_FEE_DENOMINATOR,
221237
OWNER_WITHDRAW_FEE_NUMERATOR,
222238
OWNER_WITHDRAW_FEE_DENOMINATOR,
239+
HOST_FEE_NUMERATOR,
240+
HOST_FEE_DENOMINATOR,
223241
);
224242

225243
console.log('loading token swap');
@@ -260,6 +278,10 @@ export async function createTokenSwap(): Promise<void> {
260278
OWNER_WITHDRAW_FEE_DENOMINATOR ==
261279
fetchedTokenSwap.ownerWithdrawFeeDenominator.toNumber(),
262280
);
281+
assert(HOST_FEE_NUMERATOR == fetchedTokenSwap.hostFeeNumerator.toNumber());
282+
assert(
283+
HOST_FEE_DENOMINATOR == fetchedTokenSwap.hostFeeDenominator.toNumber(),
284+
);
263285
}
264286

265287
export async function deposit(): Promise<void> {
@@ -311,10 +333,13 @@ export async function withdraw(): Promise<void> {
311333
const supply = poolMintInfo.supply.toNumber();
312334
let swapTokenA = await mintA.getAccountInfo(tokenAccountA);
313335
let swapTokenB = await mintB.getAccountInfo(tokenAccountB);
314-
const feeAmount = Math.floor(
315-
(POOL_TOKEN_AMOUNT * OWNER_WITHDRAW_FEE_NUMERATOR) /
316-
OWNER_WITHDRAW_FEE_DENOMINATOR,
317-
);
336+
let feeAmount = 0;
337+
if (OWNER_WITHDRAW_FEE_NUMERATOR !== 0) {
338+
feeAmount = Math.floor(
339+
(POOL_TOKEN_AMOUNT * OWNER_WITHDRAW_FEE_NUMERATOR) /
340+
OWNER_WITHDRAW_FEE_DENOMINATOR,
341+
);
342+
}
318343
const poolTokenAmount = POOL_TOKEN_AMOUNT - feeAmount;
319344
const tokenA = Math.floor(
320345
(swapTokenA.amount.toNumber() * poolTokenAmount) / supply,
@@ -375,32 +400,46 @@ export async function swap(): Promise<void> {
375400
await mintA.approve(userAccountA, authority, owner, [], SWAP_AMOUNT_IN);
376401
console.log('Creating swap token b account');
377402
let userAccountB = await mintB.createAccount(owner.publicKey);
403+
let poolAccount = SWAP_PROGRAM_OWNER_FEE_ADDRESS
404+
? await tokenPool.createAccount(owner.publicKey)
405+
: null;
378406

379407
console.log('Swapping');
380408
await tokenSwap.swap(
381409
userAccountA,
382410
tokenAccountA,
383411
tokenAccountB,
384412
userAccountB,
413+
poolAccount,
385414
SWAP_AMOUNT_IN,
386415
SWAP_AMOUNT_OUT,
387416
);
388417
await sleep(500);
389418
let info;
390419
info = await mintA.getAccountInfo(userAccountA);
391420
assert(info.amount.toNumber() == 0);
421+
422+
info = await mintB.getAccountInfo(userAccountB);
423+
assert(info.amount.toNumber() == SWAP_AMOUNT_OUT);
424+
392425
info = await mintA.getAccountInfo(tokenAccountA);
393426
assert(info.amount.toNumber() == currentSwapTokenA + SWAP_AMOUNT_IN);
394427
currentSwapTokenA -= SWAP_AMOUNT_IN;
428+
395429
info = await mintB.getAccountInfo(tokenAccountB);
396430
assert(info.amount.toNumber() == currentSwapTokenB - SWAP_AMOUNT_OUT);
397431
currentSwapTokenB -= SWAP_AMOUNT_OUT;
398-
info = await mintB.getAccountInfo(userAccountB);
399-
assert(info.amount.toNumber() == SWAP_AMOUNT_OUT);
432+
400433
info = await tokenPool.getAccountInfo(tokenAccountPool);
401434
assert(
402435
info.amount.toNumber() == DEFAULT_POOL_TOKEN_AMOUNT - POOL_TOKEN_AMOUNT,
403436
);
437+
404438
info = await tokenPool.getAccountInfo(feeAccount);
405-
assert(info.amount.toNumber() == currentFeeAmount + SWAP_FEE);
439+
assert(info.amount.toNumber() == currentFeeAmount + OWNER_SWAP_FEE);
440+
441+
if (poolAccount != null) {
442+
info = await tokenPool.getAccountInfo(poolAccount);
443+
assert(info.amount.toNumber() == HOST_SWAP_FEE);
444+
}
406445
}

token-swap/js/client/token-swap.js

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ export const TokenSwapLayout: typeof BufferLayout.Structure = BufferLayout.struc
7474
Layout.uint64('ownerTradeFeeDenominator'),
7575
Layout.uint64('ownerWithdrawFeeNumerator'),
7676
Layout.uint64('ownerWithdrawFeeDenominator'),
77-
BufferLayout.blob(16, 'padding'),
77+
Layout.uint64('hostFeeNumerator'),
78+
Layout.uint64('hostFeeDenominator'),
7879
],
7980
);
8081

@@ -172,6 +173,16 @@ export class TokenSwap {
172173
*/
173174
ownerWithdrawFeeDenominator: Numberu64;
174175

176+
/**
177+
* Host trading fee numerator
178+
*/
179+
hostFeeNumerator: Numberu64;
180+
181+
/**
182+
* Host trading fee denominator
183+
*/
184+
hostFeeDenominator: Numberu64;
185+
175186
/**
176187
* CurveType, current options are:
177188
*/
@@ -214,6 +225,8 @@ export class TokenSwap {
214225
ownerTradeFeeDenominator: Numberu64,
215226
ownerWithdrawFeeNumerator: Numberu64,
216227
ownerWithdrawFeeDenominator: Numberu64,
228+
hostFeeNumerator: Numberu64,
229+
hostFeeDenominator: Numberu64,
217230
payer: Account,
218231
) {
219232
Object.assign(this, {
@@ -235,6 +248,8 @@ export class TokenSwap {
235248
ownerTradeFeeDenominator,
236249
ownerWithdrawFeeNumerator,
237250
ownerWithdrawFeeDenominator,
251+
hostFeeNumerator,
252+
hostFeeDenominator,
238253
payer,
239254
});
240255
}
@@ -270,6 +285,8 @@ export class TokenSwap {
270285
ownerTradeFeeDenominator: number,
271286
ownerWithdrawFeeNumerator: number,
272287
ownerWithdrawFeeDenominator: number,
288+
hostFeeNumerator: number,
289+
hostFeeDenominator: number,
273290
): TransactionInstruction {
274291
const keys = [
275292
{pubkey: tokenSwapAccount.publicKey, isSigner: false, isWritable: true},
@@ -291,7 +308,8 @@ export class TokenSwap {
291308
BufferLayout.nu64('ownerTradeFeeDenominator'),
292309
BufferLayout.nu64('ownerWithdrawFeeNumerator'),
293310
BufferLayout.nu64('ownerWithdrawFeeDenominator'),
294-
BufferLayout.blob(16, 'padding'),
311+
BufferLayout.nu64('hostFeeNumerator'),
312+
BufferLayout.nu64('hostFeeDenominator'),
295313
]);
296314
let data = Buffer.alloc(1024);
297315
{
@@ -306,6 +324,8 @@ export class TokenSwap {
306324
ownerTradeFeeDenominator,
307325
ownerWithdrawFeeNumerator,
308326
ownerWithdrawFeeDenominator,
327+
hostFeeNumerator,
328+
hostFeeDenominator,
309329
},
310330
data,
311331
);
@@ -361,6 +381,12 @@ export class TokenSwap {
361381
const ownerWithdrawFeeDenominator = Numberu64.fromBuffer(
362382
tokenSwapData.ownerWithdrawFeeDenominator,
363383
);
384+
const hostFeeNumerator = Numberu64.fromBuffer(
385+
tokenSwapData.hostFeeNumerator,
386+
);
387+
const hostFeeDenominator = Numberu64.fromBuffer(
388+
tokenSwapData.hostFeeDenominator,
389+
);
364390
const curveType = tokenSwapData.curveType;
365391

366392
return new TokenSwap(
@@ -382,6 +408,8 @@ export class TokenSwap {
382408
ownerTradeFeeDenominator,
383409
ownerWithdrawFeeNumerator,
384410
ownerWithdrawFeeDenominator,
411+
hostFeeNumerator,
412+
hostFeeDenominator,
385413
payer,
386414
);
387415
}
@@ -426,6 +454,8 @@ export class TokenSwap {
426454
ownerTradeFeeDenominator: number,
427455
ownerWithdrawFeeNumerator: number,
428456
ownerWithdrawFeeDenominator: number,
457+
hostFeeNumerator: number,
458+
hostFeeDenominator: number,
429459
): Promise<TokenSwap> {
430460
let transaction;
431461
const tokenSwap = new TokenSwap(
@@ -447,6 +477,8 @@ export class TokenSwap {
447477
new Numberu64(ownerTradeFeeDenominator),
448478
new Numberu64(ownerWithdrawFeeNumerator),
449479
new Numberu64(ownerWithdrawFeeDenominator),
480+
new Numberu64(hostFeeNumerator),
481+
new Numberu64(hostFeeDenominator),
450482
payer,
451483
);
452484

@@ -483,6 +515,8 @@ export class TokenSwap {
483515
ownerTradeFeeDenominator,
484516
ownerWithdrawFeeNumerator,
485517
ownerWithdrawFeeDenominator,
518+
hostFeeNumerator,
519+
hostFeeDenominator,
486520
);
487521

488522
transaction.add(instruction);
@@ -512,6 +546,7 @@ export class TokenSwap {
512546
poolSource: PublicKey,
513547
poolDestination: PublicKey,
514548
userDestination: PublicKey,
549+
hostFeeAccount: ?PublicKey,
515550
amountIn: number | Numberu64,
516551
minimumAmountOut: number | Numberu64,
517552
): Promise<TransactionSignature> {
@@ -528,6 +563,7 @@ export class TokenSwap {
528563
userDestination,
529564
this.poolToken,
530565
this.feeAccount,
566+
hostFeeAccount,
531567
this.swapProgramId,
532568
this.tokenProgramId,
533569
amountIn,
@@ -547,6 +583,7 @@ export class TokenSwap {
547583
userDestination: PublicKey,
548584
poolMint: PublicKey,
549585
feeAccount: PublicKey,
586+
hostFeeAccount: ?PublicKey,
550587
swapProgramId: PublicKey,
551588
tokenProgramId: PublicKey,
552589
amountIn: number | Numberu64,
@@ -579,6 +616,9 @@ export class TokenSwap {
579616
{pubkey: feeAccount, isSigner: false, isWritable: true},
580617
{pubkey: tokenProgramId, isSigner: false, isWritable: false},
581618
];
619+
if (hostFeeAccount != null) {
620+
keys.push({pubkey: hostFeeAccount, isSigner: false, isWritable: true});
621+
}
582622
return new TransactionInstruction({
583623
keys,
584624
programId: swapProgramId,

token-swap/js/module.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ declare module '@solana/spl-token-swap' {
3939
ownerTradeFeeDenominator: Numberu64,
4040
ownerWithdrawFeeNumerator: Numberu64,
4141
ownerWithdrawFeeDenominator: Numberu64,
42+
hostFeeNumerator: Numberu64,
43+
hostFeeDenominator: Numberu64,
4244
payer: Account,
4345
);
4446

@@ -64,6 +66,8 @@ declare module '@solana/spl-token-swap' {
6466
ownerTradeFeeDenominator: number,
6567
ownerWithdrawFeeNumerator: number,
6668
ownerWithdrawFeeDenominator: number,
69+
hostFeeNumerator: number,
70+
hostFeeDenominator: number,
6771
): TransactionInstruction;
6872

6973
static loadTokenSwap(
@@ -94,6 +98,8 @@ declare module '@solana/spl-token-swap' {
9498
ownerTradeFeeDenominator: number,
9599
ownerWithdrawFeeNumerator: number,
96100
ownerWithdrawFeeDenominator: number,
101+
hostFeeNumerator: number,
102+
hostFeeDenominator: number,
97103
swapProgramId: PublicKey,
98104
): Promise<TokenSwap>;
99105

@@ -102,6 +108,7 @@ declare module '@solana/spl-token-swap' {
102108
poolSource: PublicKey,
103109
poolDestination: PublicKey,
104110
userDestination: PublicKey,
111+
hostFeeAccount: PublicKey | null,
105112
amountIn: number | Numberu64,
106113
minimumAmountOut: number | Numberu64,
107114
): Promise<TransactionSignature>;
@@ -115,6 +122,7 @@ declare module '@solana/spl-token-swap' {
115122
userDestination: PublicKey,
116123
poolMint: PublicKey,
117124
feeAccount: PublicKey,
125+
hostFeeAccount: PublicKey | null,
118126
swapProgramId: PublicKey,
119127
tokenProgramId: PublicKey,
120128
amountIn: number | Numberu64,

0 commit comments

Comments
 (0)