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