diff --git a/token-lending/cli/src/main.rs b/token-lending/cli/src/main.rs
index c590542e59d..167bcd3d078 100644
--- a/token-lending/cli/src/main.rs
+++ b/token-lending/cli/src/main.rs
@@ -1153,7 +1153,10 @@ fn main() {
 
             let added_borrow_weight_bps = value_of(arg_matches, "added_borrow_weight_bps").unwrap();
             let reserve_type = value_of(arg_matches, "reserve_type").unwrap();
-            let attributed_borrow_limit = value_of(arg_matches, "attributed_borrow_limit").unwrap();
+            let attributed_borrow_limit_open =
+                value_of(arg_matches, "attributed_borrow_limit_open").unwrap();
+            let attributed_borrow_limit_close =
+                value_of(arg_matches, "attributed_borrow_limit_close").unwrap();
 
             let borrow_fee_wad = (borrow_fee * WAD as f64) as u64;
             let flash_loan_fee_wad = (flash_loan_fee * WAD as f64) as u64;
@@ -1207,7 +1210,8 @@ fn main() {
                     protocol_take_rate,
                     added_borrow_weight_bps,
                     reserve_type,
-                    attributed_borrow_limit,
+                    attributed_borrow_limit_open,
+                    attributed_borrow_limit_close,
                 },
                 source_liquidity_pubkey,
                 source_liquidity_owner_keypair,
diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs
index 7855b0a7e20..7b8ca676528 100644
--- a/token-lending/program/src/processor.rs
+++ b/token-lending/program/src/processor.rs
@@ -1,5 +1,6 @@
 //! Program state processor
 
+use crate::state::Bonus;
 use crate::{
     self as solend_program,
     error::LendingError,
@@ -15,6 +16,7 @@ use crate::{
 };
 use bytemuck::bytes_of;
 use pyth_sdk_solana::{self, state::ProductAccount};
+
 use solana_program::{
     account_info::{next_account_info, AccountInfo},
     entrypoint::ProgramResult,
@@ -205,6 +207,10 @@ pub fn process_instruction(
             msg!("Instruction: Resize Reserve");
             process_resize_reserve(program_id, accounts)
         }
+        LendingInstruction::SetObligationCloseabilityStatus { closeable } => {
+            msg!("Instruction: Mark Obligation As Closable");
+            process_set_obligation_closeability_status(program_id, closeable, accounts)
+        }
     }
 }
 
@@ -1081,7 +1087,10 @@ fn process_refresh_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) ->
 
     obligation.last_update.update_slot(clock.slot);
 
