xrpld
Loading...
Searching...
No Matches
AMMLiquidity.cpp
1#include <xrpl/tx/paths/AMMLiquidity.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Number.h>
5#include <xrpl/basics/contract.h>
6#include <xrpl/beast/utility/Journal.h>
7#include <xrpl/beast/utility/Zero.h>
8#include <xrpl/beast/utility/instrumentation.h>
9#include <xrpl/ledger/ReadView.h>
10#include <xrpl/ledger/helpers/AMMHelpers.h>
11#include <xrpl/protocol/AccountID.h>
12#include <xrpl/protocol/Asset.h>
13#include <xrpl/protocol/Feature.h>
14#include <xrpl/protocol/IOUAmount.h>
15#include <xrpl/protocol/MPTAmount.h>
16#include <xrpl/protocol/Protocol.h>
17#include <xrpl/protocol/Quality.h>
18#include <xrpl/protocol/Rules.h>
19#include <xrpl/protocol/STAmount.h>
20#include <xrpl/protocol/XRPAmount.h>
21#include <xrpl/tx/paths/AMMOffer.h>
22#include <xrpl/tx/transactors/dex/AMMContext.h>
23
24#include <cstdint>
25#include <exception>
26#include <optional>
27#include <stdexcept>
28
29namespace xrpl {
30
31template <typename TIn, typename TOut>
33 ReadView const& view,
34 AccountID const& ammAccountID,
36 Asset const& in,
37 Asset const& out,
38 AMMContext& ammContext,
40 : ammContext_(ammContext)
41 , ammAccountID_(ammAccountID)
43 , assetIn_(in)
44 , assetOut_(out)
46 , j_(j)
47{
48}
49
50template <typename TIn, typename TOut>
53{
54 auto const amountIn = ammAccountHolds(view, ammAccountID_, assetIn_);
55 auto const amountOut = ammAccountHolds(view, ammAccountID_, assetOut_);
56 // This should not happen.
57 if (amountIn < beast::kZero || amountOut < beast::kZero)
58 Throw<std::runtime_error>("AMMLiquidity: invalid balances");
59
60 return TAmounts{get<TIn>(amountIn), get<TOut>(amountOut)};
61}
62
63template <typename TIn, typename TOut>
66{
68
69 cur.in = toAmount<TIn>(
70 getAsset(balances.in),
74
75 if (ammContext_.curIters() == 0)
76 return cur;
77
78 // clang-format off
79 static constexpr std::uint32_t kFib[AMMContext::kMaxIterations] = {
80 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987,
81 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393,
82 196418, 317811, 514229, 832040, 1346269};
83 // clang-format on
84
85 XRPL_ASSERT(
86 !ammContext_.maxItersReached(),
87 "xrpl::AMMLiquidity::generateFibSeqOffer : maximum iterations");
88
89 cur.out = toAmount<TOut>(
90 getAsset(balances.out),
91 cur.out * kFib[ammContext_.curIters() - 1],
93 // swapAssetOut() returns negative in this case
94 if (cur.out >= balances.out)
95 Throw<std::overflow_error>("AMMLiquidity: generateFibSeqOffer exceeds the balance");
96
97 cur.in = swapAssetOut(balances, cur.out, tradingFee_);
98
99 return cur;
100}
101
102namespace {
103template <typename T>
104constexpr T
105maxAmount()
106{
107 if constexpr (std::is_same_v<T, XRPAmount>)
108 {
110 }
111 else if constexpr (std::is_same_v<T, IOUAmount>)
112 {
113 return IOUAmount(STAmount::kMaxValue / 2, STAmount::kMaxOffset);
114 }
115 else if constexpr (std::is_same_v<T, STAmount>)
116 {
118 }
119 else if constexpr (std::is_same_v<T, MPTAmount>)
120 {
122 }
123}
124
125template <typename T>
126T
127maxOut(T const& out, Asset const& asset)
128{
129 Number const res = out * Number{99, -2};
131}
132} // namespace
133
134template <typename TIn, typename TOut>
135std::optional<AMMOffer<TIn, TOut>>
137{
138 if (!rules.enabled(fixAMMOverflowOffer))
139 {
140 return AMMOffer<TIn, TOut>(
141 *this,
142 {maxAmount<TIn>(), swapAssetIn(balances, maxAmount<TIn>(), tradingFee_)},
143 balances,
144 Quality{balances});
145 }
146
147 auto const out = maxOut<TOut>(balances.out, assetOut());
148 if (out <= TOut{0} || out >= balances.out)
149 return std::nullopt;
150 return AMMOffer<TIn, TOut>(
151 *this, {swapAssetOut(balances, out, tradingFee_), out}, balances, Quality{balances});
152}
153
154template <typename TIn, typename TOut>
157 const
158{
159 // Can't generate more offers if multi-path.
160 if (ammContext_.maxItersReached())
161 return std::nullopt;
162
163 auto const balances = fetchBalances(view);
164
165 // Frozen accounts
166 if (balances.in == beast::kZero || balances.out == beast::kZero)
167 {
168 JLOG(j_.debug()) << "AMMLiquidity::getOffer, frozen accounts";
169 return std::nullopt;
170 }
171
172 JLOG(j_.trace()) << "AMMLiquidity::getOffer balances " << to_string(initialBalances_.in) << " "
173 << to_string(initialBalances_.out) << " new balances "
174 << to_string(balances.in) << " " << to_string(balances.out);
175
176 // Can't generate AMM with a better quality than CLOB's
177 // quality if AMM's Spot Price quality is less than CLOB quality or is
178 // within a threshold.
179 // Spot price quality (SPQ) is calculated within some precision threshold.
180 // On the next iteration, after SPQ is changed, the new SPQ might be close
181 // to the requested clobQuality but not exactly and potentially SPQ may keep
182 // on approaching clobQuality for many iterations. Checking for the quality
183 // threshold prevents this scenario.
184 if (auto const spotPriceQ = Quality{balances}; clobQuality &&
185 (spotPriceQ <= clobQuality ||
186 withinRelativeDistance(spotPriceQ, *clobQuality, Number(1, -7))))
187 {
188 JLOG(j_.trace()) << "AMMLiquidity::getOffer, higher clob quality";
189 return std::nullopt;
190 }
191
192 auto offer = [&]() -> std::optional<AMMOffer<TIn, TOut>> {
193 try
194 {
195 if (ammContext_.multiPath())
196 {
197 auto const amounts = generateFibSeqOffer(balances);
198 if (clobQuality && Quality{amounts} < clobQuality)
199 return std::nullopt;
200 return AMMOffer<TIn, TOut>(*this, amounts, balances, Quality{amounts});
201 }
202 if (!clobQuality)
203 {
204 // If there is no CLOB to compare against, return the largest
205 // amount, which doesn't overflow. The size is going to be
206 // changed in BookStep per either deliver amount limit, or
207 // sendmax, or available output or input funds. Might return
208 // nullopt if the pool is small.
209 return maxOffer(balances, view.rules());
210 }
211 if (auto const amounts =
212 changeSpotPriceQuality(balances, *clobQuality, tradingFee_, view.rules(), j_))
213 {
214 return AMMOffer<TIn, TOut>(*this, *amounts, balances, Quality{*amounts});
215 }
216 if (view.rules().enabled(fixAMMv1_2))
217 {
218 if (auto const maxAMMOffer = maxOffer(balances, view.rules());
219 maxAMMOffer && Quality{maxAMMOffer->amount()} > *clobQuality)
220 return maxAMMOffer;
221 }
222 }
223 catch (std::overflow_error const& e)
224 {
225 JLOG(j_.error()) << "AMMLiquidity::getOffer overflow " << e.what();
226 if (!view.rules().enabled(fixAMMOverflowOffer))
227 {
228 return maxOffer(balances, view.rules());
229 }
230
231 return std::nullopt;
232 }
233 catch (std::exception const& e)
234 {
235 JLOG(j_.error()) << "AMMLiquidity::getOffer exception " << e.what();
236 }
237 return std::nullopt;
238 }();
239
240 if (offer)
241 {
242 if (offer->amount().in > beast::kZero && offer->amount().out > beast::kZero)
243 {
244 JLOG(j_.trace()) << "AMMLiquidity::getOffer, created " << to_string(offer->amount().in)
245 << "/" << assetIn_ << " " << to_string(offer->amount().out) << "/"
246 << assetOut_;
247 return offer;
248 }
249
250 JLOG(j_.debug()) << "AMMLiquidity::getOffer, no valid offer " << ammContext_.multiPath()
251 << " " << ammContext_.curIters() << " "
252 << (clobQuality ? clobQuality->rate() : STAmount{}) << " "
253 << to_string(balances.in) << " " << to_string(balances.out);
254 }
255
256 return std::nullopt;
257}
258
267
268} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Maintains AMM info per overall payment engine execution and individual iteration.
Definition AMMContext.h:16
static constexpr std::uint8_t kMaxIterations
Definition AMMContext.h:21
AMMLiquidity class provides AMM offers to BookStep class.
beast::Journal const j_
TAmounts< TIn, TOut > generateFibSeqOffer(TAmounts< TIn, TOut > const &balances) const
Generate AMM offers with the offer size based on Fibonacci sequence.
Asset const & assetOut() const
TAmounts< TIn, TOut > const initialBalances_
Asset const assetIn_
AccountID const ammAccountID_
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.
std::optional< AMMOffer< TIn, TOut > > getOffer(ReadView const &view, std::optional< Quality > const &clobQuality) const
Generate AMM offer.
Asset const assetOut_
AMMLiquidity(ReadView const &view, AccountID const &ammAccountID, std::uint32_t tradingFee, Asset const &in, Asset const &out, AMMContext &ammContext, beast::Journal j)
static Number const kInitialFibSeqPct
std::uint32_t const tradingFee_
std::uint32_t tradingFee() const
AMMContext & ammContext_
Represents synthetic AMM offer in BookStep.
Definition AMMOffer.h:22
Number is a floating point type that can represent a wide range of values.
Definition Number.h:306
Represents the logical ratio of output currency to input currency.
Definition Quality.h:91
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:33
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:171
static constexpr std::uint64_t kMaxValue
Definition STAmount.h:53
static constexpr std::uint64_t kMaxNative
Definition STAmount.h:55
static constexpr int kMaxOffset
Definition STAmount.h:48
T is_same_v
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
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:427
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
T toAmount(STAmount const &amt)=delete
STAmount ammAccountHolds(ReadView const &view, AccountID const &ammAccountID, Asset const &asset)
Returns total amount held by AMM for the given token.
Asset getAsset(T const &amt)
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:305
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:28
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:493
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:115
constexpr std::uint64_t kMaxMpTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:238
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:49
Represents a pair of input and output currencies.
Definition Quality.h:26
T what(T... args)