rippled
Loading...
Searching...
No Matches
PaySteps.cpp
1#include <xrpl/basics/contract.h>
2#include <xrpl/json/json_writer.h>
3#include <xrpl/ledger/ReadView.h>
4#include <xrpl/protocol/IOUAmount.h>
5#include <xrpl/protocol/XRPAmount.h>
6#include <xrpl/tx/paths/detail/Steps.h>
7
8#include <algorithm>
9
10namespace xrpl {
11
12// Check equal with tolerance
13bool
14checkNear(IOUAmount const& expected, IOUAmount const& actual)
15{
16 double const ratTol = 0.001;
17 if (abs(expected.exponent() - actual.exponent()) > 1)
18 return false;
19
20 if (actual.exponent() < -20)
21 return true;
22
23 auto const a =
24 (expected.exponent() < actual.exponent()) ? expected.mantissa() / 10 : expected.mantissa();
25 auto const b =
26 (actual.exponent() < expected.exponent()) ? actual.mantissa() / 10 : actual.mantissa();
27 if (a == b)
28 return true;
29
30 double const diff = std::abs(a - b);
31 auto const r = diff / std::max(std::abs(a), std::abs(b));
32 return r <= ratTol;
33};
34
35bool
36checkNear(XRPAmount const& expected, XRPAmount const& actual)
37{
38 return expected == actual;
39};
40
41static bool
43{
45 return false;
46 return isXRP(pe.getAccountID());
47};
48
51 StrandContext const& ctx,
52 STPathElement const* e1,
53 STPathElement const* e2,
54 Issue const& curIssue)
55{
56 auto& j = ctx.j;
57
58 if (ctx.isFirst && e1->isAccount() &&
59 ((e1->getNodeType() & STPathElement::typeCurrency) != 0u) && isXRP(e1->getCurrency()))
60 {
61 return make_XRPEndpointStep(ctx, e1->getAccountID());
62 }
63
64 if (ctx.isLast && isXRPAccount(*e1) && e2->isAccount())
65 return make_XRPEndpointStep(ctx, e2->getAccountID());
66
67 if (e1->isAccount() && e2->isAccount())
68 {
69 return make_DirectStepI(ctx, e1->getAccountID(), e2->getAccountID(), curIssue.currency);
70 }
71
72 if (e1->isOffer() && e2->isAccount())
73 {
74 // LCOV_EXCL_START
75 // should already be taken care of
76 JLOG(j.error()) << "Found offer/account payment step. Aborting payment strand.";
77 UNREACHABLE("xrpl::toStep : offer/account payment payment strand");
79 // LCOV_EXCL_STOP
80 }
81
82 XRPL_ASSERT(
85 "xrpl::toStep : currency or issuer");
86 auto const outCurrency = ((e2->getNodeType() & STPathElement::typeCurrency) != 0u)
87 ? e2->getCurrency()
88 : curIssue.currency;
89 auto const outIssuer = ((e2->getNodeType() & STPathElement::typeIssuer) != 0u)
90 ? e2->getIssuerID()
91 : curIssue.account;
92
93 if (isXRP(curIssue.currency) && isXRP(outCurrency))
94 {
95 JLOG(j.info()) << "Found xrp/xrp offer payment step";
97 }
98
99 XRPL_ASSERT(e2->isOffer(), "xrpl::toStep : is offer");
100
101 if (isXRP(outCurrency))
102 return make_BookStepIX(ctx, curIssue);
103
104 if (isXRP(curIssue.currency))
105 return make_BookStepXI(ctx, {outCurrency, outIssuer});
106
107 return make_BookStepII(ctx, curIssue, {outCurrency, outIssuer});
108}
109
112 ReadView const& view,
113 AccountID const& src,
114 AccountID const& dst,
115 Issue const& deliver,
116 std::optional<Quality> const& limitQuality,
117 std::optional<Issue> const& sendMaxIssue,
118 STPath const& path,
119 bool ownerPaysTransferFee,
120 OfferCrossing offerCrossing,
121 AMMContext& ammContext,
122 std::optional<uint256> const& domainID,
124{
125 if (isXRP(src) || isXRP(dst) || !isConsistent(deliver) ||
126 (sendMaxIssue && !isConsistent(*sendMaxIssue)))
127 return {temBAD_PATH, Strand{}};
128
129 if ((sendMaxIssue && sendMaxIssue->account == noAccount()) || (src == noAccount()) ||
130 (dst == noAccount()) || (deliver.account == noAccount()))
131 return {temBAD_PATH, Strand{}};
132
133 for (auto const& pe : path)
134 {
135 auto const t = pe.getNodeType();
136
137 if (((t & ~STPathElement::typeAll) != 0u) || (t == 0u))
138 return {temBAD_PATH, Strand{}};
139
140 bool const hasAccount = (t & STPathElement::typeAccount) != 0u;
141 bool const hasIssuer = (t & STPathElement::typeIssuer) != 0u;
142 bool const hasCurrency = (t & STPathElement::typeCurrency) != 0u;
143
144 if (hasAccount && (hasIssuer || hasCurrency))
145 return {temBAD_PATH, Strand{}};
146
147 if (hasIssuer && isXRP(pe.getIssuerID()))
148 return {temBAD_PATH, Strand{}};
149
150 if (hasAccount && isXRP(pe.getAccountID()))
151 return {temBAD_PATH, Strand{}};
152
153 if (hasCurrency && hasIssuer && isXRP(pe.getCurrency()) != isXRP(pe.getIssuerID()))
154 return {temBAD_PATH, Strand{}};
155
156 if (hasIssuer && (pe.getIssuerID() == noAccount()))
157 return {temBAD_PATH, Strand{}};
158
159 if (hasAccount && (pe.getAccountID() == noAccount()))
160 return {temBAD_PATH, Strand{}};
161 }
162
163 Issue curIssue = [&] {
164 auto const& currency = sendMaxIssue ? sendMaxIssue->currency : deliver.currency;
165 if (isXRP(currency))
166 return xrpIssue();
167 return Issue{currency, src};
168 }();
169
170 auto hasCurrency = [](STPathElement const pe) {
171 return pe.getNodeType() & STPathElement::typeCurrency;
172 };
173
175 // reserve enough for the path, the implied source, destination,
176 // sendmax and deliver.
177 normPath.reserve(4 + path.size());
178 {
179 normPath.emplace_back(STPathElement::typeAll, src, curIssue.currency, curIssue.account);
180
181 if (sendMaxIssue && sendMaxIssue->account != src &&
182 (path.empty() || !path[0].isAccount() ||
183 path[0].getAccountID() != sendMaxIssue->account))
184 {
185 normPath.emplace_back(sendMaxIssue->account, std::nullopt, std::nullopt);
186 }
187
188 for (auto const& i : path)
189 normPath.push_back(i);
190
191 {
192 // Note that for offer crossing (only) we do use an offer book
193 // even if all that is changing is the Issue.account.
194 STPathElement const& lastCurrency =
195 *std::find_if(normPath.rbegin(), normPath.rend(), hasCurrency);
196 if ((lastCurrency.getCurrency() != deliver.currency) ||
197 ((offerCrossing != 0u) && lastCurrency.getIssuerID() != deliver.account))
198 {
199 normPath.emplace_back(std::nullopt, deliver.currency, deliver.account);
200 }
201 }
202
203 if (!((normPath.back().isAccount() && normPath.back().getAccountID() == deliver.account) ||
204 (dst == deliver.account)))
205 {
206 normPath.emplace_back(deliver.account, std::nullopt, std::nullopt);
207 }
208
209 if (!normPath.back().isAccount() || normPath.back().getAccountID() != dst)
210 {
211 normPath.emplace_back(dst, std::nullopt, std::nullopt);
212 }
213 }
214
215 if (normPath.size() < 2)
216 return {temBAD_PATH, Strand{}};
217
218 auto const strandSrc = normPath.front().getAccountID();
219 auto const strandDst = normPath.back().getAccountID();
220 bool const isDefaultPath = path.empty();
221
222 Strand result;
223 result.reserve(2 * normPath.size());
224
225 /* A strand may not include the same account node more than once
226 in the same currency. In a direct step, an account will show up
227 at most twice: once as a src and once as a dst (hence the two element
228 array). The strandSrc and strandDst will only show up once each.
229 */
231 // A strand may not include the same offer book more than once
232 boost::container::flat_set<Issue> seenBookOuts;
233 seenDirectIssues[0].reserve(normPath.size());
234 seenDirectIssues[1].reserve(normPath.size());
235 seenBookOuts.reserve(normPath.size());
236 auto ctx = [&](bool isLast = false) {
237 return StrandContext{
238 view,
239 result,
240 strandSrc,
241 strandDst,
242 deliver,
243 limitQuality,
244 isLast,
245 ownerPaysTransferFee,
246 offerCrossing,
248 seenDirectIssues,
249 seenBookOuts,
250 ammContext,
251 domainID,
252 j};
253 };
254
255 for (std::size_t i = 0; i < normPath.size() - 1; ++i)
256 {
257 /* Iterate through the path elements considering them in pairs.
258 The first element of the pair is `cur` and the second element is
259 `next`. When an offer is one of the pairs, the step created will be
260 for `next`. This means when `cur` is an offer and `next` is an
261 account then no step is created, as a step has already been created
262 for that offer.
263 */
265 auto cur = &normPath[i];
266 auto const next = &normPath[i + 1];
267
268 if (cur->isAccount())
269 {
270 curIssue.account = cur->getAccountID();
271 }
272 else if (cur->hasIssuer())
273 {
274 curIssue.account = cur->getIssuerID();
275 }
276
277 if (cur->hasCurrency())
278 {
279 curIssue.currency = cur->getCurrency();
280 if (isXRP(curIssue.currency))
281 curIssue.account = xrpAccount();
282 }
283
284 if (cur->isAccount() && next->isAccount())
285 {
286 if (!isXRP(curIssue.currency) && curIssue.account != cur->getAccountID() &&
287 curIssue.account != next->getAccountID())
288 {
289 JLOG(j.trace()) << "Inserting implied account";
290 auto msr = make_DirectStepI(
291 ctx(), cur->getAccountID(), curIssue.account, curIssue.currency);
292 if (!isTesSuccess(msr.first))
293 return {msr.first, Strand{}};
294 result.push_back(std::move(msr.second));
295 impliedPE.emplace(
297 cur = &*impliedPE;
298 }
299 }
300 else if (cur->isAccount() && next->isOffer())
301 {
302 if (curIssue.account != cur->getAccountID())
303 {
304 JLOG(j.trace()) << "Inserting implied account before offer";
305 auto msr = make_DirectStepI(
306 ctx(), cur->getAccountID(), curIssue.account, curIssue.currency);
307 if (!isTesSuccess(msr.first))
308 return {msr.first, Strand{}};
309 result.push_back(std::move(msr.second));
310 impliedPE.emplace(
312 cur = &*impliedPE;
313 }
314 }
315 else if (cur->isOffer() && next->isAccount())
316 {
317 if (curIssue.account != next->getAccountID() && !isXRP(next->getAccountID()))
318 {
319 if (isXRP(curIssue))
320 {
321 if (i != normPath.size() - 2)
322 return {temBAD_PATH, Strand{}};
323
324 // Last step. insert xrp endpoint step
325 auto msr = make_XRPEndpointStep(ctx(), next->getAccountID());
326 if (!isTesSuccess(msr.first))
327 return {msr.first, Strand{}};
328 result.push_back(std::move(msr.second));
329 }
330 else
331 {
332 JLOG(j.trace()) << "Inserting implied account after offer";
333 auto msr = make_DirectStepI(
334 ctx(), curIssue.account, next->getAccountID(), curIssue.currency);
335 if (!isTesSuccess(msr.first))
336 return {msr.first, Strand{}};
337 result.push_back(std::move(msr.second));
338 }
339 }
340 continue;
341 }
342
343 if (!next->isOffer() && next->hasCurrency() && next->getCurrency() != curIssue.currency)
344 {
345 // Should never happen
346 // LCOV_EXCL_START
347 UNREACHABLE("xrpl::toStrand : offer currency mismatch");
348 return {temBAD_PATH, Strand{}};
349 // LCOV_EXCL_STOP
350 }
351
352 auto s = toStep(ctx(/*isLast*/ i == normPath.size() - 2), cur, next, curIssue);
353 if (isTesSuccess(s.first))
354 {
355 result.emplace_back(std::move(s.second));
356 }
357 else
358 {
359 JLOG(j.debug()) << "toStep failed: " << s.first;
360 return {s.first, Strand{}};
361 }
362 }
363
364 auto checkStrand = [&]() -> bool {
365 auto stepAccts = [](Step const& s) -> std::pair<AccountID, AccountID> {
366 if (auto r = s.directStepAccts())
367 return *r;
368 if (auto const r = s.bookStepBook())
369 return std::make_pair(r->in.account, r->out.account);
370 Throw<FlowException>(tefEXCEPTION, "Step should be either a direct or book step");
372 };
373
374 auto curAcc = src;
375 auto curIss = [&] {
376 auto& currency = sendMaxIssue ? sendMaxIssue->currency : deliver.currency;
377 if (isXRP(currency))
378 return xrpIssue();
379 return Issue{currency, src};
380 }();
381
382 for (auto const& s : result)
383 {
384 auto const accts = stepAccts(*s);
385 if (accts.first != curAcc)
386 return false;
387
388 if (auto const b = s->bookStepBook())
389 {
390 if (curIss != b->in)
391 return false;
392 curIss = b->out;
393 }
394 else
395 {
396 curIss.account = accts.second;
397 }
398
399 curAcc = accts.second;
400 }
401 if (curAcc != dst)
402 return false;
403 if (curIss.currency != deliver.currency)
404 return false;
405 if (curIss.account != deliver.account && curIss.account != dst)
406 return false;
407 return true;
408 };
409
410 if (!checkStrand())
411 {
412 // LCOV_EXCL_START
413 JLOG(j.warn()) << "Flow check strand failed";
414 UNREACHABLE("xrpl::toStrand : invalid strand");
415 return {temBAD_PATH, Strand{}};
416 // LCOV_EXCL_STOP
417 }
418
419 return {tesSUCCESS, std::move(result)};
420}
421
424 ReadView const& view,
425 AccountID const& src,
426 AccountID const& dst,
427 Issue const& deliver,
428 std::optional<Quality> const& limitQuality,
429 std::optional<Issue> const& sendMax,
430 STPathSet const& paths,
431 bool addDefaultPath,
432 bool ownerPaysTransferFee,
433 OfferCrossing offerCrossing,
434 AMMContext& ammContext,
435 std::optional<uint256> const& domainID,
437{
438 std::vector<Strand> result;
439 result.reserve(1 + paths.size());
440 // Insert the strand into result if it is not already part of the vector
441 auto insert = [&](Strand s) {
442 bool const hasStrand = std::find(result.begin(), result.end(), s) != result.end();
443
444 if (!hasStrand)
445 result.emplace_back(std::move(s));
446 };
447
448 if (addDefaultPath)
449 {
450 auto sp = toStrand(
451 view,
452 src,
453 dst,
454 deliver,
455 limitQuality,
456 sendMax,
457 STPath(),
458 ownerPaysTransferFee,
459 offerCrossing,
460 ammContext,
461 domainID,
462 j);
463 auto const ter = sp.first;
464 auto& strand = sp.second;
465
466 if (!isTesSuccess(ter))
467 {
468 JLOG(j.trace()) << "failed to add default path";
469 if (isTemMalformed(ter) || paths.empty())
470 {
471 return {ter, std::vector<Strand>{}};
472 }
473 }
474 else if (strand.empty())
475 {
476 JLOG(j.trace()) << "toStrand failed";
477 Throw<FlowException>(tefEXCEPTION, "toStrand returned tes & empty strand");
478 }
479 else
480 {
481 insert(std::move(strand));
482 }
483 }
484 else if (paths.empty())
485 {
486 JLOG(j.debug()) << "Flow: Invalid transaction: No paths and direct "
487 "ripple not allowed.";
489 }
490
491 TER lastFailTer = tesSUCCESS;
492 for (auto const& p : paths)
493 {
494 auto sp = toStrand(
495 view,
496 src,
497 dst,
498 deliver,
499 limitQuality,
500 sendMax,
501 p,
502 ownerPaysTransferFee,
503 offerCrossing,
504 ammContext,
505 domainID,
506 j);
507 auto ter = sp.first;
508 auto& strand = sp.second;
509
510 if (!isTesSuccess(ter))
511 {
512 lastFailTer = ter;
513 JLOG(j.trace()) << "failed to add path: ter: " << ter
514 << "path: " << p.getJson(JsonOptions::none);
515 if (isTemMalformed(ter))
516 return {ter, std::vector<Strand>{}};
517 }
518 else if (strand.empty())
519 {
520 JLOG(j.trace()) << "toStrand failed";
521 Throw<FlowException>(tefEXCEPTION, "toStrand returned tes & empty strand");
522 }
523 else
524 {
525 insert(std::move(strand));
526 }
527 }
528
529 if (result.empty())
530 return {lastFailTer, std::move(result)};
531
532 return {tesSUCCESS, std::move(result)};
533}
534
536 ReadView const& view_,
537 std::vector<std::unique_ptr<Step>> const& strand_,
538 // A strand may not include an inner node that
539 // replicates the source or destination.
540 AccountID const& strandSrc_,
541 AccountID const& strandDst_,
542 Issue const& strandDeliver_,
543 std::optional<Quality> const& limitQuality_,
544 bool isLast_,
545 bool ownerPaysTransferFee_,
546 OfferCrossing offerCrossing_,
547 bool isDefaultPath_,
548 std::array<boost::container::flat_set<Issue>, 2>& seenDirectIssues_,
549 boost::container::flat_set<Issue>& seenBookOuts_,
550 AMMContext& ammContext_,
551 std::optional<uint256> const& domainID_,
553 : view(view_)
554 , strandSrc(strandSrc_)
555 , strandDst(strandDst_)
556 , strandDeliver(strandDeliver_)
557 , limitQuality(limitQuality_)
558 , isFirst(strand_.empty())
559 , isLast(isLast_)
560 , ownerPaysTransferFee(ownerPaysTransferFee_)
561 , offerCrossing(offerCrossing_)
562 , isDefaultPath(isDefaultPath_)
563 , strandSize(strand_.size())
564 , prevStep(!strand_.empty() ? strand_.back().get() : nullptr)
565 , seenDirectIssues(seenDirectIssues_)
566 , seenBookOuts(seenBookOuts_)
567 , ammContext(ammContext_)
568 , domainID(domainID_)
569 , j(j_)
570{
571}
572
573template <class InAmt, class OutAmt>
574bool
575isDirectXrpToXrp(Strand const& strand)
576{
577 return false;
578}
579
580template <>
581bool
582isDirectXrpToXrp<XRPAmount, XRPAmount>(Strand const& strand)
583{
584 return (strand.size() == 2);
585}
586
587template bool
588isDirectXrpToXrp<XRPAmount, IOUAmount>(Strand const& strand);
589template bool
590isDirectXrpToXrp<IOUAmount, XRPAmount>(Strand const& strand);
591template bool
592isDirectXrpToXrp<IOUAmount, IOUAmount>(Strand const& strand);
593
594} // namespace xrpl
T back(T... args)
T begin(T... args)
A generic endpoint for log messages.
Definition Journal.h:40
Stream debug() const
Definition Journal.h:301
Stream trace() const
Severity stream access functions.
Definition Journal.h:295
Stream warn() const
Definition Journal.h:313
Maintains AMM info per overall payment engine execution and individual iteration.
Definition AMMContext.h:16
Floating point representation of amounts with high dynamic range.
Definition IOUAmount.h:25
mantissa_type mantissa() const noexcept
Definition IOUAmount.h:164
exponent_type exponent() const noexcept
Definition IOUAmount.h:158
A currency issued by an account.
Definition Issue.h:13
Currency currency
Definition Issue.h:15
AccountID account
Definition Issue.h:16
A view into a ledger.
Definition ReadView.h:31
auto getNodeType() const
Definition STPathSet.h:295
AccountID const & getAccountID() const
Definition STPathSet.h:333
bool isOffer() const
Definition STPathSet.h:301
Currency const & getCurrency() const
Definition STPathSet.h:339
AccountID const & getIssuerID() const
Definition STPathSet.h:345
bool isAccount() const
Definition STPathSet.h:307
std::vector< STPath >::size_type size() const
Definition STPathSet.h:474
bool empty() const
Definition STPathSet.h:480
std::vector< STPathElement >::size_type size() const
Definition STPathSet.h:370
bool empty() const
Definition STPathSet.h:376
A step in a payment path.
Definition Steps.h:65
T emplace_back(T... args)
T emplace(T... args)
T empty(T... args)
T end(T... args)
T find_if(T... args)
T front(T... args)
T is_same_v
T make_pair(T... args)
T max(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
bool isConsistent(Book const &book)
Definition Book.cpp:10
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:97
bool isDirectXrpToXrp(Strand const &strand)
Definition PaySteps.cpp:575
std::pair< TER, std::vector< Strand > > toStrands(ReadView const &sb, AccountID const &src, AccountID const &dst, Issue const &deliver, std::optional< Quality > const &limitQuality, std::optional< Issue > const &sendMax, STPathSet const &paths, bool addDefaultPath, bool ownerPaysTransferFee, OfferCrossing offerCrossing, AMMContext &ammContext, std::optional< uint256 > const &domainID, beast::Journal j)
Create a Strand for each specified path (including the default path, if indicated)
Definition PaySteps.cpp:423
bool isXRP(AccountID const &c)
Definition AccountID.h:70
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
bool isDirectXrpToXrp< XRPAmount, XRPAmount >(Strand const &strand)
Definition PaySteps.cpp:582
std::pair< TER, Strand > toStrand(ReadView const &sb, AccountID const &src, AccountID const &dst, Issue const &deliver, std::optional< Quality > const &limitQuality, std::optional< Issue > const &sendMaxIssue, STPath const &path, bool ownerPaysTransferFee, OfferCrossing offerCrossing, AMMContext &ammContext, std::optional< uint256 > const &domainID, beast::Journal j)
Create a Strand for the specified path.
Definition PaySteps.cpp:111
std::pair< TER, std::unique_ptr< Step > > make_XRPEndpointStep(StrandContext const &ctx, AccountID const &acc)
std::pair< TER, std::unique_ptr< Step > > make_BookStepXI(StrandContext const &ctx, Issue const &out)
@ tefEXCEPTION
Definition TER.h:152
template bool isDirectXrpToXrp< IOUAmount, IOUAmount >(Strand const &strand)
Currency const & xrpCurrency()
XRP currency.
Definition UintTypes.cpp:98
static bool isDefaultPath(STPath const &path)
static std::pair< TER, std::unique_ptr< Step > > toStep(StrandContext const &ctx, STPathElement const *e1, STPathElement const *e2, Issue const &curIssue)
Definition PaySteps.cpp:50
std::pair< TER, std::unique_ptr< Step > > make_DirectStepI(StrandContext const &ctx, AccountID const &src, AccountID const &dst, Currency const &c)
std::pair< TER, std::unique_ptr< Step > > make_BookStepIX(StrandContext const &ctx, Issue const &in)
bool checkNear(IOUAmount const &expected, IOUAmount const &actual)
Definition PaySteps.cpp:14
constexpr Number abs(Number x) noexcept
Definition Number.h:719
AccountID const & noAccount()
A placeholder for empty accounts.
@ temBAD_PATH
Definition TER.h:76
@ temRIPPLE_EMPTY
Definition TER.h:93
template bool isDirectXrpToXrp< XRPAmount, IOUAmount >(Strand const &strand)
bool isTesSuccess(TER x) noexcept
Definition TER.h:651
std::pair< TER, std::unique_ptr< Step > > make_BookStepII(StrandContext const &ctx, Issue const &in, Issue const &out)
AccountID const & xrpAccount()
Compute AccountID from public key.
static bool isXRPAccount(STPathElement const &pe)
Definition PaySteps.cpp:42
OfferCrossing
Definition Steps.h:24
template bool isDirectXrpToXrp< IOUAmount, XRPAmount >(Strand const &strand)
bool isTemMalformed(TER x) noexcept
Definition TER.h:633
@ tesSUCCESS
Definition TER.h:225
T push_back(T... args)
T rbegin(T... args)
T rend(T... args)
T reserve(T... args)
T size(T... args)
Context needed to build Strand Steps and for error checking.
Definition Steps.h:504
beast::Journal const j
Definition Steps.h:532
StrandContext(ReadView const &view_, std::vector< std::unique_ptr< Step > > const &strand_, AccountID const &strandSrc_, AccountID const &strandDst_, Issue const &strandDeliver_, std::optional< Quality > const &limitQuality_, bool isLast_, bool ownerPaysTransferFee_, OfferCrossing offerCrossing_, bool isDefaultPath_, std::array< boost::container::flat_set< Issue >, 2 > &seenDirectIssues_, boost::container::flat_set< Issue > &seenBookOuts_, AMMContext &ammContext_, std::optional< uint256 > const &domainID, beast::Journal j_)
StrandContext constructor.
Definition PaySteps.cpp:535
bool const isFirst
true if Step is first in Strand
Definition Steps.h:510
bool const isLast
true if Step is last in Strand
Definition Steps.h:511