diff --git a/src/libxrpl/ledger/helpers/LendingHelpers.cpp b/src/libxrpl/ledger/helpers/LendingHelpers.cpp index 61ad7d8dbb..67a0f88e7a 100644 --- a/src/libxrpl/ledger/helpers/LendingHelpers.cpp +++ b/src/libxrpl/ledger/helpers/LendingHelpers.cpp @@ -39,10 +39,15 @@ canApplyToBrokerCover( beast::Journal j, std::string_view logPrefix) { + XRPL_ASSERT( + sleBroker && sleBroker->getType() == ltLOAN_BROKER, + "xrpl::canApplyToBrokerCover : valid LoanBroker sle"); + XRPL_ASSERT( + vaultAsset.getIssuer() == amount.getIssuer() && amount > beast::kZERO, + "xrpl::canApplyToBrokerCover : valid LoanBroker sle"); + if (!view.rules().enabled(fixCleanup3_2_0)) return tesSUCCESS; - if (amount == beast::kZERO) - return tesSUCCESS; int const coverScale = scale(sleBroker->at(sfCoverAvailable), vaultAsset); if (amount.isZeroAtScale(coverScale)) diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp index 010830dacb..2c46ca3ffb 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp @@ -73,11 +73,6 @@ LoanBrokerCoverDeposit::preclaim(PreclaimContext const& ctx) if (amount.asset() != vaultAsset) return tecWRONG_ASSET; - // Helper handles both IOU and MPT correctly without explicit branching. - if (auto const ret = canApplyToBrokerCover( - ctx.view, sleBroker, vaultAsset, amount, ctx.j, "LoanBrokerCoverDeposit")) - return ret; - auto const pseudoAccountID = sleBroker->at(sfAccount); // Cannot transfer a non-transferable Asset if (auto const ret = canTransfer(ctx.view, vaultAsset, account, pseudoAccountID)) @@ -111,8 +106,6 @@ LoanBrokerCoverDeposit::doApply() auto const& tx = ctx_.tx; auto const brokerID = tx[sfLoanBrokerID]; - auto const amount = tx[sfAmount]; - auto broker = view().peek(keylet::loanbroker(brokerID)); if (!broker) return tecINTERNAL; // LCOV_EXCL_LINE @@ -122,9 +115,25 @@ LoanBrokerCoverDeposit::doApply() return tecINTERNAL; // LCOV_EXCL_LINE auto const vaultAsset = vault->at(sfAsset); - auto const brokerPseudoID = broker->at(sfAccount); + bool const fix320Enabled = view().rules().enabled(fixCleanup3_2_0); + auto const amount = [&]() -> STAmount { + if (!fix320Enabled) + return tx[sfAmount]; + + return roundToScale( + tx[sfAmount], + scale(broker->at(sfCoverAvailable), vaultAsset), + Number::RoundingMode::Downward); + }(); + + if (fix320Enabled && amount == beast::kZERO) + { + JLOG(ctx_.journal.warn()) << "LoanBrokerCoverDeposit: deposit amount: " << tx[sfAmount] + << "is zero at loan broker scale"; + return tecPRECISION_LOSS; + } // Transfer assets from depositor to pseudo-account. if (auto ter = accountSend(view(), account_, brokerPseudoID, amount, j_, WaiveTransferFee::Yes)) return ter; diff --git a/src/test/app/LoanBroker_test.cpp b/src/test/app/LoanBroker_test.cpp index c6e9065d79..48a8555655 100644 --- a/src/test/app/LoanBroker_test.cpp +++ b/src/test/app/LoanBroker_test.cpp @@ -1896,6 +1896,29 @@ class LoanBroker_test : public beast::unit_test::Suite } } + { + testcase("Cover precision guard: Deposit"); + // Both cases succeed; post-fix the amount is rounded DOWN to + // cover scale first, so the delta differs from pre-fix + // Input: 1.8e-14 IOU (sub-scale at cover scale -14) + // Pre-fix: 10 + 1.8e-14 → round-to-nearest → + // 10.00000000000002 → delta 2e-14 + // Post-fix: roundToScale(1.8e-14, -14, Downward) = 1e-14; + // 10 + 1e-14 = 10.00000000000001 → delta 1e-14 + Env env{*this, features}; + auto const [brokerKeylet, iou] = setup(env); + PrettyAmount const subUlpAmt = iou(Number{18, -15}); + auto const coverBefore = env.le(brokerKeylet)->at(sfCoverAvailable); + env(coverDeposit(alice, brokerKeylet.key, subUlpAmt), Ter(tesSUCCESS)); + env.close(); + auto const brokerAfter = env.le(brokerKeylet); + if (!BEAST_EXPECT(brokerAfter)) + return; + + Number const delta = features[fixCleanup3_2_0] ? Number{1, -14} : Number{2, -14}; + BEAST_EXPECT(brokerAfter->at(sfCoverAvailable) - coverBefore == delta); + } + { testcase("Cover precision guard: Withdraw"); Env env{*this, features};