rippled
Loading...
Searching...
No Matches
CashCheck.cpp
1#include <xrpld/app/ledger/Ledger.h>
2#include <xrpld/app/paths/Flow.h>
3#include <xrpld/app/tx/detail/CashCheck.h>
4
5#include <xrpl/basics/Log.h>
6#include <xrpl/basics/scope.h>
7#include <xrpl/protocol/Feature.h>
8#include <xrpl/protocol/Indexes.h>
9#include <xrpl/protocol/TER.h>
10#include <xrpl/protocol/TxFlags.h>
11
12#include <algorithm>
13
14namespace ripple {
15
18{
19 // Exactly one of Amount or DeliverMin must be present.
20 auto const optAmount = ctx.tx[~sfAmount];
21 auto const optDeliverMin = ctx.tx[~sfDeliverMin];
22
23 if (static_cast<bool>(optAmount) == static_cast<bool>(optDeliverMin))
24 {
25 JLOG(ctx.j.warn())
26 << "Malformed transaction: "
27 "does not specify exactly one of Amount and DeliverMin.";
28 return temMALFORMED;
29 }
30
31 // Make sure the amount is valid.
32 STAmount const value{optAmount ? *optAmount : *optDeliverMin};
33 if (!isLegalNet(value) || value.signum() <= 0)
34 {
35 JLOG(ctx.j.warn()) << "Malformed transaction: bad amount: "
36 << value.getFullText();
37 return temBAD_AMOUNT;
38 }
39
40 if (badCurrency() == value.getCurrency())
41 {
42 JLOG(ctx.j.warn()) << "Malformed transaction: Bad currency.";
43 return temBAD_CURRENCY;
44 }
45
46 return tesSUCCESS;
47}
48
49TER
51{
52 auto const sleCheck = ctx.view.read(keylet::check(ctx.tx[sfCheckID]));
53 if (!sleCheck)
54 {
55 JLOG(ctx.j.warn()) << "Check does not exist.";
56 return tecNO_ENTRY;
57 }
58
59 // Only cash a check with this account as the destination.
60 AccountID const dstId = sleCheck->at(sfDestination);
61 if (ctx.tx[sfAccount] != dstId)
62 {
63 JLOG(ctx.j.warn()) << "Cashing a check with wrong Destination.";
64 return tecNO_PERMISSION;
65 }
66 AccountID const srcId = sleCheck->at(sfAccount);
67 if (srcId == dstId)
68 {
69 // They wrote a check to themselves. This should be caught when
70 // the check is created, but better late than never.
71 // LCOV_EXCL_START
72 JLOG(ctx.j.error()) << "Malformed transaction: Cashing check to self.";
73 return tecINTERNAL;
74 // LCOV_EXCL_STOP
75 }
76 {
77 auto const sleSrc = ctx.view.read(keylet::account(srcId));
78 auto const sleDst = ctx.view.read(keylet::account(dstId));
79 if (!sleSrc || !sleDst)
80 {
81 // If the check exists this should never occur.
82 JLOG(ctx.j.warn())
83 << "Malformed transaction: source or destination not in ledger";
84 return tecNO_ENTRY;
85 }
86
87 if ((sleDst->getFlags() & lsfRequireDestTag) &&
88 !sleCheck->isFieldPresent(sfDestinationTag))
89 {
90 // The tag is basically account-specific information we don't
91 // understand, but we can require someone to fill it in.
92 JLOG(ctx.j.warn())
93 << "Malformed transaction: DestinationTag required in check.";
94 return tecDST_TAG_NEEDED;
95 }
96 }
97
98 if (hasExpired(ctx.view, sleCheck->at(~sfExpiration)))
99 {
100 JLOG(ctx.j.warn()) << "Cashing a check that has already expired.";
101 return tecEXPIRED;
102 }
103
104 {
105 // Preflight verified exactly one of Amount or DeliverMin is present.
106 // Make sure the requested amount is reasonable.
107 STAmount const value{[](STTx const& tx) {
108 auto const optAmount = tx[~sfAmount];
109 return optAmount ? *optAmount : tx[sfDeliverMin];
110 }(ctx.tx)};
111
112 STAmount const sendMax = sleCheck->at(sfSendMax);
113 Currency const currency{value.getCurrency()};
114 if (currency != sendMax.getCurrency())
115 {
116 JLOG(ctx.j.warn()) << "Check cash does not match check currency.";
117 return temMALFORMED;
118 }
119 AccountID const issuerId{value.getIssuer()};
120 if (issuerId != sendMax.getIssuer())
121 {
122 JLOG(ctx.j.warn()) << "Check cash does not match check issuer.";
123 return temMALFORMED;
124 }
125 if (value > sendMax)
126 {
127 JLOG(ctx.j.warn()) << "Check cashed for more than check sendMax.";
128 return tecPATH_PARTIAL;
129 }
130
131 // Make sure the check owner holds at least value. If they have
132 // less than value the check cannot be cashed.
133 {
134 STAmount availableFunds{accountFunds(
135 ctx.view,
136 sleCheck->at(sfAccount),
137 value,
139 ctx.j)};
140
141 // Note that src will have one reserve's worth of additional XRP
142 // once the check is cashed, since the check's reserve will no
143 // longer be required. So, if we're dealing in XRP, we add one
144 // reserve's worth to the available funds.
145 if (value.native())
146 availableFunds += XRPAmount{ctx.view.fees().increment};
147
148 if (value > availableFunds)
149 {
150 JLOG(ctx.j.warn())
151 << "Check cashed for more than owner's balance.";
152 return tecPATH_PARTIAL;
153 }
154 }
155
156 // An issuer can always accept their own currency.
157 if (!value.native() && (value.getIssuer() != dstId))
158 {
159 auto const sleTrustLine =
160 ctx.view.read(keylet::line(dstId, issuerId, currency));
161
162 if (!sleTrustLine &&
163 !ctx.view.rules().enabled(featureCheckCashMakesTrustLine))
164 {
165 JLOG(ctx.j.warn())
166 << "Cannot cash check for IOU without trustline.";
167 return tecNO_LINE;
168 }
169
170 auto const sleIssuer = ctx.view.read(keylet::account(issuerId));
171 if (!sleIssuer)
172 {
173 JLOG(ctx.j.warn())
174 << "Can't receive IOUs from non-existent issuer: "
175 << to_string(issuerId);
176 return tecNO_ISSUER;
177 }
178
179 if (sleIssuer->at(sfFlags) & lsfRequireAuth)
180 {
181 if (!sleTrustLine)
182 {
183 // We can only create a trust line if the issuer does not
184 // have lsfRequireAuth set.
185 return tecNO_AUTH;
186 }
187
188 // Entries have a canonical representation, determined by a
189 // lexicographical "greater than" comparison employing strict
190 // weak ordering. Determine which entry we need to access.
191 bool const canonical_gt(dstId > issuerId);
192
193 bool const is_authorized(
194 sleTrustLine->at(sfFlags) &
195 (canonical_gt ? lsfLowAuth : lsfHighAuth));
196
197 if (!is_authorized)
198 {
199 JLOG(ctx.j.warn())
200 << "Can't receive IOUs from issuer without auth.";
201 return tecNO_AUTH;
202 }
203 }
204
205 // The trustline from source to issuer does not need to
206 // be checked for freezing, since we already verified that the
207 // source has sufficient non-frozen funds available.
208
209 // However, the trustline from destination to issuer may not
210 // be frozen.
211 if (isFrozen(ctx.view, dstId, currency, issuerId))
212 {
213 JLOG(ctx.j.warn()) << "Cashing a check to a frozen trustline.";
214 return tecFROZEN;
215 }
216 }
217 }
218 return tesSUCCESS;
219}
220
221TER
223{
224 // Flow requires that we operate on a PaymentSandbox, rather than
225 // directly on a View.
226 PaymentSandbox psb(&ctx_.view());
227
228 auto sleCheck = psb.peek(keylet::check(ctx_.tx[sfCheckID]));
229 if (!sleCheck)
230 {
231 // LCOV_EXCL_START
232 JLOG(j_.fatal()) << "Precheck did not verify check's existence.";
234 // LCOV_EXCL_STOP
235 }
236
237 AccountID const srcId{sleCheck->getAccountID(sfAccount)};
238 if (!psb.exists(keylet::account(srcId)) ||
240 {
241 // LCOV_EXCL_START
242 JLOG(ctx_.journal.fatal())
243 << "Precheck did not verify source or destination's existence.";
245 // LCOV_EXCL_STOP
246 }
247
248 // Preclaim already checked that source has at least the requested
249 // funds.
250 //
251 // Therefore, if this is a check written to self, (and it shouldn't be)
252 // we know they have sufficient funds to pay the check. Since they are
253 // taking the funds from their own pocket and putting it back in their
254 // pocket no balance will change.
255 //
256 // If it is not a check to self (as should be the case), then there's
257 // work to do...
258 auto viewJ = ctx_.app.journal("View");
259 auto const optDeliverMin = ctx_.tx[~sfDeliverMin];
260
261 if (srcId != account_)
262 {
263 STAmount const sendMax = sleCheck->at(sfSendMax);
264
265 // Flow() doesn't do XRP to XRP transfers.
266 if (sendMax.native())
267 {
268 // Here we need to calculate the amount of XRP src can send.
269 // The amount they have available is their balance minus their
270 // reserve.
271 //
272 // Since (if we're successful) we're about to remove an entry
273 // from src's directory, we allow them to send that additional
274 // incremental reserve amount in the transfer. Hence the -1
275 // argument.
276 STAmount const srcLiquid{xrpLiquid(psb, srcId, -1, viewJ)};
277
278 // Now, how much do they need in order to be successful?
279 STAmount const xrpDeliver{
280 optDeliverMin
281 ? std::max(*optDeliverMin, std::min(sendMax, srcLiquid))
282 : ctx_.tx.getFieldAmount(sfAmount)};
283
284 if (srcLiquid < xrpDeliver)
285 {
286 // Vote no. However the transaction might succeed if applied
287 // in a different order.
288 JLOG(j_.trace()) << "Cash Check: Insufficient XRP: "
289 << srcLiquid.getFullText() << " < "
290 << xrpDeliver.getFullText();
291 return tecUNFUNDED_PAYMENT;
292 }
293
294 if (optDeliverMin)
295 // Set the DeliveredAmount metadata.
296 ctx_.deliver(xrpDeliver);
297
298 // The source account has enough XRP so make the ledger change.
299 if (TER const ter{
300 transferXRP(psb, srcId, account_, xrpDeliver, viewJ)};
301 ter != tesSUCCESS)
302 {
303 // The transfer failed. Return the error code.
304 return ter;
305 }
306 }
307 else
308 {
309 // Note that for DeliverMin we don't know exactly how much
310 // currency we want flow to deliver. We can't ask for the
311 // maximum possible currency because there might be a gateway
312 // transfer rate to account for. Since the transfer rate cannot
313 // exceed 200%, we use 1/2 maxValue as our limit.
314 STAmount const flowDeliver{
315 optDeliverMin ? STAmount(
316 optDeliverMin->issue(),
319 : ctx_.tx.getFieldAmount(sfAmount)};
320
321 // If a trust line does not exist yet create one.
322 Issue const& trustLineIssue = flowDeliver.issue();
323 AccountID const issuer = flowDeliver.getIssuer();
324 AccountID const truster = issuer == account_ ? srcId : account_;
325 Keylet const trustLineKey = keylet::line(truster, trustLineIssue);
326 bool const destLow = issuer > account_;
327
328 bool const checkCashMakesTrustLine =
329 psb.rules().enabled(featureCheckCashMakesTrustLine);
330
331 if (checkCashMakesTrustLine && !psb.exists(trustLineKey))
332 {
333 // 1. Can the check casher meet the reserve for the trust line?
334 // 2. Create trust line between destination (this) account
335 // and the issuer.
336 // 3. Apply correct noRipple settings on trust line. Use...
337 // a. this (destination) account and
338 // b. issuing account (not sending account).
339
340 auto const sleDst = psb.peek(keylet::account(account_));
341
342 // Can the account cover the trust line's reserve?
343 if (std::uint32_t const ownerCount = {sleDst->at(sfOwnerCount)};
344 mPriorBalance < psb.fees().accountReserve(ownerCount + 1))
345 {
346 JLOG(j_.trace()) << "Trust line does not exist. "
347 "Insufficent reserve to create line.";
348
350 }
351
352 Currency const currency = flowDeliver.getCurrency();
353 STAmount initialBalance(flowDeliver.issue());
354 initialBalance.setIssuer(noAccount());
355
356 // clang-format off
357 if (TER const ter = trustCreate(
358 psb, // payment sandbox
359 destLow, // is dest low?
360 issuer, // source
361 account_, // destination
362 trustLineKey.key, // ledger index
363 sleDst, // Account to add to
364 false, // authorize account
365 (sleDst->getFlags() & lsfDefaultRipple) == 0,
366 false, // freeze trust line
367 false, // deep freeze trust line
368 initialBalance, // zero initial balance
369 Issue(currency, account_), // limit of zero
370 0, // quality in
371 0, // quality out
372 viewJ); // journal
373 !isTesSuccess(ter))
374 {
375 return ter;
376 }
377 // clang-format on
378
379 psb.update(sleDst);
380
381 // Note that we _don't_ need to be careful about destroying
382 // the trust line if the check cashing fails. The transaction
383 // machinery will automatically clean it up.
384 }
385
386 // Since the destination is signing the check, they clearly want
387 // the funds even if their new total funds would exceed the limit
388 // on their trust line. So we tweak the trust line limits before
389 // calling flow and then restore the trust line limits afterwards.
390 auto const sleTrustLine = psb.peek(trustLineKey);
391 if (!sleTrustLine)
392 return tecNO_LINE;
393
394 SF_AMOUNT const& tweakedLimit = destLow ? sfLowLimit : sfHighLimit;
395 STAmount const savedLimit = sleTrustLine->at(tweakedLimit);
396
397 // Make sure the tweaked limits are restored when we leave scope.
398 scope_exit fixup(
399 [&psb, &trustLineKey, &tweakedLimit, &savedLimit]() {
400 if (auto const sleTrustLine = psb.peek(trustLineKey))
401 sleTrustLine->at(tweakedLimit) = savedLimit;
402 });
403
404 if (checkCashMakesTrustLine)
405 {
406 // Set the trust line limit to the highest possible value
407 // while flow runs.
408 STAmount const bigAmount(
410 sleTrustLine->at(tweakedLimit) = bigAmount;
411 }
412
413 // Let flow() do the heavy lifting on a check for an IOU.
414 auto const result = flow(
415 psb,
416 flowDeliver,
417 srcId,
418 account_,
419 STPathSet{},
420 true, // default path
421 static_cast<bool>(optDeliverMin), // partial payment
422 true, // owner pays transfer fee
425 sleCheck->getFieldAmount(sfSendMax),
426 std::nullopt, // check does not support domain
427 viewJ);
428
429 if (result.result() != tesSUCCESS)
430 {
431 JLOG(ctx_.journal.warn()) << "flow failed when cashing check.";
432 return result.result();
433 }
434
435 // Make sure that deliverMin was satisfied.
436 if (optDeliverMin)
437 {
438 if (result.actualAmountOut < *optDeliverMin)
439 {
440 JLOG(ctx_.journal.warn())
441 << "flow did not produce DeliverMin.";
442 return tecPATH_PARTIAL;
443 }
444 if (!checkCashMakesTrustLine)
445 // Set the delivered_amount metadata.
446 ctx_.deliver(result.actualAmountOut);
447 }
448
449 // Set the delivered amount metadata in all cases, not just
450 // for DeliverMin.
451 if (checkCashMakesTrustLine)
452 ctx_.deliver(result.actualAmountOut);
453
454 sleCheck = psb.peek(keylet::check(ctx_.tx[sfCheckID]));
455 }
456 }
457
458 // Check was cashed. If not a self send (and it shouldn't be), remove
459 // check link from destination directory.
460 if (srcId != account_ &&
461 !psb.dirRemove(
463 sleCheck->at(sfDestinationNode),
464 sleCheck->key(),
465 true))
466 {
467 // LCOV_EXCL_START
468 JLOG(j_.fatal()) << "Unable to delete check from destination.";
469 return tefBAD_LEDGER;
470 // LCOV_EXCL_STOP
471 }
472
473 // Remove check from check owner's directory.
474 if (!psb.dirRemove(
475 keylet::ownerDir(srcId),
476 sleCheck->at(sfOwnerNode),
477 sleCheck->key(),
478 true))
479 {
480 // LCOV_EXCL_START
481 JLOG(j_.fatal()) << "Unable to delete check from owner.";
482 return tefBAD_LEDGER;
483 // LCOV_EXCL_STOP
484 }
485
486 // If we succeeded, update the check owner's reserve.
487 adjustOwnerCount(psb, psb.peek(keylet::account(srcId)), -1, viewJ);
488
489 // Remove check from ledger.
490 psb.erase(sleCheck);
491
492 psb.apply(ctx_.rawView());
493 return tesSUCCESS;
494}
495
496} // namespace ripple
Stream fatal() const
Definition Journal.h:333
Stream error() const
Definition Journal.h:327
Stream trace() const
Severity stream access functions.
Definition Journal.h:303
Stream warn() const
Definition Journal.h:321
virtual beast::Journal journal(std::string const &name)=0
ApplyView & view()
Application & app
beast::Journal const journal
void deliver(STAmount const &amount)
Sets the DeliveredAmount field in the metadata.
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
static TER preclaim(PreclaimContext const &ctx)
Definition CashCheck.cpp:50
TER doApply() override
static NotTEC preflight(PreflightContext const &ctx)
Definition CashCheck.cpp:17
A currency issued by an account.
Definition Issue.h:14
A wrapper which makes credits unavailable to balances.
void apply(RawView &to)
Apply changes to base view.
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:111
void setIssuer(AccountID const &uIssuer)
Definition STAmount.h:569
static int const cMaxOffset
Definition STAmount.h:47
Currency const & getCurrency() const
Definition STAmount.h:483
static std::uint64_t const cMaxValue
Definition STAmount.h:51
AccountID const & getIssuer() const
Definition STAmount.h:489
bool native() const noexcept
Definition STAmount.h:439
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:652
AccountID const account_
Definition Transactor.h:128
beast::Journal const j_
Definition Transactor.h:126
XRPAmount mPriorBalance
Definition Transactor.h:129
ApplyContext & ctx_
Definition Transactor.h:124
Fees const & fees() const override
Returns the fees for the base ledger.
void erase(std::shared_ptr< SLE > const &sle) override
Remove a peeked SLE.
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
bool exists(Keylet const &k) const override
Determine if a state item exists.
Rules const & rules() const override
Returns the tx processing rules.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
T is_same_v
T max(T... args)
T min(T... args)
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:225
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:355
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:317
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
AccountID const & noAccount()
A placeholder for empty accounts.
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:535
@ fhZERO_IF_FROZEN
Definition View.h:58
bool isLegalNet(STAmount const &value)
Definition STAmount.h:581
@ lsfRequireDestTag
@ lsfDefaultRipple
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1013
TER transferXRP(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &amount, beast::Journal j)
Definition View.cpp:2421
StrandResult< TInAmt, TOutAmt > flow(PaymentSandbox const &baseView, Strand const &strand, std::optional< TInAmt > const &maxIn, TOutAmt const &out, beast::Journal j)
Request out amount from a strand.
Definition StrandFlow.h:86
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:228
@ tefBAD_LEDGER
Definition TER.h:151
@ no
Definition Steps.h:26
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:154
@ tecNO_ENTRY
Definition TER.h:288
@ tecNO_LINE_INSUF_RESERVE
Definition TER.h:274
@ tecNO_ISSUER
Definition TER.h:281
@ tecFROZEN
Definition TER.h:285
@ tecINTERNAL
Definition TER.h:292
@ tecNO_PERMISSION
Definition TER.h:287
@ tecDST_TAG_NEEDED
Definition TER.h:291
@ tecPATH_PARTIAL
Definition TER.h:264
@ tecNO_LINE
Definition TER.h:283
@ tecUNFUNDED_PAYMENT
Definition TER.h:267
@ tecFAILED_PROCESSING
Definition TER.h:268
@ tecEXPIRED
Definition TER.h:296
@ tecNO_AUTH
Definition TER.h:282
@ tesSUCCESS
Definition TER.h:226
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
TER trustCreate(ApplyView &view, bool const bSrcHigh, AccountID const &uSrcAccountID, AccountID const &uDstAccountID, uint256 const &uIndex, SLE::ref sleAccount, bool const bAuth, bool const bNoRipple, bool const bFreeze, bool bDeepFreeze, STAmount const &saBalance, STAmount const &saLimit, std::uint32_t uSrcQualityIn, std::uint32_t uSrcQualityOut, beast::Journal j)
Create a trust line.
Definition View.cpp:1379
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:590
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:599
@ temBAD_AMOUNT
Definition TER.h:70
@ temBAD_CURRENCY
Definition TER.h:71
@ temMALFORMED
Definition TER.h:68
XRPAmount increment
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
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
beast::Journal const j
Definition Transactor.h:23
A field with a type known at compile time.
Definition SField.h:301