1#include <xrpl/tx/transactors/dex/OfferCreate.h>
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/base_uint.h>
5#include <xrpl/beast/utility/Zero.h>
6#include <xrpl/beast/utility/instrumentation.h>
7#include <xrpl/core/ServiceRegistry.h>
8#include <xrpl/ledger/ApplyView.h>
9#include <xrpl/ledger/PaymentSandbox.h>
10#include <xrpl/ledger/Sandbox.h>
11#include <xrpl/ledger/View.h>
12#include <xrpl/ledger/helpers/AccountRootHelpers.h>
13#include <xrpl/ledger/helpers/CredentialHelpers.h>
14#include <xrpl/ledger/helpers/DirectoryHelpers.h>
15#include <xrpl/ledger/helpers/MPTokenHelpers.h>
16#include <xrpl/ledger/helpers/OfferHelpers.h>
17#include <xrpl/ledger/helpers/PermissionedDEXHelpers.h>
18#include <xrpl/ledger/helpers/TokenHelpers.h>
19#include <xrpl/protocol/AccountID.h>
20#include <xrpl/protocol/Asset.h>
21#include <xrpl/protocol/Book.h>
22#include <xrpl/protocol/Feature.h>
23#include <xrpl/protocol/Indexes.h>
24#include <xrpl/protocol/Issue.h>
25#include <xrpl/protocol/Keylet.h>
26#include <xrpl/protocol/LedgerFormats.h>
27#include <xrpl/protocol/MPTIssue.h>
28#include <xrpl/protocol/Protocol.h>
29#include <xrpl/protocol/Quality.h>
30#include <xrpl/protocol/Rate.h>
31#include <xrpl/protocol/SField.h>
32#include <xrpl/protocol/STAmount.h>
33#include <xrpl/protocol/STArray.h>
34#include <xrpl/protocol/STLedgerEntry.h>
35#include <xrpl/protocol/STPathSet.h>
36#include <xrpl/protocol/STTx.h>
37#include <xrpl/protocol/SeqProxy.h>
38#include <xrpl/protocol/TER.h>
39#include <xrpl/protocol/TxFlags.h>
40#include <xrpl/protocol/UintTypes.h>
41#include <xrpl/protocol/XRPAmount.h>
42#include <xrpl/tx/Transactor.h>
43#include <xrpl/tx/applySteps.h>
44#include <xrpl/tx/paths/Flow.h>
45#include <xrpl/tx/paths/detail/Steps.h>
61 auto const& amount{tx[sfTakerGets]};
84 return tfOfferCreateMask;
87 return tfOfferCreateMask | tfHybrid;
96 if (tx.isFlag(tfHybrid) && !tx.isFieldPresent(sfDomainID))
101 if (
auto const domainID = tx[~sfDomainID];
105 bool const bImmediateOrCancel(tx.isFlag(tfImmediateOrCancel));
106 bool const bFillOrKill(tx.isFlag(tfFillOrKill));
108 if (bImmediateOrCancel && bFillOrKill)
110 JLOG(j.debug()) <<
"Malformed transaction: both IoC and FoK set.";
114 bool const bHaveExpiration(tx.isFieldPresent(sfExpiration));
116 if (bHaveExpiration && (tx.getFieldU32(sfExpiration) == 0))
118 JLOG(j.debug()) <<
"Malformed offer: bad expiration";
122 if (
auto const cancelSequence = tx[~sfOfferSequence]; cancelSequence && *cancelSequence == 0)
124 JLOG(j.debug()) <<
"Malformed offer: bad cancel sequence";
128 STAmount const saTakerPays = tx[sfTakerPays];
129 STAmount const saTakerGets = tx[sfTakerGets];
136 JLOG(j.debug()) <<
"Malformed offer: redundant (XRP for XRP)";
141 JLOG(j.debug()) <<
"Malformed offer: bad amount";
145 auto const& uPaysIssuerID = saTakerPays.
getIssuer();
146 auto const& uPaysAsset = saTakerPays.
asset();
148 auto const& uGetsIssuerID = saTakerGets.
getIssuer();
149 auto const& uGetsAsset = saTakerGets.
asset();
151 if (uPaysAsset == uGetsAsset)
153 JLOG(j.debug()) <<
"Malformed offer: redundant (IOU for IOU)";
159 JLOG(j.debug()) <<
"Malformed offer: bad currency";
163 if (saTakerPays.
native() != !uPaysIssuerID || saTakerGets.
native() != !uGetsIssuerID)
165 JLOG(j.debug()) <<
"Malformed offer: bad issuer";
175 auto const id = ctx.
tx[sfAccount];
177 auto saTakerPays = ctx.
tx[sfTakerPays];
178 auto saTakerGets = ctx.
tx[sfTakerGets];
180 auto const& uPaysAsset = saTakerPays.asset();
182 auto const cancelSequence = ctx.
tx[~sfOfferSequence];
188 std::uint32_t const uAccountSequence = sleCreator->getFieldU32(sfSequence);
190 auto viewJ = ctx.
registry.get().getJournal(
"View");
194 JLOG(ctx.
j.
debug()) <<
"Offer involves frozen or locked asset";
199 JLOG(ctx.
j.
debug()) <<
"Offer involves frozen or locked asset";
213 JLOG(ctx.
j.
debug()) <<
"delay: Offers must be at least partially funded.";
219 if (cancelSequence && (uAccountSequence <= *cancelSequence))
221 JLOG(ctx.
j.
debug()) <<
"uAccountSequenceNext=" << uAccountSequence
222 <<
" uOfferSequence=" << *cancelSequence;
234 if (!saTakerPays.native())
247 auto const domainID = ctx.
tx[sfDomainID];
256 if (sleDomain->getAccountID(sfOwner) !=
id)
289 XRPL_ASSERT(!
isXRP(asset),
"xrpl::OfferCreate::checkAcceptAsset : input is not XRP");
295 JLOG(j.
debug()) <<
"delay: can't receive IOUs from non-existent issuer: "
314 if (
view.rules().enabled(fixCleanup3_4_0) &&
315 issuerAccount->isFlag(lsfDisallowIncomingTrustline))
319 JLOG(j.
debug()) <<
"delay: can't receive IOUs from issuer with "
320 "DisallowIncomingTrustline set";
325 if (issuerAccount->isFlag(lsfRequireAuth))
336 bool const canonicalGt(
id > issuer);
338 bool const isAuthorized(trustLine->isFlag(canonicalGt ? lsfLowAuth : lsfHighAuth));
342 JLOG(j.
debug()) <<
"delay: can't receive IOUs from "
343 "issuer without auth.";
356 bool const deepFrozen =
357 ((*trustLine)[sfFlags] & (lsfLowDeepFreeze | lsfHighDeepFreeze)) != 0u;
402 auto const disallowUnfunded =
407 JLOG(
j_.debug()) <<
"Not crossing: taker is unfunded.";
414 Rate gatewayXferRate{QUALITY_ONE};
419 if (gatewayXferRate.
value != QUALITY_ONE)
432 if (
ctx_.tx.isFlag(tfPassive))
436 if (sendMax > inStartBalance)
437 sendMax = inStartBalance;
452 auto const& deliverAsset = deliver.
asset();
454 if (
ctx_.tx.isFlag(tfSell))
461 [&](
Issue const& issue) {
480 auto const result =
flow(
487 !
ctx_.tx.isFlag(tfFillOrKill),
496 for (
auto const& toRemove : result.removableOffers)
505 auto afterCross = takerAmount;
520 afterCross.in.clear();
521 afterCross.out.clear();
527 if (
ctx_.tx.isFlag(tfSell))
537 STAmount nonGatewayAmountIn = result.actualAmountIn;
538 if (gatewayXferRate.
value != QUALITY_ONE)
541 result.actualAmountIn, gatewayXferRate, takerAmount.
in.
asset(),
true);
544 afterCross.in -= nonGatewayAmountIn;
552 afterCross.in.clear();
563 afterCross.out -= result.actualAmountOut;
566 "xrpl::OfferCreate::flowCross : minimum offer");
568 afterCross.out.clear();
569 afterCross.in =
mulRound(afterCross.out, rate, takerAmount.
in.
asset(),
true);
579 JLOG(
j_.error()) <<
"Exception during offer crossing: " << e.
what();
605 if (!sleOffer->isFieldPresent(sfDomainID))
609 sleOffer->setFlag(lsfHybrid);
612 Book const book{saTakerPays.
asset(), saTakerGets.
asset(), std::nullopt};
615 bool const bookExists = sb.
exists(dir);
620 setDir(sle, std::nullopt);
625 JLOG(
j_.debug()) <<
"final result: failed to add hybrid offer to open book";
629 STArray bookArr(sfAdditionalBooks, 1);
631 bookInfo.setFieldH256(sfBookDirectory, dir.key);
632 bookInfo.setFieldU64(sfBookNode, *bookNode);
633 bookArr.
pushBack(std::move(bookInfo));
636 ctx_.addOrderBook(book);
638 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
647 bool const bPassive(
ctx_.tx.isFlag(tfPassive));
648 bool const bImmediateOrCancel(
ctx_.tx.isFlag(tfImmediateOrCancel));
649 bool const bFillOrKill(
ctx_.tx.isFlag(tfFillOrKill));
650 bool const bSell(
ctx_.tx.isFlag(tfSell));
651 bool const bHybrid(
ctx_.tx.isFlag(tfHybrid));
653 auto saTakerPays =
ctx_.tx[sfTakerPays];
654 auto saTakerGets =
ctx_.tx[sfTakerGets];
655 auto const domainID =
ctx_.tx[~sfDomainID];
657 auto const cancelSequence =
ctx_.tx[~sfOfferSequence];
661 auto const offerSequence =
ctx_.tx.getSeqProxy();
666 auto uRate =
getRate(saTakerGets, saTakerPays);
668 auto viewJ =
ctx_.registry.get().getJournal(
"View");
683 JLOG(
j_.debug()) <<
"Create cancels order " << *cancelSequence;
688 auto const expiration =
ctx_.tx[~sfExpiration];
697 bool crossed =
false;
698 bool const mptV2 =
ctx_.view().rules().enabled(featureMPTokensV2);
703 auto const& uPaysIssuerID = saTakerPays.
getIssuer();
704 auto const& uGetsIssuerID = saTakerGets.
getIssuer();
711 if (sle && sle->isFieldPresent(sfTickSize))
712 uTickSize =
std::min(uTickSize, (*sle)[sfTickSize]);
718 if (sle && sle->isFieldPresent(sfTickSize))
719 uTickSize =
std::min(uTickSize, (*sle)[sfTickSize]);
725 bool const unrepresentableRate = mptV2 && uRate == 0;
728 auto const rate =
Quality{saTakerGets, saTakerPays}.
round(uTickSize).
rate();
737 saTakerPays =
multiply(saTakerGets, rate, saTakerPays.
asset());
742 saTakerGets =
divide(saTakerPays, rate, saTakerGets.
asset());
744 if (!saTakerGets || !saTakerPays)
746 JLOG(
j_.debug()) <<
"Offer rounded to zero";
747 return {result,
true};
750 uRate =
getRate(saTakerGets, saTakerPays);
754 Amounts const takerAmount(saTakerGets, saTakerPays);
756 JLOG(
j_.debug()) <<
"Attempting cross: " <<
to_string(takerAmount.
in.
asset()) <<
" -> "
759 if (
auto stream =
j_.trace())
761 stream <<
" mode: " << (bPassive ?
"passive " :
"") << (bSell ?
"sell" :
"buy");
773 std::tie(result, placeOffer) =
flowCross(psbFlow, psbCancelFlow, takerAmount, domainID);
775 psbCancelFlow.
apply(sbCancel);
781 "xrpl::OfferCreate::applyGuts : result is tesSUCCESS or "
784 if (
auto stream =
j_.trace())
786 stream <<
"Cross result: " <<
transToken(result);
796 JLOG(
j_.debug()) <<
"final result: " <<
transToken(result);
797 return {result,
true};
802 "xrpl::OfferCreate::applyGuts : taker gets issue match");
805 "xrpl::OfferCreate::applyGuts : taker pays issue match");
807 if (takerAmount != placeOffer)
812 if (placeOffer.
in < kZero || placeOffer.
out < kZero)
814 JLOG(
j_.fatal()) <<
"Cross left offer negative!"
820 if (placeOffer.
in == kZero || placeOffer.
out == kZero)
822 JLOG(
j_.debug()) <<
"Offer fully crossed!";
823 return {result,
true};
829 saTakerPays = placeOffer.
out;
830 saTakerGets = placeOffer.
in;
835 "xrpl::OfferCreate::applyGuts : taker pays and gets positive");
839 JLOG(
j_.debug()) <<
"final result: " <<
transToken(result);
840 return {result,
true};
843 if (
auto stream =
j_.trace())
845 stream <<
"Place" << (crossed ?
" remaining " :
" ") <<
"offer:";
854 JLOG(
j_.trace()) <<
"Fill or Kill: offer killed";
860 if (bImmediateOrCancel)
862 JLOG(
j_.trace()) <<
"Immediate or cancel: offer canceled";
879 if (mptV2 && uRate == 0)
881 JLOG(
j_.debug()) <<
"Unrepresentable quality: remainder not placed";
903 JLOG(
j_.debug()) <<
"final result: " <<
transToken(result);
906 return {result,
true};
914 auto const ownerNode =
920 JLOG(
j_.debug()) <<
"final result: failed to add offer to owner's directory";
928 JLOG(
j_.trace()) <<
"adding to book: " <<
to_string(saTakerPays.
asset()) <<
" : "
930 << (domainID ? (
" : " +
to_string(*domainID)) :
"");
932 Book const book{saTakerPays.
asset(), saTakerGets.
asset(), domainID};
946 bool const bookExisted =
static_cast<bool>(sb.
peek(dir));
950 [&](
Issue const& issue) {
951 sle->setFieldH160(sfTakerPaysCurrency, issue.
currency);
952 sle->setFieldH160(sfTakerPaysIssuer, issue.
account);
954 [&](
MPTIssue const& issue) { sle->setFieldH192(sfTakerPaysMPT, issue.getMptID()); });
956 [&](
Issue const& issue) {
957 sle->setFieldH160(sfTakerGetsCurrency, issue.
currency);
958 sle->setFieldH160(sfTakerGetsIssuer, issue.
account);
960 [&](
MPTIssue const& issue) { sle->setFieldH192(sfTakerGetsMPT, issue.getMptID()); });
961 sle->setFieldU64(sfExchangeRate, uRate);
963 sle->setFieldH256(sfDomainID, *maybeDomain);
968 setBookDir(sle, domainID);
974 JLOG(
j_.debug()) <<
"final result: failed to add offer to book";
980 sleOffer->setAccountID(sfAccount,
accountID_);
981 sleOffer->setFieldU32(sfSequence, offerSequence.value());
982 sleOffer->setFieldH256(sfBookDirectory, dir.key);
983 sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
984 sleOffer->setFieldAmount(sfTakerGets, saTakerGets);
985 sleOffer->setFieldU64(sfOwnerNode, *ownerNode);
986 sleOffer->setFieldU64(sfBookNode, *bookNode);
988 sleOffer->setFieldU32(sfExpiration, *expiration);
990 sleOffer->setFlag(lsfPassive);
992 sleOffer->setFlag(lsfSell);
994 sleOffer->setFieldH256(sfDomainID, *domainID);
1004 auto const openRate =
ctx_.view().rules().enabled(fixCleanup3_2_0)
1006 :
getRate(saTakerGets, saTakerPays);
1008 applyHybrid(sb, sleOffer, offerIndex, saTakerPays, saTakerGets, openRate, setBookDir);
1016 ctx_.addOrderBook(book);
1018 JLOG(
j_.debug()) <<
"final result: success";
1032 if (
ctx_.tx.isFieldPresent(sfDomainID) &&
ctx_.view().rules().enabled(fixCleanup3_4_0))
1034 auto const domainID =
ctx_.tx[sfDomainID];
1039 if (sleDomain->getAccountID(sfOwner) !=
accountID_)
1056 auto const result =
applyGuts(sb, sbCancel);
1065 return result.first;
A generic endpoint for log messages.
std::optional< std::uint64_t > dirInsert(Keylet const &directory, UInt256 const &key, std::function< void(SLE::Ref)> const &describe)
Insert an entry to a directory.
std::optional< std::uint64_t > dirAppend(Keylet const &directory, Keylet const &key, std::function< void(SLE::Ref)> const &describe)
Append an entry to a directory.
constexpr auto visit(Visitors &&... visitors) const -> decltype(auto)
AccountID const & getIssuer() const
A currency issued by an account.
AccountID const & getIssuer() const
AccountID const & getIssuer() const
static TER preclaim(PreclaimContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
static TER checkAcceptAsset(ReadView const &view, ApplyFlags const flags, AccountID const id, beast::Journal const j, Asset const &asset)
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.
TER doApply() override
Precondition: fee collection is likely.
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
static bool checkExtraFeatures(PreflightContext const &ctx)
void visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override
Inspect a single ledger entry modified by this transaction.
static NotTEC preflight(PreflightContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
std::pair< TER, bool > applyGuts(Sandbox &view, Sandbox &viewCancel)
std::pair< TER, Amounts > flowCross(PaymentSandbox &psb, PaymentSandbox &psbCancel, Amounts const &takerAmount, std::optional< UInt256 > const &domainID)
static std::string formatAmount(STAmount const &amount)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
TER applyHybrid(Sandbox &sb, STLedgerEntry::pointer sleOffer, Keylet const &offerIndex, STAmount const &saTakerPays, STAmount const &saTakerGets, std::uint64_t openRate, std::function< void(SLE::Ref, std::optional< UInt256 >)> const &setDir)
A wrapper which makes credits unavailable to balances.
void apply(RawView &to)
Apply changes to base view.
Represents the logical ratio of output currency to input currency.
static int const kMaxTickSize
STAmount rate() const
Returns the quality as STAmount.
Quality round(int tickSize) const
Returns the quality rounded up to the specified number of decimal digits.
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
bool enabled(UInt256 const &feature) const
Returns true if a feature is enabled.
constexpr bool holds() const noexcept
std::string getFullText() const override
std::string getText() const override
bool integral() const noexcept
bool native() const noexcept
Asset const & asset() const
AccountID const & getIssuer() const
static constexpr std::uint64_t kMaxValue
static constexpr std::uint64_t kMaxNative
static constexpr int kMaxOffset
void pushBack(STObject const &object)
std::shared_ptr< STLedgerEntry > pointer
std::shared_ptr< STLedgerEntry const > const & ConstRef
std::shared_ptr< STLedgerEntry > const & Ref
bool isFieldPresent(SField const &field) const
static STObject makeInnerObject(SField const &name)
bool emplaceBack(Args &&... args)
emplaceBack adds a path to the set.
Discardable, editable view to a ledger.
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
AccountID const accountID_
Class describing the consequences to the account of applying a transaction if the transaction consume...
void insert(SLE::Ref sle) override
Insert a new state SLE.
SLE::pointer peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
bool open() const override
Returns true if this reflects an open ledger.
SLE::const_pointer read(Keylet const &k) const override
Return the state item associated with a key.
bool exists(Keylet const &k) const override
Determine if a state item exists.
TER validDomain(ReadView const &view, UInt256 domainID, AccountID const &subject)
Keylet quality(Keylet const &k, std::uint64_t const q) noexcept
The initial directory page for a specific quality.
Keylet offer(AccountID const &id, SeqProxy const &seq) noexcept
An offer from an account.
Keylet book(Book const &b)
The beginning of an order book.
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
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)
TER offerDelete(ApplyView &view, SLE::Ref sle, beast::Journal j)
Delete an offer.
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp, ExpiryComparison comparison=ExpiryComparison::Inclusive)
Determines whether the given expiration time has passed.
bool isXRP(AccountID const &c)
TAmounts< STAmount, STAmount > Amounts
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
bool isLegalNet(STAmount const &value)
void increaseOwnerCount(ApplyView &view, SLE::Ref accountSle, SLE::Ref sponsorSle, std::uint32_t count, beast::Journal j)
Increase owner-count fields when the caller supplies the sponsor.
std::string transToken(TER code)
Currency const & xrpCurrency()
XRP currency.
std::string to_string(BaseUInt< Bits, Tag > const &a)
TER canTrade(ReadView const &view, Asset const &asset, std::uint8_t depth=0)
Check whether asset may be traded on the DEX.
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
TERSubset< CanCvtToNotTEC > NotTEC
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.
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)
STAmount multiplyRound(STAmount const &amount, Rate const &rate, bool roundUp)
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
TER checkGlobalFrozen(ReadView const &view, Asset const &asset)
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
STAmount mulRound(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
bool isTesSuccess(TER x) noexcept
STAmount divRoundStrict(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
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.
STAmount multiply(STAmount const &amount, Number const &frac, Number::RoundingMode rm)
bool isTecClaim(TER x) noexcept
BadAsset const & badAsset()
std::function< void(SLE::Ref)> describeOwnerDir(AccountID const &account)
Returns a function that sets the owner on a directory SLE.
constexpr std::uint64_t kMaxMpTokenAmount
The maximum amount of MPTokenIssuance.
STAmount divideRound(STAmount const &amount, Rate const &rate, bool roundUp)
A pair of SHAMap key and LedgerEntryType.
State information when determining if a tx is likely to claim a fee.
std::reference_wrapper< ServiceRegistry > registry
State information when preflighting a tx.
Represents a transfer rate.