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