rippled
Loading...
Searching...
No Matches
AMMLiquidity.cpp
1#include <xrpld/app/paths/AMMLiquidity.h>
2#include <xrpld/app/paths/AMMOffer.h>
3
4namespace ripple {
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>
41 TAmounts<TIn, TOut> const& balances) const
42{
43 TAmounts<TIn, TOut> cur{};
44
45 cur.in = toAmount<TIn>(
46 getIssue(balances.in),
47 InitialFibSeqPct * initialBalances_.in,
49 cur.out = swapAssetIn(initialBalances_, cur.in, tradingFee_);
50
51 if (ammContext_.curIters() == 0)
52 return cur;
53
54 // clang-format off
56 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987,
57 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393,
58 196418, 317811, 514229, 832040, 1346269};
59 // clang-format on
60
61 XRPL_ASSERT(
62 !ammContext_.maxItersReached(),
63 "ripple::AMMLiquidity::generateFibSeqOffer : maximum iterations");
64
65 cur.out = toAmount<TOut>(
66 getIssue(balances.out),
67 cur.out * fib[ammContext_.curIters() - 1],
69 // swapAssetOut() returns negative in this case
70 if (cur.out >= balances.out)
71 Throw<std::overflow_error>(
72 "AMMLiquidity: generateFibSeqOffer exceeds the balance");
73
74 cur.in = swapAssetOut(balances, cur.out, tradingFee_);
75
76 return cur;
77}
78
79namespace {
80template <typename T>
81constexpr T
82maxAmount()
83{
84 if constexpr (std::is_same_v<T, XRPAmount>)
86 else if constexpr (std::is_same_v<T, IOUAmount>)
88 else if constexpr (std::is_same_v<T, STAmount>)
90}
91
92template <typename T>
93T
94maxOut(T const& out, Issue const& iss)
95{
96 Number const res = out * Number{99, -2};
97 return toAmount<T>(iss, res, Number::rounding_mode::downward);
98}
99} // namespace
100
101template <typename TIn, typename TOut>
104 TAmounts<TIn, TOut> const& balances,
105 Rules const& rules) const
106{
107 if (!rules.enabled(fixAMMOverflowOffer))
108 {
109 return AMMOffer<TIn, TOut>(
110 *this,
111 {maxAmount<TIn>(),
112 swapAssetIn(balances, maxAmount<TIn>(), tradingFee_)},
113 balances,
114 Quality{balances});
115 }
116 else
117 {
118 auto const out = maxOut<TOut>(balances.out, issueOut());
119 if (out <= TOut{0} || out >= balances.out)
120 return std::nullopt;
121 return AMMOffer<TIn, TOut>(
122 *this,
123 {swapAssetOut(balances, out, tradingFee_), out},
124 balances,
125 Quality{balances});
126 }
127}
128
129template <typename TIn, typename TOut>
132 ReadView const& view,
133 std::optional<Quality> const& clobQuality) const
134{
135 // Can't generate more offers if multi-path.
136 if (ammContext_.maxItersReached())
137 return std::nullopt;
138
139 auto const balances = fetchBalances(view);
140
141 // Frozen accounts
142 if (balances.in == beast::zero || balances.out == beast::zero)
143 {
144 JLOG(j_.debug()) << "AMMLiquidity::getOffer, frozen accounts";
145 return std::nullopt;
146 }
147
148 JLOG(j_.trace()) << "AMMLiquidity::getOffer balances "
149 << to_string(initialBalances_.in) << " "
150 << to_string(initialBalances_.out) << " new balances "
151 << to_string(balances.in) << " "
152 << to_string(balances.out);
153
154 // Can't generate AMM with a better quality than CLOB's
155 // quality if AMM's Spot Price quality is less than CLOB quality or is
156 // within a threshold.
157 // Spot price quality (SPQ) is calculated within some precision threshold.
158 // On the next iteration, after SPQ is changed, the new SPQ might be close
159 // to the requested clobQuality but not exactly and potentially SPQ may keep
160 // on approaching clobQuality for many iterations. Checking for the quality
161 // threshold prevents this scenario.
162 if (auto const spotPriceQ = Quality{balances}; clobQuality &&
163 (spotPriceQ <= clobQuality ||
164 withinRelativeDistance(spotPriceQ, *clobQuality, Number(1, -7))))
165 {
166 JLOG(j_.trace()) << "AMMLiquidity::getOffer, higher clob quality";
167 return std::nullopt;
168 }
169
170 auto offer = [&]() -> std::optional<AMMOffer<TIn, TOut>> {
171 try
172 {
173 if (ammContext_.multiPath())
174 {
175 auto const amounts = generateFibSeqOffer(balances);
176 if (clobQuality && Quality{amounts} < clobQuality)
177 return std::nullopt;
178 return AMMOffer<TIn, TOut>(
179 *this, amounts, balances, Quality{amounts});
180 }
181 else if (!clobQuality)
182 {
183 // If there is no CLOB to compare against, return the largest
184 // amount, which doesn't overflow. The size is going to be
185 // changed in BookStep per either deliver amount limit, or
186 // sendmax, or available output or input funds. Might return
187 // nullopt if the pool is small.
188 return maxOffer(balances, view.rules());
189 }
190 else if (
191 auto const amounts = changeSpotPriceQuality(
192 balances, *clobQuality, tradingFee_, view.rules(), j_))
193 {
194 return AMMOffer<TIn, TOut>(
195 *this, *amounts, balances, Quality{*amounts});
196 }
197 else if (view.rules().enabled(fixAMMv1_2))
198 {
199 if (auto const maxAMMOffer = maxOffer(balances, view.rules());
200 maxAMMOffer &&
201 Quality{maxAMMOffer->amount()} > *clobQuality)
202 return maxAMMOffer;
203 }
204 }
205 catch (std::overflow_error const& e)
206 {
207 JLOG(j_.error()) << "AMMLiquidity::getOffer overflow " << e.what();
208 if (!view.rules().enabled(fixAMMOverflowOffer))
209 return maxOffer(balances, view.rules());
210 else
211 return std::nullopt;
212 }
213 catch (std::exception const& e)
214 {
215 JLOG(j_.error()) << "AMMLiquidity::getOffer exception " << e.what();
216 }
217 return std::nullopt;
218 }();
219
220 if (offer)
221 {
222 if (offer->amount().in > beast::zero &&
223 offer->amount().out > beast::zero)
224 {
225 JLOG(j_.trace())
226 << "AMMLiquidity::getOffer, created "
227 << to_string(offer->amount().in) << "/" << issueIn_ << " "
228 << to_string(offer->amount().out) << "/" << issueOut_;
229 return offer;
230 }
231
232 JLOG(j_.debug()) << "AMMLiquidity::getOffer, no valid offer "
233 << ammContext_.multiPath() << " "
234 << ammContext_.curIters() << " "
235 << (clobQuality ? clobQuality->rate() : STAmount{})
236 << " " << to_string(balances.in) << " "
237 << to_string(balances.out);
238 }
239
240 return std::nullopt;
241}
242
247
248} // namespace ripple
A generic endpoint for log messages.
Definition Journal.h:41
Stream error() const
Definition Journal.h:327
Stream debug() const
Definition Journal.h:309
Stream trace() const
Severity stream access functions.
Definition Journal.h:303
Maintains AMM info per overall payment engine execution and individual iteration.
Definition AMMContext.h:17
static constexpr std::uint8_t MaxIterations
Definition AMMContext.h:22
AMMLiquidity class provides AMM offers to BookStep class.
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:21
Floating point representation of amounts with high dynamic range.
Definition IOUAmount.h:27
A currency issued by an account.
Definition Issue.h:14
A view into a ledger.
Definition ReadView.h:32
virtual Rules const & rules() const =0
Returns the tx processing rules.
Rules controlling protocol behavior.
Definition Rules.h:19
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:111
static int const cMaxOffset
Definition STAmount.h:47
static std::uint64_t const cMaxValue
Definition STAmount.h:51
static std::uint64_t const cMaxNative
Definition STAmount.h:52
T is_same_v
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
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:445
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:312
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
STAmount ammAccountHolds(ReadView const &view, AccountID const &ammAccountID, Issue const &issue)
Returns total amount held by AMM for the given token.
Definition AMMUtils.cpp:192
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:110
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:518
T what(T... args)