diff --git a/contracts/loan_manager/src/lib.rs b/contracts/loan_manager/src/lib.rs index c7f6d05d..776db9a8 100644 --- a/contracts/loan_manager/src/lib.rs +++ b/contracts/loan_manager/src/lib.rs @@ -425,14 +425,15 @@ impl LoanManager { } let remaining_principal = Self::remaining_principal(loan); - let remaining_debt = remaining_principal - .checked_add(loan.accrued_interest) - .expect("debt overflow"); - if remaining_debt <= 0 { + if remaining_principal <= 0 { loan.last_late_fee_ledger = current_ledger; return 0; } + let remaining_debt = remaining_principal + .checked_add(loan.accrued_interest) + .expect("debt overflow"); + let overdue_ledgers = current_ledger - late_fee_start; let incremental_fee = remaining_debt .checked_mul(Self::late_fee_rate_bps(env) as i128) diff --git a/contracts/loan_manager/src/test.rs b/contracts/loan_manager/src/test.rs index ce95dd25..96461dfc 100644 --- a/contracts/loan_manager/src/test.rs +++ b/contracts/loan_manager/src/test.rs @@ -962,6 +962,63 @@ fn test_late_fee_is_capped_at_quarter_principal() { assert_eq!(loan.accrued_late_fee, 250); } +#[test] +fn test_late_fee_does_not_accrue_after_principal_fully_repaid() { + let env = Env::default(); + env.mock_all_auths_allowing_non_root_auth(); + + let (manager, nft_client, pool_client, token_id, _token_admin) = setup_test(&env); + let borrower = Address::generate(&env); + + let history_hash = soroban_sdk::BytesN::from_array(&env, &[0u8; 32]); + nft_client.mint(&borrower, &600, &history_hash, &None); + + let stellar_token = StellarAssetClient::new(&env, &token_id); + stellar_token.mint(&pool_client, &10_000); + + manager.set_late_fee_rate(&500); + manager.set_grace_period_ledgers(&0); + env.ledger().set_sequence_number(1); + + let loan_id = manager.request_loan(&borrower, &1_000); + manager.approve_loan(&loan_id); + + let due_date = manager.get_loan(&loan_id).due_date; + + env.as_contract(&manager.address, || { + let loan_key = DataKey::Loan(loan_id); + let mut loan: Loan = env.storage().persistent().get(&loan_key).unwrap(); + loan.principal_paid = loan.amount; + loan.interest_paid = 0; + loan.accrued_interest = 75; + loan.accrued_late_fee = 10; + loan.last_late_fee_ledger = loan.due_date; + env.storage().persistent().set(&loan_key, &loan); + }); + + env.ledger().set_sequence_number(due_date + 8_640); + + let late_fee_delta = env.as_contract(&manager.address, || { + let loan_key = DataKey::Loan(loan_id); + let mut loan: Loan = env.storage().persistent().get(&loan_key).unwrap(); + let accrued_before = loan.accrued_late_fee; + + let delta = LoanManager::accrue_late_fee(&env, &mut loan); + + assert_eq!(delta, 0); + assert_eq!(loan.accrued_late_fee, accrued_before); + + env.storage().persistent().set(&loan_key, &loan); + delta + }); + + assert_eq!(late_fee_delta, 0); + let loan_after = manager.get_loan(&loan_id); + assert_eq!(loan_after.principal_paid, loan_after.amount); + assert_eq!(loan_after.accrued_interest, 75); + assert_eq!(loan_after.accrued_late_fee, 10); +} + #[test] fn test_deposit_collateral_and_auto_release_on_full_repayment() { let env = Env::default();