rippled
Loading...
Searching...
No Matches
AMMLiquidity.cpp
1#include <xrpld/app/paths/AMMLiquidity.h>
2#include <xrpld/app/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 =
45 toAmount<TIn>(getIssue(balances.in), InitialFibSeqPct * initialBalances_.in, Number::rounding_mode::upward);
46 cur.out = swapAssetIn(initialBalances_, cur.in, tradingFee_);
47
48 if (ammContext_.curIters() == 0)
49 return cur;
50
51 // clang-format off
53 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987,
54 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393,
55 196418, 317811, 514229, 832040, 1346269};
56 // clang-format on
57
58 XRPL_ASSERT(!ammContext_.maxItersReached(), "xrpl::AMMLiquidity::generateFibSeqOffer : maximum iterations");
59
60 cur.out = toAmount<TOut>(
61 getIssue(balances.out), cur.out * fib[ammContext_.curIters() - 1], Number::rounding_mode::downward);
62 // swapAssetOut() returns negative in this case
63 if (cur.out >= balances.out)
64 Throw<std::overflow_error>("AMMLiquidity: generateFibSeqOffer exceeds the balance");
65
66 cur.in = swapAssetOut(balances, cur.out, tradingFee_);
67
68 return cur;
69}
70
71namespace {
72template <typename T>
73constexpr T
74maxAmount()
75{
76 if constexpr (std::is_same_v<T, XRPAmount>)
78 else if constexpr (std::is_same_v<T, IOUAmount>)
80 else if constexpr (std::is_same_v<T, STAmount>)
82}
83
84template <typename T>
85T
86maxOut(T const& out, Issue const& iss)
87{
88 Number const res = out * Number{99, -2};
89 return toAmount<T>(iss, res, Number::rounding_mode::downward);
90}
91} // namespace
92
93template <typename TIn, typename TOut>
95AMMLiquidity<TIn, TOut>::maxOffer(TAmounts<TIn, TOut> const& balances, Rules const& rules) const
96{
97 if (!rules.enabled(fixAMMOverflowOffer))
98 {
100 *this,
101 {maxAmount<TIn>(), swapAssetIn(balances, maxAmount<TIn>(), tradingFee_)},
102 balances,
103 Quality{balances});
104 }
105 else
106 {
107 auto const out = maxOut<TOut>(balances.out, issueOut());
108 if (out <= TOut{0} || out >= balances.out)
109 return std::nullopt;
110 return AMMOffer<TIn, TOut>(*this, {swapAssetOut(balances, out, tradingFee_), out}, balances, Quality{balances});
111 }
112}
113
114template <typename TIn, typename TOut>
117{
118 // Can't generate more offers if multi-path.
119 if (ammContext_.maxItersReached())
120 return std::nullopt;
121
122 auto const balances = fetchBalances(view);
123
124 // Frozen accounts
125 if (balances.in == beast::zero || balances.out == beast::zero)
126 {
127 JLOG(j_.debug()) << "AMMLiquidity::getOffer, frozen accounts";
128 return std::nullopt;
129 }
130
131 JLOG(j_.trace()) << "AMMLiquidity::getOffer balances " << to_string(initialBalances_.in) << " "
132 << to_string(initialBalances_.out) << " new balances " << to_string(balances.in) << " "
133 << to_string(balances.out);
134
135 // Can't generate AMM with a better quality than CLOB's
136 // quality if AMM's Spot Price quality is less than CLOB quality or is
137 // within a threshold.
138 // Spot price quality (SPQ) is calculated within some precision threshold.
139 // On the next iteration, after SPQ is changed, the new SPQ might be close
140 // to the requested clobQuality but not exactly and potentially SPQ may keep
141 // on approaching clobQuality for many iterations. Checking for the quality
142 // threshold prevents this scenario.
143 if (auto const spotPriceQ = Quality{balances};
144 clobQuality && (spotPriceQ <= clobQuality || withinRelativeDistance(spotPriceQ, *clobQuality, Number(1, -7))))
145 {
146 JLOG(j_.trace()) << "AMMLiquidity::getOffer, higher clob quality";
147 return std::nullopt;
148 }
149
150 auto offer = [&]() -> std::optional<AMMOffer<TIn, TOut>> {
151 try
152 {
153 if (ammContext_.multiPath())
154 {
155 auto const amounts = generateFibSeqOffer(balances);
156 if (clobQuality && Quality{amounts} < clobQuality)
157 return std::nullopt;
158 return AMMOffer<TIn, TOut>(*this, amounts, balances, Quality{amounts});
159 }
160 else if (!clobQuality)
161 {
162 // If there is no CLOB to compare against, return the largest
163 // amount, which doesn't overflow. The size is going to be
164 // changed in BookStep per either deliver amount limit, or
165 // sendmax, or available output or input funds. Might return
166 // nullopt if the pool is small.
167 return maxOffer(balances, view.rules());
168 }
169 else if (auto const amounts = changeSpotPriceQuality(balances, *clobQuality, tradingFee_, view.rules(), j_))
170 {
171 return AMMOffer<TIn, TOut>(*this, *amounts, balances, Quality{*amounts});
172 }
173 else if (view.rules().enabled(fixAMMv1_2))
174 {
175 if (auto const maxAMMOffer = maxOffer(balances, view.rules());
176 maxAMMOffer && Quality{maxAMMOffer->amount()} > *clobQuality)
177 return maxAMMOffer;
178 }
179 }
180 catch (std::overflow_error const& e)
181 {
182 JLOG(j_.error()) << "AMMLiquidity::getOffer overflow " << e.what();
183 if (!view.rules().enabled(fixAMMOverflowOffer))
184 return maxOffer(balances, view.rules());
185 else
186 return std::nullopt;
187 }
188 catch (std::exception const& e)
189 {
190 JLOG(j_.error()) << "AMMLiquidity::getOffer exception " << e.what();
191 }
192 return std::nullopt;
193 }();
194
195 if (offer)
196 {
197 if (offer->amount().in > beast::zero && offer->amount().out > beast::zero)
198 {
199 JLOG(j_.trace()) << "AMMLiquidity::getOffer, created " << to_string(offer->amount().in) << "/" << issueIn_
200 << " " << to_string(offer->amount().out) << "/" << issueOut_;
201 return offer;
202 }
203
204 JLOG(j_.debug()) << "AMMLiquidity::getOffer, no valid offer " << ammContext_.multiPath() << " "
205 << ammContext_.curIters() << " " << (clobQuality ? clobQuality->rate() : STAmount{}) << " "
206 << to_string(balances.in) << " " << to_string(balances.out);
207 }
208
209 return std::nullopt;
210}
211
216
217} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:41
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.
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
Floating point representation of amounts with high dynamic range.
Definition IOUAmount.h:26
A currency issued by an account.
Definition Issue.h:14
Number is a floating point type that can represent a wide range of values.
Definition Number.h:208
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:118
static constexpr std::uint64_t cMaxValue
Definition STAmount.h:52
static int const cMaxOffset
Definition STAmount.h:47
static constexpr std::uint64_t cMaxNative
Definition STAmount.h:54
T is_same_v
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
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
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:598
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:396
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:282
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:464
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:99
Issue getIssue(T const &amt)
T what(T... args)