mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-30 02:20:39 +00:00
Add new invariants
This commit is contained in:
@@ -37,9 +37,13 @@ namespace xrpl {
|
||||
* must be absent.
|
||||
* c. Either `Borrower`, or both `Counterparty` and `CounterpartySignature`,
|
||||
* must be present.
|
||||
* d. If `Borrower` is present, it must differ from the submitting
|
||||
* `Account`.
|
||||
* 6. A `LoanSet` that creates a loan must set `lsfLoanPending` if and only if
|
||||
* `Borrower` is present and `CounterpartySignature` is absent.
|
||||
* 7. A `LoanSet` that creates a loan must set the loan's `Borrower`.
|
||||
* Additionally, a pending loan's `StartDate` must be in the future (so it is
|
||||
* still in the future when `LoanAccept` finalizes it).
|
||||
* 8. A pending loan (`lsfLoanPending` set) must not be linked into the
|
||||
* borrower's directory (`OwnerNode` absent), and a non-pending loan must
|
||||
* be linked (`OwnerNode` present).
|
||||
|
||||
@@ -182,6 +182,14 @@ ValidLoan::finalize(
|
||||
"CounterpartySignature";
|
||||
return false;
|
||||
}
|
||||
// In the two-step flow the named Borrower must be a different
|
||||
// account from the one submitting the LoanSet.
|
||||
if (hasBorrower && tx.getAccountID(sfBorrower) == tx.getAccountID(sfAccount))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: LoanSet Borrower is the "
|
||||
"submitting account";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool const shouldPend = hasBorrower && !hasCounterpartySig;
|
||||
if (shouldPend != after->isFlag(lsfLoanPending))
|
||||
@@ -191,6 +199,18 @@ ValidLoan::finalize(
|
||||
return false;
|
||||
}
|
||||
|
||||
// A pending loan (the two-step flow) is accepted in a later ledger,
|
||||
// so its StartDate must be strictly in the future at creation to
|
||||
// remain in the future when LoanAccept finalizes it.
|
||||
if (shouldPend &&
|
||||
after->getFieldU32(sfStartDate) <=
|
||||
view.parentCloseTime().time_since_epoch().count())
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: LoanSet created a pending "
|
||||
"Loan whose StartDate is not in the future";
|
||||
return false;
|
||||
}
|
||||
|
||||
// A created loan must always record its Borrower.
|
||||
if (!after->isFieldPresent(sfBorrower))
|
||||
{
|
||||
|
||||
@@ -4123,8 +4123,9 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
// Creates a fully paid-off loan, optionally flagged pending, so the
|
||||
// earlier loan checks pass and only the LoanSet creation checks
|
||||
// fire.
|
||||
auto const createLoan = [](bool pending) {
|
||||
return [pending](Account const& a1, Account const&, ApplyContext& ac) {
|
||||
auto const createLoan = [](bool pending,
|
||||
std::optional<std::uint32_t> startDate = std::nullopt) {
|
||||
return [pending, startDate](Account const& a1, Account const&, ApplyContext& ac) {
|
||||
auto const vaultKeylet = keylet::vault(a1.id(), ac.view().seq());
|
||||
auto const loanKeylet = keylet::loan(vaultKeylet.key, 1);
|
||||
auto sleLoan = std::make_shared<SLE>(loanKeylet);
|
||||
@@ -4135,6 +4136,8 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
sleLoan->setFieldU32(sfPaymentRemaining, 0);
|
||||
if (pending)
|
||||
sleLoan->setFlag(lsfLoanPending);
|
||||
if (startDate)
|
||||
sleLoan->setFieldU32(sfStartDate, *startDate);
|
||||
ac.view().insert(sleLoan);
|
||||
return true;
|
||||
};
|
||||
@@ -4228,6 +4231,35 @@ class Invariants_test : public beast::unit_test::Suite
|
||||
tx.makeFieldPresent(sfCounterpartySignature);
|
||||
}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED});
|
||||
|
||||
// In the two-step flow the named Borrower must differ from the
|
||||
// submitting account; a LoanSet where they are equal is a violation.
|
||||
doInvariantCheck(
|
||||
Env{*this, defaultAmendments() | featureLendingProtocolV1_1},
|
||||
{"LoanSet Borrower is the submitting account"},
|
||||
createLoan(false),
|
||||
XRPAmount{},
|
||||
STTx{
|
||||
ttLOAN_SET,
|
||||
[&](STObject& tx) {
|
||||
tx.setAccountID(sfAccount, borrower.id());
|
||||
tx.setAccountID(sfBorrower, borrower.id());
|
||||
}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED});
|
||||
|
||||
// A pending loan created by the two-step flow must have a StartDate
|
||||
// in the future; creating one with a past StartDate is a violation.
|
||||
// createLoan(true, 1) makes a pending loan whose StartDate (1) is in
|
||||
// the past, and the Borrower-only LoanSet keeps the pending flag
|
||||
// consistent so this check is reached.
|
||||
doInvariantCheck(
|
||||
Env{*this, defaultAmendments() | featureLendingProtocolV1_1},
|
||||
{"LoanSet created a pending Loan whose StartDate is not in the "
|
||||
"future"},
|
||||
createLoan(true, 1u),
|
||||
XRPAmount{},
|
||||
STTx{ttLOAN_SET, [&](STObject& tx) { tx.setAccountID(sfBorrower, borrower.id()); }},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED});
|
||||
}
|
||||
|
||||
// Loan interest due (total value less principal and management fee)
|
||||
|
||||
Reference in New Issue
Block a user