rippled
Loading...
Searching...
No Matches
NFTokenAcceptOffer.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2021 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/tx/detail/NFTokenAcceptOffer.h>
21#include <xrpld/app/tx/detail/NFTokenUtils.h>
22
23#include <xrpl/ledger/View.h>
24#include <xrpl/protocol/Feature.h>
25#include <xrpl/protocol/Rate.h>
26#include <xrpl/protocol/TxFlags.h>
27
28namespace ripple {
29
35
38{
39 auto const bo = ctx.tx[~sfNFTokenBuyOffer];
40 auto const so = ctx.tx[~sfNFTokenSellOffer];
41
42 // At least one of these MUST be specified
43 if (!bo && !so)
44 return temMALFORMED;
45
46 // The `BrokerFee` field must not be present in direct mode but may be
47 // present and greater than zero in brokered mode.
48 if (auto const bf = ctx.tx[~sfNFTokenBrokerFee])
49 {
50 if (!bo || !so)
51 return temMALFORMED;
52
53 if (*bf <= beast::zero)
54 return temMALFORMED;
55 }
56
57 return tesSUCCESS;
58}
59
60TER
62{
63 auto const checkOffer = [&ctx](std::optional<uint256> id)
65 if (id)
66 {
67 if (id->isZero())
68 return {nullptr, tecOBJECT_NOT_FOUND};
69
70 auto offerSLE = ctx.view.read(keylet::nftoffer(*id));
71
72 if (!offerSLE)
73 return {nullptr, tecOBJECT_NOT_FOUND};
74
75 if (hasExpired(ctx.view, (*offerSLE)[~sfExpiration]))
76 return {nullptr, tecEXPIRED};
77
78 // The initial implementation had a bug that allowed a negative
79 // amount. The fixNFTokenNegOffer amendment fixes that.
80 if ((*offerSLE)[sfAmount].negative() &&
81 ctx.view.rules().enabled(fixNFTokenNegOffer))
82 return {nullptr, temBAD_OFFER};
83
84 return {std::move(offerSLE), tesSUCCESS};
85 }
86 return {nullptr, tesSUCCESS};
87 };
88
89 auto const [bo, err1] = checkOffer(ctx.tx[~sfNFTokenBuyOffer]);
90 if (!isTesSuccess(err1))
91 return err1;
92 auto const [so, err2] = checkOffer(ctx.tx[~sfNFTokenSellOffer]);
93 if (!isTesSuccess(err2))
94 return err2;
95
96 if (bo && so)
97 {
98 // Brokered mode:
99 // The two offers being brokered must be for the same token:
100 if ((*bo)[sfNFTokenID] != (*so)[sfNFTokenID])
102
103 // The two offers being brokered must be for the same asset:
104 if ((*bo)[sfAmount].issue() != (*so)[sfAmount].issue())
106
107 // The two offers may not form a loop. A broker may not sell the
108 // token to the current owner of the token.
109 if (ctx.view.rules().enabled(fixNonFungibleTokensV1_2) &&
110 ((*bo)[sfOwner] == (*so)[sfOwner]))
112
113 // Ensure that the buyer is willing to pay at least as much as the
114 // seller is requesting:
115 if ((*so)[sfAmount] > (*bo)[sfAmount])
117
118 // If the buyer specified a destination
119 if (auto const dest = bo->at(~sfDestination))
120 {
121 // Before this fix the destination could be either the seller or
122 // a broker. After, it must be whoever is submitting the tx.
123 if (ctx.view.rules().enabled(fixNonFungibleTokensV1_2))
124 {
125 if (*dest != ctx.tx[sfAccount])
126 return tecNO_PERMISSION;
127 }
128 else if (*dest != so->at(sfOwner) && *dest != ctx.tx[sfAccount])
130 }
131
132 // If the seller specified a destination
133 if (auto const dest = so->at(~sfDestination))
134 {
135 // Before this fix the destination could be either the seller or
136 // a broker. After, it must be whoever is submitting the tx.
137 if (ctx.view.rules().enabled(fixNonFungibleTokensV1_2))
138 {
139 if (*dest != ctx.tx[sfAccount])
140 return tecNO_PERMISSION;
141 }
142 else if (*dest != bo->at(sfOwner) && *dest != ctx.tx[sfAccount])
144 }
145
146 // The broker can specify an amount that represents their cut; if they
147 // have, ensure that the seller will get at least as much as they want
148 // to get *after* this fee is accounted for (but before the issuer's
149 // cut, if any).
150 if (auto const brokerFee = ctx.tx[~sfNFTokenBrokerFee])
151 {
152 if (brokerFee->issue() != (*bo)[sfAmount].issue())
154
155 if (brokerFee >= (*bo)[sfAmount])
157
158 if ((*so)[sfAmount] > (*bo)[sfAmount] - *brokerFee)
160
161 // Check if broker is allowed to receive the fee with these IOUs.
162 if (!brokerFee->native() &&
163 ctx.view.rules().enabled(fixEnforceNFTokenTrustlineV2))
164 {
166 ctx.view,
167 ctx.tx[sfAccount],
168 ctx.j,
169 brokerFee->asset().get<Issue>());
170 if (res != tesSUCCESS)
171 return res;
172
174 ctx.view,
175 ctx.tx[sfAccount],
176 ctx.j,
177 brokerFee->asset().get<Issue>());
178 if (res != tesSUCCESS)
179 return res;
180 }
181 }
182 }
183
184 if (bo)
185 {
186 if (((*bo)[sfFlags] & lsfSellNFToken) == lsfSellNFToken)
188
189 // An account can't accept an offer it placed:
190 if ((*bo)[sfOwner] == ctx.tx[sfAccount])
192
193 // If not in bridged mode, the account must own the token:
194 if (!so &&
195 !nft::findToken(ctx.view, ctx.tx[sfAccount], (*bo)[sfNFTokenID]))
196 return tecNO_PERMISSION;
197
198 // If not in bridged mode...
199 if (!so)
200 {
201 // If the offer has a Destination field, the acceptor must be the
202 // Destination.
203 if (auto const dest = bo->at(~sfDestination);
204 dest.has_value() && *dest != ctx.tx[sfAccount])
205 return tecNO_PERMISSION;
206 }
207
208 // The account offering to buy must have funds:
209 //
210 // After this amendment, we allow an IOU issuer to buy an NFT with their
211 // own currency
212 auto const needed = bo->at(sfAmount);
213 if (ctx.view.rules().enabled(fixNonFungibleTokensV1_2))
214 {
215 if (accountFunds(
216 ctx.view, (*bo)[sfOwner], needed, fhZERO_IF_FROZEN, ctx.j) <
217 needed)
219 }
220 else if (
222 ctx.view,
223 (*bo)[sfOwner],
224 needed.getCurrency(),
225 needed.getIssuer(),
227 ctx.j) < needed)
229
230 // Check that the account accepting the buy offer (he's selling the NFT)
231 // is allowed to receive IOUs. Also check that this offer's creator is
232 // authorized. But we need to exclude the case when the transaction is
233 // created by the broker.
234 if (ctx.view.rules().enabled(fixEnforceNFTokenTrustlineV2) &&
235 !needed.native())
236 {
238 ctx.view, bo->at(sfOwner), ctx.j, needed.asset().get<Issue>());
239 if (res != tesSUCCESS)
240 return res;
241
242 if (!so)
243 {
245 ctx.view,
246 ctx.tx[sfAccount],
247 ctx.j,
248 needed.asset().get<Issue>());
249 if (res != tesSUCCESS)
250 return res;
251
253 ctx.view,
254 ctx.tx[sfAccount],
255 ctx.j,
256 needed.asset().get<Issue>());
257 if (res != tesSUCCESS)
258 return res;
259 }
260 }
261 }
262
263 if (so)
264 {
265 if (((*so)[sfFlags] & lsfSellNFToken) != lsfSellNFToken)
267
268 // An account can't accept an offer it placed:
269 if ((*so)[sfOwner] == ctx.tx[sfAccount])
271
272 // The seller must own the token.
273 if (!nft::findToken(ctx.view, (*so)[sfOwner], (*so)[sfNFTokenID]))
274 return tecNO_PERMISSION;
275
276 // If not in bridged mode...
277 if (!bo)
278 {
279 // If the offer has a Destination field, the acceptor must be the
280 // Destination.
281 if (auto const dest = so->at(~sfDestination);
282 dest.has_value() && *dest != ctx.tx[sfAccount])
283 return tecNO_PERMISSION;
284 }
285
286 // The account offering to buy must have funds:
287 auto const needed = so->at(sfAmount);
288 if (!ctx.view.rules().enabled(fixNonFungibleTokensV1_2))
289 {
290 if (accountHolds(
291 ctx.view,
292 ctx.tx[sfAccount],
293 needed.getCurrency(),
294 needed.getIssuer(),
296 ctx.j) < needed)
298 }
299 else if (!bo)
300 {
301 // After this amendment, we allow buyers to buy with their own
302 // issued currency.
303 //
304 // In the case of brokered mode, this check is essentially
305 // redundant, since we have already confirmed that buy offer is >
306 // than the sell offer, and that the buyer can cover the buy
307 // offer.
308 //
309 // We also _must not_ check the tx submitter in brokered
310 // mode, because then we are confirming that the broker can
311 // cover what the buyer will pay, which doesn't make sense, causes
312 // an unnecessary tec, and is also resolved with this amendment.
313 if (accountFunds(
314 ctx.view,
315 ctx.tx[sfAccount],
316 needed,
318 ctx.j) < needed)
320 }
321
322 // Make sure that we are allowed to hold what the taker will pay us.
323 if (!needed.native())
324 {
325 if (ctx.view.rules().enabled(fixEnforceNFTokenTrustlineV2))
326 {
328 ctx.view,
329 (*so)[sfOwner],
330 ctx.j,
331 needed.asset().get<Issue>());
332 if (res != tesSUCCESS)
333 return res;
334
335 if (!bo)
336 {
338 ctx.view,
339 ctx.tx[sfAccount],
340 ctx.j,
341 needed.asset().get<Issue>());
342 if (res != tesSUCCESS)
343 return res;
344 }
345 }
346
347 auto const res = nft::checkTrustlineDeepFrozen(
348 ctx.view, (*so)[sfOwner], ctx.j, needed.asset().get<Issue>());
349 if (res != tesSUCCESS)
350 return res;
351 }
352 }
353
354 // Additional checks are required in case a minter set a transfer fee for
355 // this nftoken
356 auto const& offer = bo ? bo : so;
357 if (!offer)
358 // Purely defensive, should be caught in preflight.
359 return tecINTERNAL; // LCOV_EXCL_LINE
360
361 auto const& tokenID = offer->at(sfNFTokenID);
362 auto const& amount = offer->at(sfAmount);
363 auto const nftMinter = nft::getIssuer(tokenID);
364
365 if (nft::getTransferFee(tokenID) != 0 && !amount.native())
366 {
367 // Fix a bug where the transfer of an NFToken with a transfer fee could
368 // give the NFToken issuer an undesired trust line.
369 // Issuer doesn't need a trust line to accept their own currency.
370 if (ctx.view.rules().enabled(fixEnforceNFTokenTrustline) &&
371 (nft::getFlags(tokenID) & nft::flagCreateTrustLines) == 0 &&
372 nftMinter != amount.getIssuer() &&
373 !ctx.view.read(keylet::line(nftMinter, amount.issue())))
374 return tecNO_LINE;
375
376 // Check that the issuer is allowed to receive IOUs.
377 if (ctx.view.rules().enabled(fixEnforceNFTokenTrustlineV2))
378 {
380 ctx.view, nftMinter, ctx.j, amount.asset().get<Issue>());
381 if (res != tesSUCCESS)
382 return res;
383
385 ctx.view, nftMinter, ctx.j, amount.asset().get<Issue>());
386 if (res != tesSUCCESS)
387 return res;
388 }
389 }
390
391 return tesSUCCESS;
392}
393
394TER
396 AccountID const& from,
397 AccountID const& to,
398 STAmount const& amount)
399{
400 // This should never happen, but it's easy and quick to check.
401 if (amount < beast::zero)
402 return tecINTERNAL;
403
404 auto const result = accountSend(view(), from, to, amount, j_);
405
406 // After this amendment, if any payment would cause a non-IOU-issuer to
407 // have a negative balance, or an IOU-issuer to have a positive balance in
408 // their own currency, we know that something went wrong. This was
409 // originally found in the context of IOU transfer fees. Since there are
410 // several payouts in this tx, just confirm that the end state is OK.
411 if (!view().rules().enabled(fixNonFungibleTokensV1_2))
412 return result;
413 if (result != tesSUCCESS)
414 return result;
415 if (accountFunds(view(), from, amount, fhZERO_IF_FROZEN, j_).signum() < 0)
417 if (accountFunds(view(), to, amount, fhZERO_IF_FROZEN, j_).signum() < 0)
419 return tesSUCCESS;
420}
421
422TER
424 AccountID const& buyer,
425 AccountID const& seller,
426 uint256 const& nftokenID)
427{
428 auto tokenAndPage = nft::findTokenAndPage(view(), seller, nftokenID);
429
430 if (!tokenAndPage)
431 return tecINTERNAL; // LCOV_EXCL_LINE
432
433 if (auto const ret = nft::removeToken(
434 view(), seller, nftokenID, std::move(tokenAndPage->page));
435 !isTesSuccess(ret))
436 return ret;
437
438 auto const sleBuyer = view().read(keylet::account(buyer));
439 if (!sleBuyer)
440 return tecINTERNAL; // LCOV_EXCL_LINE
441
442 std::uint32_t const buyerOwnerCountBefore =
443 sleBuyer->getFieldU32(sfOwnerCount);
444
445 auto const insertRet =
446 nft::insertToken(view(), buyer, std::move(tokenAndPage->token));
447
448 // if fixNFTokenReserve is enabled, check if the buyer has sufficient
449 // reserve to own a new object, if their OwnerCount changed.
450 //
451 // There was an issue where the buyer accepts a sell offer, the ledger
452 // didn't check if the buyer has enough reserve, meaning that buyer can get
453 // NFTs free of reserve.
454 if (view().rules().enabled(fixNFTokenReserve))
455 {
456 // To check if there is sufficient reserve, we cannot use mPriorBalance
457 // because NFT is sold for a price. So we must use the balance after
458 // the deduction of the potential offer price. A small caveat here is
459 // that the balance has already deducted the transaction fee, meaning
460 // that the reserve requirement is a few drops higher.
461 auto const buyerBalance = sleBuyer->getFieldAmount(sfBalance);
462
463 auto const buyerOwnerCountAfter = sleBuyer->getFieldU32(sfOwnerCount);
464 if (buyerOwnerCountAfter > buyerOwnerCountBefore)
465 {
466 if (auto const reserve =
467 view().fees().accountReserve(buyerOwnerCountAfter);
468 buyerBalance < reserve)
470 }
471 }
472
473 return insertRet;
474}
475
476TER
478{
479 bool const isSell = offer->isFlag(lsfSellNFToken);
480 AccountID const owner = (*offer)[sfOwner];
481 AccountID const& seller = isSell ? owner : account_;
482 AccountID const& buyer = isSell ? account_ : owner;
483
484 auto const nftokenID = (*offer)[sfNFTokenID];
485
486 if (auto amount = offer->getFieldAmount(sfAmount); amount != beast::zero)
487 {
488 // Calculate the issuer's cut from this sale, if any:
489 if (auto const fee = nft::getTransferFee(nftokenID); fee != 0)
490 {
491 auto const cut = multiply(amount, nft::transferFeeAsRate(fee));
492
493 if (auto const issuer = nft::getIssuer(nftokenID);
494 cut != beast::zero && seller != issuer && buyer != issuer)
495 {
496 if (auto const r = pay(buyer, issuer, cut); !isTesSuccess(r))
497 return r;
498 amount -= cut;
499 }
500 }
501
502 // Send the remaining funds to the seller of the NFT
503 if (auto const r = pay(buyer, seller, amount); !isTesSuccess(r))
504 return r;
505 }
506
507 // Now transfer the NFT:
508 return transferNFToken(buyer, seller, nftokenID);
509}
510
511TER
513{
514 auto const loadToken = [this](std::optional<uint256> const& id) {
516 if (id)
517 sle = view().peek(keylet::nftoffer(*id));
518 return sle;
519 };
520
521 auto bo = loadToken(ctx_.tx[~sfNFTokenBuyOffer]);
522 auto so = loadToken(ctx_.tx[~sfNFTokenSellOffer]);
523
524 if (bo && !nft::deleteTokenOffer(view(), bo))
525 {
526 // LCOV_EXCL_START
527 JLOG(j_.fatal()) << "Unable to delete buy offer '"
528 << to_string(bo->key()) << "': ignoring";
529 return tecINTERNAL;
530 // LCOV_EXCL_STOP
531 }
532
533 if (so && !nft::deleteTokenOffer(view(), so))
534 {
535 // LCOV_EXCL_START
536 JLOG(j_.fatal()) << "Unable to delete sell offer '"
537 << to_string(so->key()) << "': ignoring";
538 return tecINTERNAL;
539 // LCOV_EXCL_STOP
540 }
541
542 // Bridging two different offers
543 if (bo && so)
544 {
545 AccountID const buyer = (*bo)[sfOwner];
546 AccountID const seller = (*so)[sfOwner];
547
548 auto const nftokenID = (*so)[sfNFTokenID];
549
550 // The amount is what the buyer of the NFT pays:
551 STAmount amount = (*bo)[sfAmount];
552
553 // Three different folks may be paid. The order of operations is
554 // important.
555 //
556 // o The broker is paid the cut they requested.
557 // o The issuer's cut is calculated from what remains after the
558 // broker is paid. The issuer can take up to 50% of the remainder.
559 // o Finally, the seller gets whatever is left.
560 //
561 // It is important that the issuer's cut be calculated after the
562 // broker's portion is already removed. Calculating the issuer's
563 // cut before the broker's cut is removed can result in more money
564 // being paid out than the seller authorized. That would be bad!
565
566 // Send the broker the amount they requested.
567 if (auto const cut = ctx_.tx[~sfNFTokenBrokerFee];
568 cut && cut.value() != beast::zero)
569 {
570 if (auto const r = pay(buyer, account_, cut.value());
571 !isTesSuccess(r))
572 return r;
573
574 amount -= cut.value();
575 }
576
577 // Calculate the issuer's cut, if any.
578 if (auto const fee = nft::getTransferFee(nftokenID);
579 amount != beast::zero && fee != 0)
580 {
581 auto cut = multiply(amount, nft::transferFeeAsRate(fee));
582
583 if (auto const issuer = nft::getIssuer(nftokenID);
584 seller != issuer && buyer != issuer)
585 {
586 if (auto const r = pay(buyer, issuer, cut); !isTesSuccess(r))
587 return r;
588
589 amount -= cut;
590 }
591 }
592
593 // And send whatever remains to the seller.
594 if (amount > beast::zero)
595 {
596 if (auto const r = pay(buyer, seller, amount); !isTesSuccess(r))
597 return r;
598 }
599
600 // Now transfer the NFT:
601 return transferNFToken(buyer, seller, nftokenID);
602 }
603
604 if (bo)
605 return acceptOffer(bo);
606
607 if (so)
608 return acceptOffer(so);
609
610 return tecINTERNAL; // LCOV_EXCL_LINE
611}
612
613} // namespace ripple
Stream fatal() const
Definition Journal.h:352
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
A currency issued by an account.
Definition Issue.h:33
TER acceptOffer(std::shared_ptr< SLE > const &offer)
static TER preclaim(PreclaimContext const &ctx)
TER pay(AccountID const &from, AccountID const &to, STAmount const &amount)
TER transferNFToken(AccountID const &buyer, AccountID const &seller, uint256 const &nfTokenID)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
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
STAmount const & value() const noexcept
Definition STAmount.h:594
AccountID const account_
Definition Transactor.h:147
ApplyView & view()
Definition Transactor.h:163
beast::Journal const j_
Definition Transactor.h:145
ApplyContext & ctx_
Definition Transactor.h:143
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 nftoffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Definition Indexes.cpp:427
std::uint16_t getTransferFee(uint256 const &id)
Definition nft.h:68
std::uint16_t getFlags(uint256 const &id)
Definition nft.h:60
TER removeToken(ApplyView &view, AccountID const &owner, uint256 const &nftokenID)
Remove the token from the owner's token directory.
AccountID getIssuer(uint256 const &id)
Definition nft.h:120
TER checkTrustlineDeepFrozen(ReadView const &view, AccountID const id, beast::Journal const j, Issue const &issue)
bool deleteTokenOffer(ApplyView &view, std::shared_ptr< SLE > const &offer)
Deletes the given token offer.
std::optional< TokenAndPage > findTokenAndPage(ApplyView &view, AccountID const &owner, uint256 const &nftokenID)
TER insertToken(ApplyView &view, AccountID owner, STObject &&nft)
Insert the token in the owner's token directory.
std::optional< STObject > findToken(ReadView const &view, AccountID const &owner, uint256 const &nftokenID)
Finds the specified token in the owner's token directory.
constexpr std::uint16_t const flagCreateTrustLines
Definition nft.h:55
TER checkTrustlineAuthorized(ReadView const &view, AccountID const id, beast::Journal const j, Issue const &issue)
Rate transferFeeAsRate(std::uint16_t fee)
Given a transfer fee (in basis points) convert it to a transfer rate.
Definition Rate2.cpp:45
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
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
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:53
TER accountSend(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &saAmount, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Calls static accountSendIOU if saAmount represents Issue.
Definition View.cpp:2191
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:173
@ tecOBJECT_NOT_FOUND
Definition TER.h:326
@ tecNFTOKEN_OFFER_TYPE_MISMATCH
Definition TER.h:323
@ tecINSUFFICIENT_FUNDS
Definition TER.h:325
@ tecNFTOKEN_BUY_SELL_MISMATCH
Definition TER.h:322
@ tecINTERNAL
Definition TER.h:310
@ tecNO_PERMISSION
Definition TER.h:305
@ tecNO_LINE
Definition TER.h:301
@ tecINSUFFICIENT_PAYMENT
Definition TER.h:327
@ tecINSUFFICIENT_RESERVE
Definition TER.h:307
@ tecCANT_ACCEPT_OWN_NFTOKEN_OFFER
Definition TER.h:324
@ tecEXPIRED
Definition TER.h:314
@ tesSUCCESS
Definition TER.h:244
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition View.cpp:387
bool isTesSuccess(TER x) noexcept
Definition TER.h:674
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
constexpr std::uint32_t const tfNFTokenAcceptOfferMask
Definition TxFlags.h:238
@ temMALFORMED
Definition TER.h:87
@ temBAD_OFFER
Definition TER.h:95
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