diff --git a/programs/dynamic-fee-sharing/src/event.rs b/programs/dynamic-fee-sharing/src/event.rs index b75bde8..d0306f8 100644 --- a/programs/dynamic-fee-sharing/src/event.rs +++ b/programs/dynamic-fee-sharing/src/event.rs @@ -27,3 +27,12 @@ pub struct EvtClaimFee { pub index: u8, pub claimed_fee: u64, } + +#[event] +pub struct EvtCloseFeeVault { + pub fee_vault: Pubkey, + pub admin: Pubkey, + pub fee_receiver: Pubkey, + pub rent_receiver: Pubkey, + pub remaining_fee: u64, +} diff --git a/programs/dynamic-fee-sharing/src/instructions/ix_close_permission_fee_vault.rs b/programs/dynamic-fee-sharing/src/instructions/ix_close_permission_fee_vault.rs new file mode 100644 index 0000000..3c73eec --- /dev/null +++ b/programs/dynamic-fee-sharing/src/instructions/ix_close_permission_fee_vault.rs @@ -0,0 +1,74 @@ +use crate::{ + constants::seeds::FEE_VAULT_AUTHORITY_PREFIX, + event::EvtCloseFeeVault, + state::FeeVault, + utils::token::transfer_from_fee_vault +}; +use anchor_lang::prelude::*; +use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; + +#[event_cpi] +#[derive(Accounts)] +pub struct ClosePermissionFeeVaultCtx<'info> { + /// CHECK: pool authority + #[account( + seeds = [ + FEE_VAULT_AUTHORITY_PREFIX.as_ref(), + ], + bump, + )] + pub fee_vault_authority: UncheckedAccount<'info>, + + #[account( + mut, + has_one = admin, + has_one = token_vault, + has_one = token_mint, + close = rent_receiver + )] + pub fee_vault: AccountLoader<'info, FeeVault>, + + #[account(mut)] + pub token_vault: Box>, + + #[account( + mint::token_program = token_program, + )] + pub token_mint: Box>, + + /// CHECK: rent fee receiver + #[account(mut)] + pub rent_receiver: UncheckedAccount<'info>, + + /// CHECK: remaining fee receiver + #[account(mut)] + pub fee_receiver: Box>, + + pub admin: Signer<'info>, + + pub token_program: Interface<'info, TokenInterface>, +} + +pub fn handle_close_permission_fee_vault(ctx: Context) -> Result<()> { + let remaining_fee = ctx.accounts.token_vault.amount; + if remaining_fee > 0 { + transfer_from_fee_vault( + ctx.accounts.fee_vault_authority.to_account_info(), + &ctx.accounts.token_mint, + &ctx.accounts.token_vault, + &ctx.accounts.fee_receiver, + &ctx.accounts.token_program, + remaining_fee, + )?; + + emit_cpi!(EvtCloseFeeVault{ + fee_vault: ctx.accounts.fee_vault.key(), + admin: ctx.accounts.admin.key(), + fee_receiver: ctx.accounts.fee_receiver.key(), + rent_receiver: ctx.accounts.rent_receiver.key(), + remaining_fee + }) + } + + Ok(()) +} diff --git a/programs/dynamic-fee-sharing/src/instructions/ix_initialize_fee_vault.rs b/programs/dynamic-fee-sharing/src/instructions/ix_initialize_fee_vault.rs index dadd994..e79a41c 100644 --- a/programs/dynamic-fee-sharing/src/instructions/ix_initialize_fee_vault.rs +++ b/programs/dynamic-fee-sharing/src/instructions/ix_initialize_fee_vault.rs @@ -108,6 +108,7 @@ pub fn handle_initialize_fee_vault( &Pubkey::default(), 0, FeeVaultType::NonPdaAccount.into(), + &Pubkey::default(), )?; emit_cpi!(EvtInitializeFeeVault { @@ -130,6 +131,7 @@ pub fn create_fee_vault<'info>( base: &Pubkey, fee_vault_bump: u8, fee_vault_type: u8, + admin: &Pubkey, ) -> Result<()> { require!(is_supported_mint(&token_mint)?, FeeVaultError::InvalidMint); @@ -145,6 +147,7 @@ pub fn create_fee_vault<'info>( fee_vault_bump, fee_vault_type, ¶ms.users, + admin, )?; Ok(()) } diff --git a/programs/dynamic-fee-sharing/src/instructions/ix_initialize_fee_vault_pda.rs b/programs/dynamic-fee-sharing/src/instructions/ix_initialize_fee_vault_pda.rs index 0a34e98..aaeb3b8 100644 --- a/programs/dynamic-fee-sharing/src/instructions/ix_initialize_fee_vault_pda.rs +++ b/programs/dynamic-fee-sharing/src/instructions/ix_initialize_fee_vault_pda.rs @@ -80,6 +80,7 @@ pub fn handle_initialize_fee_vault_pda( &ctx.accounts.base.key, ctx.bumps.fee_vault, FeeVaultType::PdaAccount.into(), + &Pubkey::default(), )?; emit_cpi!(EvtInitializeFeeVault { diff --git a/programs/dynamic-fee-sharing/src/instructions/ix_initialize_permission_fee_vault.rs b/programs/dynamic-fee-sharing/src/instructions/ix_initialize_permission_fee_vault.rs new file mode 100644 index 0000000..bc3039c --- /dev/null +++ b/programs/dynamic-fee-sharing/src/instructions/ix_initialize_permission_fee_vault.rs @@ -0,0 +1,98 @@ +use crate::constants::seeds::FEE_VAULT_PREFIX; +use crate::event::EvtInitializeFeeVault; +use crate::state::FeeVaultType; +use crate::{ + constants::seeds::{FEE_VAULT_AUTHORITY_PREFIX, TOKEN_VAULT_PREFIX}, + state::FeeVault, +}; +use crate::{create_fee_vault, InitializeFeeVaultParameters}; +use anchor_lang::prelude::*; +use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; + +#[event_cpi] +#[derive(Accounts)] +pub struct InitializePermissionFeeVaultCtx<'info> { + #[account( + init, + seeds = [ + FEE_VAULT_PREFIX.as_ref(), + base.key().as_ref(), + token_mint.key().as_ref(), + ], + bump, + payer = payer, + space = 8 + FeeVault::INIT_SPACE + )] + pub fee_vault: AccountLoader<'info, FeeVault>, + + /// CHECK: pool authority + #[account( + seeds = [ + FEE_VAULT_AUTHORITY_PREFIX.as_ref(), + ], + bump, + )] + pub fee_vault_authority: UncheckedAccount<'info>, + + #[account( + init, + seeds = [ + TOKEN_VAULT_PREFIX.as_ref(), + fee_vault.key().as_ref(), + ], + token::mint = token_mint, + token::authority = fee_vault_authority, + token::token_program = token_program, + payer = payer, + bump, + )] + pub token_vault: Box>, + + #[account( + mint::token_program = token_program, + )] + pub token_mint: Box>, + + /// CHECK: owner + pub owner: UncheckedAccount<'info>, + + /// CHECK: admin of fee vault + pub admin: UncheckedAccount<'info>, + + pub base: Signer<'info>, + + #[account(mut)] + pub payer: Signer<'info>, + + pub token_program: Interface<'info, TokenInterface>, + + // Sysvar for program account + pub system_program: Program<'info, System>, +} + +pub fn handle_initialize_permission_fee_vault( + ctx: Context, + params: &InitializeFeeVaultParameters, +) -> Result<()> { + create_fee_vault( + &ctx.accounts.token_mint, + params, + &ctx.accounts.fee_vault, + ctx.accounts.owner.key, + &ctx.accounts.token_vault.key(), + &ctx.accounts.base.key, + ctx.bumps.fee_vault, + FeeVaultType::PdaAccount.into(), + ctx.accounts.admin.key, + )?; + + emit_cpi!(EvtInitializeFeeVault { + fee_vault: ctx.accounts.fee_vault.key(), + owner: ctx.accounts.owner.key(), + token_mint: ctx.accounts.token_mint.key(), + params: params.clone(), + base: ctx.accounts.base.key(), + }); + + Ok(()) +} diff --git a/programs/dynamic-fee-sharing/src/instructions/mod.rs b/programs/dynamic-fee-sharing/src/instructions/mod.rs index 44af05a..244dca0 100644 --- a/programs/dynamic-fee-sharing/src/instructions/mod.rs +++ b/programs/dynamic-fee-sharing/src/instructions/mod.rs @@ -8,3 +8,7 @@ pub mod ix_initialize_fee_vault_pda; pub use ix_initialize_fee_vault_pda::*; pub mod ix_fund_by_claiming_fee; pub use ix_fund_by_claiming_fee::*; +pub mod ix_initialize_permission_fee_vault; +pub use ix_initialize_permission_fee_vault::*; +pub mod ix_close_permission_fee_vault; +pub use ix_close_permission_fee_vault::*; diff --git a/programs/dynamic-fee-sharing/src/lib.rs b/programs/dynamic-fee-sharing/src/lib.rs index 2a887bd..c9f6b41 100644 --- a/programs/dynamic-fee-sharing/src/lib.rs +++ b/programs/dynamic-fee-sharing/src/lib.rs @@ -33,6 +33,13 @@ pub mod dynamic_fee_sharing { instructions::handle_initialize_fee_vault_pda(ctx, ¶ms) } + pub fn initialize_permission_fee_vault( + ctx: Context, + params: InitializeFeeVaultParameters, + ) -> Result<()> { + instructions::handle_initialize_permission_fee_vault(ctx, ¶ms) + } + pub fn fund_fee(ctx: Context, max_amount: u64) -> Result<()> { instructions::handle_fund_fee(ctx, max_amount) } @@ -47,4 +54,8 @@ pub mod dynamic_fee_sharing { pub fn claim_fee(ctx: Context, index: u8) -> Result<()> { instructions::handle_claim_fee(ctx, index) } + + pub fn close_permission_fee_vault(ctx: Context) -> Result<()> { + instructions::handle_close_permission_fee_vault(ctx) + } } diff --git a/programs/dynamic-fee-sharing/src/state/fee_vault.rs b/programs/dynamic-fee-sharing/src/state/fee_vault.rs index a9c9519..dd8ab1d 100644 --- a/programs/dynamic-fee-sharing/src/state/fee_vault.rs +++ b/programs/dynamic-fee-sharing/src/state/fee_vault.rs @@ -39,7 +39,8 @@ pub struct FeeVault { pub total_funded_fee: u64, pub fee_per_share: u128, pub base: Pubkey, - pub padding: [u128; 4], + pub admin: Pubkey, + pub padding: [u128; 2], pub users: [UserFee; MAX_USER], } const_assert_eq!(FeeVault::INIT_SPACE, 640); @@ -67,6 +68,7 @@ impl FeeVault { fee_vault_bump: u8, fee_vault_type: u8, users: &[UserShare], + admin: &Pubkey, ) -> Result<()> { self.owner = *owner; self.token_flag = token_flag; @@ -85,6 +87,7 @@ impl FeeVault { self.base = *base; self.fee_vault_bump = fee_vault_bump; self.fee_vault_type = fee_vault_type; + self.admin = *admin; Ok(()) } diff --git a/tests/common/dfs.ts b/tests/common/dfs.ts index 8fcc364..0ec0b20 100644 --- a/tests/common/dfs.ts +++ b/tests/common/dfs.ts @@ -6,6 +6,7 @@ import { deriveFeeVaultAuthorityAddress, deriveFeeVaultPdaAddress, deriveTokenVaultAddress, + getFeeVault, getOrCreateAtA, InitializeFeeVaultParameters, U64_MAX, @@ -31,6 +32,7 @@ import { getVirtualConfigState, getVirtualPoolState, } from "./dbc"; +import BN from "bn.js"; export async function createFeeVaultPda( svm: LiteSVM, @@ -69,7 +71,124 @@ export async function createFeeVaultPda( return { feeVault, tokenVault }; } -async function fundByClaimingFee(svm: LiteSVM, signer: Keypair, feeVault: PublicKey, tokenVault: PublicKey, remainingAccounts: AccountMeta[], payload: Buffer, sourceProgram: PublicKey) { +export async function createPermissionFeeVault( + svm: LiteSVM, + admin: Keypair, + vaultOwner: PublicKey, + tokenMint: PublicKey, + params: InitializeFeeVaultParameters +): Promise<{ + feeVault: PublicKey; + tokenVault: PublicKey; +}> { + const program = createProgram(); + const baseKp = Keypair.generate(); + const feeVault = deriveFeeVaultPdaAddress(baseKp.publicKey, tokenMint); + const tokenVault = deriveTokenVaultAddress(feeVault); + const feeVaultAuthority = deriveFeeVaultAuthorityAddress(); + const tx = await program.methods + .initializePermissionFeeVault(params) + .accountsPartial({ + feeVault, + base: baseKp.publicKey, + feeVaultAuthority, + tokenVault, + tokenMint, + owner: vaultOwner, + admin: admin.publicKey, + payer: admin.publicKey, + tokenProgram: TOKEN_PROGRAM_ID, + }) + .transaction(); + + tx.recentBlockhash = svm.latestBlockhash(); + tx.sign(admin, baseKp); + + sendTransactionOrExpectThrowError(svm, tx); + + return { feeVault, tokenVault }; +} + +export async function closePermissionFeeVault( + svm: LiteSVM, + admin: Keypair, + feeVault: PublicKey +) { + const program = createProgram(); + const feeVaultState = getFeeVault(svm, feeVault); + const feeVaultAuthority = deriveFeeVaultAuthorityAddress(); + const feeReceiver = getOrCreateAtA( + svm, + admin, + feeVaultState.tokenMint, + admin.publicKey + ); + + const tx = await program.methods + .closePermissionFeeVault() + .accountsPartial({ + feeVault, + feeVaultAuthority, + tokenMint: feeVaultState.tokenMint, + tokenVault: feeVaultState.tokenVault, + rentReceiver: admin.publicKey, + feeReceiver, + admin: admin.publicKey, + tokenProgram: TOKEN_PROGRAM_ID, + }) + .transaction(); + + tx.recentBlockhash = svm.latestBlockhash(); + tx.sign(admin); + + return sendTransactionOrExpectThrowError(svm, tx, true); +} + +export async function fundFee( + svm: LiteSVM, + funder: Keypair, + feeVault: PublicKey, + fundAmount: BN +) { + const program = createProgram(); + const feeVaultState = getFeeVault(svm, feeVault); + + const tokenProgram = svm.getAccount(feeVaultState.tokenMint).owner; + + const fundTokenVault = getAssociatedTokenAddressSync( + feeVaultState.tokenMint, + funder.publicKey, + false, + tokenProgram + ); + + const fundFeeTx = await program.methods + .fundFee(fundAmount) + .accountsPartial({ + feeVault, + tokenVault: feeVaultState.tokenVault, + tokenMint: feeVaultState.tokenMint, + fundTokenVault, + funder: funder.publicKey, + tokenProgram, + }) + .transaction(); + + fundFeeTx.recentBlockhash = svm.latestBlockhash(); + fundFeeTx.sign(funder); + + svm.sendTransaction(fundFeeTx); +} + +async function fundByClaimingFee( + svm: LiteSVM, + signer: Keypair, + feeVault: PublicKey, + tokenVault: PublicKey, + remainingAccounts: AccountMeta[], + payload: Buffer, + sourceProgram: PublicKey +) { const program = createProgram(); const tx = await program.methods @@ -78,11 +197,9 @@ async function fundByClaimingFee(svm: LiteSVM, signer: Keypair, feeVault: Public feeVault, tokenVault, signer: signer.publicKey, - sourceProgram + sourceProgram, }) - .remainingAccounts( - remainingAccounts - ) + .remainingAccounts(remainingAccounts) .transaction(); tx.recentBlockhash = svm.latestBlockhash(); @@ -90,7 +207,7 @@ async function fundByClaimingFee(svm: LiteSVM, signer: Keypair, feeVault: Public const result = sendTransactionOrExpectThrowError(svm, tx); - return result + return result; } export async function claimDammV2Fee( @@ -101,9 +218,8 @@ export async function claimDammV2Fee( tokenVault: PublicKey, dammv2Pool: PublicKey, position: PublicKey, - positionNftAccount: PublicKey, + positionNftAccount: PublicKey ) { - const dammV2PoolState = getDammV2PoolState(svm, dammv2Pool); const tokenAAccount = getAssociatedTokenAddressSync( @@ -192,11 +308,20 @@ export async function claimDammV2Fee( }, ]; - const claimPositionFeeDisc = CpAmmIDL.instructions.find(instruction => instruction.name === "claim_position_fee").discriminator; - const payload = Buffer.from(claimPositionFeeDisc) - - await fundByClaimingFee(svm, signer, feeVault, tokenVault, remainingAccounts, payload, DAMM_V2_PROGRAM_ID) + const claimPositionFeeDisc = CpAmmIDL.instructions.find( + (instruction) => instruction.name === "claim_position_fee" + ).discriminator; + const payload = Buffer.from(claimPositionFeeDisc); + await fundByClaimingFee( + svm, + signer, + feeVault, + tokenVault, + remainingAccounts, + payload, + DAMM_V2_PROGRAM_ID + ); } export async function claimDammV2Reward( @@ -208,9 +333,8 @@ export async function claimDammV2Reward( dammv2Pool: PublicKey, position: PublicKey, positionNftAccount: PublicKey, - rewardIndex: number, + rewardIndex: number ) { - const dammV2PoolState = getDammV2PoolState(svm, dammv2Pool); const remainingAccounts = [ @@ -257,7 +381,9 @@ export async function claimDammV2Reward( { isSigner: false, isWritable: false, - pubkey: getProgramFromFlagDammV2(dammV2PoolState.rewardInfos[rewardIndex].rewardTokenFlag), + pubkey: getProgramFromFlagDammV2( + dammV2PoolState.rewardInfos[rewardIndex].rewardTokenFlag + ), }, { isSigner: false, @@ -271,10 +397,23 @@ export async function claimDammV2Reward( }, ]; - const claimDammV2RewardDisc = CpAmmIDL.instructions.find(instruction => instruction.name === "claim_reward").discriminator; - const payload = Buffer.concat([Buffer.from(claimDammV2RewardDisc), Buffer.from([rewardIndex]), Buffer.from([1])]) - await fundByClaimingFee(svm, signer, feeVault, tokenVault, remainingAccounts, payload, DAMM_V2_PROGRAM_ID) - + const claimDammV2RewardDisc = CpAmmIDL.instructions.find( + (instruction) => instruction.name === "claim_reward" + ).discriminator; + const payload = Buffer.concat([ + Buffer.from(claimDammV2RewardDisc), + Buffer.from([rewardIndex]), + Buffer.from([1]), + ]); + await fundByClaimingFee( + svm, + signer, + feeVault, + tokenVault, + remainingAccounts, + payload, + DAMM_V2_PROGRAM_ID + ); } export async function claimDbcCreatorTradingFee( @@ -362,10 +501,25 @@ export async function claimDbcCreatorTradingFee( isWritable: false, pubkey: DBC_PROGRAM_ID, }, - ] - const claimDbcCreatorTradingFeeDisc = DynamicBondingCurveIDL.instructions.find(instruction => instruction.name === "claim_creator_trading_fee").discriminator; - const payload = Buffer.concat([Buffer.from(claimDbcCreatorTradingFeeDisc), U64_MAX.toBuffer(), U64_MAX.toBuffer()]) - await fundByClaimingFee(svm, signer, feeVault, tokenVault, remainingAccounts, payload, DBC_PROGRAM_ID); + ]; + const claimDbcCreatorTradingFeeDisc = + DynamicBondingCurveIDL.instructions.find( + (instruction) => instruction.name === "claim_creator_trading_fee" + ).discriminator; + const payload = Buffer.concat([ + Buffer.from(claimDbcCreatorTradingFeeDisc), + U64_MAX.toBuffer(), + U64_MAX.toBuffer(), + ]); + await fundByClaimingFee( + svm, + signer, + feeVault, + tokenVault, + remainingAccounts, + payload, + DBC_PROGRAM_ID + ); } export async function claimDbcPartnerTradingFee( @@ -461,10 +615,25 @@ export async function claimDbcPartnerTradingFee( isWritable: false, pubkey: DBC_PROGRAM_ID, }, - ] - const claimDbcPartnerTradingFeeDisc = DynamicBondingCurveIDL.instructions.find(instruction => instruction.name === "claim_trading_fee").discriminator; - const payload = Buffer.concat([Buffer.from(claimDbcPartnerTradingFeeDisc), U64_MAX.toBuffer(), U64_MAX.toBuffer()]) - await fundByClaimingFee(svm, signer, feeVault, tokenVault, remainingAccounts, payload, DBC_PROGRAM_ID); + ]; + const claimDbcPartnerTradingFeeDisc = + DynamicBondingCurveIDL.instructions.find( + (instruction) => instruction.name === "claim_trading_fee" + ).discriminator; + const payload = Buffer.concat([ + Buffer.from(claimDbcPartnerTradingFeeDisc), + U64_MAX.toBuffer(), + U64_MAX.toBuffer(), + ]); + await fundByClaimingFee( + svm, + signer, + feeVault, + tokenVault, + remainingAccounts, + payload, + DBC_PROGRAM_ID + ); } export async function withdrawDbcCreatorSurplus( @@ -529,10 +698,20 @@ export async function withdrawDbcCreatorSurplus( isWritable: false, pubkey: DBC_PROGRAM_ID, }, - ] - const creatorWithdrawSurplusDisc = DynamicBondingCurveIDL.instructions.find(instruction => instruction.name === "creator_withdraw_surplus").discriminator; - const payload = Buffer.from(creatorWithdrawSurplusDisc) - await fundByClaimingFee(svm, signer, feeVault, tokenVault, remainingAccounts, payload, DBC_PROGRAM_ID); + ]; + const creatorWithdrawSurplusDisc = DynamicBondingCurveIDL.instructions.find( + (instruction) => instruction.name === "creator_withdraw_surplus" + ).discriminator; + const payload = Buffer.from(creatorWithdrawSurplusDisc); + await fundByClaimingFee( + svm, + signer, + feeVault, + tokenVault, + remainingAccounts, + payload, + DBC_PROGRAM_ID + ); } export async function withdrawDbcPartnerSurplus( @@ -597,10 +776,20 @@ export async function withdrawDbcPartnerSurplus( isWritable: false, pubkey: DBC_PROGRAM_ID, }, - ] - const partnerWithdrawSurplusDisc = DynamicBondingCurveIDL.instructions.find(instruction => instruction.name === "partner_withdraw_surplus").discriminator; - const payload = Buffer.from(partnerWithdrawSurplusDisc) - await fundByClaimingFee(svm, signer, feeVault, tokenVault, remainingAccounts, payload, DBC_PROGRAM_ID); + ]; + const partnerWithdrawSurplusDisc = DynamicBondingCurveIDL.instructions.find( + (instruction) => instruction.name === "partner_withdraw_surplus" + ).discriminator; + const payload = Buffer.from(partnerWithdrawSurplusDisc); + await fundByClaimingFee( + svm, + signer, + feeVault, + tokenVault, + remainingAccounts, + payload, + DBC_PROGRAM_ID + ); } export async function withdrawMigrationFee( @@ -610,7 +799,7 @@ export async function withdrawMigrationFee( tokenVault: PublicKey, poolConfig: PublicKey, virtualPool: PublicKey, - isPartner: number, // 0 as partner and 1 as creator + isPartner: number // 0 as partner and 1 as creator ) { const virtualPoolState = getVirtualPoolState(svm, virtualPool); const poolConfigState = getVirtualConfigState(svm, poolConfig); @@ -666,8 +855,21 @@ export async function withdrawMigrationFee( isWritable: false, pubkey: DBC_PROGRAM_ID, }, - ] - const withdrawMigrationFeeDisc = DynamicBondingCurveIDL.instructions.find(instruction => instruction.name === "withdraw_migration_fee").discriminator; - const payload = Buffer.concat([Buffer.from(withdrawMigrationFeeDisc), Buffer.from([isPartner])]) - await fundByClaimingFee(svm, signer, feeVault, tokenVault, remainingAccounts, payload, DBC_PROGRAM_ID); + ]; + const withdrawMigrationFeeDisc = DynamicBondingCurveIDL.instructions.find( + (instruction) => instruction.name === "withdraw_migration_fee" + ).discriminator; + const payload = Buffer.concat([ + Buffer.from(withdrawMigrationFeeDisc), + Buffer.from([isPartner]), + ]); + await fundByClaimingFee( + svm, + signer, + feeVault, + tokenVault, + remainingAccounts, + payload, + DBC_PROGRAM_ID + ); } diff --git a/tests/common/svm.ts b/tests/common/svm.ts index fb5152c..2ec155f 100644 --- a/tests/common/svm.ts +++ b/tests/common/svm.ts @@ -71,7 +71,7 @@ export function sendTransactionOrExpectThrowError( expect(result).instanceOf(TransactionMetadata); } - return result + return result; } export function generateUsers(svm: LiteSVM, numberOfUsers: number): Keypair[] { @@ -112,5 +112,7 @@ export function warpToTimestamp(svm: LiteSVM, timestamp: BN) { export function getTokenBalance(svm: LiteSVM, ataAccount: PublicKey): BN { const account = svm.getAccount(ataAccount); - return new BN(AccountLayout.decode(account.data).amount.toString()); + return account + ? new BN(AccountLayout.decode(account.data).amount.toString()) + : new BN(0); } diff --git a/tests/permission_fee_vault.test.ts b/tests/permission_fee_vault.test.ts new file mode 100644 index 0000000..c8fdc84 --- /dev/null +++ b/tests/permission_fee_vault.test.ts @@ -0,0 +1,113 @@ +import { LiteSVM } from "litesvm"; +import { PublicKey, Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js"; +import { + createProgram, + createToken, + DynamicFeeSharingProgram, + generateUsers, + getFeeVault, + InitializeFeeVaultParameters, + mintToken, + TOKEN_DECIMALS, +} from "./common"; +import { BN } from "bn.js"; + +import DynamicFeeSharingIDL from "../target/idl/dynamic_fee_sharing.json"; +import { + closePermissionFeeVault, + createPermissionFeeVault, + fundFee, +} from "./common/dfs"; +import { getTokenBalance } from "./common/svm"; +import { getAssociatedTokenAddressSync } from "@solana/spl-token"; +import { expect } from "chai"; + +describe("Permission fee vault", () => { + let program: DynamicFeeSharingProgram; + let svm: LiteSVM; + let admin: Keypair; + let funder: Keypair; + let vaultOwner: Keypair; + let tokenMint: PublicKey; + let user: Keypair; + + beforeEach(async () => { + program = createProgram(); + svm = new LiteSVM(); + svm.addProgramFromFile( + new PublicKey(DynamicFeeSharingIDL.address), + "./target/deploy/dynamic_fee_sharing.so" + ); + + admin = Keypair.generate(); + vaultOwner = Keypair.generate(); + funder = Keypair.generate(); + user = Keypair.generate(); + + svm.airdrop(admin.publicKey, BigInt(LAMPORTS_PER_SOL)); + svm.airdrop(vaultOwner.publicKey, BigInt(LAMPORTS_PER_SOL)); + svm.airdrop(funder.publicKey, BigInt(LAMPORTS_PER_SOL)); + svm.airdrop(user.publicKey, BigInt(LAMPORTS_PER_SOL)); + + tokenMint = createToken(svm, admin, admin.publicKey, null); + mintToken(svm, admin, tokenMint, admin, funder.publicKey); + }); + + it("Full flow", async () => { + const generatedUser = generateUsers(svm, 5); // 5 users + const users = generatedUser.map((item) => { + return { + address: item.publicKey, + share: 1000, + }; + }); + + const params: InitializeFeeVaultParameters = { + padding: [], + users, + }; + + await fullFlow(svm, admin, funder, vaultOwner.publicKey, tokenMint, params); + }); +}); + +async function fullFlow( + svm: LiteSVM, + admin: Keypair, + funder: Keypair, + vaultOwner: PublicKey, + tokenMint: PublicKey, + params: InitializeFeeVaultParameters +) { + const { feeVault } = await createPermissionFeeVault( + svm, + admin, + vaultOwner, + tokenMint, + params + ); + + const fundAmount = new BN(100_000 * 10 ** TOKEN_DECIMALS); + + console.log("fund fee vault"); + + await fundFee(svm, funder, feeVault, fundAmount); + + console.log("close fee vault"); + const feeVaultState = getFeeVault(svm, feeVault); + const preVaultBalance = getTokenBalance(svm, feeVaultState.tokenVault); + const adminPreBalance = getTokenBalance( + svm, + getAssociatedTokenAddressSync(feeVaultState.tokenMint, admin.publicKey) + ); + + await closePermissionFeeVault(svm, admin, feeVault); + + const adminPostBalance = getTokenBalance( + svm, + getAssociatedTokenAddressSync(feeVaultState.tokenMint, admin.publicKey) + ); + expect(adminPostBalance.sub(adminPreBalance).toString()).eq( + preVaultBalance.toString() + ); +}