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/STAmount.h>
34
35namespace ripple {
36
37namespace detail {
38
39Number
40reduceOffer(auto const& amount)
41{
42 static Number const reducedOfferPct(9999, -4);
43
44 // Make sure the result is always less than amount or zero.
46 return amount * reducedOfferPct;
47}
48
49} // namespace detail
50
56STAmount
58 STAmount const& asset1,
59 STAmount const& asset2,
60 Issue const& lptIssue);
61
69STAmount
71 STAmount const& asset1Balance,
72 STAmount const& asset1Deposit,
73 STAmount const& lptAMMBalance,
74 std::uint16_t tfee);
75
83STAmount
85 STAmount const& asset1Balance,
86 STAmount const& lptAMMBalance,
87 STAmount const& lpTokens,
88 std::uint16_t tfee);
89
98STAmount
100 STAmount const& asset1Balance,
101 STAmount const& asset1Withdraw,
102 STAmount const& lptAMMBalance,
103 std::uint16_t tfee);
104
112STAmount
114 STAmount const& assetBalance,
115 STAmount const& lptAMMBalance,
116 STAmount const& lpTokens,
117 std::uint16_t tfee);
118
126inline bool
128 Quality const& calcQuality,
129 Quality const& reqQuality,
130 Number const& dist)
131{
132 if (calcQuality == reqQuality)
133 return true;
134 auto const [min, max] = std::minmax(calcQuality, reqQuality);
135 // Relative distance is (max - min)/max. Can't use basic operations
136 // on Quality. Have to use Quality::rate() instead, which
137 // is inverse of quality: (1/max.rate - 1/min.rate)/(1/max.rate)
138 return ((min.rate() - max.rate()) / min.rate()) < dist;
139}
140
148// clang-format off
149template <typename Amt>
150 requires(
151 std::is_same_v<Amt, STAmount> || std::is_same_v<Amt, IOUAmount> ||
152 std::is_same_v<Amt, XRPAmount> || std::is_same_v<Amt, Number>)
153bool
154withinRelativeDistance(Amt const& calc, Amt const& req, Number const& dist)
155{
156 if (calc == req)
157 return true;
158 auto const [min, max] = std::minmax(calc, req);
159 return ((max - min) / max) < dist;
160}
161// clang-format on
162
167solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c);
168
192template <typename TIn, typename TOut>
195 TAmounts<TIn, TOut> const& pool,
196 Quality const& targetQuality,
197 std::uint16_t const& tfee)
198{
199 if (targetQuality.rate() == beast::zero)
200 return std::nullopt;
201
203 auto const f = feeMult(tfee);
204 auto const a = 1;
205 auto const b = pool.in * (1 - 1 / f) / targetQuality.rate() - 2 * pool.out;
206 auto const c =
207 pool.out * pool.out - (pool.in * pool.out) / targetQuality.rate();
208
209 auto nTakerGets = solveQuadraticEqSmallest(a, b, c);
210 if (!nTakerGets || *nTakerGets <= 0)
211 return std::nullopt; // LCOV_EXCL_LINE
212
213 auto const nTakerGetsConstraint =
214 pool.out - pool.in / (targetQuality.rate() * f);
215 if (nTakerGetsConstraint <= 0)
216 return std::nullopt;
217
218 // Select the smallest to maximize the quality
219 if (nTakerGetsConstraint < *nTakerGets)
220 nTakerGets = nTakerGetsConstraint;
221
222 auto getAmounts = [&pool, &tfee](Number const& nTakerGetsProposed) {
223 // Round downward to minimize the offer and to maximize the quality.
224 // This has the most impact when takerGets is XRP.
225 auto const takerGets = toAmount<TOut>(
226 getIssue(pool.out), nTakerGetsProposed, Number::downward);
227 return TAmounts<TIn, TOut>{
228 swapAssetOut(pool, takerGets, tfee), takerGets};
229 };
230
231 // Try to reduce the offer size to improve the quality.
232 // The quality might still not match the targetQuality for a tiny offer.
233 if (auto const amounts = getAmounts(*nTakerGets);
234 Quality{amounts} < targetQuality)
235 return getAmounts(detail::reduceOffer(amounts.out));
236 else
237 return amounts;
238}
239
263template <typename TIn, typename TOut>
266 TAmounts<TIn, TOut> const& pool,
267 Quality const& targetQuality,
268 std::uint16_t tfee)
269{
270 if (targetQuality.rate() == beast::zero)
271 return std::nullopt;
272
274 auto const f = feeMult(tfee);
275 auto const& a = f;
276 auto const b = pool.in * (1 + f);
277 auto const c =
278 pool.in * pool.in - pool.in * pool.out * targetQuality.rate();
279
280 auto nTakerPays = solveQuadraticEqSmallest(a, b, c);
281 if (!nTakerPays || nTakerPays <= 0)
282 return std::nullopt; // LCOV_EXCL_LINE
283
284 auto const nTakerPaysConstraint =
285 pool.out * targetQuality.rate() - pool.in / f;
286 if (nTakerPaysConstraint <= 0)
287 return std::nullopt;
288
289 // Select the smallest to maximize the quality
290 if (nTakerPaysConstraint < *nTakerPays)
291 nTakerPays = nTakerPaysConstraint;
292
293 auto getAmounts = [&pool, &tfee](Number const& nTakerPaysProposed) {
294 // Round downward to minimize the offer and to maximize the quality.
295 // This has the most impact when takerPays is XRP.
296 auto const takerPays = toAmount<TIn>(
297 getIssue(pool.in), nTakerPaysProposed, Number::downward);
298 return TAmounts<TIn, TOut>{
299 takerPays, swapAssetIn(pool, takerPays, tfee)};
300 };
301
302 // Try to reduce the offer size to improve the quality.
303 // The quality might still not match the targetQuality for a tiny offer.
304 if (auto const amounts = getAmounts(*nTakerPays);
305 Quality{amounts} < targetQuality)
306 return getAmounts(detail::reduceOffer(amounts.in));
307 else
308 return amounts;
309}
310
327template <typename TIn, typename TOut>
330 TAmounts<TIn, TOut> const& pool,
331 Quality const& quality,
332 std::uint16_t tfee,
333 Rules const& rules,
335{
336 if (!rules.enabled(fixAMMv1_1))
337 {
338 // Finds takerPays (i) and takerGets (o) such that given pool
339 // composition poolGets(I) and poolPays(O): (O - o) / (I + i) = quality.
340 // Where takerGets is calculated as the swapAssetIn (see below).
341 // The above equation produces the quadratic equation:
342 // i^2*(1-fee) + i*I*(2-fee) + I^2 - I*O/quality,
343 // which is solved for i, and o is found with swapAssetIn().
344 auto const f = feeMult(tfee); // 1 - fee
345 auto const& a = f;
346 auto const b = pool.in * (1 + f);
347 Number const c =
348 pool.in * pool.in - pool.in * pool.out * quality.rate();
349 if (auto const res = b * b - 4 * a * c; res < 0)
350 return std::nullopt; // LCOV_EXCL_LINE
351 else if (auto const nTakerPaysPropose = (-b + root2(res)) / (2 * a);
352 nTakerPaysPropose > 0)
353 {
354 auto const nTakerPays = [&]() {
355 // The fee might make the AMM offer quality less than CLOB
356 // quality. Therefore, AMM offer has to satisfy this constraint:
357 // o / i >= q. Substituting o with swapAssetIn() gives: i <= O /
358 // q - I / (1 - fee).
359 auto const nTakerPaysConstraint =
360 pool.out * quality.rate() - pool.in / f;
361 if (nTakerPaysPropose > nTakerPaysConstraint)
362 return nTakerPaysConstraint;
363 return nTakerPaysPropose;
364 }();
365 if (nTakerPays <= 0)
366 {
367 JLOG(j.trace())
368 << "changeSpotPriceQuality calc failed: "
369 << to_string(pool.in) << " " << to_string(pool.out) << " "
370 << quality << " " << tfee;
371 return std::nullopt;
372 }
373 auto const takerPays =
374 toAmount<TIn>(getIssue(pool.in), nTakerPays, Number::upward);
375 // should not fail
376 if (auto const amounts =
377 TAmounts<TIn, TOut>{
378 takerPays, swapAssetIn(pool, takerPays, tfee)};
379 Quality{amounts} < quality &&
381 Quality{amounts}, quality, Number(1, -7)))
382 {
383 JLOG(j.error())
384 << "changeSpotPriceQuality failed: " << to_string(pool.in)
385 << " " << to_string(pool.out) << " "
386 << " " << quality << " " << tfee << " "
387 << to_string(amounts.in) << " " << to_string(amounts.out);
388 Throw<std::runtime_error>("changeSpotPriceQuality failed");
389 }
390 else
391 {
392 JLOG(j.trace())
393 << "changeSpotPriceQuality succeeded: "
394 << to_string(pool.in) << " " << to_string(pool.out) << " "
395 << " " << quality << " " << tfee << " "
396 << to_string(amounts.in) << " " << to_string(amounts.out);
397 return amounts;
398 }
399 }
400 JLOG(j.trace()) << "changeSpotPriceQuality calc failed: "
401 << to_string(pool.in) << " " << to_string(pool.out)
402 << " " << quality << " " << tfee;
403 return std::nullopt;
404 }
405
406 // Generate the offer starting with XRP side. Return seated offer amounts
407 // if the offer can be generated, otherwise nullopt.
408 auto const amounts = [&]() {
409 if (isXRP(getIssue(pool.out)))
410 return getAMMOfferStartWithTakerGets(pool, quality, tfee);
411 return getAMMOfferStartWithTakerPays(pool, quality, tfee);
412 }();
413 if (!amounts)
414 {
415 JLOG(j.trace()) << "changeSpotPrice calc failed: " << to_string(pool.in)
416 << " " << to_string(pool.out) << " " << quality << " "
417 << tfee << std::endl;
418 return std::nullopt;
419 }
420
421 if (Quality{*amounts} < quality)
422 {
423 JLOG(j.error()) << "changeSpotPriceQuality failed: "
424 << to_string(pool.in) << " " << to_string(pool.out)
425 << " " << quality << " " << tfee << " "
426 << to_string(amounts->in) << " "
427 << to_string(amounts->out);
428 return std::nullopt;
429 }
430
431 JLOG(j.trace()) << "changeSpotPriceQuality succeeded: "
432 << to_string(pool.in) << " " << to_string(pool.out) << " "
433 << " " << quality << " " << tfee << " "
434 << to_string(amounts->in) << " " << to_string(amounts->out);
435
436 return amounts;
437}
438
460template <typename TIn, typename TOut>
461TOut
463 TAmounts<TIn, TOut> const& pool,
464 TIn const& assetIn,
465 std::uint16_t tfee)
466{
467 if (auto const& rules = getCurrentTransactionRules();
468 rules && rules->enabled(fixAMMv1_1))
469 {
470 // set rounding to always favor the amm. Clip to zero.
471 // calculate:
472 // pool.out -
473 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
474 // and explicitly set the rounding modes
475 // Favoring the amm means we should:
476 // minimize:
477 // pool.out -
478 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
479 // maximize:
480 // (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
481 // (pool.in * pool.out)
482 // minimize:
483 // (pool.in + assetIn * feeMult(tfee)),
484 // minimize:
485 // assetIn * feeMult(tfee)
486 // feeMult is: (1-fee), fee is tfee/100000
487 // minimize:
488 // 1-fee
489 // maximize:
490 // fee
492
494 auto const numerator = pool.in * pool.out;
495 auto const fee = getFee(tfee);
496
498 auto const denom = pool.in + assetIn * (1 - fee);
499
500 if (denom.signum() <= 0)
501 return toAmount<TOut>(getIssue(pool.out), 0);
502
504 auto const ratio = numerator / denom;
505
507 auto const swapOut = pool.out - ratio;
508
509 if (swapOut.signum() < 0)
510 return toAmount<TOut>(getIssue(pool.out), 0);
511
512 return toAmount<TOut>(getIssue(pool.out), swapOut, Number::downward);
513 }
514 else
515 {
516 return toAmount<TOut>(
517 getIssue(pool.out),
518 pool.out -
519 (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
521 }
522}
523
533template <typename TIn, typename TOut>
534TIn
536 TAmounts<TIn, TOut> const& pool,
537 TOut const& assetOut,
538 std::uint16_t tfee)
539{
540 if (auto const& rules = getCurrentTransactionRules();
541 rules && rules->enabled(fixAMMv1_1))
542 {
543 // set rounding to always favor the amm. Clip to zero.
544 // calculate:
545 // ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
546 // (1-tfee/100000)
547 // maximize:
548 // ((pool.in * pool.out) / (pool.out - assetOut) - pool.in)
549 // maximize:
550 // (pool.in * pool.out) / (pool.out - assetOut)
551 // maximize:
552 // (pool.in * pool.out)
553 // minimize
554 // (pool.out - assetOut)
555 // minimize:
556 // (1-tfee/100000)
557 // maximize:
558 // tfee/100000
559
561
563 auto const numerator = pool.in * pool.out;
564
566 auto const denom = pool.out - assetOut;
567 if (denom.signum() <= 0)
568 {
569 return toMaxAmount<TIn>(getIssue(pool.in));
570 }
571
573 auto const ratio = numerator / denom;
574 auto const numerator2 = ratio - pool.in;
575 auto const fee = getFee(tfee);
576
578 auto const feeMult = 1 - fee;
579
581 auto const swapIn = numerator2 / feeMult;
582 if (swapIn.signum() < 0)
583 return toAmount<TIn>(getIssue(pool.in), 0);
584
585 return toAmount<TIn>(getIssue(pool.in), swapIn, Number::upward);
586 }
587 else
588 {
589 return toAmount<TIn>(
590 getIssue(pool.in),
591 ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
592 feeMult(tfee),
594 }
595}
596
599Number
600square(Number const& n);
601
613STAmount
615 STAmount const& lptAMMBalance,
616 STAmount const& lpTokens,
617 bool isDeposit);
618
632 STAmount const& amountBalance,
633 STAmount const& amount,
634 std::optional<STAmount> const& amount2,
635 STAmount const& lptAMMBalance,
636 STAmount const& lpTokens,
637 std::uint16_t tfee,
638 bool isDeposit);
639
643Number
644solveQuadraticEq(Number const& a, Number const& b, Number const& c);
645
646} // namespace ripple
647
648#endif // RIPPLE_APP_MISC_AMMHELPERS_H_INCLUDED
A generic endpoint for log messages.
Definition: Journal.h:60
Stream error() const
Definition: Journal.h:346
Stream trace() const
Severity stream access functions.
Definition: Journal.h:322
static rounding_mode getround()
Definition: Number.cpp:47
static rounding_mode setround(rounding_mode mode)
Definition: Number.cpp:53
Rules controlling protocol behavior.
Definition: Rules.h:35
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
Set the fee on a JTx.
Definition: fee.h:37
T endl(T... args)
T minmax(T... args)
Number reduceOffer(auto const &amount)
Definition: AMMHelpers.h:40
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:462
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:194
Number feeMult(std::uint16_t tfee)
Get fee multiplier (1 - tfee) @tfee trading fee in basis points.
Definition: AMMCore.h:110
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:329
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
Definition: AMMCore.h:101
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:630
std::optional< Rules > const & getCurrentTransactionRules()
Definition: Rules.cpp:47
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:265
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:127
Number root2(Number f)
Definition: Number.cpp:701
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:535