Skip to content

reinstate_credit_line from Defaulted#213

Merged
greatest0fallt1me merged 6 commits intoCreditra:mainfrom
Phantomcall:feature/reinstate-credit-line
Mar 31, 2026
Merged

reinstate_credit_line from Defaulted#213
greatest0fallt1me merged 6 commits intoCreditra:mainfrom
Phantomcall:feature/reinstate-credit-line

Conversation

@Phantomcall
Copy link
Copy Markdown

feat(credit): reinstate_credit_line — Defaulted → Active / Suspended

Summary

Implements reinstate_credit_line as a public contract entry point, allowing an admin to transition a credit line out of the Defaulted state back to either Active or Suspended, per the documented state machine. Also resolves a set of pre-existing compilation and test errors that were blocking the build.


What Changed

Core Feature — reinstate_credit_line

contracts/credit/src/lifecycle.rs

Updated the existing reinstate_credit_line function to accept a target_status: CreditStatus parameter instead of hardcoding Active. The function now:

  • Validates the credit line exists (panics with "Credit line not found" otherwise)
  • Validates the current status is Defaulted (panics with "credit line is not defaulted" otherwise)
  • Validates target_status is either Active or Suspended (panics with "target_status must be Active or Suspended" for any other value)
  • Persists the new status
  • Emits a ("credit", "reinstate") CreditLineEvent with the new status

contracts/credit/src/lib.rs

Exposed reinstate_credit_line as a public #[contractimpl] function on the Credit struct, with full doc comments covering parameters, panics, events, and post-reinstatement invariants.

State Machine

Active ──────────────────────────────────────────► Closed
  │                                                   ▲
  ▼                                                   │
Suspended ──────────────────────────────────────────► │
  │                                                   │
  ▼                                                   │
Defaulted ──── reinstate_credit_line ──► Active ─────►│
                                     └─► Suspended ──►│

Invariants After Reinstatement

  • utilized_amount is preserved unchanged (outstanding debt is not forgiven)
  • credit_limit, interest_rate_bps, and risk_score are unchanged
  • Draws are re-enabled when target is Active; remain disabled when target is Suspended
  • Only admin can call this function

Tests Added (contracts/credit/src/test.rs)

12 new explicit transition tests:

Test What it covers
test_reinstate_to_active_enables_draws Defaulted → Active; draw succeeds after
test_reinstate_to_suspended_status Defaulted → Suspended; status is Suspended
test_reinstate_to_suspended_blocks_draws Defaulted → Suspended; draw still panics
test_reinstate_preserves_utilized_amount All fields unchanged after → Active
test_reinstate_to_suspended_preserves_utilized_amount All fields unchanged after → Suspended
test_reinstate_invalid_target_status_closed_reverts Closed as target panics
test_reinstate_invalid_target_status_defaulted_reverts Defaulted as target panics
test_reinstate_to_active_emits_event_with_active_status Event has correct status + borrower
test_reinstate_to_suspended_emits_event_with_suspended_status Event has correct status + borrower
test_reinstate_to_active_then_suspend_again Full round-trip: Defaulted → Active → Suspended
test_reinstate_to_suspended_then_admin_close Defaulted → Suspended → Closed
test_reinstate_to_suspended_unauthorized Non-admin call panics

All existing reinstate call sites updated to pass &CreditStatus::Active as the target.


Pre-existing Errors Fixed

The codebase had 97 compilation errors and several failing tests before this PR. The following were resolved as part of this work:

Compilation Errors (lib.rs)

Error Root Cause Fix
ContractError undeclared use types::{} was missing ContractError Added to import
config::set_liquidity_token / set_liquidity_source mod config was never declared in lib.rs Inlined the two function bodies directly
query::get_credit_line mod query was never declared in lib.rs Inlined env.storage().persistent().get(&borrower)
risk::set_rate_change_limits / get_rate_change_limits mod risk was never declared in lib.rs Inlined both function bodies
CreditLineData missing fields accrued_interest and last_accrual_ts added to types.rs but not to the struct literal in open_credit_line Added both fields initialised to 0
Missing SPDX header lib.rs first line was #![no_std] Added // SPDX-License-Identifier: MIT as line 1

Test Errors (lib.rs test modules)

Test Problem Fix
All repay tests in mod test setup() and approve() helpers called but not defined in that module Added both helpers to mod test
Event tests in mod test TryFromVal / TryIntoVal not in scope Added to use statement
test_suspend_nonexistent_credit_line Body opened a line with invalid rate (10001) instead of suspending a nonexistent borrower Rewrote to call suspend_credit_line on an address with no line
suspend_defaulted_line_reverts Body was testing draw/balance assertions, never called default_credit_line or suspend_credit_line Rewrote to: default → suspend → expect panic
test_draw_credit_updates_utilized Called update_risk_parameters with risk_score = 101 (exceeds max of 100) Changed to 70
test_multiple_borrowers (smoke) Called suspend_credit_line after default_credit_line — invalid transition that panics Rewrote to open two borrowers and assert independent state
test_event_reinstate_credit_line (coverage gaps) Called setup_contract_with_credit_line which is not in scope in that module Switched to base_setup which is defined in the same module

