rippled
Loading...
Searching...
No Matches
AMMDeposit.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/AMMDeposit.h>
23
24#include <xrpl/ledger/Sandbox.h>
25#include <xrpl/ledger/View.h>
26#include <xrpl/protocol/AMMCore.h>
27#include <xrpl/protocol/Feature.h>
28#include <xrpl/protocol/TxFlags.h>
29
30namespace ripple {
31
32bool
37
44
47{
48 auto const flags = ctx.tx.getFlags();
49
50 auto const amount = ctx.tx[~sfAmount];
51 auto const amount2 = ctx.tx[~sfAmount2];
52 auto const ePrice = ctx.tx[~sfEPrice];
53 auto const lpTokens = ctx.tx[~sfLPTokenOut];
54 auto const tradingFee = ctx.tx[~sfTradingFee];
55 // Valid options for the flags are:
56 // tfLPTokens: LPTokenOut, [Amount, Amount2]
57 // tfSingleAsset: Amount, [LPTokenOut]
58 // tfTwoAsset: Amount, Amount2, [LPTokenOut]
59 // tfTwoAssetIfEmpty: Amount, Amount2, [sfTradingFee]
60 // tfOnAssetLPToken: Amount and LPTokenOut
61 // tfLimitLPToken: Amount and EPrice
62 if (std::popcount(flags & tfDepositSubTx) != 1)
63 {
64 JLOG(ctx.j.debug()) << "AMM Deposit: invalid flags.";
65 return temMALFORMED;
66 }
67 if (flags & tfLPToken)
68 {
69 // if included then both amount and amount2 are deposit min
70 if (!lpTokens || ePrice || (amount && !amount2) ||
71 (!amount && amount2) || tradingFee)
72 return temMALFORMED;
73 }
74 else if (flags & tfSingleAsset)
75 {
76 // if included then lpTokens is deposit min
77 if (!amount || amount2 || ePrice || tradingFee)
78 return temMALFORMED;
79 }
80 else if (flags & tfTwoAsset)
81 {
82 // if included then lpTokens is deposit min
83 if (!amount || !amount2 || ePrice || tradingFee)
84 return temMALFORMED;
85 }
86 else if (flags & tfOneAssetLPToken)
87 {
88 if (!amount || !lpTokens || amount2 || ePrice || tradingFee)
89 return temMALFORMED;
90 }
91 else if (flags & tfLimitLPToken)
92 {
93 if (!amount || !ePrice || lpTokens || amount2 || tradingFee)
94 return temMALFORMED;
95 }
96 else if (flags & tfTwoAssetIfEmpty)
97 {
98 if (!amount || !amount2 || ePrice || lpTokens)
99 return temMALFORMED;
100 }
101
102 auto const asset = ctx.tx[sfAsset].get<Issue>();
103 auto const asset2 = ctx.tx[sfAsset2].get<Issue>();
104 if (auto const res = invalidAMMAssetPair(asset, asset2))
105 {
106 JLOG(ctx.j.debug()) << "AMM Deposit: invalid asset pair.";
107 return res;
108 }
109
110 if (amount && amount2 && amount->issue() == amount2->issue())
111 {
112 JLOG(ctx.j.debug()) << "AMM Deposit: invalid tokens, same issue."
113 << amount->issue() << " " << amount2->issue();
114 return temBAD_AMM_TOKENS;
115 }
116
117 if (lpTokens && *lpTokens <= beast::zero)
118 {
119 JLOG(ctx.j.debug()) << "AMM Deposit: invalid LPTokens";
120 return temBAD_AMM_TOKENS;
121 }
122
123 if (amount)
124 {
125 if (auto const res = invalidAMMAmount(
126 *amount,
127 std::make_optional(std::make_pair(asset, asset2)),
128 ePrice.has_value()))
129 {
130 JLOG(ctx.j.debug()) << "AMM Deposit: invalid amount";
131 return res;
132 }
133 }
134
135 if (amount2)
136 {
137 if (auto const res = invalidAMMAmount(
138 *amount2, std::make_optional(std::make_pair(asset, asset2))))
139 {
140 JLOG(ctx.j.debug()) << "AMM Deposit: invalid amount2";
141 return res;
142 }
143 }
144
145 // must be amount issue
146 if (amount && ePrice)
147 {
148 if (auto const res = invalidAMMAmount(
149 *ePrice,
151 std::make_pair(amount->issue(), amount->issue()))))
152 {
153 JLOG(ctx.j.debug()) << "AMM Deposit: invalid EPrice";
154 return res;
155 }
156 }
157
158 if (tradingFee > TRADING_FEE_THRESHOLD)
159 {
160 JLOG(ctx.j.debug()) << "AMM Deposit: invalid trading fee.";
161 return temBAD_FEE;
162 }
163
164 return tesSUCCESS;
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 Deposit: Invalid asset pair.";
177 return terNO_AMM;
178 }
179
180 auto const expected = ammHolds(
181 ctx.view,
182 *ammSle,
186 ctx.j);
187 if (!expected)
188 return expected.error(); // LCOV_EXCL_LINE
189 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
190 if (ctx.tx.getFlags() & tfTwoAssetIfEmpty)
191 {
192 if (lptAMMBalance != beast::zero)
193 return tecAMM_NOT_EMPTY;
194 if (amountBalance != beast::zero || amount2Balance != beast::zero)
195 {
196 // LCOV_EXCL_START
197 JLOG(ctx.j.debug()) << "AMM Deposit: tokens balance is not zero.";
198 return tecINTERNAL;
199 // LCOV_EXCL_STOP
200 }
201 }
202 else
203 {
204 if (lptAMMBalance == beast::zero)
205 return tecAMM_EMPTY;
206 if (amountBalance <= beast::zero || amount2Balance <= beast::zero ||
207 lptAMMBalance < beast::zero)
208 {
209 // LCOV_EXCL_START
210 JLOG(ctx.j.debug())
211 << "AMM Deposit: reserves or tokens balance is zero.";
212 return tecINTERNAL;
213 // LCOV_EXCL_STOP
214 }
215 }
216
217 // Check account has sufficient funds.
218 // Return tesSUCCESS if it does, error otherwise.
219 // Have to check again in deposit() because
220 // amounts might be derived based on tokens or
221 // limits.
222 auto balance = [&](auto const& deposit) -> TER {
223 if (isXRP(deposit))
224 {
225 auto const lpIssue = (*ammSle)[sfLPTokenBalance].issue();
226 // Adjust the reserve if LP doesn't have LPToken trustline
227 auto const sle = ctx.view.read(
228 keylet::line(accountID, lpIssue.account, lpIssue.currency));
229 if (xrpLiquid(ctx.view, accountID, !sle, ctx.j) >= deposit)
230 return TER(tesSUCCESS);
231 if (sle)
232 return tecUNFUNDED_AMM;
234 }
235 return (accountID == deposit.issue().account ||
237 ctx.view,
238 accountID,
239 deposit.issue(),
241 ctx.j) >= deposit)
242 ? TER(tesSUCCESS)
244 };
245
246 if (ctx.view.rules().enabled(featureAMMClawback))
247 {
248 // Check if either of the assets is frozen, AMMDeposit is not allowed
249 // if either asset is frozen
250 auto checkAsset = [&](Issue const& asset) -> TER {
251 if (auto const ter = requireAuth(ctx.view, asset, accountID))
252 {
253 JLOG(ctx.j.debug())
254 << "AMM Deposit: account is not authorized, " << asset;
255 return ter;
256 }
257
258 if (isFrozen(ctx.view, accountID, asset))
259 {
260 JLOG(ctx.j.debug())
261 << "AMM Deposit: account or currency is frozen, "
262 << to_string(accountID) << " " << to_string(asset.currency);
263
264 return tecFROZEN;
265 }
266
267 return tesSUCCESS;
268 };
269
270 if (auto const ter = checkAsset(ctx.tx[sfAsset].get<Issue>()))
271 return ter;
272
273 if (auto const ter = checkAsset(ctx.tx[sfAsset2].get<Issue>()))
274 return ter;
275 }
276
277 auto const amount = ctx.tx[~sfAmount];
278 auto const amount2 = ctx.tx[~sfAmount2];
279 auto const ammAccountID = ammSle->getAccountID(sfAccount);
280
281 auto checkAmount = [&](std::optional<STAmount> const& amount,
282 bool checkBalance) -> TER {
283 if (amount)
284 {
285 // This normally should not happen.
286 // Account is not authorized to hold the assets it's depositing,
287 // or it doesn't even have a trust line for them
288 if (auto const ter =
289 requireAuth(ctx.view, amount->issue(), accountID))
290 {
291 // LCOV_EXCL_START
292 JLOG(ctx.j.debug())
293 << "AMM Deposit: account is not authorized, "
294 << amount->issue();
295 return ter;
296 // LCOV_EXCL_STOP
297 }
298 // AMM account or currency frozen
299 if (isFrozen(ctx.view, ammAccountID, amount->issue()))
300 {
301 JLOG(ctx.j.debug())
302 << "AMM Deposit: AMM account or currency is frozen, "
303 << to_string(accountID);
304 return tecFROZEN;
305 }
306 // Account frozen
307 if (isIndividualFrozen(ctx.view, accountID, amount->issue()))
308 {
309 JLOG(ctx.j.debug()) << "AMM Deposit: account is frozen, "
310 << to_string(accountID) << " "
311 << to_string(amount->issue().currency);
312 return tecFROZEN;
313 }
314 if (checkBalance)
315 {
316 if (auto const ter = balance(*amount))
317 {
318 JLOG(ctx.j.debug())
319 << "AMM Deposit: account has insufficient funds, "
320 << *amount;
321 return ter;
322 }
323 }
324 }
325 return tesSUCCESS;
326 };
327
328 // amount and amount2 are deposit min in case of tfLPToken
329 if (!(ctx.tx.getFlags() & tfLPToken))
330 {
331 if (auto const ter = checkAmount(amount, true))
332 return ter;
333
334 if (auto const ter = checkAmount(amount2, true))
335 return ter;
336 }
337 else
338 {
339 if (auto const ter = checkAmount(amountBalance, false))
340 return ter;
341 if (auto const ter = checkAmount(amount2Balance, false))
342 return ter;
343 }
344
345 // Equal deposit lp tokens
346 if (auto const lpTokens = ctx.tx[~sfLPTokenOut];
347 lpTokens && lpTokens->issue() != lptAMMBalance.issue())
348 {
349 JLOG(ctx.j.debug()) << "AMM Deposit: invalid LPTokens.";
350 return temBAD_AMM_TOKENS;
351 }
352
353 // Check the reserve for LPToken trustline if not LP.
354 // We checked above but need to check again if depositing IOU only.
355 if (ammLPHolds(ctx.view, *ammSle, accountID, ctx.j) == beast::zero)
356 {
357 STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j);
358 // Insufficient reserve
359 if (xrpBalance <= beast::zero)
360 {
361 JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
363 }
364 }
365
366 return tesSUCCESS;
367}
368
371{
372 auto const amount = ctx_.tx[~sfAmount];
373 auto const amount2 = ctx_.tx[~sfAmount2];
374 auto const ePrice = ctx_.tx[~sfEPrice];
375 auto const lpTokensDeposit = ctx_.tx[~sfLPTokenOut];
376 auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
377 if (!ammSle)
378 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
379 auto const ammAccountID = (*ammSle)[sfAccount];
380
381 auto const expected = ammHolds(
382 sb,
383 *ammSle,
384 amount ? amount->issue() : std::optional<Issue>{},
385 amount2 ? amount2->issue() : std::optional<Issue>{},
387 ctx_.journal);
388 if (!expected)
389 return {expected.error(), false}; // LCOV_EXCL_LINE
390 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
391 auto const tfee = (lptAMMBalance == beast::zero)
392 ? ctx_.tx[~sfTradingFee].value_or(0)
393 : getTradingFee(ctx_.view(), *ammSle, account_);
394
395 auto const subTxType = ctx_.tx.getFlags() & tfDepositSubTx;
396
397 auto const [result, newLPTokenBalance] =
398 [&,
399 &amountBalance = amountBalance,
400 &amount2Balance = amount2Balance,
401 &lptAMMBalance = lptAMMBalance]() -> std::pair<TER, STAmount> {
402 if (subTxType & tfTwoAsset)
403 return equalDepositLimit(
404 sb,
405 ammAccountID,
406 amountBalance,
407 amount2Balance,
408 lptAMMBalance,
409 *amount,
410 *amount2,
411 lpTokensDeposit,
412 tfee);
413 if (subTxType & tfOneAssetLPToken)
414 return singleDepositTokens(
415 sb,
416 ammAccountID,
417 amountBalance,
418 *amount,
419 lptAMMBalance,
420 *lpTokensDeposit,
421 tfee);
422 if (subTxType & tfLimitLPToken)
423 return singleDepositEPrice(
424 sb,
425 ammAccountID,
426 amountBalance,
427 *amount,
428 lptAMMBalance,
429 *ePrice,
430 tfee);
431 if (subTxType & tfSingleAsset)
432 return singleDeposit(
433 sb,
434 ammAccountID,
435 amountBalance,
436 lptAMMBalance,
437 *amount,
438 lpTokensDeposit,
439 tfee);
440 if (subTxType & tfLPToken)
441 return equalDepositTokens(
442 sb,
443 ammAccountID,
444 amountBalance,
445 amount2Balance,
446 lptAMMBalance,
447 *lpTokensDeposit,
448 amount,
449 amount2,
450 tfee);
451 if (subTxType & tfTwoAssetIfEmpty)
453 sb,
454 ammAccountID,
455 *amount,
456 *amount2,
457 lptAMMBalance.issue(),
458 tfee);
459 // should not happen.
460 // LCOV_EXCL_START
461 JLOG(j_.error()) << "AMM Deposit: invalid options.";
463 // LCOV_EXCL_STOP
464 }();
465
466 if (result == tesSUCCESS)
467 {
468 XRPL_ASSERT(
469 newLPTokenBalance > beast::zero,
470 "ripple::AMMDeposit::applyGuts : valid new LP token balance");
471 ammSle->setFieldAmount(sfLPTokenBalance, newLPTokenBalance);
472 // LP depositing into AMM empty state gets the auction slot
473 // and the voting
474 if (lptAMMBalance == beast::zero)
476 sb, ammSle, account_, lptAMMBalance.issue(), tfee);
477
478 sb.update(ammSle);
479 }
480
481 return {result, result == tesSUCCESS};
482}
483
484TER
486{
487 // This is the ledger view that we work against. Transactions are applied
488 // as we go on processing transactions.
489 Sandbox sb(&ctx_.view());
490
491 auto const result = applyGuts(sb);
492 if (result.second)
493 sb.apply(ctx_.rawView());
494
495 return result.first;
496}
497
500 Sandbox& view,
501 AccountID const& ammAccount,
502 STAmount const& amountBalance,
503 STAmount const& amountDeposit,
504 std::optional<STAmount> const& amount2Deposit,
505 STAmount const& lptAMMBalance,
506 STAmount const& lpTokensDeposit,
507 std::optional<STAmount> const& depositMin,
508 std::optional<STAmount> const& deposit2Min,
509 std::optional<STAmount> const& lpTokensDepositMin,
510 std::uint16_t tfee)
511{
512 // Check account has sufficient funds.
513 // Return true if it does, false otherwise.
514 auto checkBalance = [&](auto const& depositAmount) -> TER {
515 if (depositAmount <= beast::zero)
516 return temBAD_AMOUNT;
517 if (isXRP(depositAmount))
518 {
519 auto const& lpIssue = lpTokensDeposit.issue();
520 // Adjust the reserve if LP doesn't have LPToken trustline
521 auto const sle = view.read(
522 keylet::line(account_, lpIssue.account, lpIssue.currency));
523 if (xrpLiquid(view, account_, !sle, j_) >= depositAmount)
524 return tesSUCCESS;
525 }
526 else if (
527 account_ == depositAmount.issue().account ||
529 view,
530 account_,
531 depositAmount.issue(),
533 ctx_.journal) >= depositAmount)
534 return tesSUCCESS;
535 return tecUNFUNDED_AMM;
536 };
537
538 auto const
539 [amountDepositActual, amount2DepositActual, lpTokensDepositActual] =
541 amountBalance,
542 amountDeposit,
543 amount2Deposit,
544 lptAMMBalance,
545 lpTokensDeposit,
546 tfee,
548
549 if (lpTokensDepositActual <= beast::zero)
550 {
551 JLOG(ctx_.journal.debug()) << "AMM Deposit: adjusted tokens zero";
553 }
554
555 if (amountDepositActual < depositMin ||
556 amount2DepositActual < deposit2Min ||
557 lpTokensDepositActual < lpTokensDepositMin)
558 {
559 JLOG(ctx_.journal.debug())
560 << "AMM Deposit: min deposit fails " << amountDepositActual << " "
561 << depositMin.value_or(STAmount{}) << " "
562 << amount2DepositActual.value_or(STAmount{}) << " "
563 << deposit2Min.value_or(STAmount{}) << " " << lpTokensDepositActual
564 << " " << lpTokensDepositMin.value_or(STAmount{});
565 return {tecAMM_FAILED, STAmount{}};
566 }
567
568 // Deposit amountDeposit
569 if (auto const ter = checkBalance(amountDepositActual))
570 {
571 JLOG(ctx_.journal.debug()) << "AMM Deposit: account has insufficient "
572 "checkBalance to deposit or is 0"
573 << amountDepositActual;
574 return {ter, STAmount{}};
575 }
576
577 auto res = accountSend(
578 view,
579 account_,
580 ammAccount,
581 amountDepositActual,
584 if (res != tesSUCCESS)
585 {
586 JLOG(ctx_.journal.debug())
587 << "AMM Deposit: failed to deposit " << amountDepositActual;
588 return {res, STAmount{}};
589 }
590
591 // Deposit amount2Deposit
592 if (amount2DepositActual)
593 {
594 if (auto const ter = checkBalance(*amount2DepositActual))
595 {
596 JLOG(ctx_.journal.debug())
597 << "AMM Deposit: account has insufficient checkBalance to "
598 "deposit or is 0 "
599 << *amount2DepositActual;
600 return {ter, STAmount{}};
601 }
602
603 res = accountSend(
604 view,
605 account_,
606 ammAccount,
607 *amount2DepositActual,
610 if (res != tesSUCCESS)
611 {
612 JLOG(ctx_.journal.debug())
613 << "AMM Deposit: failed to deposit " << *amount2DepositActual;
614 return {res, STAmount{}};
615 }
616 }
617
618 // Deposit LP tokens
619 res = accountSend(
620 view, ammAccount, account_, lpTokensDepositActual, ctx_.journal);
621 if (res != tesSUCCESS)
622 {
623 JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit LPTokens";
624 return {res, STAmount{}};
625 }
626
627 return {tesSUCCESS, lptAMMBalance + lpTokensDepositActual};
628}
629
630static STAmount
632 Rules const& rules,
633 STAmount const& lptAMMBalance,
634 STAmount const& lpTokensDeposit)
635{
636 if (!rules.enabled(fixAMMv1_3))
637 return lpTokensDeposit;
638 return adjustLPTokens(lptAMMBalance, lpTokensDeposit, IsDeposit::Yes);
639}
640
646 Sandbox& view,
647 AccountID const& ammAccount,
648 STAmount const& amountBalance,
649 STAmount const& amount2Balance,
650 STAmount const& lptAMMBalance,
651 STAmount const& lpTokensDeposit,
652 std::optional<STAmount> const& depositMin,
653 std::optional<STAmount> const& deposit2Min,
654 std::uint16_t tfee)
655{
656 try
657 {
658 auto const tokensAdj =
659 adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit);
660 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
662 auto const frac =
663 divide(tokensAdj, lptAMMBalance, lptAMMBalance.issue());
664 // amounts factor in the adjusted tokens
665 auto const amountDeposit =
666 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::Yes);
667 auto const amount2Deposit =
668 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::Yes);
669 return deposit(
670 view,
671 ammAccount,
672 amountBalance,
673 amountDeposit,
674 amount2Deposit,
675 lptAMMBalance,
676 tokensAdj,
677 depositMin,
678 deposit2Min,
680 tfee);
681 }
682 catch (std::exception const& e)
683 {
684 // LCOV_EXCL_START
685 JLOG(j_.error()) << "AMMDeposit::equalDepositTokens exception "
686 << e.what();
687 return {tecINTERNAL, STAmount{}};
688 // LCOV_EXCL_STOP
689 }
690}
691
722 Sandbox& view,
723 AccountID const& ammAccount,
724 STAmount const& amountBalance,
725 STAmount const& amount2Balance,
726 STAmount const& lptAMMBalance,
727 STAmount const& amount,
728 STAmount const& amount2,
729 std::optional<STAmount> const& lpTokensDepositMin,
730 std::uint16_t tfee)
731{
732 auto frac = Number{amount} / amountBalance;
733 auto tokensAdj =
734 getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes);
735 if (tokensAdj == beast::zero)
736 {
737 if (!view.rules().enabled(fixAMMv1_3))
738 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
739 else
741 }
742 // factor in the adjusted tokens
743 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
744 auto const amount2Deposit =
745 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::Yes);
746 if (amount2Deposit <= amount2)
747 return deposit(
748 view,
749 ammAccount,
750 amountBalance,
751 amount,
752 amount2Deposit,
753 lptAMMBalance,
754 tokensAdj,
757 lpTokensDepositMin,
758 tfee);
759 frac = Number{amount2} / amount2Balance;
760 tokensAdj =
761 getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes);
762 if (tokensAdj == beast::zero)
763 {
764 if (!view.rules().enabled(fixAMMv1_3))
765 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
766 else
767 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
768 }
769 // factor in the adjusted tokens
770 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
771 auto const amountDeposit =
772 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::Yes);
773 if (amountDeposit <= amount)
774 return deposit(
775 view,
776 ammAccount,
777 amountBalance,
778 amountDeposit,
779 amount2,
780 lptAMMBalance,
781 tokensAdj,
784 lpTokensDepositMin,
785 tfee);
786 return {tecAMM_FAILED, STAmount{}};
787}
788
799 Sandbox& view,
800 AccountID const& ammAccount,
801 STAmount const& amountBalance,
802 STAmount const& lptAMMBalance,
803 STAmount const& amount,
804 std::optional<STAmount> const& lpTokensDepositMin,
805 std::uint16_t tfee)
806{
807 auto const tokens = adjustLPTokensOut(
808 view.rules(),
809 lptAMMBalance,
810 lpTokensOut(amountBalance, amount, lptAMMBalance, tfee));
811 if (tokens == beast::zero)
812 {
813 if (!view.rules().enabled(fixAMMv1_3))
814 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
815 else
817 }
818 // factor in the adjusted tokens
819 auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens(
820 view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
821 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
822 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
823 return deposit(
824 view,
825 ammAccount,
826 amountBalance,
827 amountDepositAdj,
829 lptAMMBalance,
830 tokensAdj,
833 lpTokensDepositMin,
834 tfee);
835}
836
846 Sandbox& view,
847 AccountID const& ammAccount,
848 STAmount const& amountBalance,
849 STAmount const& amount,
850 STAmount const& lptAMMBalance,
851 STAmount const& lpTokensDeposit,
852 std::uint16_t tfee)
853{
854 auto const tokensAdj =
855 adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit);
856 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
858 // the adjusted tokens are factored in
859 auto const amountDeposit =
860 ammAssetIn(amountBalance, lptAMMBalance, tokensAdj, tfee);
861 if (amountDeposit > amount)
862 return {tecAMM_FAILED, STAmount{}};
863 return deposit(
864 view,
865 ammAccount,
866 amountBalance,
867 amountDeposit,
869 lptAMMBalance,
870 tokensAdj,
874 tfee);
875}
876
904 Sandbox& view,
905 AccountID const& ammAccount,
906 STAmount const& amountBalance,
907 STAmount const& amount,
908 STAmount const& lptAMMBalance,
909 STAmount const& ePrice,
910 std::uint16_t tfee)
911{
912 if (amount != beast::zero)
913 {
914 auto const tokens = adjustLPTokensOut(
915 view.rules(),
916 lptAMMBalance,
917 lpTokensOut(amountBalance, amount, lptAMMBalance, tfee));
918 if (tokens <= beast::zero)
919 {
920 if (!view.rules().enabled(fixAMMv1_3))
921 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
922 else
924 }
925 // factor in the adjusted tokens
926 auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens(
927 view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
928 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
929 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
930 auto const ep = Number{amountDepositAdj} / tokensAdj;
931 if (ep <= ePrice)
932 return deposit(
933 view,
934 ammAccount,
935 amountBalance,
936 amountDepositAdj,
938 lptAMMBalance,
939 tokensAdj,
943 tfee);
944 }
945
946 // LPTokens is asset out => E = b / t
947 // substituting t in formula (3) as b/E:
948 // b/E = T * [b/B - sqrt(t2**2 + b/(f1*B)) + t2]/
949 // [1 + sqrt(t2**2 + b/(f1*B)) -t2] (A)
950 // where f1 = 1 - fee, f2 = (1 - fee/2)/f1
951 // Let R = b/(f1*B), then b/B = f1*R and b = R*f1*B
952 // Then (A) is
953 // R*f1*B = E*T*[R*f1 -sqrt(f2**2 + R) + f2]/[1 + sqrt(f2**2 + R) - f2] =>
954 // Let c = f1*B/(E*T) =>
955 // R*c*(1 + sqrt(f2**2 + R) + f2) = R*f1 - sqrt(f2**2 + R) - f2 =>
956 // (R*c + 1)*sqrt(f2**2 + R) = R*(f1 + c*f2 - c) + f2 =>
957 // Let d = f1 + c*f2 - c =>
958 // (R*c + 1)*sqrt(f2**2 + R) = R*d + f2 =>
959 // (R*c + 1)**2 * (f2**2 + R) = (R*d + f2)**2 =>
960 // (R*c)**2 + R*((c*f2)**2 + 2*c - d**2) + 2*c*f2**2 + 1 -2*d*f2 = 0 =>
961 // a1 = c**2, b1 = (c*f2)**2 + 2*c - d**2, c1 = 2*c*f2**2 + 1 - 2*d*f2
962 // R = (-b1 + sqrt(b1**2 + 4*a1*c1))/(2*a1)
963 auto const f1 = feeMult(tfee);
964 auto const f2 = feeMultHalf(tfee) / f1;
965 auto const c = f1 * amountBalance / (ePrice * lptAMMBalance);
966 auto const d = f1 + c * f2 - c;
967 auto const a1 = c * c;
968 auto const b1 = c * c * f2 * f2 + 2 * c - d * d;
969 auto const c1 = 2 * c * f2 * f2 + 1 - 2 * d * f2;
970 auto amtNoRoundCb = [&] {
971 return f1 * amountBalance * solveQuadraticEq(a1, b1, c1);
972 };
973 auto amtProdCb = [&] { return f1 * solveQuadraticEq(a1, b1, c1); };
974 auto const amountDeposit = getRoundedAsset(
975 view.rules(), amtNoRoundCb, amountBalance, amtProdCb, IsDeposit::Yes);
976 if (amountDeposit <= beast::zero)
977 return {tecAMM_FAILED, STAmount{}};
978 auto tokNoRoundCb = [&] { return amountDeposit / ePrice; };
979 auto tokProdCb = [&] { return amountDeposit / ePrice; };
980 auto const tokens = getRoundedLPTokens(
981 view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::Yes);
982 // factor in the adjusted tokens
983 auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens(
984 view.rules(),
985 amountBalance,
986 amountDeposit,
987 lptAMMBalance,
988 tokens,
989 tfee);
990 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
991 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
992
993 return deposit(
994 view,
995 ammAccount,
996 amountBalance,
997 amountDepositAdj,
999 lptAMMBalance,
1000 tokensAdj,
1004 tfee);
1005}
1006
1009 Sandbox& view,
1010 AccountID const& ammAccount,
1011 STAmount const& amount,
1012 STAmount const& amount2,
1013 Issue const& lptIssue,
1014 std::uint16_t tfee)
1015{
1016 return deposit(
1017 view,
1018 ammAccount,
1019 amount,
1020 amount,
1021 amount2,
1022 STAmount{lptIssue, 0},
1023 ammLPTokens(amount, amount2, lptIssue),
1027 tfee);
1028}
1029
1030} // namespace ripple
Stream error() const
Definition Journal.h:346
Stream debug() const
Definition Journal.h:328
std::pair< TER, bool > applyGuts(Sandbox &view)
std::pair< TER, STAmount > equalDepositLimit(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &amount, STAmount const &amount2, std::optional< STAmount > const &lpTokensDepositMin, std::uint16_t tfee)
Equal asset deposit (Asset1In, Asset2In) with the constraint on the maximum amount of both assets tha...
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
std::pair< TER, STAmount > singleDepositEPrice(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &ePrice, std::uint16_t tfee)
Single asset deposit (Asset1In, EPrice) with two constraints.
static TER preclaim(PreclaimContext const &ctx)
std::pair< TER, STAmount > singleDeposit(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &lptAMMBalance, STAmount const &amount, std::optional< STAmount > const &lpTokensDepositMin, std::uint16_t tfee)
Single asset deposit (Asset1In) by the amount.
static NotTEC preflight(PreflightContext const &ctx)
std::pair< TER, STAmount > equalDepositTokens(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount2Balance, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit, std::optional< STAmount > const &depositMin, std::optional< STAmount > const &deposit2Min, std::uint16_t tfee)
Equal asset deposit (LPTokens) for the specified share of the AMM instance pools.
static bool checkExtraFeatures(PreflightContext const &ctx)
TER doApply() override
std::pair< TER, STAmount > equalDepositInEmptyState(Sandbox &view, AccountID const &ammAccount, STAmount const &amount, STAmount const &amount2, Issue const &lptIssue, std::uint16_t tfee)
Equal deposit in empty AMM state (LP tokens balance is 0)
std::pair< TER, STAmount > singleDepositTokens(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit, std::uint16_t tfee)
Single asset deposit (Asset1In, LPTokens) by the tokens.
std::pair< TER, STAmount > deposit(Sandbox &view, AccountID const &ammAccount, STAmount const &amountBalance, STAmount const &amountDeposit, std::optional< STAmount > const &amount2Deposit, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit, std::optional< STAmount > const &depositMin, std::optional< STAmount > const &deposit2Min, std::optional< STAmount > const &lpTokensDepositMin, std::uint16_t tfee)
Deposit requested assets and token amount into LP account.
ApplyView & view()
beast::Journal const journal
A currency issued by an account.
Definition Issue.h:33
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual Rules const & rules() const =0
Returns the tx processing rules.
Rules controlling protocol behavior.
Definition Rules.h:38
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:130
Issue const & issue() const
Definition STAmount.h:496
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:657
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:147
ApplyView & view()
Definition Transactor.h:163
beast::Journal const j_
Definition Transactor.h:145
ApplyContext & ctx_
Definition Transactor.h:143
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.
T is_same_v
T make_optional(T... args)
T make_pair(T... args)
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:446
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:244
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
constexpr std::uint32_t tfSingleAsset
Definition TxFlags.h:247
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
std::pair< STAmount, STAmount > adjustAssetInByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
@ fhZERO_IF_FROZEN
Definition View.h:77
@ fhIGNORE_FREEZE
Definition View.h:77
bool isXRP(AccountID const &c)
Definition AccountID.h:90
bool isIndividualFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:213
std::uint16_t getTradingFee(ReadView const &view, SLE const &ammSle, AccountID const &account)
Get AMM trading fee for the given account.
Definition AMMUtils.cpp:179
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)
constexpr std::uint32_t tfLimitLPToken
Definition TxFlags.h:250
STAmount ammAssetIn(STAmount const &asset1Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset deposit given LP Tokens.
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition AMMCore.cpp:129
constexpr std::uint32_t tfOneAssetLPToken
Definition TxFlags.h:249
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:2191
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:247
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:2485
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
Number feeMult(std::uint16_t tfee)
Get fee multiplier (1 - tfee) @tfee trading fee in basis points.
Definition AMMCore.h:110
void initializeFeeAuctionVote(ApplyView &view, std::shared_ptr< SLE > &ammSle, AccountID const &account, Issue const &lptIssue, std::uint16_t tfee)
Initialize Auction and Voting slots and set the trading/discounted fee.
Definition AMMUtils.cpp:340
constexpr std::uint32_t tfTwoAsset
Definition TxFlags.h:248
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:113
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...
constexpr std::uint32_t tfDepositSubTx
Definition TxFlags.h:255
static STAmount adjustLPTokensOut(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit)
@ tecINSUF_RESERVE_LINE
Definition TER.h:289
@ tecFROZEN
Definition TER.h:304
@ tecAMM_EMPTY
Definition TER.h:333
@ tecINTERNAL
Definition TER.h:311
@ tecAMM_NOT_EMPTY
Definition TER.h:334
@ tecUNFUNDED_AMM
Definition TER.h:329
@ tecAMM_FAILED
Definition TER.h:331
@ tecAMM_INVALID_TOKENS
Definition TER.h:332
constexpr std::uint32_t tfLPToken
Definition TxFlags.h:244
@ tesSUCCESS
Definition TER.h:245
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue > > const &pair=std::nullopt)
Definition AMMCore.cpp:80
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:387
constexpr std::uint32_t tfDepositMask
Definition TxFlags.h:259
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:47
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Issue const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
constexpr std::uint32_t tfTwoAssetIfEmpty
Definition TxFlags.h:251
Number feeMultHalf(std::uint16_t tfee)
Get fee multiplier (1 - tfee / 2) @tfee trading fee in basis points.
Definition AMMCore.h:119
STAmount lpTokensOut(STAmount const &asset1Balance, STAmount const &asset1Deposit, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's deposit amount.
@ terNO_AMM
Definition TER.h:227
TERSubset< CanCvtToTER > TER
Definition TER.h:649
std::uint16_t constexpr TRADING_FEE_THRESHOLD
Definition AMMCore.h:31
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition AMMHelpers.h:678
Number adjustFracByTokens(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &tokens, Number const &frac)
Find a fraction of tokens after the tokens are adjusted.
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:618
@ temBAD_AMOUNT
Definition TER.h:89
@ temBAD_FEE
Definition TER.h:92
@ temMALFORMED
Definition TER.h:87
@ temBAD_AMM_TOKENS
Definition TER.h:129
T popcount(T... args)
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:80
ReadView const & view
Definition Transactor.h:83
beast::Journal const j
Definition Transactor.h:88
State information when preflighting a tx.
Definition Transactor.h:35
beast::Journal const j
Definition Transactor.h:42
T value_or(T... args)
T what(T... args)