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