Skip to content

Commit aabf56b

Browse files
authored
Merge pull request #37 from BigJohn-dev/LoanManager-uses-raw-panics-for-user-facing-failures-instead-of-typed-LoanError-returns
feat(loan_manager): add repayment below minimum error handling and up…
2 parents da3281d + 618724a commit aabf56b

4 files changed

Lines changed: 39 additions & 14 deletions

File tree

lending_pool/src/lib.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub enum PoolError {
2121
NoProposedAdmin = 10,
2222
CooldownTooLong = 11,
2323
NotPaused = 12,
24+
WithdrawalCooldownActive = 13,
2425
}
2526

2627
/// Storage keys.
@@ -300,20 +301,26 @@ impl LendingPool {
300301
.ok_or(PoolError::InvalidAmount)
301302
}
302303

303-
fn assert_withdrawal_cooldown_elapsed(env: &Env, provider: &Address, token: &Address) {
304+
fn assert_withdrawal_cooldown_elapsed(
305+
env: &Env,
306+
provider: &Address,
307+
token: &Address,
308+
) -> Result<(), PoolError> {
304309
let cooldown = Self::withdrawal_cooldown(env);
305310
if cooldown == 0 {
306-
return;
311+
return Ok(());
307312
}
308313

309314
let Some(deposit_ledger) = Self::read_deposit_timestamp(env, provider, token) else {
310-
return;
315+
return Ok(());
311316
};
312317

313318
let current_ledger = env.ledger().sequence();
314319
if current_ledger < deposit_ledger.saturating_add(cooldown) {
315-
panic!("withdrawal_cooldown_active");
320+
return Err(PoolError::WithdrawalCooldownActive);
316321
}
322+
323+
Ok(())
317324
}
318325

319326
/// Burns `shares` for `provider` and transfers out the proportional
@@ -726,7 +733,7 @@ impl LendingPool {
726733
) -> Result<(), PoolError> {
727734
provider.require_auth();
728735
Self::assert_not_paused(&env)?;
729-
Self::assert_withdrawal_cooldown_elapsed(&env, &provider, &token);
736+
Self::assert_withdrawal_cooldown_elapsed(&env, &provider, &token)?;
730737
let assets = Self::redeem_shares(&env, &provider, &token, shares)?;
731738
withdraw(&env, provider, token, assets, shares);
732739
Ok(())

lending_pool/src/test.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,7 @@ fn test_insufficient_balance_withdraw_panic() {
194194
}
195195

196196
#[test]
197-
#[should_panic(expected = "withdrawal_cooldown_active")]
198-
fn test_immediate_withdraw_panics_when_cooldown_active() {
197+
fn test_withdraw_returns_cooldown_error_when_cooldown_active() {
199198
let env = Env::default();
200199
env.mock_all_auths();
201200

@@ -210,7 +209,8 @@ fn test_immediate_withdraw_panics_when_cooldown_active() {
210209
stellar_asset_client.mint(&provider, &5_000);
211210
pool_client.deposit(&provider, &token_id, &1_000);
212211

213-
pool_client.withdraw(&provider, &token_id, &1_000);
212+
let result = pool_client.try_withdraw(&provider, &token_id, &1_000);
213+
assert_eq!(result, Err(Ok(crate::PoolError::WithdrawalCooldownActive)));
214214
}
215215

216216
#[test]

loan_manager/src/lib.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub enum LoanError {
6262
InvalidExtension = 25,
6363
InsufficientCollateral = 26,
6464
LoanNotLiquidatable = 27,
65+
RepaymentBelowMinimum = 28,
6566
}
6667

6768
#[contracttype]
@@ -515,6 +516,10 @@ impl LoanManager {
515516
.checked_add(delta)
516517
.expect("total outstanding overflow");
517518

519+
// This can only become negative if the contract state is inconsistent:
520+
// repayment logic only ever subtracts the exact principal amount of a
521+
// fully repaid loan, and the corresponding loan-approval bookkeeping
522+
// prevents a second subtraction from the same outstanding balance.
518523
if updated < 0 {
519524
panic!("total outstanding underflow");
520525
}
@@ -1270,7 +1275,7 @@ impl LoanManager {
12701275
let is_rounding_dust_forgiveness = total_debt <= min_repayment_amount;
12711276

12721277
if amount < total_debt && amount < min_repayment_amount && !is_rounding_dust_forgiveness {
1273-
panic!("repayment amount below minimum");
1278+
return Err(LoanError::RepaymentBelowMinimum);
12741279
}
12751280

12761281
let token: Address = env
@@ -2057,16 +2062,16 @@ impl LoanManager {
20572062
Self::max_loan_amount(&env)
20582063
}
20592064

2060-
pub fn set_min_repayment_amount(env: Env, amount: i128) {
2065+
pub fn set_min_repayment_amount(env: Env, amount: i128) -> Result<(), LoanError> {
20612066
if amount < 0 {
2062-
panic!("min repayment amount cannot be negative");
2067+
return Err(LoanError::InvalidAmount);
20632068
}
20642069

20652070
let admin: Address = env
20662071
.storage()
20672072
.instance()
20682073
.get(&DataKey::Admin)
2069-
.expect("not initialized");
2074+
.ok_or(LoanError::NotInitialized)?;
20702075
admin.require_auth();
20712076

20722077
let old_amount = Self::min_repayment_amount(&env);
@@ -2075,6 +2080,8 @@ impl LoanManager {
20752080
.set(&DataKey::MinRepaymentAmount, &amount);
20762081
Self::bump_instance_ttl(&env);
20772082
events::min_repayment_updated(&env, admin, old_amount, amount);
2083+
2084+
Ok(())
20782085
}
20792086

20802087
pub fn get_min_repayment_amount(env: Env) -> i128 {

loan_manager/src/test.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,6 @@ fn test_partial_repayment_tracks_split_balances() {
710710
}
711711

712712
#[test]
713-
#[should_panic(expected = "repayment amount below minimum")]
714713
fn test_minimum_repayment_amount_enforced() {
715714
let env = Env::default();
716715
env.mock_all_auths_allowing_non_root_auth();
@@ -737,7 +736,19 @@ fn test_minimum_repayment_amount_enforced() {
737736
manager.approve_loan(&loan_id);
738737

739738
manager.set_min_repayment_amount(&150);
740-
manager.repay(&borrower, &loan_id, &100);
739+
let result = manager.try_repay(&borrower, &loan_id, &100);
740+
assert_eq!(result, Err(Ok(LoanError::RepaymentBelowMinimum)));
741+
}
742+
743+
#[test]
744+
fn test_set_min_repayment_amount_rejects_negative_values() {
745+
let env = Env::default();
746+
env.mock_all_auths();
747+
748+
let (manager, _nft_client, _pool_client, _token_id, _token_admin) = setup_test(&env);
749+
750+
let result = manager.try_set_min_repayment_amount(&-1);
751+
assert_eq!(result, Err(Ok(LoanError::InvalidAmount)));
741752
}
742753

743754
#[test]

0 commit comments

Comments
 (0)