xrpld
Loading...
Searching...
No Matches
LoanInvariant.cpp
1#include <xrpl/tx/invariants/LoanInvariant.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/beast/utility/Journal.h>
5#include <xrpl/beast/utility/Zero.h>
6#include <xrpl/ledger/ReadView.h>
7#include <xrpl/ledger/helpers/VaultHelpers.h>
8#include <xrpl/protocol/Indexes.h>
9#include <xrpl/protocol/LedgerFormats.h>
10#include <xrpl/protocol/Protocol.h>
11#include <xrpl/protocol/SField.h>
12#include <xrpl/protocol/STLedgerEntry.h>
13#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
14#include <xrpl/protocol/STTx.h>
15#include <xrpl/protocol/TER.h>
16#include <xrpl/protocol/XRPAmount.h>
17
18#include <cstdint>
19
20namespace xrpl {
21
22void
24{
25 if (after && after->getType() == ltLOAN)
26 {
27 loans_.emplace_back(before, after);
28 }
29}
30
31bool
33 STTx const& tx,
34 TER const result,
35 XRPAmount const,
36 ReadView const& view,
37 beast::Journal const& j)
38{
39 // Loans will not exist on ledger if the Lending Protocol amendment
40 // is not enabled, so there's no need to check it.
41
42 for (auto const& [before, after] : loans_)
43 {
44 // A closed-ended vault must not accept a loan whose final scheduled payment falls on or
45 // after the vault's RedemptionDate. This mirrors the LoanSet::preclaim gate and only fires
46 // on loan creation; once the loan exists, its StartDate / PaymentInterval are immutable and
47 // PaymentRemaining only decreases, so the bound is preserved.
48 if (!before && isTesSuccess(result))
49 {
50 auto const broker = view.read(keylet::loanBroker(after->at(sfLoanBrokerID)));
51 if (broker)
52 {
53 auto const vault = view.read(keylet::vault(broker->at(sfVaultID)));
54 // We don't check for LendingProtocolV1_1 amendment because a ClosedEnded Vault will
55 // not exist without the amendment enabled
56 if (vault && getVaultKind(vault) == VaultKind::ClosedEnded)
57 {
58 std::uint32_t const startDate = after->at(sfStartDate);
59 std::uint32_t const interval = after->at(sfPaymentInterval);
60 std::uint32_t const remaining = after->at(sfPaymentRemaining);
61 std::uint32_t const redemption = vault->at(sfRedemptionDate);
62 if (std::uint64_t{startDate} + (std::uint64_t{interval} * remaining) >=
63 redemption)
64 {
65 JLOG(j.fatal()) << "Invariant failed: closed-ended loan final payment "
66 "must precede RedemptionDate";
67 return false;
68 }
69 }
70 }
71 }
72
73 // https://github.com/Tapanito/XRPL-Standards/blob/xls-66-lending-protocol/XLS-0066d-lending-protocol/README.md#3223-invariants
74 // If `Loan.PaymentRemaining = 0` then the loan MUST be fully paid off
75 if (after->at(sfPaymentRemaining) == 0 &&
76 (after->at(sfTotalValueOutstanding) != beast::kZero ||
77 after->at(sfPrincipalOutstanding) != beast::kZero ||
78 after->at(sfManagementFeeOutstanding) != beast::kZero))
79 {
80 JLOG(j.fatal()) << "Invariant failed: Loan with zero payments "
81 "remaining has not been paid off";
82 return false;
83 }
84 // If `Loan.PaymentRemaining != 0` then the loan MUST NOT be fully paid
85 // off
86 if (after->at(sfPaymentRemaining) != 0 &&
87 after->at(sfTotalValueOutstanding) == beast::kZero &&
88 after->at(sfPrincipalOutstanding) == beast::kZero &&
89 after->at(sfManagementFeeOutstanding) == beast::kZero)
90 {
91 JLOG(j.fatal()) << "Invariant failed: Fully paid off Loan still has payments remaining";
92 return false;
93 }
94 if (before && (before->isFlag(lsfLoanOverpayment) != after->isFlag(lsfLoanOverpayment)))
95 {
96 JLOG(j.fatal()) << "Invariant failed: Loan Overpayment flag changed";
97 return false;
98 }
99 // Must not be negative - STNumber
100 for (auto const field :
101 {&sfLoanServiceFee,
102 &sfLatePaymentFee,
103 &sfClosePaymentFee,
104 &sfPrincipalOutstanding,
105 &sfTotalValueOutstanding,
106 &sfManagementFeeOutstanding})
107 {
108 if (after->at(*field) < 0)
109 {
110 JLOG(j.fatal()) << "Invariant failed: " << field->getName() << " is negative ";
111 return false;
112 }
113 }
114 // Must be positive - STNumber
115 for (auto const field : {
116 &sfPeriodicPayment,
117 })
118 {
119 if (after->at(*field) <= 0)
120 {
121 JLOG(j.fatal()) << "Invariant failed: " << field->getName()
122 << " is zero or negative ";
123 return false;
124 }
125 }
126 }
127 return true;
128}
129
130} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
Stream fatal() const
Definition Journal.h:368
A view into a ledger.
Definition ReadView.h:41
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
std::shared_ptr< STLedgerEntry const > const & const_ref
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
std::vector< std::pair< SLE::const_pointer, SLE::const_pointer > > loans_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
constexpr Zero kZero
Definition Zero.h:30
Keylet vault(AccountID const &owner, SeqProxy const &seq) noexcept
Definition Indexes.cpp:561
Keylet loanBroker(AccountID const &owner, SeqProxy const &seq) noexcept
Definition Indexes.cpp:567
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
VaultKind getVaultKind(SLE::const_ref vault)
Resolves the VaultKind of a vault SLE.
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:572
bool isTesSuccess(TER x) noexcept
Definition TER.h:676
TERSubset< CanCvtToTER > TER
Definition TER.h:647