xrpld
Loading...
Searching...
No Matches
LoanBrokerInvariant.cpp
1#include <xrpl/tx/invariants/LoanBrokerInvariant.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/beast/utility/Journal.h>
5#include <xrpl/ledger/ReadView.h>
6#include <xrpl/ledger/helpers/TokenHelpers.h>
7#include <xrpl/protocol/Feature.h>
8#include <xrpl/protocol/Indexes.h>
9#include <xrpl/protocol/LedgerFormats.h>
10#include <xrpl/protocol/SField.h>
11#include <xrpl/protocol/STLedgerEntry.h>
12#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
13#include <xrpl/protocol/STTx.h>
14#include <xrpl/protocol/TER.h>
15#include <xrpl/protocol/TxFormats.h>
16#include <xrpl/protocol/XRPAmount.h>
17
18#include <algorithm>
19
20namespace xrpl {
21
22void
24{
25 if (after)
26 {
27 if (after->getType() == ltLOAN_BROKER)
28 {
29 auto& broker = brokers_[after->key()];
30 broker.brokerBefore = before;
31 broker.brokerAfter = after;
32 }
33 else if (after->getType() == ltACCOUNT_ROOT && after->isFieldPresent(sfLoanBrokerID))
34 {
35 auto const& loanBrokerID = after->at(sfLoanBrokerID);
36 // create an entry if one doesn't already exist
37 brokers_.emplace(loanBrokerID, BrokerInfo{});
38 }
39 else if (after->getType() == ltRIPPLE_STATE)
40 {
41 lines_.emplace_back(after);
42 }
43 else if (after->getType() == ltMPTOKEN)
44 {
45 mpts_.emplace_back(after);
46 }
47 }
48}
49
50bool
52 ReadView const& view,
54 beast::Journal const& j)
55{
56 auto const next = dir->at(~sfIndexNext);
57 auto const prev = dir->at(~sfIndexPrevious);
58 if ((prev && (*prev != 0u)) || (next && (*next != 0u)))
59 {
60 JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero "
61 "OwnerCount has multiple directory pages";
62 return false;
63 }
64 auto indexes = dir->getFieldV256(sfIndexes);
65 if (indexes.size() > 1)
66 {
67 JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero "
68 "OwnerCount has multiple indexes in the Directory root";
69 return false;
70 }
71 if (indexes.size() == 1)
72 {
73 auto const index = indexes.value().front();
74 auto const sle = view.read(keylet::unchecked(index));
75 if (!sle)
76 {
77 JLOG(j.fatal()) << "Invariant failed: Loan Broker directory corrupt";
78 return false;
79 }
80 if (sle->getType() != ltRIPPLE_STATE && sle->getType() != ltMPTOKEN)
81 {
82 JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero "
83 "OwnerCount has an unexpected entry in the directory";
84 return false;
85 }
86 }
87
88 return true;
89}
90
91bool
93 STTx const& tx,
94 TER const,
95 XRPAmount const,
96 ReadView const& view,
97 beast::Journal const& j)
98{
99 // Loan Brokers will not exist on ledger if the Lending Protocol amendment
100 // is not enabled, so there's no need to check it.
101
102 for (auto const& line : lines_)
103 {
104 for (auto const& field : {&sfLowLimit, &sfHighLimit})
105 {
106 auto const account = view.read(keylet::account(line->at(*field).getIssuer()));
107 // This Invariant doesn't know about the rules for Trust Lines, so
108 // if the account is missing, don't treat it as an error. This
109 // loop is only concerned with finding Broker pseudo-accounts
110 if (account && account->isFieldPresent(sfLoanBrokerID))
111 {
112 auto const& loanBrokerID = account->at(sfLoanBrokerID);
113 // create an entry if one doesn't already exist
114 brokers_.emplace(loanBrokerID, BrokerInfo{});
115 }
116 }
117 }
118 for (auto const& mpt : mpts_)
119 {
120 auto const account = view.read(keylet::account(mpt->at(sfAccount)));
121 // This Invariant doesn't know about the rules for MPTokens, so
122 // if the account is missing, don't treat is as an error. This
123 // loop is only concerned with finding Broker pseudo-accounts
124 if (account && account->isFieldPresent(sfLoanBrokerID))
125 {
126 auto const& loanBrokerID = account->at(sfLoanBrokerID);
127 // create an entry if one doesn't already exist
128 brokers_.emplace(loanBrokerID, BrokerInfo{});
129 }
130 }
131
132 return std::ranges::all_of(brokers_, [&](auto const& entry) {
133 auto const& [brokerID, broker] = entry;
134 auto const& after =
135 broker.brokerAfter ? broker.brokerAfter : view.read(keylet::loanBroker(brokerID));
136
137 if (!after)
138 {
139 JLOG(j.fatal()) << "Invariant failed: Loan Broker missing";
140 return false;
141 }
142
143 auto const& before = broker.brokerBefore;
144
145 // https://github.com/Tapanito/XRPL-Standards/blob/xls-66-lending-protocol/XLS-0066d-lending-protocol/README.md#3123-invariants
146 // If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most
147 // one node (the root), which will only hold entries for `RippleState`
148 // or `MPToken` objects.
149 if (after->at(sfOwnerCount) == 0)
150 {
151 auto const dir = view.read(keylet::ownerDir(after->at(sfAccount)));
152 if (dir)
153 {
154 if (!goodZeroDirectory(view, dir, j))
155 {
156 return false;
157 }
158 }
159 }
160 if (before && before->at(sfLoanSequence) > after->at(sfLoanSequence))
161 {
162 JLOG(j.fatal()) << "Invariant failed: Loan Broker sequence number "
163 "decreased";
164 return false;
165 }
166 if (after->at(sfDebtTotal) < 0)
167 {
168 JLOG(j.fatal()) << "Invariant failed: Loan Broker debt total is negative";
169 return false;
170 }
171 if (after->at(sfCoverAvailable) < 0)
172 {
173 JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available is negative";
174 return false;
175 }
176 auto const vault = view.read(keylet::vault(after->at(sfVaultID)));
177 if (!vault)
178 {
179 JLOG(j.fatal()) << "Invariant failed: Loan Broker vault ID is invalid";
180 return false;
181 }
182 auto const& vaultAsset = vault->at(sfAsset);
183 auto const pseudoBalance = accountHolds(
184 view,
185 after->at(sfAccount),
186 vaultAsset,
189 j);
190 if (after->at(sfCoverAvailable) < pseudoBalance)
191 {
192 JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available "
193 "is less than pseudo-account asset balance";
194 return false;
195 }
196
197 if (view.rules().enabled(fixCleanup3_1_3))
198 {
199 // Don't check the balance when LoanBroker is deleted,
200 // sfCoverAvailable is not zeroed
201 if (tx.getTxnType() != ttLOAN_BROKER_DELETE &&
202 after->at(sfCoverAvailable) > pseudoBalance)
203 {
204 JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available is greater "
205 "than pseudo-account asset balance";
206 return false;
207 }
208 }
209 return true;
210 });
211}
212
213} // namespace xrpl
T all_of(T... args)
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 Rules const & rules() const =0
Returns the tx processing rules.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:180
std::shared_ptr< STLedgerEntry const > const & const_ref
TxType getTxnType() const
Definition STTx.h:226
std::map< uint256, BrokerInfo > brokers_
static bool goodZeroDirectory(ReadView const &view, SLE::const_ref dir, beast::Journal const &j)
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
std::vector< SLE::const_pointer > mpts_
std::vector< SLE::const_pointer > lines_
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
Keylet unchecked(uint256 const &key) noexcept
Any ledger entry.
Definition Indexes.cpp:367
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:373
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
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:198
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:572
TERSubset< CanCvtToTER > TER
Definition TER.h:647
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=SpendableHandling::SimpleBalance)