#pragma once #include #include #include #include #include #include #include #include #include #include #include namespace xrpl { template class TOffer { private: SLE::pointer entry_; Quality quality_{}; AccountID accountID_; Asset assetIn_; Asset assetOut_; TAmounts amounts_{}; void setFieldAmounts(); public: TOffer() = default; TOffer(SLE::pointer 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. */ [[nodiscard]] Quality quality() const noexcept { return quality_; } /** Returns the account id of the offer's owner. */ [[nodiscard]] AccountID const& owner() const { return accountID_; } /** Returns the in and out amounts. Some or all of the out amount may be unfunded. */ [[nodiscard]] TAmounts const& amount() const { return amounts_; } /** Returns `true` if no more funds can flow through this offer. */ [[nodiscard]] bool fullyConsumed() const { if (amounts_.in <= beast::kZero) return true; if (amounts_.out <= beast::kZero) 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 > amounts_.in) Throw("can't consume more than is available."); if (consumed.out > amounts_.out) Throw("can't produce more than is available."); amounts_ -= consumed; setFieldAmounts(); view.update(entry_); } [[nodiscard]] std::string id() const { return to_string(entry_->key()); } [[nodiscard]] std::optional key() const { return entry_->key(); } [[nodiscard]] Asset const& assetIn() const; [[nodiscard]] Asset const& assetOut() const; [[nodiscard]] TAmounts limitOut(TAmounts const& offerAmount, TOut const& limit, bool roundUp) const; [[nodiscard]] TAmounts limitIn(TAmounts const& offerAmount, TIn const& limit, bool roundUp) const; template static TER send(Args&&... args); [[nodiscard]] bool isFunded() const { // Offer owner is issuer; they have unlimited funds if IOU return accountID_ == assetOut_.getIssuer() && assetOut_.holds(); } 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. */ [[nodiscard]] bool checkInvariant(TAmounts const& consumed, beast::Journal j) const { if (!isFeatureEnabled(fixAMMv1_3)) return true; if (consumed.in > amounts_.in || consumed.out > amounts_.out) { // LCOV_EXCL_START JLOG(j.error()) << "AMMOffer::checkInvariant failed: consumed " << to_string(consumed.in) << " " << to_string(consumed.out) << " amounts " << to_string(amounts_.in) << " " << to_string(amounts_.out); return false; // LCOV_EXCL_STOP } return true; } }; template TOffer::TOffer(SLE::pointer entry, Quality quality) : entry_(std::move(entry)), quality_(quality), accountID_(entry_->getAccountID(sfAccount)) { auto const tp = entry_->getFieldAmount(sfTakerPays); auto const tg = entry_->getFieldAmount(sfTakerGets); amounts_.in = toAmount(tp); amounts_.out = toAmount(tg); assetIn_ = tp.asset(); assetOut_ = tg.asset(); } template void TOffer::setFieldAmounts() { if constexpr (std::is_same_v) { entry_->setFieldAmount(sfTakerPays, toSTAmount(amounts_.in)); } else { entry_->setFieldAmount(sfTakerPays, toSTAmount(amounts_.in, assetIn_)); } if constexpr (std::is_same_v) { entry_->setFieldAmount(sfTakerGets, toSTAmount(amounts_.out)); } else { entry_->setFieldAmount(sfTakerGets, toSTAmount(amounts_.out, assetOut_)); } } 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().ceilOutStrict(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().ceilInStrict(offerAmount, limit, roundUp); } return quality_.ceilIn(offerAmount, limit); } template template TER TOffer::send(Args&&... args) { return accountSend(std::forward(args)..., WaiveTransferFee::No, AllowMPTOverflow::Yes); } template Asset const& TOffer::assetIn() const { return assetIn_; } template Asset const& TOffer::assetOut() const { return assetOut_; } template inline std::ostream& operator<<(std::ostream& os, TOffer const& offer) { return os << offer.id(); } } // namespace xrpl