text: //------------------------------------------------------------------------------
  /*
    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/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:
                  drops_ -=
                      ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops();
                  break;
              case ltESCROW:
                  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)
                      drops_ += ((*after)[sfAmount] - (*after)[sfBalance])
                                    .xrp()
                                    .drops();
                  break;
              case ltESCROW:
                  if (!isDelete)
                      drops_ += (*after)[sfAmount].xrp().drops();
                  break;
              default:
                  break;
          }
      }
  }
  
  bool
  XRPNotCreated::finalize(
      STTx const&,
      TER const,
      XRPAmount const fee,
      ReadView const&,
      beast::Journal const& j)
  {
      // 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&,
      TER const,
      XRPAmount const,
      ReadView const&,
      beast::Journal const& j)
  {
      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 ltNFTOKEN_PAGE:
              case ltNFTOKEN_OFFER:
                  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;
  
      if (accountsCreated_ > 1)
      {
          JLOG(j.fatal()) << "Invariant failed: multiple accounts "
                             "created in a single transaction";
          return false;
      }
  
      // From this point on we know exactly one account was created.
      if (tx.getTxnType() == ttPAYMENT && result == tesSUCCESS)
      {
          std::uint32_t const startingSeq{
              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,
      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](std::shared_ptr<SLE const> const& sle) {
          auto const account = sle->key() & accountBits;
          auto const limit = sle->key() & pageBits;
  
          if (auto const prev = (*sle)[~sfPreviousPageMin])
          {
              if (account != (*prev & accountBits))
                  badLink_ = true;
  
              if (limit <= (*prev & pageBits))
                  badLink_ = true;
          }
  
          if (auto const next = (*sle)[~sfNextPageMin])
          {
              if (account != (*next & accountBits))
                  badLink_ = true;
  
              if (limit >= (*next & pageBits))
                  badLink_ = true;
          }
  
          for (auto const& obj : sle->getFieldArray(sfNFTokens))
          {
              if ((obj[sfNFTokenID] & pageBits) >= limit)
                  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 (badURI_)
      {
          JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI.";
          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
  