rippled
AMMBid.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2022 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 <ripple/app/tx/impl/AMMBid.h>
21 
22 #include <ripple/app/misc/AMMHelpers.h>
23 #include <ripple/app/misc/AMMUtils.h>
24 #include <ripple/ledger/Sandbox.h>
25 #include <ripple/ledger/View.h>
26 #include <ripple/protocol/AMMCore.h>
27 #include <ripple/protocol/Feature.h>
28 #include <ripple/protocol/STAccount.h>
29 #include <ripple/protocol/TER.h>
30 #include <ripple/protocol/TxFlags.h>
31 
32 namespace ripple {
33 
34 NotTEC
36 {
37  if (!ammEnabled(ctx.rules))
38  return temDISABLED;
39 
40  if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
41  return ret;
42 
43  if (ctx.tx.getFlags() & tfUniversalMask)
44  {
45  JLOG(ctx.j.debug()) << "AMM Bid: invalid flags.";
46  return temINVALID_FLAG;
47  }
48 
49  if (auto const res = invalidAMMAssetPair(ctx.tx[sfAsset], ctx.tx[sfAsset2]))
50  {
51  JLOG(ctx.j.debug()) << "AMM Bid: Invalid asset pair.";
52  return res;
53  }
54 
55  if (auto const bidMin = ctx.tx[~sfBidMin])
56  {
57  if (auto const res = invalidAMMAmount(*bidMin))
58  {
59  JLOG(ctx.j.debug()) << "AMM Bid: invalid min slot price.";
60  return res;
61  }
62  }
63 
64  if (auto const bidMax = ctx.tx[~sfBidMax])
65  {
66  if (auto const res = invalidAMMAmount(*bidMax))
67  {
68  JLOG(ctx.j.debug()) << "AMM Bid: invalid max slot price.";
69  return res;
70  }
71  }
72 
74  {
75  if (auto const authAccounts = ctx.tx.getFieldArray(sfAuthAccounts);
76  authAccounts.size() > AUCTION_SLOT_MAX_AUTH_ACCOUNTS)
77  {
78  JLOG(ctx.j.debug()) << "AMM Bid: Invalid number of AuthAccounts.";
79  return temMALFORMED;
80  }
81  }
82 
83  return preflight2(ctx);
84 }
85 
86 TER
88 {
89  auto const ammSle =
90  ctx.view.read(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
91  if (!ammSle)
92  {
93  JLOG(ctx.j.debug()) << "AMM Bid: Invalid asset pair.";
94  return terNO_AMM;
95  }
96 
98  {
99  for (auto const& account : ctx.tx.getFieldArray(sfAuthAccounts))
100  {
101  if (!ctx.view.read(keylet::account(account[sfAccount])))
102  {
103  JLOG(ctx.j.debug()) << "AMM Bid: Invalid Account.";
104  return terNO_ACCOUNT;
105  }
106  }
107  }
108 
109  auto const lpTokens =
110  ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j);
111  // Not LP
112  if (lpTokens == beast::zero)
113  {
114  JLOG(ctx.j.debug()) << "AMM Bid: account is not LP.";
115  return tecAMM_INVALID_TOKENS;
116  }
117  auto const lpTokensBalance = (*ammSle)[sfLPTokenBalance];
118 
119  auto const bidMin = ctx.tx[~sfBidMin];
120 
121  if (bidMin)
122  {
123  if (bidMin->issue() != lpTokens.issue())
124  {
125  JLOG(ctx.j.debug()) << "AMM Bid: Invalid LPToken.";
126  return temBAD_AMM_TOKENS;
127  }
128  if (*bidMin > lpTokens || *bidMin >= lpTokensBalance)
129  {
130  JLOG(ctx.j.debug()) << "AMM Bid: Invalid Tokens.";
131  return tecAMM_INVALID_TOKENS;
132  }
133  }
134 
135  auto const bidMax = ctx.tx[~sfBidMax];
136  if (bidMax)
137  {
138  if (bidMax->issue() != lpTokens.issue())
139  {
140  JLOG(ctx.j.debug()) << "AMM Bid: Invalid LPToken.";
141  return temBAD_AMM_TOKENS;
142  }
143  if (*bidMax > lpTokens || *bidMax >= lpTokensBalance)
144  {
145  JLOG(ctx.j.debug()) << "AMM Bid: Invalid Tokens.";
146  return tecAMM_INVALID_TOKENS;
147  }
148  }
149 
150  if (bidMin && bidMax && bidMin > bidMax)
151  {
152  JLOG(ctx.j.debug()) << "AMM Bid: Invalid Max/MinSlotPrice.";
153  return tecAMM_INVALID_TOKENS;
154  }
155 
156  return tesSUCCESS;
157 }
158 
161  ApplyContext& ctx_,
162  Sandbox& sb,
163  AccountID const& account_,
164  beast::Journal j_)
165 {
166  using namespace std::chrono;
167  auto const ammSle =
168  sb.peek(keylet::amm(ctx_.tx[sfAsset], ctx_.tx[sfAsset2]));
169  if (!ammSle)
170  return {tecINTERNAL, false};
171  STAmount const lptAMMBalance = (*ammSle)[sfLPTokenBalance];
172  auto const lpTokens = ammLPHolds(sb, *ammSle, account_, ctx_.journal);
173  if (!ammSle->isFieldPresent(sfAuctionSlot))
174  ammSle->makeFieldPresent(sfAuctionSlot);
175  auto& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot);
176  auto const current =
177  duration_cast<seconds>(
179  .count();
180  // Auction slot discounted fee
181  auto const discountedFee =
183  auto const tradingFee = getFee((*ammSle)[sfTradingFee]);
184  // Min price
185  auto const minSlotPrice =
186  lptAMMBalance * tradingFee / AUCTION_SLOT_MIN_FEE_FRACTION;
187 
188  std::uint32_t constexpr tailingSlot = AUCTION_SLOT_TIME_INTERVALS - 1;
189 
190  // If seated then it is the current slot-holder time slot, otherwise
191  // the auction slot is not owned. Slot range is in {0-19}
192  auto const timeSlot = ammAuctionTimeSlot(current, auctionSlot);
193 
194  // Account must exist and the slot not expired.
195  auto validOwner = [&](AccountID const& account) {
196  // Valid range is 0-19 but the tailing slot pays MinSlotPrice
197  // and doesn't refund so the check is < instead of <= to optimize.
198  return timeSlot && *timeSlot < tailingSlot &&
199  sb.read(keylet::account(account));
200  };
201 
202  auto updateSlot = [&](std::uint32_t fee,
203  Number const& minPrice,
204  Number const& burn) -> TER {
205  auctionSlot.setAccountID(sfAccount, account_);
206  auctionSlot.setFieldU32(sfExpiration, current + TOTAL_TIME_SLOT_SECS);
207  if (fee == 0)
208  auctionSlot.makeFieldAbsent(sfDiscountedFee);
209  else
210  auctionSlot.setFieldU16(sfDiscountedFee, fee);
211  auctionSlot.setFieldAmount(
212  sfPrice, toSTAmount(lpTokens.issue(), minPrice));
213  if (ctx_.tx.isFieldPresent(sfAuthAccounts))
214  auctionSlot.setFieldArray(
216  else
217  auctionSlot.makeFieldAbsent(sfAuthAccounts);
218  // Burn the remaining bid amount
219  auto const saBurn = adjustLPTokens(
220  lptAMMBalance, toSTAmount(lptAMMBalance.issue(), burn), false);
221  if (saBurn >= lptAMMBalance)
222  {
223  JLOG(ctx_.journal.debug())
224  << "AMM Bid: invalid burn " << burn << " " << lptAMMBalance;
225  return tecAMM_BALANCE;
226  }
227  auto res =
228  redeemIOU(sb, account_, saBurn, lpTokens.issue(), ctx_.journal);
229  if (res != tesSUCCESS)
230  {
231  JLOG(ctx_.journal.debug()) << "AMM Bid: failed to redeem.";
232  return res;
233  }
234  ammSle->setFieldAmount(sfLPTokenBalance, lptAMMBalance - saBurn);
235  sb.update(ammSle);
236  return tesSUCCESS;
237  };
238 
239  TER res = tesSUCCESS;
240 
241  auto const bidMin = ctx_.tx[~sfBidMin];
242  auto const bidMax = ctx_.tx[~sfBidMax];
243 
244  auto getPayPrice =
245  [&](Number const& computedPrice) -> Expected<Number, TER> {
246  auto const payPrice = [&]() -> std::optional<Number> {
247  // Both min/max bid price are defined
248  if (bidMin && bidMax)
249  {
250  if (computedPrice <= *bidMax)
251  return std::max(computedPrice, Number(*bidMin));
252  JLOG(ctx_.journal.debug())
253  << "AMM Bid: not in range " << computedPrice << " "
254  << *bidMin << " " << *bidMax;
255  return std::nullopt;
256  }
257  // Bidder pays max(bidPrice, computedPrice)
258  if (bidMin)
259  {
260  return std::max(computedPrice, Number(*bidMin));
261  }
262  else if (bidMax)
263  {
264  if (computedPrice <= *bidMax)
265  return computedPrice;
266  JLOG(ctx_.journal.debug()) << "AMM Bid: not in range "
267  << computedPrice << " " << *bidMax;
268  return std::nullopt;
269  }
270  else
271  return computedPrice;
272  }();
273  if (!payPrice)
274  return Unexpected(tecAMM_FAILED);
275  else if (payPrice > lpTokens)
277  return *payPrice;
278  };
279 
280  // No one owns the slot or expired slot.
281  if (auto const acct = auctionSlot[~sfAccount]; !acct || !validOwner(*acct))
282  {
283  if (auto const payPrice = getPayPrice(minSlotPrice); !payPrice)
284  return {payPrice.error(), false};
285  else
286  res = updateSlot(discountedFee, *payPrice, *payPrice);
287  }
288  else
289  {
290  // Price the slot was purchased at.
291  STAmount const pricePurchased = auctionSlot[sfPrice];
292  assert(timeSlot);
293  auto const fractionUsed =
294  (Number(*timeSlot) + 1) / AUCTION_SLOT_TIME_INTERVALS;
295  auto const fractionRemaining = Number(1) - fractionUsed;
296  auto const computedPrice = [&]() -> Number {
297  auto const p1_05 = Number(105, -2);
298  // First interval slot price
299  if (*timeSlot == 0)
300  return pricePurchased * p1_05 + minSlotPrice;
301  // Other intervals slot price
302  return pricePurchased * p1_05 * (1 - power(fractionUsed, 60)) +
303  minSlotPrice;
304  }();
305 
306  auto const payPrice = getPayPrice(computedPrice);
307 
308  if (!payPrice)
309  return {payPrice.error(), false};
310 
311  // Refund the previous owner. If the time slot is 0 then
312  // the owner is refunded 95% of the amount.
313  auto const refund = fractionRemaining * pricePurchased;
314  if (refund > *payPrice)
315  {
316  JLOG(ctx_.journal.debug())
317  << "AMM Bid: invalid refund " << refund << " " << *payPrice;
318  return {tecINSUFFICIENT_PAYMENT, false};
319  }
320  res = accountSend(
321  sb,
322  account_,
323  auctionSlot[sfAccount],
324  toSTAmount(lpTokens.issue(), refund),
325  ctx_.journal);
326  if (res != tesSUCCESS)
327  {
328  JLOG(ctx_.journal.debug()) << "AMM Bid: failed to refund.";
329  return {res, false};
330  }
331 
332  auto const burn = *payPrice - refund;
333  res = updateSlot(discountedFee, *payPrice, burn);
334  }
335 
336  return {res, res == tesSUCCESS};
337 }
338 
339 TER
341 {
342  // This is the ledger view that we work against. Transactions are applied
343  // as we go on processing transactions.
344  Sandbox sb(&ctx_.view());
345 
346  auto const result = applyBid(ctx_, sb, account_, j_);
347  if (result.second)
348  sb.apply(ctx_.rawView());
349 
350  return result.first;
351 }
352 
353 } // namespace ripple
ripple::ReadView::info
virtual LedgerInfo const & info() const =0
Returns information about the ledger.
ripple::STArray::size
size_type size() const
Definition: STArray.h:248
ripple::STObject::getFieldArray
const STArray & getFieldArray(SField const &field) const
Definition: STObject.cpp:624
ripple::preflight2
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:130
ripple::sfDiscountedFee
const SF_UINT16 sfDiscountedFee
ripple::sfAsset
const SF_ISSUE sfAsset
ripple::PreclaimContext::view
ReadView const & view
Definition: Transactor.h:56
ripple::applyBid
static std::pair< TER, bool > applyBid(ApplyContext &ctx_, Sandbox &sb, AccountID const &account_, beast::Journal j_)
Definition: AMMBid.cpp:160
ripple::accountSend
TER accountSend(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee)
Definition: View.cpp:1159
ripple::PreclaimContext::j
const beast::Journal j
Definition: Transactor.h:60
ripple::STAmount::issue
Issue const & issue() const
Definition: STAmount.h:347
ripple::AUCTION_SLOT_TIME_INTERVALS
constexpr std::uint16_t AUCTION_SLOT_TIME_INTERVALS
Definition: AMMCore.h:35
ripple::Transactor::j_
const beast::Journal j_
Definition: Transactor.h:89
ripple::isTesSuccess
bool isTesSuccess(TER x)
Definition: TER.h:604
ripple::Sandbox::apply
void apply(RawView &to)
Definition: Sandbox.h:55
std::pair
ripple::sfLPTokenBalance
const SF_AMOUNT sfLPTokenBalance
ripple::AUCTION_SLOT_DISCOUNTED_FEE_FRACTION
constexpr std::uint32_t AUCTION_SLOT_DISCOUNTED_FEE_FRACTION
Definition: AMMCore.h:38
ripple::ammEnabled
bool ammEnabled(Rules const &)
Return true if required AMM amendments are enabled.
Definition: AMMCore.cpp:126
ripple::keylet::amm
Keylet amm(Issue const &issue1, Issue const &issue2) noexcept
AMM entry.
Definition: Indexes.cpp:375
ripple::AMMBid::preflight
static NotTEC preflight(PreflightContext const &ctx)
Definition: AMMBid.cpp:35
ripple::Unexpected
Unexpected(E(&)[N]) -> Unexpected< E const * >
ripple::detail::ApplyViewBase::update
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
Definition: ApplyViewBase.cpp:146
ripple::getFee
Number getFee(std::uint16_t tfee)
Convert to the fee from the basis points.
Definition: AMMCore.h:109
ripple::ApplyContext::journal
const beast::Journal journal
Definition: ApplyContext.h:51
ripple::AMMBid::doApply
TER doApply() override
Definition: AMMBid.cpp:340
ripple::ApplyContext::rawView
RawView & rawView()
Definition: ApplyContext.h:67
ripple::sfPrice
const SF_AMOUNT sfPrice
ripple::PreflightContext::j
const beast::Journal j
Definition: Transactor.h:38
ripple::preflight1
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:78
ripple::sfTradingFee
const SF_UINT16 sfTradingFee
ripple::tecAMM_FAILED
@ tecAMM_FAILED
Definition: TER.h:300
ripple::invalidAMMAmount
NotTEC invalidAMMAmount(STAmount const &amount, std::optional< std::pair< Issue, Issue >> const &pair=std::nullopt, bool validZero=false)
Validate the amount.
Definition: AMMCore.cpp:94
ripple::sfExpiration
const SF_UINT32 sfExpiration
ripple::AUCTION_SLOT_MIN_FEE_FRACTION
constexpr std::uint32_t AUCTION_SLOT_MIN_FEE_FRACTION
Definition: AMMCore.h:39
ripple::base_uint
Integers of any length that is a multiple of 32-bits.
Definition: base_uint.h:82
ripple::temINVALID_FLAG
@ temINVALID_FLAG
Definition: TER.h:109
std::chrono::time_point::time_since_epoch
T time_since_epoch(T... args)
ripple::temBAD_AMM_TOKENS
@ temBAD_AMM_TOKENS
Definition: TER.h:127
ripple::sfAsset2
const SF_ISSUE sfAsset2
ripple::Expected
Definition: Expected.h:132
ripple::sfBidMax
const SF_AMOUNT sfBidMax
ripple::Number
Definition: Number.h:36
ripple::keylet::account
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:134
ripple::toSTAmount
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
Definition: AmountConversions.h:30
ripple::tecAMM_INVALID_TOKENS
@ tecAMM_INVALID_TOKENS
Definition: TER.h:301
ripple::TERSubset< CanCvtToTER >
ripple::sfAuthAccounts
const SField sfAuthAccounts
ripple::Sandbox
Discardable, editable view to a ledger.
Definition: Sandbox.h:34
ripple::tecAMM_BALANCE
@ tecAMM_BALANCE
Definition: TER.h:299
ripple::TER
TERSubset< CanCvtToTER > TER
Definition: TER.h:575
ripple::STAmount
Definition: STAmount.h:45
ripple::tecINTERNAL
@ tecINTERNAL
Definition: TER.h:280
ripple::STObject::getFlags
std::uint32_t getFlags() const
Definition: STObject.cpp:481
ripple::ValStatus::current
@ current
This was a new validation and was added.
ripple::ApplyContext
State information when applying a tx.
Definition: ApplyContext.h:35
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
std::uint32_t
ripple::invalidAMMAssetPair
NotTEC invalidAMMAssetPair(Issue const &issue1, Issue const &issue2, std::optional< std::pair< Issue, Issue >> const &pair=std::nullopt)
Definition: AMMCore.cpp:79
ripple::sfBidMin
const SF_AMOUNT sfBidMin
ripple::ReadView::read
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
ripple::ApplyContext::view
ApplyView & view()
Definition: ApplyContext.h:54
ripple::PreclaimContext::tx
STTx const & tx
Definition: Transactor.h:58
ripple::power
Number power(Number const &f, unsigned n)
Definition: Number.cpp:601
ripple::terNO_ACCOUNT
@ terNO_ACCOUNT
Definition: TER.h:200
ripple::PreclaimContext
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:52
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::tecINSUFFICIENT_PAYMENT
@ tecINSUFFICIENT_PAYMENT
Definition: TER.h:297
ripple::TOTAL_TIME_SLOT_SECS
constexpr std::uint32_t TOTAL_TIME_SLOT_SECS
Definition: AMMCore.h:34
ripple::temDISABLED
@ temDISABLED
Definition: TER.h:112
ripple::redeemIOU
TER redeemIOU(ApplyView &view, AccountID const &account, STAmount const &amount, Issue const &issue, beast::Journal j)
Definition: View.cpp:1416
ripple::terNO_AMM
@ terNO_AMM
Definition: TER.h:210
ripple::STObject::isFieldPresent
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:428
ripple::ammLPHolds
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:104
ripple::Transactor::ctx_
ApplyContext & ctx_
Definition: Transactor.h:88
std::optional
beast::Journal::debug
Stream debug() const
Definition: Journal.h:315
ripple::AUCTION_SLOT_MAX_AUTH_ACCOUNTS
constexpr std::uint16_t AUCTION_SLOT_MAX_AUTH_ACCOUNTS
Definition: AMMCore.h:36
ripple::detail::ApplyViewBase::read
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
Definition: ApplyViewBase.cpp:71
ripple::sfAccount
const SF_ACCOUNT sfAccount
ripple::detail::ApplyViewBase::peek
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
Definition: ApplyViewBase.cpp:128
ripple::AMMBid::preclaim
static TER preclaim(PreclaimContext const &ctx)
Definition: AMMBid.cpp:87
ripple::temMALFORMED
@ temMALFORMED
Definition: TER.h:85
ripple::PreflightContext::tx
STTx const & tx
Definition: Transactor.h:35
std::max
T max(T... args)
ripple::PreflightContext
State information when preflighting a tx.
Definition: Transactor.h:31
ripple::PreflightContext::rules
const Rules rules
Definition: Transactor.h:36
ripple::adjustLPTokens
STAmount adjustLPTokens(STAmount const &lptAMMBalance, STAmount const &lpTokens, bool isDeposit)
Definition: AMMHelpers.cpp:133
ripple::tfUniversalMask
constexpr std::uint32_t tfUniversalMask
Definition: TxFlags.h:60
ripple::sfAuctionSlot
const SField sfAuctionSlot
ripple::tesSUCCESS
@ tesSUCCESS
Definition: TER.h:225
ripple::Transactor::account_
const AccountID account_
Definition: Transactor.h:91
ripple::ApplyContext::tx
STTx const & tx
Definition: ApplyContext.h:48
ripple::ammAuctionTimeSlot
std::optional< std::uint8_t > ammAuctionTimeSlot(std::uint64_t current, STObject const &auctionSlot)
Get time slot of the auction slot.
Definition: AMMCore.cpp:107
ripple::NotTEC
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:535
ripple::LedgerInfo::parentCloseTime
NetClock::time_point parentCloseTime
Definition: ReadView.h:84
std::chrono