Skip to content

fix: Enforce asset authorization on LoanBrokerDelete - #8204

Open
tyalymov wants to merge 4 commits into
developfrom
tialymov/FN-102-loanbrokerdelete_asset_auth
Open

tyalymov wants to merge 4 commits into
developfrom
tialymov/FN-102-loanbrokerdelete_asset_auth

Conversation

@tyalymov

@tyalymov tyalymov commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

High Level Overview of Change

LoanBrokerDelete now checks asset transfer restrictions and owner authorization before returning nonzero broker cover.

Context of Change

LoanBrokerDelete previously checked freeze state but returned cover through accountSend without the checks used by LoanBrokerCoverWithdraw. Under fixCleanup3_4_0, deletion now follows the same self-withdrawal policy. Pre-amendment and zero-cover behavior remain unchanged.

API Impact

  • libxrpl change

Test Plan

LoanBroker tests cover authorized and revoked MPT owners, IOU NoRipple restrictions, both amendment states, and deletion after cover reaches zero.

@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Production change extends LoanBrokerDelete::preclaim to run the same transfer/authorization checks as LoanBrokerCoverWithdraw under fixCleanup3_4_0 before releasing cover; the added canTransfer/requireAuth/checkDeepFrozen/checkFrozen ordering looks consistent with the test expectations (terNO_RIPPLE for canTransfer failures, tecNO_AUTH for requireAuth failures) and the waive-CanTransfer logic correctly mirrors the fixCleanup3_2_0 self-withdrawal exemption. The new holdingExists/canAddHolding branch (taken when the broker owner has no existing holding for the asset) is not exercised by any of the added tests, which otherwise cover authorized/unauthorized MPT owners, IOU NoRipple, both amendment states, and zero-cover deletion.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes consensus-critical transaction preclaim behavior for asset transfer/authorization enforcement and should receive a final human review despite good targeted test coverage.

Pull request overview

This PR updates the LoanBrokerDelete transactor to enforce the same asset transfer restriction and authorization checks that already apply to LoanBrokerCoverWithdraw, but only when fixCleanup3_4_0 is enabled. This closes a gap where deletion could previously pay out nonzero broker cover via accountSend without the newer transferability/authorization gating.

Changes:

  • Added post-fixCleanup3_4_0 preclaim checks in LoanBrokerDelete for canTransfer, requireAuth, and (when needed) canAddHolding viability before allowing cover payout.
  • Expanded lending unit tests to cover IOU NoRipple blocking behavior and MPT owner authorization revoked/allowed scenarios for LoanBrokerDelete.
File summaries
File Description
src/test/app/lending/LoanBroker_test.cpp Adds regression coverage for deletion payout restrictions: IOU NoRipple enforcement post-fix and MPT owner-authorization failure/success paths.
src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp Aligns delete-with-cover behavior (post-fixCleanup3_4_0) with cover-withdraw transfer/authorization/holding checks while preserving pre-amendment behavior.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

The cover payout has to recreate a deleted holding, so canAddHolding
decides whether deletion can proceed. Post-fixCleanup3_4_0 an issuer
that cleared DefaultRipple blocks it with terNO_RIPPLE, while deletion
still succeeds when the line can be recreated.

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core fix in LoanBrokerDelete.cpp consolidates the freeze/frozen checks under the existing coverAvailable > 0 guard and adds a new fixCleanup3_4_0-gated block (canTransfer, requireAuth, holding-existence/canAddHolding) before the pre-existing deep-freeze and pseudo-account-freeze checks. The restructuring correctly preserves pre-amendment behavior (only freeze checks run) while adding the self-withdrawal-style authorization/transfer checks post-amendment, and it removes the now-redundant duplicate sfAccount lookup. This matches the accompanying test additions (NoRipple IOU line, deleted trustline recreation via canAddHolding, and MPT auth-revoked owner scenarios with clawback-then-delete), which cover both amendment states and the described behaviors. I did not find any lines added in this diff that introduce a clear correctness or security bug within the visible context; the logic is consistent with the stated intent and the referenced helper functions (canTransfer, requireAuth, holdingExists, canAddHolding) are presumably already vetted elsewhere (e.g. LoanBrokerCoverWithdraw) for this same self-withdrawal policy.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes core transaction preclaim rules for asset movement on broker deletion (consensus-sensitive behavior) and should receive final human review despite strong test coverage.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core fix in LoanBrokerDelete.cpp cleanly folds the new cover-payout authorization/transfer checks (canTransfer, requireAuth, holdingExists/canAddHolding) into the existing coverAvailable-gated block, gated by featureLendingProtocolV1_2, and mirrors the self-withdrawal policy used by LoanBrokerCoverWithdraw. Test coverage for authorized/unauthorized MPT owners, IOU NoRipple, and both amendment states looks thorough and internally consistent. One notable discrepancy: the PR description attributes the new self-withdrawal enforcement to fixCleanup3_4_0, but the actual code (and the test comments) gate this behavior on featureLendingProtocolV1_2 — worth reconciling so reviewers assess risk against the correct amendment.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Fix missing MPT holding creation and preserve pre-Cleanup3.4 behavior through amendment gating.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (1)

