Skip to content

Commit 35248a6

Browse files
feat: add folio fee claimed account (#117)
* feat: add folio fee claimed account * feat: just file for arch based linux
1 parent adeb669 commit 35248a6

22 files changed

+508
-51
lines changed

justfile

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,14 @@ build-local:
8181
# Anchor build folio program, which is used for folio migration related tests.
8282
@anchor build --program-name folio -- --features test,dev
8383

84-
# Update program ID in IDL and type files (Mac compatible)
85-
@sed -i '' 's/DTF4yDGBkXJ25Ech1JVQpfwVb1vqYW4RJs5SuGNWdDev/n6sR7Eg5LMg5SGorxK9q3ZePHs9e8gjoQ7TgUW2YCaG/g' target/idl/folio.json
86-
@sed -i '' 's/DTF4yDGBkXJ25Ech1JVQpfwVb1vqYW4RJs5SuGNWdDev/n6sR7Eg5LMg5SGorxK9q3ZePHs9e8gjoQ7TgUW2YCaG/g' target/types/folio.ts
84+
# Update program ID in IDL and type files (cross-platform sed)
85+
@if [[ "$OSTYPE" == "darwin"* ]]; then \
86+
sed -i '' 's/DTF4yDGBkXJ25Ech1JVQpfwVb1vqYW4RJs5SuGNWdDev/n6sR7Eg5LMg5SGorxK9q3ZePHs9e8gjoQ7TgUW2YCaG/g' target/idl/folio.json; \
87+
sed -i '' 's/DTF4yDGBkXJ25Ech1JVQpfwVb1vqYW4RJs5SuGNWdDev/n6sR7Eg5LMg5SGorxK9q3ZePHs9e8gjoQ7TgUW2YCaG/g' target/types/folio.ts; \
88+
else \
89+
sed -i 's/DTF4yDGBkXJ25Ech1JVQpfwVb1vqYW4RJs5SuGNWdDev/n6sR7Eg5LMg5SGorxK9q3ZePHs9e8gjoQ7TgUW2YCaG/g' target/idl/folio.json; \
90+
sed -i 's/DTF4yDGBkXJ25Ech1JVQpfwVb1vqYW4RJs5SuGNWdDev/n6sR7Eg5LMg5SGorxK9q3ZePHs9e8gjoQ7TgUW2YCaG/g' target/types/folio.ts; \
91+
fi
8792

8893
# Rename second instance files
8994
@mv target/idl/folio.json target/idl/second_folio.json
@@ -125,9 +130,14 @@ build-dev:
125130
# Anchor build with dev feature flag
126131
@anchor build -- --features dev
127132

128-
# Replaces keys in folio with dev keys.
129-
@sed -i '' 's/DTF4yDGBkXJ25Ech1JVQpfwVb1vqYW4RJs5SuGNWdDev/n6sR7Eg5LMg5SGorxK9q3ZePHs9e8gjoQ7TgUW2YCaG/g' target/idl/folio.json
130-
@sed -i '' 's/DTF4yDGBkXJ25Ech1JVQpfwVb1vqYW4RJs5SuGNWdDev/n6sR7Eg5LMg5SGorxK9q3ZePHs9e8gjoQ7TgUW2YCaG/g' target/types/folio.ts
133+
# Replaces keys in folio with dev keys (cross-platform sed)
134+
@if [[ "$OSTYPE" == "darwin"* ]]; then \
135+
sed -i '' 's/DTF4yDGBkXJ25Ech1JVQpfwVb1vqYW4RJs5SuGNWdDev/n6sR7Eg5LMg5SGorxK9q3ZePHs9e8gjoQ7TgUW2YCaG/g' target/idl/folio.json; \
136+
sed -i '' 's/DTF4yDGBkXJ25Ech1JVQpfwVb1vqYW4RJs5SuGNWdDev/n6sR7Eg5LMg5SGorxK9q3ZePHs9e8gjoQ7TgUW2YCaG/g' target/types/folio.ts; \
137+
else \
138+
sed -i 's/DTF4yDGBkXJ25Ech1JVQpfwVb1vqYW4RJs5SuGNWdDev/n6sR7Eg5LMg5SGorxK9q3ZePHs9e8gjoQ7TgUW2YCaG/g' target/idl/folio.json; \
139+
sed -i 's/DTF4yDGBkXJ25Ech1JVQpfwVb1vqYW4RJs5SuGNWdDev/n6sR7Eg5LMg5SGorxK9q3ZePHs9e8gjoQ7TgUW2YCaG/g' target/types/folio.ts; \
140+
fi
131141

132142
@echo "Done| Governance program is not built"
133143

@@ -206,4 +216,3 @@ test-coverage:
206216
"programs/*/src/lib.rs" \
207217
"programs/*/src/**/errors.rs" \
208218
--out Html \
209-
--output-dir target/tarpaulin

programs/folio-admin/src/instructions/set_folio_fee_config.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ pub struct SetFolioFeeConfig<'info> {
7979
/// CHECK: DAO fee recipient account, checks done on the folio program
8080
#[account(mut)]
8181
pub dao_fee_recipient: UncheckedAccount<'info>,
82+
83+
/// CHECK: DAO fee recipient account, checks done on the folio program
84+
#[account(mut)]
85+
pub dao_fee_claimed: UncheckedAccount<'info>,
8286
}
8387

