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
64 STAmount const& asset1Balance,
65 STAmount const& lptAMMBalance,
66 STAmount const& lpTokens,
67 std::uint16_t tfee);
68
77STAmount
79 STAmount const& asset1Balance,
80 STAmount const& asset1Withdraw,
81 STAmount const& lptAMMBalance,
82 std::uint16_t tfee);
83
91STAmount
93 STAmount const& assetBalance,
94 STAmount const& lptAMMBalance,
95 STAmount const& lpTokens,
96 std::uint16_t tfee);
97
105inline bool
106withinRelativeDistance(Quality const& calcQuality, Quality const& reqQuality, Number const& dist)
107{
108 if (calcQuality == reqQuality)
109 return true;
110 auto const [min, max] = std::minmax(calcQuality, reqQuality);
111 // Relative distance is (max - min)/max. Can't use basic operations
112 // on Quality. Have to use Quality::rate() instead, which
113 // is inverse of quality: (1/max.rate - 1/min.rate)/(1/max.rate)
114 return ((min.rate() - max.rate()) / min.rate()) < dist;
115}
116
124template <typename Amt>
125 requires(
128bool
129withinRelativeDistance(Amt const& calc, Amt const& req, Number const& dist)
130{
131 if (calc == req)
132 return true;
133 auto const [min, max] = std::minmax(calc, req);
134 return ((max - min) / max) < dist;
135}
136
141solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c);
142
166template <typename TIn, typename TOut>
169 TAmounts<TIn, TOut> const& pool,
170 Quality const& targetQuality,
171 std::uint16_t const& tfee)
172{
173 if (targetQuality.rate() == beast::zero)
174 return std::nullopt;
175
177 auto const f = feeMult(tfee);
178 auto const a = 1;
179 auto const b = pool.in * (1 - 1 / f) / targetQuality.rate() - 2 * pool.out;
180 auto const c = pool.out * pool.out - (pool.in * pool.out) / targetQuality.rate();
181
182 auto nTakerGets = solveQuadraticEqSmallest(a, b, c);
183 if (!nTakerGets || *nTakerGets <= 0)
184 return std::nullopt; // LCOV_EXCL_LINE
185
186 auto const nTakerGetsConstraint = pool.out - pool.in / (targetQuality.rate() * f);
187 if (nTakerGetsConstraint <= 0)
188 return std::nullopt;
189
190 // Select the smallest to maximize the quality
191 if (nTakerGetsConstraint < *nTakerGets)
192 nTakerGets = nTakerGetsConstraint;
193
194 auto getAmounts = [&pool, &tfee](Number const& nTakerGetsProposed) {
195 // Round downward to minimize the offer and to maximize the quality.
196 // This has the most impact when takerGets is XRP.
197 auto const takerGets =
198 toAmount<TOut>(getIssue(pool.out), nTakerGetsProposed, Number::downward);
199 return TAmounts<TIn, TOut>{swapAssetOut(pool, takerGets, tfee), takerGets};
200 };
201
202 // Try to reduce the offer size to improve the quality.
203 // The quality might still not match the targetQuality for a tiny offer.
204 if (auto amounts = getAmounts(*nTakerGets); Quality{amounts} < targetQuality)
205 return getAmounts(detail::reduceOffer(amounts.out));
206 else
207 return amounts;
208}
209
233template <typename TIn, typename TOut>
236 TAmounts<TIn, TOut> const& pool,
237 Quality const& targetQuality,
238 std::uint16_t tfee)
239{
240 if (targetQuality.rate() == beast::zero)
241 return std::nullopt;
242
244 auto const f = feeMult(tfee);
245 auto const& a = f;
246 auto const b = pool.in * (1 + f);
247 auto const c = pool.in * pool.in - pool.in * pool.out * targetQuality.rate();
248
249 auto nTakerPays = solveQuadraticEqSmallest(a, b, c);
250 if (!nTakerPays || nTakerPays <= 0)
251 return std::nullopt; // LCOV_EXCL_LINE
252
253 auto const nTakerPaysConstraint = pool.out * targetQuality.rate() - pool.in / f;
254 if (nTakerPaysConstraint <= 0)
255 return std::nullopt;
256
257 // Select the smallest to maximize the quality
258 if (nTakerPaysConstraint < *nTakerPays)
259 nTakerPays = nTakerPaysConstraint;
260
261 auto getAmounts = [&pool, &tfee](Number const& nTakerPaysProposed) {
262 // Round downward to minimize the offer and to maximize the quality.
263 // This has the most impact when takerPays is XRP.
264 auto const takerPays =
265 toAmount<TIn>(getIssue(pool.in), nTakerPaysProposed, Number::downward);
266 return TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
267 };
268
269 // Try to reduce the offer size to improve the quality.
270 // The quality might still not match the targetQuality for a tiny offer.
271 if (auto amounts = getAmounts(*nTakerPays); Quality{amounts} < targetQuality)
272 return getAmounts(detail::reduceOffer(amounts.in));
273 else
274 return amounts;
275}
276
293template <typename TIn, typename TOut>
296 TAmounts<TIn, TOut> const& pool,
297 Quality const& quality,
298 std::uint16_t tfee,
299 Rules const& rules,
301{
302 if (!rules.enabled(fixAMMv1_1))
303 {
304 // Finds takerPays (i) and takerGets (o) such that given pool
305 // composition poolGets(I) and poolPays(O): (O - o) / (I + i) = quality.
306 // Where takerGets is calculated as the swapAssetIn (see below).
307 // The above equation produces the quadratic equation:
308 // i^2*(1-fee) + i*I*(2-fee) + I^2 - I*O/quality,
309 // which is solved for i, and o is found with swapAssetIn().
310 auto const f = feeMult(tfee); // 1 - fee
311 auto const& a = f;
312 auto const b = pool.in * (1 + f);
313 Number const c = pool.in * pool.in - pool.in * pool.out * quality.rate();
314 if (auto const res = b * b - 4 * a * c; res < 0)
315 return std::nullopt; // LCOV_EXCL_LINE
316 else if (auto const nTakerPaysPropose = (-b + root2(res)) / (2 * a); nTakerPaysPropose > 0)
317 {
318 auto const nTakerPays = [&]() {
319 // The fee might make the AMM offer quality less than CLOB
320 // quality. Therefore, AMM offer has to satisfy this constraint:
321 // o / i >= q. Substituting o with swapAssetIn() gives: i <= O /
322 // q - I / (1 - fee).
323 auto const nTakerPaysConstraint = pool.out * quality.rate() - pool.in / f;
324 if (nTakerPaysPropose > nTakerPaysConstraint)
325 return nTakerPaysConstraint;
326 return nTakerPaysPropose;
327 }();
328 if (nTakerPays <= 0)
329 {
330 JLOG(j.trace()) << "changeSpotPriceQuality calc failed: " << to_string(pool.in)
331 << " " << to_string(pool.out) << " " << quality << " " << tfee;
332 return std::nullopt;
333 }
334 auto const takerPays = toAmount<TIn>(getIssue(pool.in), nTakerPays, Number::upward);
335 // should not fail
336 if (auto amounts = TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
337 Quality{amounts} < quality &&
338 !withinRelativeDistance(Quality{amounts}, quality, Number(1, -7)))
339 {
340 JLOG(j.error()) << "changeSpotPriceQuality failed: " << to_string(pool.in) << " "
341 << to_string(pool.out) << " "
342 << " " << quality << " " << tfee << " " << to_string(amounts.in)
343 << " " << to_string(amounts.out);
344 Throw<std::runtime_error>("changeSpotPriceQuality failed");
345 }
346 else
347 {
348 JLOG(j.trace()) << "changeSpotPriceQuality succeeded: " << to_string(pool.in) << " "
349 << to_string(pool.out) << " "
350 << " " << quality << " " << tfee << " " << to_string(amounts.in)
351 << " " << to_string(amounts.out);
352 return amounts;
353 }
354 }
355 JLOG(j.trace()) << "changeSpotPriceQuality calc failed: " << to_string(pool.in) << " "
356 << to_string(pool.out) << " " << quality << " " << tfee;
357 return std::nullopt;
358 }
359
360 // Generate the offer starting with XRP side. Return seated offer amounts
361 // if the offer can be generated, otherwise nullopt.
362 auto amounts = [&]() {
363 if (isXRP(getIssue(pool.out)))
364 return getAMMOfferStartWithTakerGets(pool, quality, tfee);
365 return getAMMOfferStartWithTakerPays(pool, quality, tfee);
366 }();
367 if (!amounts)
368 {
369 JLOG(j.trace()) << "changeSpotPrice calc failed: " << to_string(pool.in) << " "
370 << to_string(pool.out) << " " << quality << " " << tfee;
371 return std::nullopt;
372 }
373
374 if (Quality{*amounts} < quality)
375 {
376 JLOG(j.error()) << "changeSpotPriceQuality failed: " << to_string(pool.in) << " "
377 << to_string(pool.out) << " " << quality << " " << tfee << " "
378 << to_string(amounts->in) << " " << to_string(amounts->out);
379 return std::nullopt;
380 }
381
382 JLOG(j.trace()) << "changeSpotPriceQuality succeeded: " << to_string(pool.in) << " "
383 << to_string(pool.out) << " "
384 << " " << quality << " " << tfee << " " << to_string(amounts->in) << " "
385 << to_string(amounts->out);
386
387 return amounts;
388}
389
411template <typename TIn, typename TOut>
412TOut
413swapAssetIn(TAmounts<TIn, TOut> const& pool, TIn const& assetIn, std::uint16_t tfee)
414{
415 if (auto const& rules = getCurrentTransactionRules(); rules && rules->enabled(fixAMMv1_1))
416 {
417 // set rounding to always favor the amm. Clip to zero.
418 // calculate:
419 // pool.out -
420 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
421 // and explicitly set the rounding modes
422 // Favoring the amm means we should:
423 // minimize:
424 // pool.out -
425 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
426 // maximize:
427 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
428 // (pool.in * pool.out)
429 // minimize:
430 // (pool.in + assetIn * feeMult(tfee)),
431 // minimize:
432 // assetIn * feeMult(tfee)
433 // feeMult is: (1-fee), fee is tfee/100000
434 // minimize:
435 // 1-fee
436 // maximize:
437 // fee
439
441 auto const numerator = pool.in * pool.out;
442 auto const fee = getFee(tfee);
443
445 auto const denom = pool.in + assetIn * (1 - fee);
446
447 if (denom.signum() <= 0)
448 return toAmount<TOut>(getIssue(pool.out), 0);
449
451 auto const ratio = numerator / denom;
452
454 auto const swapOut = pool.out - ratio;
455
456 if (swapOut.signum() < 0)
457 return toAmount<TOut>(getIssue(pool.out), 0);
458
459 return toAmount<TOut>(getIssue(pool.out), swapOut, Number::downward);
460 }
461 else
462 {
463 return toAmount<TOut>(
464 getIssue(pool.out),
465 pool.out - (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
467 }
468}
469
479template <typename TIn, typename TOut>
480TIn
481swapAssetOut(TAmounts<TIn, TOut> const& pool, TOut const& assetOut, std::uint16_t tfee)
482{
483 if (auto const& rules = getCurrentTransactionRules(); rules && rules->enabled(fixAMMv1_1))
484 {
485 // set rounding to always favor the amm. Clip to zero.
486 // calculate:
487 // ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
488 // (1-tfee/100000)
489 // maximize:
490 // ((pool.in * pool.out) / (pool.out - assetOut) - pool.in)
491 // maximize:
492 // (pool.in * pool.out) / (pool.out - assetOut)
493 // maximize:
494 // (pool.in * pool.out)
495 // minimize
496 // (pool.out - assetOut)
497 // minimize:
498 // (1-tfee/100000)
499 // maximize:
500 // tfee/100000
501
503
505 auto const numerator = pool.in * pool.out;
506
508 auto const denom = pool.out - assetOut;
509 if (denom.signum() <= 0)
510 {
511 return toMaxAmount<TIn>(getIssue(pool.in));
512 }
513
515 auto const ratio = numerator / denom;
516 auto const numerator2 = ratio - pool.in;
517 auto const fee = getFee(tfee);
518
520 auto const feeMult = 1 - fee;
521
523 auto const swapIn = numerator2 / feeMult;
524 if (swapIn.signum() < 0)
525 return toAmount<TIn>(getIssue(pool.in), 0);
526
527 return toAmount<TIn>(getIssue(pool.in), swapIn, Number::upward);
528 }
529 else
530 {
531 return toAmount<TIn>(
532 getIssue(pool.in),
533 ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) / feeMult(tfee),
535 }
536}
537
540Number
541square(Number const& n);
542
554STAmount
555adjustLPTokens(STAmount const& lptAMMBalance, STAmount const& lpTokens, IsDeposit isDeposit);
556
570 STAmount const& amountBalance,
571 STAmount const& amount,
572 std::optional<STAmount> const& amount2,
573 STAmount const& lptAMMBalance,
574 STAmount const& lpTokens,
575 std::uint16_t tfee,
576 IsDeposit isDeposit);
577
581Number
582solveQuadraticEq(Number const& a, Number const& b, Number const& c);
583
584STAmount
585multiply(STAmount const& amount, Number const& frac, Number::rounding_mode rm);
586
587namespace detail {
588
591{
592 // Minimize on deposit, maximize on withdraw to ensure
593 // AMM invariant sqrt(poolAsset1 * poolAsset2) >= LPTokensBalance
594 return isDeposit == IsDeposit::Yes ? Number::downward : Number::upward;
595}
596
599{
600 // Maximize on deposit, minimize on withdraw to ensure
601 // AMM invariant sqrt(poolAsset1 * poolAsset2) >= LPTokensBalance
602 return isDeposit == IsDeposit::Yes ? Number::upward : Number::downward;
603}
604
605} // namespace detail
606
612template <typename A>
613STAmount
614getRoundedAsset(Rules const& rules, STAmount const& balance, A const& frac, IsDeposit isDeposit)
615{
616 if (!rules.enabled(fixAMMv1_3))
617 {
618 if constexpr (std::is_same_v<A, STAmount>)
619 return multiply(balance, frac, balance.issue());
620 else
621 return toSTAmount(balance.issue(), balance * frac);
622 }
623 auto const rm = detail::getAssetRounding(isDeposit);
624 return multiply(balance, frac, rm);
625}
626
636STAmount
638 Rules const& rules,
639 std::function<Number()> const& noRoundCb,
640 STAmount const& balance,
641 std::function<Number()> const& productCb,
642 IsDeposit isDeposit);
643
651STAmount
653 Rules const& rules,
654 STAmount const& balance,
655 Number const& frac,
656 IsDeposit isDeposit);
657
669STAmount
671 Rules const& rules,
672 std::function<Number()> const& noRoundCb,
673 STAmount const& lptAMMBalance,
674 std::function<Number()> const& productCb,
675 IsDeposit isDeposit);
676
677/* Next two functions adjust asset in/out amount to factor in the adjusted
678 * lptokens. The lptokens are calculated from the asset in/out. The lptokens are
679 * then adjusted to factor in the loss in precision. The adjusted lptokens might
680 * be less than the initially calculated tokens. Therefore, the asset in/out
681 * must be adjusted. The rounding might result in the adjusted amount being
682 * greater than the original asset in/out amount. If this happens,
683 * then the original amount is reduced by the difference in the adjusted amount
684 * and the original amount. The actual tokens and the actual adjusted amount
685 * are then recalculated. The minimum of the original and the actual
686 * adjusted amount is returned.
687 */
690 Rules const& rules,
691 STAmount const& balance,
692 STAmount const& amount,
693 STAmount const& lptAMMBalance,
694 STAmount const& tokens,
695 std::uint16_t tfee);
698 Rules const& rules,
699 STAmount const& balance,
700 STAmount const& amount,
701 STAmount const& lptAMMBalance,
702 STAmount const& tokens,
703 std::uint16_t tfee);
704
708Number
710 Rules const& rules,
711 STAmount const& lptAMMBalance,
712 STAmount const& tokens,
713 Number const& frac);
714
715} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:40
Stream error() const
Definition Journal.h:319
Stream trace() const
Severity stream access functions.
Definition Journal.h:295
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:120
Issue const & issue() const
Definition STAmount.h:470
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:590
Number::rounding_mode getAssetRounding(IsDeposit isDeposit)
Definition AMMHelpers.h:598
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:602
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:413
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:168
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:235
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition AMMHelpers.h:614
Number feeMult(std::uint16_t tfee)
Get fee multiplier (1 - tfee) @tfee trading fee in basis points.
Definition AMMCore.h:87
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:1030
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:295
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:78
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:481
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:106
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)