xrpld
Loading...
Searching...
No Matches
AMMDeposit.cpp
1#include <xrpl/tx/transactors/dex/AMMDeposit.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Number.h>
5#include <xrpl/beast/utility/Zero.h>
6#include <xrpl/beast/utility/instrumentation.h>
7#include <xrpl/ledger/Sandbox.h>
8#include <xrpl/ledger/helpers/AMMHelpers.h>
9#include <xrpl/ledger/helpers/AccountRootHelpers.h>
10#include <xrpl/ledger/helpers/MPTokenHelpers.h>
11#include <xrpl/ledger/helpers/TokenHelpers.h>
12#include <xrpl/protocol/AMMCore.h>
13#include <xrpl/protocol/AccountID.h>
14#include <xrpl/protocol/Asset.h>
15#include <xrpl/protocol/Feature.h>
16#include <xrpl/protocol/Indexes.h>
17#include <xrpl/protocol/MPTIssue.h>
18#include <xrpl/protocol/SField.h>
19#include <xrpl/protocol/STAmount.h>
20#include <xrpl/protocol/STLedgerEntry.h>
21#include <xrpl/protocol/STTx.h>
22#include <xrpl/protocol/TER.h>
23#include <xrpl/protocol/TxFlags.h>
24#include <xrpl/protocol/XRPAmount.h>
25#include <xrpl/tx/Transactor.h>
26
27#include <bit>
28#include <cstdint>
29#include <exception>
30#include <optional>
31#include <utility>
32
33namespace xrpl {
34
35bool
37{
38 if (!ammEnabled(ctx.rules))
39 return false;
40
41 auto const amount = ctx.tx[~sfAmount];
42 auto const amount2 = ctx.tx[~sfAmount2];
43
44 return ctx.rules.enabled(featureMPTokensV2) ||
45 (!ctx.tx[sfAsset].holds<MPTIssue>() && !ctx.tx[sfAsset2].holds<MPTIssue>() &&
46 !(amount && amount->holds<MPTIssue>()) && !(amount2 && amount2->holds<MPTIssue>()));
47}
48
51{
52 return tfAMMDepositMask;
53}
54
57{
58 auto const flags = ctx.tx.getFlags();
59 auto const amount = ctx.tx[~sfAmount];
60 auto const amount2 = ctx.tx[~sfAmount2];
61 auto const ePrice = ctx.tx[~sfEPrice];
62 auto const lpTokens = ctx.tx[~sfLPTokenOut];
63 auto const tradingFee = ctx.tx[~sfTradingFee];
64 // Valid options for the flags are:
65 // tfLPTokens: LPTokenOut, [Amount, Amount2]
66 // tfSingleAsset: Amount, [LPTokenOut]
67 // tfTwoAsset: Amount, Amount2, [LPTokenOut]
68 // tfTwoAssetIfEmpty: Amount, Amount2, [sfTradingFee]
69 // tfOnAssetLPToken: Amount and LPTokenOut
70 // tfLimitLPToken: Amount and EPrice
71 if (std::popcount(flags & tfDepositSubTx) != 1)
72 {
73 JLOG(ctx.j.debug()) << "AMM Deposit: invalid flags.";
74 return temMALFORMED;
75 }
76 if (ctx.tx.isFlag(tfLPToken))
77 {
78 // if included then both amount and amount2 are deposit min
79 if (!lpTokens || ePrice || (amount && !amount2) || (!amount && amount2) || tradingFee)
80 return temMALFORMED;
81 }
82 else if (ctx.tx.isFlag(tfSingleAsset))
83 {
84 // if included then lpTokens is deposit min
85 if (!amount || amount2 || ePrice || tradingFee)
86 return temMALFORMED;
87 }
88 else if (ctx.tx.isFlag(tfTwoAsset))
89 {
90 // if included then lpTokens is deposit min
91 if (!amount || !amount2 || ePrice || tradingFee)
92 return temMALFORMED;
93 }
94 else if (ctx.tx.isFlag(tfOneAssetLPToken))
95 {
96 if (!amount || !lpTokens || amount2 || ePrice || tradingFee)
97 return temMALFORMED;
98 }
99 else if (ctx.tx.isFlag(tfLimitLPToken))
100 {
101 if (!amount || !ePrice || lpTokens || amount2 || tradingFee)
102 return temMALFORMED;
103 }
104 else if (ctx.tx.isFlag(tfTwoAssetIfEmpty))
105 {
106 if (!amount || !amount2 || ePrice || lpTokens)
107 return temMALFORMED;
108 }
109
110 auto const asset = ctx.tx[sfAsset];
111 auto const asset2 = ctx.tx[sfAsset2];
112 if (auto const res = invalidAMMAssetPair(asset, asset2))
113 {
114 JLOG(ctx.j.debug()) << "AMM Deposit: invalid asset pair.";
115 return res;
116 }
117
118 if (amount && amount2 && amount->asset() == amount2->asset())
119 {
120 JLOG(ctx.j.debug()) << "AMM Deposit: invalid tokens, same issue." << amount->asset() << " "
121 << amount2->asset();
122 return temBAD_AMM_TOKENS;
123 }
124
125 if (lpTokens && *lpTokens <= beast::kZero)
126 {
127 JLOG(ctx.j.debug()) << "AMM Deposit: invalid LPTokens";
128 return temBAD_AMM_TOKENS;
129 }
130
131 if (amount)
132 {
133 if (auto const res = invalidAMMAmount(
134 *amount, std::make_optional(std::make_pair(asset, asset2)), ePrice.has_value()))
135 {
136 JLOG(ctx.j.debug()) << "AMM Deposit: invalid amount";
137 return res;
138 }
139 }
140
141 if (amount2)
142 {
143 if (auto const res =
144 invalidAMMAmount(*amount2, std::make_optional(std::make_pair(asset, asset2))))
145 {
146 JLOG(ctx.j.debug()) << "AMM Deposit: invalid amount2";
147 return res;
148 }
149 }
150
151 if (amount && ePrice)
152 {
153 auto assets = [&]() -> std::optional<std::pair<Asset, Asset>> {
154 // don't check ePrice issue
155 if (ctx.rules.enabled(featureMPTokensV2))
156 return std::nullopt;
157 // must be amount issue
158 return std::make_optional(std::make_pair(amount->asset(), amount->asset()));
159 }();
160 if (auto const res = invalidAMMAmount(*ePrice, assets))
161 {
162 JLOG(ctx.j.debug()) << "AMM Deposit: invalid EPrice";
163 return res;
164 }
165 }
166
167 if (tradingFee > kTradingFeeThreshold)
168 {
169 JLOG(ctx.j.debug()) << "AMM Deposit: invalid trading fee.";
170 return temBAD_FEE;
171 }
172
173 return tesSUCCESS;
174}
175
176TER
178{
179 auto const accountID = ctx.tx[sfAccount];
180
181 auto const ammSle = ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
182 if (!ammSle)
183 {
184 JLOG(ctx.j.debug()) << "AMM Deposit: Invalid asset pair.";
185 return terNO_AMM;
186 }
187
188 auto const expected = ammHolds(
189 ctx.view,
190 *ammSle,
191 std::nullopt,
192 std::nullopt,
195 ctx.j);
196 if (!expected)
197 return expected.error(); // LCOV_EXCL_LINE
198 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
199 if (ctx.tx.isFlag(tfTwoAssetIfEmpty))
200 {
201 if (lptAMMBalance != beast::kZero)
202 return tecAMM_NOT_EMPTY;
203 if (amountBalance != beast::kZero || amount2Balance != beast::kZero)
204 {
205 // LCOV_EXCL_START
206 JLOG(ctx.j.debug()) << "AMM Deposit: tokens balance is not zero.";
207 return tecINTERNAL;
208 // LCOV_EXCL_STOP
209 }
210 }
211 else
212 {
213 if (lptAMMBalance == beast::kZero)
214 return tecAMM_EMPTY;
215 if (amountBalance <= beast::kZero || amount2Balance <= beast::kZero ||
216 lptAMMBalance < beast::kZero)
217 {
218 // LCOV_EXCL_START
219 JLOG(ctx.j.debug()) << "AMM Deposit: reserves or tokens balance is zero.";
220 return tecINTERNAL;
221 // LCOV_EXCL_STOP
222 }
223 }
224
225 // Check account has sufficient funds.
226 // Return tesSUCCESS if it does, error otherwise.
227 // Have to check again in deposit() because
228 // amounts might be derived based on tokens or
229 // limits.
230 auto balance = [&](auto const& deposit) -> TER {
231 if (isXRP(deposit))
232 {
233 auto const lpIssue = (*ammSle)[sfLPTokenBalance].get<Issue>();
234 // Adjust the reserve if LP doesn't have LPToken trustline
235 auto const sle =
236 ctx.view.read(keylet::trustLine(accountID, lpIssue.account, lpIssue.currency));
237 if (xrpLiquid(ctx.view, accountID, !sle, ctx.j) >= deposit)
238 return TER(tesSUCCESS);
239 if (sle)
240 return tecUNFUNDED_AMM;
242 }
243 return accountFunds(
244 ctx.view,
245 accountID,
246 deposit,
249 ctx.j) >= deposit
250 ? TER(tesSUCCESS)
252 };
253
254 auto const amount = ctx.tx[~sfAmount];
255 auto const amount2 = ctx.tx[~sfAmount2];
256 auto const ammAccountID = ammSle->getAccountID(sfAccount);
257
258 if (ctx.view.rules().enabled(fixCleanup3_3_0))
259 {
260 // Unified deposit freeze check for both pool assets.
261 // AMMDeposit is not allowed if either asset is frozen.
262 auto checkAsset = [&](Asset const& asset) -> TER {
263 if (auto const ter = requireAuth(ctx.view, asset, accountID, AuthType::WeakAuth))
264 {
265 JLOG(ctx.j.debug()) << "AMM Deposit: account is not authorized, " << asset;
266 return ter;
267 }
268 if (auto const ter = checkDepositFreeze(ctx.view, accountID, ammAccountID, asset))
269 {
270 JLOG(ctx.j.debug())
271 << "AMM Deposit: frozen, " << to_string(accountID) << " " << to_string(asset);
272 return ter;
273 }
274 return tesSUCCESS;
275 };
276
277 if (auto const ter = checkAsset(ctx.tx[sfAsset]))
278 return ter;
279
280 if (auto const ter = checkAsset(ctx.tx[sfAsset2]))
281 return ter;
282 }
283 else if (ctx.view.rules().enabled(featureAMMClawback))
284 {
285 // Check if either of the assets is frozen, AMMDeposit is not allowed
286 // if either asset is frozen
287 auto checkAsset = [&](Asset const& asset) -> TER {
288 // WeakAuth - don't need to check if MPT object exists as might be
289 // depositing into non-MPT pool. It'll fail on send if MPT doesn't
290 // exist.
291 if (auto const ter = requireAuth(ctx.view, asset, accountID, AuthType::WeakAuth))
292 {
293 JLOG(ctx.j.debug()) << "AMM Deposit: account is not authorized, " << asset;
294 return ter;
295 }
296
297 if (auto const ter = checkFrozen(ctx.view, accountID, asset); !isTesSuccess(ter))
298 {
299 JLOG(ctx.j.debug()) << "AMM Deposit: account or currency is frozen or locked, "
300 << to_string(accountID) << " " << to_string(asset);
301
302 return ter;
303 }
304
305 return tesSUCCESS;
306 };
307
308 if (auto const ter = checkAsset(ctx.tx[sfAsset]))
309 return ter;
310
311 if (auto const ter = checkAsset(ctx.tx[sfAsset2]))
312 return ter;
313 }
314
315 auto checkAmount = [&](std::optional<STAmount> const& amount, bool checkBalance) -> TER {
316 if (amount)
317 {
318 // This normally should not happen.
319 // Account is not authorized to hold the assets it's depositing,
320 // or it doesn't even have a trust line or MPT for them.
321 if (auto const ter = requireAuth(ctx.view, amount->asset(), accountID))
322 {
323 // LCOV_EXCL_START
324 JLOG(ctx.j.debug())
325 << "AMM Deposit: account is not authorized, " << amount->asset();
326 return ter;
327 // LCOV_EXCL_STOP
328 }
329 if (!ctx.view.rules().enabled(fixCleanup3_3_0))
330 {
331 // AMM account or currency frozen
332 if (auto const ter = checkFrozen(ctx.view, ammAccountID, amount->asset());
333 !isTesSuccess(ter))
334 {
335 JLOG(ctx.j.debug())
336 << "AMM Deposit: AMM account or currency is frozen or locked, "
337 << to_string(accountID);
338 return ter;
339 }
340 // Account frozen
341 if (auto const ter = checkIndividualFrozen(ctx.view, accountID, amount->asset());
342 !isTesSuccess(ter))
343 {
344 JLOG(ctx.j.debug())
345 << "AMM Deposit: account is frozen or locked, " << to_string(accountID)
346 << " " << to_string(amount->asset());
347 return ter;
348 }
349 }
350 if (checkBalance)
351 {
352 if (auto const ter = balance(*amount))
353 {
354 JLOG(ctx.j.debug())
355 << "AMM Deposit: account has insufficient funds, " << *amount;
356 return ter;
357 }
358 }
359 }
360 return tesSUCCESS;
361 };
362
363 // amount and amount2 are deposit min in case of tfLPToken
364 if (!ctx.tx.isFlag(tfLPToken))
365 {
366 if (auto const ter = checkAmount(amount, true))
367 return ter;
368
369 if (auto const ter = checkAmount(amount2, true))
370 return ter;
371 }
372 else
373 {
374 if (auto const ter = checkAmount(amountBalance, false))
375 return ter;
376 if (auto const ter = checkAmount(amount2Balance, false))
377 return ter;
378 }
379
380 // Equal deposit lp tokens
381 if (auto const lpTokens = ctx.tx[~sfLPTokenOut];
382 lpTokens && lpTokens->asset() != lptAMMBalance.asset())
383 {
384 JLOG(ctx.j.debug()) << "AMM Deposit: invalid LPTokens.";
385 return temBAD_AMM_TOKENS;
386 }
387
388 // Check the reserve for LPToken trustline if not LP.
389 // We checked above but need to check again if depositing IOU only.
390 if (ammLPHolds(ctx.view, *ammSle, accountID, ctx.j) == beast::kZero)
391 {
392 STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j);
393 // Insufficient reserve
394 if (xrpBalance <= beast::kZero)
395 {
396 JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
398 }
399 }
400
401 if (auto const ter = canMPTTradeAndTransfer(ctx.view, ctx.tx[sfAsset], accountID, accountID);
402 !isTesSuccess(ter))
403 return ter;
404 if (auto const ter = canMPTTradeAndTransfer(ctx.view, ctx.tx[sfAsset2], accountID, accountID);
405 !isTesSuccess(ter))
406 return ter;
407
408 return tesSUCCESS;
409}
410
413{
414 auto const amount = ctx_.tx[~sfAmount];
415 auto const amount2 = ctx_.tx[~sfAmount2];
416 auto const ePrice = ctx_.tx[~sfEPrice];
417 auto const lpTokensDeposit = ctx_.tx[~sfLPTokenOut];
418 auto ammSle = sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
419 if (!ammSle)
420 return {tecINTERNAL, false}; // LCOV_EXCL_LINE
421 auto const ammAccountID = (*ammSle)[sfAccount];
422
423 auto const expected = ammHolds(
424 sb,
425 *ammSle,
426 amount ? amount->asset() : std::optional<Asset>{},
427 amount2 ? amount2->asset() : std::optional<Asset>{},
430 ctx_.journal);
431 if (!expected)
432 return {expected.error(), false}; // LCOV_EXCL_LINE
433 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
434 auto const tfee = (lptAMMBalance == beast::kZero)
435 ? ctx_.tx[~sfTradingFee].value_or(0)
436 : getTradingFee(ctx_.view(), *ammSle, accountID_);
437
438 auto const subTxType = ctx_.tx.getFlags() & tfDepositSubTx;
439
440 auto const [result, newLPTokenBalance] = [&,
441 &amountBalance = amountBalance,
442 &amount2Balance = amount2Balance,
443 &lptAMMBalance =
444 lptAMMBalance]() -> std::pair<TER, STAmount> {
445 if (subTxType & tfTwoAsset)
446 {
447 return equalDepositLimit(
448 sb,
449 ammAccountID,
450 amountBalance,
451 amount2Balance,
452 lptAMMBalance,
453 *amount,
454 *amount2,
455 lpTokensDeposit,
456 tfee);
457 }
458 if (subTxType & tfOneAssetLPToken)
459 {
460 return singleDepositTokens(
461 sb, ammAccountID, amountBalance, *amount, lptAMMBalance, *lpTokensDeposit, tfee);
462 }
463 if (subTxType & tfLimitLPToken)
464 {
465 return singleDepositEPrice(
466 sb, ammAccountID, amountBalance, *amount, lptAMMBalance, *ePrice, tfee);
467 }
468 if (subTxType & tfSingleAsset)
469 {
470 return singleDeposit(
471 sb, ammAccountID, amountBalance, lptAMMBalance, *amount, lpTokensDeposit, tfee);
472 }
473 if (subTxType & tfLPToken)
474 {
475 return equalDepositTokens(
476 sb,
477 ammAccountID,
478 amountBalance,
479 amount2Balance,
480 lptAMMBalance,
481 *lpTokensDeposit,
482 amount,
483 amount2,
484 tfee);
485 }
486 if (subTxType & tfTwoAssetIfEmpty)
487 {
489 sb, ammAccountID, *amount, *amount2, lptAMMBalance.asset(), tfee);
490 }
491 // should not happen.
492 // LCOV_EXCL_START
493 JLOG(j_.error()) << "AMM Deposit: invalid options.";
495 // LCOV_EXCL_STOP
496 }();
497
498 if (isTesSuccess(result))
499 {
500 XRPL_ASSERT(
501 newLPTokenBalance > beast::kZero,
502 "xrpl::AMMDeposit::applyGuts : valid new LP token balance");
503 // Defensive check: deposit formulas with fixAMMv1_3 round LP tokens
504 // down and asset amounts up, so sqrt(pool1*pool2) >= newLPTokenBalance
505 // is guaranteed to hold. A precision loss failure is not expected.
506 if (sb.rules().enabled(fixCleanup3_3_0) && sb.rules().enabled(fixAMMv1_3))
507 {
508 if (auto const ter = checkAMMPrecisionLoss(
509 sb, ammAccountID, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], newLPTokenBalance, j_);
510 !isTesSuccess(ter))
511 {
512 UNREACHABLE("xrpl::AMMDeposit::applyGuts : AMM precision loss");
513 return {ter, false}; // LCOV_EXCL_LINE
514 }
515 }
516 ammSle->setFieldAmount(sfLPTokenBalance, newLPTokenBalance);
517 // LP depositing into AMM empty state gets the auction slot
518 // and the voting
519 if (lptAMMBalance == beast::kZero)
520 initializeFeeAuctionVote(sb, ammSle, accountID_, lptAMMBalance.asset(), tfee);
521
522 sb.update(ammSle);
523 }
524
525 return {result, isTesSuccess(result)};
526}
527
528TER
530{
531 // This is the ledger view that we work against. Transactions are applied
532 // as we go on processing transactions.
533 Sandbox sb(&ctx_.view());
534
535 auto const result = applyGuts(sb);
536 if (result.second)
537 sb.apply(ctx_.rawView());
538
539 return result.first;
540}
541
544 Sandbox& view,
545 AccountID const& ammAccount,
546 STAmount const& amountBalance,
547 STAmount const& amountDeposit,
548 std::optional<STAmount> const& amount2Deposit,
549 STAmount const& lptAMMBalance,
550 STAmount const& lpTokensDeposit,
551 std::optional<STAmount> const& depositMin,
552 std::optional<STAmount> const& deposit2Min,
553 std::optional<STAmount> const& lpTokensDepositMin,
554 std::uint16_t tfee)
555{
556 // Check account has sufficient funds.
557 // Return true if it does, false otherwise.
558 auto checkBalance = [&](auto const& depositAmount) -> TER {
559 if (depositAmount <= beast::kZero)
560 return temBAD_AMOUNT;
561 if (isXRP(depositAmount))
562 {
563 auto const& lpIssue = lpTokensDeposit.get<Issue>();
564 // Adjust the reserve if LP doesn't have LPToken trustline
565 auto const sle =
566 view.read(keylet::trustLine(accountID_, lpIssue.account, lpIssue.currency));
567 if (xrpLiquid(view, accountID_, !sle, j_) >= depositAmount)
568 return tesSUCCESS;
569 }
570 else if (
572 view,
574 depositAmount,
577 ctx_.journal) >= depositAmount)
578 {
579 return tesSUCCESS;
580 }
581 return tecUNFUNDED_AMM;
582 };
583
584 auto const [amountDepositActual, amount2DepositActual, lpTokensDepositActual] =
586 amountBalance,
587 amountDeposit,
588 amount2Deposit,
589 lptAMMBalance,
590 lpTokensDeposit,
591 tfee,
593
594 if (lpTokensDepositActual <= beast::kZero)
595 {
596 JLOG(ctx_.journal.debug()) << "AMM Deposit: adjusted tokens zero";
598 }
599
600 if (amountDepositActual < depositMin || amount2DepositActual < deposit2Min ||
601 lpTokensDepositActual < lpTokensDepositMin)
602 {
603 JLOG(ctx_.journal.debug())
604 << "AMM Deposit: min deposit fails " << amountDepositActual << " "
605 << depositMin.value_or(STAmount{}) << " " << amount2DepositActual.value_or(STAmount{})
606 << " " << deposit2Min.value_or(STAmount{}) << " " << lpTokensDepositActual << " "
607 << lpTokensDepositMin.value_or(STAmount{});
608 return {tecAMM_FAILED, STAmount{}};
609 }
610
611 // Deposit amountDeposit
612 if (auto const ter = checkBalance(amountDepositActual))
613 {
614 JLOG(ctx_.journal.debug()) << "AMM Deposit: account has insufficient "
615 "checkBalance to deposit or is 0"
616 << amountDepositActual;
617 return {ter, STAmount{}};
618 }
619
620 auto res = accountSend(
621 view, accountID_, ammAccount, amountDepositActual, ctx_.journal, WaiveTransferFee::Yes);
622 if (!isTesSuccess(res))
623 {
624 JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit " << amountDepositActual;
625 return {res, STAmount{}};
626 }
627
628 // Deposit amount2Deposit
629 if (amount2DepositActual)
630 {
631 if (auto const ter = checkBalance(*amount2DepositActual))
632 {
633 JLOG(ctx_.journal.debug()) << "AMM Deposit: account has insufficient checkBalance to "
634 "deposit or is 0 "
635 << *amount2DepositActual;
636 return {ter, STAmount{}};
637 }
638
639 res = accountSend(
640 view,
642 ammAccount,
643 *amount2DepositActual,
644 ctx_.journal,
646 if (!isTesSuccess(res))
647 {
648 JLOG(ctx_.journal.debug())
649 << "AMM Deposit: failed to deposit " << *amount2DepositActual;
650 return {res, STAmount{}};
651 }
652 }
653
654 // Deposit LP tokens
655 res = accountSend(view, ammAccount, accountID_, lpTokensDepositActual, ctx_.journal);
656 if (!isTesSuccess(res))
657 {
658 JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit LPTokens";
659 return {res, STAmount{}};
660 }
661
662 return {tesSUCCESS, lptAMMBalance + lpTokensDepositActual};
663}
664
665static STAmount
667 Rules const& rules,
668 STAmount const& lptAMMBalance,
669 STAmount const& lpTokensDeposit)
670{
671 if (!rules.enabled(fixAMMv1_3))
672 return lpTokensDeposit;
673 return adjustLPTokens(lptAMMBalance, lpTokensDeposit, IsDeposit::Yes);
674}
675
681 Sandbox& view,
682 AccountID const& ammAccount,
683 STAmount const& amountBalance,
684 STAmount const& amount2Balance,
685 STAmount const& lptAMMBalance,
686 STAmount const& lpTokensDeposit,
687 std::optional<STAmount> const& depositMin,
688 std::optional<STAmount> const& deposit2Min,
689 std::uint16_t tfee)
690{
691 try
692 {
693 auto const tokensAdj = adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit);
694 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::kZero)
696 auto const frac = divide(tokensAdj, lptAMMBalance, lptAMMBalance.asset());
697 // amounts factor in the adjusted tokens
698 auto const amountDeposit =
699 getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::Yes);
700 auto const amount2Deposit =
701 getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::Yes);
702 return deposit(
703 view,
704 ammAccount,
705 amountBalance,
706 amountDeposit,
707 amount2Deposit,
708 lptAMMBalance,
709 tokensAdj,
710 depositMin,
711 deposit2Min,
712 std::nullopt,
713 tfee);
714 }
715 catch (std::exception const& e)
716 {
717 // LCOV_EXCL_START
718 JLOG(j_.error()) << "AMMDeposit::equalDepositTokens exception " << e.what();
719 return {tecINTERNAL, STAmount{}};
720 // LCOV_EXCL_STOP
721 }
722}
723
754 Sandbox& view,
755 AccountID const& ammAccount,
756 STAmount const& amountBalance,
757 STAmount const& amount2Balance,
758 STAmount const& lptAMMBalance,
759 STAmount const& amount,
760 STAmount const& amount2,
761 std::optional<STAmount> const& lpTokensDepositMin,
762 std::uint16_t tfee)
763{
764 auto frac = Number{amount} / amountBalance;
765 auto tokensAdj = getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes);
766 if (tokensAdj == beast::kZero)
767 {
768 if (!view.rules().enabled(fixAMMv1_3))
769 {
770 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
771 }
772
774 }
775 // factor in the adjusted tokens
776 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
777 auto const amount2Deposit = getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::Yes);
778 if (amount2Deposit <= amount2)
779 {
780 return deposit(
781 view,
782 ammAccount,
783 amountBalance,
784 amount,
785 amount2Deposit,
786 lptAMMBalance,
787 tokensAdj,
788 std::nullopt,
789 std::nullopt,
790 lpTokensDepositMin,
791 tfee);
792 }
793 frac = Number{amount2} / amount2Balance;
794 tokensAdj = getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::Yes);
795 if (tokensAdj == beast::kZero)
796 {
797 if (!view.rules().enabled(fixAMMv1_3))
798 {
799 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
800 }
801
802 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
803 }
804 // factor in the adjusted tokens
805 frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac);
806 auto const amountDeposit = getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::Yes);
807 if (amountDeposit <= amount)
808 {
809 return deposit(
810 view,
811 ammAccount,
812 amountBalance,
813 amountDeposit,
814 amount2,
815 lptAMMBalance,
816 tokensAdj,
817 std::nullopt,
818 std::nullopt,
819 lpTokensDepositMin,
820 tfee);
821 }
822 return {tecAMM_FAILED, STAmount{}};
823}
824
835 Sandbox& view,
836 AccountID const& ammAccount,
837 STAmount const& amountBalance,
838 STAmount const& lptAMMBalance,
839 STAmount const& amount,
840 std::optional<STAmount> const& lpTokensDepositMin,
841 std::uint16_t tfee)
842{
843 auto const tokens = adjustLPTokensOut(
844 view.rules(), lptAMMBalance, lpTokensOut(amountBalance, amount, lptAMMBalance, tfee));
845 if (tokens == beast::kZero)
846 {
847 if (!view.rules().enabled(fixAMMv1_3))
848 {
849 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
850 }
851
853 }
854 // factor in the adjusted tokens
855 auto const [tokensAdj, amountDepositAdj] =
856 adjustAssetInByTokens(view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
857 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::kZero)
858 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
859 return deposit(
860 view,
861 ammAccount,
862 amountBalance,
863 amountDepositAdj,
864 std::nullopt,
865 lptAMMBalance,
866 tokensAdj,
867 std::nullopt,
868 std::nullopt,
869 lpTokensDepositMin,
870 tfee);
871}
872
882 Sandbox& view,
883 AccountID const& ammAccount,
884 STAmount const& amountBalance,
885 STAmount const& amount,
886 STAmount const& lptAMMBalance,
887 STAmount const& lpTokensDeposit,
888 std::uint16_t tfee)
889{
890 auto const tokensAdj = adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit);
891 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::kZero)
893 // the adjusted tokens are factored in
894 auto const amountDeposit = ammAssetIn(amountBalance, lptAMMBalance, tokensAdj, tfee);
895 if (amountDeposit > amount)
896 return {tecAMM_FAILED, STAmount{}};
897 return deposit(
898 view,
899 ammAccount,
900 amountBalance,
901 amountDeposit,
902 std::nullopt,
903 lptAMMBalance,
904 tokensAdj,
905 std::nullopt,
906 std::nullopt,
907 std::nullopt,
908 tfee);
909}
910
938 Sandbox& view,
939 AccountID const& ammAccount,
940 STAmount const& amountBalance,
941 STAmount const& amount,
942 STAmount const& lptAMMBalance,
943 STAmount const& ePrice,
944 std::uint16_t tfee)
945{
946 if (amount != beast::kZero)
947 {
948 auto const tokens = adjustLPTokensOut(
949 view.rules(), lptAMMBalance, lpTokensOut(amountBalance, amount, lptAMMBalance, tfee));
950 if (tokens <= beast::kZero)
951 {
952 if (!view.rules().enabled(fixAMMv1_3))
953 {
954 return {tecAMM_FAILED, STAmount{}}; // LCOV_EXCL_LINE
955 }
956
958 }
959 // factor in the adjusted tokens
960 auto const [tokensAdj, amountDepositAdj] =
961 adjustAssetInByTokens(view.rules(), amountBalance, amount, lptAMMBalance, tokens, tfee);
962 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::kZero)
963 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
964 auto const ep = Number{amountDepositAdj} / tokensAdj;
965 if (ep <= ePrice)
966 {
967 return deposit(
968 view,
969 ammAccount,
970 amountBalance,
971 amountDepositAdj,
972 std::nullopt,
973 lptAMMBalance,
974 tokensAdj,
975 std::nullopt,
976 std::nullopt,
977 std::nullopt,
978 tfee);
979 }
980 }
981
982 // LPTokens is asset out => E = b / t
983 // substituting t in formula (3) as b/E:
984 // b/E = T * [b/B - sqrt(t2**2 + b/(f1*B)) + t2]/
985 // [1 + sqrt(t2**2 + b/(f1*B)) -t2] (A)
986 // where f1 = 1 - fee, f2 = (1 - fee/2)/f1
987 // Let R = b/(f1*B), then b/B = f1*R and b = R*f1*B
988 // Then (A) is
989 // R*f1*B = E*T*[R*f1 -sqrt(f2**2 + R) + f2]/[1 + sqrt(f2**2 + R) - f2] =>
990 // Let c = f1*B/(E*T) =>
991 // R*c*(1 + sqrt(f2**2 + R) + f2) = R*f1 - sqrt(f2**2 + R) - f2 =>
992 // (R*c + 1)*sqrt(f2**2 + R) = R*(f1 + c*f2 - c) + f2 =>
993 // Let d = f1 + c*f2 - c =>
994 // (R*c + 1)*sqrt(f2**2 + R) = R*d + f2 =>
995 // (R*c + 1)**2 * (f2**2 + R) = (R*d + f2)**2 =>
996 // (R*c)**2 + R*((c*f2)**2 + 2*c - d**2) + 2*c*f2**2 + 1 -2*d*f2 = 0 =>
997 // a1 = c**2, b1 = (c*f2)**2 + 2*c - d**2, c1 = 2*c*f2**2 + 1 - 2*d*f2
998 // R = (-b1 + sqrt(b1**2 + 4*a1*c1))/(2*a1)
999 auto const f1 = feeMult(tfee);
1000 auto const f2 = feeMultHalf(tfee) / f1;
1001 auto const c = f1 * amountBalance / (ePrice * lptAMMBalance);
1002 auto const d = f1 + c * f2 - c;
1003 auto const a1 = c * c;
1004 auto const b1 = c * c * f2 * f2 + 2 * c - d * d;
1005 auto const c1 = 2 * c * f2 * f2 + 1 - 2 * d * f2;
1006 auto amtNoRoundCb = [&] { return f1 * amountBalance * solveQuadraticEq(a1, b1, c1); };
1007 auto amtProdCb = [&] { return f1 * solveQuadraticEq(a1, b1, c1); };
1008 auto const amountDeposit =
1009 getRoundedAsset(view.rules(), amtNoRoundCb, amountBalance, amtProdCb, IsDeposit::Yes);
1010 if (amountDeposit <= beast::kZero)
1011 return {tecAMM_FAILED, STAmount{}};
1012 auto tokNoRoundCb = [&] { return amountDeposit / ePrice; };
1013 auto tokProdCb = [&] { return amountDeposit / ePrice; };
1014 auto const tokens =
1015 getRoundedLPTokens(view.rules(), tokNoRoundCb, lptAMMBalance, tokProdCb, IsDeposit::Yes);
1016 // factor in the adjusted tokens
1017 auto const [tokensAdj, amountDepositAdj] = adjustAssetInByTokens(
1018 view.rules(), amountBalance, amountDeposit, lptAMMBalance, tokens, tfee);
1019 if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::kZero)
1020 return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
1021
1022 return deposit(
1023 view,
1024 ammAccount,
1025 amountBalance,
1026 amountDepositAdj,
1027 std::nullopt,
1028 lptAMMBalance,
1029 tokensAdj,
1030 std::nullopt,
1031 std::nullopt,
1032 std::nullopt,
1033 tfee);
1034}
1035
1038 Sandbox& view,
1039 AccountID const& ammAccount,
1040 STAmount const& amount,
1041 STAmount const& amount2,
1042 Asset const& lptIssue,
1043 std::uint16_t tfee)
1044{
1045 return deposit(
1046 view,
1047 ammAccount,
1048 amount,
1049 amount,
1050 amount2,
1051 STAmount{lptIssue, 0},
1052 ammLPTokens(amount, amount2, lptIssue),
1053 std::nullopt,
1054 std::nullopt,
1055 std::nullopt,
1056 tfee);
1057}
1058
1059void
1061{
1062 // No transaction-specific invariants yet (future work).
1063}
1064
1065bool
1067{
1068 // No transaction-specific invariants yet (future work).
1069 return true;
1070}
1071
1072} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Stream debug() const
Definition Journal.h:297
static TER preclaim(PreclaimContext const &ctx)
static bool checkExtraFeatures(PreflightContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override
Inspect a single ledger entry modified by this transaction.
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 > equalDepositInEmptyState(Sandbox &view, AccountID const &ammAccount, STAmount const &amount, STAmount const &amount2, Asset const &lptIssue, std::uint16_t tfee)
Equal deposit in empty AMM state (LP tokens balance is 0).
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.
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.
std::pair< TER, bool > applyGuts(Sandbox &view)
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)
TER doApply() override
bool finalizeInvariants(STTx const &tx, TER result, XRPAmount fee, ReadView const &view, beast::Journal const &j) override
Check transaction-specific post-conditions after all entries have been visited.
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 > 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.
A currency issued by an account.
Definition Issue.h:13
Number is a floating point type that can represent a wide range of values.
Definition Number.h:306
A view into a ledger.
Definition ReadView.h:31
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
Rules controlling protocol behavior.
Definition Rules.h:33
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:171
constexpr TIss const & get() const
Asset const & asset() const
Definition STAmount.h:478
std::shared_ptr< STLedgerEntry const > const & const_ref
bool isFlag(std::uint32_t) const
Definition STObject.cpp:501
AccountID getAccountID(SField const &field) const
Definition STObject.cpp:633
std::uint32_t getFlags() const
Definition STObject.cpp:507
Discardable, editable view to a ledger.
Definition Sandbox.h:15
void apply(RawView &to)
Definition Sandbox.h:35
beast::Journal const j_
Definition Transactor.h:118
ApplyView & view()
Definition Transactor.h:136
AccountID const accountID_
Definition Transactor.h:120
ApplyContext & ctx_
Definition Transactor.h:116
SLE::pointer peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
void update(SLE::ref sle) override
Indicate changes to a peeked SLE.
Rules const & rules() const override
Returns the tx processing rules.
T make_optional(T... args)
T make_pair(T... args)
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:425
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:241
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Asset, Asset > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition AMMCore.cpp:97
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:69
@ terNO_AMM
Definition TER.h:219
constexpr std::uint16_t kTradingFeeThreshold
Definition AMMCore.h:11
STAmount ammLPHolds(ReadView const &view, Asset const &asset1, Asset const &asset2, AccountID const &ammAccount, AccountID const &lpAccount, beast::Journal const j)
Get the balance of LP tokens.
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
static STAmount adjustLPTokensOut(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &lpTokensDeposit)
Number feeMultHalf(std::uint16_t tfee)
Get fee multiplier (1 - tfee / 2) @tfee trading fee in basis points.
Definition AMMCore.h:96
TER checkIndividualFrozen(ReadView const &view, AccountID const &account, Asset const &asset)
bool ammEnabled(Rules const &)
Return true if required AMM amendment is enabled.
Definition AMMCore.cpp:128
constexpr FlagValue tfDepositSubTx
Definition TxFlags.h:398
bool isXRP(AccountID const &c)
Definition AccountID.h:70
Number adjustFracByTokens(Rules const &rules, STAmount const &lptAMMBalance, STAmount const &tokens, Number const &frac)
Find a fraction of tokens after the tokens are adjusted.
STAmount ammLPTokens(STAmount const &asset1, STAmount const &asset2, Asset const &lptIssue)
Calculate LP Tokens given AMM pool reserves.
NotTEC invalidAMMAssetPair(Asset const &asset1, Asset const &asset2, std::optional< std::pair< Asset, Asset > > const &pair=std::nullopt)
Definition AMMCore.cpp:82
void initializeFeeAuctionVote(ApplyView &view, SLE::pointer &ammSle, AccountID const &account, Asset const &lptAsset, std::uint16_t tfee)
Initialize Auction and Voting slots and set the trading/discounted fee.
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
TER canMPTTradeAndTransfer(ReadView const &v, Asset const &asset, AccountID const &from, AccountID const &to)
Convenience to combine canTrade/Transfer.
Number solveQuadraticEq(Number const &a, Number const &b, Number const &c)
Positive solution for quadratic equation: x = (-b + sqrt(b**2 + 4*a*c))/(2*a).
STAmount getRoundedLPTokens(Rules const &rules, STAmount const &balance, Number const &frac, IsDeposit isDeposit)
Round AMM deposit/withdrawal LPToken amount.
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
std::pair< STAmount, STAmount > adjustAssetInByTokens(Rules const &rules, STAmount const &balance, STAmount const &amount, STAmount const &lptAMMBalance, STAmount const &tokens, std::uint16_t tfee)
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:594
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Definition AMMHelpers.h:626
Number feeMult(std::uint16_t tfee)
Get fee multiplier (1 - tfee) @tfee trading fee in basis points.
Definition AMMCore.h:87
TER checkAMMPrecisionLoss(Number const &poolProductMean, STAmount const &newLPTokenBalance)
Check AMM pool product invariant after an AMM operation that changes LP tokens (deposit/withdraw/claw...
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No, AllowMPTOverflow allowOverflow=AllowMPTOverflow::No)
Calls static accountSendIOU if saAmount represents Issue.
STAmount ammAssetIn(STAmount const &asset1Balance, STAmount const &lptAMMBalance, STAmount const &lpTokens, std::uint16_t tfee)
Calculate asset deposit given LP Tokens.
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:28
TER checkDepositFreeze(ReadView const &view, AccountID const &srcAcct, AccountID const &dstAcct, Asset const &asset)
Checks freeze compliance for depositing an asset into a pseudo-account (e.g.
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...
@ temBAD_FEE
Definition TER.h:78
@ temBAD_AMM_TOKENS
Definition TER.h:115
@ temMALFORMED
Definition TER.h:73
@ temBAD_AMOUNT
Definition TER.h:75
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
TERSubset< CanCvtToTER > TER
Definition TER.h:634
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, std::uint8_t depth=0)
Check if the account lacks required authorization for MPT.
@ tecAMM_EMPTY
Definition TER.h:330
@ tecAMM_INVALID_TOKENS
Definition TER.h:329
@ tecINSUF_RESERVE_LINE
Definition TER.h:286
@ tecAMM_FAILED
Definition TER.h:328
@ tecAMM_NOT_EMPTY
Definition TER.h:331
@ tecUNFUNDED_AMM
Definition TER.h:326
@ tecINTERNAL
Definition TER.h:308
std::expected< std::tuple< STAmount, STAmount, STAmount >, TER > ammHolds(ReadView const &view, SLE const &ammSle, std::optional< Asset > const &optAsset1, std::optional< Asset > const &optAsset2, FreezeHandling freezeHandling, AuthHandling authHandling, beast::Journal const j)
Get AMM pool and LP token balances.
std::uint16_t getTradingFee(ReadView const &view, SLE const &ammSle, AccountID const &account)
Get AMM trading fee for the given account.
@ tesSUCCESS
Definition TER.h:240
STAmount lpTokensOut(STAmount const &asset1Balance, STAmount const &asset1Deposit, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's deposit amount.
T popcount(T... args)
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
ReadView const & view
Definition Transactor.h:64
beast::Journal const j
Definition Transactor.h:69
State information when preflighting a tx.
Definition Transactor.h:18
beast::Journal const j
Definition Transactor.h:25
T value_or(T... args)
T what(T... args)