8488
impl SetFolioFeeConfig<'_> {
@@ -135,6 +139,7 @@ pub fn handler(
135139
&ctx.accounts.fee_recipients.to_account_info(),
136140
&ctx.accounts.fee_distribution.to_account_info(),
137141
&ctx.accounts.dao_fee_recipient.to_account_info(),
142+
&ctx.accounts.dao_fee_claimed.to_account_info(),
138143
)?;
139144

140145
let folio_fee_config = &mut ctx.accounts.folio_fee_config;

programs/folio-admin/src/utils/external/folio_program.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ impl FolioProgram {
8787
fee_recipients: &AccountInfo<'a>,
8888
fee_distribution: &AccountInfo<'a>,
8989
dao_fee_recipient: &AccountInfo<'a>,
90+
dao_fee_claimed: &AccountInfo<'a>,
9091
) -> Result<()> {
9192
// Won't distribute the fees if the fee recipients account is not initialized (since it's initialized on update_folio instruction)
9293
if fee_recipients.data_is_empty() {
@@ -105,6 +106,7 @@ impl FolioProgram {
105106
fee_recipients.clone(),
106107
fee_distribution.clone(),
107108
dao_fee_recipient.clone(),
109+
dao_fee_claimed.clone(),
108110
];
109111

110112
let account_metas = vec![
@@ -119,6 +121,7 @@ impl FolioProgram {
119121
AccountMeta::new(fee_recipients.key(), false),
120122
AccountMeta::new(fee_distribution.key(), false),
121123
AccountMeta::new(dao_fee_recipient.key(), false),
124+
AccountMeta::new(dao_fee_claimed.key(), false),
122125
];
123126

124127
let mut data =

programs/folio/src/instructions/crank/crank_fee_distribution.rs

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
use crate::events::TVLFeePaid;
2-
use crate::state::{FeeDistribution, Folio};
2+
use crate::state::{FeeDistribution, Folio, FolioFeeClaimed};
33
use crate::utils::structs::FolioStatus;
44
use anchor_lang::prelude::*;
55
use anchor_spl::associated_token::get_associated_token_address_with_program_id;
66
use anchor_spl::token_2022;
77
use anchor_spl::token_interface::{self, Mint, TokenInterface};
88
use shared::check_condition;
9-
use shared::constants::{FEE_DISTRIBUTION_SEEDS, FOLIO_SEEDS, MAX_FEE_RECIPIENTS_PORTION};
9+
use shared::constants::{
10+
FEE_DISTRIBUTION_SEEDS, FOLIO_FEE_CLAIMED_ACCOUNT_SEEDS, FOLIO_SEEDS,
11+
MAX_FEE_RECIPIENTS_PORTION,
12+
};
1013
use shared::errors::ErrorCode;
1114
use shared::utils::account_util::next_account;
12-
use shared::utils::{Decimal, Rounding};
15+
use shared::utils::{init_pda_account_rent_if_needed, Decimal, Rounding};
1316

1417
/// Crank Fee Distribution
1518
///
@@ -112,12 +115,12 @@ pub fn handler<'info>(
112115
) -> Result<()> {
113116
let folio_bump: u8;
114117
let scaled_total_amount_to_distribute: u128;
118+
let current_time = Clock::get()?.unix_timestamp as u64;
115119

116120
let token_mint_key = ctx.accounts.folio_token_mint.key();
117-
121+
let folio_key = ctx.accounts.folio.key();
118122
{
119123
let folio = &ctx.accounts.folio.load()?;
120-
121124
let fee_distribution = &ctx.accounts.fee_distribution.load()?;
122125

123126
folio_bump = folio.bump;
@@ -141,6 +144,7 @@ pub fn handler<'info>(
141144
true,
142145
ctx.accounts.token_program.key,
143146
)?;
147+
let fee_claimed = next_account(&mut remaining_accounts_iter, false, true, &crate::ID)?;
144148

145149
let related_fee_distribution =
146150
&mut fee_distribution.fee_recipients_state[index as usize];
@@ -150,6 +154,42 @@ pub fn handler<'info>(
150154
continue;
151155
}
152156

157+
let recipient_key = related_fee_distribution.recipient.key();
158+
let seeds_for_fee_claimed_account = &[
159+
FOLIO_FEE_CLAIMED_ACCOUNT_SEEDS,
160+
folio_key.as_ref(),
161+
recipient_key.as_ref(),
162+
];
163+
164+
let (fee_claimed_key_derived, fee_claimed_bump) =
165+
Pubkey::find_program_address(seeds_for_fee_claimed_account, &crate::id());
166+
167+
let seeds_with_bump = [
168+
FOLIO_FEE_CLAIMED_ACCOUNT_SEEDS,
169+
folio_key.as_ref(),
170+
recipient_key.as_ref(),
171+
&[fee_claimed_bump],
172+
];
173+
174+
check_condition!(
175+
fee_claimed_key_derived == fee_claimed.key(),
176+
InvalidFeeClaimedAccount
177+
);
178+
179+
let was_inialized = init_pda_account_rent_if_needed(
180+
fee_claimed,
181+
FolioFeeClaimed::SIZE,
182+
&ctx.accounts.user,
183+
&crate::id(),
184+
&ctx.accounts.system_program,
185+
&[&seeds_with_bump[..]],
186+
)?;
187+
let mut fee_claimed: Account<'info, FolioFeeClaimed> = if was_inialized {
188+
Account::try_from_unchecked(fee_claimed)?
189+
} else {
190+
Account::try_from(fee_claimed)?
191+
};
192+
153193
// Validate proper token account for the recipient
154194
check_condition!(
155195
fee_recipient.key()
@@ -161,9 +201,6 @@ pub fn handler<'info>(
161201
InvalidFeeRecipient
162202
);
163203

164-
// Set as distributed
165-
related_fee_distribution.recipient = Pubkey::default();
166-
167204
let raw_amount_to_distribute = Decimal::from_scaled(scaled_total_amount_to_distribute)
168205
.mul(&Decimal::from_scaled(related_fee_distribution.portion))?
169206
.div(&Decimal::from_scaled(MAX_FEE_RECIPIENTS_PORTION))?
@@ -193,6 +230,20 @@ pub fn handler<'info>(
193230
recipient: related_fee_distribution.recipient.key(),
194231
amount: raw_amount_to_distribute,
195232
});
233+
234+
fee_claimed.bump = fee_claimed_bump;
235+
fee_claimed.folio = folio_key;
236+
fee_claimed.last_update = current_time;
237+
fee_claimed.user = related_fee_distribution.recipient.key();
238+
fee_claimed.amount = fee_claimed
239+
.amount
240+
.checked_add(raw_amount_to_distribute)
241+
.ok_or(ErrorCode::MathOverflow)?;
242+
// This save the data in account along with the Discriminator if needed.
243+
fee_claimed.exit(&crate::id())?;
244+
245+
// Set as distributed
246+
related_fee_distribution.recipient = Pubkey::default();
196247
}
197248
}
198249

