rippled
OfferStream.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012, 2013 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/OfferStream.h>
21 #include <ripple/basics/Log.h>
22 #include <ripple/protocol/Feature.h>
23 
24 namespace ripple {
25 
26 namespace {
27 bool
28 checkIssuers(ReadView const& view, Book const& book)
29 {
30  auto issuerExists = [](ReadView const& view, Issue const& iss) -> bool {
31  return isXRP(iss.account) || view.read(keylet::account(iss.account));
32  };
33  return issuerExists(view, book.in) && issuerExists(view, book.out);
34 }
35 } // namespace
36 
37 template <class TIn, class TOut>
39  ApplyView& view,
40  ApplyView& cancelView,
41  Book const& book,
43  StepCounter& counter,
44  beast::Journal journal)
45  : j_(journal)
46  , view_(view)
47  , cancelView_(cancelView)
48  , book_(book)
49  , validBook_(checkIssuers(view, book))
50  , expire_(when)
51  , tip_(view, book_)
52  , counter_(counter)
53 {
54  assert(validBook_);
55 }
56 
57 // Handle the case where a directory item with no corresponding ledger entry
58 // is found. This shouldn't happen but if it does we clean it up.
59 template <class TIn, class TOut>
60 void
62 {
63  // NIKB NOTE This should be using ApplyView::dirRemove, which would
64  // correctly remove the directory if its the last entry.
65  // Unfortunately this is a protocol breaking change.
66 
67  auto p = view.peek(keylet::page(tip_.dir()));
68 
69  if (p == nullptr)
70  {
71  JLOG(j_.error()) << "Missing directory " << tip_.dir() << " for offer "
72  << tip_.index();
73  return;
74  }
75 
76  auto v(p->getFieldV256(sfIndexes));
77  auto it(std::find(v.begin(), v.end(), tip_.index()));
78 
79  if (it == v.end())
80  {
81  JLOG(j_.error()) << "Missing offer " << tip_.index()
82  << " for directory " << tip_.dir();
83  return;
84  }
85 
86  v.erase(it);
87  p->setFieldV256(sfIndexes, v);
88  view.update(p);
89 
90  JLOG(j_.trace()) << "Missing offer " << tip_.index()
91  << " removed from directory " << tip_.dir();
92 }
93 
94 static STAmount
96  ReadView const& view,
97  AccountID const& id,
98  STAmount const& saDefault,
99  Issue const&,
100  FreezeHandling freezeHandling,
101  beast::Journal j)
102 {
103  return accountFunds(view, id, saDefault, freezeHandling, j);
104 }
105 
106 static IOUAmount
108  ReadView const& view,
109  AccountID const& id,
110  IOUAmount const& amtDefault,
111  Issue const& issue,
112  FreezeHandling freezeHandling,
113  beast::Journal j)
114 {
115  if (issue.account == id)
116  // self funded
117  return amtDefault;
118 
119  return toAmount<IOUAmount>(accountHolds(
120  view, id, issue.currency, issue.account, freezeHandling, j));
121 }
122 
123 static XRPAmount
125  ReadView const& view,
126  AccountID const& id,
127  XRPAmount const& amtDefault,
128  Issue const& issue,
129  FreezeHandling freezeHandling,
130  beast::Journal j)
131 {
132  return toAmount<XRPAmount>(accountHolds(
133  view, id, issue.currency, issue.account, freezeHandling, j));
134 }
135 
136 template <class TIn, class TOut>
137 template <class TTakerPays, class TTakerGets>
138 bool
140 {
141  static_assert(
142  std::is_same_v<TTakerPays, IOUAmount> ||
143  std::is_same_v<TTakerPays, XRPAmount>,
144  "STAmount is not supported");
145 
146  static_assert(
147  std::is_same_v<TTakerGets, IOUAmount> ||
148  std::is_same_v<TTakerGets, XRPAmount>,
149  "STAmount is not supported");
150 
151  static_assert(
152  !std::is_same_v<TTakerPays, XRPAmount> ||
153  !std::is_same_v<TTakerGets, XRPAmount>,
154  "Cannot have XRP/XRP offers");
155 
156  if (!view_.rules().enabled(fixRmSmallIncreasedQOffers))
157  return false;
158 
159  // Consider removing the offer if:
160  // o `TakerPays` is XRP (because of XRP drops granularity) or
161  // o `TakerPays` and `TakerGets` are both IOU and `TakerPays`<`TakerGets`
162  constexpr bool const inIsXRP = std::is_same_v<TTakerPays, XRPAmount>;
163  constexpr bool const outIsXRP = std::is_same_v<TTakerGets, XRPAmount>;
164 
165  if constexpr (outIsXRP)
166  {
167  // If `TakerGets` is XRP, the worst this offer's quality can change is
168  // to about 10^-81 `TakerPays` and 1 drop `TakerGets`. This will be
169  // remarkably good quality for any realistic asset, so these offers
170  // don't need this extra check.
171  return false;
172  }
173 
174  TAmounts<TTakerPays, TTakerGets> const ofrAmts{
175  toAmount<TTakerPays>(offer_.amount().in),
176  toAmount<TTakerGets>(offer_.amount().out)};
177 
178  if constexpr (!inIsXRP && !outIsXRP)
179  {
180  if (ofrAmts.in >= ofrAmts.out)
181  return false;
182  }
183 
184  TTakerGets const ownerFunds = toAmount<TTakerGets>(*ownerFunds_);
185  bool const fixReduced = view_.rules().enabled(fixReducedOffersV1);
186 
187  auto const effectiveAmounts = [&] {
188  if (offer_.owner() != offer_.issueOut().account &&
189  ownerFunds < ofrAmts.out)
190  {
191  // adjust the amounts by owner funds.
192  //
193  // It turns out we can prevent order book blocking by rounding down
194  // the ceil_out() result. This adjustment changes transaction
195  // results, so it must be made under an amendment.
196  if (fixReduced)
197  return offer_.quality().ceil_out_strict(
198  ofrAmts, ownerFunds, /* roundUp */ false);
199 
200  return offer_.quality().ceil_out(ofrAmts, ownerFunds);
201  }
202  return ofrAmts;
203  }();
204 
205  // If either the effective in or out are zero then remove the offer.
206  // This can happen with fixReducedOffersV1 since it rounds down.
207  if (fixReduced &&
208  (effectiveAmounts.in.signum() <= 0 ||
209  effectiveAmounts.out.signum() <= 0))
210  return true;
211 
212  if (effectiveAmounts.in > TTakerPays::minPositiveAmount())
213  return false;
214 
215  Quality const effectiveQuality{effectiveAmounts};
216  return effectiveQuality < offer_.quality();
217 }
218 
219 template <class TIn, class TOut>
220 bool
222 {
223  // Modifying the order or logic of these
224  // operations causes a protocol breaking change.
225 
226  if (!validBook_)
227  return false;
228 
229  for (;;)
230  {
231  ownerFunds_ = std::nullopt;
232  // BookTip::step deletes the current offer from the view before
233  // advancing to the next (unless the ledger entry is missing).
234  if (!tip_.step(j_))
235  return false;
236 
237  std::shared_ptr<SLE> entry = tip_.entry();
238 
239  // If we exceed the maximum number of allowed steps, we're done.
240  if (!counter_.step())
241  return false;
242 
243  // Remove if missing
244  if (!entry)
245  {
246  erase(view_);
247  erase(cancelView_);
248  continue;
249  }
250 
251  // Remove if expired
252  using d = NetClock::duration;
253  using tp = NetClock::time_point;
254  if (entry->isFieldPresent(sfExpiration) &&
255  tp{d{(*entry)[sfExpiration]}} <= expire_)
256  {
257  JLOG(j_.trace()) << "Removing expired offer " << entry->key();
258  permRmOffer(entry->key());
259  continue;
260  }
261 
262  offer_ = TOffer<TIn, TOut>(entry, tip_.quality());
263 
264  auto const amount(offer_.amount());
265 
266  // Remove if either amount is zero
267  if (amount.empty())
268  {
269  JLOG(j_.warn()) << "Removing bad offer " << entry->key();
270  permRmOffer(entry->key());
271  offer_ = TOffer<TIn, TOut>{};
272  continue;
273  }
274 
275  // Calculate owner funds
276  ownerFunds_ = accountFundsHelper(
277  view_,
278  offer_.owner(),
279  amount.out,
280  offer_.issueOut(),
282  j_);
283 
284  // Check for unfunded offer
285  if (*ownerFunds_ <= beast::zero)
286  {
287  // If the owner's balance in the pristine view is the same,
288  // we haven't modified the balance and therefore the
289  // offer is "found unfunded" versus "became unfunded"
290  auto const original_funds = accountFundsHelper(
291  cancelView_,
292  offer_.owner(),
293  amount.out,
294  offer_.issueOut(),
296  j_);
297 
298  if (original_funds == *ownerFunds_)
299  {
300  permRmOffer(entry->key());
301  JLOG(j_.trace()) << "Removing unfunded offer " << entry->key();
302  }
303  else
304  {
305  JLOG(j_.trace())
306  << "Removing became unfunded offer " << entry->key();
307  }
308  offer_ = TOffer<TIn, TOut>{};
309  // See comment at top of loop for how the offer is removed
310  continue;
311  }
312 
313  bool const rmSmallIncreasedQOffer = [&] {
314  bool const inIsXRP = isXRP(offer_.issueIn());
315  bool const outIsXRP = isXRP(offer_.issueOut());
316  if (inIsXRP && !outIsXRP)
317  {
318  // Without the `if constexpr`, the
319  // `shouldRmSmallIncreasedQOffer` template will be instantiated
320  // even if it is never used. This can cause compiler errors in
321  // some cases, hence the `if constexpr` guard.
322  // Note that TIn can be XRPAmount or STAmount, and TOut can be
323  // IOUAmount or STAmount.
324  if constexpr (!(std::is_same_v<TIn, IOUAmount> ||
325  std::is_same_v<TOut, XRPAmount>))
326  return shouldRmSmallIncreasedQOffer<XRPAmount, IOUAmount>();
327  }
328  if (!inIsXRP && outIsXRP)
329  {
330  // See comment above for `if constexpr` rationale
331  if constexpr (!(std::is_same_v<TIn, XRPAmount> ||
332  std::is_same_v<TOut, IOUAmount>))
333  return shouldRmSmallIncreasedQOffer<IOUAmount, XRPAmount>();
334  }
335  if (!inIsXRP && !outIsXRP)
336  {
337  // See comment above for `if constexpr` rationale
338  if constexpr (!(std::is_same_v<TIn, XRPAmount> ||
339  std::is_same_v<TOut, XRPAmount>))
340  return shouldRmSmallIncreasedQOffer<IOUAmount, IOUAmount>();
341  }
342  assert(0); // xrp/xrp offer!?! should never happen
343  return false;
344  }();
345 
346  if (rmSmallIncreasedQOffer)
347  {
348  auto const original_funds = accountFundsHelper(
349  cancelView_,
350  offer_.owner(),
351  amount.out,
352  offer_.issueOut(),
354  j_);
355 
356  if (original_funds == *ownerFunds_)
357  {
358  permRmOffer(entry->key());
359  JLOG(j_.trace())
360  << "Removing tiny offer due to reduced quality "
361  << entry->key();
362  }
363  else
364  {
365  JLOG(j_.trace()) << "Removing tiny offer that became tiny due "
366  "to reduced quality "
367  << entry->key();
368  }
369  offer_ = TOffer<TIn, TOut>{};
370  // See comment at top of loop for how the offer is removed
371  continue;
372  }
373 
374  break;
375  }
376 
377  return true;
378 }
379 
380 void
381 OfferStream::permRmOffer(uint256 const& offerIndex)
382 {
383  offerDelete(cancelView_, cancelView_.peek(keylet::offer(offerIndex)), j_);
384 }
385 
386 template <class TIn, class TOut>
387 void
389 {
390  permToRemove_.insert(offerIndex);
391 }
392 
397 
402 } // namespace ripple
ripple::Issue
A currency issued by an account.
Definition: Issue.h:35
std::shared_ptr
STL class.
ripple::fhZERO_IF_FROZEN
@ fhZERO_IF_FROZEN
Definition: View.h:79
ripple::FlowOfferStream
Presents and consumes the offers in an order book.
Definition: OfferStream.h:175
beast::Journal::trace
Stream trace() const
Severity stream access functions.
Definition: Journal.h:308
ripple::ApplyView::peek
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
std::find
T find(T... args)
ripple::fixReducedOffersV1
const uint256 fixReducedOffersV1
ripple::accountHolds
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition: View.cpp:226
std::chrono::duration
ripple::Issue::currency
Currency currency
Definition: Issue.h:38
beast::Journal::warn
Stream warn() const
Definition: Journal.h:326
ripple::ApplyView::update
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
ripple::IOUAmount
Floating point representation of amounts with high dynamic range.
Definition: IOUAmount.h:43
ripple::FreezeHandling
FreezeHandling
Controls the treatment of frozen account balances.
Definition: View.h:79
ripple::ApplyView
Writeable view to a ledger, for applying a transaction.
Definition: ApplyView.h:134
ripple::sfExpiration
const SF_UINT32 sfExpiration
ripple::sfIndexes
const SF_VECTOR256 sfIndexes
ripple::erase
void erase(STObject &st, TypedField< U > const &f)
Remove a field in an STObject.
Definition: STExchange.h:171
ripple::base_uint
Integers of any length that is a multiple of 32-bits.
Definition: base_uint.h:82
ripple::keylet::account
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:141
ripple::offerDelete
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition: View.cpp:910
ripple::TOfferStreamBase::TOfferStreamBase
TOfferStreamBase(ApplyView &view, ApplyView &cancelView, Book const &book, NetClock::time_point when, StepCounter &counter, beast::Journal journal)
Definition: OfferStream.cpp:38
ripple::STAmount
Definition: STAmount.h:46
beast::Journal::error
Stream error() const
Definition: Journal.h:332
std::chrono::time_point
ripple::isXRP
bool isXRP(AccountID const &c)
Definition: AccountID.h:91
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
ripple::fixRmSmallIncreasedQOffers
const uint256 fixRmSmallIncreasedQOffers
ripple::accountFunds
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition: View.cpp:282
ripple::TOfferStreamBase
Definition: OfferStream.h:36
ripple::ReadView
A view into a ledger.
Definition: ReadView.h:54
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::Book
Specifies an order book.
Definition: Book.h:33
ripple::accountFundsHelper
static STAmount accountFundsHelper(ReadView const &view, AccountID const &id, STAmount const &saDefault, Issue const &, FreezeHandling freezeHandling, beast::Journal j)
Definition: OfferStream.cpp:95
ripple::TOffer
Definition: Offer.h:49
ripple::Issue::account
AccountID account
Definition: Issue.h:39
ripple::XRPAmount
Definition: XRPAmount.h:46