diff --git a/token/js/src/extensions/transferFee/instructions.ts b/token/js/src/extensions/transferFee/instructions.ts index 3562debe176..b2db4b230aa 100644 --- a/token/js/src/extensions/transferFee/instructions.ts +++ b/token/js/src/extensions/transferFee/instructions.ts @@ -1,7 +1,7 @@ import { struct, u16, u8 } from '@solana/buffer-layout'; -import { publicKey, u64 } from '@solana/buffer-layout-utils'; -import type { AccountMeta, Signer } from '@solana/web3.js'; -import { PublicKey, TransactionInstruction } from '@solana/web3.js'; +import { u64 } from '@solana/buffer-layout-utils'; +import type { AccountMeta, Signer, PublicKey } from '@solana/web3.js'; +import { TransactionInstruction } from '@solana/web3.js'; import { programSupportsExtensions, TOKEN_2022_PROGRAM_ID } from '../../constants.js'; import { TokenInvalidInstructionDataError, @@ -12,6 +12,7 @@ import { } from '../../errors.js'; import { addSigners } from '../../instructions/internal.js'; import { TokenInstruction } from '../../instructions/types.js'; +import { COptionPublicKeyLayout } from '../../serialization.js'; export enum TransferFeeInstruction { InitializeTransferFeeConfig = 0, @@ -28,10 +29,8 @@ export enum TransferFeeInstruction { export interface InitializeTransferFeeConfigInstructionData { instruction: TokenInstruction.TransferFeeExtension; transferFeeInstruction: TransferFeeInstruction.InitializeTransferFeeConfig; - transferFeeConfigAuthorityOption: 1 | 0; - transferFeeConfigAuthority: PublicKey; - withdrawWithheldAuthorityOption: 1 | 0; - withdrawWithheldAuthority: PublicKey; + transferFeeConfigAuthority: PublicKey | null; + withdrawWithheldAuthority: PublicKey | null; transferFeeBasisPoints: number; maximumFee: bigint; } @@ -40,10 +39,8 @@ export interface InitializeTransferFeeConfigInstructionData { export const initializeTransferFeeConfigInstructionData = struct([ u8('instruction'), u8('transferFeeInstruction'), - u8('transferFeeConfigAuthorityOption'), - publicKey('transferFeeConfigAuthority'), - u8('withdrawWithheldAuthorityOption'), - publicKey('withdrawWithheldAuthority'), + new COptionPublicKeyLayout('transferFeeConfigAuthority'), + new COptionPublicKeyLayout('withdrawWithheldAuthority'), u16('transferFeeBasisPoints'), u64('maximumFee'), ]); @@ -78,10 +75,8 @@ export function createInitializeTransferFeeConfigInstruction( { instruction: TokenInstruction.TransferFeeExtension, transferFeeInstruction: TransferFeeInstruction.InitializeTransferFeeConfig, - transferFeeConfigAuthorityOption: transferFeeConfigAuthority ? 1 : 0, - transferFeeConfigAuthority: transferFeeConfigAuthority || new PublicKey(0), - withdrawWithheldAuthorityOption: withdrawWithheldAuthority ? 1 : 0, - withdrawWithheldAuthority: withdrawWithheldAuthority || new PublicKey(0), + transferFeeConfigAuthority: transferFeeConfigAuthority, + withdrawWithheldAuthority: withdrawWithheldAuthority, transferFeeBasisPoints: transferFeeBasisPoints, maximumFee: maximumFee, }, @@ -174,9 +169,7 @@ export function decodeInitializeTransferFeeConfigInstructionUnchecked({ const { instruction, transferFeeInstruction, - transferFeeConfigAuthorityOption, transferFeeConfigAuthority, - withdrawWithheldAuthorityOption, withdrawWithheldAuthority, transferFeeBasisPoints, maximumFee, @@ -190,8 +183,8 @@ export function decodeInitializeTransferFeeConfigInstructionUnchecked({ data: { instruction, transferFeeInstruction, - transferFeeConfigAuthority: transferFeeConfigAuthorityOption ? transferFeeConfigAuthority : null, - withdrawWithheldAuthority: withdrawWithheldAuthorityOption ? withdrawWithheldAuthority : null, + transferFeeConfigAuthority, + withdrawWithheldAuthority, transferFeeBasisPoints, maximumFee, }, diff --git a/token/js/src/serialization.ts b/token/js/src/serialization.ts new file mode 100644 index 00000000000..57d426baa7f --- /dev/null +++ b/token/js/src/serialization.ts @@ -0,0 +1,39 @@ +import { Layout } from '@solana/buffer-layout'; +import { publicKey } from '@solana/buffer-layout-utils'; +import type { PublicKey } from '@solana/web3.js'; + +export class COptionPublicKeyLayout extends Layout { + private publicKeyLayout: Layout; + + constructor(property?: string | undefined) { + super(-1, property); + this.publicKeyLayout = publicKey(); + } + + decode(buffer: Uint8Array, offset: number = 0): PublicKey | null { + const option = buffer[offset]; + if (option === 0) { + return null; + } + return this.publicKeyLayout.decode(buffer, offset + 1); + } + + encode(src: PublicKey | null, buffer: Uint8Array, offset: number = 0): number { + if (src === null) { + buffer[offset] = 0; + return 1; + } else { + buffer[offset] = 1; + this.publicKeyLayout.encode(src, buffer, offset + 1); + return 33; + } + } + + getSpan(buffer?: Uint8Array, offset: number = 0): number { + if (buffer) { + const option = buffer[offset]; + return option === 0 ? 1 : 1 + this.publicKeyLayout.span; + } + return 1 + this.publicKeyLayout.span; + } +} diff --git a/token/js/test/e2e-2022/transferFee.test.ts b/token/js/test/e2e-2022/transferFee.test.ts index 8e5cad6266d..e007ce36a97 100644 --- a/token/js/test/e2e-2022/transferFee.test.ts +++ b/token/js/test/e2e-2022/transferFee.test.ts @@ -44,18 +44,19 @@ describe('transferFee', () => { let sourceAccount: PublicKey; let destinationAccount: PublicKey; let mint: PublicKey; - let transferFeeConfigAuthority: Keypair; - let withdrawWithheldAuthority: Keypair; + let mintAuthority: Keypair; before(async () => { connection = await getConnection(); payer = await newAccountWithLamports(connection, 1000000000); - transferFeeConfigAuthority = Keypair.generate(); - withdrawWithheldAuthority = Keypair.generate(); }); - beforeEach(async () => { - const mintAuthority = Keypair.generate(); + + async function setupTransferFeeMint( + transferFeeConfigAuthority: PublicKey | null, + withdrawWithheldAuthority: PublicKey | null + ) { const mintKeypair = Keypair.generate(); mint = mintKeypair.publicKey; + mintAuthority = Keypair.generate(); const mintLen = getMintLen(MINT_EXTENSIONS); const mintLamports = await connection.getMinimumBalanceForRentExemption(mintLen); const mintTransaction = new Transaction().add( @@ -68,8 +69,8 @@ describe('transferFee', () => { }), createInitializeTransferFeeConfigInstruction( mint, - transferFeeConfigAuthority.publicKey, - withdrawWithheldAuthority.publicKey, + transferFeeConfigAuthority, + withdrawWithheldAuthority, FEE_BASIS_POINTS, MAX_FEE, TEST_PROGRAM_ID @@ -77,194 +78,272 @@ describe('transferFee', () => { createInitializeMintInstruction(mint, TEST_TOKEN_DECIMALS, mintAuthority.publicKey, null, TEST_PROGRAM_ID) ); await sendAndConfirmTransaction(connection, mintTransaction, [payer, mintKeypair], undefined); + } - owner = Keypair.generate(); - sourceAccount = await createAccount( - connection, - payer, - mint, - owner.publicKey, - undefined, - undefined, - TEST_PROGRAM_ID - ); - await mintTo( - connection, - payer, - mint, - sourceAccount, - mintAuthority, - MINT_AMOUNT, - [], - undefined, - TEST_PROGRAM_ID - ); + describe('with authorities set', () => { + let transferFeeConfigAuthority: Keypair; + let withdrawWithheldAuthority: Keypair; + beforeEach(async () => { + transferFeeConfigAuthority = Keypair.generate(); + withdrawWithheldAuthority = Keypair.generate(); - const accountKeypair = Keypair.generate(); - destinationAccount = await createAccount( - connection, - payer, - mint, - owner.publicKey, - accountKeypair, - undefined, - TEST_PROGRAM_ID - ); + await setupTransferFeeMint(transferFeeConfigAuthority.publicKey, withdrawWithheldAuthority.publicKey); - await transferChecked( - connection, - payer, - sourceAccount, - mint, - destinationAccount, - owner, - TRANSFER_AMOUNT, - TEST_TOKEN_DECIMALS, - [], - undefined, - TEST_PROGRAM_ID - ); - }); - it('initializes', async () => { - const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); - const transferFeeConfig = getTransferFeeConfig(mintInfo); - expect(transferFeeConfig).to.not.be.null; - if (transferFeeConfig !== null) { - expect(transferFeeConfig.transferFeeConfigAuthority).to.eql(transferFeeConfigAuthority.publicKey); - expect(transferFeeConfig.withdrawWithheldAuthority).to.eql(withdrawWithheldAuthority.publicKey); - expect(transferFeeConfig.olderTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS); - expect(transferFeeConfig.olderTransferFee.maximumFee).to.eql(MAX_FEE); - expect(transferFeeConfig.newerTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS); - expect(transferFeeConfig.newerTransferFee.maximumFee).to.eql(MAX_FEE); - expect(transferFeeConfig.withheldAmount).to.eql(BigInt(0)); - } + owner = Keypair.generate(); + sourceAccount = await createAccount( + connection, + payer, + mint, + owner.publicKey, + undefined, + undefined, + TEST_PROGRAM_ID + ); + await mintTo( + connection, + payer, + mint, + sourceAccount, + mintAuthority, + MINT_AMOUNT, + [], + undefined, + TEST_PROGRAM_ID + ); - const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID); - const transferFeeAmount = getTransferFeeAmount(accountInfo); - expect(transferFeeAmount).to.not.be.null; - if (transferFeeAmount !== null) { - expect(transferFeeAmount.withheldAmount).to.eql(FEE); - } - }); - it('transferCheckedWithFee', async () => { - await transferCheckedWithFee( - connection, - payer, - sourceAccount, - mint, - destinationAccount, - owner, - TRANSFER_AMOUNT, - TEST_TOKEN_DECIMALS, - FEE, - [], - undefined, - TEST_PROGRAM_ID - ); - const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID); - const transferFeeAmount = getTransferFeeAmount(accountInfo); - expect(transferFeeAmount).to.not.be.null; - if (transferFeeAmount !== null) { - expect(transferFeeAmount.withheldAmount).to.eql(FEE * BigInt(2)); - } - }); - it('withdrawWithheldTokensFromAccounts', async () => { - await withdrawWithheldTokensFromAccounts( - connection, - payer, - mint, - destinationAccount, - withdrawWithheldAuthority, - [], - [destinationAccount], - undefined, - TEST_PROGRAM_ID - ); - const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID); - expect(accountInfo.amount).to.eql(TRANSFER_AMOUNT); - const transferFeeAmount = getTransferFeeAmount(accountInfo); - expect(transferFeeAmount).to.not.be.null; - if (transferFeeAmount !== null) { - expect(transferFeeAmount.withheldAmount).to.eql(BigInt(0)); - } - }); - it('harvestWithheldTokensToMint', async () => { - await harvestWithheldTokensToMint(connection, payer, mint, [destinationAccount], undefined, TEST_PROGRAM_ID); - const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID); - const transferFeeAmount = getTransferFeeAmount(accountInfo); - expect(transferFeeAmount).to.not.be.null; - if (transferFeeAmount !== null) { - expect(transferFeeAmount.withheldAmount).to.eql(BigInt(0)); - } - const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); - const transferFeeConfig = getTransferFeeConfig(mintInfo); - expect(transferFeeConfig).to.not.be.null; - if (transferFeeConfig !== null) { - expect(transferFeeConfig.withheldAmount).to.eql(FEE); - } - }); - it('withdrawWithheldTokensFromMint', async () => { - await harvestWithheldTokensToMint(connection, payer, mint, [destinationAccount], undefined, TEST_PROGRAM_ID); - await withdrawWithheldTokensFromMint( - connection, - payer, - mint, - destinationAccount, - withdrawWithheldAuthority, - [], - undefined, - TEST_PROGRAM_ID - ); - const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID); - expect(accountInfo.amount).to.eql(TRANSFER_AMOUNT); - const transferFeeAmount = getTransferFeeAmount(accountInfo); - expect(transferFeeAmount).to.not.be.null; - if (transferFeeAmount !== null) { - expect(transferFeeAmount.withheldAmount).to.eql(BigInt(0)); - } - const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); - const transferFeeConfig = getTransferFeeConfig(mintInfo); - expect(transferFeeConfig).to.not.be.null; - if (transferFeeConfig !== null) { - expect(transferFeeConfig.withheldAmount).to.eql(BigInt(0)); - } - }); - it('transferFeeConfigAuthority', async () => { - await setAuthority( - connection, - payer, - mint, - transferFeeConfigAuthority, - AuthorityType.TransferFeeConfig, - null, - [], - undefined, - TEST_PROGRAM_ID - ); - const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); - const transferFeeConfig = getTransferFeeConfig(mintInfo); - expect(transferFeeConfig).to.not.be.null; - if (transferFeeConfig !== null) { - expect(transferFeeConfig.transferFeeConfigAuthority).to.eql(PublicKey.default); - } + const accountKeypair = Keypair.generate(); + destinationAccount = await createAccount( + connection, + payer, + mint, + owner.publicKey, + accountKeypair, + undefined, + TEST_PROGRAM_ID + ); + + await transferChecked( + connection, + payer, + sourceAccount, + mint, + destinationAccount, + owner, + TRANSFER_AMOUNT, + TEST_TOKEN_DECIMALS, + [], + undefined, + TEST_PROGRAM_ID + ); + }); + it('initializes', async () => { + const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); + const transferFeeConfig = getTransferFeeConfig(mintInfo); + expect(transferFeeConfig).to.not.be.null; + if (transferFeeConfig !== null) { + expect(transferFeeConfig.transferFeeConfigAuthority).to.eql(transferFeeConfigAuthority.publicKey); + expect(transferFeeConfig.withdrawWithheldAuthority).to.eql(withdrawWithheldAuthority.publicKey); + expect(transferFeeConfig.olderTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS); + expect(transferFeeConfig.olderTransferFee.maximumFee).to.eql(MAX_FEE); + expect(transferFeeConfig.newerTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS); + expect(transferFeeConfig.newerTransferFee.maximumFee).to.eql(MAX_FEE); + expect(transferFeeConfig.withheldAmount).to.eql(BigInt(0)); + } + + const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID); + const transferFeeAmount = getTransferFeeAmount(accountInfo); + expect(transferFeeAmount).to.not.be.null; + if (transferFeeAmount !== null) { + expect(transferFeeAmount.withheldAmount).to.eql(FEE); + } + }); + it('transferCheckedWithFee', async () => { + await transferCheckedWithFee( + connection, + payer, + sourceAccount, + mint, + destinationAccount, + owner, + TRANSFER_AMOUNT, + TEST_TOKEN_DECIMALS, + FEE, + [], + undefined, + TEST_PROGRAM_ID + ); + const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID); + const transferFeeAmount = getTransferFeeAmount(accountInfo); + expect(transferFeeAmount).to.not.be.null; + if (transferFeeAmount !== null) { + expect(transferFeeAmount.withheldAmount).to.eql(FEE * BigInt(2)); + } + }); + it('withdrawWithheldTokensFromAccounts', async () => { + await withdrawWithheldTokensFromAccounts( + connection, + payer, + mint, + destinationAccount, + withdrawWithheldAuthority, + [], + [destinationAccount], + undefined, + TEST_PROGRAM_ID + ); + const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID); + expect(accountInfo.amount).to.eql(TRANSFER_AMOUNT); + const transferFeeAmount = getTransferFeeAmount(accountInfo); + expect(transferFeeAmount).to.not.be.null; + if (transferFeeAmount !== null) { + expect(transferFeeAmount.withheldAmount).to.eql(BigInt(0)); + } + }); + it('harvestWithheldTokensToMint', async () => { + await harvestWithheldTokensToMint( + connection, + payer, + mint, + [destinationAccount], + undefined, + TEST_PROGRAM_ID + ); + const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID); + const transferFeeAmount = getTransferFeeAmount(accountInfo); + expect(transferFeeAmount).to.not.be.null; + if (transferFeeAmount !== null) { + expect(transferFeeAmount.withheldAmount).to.eql(BigInt(0)); + } + const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); + const transferFeeConfig = getTransferFeeConfig(mintInfo); + expect(transferFeeConfig).to.not.be.null; + if (transferFeeConfig !== null) { + expect(transferFeeConfig.withheldAmount).to.eql(FEE); + } + }); + it('withdrawWithheldTokensFromMint', async () => { + await harvestWithheldTokensToMint( + connection, + payer, + mint, + [destinationAccount], + undefined, + TEST_PROGRAM_ID + ); + await withdrawWithheldTokensFromMint( + connection, + payer, + mint, + destinationAccount, + withdrawWithheldAuthority, + [], + undefined, + TEST_PROGRAM_ID + ); + const accountInfo = await getAccount(connection, destinationAccount, undefined, TEST_PROGRAM_ID); + expect(accountInfo.amount).to.eql(TRANSFER_AMOUNT); + const transferFeeAmount = getTransferFeeAmount(accountInfo); + expect(transferFeeAmount).to.not.be.null; + if (transferFeeAmount !== null) { + expect(transferFeeAmount.withheldAmount).to.eql(BigInt(0)); + } + const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); + const transferFeeConfig = getTransferFeeConfig(mintInfo); + expect(transferFeeConfig).to.not.be.null; + if (transferFeeConfig !== null) { + expect(transferFeeConfig.withheldAmount).to.eql(BigInt(0)); + } + }); + it('transferFeeConfigAuthority', async () => { + await setAuthority( + connection, + payer, + mint, + transferFeeConfigAuthority, + AuthorityType.TransferFeeConfig, + null, + [], + undefined, + TEST_PROGRAM_ID + ); + const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); + const transferFeeConfig = getTransferFeeConfig(mintInfo); + expect(transferFeeConfig).to.not.be.null; + if (transferFeeConfig !== null) { + expect(transferFeeConfig.transferFeeConfigAuthority).to.eql(PublicKey.default); + } + }); + it('withdrawWithheldAuthority', async () => { + await setAuthority( + connection, + payer, + mint, + withdrawWithheldAuthority, + AuthorityType.WithheldWithdraw, + null, + [], + undefined, + TEST_PROGRAM_ID + ); + const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); + const transferFeeConfig = getTransferFeeConfig(mintInfo); + expect(transferFeeConfig).to.not.be.null; + if (transferFeeConfig !== null) { + expect(transferFeeConfig.withdrawWithheldAuthority).to.eql(PublicKey.default); + } + }); }); - it('withdrawWithheldAuthority', async () => { - await setAuthority( - connection, - payer, - mint, - withdrawWithheldAuthority, - AuthorityType.WithheldWithdraw, - null, - [], - undefined, - TEST_PROGRAM_ID - ); - const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); - const transferFeeConfig = getTransferFeeConfig(mintInfo); - expect(transferFeeConfig).to.not.be.null; - if (transferFeeConfig !== null) { - expect(transferFeeConfig.withdrawWithheldAuthority).to.eql(PublicKey.default); - } + + describe('with null authorities', () => { + it('initializes with null transfer fee config authority', async () => { + const withdrawWithheldAuthority = Keypair.generate(); + await setupTransferFeeMint(null, withdrawWithheldAuthority.publicKey); + + const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); + const transferFeeConfig = getTransferFeeConfig(mintInfo); + expect(transferFeeConfig).to.not.be.null; + if (transferFeeConfig !== null) { + expect(transferFeeConfig.transferFeeConfigAuthority).to.eql(PublicKey.default); + expect(transferFeeConfig.withdrawWithheldAuthority).to.eql(withdrawWithheldAuthority.publicKey); + expect(transferFeeConfig.olderTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS); + expect(transferFeeConfig.olderTransferFee.maximumFee).to.eql(MAX_FEE); + expect(transferFeeConfig.newerTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS); + expect(transferFeeConfig.newerTransferFee.maximumFee).to.eql(MAX_FEE); + expect(transferFeeConfig.withheldAmount).to.eql(BigInt(0)); + } + }); + it('initializes with null withdraw withheld authority', async () => { + const transferFeeConfigAuthority = Keypair.generate(); + await setupTransferFeeMint(transferFeeConfigAuthority.publicKey, null); + + const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); + const transferFeeConfig = getTransferFeeConfig(mintInfo); + expect(transferFeeConfig).to.not.be.null; + if (transferFeeConfig !== null) { + expect(transferFeeConfig.transferFeeConfigAuthority).to.eql(transferFeeConfigAuthority.publicKey); + expect(transferFeeConfig.withdrawWithheldAuthority).to.eql(PublicKey.default); + expect(transferFeeConfig.olderTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS); + expect(transferFeeConfig.olderTransferFee.maximumFee).to.eql(MAX_FEE); + expect(transferFeeConfig.newerTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS); + expect(transferFeeConfig.newerTransferFee.maximumFee).to.eql(MAX_FEE); + expect(transferFeeConfig.withheldAmount).to.eql(BigInt(0)); + } + }); + it('initializes with both authorities null', async () => { + await setupTransferFeeMint(null, null); + + const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID); + const transferFeeConfig = getTransferFeeConfig(mintInfo); + expect(transferFeeConfig).to.not.be.null; + if (transferFeeConfig !== null) { + expect(transferFeeConfig.transferFeeConfigAuthority).to.eql(PublicKey.default); + expect(transferFeeConfig.withdrawWithheldAuthority).to.eql(PublicKey.default); + expect(transferFeeConfig.olderTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS); + expect(transferFeeConfig.olderTransferFee.maximumFee).to.eql(MAX_FEE); + expect(transferFeeConfig.newerTransferFee.transferFeeBasisPoints).to.eql(FEE_BASIS_POINTS); + expect(transferFeeConfig.newerTransferFee.maximumFee).to.eql(MAX_FEE); + expect(transferFeeConfig.withheldAmount).to.eql(BigInt(0)); + } + }); }); }); diff --git a/token/js/test/unit/transferfee.test.ts b/token/js/test/unit/transferfee.test.ts index f329de32772..bfde797d778 100644 --- a/token/js/test/unit/transferfee.test.ts +++ b/token/js/test/unit/transferfee.test.ts @@ -1,98 +1,167 @@ -import { TransferFee, TransferFeeConfig, calculateFee, calculateEpochFee, ONE_IN_BASIS_POINTS } from '../../src'; +import { + calculateFee, + calculateEpochFee, + ONE_IN_BASIS_POINTS, + createInitializeTransferFeeConfigInstruction, + decodeInitializeTransferFeeConfigInstructionUnchecked, +} from '../../src'; import { expect } from 'chai'; -import { PublicKey } from '@solana/web3.js'; +import { Keypair, PublicKey } from '@solana/web3.js'; -describe('calculateFee', () => { - it('should return 0 fee when transferFeeBasisPoints is 0', () => { - const transferFee = { - epoch: 1n, - maximumFee: 100n, - transferFeeBasisPoints: 0, - }; - const preFeeAmount = 100n; - const fee = calculateFee(transferFee, preFeeAmount); - expect(fee).to.eql(0n); +describe('transferFee', () => { + describe('encoding/decoding `InitializeTransferFeeConfig` instructions', () => { + it('should encode and decode with both authorities', () => { + const mint = Keypair.generate().publicKey; + const transferFeeConfigAuthority = Keypair.generate().publicKey; + const withdrawWithheldAuthority = Keypair.generate().publicKey; + const instruction = createInitializeTransferFeeConfigInstruction( + mint, + transferFeeConfigAuthority, + withdrawWithheldAuthority, + 100, + 100n + ); + const decoded = decodeInitializeTransferFeeConfigInstructionUnchecked(instruction); + expect(decoded.data.transferFeeConfigAuthority).to.eql(transferFeeConfigAuthority); + expect(decoded.data.withdrawWithheldAuthority).to.eql(withdrawWithheldAuthority); + expect(decoded.data.transferFeeBasisPoints).to.eql(100); + expect(decoded.data.maximumFee).to.eql(100n); + }); + it('should encode and decode with no transfer fee config authority', () => { + const mint = Keypair.generate().publicKey; + const withdrawWithheldAuthority = Keypair.generate().publicKey; + const instruction = createInitializeTransferFeeConfigInstruction( + mint, + null, + withdrawWithheldAuthority, + 100, + 100n + ); + const decoded = decodeInitializeTransferFeeConfigInstructionUnchecked(instruction); + expect(decoded.data.transferFeeConfigAuthority).to.eql(null); + expect(decoded.data.withdrawWithheldAuthority).to.eql(withdrawWithheldAuthority); + expect(decoded.data.transferFeeBasisPoints).to.eql(100); + expect(decoded.data.maximumFee).to.eql(100n); + }); + it('should encode and decode with no withdraw withheld authority', () => { + const mint = Keypair.generate().publicKey; + const transferFeeConfigAuthority = Keypair.generate().publicKey; + const instruction = createInitializeTransferFeeConfigInstruction( + mint, + transferFeeConfigAuthority, + null, + 100, + 100n + ); + const decoded = decodeInitializeTransferFeeConfigInstructionUnchecked(instruction); + expect(decoded.data.transferFeeConfigAuthority).to.eql(transferFeeConfigAuthority); + expect(decoded.data.withdrawWithheldAuthority).to.eql(null); + expect(decoded.data.transferFeeBasisPoints).to.eql(100); + expect(decoded.data.maximumFee).to.eql(100n); + }); + it('should encode and decode with no authorities', () => { + const mint = Keypair.generate().publicKey; + const instruction = createInitializeTransferFeeConfigInstruction(mint, null, null, 100, 100n); + const decoded = decodeInitializeTransferFeeConfigInstructionUnchecked(instruction); + expect(decoded.data.transferFeeConfigAuthority).to.eql(null); + expect(decoded.data.withdrawWithheldAuthority).to.eql(null); + expect(decoded.data.transferFeeBasisPoints).to.eql(100); + expect(decoded.data.maximumFee).to.eql(100n); + }); }); - it('should return 0 fee when preFeeAmount is 0', () => { - const transferFee = { - epoch: 1n, - maximumFee: 100n, - transferFeeBasisPoints: 100, - }; - const preFeeAmount = 0n; - const fee = calculateFee(transferFee, preFeeAmount); - expect(fee).to.eql(0n); - }); + describe('calculateFee', () => { + it('should return 0 fee when transferFeeBasisPoints is 0', () => { + const transferFee = { + epoch: 1n, + maximumFee: 100n, + transferFeeBasisPoints: 0, + }; + const preFeeAmount = 100n; + const fee = calculateFee(transferFee, preFeeAmount); + expect(fee).to.eql(0n); + }); - it('should calculate the fee correctly when preFeeAmount is non-zero', () => { - const transferFee = { - epoch: 1n, - maximumFee: 100n, - transferFeeBasisPoints: 50, - }; - const preFeeAmount = 500n; - const fee = calculateFee(transferFee, preFeeAmount); - expect(fee).to.eql(3n); - }); + it('should return 0 fee when preFeeAmount is 0', () => { + const transferFee = { + epoch: 1n, + maximumFee: 100n, + transferFeeBasisPoints: 100, + }; + const preFeeAmount = 0n; + const fee = calculateFee(transferFee, preFeeAmount); + expect(fee).to.eql(0n); + }); - it('fee should be equal to maximum fee', () => { - const transferFee = { - epoch: 1n, - maximumFee: 5000n, - transferFeeBasisPoints: 50, - }; - const preFeeAmount = transferFee.maximumFee; - const fee = calculateFee(transferFee, preFeeAmount * ONE_IN_BASIS_POINTS); - expect(fee).to.eql(transferFee.maximumFee); - }); - it('fee should be equal to maximum fee when added 1 to preFeeAmount', () => { - const transferFee = { - epoch: 1n, - maximumFee: 5000n, - transferFeeBasisPoints: 50, - }; - const preFeeAmount = transferFee.maximumFee; - const fee = calculateFee(transferFee, preFeeAmount * ONE_IN_BASIS_POINTS + 1n); - expect(fee).to.eql(transferFee.maximumFee); + it('should calculate the fee correctly when preFeeAmount is non-zero', () => { + const transferFee = { + epoch: 1n, + maximumFee: 100n, + transferFeeBasisPoints: 50, + }; + const preFeeAmount = 500n; + const fee = calculateFee(transferFee, preFeeAmount); + expect(fee).to.eql(3n); + }); + + it('fee should be equal to maximum fee', () => { + const transferFee = { + epoch: 1n, + maximumFee: 5000n, + transferFeeBasisPoints: 50, + }; + const preFeeAmount = transferFee.maximumFee; + const fee = calculateFee(transferFee, preFeeAmount * ONE_IN_BASIS_POINTS); + expect(fee).to.eql(transferFee.maximumFee); + }); + it('fee should be equal to maximum fee when added 1 to preFeeAmount', () => { + const transferFee = { + epoch: 1n, + maximumFee: 5000n, + transferFeeBasisPoints: 50, + }; + const preFeeAmount = transferFee.maximumFee; + const fee = calculateFee(transferFee, preFeeAmount * ONE_IN_BASIS_POINTS + 1n); + expect(fee).to.eql(transferFee.maximumFee); + }); }); -}); -describe('calculateEpochFee', () => { - const transferFeeConfig = { - transferFeeConfigAuthority: PublicKey.default, - withdrawWithheldAuthority: PublicKey.default, - withheldAmount: 500n, - olderTransferFee: { - epoch: 1n, - maximumFee: 100n, - transferFeeBasisPoints: 50, - }, - newerTransferFee: { - epoch: 2n, - maximumFee: 200n, - transferFeeBasisPoints: 75, - }, - }; + describe('calculateEpochFee', () => { + const transferFeeConfig = { + transferFeeConfigAuthority: PublicKey.default, + withdrawWithheldAuthority: PublicKey.default, + withheldAmount: 500n, + olderTransferFee: { + epoch: 1n, + maximumFee: 100n, + transferFeeBasisPoints: 50, + }, + newerTransferFee: { + epoch: 2n, + maximumFee: 200n, + transferFeeBasisPoints: 75, + }, + }; - it('should return olderTransferFee when epoch is less than newerTransferFee.epoch', () => { - const preFeeAmount = 200n; - const epoch = 1n; - const fee = calculateEpochFee(transferFeeConfig, epoch, preFeeAmount); - expect(fee).to.eql(1n); - }); + it('should return olderTransferFee when epoch is less than newerTransferFee.epoch', () => { + const preFeeAmount = 200n; + const epoch = 1n; + const fee = calculateEpochFee(transferFeeConfig, epoch, preFeeAmount); + expect(fee).to.eql(1n); + }); - it('should return newerTransferFee when epoch is greater than or equal to newerTransferFee.epoch', () => { - const preFeeAmount = 200n; - const epoch = 2n; - const fee = calculateEpochFee(transferFeeConfig, epoch, preFeeAmount); - expect(fee).to.eql(2n); - }); + it('should return newerTransferFee when epoch is greater than or equal to newerTransferFee.epoch', () => { + const preFeeAmount = 200n; + const epoch = 2n; + const fee = calculateEpochFee(transferFeeConfig, epoch, preFeeAmount); + expect(fee).to.eql(2n); + }); - it('should cap the fee to the maximumFee when calculated fee exceeds maximumFee', () => { - const preFeeAmount = 500n; - const epoch = 2n; - const fee = calculateEpochFee(transferFeeConfig, epoch, preFeeAmount); - expect(fee).to.eql(4n); + it('should cap the fee to the maximumFee when calculated fee exceeds maximumFee', () => { + const preFeeAmount = 500n; + const epoch = 2n; + const fee = calculateEpochFee(transferFeeConfig, epoch, preFeeAmount); + expect(fee).to.eql(4n); + }); }); });