1#include <xrpl/tx/transactors/dex/AMMDeposit.h>
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>
41 auto const amount = ctx.
tx[~sfAmount];
42 auto const amount2 = ctx.
tx[~sfAmount2];
46 !(amount && amount->holds<
MPTIssue>()) && !(amount2 && amount2->holds<
MPTIssue>()));
52 return tfAMMDepositMask;
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];
73 JLOG(ctx.
j.
debug()) <<
"AMM Deposit: invalid flags.";
79 if (!lpTokens || ePrice || (amount && !amount2) || (!amount && amount2) || tradingFee)
82 else if (ctx.
tx.
isFlag(tfSingleAsset))
85 if (!amount || amount2 || ePrice || tradingFee)
91 if (!amount || !amount2 || ePrice || tradingFee)
94 else if (ctx.
tx.
isFlag(tfOneAssetLPToken))
96 if (!amount || !lpTokens || amount2 || ePrice || tradingFee)
99 else if (ctx.
tx.
isFlag(tfLimitLPToken))
101 if (!amount || !ePrice || lpTokens || amount2 || tradingFee)
104 else if (ctx.
tx.
isFlag(tfTwoAssetIfEmpty))
106 if (!amount || !amount2 || ePrice || lpTokens)
110 auto const asset = ctx.
tx[sfAsset];
111 auto const asset2 = ctx.
tx[sfAsset2];
114 JLOG(ctx.
j.
debug()) <<
"AMM Deposit: invalid asset pair.";
118 if (amount && amount2 && amount->asset() == amount2->asset())
120 JLOG(ctx.
j.
debug()) <<
"AMM Deposit: invalid tokens, same issue." << amount->asset() <<
" "
125 if (lpTokens && *lpTokens <= beast::kZero)
127 JLOG(ctx.
j.
debug()) <<
"AMM Deposit: invalid LPTokens";
136 JLOG(ctx.
j.
debug()) <<
"AMM Deposit: invalid amount";
146 JLOG(ctx.
j.
debug()) <<
"AMM Deposit: invalid amount2";
151 if (amount && ePrice)
162 JLOG(ctx.
j.
debug()) <<
"AMM Deposit: invalid EPrice";
169 JLOG(ctx.
j.
debug()) <<
"AMM Deposit: invalid trading fee.";
179 auto const accountID = ctx.
tx[sfAccount];
184 JLOG(ctx.
j.
debug()) <<
"AMM Deposit: Invalid asset pair.";
197 return expected.error();
198 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
199 if (ctx.
tx.
isFlag(tfTwoAssetIfEmpty))
201 if (lptAMMBalance != beast::kZero)
203 if (amountBalance != beast::kZero || amount2Balance != beast::kZero)
206 JLOG(ctx.
j.
debug()) <<
"AMM Deposit: tokens balance is not zero.";
213 if (lptAMMBalance == beast::kZero)
215 if (amountBalance <= beast::kZero || amount2Balance <= beast::kZero ||
216 lptAMMBalance < beast::kZero)
219 JLOG(ctx.
j.
debug()) <<
"AMM Deposit: reserves or tokens balance is zero.";
230 auto balance = [&](
auto const&
deposit) ->
TER {
233 auto const lpIssue = (*ammSle)[sfLPTokenBalance].get<
Issue>();
254 auto const amount = ctx.
tx[~sfAmount];
255 auto const amount2 = ctx.
tx[~sfAmount2];
256 auto const ammAccountID = ammSle->
getAccountID(sfAccount);
262 auto checkAsset = [&](
Asset const& asset) ->
TER {
265 JLOG(ctx.
j.
debug()) <<
"AMM Deposit: account is not authorized, " << asset;
277 if (
auto const ter = checkAsset(ctx.
tx[sfAsset]))
280 if (
auto const ter = checkAsset(ctx.
tx[sfAsset2]))
287 auto checkAsset = [&](
Asset const& asset) ->
TER {
293 JLOG(ctx.
j.
debug()) <<
"AMM Deposit: account is not authorized, " << asset;
299 JLOG(ctx.
j.
debug()) <<
"AMM Deposit: account or currency is frozen or locked, "
308 if (
auto const ter = checkAsset(ctx.
tx[sfAsset]))
311 if (
auto const ter = checkAsset(ctx.
tx[sfAsset2]))
321 if (
auto const ter =
requireAuth(ctx.
view, amount->asset(), accountID))
325 <<
"AMM Deposit: account is not authorized, " << amount->asset();
332 if (
auto const ter =
checkFrozen(ctx.
view, ammAccountID, amount->asset());
336 <<
"AMM Deposit: AMM account or currency is frozen or locked, "
345 <<
"AMM Deposit: account is frozen or locked, " <<
to_string(accountID)
352 if (
auto const ter = balance(*amount))
355 <<
"AMM Deposit: account has insufficient funds, " << *amount;
366 if (
auto const ter = checkAmount(amount,
true))
369 if (
auto const ter = checkAmount(amount2,
true))
374 if (
auto const ter = checkAmount(amountBalance,
false))
376 if (
auto const ter = checkAmount(amount2Balance,
false))
381 if (
auto const lpTokens = ctx.
tx[~sfLPTokenOut];
382 lpTokens && lpTokens->asset() != lptAMMBalance.asset())
384 JLOG(ctx.
j.
debug()) <<
"AMM Deposit: invalid LPTokens.";
394 if (xrpBalance <= beast::kZero)
396 JLOG(ctx.
j.
debug()) <<
"AMM Instance: insufficient reserves";
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];
421 auto const ammAccountID = (*ammSle)[sfAccount];
432 return {expected.error(),
false};
433 auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
434 auto const tfee = (lptAMMBalance == beast::kZero)
435 ?
ctx_.tx[~sfTradingFee].value_or(0)
440 auto const [result, newLPTokenBalance] = [&,
441 &amountBalance = amountBalance,
442 &amount2Balance = amount2Balance,
445 if (subTxType & tfTwoAsset)
458 if (subTxType & tfOneAssetLPToken)
461 sb, ammAccountID, amountBalance, *amount, lptAMMBalance, *lpTokensDeposit, tfee);
463 if (subTxType & tfLimitLPToken)
466 sb, ammAccountID, amountBalance, *amount, lptAMMBalance, *ePrice, tfee);
468 if (subTxType & tfSingleAsset)
471 sb, ammAccountID, amountBalance, lptAMMBalance, *amount, lpTokensDeposit, tfee);
473 if (subTxType & tfLPToken)
486 if (subTxType & tfTwoAssetIfEmpty)
489 sb, ammAccountID, *amount, *amount2, lptAMMBalance.asset(), tfee);
493 JLOG(
j_.error()) <<
"AMM Deposit: invalid options.";
501 newLPTokenBalance > beast::kZero,
502 "xrpl::AMMDeposit::applyGuts : valid new LP token balance");
509 sb, ammAccountID,
ctx_.tx[sfAsset],
ctx_.tx[sfAsset2], newLPTokenBalance,
j_);
512 UNREACHABLE(
"xrpl::AMMDeposit::applyGuts : AMM precision loss");
516 ammSle->setFieldAmount(sfLPTokenBalance, newLPTokenBalance);
519 if (lptAMMBalance == beast::kZero)
558 auto checkBalance = [&](
auto const& depositAmount) ->
TER {
559 if (depositAmount <= beast::kZero)
561 if (
isXRP(depositAmount))
563 auto const& lpIssue = lpTokensDeposit.
get<
Issue>();
577 ctx_.journal) >= depositAmount)
584 auto const [amountDepositActual, amount2DepositActual, lpTokensDepositActual] =
594 if (lpTokensDepositActual <= beast::kZero)
596 JLOG(
ctx_.journal.debug()) <<
"AMM Deposit: adjusted tokens zero";
600 if (amountDepositActual < depositMin || amount2DepositActual < deposit2Min ||
601 lpTokensDepositActual < lpTokensDepositMin)
603 JLOG(
ctx_.journal.debug())
604 <<
"AMM Deposit: min deposit fails " << amountDepositActual <<
" "
606 <<
" " << deposit2Min.
value_or(
STAmount{}) <<
" " << lpTokensDepositActual <<
" "
612 if (
auto const ter = checkBalance(amountDepositActual))
614 JLOG(
ctx_.journal.debug()) <<
"AMM Deposit: account has insufficient "
615 "checkBalance to deposit or is 0"
616 << amountDepositActual;
624 JLOG(
ctx_.journal.debug()) <<
"AMM Deposit: failed to deposit " << amountDepositActual;
629 if (amount2DepositActual)
631 if (
auto const ter = checkBalance(*amount2DepositActual))
633 JLOG(
ctx_.journal.debug()) <<
"AMM Deposit: account has insufficient checkBalance to "
635 << *amount2DepositActual;
643 *amount2DepositActual,
648 JLOG(
ctx_.journal.debug())
649 <<
"AMM Deposit: failed to deposit " << *amount2DepositActual;
658 JLOG(
ctx_.journal.debug()) <<
"AMM Deposit: failed to deposit LPTokens";
662 return {
tesSUCCESS, lptAMMBalance + lpTokensDepositActual};
671 if (!rules.
enabled(fixAMMv1_3))
672 return lpTokensDeposit;
694 if (
view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::kZero)
696 auto const frac =
divide(tokensAdj, lptAMMBalance, lptAMMBalance.
asset());
698 auto const amountDeposit =
700 auto const amount2Deposit =
718 JLOG(
j_.error()) <<
"AMMDeposit::equalDepositTokens exception " << e.
what();
764 auto frac =
Number{amount} / amountBalance;
766 if (tokensAdj == beast::kZero)
768 if (!
view.rules().enabled(fixAMMv1_3))
778 if (amount2Deposit <= amount2)
793 frac =
Number{amount2} / amount2Balance;
795 if (tokensAdj == beast::kZero)
797 if (!
view.rules().enabled(fixAMMv1_3))
807 if (amountDeposit <= amount)
844 view.rules(), lptAMMBalance,
lpTokensOut(amountBalance, amount, lptAMMBalance, tfee));
845 if (tokens == beast::kZero)
847 if (!
view.rules().enabled(fixAMMv1_3))
855 auto const [tokensAdj, amountDepositAdj] =
857 if (
view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::kZero)
891 if (
view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::kZero)
894 auto const amountDeposit =
ammAssetIn(amountBalance, lptAMMBalance, tokensAdj, tfee);
895 if (amountDeposit > amount)
946 if (amount != beast::kZero)
949 view.rules(), lptAMMBalance,
lpTokensOut(amountBalance, amount, lptAMMBalance, tfee));
950 if (tokens <= beast::kZero)
952 if (!
view.rules().enabled(fixAMMv1_3))
960 auto const [tokensAdj, amountDepositAdj] =
962 if (
view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::kZero)
964 auto const ep =
Number{amountDepositAdj} / tokensAdj;
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); };
1008 auto const amountDeposit =
1010 if (amountDeposit <= beast::kZero)
1012 auto tokNoRoundCb = [&] {
return amountDeposit / ePrice; };
1013 auto tokProdCb = [&] {
return amountDeposit / ePrice; };
1018 view.rules(), amountBalance, amountDeposit, lptAMMBalance, tokens, tfee);
1019 if (
view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::kZero)
1042 Asset const& lptIssue,
A generic endpoint for log messages.
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)
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.
Number is a floating point type that can represent a wide range of values.
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.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
constexpr TIss const & get() const
Asset const & asset() const
std::shared_ptr< STLedgerEntry const > const & const_ref
bool isFlag(std::uint32_t) const
AccountID getAccountID(SField const &field) const
std::uint32_t getFlags() const
Discardable, editable view to a ledger.
AccountID const accountID_
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)
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const ¤cy) noexcept
The index of a trust line for a given currency.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Asset, Asset > > const &pair=std::nullopt, bool validZero=false)
Validate the amount.
STAmount divide(STAmount const &amount, Rate const &rate)
constexpr std::uint16_t kTradingFeeThreshold
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.
TER checkIndividualFrozen(ReadView const &view, AccountID const &account, Asset const &asset)
bool ammEnabled(Rules const &)
Return true if required AMM amendment is enabled.
constexpr FlagValue tfDepositSubTx
bool isXRP(AccountID const &c)
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)
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)
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
STAmount getRoundedAsset(Rules const &rules, STAmount const &balance, A const &frac, IsDeposit isDeposit)
Round AMM equal deposit/withdrawal amount.
Number feeMult(std::uint16_t tfee)
Get fee multiplier (1 - tfee) @tfee trading fee in basis points.
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.
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...
bool isTesSuccess(TER x) noexcept
TERSubset< CanCvtToTER > TER
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.
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.
STAmount lpTokensOut(STAmount const &asset1Balance, STAmount const &asset1Deposit, STAmount const &lptAMMBalance, std::uint16_t tfee)
Calculate LP Tokens given asset's deposit amount.
State information when determining if a tx is likely to claim a fee.
State information when preflighting a tx.