rippled
AMMLiquidity.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2023 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 #include <ripple/app/paths/AMMLiquidity.h>
20 
21 #include <ripple/app/paths/AMMOffer.h>
22 
23 namespace ripple {
24 
25 template <typename TIn, typename TOut>
27  ReadView const& view,
28  AccountID const& ammAccountID,
29  std::uint32_t tradingFee,
30  Issue const& in,
31  Issue const& out,
32  AMMContext& ammContext,
34  : ammContext_(ammContext)
35  , ammAccountID_(ammAccountID)
36  , tradingFee_(tradingFee)
37  , issueIn_(in)
38  , issueOut_(out)
39  , initialBalances_{fetchBalances(view)}
40  , j_(j)
41 {
42 }
43 
44 template <typename TIn, typename TOut>
45 TAmounts<TIn, TOut>
47 {
48  auto const assetIn = ammAccountHolds(view, ammAccountID_, issueIn_);
49  auto const assetOut = ammAccountHolds(view, ammAccountID_, issueOut_);
50  // This should not happen.
51  if (assetIn < beast::zero || assetOut < beast::zero)
52  Throw<std::runtime_error>("AMMLiquidity: invalid balances");
53 
54  return TAmounts{get<TIn>(assetIn), get<TOut>(assetOut)};
55 }
56 
57 template <typename TIn, typename TOut>
58 TAmounts<TIn, TOut>
60  TAmounts<TIn, TOut> const& balances) const
61 {
62  TAmounts<TIn, TOut> cur{};
63 
64  cur.in = toAmount<TIn>(
65  getIssue(balances.in),
66  InitialFibSeqPct * initialBalances_.in,
67  Number::rounding_mode::upward);
68  cur.out = swapAssetIn(initialBalances_, cur.in, tradingFee_);
69 
70  if (ammContext_.curIters() == 0)
71  return cur;
72 
73  // clang-format off
74  constexpr std::uint32_t fib[AMMContext::MaxIterations] = {
75  1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987,
76  1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393,
77  196418, 317811, 514229, 832040, 1346269};
78  // clang-format on
79 
80  assert(!ammContext_.maxItersReached());
81 
82  cur.out = toAmount<TOut>(
83  getIssue(balances.out),
84  cur.out * fib[ammContext_.curIters() - 1],
85  Number::rounding_mode::downward);
86  // swapAssetOut() returns negative in this case
87  if (cur.out >= balances.out)
88  Throw<std::overflow_error>(
89  "AMMLiquidity: generateFibSeqOffer exceeds the balance");
90 
91  cur.in = swapAssetOut(balances, cur.out, tradingFee_);
92 
93  return cur;
94 }
95 
96 template <typename T>
97 constexpr T
99 {
100  if constexpr (std::is_same_v<T, XRPAmount>)
102  else if constexpr (std::is_same_v<T, IOUAmount>)
104  else if constexpr (std::is_same_v<T, STAmount>)
106 }
107 
108 template <typename TIn, typename TOut>
109 AMMOffer<TIn, TOut>
110 AMMLiquidity<TIn, TOut>::maxOffer(TAmounts<TIn, TOut> const& balances) const
111 {
112  return AMMOffer<TIn, TOut>(
113  *this,
114  {maxAmount<TIn>(),
115  swapAssetIn(balances, maxAmount<TIn>(), tradingFee_)},
116  balances,
117  Quality{balances});
118 }
119 
120 template <typename TIn, typename TOut>
123  ReadView const& view,
124  std::optional<Quality> const& clobQuality) const
125 {
126  // Can't generate more offers if multi-path.
127  if (ammContext_.maxItersReached())
128  return std::nullopt;
129 
130  auto const balances = fetchBalances(view);
131 
132  // Frozen accounts
133  if (balances.in == beast::zero || balances.out == beast::zero)
134  {
135  JLOG(j_.debug()) << "AMMLiquidity::getOffer, frozen accounts";
136  return std::nullopt;
137  }
138 
139  JLOG(j_.trace()) << "AMMLiquidity::getOffer balances "
140  << to_string(initialBalances_.in) << " "
141  << to_string(initialBalances_.out) << " new balances "
142  << to_string(balances.in) << " "
143  << to_string(balances.out);
144 
145  // Can't generate AMM with a better quality than CLOB's
146  // quality if AMM's Spot Price quality is less than CLOB quality or is
147  // within a threshold.
148  // Spot price quality (SPQ) is calculated within some precision threshold.
149  // On the next iteration, after SPQ is changed, the new SPQ might be close
150  // to the requested clobQuality but not exactly and potentially SPQ may keep
151  // on approaching clobQuality for many iterations. Checking for the quality
152  // threshold prevents this scenario.
153  if (auto const spotPriceQ = Quality{balances}; clobQuality &&
154  (spotPriceQ <= clobQuality ||
155  withinRelativeDistance(spotPriceQ, *clobQuality, Number(1, -7))))
156  {
157  JLOG(j_.trace()) << "AMMLiquidity::getOffer, higher clob quality";
158  return std::nullopt;
159  }
160 
161  auto offer = [&]() -> std::optional<AMMOffer<TIn, TOut>> {
162  try
163  {
164  if (ammContext_.multiPath())
165  {
166  auto const amounts = generateFibSeqOffer(balances);
167  if (clobQuality && Quality{amounts} < clobQuality)
168  return std::nullopt;
169  return AMMOffer<TIn, TOut>(
170  *this, amounts, std::nullopt, Quality{amounts});
171  }
172  else if (!clobQuality)
173  {
174  // If there is no CLOB to compare against, return the largest
175  // amount, which doesn't overflow. The size is going to be
176  // changed in BookStep per either deliver amount limit, or
177  // sendmax, or available output or input funds.
178  return maxOffer(balances);
179  }
180  else if (
181  auto const amounts =
182  changeSpotPriceQuality(balances, *clobQuality, tradingFee_))
183  {
184  return AMMOffer<TIn, TOut>(
185  *this, *amounts, balances, Quality{*amounts});
186  }
187  }
188  catch (std::overflow_error const& e)
189  {
190  JLOG(j_.error()) << "AMMLiquidity::getOffer overflow " << e.what();
191  return maxOffer(balances);
192  }
193  catch (std::exception const& e)
194  {
195  JLOG(j_.error()) << "AMMLiquidity::getOffer exception " << e.what();
196  }
197  return std::nullopt;
198  }();
199 
200  if (offer)
201  {
202  if (offer->amount().in > beast::zero &&
203  offer->amount().out > beast::zero)
204  {
205  JLOG(j_.trace())
206  << "AMMLiquidity::getOffer, created "
207  << to_string(offer->amount().in) << "/" << issueIn_ << " "
208  << to_string(offer->amount().out) << "/" << issueOut_;
209  return offer;
210  }
211 
212  JLOG(j_.error()) << "AMMLiquidity::getOffer, failed";
213  }
214 
215  return std::nullopt;
216 }
217 
218 template class AMMLiquidity<STAmount, STAmount>;
222 
223 } // namespace ripple
ripple::AMMLiquidity::maxOffer
AMMOffer< TIn, TOut > maxOffer(TAmounts< TIn, TOut > const &balances) const
Generate max offer.
Definition: AMMLiquidity.cpp:110
ripple::Issue
A currency issued by an account.
Definition: Issue.h:35
std::exception
STL class.
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:308
std::overflow_error
STL class.
ripple::STAmount::cMaxNative
static const std::uint64_t cMaxNative
Definition: STAmount.h:69
ripple::AMMLiquidity::fetchBalances
TAmounts< TIn, TOut > fetchBalances(ReadView const &view) const
Fetches current AMM balances.
Definition: AMMLiquidity.cpp:46
ripple::QualityDirection::in
@ in
ripple::AMMLiquidity::getOffer
std::optional< AMMOffer< TIn, TOut > > getOffer(ReadView const &view, std::optional< Quality > const &clobQuality) const
Generate AMM offer.
Definition: AMMLiquidity.cpp:122
ripple::IOUAmount
Floating point representation of amounts with high dynamic range.
Definition: IOUAmount.h:43
ripple::AMMLiquidity::AMMLiquidity
AMMLiquidity(ReadView const &view, AccountID const &ammAccountID, std::uint32_t tradingFee, Issue const &in, Issue const &out, AMMContext &ammContext, beast::Journal j)
Definition: AMMLiquidity.cpp:26
ripple::ammAccountID
AccountID ammAccountID(std::uint16_t prefix, uint256 const &parentHash, uint256 const &ammID)
Calculate AMM account ID.
Definition: AMMCore.cpp:30
ripple::AMMLiquidity::generateFibSeqOffer
TAmounts< TIn, TOut > generateFibSeqOffer(TAmounts< TIn, TOut > const &balances) const
Generate AMM offers with the offer size based on Fibonacci sequence.
Definition: AMMLiquidity.cpp:59
ripple::base_uint< 160, detail::AccountIDTag >
ripple::QualityDirection::out
@ out
ripple::getIssue
Issue getIssue(T const &amt)
Definition: AmountConversions.h:156
ripple::Number
Definition: Number.h:36
ripple::maxAmount
constexpr T maxAmount()
Definition: AMMLiquidity.cpp:98
ripple::AMMContext
Maintains AMM info per overall payment engine execution and individual iteration.
Definition: AMMContext.h:35
ripple::STAmount
Definition: STAmount.h:46
beast::Journal::error
Stream error() const
Definition: Journal.h:332
ripple::AMMOffer
Represents synthetic AMM offer in BookStep.
Definition: AMMLiquidity.h:35
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
std::uint32_t
ripple::ReadView
A view into a ledger.
Definition: ReadView.h:54
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::AMMLiquidity
AMMLiquidity class provides AMM offers to BookStep class.
Definition: AMMLiquidity.h:52
ripple::AMMContext::MaxIterations
constexpr static std::uint8_t MaxIterations
Definition: AMMContext.h:41
std::optional
beast::Journal::debug
Stream debug() const
Definition: Journal.h:314
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:41
ripple::STAmount::cMaxOffset
static const int cMaxOffset
Definition: STAmount.h:64
ripple::ammAccountHolds
STAmount ammAccountHolds(ReadView const &view, AccountID const &ammAccountID, Issue const &issue)
Returns total amount held by AMM for the given token.
Definition: AMMUtils.cpp:166
ripple::STAmount::cMaxValue
static const std::uint64_t cMaxValue
Definition: STAmount.h:68
std::overflow_error::what
T what(T... args)
ripple::XRPAmount
Definition: XRPAmount.h:46