rippled
Loading...
Searching...
No Matches
AMMHelpers.h
1#ifndef XRPL_APP_MISC_AMMHELPERS_H_INCLUDED
2#define XRPL_APP_MISC_AMMHELPERS_H_INCLUDED
3
4#include <xrpl/basics/Log.h>
5#include <xrpl/basics/Number.h>
6#include <xrpl/beast/utility/Journal.h>
7#include <xrpl/protocol/AMMCore.h>
8#include <xrpl/protocol/AmountConversions.h>
9#include <xrpl/protocol/Feature.h>
10#include <xrpl/protocol/IOUAmount.h>
11#include <xrpl/protocol/Issue.h>
12#include <xrpl/protocol/Quality.h>
13#include <xrpl/protocol/Rules.h>
14#include <xrpl/protocol/STAmount.h>
15
16namespace ripple {
17
18namespace detail {
19
20Number
21reduceOffer(auto const& amount)
22{
23 static Number const reducedOfferPct(9999, -4);
24
25 // Make sure the result is always less than amount or zero.
27 return amount * reducedOfferPct;
28}
29
30} // namespace detail
31
32enum class IsDeposit : bool { No = false, Yes = true };
33
39STAmount
41 STAmount const& asset1,
42 STAmount const& asset2,
43 Issue const& lptIssue);
44
52STAmount
54 STAmount const& asset1Balance,
55 STAmount const& asset1Deposit,
56 STAmount const& lptAMMBalance,
57 std::uint16_t tfee);
58
66STAmount
68 STAmount const& asset1Balance,
69 STAmount const& lptAMMBalance,
70 STAmount const& lpTokens,
71 std::uint16_t tfee);
72
81STAmount
83 STAmount const& asset1Balance,
84 STAmount const& asset1Withdraw,
85 STAmount const& lptAMMBalance,
86 std::uint16_t tfee);
87
95STAmount
97 STAmount const& assetBalance,
98 STAmount const& lptAMMBalance,
99 STAmount const& lpTokens,
100 std::uint16_t tfee);
101
109inline bool
111 Quality const& calcQuality,
112 Quality const& reqQuality,
113 Number const& dist)
114{
115 if (calcQuality == reqQuality)
116 return true;
117 auto const [min, max] = std::minmax(calcQuality, reqQuality);
118 // Relative distance is (max - min)/max. Can't use basic operations
119 // on Quality. Have to use Quality::rate() instead, which
120 // is inverse of quality: (1/max.rate - 1/min.rate)/(1/max.rate)
121 return ((min.rate() - max.rate()) / min.rate()) < dist;
122}
123
131// clang-format off
132template <typename Amt>
133 requires(
136bool
137withinRelativeDistance(Amt const& calc, Amt const& req, Number const& dist)
138{
139 if (calc == req)
140 return true;
141 auto const [min, max] = std::minmax(calc, req);
142 return ((max - min) / max) < dist;
143}
144// clang-format on
145
150solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c);
151
175template <typename TIn, typename TOut>
178 TAmounts<TIn, TOut> const& pool,
179 Quality const& targetQuality,
180 std::uint16_t const& tfee)
181{
182 if (targetQuality.rate() == beast::zero)
183 return std::nullopt;
184
186 auto const f = feeMult(tfee);
187 auto const a = 1;
188 auto const b = pool.in * (1 - 1 / f) / targetQuality.rate() - 2 * pool.out;
189 auto const c =
190 pool.out * pool.out - (pool.in * pool.out) / targetQuality.rate();
191
192 auto nTakerGets = solveQuadraticEqSmallest(a, b, c);
193 if (!nTakerGets || *nTakerGets <= 0)
194 return std::nullopt; // LCOV_EXCL_LINE
195
196 auto const nTakerGetsConstraint =
197 pool.out - pool.in / (targetQuality.rate() * f);
198 if (nTakerGetsConstraint <= 0)
199 return std::nullopt;
200
201 // Select the smallest to maximize the quality
202 if (nTakerGetsConstraint < *nTakerGets)
203 nTakerGets = nTakerGetsConstraint;
204
205 auto getAmounts = [&pool, &tfee](Number const& nTakerGetsProposed) {
206 // Round downward to minimize the offer and to maximize the quality.
207 // This has the most impact when takerGets is XRP.
208 auto const takerGets = toAmount<TOut>(
209 getIssue(pool.out), nTakerGetsProposed, Number::downward);
210 return TAmounts<TIn, TOut>{
211 swapAssetOut(pool, takerGets, tfee), takerGets};
212 };
213
214 // Try to reduce the offer size to improve the quality.
215 // The quality might still not match the targetQuality for a tiny offer.
216 if (auto const amounts = getAmounts(*nTakerGets);
217 Quality{amounts} < targetQuality)
218 return getAmounts(detail::reduceOffer(amounts.out));
219 else
220 return amounts;
221}
222
246template <typename TIn, typename TOut>
249 TAmounts<TIn, TOut> const& pool,
250 Quality const& targetQuality,
251 std::uint16_t tfee)
252{
253 if (targetQuality.rate() == beast::zero)
254 return std::nullopt;
255
257 auto const f = feeMult(tfee);
258 auto const& a = f;
259 auto const b = pool.in * (1 + f);
260 auto const c =
261 pool.in * pool.in - pool.in * pool.out * targetQuality.rate();
262
263 auto nTakerPays = solveQuadraticEqSmallest(a, b, c);
264 if (!nTakerPays || nTakerPays <= 0)
265 return std::nullopt; // LCOV_EXCL_LINE
266
267 auto const nTakerPaysConstraint =
268 pool.out * targetQuality.rate() - pool.in / f;
269 if (nTakerPaysConstraint <= 0)
270 return std::nullopt;
271
272 // Select the smallest to maximize the quality
273 if (nTakerPaysConstraint < *nTakerPays)
274 nTakerPays = nTakerPaysConstraint;
275
276 auto getAmounts = [&pool, &tfee](Number const& nTakerPaysProposed) {
277 // Round downward to minimize the offer and to maximize the quality.
278 // This has the most impact when takerPays is XRP.
279 auto const takerPays = toAmount<TIn>(
280 getIssue(pool.in), nTakerPaysProposed, Number::downward);
281 return TAmounts<TIn, TOut>{
282 takerPays, swapAssetIn(pool, takerPays, tfee)};
283 };
284
285 // Try to reduce the offer size to improve the quality.
286 // The quality might still not match the targetQuality for a tiny offer.
287 if (auto const amounts = getAmounts(*nTakerPays);
288 Quality{amounts} < targetQuality)
289 return getAmounts(detail::reduceOffer(amounts.in));
290 else
291 return amounts;
292}
293
310template <typename TIn, typename TOut>
313 TAmounts<TIn, TOut> const& pool,
314 Quality const& quality,
315 std::uint16_t tfee,
316 Rules const& rules,
318{
319 if (!rules.enabled(fixAMMv1_1))
320 {
321 // Finds takerPays (i) and takerGets (o) such that given pool
322 // composition poolGets(I) and poolPays(O): (O - o) / (I + i) = quality.
323 // Where takerGets is calculated as the swapAssetIn (see below).
324 // The above equation produces the quadratic equation:
325 // i^2*(1-fee) + i*I*(2-fee) + I^2 - I*O/quality,
326 // which is solved for i, and o is found with swapAssetIn().
327 auto const f = feeMult(tfee); // 1 - fee
328 auto const& a = f;
329 auto const b = pool.in * (1 + f);
330 Number const c =
331 pool.in * pool.in - pool.in * pool.out * quality.rate();
332 if (auto const res = b * b - 4 * a * c; res < 0)
333 return std::nullopt; // LCOV_EXCL_LINE
334 else if (auto const nTakerPaysPropose = (-b + root2(res)) / (2 * a);
335 nTakerPaysPropose > 0)
336 {
337 auto const nTakerPays = [&]() {
338 // The fee might make the AMM offer quality less than CLOB
339 // quality. Therefore, AMM offer has to satisfy this constraint:
340 // o / i >= q. Substituting o with swapAssetIn() gives: i <= O /
341 // q - I / (1 - fee).
342 auto const nTakerPaysConstraint =
343 pool.out * quality.rate() - pool.in / f;
344 if (nTakerPaysPropose > nTakerPaysConstraint)
345 return nTakerPaysConstraint;
346 return nTakerPaysPropose;
347 }();
348 if (nTakerPays <= 0)
349 {
350 JLOG(j.trace())
351 << "changeSpotPriceQuality calc failed: "
352 << to_string(pool.in) << " " << to_string(pool.out) << " "
353 << quality << " " << tfee;
354 return std::nullopt;
355 }
356 auto const takerPays =
357 toAmount<TIn>(getIssue(pool.in), nTakerPays, Number::upward);
358 // should not fail
359 if (auto const amounts =
360 TAmounts<TIn, TOut>{
361 takerPays, swapAssetIn(pool, takerPays, tfee)};
362 Quality{amounts} < quality &&
364 Quality{amounts}, quality, Number(1, -7)))
365 {
366 JLOG(j.error())
367 << "changeSpotPriceQuality failed: " << to_string(pool.in)
368 << " " << to_string(pool.out) << " "
369 << " " << quality << " " << tfee << " "
370 << to_string(amounts.in) << " " << to_string(amounts.out);
371 Throw<std::runtime_error>("changeSpotPriceQuality failed");
372 }
373 else
374 {
375 JLOG(j.trace())
376 << "changeSpotPriceQuality succeeded: "
377 << to_string(pool.in) << " " << to_string(pool.out) << " "
378 << " " << quality << " " << tfee << " "
379 << to_string(amounts.in) << " " << to_string(amounts.out);
380 return amounts;
381 }
382 }
383 JLOG(j.trace()) << "changeSpotPriceQuality calc failed: "
384 << to_string(pool.in) << " " << to_string(pool.out)
385 << " " << quality << " " << tfee;
386 return std::nullopt;
387 }
388
389 // Generate the offer starting with XRP side. Return seated offer amounts
390 // if the offer can be generated, otherwise nullopt.
391 auto const amounts = [&]() {
392 if (isXRP(getIssue(pool.out)))
393 return getAMMOfferStartWithTakerGets(pool, quality, tfee);
394 return getAMMOfferStartWithTakerPays(pool, quality, tfee);
395 }();
396 if (!amounts)
397 {
398 JLOG(j.trace()) << "changeSpotPrice calc failed: " << to_string(pool.in)
399 << " " << to_string(pool.out) << " " << quality << " "
400 << tfee << std::endl;
401 return std::nullopt;
402 }
403
404 if (Quality{*amounts} < quality)
405 {
406 JLOG(j.error()) << "changeSpotPriceQuality failed: "
407 << to_string(pool.in) << " " << to_string(pool.out)
408 << " " << quality << " " << tfee << " "
409 << to_string(amounts->in) << " "
410 << to_string(amounts->out);
411 return std::nullopt;
412 }
413
414 JLOG(j.trace()) << "changeSpotPriceQuality succeeded: "
415 << to_string(pool.in) << " " << to_string(pool.out) << " "
416 << " " << quality << " " << tfee << " "
417 << to_string(amounts->in) << " " << to_string(amounts->out);
418
419 return amounts;
420}
421
443template <typename TIn, typename TOut>
444TOut
446 TAmounts<TIn, TOut> const& pool,
447 TIn const& assetIn,
448 std::uint16_t tfee)
449{
450 if (auto const& rules = getCurrentTransactionRules();
451 rules && rules->enabled(fixAMMv1_1))
452 {
453 // set rounding to always favor the amm. Clip to zero.
454 // calculate:
455 // pool.out -
456 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
457 // and explicitly set the rounding modes
458 // Favoring the amm means we should:
459 // minimize:
460 // pool.out -
461 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
462 // maximize:
463 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
464 // (pool.in * pool.out)
465 // minimize:
466 // (pool.in + assetIn * feeMult(tfee)),
467 // minimize:
468 // assetIn * feeMult(tfee)
469 // feeMult is: (1-fee), fee is tfee/100000
470 // minimize:
471 // 1-fee
472 // maximize:
473 // fee
475
477 auto const numerator = pool.in * pool.out;
478 auto const fee = getFee(tfee);
479
481 auto const denom = pool.in + assetIn * (1 - fee);
482
483 if (denom.signum() <= 0)
484 return toAmount<TOut>(getIssue(pool.out), 0);
485
487 auto const ratio = numerator / denom;
488
490 auto const swapOut = pool.out - ratio;
491
492 if (swapOut.signum() < 0)
493 return toAmount<TOut>(getIssue(pool.out), 0);
494
495 return toAmount<TOut>(getIssue(pool.out), swapOut, Number::downward);
496 }
497 else
498 {
499 return toAmount<TOut>(
500 getIssue(pool.out),
501 pool.out -
502 (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
504 }
505}
506
516template <typename TIn, typename TOut>
517TIn
519 TAmounts<TIn, TOut> const& pool,
520 TOut const& assetOut,
521 std::uint16_t tfee)
522{
523 if (auto const& rules = getCurrentTransactionRules();
524 rules && rules->enabled(fixAMMv1_1))
525 {
526 // set rounding to always favor the amm. Clip to zero.
527 // calculate:
528 // ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
529 // (1-tfee/100000)
530 // maximize:
531 // ((pool.in * pool.out) / (pool.out - assetOut) - pool.in)
532 // maximize:
533 // (pool.in * pool.out) / (pool.out - assetOut)
534 // maximize:
535 // (pool.in * pool.out)
536 // minimize
537 // (pool.out - assetOut)
538 // minimize:
539 // (1-tfee/100000)
540 // maximize:
541 // tfee/100000
542
544
546 auto const numerator = pool.in * pool.out;
547
549 auto const denom = pool.out - assetOut;
550 if (denom.signum() <= 0)
551 {
552 return toMaxAmount<TIn>(getIssue(pool.in));
553 }
554
556 auto const ratio = numerator / denom;
557 auto const numerator2 = ratio - pool.in;
558 auto const fee = getFee(tfee);
559
561 auto const feeMult = 1 - fee;
562
564 auto const swapIn = numerator2 / feeMult;
565 if (swapIn.signum() < 0)
566 return toAmount<TIn>(getIssue(pool.in), 0);
567
568 return toAmount<TIn>(getIssue(pool.in), swapIn, Number::upward);
569 }
570 else
571 {
572 return toAmount<TIn>(
573 getIssue(pool.in),
574 ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
575 feeMult(tfee),
577 }
578}
579
582Number
583square(Number const& n);
584
596STAmount
598 STAmount const& lptAMMBalance,
599 STAmount const& lpTokens,
600 IsDeposit isDeposit);
601
615 STAmount const& amountBalance,
616 STAmount const& amount,
617 std::optional<STAmount> const& amount2,
618 STAmount const& lptAMMBalance,
619 STAmount const& lpTokens,
620 std::uint16_t tfee,
621 IsDeposit isDeposit);
622
626Number
627solveQuadraticEq(Number const& a, Number const& b, Number const& c);
628
629STAmount
630multiply(STAmount const& amount, Number const& frac, Number::rounding_mode rm);
631
632namespace detail {
633
636{
637 // Minimize on deposit, maximize on withdraw to ensure
638 // AMM invariant sqrt(poolAsset1 * poolAsset2) >= LPTokensBalance
639 return isDeposit == IsDeposit::Yes ? Number::downward : Number::upward;
640}
641
644{
645 // Maximize on deposit, minimize on withdraw to ensure
646 // AMM invariant sqrt(poolAsset1 * poolAsset2) >= LPTokensBalance
647 return isDeposit == IsDeposit::Yes ? Number::upward : Number::downward;
648}
649
650} // namespace detail
651
657template <typename A>
658STAmount
660 Rules const& rules,
661 STAmount const& balance,
662 A const& frac,
663 IsDeposit isDeposit)
664{
665 if (!rules.enabled(fixAMMv1_3))
666 {
667 if constexpr (std::is_same_v<A, STAmount>)
668 return multiply(balance, frac, balance.issue());
669 else
670 return toSTAmount(balance.issue(), balance * frac);
671 }
672 auto const rm = detail::getAssetRounding(isDeposit);
673 return multiply(balance, frac, rm);
674}
675
685STAmount
687 Rules const& rules,
688 std::function<Number()>&& noRoundCb,
689 STAmount const& balance,
690 std::function<Number()>&& productCb,
691 IsDeposit isDeposit);
692
700STAmount
702 Rules const& rules,
703 STAmount const& balance,
704 Number const& frac,
705 IsDeposit isDeposit);
706
718STAmount
720 Rules const& rules,
721 std::function<Number()>&& noRoundCb,
722 STAmount const& lptAMMBalance,
723 std::function<Number()>&& productCb,
724 IsDeposit isDeposit);
725
726/* Next two functions adjust asset in/out amount to factor in the adjusted
727 * lptokens. The lptokens are calculated from the asset in/out. The lptokens are
728 * then adjusted to factor in the loss in precision. The adjusted lptokens might
729 * be less than the initially calculated tokens. Therefore, the asset in/out
730 * must be adjusted. The rounding might result in the adjusted amount being
731 * greater than the original asset in/out amount. If this happens,
732 * then the original amount is reduced by the difference in the adjusted amount
733 * and the original amount. The actual tokens and the actual adjusted amount
734 * are then recalculated. The minimum of the original and the actual
735 * adjusted amount is returned.
736 */
739 Rules const& rules,
740 STAmount const& balance,
741 STAmount const& amount,
742 STAmount const& lptAMMBalance,
743 STAmount const& tokens,
744 std::uint16_t tfee);
747 Rules const& rules,
748 STAmount const& balance,
749 STAmount const& amount,
750 STAmount const& lptAMMBalance,
751 STAmount const& tokens,
752 std::uint16_t tfee);
753
757Number
759 Rules const& rules,
760 STAmount const& lptAMMBalance,
761 STAmount const& tokens,
762 Number const& frac);
763
764} // namespace ripple
765
766#endif // XRPL_APP_MISC_AMMHELPERS_H_INCLUDED
A generic endpoint for log messages.
Definition Journal.h:41
Stream error() const
Definition Journal.h:327
Stream trace() const
Severity stream access functions.
Definition Journal.h:303
static rounding_mode getround()
Definition Number.cpp:28
static rounding_mode setround(rounding_mode mode)
Definition Number.cpp:34
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
Issue const & issue() const
Definition STAmount.h:477
T endl(T... args)
T is_same_v
T minmax(T... args)
Number::rounding_mode getAssetRounding(IsDeposit isDeposit)
Definition AMMHelpers.h:643
Number::rounding_mode getLPTokenRounding(IsDeposit isDeposit)
Definition AMMHelpers.h:635
Number reduceOffer(auto const &amount)
Definition AMMHelpers.h:21
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
std::pair< STAmount, STAmount > adjustAssetInByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
bool isXRP(AccountID const &c)
Definition AccountID.h:71
std::optional< Number > solveQuadraticEqSmallest(Number const &a, Number const &b, Number const &c)
Solve quadratic equation to find takerGets or takerPays.
Issue getIssue(T const &amt)
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)
std::pair< STAmount, STAmount > adjustAssetOutByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
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
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
STAmount ammAssetIn(STAmount const &asset1Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset deposit given LP Tokens.
Number square(Number const &n)
Return square of n.
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:34
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:177
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
Number feeMult(std::uint16_t tfee)
Get fee multiplier (1 - tfee) @tfee trading fee in basis points.
Definition AMMCore.h:91
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...
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
STAmount ammAssetOut(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
Definition AMMCore.h:82
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition AMMHelpers.cpp:6
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
std::optional< Rules > const & getCurrentTransactionRules()
Definition Rules.cpp:28
STAmount lpTokensIn(STAmount const &asset1Balance, STAmount const &asset1Withdraw, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's withdraw amount.
STAmount lpTokensOut(STAmount const &asset1Balance, STAmount const &asset1Deposit, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's deposit amount.
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition AMMHelpers.h:659
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:248
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
Number root2(Number f)
Definition Number.cpp:682
Number adjustFracByTokens(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &tokens, Number const &frac)
Find a fraction of tokens after the tokens are adjusted.
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