mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
* clang-format * levelization * clang-format * update workflow (#172) * update workflow * Update build-in-docker.yml * fix from `clang-format` * Update Enum.h
850 lines
24 KiB
C++
850 lines
24 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2012-2016 Ripple Labs Inc.
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
//==============================================================================
|
|
|
|
#include <ripple/app/tx/impl/InvariantCheck.h>
|
|
|
|
#include <ripple/app/tx/impl/Import.h>
|
|
#include <ripple/app/tx/impl/details/NFTokenUtils.h>
|
|
#include <ripple/basics/FeeUnits.h>
|
|
#include <ripple/basics/Log.h>
|
|
#include <ripple/ledger/ReadView.h>
|
|
#include <ripple/protocol/Feature.h>
|
|
#include <ripple/protocol/STArray.h>
|
|
#include <ripple/protocol/SystemParameters.h>
|
|
#include <ripple/protocol/nftPageMask.h>
|
|
|
|
namespace ripple {
|
|
|
|
void
|
|
TransactionFeeCheck::visitEntry(
|
|
bool,
|
|
std::shared_ptr<SLE const> const&,
|
|
std::shared_ptr<SLE const> const&)
|
|
{
|
|
// nothing to do
|
|
}
|
|
|
|
bool
|
|
TransactionFeeCheck::finalize(
|
|
STTx const& tx,
|
|
TER const,
|
|
XRPAmount const fee,
|
|
ReadView const&,
|
|
beast::Journal const& j)
|
|
{
|
|
// We should never charge a negative fee
|
|
if (fee.drops() < 0)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: fee paid was negative: "
|
|
<< fee.drops();
|
|
return false;
|
|
}
|
|
|
|
// We should never charge a fee that's greater than or equal to the
|
|
// entire XRP supply.
|
|
if (fee >= INITIAL_XRP)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: fee paid exceeds system limit: "
|
|
<< fee.drops();
|
|
return false;
|
|
}
|
|
|
|
// We should never charge more for a transaction than the transaction
|
|
// authorizes. It's possible to charge less in some circumstances.
|
|
if (fee > tx.getFieldAmount(sfFee).xrp())
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: fee paid is " << fee.drops()
|
|
<< " exceeds fee specified in transaction.";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void
|
|
XRPNotCreated::visitEntry(
|
|
bool isDelete,
|
|
std::shared_ptr<SLE const> const& before,
|
|
std::shared_ptr<SLE const> const& after)
|
|
{
|
|
/* We go through all modified ledger entries, looking only at account roots,
|
|
* escrow payments, and payment channels. We remove from the total any
|
|
* previous XRP values and add to the total any new XRP values. The net
|
|
* balance of a payment channel is computed from two fields (amount and
|
|
* balance) and deletions are ignored for paychan and escrow because the
|
|
* amount fields have not been adjusted for those in the case of deletion.
|
|
*/
|
|
if (before)
|
|
{
|
|
switch (before->getType())
|
|
{
|
|
case ltACCOUNT_ROOT:
|
|
drops_ -= (*before)[sfBalance].xrp().drops();
|
|
break;
|
|
case ltPAYCHAN:
|
|
if (isXRP((*before)[sfAmount]))
|
|
drops_ -= ((*before)[sfAmount] - (*before)[sfBalance])
|
|
.xrp()
|
|
.drops();
|
|
break;
|
|
case ltESCROW:
|
|
if (isXRP((*before)[sfAmount]))
|
|
drops_ -= (*before)[sfAmount].xrp().drops();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (after)
|
|
{
|
|
switch (after->getType())
|
|
{
|
|
case ltACCOUNT_ROOT:
|
|
drops_ += (*after)[sfBalance].xrp().drops();
|
|
break;
|
|
case ltPAYCHAN:
|
|
if (!isDelete && isXRP((*after)[sfAmount]))
|
|
drops_ += ((*after)[sfAmount] - (*after)[sfBalance])
|
|
.xrp()
|
|
.drops();
|
|
break;
|
|
case ltESCROW:
|
|
if (!isDelete && isXRP((*after)[sfAmount]))
|
|
drops_ += (*after)[sfAmount].xrp().drops();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!before && after->getType() == ltACCOUNT_ROOT)
|
|
accountsCreated_++;
|
|
}
|
|
|
|
bool
|
|
XRPNotCreated::finalize(
|
|
STTx const& tx,
|
|
TER const res,
|
|
XRPAmount const fee,
|
|
ReadView const& view,
|
|
beast::Journal const& j)
|
|
{
|
|
auto const tt = tx.getTxnType();
|
|
|
|
if (tt == ttAMENDMENT &&
|
|
tx.getFieldH256(sfAmendment) == featureXahauGenesis)
|
|
return true;
|
|
|
|
if (view.rules().enabled(featureImport) && tt == ttIMPORT &&
|
|
res == tesSUCCESS)
|
|
{
|
|
// different rules for ttIMPORT
|
|
auto const [inner, meta] = Import::getInnerTxn(tx, j);
|
|
if (!inner || !meta)
|
|
return false;
|
|
|
|
auto const result = meta->getFieldU8(sfTransactionResult);
|
|
|
|
XRPAmount maxDropsAdded = result == tesSUCCESS ||
|
|
(result >= tecCLAIM && result <= tecLAST_POSSIBLE_ENTRY)
|
|
? inner->getFieldAmount(sfFee).xrp() // burned in PoB
|
|
: beast::zero; // if the txn didnt burn a fee we add nothing
|
|
|
|
if (accountsCreated_ == 1)
|
|
maxDropsAdded += Import::computeStartingBonus(view);
|
|
|
|
JLOG(j.trace()) << "Invariant XRPNotCreated Import: "
|
|
<< "maxDropsAdded: " << maxDropsAdded
|
|
<< " fee.drops(): " << fee.drops()
|
|
<< " drops_: " << drops_
|
|
<< " <= maxDropsAdded - fee.drops(): "
|
|
<< maxDropsAdded - fee.drops();
|
|
|
|
// We should never allow more than the max supply in totalCoins.
|
|
XRPAmount const newTotal = view.info().drops + maxDropsAdded;
|
|
if (newTotal > INITIAL_XRP)
|
|
{
|
|
JLOG(j.fatal())
|
|
<< "Invariant failed Import: total coins paid exceeds "
|
|
<< "system limit: " << INITIAL_XRP
|
|
<< "maxDropsAdded: " << maxDropsAdded
|
|
<< " fee.drops(): " << fee.drops()
|
|
<< " info().drops: " << view.info().drops
|
|
<< " newTotal: " << newTotal;
|
|
return false;
|
|
}
|
|
|
|
bool const passed = (drops_ <= maxDropsAdded.drops() - fee.drops());
|
|
if (!passed)
|
|
{
|
|
JLOG(j.trace()) << "XRPNotCreated failed.";
|
|
}
|
|
|
|
return passed;
|
|
}
|
|
|
|
if (view.rules().enabled(featureXahauGenesis) && tt == ttGENESIS_MINT &&
|
|
res == tesSUCCESS)
|
|
{
|
|
// different rules for ttGENESIS_MINT
|
|
auto const& dests = tx.getFieldArray(sfGenesisMints);
|
|
XRPAmount dropsAdded{beast::zero};
|
|
for (auto const& dest : dests)
|
|
dropsAdded += dest.getFieldAmount(sfAmount).xrp();
|
|
|
|
JLOG(j.trace()) << "Invariant XRPNotCreated GenesisMint: "
|
|
<< "dropsAdded: " << dropsAdded
|
|
<< " fee.drops(): " << fee.drops()
|
|
<< " drops_: " << drops_
|
|
<< " dropsAdded - fee.drops(): "
|
|
<< dropsAdded - fee.drops();
|
|
|
|
int64_t drops = dropsAdded.drops() - fee.drops();
|
|
|
|
// catch any overflow or funny business
|
|
if (drops > dropsAdded.drops())
|
|
return false;
|
|
|
|
// We should never allow more than the max supply in totalCoins.
|
|
XRPAmount const newTotal = view.info().drops + dropsAdded;
|
|
if (newTotal > INITIAL_XRP)
|
|
{
|
|
JLOG(j.fatal())
|
|
<< "Invariant failed GenesisMint: total coins exceeds "
|
|
<< "system limit: " << INITIAL_XRP
|
|
<< "dropsAdded: " << dropsAdded
|
|
<< " fee.drops(): " << fee.drops()
|
|
<< " info().drops: " << view.info().drops
|
|
<< " newTotal: " << newTotal;
|
|
return false;
|
|
}
|
|
|
|
return drops_ == drops;
|
|
}
|
|
|
|
// The net change should never be positive, as this would mean that the
|
|
// transaction created XRP out of thin air. That's not possible.
|
|
if (drops_ > 0)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: XRP net change was positive: "
|
|
<< drops_;
|
|
return false;
|
|
}
|
|
|
|
// The negative of the net change should be equal to actual fee charged.
|
|
if (-drops_ != fee.drops())
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: XRP net change of " << drops_
|
|
<< " doesn't match fee " << fee.drops();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void
|
|
XRPBalanceChecks::visitEntry(
|
|
bool,
|
|
std::shared_ptr<SLE const> const& before,
|
|
std::shared_ptr<SLE const> const& after)
|
|
{
|
|
auto isBad = [](STAmount const& balance) {
|
|
if (!balance.native())
|
|
return true;
|
|
|
|
auto const drops = balance.xrp();
|
|
|
|
// Can't have more than the number of drops instantiated
|
|
// in the genesis ledger.
|
|
if (drops > INITIAL_XRP)
|
|
return true;
|
|
|
|
// Can't have a negative balance (0 is OK)
|
|
if (drops < XRPAmount{0})
|
|
return true;
|
|
|
|
return false;
|
|
};
|
|
|
|
if (before && before->getType() == ltACCOUNT_ROOT)
|
|
bad_ |= isBad((*before)[sfBalance]);
|
|
|
|
if (after && after->getType() == ltACCOUNT_ROOT)
|
|
bad_ |= isBad((*after)[sfBalance]);
|
|
}
|
|
|
|
bool
|
|
XRPBalanceChecks::finalize(
|
|
STTx const&,
|
|
TER const,
|
|
XRPAmount const,
|
|
ReadView const&,
|
|
beast::Journal const& j)
|
|
{
|
|
if (bad_)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: incorrect account XRP balance";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void
|
|
NoBadOffers::visitEntry(
|
|
bool isDelete,
|
|
std::shared_ptr<SLE const> const& before,
|
|
std::shared_ptr<SLE const> const& after)
|
|
{
|
|
auto isBad = [](STAmount const& pays, STAmount const& gets) {
|
|
// An offer should never be negative
|
|
if (pays < beast::zero)
|
|
return true;
|
|
|
|
if (gets < beast::zero)
|
|
return true;
|
|
|
|
// Can't have an XRP to XRP offer:
|
|
return pays.native() && gets.native();
|
|
};
|
|
|
|
if (before && before->getType() == ltOFFER)
|
|
bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]);
|
|
|
|
if (after && after->getType() == ltOFFER)
|
|
bad_ |= isBad((*after)[sfTakerPays], (*after)[sfTakerGets]);
|
|
}
|
|
|
|
bool
|
|
NoBadOffers::finalize(
|
|
STTx const&,
|
|
TER const,
|
|
XRPAmount const,
|
|
ReadView const&,
|
|
beast::Journal const& j)
|
|
{
|
|
if (bad_)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: offer with a bad amount";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void
|
|
NoZeroEscrow::visitEntry(
|
|
bool isDelete,
|
|
std::shared_ptr<SLE const> const& before,
|
|
std::shared_ptr<SLE const> const& after)
|
|
{
|
|
auto isBad = [](STAmount const& amount) {
|
|
if (!amount.native())
|
|
return true;
|
|
|
|
if (amount.xrp() <= XRPAmount{0})
|
|
return true;
|
|
|
|
if (amount.xrp() >= INITIAL_XRP)
|
|
return true;
|
|
|
|
return false;
|
|
};
|
|
|
|
if (before && before->getType() == ltESCROW)
|
|
bad_ |= isBad((*before)[sfAmount]);
|
|
|
|
if (after && after->getType() == ltESCROW)
|
|
bad_ |= isBad((*after)[sfAmount]);
|
|
}
|
|
|
|
bool
|
|
NoZeroEscrow::finalize(
|
|
STTx const& txn,
|
|
TER const,
|
|
XRPAmount const,
|
|
ReadView const& rv,
|
|
beast::Journal const& j)
|
|
{
|
|
// bypass this invariant check for IOU escrows
|
|
if (bad_ && rv.rules().enabled(featurePaychanAndEscrowForTokens) &&
|
|
txn.isFieldPresent(sfTransactionType))
|
|
{
|
|
uint16_t const tt = txn.getFieldU16(sfTransactionType);
|
|
if (tt == ttESCROW_CANCEL || tt == ttESCROW_FINISH)
|
|
return true;
|
|
|
|
if (txn.isFieldPresent(sfAmount) &&
|
|
!isXRP(txn.getFieldAmount(sfAmount)))
|
|
return true;
|
|
}
|
|
|
|
if (bad_)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: escrow specifies invalid amount";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void
|
|
AccountRootsNotDeleted::visitEntry(
|
|
bool isDelete,
|
|
std::shared_ptr<SLE const> const& before,
|
|
std::shared_ptr<SLE const> const&)
|
|
{
|
|
if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
|
|
accountsDeleted_++;
|
|
}
|
|
|
|
bool
|
|
AccountRootsNotDeleted::finalize(
|
|
STTx const& tx,
|
|
TER const result,
|
|
XRPAmount const,
|
|
ReadView const&,
|
|
beast::Journal const& j)
|
|
{
|
|
if (tx.getTxnType() == ttACCOUNT_DELETE && result == tesSUCCESS)
|
|
{
|
|
if (accountsDeleted_ == 1)
|
|
return true;
|
|
|
|
if (accountsDeleted_ == 0)
|
|
JLOG(j.fatal()) << "Invariant failed: account deletion "
|
|
"succeeded without deleting an account";
|
|
else
|
|
JLOG(j.fatal()) << "Invariant failed: account deletion "
|
|
"succeeded but deleted multiple accounts!";
|
|
return false;
|
|
}
|
|
|
|
if (accountsDeleted_ == 0)
|
|
return true;
|
|
|
|
JLOG(j.fatal()) << "Invariant failed: an account root was deleted";
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void
|
|
LedgerEntryTypesMatch::visitEntry(
|
|
bool,
|
|
std::shared_ptr<SLE const> const& before,
|
|
std::shared_ptr<SLE const> const& after)
|
|
{
|
|
if (before && after && before->getType() != after->getType())
|
|
typeMismatch_ = true;
|
|
|
|
if (after)
|
|
{
|
|
switch (after->getType())
|
|
{
|
|
case ltACCOUNT_ROOT:
|
|
case ltDIR_NODE:
|
|
case ltRIPPLE_STATE:
|
|
case ltTICKET:
|
|
case ltSIGNER_LIST:
|
|
case ltOFFER:
|
|
case ltLEDGER_HASHES:
|
|
case ltAMENDMENTS:
|
|
case ltFEE_SETTINGS:
|
|
case ltESCROW:
|
|
case ltPAYCHAN:
|
|
case ltCHECK:
|
|
case ltDEPOSIT_PREAUTH:
|
|
case ltNEGATIVE_UNL:
|
|
case ltHOOK:
|
|
case ltHOOK_DEFINITION:
|
|
case ltHOOK_STATE:
|
|
case ltEMITTED_TXN:
|
|
case ltNFTOKEN_PAGE:
|
|
case ltNFTOKEN_OFFER:
|
|
case ltURI_TOKEN:
|
|
case ltIMPORT_VLSEQ:
|
|
case ltUNL_REPORT:
|
|
break;
|
|
default:
|
|
invalidTypeAdded_ = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
LedgerEntryTypesMatch::finalize(
|
|
STTx const&,
|
|
TER const,
|
|
XRPAmount const,
|
|
ReadView const&,
|
|
beast::Journal const& j)
|
|
{
|
|
if ((!typeMismatch_) && (!invalidTypeAdded_))
|
|
return true;
|
|
|
|
if (typeMismatch_)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: ledger entry type mismatch";
|
|
}
|
|
|
|
if (invalidTypeAdded_)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: invalid ledger entry type added";
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void
|
|
NoXRPTrustLines::visitEntry(
|
|
bool,
|
|
std::shared_ptr<SLE const> const&,
|
|
std::shared_ptr<SLE const> const& after)
|
|
{
|
|
if (after && after->getType() == ltRIPPLE_STATE)
|
|
{
|
|
// checking the issue directly here instead of
|
|
// relying on .native() just in case native somehow
|
|
// were systematically incorrect
|
|
xrpTrustLine_ =
|
|
after->getFieldAmount(sfLowLimit).issue() == xrpIssue() ||
|
|
after->getFieldAmount(sfHighLimit).issue() == xrpIssue();
|
|
}
|
|
}
|
|
|
|
bool
|
|
NoXRPTrustLines::finalize(
|
|
STTx const&,
|
|
TER const,
|
|
XRPAmount const,
|
|
ReadView const&,
|
|
beast::Journal const& j)
|
|
{
|
|
if (!xrpTrustLine_)
|
|
return true;
|
|
|
|
JLOG(j.fatal()) << "Invariant failed: an XRP trust line was created";
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void
|
|
ValidNewAccountRoot::visitEntry(
|
|
bool,
|
|
std::shared_ptr<SLE const> const& before,
|
|
std::shared_ptr<SLE const> const& after)
|
|
{
|
|
if (!before && after->getType() == ltACCOUNT_ROOT)
|
|
{
|
|
accountsCreated_++;
|
|
accountSeq_ = (*after)[sfSequence];
|
|
}
|
|
}
|
|
|
|
bool
|
|
ValidNewAccountRoot::finalize(
|
|
STTx const& tx,
|
|
TER const result,
|
|
XRPAmount const,
|
|
ReadView const& view,
|
|
beast::Journal const& j)
|
|
{
|
|
if (accountsCreated_ == 0)
|
|
return true;
|
|
|
|
auto tt = tx.getTxnType();
|
|
|
|
if (tt == ttAMENDMENT &&
|
|
tx.getFieldH256(sfAmendment) == featureXahauGenesis)
|
|
return true;
|
|
|
|
if (accountsCreated_ > 1 && tt != ttGENESIS_MINT)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: multiple accounts "
|
|
"created in a single transaction";
|
|
return false;
|
|
}
|
|
|
|
if ((tt == ttPAYMENT || tt == ttIMPORT || tt == ttGENESIS_MINT) &&
|
|
result == tesSUCCESS)
|
|
{
|
|
std::uint32_t const startingSeq{
|
|
view.rules().enabled(featureXahauGenesis)
|
|
? view.info().parentCloseTime.time_since_epoch().count()
|
|
: view.rules().enabled(featureDeletableAccounts) ? view.seq()
|
|
: 1};
|
|
|
|
if (accountSeq_ != startingSeq)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: account created with "
|
|
"wrong starting sequence number";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
JLOG(j.fatal()) << "Invariant failed: account root created "
|
|
"by a non-Payment or by an unsuccessful transaction";
|
|
return false;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void
|
|
ValidNFTokenPage::visitEntry(
|
|
bool isDelete,
|
|
std::shared_ptr<SLE const> const& before,
|
|
std::shared_ptr<SLE const> const& after)
|
|
{
|
|
static constexpr uint256 const& pageBits = nft::pageMask;
|
|
static constexpr uint256 const accountBits = ~pageBits;
|
|
|
|
auto check = [this, isDelete](std::shared_ptr<SLE const> const& sle) {
|
|
uint256 const account = sle->key() & accountBits;
|
|
uint256 const hiLimit = sle->key() & pageBits;
|
|
std::optional<uint256> const prev = (*sle)[~sfPreviousPageMin];
|
|
|
|
// Make sure that any page links...
|
|
// 1. Are properly associated with the owning account and
|
|
// 2. The page is correctly ordered between links.
|
|
if (prev)
|
|
{
|
|
if (account != (*prev & accountBits))
|
|
badLink_ = true;
|
|
|
|
if (hiLimit <= (*prev & pageBits))
|
|
badLink_ = true;
|
|
}
|
|
|
|
if (auto const next = (*sle)[~sfNextPageMin])
|
|
{
|
|
if (account != (*next & accountBits))
|
|
badLink_ = true;
|
|
|
|
if (hiLimit >= (*next & pageBits))
|
|
badLink_ = true;
|
|
}
|
|
|
|
{
|
|
auto const& nftokens = sle->getFieldArray(sfNFTokens);
|
|
|
|
// An NFTokenPage should never contain too many tokens or be empty.
|
|
if (std::size_t const nftokenCount = nftokens.size();
|
|
(!isDelete && nftokenCount == 0) ||
|
|
nftokenCount > dirMaxTokensPerPage)
|
|
invalidSize_ = true;
|
|
|
|
// If prev is valid, use it to establish a lower bound for
|
|
// page entries. If prev is not valid the lower bound is zero.
|
|
uint256 const loLimit =
|
|
prev ? *prev & pageBits : uint256(beast::zero);
|
|
|
|
// Also verify that all NFTokenIDs in the page are sorted.
|
|
uint256 loCmp = loLimit;
|
|
for (auto const& obj : nftokens)
|
|
{
|
|
uint256 const tokenID = obj[sfNFTokenID];
|
|
if (!nft::compareTokens(loCmp, tokenID))
|
|
badSort_ = true;
|
|
loCmp = tokenID;
|
|
|
|
// None of the NFTs on this page should belong on lower or
|
|
// higher pages.
|
|
if (uint256 const tokenPageBits = tokenID & pageBits;
|
|
tokenPageBits < loLimit || tokenPageBits >= hiLimit)
|
|
badEntry_ = true;
|
|
|
|
if (auto uri = obj[~sfURI]; uri && uri->empty())
|
|
badURI_ = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
if (before && before->getType() == ltNFTOKEN_PAGE)
|
|
check(before);
|
|
|
|
if (after && after->getType() == ltNFTOKEN_PAGE)
|
|
check(after);
|
|
}
|
|
|
|
bool
|
|
ValidNFTokenPage::finalize(
|
|
STTx const& tx,
|
|
TER const result,
|
|
XRPAmount const,
|
|
ReadView const& view,
|
|
beast::Journal const& j)
|
|
{
|
|
if (badLink_)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked.";
|
|
return false;
|
|
}
|
|
|
|
if (badEntry_)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page.";
|
|
return false;
|
|
}
|
|
|
|
if (badSort_)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted.";
|
|
return false;
|
|
}
|
|
|
|
if (badURI_)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI.";
|
|
return false;
|
|
}
|
|
|
|
if (invalidSize_)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size.";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
void
|
|
NFTokenCountTracking::visitEntry(
|
|
bool,
|
|
std::shared_ptr<SLE const> const& before,
|
|
std::shared_ptr<SLE const> const& after)
|
|
{
|
|
if (before && before->getType() == ltACCOUNT_ROOT)
|
|
{
|
|
beforeMintedTotal += (*before)[~sfMintedNFTokens].value_or(0);
|
|
beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0);
|
|
}
|
|
|
|
if (after && after->getType() == ltACCOUNT_ROOT)
|
|
{
|
|
afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0);
|
|
afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0);
|
|
}
|
|
}
|
|
|
|
bool
|
|
NFTokenCountTracking::finalize(
|
|
STTx const& tx,
|
|
TER const result,
|
|
XRPAmount const,
|
|
ReadView const& view,
|
|
beast::Journal const& j)
|
|
{
|
|
if (TxType const txType = tx.getTxnType();
|
|
txType != ttNFTOKEN_MINT && txType != ttNFTOKEN_BURN)
|
|
{
|
|
if (beforeMintedTotal != afterMintedTotal)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: the number of minted tokens "
|
|
"changed without a mint transaction!";
|
|
return false;
|
|
}
|
|
|
|
if (beforeBurnedTotal != afterBurnedTotal)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: the number of burned tokens "
|
|
"changed without a burn transaction!";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (tx.getTxnType() == ttNFTOKEN_MINT)
|
|
{
|
|
if (result == tesSUCCESS && beforeMintedTotal >= afterMintedTotal)
|
|
{
|
|
JLOG(j.fatal())
|
|
<< "Invariant failed: successful minting didn't increase "
|
|
"the number of minted tokens.";
|
|
return false;
|
|
}
|
|
|
|
if (result != tesSUCCESS && beforeMintedTotal != afterMintedTotal)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: failed minting changed the "
|
|
"number of minted tokens.";
|
|
return false;
|
|
}
|
|
|
|
if (beforeBurnedTotal != afterBurnedTotal)
|
|
{
|
|
JLOG(j.fatal())
|
|
<< "Invariant failed: minting changed the number of "
|
|
"burned tokens.";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (tx.getTxnType() == ttNFTOKEN_BURN)
|
|
{
|
|
if (result == tesSUCCESS)
|
|
{
|
|
if (beforeBurnedTotal >= afterBurnedTotal)
|
|
{
|
|
JLOG(j.fatal())
|
|
<< "Invariant failed: successful burning didn't increase "
|
|
"the number of burned tokens.";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (result != tesSUCCESS && beforeBurnedTotal != afterBurnedTotal)
|
|
{
|
|
JLOG(j.fatal()) << "Invariant failed: failed burning changed the "
|
|
"number of burned tokens.";
|
|
return false;
|
|
}
|
|
|
|
if (beforeMintedTotal != afterMintedTotal)
|
|
{
|
|
JLOG(j.fatal())
|
|
<< "Invariant failed: burning changed the number of "
|
|
"minted tokens.";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace ripple
|