rippled
Loading...
Searching...
No Matches
LoanBrokerCoverClawback.cpp
1#include <xrpld/app/tx/detail/LoanBrokerCoverClawback.h>
2//
3#include <xrpld/app/misc/LendingHelpers.h>
4
5namespace xrpl {
6
7bool
12
15{
16 auto const brokerID = ctx.tx[~sfLoanBrokerID];
17 auto const amount = ctx.tx[~sfAmount];
18
19 if (!brokerID && !amount)
20 return temINVALID;
21
22 if (brokerID && *brokerID == beast::zero)
23 return temINVALID;
24
25 if (amount)
26 {
27 // XRP has no counterparty, and thus nobody can claw it back
28 if (amount->native())
29 return temBAD_AMOUNT;
30
31 // Zero is OK, and indicates "take it all" (down to the minimum cover)
32 if (*amount < beast::zero)
33 return temBAD_AMOUNT;
34
35 // This should be redundant
36 if (!isLegalNet(*amount))
37 return temBAD_AMOUNT; // LCOV_EXCL_LINE
38
39 if (!brokerID)
40 {
41 if (amount->holds<MPTIssue>())
42 return temINVALID;
43
44 auto const account = ctx.tx[sfAccount];
45 // Since we don't have a LoanBrokerID, holder _should_ be the loan
46 // broker's pseudo-account, but we don't know yet whether it is, so
47 // use a generic placeholder name.
48 auto const holder = amount->getIssuer();
49 if (holder == account || holder == beast::zero)
50 return temINVALID;
51 }
52 }
53
54 return tesSUCCESS;
55}
56
58determineBrokerID(ReadView const& view, STTx const& tx)
59{
60 // If the broker ID was provided in the transaction, that's all we
61 // need.
62 if (auto const brokerID = tx[~sfLoanBrokerID])
63 return *brokerID;
64
65 // If the broker ID was not provided, and the amount is either
66 // absent or holds a non-IOU - including MPT, something went wrong,
67 // because that should have been rejected in preflight().
68 auto const dstAmount = tx[~sfAmount];
69 if (!dstAmount || !dstAmount->holds<Issue>())
70 return Unexpected{tecINTERNAL}; // LCOV_EXCL_LINE
71
72 // Every trust line is bidirectional. Both sides are simultaneously
73 // issuer and holder. For this transaction, the Account is acting as
74 // a holder, and clawing back funds from the LoanBroker
75 // Pseudo-account acting as holder. If the Amount is an IOU, and the
76 // `issuer` field specified in that Amount is a LoanBroker
77 // Pseudo-account, we can get the LoanBrokerID from there.
78 //
79 // Thus, Amount.issuer _should_ be the loan broker's
80 // pseudo-account, but we don't know yet whether it is.
81 auto const maybePseudo = dstAmount->getIssuer();
82 auto const sle = view.read(keylet::account(maybePseudo));
83
84 // If the account was not found, the transaction can't go further.
85 if (!sle)
86 return Unexpected{tecNO_ENTRY};
87
88 // If the account was found, and has a LoanBrokerID (and therefore
89 // is a pseudo-account), that's the
90 // answer we need.
91 if (auto const brokerID = sle->at(~sfLoanBrokerID))
92 return *brokerID;
93
94 // If the account does not have a LoanBrokerID, the transaction
95 // can't go further, even if it's a different type of Pseudo-account.
97 // Or tecWRONG_ASSET?
98}
99
100Expected<Asset, TER>
102 ReadView const& view,
103 AccountID const& account,
104 AccountID const& brokerPseudoAccountID,
105 STAmount const& amount)
106{
107 if (amount.holds<MPTIssue>())
108 return amount.asset();
109
110 // An IOU has an issue, which could be either end of the trust line.
111 // This check only applies to IOUs
112 auto const holder = amount.getIssuer();
113
114 // holder can be the submitting account (the issuer of the asset) if a
115 // LoanBrokerID was provided in the transaction.
116 if (holder == account)
117 {
118 return amount.asset();
119 }
120 else if (holder == brokerPseudoAccountID)
121 {
122 // We want the asset to match the vault asset, so use the account as the
123 // issuer
124 return Issue{amount.getCurrency(), account};
125 }
126 else
128}
129
130Expected<STAmount, TER>
132 SLE const& sleBroker,
133 Asset const& vaultAsset,
134 std::optional<STAmount> const& amount)
135{
136 auto const maxClawAmount = [&]() {
137 // Always round the minimum required up
139 auto const minRequiredCover = tenthBipsOfValue(
140 sleBroker[sfDebtTotal], TenthBips32(sleBroker[sfCoverRateMinimum]));
141 // The subtraction probably won't round, but round down if it does.
143 return sleBroker[sfCoverAvailable] - minRequiredCover;
144 }();
145 if (maxClawAmount <= beast::zero)
147
148 // Use the vaultAsset here, because it will be the right type in all
149 // circumstances. The amount may be an IOU indicating the pseudo-account's
150 // asset, which is correct, but not what is needed here.
151 if (!amount || *amount == beast::zero)
152 return STAmount{vaultAsset, maxClawAmount};
153 Number const magnitude{*amount};
154 if (magnitude > maxClawAmount)
155 return STAmount{vaultAsset, maxClawAmount};
156 return STAmount{vaultAsset, magnitude};
157}
158
159template <ValidIssueType T>
160static TER
162 PreclaimContext const& ctx,
163 SLE const& sleIssuer,
164 STAmount const& clawAmount);
165
166template <>
169 PreclaimContext const& ctx,
170 SLE const& sleIssuer,
171 STAmount const& clawAmount)
172{
173 // If AllowTrustLineClawback is not set or NoFreeze is set, return no
174 // permission
175 if (!(sleIssuer.isFlag(lsfAllowTrustLineClawback)) ||
176 (sleIssuer.isFlag(lsfNoFreeze)))
177 return tecNO_PERMISSION;
178
179 return tesSUCCESS;
180}
181
182template <>
185 PreclaimContext const& ctx,
186 SLE const& sleIssuer,
187 STAmount const& clawAmount)
188{
189 auto const issuanceKey =
190 keylet::mptIssuance(clawAmount.get<MPTIssue>().getMptID());
191 auto const sleIssuance = ctx.view.read(issuanceKey);
192 if (!sleIssuance)
193 return tecOBJECT_NOT_FOUND;
194
195 if (!sleIssuance->isFlag(lsfMPTCanClawback))
196 return tecNO_PERMISSION;
197
198 // With all the checking already done, this should be impossible
199 if (sleIssuance->at(sfIssuer) != sleIssuer[sfAccount])
200 return tecINTERNAL; // LCOV_EXCL_LINE
201
202 return tesSUCCESS;
203}
204
205TER
207{
208 auto const& tx = ctx.tx;
209
210 auto const account = tx[sfAccount];
211 auto const findBrokerID = determineBrokerID(ctx.view, tx);
212 if (!findBrokerID)
213 return findBrokerID.error();
214 auto const brokerID = *findBrokerID;
215 auto const amount = tx[~sfAmount];
216
217 auto const sleBroker = ctx.view.read(keylet::loanbroker(brokerID));
218 if (!sleBroker)
219 {
220 JLOG(ctx.j.warn()) << "LoanBroker does not exist.";
221 return tecNO_ENTRY;
222 }
223
224 auto const brokerPseudoAccountID = sleBroker->at(sfAccount);
225
226 auto const vault = ctx.view.read(keylet::vault(sleBroker->at(sfVaultID)));
227 if (!vault)
228 {
229 // LCOV_EXCL_START
230 JLOG(ctx.j.fatal()) << "Vault is missing for Broker " << brokerID;
231 return tefBAD_LEDGER;
232 // LCOV_EXCL_STOP
233 }
234
235 auto const vaultAsset = vault->at(sfAsset);
236
237 if (vaultAsset.native())
238 {
239 JLOG(ctx.j.warn()) << "Cannot clawback native asset.";
240 return tecNO_PERMISSION;
241 }
242
243 // Only the issuer of the vault asset can claw it back from the broker's
244 // cover funds.
245 if (vaultAsset.getIssuer() != account)
246 {
247 JLOG(ctx.j.warn()) << "Account is not the issuer of the vault asset.";
248 return tecNO_PERMISSION;
249 }
250
251 if (amount)
252 {
253 auto const findAsset =
254 determineAsset(ctx.view, account, brokerPseudoAccountID, *amount);
255 if (!findAsset)
256 return findAsset.error();
257 auto const txAsset = *findAsset;
258 if (txAsset != vaultAsset)
259 {
260 JLOG(ctx.j.warn()) << "Account is the correct issuer, but trying "
261 "to clawback the wrong asset from LoanBroker";
262 return tecWRONG_ASSET;
263 }
264 }
265
266 auto const findClawAmount =
267 determineClawAmount(*sleBroker, vaultAsset, amount);
268 if (!findClawAmount)
269 {
270 JLOG(ctx.j.warn()) << "LoanBroker cover is already at minimum.";
271 return findClawAmount.error();
272 }
273 STAmount const clawAmount = *findClawAmount;
274
275 // Explicitly check the balance of the trust line / MPT to make sure the
276 // balance is actually there. It should always match `sfCoverAvailable`, so
277 // if there isn't, this is an internal error.
278 if (accountHolds(
279 ctx.view,
280 brokerPseudoAccountID,
281 vaultAsset,
284 ctx.j) < clawAmount)
285 return tecINTERNAL; // tecINSUFFICIENT_FUNDS; LCOV_EXCL_LINE
286
287 // Check if the vault asset issuer has the correct flags
288 auto const sleIssuer =
289 ctx.view.read(keylet::account(vaultAsset.getIssuer()));
290 return std::visit(
291 [&]<typename T>(T const&) {
292 return preclaimHelper<T>(ctx, *sleIssuer, clawAmount);
293 },
294 vaultAsset.value());
295}
296
297TER
299{
300 auto const& tx = ctx_.tx;
301 auto const account = tx[sfAccount];
302 auto const findBrokerID = determineBrokerID(view(), tx);
303 if (!findBrokerID)
304 return tecINTERNAL; // LCOV_EXCL_LINE
305 auto const brokerID = *findBrokerID;
306 auto const amount = tx[~sfAmount];
307
308 auto sleBroker = view().peek(keylet::loanbroker(brokerID));
309 if (!sleBroker)
310 return tecINTERNAL; // LCOV_EXCL_LINE
311
312 auto const brokerPseudoID = *sleBroker->at(sfAccount);
313
314 auto const vault = view().read(keylet::vault(sleBroker->at(sfVaultID)));
315 if (!vault)
316 return tecINTERNAL; // LCOV_EXCL_LINE
317
318 auto const vaultAsset = vault->at(sfAsset);
319
320 auto const findClawAmount =
321 determineClawAmount(*sleBroker, vaultAsset, amount);
322 if (!findClawAmount)
323 return tecINTERNAL; // LCOV_EXCL_LINE
324 STAmount const clawAmount = *findClawAmount;
325 // Just for paranoia's sake
326 if (clawAmount.native())
327 return tecINTERNAL; // LCOV_EXCL_LINE
328
329 // Decrease the LoanBroker's CoverAvailable by Amount
330 sleBroker->at(sfCoverAvailable) -= clawAmount;
331 view().update(sleBroker);
332
333 // Transfer assets from pseudo-account to depositor.
334 return accountSend(
335 view(), brokerPseudoID, account, clawAmount, j_, WaiveTransferFee::Yes);
336}
337
338//------------------------------------------------------------------------------
339
340} // namespace xrpl
Stream fatal() const
Definition Journal.h:333
Stream warn() const
Definition Journal.h:321
STTx const & tx
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
A currency issued by an account.
Definition Issue.h:14
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static bool checkExtraFeatures(PreflightContext const &ctx)
constexpr MPTID const & getMptID() const
Definition MPTIssue.h:27
A view into a ledger.
Definition ReadView.h:32
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
constexpr TIss const & get() const
bool native() const noexcept
Definition STAmount.h:450
bool isFlag(std::uint32_t) const
Definition STObject.cpp:512
beast::Journal const j_
Definition Transactor.h:126
ApplyView & view()
Definition Transactor.h:144
ApplyContext & ctx_
Definition Transactor.h:124
Keylet loanbroker(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:552
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:508
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:546
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:166
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
@ fhIGNORE_FREEZE
Definition View.h:59
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
Definition Protocol.h:108
@ tefBAD_LEDGER
Definition TER.h:151
bool isLegalNet(STAmount const &value)
Definition STAmount.h:592
bool checkLendingProtocolDependencies(PreflightContext const &ctx)
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:443
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:461
static TER preclaimHelper(PreclaimContext const &ctx, SLE const &sleIssuer, AccountID const &issuer, AccountID const &holder, STAmount const &clawAmount)
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2777
TERSubset< CanCvtToTER > TER
Definition TER.h:630
TER preclaimHelper< Issue >(PreclaimContext const &ctx, SLE const &sleIssuer, AccountID const &issuer, AccountID const &holder, STAmount const &clawAmount)
Definition Clawback.cpp:88
@ ahIGNORE_AUTH
Definition View.h:62
Expected< uint256, TER > determineBrokerID(ReadView const &view, STTx const &tx)
Expected< STAmount, TER > determineClawAmount(SLE const &sleBroker, Asset const &vaultAsset, std::optional< STAmount > const &amount)
@ temINVALID
Definition TER.h:91
@ temBAD_AMOUNT
Definition TER.h:70
@ tecWRONG_ASSET
Definition TER.h:342
@ tecNO_ENTRY
Definition TER.h:288
@ tecOBJECT_NOT_FOUND
Definition TER.h:308
@ tecINTERNAL
Definition TER.h:292
@ tecINSUFFICIENT_FUNDS
Definition TER.h:307
@ tecNO_PERMISSION
Definition TER.h:287
@ lsfAllowTrustLineClawback
@ lsfNoFreeze
@ lsfMPTCanClawback
TER preclaimHelper< MPTIssue >(PreclaimContext const &ctx, SLE const &sleIssuer, AccountID const &issuer, AccountID const &holder, STAmount const &clawAmount)
Definition Clawback.cpp:141
@ tesSUCCESS
Definition TER.h:226
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:16
T visit(T... args)