diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index bd98a5d4195..00ce8999007 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -59,6 +59,7 @@ else () src/ripple/basics/impl/contract.cpp src/ripple/basics/impl/CountedObject.cpp src/ripple/basics/impl/FileUtilities.cpp + src/ripple/basics/impl/IOUAmount.cpp src/ripple/basics/impl/Log.cpp src/ripple/basics/impl/strHex.cpp src/ripple/basics/impl/StringUtilities.cpp @@ -85,7 +86,6 @@ else () src/ripple/protocol/impl/ErrorCodes.cpp src/ripple/protocol/impl/Feature.cpp src/ripple/protocol/impl/HashPrefix.cpp - src/ripple/protocol/impl/IOUAmount.cpp src/ripple/protocol/impl/Indexes.cpp src/ripple/protocol/impl/InnerObjectFormats.cpp src/ripple/protocol/impl/Issue.cpp @@ -159,6 +159,7 @@ install ( src/ripple/basics/Buffer.h src/ripple/basics/CountedObject.h src/ripple/basics/FileUtilities.h + src/ripple/basics/IOUAmount.h src/ripple/basics/LocalValue.h src/ripple/basics/Log.h src/ripple/basics/safe_cast.h @@ -166,10 +167,12 @@ install ( src/ripple/basics/StringUtilities.h src/ripple/basics/ToString.h src/ripple/basics/UnorderedContainers.h + src/ripple/basics/XRPAmount.h src/ripple/basics/algorithm.h src/ripple/basics/base_uint.h src/ripple/basics/chrono.h src/ripple/basics/contract.h + src/ripple/basics/FeeUnits.h src/ripple/basics/hardened_hash.h src/ripple/basics/strHex.h DESTINATION include/ripple/basics) @@ -209,7 +212,6 @@ install ( src/ripple/protocol/ErrorCodes.h src/ripple/protocol/Feature.h src/ripple/protocol/HashPrefix.h - src/ripple/protocol/IOUAmount.h src/ripple/protocol/Indexes.h src/ripple/protocol/InnerObjectFormats.h src/ripple/protocol/Issue.h @@ -247,7 +249,6 @@ install ( src/ripple/protocol/TxFlags.h src/ripple/protocol/TxFormats.h src/ripple/protocol/UintTypes.h - src/ripple/protocol/XRPAmount.h src/ripple/protocol/digest.h src/ripple/protocol/jss.h src/ripple/protocol/tokens.h @@ -728,6 +729,7 @@ else () src/test/app/DepositAuth_test.cpp src/test/app/Discrepancy_test.cpp src/test/app/Escrow_test.cpp + src/test/app/FeeVote_test.cpp src/test/app/Flow_test.cpp src/test/app/Freeze_test.cpp src/test/app/HashRouter_test.cpp @@ -766,15 +768,18 @@ else () src/test/basics/Buffer_test.cpp src/test/basics/DetectCrash_test.cpp src/test/basics/FileUtilities_test.cpp + src/test/basics/IOUAmount_test.cpp src/test/basics/KeyCache_test.cpp src/test/basics/PerfLog_test.cpp src/test/basics/RangeSet_test.cpp src/test/basics/Slice_test.cpp src/test/basics/StringUtilities_test.cpp src/test/basics/TaggedCache_test.cpp + src/test/basics/XRPAmount_test.cpp src/test/basics/base64_test.cpp src/test/basics/base_uint_test.cpp src/test/basics/contract_test.cpp + src/test/basics/FeeUnits_test.cpp src/test/basics/hardened_hash_test.cpp src/test/basics/mulDiv_test.cpp src/test/basics/qalloc_test.cpp @@ -929,7 +934,6 @@ else () nounity, test sources: subdir: protocol #]===============================] - src/test/protocol/IOUAmount_test.cpp src/test/protocol/InnerObjectFormats_test.cpp src/test/protocol/Issue_test.cpp src/test/protocol/PublicKey_test.cpp @@ -942,7 +946,6 @@ else () src/test/protocol/SecretKey_test.cpp src/test/protocol/Seed_test.cpp src/test/protocol/TER_test.cpp - src/test/protocol/XRPAmount_test.cpp src/test/protocol/digest_test.cpp src/test/protocol/types_test.cpp #[===============================[ diff --git a/src/ripple/app/ledger/Ledger.cpp b/src/ripple/app/ledger/Ledger.cpp index 6ac6eef5841..6125149796f 100644 --- a/src/ripple/app/ledger/Ledger.cpp +++ b/src/ripple/app/ledger/Ledger.cpp @@ -190,7 +190,7 @@ Ledger::Ledger ( , rules_{config.features} { info_.seq = 1; - info_.drops = SYSTEM_CURRENCY_START; + info_.drops = INITIAL_XRP; info_.closeTimeResolution = ledgerDefaultTimeResolution; static auto const id = calcAccountID( diff --git a/src/ripple/app/misc/FeeVote.h b/src/ripple/app/misc/FeeVote.h index 808fd0581c6..77d57e95324 100644 --- a/src/ripple/app/misc/FeeVote.h +++ b/src/ripple/app/misc/FeeVote.h @@ -40,16 +40,16 @@ class FeeVote struct Setup { /** The cost of a reference transaction in drops. */ - std::uint64_t reference_fee = 10; + XRPAmount reference_fee{ 10 }; /** The cost of a reference transaction in fee units. */ - std::uint32_t const reference_fee_units = 10; + static constexpr FeeUnit32 reference_fee_units{ 10 }; /** The account reserve requirement in drops. */ - std::uint64_t account_reserve = 20 * SYSTEM_CURRENCY_PARTS; + XRPAmount account_reserve{ 20 * DROPS_PER_XRP }; /** The per-owned item reserve requirement in drops. */ - std::uint64_t owner_reserve = 5 * SYSTEM_CURRENCY_PARTS; + XRPAmount owner_reserve{ 5 * DROPS_PER_XRP }; }; virtual ~FeeVote () = default; diff --git a/src/ripple/app/misc/FeeVoteImpl.cpp b/src/ripple/app/misc/FeeVoteImpl.cpp index cce06c81d62..dec8ba1bebb 100644 --- a/src/ripple/app/misc/FeeVoteImpl.cpp +++ b/src/ripple/app/misc/FeeVoteImpl.cpp @@ -28,17 +28,16 @@ namespace ripple { namespace detail { -template -class VotableInteger +class VotableValue { private: - using map_type = std::map ; - Integer mCurrent; // The current setting - Integer mTarget; // The setting we want - map_type mVoteMap; + using value_type = XRPAmount; + value_type const mCurrent; // The current setting + value_type const mTarget; // The setting we want + std::map mVoteMap; public: - VotableInteger (Integer current, Integer target) + VotableValue (value_type current, value_type target) : mCurrent (current) , mTarget (target) { @@ -47,7 +46,7 @@ class VotableInteger } void - addVote(Integer vote) + addVote(value_type vote) { ++mVoteMap[vote]; } @@ -58,15 +57,15 @@ class VotableInteger addVote (mCurrent); } - Integer + value_type getVotes() const; }; -template -Integer -VotableInteger ::getVotes() const +auto +VotableValue::getVotes() const + -> value_type { - Integer ourVote = mCurrent; + value_type ourVote = mCurrent; int weight = 0; for (auto const& [key, val] : mVoteMap) { @@ -153,14 +152,17 @@ FeeVoteImpl::doVoting( // LCL must be flag ledger assert ((lastClosedLedger->info().seq % 256) == 0); - detail::VotableInteger baseFeeVote ( - lastClosedLedger->fees().base, target_.reference_fee); + detail::VotableValue baseFeeVote ( + lastClosedLedger->fees().base, + target_.reference_fee); - detail::VotableInteger baseReserveVote ( - lastClosedLedger->fees().accountReserve(0).drops(), target_.account_reserve); + detail::VotableValue baseReserveVote( + lastClosedLedger->fees().accountReserve(0), + target_.account_reserve); - detail::VotableInteger incReserveVote ( - lastClosedLedger->fees().increment, target_.owner_reserve); + detail::VotableValue incReserveVote ( + lastClosedLedger->fees().increment, + target_.owner_reserve); for (auto const& val : set) { @@ -168,7 +170,17 @@ FeeVoteImpl::doVoting( { if (val->isFieldPresent (sfBaseFee)) { - baseFeeVote.addVote (val->getFieldU64 (sfBaseFee)); + using xrptype = XRPAmount::value_type; + auto const vote = val->getFieldU64 (sfBaseFee); + if (vote <= std::numeric_limits::max() && + isLegalAmount(XRPAmount{unsafe_cast(vote)})) + baseFeeVote.addVote(XRPAmount{ + unsafe_cast(vote)}); + else + // Invalid amounts will be treated as if they're + // not provided. Don't throw because this value is + // provided by an external entity. + baseFeeVote.noVote(); } else { @@ -177,7 +189,8 @@ FeeVoteImpl::doVoting( if (val->isFieldPresent (sfReserveBase)) { - baseReserveVote.addVote (val->getFieldU32 (sfReserveBase)); + baseReserveVote.addVote(XRPAmount{ + val->getFieldU32(sfReserveBase)}); } else { @@ -186,7 +199,8 @@ FeeVoteImpl::doVoting( if (val->isFieldPresent (sfReserveIncrement)) { - incReserveVote.addVote (val->getFieldU32 (sfReserveIncrement)); + incReserveVote.addVote (XRPAmount{ + val->getFieldU32 (sfReserveIncrement)}); } else { @@ -196,15 +210,19 @@ FeeVoteImpl::doVoting( } // choose our positions - std::uint64_t const baseFee = baseFeeVote.getVotes (); - std::uint32_t const baseReserve = baseReserveVote.getVotes (); - std::uint32_t const incReserve = incReserveVote.getVotes (); - std::uint32_t const feeUnits = target_.reference_fee_units; + // If any of the values are invalid, send the current values. + auto const baseFee = baseFeeVote.getVotes ().dropsAs( + lastClosedLedger->fees().base); + auto const baseReserve = baseReserveVote.getVotes ().dropsAs( + lastClosedLedger->fees().accountReserve(0)); + auto const incReserve = incReserveVote.getVotes ().dropsAs( + lastClosedLedger->fees().increment); + constexpr FeeUnit32 feeUnits = Setup::reference_fee_units; auto const seq = lastClosedLedger->info().seq + 1; // add transactions to our position if ((baseFee != lastClosedLedger->fees().base) || - (baseReserve != lastClosedLedger->fees().accountReserve(0)) || + (baseReserve != lastClosedLedger->fees().accountReserve(0)) || (incReserve != lastClosedLedger->fees().increment)) { JLOG(journal_.warn()) << @@ -220,7 +238,7 @@ FeeVoteImpl::doVoting( obj[sfBaseFee] = baseFee; obj[sfReserveBase] = baseReserve; obj[sfReserveIncrement] = incReserve; - obj[sfReferenceFeeUnits] = feeUnits; + obj[sfReferenceFeeUnits] = feeUnits.fee(); }); uint256 txID = feeTx.getTransactionID (); @@ -247,9 +265,19 @@ FeeVote::Setup setup_FeeVote (Section const& section) { FeeVote::Setup setup; - set (setup.reference_fee, "reference_fee", section); - set (setup.account_reserve, "account_reserve", section); - set (setup.owner_reserve, "owner_reserve", section); + { + std::uint64_t temp; + if (set(temp, "reference_fee", section) && + temp <= std::numeric_limits::max()) + setup.reference_fee = temp; + } + { + std::uint32_t temp; + if (set(temp, "account_reserve", section)) + setup.account_reserve = temp; + if (set(temp, "owner_reserve", section)) + setup.owner_reserve = temp; + } return setup; } diff --git a/src/ripple/app/misc/LoadFeeTrack.h b/src/ripple/app/misc/LoadFeeTrack.h index f7ad2700e43..43715f6d324 100644 --- a/src/ripple/app/misc/LoadFeeTrack.h +++ b/src/ripple/app/misc/LoadFeeTrack.h @@ -20,6 +20,7 @@ #ifndef RIPPLE_CORE_LOADFEETRACK_H_INCLUDED #define RIPPLE_CORE_LOADFEETRACK_H_INCLUDED +#include #include #include #include @@ -140,7 +141,8 @@ class LoadFeeTrack final //------------------------------------------------------------------------------ // Scale using load as well as base rate -std::uint64_t scaleFeeLoad(std::uint64_t fee, LoadFeeTrack const& feeTrack, +XRPAmount +scaleFeeLoad(FeeUnit64 fee, LoadFeeTrack const& feeTrack, Fees const& fees, bool bUnlimited); } // ripple diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index c014216a9a4..51f99ede9f8 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -176,7 +176,7 @@ class NetworkOPsImp final { ServerFeeSummary() = default; - ServerFeeSummary(std::uint64_t fee, + ServerFeeSummary(XRPAmount fee, TxQ::Metrics&& escalationMetrics, LoadFeeTrack const & loadFeeTrack); bool @@ -190,7 +190,7 @@ class NetworkOPsImp final std::uint32_t loadFactorServer = 256; std::uint32_t loadBaseServer = 256; - std::uint64_t baseFee = 10; + XRPAmount baseFee{ 10 }; boost::optional em = boost::none; }; @@ -1651,7 +1651,7 @@ void NetworkOPsImp::pubManifest (Manifest const& mo) } NetworkOPsImp::ServerFeeSummary::ServerFeeSummary( - std::uint64_t fee, + XRPAmount fee, TxQ::Metrics&& escalationMetrics, LoadFeeTrack const & loadFeeTrack) : loadFactorServer{loadFeeTrack.getLoadFactor()} @@ -1682,6 +1682,15 @@ NetworkOPsImp::ServerFeeSummary::operator !=(NetworkOPsImp::ServerFeeSummary con return false; } +// Need to cap to uint64 to uint32 due to JSON limitations +static std::uint32_t trunc32(std::uint64_t v) +{ + constexpr std::uint64_t max32 = + std::numeric_limits::max(); + + return std::min(max32, v); +}; + void NetworkOPsImp::pubServer () { // VFALCO TODO Don't hold the lock across calls to send...make a copy of the @@ -1698,21 +1707,11 @@ void NetworkOPsImp::pubServer () app_.getTxQ().getMetrics(*app_.openLedger().current()), app_.getFeeTrack()}; - // Need to cap to uint64 to uint32 due to JSON limitations - auto clamp = [](std::uint64_t v) - { - constexpr std::uint64_t max32 = - std::numeric_limits::max(); - - return static_cast(std::min(max32, v)); - }; - - jvObj [jss::type] = "serverStatus"; jvObj [jss::server_status] = strOperatingMode (); jvObj [jss::load_base] = f.loadBaseServer; jvObj [jss::load_factor_server] = f.loadFactorServer; - jvObj [jss::base_fee] = clamp(f.baseFee); + jvObj [jss::base_fee] = f.baseFee.jsonClipped(); if(f.em) { @@ -1721,11 +1720,13 @@ void NetworkOPsImp::pubServer () mulDiv(f.em->openLedgerFeeLevel, f.loadBaseServer, f.em->referenceFeeLevel).second); - jvObj [jss::load_factor] = clamp(loadFactor); - jvObj [jss::load_factor_fee_escalation] = clamp(f.em->openLedgerFeeLevel); - jvObj [jss::load_factor_fee_queue] = clamp(f.em->minProcessingFeeLevel); - jvObj [jss::load_factor_fee_reference] - = clamp(f.em->referenceFeeLevel); + jvObj [jss::load_factor] = trunc32(loadFactor); + jvObj [jss::load_factor_fee_escalation] = + f.em->openLedgerFeeLevel.jsonClipped(); + jvObj [jss::load_factor_fee_queue] = + f.em->minProcessingFeeLevel.jsonClipped(); + jvObj [jss::load_factor_fee_reference] = + f.em->referenceFeeLevel.jsonClipped(); } else @@ -2313,22 +2314,20 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin, bool counters) auto const loadFactorServer = app_.getFeeTrack().getLoadFactor(); auto const loadBaseServer = app_.getFeeTrack().getLoadBase(); - auto const loadFactorFeeEscalation = - escalationMetrics.openLedgerFeeLevel; - auto const loadBaseFeeEscalation = - escalationMetrics.referenceFeeLevel; + /* Scale the escalated fee level to unitless "load factor". + In practice, this just strips the units, but it will continue + to work correctly if either base value ever changes. */ + auto const loadFactorFeeEscalation = mulDiv( + escalationMetrics.openLedgerFeeLevel, loadBaseServer, + escalationMetrics.referenceFeeLevel).second; auto const loadFactor = std::max(safe_cast(loadFactorServer), - mulDiv(loadFactorFeeEscalation, loadBaseServer, loadBaseFeeEscalation).second); + loadFactorFeeEscalation); if (!human) { - constexpr std::uint64_t max32 = - std::numeric_limits::max(); - info[jss::load_base] = loadBaseServer; - info[jss::load_factor] = static_cast( - std::min(max32, loadFactor)); + info[jss::load_factor] = trunc32(loadFactor); info[jss::load_factor_server] = loadFactorServer; /* Json::Value doesn't support uint64, so clamp to max @@ -2337,14 +2336,11 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin, bool counters) that high. */ info[jss::load_factor_fee_escalation] = - static_cast (std::min( - max32, loadFactorFeeEscalation)); + escalationMetrics.openLedgerFeeLevel.jsonClipped(); info[jss::load_factor_fee_queue] = - static_cast (std::min( - max32, escalationMetrics.minProcessingFeeLevel)); + escalationMetrics.minProcessingFeeLevel.jsonClipped(); info[jss::load_factor_fee_reference] = - static_cast (std::min( - max32, loadBaseFeeEscalation)); + escalationMetrics.referenceFeeLevel.jsonClipped(); } else { @@ -2369,18 +2365,18 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin, bool counters) info[jss::load_factor_cluster] = static_cast (fee) / loadBaseServer; } - if (loadFactorFeeEscalation != + if (escalationMetrics.openLedgerFeeLevel != escalationMetrics.referenceFeeLevel && - (admin || loadFactorFeeEscalation != loadFactor)) + (admin || + loadFactorFeeEscalation != loadFactor)) info[jss::load_factor_fee_escalation] = - static_cast (loadFactorFeeEscalation) / - escalationMetrics.referenceFeeLevel; + escalationMetrics.openLedgerFeeLevel.decimalFromReference( + escalationMetrics.referenceFeeLevel); if (escalationMetrics.minProcessingFeeLevel != escalationMetrics.referenceFeeLevel) info[jss::load_factor_fee_queue] = - static_cast ( - escalationMetrics.minProcessingFeeLevel) / - escalationMetrics.referenceFeeLevel; + escalationMetrics.minProcessingFeeLevel.decimalFromReference( + escalationMetrics.referenceFeeLevel); } bool valid = false; @@ -2393,33 +2389,28 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin, bool counters) if (lpClosed) { - std::uint64_t baseFee = lpClosed->fees().base; - std::uint64_t baseRef = lpClosed->fees().units; + XRPAmount const baseFee = lpClosed->fees().base; Json::Value l (Json::objectValue); l[jss::seq] = Json::UInt (lpClosed->info().seq); l[jss::hash] = to_string (lpClosed->info().hash); if (!human) { - l[jss::base_fee] = Json::Value::UInt (baseFee); - l[jss::reserve_base] = Json::Value::UInt (lpClosed->fees().accountReserve(0).drops()); + l[jss::base_fee] = baseFee.jsonClipped(); + l[jss::reserve_base] = lpClosed->fees().accountReserve(0).jsonClipped(); l[jss::reserve_inc] = - Json::Value::UInt (lpClosed->fees().increment); - l[jss::close_time] = - Json::Value::UInt (lpClosed->info().closeTime.time_since_epoch().count()); + lpClosed->fees().increment.jsonClipped(); + l[jss::close_time] = Json::Value::UInt ( + lpClosed->info().closeTime.time_since_epoch().count()); } else { - l[jss::base_fee_xrp] = static_cast (baseFee) / - SYSTEM_CURRENCY_PARTS; - l[jss::reserve_base_xrp] = - static_cast (Json::UInt ( - lpClosed->fees().accountReserve(0).drops() * baseFee / baseRef)) - / SYSTEM_CURRENCY_PARTS; - l[jss::reserve_inc_xrp] = - static_cast (Json::UInt ( - lpClosed->fees().increment * baseFee / baseRef)) - / SYSTEM_CURRENCY_PARTS; + l[jss::base_fee_xrp] = + baseFee.decimalXRP(); + l[jss::reserve_base_xrp] = + lpClosed->fees().accountReserve(0).decimalXRP(); + l[jss::reserve_inc_xrp] = + lpClosed->fees().increment.decimalXRP(); auto const nowOffset = app_.timeKeeper().nowOffset(); if (std::abs (nowOffset.count()) >= 60) @@ -2537,11 +2528,11 @@ void NetworkOPsImp::pubLedger ( jvObj[jss::ledger_time] = Json::Value::UInt (lpAccepted->info().closeTime.time_since_epoch().count()); - jvObj[jss::fee_ref] - = Json::UInt (lpAccepted->fees().units); - jvObj[jss::fee_base] = Json::UInt (lpAccepted->fees().base); - jvObj[jss::reserve_base] = Json::UInt (lpAccepted->fees().accountReserve(0).drops()); - jvObj[jss::reserve_inc] = Json::UInt (lpAccepted->fees().increment); + jvObj[jss::fee_ref] = lpAccepted->fees().units.jsonClipped(); + jvObj[jss::fee_base] = lpAccepted->fees().base.jsonClipped(); + jvObj[jss::reserve_base] = + lpAccepted->fees().accountReserve(0).jsonClipped(); + jvObj[jss::reserve_inc] = lpAccepted->fees().increment.jsonClipped(); jvObj[jss::txn_count] = Json::UInt (alpAccepted->getTxnCount ()); @@ -2914,13 +2905,13 @@ bool NetworkOPsImp::subLedger (InfoSub::ref isrListener, Json::Value& jvResult) { jvResult[jss::ledger_index] = lpClosed->info().seq; jvResult[jss::ledger_hash] = to_string (lpClosed->info().hash); - jvResult[jss::ledger_time] - = Json::Value::UInt(lpClosed->info().closeTime.time_since_epoch().count()); - jvResult[jss::fee_ref] - = Json::UInt (lpClosed->fees().units); - jvResult[jss::fee_base] = Json::UInt (lpClosed->fees().base); - jvResult[jss::reserve_base] = Json::UInt (lpClosed->fees().accountReserve(0).drops()); - jvResult[jss::reserve_inc] = Json::UInt (lpClosed->fees().increment); + jvResult[jss::ledger_time] = Json::Value::UInt( + lpClosed->info().closeTime.time_since_epoch().count()); + jvResult[jss::fee_ref] = lpClosed->fees().units.jsonClipped(); + jvResult[jss::fee_base] = lpClosed->fees().base.jsonClipped(); + jvResult[jss::reserve_base] = + lpClosed->fees().accountReserve(0).jsonClipped(); + jvResult[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped(); } if ((mMode >= OperatingMode::SYNCING) && !isNeedNetworkLedger ()) diff --git a/src/ripple/app/misc/TxQ.h b/src/ripple/app/misc/TxQ.h index 85265ce48b0..72640739f17 100644 --- a/src/ripple/app/misc/TxQ.h +++ b/src/ripple/app/misc/TxQ.h @@ -55,7 +55,7 @@ class TxQ { public: /// Fee level for single-signed reference transaction. - static constexpr std::uint64_t baseLevel = 256; + static constexpr FeeLevel64 baseLevel{ 256 }; /** Structure used to customize @ref TxQ behavior. @@ -104,7 +104,7 @@ class TxQ std::int32_t multiTxnPercent = -90; /// Minimum value of the escalation multiplier, regardless /// of the prior ledger's median fee level. - std::uint64_t minimumEscalationMultiplier = baseLevel * 500; + FeeLevel64 minimumEscalationMultiplier = baseLevel * 500; /// Minimum number of transactions to allow into the ledger /// before escalation, regardless of the prior ledger's size. std::uint32_t minimumTxnInLedger = 5; @@ -168,7 +168,7 @@ class TxQ we can make this more complicated. But avoid bikeshedding for now. */ - std::uint64_t zeroBaseFeeTransactionFeeLevel = 256000; + FeeLevel64 zeroBaseFeeTransactionFeeLevel{ 256000 }; /// Use standalone mode behavior. bool standAlone = false; }; @@ -191,15 +191,15 @@ class TxQ /// Number of transactions expected per ledger std::size_t txPerLedger; /// Reference transaction fee level - std::uint64_t referenceFeeLevel; + FeeLevel64 referenceFeeLevel; /// Minimum fee level for a transaction to be considered for /// the open ledger or the queue - std::uint64_t minProcessingFeeLevel; + FeeLevel64 minProcessingFeeLevel; /// Median fee level of the last ledger - std::uint64_t medFeeLevel; + FeeLevel64 medFeeLevel; /// Minimum fee level to get into the current open ledger, /// bypassing the queue - std::uint64_t openLedgerFeeLevel; + FeeLevel64 openLedgerFeeLevel; }; /** @@ -212,7 +212,7 @@ class TxQ explicit AccountTxDetails() = default; /// Fee level of the queued transaction - uint64_t feeLevel; + FeeLevel64 feeLevel; /// LastValidLedger field of the queued transaction, if any boost::optional lastValid; /** Potential @ref TxConsequences of applying the queued transaction @@ -389,7 +389,7 @@ class TxQ boost::circular_buffer recentTxnCounts_; /// Based on the median fee of the LCL. Used /// when fee escalation kicks in. - std::uint64_t escalationMultiplier_; + FeeLevel64 escalationMultiplier_; /// Journal beast::Journal const j_; @@ -437,7 +437,7 @@ class TxQ std::size_t const txnsExpected; // Based on the median fee of the LCL. Used // when fee escalation kicks in. - std::uint64_t const escalationMultiplier; + FeeLevel64 const escalationMultiplier; }; /// Get the current @ref Snapshot @@ -459,7 +459,7 @@ class TxQ @return A fee level value. */ static - std::uint64_t + FeeLevel64 scaleFeeLevel(Snapshot const& snapshot, OpenView const& view); /** @@ -493,7 +493,7 @@ class TxQ whether the calculation result overflows. */ static - std::pair + std::pair escalatedSeriesFeeLevel(Snapshot const& snapshot, OpenView const& view, std::size_t extraCount, std::size_t seriesSize); }; @@ -518,7 +518,7 @@ class TxQ boost::optional consequences; /// Computed fee level that the transaction will pay. - uint64_t const feeLevel; + FeeLevel64 const feeLevel; /// Transaction ID. TxID const txID; /// Prior transaction ID (`sfAccountTxnID` field). @@ -579,7 +579,7 @@ class TxQ public: /// Constructor MaybeTx(std::shared_ptr const&, - TxID const& txID, std::uint64_t feeLevel, + TxID const& txID, FeeLevel64 feeLevel, ApplyFlags const flags, PreflightResult const& pfresult); @@ -740,7 +740,7 @@ class TxQ std::pair tryClearAccountQueue(Application& app, OpenView& view, STTx const& tx, AccountMap::iterator const& accountIter, - TxQAccount::TxMap::iterator, std::uint64_t feeLevelPaid, + TxQAccount::TxMap::iterator, FeeLevel64 feeLevelPaid, PreflightResult const& pfresult, std::size_t const txExtraCount, ApplyFlags flags, FeeMetrics::Snapshot const& metricsSnapshot, @@ -760,6 +760,20 @@ setup_TxQ(Config const&); std::unique_ptr make_TxQ(TxQ::Setup const&, beast::Journal); +template +std::pair +toDrops(FeeLevel const& level, XRPAmount const& baseFee) +{ + return mulDiv(level, baseFee, TxQ::baseLevel); +} + +inline +std::pair +toFeeLevel(XRPAmount const& drops, XRPAmount const& baseFee) +{ + return mulDiv(drops, TxQ::baseLevel, baseFee); +} + } // ripple #endif diff --git a/src/ripple/app/misc/impl/LoadFeeTrack.cpp b/src/ripple/app/misc/impl/LoadFeeTrack.cpp index d33c5b26353..6ea4b378ad5 100644 --- a/src/ripple/app/misc/impl/LoadFeeTrack.cpp +++ b/src/ripple/app/misc/impl/LoadFeeTrack.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -83,27 +84,32 @@ LoadFeeTrack::lowerLocalFee () //------------------------------------------------------------------------------ -template && - std::is_integral_v> -> -void lowestTerms(T1& a, T2& b) -{ - if (auto const gcd = std::gcd(a, b)) - { - a /= gcd; - b /= gcd; - } -} - // Scale using load as well as base rate -std::uint64_t -scaleFeeLoad(std::uint64_t fee, LoadFeeTrack const& feeTrack, +XRPAmount +scaleFeeLoad(FeeUnit64 fee, LoadFeeTrack const& feeTrack, Fees const& fees, bool bUnlimited) { if (fee == 0) - return fee; + return XRPAmount{0}; + + // Normally, types with different units wouldn't be mathematically + // compatible. This function is an exception. + auto lowestTerms = [](auto& a, auto& b) + { + auto value = [](auto val) + { + if constexpr(std::is_arithmetic_v) + return val; + else + return val.value(); + }; + + if (auto const g = std::gcd(value(a), value(b))) + { + a = value(a) / g; + b = value(b) / g; + } + }; // Collect the fee rates auto [feeFactor, uRemFee] = feeTrack.getScalingFactors(); @@ -113,7 +119,7 @@ scaleFeeLoad(std::uint64_t fee, LoadFeeTrack const& feeTrack, if (bUnlimited && (feeFactor > uRemFee) && (feeFactor < (4 * uRemFee))) feeFactor = uRemFee; - auto baseFee = fees.base; + XRPAmount baseFee{fees.base}; // Compute: // fee = fee * baseFee * feeFactor / (fees.units * lftNormalFee); // without overflow, and as accurately as possible @@ -121,7 +127,7 @@ scaleFeeLoad(std::uint64_t fee, LoadFeeTrack const& feeTrack, // The denominator of the fraction we're trying to compute. // fees.units and lftNormalFee are both 32 bit, // so the multiplication can't overflow. - auto den = safe_cast(fees.units) + auto den = FeeUnit64{ fees.units } * safe_cast(feeTrack.getLoadBase()); // Reduce fee * baseFee * feeFactor / (fees.units * lftNormalFee) // to lowest terms. @@ -131,33 +137,30 @@ scaleFeeLoad(std::uint64_t fee, LoadFeeTrack const& feeTrack, // fee and baseFee are 64 bit, feeFactor is 32 bit // Order fee and baseFee largest first - if (fee < baseFee) - std::swap(fee, baseFee); - // If baseFee * feeFactor overflows, the final result will overflow - const auto max = std::numeric_limits::max(); - if (baseFee > max / feeFactor) - Throw ("scaleFeeLoad"); - baseFee *= feeFactor; - // Reorder fee and baseFee - if (fee < baseFee) - std::swap(fee, baseFee); - // If fee * baseFee / den might overflow... - if (fee > max / baseFee) + // Normally, these types wouldn't be comparable or swappable. + // This function is an exception. + if (fee.value() < baseFee.value()) { - // Do the division first, on the larger of fee and baseFee - fee /= den; - if (fee > max / baseFee) - Throw ("scaleFeeLoad"); - fee *= baseFee; + auto tmp = fee.value(); + fee = baseFee.value(); + baseFee = tmp; } - else + // double check + assert(fee.value() >= baseFee.value()); + + // If baseFee * feeFactor overflows, the final result will overflow + XRPAmount const baseFeeOverflow{ + std::numeric_limits::max() / feeFactor}; + if (baseFee > baseFeeOverflow) { - // Otherwise fee * baseFee won't overflow, - // so do it prior to the division. - fee *= baseFee; - fee /= den; + Throw("scaleFeeLoad"); } - return fee; + baseFee *= feeFactor; + + auto const result = mulDiv(fee, baseFee, den); + if (!result.first) + Throw ("scaleFeeLoad"); + return result.second; } } // ripple diff --git a/src/ripple/app/misc/impl/TxQ.cpp b/src/ripple/app/misc/impl/TxQ.cpp index 70be61d7ddd..da4e57e5761 100644 --- a/src/ripple/app/misc/impl/TxQ.cpp +++ b/src/ripple/app/misc/impl/TxQ.cpp @@ -36,11 +36,11 @@ namespace ripple { ////////////////////////////////////////////////////////////////////////// static -std::uint64_t +FeeLevel64 getFeeLevelPaid( STTx const& tx, - std::uint64_t baseRefLevel, - std::uint64_t refTxnCostDrops, + FeeLevel64 baseRefLevel, + XRPAmount refTxnCostDrops, TxQ::Setup const& setup) { if (refTxnCostDrops == 0) @@ -51,7 +51,7 @@ getFeeLevelPaid( // If the math overflows, return the clipped // result blindly. This is very unlikely to ever // happen. - return mulDiv(tx[sfFee].xrp().drops(), + return mulDiv(tx[sfFee].xrp(), baseRefLevel, refTxnCostDrops).second; } @@ -66,23 +66,24 @@ getLastLedgerSequence(STTx const& tx) } static -std::uint64_t -increase(std::uint64_t level, +FeeLevel64 +increase(FeeLevel64 level, std::uint32_t increasePercent) { - return mulDiv( - level, 100 + increasePercent, 100).second; + return mulDiv(level, 100 + increasePercent, 100).second; } ////////////////////////////////////////////////////////////////////////// +constexpr FeeLevel64 TxQ::baseLevel; + std::size_t TxQ::FeeMetrics::update(Application& app, ReadView const& view, bool timeLeap, TxQ::Setup const& setup) { - std::vector feeLevels; + std::vector feeLevels; auto const txBegin = view.txs.begin(); auto const txEnd = view.txs.end(); auto const size = std::distance(txBegin, txEnd); @@ -90,7 +91,8 @@ TxQ::FeeMetrics::update(Application& app, std::for_each(txBegin, txEnd, [&](auto const& tx) { - auto const baseFee = calculateBaseFee(view, *tx.first); + auto const baseFee = view.fees().toDrops( + calculateBaseFee(view, *tx.first)).second; feeLevels.push_back(getFeeLevelPaid(*tx.first, baseLevel, baseFee, setup)); } @@ -158,7 +160,7 @@ TxQ::FeeMetrics::update(Application& app, // number of elements, it will add the two elements // on either side of the "middle" and average them. escalationMultiplier_ = (feeLevels[size / 2] + - feeLevels[(size - 1) / 2] + 1) / 2; + feeLevels[(size - 1) / 2] + FeeLevel64{ 1 }) / 2; escalationMultiplier_ = std::max(escalationMultiplier_, setup.minimumEscalationMultiplier); } @@ -169,7 +171,7 @@ TxQ::FeeMetrics::update(Application& app, return size; } -std::uint64_t +FeeLevel64 TxQ::FeeMetrics::scaleFeeLevel(Snapshot const& snapshot, OpenView const& view) { @@ -213,7 +215,7 @@ sumOfFirstSquares(std::size_t x) } -std::pair +std::pair TxQ::FeeMetrics::escalatedSeriesFeeLevel(Snapshot const& snapshot, OpenView const& view, std::size_t extraCount, std::size_t seriesSize) @@ -245,7 +247,7 @@ TxQ::FeeMetrics::escalatedSeriesFeeLevel(Snapshot const& snapshot, // `sumNlast` definitely overflowed. Also the odds of this // are nearly nil. if (!sumNlast.first) - return sumNlast; + return { sumNlast.first, FeeLevel64{ sumNlast.second } }; auto const totalFeeLevel = mulDiv(multiplier, sumNlast.second - sumNcurrent.second, target * target); @@ -255,7 +257,7 @@ TxQ::FeeMetrics::escalatedSeriesFeeLevel(Snapshot const& snapshot, TxQ::MaybeTx::MaybeTx( std::shared_ptr const& txn_, - TxID const& txID_, std::uint64_t feeLevel_, + TxID const& txID_, FeeLevel64 feeLevel_, ApplyFlags const flags_, PreflightResult const& pfresult_) : txn(txn_) @@ -483,7 +485,7 @@ TxQ::erase(TxQ::TxQAccount& txQAccount, std::pair TxQ::tryClearAccountQueue(Application& app, OpenView& view, STTx const& tx, TxQ::AccountMap::iterator const& accountIter, - TxQAccount::TxMap::iterator beginTxIter, std::uint64_t feeLevelPaid, + TxQAccount::TxMap::iterator beginTxIter, FeeLevel64 feeLevelPaid, PreflightResult const& pfresult, std::size_t const txExtraCount, ApplyFlags flags, FeeMetrics::Snapshot const& metricsSnapshot, beast::Journal j) @@ -668,7 +670,8 @@ TxQ::apply(Application& app, OpenView& view, // or transaction replacement, so just pull it up now. // TODO: Do we want to avoid doing it again during // preclaim? - auto const baseFee = calculateBaseFee(view, *tx); + auto const baseFee = view.fees().toDrops( + calculateBaseFee(view, *tx)).second; auto const feeLevelPaid = getFeeLevelPaid(*tx, baseLevel, baseFee, setup_); auto const requiredFeeLevel = [&]() @@ -1003,7 +1006,8 @@ TxQ::apply(Application& app, OpenView& view, multiTxn->nextTxIter->second.retriesRemaining == MaybeTx::retriesAllowed && feeLevelPaid > requiredFeeLevel && - requiredFeeLevel > baseLevel && baseFee != 0) + requiredFeeLevel > baseLevel && + baseFee != 0) { OpenView sandbox(open_ledger, &view, view.rules()); @@ -1077,11 +1081,11 @@ TxQ::apply(Application& app, OpenView& view, || endAccount.transactions.size() == 1) return lastRIter->feeLevel; - constexpr std::uint64_t max = - std::numeric_limits::max(); + constexpr FeeLevel64 max{ + std::numeric_limits::max() }; auto endTotal = std::accumulate(endAccount.transactions.begin(), endAccount.transactions.end(), - std::pair(0, 0), + std::pair(0, 0), [&](auto const& total, auto const& txn) { // Check for overflow. @@ -1091,7 +1095,7 @@ TxQ::apply(Application& app, OpenView& view, endAccount.transactions.size(); if (total.first >= max - next || total.second >= max - mod) - return std::make_pair(max, std::uint64_t(0)); + return std::make_pair(max, FeeLevel64 { 0 }); return std::make_pair(total.first + next, total.second + mod); }); return endTotal.first + endTotal.second / @@ -1385,7 +1389,8 @@ TxQ::getMetrics(OpenView const& view) const result.txInLedger = view.txCount(); result.txPerLedger = snapshot.txnsExpected; result.referenceFeeLevel = baseLevel; - result.minProcessingFeeLevel = isFull() ? byFee_.rbegin()->feeLevel + 1 : + result.minProcessingFeeLevel = isFull() ? + byFee_.rbegin()->feeLevel + FeeLevel64{ 1 } : baseLevel; result.medFeeLevel = snapshot.escalationMultiplier; result.openLedgerFeeLevel = FeeMetrics::scaleFeeLevel(snapshot, view); @@ -1402,7 +1407,8 @@ TxQ::getTxRequiredFeeAndSeq(OpenView const& view, std::lock_guard lock(mutex_); auto const snapshot = feeMetrics_.getSnapshot(); - auto const baseFee = calculateBaseFee(view, *tx); + auto const baseFee = view.fees().toDrops( + calculateBaseFee(view, *tx)).second; auto const fee = FeeMetrics::scaleFeeLevel(snapshot, view); auto const accountSeq = [&view, &account]() -> std::uint32_t { @@ -1425,7 +1431,7 @@ TxQ::getTxRequiredFeeAndSeq(OpenView const& view, } } - return {fee * baseFee / baseLevel, accountSeq, availableSeq}; + return {mulDiv(fee, baseFee, baseLevel).second, accountSeq, availableSeq}; } auto @@ -1496,8 +1502,6 @@ TxQ::getTxs(ReadView const& view) const Json::Value TxQ::doRPC(Application& app) const { - using std::to_string; - auto const view = app.openLedger().current(); if (!view) { @@ -1527,23 +1531,15 @@ TxQ::doRPC(Application& app) const auto& drops = ret[jss::drops] = Json::Value(); // Don't care about the overflow flags - drops[jss::base_fee] = to_string(mulDiv( - metrics.referenceFeeLevel, baseFee, - metrics.referenceFeeLevel).second); - drops[jss::minimum_fee] = to_string(mulDiv( - metrics.minProcessingFeeLevel, baseFee, - metrics.referenceFeeLevel).second); - drops[jss::median_fee] = to_string(mulDiv( - metrics.medFeeLevel, baseFee, - metrics.referenceFeeLevel).second); - auto escalatedFee = mulDiv( - metrics.openLedgerFeeLevel, baseFee, - metrics.referenceFeeLevel).second; - if (mulDiv(escalatedFee, metrics.referenceFeeLevel, - baseFee).second < metrics.openLedgerFeeLevel) - ++escalatedFee; - - drops[jss::open_ledger_fee] = to_string(escalatedFee); + drops[jss::base_fee] = to_string(toDrops( + metrics.referenceFeeLevel, baseFee).second); + drops[jss::minimum_fee] = to_string(toDrops( + metrics.minProcessingFeeLevel, baseFee).second); + drops[jss::median_fee] = to_string(toDrops( + metrics.medFeeLevel, baseFee).second); + drops[jss::open_ledger_fee] = to_string(toDrops( + metrics.openLedgerFeeLevel - FeeLevel64{ 1 }, baseFee).second + + 1); return ret; } diff --git a/src/ripple/app/paths/Credit.h b/src/ripple/app/paths/Credit.h index 9d1cfc2cfc8..8e55bbe6729 100644 --- a/src/ripple/app/paths/Credit.h +++ b/src/ripple/app/paths/Credit.h @@ -20,9 +20,9 @@ #ifndef RIPPLE_APP_PATHS_CREDIT_H_INCLUDED #define RIPPLE_APP_PATHS_CREDIT_H_INCLUDED +#include #include #include -#include namespace ripple { diff --git a/src/ripple/app/paths/Flow.cpp b/src/ripple/app/paths/Flow.cpp index 7e4b5aac611..1834b434640 100644 --- a/src/ripple/app/paths/Flow.cpp +++ b/src/ripple/app/paths/Flow.cpp @@ -23,8 +23,8 @@ #include #include #include -#include -#include +#include +#include #include diff --git a/src/ripple/app/paths/impl/AmountSpec.h b/src/ripple/app/paths/impl/AmountSpec.h index 4d32fad476c..315b4cde782 100644 --- a/src/ripple/app/paths/impl/AmountSpec.h +++ b/src/ripple/app/paths/impl/AmountSpec.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_PATH_IMPL_AMOUNTSPEC_H_INCLUDED #define RIPPLE_PATH_IMPL_AMOUNTSPEC_H_INCLUDED -#include -#include +#include +#include #include namespace ripple { diff --git a/src/ripple/app/paths/impl/BookStep.cpp b/src/ripple/app/paths/impl/BookStep.cpp index 18435e5b19a..8d391ff063d 100644 --- a/src/ripple/app/paths/impl/BookStep.cpp +++ b/src/ripple/app/paths/impl/BookStep.cpp @@ -23,14 +23,14 @@ #include #include #include +#include #include +#include #include #include #include #include -#include #include -#include #include diff --git a/src/ripple/app/paths/impl/DirectStep.cpp b/src/ripple/app/paths/impl/DirectStep.cpp index 2edb527bd2b..58229b1c34d 100644 --- a/src/ripple/app/paths/impl/DirectStep.cpp +++ b/src/ripple/app/paths/impl/DirectStep.cpp @@ -20,9 +20,9 @@ #include #include #include +#include #include #include -#include #include #include diff --git a/src/ripple/app/paths/impl/FlowDebugInfo.h b/src/ripple/app/paths/impl/FlowDebugInfo.h index 8343140dddc..ecd85f53c0d 100644 --- a/src/ripple/app/paths/impl/FlowDebugInfo.h +++ b/src/ripple/app/paths/impl/FlowDebugInfo.h @@ -21,9 +21,9 @@ #define RIPPLE_PATH_IMPL_FLOWDEBUGINFO_H_INCLUDED #include +#include +#include #include -#include -#include #include #include diff --git a/src/ripple/app/paths/impl/PaySteps.cpp b/src/ripple/app/paths/impl/PaySteps.cpp index d2335ef5bf4..9e177ee3169 100644 --- a/src/ripple/app/paths/impl/PaySteps.cpp +++ b/src/ripple/app/paths/impl/PaySteps.cpp @@ -19,11 +19,11 @@ #include #include +#include +#include #include #include #include -#include -#include #include #include diff --git a/src/ripple/app/paths/impl/StrandFlow.h b/src/ripple/app/paths/impl/StrandFlow.h index edd96d32a7f..62192b8e99a 100644 --- a/src/ripple/app/paths/impl/StrandFlow.h +++ b/src/ripple/app/paths/impl/StrandFlow.h @@ -26,9 +26,9 @@ #include #include #include +#include +#include #include -#include -#include #include diff --git a/src/ripple/app/paths/impl/XRPEndpointStep.cpp b/src/ripple/app/paths/impl/XRPEndpointStep.cpp index a75d61c2f3b..e5d4f5e5293 100644 --- a/src/ripple/app/paths/impl/XRPEndpointStep.cpp +++ b/src/ripple/app/paths/impl/XRPEndpointStep.cpp @@ -21,11 +21,11 @@ #include #include #include +#include #include +#include #include -#include #include -#include #include diff --git a/src/ripple/app/tx/applySteps.h b/src/ripple/app/tx/applySteps.h index 0f4768fe798..d49de7bba3b 100644 --- a/src/ripple/app/tx/applySteps.h +++ b/src/ripple/app/tx/applySteps.h @@ -234,7 +234,7 @@ preclaim(PreflightResult const& preflightResult, @return The base fee. */ -std::uint64_t +FeeUnit64 calculateBaseFee(ReadView const& view, STTx const& tx); diff --git a/src/ripple/app/tx/impl/ApplyContext.cpp b/src/ripple/app/tx/impl/ApplyContext.cpp index 33b69def06e..5dc068eee1c 100644 --- a/src/ripple/app/tx/impl/ApplyContext.cpp +++ b/src/ripple/app/tx/impl/ApplyContext.cpp @@ -30,7 +30,7 @@ namespace ripple { ApplyContext::ApplyContext(Application& app_, OpenView& base, STTx const& tx_, TER preclaimResult_, - std::uint64_t baseFee_, ApplyFlags flags, + FeeUnit64 baseFee_, ApplyFlags flags, beast::Journal journal_) : app(app_) , tx(tx_) diff --git a/src/ripple/app/tx/impl/ApplyContext.h b/src/ripple/app/tx/impl/ApplyContext.h index a545a1c9013..fe4b830856b 100644 --- a/src/ripple/app/tx/impl/ApplyContext.h +++ b/src/ripple/app/tx/impl/ApplyContext.h @@ -22,9 +22,9 @@ #include #include +#include #include #include -#include #include #include #include @@ -38,13 +38,13 @@ class ApplyContext explicit ApplyContext (Application& app, OpenView& base, STTx const& tx, TER preclaimResult, - std::uint64_t baseFee, ApplyFlags flags, + FeeUnit64 baseFee, ApplyFlags flags, beast::Journal = beast::Journal{beast::Journal::getNullSink()}); Application& app; STTx const& tx; TER const preclaimResult; - std::uint64_t const baseFee; + FeeUnit64 const baseFee; beast::Journal const journal; ApplyView& diff --git a/src/ripple/app/tx/impl/CashCheck.cpp b/src/ripple/app/tx/impl/CashCheck.cpp index 0d0590e9083..d546b7779ec 100644 --- a/src/ripple/app/tx/impl/CashCheck.cpp +++ b/src/ripple/app/tx/impl/CashCheck.cpp @@ -178,7 +178,7 @@ CashCheck::preclaim (PreclaimContext const& ctx) // longer be required. So, if we're dealing in XRP, we add one // reserve's worth to the available funds. if (value.native()) - availableFunds += XRPAmount (ctx.view.fees().increment); + availableFunds += XRPAmount{ctx.view.fees().increment}; if (value > availableFunds) { diff --git a/src/ripple/app/tx/impl/Change.h b/src/ripple/app/tx/impl/Change.h index e5d84cf1a2e..a60bd9f11e2 100644 --- a/src/ripple/app/tx/impl/Change.h +++ b/src/ripple/app/tx/impl/Change.h @@ -46,12 +46,12 @@ class Change void preCompute() override; static - std::uint64_t + FeeUnit64 calculateBaseFee ( ReadView const& view, STTx const& tx) { - return 0; + return FeeUnit64{0}; } static diff --git a/src/ripple/app/tx/impl/DeleteAccount.cpp b/src/ripple/app/tx/impl/DeleteAccount.cpp index d30738132af..01cd7d61b55 100644 --- a/src/ripple/app/tx/impl/DeleteAccount.cpp +++ b/src/ripple/app/tx/impl/DeleteAccount.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -51,7 +52,7 @@ DeleteAccount::preflight (PreflightContext const& ctx) return preflight2 (ctx); } -std::uint64_t +FeeUnit64 DeleteAccount::calculateBaseFee ( ReadView const& view, STTx const& tx) @@ -59,8 +60,8 @@ DeleteAccount::calculateBaseFee ( // The fee required for AccountDelete is one owner reserve. But the // owner reserve is stored in drops. We need to convert it to fee units. Fees const& fees {view.fees()}; - std::pair const mulDivResult { - mulDiv (fees.increment, fees.units, fees.base)}; + std::pair const mulDivResult { + mulDiv (fees.increment, safe_cast(fees.units), fees.base)}; if (mulDivResult.first) return mulDivResult.second; diff --git a/src/ripple/app/tx/impl/DeleteAccount.h b/src/ripple/app/tx/impl/DeleteAccount.h index 24ec02842db..5b42d11a9fe 100644 --- a/src/ripple/app/tx/impl/DeleteAccount.h +++ b/src/ripple/app/tx/impl/DeleteAccount.h @@ -55,7 +55,7 @@ class DeleteAccount preflight (PreflightContext const& ctx); static - std::uint64_t + FeeUnit64 calculateBaseFee ( ReadView const& view, STTx const& tx); diff --git a/src/ripple/app/tx/impl/Escrow.cpp b/src/ripple/app/tx/impl/Escrow.cpp index 4d0d3d82078..2af6d57cf85 100644 --- a/src/ripple/app/tx/impl/Escrow.cpp +++ b/src/ripple/app/tx/impl/Escrow.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -32,7 +33,6 @@ #include #include #include -#include // During an EscrowFinish, the transaction must specify both // a condition and a fulfillment. We track whether that @@ -350,17 +350,17 @@ EscrowFinish::preflight (PreflightContext const& ctx) return tesSUCCESS; } -std::uint64_t +FeeUnit64 EscrowFinish::calculateBaseFee ( ReadView const& view, STTx const& tx) { - std::uint64_t extraFee = 0; + FeeUnit64 extraFee{ 0 }; if (auto const fb = tx[~sfFulfillment]) { - extraFee += view.fees().units * - (32 + safe_cast (fb->size() / 16)); + extraFee += safe_cast(view.fees().units) * + (32 + (fb->size() / 16)); } return Transactor::calculateBaseFee (view, tx) + extraFee; diff --git a/src/ripple/app/tx/impl/Escrow.h b/src/ripple/app/tx/impl/Escrow.h index 3c600868ae6..939a72749b4 100644 --- a/src/ripple/app/tx/impl/Escrow.h +++ b/src/ripple/app/tx/impl/Escrow.h @@ -63,7 +63,7 @@ class EscrowFinish preflight (PreflightContext const& ctx); static - std::uint64_t + FeeUnit64 calculateBaseFee ( ReadView const& view, STTx const& tx); diff --git a/src/ripple/app/tx/impl/InvariantCheck.cpp b/src/ripple/app/tx/impl/InvariantCheck.cpp index 0ef4c9dfd1e..f475dfb35d5 100644 --- a/src/ripple/app/tx/impl/InvariantCheck.cpp +++ b/src/ripple/app/tx/impl/InvariantCheck.cpp @@ -18,9 +18,11 @@ //============================================================================== #include +#include #include #include #include +#include namespace ripple { @@ -50,7 +52,7 @@ TransactionFeeCheck::finalize( // We should never charge a fee that's greater than or equal to the // entire XRP supply. - if (fee.drops() >= SYSTEM_CURRENCY_START) + if (fee >= INITIAL_XRP) { JLOG(j.fatal()) << "Invariant failed: fee paid exceeds system limit: " << fee.drops(); return false; @@ -164,11 +166,11 @@ XRPBalanceChecks::visitEntry( if (!balance.native()) return true; - auto const drops = balance.xrp().drops(); + auto const drops = balance.xrp(); // Can't have more than the number of drops instantiated // in the genesis ledger. - if (drops > SYSTEM_CURRENCY_START) + if (drops > INITIAL_XRP) return true; // Can't have a negative balance (0 is OK) @@ -260,10 +262,10 @@ NoZeroEscrow::visitEntry( if (!amount.native()) return true; - if (amount.xrp().drops() <= 0) + if (amount.xrp() <= 0) return true; - if (amount.xrp().drops() >= SYSTEM_CURRENCY_START) + if (amount.xrp() >= INITIAL_XRP) return true; return false; diff --git a/src/ripple/app/tx/impl/InvariantCheck.h b/src/ripple/app/tx/impl/InvariantCheck.h index af433221a57..f90dd20c57e 100644 --- a/src/ripple/app/tx/impl/InvariantCheck.h +++ b/src/ripple/app/tx/impl/InvariantCheck.h @@ -165,7 +165,7 @@ class AccountRootsNotDeleted /** * @brief Invariant: An account XRP balance must be in XRP and take a value - * between 0 and SYSTEM_CURRENCY_START drops, inclusive. + * between 0 and INITIAL_XRP drops, inclusive. * * We iterate all account roots modified by the transaction and ensure that * their XRP balances are reasonable. @@ -270,7 +270,7 @@ class NoBadOffers /** * @brief Invariant: an escrow entry must take a value between 0 and - * SYSTEM_CURRENCY_START drops exclusive. + * INITIAL_XRP drops exclusive. */ class NoZeroEscrow { diff --git a/src/ripple/app/tx/impl/PayChan.cpp b/src/ripple/app/tx/impl/PayChan.cpp index 6efe2aa9a56..8659e01435a 100644 --- a/src/ripple/app/tx/impl/PayChan.cpp +++ b/src/ripple/app/tx/impl/PayChan.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -29,7 +30,6 @@ #include #include #include -#include namespace ripple { diff --git a/src/ripple/app/tx/impl/SetRegularKey.cpp b/src/ripple/app/tx/impl/SetRegularKey.cpp index b6e3ad3142a..96afc71f198 100644 --- a/src/ripple/app/tx/impl/SetRegularKey.cpp +++ b/src/ripple/app/tx/impl/SetRegularKey.cpp @@ -24,7 +24,7 @@ namespace ripple { -std::uint64_t +FeeUnit64 SetRegularKey::calculateBaseFee ( ReadView const& view, STTx const& tx) @@ -41,7 +41,7 @@ SetRegularKey::calculateBaseFee ( if (sle && (! (sle->getFlags () & lsfPasswordSpent))) { // flag is armed and they signed with the right account - return 0; + return FeeUnit64{0}; } } } diff --git a/src/ripple/app/tx/impl/SetRegularKey.h b/src/ripple/app/tx/impl/SetRegularKey.h index 0b51f5841c5..b8001e83ec2 100644 --- a/src/ripple/app/tx/impl/SetRegularKey.h +++ b/src/ripple/app/tx/impl/SetRegularKey.h @@ -48,7 +48,7 @@ class SetRegularKey preflight (PreflightContext const& ctx); static - std::uint64_t + FeeUnit64 calculateBaseFee ( ReadView const& view, STTx const& tx); diff --git a/src/ripple/app/tx/impl/Transactor.cpp b/src/ripple/app/tx/impl/Transactor.cpp index 3fcf68c5b5d..0c093b1ed0a 100644 --- a/src/ripple/app/tx/impl/Transactor.cpp +++ b/src/ripple/app/tx/impl/Transactor.cpp @@ -122,7 +122,8 @@ Transactor::Transactor( { } -std::uint64_t Transactor::calculateBaseFee ( +FeeUnit64 +Transactor::calculateBaseFee ( ReadView const& view, STTx const& tx) { @@ -131,13 +132,12 @@ std::uint64_t Transactor::calculateBaseFee ( // The computation has two parts: // * The base fee, which is the same for most transactions. // * The additional cost of each multisignature on the transaction. - std::uint64_t baseFee = view.fees().units; + FeeUnit64 const baseFee = safe_cast(view.fees().units); // Each signer adds one more baseFee to the minimum required fee // for the transaction. - std::uint32_t signerCount = 0; - if (tx.isFieldPresent (sfSigners)) - signerCount = tx.getFieldArray (sfSigners).size(); + std::size_t const signerCount = tx.isFieldPresent (sfSigners) ? + tx.getFieldArray (sfSigners).size() : 0; return baseFee + (signerCount * baseFee); } @@ -149,7 +149,7 @@ Transactor::calculateFeePaid(STTx const& tx) } XRPAmount -Transactor::minimumFee (Application& app, std::uint64_t baseFee, +Transactor::minimumFee (Application& app, FeeUnit64 baseFee, Fees const& fees, ApplyFlags flags) { return scaleFeeLoad (baseFee, app.getFeeTrack (), @@ -164,7 +164,7 @@ Transactor::calculateMaxSpend(STTx const& tx) TER Transactor::checkFee (PreclaimContext const& ctx, - std::uint64_t baseFee) + FeeUnit64 baseFee) { auto const feePaid = calculateFeePaid(ctx.tx); if (!isLegalAmount (feePaid) || feePaid < beast::zero) diff --git a/src/ripple/app/tx/impl/Transactor.h b/src/ripple/app/tx/impl/Transactor.h index e69203b657b..e5a4c90f89f 100644 --- a/src/ripple/app/tx/impl/Transactor.h +++ b/src/ripple/app/tx/impl/Transactor.h @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include @@ -122,7 +122,7 @@ class Transactor static TER - checkFee (PreclaimContext const& ctx, std::uint64_t baseFee); + checkFee (PreclaimContext const& ctx, FeeUnit64 baseFee); static NotTEC @@ -130,7 +130,7 @@ class Transactor // Returns the fee in fee units, not scaled for load. static - std::uint64_t + FeeUnit64 calculateBaseFee ( ReadView const& view, STTx const& tx); @@ -182,7 +182,7 @@ class Transactor */ static XRPAmount - minimumFee (Application& app, std::uint64_t baseFee, + minimumFee (Application& app, FeeUnit64 baseFee, Fees const& fees, ApplyFlags flags); private: diff --git a/src/ripple/app/tx/impl/applySteps.cpp b/src/ripple/app/tx/impl/applySteps.cpp index e42557150c4..30cf785f43f 100644 --- a/src/ripple/app/tx/impl/applySteps.cpp +++ b/src/ripple/app/tx/impl/applySteps.cpp @@ -144,7 +144,7 @@ invoke_preclaim (PreclaimContext const& ctx) } static -std::uint64_t +FeeUnit64 invoke_calculateBaseFee( ReadView const& view, STTx const& tx) @@ -175,7 +175,7 @@ invoke_calculateBaseFee( case ttFEE: return Change::calculateBaseFee(view, tx); default: assert(false); - return 0; + return FeeUnit64{0}; } } @@ -314,7 +314,7 @@ preclaim (PreflightResult const& preflightResult, } } -std::uint64_t +FeeUnit64 calculateBaseFee(ReadView const& view, STTx const& tx) { diff --git a/src/ripple/basics/FeeUnits.h b/src/ripple/basics/FeeUnits.h new file mode 100644 index 00000000000..2af068313d1 --- /dev/null +++ b/src/ripple/basics/FeeUnits.h @@ -0,0 +1,576 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2019 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. +*/ + +#ifndef BASICS_FEES_H_INCLUDED +#define BASICS_FEES_H_INCLUDED + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace ripple { + +namespace feeunit { + +/** "fee units" calculations are a not-really-unitless value that is used + to express the cost of a given transaction vs. a reference transaction. + They are primarily used by the Transactor classes. */ +struct feeunitTag; +/** "fee levels" are used by the transaction queue to compare the relative + cost of transactions that require different levels of effort to process. + See also: src/ripple/app/misc/FeeEscalation.md#fee-level */ +struct feelevelTag; +/** unitless values are plain scalars wrapped in a TaggedFee. They are + used for calculations in this header. */ +struct unitlessTag; + +template +using enable_if_unit_t = typename std::enable_if_t< + std::is_class_v && + std::is_object_v && + std::is_object_v +>; + +/** `is_usable_unit_v` is checked to ensure that only values with + known valid type tags can be used (sometimes transparently) in + non-fee contexts. At the time of implementation, this includes + all known tags, but more may be added in the future, and they + should not be added automatically unless determined to be + appropriate. +*/ +template > +constexpr bool is_usable_unit_v = + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v; + +template +class TaggedFee + : private boost::totally_ordered > + , private boost::additive > + , private boost::equality_comparable, T> + , private boost::dividable , T> + , private boost::modable , T> + , private boost::unit_steppable > +{ +public: + using unit_type = UnitTag; + using value_type = T; +private: + value_type fee_; + +protected: + template + static constexpr bool is_compatible_v = + std::is_arithmetic_v && std::is_arithmetic_v && + std::is_convertible_v; + + template > + static constexpr bool is_compatiblefee_v = + is_compatible_v && + std::is_same_v; + + template + using enable_if_compatible_t = + typename std::enable_if_t>; + + template + using enable_if_compatiblefee_t = + typename std::enable_if_t>; + +public: + TaggedFee () = default; + constexpr TaggedFee (TaggedFee const& other) = default; + constexpr TaggedFee& operator= (TaggedFee const& other) = default; + + constexpr + explicit + TaggedFee (beast::Zero) + : fee_ (0) + { + } + + constexpr + TaggedFee& + operator= (beast::Zero) + { + fee_ = 0; + return *this; + } + + constexpr + explicit + TaggedFee (value_type fee) + : fee_ (fee) + { + } + + TaggedFee& + operator= (value_type fee) + { + fee_ = fee; + return *this; + } + + /** Instances with the same unit, and a type that is + "safe" to covert to this one can be converted + implicitly */ + template && + is_safetocasttovalue_v >> + constexpr + TaggedFee(TaggedFee const& fee) + : TaggedFee (safe_cast (fee.fee())) + { + } + + constexpr + TaggedFee + operator* (value_type const& rhs) const + { + return TaggedFee{ fee_ * rhs }; + } + + friend + constexpr + TaggedFee + operator*(value_type lhs, TaggedFee const& rhs) + { + // multiplication is commutative + return rhs * lhs; + } + + constexpr + value_type + operator/ (TaggedFee const& rhs) const + { + return fee_ / rhs.fee_; + } + + TaggedFee& + operator+= (TaggedFee const& other) + { + fee_ += other.fee(); + return *this; + } + + TaggedFee& + operator-= (TaggedFee const& other) + { + fee_ -= other.fee(); + return *this; + } + + TaggedFee& + operator++() + { + ++fee_; + return *this; + } + + TaggedFee& + operator--() + { + --fee_; + return *this; + } + + TaggedFee& + operator*= (value_type const& rhs) + { + fee_ *= rhs; + return *this; + } + + TaggedFee& + operator/= (value_type const& rhs) + { + fee_ /= rhs; + return *this; + } + + template + std::enable_if_t, + TaggedFee&> + operator%= (value_type const& rhs) + { + fee_ %= rhs; + return *this; + } + + TaggedFee + operator- () const + { + static_assert( std::is_signed_v, + "- operator illegal on unsigned fee types"); + return TaggedFee{ -fee_ }; + } + + bool + operator==(TaggedFee const& other) const + { + return fee_ == other.fee_; + } + + template > + bool + operator==(TaggedFee const& other) const + { + return fee_ == other.fee(); + } + + bool + operator==(value_type other) const + { + return fee_ == other; + } + + template > + bool + operator!=(TaggedFee const& other) const + { + return !operator==(other); + } + + bool + operator<(TaggedFee const& other) const + { + return fee_ < other.fee_; + } + + /** Returns true if the amount is not zero */ + explicit + constexpr + operator bool() const noexcept + { + return fee_ != 0; + } + + /** Return the sign of the amount */ + constexpr + int + signum() const noexcept + { + return (fee_ < 0) ? -1 : (fee_ ? 1 : 0); + } + + /** Returns the number of drops */ + constexpr + value_type + fee () const + { + return fee_; + } + + template + constexpr + double + decimalFromReference (TaggedFee reference) const + { + return static_cast(fee_) / reference.fee(); + } + + // `is_usable_unit_v` is checked to ensure that only values with + // known valid type tags can be converted to JSON. At the time + // of implementation, that includes all known tags, but more may + // be added in the future. + std::enable_if_t, + Json::Value> + jsonClipped() const + { + if constexpr (std::is_integral_v) + { + using jsontype = std::conditional_t, + Json::Int, Json::UInt>; + + constexpr auto min = std::numeric_limits::min(); + constexpr auto max = std::numeric_limits::max(); + + if (fee_ < min) + return min; + if (fee_ > max) + return max; + return static_cast(fee_); + } + else + { + return fee_; + } + } + + /** Returns the underlying value. Code SHOULD NOT call this + function unless the type has been abstracted away, + e.g. in a templated function. + */ + constexpr + value_type + value () const + { + return fee_; + } + + friend + std::istream& + operator>> (std::istream& s, TaggedFee& val) + { + s >> val.fee_; + return s; + } + +}; + +// Output Fees as just their numeric value. +template +std::basic_ostream& +operator<<(std::basic_ostream& os, + const TaggedFee& q) +{ + return os << q.value(); +} + +template +std::string +to_string (TaggedFee const& amount) +{ + return std::to_string (amount.fee ()); +} + +template > +constexpr bool can_muldiv_source_v = + std::is_convertible_v; + +template > +constexpr bool can_muldiv_dest_v = + can_muldiv_source_v && // Dest is also a source + std::is_convertible_v && + sizeof(typename Dest::value_type) >= sizeof(std::uint64_t); + +template, + class = enable_if_unit_t> +constexpr bool can_muldiv_sources_v = + can_muldiv_source_v && + can_muldiv_source_v && + std::is_same_v; + +template, + class = enable_if_unit_t, + class = enable_if_unit_t> +constexpr bool can_muldiv_v = + can_muldiv_sources_v && + can_muldiv_dest_v; + // Source and Dest can be the same by default + +template, + class = enable_if_unit_t, + class = enable_if_unit_t> +constexpr bool can_muldiv_commute_v = + can_muldiv_v && + ! std::is_same_v; + +template +using enable_muldiv_source_t = + typename std::enable_if_t< can_muldiv_source_v >; + +template +using enable_muldiv_dest_t = + typename std::enable_if_t< can_muldiv_dest_v >; + +template +using enable_muldiv_sources_t = + typename std::enable_if_t< can_muldiv_sources_v >; + +template +using enable_muldiv_t = + typename std::enable_if_t< can_muldiv_v >; + +template +using enable_muldiv_commute_t = + typename std::enable_if_t< can_muldiv_commute_v >; + +template +TaggedFee +scalar(T value) +{ + return TaggedFee{ value }; +} + +template > +std::pair +mulDivU(Source1 value, Dest mul, Source2 div) +{ + // Fees can never be negative in any context. + if(value.value() < 0 || mul.value() < 0 || div.value() < 0) + { + // split the asserts so if one hits, the user can tell which + // without a debugger. + assert(value.value() >= 0); + assert(mul.value() >= 0); + assert(div.value() >= 0); + return { false, Dest{ 0 } }; + } + + using desttype = typename Dest::value_type; + constexpr auto max = + std::numeric_limits::max(); + + // Shortcuts, since these happen a lot in the real world + if (value == div) + return { true, mul }; + if (mul.value() == div.value()) + { + if (value.value() > max) + return{ false, Dest{max} }; + return { true, + Dest{ static_cast(value.value()) } }; + } + + using namespace boost::multiprecision; + + uint128_t product; + product = multiply(product, + static_cast(value.value()), + static_cast(mul.value())); + + auto quotient = product / div.value(); + + if (quotient > max) + return { false, Dest{ max } }; + + return { true, Dest{ static_cast(quotient) } }; +} + +} // feeunit + +template +using FeeUnit = feeunit::TaggedFee; +using FeeUnit32 = FeeUnit; +using FeeUnit64 = FeeUnit; + +template +using FeeLevel = feeunit::TaggedFee; +using FeeLevel64 = FeeLevel; +using FeeLevelDouble = FeeLevel; + +template > +std::pair +mulDiv(Source1 value, Dest mul, Source2 div) +{ + return feeunit::mulDivU(value, mul, div); +} + +template > +std::pair +mulDiv(Dest value, Source1 mul, Source2 div) +{ + // Multiplication is commutative + return feeunit::mulDivU(mul, value, div); +} + +template > +std::pair +mulDiv(std::uint64_t value, + Dest mul, + std::uint64_t div) +{ + // Give the scalars a non-tag so the + // unit-handling version gets called. + return feeunit::mulDivU(feeunit::scalar(value), mul, feeunit::scalar(div)); +} + +template > +std::pair +mulDiv(Dest value, std::uint64_t mul, std::uint64_t div) +{ + // Multiplication is commutative + return mulDiv(mul, value, div); +} + +template > +std::pair +mulDiv(Source1 value, + std::uint64_t mul, + Source2 div) +{ + // Give the scalars a dimensionless unit so the + // unit-handling version gets called. + auto unitresult = feeunit::mulDivU(value, feeunit::scalar(mul), div); + return { unitresult.first, unitresult.second.value() }; +} + +template > +std::pair +mulDiv(std::uint64_t value, Source1 mul, Source2 div) +{ + // Multiplication is commutative + return mulDiv(mul, value, div); +} + +template +constexpr +std::enable_if_t +< + std::is_same_v && + std::is_integral_v && + std::is_integral_v, + Dest +> +safe_cast(Src s) noexcept +{ + // Dest may not have an explicit value constructor + return Dest{ safe_cast(s.value()) }; +} + +template +constexpr +std::enable_if_t +< + std::is_same_v && + std::is_integral_v && + std::is_integral_v, + Dest +> +unsafe_cast(Src s) noexcept +{ + // Dest may not have an explicit value constructor + return Dest{ unsafe_cast(s.value()) }; +} + +} // ripple + +#endif // BASICS_FEES_H_INCLUDED diff --git a/src/ripple/protocol/IOUAmount.h b/src/ripple/basics/IOUAmount.h similarity index 97% rename from src/ripple/protocol/IOUAmount.h rename to src/ripple/basics/IOUAmount.h index 5ae3e5d1457..c888c158077 100644 --- a/src/ripple/protocol/IOUAmount.h +++ b/src/ripple/basics/IOUAmount.h @@ -17,8 +17,8 @@ */ //============================================================================== -#ifndef RIPPLE_PROTOCOL_IOUAMOUNT_H_INCLUDED -#define RIPPLE_PROTOCOL_IOUAMOUNT_H_INCLUDED +#ifndef RIPPLE_BASICS_IOUAMOUNT_H_INCLUDED +#define RIPPLE_BASICS_IOUAMOUNT_H_INCLUDED #include #include diff --git a/src/ripple/basics/XRPAmount.h b/src/ripple/basics/XRPAmount.h new file mode 100644 index 00000000000..ba38479f2e1 --- /dev/null +++ b/src/ripple/basics/XRPAmount.h @@ -0,0 +1,318 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 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. +*/ +//============================================================================== + +#ifndef RIPPLE_BASICS_XRPAMOUNT_H_INCLUDED +#define RIPPLE_BASICS_XRPAMOUNT_H_INCLUDED + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace ripple { + +namespace feeunit { + +/** "drops" are the smallest divisible amount of XRP. This is what most + of the code uses. */ +struct dropTag; + +} // feeunit + +class XRPAmount + : private boost::totally_ordered + , private boost::additive + , private boost::equality_comparable + , private boost::additive +{ +public: + using unit_type = feeunit::dropTag; + using value_type = std::int64_t; +private: + value_type drops_; + +public: + XRPAmount () = default; + constexpr XRPAmount (XRPAmount const& other) = default; + constexpr XRPAmount& operator= (XRPAmount const& other) = default; + + constexpr + XRPAmount (beast::Zero) + : drops_ (0) + { + } + + constexpr + XRPAmount& + operator= (beast::Zero) + { + drops_ = 0; + return *this; + } + + constexpr + XRPAmount (value_type drops) + : drops_ (drops) + { + } + + XRPAmount& + operator= (value_type drops) + { + drops_ = drops; + return *this; + } + + constexpr + XRPAmount + operator*(value_type const& rhs) const + { + return XRPAmount{ drops_ * rhs }; + } + + friend + constexpr + XRPAmount + operator*(value_type lhs, XRPAmount const& rhs) + { + // multiplication is commutative + return rhs * lhs; + } + + XRPAmount& + operator+= (XRPAmount const& other) + { + drops_ += other.drops(); + return *this; + } + + XRPAmount& + operator-= (XRPAmount const& other) + { + drops_ -= other.drops(); + return *this; + } + + XRPAmount& + operator+= (value_type const& rhs) + { + drops_ += rhs; + return *this; + } + + XRPAmount& + operator-= (value_type const& rhs) + { + drops_ -= rhs; + return *this; + } + + XRPAmount& + operator*= (value_type const& rhs) + { + drops_ *= rhs; + return *this; + } + + XRPAmount + operator- () const + { + return XRPAmount{ -drops_ }; + } + + bool + operator==(XRPAmount const& other) const + { + return drops_ == other.drops_; + } + + bool + operator==(value_type other) const + { + return drops_ == other; + } + + bool + operator<(XRPAmount const& other) const + { + return drops_ < other.drops_; + } + + /** Returns true if the amount is not zero */ + explicit + constexpr + operator bool() const noexcept + { + return drops_ != 0; + } + + /** Return the sign of the amount */ + constexpr + int + signum() const noexcept + { + return (drops_ < 0) ? -1 : (drops_ ? 1 : 0); + } + + /** Returns the number of drops */ + constexpr + value_type + drops () const + { + return drops_; + } + + constexpr + double + decimalXRP () const; + + template + boost::optional + dropsAs() const + { + if ((drops_ > std::numeric_limits::max()) || + (!std::numeric_limits::is_signed && drops_ < 0) || + (std::numeric_limits::is_signed && + drops_ < std::numeric_limits::lowest())) + { + return boost::none; + } + return static_cast(drops_); + } + + template + Dest + dropsAs(Dest defaultValue) const + { + return dropsAs().value_or(defaultValue); + } + + template + Dest + dropsAs(XRPAmount defaultValue) const + { + return dropsAs().value_or(defaultValue.drops()); + } + + Json::Value + jsonClipped() const + { + static_assert(std::is_signed_v && + std::is_integral_v, + "Expected XRPAmount to be a signed integral type"); + + constexpr auto min = std::numeric_limits::min(); + constexpr auto max = std::numeric_limits::max(); + + if (drops_ < min) + return min; + if (drops_ > max) + return max; + return static_cast(drops_); + } + + /** Returns the underlying value. Code SHOULD NOT call this + function unless the type has been abstracted away, + e.g. in a templated function. + */ + constexpr + value_type + value () const + { + return drops_; + } + + friend + std::istream& + operator>> (std::istream& s, XRPAmount& val) + { + s >> val.drops_; + return s; + } + +}; + +/** Number of drops per 1 XRP */ +constexpr +XRPAmount +DROPS_PER_XRP{1'000'000}; + +constexpr +double +XRPAmount::decimalXRP () const +{ + return static_cast(drops_) / DROPS_PER_XRP.drops(); +} + +// Output XRPAmount as just the drops value. +template +std::basic_ostream& +operator<<(std::basic_ostream& os, + const XRPAmount& q) +{ + return os << q.drops(); +} + +inline +std::string +to_string (XRPAmount const& amount) +{ + return std::to_string (amount.drops ()); +} + +inline +XRPAmount +mulRatio ( + XRPAmount const& amt, + std::uint32_t num, + std::uint32_t den, + bool roundUp) +{ + using namespace boost::multiprecision; + + if (!den) + Throw ("division by zero"); + + int128_t const amt128 (amt.drops ()); + auto const neg = amt.drops () < 0; + auto const m = amt128 * num; + auto r = m / den; + if (m % den) + { + if (!neg && roundUp) + r += 1; + if (neg && !roundUp) + r -= 1; + } + if (r > std::numeric_limits::max ()) + Throw ("XRP mulRatio overflow"); + return XRPAmount (r.convert_to ()); +} + +} + +#endif // RIPPLE_BASICS_XRPAMOUNT_H_INCLUDED diff --git a/src/ripple/protocol/impl/IOUAmount.cpp b/src/ripple/basics/impl/IOUAmount.cpp similarity index 99% rename from src/ripple/protocol/impl/IOUAmount.cpp rename to src/ripple/basics/impl/IOUAmount.cpp index aaa5d8b108e..7a2806f8f6c 100644 --- a/src/ripple/protocol/impl/IOUAmount.cpp +++ b/src/ripple/basics/impl/IOUAmount.cpp @@ -18,7 +18,7 @@ //============================================================================== #include -#include +#include #include #include #include diff --git a/src/ripple/basics/impl/mulDiv.cpp b/src/ripple/basics/impl/mulDiv.cpp index 69fc2483430..232817aaeab 100644 --- a/src/ripple/basics/impl/mulDiv.cpp +++ b/src/ripple/basics/impl/mulDiv.cpp @@ -36,7 +36,7 @@ mulDiv(std::uint64_t value, std::uint64_t mul, std::uint64_t div) result /= div; - auto const limit = std::numeric_limits::max(); + auto constexpr limit = std::numeric_limits::max(); if (result > limit) return { false, limit }; diff --git a/src/ripple/basics/safe_cast.h b/src/ripple/basics/safe_cast.h index 80422c02d7c..0e1d1aada8e 100644 --- a/src/ripple/basics/safe_cast.h +++ b/src/ripple/basics/safe_cast.h @@ -20,6 +20,7 @@ #ifndef RIPPLE_BASICS_SAFE_CAST_H_INCLUDED #define RIPPLE_BASICS_SAFE_CAST_H_INCLUDED +#include #include namespace ripple { @@ -28,20 +29,31 @@ namespace ripple { // the destination can hold all values of the source. This is particularly // handy when the source or destination is an enumeration type. +template +static constexpr bool is_safetocasttovalue_v = + ( std::is_integral_v && + std::is_integral_v ) && + ( std::is_signed::value || + std::is_unsigned::value ) && + ( std::is_signed::value != + std::is_signed::value ? + sizeof(Dest) > sizeof(Src) : + sizeof(Dest) >= sizeof(Src) ); + template inline constexpr std::enable_if_t < - std::is_integral::value && std::is_integral::value, + std::is_integral_v && std::is_integral_v, Dest > safe_cast(Src s) noexcept { - static_assert(std::is_signed::value || std::is_unsigned::value, + static_assert(std::is_signed_v || std::is_unsigned_v, "Cannot cast signed to unsigned"); - constexpr unsigned not_same = std::is_signed::value != - std::is_signed::value; + constexpr unsigned not_same = std::is_signed_v != + std::is_signed_v; static_assert(sizeof(Dest) >= sizeof(Src) + not_same, "Destination is too small to hold all values of source"); return static_cast(s); @@ -52,7 +64,7 @@ inline constexpr std::enable_if_t < - std::is_enum::value && std::is_integral::value, + std::is_enum_v && std::is_integral_v, Dest > safe_cast(Src s) noexcept @@ -65,7 +77,7 @@ inline constexpr std::enable_if_t < - std::is_integral::value && std::is_enum::value, + std::is_integral_v && std::is_enum_v, Dest > safe_cast(Src s) noexcept @@ -73,6 +85,52 @@ safe_cast(Src s) noexcept return safe_cast(static_cast>(s)); } +// unsafe_cast explicitly flags a static_cast as not necessarily able to hold +// all values of the source. It includes a compile-time check so that if +// underlying types become safe, it can be converted to a safe_cast. + +template +inline +constexpr +std::enable_if_t +< + std::is_integral_v && std::is_integral_v, + Dest +> +unsafe_cast(Src s) noexcept +{ + static_assert(! is_safetocasttovalue_v, + "Only unsafe if casting signed to unsigned or " + "destination is too small"); + return static_cast(s); +} + +template +inline +constexpr +std::enable_if_t +< + std::is_enum_v && std::is_integral_v, + Dest +> +unsafe_cast(Src s) noexcept +{ + return static_cast(unsafe_cast>(s)); +} + +template +inline +constexpr +std::enable_if_t +< + std::is_integral_v && std::is_enum_v, + Dest +> +unsafe_cast(Src s) noexcept +{ + return unsafe_cast(static_cast>(s)); +} + } // ripple #endif diff --git a/src/ripple/basics/tagged_integer.h b/src/ripple/basics/tagged_integer.h index 5267fa9359b..4afe0847a90 100644 --- a/src/ripple/basics/tagged_integer.h +++ b/src/ripple/basics/tagged_integer.h @@ -68,9 +68,9 @@ class tagged_integer std::is_integral::value && sizeof(OtherInt) <= sizeof(Int)>::type> explicit - /* constexpr */ - tagged_integer(OtherInt value) noexcept - : m_value(value) + constexpr + tagged_integer(OtherInt value) noexcept + : m_value(value) { static_assert( sizeof(tagged_integer) == sizeof(Int), diff --git a/src/ripple/core/Config.h b/src/ripple/core/Config.h index dc6953e5394..e1200c63cef 100644 --- a/src/ripple/core/Config.h +++ b/src/ripple/core/Config.h @@ -22,6 +22,7 @@ #include #include +#include #include // VFALCO Breaks levelization #include #include @@ -131,14 +132,18 @@ class Config : public BasicConfig std::string START_LEDGER; // Network parameters - int const TRANSACTION_FEE_BASE = 10; // The number of fee units a reference transaction costs + + // The number of fee units a reference transaction costs + static constexpr FeeUnit32 TRANSACTION_FEE_BASE{ 10 }; // Note: The following parameters do not relate to the UNL or trust at all // Minimum number of nodes to consider the network present std::size_t NETWORK_QUORUM = 1; // Peer networking parameters - bool PEER_PRIVATE = false; // True to ask peers not to relay current IP. + + // True to ask peers not to relay current IP. + bool PEER_PRIVATE = false; std::size_t PEERS_MAX = 0; std::chrono::seconds WEBSOCKET_PING_FREQ = std::chrono::minutes {5}; @@ -152,9 +157,9 @@ class Config : public BasicConfig // Validation boost::optional VALIDATION_QUORUM; // validations to consider ledger authoritative - std::uint64_t FEE_DEFAULT = 10; - std::uint64_t FEE_ACCOUNT_RESERVE = 200*SYSTEM_CURRENCY_PARTS; - std::uint64_t FEE_OWNER_RESERVE = 50*SYSTEM_CURRENCY_PARTS; + XRPAmount FEE_DEFAULT{10}; + XRPAmount FEE_ACCOUNT_RESERVE{200 * DROPS_PER_XRP}; + XRPAmount FEE_OWNER_RESERVE{50 * DROPS_PER_XRP}; // Node storage configuration std::uint32_t LEDGER_HISTORY = 256; diff --git a/src/ripple/core/impl/Config.cpp b/src/ripple/core/impl/Config.cpp index 28c8bd47313..b49bcebffb9 100644 --- a/src/ripple/core/impl/Config.cpp +++ b/src/ripple/core/impl/Config.cpp @@ -200,6 +200,8 @@ getEnvVar (char const* name) return value; } +constexpr FeeUnit32 Config::TRANSACTION_FEE_BASE; + void Config::setupControl(bool bQuiet, bool bSilent, bool bStandalone) { @@ -406,13 +408,13 @@ void Config::loadFromString (std::string const& fileContents) NETWORK_QUORUM = beast::lexicalCastThrow(strTemp); if (getSingleSection (secConfig, SECTION_FEE_ACCOUNT_RESERVE, strTemp, j_)) - FEE_ACCOUNT_RESERVE = beast::lexicalCastThrow (strTemp); + FEE_ACCOUNT_RESERVE = beast::lexicalCastThrow (strTemp); if (getSingleSection (secConfig, SECTION_FEE_OWNER_RESERVE, strTemp, j_)) - FEE_OWNER_RESERVE = beast::lexicalCastThrow (strTemp); + FEE_OWNER_RESERVE = beast::lexicalCastThrow (strTemp); if (getSingleSection (secConfig, SECTION_FEE_DEFAULT, strTemp, j_)) - FEE_DEFAULT = beast::lexicalCastThrow (strTemp); + FEE_DEFAULT = beast::lexicalCastThrow (strTemp); if (getSingleSection (secConfig, SECTION_LEDGER_HISTORY, strTemp, j_)) { diff --git a/src/ripple/json/impl/json_assert.h b/src/ripple/json/impl/json_assert.h index fc8ab335aa4..c4af5c04435 100644 --- a/src/ripple/json/impl/json_assert.h +++ b/src/ripple/json/impl/json_assert.h @@ -20,7 +20,7 @@ #ifndef RIPPLE_JSON_JSON_ASSERT_H_INCLUDED #define RIPPLE_JSON_JSON_ASSERT_H_INCLUDED -#include "ripple/json/json_errors.h" +#include #define JSON_ASSERT_UNREACHABLE assert( false ) #define JSON_ASSERT( condition ) assert( condition ); // @todo <= change this into an exception throw diff --git a/src/ripple/ledger/OpenView.h b/src/ripple/ledger/OpenView.h index bda42e0fe9b..d25bf77060f 100644 --- a/src/ripple/ledger/OpenView.h +++ b/src/ripple/ledger/OpenView.h @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/ripple/ledger/ReadView.h b/src/ripple/ledger/ReadView.h index f8135f739ff..15c5c79e017 100644 --- a/src/ripple/ledger/ReadView.h +++ b/src/ripple/ledger/ReadView.h @@ -22,12 +22,13 @@ #include #include +#include +#include +#include #include -#include #include #include #include -#include #include #include #include @@ -45,10 +46,10 @@ namespace ripple { */ struct Fees { - std::uint64_t base = 0; // Reference tx cost (drops) - std::uint32_t units = 0; // Reference fee units - std::uint32_t reserve = 0; // Reserve base (drops) - std::uint32_t increment = 0; // Reserve increment (drops) + XRPAmount base{ 0 }; // Reference tx cost (drops) + FeeUnit32 units{ 0 }; // Reference fee units + XRPAmount reserve{ 0 }; // Reserve base (drops) + XRPAmount increment{ 0 }; // Reserve increment (drops) explicit Fees() = default; Fees (Fees const&) = default; @@ -62,7 +63,13 @@ struct Fees XRPAmount accountReserve (std::size_t ownerCount) const { - return { reserve + ownerCount * increment }; + return reserve + ownerCount * increment; + } + + std::pair + toDrops(FeeUnit64 const& fee) const + { + return mulDiv(base, fee, units); } }; diff --git a/src/ripple/ledger/detail/ApplyStateTable.h b/src/ripple/ledger/detail/ApplyStateTable.h index 66c5dd224a9..1bbfbe30e1c 100644 --- a/src/ripple/ledger/detail/ApplyStateTable.h +++ b/src/ripple/ledger/detail/ApplyStateTable.h @@ -24,8 +24,8 @@ #include #include #include +#include #include -#include #include #include @@ -51,7 +51,7 @@ class ApplyStateTable std::pair>>; items_t items_; - XRPAmount dropsDestroyed_ = 0; + XRPAmount dropsDestroyed_{0}; public: ApplyStateTable() = default; diff --git a/src/ripple/ledger/detail/ApplyViewBase.h b/src/ripple/ledger/detail/ApplyViewBase.h index cf7a52a74b7..91bf1e02dca 100644 --- a/src/ripple/ledger/detail/ApplyViewBase.h +++ b/src/ripple/ledger/detail/ApplyViewBase.h @@ -25,7 +25,7 @@ #include #include #include -#include +#include namespace ripple { namespace detail { diff --git a/src/ripple/ledger/detail/RawStateTable.h b/src/ripple/ledger/detail/RawStateTable.h index 651b6b60ce5..fcd2d2836ce 100644 --- a/src/ripple/ledger/detail/RawStateTable.h +++ b/src/ripple/ledger/detail/RawStateTable.h @@ -95,7 +95,7 @@ class RawStateTable std::pair>>, false>>; items_t items_; - XRPAmount dropsDestroyed_ = 0; + XRPAmount dropsDestroyed_{0}; }; } // detail diff --git a/src/ripple/ledger/impl/View.cpp b/src/ripple/ledger/impl/View.cpp index e454c33305e..fdabe6b04dd 100644 --- a/src/ripple/ledger/impl/View.cpp +++ b/src/ripple/ledger/impl/View.cpp @@ -304,9 +304,9 @@ xrpLiquid (ReadView const& view, AccountID const& id, " amount=" << amount.getFullText() << " fullBalance=" << fullBalance.getFullText() << " balance=" << balance.getFullText() << - " reserve=" << to_string (reserve) << - " ownerCount=" << to_string (ownerCount) << - " ownerCountAdj=" << to_string (ownerCountAdj); + " reserve=" << reserve << + " ownerCount=" << ownerCount << + " ownerCountAdj=" << ownerCountAdj; return amount.xrp(); } @@ -328,9 +328,9 @@ xrpLiquid (ReadView const& view, AccountID const& id, " account=" << to_string (id) << " amount=" << amount.getFullText() << " balance=" << balance.getFullText() << - " reserve=" << to_string (reserve) << - " ownerCount=" << to_string (ownerCount) << - " ownerCountAdj=" << to_string (ownerCountAdj); + " reserve=" << reserve << + " ownerCount=" << ownerCount << + " ownerCountAdj=" << ownerCountAdj; return view.balanceHook(id, xrpAccount(), amount).xrp(); } diff --git a/src/ripple/protocol/AmountConversions.h b/src/ripple/protocol/AmountConversions.h index 3525b848396..86e7c36e80a 100644 --- a/src/ripple/protocol/AmountConversions.h +++ b/src/ripple/protocol/AmountConversions.h @@ -20,8 +20,8 @@ #ifndef RIPPLE_PROTOCOL_AMOUNTCONVERSION_H_INCLUDED #define RIPPLE_PROTOCOL_AMOUNTCONVERSION_H_INCLUDED -#include -#include +#include +#include #include namespace ripple { diff --git a/src/ripple/protocol/PayChan.h b/src/ripple/protocol/PayChan.h index 90352b8a820..5957668e7e1 100644 --- a/src/ripple/protocol/PayChan.h +++ b/src/ripple/protocol/PayChan.h @@ -21,9 +21,9 @@ #define RIPPLE_PROTOCOL_PAYCHAN_H_INCLUDED #include +#include #include #include -#include namespace ripple { diff --git a/src/ripple/protocol/Quality.h b/src/ripple/protocol/Quality.h index 3443640ac5a..e2048ed3c2b 100644 --- a/src/ripple/protocol/Quality.h +++ b/src/ripple/protocol/Quality.h @@ -20,10 +20,10 @@ #ifndef RIPPLE_PROTOCOL_QUALITY_H_INCLUDED #define RIPPLE_PROTOCOL_QUALITY_H_INCLUDED +#include +#include #include -#include #include -#include #include #include @@ -104,7 +104,7 @@ operator!= ( //------------------------------------------------------------------------------ // Ripple specific constant used for parsing qualities and other things -#define QUALITY_ONE 1000000000 +#define QUALITY_ONE 1'000'000'000 /** Represents the logical ratio of output currency to input currency. Internally this is stored using a custom floating point representation, diff --git a/src/ripple/protocol/STAmount.h b/src/ripple/protocol/STAmount.h index e4d4cbd0343..f8994bcc705 100644 --- a/src/ripple/protocol/STAmount.h +++ b/src/ripple/protocol/STAmount.h @@ -21,13 +21,13 @@ #define RIPPLE_PROTOCOL_STAMOUNT_H_INCLUDED #include +#include #include +#include #include #include #include #include -#include -#include #include namespace ripple { diff --git a/src/ripple/protocol/STObject.h b/src/ripple/protocol/STObject.h index 37a36ac0dd0..5f94aadabf3 100644 --- a/src/ripple/protocol/STObject.h +++ b/src/ripple/protocol/STObject.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -81,7 +82,7 @@ class STObject template std::enable_if_t< - std::is_assignable::value, + std::is_assignable_v, ValueProxy&> operator= (U&& u); @@ -209,7 +210,7 @@ class STObject template std::enable_if_t< - std::is_assignable::value, + std::is_assignable_v, OptionalProxy&> operator= (U&& u); @@ -726,7 +727,7 @@ STObject::Proxy::assign(U&& u) template template std::enable_if_t< - std::is_assignable::value, + std::is_assignable_v, STObject::ValueProxy&> STObject::ValueProxy::operator= (U&& u) { @@ -813,7 +814,7 @@ STObject::OptionalProxy::operator=(optional_type const& v) -> template template std::enable_if_t< - std::is_assignable::value, + std::is_assignable_v, STObject::OptionalProxy&> STObject::OptionalProxy::operator=(U&& u) { diff --git a/src/ripple/protocol/STValidation.h b/src/ripple/protocol/STValidation.h index f09c0219081..d5583308e52 100644 --- a/src/ripple/protocol/STValidation.h +++ b/src/ripple/protocol/STValidation.h @@ -20,6 +20,7 @@ #ifndef RIPPLE_PROTOCOL_STVALIDATION_H_INCLUDED #define RIPPLE_PROTOCOL_STVALIDATION_H_INCLUDED +#include #include #include #include @@ -100,9 +101,9 @@ class STValidation final : public STObject, public CountedObject struct FeeSettings { boost::optional loadFee; - boost::optional baseFee; - boost::optional reserveBase; - boost::optional reserveIncrement; + boost::optional baseFee; + boost::optional reserveBase; + boost::optional reserveIncrement; }; /** Construct, sign and trust a new STValidation diff --git a/src/ripple/protocol/SystemParameters.h b/src/ripple/protocol/SystemParameters.h index ef95206f5c5..ed07733063c 100644 --- a/src/ripple/protocol/SystemParameters.h +++ b/src/ripple/protocol/SystemParameters.h @@ -20,6 +20,7 @@ #ifndef RIPPLE_PROTOCOL_SYSTEMPARAMETERS_H_INCLUDED #define RIPPLE_PROTOCOL_SYSTEMPARAMETERS_H_INCLUDED +#include #include #include @@ -37,23 +38,18 @@ systemName () } /** Configure the native currency. */ -static -std::uint64_t const -SYSTEM_CURRENCY_GIFT = 1000; - -static -std::uint64_t const -SYSTEM_CURRENCY_USERS = 100000000; - -/** Number of drops per 1 XRP */ -static -std::uint64_t const -SYSTEM_CURRENCY_PARTS = 1000000; /** Number of drops in the genesis account. */ -static -std::uint64_t const -SYSTEM_CURRENCY_START = SYSTEM_CURRENCY_GIFT * SYSTEM_CURRENCY_USERS * SYSTEM_CURRENCY_PARTS; +constexpr +XRPAmount +INITIAL_XRP{ 100'000'000'000 * DROPS_PER_XRP }; + +/** Returns true if the amount does not exceed the initial XRP in existence. */ +inline +bool isLegalAmount (XRPAmount const& amount) +{ + return amount <= INITIAL_XRP; +} /* The currency code for the native currency. */ static inline diff --git a/src/ripple/protocol/XRPAmount.h b/src/ripple/protocol/XRPAmount.h deleted file mode 100644 index cd625cd6b9a..00000000000 --- a/src/ripple/protocol/XRPAmount.h +++ /dev/null @@ -1,175 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 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. -*/ -//============================================================================== - -#ifndef RIPPLE_PROTOCOL_XRPAMOUNT_H_INCLUDED -#define RIPPLE_PROTOCOL_XRPAMOUNT_H_INCLUDED - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { - -class XRPAmount - : private boost::totally_ordered - , private boost::additive -{ -private: - std::int64_t drops_; - -public: - XRPAmount () = default; - XRPAmount (XRPAmount const& other) = default; - XRPAmount& operator= (XRPAmount const& other) = default; - - XRPAmount (beast::Zero) - : drops_ (0) - { - } - - XRPAmount& - operator= (beast::Zero) - { - drops_ = 0; - return *this; - } - - template ::value>> - XRPAmount (Integer drops) - : drops_ (static_cast (drops)) - { - } - - template ::value>> - XRPAmount& - operator= (Integer drops) - { - drops_ = static_cast (drops); - return *this; - } - - XRPAmount& - operator+= (XRPAmount const& other) - { - drops_ += other.drops_; - return *this; - } - - XRPAmount& - operator-= (XRPAmount const& other) - { - drops_ -= other.drops_; - return *this; - } - - XRPAmount - operator- () const - { - return { -drops_ }; - } - - bool - operator==(XRPAmount const& other) const - { - return drops_ == other.drops_; - } - - bool - operator<(XRPAmount const& other) const - { - return drops_ < other.drops_; - } - - /** Returns true if the amount is not zero */ - explicit - operator bool() const noexcept - { - return drops_ != 0; - } - - /** Return the sign of the amount */ - int - signum() const noexcept - { - return (drops_ < 0) ? -1 : (drops_ ? 1 : 0); - } - - /** Returns the number of drops */ - std::int64_t - drops () const - { - return drops_; - } -}; - -inline -std::string -to_string (XRPAmount const& amount) -{ - return std::to_string (amount.drops ()); -} - -inline -XRPAmount -mulRatio ( - XRPAmount const& amt, - std::uint32_t num, - std::uint32_t den, - bool roundUp) -{ - using namespace boost::multiprecision; - - if (!den) - Throw ("division by zero"); - - int128_t const amt128 (amt.drops ()); - auto const neg = amt.drops () < 0; - auto const m = amt128 * num; - auto r = m / den; - if (m % den) - { - if (!neg && roundUp) - r += 1; - if (neg && !roundUp) - r -= 1; - } - if (r > std::numeric_limits::max ()) - Throw ("XRP mulRatio overflow"); - return XRPAmount (r.convert_to ()); -} - -/** Returns true if the amount does not exceed the initial XRP in existence. */ -inline -bool isLegalAmount (XRPAmount const& amount) -{ - return amount.drops () <= SYSTEM_CURRENCY_START; -} - -} - -#endif diff --git a/src/ripple/protocol/impl/STAmount.cpp b/src/ripple/protocol/impl/STAmount.cpp index 9bfb883367a..9b653ff0094 100644 --- a/src/ripple/protocol/impl/STAmount.cpp +++ b/src/ripple/protocol/impl/STAmount.cpp @@ -205,6 +205,7 @@ STAmount::STAmount (SField const& name, , mIsNative (true) , mIsNegative (negative) { + assert(mValue <= std::numeric_limits::max()); } STAmount::STAmount (SField const& name, Issue const& issue, @@ -215,6 +216,7 @@ STAmount::STAmount (SField const& name, Issue const& issue, , mOffset (exponent) , mIsNegative (negative) { + assert(mValue <= std::numeric_limits::max()); canonicalize (); } @@ -226,6 +228,7 @@ STAmount::STAmount (std::uint64_t mantissa, bool negative) , mIsNative (true) , mIsNegative (mantissa != 0 && negative) { + assert(mValue <= std::numeric_limits::max()); } STAmount::STAmount (Issue const& issue, @@ -235,6 +238,7 @@ STAmount::STAmount (Issue const& issue, , mOffset (exponent) , mIsNegative (negative) { + assert(mValue <= std::numeric_limits::max()); canonicalize (); } @@ -280,9 +284,9 @@ STAmount::STAmount (XRPAmount const& amount) , mIsNegative (amount < beast::zero) { if (mIsNegative) - mValue = static_cast (-amount.drops ()); + mValue = unsafe_cast (-amount.drops ()); else - mValue = static_cast (amount.drops ()); + mValue = unsafe_cast (amount.drops ()); canonicalize (); } @@ -298,20 +302,23 @@ STAmount::construct (SerialIter& sit, SField const& name) // Conversion // //------------------------------------------------------------------------------ -XRPAmount STAmount::xrp () const +XRPAmount +STAmount::xrp () const { if (!mIsNative) - Throw ("Cannot return non-native STAmount as XRPAmount"); + Throw ( + "Cannot return non-native STAmount as XRPAmount"); - auto drops = static_cast (mValue); + auto drops = static_cast (mValue); if (mIsNegative) drops = -drops; - return { drops }; + return XRPAmount{ drops }; } -IOUAmount STAmount::iou () const +IOUAmount +STAmount::iou () const { if (mIsNative) Throw ("Cannot return native STAmount as IOUAmount"); diff --git a/src/ripple/protocol/impl/STValidation.cpp b/src/ripple/protocol/impl/STValidation.cpp index 73327f25184..1fd269ea055 100644 --- a/src/ripple/protocol/impl/STValidation.cpp +++ b/src/ripple/protocol/impl/STValidation.cpp @@ -55,14 +55,26 @@ STValidation::STValidation( if (fees.loadFee) setFieldU32(sfLoadFee, *fees.loadFee); + // IF any of the values are out of the valid range, don't send a value. + // They should not be an issue, though, because the voting + // process (FeeVoteImpl) ignores any out of range values. if (fees.baseFee) - setFieldU64(sfBaseFee, *fees.baseFee); + { + if (auto const v = fees.baseFee->dropsAs()) + setFieldU64(sfBaseFee, *v); + } if (fees.reserveBase) - setFieldU32(sfReserveBase, *fees.reserveBase); + { + if (auto const v = fees.reserveBase->dropsAs()) + setFieldU32(sfReserveBase, *v); + } if (fees.reserveIncrement) - setFieldU32(sfReserveIncrement, *fees.reserveIncrement); + { + if (auto const v = fees.reserveIncrement->dropsAs()) + setFieldU32(sfReserveIncrement, *v); + } if (!amendments.empty()) setFieldV256(sfAmendments, STVector256(sfAmendments, amendments)); diff --git a/src/ripple/rpc/handlers/NoRippleCheck.cpp b/src/ripple/rpc/handlers/NoRippleCheck.cpp index fdd264e5878..2b8bfd1c8d2 100644 --- a/src/ripple/rpc/handlers/NoRippleCheck.cpp +++ b/src/ripple/rpc/handlers/NoRippleCheck.cpp @@ -43,8 +43,8 @@ static void fillTransaction ( auto& fees = ledger.fees(); // Convert the reference transaction cost in fee units to drops // scaled to represent the current fee load. - txArray["Fee"] = Json::UInt (scaleFeeLoad(fees.units, - context.app.getFeeTrack(), fees, false)); + txArray["Fee"] = scaleFeeLoad(fees.units, + context.app.getFeeTrack(), fees, false).jsonClipped(); } // { diff --git a/src/ripple/rpc/impl/TransactionSign.cpp b/src/ripple/rpc/impl/TransactionSign.cpp index 266596a69d6..e393d439708 100644 --- a/src/ripple/rpc/impl/TransactionSign.cpp +++ b/src/ripple/rpc/impl/TransactionSign.cpp @@ -709,22 +709,19 @@ Json::Value checkFee ( } // Default fee in fee units. - std::uint64_t const feeDefault = config.TRANSACTION_FEE_BASE; + FeeUnit32 const feeDefault = config.TRANSACTION_FEE_BASE; // Administrative and identified endpoints are exempt from local fees. - std::uint64_t const loadFee = + XRPAmount const loadFee = scaleFeeLoad (feeDefault, feeTrack, ledger->fees(), isUnlimited (role)); - std::uint64_t fee = loadFee; + XRPAmount fee = loadFee; { auto const metrics = txQ.getMetrics(*ledger); auto const baseFee = ledger->fees().base; - auto escalatedFee = mulDiv( - metrics.openLedgerFeeLevel, baseFee, - metrics.referenceFeeLevel).second; - if (mulDiv(escalatedFee, metrics.referenceFeeLevel, - baseFee).second < metrics.openLedgerFeeLevel) - ++escalatedFee; + auto escalatedFee = toDrops( + metrics.openLedgerFeeLevel - FeeLevel64{ 1 }, baseFee).second + + 1; fee = std::max(fee, escalatedFee); } @@ -749,7 +746,7 @@ Json::Value checkFee ( return RPC::make_error (rpcHIGH_FEE, ss.str()); } - tx [jss::Fee] = static_cast(fee); + tx [jss::Fee] = fee.jsonClipped(); return Json::Value(); } diff --git a/src/ripple/unity/basics1.cpp b/src/ripple/unity/basics1.cpp index 5b1eab270e4..852523dc50a 100644 --- a/src/ripple/unity/basics1.cpp +++ b/src/ripple/unity/basics1.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include diff --git a/src/ripple/unity/protocol.cpp b/src/ripple/unity/protocol.cpp index 867557f821a..db5f5a4e5c1 100644 --- a/src/ripple/unity/protocol.cpp +++ b/src/ripple/unity/protocol.cpp @@ -58,7 +58,6 @@ #include #include #include -#include #if DOXYGEN #include diff --git a/src/test/app/Check_test.cpp b/src/test/app/Check_test.cpp index 6a46c7be5f0..773532adf89 100644 --- a/src/test/app/Check_test.cpp +++ b/src/test/app/Check_test.cpp @@ -305,7 +305,7 @@ class Check_test : public beast::unit_test::suite BEAST_EXPECT (checksOnAccount (env, bob ).size() == bobCount + 6); // alice uses multisigning to create a check. - std::uint64_t const baseFeeDrops {env.current()->fees().base}; + XRPAmount const baseFeeDrops {env.current()->fees().base}; env (check::create (alice, bob, USD(50)), msig (bogie, demon), fee (3 * baseFeeDrops)); env.close(); @@ -511,7 +511,7 @@ class Check_test : public beast::unit_test::suite fix1449Time() + 100 * env.closed()->info().closeTimeResolution; env.close (closeTime); - std::uint64_t const baseFeeDrops {env.current()->fees().base}; + XRPAmount const baseFeeDrops {env.current()->fees().base}; STAmount const startBalance {XRP(300).value()}; env.fund (startBalance, alice, bob); { @@ -601,7 +601,7 @@ class Check_test : public beast::unit_test::suite verifyDeliveredAmount (env, drops(checkAmount.mantissa() - 1)); env.require (balance (alice, reserve)); env.require (balance (bob, - startBalance + checkAmount - drops ((baseFeeDrops * 2) + 1))); + startBalance + checkAmount - drops (baseFeeDrops * 2 + 1))); BEAST_EXPECT (checksOnAccount (env, alice).size() == 0); BEAST_EXPECT (checksOnAccount (env, bob ).size() == 0); BEAST_EXPECT (ownerCount (env, alice) == 0); @@ -611,7 +611,7 @@ class Check_test : public beast::unit_test::suite env (pay (env.master, alice, checkAmount + drops (baseFeeDrops - 1))); env (pay (bob, env.master, - checkAmount - drops ((baseFeeDrops * 3) + 1))); + checkAmount - drops (baseFeeDrops * 3 + 1))); env.close(); env.require (balance (alice, startBalance)); env.require (balance (bob, startBalance)); @@ -947,7 +947,7 @@ class Check_test : public beast::unit_test::suite BEAST_EXPECT (ownerCount (env, bob ) == signersCount + 1); // bob uses multisigning to cash a check. - std::uint64_t const baseFeeDrops {env.current()->fees().base}; + XRPAmount const baseFeeDrops {env.current()->fees().base}; env (check::cash (bob, chkId2, (USD(2))), msig (bogie, demon), fee (3 * baseFeeDrops)); env.close(); @@ -1677,7 +1677,7 @@ class Check_test : public beast::unit_test::suite BEAST_EXPECT (ownerCount (env, alice) == signersCount + 3); // alice uses multisigning to cancel a check. - std::uint64_t const baseFeeDrops {env.current()->fees().base}; + XRPAmount const baseFeeDrops {env.current()->fees().base}; env (check::cancel (alice, chkIdMSig), msig (bogie, demon), fee (3 * baseFeeDrops)); env.close(); diff --git a/src/test/app/FeeVote_test.cpp b/src/test/app/FeeVote_test.cpp new file mode 100644 index 00000000000..e818231ada8 --- /dev/null +++ b/src/test/app/FeeVote_test.cpp @@ -0,0 +1,104 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2019 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 +#include +#include + +namespace ripple { +namespace test { + +class FeeVote_test : + public beast::unit_test::suite +{ + void testSetup() + { + { + // defaults + Section config; + auto setup = setup_FeeVote(config); + BEAST_EXPECT(setup.reference_fee == 10); + BEAST_EXPECT(setup.account_reserve == 20 * DROPS_PER_XRP); + BEAST_EXPECT(setup.owner_reserve == 5 * DROPS_PER_XRP); + } + { + Section config; + config.append({ + "reference_fee = 50", + "account_reserve = 1234567", + "owner_reserve = 1234" + }); + auto setup = setup_FeeVote(config); + BEAST_EXPECT(setup.reference_fee == 50); + BEAST_EXPECT(setup.account_reserve == 1234567); + BEAST_EXPECT(setup.owner_reserve == 1234); + } + { + Section config; + config.append({ + "reference_fee = blah", + "account_reserve = yada", + "owner_reserve = foo" + }); + // Illegal values are ignored, and the defaults left unchanged + auto setup = setup_FeeVote(config); + BEAST_EXPECT(setup.reference_fee == 10); + BEAST_EXPECT(setup.account_reserve == 20 * DROPS_PER_XRP); + BEAST_EXPECT(setup.owner_reserve == 5 * DROPS_PER_XRP); + } + { + Section config; + config.append({ + "reference_fee = -50", + "account_reserve = -1234567", + "owner_reserve = -1234" + }); + // Illegal values are ignored, and the defaults left unchanged + auto setup = setup_FeeVote(config); + BEAST_EXPECT(setup.reference_fee == 10); + BEAST_EXPECT(setup.account_reserve == static_cast(-1234567)); + BEAST_EXPECT(setup.owner_reserve == static_cast(-1234)); + } + { + const auto big64 = std::to_string(static_cast( + std::numeric_limits::max()) + 1); + Section config; + config.append({ + "reference_fee = " + big64, + "account_reserve = " + big64, + "owner_reserve = " + big64 + }); + // Illegal values are ignored, and the defaults left unchanged + auto setup = setup_FeeVote(config); + BEAST_EXPECT(setup.reference_fee == 10); + BEAST_EXPECT(setup.account_reserve == 20 * DROPS_PER_XRP); + BEAST_EXPECT(setup.owner_reserve == 5 * DROPS_PER_XRP); + } + } + + void run() override + { + testSetup(); + } +}; + +BEAST_DEFINE_TESTSUITE(FeeVote, server, ripple); + +} // test +} // ripple diff --git a/src/test/app/Flow_test.cpp b/src/test/app/Flow_test.cpp index c9515371994..656f5a56cdb 100644 --- a/src/test/app/Flow_test.cpp +++ b/src/test/app/Flow_test.cpp @@ -52,7 +52,7 @@ xrpMinusFee (jtx::Env const& env, std::int64_t xrpAmount) using namespace jtx; auto feeDrops = env.current ()->fees ().base; return drops ( - dropsPerXRP::value * xrpAmount - feeDrops); + dropsPerXRP * xrpAmount - feeDrops); }; struct Flow_test : public beast::unit_test::suite @@ -1212,8 +1212,8 @@ struct Flow_test : public beast::unit_test::suite auto const CTB = gw["CTB"]; auto const fee = env.current ()->fees ().base; - env.fund (reserve(env, 2) + drops (9999640) + (fee), ann); - env.fund (reserve(env, 2) + (fee*4), gw); + env.fund (reserve(env, 2) + drops (9999640) + fee, ann); + env.fund (reserve(env, 2) + fee*4, gw); env.close(); env (rate(gw, 1.002)); diff --git a/src/test/app/LoadFeeTrack_test.cpp b/src/test/app/LoadFeeTrack_test.cpp index 973ed15051c..0d8495c2fcf 100644 --- a/src/test/app/LoadFeeTrack_test.cpp +++ b/src/test/app/LoadFeeTrack_test.cpp @@ -31,18 +31,58 @@ class LoadFeeTrack_test : public beast::unit_test::suite { Config d; // get a default configuration object LoadFeeTrack l; - Fees const fees = [&]() { - Fees f; - f.base = d.FEE_DEFAULT; - f.units = d.TRANSACTION_FEE_BASE; - f.reserve = 200 * SYSTEM_CURRENCY_PARTS; - f.increment = 50 * SYSTEM_CURRENCY_PARTS; - return f; - }(); - - BEAST_EXPECT (scaleFeeLoad (10000, l, fees, false) == 10000); - BEAST_EXPECT (scaleFeeLoad (1, l, fees, false) == 1); + Fees const fees = [&]() { + Fees f; + f.base = d.FEE_DEFAULT; + f.units = d.TRANSACTION_FEE_BASE; + f.reserve = 200 * DROPS_PER_XRP; + f.increment = 50 * DROPS_PER_XRP; + return f; + }(); + + BEAST_EXPECT(scaleFeeLoad(FeeUnit64{ 0 }, l, fees, false) == + XRPAmount{ 0 }); + BEAST_EXPECT(scaleFeeLoad(FeeUnit64{ 10000 }, l, fees, false) == + XRPAmount{ 10000 }); + BEAST_EXPECT(scaleFeeLoad(FeeUnit64{ 1 }, l, fees, false) == + XRPAmount{ 1 }); + } + { + Fees const fees = [&]() { + Fees f; + f.base = d.FEE_DEFAULT * 10; + f.units = d.TRANSACTION_FEE_BASE; + f.reserve = 200 * DROPS_PER_XRP; + f.increment = 50 * DROPS_PER_XRP; + return f; + }(); + + BEAST_EXPECT(scaleFeeLoad(FeeUnit64{ 0 }, l, fees, false) == + XRPAmount{ 0 }); + BEAST_EXPECT(scaleFeeLoad(FeeUnit64{ 10000 }, l, fees, false) == + XRPAmount{ 100000 }); + BEAST_EXPECT(scaleFeeLoad(FeeUnit64{ 1 }, l, fees, false) == + XRPAmount{ 10 }); + } + { + Fees const fees = [&]() + { + Fees f; + f.base = d.FEE_DEFAULT; + f.units = d.TRANSACTION_FEE_BASE * 10; + f.reserve = 200 * DROPS_PER_XRP; + f.increment = 50 * DROPS_PER_XRP; + return f; + }(); + + BEAST_EXPECT(scaleFeeLoad(FeeUnit64{ 0 }, l, fees, false) == + XRPAmount{ 0 }); + BEAST_EXPECT(scaleFeeLoad(FeeUnit64{ 10000 }, l, fees, false) == + XRPAmount{ 1000 }); + BEAST_EXPECT(scaleFeeLoad(FeeUnit64{ 1 }, l, fees, false) == + XRPAmount{ 0 }); + } } }; diff --git a/src/test/app/MultiSign_test.cpp b/src/test/app/MultiSign_test.cpp index d5b8fdb81ee..a20c112637c 100644 --- a/src/test/app/MultiSign_test.cpp +++ b/src/test/app/MultiSign_test.cpp @@ -64,7 +64,7 @@ class MultiSign_test : public beast::unit_test::suite env.require (owners (alice, 0)); // Fund alice enough to set the signer list, then attach signers. - env(pay(env.master, alice, fee + drops (1))); + env(pay(env.master, alice, fee + drops(1))); env.close(); env(smallSigners); env.close(); @@ -281,7 +281,8 @@ class MultiSign_test : public beast::unit_test::suite // This should fail because the fee is too small. aliceSeq = env.seq (alice); env(noop(alice), - msig(bogie), fee((2 * baseFee) - 1), ter(telINSUF_FEE_P)); + msig(bogie), fee((2 * baseFee) - 1), + ter(telINSUF_FEE_P)); env.close(); BEAST_EXPECT(env.seq(alice) == aliceSeq); @@ -482,7 +483,7 @@ class MultiSign_test : public beast::unit_test::suite Json::Value jv; jv[jss::tx_json][jss::Account] = alice.human(); jv[jss::tx_json][jss::TransactionType] = jss::AccountSet; - jv[jss::tx_json][jss::Fee] = static_cast(8 * baseFee); + jv[jss::tx_json][jss::Fee] = (8 * baseFee).jsonClipped(); jv[jss::tx_json][jss::Sequence] = env.seq(alice); jv[jss::tx_json][jss::SigningPubKey] = ""; return jv; @@ -1211,14 +1212,14 @@ class MultiSign_test : public beast::unit_test::suite // Use sign_for to sign a transaction where alice pays 10 XRP to // masterpassphrase. - std::uint32_t baseFee = env.current()->fees().base; + auto const baseFee = env.current()->fees().base; Json::Value jvSig1; jvSig1[jss::account] = bogie.human(); jvSig1[jss::secret] = bogie.name(); jvSig1[jss::tx_json][jss::Account] = alice.human(); jvSig1[jss::tx_json][jss::Amount] = 10000000; jvSig1[jss::tx_json][jss::Destination] = env.master.human(); - jvSig1[jss::tx_json][jss::Fee] = 3 * baseFee; + jvSig1[jss::tx_json][jss::Fee] = (3 * baseFee).jsonClipped(); jvSig1[jss::tx_json][jss::Sequence] = env.seq(alice); jvSig1[jss::tx_json][jss::TransactionType] = jss::Payment; diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index ddc0a91f847..4435aed1a8f 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -45,7 +45,7 @@ class Offer_test : public beast::unit_test::suite { using namespace jtx; auto feeDrops = env.current()->fees().base; - return drops (dropsPerXRP::value * xrpAmount - feeDrops); + return drops (dropsPerXRP * xrpAmount - feeDrops); } static auto @@ -1046,7 +1046,7 @@ class Offer_test : public beast::unit_test::suite env.fund (XRP(1000000), gw); // The fee that's charged for transactions - auto const f = env.current ()->fees ().base; + auto const f = env.current()->fees().base; // Account is at the reserve, and will dip below once // fees are subtracted. @@ -1342,10 +1342,10 @@ class Offer_test : public beast::unit_test::suite jrr = ledgerEntryRoot (env, bob); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - std::to_string( - XRP (10000).value ().mantissa () - - XRP (reverse_order ? 4000 : 3000).value ().mantissa () - - env.current ()->fees ().base * 2) + to_string( + (XRP (10000) - + XRP (reverse_order ? 4000 : 3000) - + env.current ()->fees ().base * 2).xrp()) ); jrr = ledgerEntryState (env, alice, gw, "USD"); @@ -1353,10 +1353,10 @@ class Offer_test : public beast::unit_test::suite jrr = ledgerEntryRoot (env, alice); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - std::to_string( - XRP (10000).value ().mantissa( ) + - XRP(reverse_order ? 4000 : 3000).value ().mantissa () - - env.current ()->fees ().base * 2) + to_string( + (XRP (10000) + + XRP(reverse_order ? 4000 : 3000) - + env.current ()->fees ().base * 2).xrp ()) ); } @@ -1392,10 +1392,10 @@ class Offer_test : public beast::unit_test::suite jrr = ledgerEntryRoot(env, bob); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - std::to_string( - XRP (100000).value ().mantissa () - - XRP (3000).value ().mantissa () - - env.current ()->fees ().base * 1) + to_string( + (XRP (100000) - + XRP (3000) - + env.current ()->fees ().base * 1).xrp ()) ); jrr = ledgerEntryState (env, alice, gw, "USD"); @@ -1403,10 +1403,10 @@ class Offer_test : public beast::unit_test::suite jrr = ledgerEntryRoot (env, alice); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - std::to_string( - XRP (100000).value ().mantissa () + - XRP (3000).value ().mantissa () - - env.current ()->fees ().base * 2) + to_string( + (XRP (100000) + + XRP (3000) - + env.current ()->fees ().base * 2).xrp ()) ); } @@ -1526,10 +1526,10 @@ class Offer_test : public beast::unit_test::suite jrr = ledgerEntryRoot (env, alice); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - std::to_string( - XRP (10000).value ().mantissa () + - XRP (500).value ().mantissa () - - env.current ()->fees ().base * 2) + to_string( + (XRP (10000) + + XRP (500) - + env.current ()->fees ().base * 2).xrp ()) ); jrr = ledgerEntryState (env, bob, gw, "USD"); @@ -1622,10 +1622,10 @@ class Offer_test : public beast::unit_test::suite jrr = ledgerEntryRoot (env, alice); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - std::to_string( - XRP (10000).value ().mantissa () + - XRP (200).value ().mantissa () - - env.current ()->fees ().base * 2) + to_string( + (XRP (10000) + + XRP (200) - + env.current ()->fees ().base * 2).xrp ()) ); // bob got 40 USD from partial consumption of the offer @@ -1656,11 +1656,11 @@ class Offer_test : public beast::unit_test::suite jrr = ledgerEntryRoot (env, alice); BEAST_EXPECT( jrr[jss::node][sfBalance.fieldName] == - std::to_string( - XRP (10000).value ().mantissa () + - XRP (200).value ().mantissa () + - XRP (300).value ().mantissa () - - env.current ()->fees ().base * 4) + to_string( + (XRP (10000) + + XRP (200) + + XRP (300) - + env.current ()->fees ().base * 4).xrp ()) ); // bob now has 100 USD - 40 from the first payment and 60 from the @@ -2154,7 +2154,7 @@ class Offer_test : public beast::unit_test::suite env.current ()->read ( keylet::account (bob.id ()))->getFieldU32 (sfSequence); payment[jss::tx_json][jss::Fee] = - std::to_string( env.current ()->fees ().base); + to_string( env.current()->fees().base); payment[jss::tx_json][jss::SendMax] = bob ["XTS"] (1.5).value ().getJson (JsonOptions::none); auto jrr = wsc->invoke("submit", payment); @@ -2403,7 +2403,7 @@ class Offer_test : public beast::unit_test::suite // alice's account has enough for the reserve, one trust line plus two // offers, and two fees. - env.fund (reserve (env, 2) + (fee * 2), alice); + env.fund (reserve (env, 2) + fee * 2, alice); env.close(); env (trust(alice, usdOffer)); @@ -2488,8 +2488,8 @@ class Offer_test : public beast::unit_test::suite // Each account has enough for the reserve, two trust lines, one // offer, and two fees. - env.fund (reserve (env, 3) + (fee * 3), alice); - env.fund (reserve (env, 3) + (fee * 2), bob); + env.fund (reserve (env, 3) + fee * 3, alice); + env.fund (reserve (env, 3) + fee * 2, bob); env.close(); env (trust(alice, usdOffer)); @@ -3038,7 +3038,7 @@ class Offer_test : public beast::unit_test::suite auto const eve = Account("eve"); auto const fyn = Account("fyn"); - env.fund (XRP(20000) + fee*2, eve, fyn); + env.fund (XRP(20000) + (fee*2), eve, fyn); env.close(); env (trust (eve, USD(1000))); diff --git a/src/test/app/Regression_test.cpp b/src/test/app/Regression_test.cpp index 927c82d0d0c..c8cde98efde 100644 --- a/src/test/app/Regression_test.cpp +++ b/src/test/app/Regression_test.cpp @@ -55,7 +55,7 @@ struct Regression_test : public beast::unit_test::suite auto closed = std::make_shared( create_genesis, env.app().config(), std::vector{}, env.app().family()); - auto expectedDrops = SYSTEM_CURRENCY_START; + auto expectedDrops = INITIAL_XRP; BEAST_EXPECT(closed->info().drops == expectedDrops); auto const aliceXRP = 400; @@ -110,7 +110,7 @@ struct Regression_test : public beast::unit_test::suite BEAST_EXPECT(balance == XRP(0)); } - expectedDrops -= aliceXRP * dropsPerXRP::value; + expectedDrops -= aliceXRP * dropsPerXRP; BEAST_EXPECT(next->info().drops == expectedDrops); } diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index 55056f62f0a..c506c347980 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -50,16 +50,18 @@ class TxQ_test : public beast::unit_test::suite std::uint64_t expectedMinFeeLevel, std::uint64_t expectedMedFeeLevel = 256 * 500) { + FeeLevel64 const expectedMin{ expectedMinFeeLevel }; + FeeLevel64 const expectedMed{ expectedMedFeeLevel }; auto const metrics = env.app().getTxQ().getMetrics(*env.current()); - BEAST_EXPECT(metrics.referenceFeeLevel == 256); + BEAST_EXPECT(metrics.referenceFeeLevel == FeeLevel64{ 256 }); BEAST_EXPECT(metrics.txCount == expectedCount); BEAST_EXPECT(metrics.txQMaxSize == expectedMaxCount); BEAST_EXPECT(metrics.txInLedger == expectedInLedger); BEAST_EXPECT(metrics.txPerLedger == expectedPerLedger); - BEAST_EXPECT(metrics.minProcessingFeeLevel == expectedMinFeeLevel); - BEAST_EXPECT(metrics.medFeeLevel == expectedMedFeeLevel); + BEAST_EXPECT(metrics.minProcessingFeeLevel == expectedMin); + BEAST_EXPECT(metrics.medFeeLevel == expectedMed); auto expectedCurFeeLevel = expectedInLedger > expectedPerLedger ? - expectedMedFeeLevel * expectedInLedger * expectedInLedger / + expectedMed *expectedInLedger * expectedInLedger / (expectedPerLedger * expectedPerLedger) : metrics.referenceFeeLevel; BEAST_EXPECT(metrics.openLedgerFeeLevel == expectedCurFeeLevel); @@ -84,8 +86,8 @@ class TxQ_test : public beast::unit_test::suite auto metrics = env.app().getTxQ().getMetrics(view); // Don't care about the overflow flag - return fee(mulDiv(metrics.openLedgerFeeLevel, - view.fees().base, metrics.referenceFeeLevel).second + 1); + return fee(toDrops(metrics.openLedgerFeeLevel, + view.fees().base).second + 1); } static @@ -156,10 +158,10 @@ class TxQ_test : public beast::unit_test::suite env.close(env.now() + 5s, 10000ms); checkMetrics(env, 0, flagMaxQueue, 0, expectedPerLedger, 256); auto const fees = env.current()->fees(); - BEAST_EXPECT(fees.base == base); - BEAST_EXPECT(fees.units == units); - BEAST_EXPECT(fees.reserve == reserve); - BEAST_EXPECT(fees.increment == increment); + BEAST_EXPECT(fees.base == XRPAmount{ base }); + BEAST_EXPECT(fees.units == FeeUnit64{ units }); + BEAST_EXPECT(fees.reserve == XRPAmount{ reserve }); + BEAST_EXPECT(fees.increment == XRPAmount{ increment }); return flagMaxQueue; } @@ -494,14 +496,16 @@ class TxQ_test : public beast::unit_test::suite auto& txQ = env.app().getTxQ(); auto aliceStat = txQ.getAccountTxs(alice.id(), *env.current()); BEAST_EXPECT(aliceStat.size() == 1); - BEAST_EXPECT(aliceStat.begin()->second.feeLevel == 256); + BEAST_EXPECT(aliceStat.begin()->second.feeLevel == + FeeLevel64{ 256 }); BEAST_EXPECT(aliceStat.begin()->second.lastValid && *aliceStat.begin()->second.lastValid == 8); BEAST_EXPECT(!aliceStat.begin()->second.consequences); auto bobStat = txQ.getAccountTxs(bob.id(), *env.current()); BEAST_EXPECT(bobStat.size() == 1); - BEAST_EXPECT(bobStat.begin()->second.feeLevel == 7000 * 256 / 10); + BEAST_EXPECT(bobStat.begin()->second.feeLevel == + FeeLevel64{ 7000 * 256 / 10 }); BEAST_EXPECT(!bobStat.begin()->second.lastValid); BEAST_EXPECT(!bobStat.begin()->second.consequences); @@ -833,13 +837,14 @@ class TxQ_test : public beast::unit_test::suite { auto& txQ = env.app().getTxQ(); auto aliceStat = txQ.getAccountTxs(alice.id(), *env.current()); - std::int64_t fee = 20; + constexpr XRPAmount fee{ 20 }; + auto const& baseFee = env.current()->fees().base; auto seq = env.seq(alice); BEAST_EXPECT(aliceStat.size() == 7); for (auto const& [txSeq, details] : aliceStat) { BEAST_EXPECT(txSeq == seq); - BEAST_EXPECT(details.feeLevel == mulDiv(fee, 256, 10).second); + BEAST_EXPECT(details.feeLevel == toFeeLevel(fee, baseFee).second); BEAST_EXPECT(details.lastValid); BEAST_EXPECT((details.consequences && details.consequences->fee == drops(fee) && @@ -1995,7 +2000,7 @@ class TxQ_test : public beast::unit_test::suite for (auto const& tx : aliceStat) { BEAST_EXPECT(tx.first == seq); - BEAST_EXPECT(tx.second.feeLevel == 25600); + BEAST_EXPECT(tx.second.feeLevel == FeeLevel64{ 25600 }); if(seq == aliceSeq + 2) { BEAST_EXPECT(tx.second.lastValid && @@ -2049,7 +2054,7 @@ class TxQ_test : public beast::unit_test::suite ++seq; BEAST_EXPECT(tx.first == seq); - BEAST_EXPECT(tx.second.feeLevel == 25600); + BEAST_EXPECT(tx.second.feeLevel == FeeLevel64{ 25600 }); BEAST_EXPECT(!tx.second.lastValid); ++seq; } @@ -2064,7 +2069,7 @@ class TxQ_test : public beast::unit_test::suite for (auto const& tx : aliceStat) { BEAST_EXPECT(tx.first == seq); - BEAST_EXPECT(tx.second.feeLevel == 25600); + BEAST_EXPECT(tx.second.feeLevel == FeeLevel64{ 25600 }); BEAST_EXPECT(!tx.second.lastValid); ++seq; } @@ -2763,10 +2768,10 @@ class TxQ_test : public beast::unit_test::suite totalFactor += inLedger * inLedger; } auto result = - mulDiv (metrics.medFeeLevel * totalFactor / + toDrops (metrics.medFeeLevel * totalFactor / (metrics.txPerLedger * metrics.txPerLedger), - env.current ()->fees ().base, metrics.referenceFeeLevel) - .second; + env.current ()->fees ().base) + .second.drops(); // Subtract the fees already paid result -= alreadyPaid; // round up @@ -2807,7 +2812,8 @@ class TxQ_test : public beast::unit_test::suite // Now repeat the process including the new tx // and avoiding the rounding error - std::uint64_t const totalFee2 = calcTotalFee (100 * 2 + 8889 + 60911); + std::uint64_t const totalFee2 = calcTotalFee (100 * 2 + 8889 + + 60911); BEAST_EXPECT(totalFee2 == 35556); // Submit a transaction with that fee. It will succeed. env(noop(alice), fee(totalFee2), seq(aliceSeq++)); @@ -2877,7 +2883,7 @@ class TxQ_test : public beast::unit_test::suite for (auto const& tx : aliceQueue) { BEAST_EXPECT(tx.first == seq); - BEAST_EXPECT(tx.second.feeLevel == 2560); + BEAST_EXPECT(tx.second.feeLevel == FeeLevel64{ 2560 }); ++seq; } diff --git a/src/test/basics/FeeUnits_test.cpp b/src/test/basics/FeeUnits_test.cpp new file mode 100644 index 00000000000..681c149ff00 --- /dev/null +++ b/src/test/basics/FeeUnits_test.cpp @@ -0,0 +1,359 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2019 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 +#include +#include +#include + +namespace ripple { +namespace test { + +class feeunits_test + : public beast::unit_test::suite +{ +private: + void testTypes() + { + { + XRPAmount x{ 100 }; + BEAST_EXPECT(x.drops() == 100); + BEAST_EXPECT((std::is_same_v)); + auto y = 4u * x; + BEAST_EXPECT(y.value() == 400); + BEAST_EXPECT((std::is_same_v)); + + auto z = 4 * y; + BEAST_EXPECT(z.value() == 1600); + BEAST_EXPECT((std::is_same_v)); + + FeeUnit32 f{ 10 }; + FeeUnit32 baseFee{ 100 }; + + auto drops = mulDiv(baseFee, x, f).second; + + BEAST_EXPECT(drops.value() == 1000); + BEAST_EXPECT((std::is_same_v)); + BEAST_EXPECT((std::is_same_v)); + } + { + XRPAmount x{ 100 }; + BEAST_EXPECT(x.value() == 100); + BEAST_EXPECT((std::is_same_v)); + auto y = 4u * x; + BEAST_EXPECT(y.value() == 400); + BEAST_EXPECT((std::is_same_v)); + + FeeUnit64 f{ 10 }; + FeeUnit64 baseFee{ 100 }; + + auto drops = mulDiv(baseFee, x, f).second; + + BEAST_EXPECT(drops.value() == 1000); + BEAST_EXPECT((std::is_same_v)); + BEAST_EXPECT((std::is_same_v)); + } + { + FeeLevel64 x{ 1024 }; + BEAST_EXPECT(x.value() == 1024); + BEAST_EXPECT((std::is_same_v)); + std::uint64_t m = 4; + auto y = m * x; + BEAST_EXPECT(y.value() == 4096); + BEAST_EXPECT((std::is_same_v)); + + XRPAmount basefee{ 10 }; + FeeLevel64 referencefee{ 256 }; + + auto drops = mulDiv(x, basefee, referencefee).second; + + BEAST_EXPECT(drops.value() == 40); + BEAST_EXPECT((std::is_same_v)); + BEAST_EXPECT((std::is_same_v)); + } + } + + void testJson() + { + // Json value functionality + { + FeeUnit32 x{std::numeric_limits::max()}; + auto y = x.jsonClipped(); + BEAST_EXPECT(y.type() == Json::uintValue); + BEAST_EXPECT(y == Json::Value{x.fee()}); + } + + { + FeeUnit32 x{std::numeric_limits::min()}; + auto y = x.jsonClipped(); + BEAST_EXPECT(y.type() == Json::uintValue); + BEAST_EXPECT(y == Json::Value{x.fee()}); + } + + { + FeeUnit64 x{std::numeric_limits::max()}; + auto y = x.jsonClipped(); + BEAST_EXPECT(y.type() == Json::uintValue); + BEAST_EXPECT(y == + Json::Value{std::numeric_limits::max()}); + } + + { + FeeUnit64 x{std::numeric_limits::min()}; + auto y = x.jsonClipped(); + BEAST_EXPECT(y.type() == Json::uintValue); + BEAST_EXPECT(y == Json::Value{0}); + } + + { + FeeLevelDouble x{std::numeric_limits::max()}; + auto y = x.jsonClipped(); + BEAST_EXPECT(y.type() == Json::realValue); + BEAST_EXPECT(y == Json::Value{std::numeric_limits::max()}); + } + + { + FeeLevelDouble x{std::numeric_limits::min()}; + auto y = x.jsonClipped(); + BEAST_EXPECT(y.type() == Json::realValue); + BEAST_EXPECT(y == Json::Value{std::numeric_limits::min()}); + } + + { + XRPAmount x{std::numeric_limits::max()}; + auto y = x.jsonClipped(); + BEAST_EXPECT(y.type() == Json::intValue); + BEAST_EXPECT(y == + Json::Value{std::numeric_limits::max()}); + } + + { + XRPAmount x{std::numeric_limits::min()}; + auto y = x.jsonClipped(); + BEAST_EXPECT(y.type() == Json::intValue); + BEAST_EXPECT(y == + Json::Value{std::numeric_limits::min()}); + } + } + + void testFunctions() + { + // Explicitly test every defined function for the TaggedFee class + // since some of them are templated, but not used anywhere else. + { + auto make = [&](auto x) -> FeeUnit64 { + return x; }; + auto explicitmake = [&](auto x) -> FeeUnit64 { + return FeeUnit64{ x }; + }; + + FeeUnit64 defaulted; + (void)defaulted; + FeeUnit64 test{ 0 }; + BEAST_EXPECT(test.fee() == 0); + + test = explicitmake(beast::zero); + BEAST_EXPECT(test.fee() == 0); + + test = beast::zero; + BEAST_EXPECT(test.fee() == 0); + + test = explicitmake(100u); + BEAST_EXPECT(test.fee() == 100); + + FeeUnit64 const targetSame{ 200u }; + FeeUnit32 const targetOther{ 300u }; + test = make(targetSame); + BEAST_EXPECT(test.fee() == 200); + BEAST_EXPECT(test == targetSame); + BEAST_EXPECT(test < FeeUnit64{ 1000 }); + BEAST_EXPECT(test > FeeUnit64{ 100 }); + test = make(targetOther); + BEAST_EXPECT(test.fee() == 300); + BEAST_EXPECT(test == targetOther); + + test = std::uint64_t(200); + BEAST_EXPECT(test.fee() == 200); + test = std::uint32_t(300); + BEAST_EXPECT(test.fee() == 300); + + test = targetSame; + BEAST_EXPECT(test.fee() == 200); + test = targetOther.fee(); + BEAST_EXPECT(test.fee() == 300); + BEAST_EXPECT(test == targetOther); + + test = targetSame * 2; + BEAST_EXPECT(test.fee() == 400); + test = 3 * targetSame; + BEAST_EXPECT(test.fee() == 600); + test = targetSame / 10; + BEAST_EXPECT(test.fee() == 20); + + test += targetSame; + BEAST_EXPECT(test.fee() == 220); + + test -= targetSame; + BEAST_EXPECT(test.fee() == 20); + + test++; + BEAST_EXPECT(test.fee() == 21); + ++test; + BEAST_EXPECT(test.fee() == 22); + test--; + BEAST_EXPECT(test.fee() == 21); + --test; + BEAST_EXPECT(test.fee() == 20); + + test *= 5; + BEAST_EXPECT(test.fee() == 100); + test /= 2; + BEAST_EXPECT(test.fee() == 50); + test %= 13; + BEAST_EXPECT(test.fee() == 11); + + /* + // illegal with unsigned + test = -test; + BEAST_EXPECT(test.fee() == -11); + BEAST_EXPECT(test.signum() == -1); + BEAST_EXPECT(to_string(test) == "-11"); + */ + + BEAST_EXPECT(test); + test = 0; + BEAST_EXPECT(!test); + BEAST_EXPECT(test.signum() == 0); + test = targetSame; + BEAST_EXPECT(test.signum() == 1); + BEAST_EXPECT(to_string(test) == "200"); + } + { + auto make = [&](auto x) -> FeeLevelDouble { + return x; }; + auto explicitmake = [&](auto x) -> FeeLevelDouble { + return FeeLevelDouble{ x }; + }; + + FeeLevelDouble defaulted; + (void)defaulted; + FeeLevelDouble test{ 0 }; + BEAST_EXPECT(test.fee() == 0); + + test = explicitmake(beast::zero); + BEAST_EXPECT(test.fee() == 0); + + test = beast::zero; + BEAST_EXPECT(test.fee() == 0); + + test = explicitmake(100.0); + BEAST_EXPECT(test.fee() == 100); + + FeeLevelDouble const targetSame{ 200.0 }; + FeeLevel64 const targetOther{ 300 }; + test = make(targetSame); + BEAST_EXPECT(test.fee() == 200); + BEAST_EXPECT(test == targetSame); + BEAST_EXPECT(test < FeeLevelDouble{ 1000.0 }); + BEAST_EXPECT(test > FeeLevelDouble{ 100.0 }); + test = targetOther.fee(); + BEAST_EXPECT(test.fee() == 300); + BEAST_EXPECT(test == targetOther); + + test = 200.0; + BEAST_EXPECT(test.fee() == 200); + test = std::uint64_t(300); + BEAST_EXPECT(test.fee() == 300); + + test = targetSame; + BEAST_EXPECT(test.fee() == 200); + + test = targetSame * 2; + BEAST_EXPECT(test.fee() == 400); + test = 3 * targetSame; + BEAST_EXPECT(test.fee() == 600); + test = targetSame / 10; + BEAST_EXPECT(test.fee() == 20); + + test += targetSame; + BEAST_EXPECT(test.fee() == 220); + + test -= targetSame; + BEAST_EXPECT(test.fee() == 20); + + test++; + BEAST_EXPECT(test.fee() == 21); + ++test; + BEAST_EXPECT(test.fee() == 22); + test--; + BEAST_EXPECT(test.fee() == 21); + --test; + BEAST_EXPECT(test.fee() == 20); + + test *= 5; + BEAST_EXPECT(test.fee() == 100); + test /= 2; + BEAST_EXPECT(test.fee() == 50); + /* illegal with floating + test %= 13; + BEAST_EXPECT(test.fee() == 11); + */ + + // legal with signed + test = -test; + BEAST_EXPECT(test.fee() == -50); + BEAST_EXPECT(test.signum() == -1); + BEAST_EXPECT(to_string(test) == "-50.000000"); + + BEAST_EXPECT(test); + test = 0; + BEAST_EXPECT(!test); + BEAST_EXPECT(test.signum() == 0); + test = targetSame; + BEAST_EXPECT(test.signum() == 1); + BEAST_EXPECT(to_string(test) == "200.000000"); + } + } + +public: + void run() override + { + BEAST_EXPECT(INITIAL_XRP.drops() == 100'000'000'000'000'000); + BEAST_EXPECT(INITIAL_XRP == + XRPAmount{ 100'000'000'000'000'000 }); + + testTypes(); + testJson(); + testFunctions(); + } +}; + +BEAST_DEFINE_TESTSUITE(feeunits,ripple_basics,ripple); + +} // test +} //ripple diff --git a/src/test/protocol/IOUAmount_test.cpp b/src/test/basics/IOUAmount_test.cpp similarity index 99% rename from src/test/protocol/IOUAmount_test.cpp rename to src/test/basics/IOUAmount_test.cpp index a3ba5c32ca2..c7e685c3546 100644 --- a/src/test/protocol/IOUAmount_test.cpp +++ b/src/test/basics/IOUAmount_test.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include namespace ripple { diff --git a/src/test/protocol/XRPAmount_test.cpp b/src/test/basics/XRPAmount_test.cpp similarity index 59% rename from src/test/protocol/XRPAmount_test.cpp rename to src/test/basics/XRPAmount_test.cpp index c4aac63c306..3bca6ec23e5 100644 --- a/src/test/protocol/XRPAmount_test.cpp +++ b/src/test/basics/XRPAmount_test.cpp @@ -17,7 +17,7 @@ */ //============================================================================== -#include +#include #include namespace ripple { @@ -110,28 +110,143 @@ class XRPAmount_test : public beast::unit_test::suite } } + void testDecimal () + { + // Tautology + BEAST_EXPECT(DROPS_PER_XRP.decimalXRP() == 1); + + XRPAmount test{1}; + BEAST_EXPECT(test.decimalXRP() == 0.000001); + + test = -test; + BEAST_EXPECT(test.decimalXRP() == -0.000001); + + test = 100'000'000; + BEAST_EXPECT(test.decimalXRP() == 100); + + test = -test; + BEAST_EXPECT(test.decimalXRP() == -100); + } + + void testFunctions() + { + // Explicitly test every defined function for the XRPAmount class + // since some of them are templated, but not used anywhere else. + auto make = [&](auto x) -> XRPAmount { + return x; }; + + XRPAmount defaulted; + (void)defaulted; + XRPAmount test{ 0 }; + BEAST_EXPECT(test.drops() == 0); + + test = make(beast::zero); + BEAST_EXPECT(test.drops() == 0); + + test = beast::zero; + BEAST_EXPECT(test.drops() == 0); + + test = make(100); + BEAST_EXPECT(test.drops() == 100); + + test = make(100u); + BEAST_EXPECT(test.drops() == 100); + + XRPAmount const targetSame{ 200u }; + test = make(targetSame); + BEAST_EXPECT(test.drops() == 200); + BEAST_EXPECT(test == targetSame); + BEAST_EXPECT(test < XRPAmount{ 1000 }); + BEAST_EXPECT(test > XRPAmount{ 100 }); + + test = std::int64_t(200); + BEAST_EXPECT(test.drops() == 200); + test = std::uint32_t(300); + BEAST_EXPECT(test.drops() == 300); + + test = targetSame; + BEAST_EXPECT(test.drops() == 200); + auto testOther = test.dropsAs(); + BEAST_EXPECT(testOther); + BEAST_EXPECT(*testOther == 200); + test = std::numeric_limits::max(); + testOther = test.dropsAs(); + BEAST_EXPECT(!testOther); + test = -1; + testOther = test.dropsAs(); + BEAST_EXPECT(!testOther); + + test = targetSame * 2; + BEAST_EXPECT(test.drops() == 400); + test = 3 * targetSame; + BEAST_EXPECT(test.drops() == 600); + test = 20; + BEAST_EXPECT(test.drops() == 20); + + test += targetSame; + BEAST_EXPECT(test.drops() == 220); + + test -= targetSame; + BEAST_EXPECT(test.drops() == 20); + + test *= 5; + BEAST_EXPECT(test.drops() == 100); + test = 50; + BEAST_EXPECT(test.drops() == 50); + test -= 39; + BEAST_EXPECT(test.drops() == 11); + + // legal with signed + test = -test; + BEAST_EXPECT(test.drops() == -11); + BEAST_EXPECT(test.signum() == -1); + BEAST_EXPECT(to_string(test) == "-11"); + + BEAST_EXPECT(test); + test = 0; + BEAST_EXPECT(!test); + BEAST_EXPECT(test.signum() == 0); + test = targetSame; + BEAST_EXPECT(test.signum() == 1); + BEAST_EXPECT(to_string(test) == "200"); + } + void testMulRatio() { testcase ("mulRatio"); constexpr auto maxUInt32 = std::numeric_limits::max (); - constexpr auto maxUInt64 = std::numeric_limits::max (); + constexpr auto maxXRP = std::numeric_limits::max (); + constexpr auto minXRP = std::numeric_limits::min (); { // multiply by a number that would overflow then divide by the same // number, and check we didn't lose any value - XRPAmount big (maxUInt64); + XRPAmount big (maxXRP); BEAST_EXPECT(big == mulRatio (big, maxUInt32, maxUInt32, true)); // rounding mode shouldn't matter as the result is exact BEAST_EXPECT(big == mulRatio (big, maxUInt32, maxUInt32, false)); + + // multiply and divide by values that would overflow if done naively, + // and check that it gives the correct answer + big -= 0xf; // Subtract a little so it's divisable by 4 + BEAST_EXPECT(mulRatio(big, 3, 4, false).value() == (big.value() / 4) * 3); + BEAST_EXPECT(mulRatio(big, 3, 4, true).value() == (big.value() / 4) * 3); + BEAST_EXPECT((big.value() * 3) / 4 != (big.value() / 4) * 3); } { - // Similar test as above, but for neative values - XRPAmount big (maxUInt64); + // Similar test as above, but for negative values + XRPAmount big (minXRP); BEAST_EXPECT(big == mulRatio (big, maxUInt32, maxUInt32, true)); // rounding mode shouldn't matter as the result is exact BEAST_EXPECT(big == mulRatio (big, maxUInt32, maxUInt32, false)); + + // multiply and divide by values that would overflow if done naively, + // and check that it gives the correct answer + BEAST_EXPECT(mulRatio(big, 3, 4, false).value() == (big.value() / 4) * 3); + BEAST_EXPECT(mulRatio(big, 3, 4, true).value() == (big.value() / 4) * 3); + BEAST_EXPECT((big.value() * 3) / 4 != (big.value() / 4) * 3); } { @@ -163,7 +278,7 @@ class XRPAmount_test : public beast::unit_test::suite } { - XRPAmount big (maxUInt64); + XRPAmount big (maxXRP); auto const rup = mulRatio (big, maxUInt32 - 1, maxUInt32, true); auto const rdown = mulRatio (big, maxUInt32 - 1, maxUInt32, false); BEAST_EXPECT(rup.drops () - rdown.drops () == 1); @@ -185,8 +300,14 @@ class XRPAmount_test : public beast::unit_test::suite { // overflow - XRPAmount big (maxUInt64); - except ([&] {mulRatio (big, 2, 0, true);}); + XRPAmount big (maxXRP); + except ([&] {mulRatio (big, 2, 1, true);}); + } + + { + // underflow + XRPAmount bigNegative (minXRP + 10); + BEAST_EXPECT(mulRatio(bigNegative, 2, 1, true) == minXRP); } } @@ -198,6 +319,8 @@ class XRPAmount_test : public beast::unit_test::suite testBeastZero (); testComparisons (); testAddSub (); + testDecimal (); + testFunctions (); testMulRatio (); } }; diff --git a/src/test/jtx/Env_test.cpp b/src/test/jtx/Env_test.cpp index 907a4d88a25..86bf2db2795 100644 --- a/src/test/jtx/Env_test.cpp +++ b/src/test/jtx/Env_test.cpp @@ -79,13 +79,13 @@ class Env_test : public beast::unit_test::suite PrettyAmount(0u); PrettyAmount(1u); PrettyAmount(-1); - static_assert(! std::is_constructible< + static_assert(! std::is_trivially_constructible< PrettyAmount, char>::value, ""); - static_assert(! std::is_constructible< + static_assert(! std::is_trivially_constructible< PrettyAmount, unsigned char>::value, ""); - static_assert(! std::is_constructible< + static_assert(! std::is_trivially_constructible< PrettyAmount, short>::value, ""); - static_assert(! std::is_constructible< + static_assert(! std::is_trivially_constructible< PrettyAmount, unsigned short>::value, ""); try @@ -367,7 +367,7 @@ class Env_test : public beast::unit_test::suite env(noop("alice"), msig("carol"), fee(2 * baseFee)); env(noop("alice"), msig("bob", "carol"), fee(3 * baseFee)); env(noop("alice"), msig("bob", "carol", "dilbert"), - fee(4 * baseFee), ter(tefBAD_SIGNATURE)); + fee(4 * baseFee), ter(tefBAD_SIGNATURE)); env(signers("alice", none)); } diff --git a/src/test/jtx/amount.h b/src/test/jtx/amount.h index 1e2d99d90fb..245c42c5443 100644 --- a/src/test/jtx/amount.h +++ b/src/test/jtx/amount.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -60,11 +61,10 @@ struct None //------------------------------------------------------------------------------ -template -struct dropsPerXRP -{ - static T const value = 1000000; -}; +// This value is also defined in SystemParameters.h. It's +// duplicated here to catch any possible future errors that +// could change that value (however unlikely). +constexpr XRPAmount dropsPerXRP{ 1'000'000 }; /** Represents an XRP or IOU quantity This customizes the string conversion and supports @@ -93,8 +93,8 @@ struct PrettyAmount template PrettyAmount (T v, std::enable_if_t< sizeof(T) >= sizeof(int) && - std::is_integral::value && - std::is_signed::value>* = nullptr) + std::is_integral_v && + std::is_signed_v>* = nullptr) : amount_((v > 0) ? v : -v, v < 0) { @@ -104,8 +104,13 @@ struct PrettyAmount template PrettyAmount (T v, std::enable_if_t< sizeof(T) >= sizeof(int) && - std::is_integral::value && - std::is_unsigned::value>* = nullptr) + std::is_unsigned_v>* = nullptr) + : amount_(v) + { + } + + /** drops */ + PrettyAmount (XRPAmount v) : amount_(v) { } @@ -172,27 +177,28 @@ struct XRP_t return xrpIssue(); } - /** Returns an amount of XRP as STAmount + /** Returns an amount of XRP as PrettyAmount, + which is trivially convertable to STAmount @param v The number of XRP (not drops) */ /** @{ */ template ::value>> + std::is_integral_v>> PrettyAmount operator()(T v) const { - return { std::conditional_t< - std::is_signed::value, - std::int64_t, std::uint64_t>{v} * - dropsPerXRP::value }; + using TOut = std::conditional_t< + std::is_signed_v, + std::int64_t, std::uint64_t>; + return { TOut{v} * dropsPerXRP }; } PrettyAmount operator()(double v) const { auto const c = - dropsPerXRP::value; + dropsPerXRP.drops(); if (v >= 0) { auto const d = std::uint64_t( @@ -235,20 +241,32 @@ struct XRP_t */ extern XRP_t const XRP; -/** Returns an XRP STAmount. +/** Returns an XRP PrettyAmount, which is trivially convertible to STAmount. Example: - drops(10) Returns STAmount of 10 drops + drops(10) Returns PrettyAmount of 10 drops */ template ::value>> + std::is_integral_v>> PrettyAmount drops (Integer i) { return { i }; } +/** Returns an XRP PrettyAmount, which is trivially convertible to STAmount. + +Example: +drops(view->fee().basefee) Returns PrettyAmount of 10 drops +*/ +inline +PrettyAmount +drops (XRPAmount i) +{ + return { i }; +} + //------------------------------------------------------------------------------ namespace detail { diff --git a/src/test/jtx/impl/amount.cpp b/src/test/jtx/impl/amount.cpp index e1c4ecbcfee..16a6450663e 100644 --- a/src/test/jtx/impl/amount.cpp +++ b/src/test/jtx/impl/amount.cpp @@ -77,8 +77,7 @@ operator<< (std::ostream& os, if (amount.value().native()) { // measure in hundredths - auto const c = - dropsPerXRP::value / 100; + XRPAmount const c{dropsPerXRP.drops() / 100}; auto const n = amount.value().mantissa(); if(n < c) { @@ -89,7 +88,7 @@ operator<< (std::ostream& os, return os; } auto const d = double(n) / - dropsPerXRP::value; + dropsPerXRP.drops(); if (amount.value().negative()) os << "-"; diff --git a/src/test/jtx/impl/utility.cpp b/src/test/jtx/impl/utility.cpp index 2cc24116d50..7d70a2a220c 100644 --- a/src/test/jtx/impl/utility.cpp +++ b/src/test/jtx/impl/utility.cpp @@ -62,7 +62,7 @@ fill_fee (Json::Value& jv, { if (jv.isMember(jss::Fee)) return; - jv[jss::Fee] = std::to_string( + jv[jss::Fee] = to_string( view.fees().base); } diff --git a/src/test/ledger/CashDiff_test.cpp b/src/test/ledger/CashDiff_test.cpp index 22ed1a391d8..6c8d0f0c194 100644 --- a/src/test/ledger/CashDiff_test.cpp +++ b/src/test/ledger/CashDiff_test.cpp @@ -69,7 +69,8 @@ class CashDiff_test : public beast::unit_test::suite oldProbe = newProbe; newProbe = oldProbe * 10; e10 += 1; - } while (newProbe > oldProbe); + } while (newProbe > oldProbe && + newProbe < std::numeric_limits::max()); } { // Test XRP. @@ -92,7 +93,8 @@ class CashDiff_test : public beast::unit_test::suite oldProbe = newProbe; newProbe = oldProbe * 10; e10 += 1; - } while (newProbe > oldProbe); + } while (newProbe > oldProbe && + newProbe < std::numeric_limits::max()); } } diff --git a/src/test/ledger/Invariants_test.cpp b/src/test/ledger/Invariants_test.cpp index 9e2fe1e56e8..6ba629ca1d8 100644 --- a/src/test/ledger/Invariants_test.cpp +++ b/src/test/ledger/Invariants_test.cpp @@ -71,7 +71,7 @@ class Invariants_test : public beast::unit_test::suite ov, tx, tesSUCCESS, - env.current()->fees().base, + safe_cast(env.current()->fees().units), tapNONE, jlog }; @@ -275,13 +275,17 @@ class Invariants_test : public beast::unit_test::suite doInvariantCheck (enabled, {{ "incorrect account XRP balance" }, { "XRP net change was positive: 99999999000000001" }}, - [](Account const& A1, Account const&, ApplyContext& ac) + [this](Account const& A1, Account const&, ApplyContext& ac) { // balance exceeds genesis amount auto const sle = ac.view().peek (keylet::account(A1.id())); if(! sle) return false; - sle->setFieldAmount (sfBalance, SYSTEM_CURRENCY_START + 1); + // Use `drops(1)` to bypass a call to STAmount::canonicalize + // with an invalid value + sle->setFieldAmount (sfBalance, + INITIAL_XRP + drops(1)); + BEAST_EXPECT(!sle->getFieldAmount(sfBalance).negative()); ac.view().update (sle); return true; }); @@ -289,13 +293,14 @@ class Invariants_test : public beast::unit_test::suite doInvariantCheck (enabled, {{ "incorrect account XRP balance" }, { "XRP net change of -1000000001 doesn't match fee 0" }}, - [](Account const& A1, Account const&, ApplyContext& ac) + [this](Account const& A1, Account const&, ApplyContext& ac) { // balance is negative auto const sle = ac.view().peek (keylet::account(A1.id())); if(! sle) return false; - sle->setFieldAmount (sfBalance, -1); + sle->setFieldAmount (sfBalance, {1, true}); + BEAST_EXPECT(sle->getFieldAmount(sfBalance).negative()); ac.view().update (sle); return true; }); @@ -317,11 +322,11 @@ class Invariants_test : public beast::unit_test::suite doInvariantCheck (enabled, {{ "fee paid exceeds system limit: "s + - std::to_string(SYSTEM_CURRENCY_START) }, + to_string(INITIAL_XRP) }, { "XRP net change of 0 doesn't match fee "s + - std::to_string(SYSTEM_CURRENCY_START) }}, + to_string(INITIAL_XRP) }}, [](Account const&, Account const&, ApplyContext&) { return true; }, - XRPAmount{SYSTEM_CURRENCY_START}); + XRPAmount{INITIAL_XRP}); doInvariantCheck (enabled, {{ "fee paid is 20 exceeds fee specified in transaction." }, @@ -447,7 +452,10 @@ class Invariants_test : public beast::unit_test::suite return false; auto sleNew = std::make_shared ( keylet::escrow(A1, (*sle)[sfSequence] + 2)); - sleNew->setFieldAmount (sfAmount, SYSTEM_CURRENCY_START + 1); + // Use `drops(1)` to bypass a call to STAmount::canonicalize + // with an invalid value + sleNew->setFieldAmount (sfAmount, + INITIAL_XRP + drops(1)); ac.view().insert (sleNew); return true; }); diff --git a/src/test/rpc/AmendmentBlocked_test.cpp b/src/test/rpc/AmendmentBlocked_test.cpp index a9067c8debb..6e84c74209b 100644 --- a/src/test/rpc/AmendmentBlocked_test.cpp +++ b/src/test/rpc/AmendmentBlocked_test.cpp @@ -91,7 +91,7 @@ class AmendmentBlocked_test : public beast::unit_test::suite set_tx[jss::Account] = bob.human(); set_tx[jss::TransactionType] = jss::AccountSet; set_tx[jss::Fee] = - static_cast(8 * env.current()->fees().base); + (8 * env.current()->fees().base).jsonClipped(); set_tx[jss::Sequence] = env.seq(bob); set_tx[jss::SigningPubKey] = ""; diff --git a/src/test/rpc/NoRippleCheck_test.cpp b/src/test/rpc/NoRippleCheck_test.cpp index b28aeeb8a16..09a8df59da6 100644 --- a/src/test/rpc/NoRippleCheck_test.cpp +++ b/src/test/rpc/NoRippleCheck_test.cpp @@ -288,16 +288,23 @@ class NoRippleCheckLimits_test : public beast::unit_test::suite auto& txq = env.app().getTxQ(); auto const gw = Account {"gw" + std::to_string(i)}; env.memoize(gw); + auto const baseFee = env.current()->fees().base; env (pay (env.master, gw, XRP(1000)), seq (autofill), - fee (txq.getMetrics(*env.current()).openLedgerFeeLevel + 1), + fee (toDrops( + txq.getMetrics(*env.current()).openLedgerFeeLevel, + baseFee).second + 1), sig (autofill)); env (fset (gw, asfDefaultRipple), seq (autofill), - fee (txq.getMetrics(*env.current()).openLedgerFeeLevel + 1), + fee (toDrops( + txq.getMetrics(*env.current()).openLedgerFeeLevel, + baseFee).second + 1), sig (autofill)); env (trust (alice, gw["USD"](10)), - fee (txq.getMetrics(*env.current()).openLedgerFeeLevel + 1)); + fee (toDrops( + txq.getMetrics(*env.current()).openLedgerFeeLevel, + baseFee).second + 1)); env.close(); } diff --git a/src/test/unity/app_test_unity1.cpp b/src/test/unity/app_test_unity1.cpp index daf52b11c2b..aa68feb6c98 100644 --- a/src/test/unity/app_test_unity1.cpp +++ b/src/test/unity/app_test_unity1.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include diff --git a/src/test/unity/basics_test_unity.cpp b/src/test/unity/basics_test_unity.cpp index ab9ab552bc9..7d6800f510b 100644 --- a/src/test/unity/basics_test_unity.cpp +++ b/src/test/unity/basics_test_unity.cpp @@ -22,9 +22,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -34,3 +36,4 @@ #include #include #include +#include diff --git a/src/test/unity/protocol_test_unity.cpp b/src/test/unity/protocol_test_unity.cpp index 016770c11cd..76b7e26e62c 100644 --- a/src/test/unity/protocol_test_unity.cpp +++ b/src/test/unity/protocol_test_unity.cpp @@ -20,7 +20,6 @@ #include #include -#include #include #include #include @@ -33,4 +32,3 @@ #include #include #include -#include