Improve test coverage

This commit is contained in:
JCW
2026-08-19 11:16:45 +01:00
parent 15fe434ae8
commit 108c032896

View File

@@ -3,18 +3,32 @@
#include <test/jtx/Account.h>
#include <test/jtx/Env.h>
#include <test/jtx/amount.h>
#include <test/jtx/vault.h>
#include <test/unit_test/SuiteJournal.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/OpenView.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/Units.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <cstdint>
#include <memory>
@@ -1871,6 +1885,93 @@ public:
}
}
// Covers the accountSendMulti failure branch of disburseLoan
// (the final `return ter;` in LendingHelpers.cpp). In production this
// line is unreachable: LoanSet::preclaim verifies
// Vault.AssetsAvailable >= principalRequested, and ValidVault keeps
// AssetsAvailable in sync with the vault pseudo-account's actual XRP
// holding. To reach it we drive the helper directly from a synthetic
// ApplyContext (same pattern as LoanBroker_test's
// testLoanBrokerCoverDepositNullVault), drain the pseudo-account's
// sfBalance on the scratch view, and observe disburseLoan surface the
// tec that accountSendMultiIOU returns for a native-asset transfer
// whose sender balance is insufficient. Bypassing LoanSet's own
// preclaim/doApply means the AssetsAvailable guard is skipped; the
// mutation lives on a cloned OpenView, so nothing commits back to the
// real ledger and no invariant fires.
void
testDisburseLoanTransferFailure()
{
testcase("disburseLoan: accountSendMulti failure surfaces the tec");
using namespace jtx;
Env env{*this};
Account const lender{"lender"};
Account const borrower{"borrower"};
env.fund(XRP(1'000'000), lender, borrower);
env.close();
// Standard XRP vault owned by the lender, with a deposit that
// funds the pseudo-account so the drain below is meaningful.
PrettyAsset const asset{xrpIssue(), 1};
Vault const vault{env};
auto const [createTx, vaultKeylet] = vault.create({.owner = lender, .asset = asset});
env(createTx);
env.close();
env(vault.deposit({.depositor = lender, .id = vaultKeylet.key, .amount = asset(100'000)}));
env.close();
auto const vaultSle0 = env.le(vaultKeylet);
if (!BEAST_EXPECT(vaultSle0))
return;
AccountID const vaultPseudo = vaultSle0->at(sfAccount);
Asset const vaultAsset = vaultSle0->at(sfAsset);
// Dummy STTx: disburseLoan does not inspect tx fields, but
// ApplyContext requires an STTx. Use a Payment (arbitrary type)
// signed by the lender so the account field is well-formed.
STTx const tx{ttPAYMENT, [&](STObject& obj) { obj.setAccountID(sfAccount, lender.id()); }};
// Clone the current ledger into a writable ApplyContext.
OpenView ov{*env.current()};
test::StreamSink sink{beast::Severity::Warning};
beast::Journal const jlog{sink};
ApplyContext ac{env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
auto borrowerSle = ac.view().peek(keylet::account(borrower.id()));
auto brokerOwnerSle = ac.view().peek(keylet::account(lender.id()));
auto pseudoSle = ac.view().peek(keylet::account(vaultPseudo));
if (!BEAST_EXPECT(borrowerSle && brokerOwnerSle && pseudoSle))
return;
// Drain the vault pseudo-account so accountSendMultiIOU's native
// branch (sfBalance < takeFromSender) returns tecFAILED_PROCESSING.
pseudoSle->setFieldAmount(sfBalance, STAmount(XRPAmount(0)));
ac.view().update(pseudoSle);
// originationFee > 0 also exercises the second addEmptyHolding leg
// (broker owner side). Both are no-ops for native XRP.
Number const originationFee{5'000};
Number const toBorrower{95'000};
auto viewContext = ac.getApplyViewContext();
TER const result = disburseLoan(
viewContext,
borrowerSle,
brokerOwnerSle,
vaultPseudo,
vaultAsset,
toBorrower,
originationFee,
borrower.id(),
lender.id(),
jlog);
BEAST_EXPECT(result == TER{tecFAILED_PROCESSING});
}
void
run() override
{
@@ -1906,6 +2007,8 @@ public:
testLoanOriginationExceedsVaultMaximumDispatcher();
testLoanVaultExposureDispatcher();
testLoanPaymentDeltasDispatcher();
testDisburseLoanTransferFailure();
}
};