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/OrderBookDB.h>
10#include <xrpl/ledger/PaymentSandbox.h>
11#include <xrpl/ledger/Sandbox.h>
12#include <xrpl/ledger/View.h>
13#include <xrpl/ledger/helpers/AccountRootHelpers.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())
266 XRPL_ASSERT(!
isXRP(asset),
"xrpl::OfferCreate::checkAcceptAsset : input is not XRP");
272 JLOG(j.
debug()) <<
"delay: can't receive IOUs from non-existent issuer: "
291 if (
view.rules().enabled(fixCleanup3_4_0) &&
292 issuerAccount->isFlag(lsfDisallowIncomingTrustline))
296 JLOG(j.
debug()) <<
"delay: can't receive IOUs from issuer with "
297 "DisallowIncomingTrustline set";
302 if (issuerAccount->isFlag(lsfRequireAuth))
313 bool const canonicalGt(
id > issuer);
315 bool const isAuthorized(trustLine->isFlag(canonicalGt ? lsfLowAuth : lsfHighAuth));
319 JLOG(j.
debug()) <<
"delay: can't receive IOUs from "
320 "issuer without auth.";
333 bool const deepFrozen =
334 ((*trustLine)[sfFlags] & (lsfLowDeepFreeze | lsfHighDeepFreeze)) != 0u;
379 auto const disallowUnfunded =
384 JLOG(
j_.debug()) <<
"Not crossing: taker is unfunded.";
391 Rate gatewayXferRate{QUALITY_ONE};
396 if (gatewayXferRate.
value != QUALITY_ONE)
409 if (
ctx_.tx.isFlag(tfPassive))
413 if (sendMax > inStartBalance)
414 sendMax = inStartBalance;
429 auto const& deliverAsset = deliver.
asset();
431 if (
ctx_.tx.isFlag(tfSell))
438 [&](
Issue const& issue) {
457 auto const result =
flow(
464 !
ctx_.tx.isFlag(tfFillOrKill),
473 for (
auto const& toRemove : result.removableOffers)
482 auto afterCross = takerAmount;
497 afterCross.in.clear();
498 afterCross.out.clear();
504 if (
ctx_.tx.isFlag(tfSell))
514 STAmount nonGatewayAmountIn = result.actualAmountIn;
515 if (gatewayXferRate.
value != QUALITY_ONE)
518 result.actualAmountIn, gatewayXferRate, takerAmount.
in.
asset(),
true);
521 afterCross.in -= nonGatewayAmountIn;
529 afterCross.in.clear();
540 afterCross.out -= result.actualAmountOut;
543 "xrpl::OfferCreate::flowCross : minimum offer");
545 afterCross.out.clear();
546 afterCross.in =
mulRound(afterCross.out, rate, takerAmount.
in.
asset(),
true);
556 JLOG(
j_.error()) <<
"Exception during offer crossing: " << e.
what();
582 if (!sleOffer->isFieldPresent(sfDomainID))
586 sleOffer->setFlag(lsfHybrid);
589 Book const book{saTakerPays.
asset(), saTakerGets.
asset(), std::nullopt};
592 bool const bookExists = sb.
exists(dir);
597 setDir(sle, std::nullopt);
602 JLOG(
j_.debug()) <<
"final result: failed to add hybrid offer to open book";
606 STArray bookArr(sfAdditionalBooks, 1);
608 bookInfo.setFieldH256(sfBookDirectory, dir.key);
609 bookInfo.setFieldU64(sfBookNode, *bookNode);
610 bookArr.
pushBack(std::move(bookInfo));
613 ctx_.registry.get().getOrderBookDB().addOrderBook(book);
615 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
624 bool const bPassive(
ctx_.tx.isFlag(tfPassive));
625 bool const bImmediateOrCancel(
ctx_.tx.isFlag(tfImmediateOrCancel));
626 bool const bFillOrKill(
ctx_.tx.isFlag(tfFillOrKill));
627 bool const bSell(
ctx_.tx.isFlag(tfSell));
628 bool const bHybrid(
ctx_.tx.isFlag(tfHybrid));
630 auto saTakerPays =
ctx_.tx[sfTakerPays];
631 auto saTakerGets =
ctx_.tx[sfTakerGets];
632 auto const domainID =
ctx_.tx[~sfDomainID];
634 auto const cancelSequence =
ctx_.tx[~sfOfferSequence];
638 auto const offerSequence =
ctx_.tx.getSeqProxy();
643 auto uRate =
getRate(saTakerGets, saTakerPays);
645 auto viewJ =
ctx_.registry.get().getJournal(
"View");
660 JLOG(
j_.debug()) <<
"Create cancels order " << *cancelSequence;
665 auto const expiration =
ctx_.tx[~sfExpiration];
674 bool crossed =
false;
679 auto const& uPaysIssuerID = saTakerPays.
getIssuer();
680 auto const& uGetsIssuerID = saTakerGets.
getIssuer();
687 if (sle && sle->isFieldPresent(sfTickSize))
688 uTickSize =
std::min(uTickSize, (*sle)[sfTickSize]);
694 if (sle && sle->isFieldPresent(sfTickSize))
695 uTickSize =
std::min(uTickSize, (*sle)[sfTickSize]);
699 auto const rate =
Quality{saTakerGets, saTakerPays}.
round(uTickSize).
rate();
708 saTakerPays =
multiply(saTakerGets, rate, saTakerPays.
asset());
713 saTakerGets =
divide(saTakerPays, rate, saTakerGets.
asset());
715 if (!saTakerGets || !saTakerPays)
717 JLOG(
j_.debug()) <<
"Offer rounded to zero";
718 return {result,
true};
721 uRate =
getRate(saTakerGets, saTakerPays);
725 Amounts const takerAmount(saTakerGets, saTakerPays);
727 JLOG(
j_.debug()) <<
"Attempting cross: " <<
to_string(takerAmount.
in.
asset()) <<
" -> "
730 if (
auto stream =
j_.trace())
732 stream <<
" mode: " << (bPassive ?
"passive " :
"") << (bSell ?
"sell" :
"buy");
744 std::tie(result, placeOffer) =
flowCross(psbFlow, psbCancelFlow, takerAmount, domainID);
746 psbCancelFlow.
apply(sbCancel);
752 "xrpl::OfferCreate::applyGuts : result is tesSUCCESS or "
755 if (
auto stream =
j_.trace())
757 stream <<
"Cross result: " <<
transToken(result);
767 JLOG(
j_.debug()) <<
"final result: " <<
transToken(result);
768 return {result,
true};
773 "xrpl::OfferCreate::applyGuts : taker gets issue match");
776 "xrpl::OfferCreate::applyGuts : taker pays issue match");
778 if (takerAmount != placeOffer)
783 if (placeOffer.
in < kZero || placeOffer.
out < kZero)
785 JLOG(
j_.fatal()) <<
"Cross left offer negative!"
791 if (placeOffer.
in == kZero || placeOffer.
out == kZero)
793 JLOG(
j_.debug()) <<
"Offer fully crossed!";
794 return {result,
true};
800 saTakerPays = placeOffer.
out;
801 saTakerGets = placeOffer.
in;
806 "xrpl::OfferCreate::applyGuts : taker pays and gets positive");
810 JLOG(
j_.debug()) <<
"final result: " <<
transToken(result);
811 return {result,
true};
814 if (
auto stream =
j_.trace())
816 stream <<
"Place" << (crossed ?
" remaining " :
" ") <<
"offer:";
825 JLOG(
j_.trace()) <<
"Fill or Kill: offer killed";
831 if (bImmediateOrCancel)
833 JLOG(
j_.trace()) <<
"Immediate or cancel: offer canceled";
860 JLOG(
j_.debug()) <<
"final result: " <<
transToken(result);
863 return {result,
true};
871 auto const ownerNode =
877 JLOG(
j_.debug()) <<
"final result: failed to add offer to owner's directory";
885 JLOG(
j_.trace()) <<
"adding to book: " <<
to_string(saTakerPays.
asset()) <<
" : "
887 << (domainID ? (
" : " +
to_string(*domainID)) :
"");
889 Book const book{saTakerPays.
asset(), saTakerGets.
asset(), domainID};
903 bool const bookExisted =
static_cast<bool>(sb.
peek(dir));
907 [&](
Issue const& issue) {
908 sle->setFieldH160(sfTakerPaysCurrency, issue.
currency);
909 sle->setFieldH160(sfTakerPaysIssuer, issue.
account);
911 [&](
MPTIssue const& issue) { sle->setFieldH192(sfTakerPaysMPT, issue.getMptID()); });
913 [&](
Issue const& issue) {
914 sle->setFieldH160(sfTakerGetsCurrency, issue.
currency);
915 sle->setFieldH160(sfTakerGetsIssuer, issue.
account);
917 [&](
MPTIssue const& issue) { sle->setFieldH192(sfTakerGetsMPT, issue.getMptID()); });
918 sle->setFieldU64(sfExchangeRate, uRate);
920 sle->setFieldH256(sfDomainID, *maybeDomain);
925 setBookDir(sle, domainID);
931 JLOG(
j_.debug()) <<
"final result: failed to add offer to book";
937 sleOffer->setAccountID(sfAccount,
accountID_);
938 sleOffer->setFieldU32(sfSequence, offerSequence.value());
939 sleOffer->setFieldH256(sfBookDirectory, dir.key);
940 sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
941 sleOffer->setFieldAmount(sfTakerGets, saTakerGets);
942 sleOffer->setFieldU64(sfOwnerNode, *ownerNode);
943 sleOffer->setFieldU64(sfBookNode, *bookNode);
945 sleOffer->setFieldU32(sfExpiration, *expiration);
947 sleOffer->setFlag(lsfPassive);
949 sleOffer->setFlag(lsfSell);
951 sleOffer->setFieldH256(sfDomainID, *domainID);
961 auto const openRate =
ctx_.view().rules().enabled(fixCleanup3_2_0)
963 :
getRate(saTakerGets, saTakerPays);
965 applyHybrid(sb, sleOffer, offerIndex, saTakerPays, saTakerGets, openRate, setBookDir);
973 ctx_.registry.get().getOrderBookDB().addOrderBook(book);
975 JLOG(
j_.debug()) <<
"final result: success";
992 auto const result =
applyGuts(sb, sbCancel);
1001 return result.first;
A generic endpoint for log messages.
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.
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.
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)
std::pair< TER, Amounts > flowCross(PaymentSandbox &psb, PaymentSandbox &psbCancel, Amounts const &takerAmount, std::optional< uint256 > const &domainID)
static bool checkExtraFeatures(PreflightContext const &ctx)
void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref 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.
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)
std::pair< TER, bool > applyGuts(Sandbox &view, Sandbox &viewCancel)
static std::string formatAmount(STAmount const &amount)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
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 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 > const & ref
std::shared_ptr< STLedgerEntry > pointer
std::shared_ptr< STLedgerEntry const > const & const_ref
bool isFieldPresent(SField const &field) const
static STObject makeInnerObject(SField const &name)
void emplaceBack(Args &&... args)
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.
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 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)
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)
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.
TAmounts< STAmount, STAmount > Amounts
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
bool isLegalNet(STAmount const &value)
std::string transToken(TER code)
TER offerDelete(ApplyView &view, SLE::ref sle, beast::Journal j)
Delete an offer.
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.
STAmount multiplyRound(STAmount const &amount, Rate const &rate, bool roundUp)
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Returns a function that sets the owner on a directory SLE.
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
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()
constexpr std::uint64_t kMaxMpTokenAmount
The maximum amount of MPTokenIssuance.
STAmount divideRound(STAmount const &amount, Rate const &rate, bool roundUp)
XRPAmount accountReserve(ReadView const &view, SLE::const_ref sle, beast::Journal j, Adjustment adj={})
Returns the account reserve, in drops.
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.