Skip to content

Commit 9b944ee

Browse files
authored
refactor: Split LoanInvariant into LoanBrokerInvariant and LoanInvariant (#6674)
1 parent 509677a commit 9b944ee

File tree

5 files changed

+250
-229
lines changed

5 files changed

+250
-229
lines changed

include/xrpl/tx/invariants/InvariantCheck.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <xrpl/protocol/TER.h>
88
#include <xrpl/tx/invariants/AMMInvariant.h>
99
#include <xrpl/tx/invariants/FreezeInvariant.h>
10+
#include <xrpl/tx/invariants/LoanBrokerInvariant.h>
1011
#include <xrpl/tx/invariants/LoanInvariant.h>
1112
#include <xrpl/tx/invariants/MPTInvariant.h>
1213
#include <xrpl/tx/invariants/NFTInvariant.h>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#pragma once
2+
3+
#include <xrpl/basics/base_uint.h>
4+
#include <xrpl/beast/utility/Journal.h>
5+
#include <xrpl/ledger/ReadView.h>
6+
#include <xrpl/protocol/STTx.h>
7+
#include <xrpl/protocol/TER.h>
8+
9+
#include <map>
10+
#include <vector>
11+
12+
namespace xrpl {
13+
14+
/**
15+
* @brief Invariants: Loan brokers are internally consistent
16+
*
17+
* 1. If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most one
18+
* node (the root), which will only hold entries for `RippleState` or
19+
* `MPToken` objects.
20+
*
21+
*/
22+
class ValidLoanBroker
23+
{
24+
// Not all of these elements will necessarily be populated. Remaining items
25+
// will be looked up as needed.
26+
struct BrokerInfo
27+
{
28+
SLE::const_pointer brokerBefore = nullptr;
29+
// After is used for most of the checks, except
30+
// those that check changed values.
31+
SLE::const_pointer brokerAfter = nullptr;
32+
};
33+
// Collect all the LoanBrokers found directly or indirectly through
34+
// pseudo-accounts. Key is the brokerID / index. It will be used to find the
35+
// LoanBroker object if brokerBefore and brokerAfter are nullptr
36+
std::map<uint256, BrokerInfo> brokers_;
37+
// Collect all the modified trust lines. Their high and low accounts will be
38+
// loaded to look for LoanBroker pseudo-accounts.
39+
std::vector<SLE::const_pointer> lines_;
40+
// Collect all the modified MPTokens. Their accounts will be loaded to look
41+
// for LoanBroker pseudo-accounts.
42+
std::vector<SLE::const_pointer> mpts_;
43+
44+
static bool
45+
goodZeroDirectory(ReadView const& view, SLE::const_ref dir, beast::Journal const& j);
46+
47+
public:
48+
void
49+
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
50+
51+
bool
52+
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
53+
};
54+
55+
} // namespace xrpl

include/xrpl/tx/invariants/LoanInvariant.h

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,14 @@
11
#pragma once
22

3-
#include <xrpl/basics/base_uint.h>
43
#include <xrpl/beast/utility/Journal.h>
54
#include <xrpl/ledger/ReadView.h>
65
#include <xrpl/protocol/STTx.h>
76
#include <xrpl/protocol/TER.h>
87

9-
#include <map>
108
#include <vector>
119

1210
namespace xrpl {
1311

14-
/**
15-
* @brief Invariants: Loan brokers are internally consistent
16-
*
17-
* 1. If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most one
18-
* node (the root), which will only hold entries for `RippleState` or
19-
* `MPToken` objects.
20-
*
21-
*/
22-
class ValidLoanBroker
23-
{
24-
// Not all of these elements will necessarily be populated. Remaining items
25-
// will be looked up as needed.
26-
struct BrokerInfo
27-
{
28-
SLE::const_pointer brokerBefore = nullptr;
29-
// After is used for most of the checks, except
30-
// those that check changed values.
31-
SLE::const_pointer brokerAfter = nullptr;
32-
};
33-
// Collect all the LoanBrokers found directly or indirectly through
34-
// pseudo-accounts. Key is the brokerID / index. It will be used to find the
35-
// LoanBroker object if brokerBefore and brokerAfter are nullptr
36-
std::map<uint256, BrokerInfo> brokers_;
37-
// Collect all the modified trust lines. Their high and low accounts will be
38-
// loaded to look for LoanBroker pseudo-accounts.
39-
std::vector<SLE::const_pointer> lines_;
40-
// Collect all the modified MPTokens. Their accounts will be loaded to look
41-
// for LoanBroker pseudo-accounts.
42-
std::vector<SLE::const_pointer> mpts_;
43-
44-
static bool
45-
goodZeroDirectory(ReadView const& view, SLE::const_ref dir, beast::Journal const& j);
46-
47-
public:
48-
void
49-
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
50-
51-
bool
52-
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
53-
};
54-
5512
/**
5613
* @brief Invariants: Loans are internally consistent
5714
*
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
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+
12+
namespace xrpl {
13+
14+
void
15+
ValidLoanBroker::visitEntry(
16+
bool isDelete,
17+
std::shared_ptr<SLE const> const& before,
18+
std::shared_ptr<SLE const> const& after)
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+
45+
bool
46+
ValidLoanBroker::goodZeroDirectory(
47+
ReadView const& view,
48+
SLE::const_ref dir,
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+
86+
bool
87+
ValidLoanBroker::finalize(
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+
if (after->at(sfCoverAvailable) < accountHolds(
179+
view,
180+
after->at(sfAccount),
181+
vaultAsset,
182+
FreezeHandling::fhIGNORE_FREEZE,
183+
AuthHandling::ahIGNORE_AUTH,
184+
j))
185+
{
186+
JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available "
187+
"is less than pseudo-account asset balance";
188+
return false;
189+
}
190+
}
191+
return true;
192+
}
193+
194+
} // namespace xrpl

0 commit comments

Comments
 (0)