xrpld
Loading...
Searching...
No Matches
LoanBrokerCoverClawback.cpp
1#include <xrpl/tx/transactors/lending/LoanBrokerCoverClawback.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Number.h>
5#include <xrpl/basics/base_uint.h>
6#include <xrpl/beast/utility/Zero.h>
7#include <xrpl/core/ServiceRegistry.h>
8#include <xrpl/ledger/ReadView.h>
9#include <xrpl/ledger/helpers/LendingHelpers.h>
10#include <xrpl/ledger/helpers/TokenHelpers.h>
11#include <xrpl/protocol/AccountID.h>
12#include <xrpl/protocol/Asset.h>
13#include <xrpl/protocol/Concepts.h>
14#include <xrpl/protocol/Feature.h>
15#include <xrpl/protocol/Indexes.h>
16#include <xrpl/protocol/Issue.h>
17#include <xrpl/protocol/LedgerFormats.h>
18#include <xrpl/protocol/MPTIssue.h>
19#include <xrpl/protocol/Protocol.h>
20#include <xrpl/protocol/SField.h>
21#include <xrpl/protocol/STAmount.h>
22#include <xrpl/protocol/STLedgerEntry.h>
23#include <xrpl/protocol/STTakesAsset.h>
24#include <xrpl/protocol/STTx.h>
25#include <xrpl/protocol/TER.h>
26#include <xrpl/protocol/Units.h>
27#include <xrpl/protocol/XRPAmount.h>
28#include <xrpl/tx/Transactor.h>
29
30#include <expected>
31#include <optional>
32#include <variant>
33
34namespace xrpl {
35
36bool
41
44{
45 auto const brokerID = ctx.tx[~sfLoanBrokerID];
46 auto const amount = ctx.tx[~sfAmount];
47
48 if (!brokerID && !amount)
49 return temINVALID;
50
51 if (brokerID && *brokerID == beast::kZero)
52 return temINVALID;
53
54 if (amount)
55 {
56 // XRP has no counterparty, and thus nobody can claw it back
57 if (amount->native())
58 return temBAD_AMOUNT;
59
60 // Zero is OK, and indicates "take it all" (down to the minimum cover)
61 if (*amount < beast::kZero)
62 return temBAD_AMOUNT;
63
64 // This should be redundant
65 if (!isLegalNet(*amount))
66 return temBAD_AMOUNT; // LCOV_EXCL_LINE
67
68 if (!brokerID)
69 {
70 if (amount->holds<MPTIssue>())
71 return temINVALID;
72
73 auto const account = ctx.tx[sfAccount];
74 // Since we don't have a LoanBrokerID, holder _should_ be the loan
75 // broker's pseudo-account, but we don't know yet whether it is, so
76 // use a generic placeholder name.
77 auto const holder = amount->getIssuer();
78 if (holder == account || holder == beast::kZero)
79 return temINVALID;
80 }
81 }
82
83 return tesSUCCESS;
84}
85
86std::expected<uint256, TER>
87determineBrokerID(ReadView const& view, STTx const& tx)
88{
89 // If the broker ID was provided in the transaction, that's all we
90 // need.
91 if (auto const brokerID = tx[~sfLoanBrokerID])
92 return *brokerID;
93
94 // If the broker ID was not provided, and the amount is either
95 // absent or holds a non-IOU - including MPT, something went wrong,
96 // because that should have been rejected in preflight().
97 auto const dstAmount = tx[~sfAmount];
98 if (!dstAmount || !dstAmount->holds<Issue>())
99 return std::unexpected{tecINTERNAL}; // LCOV_EXCL_LINE
100
101 // Every trust line is bidirectional. Both sides are simultaneously
102 // issuer and holder. For this transaction, the Account is acting as
103 // a holder, and clawing back funds from the LoanBroker
104 // Pseudo-account acting as holder. If the Amount is an IOU, and the
105 // `issuer` field specified in that Amount is a LoanBroker
106 // Pseudo-account, we can get the LoanBrokerID from there.
107 //
108 // Thus, Amount.issuer _should_ be the loan broker's
109 // pseudo-account, but we don't know yet whether it is.
110 auto const maybePseudo = dstAmount->getIssuer();
111 auto const sle = view.read(keylet::account(maybePseudo));
112
113 // If the account was not found, the transaction can't go further.
114 if (!sle)
116
117 // If the account was found, and has a LoanBrokerID (and therefore
118 // is a pseudo-account), that's the
119 // answer we need.
120 if (auto const brokerID = sle->at(~sfLoanBrokerID))
121 return *brokerID;
122
123 // If the account does not have a LoanBrokerID, the transaction
124 // can't go further, even if it's a different type of Pseudo-account.
126 // Or tecWRONG_ASSET?
127}
128
129std::expected<Asset, TER>
131 ReadView const& view,
132 AccountID const& account,
133 AccountID const& brokerPseudoAccountID,
134 STAmount const& amount)
135{
136 if (amount.holds<MPTIssue>())
137 return amount.asset();
138
139 // An IOU has an issue, which could be either end of the trust line.
140 // This check only applies to IOUs
141 auto const holder = amount.getIssuer();
142
143 // holder can be the submitting account (the issuer of the asset) if a
144 // LoanBrokerID was provided in the transaction.
145 if (holder == account)
146 {
147 return amount.asset();
148 }
149 if (holder == brokerPseudoAccountID)
150 {
151 // We want the asset to match the vault asset, so use the account as the
152 // issuer
153 return Issue{amount.get<Issue>().currency, account};
154 }
155
157}
158
159std::expected<STAmount, TER>
161 SLE const& sleBroker,
162 Asset const& vaultAsset,
163 std::optional<STAmount> const& amount,
164 SLE::const_ref vaultSle,
165 Rules const& rules)
166{
167 auto const maxClawAmount = [&]() {
168 auto const minRequiredCover = [&]() {
169 if (rules.enabled(fixCleanup3_2_0))
170 {
171 return minimumBrokerCover(
172 sleBroker[sfDebtTotal], TenthBips32(sleBroker[sfCoverRateMinimum]), vaultSle);
173 }
174
175 // Always round the minimum required up
177 return tenthBipsOfValue(
178 sleBroker[sfDebtTotal], TenthBips32(sleBroker[sfCoverRateMinimum]));
179 }();
180 // The subtraction probably won't round, but round down if it does.
182 return sleBroker[sfCoverAvailable] - minRequiredCover;
183 }();
184 if (maxClawAmount <= beast::kZero)
186
187 // Use the vaultAsset here, because it will be the right type in all
188 // circumstances. The amount may be an IOU indicating the pseudo-account's
189 // asset, which is correct, but not what is needed here.
190 if (!amount || *amount == beast::kZero)
191 return STAmount{vaultAsset, maxClawAmount};
192 Number const magnitude{*amount};
193 if (magnitude > maxClawAmount)
194 return STAmount{vaultAsset, maxClawAmount};
195 return STAmount{vaultAsset, magnitude};
196}
197
198template <ValidIssueType T>
199static TER
200preclaimHelper(PreclaimContext const& ctx, SLE const& sleIssuer, STAmount const& clawAmount);
201
202template <>
204preclaimHelper<Issue>(PreclaimContext const& ctx, SLE const& sleIssuer, STAmount const& clawAmount)
205{
206 // If AllowTrustLineClawback is not set or NoFreeze is set, return no
207 // permission
208 if (!(sleIssuer.isFlag(lsfAllowTrustLineClawback)) || (sleIssuer.isFlag(lsfNoFreeze)))
209 return tecNO_PERMISSION;
210
211 return tesSUCCESS;
212}
213
214template <>
217 PreclaimContext const& ctx,
218 SLE const& sleIssuer,
219 STAmount const& clawAmount)
220{
221 auto const issuanceKey = keylet::mptokenIssuance(clawAmount.get<MPTIssue>().getMptID());
222 auto const sleIssuance = ctx.view.read(issuanceKey);
223 if (!sleIssuance)
224 return tecOBJECT_NOT_FOUND;
225
226 if (!sleIssuance->isFlag(lsfMPTCanClawback))
227 return tecNO_PERMISSION;
228
229 // With all the checking already done, this should be impossible
230 if (sleIssuance->at(sfIssuer) != sleIssuer[sfAccount])
231 return tecINTERNAL; // LCOV_EXCL_LINE
232
233 return tesSUCCESS;
234}
235
236TER
238{
239 auto const& tx = ctx.tx;
240
241 auto const account = tx[sfAccount];
242 auto const findBrokerID = determineBrokerID(ctx.view, tx);
243 if (!findBrokerID)
244 return findBrokerID.error();
245 auto const brokerID = *findBrokerID;
246 auto const amount = tx[~sfAmount];
247
248 auto const sleBroker = ctx.view.read(keylet::loanBroker(brokerID));
249 if (!sleBroker)
250 {
251 JLOG(ctx.j.warn()) << "LoanBroker does not exist.";
252 return tecNO_ENTRY;
253 }
254
255 auto const brokerPseudoAccountID = sleBroker->at(sfAccount);
256
257 auto const vault = ctx.view.read(keylet::vault(sleBroker->at(sfVaultID)));
258 if (!vault)
259 {
260 // LCOV_EXCL_START
261 JLOG(ctx.j.fatal()) << "Vault is missing for Broker " << brokerID;
262 return tefBAD_LEDGER;
263 // LCOV_EXCL_STOP
264 }
265
266 auto const vaultAsset = vault->at(sfAsset);
267
268 if (vaultAsset.native())
269 {
270 JLOG(ctx.j.warn()) << "Cannot clawback native asset.";
271 return tecNO_PERMISSION;
272 }
273
274 // Only the issuer of the vault asset can claw it back from the broker's
275 // cover funds.
276 if (vaultAsset.getIssuer() != account)
277 {
278 JLOG(ctx.j.warn()) << "Account is not the issuer of the vault asset.";
279 return tecNO_PERMISSION;
280 }
281
282 if (amount)
283 {
284 auto const findAsset = determineAsset(ctx.view, account, brokerPseudoAccountID, *amount);
285 if (!findAsset)
286 return findAsset.error();
287 auto const txAsset = *findAsset;
288 if (txAsset != vaultAsset)
289 {
290 JLOG(ctx.j.warn()) << "Account is the correct issuer, but trying "
291 "to clawback the wrong asset from LoanBroker";
292 return tecWRONG_ASSET;
293 }
294 }
295
296 auto const findClawAmount =
297 determineClawAmount(*sleBroker, vaultAsset, amount, vault, ctx.view.rules());
298 if (!findClawAmount)
299 {
300 JLOG(ctx.j.warn()) << "LoanBroker cover is already at minimum.";
301 return findClawAmount.error();
302 }
303 STAmount const& clawAmount = *findClawAmount;
304
305 if (auto const ret = canApplyToBrokerCover(
306 ctx.view, sleBroker, vaultAsset, clawAmount, ctx.j, "LoanBrokerCoverClawback"))
307 return ret;
308
309 // Explicitly check the balance of the trust line / MPT to make sure the
310 // balance is actually there. It should always match `sfCoverAvailable`, so
311 // if there isn't, this is an internal error.
312 if (accountHolds(
313 ctx.view,
314 brokerPseudoAccountID,
315 vaultAsset,
318 ctx.j) < clawAmount)
319 return tecINTERNAL; // tecINSUFFICIENT_FUNDS; LCOV_EXCL_LINE
320
321 // Check if the vault asset issuer has the correct flags
322 auto const sleIssuer = ctx.view.read(keylet::account(vaultAsset.getIssuer()));
323 if (!sleIssuer)
324 {
325 // LCOV_EXCL_START
326 JLOG(ctx.j.fatal()) << "Issuer account does not exist.";
327 return tefBAD_LEDGER;
328 // LCOV_EXCL_STOP
329 }
330
331 return std::visit(
332 [&]<typename T>(T const&) { return preclaimHelper<T>(ctx, *sleIssuer, clawAmount); },
333 vaultAsset.value());
334}
335
336TER
338{
339 auto const& tx = ctx_.tx;
340 auto const account = tx[sfAccount];
341 auto const findBrokerID = determineBrokerID(view(), tx);
342 if (!findBrokerID)
343 return tecINTERNAL; // LCOV_EXCL_LINE
344 auto const brokerID = *findBrokerID;
345 auto const amount = tx[~sfAmount];
346
347 auto sleBroker = view().peek(keylet::loanBroker(brokerID));
348 if (!sleBroker)
349 return tecINTERNAL; // LCOV_EXCL_LINE
350
351 auto const brokerPseudoID = *sleBroker->at(sfAccount);
352
353 auto const vault = view().read(keylet::vault(sleBroker->at(sfVaultID)));
354 if (!vault)
355 return tecINTERNAL; // LCOV_EXCL_LINE
356
357 auto const vaultAsset = vault->at(sfAsset);
358
359 auto const findClawAmount =
360 determineClawAmount(*sleBroker, vaultAsset, amount, vault, view().rules());
361 if (!findClawAmount)
362 return tecINTERNAL; // LCOV_EXCL_LINE
363 STAmount const& clawAmount = *findClawAmount;
364 // Just for paranoia's sake
365 if (clawAmount.native())
366 return tecINTERNAL; // LCOV_EXCL_LINE
367
368 // Decrease the LoanBroker's CoverAvailable by Amount
369 sleBroker->at(sfCoverAvailable) -= clawAmount;
370 view().update(sleBroker);
371
372 associateAsset(*sleBroker, vaultAsset);
373
374 // Transfer assets from pseudo-account to depositor.
375 return accountSend(view(), brokerPseudoID, account, clawAmount, j_, WaiveTransferFee::Yes);
376}
377
378void
380{
381 // No transaction-specific invariants yet (future work).
382}
383
384bool
386 STTx const&,
387 TER,
388 XRPAmount,
389 ReadView const&,
390 beast::Journal const&)
391{
392 // No transaction-specific invariants yet (future work).
393 return true;
394}
395
396//------------------------------------------------------------------------------
397
398} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Stream fatal() const
Definition Journal.h:321
Stream warn() const
Definition Journal.h:309
virtual SLE::pointer peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void update(SLE::ref sle)=0
Indicate changes to a peeked SLE.
A currency issued by an account.
Definition Issue.h:13
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 TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override
Inspect a single ledger entry modified by this transaction.
static bool checkExtraFeatures(PreflightContext const &ctx)
constexpr MPTID const & getMptID() const
Definition MPTIssue.h:33
Number is a floating point type that can represent a wide range of values.
Definition Number.h:306
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.
Rules controlling protocol behavior.
Definition Rules.h:33
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:171
constexpr bool holds() const noexcept
Definition STAmount.h:460
constexpr TIss const & get() const
bool native() const noexcept
Definition STAmount.h:453
Asset const & asset() const
Definition STAmount.h:478
AccountID const & getIssuer() const
Definition STAmount.h:498
std::shared_ptr< STLedgerEntry const > const & const_ref
bool isFlag(std::uint32_t) const
Definition STObject.cpp:501
beast::Journal const j_
Definition Transactor.h:118
ApplyView & view()
Definition Transactor.h:136
ApplyContext & ctx_
Definition Transactor.h:116
Keylet loanBroker(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:557
Keylet mptokenIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:521
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
TER canApplyToBrokerCover(ReadView const &view, SLE::const_ref sleBroker, Asset const &vaultAsset, STAmount const &amount, beast::Journal j, std::string_view logPrefix)
Broker cover preclaim precision guard (fixCleanup3_2_0).
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
Definition Protocol.h:111
@ tefBAD_LEDGER
Definition TER.h:160
Number minimumBrokerCover(Number const &debtTotal, TenthBips32 coverRateMinimum, SLE::const_ref vaultSle)
bool isLegalNet(STAmount const &value)
Definition STAmount.h:598
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:439
STLedgerEntry SLE
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:594
TER preclaimHelper< Issue >(PreclaimContext const &ctx, SLE const &sleIssuer, STAmount const &clawAmount)
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No, AllowMPTOverflow allowOverflow=AllowMPTOverflow::No)
Calls static accountSendIOU if saAmount represents Issue.
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:28
@ temINVALID
Definition TER.h:96
@ temBAD_AMOUNT
Definition TER.h:75
TERSubset< CanCvtToTER > TER
Definition TER.h:634
TER preclaimHelper< MPTIssue >(PreclaimContext const &ctx, SLE const &sleIssuer, STAmount const &clawAmount)
@ tecWRONG_ASSET
Definition TER.h:358
@ tecNO_ENTRY
Definition TER.h:304
@ tecOBJECT_NOT_FOUND
Definition TER.h:324
@ tecINTERNAL
Definition TER.h:308
@ tecINSUFFICIENT_FUNDS
Definition TER.h:323
@ tecNO_PERMISSION
Definition TER.h:303
void associateAsset(STLedgerEntry &sle, Asset const &asset)
Associate an Asset with all sMD_NeedsAsset fields in a ledger entry.
std::expected< uint256, TER > determineBrokerID(ReadView const &view, STTx const &tx)
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=SpendableHandling::SimpleBalance)
@ tesSUCCESS
Definition TER.h:240
static TER preclaimHelper(PreclaimContext const &ctx, SLE const &sleIssuer, STAmount const &clawAmount)
std::expected< STAmount, TER > determineClawAmount(SLE const &sleBroker, Asset const &vaultAsset, std::optional< STAmount > const &amount, SLE::const_ref vaultSle, Rules const &rules)
bool checkLendingProtocolDependencies(Rules const &rules, STTx const &tx)
std::expected< Asset, TER > determineAsset(ReadView const &view, AccountID const &account, AccountID const &brokerPseudoAccountID, STAmount const &amount)
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
ReadView const & view
Definition Transactor.h:64
beast::Journal const j
Definition Transactor.h:69
State information when preflighting a tx.
Definition Transactor.h:18
T unexpected(T... args)
T visit(T... args)