1#include <xrpl/tx/transactors/dex/AMMBid.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/RippleStateHelpers.h>
10#include <xrpl/ledger/helpers/TokenHelpers.h>
11#include <xrpl/protocol/AMMCore.h>
12#include <xrpl/protocol/AccountID.h>
13#include <xrpl/protocol/AmountConversions.h>
14#include <xrpl/protocol/Feature.h>
15#include <xrpl/protocol/Indexes.h>
16#include <xrpl/protocol/Issue.h>
17#include <xrpl/protocol/MPTIssue.h>
18#include <xrpl/protocol/SField.h>
19#include <xrpl/protocol/STLedgerEntry.h>
20#include <xrpl/protocol/STTx.h>
21#include <xrpl/protocol/TER.h>
22#include <xrpl/protocol/XRPAmount.h>
23#include <xrpl/tx/ApplyContext.h>
24#include <xrpl/tx/Transactor.h>
54 JLOG(ctx.
j.
debug()) <<
"AMM Bid: Invalid asset pair.";
58 if (
auto const bidMin = ctx.
tx[~sfBidMin])
62 JLOG(ctx.
j.
debug()) <<
"AMM Bid: invalid min slot price.";
67 if (
auto const bidMax = ctx.
tx[~sfBidMax])
71 JLOG(ctx.
j.
debug()) <<
"AMM Bid: invalid max slot price.";
81 JLOG(ctx.
j.
debug()) <<
"AMM Bid: Invalid number of AuthAccounts.";
88 for (
auto const& obj : authAccounts)
90 auto authAccount = obj[sfAccount];
91 if (authAccount == account || unique.contains(authAccount))
93 JLOG(ctx.
j.
debug()) <<
"AMM Bid: Invalid auth.account.";
96 unique.insert(authAccount);
110 JLOG(ctx.
j.
debug()) <<
"AMM Bid: Invalid asset pair.";
114 auto const lpTokensBalance = (*ammSle)[sfLPTokenBalance];
115 if (lpTokensBalance == beast::kZero)
124 JLOG(ctx.
j.
debug()) <<
"AMM Bid: Invalid Account.";
132 if (lpTokens == beast::kZero)
134 JLOG(ctx.
j.
debug()) <<
"AMM Bid: account is not LP.";
138 auto const bidMin = ctx.
tx[~sfBidMin];
142 if (bidMin->asset() != lpTokens.asset())
144 JLOG(ctx.
j.
debug()) <<
"AMM Bid: Invalid LPToken.";
147 if (*bidMin > lpTokens || *bidMin >= lpTokensBalance)
149 JLOG(ctx.
j.
debug()) <<
"AMM Bid: Invalid Tokens.";
154 auto const bidMax = ctx.
tx[~sfBidMax];
157 if (bidMax->asset() != lpTokens.asset())
159 JLOG(ctx.
j.
debug()) <<
"AMM Bid: Invalid LPToken.";
162 if (*bidMax > lpTokens || *bidMax >= lpTokensBalance)
164 JLOG(ctx.
j.
debug()) <<
"AMM Bid: Invalid Tokens.";
169 if (bidMin && bidMax && bidMin > bidMax)
171 JLOG(ctx.
j.
debug()) <<
"AMM Bid: Invalid Max/MinSlotPrice.";
185 STAmount const lptAMMBalance = (*ammSle)[sfLPTokenBalance];
188 if (!rules.enabled(fixInnerObjTemplate))
190 if (!ammSle->isFieldPresent(sfAuctionSlot))
191 ammSle->makeFieldPresent(sfAuctionSlot);
195 XRPL_ASSERT(ammSle->isFieldPresent(sfAuctionSlot),
"xrpl::applyBid : has auction slot");
196 if (!ammSle->isFieldPresent(sfAuctionSlot))
199 auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
204 auto const tradingFee =
getFee((*ammSle)[sfTradingFee]);
215 auto validOwner = [&](
AccountID const& account) {
222 auctionSlot.setAccountID(sfAccount, account);
226 auctionSlot.setFieldU16(sfDiscountedFee, fee);
228 else if (auctionSlot.isFieldPresent(sfDiscountedFee))
230 auctionSlot.makeFieldAbsent(sfDiscountedFee);
232 auctionSlot.setFieldAmount(sfPrice,
toSTAmount(lpTokens.asset(), minPrice));
235 auctionSlot.setFieldArray(sfAuthAccounts, ctx.
tx.
getFieldArray(sfAuthAccounts));
239 auctionSlot.makeFieldAbsent(sfAuthAccounts);
244 if (saBurn >= lptAMMBalance)
249 <<
"AMM Bid: LP Token burn exceeds AMM balance " << burn <<
" " << lptAMMBalance;
256 JLOG(ctx.
journal.
debug()) <<
"AMM Bid: failed to redeem.";
259 ammSle->setFieldAmount(sfLPTokenBalance, lptAMMBalance - saBurn);
266 auto const bidMin = ctx.
tx[~sfBidMin];
267 auto const bidMax = ctx.
tx[~sfBidMax];
269 auto getPayPrice = [&](
Number const& computedPrice) -> std::expected<Number, TER> {
272 if (bidMin && bidMax)
274 if (computedPrice <= *bidMax)
276 JLOG(ctx.
journal.
debug()) <<
"AMM Bid: not in range " << computedPrice <<
" "
277 << *bidMin <<
" " << *bidMax;
287 if (computedPrice <= *bidMax)
288 return computedPrice;
290 <<
"AMM Bid: not in range " << computedPrice <<
" " << *bidMax;
294 return computedPrice;
300 if (payPrice > lpTokens)
308 if (
auto const acct = auctionSlot[~sfAccount]; !acct || !validOwner(*acct))
310 auto const payPrice = getPayPrice(minSlotPrice);
313 return {payPrice.error(),
false};
316 res = updateSlot(discountedFee, *payPrice, *payPrice);
321 STAmount const pricePurchased = auctionSlot[sfPrice];
322 XRPL_ASSERT(timeSlot,
"xrpl::applyBid : timeSlot is set");
325 auto const fractionRemaining =
Number(1) - fractionUsed;
326 auto const computedPrice = [&]() ->
Number {
327 auto const p105 =
Number(105, -2);
330 return pricePurchased * p105 + minSlotPrice;
332 return pricePurchased * p105 * (1 -
power(fractionUsed, 60)) + minSlotPrice;
336 auto const payPrice = getPayPrice(computedPrice);
339 return {payPrice.error(),
false};
343 auto const refund = fractionRemaining * pricePurchased;
344 if (refund > *payPrice)
348 <<
"AMM Bid: refund exceeds payPrice " << refund <<
" " << *payPrice;
352 sb, account, auctionSlot[sfAccount],
toSTAmount(lpTokens.asset(), refund), ctx.
journal);
355 JLOG(ctx.
journal.
debug()) <<
"AMM Bid: failed to refund.";
359 auto const burn = *payPrice - refund;
360 res = updateSlot(discountedFee, *payPrice, burn);
A generic endpoint for log messages.
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.
static NotTEC preflight(PreflightContext const &ctx)
static bool checkExtraFeatures(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.
static TER preclaim(PreclaimContext const &ctx)
State information when applying a tx.
beast::Journal const journal
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.
virtual LedgerHeader const & header() const =0
Returns information about the ledger.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Asset const & asset() const
std::shared_ptr< STLedgerEntry const > const & const_ref
STArray const & getFieldArray(SField const &field) const
bool isFieldPresent(SField const &field) 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.
SLE::const_pointer read(Keylet const &k) const override
Return the state item associated with a key.
T duration_cast(T... args)
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Keylet account(AccountID const &id) noexcept
AccountID root.
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 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.
constexpr std::uint32_t kTotalTimeSlotSecs
constexpr std::uint32_t kAuctionSlotDiscountedFeeFraction
bool ammEnabled(Rules const &)
Return true if required AMM amendment is enabled.
NotTEC invalidAMMAssetPair(Asset const &asset1, Asset const &asset2, std::optional< std::pair< Asset, Asset > > const &pair=std::nullopt)
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, IsDeposit isDeposit)
Adjust LP tokens to deposit/withdraw.
Number power(Number const &f, unsigned n)
TERSubset< CanCvtToNotTEC > NotTEC
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.
constexpr std::uint32_t kAuctionSlotMinFeeFraction
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
static std::pair< TER, bool > applyBid(ApplyContext &ctx, Sandbox &sb, AccountID const &account, beast::Journal j)
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
constexpr std::uint16_t kAuctionSlotTimeIntervals
bool isTesSuccess(TER x) noexcept
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
TERSubset< CanCvtToTER > TER
std::optional< std::uint8_t > ammAuctionTimeSlot(std::uint64_t current, STObject const &auctionSlot)
Get time slot of the auction slot.
constexpr std::uint16_t kAuctionSlotMaxAuthAccounts
STAmount toSTAmount(IOUAmount const &iou, Asset const &asset)
State information when determining if a tx is likely to claim a fee.
State information when preflighting a tx.
T time_since_epoch(T... args)