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 // Consider removing the offer if:
162 // o `TakerPays` is XRP (because of XRP drops granularity) or
163 // o `TakerPays` and `TakerGets` are both IOU and `TakerPays`<`TakerGets`
164 constexpr bool const inIsXRP = std::is_same_v<TTakerPays, XRPAmount>;
165 constexpr bool const outIsXRP = std::is_same_v<TTakerGets, XRPAmount>;
166
167 if constexpr (outIsXRP)
168 {
169 // If `TakerGets` is XRP, the worst this offer's quality can change is
170 // to about 10^-81 `TakerPays` and 1 drop `TakerGets`. This will be
171 // remarkably good quality for any realistic asset, so these offers
172 // don't need this extra check.
173 return false;
174 }
175
176 TAmounts<TTakerPays, TTakerGets> const ofrAmts{
177 toAmount<TTakerPays>(offer_.amount().in),
178 toAmount<TTakerGets>(offer_.amount().out)};
179
180 if constexpr (!inIsXRP && !outIsXRP)
181 {
182 if (ofrAmts.in >= ofrAmts.out)
183 return false;
184 }
185
186 TTakerGets const ownerFunds = toAmount<TTakerGets>(*ownerFunds_);
187 bool const fixReduced = view_.rules().enabled(fixReducedOffersV1);
188
189 auto const effectiveAmounts = [&] {
190 if (offer_.owner() != offer_.issueOut().account &&
191 ownerFunds < ofrAmts.out)
192 {
193 // adjust the amounts by owner funds.
194 //
195 // It turns out we can prevent order book blocking by rounding down
196 // the ceil_out() result. This adjustment changes transaction
197 // results, so it must be made under an amendment.
198 if (fixReduced)
199 return offer_.quality().ceil_out_strict(
200 ofrAmts, ownerFunds, /* roundUp */ false);
201
202 return offer_.quality().ceil_out(ofrAmts, ownerFunds);
203 }
204 return ofrAmts;
205 }();
206
207 // If either the effective in or out are zero then remove the offer.
208 // This can happen with fixReducedOffersV1 since it rounds down.
209 if (fixReduced &&
210 (effectiveAmounts.in.signum() <= 0 ||
211 effectiveAmounts.out.signum() <= 0))
212 return true;
213
214 if (effectiveAmounts.in > TTakerPays::minPositiveAmount())
215 return false;
216
217 Quality const effectiveQuality{effectiveAmounts};
218 return effectiveQuality < offer_.quality();
219}
220
221template <class TIn, class TOut>
222bool
224{
225 // Modifying the order or logic of these
226 // operations causes a protocol breaking change.
227
228 if (!validBook_)
229 return false;
230
231 for (;;)
232 {
233 ownerFunds_ = std::nullopt;
234 // BookTip::step deletes the current offer from the view before
235 // advancing to the next (unless the ledger entry is missing).
236 if (!tip_.step(j_))
237 return false;
238
239 std::shared_ptr<SLE> entry = tip_.entry();
240
241 // If we exceed the maximum number of allowed steps, we're done.
242 if (!counter_.step())
243 return false;
244
245 // Remove if missing
246 if (!entry)
247 {
248 erase(view_);
249 erase(cancelView_);
250 continue;
251 }
252
253 // Remove if expired
254 using d = NetClock::duration;
255 using tp = NetClock::time_point;
256 if (entry->isFieldPresent(sfExpiration) &&
257 tp{d{(*entry)[sfExpiration]}} <= expire_)
258 {
259 JLOG(j_.trace()) << "Removing expired offer " << entry->key();
260 permRmOffer(entry->key());
261 continue;
262 }
263
264 offer_ = TOffer<TIn, TOut>(entry, tip_.quality());
265
266 auto const amount(offer_.amount());
267
268 // Remove if either amount is zero
269 if (amount.empty())
270 {
271 JLOG(j_.warn()) << "Removing bad offer " << entry->key();
272 permRmOffer(entry->key());
273 offer_ = TOffer<TIn, TOut>{};
274 continue;
275 }
276
277 bool const deepFrozen = isDeepFrozen(
278 view_,
279 offer_.owner(),
280 offer_.issueIn().currency,
281 offer_.issueIn().account);
282 if (deepFrozen)
283 {
284 JLOG(j_.trace())
285 << "Removing deep frozen unfunded offer " << entry->key();
286 permRmOffer(entry->key());
287 offer_ = TOffer<TIn, TOut>{};
288 continue;
289 }
290
291 if (entry->isFieldPresent(sfDomainID) &&
293 view_, entry->key(), entry->getFieldH256(sfDomainID), j_))
294 {
295 JLOG(j_.trace())
296 << "Removing offer no longer in domain " << entry->key();
297 permRmOffer(entry->key());
298 offer_ = TOffer<TIn, TOut>{};
299 continue;
300 }
301
302 // Calculate owner funds
303 ownerFunds_ = accountFundsHelper(
304 view_,
305 offer_.owner(),
306 amount.out,
307 offer_.issueOut(),
309 j_);
310
311 // Check for unfunded offer
312 if (*ownerFunds_ <= beast::zero)
313 {
314 // If the owner's balance in the pristine view is the same,
315 // we haven't modified the balance and therefore the
316 // offer is "found unfunded" versus "became unfunded"
317 auto const original_funds = accountFundsHelper(
318 cancelView_,
319 offer_.owner(),
320 amount.out,
321 offer_.issueOut(),
323 j_);
324
325 if (original_funds == *ownerFunds_)
326 {
327 permRmOffer(entry->key());
328 JLOG(j_.trace()) << "Removing unfunded offer " << entry->key();
329 }
330 else
331 {
332 JLOG(j_.trace())
333 << "Removing became unfunded offer " << entry->key();
334 }
335 offer_ = TOffer<TIn, TOut>{};
336 // See comment at top of loop for how the offer is removed
337 continue;
338 }
339
340 bool const rmSmallIncreasedQOffer = [&] {
341 bool const inIsXRP = isXRP(offer_.issueIn());
342 bool const outIsXRP = isXRP(offer_.issueOut());
343 if (inIsXRP && !outIsXRP)
344 {
345 // Without the `if constexpr`, the
346 // `shouldRmSmallIncreasedQOffer` template will be instantiated
347 // even if it is never used. This can cause compiler errors in
348 // some cases, hence the `if constexpr` guard.
349 // Note that TIn can be XRPAmount or STAmount, and TOut can be
350 // IOUAmount or STAmount.
351 if constexpr (!(std::is_same_v<TIn, IOUAmount> ||
353 return shouldRmSmallIncreasedQOffer<XRPAmount, IOUAmount>();
354 }
355 if (!inIsXRP && outIsXRP)
356 {
357 // See comment above for `if constexpr` rationale
358 if constexpr (!(std::is_same_v<TIn, XRPAmount> ||
360 return shouldRmSmallIncreasedQOffer<IOUAmount, XRPAmount>();
361 }
362 if (!inIsXRP && !outIsXRP)
363 {
364 // See comment above for `if constexpr` rationale
365 if constexpr (!(std::is_same_v<TIn, XRPAmount> ||
367 return shouldRmSmallIncreasedQOffer<IOUAmount, IOUAmount>();
368 }
369 // LCOV_EXCL_START
370 UNREACHABLE(
371 "rippls::TOfferStreamBase::step::rmSmallIncreasedQOffer : XRP "
372 "vs XRP offer");
373 return false;
374 // LCOV_EXCL_STOP
375 }();
376
377 if (rmSmallIncreasedQOffer)
378 {
379 auto const original_funds = accountFundsHelper(
380 cancelView_,
381 offer_.owner(),
382 amount.out,
383 offer_.issueOut(),
385 j_);
386
387 if (original_funds == *ownerFunds_)
388 {
389 permRmOffer(entry->key());
390 JLOG(j_.trace())
391 << "Removing tiny offer due to reduced quality "
392 << entry->key();
393 }
394 else
395 {
396 JLOG(j_.trace()) << "Removing tiny offer that became tiny due "
397 "to reduced quality "
398 << entry->key();
399 }
400 offer_ = TOffer<TIn, TOut>{};
401 // See comment at top of loop for how the offer is removed
402 continue;
403 }
404
405 break;
406 }
407
408 return true;
409}
410
411void
412OfferStream::permRmOffer(uint256 const& offerIndex)
413{
414 offerDelete(cancelView_, cancelView_.peek(keylet::offer(offerIndex)), j_);
415}
416
417template <class TIn, class TOut>
418void
420{
421 permToRemove_.insert(offerIndex);
422}
423
428
433} // 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:554
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:350
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:387
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:1647