Skip to content
Open
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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ on:
- release_*

env:
SOLANA_CLI_VERSION: 2.1.0
SOLANA_CLI_VERSION: 2.3.13
NODE_VERSION: 22.15.0
ANCHOR_CLI_VERSION: 0.31.0
TOOLCHAIN: 1.76.0
ANCHOR_CLI_VERSION: 0.31.1
TOOLCHAIN: 1.85.0

jobs:
program_changed_files:
Expand Down
2 changes: 2 additions & 0 deletions Anchor.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
[toolchain]
anchor_version = "0.31.1"
solana_version = "2.3.13"
package_manager = "yarn"

[features]
Expand Down
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Breaking Changes

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

### Added

- Add a new field `mutable_flag` to `FeeVault` to indicate its mutability
- Add a new field `operator` to `FeeVault`. The `operator` and vault owner can perform operator instructions on mutable `FeeVault`
- Add a new owner endpoint `update_operator` for vault owner to update the operator field
- Add a new operator endpoint `add_user` to add a user to a `FeeVault`
- Add a new operator endpoint `remove_user` which removes a user and transfers any unclaimed fee into an account for the removed user to claim
- Add a new endpoint `claim_removed_user_fee` where a user who have been removed from the `FeeVault` can claim any unclaimed fees
- Add a new operator endpoint `update_user_share` to update a user's share. This affects the fees the user will be entitled to when the vault is funded. Any fees users earned before the share changed will be preserved

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

### Added

- Add new field `fee_vault_type` in `FeeVault` to distinguish between PDA-derived and keypair-derived fee vaults.
- Add new endpoint `fund_by_claiming_fee`, that allow share holder in fee vault to claim fees from whitelisted endpoints of DAMM-v2 or Dynamic Bonding Curve

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ incremental = false
codegen-units = 1

[workspace.dependencies]
anchor-lang = {version = "0.31.1", features = ["event-cpi"]}
anchor-lang = {version = "0.31.1", features = ["event-cpi", "init-if-needed"]}
anchor-spl = "0.31.1"
bytemuck = { version = "1.20.0", features = ["derive", "min_const_generics"] }
10 changes: 2 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,11 @@

- Program ID: `dfsdo2UqvwfN8DuUVrMRNfQe11VaiNoKcMqLHVvDPzh`


### Development

### Dependencies

- anchor 0.31.0
- solana 2.2.14

### Build

Program
Program

```
anchor build
Expand All @@ -25,4 +19,4 @@ anchor build
```
pnpm install
pnpm test
```
```
2 changes: 2 additions & 0 deletions programs/dynamic-fee-sharing/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use anchor_lang::prelude::Pubkey;
use anchor_lang::Discriminator;

pub const MIN_USER: usize = 2;
pub const MAX_USER: usize = 5;
pub const PRECISION_SCALE: u8 = 64;

pub mod seeds {
pub const FEE_VAULT_PREFIX: &[u8] = b"fee_vault";
pub const FEE_VAULT_AUTHORITY_PREFIX: &[u8] = b"fee_vault_authority";
pub const TOKEN_VAULT_PREFIX: &[u8] = b"token_vault";
pub const REMOVED_USER_TOKEN_VAULT: &[u8] = b"removed_user_token_vault";
}

// (program_id, instruction, index_of_token_vault_account)
Expand Down
10 changes: 8 additions & 2 deletions programs/dynamic-fee-sharing/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ pub enum FeeVaultError {
#[msg("Invalid user address")]
InvalidUserAddress,

#[msg("Exceeded number of users allowed")]
ExceededUser,
#[msg("Invalid number of users")]
InvalidNumberOfUsers,
Comment on lines +26 to +27
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the error name here since. This error is also used when the number of user is less than the minimum amount (2)


#[msg("Invalid fee vault")]
InvalidFeeVault,
Expand All @@ -34,4 +34,10 @@ pub enum FeeVaultError {

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

#[msg("Invalid permission")]
InvalidPermission,

#[msg("Invalid operator address")]
InvalidOperatorAddress,
}
34 changes: 34 additions & 0 deletions programs/dynamic-fee-sharing/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,37 @@ pub struct EvtClaimFee {
pub index: u8,
pub claimed_fee: u64,
}

#[event]
pub struct EvtAddUser {
pub fee_vault: Pubkey,
pub user: Pubkey,
pub share: u32,
}

#[event]
pub struct EvtUpdateUserShare {
pub fee_vault: Pubkey,
pub user: Pubkey,
pub share: u32,
}

#[event]
pub struct EvtRemoveUser {
pub fee_vault: Pubkey,
pub user: Pubkey,
pub unclaimed_fee: u64,
}

#[event]
pub struct EvtClaimRemovedUserFee {
pub fee_vault: Pubkey,
pub user: Pubkey,
pub claimed_fee: u64,
}

#[event]
pub struct EvtUpdateOperator {
pub fee_vault: Pubkey,
pub operator: Pubkey,
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ pub struct ClaimFeeCtx<'info> {

pub fn handle_claim_fee(ctx: Context<ClaimFeeCtx>, index: u8) -> Result<()> {
let mut fee_vault = ctx.accounts.fee_vault.load_mut()?;
let fee_being_claimed = fee_vault.validate_and_claim_fee(index, &ctx.accounts.user.key())?;
let fee_being_claimed =
fee_vault.validate_and_claim_fee(index.into(), &ctx.accounts.user.key())?;

if fee_being_claimed > 0 {
transfer_from_fee_vault(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use crate::const_pda;
use crate::constants::seeds::REMOVED_USER_TOKEN_VAULT;
use crate::event::EvtClaimRemovedUserFee;
use crate::state::FeeVault;
use crate::utils::token::transfer_from_fee_vault;
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{
close_account, CloseAccount, Mint, TokenAccount, TokenInterface,
};

#[event_cpi]
#[derive(Accounts)]
pub struct ClaimRemovedUserFeeCtx<'info> {
#[account(has_one = token_mint, has_one = owner)]
pub fee_vault: AccountLoader<'info, FeeVault>,

/// CHECK: fee vault authority
#[account(address = const_pda::fee_vault_authority::ID)]
pub fee_vault_authority: UncheckedAccount<'info>,

pub token_mint: Box<InterfaceAccount<'info, Mint>>,

#[account(
mut,
seeds = [
REMOVED_USER_TOKEN_VAULT,
fee_vault.key().as_ref(),
token_mint.key().as_ref(),
user.key().as_ref(),
],
bump,
token::mint = token_mint,
token::authority = fee_vault_authority,
)]
pub removed_user_token_vault: Box<InterfaceAccount<'info, TokenAccount>>,

#[account(
mut,
token::authority = user,
token::mint = token_mint,
)]
pub user_token_vault: Box<InterfaceAccount<'info, TokenAccount>>,

/// CHECK: fee vault owner, receives rent from closed account
#[account(mut)]
pub owner: UncheckedAccount<'info>,

pub user: Signer<'info>,

pub token_program: Interface<'info, TokenInterface>,
}

