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