rippled
Loading...
Searching...
No Matches
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
20#include <xrpld/app/paths/AMMLiquidity.h>
21#include <xrpld/app/paths/AMMOffer.h>
22
23namespace ripple {
24
25template <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
44template <typename TIn, typename TOut>
45TAmounts<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
57template <typename TIn, typename TOut>
58TAmounts<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,
68 cur.out = swapAssetIn(initialBalances_, cur.in, tradingFee_);
69
70 if (ammContext_.curIters() == 0)
71 return cur;
72
73 // clang-format off
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 XRPL_ASSERT(
81 !ammContext_.maxItersReached(),
82 "ripple::AMMLiquidity::generateFibSeqOffer : maximum iterations");
83
84 cur.out = toAmount<TOut>(
85 getIssue(balances.out),
86 cur.out * fib[ammContext_.curIters() - 1],
88 // swapAssetOut() returns negative in this case
89 if (cur.out >= balances.out)
90 Throw<std::overflow_error>(
91 "AMMLiquidity: generateFibSeqOffer exceeds the balance");
92
93 cur.in = swapAssetOut(balances, cur.out, tradingFee_);
94
95 return cur;
96}
97
98namespace {
99template <typename T>
100constexpr T
101maxAmount()
102{
103 if constexpr (std::is_same_v<T, XRPAmount>)
105 else if constexpr (std::is_same_v<T, IOUAmount>)
107 else if constexpr (std::is_same_v<T, STAmount>)
109}
110
111template <typename T>
112T
113maxOut(T const& out, Issue const& iss)
114{
115 Number const res = out * Number{99, -2};
116 return toAmount<T>(iss, res, Number::rounding_mode::downward);
117}
118} // namespace
119
120template <typename TIn, typename TOut>
123 TAmounts<TIn, TOut> const& balances,
124 Rules const& rules) const
125{
126 if (!rules.enabled(fixAMMOverflowOffer))
127 {
128 return AMMOffer<TIn, TOut>(
129 *this,
130 {maxAmount<TIn>(),
131 swapAssetIn(balances, maxAmount<TIn>(), tradingFee_)},
132 balances,
133 Quality{balances});
134 }
135 else
136 {
137 auto const out = maxOut<TOut>(balances.out, issueOut());
138 if (out <= TOut{0} || out >= balances.out)
139 return std::nullopt;
140 return AMMOffer<TIn, TOut>(
141 *this,
142 {swapAssetOut(balances, out, tradingFee_), out},
143 balances,
144 Quality{balances});
145 }
146}
147
148template <typename TIn, typename TOut>
151 ReadView const& view,
152 std::optional<Quality> const& clobQuality) const
153{
154 // Can't generate more offers if multi-path.
155 if (ammContext_.maxItersReached())
156 return std::nullopt;
157
158 auto const balances = fetchBalances(view);
159
160 // Frozen accounts
161 if (balances.in == beast::zero || balances.out == beast::zero)
162 {
163 JLOG(j_.debug()) << "AMMLiquidity::getOffer, frozen accounts";
164 return std::nullopt;
165 }
166
167 JLOG(j_.trace()) << "AMMLiquidity::getOffer balances "
168 << to_string(initialBalances_.in) << " "
169 << to_string(initialBalances_.out) << " new balances "
170 << to_string(balances.in) << " "
171 << to_string(balances.out);
172
173 // Can't generate AMM with a better quality than CLOB's
174 // quality if AMM's Spot Price quality is less than CLOB quality or is
175 // within a threshold.
176 // Spot price quality (SPQ) is calculated within some precision threshold.
177 // On the next iteration, after SPQ is changed, the new SPQ might be close
178 // to the requested clobQuality but not exactly and potentially SPQ may keep
179 // on approaching clobQuality for many iterations. Checking for the quality
180 // threshold prevents this scenario.
181 if (auto const spotPriceQ = Quality{balances}; clobQuality &&
182 (spotPriceQ <= clobQuality ||
183 withinRelativeDistance(spotPriceQ, *clobQuality, Number(1, -7))))
184 {
185 JLOG(j_.trace()) << "AMMLiquidity::getOffer, higher clob quality";
186 return std::nullopt;
187 }
188
189 auto offer = [&]() -> std::optional<AMMOffer<TIn, TOut>> {
190 try
191 {
192 if (ammContext_.multiPath())
193 {
194 auto const amounts = generateFibSeqOffer(balances);
195 if (clobQuality && Quality{amounts} < clobQuality)
196 return std::nullopt;
197 return AMMOffer<TIn, TOut>(
198 *this, amounts, balances, Quality{amounts});
199 }
200 else if (!clobQuality)
201 {
202 // If there is no CLOB to compare against, return the largest
203 // amount, which doesn't overflow. The size is going to be
204 // changed in BookStep per either deliver amount limit, or
205 // sendmax, or available output or input funds. Might return
206 // nullopt if the pool is small.
207 return maxOffer(balances, view.rules());
208 }
209 else if (
210 auto const amounts = changeSpotPriceQuality(
211 balances, *clobQuality, tradingFee_, view.rules(), j_))
212 {
213 return AMMOffer<TIn, TOut>(
214 *this, *amounts, balances, Quality{*amounts});
215 }
216 else if (view.rules().enabled(fixAMMv1_2))
217 {
218 if (auto const maxAMMOffer = maxOffer(balances, view.rules());
219 maxAMMOffer &&
220 Quality{maxAMMOffer->amount()} > *clobQuality)
221 return maxAMMOffer;
222 }
223 }
224 catch (std::overflow_error const& e)
225 {
226 JLOG(j_.error()) << "AMMLiquidity::getOffer overflow " << e.what();
227 if (!view.rules().enabled(fixAMMOverflowOffer))
228 return maxOffer(balances, view.rules());
229 else
230 return std::nullopt;
231 }
232 catch (std::exception const& e)
233 {
234 JLOG(j_.error()) << "AMMLiquidity::getOffer exception " << e.what();
235 }
236 return std::nullopt;
237 }();
238
239 if (offer)
240 {
241 if (offer->amount().in > beast::zero &&
242 offer->amount().out > beast::zero)
243 {
244 JLOG(j_.trace())
245 << "AMMLiquidity::getOffer, created "
246 << to_string(offer->amount().in) << "/" << issueIn_ << " "
247 << to_string(offer->amount().out) << "/" << issueOut_;
248 return offer;
249 }
250
251 JLOG(j_.error()) << "AMMLiquidity::getOffer, failed "
252 << ammContext_.multiPath() << " "
253 << ammContext_.curIters() << " "
254 << (clobQuality ? clobQuality->rate() : STAmount{})
255 << " " << to_string(balances.in) << " "
256 << to_string(balances.out);
257 }
258
259 return std::nullopt;
260}
261
266
267} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:60
Stream error() const
Definition: Journal.h:346
Stream debug() const
Definition: Journal.h:328
Stream trace() const
Severity stream access functions.
Definition: Journal.h:322
Maintains AMM info per overall payment engine execution and individual iteration.
Definition: AMMContext.h:36
static constexpr std::uint8_t MaxIterations
Definition: AMMContext.h:41
AMMLiquidity class provides AMM offers to BookStep class.
Definition: AMMLiquidity.h:53
std::optional< AMMOffer< TIn, TOut > > getOffer(ReadView const &view, std::optional< Quality > const &clobQuality) const
Generate AMM offer.
TAmounts< TIn, TOut > fetchBalances(ReadView const &view) const
Fetches current AMM balances.
AMMLiquidity(ReadView const &view, AccountID const &ammAccountID, std::uint32_t tradingFee, Issue const &in, Issue const &out, AMMContext &ammContext, beast::Journal j)
std::optional< AMMOffer< TIn, TOut > > maxOffer(TAmounts< TIn, TOut > const &balances, Rules const &rules) const
Generate max offer.
TAmounts< TIn, TOut > generateFibSeqOffer(TAmounts< TIn, TOut > const &balances) const
Generate AMM offers with the offer size based on Fibonacci sequence.
Represents synthetic AMM offer in BookStep.
Definition: AMMOffer.h:41
Floating point representation of amounts with high dynamic range.
Definition: IOUAmount.h:46
A currency issued by an account.
Definition: Issue.h:36
A view into a ledger.
Definition: ReadView.h:52
virtual Rules const & rules() const =0
Returns the tx processing rules.
Rules controlling protocol behavior.
Definition: Rules.h:35
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
static int const cMaxOffset
Definition: STAmount.h:66
static std::uint64_t const cMaxValue
Definition: STAmount.h:70
static std::uint64_t const cMaxNative
Definition: STAmount.h:71
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
Issue getIssue(T const &amt)
TOut swapAssetIn(TAmounts< TIn, TOut > const &pool, TIn const &assetIn, std::uint16_t tfee)
AMM pool invariant - the product (A * B) after swap in/out has to remain at least the same: (A + in) ...
Definition: AMMHelpers.h:462
std::optional< TAmounts< TIn, TOut > > changeSpotPriceQuality(TAmounts< TIn, TOut > const &pool, Quality const &quality, std::uint16_t tfee, Rules const &rules, beast::Journal j)
Generate AMM offer so that either updated Spot Price Quality (SPQ) is equal to LOB quality (in this c...
Definition: AMMHelpers.h:329
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
STAmount ammAccountHolds(ReadView const &view, AccountID const &ammAccountID, Issue const &issue)
Returns total amount held by AMM for the given token.
Definition: AMMUtils.cpp:210
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition: AMMHelpers.h:127
TIn swapAssetOut(TAmounts< TIn, TOut > const &pool, TOut const &assetOut, std::uint16_t tfee)
Swap assetOut out of the pool and swap in a proportional amount of the other asset.
Definition: AMMHelpers.h:535
T what(T... args)