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