Conversation
Closes LabsCrypt#1086 The per-borrower list of loan ids was stored in instance storage, which is loaded in full on every contract call. Since the list only ever grows, every call got more expensive until the contract could brick. Instance storage is the wrong place for per-borrower unbounded data. Changes: - Move BorrowerLoans(Address) read/write from instance to persistent storage, keyed per borrower with TTL bump - get_borrower_loans reads from the same persistent key - Add migration documentation explaining that existing instance-stored lists cannot be automatically migrated (Soroban does not support iterating instance keys), but this is safe because BorrowerLoanCount (used for cap enforcement) was already in persistent storage - Bump CURRENT_VERSION to 5 - Update version assertions in tests - All 130 tests pass, cargo fmt clean, cargo clippy clean
Upstream added purge_loan, which removes a loan id from the borrower's loan list, but it still read and wrote BorrowerLoans in instance storage. After this branch moved BorrowerLoans to persistent storage, the purge path operated on an empty instance entry and left the id dangling in the persistent list, failing test_purge_removes_id_from_get_borrower_loans. Point the purge cleanup at persistent storage and bump its TTL, matching request_loan and get_borrower_loans. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fury03
force-pushed
the
fix/1086-borrower-loans-instance-storage
branch
from
September 6, 2026 04:20
46fb431 to
2717401
Compare
This branch has not been deployed
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 #1086
Problem Statement (The Bug)
The per-borrower list of loan ids (
BorrowerLoans(Address)) is stored in instance storage, which is loaded in full on every contract call. Since the list only ever grows, every call gets more expensive until the contract can brick. This is per-borrower unbounded data that belongs in persistent storage keyed per borrower.Instance storage is one bucket loaded in full on every invocation. An unbounded, append-only list per borrower inflates that entry over time, raising read/write cost on every call and eventually pushing toward the instance size limit, at which point the whole contract becomes unusable. This cannot be fixed with a local patch because it's a fundamental storage layout issue — instance storage simply cannot hold unbounded per-key data.
Solution Comparison and Decision
Option A: Paginate the instance-stored list
Option B: Move to persistent storage keyed per borrower
Option C: Store in a Soroban contract with Map
Chosen: Option B — Moving
BorrowerLoans(Address)from instance to persistent storage is the only correct path. It's how Soroban is designed to handle per-key unbounded data.The Change (Code modifications)
request_loan(write)instance().get/set(BorrowerLoans)persistent().get/set(BorrowerLoans)+ TTL bumpget_borrower_loans(read)instance().get(BorrowerLoans)persistent().get(BorrowerLoans)+ TTL bumpget_borrower_loanssimilarly reads frompersistent()and bumps TTL.Migration documentation added in the
migrate()function explaining that existing instance-stored lists cannot be automatically migrated (Soroban doesn't support iterating instance keys), but this is safe because:BorrowerLoanCount(used for cap enforcement) was already in persistent storageLoanrecords were already in persistent storageCompatibility Note
INTERFACE_VERSION is not modified (not applicable to this contract's interface pattern). The
CURRENT_VERSIONconstant is bumped from 4 to 5 to mark the storage layout change. The external API (get_borrower_loansreturn shape) is unchanged.Incidental Fixes
Testing
All 130 tests pass:
Key test:
test_get_borrower_loans— verifies write-then-read round trip, which now exercises persistent storage. The test creates two loans for a borrower, verifies both are returned, repays one, and confirms the list still contains both (historical record).cargo fmt --check— cleancargo clippy— only pre-existing doc warning in events.rs (unrelated)Additional Notes
Test snapshots were regenerated (gitignored) due to the version bump. No changes to shared code — only
contracts/loan_manager/src/lib.rsandcontracts/loan_manager/src/test.rswere modified.