src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp:117

  • canAddHolding is an issuer-level create-path check, so this must be gated by fixCleanup3_4_0 just like LoanBrokerCoverWithdraw and VaultWithdraw. As written, a post-featureLendingProtocolV1_2 deletion before Cleanup3.4 is rejected whenever the owner has no holding (for example, an MPT whose lsfMPTCanTransfer is cleared), even though the pre-fix accountSend path is allowed to create the holding; this changes the advertised pre-fix behavior.
            if (!holdingExists(ctx.view, brokerOwner, asset))
            {
                if (auto const ret = canAddHolding(ctx.view, asset); !isTesSuccess(ret))
                    return ret;
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +114 to +118
if (!holdingExists(ctx.view, brokerOwner, asset))
{
if (auto const ret = canAddHolding(ctx.view, asset); !isTesSuccess(ret))
return ret;
}
// Pre-featureLendingProtocolV1_2: only freeze checks apply to the cover payout.
// Post-featureLendingProtocolV1_2: apply the cover-withdraw transfer and authorization
// checks too.
if (ctx.view.rules().enabled(featureLendingProtocolV1_2))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a fix, let's place it under fixCleanup3_5_0

// checks too.
if (ctx.view.rules().enabled(featureLendingProtocolV1_2))
{
auto const waive = ctx.view.rules().enabled(fixCleanup3_2_0) ? WaiveMPTCanTransfer::Yes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a big no no. When an amendment is released, new changes cannot be gated under it.

That said, I'm not sure this wave is needed in the first place. Think of it this way: after 3_2_0 we will wave the transfer check. Since this code is adding new logic, the 3_2_0 gating is unnecessary, as this code didn't exist when 3_2_0 was released.

}

void
testLoanBrokerDeleteDeletedLineIOU(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't pass clearDefaultRipple in from run(). The policy belongs in the test.

Call sites like testLoanBrokerDeleteDeletedLineIOU(all_, true, terNO_RIPPLE) are unreadable. Either loop DefaultRipple on/off inside this function, or split into two named tests.


void
testLoanBrokerDeleteRequireAuthMPT(FeatureBitset features)
testLoanBrokerDeleteNoRippleIOU(FeatureBitset features)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two new IOU tests duplicate the fund / vault / broker / cover setup and only differ in the restriction applied before del. Pull the shared setup into a small helper, then keep two named tests (NoRipple vs deleted dest line). Don't merge them into one bool-parameterized function.

}

void
testLoanBrokerDeleteRequireAuthMPT(FeatureBitset features, bool ownerAuthorized)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This used to be one claim: authorized owner, Cleanup 3.4 vs not. The ownerAuthorized flag folds in two more: unauthorized dest (tecNO_AUTH / invariant) and clawback-to-zero then delete. Those are separate tests.

Restore this function to authorized-owner only (and drop tfMPTCanClawback unless this test clawbacks). Add testLoanBrokerDeleteUnauthorizedOwnerMPT that always unauthorizes and computes TER from the amendments. Put the zero-cover success path in its own test so it is not an early return here.

testLoanBrokerDeleteNoRippleIOU(all_);
testLoanBrokerDeleteNoRippleIOU(all_ - featureLendingProtocolV1_2);

testLoanBrokerDeleteDeletedLineIOU(all_, true, terNO_RIPPLE);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same pattern as the helpers above: positional true / false / TER does not say what is being varied. run() should look like FrozenIOU — feature sets only.

Also, unauthorized owner with neither V1.2 nor Cleanup 3.4 is never run. If pre-amendment delete is supposed to succeed, that cell should be explicit.

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.

3 participants