1#include <xrpl/tx/transactors/payment/Payment.h>
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>
52 STAmount const maxAmount = tx.isFieldPresent(sfSendMax) ? tx[sfSendMax] : tx[sfAmount];
73 [&](
MPTIssue const& issue) {
return dstAmount; },
74 [&](
Issue const& issue) {
78 Issue{issue.currency, account},
101 STAmount const dstAmount(tx.getFieldAmount(sfAmount));
103 bool const mpTokensV2 = ctx.
rules.
enabled(featureMPTokensV2);
107 (isDstMPT && !mpTokensV2) ? kTfMptPaymentMaskV1 : tfPaymentMask;
118 STAmount const dstAmount(tx.getFieldAmount(sfAmount));
120 bool const mpTokensV2 = ctx.
rules.
enabled(featureMPTokensV2);
125 if (tx.isFlag(tfSponsorCreatedAccount))
130 if (tx.isFlag(tfNoRippleDirect) || tx.isFlag(tfPartialPayment) || tx.isFlag(tfLimitQuality))
133 if (tx.isFieldPresent(sfSendMax) || tx.isFieldPresent(sfPaths))
145 if (
auto const domainID = tx[~sfDomainID];
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);
155 auto const deliverMin = tx[~sfDeliverMin];
157 auto const account = tx.getAccountID(sfAccount);
161 ((isDstMPT && dstAmount.
asset() != maxSourceAmount.
asset()) ||
164 JLOG(j.trace()) <<
"Malformed transaction: inconsistent issues: " << dstAmount.
getFullText()
166 << deliverMin.value_or(
STAmount{}).getFullText();
170 auto const& srcAsset = maxSourceAmount.
asset();
171 auto const& dstAsset = dstAmount.
asset();
173 bool const xrpDirect = srcAsset.
native() && dstAsset.native();
178 auto const dstAccountID = tx.getAccountID(sfDestination);
182 JLOG(j.trace()) <<
"Malformed transaction: "
183 <<
"Payment destination account not specified.";
188 JLOG(j.trace()) <<
"Malformed transaction: bad max amount: "
194 JLOG(j.trace()) <<
"Malformed transaction: bad dst amount: " << dstAmount.
getFullText();
197 auto bad = [&](
auto const& asset) {
202 if (bad(srcAsset) || bad(dstAsset))
204 JLOG(j.trace()) <<
"Malformed transaction: Bad currency.";
207 if (account == dstAccountID &&
equalTokens(srcAsset, dstAsset) && !hasPaths)
211 JLOG(j.trace()) <<
"Malformed transaction: "
212 <<
"Redundant payment from " <<
to_string(account)
213 <<
" to self without path for " <<
to_string(dstAsset);
216 if (xrpDirect && hasMax)
219 JLOG(j.trace()) <<
"Malformed transaction: "
220 <<
"SendMax specified for XRP to XRP.";
223 if ((xrpDirect || (!mpTokensV2 && isDstMPT)) && hasPaths)
226 JLOG(j.trace()) <<
"Malformed transaction: "
227 <<
"Paths specified for XRP to XRP or MPT to MPT.";
230 if (xrpDirect && partialPaymentAllowed)
233 JLOG(j.trace()) <<
"Malformed transaction: "
234 <<
"Partial payment specified for XRP to XRP.";
237 if ((xrpDirect || (!mpTokensV2 && isDstMPT)) && limitQuality)
240 JLOG(j.trace()) <<
"Malformed transaction: "
241 <<
"Limit quality specified for XRP to XRP or MPT to MPT.";
244 if ((xrpDirect || (!mpTokensV2 && isDstMPT)) && !defaultPathsAllowed)
247 JLOG(j.trace()) <<
"Malformed transaction: "
248 <<
"No ripple direct specified for XRP to XRP or MPT to MPT.";
254 if (!partialPaymentAllowed)
256 JLOG(j.trace()) <<
"Malformed transaction: Partial payment not "
258 << jss::DeliverMin.cStr() <<
".";
262 auto const dMin = *deliverMin;
265 JLOG(j.trace()) <<
"Malformed transaction: Invalid " << jss::DeliverMin.cStr()
266 <<
" amount. " << dMin.getFullText();
269 if (dMin.asset() != dstAmount.
asset())
271 JLOG(j.trace()) <<
"Malformed transaction: Dst issue differs "
273 << jss::DeliverMin.cStr() <<
". " << dMin.getFullText();
276 if (dMin > dstAmount)
278 JLOG(j.trace()) <<
"Malformed transaction: Dst amount less than "
279 << jss::DeliverMin.cStr() <<
". " << dMin.getFullText();
297 auto const& amountAsset = dstAmount.
asset();
300 if (tx.
isFieldPresent(sfSendMax) && tx[sfSendMax].asset() != amountAsset)
303 if (
isXRP(amountAsset))
306 return amountAsset.visit(
311 if (heldGranularPermissions.
contains(PaymentMint) &&
314 if (heldGranularPermissions.
contains(PaymentBurn) &&
315 mptIssue.
getIssuer() == tx[sfDestination])
325 auto const account = tx[sfAccount];
326 auto const destination = tx[sfDestination];
329 if (issue.getIssuer() != account && issue.getIssuer() != destination)
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 =
342 bool const mayIssue =
348 if (mayIssue && !accountIsHolder)
353 if (heldGranularPermissions.
contains(PaymentBurn) && accountIsHolder)
355 if (
view.rules().enabled(fixCleanup3_4_0))
363 STAmount const held = accountIsLow ? rawBalance : -rawBalance;
364 if (dstAmount <= held || mayIssue)
381 bool const partialPaymentAllowed = ctx.
tx.
isFlag(tfPartialPayment);
383 auto const sendMax = ctx.
tx[~sfSendMax];
385 AccountID const dstAccountID(ctx.
tx[sfDestination]);
389 auto const sleDst = ctx.
view.
read(k);
396 JLOG(ctx.
j.
trace()) <<
"Delay transaction: Destination account does not exist.";
403 if (partialPaymentAllowed)
409 JLOG(ctx.
j.
trace()) <<
"Delay transaction: Partial payment not "
410 "allowed to create account.";
422 if (!ctx.
tx.
isFlag(tfSponsorCreatedAccount))
427 JLOG(ctx.
j.
trace()) <<
"Delay transaction: Destination account does not exist. "
428 <<
"Insufficient payment to create account.";
437 else if (ctx.
tx.
isFlag(tfSponsorCreatedAccount))
444 else if (sleDst->isFlag(lsfRequireDestTag) && !ctx.
tx.
isFieldPresent(sfDestinationTag))
451 JLOG(ctx.
j.
trace()) <<
"Malformed transaction: DestinationTag required.";
457 if (hasPaths || sendMax || !dstAmount.
native())
462 return path.size() > kMaxPathLength;
482 auto const domainID = ctx.
tx[sfDomainID];
490 auto const checkAccount = [&](
AccountID const& acct) ->
TER {
491 if (sleDomain->getAccountID(sfOwner) == acct)
501 if (
auto const err = checkAccount(ctx.
tx[sfAccount]); !
isTesSuccess(err))
503 if (
auto const err = checkAccount(ctx.
tx[sfDestination]); !
isTesSuccess(err))
512 ctx.
view, ctx.
tx[sfDestination], ctx.
tx[sfDomainID]))
525 if (
ctx_.tx.isFieldPresent(sfDomainID) &&
ctx_.view().rules().enabled(fixCleanup3_4_0))
527 auto const domainID =
ctx_.tx[sfDomainID];
532 auto const cleanupFor = [&](
AccountID const& acct) ->
TER {
533 if (sleDomain->getAccountID(sfOwner) == acct)
538 auto const destination =
ctx_.tx[sfDestination];
539 auto const senderErr = cleanupFor(
accountID_);
540 auto const destinationErr =
accountID_ == destination ? senderErr : cleanupFor(destination);
545 return destinationErr;
548 auto const deliverMin =
ctx_.tx[~sfDeliverMin];
551 bool const partialPaymentAllowed =
ctx_.tx.isFlag(tfPartialPayment);
552 bool const limitQuality =
ctx_.tx.isFlag(tfLimitQuality);
553 bool const defaultPathsAllowed = !
ctx_.tx.isFlag(tfNoRippleDirect);
554 auto const hasPaths =
ctx_.tx.isFieldPresent(sfPaths);
555 auto const sendMax =
ctx_.tx[~sfSendMax];
557 AccountID const dstAccountID(
ctx_.tx.getAccountID(sfDestination));
558 STAmount const dstAmount(
ctx_.tx.getFieldAmount(sfAmount));
562 JLOG(
j_.trace()) <<
"maxSourceAmount=" << maxSourceAmount.getFullText()
573 sleDst->setAccountID(sfAccount, dstAccountID);
574 sleDst->setFieldU32(sfSequence,
view().seq());
577 if (
ctx_.tx.isFlag(tfSponsorCreatedAccount))
582 auto const currentSponsoringAccountCount =
583 sponsor->getFieldU32(sfSponsoringAccountCount);
587 JLOG(
j_.fatal()) <<
"Sponsoring account count overflow for account "
592 sponsor->setFieldU32(sfSponsoringAccountCount, currentSponsoringAccountCount + 1);
611 bool const ripple = (hasPaths || sendMax || !dstAmount.
native()) && (!isDstMPT || mpTokensV2);
637 JLOG(
j_.debug()) <<
"Entering RippleCalc in payment: " <<
ctx_.tx.getTransactionID();
644 ctx_.tx.getFieldPathSet(sfPaths),
645 ctx_.tx[~sfDomainID],
668 auto terResult = rc.
result();
680 JLOG(
j_.trace()) <<
" dstAmount=" << dstAmount.
getFullText();
698 auto const& issuer = mptIssue.
getIssuer();
701 Rate rate{QUALITY_ONE};
703 if (
accountID_ != issuer && dstAccountID != issuer)
725 if (partialPaymentAllowed && requiredMaxSourceAmount > maxSourceAmount)
727 requiredMaxSourceAmount = maxSourceAmount;
729 amountDeliver =
divide(maxSourceAmount, rate);
732 if (requiredMaxSourceAmount > maxSourceAmount ||
733 (deliverMin && amountDeliver < *deliverMin))
745 if (
view().rules().enabled(fixMPTDeliveredAmount) && amountDeliver != dstAmount)
746 ctx_.deliver(amountDeliver);
756 XRPL_ASSERT(dstAmount.
native(),
"xrpl::Payment::doApply : amount is XRP");
769 bool const accountIsPayer =
ctx_.tx.getFeePayerID() ==
accountID_;
774 auto const minRequiredFunds =
775 accountIsPayer ?
std::max(reserve,
ctx_.tx.getFieldAmount(sfFee).xrp()) : reserve;
782 <<
" / " <<
to_string(dstAmount.
xrp() + minRequiredFunds) <<
" ("
820 if (dstAmount > dstReserve || sleDst->getFieldAmount(sfBalance) > dstReserve)
829 sleSrc->setFieldAmount(sfBalance, sleSrc->getFieldAmount(sfBalance) - dstAmount);
830 sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(sfBalance) + dstAmount);
833 if (sleDst->isFlag(lsfPasswordSpent))
834 sleDst->clearFlag(lsfPasswordSpent);
A generic endpoint for log messages.
Stream trace() const
Severity stream access functions.
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)
constexpr bool native() const
A currency issued by an account.
AccountID const & getIssuer() const
A wrapper which makes credits unavailable to balances.
void apply(RawView &to)
Apply changes to base view.
void visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override
Inspect a single ledger entry modified by this transaction.
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static bool checkExtraFeatures(PreflightContext const &ctx)
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.
static std::size_t const kMaxPathSize
static NotTEC checkGranularSemantics(ReadView const &view, STTx const &tx, std::unordered_set< GranularPermissionType > const &heldGranularPermissions)
static TER preclaim(PreclaimContext const &ctx)
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.
constexpr bool holds() const noexcept
constexpr TIss const & get() const
std::string getFullText() const override
std::uint64_t mantissa() const noexcept
bool native() const noexcept
Asset const & asset() const
int exponent() const noexcept
std::shared_ptr< STLedgerEntry > pointer
std::shared_ptr< STLedgerEntry const > const & ConstRef
bool isFlag(std::uint32_t) const
bool isFieldPresent(SField const &field) const
STPathSet const & getFieldPathSet(SField const &field) const
STAmount const & getFieldAmount(SField const &field) const
std::vector< STPath >::size_type size() const
AccountID const accountID_
Class describing the consequences to the account of applying a transaction if the transaction consume...
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 ®istry, Input const *const pInputs=nullptr)
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)
TER validDomain(ReadView const &view, UInt256 domainID, AccountID const &subject)
Keylet permissionedDomain(AccountID const &account, SeqProxy const &seq) noexcept
Keylet account(AccountID const &id) noexcept
AccountID root.
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const ¤cy) noexcept
The index of a trust line for a given currency.
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.
STAmount divide(STAmount const &amount, Rate const &rate)
@ terNO_DELEGATE_PERMISSION
bool isTerRetry(TER x) noexcept
bool isXRP(AccountID const &c)
STAmount getMaxSourceAmount(AccountID const &account, STAmount const &dstAmount, std::optional< STAmount > const &sendMax)
bool isLegalNet(STAmount const &value)
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
void addSponsorToLedgerEntry(SLE::Ref sle, SLE::ConstRef sponsorSle, SF_ACCOUNT const &field=sfSponsor)
Stamp a reserve sponsor onto a ledger entry using an explicit sponsor SLE.
std::string to_string(BaseUInt< Bits, Tag > const &a)
TERSubset< CanCvtToNotTEC > NotTEC
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.
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
TER verifyValidDomain(ApplyView &view, AccountID const &account, UInt256 domainID, beast::Journal j)
bool isPseudoAccount(SLE::const_pointer sleAcct)
Returns true if and only if sleAcct is a pseudo-account of any kind (i.e.
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
@ temBAD_SEND_XRP_PARTIAL
@ temBAD_SEND_XRP_NO_DIRECT
bool isTesSuccess(TER x) noexcept
bool isAnyFrozen(ReadView const &view, std::initializer_list< AccountID > const &accounts, MPTIssue const &mptIssue, std::uint8_t depth=0)
TERSubset< CanCvtToTER > TER
XRPAmount accountReserve(ReadView const &view, SLE::ConstRef sle, beast::Journal j, Adjustment adj={})
Returns the account reserve, in drops.
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.
@ tecNO_SPONSOR_PERMISSION
STAmount multiply(STAmount const &amount, Number const &frac, Number::RoundingMode rm)
BadAsset const & badAsset()
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, SLE::ConstRef sleDst, beast::Journal j)
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
constexpr bool equalTokens(Asset const &lhs, Asset const &rhs)
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.
std::optional< UInt256 const > const parentBatchId
State information when preflighting a tx.
Represents a transfer rate.
void setResult(TER const value)