rippled
Loading...
Searching...
No Matches
AMMWithdraw.cpp
1#include <xrpld/app/misc/AMMHelpers.h>
2#include <xrpld/app/misc/AMMUtils.h>
3#include <xrpld/app/tx/detail/AMMWithdraw.h>
4
5#include <xrpl/basics/Number.h>
6#include <xrpl/ledger/Sandbox.h>
7#include <xrpl/protocol/AMMCore.h>
8#include <xrpl/protocol/TxFlags.h>
9
10namespace xrpl {
11
12bool
17
23
26{
27 auto const flags = ctx.tx.getFlags();
28
29 auto const amount = ctx.tx[~sfAmount];
30 auto const amount2 = ctx.tx[~sfAmount2];
31 auto const ePrice = ctx.tx[~sfEPrice];
32 auto const lpTokens = ctx.tx[~sfLPTokenIn];
33 // Valid combinations are:
34 // LPTokens
35 // tfWithdrawAll
36 // Amount
37 // tfOneAssetWithdrawAll & Amount
38 // Amount and Amount2
39 // Amount and LPTokens
40 // Amount and EPrice
41 if (std::popcount(flags & tfWithdrawSubTx) != 1)
42 {
43 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid flags.";
44 return temMALFORMED;
45 }
46 if (flags & tfLPToken)
47 {
48 if (!lpTokens || amount || amount2 || ePrice)
49 return temMALFORMED;
50 }
51 else if (flags & tfWithdrawAll)
52 {
53 if (lpTokens || amount || amount2 || ePrice)
54 return temMALFORMED;
55 }
56 else if (flags & tfOneAssetWithdrawAll)
57 {
58 if (!amount || lpTokens || amount2 || ePrice)
59 return temMALFORMED;
60 }
61 else if (flags & tfSingleAsset)
62 {
63 if (!amount || lpTokens || amount2 || ePrice)
64 return temMALFORMED;
65 }
66 else if (flags & tfTwoAsset)
67 {
68 if (!amount || !amount2 || lpTokens || ePrice)
69 return temMALFORMED;
70 }
71 else if (flags & tfOneAssetLPToken)
72 {
73 if (!amount || !lpTokens || amount2 || ePrice)
74 return temMALFORMED;
75 }
76 else if (flags & tfLimitLPToken)
77 {
78 if (!amount || !ePrice || lpTokens || amount2)
79 return temMALFORMED;
80 }
81
82 auto const asset = ctx.tx[sfAsset].get<Issue>();
83 auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
84 if (auto const res = invalidAMMAssetPair(asset, asset2))
85 {
86 JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair.";
87 return res;
88 }
89
90 if (amount && amount2 && amount->issue() == amount2->issue())
91 {
92 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens, same issue." << amount->issue() << " "
93 << amount2->issue();
94 return temBAD_AMM_TOKENS;
95 }
96
97 if (lpTokens && *lpTokens <= beast::zero)
98 {
99 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens.";
100 return temBAD_AMM_TOKENS;
101 }
102
103 if (amount)
104 {
105 if (auto const res = invalidAMMAmount(
106 *amount,
107 std::make_optional(std::make_pair(asset, asset2)),
108 (flags & (tfOneAssetWithdrawAll | tfOneAssetLPToken)) || ePrice))
109 {
110 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid Asset1Out";
111 return res;
112 }
113 }
114
115 if (amount2)
116 {
117 if (auto const res = invalidAMMAmount(*amount2, std::make_optional(std::make_pair(asset, asset2))))
118 {
119 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid Asset2OutAmount";
120 return res;
121 }
122 }
123
124 if (ePrice)
125 {
126 if (auto const res = invalidAMMAmount(*ePrice))
127 {
128 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid EPrice";
129 return res;
130 }
131 }
132
133 return tesSUCCESS;
134}
135
137tokensWithdraw(STAmount const& lpTokens, std::optional<STAmount> const& tokensIn, std::uint32_t flags)
138{
139 if (flags & (tfWithdrawAll | tfOneAssetWithdrawAll))
140 return lpTokens;
141 return tokensIn;
142}
143
144TER
146{
147 auto const accountID = ctx.tx[sfAccount];
148
149 auto const ammSle = ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
150 if (!ammSle)
151 {
152 JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair.";
153 return terNO_AMM;
154 }
155
156 auto const amount = ctx.tx[~sfAmount];
157 auto const amount2 = ctx.tx[~sfAmount2];
158
159 auto const expected = ammHolds(
160 ctx.view,
161 *ammSle,
162 amount ? amount->issue() : std::optional<Issue>{},
163 amount2 ? amount2->issue() : std::optional<Issue>{},
165 ctx.j);
166 if (!expected)
167 return expected.error();
168 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
169 if (lptAMMBalance == beast::zero)
170 return tecAMM_EMPTY;
171 if (amountBalance <= beast::zero || amount2Balance <= beast::zero || lptAMMBalance < beast::zero)
172 {
173 // LCOV_EXCL_START
174 JLOG(ctx.j.debug()) << "AMM Withdraw: reserves or tokens balance is zero.";
175 return tecINTERNAL;
176 // LCOV_EXCL_STOP
177 }
178
179 auto const ammAccountID = ammSle->getAccountID(sfAccount);
180
181 auto checkAmount = [&](std::optional<STAmount> const& amount, auto const& balance) -> TER {
182 if (amount)
183 {
184 if (amount > balance)
185 {
186 JLOG(ctx.j.debug()) << "AMM Withdraw: withdrawing more than the balance, " << *amount;
187 return tecAMM_BALANCE;
188 }
189 if (auto const ter = requireAuth(ctx.view, amount->issue(), accountID))
190 {
191 JLOG(ctx.j.debug()) << "AMM Withdraw: account is not authorized, " << amount->issue();
192 return ter;
193 }
194 // AMM account or currency frozen
195 if (isFrozen(ctx.view, ammAccountID, amount->issue()))
196 {
197 JLOG(ctx.j.debug()) << "AMM Withdraw: AMM account or currency is frozen, " << to_string(accountID);
198 return tecFROZEN;
199 }
200 // Account frozen
201 if (isIndividualFrozen(ctx.view, accountID, amount->issue()))
202 {
203 JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen, " << to_string(accountID) << " "
204 << to_string(amount->issue().currency);
205 return tecFROZEN;
206 }
207 }
208 return tesSUCCESS;
209 };
210
211 if (auto const ter = checkAmount(amount, amountBalance))
212 return ter;
213
214 if (auto const ter = checkAmount(amount2, amount2Balance))
215 return ter;
216
217 auto const lpTokens = ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
218 auto const lpTokensWithdraw = tokensWithdraw(lpTokens, ctx.tx[~sfLPTokenIn], ctx.tx.getFlags());
219
220 if (lpTokens <= beast::zero)
221 {
222 JLOG(ctx.j.debug()) << "AMM Withdraw: tokens balance is zero.";
223 return tecAMM_BALANCE;
224 }
225
226 if (lpTokensWithdraw && lpTokensWithdraw->issue() != lpTokens.issue())
227 {
228 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid LPTokens.";
229 return temBAD_AMM_TOKENS;
230 }
231
232 if (lpTokensWithdraw && *lpTokensWithdraw > lpTokens)
233 {
234 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens.";
236 }
237
238 if (auto const ePrice = ctx.tx[~sfEPrice]; ePrice && ePrice->issue() != lpTokens.issue())
239 {
240 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid EPrice.";
241 return temBAD_AMM_TOKENS;
242 }
243
244 if (ctx.tx.getFlags() & (tfLPToken | tfWithdrawAll))
245 {
246 if (auto const ter = checkAmount(amountBalance, amountBalance))
247 return ter;
248 if (auto const ter = checkAmount(amount2Balance, amount2Balance))
249 return ter;
250 }
251
252 return tesSUCCESS;
253}
254
257{
258 auto const amount = ctx_.tx[~sfAmount];
259 auto const amount2 = ctx_.tx[~sfAmount2];
260 auto const ePrice = ctx_.tx[~sfEPrice];
261 auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
262 if (!ammSle)
263 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
264 auto const ammAccountID = (*ammSle)[sfAccount];
265 auto const accountSle = sb.read(keylet::account(ammAccountID));
266 if (!accountSle)
267 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
268 auto const lpTokens = ammLPHolds(ctx_.view(), *ammSle, ctx_.tx[sfAccount], ctx_.journal);
269 auto const lpTokensWithdraw = tokensWithdraw(lpTokens, ctx_.tx[~sfLPTokenIn], ctx_.tx.getFlags());
270
271 // Due to rounding, the LPTokenBalance of the last LP
272 // might not match the LP's trustline balance
273 if (sb.rules().enabled(fixAMMv1_1))
274 {
275 if (auto const res = verifyAndAdjustLPTokenBalance(sb, lpTokens, ammSle, account_); !res)
276 return {res.error(), false};
277 }
278
279 auto const tfee = getTradingFee(ctx_.view(), *ammSle, account_);
280
281 auto const expected = ammHolds(
282 sb,
283 *ammSle,
284 amount ? amount->issue() : std::optional<Issue>{},
285 amount2 ? amount2->issue() : std::optional<Issue>{},
287 ctx_.journal);
288 if (!expected)
289 return {expected.error(), false};
290 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
291
292 auto const subTxType = ctx_.tx.getFlags() & tfWithdrawSubTx;
293
294 auto const [result, newLPTokenBalance] = [&,
295 &amountBalance = amountBalance,
296 &amount2Balance = amount2Balance,
297 &lptAMMBalance = lptAMMBalance]() -> std::pair<TER, STAmount> {
298 if (subTxType & tfTwoAsset)
299 return equalWithdrawLimit(
300 sb, *ammSle, ammAccountID, amountBalance, amount2Balance, lptAMMBalance, *amount, *amount2, tfee);
301 if (subTxType & tfOneAssetLPToken || subTxType & tfOneAssetWithdrawAll)
303 sb, *ammSle, ammAccountID, amountBalance, lptAMMBalance, *amount, *lpTokensWithdraw, tfee);
304 if (subTxType & tfLimitLPToken)
306 sb, *ammSle, ammAccountID, amountBalance, lptAMMBalance, *amount, *ePrice, tfee);
307 if (subTxType & tfSingleAsset)
308 return singleWithdraw(sb, *ammSle, ammAccountID, amountBalance, lptAMMBalance, *amount, tfee);
309 if (subTxType & tfLPToken || subTxType & tfWithdrawAll)
310 {
311 return equalWithdrawTokens(
312 sb,
313 *ammSle,
314 ammAccountID,
315 amountBalance,
316 amount2Balance,
317 lptAMMBalance,
318 lpTokens,
319 *lpTokensWithdraw,
320 tfee);
321 }
322 // should not happen.
323 // LCOV_EXCL_START
324 JLOG(j_.error()) << "AMM Withdraw: invalid options.";
326 // LCOV_EXCL_STOP
327 }();
328
329 if (result != tesSUCCESS)
330 return {result, false};
331
332 auto const res = deleteAMMAccountIfEmpty(
333 sb, ammSle, newLPTokenBalance, ctx_.tx[sfAsset].get<Issue>(), ctx_.tx[sfAsset2].get<Issue>(), j_);
334 // LCOV_EXCL_START
335 if (!res.second)
336 return {res.first, false};
337 // LCOV_EXCL_STOP
338
339 JLOG(ctx_.journal.trace()) << "AMM Withdraw: tokens " << to_string(newLPTokenBalance.iou()) << " "
340 << to_string(lpTokens.iou()) << " " << to_string(lptAMMBalance.iou());
341
342 return {tesSUCCESS, true};
343}
344
345TER
347{
348 // This is the ledger view that we work against. Transactions are applied
349 // as we go on processing transactions.
350 Sandbox sb(&ctx_.view());
351
352 auto const result = applyGuts(sb);
353 if (result.second)
354 sb.apply(ctx_.rawView());
355
356 return result.first;
357}
358
361 Sandbox& view,
362 SLE const& ammSle,
363 AccountID const& ammAccount,
364 STAmount const& amountBalance,
365 STAmount const& amountWithdraw,
366 std::optional<STAmount> const& amount2Withdraw,
367 STAmount const& lpTokensAMMBalance,
368 STAmount const& lpTokensWithdraw,
369 std::uint16_t tfee)
370{
371 TER ter;
372 STAmount newLPTokenBalance;
373 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = withdraw(
374 view,
375 ammSle,
376 ammAccount,
377 account_,
378 amountBalance,
379 amountWithdraw,
380 amount2Withdraw,
381 lpTokensAMMBalance,
382 lpTokensWithdraw,
383 tfee,
387 j_);
388 return {ter, newLPTokenBalance};
389}
390
393 Sandbox& view,
394 SLE const& ammSle,
395 AccountID const& ammAccount,
396 AccountID const& account,
397 STAmount const& amountBalance,
398 STAmount const& amountWithdraw,
399 std::optional<STAmount> const& amount2Withdraw,
400 STAmount const& lpTokensAMMBalance,
401 STAmount const& lpTokensWithdraw,
402 std::uint16_t tfee,
403 FreezeHandling freezeHandling,
404 WithdrawAll withdrawAll,
405 XRPAmount const& priorBalance,
406 beast::Journal const& journal)
407{
408 auto const lpTokens = ammLPHolds(view, ammSle, account, journal);
409 auto const expected = ammHolds(view, ammSle, amountWithdraw.issue(), std::nullopt, freezeHandling, journal);
410 // LCOV_EXCL_START
411 if (!expected)
412 return {expected.error(), STAmount{}, STAmount{}, STAmount{}};
413 // LCOV_EXCL_STOP
414 auto const [curBalance, curBalance2, _] = *expected;
415 (void)_;
416
417 auto const [amountWithdrawActual, amount2WithdrawActual, lpTokensWithdrawActual] =
419 if (withdrawAll == WithdrawAll::No)
421 amountBalance,
422 amountWithdraw,
423 amount2Withdraw,
424 lpTokensAMMBalance,
425 lpTokensWithdraw,
426 tfee,
428 return std::make_tuple(amountWithdraw, amount2Withdraw, lpTokensWithdraw);
429 }();
430
431 if (lpTokensWithdrawActual <= beast::zero || lpTokensWithdrawActual > lpTokens)
432 {
433 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw, invalid LP tokens: " << lpTokensWithdrawActual
434 << " " << lpTokens << " " << lpTokensAMMBalance;
436 }
437
438 // Should not happen since the only LP on last withdraw
439 // has the balance set to the lp token trustline balance.
440 if (view.rules().enabled(fixAMMv1_1) && lpTokensWithdrawActual > lpTokensAMMBalance)
441 {
442 // LCOV_EXCL_START
443 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw, unexpected LP tokens: " << lpTokensWithdrawActual
444 << " " << lpTokens << " " << lpTokensAMMBalance;
445 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
446 // LCOV_EXCL_STOP
447 }
448
449 // Withdrawing one side of the pool
450 if ((amountWithdrawActual == curBalance && amount2WithdrawActual != curBalance2) ||
451 (amount2WithdrawActual == curBalance2 && amountWithdrawActual != curBalance))
452 {
453 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw one side of the pool "
454 << " curBalance: " << curBalance << " " << amountWithdrawActual
455 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance " << lpTokensAMMBalance;
456 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
457 }
458
459 // May happen if withdrawing an amount close to one side of the pool
460 if (lpTokensWithdrawActual == lpTokensAMMBalance &&
461 (amountWithdrawActual != curBalance || amount2WithdrawActual != curBalance2))
462 {
463 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw all tokens "
464 << " curBalance: " << curBalance << " " << amountWithdrawActual
465 << " curBalance2: " << amount2WithdrawActual.value_or(STAmount{0})
466 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance " << lpTokensAMMBalance;
467 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
468 }
469
470 // Withdrawing more than the pool's balance
471 if (amountWithdrawActual > curBalance || amount2WithdrawActual > curBalance2)
472 {
473 JLOG(journal.debug()) << "AMM Withdraw: withdrawing more than the pool's balance "
474 << " curBalance: " << curBalance << " " << amountWithdrawActual
475 << " curBalance2: " << curBalance2 << " "
476 << (amount2WithdrawActual ? *amount2WithdrawActual : STAmount{})
477 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance " << lpTokensAMMBalance;
478 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
479 }
480
481 // Check the reserve in case a trustline has to be created
482 bool const enabledFixAMMv1_2 = view.rules().enabled(fixAMMv1_2);
483 auto sufficientReserve = [&](Issue const& issue) -> TER {
484 if (!enabledFixAMMv1_2 || isXRP(issue))
485 return tesSUCCESS;
486 if (!view.exists(keylet::line(account, issue)))
487 {
488 auto const sleAccount = view.read(keylet::account(account));
489 if (!sleAccount)
490 return tecINTERNAL; // LCOV_EXCL_LINE
491 auto const balance = (*sleAccount)[sfBalance].xrp();
492 std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount);
493
494 // See also SetTrust::doApply()
495 XRPAmount const reserve(
496 (ownerCount < 2) ? XRPAmount(beast::zero) : view.fees().accountReserve(ownerCount + 1));
497
498 if (std::max(priorBalance, balance) < reserve)
500 }
501 return tesSUCCESS;
502 };
503
504 if (auto const err = sufficientReserve(amountWithdrawActual.issue()))
505 return {err, STAmount{}, STAmount{}, STAmount{}};
506
507 // Withdraw amountWithdraw
508 auto res = accountSend(view, ammAccount, account, amountWithdrawActual, journal, WaiveTransferFee::Yes);
509 if (res != tesSUCCESS)
510 {
511 // LCOV_EXCL_START
512 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw " << amountWithdrawActual;
513 return {res, STAmount{}, STAmount{}, STAmount{}};
514 // LCOV_EXCL_STOP
515 }
516
517 // Withdraw amount2Withdraw
518 if (amount2WithdrawActual)
519 {
520 if (auto const err = sufficientReserve(amount2WithdrawActual->issue()); err != tesSUCCESS)
521 return {err, STAmount{}, STAmount{}, STAmount{}};
522
523 res = accountSend(view, ammAccount, account, *amount2WithdrawActual, journal, WaiveTransferFee::Yes);
524 if (res != tesSUCCESS)
525 {
526 // LCOV_EXCL_START
527 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw " << *amount2WithdrawActual;
528 return {res, STAmount{}, STAmount{}, STAmount{}};
529 // LCOV_EXCL_STOP
530 }
531 }
532
533 // Withdraw LP tokens
534 res = redeemIOU(view, account, lpTokensWithdrawActual, lpTokensWithdrawActual.issue(), journal);
535 if (res != tesSUCCESS)
536 {
537 // LCOV_EXCL_START
538 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw LPTokens";
539 return {res, STAmount{}, STAmount{}, STAmount{}};
540 // LCOV_EXCL_STOP
541 }
542
543 return std::make_tuple(
544 tesSUCCESS, lpTokensAMMBalance - lpTokensWithdrawActual, amountWithdrawActual, amount2WithdrawActual);
545}
546
547static STAmount
549 Rules const& rules,
550 STAmount const& lptAMMBalance,
551 STAmount const& lpTokensWithdraw,
552 WithdrawAll withdrawAll)
553{
554 if (!rules.enabled(fixAMMv1_3) || withdrawAll == WithdrawAll::Yes)
555 return lpTokensWithdraw;
556 return adjustLPTokens(lptAMMBalance, lpTokensWithdraw, IsDeposit::No);
557}
558
563 Sandbox& view,
564 SLE const& ammSle,
565 AccountID const& ammAccount,
566 STAmount const& amountBalance,
567 STAmount const& amount2Balance,
568 STAmount const& lptAMMBalance,
569 STAmount const& lpTokens,
570 STAmount const& lpTokensWithdraw,
571 std::uint16_t tfee)
572{
573 TER ter;
574 STAmount newLPTokenBalance;
575 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = equalWithdrawTokens(
576 view,
577 ammSle,
578 account_,
579 ammAccount,
580 amountBalance,
581 amount2Balance,
582 lptAMMBalance,
583 lpTokens,
584 lpTokensWithdraw,
585 tfee,
589 ctx_.journal);
590 return {ter, newLPTokenBalance};
591}
592
595 Sandbox& sb,
596 std::shared_ptr<SLE> const ammSle,
597 STAmount const& lpTokenBalance,
598 Issue const& issue1,
599 Issue const& issue2,
600 beast::Journal const& journal)
601{
602 TER ter;
603 bool updateBalance = true;
604 if (lpTokenBalance == beast::zero)
605 {
606 ter = deleteAMMAccount(sb, issue1, issue2, journal);
607 if (ter != tesSUCCESS && ter != tecINCOMPLETE)
608 return {ter, false}; // LCOV_EXCL_LINE
609 else
610 updateBalance = (ter == tecINCOMPLETE);
611 }
612
613 if (updateBalance)
614 {
615 ammSle->setFieldAmount(sfLPTokenBalance, lpTokenBalance);
616 sb.update(ammSle);
617 }
618
619 return {ter, true};
620}
621
626 Sandbox& view,
627 SLE const& ammSle,
628 AccountID const account,
629 AccountID const& ammAccount,
630 STAmount const& amountBalance,
631 STAmount const& amount2Balance,
632 STAmount const& lptAMMBalance,
633 STAmount const& lpTokens,
634 STAmount const& lpTokensWithdraw,
635 std::uint16_t tfee,
636 FreezeHandling freezeHanding,
637 WithdrawAll withdrawAll,
638 XRPAmount const& priorBalance,
639 beast::Journal const& journal)
640{
641 try
642 {
643 // Withdrawing all tokens in the pool
644 if (lpTokensWithdraw == lptAMMBalance)
645 {
646 return withdraw(
647 view,
648 ammSle,
649 ammAccount,
650 account,
651 amountBalance,
652 amountBalance,
653 amount2Balance,
654 lptAMMBalance,
655 lpTokensWithdraw,
656 tfee,
657 freezeHanding,
659 priorBalance,
660 journal);
661 }
662
663 auto const tokensAdj = adjustLPTokensIn(view.rules(), lptAMMBalance, lpTokensWithdraw, withdrawAll);
664 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
666 // the adjusted tokens are factored in
667 auto const frac = divide(tokensAdj, lptAMMBalance, noIssue());
668 auto const amountWithdraw = getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
669 auto const amount2Withdraw = getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
670 // LP is making equal withdrawal by tokens but the requested amount
671 // of LP tokens is likely too small and results in one-sided pool
672 // withdrawal due to round off. Fail so the user withdraws
673 // more tokens.
674 if (amountWithdraw == beast::zero || amount2Withdraw == beast::zero)
675 return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}};
676
677 return withdraw(
678 view,
679 ammSle,
680 ammAccount,
681 account,
682 amountBalance,
683 amountWithdraw,
684 amount2Withdraw,
685 lptAMMBalance,
686 tokensAdj,
687 tfee,
688 freezeHanding,
689 withdrawAll,
690 priorBalance,
691 journal);
692 }
693 // LCOV_EXCL_START
694 catch (std::exception const& e)
695 {
696 JLOG(journal.error()) << "AMMWithdraw::equalWithdrawTokens exception " << e.what();
697 }
698 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
699 // LCOV_EXCL_STOP
700}
701
729 Sandbox& view,
730 SLE const& ammSle,
731 AccountID const& ammAccount,
732 STAmount const& amountBalance,
733 STAmount const& amount2Balance,
734 STAmount const& lptAMMBalance,
735 STAmount const& amount,
736 STAmount const& amount2,
737 std::uint16_t tfee)
738{
739 auto frac = Number{amount} / amountBalance;
740 auto amount2Withdraw = getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
741 auto tokensAdj = getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
742 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
744 // factor in the adjusted tokens
745 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
746 amount2Withdraw = getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No);
747 if (amount2Withdraw <= amount2)
748 {
749 return withdraw(
750 view, ammSle, ammAccount, amountBalance, amount, amount2Withdraw, lptAMMBalance, tokensAdj, tfee);
751 }
752
753 frac = Number{amount2} / amount2Balance;
754 auto amountWithdraw = getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
755 tokensAdj = getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No);
756 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
757 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
758 // factor in the adjusted tokens
759 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
760 amountWithdraw = getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::No);
761 if (!view.rules().enabled(fixAMMv1_3))
762 {
763 // LCOV_EXCL_START
764 XRPL_ASSERT(amountWithdraw <= amount, "xrpl::AMMWithdraw::equalWithdrawLimit : maximum amountWithdraw");
765 // LCOV_EXCL_STOP
766 }
767 else if (amountWithdraw > amount)
768 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
769 return withdraw(view, ammSle, ammAccount, amountBalance, amountWithdraw, amount2, lptAMMBalance, tokensAdj, tfee);
770}
771
779 Sandbox& view,
780 SLE const& ammSle,
781 AccountID const& ammAccount,
782 STAmount const& amountBalance,
783 STAmount const& lptAMMBalance,
784 STAmount const& amount,
785 std::uint16_t tfee)
786{
787 auto const tokens = adjustLPTokensIn(
788 view.rules(), lptAMMBalance, lpTokensIn(amountBalance, amount, lptAMMBalance, tfee), isWithdrawAll(ctx_.tx));
789 if (tokens == beast::zero)
790 {
791 if (!view.rules().enabled(fixAMMv1_3))
792 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
793 else
795 }
796 // factor in the adjusted tokens
797 auto const [tokensAdj, amountWithdrawAdj] =
798 adjustAssetOutByTokens(view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
799 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
800 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
801 return withdraw(
802 view, ammSle, ammAccount, amountBalance, amountWithdrawAdj, std::nullopt, lptAMMBalance, tokensAdj, tfee);
803}
804
817 Sandbox& view,
818 SLE const& ammSle,
819 AccountID const& ammAccount,
820 STAmount const& amountBalance,
821 STAmount const& lptAMMBalance,
822 STAmount const& amount,
823 STAmount const& lpTokensWithdraw,
824 std::uint16_t tfee)
825{
826 auto const tokensAdj = adjustLPTokensIn(view.rules(), lptAMMBalance, lpTokensWithdraw, isWithdrawAll(ctx_.tx));
827 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
829 // the adjusted tokens are factored in
830 auto const amountWithdraw = ammAssetOut(amountBalance, lptAMMBalance, tokensAdj, tfee);
831 if (amount == beast::zero || amountWithdraw >= amount)
832 {
833 return withdraw(
834 view, ammSle, ammAccount, amountBalance, amountWithdraw, std::nullopt, lptAMMBalance, tokensAdj, tfee);
835 }
836
837 return {tecAMM_FAILED, STAmount{}};
838}
839
861 Sandbox& view,
862 SLE const& ammSle,
863 AccountID const& ammAccount,
864 STAmount const& amountBalance,
865 STAmount const& lptAMMBalance,
866 STAmount const& amount,
867 STAmount const& ePrice,
868 std::uint16_t tfee)
869{
870 // LPTokens is asset in => E = t / a and formula (8) is:
871 // a = A*(t1**2 + t1*(f - 2))/(t1*f - 1)
872 // substitute a as t/E =>
873 // t/E = A*(t1**2 + t1*(f - 2))/(t1*f - 1), t1=t/T => t = t1*T
874 // t1*T/E = A*((t/T)**2 + t*(f - 2)/T)/(t*f/T - 1) =>
875 // T/E = A*(t1 + f-2)/(t1*f - 1) =>
876 // T*(t1*f - 1) = A*E*(t1 + f - 2) =>
877 // t1*T*f - T = t1*A*E + A*E*(f - 2) =>
878 // t1*(T*f - A*E) = T + A*E*(f - 2) =>
879 // t = T*(T + A*E*(f - 2))/(T*f - A*E)
880 Number const ae = amountBalance * ePrice;
881 auto const f = getFee(tfee);
882 auto tokNoRoundCb = [&] { return lptAMMBalance * (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae); };
883 auto tokProdCb = [&] { return (lptAMMBalance + ae * (f - 2)) / (lptAMMBalance * f - ae); };
884 auto const tokensAdj = getRoundedLPTokens(view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::No);
885 if (tokensAdj <= beast::zero)
886 {
887 if (!view.rules().enabled(fixAMMv1_3))
888 return {tecAMM_FAILED, STAmount{}};
889 else
891 }
892 auto amtNoRoundCb = [&] { return tokensAdj / ePrice; };
893 auto amtProdCb = [&] { return tokensAdj / ePrice; };
894 // the adjusted tokens are factored in
895 auto const amountWithdraw = getRoundedAsset(view.rules(), amtNoRoundCb, amount, amtProdCb, IsDeposit::No);
896 if (amount == beast::zero || amountWithdraw >= amount)
897 {
898 return withdraw(
899 view, ammSle, ammAccount, amountBalance, amountWithdraw, std::nullopt, lptAMMBalance, tokensAdj, tfee);
900 }
901
902 return {tecAMM_FAILED, STAmount{}};
903}
904
907{
908 if (tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll))
909 return WithdrawAll::Yes;
910 return WithdrawAll::No;
911}
912} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:41
Stream error() const
Definition Journal.h:319
Stream debug() const
Definition Journal.h:301
Stream trace() const
Severity stream access functions.
Definition Journal.h:295
static WithdrawAll isWithdrawAll(STTx const &tx)
Check from the flags if it's withdraw all.
static std::tuple< TER, STAmount, STAmount, std::optional< STAmount > > equalWithdrawTokens(Sandbox &view, SLE const &ammSle, AccountID const account, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, STAmount const &lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHanding, WithdrawAll withdrawAll, XRPAmount const &priorBalance, beast::Journal const &journal)
Equal-asset withdrawal (LPTokens) of some AMM instance pools shares represented by the number of LPTo...
static NotTEC preflight(PreflightContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static std::tuple< TER, STAmount, STAmount, std::optional< STAmount > > withdraw(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, AccountID const &account, STAmount const &amountBalance, STAmount const &amountWithdraw, std::optional< STAmount > const &amount2Withdraw, STAmount const &lpTokensAMMBalance, STAmount const &lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHandling, WithdrawAll withdrawAll, XRPAmount const &priorBalance, beast::Journal const &journal)
Withdraw requested assets and token from AMM into LP account.
TER doApply() override
std::pair< TER, STAmount > equalWithdrawLimit(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &amount2, std::uint16_t tfee)
Withdraw both assets (Asset1Out, Asset2Out) with the constraints on the maximum amount of each asset ...
static std::pair< TER, bool > deleteAMMAccountIfEmpty(Sandbox &sb, std::shared_ptr< SLE > const ammSle, STAmount const &lpTokenBalance, Issue const &issue1, Issue const &issue2, beast::Journal const &journal)
std::pair< TER, STAmount > singleWithdrawTokens(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &lpTokensWithdraw, std::uint16_t tfee)
Single asset withdrawal (Asset1Out, LPTokens) proportional to the share specified by tokens.
std::pair< TER, STAmount > singleWithdraw(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, std::uint16_t tfee)
Single asset withdrawal (Asset1Out) equivalent to the amount specified in Asset1Out.
static bool checkExtraFeatures(PreflightContext const &ctx)
std::pair< TER, bool > applyGuts(Sandbox &view)
std::pair< TER, STAmount > singleWithdrawEPrice(Sandbox &view, SLE const &ammSle, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &ePrice, std::uint16_t tfee)
Withdraw single asset (Asset1Out, EPrice) with two constraints.
STTx const & tx
beast::Journal const journal
RawView & rawView()
ApplyView & view()
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
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
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
std::uint32_t getFlags() const
Definition STObject.cpp:492
Discardable, editable view to a ledger.
Definition Sandbox.h:16
void apply(RawView &to)
Definition Sandbox.h:36
AccountID const account_
Definition Transactor.h:113
beast::Journal const j_
Definition Transactor.h:111
ApplyView & view()
Definition Transactor.h:129
XRPAmount mPriorBalance
Definition Transactor.h:114
ApplyContext & ctx_
Definition Transactor.h:109
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
Rules const & rules() const override
Returns the tx processing rules.
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
T is_same_v
T make_optional(T... args)
T make_pair(T... args)
T make_tuple(T... args)
T max(T... args)
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:393
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:214
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:160
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:69
@ terNO_AMM
Definition TER.h:208
FreezeHandling
Controls the treatment of frozen account balances.
Definition View.h:59
@ fhZERO_IF_FROZEN
Definition View.h:59
@ fhIGNORE_FREEZE
Definition View.h:59
WithdrawAll
AMMWithdraw implements AMM withdraw Transactor.
Definition AMMWithdraw.h:49
TER deleteAMMAccount(Sandbox &view, Issue const &asset, Issue const &asset2, beast::Journal j)
Delete trustlines to AMM.
Definition AMMUtils.cpp:222
constexpr std::uint32_t tfWithdrawAll
Definition TxFlags.h:226
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition AMMCore.cpp:95
bool isXRP(AccountID const &c)
Definition AccountID.h:71
constexpr std::uint32_t tfSingleAsset
Definition TxFlags.h:228
Number adjustFracByTokens(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &tokens, Number const &frac)
Find a fraction of tokens after the tokens are adjusted.
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:598
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt)
Definition AMMCore.cpp:55
static std::optional< STAmount > tokensWithdraw(STAmount const &lpTokens, std::optional< STAmount > const &tokensIn, std::uint32_t flags)
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
constexpr std::uint32_t tfTwoAsset
Definition TxFlags.h:229
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.
constexpr std::uint32_t tfOneAssetLPToken
Definition TxFlags.h:230
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2446
Expected< std::tuple< STAmount, STAmount, STAmount >, TER > ammHolds(ReadView const &view, SLE const &ammSle, std::optional< Issue > const &optIssue1, std::optional< Issue > const &optIssue2, FreezeHandling freezeHandling, beast::Journal const j)
Get AMM pool and LP token balances.
Definition AMMUtils.cpp:26
constexpr std::uint32_t tfOneAssetWithdrawAll
Definition TxFlags.h:227
static STAmount adjustLPTokensIn(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &lpTokensWithdraw, WithdrawAll withdrawAll)
constexpr std::uint32_t tfWithdrawSubTx
Definition TxFlags.h:233
std::pair< STAmount, STAmount > adjustAssetOutByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
TERSubset< CanCvtToTER > TER
Definition TER.h:621
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition AMMHelpers.h:597
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:2710
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:194
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:169
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition AMMCore.cpp:67
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...
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition View.cpp:2616
@ temBAD_AMM_TOKENS
Definition TER.h:110
@ temMALFORMED
Definition TER.h:68
STAmount lpTokensIn(STAmount const &asset1Balance, STAmount const &asset1Withdraw, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's withdraw amount.
constexpr std::uint32_t tfWithdrawMask
Definition TxFlags.h:239
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
Definition AMMCore.h:76
constexpr std::uint32_t tfLimitLPToken
Definition TxFlags.h:231
constexpr std::uint32_t tfLPToken
Definition TxFlags.h:225
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
Definition Issue.h:106
@ tecAMM_EMPTY
Definition TER.h:314
@ tecAMM_INVALID_TOKENS
Definition TER.h:313
@ tecAMM_FAILED
Definition TER.h:312
@ tecINCOMPLETE
Definition TER.h:317
@ tecINTERNAL
Definition TER.h:292
@ tecAMM_BALANCE
Definition TER.h:311
@ tecFROZEN
Definition TER.h:285
@ tecINSUFFICIENT_RESERVE
Definition TER.h:289
std::uint16_t getTradingFee(ReadView const &view, SLE const &ammSle, AccountID const &account)
Get AMM trading fee for the given account.
Definition AMMUtils.cpp:139
@ tesSUCCESS
Definition TER.h:226
STAmount ammLPHolds(ReadView const &view, Currency const &cur1, Currency const &cur2, AccountID const &ammAccount, AccountID const &lpAccount, beast::Journal const j)
Get the balance of LP tokens.
Definition AMMUtils.cpp:80
Expected< bool, TER > verifyAndAdjustLPTokenBalance(Sandbox &sb, STAmount const &lpTokens, std::shared_ptr< SLE > &ammSle, AccountID const &account)
Due to rounding, the LPTokenBalance of the last LP might not match the LP's trustline balance.
Definition AMMUtils.cpp:390
T popcount(T... args)
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:54
ReadView const & view
Definition Transactor.h:57
beast::Journal const j
Definition Transactor.h:62
State information when preflighting a tx.
Definition Transactor.h:16
beast::Journal const j
Definition Transactor.h:23
T tie(T... args)
T what(T... args)