Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- Update anchor to `1.0.2`
- The `fund_by_claiming_fee` endpoint now rejects when `skip_reward` is non-zero for DAMM v2 `claim_reward` instruction to prevent clearing pending rewards without funding the `FeeVault`

## dynamic-fee-sharing [0.1.1] [PR #8](https://github.com/MeteoraAg/dynamic-fee-sharing/pull/8)

Expand Down
3 changes: 3 additions & 0 deletions programs/dynamic-fee-sharing/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,7 @@ pub enum FeeVaultError {

#[msg("Invalid action")]
InvalidAction,

#[msg("Invalid parameters")]
InvalidParameters,
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub struct FundByClaimingFeeCtx<'info> {
pub source_program: UncheckedAccount<'info>,
}

pub fn is_support_action<'info>(
fn is_support_action<'info>(
source_program: &Pubkey,
discriminator: &[u8],
token_vault: Pubkey,
Expand All @@ -38,11 +38,29 @@ pub fn is_support_action<'info>(
false
}

fn validate_payload(source_program: &Pubkey, discriminator: &[u8], payload: &[u8]) -> Result<()> {
if source_program.eq(&damm_v2::ID)
&& discriminator == damm_v2::client::args::ClaimReward::DISCRIMINATOR
{
let args = damm_v2::client::args::ClaimReward::try_from_slice(&payload[8..])
.map_err(|_| FeeVaultError::InvalidAction)?;

// when skip_reward == 1 and the token_account is frozen, damm_v2 clears the pending reward without transferring it
// so skipping the reward is not allowed when funding the FeeVault
require!(args.skip_reward == 0, FeeVaultError::InvalidParameters);
}

Ok(())
}

pub fn handle_fund_by_claiming_fee(
ctx: Context<FundByClaimingFeeCtx>,
payload: Vec<u8>,
) -> Result<()> {
require!(payload.len() >= 8, FeeVaultError::InvalidAction);

let discriminator = &payload[..8]; // first 8 bytes is discriminator

require!(
is_support_action(
ctx.accounts.source_program.key,
Expand All @@ -53,6 +71,8 @@ pub fn handle_fund_by_claiming_fee(
FeeVaultError::InvalidAction
);

validate_payload(ctx.accounts.source_program.key, &discriminator, &payload)?;

let fee_vault = ctx.accounts.fee_vault.load()?;

require!(
Expand Down
69 changes: 68 additions & 1 deletion tests/claim_damm_v2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import {
startSvm,
warpToTimestamp,
} from "./common/svm";
import { createToken, getFeeVault, mintToken } from "./common";
import {
createToken,
getFeeVault,
getProgramErrorCodeHexString,
mintToken,
} from "./common";
import {
createDammV2Pool,
dammV2Swap,
Expand Down Expand Up @@ -213,4 +218,66 @@ describe("Fund by claiming damm v2", () => {
);
expect(Number(postFeePerShare.sub(preFeePerShare))).gt(0);
});

it("Reject claiming damm v2 reward with skip_reward set", async () => {
const { feeVault, tokenVault } = await createFeeVaultPda(
svm,
admin,
vaultOwner.publicKey,
rewardMint,
{
padding: [],
users: [
{
address: shareHolder.publicKey,
share: 100,
},
{
address: PublicKey.unique(),
share: 100,
},
],
}
);

const setAuthorityIx = createSetAuthorityInstruction(
positionNftAccount,
creator.publicKey,
AuthorityType.AccountOwner,
feeVault,
[],
TOKEN_2022_PROGRAM_ID
);
const assignOwnerTx = new Transaction().add(setAuthorityIx);
assignOwnerTx.recentBlockhash = svm.latestBlockhash();
assignOwnerTx.sign(creator);

sendTransactionOrExpectThrowError(svm, assignOwnerTx);

const rewardIndex = 0;
await initializeAndFundReward(
svm,
creator,
dammV2Pool,
rewardMint,
rewardIndex
);

warpToTimestamp(svm, new BN(12 * 60 * 60));

const errorCode = getProgramErrorCodeHexString("InvalidParameters");
await claimDammV2Reward(
svm,
shareHolder,
creator,
feeVault,
tokenVault,
dammV2Pool,
position,
positionNftAccount,
rewardIndex,
true,
errorCode
);
});
});
14 changes: 9 additions & 5 deletions tests/common/dfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ async function fundByClaimingFee(
tokenVault: PublicKey,
remainingAccounts: AccountMeta[],
payload: Buffer,
sourceProgram: PublicKey
sourceProgram: PublicKey,
errorCode?: number
) {
const program = createProgram();

Expand All @@ -94,7 +95,7 @@ async function fundByClaimingFee(
tx.recentBlockhash = svm.latestBlockhash();
tx.sign(signer);

const result = sendTransactionOrExpectThrowError(svm, tx);
const result = sendTransactionOrExpectThrowError(svm, tx, false, errorCode);

return result;
}
Expand Down Expand Up @@ -222,7 +223,9 @@ export async function claimDammV2Reward(
dammv2Pool: PublicKey,
position: PublicKey,
positionNftAccount: PublicKey,
rewardIndex: number
rewardIndex: number,
skipReward = false,
errorCode?: number
) {
const dammV2PoolState = getDammV2PoolState(svm, dammv2Pool);

Expand Down Expand Up @@ -292,7 +295,7 @@ export async function claimDammV2Reward(
const payload = Buffer.concat([
Buffer.from(claimDammV2RewardDisc),
Buffer.from([rewardIndex]),
Buffer.from([1]),
Buffer.from([Number(skipReward)]),
]);
await fundByClaimingFee(
svm,
Expand All @@ -301,7 +304,8 @@ export async function claimDammV2Reward(
tokenVault,
remainingAccounts,
payload,
DAMM_V2_PROGRAM_ID
DAMM_V2_PROGRAM_ID,
errorCode
);
}

Expand Down
Loading