Skip to content

Commit

Permalink
Add testing for fee state init and charging of fee state on pool crea…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
jgur-psyops committed Aug 28, 2024
1 parent 18b2b3a commit b29cbb6
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 77 deletions.
3 changes: 3 additions & 0 deletions programs/marginfi/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ cfg_if::cfg_if! {
pub const LIQUIDATION_LIQUIDATOR_FEE: I80F48 = I80F48!(0.025);
pub const LIQUIDATION_INSURANCE_FEE: I80F48 = I80F48!(0.025);

/// The default fee, in native SOL in native decimals (i.e. lamports) used in testing
pub const INIT_BANK_ORIGINATION_FEE_DEFAULT: u32 = 10000;

pub const SECONDS_PER_YEAR: I80F48 = I80F48!(31_536_000);

pub const MAX_PYTH_ORACLE_AGE: u64 = 60;
Expand Down
62 changes: 61 additions & 1 deletion programs/marginfi/tests/admin_actions/setup_bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use fixed::types::I80F48;
use fixed_macro::types::I80F48;
use fixtures::{assert_custom_error, prelude::*};
use marginfi::{
constants::PERMISSIONLESS_BAD_DEBT_SETTLEMENT_FLAG,
constants::{INIT_BANK_ORIGINATION_FEE_DEFAULT, PERMISSIONLESS_BAD_DEBT_SETTLEMENT_FLAG},
prelude::MarginfiError,
state::marginfi_group::{Bank, BankConfig, BankConfigOpt, BankVaultType},
};
Expand All @@ -16,6 +16,8 @@ async fn add_bank_success() -> anyhow::Result<()> {
// Setup test executor with non-admin payer
let test_f = TestFixture::new(None).await;

let fee_wallet = test_f.marginfi_group.fee_wallet;

let mints = vec![
(
MintFixture::new(test_f.context.clone(), None, None).await,
Expand All @@ -38,6 +40,19 @@ async fn add_bank_success() -> anyhow::Result<()> {
];

for (mint_f, bank_config) in mints {
// Load the fee state before the start of the test
let fee_balance_before: u64;
{
let mut ctx = test_f.context.borrow_mut();
fee_balance_before = ctx
.banks_client
.get_account(fee_wallet)
.await
.unwrap()
.unwrap()
.lamports;
}

let res = test_f
.marginfi_group
.try_lending_pool_add_bank(&mint_f, bank_config)
Expand Down Expand Up @@ -106,6 +121,22 @@ async fn add_bank_success() -> anyhow::Result<()> {
// this is the only loosely checked field
assert!(last_update >= 0 && last_update <= 5);
};

// Load the fee state after the test
let fee_balance_after: u64;
{
let mut ctx = test_f.context.borrow_mut();
fee_balance_after = ctx
.banks_client
.get_account(fee_wallet)
.await
.unwrap()
.unwrap()
.lamports;
}
let expected_fee_delta = INIT_BANK_ORIGINATION_FEE_DEFAULT as u64;
let actual_fee_delta = fee_balance_after - fee_balance_before;
assert_eq!(expected_fee_delta, actual_fee_delta);
}

Ok(())
Expand All @@ -116,6 +147,8 @@ async fn add_bank_with_seed_success() -> anyhow::Result<()> {
// Setup test executor with non-admin payer
let test_f = TestFixture::new(None).await;

let fee_wallet = test_f.marginfi_group.fee_wallet;

let mints = vec![
(
MintFixture::new(test_f.context.clone(), None, None).await,
Expand All @@ -138,6 +171,18 @@ async fn add_bank_with_seed_success() -> anyhow::Result<()> {
];

for (mint_f, bank_config) in mints {
let fee_balance_before: u64;
{
let mut ctx = test_f.context.borrow_mut();
fee_balance_before = ctx
.banks_client
.get_account(fee_wallet)
.await
.unwrap()
.unwrap()
.lamports;
}

let bank_seed = 1200_u64;

let res = test_f
Expand Down Expand Up @@ -209,6 +254,21 @@ async fn add_bank_with_seed_success() -> anyhow::Result<()> {
// this is the only loosely checked field
assert!(last_update >= 0 && last_update <= 5);
};

let fee_balance_after: u64;
{
let mut ctx = test_f.context.borrow_mut();
fee_balance_after = ctx
.banks_client
.get_account(fee_wallet)
.await
.unwrap()
.unwrap()
.lamports;
}
let expected_fee_delta = INIT_BANK_ORIGINATION_FEE_DEFAULT as u64;
let actual_fee_delta = fee_balance_after - fee_balance_before;
assert_eq!(expected_fee_delta, actual_fee_delta);
}

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion scripts/single-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ cd $ROOT

SBF_OUT_DIR="$ROOT/target/deploy"
RUST_LOG="solana_runtime::message_processor::stable_log=debug"
CARGO_CMD="SBF_OUT_DIR=$SBF_OUT_DIR RUST_LOG=$RUST_LOG cargo nextest run --package $program_name --features=test,test-bpf --test-threads=1 -- $test_name"
CARGO_CMD="SBF_OUT_DIR=$SBF_OUT_DIR RUST_LOG=$RUST_LOG cargo nextest run --package $program_name --features=test,test-bpf --nocapture -- $test_name"

echo "Running: $CARGO_CMD"

Expand Down
148 changes: 74 additions & 74 deletions test-utils/src/marginfi_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,86 +4,32 @@ use crate::utils::*;
use anchor_lang::{prelude::*, solana_program::system_program, InstructionData};

use anyhow::Result;
use marginfi::constants::FEE_STATE_SEED;
use marginfi::constants::{FEE_STATE_SEED, INIT_BANK_ORIGINATION_FEE_DEFAULT};
use marginfi::state::fee_state::FeeState;
use marginfi::{
prelude::MarginfiGroup,
state::marginfi_group::{BankConfig, BankConfigOpt, BankVaultType, GroupConfig},
};
use solana_program::sysvar;
use solana_program_test::*;
use solana_sdk::system_transaction;
use solana_sdk::{
compute_budget::ComputeBudgetInstruction, instruction::Instruction, signature::Keypair,
signer::Signer, transaction::Transaction,
};
use std::{cell::RefCell, mem, rc::Rc};

pub struct FeeStateFixture {
pub fee_state: Pubkey,
pub fee_wallet: Pubkey,
}

impl FeeStateFixture {
pub async fn new(ctx: Rc<RefCell<ProgramTestContext>>) -> FeeStateFixture {
let (fee_state_key, _bump) =
Pubkey::find_program_address(&[FEE_STATE_SEED.as_bytes()], &marginfi::id());

{
let mut ctx = ctx.borrow_mut();

// Skip setup if the fee state was already initialized
let fee_state_account = ctx.banks_client.get_account(fee_state_key).await.unwrap();

if let Some(account) = fee_state_account {
if !account.data.is_empty() {
let fee_state_data: FeeState =
FeeState::try_deserialize(&mut &account.data[..]).unwrap();
return FeeStateFixture {
fee_state: fee_state_key,
fee_wallet: fee_state_data.global_fee_wallet,
};
}
}

let fee_wallet = Keypair::new();

let init_fee_state_ix = Instruction {
program_id: marginfi::id(),
accounts: marginfi::accounts::InitFeeState {
payer: ctx.payer.pubkey(),
fee_state: fee_state_key,
rent: sysvar::rent::id(),
system_program: system_program::id(),
}
.to_account_metas(Some(true)),
data: marginfi::instruction::InitGlobalFeeState {
admin: ctx.payer.pubkey(),
fee_wallet: fee_wallet.pubkey(),
bank_init_flat_sol_fee: 10000,
}
.data(),
};

let tx = Transaction::new_signed_with_payer(
&[init_fee_state_ix],
Some(&ctx.payer.pubkey().clone()),
&[&ctx.payer],
ctx.last_blockhash,
);
ctx.banks_client.process_transaction(tx).await.unwrap();

FeeStateFixture {
fee_state: fee_state_key,
fee_wallet: fee_wallet.pubkey(),
}
}
}
async fn airdrop_sol(context: &mut ProgramTestContext, key: &Pubkey, amount: u64) {
let recent_blockhash = context.banks_client.get_latest_blockhash().await.unwrap();
let tx = system_transaction::transfer(&context.payer, &key, amount, recent_blockhash);
context.banks_client.process_transaction(tx).await.unwrap();
}

pub struct MarginfiGroupFixture {
ctx: Rc<RefCell<ProgramTestContext>>,
pub key: Pubkey,
pub fee_state_fixture: FeeStateFixture
pub fee_state: Pubkey,
pub fee_wallet: Pubkey,
}

impl MarginfiGroupFixture {
Expand All @@ -94,6 +40,9 @@ impl MarginfiGroupFixture {
let ctx_ref = ctx.clone();

let group_key = Keypair::new();
let fee_wallet_key: Pubkey;
let (fee_state_key, _bump) =
Pubkey::find_program_address(&[FEE_STATE_SEED.as_bytes()], &marginfi::id());

{
let mut ctx = ctx.borrow_mut();
Expand All @@ -119,19 +68,70 @@ impl MarginfiGroupFixture {
data: marginfi::instruction::MarginfiGroupConfigure { config }.data(),
};

let tx = Transaction::new_signed_with_payer(
&[initialize_marginfi_group_ix, configure_marginfi_group_ix],
Some(&ctx.payer.pubkey().clone()),
&[&ctx.payer, &group_key],
ctx.last_blockhash,
);
ctx.banks_client.process_transaction(tx).await.unwrap();
// Check if the fee state account already exists
let fee_state_account = ctx.banks_client.get_account(fee_state_key).await.unwrap();

// Account exists, read it and proceed with group initialization
if let Some(account) = fee_state_account {
if !account.data.is_empty() {
// Deserialize the account data to extract the fee_wallet public key
let fee_state_data: FeeState =
FeeState::try_deserialize(&mut &account.data[..]).unwrap();
fee_wallet_key = fee_state_data.global_fee_wallet;

let tx = Transaction::new_signed_with_payer(
&[initialize_marginfi_group_ix, configure_marginfi_group_ix],
Some(&ctx.payer.pubkey().clone()),
&[&ctx.payer, &group_key],
ctx.last_blockhash,
);
ctx.banks_client.process_transaction(tx).await.unwrap();
} else {
panic!("Fee state exists but is empty")
}
} else {
// Account does not exist, proceed with group and fee state initialization
let fee_wallet = Keypair::new();
// The wallet needs some sol to be rent exempt
airdrop_sol(&mut ctx, &fee_wallet.pubkey(), 1_000_000).await;
fee_wallet_key = fee_wallet.pubkey();

let init_fee_state_ix = Instruction {
program_id: marginfi::id(),
accounts: marginfi::accounts::InitFeeState {
payer: ctx.payer.pubkey(),
fee_state: fee_state_key,
rent: sysvar::rent::id(),
system_program: system_program::id(),
}
.to_account_metas(Some(true)),
data: marginfi::instruction::InitGlobalFeeState {
admin: ctx.payer.pubkey(),
fee_wallet: fee_wallet.pubkey(),
bank_init_flat_sol_fee: INIT_BANK_ORIGINATION_FEE_DEFAULT,
}
.data(),
};

let tx = Transaction::new_signed_with_payer(
&[
initialize_marginfi_group_ix,
configure_marginfi_group_ix,
init_fee_state_ix,
],
Some(&ctx.payer.pubkey().clone()),
&[&ctx.payer, &group_key],
ctx.last_blockhash,
);
ctx.banks_client.process_transaction(tx).await.unwrap();
}
}

MarginfiGroupFixture {
ctx: ctx_ref.clone(),
key: group_key.pubkey(),
fee_state_fixture: FeeStateFixture::new(ctx).await
fee_state: fee_state_key,
fee_wallet: fee_wallet_key,
}
}

Expand All @@ -149,8 +149,8 @@ impl MarginfiGroupFixture {
marginfi_group: self.key,
admin: self.ctx.borrow().payer.pubkey(),
fee_payer: self.ctx.borrow().payer.pubkey(),
fee_state: self.fee_state_fixture.fee_state,
global_fee_wallet: self.fee_state_fixture.fee_wallet,
fee_state: self.fee_state,
global_fee_wallet: self.fee_wallet,
bank_mint,
bank: bank_key.pubkey(),
liquidity_vault_authority: bank_fixture.get_vault_authority(BankVaultType::Liquidity).0,
Expand Down Expand Up @@ -228,8 +228,8 @@ impl MarginfiGroupFixture {
marginfi_group: self.key,
admin: self.ctx.borrow().payer.pubkey(),
fee_payer: self.ctx.borrow().payer.pubkey(),
fee_state: self.fee_state_fixture.fee_state,
global_fee_wallet: self.fee_state_fixture.fee_wallet,
fee_state: self.fee_state,
global_fee_wallet: self.fee_wallet,
bank_mint,
bank: pda,
liquidity_vault_authority: bank_fixture.get_vault_authority(BankVaultType::Liquidity).0,
Expand Down
17 changes: 17 additions & 0 deletions tests/03_addBank.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
bankKeypairA,
bankKeypairUsdc,
ecosystem,
globalFeeWallet,
groupAdmin,
INIT_POOL_ORIGINATION_FEE,
marginfiGroup,
oracles,
verbose,
Expand Down Expand Up @@ -38,6 +40,10 @@ describe("Lending pool add bank (add bank to group)", () => {
let bankKey = bankKeypairUsdc.publicKey;
const now = Date.now() / 1000;

const feeAccSolBefore = await program.provider.connection.getBalance(
globalFeeWallet
);

await groupAdmin.userMarginProgram!.provider.sendAndConfirm!(
new Transaction().add(
await addBank(program, {
Expand All @@ -46,16 +52,26 @@ describe("Lending pool add bank (add bank to group)", () => {
feePayer: groupAdmin.wallet.publicKey,
bankMint: ecosystem.usdcMint.publicKey,
bank: bankKey,
// globalFeeWallet: globalFeeWallet,
config: setConfig,
})
),
[bankKeypairUsdc]
);

const feeAccSolAfter = await program.provider.connection.getBalance(
globalFeeWallet
);

if (verbose) {
console.log("*init USDC bank " + bankKey);
console.log(
" Origination fee collected: " + (feeAccSolAfter - feeAccSolBefore)
);
}

assert.equal(feeAccSolAfter - feeAccSolBefore, INIT_POOL_ORIGINATION_FEE);

let bankData = (
await program.provider.connection.getAccountInfo(bankKey)
).data.subarray(8);
Expand Down Expand Up @@ -141,6 +157,7 @@ describe("Lending pool add bank (add bank to group)", () => {
feePayer: groupAdmin.wallet.publicKey,
bankMint: ecosystem.tokenAMint.publicKey,
bank: bankKey,
// globalFeeWallet: globalFeeWallet,
config: config,
})
),
Expand Down
Loading

0 comments on commit b29cbb6

Please sign in to comment.