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