rippled
Loading...
Searching...
No Matches
CashCheck.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2017 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/ledger/Ledger.h>
21#include <xrpld/app/paths/Flow.h>
22#include <xrpld/app/tx/detail/CashCheck.h>
23
24#include <xrpl/basics/Log.h>
25#include <xrpl/basics/scope.h>
26#include <xrpl/protocol/Feature.h>
27#include <xrpl/protocol/Indexes.h>
28#include <xrpl/protocol/TER.h>
29#include <xrpl/protocol/TxFlags.h>
30
31#include <algorithm>
32
33namespace ripple {
34
37{
38 // Exactly one of Amount or DeliverMin must be present.
39 auto const optAmount = ctx.tx[~sfAmount];
40 auto const optDeliverMin = ctx.tx[~sfDeliverMin];
41
42 if (static_cast<bool>(optAmount) == static_cast<bool>(optDeliverMin))
43 {
44 JLOG(ctx.j.warn())
45 << "Malformed transaction: "
46 "does not specify exactly one of Amount and DeliverMin.";
47 return temMALFORMED;
48 }
49
50 // Make sure the amount is valid.
51 STAmount const value{optAmount ? *optAmount : *optDeliverMin};
52 if (!isLegalNet(value) || value.signum() <= 0)
53 {
54 JLOG(ctx.j.warn()) << "Malformed transaction: bad amount: "
55 << value.getFullText();
56 return temBAD_AMOUNT;
57 }
58
59 if (badCurrency() == value.getCurrency())
60 {
61 JLOG(ctx.j.warn()) << "Malformed transaction: Bad currency.";
62 return temBAD_CURRENCY;
63 }
64
65 return tesSUCCESS;
66}
67
68TER
70{
71 auto const sleCheck = ctx.view.read(keylet::check(ctx.tx[sfCheckID]));
72 if (!sleCheck)
73 {
74 JLOG(ctx.j.warn()) << "Check does not exist.";
75 return tecNO_ENTRY;
76 }
77
78 // Only cash a check with this account as the destination.
79 AccountID const dstId = sleCheck->at(sfDestination);
80 if (ctx.tx[sfAccount] != dstId)
81 {
82 JLOG(ctx.j.warn()) << "Cashing a check with wrong Destination.";
83 return tecNO_PERMISSION;
84 }
85 AccountID const srcId = sleCheck->at(sfAccount);
86 if (srcId == dstId)
87 {
88 // They wrote a check to themselves. This should be caught when
89 // the check is created, but better late than never.
90 // LCOV_EXCL_START
91 JLOG(ctx.j.error()) << "Malformed transaction: Cashing check to self.";
92 return tecINTERNAL;
93 // LCOV_EXCL_STOP
94 }
95 {
96 auto const sleSrc = ctx.view.read(keylet::account(srcId));
97 auto const sleDst = ctx.view.read(keylet::account(dstId));
98 if (!sleSrc || !sleDst)
99 {
100 // If the check exists this should never occur.
101 JLOG(ctx.j.warn())
102 << "Malformed transaction: source or destination not in ledger";
103 return tecNO_ENTRY;
104 }
105
106 if ((sleDst->getFlags() & lsfRequireDestTag) &&
107 !sleCheck->isFieldPresent(sfDestinationTag))
108 {
109 // The tag is basically account-specific information we don't
110 // understand, but we can require someone to fill it in.
111 JLOG(ctx.j.warn())
112 << "Malformed transaction: DestinationTag required in check.";
113 return tecDST_TAG_NEEDED;
114 }
115 }
116
117 if (hasExpired(ctx.view, sleCheck->at(~sfExpiration)))
118 {
119 JLOG(ctx.j.warn()) << "Cashing a check that has already expired.";
120 return tecEXPIRED;
121 }
122
123 {
124 // Preflight verified exactly one of Amount or DeliverMin is present.
125 // Make sure the requested amount is reasonable.
126 STAmount const value{[](STTx const& tx) {
127 auto const optAmount = tx[~sfAmount];
128 return optAmount ? *optAmount : tx[sfDeliverMin];
129 }(ctx.tx)};
130
131 STAmount const sendMax = sleCheck->at(sfSendMax);
132 Currency const currency{value.getCurrency()};
133 if (currency != sendMax.getCurrency())
134 {
135 JLOG(ctx.j.warn()) << "Check cash does not match check currency.";
136 return temMALFORMED;
137 }
138 AccountID const issuerId{value.getIssuer()};
139 if (issuerId != sendMax.getIssuer())
140 {
141 JLOG(ctx.j.warn()) << "Check cash does not match check issuer.";
142 return temMALFORMED;
143 }
144 if (value > sendMax)
145 {
146 JLOG(ctx.j.warn()) << "Check cashed for more than check sendMax.";
147 return tecPATH_PARTIAL;
148 }
149
150 // Make sure the check owner holds at least value. If they have
151 // less than value the check cannot be cashed.
152 {
153 STAmount availableFunds{accountFunds(
154 ctx.view,
155 sleCheck->at(sfAccount),
156 value,
158 ctx.j)};
159
160 // Note that src will have one reserve's worth of additional XRP
161 // once the check is cashed, since the check's reserve will no
162 // longer be required. So, if we're dealing in XRP, we add one
163 // reserve's worth to the available funds.
164 if (value.native())
165 availableFunds += XRPAmount{ctx.view.fees().increment};
166
167 if (value > availableFunds)
168 {
169 JLOG(ctx.j.warn())
170 << "Check cashed for more than owner's balance.";
171 return tecPATH_PARTIAL;
172 }
173 }
174
175 // An issuer can always accept their own currency.
176 if (!value.native() && (value.getIssuer() != dstId))
177 {
178 auto const sleTrustLine =
179 ctx.view.read(keylet::line(dstId, issuerId, currency));
180
181 if (!sleTrustLine &&
182 !ctx.view.rules().enabled(featureCheckCashMakesTrustLine))
183 {
184 JLOG(ctx.j.warn())
185 << "Cannot cash check for IOU without trustline.";
186 return tecNO_LINE;
187 }
188
189 auto const sleIssuer = ctx.view.read(keylet::account(issuerId));
190 if (!sleIssuer)
191 {
192 JLOG(ctx.j.warn())
193 << "Can't receive IOUs from non-existent issuer: "
194 << to_string(issuerId);
195 return tecNO_ISSUER;
196 }
197
198 if (sleIssuer->at(sfFlags) & lsfRequireAuth)
199 {
200 if (!sleTrustLine)
201 {
202 // We can only create a trust line if the issuer does not
203 // have lsfRequireAuth set.
204 return tecNO_AUTH;
205 }
206
207 // Entries have a canonical representation, determined by a
208 // lexicographical "greater than" comparison employing strict
209 // weak ordering. Determine which entry we need to access.
210 bool const canonical_gt(dstId > issuerId);
211
212 bool const is_authorized(
213 sleTrustLine->at(sfFlags) &
214 (canonical_gt ? lsfLowAuth : lsfHighAuth));
215
216 if (!is_authorized)
217 {
218 JLOG(ctx.j.warn())
219 << "Can't receive IOUs from issuer without auth.";
220 return tecNO_AUTH;
221 }
222 }
223
224 // The trustline from source to issuer does not need to
225 // be checked for freezing, since we already verified that the
226 // source has sufficient non-frozen funds available.
227
228 // However, the trustline from destination to issuer may not
229 // be frozen.
230 if (isFrozen(ctx.view, dstId, currency, issuerId))
231 {
232 JLOG(ctx.j.warn()) << "Cashing a check to a frozen trustline.";
233 return tecFROZEN;
234 }
235 }
236 }
237 return tesSUCCESS;
238}
239
240TER
242{
243 // Flow requires that we operate on a PaymentSandbox, rather than
244 // directly on a View.
245 PaymentSandbox psb(&ctx_.view());
246
247 auto sleCheck = psb.peek(keylet::check(ctx_.tx[sfCheckID]));
248 if (!sleCheck)
249 {
250 // LCOV_EXCL_START
251 JLOG(j_.fatal()) << "Precheck did not verify check's existence.";
253 // LCOV_EXCL_STOP
254 }
255
256 AccountID const srcId{sleCheck->getAccountID(sfAccount)};
257 if (!psb.exists(keylet::account(srcId)) ||
259 {
260 // LCOV_EXCL_START
261 JLOG(ctx_.journal.fatal())
262 << "Precheck did not verify source or destination's existence.";
264 // LCOV_EXCL_STOP
265 }
266
267 // Preclaim already checked that source has at least the requested
268 // funds.
269 //
270 // Therefore, if this is a check written to self, (and it shouldn't be)
271 // we know they have sufficient funds to pay the check. Since they are
272 // taking the funds from their own pocket and putting it back in their
273 // pocket no balance will change.
274 //
275 // If it is not a check to self (as should be the case), then there's
276 // work to do...
277 auto viewJ = ctx_.app.journal("View");
278 auto const optDeliverMin = ctx_.tx[~sfDeliverMin];
279 bool const doFix1623{psb.rules().enabled(fix1623)};
280
281 if (srcId != account_)
282 {
283 STAmount const sendMax = sleCheck->at(sfSendMax);
284
285 // Flow() doesn't do XRP to XRP transfers.
286 if (sendMax.native())
287 {
288 // Here we need to calculate the amount of XRP src can send.
289 // The amount they have available is their balance minus their
290 // reserve.
291 //
292 // Since (if we're successful) we're about to remove an entry
293 // from src's directory, we allow them to send that additional
294 // incremental reserve amount in the transfer. Hence the -1
295 // argument.
296 STAmount const srcLiquid{xrpLiquid(psb, srcId, -1, viewJ)};
297
298 // Now, how much do they need in order to be successful?
299 STAmount const xrpDeliver{
300 optDeliverMin
301 ? std::max(*optDeliverMin, std::min(sendMax, srcLiquid))
302 : ctx_.tx.getFieldAmount(sfAmount)};
303
304 if (srcLiquid < xrpDeliver)
305 {
306 // Vote no. However the transaction might succeed if applied
307 // in a different order.
308 JLOG(j_.trace()) << "Cash Check: Insufficient XRP: "
309 << srcLiquid.getFullText() << " < "
310 << xrpDeliver.getFullText();
311 return tecUNFUNDED_PAYMENT;
312 }
313
314 if (optDeliverMin && doFix1623)
315 // Set the DeliveredAmount metadata.
316 ctx_.deliver(xrpDeliver);
317
318 // The source account has enough XRP so make the ledger change.
319 if (TER const ter{
320 transferXRP(psb, srcId, account_, xrpDeliver, viewJ)};
321 ter != tesSUCCESS)
322 {
323 // The transfer failed. Return the error code.
324 return ter;
325 }
326 }
327 else
328 {
329 // Note that for DeliverMin we don't know exactly how much
330 // currency we want flow to deliver. We can't ask for the
331 // maximum possible currency because there might be a gateway
332 // transfer rate to account for. Since the transfer rate cannot
333 // exceed 200%, we use 1/2 maxValue as our limit.
334 STAmount const flowDeliver{
335 optDeliverMin ? STAmount(
336 optDeliverMin->issue(),
339 : ctx_.tx.getFieldAmount(sfAmount)};
340
341 // If a trust line does not exist yet create one.
342 Issue const& trustLineIssue = flowDeliver.issue();
343 AccountID const issuer = flowDeliver.getIssuer();
344 AccountID const truster = issuer == account_ ? srcId : account_;
345 Keylet const trustLineKey = keylet::line(truster, trustLineIssue);
346 bool const destLow = issuer > account_;
347
348 bool const checkCashMakesTrustLine =
349 psb.rules().enabled(featureCheckCashMakesTrustLine);
350
351 if (checkCashMakesTrustLine && !psb.exists(trustLineKey))
352 {
353 // 1. Can the check casher meet the reserve for the trust line?
354 // 2. Create trust line between destination (this) account
355 // and the issuer.
356 // 3. Apply correct noRipple settings on trust line. Use...
357 // a. this (destination) account and
358 // b. issuing account (not sending account).
359
360 auto const sleDst = psb.peek(keylet::account(account_));
361
362 // Can the account cover the trust line's reserve?
363 if (std::uint32_t const ownerCount = {sleDst->at(sfOwnerCount)};
364 mPriorBalance < psb.fees().accountReserve(ownerCount + 1))
365 {
366 JLOG(j_.trace()) << "Trust line does not exist. "
367 "Insufficent reserve to create line.";
368
370 }
371
372 Currency const currency = flowDeliver.getCurrency();
373 STAmount initialBalance(flowDeliver.issue());
374 initialBalance.setIssuer(noAccount());
375
376 // clang-format off
377 if (TER const ter = trustCreate(
378 psb, // payment sandbox
379 destLow, // is dest low?
380 issuer, // source
381 account_, // destination
382 trustLineKey.key, // ledger index
383 sleDst, // Account to add to
384 false, // authorize account
385 (sleDst->getFlags() & lsfDefaultRipple) == 0,
386 false, // freeze trust line
387 false, // deep freeze trust line
388 initialBalance, // zero initial balance
389 Issue(currency, account_), // limit of zero
390 0, // quality in
391 0, // quality out
392 viewJ); // journal
393 !isTesSuccess(ter))
394 {
395 return ter;
396 }
397 // clang-format on
398
399 psb.update(sleDst);
400
401 // Note that we _don't_ need to be careful about destroying
402 // the trust line if the check cashing fails. The transaction
403 // machinery will automatically clean it up.
404 }
405
406 // Since the destination is signing the check, they clearly want
407 // the funds even if their new total funds would exceed the limit
408 // on their trust line. So we tweak the trust line limits before
409 // calling flow and then restore the trust line limits afterwards.
410 auto const sleTrustLine = psb.peek(trustLineKey);
411 if (!sleTrustLine)
412 return tecNO_LINE;
413
414 SF_AMOUNT const& tweakedLimit = destLow ? sfLowLimit : sfHighLimit;
415 STAmount const savedLimit = sleTrustLine->at(tweakedLimit);
416
417 // Make sure the tweaked limits are restored when we leave scope.
418 scope_exit fixup(
419 [&psb, &trustLineKey, &tweakedLimit, &savedLimit]() {
420 if (auto const sleTrustLine = psb.peek(trustLineKey))
421 sleTrustLine->at(tweakedLimit) = savedLimit;
422 });
423
424 if (checkCashMakesTrustLine)
425 {
426 // Set the trust line limit to the highest possible value
427 // while flow runs.
428 STAmount const bigAmount(
430 sleTrustLine->at(tweakedLimit) = bigAmount;
431 }
432
433 // Let flow() do the heavy lifting on a check for an IOU.
434 auto const result = flow(
435 psb,
436 flowDeliver,
437 srcId,
438 account_,
439 STPathSet{},
440 true, // default path
441 static_cast<bool>(optDeliverMin), // partial payment
442 true, // owner pays transfer fee
445 sleCheck->getFieldAmount(sfSendMax),
446 std::nullopt, // check does not support domain
447 viewJ);
448
449 if (result.result() != tesSUCCESS)
450 {
451 JLOG(ctx_.journal.warn()) << "flow failed when cashing check.";
452 return result.result();
453 }
454
455 // Make sure that deliverMin was satisfied.
456 if (optDeliverMin)
457 {
458 if (result.actualAmountOut < *optDeliverMin)
459 {
460 JLOG(ctx_.journal.warn())
461 << "flow did not produce DeliverMin.";
462 return tecPATH_PARTIAL;
463 }
464 if (doFix1623 && !checkCashMakesTrustLine)
465 // Set the delivered_amount metadata.
466 ctx_.deliver(result.actualAmountOut);
467 }
468
469 // Set the delivered amount metadata in all cases, not just
470 // for DeliverMin.
471 if (checkCashMakesTrustLine)
472 ctx_.deliver(result.actualAmountOut);
473
474 sleCheck = psb.peek(keylet::check(ctx_.tx[sfCheckID]));
475 }
476 }
477
478 // Check was cashed. If not a self send (and it shouldn't be), remove
479 // check link from destination directory.
480 if (srcId != account_ &&
481 !psb.dirRemove(
483 sleCheck->at(sfDestinationNode),
484 sleCheck->key(),
485 true))
486 {
487 // LCOV_EXCL_START
488 JLOG(j_.fatal()) << "Unable to delete check from destination.";
489 return tefBAD_LEDGER;
490 // LCOV_EXCL_STOP
491 }
492
493 // Remove check from check owner's directory.
494 if (!psb.dirRemove(
495 keylet::ownerDir(srcId),
496 sleCheck->at(sfOwnerNode),
497 sleCheck->key(),
498 true))
499 {
500 // LCOV_EXCL_START
501 JLOG(j_.fatal()) << "Unable to delete check from owner.";
502 return tefBAD_LEDGER;
503 // LCOV_EXCL_STOP
504 }
505
506 // If we succeeded, update the check owner's reserve.
507 adjustOwnerCount(psb, psb.peek(keylet::account(srcId)), -1, viewJ);
508
509 // Remove check from ledger.
510 psb.erase(sleCheck);
511
512 psb.apply(ctx_.rawView());
513 return tesSUCCESS;
514}
515
516} // namespace ripple
Stream fatal() const
Definition Journal.h:352
Stream error() const
Definition Journal.h:346
Stream trace() const
Severity stream access functions.
Definition Journal.h:322
Stream warn() const
Definition Journal.h:340
virtual beast::Journal journal(std::string const &name)=0
ApplyView & view()
Application & app
beast::Journal const journal
void deliver(STAmount const &amount)
Sets the DeliveredAmount field in the metadata.
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
static TER preclaim(PreclaimContext const &ctx)
Definition CashCheck.cpp:69
TER doApply() override
static NotTEC preflight(PreflightContext const &ctx)
Definition CashCheck.cpp:36
A currency issued by an account.
Definition Issue.h:33
A wrapper which makes credits unavailable to balances.
void apply(RawView &to)
Apply changes to base view.
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:130
void setIssuer(AccountID const &uIssuer)
Definition STAmount.h:588
static int const cMaxOffset
Definition STAmount.h:66
Currency const & getCurrency() const
Definition STAmount.h:502
static std::uint64_t const cMaxValue
Definition STAmount.h:70
AccountID const & getIssuer() const
Definition STAmount.h:508
bool native() const noexcept
Definition STAmount.h:458
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:671
AccountID const account_
Definition Transactor.h:147
beast::Journal const j_
Definition Transactor.h:145
XRPAmount mPriorBalance
Definition Transactor.h:148
ApplyContext & ctx_
Definition Transactor.h:143
Fees const & fees() const override
Returns the fees for the base ledger.
void erase(std::shared_ptr< SLE > const &sle) override
Remove a peeked SLE.
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
bool exists(Keylet const &k) const override
Determine if a state item exists.
Rules const & rules() const override
Returns the tx processing rules.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
T is_same_v
T max(T... args)
T min(T... args)
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:244
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:374
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:336
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
AccountID const & noAccount()
A placeholder for empty accounts.
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:554
@ fhZERO_IF_FROZEN
Definition View.h:77
bool isLegalNet(STAmount const &value)
Definition STAmount.h:600
@ lsfRequireDestTag
@ lsfDefaultRipple
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1032
TER transferXRP(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &amount, beast::Journal j)
Definition View.cpp:2434
StrandResult< TInAmt, TOutAmt > flow(PaymentSandbox const &baseView, Strand const &strand, std::optional< TInAmt > const &maxIn, TOutAmt const &out, beast::Journal j)
Request out amount from a strand.
Definition StrandFlow.h:105
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:247
@ tefBAD_LEDGER
Definition TER.h:170
@ no
Definition Steps.h:45
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:173
@ tecNO_ENTRY
Definition TER.h:306
@ tecNO_LINE_INSUF_RESERVE
Definition TER.h:292
@ tecNO_ISSUER
Definition TER.h:299
@ tecFROZEN
Definition TER.h:303
@ tecINTERNAL
Definition TER.h:310
@ tecNO_PERMISSION
Definition TER.h:305
@ tecDST_TAG_NEEDED
Definition TER.h:309
@ tecPATH_PARTIAL
Definition TER.h:282
@ tecNO_LINE
Definition TER.h:301
@ tecUNFUNDED_PAYMENT
Definition TER.h:285
@ tecFAILED_PROCESSING
Definition TER.h:286
@ tecEXPIRED
Definition TER.h:314
@ tecNO_AUTH
Definition TER.h:300
@ tesSUCCESS
Definition TER.h:244
bool isTesSuccess(TER x) noexcept
Definition TER.h:674
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
TER trustCreate(ApplyView &view, bool const bSrcHigh, AccountID const &uSrcAccountID, AccountID const &uDstAccountID, uint256 const &uIndex, SLE::ref sleAccount, bool const bAuth, bool const bNoRipple, bool const bFreeze, bool bDeepFreeze, STAmount const &saBalance, STAmount const &saLimit, std::uint32_t uSrcQualityIn, std::uint32_t uSrcQualityOut, beast::Journal j)
Create a trust line.
Definition View.cpp:1392
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:605
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:618
@ temBAD_AMOUNT
Definition TER.h:89
@ temBAD_CURRENCY
Definition TER.h:90
@ temMALFORMED
Definition TER.h:87
XRPAmount increment
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:39
uint256 key
Definition Keylet.h:40
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:80
ReadView const & view
Definition Transactor.h:83
beast::Journal const j
Definition Transactor.h:88
State information when preflighting a tx.
Definition Transactor.h:35
beast::Journal const j
Definition Transactor.h:42
A field with a type known at compile time.
Definition SField.h:320