rippled
Loading...
Searching...
No Matches
AMMHelpers.h
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2023 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#ifndef RIPPLE_APP_MISC_AMMHELPERS_H_INCLUDED
21#define RIPPLE_APP_MISC_AMMHELPERS_H_INCLUDED
22
23#include <xrpl/basics/Log.h>
24#include <xrpl/basics/Number.h>
25#include <xrpl/beast/utility/Journal.h>
26#include <xrpl/protocol/AMMCore.h>
27#include <xrpl/protocol/AmountConversions.h>
28#include <xrpl/protocol/Feature.h>
29#include <xrpl/protocol/IOUAmount.h>
30#include <xrpl/protocol/Issue.h>
31#include <xrpl/protocol/Quality.h>
32#include <xrpl/protocol/Rules.h>
33#include <xrpl/protocol/STAccount.h>
34#include <xrpl/protocol/STAmount.h>
35
36#include <type_traits>
37
38namespace ripple {
39
40namespace detail {
41
42Number
43reduceOffer(auto const& amount)
44{
45 static Number const reducedOfferPct(9999, -4);
46
47 // Make sure the result is always less than amount or zero.
49 return amount * reducedOfferPct;
50}
51
52} // namespace detail
53
59STAmount
61 STAmount const& asset1,
62 STAmount const& asset2,
63 Issue const& lptIssue);
64
72STAmount
74 STAmount const& asset1Balance,
75 STAmount const& asset1Deposit,
76 STAmount const& lptAMMBalance,
77 std::uint16_t tfee);
78
86STAmount
88 STAmount const& asset1Balance,
89 STAmount const& lptAMMBalance,
90 STAmount const& lpTokens,
91 std::uint16_t tfee);
92
101STAmount
103 STAmount const& asset1Balance,
104 STAmount const& asset1Withdraw,
105 STAmount const& lptAMMBalance,
106 std::uint16_t tfee);
107
115STAmount
117 STAmount const& assetBalance,
118 STAmount const& lptAMMBalance,
119 STAmount const& lpTokens,
120 std::uint16_t tfee);
121
129inline bool
131 Quality const& calcQuality,
132 Quality const& reqQuality,
133 Number const& dist)
134{
135 if (calcQuality == reqQuality)
136 return true;
137 auto const [min, max] = std::minmax(calcQuality, reqQuality);
138 // Relative distance is (max - min)/max. Can't use basic operations
139 // on Quality. Have to use Quality::rate() instead, which
140 // is inverse of quality: (1/max.rate - 1/min.rate)/(1/max.rate)
141 return ((min.rate() - max.rate()) / min.rate()) < dist;
142}
143
151// clang-format off
152template <typename Amt>
153 requires(
154 std::is_same_v<Amt, STAmount> || std::is_same_v<Amt, IOUAmount> ||
155 std::is_same_v<Amt, XRPAmount> || std::is_same_v<Amt, Number>)
156bool
157withinRelativeDistance(Amt const& calc, Amt const& req, Number const& dist)
158{
159 if (calc == req)
160 return true;
161 auto const [min, max] = std::minmax(calc, req);
162 return ((max - min) / max) < dist;
163}
164// clang-format on
165
170solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c);
171
195template <typename TIn, typename TOut>
198 TAmounts<TIn, TOut> const& pool,
199 Quality const& targetQuality,
200 std::uint16_t const& tfee)
201{
202 if (targetQuality.rate() == beast::zero)
203 return std::nullopt;
204
206 auto const f = feeMult(tfee);
207 auto const a = 1;
208 auto const b = pool.in * (1 - 1 / f) / targetQuality.rate() - 2 * pool.out;
209 auto const c =
210 pool.out * pool.out - (pool.in * pool.out) / targetQuality.rate();
211
212 auto nTakerGets = solveQuadraticEqSmallest(a, b, c);
213 if (!nTakerGets || *nTakerGets <= 0)
214 return std::nullopt; // LCOV_EXCL_LINE
215
216 auto const nTakerGetsConstraint =
217 pool.out - pool.in / (targetQuality.rate() * f);
218 if (nTakerGetsConstraint <= 0)
219 return std::nullopt;
220
221 // Select the smallest to maximize the quality
222 if (nTakerGetsConstraint < *nTakerGets)
223 nTakerGets = nTakerGetsConstraint;
224
225 auto getAmounts = [&pool, &tfee](Number const& nTakerGetsProposed) {
226 // Round downward to minimize the offer and to maximize the quality.
227 // This has the most impact when takerGets is XRP.
228 auto const takerGets = toAmount<TOut>(
229 getIssue(pool.out), nTakerGetsProposed, Number::downward);
230 return TAmounts<TIn, TOut>{
231 swapAssetOut(pool, takerGets, tfee), takerGets};
232 };
233
234 // Try to reduce the offer size to improve the quality.
235 // The quality might still not match the targetQuality for a tiny offer.
236 if (auto const amounts = getAmounts(*nTakerGets);
237 Quality{amounts} < targetQuality)
238 return getAmounts(detail::reduceOffer(amounts.out));
239 else
240 return amounts;
241}
242
266template <typename TIn, typename TOut>
269 TAmounts<TIn, TOut> const& pool,
270 Quality const& targetQuality,
271 std::uint16_t tfee)
272{
273 if (targetQuality.rate() == beast::zero)
274 return std::nullopt;
275
277 auto const f = feeMult(tfee);
278 auto const& a = f;
279 auto const b = pool.in * (1 + f);
280 auto const c =
281 pool.in * pool.in - pool.in * pool.out * targetQuality.rate();
282
283 auto nTakerPays = solveQuadraticEqSmallest(a, b, c);
284 if (!nTakerPays || nTakerPays <= 0)
285 return std::nullopt; // LCOV_EXCL_LINE
286
287 auto const nTakerPaysConstraint =
288 pool.out * targetQuality.rate() - pool.in / f;
289 if (nTakerPaysConstraint <= 0)
290 return std::nullopt;
291
292 // Select the smallest to maximize the quality
293 if (nTakerPaysConstraint < *nTakerPays)
294 nTakerPays = nTakerPaysConstraint;
295
296 auto getAmounts = [&pool, &tfee](Number const& nTakerPaysProposed) {
297 // Round downward to minimize the offer and to maximize the quality.
298 // This has the most impact when takerPays is XRP.
299 auto const takerPays = toAmount<TIn>(
300 getIssue(pool.in), nTakerPaysProposed, Number::downward);
301 return TAmounts<TIn, TOut>{
302 takerPays, swapAssetIn(pool, takerPays, tfee)};
303 };
304
305 // Try to reduce the offer size to improve the quality.
306 // The quality might still not match the targetQuality for a tiny offer.
307 if (auto const amounts = getAmounts(*nTakerPays);
308 Quality{amounts} < targetQuality)
309 return getAmounts(detail::reduceOffer(amounts.in));
310 else
311 return amounts;
312}
313
330template <typename TIn, typename TOut>
333 TAmounts<TIn, TOut> const& pool,
334 Quality const& quality,
335 std::uint16_t tfee,
336 Rules const& rules,
338{
339 if (!rules.enabled(fixAMMv1_1))
340 {
341 // Finds takerPays (i) and takerGets (o) such that given pool
342 // composition poolGets(I) and poolPays(O): (O - o) / (I + i) = quality.
343 // Where takerGets is calculated as the swapAssetIn (see below).
344 // The above equation produces the quadratic equation:
345 // i^2*(1-fee) + i*I*(2-fee) + I^2 - I*O/quality,
346 // which is solved for i, and o is found with swapAssetIn().
347 auto const f = feeMult(tfee); // 1 - fee
348 auto const& a = f;
349 auto const b = pool.in * (1 + f);
350 Number const c =
351 pool.in * pool.in - pool.in * pool.out * quality.rate();
352 if (auto const res = b * b - 4 * a * c; res < 0)
353 return std::nullopt; // LCOV_EXCL_LINE
354 else if (auto const nTakerPaysPropose = (-b + root2(res)) / (2 * a);
355 nTakerPaysPropose > 0)
356 {
357 auto const nTakerPays = [&]() {
358 // The fee might make the AMM offer quality less than CLOB
359 // quality. Therefore, AMM offer has to satisfy this constraint:
360 // o / i >= q. Substituting o with swapAssetIn() gives: i <= O /
361 // q - I / (1 - fee).
362 auto const nTakerPaysConstraint =
363 pool.out * quality.rate() - pool.in / f;
364 if (nTakerPaysPropose > nTakerPaysConstraint)
365 return nTakerPaysConstraint;
366 return nTakerPaysPropose;
367 }();
368 if (nTakerPays <= 0)
369 {
370 JLOG(j.trace())
371 << "changeSpotPriceQuality calc failed: "
372 << to_string(pool.in) << " " << to_string(pool.out) << " "
373 << quality << " " << tfee;
374 return std::nullopt;
375 }
376 auto const takerPays =
377 toAmount<TIn>(getIssue(pool.in), nTakerPays, Number::upward);
378 // should not fail
379 if (auto const amounts =
380 TAmounts<TIn, TOut>{
381 takerPays, swapAssetIn(pool, takerPays, tfee)};
382 Quality{amounts} < quality &&
384 Quality{amounts}, quality, Number(1, -7)))
385 {
386 JLOG(j.error())
387 << "changeSpotPriceQuality failed: " << to_string(pool.in)
388 << " " << to_string(pool.out) << " " << " " << quality
389 << " " << tfee << " " << to_string(amounts.in) << " "
390 << to_string(amounts.out);
391 Throw<std::runtime_error>("changeSpotPriceQuality failed");
392 }
393 else
394 {
395 JLOG(j.trace())
396 << "changeSpotPriceQuality succeeded: "
397 << to_string(pool.in) << " " << to_string(pool.out) << " "
398 << " " << quality << " " << tfee << " "
399 << to_string(amounts.in) << " " << to_string(amounts.out);
400 return amounts;
401 }
402 }
403 JLOG(j.trace()) << "changeSpotPriceQuality calc failed: "
404 << to_string(pool.in) << " " << to_string(pool.out)
405 << " " << quality << " " << tfee;
406 return std::nullopt;
407 }
408
409 // Generate the offer starting with XRP side. Return seated offer amounts
410 // if the offer can be generated, otherwise nullopt.
411 auto const amounts = [&]() {
412 if (isXRP(getIssue(pool.out)))
413 return getAMMOfferStartWithTakerGets(pool, quality, tfee);
414 return getAMMOfferStartWithTakerPays(pool, quality, tfee);
415 }();
416 if (!amounts)
417 {
418 JLOG(j.trace()) << "changeSpotPrice calc failed: " << to_string(pool.in)
419 << " " << to_string(pool.out) << " " << quality << " "
420 << tfee << std::endl;
421 return std::nullopt;
422 }
423
424 if (Quality{*amounts} < quality)
425 {
426 JLOG(j.error()) << "changeSpotPriceQuality failed: "
427 << to_string(pool.in) << " " << to_string(pool.out)
428 << " " << quality << " " << tfee << " "
429 << to_string(amounts->in) << " "
430 << to_string(amounts->out);
431 return std::nullopt;
432 }
433
434 JLOG(j.trace()) << "changeSpotPriceQuality succeeded: "
435 << to_string(pool.in) << " " << to_string(pool.out) << " "
436 << " " << quality << " " << tfee << " "
437 << to_string(amounts->in) << " " << to_string(amounts->out);
438
439 return amounts;
440}
441
463template <typename TIn, typename TOut>
464TOut
466 TAmounts<TIn, TOut> const& pool,
467 TIn const& assetIn,
468 std::uint16_t tfee)
469{
470 if (auto const& rules = getCurrentTransactionRules();
471 rules && rules->enabled(fixAMMv1_1))
472 {
473 // set rounding to always favor the amm. Clip to zero.
474 // calculate:
475 // pool.out -
476 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
477 // and explicitly set the rounding modes
478 // Favoring the amm means we should:
479 // minimize:
480 // pool.out -
481 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
482 // maximize:
483 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
484 // (pool.in * pool.out)
485 // minimize:
486 // (pool.in + assetIn * feeMult(tfee)),
487 // minimize:
488 // assetIn * feeMult(tfee)
489 // feeMult is: (1-fee), fee is tfee/100000
490 // minimize:
491 // 1-fee
492 // maximize:
493 // fee
495
497 auto const numerator = pool.in * pool.out;
498 auto const fee = getFee(tfee);
499
501 auto const denom = pool.in + assetIn * (1 - fee);
502
503 if (denom.signum() <= 0)
504 return toAmount<TOut>(getIssue(pool.out), 0);
505
507 auto const ratio = numerator / denom;
508
510 auto const swapOut = pool.out - ratio;
511
512 if (swapOut.signum() < 0)
513 return toAmount<TOut>(getIssue(pool.out), 0);
514
515 return toAmount<TOut>(getIssue(pool.out), swapOut, Number::downward);
516 }
517 else
518 {
519 return toAmount<TOut>(
520 getIssue(pool.out),
521 pool.out -
522 (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
524 }
525}
526
536template <typename TIn, typename TOut>
537TIn
539 TAmounts<TIn, TOut> const& pool,
540 TOut const& assetOut,
541 std::uint16_t tfee)
542{
543 if (auto const& rules = getCurrentTransactionRules();
544 rules && rules->enabled(fixAMMv1_1))
545 {
546 // set rounding to always favor the amm. Clip to zero.
547 // calculate:
548 // ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
549 // (1-tfee/100000)
550 // maximize:
551 // ((pool.in * pool.out) / (pool.out - assetOut) - pool.in)
552 // maximize:
553 // (pool.in * pool.out) / (pool.out - assetOut)
554 // maximize:
555 // (pool.in * pool.out)
556 // minimize
557 // (pool.out - assetOut)
558 // minimize:
559 // (1-tfee/100000)
560 // maximize:
561 // tfee/100000
562
564
566 auto const numerator = pool.in * pool.out;
567
569 auto const denom = pool.out - assetOut;
570 if (denom.signum() <= 0)
571 {
572 return toMaxAmount<TIn>(getIssue(pool.in));
573 }
574
576 auto const ratio = numerator / denom;
577 auto const numerator2 = ratio - pool.in;
578 auto const fee = getFee(tfee);
579
581 auto const feeMult = 1 - fee;
582
584 auto const swapIn = numerator2 / feeMult;
585 if (swapIn.signum() < 0)
586 return toAmount<TIn>(getIssue(pool.in), 0);
587
588 return toAmount<TIn>(getIssue(pool.in), swapIn, Number::upward);
589 }
590 else
591 {
592 return toAmount<TIn>(
593 getIssue(pool.in),
594 ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
595 feeMult(tfee),
597 }
598}
599
602Number
603square(Number const& n);
604
616STAmount
618 STAmount const& lptAMMBalance,
619 STAmount const& lpTokens,
620 bool isDeposit);
621
635 STAmount const& amountBalance,
636 STAmount const& amount,
637 std::optional<STAmount> const& amount2,
638 STAmount const& lptAMMBalance,
639 STAmount const& lpTokens,
640 std::uint16_t tfee,
641 bool isDeposit);
642
646Number
647solveQuadraticEq(Number const& a, Number const& b, Number const& c);
648
649} // namespace ripple
650
651#endif // RIPPLE_APP_MISC_AMMHELPERS_H_INCLUDED
A generic endpoint for log messages.
Definition: Journal.h:59
Stream error() const
Definition: Journal.h:335
Stream trace() const
Severity stream access functions.
Definition: Journal.h:311
static rounding_mode getround()
Definition: Number.cpp:41
static rounding_mode setround(rounding_mode mode)
Definition: Number.cpp:47
Rules controlling protocol behavior.
Definition: Rules.h:35
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:122
T endl(T... args)
T minmax(T... args)
Number reduceOffer(auto const &amount)
Definition: AMMHelpers.h:43
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
STAmount lpTokensIn(STAmount const &asset1Balance, STAmount const &asset1Deposit, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's deposit amount.
Definition: AMMHelpers.cpp:41
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, bool isDeposit)
Adjust LP tokens to deposit/withdraw.
Definition: AMMHelpers.cpp:133
bool isXRP(AccountID const &c)
Definition: AccountID.h:91
std::optional< Number > solveQuadraticEqSmallest(Number const &a, Number const &b, Number const &c)
Solve quadratic equation to find takerGets or takerPays.
Definition: AMMHelpers.cpp:227
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)
Definition: AMMHelpers.cpp:220
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:465
STAmount lpTokensOut(STAmount const &asset1Balance, STAmount const &asset1Withdraw, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's withdraw amount.
Definition: AMMHelpers.cpp:90
STAmount ammAssetIn(STAmount const &asset1Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset deposit given LP Tokens.
Definition: AMMHelpers.cpp:67
Number square(Number const &n)
Return square of n.
Definition: AMMHelpers.cpp:127
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, bool isDeposit)
Calls adjustLPTokens() and adjusts deposit or withdraw amounts if the adjusted LP tokens are less tha...
Definition: AMMHelpers.cpp:147
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:197
Number feeMult(std::uint16_t tfee)
Get fee multiplier (1 - tfee) @tfee trading fee in basis points.
Definition: AMMCore.h:118
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:332
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
Definition: AMMCore.h:109
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
Definition: AMMHelpers.cpp:25
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:629
std::optional< Rules > const & getCurrentTransactionRules()
Definition: Rules.cpp:39
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:268
STAmount withdrawByTokens(STAmount const &assetBalance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset withdrawal by tokens.
Definition: AMMHelpers.cpp:114
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:130
Number root2(Number f)
Definition: Number.cpp:695
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:538