mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-26 23:19:07 +00:00
Improve test coverage
This commit is contained in:
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user