1#include <xrpl/basics/base_uint.h>
2#include <xrpl/beast/utility/WrappedSink.h>
3#include <xrpl/ledger/OrderBookDB.h>
4#include <xrpl/ledger/PaymentSandbox.h>
5#include <xrpl/ledger/helpers/AccountRootHelpers.h>
6#include <xrpl/ledger/helpers/DirectoryHelpers.h>
7#include <xrpl/ledger/helpers/OfferHelpers.h>
8#include <xrpl/ledger/helpers/RippleStateHelpers.h>
9#include <xrpl/protocol/Feature.h>
10#include <xrpl/protocol/STAmount.h>
11#include <xrpl/protocol/TER.h>
12#include <xrpl/protocol/TxFlags.h>
13#include <xrpl/protocol/st.h>
14#include <xrpl/tx/paths/Flow.h>
15#include <xrpl/tx/transactors/dex/OfferCreate.h>
16#include <xrpl/tx/transactors/dex/PermissionedDEXHelpers.h>
23 auto const& amount{tx[sfTakerGets]};
24 return amount.native() ? amount.xrp() : beast::zero;
42 return tfOfferCreateMask;
45 return tfOfferCreateMask | tfHybrid;
56 if (tx.isFlag(tfHybrid) && !tx.isFieldPresent(sfDomainID))
59 bool const bImmediateOrCancel((uTxFlags & tfImmediateOrCancel) != 0u);
60 bool const bFillOrKill((uTxFlags & tfFillOrKill) != 0u);
62 if (bImmediateOrCancel && bFillOrKill)
64 JLOG(j.debug()) <<
"Malformed transaction: both IoC and FoK set.";
68 bool const bHaveExpiration(tx.isFieldPresent(sfExpiration));
70 if (bHaveExpiration && (tx.getFieldU32(sfExpiration) == 0))
72 JLOG(j.debug()) <<
"Malformed offer: bad expiration";
76 if (
auto const cancelSequence = tx[~sfOfferSequence]; cancelSequence && *cancelSequence == 0)
78 JLOG(j.debug()) <<
"Malformed offer: bad cancel sequence";
82 STAmount const saTakerPays = tx[sfTakerPays];
83 STAmount const saTakerGets = tx[sfTakerGets];
90 JLOG(j.debug()) <<
"Malformed offer: redundant (XRP for XRP)";
93 if (saTakerPays <= beast::zero || saTakerGets <= beast::zero)
95 JLOG(j.debug()) <<
"Malformed offer: bad amount";
99 auto const& uPaysIssuerID = saTakerPays.
getIssuer();
100 auto const& uPaysCurrency = saTakerPays.
getCurrency();
102 auto const& uGetsIssuerID = saTakerGets.
getIssuer();
103 auto const& uGetsCurrency = saTakerGets.
getCurrency();
105 if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID)
107 JLOG(j.debug()) <<
"Malformed offer: redundant (IOU for IOU)";
113 JLOG(j.debug()) <<
"Malformed offer: bad currency";
117 if (saTakerPays.
native() != !uPaysIssuerID || saTakerGets.
native() != !uGetsIssuerID)
119 JLOG(j.debug()) <<
"Malformed offer: bad issuer";
129 auto const id = ctx.
tx[sfAccount];
131 auto saTakerPays = ctx.
tx[sfTakerPays];
132 auto saTakerGets = ctx.
tx[sfTakerGets];
134 auto const& uPaysIssuerID = saTakerPays.getIssuer();
135 auto const& uPaysCurrency = saTakerPays.getCurrency();
137 auto const& uGetsIssuerID = saTakerGets.getIssuer();
139 auto const cancelSequence = ctx.
tx[~sfOfferSequence];
145 std::uint32_t const uAccountSequence = sleCreator->getFieldU32(sfSequence);
147 auto viewJ = ctx.
registry.get().getJournal(
"View");
151 JLOG(ctx.
j.
debug()) <<
"Offer involves frozen asset";
157 JLOG(ctx.
j.
debug()) <<
"delay: Offers must be at least partially funded.";
163 if (cancelSequence && (uAccountSequence <= *cancelSequence))
165 JLOG(ctx.
j.
debug()) <<
"uAccountSequenceNext=" << uAccountSequence
166 <<
" uOfferSequence=" << *cancelSequence;
178 if (!saTakerPays.native())
206 XRPL_ASSERT(!
isXRP(issue.
currency),
"xrpl::OfferCreate::checkAcceptAsset : input is not XRP");
212 JLOG(j.
debug()) <<
"delay: can't receive IOUs from non-existent issuer: "
224 if (((*issuerAccount)[sfFlags] & lsfRequireAuth) != 0u)
236 bool const canonical_gt(
id > issue.
account);
238 bool const is_authorized(
239 ((*trustLine)[sfFlags] & (canonical_gt ? lsfLowAuth : lsfHighAuth)) != 0u);
243 JLOG(j.
debug()) <<
"delay: can't receive IOUs from issuer without auth.";
258 bool const deepFrozen = ((*trustLine)[sfFlags] & (lsfLowDeepFreeze | lsfHighDeepFreeze)) != 0u;
272 Amounts
const& takerAmount,
285 if (inStartBalance <= beast::zero)
288 JLOG(
j_.
debug()) <<
"Not crossing: taker is unfunded.";
295 Rate gatewayXferRate{QUALITY_ONE};
300 if (gatewayXferRate.value != QUALITY_ONE)
303 multiplyRound(takerAmount.in, gatewayXferRate, takerAmount.in.issue(),
true);
309 Quality threshold{takerAmount.out, sendMax};
314 if ((txFlags & tfPassive) != 0u)
318 if (sendMax > inStartBalance)
319 sendMax = inStartBalance;
326 if (!takerAmount.in.native() && !takerAmount.out.native())
335 if ((txFlags & tfSell) != 0u)
357 auto const result =
flow(
364 (txFlags & tfFillOrKill) == 0u,
373 for (
auto const& toRemove : result.removableOffers)
382 auto afterCross = takerAmount;
388 if (takerInBalance <= beast::zero)
392 afterCross.in.clear();
393 afterCross.out.clear();
397 STAmount const rate{Quality{takerAmount.out, takerAmount.in}.rate()};
399 if ((txFlags & tfSell) != 0u)
409 STAmount nonGatewayAmountIn = result.actualAmountIn;
410 if (gatewayXferRate.value != QUALITY_ONE)
413 result.actualAmountIn, gatewayXferRate, takerAmount.in.issue(),
true);
416 afterCross.in -= nonGatewayAmountIn;
420 if (afterCross.in < beast::zero)
424 afterCross.in.
clear();
428 divRoundStrict(afterCross.in, rate, takerAmount.out.issue(),
false);
435 afterCross.out -= result.actualAmountOut;
437 afterCross.out >= beast::zero,
438 "xrpl::OfferCreate::flowCross : minimum offer");
439 if (afterCross.out < beast::zero)
440 afterCross.out.clear();
441 afterCross.in =
mulRound(afterCross.out, rate, takerAmount.in.issue(),
true);
451 JLOG(
j_.
error()) <<
"Exception during offer crossing: " << e.
what();
461 txt +=
to_string(amount.issue().currency);
474 if (!sleOffer->isFieldPresent(sfDomainID))
478 sleOffer->setFlag(lsfHybrid);
484 bool const bookExists = sb.
exists(dir);
494 JLOG(
j_.
debug()) <<
"final result: failed to add hybrid offer to open book";
498 STArray bookArr(sfAdditionalBooks, 1);
500 bookInfo.setFieldH256(sfBookDirectory, dir.key);
501 bookInfo.setFieldU64(sfBookNode, *bookNode);
505 ctx_.
registry.get().getOrderBookDB().addOrderBook(book);
507 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
518 bool const bPassive((uTxFlags & tfPassive) != 0u);
519 bool const bImmediateOrCancel((uTxFlags & tfImmediateOrCancel) != 0u);
520 bool const bFillOrKill((uTxFlags & tfFillOrKill) != 0u);
521 bool const bSell((uTxFlags & tfSell) != 0u);
522 bool const bHybrid((uTxFlags & tfHybrid) != 0u);
524 auto saTakerPays =
ctx_.
tx[sfTakerPays];
525 auto saTakerGets =
ctx_.
tx[sfTakerGets];
526 auto const domainID =
ctx_.
tx[~sfDomainID];
528 auto const cancelSequence =
ctx_.
tx[~sfOfferSequence];
537 auto uRate =
getRate(saTakerGets, saTakerPays);
553 JLOG(
j_.
debug()) <<
"Create cancels order " << *cancelSequence;
558 auto const expiration =
ctx_.
tx[~sfExpiration];
567 bool const bOpenLedger = sb.
open();
568 bool crossed =
false;
573 auto const& uPaysIssuerID = saTakerPays.
getIssuer();
574 auto const& uGetsIssuerID = saTakerGets.
getIssuer();
577 if (!
isXRP(uPaysIssuerID))
580 if (sle && sle->isFieldPresent(sfTickSize))
581 uTickSize =
std::min(uTickSize, (*sle)[sfTickSize]);
583 if (!
isXRP(uGetsIssuerID))
586 if (sle && sle->isFieldPresent(sfTickSize))
587 uTickSize =
std::min(uTickSize, (*sle)[sfTickSize]);
589 if (uTickSize < Quality::maxTickSize)
591 auto const rate = Quality{saTakerGets, saTakerPays}.round(uTickSize).rate();
599 saTakerPays =
multiply(saTakerGets, rate, saTakerPays.
issue());
604 saTakerGets =
divide(saTakerPays, rate, saTakerGets.
issue());
606 if (!saTakerGets || !saTakerPays)
608 JLOG(
j_.
debug()) <<
"Offer rounded to zero";
609 return {result,
true};
612 uRate =
getRate(saTakerGets, saTakerPays);
616 Amounts
const takerAmount(saTakerGets, saTakerPays);
618 JLOG(
j_.
debug()) <<
"Attempting cross: " <<
to_string(takerAmount.in.issue()) <<
" -> "
623 stream <<
" mode: " << (bPassive ?
"passive " :
"") << (bSell ?
"sell" :
"buy");
635 std::tie(result, place_offer) =
flowCross(psbFlow, psbCancelFlow, takerAmount, domainID);
637 psbCancelFlow.apply(sbCancel);
643 "xrpl::OfferCreate::applyGuts : result is tesSUCCESS or "
648 stream <<
"Cross result: " <<
transToken(result);
659 return {result,
true};
663 saTakerGets.
issue() == place_offer.in.issue(),
664 "xrpl::OfferCreate::applyGuts : taker gets issue match");
666 saTakerPays.
issue() == place_offer.out.issue(),
667 "xrpl::OfferCreate::applyGuts : taker pays issue match");
669 if (takerAmount != place_offer)
674 if (place_offer.in < zero || place_offer.out < zero)
676 JLOG(
j_.
fatal()) <<
"Cross left offer negative!"
682 if (place_offer.in == zero || place_offer.out == zero)
684 JLOG(
j_.
debug()) <<
"Offer fully crossed!";
685 return {result,
true};
691 saTakerPays = place_offer.out;
692 saTakerGets = place_offer.in;
696 saTakerPays > zero && saTakerGets > zero,
697 "xrpl::OfferCreate::applyGuts : taker pays and gets positive");
702 return {result,
true};
707 stream <<
"Place" << (crossed ?
" remaining " :
" ") <<
"offer:";
716 JLOG(
j_.
trace()) <<
"Fill or Kill: offer killed";
722 if (bImmediateOrCancel)
724 JLOG(
j_.
trace()) <<
"Immediate or cancel: offer canceled";
756 return {result,
true};
764 auto const ownerNode =
770 JLOG(
j_.
debug()) <<
"final result: failed to add offer to owner's directory";
780 << (domainID ? (
" : " +
to_string(*domainID)) :
"");
782 Book const book{saTakerPays.
issue(), saTakerGets.
issue(), domainID};
796 bool const bookExisted =
static_cast<bool>(sb.
peek(dir));
799 sle->setFieldH160(sfTakerPaysCurrency, saTakerPays.
issue().
currency);
800 sle->setFieldH160(sfTakerPaysIssuer, saTakerPays.
issue().
account);
801 sle->setFieldH160(sfTakerGetsCurrency, saTakerGets.
issue().
currency);
802 sle->setFieldH160(sfTakerGetsIssuer, saTakerGets.
issue().
account);
803 sle->setFieldU64(sfExchangeRate, uRate);
805 sle->setFieldH256(sfDomainID, *maybeDomain);
810 setBookDir(sle, domainID);
816 JLOG(
j_.
debug()) <<
"final result: failed to add offer to book";
822 sleOffer->setAccountID(sfAccount,
account_);
823 sleOffer->setFieldU32(sfSequence, offerSequence);
824 sleOffer->setFieldH256(sfBookDirectory, dir.key);
825 sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
826 sleOffer->setFieldAmount(sfTakerGets, saTakerGets);
827 sleOffer->setFieldU64(sfOwnerNode, *ownerNode);
828 sleOffer->setFieldU64(sfBookNode, *bookNode);
830 sleOffer->setFieldU32(sfExpiration, *expiration);
832 sleOffer->setFlag(lsfPassive);
834 sleOffer->setFlag(lsfSell);
836 sleOffer->setFieldH256(sfDomainID, *domainID);
842 applyHybrid(sb, sleOffer, offer_index, saTakerPays, saTakerGets, setBookDir);
850 ctx_.
registry.get().getOrderBookDB().addOrderBook(book);
852 JLOG(
j_.
debug()) <<
"final result: success";
869 auto const result =
applyGuts(sb, sbCancel);
A generic endpoint for log messages.
Stream trace() const
Severity stream access functions.
std::reference_wrapper< ServiceRegistry > registry
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
std::optional< std::uint64_t > dirAppend(Keylet const &directory, Keylet const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Append an entry to a directory.
A currency issued by an account.
static TER preclaim(PreclaimContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
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)
static NotTEC preflight(PreflightContext const &ctx)
Enforce constraints beyond those of the Transactor base class.
TER applyHybrid(Sandbox &sb, std::shared_ptr< STLedgerEntry > sleOffer, Keylet const &offer_index, STAmount const &saTakerPays, STAmount const &saTakerGets, std::function< void(SLE::ref, std::optional< uint256 >)> const &setDir)
static TER checkAcceptAsset(ReadView const &view, ApplyFlags const flags, AccountID const id, beast::Journal const j, Issue const &issue)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
std::pair< TER, bool > applyGuts(Sandbox &view, Sandbox &view_cancel)
static std::string format_amount(STAmount const &amount)
A wrapper which makes credits unavailable to balances.
virtual std::shared_ptr< SLE const > 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.
std::string getFullText() const override
static constexpr std::uint64_t cMaxValue
Issue const & issue() const
static int const cMaxOffset
Currency const & getCurrency() const
bool native() const noexcept
static constexpr std::uint64_t cMaxNative
AccountID const & getIssuer() const
void push_back(STObject const &object)
bool isFieldPresent(SField const &field) const
static STObject makeInnerObject(SField const &name)
std::uint32_t getFlags() const
void emplace_back(Args &&... args)
void emplace_back(Args &&... args)
std::uint32_t getSeqValue() const
Returns the first non-zero value of (Sequence, TicketSequence).
Discardable, editable view to a ledger.
Class describing the consequences to the account of applying a transaction if the transaction consume...
bool open() const override
Returns true if this reflects an open ledger.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
std::shared_ptr< SLE const > 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.
void insert(std::shared_ptr< SLE > const &sle) override
Insert a new state SLE.
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 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 line(AccountID const &id0, AccountID const &id1, Currency const ¤cy) noexcept
The index of a trust line for a given currency.
Keylet account(AccountID const &id) noexcept
AccountID root.
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)
std::string to_string(base_uint< Bits, Tag > const &a)
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
bool isLegalNet(STAmount const &value)
STAmount multiply(STAmount const &amount, Rate const &rate)
std::string transToken(TER code)
Currency const & xrpCurrency()
XRP currency.
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Check if the issuer has the global freeze flag set.
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
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 offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
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)
bool isTecClaim(TER x) noexcept
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
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.