programs/folio/src/instructions/crank/distribute_fees.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ use folio_admin::ID as FOLIO_ADMIN_PROGRAM_ID;
1111
use shared::check_condition;
1212
use shared::constants::{
1313
D9_U128, DAO_FEE_CONFIG_SEEDS, FEE_DISTRIBUTION_SEEDS, FEE_RECIPIENTS_SEEDS,
14-
FOLIO_FEE_CONFIG_SEEDS, FOLIO_SEEDS,
14+
FOLIO_FEE_CLAIMED_ACCOUNT_SEEDS, FOLIO_FEE_CONFIG_SEEDS, FOLIO_SEEDS,
1515
};
1616
use shared::errors::ErrorCode;
1717
use shared::utils::{Decimal, Rounding};
1818

1919
use crate::events::ProtocolFeePaid;
20-
use crate::state::{FeeDistribution, FeeRecipients, Folio};
20+
use crate::state::{FeeDistribution, FeeRecipients, Folio, FolioFeeClaimed};
2121

2222
/// Distribute Fees
2323
///
@@ -73,6 +73,14 @@ pub struct DistributeFees<'info> {
7373

7474
#[account(mut)]
7575
pub dao_fee_recipient: Box<InterfaceAccount<'info, TokenAccount>>,
76+
77+
#[account(init_if_needed,
78+
payer = user,
79+
space = FolioFeeClaimed::SIZE,
80+
seeds = [FOLIO_FEE_CLAIMED_ACCOUNT_SEEDS, folio.key().as_ref(), dao_fee_recipient.owner.to_bytes().as_ref()],
81+
bump
82+
)]
83+
pub dao_fee_claimed: Account<'info, FolioFeeClaimed>,
7684
}
7785

