rippled
NFTokenCreateOffer.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 <ripple/app/tx/impl/NFTokenCreateOffer.h>
21 #include <ripple/app/tx/impl/details/NFTokenUtils.h>
22 #include <ripple/ledger/View.h>
23 #include <ripple/protocol/Feature.h>
24 #include <ripple/protocol/TxFlags.h>
25 #include <ripple/protocol/st.h>
26 #include <boost/endian/conversion.hpp>
27 
28 namespace ripple {
29 
30 NotTEC
32 {
34  return temDISABLED;
35 
36  if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
37  return ret;
38 
39  auto const txFlags = ctx.tx.getFlags();
40  bool const isSellOffer = txFlags & tfSellNFToken;
41 
42  if (txFlags & tfNFTokenCreateOfferMask)
43  return temINVALID_FLAG;
44 
45  auto const account = ctx.tx[sfAccount];
46  auto const nftFlags = nft::getFlags(ctx.tx[sfNFTokenID]);
47 
48  {
49  STAmount const amount = ctx.tx[sfAmount];
50 
51  if (amount.negative() && ctx.rules.enabled(fixNFTokenNegOffer))
52  // An offer for a negative amount makes no sense.
53  return temBAD_AMOUNT;
54 
55  if (!isXRP(amount))
56  {
57  if (nftFlags & nft::flagOnlyXRP)
58  return temBAD_AMOUNT;
59 
60  if (!amount)
61  return temBAD_AMOUNT;
62  }
63 
64  // If this is an offer to buy, you must offer something; if it's an
65  // offer to sell, you can ask for nothing.
66  if (!isSellOffer && !amount)
67  return temBAD_AMOUNT;
68  }
69 
70  if (auto exp = ctx.tx[~sfExpiration]; exp == 0)
71  return temBAD_EXPIRATION;
72 
73  auto const owner = ctx.tx[~sfOwner];
74 
75  // The 'Owner' field must be present when offering to buy, but can't
76  // be present when selling (it's implicit):
77  if (owner.has_value() == isSellOffer)
78  return temMALFORMED;
79 
80  if (owner && owner == account)
81  return temMALFORMED;
82 
83  if (auto dest = ctx.tx[~sfDestination])
84  {
85  // Some folks think it makes sense for a buy offer to specify a
86  // specific broker using the Destination field. This change doesn't
87  // deserve it's own amendment, so we're piggy-backing on
88  // fixNFTokenNegOffer.
89  //
90  // Prior to fixNFTokenNegOffer any use of the Destination field on
91  // a buy offer was malformed.
92  if (!isSellOffer && !ctx.rules.enabled(fixNFTokenNegOffer))
93  return temMALFORMED;
94 
95  // The destination can't be the account executing the transaction.
96  if (dest == account)
97  return temMALFORMED;
98  }
99 
100  return preflight2(ctx);
101 }
102 
103 TER
105 {
106  if (hasExpired(ctx.view, ctx.tx[~sfExpiration]))
107  return tecEXPIRED;
108 
109  auto const nftokenID = ctx.tx[sfNFTokenID];
110  bool const isSellOffer = ctx.tx.isFlag(tfSellNFToken);
111 
112  if (!nft::findToken(
113  ctx.view, ctx.tx[isSellOffer ? sfAccount : sfOwner], nftokenID))
114  return tecNO_ENTRY;
115 
116  auto const nftFlags = nft::getFlags(nftokenID);
117  auto const issuer = nft::getIssuer(nftokenID);
118  auto const amount = ctx.tx[sfAmount];
119 
120  if (!(nftFlags & nft::flagCreateTrustLines) && !amount.native() &&
121  nft::getTransferFee(nftokenID))
122  {
123  if (!ctx.view.exists(keylet::account(issuer)))
124  return tecNO_ISSUER;
125 
126  if (!ctx.view.exists(keylet::line(issuer, amount.issue())))
127  return tecNO_LINE;
128 
129  if (isFrozen(
130  ctx.view, issuer, amount.getCurrency(), amount.getIssuer()))
131  return tecFROZEN;
132  }
133 
134  if (issuer != ctx.tx[sfAccount] && !(nftFlags & nft::flagTransferable))
135  {
136  auto const root = ctx.view.read(keylet::account(issuer));
137  assert(root);
138 
139  if (auto minter = (*root)[~sfNFTokenMinter];
140  minter != ctx.tx[sfAccount])
142  }
143 
144  if (isFrozen(
145  ctx.view,
146  ctx.tx[sfAccount],
147  amount.getCurrency(),
148  amount.getIssuer()))
149  return tecFROZEN;
150 
151  // If this is an offer to buy the token, the account must have the
152  // needed funds at hand; but note that funds aren't reserved and the
153  // offer may later become unfunded.
154  if (!isSellOffer)
155  {
156  // After this amendment, we allow an IOU issuer to make a buy offer
157  // using their own currency.
159  {
160  if (accountFunds(
161  ctx.view,
162  ctx.tx[sfAccount],
163  amount,
164  FreezeHandling::fhZERO_IF_FROZEN,
165  ctx.j)
166  .signum() <= 0)
167  return tecUNFUNDED_OFFER;
168  }
169  else if (
170  accountHolds(
171  ctx.view,
172  ctx.tx[sfAccount],
173  amount.getCurrency(),
174  amount.getIssuer(),
175  FreezeHandling::fhZERO_IF_FROZEN,
176  ctx.j)
177  .signum() <= 0)
178  return tecUNFUNDED_OFFER;
179  }
180 
181  if (auto const destination = ctx.tx[~sfDestination])
182  {
183  // If a destination is specified, the destination must already be in
184  // the ledger.
185  auto const sleDst = ctx.view.read(keylet::account(*destination));
186 
187  if (!sleDst)
188  return tecNO_DST;
189 
190  // check if the destination has disallowed incoming offers
192  {
193  // flag cannot be set unless amendment is enabled but
194  // out of an abundance of caution check anyway
195 
196  if (sleDst->getFlags() & lsfDisallowIncomingNFTokenOffer)
197  return tecNO_PERMISSION;
198  }
199  }
200 
201  if (auto const owner = ctx.tx[~sfOwner])
202  {
203  // Check if the owner (buy offer) has disallowed incoming offers
205  {
206  auto const sleOwner = ctx.view.read(keylet::account(*owner));
207 
208  // defensively check
209  // it should not be possible to specify owner that doesn't exist
210  if (!sleOwner)
211  return tecNO_TARGET;
212 
213  if (sleOwner->getFlags() & lsfDisallowIncomingNFTokenOffer)
214  return tecNO_PERMISSION;
215  }
216  }
217 
218  return tesSUCCESS;
219 }
220 
221 TER
223 {
224  if (auto const acct = view().read(keylet::account(ctx_.tx[sfAccount]));
225  mPriorBalance < view().fees().accountReserve((*acct)[sfOwnerCount] + 1))
227 
228  auto const nftokenID = ctx_.tx[sfNFTokenID];
229 
230  auto const offerID =
232 
233  // Create the offer:
234  {
235  // Token offers are always added to the owner's owner directory:
236  auto const ownerNode = view().dirInsert(
238 
239  if (!ownerNode)
240  return tecDIR_FULL;
241 
242  bool const isSellOffer = ctx_.tx.isFlag(tfSellNFToken);
243 
244  // Token offers are also added to the token's buy or sell offer
245  // directory
246  auto const offerNode = view().dirInsert(
247  isSellOffer ? keylet::nft_sells(nftokenID)
248  : keylet::nft_buys(nftokenID),
249  offerID,
250  [&nftokenID, isSellOffer](std::shared_ptr<SLE> const& sle) {
251  (*sle)[sfFlags] =
253  (*sle)[sfNFTokenID] = nftokenID;
254  });
255 
256  if (!offerNode)
257  return tecDIR_FULL;
258 
259  std::uint32_t sleFlags = 0;
260 
261  if (isSellOffer)
262  sleFlags |= lsfSellNFToken;
263 
264  auto offer = std::make_shared<SLE>(offerID);
265  (*offer)[sfOwner] = account_;
266  (*offer)[sfNFTokenID] = nftokenID;
267  (*offer)[sfAmount] = ctx_.tx[sfAmount];
268  (*offer)[sfFlags] = sleFlags;
269  (*offer)[sfOwnerNode] = *ownerNode;
270  (*offer)[sfNFTokenOfferNode] = *offerNode;
271 
272  if (auto const expiration = ctx_.tx[~sfExpiration])
273  (*offer)[sfExpiration] = *expiration;
274 
275  if (auto const destination = ctx_.tx[~sfDestination])
276  (*offer)[sfDestination] = *destination;
277 
278  view().insert(offer);
279  }
280 
281  // Update owner count.
283 
284  return tesSUCCESS;
285 }
286 
287 } // namespace ripple
ripple::tecUNFUNDED_OFFER
@ tecUNFUNDED_OFFER
Definition: TER.h:264
ripple::keylet::ownerDir
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:312
ripple::sfOwnerCount
const SF_UINT32 sfOwnerCount
ripple::tecFROZEN
@ tecFROZEN
Definition: TER.h:283
ripple::preflight2
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:133
ripple::fixNFTokenNegOffer
const uint256 fixNFTokenNegOffer
ripple::tecNO_TARGET
@ tecNO_TARGET
Definition: TER.h:284
ripple::nft::getFlags
std::uint16_t getFlags(uint256 const &id)
Definition: nft.h:59
ripple::Rules::enabled
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:94
std::shared_ptr
STL class.
ripple::PreclaimContext::view
ReadView const & view
Definition: Transactor.h:56
ripple::sfOwnerNode
const SF_UINT64 sfOwnerNode
ripple::PreclaimContext::j
const beast::Journal j
Definition: Transactor.h:60
ripple::NFTokenCreateOffer::doApply
TER doApply() override
Definition: NFTokenCreateOffer.cpp:222
ripple::sfDestination
const SF_ACCOUNT sfDestination
ripple::describeOwnerDir
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition: View.cpp:748
ripple::sfAmount
const SF_AMOUNT sfAmount
ripple::sfNFTokenID
const SF_UINT256 sfNFTokenID
ripple::Transactor::j_
const beast::Journal j_
Definition: Transactor.h:89
ripple::isTesSuccess
bool isTesSuccess(TER x)
Definition: TER.h:636
ripple::sfOwner
const SF_ACCOUNT sfOwner
ripple::nft::flagTransferable
constexpr const std::uint16_t flagTransferable
Definition: nft.h:56
ripple::accountHolds
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition: View.cpp:226
ripple::keylet::nftoffer
Keylet nftoffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Definition: Indexes.cpp:364
ripple::STAmount::signum
int signum() const noexcept
Definition: STAmount.h:368
ripple::hasExpired
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition: View.cpp:162
ripple::nft::flagOnlyXRP
constexpr const std::uint16_t flagOnlyXRP
Definition: nft.h:54
ripple::STTx::getSeqProxy
SeqProxy getSeqProxy() const
Definition: STTx.cpp:184
ripple::nft::findToken
std::optional< STObject > findToken(ReadView const &view, AccountID const &owner, uint256 const &nftokenID)
Finds the specified token in the owner's token directory.
Definition: NFTokenUtils.cpp:483
ripple::NFTokenCreateOffer::preclaim
static TER preclaim(PreclaimContext const &ctx)
Definition: NFTokenCreateOffer.cpp:104
ripple::sfNFTokenOfferNode
const SF_UINT64 sfNFTokenOfferNode
ripple::preflight1
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:81
ripple::lsfSellNFToken
@ lsfSellNFToken
Definition: LedgerFormats.h:305
ripple::sfExpiration
const SF_UINT32 sfExpiration
ripple::nft::getIssuer
AccountID getIssuer(uint256 const &id)
Definition: nft.h:119
ripple::temINVALID_FLAG
@ temINVALID_FLAG
Definition: TER.h:110
ripple::adjustOwnerCount
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:730
ripple::keylet::account
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:142
ripple::tefNFTOKEN_IS_NOT_TRANSFERABLE
@ tefNFTOKEN_IS_NOT_TRANSFERABLE
Definition: TER.h:180
ripple::featureDisallowIncoming
const uint256 featureDisallowIncoming
ripple::sfNFTokenMinter
const SF_ACCOUNT sfNFTokenMinter
ripple::TERSubset< CanCvtToTER >
ripple::NFTokenCreateOffer::preflight
static NotTEC preflight(PreflightContext const &ctx)
Definition: NFTokenCreateOffer.cpp:31
ripple::STAmount
Definition: STAmount.h:46
ripple::keylet::nft_sells
Keylet nft_sells(uint256 const &id) noexcept
The directory of sell offers for the specified NFT.
Definition: Indexes.cpp:377
ripple::ReadView::exists
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
ripple::STObject::getFlags
std::uint32_t getFlags() const
Definition: STObject.cpp:481
ripple::tfSellNFToken
constexpr const std::uint32_t tfSellNFToken
Definition: TxFlags.h:153
ripple::isXRP
bool isXRP(AccountID const &c)
Definition: AccountID.h:91
ripple::temBAD_AMOUNT
@ temBAD_AMOUNT
Definition: TER.h:88
std::uint32_t
ripple::keylet::nft_buys
Keylet nft_buys(uint256 const &id) noexcept
The directory of buy offers for the specified NFT.
Definition: Indexes.cpp:371
ripple::keylet::line
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:202
ripple::ReadView::read
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
ripple::PreclaimContext::tx
STTx const & tx
Definition: Transactor.h:58
ripple::accountFunds
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition: View.cpp:282
ripple::tecDIR_FULL
@ tecDIR_FULL
Definition: TER.h:267
ripple::PreclaimContext
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:52
ripple::SeqProxy::value
constexpr std::uint32_t value() const
Definition: SeqProxy.h:82
ripple::ApplyView::insert
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::lsfNFTokenSellOffers
@ lsfNFTokenSellOffers
Definition: LedgerFormats.h:302
ripple::featureNonFungibleTokensV1
const uint256 featureNonFungibleTokensV1
ripple::lsfNFTokenBuyOffers
@ lsfNFTokenBuyOffers
Definition: LedgerFormats.h:301
ripple::Transactor::view
ApplyView & view()
Definition: Transactor.h:107
ripple::tecNO_LINE
@ tecNO_LINE
Definition: TER.h:281
ripple::tecEXPIRED
@ tecEXPIRED
Definition: TER.h:294
ripple::lsfDisallowIncomingNFTokenOffer
@ lsfDisallowIncomingNFTokenOffer
Definition: LedgerFormats.h:269
ripple::temDISABLED
@ temDISABLED
Definition: TER.h:113
ripple::ReadView::rules
virtual Rules const & rules() const =0
Returns the tx processing rules.
ripple::sfFlags
const SF_UINT32 sfFlags
ripple::tfNFTokenCreateOfferMask
constexpr const std::uint32_t tfNFTokenCreateOfferMask
Definition: TxFlags.h:154
ripple::tecNO_ISSUER
@ tecNO_ISSUER
Definition: TER.h:279
ripple::Transactor::mPriorBalance
XRPAmount mPriorBalance
Definition: Transactor.h:92
ripple::STAmount::negative
bool negative() const noexcept
Definition: STAmount.h:338
ripple::tecNO_PERMISSION
@ tecNO_PERMISSION
Definition: TER.h:285
ripple::STObject::isFlag
bool isFlag(std::uint32_t) const
Definition: STObject.cpp:475
ripple::tecINSUFFICIENT_RESERVE
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:287
ripple::Transactor::ctx_
ApplyContext & ctx_
Definition: Transactor.h:88
ripple::nft::flagCreateTrustLines
constexpr const std::uint16_t flagCreateTrustLines
Definition: nft.h:55
ripple::sfAccount
const SF_ACCOUNT sfAccount
ripple::tecNO_ENTRY
@ tecNO_ENTRY
Definition: TER.h:286
ripple::temMALFORMED
@ temMALFORMED
Definition: TER.h:86
ripple::fixNonFungibleTokensV1_2
const uint256 fixNonFungibleTokensV1_2
ripple::PreflightContext::tx
STTx const & tx
Definition: Transactor.h:35
ripple::nft::getTransferFee
std::uint16_t getTransferFee(uint256 const &id)
Definition: nft.h:67
ripple::PreflightContext
State information when preflighting a tx.
Definition: Transactor.h:31
ripple::temBAD_EXPIRATION
@ temBAD_EXPIRATION
Definition: TER.h:90
ripple::ApplyView::dirInsert
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition: ApplyView.h:306
ripple::PreflightContext::rules
const Rules rules
Definition: Transactor.h:36
ripple::tesSUCCESS
@ tesSUCCESS
Definition: TER.h:235
ripple::Transactor::account_
const AccountID account_
Definition: Transactor.h:91
ripple::isFrozen
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition: View.cpp:203
ripple::ApplyContext::tx
STTx const & tx
Definition: ApplyContext.h:48
ripple::tecNO_DST
@ tecNO_DST
Definition: TER.h:270
ripple::NotTEC
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:567
ripple::root
Number root(Number f, unsigned d)
Definition: Number.cpp:624