From 40c29bbfd66360c5c9e597aaeea809588456cdaa Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Tue, 16 Dec 2025 19:19:27 +0100 Subject: [PATCH] Fix LoanBrokerSet debtMaximum limits (#6116) --- src/test/app/LoanBroker_test.cpp | 73 +++++++++++++++++++++++ src/xrpld/app/tx/detail/LoanBrokerSet.cpp | 12 ++++ 2 files changed, 85 insertions(+) diff --git a/src/test/app/LoanBroker_test.cpp b/src/test/app/LoanBroker_test.cpp index 72a732d043..ef4804f217 100644 --- a/src/test/app/LoanBroker_test.cpp +++ b/src/test/app/LoanBroker_test.cpp @@ -1436,10 +1436,83 @@ class LoanBroker_test : public beast::unit_test::suite }); } + void + testLoanBrokerSetDebtMaximum() + { + testcase("testLoanBrokerSetDebtMaximum"); + using namespace jtx; + using namespace loanBroker; + Account const issuer{"issuer"}; + Account const alice{"alice"}; + Env env(*this); + Vault vault{env}; + + env.fund(XRP(100'000), issuer, alice); + env.close(); + + PrettyAsset const asset = [&]() { + env(trust(alice, issuer["IOU"](1'000'000)), THISLINE); + env.close(); + return PrettyAsset(issuer["IOU"]); + }(); + + env(pay(issuer, alice, asset(100'000)), THISLINE); + env.close(); + + auto [tx, vaultKeylet] = vault.create({.owner = alice, .asset = asset}); + env(tx, THISLINE); + env.close(); + auto const le = env.le(vaultKeylet); + VaultInfo vaultInfo = [&]() { + if (BEAST_EXPECT(le)) + return VaultInfo{asset, vaultKeylet.key, le->at(sfAccount)}; + return VaultInfo{asset, {}, {}}; + }(); + if (vaultInfo.vaultID == uint256{}) + return; + + env(vault.deposit( + {.depositor = alice, + .id = vaultKeylet.key, + .amount = asset(50)}), + THISLINE); + env.close(); + + auto const brokerKeylet = + keylet::loanbroker(alice.id(), env.seq(alice)); + env(set(alice, vaultInfo.vaultID), THISLINE); + env.close(); + + Account const borrower{"borrower"}; + env.fund(XRP(1'000), borrower); + env(loan::set(borrower, brokerKeylet.key, asset(50).value()), + sig(sfCounterpartySignature, alice), + fee(env.current()->fees().base * 2), + THISLINE); + auto const broker = env.le(brokerKeylet); + if (!BEAST_EXPECT(broker)) + return; + + BEAST_EXPECT(broker->at(sfDebtTotal) == 50); + auto debtTotal = broker->at(sfDebtTotal); + + auto tx2 = set(alice, vaultInfo.vaultID); + tx2[sfLoanBrokerID] = to_string(brokerKeylet.key); + tx2[sfDebtMaximum] = debtTotal - 1; + env(tx2, ter(tecLIMIT_EXCEEDED), THISLINE); + + tx2[sfDebtMaximum] = debtTotal + 1; + env(tx2, ter(tesSUCCESS), THISLINE); + + tx2[sfDebtMaximum] = 0; + env(tx2, ter(tesSUCCESS), THISLINE); + } + public: void run() override { + testLoanBrokerSetDebtMaximum(); testLoanBrokerCoverDepositNullVault(); testDisabled(); diff --git a/src/xrpld/app/tx/detail/LoanBrokerSet.cpp b/src/xrpld/app/tx/detail/LoanBrokerSet.cpp index 7b12a6cf39..a3ac785674 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerSet.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerSet.cpp @@ -89,6 +89,18 @@ LoanBrokerSet::preclaim(PreclaimContext const& ctx) JLOG(ctx.j.warn()) << "Account is not the owner of the LoanBroker."; return tecNO_PERMISSION; } + + if (auto const debtMax = tx[~sfDebtMaximum]) + { + // Can't reduce the debt maximum below the current total debt + auto const currentDebtTotal = sleBroker->at(sfDebtTotal); + if (*debtMax != 0 && *debtMax < currentDebtTotal) + { + JLOG(ctx.j.warn()) + << "Cannot reduce DebtMaximum below current DebtTotal."; + return tecLIMIT_EXCEEDED; + } + } } else {