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 
186  auto const effectiveAmounts = [&] {
187  if (offer_.owner() != offer_.issueOut().account &&
188  ownerFunds < ofrAmts.out)
189  {
190  // adjust the amounts by owner funds
191  return offer_.quality().ceil_out(ofrAmts, ownerFunds);
192  }
193  return ofrAmts;
194  }();
195 
196  if (effectiveAmounts.in > TTakerPays::minPositiveAmount())
197  return false;
198 
199  Quality const effectiveQuality{effectiveAmounts};
200  return effectiveQuality < offer_.quality();
201 }
202 
203 template <class TIn, class TOut>
204 bool
206 {
207  // Modifying the order or logic of these
208  // operations causes a protocol breaking change.
209 
210  if (!validBook_)
211  return false;
212 
213  for (;;)
214  {
215  ownerFunds_ = std::nullopt;
216  // BookTip::step deletes the current offer from the view before
217  // advancing to the next (unless the ledger entry is missing).
218  if (!tip_.step(j_))
219  return false;
220 
221  std::shared_ptr<SLE> entry = tip_.entry();
222 
223  // If we exceed the maximum number of allowed steps, we're done.
224  if (!counter_.step())
225  return false;
226 
227  // Remove if missing
228  if (!entry)
229  {
230  erase(view_);
231  erase(cancelView_);
232  continue;
233  }
234 
235  // Remove if expired
236  using d = NetClock::duration;
237  using tp = NetClock::time_point;
238  if (entry->isFieldPresent(sfExpiration) &&
239  tp{d{(*entry)[sfExpiration]}} <= expire_)
240  {
241  JLOG(j_.trace()) << "Removing expired offer " << entry->key();
242  permRmOffer(entry->key());
243  continue;
244  }
245 
246  offer_ = TOffer<TIn, TOut>(entry, tip_.quality());
247 
248  auto const amount(offer_.amount());
249 
250  // Remove if either amount is zero
251  if (amount.empty())
252  {
253  JLOG(j_.warn()) << "Removing bad offer " << entry->key();
254  permRmOffer(entry->key());
255  offer_ = TOffer<TIn, TOut>{};
256  continue;
257  }
258 
259  // Calculate owner funds
260  ownerFunds_ = accountFundsHelper(
261  view_,
262  offer_.owner(),
263  amount.out,
264  offer_.issueOut(),
266  j_);
267 
268  // Check for unfunded offer
269  if (*ownerFunds_ <= beast::zero)
270  {
271  // If the owner's balance in the pristine view is the same,
272  // we haven't modified the balance and therefore the
273  // offer is "found unfunded" versus "became unfunded"
274  auto const original_funds = accountFundsHelper(
275  cancelView_,
276  offer_.owner(),
277  amount.out,
278  offer_.issueOut(),
280  j_);
281 
282  if (original_funds == *ownerFunds_)
283  {
284  permRmOffer(entry->key());
285  JLOG(j_.trace()) << "Removing unfunded offer " << entry->key();
286  }
287  else
288  {
289  JLOG(j_.trace())
290  << "Removing became unfunded offer " << entry->key();
291  }
292  offer_ = TOffer<TIn, TOut>{};
293  // See comment at top of loop for how the offer is removed
294  continue;
295  }
296 
297  bool const rmSmallIncreasedQOffer = [&] {
298  bool const inIsXRP = isXRP(offer_.issueIn());
299  bool const outIsXRP = isXRP(offer_.issueOut());
300  if (inIsXRP && !outIsXRP)
301  {
302  // Without the `if constexpr`, the
303  // `shouldRmSmallIncreasedQOffer` template will be instantiated
304  // even if it is never used. This can cause compiler errors in
305  // some cases, hence the `if constexpr` guard.
306  // Note that TIn can be XRPAmount or STAmount, and TOut can be
307  // IOUAmount or STAmount.
308  if constexpr (!(std::is_same_v<TIn, IOUAmount> ||
309  std::is_same_v<TOut, XRPAmount>))
310  return shouldRmSmallIncreasedQOffer<XRPAmount, IOUAmount>();
311  }
312  if (!inIsXRP && outIsXRP)
313  {
314  // See comment above for `if constexpr` rationale
315  if constexpr (!(std::is_same_v<TIn, XRPAmount> ||
316  std::is_same_v<TOut, IOUAmount>))
317  return shouldRmSmallIncreasedQOffer<IOUAmount, XRPAmount>();
318  }
319  if (!inIsXRP && !outIsXRP)
320  {
321  // See comment above for `if constexpr` rationale
322  if constexpr (!(std::is_same_v<TIn, XRPAmount> ||
323  std::is_same_v<TOut, XRPAmount>))
324  return shouldRmSmallIncreasedQOffer<IOUAmount, IOUAmount>();
325  }
326  assert(0); // xrp/xrp offer!?! should never happen
327  return false;
328  }();
329 
330  if (rmSmallIncreasedQOffer)
331  {
332  auto const original_funds = accountFundsHelper(
333  cancelView_,
334  offer_.owner(),
335  amount.out,
336  offer_.issueOut(),
338  j_);
339 
340  if (original_funds == *ownerFunds_)
341  {
342  permRmOffer(entry->key());
343  JLOG(j_.trace())
344  << "Removing tiny offer due to reduced quality "
345  << entry->key();
346  }
347  else
348  {
349  JLOG(j_.trace()) << "Removing tiny offer that became tiny due "
350  "to reduced quality "
351  << entry->key();
352  }
353  offer_ = TOffer<TIn, TOut>{};
354  // See comment at top of loop for how the offer is removed
355  continue;
356  }
357 
358  break;
359  }
360 
361  return true;
362 }
363 
364 void
365 OfferStream::permRmOffer(uint256 const& offerIndex)
366 {
367  offerDelete(cancelView_, cancelView_.peek(keylet::offer(offerIndex)), j_);
368 }
369 
370 template <class TIn, class TOut>
371 void
373 {
374  permToRemove_.insert(offerIndex);
375 }
376 
381 
386 } // namespace ripple
ripple::Issue
A currency issued by an account.
Definition: Issue.h:34
std::shared_ptr
STL class.
ripple::fhZERO_IF_FROZEN
@ fhZERO_IF_FROZEN
Definition: View.h:76
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:309
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::accountHolds
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition: View.cpp:223
std::chrono::duration
ripple::Issue::currency
Currency currency
Definition: Issue.h:37
beast::Journal::warn
Stream warn() const
Definition: Journal.h:327
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:76
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:133
ripple::offerDelete
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition: View.cpp:893
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:45
beast::Journal::error
Stream error() const
Definition: Journal.h:333
std::chrono::time_point
ripple::isXRP
bool isXRP(AccountID const &c)
Definition: AccountID.h:89
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:267
ripple::TOfferStreamBase
Definition: OfferStream.h:36
ripple::ReadView
A view into a ledger.
Definition: ReadView.h:125
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:38
ripple::XRPAmount
Definition: XRPAmount.h:46