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:251
ripple::keylet::ownerDir
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:303
ripple::sfOwnerCount
const SF_UINT32 sfOwnerCount
ripple::tecFROZEN
@ tecFROZEN
Definition: TER.h:270
ripple::preflight2
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:130
ripple::fixNFTokenNegOffer
const uint256 fixNFTokenNegOffer
ripple::tecNO_TARGET
@ tecNO_TARGET
Definition: TER.h:271
ripple::nft::getFlags
std::uint16_t getFlags(uint256 const &id)
Definition: NFTokenUtils.h:120
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:731
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:597
ripple::sfOwner
const SF_ACCOUNT sfOwner
ripple::nft::flagTransferable
constexpr const std::uint16_t flagTransferable
Definition: NFTokenUtils.h:54
ripple::accountHolds
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j)
Definition: View.cpp:223
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:355
ripple::STAmount::signum
int signum() const noexcept
Definition: STAmount.h:365
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:179
ripple::nft::flagOnlyXRP
constexpr const std::uint16_t flagOnlyXRP
Definition: NFTokenUtils.h:52
ripple::STTx::getSeqProxy
SeqProxy getSeqProxy() const
Definition: STTx.cpp:183
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:78
ripple::lsfSellNFToken
@ lsfSellNFToken
Definition: LedgerFormats.h:269
ripple::sfExpiration
const SF_UINT32 sfExpiration
ripple::nft::getIssuer
AccountID getIssuer(uint256 const &id)
Definition: NFTokenUtils.h:180
ripple::temINVALID_FLAG
@ temINVALID_FLAG
Definition: TER.h:109
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:713
ripple::keylet::account
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:133
ripple::tefNFTOKEN_IS_NOT_TRANSFERABLE
@ tefNFTOKEN_IS_NOT_TRANSFERABLE
Definition: TER.h:168
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:45
ripple::keylet::nft_sells
Keylet nft_sells(uint256 const &id) noexcept
The directory of sell offers for the specified NFT.
Definition: Indexes.cpp:368
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:152
ripple::isXRP
bool isXRP(AccountID const &c)
Definition: AccountID.h:89
ripple::temBAD_AMOUNT
@ temBAD_AMOUNT
Definition: TER.h:87
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:362
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:193
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:267
ripple::tecDIR_FULL
@ tecDIR_FULL
Definition: TER.h:254
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:266
ripple::featureNonFungibleTokensV1
const uint256 featureNonFungibleTokensV1
ripple::lsfNFTokenBuyOffers
@ lsfNFTokenBuyOffers
Definition: LedgerFormats.h:265
ripple::Transactor::view
ApplyView & view()
Definition: Transactor.h:107
ripple::tecNO_LINE
@ tecNO_LINE
Definition: TER.h:268
ripple::tecEXPIRED
@ tecEXPIRED
Definition: TER.h:281
ripple::lsfDisallowIncomingNFTokenOffer
@ lsfDisallowIncomingNFTokenOffer
Definition: LedgerFormats.h:238
ripple::temDISABLED
@ temDISABLED
Definition: TER.h:112
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:153
ripple::tecNO_ISSUER
@ tecNO_ISSUER
Definition: TER.h:266
ripple::Transactor::mPriorBalance
XRPAmount mPriorBalance
Definition: Transactor.h:92
ripple::STAmount::negative
bool negative() const noexcept
Definition: STAmount.h:335
ripple::tecNO_PERMISSION
@ tecNO_PERMISSION
Definition: TER.h:272
ripple::STObject::isFlag
bool isFlag(std::uint32_t) const
Definition: STObject.cpp:475
ripple::tecINSUFFICIENT_RESERVE
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:274
ripple::Transactor::ctx_
ApplyContext & ctx_
Definition: Transactor.h:88
ripple::nft::flagCreateTrustLines
constexpr const std::uint16_t flagCreateTrustLines
Definition: NFTokenUtils.h:53
ripple::sfAccount
const SF_ACCOUNT sfAccount
ripple::tecNO_ENTRY
@ tecNO_ENTRY
Definition: TER.h:273
ripple::temMALFORMED
@ temMALFORMED
Definition: TER.h:85
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: NFTokenUtils.h:128
ripple::PreflightContext
State information when preflighting a tx.
Definition: Transactor.h:31
ripple::temBAD_EXPIRATION
@ temBAD_EXPIRATION
Definition: TER.h:89
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:222
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:200
ripple::ApplyContext::tx
STTx const & tx
Definition: ApplyContext.h:48
ripple::tecNO_DST
@ tecNO_DST
Definition: TER.h:257
ripple::NotTEC
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:528
ripple::root
Number root(Number f, unsigned d)
Definition: Number.cpp:624