-    update_borrow_attribution_values(&mut obligation, &accounts[1..], false)?;
+    let (_, close_exceeded) = update_borrow_attribution_values(&mut obligation, &accounts[1..])?;
+    if close_exceeded.is_none() {
+        obligation.closeable = false;
+    }
 
     // move the ObligationLiquidity with the max borrow weight to the front
     if let Some((_, max_borrow_weight_index)) = max_borrow_weight {
@@ -1113,10 +1122,12 @@ fn process_refresh_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) ->
 fn update_borrow_attribution_values(
     obligation: &mut Obligation,
     deposit_reserve_infos: &[AccountInfo],
-    error_if_limit_exceeded: bool,
-) -> ProgramResult {
+) -> Result<(Option<Pubkey>, Option<Pubkey>), ProgramError> {
     let deposit_infos = &mut deposit_reserve_infos.iter();
 
+    let mut open_exceeded = None;
+    let mut close_exceeded = None;
+
     for collateral in obligation.deposits.iter_mut() {
         let deposit_reserve_info = next_account_info(deposit_infos)?;
         let mut deposit_reserve = Reserve::unpack(&deposit_reserve_info.data.borrow())?;
@@ -1127,11 +1138,9 @@ fn update_borrow_attribution_values(
             return Err(LendingError::InvalidAccountInput.into());
         }
 
-        if obligation.updated_borrow_attribution_after_upgrade {
-            deposit_reserve.attributed_borrow_value = deposit_reserve
-                .attributed_borrow_value
-                .saturating_sub(collateral.attributed_borrow_value);
-        }
+        deposit_reserve.attributed_borrow_value = deposit_reserve
+            .attributed_borrow_value
+            .saturating_sub(collateral.attributed_borrow_value);
 
         if obligation.deposited_value > Decimal::zero() {
             collateral.attributed_borrow_value = collateral
@@ -1146,24 +1155,21 @@ fn update_borrow_attribution_values(
             .attributed_borrow_value
             .try_add(collateral.attributed_borrow_value)?;
 
-        if error_if_limit_exceeded
-            && deposit_reserve.attributed_borrow_value
-                > Decimal::from(deposit_reserve.config.attributed_borrow_limit)
+        if deposit_reserve.attributed_borrow_value
+            > Decimal::from(deposit_reserve.config.attributed_borrow_limit_open)
         {
-            msg!(
-                "Attributed borrow value is over the limit for reserve {} and mint {}",
-                deposit_reserve_info.key,
-                deposit_reserve.liquidity.mint_pubkey
-            );
-            return Err(LendingError::BorrowAttributionLimitExceeded.into());
+            open_exceeded = Some(*deposit_reserve_info.key);
+        }
+        if deposit_reserve.attributed_borrow_value
+            > Decimal::from(deposit_reserve.config.attributed_borrow_limit_close)
+        {
+            close_exceeded = Some(*deposit_reserve_info.key);
         }
 
         Reserve::pack(deposit_reserve, &mut deposit_reserve_info.data.borrow_mut())?;
     }
 
-    obligation.updated_borrow_attribution_after_upgrade = true;
-
-    Ok(())
+    Ok((open_exceeded, close_exceeded))
 }
 
 #[inline(never)] // avoid stack frame limit
@@ -1550,7 +1556,15 @@ fn _withdraw_obligation_collateral<'a>(
         .market_value
         .saturating_sub(withdraw_value);
 
-    update_borrow_attribution_values(&mut obligation, deposit_reserve_infos, true)?;
+    let (open_exceeded, _) =
+        update_borrow_attribution_values(&mut obligation, deposit_reserve_infos)?;
+    if let Some(reserve_pubkey) = open_exceeded {
+        msg!(
+            "Open borrow attribution limit exceeded for reserve {:?}",
+            reserve_pubkey
+        );
+        return Err(LendingError::BorrowAttributionLimitExceeded.into());
+    }
 
     // obligation.withdraw must be called after updating borrow attribution values, since we can
     // lose information if an entire deposit is removed, making the former calculation incorrect
@@ -1805,8 +1819,16 @@ fn process_borrow_obligation_liquidity(
     obligation_liquidity.borrow(borrow_amount)?;
     obligation.last_update.mark_stale();
 
-    update_borrow_attribution_values(&mut obligation, &accounts[9..], true)?;
-    // HACK: fast forward through the used account info's
+    let (open_exceeded, _) = update_borrow_attribution_values(&mut obligation, &accounts[9..])?;
+    if let Some(reserve_pubkey) = open_exceeded {
+        msg!(
+            "Open borrow attribution limit exceeded for reserve {:?}",
+            reserve_pubkey
+        );
+        return Err(LendingError::BorrowAttributionLimitExceeded.into());
+    }
+
+    // HACK: fast forward through the deposit reserve infos
     for _ in 0..obligation.deposits.len() {
         next_account_info(account_info_iter)?;
     }
@@ -1975,7 +1997,7 @@ fn _liquidate_obligation<'a>(
     user_transfer_authority_info: &AccountInfo<'a>,
     clock: &Clock,
     token_program_id: &AccountInfo<'a>,
-) -> Result<(u64, Decimal), ProgramError> {
+) -> Result<(u64, Bonus), ProgramError> {
     let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
     if lending_market_info.owner != program_id {
         msg!("Lending market provided is not owned by the lending program");
@@ -2061,8 +2083,9 @@ fn _liquidate_obligation<'a>(
         msg!("Obligation borrowed value is zero");
         return Err(LendingError::ObligationBorrowsZero.into());
     }
-    if obligation.borrowed_value < obligation.unhealthy_borrow_value {
-        msg!("Obligation is healthy and cannot be liquidated");
+
+    if obligation.borrowed_value < obligation.unhealthy_borrow_value && !obligation.closeable {
+        msg!("Obligation must be unhealthy or marked as closeable to be liquidated");
         return Err(LendingError::ObligationHealthy.into());
     }
 
@@ -2104,16 +2127,17 @@ fn _liquidate_obligation<'a>(
         return Err(LendingError::InvalidMarketAuthority.into());
     }
 
+    let bonus = withdraw_reserve.calculate_bonus(&obligation)?;
     let CalculateLiquidationResult {
         settle_amount,
         repay_amount,
         withdraw_amount,
-        bonus_rate,
     } = withdraw_reserve.calculate_liquidation(
         liquidity_amount,
         &obligation,
         liquidity,
         collateral,
+        &bonus,
     )?;
 
     if repay_amount == 0 {
@@ -2168,7 +2192,7 @@ fn _liquidate_obligation<'a>(
         token_program: token_program_id.clone(),
     })?;
 
-    Ok((withdraw_amount, bonus_rate))
+    Ok((withdraw_amount, bonus))
 }
 
 #[inline(never)] // avoid stack frame limit
@@ -2200,7 +2224,7 @@ fn process_liquidate_obligation_and_redeem_reserve_collateral(
     let token_program_id = next_account_info(account_info_iter)?;
     let clock = &Clock::get()?;
 
-    let (withdrawn_collateral_amount, bonus_rate) = _liquidate_obligation(
+    let (withdrawn_collateral_amount, bonus) = _liquidate_obligation(
         program_id,
         liquidity_amount,
         source_liquidity_info,
@@ -2247,7 +2271,7 @@ fn process_liquidate_obligation_and_redeem_reserve_collateral(
             return Err(LendingError::InvalidAccountInput.into());
         }
         let protocol_fee = withdraw_reserve
-            .calculate_protocol_liquidation_fee(withdraw_liquidity_amount, bonus_rate)?;
+            .calculate_protocol_liquidation_fee(withdraw_liquidity_amount, &bonus)?;
 
         spl_token_transfer(TokenTransferParams {
             source: destination_liquidity_info.clone(),
@@ -3115,6 +3139,86 @@ pub fn process_resize_reserve(_program_id: &Pubkey, accounts: &[AccountInfo]) ->
     Ok(())
 }
 
+/// process mark obligation as closable
+pub fn process_set_obligation_closeability_status(
+    program_id: &Pubkey,
+    closeable: bool,
+    accounts: &[AccountInfo],
+) -> ProgramResult {
+    let account_info_iter = &mut accounts.iter();
+    let obligation_info = next_account_info(account_info_iter)?;
+    let lending_market_info = next_account_info(account_info_iter)?;
+    let reserve_info = next_account_info(account_info_iter)?;
+    let signer_info = next_account_info(account_info_iter)?;
+    let clock = Clock::get()?;
+
+    let lending_market = LendingMarket::unpack(&lending_market_info.data.borrow())?;
+    if lending_market_info.owner != program_id {
+        msg!("Lending market provided is not owned by the lending program");
+        return Err(LendingError::InvalidAccountOwner.into());
+    }
+
+    let reserve = Reserve::unpack(&reserve_info.data.borrow())?;
+    if reserve_info.owner != program_id {
+        msg!("Reserve provided is not owned by the lending program");
+        return Err(LendingError::InvalidAccountOwner.into());
+    }
+    if &reserve.lending_market != lending_market_info.key {
+        msg!("Reserve lending market does not match the lending market provided");
+        return Err(LendingError::InvalidAccountInput.into());
+    }
+
+    if reserve.attributed_borrow_value < Decimal::from(reserve.config.attributed_borrow_limit_close)
+    {
+        msg!("Reserve attributed borrow value is below the attributed borrow limit");
+        return Err(LendingError::BorrowAttributionLimitNotExceeded.into());
+    }
+
+    let mut obligation = Obligation::unpack(&obligation_info.data.borrow())?;
+    if obligation_info.owner != program_id {
+        msg!("Obligation provided is not owned by the lending program");
+        return Err(LendingError::InvalidAccountOwner.into());
+    }
+
+    if &obligation.lending_market != lending_market_info.key {
+        msg!("Obligation lending market does not match the lending market provided");
+        return Err(LendingError::InvalidAccountInput.into());
+    }
+    if obligation.last_update.is_stale(clock.slot)? {
+        msg!("Obligation is stale and must be refreshed");
+        return Err(LendingError::ObligationStale.into());
+    }
+
+    if &lending_market.risk_authority != signer_info.key && &lending_market.owner != signer_info.key
+    {
+        msg!("Signer must be risk authority or lending market owner");
+        return Err(LendingError::InvalidAccountInput.into());
+    }
+
+    if !signer_info.is_signer {
+        msg!("Risk authority or lending market owner must be a signer");
+        return Err(LendingError::InvalidSigner.into());
+    }
+
+    if obligation.borrowed_value == Decimal::zero() {
+        msg!("Obligation borrowed value is zero");
+        return Err(LendingError::ObligationBorrowsZero.into());
+    }
+
+    obligation
+        .find_collateral_in_deposits(*reserve_info.key)
+        .map_err(|_| {
+            msg!("Obligation does not have a deposit for the reserve provided");
+            LendingError::ObligationCollateralEmpty
+        })?;
+
+    obligation.closeable = closeable;
+
+    Obligation::pack(obligation, &mut obligation_info.data.borrow_mut())?;
+
+    Ok(())
+}
+
 fn assert_uninitialized<T: Pack + IsInitialized>(
     account_info: &AccountInfo,
 ) -> Result<T, ProgramError> {
diff --git a/token-lending/program/tests/attributed_borrows.rs b/token-lending/program/tests/attributed_borrows.rs
index 2e2851d9e9a..6d62ccfb657 100644
--- a/token-lending/program/tests/attributed_borrows.rs
+++ b/token-lending/program/tests/attributed_borrows.rs
@@ -1,17 +1,10 @@
 #![cfg(feature = "test-bpf")]
 
-use solana_sdk::compute_budget::ComputeBudgetInstruction;
-use solend_sdk::instruction::refresh_obligation;
-
 use crate::solend_program_test::custom_scenario;
-use crate::solend_program_test::SolendProgramTest;
+
 use crate::solend_program_test::User;
-use solana_sdk::pubkey::Pubkey;
+
 use solend_program::math::TryDiv;
-use solend_program::processor::process_instruction;
-use solend_sdk::state::ObligationCollateral;
-use solend_sdk::state::ObligationLiquidity;
-use solend_sdk::state::PROGRAM_VERSION;
 
 use solana_sdk::instruction::InstructionError;
 use solana_sdk::transaction::TransactionError;
@@ -157,7 +150,7 @@ async fn test_refresh_obligation() {
             &lending_market_owner,
             &reserves[0],
             ReserveConfig {
-                attributed_borrow_limit: 1,
+                attributed_borrow_limit_open: 1,
                 ..reserves[0].account.config
             },
             reserves[0].account.rate_limiter.config,
@@ -295,7 +288,7 @@ async fn test_calculations() {
             &lending_market_owner,
             &reserves[0],
             ReserveConfig {
-                attributed_borrow_limit: 113,
+                attributed_borrow_limit_open: 113,
                 ..reserves[0].account.config
             },
             reserves[0].account.rate_limiter.config,
@@ -333,7 +326,7 @@ async fn test_calculations() {
             &lending_market_owner,
             &reserves[0],
             ReserveConfig {
-                attributed_borrow_limit: 120,
+                attributed_borrow_limit_open: 120,
                 ..reserves[0].account.config
             },
             reserves[0].account.rate_limiter.config,
@@ -386,7 +379,7 @@ async fn test_calculations() {
             },
             attributed_borrow_value: Decimal::from(120u64),
             config: ReserveConfig {
-                attributed_borrow_limit: 120,
+                attributed_borrow_limit_open: 120,
                 ..usdc_reserve.config
             },
             ..usdc_reserve
@@ -619,7 +612,7 @@ async fn test_withdraw() {
             &lending_market_owner,
             &reserves[0],
             ReserveConfig {
-                attributed_borrow_limit: 6,
+                attributed_borrow_limit_open: 6,
                 ..reserves[0].account.config
             },
             reserves[0].account.rate_limiter.config,
@@ -656,7 +649,7 @@ async fn test_withdraw() {
             &lending_market_owner,
             &reserves[0],
             ReserveConfig {
-                attributed_borrow_limit: 10,
+                attributed_borrow_limit_open: 10,
                 ..reserves[0].account.config
             },
             reserves[0].account.rate_limiter.config,
@@ -860,115 +853,3 @@ async fn test_liquidate() {
         Decimal::zero()
     );
 }
-
-#[tokio::test]
-async fn test_calculation_on_program_upgrade() {
-    let mut test = ProgramTest::new(
-        "solend_program",
-        solend_program::id(),
-        processor!(process_instruction),
-    );
-
-    let reserve_1 = Reserve {
-        version: PROGRAM_VERSION,
-        last_update: LastUpdate {
-            slot: 1,
-            stale: false,
-        },
-        attributed_borrow_value: Decimal::from(10u64),
-        liquidity: ReserveLiquidity {
-            market_price: Decimal::from(10u64),
-            mint_decimals: 0,
-            ..ReserveLiquidity::default()
-        },
-        ..Reserve::default()
-    };
-    let reserve_1_pubkey = Pubkey::new_unique();
-
-    test.add_packable_account(
-        reserve_1_pubkey,
-        u32::MAX as u64,
-        &reserve_1,
-        &solend_program::id(),
-    );
-
-    let reserve_2 = Reserve {
-        version: PROGRAM_VERSION,
-        last_update: LastUpdate {
-            slot: 1,
-            stale: false,
-        },
-        liquidity: ReserveLiquidity {
-            market_price: Decimal::from(10u64),
-            mint_decimals: 0,
-            ..ReserveLiquidity::default()
-        },
-        ..Reserve::default()
-    };
-    let reserve_2_pubkey = Pubkey::new_unique();
-    test.add_packable_account(
-        reserve_2_pubkey,
-        u32::MAX as u64,
-        &reserve_2,
-        &solend_program::id(),
-    );
-
-    let obligation_pubkey = Pubkey::new_unique();
-    let obligation = Obligation {
-        version: PROGRAM_VERSION,
-        deposits: vec![ObligationCollateral {
-            deposit_reserve: reserve_1_pubkey,
-            deposited_amount: 2u64,
-            market_value: Decimal::from(20u64),
-            attributed_borrow_value: Decimal::from(10u64),
-        }],
-        borrows: vec![ObligationLiquidity {
-            borrow_reserve: reserve_2_pubkey,
-            borrowed_amount_wads: Decimal::from(1u64),
-            ..ObligationLiquidity::default()
-        }],
-        updated_borrow_attribution_after_upgrade: false,
-        ..Obligation::default()
-    };
-
-    test.add_packable_account(
-        obligation_pubkey,
-        u32::MAX as u64,
-        &obligation,
-        &solend_program::id(),
-    );
-
-    let mut test = SolendProgramTest::start_with_test(test).await;
-
-    let ix = [refresh_obligation(
-        solend_program::id(),
-        obligation_pubkey,
-        vec![reserve_1_pubkey, reserve_2_pubkey],
-    )];
-
-    test.process_transaction(&ix, None).await.unwrap();
-
-    let reserve_1 = test.load_account::<Reserve>(reserve_1_pubkey).await;
-    assert_eq!(
-        reserve_1.account.attributed_borrow_value,
-        Decimal::from(20u64)
-    );
-
-    // run it again, this time make sure the borrow attribution value gets correctly subtracted
-    let ix = [
-        ComputeBudgetInstruction::set_compute_unit_price(1),
-        refresh_obligation(
-            solend_program::id(),
-            obligation_pubkey,
-            vec![reserve_1_pubkey, reserve_2_pubkey],
-        ),
-    ];
-
-    test.process_transaction(&ix, None).await.unwrap();
-
-    let reserve_1 = test.load_account::<Reserve>(reserve_1_pubkey).await;
-    assert_eq!(
-        reserve_1.account.attributed_borrow_value,
-        Decimal::from(20u64)
-    );
-}
diff --git a/token-lending/program/tests/helpers/mod.rs b/token-lending/program/tests/helpers/mod.rs
index 8657fa5bb0a..6850cfadc5a 100644
--- a/token-lending/program/tests/helpers/mod.rs
+++ b/token-lending/program/tests/helpers/mod.rs
@@ -27,6 +27,36 @@ pub const QUOTE_CURRENCY: [u8; 32] =
 pub const LAMPORTS_TO_SOL: u64 = 1_000_000_000;
 pub const FRACTIONAL_TO_USDC: u64 = 1_000_000;
 
+pub fn reserve_config_no_fees() -> ReserveConfig {
+    ReserveConfig {
+        optimal_utilization_rate: 80,
+        max_utilization_rate: 80,
+        loan_to_value_ratio: 50,
+        liquidation_bonus: 0,
+        max_liquidation_bonus: 0,
+        liquidation_threshold: 55,
+        max_liquidation_threshold: 65,
+        min_borrow_rate: 0,
+        optimal_borrow_rate: 0,
+        max_borrow_rate: 0,
+        super_max_borrow_rate: 0,
+        fees: ReserveFees {
+            borrow_fee_wad: 0,
+            flash_loan_fee_wad: 0,
+            host_fee_percentage: 0,
+        },
+        deposit_limit: u64::MAX,
+        borrow_limit: u64::MAX,
+        fee_receiver: Keypair::new().pubkey(),
+        protocol_liquidation_fee: 0,
+        protocol_take_rate: 0,
+        added_borrow_weight_bps: 0,
+        reserve_type: ReserveType::Regular,
+        attributed_borrow_limit_open: u64::MAX,
+        attributed_borrow_limit_close: u64::MAX,
+    }
+}
+
 pub fn test_reserve_config() -> ReserveConfig {
     ReserveConfig {
         optimal_utilization_rate: 80,
@@ -52,7 +82,8 @@ pub fn test_reserve_config() -> ReserveConfig {
         protocol_take_rate: 0,
         added_borrow_weight_bps: 0,
         reserve_type: ReserveType::Regular,
-        attributed_borrow_limit: u64::MAX,
+        attributed_borrow_limit_open: u64::MAX,
+        attributed_borrow_limit_close: u64::MAX,
     }
 }
 
diff --git a/token-lending/program/tests/helpers/solend_program_test.rs b/token-lending/program/tests/helpers/solend_program_test.rs
index bb8c2c94485..176ee44bb3d 100644
--- a/token-lending/program/tests/helpers/solend_program_test.rs
+++ b/token-lending/program/tests/helpers/solend_program_test.rs
@@ -1,4 +1,5 @@
 use bytemuck::checked::from_bytes;
+
 use solend_sdk::instruction::*;
 use solend_sdk::state::*;
 
@@ -679,6 +680,32 @@ pub struct SwitchboardPriceArgs {
 }
 
 impl Info<LendingMarket> {
+    pub async fn set_obligation_closeability_status(
+        &self,
+        test: &mut SolendProgramTest,
+        obligation: &Info<Obligation>,
+        reserve: &Info<Reserve>,
+        risk_authority: &User,
+        closeable: bool,
+    ) -> Result<(), BanksClientError> {
+        let refresh_ixs = self
+            .build_refresh_instructions(test, obligation, None)
+            .await;
+        test.process_transaction(&refresh_ixs, None).await.unwrap();
+
+        let ix = vec![set_obligation_closeability_status(
+            solend_program::id(),
+            obligation.pubkey,
+            reserve.pubkey,
+            self.pubkey,
+            risk_authority.keypair.pubkey(),
+            closeable,
+        )];
+
+        test.process_transaction(&ix, Some(&[&risk_authority.keypair]))
+            .await
+    }
+
     pub async fn deposit(
         &self,
         test: &mut SolendProgramTest,
diff --git a/token-lending/program/tests/init_obligation.rs b/token-lending/program/tests/init_obligation.rs
index bc802bc3954..943f5768d6a 100644
--- a/token-lending/program/tests/init_obligation.rs
+++ b/token-lending/program/tests/init_obligation.rs
@@ -51,7 +51,7 @@ async fn test_success() {
             unhealthy_borrow_value: Decimal::zero(),
             super_unhealthy_borrow_value: Decimal::zero(),
             borrowing_isolated_asset: false,
-            updated_borrow_attribution_after_upgrade: false
+            closeable: false,
         }
     );
 }
diff --git a/token-lending/program/tests/liquidate_obligation_and_redeem_collateral.rs b/token-lending/program/tests/liquidate_obligation_and_redeem_collateral.rs
index 19f31a5fd6b..009fe817e8c 100644
--- a/token-lending/program/tests/liquidate_obligation_and_redeem_collateral.rs
+++ b/token-lending/program/tests/liquidate_obligation_and_redeem_collateral.rs
@@ -15,6 +15,7 @@ use solend_program::state::ObligationCollateral;
 use solend_program::state::ObligationLiquidity;
 use solend_program::state::ReserveConfig;
 use solend_program::state::ReserveFees;
+use solend_sdk::state::Bonus;
 use solend_sdk::NULL_PUBKEY;
 mod helpers;
 
@@ -458,7 +459,12 @@ async fn test_success_insufficient_liquidity() {
         .account
         .calculate_protocol_liquidation_fee(
             available_amount * FRACTIONAL_TO_USDC,
-            Decimal::from_percent(105),
+            &Bonus {
+                total_bonus: Decimal::from_percent(bonus as u8),
+                protocol_liquidation_fee: Decimal::from_deca_bps(
+                    usdc_reserve.account.config.protocol_liquidation_fee,
+                ),
+            },
         )
         .unwrap();
 
@@ -660,3 +666,157 @@ async fn test_liquidity_ordering() {
         .await
         .unwrap();
 }
+
+#[tokio::test]
+async fn test_liquidate_closeable_obligation() {
+    let (mut test, lending_market, reserves, obligations, _users, lending_market_owner) =
+        custom_scenario(
+            &[
+                ReserveArgs {
+                    mint: usdc_mint::id(),
+                    config: ReserveConfig {
+                        liquidation_bonus: 5,
+                        max_liquidation_bonus: 10,
+                        protocol_liquidation_fee: 1,
+                        ..reserve_config_no_fees()
+                    },
+                    liquidity_amount: 100_000 * FRACTIONAL_TO_USDC,
+                    price: PriceArgs {
+                        price: 10,
+                        conf: 0,
+                        expo: -1,
+                        ema_price: 10,
+                        ema_conf: 1,
+                    },
+                },
+                ReserveArgs {
+                    mint: wsol_mint::id(),
+                    config: reserve_config_no_fees(),
+                    liquidity_amount: LAMPORTS_PER_SOL,
+                    price: PriceArgs {
+                        price: 10,
+                        conf: 0,
+                        expo: 0,
+                        ema_price: 10,
+                        ema_conf: 0,
+                    },
+                },
+            ],
+            &[ObligationArgs {
+                deposits: vec![(usdc_mint::id(), 20 * FRACTIONAL_TO_USDC)],
+                borrows: vec![(wsol_mint::id(), LAMPORTS_PER_SOL)],
+            }],
+        )
+        .await;
+
+    let usdc_reserve = reserves
+        .iter()
+        .find(|r| r.account.liquidity.mint_pubkey == usdc_mint::id())
+        .unwrap();
+    let wsol_reserve = reserves
+        .iter()
+        .find(|r| r.account.liquidity.mint_pubkey == wsol_mint::id())
+        .unwrap();
+
+    let liquidator = User::new_with_balances(
+        &mut test,
+        &[
+            (&wsol_mint::id(), 100 * LAMPORTS_TO_SOL),
+            (&usdc_reserve.account.collateral.mint_pubkey, 0),
+            (&usdc_mint::id(), 0),
+        ],
+    )
+    .await;
+
+    let balance_checker =
+        BalanceChecker::start(&mut test, &[usdc_reserve, &liquidator, wsol_reserve]).await;
+
+    lending_market
+        .update_reserve_config(
+            &mut test,
+            &lending_market_owner,
+            usdc_reserve,
+            ReserveConfig {
+                attributed_borrow_limit_open: 1,
+                attributed_borrow_limit_close: 1,
+                ..usdc_reserve.account.config
+            },
+            usdc_reserve.account.rate_limiter.config,
+            None,
+        )
+        .await
+        .unwrap();
+
+    lending_market
+        .set_obligation_closeability_status(
+            &mut test,
+            &obligations[0],
+            usdc_reserve,
+            &lending_market_owner,
+            true,
+        )
+        .await
+        .unwrap();
+
+    test.advance_clock_by_slots(1).await;
+
+    lending_market
+        .liquidate_obligation_and_redeem_reserve_collateral(
+            &mut test,
+            wsol_reserve,
+            usdc_reserve,
+            &obligations[0],
+            &liquidator,
+            u64::MAX,
+        )
+        .await
+        .unwrap();
+
+    let (balance_changes, mint_supply_changes) =
+        balance_checker.find_balance_changes(&mut test).await;
+
+    let expected_balance_changes = HashSet::from([
+        // liquidator
+        TokenBalanceChange {
+            token_account: liquidator.get_account(&usdc_mint::id()).unwrap(),
+            mint: usdc_mint::id(),
+            diff: (2 * FRACTIONAL_TO_USDC - 1) as i128,
+        },
+        TokenBalanceChange {
+            token_account: liquidator.get_account(&wsol_mint::id()).unwrap(),
+            mint: wsol_mint::id(),
+            diff: -((LAMPORTS_PER_SOL / 5) as i128),
+        },
+        // usdc reserve
+        TokenBalanceChange {
+            token_account: usdc_reserve.account.collateral.supply_pubkey,
+            mint: usdc_reserve.account.collateral.mint_pubkey,
+            diff: -((2 * FRACTIONAL_TO_USDC) as i128),
+        },
+        TokenBalanceChange {
+            token_account: usdc_reserve.account.liquidity.supply_pubkey,
+            mint: usdc_mint::id(),
+            diff: -((2 * FRACTIONAL_TO_USDC) as i128),
+        },
+        TokenBalanceChange {
+            token_account: usdc_reserve.account.config.fee_receiver,
+            mint: usdc_mint::id(),
+            diff: 1,
+        },
+        // wsol reserve
+        TokenBalanceChange {
+            token_account: wsol_reserve.account.liquidity.supply_pubkey,
+            mint: wsol_mint::id(),
+            diff: (LAMPORTS_TO_SOL / 5) as i128,
+        },
+    ]);
+    assert_eq!(balance_changes, expected_balance_changes);
+
+    assert_eq!(
+        mint_supply_changes,
+        HashSet::from([MintSupplyChange {
+            mint: usdc_reserve.account.collateral.mint_pubkey,
+            diff: -((2 * FRACTIONAL_TO_USDC) as i128)
+        }])
+    );
+}
diff --git a/token-lending/program/tests/mark_obligation_as_closeable.rs b/token-lending/program/tests/mark_obligation_as_closeable.rs
new file mode 100644
index 00000000000..16b81c45ff5
--- /dev/null
+++ b/token-lending/program/tests/mark_obligation_as_closeable.rs
@@ -0,0 +1,270 @@
+#![cfg(feature = "test-bpf")]
+
+use crate::solend_program_test::custom_scenario;
+use solana_program::{
+    instruction::{AccountMeta, Instruction},
+    pubkey::Pubkey,
+};
+
+use crate::solend_program_test::User;
+
+use solana_sdk::signer::keypair::Keypair;
+use solana_sdk::signer::Signer;
+
+use crate::solend_program_test::ObligationArgs;
+use crate::solend_program_test::PriceArgs;
+use crate::solend_program_test::ReserveArgs;
+
+use solana_program::native_token::LAMPORTS_PER_SOL;
+use solana_sdk::instruction::InstructionError;
+use solana_sdk::transaction::TransactionError;
+use solend_program::error::LendingError;
+
+use solend_program::state::ReserveConfig;
+
+use solend_sdk::{instruction::LendingInstruction, solend_mainnet, state::*};
+mod helpers;
+
+use helpers::*;
+use solana_program_test::*;
+
+#[tokio::test]
+async fn test_mark_obligation_as_closeable_success() {
+    let (mut test, lending_market, reserves, obligations, _users, lending_market_owner) =
+        custom_scenario(
+            &[
+                ReserveArgs {
+                    mint: usdc_mint::id(),
+                    config: reserve_config_no_fees(),
+                    liquidity_amount: 100_000 * FRACTIONAL_TO_USDC,
+                    price: PriceArgs {
+                        price: 10,
+                        conf: 0,
+                        expo: -1,
+                        ema_price: 10,
+                        ema_conf: 1,
+                    },
+                },
+                ReserveArgs {
+                    mint: wsol_mint::id(),
+                    config: reserve_config_no_fees(),
+                    liquidity_amount: LAMPORTS_PER_SOL,
+                    price: PriceArgs {
+                        price: 10,
+                        conf: 0,
+                        expo: 0,
+                        ema_price: 10,
+                        ema_conf: 0,
+                    },
+                },
+            ],
+            &[ObligationArgs {
+                deposits: vec![(usdc_mint::id(), 20 * FRACTIONAL_TO_USDC)],
+                borrows: vec![(wsol_mint::id(), LAMPORTS_PER_SOL)],
+            }],
+        )
+        .await;
+
+    let risk_authority = User::new_with_keypair(Keypair::new());
+    lending_market
+        .set_lending_market_owner_and_config(
+            &mut test,
+            &lending_market_owner,
+            &lending_market_owner.keypair.pubkey(),
+            lending_market.account.rate_limiter.config,
+            lending_market.account.whitelisted_liquidator,
+            risk_authority.keypair.pubkey(),
+        )
+        .await
+        .unwrap();
+
+    test.advance_clock_by_slots(1).await;
+
+    let err = lending_market
+        .set_obligation_closeability_status(
+            &mut test,
+            &obligations[0],
+            &reserves[0],
+            &risk_authority,
+            true,
+        )
+        .await
+        .unwrap_err()
+        .unwrap();
+
+    assert_eq!(
+        err,
+        TransactionError::InstructionError(
+            0,
+            InstructionError::Custom(LendingError::BorrowAttributionLimitNotExceeded as u32)
+        )
+    );
+
+    test.advance_clock_by_slots(1).await;
+
+    lending_market
+        .update_reserve_config(
+            &mut test,
+            &lending_market_owner,
+            &reserves[0],
+            ReserveConfig {
+                attributed_borrow_limit_open: 1,
+                attributed_borrow_limit_close: 1,
+                ..reserves[0].account.config
+            },
+            reserves[0].account.rate_limiter.config,
+            None,
+        )
+        .await
+        .unwrap();
+
+    lending_market
+        .set_obligation_closeability_status(
+            &mut test,
+            &obligations[0],
+            &reserves[0],
+            &risk_authority,
+            true,
+        )
+        .await
+        .unwrap();
+
+    let obligation_post = test.load_account::<Obligation>(obligations[0].pubkey).await;
+    assert_eq!(
+        obligation_post.account,
+        Obligation {
+            last_update: LastUpdate {
+                slot: 1002,
+                stale: false
+            },
+            closeable: true,
+            ..obligations[0].account.clone()
+        }
+    );
+}
+
+#[tokio::test]
+async fn invalid_signer() {
+    let (mut test, lending_market, reserves, obligations, _users, lending_market_owner) =
+        custom_scenario(
+            &[
+                ReserveArgs {
+                    mint: usdc_mint::id(),
+                    config: reserve_config_no_fees(),
+                    liquidity_amount: 100_000 * FRACTIONAL_TO_USDC,
+                    price: PriceArgs {
+                        price: 10,
+                        conf: 0,
+                        expo: -1,
+                        ema_price: 10,
+                        ema_conf: 1,
+                    },
+                },
+                ReserveArgs {
+                    mint: wsol_mint::id(),
+                    config: reserve_config_no_fees(),
+                    liquidity_amount: LAMPORTS_PER_SOL,
+                    price: PriceArgs {
+                        price: 10,
+                        conf: 0,
+                        expo: 0,
+                        ema_price: 10,
+                        ema_conf: 0,
+                    },
+                },
+            ],
+            &[ObligationArgs {
+                deposits: vec![(usdc_mint::id(), 20 * FRACTIONAL_TO_USDC)],
+                borrows: vec![(wsol_mint::id(), LAMPORTS_PER_SOL)],
+            }],
+        )
+        .await;
+
+    let risk_authority = User::new_with_keypair(Keypair::new());
+    lending_market
+        .set_lending_market_owner_and_config(
+            &mut test,
+            &lending_market_owner,
+            &lending_market_owner.keypair.pubkey(),
+            lending_market.account.rate_limiter.config,
+            lending_market.account.whitelisted_liquidator,
+            risk_authority.keypair.pubkey(),
+        )
+        .await
+        .unwrap();
+
+    lending_market
+        .update_reserve_config(
+            &mut test,
+            &lending_market_owner,
+            &reserves[0],
+            ReserveConfig {
+                attributed_borrow_limit_open: 1,
+                attributed_borrow_limit_close: 1,
+                ..reserves[0].account.config
+            },
+            reserves[0].account.rate_limiter.config,
+            None,
+        )
+        .await
+        .unwrap();
+
+    let rando = User::new_with_keypair(Keypair::new());
+    let err = lending_market
+        .set_obligation_closeability_status(&mut test, &obligations[0], &reserves[0], &rando, true)
+        .await
+        .unwrap_err()
+        .unwrap();
+
+    assert_eq!(
+        err,
+        TransactionError::InstructionError(
+            0,
+            InstructionError::Custom(LendingError::InvalidAccountInput as u32)
+        )
+    );
+
+    let err = test
+        .process_transaction(
+            &[malicious_set_obligation_closeability_status(
+                solend_mainnet::id(),
+                obligations[0].pubkey,
+                reserves[0].pubkey,
+                lending_market.pubkey,
+                risk_authority.keypair.pubkey(),
+                true,
+            )],
+            None,
+        )
+        .await
+        .unwrap_err()
+        .unwrap();
+
+    assert_eq!(
+        err,
+        TransactionError::InstructionError(
+            0,
+            InstructionError::Custom(LendingError::InvalidSigner as u32)
+        )
+    );
+}
+
+pub fn malicious_set_obligation_closeability_status(
+    program_id: Pubkey,
+    obligation_pubkey: Pubkey,
+    reserve_pubkey: Pubkey,
+    lending_market_pubkey: Pubkey,
+    risk_authority: Pubkey,
+    closeable: bool,
+) -> Instruction {
+    Instruction {
+        program_id,
+        accounts: vec![
+            AccountMeta::new(obligation_pubkey, false),
+            AccountMeta::new_readonly(lending_market_pubkey, false),
+            AccountMeta::new_readonly(reserve_pubkey, false),
+            AccountMeta::new_readonly(risk_authority, false),
+        ],
+        data: LendingInstruction::SetObligationCloseabilityStatus { closeable }.pack(),
+    }
+}
diff --git a/token-lending/program/tests/two_prices.rs b/token-lending/program/tests/two_prices.rs
index cbd224fb899..463b562fb06 100644
--- a/token-lending/program/tests/two_prices.rs
+++ b/token-lending/program/tests/two_prices.rs
@@ -478,7 +478,7 @@ async fn test_liquidation_doesnt_use_smoothed_price() {
         TokenBalanceChange {
             token_account: liquidator.get_account(&usdc_mint::id()).unwrap(),
             mint: usdc_mint::id(),
-            diff: (20 * FRACTIONAL_TO_USDC * 105 / 100) as i128 - 1,
+            diff: (20 * FRACTIONAL_TO_USDC * 105 / 100 - 1) as i128,
         },
         TokenBalanceChange {
             token_account: liquidator.get_account(&wsol_mint::id()).unwrap(),
diff --git a/token-lending/sdk/src/error.rs b/token-lending/sdk/src/error.rs
index 8666d57e238..597521cd91c 100644
--- a/token-lending/sdk/src/error.rs
+++ b/token-lending/sdk/src/error.rs
@@ -206,6 +206,9 @@ pub enum LendingError {
     /// Borrow Attribution Limit Exceeded
     #[error("Borrow Attribution Limit Exceeded")]
     BorrowAttributionLimitExceeded,
+    /// Borrow Attribution Limit Not Exceeded
+    #[error("Borrow Attribution Limit Not Exceeded")]
+    BorrowAttributionLimitNotExceeded,
 }
 
 impl From<LendingError> for ProgramError {
diff --git a/token-lending/sdk/src/instruction.rs b/token-lending/sdk/src/instruction.rs
index a2ae04f901c..0e4d37b6362 100644
--- a/token-lending/sdk/src/instruction.rs
+++ b/token-lending/sdk/src/instruction.rs
@@ -507,6 +507,19 @@ pub enum LendingInstruction {
     /// 1. `[signer]` fee payer.
     /// 2. '[]' System Program
     ResizeReserve,
+
+    // 24
+    /// MarkObligationAsClosable
+    ///
+    /// Accounts expected by this instruction
+    /// 0. `[writable]` Obligation account - refreshed.
+    /// 1. `[]` Lending market account.
+    /// 2. `[]` Reserve account - refreshed.
+    /// 3. `[signer]` risk authority of lending market or lending market owner
+    SetObligationCloseabilityStatus {
+        /// Obligation is closable
+        closeable: bool,
+    },
 }
 
 impl LendingInstruction {
@@ -571,7 +584,8 @@ impl LendingInstruction {
                 let (asset_type, rest) = Self::unpack_u8(rest)?;
                 let (max_liquidation_bonus, rest) = Self::unpack_u8(rest)?;
                 let (max_liquidation_threshold, rest) = Self::unpack_u8(rest)?;
-                let (attributed_borrow_limit, _rest) = Self::unpack_u64(rest)?;
+                let (attributed_borrow_limit_open, rest) = Self::unpack_u64(rest)?;
+                let (attributed_borrow_limit_close, _rest) = Self::unpack_u64(rest)?;
                 Self::InitReserve {
                     liquidity_amount,
                     config: ReserveConfig {
@@ -598,7 +612,8 @@ impl LendingInstruction {
                         protocol_take_rate,
                         added_borrow_weight_bps,
                         reserve_type: ReserveType::from_u8(asset_type).unwrap(),
-                        attributed_borrow_limit,
+                        attributed_borrow_limit_open,
+                        attributed_borrow_limit_close,
                     },
                 }
             }
@@ -667,7 +682,8 @@ impl LendingInstruction {
                 let (asset_type, rest) = Self::unpack_u8(rest)?;
                 let (max_liquidation_bonus, rest) = Self::unpack_u8(rest)?;
                 let (max_liquidation_threshold, rest) = Self::unpack_u8(rest)?;
-                let (attributed_borrow_limit, rest) = Self::unpack_u64(rest)?;
+                let (attributed_borrow_limit_open, rest) = Self::unpack_u64(rest)?;
+                let (attributed_borrow_limit_close, rest) = Self::unpack_u64(rest)?;
                 let (window_duration, rest) = Self::unpack_u64(rest)?;
                 let (max_outflow, _rest) = Self::unpack_u64(rest)?;
 
@@ -696,7 +712,8 @@ impl LendingInstruction {
                         protocol_take_rate,
                         added_borrow_weight_bps,
                         reserve_type: ReserveType::from_u8(asset_type).unwrap(),
-                        attributed_borrow_limit,
+                        attributed_borrow_limit_open,
+                        attributed_borrow_limit_close,
                     },
                     rate_limiter_config: RateLimiterConfig {
                         window_duration,
@@ -727,6 +744,15 @@ impl LendingInstruction {
             }
             22 => Self::UpdateMarketMetadata,
             23 => Self::ResizeReserve,
+            24 => {
+                let (closeable, _rest) = match Self::unpack_u8(rest)? {
+                    (0, rest) => (false, rest),
+                    (1, rest) => (true, rest),
+                    _ => return Err(LendingError::InstructionUnpackError.into()),
+                };
+
+                Self::SetObligationCloseabilityStatus { closeable }
+            }
             _ => {
                 msg!("Instruction cannot be unpacked {:?} {:?}", tag, rest);
                 return Err(LendingError::InstructionUnpackError.into());
@@ -847,7 +873,8 @@ impl LendingInstruction {
                         protocol_take_rate,
                         added_borrow_weight_bps: borrow_weight_bps,
                         reserve_type: asset_type,
-                        attributed_borrow_limit,
+                        attributed_borrow_limit_open,
+                        attributed_borrow_limit_close,
                     },
             } => {
                 buf.push(2);
@@ -873,7 +900,8 @@ impl LendingInstruction {
                 buf.extend_from_slice(&(asset_type as u8).to_le_bytes());
                 buf.extend_from_slice(&max_liquidation_bonus.to_le_bytes());
                 buf.extend_from_slice(&max_liquidation_threshold.to_le_bytes());
-                buf.extend_from_slice(&attributed_borrow_limit.to_le_bytes());
+                buf.extend_from_slice(&attributed_borrow_limit_open.to_le_bytes());
+                buf.extend_from_slice(&attributed_borrow_limit_close.to_le_bytes());
             }
             Self::RefreshReserve => {
                 buf.push(3);
@@ -950,7 +978,8 @@ impl LendingInstruction {
                 buf.extend_from_slice(&(config.reserve_type as u8).to_le_bytes());
                 buf.extend_from_slice(&config.max_liquidation_bonus.to_le_bytes());
                 buf.extend_from_slice(&config.max_liquidation_threshold.to_le_bytes());
-                buf.extend_from_slice(&config.attributed_borrow_limit.to_le_bytes());
+                buf.extend_from_slice(&config.attributed_borrow_limit_open.to_le_bytes());
+                buf.extend_from_slice(&config.attributed_borrow_limit_close.to_le_bytes());
                 buf.extend_from_slice(&rate_limiter_config.window_duration.to_le_bytes());
                 buf.extend_from_slice(&rate_limiter_config.max_outflow.to_le_bytes());
             }
@@ -982,6 +1011,10 @@ impl LendingInstruction {
             Self::ResizeReserve => {
                 buf.push(23);
             }
+            Self::SetObligationCloseabilityStatus { closeable } => {
+                buf.push(24);
+                buf.extend_from_slice(&(closeable as u8).to_le_bytes());
+            }
         }
         buf
     }
@@ -1746,6 +1779,27 @@ pub fn resize_reserve(program_id: Pubkey, reserve_pubkey: Pubkey, signer: Pubkey
     }
 }
 
+/// Creates a `MarkObligationAsClosable` instruction
+pub fn set_obligation_closeability_status(
+    program_id: Pubkey,
+    obligation_pubkey: Pubkey,
+    reserve_pubkey: Pubkey,
+    lending_market_pubkey: Pubkey,
+    risk_authority: Pubkey,
+    closeable: bool,
+) -> Instruction {
+    Instruction {
+        program_id,
+        accounts: vec![
+            AccountMeta::new(obligation_pubkey, false),
+            AccountMeta::new_readonly(lending_market_pubkey, false),
+            AccountMeta::new_readonly(reserve_pubkey, false),
+            AccountMeta::new_readonly(risk_authority, true),
+        ],
+        data: LendingInstruction::SetObligationCloseabilityStatus { closeable }.pack(),
+    }
+}
+
 #[cfg(test)]
 mod test {
     use super::*;
@@ -1815,7 +1869,8 @@ mod test {
                         protocol_take_rate: rng.gen::<u8>(),
                         added_borrow_weight_bps: rng.gen::<u64>(),
                         reserve_type: ReserveType::from_u8(rng.gen::<u8>() % 2).unwrap(),
-                        attributed_borrow_limit: rng.gen(),
+                        attributed_borrow_limit_open: rng.gen(),
+                        attributed_borrow_limit_close: rng.gen(),
                     },
                 };
 
@@ -1976,7 +2031,8 @@ mod test {
                         protocol_take_rate: rng.gen::<u8>(),
                         added_borrow_weight_bps: rng.gen::<u64>(),
                         reserve_type: ReserveType::from_u8(rng.gen::<u8>() % 2).unwrap(),
-                        attributed_borrow_limit: rng.gen(),
+                        attributed_borrow_limit_open: rng.gen(),
+                        attributed_borrow_limit_close: rng.gen(),
                     },
                     rate_limiter_config: RateLimiterConfig {
                         window_duration: rng.gen::<u64>(),
@@ -2043,6 +2099,26 @@ mod test {
                 let unpacked = LendingInstruction::unpack(&packed).unwrap();
                 assert_eq!(instruction, unpacked);
             }
+
+            // resize reserve
+            {
+                let instruction = LendingInstruction::ResizeReserve {};
+
+                let packed = instruction.pack();
+                let unpacked = LendingInstruction::unpack(&packed).unwrap();
+                assert_eq!(instruction, unpacked);
+            }
+
+            // MarkObligationAsClosable
+            {
+                let instruction = LendingInstruction::SetObligationCloseabilityStatus {
+                    closeable: rng.gen(),
+                };
+
+                let packed = instruction.pack();
+                let unpacked = LendingInstruction::unpack(&packed).unwrap();
+                assert_eq!(instruction, unpacked);
+            }
         }
     }
 }
diff --git a/token-lending/sdk/src/state/obligation.rs b/token-lending/sdk/src/state/obligation.rs
index 6fea396778c..ab833798e19 100644
--- a/token-lending/sdk/src/state/obligation.rs
+++ b/token-lending/sdk/src/state/obligation.rs
@@ -61,8 +61,8 @@ pub struct Obligation {
     pub super_unhealthy_borrow_value: Decimal,
     /// True if the obligation is currently borrowing an isolated tier asset
     pub borrowing_isolated_asset: bool,
-    /// Updated borrow attribution after upgrade. initially false when upgrading to v2.0.3
-    pub updated_borrow_attribution_after_upgrade: bool,
+    /// Obligation can be marked as closeable
+    pub closeable: bool,
 }
 
 impl Obligation {
@@ -439,7 +439,7 @@ impl Pack for Obligation {
             borrowing_isolated_asset,
             super_unhealthy_borrow_value,
             unweighted_borrowed_value,
-            updated_borrow_attribution_after_upgrade,
+            closeable,
             _padding,
             deposits_len,
             borrows_len,
@@ -483,10 +483,7 @@ impl Pack for Obligation {
             super_unhealthy_borrow_value,
         );
         pack_decimal(self.unweighted_borrowed_value, unweighted_borrowed_value);
-        pack_bool(
-            self.updated_borrow_attribution_after_upgrade,
-            updated_borrow_attribution_after_upgrade,
-        );
+        pack_bool(self.closeable, closeable);
 
         *deposits_len = u8::try_from(self.deposits.len()).unwrap().to_le_bytes();
         *borrows_len = u8::try_from(self.borrows.len()).unwrap().to_le_bytes();
@@ -551,7 +548,7 @@ impl Pack for Obligation {
             borrowing_isolated_asset,
             super_unhealthy_borrow_value,
             unweighted_borrowed_value,
-            updated_borrow_attribution_after_upgrade,
+            closeable,
             _padding,
             deposits_len,
             borrows_len,
@@ -645,9 +642,7 @@ impl Pack for Obligation {
             unhealthy_borrow_value: unpack_decimal(unhealthy_borrow_value),
             super_unhealthy_borrow_value: unpack_decimal(super_unhealthy_borrow_value),
             borrowing_isolated_asset: unpack_bool(borrowing_isolated_asset)?,
-            updated_borrow_attribution_after_upgrade: unpack_bool(
-                updated_borrow_attribution_after_upgrade,
-            )?,
+            closeable: unpack_bool(closeable)?,
         })
     }
 }
@@ -698,7 +693,7 @@ mod test {
                 unhealthy_borrow_value: rand_decimal(),
                 super_unhealthy_borrow_value: rand_decimal(),
                 borrowing_isolated_asset: rng.gen(),
-                updated_borrow_attribution_after_upgrade: rng.gen(),
+                closeable: rng.gen(),
             };
 
             let mut packed = [0u8; OBLIGATION_LEN];
diff --git a/token-lending/sdk/src/state/reserve.rs b/token-lending/sdk/src/state/reserve.rs
index f1cc90a643a..e9743213985 100644
--- a/token-lending/sdk/src/state/reserve.rs
+++ b/token-lending/sdk/src/state/reserve.rs
@@ -338,8 +338,15 @@ impl Reserve {
 
     /// Calculate bonus as a percentage
     /// the value will be in range [0, MAX_BONUS_PCT]
-    pub fn calculate_bonus(&self, obligation: &Obligation) -> Result<Decimal, ProgramError> {
+    pub fn calculate_bonus(&self, obligation: &Obligation) -> Result<Bonus, ProgramError> {
         if obligation.borrowed_value < obligation.unhealthy_borrow_value {
+            if obligation.closeable {
+                return Ok(Bonus {
+                    total_bonus: Decimal::zero(),
+                    protocol_liquidation_fee: Decimal::zero(),
+                });
+            }
+
             msg!("Obligation is healthy so a liquidation bonus can't be calculated");
             return Err(LendingError::ObligationHealthy.into());
         }
@@ -351,10 +358,13 @@ impl Reserve {
         // could also return the average of liquidation bonus and max liquidation bonus here, but
         // i don't think it matters
         if obligation.unhealthy_borrow_value == obligation.super_unhealthy_borrow_value {
-            return Ok(min(
-                liquidation_bonus.try_add(protocol_liquidation_fee)?,
-                Decimal::from_percent(MAX_BONUS_PCT),
-            ));
+            return Ok(Bonus {
+                total_bonus: min(
+                    liquidation_bonus.try_add(protocol_liquidation_fee)?,
+                    Decimal::from_percent(MAX_BONUS_PCT),
+                ),
+                protocol_liquidation_fee,
+            });
         }
 
         // safety:
@@ -383,7 +393,10 @@ impl Reserve {
             .try_add(weight.try_mul(max_liquidation_bonus.try_sub(liquidation_bonus)?)?)?
             .try_add(protocol_liquidation_fee)?;
 
-        Ok(min(bonus, Decimal::from_percent(MAX_BONUS_PCT)))
+        Ok(Bonus {
+            total_bonus: min(bonus, Decimal::from_percent(MAX_BONUS_PCT)),
+            protocol_liquidation_fee,
+        })
     }
 
     /// Liquidate some or all of an unhealthy obligation
@@ -393,8 +406,14 @@ impl Reserve {
         obligation: &Obligation,
         liquidity: &ObligationLiquidity,
         collateral: &ObligationCollateral,
+        bonus: &Bonus,
     ) -> Result<CalculateLiquidationResult, ProgramError> {
-        let bonus_rate = self.calculate_bonus(obligation)?.try_add(Decimal::one())?;
+        if bonus.total_bonus > Decimal::from_percent(MAX_BONUS_PCT) {
+            msg!("Bonus rate cannot exceed maximum bonus rate");
+            return Err(LendingError::InvalidAmount.into());
+        }
+
+        let bonus_rate = bonus.total_bonus.try_add(Decimal::one())?;
 
         let max_amount = if amount_to_liquidate == u64::MAX {
             liquidity.borrowed_amount_wads
@@ -485,29 +504,33 @@ impl Reserve {
             settle_amount,
             repay_amount,
             withdraw_amount,
-            bonus_rate,
         })
     }
 
     /// Calculate protocol cut of liquidation bonus always at least 1 lamport
-    /// the bonus rate is always >=1 and includes both liquidator bonus and protocol fee.
+    /// the bonus rate is always <= MAX_BONUS_PCT
     /// the bonus rate has to be passed into this function because bonus calculations are dynamic
     /// and can't be recalculated after liquidation.
     pub fn calculate_protocol_liquidation_fee(
         &self,
         amount_liquidated: u64,
-        bonus_rate: Decimal,
+        bonus: &Bonus,
     ) -> Result<u64, ProgramError> {
+        if bonus.total_bonus > Decimal::from_percent(MAX_BONUS_PCT) {
+            msg!("Bonus rate cannot exceed maximum bonus rate");
+            return Err(LendingError::InvalidAmount.into());
+        }
+
         let amount_liquidated_wads = Decimal::from(amount_liquidated);
-        let nonbonus_amount = amount_liquidated_wads.try_div(bonus_rate)?;
-        // After deploying must update all reserves to set liquidation fee then redeploy with this line instead of hardcode
-        let protocol_fee = std::cmp::max(
+        let nonbonus_amount =
+            amount_liquidated_wads.try_div(Decimal::one().try_add(bonus.total_bonus)?)?;
+
+        Ok(std::cmp::max(
             nonbonus_amount
-                .try_mul(Decimal::from_deca_bps(self.config.protocol_liquidation_fee))?
+                .try_mul(bonus.protocol_liquidation_fee)?
                 .try_ceil_u64()?,
             1,
-        );
-        Ok(protocol_fee)
+        ))
     }
 
     /// Calculate protocol fee redemption accounting for availible liquidity and accumulated fees
@@ -569,9 +592,17 @@ pub struct CalculateLiquidationResult {
     pub repay_amount: u64,
     /// Amount of collateral to withdraw in exchange for repay amount
     pub withdraw_amount: u64,
-    /// Liquidator bonus as a percentage, including the protocol fee
-    /// always greater than or equal to 1.
-    pub bonus_rate: Decimal,
+}
+
+/// Bonus
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Bonus {
+    /// Total bonus (liquidator bonus + protocol liquidation fee). 0 <= x <= MAX_BONUS_PCT
+    /// eg if the total bonus is 5%, this value is 0.05
+    pub total_bonus: Decimal,
+    /// protocol liquidation fee pct. 0 <= x <= reserve.config.protocol_liquidation_fee / 10
+    /// eg if the protocol liquidation fee is 1%, this value is 0.01
+    pub protocol_liquidation_fee: Decimal,
 }
 
 /// Reserve liquidity
@@ -903,8 +934,10 @@ pub struct ReserveConfig {
     pub added_borrow_weight_bps: u64,
     /// Type of the reserve (Regular, Isolated)
     pub reserve_type: ReserveType,
-    /// Attributed Borrow limit in USD
-    pub attributed_borrow_limit: u64,
+    /// Open Attributed Borrow limit in USD
+    pub attributed_borrow_limit_open: u64,
+    /// Close Attributed Borrow limit in USD
+    pub attributed_borrow_limit_close: u64,
 }
 
 /// validates reserve configs
@@ -992,6 +1025,11 @@ pub fn validate_reserve_config(config: ReserveConfig) -> ProgramResult {
         msg!("open/close LTV must be 0 for isolated reserves");
         return Err(LendingError::InvalidConfig.into());
     }
+    if config.attributed_borrow_limit_open > config.attributed_borrow_limit_close {
+        msg!("open attributed borrow limit must be <= close attributed borrow limit");
+        return Err(LendingError::InvalidConfig.into());
+    }
+
     Ok(())
 }
 
@@ -1179,7 +1217,8 @@ impl Pack for Reserve {
             config_max_liquidation_bonus,
             config_max_liquidation_threshold,
             attributed_borrow_value,
-            config_attributed_borrow_limit,
+            config_attributed_borrow_limit_open,
+            config_attributed_borrow_limit_close,
             _padding,
         ) = mut_array_refs![
             output,
@@ -1225,7 +1264,8 @@ impl Pack for Reserve {
             1,
             16,
             8,
-            714
+            8,
+            706
         ];
 
         // reserve
@@ -1290,7 +1330,10 @@ impl Pack for Reserve {
         *config_added_borrow_weight_bps = self.config.added_borrow_weight_bps.to_le_bytes();
         *config_max_liquidation_bonus = self.config.max_liquidation_bonus.to_le_bytes();
         *config_max_liquidation_threshold = self.config.max_liquidation_threshold.to_le_bytes();
-        *config_attributed_borrow_limit = self.config.attributed_borrow_limit.to_le_bytes();
+        *config_attributed_borrow_limit_open =
+            self.config.attributed_borrow_limit_open.to_le_bytes();
+        *config_attributed_borrow_limit_close =
+            self.config.attributed_borrow_limit_close.to_le_bytes();
 
         pack_decimal(self.attributed_borrow_value, attributed_borrow_value);
     }
@@ -1341,7 +1384,8 @@ impl Pack for Reserve {
             config_max_liquidation_bonus,
             config_max_liquidation_threshold,
             attributed_borrow_value,
-            config_attributed_borrow_limit,
+            config_attributed_borrow_limit_open,
+            config_attributed_borrow_limit_close,
             _padding,
         ) = array_refs![
             input,
@@ -1387,7 +1431,8 @@ impl Pack for Reserve {
             1,
             16,
             8,
-            714
+            8,
+            706
         ];
 
         let version = u8::from_le_bytes(*version);
@@ -1478,11 +1523,19 @@ impl Pack for Reserve {
                 protocol_take_rate: u8::from_le_bytes(*config_protocol_take_rate),
                 added_borrow_weight_bps: u64::from_le_bytes(*config_added_borrow_weight_bps),
                 reserve_type: ReserveType::from_u8(config_asset_type[0]).unwrap(),
-                // this field is added in v2.0.3 and we will never set it to zero. only time it'll
+                // the following two fields are added in v2.0.3 and we will never set it to zero. only time they will
                 // be zero is when we upgrade from v2.0.2 to v2.0.3. in that case, the correct
                 // thing to do is set the value to u64::MAX.
-                attributed_borrow_limit: {
-                    let value = u64::from_le_bytes(*config_attributed_borrow_limit);
+                attributed_borrow_limit_open: {
+                    let value = u64::from_le_bytes(*config_attributed_borrow_limit_open);
+                    if value == 0 {
+                        u64::MAX
+                    } else {
+                        value
+                    }
+                },
+                attributed_borrow_limit_close: {
+                    let value = u64::from_le_bytes(*config_attributed_borrow_limit_close);
                     if value == 0 {
                         u64::MAX
                     } else {
@@ -1567,7 +1620,8 @@ mod test {
                     protocol_take_rate: rng.gen(),
                     added_borrow_weight_bps: rng.gen(),
                     reserve_type: ReserveType::from_u8(rng.gen::<u8>() % 2).unwrap(),
-                    attributed_borrow_limit: rng.gen(),
+                    attributed_borrow_limit_open: rng.gen(),
+                    attributed_borrow_limit_close: rng.gen(),
                 },
                 rate_limiter: rand_rate_limiter(),
                 attributed_borrow_value: rand_decimal(),
@@ -1970,7 +2024,7 @@ mod test {
 
     #[test]
     fn calculate_protocol_liquidation_fee() {
-        let mut reserve = Reserve {
+        let reserve = Reserve {
             config: ReserveConfig {
                 protocol_liquidation_fee: 10,
                 ..Default::default()
@@ -1980,18 +2034,55 @@ mod test {
 
         assert_eq!(
             reserve
-                .calculate_protocol_liquidation_fee(105, Decimal::from_percent(105))
+                .calculate_protocol_liquidation_fee(
+                    105,
+                    &Bonus {
+                        total_bonus: Decimal::from_percent(5),
+                        protocol_liquidation_fee: Decimal::from_percent(1),
+                    }
+                )
                 .unwrap(),
             1
         );
 
-        reserve.config.protocol_liquidation_fee = 20;
         assert_eq!(
             reserve
-                .calculate_protocol_liquidation_fee(105, Decimal::from_percent(105))
+                .calculate_protocol_liquidation_fee(
+                    105,
+                    &Bonus {
+                        total_bonus: Decimal::from_percent(5),
+                        protocol_liquidation_fee: Decimal::from_percent(2),
+                    }
+                )
                 .unwrap(),
             2
         );
+
+        assert_eq!(
+            reserve
+                .calculate_protocol_liquidation_fee(
+                    10000,
+                    &Bonus {
+                        total_bonus: Decimal::from_percent(5),
+                        protocol_liquidation_fee: Decimal::from_percent(0),
+                    }
+                )
+                .unwrap(),
+            1
+        );
+
+        assert_eq!(
+            reserve
+                .calculate_protocol_liquidation_fee(
+                    10000,
+                    &Bonus {
+                        total_bonus: Decimal::from_percent(1),
+                        protocol_liquidation_fee: Decimal::from_percent(1),
+                    }
+                )
+                .unwrap(),
+            100
+        );
     }
 
     #[test]
@@ -2123,6 +2214,22 @@ mod test {
                     ..ReserveConfig::default()
                 },
                 result: Err(LendingError::InvalidConfig.into()),
+            }),
+            Just(ReserveConfigTestCase {
+                config: ReserveConfig {
+                    attributed_borrow_limit_open: 50,
+                    attributed_borrow_limit_close: 51,
+                    ..ReserveConfig::default()
+                },
+                result: Ok(())
+            }),
+            Just(ReserveConfigTestCase {
+                config: ReserveConfig {
+                    attributed_borrow_limit_open: 51,
+                    attributed_borrow_limit_close: 50,
+                    ..ReserveConfig::default()
+                },
+                result: Err(LendingError::InvalidConfig.into()),
             })
         ]
     }
@@ -2139,12 +2246,13 @@ mod test {
         borrowed_value: Decimal,
         unhealthy_borrow_value: Decimal,
         super_unhealthy_borrow_value: Decimal,
+        closeable: bool,
 
         liquidation_bonus: u8,
         max_liquidation_bonus: u8,
         protocol_liquidation_fee: u8,
 
-        result: Result<Decimal, ProgramError>,
+        result: Result<Bonus, ProgramError>,
     }
 
     fn calculate_bonus_test_cases() -> impl Strategy<Value = LiquidationBonusTestCase> {
@@ -2154,73 +2262,130 @@ mod test {
                 borrowed_value: Decimal::from(100u64),
                 unhealthy_borrow_value: Decimal::from(101u64),
                 super_unhealthy_borrow_value: Decimal::from(150u64),
+                closeable: false,
                 liquidation_bonus: 10,
                 max_liquidation_bonus: 20,
                 protocol_liquidation_fee: 10,
                 result: Err(LendingError::ObligationHealthy.into()),
             }),
+            // healthy but closeable
+            Just(LiquidationBonusTestCase {
+                borrowed_value: Decimal::from(100u64),
+                unhealthy_borrow_value: Decimal::from(101u64),
+                super_unhealthy_borrow_value: Decimal::from(150u64),
+                closeable: true,
+                liquidation_bonus: 10,
+                max_liquidation_bonus: 20,
+                protocol_liquidation_fee: 10,
+                result: Ok(Bonus {
+                    total_bonus: Decimal::zero(),
+                    protocol_liquidation_fee: Decimal::zero()
+                }),
+            }),
+            // unhealthy and also closeable
             Just(LiquidationBonusTestCase {
                 borrowed_value: Decimal::from(100u64),
                 unhealthy_borrow_value: Decimal::from(100u64),
                 super_unhealthy_borrow_value: Decimal::from(150u64),
+                closeable: true,
                 liquidation_bonus: 10,
                 max_liquidation_bonus: 20,
                 protocol_liquidation_fee: 10,
-                result: Ok(Decimal::from_percent(11))
+                result: Ok(Bonus {
+                    total_bonus: Decimal::from_percent(11),
+                    protocol_liquidation_fee: Decimal::from_percent(1)
+                }),
+            }),
+            Just(LiquidationBonusTestCase {
+                borrowed_value: Decimal::from(100u64),
+                unhealthy_borrow_value: Decimal::from(100u64),
+                super_unhealthy_borrow_value: Decimal::from(150u64),
+                closeable: false,
+                liquidation_bonus: 10,
+                max_liquidation_bonus: 20,
+                protocol_liquidation_fee: 10,
+                result: Ok(Bonus {
+                    total_bonus: Decimal::from_percent(11),
+                    protocol_liquidation_fee: Decimal::from_percent(1)
+                }),
             }),
             Just(LiquidationBonusTestCase {
                 borrowed_value: Decimal::from(100u64),
                 unhealthy_borrow_value: Decimal::from(50u64),
                 super_unhealthy_borrow_value: Decimal::from(150u64),
+                closeable: false,
                 liquidation_bonus: 10,
                 max_liquidation_bonus: 20,
                 protocol_liquidation_fee: 10,
-                result: Ok(Decimal::from_percent(16))
+                result: Ok(Bonus {
+                    total_bonus: Decimal::from_percent(16),
+                    protocol_liquidation_fee: Decimal::from_percent(1)
+                }),
             }),
             Just(LiquidationBonusTestCase {
                 borrowed_value: Decimal::from(100u64),
                 unhealthy_borrow_value: Decimal::from(50u64),
                 super_unhealthy_borrow_value: Decimal::from(100u64),
+                closeable: false,
                 liquidation_bonus: 10,
                 max_liquidation_bonus: 20,
                 protocol_liquidation_fee: 10,
-                result: Ok(Decimal::from_percent(21))
+                result: Ok(Bonus {
+                    total_bonus: Decimal::from_percent(21),
+                    protocol_liquidation_fee: Decimal::from_percent(1)
+                }),
             }),
             Just(LiquidationBonusTestCase {
                 borrowed_value: Decimal::from(200u64),
                 unhealthy_borrow_value: Decimal::from(50u64),
                 super_unhealthy_borrow_value: Decimal::from(100u64),
+                closeable: false,
                 liquidation_bonus: 10,
                 max_liquidation_bonus: 20,
                 protocol_liquidation_fee: 10,
-                result: Ok(Decimal::from_percent(21))
+                result: Ok(Bonus {
+                    total_bonus: Decimal::from_percent(21),
+                    protocol_liquidation_fee: Decimal::from_percent(1)
+                }),
             }),
             Just(LiquidationBonusTestCase {
                 borrowed_value: Decimal::from(60u64),
                 unhealthy_borrow_value: Decimal::from(50u64),
                 super_unhealthy_borrow_value: Decimal::from(50u64),
+                closeable: false,
                 liquidation_bonus: 10,
                 max_liquidation_bonus: 20,
                 protocol_liquidation_fee: 10,
-                result: Ok(Decimal::from_percent(11))
+                result: Ok(Bonus {
+                    total_bonus: Decimal::from_percent(11),
+                    protocol_liquidation_fee: Decimal::from_percent(1)
+                }),
             }),
             Just(LiquidationBonusTestCase {
                 borrowed_value: Decimal::from(60u64),
                 unhealthy_borrow_value: Decimal::from(40u64),
                 super_unhealthy_borrow_value: Decimal::from(60u64),
+                closeable: false,
                 liquidation_bonus: 10,
                 max_liquidation_bonus: 30,
                 protocol_liquidation_fee: 10,
-                result: Ok(Decimal::from_percent(25))
+                result: Ok(Bonus {
+                    total_bonus: Decimal::from_percent(25),
+                    protocol_liquidation_fee: Decimal::from_percent(1)
+                }),
             }),
             Just(LiquidationBonusTestCase {
                 borrowed_value: Decimal::from(60u64),
                 unhealthy_borrow_value: Decimal::from(40u64),
                 super_unhealthy_borrow_value: Decimal::from(60u64),
+                closeable: false,
                 liquidation_bonus: 30,
                 max_liquidation_bonus: 30,
                 protocol_liquidation_fee: 30,
-                result: Ok(Decimal::from_percent(25))
+                result: Ok(Bonus {
+                    total_bonus: Decimal::from_percent(25),
+                    protocol_liquidation_fee: Decimal::from_percent(3)
+                }),
             }),
         ]
     }
@@ -2242,6 +2407,7 @@ mod test {
                 borrowed_value: test_case.borrowed_value,
                 unhealthy_borrow_value: test_case.unhealthy_borrow_value,
                 super_unhealthy_borrow_value: test_case.super_unhealthy_borrow_value,
+                closeable: test_case.closeable,
                 ..Obligation::default()
             };
 
@@ -2258,6 +2424,7 @@ mod test {
         deposit_market_value: Decimal,
         borrow_amount: u64,
         borrow_market_value: Decimal,
+        bonus: Bonus,
         liquidation_result: CalculateLiquidationResult,
     }
 
@@ -2278,6 +2445,10 @@ mod test {
                 deposit_market_value: Decimal::from(100u64),
                 borrow_amount: 800,
                 borrow_market_value: Decimal::from(80u64),
+                bonus: Bonus {
+                    total_bonus: Decimal::from_percent(5),
+                    protocol_liquidation_fee: Decimal::from_percent(1)
+                },
                 liquidation_result: CalculateLiquidationResult {
                     settle_amount: close_factor.try_mul(Decimal::from(800u64)).unwrap(),
                     repay_amount: close_factor
@@ -2292,7 +2463,6 @@ mod test {
                         .unwrap()
                         .try_floor_u64()
                         .unwrap(),
-                    bonus_rate: liquidation_bonus
                 },
             }),
             // collateral market value == liquidation_value
@@ -2303,12 +2473,15 @@ mod test {
                 deposit_market_value: Decimal::from(
                     (8000 * LIQUIDATION_CLOSE_FACTOR as u64) * 105 / 10000
                 ),
+                bonus: Bonus {
+                    total_bonus: Decimal::from_percent(5),
+                    protocol_liquidation_fee: Decimal::from_percent(1)
+                },
 
                 liquidation_result: CalculateLiquidationResult {
                     settle_amount: Decimal::from((8000 * LIQUIDATION_CLOSE_FACTOR as u64) / 100),
                     repay_amount: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) / 100,
                     withdraw_amount: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) * 105 / 10000,
-                    bonus_rate: liquidation_bonus
                 },
             }),
             // collateral market value < liquidation_value
@@ -2321,6 +2494,10 @@ mod test {
                 deposit_market_value: Decimal::from(
                     (8000 * LIQUIDATION_CLOSE_FACTOR as u64) * 105 / 10000 / 2
                 ),
+                bonus: Bonus {
+                    total_bonus: Decimal::from_percent(5),
+                    protocol_liquidation_fee: Decimal::from_percent(1)
+                },
 
                 liquidation_result: CalculateLiquidationResult {
                     settle_amount: Decimal::from(
@@ -2328,7 +2505,6 @@ mod test {
                     ),
                     repay_amount: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) / 100 / 2,
                     withdraw_amount: (8000 * LIQUIDATION_CLOSE_FACTOR as u64) * 105 / 10000 / 2,
-                    bonus_rate: liquidation_bonus
                 },
             }),
             // dust ObligationLiquidity where collateral market value > liquidation value
@@ -2337,13 +2513,16 @@ mod test {
                 borrow_market_value: Decimal::from_percent(50),
                 deposit_amount: 100,
                 deposit_market_value: Decimal::from(1u64),
+                bonus: Bonus {
+                    total_bonus: Decimal::from_percent(5),
+                    protocol_liquidation_fee: Decimal::from_percent(1)
+                },
 
                 liquidation_result: CalculateLiquidationResult {
                     settle_amount: Decimal::from(100u64),
                     repay_amount: 100,
                     // $0.5 * 1.05 = $0.525
                     withdraw_amount: 52,
-                    bonus_rate: liquidation_bonus
                 },
             }),
             // dust ObligationLiquidity where collateral market value == liquidation value
@@ -2352,12 +2531,15 @@ mod test {
                 borrow_market_value: Decimal::from(1u64),
                 deposit_amount: 1000,
                 deposit_market_value: Decimal::from_percent(105),
+                bonus: Bonus {
+                    total_bonus: Decimal::from_percent(5),
+                    protocol_liquidation_fee: Decimal::from_percent(1)
+                },
 
                 liquidation_result: CalculateLiquidationResult {
                     settle_amount: Decimal::from(1u64),
                     repay_amount: 1,
                     withdraw_amount: 1000,
-                    bonus_rate: liquidation_bonus
                 },
             }),
             // dust ObligationLiquidity where collateral market value < liquidation value
@@ -2366,12 +2548,15 @@ mod test {
                 borrow_market_value: Decimal::one(),
                 deposit_amount: 10,
                 deposit_market_value: Decimal::from_bps(5250), // $0.525
+                bonus: Bonus {
+                    total_bonus: Decimal::from_percent(5),
+                    protocol_liquidation_fee: Decimal::from_percent(1)
+                },
 
                 liquidation_result: CalculateLiquidationResult {
                     settle_amount: Decimal::from(5u64),
                     repay_amount: 5,
                     withdraw_amount: 10,
-                    bonus_rate: liquidation_bonus
                 },
             }),
             // dust ObligationLiquidity where collateral market value > liquidation value and the
@@ -2381,12 +2566,32 @@ mod test {
                 borrow_market_value: Decimal::one(),
                 deposit_amount: 1,
                 deposit_market_value: Decimal::from(10u64),
+                bonus: Bonus {
+                    total_bonus: Decimal::from_percent(5),
+                    protocol_liquidation_fee: Decimal::from_percent(1)
+                },
 
                 liquidation_result: CalculateLiquidationResult {
                     settle_amount: Decimal::from(1u64),
                     repay_amount: 1,
                     withdraw_amount: 1,
-                    bonus_rate: liquidation_bonus
+                },
+            }),
+            // zero bonus rate
+            Just(LiquidationTestCase {
+                borrow_amount: 100,
+                borrow_market_value: Decimal::from(100u64),
+                deposit_amount: 100,
+                deposit_market_value: Decimal::from(100u64),
+                bonus: Bonus {
+                    total_bonus: Decimal::zero(),
+                    protocol_liquidation_fee: Decimal::zero()
+                },
+
+                liquidation_result: CalculateLiquidationResult {
+                    settle_amount: Decimal::from(20u64),
+                    repay_amount: 20,
+                    withdraw_amount: 20,
                 },
             }),
         ]
@@ -2396,11 +2601,7 @@ mod test {
         #[test]
         fn calculate_liquidation(test_case in calculate_liquidation_test_cases()) {
             let reserve = Reserve {
-                config: ReserveConfig {
-                    liquidation_bonus: 5,
-                    max_liquidation_bonus: 5,
-                    ..ReserveConfig::default()
-                },
+                config: ReserveConfig::default(),
                 ..Reserve::default()
             };
 
@@ -2425,7 +2626,12 @@ mod test {
 
             assert_eq!(
                 reserve.calculate_liquidation(
-                    u64::MAX, &obligation, &obligation.borrows[0], &obligation.deposits[0]).unwrap(),
+                    u64::MAX,
+                    &obligation,
+                    &obligation.borrows[0],
+                    &obligation.deposits[0],
+                    &test_case.bonus,
+                ).unwrap(),
                 test_case.liquidation_result);
         }
     }