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