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