xrpld
Loading...
Searching...
No Matches
Payment.cpp
1#include <xrpl/tx/transactors/payment/Payment.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/beast/utility/Zero.h>
5#include <xrpl/beast/utility/instrumentation.h>
6#include <xrpl/core/ServiceRegistry.h>
7#include <xrpl/ledger/PaymentSandbox.h>
8#include <xrpl/ledger/ReadView.h>
9#include <xrpl/ledger/helpers/AccountRootHelpers.h>
10#include <xrpl/ledger/helpers/CredentialHelpers.h>
11#include <xrpl/ledger/helpers/MPTokenHelpers.h>
12#include <xrpl/ledger/helpers/PermissionedDEXHelpers.h>
13#include <xrpl/ledger/helpers/TokenHelpers.h>
14#include <xrpl/protocol/AccountID.h>
15#include <xrpl/protocol/Asset.h>
16#include <xrpl/protocol/Feature.h>
17#include <xrpl/protocol/Indexes.h>
18#include <xrpl/protocol/Issue.h>
19#include <xrpl/protocol/LedgerFormats.h>
20#include <xrpl/protocol/MPTIssue.h>
21#include <xrpl/protocol/Permissions.h>
22#include <xrpl/protocol/Quality.h>
23#include <xrpl/protocol/Rate.h>
24#include <xrpl/protocol/SField.h>
25#include <xrpl/protocol/STAmount.h>
26#include <xrpl/protocol/STLedgerEntry.h>
27#include <xrpl/protocol/STPathSet.h>
28#include <xrpl/protocol/STTx.h>
29#include <xrpl/protocol/TER.h>
30#include <xrpl/protocol/TxFlags.h>
31#include <xrpl/protocol/UintTypes.h>
32#include <xrpl/protocol/XRPAmount.h>
33#include <xrpl/protocol/jss.h>
34#include <xrpl/tx/Transactor.h>
35#include <xrpl/tx/applySteps.h>
36#include <xrpl/tx/paths/RippleCalc.h>
37
38#include <algorithm>
39#include <cstdint>
40#include <memory>
41#include <optional>
42#include <unordered_set>
43
44namespace xrpl {
45
48{
49 auto calculateMaxXRPSpend = [](STTx const& tx) -> XRPAmount {
50 STAmount const maxAmount = tx.isFieldPresent(sfSendMax) ? tx[sfSendMax] : tx[sfAmount];
51
52 // If there's no sfSendMax in XRP, and the sfAmount isn't
53 // in XRP, then the transaction does not spend XRP.
54 return maxAmount.native() ? maxAmount.xrp() : beast::kZero;
55 };
56
57 return TxConsequences{ctx.tx, calculateMaxXRPSpend(ctx.tx)};
58}
59
62 AccountID const& account,
63 STAmount const& dstAmount,
64 std::optional<STAmount> const& sendMax)
65{
66 if (sendMax)
67 {
68 return *sendMax;
69 }
70 return dstAmount.asset().visit(
71 [&](MPTIssue const& issue) { return dstAmount; },
72 [&](Issue const& issue) {
73 if (issue.native())
74 return dstAmount;
75 return STAmount(
76 Issue{issue.currency, account},
77 dstAmount.mantissa(),
78 dstAmount.exponent(),
79 dstAmount < beast::kZero);
80 });
81}
82
83bool
85{
86 if (ctx.tx.isFieldPresent(sfCredentialIDs) && !ctx.rules.enabled(featureCredentials))
87 return false;
88 if (ctx.tx.isFieldPresent(sfDomainID) && !ctx.rules.enabled(featurePermissionedDEX))
89 return false;
90
91 return true;
92}
93
96{
97 auto& tx = ctx.tx;
98
99 STAmount const dstAmount(tx.getFieldAmount(sfAmount));
100 bool const isDstMPT = dstAmount.holds<MPTIssue>();
101 bool const mpTokensV2 = ctx.rules.enabled(featureMPTokensV2);
102
103 static constexpr std::uint32_t kTfMptPaymentMaskV1 = ~(tfUniversal | tfPartialPayment);
104 std::uint32_t const paymentMask =
105 (isDstMPT && !mpTokensV2) ? kTfMptPaymentMaskV1 : tfPaymentMask;
106
107 return paymentMask;
108}
109
110NotTEC
112{
113 auto& tx = ctx.tx;
114 auto& j = ctx.j;
115
116 STAmount const dstAmount(tx.getFieldAmount(sfAmount));
117 bool const isDstMPT = dstAmount.holds<MPTIssue>();
118 bool const mpTokensV2 = ctx.rules.enabled(featureMPTokensV2);
119
120 if (!ctx.rules.enabled(featureMPTokensV1) && isDstMPT)
121 return temDISABLED;
122
123 if (!mpTokensV2 && isDstMPT && ctx.tx.isFieldPresent(sfPaths))
124 return temMALFORMED;
125
126 // A zero DomainID is invalid for a PermissionedDomain ledger entry because
127 // keylet::permissionedDomain(uint256) uses the DomainID as the ledger key.
128 if (auto const domainID = tx[~sfDomainID];
129 ctx.rules.enabled(fixCleanup3_2_0) && domainID && *domainID == beast::kZero)
130 return temMALFORMED;
131
132 bool const partialPaymentAllowed = tx.isFlag(tfPartialPayment);
133 bool const limitQuality = tx.isFlag(tfLimitQuality);
134 bool const defaultPathsAllowed = !tx.isFlag(tfNoRippleDirect);
135 bool const hasPaths = tx.isFieldPresent(sfPaths);
136 bool const hasMax = tx.isFieldPresent(sfSendMax);
137
138 auto const deliverMin = tx[~sfDeliverMin];
139
140 auto const account = tx.getAccountID(sfAccount);
141 STAmount const maxSourceAmount = getMaxSourceAmount(account, dstAmount, tx[~sfSendMax]);
142
143 if (!mpTokensV2 &&
144 ((isDstMPT && dstAmount.asset() != maxSourceAmount.asset()) ||
145 (!isDstMPT && maxSourceAmount.holds<MPTIssue>())))
146 {
147 JLOG(j.trace()) << "Malformed transaction: inconsistent issues: " << dstAmount.getFullText()
148 << " " << maxSourceAmount.getFullText() << " "
149 << deliverMin.value_or(STAmount{}).getFullText();
150 return temMALFORMED;
151 }
152
153 auto const& srcAsset = maxSourceAmount.asset();
154 auto const& dstAsset = dstAmount.asset();
155
156 bool const xrpDirect = srcAsset.native() && dstAsset.native();
157
158 if (!isLegalNet(dstAmount) || !isLegalNet(maxSourceAmount))
159 return temBAD_AMOUNT;
160
161 auto const dstAccountID = tx.getAccountID(sfDestination);
162
163 if (!dstAccountID)
164 {
165 JLOG(j.trace()) << "Malformed transaction: "
166 << "Payment destination account not specified.";
167 return temDST_NEEDED;
168 }
169 if (hasMax && maxSourceAmount <= beast::kZero)
170 {
171 JLOG(j.trace()) << "Malformed transaction: bad max amount: "
172 << maxSourceAmount.getFullText();
173 return temBAD_AMOUNT;
174 }
175 if (dstAmount <= beast::kZero)
176 {
177 JLOG(j.trace()) << "Malformed transaction: bad dst amount: " << dstAmount.getFullText();
178 return temBAD_AMOUNT;
179 }
180 auto bad = [&](auto const& asset) {
181 if (ctx.rules.enabled(featureMPTokensV2))
182 return badAsset() == asset;
183 return badCurrency() == asset;
184 };
185 if (bad(srcAsset) || bad(dstAsset))
186 {
187 JLOG(j.trace()) << "Malformed transaction: Bad currency.";
188 return temBAD_CURRENCY;
189 }
190 if (account == dstAccountID && equalTokens(srcAsset, dstAsset) && !hasPaths)
191 {
192 // You're signing yourself a payment.
193 // If hasPaths is true, you might be trying some arbitrage.
194 JLOG(j.trace()) << "Malformed transaction: "
195 << "Redundant payment from " << to_string(account)
196 << " to self without path for " << to_string(dstAsset);
197 return temREDUNDANT;
198 }
199 if (xrpDirect && hasMax)
200 {
201 // Consistent but redundant transaction.
202 JLOG(j.trace()) << "Malformed transaction: "
203 << "SendMax specified for XRP to XRP.";
204 return temBAD_SEND_XRP_MAX;
205 }
206 if ((xrpDirect || (!mpTokensV2 && isDstMPT)) && hasPaths)
207 {
208 // XRP is sent without paths.
209 JLOG(j.trace()) << "Malformed transaction: "
210 << "Paths specified for XRP to XRP or MPT to MPT.";
212 }
213 if (xrpDirect && partialPaymentAllowed)
214 {
215 // Consistent but redundant transaction.
216 JLOG(j.trace()) << "Malformed transaction: "
217 << "Partial payment specified for XRP to XRP.";
219 }
220 if ((xrpDirect || (!mpTokensV2 && isDstMPT)) && limitQuality)
221 {
222 // Consistent but redundant transaction.
223 JLOG(j.trace()) << "Malformed transaction: "
224 << "Limit quality specified for XRP to XRP or MPT to MPT.";
226 }
227 if ((xrpDirect || (!mpTokensV2 && isDstMPT)) && !defaultPathsAllowed)
228 {
229 // Consistent but redundant transaction.
230 JLOG(j.trace()) << "Malformed transaction: "
231 << "No ripple direct specified for XRP to XRP or MPT to MPT.";
233 }
234
235 if (deliverMin)
236 {
237 if (!partialPaymentAllowed)
238 {
239 JLOG(j.trace()) << "Malformed transaction: Partial payment not "
240 "specified for "
241 << jss::DeliverMin.cStr() << ".";
242 return temBAD_AMOUNT;
243 }
244
245 auto const dMin = *deliverMin;
246 if (!isLegalNet(dMin) || dMin <= beast::kZero)
247 {
248 JLOG(j.trace()) << "Malformed transaction: Invalid " << jss::DeliverMin.cStr()
249 << " amount. " << dMin.getFullText();
250 return temBAD_AMOUNT;
251 }
252 if (dMin.asset() != dstAmount.asset())
253 {
254 JLOG(j.trace()) << "Malformed transaction: Dst issue differs "
255 "from "
256 << jss::DeliverMin.cStr() << ". " << dMin.getFullText();
257 return temBAD_AMOUNT;
258 }
259 if (dMin > dstAmount)
260 {
261 JLOG(j.trace()) << "Malformed transaction: Dst amount less than "
262 << jss::DeliverMin.cStr() << ". " << dMin.getFullText();
263 return temBAD_AMOUNT;
264 }
265 }
266
267 if (auto const err = credentials::checkFields(ctx.tx, ctx.j); !isTesSuccess(err))
268 return err;
269
270 return tesSUCCESS;
271}
272
273NotTEC
275 ReadView const& view,
276 STTx const& tx,
277 std::unordered_set<GranularPermissionType> const& heldGranularPermissions)
278{
279 auto const& dstAmount = tx.getFieldAmount(sfAmount);
280 auto const& amountAsset = dstAmount.asset();
281
282 // Granular permissions are only valid for direct payments.
283 if (tx.isFieldPresent(sfSendMax) && tx[sfSendMax].asset() != amountAsset)
285
286 if (isXRP(amountAsset))
288
289 return amountAsset.visit(
290 [&](MPTIssue const& mptIssue) -> NotTEC {
291 // For MPT payments, the MPTokenIssuanceID encodes the issuer unambiguously,
292 // unlike IOU, there is no endpoint aliasing where either side of the
293 // trustline can appear as the issuer.
294 if (heldGranularPermissions.contains(PaymentMint) &&
295 mptIssue.getIssuer() == tx[sfAccount])
296 return tesSUCCESS;
297 if (heldGranularPermissions.contains(PaymentBurn) &&
298 mptIssue.getIssuer() == tx[sfDestination])
299 return tesSUCCESS;
301 },
302 [&](Issue const& issue) -> NotTEC {
303 // For IOU payments, either endpoint may be encoded as the issuer in
304 // sfAmount. PaySteps normalizes those endpoint aliases, so sfAmount.issuer
305 // alone does not reliably identify whether the transaction issues or redeems
306 // IOUs. We determine PaymentMint vs PaymentBurn from the trustline balance
307 // direction instead.
308 auto const account = tx[sfAccount];
309 auto const destination = tx[sfDestination];
310
311 // Reject if neither endpoint is the issuer.
312 if (issue.getIssuer() != account && issue.getIssuer() != destination)
314
315 auto const sle = view.read(keylet::trustLine(account, destination, issue.currency));
316 if (!sle)
318
319 bool const accountIsLow = (account < destination);
320 auto const destLimit = sle->getFieldAmount(accountIsLow ? sfHighLimit : sfLowLimit);
321 auto const rawBalance = sle->getFieldAmount(sfBalance);
322 bool const accountIsHolder =
323 accountIsLow ? rawBalance > beast::kZero : rawBalance < beast::kZero;
324
325 // PaymentMint requires the destination to be the holder and the account to be the
326 // issuer. destLimit > 0: destination is willing to hold account's IOUs (account is the
327 // issuer). !accountIsHolder: DirectStepI will issue, not redeem.
328 if (heldGranularPermissions.contains(PaymentMint) && destLimit > beast::kZero &&
329 !accountIsHolder)
330 return tesSUCCESS;
331
332 // PaymentBurn requires the source account to be the holder and the destination to be
333 // the issuer. accountIsHolder: DirectStepI will redeem, not issue.
334 if (heldGranularPermissions.contains(PaymentBurn) && accountIsHolder)
335 return tesSUCCESS;
336
338 });
339}
340
341TER
343{
344 // Ripple if source or destination is non-native or if there are paths.
345 bool const partialPaymentAllowed = ctx.tx.isFlag(tfPartialPayment);
346 auto const hasPaths = ctx.tx.isFieldPresent(sfPaths);
347 auto const sendMax = ctx.tx[~sfSendMax];
348
349 AccountID const dstAccountID(ctx.tx[sfDestination]);
350 STAmount const dstAmount(ctx.tx[sfAmount]);
351
352 auto const k = keylet::account(dstAccountID);
353 auto const sleDst = ctx.view.read(k);
354
355 if (!sleDst)
356 {
357 // Destination account does not exist.
358 if (!dstAmount.native())
359 {
360 JLOG(ctx.j.trace()) << "Delay transaction: Destination account does not exist.";
361
362 // Another transaction could create the account and then this
363 // transaction would succeed.
364 return tecNO_DST;
365 }
366 if (ctx.view.open() && partialPaymentAllowed)
367 {
368 // You cannot fund an account with a partial payment.
369 // Make retry work smaller, by rejecting this.
370 JLOG(ctx.j.trace()) << "Delay transaction: Partial payment not "
371 "allowed to create account.";
372
373 // Another transaction could create the account and then this
374 // transaction would succeed.
375 return telNO_DST_PARTIAL;
376 }
377 if (dstAmount < STAmount(ctx.view.fees().reserve))
378 {
379 // accountReserve is the minimum amount that an account can have.
380 // Reserve is not scaled by load.
381 JLOG(ctx.j.trace()) << "Delay transaction: Destination account does not exist. "
382 << "Insufficent payment to create account.";
383
384 // TODO: de-dupe
385 // Another transaction could create the account and then this
386 // transaction would succeed.
387 return tecNO_DST_INSUF_XRP;
388 }
389 }
390 else if (sleDst->isFlag(lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag))
391 {
392 // The tag is basically account-specific information we don't
393 // understand, but we can require someone to fill it in.
394
395 // We didn't make this test for a newly-formed account because there's
396 // no way for this field to be set.
397 JLOG(ctx.j.trace()) << "Malformed transaction: DestinationTag required.";
398
399 return tecDST_TAG_NEEDED;
400 }
401
402 // Payment with at least one intermediate step and uses transitive balances.
403 if ((hasPaths || sendMax || !dstAmount.native()) && ctx.view.open())
404 {
405 STPathSet const& paths = ctx.tx.getFieldPathSet(sfPaths);
406
407 if (paths.size() > kMaxPathSize || std::ranges::any_of(paths, [](STPath const& path) {
408 return path.size() > kMaxPathLength;
409 }))
410 {
411 return telBAD_PATH_COUNT;
412 }
413 }
414
415 if (auto const err = credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j);
416 !isTesSuccess(err))
417 return err;
418
419 if (ctx.tx.isFieldPresent(sfDomainID))
420 {
421 if (!permissioned_dex::accountInDomain(ctx.view, ctx.tx[sfAccount], ctx.tx[sfDomainID]))
422 return tecNO_PERMISSION;
423
424 if (!permissioned_dex::accountInDomain(ctx.view, ctx.tx[sfDestination], ctx.tx[sfDomainID]))
425 return tecNO_PERMISSION;
426 }
427
428 return tesSUCCESS;
429}
430
431TER
433{
434 auto const deliverMin = ctx_.tx[~sfDeliverMin];
435
436 // Ripple if source or destination is non-native or if there are paths.
437 bool const partialPaymentAllowed = ctx_.tx.isFlag(tfPartialPayment);
438 bool const limitQuality = ctx_.tx.isFlag(tfLimitQuality);
439 bool const defaultPathsAllowed = !ctx_.tx.isFlag(tfNoRippleDirect);
440 auto const hasPaths = ctx_.tx.isFieldPresent(sfPaths);
441 auto const sendMax = ctx_.tx[~sfSendMax];
442
443 AccountID const dstAccountID(ctx_.tx.getAccountID(sfDestination));
444 STAmount const dstAmount(ctx_.tx.getFieldAmount(sfAmount));
445 bool const isDstMPT = dstAmount.holds<MPTIssue>();
446 STAmount const maxSourceAmount = getMaxSourceAmount(accountID_, dstAmount, sendMax);
447
448 JLOG(j_.trace()) << "maxSourceAmount=" << maxSourceAmount.getFullText()
449 << " dstAmount=" << dstAmount.getFullText();
450
451 // Open a ledger for editing.
452 auto const k = keylet::account(dstAccountID);
453 SLE::pointer sleDst = view().peek(k);
454
455 if (!sleDst)
456 {
457 // Create the account.
458 sleDst = std::make_shared<SLE>(k);
459 sleDst->setAccountID(sfAccount, dstAccountID);
460 sleDst->setFieldU32(sfSequence, view().seq());
461 sleDst->setFieldAmount(sfBalance, XRPAmount(beast::kZero));
462
463 view().insert(sleDst);
464 }
465 else
466 {
467 // Tell the engine that we are intending to change the destination
468 // account. The source account gets always charged a fee so it's always
469 // marked as modified.
470 view().update(sleDst);
471 }
472
473 bool const mpTokensV2 = view().rules().enabled(featureMPTokensV2);
474
475 // Direct MPT payment is handled by payment engine if MPTokensV2 is enabled
476 bool const ripple = (hasPaths || sendMax || !dstAmount.native()) && (!isDstMPT || mpTokensV2);
477
478 if (ripple)
479 {
480 // XRPL payment with at least one intermediate step and uses
481 // transitive balances.
482
483 // An account that requires authorization has two ways to get an
484 // IOU Payment in:
485 // 1. If Account == Destination, or
486 // 2. If Account is deposit preauthorized by destination.
487
488 if (auto err = verifyDepositPreauth(
489 ctx_.tx, ctx_.view(), accountID_, dstAccountID, sleDst, ctx_.journal);
490 !isTesSuccess(err))
491 return err;
492
494 rcInput.partialPaymentAllowed = partialPaymentAllowed;
495 rcInput.defaultPathsAllowed = defaultPathsAllowed;
496 rcInput.limitQuality = limitQuality;
497 rcInput.isLedgerOpen = view().open();
498
500 {
501 PaymentSandbox pv(&view());
502 JLOG(j_.debug()) << "Entering RippleCalc in payment: " << ctx_.tx.getTransactionID();
504 pv,
505 maxSourceAmount,
506 dstAmount,
507 dstAccountID,
509 ctx_.tx.getFieldPathSet(sfPaths),
510 ctx_.tx[~sfDomainID],
511 ctx_.registry,
512 &rcInput);
513 // VFALCO NOTE We might not need to apply, depending
514 // on the TER. But always applying *should*
515 // be safe.
516 pv.apply(ctx_.rawView());
517 }
518
519 // TODO: is this right? If the amount is the correct amount, was
520 // the delivered amount previously set?
521 if (isTesSuccess(rc.result()) && rc.actualAmountOut != dstAmount)
522 {
523 if (deliverMin && rc.actualAmountOut < *deliverMin)
524 {
526 }
527 else
528 {
529 ctx_.deliver(rc.actualAmountOut);
530 }
531 }
532
533 auto terResult = rc.result();
534
535 // Because of its overhead, if RippleCalc
536 // fails with a retry code, claim a fee
537 // instead. Maybe the user will be more
538 // careful with their path spec next time.
539 if (isTerRetry(terResult))
540 terResult = tecPATH_DRY;
541 return terResult;
542 }
543 if (isDstMPT)
544 {
545 JLOG(j_.trace()) << " dstAmount=" << dstAmount.getFullText();
546 auto const& mptIssue = dstAmount.get<MPTIssue>();
547
548 if (auto const ter = requireAuth(view(), mptIssue, accountID_); !isTesSuccess(ter))
549 return ter;
550
551 if (auto const ter = requireAuth(view(), mptIssue, dstAccountID); !isTesSuccess(ter))
552 return ter;
553
554 if (auto const ter = canTransfer(view(), mptIssue, accountID_, dstAccountID);
555 !isTesSuccess(ter))
556 return ter;
557
558 if (auto err = verifyDepositPreauth(
559 ctx_.tx, ctx_.view(), accountID_, dstAccountID, sleDst, ctx_.journal);
560 !isTesSuccess(err))
561 return err;
562
563 auto const& issuer = mptIssue.getIssuer();
564
565 // Transfer rate
566 Rate rate{QUALITY_ONE};
567 // Payment between the holders
568 if (accountID_ != issuer && dstAccountID != issuer)
569 {
570 // If globally/individually locked then
571 // - can't send between holders
572 // - holder can send back to issuer
573 // - issuer can send to holder
574 if (isAnyFrozen(view(), {accountID_, dstAccountID}, mptIssue))
575 return tecLOCKED;
576
577 // Get the rate for a payment between the holders.
578 rate = transferRate(view(), mptIssue.getMptID());
579 }
580
581 // Amount to deliver.
582 STAmount amountDeliver = dstAmount;
583 // Factor in the transfer rate.
584 // No rounding. It'll change once MPT integrated into DEX.
585 STAmount requiredMaxSourceAmount = multiply(dstAmount, rate);
586
587 // Send more than the account wants to pay or less than
588 // the account wants to deliver (if no SendMax).
589 // Adjust the amount to deliver.
590 if (partialPaymentAllowed && requiredMaxSourceAmount > maxSourceAmount)
591 {
592 requiredMaxSourceAmount = maxSourceAmount;
593 // No rounding. It'll change once MPT integrated into DEX.
594 amountDeliver = divide(maxSourceAmount, rate);
595 }
596
597 if (requiredMaxSourceAmount > maxSourceAmount ||
598 (deliverMin && amountDeliver < *deliverMin))
599 return tecPATH_PARTIAL;
600
601 PaymentSandbox pv(&view());
602 auto res = accountSend(pv, accountID_, dstAccountID, amountDeliver, ctx_.journal);
603 if (isTesSuccess(res))
604 {
605 pv.apply(ctx_.rawView());
606
607 // If the actual amount delivered is different from the original
608 // amount due to partial payment or transfer fee, we need to update
609 // DeliveredAmount using the actual delivered amount
610 if (view().rules().enabled(fixMPTDeliveredAmount) && amountDeliver != dstAmount)
611 ctx_.deliver(amountDeliver);
612 }
613 else if (res == tecINSUFFICIENT_FUNDS || res == tecPATH_DRY)
614 {
615 res = tecPATH_PARTIAL;
616 }
617
618 return res;
619 }
620
621 XRPL_ASSERT(dstAmount.native(), "xrpl::Payment::doApply : amount is XRP");
622
623 // Direct XRP payment.
624
625 auto const sleSrc = view().peek(keylet::account(accountID_));
626 if (!sleSrc)
627 return tefINTERNAL; // LCOV_EXCL_LINE
628
629 // ownerCount is the number of entries in this ledger for this
630 // account that require a reserve.
631 auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
632
633 // This is the total reserve in drops.
634 auto const reserve = view().fees().accountReserve(ownerCount);
635
636 // In a delegated payment, the fee payer is the delegated account,
637 // not the source account (accountID_).
638 bool const accountIsPayer = (ctx_.tx.getFeePayer() == accountID_);
639
640 // preFeeBalance_ is the balance on the source account (accountID_) BEFORE the fees
641 // were charged. If source account is the fee payer, it must also cover the fee.
642 // The final spend may use the reserve to cover fees.
643 auto const minRequiredFunds =
644 accountIsPayer ? std::max(reserve, ctx_.tx.getFieldAmount(sfFee).xrp()) : reserve;
645
646 if (preFeeBalance_ < dstAmount.xrp() + minRequiredFunds)
647 {
648 // Vote no. However the transaction might succeed, if applied in
649 // a different order.
650 JLOG(j_.trace()) << "Delay transaction: Insufficient funds: " << to_string(preFeeBalance_)
651 << " / " << to_string(dstAmount.xrp() + minRequiredFunds) << " ("
652 << to_string(reserve) << ")";
653
654 return tecUNFUNDED_PAYMENT;
655 }
656
657 // Pseudo-accounts cannot receive payments, other than these native to
658 // their underlying ledger object - implemented in their respective
659 // transaction types. Note, this is not amendment-gated because all writes
660 // to pseudo-account discriminator fields **are** amendment gated, hence the
661 // behaviour of this check will always match the active amendments.
662 if (isPseudoAccount(sleDst))
663 return tecNO_PERMISSION;
664
665 // The source account does have enough money. Make sure the
666 // source account has authority to deposit to the destination.
667 // An account that requires authorization has three ways to get an XRP
668 // Payment in:
669 // 1. If Account == Destination, or
670 // 2. If Account is deposit preauthorized by destination, or
671 // 3. If the destination's XRP balance is
672 // a. less than or equal to the base reserve and
673 // b. the deposit amount is less than or equal to the base reserve,
674 // then we allow the deposit.
675 //
676 // Rule 3 is designed to keep an account from getting wedged
677 // in an unusable state if it sets the lsfDepositAuth flag and
678 // then consumes all of its XRP. Without the rule if an
679 // account with lsfDepositAuth set spent all of its XRP, it
680 // would be unable to acquire more XRP required to pay fees.
681 //
682 // We choose the base reserve as our bound because it is
683 // a small number that seldom changes but is always sufficient
684 // to get the account un-wedged.
685
686 // Get the base reserve.
687 XRPAmount const dstReserve{view().fees().reserve};
688
689 if (dstAmount > dstReserve || sleDst->getFieldAmount(sfBalance) > dstReserve)
690 {
691 if (auto err = verifyDepositPreauth(
692 ctx_.tx, ctx_.view(), accountID_, dstAccountID, sleDst, ctx_.journal);
693 !isTesSuccess(err))
694 return err;
695 }
696
697 // Do the arithmetic for the transfer and make the ledger change.
698 sleSrc->setFieldAmount(sfBalance, sleSrc->getFieldAmount(sfBalance) - dstAmount);
699 sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(sfBalance) + dstAmount);
700
701 // Re-arm the password change fee if we can and need to.
702 if (sleDst->isFlag(lsfPasswordSpent))
703 sleDst->clearFlag(lsfPasswordSpent);
704
705 return tesSUCCESS;
706}
707
708void
710{
711 // No transaction-specific invariants yet (future work).
712}
713
714bool
716{
717 // No transaction-specific invariants yet (future work).
718 return true;
719}
720
721} // namespace xrpl
T any_of(T... args)
A generic endpoint for log messages.
Definition Journal.h:38
Stream trace() const
Severity stream access functions.
Definition Journal.h:291
virtual SLE::pointer peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void insert(SLE::ref sle)=0
Insert a new state SLE.
virtual void update(SLE::ref sle)=0
Indicate changes to a peeked SLE.
constexpr auto visit(Visitors &&... visitors) const -> decltype(auto)
Definition Asset.h:107
constexpr bool native() const
Definition Asset.h:115
A currency issued by an account.
Definition Issue.h:13
static bool native()
Definition MPTIssue.h:51
AccountID const & getIssuer() const
Definition MPTIssue.cpp:29
A wrapper which makes credits unavailable to balances.
void apply(RawView &to)
Apply changes to base view.
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
Definition Payment.cpp:47
static NotTEC preflight(PreflightContext const &ctx)
Definition Payment.cpp:111
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
Definition Payment.cpp:95
static bool checkExtraFeatures(PreflightContext const &ctx)
Definition Payment.cpp:84
void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override
Inspect a single ledger entry modified by this transaction.
Definition Payment.cpp:709
bool finalizeInvariants(STTx const &tx, TER result, XRPAmount fee, ReadView const &view, beast::Journal const &j) override
Check transaction-specific post-conditions after all entries have been visited.
Definition Payment.cpp:715
static std::size_t const kMaxPathSize
TER doApply() override
Definition Payment.cpp:432
static NotTEC checkGranularSemantics(ReadView const &view, STTx const &tx, std::unordered_set< GranularPermissionType > const &heldGranularPermissions)
Definition Payment.cpp:274
static TER preclaim(PreclaimContext const &ctx)
Definition Payment.cpp:342
A view into a ledger.
Definition ReadView.h:31
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
virtual bool open() const =0
Returns true if this reflects an open ledger.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:171
constexpr bool holds() const noexcept
Definition STAmount.h:460
constexpr TIss const & get() const
std::string getFullText() const override
Definition STAmount.cpp:636
std::uint64_t mantissa() const noexcept
Definition STAmount.h:472
bool native() const noexcept
Definition STAmount.h:453
Asset const & asset() const
Definition STAmount.h:478
int exponent() const noexcept
Definition STAmount.h:441
XRPAmount xrp() const
Definition STAmount.cpp:271
std::shared_ptr< STLedgerEntry > pointer
std::shared_ptr< STLedgerEntry const > const & const_ref
bool isFlag(std::uint32_t) const
Definition STObject.cpp:501
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:454
STPathSet const & getFieldPathSet(SField const &field) const
Definition STObject.cpp:654
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:647
std::vector< STPath >::size_type size() const
Definition STPathSet.h:528
beast::Journal const j_
Definition Transactor.h:118
ApplyView & view()
Definition Transactor.h:136
AccountID const accountID_
Definition Transactor.h:120
XRPAmount preFeeBalance_
Definition Transactor.h:121
ApplyContext & ctx_
Definition Transactor.h:116
Class describing the consequences to the account of applying a transaction if the transaction consume...
Definition applySteps.h:38
static Output rippleCalculate(PaymentSandbox &view, STAmount const &saMaxAmountReq, STAmount const &saDstAmountReq, AccountID const &uDstAccountID, AccountID const &uSrcAccountID, STPathSet const &spsPaths, std::optional< uint256 > const &domainID, ServiceRegistry &registry, Input const *const pInputs=nullptr)
T contains(T... args)
T make_shared(T... args)
T max(T... args)
NotTEC checkFields(STTx const &tx, beast::Journal j)
TER valid(STTx const &tx, ReadView const &view, AccountID const &src, beast::Journal j)
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:186
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:241
bool accountInDomain(ReadView const &view, AccountID const &account, Domain const &domainID)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ telBAD_PATH_COUNT
Definition TER.h:38
@ telNO_DST_PARTIAL
Definition TER.h:42
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:69
@ terNO_DELEGATE_PERMISSION
Definition TER.h:222
bool isTerRetry(TER x) noexcept
Definition TER.h:657
bool isXRP(AccountID const &c)
Definition AccountID.h:70
STAmount getMaxSourceAmount(AccountID const &account, STAmount const &dstAmount, std::optional< STAmount > const &sendMax)
Definition Payment.cpp:61
@ tefINTERNAL
Definition TER.h:163
bool isLegalNet(STAmount const &value)
Definition STAmount.h:598
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to, WaiveMPTCanTransfer waive=WaiveMPTCanTransfer::No, std::uint8_t depth=0)
Check whether to may receive the given MPT from from.
constexpr FlagValue tfUniversal
Definition TxFlags.h:44
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:594
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No, AllowMPTOverflow allowOverflow=AllowMPTOverflow::No)
Calls static accountSendIOU if saAmount represents Issue.
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:28
@ temBAD_SEND_XRP_PATHS
Definition TER.h:89
@ temBAD_CURRENCY
Definition TER.h:76
@ temBAD_SEND_XRP_MAX
Definition TER.h:86
@ temBAD_SEND_XRP_LIMIT
Definition TER.h:85
@ temBAD_SEND_XRP_PARTIAL
Definition TER.h:88
@ temDST_NEEDED
Definition TER.h:95
@ temMALFORMED
Definition TER.h:73
@ temBAD_SEND_XRP_NO_DIRECT
Definition TER.h:87
@ temDISABLED
Definition TER.h:100
@ temBAD_AMOUNT
Definition TER.h:75
@ temREDUNDANT
Definition TER.h:98
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
bool isAnyFrozen(ReadView const &view, std::initializer_list< AccountID > const &accounts, MPTIssue const &mptIssue, std::uint8_t depth=0)
TERSubset< CanCvtToTER > TER
Definition TER.h:634
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, std::uint8_t depth=0)
Check if the account lacks required authorization for MPT.
@ tecLOCKED
Definition TER.h:356
@ tecPATH_PARTIAL
Definition TER.h:280
@ tecUNFUNDED_PAYMENT
Definition TER.h:283
@ tecPATH_DRY
Definition TER.h:292
@ tecNO_DST_INSUF_XRP
Definition TER.h:289
@ tecINSUFFICIENT_FUNDS
Definition TER.h:323
@ tecNO_PERMISSION
Definition TER.h:303
@ tecDST_TAG_NEEDED
Definition TER.h:307
@ tecNO_DST
Definition TER.h:288
STAmount multiply(STAmount const &amount, Number const &frac, Number::RoundingMode rm)
BadAsset const & badAsset()
Definition Asset.h:31
bool isPseudoAccount(SLE::const_pointer sleAcct, std::set< SField const * > const &pseudoFieldFilter={})
Returns true if and only if sleAcct is a pseudo-account or specific pseudo-accounts in pseudoFieldFil...
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
@ tesSUCCESS
Definition TER.h:240
constexpr bool equalTokens(Asset const &lhs, Asset const &rhs)
Definition Asset.h:275
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, SLE::const_ref sleDst, beast::Journal j)
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
XRPAmount reserve
Minimum XRP an account must hold to exist on the ledger.
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:18
beast::Journal const j
Definition Transactor.h:25
Represents a transfer rate.
Definition Rate.h:20
void setResult(TER const value)
Definition RippleCalc.h:62