rippled
Loading...
Searching...
No Matches
AMMLiquidity.cpp
1#include <xrpl/tx/paths/AMMLiquidity.h>
2#include <xrpl/tx/paths/AMMOffer.h>
3
4namespace xrpl {
5
6template <typename TIn, typename TOut>
8 ReadView const& view,
9 AccountID const& ammAccountID,
10 std::uint32_t tradingFee,
11 Issue const& in,
12 Issue const& out,
13 AMMContext& ammContext,
15 : ammContext_(ammContext)
16 , ammAccountID_(ammAccountID)
17 , tradingFee_(tradingFee)
18 , issueIn_(in)
19 , issueOut_(out)
20 , initialBalances_{fetchBalances(view)}
21 , j_(j)
22{
23}
24
25template <typename TIn, typename TOut>
26TAmounts<TIn, TOut>
28{
29 auto const assetIn = ammAccountHolds(view, ammAccountID_, issueIn_);
30 auto const assetOut = ammAccountHolds(view, ammAccountID_, issueOut_);
31 // This should not happen.
32 if (assetIn < beast::zero || assetOut < beast::zero)
33 Throw<std::runtime_error>("AMMLiquidity: invalid balances");
34
35 return TAmounts{get<TIn>(assetIn), get<TOut>(assetOut)};
36}
37
38template <typename TIn, typename TOut>
39TAmounts<TIn, TOut>
40AMMLiquidity<TIn, TOut>::generateFibSeqOffer(TAmounts<TIn, TOut> const& balances) const
41{
42 TAmounts<TIn, TOut> cur{};
43
44 cur.in = toAmount<TIn>(
45 getIssue(balances.in),
46 InitialFibSeqPct * initialBalances_.in,
48 cur.out = swapAssetIn(initialBalances_, cur.in, tradingFee_);
49
50 if (ammContext_.curIters() == 0)
51 return cur;
52
54 1, 2, 3, 5, 8, 13, 21, 34, 55, 89,
55 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946,
56 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269};
57
58 XRPL_ASSERT(
59 !ammContext_.maxItersReached(),
60 "xrpl::AMMLiquidity::generateFibSeqOffer : maximum iterations");
61
62 cur.out = toAmount<TOut>(
63 getIssue(balances.out),
64 cur.out * fib[ammContext_.curIters() - 1],
66 // swapAssetOut() returns negative in this case
67 if (cur.out >= balances.out)
68 Throw<std::overflow_error>("AMMLiquidity: generateFibSeqOffer exceeds the balance");
69
70 cur.in = swapAssetOut(balances, cur.out, tradingFee_);
71
72 return cur;
73}
74
75namespace {
76template <typename T>
77constexpr T
78maxAmount()
79{
80 if constexpr (std::is_same_v<T, XRPAmount>)
81 {
83 }
84 else if constexpr (std::is_same_v<T, IOUAmount>)
85 {
86 return IOUAmount(STAmount::cMaxValue / 2, STAmount::cMaxOffset);
87 }
88 else if constexpr (std::is_same_v<T, STAmount>)
89 {
90 return STAmount(STAmount::cMaxValue / 2, STAmount::cMaxOffset);
91 }
92}
93
94template <typename T>
95T
96maxOut(T const& out, Issue const& iss)
97{
98 Number const res = out * Number{99, -2};
99 return toAmount<T>(iss, res, Number::rounding_mode::downward);
100}
101} // namespace
102
103template <typename TIn, typename TOut>
105AMMLiquidity<TIn, TOut>::maxOffer(TAmounts<TIn, TOut> const& balances, Rules const& rules) const
106{
107 if (!rules.enabled(fixAMMOverflowOffer))
108 {
109 return AMMOffer<TIn, TOut>(
110 *this,
111 {maxAmount<TIn>(), swapAssetIn(balances, maxAmount<TIn>(), tradingFee_)},
112 balances,
113 Quality{balances});
114 }
115
116 auto const out = maxOut<TOut>(balances.out, issueOut());
117 if (out <= TOut{0} || out >= balances.out)
118 return std::nullopt;
119 return AMMOffer<TIn, TOut>(
120 *this, {swapAssetOut(balances, out, tradingFee_), out}, balances, Quality{balances});
121}
122
123template <typename TIn, typename TOut>
126 const
127{
128 // Can't generate more offers if multi-path.
129 if (ammContext_.maxItersReached())
130 return std::nullopt;
131
132 auto const balances = fetchBalances(view);
133
134 // Frozen accounts
135 if (balances.in == beast::zero || balances.out == beast::zero)
136 {
137 JLOG(j_.debug()) << "AMMLiquidity::getOffer, frozen accounts";
138 return std::nullopt;
139 }
140
141 JLOG(j_.trace()) << "AMMLiquidity::getOffer balances " << to_string(initialBalances_.in) << " "
142 << to_string(initialBalances_.out) << " new balances "
143 << to_string(balances.in) << " " << 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>(*this, amounts, balances, Quality{amounts});
170 }
171 if (!clobQuality)
172 {
173 // If there is no CLOB to compare against, return the largest
174 // amount, which doesn't overflow. The size is going to be
175 // changed in BookStep per either deliver amount limit, or
176 // sendmax, or available output or input funds. Might return
177 // nullopt if the pool is small.
178 return maxOffer(balances, view.rules());
179 }
180 if (auto const amounts =
181 changeSpotPriceQuality(balances, *clobQuality, tradingFee_, view.rules(), j_))
182 {
183 return AMMOffer<TIn, TOut>(*this, *amounts, balances, Quality{*amounts});
184 }
185 if (view.rules().enabled(fixAMMv1_2))
186 {
187 if (auto const maxAMMOffer = maxOffer(balances, view.rules());
188 maxAMMOffer && Quality{maxAMMOffer->amount()} > *clobQuality)
189 return maxAMMOffer;
190 }
191 }
192 catch (std::overflow_error const& e)
193 {
194 JLOG(j_.error()) << "AMMLiquidity::getOffer overflow " << e.what();
195 if (!view.rules().enabled(fixAMMOverflowOffer))
196 {
197 return maxOffer(balances, view.rules());
198 }
199
200 return std::nullopt;
201 }
202 catch (std::exception const& e)
203 {
204 JLOG(j_.error()) << "AMMLiquidity::getOffer exception " << e.what();
205 }
206 return std::nullopt;
207 }();
208
209 if (offer)
210 {
211 if (offer->amount().in > beast::zero && offer->amount().out > beast::zero)
212 {
213 JLOG(j_.trace()) << "AMMLiquidity::getOffer, created " << to_string(offer->amount().in)
214 << "/" << issueIn_ << " " << to_string(offer->amount().out) << "/"
215 << issueOut_;
216 return offer;
217 }
218
219 JLOG(j_.debug()) << "AMMLiquidity::getOffer, no valid offer " << ammContext_.multiPath()
220 << " " << ammContext_.curIters() << " "
221 << (clobQuality ? clobQuality->rate() : STAmount{}) << " "
222 << to_string(balances.in) << " " << to_string(balances.out);
223 }
224
225 return std::nullopt;
226}
227
232
233} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:40
Stream error() const
Definition Journal.h:319
Stream debug() const
Definition Journal.h:301
Stream trace() const
Severity stream access functions.
Definition Journal.h:295
Maintains AMM info per overall payment engine execution and individual iteration.
Definition AMMContext.h:16
static constexpr std::uint8_t MaxIterations
Definition AMMContext.h:21
AMMLiquidity class provides AMM offers to BookStep class.
TAmounts< TIn, TOut > generateFibSeqOffer(TAmounts< TIn, TOut > const &balances) const
Generate AMM offers with the offer size based on Fibonacci sequence.
std::optional< AMMOffer< TIn, TOut > > maxOffer(TAmounts< TIn, TOut > const &balances, Rules const &rules) const
Generate max 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 > > getOffer(ReadView const &view, std::optional< Quality > const &clobQuality) const
Generate AMM offer.
Represents synthetic AMM offer in BookStep.
Definition AMMOffer.h:21
A currency issued by an account.
Definition Issue.h:13
Number is a floating point type that can represent a wide range of values.
Definition Number.h:207
A view into a ledger.
Definition ReadView.h:31
virtual Rules const & rules() const =0
Returns the tx processing rules.
Rules controlling protocol behavior.
Definition Rules.h:18
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:120
static constexpr std::uint64_t cMaxValue
Definition STAmount.h:51
static int const cMaxOffset
Definition STAmount.h:46
static constexpr std::uint64_t cMaxNative
Definition STAmount.h:53
T is_same_v
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
STAmount ammAccountHolds(ReadView const &view, AccountID const &ammAccountID, Issue const &issue)
Returns total amount held by AMM for the given token.
Definition AMMUtils.cpp:183
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:602
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:413
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:295
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:481
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:106
Issue getIssue(T const &amt)
T what(T... args)