#pragma once #include #include #include #include #include #include #include #include namespace xrpl { template class TOfferBase { protected: Issue issIn_; Issue issOut_; }; template <> class TOfferBase { public: explicit TOfferBase() = default; }; template class TOffer : private TOfferBase { private: SLE::pointer m_entry; Quality m_quality; AccountID m_account; TAmounts m_amounts; void setFieldAmounts(); public: TOffer() = default; TOffer(SLE::pointer const& entry, Quality quality); /** Returns the quality of the offer. Conceptually, the quality is the ratio of output to input currency. The implementation calculates it as the ratio of input to output currency (so it sorts ascending). The quality is computed at the time the offer is placed, and never changes for the lifetime of the offer. This is an important business rule that maintains accuracy when an offer is partially filled; Subsequent partial fills will use the original quality. */ Quality quality() const noexcept { return m_quality; } /** Returns the account id of the offer's owner. */ AccountID const& owner() const { return m_account; } /** Returns the in and out amounts. Some or all of the out amount may be unfunded. */ TAmounts const& amount() const { return m_amounts; } /** Returns `true` if no more funds can flow through this offer. */ bool fully_consumed() const { if (m_amounts.in <= beast::zero) return true; if (m_amounts.out <= beast::zero) return true; return false; } /** Adjusts the offer to indicate that we consumed some (or all) of it. */ void consume(ApplyView& view, TAmounts const& consumed) { if (consumed.in > m_amounts.in) Throw("can't consume more than is available."); if (consumed.out > m_amounts.out) Throw("can't produce more than is available."); m_amounts -= consumed; setFieldAmounts(); view.update(m_entry); } std::string id() const { return to_string(m_entry->key()); } std::optional key() const { return m_entry->key(); } Issue const& issueIn() const; Issue const& issueOut() const; TAmounts limitOut(TAmounts const& offerAmount, TOut const& limit, bool roundUp) const; TAmounts limitIn(TAmounts const& offerAmount, TIn const& limit, bool roundUp) const; template static TER send(Args&&... args); bool isFunded() const { // Offer owner is issuer; they have unlimited funds return m_account == issueOut().account; } static std::pair adjustRates(std::uint32_t ofrInRate, std::uint32_t ofrOutRate) { // CLOB offer pays the transfer fee return {ofrInRate, ofrOutRate}; } /** Check any required invariant. Limit order book offer * always returns true. */ bool checkInvariant(TAmounts const& consumed, beast::Journal j) const { if (!isFeatureEnabled(fixAMMv1_3)) return true; if (consumed.in > m_amounts.in || consumed.out > m_amounts.out) { // LCOV_EXCL_START JLOG(j.error()) << "AMMOffer::checkInvariant failed: consumed " << to_string(consumed.in) << " " << to_string(consumed.out) << " amounts " << to_string(m_amounts.in) << " " << to_string(m_amounts.out); return false; // LCOV_EXCL_STOP } return true; } }; using Offer = TOffer<>; template TOffer::TOffer(SLE::pointer const& entry, Quality quality) : m_entry(entry), m_quality(quality), m_account(m_entry->getAccountID(sfAccount)) { auto const tp = m_entry->getFieldAmount(sfTakerPays); auto const tg = m_entry->getFieldAmount(sfTakerGets); m_amounts.in = toAmount(tp); m_amounts.out = toAmount(tg); this->issIn_ = tp.issue(); this->issOut_ = tg.issue(); } template <> inline TOffer::TOffer(SLE::pointer const& entry, Quality quality) : m_entry(entry) , m_quality(quality) , m_account(m_entry->getAccountID(sfAccount)) , m_amounts(m_entry->getFieldAmount(sfTakerPays), m_entry->getFieldAmount(sfTakerGets)) { } template void TOffer::setFieldAmounts() { // LCOV_EXCL_START #ifdef _MSC_VER UNREACHABLE("xrpl::TOffer::setFieldAmounts : must be specialized"); #else static_assert(sizeof(TOut) == -1, "Must be specialized"); #endif // LCOV_EXCL_STOP } template TAmounts TOffer::limitOut(TAmounts const& offerAmount, TOut const& limit, bool roundUp) const { // It turns out that the ceil_out implementation has some slop in // it, which ceil_out_strict removes. return quality().ceil_out_strict(offerAmount, limit, roundUp); } template TAmounts TOffer::limitIn(TAmounts const& offerAmount, TIn const& limit, bool roundUp) const { if (auto const& rules = getCurrentTransactionRules(); rules && rules->enabled(fixReducedOffersV2)) // It turns out that the ceil_in implementation has some slop in // it. ceil_in_strict removes that slop. But removing that slop // affects transaction outcomes, so the change must be made using // an amendment. return quality().ceil_in_strict(offerAmount, limit, roundUp); return m_quality.ceil_in(offerAmount, limit); } template template TER TOffer::send(Args&&... args) { return accountSend(std::forward(args)...); } template <> inline void TOffer::setFieldAmounts() { m_entry->setFieldAmount(sfTakerPays, m_amounts.in); m_entry->setFieldAmount(sfTakerGets, m_amounts.out); } template <> inline void TOffer::setFieldAmounts() { m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, issIn_)); m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, issOut_)); } template <> inline void TOffer::setFieldAmounts() { m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, issIn_)); m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out)); } template <> inline void TOffer::setFieldAmounts() { m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in)); m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, issOut_)); } template Issue const& TOffer::issueIn() const { return this->issIn_; } template <> inline Issue const& TOffer::issueIn() const { return m_amounts.in.issue(); } template Issue const& TOffer::issueOut() const { return this->issOut_; } template <> inline Issue const& TOffer::issueOut() const { return m_amounts.out.issue(); } template inline std::ostream& operator<<(std::ostream& os, TOffer const& offer) { return os << offer.id(); } } // namespace xrpl