rippled
Loading...
Searching...
No Matches
AMMHelpers.h
1#pragma once
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Number.h>
5#include <xrpl/beast/utility/Journal.h>
6#include <xrpl/protocol/AMMCore.h>
7#include <xrpl/protocol/AmountConversions.h>
8#include <xrpl/protocol/Feature.h>
9#include <xrpl/protocol/IOUAmount.h>
10#include <xrpl/protocol/Issue.h>
11#include <xrpl/protocol/Quality.h>
12#include <xrpl/protocol/Rules.h>
13#include <xrpl/protocol/STAmount.h>
14
15namespace xrpl {
16
17namespace detail {
18
19Number
20reduceOffer(auto const& amount)
21{
22 static Number const reducedOfferPct(9999, -4);
23
24 // Make sure the result is always less than amount or zero.
26 return amount * reducedOfferPct;
27}
28
29} // namespace detail
30
31enum class IsDeposit : bool { No = false, Yes = true };
32
38STAmount
39ammLPTokens(STAmount const& asset1, STAmount const& asset2, Issue const& lptIssue);
40
48STAmount
50 STAmount const& asset1Balance,
51 STAmount const& asset1Deposit,
52 STAmount const& lptAMMBalance,
53 std::uint16_t tfee);
54
62STAmount
63ammAssetIn(STAmount const& asset1Balance, STAmount const& lptAMMBalance, STAmount const& lpTokens, std::uint16_t tfee);
64
73STAmount
75 STAmount const& asset1Balance,
76 STAmount const& asset1Withdraw,
77 STAmount const& lptAMMBalance,
78 std::uint16_t tfee);
79
87STAmount
88ammAssetOut(STAmount const& assetBalance, STAmount const& lptAMMBalance, STAmount const& lpTokens, std::uint16_t tfee);
89
97inline bool
98withinRelativeDistance(Quality const& calcQuality, Quality const& reqQuality, Number const& dist)
99{
100 if (calcQuality == reqQuality)
101 return true;
102 auto const [min, max] = std::minmax(calcQuality, reqQuality);
103 // Relative distance is (max - min)/max. Can't use basic operations
104 // on Quality. Have to use Quality::rate() instead, which
105 // is inverse of quality: (1/max.rate - 1/min.rate)/(1/max.rate)
106 return ((min.rate() - max.rate()) / min.rate()) < dist;
107}
108
116// clang-format off
117template <typename Amt>
118 requires(
121bool
122withinRelativeDistance(Amt const& calc, Amt const& req, Number const& dist)
123{
124 if (calc == req)
125 return true;
126 auto const [min, max] = std::minmax(calc, req);
127 return ((max - min) / max) < dist;
128}
129// clang-format on
130
135solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c);
136
160template <typename TIn, typename TOut>
162getAMMOfferStartWithTakerGets(TAmounts<TIn, TOut> const& pool, Quality const& targetQuality, std::uint16_t const& tfee)
163{
164 if (targetQuality.rate() == beast::zero)
165 return std::nullopt;
166
168 auto const f = feeMult(tfee);
169 auto const a = 1;
170 auto const b = pool.in * (1 - 1 / f) / targetQuality.rate() - 2 * pool.out;
171 auto const c = pool.out * pool.out - (pool.in * pool.out) / targetQuality.rate();
172
173 auto nTakerGets = solveQuadraticEqSmallest(a, b, c);
174 if (!nTakerGets || *nTakerGets <= 0)
175 return std::nullopt; // LCOV_EXCL_LINE
176
177 auto const nTakerGetsConstraint = pool.out - pool.in / (targetQuality.rate() * f);
178 if (nTakerGetsConstraint <= 0)
179 return std::nullopt;
180
181 // Select the smallest to maximize the quality
182 if (nTakerGetsConstraint < *nTakerGets)
183 nTakerGets = nTakerGetsConstraint;
184
185 auto getAmounts = [&pool, &tfee](Number const& nTakerGetsProposed) {
186 // Round downward to minimize the offer and to maximize the quality.
187 // This has the most impact when takerGets is XRP.
188 auto const takerGets = toAmount<TOut>(getIssue(pool.out), nTakerGetsProposed, Number::downward);
189 return TAmounts<TIn, TOut>{swapAssetOut(pool, takerGets, tfee), takerGets};
190 };
191
192 // Try to reduce the offer size to improve the quality.
193 // The quality might still not match the targetQuality for a tiny offer.
194 if (auto const amounts = getAmounts(*nTakerGets); Quality{amounts} < targetQuality)
195 return getAmounts(detail::reduceOffer(amounts.out));
196 else
197 return amounts;
198}
199
223template <typename TIn, typename TOut>
225getAMMOfferStartWithTakerPays(TAmounts<TIn, TOut> const& pool, Quality const& targetQuality, std::uint16_t tfee)
226{
227 if (targetQuality.rate() == beast::zero)
228 return std::nullopt;
229
231 auto const f = feeMult(tfee);
232 auto const& a = f;
233 auto const b = pool.in * (1 + f);
234 auto const c = pool.in * pool.in - pool.in * pool.out * targetQuality.rate();
235
236 auto nTakerPays = solveQuadraticEqSmallest(a, b, c);
237 if (!nTakerPays || nTakerPays <= 0)
238 return std::nullopt; // LCOV_EXCL_LINE
239
240 auto const nTakerPaysConstraint = pool.out * targetQuality.rate() - pool.in / f;
241 if (nTakerPaysConstraint <= 0)
242 return std::nullopt;
243
244 // Select the smallest to maximize the quality
245 if (nTakerPaysConstraint < *nTakerPays)
246 nTakerPays = nTakerPaysConstraint;
247
248 auto getAmounts = [&pool, &tfee](Number const& nTakerPaysProposed) {
249 // Round downward to minimize the offer and to maximize the quality.
250 // This has the most impact when takerPays is XRP.
251 auto const takerPays = toAmount<TIn>(getIssue(pool.in), nTakerPaysProposed, Number::downward);
252 return TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
253 };
254
255 // Try to reduce the offer size to improve the quality.
256 // The quality might still not match the targetQuality for a tiny offer.
257 if (auto const amounts = getAmounts(*nTakerPays); Quality{amounts} < targetQuality)
258 return getAmounts(detail::reduceOffer(amounts.in));
259 else
260 return amounts;
261}
262
279template <typename TIn, typename TOut>
282 TAmounts<TIn, TOut> const& pool,
283 Quality const& quality,
284 std::uint16_t tfee,
285 Rules const& rules,
287{
288 if (!rules.enabled(fixAMMv1_1))
289 {
290 // Finds takerPays (i) and takerGets (o) such that given pool
291 // composition poolGets(I) and poolPays(O): (O - o) / (I + i) = quality.
292 // Where takerGets is calculated as the swapAssetIn (see below).
293 // The above equation produces the quadratic equation:
294 // i^2*(1-fee) + i*I*(2-fee) + I^2 - I*O/quality,
295 // which is solved for i, and o is found with swapAssetIn().
296 auto const f = feeMult(tfee); // 1 - fee
297 auto const& a = f;
298 auto const b = pool.in * (1 + f);
299 Number const c = pool.in * pool.in - pool.in * pool.out * quality.rate();
300 if (auto const res = b * b - 4 * a * c; res < 0)
301 return std::nullopt; // LCOV_EXCL_LINE
302 else if (auto const nTakerPaysPropose = (-b + root2(res)) / (2 * a); nTakerPaysPropose > 0)
303 {
304 auto const nTakerPays = [&]() {
305 // The fee might make the AMM offer quality less than CLOB
306 // quality. Therefore, AMM offer has to satisfy this constraint:
307 // o / i >= q. Substituting o with swapAssetIn() gives: i <= O /
308 // q - I / (1 - fee).
309 auto const nTakerPaysConstraint = pool.out * quality.rate() - pool.in / f;
310 if (nTakerPaysPropose > nTakerPaysConstraint)
311 return nTakerPaysConstraint;
312 return nTakerPaysPropose;
313 }();
314 if (nTakerPays <= 0)
315 {
316 JLOG(j.trace()) << "changeSpotPriceQuality calc failed: " << to_string(pool.in) << " "
317 << to_string(pool.out) << " " << quality << " " << tfee;
318 return std::nullopt;
319 }
320 auto const takerPays = toAmount<TIn>(getIssue(pool.in), nTakerPays, Number::upward);
321 // should not fail
322 if (auto const amounts = TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
323 Quality{amounts} < quality && !withinRelativeDistance(Quality{amounts}, quality, Number(1, -7)))
324 {
325 JLOG(j.error()) << "changeSpotPriceQuality failed: " << to_string(pool.in) << " " << to_string(pool.out)
326 << " "
327 << " " << quality << " " << tfee << " " << to_string(amounts.in) << " "
328 << to_string(amounts.out);
329 Throw<std::runtime_error>("changeSpotPriceQuality failed");
330 }
331 else
332 {
333 JLOG(j.trace()) << "changeSpotPriceQuality succeeded: " << to_string(pool.in) << " "
334 << to_string(pool.out) << " "
335 << " " << quality << " " << tfee << " " << to_string(amounts.in) << " "
336 << to_string(amounts.out);
337 return amounts;
338 }
339 }
340 JLOG(j.trace()) << "changeSpotPriceQuality calc failed: " << to_string(pool.in) << " " << to_string(pool.out)
341 << " " << quality << " " << tfee;
342 return std::nullopt;
343 }
344
345 // Generate the offer starting with XRP side. Return seated offer amounts
346 // if the offer can be generated, otherwise nullopt.
347 auto const amounts = [&]() {
348 if (isXRP(getIssue(pool.out)))
349 return getAMMOfferStartWithTakerGets(pool, quality, tfee);
350 return getAMMOfferStartWithTakerPays(pool, quality, tfee);
351 }();
352 if (!amounts)
353 {
354 JLOG(j.trace()) << "changeSpotPrice calc failed: " << to_string(pool.in) << " " << to_string(pool.out) << " "
355 << quality << " " << tfee << std::endl;
356 return std::nullopt;
357 }
358
359 if (Quality{*amounts} < quality)
360 {
361 JLOG(j.error()) << "changeSpotPriceQuality failed: " << to_string(pool.in) << " " << to_string(pool.out) << " "
362 << quality << " " << tfee << " " << to_string(amounts->in) << " " << to_string(amounts->out);
363 return std::nullopt;
364 }
365
366 JLOG(j.trace()) << "changeSpotPriceQuality succeeded: " << to_string(pool.in) << " " << to_string(pool.out) << " "
367 << " " << quality << " " << tfee << " " << to_string(amounts->in) << " " << to_string(amounts->out);
368
369 return amounts;
370}
371
393template <typename TIn, typename TOut>
394TOut
395swapAssetIn(TAmounts<TIn, TOut> const& pool, TIn const& assetIn, std::uint16_t tfee)
396{
397 if (auto const& rules = getCurrentTransactionRules(); rules && rules->enabled(fixAMMv1_1))
398 {
399 // set rounding to always favor the amm. Clip to zero.
400 // calculate:
401 // pool.out -
402 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
403 // and explicitly set the rounding modes
404 // Favoring the amm means we should:
405 // minimize:
406 // pool.out -
407 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
408 // maximize:
409 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
410 // (pool.in * pool.out)
411 // minimize:
412 // (pool.in + assetIn * feeMult(tfee)),
413 // minimize:
414 // assetIn * feeMult(tfee)
415 // feeMult is: (1-fee), fee is tfee/100000
416 // minimize:
417 // 1-fee
418 // maximize:
419 // fee
421
423 auto const numerator = pool.in * pool.out;
424 auto const fee = getFee(tfee);
425
427 auto const denom = pool.in + assetIn * (1 - fee);
428
429 if (denom.signum() <= 0)
430 return toAmount<TOut>(getIssue(pool.out), 0);
431
433 auto const ratio = numerator / denom;
434
436 auto const swapOut = pool.out - ratio;
437
438 if (swapOut.signum() < 0)
439 return toAmount<TOut>(getIssue(pool.out), 0);
440
441 return toAmount<TOut>(getIssue(pool.out), swapOut, Number::downward);
442 }
443 else
444 {
445 return toAmount<TOut>(
446 getIssue(pool.out),
447 pool.out - (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
449 }
450}
451
461template <typename TIn, typename TOut>
462TIn
463swapAssetOut(TAmounts<TIn, TOut> const& pool, TOut const& assetOut, std::uint16_t tfee)
464{
465 if (auto const& rules = getCurrentTransactionRules(); rules && rules->enabled(fixAMMv1_1))
466 {
467 // set rounding to always favor the amm. Clip to zero.
468 // calculate:
469 // ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
470 // (1-tfee/100000)
471 // maximize:
472 // ((pool.in * pool.out) / (pool.out - assetOut) - pool.in)
473 // maximize:
474 // (pool.in * pool.out) / (pool.out - assetOut)
475 // maximize:
476 // (pool.in * pool.out)
477 // minimize
478 // (pool.out - assetOut)
479 // minimize:
480 // (1-tfee/100000)
481 // maximize:
482 // tfee/100000
483
485
487 auto const numerator = pool.in * pool.out;
488
490 auto const denom = pool.out - assetOut;
491 if (denom.signum() <= 0)
492 {
493 return toMaxAmount<TIn>(getIssue(pool.in));
494 }
495
497 auto const ratio = numerator / denom;
498 auto const numerator2 = ratio - pool.in;
499 auto const fee = getFee(tfee);
500
502 auto const feeMult = 1 - fee;
503
505 auto const swapIn = numerator2 / feeMult;
506 if (swapIn.signum() < 0)
507 return toAmount<TIn>(getIssue(pool.in), 0);
508
509 return toAmount<TIn>(getIssue(pool.in), swapIn, Number::upward);
510 }
511 else
512 {
513 return toAmount<TIn>(
514 getIssue(pool.in),
515 ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) / feeMult(tfee),
517 }
518}
519
522Number
523square(Number const& n);
524
536STAmount
537adjustLPTokens(STAmount const& lptAMMBalance, STAmount const& lpTokens, IsDeposit isDeposit);
538
552 STAmount const& amountBalance,
553 STAmount const& amount,
554 std::optional<STAmount> const& amount2,
555 STAmount const& lptAMMBalance,
556 STAmount const& lpTokens,
557 std::uint16_t tfee,
558 IsDeposit isDeposit);
559
563Number
564solveQuadraticEq(Number const& a, Number const& b, Number const& c);
565
566STAmount
567multiply(STAmount const& amount, Number const& frac, Number::rounding_mode rm);
568
569namespace detail {
570
573{
574 // Minimize on deposit, maximize on withdraw to ensure
575 // AMM invariant sqrt(poolAsset1 * poolAsset2) >= LPTokensBalance
576 return isDeposit == IsDeposit::Yes ? Number::downward : Number::upward;
577}
578
581{
582 // Maximize on deposit, minimize on withdraw to ensure
583 // AMM invariant sqrt(poolAsset1 * poolAsset2) >= LPTokensBalance
584 return isDeposit == IsDeposit::Yes ? Number::upward : Number::downward;
585}
586
587} // namespace detail
588
594template <typename A>
595STAmount
596getRoundedAsset(Rules const& rules, STAmount const& balance, A const& frac, IsDeposit isDeposit)
597{
598 if (!rules.enabled(fixAMMv1_3))
599 {
600 if constexpr (std::is_same_v<A, STAmount>)
601 return multiply(balance, frac, balance.issue());
602 else
603 return toSTAmount(balance.issue(), balance * frac);
604 }
605 auto const rm = detail::getAssetRounding(isDeposit);
606 return multiply(balance, frac, rm);
607}
608
618STAmount
620 Rules const& rules,
621 std::function<Number()>&& noRoundCb,
622 STAmount const& balance,
623 std::function<Number()>&& productCb,
624 IsDeposit isDeposit);
625
633STAmount
634getRoundedLPTokens(Rules const& rules, STAmount const& balance, Number const& frac, IsDeposit isDeposit);
635
647STAmount
649 Rules const& rules,
650 std::function<Number()>&& noRoundCb,
651 STAmount const& lptAMMBalance,
652 std::function<Number()>&& productCb,
653 IsDeposit isDeposit);
654
655/* Next two functions adjust asset in/out amount to factor in the adjusted
656 * lptokens. The lptokens are calculated from the asset in/out. The lptokens are
657 * then adjusted to factor in the loss in precision. The adjusted lptokens might
658 * be less than the initially calculated tokens. Therefore, the asset in/out
659 * must be adjusted. The rounding might result in the adjusted amount being
660 * greater than the original asset in/out amount. If this happens,
661 * then the original amount is reduced by the difference in the adjusted amount
662 * and the original amount. The actual tokens and the actual adjusted amount
663 * are then recalculated. The minimum of the original and the actual
664 * adjusted amount is returned.
665 */
668 Rules const& rules,
669 STAmount const& balance,
670 STAmount const& amount,
671 STAmount const& lptAMMBalance,
672 STAmount const& tokens,
673 std::uint16_t tfee);
676 Rules const& rules,
677 STAmount const& balance,
678 STAmount const& amount,
679 STAmount const& lptAMMBalance,
680 STAmount const& tokens,
681 std::uint16_t tfee);
682
686Number
687adjustFracByTokens(Rules const& rules, STAmount const& lptAMMBalance, STAmount const& tokens, Number const& frac);
688
689} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:40
Stream error() const
Definition Journal.h:318
Stream trace() const
Severity stream access functions.
Definition Journal.h:294
Number is a floating point type that can represent a wide range of values.
Definition Number.h:207
static rounding_mode getround()
Definition Number.cpp:33
static rounding_mode setround(rounding_mode mode)
Definition Number.cpp:39
Rules controlling protocol behavior.
Definition Rules.h:18
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:118
Issue const & issue() const
Definition STAmount.h:454
T endl(T... args)
T is_same_v
T minmax(T... args)
Number reduceOffer(auto const &amount)
Definition AMMHelpers.h:20
Number::rounding_mode getLPTokenRounding(IsDeposit isDeposit)
Definition AMMHelpers.h:572
Number::rounding_mode getAssetRounding(IsDeposit isDeposit)
Definition AMMHelpers.h:580
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
bool isXRP(AccountID const &c)
Definition AccountID.h:70
Number adjustFracByTokens(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &tokens, Number const &frac)
Find a fraction of tokens after the tokens are adjusted.
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:597
IsDeposit
Definition AMMHelpers.h:31
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
std::optional< Rules > const & getCurrentTransactionRules()
Definition Rules.cpp:31
Number solveQuadraticEq(Number const &a, Number const &b, Number const &c)
Positive solution for quadratic equation: x = (-b + sqrt(b**2 + 4*a*c))/(2*a)
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:395
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition AMMHelpers.cpp:6
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:34
STAmount ammAssetOut(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
std::pair< STAmount, STAmount > adjustAssetInByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
std::optional< TAmounts< TIn, TOut > > getAMMOfferStartWithTakerGets(TAmounts< TIn, TOut > const &pool, Quality const &targetQuality, std::uint16_t const &tfee)
Generate AMM offer starting with takerGets when AMM pool from the payment perspective is IOU(in)/XRP(...
Definition AMMHelpers.h:162
std::pair< STAmount, STAmount > adjustAssetOutByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
std::optional< TAmounts< TIn, TOut > > getAMMOfferStartWithTakerPays(TAmounts< TIn, TOut > const &pool, Quality const &targetQuality, std::uint16_t tfee)
Generate AMM offer starting with takerPays when AMM pool from the payment perspective is XRP(in)/IOU(...
Definition AMMHelpers.h:225
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition AMMHelpers.h:596
Number feeMult(std::uint16_t tfee)
Get fee multiplier (1 - tfee) @tfee trading fee in basis points.
Definition AMMCore.h:84
std::optional< Number > solveQuadraticEqSmallest(Number const &a, Number const &b, Number const &c)
Solve quadratic equation to find takerGets or takerPays.
Number root2(Number f)
Definition Number.cpp:1010
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:281
STAmount ammAssetIn(STAmount const &asset1Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset deposit given LP Tokens.
std::tuple< STAmount, std::optional< STAmount >, STAmount > adjustAmountsByLPTokens(STAmount const &amountBalance, STAmount const &amount, std::optional< STAmount > const &amount2, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee, IsDeposit isDeposit)
Calls adjustLPTokens() and adjusts deposit or withdraw amounts if the adjusted LP tokens are less tha...
STAmount lpTokensIn(STAmount const &asset1Balance, STAmount const &asset1Withdraw, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's withdraw amount.
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
Definition AMMCore.h:75
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:463
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:98
Number square(Number const &n)
Return square of n.
STAmount lpTokensOut(STAmount const &asset1Balance, STAmount const &asset1Deposit, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's deposit amount.
Issue getIssue(T const &amt)