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 
163  // Get the unique sequence number for this token:
164  std::uint32_t const tokenSeq = (*root)[~sfMintedNFTokens].value_or(0);
165  {
166  std::uint32_t const nextTokenSeq = tokenSeq + 1;
167  if (nextTokenSeq < tokenSeq)
169 
170  (*root)[sfMintedNFTokens] = nextTokenSeq;
171  }
172  ctx_.view().update(root);
173  return tokenSeq;
174  }();
175 
176  if (!tokenSeq.has_value())
177  return (tokenSeq.error());
178 
179  std::uint32_t const ownerCountBefore =
180  view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
181 
182  // Assemble the new NFToken.
183  SOTemplate const* nfTokenTemplate =
185 
186  if (nfTokenTemplate == nullptr)
187  // Should never happen.
188  return tecINTERNAL;
189 
190  STObject newToken(
191  *nfTokenTemplate,
192  sfNFToken,
193  [this, &issuer, &tokenSeq](STObject& object) {
194  object.setFieldH256(
195  sfNFTokenID,
197  static_cast<std::uint16_t>(ctx_.tx.getFlags() & 0x0000FFFF),
198  ctx_.tx[~sfTransferFee].value_or(0),
199  issuer,
201  tokenSeq.value()));
202 
203  if (auto const uri = ctx_.tx[~sfURI])
204  object.setFieldVL(sfURI, *uri);
205  });
206 
207  if (TER const ret =
208  nft::insertToken(ctx_.view(), account_, std::move(newToken));
209  ret != tesSUCCESS)
210  return ret;
211 
212  // Only check the reserve if the owner count actually changed. This
213  // allows NFTs to be added to the page (and burn fees) without
214  // requiring the reserve to be met each time. The reserve is
215  // only managed when a new NFT page is added.
216  if (auto const ownerCountAfter =
217  view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
218  ownerCountAfter > ownerCountBefore)
219  {
220  if (auto const reserve = view().fees().accountReserve(ownerCountAfter);
221  mPriorBalance < reserve)
223  }
224  return tesSUCCESS;
225 }
226 
227 } // 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::preflight2
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:109
ripple::tfTransferable
constexpr const std::uint32_t tfTransferable
Definition: TxFlags.h:128
ripple::Rules::enabled
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:81
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:594
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::tfNFTokenMintOldMask
constexpr const std::uint32_t tfNFTokenMintOldMask
Definition: TxFlags.h:143
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::nft::toTaxon
Taxon toTaxon(std::uint32_t i)
Definition: NFTokenUtils.h:40
ripple::base_uint::data
pointer data()
Definition: base_uint.h:121
ripple::preflight1
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:57
ripple::sfTransferFee
const SF_UINT16 sfTransferFee
ripple::base_uint::size
constexpr static std::size_t size()
Definition: base_uint.h:518
ripple::base_uint
Integers of any length that is a multiple of 32-bits.
Definition: base_uint.h:81
ripple::NFTokenMint::preflight
static NotTEC preflight(PreflightContext const &ctx)
Definition: NFTokenMint.cpp:35
ripple::temINVALID_FLAG
@ temINVALID_FLAG
Definition: TER.h:106
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:274
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:109
ripple::sfIssuer
const SF_ACCOUNT sfIssuer
ripple::base_uint< 256 >::fromVoid
static base_uint fromVoid(void const *data)
Definition: base_uint.h:311
ripple::tecNO_ISSUER
@ tecNO_ISSUER
Definition: TER.h:263
ripple::Transactor::mPriorBalance
XRPAmount mPriorBalance
Definition: Transactor.h:92
ripple::tecNO_PERMISSION
@ tecNO_PERMISSION
Definition: TER.h:269
ripple::tecMAX_SEQUENCE_REACHED
@ tecMAX_SEQUENCE_REACHED
Definition: TER.h:284
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
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:82
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:219
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:122
ripple::nft::insertToken
TER insertToken(ApplyView &view, AccountID owner, STObject &&nft)
Insert the token in the owner's token directory.
Definition: NFTokenUtils.cpp:240
ripple::ApplyContext::tx
STTx const & tx
Definition: ApplyContext.h:48
ripple::NotTEC
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:525
ripple::root
Number root(Number f, unsigned d)
Definition: Number.cpp:624
ripple::tfNFTokenMintMask
constexpr const std::uint32_t tfNFTokenMintMask
Definition: TxFlags.h:146