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/tx/detail/OfferStream.h>
21
22#include <xrpl/basics/Log.h>
23#include <xrpl/protocol/Feature.h>
24
25namespace ripple {
26
27namespace {
28bool
29checkIssuers(ReadView const& view, Book const& book)
30{
31 auto issuerExists = [](ReadView const& view, Issue const& iss) -> bool {
32 return isXRP(iss.account) || view.read(keylet::account(iss.account));
33 };
34 return issuerExists(view, book.in) && issuerExists(view, book.out);
35}
36} // namespace
37
38template <class TIn, class TOut>
40 ApplyView& view,
41 ApplyView& cancelView,
42 Book const& book,
44 StepCounter& counter,
45 beast::Journal journal)
46 : j_(journal)
47 , view_(view)
48 , cancelView_(cancelView)
49 , book_(book)
50 , validBook_(checkIssuers(view, book))
51 , expire_(when)
52 , tip_(view, book_)
53 , counter_(counter)
54{
55 XRPL_ASSERT(
56 validBook_, "ripple::TOfferStreamBase::TOfferStreamBase : valid book");
57}
58
59// Handle the case where a directory item with no corresponding ledger entry
60// is found. This shouldn't happen but if it does we clean it up.
61template <class TIn, class TOut>
62void
64{
65 // NIKB NOTE This should be using ApplyView::dirRemove, which would
66 // correctly remove the directory if its the last entry.
67 // Unfortunately this is a protocol breaking change.
68
69 auto p = view.peek(keylet::page(tip_.dir()));
70
71 if (p == nullptr)
72 {
73 JLOG(j_.error()) << "Missing directory " << tip_.dir() << " for offer "
74 << tip_.index();
75 return;
76 }
77
78 auto v(p->getFieldV256(sfIndexes));
79 auto it(std::find(v.begin(), v.end(), tip_.index()));
80
81 if (it == v.end())
82 {
83 JLOG(j_.error()) << "Missing offer " << tip_.index()
84 << " for directory " << tip_.dir();
85 return;
86 }
87
88 v.erase(it);
89 p->setFieldV256(sfIndexes, v);
90 view.update(p);
91
92 JLOG(j_.trace()) << "Missing offer " << tip_.index()
93 << " removed from directory " << tip_.dir();
94}
95
96static STAmount
98 ReadView const& view,
99 AccountID const& id,
100 STAmount const& saDefault,
101 Issue const&,
102 FreezeHandling freezeHandling,
104{
105 return accountFunds(view, id, saDefault, freezeHandling, j);
106}
107
108static IOUAmount
110 ReadView const& view,
111 AccountID const& id,
112 IOUAmount const& amtDefault,
113 Issue const& issue,
114 FreezeHandling freezeHandling,
116{
117 if (issue.account == id)
118 // self funded
119 return amtDefault;
120
122 view, id, issue.currency, issue.account, freezeHandling, j));
123}
124
125static XRPAmount
127 ReadView const& view,
128 AccountID const& id,
129 XRPAmount const& amtDefault,
130 Issue const& issue,
131 FreezeHandling freezeHandling,
133{
135 view, id, issue.currency, issue.account, freezeHandling, j));
136}
137
138template <class TIn, class TOut>
139template <class TTakerPays, class TTakerGets>
140bool
142{
143 static_assert(
144 std::is_same_v<TTakerPays, IOUAmount> ||
145 std::is_same_v<TTakerPays, XRPAmount>,
146 "STAmount is not supported");
147
148 static_assert(
149 std::is_same_v<TTakerGets, IOUAmount> ||
150 std::is_same_v<TTakerGets, XRPAmount>,
151 "STAmount is not supported");
152
153 static_assert(
154 !std::is_same_v<TTakerPays, XRPAmount> ||
155 !std::is_same_v<TTakerGets, XRPAmount>,
156 "Cannot have XRP/XRP offers");
157
158 if (!view_.rules().enabled(fixRmSmallIncreasedQOffers))
159 return false;
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 // Calculate owner funds
292 ownerFunds_ = accountFundsHelper(
293 view_,
294 offer_.owner(),
295 amount.out,
296 offer_.issueOut(),
298 j_);
299
300 // Check for unfunded offer
301 if (*ownerFunds_ <= beast::zero)
302 {
303 // If the owner's balance in the pristine view is the same,
304 // we haven't modified the balance and therefore the
305 // offer is "found unfunded" versus "became unfunded"
306 auto const original_funds = accountFundsHelper(
307 cancelView_,
308 offer_.owner(),
309 amount.out,
310 offer_.issueOut(),
312 j_);
313
314 if (original_funds == *ownerFunds_)
315 {
316 permRmOffer(entry->key());
317 JLOG(j_.trace()) << "Removing unfunded offer " << entry->key();
318 }
319 else
320 {
321 JLOG(j_.trace())
322 << "Removing became unfunded offer " << entry->key();
323 }
324 offer_ = TOffer<TIn, TOut>{};
325 // See comment at top of loop for how the offer is removed
326 continue;
327 }
328
329 bool const rmSmallIncreasedQOffer = [&] {
330 bool const inIsXRP = isXRP(offer_.issueIn());
331 bool const outIsXRP = isXRP(offer_.issueOut());
332 if (inIsXRP && !outIsXRP)
333 {
334 // Without the `if constexpr`, the
335 // `shouldRmSmallIncreasedQOffer` template will be instantiated
336 // even if it is never used. This can cause compiler errors in
337 // some cases, hence the `if constexpr` guard.
338 // Note that TIn can be XRPAmount or STAmount, and TOut can be
339 // IOUAmount or STAmount.
340 if constexpr (!(std::is_same_v<TIn, IOUAmount> ||
341 std::is_same_v<TOut, XRPAmount>))
342 return shouldRmSmallIncreasedQOffer<XRPAmount, IOUAmount>();
343 }
344 if (!inIsXRP && outIsXRP)
345 {
346 // See comment above for `if constexpr` rationale
347 if constexpr (!(std::is_same_v<TIn, XRPAmount> ||
348 std::is_same_v<TOut, IOUAmount>))
349 return shouldRmSmallIncreasedQOffer<IOUAmount, XRPAmount>();
350 }
351 if (!inIsXRP && !outIsXRP)
352 {
353 // See comment above for `if constexpr` rationale
354 if constexpr (!(std::is_same_v<TIn, XRPAmount> ||
355 std::is_same_v<TOut, XRPAmount>))
356 return shouldRmSmallIncreasedQOffer<IOUAmount, IOUAmount>();
357 }
358 UNREACHABLE(
359 "rippls::TOfferStreamBase::step::rmSmallIncreasedQOffer : XRP "
360 "vs XRP offer");
361 return false;
362 }();
363
364 if (rmSmallIncreasedQOffer)
365 {
366 auto const original_funds = accountFundsHelper(
367 cancelView_,
368 offer_.owner(),
369 amount.out,
370 offer_.issueOut(),
372 j_);
373
374 if (original_funds == *ownerFunds_)
375 {
376 permRmOffer(entry->key());
377 JLOG(j_.trace())
378 << "Removing tiny offer due to reduced quality "
379 << entry->key();
380 }
381 else
382 {
383 JLOG(j_.trace()) << "Removing tiny offer that became tiny due "
384 "to reduced quality "
385 << entry->key();
386 }
387 offer_ = TOffer<TIn, TOut>{};
388 // See comment at top of loop for how the offer is removed
389 continue;
390 }
391
392 break;
393 }
394
395 return true;
396}
397
398void
399OfferStream::permRmOffer(uint256 const& offerIndex)
400{
401 offerDelete(cancelView_, cancelView_.peek(keylet::offer(offerIndex)), j_);
402}
403
404template <class TIn, class TOut>
405void
407{
408 permToRemove_.insert(offerIndex);
409}
410
415
420} // 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:144
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:35
Presents and consumes the offers in an order book.
Definition: OfferStream.h:176
Floating point representation of amounts with high dynamic range.
Definition: IOUAmount.h:46
A currency issued by an account.
Definition: Issue.h:36
AccountID account
Definition: Issue.h:39
Currency currency
Definition: Issue.h:38
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:52
void erase(ApplyView &view)
Definition: OfferStream.cpp:63
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)
Definition: OfferStream.cpp:39
bool shouldRmSmallIncreasedQOffer() const
T find(T... args)
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:177
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition: Indexes.cpp:373
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
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:78
@ fhZERO_IF_FROZEN
Definition: View.h:78
bool isXRP(AccountID const &c)
Definition: AccountID.h:91
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)
Definition: OfferStream.cpp:97
IOUAmount toAmount< IOUAmount >(STAmount const &amt)
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition: View.cpp:1459
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)