test(contracts): regression tests for liquidate TotalOutstanding accounting (#1084) - #1735
Open
Banx17 wants to merge 2 commits into
Open
test(contracts): regression tests for liquidate TotalOutstanding accounting (#1084)#1735Banx17 wants to merge 2 commits into
Banx17 wants to merge 2 commits into
Conversation
The doc comment for loan_approved' continuing paragraph was flagged by clippy::doc-lazy-continuation under -D warnings, which has been failing the contracts CI job on main since it was introduced. Add the separating blank line so the paragraph is no longer treated as a continuation of the preceding numbered list.
…accounting (LabsCrypt#1084) liquidate() retires TotalOutstanding via adjust_total_outstanding(-loan.amount), mirroring the increment recorded at approval. The "done when" checklist for LabsCrypt#1084 required regression tests proving the accounting never permanently inflates TotalOutstanding and starves later loans of liquidity: - A single approve -> liquidate cycle returns get_total_outstanding() exactly to its pre-loan baseline (not merely "decreased"). - Repeated approve -> liquidate cycles across two loans do not compound the inflation and leave available_liquidity non-starved, and a subsequent approve_loan still succeeds. - Combined with the existing repay and check_default tests, the full lifecycle (repay, default, liquidate) is covered: every terminal path returns TotalOutstanding to its pre-loan baseline. These tests genuinely catch the original bug: if the liquidate() decrement is removed, the outstanding assertion after liquidation fails because TotalOutstanding stays inflated by the full loan amount.
Banx17
force-pushed
the
fix/1084-liquidate-total-outstanding
branch
from
August 31, 2026 12:57
3621ab1 to
e90eb30
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1084
Summary
This PR closes #1084 by adding the regression tests required by the issue's
"done when" checklist, verifying that
liquidate()correctly retiresTotalOutstandingso it never permanently inflates and starves new loans ofliquidity.
Important context: the core code fix is already merged into
main(line 1714 of
contracts/loan_manager/src/lib.rs):was added inside
liquidate()(merged via #1460, commitb225859),mirroring the
+loan.amountrecorded atapprove_loantime. The only unmet"done when" items were the regression tests, which this PR adds.
Changed files
contracts/loan_manager/src/test.rs— adds two new regression tests:test_get_total_outstanding_returns_to_baseline_after_liquidationtest_repeated_liquidations_do_not_shrink_available_liquiditycontracts/loan_manager/src/events.rs— 1-line doc-comment fix for apre-existing, unrelated
clippy::doc-lazy-continuationwarning that wasfailing the
contractsCI job onmain(would otherwise block this PR'scheck). No behavior change.
"done when" checklist
liquidate()decrementsTotalOutstandingexactly once — satisfied bythe already-merged code:
adjust_total_outstanding(&env, &token, -loan.amount)is called in
liquidate()alongside the other terminal state mutations(status →
Liquidated,collateral_amount = 0), before the storage commitand external transfers, in the same CEI transaction.
liquidate()has asingle execution path to
Liquidated, so there is no double-decrement (adouble decrement would underflow-panic in
adjust_total_outstanding) and nomissed decrement. The new repeated-liquidation test asserts
get_total_outstanding()equals the baseline after each of two liquidations,catching either a missing or a double decrement.
adjust_total_outstandinguseschecked_add(delta)and panics on negativeupdated(underflow), matchingrepay,check_default, andcheck_defaultsexactly. Nounwrapon theadjustment and no changes to caller-side arithmetic were needed or added.
confined to
loan_manager's ownTotalOutstandinginstance storage vialiquidate().LendingPool'spool_balance/accounting is untouched. (Note:the already-merged fix likewise only touches loan_manager storage.)
test_get_total_outstanding_returns_to_baseline_after_liquidation: approvesa loan, asserts outstanding rose by the loan amount, liquidates, and asserts
get_total_outstanding()returns exactly to its pre-loan baseline (0),not merely "decreased."
test_repeated_liquidations_do_not_shrink_available_liquidity: approve →liquidate for loan A, then approve → liquidate for loan B, asserting
get_total_outstanding()returns to baseline after each (proving thecompounding bug is fixed, not just a single isolated liquidation), and a
subsequent
approve_loanstill succeeds against non-starvedavailable_liquidity(which ispool_balance - total_outstandinginapprove_loan).the existing
test_get_total_outstanding_tracks_approve_and_repay(repay → 0)and
test_get_total_outstanding_decreases_on_check_default(default → 0)cover the repay and default legs; the new liquidate test above covers the
third leg. Together all three terminal paths are verified to restore
TotalOutstandingto baseline, matching the issue's cross-path invariant.These tests genuinely catch the original bug: if the
liquidate()decrement isremoved, the outstanding-after-liquidation assertions (
assert_eq!( get_total_outstanding, baseline)) fail becauseTotalOutstandingstaysinflated by the full loan amount (1000 / 2000 in the two tests).
Local verification
Environment note: this is a Windows host. The contract test harness links the
soroban contract cdylibs as Windows DLLs, and those cdylibs exceed the Windows
PE export table limit (max 65,535 symbols), so
cargo testcannot link/run thecontract unit tests locally — a hard, pre-existing, environment-specific
limitation. CI runs the same suite on Linux (
ubuntu-24.04) where it passes.I validated the change with the equivalent compile/check/lint commands that CI
runs (CI's exact commands per
.github/workflows/ci.yml):cargo clippybefore the events.rs fix surfaced the pre-existingclippy::doc-lazy-continuationwarning atevents.rs:62; after the 1-line fixclippy passes with zero warnings.
cargo testwill be exercised by CI on Linux.