Skip to content

Commit

Permalink
Combined protocol "program" fees (#244)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
jgur-psyops authored Oct 21, 2024
1 parent f9033ac commit 8a01852
Show file tree
Hide file tree
Showing 67 changed files with 1,974 additions and 458 deletions.
4 changes: 3 additions & 1 deletion clients/rust/marginfi-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ path = "src/bin/main.rs"
[features]
devnet = ["marginfi/devnet"]
mainnet-beta = ["marginfi/mainnet-beta"]
default = ["mainnet-beta", "admin", "dev", "lip"]
admin = []
dev = []
staging = ["marginfi/staging"]
default = ["mainnet-beta"]
lip = []

[dependencies]
Expand Down
17 changes: 11 additions & 6 deletions clients/rust/marginfi-cli/src/entrypoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ pub enum Command {
},
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug, Parser)]
pub enum GroupCommand {
Get {
Expand Down Expand Up @@ -133,9 +134,9 @@ pub enum GroupCommand {
#[clap(long)]
insurance_ir_fee: f64,
#[clap(long)]
protocol_fixed_fee_apr: f64,
group_fixed_fee_apr: f64,
#[clap(long)]
protocol_ir_fee: f64,
group_ir_fee: f64,
#[clap(long, arg_enum)]
risk_tier: RiskTierArg,
#[clap(long, arg_enum)]
Expand All @@ -146,6 +147,8 @@ pub enum GroupCommand {
default_value = "60"
)]
oracle_max_age: u16,
#[clap(long)]
global_fee_wallet: Pubkey,
},
HandleBankruptcy {
accounts: Vec<Pubkey>,
Expand Down Expand Up @@ -576,13 +579,14 @@ fn group(subcmd: GroupCommand, global_options: &GlobalOptions) -> Result<()> {
max_interest_rate,
insurance_fee_fixed_apr,
insurance_ir_fee,
protocol_fixed_fee_apr,
protocol_ir_fee,
group_fixed_fee_apr,
group_ir_fee,
deposit_limit_ui,
borrow_limit_ui,
risk_tier,
oracle_type,
oracle_max_age,
global_fee_wallet,
} => processor::group_add_bank(
config,
profile,
Expand All @@ -602,11 +606,12 @@ fn group(subcmd: GroupCommand, global_options: &GlobalOptions) -> Result<()> {
max_interest_rate,
insurance_fee_fixed_apr,
insurance_ir_fee,
protocol_fixed_fee_apr,
protocol_ir_fee,
group_fixed_fee_apr,
group_ir_fee,
risk_tier,
oracle_max_age,
global_options.compute_unit_price,
global_fee_wallet,
),

GroupCommand::HandleBankruptcy { accounts } => {
Expand Down
4 changes: 3 additions & 1 deletion clients/rust/marginfi-cli/src/processor/admin.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
config::Config,
utils::{process_transaction, ui_to_native},
utils::{find_fee_state_pda, process_transaction, ui_to_native},
};
use anchor_client::anchor_lang::{prelude::*, InstructionData};
use anchor_spl::associated_token;
Expand Down Expand Up @@ -32,6 +32,8 @@ pub fn process_collect_fees(config: Config, bank_pk: Pubkey) -> Result<()> {
liquidity_vault_authority,
liquidity_vault: bank.liquidity_vault,
insurance_vault: bank.insurance_vault,
fee_state: find_fee_state_pda(&marginfi::id()).0,
fee_ata: find_fee_state_pda(&marginfi::id()).0, // TODO
}
.to_account_metas(Some(true)),
data: marginfi::instruction::LendingPoolCollectBankFees {}.data(),
Expand Down
53 changes: 33 additions & 20 deletions clients/rust/marginfi-cli/src/processor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use {
utils::{
bank_to_oracle_key, calc_emissions_rate, create_oracle_key_array,
find_bank_emssions_auth_pda, find_bank_emssions_token_account_pda,
find_bank_vault_authority_pda, find_bank_vault_pda, load_observation_account_metas,
process_transaction, EXP_10_I80F48,
find_bank_vault_authority_pda, find_bank_vault_pda, find_fee_state_pda,
load_observation_account_metas, process_transaction, EXP_10_I80F48,
},
},
anchor_client::{
Expand Down Expand Up @@ -180,10 +180,10 @@ Last Update: {:?}h ago ({})
bank.config.interest_rate_config.optimal_utilization_rate,
bank.config.interest_rate_config.plateau_interest_rate,
bank.config.interest_rate_config.max_interest_rate,
bank.config.interest_rate_config.insurance_ir_fee,
bank.config.interest_rate_config.insurance_fee_fixed_apr,
bank.config.interest_rate_config.protocol_ir_fee,
bank.config.interest_rate_config.insurance_ir_fee,
bank.config.interest_rate_config.protocol_fixed_fee_apr,
bank.config.interest_rate_config.protocol_ir_fee,
bank.config.oracle_setup,
bank.config.oracle_keys,
bank.config.get_oracle_max_age(),
Expand Down Expand Up @@ -231,6 +231,7 @@ pub fn group_create(
.accounts(marginfi::accounts::MarginfiGroupInitialize {
marginfi_group: marginfi_group_keypair.pubkey(),
admin,
fee_state: find_fee_state_pda(&marginfi::id()).0,
system_program: system_program::id(),
})
.args(marginfi::instruction::MarginfiGroupInitialize {})
Expand Down Expand Up @@ -312,11 +313,12 @@ pub fn group_add_bank(
max_interest_rate: f64,
insurance_fee_fixed_apr: f64,
insurance_ir_fee: f64,
protocol_fixed_fee_apr: f64,
protocol_ir_fee: f64,
group_fixed_fee_apr: f64,
group_ir_fee: f64,
risk_tier: crate::RiskTierArg,
oracle_max_age: u16,
compute_unit_price: Option<u64>,
global_fee_wallet: Pubkey,
) -> Result<()> {
let rpc_client = config.mfi_program.rpc();

Expand All @@ -334,8 +336,9 @@ pub fn group_add_bank(
let max_interest_rate: WrappedI80F48 = I80F48::from_num(max_interest_rate).into();
let insurance_fee_fixed_apr: WrappedI80F48 = I80F48::from_num(insurance_fee_fixed_apr).into();
let insurance_ir_fee: WrappedI80F48 = I80F48::from_num(insurance_ir_fee).into();
let protocol_fixed_fee_apr: WrappedI80F48 = I80F48::from_num(protocol_fixed_fee_apr).into();
let protocol_ir_fee: WrappedI80F48 = I80F48::from_num(protocol_ir_fee).into();
let group_fixed_fee_apr: WrappedI80F48 = I80F48::from_num(group_fixed_fee_apr).into();
let group_ir_fee: WrappedI80F48 = I80F48::from_num(group_ir_fee).into();

let mint_account = rpc_client.get_account(&bank_mint)?;
let token_program = mint_account.owner;
let mint = spl_token_2022::state::Mint::unpack(
Expand All @@ -350,8 +353,8 @@ pub fn group_add_bank(
max_interest_rate,
insurance_fee_fixed_apr,
insurance_ir_fee,
protocol_fixed_fee_apr,
protocol_ir_fee,
protocol_fixed_fee_apr: group_fixed_fee_apr,
protocol_ir_fee: group_ir_fee,
..InterestRateConfig::default()
};

Expand Down Expand Up @@ -384,6 +387,7 @@ pub fn group_add_bank(
oracle_setup,
risk_tier,
oracle_max_age,
global_fee_wallet,
)?
} else {
create_bank_ix(
Expand All @@ -404,6 +408,7 @@ pub fn group_add_bank(
oracle_setup,
risk_tier,
oracle_max_age,
global_fee_wallet,
)?
};

Expand Down Expand Up @@ -445,6 +450,7 @@ fn create_bank_ix_with_seed(
oracle_setup: crate::OracleTypeArg,
risk_tier: crate::RiskTierArg,
oracle_max_age: u16,
global_fee_wallet: Pubkey,
) -> Result<Vec<Instruction>> {
use solana_sdk::commitment_config::CommitmentConfig;

Expand Down Expand Up @@ -514,6 +520,8 @@ fn create_bank_ix_with_seed(
token_program,
system_program: system_program::id(),
fee_payer: config.authority(),
fee_state: find_fee_state_pda(&config.program_id).0,
global_fee_wallet,
})
.accounts(AccountMeta::new_readonly(oracle_key, false))
.args(marginfi::instruction::LendingPoolAddBankWithSeed {
Expand Down Expand Up @@ -562,6 +570,7 @@ fn create_bank_ix(
oracle_setup: crate::OracleTypeArg,
risk_tier: crate::RiskTierArg,
oracle_max_age: u16,
global_fee_wallet: Pubkey,
) -> Result<Vec<Instruction>> {
let add_bank_ixs_builder = config.mfi_program.request();
let add_bank_ixs = add_bank_ixs_builder
Expand Down Expand Up @@ -610,6 +619,8 @@ fn create_bank_ix(
token_program,
system_program: system_program::id(),
fee_payer: config.explicit_fee_payer(),
fee_state: find_fee_state_pda(&config.program_id).0,
global_fee_wallet,
})
.accounts(AccountMeta::new_readonly(oracle_key, false))
.args(marginfi::instruction::LendingPoolAddBank {
Expand Down Expand Up @@ -988,7 +999,11 @@ pub fn bank_get(config: Config, bank_pk: Option<Pubkey>) -> Result<()> {
let rpc_client = config.mfi_program.rpc();

if let Some(address) = bank_pk {
let bank: Bank = config.mfi_program.account(address)?;
let mut bank: Bank = config.mfi_program.account(address)?;
let group: MarginfiGroup = config.mfi_program.account(bank.group)?;

bank.accrue_interest(Clock::get()?.unix_timestamp, &group)?;

print_bank(&address, &bank);

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

let mut clock = config.mfi_program.rpc().get_account(&sysvar::clock::ID)?;
let clock = Clock::from_account_info(&(&sysvar::clock::ID, &mut clock).into_account_info())?;

let mut banks_with_addresses = config.mfi_program.accounts::<Bank>(filters)?;

banks_with_addresses.iter_mut().for_each(|(_, bank)| {
bank.accrue_interest(clock.unix_timestamp).unwrap();
});
let banks_with_addresses = config.mfi_program.accounts::<Bank>(filters)?;

Ok(banks_with_addresses)
}
Expand Down Expand Up @@ -2323,6 +2331,8 @@ pub fn marginfi_account_create(profile: &Profile, config: &Config) -> Result<()>
#[cfg(feature = "lip")]
pub fn process_list_lip_campaigns(config: &Config) {
use liquidity_incentive_program::state::Campaign;

let campaings = config.lip_program.accounts::<Campaign>(vec![]).unwrap();

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

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

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

#[cfg(feature = "lip")]
fn timestamp_to_string(timestamp: i64) -> String {
use chrono::{DateTime, Utc};

DateTime::<Utc>::from_naive_utc_and_offset(
NaiveDateTime::from_timestamp_opt(timestamp, 0).unwrap(),
DateTime::from_timestamp(timestamp, 0).unwrap().naive_utc(),
Utc,
)
.format("%Y-%m-%d %H:%M:%S")
Expand Down
6 changes: 5 additions & 1 deletion clients/rust/marginfi-cli/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use {
marginfi::{
bank_authority_seed, bank_seed,
constants::{
EMISSIONS_AUTH_SEED, EMISSIONS_TOKEN_ACCOUNT_SEED, MAX_ORACLE_KEYS,
EMISSIONS_AUTH_SEED, EMISSIONS_TOKEN_ACCOUNT_SEED, FEE_STATE_SEED, MAX_ORACLE_KEYS,
PYTH_PUSH_PYTH_SPONSORED_SHARD_ID,
},
state::{
Expand Down Expand Up @@ -126,6 +126,10 @@ pub fn find_bank_emssions_token_account_pda(
)
}

pub fn find_fee_state_pda(program_id: &Pubkey) -> (Pubkey, u8) {
Pubkey::find_program_address(&[FEE_STATE_SEED.as_bytes()], program_id)
}

pub fn create_oracle_key_array(oracle_key: Pubkey) -> [Pubkey; MAX_ORACLE_KEYS] {
let mut oracle_keys = [Pubkey::default(); MAX_ORACLE_KEYS];
oracle_keys[0] = oracle_key;
Expand Down
16 changes: 8 additions & 8 deletions observability/etl/dataflow-etls/dataflow_etls/orm/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ class LendingPoolBankUpdateRecord(AccountUpdateRecordBase):
"config_interest_rate_config_max_interest_rate:BIGNUMERIC",
"config_interest_rate_config_insurance_fee_fixed_apr:BIGNUMERIC",
"config_interest_rate_config_insurance_ir_fee:BIGNUMERIC",
"config_interest_rate_config_protocol_fixed_fee_apr:BIGNUMERIC",
"config_interest_rate_config_protocol_ir_fee:BIGNUMERIC",
"config_interest_rate_config_group_fixed_fee_apr:BIGNUMERIC",
"config_interest_rate_config_group_ir_fee:BIGNUMERIC",
"config_operational_state:STRING",
"config_oracle_setup:STRING",
"config_oracle_keys:STRING",
Expand Down Expand Up @@ -169,8 +169,8 @@ class LendingPoolBankUpdateRecord(AccountUpdateRecordBase):
config_interest_rate_config_max_interest_rate: float
config_interest_rate_config_insurance_fee_fixed_apr: float
config_interest_rate_config_insurance_ir_fee: float
config_interest_rate_config_protocol_fixed_fee_apr: float
config_interest_rate_config_protocol_ir_fee: float
config_interest_rate_config_group_fixed_fee_apr: float
config_interest_rate_config_group_ir_fee: float
config_operational_state: str
config_oracle_setup: str
config_oracle_keys: str
Expand Down Expand Up @@ -222,10 +222,10 @@ def __init__(self, parsed_data: NamedAccountData, account_update: "AccountUpdate
parsed_data.data.config.interest_rate_config.insurance_fee_fixed_apr)
self.config_interest_rate_config_insurance_ir_fee = wrapped_i80f48_to_float(
parsed_data.data.config.interest_rate_config.insurance_ir_fee)
self.config_interest_rate_config_protocol_fixed_fee_apr = wrapped_i80f48_to_float(
parsed_data.data.config.interest_rate_config.protocol_fixed_fee_apr)
self.config_interest_rate_config_protocol_ir_fee = wrapped_i80f48_to_float(
parsed_data.data.config.interest_rate_config.protocol_ir_fee)
self.config_interest_rate_config_group_fixed_fee_apr = wrapped_i80f48_to_float(
parsed_data.data.config.interest_rate_config.group_fixed_fee_apr)
self.config_interest_rate_config_group_ir_fee = wrapped_i80f48_to_float(
parsed_data.data.config.interest_rate_config.group_ir_fee)


AccountUpdateRecordTypes = [MarginfiGroupUpdateRecord,
Expand Down
16 changes: 8 additions & 8 deletions observability/etl/dataflow-etls/dataflow_etls/orm/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ class LendingPoolBankConfigureRecord(GroupRecordBase):
"max_interest_rate:NUMERIC",
"insurance_fee_fixed_apr:NUMERIC",
"insurance_ir_fee:NUMERIC",
"protocol_fixed_fee_apr:NUMERIC",
"protocol_ir_fee:NUMERIC",
"group_fixed_fee_apr:NUMERIC",
"group_ir_fee:NUMERIC",
]
)

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

insurance_fee_fixed_apr: Optional[float]
insurance_ir_fee: Optional[float]
protocol_fixed_fee_apr: Optional[float]
protocol_ir_fee: Optional[float]
group_fixed_fee_apr: Optional[float]
group_ir_fee: Optional[float]

def __init__(self, event: Event, instruction: "InstructionWithLogs", instruction_args: NamedInstruction):
super().__init__(event, instruction, instruction_args)
Expand Down Expand Up @@ -238,10 +238,10 @@ def __init__(self, event: Event, instruction: "InstructionWithLogs", instruction
event.data.config.interest_rate_config.insurance_fee_fixed_apr, wrapped_i80f48_to_float)
self.insurance_ir_fee = map_optional(
event.data.config.interest_rate_config.insurance_ir_fee, wrapped_i80f48_to_float)
self.protocol_fixed_fee_apr = map_optional(
event.data.config.interest_rate_config.protocol_fixed_fee_apr, wrapped_i80f48_to_float)
self.protocol_ir_fee = map_optional(
event.data.config.interest_rate_config.protocol_ir_fee, wrapped_i80f48_to_float)
self.group_fixed_fee_apr = map_optional(
event.data.config.interest_rate_config.group_fixed_fee_apr, wrapped_i80f48_to_float)
self.group_ir_fee = map_optional(
event.data.config.interest_rate_config.group_ir_fee, wrapped_i80f48_to_float)


@dataclass
Expand Down
Loading

0 comments on commit 8a01852

Please sign in to comment.