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