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
34{
35 if (!ammEnabled(ctx.rules))
36 return temDISABLED;
37
38 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
39 return ret;
40
41 auto const flags = ctx.tx.getFlags();
42 if (flags & tfDepositMask)
43 {
44 JLOG(ctx.j.debug()) << "AMM Deposit: invalid flags.";
45 return temINVALID_FLAG;
46 }
47
48 auto const amount = ctx.tx[~sfAmount];
49 auto const amount2 = ctx.tx[~sfAmount2];
50 auto const ePrice = ctx.tx[~sfEPrice];
51 auto const lpTokens = ctx.tx[~sfLPTokenOut];
52 auto const tradingFee = ctx.tx[~sfTradingFee];
53 // Valid options for the flags are:
54 // tfLPTokens: LPTokenOut, [Amount, Amount2]
55 // tfSingleAsset: Amount, [LPTokenOut]
56 // tfTwoAsset: Amount, Amount2, [LPTokenOut]
57 // tfTwoAssetIfEmpty: Amount, Amount2, [sfTradingFee]
58 // tfOnAssetLPToken: Amount and LPTokenOut
59 // tfLimitLPToken: Amount and EPrice
60 if (std::popcount(flags & tfDepositSubTx) != 1)
61 {
62 JLOG(ctx.j.debug()) << "AMM Deposit: invalid flags.";
63 return temMALFORMED;
64 }
65 if (flags & tfLPToken)
66 {
67 // if included then both amount and amount2 are deposit min
68 if (!lpTokens || ePrice || (amount && !amount2) ||
69 (!amount && amount2) || tradingFee)
70 return temMALFORMED;
71 }
72 else if (flags & tfSingleAsset)
73 {
74 // if included then lpTokens is deposit min
75 if (!amount || amount2 || ePrice || tradingFee)
76 return temMALFORMED;
77 }
78 else if (flags & tfTwoAsset)
79 {
80 // if included then lpTokens is deposit min
81 if (!amount || !amount2 || ePrice || tradingFee)
82 return temMALFORMED;
83 }
84 else if (flags & tfOneAssetLPToken)
85 {
86 if (!amount || !lpTokens || amount2 || ePrice || tradingFee)
87 return temMALFORMED;
88 }
89 else if (flags & tfLimitLPToken)
90 {
91 if (!amount || !ePrice || lpTokens || amount2 || tradingFee)
92 return temMALFORMED;
93 }
94 else if (flags & tfTwoAssetIfEmpty)
95 {
96 if (!amount || !amount2 || ePrice || lpTokens)
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 Deposit: invalid asset pair.";
105 return res;
106 }
107
108 if (amount && amount2 && amount->issue() == amount2->issue())
109 {
110 JLOG(ctx.j.debug()) << "AMM Deposit: 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 Deposit: invalid LPTokens";
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)),
126 ePrice.has_value()))
127 {
128 JLOG(ctx.j.debug()) << "AMM Deposit: invalid amount";
129 return res;
130 }
131 }
132
133 if (amount2)
134 {
135 if (auto const res = invalidAMMAmount(
136 *amount2, std::make_optional(std::make_pair(asset, asset2))))
137 {
138 JLOG(ctx.j.debug()) << "AMM Deposit: invalid amount2";
139 return res;
140 }
141 }
142
143 // must be amount issue
144 if (amount && ePrice)
145 {
146 if (auto const res = invalidAMMAmount(
147 *ePrice,
149 std::make_pair(amount->issue(), amount->issue()))))
150 {
151 JLOG(ctx.j.debug()) << "AMM Deposit: invalid EPrice";
152 return res;
153 }
154 }
155
156 if (tradingFee > TRADING_FEE_THRESHOLD)
157 {
158 JLOG(ctx.j.debug()) << "AMM Deposit: invalid trading fee.";
159 return temBAD_FEE;
160 }
161
162 return preflight2(ctx);
163}
164
165TER
167{
168 auto const accountID = ctx.tx[sfAccount];
169
170 auto const ammSle =
171 ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
172 if (!ammSle)
173 {
174 JLOG(ctx.j.debug()) << "AMM Deposit: Invalid asset pair.";
175 return terNO_AMM;
176 }
177
178 auto const expected = ammHolds(
179 ctx.view,
180 *ammSle,
184 ctx.j);
185 if (!expected)
186 return expected.error(); // LCOV_EXCL_LINE
187 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
188 if (ctx.tx.getFlags() & tfTwoAssetIfEmpty)
189 {
190 if (lptAMMBalance != beast::zero)
191 return tecAMM_NOT_EMPTY;
192 if (amountBalance != beast::zero || amount2Balance != beast::zero)
193 {
194 // LCOV_EXCL_START
195 JLOG(ctx.j.debug()) << "AMM Deposit: tokens balance is not zero.";
196 return tecINTERNAL;
197 // LCOV_EXCL_STOP
198 }
199 }
200 else
201 {
202 if (lptAMMBalance == beast::zero)
203 return tecAMM_EMPTY;
204 if (amountBalance <= beast::zero || amount2Balance <= beast::zero ||
205 lptAMMBalance < beast::zero)
206 {
207 // LCOV_EXCL_START
208 JLOG(ctx.j.debug())
209 << "AMM Deposit: reserves or tokens balance is zero.";
210 return tecINTERNAL;
211 // LCOV_EXCL_STOP
212 }
213 }
214
215 // Check account has sufficient funds.
216 // Return tesSUCCESS if it does, error otherwise.
217 // Have to check again in deposit() because
218 // amounts might be derived based on tokens or
219 // limits.
220 auto balance = [&](auto const& deposit) -> TER {
221 if (isXRP(deposit))
222 {
223 auto const lpIssue = (*ammSle)[sfLPTokenBalance].issue();
224 // Adjust the reserve if LP doesn't have LPToken trustline
225 auto const sle = ctx.view.read(
226 keylet::line(accountID, lpIssue.account, lpIssue.currency));
227 if (xrpLiquid(ctx.view, accountID, !sle, ctx.j) >= deposit)
228 return TER(tesSUCCESS);
229 if (sle)
230 return tecUNFUNDED_AMM;
232 }
233 return (accountID == deposit.issue().account ||
235 ctx.view,
236 accountID,
237 deposit.issue(),
239 ctx.j) >= deposit)
240 ? TER(tesSUCCESS)
242 };
243
244 if (ctx.view.rules().enabled(featureAMMClawback))
245 {
246 // Check if either of the assets is frozen, AMMDeposit is not allowed
247 // if either asset is frozen
248 auto checkAsset = [&](Issue const& asset) -> TER {
249 if (auto const ter = requireAuth(ctx.view, asset, accountID))
250 {
251 JLOG(ctx.j.debug())
252 << "AMM Deposit: account is not authorized, " << asset;
253 return ter;
254 }
255
256 if (isFrozen(ctx.view, accountID, asset))
257 {
258 JLOG(ctx.j.debug())
259 << "AMM Deposit: account or currency is frozen, "
260 << to_string(accountID) << " " << to_string(asset.currency);
261
262 return tecFROZEN;
263 }
264
265 return tesSUCCESS;
266 };
267
268 if (auto const ter = checkAsset(ctx.tx[sfAsset].get<Issue>()))
269 return ter;
270
271 if (auto const ter = checkAsset(ctx.tx[sfAsset2].get<Issue>()))
272 return ter;
273 }
274
275 auto const amount = ctx.tx[~sfAmount];
276 auto const amount2 = ctx.tx[~sfAmount2];
277 auto const ammAccountID = ammSle->getAccountID(sfAccount);
278
279 auto checkAmount = [&](std::optional<STAmount> const& amount,
280 bool checkBalance) -> TER {
281 if (amount)
282 {
283 // This normally should not happen.
284 // Account is not authorized to hold the assets it's depositing,
285 // or it doesn't even have a trust line for them
286 if (auto const ter =
287 requireAuth(ctx.view, amount->issue(), accountID))
288 {
289 // LCOV_EXCL_START
290 JLOG(ctx.j.debug())
291 << "AMM Deposit: account is not authorized, "
292 << amount->issue();
293 return ter;
294 // LCOV_EXCL_STOP
295 }
296 // AMM account or currency frozen
297 if (isFrozen(ctx.view, ammAccountID, amount->issue()))
298 {
299 JLOG(ctx.j.debug())
300 << "AMM Deposit: AMM account or currency is frozen, "
301 << to_string(accountID);
302 return tecFROZEN;
303 }
304 // Account frozen
305 if (isIndividualFrozen(ctx.view, accountID, amount->issue()))
306 {
307 JLOG(ctx.j.debug()) << "AMM Deposit: account is frozen, "
308 << to_string(accountID) << " "
309 << to_string(amount->issue().currency);
310 return tecFROZEN;
311 }
312 if (checkBalance)
313 {
314 if (auto const ter = balance(*amount))
315 {
316 JLOG(ctx.j.debug())
317 << "AMM Deposit: account has insufficient funds, "
318 << *amount;
319 return ter;
320 }
321 }
322 }
323 return tesSUCCESS;
324 };
325
326 // amount and amount2 are deposit min in case of tfLPToken
327 if (!(ctx.tx.getFlags() & tfLPToken))
328 {
329 if (auto const ter = checkAmount(amount, true))
330 return ter;
331
332 if (auto const ter = checkAmount(amount2, true))
333 return ter;
334 }
335 else
336 {
337 if (auto const ter = checkAmount(amountBalance, false))
338 return ter;
339 if (auto const ter = checkAmount(amount2Balance, false))
340 return ter;
341 }
342
343 // Equal deposit lp tokens
344 if (auto const lpTokens = ctx.tx[~sfLPTokenOut];
345 lpTokens && lpTokens->issue() != lptAMMBalance.issue())
346 {
347 JLOG(ctx.j.debug()) << "AMM Deposit: invalid LPTokens.";
348 return temBAD_AMM_TOKENS;
349 }
350
351 // Check the reserve for LPToken trustline if not LP.
352 // We checked above but need to check again if depositing IOU only.
353 if (ammLPHolds(ctx.view, *ammSle, accountID, ctx.j) == beast::zero)
354 {
355 STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j);
356 // Insufficient reserve
357 if (xrpBalance <= beast::zero)
358 {
359 JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
361 }
362 }
363
364 return tesSUCCESS;
365}
366
369{
370 auto const amount = ctx_.tx[~sfAmount];
371 auto const amount2 = ctx_.tx[~sfAmount2];
372 auto const ePrice = ctx_.tx[~sfEPrice];
373 auto const lpTokensDeposit = ctx_.tx[~sfLPTokenOut];
374 auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
375 if (!ammSle)
376 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
377 auto const ammAccountID = (*ammSle)[sfAccount];
378
379 auto const expected = ammHolds(
380 sb,
381 *ammSle,
382 amount ? amount->issue() : std::optional<Issue>{},
383 amount2 ? amount2->issue() : std::optional<Issue>{},
385 ctx_.journal);
386 if (!expected)
387 return {expected.error(), false}; // LCOV_EXCL_LINE
388 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
389 auto const tfee = (lptAMMBalance == beast::zero)
390 ? ctx_.tx[~sfTradingFee].value_or(0)
391 : getTradingFee(ctx_.view(), *ammSle, account_);
392
393 auto const subTxType = ctx_.tx.getFlags() & tfDepositSubTx;
394
395 auto const [result, newLPTokenBalance] =
396 [&,
397 &amountBalance = amountBalance,
398 &amount2Balance = amount2Balance,
399 &lptAMMBalance = lptAMMBalance]() -> std::pair<TER, STAmount> {
400 if (subTxType & tfTwoAsset)
401 return equalDepositLimit(
402 sb,
403 ammAccountID,
404 amountBalance,
405 amount2Balance,
406 lptAMMBalance,
407 *amount,
408 *amount2,
409 lpTokensDeposit,
410 tfee);
411 if (subTxType & tfOneAssetLPToken)
412 return singleDepositTokens(
413 sb,
414 ammAccountID,
415 amountBalance,
416 *amount,
417 lptAMMBalance,
418 *lpTokensDeposit,
419 tfee);
420 if (subTxType & tfLimitLPToken)
421 return singleDepositEPrice(
422 sb,
423 ammAccountID,
424 amountBalance,
425 *amount,
426 lptAMMBalance,
427 *ePrice,
428 tfee);
429 if (subTxType & tfSingleAsset)
430 return singleDeposit(
431 sb,
432 ammAccountID,
433 amountBalance,
434 lptAMMBalance,
435 *amount,
436 lpTokensDeposit,
437 tfee);
438 if (subTxType & tfLPToken)
439 return equalDepositTokens(
440 sb,
441 ammAccountID,
442 amountBalance,
443 amount2Balance,
444 lptAMMBalance,
445 *lpTokensDeposit,
446 amount,
447 amount2,
448 tfee);
449 if (subTxType & tfTwoAssetIfEmpty)
451 sb,
452 ammAccountID,
453 *amount,
454 *amount2,
455 lptAMMBalance.issue(),
456 tfee);
457 // should not happen.
458 // LCOV_EXCL_START
459 JLOG(j_.error()) << "AMM Deposit: invalid options.";
461 // LCOV_EXCL_STOP
462 }();
463
464 if (result == tesSUCCESS)
465 {
466 XRPL_ASSERT(
467 newLPTokenBalance > beast::zero,
468 "ripple::AMMDeposit::applyGuts : valid new LP token balance");
469 ammSle->setFieldAmount(sfLPTokenBalance, newLPTokenBalance);
470 // LP depositing into AMM empty state gets the auction slot
471 // and the voting
472 if (lptAMMBalance == beast::zero)
474 sb, ammSle, account_, lptAMMBalance.issue(), tfee);
475
476 sb.update(ammSle);
477 }
478
479 return {result, result == tesSUCCESS};
480}
481
482TER
484{
485 // This is the ledger view that we work against. Transactions are applied
486 // as we go on processing transactions.
487 Sandbox sb(&ctx_.view());
488
489 auto const result = applyGuts(sb);
490 if (result.second)
491 sb.apply(ctx_.rawView());
492
493 return result.first;
494}
495
498 Sandbox& view,
499 AccountID const& ammAccount,
500 STAmount const& amountBalance,
501 STAmount const& amountDeposit,
502 std::optional<STAmount> const& amount2Deposit,
503 STAmount const& lptAMMBalance,
504 STAmount const& lpTokensDeposit,
505 std::optional<STAmount> const& depositMin,
506 std::optional<STAmount> const& deposit2Min,
507 std::optional<STAmount> const& lpTokensDepositMin,
508 std::uint16_t tfee)
509{
510 // Check account has sufficient funds.
511 // Return true if it does, false otherwise.
512 auto checkBalance = [&](auto const& depositAmount) -> TER {
513 if (depositAmount <= beast::zero)
514 return temBAD_AMOUNT;
515 if (isXRP(depositAmount))
516 {
517 auto const& lpIssue = lpTokensDeposit.issue();
518 // Adjust the reserve if LP doesn't have LPToken trustline
519 auto const sle = view.read(
520 keylet::line(account_, lpIssue.account, lpIssue.currency));
521 if (xrpLiquid(view, account_, !sle, j_) >= depositAmount)
522 return tesSUCCESS;
523 }
524 else if (
525 account_ == depositAmount.issue().account ||
527 view,
528 account_,
529 depositAmount.issue(),
531 ctx_.journal) >= depositAmount)
532 return tesSUCCESS;
533 return tecUNFUNDED_AMM;
534 };
535
536 auto const
537 [amountDepositActual, amount2DepositActual, lpTokensDepositActual] =
539 amountBalance,
540 amountDeposit,
541 amount2Deposit,
542 lptAMMBalance,
543 lpTokensDeposit,
544 tfee,
546
547 if (lpTokensDepositActual <= beast::zero)
548 {
549 JLOG(ctx_.journal.debug()) << "AMM Deposit: adjusted tokens zero";
551 }
552
553 if (amountDepositActual < depositMin ||
554 amount2DepositActual < deposit2Min ||
555 lpTokensDepositActual < lpTokensDepositMin)
556 {
557 JLOG(ctx_.journal.debug())
558 << "AMM Deposit: min deposit fails " << amountDepositActual << " "
559 << depositMin.value_or(STAmount{}) << " "
560 << amount2DepositActual.value_or(STAmount{}) << " "
561 << deposit2Min.value_or(STAmount{}) << " " << lpTokensDepositActual
562 << " " << lpTokensDepositMin.value_or(STAmount{});
563 return {tecAMM_FAILED, STAmount{}};
564 }
565
566 // Deposit amountDeposit
567 if (auto const ter = checkBalance(amountDepositActual))
568 {
569 JLOG(ctx_.journal.debug()) << "AMM Deposit: account has insufficient "
570 "checkBalance to deposit or is 0"
571 << amountDepositActual;
572 return {ter, STAmount{}};
573 }
574
575 auto res = accountSend(
576 view,
577 account_,
578 ammAccount,
579 amountDepositActual,
582 if (res != tesSUCCESS)
583 {
584 JLOG(ctx_.journal.debug())
585 << "AMM Deposit: failed to deposit " << amountDepositActual;
586 return {res, STAmount{}};
587 }
588
589 // Deposit amount2Deposit
590 if (amount2DepositActual)
591 {
592 if (auto const ter = checkBalance(*amount2DepositActual))
593 {
594 JLOG(ctx_.journal.debug())
595 << "AMM Deposit: account has insufficient checkBalance to "
596 "deposit or is 0 "
597 << *amount2DepositActual;
598 return {ter, STAmount{}};
599 }
600
601 res = accountSend(
602 view,
603 account_,
604 ammAccount,
605 *amount2DepositActual,
608 if (res != tesSUCCESS)
609 {
610 JLOG(ctx_.journal.debug())
611 << "AMM Deposit: failed to deposit " << *amount2DepositActual;
612 return {res, STAmount{}};
613 }
614 }
615
616 // Deposit LP tokens
617 res = accountSend(
618 view, ammAccount, account_, lpTokensDepositActual, ctx_.journal);
619 if (res != tesSUCCESS)
620 {
621 JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit LPTokens";
622 return {res, STAmount{}};
623 }
624
625 return {tesSUCCESS, lptAMMBalance + lpTokensDepositActual};
626}
627
628static STAmount
630 Rules const& rules,
631 STAmount const& lptAMMBalance,
632 STAmount const& lpTokensDeposit)
633{
634 if (!rules.enabled(fixAMMv1_3))
635 return lpTokensDeposit;
636 return adjustLPTokens(lptAMMBalance, lpTokensDeposit, IsDeposit::Yes);
637}
638
644 Sandbox& view,
645 AccountID const& ammAccount,
646 STAmount const& amountBalance,
647 STAmount const& amount2Balance,
648 STAmount const& lptAMMBalance,
649 STAmount const& lpTokensDeposit,
650 std::optional<STAmount> const& depositMin,
651 std::optional<STAmount> const& deposit2Min,
652 std::uint16_t tfee)
653{
654 try
655 {
656 auto const tokensAdj =
657 adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit);
658 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
660 auto const frac =
661 divide(tokensAdj, lptAMMBalance, lptAMMBalance.issue());
662 // amounts factor in the adjusted tokens
663 auto const amountDeposit =
664 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::Yes);
665 auto const amount2Deposit =
666 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::Yes);
667 return deposit(
668 view,
669 ammAccount,
670 amountBalance,
671 amountDeposit,
672 amount2Deposit,
673 lptAMMBalance,
674 tokensAdj,
675 depositMin,
676 deposit2Min,
678 tfee);
679 }
680 catch (std::exception const& e)
681 {
682 // LCOV_EXCL_START
683 JLOG(j_.error()) << "AMMDeposit::equalDepositTokens exception "
684 << e.what();
685 return {tecINTERNAL, STAmount{}};
686 // LCOV_EXCL_STOP
687 }
688}
689
720 Sandbox& view,
721 AccountID const& ammAccount,
722 STAmount const& amountBalance,
723 STAmount const& amount2Balance,
724 STAmount const& lptAMMBalance,
725 STAmount const& amount,
726 STAmount const& amount2,
727 std::optional<STAmount> const& lpTokensDepositMin,
728 std::uint16_t tfee)
729{
730 auto frac = Number{amount} / amountBalance;
731 auto tokensAdj =
732 getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes);
733 if (tokensAdj == beast::zero)
734 {
735 if (!view.rules().enabled(fixAMMv1_3))
736 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
737 else
739 }
740 // factor in the adjusted tokens
741 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
742 auto const amount2Deposit =
743 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::Yes);
744 if (amount2Deposit <= amount2)
745 return deposit(
746 view,
747 ammAccount,
748 amountBalance,
749 amount,
750 amount2Deposit,
751 lptAMMBalance,
752 tokensAdj,
755 lpTokensDepositMin,
756 tfee);
757 frac = Number{amount2} / amount2Balance;
758 tokensAdj =
759 getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes);
760 if (tokensAdj == beast::zero)
761 {
762 if (!view.rules().enabled(fixAMMv1_3))
763 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
764 else
765 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
766 }
767 // factor in the adjusted tokens
768 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
769 auto const amountDeposit =
770 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::Yes);
771 if (amountDeposit <= amount)
772 return deposit(
773 view,
774 ammAccount,
775 amountBalance,
776 amountDeposit,
777 amount2,
778 lptAMMBalance,
779 tokensAdj,
782 lpTokensDepositMin,
783 tfee);
784 return {tecAMM_FAILED, STAmount{}};
785}
786
797 Sandbox& view,
798 AccountID const& ammAccount,
799 STAmount const& amountBalance,
800 STAmount const& lptAMMBalance,
801 STAmount const& amount,
802 std::optional<STAmount> const& lpTokensDepositMin,
803 std::uint16_t tfee)
804{
805 auto const tokens = adjustLPTokensOut(
806 view.rules(),
807 lptAMMBalance,
808 lpTokensOut(amountBalance, amount, lptAMMBalance, tfee));
809 if (tokens == beast::zero)
810 {
811 if (!view.rules().enabled(fixAMMv1_3))
812 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
813 else
815 }
816 // factor in the adjusted tokens
817 auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens(
818 view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
819 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
820 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
821 return deposit(
822 view,
823 ammAccount,
824 amountBalance,
825 amountDepositAdj,
827 lptAMMBalance,
828 tokensAdj,
831 lpTokensDepositMin,
832 tfee);
833}
834
844 Sandbox& view,
845 AccountID const& ammAccount,
846 STAmount const& amountBalance,
847 STAmount const& amount,
848 STAmount const& lptAMMBalance,
849 STAmount const& lpTokensDeposit,
850 std::uint16_t tfee)
851{
852 auto const tokensAdj =
853 adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit);
854 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
856 // the adjusted tokens are factored in
857 auto const amountDeposit =
858 ammAssetIn(amountBalance, lptAMMBalance, tokensAdj, tfee);
859 if (amountDeposit > amount)
860 return {tecAMM_FAILED, STAmount{}};
861 return deposit(
862 view,
863 ammAccount,
864 amountBalance,
865 amountDeposit,
867 lptAMMBalance,
868 tokensAdj,
872 tfee);
873}
874
902 Sandbox& view,
903 AccountID const& ammAccount,
904 STAmount const& amountBalance,
905 STAmount const& amount,
906 STAmount const& lptAMMBalance,
907 STAmount const& ePrice,
908 std::uint16_t tfee)
909{
910 if (amount != beast::zero)
911 {
912 auto const tokens = adjustLPTokensOut(
913 view.rules(),
914 lptAMMBalance,
915 lpTokensOut(amountBalance, amount, lptAMMBalance, tfee));
916 if (tokens <= beast::zero)
917 {
918 if (!view.rules().enabled(fixAMMv1_3))
919 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
920 else
922 }
923 // factor in the adjusted tokens
924 auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens(
925 view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
926 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
927 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
928 auto const ep = Number{amountDepositAdj} / tokensAdj;
929 if (ep <= ePrice)
930 return deposit(
931 view,
932 ammAccount,
933 amountBalance,
934 amountDepositAdj,
936 lptAMMBalance,
937 tokensAdj,
941 tfee);
942 }
943
944 // LPTokens is asset out => E = b / t
945 // substituting t in formula (3) as b/E:
946 // b/E = T * [b/B - sqrt(t2**2 + b/(f1*B)) + t2]/
947 // [1 + sqrt(t2**2 + b/(f1*B)) -t2] (A)
948 // where f1 = 1 - fee, f2 = (1 - fee/2)/f1
949 // Let R = b/(f1*B), then b/B = f1*R and b = R*f1*B
950 // Then (A) is
951 // R*f1*B = E*T*[R*f1 -sqrt(f2**2 + R) + f2]/[1 + sqrt(f2**2 + R) - f2] =>
952 // Let c = f1*B/(E*T) =>
953 // R*c*(1 + sqrt(f2**2 + R) + f2) = R*f1 - sqrt(f2**2 + R) - f2 =>
954 // (R*c + 1)*sqrt(f2**2 + R) = R*(f1 + c*f2 - c) + f2 =>
955 // Let d = f1 + c*f2 - c =>
956 // (R*c + 1)*sqrt(f2**2 + R) = R*d + f2 =>
957 // (R*c + 1)**2 * (f2**2 + R) = (R*d + f2)**2 =>
958 // (R*c)**2 + R*((c*f2)**2 + 2*c - d**2) + 2*c*f2**2 + 1 -2*d*f2 = 0 =>
959 // a1 = c**2, b1 = (c*f2)**2 + 2*c - d**2, c1 = 2*c*f2**2 + 1 - 2*d*f2
960 // R = (-b1 + sqrt(b1**2 + 4*a1*c1))/(2*a1)
961 auto const f1 = feeMult(tfee);
962 auto const f2 = feeMultHalf(tfee) / f1;
963 auto const c = f1 * amountBalance / (ePrice * lptAMMBalance);
964 auto const d = f1 + c * f2 - c;
965 auto const a1 = c * c;
966 auto const b1 = c * c * f2 * f2 + 2 * c - d * d;
967 auto const c1 = 2 * c * f2 * f2 + 1 - 2 * d * f2;
968 auto amtNoRoundCb = [&] {
969 return f1 * amountBalance * solveQuadraticEq(a1, b1, c1);
970 };
971 auto amtProdCb = [&] { return f1 * solveQuadraticEq(a1, b1, c1); };
972 auto const amountDeposit = getRoundedAsset(
973 view.rules(), amtNoRoundCb, amountBalance, amtProdCb, IsDeposit::Yes);
974 if (amountDeposit <= beast::zero)
975 return {tecAMM_FAILED, STAmount{}};
976 auto tokNoRoundCb = [&] { return amountDeposit / ePrice; };
977 auto tokProdCb = [&] { return amountDeposit / ePrice; };
978 auto const tokens = getRoundedLPTokens(
979 view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::Yes);
980 // factor in the adjusted tokens
981 auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens(
982 view.rules(),
983 amountBalance,
984 amountDeposit,
985 lptAMMBalance,
986 tokens,
987 tfee);
988 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero)
989 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
990
991 return deposit(
992 view,
993 ammAccount,
994 amountBalance,
995 amountDepositAdj,
997 lptAMMBalance,
998 tokensAdj,
1002 tfee);
1003}
1004
1007 Sandbox& view,
1008 AccountID const& ammAccount,
1009 STAmount const& amount,
1010 STAmount const& amount2,
1011 Issue const& lptIssue,
1012 std::uint16_t tfee)
1013{
1014 return deposit(
1015 view,
1016 ammAccount,
1017 amount,
1018 amount,
1019 amount2,
1020 STAmount{lptIssue, 0},
1021 ammLPTokens(amount, amount2, lptIssue),
1025 tfee);
1026}
1027
1028} // 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...
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.
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:651
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:145
ApplyView & view()
Definition Transactor.h:161
beast::Journal const j_
Definition Transactor.h:143
ApplyContext & ctx_
Definition Transactor.h:141
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
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
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:2174
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:2464
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
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
static STAmount adjustLPTokensOut(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit)
@ tecINSUF_RESERVE_LINE
Definition TER.h:288
@ tecFROZEN
Definition TER.h:303
@ tecAMM_EMPTY
Definition TER.h:332
@ tecINTERNAL
Definition TER.h:310
@ tecAMM_NOT_EMPTY
Definition TER.h:333
@ tecUNFUNDED_AMM
Definition TER.h:328
@ tecAMM_FAILED
Definition TER.h:330
@ tecAMM_INVALID_TOKENS
Definition TER.h:331
constexpr std::uint32_t tfLPToken
Definition TxFlags.h:244
@ 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
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:384
bool isTesSuccess(TER x) noexcept
Definition TER.h:674
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:645
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.
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:605
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:615
@ temBAD_AMOUNT
Definition TER.h:89
@ temBAD_FEE
Definition TER.h:92
@ 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)
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)