#pragma once #include #include #include #include #include #include #include #include #include #include #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)..., SLE::pointer(), 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