rippled
Loading...
Searching...
No Matches
OfferStream.cpp
1#include <xrpld/app/misc/PermissionedDEXHelpers.h>
2#include <xrpld/app/tx/detail/OfferStream.h>
3
4#include <xrpl/basics/Log.h>
5#include <xrpl/ledger/View.h>
6#include <xrpl/protocol/Feature.h>
7#include <xrpl/protocol/LedgerFormats.h>
8
9namespace xrpl {
10
11namespace {
12bool
13checkIssuers(ReadView const& view, Book const& book)
14{
15 auto issuerExists = [](ReadView const& view, Issue const& iss) -> bool {
16 return isXRP(iss.account) || view.read(keylet::account(iss.account));
17 };
18 return issuerExists(view, book.in) && issuerExists(view, book.out);
19}
20} // namespace
21
22template <class TIn, class TOut>
24 ApplyView& view,
25 ApplyView& cancelView,
26 Book const& book,
28 StepCounter& counter,
29 beast::Journal journal)
30 : j_(journal)
31 , view_(view)
32 , cancelView_(cancelView)
33 , book_(book)
34 , validBook_(checkIssuers(view, book))
35 , expire_(when)
36 , tip_(view, book_)
37 , counter_(counter)
38{
39 XRPL_ASSERT(validBook_, "xrpl::TOfferStreamBase::TOfferStreamBase : valid book");
40}
41
42// Handle the case where a directory item with no corresponding ledger entry
43// is found. This shouldn't happen but if it does we clean it up.
44template <class TIn, class TOut>
45void
47{
48 // NIKB NOTE This should be using ApplyView::dirRemove, which would
49 // correctly remove the directory if its the last entry.
50 // Unfortunately this is a protocol breaking change.
51
52 auto p = view.peek(keylet::page(tip_.dir()));
53
54 if (p == nullptr)
55 {
56 JLOG(j_.error()) << "Missing directory " << tip_.dir() << " for offer " << tip_.index();
57 return;
58 }
59
60 auto v(p->getFieldV256(sfIndexes));
61 auto it(std::find(v.begin(), v.end(), tip_.index()));
63 if (it == v.end())
64 {
65 JLOG(j_.error()) << "Missing offer " << tip_.index() << " for directory " << tip_.dir();
66 return;
67 }
68
69 v.erase(it);
70 p->setFieldV256(sfIndexes, v);
71 view.update(p);
73 JLOG(j_.trace()) << "Missing offer " << tip_.index() << " removed from directory " << tip_.dir();
74}
75
76static STAmount
78 ReadView const& view,
79 AccountID const& id,
80 STAmount const& saDefault,
81 Issue const&,
82 FreezeHandling freezeHandling,
84{
85 return accountFunds(view, id, saDefault, freezeHandling, j);
86}
87
88static IOUAmount
90 ReadView const& view,
91 AccountID const& id,
92 IOUAmount const& amtDefault,
93 Issue const& issue,
94 FreezeHandling freezeHandling,
96{
97 if (issue.account == id)
98 // self funded
99 return amtDefault;
101 return toAmount<IOUAmount>(accountHolds(view, id, issue.currency, issue.account, freezeHandling, j));
102}
103
104static XRPAmount
106 ReadView const& view,
107 AccountID const& id,
108 XRPAmount const& amtDefault,
109 Issue const& issue,
110 FreezeHandling freezeHandling,
112{
113 return toAmount<XRPAmount>(accountHolds(view, id, issue.currency, issue.account, freezeHandling, j));
114}
115
116template <class TIn, class TOut>
117template <class TTakerPays, class TTakerGets>
118bool
120{
121 static_assert(
123
124 static_assert(
126
127 static_assert(
129
130 // Consider removing the offer if:
131 // o `TakerPays` is XRP (because of XRP drops granularity) or
132 // o `TakerPays` and `TakerGets` are both IOU and `TakerPays`<`TakerGets`
133 constexpr bool const inIsXRP = std::is_same_v<TTakerPays, XRPAmount>;
134 constexpr bool const outIsXRP = std::is_same_v<TTakerGets, XRPAmount>;
135
136 if constexpr (outIsXRP)
137 {
138 // If `TakerGets` is XRP, the worst this offer's quality can change is
139 // to about 10^-81 `TakerPays` and 1 drop `TakerGets`. This will be
140 // remarkably good quality for any realistic asset, so these offers
141 // don't need this extra check.
142 return false;
143 }
144
145 TAmounts<TTakerPays, TTakerGets> const ofrAmts{
146 toAmount<TTakerPays>(offer_.amount().in), toAmount<TTakerGets>(offer_.amount().out)};
147
148 if constexpr (!inIsXRP && !outIsXRP)
149 {
150 if (ofrAmts.in >= ofrAmts.out)
151 return false;
152 }
153
154 TTakerGets const ownerFunds = toAmount<TTakerGets>(*ownerFunds_);
155
156 auto const effectiveAmounts = [&] {
157 if (offer_.owner() != offer_.issueOut().account && ownerFunds < ofrAmts.out)
158 {
159 // adjust the amounts by owner funds.
160 //
161 // It turns out we can prevent order book blocking by rounding down
162 // the ceil_out() result.
163 return offer_.quality().ceil_out_strict(ofrAmts, ownerFunds, /* roundUp */ false);
164 }
165 return ofrAmts;
166 }();
167
168 // If either the effective in or out are zero then remove the offer.
169 if (effectiveAmounts.in.signum() <= 0 || effectiveAmounts.out.signum() <= 0)
170 return true;
171
172 if (effectiveAmounts.in > TTakerPays::minPositiveAmount())
173 return false;
174
175 Quality const effectiveQuality{effectiveAmounts};
176 return effectiveQuality < offer_.quality();
177}
178
179template <class TIn, class TOut>
180bool
182{
183 // Modifying the order or logic of these
184 // operations causes a protocol breaking change.
185
186 if (!validBook_)
187 return false;
188
189 for (;;)
190 {
191 ownerFunds_ = std::nullopt;
192 // BookTip::step deletes the current offer from the view before
193 // advancing to the next (unless the ledger entry is missing).
194 if (!tip_.step(j_))
195 return false;
196
197 std::shared_ptr<SLE> entry = tip_.entry();
198
199 // If we exceed the maximum number of allowed steps, we're done.
200 if (!counter_.step())
201 return false;
202
203 // Remove if missing
204 if (!entry)
205 {
206 erase(view_);
207 erase(cancelView_);
208 continue;
209 }
210
211 // Remove if expired
212 using d = NetClock::duration;
213 using tp = NetClock::time_point;
214 if (entry->isFieldPresent(sfExpiration) && tp{d{(*entry)[sfExpiration]}} <= expire_)
215 {
216 JLOG(j_.trace()) << "Removing expired offer " << entry->key();
217 permRmOffer(entry->key());
218 continue;
219 }
220
221 offer_ = TOffer<TIn, TOut>(entry, tip_.quality());
222
223 auto const amount(offer_.amount());
224
225 // Remove if either amount is zero
226 if (amount.empty())
227 {
228 JLOG(j_.warn()) << "Removing bad offer " << entry->key();
229 permRmOffer(entry->key());
230 offer_ = TOffer<TIn, TOut>{};
231 continue;
232 }
233
234 bool const deepFrozen =
235 isDeepFrozen(view_, offer_.owner(), offer_.issueIn().currency, offer_.issueIn().account);
236 if (deepFrozen)
237 {
238 JLOG(j_.trace()) << "Removing deep frozen unfunded offer " << entry->key();
239 permRmOffer(entry->key());
240 offer_ = TOffer<TIn, TOut>{};
241 continue;
242 }
243
244 if (entry->isFieldPresent(sfDomainID) &&
245 !permissioned_dex::offerInDomain(view_, entry->key(), entry->getFieldH256(sfDomainID), j_))
246 {
247 JLOG(j_.trace()) << "Removing offer no longer in domain " << entry->key();
248 permRmOffer(entry->key());
249 offer_ = TOffer<TIn, TOut>{};
250 continue;
251 }
252
253 // Calculate owner funds
254 ownerFunds_ = accountFundsHelper(view_, offer_.owner(), amount.out, offer_.issueOut(), fhZERO_IF_FROZEN, j_);
255
256 // Check for unfunded offer
257 if (*ownerFunds_ <= beast::zero)
258 {
259 // If the owner's balance in the pristine view is the same,
260 // we haven't modified the balance and therefore the
261 // offer is "found unfunded" versus "became unfunded"
262 auto const original_funds =
263 accountFundsHelper(cancelView_, offer_.owner(), amount.out, offer_.issueOut(), fhZERO_IF_FROZEN, j_);
264
265 if (original_funds == *ownerFunds_)
266 {
267 permRmOffer(entry->key());
268 JLOG(j_.trace()) << "Removing unfunded offer " << entry->key();
269 }
270 else
271 {
272 JLOG(j_.trace()) << "Removing became unfunded offer " << entry->key();
273 }
274 offer_ = TOffer<TIn, TOut>{};
275 // See comment at top of loop for how the offer is removed
276 continue;
277 }
278
279 bool const rmSmallIncreasedQOffer = [&] {
280 bool const inIsXRP = isXRP(offer_.issueIn());
281 bool const outIsXRP = isXRP(offer_.issueOut());
282 if (inIsXRP && !outIsXRP)
283 {
284 // Without the `if constexpr`, the
285 // `shouldRmSmallIncreasedQOffer` template will be instantiated
286 // even if it is never used. This can cause compiler errors in
287 // some cases, hence the `if constexpr` guard.
288 // Note that TIn can be XRPAmount or STAmount, and TOut can be
289 // IOUAmount or STAmount.
291 return shouldRmSmallIncreasedQOffer<XRPAmount, IOUAmount>();
292 }
293 if (!inIsXRP && outIsXRP)
294 {
295 // See comment above for `if constexpr` rationale
297 return shouldRmSmallIncreasedQOffer<IOUAmount, XRPAmount>();
298 }
299 if (!inIsXRP && !outIsXRP)
300 {
301 // See comment above for `if constexpr` rationale
303 return shouldRmSmallIncreasedQOffer<IOUAmount, IOUAmount>();
304 }
305 // LCOV_EXCL_START
306 UNREACHABLE(
307 "xrpl::TOfferStreamBase::step::rmSmallIncreasedQOffer : XRP "
308 "vs XRP offer");
309 return false;
310 // LCOV_EXCL_STOP
311 }();
312
313 if (rmSmallIncreasedQOffer)
314 {
315 auto const original_funds =
316 accountFundsHelper(cancelView_, offer_.owner(), amount.out, offer_.issueOut(), fhZERO_IF_FROZEN, j_);
317
318 if (original_funds == *ownerFunds_)
319 {
320 permRmOffer(entry->key());
321 JLOG(j_.trace()) << "Removing tiny offer due to reduced quality " << entry->key();
322 }
323 else
324 {
325 JLOG(j_.trace()) << "Removing tiny offer that became tiny due "
326 "to reduced quality "
327 << entry->key();
328 }
329 offer_ = TOffer<TIn, TOut>{};
330 // See comment at top of loop for how the offer is removed
331 continue;
332 }
333
334 break;
335 }
336
337 return true;
338}
339
340void
341OfferStream::permRmOffer(uint256 const& offerIndex)
342{
343 offerDelete(cancelView_, cancelView_.peek(keylet::offer(offerIndex)), j_);
344}
345
346template <class TIn, class TOut>
347void
349{
350 permToRemove_.insert(offerIndex);
351}
352
357
362} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:40
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:114
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:16
Presents and consumes the offers in an order book.
Floating point representation of amounts with high dynamic range.
Definition IOUAmount.h:25
A currency issued by an account.
Definition Issue.h:13
Currency currency
Definition Issue.h:15
AccountID account
Definition Issue.h:16
std::chrono::time_point< NetClock > time_point
Definition chrono.h:45
std::chrono::duration< rep, period > duration
Definition chrono.h:44
A view into a ledger.
Definition ReadView.h:31
TOfferStreamBase(ApplyView &view, ApplyView &cancelView, Book const &book, NetClock::time_point when, StepCounter &counter, beast::Journal journal)
bool shouldRmSmallIncreasedQOffer() const
bool step()
Advance to the next valid offer.
void erase(ApplyView &view)
T find(T... args)
T is_same_v
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:331
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:5
FreezeHandling
Controls the treatment of frozen account balances.
Definition View.h:58
@ fhZERO_IF_FROZEN
Definition View.h:58
bool isXRP(AccountID const &c)
Definition AccountID.h:70
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=shSIMPLE_BALANCE)
Definition View.cpp:392
bool isDeepFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:277
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:515
XRPAmount toAmount< XRPAmount >(STAmount const &amt)
static STAmount accountFundsHelper(ReadView const &view, AccountID const &id, STAmount const &saDefault, Issue const &, FreezeHandling freezeHandling, beast::Journal j)
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1672
IOUAmount toAmount< IOUAmount >(STAmount const &amt)
void erase(STObject &st, TypedField< U > const &f)
Remove a field in an STObject.
Definition STExchange.h:148