rippled
Loading...
Searching...
No Matches
LoanBrokerCoverClawback.cpp
1#include <xrpld/app/tx/detail/LoanBrokerCoverClawback.h>
2//
3#include <xrpld/app/misc/LendingHelpers.h>
4
5#include <xrpl/protocol/STTakesAsset.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 else 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 else
130}
131
132Expected<STAmount, TER>
133determineClawAmount(SLE const& sleBroker, Asset const& vaultAsset, std::optional<STAmount> const& amount)
134{
135 auto const maxClawAmount = [&]() {
136 // Always round the minimum required up
138 auto const minRequiredCover =
139 tenthBipsOfValue(sleBroker[sfDebtTotal], TenthBips32(sleBroker[sfCoverRateMinimum]));
140 // The subtraction probably won't round, but round down if it does.
142 return sleBroker[sfCoverAvailable] - minRequiredCover;
143 }();
144 if (maxClawAmount <= beast::zero)
146
147 // Use the vaultAsset here, because it will be the right type in all
148 // circumstances. The amount may be an IOU indicating the pseudo-account's
149 // asset, which is correct, but not what is needed here.
150 if (!amount || *amount == beast::zero)
151 return STAmount{vaultAsset, maxClawAmount};
152 Number const magnitude{*amount};
153 if (magnitude > maxClawAmount)
154 return STAmount{vaultAsset, maxClawAmount};
155 return STAmount{vaultAsset, magnitude};
156}
157
158template <ValidIssueType T>
159static TER
160preclaimHelper(PreclaimContext const& ctx, SLE const& sleIssuer, STAmount const& clawAmount);
161
162template <>
164preclaimHelper<Issue>(PreclaimContext const& ctx, SLE const& sleIssuer, STAmount const& clawAmount)
165{
166 // If AllowTrustLineClawback is not set or NoFreeze is set, return no
167 // permission
168 if (!(sleIssuer.isFlag(lsfAllowTrustLineClawback)) || (sleIssuer.isFlag(lsfNoFreeze)))
169 return tecNO_PERMISSION;
170
171 return tesSUCCESS;
172}
173
174template <>
176preclaimHelper<MPTIssue>(PreclaimContext const& ctx, SLE const& sleIssuer, STAmount const& clawAmount)
177{
178 auto const issuanceKey = keylet::mptIssuance(clawAmount.get<MPTIssue>().getMptID());
179 auto const sleIssuance = ctx.view.read(issuanceKey);
180 if (!sleIssuance)
181 return tecOBJECT_NOT_FOUND;
182
183 if (!sleIssuance->isFlag(lsfMPTCanClawback))
184 return tecNO_PERMISSION;
185
186 // With all the checking already done, this should be impossible
187 if (sleIssuance->at(sfIssuer) != sleIssuer[sfAccount])
188 return tecINTERNAL; // LCOV_EXCL_LINE
189
190 return tesSUCCESS;
191}
192
193TER
195{
196 auto const& tx = ctx.tx;
197
198 auto const account = tx[sfAccount];
199 auto const findBrokerID = determineBrokerID(ctx.view, tx);
200 if (!findBrokerID)
201 return findBrokerID.error();
202 auto const brokerID = *findBrokerID;
203 auto const amount = tx[~sfAmount];
204
205 auto const sleBroker = ctx.view.read(keylet::loanbroker(brokerID));
206 if (!sleBroker)
207 {
208 JLOG(ctx.j.warn()) << "LoanBroker does not exist.";
209 return tecNO_ENTRY;
210 }
211
212 auto const brokerPseudoAccountID = sleBroker->at(sfAccount);
213
214 auto const vault = ctx.view.read(keylet::vault(sleBroker->at(sfVaultID)));
215 if (!vault)
216 {
217 // LCOV_EXCL_START
218 JLOG(ctx.j.fatal()) << "Vault is missing for Broker " << brokerID;
219 return tefBAD_LEDGER;
220 // LCOV_EXCL_STOP
221 }
222
223 auto const vaultAsset = vault->at(sfAsset);
224
225 if (vaultAsset.native())
226 {
227 JLOG(ctx.j.warn()) << "Cannot clawback native asset.";
228 return tecNO_PERMISSION;
229 }
230
231 // Only the issuer of the vault asset can claw it back from the broker's
232 // cover funds.
233 if (vaultAsset.getIssuer() != account)
234 {
235 JLOG(ctx.j.warn()) << "Account is not the issuer of the vault asset.";
236 return tecNO_PERMISSION;
237 }
238
239 if (amount)
240 {
241 auto const findAsset = determineAsset(ctx.view, account, brokerPseudoAccountID, *amount);
242 if (!findAsset)
243 return findAsset.error();
244 auto const txAsset = *findAsset;
245 if (txAsset != vaultAsset)
246 {
247 JLOG(ctx.j.warn()) << "Account is the correct issuer, but trying "
248 "to clawback the wrong asset from LoanBroker";
249 return tecWRONG_ASSET;
250 }
251 }
252
253 auto const findClawAmount = determineClawAmount(*sleBroker, vaultAsset, amount);
254 if (!findClawAmount)
255 {
256 JLOG(ctx.j.warn()) << "LoanBroker cover is already at minimum.";
257 return findClawAmount.error();
258 }
259 STAmount const& clawAmount = *findClawAmount;
260
261 // Explicitly check the balance of the trust line / MPT to make sure the
262 // balance is actually there. It should always match `sfCoverAvailable`, so
263 // if there isn't, this is an internal error.
264 if (accountHolds(ctx.view, brokerPseudoAccountID, vaultAsset, fhIGNORE_FREEZE, ahIGNORE_AUTH, ctx.j) < clawAmount)
265 return tecINTERNAL; // tecINSUFFICIENT_FUNDS; LCOV_EXCL_LINE
266
267 // Check if the vault asset issuer has the correct flags
268 auto const sleIssuer = ctx.view.read(keylet::account(vaultAsset.getIssuer()));
269 if (!sleIssuer)
270 {
271 // LCOV_EXCL_START
272 JLOG(ctx.j.fatal()) << "Issuer account does not exist.";
273 return tefBAD_LEDGER;
274 // LCOV_EXCL_STOP
275 }
276
277 return std::visit(
278 [&]<typename T>(T const&) { return preclaimHelper<T>(ctx, *sleIssuer, clawAmount); }, vaultAsset.value());
279}
280
281TER
283{
284 auto const& tx = ctx_.tx;
285 auto const account = tx[sfAccount];
286 auto const findBrokerID = determineBrokerID(view(), tx);
287 if (!findBrokerID)
288 return tecINTERNAL; // LCOV_EXCL_LINE
289 auto const brokerID = *findBrokerID;
290 auto const amount = tx[~sfAmount];
291
292 auto sleBroker = view().peek(keylet::loanbroker(brokerID));
293 if (!sleBroker)
294 return tecINTERNAL; // LCOV_EXCL_LINE
295
296 auto const brokerPseudoID = *sleBroker->at(sfAccount);
297
298 auto const vault = view().read(keylet::vault(sleBroker->at(sfVaultID)));
299 if (!vault)
300 return tecINTERNAL; // LCOV_EXCL_LINE
301
302 auto const vaultAsset = vault->at(sfAsset);
303
304 auto const findClawAmount = determineClawAmount(*sleBroker, vaultAsset, amount);
305 if (!findClawAmount)
306 return tecINTERNAL; // LCOV_EXCL_LINE
307 STAmount const& clawAmount = *findClawAmount;
308 // Just for paranoia's sake
309 if (clawAmount.native())
310 return tecINTERNAL; // LCOV_EXCL_LINE
311
312 // Decrease the LoanBroker's CoverAvailable by Amount
313 sleBroker->at(sfCoverAvailable) -= clawAmount;
314 view().update(sleBroker);
315
316 associateAsset(*sleBroker, vaultAsset);
317
318 // Transfer assets from pseudo-account to depositor.
319 return accountSend(view(), brokerPseudoID, account, clawAmount, j_, WaiveTransferFee::Yes);
320}
321
322//------------------------------------------------------------------------------
323
324} // namespace xrpl
Stream fatal() const
Definition Journal.h:324
Stream warn() const
Definition Journal.h:312
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:416
bool isFlag(std::uint32_t) const
Definition STObject.cpp:486
beast::Journal const j_
Definition Transactor.h:110
ApplyView & view()
Definition Transactor.h:128
ApplyContext & ctx_
Definition Transactor.h:108
Keylet loanbroker(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:498
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:462
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:492
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:160
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ fhIGNORE_FREEZE
Definition View.h:58
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:566
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)
Definition View.cpp:392
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:429
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:2446
TERSubset< CanCvtToTER > TER
Definition TER.h:620
TER preclaimHelper< Issue >(PreclaimContext const &ctx, SLE const &sleIssuer, AccountID const &issuer, AccountID const &holder, STAmount const &clawAmount)
Definition Clawback.cpp:86
@ ahIGNORE_AUTH
Definition View.h:61
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
@ 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
@ lsfAllowTrustLineClawback
@ lsfNoFreeze
@ lsfMPTCanClawback
void associateAsset(STLedgerEntry &sle, Asset const &asset)
Associate an Asset with all sMD_NeedsAsset fields in a ledger entry.
TER preclaimHelper< MPTIssue >(PreclaimContext const &ctx, SLE const &sleIssuer, AccountID const &issuer, AccountID const &holder, STAmount const &clawAmount)
Definition Clawback.cpp:131
@ tesSUCCESS
Definition TER.h:225
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:53
ReadView const & view
Definition Transactor.h:56
beast::Journal const j
Definition Transactor.h:61
State information when preflighting a tx.
Definition Transactor.h:15
T visit(T... args)