rippled
Loading...
Searching...
No Matches
NFTokenMint.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/NFTokenMint.h>
21#include <xrpld/ledger/View.h>
22#include <xrpl/basics/Expected.h>
23#include <xrpl/basics/Log.h>
24#include <xrpl/protocol/Feature.h>
25#include <xrpl/protocol/InnerObjectFormats.h>
26#include <xrpl/protocol/Rate.h>
27#include <xrpl/protocol/TxFlags.h>
28#include <xrpl/protocol/st.h>
29#include <boost/endian/conversion.hpp>
30#include <array>
31
32namespace ripple {
33
34static std::uint16_t
36{
37 return static_cast<std::uint16_t>(txFlags & 0x0000FFFF);
38}
39
42{
43 if (!ctx.rules.enabled(featureNonFungibleTokensV1))
44 return temDISABLED;
45
46 bool const hasOfferFields = ctx.tx.isFieldPresent(sfAmount) ||
47 ctx.tx.isFieldPresent(sfDestination) ||
48 ctx.tx.isFieldPresent(sfExpiration);
49
50 if (!ctx.rules.enabled(featureNFTokenMintOffer) && hasOfferFields)
51 return temDISABLED;
52
53 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
54 return ret;
55
56 // Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between
57 // accounts allowed a TrustLine to be added to the issuer of that token
58 // without explicit permission from that issuer. This was enabled by
59 // minting the NFToken with the tfTrustLine flag set.
60 //
61 // That capability could be used to attack the NFToken issuer. It
62 // would be possible for two accounts to trade the NFToken back and forth
63 // building up any number of TrustLines on the issuer, increasing the
64 // issuer's reserve without bound.
65 //
66 // The fixRemoveNFTokenAutoTrustLine amendment disables minting with the
67 // tfTrustLine flag as a way to prevent the attack. But until the
68 // amendment passes we still need to keep the old behavior available.
69 std::uint32_t const NFTokenMintMask =
70 ctx.rules.enabled(fixRemoveNFTokenAutoTrustLine)
71 // if featureDynamicNFT enabled then new flag allowing mutable URI
72 // available
73 ? ctx.rules.enabled(featureDynamicNFT) ? tfNFTokenMintMaskWithMutable
75 : ctx.rules.enabled(featureDynamicNFT) ? tfNFTokenMintOldMaskWithMutable
77
78 if (ctx.tx.getFlags() & NFTokenMintMask)
79 return temINVALID_FLAG;
80
81 if (auto const f = ctx.tx[~sfTransferFee])
82 {
83 if (f > maxTransferFee)
85
86 // If a non-zero TransferFee is set then the tfTransferable flag
87 // must also be set.
88 if (f > 0u && !ctx.tx.isFlag(tfTransferable))
89 return temMALFORMED;
90 }
91
92 // An issuer must only be set if the tx is executed by the minter
93 if (auto iss = ctx.tx[~sfIssuer]; iss == ctx.tx[sfAccount])
94 return temMALFORMED;
95
96 if (auto uri = ctx.tx[~sfURI])
97 {
98 if (uri->length() == 0 || uri->length() > maxTokenURILength)
99 return temMALFORMED;
100 }
101
102 if (hasOfferFields)
103 {
104 // The Amount field must be present if either the Destination or
105 // Expiration fields are present.
106 if (!ctx.tx.isFieldPresent(sfAmount))
107 return temMALFORMED;
108
109 // Rely on the common code shared with NFTokenCreateOffer to
110 // do the validation. We pass tfSellNFToken as the transaction flags
111 // because a Mint is only allowed to create a sell offer.
113 ctx.tx[sfAccount],
114 ctx.tx[sfAmount],
115 ctx.tx[~sfDestination],
116 ctx.tx[~sfExpiration],
118 ctx.rules);
119 !isTesSuccess(notTec))
120 {
121 return notTec;
122 }
123 }
124
125 return preflight2(ctx);
126}
127
130 std::uint16_t flags,
131 std::uint16_t fee,
132 AccountID const& issuer,
133 nft::Taxon taxon,
134 std::uint32_t tokenSeq)
135{
136 // An issuer may issue several NFTs with the same taxon; to ensure that NFTs
137 // are spread across multiple pages we lightly mix the taxon up by using the
138 // sequence (which is not under the issuer's direct control) as the seed for
139 // a simple linear congruential generator. cipheredTaxon() does this work.
140 taxon = nft::cipheredTaxon(tokenSeq, taxon);
141
142 // The values are packed inside a 32-byte buffer, so we need to make sure
143 // that the endianess is fixed.
144 flags = boost::endian::native_to_big(flags);
145 fee = boost::endian::native_to_big(fee);
146 taxon = nft::toTaxon(boost::endian::native_to_big(nft::toUInt32(taxon)));
147 tokenSeq = boost::endian::native_to_big(tokenSeq);
148
150
151 auto ptr = buf.data();
152
153 // This code is awkward but the idea is to pack these values into a single
154 // 256-bit value that uniquely identifies this NFT.
155 std::memcpy(ptr, &flags, sizeof(flags));
156 ptr += sizeof(flags);
157
158 std::memcpy(ptr, &fee, sizeof(fee));
159 ptr += sizeof(fee);
160
161 std::memcpy(ptr, issuer.data(), issuer.size());
162 ptr += issuer.size();
163
164 std::memcpy(ptr, &taxon, sizeof(taxon));
165 ptr += sizeof(taxon);
166
167 std::memcpy(ptr, &tokenSeq, sizeof(tokenSeq));
168 ptr += sizeof(tokenSeq);
169 XRPL_ASSERT(
170 std::distance(buf.data(), ptr) == buf.size(),
171 "ripple::NFTokenMint::createNFTokenID : data size matches the buffer");
172
173 return uint256::fromVoid(buf.data());
174}
175
176TER
178{
179 // The issuer of the NFT may or may not be the account executing this
180 // transaction. Check that and verify that this is allowed:
181 if (auto issuer = ctx.tx[~sfIssuer])
182 {
183 auto const sle = ctx.view.read(keylet::account(*issuer));
184
185 if (!sle)
186 return tecNO_ISSUER;
187
188 if (auto const minter = (*sle)[~sfNFTokenMinter];
189 minter != ctx.tx[sfAccount])
190 return tecNO_PERMISSION;
191 }
192
193 if (ctx.tx.isFieldPresent(sfAmount))
194 {
195 // The Amount field says create an offer for the minted token.
196 if (hasExpired(ctx.view, ctx.tx[~sfExpiration]))
197 return tecEXPIRED;
198
199 // Rely on the common code shared with NFTokenCreateOffer to
200 // do the validation. We pass tfSellNFToken as the transaction flags
201 // because a Mint is only allowed to create a sell offer.
202 if (TER const ter = nft::tokenOfferCreatePreclaim(
203 ctx.view,
204 ctx.tx[sfAccount],
205 ctx.tx[~sfIssuer].value_or(ctx.tx[sfAccount]),
206 ctx.tx[sfAmount],
207 ctx.tx[~sfDestination],
209 ctx.tx[~sfTransferFee].value_or(0),
210 ctx.j);
211 !isTesSuccess(ter))
212 return ter;
213 }
214 return tesSUCCESS;
215}
216
217TER
219{
220 auto const issuer = ctx_.tx[~sfIssuer].value_or(account_);
221
222 auto const tokenSeq = [this, &issuer]() -> Expected<std::uint32_t, TER> {
223 auto const root = view().peek(keylet::account(issuer));
224 if (root == nullptr)
225 // Should not happen. Checked in preclaim.
226 return Unexpected(tecNO_ISSUER);
227
228 if (!ctx_.view().rules().enabled(fixNFTokenRemint))
229 {
230 // Get the unique sequence number for this token:
231 std::uint32_t const tokenSeq =
232 (*root)[~sfMintedNFTokens].value_or(0);
233 {
234 std::uint32_t const nextTokenSeq = tokenSeq + 1;
235 if (nextTokenSeq < tokenSeq)
237
238 (*root)[sfMintedNFTokens] = nextTokenSeq;
239 }
240 ctx_.view().update(root);
241 return tokenSeq;
242 }
243
244 // With fixNFTokenRemint amendment enabled:
245 //
246 // If the issuer hasn't minted an NFToken before we must add a
247 // FirstNFTokenSequence field to the issuer's AccountRoot. The
248 // value of the FirstNFTokenSequence must equal the issuer's
249 // current account sequence.
250 //
251 // There are three situations:
252 // o If the first token is being minted by the issuer and
253 // * If the transaction consumes a Sequence number, then the
254 // Sequence has been pre-incremented by the time we get here in
255 // doApply. We must decrement the value in the Sequence field.
256 // * Otherwise the transaction uses a Ticket so the Sequence has
257 // not been pre-incremented. We use the Sequence value as is.
258 // o The first token is being minted by an authorized minter. In
259 // this case the issuer's Sequence field has been left untouched.
260 // We use the issuer's Sequence value as is.
261 if (!root->isFieldPresent(sfFirstNFTokenSequence))
262 {
263 std::uint32_t const acctSeq = root->at(sfSequence);
264
265 root->at(sfFirstNFTokenSequence) =
266 ctx_.tx.isFieldPresent(sfIssuer) ||
268 ? acctSeq
269 : acctSeq - 1;
270 }
271
272 std::uint32_t const mintedNftCnt =
273 (*root)[~sfMintedNFTokens].value_or(0u);
274
275 (*root)[sfMintedNFTokens] = mintedNftCnt + 1u;
276 if ((*root)[sfMintedNFTokens] == 0u)
278
279 // Get the unique sequence number of this token by
280 // sfFirstNFTokenSequence + sfMintedNFTokens
281 std::uint32_t const offset = (*root)[sfFirstNFTokenSequence];
282 std::uint32_t const tokenSeq = offset + mintedNftCnt;
283
284 // Check for more overflow cases
285 if (tokenSeq + 1u == 0u || tokenSeq < offset)
287
288 ctx_.view().update(root);
289 return tokenSeq;
290 }();
291
292 if (!tokenSeq.has_value())
293 return (tokenSeq.error());
294
295 std::uint32_t const ownerCountBefore =
296 view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
297
298 // Assemble the new NFToken.
299 SOTemplate const* nfTokenTemplate =
301
302 if (nfTokenTemplate == nullptr)
303 // Should never happen.
304 return tecINTERNAL;
305
306 auto const nftokenID = createNFTokenID(
308 ctx_.tx[~sfTransferFee].value_or(0),
309 issuer,
310 nft::toTaxon(ctx_.tx[sfNFTokenTaxon]),
311 tokenSeq.value());
312
313 STObject newToken(
314 *nfTokenTemplate, sfNFToken, [this, &nftokenID](STObject& object) {
315 object.setFieldH256(sfNFTokenID, nftokenID);
316
317 if (auto const uri = ctx_.tx[~sfURI])
318 object.setFieldVL(sfURI, *uri);
319 });
320
321 if (TER const ret =
322 nft::insertToken(ctx_.view(), account_, std::move(newToken));
323 ret != tesSUCCESS)
324 return ret;
325
326 if (ctx_.tx.isFieldPresent(sfAmount))
327 {
328 // Rely on the common code shared with NFTokenCreateOffer to create
329 // the offer. We pass tfSellNFToken as the transaction flags
330 // because a Mint is only allowed to create a sell offer.
331 if (TER const ter = nft::tokenOfferCreateApply(
332 view(),
333 ctx_.tx[sfAccount],
334 ctx_.tx[sfAmount],
335 ctx_.tx[~sfDestination],
336 ctx_.tx[~sfExpiration],
338 nftokenID,
340 j_);
341 !isTesSuccess(ter))
342 return ter;
343 }
344
345 // Only check the reserve if the owner count actually changed. This
346 // allows NFTs to be added to the page (and burn fees) without
347 // requiring the reserve to be met each time. The reserve is
348 // only managed when a new NFT page or sell offer is added.
349 if (auto const ownerCountAfter =
350 view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
351 ownerCountAfter > ownerCountBefore)
352 {
353 if (auto const reserve = view().fees().accountReserve(ownerCountAfter);
354 mPriorBalance < reserve)
356 }
357 return tesSUCCESS;
358}
359
360} // namespace ripple
ApplyView & view()
Definition: ApplyContext.h:54
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
SOTemplate const * findSOTemplateBySField(SField const &sField) const
static InnerObjectFormats const & getInstance()
static NotTEC preflight(PreflightContext const &ctx)
Definition: NFTokenMint.cpp:41
static uint256 createNFTokenID(std::uint16_t flags, std::uint16_t fee, AccountID const &issuer, nft::Taxon taxon, std::uint32_t tokenSeq)
static TER preclaim(PreclaimContext const &ctx)
TER doApply() override
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:122
Defines the fields and their attributes within a STObject.
Definition: SOTemplate.h:113
bool isFlag(std::uint32_t) const
Definition: STObject.cpp:501
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:454
std::uint32_t getFlags() const
Definition: STObject.cpp:507
SeqProxy getSeqProxy() const
Definition: STTx.cpp:186
constexpr bool isTicket() const
Definition: SeqProxy.h:94
AccountID const account_
Definition: Transactor.h:91
ApplyView & view()
Definition: Transactor.h:107
beast::Journal const j_
Definition: Transactor.h:89
XRPAmount mPriorBalance
Definition: Transactor.h:92
ApplyContext & ctx_
Definition: Transactor.h:88
static base_uint fromVoid(void const *data)
Definition: base_uint.h:318
pointer data()
Definition: base_uint.h:124
static constexpr std::size_t size()
Definition: base_uint.h:525
A type-safe wrap around standard integral types.
T data(T... args)
T distance(T... args)
T memcpy(T... args)
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:160
std::uint32_t toUInt32(Taxon t)
Definition: nft.h:48
NotTEC tokenOfferCreatePreflight(AccountID const &acctID, STAmount const &amount, std::optional< AccountID > const &dest, std::optional< std::uint32_t > const &expiration, std::uint16_t nftFlags, Rules const &rules, std::optional< AccountID > const &owner, std::uint32_t txFlags)
Preflight checks shared by NFTokenCreateOffer and NFTokenMint.
TER tokenOfferCreateApply(ApplyView &view, AccountID const &acctID, STAmount const &amount, std::optional< AccountID > const &dest, std::optional< std::uint32_t > const &expiration, SeqProxy seqProxy, uint256 const &nftokenID, XRPAmount const &priorBalance, beast::Journal j, std::uint32_t txFlags)
doApply implementation shared by NFTokenCreateOffer and NFTokenMint
Taxon cipheredTaxon(std::uint32_t tokenSeq, Taxon taxon)
Definition: nft.h:84
TER tokenOfferCreatePreclaim(ReadView const &view, AccountID const &acctID, AccountID const &nftIssuer, STAmount const &amount, std::optional< AccountID > const &dest, std::uint16_t nftFlags, std::uint16_t xferFee, beast::Journal j, std::optional< AccountID > const &owner, std::uint32_t txFlags)
Preclaim checks shared by NFTokenCreateOffer and NFTokenMint.
TER insertToken(ApplyView &view, AccountID owner, STObject &&nft)
Insert the token in the owner's token directory.
Taxon toTaxon(std::uint32_t i)
Definition: nft.h:42
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
constexpr std::uint32_t const tfNFTokenMintOldMaskWithMutable
Definition: TxFlags.h:182
constexpr std::uint32_t const tfNFTokenMintMaskWithMutable
Definition: TxFlags.h:185
constexpr std::uint32_t const tfNFTokenMintOldMask
Definition: TxFlags.h:178
std::uint16_t constexpr maxTransferFee
The maximum token transfer fee allowed.
Definition: Protocol.h:82
bool isTesSuccess(TER x)
Definition: TER.h:656
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:82
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:134
std::size_t constexpr maxTokenURILength
The maximum length of a URI inside an NFT.
Definition: Protocol.h:85
@ tecNO_ISSUER
Definition: TER.h:286
@ tecINTERNAL
Definition: TER.h:297
@ tecNO_PERMISSION
Definition: TER.h:292
@ tecMAX_SEQUENCE_REACHED
Definition: TER.h:307
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:294
@ tecEXPIRED
Definition: TER.h:301
@ tesSUCCESS
Definition: TER.h:242
Number root(Number f, unsigned d)
Definition: Number.cpp:630
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition: View.cpp:164
static std::uint16_t extractNFTokenFlagsFromTxFlags(std::uint32_t txFlags)
Definition: NFTokenMint.cpp:35
constexpr std::uint32_t const tfTransferable
Definition: TxFlags.h:136
constexpr std::uint32_t const tfNFTokenMintMask
Definition: TxFlags.h:175
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:587
@ temMALFORMED
Definition: TER.h:87
@ temINVALID_FLAG
Definition: TER.h:111
@ temDISABLED
Definition: TER.h:114
@ temBAD_NFTOKEN_TRANSFER_FEE
Definition: TER.h:127
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:53
ReadView const & view
Definition: Transactor.h:56
beast::Journal const j
Definition: Transactor.h:60
State information when preflighting a tx.
Definition: Transactor.h:32