Skip to content

Commit 8a01852

Browse files
authored
Combined protocol "program" fees (#244)
* Adds protocol fees and pool creation fees. * Adds two new accounts to the add_pool and add_pool_with_seed ixes * The FeeState account (unique per program) now administers "program fees", which go directly to the FeeState.global_fee_wallet's canonical ATA for any given bank's mint. For example, if a third party opens a new group and a new bank, the program fee will always go to get_associated_token_address(FeeState.global_fee_wallet, bank.mint). * Groups will cache the program fees and the fee state's global fee wallet, which the global fee admin can change at any time. When fees update, they will propagate to groups using the permissionless propagate_fee ix. * The global fee state admin can enable/disable program fees for any group without the group admin's permission. * Note that fees which go to the group are still called "protocol" or "group" fees for legacy purposes. The new fees are always referred to as "program" fees.
1 parent f9033ac commit 8a01852

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+1974
-458
lines changed

clients/rust/marginfi-cli/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ path = "src/bin/main.rs"
1010
[features]
1111
devnet = ["marginfi/devnet"]
1212
mainnet-beta = ["marginfi/mainnet-beta"]
13+
default = ["mainnet-beta", "admin", "dev", "lip"]
14+
admin = []
15+
dev = []
1316
staging = ["marginfi/staging"]
14-
default = ["mainnet-beta"]
1517
lip = []
1618

1719
[dependencies]

clients/rust/marginfi-cli/src/entrypoint.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ pub enum Command {
8686
},
8787
}
8888

