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