rippled
Loading...
Searching...
No Matches
AMMWithdraw.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#include <xrpld/app/misc/AMMUtils.h>
22#include <xrpld/app/tx/detail/AMMWithdraw.h>
23#include <xrpld/ledger/Sandbox.h>
24
25#include <xrpl/basics/Number.h>
26#include <xrpl/protocol/AMMCore.h>
27#include <xrpl/protocol/TxFlags.h>
28
29namespace ripple {
30
33{
34 if (!ammEnabled(ctx.rules))
35 return temDISABLED;
36
37 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
38 return ret;
39
40 auto const flags = ctx.tx.getFlags();
42 {
43 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid flags.";
44 return temINVALID_FLAG;
45 }
46
47 auto const amount = ctx.tx[~sfAmount];
48 auto const amount2 = ctx.tx[~sfAmount2];
49 auto const ePrice = ctx.tx[~sfEPrice];
50 auto const lpTokens = ctx.tx[~sfLPTokenIn];
51 // Valid combinations are:
52 // LPTokens
53 // tfWithdrawAll
54 // Amount
55 // tfOneAssetWithdrawAll & Amount
56 // Amount and Amount2
57 // Amount and LPTokens
58 // Amount and EPrice
60 {
61 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid flags.";
62 return temMALFORMED;
63 }
64 if (flags & tfLPToken)
65 {
66 if (!lpTokens || amount || amount2 || ePrice)
67 return temMALFORMED;
68 }
69 else if (flags & tfWithdrawAll)
70 {
71 if (lpTokens || amount || amount2 || ePrice)
72 return temMALFORMED;
73 }
75 {
76 if (!amount || lpTokens || amount2 || ePrice)
77 return temMALFORMED;
78 }
79 else if (flags & tfSingleAsset)
80 {
81 if (!amount || lpTokens || amount2 || ePrice)
82 return temMALFORMED;
83 }
84 else if (flags & tfTwoAsset)
85 {
86 if (!amount || !amount2 || lpTokens || ePrice)
87 return temMALFORMED;
88 }
89 else if (flags & tfOneAssetLPToken)
90 {
91 if (!amount || !lpTokens || amount2 || ePrice)
92 return temMALFORMED;
93 }
94 else if (flags & tfLimitLPToken)
95 {
96 if (!amount || !ePrice || lpTokens || amount2)
97 return temMALFORMED;
98 }
99
100 auto const asset = ctx.tx[sfAsset].get<Issue>();
101 auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
102 if (auto const res = invalidAMMAssetPair(asset, asset2))
103 {
104 JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair.";
105 return res;
106 }
107
108 if (amount && amount2 && amount->issue() == amount2->issue())
109 {
110 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens, same issue."
111 << amount->issue() << " " << amount2->issue();
112 return temBAD_AMM_TOKENS;
113 }
114
115 if (lpTokens && *lpTokens <= beast::zero)
116 {
117 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens.";
118 return temBAD_AMM_TOKENS;
119 }
120
121 if (amount)
122 {
123 if (auto const res = invalidAMMAmount(
124 *amount,
125 std::make_optional(std::make_pair(asset, asset2)),
127 ePrice))
128 {
129 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid Asset1Out";
130 return res;
131 }
132 }
133
134 if (amount2)
135 {
136 if (auto const res = invalidAMMAmount(
137 *amount2, std::make_optional(std::make_pair(asset, asset2))))
138 {
139 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid Asset2OutAmount";
140 return res;
141 }
142 }
143
144 if (ePrice)
145 {
146 if (auto const res = invalidAMMAmount(*ePrice))
147 {
148 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid EPrice";
149 return res;
150 }
151 }
152
153 return preflight2(ctx);
154}
155
158 STAmount const& lpTokens,
159 std::optional<STAmount> const& tokensIn,
161{
163 return lpTokens;
164 return tokensIn;
165}
166
167TER
169{
170 auto const accountID = ctx.tx[sfAccount];
171
172 auto const ammSle =
173 ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
174 if (!ammSle)
175 {
176 JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair.";
177 return terNO_AMM;
178 }
179
180 auto const amount = ctx.tx[~sfAmount];
181 auto const amount2 = ctx.tx[~sfAmount2];
182
183 auto const expected = ammHolds(
184 ctx.view,
185 *ammSle,
186 amount ? amount->issue() : std::optional<Issue>{},
187 amount2 ? amount2->issue() : std::optional<Issue>{},
189 ctx.j);
190 if (!expected)
191 return expected.error();
192 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
193 if (lptAMMBalance == beast::zero)
194 return tecAMM_EMPTY;
195 if (amountBalance <= beast::zero || amount2Balance <= beast::zero ||
196 lptAMMBalance < beast::zero)
197 {
198 JLOG(ctx.j.debug())
199 << "AMM Withdraw: reserves or tokens balance is zero.";
200 return tecINTERNAL; // LCOV_EXCL_LINE
201 }
202
203 auto const ammAccountID = ammSle->getAccountID(sfAccount);
204
205 auto checkAmount = [&](std::optional<STAmount> const& amount,
206 auto const& balance) -> TER {
207 if (amount)
208 {
209 if (amount > balance)
210 {
211 JLOG(ctx.j.debug())
212 << "AMM Withdraw: withdrawing more than the balance, "
213 << *amount;
214 return tecAMM_BALANCE;
215 }
216 if (auto const ter =
217 requireAuth(ctx.view, amount->issue(), accountID))
218 {
219 JLOG(ctx.j.debug())
220 << "AMM Withdraw: account is not authorized, "
221 << amount->issue();
222 return ter;
223 }
224 // AMM account or currency frozen
225 if (isFrozen(ctx.view, ammAccountID, amount->issue()))
226 {
227 JLOG(ctx.j.debug())
228 << "AMM Withdraw: AMM account or currency is frozen, "
229 << to_string(accountID);
230 return tecFROZEN;
231 }
232 // Account frozen
233 if (isIndividualFrozen(ctx.view, accountID, amount->issue()))
234 {
235 JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen, "
236 << to_string(accountID) << " "
237 << to_string(amount->issue().currency);
238 return tecFROZEN;
239 }
240 }
241 return tesSUCCESS;
242 };
243
244 if (auto const ter = checkAmount(amount, amountBalance))
245 return ter;
246
247 if (auto const ter = checkAmount(amount2, amount2Balance))
248 return ter;
249
250 auto const lpTokens =
251 ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
252 auto const lpTokensWithdraw =
253 tokensWithdraw(lpTokens, ctx.tx[~sfLPTokenIn], ctx.tx.getFlags());
254
255 if (lpTokens <= beast::zero)
256 {
257 JLOG(ctx.j.debug()) << "AMM Withdraw: tokens balance is zero.";
258 return tecAMM_BALANCE;
259 }
260
261 if (lpTokensWithdraw && lpTokensWithdraw->issue() != lpTokens.issue())
262 {
263 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid LPTokens.";
264 return temBAD_AMM_TOKENS;
265 }
266
267 if (lpTokensWithdraw && *lpTokensWithdraw > lpTokens)
268 {
269 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens.";
271 }
272
273 if (auto const ePrice = ctx.tx[~sfEPrice];
274 ePrice && ePrice->issue() != lpTokens.issue())
275 {
276 JLOG(ctx.j.debug()) << "AMM Withdraw: invalid EPrice.";
277 return temBAD_AMM_TOKENS;
278 }
279
280 if (ctx.tx.getFlags() & (tfLPToken | tfWithdrawAll))
281 {
282 if (auto const ter = checkAmount(amountBalance, amountBalance))
283 return ter;
284 if (auto const ter = checkAmount(amount2Balance, amount2Balance))
285 return ter;
286 }
287
288 return tesSUCCESS;
289}
290
293{
294 auto const amount = ctx_.tx[~sfAmount];
295 auto const amount2 = ctx_.tx[~sfAmount2];
296 auto const ePrice = ctx_.tx[~sfEPrice];
297 auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
298 if (!ammSle)
299 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
300 auto const ammAccountID = (*ammSle)[sfAccount];
301 auto const accountSle = sb.read(keylet::account(ammAccountID));
302 if (!accountSle)
303 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
304 auto const lpTokens =
305 ammLPHolds(ctx_.view(), *ammSle, ctx_.tx[sfAccount], ctx_.journal);
306 auto const lpTokensWithdraw =
307 tokensWithdraw(lpTokens, ctx_.tx[~sfLPTokenIn], ctx_.tx.getFlags());
308
309 // Due to rounding, the LPTokenBalance of the last LP
310 // might not match the LP's trustline balance
311 if (sb.rules().enabled(fixAMMv1_1))
312 {
313 if (auto const res =
314 isOnlyLiquidityProvider(sb, lpTokens.issue(), account_);
315 !res)
316 return {res.error(), false};
317 else if (res.value())
318 {
320 lpTokens,
321 ammSle->getFieldAmount(sfLPTokenBalance),
322 Number{1, -3}))
323 {
324 ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
325 sb.update(ammSle);
326 }
327 else
328 {
329 return {tecAMM_INVALID_TOKENS, false};
330 }
331 }
332 }
333
334 auto const tfee = getTradingFee(ctx_.view(), *ammSle, account_);
335
336 auto const expected = ammHolds(
337 sb,
338 *ammSle,
339 amount ? amount->issue() : std::optional<Issue>{},
340 amount2 ? amount2->issue() : std::optional<Issue>{},
342 ctx_.journal);
343 if (!expected)
344 return {expected.error(), false};
345 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
346
347 auto const subTxType = ctx_.tx.getFlags() & tfWithdrawSubTx;
348
349 auto const [result, newLPTokenBalance] =
350 [&,
351 &amountBalance = amountBalance,
352 &amount2Balance = amount2Balance,
353 &lptAMMBalance = lptAMMBalance]() -> std::pair<TER, STAmount> {
354 if (subTxType & tfTwoAsset)
355 return equalWithdrawLimit(
356 sb,
357 *ammSle,
358 ammAccountID,
359 amountBalance,
360 amount2Balance,
361 lptAMMBalance,
362 *amount,
363 *amount2,
364 tfee);
365 if (subTxType & tfOneAssetLPToken || subTxType & tfOneAssetWithdrawAll)
367 sb,
368 *ammSle,
369 ammAccountID,
370 amountBalance,
371 lptAMMBalance,
372 *amount,
373 *lpTokensWithdraw,
374 tfee);
375 if (subTxType & tfLimitLPToken)
377 sb,
378 *ammSle,
379 ammAccountID,
380 amountBalance,
381 lptAMMBalance,
382 *amount,
383 *ePrice,
384 tfee);
385 if (subTxType & tfSingleAsset)
386 return singleWithdraw(
387 sb,
388 *ammSle,
389 ammAccountID,
390 amountBalance,
391 lptAMMBalance,
392 *amount,
393 tfee);
394 if (subTxType & tfLPToken || subTxType & tfWithdrawAll)
395 {
396 return equalWithdrawTokens(
397 sb,
398 *ammSle,
399 ammAccountID,
400 amountBalance,
401 amount2Balance,
402 lptAMMBalance,
403 lpTokens,
404 *lpTokensWithdraw,
405 tfee);
406 }
407 // should not happen.
408 // LCOV_EXCL_START
409 JLOG(j_.error()) << "AMM Withdraw: invalid options.";
411 // LCOV_EXCL_STOP
412 }();
413
414 if (result != tesSUCCESS)
415 return {result, false};
416
417 auto const res = deleteAMMAccountIfEmpty(
418 sb,
419 ammSle,
420 newLPTokenBalance,
421 ctx_.tx[sfAsset].get<Issue>(),
422 ctx_.tx[sfAsset2].get<Issue>(),
423 j_);
424 // LCOV_EXCL_START
425 if (!res.second)
426 return {res.first, false};
427 // LCOV_EXCL_STOP
428
429 JLOG(ctx_.journal.trace())
430 << "AMM Withdraw: tokens " << to_string(newLPTokenBalance.iou()) << " "
431 << to_string(lpTokens.iou()) << " " << to_string(lptAMMBalance.iou());
432
433 return {tesSUCCESS, true};
434}
435
436TER
438{
439 // This is the ledger view that we work against. Transactions are applied
440 // as we go on processing transactions.
441 Sandbox sb(&ctx_.view());
442
443 auto const result = applyGuts(sb);
444 if (result.second)
445 sb.apply(ctx_.rawView());
446
447 return result.first;
448}
449
452 Sandbox& view,
453 SLE const& ammSle,
454 AccountID const& ammAccount,
455 STAmount const& amountBalance,
456 STAmount const& amountWithdraw,
457 std::optional<STAmount> const& amount2Withdraw,
458 STAmount const& lpTokensAMMBalance,
459 STAmount const& lpTokensWithdraw,
460 std::uint16_t tfee)
461{
462 TER ter;
463 STAmount newLPTokenBalance;
464 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = withdraw(
465 view,
466 ammSle,
467 ammAccount,
468 account_,
469 amountBalance,
470 amountWithdraw,
471 amount2Withdraw,
472 lpTokensAMMBalance,
473 lpTokensWithdraw,
474 tfee,
478 j_);
479 return {ter, newLPTokenBalance};
480}
481
484 Sandbox& view,
485 SLE const& ammSle,
486 AccountID const& ammAccount,
487 AccountID const& account,
488 STAmount const& amountBalance,
489 STAmount const& amountWithdraw,
490 std::optional<STAmount> const& amount2Withdraw,
491 STAmount const& lpTokensAMMBalance,
492 STAmount const& lpTokensWithdraw,
493 std::uint16_t tfee,
494 FreezeHandling freezeHandling,
495 WithdrawAll withdrawAll,
496 XRPAmount const& priorBalance,
497 beast::Journal const& journal)
498{
499 auto const lpTokens = ammLPHolds(view, ammSle, account, journal);
500 auto const expected = ammHolds(
501 view,
502 ammSle,
503 amountWithdraw.issue(),
504 std::nullopt,
505 freezeHandling,
506 journal);
507 // LCOV_EXCL_START
508 if (!expected)
509 return {expected.error(), STAmount{}, STAmount{}, STAmount{}};
510 // LCOV_EXCL_STOP
511 auto const [curBalance, curBalance2, _] = *expected;
512 (void)_;
513
514 auto const
515 [amountWithdrawActual, amount2WithdrawActual, lpTokensWithdrawActual] =
517 if (withdrawAll == WithdrawAll::No)
519 amountBalance,
520 amountWithdraw,
521 amount2Withdraw,
522 lpTokensAMMBalance,
523 lpTokensWithdraw,
524 tfee,
525 false);
526 return std::make_tuple(
527 amountWithdraw, amount2Withdraw, lpTokensWithdraw);
528 }();
529
530 if (lpTokensWithdrawActual <= beast::zero ||
531 lpTokensWithdrawActual > lpTokens)
532 {
533 JLOG(journal.debug())
534 << "AMM Withdraw: failed to withdraw, invalid LP tokens: "
535 << lpTokensWithdrawActual << " " << lpTokens << " "
536 << lpTokensAMMBalance;
538 }
539
540 // Should not happen since the only LP on last withdraw
541 // has the balance set to the lp token trustline balance.
542 if (view.rules().enabled(fixAMMv1_1) &&
543 lpTokensWithdrawActual > lpTokensAMMBalance)
544 {
545 // LCOV_EXCL_START
546 JLOG(journal.debug())
547 << "AMM Withdraw: failed to withdraw, unexpected LP tokens: "
548 << lpTokensWithdrawActual << " " << lpTokens << " "
549 << lpTokensAMMBalance;
550 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
551 // LCOV_EXCL_STOP
552 }
553
554 // Withdrawing one side of the pool
555 if ((amountWithdrawActual == curBalance &&
556 amount2WithdrawActual != curBalance2) ||
557 (amount2WithdrawActual == curBalance2 &&
558 amountWithdrawActual != curBalance))
559 {
560 JLOG(journal.debug())
561 << "AMM Withdraw: failed to withdraw one side of the pool "
562 << " curBalance: " << curBalance << " " << amountWithdrawActual
563 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
564 << lpTokensAMMBalance;
565 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
566 }
567
568 // May happen if withdrawing an amount close to one side of the pool
569 if (lpTokensWithdrawActual == lpTokensAMMBalance &&
570 (amountWithdrawActual != curBalance ||
571 amount2WithdrawActual != curBalance2))
572 {
573 JLOG(journal.debug())
574 << "AMM Withdraw: failed to withdraw all tokens "
575 << " curBalance: " << curBalance << " " << amountWithdrawActual
576 << " curBalance2: " << amount2WithdrawActual.value_or(STAmount{0})
577 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
578 << lpTokensAMMBalance;
579 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
580 }
581
582 // Withdrawing more than the pool's balance
583 if (amountWithdrawActual > curBalance ||
584 amount2WithdrawActual > curBalance2)
585 {
586 JLOG(journal.debug())
587 << "AMM Withdraw: withdrawing more than the pool's balance "
588 << " curBalance: " << curBalance << " " << amountWithdrawActual
589 << " curBalance2: " << curBalance2 << " "
590 << (amount2WithdrawActual ? *amount2WithdrawActual : STAmount{})
591 << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance "
592 << lpTokensAMMBalance;
593 return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}};
594 }
595
596 // Check the reserve in case a trustline has to be created
597 bool const enabledFixAMMv1_2 = view.rules().enabled(fixAMMv1_2);
598 auto sufficientReserve = [&](Issue const& issue) -> TER {
599 if (!enabledFixAMMv1_2 || isXRP(issue))
600 return tesSUCCESS;
601 if (!view.exists(keylet::line(account, issue)))
602 {
603 auto const sleAccount = view.read(keylet::account(account));
604 if (!sleAccount)
605 return tecINTERNAL; // LCOV_EXCL_LINE
606 auto const balance = (*sleAccount)[sfBalance].xrp();
607 std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount);
608
609 // See also SetTrust::doApply()
610 XRPAmount const reserve(
611 (ownerCount < 2) ? XRPAmount(beast::zero)
613
614 if (std::max(priorBalance, balance) < reserve)
616 }
617 return tesSUCCESS;
618 };
619
620 if (auto const err = sufficientReserve(amountWithdrawActual.issue()))
621 return {err, STAmount{}, STAmount{}, STAmount{}};
622
623 // Withdraw amountWithdraw
624 auto res = accountSend(
625 view,
626 ammAccount,
627 account,
628 amountWithdrawActual,
629 journal,
631 if (res != tesSUCCESS)
632 {
633 // LCOV_EXCL_START
634 JLOG(journal.debug())
635 << "AMM Withdraw: failed to withdraw " << amountWithdrawActual;
636 return {res, STAmount{}, STAmount{}, STAmount{}};
637 // LCOV_EXCL_STOP
638 }
639
640 // Withdraw amount2Withdraw
641 if (amount2WithdrawActual)
642 {
643 if (auto const err = sufficientReserve(amount2WithdrawActual->issue());
644 err != tesSUCCESS)
645 return {err, STAmount{}, STAmount{}, STAmount{}};
646
647 res = accountSend(
648 view,
649 ammAccount,
650 account,
651 *amount2WithdrawActual,
652 journal,
654 if (res != tesSUCCESS)
655 {
656 // LCOV_EXCL_START
657 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw "
658 << *amount2WithdrawActual;
659 return {res, STAmount{}, STAmount{}, STAmount{}};
660 // LCOV_EXCL_STOP
661 }
662 }
663
664 // Withdraw LP tokens
665 res = redeemIOU(
666 view,
667 account,
668 lpTokensWithdrawActual,
669 lpTokensWithdrawActual.issue(),
670 journal);
671 if (res != tesSUCCESS)
672 {
673 // LCOV_EXCL_START
674 JLOG(journal.debug()) << "AMM Withdraw: failed to withdraw LPTokens";
675 return {res, STAmount{}, STAmount{}, STAmount{}};
676 // LCOV_EXCL_STOP
677 }
678
679 return std::make_tuple(
681 lpTokensAMMBalance - lpTokensWithdrawActual,
682 amountWithdrawActual,
683 amount2WithdrawActual);
684}
685
688 Sandbox& view,
689 SLE const& ammSle,
690 AccountID const& ammAccount,
691 STAmount const& amountBalance,
692 STAmount const& amount2Balance,
693 STAmount const& lptAMMBalance,
694 STAmount const& lpTokens,
695 STAmount const& lpTokensWithdraw,
696 std::uint16_t tfee)
697{
698 TER ter;
699 STAmount newLPTokenBalance;
700 std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) =
702 view,
703 ammSle,
704 account_,
705 ammAccount,
706 amountBalance,
707 amount2Balance,
708 lptAMMBalance,
709 lpTokens,
710 lpTokensWithdraw,
711 tfee,
715 ctx_.journal);
716 return {ter, newLPTokenBalance};
717}
718
721 Sandbox& sb,
722 std::shared_ptr<SLE> const ammSle,
723 STAmount const& lpTokenBalance,
724 Issue const& issue1,
725 Issue const& issue2,
726 beast::Journal const& journal)
727{
728 TER ter;
729 bool updateBalance = true;
730 if (lpTokenBalance == beast::zero)
731 {
732 ter = deleteAMMAccount(sb, issue1, issue2, journal);
733 if (ter != tesSUCCESS && ter != tecINCOMPLETE)
734 return {ter, false}; // LCOV_EXCL_LINE
735 else
736 updateBalance = (ter == tecINCOMPLETE);
737 }
738
739 if (updateBalance)
740 {
741 ammSle->setFieldAmount(sfLPTokenBalance, lpTokenBalance);
742 sb.update(ammSle);
743 }
744
745 return {ter, true};
746}
747
752 Sandbox& view,
753 SLE const& ammSle,
754 AccountID const account,
755 AccountID const& ammAccount,
756 STAmount const& amountBalance,
757 STAmount const& amount2Balance,
758 STAmount const& lptAMMBalance,
759 STAmount const& lpTokens,
760 STAmount const& lpTokensWithdraw,
761 std::uint16_t tfee,
762 FreezeHandling freezeHanding,
763 WithdrawAll withdrawAll,
764 XRPAmount const& priorBalance,
765 beast::Journal const& journal)
766{
767 try
768 {
769 // Withdrawing all tokens in the pool
770 if (lpTokensWithdraw == lptAMMBalance)
771 {
772 return withdraw(
773 view,
774 ammSle,
775 ammAccount,
776 account,
777 amountBalance,
778 amountBalance,
779 amount2Balance,
780 lptAMMBalance,
781 lpTokensWithdraw,
782 tfee,
783 freezeHanding,
785 priorBalance,
786 journal);
787 }
788
789 auto const frac = divide(lpTokensWithdraw, lptAMMBalance, noIssue());
790 auto const withdrawAmount =
791 multiply(amountBalance, frac, amountBalance.issue());
792 auto const withdraw2Amount =
793 multiply(amount2Balance, frac, amount2Balance.issue());
794 // LP is making equal withdrawal by tokens but the requested amount
795 // of LP tokens is likely too small and results in one-sided pool
796 // withdrawal due to round off. Fail so the user withdraws
797 // more tokens.
798 if (withdrawAmount == beast::zero || withdraw2Amount == beast::zero)
799 return {tecAMM_FAILED, STAmount{}, STAmount{}, STAmount{}};
800
801 return withdraw(
802 view,
803 ammSle,
804 ammAccount,
805 account,
806 amountBalance,
807 withdrawAmount,
808 withdraw2Amount,
809 lptAMMBalance,
810 lpTokensWithdraw,
811 tfee,
812 freezeHanding,
813 withdrawAll,
814 priorBalance,
815 journal);
816 }
817 // LCOV_EXCL_START
818 catch (std::exception const& e)
819 {
820 JLOG(journal.error())
821 << "AMMWithdraw::equalWithdrawTokens exception " << e.what();
822 }
823 return {tecINTERNAL, STAmount{}, STAmount{}, STAmount{}};
824 // LCOV_EXCL_STOP
825}
826
854 Sandbox& view,
855 SLE const& ammSle,
856 AccountID const& ammAccount,
857 STAmount const& amountBalance,
858 STAmount const& amount2Balance,
859 STAmount const& lptAMMBalance,
860 STAmount const& amount,
861 STAmount const& amount2,
862 std::uint16_t tfee)
863{
864 auto frac = Number{amount} / amountBalance;
865 auto const amount2Withdraw = amount2Balance * frac;
866 if (amount2Withdraw <= amount2)
867 {
868 return withdraw(
869 view,
870 ammSle,
871 ammAccount,
872 amountBalance,
873 amount,
874 toSTAmount(amount2.issue(), amount2Withdraw),
875 lptAMMBalance,
876 toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac),
877 tfee);
878 }
879
880 frac = Number{amount2} / amount2Balance;
881 auto const amountWithdraw = amountBalance * frac;
882 XRPL_ASSERT(
883 amountWithdraw <= amount,
884 "ripple::AMMWithdraw::equalWithdrawLimit : maximum amountWithdraw");
885 return withdraw(
886 view,
887 ammSle,
888 ammAccount,
889 amountBalance,
890 toSTAmount(amount.issue(), amountWithdraw),
891 amount2,
892 lptAMMBalance,
893 toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac),
894 tfee);
895}
896
904 Sandbox& view,
905 SLE const& ammSle,
906 AccountID const& ammAccount,
907 STAmount const& amountBalance,
908 STAmount const& lptAMMBalance,
909 STAmount const& amount,
910 std::uint16_t tfee)
911{
912 auto const tokens = lpTokensOut(amountBalance, amount, lptAMMBalance, tfee);
913 if (tokens == beast::zero)
914 return {tecAMM_FAILED, STAmount{}};
915
916 return withdraw(
917 view,
918 ammSle,
919 ammAccount,
920 amountBalance,
921 amount,
922 std::nullopt,
923 lptAMMBalance,
924 tokens,
925 tfee);
926}
927
940 Sandbox& view,
941 SLE const& ammSle,
942 AccountID const& ammAccount,
943 STAmount const& amountBalance,
944 STAmount const& lptAMMBalance,
945 STAmount const& amount,
946 STAmount const& lpTokensWithdraw,
947 std::uint16_t tfee)
948{
949 auto const amountWithdraw =
950 withdrawByTokens(amountBalance, lptAMMBalance, lpTokensWithdraw, tfee);
951 if (amount == beast::zero || amountWithdraw >= amount)
952 {
953 return withdraw(
954 view,
955 ammSle,
956 ammAccount,
957 amountBalance,
958 amountWithdraw,
959 std::nullopt,
960 lptAMMBalance,
961 lpTokensWithdraw,
962 tfee);
963 }
964
965 return {tecAMM_FAILED, STAmount{}};
966}
967
989 Sandbox& view,
990 SLE const& ammSle,
991 AccountID const& ammAccount,
992 STAmount const& amountBalance,
993 STAmount const& lptAMMBalance,
994 STAmount const& amount,
995 STAmount const& ePrice,
996 std::uint16_t tfee)
997{
998 // LPTokens is asset in => E = t / a and formula (8) is:
999 // a = A*(t1**2 + t1*(f - 2))/(t1*f - 1)
1000 // substitute a as t/E =>
1001 // t/E = A*(t1**2 + t1*(f - 2))/(t1*f - 1), t1=t/T => t = t1*T
1002 // t1*T/E = A*((t/T)**2 + t*(f - 2)/T)/(t*f/T - 1) =>
1003 // T/E = A*(t1 + f-2)/(t1*f - 1) =>
1004 // T*(t1*f - 1) = A*E*(t1 + f - 2) =>
1005 // t1*T*f - T = t1*A*E + A*E*(f - 2) =>
1006 // t1*(T*f - A*E) = T + A*E*(f - 2) =>
1007 // t = T*(T + A*E*(f - 2))/(T*f - A*E)
1008 Number const ae = amountBalance * ePrice;
1009 auto const f = getFee(tfee);
1010 auto const tokens = lptAMMBalance * (lptAMMBalance + ae * (f - 2)) /
1011 (lptAMMBalance * f - ae);
1012 if (tokens <= 0)
1013 return {tecAMM_FAILED, STAmount{}};
1014 auto const amountWithdraw = toSTAmount(amount.issue(), tokens / ePrice);
1015 if (amount == beast::zero || amountWithdraw >= amount)
1016 {
1017 return withdraw(
1018 view,
1019 ammSle,
1020 ammAccount,
1021 amountBalance,
1022 amountWithdraw,
1023 std::nullopt,
1024 lptAMMBalance,
1025 toSTAmount(lptAMMBalance.issue(), tokens),
1026 tfee);
1027 }
1028
1029 return {tecAMM_FAILED, STAmount{}};
1030}
1031
1034{
1035 if (tx[sfFlags] & (tfWithdrawAll | tfOneAssetWithdrawAll))
1036 return WithdrawAll::Yes;
1037 return WithdrawAll::No;
1038}
1039} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:60
Stream error() const
Definition: Journal.h:346
Stream debug() const
Definition: Journal.h:328
Stream trace() const
Severity stream access functions.
Definition: Journal.h:322
WithdrawAll isWithdrawAll(STTx const &tx)
Check from the flags if it's withdraw all.
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.
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::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...
std::pair< TER, bool > applyGuts(Sandbox &view)
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 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)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
Definition: AMMWithdraw.cpp:32
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 > 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.
RawView & rawView()
Definition: ApplyContext.h:91
ApplyView & view()
Definition: ApplyContext.h:78
beast::Journal const journal
Definition: ApplyContext.h:75
A currency issued by an account.
Definition: Issue.h:36
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
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 Rules const & rules() const =0
Returns the tx processing rules.
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
std::uint32_t getFlags() const
Definition: STObject.cpp:537
Discardable, editable view to a ledger.
Definition: Sandbox.h:35
void apply(RawView &to)
Definition: Sandbox.h:55
AccountID const account_
Definition: Transactor.h:143
ApplyView & view()
Definition: Transactor.h:159
beast::Journal const j_
Definition: Transactor.h:141
XRPAmount mPriorBalance
Definition: Transactor.h:144
ApplyContext & ctx_
Definition: Transactor.h:140
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
Rules const & rules() const override
Returns the tx processing rules.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
A balance matches.
Definition: balance.h:39
Match set account flags.
Definition: flags.h:125
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: ter.h:35
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:439
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:237
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:177
std::uint32_t ownerCount(Env const &env, Account const &account)
Definition: TestHelpers.cpp:54
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
constexpr std::uint32_t tfSingleAsset
Definition: TxFlags.h:217
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition: AMMCore.cpp:95
STAmount divide(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:93
constexpr std::uint32_t tfOneAssetWithdrawAll
Definition: TxFlags.h:216
WithdrawAll
AMMWithdraw implements AMM withdraw Transactor.
Definition: AMMWithdraw.h:67
FreezeHandling
Controls the treatment of frozen account balances.
Definition: View.h:78
@ fhZERO_IF_FROZEN
Definition: View.h:78
@ fhIGNORE_FREEZE
Definition: View.h:78
bool isXRP(AccountID const &c)
Definition: AccountID.h:91
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition: View.cpp:216
std::uint16_t getTradingFee(ReadView const &view, SLE const &ammSle, AccountID const &account)
Get AMM trading fee for the given account.
Definition: AMMUtils.cpp:178
constexpr std::uint32_t tfWithdrawMask
Definition: TxFlags.h:228
TER deleteAMMAccount(Sandbox &view, Issue const &asset, Issue const &asset2, beast::Journal j)
Delete trustlines to AMM.
Definition: AMMUtils.cpp:282
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account)
Check if the account lacks required authorization.
Definition: View.cpp:2268
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition: View.cpp:2152
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
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
constexpr std::uint32_t tfLimitLPToken
Definition: TxFlags.h:220
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:53
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition: AMMCore.cpp:129
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp: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, bool isDeposit)
Calls adjustLPTokens() and adjusts deposit or withdraw amounts if the adjusted LP tokens are less tha...
Definition: AMMHelpers.cpp:147
constexpr std::uint32_t tfOneAssetLPToken
Definition: TxFlags.h:219
Expected< bool, TER > isOnlyLiquidityProvider(ReadView const &view, Issue const &ammIssue, AccountID const &lpAccount)
Return true if the Liquidity Provider is the only AMM provider, false otherwise.
Definition: AMMUtils.cpp:386
static std::optional< STAmount > tokensWithdraw(STAmount const &lpTokens, std::optional< STAmount > const &tokensIn, std::uint32_t flags)
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition: View.cpp:250
constexpr std::uint32_t tfTwoAsset
Definition: TxFlags.h:218
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:112
constexpr std::uint32_t tfWithdrawAll
Definition: TxFlags.h:215
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:160
Issue const & noIssue()
Returns an asset specifier that represents no account and currency.
Definition: Issue.h:126
@ tecINCOMPLETE
Definition: TER.h:335
@ tecFROZEN
Definition: TER.h:303
@ tecAMM_EMPTY
Definition: TER.h:332
@ tecINTERNAL
Definition: TER.h:310
@ tecAMM_FAILED
Definition: TER.h:330
@ tecAMM_INVALID_TOKENS
Definition: TER.h:331
@ tecAMM_BALANCE
Definition: TER.h:329
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:307
constexpr std::uint32_t tfLPToken
Definition: TxFlags.h:214
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
Definition: AMMCore.h:101
@ tesSUCCESS
Definition: TER.h:244
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt)
Definition: AMMCore.cpp:80
bool isTesSuccess(TER x) noexcept
Definition: TER.h:672
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:46
constexpr std::uint32_t tfWithdrawSubTx
Definition: TxFlags.h:222
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
@ terNO_AMM
Definition: TER.h:227
TERSubset< CanCvtToTER > TER
Definition: TER.h:643
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
TER accountSend(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Calls static accountSendIOU if saAmount represents Issue.
Definition: View.cpp:1978
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:603
@ temMALFORMED
Definition: TER.h:87
@ temBAD_AMM_TOKENS
Definition: TER.h:129
@ temINVALID_FLAG
Definition: TER.h:111
@ temDISABLED
Definition: TER.h:114
T popcount(T... args)
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
Definition: protocol/Fees.h:49
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:79
ReadView const & view
Definition: Transactor.h:82
beast::Journal const j
Definition: Transactor.h:87
State information when preflighting a tx.
Definition: Transactor.h:34
beast::Journal const j
Definition: Transactor.h:41
T tie(T... args)
T what(T... args)