89+
#[allow(clippy::large_enum_variant)]
8990
#[derive(Debug, Parser)]
9091
pub enum GroupCommand {
9192
Get {
@@ -133,9 +134,9 @@ pub enum GroupCommand {
133134
#[clap(long)]
134135
insurance_ir_fee: f64,
135136
#[clap(long)]
136-
protocol_fixed_fee_apr: f64,
137+
group_fixed_fee_apr: f64,
137138
#[clap(long)]
138-
protocol_ir_fee: f64,
139+
group_ir_fee: f64,
139140
#[clap(long, arg_enum)]
140141
risk_tier: RiskTierArg,
141142
#[clap(long, arg_enum)]
@@ -146,6 +147,8 @@ pub enum GroupCommand {
146147
default_value = "60"
147148
)]
148149
oracle_max_age: u16,
150+
#[clap(long)]
151+
global_fee_wallet: Pubkey,
149152
},
150153
HandleBankruptcy {
151154
accounts: Vec<Pubkey>,
@@ -576,13 +579,14 @@ fn group(subcmd: GroupCommand, global_options: &GlobalOptions) -> Result<()> {
576579
max_interest_rate,
577580
insurance_fee_fixed_apr,
578581
insurance_ir_fee,
579-
protocol_fixed_fee_apr,
580-
protocol_ir_fee,
582+
group_fixed_fee_apr,
583+
group_ir_fee,
581584
deposit_limit_ui,
582585
borrow_limit_ui,
583586
risk_tier,
584587
oracle_type,
585588
oracle_max_age,
589+
global_fee_wallet,
586590
} => processor::group_add_bank(
587591
config,
588592
profile,
@@ -602,11 +606,12 @@ fn group(subcmd: GroupCommand, global_options: &GlobalOptions) -> Result<()> {
602606
max_interest_rate,
603607
insurance_fee_fixed_apr,
604608
insurance_ir_fee,
605-
protocol_fixed_fee_apr,
606-
protocol_ir_fee,
609+
group_fixed_fee_apr,
610+
group_ir_fee,
607611
risk_tier,
608612
oracle_max_age,
609613
global_options.compute_unit_price,
614+
global_fee_wallet,
610615
),
611616

612617
GroupCommand::HandleBankruptcy { accounts } => {

clients/rust/marginfi-cli/src/processor/admin.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
22
config::Config,
3-
utils::{process_transaction, ui_to_native},
3+
utils::{find_fee_state_pda, process_transaction, ui_to_native},
44
};
55
use anchor_client::anchor_lang::{prelude::*, InstructionData};
66
use anchor_spl::associated_token;
@@ -32,6 +32,8 @@ pub fn process_collect_fees(config: Config, bank_pk: Pubkey) -> Result<()> {
3232
liquidity_vault_authority,
3333
liquidity_vault: bank.liquidity_vault,
3434
insurance_vault: bank.insurance_vault,
35+
fee_state: find_fee_state_pda(&marginfi::id()).0,
36+
fee_ata: find_fee_state_pda(&marginfi::id()).0, // TODO
3537
}
3638
.to_account_metas(Some(true)),
3739
data: marginfi::instruction::LendingPoolCollectBankFees {}.data(),

clients/rust/marginfi-cli/src/processor/mod.rs

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use {
1010
utils::{
1111
bank_to_oracle_key, calc_emissions_rate, create_oracle_key_array,
1212
find_bank_emssions_auth_pda, find_bank_emssions_token_account_pda,
13-
find_bank_vault_authority_pda, find_bank_vault_pda, load_observation_account_metas,
14-
process_transaction, EXP_10_I80F48,
13+
find_bank_vault_authority_pda, find_bank_vault_pda, find_fee_state_pda,
14+
load_observation_account_metas, process_transaction, EXP_10_I80F48,
1515
},
1616
},
1717
anchor_client::{
@@ -180,10 +180,10 @@ Last Update: {:?}h ago ({})
180180
bank.config.interest_rate_config.optimal_utilization_rate,
181181
bank.config.interest_rate_config.plateau_interest_rate,
182182
bank.config.interest_rate_config.max_interest_rate,
183-
bank.config.interest_rate_config.insurance_ir_fee,
184183
bank.config.interest_rate_config.insurance_fee_fixed_apr,
185-
bank.config.interest_rate_config.protocol_ir_fee,
184+
bank.config.interest_rate_config.insurance_ir_fee,
186185
bank.config.interest_rate_config.protocol_fixed_fee_apr,
186+
bank.config.interest_rate_config.protocol_ir_fee,
187187
bank.config.oracle_setup,
188188
bank.config.oracle_keys,
189189
bank.config.get_oracle_max_age(),
@@ -231,6 +231,7 @@ pub fn group_create(
231231
.accounts(marginfi::accounts::MarginfiGroupInitialize {
232232
marginfi_group: marginfi_group_keypair.pubkey(),
233233
admin,
234+
fee_state: find_fee_state_pda(&marginfi::id()).0,
234235
system_program: system_program::id(),
235236
})
236237
.args(marginfi::instruction::MarginfiGroupInitialize {})
@@ -312,11 +313,12 @@ pub fn group_add_bank(
312313
max_interest_rate: f64,
313314
insurance_fee_fixed_apr: f64,
314315
insurance_ir_fee: f64,
315-
protocol_fixed_fee_apr: f64,
316-
protocol_ir_fee: f64,
316+
group_fixed_fee_apr: f64,
317+
group_ir_fee: f64,
317318
risk_tier: crate::RiskTierArg,
318319
oracle_max_age: u16,
319320
compute_unit_price: Option<u64>,
321+
global_fee_wallet: Pubkey,
320322
) -> Result<()> {
321323
let rpc_client = config.mfi_program.rpc();
322324

@@ -334,8 +336,9 @@ pub fn group_add_bank(
334336
let max_interest_rate: WrappedI80F48 = I80F48::from_num(max_interest_rate).into();
335337
let insurance_fee_fixed_apr: WrappedI80F48 = I80F48::from_num(insurance_fee_fixed_apr).into();
336338
let insurance_ir_fee: WrappedI80F48 = I80F48::from_num(insurance_ir_fee).into();
337-
let protocol_fixed_fee_apr: WrappedI80F48 = I80F48::from_num(protocol_fixed_fee_apr).into();
338-
let protocol_ir_fee: WrappedI80F48 = I80F48::from_num(protocol_ir_fee).into();
339+
let group_fixed_fee_apr: WrappedI80F48 = I80F48::from_num(group_fixed_fee_apr).into();
340+
let group_ir_fee: WrappedI80F48 = I80F48::from_num(group_ir_fee).into();
341+
339342
let mint_account = rpc_client.get_account(&bank_mint)?;
340343
let token_program = mint_account.owner;
341344
let mint = spl_token_2022::state::Mint::unpack(
@@ -350,8 +353,8 @@ pub fn group_add_bank(
350353
max_interest_rate,
351354
insurance_fee_fixed_apr,
352355
insurance_ir_fee,
353-
protocol_fixed_fee_apr,
354-
protocol_ir_fee,
356+
protocol_fixed_fee_apr: group_fixed_fee_apr,
357+
protocol_ir_fee: group_ir_fee,
355358
..InterestRateConfig::default()
356359
};
357360

@@ -384,6 +387,7 @@ pub fn group_add_bank(
384387
oracle_setup,
385388
risk_tier,
386389
oracle_max_age,
390+
global_fee_wallet,
387391
)?
388392
} else {
389393
create_bank_ix(
@@ -404,6 +408,7 @@ pub fn group_add_bank(
404408
oracle_setup,
405409
risk_tier,
406410
oracle_max_age,
411+
global_fee_wallet,
407412
)?
408413
};
409414

@@ -445,6 +450,7 @@ fn create_bank_ix_with_seed(
445450
oracle_setup: crate::OracleTypeArg,
446451
risk_tier: crate::RiskTierArg,
447452
oracle_max_age: u16,
453+
global_fee_wallet: Pubkey,
448454
) -> Result<Vec<Instruction>> {
449455
use solana_sdk::commitment_config::CommitmentConfig;
450456

@@ -514,6 +520,8 @@ fn create_bank_ix_with_seed(
514520
token_program,
515521
system_program: system_program::id(),
516522
fee_payer: config.authority(),
523+
fee_state: find_fee_state_pda(&config.program_id).0,
524+
global_fee_wallet,
517525
})
518526
.accounts(AccountMeta::new_readonly(oracle_key, false))
519527
.args(marginfi::instruction::LendingPoolAddBankWithSeed {
@@ -562,6 +570,7 @@ fn create_bank_ix(
562570
oracle_setup: crate::OracleTypeArg,
563571
risk_tier: crate::RiskTierArg,
564572
oracle_max_age: u16,
573+
global_fee_wallet: Pubkey,
565574
) -> Result<Vec<Instruction>> {
566575
let add_bank_ixs_builder = config.mfi_program.request();
567576
let add_bank_ixs = add_bank_ixs_builder
@@ -610,6 +619,8 @@ fn create_bank_ix(
610619
token_program,
611620
system_program: system_program::id(),
612621
fee_payer: config.explicit_fee_payer(),
622+
fee_state: find_fee_state_pda(&config.program_id).0,
623+
global_fee_wallet,
613624
})
614625
.accounts(AccountMeta::new_readonly(oracle_key, false))
615626
.args(marginfi::instruction::LendingPoolAddBank {
@@ -988,7 +999,11 @@ pub fn bank_get(config: Config, bank_pk: Option<Pubkey>) -> Result<()> {
988999
let rpc_client = config.mfi_program.rpc();
9891000

9901001
if let Some(address) = bank_pk {
991-
let bank: Bank = config.mfi_program.account(address)?;
1002+
let mut bank: Bank = config.mfi_program.account(address)?;
1003+
let group: MarginfiGroup = config.mfi_program.account(bank.group)?;
1004+
1005+
bank.accrue_interest(Clock::get()?.unix_timestamp, &group)?;
1006+
9921007
print_bank(&address, &bank);
9931008

9941009
let liquidity_vault_balance =
@@ -1046,14 +1061,7 @@ fn load_all_banks(config: &Config, marginfi_group: Option<Pubkey>) -> Result<Vec
10461061
None => vec![],
10471062
};
10481063

1049-
let mut clock = config.mfi_program.rpc().get_account(&sysvar::clock::ID)?;
1050-
let clock = Clock::from_account_info(&(&sysvar::clock::ID, &mut clock).into_account_info())?;
1051-
1052-
let mut banks_with_addresses = config.mfi_program.accounts::<Bank>(filters)?;
1053-
1054-
banks_with_addresses.iter_mut().for_each(|(_, bank)| {
1055-
bank.accrue_interest(clock.unix_timestamp).unwrap();
1056-
});
1064+
let banks_with_addresses = config.mfi_program.accounts::<Bank>(filters)?;
10571065

10581066
Ok(banks_with_addresses)
10591067
}
@@ -2323,6 +2331,8 @@ pub fn marginfi_account_create(profile: &Profile, config: &Config) -> Result<()>
23232331
23242332
#[cfg(feature = "lip")]
23252333
pub fn process_list_lip_campaigns(config: &Config) {
2334+
use liquidity_incentive_program::state::Campaign;
2335+
23262336
let campaings = config.lip_program.accounts::<Campaign>(vec![]).unwrap();
23272337

23282338
print!("Found {} campaigns", campaings.len());
@@ -2356,6 +2366,7 @@ Max Rewards: {}
23562366

23572367
#[cfg(feature = "lip")]
23582368
pub fn process_list_deposits(config: &Config) {
2369+
use liquidity_incentive_program::state::{Campaign, Deposit};
23592370
use solana_sdk::clock::SECONDS_PER_DAY;
23602371

23612372
let mut deposits = config.lip_program.accounts::<Deposit>(vec![]).unwrap();
@@ -2409,8 +2420,10 @@ Deposit start {}, end {} ({})
24092420

24102421
#[cfg(feature = "lip")]
24112422
fn timestamp_to_string(timestamp: i64) -> String {
2423+
use chrono::{DateTime, Utc};
2424+
24122425
DateTime::<Utc>::from_naive_utc_and_offset(
2413-
NaiveDateTime::from_timestamp_opt(timestamp, 0).unwrap(),
2426+
DateTime::from_timestamp(timestamp, 0).unwrap().naive_utc(),
24142427
Utc,
24152428
)
24162429
.format("%Y-%m-%d %H:%M:%S")

clients/rust/marginfi-cli/src/utils.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use {
77
marginfi::{
88
bank_authority_seed, bank_seed,
99
constants::{
10-
EMISSIONS_AUTH_SEED, EMISSIONS_TOKEN_ACCOUNT_SEED, MAX_ORACLE_KEYS,
10+
EMISSIONS_AUTH_SEED, EMISSIONS_TOKEN_ACCOUNT_SEED, FEE_STATE_SEED, MAX_ORACLE_KEYS,
1111
PYTH_PUSH_PYTH_SPONSORED_SHARD_ID,
1212
},
1313
state::{
@@ -126,6 +126,10 @@ pub fn find_bank_emssions_token_account_pda(
126126
)
127127
}
128128

129+
pub fn find_fee_state_pda(program_id: &Pubkey) -> (Pubkey, u8) {
130+
Pubkey::find_program_address(&[FEE_STATE_SEED.as_bytes()], program_id)
131+
}
132+
129133
pub fn create_oracle_key_array(oracle_key: Pubkey) -> [Pubkey; MAX_ORACLE_KEYS] {
130134
let mut oracle_keys = [Pubkey::default(); MAX_ORACLE_KEYS];
131135
oracle_keys[0] = oracle_key;

observability/etl/dataflow-etls/dataflow_etls/orm/accounts.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ class LendingPoolBankUpdateRecord(AccountUpdateRecordBase):
130130
"config_interest_rate_config_max_interest_rate:BIGNUMERIC",
131131
"config_interest_rate_config_insurance_fee_fixed_apr:BIGNUMERIC",
132132
"config_interest_rate_config_insurance_ir_fee:BIGNUMERIC",
133-
"config_interest_rate_config_protocol_fixed_fee_apr:BIGNUMERIC",
134-
"config_interest_rate_config_protocol_ir_fee:BIGNUMERIC",
133+
"config_interest_rate_config_group_fixed_fee_apr:BIGNUMERIC",
134+
"config_interest_rate_config_group_ir_fee:BIGNUMERIC",
135135
"config_operational_state:STRING",
136136
"config_oracle_setup:STRING",
137137
"config_oracle_keys:STRING",
@@ -169,8 +169,8 @@ class LendingPoolBankUpdateRecord(AccountUpdateRecordBase):
169169
config_interest_rate_config_max_interest_rate: float
170170
config_interest_rate_config_insurance_fee_fixed_apr: float
171171
config_interest_rate_config_insurance_ir_fee: float
172-
config_interest_rate_config_protocol_fixed_fee_apr: float
173-
config_interest_rate_config_protocol_ir_fee: float
172+
config_interest_rate_config_group_fixed_fee_apr: float
173+
config_interest_rate_config_group_ir_fee: float
174174
config_operational_state: str
175175
config_oracle_setup: str
176176
config_oracle_keys: str
@@ -222,10 +222,10 @@ def __init__(self, parsed_data: NamedAccountData, account_update: "AccountUpdate
222222
parsed_data.data.config.interest_rate_config.insurance_fee_fixed_apr)
223223
self.config_interest_rate_config_insurance_ir_fee = wrapped_i80f48_to_float(
224224
parsed_data.data.config.interest_rate_config.insurance_ir_fee)
225-
self.config_interest_rate_config_protocol_fixed_fee_apr = wrapped_i80f48_to_float(
226-
parsed_data.data.config.interest_rate_config.protocol_fixed_fee_apr)
227-
self.config_interest_rate_config_protocol_ir_fee = wrapped_i80f48_to_float(
228-
parsed_data.data.config.interest_rate_config.protocol_ir_fee)
225+
self.config_interest_rate_config_group_fixed_fee_apr = wrapped_i80f48_to_float(
226+
parsed_data.data.config.interest_rate_config.group_fixed_fee_apr)
227+
self.config_interest_rate_config_group_ir_fee = wrapped_i80f48_to_float(
228+
parsed_data.data.config.interest_rate_config.group_ir_fee)
229229

230230

231231
AccountUpdateRecordTypes = [MarginfiGroupUpdateRecord,

observability/etl/dataflow-etls/dataflow_etls/orm/events.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,8 @@ class LendingPoolBankConfigureRecord(GroupRecordBase):
177177
"max_interest_rate:NUMERIC",
178178
"insurance_fee_fixed_apr:NUMERIC",
179179
"insurance_ir_fee:NUMERIC",
180-
"protocol_fixed_fee_apr:NUMERIC",
181-
"protocol_ir_fee:NUMERIC",
180+
"group_fixed_fee_apr:NUMERIC",
181+
"group_ir_fee:NUMERIC",
182182
]
183183
)
184184

@@ -204,8 +204,8 @@ class LendingPoolBankConfigureRecord(GroupRecordBase):
204204

205205
insurance_fee_fixed_apr: Optional[float]
206206
insurance_ir_fee: Optional[float]
207-
protocol_fixed_fee_apr: Optional[float]
208-
protocol_ir_fee: Optional[float]
207+
group_fixed_fee_apr: Optional[float]
208+
group_ir_fee: Optional[float]
209209

210210
def __init__(self, event: Event, instruction: "InstructionWithLogs", instruction_args: NamedInstruction):
211211
super().__init__(event, instruction, instruction_args)
@@ -238,10 +238,10 @@ def __init__(self, event: Event, instruction: "InstructionWithLogs", instruction
238238
event.data.config.interest_rate_config.insurance_fee_fixed_apr, wrapped_i80f48_to_float)
239239
self.insurance_ir_fee = map_optional(
240240
event.data.config.interest_rate_config.insurance_ir_fee, wrapped_i80f48_to_float)
241-
self.protocol_fixed_fee_apr = map_optional(
242-
event.data.config.interest_rate_config.protocol_fixed_fee_apr, wrapped_i80f48_to_float)
243-
self.protocol_ir_fee = map_optional(
244-
event.data.config.interest_rate_config.protocol_ir_fee, wrapped_i80f48_to_float)
241+
self.group_fixed_fee_apr = map_optional(
242+
event.data.config.interest_rate_config.group_fixed_fee_apr, wrapped_i80f48_to_float)
243+
self.group_ir_fee = map_optional(
244+
event.data.config.interest_rate_config.group_ir_fee, wrapped_i80f48_to_float)
245245

246246

247247
@dataclass

0 commit comments

Comments
 (0)