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