rippled
Loading...
Searching...
No Matches
AMMHelpers.cpp
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#include <xrpld/app/misc/AMMHelpers.h>
21
22namespace ripple {
23
24STAmount
26 STAmount const& asset1,
27 STAmount const& asset2,
28 Issue const& lptIssue)
29{
30 // AMM invariant: sqrt(asset1 * asset2) >= LPTokensBalance
31 auto const rounding =
33 NumberRoundModeGuard g(rounding);
34 auto const tokens = root2(asset1 * asset2);
35 return toSTAmount(lptIssue, tokens);
36}
37
38/*
39 * Equation 3:
40 * t = T * [(b/B - (sqrt(f2**2 - b/(B*f1)) - f2)) /
41 * (1 + sqrt(f2**2 - b/(B*f1)) - f2)]
42 * where f1 = 1 - tfee, f2 = (1 - tfee/2)/f1
43 */
44STAmount
46 STAmount const& asset1Balance,
47 STAmount const& asset1Deposit,
48 STAmount const& lptAMMBalance,
49 std::uint16_t tfee)
50{
51 auto const f1 = feeMult(tfee);
52 auto const f2 = feeMultHalf(tfee) / f1;
53 Number const r = asset1Deposit / asset1Balance;
54 auto const c = root2(f2 * f2 + r / f1) - f2;
55 if (!isFeatureEnabled(fixAMMv1_3))
56 {
57 auto const t = lptAMMBalance * (r - c) / (1 + c);
58 return toSTAmount(lptAMMBalance.issue(), t);
59 }
60 else
61 {
62 // minimize tokens out
63 auto const frac = (r - c) / (1 + c);
64 return multiply(lptAMMBalance, frac, Number::downward);
65 }
66}
67
68/* Equation 4 solves equation 3 for b:
69 * Let f1 = 1 - tfee, f2 = (1 - tfee/2)/f1, t1 = t/T, t2 = 1 + t1, R = b/B
70 * then
71 * t1 = [R - sqrt(f2**2 + R/f1) + f2] / [1 + sqrt(f2**2 + R/f1] - f2] =>
72 * sqrt(f2**2 + R/f1)*(t1 + 1) = R + f2 + t1*f2 - t1 =>
73 * sqrt(f2**2 + R/f1)*t2 = R + t2*f2 - t1 =>
74 * sqrt(f2**2 + R/f1) = R/t2 + f2 - t1/t2, let d = f2 - t1/t2 =>
75 * sqrt(f2**2 + R/f1) = R/t2 + d =>
76 * f2**2 + R/f1 = (R/t2)**2 +2*d*R/t2 + d**2 =>
77 * (R/t2)**2 + R*(2*d/t2 - 1/f1) + d**2 - f2**2 = 0
78 */
79STAmount
81 STAmount const& asset1Balance,
82 STAmount const& lptAMMBalance,
83 STAmount const& lpTokens,
84 std::uint16_t tfee)
85{
86 auto const f1 = feeMult(tfee);
87 auto const f2 = feeMultHalf(tfee) / f1;
88 auto const t1 = lpTokens / lptAMMBalance;
89 auto const t2 = 1 + t1;
90 auto const d = f2 - t1 / t2;
91 auto const a = 1 / (t2 * t2);
92 auto const b = 2 * d / t2 - 1 / f1;
93 auto const c = d * d - f2 * f2;
94 if (!isFeatureEnabled(fixAMMv1_3))
95 {
96 return toSTAmount(
97 asset1Balance.issue(), asset1Balance * solveQuadraticEq(a, b, c));
98 }
99 else
100 {
101 // maximize deposit
102 auto const frac = solveQuadraticEq(a, b, c);
103 return multiply(asset1Balance, frac, Number::upward);
104 }
105}
106
107/* Equation 7:
108 * t = T * (c - sqrt(c**2 - 4*R))/2
109 * where R = b/B, c = R*fee + 2 - fee
110 */
111STAmount
113 STAmount const& asset1Balance,
114 STAmount const& asset1Withdraw,
115 STAmount const& lptAMMBalance,
116 std::uint16_t tfee)
117{
118 Number const fr = asset1Withdraw / asset1Balance;
119 auto const f1 = getFee(tfee);
120 auto const c = fr * f1 + 2 - f1;
121 if (!isFeatureEnabled(fixAMMv1_3))
122 {
123 auto const t = lptAMMBalance * (c - root2(c * c - 4 * fr)) / 2;
124 return toSTAmount(lptAMMBalance.issue(), t);
125 }
126 else
127 {
128 // maximize tokens in
129 auto const frac = (c - root2(c * c - 4 * fr)) / 2;
130 return multiply(lptAMMBalance, frac, Number::upward);
131 }
132}
133
134/* Equation 8 solves equation 7 for b:
135 * c - 2*t/T = sqrt(c**2 - 4*R) =>
136 * c**2 - 4*c*t/T + 4*t**2/T**2 = c**2 - 4*R =>
137 * -4*c*t/T + 4*t**2/T**2 = -4*R =>
138 * -c*t/T + t**2/T**2 = -R -=>
139 * substitute c = R*f + 2 - f =>
140 * -(t/T)*(R*f + 2 - f) + (t/T)**2 = -R, let t1 = t/T =>
141 * -t1*R*f -2*t1 +t1*f +t1**2 = -R =>
142 * R = (t1**2 + t1*(f - 2)) / (t1*f - 1)
143 */
144STAmount
146 STAmount const& assetBalance,
147 STAmount const& lptAMMBalance,
148 STAmount const& lpTokens,
149 std::uint16_t tfee)
150{
151 auto const f = getFee(tfee);
152 Number const t1 = lpTokens / lptAMMBalance;
153 if (!isFeatureEnabled(fixAMMv1_3))
154 {
155 auto const b = assetBalance * (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1);
156 return toSTAmount(assetBalance.issue(), b);
157 }
158 else
159 {
160 // minimize withdraw
161 auto const frac = (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1);
162 return multiply(assetBalance, frac, Number::downward);
163 }
164}
165
166Number
167square(Number const& n)
168{
169 return n * n;
170}
171
172STAmount
174 STAmount const& lptAMMBalance,
175 STAmount const& lpTokens,
176 IsDeposit isDeposit)
177{
178 // Force rounding downward to ensure adjusted tokens are less or equal
179 // to requested tokens.
181 if (isDeposit == IsDeposit::Yes)
182 return (lptAMMBalance + lpTokens) - lptAMMBalance;
183 return (lpTokens - lptAMMBalance) + lptAMMBalance;
184}
185
188 STAmount const& amountBalance,
189 STAmount const& amount,
190 std::optional<STAmount> const& amount2,
191 STAmount const& lptAMMBalance,
192 STAmount const& lpTokens,
193 std::uint16_t tfee,
194 IsDeposit isDeposit)
195{
196 // AMMv1_3 amendment adjusts tokens and amounts in deposit/withdraw
197 if (isFeatureEnabled(fixAMMv1_3))
198 return std::make_tuple(amount, amount2, lpTokens);
199
200 auto const lpTokensActual =
201 adjustLPTokens(lptAMMBalance, lpTokens, isDeposit);
202
203 if (lpTokensActual == beast::zero)
204 {
205 auto const amount2Opt =
207 return std::make_tuple(STAmount{}, amount2Opt, lpTokensActual);
208 }
209
210 if (lpTokensActual < lpTokens)
211 {
212 bool const ammRoundingEnabled = [&]() {
213 if (auto const& rules = getCurrentTransactionRules();
214 rules && rules->enabled(fixAMMv1_1))
215 return true;
216 return false;
217 }();
218
219 // Equal trade
220 if (amount2)
221 {
222 Number const fr = lpTokensActual / lpTokens;
223 auto const amountActual = toSTAmount(amount.issue(), fr * amount);
224 auto const amount2Actual =
225 toSTAmount(amount2->issue(), fr * *amount2);
226 if (!ammRoundingEnabled)
227 return std::make_tuple(
228 amountActual < amount ? amountActual : amount,
229 amount2Actual < amount2 ? amount2Actual : amount2,
230 lpTokensActual);
231 else
232 return std::make_tuple(
233 amountActual, amount2Actual, lpTokensActual);
234 }
235
236 // Single trade
237 auto const amountActual = [&]() {
238 if (isDeposit == IsDeposit::Yes)
239 return ammAssetIn(
240 amountBalance, lptAMMBalance, lpTokensActual, tfee);
241 else if (!ammRoundingEnabled)
242 return ammAssetOut(
243 amountBalance, lptAMMBalance, lpTokens, tfee);
244 else
245 return ammAssetOut(
246 amountBalance, lptAMMBalance, lpTokensActual, tfee);
247 }();
248 if (!ammRoundingEnabled)
249 return amountActual < amount
250 ? std::make_tuple(amountActual, std::nullopt, lpTokensActual)
251 : std::make_tuple(amount, std::nullopt, lpTokensActual);
252 else
253 return std::make_tuple(amountActual, std::nullopt, lpTokensActual);
254 }
255
256 XRPL_ASSERT(
257 lpTokensActual == lpTokens,
258 "ripple::adjustAmountsByLPTokens : LP tokens match actual");
259
260 return {amount, amount2, lpTokensActual};
261}
262
263Number
264solveQuadraticEq(Number const& a, Number const& b, Number const& c)
265{
266 return (-b + root2(b * b - 4 * a * c)) / (2 * a);
267}
268
269// Minimize takerGets or takerPays
271solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c)
272{
273 auto const d = b * b - 4 * a * c;
274 if (d < 0)
275 return std::nullopt;
276 // use numerically stable citardauq formula for quadratic equation solution
277 // https://people.csail.mit.edu/bkph/articles/Quadratics.pdf
278 if (b > 0)
279 return (2 * c) / (-b - root2(d));
280 else
281 return (2 * c) / (-b + root2(d));
282}
283
284STAmount
285multiply(STAmount const& amount, Number const& frac, Number::rounding_mode rm)
286{
288 auto const t = amount * frac;
289 return toSTAmount(amount.issue(), t, rm);
290}
291
292STAmount
294 Rules const& rules,
295 std::function<Number()>&& noRoundCb,
296 STAmount const& balance,
297 std::function<Number()>&& productCb,
298 IsDeposit isDeposit)
299{
300 if (!rules.enabled(fixAMMv1_3))
301 return toSTAmount(balance.issue(), noRoundCb());
302
303 auto const rm = detail::getAssetRounding(isDeposit);
304 if (isDeposit == IsDeposit::Yes)
305 return multiply(balance, productCb(), rm);
307 return toSTAmount(balance.issue(), productCb(), rm);
308}
309
310STAmount
312 Rules const& rules,
313 STAmount const& balance,
314 Number const& frac,
315 IsDeposit isDeposit)
316{
317 if (!rules.enabled(fixAMMv1_3))
318 return toSTAmount(balance.issue(), balance * frac);
319
320 auto const rm = detail::getLPTokenRounding(isDeposit);
321 auto const tokens = multiply(balance, frac, rm);
322 return adjustLPTokens(balance, tokens, isDeposit);
323}
324
325STAmount
327 Rules const& rules,
328 std::function<Number()>&& noRoundCb,
329 STAmount const& lptAMMBalance,
330 std::function<Number()>&& productCb,
331 IsDeposit isDeposit)
332{
333 if (!rules.enabled(fixAMMv1_3))
334 return toSTAmount(lptAMMBalance.issue(), noRoundCb());
335
336 auto const tokens = [&] {
337 auto const rm = detail::getLPTokenRounding(isDeposit);
338 if (isDeposit == IsDeposit::Yes)
339 {
341 return toSTAmount(lptAMMBalance.issue(), productCb(), rm);
342 }
343 return multiply(lptAMMBalance, productCb(), rm);
344 }();
345 return adjustLPTokens(lptAMMBalance, tokens, isDeposit);
346}
347
350 Rules const& rules,
351 STAmount const& balance,
352 STAmount const& amount,
353 STAmount const& lptAMMBalance,
354 STAmount const& tokens,
355 std::uint16_t tfee)
356{
357 if (!rules.enabled(fixAMMv1_3))
358 return {tokens, amount};
359 auto assetAdj = ammAssetIn(balance, lptAMMBalance, tokens, tfee);
360 auto tokensAdj = tokens;
361 // Rounding didn't work the right way.
362 // Try to adjust the original deposit amount by difference
363 // in adjust and original amount. Then adjust tokens and deposit amount.
364 if (assetAdj > amount)
365 {
366 auto const adjAmount = amount - (assetAdj - amount);
367 auto const t = lpTokensOut(balance, adjAmount, lptAMMBalance, tfee);
368 tokensAdj = adjustLPTokens(lptAMMBalance, t, IsDeposit::Yes);
369 assetAdj = ammAssetIn(balance, lptAMMBalance, tokensAdj, tfee);
370 }
371 return {tokensAdj, std::min(amount, assetAdj)};
372}
373
376 Rules const& rules,
377 STAmount const& balance,
378 STAmount const& amount,
379 STAmount const& lptAMMBalance,
380 STAmount const& tokens,
381 std::uint16_t tfee)
382{
383 if (!rules.enabled(fixAMMv1_3))
384 return {tokens, amount};
385 auto assetAdj = ammAssetOut(balance, lptAMMBalance, tokens, tfee);
386 auto tokensAdj = tokens;
387 // Rounding didn't work the right way.
388 // Try to adjust the original deposit amount by difference
389 // in adjust and original amount. Then adjust tokens and deposit amount.
390 if (assetAdj > amount)
391 {
392 auto const adjAmount = amount - (assetAdj - amount);
393 auto const t = lpTokensIn(balance, adjAmount, lptAMMBalance, tfee);
394 tokensAdj = adjustLPTokens(lptAMMBalance, t, IsDeposit::No);
395 assetAdj = ammAssetOut(balance, lptAMMBalance, tokensAdj, tfee);
396 }
397 return {tokensAdj, std::min(amount, assetAdj)};
398}
399
400Number
402 Rules const& rules,
403 STAmount const& lptAMMBalance,
404 STAmount const& tokens,
405 Number const& frac)
406{
407 if (!rules.enabled(fixAMMv1_3))
408 return frac;
409 return tokens / lptAMMBalance;
410}
411
412} // namespace ripple
A currency issued by an account.
Definition Issue.h:33
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:38
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:130
Issue const & issue() const
Definition STAmount.h:496
T is_same_v
T make_optional(T... args)
T make_tuple(T... args)
T min(T... args)
Number::rounding_mode getAssetRounding(IsDeposit isDeposit)
Definition AMMHelpers.h:662
Number::rounding_mode getLPTokenRounding(IsDeposit isDeposit)
Definition AMMHelpers.h:654
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
bool isFeatureEnabled(uint256 const &feature)
Definition Rules.cpp:155
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< Number > solveQuadraticEqSmallest(Number const &a, Number const &b, Number const &c)
Solve quadratic equation to find takerGets or takerPays.
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)
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:53
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:110
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 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:101
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
std::optional< Rules > const & getCurrentTransactionRules()
Definition Rules.cpp:47
STAmount lpTokensIn(STAmount const &asset1Balance, STAmount const &asset1Withdraw, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's withdraw amount.
Number feeMultHalf(std::uint16_t tfee)
Get fee multiplier (1 - tfee / 2) @tfee trading fee in basis points.
Definition AMMCore.h:119
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:678
Number root2(Number f)
Definition Number.cpp:701
Number adjustFracByTokens(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &tokens, Number const &frac)
Find a fraction of tokens after the tokens are adjusted.