7886
/// Validate the instruction.
@@ -188,8 +196,12 @@ pub fn distribute_fees<'info>(
188196
fee_recipients: &AccountLoader<'info, FeeRecipients>,
189197
fee_distribution: &AccountLoader<'info, FeeDistribution>,
190198
dao_fee_recipient: &AccountInfo<'info>,
199+
dao_fee_claimed: &mut Account<'info, FolioFeeClaimed>,
200+
dao_fee_claimed_bump: u8,
191201
index: u64,
192202
) -> Result<()> {
203+
let current_time = Clock::get()?.unix_timestamp;
204+
let dao_fee_recipient_key;
193205
{
194206
let fee_recipients_data = fee_recipients.load()?;
195207

@@ -220,8 +232,8 @@ pub fn distribute_fees<'info>(
220232
InvalidDaoFeeRecipient
221233
);
222234

235+
dao_fee_recipient_key = fee_details.fee_recipient;
223236
// Update pending fees by poking to get latest fees
224-
let current_time = Clock::get()?.unix_timestamp;
225237
folio.poke(
226238
folio_token_mint.supply,
227239
current_time,
@@ -322,6 +334,14 @@ pub fn distribute_fees<'info>(
322334
recipient: dao_fee_recipient.key(),
323335
amount: raw_dao_pending_fee_shares,
324336
});
337+
dao_fee_claimed.bump = dao_fee_claimed_bump;
338+
dao_fee_claimed.folio = folio_key;
339+
dao_fee_claimed.last_update = current_time as u64;
340+
dao_fee_claimed.user = dao_fee_recipient_key;
341+
dao_fee_claimed.amount = dao_fee_claimed
342+
.amount
343+
.checked_add(raw_dao_pending_fee_shares)
344+
.ok_or(ErrorCode::MathOverflow)?;
325345
}
326346

327347
// Update folio pending fees based on what was distributed
@@ -390,6 +410,8 @@ pub fn handler<'info>(
390410
&ctx.accounts.fee_recipients,
391411
&ctx.accounts.fee_distribution,
392412
&ctx.accounts.dao_fee_recipient.to_account_info(),
413+
&mut ctx.accounts.dao_fee_claimed,
414+
ctx.bumps.dao_fee_claimed,
393415
index,
394416
)?;
395417

programs/folio/src/instructions/owner/update_folio.rs

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
use crate::events::{AuctionLengthSet, MintFeeSet};
22

33
use crate::instructions::distribute_fees;
4-
use crate::state::{Actor, FeeDistribution, FeeRecipients, Folio};
4+
use crate::state::{Actor, FeeDistribution, FeeRecipients, Folio, FolioFeeClaimed};
55
use crate::utils::structs::{FeeRecipient, Role};
66
use crate::utils::{FixedSizeString, FolioStatus, MAX_PADDED_STRING_LENGTH};
77
use crate::ID;
88
use anchor_lang::prelude::*;
99
use anchor_spl::token_interface::Mint;
10+
use folio_admin::state::DAOFeeConfig;
1011
use shared::constants::{
11-
FEE_DISTRIBUTION_SEEDS, FEE_RECIPIENTS_SEEDS, MAX_AUCTION_LENGTH, MAX_MINT_FEE, MAX_TVL_FEE,
12-
MIN_AUCTION_LENGTH,
12+
FEE_DISTRIBUTION_SEEDS, FEE_RECIPIENTS_SEEDS, FOLIO_FEE_CLAIMED_ACCOUNT_SEEDS,
13+
MAX_AUCTION_LENGTH, MAX_MINT_FEE, MAX_TVL_FEE, MIN_AUCTION_LENGTH,
1314
};
1415
use shared::errors::ErrorCode;
15-
use shared::utils::init_pda_account_rent;
16+
use shared::utils::{init_pda_account_rent, init_pda_account_rent_if_needed};
1617
use shared::{check_condition, constants::ACTOR_SEEDS};
1718
use solana_system_interface::program::ID as SYSTEM_PROGRAM_ID;
1819

