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