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