@@ -24,6 +25,7 @@ enum IndexPerAccount {
2425
FolioTokenMint,
2526
FeeDistribution,
2627
DAOFeeRecipient,
28+
DAOFeeClaimed,
2729
}
2830

2931
/// Update Folio (one or multiple different fields of the folio)
@@ -131,7 +133,7 @@ impl<'info> UpdateFolio<'info> {
131133
return Err(error!(ErrorCode::MissingFeeDistributionIndex));
132134
}
133135

134-
let dao_fee_config =
136+
let dao_fee_config: Account<'info, DAOFeeConfig> =
135137
Account::try_from(&remaining_accounts[IndexPerAccount::DAOFeeConfig as usize])?;
136138

137139
let folio_token_mint: Box<InterfaceAccount<Mint>> =
@@ -182,6 +184,48 @@ impl<'info> UpdateFolio<'info> {
182184
&remaining_accounts[IndexPerAccount::FeeDistribution as usize],
183185
)?;
184186

187+
let folio_fee_config = &remaining_accounts[IndexPerAccount::FolioFeeConfig as usize];
188+
let fee_details = dao_fee_config.get_fee_details(folio_fee_config)?;
189+
let dao_fee_recipient_key = fee_details.fee_recipient;
190+
191+
let seeds_for_fee_claimed_account = &[
192+
FOLIO_FEE_CLAIMED_ACCOUNT_SEEDS,
193+
folio_key.as_ref(),
194+
dao_fee_recipient_key.as_ref(),
195+
];
196+
197+
let (dao_fee_claimed_key, dao_fee_claimed_bump) =
198+
Pubkey::find_program_address(seeds_for_fee_claimed_account, &ID);
199+
200+
let seeds_with_bump = [
201+
FOLIO_FEE_CLAIMED_ACCOUNT_SEEDS,
202+
folio_key.as_ref(),
203+
dao_fee_recipient_key.as_ref(),
204+
&[dao_fee_claimed_bump],
205+
];
206+
207+
check_condition!(
208+
dao_fee_claimed_key
209+
== remaining_accounts[IndexPerAccount::DAOFeeClaimed as usize].key(),
210+
InvalidDaoFeeClaimedAccount
211+
);
212+
213+
let was_inialized = init_pda_account_rent_if_needed(
214+
&remaining_accounts[IndexPerAccount::DAOFeeClaimed as usize],
215+
FolioFeeClaimed::SIZE,
216+
&self.folio_owner,
217+
&ID,
218+
&self.system_program,
219+
&[&seeds_with_bump[..]],
220+
)?;
221+
let mut dao_fee_claimed = if was_inialized {
222+
Account::try_from_unchecked(
223+
&remaining_accounts[IndexPerAccount::DAOFeeClaimed as usize],
224+
)?
225+
} else {
226+
Account::try_from(&remaining_accounts[IndexPerAccount::DAOFeeClaimed as usize])?
227+
};
228+
185229
distribute_fees(
186230
&remaining_accounts[IndexPerAccount::TokenProgram as usize],
187231
&self.folio_owner,
@@ -192,8 +236,11 @@ impl<'info> UpdateFolio<'info> {
192236
&self.fee_recipients,
193237
&fee_distribution,
194238
&remaining_accounts[IndexPerAccount::DAOFeeRecipient as usize],
239+
&mut dao_fee_claimed,
240+
dao_fee_claimed_bump,
195241
index_for_fee_distribution.unwrap(),
196242
)?;
243+
dao_fee_claimed.exit(&ID)?;
197244
}
198245

199246
Ok(())

0 commit comments

Comments
 (0)