Integration Test Error (tests/duplicate_open_policy.rs)

Error Root Cause Fix
Non-exhaustive match on CreditStatus Restricted variant added to enum but not covered in match Added CreditStatus::Restricted => {} arm

Test Results

test result: ok. 66 passed; 0 failed  (lib)
test result: ok. 28 passed; 0 failed  (integration)
test result: ok. 3 passed;  0 failed  (spdx_header_bug_exploration)
test result: ok. 6 passed;  0 failed  (spdx_preservation)
test result: ok. 7 passed;  0 failed  (duplicate_open_policy)

Security Notes

  • reinstate_credit_line is admin-only. No borrower-initiated reinstatement path exists.
  • Trust boundary: the admin is assumed to be a trusted off-chain system or multisig. No on-chain oracle or automated trigger is wired to this function.
  • utilized_amount is intentionally preserved on reinstatement — the debt does not disappear. Reinstating to Active re-enables draws, so the admin should verify the borrower's repayment capacity before reinstating.
  • Reinstating to Suspended is a safer intermediate step: it clears the Defaulted flag (e.g. for accounting) while keeping draws locked until a subsequent Active transition.
  • Failure mode: if the admin key is compromised, an attacker could reinstate defaulted lines and allow draws. This is the same trust boundary as all other admin-only lifecycle functions.

Closes #115

Implement reinstate_credit_line as a public contract function that
transitions a Defaulted credit line to either Active or Suspended,
per the documented state machine.

Changes:
- lifecycle.rs: update reinstate_credit_line to accept target_status
  parameter (Active or Suspended); validate invalid targets panic with
  'target_status must be Active or Suspended'
- lib.rs: expose reinstate_credit_line as a public #[contractimpl] fn;
  inline config:: and query:: calls that were referencing undeclared
  modules; add missing ContractError import; fix CreditLineData init
  to include accrued_interest and last_accrual_ts fields
- test.rs: update all existing call sites to pass target_status; add
  12 new explicit transition tests covering:
  - Defaulted → Active (draws re-enabled)
  - Defaulted → Suspended (draws still blocked)
  - utilized_amount preserved on both paths
  - invalid target_status (Closed, Defaulted) reverts
  - event emission with correct status for both targets
  - state machine round-trips (reinstate → suspend, reinstate → close)
  - unauthorized caller reverts

Invariants documented:
  - utilized_amount, credit_limit, interest_rate_bps, risk_score
    are all preserved unchanged after reinstatement
  - Only admin can reinstate; only from Defaulted status

Closes Creditra#115
- Add missing SPDX-License-Identifier: MIT header to lib.rs
- Import ContractError from types in lib.rs (was undeclared)
- Inline config:: calls (set_liquidity_token, set_liquidity_source)
- Inline query::get_credit_line (module was never declared)
- Inline risk::set/get_rate_change_limits (module was never declared)
- Add missing accrued_interest and last_accrual_ts fields to CreditLineData init
- Add missing setup() and approve() helpers to test module
- Add TryFromVal/TryIntoVal imports to test module
- Fix test_suspend_nonexistent_credit_line: was opening with invalid rate instead of suspending nonexistent borrower
- Fix suspend_defaulted_line_reverts: body was testing draw/balance, not the suspend transition
- Fix test_draw_credit_updates_utilized: risk_score 101 exceeds max, changed to 70
- Fix test_multiple_borrowers: was calling suspend after default (invalid transition), rewrote to actually test multiple borrowers
- Fix duplicate_open_policy.rs: add Restricted arm to non-exhaustive match on CreditStatus
- Delete stale test snapshots that caused false failures
@drips-wave
Copy link
Copy Markdown

drips-wave bot commented Mar 30, 2026

@Phantomcall Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Phantomcall and others added 3 commits March 30, 2026 13:15
- cargo fmt: fix all formatting issues flagged by CI
- Add test_events_coverage module covering all uncovered events.rs
  functions: publish_credit_line_event_v2, publish_repayment_event_v2,
  publish_drawn_event_v2, publish_interest_accrued_event
- Fix test_reinstate_to_suspended_blocks_draws: expected panic message
  was 'draws are not allowed', actual is 'credit line is suspended'
- Add SPDX header to lib.rs (fixes spdx_header_bug_exploration tests)

Coverage: events.rs was at 40.68% dragging total to 85.71%.
New tests cover all 4 previously-dead publish functions, pushing
events.rs coverage up and total line coverage above 95% threshold.
- Remove unused Ledger import from test_smoke_coverage module
- Remove dead-code helpers from mod test (setup_contract_with_credit_line,
  setup_test, call_contract, get_credit_data) — none were called
- Fix unused variable: borrower shadowed in test_event_reinstate_credit_line
- Fix unused variable: token_id in draw_credit_overflow test (prefix _)
- Add #[allow(dead_code)] to events.rs v2 publish functions and
  publish_interest_accrued_event — these are public API for indexers,
  intentionally not called from contract logic
@greatest0fallt1me greatest0fallt1me merged commit 17b2dd7 into Creditra:main Mar 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

reinstate_credit_line from Defaulted

2 participants