xrpld
Loading...
Searching...
No Matches
LoanBrokerSet.cpp
1#include <xrpl/tx/transactors/lending/LoanBrokerSet.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Number.h>
5#include <xrpl/beast/utility/Zero.h>
6#include <xrpl/core/ServiceRegistry.h>
7#include <xrpl/ledger/View.h>
8#include <xrpl/ledger/helpers/AccountRootHelpers.h>
9#include <xrpl/ledger/helpers/LendingHelpers.h>
10#include <xrpl/ledger/helpers/TokenHelpers.h>
11#include <xrpl/ledger/helpers/VaultHelpers.h>
12#include <xrpl/protocol/Asset.h>
13#include <xrpl/protocol/Feature.h>
14#include <xrpl/protocol/Indexes.h>
15#include <xrpl/protocol/Protocol.h>
16#include <xrpl/protocol/SField.h>
17#include <xrpl/protocol/STLedgerEntry.h>
18#include <xrpl/protocol/STNumber.h>
19#include <xrpl/protocol/STTakesAsset.h>
20#include <xrpl/protocol/STTx.h>
21#include <xrpl/protocol/TER.h>
22#include <xrpl/protocol/XRPAmount.h>
23#include <xrpl/tx/Transactor.h>
24
25#include <memory>
26#include <vector>
27
28namespace xrpl {
29
30bool
35
38{
39 using namespace lending;
40
41 auto const& tx = ctx.tx;
42 if (auto const data = tx[~sfData];
43 data && !data->empty() && !validDataLength(tx[~sfData], kMaxDataPayloadLength))
44 return temINVALID;
45 if (!validNumericRange(tx[~sfManagementFeeRate], kMaxManagementFeeRate))
46 return temINVALID;
47 if (!validNumericRange(tx[~sfCoverRateMinimum], kMaxCoverRate))
48 return temINVALID;
49 if (!validNumericRange(tx[~sfCoverRateLiquidation], kMaxCoverRate))
50 return temINVALID;
51 if (!validNumericRange(tx[~sfDebtMaximum], Number(kMaxMpTokenAmount), Number(0)))
52 return temINVALID;
53
54 if (tx.isFieldPresent(sfLoanBrokerID))
55 {
56 // Fixed fields can not be specified if we're modifying an existing
57 // LoanBroker Object
58 if (tx.isFieldPresent(sfManagementFeeRate) || tx.isFieldPresent(sfCoverRateMinimum) ||
59 tx.isFieldPresent(sfCoverRateLiquidation))
60 return temINVALID;
61
62 if (tx[sfLoanBrokerID] == beast::kZero)
63 return temINVALID;
64 }
65
66 if (auto const vaultID = tx.at(~sfVaultID))
67 {
68 if (*vaultID == beast::kZero)
69 return temINVALID;
70 }
71
72 {
73 auto const minimumZero = tx[~sfCoverRateMinimum].value_or(0) == 0;
74 auto const liquidationZero = tx[~sfCoverRateLiquidation].value_or(0) == 0;
75 // Both must be zero or non-zero.
76 if (minimumZero != liquidationZero)
77 {
78 return temINVALID;
79 }
80 }
81
82 return tesSUCCESS;
83}
84
87{
88 static std::vector<OptionaledField<STNumber>> const kValueFields{~sfDebtMaximum};
89
90 return kValueFields;
91}
92
93TER
95{
96 auto const& tx = ctx.tx;
97
98 auto const account = tx[sfAccount];
99 auto const vaultID = tx[sfVaultID];
100
101 auto const sleVault = ctx.view.read(keylet::vault(vaultID));
102 if (!sleVault)
103 {
104 JLOG(ctx.j.warn()) << "Vault does not exist.";
105 return tecNO_ENTRY;
106 }
107 Asset const asset = sleVault->at(sfAsset);
108
109 if (account != sleVault->at(sfOwner))
110 {
111 JLOG(ctx.j.warn()) << "Account is not the owner of the Vault.";
112 return tecNO_PERMISSION;
113 }
114
115 if (auto const brokerID = tx[~sfLoanBrokerID])
116 {
117 // Updating an existing Broker
118
119 auto const sleBroker = ctx.view.read(keylet::loanBroker(*brokerID));
120 if (!sleBroker)
121 {
122 JLOG(ctx.j.warn()) << "LoanBroker does not exist.";
123 return tecNO_ENTRY;
124 }
125 if (vaultID != sleBroker->at(sfVaultID))
126 {
127 JLOG(ctx.j.warn()) << "Can not change VaultID on an existing LoanBroker.";
128 return tecNO_PERMISSION;
129 }
130 if (account != sleBroker->at(sfOwner))
131 {
132 JLOG(ctx.j.warn()) << "Account is not the owner of the LoanBroker.";
133 return tecNO_PERMISSION;
134 }
135
136 if (auto const debtMax = tx[~sfDebtMaximum])
137 {
138 // Can't reduce the debt maximum below the current total debt
139 auto const currentDebtTotal = sleBroker->at(sfDebtTotal);
140 if (*debtMax != 0 && *debtMax < currentDebtTotal)
141 {
142 JLOG(ctx.j.warn()) << "Cannot reduce DebtMaximum below current DebtTotal.";
143 return tecLIMIT_EXCEEDED;
144 }
145 }
146 }
147 else
148 {
149 // LP V1.1: only closed-ended vaults may host a loan broker. The
150 // lending protocol relies on the closed-ended Subscription /
151 // Investment / Redemption phase structure; attaching a broker to
152 // an open-ended vault has no well-defined lifecycle. VaultCreate
153 // stays unrestricted so existing open-ended flows keep working;
154 // the constraint is enforced here, at the point where the vault
155 // is first bound to the lending protocol.
156 if (ctx.view.rules().enabled(featureLendingProtocolV1_1) &&
158 {
159 JLOG(ctx.j.warn()) << "LoanBroker requires a closed-ended Vault.";
160 return tecNO_PERMISSION;
161 }
162
163 if (auto const ter = canAddHolding(ctx.view, asset))
164 return ter;
165
166 if (auto const ter = checkFrozen(ctx.view, sleVault->at(sfAccount), sleVault->at(sfAsset)))
167 {
168 JLOG(ctx.j.warn()) << "Vault pseudo-account is frozen.";
169 return ter;
170 }
171 }
172
173 // Check that relevant values can be represented as the vault asset
174 // type. This is mostly only relevant for integral (non-IOU) types
175 for (auto const& field : getValueFields())
176 {
177 if (auto const value = tx[field]; value && STAmount{asset, *value} != *value)
178 {
179 JLOG(ctx.j.warn()) << field.f->getName() << " (" << *value
180 << ") can not be represented as a(n) " << to_string(asset) << ".";
181 return tecPRECISION_LOSS;
182 }
183 }
184
185 return tesSUCCESS;
186}
187
188TER
190{
191 auto const& tx = ctx_.tx;
192 auto& view = ctx_.view();
193
194 if (auto const brokerID = tx[~sfLoanBrokerID])
195 {
196 // Modify an existing LoanBroker
197 auto broker = view.peek(keylet::loanBroker(*brokerID));
198 if (!broker)
199 {
200 // This should be impossible
201 // LCOV_EXCL_START
202 JLOG(j_.fatal()) << "LoanBroker does not exist.";
203 return tefBAD_LEDGER;
204 // LCOV_EXCL_STOP
205 }
206
207 auto const vault = view.read(keylet::vault(broker->at(sfVaultID)));
208 if (!vault)
209 return tecINTERNAL; // LCOV_EXCL_LINE
210
211 auto const vaultAsset = vault->at(sfAsset);
212
213 if (auto const data = tx[~sfData])
214 broker->at(sfData) = *data;
215 if (auto const debtMax = tx[~sfDebtMaximum])
216 broker->at(sfDebtMaximum) = *debtMax;
217
218 view.update(broker);
219
220 associateAsset(*broker, vaultAsset);
221 }
222 else
223 {
224 // Create a new LoanBroker pointing back to the given Vault
225 auto const vaultID = tx[sfVaultID];
226 auto const sleVault = view.read(keylet::vault(vaultID));
227 if (!sleVault)
228 {
229 // This should be impossible
230 // LCOV_EXCL_START
231 JLOG(j_.fatal()) << "Vault does not exist.";
232 return tefBAD_LEDGER;
233 // LCOV_EXCL_STOP
234 }
235 auto const vaultPseudoID = sleVault->at(sfAccount);
236 auto const vaultAsset = sleVault->at(sfAsset);
237 auto const sequence = tx.getSeqProxy();
238
239 auto owner = view.peek(keylet::account(accountID_));
240 if (!owner)
241 {
242 // This should be impossible
243 // LCOV_EXCL_START
244 JLOG(j_.fatal()) << "Account does not exist.";
245 return tefBAD_LEDGER;
246 // LCOV_EXCL_STOP
247 }
248 auto broker = std::make_shared<SLE>(keylet::loanBroker(accountID_, sequence));
249
250 if (auto const ter = dirLink(view, accountID_, broker))
251 return ter; // LCOV_EXCL_LINE
252 if (auto const ter = dirLink(view, vaultPseudoID, broker, sfVaultNode))
253 return ter; // LCOV_EXCL_LINE
254
255 // Increases the owner count by two: one for the LoanBroker object, and
256 // one for the pseudo-account.
257 increaseOwnerCount(view, owner, {}, 2, j_);
258 if (preFeeBalance_ < accountReserve(view, owner, j_))
260
261 auto maybePseudo = createPseudoAccount(view, broker->key(), sfLoanBrokerID);
262 if (!maybePseudo)
263 return maybePseudo.error(); // LCOV_EXCL_LINE
264 auto& pseudo = *maybePseudo;
265 auto pseudoId = pseudo->at(sfAccount);
266
267 if (auto ter = addEmptyHolding(
268 ctx_.getApplyViewContext(), pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_))
269 return ter;
270
271 // Initialize data fields:
272 broker->at(sfSequence) = sequence.value();
273 broker->at(sfVaultID) = vaultID;
274 broker->at(sfOwner) = accountID_;
275 broker->at(sfAccount) = pseudoId;
276 // The LoanSequence indexes loans created by this broker, starting at 1
277 broker->at(sfLoanSequence) = 1;
278 if (auto const data = tx[~sfData])
279 broker->at(sfData) = *data;
280 if (auto const rate = tx[~sfManagementFeeRate])
281 broker->at(sfManagementFeeRate) = *rate;
282 if (auto const debtMax = tx[~sfDebtMaximum])
283 broker->at(sfDebtMaximum) = *debtMax;
284 if (auto const coverMin = tx[~sfCoverRateMinimum])
285 broker->at(sfCoverRateMinimum) = *coverMin;
286 if (auto const coverLiq = tx[~sfCoverRateLiquidation])
287 broker->at(sfCoverRateLiquidation) = *coverLiq;
288
289 view.insert(broker);
290
291 associateAsset(*broker, vaultAsset);
292 }
293
294 return tesSUCCESS;
295}
296
297void
299{
300 // No transaction-specific invariants yet (future work).
301}
302
303bool
305 STTx const&,
306 TER,
307 XRPAmount,
308 ReadView const&,
309 beast::Journal const&)
310{
311 // No transaction-specific invariants yet (future work).
312 return true;
313}
314
315//------------------------------------------------------------------------------
316
317} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
Stream warn() const
Definition Journal.h:356
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
void visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override
Inspect a single ledger entry modified by this transaction.
bool finalizeInvariants(STTx const &tx, TER result, XRPAmount fee, ReadView const &view, beast::Journal const &j) override
Check transaction-specific post-conditions after all entries have been visited.
static std::vector< OptionaledField< STNumber > > const & getValueFields()
TER doApply() override
static bool checkExtraFeatures(PreflightContext const &ctx)
Number is a floating point type that can represent a wide range of values.
Definition Number.h:351
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:182
std::shared_ptr< STLedgerEntry const > const & ConstRef
beast::Journal const j_
Definition Transactor.h:164
ApplyView & view()
Definition Transactor.h:184
AccountID const accountID_
Definition Transactor.h:166
XRPAmount preFeeBalance_
Definition Transactor.h:167
static bool validDataLength(std::optional< Slice > const &slice, std::size_t maxLength)
ApplyContext & ctx_
Definition Transactor.h:162
static bool validNumericRange(std::optional< T > value, T max, T min=T{})
Definition Transactor.h:677
T make_shared(T... args)
constexpr Zero kZero
Definition Zero.h:30
Keylet vault(AccountID const &owner, SeqProxy const &seq) noexcept
Definition Indexes.cpp:591
Keylet loanBroker(AccountID const &owner, SeqProxy const &seq) noexcept
Definition Indexes.cpp:597
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
@ tefBAD_LEDGER
Definition TER.h:165
std::expected< SLE::pointer, TER > createPseudoAccount(ApplyView &view, UInt256 const &pseudoOwnerKey, SField const &ownerField)
Create pseudo-account, storing pseudoOwnerKey into ownerField.
TER addEmptyHolding(ApplyViewContext ctx, AccountID const &accountID, XRPAmount priorBalance, MPTIssue const &mptIssue, beast::Journal journal)
void increaseOwnerCount(ApplyView &view, SLE::Ref accountSle, SLE::Ref sponsorSle, std::uint32_t count, beast::Journal j)
Increase owner-count fields when the caller supplies the sponsor.
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
constexpr std::size_t kMaxDataPayloadLength
The maximum length of Data payload.
Definition Protocol.h:303
TER canAddHolding(ReadView const &view, MPTIssue const &mptIssue)
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:614
TER dirLink(ApplyView &view, AccountID const &owner, SLE::pointer &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:384
VaultKind getVaultKind(SLE::ConstRef vault)
Resolves the VaultKind of a vault SLE.
@ temINVALID
Definition TER.h:98
TERSubset< CanCvtToTER > TER
Definition TER.h:654
XRPAmount accountReserve(ReadView const &view, SLE::ConstRef sle, beast::Journal j, Adjustment adj={})
Returns the account reserve, in drops.
@ tecNO_ENTRY
Definition TER.h:314
@ tecINTERNAL
Definition TER.h:318
@ tecPRECISION_LOSS
Definition TER.h:371
@ tecINSUFFICIENT_RESERVE
Definition TER.h:315
@ tecLIMIT_EXCEEDED
Definition TER.h:369
@ tecNO_PERMISSION
Definition TER.h:313
void associateAsset(STLedgerEntry &sle, Asset const &asset)
Associate an Asset with all sMD_NeedsAsset fields in a ledger entry.
constexpr std::uint64_t kMaxMpTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:297
@ tesSUCCESS
Definition TER.h:250
bool checkLendingProtocolDependencies(Rules const &rules, STTx const &tx)
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:92
ReadView const & view
Definition Transactor.h:95
beast::Journal const j
Definition Transactor.h:100
State information when preflighting a tx.
Definition Transactor.h:39