1#include <test/app/lending/LoanTestBase.h>
2#include <test/jtx/Account.h>
3#include <test/jtx/Env.h>
4#include <test/jtx/TestHelpers.h>
5#include <test/jtx/amount.h>
6#include <test/jtx/envconfig.h>
7#include <test/jtx/fee.h>
8#include <test/jtx/flags.h>
9#include <test/jtx/mpt.h>
10#include <test/jtx/pay.h>
11#include <test/jtx/sponsor.h>
12#include <test/jtx/ter.h>
13#include <test/jtx/trust.h>
14#include <test/jtx/txflags.h>
15#include <test/jtx/vault.h>
17#include <xrpl/basics/Number.h>
18#include <xrpl/basics/base_uint.h>
19#include <xrpl/basics/strHex.h>
20#include <xrpl/beast/unit_test/suite.h>
21#include <xrpl/beast/utility/Journal.h>
22#include <xrpl/beast/utility/Zero.h>
23#include <xrpl/json/json_value.h>
24#include <xrpl/json/to_string.h>
25#include <xrpl/ledger/helpers/AccountRootHelpers.h>
26#include <xrpl/protocol/Feature.h>
27#include <xrpl/protocol/Indexes.h>
28#include <xrpl/protocol/Issue.h>
29#include <xrpl/protocol/Protocol.h>
30#include <xrpl/protocol/SField.h>
31#include <xrpl/protocol/STTx.h>
32#include <xrpl/protocol/SeqProxy.h>
33#include <xrpl/protocol/TER.h>
34#include <xrpl/protocol/TxFlags.h>
35#include <xrpl/protocol/jss.h>
36#include <xrpl/tx/transactors/lending/LoanSet.h>
56 Env env(*
this, features);
60 env.
fund(
XRP(10000), alice, bob);
64 using namespace std::chrono_literals;
83 env(manage(alice, loanKeylet.key, tfLoanImpair),
Ter(
temDISABLED));
87 failAll(
all_ - featureMPTokensV1);
88 failAll(
all_ - featureSingleAssetVault - featureLendingProtocol);
89 failAll(
all_ - featureSingleAssetVault);
90 failAll(
all_ - featureLendingProtocol);
100 using namespace loan;
101 Account const lender{
"lender"};
102 Account const issuer{
"issuer"};
103 Account const borrower{
"borrower"};
105 auto const iou = issuer[
"IOU"];
107 auto testWrapper = [&](
auto&&
test) {
110 env(
trust(lender, iou(10'000'000)));
111 env(
pay(issuer, lender, iou(5'000'000)));
115 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
116 Number const debtMaximumRequest = brokerInfo.
asset(1'000).value();
117 test(env, brokerInfo, loanSetFee, debtMaximumRequest);
121 testWrapper([&](
Env& env,
124 Number const& debtMaximumRequest) {
127 env(
set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
129 Sig(sfCounterpartySignature, lender),
138 env(
set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
139 Sig(sfCounterpartySignature, lender),
145 env(
set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
146 Sig(sfCounterpartySignature, lender),
152 env(
set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
153 Sig(sfCounterpartySignature, lender),
154 kPaymentInterval(120),
161 auto jv =
set(borrower,
UInt256{}, debtMaximumRequest);
165 jv[sfLoanBrokerID] = id;
167 Sig(sfCounterpartySignature, lender),
184 JTx const tx = env.
jt(
185 set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
186 Sig(sfCounterpartySignature, lender),
189 auto counterpartySig = local.
getFieldObject(sfCounterpartySignature);
190 auto badPubKey = counterpartySig.
getFieldVL(sfSigningPubKey);
191 badPubKey[20] ^= 0xAA;
192 counterpartySig.setFieldVL(sfSigningPubKey, badPubKey);
196 auto res = env.
rpc(
"json",
"submit",
to_string(jvResult))[
"result"];
198 res[jss::error] ==
"invalidTransaction" &&
199 res[jss::error_exception] ==
200 "fails local checks: Counterparty: Invalid signature.");
204 testWrapper([&](
Env& env,
207 Number const& debtMaximumRequest) {
211 env(
fclear(issuer, asfDefaultRipple));
213 env(
set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
214 Sig(sfCounterpartySignature, lender),
220 testWrapper([&](
Env& env,
223 Number const& debtMaximumRequest) {
226 env(
pay(borrower, issuer, amt));
229 env(
set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
230 Sig(sfCounterpartySignature, lender),
235 env(
pay(issuer, borrower, amt));
236 env(
fset(issuer, asfGlobalFreeze));
239 env(
set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
240 Sig(sfCounterpartySignature, lender),
251 using namespace loan;
268 using namespace loan;
285 using namespace loan;
286 Account const lender{
"lender"};
287 Account const issuer{
"issuer"};
288 Account const borrower{
"borrower"};
289 auto const iou = issuer[
"IOU"];
293 env.
fund(
XRP(1'000), lender, issuer, borrower);
294 env(
trust(lender, iou(10'000'000)));
295 env(
pay(issuer, lender, iou(5'000'000)));
298 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
299 STAmount const debtMaximumRequest = brokerInfo.
asset(1'000).value();
301 env(
set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
302 Sig(sfCounterpartySignature, lender),
308 auto const loanKeylet =
311 env(
fset(issuer, asfGlobalFreeze));
315 env(
pay(borrower, loanKeylet.key, debtMaximumRequest),
Ter(
tecFROZEN));
318 env(
fclear(issuer, asfGlobalFreeze));
323 BEAST_EXPECT(brokerSle))
325 return Account{
"pseudo", brokerSle->at(sfAccount)};
334 env(
trust(issuer, lender[
"IOU"](1'000), lender, tfSetFreeze | tfSetDeepFreeze));
336 issuer, (*pseudoBroker)[
"IOU"](1'000), *pseudoBroker, tfSetFreeze | tfSetDeepFreeze));
340 env(
pay(borrower, loanKeylet.key, debtMaximumRequest),
Ter(
tecFROZEN));
344 env(
trust(issuer, lender[
"IOU"](1'000), tfClearFreeze | tfClearDeepFreeze));
355 env(
pay(borrower, loanKeylet.key, debtMaximumRequest, tfLoanLatePayment));
361 env(
pay(borrower, loanKeylet.key, debtMaximumRequest),
Ter(
tecKILLED));
367 testcase(
"Require Auth - Implicit Pseudo-account authorization");
369 using namespace loan;
370 Account const lender{
"lender"};
371 Account const issuer{
"issuer"};
372 Account const borrower{
"borrower"};
375 env.
fund(
XRP(100'000), issuer, lender, borrower);
381 .holders = {lender, borrower},
382 .flags =
kMptDexFlags | tfMPTRequireAuth | tfMPTCanClawback | tfMPTCanLock,
386 env(
pay(issuer, lender, asset(5'000'000)));
389 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
390 STAmount const debtMaximumRequest = brokerInfo.
asset(1'000).value();
392 auto forUnauthAuth = [&](
auto&& doTx) {
393 for (
auto const flag : {tfMPTUnauthorize, 0u})
395 asset.authorize({.account = issuer, .holder = borrower, .flags = flag});
405 env(
set(borrower, brokerInfo.
brokerID, debtMaximumRequest),
406 Sig(sfCounterpartySignature, lender),
412 auto const loanKeylet =
418 env(
pay(borrower, loanKeylet.key, debtMaximumRequest), err);
425 testcase(
"RIPD-4125 - overpayment");
429 Account const issuer(
"issuer");
430 Account const lender(
"lender");
431 Account const borrower(
"borrower");
434 .vaultDeposit = 100'000,
442 .principalRequest =
Number{200000, -6},
447 .flags = tfLoanOverpayment,
455 createLoan(env, assetType, brokerParams, loanParams, issuer, lender, borrower);
457 if (BEAST_EXPECT(loanResult); !loanResult.has_value())
460 auto broker = std::get<BrokerInfo>(*loanResult);
461 auto loanKeylet = std::get<Keylet>(*loanResult);
462 auto pseudoAcct = std::get<Account>(*loanResult);
464 VerifyLoanStatus const verifyLoanStatus(env, broker, pseudoAcct, loanKeylet);
471 STAmount{broker.asset, state.periodicPayment * 3 / 2 + 1},
476 .showStepBalances =
false,
477 .validateBalances =
true,
496 testcase <<
"Wrong Max Debt Behavior";
499 using namespace std::chrono_literals;
500 Env env(*
this, features);
502 Account const issuer{
"issuer"};
503 Account const lender{
"lender"};
514 BEAST_EXPECT(brokerSle))
516 BEAST_EXPECT(brokerSle->at(sfDebtMaximum) == 0);
519 using namespace loan;
521 auto const loanSetFee =
Fee(env.
current()->fees().base * 2);
522 Number const principalRequest{1, 3};
527 auto const createJson = env.
json(
529 Sig(sfCounterpartySignature, lender),
546 testcase(
"LoanBrokerSet requires closed-ended vault under LP V1.1");
549 Account const owner{
"lp11_owner"};
554 Env env(*
this, features);
558 Vault const vault{env};
559 auto [tx, vaultKeylet] = vault.create({.owner = owner, .asset =
xrpIssue()});
562 env(vault.deposit({.depositor = owner, .id = vaultKeylet.key, .amount = XRP(100)}));
565 auto const brokerKeylet =
579 Ter(*updateExpected));
618 {fixCleanup3_1_3, fixCleanup3_2_0, featureMPTokensV2},
all_))
TestcaseT testcase
Memberspace for declaring test cases.
static constexpr std::uint32_t kDefaultGracePeriod
Number is a floating point type that can represent a wide range of values.
Blob getFieldVL(SField const &field) const
Serializer getSerializer() const
void setFieldObject(SField const &field, STObject const &v)
STObject getFieldObject(SField const &field) const
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
Slice slice() const noexcept
Shared base for the Loan*_test family under src/test/app/lending/.
BrokerInfo createVaultAndBroker(jtx::Env &env, jtx::PrettyAsset const &asset, jtx::Account const &lender, BrokerParameters const ¶ms=BrokerParameters::defaults())
LoanState getCurrentState(jtx::Env const &env, BrokerInfo const &broker, Keylet const &loanKeylet)
Get the state without checking anything.
void makeLoanPayments(jtx::Env &env, BrokerInfo const &broker, LoanParameters const &loanParams, Keylet const &loanKeylet, VerifyLoanStatus const &verifyLoanStatus, jtx::Account const &issuer, jtx::Account const &lender, jtx::Account const &borrower, PaymentParameters const &paymentParams=PaymentParameters::defaults())
std::optional< std::tuple< BrokerInfo, Keylet, jtx::Account > > createLoan(jtx::Env &env, AssetType assetType, BrokerParameters const &brokerParams, LoanParameters const &loanParams, jtx::Account const &issuer, jtx::Account const &lender, jtx::Account const &borrower)
void testInvalidLoanPay()
void testInvalidLoanDelete()
void runAmendmentSensitive(FeatureBitset features)
void run() override
Runs the suite.
void testWrongMaxDebtBehavior(FeatureBitset features)
void testInvalidLoanManage()
void testInvalidLoanSet(VaultKind vaultKind)
void testLoanBrokerRequiresClosedEndedVault()
void runAmendmentIndependent()
Immutable cryptographic account descriptor.
AccountID id() const
Returns the Account ID.
A transaction testing environment.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
json::Value json(JsonValue &&jv, FN const &... fN)
Create JSON from parameters.
SLE::const_pointer le(Account const &account) const
Return an account root.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
JTx jt(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
beast::Journal const journal
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
Set the regular signature on a JTx.
Set the expected result code for a JTx The test will fail if the code doesn't match.
Keylet computation functions.
Keylet loanBroker(AccountID const &owner, SeqProxy const &seq) noexcept
Keylet loan(UInt256 const &loanBrokerID, SeqProxy const &loanSeq) noexcept
json::Value set(AccountID const &account, UInt256 const &vaultId, uint32_t flags)
json::Value pay(AccountID const &account, UInt256 const &loanID, STAmount const &amount, std::uint32_t flags)
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
std::vector< FeatureBitset > amendmentCombinations(std::initializer_list< UInt256 > features, FeatureBitset seed)
Returns all 2^N permutations of a seed FeatureBitset with each subset of the given features excluded.
XrpT const XRP
Converts to XRP Issue or STAmount.
std::unique_ptr< Config > makeConfig(std::map< std::string, std::string > extraTxQ={}, std::map< std::string, std::string > extraVoting={})
json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
std::array< Account, 1+sizeof...(Args)> noripple(Account const &account, Args const &... args)
Designate accounts as no-ripple in Env::fund.
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
static STAmount accountReserve(jtx::Env &env, std::uint32_t count=1)
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
constexpr FlagValue spfSponsorFee
bool set(T &target, std::string const &name, Section const §ion)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
std::string strHex(FwdIt begin, FwdIt end)
static bool authorized(Port const &port, std::map< std::string, std::string > const &h)
TenthBips< std::uint32_t > TenthBips32
TenthBips< std::uint16_t > TenthBips16
std::string to_string(BaseUInt< Bits, Tag > const &a)
TERSubset< CanCvtToTER > TER
@ tecINSUFFICIENT_RESERVE
constexpr FlagValue spfSponsorReserve
constexpr FlagValue tfFullyCanonicalSig
Helper class to compare the expected state of a loan and loan broker against the data in the ledger.
Execution context for applying a JSON transaction.
std::shared_ptr< STTx const > stx