rippled
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 <ripple/app/tx/impl/NFTokenMint.h>
21 #include <ripple/basics/Expected.h>
22 #include <ripple/basics/Log.h>
23 #include <ripple/ledger/View.h>
24 #include <ripple/protocol/Feature.h>
25 #include <ripple/protocol/InnerObjectFormats.h>
26 #include <ripple/protocol/Rate.h>
27 #include <ripple/protocol/TxFlags.h>
28 #include <ripple/protocol/st.h>
29 #include <boost/endian/conversion.hpp>
30 #include <array>
31 
32 namespace ripple {
33 
34 NotTEC
36 {
38  return temDISABLED;
39 
40  if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
41  return ret;
42 
43  // Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between
44  // accounts allowed a TrustLine to be added to the issuer of that token
45  // without explicit permission from that issuer. This was enabled by
46  // minting the NFToken with the tfTrustLine flag set.
47  //
48  // That capability could be used to attack the NFToken issuer. It
49  // would be possible for two accounts to trade the NFToken back and forth
50  // building up any number of TrustLines on the issuer, increasing the
51  // issuer's reserve without bound.
52  //
53  // The fixRemoveNFTokenAutoTrustLine amendment disables minting with the
54  // tfTrustLine flag as a way to prevent the attack. But until the
55  // amendment passes we still need to keep the old behavior available.
56  std::uint32_t const NFTokenMintMask =
59  if (ctx.tx.getFlags() & NFTokenMintMask)
60  return temINVALID_FLAG;
61 
62  if (auto const f = ctx.tx[~sfTransferFee])
63  {
64  if (f > maxTransferFee)
66 
67  // If a non-zero TransferFee is set then the tfTransferable flag
68  // must also be set.
69  if (f > 0u && !ctx.tx.isFlag(tfTransferable))
70  return temMALFORMED;
71  }
72 
73  // An issuer must only be set if the tx is executed by the minter
74  if (auto iss = ctx.tx[~sfIssuer]; iss == ctx.tx[sfAccount])
75  return temMALFORMED;
76 
77  if (auto uri = ctx.tx[~sfURI])
78  {
79  if (uri->length() == 0 || uri->length() > maxTokenURILength)
80  return temMALFORMED;
81  }
82 
83  return preflight2(ctx);
84 }
85 
86 uint256
88  std::uint16_t flags,
89  std::uint16_t fee,
90  AccountID const& issuer,
91  nft::Taxon taxon,
92  std::uint32_t tokenSeq)
93 {
94  // An issuer may issue several NFTs with the same taxon; to ensure that NFTs
95  // are spread across multiple pages we lightly mix the taxon up by using the
96  // sequence (which is not under the issuer's direct control) as the seed for
97  // a simple linear congruential generator. cipheredTaxon() does this work.
98  taxon = nft::cipheredTaxon(tokenSeq, taxon);
99 
100  // The values are packed inside a 32-byte buffer, so we need to make sure
101  // that the endianess is fixed.
102  flags = boost::endian::native_to_big(flags);
103  fee = boost::endian::native_to_big(fee);
104  taxon = nft::toTaxon(boost::endian::native_to_big(nft::toUInt32(taxon)));
105  tokenSeq = boost::endian::native_to_big(tokenSeq);
106 
108 
109  auto ptr = buf.data();
110 
111  // This code is awkward but the idea is to pack these values into a single
112  // 256-bit value that uniquely identifies this NFT.
113  std::memcpy(ptr, &flags, sizeof(flags));
114  ptr += sizeof(flags);
115 
116  std::memcpy(ptr, &fee, sizeof(fee));
117  ptr += sizeof(fee);
118 
119  std::memcpy(ptr, issuer.data(), issuer.size());
120  ptr += issuer.size();
121 
122  std::memcpy(ptr, &taxon, sizeof(taxon));
123  ptr += sizeof(taxon);
124 
125  std::memcpy(ptr, &tokenSeq, sizeof(tokenSeq));
126  ptr += sizeof(tokenSeq);
127  assert(std::distance(buf.data(), ptr) == buf.size());
128 
129  return uint256::fromVoid(buf.data());
130 }
131 
132 TER
134 {
135  // The issuer of the NFT may or may not be the account executing this
136  // transaction. Check that and verify that this is allowed:
137  if (auto issuer = ctx.tx[~sfIssuer])
138  {
139  auto const sle = ctx.view.read(keylet::account(*issuer));
140 
141  if (!sle)
142  return tecNO_ISSUER;
143 
144  if (auto const minter = (*sle)[~sfNFTokenMinter];
145  minter != ctx.tx[sfAccount])
146  return tecNO_PERMISSION;
147  }
148 
149  return tesSUCCESS;
150 }
151 
152 TER
154 {
155  auto const issuer = ctx_.tx[~sfIssuer].value_or(account_);
156 
157  auto const tokenSeq = [this, &issuer]() -> Expected<std::uint32_t, TER> {
158  auto const root = view().peek(keylet::account(issuer));
159  if (root == nullptr)
160  // Should not happen. Checked in preclaim.
161  return Unexpected(tecNO_ISSUER);
162 
164  {
165  // Get the unique sequence number for this token:
166  std::uint32_t const tokenSeq =
167  (*root)[~sfMintedNFTokens].value_or(0);
168  {
169  std::uint32_t const nextTokenSeq = tokenSeq + 1;
170  if (nextTokenSeq < tokenSeq)
172 
173  (*root)[sfMintedNFTokens] = nextTokenSeq;
174  }
175  ctx_.view().update(root);
176  return tokenSeq;
177  }
178 
179  // With fixNFTokenRemint amendment enabled:
180  //
181  // If the issuer hasn't minted an NFToken before we must add a
182  // FirstNFTokenSequence field to the issuer's AccountRoot. The
183  // value of the FirstNFTokenSequence must equal the issuer's
184  // current account sequence.
185  //
186  // There are three situations:
187  // o If the first token is being minted by the issuer and
188  // * If the transaction consumes a Sequence number, then the
189  // Sequence has been pre-incremented by the time we get here in
190  // doApply. We must decrement the value in the Sequence field.
191  // * Otherwise the transaction uses a Ticket so the Sequence has
192  // not been pre-incremented. We use the Sequence value as is.
193  // o The first token is being minted by an authorized minter. In
194  // this case the issuer's Sequence field has been left untouched.
195  // We use the issuer's Sequence value as is.
196  if (!root->isFieldPresent(sfFirstNFTokenSequence))
197  {
198  std::uint32_t const acctSeq = root->at(sfSequence);
199 
203  ? acctSeq
204  : acctSeq - 1;
205  }
206 
207  std::uint32_t const mintedNftCnt =
208  (*root)[~sfMintedNFTokens].value_or(0u);
209 
210  (*root)[sfMintedNFTokens] = mintedNftCnt + 1u;
211  if ((*root)[sfMintedNFTokens] == 0u)
213 
214  // Get the unique sequence number of this token by
215  // sfFirstNFTokenSequence + sfMintedNFTokens
216  std::uint32_t const offset = (*root)[sfFirstNFTokenSequence];
217  std::uint32_t const tokenSeq = offset + mintedNftCnt;
218 
219  // Check for more overflow cases
220  if (tokenSeq + 1u == 0u || tokenSeq < offset)
222 
223  ctx_.view().update(root);
224  return tokenSeq;
225  }();
226 
227  if (!tokenSeq.has_value())
228  return (tokenSeq.error());
229 
230  std::uint32_t const ownerCountBefore =
231  view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
232 
233  // Assemble the new NFToken.
234  SOTemplate const* nfTokenTemplate =
236 
237  if (nfTokenTemplate == nullptr)
238  // Should never happen.
239  return tecINTERNAL;
240 
241  STObject newToken(
242  *nfTokenTemplate,
243  sfNFToken,
244  [this, &issuer, &tokenSeq](STObject& object) {
245  object.setFieldH256(
246  sfNFTokenID,
248  static_cast<std::uint16_t>(ctx_.tx.getFlags() & 0x0000FFFF),
249  ctx_.tx[~sfTransferFee].value_or(0),
250  issuer,
252  tokenSeq.value()));
253 
254  if (auto const uri = ctx_.tx[~sfURI])
255  object.setFieldVL(sfURI, *uri);
256  });
257 
258  if (TER const ret =
259  nft::insertToken(ctx_.view(), account_, std::move(newToken));
260  ret != tesSUCCESS)
261  return ret;
262 
263  // Only check the reserve if the owner count actually changed. This
264  // allows NFTs to be added to the page (and burn fees) without
265  // requiring the reserve to be met each time. The reserve is
266  // only managed when a new NFT page is added.
267  if (auto const ownerCountAfter =
268  view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
269  ownerCountAfter > ownerCountBefore)
270  {
271  if (auto const reserve = view().fees().accountReserve(ownerCountAfter);
272  mPriorBalance < reserve)
274  }
275  return tesSUCCESS;
276 }
277 
278 } // namespace ripple
ripple::NFTokenMint::preclaim
static TER preclaim(PreclaimContext const &ctx)
Definition: NFTokenMint.cpp:133
ripple::maxTransferFee
constexpr std::uint16_t maxTransferFee
The maximum token transfer fee allowed.
Definition: Protocol.h:81
ripple::sfOwnerCount
const SF_UINT32 sfOwnerCount
ripple::fixRemoveNFTokenAutoTrustLine
const uint256 fixRemoveNFTokenAutoTrustLine
ripple::sfFirstNFTokenSequence
const SF_UINT32 sfFirstNFTokenSequence
ripple::preflight2
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:130
ripple::tfTransferable
constexpr const std::uint32_t tfTransferable
Definition: TxFlags.h:130
ripple::Rules::enabled
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:94
ripple::PreclaimContext::view
ReadView const & view
Definition: Transactor.h:56
ripple::ApplyView::peek
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
ripple::sfNFTokenID
const SF_UINT256 sfNFTokenID
ripple::isTesSuccess
bool isTesSuccess(TER x)
Definition: TER.h:597
ripple::InnerObjectFormats::findSOTemplateBySField
SOTemplate const * findSOTemplateBySField(SField const &sField) const
Definition: InnerObjectFormats.cpp:72
ripple::NFTokenMint::doApply
TER doApply() override
Definition: NFTokenMint.cpp:153
ripple::sfSequence
const SF_UINT32 sfSequence
ripple::tfNFTokenMintOldMask
constexpr const std::uint32_t tfNFTokenMintOldMask
Definition: TxFlags.h:145
ripple::Unexpected
Unexpected(E(&)[N]) -> Unexpected< E const * >
ripple::sfMintedNFTokens
const SF_UINT32 sfMintedNFTokens
std::distance
T distance(T... args)
ripple::ApplyView::update
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
ripple::STTx::getSeqProxy
SeqProxy getSeqProxy() const
Definition: STTx.cpp:183
ripple::nft::toTaxon
Taxon toTaxon(std::uint32_t i)
Definition: NFTokenUtils.h:40
ripple::base_uint::data
pointer data()
Definition: base_uint.h:122
ripple::preflight1
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:78
ripple::sfTransferFee
const SF_UINT16 sfTransferFee
ripple::base_uint::size
constexpr static std::size_t size()
Definition: base_uint.h:519
ripple::fixNFTokenRemint
const uint256 fixNFTokenRemint
ripple::SeqProxy::isTicket
constexpr bool isTicket() const
Definition: SeqProxy.h:94
ripple::base_uint< 256 >
ripple::NFTokenMint::preflight
static NotTEC preflight(PreflightContext const &ctx)
Definition: NFTokenMint.cpp:35
ripple::temINVALID_FLAG
@ temINVALID_FLAG
Definition: TER.h:109
ripple::Expected
Definition: Expected.h:132
ripple::SOTemplate
Defines the fields and their attributes within a STObject.
Definition: SOTemplate.h:82
ripple::keylet::account
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:133
ripple::sfNFTokenMinter
const SF_ACCOUNT sfNFTokenMinter
ripple::nft::toUInt32
std::uint32_t toUInt32(Taxon t)
Definition: NFTokenUtils.h:46
ripple::TERSubset< CanCvtToTER >
array
ripple::NFTokenMint::createNFTokenID
static uint256 createNFTokenID(std::uint16_t flags, std::uint16_t fee, AccountID const &issuer, nft::Taxon taxon, std::uint32_t tokenSeq)
Definition: NFTokenMint.cpp:87
ripple::tecINTERNAL
@ tecINTERNAL
Definition: TER.h:277
ripple::STObject::getFlags
std::uint32_t getFlags() const
Definition: STObject.cpp:481
std::uint32_t
ripple::maxTokenURILength
constexpr std::size_t maxTokenURILength
The maximum length of a URI inside an NFT.
Definition: Protocol.h:84
ripple::ReadView::read
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
ripple::ApplyContext::view
ApplyView & view()
Definition: ApplyContext.h:54
ripple::PreclaimContext::tx
STTx const & tx
Definition: Transactor.h:58
ripple::sfNFToken
const SField sfNFToken
ripple::PreclaimContext
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:52
ripple::STObject
Definition: STObject.h:51
ripple::sfURI
const SF_VL sfURI
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::featureNonFungibleTokensV1
const uint256 featureNonFungibleTokensV1
ripple::Transactor::view
ApplyView & view()
Definition: Transactor.h:107
ripple::temDISABLED
@ temDISABLED
Definition: TER.h:112
ripple::sfIssuer
const SF_ACCOUNT sfIssuer
ripple::ReadView::rules
virtual Rules const & rules() const =0
Returns the tx processing rules.
ripple::STObject::isFieldPresent
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:428
ripple::base_uint< 256 >::fromVoid
static base_uint fromVoid(void const *data)
Definition: base_uint.h:312
ripple::tecNO_ISSUER
@ tecNO_ISSUER
Definition: TER.h:266
ripple::Transactor::mPriorBalance
XRPAmount mPriorBalance
Definition: Transactor.h:92
ripple::tecNO_PERMISSION
@ tecNO_PERMISSION
Definition: TER.h:272
ripple::tecMAX_SEQUENCE_REACHED
@ tecMAX_SEQUENCE_REACHED
Definition: TER.h:287
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
std::memcpy
T memcpy(T... args)
ripple::sfNFTokenTaxon
const SF_UINT32 sfNFTokenTaxon
ripple::sfAccount
const SF_ACCOUNT sfAccount
ripple::InnerObjectFormats::getInstance
static InnerObjectFormats const & getInstance()
Definition: InnerObjectFormats.cpp:65
ripple::temMALFORMED
@ temMALFORMED
Definition: TER.h:85
ripple::tagged_integer
A type-safe wrap around standard integral types.
Definition: tagged_integer.h:44
ripple::PreflightContext::tx
STTx const & tx
Definition: Transactor.h:35
ripple::PreflightContext
State information when preflighting a tx.
Definition: Transactor.h:31
ripple::nft::cipheredTaxon
Taxon cipheredTaxon(std::uint32_t tokenSeq, Taxon taxon)
Definition: NFTokenUtils.h:144
ripple::PreflightContext::rules
const Rules rules
Definition: Transactor.h:36
ripple::tesSUCCESS
@ tesSUCCESS
Definition: TER.h:222
std::array::data
T data(T... args)
ripple::Transactor::account_
const AccountID account_
Definition: Transactor.h:91
ripple::temBAD_NFTOKEN_TRANSFER_FEE
@ temBAD_NFTOKEN_TRANSFER_FEE
Definition: TER.h:125
ripple::nft::insertToken
TER insertToken(ApplyView &view, AccountID owner, STObject &&nft)
Insert the token in the owner's token directory.
Definition: NFTokenUtils.cpp:243
ripple::ApplyContext::tx
STTx const & tx
Definition: ApplyContext.h:48
ripple::NotTEC
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:528
ripple::root
Number root(Number f, unsigned d)
Definition: Number.cpp:624
ripple::tfNFTokenMintMask
constexpr const std::uint32_t tfNFTokenMintMask
Definition: TxFlags.h:148