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  auto const funds = accountHolds(
157  ctx.view,
158  ctx.tx[sfAccount],
159  amount.getCurrency(),
160  amount.getIssuer(),
161  FreezeHandling::fhZERO_IF_FROZEN,
162  ctx.j);
163 
164  if (funds.signum() <= 0)
165  return tecUNFUNDED_OFFER;
166  }
167 
168  if (auto const destination = ctx.tx[~sfDestination])
169  {
170  // If a destination is specified, the destination must already be in
171  // the ledger.
172  auto const sleDst = ctx.view.read(keylet::account(*destination));
173 
174  if (!sleDst)
175  return tecNO_DST;
176 
177  // check if the destination has disallowed incoming offers
179  {
180  // flag cannot be set unless amendment is enabled but
181  // out of an abundance of caution check anyway
182 
183  if (sleDst->getFlags() & lsfDisallowIncomingNFTOffer)
184  return tecNO_PERMISSION;
185  }
186  }
187 
188  if (auto const owner = ctx.tx[~sfOwner])
189  {
190  // Check if the owner (buy offer) has disallowed incoming offers
192  {
193  auto const sleOwner = ctx.view.read(keylet::account(*owner));
194 
195  // defensively check
196  // it should not be possible to specify owner that doesn't exist
197  if (!sleOwner)
198  return tecNO_TARGET;
199 
200  if (sleOwner->getFlags() & lsfDisallowIncomingNFTOffer)
201  return tecNO_PERMISSION;
202  }
203  }
204 
205  return tesSUCCESS;
206 }
207 
208 TER
210 {
211  if (auto const acct = view().read(keylet::account(ctx_.tx[sfAccount]));
212  mPriorBalance < view().fees().accountReserve((*acct)[sfOwnerCount] + 1))
214 
215  auto const nftokenID = ctx_.tx[sfNFTokenID];
216 
217  auto const offerID =
219 
220  // Create the offer:
221  {
222  // Token offers are always added to the owner's owner directory:
223  auto const ownerNode = view().dirInsert(
225 
226  if (!ownerNode)
227  return tecDIR_FULL;
228 
229  bool const isSellOffer = ctx_.tx.isFlag(tfSellNFToken);
230 
231  // Token offers are also added to the token's buy or sell offer
232  // directory
233  auto const offerNode = view().dirInsert(
234  isSellOffer ? keylet::nft_sells(nftokenID)
235  : keylet::nft_buys(nftokenID),
236  offerID,
237  [&nftokenID, isSellOffer](std::shared_ptr<SLE> const& sle) {
238  (*sle)[sfFlags] =
240  (*sle)[sfNFTokenID] = nftokenID;
241  });
242 
243  if (!offerNode)
244  return tecDIR_FULL;
245 
246  std::uint32_t sleFlags = 0;
247 
248  if (isSellOffer)
249  sleFlags |= lsfSellNFToken;
250 
251  auto offer = std::make_shared<SLE>(offerID);
252  (*offer)[sfOwner] = account_;
253  (*offer)[sfNFTokenID] = nftokenID;
254  (*offer)[sfAmount] = ctx_.tx[sfAmount];
255  (*offer)[sfFlags] = sleFlags;
256  (*offer)[sfOwnerNode] = *ownerNode;
257  (*offer)[sfNFTokenOfferNode] = *offerNode;
258 
259  if (auto const expiration = ctx_.tx[~sfExpiration])
260  (*offer)[sfExpiration] = *expiration;
261 
262  if (auto const destination = ctx_.tx[~sfDestination])
263  (*offer)[sfDestination] = *destination;
264 
265  view().insert(offer);
266  }
267 
268  // Update owner count.
270 
271  return tesSUCCESS;
272 }
273 
274 } // namespace ripple
ripple::tecUNFUNDED_OFFER
@ tecUNFUNDED_OFFER
Definition: TER.h:248
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:267
ripple::preflight2
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:109
ripple::fixNFTokenNegOffer
const uint256 fixNFTokenNegOffer
ripple::tecNO_TARGET
@ tecNO_TARGET
Definition: TER.h:268
ripple::nft::getFlags
std::uint16_t getFlags(uint256 const &id)
Definition: NFTokenUtils.h:116
ripple::Rules::enabled
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:81
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:209
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:594
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::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:480
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:57
ripple::lsfSellNFToken
@ lsfSellNFToken
Definition: LedgerFormats.h:269
ripple::sfExpiration
const SF_UINT32 sfExpiration
ripple::nft::getIssuer
AccountID getIssuer(uint256 const &id)
Definition: NFTokenUtils.h:176
ripple::temINVALID_FLAG
@ temINVALID_FLAG
Definition: TER.h:106
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:165
ripple::featureDisallowIncoming
const uint256 featureDisallowIncoming
ripple::sfNFTokenMinter
const SF_ACCOUNT sfNFTokenMinter
ripple::lsfDisallowIncomingNFTOffer
@ lsfDisallowIncomingNFTOffer
Definition: LedgerFormats.h:238
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:150
ripple::isXRP
bool isXRP(AccountID const &c)
Definition: AccountID.h:89
ripple::temBAD_AMOUNT
@ temBAD_AMOUNT
Definition: TER.h:84
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::tecDIR_FULL
@ tecDIR_FULL
Definition: TER.h:251
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:265
ripple::tecEXPIRED
@ tecEXPIRED
Definition: TER.h:278
ripple::temDISABLED
@ temDISABLED
Definition: TER.h:109
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:151
ripple::tecNO_ISSUER
@ tecNO_ISSUER
Definition: TER.h:263
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:269
ripple::STObject::isFlag
bool isFlag(std::uint32_t) const
Definition: STObject.cpp:475
ripple::tecINSUFFICIENT_RESERVE
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:271
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:270
ripple::temMALFORMED
@ temMALFORMED
Definition: TER.h:82
ripple::PreflightContext::tx
STTx const & tx
Definition: Transactor.h:35
ripple::nft::getTransferFee
std::uint16_t getTransferFee(uint256 const &id)
Definition: NFTokenUtils.h:124
ripple::PreflightContext
State information when preflighting a tx.
Definition: Transactor.h:31
ripple::temBAD_EXPIRATION
@ temBAD_EXPIRATION
Definition: TER.h:86
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:219
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:254
ripple::NotTEC
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:525
ripple::root
Number root(Number f, unsigned d)
Definition: Number.cpp:624