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/View.h>
12#include <xrpl/ledger/helpers/AccountRootHelpers.h>
13#include <xrpl/ledger/helpers/DirectoryHelpers.h>
14#include <xrpl/ledger/helpers/MPTokenHelpers.h>
15#include <xrpl/ledger/helpers/OfferHelpers.h>
16#include <xrpl/ledger/helpers/PermissionedDEXHelpers.h>
17#include <xrpl/ledger/helpers/TokenHelpers.h>
18#include <xrpl/protocol/AccountID.h>
19#include <xrpl/protocol/Asset.h>
20#include <xrpl/protocol/Book.h>
21#include <xrpl/protocol/Feature.h>
22#include <xrpl/protocol/Indexes.h>
23#include <xrpl/protocol/Issue.h>
24#include <xrpl/protocol/Keylet.h>
25#include <xrpl/protocol/LedgerFormats.h>
26#include <xrpl/protocol/MPTIssue.h>
27#include <xrpl/protocol/Protocol.h>
28#include <xrpl/protocol/Quality.h>
29#include <xrpl/protocol/Rate.h>
30#include <xrpl/protocol/SField.h>
31#include <xrpl/protocol/STAmount.h>
32#include <xrpl/protocol/STArray.h>
33#include <xrpl/protocol/STLedgerEntry.h>
34#include <xrpl/protocol/STPathSet.h>
35#include <xrpl/protocol/STTx.h>
36#include <xrpl/protocol/TER.h>
37#include <xrpl/protocol/TxFlags.h>
38#include <xrpl/protocol/UintTypes.h>
39#include <xrpl/protocol/XRPAmount.h>
40#include <xrpl/tx/Transactor.h>
41#include <xrpl/tx/applySteps.h>
42#include <xrpl/tx/paths/Flow.h>
43#include <xrpl/tx/paths/detail/Steps.h>
59 auto const& amount{tx[sfTakerGets]};
60 return amount.native() ? amount.xrp() : beast::kZero;
82 return tfOfferCreateMask;
85 return tfOfferCreateMask | tfHybrid;
94 if (tx.isFlag(tfHybrid) && !tx.isFieldPresent(sfDomainID))
99 if (
auto const domainID = tx[~sfDomainID];
100 ctx.
rules.
enabled(fixCleanup3_2_0) && domainID && *domainID == beast::kZero)
103 bool const bImmediateOrCancel(tx.isFlag(tfImmediateOrCancel));
104 bool const bFillOrKill(tx.isFlag(tfFillOrKill));
106 if (bImmediateOrCancel && bFillOrKill)
108 JLOG(j.debug()) <<
"Malformed transaction: both IoC and FoK set.";
112 bool const bHaveExpiration(tx.isFieldPresent(sfExpiration));
114 if (bHaveExpiration && (tx.getFieldU32(sfExpiration) == 0))
116 JLOG(j.debug()) <<
"Malformed offer: bad expiration";
120 if (
auto const cancelSequence = tx[~sfOfferSequence]; cancelSequence && *cancelSequence == 0)
122 JLOG(j.debug()) <<
"Malformed offer: bad cancel sequence";
126 STAmount const saTakerPays = tx[sfTakerPays];
127 STAmount const saTakerGets = tx[sfTakerGets];
134 JLOG(j.debug()) <<
"Malformed offer: redundant (XRP for XRP)";
137 if (saTakerPays <= beast::kZero || saTakerGets <= beast::kZero)
139 JLOG(j.debug()) <<
"Malformed offer: bad amount";
143 auto const& uPaysIssuerID = saTakerPays.
getIssuer();
144 auto const& uPaysAsset = saTakerPays.
asset();
146 auto const& uGetsIssuerID = saTakerGets.
getIssuer();
147 auto const& uGetsAsset = saTakerGets.
asset();
149 if (uPaysAsset == uGetsAsset)
151 JLOG(j.debug()) <<
"Malformed offer: redundant (IOU for IOU)";
157 JLOG(j.debug()) <<
"Malformed offer: bad currency";
161 if (saTakerPays.
native() != !uPaysIssuerID || saTakerGets.
native() != !uGetsIssuerID)
163 JLOG(j.debug()) <<
"Malformed offer: bad issuer";
173 auto const id = ctx.
tx[sfAccount];
175 auto saTakerPays = ctx.
tx[sfTakerPays];
176 auto saTakerGets = ctx.
tx[sfTakerGets];
178 auto const& uPaysAsset = saTakerPays.asset();
180 auto const cancelSequence = ctx.
tx[~sfOfferSequence];
186 std::uint32_t const uAccountSequence = sleCreator->getFieldU32(sfSequence);
188 auto viewJ = ctx.
registry.get().getJournal(
"View");
192 JLOG(ctx.
j.
debug()) <<
"Offer involves frozen or locked asset";
197 JLOG(ctx.
j.
debug()) <<
"Offer involves frozen or locked asset";
209 viewJ) <= beast::kZero)
211 JLOG(ctx.
j.
debug()) <<
"delay: Offers must be at least partially funded.";
217 if (cancelSequence && (uAccountSequence <= *cancelSequence))
219 JLOG(ctx.
j.
debug()) <<
"uAccountSequenceNext=" << uAccountSequence
220 <<
" uOfferSequence=" << *cancelSequence;
232 if (!saTakerPays.native())
264 XRPL_ASSERT(!
isXRP(asset),
"xrpl::OfferCreate::checkAcceptAsset : input is not XRP");
270 JLOG(j.
debug()) <<
"delay: can't receive IOUs from non-existent issuer: "
285 if (issuerAccount->isFlag(lsfRequireAuth))
298 bool const canonicalGt(
id > issuer);
300 bool const isAuthorized(trustLine->isFlag(canonicalGt ? lsfLowAuth : lsfHighAuth));
304 JLOG(j.
debug()) <<
"delay: can't receive IOUs from "
305 "issuer without auth.";
320 bool const deepFrozen =
321 ((*trustLine)[sfFlags] & (lsfLowDeepFreeze | lsfHighDeepFreeze)) != 0u;
366 auto const disallowUnfunded =
368 if (disallowUnfunded && inStartBalance <= beast::kZero)
371 JLOG(
j_.debug()) <<
"Not crossing: taker is unfunded.";
378 Rate gatewayXferRate{QUALITY_ONE};
383 if (gatewayXferRate.
value != QUALITY_ONE)
396 if (
ctx_.tx.isFlag(tfPassive))
400 if (sendMax > inStartBalance)
401 sendMax = inStartBalance;
416 auto const& deliverAsset = deliver.
asset();
418 if (
ctx_.tx.isFlag(tfSell))
425 [&](
Issue const& issue) {
444 auto const result =
flow(
451 !
ctx_.tx.isFlag(tfFillOrKill),
460 for (
auto const& toRemove : result.removableOffers)
469 auto afterCross = takerAmount;
480 if (disallowUnfunded && takerInBalance <= beast::kZero)
484 afterCross.in.clear();
485 afterCross.out.clear();
491 if (
ctx_.tx.isFlag(tfSell))
501 STAmount nonGatewayAmountIn = result.actualAmountIn;
502 if (gatewayXferRate.
value != QUALITY_ONE)
505 result.actualAmountIn, gatewayXferRate, takerAmount.
in.
asset(),
true);
508 afterCross.in -= nonGatewayAmountIn;
512 if (afterCross.in < beast::kZero)
516 afterCross.in.clear();
527 afterCross.out -= result.actualAmountOut;
529 afterCross.out >= beast::kZero,
530 "xrpl::OfferCreate::flowCross : minimum offer");
531 if (afterCross.out < beast::kZero)
532 afterCross.out.clear();
533 afterCross.in =
mulRound(afterCross.out, rate, takerAmount.
in.
asset(),
true);
543 JLOG(
j_.error()) <<
"Exception during offer crossing: " << e.
what();
569 if (!sleOffer->isFieldPresent(sfDomainID))
573 sleOffer->setFlag(lsfHybrid);
576 Book const book{saTakerPays.
asset(), saTakerGets.
asset(), std::nullopt};
579 bool const bookExists = sb.
exists(dir);
584 setDir(sle, std::nullopt);
589 JLOG(
j_.debug()) <<
"final result: failed to add hybrid offer to open book";
593 STArray bookArr(sfAdditionalBooks, 1);
595 bookInfo.setFieldH256(sfBookDirectory, dir.key);
596 bookInfo.setFieldU64(sfBookNode, *bookNode);
597 bookArr.
pushBack(std::move(bookInfo));
600 ctx_.registry.get().getOrderBookDB().addOrderBook(book);
602 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
611 bool const bPassive(
ctx_.tx.isFlag(tfPassive));
612 bool const bImmediateOrCancel(
ctx_.tx.isFlag(tfImmediateOrCancel));
613 bool const bFillOrKill(
ctx_.tx.isFlag(tfFillOrKill));
614 bool const bSell(
ctx_.tx.isFlag(tfSell));
615 bool const bHybrid(
ctx_.tx.isFlag(tfHybrid));
617 auto saTakerPays =
ctx_.tx[sfTakerPays];
618 auto saTakerGets =
ctx_.tx[sfTakerGets];
619 auto const domainID =
ctx_.tx[~sfDomainID];
621 auto const cancelSequence =
ctx_.tx[~sfOfferSequence];
625 auto const offerSequence =
ctx_.tx.getSeqValue();
630 auto uRate =
getRate(saTakerGets, saTakerPays);
632 auto viewJ =
ctx_.registry.get().getJournal(
"View");
646 JLOG(
j_.debug()) <<
"Create cancels order " << *cancelSequence;
651 auto const expiration =
ctx_.tx[~sfExpiration];
660 bool crossed =
false;
665 auto const& uPaysIssuerID = saTakerPays.
getIssuer();
666 auto const& uGetsIssuerID = saTakerGets.
getIssuer();
673 if (sle && sle->isFieldPresent(sfTickSize))
674 uTickSize =
std::min(uTickSize, (*sle)[sfTickSize]);
680 if (sle && sle->isFieldPresent(sfTickSize))
681 uTickSize =
std::min(uTickSize, (*sle)[sfTickSize]);
685 auto const rate =
Quality{saTakerGets, saTakerPays}.
round(uTickSize).
rate();
694 saTakerPays =
multiply(saTakerGets, rate, saTakerPays.
asset());
699 saTakerGets =
divide(saTakerPays, rate, saTakerGets.
asset());
701 if (!saTakerGets || !saTakerPays)
703 JLOG(
j_.debug()) <<
"Offer rounded to zero";
704 return {result,
true};
707 uRate =
getRate(saTakerGets, saTakerPays);
711 Amounts const takerAmount(saTakerGets, saTakerPays);
713 JLOG(
j_.debug()) <<
"Attempting cross: " <<
to_string(takerAmount.
in.
asset()) <<
" -> "
716 if (
auto stream =
j_.trace())
718 stream <<
" mode: " << (bPassive ?
"passive " :
"") << (bSell ?
"sell" :
"buy");
730 std::tie(result, placeOffer) =
flowCross(psbFlow, psbCancelFlow, takerAmount, domainID);
732 psbCancelFlow.
apply(sbCancel);
738 "xrpl::OfferCreate::applyGuts : result is tesSUCCESS or "
741 if (
auto stream =
j_.trace())
743 stream <<
"Cross result: " <<
transToken(result);
753 JLOG(
j_.debug()) <<
"final result: " <<
transToken(result);
754 return {result,
true};
759 "xrpl::OfferCreate::applyGuts : taker gets issue match");
762 "xrpl::OfferCreate::applyGuts : taker pays issue match");
764 if (takerAmount != placeOffer)
769 if (placeOffer.
in < kZero || placeOffer.
out < kZero)
771 JLOG(
j_.fatal()) <<
"Cross left offer negative!"
777 if (placeOffer.
in == kZero || placeOffer.
out == kZero)
779 JLOG(
j_.debug()) <<
"Offer fully crossed!";
780 return {result,
true};
786 saTakerPays = placeOffer.
out;
787 saTakerGets = placeOffer.
in;
791 saTakerPays > beast::kZero && saTakerGets > beast::kZero,
792 "xrpl::OfferCreate::applyGuts : taker pays and gets positive");
796 JLOG(
j_.debug()) <<
"final result: " <<
transToken(result);
797 return {result,
true};
800 if (
auto stream =
j_.trace())
802 stream <<
"Place" << (crossed ?
" remaining " :
" ") <<
"offer:";
811 JLOG(
j_.trace()) <<
"Fill or Kill: offer killed";
817 if (bImmediateOrCancel)
819 JLOG(
j_.trace()) <<
"Immediate or cancel: offer canceled";
848 JLOG(
j_.debug()) <<
"final result: " <<
transToken(result);
851 return {result,
true};
859 auto const ownerNode =
865 JLOG(
j_.debug()) <<
"final result: failed to add offer to owner's directory";
873 JLOG(
j_.trace()) <<
"adding to book: " <<
to_string(saTakerPays.
asset()) <<
" : "
875 << (domainID ? (
" : " +
to_string(*domainID)) :
"");
877 Book const book{saTakerPays.
asset(), saTakerGets.
asset(), domainID};
891 bool const bookExisted =
static_cast<bool>(sb.
peek(dir));
895 [&](
Issue const& issue) {
896 sle->setFieldH160(sfTakerPaysCurrency, issue.
currency);
897 sle->setFieldH160(sfTakerPaysIssuer, issue.
account);
899 [&](
MPTIssue const& issue) { sle->setFieldH192(sfTakerPaysMPT, issue.getMptID()); });
901 [&](
Issue const& issue) {
902 sle->setFieldH160(sfTakerGetsCurrency, issue.
currency);
903 sle->setFieldH160(sfTakerGetsIssuer, issue.
account);
905 [&](
MPTIssue const& issue) { sle->setFieldH192(sfTakerGetsMPT, issue.getMptID()); });
906 sle->setFieldU64(sfExchangeRate, uRate);
908 sle->setFieldH256(sfDomainID, *maybeDomain);
913 setBookDir(sle, domainID);
919 JLOG(
j_.debug()) <<
"final result: failed to add offer to book";
925 sleOffer->setAccountID(sfAccount,
accountID_);
926 sleOffer->setFieldU32(sfSequence, offerSequence);
927 sleOffer->setFieldH256(sfBookDirectory, dir.key);
928 sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
929 sleOffer->setFieldAmount(sfTakerGets, saTakerGets);
930 sleOffer->setFieldU64(sfOwnerNode, *ownerNode);
931 sleOffer->setFieldU64(sfBookNode, *bookNode);
933 sleOffer->setFieldU32(sfExpiration, *expiration);
935 sleOffer->setFlag(lsfPassive);
937 sleOffer->setFlag(lsfSell);
939 sleOffer->setFieldH256(sfDomainID, *domainID);
949 auto const openRate =
ctx_.view().rules().enabled(fixCleanup3_2_0)
951 :
getRate(saTakerGets, saTakerPays);
953 applyHybrid(sb, sleOffer, offerIndex, saTakerPays, saTakerGets, openRate, setBookDir);
961 ctx_.registry.get().getOrderBookDB().addOrderBook(book);
963 JLOG(
j_.debug()) <<
"final result: success";
980 auto const result =
applyGuts(sb, sbCancel);
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.
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.
Fees const & fees() const override
Returns the fees for the base ledger.
bool exists(Keylet const &k) const override
Determine if a state item exists.
Keylet quality(Keylet const &k, std::uint64_t q) noexcept
The initial directory page for a specific quality.
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 offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
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 isXRP(AccountID const &c)
TAmounts< STAmount, STAmount > Amounts
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
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)
void adjustOwnerCount(ApplyView &view, SLE::ref sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
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(std::size_t ownerCount) const
Returns the account reserve given the owner count, 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.