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