pub fn handle_claim_removed_user_fee(ctx: Context<ClaimRemovedUserFeeCtx>) -> Result<()> {
let fee_being_claimed = ctx.accounts.removed_user_token_vault.amount;

if fee_being_claimed > 0 {
transfer_from_fee_vault(
ctx.accounts.fee_vault_authority.to_account_info(),
&ctx.accounts.token_mint,
&ctx.accounts.removed_user_token_vault,
&ctx.accounts.user_token_vault,
&ctx.accounts.token_program,
fee_being_claimed,
)?;
}

let signer_seeds = fee_vault_authority_seeds!();
close_account(CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
CloseAccount {
account: ctx.accounts.removed_user_token_vault.to_account_info(),
destination: ctx.accounts.owner.to_account_info(),
authority: ctx.accounts.fee_vault_authority.to_account_info(),
},
&[&signer_seeds[..]],
))?;

emit_cpi!(EvtClaimRemovedUserFee {
fee_vault: ctx.accounts.fee_vault.key(),
user: ctx.accounts.user.key(),
claimed_fee: fee_being_claimed,
});

Ok(())
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::constants::MAX_USER;
use crate::constants::{MAX_USER, MIN_USER};
use crate::error::FeeVaultError;
use crate::event::EvtInitializeFeeVault;
use crate::state::FeeVaultType;
Expand All @@ -12,7 +12,8 @@ use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};

#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone)]
pub struct InitializeFeeVaultParameters {
pub padding: [u64; 8], // for future use
pub padding: [u8; 63], // for future use
pub mutable_flag: bool,
pub users: Vec<UserShare>,
}

Expand All @@ -24,12 +25,12 @@ pub struct UserShare {

impl InitializeFeeVaultParameters {
pub fn validate(&self) -> Result<()> {
let number_of_user = self.users.len();
let number_of_users = self.users.len();
require!(
number_of_user >= 2 && number_of_user <= MAX_USER,
FeeVaultError::ExceededUser
number_of_users >= MIN_USER && number_of_users <= MAX_USER,
FeeVaultError::InvalidNumberOfUsers
);
for i in 0..number_of_user {
for i in 0..number_of_users {
require!(
self.users[i].share > 0,
FeeVaultError::InvalidFeeVaultParameters
Expand Down Expand Up @@ -108,6 +109,7 @@ pub fn handle_initialize_fee_vault(
&Pubkey::default(),
0,
FeeVaultType::NonPdaAccount.into(),
params.mutable_flag.into(),
)?;

emit_cpi!(EvtInitializeFeeVault {
Expand All @@ -130,6 +132,7 @@ pub fn create_fee_vault<'info>(
base: &Pubkey,
fee_vault_bump: u8,
fee_vault_type: u8,
mutable_flag: u8,
) -> Result<()> {
require!(is_supported_mint(&token_mint)?, FeeVaultError::InvalidMint);

Expand All @@ -145,6 +148,7 @@ pub fn create_fee_vault<'info>(
fee_vault_bump,
fee_vault_type,
&params.users,
mutable_flag,
)?;
Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pub fn handle_initialize_fee_vault_pda(
&ctx.accounts.base.key,
ctx.bumps.fee_vault,
FeeVaultType::PdaAccount.into(),
params.mutable_flag.into(),
)?;

emit_cpi!(EvtInitializeFeeVault {
Expand Down
6 changes: 6 additions & 0 deletions programs/dynamic-fee-sharing/src/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@ 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_claim_removed_user_fee;
pub use ix_claim_removed_user_fee::*;
pub mod operator;
pub use operator::*;
pub mod owner;
pub use owner::*;
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use crate::event::EvtAddUser;
use crate::state::FeeVault;
use anchor_lang::prelude::*;

#[event_cpi]
#[derive(Accounts)]
pub struct AddUserCtx<'info> {
#[account(mut)]
pub fee_vault: AccountLoader<'info, FeeVault>,

/// CHECK: the user being added
pub user: UncheckedAccount<'info>,

pub signer: Signer<'info>,
}

pub fn handle_add_user(ctx: Context<AddUserCtx>, share: u32) -> Result<()> {
let mut fee_vault = ctx.accounts.fee_vault.load_mut()?;
let user = ctx.accounts.user.key();
fee_vault.validate_and_add_user(&user, share)?;

emit_cpi!(EvtAddUser {
fee_vault: ctx.accounts.fee_vault.key(),
user,
share,
});

Ok(())
}
Loading
Loading