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 sleIssuer = ctx.view.read(keylet::account(issuerId));
160 if (!sleIssuer)
161 {
162 JLOG(ctx.j.warn())
163 << "Can't receive IOUs from non-existent issuer: "
164 << to_string(issuerId);
165 return tecNO_ISSUER;
166 }
167
168 if (sleIssuer->at(sfFlags) & lsfRequireAuth)
169 {
170 auto const sleTrustLine =
171 ctx.view.read(keylet::line(dstId, issuerId, currency));
172
173 if (!sleTrustLine)
174 {
175 // We can only create a trust line if the issuer does not
176 // have lsfRequireAuth set.
177 return tecNO_AUTH;
178 }
179
180 // Entries have a canonical representation, determined by a
181 // lexicographical "greater than" comparison employing strict
182 // weak ordering. Determine which entry we need to access.
183 bool const canonical_gt(dstId > issuerId);
184
185 bool const is_authorized(
186 sleTrustLine->at(sfFlags) &
187 (canonical_gt ? lsfLowAuth : lsfHighAuth));
188
189 if (!is_authorized)
190 {
191 JLOG(ctx.j.warn())
192 << "Can't receive IOUs from issuer without auth.";
193 return tecNO_AUTH;
194 }
195 }
196
197 // The trustline from source to issuer does not need to
198 // be checked for freezing, since we already verified that the
199 // source has sufficient non-frozen funds available.
200
201 // However, the trustline from destination to issuer may not
202 // be frozen.
203 if (isFrozen(ctx.view, dstId, currency, issuerId))
204 {
205 JLOG(ctx.j.warn()) << "Cashing a check to a frozen trustline.";
206 return tecFROZEN;
207 }
208 }
209 }
210 return tesSUCCESS;
211}
212
213TER
215{
216 // Flow requires that we operate on a PaymentSandbox, rather than
217 // directly on a View.
218 PaymentSandbox psb(&ctx_.view());
219
220 auto sleCheck = psb.peek(keylet::check(ctx_.tx[sfCheckID]));
221 if (!sleCheck)
222 {
223 // LCOV_EXCL_START
224 JLOG(j_.fatal()) << "Precheck did not verify check's existence.";
226 // LCOV_EXCL_STOP
227 }
228
229 AccountID const srcId{sleCheck->getAccountID(sfAccount)};
230 if (!psb.exists(keylet::account(srcId)) ||
232 {
233 // LCOV_EXCL_START
234 JLOG(ctx_.journal.fatal())
235 << "Precheck did not verify source or destination's existence.";
237 // LCOV_EXCL_STOP
238 }
239
240 // Preclaim already checked that source has at least the requested
241 // funds.
242 //
243 // Therefore, if this is a check written to self, (and it shouldn't be)
244 // we know they have sufficient funds to pay the check. Since they are
245 // taking the funds from their own pocket and putting it back in their
246 // pocket no balance will change.
247 //
248 // If it is not a check to self (as should be the case), then there's
249 // work to do...
250 auto viewJ = ctx_.app.journal("View");
251 auto const optDeliverMin = ctx_.tx[~sfDeliverMin];
252
253 if (srcId != account_)
254 {
255 STAmount const sendMax = sleCheck->at(sfSendMax);
256
257 // Flow() doesn't do XRP to XRP transfers.
258 if (sendMax.native())
259 {
260 // Here we need to calculate the amount of XRP src can send.
261 // The amount they have available is their balance minus their
262 // reserve.
263 //
264 // Since (if we're successful) we're about to remove an entry
265 // from src's directory, we allow them to send that additional
266 // incremental reserve amount in the transfer. Hence the -1
267 // argument.
268 STAmount const srcLiquid{xrpLiquid(psb, srcId, -1, viewJ)};
269
270 // Now, how much do they need in order to be successful?
271 STAmount const xrpDeliver{
272 optDeliverMin
273 ? std::max(*optDeliverMin, std::min(sendMax, srcLiquid))
274 : ctx_.tx.getFieldAmount(sfAmount)};
275
276 if (srcLiquid < xrpDeliver)
277 {
278 // Vote no. However the transaction might succeed if applied
279 // in a different order.
280 JLOG(j_.trace()) << "Cash Check: Insufficient XRP: "
281 << srcLiquid.getFullText() << " < "
282 << xrpDeliver.getFullText();
283 return tecUNFUNDED_PAYMENT;
284 }
285
286 if (optDeliverMin)
287 // Set the DeliveredAmount metadata.
288 ctx_.deliver(xrpDeliver);
289
290 // The source account has enough XRP so make the ledger change.
291 if (TER const ter{
292 transferXRP(psb, srcId, account_, xrpDeliver, viewJ)};
293 ter != tesSUCCESS)
294 {
295 // The transfer failed. Return the error code.
296 return ter;
297 }
298 }
299 else
300 {
301 // Note that for DeliverMin we don't know exactly how much
302 // currency we want flow to deliver. We can't ask for the
303 // maximum possible currency because there might be a gateway
304 // transfer rate to account for. Since the transfer rate cannot
305 // exceed 200%, we use 1/2 maxValue as our limit.
306 STAmount const flowDeliver{
307 optDeliverMin ? STAmount(
308 optDeliverMin->issue(),
311 : ctx_.tx.getFieldAmount(sfAmount)};
312
313 // If a trust line does not exist yet create one.
314 Issue const& trustLineIssue = flowDeliver.issue();
315 AccountID const issuer = flowDeliver.getIssuer();
316 AccountID const truster = issuer == account_ ? srcId : account_;
317 Keylet const trustLineKey = keylet::line(truster, trustLineIssue);
318 bool const destLow = issuer > account_;
319
320 if (!psb.exists(trustLineKey))
321 {
322 // 1. Can the check casher meet the reserve for the trust line?
323 // 2. Create trust line between destination (this) account
324 // and the issuer.
325 // 3. Apply correct noRipple settings on trust line. Use...
326 // a. this (destination) account and
327 // b. issuing account (not sending account).
328
329 auto const sleDst = psb.peek(keylet::account(account_));
330
331 // Can the account cover the trust line's reserve?
332 if (std::uint32_t const ownerCount = {sleDst->at(sfOwnerCount)};
333 mPriorBalance < psb.fees().accountReserve(ownerCount + 1))
334 {
335 JLOG(j_.trace()) << "Trust line does not exist. "
336 "Insufficent reserve to create line.";
337
339 }
340
341 Currency const currency = flowDeliver.getCurrency();
342 STAmount initialBalance(flowDeliver.issue());
343 initialBalance.setIssuer(noAccount());
344
345 // clang-format off
346 if (TER const ter = trustCreate(
347 psb, // payment sandbox
348 destLow, // is dest low?
349 issuer, // source
350 account_, // destination
351 trustLineKey.key, // ledger index
352 sleDst, // Account to add to
353 false, // authorize account
354 (sleDst->getFlags() & lsfDefaultRipple) == 0,
355 false, // freeze trust line
356 false, // deep freeze trust line
357 initialBalance, // zero initial balance
358 Issue(currency, account_), // limit of zero
359 0, // quality in
360 0, // quality out
361 viewJ); // journal
362 !isTesSuccess(ter))
363 {
364 return ter;
365 }
366 // clang-format on
367
368 psb.update(sleDst);
369
370 // Note that we _don't_ need to be careful about destroying
371 // the trust line if the check cashing fails. The transaction
372 // machinery will automatically clean it up.
373 }
374
375 // Since the destination is signing the check, they clearly want
376 // the funds even if their new total funds would exceed the limit
377 // on their trust line. So we tweak the trust line limits before
378 // calling flow and then restore the trust line limits afterwards.
379 auto const sleTrustLine = psb.peek(trustLineKey);
380 if (!sleTrustLine)
381 return tecNO_LINE;
382
383 SF_AMOUNT const& tweakedLimit = destLow ? sfLowLimit : sfHighLimit;
384 STAmount const savedLimit = sleTrustLine->at(tweakedLimit);
385
386 // Make sure the tweaked limits are restored when we leave scope.
387 scope_exit fixup(
388 [&psb, &trustLineKey, &tweakedLimit, &savedLimit]() {
389 if (auto const sleTrustLine = psb.peek(trustLineKey))
390 sleTrustLine->at(tweakedLimit) = savedLimit;
391 });
392
393 // Set the trust line limit to the highest possible value
394 // while flow runs.
395 STAmount const bigAmount(
397 sleTrustLine->at(tweakedLimit) = bigAmount;
398
399 // Let flow() do the heavy lifting on a check for an IOU.
400 auto const result = flow(
401 psb,
402 flowDeliver,
403 srcId,
404 account_,
405 STPathSet{},
406 true, // default path
407 static_cast<bool>(optDeliverMin), // partial payment
408 true, // owner pays transfer fee
411 sleCheck->getFieldAmount(sfSendMax),
412 std::nullopt, // check does not support domain
413 viewJ);
414
415 if (result.result() != tesSUCCESS)
416 {
417 JLOG(ctx_.journal.warn()) << "flow failed when cashing check.";
418 return result.result();
419 }
420
421 // Make sure that deliverMin was satisfied.
422 if (optDeliverMin)
423 {
424 if (result.actualAmountOut < *optDeliverMin)
425 {
426 JLOG(ctx_.journal.warn())
427 << "flow did not produce DeliverMin.";
428 return tecPATH_PARTIAL;
429 }
430 }
431
432 // Set the delivered amount metadata in all cases, not just
433 // for DeliverMin.
434 ctx_.deliver(result.actualAmountOut);
435
436 sleCheck = psb.peek(keylet::check(ctx_.tx[sfCheckID]));
437 }
438 }
439
440 // Check was cashed. If not a self send (and it shouldn't be), remove
441 // check link from destination directory.
442 if (srcId != account_ &&
443 !psb.dirRemove(
445 sleCheck->at(sfDestinationNode),
446 sleCheck->key(),
447 true))
448 {
449 // LCOV_EXCL_START
450 JLOG(j_.fatal()) << "Unable to delete check from destination.";
451 return tefBAD_LEDGER;
452 // LCOV_EXCL_STOP
453 }
454
455 // Remove check from check owner's directory.
456 if (!psb.dirRemove(
457 keylet::ownerDir(srcId),
458 sleCheck->at(sfOwnerNode),
459 sleCheck->key(),
460 true))
461 {
462 // LCOV_EXCL_START
463 JLOG(j_.fatal()) << "Unable to delete check from owner.";
464 return tefBAD_LEDGER;
465 // LCOV_EXCL_STOP
466 }
467
468 // If we succeeded, update the check owner's reserve.
469 adjustOwnerCount(psb, psb.peek(keylet::account(srcId)), -1, viewJ);
470
471 // Remove check from ledger.
472 psb.erase(sleCheck);
473
474 psb.apply(ctx_.rawView());
475 return tesSUCCESS;
476}
477
478} // 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.
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.
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