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