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