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